From: Jason Ish Date: Tue, 6 May 2025 21:58:57 +0000 (-0600) Subject: lua: convert SMTP functions to lib: suricata.smtp X-Git-Tag: suricata-8.0.0-rc1~336 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F13183%2Fhead;p=thirdparty%2Fsuricata.git lua: convert SMTP functions to lib: suricata.smtp Ticket: #7606 --- diff --git a/doc/userguide/lua/libs/index.rst b/doc/userguide/lua/libs/index.rst index b6555bdfa7..aea48e030e 100644 --- a/doc/userguide/lua/libs/index.rst +++ b/doc/userguide/lua/libs/index.rst @@ -17,5 +17,6 @@ environment without access to additional modules. http packetlib rule + smtp ssh ja3 diff --git a/doc/userguide/lua/libs/smtp.rst b/doc/userguide/lua/libs/smtp.rst new file mode 100644 index 0000000000..10ccccd85b --- /dev/null +++ b/doc/userguide/lua/libs/smtp.rst @@ -0,0 +1,105 @@ +SMTP +#### + +.. role:: example-rule-emphasis + +SMTP transaction details are exposed to Lua scripts with the +``suricata.smtp`` library, for example:: + + local smtp = require("suricata.smtp") + +Setup +***** + +If your purpose is to create a logging script, initialize the buffer as: + +:: + + function init (args) + local needs = {} + needs["protocol"] = "smtp" + return needs + end + +Otherwise if a detection script:: + + function init (args) + return {} + end + +API +*** + +Transaction +=========== + +SMTP is transaction based, and the current transaction must be +obtained before use:: + + local tx, err = smtp.get_tx() + if tx == nil then + print(err) + end + +All other functions are methods on the transaction table. + +Transaction Methods +=================== + +``get_mime_field(name)`` +------------------------ + +Get a specific MIME header field by name from the SMTP transaction. + +Example:: + + local tx = smtp.get_tx() + local encoding = tx:get_mime_field("Content-Transfer-Encoding") + if encoding ~= nil then + print("Encoding: " .. subject) + end + +``get_mime_list()`` +------------------- + +Get all the MIME header field names from the SMTP transaction as a +table. + +Example:: + + local tx = smtp.get_tx() + local mime_fields = tx:get_mime_list() + if mime_fields ~= nil then + for i, name in pairs(mime_fields) do + local value = tx:get_mime_field(name) + print(name .. ": " .. value) + end + end + +``get_mail_from()`` +------------------- + +Get the sender email address from the MAIL FROM command. + +Example:: + + local tx = smtp.get_tx() + local mail_from = tx:get_mail_from() + if mail_from ~= nil then + print("Sender: " .. mail_from) + end + +``get_rcpt_list()`` +------------------- + +Get all recipient email addresses from RCPT TO commands as a table. + +Example:: + + local tx = smtp.get_tx() + local recipients = tx:get_rcpt_list() + if recipients ~= nil then + for i, recipient in pairs(recipients) do + print("Recipient " .. i .. ": " .. recipient) + end + end diff --git a/rust/src/mime/smtp.rs b/rust/src/mime/smtp.rs index 71dd477100..74d05e96a4 100644 --- a/rust/src/mime/smtp.rs +++ b/rust/src/mime/smtp.rs @@ -677,8 +677,11 @@ pub unsafe extern "C" fn SCMimeSmtpGetHeader( buffer_len: *mut u32, ) -> bool { let name: &CStr = CStr::from_ptr(str); //unsafe + + // Convert to lowercase, mime::slice_equals_lowercase expects it. + let name: Vec = name.to_bytes().iter().map(|b| b.to_ascii_lowercase()).collect(); for h in &ctx.headers[ctx.main_headers_nb..] { - if mime::slice_equals_lowercase(&h.name, name.to_bytes()) { + if mime::slice_equals_lowercase(&h.name, &name) { *buffer = h.value.as_ptr(); *buffer_len = h.value.len() as u32; return true; diff --git a/src/detect-lua-extensions.c b/src/detect-lua-extensions.c index c55511c52b..75619c391d 100644 --- a/src/detect-lua-extensions.c +++ b/src/detect-lua-extensions.c @@ -121,6 +121,5 @@ int LuaRegisterExtensions(lua_State *lua_state) LuaRegisterFunctions(lua_state); LuaRegisterTlsFunctions(lua_state); - LuaRegisterSmtpFunctions(lua_state); return 0; } diff --git a/src/output-lua.c b/src/output-lua.c index 5c087044e1..3fec4b1561 100644 --- a/src/output-lua.c +++ b/src/output-lua.c @@ -588,7 +588,6 @@ static lua_State *LuaScriptSetup(const char *filename, LogLuaMasterCtx *ctx) /* register functions common to all */ LuaRegisterFunctions(luastate); LuaRegisterTlsFunctions(luastate); - LuaRegisterSmtpFunctions(luastate); if (lua_pcall(luastate, 0, 0, 0) != 0) { SCLogError("couldn't run script 'setup' function: %s", lua_tostring(luastate, -1)); diff --git a/src/util-lua-builtins.c b/src/util-lua-builtins.c index 1534b64939..e4228e9924 100644 --- a/src/util-lua-builtins.c +++ b/src/util-lua-builtins.c @@ -24,6 +24,7 @@ #include "util-lua-flowvarlib.h" #include "util-lua-http.h" #include "util-lua-dns.h" +#include "util-lua-smtp.h" #include "util-lua-ssh.h" #include "util-lua-flowlib.h" #include "util-lua-hashlib.h" @@ -46,6 +47,7 @@ static const luaL_Reg builtins[] = { { "suricata.ja3", SCLuaLoadJa3Lib }, { "suricata.packet", LuaLoadPacketLib }, { "suricata.rule", SCLuaLoadRuleLib }, + { "suricata.smtp", SCLuaLoadSmtpLib }, { "suricata.ssh", SCLuaLoadSshLib }, { NULL, NULL }, }; diff --git a/src/util-lua-smtp.c b/src/util-lua-smtp.c index f64bb88c5a..9f2cae2040 100644 --- a/src/util-lua-smtp.c +++ b/src/util-lua-smtp.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Open Information Security Foundation +/* Copyright (C) 2014-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 @@ -26,306 +26,149 @@ */ #include "suricata-common.h" - -#include "conf.h" - -#include "threads.h" -#include "threadvars.h" -#include "tm-threads.h" -#include "output.h" - #include "app-layer-smtp.h" -#include "lua.h" -#include "lualib.h" - #include "util-lua.h" #include "util-lua-common.h" #include "util-lua-smtp.h" -#include "util-file.h" -/* - * \brief internal function used by SMTPGetMimeField - * - * \param luastate luastate stack to use and push attributes to - * \param flow network flow of SMTP packets - * \param name name of the attribute to extract from MimeDecField - * - * \retval 1 if success mimefield found and pushed to stack. Returns error - * int and msg pushed to luastate stack if error occurs. - */ +#include "lua.h" +#include "lauxlib.h" + +static const char smtp_tx_mt[] = "suricata:smtp:tx"; -static int GetMimeDecField(lua_State *luastate, Flow *flow, const char *name) +struct LuaSmtpTx { + SMTPTransaction *tx; +}; + +static int LuaSmtpGetTx(lua_State *L) { - /* extract state from flow */ - SMTPState *state = (SMTPState *) FlowGetAppState(flow); - /* check that state exists */ - if(state == NULL) { - return LuaCallbackError(luastate, "Internal error: no state in flow"); + if (!(LuaStateNeedProto(L, ALPROTO_SMTP))) { + return LuaCallbackError(L, "error: protocol not SMTP"); } - /* pointer to current transaction in state */ - SMTPTransaction *smtp_tx = state->curr_tx; - if(smtp_tx == NULL) { - return LuaCallbackError(luastate, "Transaction ending or not found"); + + Flow *flow = LuaStateGetFlow(L); + if (flow == NULL) { + return LuaCallbackError(L, "error: no flow found"); } - /* pointer to tail of msg list of MimeStateSMTP in current transaction. */ - MimeStateSMTP *mime = smtp_tx->mime_state; - /* check if msg_tail was hit */ - if(mime == NULL){ - return LuaCallbackError(luastate, "Internal error: no fields in transaction"); + + SMTPState *state = (SMTPState *)FlowGetAppState(flow); + if (state == NULL) { + return LuaCallbackError(L, "error: no SMTP state"); } - /* extract MIME field based on specific field name. */ - const uint8_t *field_value; - uint32_t field_len; - /* check MIME field */ - if (!SCMimeSmtpGetHeader(mime, name, &field_value, &field_len)) { - return LuaCallbackError(luastate, "Error: mimefield not found"); + + SMTPTransaction *tx = state->curr_tx; + if (tx == NULL) { + return LuaCallbackError(L, "error: no SMTP transaction found"); } - if (field_len == 0) { - return LuaCallbackError(luastate, "Error, pointer error"); + + struct LuaSmtpTx *lua_tx = (struct LuaSmtpTx *)lua_newuserdata(L, sizeof(*lua_tx)); + if (lua_tx == NULL) { + return LuaCallbackError(L, "error: fail to allocate user data"); } - return LuaPushStringBuffer(luastate, field_value, field_len); -} + lua_tx->tx = tx; -/** - * \brief Function extracts specific MIME field based on argument from luastate - * stack then pushing the attribute onto the luastate stack. - * - * \param luastate luastate stack to pop and push attributes for I/O to lua - * - * \retval 1 if success mimefield found and pushed to stack. Returns error - * int and msg pushed to luastate stack if error occurs. - */ + luaL_getmetatable(L, smtp_tx_mt); + lua_setmetatable(L, -2); + + return 1; +} -static int SMTPGetMimeField(lua_State *luastate) +static int LuaSmtpTxGetMimeField(lua_State *L) { - if(!(LuaStateNeedProto(luastate, ALPROTO_SMTP))) { - return LuaCallbackError(luastate, "error: protocol not SMTP"); + struct LuaSmtpTx *tx = luaL_checkudata(L, 1, smtp_tx_mt); + + if (tx->tx->mime_state == NULL) { + return LuaCallbackError(L, "no mime state"); } - Flow *flow = LuaStateGetFlow(luastate); - /* check that flow exist */ - if(flow == NULL) { - return LuaCallbackError(luastate, "Error: no flow found"); + + const char *name = luaL_checkstring(L, 2); + if (name == NULL) { + return LuaCallbackError(L, "2nd argument missing, empty or wrong type"); } - const char *name = LuaGetStringArgument(luastate, 1); - if (name == NULL) - return LuaCallbackError(luastate, "1st argument missing, empty or wrong type"); - GetMimeDecField(luastate, flow, name); + const uint8_t *field_value; + uint32_t field_len; + if (SCMimeSmtpGetHeader(tx->tx->mime_state, name, &field_value, &field_len)) { + return LuaPushStringBuffer(L, field_value, field_len); + } - return 1; + return LuaCallbackError(L, "request mime field not found"); } -/** - * \brief Internal function used by SMTPGetMimeList - * - * \param luastate luastate stack to pop and push attributes for I/O to lua - * \param flow network flow of SMTP packets - * - * \retval 1 if the mimelist table is pushed to luastate stack. - * Returns error int and msg pushed to luastate stack if error occurs. -*/ - -static int GetMimeList(lua_State *luastate, Flow *flow) +static int LuaSmtpTxGetMimeList(lua_State *L) { + struct LuaSmtpTx *tx = luaL_checkudata(L, 1, smtp_tx_mt); - SMTPState *state = (SMTPState *) FlowGetAppState(flow); - if(state == NULL) { - return LuaCallbackError(luastate, "Error: no SMTP state"); - } - /* Create a pointer to the current SMTPtransaction */ - SMTPTransaction *smtp_tx = state->curr_tx; - if(smtp_tx == NULL) { - return LuaCallbackError(luastate, "Error: no SMTP transaction found"); - } - /* Create a pointer to the tail of MimeStateSMTP list */ - MimeStateSMTP *mime = smtp_tx->mime_state; - if(mime == NULL) { - return LuaCallbackError(luastate, "Error: no mime entity found"); + if (tx->tx->mime_state == NULL) { + return LuaCallbackError(L, "no mime state"); } + const uint8_t *field_name; uint32_t field_len; - /* Counter of MIME fields found */ int num = 1; - /* loop trough the list of mimeFields, printing each name found */ - lua_newtable(luastate); - while (SCMimeSmtpGetHeaderName(mime, &field_name, &field_len, (uint32_t)num)) { + lua_newtable(L); + while (SCMimeSmtpGetHeaderName(tx->tx->mime_state, &field_name, &field_len, (uint32_t)num)) { if (field_len != 0) { - lua_pushinteger(luastate,num++); - LuaPushStringBuffer(luastate, field_name, field_len); - lua_settable(luastate,-3); + lua_pushinteger(L, num++); + LuaPushStringBuffer(L, field_name, field_len); + lua_settable(L, -3); } } return 1; } -/** - * \brief Lists name and value to all MIME fields which - * is included in a SMTP transaction. - * - * \param luastate luastate stack to pop and push attributes for I/O to lua. - * - * \retval 1 if the table is pushed to lua. - * Returns error int and msg pushed to luastate stack if error occurs - * - */ - -static int SMTPGetMimeList(lua_State *luastate) +static int LuaSmtpTxGetMailFrom(lua_State *L) { - /* Check if right protocol */ - if(!(LuaStateNeedProto(luastate, ALPROTO_SMTP))) { - return LuaCallbackError(luastate, "Error: protocol not SMTP"); - } - /* Extract network flow */ - Flow *flow = LuaStateGetFlow(luastate); - if(flow == NULL) { - return LuaCallbackError(luastate, "Error: no flow found"); - } + struct LuaSmtpTx *tx = luaL_checkudata(L, 1, smtp_tx_mt); - GetMimeList(luastate, flow); - - return 1; -} - -/** - * \brief internal function used by SMTPGetMailFrom - * - * \param luastate luastate stack to pop and push attributes for I/O to lua. - * \param flow flow to get state for SMTP - * - * \retval 1 if mailfrom field found. - * Returns error int and msg pushed to luastate stack if error occurs - */ - -static int GetMailFrom(lua_State *luastate, Flow *flow) -{ - /* Extract SMTPstate from current flow */ - SMTPState *state = (SMTPState *) FlowGetAppState(flow); - - if(state == NULL) { - return LuaCallbackError(luastate, "Internal Error: no state"); - } - SMTPTransaction *smtp_tx = state->curr_tx; - if(smtp_tx == NULL) { - return LuaCallbackError(luastate, "Internal Error: no SMTP transaction"); - } - if(smtp_tx->mail_from == NULL || smtp_tx->mail_from_len == 0) { - return LuaCallbackError(luastate, "MailFrom not found"); - } - return LuaPushStringBuffer(luastate, smtp_tx->mail_from, smtp_tx->mail_from_len); - /* Returns 1 because we never push more then 1 item to the lua stack */ -} - -/** - * \brief Extracts mail_from parameter from SMTPState. - * Attribute may also be available from mimefields, although there is no - * guarantee of it existing as mime. - * - * \param luastate luastate stack to pop and push attributes for I/O to lua. - * - * \retval 1 if mailfrom field found. - * Returns error int and msg pushed to luastate stack if error occurs - */ - -static int SMTPGetMailFrom(lua_State *luastate) -{ - /* check protocol */ - if(!(LuaStateNeedProto(luastate, ALPROTO_SMTP))) { - return LuaCallbackError(luastate, "Error: protocol not SMTP"); - } - /* Extract flow, with lockhint to check mutexlocking */ - Flow *flow = LuaStateGetFlow(luastate); - if(flow == NULL) { - return LuaCallbackError(luastate, "Internal Error: no flow"); + if (tx->tx->mail_from == NULL || tx->tx->mail_from_len == 0) { + lua_pushnil(L); + return 1; } - GetMailFrom(luastate, flow); - - return 1; + return LuaPushStringBuffer(L, tx->tx->mail_from, tx->tx->mail_from_len); } -/** - * \brief intern function used by SMTPGetRcpList - * - * \param luastate luastate stack for internal communication with Lua. - * Used to hand over data to the receiving luascript. - * - * \retval 1 if the table is pushed to lua. - * Returns error int and msg pushed to luastate stack if error occurs - */ - -static int GetRcptList(lua_State *luastate, Flow *flow) +static int LuaSmtpTxGetRcptList(lua_State *L) { - - SMTPState *state = (SMTPState *) FlowGetAppState(flow); - if(state == NULL) { - return LuaCallbackError(luastate, "Internal error, no state"); - } - - SMTPTransaction *smtp_tx = state->curr_tx; - if(smtp_tx == NULL) { - return LuaCallbackError(luastate, "No more tx, or tx not found"); - } + struct LuaSmtpTx *tx = luaL_checkudata(L, 1, smtp_tx_mt); /* Create a new table in luastate for rcpt list */ - lua_newtable(luastate); + lua_newtable(L); /* rcpt var for iterator */ int u = 1; SMTPString *rcpt; - TAILQ_FOREACH(rcpt, &smtp_tx->rcpt_to_list, next) { - lua_pushinteger(luastate, u++); - LuaPushStringBuffer(luastate, rcpt->str, rcpt->len); - lua_settable(luastate, -3); - } - /* return 1 since we always push one table to luastate */ - return 1; -} - -/** - * \brief function loops through rcpt-list located in - * flow->SMTPState->SMTPTransaction, adding all items to a table. - * Then pushing it to the luastate stack. - * - * \param luastate luastate stack for internal communication with Lua. - * Used to hand over data to the receiving luascript. - * - * \retval 1 if the table is pushed to lua. - * Returns error int and msg pushed to luastate stack if error occurs - */ - -static int SMTPGetRcptList(lua_State *luastate) -{ - /* check protocol */ - if(!(LuaStateNeedProto(luastate, ALPROTO_SMTP))) { - return LuaCallbackError(luastate, "Error: protocol not SMTP"); - } - /* Extract flow, with lockhint to check mutexlocking */ - Flow *flow = LuaStateGetFlow(luastate); - if(flow == NULL) { - return LuaCallbackError(luastate, "Internal error: no flow"); + TAILQ_FOREACH (rcpt, &tx->tx->rcpt_to_list, next) { + lua_pushinteger(L, u++); + LuaPushStringBuffer(L, rcpt->str, rcpt->len); + lua_settable(L, -3); } - GetRcptList(luastate, flow); - - /* return 1 since we always push one table to luastate */ return 1; } -int LuaRegisterSmtpFunctions(lua_State *luastate) -{ - - lua_pushcfunction(luastate, SMTPGetMimeField); - lua_setglobal(luastate, "SMTPGetMimeField"); +static const struct luaL_Reg smtptxlib[] = { + { "get_mime_field", LuaSmtpTxGetMimeField }, + { "get_mime_list", LuaSmtpTxGetMimeList }, + { "get_mail_from", LuaSmtpTxGetMailFrom }, + { "get_rcpt_list", LuaSmtpTxGetRcptList }, + { NULL, NULL }, +}; - lua_pushcfunction(luastate, SMTPGetMimeList); - lua_setglobal(luastate, "SMTPGetMimeList"); +static const struct luaL_Reg smtplib[] = { + { "get_tx", LuaSmtpGetTx }, + { NULL, NULL }, +}; - lua_pushcfunction(luastate, SMTPGetMailFrom); - lua_setglobal(luastate, "SMTPGetMailFrom"); - - lua_pushcfunction(luastate, SMTPGetRcptList); - lua_setglobal(luastate, "SMTPGetRcptList"); +int SCLuaLoadSmtpLib(lua_State *L) +{ + luaL_newmetatable(L, smtp_tx_mt); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, smtptxlib, 0); - return 0; + luaL_newlib(L, smtplib); + return 1; } diff --git a/src/util-lua-smtp.h b/src/util-lua-smtp.h index 8172a663f9..1d26019f23 100644 --- a/src/util-lua-smtp.h +++ b/src/util-lua-smtp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Open Information Security Foundation +/* Copyright (C) 2014-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 @@ -18,6 +18,8 @@ #ifndef SURICATA_UTIL_LUA_SMTP_H #define SURICATA_UTIL_LUA_SMTP_H -int LuaRegisterSmtpFunctions(lua_State *luastate); +#include "lua.h" + +int SCLuaLoadSmtpLib(lua_State *L); #endif /* SURICATA_UTIL_LUA_SMTP_H */