lua: convert SMTP functions to lib: suricata.smtp

Ticket: #7606
pull/13183/head
Jason Ish 4 months ago committed by Victor Julien
parent e499a98ba9
commit 97eaeef7d8

@ -17,5 +17,6 @@ environment without access to additional modules.
http http
packetlib packetlib
rule rule
smtp
ssh ssh
ja3 ja3

@ -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

@ -677,8 +677,11 @@ pub unsafe extern "C" fn SCMimeSmtpGetHeader(
buffer_len: *mut u32, buffer_len: *mut u32,
) -> bool { ) -> bool {
let name: &CStr = CStr::from_ptr(str); //unsafe 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..] { 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 = h.value.as_ptr();
*buffer_len = h.value.len() as u32; *buffer_len = h.value.len() as u32;
return true; return true;

@ -121,6 +121,5 @@ int LuaRegisterExtensions(lua_State *lua_state)
LuaRegisterFunctions(lua_state); LuaRegisterFunctions(lua_state);
LuaRegisterTlsFunctions(lua_state); LuaRegisterTlsFunctions(lua_state);
LuaRegisterSmtpFunctions(lua_state);
return 0; return 0;
} }

@ -588,7 +588,6 @@ static lua_State *LuaScriptSetup(const char *filename, LogLuaMasterCtx *ctx)
/* register functions common to all */ /* register functions common to all */
LuaRegisterFunctions(luastate); LuaRegisterFunctions(luastate);
LuaRegisterTlsFunctions(luastate); LuaRegisterTlsFunctions(luastate);
LuaRegisterSmtpFunctions(luastate);
if (lua_pcall(luastate, 0, 0, 0) != 0) { if (lua_pcall(luastate, 0, 0, 0) != 0) {
SCLogError("couldn't run script 'setup' function: %s", lua_tostring(luastate, -1)); SCLogError("couldn't run script 'setup' function: %s", lua_tostring(luastate, -1));

@ -24,6 +24,7 @@
#include "util-lua-flowvarlib.h" #include "util-lua-flowvarlib.h"
#include "util-lua-http.h" #include "util-lua-http.h"
#include "util-lua-dns.h" #include "util-lua-dns.h"
#include "util-lua-smtp.h"
#include "util-lua-ssh.h" #include "util-lua-ssh.h"
#include "util-lua-flowlib.h" #include "util-lua-flowlib.h"
#include "util-lua-hashlib.h" #include "util-lua-hashlib.h"
@ -46,6 +47,7 @@ static const luaL_Reg builtins[] = {
{ "suricata.ja3", SCLuaLoadJa3Lib }, { "suricata.ja3", SCLuaLoadJa3Lib },
{ "suricata.packet", LuaLoadPacketLib }, { "suricata.packet", LuaLoadPacketLib },
{ "suricata.rule", SCLuaLoadRuleLib }, { "suricata.rule", SCLuaLoadRuleLib },
{ "suricata.smtp", SCLuaLoadSmtpLib },
{ "suricata.ssh", SCLuaLoadSshLib }, { "suricata.ssh", SCLuaLoadSshLib },
{ NULL, NULL }, { NULL, NULL },
}; };

@ -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 * You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free * the GNU General Public License version 2 as published by the Free
@ -26,306 +26,149 @@
*/ */
#include "suricata-common.h" #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 "app-layer-smtp.h"
#include "lua.h"
#include "lualib.h"
#include "util-lua.h" #include "util-lua.h"
#include "util-lua-common.h" #include "util-lua-common.h"
#include "util-lua-smtp.h" #include "util-lua-smtp.h"
#include "util-file.h"
/* #include "lua.h"
* \brief internal function used by SMTPGetMimeField #include "lauxlib.h"
*
* \param luastate luastate stack to use and push attributes to static const char smtp_tx_mt[] = "suricata:smtp:tx";
* \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.
*/
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 */ if (!(LuaStateNeedProto(L, ALPROTO_SMTP))) {
SMTPState *state = (SMTPState *) FlowGetAppState(flow); return LuaCallbackError(L, "error: protocol not SMTP");
/* check that state exists */
if(state == NULL) {
return LuaCallbackError(luastate, "Internal error: no state in flow");
} }
/* pointer to current transaction in state */
SMTPTransaction *smtp_tx = state->curr_tx; Flow *flow = LuaStateGetFlow(L);
if(smtp_tx == NULL) { if (flow == NULL) {
return LuaCallbackError(luastate, "Transaction ending or not found"); return LuaCallbackError(L, "error: no flow found");
} }
/* pointer to tail of msg list of MimeStateSMTP in current transaction. */
MimeStateSMTP *mime = smtp_tx->mime_state; SMTPState *state = (SMTPState *)FlowGetAppState(flow);
/* check if msg_tail was hit */ if (state == NULL) {
if(mime == NULL){ return LuaCallbackError(L, "error: no SMTP state");
return LuaCallbackError(luastate, "Internal error: no fields in transaction");
} }
/* extract MIME field based on specific field name. */
const uint8_t *field_value; SMTPTransaction *tx = state->curr_tx;
uint32_t field_len; if (tx == NULL) {
/* check MIME field */ return LuaCallbackError(L, "error: no SMTP transaction found");
if (!SCMimeSmtpGetHeader(mime, name, &field_value, &field_len)) {
return LuaCallbackError(luastate, "Error: mimefield not 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;
}
/** luaL_getmetatable(L, smtp_tx_mt);
* \brief Function extracts specific MIME field based on argument from luastate lua_setmetatable(L, -2);
* stack then pushing the attribute onto the luastate stack.
* return 1;
* \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.
*/
static int SMTPGetMimeField(lua_State *luastate) static int LuaSmtpTxGetMimeField(lua_State *L)
{ {
if(!(LuaStateNeedProto(luastate, ALPROTO_SMTP))) { struct LuaSmtpTx *tx = luaL_checkudata(L, 1, smtp_tx_mt);
return LuaCallbackError(luastate, "error: protocol not SMTP");
if (tx->tx->mime_state == NULL) {
return LuaCallbackError(L, "no mime state");
} }
Flow *flow = LuaStateGetFlow(luastate);
/* check that flow exist */ const char *name = luaL_checkstring(L, 2);
if(flow == NULL) { if (name == NULL) {
return LuaCallbackError(luastate, "Error: no flow found"); 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");
} }
/** static int LuaSmtpTxGetMimeList(lua_State *L)
* \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)
{ {
struct LuaSmtpTx *tx = luaL_checkudata(L, 1, smtp_tx_mt);
SMTPState *state = (SMTPState *) FlowGetAppState(flow); if (tx->tx->mime_state == NULL) {
if(state == NULL) { return LuaCallbackError(L, "no mime state");
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");
} }
const uint8_t *field_name; const uint8_t *field_name;
uint32_t field_len; uint32_t field_len;
/* Counter of MIME fields found */
int num = 1; int num = 1;
/* loop trough the list of mimeFields, printing each name found */ lua_newtable(L);
lua_newtable(luastate); while (SCMimeSmtpGetHeaderName(tx->tx->mime_state, &field_name, &field_len, (uint32_t)num)) {
while (SCMimeSmtpGetHeaderName(mime, &field_name, &field_len, (uint32_t)num)) {
if (field_len != 0) { if (field_len != 0) {
lua_pushinteger(luastate,num++); lua_pushinteger(L, num++);
LuaPushStringBuffer(luastate, field_name, field_len); LuaPushStringBuffer(L, field_name, field_len);
lua_settable(luastate,-3); lua_settable(L, -3);
} }
} }
return 1; return 1;
} }
/** static int LuaSmtpTxGetMailFrom(lua_State *L)
* \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)
{ {
/* Check if right protocol */ struct LuaSmtpTx *tx = luaL_checkudata(L, 1, smtp_tx_mt);
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");
}
GetMimeList(luastate, flow); if (tx->tx->mail_from == NULL || tx->tx->mail_from_len == 0) {
lua_pushnil(L);
return 1; 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");
} }
GetMailFrom(luastate, flow); return LuaPushStringBuffer(L, tx->tx->mail_from, tx->tx->mail_from_len);
return 1;
} }
/** static int LuaSmtpTxGetRcptList(lua_State *L)
* \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)
{ {
struct LuaSmtpTx *tx = luaL_checkudata(L, 1, smtp_tx_mt);
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");
}
/* Create a new table in luastate for rcpt list */ /* Create a new table in luastate for rcpt list */
lua_newtable(luastate); lua_newtable(L);
/* rcpt var for iterator */ /* rcpt var for iterator */
int u = 1; int u = 1;
SMTPString *rcpt; SMTPString *rcpt;
TAILQ_FOREACH(rcpt, &smtp_tx->rcpt_to_list, next) { TAILQ_FOREACH (rcpt, &tx->tx->rcpt_to_list, next) {
lua_pushinteger(luastate, u++); lua_pushinteger(L, u++);
LuaPushStringBuffer(luastate, rcpt->str, rcpt->len); LuaPushStringBuffer(L, rcpt->str, rcpt->len);
lua_settable(luastate, -3); lua_settable(L, -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");
} }
GetRcptList(luastate, flow);
/* return 1 since we always push one table to luastate */
return 1; return 1;
} }
int LuaRegisterSmtpFunctions(lua_State *luastate) static const struct luaL_Reg smtptxlib[] = {
{ { "get_mime_field", LuaSmtpTxGetMimeField },
{ "get_mime_list", LuaSmtpTxGetMimeList },
lua_pushcfunction(luastate, SMTPGetMimeField); { "get_mail_from", LuaSmtpTxGetMailFrom },
lua_setglobal(luastate, "SMTPGetMimeField"); { "get_rcpt_list", LuaSmtpTxGetRcptList },
{ NULL, NULL },
};
lua_pushcfunction(luastate, SMTPGetMimeList); static const struct luaL_Reg smtplib[] = {
lua_setglobal(luastate, "SMTPGetMimeList"); { "get_tx", LuaSmtpGetTx },
{ NULL, NULL },
};
lua_pushcfunction(luastate, SMTPGetMailFrom); int SCLuaLoadSmtpLib(lua_State *L)
lua_setglobal(luastate, "SMTPGetMailFrom"); {
luaL_newmetatable(L, smtp_tx_mt);
lua_pushcfunction(luastate, SMTPGetRcptList); lua_pushvalue(L, -1);
lua_setglobal(luastate, "SMTPGetRcptList"); lua_setfield(L, -2, "__index");
luaL_setfuncs(L, smtptxlib, 0);
return 0; luaL_newlib(L, smtplib);
return 1;
} }

@ -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 * You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free * the GNU General Public License version 2 as published by the Free
@ -18,6 +18,8 @@
#ifndef SURICATA_UTIL_LUA_SMTP_H #ifndef SURICATA_UTIL_LUA_SMTP_H
#define 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 */ #endif /* SURICATA_UTIL_LUA_SMTP_H */

Loading…
Cancel
Save