diff --git a/doc/userguide/lua/libs/file.rst b/doc/userguide/lua/libs/file.rst new file mode 100644 index 0000000000..9e75c022af --- /dev/null +++ b/doc/userguide/lua/libs/file.rst @@ -0,0 +1,171 @@ +File +#### + +File information is exposed to Lua scripts with the ``suricata.file`` +library, for example:: + + local filelib = require("suricata.file") + +Setup +***** + +If your purpose is to create a logging script, initialize the script +as: + +:: + + function init (args) + local needs = {} + needs["type"] = "file" + return needs + end + +Currently the Lua file library is not implemented for rules. + +API +*** + +File Object +=========== + +File data is accessed through the file object, which must be +obtained before use:: + + local file, err = filelib.get_file() + if file == nil then + print(err) + end + +File Methods +============ + +``file_id()`` +------------- + +Returns the ID number of the file. + +Example:: + + local file = filelib.get_file() + local id = file:file_id() + print("File ID: " .. id) + +``tx_id()`` +----------- + +Returns the transaction ID associated with the file. + +Example:: + + local file = filelib.get_file() + local tx_id = file:tx_id() + print("Transaction ID: " .. tx_id) + +``name()`` +---------- + +Returns the file name. + +Example:: + + local file = filelib.get_file() + local name = file:name() + if name ~= nil then + print("Filename: " .. name) + end + +``size()`` +---------- + +Returns the file size. + +Example:: + + local file = filelib.get_file() + local size = file:size() + print("File size: " .. size .. " bytes") + +``magic()`` +----------- + +Returns the file type based on libmagic (if available). Will return +nil if magic is not available. + +Example:: + + local file = filelib.get_file() + local magic = file:magic() + if magic ~= nil then + print("File type: " .. magic) + end + +``md5()`` +--------- + +Returns the MD5 hash of the file (if calculated). Will return nil if +the MD5 hash was not calculated. + +Example:: + + local file = filelib.get_file() + local md5 = file:md5() + if md5 ~= nil then + print("MD5: " .. md5) + end + +``sha1()`` +---------- + +Returns the SHA1 hash of the file (if calculated). Will return nil if +the SHA1 hash was not calculated. + +Example:: + + local file = filelib.get_file() + local sha1 = file:sha1() + if sha1 ~= nil then + print("SHA1: " .. sha1) + end + +``sha256()`` +------------ + +Returns the SHA256 hash of the file (if calculated). Will return nil +if the SHA256 hash was not calculated. + +Example:: + + local file = filelib.get_file() + local sha256 = file:sha256() + if sha256 ~= nil then + print("SHA256: " .. sha256) + end + +``get_state()`` +--------------- + +Returns the current state of the file. + +Returns: + +- State: "CLOSED", "TRUNCATED", "ERROR", "OPENED", "NONE", or + "UNKNOWN" + +Example:: + + local file = filelib.get_file() + local state = file:get_state() + if state ~= nil then + print("File state: " .. state) + end + +``is_stored()`` +--------------- + +Returns true if the file has been stored to disk, false otherwise. + +Example:: + + local file = filelib.get_file() + local stored = file:is_stored() + print("File stored: " .. tostring(stored)) diff --git a/doc/userguide/lua/libs/index.rst b/doc/userguide/lua/libs/index.rst index aea48e030e..ec42f0b15d 100644 --- a/doc/userguide/lua/libs/index.rst +++ b/doc/userguide/lua/libs/index.rst @@ -10,6 +10,7 @@ environment without access to additional modules. base64 dns + file flowlib flowint flowvar diff --git a/doc/userguide/lua/lua-functions.rst b/doc/userguide/lua/lua-functions.rst index f8c521937d..dadbb534a1 100644 --- a/doc/userguide/lua/lua-functions.rst +++ b/doc/userguide/lua/lua-functions.rst @@ -348,39 +348,6 @@ Example: end end -Files ------ - -To use the file logging API, the script's init() function needs to look like: - -:: - - function init (args) - local needs = {} - needs['type'] = 'file' - return needs - end - -SCFileInfo -~~~~~~~~~~ - -:: - - - fileid, txid, name, size, magic, md5, sha1, sha256 = SCFileInfo() - -returns fileid (number), txid (number), name (string), size (number), -magic (string), md5 in hex (string), sha1 (string), sha256 (string) - -SCFileState -~~~~~~~~~~~ - -:: - - state, stored = SCFileState() - -returns state (string), stored (bool) - Streaming Data -------------- diff --git a/src/Makefile.am b/src/Makefile.am index 90ddedd816..8c4db1df66 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -535,6 +535,7 @@ noinst_HEADERS = \ util-lua-dnp3-objects.h \ util-lua-dnp3.h \ util-lua-dns.h \ + util-lua-filelib.h \ util-lua-flowintlib.h \ util-lua-flowlib.h \ util-lua-flowvarlib.h \ @@ -1105,6 +1106,7 @@ libsuricata_c_a_SOURCES = \ util-lua-dnp3-objects.c \ util-lua-dnp3.c \ util-lua-dns.c \ + util-lua-filelib.c \ util-lua-flowintlib.c \ util-lua-flowlib.c \ util-lua-flowvarlib.c \ diff --git a/src/util-lua-builtins.c b/src/util-lua-builtins.c index e4228e9924..dc6a5053f2 100644 --- a/src/util-lua-builtins.c +++ b/src/util-lua-builtins.c @@ -31,6 +31,7 @@ #include "util-lua-packetlib.h" #include "util-lua-rule.h" #include "util-lua-ja3.h" +#include "util-lua-filelib.h" #include "lauxlib.h" @@ -39,6 +40,7 @@ static const luaL_Reg builtins[] = { { "suricata.dataset", LuaLoadDatasetLib }, { "suricata.dnp3", SCLuaLoadDnp3Lib }, { "suricata.dns", SCLuaLoadDnsLib }, + { "suricata.file", SCLuaLoadFileLib }, { "suricata.flow", LuaLoadFlowLib }, { "suricata.flowint", LuaLoadFlowintLib }, { "suricata.flowvar", LuaLoadFlowvarLib }, diff --git a/src/util-lua-common.c b/src/util-lua-common.c index 944b384d6d..4082c0e1cc 100644 --- a/src/util-lua-common.c +++ b/src/util-lua-common.c @@ -206,133 +206,6 @@ static int LuaCallbackLogError(lua_State *luastate) return 0; } -/** \internal - * \brief fill lua stack with file info - * \param luastate the lua state - * \param pa pointer to packet alert struct - * \retval cnt number of data items placed on the stack - * - * Places: fileid (number), txid (number), name (string), - * size (number), magic (string), md5 in hex (string), - * sha1 (string), sha256 (string) - */ -static int LuaCallbackFileInfoPushToStackFromFile(lua_State *luastate, const File *file) -{ - char *md5ptr = NULL; - char *sha1ptr = NULL; - char *sha256ptr = NULL; - - char md5[33] = ""; - md5ptr = md5; - if (file->flags & FILE_MD5) { - size_t x; - for (x = 0; x < sizeof(file->md5); x++) { - char one[3] = ""; - snprintf(one, sizeof(one), "%02x", file->md5[x]); - strlcat(md5, one, sizeof(md5)); - } - } - char sha1[41] = ""; - sha1ptr = sha1; - if (file->flags & FILE_SHA1) { - size_t x; - for (x = 0; x < sizeof(file->sha1); x++) { - char one[3] = ""; - snprintf(one, sizeof(one), "%02x", file->sha1[x]); - strlcat(sha1, one, sizeof(sha1)); - } - } - char sha256[65] = ""; - sha256ptr = sha256; - if (file->flags & FILE_SHA256) { - size_t x; - for (x = 0; x < sizeof(file->sha256); x++) { - char one[3] = ""; - snprintf(one, sizeof(one), "%02x", file->sha256[x]); - strlcat(sha256, one, sizeof(sha256)); - } - } - - lua_Integer tx_id = LuaStateGetTxId(luastate); - lua_pushinteger(luastate, file->file_store_id); - lua_pushinteger(luastate, tx_id); - lua_pushlstring(luastate, (char *)file->name, file->name_len); - lua_pushinteger(luastate, FileTrackedSize(file)); - lua_pushstring (luastate, -#ifdef HAVE_MAGIC - file->magic -#else - "nomagic" -#endif - ); - lua_pushstring(luastate, md5ptr); - lua_pushstring(luastate, sha1ptr); - lua_pushstring(luastate, sha256ptr); - return 8; -} - -/** \internal - * \brief Wrapper for getting tuple info into a lua script - * \retval cnt number of items placed on the stack - */ -static int LuaCallbackFileInfo(lua_State *luastate) -{ - const File *file = LuaStateGetFile(luastate); - if (file == NULL) - return LuaCallbackError(luastate, "internal error: no file"); - - return LuaCallbackFileInfoPushToStackFromFile(luastate, file); -} - -/** \internal - * \brief fill lua stack with file info - * \param luastate the lua state - * \param pa pointer to packet alert struct - * \retval cnt number of data items placed on the stack - * - * Places: state (string), stored (bool) - */ -static int LuaCallbackFileStatePushToStackFromFile(lua_State *luastate, const File *file) -{ - const char *state = "UNKNOWN"; - switch (file->state) { - case FILE_STATE_CLOSED: - state = "CLOSED"; - break; - case FILE_STATE_TRUNCATED: - state = "TRUNCATED"; - break; - case FILE_STATE_ERROR: - state = "ERROR"; - break; - case FILE_STATE_OPENED: - state = "OPENED"; - break; - case FILE_STATE_NONE: - state = "NONE"; - break; - case FILE_STATE_MAX: - break; - } - - lua_pushstring (luastate, state); - lua_pushboolean (luastate, file->flags & FILE_STORED); - return 2; -} - -/** \internal - * \brief Wrapper for getting tuple info into a lua script - * \retval cnt number of items placed on the stack - */ -static int LuaCallbackFileState(lua_State *luastate) -{ - const File *file = LuaStateGetFile(luastate); - if (file == NULL) - return LuaCallbackError(luastate, "internal error: no file"); - - return LuaCallbackFileStatePushToStackFromFile(luastate, file); -} - /** \internal * \brief fill lua stack with thread info * \param luastate the lua state @@ -383,11 +256,6 @@ int LuaRegisterFunctions(lua_State *luastate) lua_pushcfunction(luastate, LuaCallbackLogError); lua_setglobal(luastate, "SCLogError"); - lua_pushcfunction(luastate, LuaCallbackFileInfo); - lua_setglobal(luastate, "SCFileInfo"); - lua_pushcfunction(luastate, LuaCallbackFileState); - lua_setglobal(luastate, "SCFileState"); - lua_pushcfunction(luastate, LuaCallbackThreadInfo); lua_setglobal(luastate, "SCThreadInfo"); return 0; diff --git a/src/util-lua-filelib.c b/src/util-lua-filelib.c new file mode 100644 index 0000000000..ab1f3a71da --- /dev/null +++ b/src/util-lua-filelib.c @@ -0,0 +1,227 @@ +/* Copyright (C) 2025 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "suricata-common.h" +#include "util-lua.h" +#include "util-lua-common.h" +#include "util-lua-filelib.h" + +#include "lua.h" +#include "lauxlib.h" + +static const char file_mt[] = "suricata:file:mt"; + +struct LuaFile { + File *file; +}; + +static int LuaFileGetFile(lua_State *L) +{ + File *file = LuaStateGetFile(L); + if (file == NULL) { + return LuaCallbackError(L, "error: no file found"); + } + + struct LuaFile *lua_file = (struct LuaFile *)lua_newuserdata(L, sizeof(*lua_file)); + if (lua_file == NULL) { + return LuaCallbackError(L, "error: fail to allocate user data"); + } + lua_file->file = file; + + luaL_getmetatable(L, file_mt); + lua_setmetatable(L, -2); + + return 1; +} + +static int LuaFileGetFileId(lua_State *L) +{ + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + lua_pushinteger(L, file->file_store_id); + + return 1; +} + +static int LuaFileGetTxId(lua_State *L) +{ + lua_Integer tx_id = LuaStateGetTxId(L); + lua_pushinteger(L, tx_id); + + return 1; +} + +static int LuaFileGetName(lua_State *L) +{ + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + lua_pushlstring(L, (char *)file->name, file->name_len); + + return 1; +} + +static int LuaFileGetSize(lua_State *L) +{ + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + lua_pushinteger(L, FileTrackedSize(file)); + + return 1; +} + +static int LuaFileGetMagic(lua_State *L) +{ +#ifdef HAVE_MAGIC + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + if (file->magic != NULL) { + lua_pushstring(L, file->magic); + } else { + lua_pushnil(L); + } +#else + lua_pushnil(L); +#endif + + return 1; +} + +static void PushHex(lua_State *L, const uint8_t *buf, size_t len) +{ + /* Large enough for sha256. */ + char hex[65] = ""; + for (size_t i = 0; i < len; i++) { + char one[3] = ""; + snprintf(one, sizeof(one), "%02x", buf[i]); + strlcat(hex, one, sizeof(hex)); + } + + lua_pushstring(L, hex); +} + +static int LuaFileGetMd5(lua_State *L) +{ + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + + if (file->flags & FILE_MD5) { + PushHex(L, file->md5, sizeof(file->md5)); + } else { + lua_pushnil(L); + } + + return 1; +} + +static int LuaFileGetSha1(lua_State *L) +{ + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + + if (file->flags & FILE_SHA1) { + PushHex(L, file->sha1, sizeof(file->sha1)); + } else { + lua_pushnil(L); + } + + return 1; +} + +static int LuaFileGetSha256(lua_State *L) +{ + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + + if (file->flags & FILE_SHA256) { + PushHex(L, file->sha256, sizeof(file->sha256)); + } else { + lua_pushnil(L); + } + + return 1; +} + +static int LuaFileGetState(lua_State *L) +{ + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + + const char *state = "UNKNOWN"; + switch (file->state) { + case FILE_STATE_CLOSED: + state = "CLOSED"; + break; + case FILE_STATE_TRUNCATED: + state = "TRUNCATED"; + break; + case FILE_STATE_ERROR: + state = "ERROR"; + break; + case FILE_STATE_OPENED: + state = "OPENED"; + break; + case FILE_STATE_NONE: + state = "NONE"; + break; + case FILE_STATE_MAX: + break; + } + + lua_pushstring(L, state); + + return 1; +} + +static int LuaFileIsStored(lua_State *L) +{ + struct LuaFile *lua_file = luaL_checkudata(L, 1, file_mt); + const File *file = lua_file->file; + lua_pushboolean(L, file->flags & FILE_STORED); + + return 1; +} + +static const struct luaL_Reg filelib[] = { + { "get_state", LuaFileGetState }, + { "is_stored", LuaFileIsStored }, + { "file_id", LuaFileGetFileId }, + { "tx_id", LuaFileGetTxId }, + { "name", LuaFileGetName }, + { "size", LuaFileGetSize }, + { "magic", LuaFileGetMagic }, + { "md5", LuaFileGetMd5 }, + { "sha1", LuaFileGetSha1 }, + { "sha256", LuaFileGetSha256 }, + { NULL, NULL }, +}; + +static const struct luaL_Reg filemodlib[] = { + { "get_file", LuaFileGetFile }, + { NULL, NULL }, +}; + +int SCLuaLoadFileLib(lua_State *L) +{ + luaL_newmetatable(L, file_mt); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, filelib, 0); + + luaL_newlib(L, filemodlib); + + return 1; +} diff --git a/src/util-lua-filelib.h b/src/util-lua-filelib.h new file mode 100644 index 0000000000..9a498dc636 --- /dev/null +++ b/src/util-lua-filelib.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2025 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef SURICATA_UTIL_LUA_FILELIB_H +#define SURICATA_UTIL_LUA_FILELIB_H + +#include "lua.h" + +int SCLuaLoadFileLib(lua_State *L); + +#endif /* SURICATA_UTIL_LUA_FILELIB_H */