]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
lua: convert SMTP functions to lib: suricata.smtp 13183/head
authorJason Ish <jason.ish@oisf.net>
Tue, 6 May 2025 21:58:57 +0000 (15:58 -0600)
committerVictor Julien <victor@inliniac.net>
Wed, 7 May 2025 19:01:01 +0000 (21:01 +0200)
Ticket: #7606

doc/userguide/lua/libs/index.rst
doc/userguide/lua/libs/smtp.rst [new file with mode: 0644]
rust/src/mime/smtp.rs
src/detect-lua-extensions.c
src/output-lua.c
src/util-lua-builtins.c
src/util-lua-smtp.c
src/util-lua-smtp.h

index b6555bdfa7200881eb01ee89dfaf89618a0b6473..aea48e030e29aa79b1aa1e1de0477b9f88dbec24 100644 (file)
@@ -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 (file)
index 0000000..10ccccd
--- /dev/null
@@ -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
index 71dd4771008e6542c7df59d0679d20873d6407c1..74d05e96a44ff951120d65193b0b1e0781a5a212 100644 (file)
@@ -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<u8> = 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;
index c55511c52bf63e73d0e913e4de2e49b7827fb3a9..75619c391d8cb16c0470edc6f7c09bbbb79cf2d1 100644 (file)
@@ -121,6 +121,5 @@ int LuaRegisterExtensions(lua_State *lua_state)
 
     LuaRegisterFunctions(lua_state);
     LuaRegisterTlsFunctions(lua_state);
-    LuaRegisterSmtpFunctions(lua_state);
     return 0;
 }
index 5c087044e13aa821d4c5b358d5852ada25b8b404..3fec4b15619751b82532701b67b25a8ffd154b15 100644 (file)
@@ -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));
index 1534b649397e30aac0020654c1dbc4f4a84cd5b4..e4228e9924abe066599ef176631d005233a6c278 100644 (file)
@@ -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 },
 };
index f64bb88c5afe9efe84e433f911bff5b0d3236f26..9f2cae204039670aa138f6bae248b6e6df5afd9f 100644 (file)
@@ -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
  */
 
 #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;
 }
index 8172a663f965327222031a97212ac506f8634b99..1d26019f2345353d6db7f3e3a29eb71c23efb8f8 100644 (file)
@@ -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 */