Pending - build 152
+-- ported smtp inspector from Snort
-- static analysis fix for new_http_inspect
15/05/08 - build 151
src/service_inspectors/nhttp_inspect/Makefile \
src/service_inspectors/pop/Makefile \
src/service_inspectors/rpc_decode/Makefile \
+src/service_inspectors/smtp/Makefile \
src/service_inspectors/ssh/Makefile \
src/service_inspectors/wizard/Makefile \
src/protocols/Makefile \
back_orifice = { }
dns = { }
imap = { }
+smtp = { }
perf_monitor = { }
pop = { }
port_scan = { }
nhttp_inspect
pop
rpc_decode
+ smtp
ssh
wizard
)
service_inspectors/nhttp_inspect/libnhttp_inspect.a \
service_inspectors/pop/libpop.a \
service_inspectors/rpc_decode/librpc_decode.a \
+service_inspectors/smtp/libsmtp.a \
service_inspectors/ssh/libssh.a \
service_inspectors/wizard/libwizard.a
endif
#define FILE_RESUME_BLOCK 0x01
#define FILE_RESUME_LOG 0x02
+/* log flags */
+#define MIME_FLAG_MAIL_FROM_PRESENT 0x00000001
+#define MIME_FLAG_RCPT_TO_PRESENT 0x00000002
+#define MIME_FLAG_FILENAME_PRESENT 0x00000004
+#define MIME_FLAG_EMAIL_HDRS_PRESENT 0x00000008
+
+
struct FILE_LogState
{
uint8_t* filenames;
struct MAIL_LogState
{
- void* log_hdrs_bkt;
unsigned char* emailHdrs;
uint32_t log_depth;
uint32_t hdrs_logged;
MimeBoundaryState boundary_state;
};
-typedef int (* Handle_header_line_func)(void* pkt, const uint8_t* ptr, const uint8_t* eol, int
+typedef int (* Handle_header_line_func)(void *conf, void* pkt, const uint8_t* ptr, const uint8_t* eol, int
max_header_len, void* mime_ssn);
-typedef int (* Normalize_data_func)(void* pkt, const uint8_t* ptr, const uint8_t* data_end);
+typedef int (* Normalize_data_func)(void *conf, void* pkt, const uint8_t* ptr, const uint8_t* data_end);
typedef void (* Decode_alert_func)(void* decode_state);
typedef void (* Reset_state_func)(void *ssn);
typedef bool (* Is_end_of_data_func)(void* ssn);
DecodeConfig* decode_conf;
MAIL_LogConfig* log_config;
MAIL_LogState* log_state;
- void* decode_bkt;
- void* log_mempool;
+ void *config;
MimeMethods* methods;
};
|| conf->log_mailfrom || conf->log_rcptto))
{
uint32_t bufsz = (2* MAX_EMAIL) + MAX_FILE + conf->email_hdrs_log_depth;
- *log_state = (MAIL_LogState*)calloc(1, sizeof(*log_state) + bufsz);
+ *log_state = (MAIL_LogState*)calloc(1, sizeof(MAIL_LogState) + bufsz);
if ((*log_state) != NULL)
{
- uint8_t* buf = ((uint8_t*)(*log_state)) + sizeof(*log_state);
+ uint8_t* buf = ((uint8_t*)(*log_state)) + sizeof(MAIL_LogState);
(*log_state)->log_depth = conf->email_hdrs_log_depth;
(*log_state)->recipients = buf;
(*log_state)->rcpts_logged = 0;
if (mime_ssn->methods && mime_ssn->methods->handle_header_line)
{
- int ret = mime_ssn->methods->handle_header_line(p, ptr, eol, max_header_name_len,
+ int ret = mime_ssn->methods->handle_header_line(mime_ssn->config, p, ptr, eol, max_header_name_len,
mime_ssn);
if (ret < 0)
return NULL;
* and dot to alt buffer */
if (mime_ssn->methods && mime_ssn->methods->normalize_data)
{
- if (mime_ssn->methods->normalize_data(p, start, end) < 0)
+ if (mime_ssn->methods->normalize_data(mime_ssn->config, p, start, end) < 0)
return NULL;
}
if (mime_ssn->methods && mime_ssn->methods->normalize_data)
{
- if (mime_ssn->methods->normalize_data(p, start, end) < 0)
+ if (mime_ssn->methods->normalize_data(mime_ssn->config, p, start, end) < 0)
return NULL;
}
/* now we shouldn't have to worry about copying any data to the alt buffer
/* Maximum length of header chars before colon, based on Exim 4.32 exploit */
#define MAX_HEADER_NAME_LEN 64
-/* log flags */
-#define MIME_FLAG_FILENAME_PRESENT 0x00000004
-
typedef struct _MimePcre
{
pcre* re;
add_subdirectory(nhttp_inspect)
add_subdirectory(pop)
add_subdirectory(rpc_decode)
+add_subdirectory(smtp)
add_subdirectory(ssh)
add_subdirectory(wizard)
nhttp_inspect
pop
rpc_decode
+ smtp
ssh
wizard
)
nhttp_inspect \
pop \
rpc_decode \
+smtp \
ssh \
wizard
imap_ssn->mime_ssn.log_config = &(config->log_config);
imap_ssn->mime_ssn.decode_conf = &(config->decode_conf);
imap_ssn->mime_ssn.methods = &(imap_mime_methods);
+ imap_ssn->mime_ssn.config = config;
if (file_api->set_log_buffers(&(imap_ssn->mime_ssn.log_state), &(config->log_config)) < 0)
{
return NULL;
pop_ssn->mime_ssn.log_config = &(config->log_config);
pop_ssn->mime_ssn.decode_conf = &(config->decode_conf);
pop_ssn->mime_ssn.methods = &(pop_mime_methods);
+ pop_ssn->mime_ssn.config = config;
if (file_api->set_log_buffers(&(pop_ssn->mime_ssn.log_state), &(config->log_config)) < 0)
{
return NULL;
extern const BaseApi* sin_nhttp;
extern const BaseApi* sin_pop;
extern const BaseApi* sin_rpc_decode;
+extern const BaseApi* sin_smtp;
extern const BaseApi* sin_ssh;
extern const BaseApi* sin_telnet;
extern const BaseApi* sin_wizard;
sin_nhttp,
sin_pop,
sin_rpc_decode,
+ sin_smtp,
sin_ssh,
sin_telnet,
sin_wizard,
--- /dev/null
+
+set( FILE_LIST
+ smtp.cc
+ smtp.h
+ smtp_paf.cc
+ smtp_paf.h
+ smtp_util.cc
+ smtp_util.h
+ smtp_xlink2state.cc
+ smtp_xlink2state.h
+ smtp_config.h
+ smtp_module.cc
+ smtp_module.h
+)
+
+if (STATIC_INSPECTORS)
+ add_library( smtp STATIC ${FILE_LIST})
+
+else (STATIC_INSPECTORS)
+ add_shared_library(smtp inspectors ${FILE_LIST})
+
+endif (STATIC_INSPECTORS)
--- /dev/null
+AUTOMAKE_OPTIONS=foreign no-dependencies
+
+file_list = \
+smtp_config.h \
+smtp.cc \
+smtp.h \
+smtp_util.cc \
+smtp_util.h \
+smtp_xlink2state.cc \
+smtp_xlink2state.h \
+smtp_normalize.cc \
+smtp_normalize.h \
+smtp_paf.cc \
+smtp_paf.h \
+smtp_module.cc \
+smtp_module.h
+
+if STATIC_INSPECTORS
+noinst_LIBRARIES = libsmtp.a
+libsmtp_a_SOURCES = $(file_list)
+else
+shlibdir = $(pkglibdir)/inspectors
+shlib_LTLIBRARIES = libsmtp.la
+libsmtp_la_CXXFLAGS = $(AM_CXXFLAGS) -DBUILDING_SO
+libsmtp_la_LDFLAGS = -export-dynamic -shared
+libsmtp_la_SOURCES = $(file_list)
+endif
+
+AM_CXXFLAGS = @AM_CXXFLAGS@
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+#include "smtp.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "snort_types.h"
+#include "snort_debug.h"
+
+#include "smtp_module.h"
+#include "profiler.h"
+#include "stream/stream_api.h"
+#include "file_api/file_api.h"
+#include "parser.h"
+#include "framework/inspector.h"
+#include "utils/sfsnprintfappend.h"
+#include "target_based/sftarget_protocol_reference.h"
+#include "smtp_paf.h"
+#include "smtp_util.h"
+#include "smtp_normalize.h"
+#include "smtp_xlink2state.h"
+#include "sf_email_attach_decode.h"
+#include "protocols/ssl.h"
+#include "unified2_common.h"
+#include "detection/detection_util.h"
+
+THREAD_LOCAL ProfileStats smtpPerfStats;
+THREAD_LOCAL SimpleStats smtpstats;
+char smtp_normalizing;
+
+/* Globals ****************************************************************/
+
+const SMTPToken smtp_known_cmds[] =
+{
+ { "ATRN", 4, CMD_ATRN, SMTP_CMD_TYPE_NORMAL },
+ { "AUTH", 4, CMD_AUTH, SMTP_CMD_TYPE_AUTH },
+ { "BDAT", 4, CMD_BDAT, SMTP_CMD_TYPE_BDATA },
+ { "DATA", 4, CMD_DATA, SMTP_CMD_TYPE_DATA },
+ { "DEBUG", 5, CMD_DEBUG, SMTP_CMD_TYPE_NORMAL },
+ { "EHLO", 4, CMD_EHLO, SMTP_CMD_TYPE_NORMAL },
+ { "EMAL", 4, CMD_EMAL, SMTP_CMD_TYPE_NORMAL },
+ { "ESAM", 4, CMD_ESAM, SMTP_CMD_TYPE_NORMAL },
+ { "ESND", 4, CMD_ESND, SMTP_CMD_TYPE_NORMAL },
+ { "ESOM", 4, CMD_ESOM, SMTP_CMD_TYPE_NORMAL },
+ { "ETRN", 4, CMD_ETRN, SMTP_CMD_TYPE_NORMAL },
+ { "EVFY", 4, CMD_EVFY, SMTP_CMD_TYPE_NORMAL },
+ { "EXPN", 4, CMD_EXPN, SMTP_CMD_TYPE_NORMAL },
+ { "HELO", 4, CMD_HELO, SMTP_CMD_TYPE_NORMAL },
+ { "HELP", 4, CMD_HELP, SMTP_CMD_TYPE_NORMAL },
+ { "IDENT", 5, CMD_IDENT, SMTP_CMD_TYPE_NORMAL },
+ { "MAIL", 4, CMD_MAIL, SMTP_CMD_TYPE_NORMAL },
+ { "NOOP", 4, CMD_NOOP, SMTP_CMD_TYPE_NORMAL },
+ { "ONEX", 4, CMD_ONEX, SMTP_CMD_TYPE_NORMAL },
+ { "QUEU", 4, CMD_QUEU, SMTP_CMD_TYPE_NORMAL },
+ { "QUIT", 4, CMD_QUIT, SMTP_CMD_TYPE_NORMAL },
+ { "RCPT", 4, CMD_RCPT, SMTP_CMD_TYPE_NORMAL },
+ { "RSET", 4, CMD_RSET, SMTP_CMD_TYPE_NORMAL },
+ { "SAML", 4, CMD_SAML, SMTP_CMD_TYPE_NORMAL },
+ { "SEND", 4, CMD_SEND, SMTP_CMD_TYPE_NORMAL },
+ { "SIZE", 4, CMD_SIZE, SMTP_CMD_TYPE_NORMAL },
+ { "STARTTLS", 8, CMD_STARTTLS, SMTP_CMD_TYPE_NORMAL },
+ { "SOML", 4, CMD_SOML, SMTP_CMD_TYPE_NORMAL },
+ { "TICK", 4, CMD_TICK, SMTP_CMD_TYPE_NORMAL },
+ { "TIME", 4, CMD_TIME, SMTP_CMD_TYPE_NORMAL },
+ { "TURN", 4, CMD_TURN, SMTP_CMD_TYPE_NORMAL },
+ { "TURNME", 6, CMD_TURNME, SMTP_CMD_TYPE_NORMAL },
+ { "VERB", 4, CMD_VERB, SMTP_CMD_TYPE_NORMAL },
+ { "VRFY", 4, CMD_VRFY, SMTP_CMD_TYPE_NORMAL },
+ { "X-EXPS", 6, CMD_X_EXPS, SMTP_CMD_TYPE_AUTH },
+ { "XADR", 4, CMD_XADR, SMTP_CMD_TYPE_NORMAL },
+ { "XAUTH", 5, CMD_XAUTH, SMTP_CMD_TYPE_AUTH },
+ { "XCIR", 4, CMD_XCIR, SMTP_CMD_TYPE_NORMAL },
+ { "XEXCH50", 7, CMD_XEXCH50, SMTP_CMD_TYPE_BDATA },
+ { "XGEN", 4, CMD_XGEN, SMTP_CMD_TYPE_NORMAL },
+ { "XLICENSE", 8, CMD_XLICENSE, SMTP_CMD_TYPE_NORMAL },
+ { "X-LINK2STATE", 12, CMD_X_LINK2STATE, SMTP_CMD_TYPE_NORMAL },
+ { "XQUE", 4, CMD_XQUE, SMTP_CMD_TYPE_NORMAL },
+ { "XSTA", 4, CMD_XSTA, SMTP_CMD_TYPE_NORMAL },
+ { "XTRN", 4, CMD_XTRN, SMTP_CMD_TYPE_NORMAL },
+ { "XUSR", 4, CMD_XUSR, SMTP_CMD_TYPE_NORMAL },
+ { "*", 1, CMD_ABORT, SMTP_CMD_TYPE_NORMAL },
+ { NULL, 0, 0, SMTP_CMD_TYPE_NORMAL }
+};
+
+const SMTPToken smtp_resps[] =
+{
+ { "220", 3, RESP_220, SMTP_CMD_TYPE_NORMAL }, /* Service ready - initial response and
+ STARTTLS response */
+ { "221", 3, RESP_221, SMTP_CMD_TYPE_NORMAL }, /* Goodbye - response to QUIT */
+ { "235", 3, RESP_235, SMTP_CMD_TYPE_NORMAL }, /* Auth done response */
+ { "250", 3, RESP_250, SMTP_CMD_TYPE_NORMAL }, /* Requested mail action okay, completed */
+ { "334", 3, RESP_334, SMTP_CMD_TYPE_NORMAL }, /* Auth intermediate response */
+ { "354", 3, RESP_354, SMTP_CMD_TYPE_NORMAL }, /* Start mail input - data response */
+ { "421", 3, RESP_421, SMTP_CMD_TYPE_NORMAL }, /* Service not availiable - closes connection
+ */
+ { "450", 3, RESP_450, SMTP_CMD_TYPE_NORMAL }, /* Mailbox unavailable */
+ { "451", 3, RESP_451, SMTP_CMD_TYPE_NORMAL }, /* Local error in processing */
+ { "452", 3, RESP_452, SMTP_CMD_TYPE_NORMAL }, /* Insufficient system storage */
+ { "500", 3, RESP_500, SMTP_CMD_TYPE_NORMAL }, /* Command unrecognized */
+ { "501", 3, RESP_501, SMTP_CMD_TYPE_NORMAL }, /* Syntax error in parameters or arguments */
+ { "502", 3, RESP_502, SMTP_CMD_TYPE_NORMAL }, /* Command not implemented */
+ { "503", 3, RESP_503, SMTP_CMD_TYPE_NORMAL }, /* Bad sequence of commands */
+ { "504", 3, RESP_504, SMTP_CMD_TYPE_NORMAL }, /* Command parameter not implemented */
+ { "535", 3, RESP_535, SMTP_CMD_TYPE_NORMAL }, /* Authentication failed */
+ { "550", 3, RESP_550, SMTP_CMD_TYPE_NORMAL }, /* Action not taken - mailbox unavailable */
+ { "551", 3, RESP_551, SMTP_CMD_TYPE_NORMAL }, /* User not local; please try <forward-path>
+ */
+ { "552", 3, RESP_552, SMTP_CMD_TYPE_NORMAL }, /* Mail action aborted: exceeded storage
+ allocation */
+ { "553", 3, RESP_553, SMTP_CMD_TYPE_NORMAL }, /* Action not taken: mailbox name not allowed
+ */
+ { "554", 3, RESP_554, SMTP_CMD_TYPE_NORMAL }, /* Transaction failed */
+ { NULL, 0, 0, SMTP_CMD_TYPE_NORMAL }
+};
+
+typedef struct _SMTPAuth
+{
+ const char* name;
+ int name_len;
+} SMTPAuth;
+
+/* Cyrus SASL authentication mechanisms ANONYMOUS, PLAIN and LOGIN
+ * does not have context
+ */
+const SMTPAuth smtp_auth_no_ctx[] =
+{
+ { "ANONYMOUS", 9 },
+ { "PLAIN", 5 },
+ { "LOGIN", 5 },
+ { NULL, 0 }
+};
+
+SearchTool* smtp_resp_search_mpse = nullptr;
+
+SMTPSearch smtp_resp_search[RESP_LAST];
+THREAD_LOCAL const SMTPSearch* smtp_current_search = NULL;
+THREAD_LOCAL SMTPSearchInfo smtp_search_info;
+
+static void snort_smtp(SMTP_PROTO_CONF* GlobalConf, Packet* p);
+static void SMTP_ResetState(void*);
+void SMTP_DecodeAlert(void* ds);
+
+static int SMTP_HandleHeaderLine(void* conf, void* pkt, const uint8_t* ptr, const uint8_t* eol,
+ int max_header_len, void* ssn);
+static int SMTP_NormalizeData(void* conf, void* pkt, const uint8_t* ptr, const uint8_t* data_end);
+
+MimeMethods smtp_mime_methods = { SMTP_HandleHeaderLine, SMTP_NormalizeData, SMTP_DecodeAlert,
+ SMTP_ResetState, smtp_is_data_end };
+
+unsigned SmtpFlowData::flow_id = 0;
+static SMTPData* get_session_data(Flow* flow)
+{
+ SmtpFlowData* fd = (SmtpFlowData*)flow->get_application_data(
+ SmtpFlowData::flow_id);
+
+ return fd ? &fd->session : NULL;
+}
+
+SMTPData* SetNewSMTPData(SMTP_PROTO_CONF* config, Packet* p)
+{
+ SMTPData* smtp_ssn;
+ SmtpFlowData* fd = new SmtpFlowData;
+
+ p->flow->set_application_data(fd);
+ smtp_ssn = &fd->session;
+
+ smtp_ssn->mime_ssn.log_config = &(config->log_config);
+ smtp_ssn->mime_ssn.decode_conf = &(config->decode_conf);
+ smtp_ssn->mime_ssn.methods = &(smtp_mime_methods);
+ smtp_ssn->mime_ssn.config = config;
+ if (file_api->set_log_buffers(&(smtp_ssn->mime_ssn.log_state), &(config->log_config)) < 0)
+ {
+ return NULL;
+ }
+
+ if(stream.is_midstream(p->flow))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Got midstream packet - "
+ "setting state to unknown\n"); );
+ smtp_ssn->state = STATE_UNKNOWN;
+ }
+
+ return smtp_ssn;
+}
+
+void SMTP_DecodeAlert(void* ds)
+{
+ Email_DecodeState* decode_state = (Email_DecodeState*)ds;
+ switch ( decode_state->decode_type )
+ {
+ case DECODE_B64:
+ SnortEventqAdd(GID_SMTP, SMTP_B64_DECODING_FAILED);
+ break;
+ case DECODE_QP:
+ SnortEventqAdd(GID_SMTP, SMTP_QP_DECODING_FAILED);
+ break;
+ case DECODE_UU:
+ SnortEventqAdd(GID_SMTP, SMTP_UU_DECODING_FAILED);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void SMTP_InitCmds(SMTP_PROTO_CONF* config)
+{
+ const SMTPToken* tmp;
+
+ if (config == NULL)
+ return;
+
+ /* add one to CMD_LAST for NULL entry */
+ config->cmds = (SMTPToken*)calloc(CMD_LAST + 1, sizeof(SMTPToken));
+ if (config->cmds == NULL)
+ {
+ FatalError("Could not allocate memory for SMTP Command structure.\n");
+ }
+
+ for (tmp = &smtp_known_cmds[0]; tmp->name != NULL; tmp++)
+ {
+ config->cmds[tmp->search_id].name_len = tmp->name_len;
+ config->cmds[tmp->search_id].search_id = tmp->search_id;
+ config->cmds[tmp->search_id].name = strdup(tmp->name);
+ config->cmds[tmp->search_id].type = tmp->type;
+
+ if (config->cmds[tmp->search_id].name == NULL)
+ {
+ FatalError("Could not allocate memory for SMTP Command structure.\n");
+ }
+ }
+
+ /* initialize memory for command searches */
+ config->cmd_search = (SMTPSearch*)calloc(CMD_LAST, sizeof(SMTPSearch));
+ if (config->cmd_search == NULL)
+ {
+ FatalError("Could not allocate memory for SMTP Command Structure.\n");
+ }
+
+ config->num_cmds = CMD_LAST;
+}
+
+void SMTP_CommandSearchInit(SMTP_PROTO_CONF* config)
+{
+ const SMTPToken* tmp;
+ config->cmd_search_mpse = new SearchTool();
+ if (config->cmd_search_mpse == NULL)
+ {
+ FatalError("Could not allocate memory for SMTP Command search.\n");
+ }
+ for (tmp = config->cmds; tmp->name != NULL; tmp++)
+ {
+ config->cmd_search[tmp->search_id].name = (char *)tmp->name;
+ config->cmd_search[tmp->search_id].name_len = tmp->name_len;
+ config->cmd_search_mpse->add(tmp->name, tmp->name_len, tmp->search_id);
+ }
+
+ config->cmd_search_mpse->prep();
+}
+
+void SMTP_ResponseSearchInit(void)
+{
+ const SMTPToken* tmp;
+ smtp_resp_search_mpse = new SearchTool();
+ if (smtp_resp_search_mpse == NULL)
+ {
+ FatalError("Could not allocate memory for SMTP Response search.\n");
+ }
+ for (tmp = &smtp_resps[0]; tmp->name != NULL; tmp++)
+ {
+ smtp_resp_search[tmp->search_id].name = (char *)tmp->name;
+ smtp_resp_search[tmp->search_id].name_len = tmp->name_len;
+ smtp_resp_search_mpse->add(tmp->name, tmp->name_len, tmp->search_id);
+ }
+ smtp_resp_search_mpse->prep();
+}
+
+void SMTP_SearchFree(void)
+{
+ if (smtp_resp_search_mpse != NULL)
+ delete smtp_resp_search_mpse;
+}
+
+static int AddCmd(SMTP_PROTO_CONF* config, const char* name, SMTPCmdTypeEnum type)
+{
+ SMTPToken* cmds, * tmp_cmds;
+ SMTPSearch* cmd_search;
+ SMTPCmdConfig* cmd_config;
+ int ret;
+
+ config->num_cmds++;
+
+ /* allocate enough memory for new commmand - alloc one extra for NULL entry */
+ cmds = (SMTPToken*)calloc(config->num_cmds + 1, sizeof(SMTPToken));
+ if (cmds == NULL)
+ {
+ FatalError("Failed to allocate memory for SMTP command structure\n");
+ }
+
+ /* This gets filled in later */
+ cmd_search = (SMTPSearch*)calloc(config->num_cmds, sizeof(SMTPSearch));
+ if (cmd_search == NULL)
+ {
+ FatalError("Failed to allocate memory for SMTP command structure\n");
+ }
+
+ cmd_config = (SMTPCmdConfig*)calloc(config->num_cmds, sizeof(SMTPCmdConfig));
+ if (cmd_config == NULL)
+ {
+ FatalError("Failed to allocate memory for SMTP command structure\n");
+ }
+
+ /* copy existing commands into newly allocated memory
+ * * don't need to copy anything from cmd_search since this hasn't been initialized yet */
+ ret = SafeMemcpy(cmds, config->cmds, (config->num_cmds - 1) * sizeof(SMTPToken),
+ cmds, cmds + (config->num_cmds - 1));
+
+ if (ret != SAFEMEM_SUCCESS)
+ {
+ FatalError("Failed to memory copy SMTP command structure\n");
+ }
+
+ ret = SafeMemcpy(cmd_config, config->cmd_config, (config->num_cmds - 1) *
+ sizeof(SMTPCmdConfig),
+ cmd_config, cmd_config + (config->num_cmds - 1));
+
+ if (ret != SAFEMEM_SUCCESS)
+ {
+ FatalError("Failed to memory copy SMTP command structure\n");
+ }
+
+ /* add new command to cmds
+ * * cmd_config doesn't need anything added - this will probably be done by a calling function
+ * * cmd_search will be initialized when the searches are initialized */
+ tmp_cmds = &cmds[config->num_cmds - 1];
+ tmp_cmds->name = strdup(name);
+ tmp_cmds->name_len = strlen(name);
+ tmp_cmds->search_id = config->num_cmds - 1;
+ if (type)
+ tmp_cmds->type = type;
+
+ if (tmp_cmds->name == NULL)
+ {
+ FatalError("Failed to allocate memory for SMTP command structure\n");
+ }
+
+ /* free global memory structures */
+ if (config->cmds != NULL)
+ free(config->cmds);
+
+ if (config->cmd_search != NULL)
+ free(config->cmd_search);
+
+ if (config->cmd_config != NULL)
+ free(config->cmd_config);
+
+ /* set globals to new memory */
+ config->cmds = cmds;
+ config->cmd_search = cmd_search;
+ config->cmd_config = cmd_config;
+
+ return (config->num_cmds - 1);
+}
+
+/* Return id associated with a given command string */
+static int GetCmdId(SMTP_PROTO_CONF* config, const char* name, SMTPCmdTypeEnum type)
+{
+ SMTPToken* cmd;
+
+ for (cmd = config->cmds; cmd->name != NULL; cmd++)
+ {
+ if (strcasecmp(cmd->name, name) == 0)
+ {
+ if (type && (type != cmd->type))
+ cmd->type = type;
+
+ return cmd->search_id;
+ }
+ }
+
+ return AddCmd(config, name, type);
+}
+
+void ProcessSmtpCmdsList(SMTP_PROTO_CONF* config, const SmtpCmd* sc)
+{
+ const char* cmd = sc->name.c_str();
+ int id;
+ SMTPCmdTypeEnum type;
+
+ if ( sc->flags & PCMD_AUTH )
+ type = SMTP_CMD_TYPE_AUTH;
+
+ else if ( sc->flags & PCMD_BDATA )
+ type = SMTP_CMD_TYPE_BDATA;
+
+ else if ( sc->flags & PCMD_DATA )
+ type = SMTP_CMD_TYPE_DATA;
+
+ else
+ type = SMTP_CMD_TYPE_NORMAL;
+
+ id = GetCmdId(config, cmd, type);
+ if ( sc->flags & PCMD_INVALID )
+ config->cmd_config[id].alert = true;
+
+ else if ( sc->flags & PCMD_NORM )
+ config->cmd_config[id].normalize = true;
+
+ else
+ config->cmd_config[id].alert = false;
+
+ if ( sc->flags & PCMD_ALT )
+ config->cmd_config[id].max_line_len = sc->number;
+}
+
+void SMTP_PrintConfig(SMTP_PROTO_CONF *config)
+{
+ const SMTPToken* cmd;
+ char buf[8192];
+ int max_line_len_count = 0;
+ int max_line_len = 0;
+ int alert_count = 0;
+
+ if (config == NULL)
+ return;
+
+ memset(&buf[0], 0, sizeof(buf));
+
+ LogMessage("SMTP Config:\n");
+ snprintf(buf, sizeof(buf) - 1, " Normalize: ");
+
+ if(config->normalize == NORMALIZE_ALL)
+ sfsnprintfappend(buf, sizeof(buf) - 1, "all");
+ else if(config->normalize == NORMALIZE_NONE)
+ sfsnprintfappend(buf, sizeof(buf) - 1, "none");
+ else if(config->normalize == NORMALIZE_CMDS)
+ {
+ for (cmd = config->cmds; cmd->name != NULL; cmd++)
+ {
+ if (config->cmd_config[cmd->search_id].normalize)
+ {
+ sfsnprintfappend(buf, sizeof(buf) - 1, "%s ", cmd->name);
+ }
+ }
+ }
+
+ LogMessage("%s\n", buf);
+
+ LogMessage(" Ignore Data: %s\n",
+ config->decode_conf.ignore_data ? "Yes" : "No");
+ LogMessage(" Ignore TLS Data: %s\n",
+ config->ignore_tls_data ? "Yes" : "No");
+ snprintf(buf, sizeof(buf) - 1, " Max Command Line Length: ");
+
+ if (config->max_command_line_len == 0)
+ sfsnprintfappend(buf, sizeof(buf) - 1, "Unlimited");
+ else
+ sfsnprintfappend(buf, sizeof(buf) - 1, "%d", config->max_command_line_len);
+
+ LogMessage("%s\n", buf);
+
+ {
+ snprintf(buf, sizeof(buf) - 1, " Max Specific Command Line Length: ");
+
+ for (cmd = config->cmds; cmd->name != NULL; cmd++)
+ {
+ max_line_len = config->cmd_config[cmd->search_id].max_line_len;
+
+ if (max_line_len != 0)
+ {
+ if (max_line_len_count % 5 == 0)
+ {
+ LogMessage("%s\n", buf);
+ snprintf(buf, sizeof(buf) - 1, " %s:%d ", cmd->name, max_line_len);
+ }
+ else
+ {
+ sfsnprintfappend(buf, sizeof(buf) - 1, "%s:%d ", cmd->name, max_line_len);
+ }
+
+ max_line_len_count++;
+ }
+ }
+
+ if (max_line_len_count == 0)
+ LogMessage("%sNone\n", buf);
+ else
+ LogMessage("%s\n", buf);
+ }
+ snprintf(buf, sizeof(buf) - 1, " Max Header Line Length: ");
+
+ if (config->max_header_line_len == 0)
+ LogMessage("%sUnlimited\n", buf);
+ else
+ LogMessage("%s%d\n", buf, config->max_header_line_len);
+
+ snprintf(buf, sizeof(buf) - 1, " Max Response Line Length: ");
+
+ if (config->max_response_line_len == 0)
+ LogMessage("%sUnlimited\n", buf);
+ else
+ LogMessage("%s%d\n", buf, config->max_response_line_len);
+
+ LogMessage(" X-Link2State Enabled: %s\n",
+ (config->xlink2state == ALERT_XLINK2STATE) ? "Yes" : "No");
+ if (config->xlink2state == DROP_XLINK2STATE)
+ {
+ LogMessage(" Drop on X-Link2State Alert: %s\n", "Yes" );
+ }
+ else
+ {
+ LogMessage(" Drop on X-Link2State Alert: %s\n", "No" );
+ }
+
+ snprintf(buf, sizeof(buf) - 1, " Alert on commands: ");
+
+ for (cmd = config->cmds; cmd->name != NULL; cmd++)
+ {
+ if (config->cmd_config[cmd->search_id].alert)
+ {
+ sfsnprintfappend(buf, sizeof(buf) - 1, "%s ", cmd->name);
+ alert_count++;
+ }
+ }
+
+ if (alert_count == 0)
+ {
+ LogMessage("%sNone\n", buf);
+ }
+ else
+ {
+ LogMessage("%s\n", buf);
+ }
+ if (config->decode_conf.b64_depth > -1)
+ {
+ LogMessage(" Base64 Decoding: %s\n", "Enabled");
+ switch (config->decode_conf.b64_depth)
+ {
+ case 0:
+ LogMessage(" Base64 Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Base64 Decoding Depth: %d\n", config->decode_conf.b64_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Base64 Decoding: %s\n", "Disabled");
+
+ if (config->decode_conf.qp_depth > -1)
+ {
+ LogMessage(" Quoted-Printable Decoding: %s\n","Enabled");
+ switch (config->decode_conf.qp_depth)
+ {
+ case 0:
+ LogMessage(" Quoted-Printable Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Quoted-Printable Decoding Depth: %d\n", config->decode_conf.qp_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Quoted-Printable Decoding: %s\n", "Disabled");
+
+ if (config->decode_conf.uu_depth > -1)
+ {
+ LogMessage(" Unix-to-Unix Decoding: %s\n","Enabled");
+ switch (config->decode_conf.uu_depth)
+ {
+ case 0:
+ LogMessage(" Unix-to-Unix Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Unix-to-Unix Decoding Depth: %d\n", config->decode_conf.uu_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Unix-to-Unix Decoding: %s\n", "Disabled");
+
+ if (config->decode_conf.bitenc_depth > -1)
+ {
+ LogMessage(" Non-Encoded MIME attachment Extraction: %s\n","Enabled");
+ switch (config->decode_conf.bitenc_depth)
+ {
+ case 0:
+ LogMessage(" Non-Encoded MIME attachment Extraction Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Non-Encoded MIME attachment Extraction Depth: %d\n",
+ config->decode_conf.bitenc_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Non-Encoded MIME attachment Extraction/text: %s\n", "Disabled");
+
+ LogMessage(" Log Attachment filename: %s\n",
+ config->log_config.log_filename ? "Enabled" : "Not Enabled");
+
+ LogMessage(" Log MAIL FROM Address: %s\n",
+ config->log_config.log_mailfrom ? "Enabled" : "Not Enabled");
+
+ LogMessage(" Log RCPT TO Addresses: %s\n",
+ config->log_config.log_rcptto ? "Enabled" : "Not Enabled");
+
+ LogMessage(" Log Email Headers: %s\n",
+ config->log_config.log_email_hdrs ? "Enabled" : "Not Enabled");
+ if (config->log_config.log_email_hdrs)
+ {
+ LogMessage(" Email Hdrs Log Depth: %u\n",
+ config->log_config.email_hdrs_log_depth);
+ }
+}
+
+/*
+ * * Reset SMTP session state
+ * *
+ * * @param none
+ * *
+ * * @return none
+ * */
+static void SMTP_ResetState(void* ssn)
+{
+ SMTPData* smtp_ssn = get_session_data((Flow*)ssn);
+ smtp_ssn->state = STATE_COMMAND;
+ smtp_ssn->state_flags = 0;
+}
+
+static inline int InspectPacket(Packet* p)
+{
+ return PacketHasPAFPayload(p);
+}
+
+/*
+ * Do first-packet setup
+ *
+ * @param p standard Packet structure
+ *
+ * @return none
+ */
+static int SMTP_Setup(Packet* p, SMTPData* ssn)
+{
+ int pkt_dir;
+
+ /* Get the direction of the packet. */
+ if ( p->packet_flags & PKT_FROM_SERVER )
+ pkt_dir = SMTP_PKT_FROM_SERVER;
+ else
+ pkt_dir = SMTP_PKT_FROM_CLIENT;
+
+ if (!(ssn->session_flags & SMTP_FLAG_CHECK_SSL))
+ ssn->session_flags |= SMTP_FLAG_CHECK_SSL;
+ /* Check to see if there is a reassembly gap. If so, we won't know
+ * * * what state we're in when we get the _next_ reassembled packet */
+
+ /* Check to see if there is a reassembly gap. If so, we won't know
+ * what state we're in when we get the _next_ reassembled packet */
+ if ((pkt_dir != SMTP_PKT_FROM_SERVER) &&
+ (p->packet_flags & PKT_REBUILT_STREAM))
+ {
+ int missing_in_rebuilt =
+ stream.missing_in_reassembled(p->flow, SSN_DIR_FROM_CLIENT);
+
+ if (ssn->session_flags & SMTP_FLAG_NEXT_STATE_UNKNOWN)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Found gap in previous reassembly buffer - "
+ "set state to unknown\n"); );
+ ssn->state = STATE_UNKNOWN;
+ ssn->session_flags &= ~SMTP_FLAG_NEXT_STATE_UNKNOWN;
+ }
+
+ if (missing_in_rebuilt == SSN_MISSING_BEFORE)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Found missing packets before "
+ "in reassembly buffer - set state to unknown\n"); );
+ ssn->state = STATE_UNKNOWN;
+ }
+ }
+
+ return pkt_dir;
+}
+
+/*
+ * Callback function for string search
+ *
+ * @param id id in array of search strings from smtp_config.cmds
+ * @param index index in array of search strings from smtp_config.cmds
+ * @param data buffer passed in to search function
+ *
+ * @return response
+ * @retval 1 commands caller to stop searching
+ */
+static int SMTP_SearchStrFound(void* id, void*, int index, void*, void*)
+{
+ int search_id = (int)(uintptr_t)id;
+
+ smtp_search_info.id = search_id;
+ smtp_search_info.index = index;
+ smtp_search_info.length = smtp_current_search[search_id].name_len;
+
+ /* Returning non-zero stops search, which is okay since we only look for one at a time */
+ return 1;
+}
+
+static bool SMTP_IsAuthCtxIgnored(const uint8_t* start, int length)
+{
+ const SMTPAuth* tmp;
+ for (tmp = &smtp_auth_no_ctx[0]; tmp->name != NULL; tmp++)
+ {
+ if ((tmp->name_len == length) && (!memcmp(start, tmp->name, length)))
+ return true;
+ }
+
+ return false;
+}
+
+static bool SMTP_IsAuthChanged(SMTPData* smtp_ssn, const uint8_t* start_ptr, const
+ uint8_t* end_ptr)
+{
+ int length;
+ bool auth_changed = false;
+ uint8_t* start = (uint8_t*)start_ptr;
+ uint8_t* end = (uint8_t*)end_ptr;
+
+ while ((start < end) && isspace(*start))
+ start++;
+ while ((start < end) && isspace(*(end-1)))
+ end--;
+
+ if (start >= end)
+ return auth_changed;
+
+ length = end - start;
+
+ if (length > MAX_AUTH_NAME_LEN)
+ return auth_changed;
+
+ if (SMTP_IsAuthCtxIgnored(start, length))
+ return auth_changed;
+
+ /* if authentication mechanism is set, compare it with current one*/
+ if (smtp_ssn->auth_name)
+ {
+ if (smtp_ssn->auth_name->length != length)
+ auth_changed = true;
+ else if (memcmp(start, smtp_ssn->auth_name->name, length))
+ auth_changed = true;
+ }
+ else
+ smtp_ssn->auth_name = (SMTPAuthName*)calloc(1, sizeof(*(smtp_ssn->auth_name)));
+
+ /* save the current authentication mechanism*/
+ if (!smtp_ssn->auth_name)
+ return auth_changed;
+
+ if (auth_changed || (!smtp_ssn->auth_name->length))
+ {
+ memcpy(smtp_ssn->auth_name->name, start, length);
+ smtp_ssn->auth_name->length = length;
+ }
+
+ return auth_changed;
+}
+
+/*
+ * Handle COMMAND state
+ *
+ * @param p standard Packet structure
+ * @param ptr pointer into p->data buffer to start looking at data
+ * @param end points to end of p->data buffer
+ *
+ * @return pointer into p->data where we stopped looking at data
+ * will be end of line or end of packet
+ */
+static const uint8_t* SMTP_HandleCommand(SMTP_PROTO_CONF* config, Packet* p, SMTPData* smtp_ssn,
+ const uint8_t* ptr, const uint8_t* end)
+{
+ const uint8_t* eol; /* end of line */
+ const uint8_t* eolm; /* end of line marker */
+ int cmd_line_len;
+ int ret;
+ int cmd_found;
+ char alert_long_command_line = 0;
+
+ /* get end of line and end of line marker */
+ SMTP_GetEOL(ptr, end, &eol, &eolm);
+
+ /* calculate length of command line */
+ cmd_line_len = eol - ptr;
+
+ /* check for command line exceeding maximum
+ * do this before checking for a command since this could overflow
+ * some server's buffers without the presence of a known command */
+ if ((config->max_command_line_len != 0) &&
+ (cmd_line_len > config->max_command_line_len))
+ {
+ alert_long_command_line = 1;
+ }
+
+ /* TODO If the end of line marker coincides with the end of data we can't be
+ * sure that we got a command and not a substring which we could tell through
+ * inspection of the next packet. Maybe a command pending state where the first
+ * char in the next packet is checked for a space and end of line marker */
+
+ /* do not confine since there could be space chars before command */
+ smtp_current_search = &config->cmd_search[0];
+ cmd_found = config->cmd_search_mpse->find(
+ (const char*)ptr, eolm - ptr, SMTP_SearchStrFound);
+ /* see if we actually found a command and not a substring */
+ if (cmd_found > 0)
+ {
+ const uint8_t* tmp = ptr;
+ const uint8_t* cmd_start = ptr + smtp_search_info.index;
+ const uint8_t* cmd_end = cmd_start + smtp_search_info.length;
+
+ /* move past spaces up until start of command */
+ while ((tmp < cmd_start) && isspace((int)*tmp))
+ tmp++;
+
+ /* if not all spaces before command, we found a
+ * substring */
+ if (tmp != cmd_start)
+ cmd_found = 0;
+
+ /* if we're before the end of line marker and the next
+ * character is not whitespace, we found a substring */
+ if ((cmd_end < eolm) && !isspace((int)*cmd_end))
+ cmd_found = 0;
+
+ /* there is a chance that end of command coincides with the end of data
+ * in which case, it could be a substring, but for now, we will treat it as found */
+ }
+
+ /* if command not found, alert and move on */
+ if (!cmd_found)
+ {
+ /* If we missed one or more packets we might not actually be in the command
+ * state. Check to see if we're encrypted */
+ if (smtp_ssn->state == STATE_UNKNOWN)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Command not found, but state is "
+ "unknown - checking for SSL\n"); );
+
+ /* check for encrypted */
+
+ if ((smtp_ssn->session_flags & SMTP_FLAG_CHECK_SSL) &&
+ (IsSSL(ptr, end - ptr, p->packet_flags)))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Packet is SSL encrypted\n"); );
+
+ smtp_ssn->state = STATE_TLS_DATA;
+
+ /* Ignore data */
+ if (config->ignore_tls_data)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Ignoring encrypted data\n"); );
+ set_alt_data(NULL, 0);
+ }
+
+ return end;
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Not SSL - try data state\n"); );
+ /* don't check for ssl again in this packet */
+ if (smtp_ssn->session_flags & SMTP_FLAG_CHECK_SSL)
+ smtp_ssn->session_flags &= ~SMTP_FLAG_CHECK_SSL;
+
+ smtp_ssn->state = STATE_DATA;
+ smtp_ssn->mime_ssn.data_state = STATE_DATA_UNKNOWN;
+
+ return ptr;
+ }
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "No known command found\n"); );
+
+ if (smtp_ssn->state != STATE_AUTH)
+ {
+ SnortEventqAdd(GID_SMTP,SMTP_UNKNOWN_CMD);
+
+ if (alert_long_command_line)
+ SnortEventqAdd(GID_SMTP, SMTP_COMMAND_OVERFLOW);
+ }
+
+ /* if normalizing, copy line to alt buffer */
+ if (smtp_normalizing)
+ {
+ ret = SMTP_CopyToAltBuffer(p, ptr, eol - ptr);
+ if (ret == -1)
+ return NULL;
+ }
+
+ return eol;
+ }
+ }
+
+ /* At this point we have definitely found a legitimate command */
+
+ /* check if max command line length for a specific command is exceeded */
+ if (config->cmd_config[smtp_search_info.id].max_line_len != 0)
+ {
+ if (cmd_line_len > config->cmd_config[smtp_search_info.id].max_line_len)
+ {
+ SnortEventqAdd(GID_SMTP, SMTP_SPECIFIC_CMD_OVERFLOW);
+ }
+ }
+ else if (alert_long_command_line)
+ {
+ SnortEventqAdd(GID_SMTP, SMTP_COMMAND_OVERFLOW);
+ }
+
+ if (config->cmd_config[smtp_search_info.id].alert)
+ {
+ /* Are we alerting on this command? */
+ SnortEventqAdd(GID_SMTP, SMTP_ILLEGAL_CMD);
+ }
+
+ switch (smtp_search_info.id)
+ {
+ /* unless we do our own parsing of MAIL and RCTP commands we have to assume they
+ * are ok unless we got a server error in which case we flush and if this is a
+ * reassembled packet, the last command in this packet will be the command that
+ * caused the error */
+ case CMD_MAIL:
+ smtp_ssn->state_flags |= SMTP_FLAG_GOT_MAIL_CMD;
+ if ( config->log_config.log_mailfrom )
+ {
+ if (!SMTP_CopyEmailID(ptr, eolm - ptr, CMD_MAIL, smtp_ssn->mime_ssn.log_state))
+ smtp_ssn->mime_ssn.log_flags |= MIME_FLAG_MAIL_FROM_PRESENT;
+ }
+
+ break;
+
+ case CMD_RCPT:
+ if ((smtp_ssn->state_flags & SMTP_FLAG_GOT_MAIL_CMD) ||
+ smtp_ssn->state == STATE_UNKNOWN)
+ {
+ smtp_ssn->state_flags |= SMTP_FLAG_GOT_RCPT_CMD;
+ }
+
+ if ( config->log_config.log_rcptto)
+ {
+ if (!SMTP_CopyEmailID(ptr, eolm - ptr, CMD_RCPT, smtp_ssn->mime_ssn.log_state))
+ smtp_ssn->mime_ssn.log_flags |= MIME_FLAG_RCPT_TO_PRESENT;
+ }
+
+ break;
+
+ case CMD_RSET:
+ case CMD_HELO:
+ case CMD_EHLO:
+ case CMD_QUIT:
+ smtp_ssn->state_flags &= ~(SMTP_FLAG_GOT_MAIL_CMD | SMTP_FLAG_GOT_RCPT_CMD);
+
+ break;
+
+ case CMD_STARTTLS:
+ /* if reassembled we flush after seeing a 220 so this should be the last
+ * command in reassembled packet and if not reassembled it should be the
+ * last line in the packet as you can't pipeline the tls hello */
+ if (eol == end)
+ smtp_ssn->state = STATE_TLS_CLIENT_PEND;
+
+ break;
+
+ case CMD_X_LINK2STATE:
+ if (config->xlink2state)
+ ParseXLink2State(config, p, smtp_ssn, ptr + smtp_search_info.index);
+
+ break;
+
+ case CMD_AUTH:
+ smtp_ssn->state = STATE_AUTH;
+ if (SMTP_IsAuthChanged(smtp_ssn, ptr + smtp_search_info.index + smtp_search_info.length,
+ eolm)
+ && (smtp_ssn->state_flags & SMTP_FLAG_ABORT))
+ {
+ SnortEventqAdd(GID_SMTP, SMTP_AUTH_ABORT_AUTH);
+ }
+ smtp_ssn->state_flags &= ~(SMTP_FLAG_ABORT);
+ break;
+
+ case CMD_ABORT:
+ smtp_ssn->state_flags |= SMTP_FLAG_ABORT;
+ break;
+
+ default:
+ switch (smtp_known_cmds[smtp_search_info.id].type)
+ {
+ case SMTP_CMD_TYPE_DATA:
+ if ((smtp_ssn->state_flags & SMTP_FLAG_GOT_RCPT_CMD) ||
+ smtp_ssn->state == STATE_UNKNOWN)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Set to data state.\n"); );
+
+ smtp_ssn->state = STATE_DATA;
+ smtp_ssn->state_flags &= ~(SMTP_FLAG_GOT_MAIL_CMD | SMTP_FLAG_GOT_RCPT_CMD);
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Didn't get MAIL -> RCPT command sequence - "
+ "stay in command state.\n"); );
+ }
+
+ break;
+
+ case SMTP_CMD_TYPE_BDATA:
+ if ((smtp_ssn->state_flags & (SMTP_FLAG_GOT_RCPT_CMD | SMTP_FLAG_BDAT))
+ || (smtp_ssn->state == STATE_UNKNOWN))
+ {
+ const uint8_t* begin_chunk;
+ const uint8_t* end_chunk;
+ const uint8_t* tmp;
+ int num_digits;
+ int ten_power;
+ uint32_t dat_chunk = 0;
+
+ begin_chunk = ptr + smtp_search_info.index + smtp_search_info.length;
+ while ((begin_chunk < eolm) && isspace((int)*begin_chunk))
+ begin_chunk++;
+
+ /* bad BDAT command - needs chunk argument */
+ if (begin_chunk == eolm)
+ break;
+
+ end_chunk = begin_chunk;
+ while ((end_chunk < eolm) && isdigit((int)*end_chunk))
+ end_chunk++;
+
+ /* didn't get all digits */
+ if ((end_chunk < eolm) && !isspace((int)*end_chunk))
+ break;
+
+ /* get chunk size */
+ num_digits = end_chunk - begin_chunk;
+
+ /* more than 9 digits could potentially overflow a 32 bit integer
+ * most servers won't accept this much in a chunk */
+ if (num_digits > 9)
+ break;
+
+ tmp = end_chunk;
+ for (ten_power = 1, tmp--; tmp >= begin_chunk; ten_power *= 10, tmp--)
+ dat_chunk += (*tmp - '0') * ten_power;
+
+ if (smtp_search_info.id == CMD_BDAT)
+ {
+ /* got a valid chunk size - check to see if this is the last chunk */
+ const uint8_t* last = end_chunk;
+ bool bdat_last = false;
+
+ while ((last < eolm) && isspace((int)*last))
+ last++;
+
+ if (((eolm - last) >= 4)
+ && (strncasecmp("LAST", (const char*)last, 4) == 0))
+ {
+ bdat_last = true;
+ }
+
+ if (bdat_last || (dat_chunk == 0))
+ smtp_ssn->state_flags &= ~(SMTP_FLAG_BDAT);
+ else
+ smtp_ssn->state_flags |= SMTP_FLAG_BDAT;
+
+ smtp_ssn->state = STATE_BDATA;
+ smtp_ssn->state_flags &= ~(SMTP_FLAG_GOT_MAIL_CMD | SMTP_FLAG_GOT_RCPT_CMD);
+ }
+ else if (smtp_search_info.id == CMD_XEXCH50)
+ {
+ smtp_ssn->state = STATE_XEXCH50;
+ }
+ else
+ {
+ smtp_ssn->state = STATE_BDATA;
+ smtp_ssn->state_flags &= ~(SMTP_FLAG_GOT_MAIL_CMD | SMTP_FLAG_GOT_RCPT_CMD);
+ }
+
+ smtp_ssn->dat_chunk = dat_chunk;
+ }
+
+ break;
+
+ case SMTP_CMD_TYPE_AUTH:
+ smtp_ssn->state = STATE_AUTH;
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ /* Since we found a command, if state is still unknown,
+ * set to command state */
+ if (smtp_ssn->state == STATE_UNKNOWN)
+ smtp_ssn->state = STATE_COMMAND;
+
+ /* normalize command line */
+ if (config->normalize == NORMALIZE_ALL ||
+ config->cmd_config[smtp_search_info.id].normalize)
+ {
+ ret = SMTP_NormalizeCmd(p, ptr, eolm, eol);
+ if (ret == -1)
+ return NULL;
+ }
+ else if (smtp_normalizing) /* Already normalizing */
+ {
+ ret = SMTP_CopyToAltBuffer(p, ptr, eol - ptr);
+ if (ret == -1)
+ return NULL;
+ }
+
+ return eol;
+}
+
+static int SMTP_NormalizeData(void* conf, void* pkt, const uint8_t* ptr, const uint8_t* data_end)
+{
+ Packet* p = (Packet*)pkt;
+ SMTP_PROTO_CONF* config = (SMTP_PROTO_CONF*)conf;
+
+ /* if we're ignoring data and not already normalizing, copy everything
+ * up to here into alt buffer so detection engine doesn't have
+ * to look at the data; otherwise, if we're normalizing and not
+ * ignoring data, copy all of the data into the alt buffer */
+ if (config->decode_conf.ignore_data && !smtp_normalizing)
+ {
+ return SMTP_CopyToAltBuffer(p, p->data, ptr - p->data);
+ }
+ else if (!config->decode_conf.ignore_data && smtp_normalizing)
+ {
+ return SMTP_CopyToAltBuffer(p, ptr, data_end - ptr);
+ }
+
+ return 0;
+}
+
+static int SMTP_HandleHeaderLine(void* conf, void* pkt, const uint8_t* ptr, const uint8_t* eol,
+ int max_header_len, void* ssn)
+{
+ int ret;
+ int header_line_len;
+ Packet* p = (Packet*)pkt;
+ SMTP_PROTO_CONF* config = (SMTP_PROTO_CONF*)conf;
+ MimeState* mime_ssn = (MimeState*)ssn;
+ /* get length of header line */
+ header_line_len = eol - ptr;
+
+ if (max_header_len)
+ SnortEventqAdd(GID_SMTP, SMTP_HEADER_NAME_OVERFLOW);
+
+ if ((config->max_header_line_len != 0) &&
+ (header_line_len > config->max_header_line_len))
+ {
+ if (mime_ssn->data_state != STATE_DATA_UNKNOWN)
+ {
+ SnortEventqAdd(GID_SMTP, SMTP_DATA_HDR_OVERFLOW);
+ }
+ else
+ {
+ /* assume we guessed wrong and are in the body */
+ return 1;
+ }
+ }
+
+ /* XXX Does VRT want data headers normalized?
+ * currently the code does not normalize headers */
+ if (smtp_normalizing)
+ {
+ ret = SMTP_CopyToAltBuffer(p, ptr, eol - ptr);
+ if (ret == -1)
+ return (-1);
+ }
+
+ if (config->log_config.log_email_hdrs)
+ {
+ if (mime_ssn->data_state == STATE_DATA_HEADER)
+ {
+ ret = SMTP_CopyEmailHdrs(ptr, eol - ptr, mime_ssn->log_state);
+ if (ret == 0)
+ mime_ssn->log_flags |= MIME_FLAG_EMAIL_HDRS_PRESENT;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Process client packet
+ *
+ * @param packet standard Packet structure
+ *
+ * @return none
+ */
+static void SMTP_ProcessClientPacket(SMTP_PROTO_CONF* config, Packet* p, SMTPData* smtp_ssn)
+{
+ const uint8_t* ptr = p->data;
+ const uint8_t* end = p->data + p->dsize;
+
+ if (smtp_ssn->state == STATE_CONNECT)
+ {
+ smtp_ssn->state = STATE_COMMAND;
+ }
+
+ while ((ptr != NULL) && (ptr < end))
+ {
+ switch (smtp_ssn->state)
+ {
+ case STATE_COMMAND:
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "COMMAND STATE ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
+ ptr = SMTP_HandleCommand(config, p, smtp_ssn, ptr, end);
+ break;
+ case STATE_DATA:
+ case STATE_BDATA:
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "DATA STATE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
+ ptr = file_api->process_mime_data(p, ptr, end, &(smtp_ssn->mime_ssn), 1, true);
+ //ptr = SMTP_HandleData(p, ptr, end, &(smtp_ssn->mime_ssn));
+ break;
+ case STATE_XEXCH50:
+ if (smtp_normalizing)
+ SMTP_CopyToAltBuffer(p, ptr, end - ptr);
+ if (smtp_is_data_end (p->flow))
+ smtp_ssn->state = STATE_COMMAND;
+ return;
+ case STATE_AUTH:
+ ptr = SMTP_HandleCommand(config, p, smtp_ssn, ptr, end);
+ break;
+ case STATE_UNKNOWN:
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "UNKNOWN STATE ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
+ /* If state is unknown try command state to see if we can
+ * regain our bearings */
+ ptr = SMTP_HandleCommand(config, p, smtp_ssn, ptr, end);
+ break;
+ default:
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Bad SMTP state\n"); );
+ return;
+ }
+ }
+
+#ifdef DEBUG_MSGS
+ if (smtp_normalizing)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Normalized data\n%s\n", SMTP_PrintBuffer(p)); );
+ }
+#endif
+}
+
+/*
+ * Process server packet
+ *
+ * @param packet standard Packet structure
+ *
+ * @return None
+ */
+static void SMTP_ProcessServerPacket(SMTP_PROTO_CONF* config, Packet* p, SMTPData* smtp_ssn,
+ int* next_state)
+{
+ int resp_found;
+ const uint8_t* ptr;
+ const uint8_t* end;
+ const uint8_t* eolm;
+ const uint8_t* eol;
+ int resp_line_len;
+#ifdef DEBUG_MSGS
+ const uint8_t* dash;
+#endif
+
+ *next_state = 0;
+
+ ptr = p->data;
+ end = p->data + p->dsize;
+
+ if (smtp_ssn->state == STATE_TLS_SERVER_PEND)
+ {
+ if (IsTlsServerHello(ptr, end))
+ {
+ smtp_ssn->state = STATE_TLS_DATA;
+ }
+ else if (!(stream.get_session_flags(p->flow) & SSNFLAG_MIDSTREAM)
+ && !stream.missed_packets(p->flow, SSN_DIR_BOTH))
+ {
+ /* Check to see if the raw packet is in order */
+ if (p->packet_flags & PKT_STREAM_ORDER_OK)
+ {
+ /* revert back to command state - assume server didn't accept STARTTLS */
+ smtp_ssn->state = STATE_COMMAND;
+ }
+ else
+ return;
+ }
+ }
+
+ if (smtp_ssn->state == STATE_TLS_DATA)
+ {
+ smtp_ssn->state = STATE_COMMAND;
+ }
+
+ while (ptr < end)
+ {
+ SMTP_GetEOL(ptr, end, &eol, &eolm);
+
+ resp_line_len = eol - ptr;
+
+ /* Check for response code */
+ smtp_current_search = &smtp_resp_search[0];
+ resp_found = smtp_resp_search_mpse->find(
+ (const char*)ptr, resp_line_len, SMTP_SearchStrFound);
+
+ if (resp_found > 0)
+ {
+ switch (smtp_search_info.id)
+ {
+ case RESP_220:
+ /* This is either an initial server response or a STARTTLS response */
+ if (smtp_ssn->state == STATE_CONNECT)
+ smtp_ssn->state = STATE_COMMAND;
+ break;
+
+ case RESP_250:
+ case RESP_221:
+ case RESP_334:
+ case RESP_354:
+ break;
+
+ case RESP_235:
+ // Auth done
+ *next_state = STATE_COMMAND;
+ break;
+
+ default:
+ if (smtp_ssn->state != STATE_COMMAND)
+ {
+ *next_state = STATE_COMMAND;
+ }
+ break;
+ }
+
+#ifdef DEBUG_MSGS
+ dash = ptr + smtp_search_info.index + smtp_search_info.length;
+
+ /* only add response if not a dash after response code */
+ if ((dash == eolm) || ((dash < eolm) && (*dash != '-')))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Server sent %s response\n",
+ smtp_resps[smtp_search_info.id].name); );
+ }
+#endif
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP,
+ "Server response not found - see if it's SSL data\n"); );
+
+ if ((smtp_ssn->session_flags & SMTP_FLAG_CHECK_SSL) &&
+ (IsSSL(ptr, end - ptr, p->packet_flags)))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Server response is an SSL packet\n"); );
+
+ smtp_ssn->state = STATE_TLS_DATA;
+
+ /* Ignore data */
+ if (config->ignore_tls_data)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Ignoring Server TLS encrypted data\n"); );
+ set_alt_data(NULL, 0);
+ }
+
+ return;
+ }
+ else if (smtp_ssn->session_flags & SMTP_FLAG_CHECK_SSL)
+ {
+ smtp_ssn->session_flags &= ~SMTP_FLAG_CHECK_SSL;
+ }
+ }
+
+ if ((config->max_response_line_len != 0) &&
+ (resp_line_len > config->max_response_line_len))
+ {
+ SnortEventqAdd(GID_SMTP, SMTP_RESPONSE_OVERFLOW);
+ }
+
+ ptr = eol;
+ }
+}
+
+/*
+ * Entry point to snort preprocessor for each packet
+ *
+ * @param packet standard Packet structure
+ *
+ * @return none
+ */
+static void snort_smtp(SMTP_PROTO_CONF* config, Packet* p)
+{
+ int pkt_dir;
+
+ /* Attempt to get a previously allocated SMTP block. */
+
+ SMTPData* smtp_ssn = get_session_data(p->flow);
+
+ if (smtp_ssn == NULL)
+ {
+ /* Check the stream session. If it does not currently
+ * * have our SMTP data-block attached, create one.
+ * */
+ smtp_ssn = SetNewSMTPData(config, p);
+
+ if ( !smtp_ssn )
+ {
+ /* Could not get/create the session data for this packet. */
+ return;
+ }
+ }
+
+ pkt_dir = SMTP_Setup(p, smtp_ssn);
+ SMTP_ResetAltBuffer();
+
+ /* reset normalization stuff */
+ smtp_normalizing = 0;
+ SetDetectLimit(p, 0);
+
+ if (pkt_dir == SMTP_PKT_FROM_SERVER)
+ {
+ int next_state = 0;
+
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "SMTP server packet\n"); );
+
+ /* Process as a server packet */
+ SMTP_ProcessServerPacket(config, p, smtp_ssn, &next_state);
+
+ if (next_state)
+ smtp_ssn->state = next_state;
+ }
+ else
+ {
+#ifdef DEBUG_MSGS
+ if (pkt_dir == SMTP_PKT_FROM_CLIENT)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "SMTP client packet\n"); );
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "SMTP packet NOT from client or server! "
+ "Processing as a client packet\n"); );
+ }
+#endif
+
+ /* This packet should be a tls client hello */
+ if (smtp_ssn->state == STATE_TLS_CLIENT_PEND)
+ {
+ if (IsTlsClientHello(p->data, p->data + p->dsize))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP,
+ "TLS DATA STATE ~~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
+
+ smtp_ssn->state = STATE_TLS_SERVER_PEND;
+ }
+ else if (p->packet_flags & PKT_STREAM_ORDER_OK)
+ {
+ /* reset state - server may have rejected STARTTLS command */
+ smtp_ssn->state = STATE_COMMAND;
+ }
+ }
+
+ if ((smtp_ssn->state == STATE_TLS_DATA)
+ || (smtp_ssn->state == STATE_TLS_SERVER_PEND))
+ {
+ /* if we're ignoring tls data, set a zero length alt buffer */
+ if (config->ignore_tls_data)
+ {
+ set_alt_data(NULL, 0);
+ stream.stop_inspection(p->flow, p, SSN_DIR_BOTH, -1, 0);
+ return;
+ }
+ }
+ else
+ {
+ if ( !InspectPacket(p))
+ {
+ /* Packet will be rebuilt, so wait for it */
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Client packet will be reassembled\n"));
+ return;
+ }
+ else if (!(p->packet_flags & PKT_REBUILT_STREAM))
+ {
+ /* If this isn't a reassembled packet and didn't get
+ * inserted into reassembly buffer, there could be a
+ * problem. If we miss syn or syn-ack that had window
+ * scaling this packet might not have gotten inserted
+ * into reassembly buffer because it fell outside of
+ * window, because we aren't scaling it */
+ smtp_ssn->session_flags |= SMTP_FLAG_GOT_NON_REBUILT;
+ smtp_ssn->state = STATE_UNKNOWN;
+ }
+ else if ((smtp_ssn->session_flags & SMTP_FLAG_GOT_NON_REBUILT))
+ {
+ /* This is a rebuilt packet. If we got previous packets
+ * that were not rebuilt, state is going to be messed up
+ * so set state to unknown. It's likely this was the
+ * beginning of the conversation so reset state */
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Got non-rebuilt packets before "
+ "this rebuilt packet\n"); );
+
+ smtp_ssn->state = STATE_UNKNOWN;
+ smtp_ssn->session_flags &= ~SMTP_FLAG_GOT_NON_REBUILT;
+ }
+
+#ifdef DEBUG_MSGS
+ /* Interesting to see how often packets are rebuilt */
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Payload: %s\n%s\n",
+ (p->packet_flags & PKT_REBUILT_STREAM) ?
+ "reassembled" : "not reassembled",
+ SMTP_PrintBuffer(p)); );
+#endif
+
+ SMTP_ProcessClientPacket(config, p, smtp_ssn);
+ }
+ }
+
+ SMTP_LogFuncs(config, p, &(smtp_ssn->mime_ssn));
+}
+
+/* Callback to return the MIME attachment filenames accumulated */
+int SMTP_GetFilename(Flow* flow, uint8_t** buf, uint32_t* len, uint32_t* type)
+{
+ SMTPData* ssn = get_session_data(flow);
+
+ if (ssn == NULL)
+ return 0;
+
+ *buf = ssn->mime_ssn.log_state->file_log.filenames;
+ *len = ssn->mime_ssn.log_state->file_log.file_logged;
+ *type = EVENT_INFO_SMTP_FILENAME;
+ return 1;
+}
+
+/* Callback to return the email addresses accumulated from the MAIL FROM command */
+int SMTP_GetMailFrom(Flow* flow, uint8_t** buf, uint32_t* len, uint32_t* type)
+{
+ SMTPData* ssn = get_session_data(flow);
+
+ if (ssn == NULL)
+ return 0;
+
+ *buf = ssn->mime_ssn.log_state->senders;
+ *len = ssn->mime_ssn.log_state->snds_logged;
+ *type = EVENT_INFO_SMTP_MAILFROM;
+ return 1;
+}
+
+/* Callback to return the email addresses accumulated from the RCP TO command */
+int SMTP_GetRcptTo(Flow* flow, uint8_t** buf, uint32_t* len, uint32_t* type)
+{
+ SMTPData* ssn = get_session_data(flow);
+
+ if (ssn == NULL)
+ return 0;
+
+ *buf = ssn->mime_ssn.log_state->recipients;
+ *len = ssn->mime_ssn.log_state->rcpts_logged;
+ *type = EVENT_INFO_SMTP_RCPTTO;
+ return 1;
+}
+
+/* Calback to return the email headers */
+int SMTP_GetEmailHdrs(Flow* flow, uint8_t** buf, uint32_t* len, uint32_t* type)
+{
+ SMTPData* ssn = get_session_data(flow);
+
+ if (ssn == NULL)
+ return 0;
+
+ *buf = ssn->mime_ssn.log_state->emailHdrs;
+ *len = ssn->mime_ssn.log_state->hdrs_logged;
+ *type = EVENT_INFO_SMTP_EMAIL_HDRS;
+ return 1;
+}
+
+static void SMTP_RegXtraDataFuncs(SMTP_PROTO_CONF* config)
+{
+ config->xtra_filename_id = stream.reg_xtra_data_cb(SMTP_GetFilename);
+ config->xtra_mfrom_id = stream.reg_xtra_data_cb(SMTP_GetMailFrom);
+ config->xtra_rcptto_id = stream.reg_xtra_data_cb(SMTP_GetRcptTo);
+ config->xtra_ehdrs_id = stream.reg_xtra_data_cb(SMTP_GetEmailHdrs);
+}
+
+//-------------------------------------------------------------------------
+// class stuff
+//-------------------------------------------------------------------------
+
+class Smtp : public Inspector
+{
+public:
+ Smtp(SMTP_PROTO_CONF*);
+ ~Smtp();
+
+ bool configure(SnortConfig*) override;
+ void show(SnortConfig*) override;
+ void eval(Packet*) override;
+
+ StreamSplitter* get_splitter(bool c2s) override
+ { return new SmtpSplitter(c2s); }
+
+private:
+ SMTP_PROTO_CONF* config;
+};
+
+Smtp::Smtp(SMTP_PROTO_CONF* pc)
+{
+ config = pc;
+}
+
+Smtp::~Smtp()
+{
+ if ( config )
+ delete config;
+}
+
+bool Smtp::configure(SnortConfig*)
+{
+ config->decode_conf.file_depth = file_api->get_max_file_depth();
+
+ if (config->decode_conf.file_depth > 0)
+ config->log_config.log_filename = 1;
+
+ if (file_api->is_decoding_enabled(&config->decode_conf) )
+ {
+ updateMaxDepth(config->decode_conf.file_depth,
+ &config->decode_conf.max_depth);
+ }
+ file_api->check_decode_config(&config->decode_conf);
+
+ return true;
+}
+
+void Smtp::show(SnortConfig*)
+{
+ SMTP_PrintConfig(config);
+}
+
+void Smtp::eval(Packet* p)
+{
+ PROFILE_VARS;
+ // precondition - what we registered for
+ assert(p->is_tcp() && p->dsize && p->data);
+
+ ++smtpstats.total_packets;
+
+ MODULE_PROFILE_START(smtpPerfStats);
+
+ snort_smtp(config, p);
+
+ MODULE_PROFILE_END(smtpPerfStats);
+}
+
+//-------------------------------------------------------------------------
+// api stuff
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{ return new SmtpModule; }
+
+static void mod_dtor(Module* m)
+{ delete m; }
+
+static void smtp_init()
+{
+ SmtpFlowData::init();
+ SMTP_ResponseSearchInit();
+}
+
+static void smtp_term()
+{
+ SMTP_SearchFree();
+}
+
+static Inspector* smtp_ctor(Module* m)
+{
+ SmtpModule* mod = (SmtpModule*)m;
+ SMTP_PROTO_CONF* conf = mod->get_data();
+ unsigned i = 0;
+ SMTP_RegXtraDataFuncs(conf);
+ SMTP_InitCmds(conf);
+
+ while ( const SmtpCmd* cmd = mod->get_cmd(i++) )
+ ProcessSmtpCmdsList(conf, cmd);
+
+ SMTP_CommandSearchInit(conf);
+
+ return new Smtp(conf);
+}
+
+static void smtp_dtor(Inspector* p)
+{
+ delete p;
+}
+
+const InspectApi smtp_api =
+{
+ {
+ PT_INSPECTOR,
+ sizeof(InspectApi),
+ INSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ SMTP_NAME,
+ SMTP_HELP,
+ mod_ctor,
+ mod_dtor
+ },
+ IT_SERVICE,
+ (uint16_t)PktType::TCP,
+ nullptr, // buffers
+ "smtp",
+ smtp_init,
+ smtp_term,
+ nullptr, // tinit
+ nullptr, // tterm
+ smtp_ctor,
+ smtp_dtor,
+ nullptr, // ssn
+ nullptr // reset
+};
+
+#ifdef BUILDING_SO
+SO_PUBLIC const BaseApi* snort_plugins[] =
+{
+ &smtp_api.base,
+ nullptr
+};
+#else
+const BaseApi* sin_smtp = &smtp_api.base;
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+/*
+ * smtp.h: Definitions, structs, function prototype(s) for
+ * the SMTP service inspectors.
+ * Author: Bhagyashree Bantwal <bbantwal@cisco.com>
+ */
+
+#ifndef SMTP_H
+#define SMTP_H
+
+#include "protocols/packet.h"
+#include "stream/stream_api.h"
+#include "profiler.h"
+#include "smtp_config.h"
+
+/* Direction packet is coming from, if we can figure it out */
+#define SMTP_PKT_FROM_UNKNOWN 0
+#define SMTP_PKT_FROM_CLIENT 1
+#define SMTP_PKT_FROM_SERVER 2
+
+/* Inspection type */
+#define SMTP_STATELESS 0
+#define SMTP_STATEFUL 1
+
+#define SEARCH_CMD 0
+#define SEARCH_RESP 1
+#define SEARCH_HDR 2
+#define SEARCH_DATA_END 3
+#define NUM_SEARCHES 4
+
+#define BOUNDARY 0
+
+#define STATE_CONNECT 0
+#define STATE_COMMAND 1 /* Command state of SMTP transaction */
+#define STATE_DATA 2 /* Data state */
+#define STATE_BDATA 3 /* Binary data state */
+#define STATE_TLS_CLIENT_PEND 4 /* Got STARTTLS */
+#define STATE_TLS_SERVER_PEND 5 /* Got STARTTLS */
+#define STATE_TLS_DATA 6 /* Successful handshake, TLS encrypted data */
+#define STATE_AUTH 7
+#define STATE_XEXCH50 8
+#define STATE_UNKNOWN 9
+
+#define STATE_DATA_INIT 0
+#define STATE_DATA_HEADER 1 /* Data header section of data state */
+#define STATE_DATA_BODY 2 /* Data body section of data state */
+#define STATE_MIME_HEADER 3 /* MIME header section within data section */
+#define STATE_DATA_UNKNOWN 4
+
+/* state flags */
+#define SMTP_FLAG_GOT_MAIL_CMD 0x00000001
+#define SMTP_FLAG_GOT_RCPT_CMD 0x00000002
+#define SMTP_FLAG_BDAT 0x00001000
+#define SMTP_FLAG_ABORT 0x00002000
+/* state flags */
+#define SMTP_FLAG_GOT_MAIL_CMD 0x00000001
+#define SMTP_FLAG_GOT_RCPT_CMD 0x00000002
+#define SMTP_FLAG_BDAT 0x00001000
+#define SMTP_FLAG_ABORT 0x00002000
+
+/* session flags */
+#define SMTP_FLAG_XLINK2STATE_GOTFIRSTCHUNK 0x00000001
+#define SMTP_FLAG_XLINK2STATE_ALERTED 0x00000002
+#define SMTP_FLAG_NEXT_STATE_UNKNOWN 0x00000004
+#define SMTP_FLAG_GOT_NON_REBUILT 0x00000008
+#define SMTP_FLAG_CHECK_SSL 0x00000010
+
+#define SMTP_SSL_ERROR_FLAGS \
+ (SSL_BOGUS_HS_DIR_FLAG | \
+ SSL_BAD_VER_FLAG | \
+ SSL_BAD_TYPE_FLAG | \
+ SSL_UNKNOWN_FLAG)
+
+/* Maximum length of header chars before colon, based on Exim 4.32 exploit */
+#define MAX_HEADER_NAME_LEN 64
+
+#define MAX_AUTH_NAME_LEN 20 /* Max length of SASL mechanisms, defined in RFC 4422 */
+
+enum SMTPRespEnum
+{
+ RESP_220 = 0,
+ RESP_221,
+ RESP_235,
+ RESP_250,
+ RESP_334,
+ RESP_354,
+ RESP_421,
+ RESP_450,
+ RESP_451,
+ RESP_452,
+ RESP_500,
+ RESP_501,
+ RESP_502,
+ RESP_503,
+ RESP_504,
+ RESP_535,
+ RESP_550,
+ RESP_551,
+ RESP_552,
+ RESP_553,
+ RESP_554,
+ RESP_LAST
+};
+
+enum SMTPHdrEnum
+{
+ HDR_CONTENT_TYPE = 0,
+ HDR_CONT_TRANS_ENC,
+ HDR_CONT_DISP,
+ HDR_LAST
+};
+
+enum SMTPDataEndEnum
+{
+ DATA_END_1 = 0,
+ DATA_END_2,
+ DATA_END_3,
+ DATA_END_4,
+ DATA_END_LAST
+};
+
+struct SMTPSearchInfo
+{
+ int id;
+ int index;
+ int length;
+};
+
+struct SMTPAuthName
+{
+ int length;
+ char name[MAX_AUTH_NAME_LEN];
+};
+
+struct SMTPData
+{
+ int state;
+ int state_flags;
+ int session_flags;
+ uint32_t dat_chunk;
+ MimeState mime_ssn;
+ SMTPAuthName* auth_name;
+};
+
+class SmtpFlowData : public FlowData
+{
+public:
+ SmtpFlowData() : FlowData(flow_id)
+ { memset(&session, 0, sizeof(session)); }
+
+ ~SmtpFlowData() { }
+
+ static void init()
+ { flow_id = FlowData::get_flow_id(); }
+
+public:
+ static unsigned flow_id;
+ SMTPData session;
+};
+
+#endif /* SMTP_H */
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+
+#ifndef SMTP_CONFIG_H
+#define SMTP_CONFIG_H
+
+#include "file_api/file_api.h"
+#include "search_engines/search_tool.h"
+enum NORM_TYPES
+{
+ NORMALIZE_NONE = 0,
+ NORMALIZE_CMDS,
+ NORMALIZE_ALL
+};
+
+enum XLINK2STATE
+{
+ DISABLE_XLINK2STATE = 0,
+ ALERT_XLINK2STATE,
+ DROP_XLINK2STATE
+};
+
+enum SMTPCmdEnum
+{
+ CMD_ATRN = 0,
+ CMD_AUTH,
+ CMD_BDAT,
+ CMD_DATA,
+ CMD_DEBUG,
+ CMD_EHLO,
+ CMD_EMAL,
+ CMD_ESAM,
+ CMD_ESND,
+ CMD_ESOM,
+ CMD_ETRN,
+ CMD_EVFY,
+ CMD_EXPN,
+ CMD_HELO,
+ CMD_HELP,
+ CMD_IDENT,
+ CMD_MAIL,
+ CMD_NOOP,
+ CMD_ONEX,
+ CMD_QUEU,
+ CMD_QUIT,
+ CMD_RCPT,
+ CMD_RSET,
+ CMD_SAML,
+ CMD_SEND,
+ CMD_SIZE,
+ CMD_STARTTLS,
+ CMD_SOML,
+ CMD_TICK,
+ CMD_TIME,
+ CMD_TURN,
+ CMD_TURNME,
+ CMD_VERB,
+ CMD_VRFY,
+ CMD_X_EXPS,
+ CMD_XADR,
+ CMD_XAUTH,
+ CMD_XCIR,
+ CMD_XEXCH50,
+ CMD_XGEN,
+ CMD_XLICENSE,
+ CMD_X_LINK2STATE,
+ CMD_XQUE,
+ CMD_XSTA,
+ CMD_XTRN,
+ CMD_XUSR,
+ CMD_ABORT,
+ CMD_LAST
+};
+
+enum SMTPCmdTypeEnum
+{
+ SMTP_CMD_TYPE_NORMAL = 0,
+ SMTP_CMD_TYPE_DATA,
+ SMTP_CMD_TYPE_BDATA,
+ SMTP_CMD_TYPE_AUTH,
+ SMTP_CMD_TYPE_LAST
+};
+
+struct SMTPCmdConfig
+{
+ bool alert;
+ bool normalize; /* 1 if we should normalize this command */
+ int max_line_len; /* Max length of this particular command */
+};
+
+struct SMTPSearch
+{
+ char* name;
+ int name_len;
+};
+
+struct SMTPToken
+{
+ const char* name;
+ int name_len;
+ int search_id;
+ SMTPCmdTypeEnum type;
+};
+
+struct SMTP_PROTO_CONF
+{
+ NORM_TYPES normalize;
+ bool ignore_tls_data;
+ int max_command_line_len;
+ int max_header_line_len;
+ int max_response_line_len;
+ int xlink2state;
+ MAIL_LogConfig log_config;
+ DecodeConfig decode_conf;
+
+ uint32_t xtra_filename_id;
+ uint32_t xtra_mfrom_id;
+ uint32_t xtra_rcptto_id;
+ uint32_t xtra_ehdrs_id;
+
+ int num_cmds;
+ SMTPToken* cmds;
+ SMTPCmdConfig* cmd_config;
+ SMTPSearch* cmd_search;
+ SearchTool* cmd_search_mpse;
+};
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// smtp_module.cc author Bhagyashree Bantwal <bbantwal@cisco.com>
+
+#include "smtp_module.h"
+#include <assert.h>
+#include <sstream>
+#include "main/snort_config.h"
+
+using namespace std;
+
+SmtpCmd::SmtpCmd(std::string& key, uint32_t flg, int num)
+{
+ name = key;
+ flags = flg;
+ number = num;
+}
+
+SmtpCmd::SmtpCmd(std::string& key, int num)
+{
+ name = key;
+
+ flags = PCMD_ALT;
+ number = 0;
+
+ if ( num >= 0 )
+ {
+ number = num;
+ flags |= PCMD_LEN;
+ }
+}
+
+#define SMTP_COMMAND_OVERFLOW_STR "Attempted command buffer overflow"
+#define SMTP_DATA_HDR_OVERFLOW_STR "Attempted data header buffer overflow"
+#define SMTP_RESPONSE_OVERFLOW_STR "Attempted response buffer overflow"
+#define SMTP_SPECIFIC_CMD_OVERFLOW_STR "Attempted specific command buffer overflow"
+#define SMTP_UNKNOWN_CMD_STR "Unknown command"
+#define SMTP_ILLEGAL_CMD_STR "Illegal command"
+#define SMTP_HEADER_NAME_OVERFLOW_STR "Attempted header name buffer overflow"
+#define SMTP_XLINK2STATE_OVERFLOW_STR "Attempted X-Link2State command buffer overflow"
+#define SMTP_B64_DECODING_FAILED_STR "Base64 Decoding failed."
+#define SMTP_QP_DECODING_FAILED_STR "Quoted-Printable Decoding failed."
+#define SMTP_UU_DECODING_FAILED_STR "Unix-to-Unix Decoding failed."
+#define SMTP_AUTH_ABORT_AUTH_STR "Cyrus SASL authentication attack."
+
+static const Parameter smtp_command_params[] =
+{
+ { "command", Parameter::PT_STRING, nullptr, nullptr,
+ "command string" },
+
+ { "length", Parameter::PT_INT, "0:", "0",
+ "specify non-default maximum for command" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+static const Parameter s_params[] =
+{
+ { "alt_max_command_line_len", Parameter::PT_LIST, smtp_command_params, nullptr,
+ "overrides max_command_line_len for specific commands" },
+
+ { "auth_cmds", Parameter::PT_STRING, nullptr, nullptr,
+ "commands that initiate an authentication exchange" },
+
+ { "binary_data_cmds", Parameter::PT_STRING, nullptr, nullptr,
+ "commands that initiate sending of data and use a length value after the command" },
+
+ { "bitenc_decode_depth", Parameter::PT_INT, "-1:65535", "25",
+ "depth used to extract the non-encoded MIME attachments" },
+
+ { "b64_decode_depth", Parameter::PT_INT, "-1:65535", "25",
+ "depth used to decode the base64 encoded MIME attachments" },
+
+ { "data_cmds", Parameter::PT_STRING, nullptr, nullptr,
+ "commands that initiate sending of data with an end of data delimiter" },
+
+ { "email_hdrs_log_depth", Parameter::PT_INT, "0:20480", "1464",
+ "depth for logging email headers" },
+
+ { "ignore_data", Parameter::PT_BOOL, nullptr, "false",
+ "ignore data section of mail" },
+
+ { "ignore_tls_data", Parameter::PT_BOOL, nullptr, "false",
+ "ignore TLS-encrypted data when processing rules" },
+
+ { "invalid_cmds", Parameter::PT_STRING, nullptr, nullptr,
+ "alert if this command is sent from client side" },
+
+ { "log_email_hdrs", Parameter::PT_BOOL, nullptr, "false",
+ "log the SMTP email headers extracted from SMTP data" },
+
+ { "log_filename", Parameter::PT_BOOL, nullptr, "false",
+ "log the MIME attachment filenames extracted from the Content-Disposition header within the MIME body" },
+
+ { "log_mailfrom", Parameter::PT_BOOL, nullptr, "false",
+ "log the sender's email address extracted from the MAIL FROM command" },
+
+ { "log_rcptto", Parameter::PT_BOOL, nullptr, "false",
+ "log the recipient's email address extracted from the RCPT TO command" },
+
+ { "max_command_line_len", Parameter::PT_INT, "0:65535", "0",
+ "max Command Line Length" },
+
+ { "max_header_line_len", Parameter::PT_INT, "0:65535", "0",
+ "max SMTP DATA header line" },
+
+ { "max_response_line_len", Parameter::PT_INT, "0:65535", "0",
+ "max SMTP response line" },
+
+ { "normalize", Parameter::PT_ENUM, "none | cmds | all", "none",
+ "turns on/off normalization" },
+
+ { "normalize_cmds", Parameter::PT_STRING, nullptr, nullptr,
+ "list of commands to normalize" },
+
+ { "qp_decode_depth", Parameter::PT_INT, "-1:65535", "25",
+ "quoted-Printable decoding depth" },
+
+ { "uu_decode_depth", Parameter::PT_INT, "-1:65535", "25",
+ "unix-to-Unix decoding depth" },
+
+ { "valid_cmds", Parameter::PT_STRING, nullptr, nullptr,
+ "list of valid commands" },
+
+ { "xlink2state", Parameter::PT_ENUM, "disable | alert | drop", "alert",
+ "enable/disable xlink2state alert" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+static const RuleMap smtp_rules[] =
+{
+ { SMTP_COMMAND_OVERFLOW, SMTP_COMMAND_OVERFLOW_STR },
+ { SMTP_DATA_HDR_OVERFLOW, SMTP_DATA_HDR_OVERFLOW_STR },
+ { SMTP_RESPONSE_OVERFLOW, SMTP_RESPONSE_OVERFLOW_STR },
+ { SMTP_SPECIFIC_CMD_OVERFLOW, SMTP_SPECIFIC_CMD_OVERFLOW_STR },
+ { SMTP_UNKNOWN_CMD, SMTP_UNKNOWN_CMD_STR },
+ { SMTP_ILLEGAL_CMD, SMTP_ILLEGAL_CMD_STR },
+ { SMTP_HEADER_NAME_OVERFLOW, SMTP_HEADER_NAME_OVERFLOW_STR },
+ { SMTP_XLINK2STATE_OVERFLOW, SMTP_XLINK2STATE_OVERFLOW_STR },
+ { SMTP_B64_DECODING_FAILED, SMTP_B64_DECODING_FAILED_STR },
+ { SMTP_QP_DECODING_FAILED, SMTP_QP_DECODING_FAILED_STR },
+ { SMTP_UU_DECODING_FAILED, SMTP_UU_DECODING_FAILED_STR },
+ { SMTP_AUTH_ABORT_AUTH, SMTP_AUTH_ABORT_AUTH_STR },
+
+ { 0, nullptr }
+};
+
+//-------------------------------------------------------------------------
+// smtp module
+//-------------------------------------------------------------------------
+
+SmtpModule::SmtpModule() : Module(SMTP_NAME, SMTP_HELP, s_params)
+{
+ config = nullptr;
+}
+
+SmtpModule::~SmtpModule()
+{
+ if ( config )
+ {
+ if (config->cmds != NULL)
+ {
+ SMTPToken* tmp = config->cmds;
+
+ for (; tmp->name != NULL; tmp++)
+ free((char *)tmp->name);
+
+ free(config->cmds);
+ }
+
+ if (config->cmd_config != NULL)
+ free(config->cmd_config);
+
+ if (config->cmd_search_mpse != NULL)
+ delete config->cmd_search_mpse;
+
+ if (config->cmd_search != NULL)
+ free(config->cmd_search);
+
+ delete config;
+ }
+
+ for ( auto p : cmds )
+ delete p;
+}
+
+const RuleMap* SmtpModule::get_rules() const
+{ return smtp_rules; }
+
+const PegInfo* SmtpModule::get_pegs() const
+{ return simple_pegs; }
+
+PegCount* SmtpModule::get_counts() const
+{ return (PegCount*)&smtpstats; }
+
+ProfileStats* SmtpModule::get_profile() const
+{ return &smtpPerfStats; }
+
+void SmtpModule::add_commands(
+ Value& v, uint32_t flags)
+{
+ string tok;
+ v.set_first_token();
+
+ while ( v.get_next_token(tok) )
+ cmds.push_back(new SmtpCmd(tok, flags, 0));
+}
+
+const SmtpCmd* SmtpModule::get_cmd(unsigned idx)
+{
+ if ( idx < cmds.size() )
+ return cmds[idx];
+ else
+ return nullptr;
+}
+
+bool SmtpModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( v.is("auth_cmds") )
+ add_commands(v, PCMD_AUTH);
+
+ else if ( v.is("binary_data_cmds") )
+ add_commands(v, PCMD_BDATA);
+
+ else if ( v.is("b64_decode_depth") )
+ {
+ int decode_depth = v.get_long();
+
+ if ((decode_depth > 0) && (decode_depth & 3))
+ {
+ decode_depth += 4 - (decode_depth & 3);
+ if (decode_depth > 65535 )
+ {
+ decode_depth = decode_depth - 4;
+ }
+ LogMessage("WARNING: SMTP: 'b64_decode_depth' is not a multiple of 4. "
+ "Rounding up to the next multiple of 4. The new 'b64_decode_depth' is %d.\n",
+ decode_depth);
+ }
+ config->decode_conf.b64_depth = decode_depth;
+ }
+
+ else if ( v.is("bitenc_decode_depth") )
+ config->decode_conf.bitenc_depth = v.get_long();
+
+ else if ( v.is("command") )
+ names = v.get_string();
+
+ else if ( v.is("commands") )
+ names = v.get_string();
+
+ else if ( v.is("data_cmds"))
+ add_commands(v, PCMD_DATA);
+
+ else if ( v.is("email_hdrs_log_depth") )
+ config->log_config.email_hdrs_log_depth = v.get_long();
+
+ else if ( v.is("ignore_data") )
+ config->decode_conf.ignore_data = v.get_bool();
+
+ else if ( v.is("ignore_tls_data") )
+ config->ignore_tls_data = v.get_bool();
+
+ else if ( v.is("invalid_cmds"))
+ add_commands(v, PCMD_INVALID);
+
+ else if ( v.is("length") )
+ number = v.get_long();
+
+ else if ( v.is("log_filename") )
+ config->log_config.log_filename =v.get_bool();
+
+ else if ( v.is("log_mailfrom") )
+ config->log_config.log_mailfrom = v.get_bool();
+
+ else if ( v.is("log_rcptto"))
+ config->log_config.log_rcptto = v.get_bool();
+
+ else if ( v.is("log_email_hdrs"))
+ config->log_config.log_email_hdrs = v.get_bool();
+
+ else if ( v.is("max_command_line_len") )
+ config->max_command_line_len = v.get_long();
+
+ else if ( v.is("max_header_line_len") )
+ config->max_header_line_len = v.get_long();
+
+ else if ( v.is("max_response_line_len") )
+ config->max_response_line_len = v.get_long();
+
+ else if ( v.is("normalize") )
+ config->normalize = (NORM_TYPES)v.get_long();
+
+ else if ( v.is("normalize_cmds"))
+ add_commands(v, PCMD_NORM);
+
+ else if ( v.is("qp_decode_depth") )
+ config->decode_conf.qp_depth = v.get_long();
+
+ else if ( v.is("valid_cmds"))
+ add_commands(v, PCMD_VALID);
+
+ else if ( v.is("uu_decode_depth") )
+ config->decode_conf.uu_depth = v.get_long();
+
+ else if ( v.is("xlink2state") )
+ config->xlink2state = (XLINK2STATE)v.get_long();
+
+ else
+ return false;
+
+ return true;
+}
+
+SMTP_PROTO_CONF* SmtpModule::get_data()
+{
+ SMTP_PROTO_CONF* tmp = config;
+ config = nullptr;
+ return tmp;
+}
+
+bool SmtpModule::begin(const char*, int, SnortConfig*)
+{
+ names.clear();
+ number = -1;
+
+ if(!config)
+ {
+ config = new SMTP_PROTO_CONF;
+ config->max_header_line_len = 0;
+ config->max_response_line_len = 0;
+ config->max_command_line_len = 0;
+ config->xlink2state = ALERT_XLINK2STATE;
+ config->decode_conf.ignore_data = config->ignore_tls_data = false;
+ config->normalize = NORMALIZE_NONE;
+
+ file_api->set_mime_decode_config_defauts(&(config->decode_conf));
+ file_api->set_mime_log_config_defauts(&(config->log_config));
+ config->log_config.email_hdrs_log_depth = 1464;
+ config->cmd_config = (SMTPCmdConfig*)calloc(CMD_LAST, sizeof(SMTPCmdConfig));
+ if (config->cmd_config == NULL)
+ {
+ FatalError("Failed to allocate memory for SMTP command structure\n");
+ }
+ }
+
+ return true;
+}
+
+bool SmtpModule::end(const char* fqn, int idx, SnortConfig*)
+{
+ if ( !idx )
+ return true;
+
+ if ( !strcmp(fqn, "smtp.alt_max_command_line_len") )
+ cmds.push_back(new SmtpCmd(names, number));
+
+ return true;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// smtp_module.h author Bhagyashree Bantwal <bbantwal@cisco.com>
+
+#ifndef SMTP_MODULE_H
+#define SMTP_MODULE_H
+
+#include "framework/module.h"
+#include "framework/bits.h"
+#include "main/thread.h"
+#include "smtp_config.h"
+
+#define GID_SMTP 124
+
+#define SMTP_COMMAND_OVERFLOW 1
+#define SMTP_DATA_HDR_OVERFLOW 2
+#define SMTP_RESPONSE_OVERFLOW 3
+#define SMTP_SPECIFIC_CMD_OVERFLOW 4
+#define SMTP_UNKNOWN_CMD 5
+#define SMTP_ILLEGAL_CMD 6
+#define SMTP_HEADER_NAME_OVERFLOW 7
+#define SMTP_XLINK2STATE_OVERFLOW 8
+#define SMTP_DECODE_MEMCAP_EXCEEDED 9
+#define SMTP_B64_DECODING_FAILED 10
+#define SMTP_QP_DECODING_FAILED 11
+/* Do not delete or reuse this SID. Commenting this SID as this alert is no longer valid.*
+ * * #define SMTP_BITENC_DECODING_FAILED 12
+ * */
+#define SMTP_UU_DECODING_FAILED 13
+#define SMTP_AUTH_ABORT_AUTH 14
+
+#define SMTP_NAME "smtp"
+#define SMTP_HELP "smtp inspection"
+
+#define PCMD_LEN 0x0000
+#define PCMD_ALT 0x0001
+#define PCMD_AUTH 0x0002
+#define PCMD_BDATA 0x0004
+#define PCMD_DATA 0x0008
+#define PCMD_INVALID 0x0010
+#define PCMD_NORM 0x0020
+#define PCMD_VALID 0x0040
+
+struct SnortConfig;
+
+extern THREAD_LOCAL SimpleStats smtpstats;
+extern THREAD_LOCAL ProfileStats smtpPerfStats;
+struct SmtpCmd
+{
+ std::string name;
+
+ uint32_t flags;
+ unsigned number;
+
+ SmtpCmd(std::string&, uint32_t, int);
+ SmtpCmd(std::string&, int);
+};
+
+class SmtpModule : public Module
+{
+public:
+ SmtpModule();
+ ~SmtpModule();
+
+ bool set(const char*, Value&, SnortConfig*) override;
+ bool begin(const char*, int, SnortConfig*) override;
+ bool end(const char*, int, SnortConfig*) override;
+
+ unsigned get_gid() const override
+ { return GID_SMTP; }
+
+ const RuleMap* get_rules() const override;
+ const PegInfo* get_pegs() const override;
+ PegCount* get_counts() const override;
+ ProfileStats* get_profile() const override;
+
+ SMTP_PROTO_CONF* get_data();
+ const SmtpCmd* get_cmd(unsigned idx);
+
+private:
+ void add_commands(Value&, uint32_t flags);
+
+private:
+ SMTP_PROTO_CONF* config;
+ std::vector<SmtpCmd*> cmds;
+ std::string names;
+ int number;
+};
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+/*
+ *
+ * Description:
+ *
+ * This file handles normalizing SMTP traffic into the alternate buffer.
+ *
+ * Entry point functions:
+ *
+ * SMTP_NeedNormalize()
+ * SMTP_Normalize()
+ *
+ *
+ */
+
+#include "smtp_normalize.h"
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "smtp.h"
+#include "smtp_util.h"
+
+extern char smtp_normalizing;
+
+/*
+ * SMTP_NormalizeCmd
+ *
+ * If command doesn't need normalizing it will do nothing, except in
+ * the case where we are already normalizing in which case the line
+ * will get copied to the alt buffer.
+ * If the command needs normalizing the normalized data will be copied
+ * to the alt buffer. If we are not already normalizing, all of the
+ * data up to this point will be copied into the alt buffer first.
+ *
+ * XXX This may copy unwanted data if we are ignoring the data in the
+ * message and there was data that came before the command in the
+ * packet, for example if there are multiple transactions on the
+ * session or if we're normalizing QUIT.
+ *
+ * @param p pointer to packet structure
+ * @param ptr pointer to beginning of command line
+ * @param eolm start of end of line marker
+ * @param eol end of end of line marker
+ *
+ * @return response
+ * @retval 0 function succeded without error
+ * @retval -1 there were errors
+ */
+int SMTP_NormalizeCmd(Packet* p, const uint8_t* ptr, const uint8_t* eolm, const uint8_t* eol)
+{
+ const uint8_t* tmp;
+ const uint8_t* cmd_start;
+ const uint8_t* cmd_end;
+ const uint8_t* args_start;
+ const uint8_t* args_end;
+ const uint8_t* space = (uint8_t*)" ";
+ int need_normalize = 0;
+ int ret;
+
+ tmp = ptr;
+
+ /* move past initial whitespace */
+ while ((tmp < eolm) && isspace((int)*tmp))
+ tmp++;
+
+ /* we got whitespace before command */
+ if (tmp > ptr)
+ need_normalize = 1;
+
+ /* move past the command */
+ cmd_start = cmd_end = tmp;
+ while ((cmd_end < eolm) && !isspace((int)*cmd_end))
+ cmd_end++;
+
+ args_start = cmd_end;
+ while ((args_start < eolm) && isspace((int)*args_start))
+ args_start++;
+
+ if (args_start == eolm)
+ {
+ /* nothing but space after command - normalize if we got any
+ * spaces since there is not an argument */
+ if (args_start > cmd_end)
+ need_normalize = 1;
+
+ args_end = args_start;
+ }
+ else
+ {
+ /* more than one space between command and argument or
+ * whitespace between command and argument is not a regular space character */
+ if ((args_start > (cmd_end + 1)) || (*cmd_end != ' '))
+ need_normalize = 1;
+
+ /* see if there is any dangling space at end of argument */
+ args_end = eolm;
+ while (isspace((int)*(args_end - 1)))
+ args_end--;
+
+ if (args_end != eolm)
+ need_normalize = 1;
+ }
+
+ if (need_normalize)
+ {
+ /* if we're not yet normalizing copy everything in the payload up to this
+ * line into the alt buffer */
+ if (!smtp_normalizing)
+ {
+ ret = SMTP_CopyToAltBuffer(p, p->data, ptr - p->data);
+ if (ret == -1)
+ return -1;
+ }
+
+ /* copy the command into the alt buffer */
+ ret = SMTP_CopyToAltBuffer(p, cmd_start, cmd_end - cmd_start);
+ if (ret == -1)
+ return -1;
+
+ /* if we actually have an argument, copy it into the alt buffer */
+ if (args_start != args_end)
+ {
+ /* copy a 'pure' space */
+ ret = SMTP_CopyToAltBuffer(p, space, 1);
+ if (ret == -1)
+ return -1;
+
+ ret = SMTP_CopyToAltBuffer(p, args_start, args_end - args_start);
+ if (ret == -1)
+ return -1;
+ }
+
+ /* copy the end of line marker into the alt buffer */
+ ret = SMTP_CopyToAltBuffer(p, eolm, eol - eolm);
+ if (ret == -1)
+ return -1;
+ }
+ else if (smtp_normalizing)
+ {
+ /* if we're already normalizing and didn't need to normalize this line, just
+ * copy it into the alt buffer */
+ ret = SMTP_CopyToAltBuffer(p, ptr, eol - ptr);
+ if (ret == -1)
+ return -1;
+ }
+
+ return 0;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+#ifndef SMTP_NORMALIZE_H
+#define SMTP_NORMALIZE_H
+
+#include "protocols/packet.h"
+
+int SMTP_NormalizeCmd(Packet*, const uint8_t*, const uint8_t*, const uint8_t*);
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+#include "smtp_paf.h"
+#include <sys/types.h>
+#include "snort_types.h"
+#include "snort_debug.h"
+
+#include "smtp.h"
+
+/* State tracker for MIME PAF */
+enum SmtpDataCMD
+{
+ SMTP_PAF_BDAT_CMD,
+ SMTP_PAF_DATA_CMD,
+ SMTP_PAF_XEXCH50_CMD,
+ SMTP_PAF_STRARTTLS_CMD
+};
+
+struct SmtpPAFToken
+{
+ const char* name;
+ int name_len;
+ int search_id;
+ bool has_length;
+};
+
+const SmtpPAFToken smtp_paf_tokens[] =
+{
+ { "BDAT", 4, SMTP_PAF_BDAT_CMD, true },
+ { "DATA", 4, SMTP_PAF_DATA_CMD, true },
+ { "XEXCH50", 7, SMTP_PAF_XEXCH50_CMD, true },
+ { "STRARTTLS", 9, SMTP_PAF_STRARTTLS_CMD, false },
+ { NULL, 0, 0, false }
+};
+
+/* State tracker for SMTP PAF */
+enum SmtpPafDataLenStatus
+{
+ SMTP_PAF_LENGTH_INVALID,
+ SMTP_PAF_LENGTH_CONTINUE,
+ SMTP_PAF_LENGTH_DONE
+};
+
+static inline SmtpPafData* get_state(Flow* flow, bool c2s)
+{
+ if ( !flow )
+ return nullptr;
+
+ SmtpSplitter* s = (SmtpSplitter*)stream.get_splitter(flow, c2s);
+ return s ? &s->state : nullptr;
+}
+
+/* Process responses from server, flushed at EOL*/
+
+static inline void reset_data_states(SmtpPafData* pfdata)
+{
+ // reset MIME info
+ file_api->reset_mime_paf_state(&(pfdata->data_info));
+
+ pfdata->length = 0;
+}
+
+static inline StreamSplitter::Status smtp_paf_server(SmtpPafData* pfdata,
+ const uint8_t* data, uint32_t len, uint32_t* fp)
+{
+ const char* pch;
+
+ pfdata->smtp_state = SMTP_PAF_CMD_STATE;
+ pch = (const char*)memchr (data, '\n', len);
+
+ if (pch != NULL)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Find end of line!\n"); );
+ *fp = (uint32_t)(pch - (const char*)data) + 1;
+ return StreamSplitter::FLUSH;
+ }
+ return StreamSplitter::SEARCH;
+}
+
+/* Initialize command search based on first byte of command*/
+static inline char* init_cmd_search(SmtpCmdSearchInfo* search_info, uint8_t ch)
+{
+ /* Use the first byte to choose data command)*/
+ switch (ch)
+ {
+ case 'b':
+ case 'B':
+ search_info->search_state = &smtp_paf_tokens[SMTP_PAF_BDAT_CMD].name[1];
+ search_info->search_id = SMTP_PAF_BDAT_CMD;
+ break;
+ case 'd':
+ case 'D':
+ search_info->search_state = &smtp_paf_tokens[SMTP_PAF_DATA_CMD].name[1];
+ search_info->search_id = SMTP_PAF_DATA_CMD;
+ break;
+ case 'x':
+ case 'X':
+ search_info->search_state = &smtp_paf_tokens[SMTP_PAF_XEXCH50_CMD].name[1];
+ search_info->search_id = SMTP_PAF_XEXCH50_CMD;
+ break;
+ case 's':
+ case 'S':
+ search_info->search_state = &smtp_paf_tokens[SMTP_PAF_STRARTTLS_CMD].name[1];
+ search_info->search_id = SMTP_PAF_STRARTTLS_CMD;
+ break;
+ default:
+ search_info->search_state = NULL;
+ break;
+ }
+ return (char *)search_info->search_state;
+}
+
+/* Validate whether the command is a data command*/
+static inline void validate_command(SmtpCmdSearchInfo* search_info, uint8_t val)
+{
+ if (search_info->search_state )
+ {
+ uint8_t expected = *(search_info->search_state);
+
+ if (toupper(val) == toupper(expected))
+ {
+ search_info->search_state++;
+ /* Found data command, change to SMTP_PAF_CMD_DATA_LENGTH_STATE */
+ if (*(search_info->search_state) == '\0')
+ {
+ search_info->search_state = NULL;
+ search_info->cmd_state = SMTP_PAF_CMD_DATA_LENGTH_STATE;
+ return;
+ }
+ }
+ else
+ {
+ search_info->search_state = NULL;
+ search_info->cmd_state = SMTP_PAF_CMD_UNKNOWN;
+ return;
+ }
+ }
+}
+
+/* Get the length of data from data command
+ * */
+static SmtpPafDataLenStatus get_length(char c, uint32_t* len)
+{
+ uint32_t length = *len;
+
+ if (isblank(c))
+ {
+ if (length)
+ {
+ *len = length;
+ return SMTP_PAF_LENGTH_DONE;
+ }
+ }
+ else if (isdigit(c))
+ {
+ uint64_t tmp_len = (10 * length) + (c - '0');
+ if (tmp_len < UINT32_MAX)
+ length = (uint32_t)tmp_len;
+ else
+ {
+ *len = 0;
+ return SMTP_PAF_LENGTH_INVALID;
+ }
+ }
+ else
+ {
+ *len = 0;
+ return SMTP_PAF_LENGTH_INVALID;
+ }
+
+ *len = length;
+ return SMTP_PAF_LENGTH_CONTINUE;
+}
+
+/* Currently, we support "BDAT", "DATA", "XEXCH50", "STRARTTLS"
+ * * Each data command should start from offset 0,
+ * * since previous data have been flushed
+ * */
+static inline bool process_command(SmtpPafData* pfdata, uint8_t val)
+{
+ /*State unknown, start cmd search start from EOL, flush on EOL*/
+ if (val == '\n')
+ {
+ if (pfdata->cmd_info.cmd_state == SMTP_PAF_CMD_DATA_END_STATE)
+ {
+ pfdata->smtp_state = SMTP_PAF_DATA_STATE;
+ reset_data_states(pfdata);
+ pfdata->end_of_data = false;
+ }
+
+ pfdata->cmd_info.cmd_state = SMTP_PAF_CMD_START;
+ return 1;
+ }
+
+ switch (pfdata->cmd_info.cmd_state)
+ {
+ case SMTP_PAF_CMD_UNKNOWN:
+ break;
+ case SMTP_PAF_CMD_START:
+ if (init_cmd_search(&(pfdata->cmd_info), val))
+ pfdata->cmd_info.cmd_state = SMTP_PAF_CMD_DETECT;
+ else
+ pfdata->cmd_info.cmd_state = SMTP_PAF_CMD_UNKNOWN;
+ break;
+ case SMTP_PAF_CMD_DETECT:
+ /* Search for data command */
+ validate_command(&(pfdata->cmd_info), val);
+ break;
+ case SMTP_PAF_CMD_DATA_LENGTH_STATE:
+ /* Continue finding the data length ...*/
+ if (get_length(val, &pfdata->length) != SMTP_PAF_LENGTH_CONTINUE)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Find data length: %d\n",
+ pfdata->length); );
+ pfdata->cmd_info.cmd_state = SMTP_PAF_CMD_DATA_END_STATE;
+ }
+ break;
+ case SMTP_PAF_CMD_DATA_END_STATE:
+ /* Change to Data state at EOL*/
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* Flush based on data length*/
+static inline bool flush_based_length(SmtpPafData* pfdata)
+{
+ if (pfdata->length)
+ {
+ pfdata->length--;
+ if (!pfdata->length)
+ return true;
+ }
+ return false;
+}
+
+/* Process data length if specified, or end of data marker, flush at the end
+ * * or
+ * * Process data boundary and flush each file based on boundary*/
+static inline bool process_data(SmtpPafData* pfdata, uint8_t data)
+{
+ if (flush_based_length(pfdata)|| file_api->check_data_end(&(pfdata->data_end_state), data))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "End of data\n"); );
+ /*Clean up states*/
+ pfdata->smtp_state = SMTP_PAF_CMD_STATE;
+ pfdata->end_of_data = true;
+ reset_data_states(pfdata);
+ return true;
+ }
+
+ return file_api->process_mime_paf_data(&(pfdata->data_info), data);
+}
+
+/* Process commands/data from client
+ * * For command, flush at EOL
+ * * For data, flush at boundary
+ * */
+static inline StreamSplitter::Status smtp_paf_client(SmtpPafData* pfdata,
+ const uint8_t* data, uint32_t len, uint32_t* fp)
+{
+ uint32_t i;
+ uint32_t boundary_start = 0;
+
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "From client: %s \n", data); );
+ for (i = 0; i < len; i++)
+ {
+ uint8_t ch = data[i];
+ switch (pfdata->smtp_state)
+ {
+ case SMTP_PAF_CMD_STATE:
+ if (process_command(pfdata, ch))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Flush command: %s \n", data); );
+ *fp = i + 1;
+ return StreamSplitter::FLUSH;
+ }
+ break;
+ case SMTP_PAF_DATA_STATE:
+ if (process_data(pfdata, ch))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "Flush data!\n"); );
+ *fp = i + 1;
+ return StreamSplitter::FLUSH;
+ }
+
+ if (pfdata->data_info.boundary_state == MIME_PAF_BOUNDARY_UNKNOWN)
+ boundary_start = i;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ( scanning_boundary(&pfdata->data_info, boundary_start, fp) )
+ return StreamSplitter::LIMIT;
+
+ return StreamSplitter::SEARCH;
+}
+
+//--------------------------------------------------------------------
+// callback for stateful scanning of in-order raw payload
+//--------------------------------------------------------------------
+
+SmtpSplitter::SmtpSplitter(bool c2s) : StreamSplitter(c2s)
+{
+ memset(&state, 0, sizeof(state));
+ reset_data_states(&state);
+}
+
+SmtpSplitter::~SmtpSplitter() { }
+
+/* Function: smtp_paf()
+
+ Purpose: SMTP PAF callback.
+ Inspects smtp traffic. Checks client traffic for the current command
+ and sets correct server termination sequence. Client side data will
+ flush after receiving CRLF ("\r\n"). Server data flushes after
+ finding set termination sequence.
+
+ Arguments:
+ void * - stream5 session pointer
+ void ** - DNP3 state tracking structure
+ const uint8_t * - payload data to inspect
+ uint32_t - length of payload data
+ uint32_t - flags to check whether client or server
+ uint32_t * - pointer to set flush point
+
+ Returns:
+ StreamSplitter::Status - StreamSplitter::FLUSH if flush point found, StreamSplitter::SEARCH otherwise
+*/
+
+StreamSplitter::Status SmtpSplitter::scan(
+ Flow* , const uint8_t* data, uint32_t len,
+ uint32_t flags, uint32_t* fp)
+{
+ SmtpPafData* pfdata = &state;
+
+ if (flags & PKT_FROM_SERVER)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "PAF: From server.\n"); );
+ return smtp_paf_server(pfdata, data, len, fp);
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_SMTP, "PAF: From client.\n"); );
+ return smtp_paf_client(pfdata, data, len, fp);
+ }
+}
+
+bool smtp_is_data_end(void* session)
+{
+ Flow* ssn = (Flow*)session;
+ SmtpPafData* s = get_state(ssn, true);
+ return s->end_of_data;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+#ifndef SMTP_PAF_H
+#define SMTP_PAF_H
+
+#include "snort_types.h"
+#include "stream/stream_api.h"
+#include "stream/stream_splitter.h"
+#include "file_api/file_api.h"
+
+/* State tracker for SMTP PAF */
+enum SmtpPafState
+{
+ SMTP_PAF_CMD_STATE,
+ SMTP_PAF_DATA_STATE
+};
+/* State tracker for data command */
+typedef enum _SmtpPafCmdState
+{
+ SMTP_PAF_CMD_UNKNOWN,
+ SMTP_PAF_CMD_START,
+ SMTP_PAF_CMD_DETECT,
+ SMTP_PAF_CMD_DATA_LENGTH_STATE,
+ SMTP_PAF_CMD_DATA_END_STATE
+} SmtpPafCmdState;
+
+struct SmtpCmdSearchInfo
+{
+ SmtpPafCmdState cmd_state;
+ int search_id;
+ const char* search_state;
+};
+
+/* State tracker for SMTP PAF */
+struct SmtpPafData
+{
+ DataEndState data_end_state;
+ uint32_t length;
+ SmtpPafState smtp_state;
+ SmtpCmdSearchInfo cmd_info;
+ MimeDataPafInfo data_info;
+ bool end_of_data;
+};
+
+class SmtpSplitter : public StreamSplitter
+{
+public:
+ SmtpSplitter(bool c2s);
+ ~SmtpSplitter();
+
+ Status scan(Flow*, const uint8_t* data, uint32_t len,
+ uint32_t flags, uint32_t* fp) override;
+
+ virtual bool is_paf() override { return true; }
+
+public:
+ SmtpPafData state;
+};
+
+bool smtp_is_data_end(void* ssn);
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+ /*
+ * Author: Andy Mullican
+ *
+ * Description:
+ *
+ * This file contains SMTP helper functions.
+ *
+ * Entry point functions:
+ *
+ * safe_strchr()
+ * safe_strstr()
+ * copy_to_space()
+ * safe_sscanf()
+ *
+ *
+ */
+
+#include "smtp_util.h"
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "smtp.h"
+#include "smtp_config.h"
+#include "stream/stream_api.h"
+#include "snort_bounds.h"
+#include "detection/detection_util.h"
+
+extern char smtp_normalizing;
+static THREAD_LOCAL DataBuffer DecodeBuf;
+
+void SMTP_GetEOL(const uint8_t* ptr, const uint8_t* end,
+ const uint8_t** eol, const uint8_t** eolm)
+{
+ const uint8_t* tmp_eol;
+ const uint8_t* tmp_eolm;
+
+ /* XXX maybe should fatal error here since none of these
+ * pointers should be NULL */
+ if (ptr == NULL || end == NULL || eol == NULL || eolm == NULL)
+ return;
+
+ tmp_eol = (uint8_t*)memchr(ptr, '\n', end - ptr);
+ if (tmp_eol == NULL)
+ {
+ tmp_eol = end;
+ tmp_eolm = end;
+ }
+ else
+ {
+ /* end of line marker (eolm) should point to marker and
+ * end of line (eol) should point to end of marker */
+ if ((tmp_eol > ptr) && (*(tmp_eol - 1) == '\r'))
+ {
+ tmp_eolm = tmp_eol - 1;
+ }
+ else
+ {
+ tmp_eolm = tmp_eol;
+ }
+
+ /* move past newline */
+ tmp_eol++;
+ }
+
+ *eol = tmp_eol;
+ *eolm = tmp_eolm;
+}
+
+void SMTP_ResetAltBuffer(void)
+{
+ DecodeBuf.len = 0;
+}
+
+int SMTP_CopyToAltBuffer(Packet* p, const uint8_t* start, int length)
+{
+ uint8_t* alt_buf;
+ int alt_size;
+ unsigned int* alt_len;
+ int ret;
+
+ /* if we make a call to this it means we want to use the alt buffer
+ * regardless of whether we copy any data into it or not - barring a failure */
+ smtp_normalizing = 1;
+
+ /* if start and end the same, nothing to copy */
+ if (length == 0)
+ return 0;
+
+ alt_buf = DecodeBuf.data;
+ alt_size = sizeof(DecodeBuf.data);
+ alt_len = &DecodeBuf.len;
+
+ ret = SafeMemcpy(alt_buf + *alt_len, start, length, alt_buf, alt_buf + alt_size);
+
+ if (ret != SAFEMEM_SUCCESS)
+ {
+ SetDetectLimit(p, 0);
+ smtp_normalizing = 0;
+ return -1;
+ }
+ *alt_len += length;
+
+ set_alt_data(DecodeBuf.data, *alt_len);
+
+ return 0;
+}
+
+/* Accumulate EOL seperated headers, one or more at a time */
+int SMTP_CopyEmailHdrs(const uint8_t* start, int length, MAIL_LogState* log_state)
+{
+ int log_avail = 0;
+ uint8_t* log_buf;
+ uint32_t* hdrs_logged;
+ int ret = 0;
+
+ if ((log_state == NULL) || (length <= 0))
+ return -1;
+
+ log_avail = (log_state->log_depth - log_state->hdrs_logged);
+ hdrs_logged = &(log_state->hdrs_logged);
+ log_buf = (uint8_t*)log_state->emailHdrs;
+
+ if (log_avail <= 0)
+ {
+ return -1;
+ }
+
+ if (length > log_avail )
+ {
+ length = log_avail;
+ }
+
+ /* appended by the EOL \r\n */
+
+ ret = SafeMemcpy(log_buf + *hdrs_logged, start, length, log_buf, log_buf+
+ (log_state->log_depth));
+
+ if (ret != SAFEMEM_SUCCESS)
+ {
+ return -1;
+ }
+
+ *hdrs_logged += length;
+
+ return 0;
+}
+
+/* Accumulate email addresses from RCPT TO and/or MAIL FROM commands. Email addresses are separated
+ by comma */
+int SMTP_CopyEmailID(const uint8_t* start, int length, int command_type, MAIL_LogState* log_state)
+{
+ uint8_t* alt_buf;
+ int alt_size;
+ uint16_t* alt_len;
+ int ret;
+ int log_avail=0;
+ const uint8_t* tmp_eol;
+
+ if ((log_state == NULL) || (length <= 0))
+ return -1;
+
+ tmp_eol = (uint8_t*)memchr(start, ':', length);
+ if (tmp_eol == NULL)
+ return -1;
+
+ if ((tmp_eol+1) < (start+length))
+ {
+ length = length - ( (tmp_eol+1) - start );
+ start = tmp_eol+1;
+ }
+ else
+ return -1;
+
+ switch (command_type)
+ {
+ case CMD_MAIL:
+ alt_buf = log_state->senders;
+ alt_size = MAX_EMAIL;
+ alt_len = &(log_state->snds_logged);
+ break;
+
+ case CMD_RCPT:
+ alt_buf = log_state->recipients;
+ alt_size = MAX_EMAIL;
+ alt_len = &(log_state->rcpts_logged);
+ break;
+
+ default:
+ return -1;
+ }
+
+ log_avail = alt_size - *alt_len;
+
+ if (log_avail <= 0 || !alt_buf)
+ return -1;
+ else if (log_avail < length)
+ length = log_avail;
+
+ if ( *alt_len > 0 && ((*alt_len + 1) < alt_size))
+ {
+ alt_buf[*alt_len] = ',';
+ *alt_len = *alt_len + 1;
+ }
+
+ ret = SafeMemcpy(alt_buf + *alt_len, start, length, alt_buf, alt_buf + alt_size);
+
+ if (ret != SAFEMEM_SUCCESS)
+ {
+ if (*alt_len != 0)
+ *alt_len = *alt_len - 1;
+ return -1;
+ }
+
+ *alt_len += length;
+
+ return 0;
+}
+
+void SMTP_LogFuncs(SMTP_PROTO_CONF* config, Packet* p, MimeState* mime_ssn)
+{
+ if ((mime_ssn->log_flags == 0) || !config)
+ return;
+
+ if (mime_ssn->log_flags & MIME_FLAG_FILENAME_PRESENT)
+ {
+ stream.set_extra_data(p->flow, p, config->xtra_filename_id);
+ }
+
+ if (mime_ssn->log_flags & MIME_FLAG_MAIL_FROM_PRESENT)
+ {
+ stream.set_extra_data(p->flow, p, config->xtra_mfrom_id);
+ }
+
+ if (mime_ssn->log_flags & MIME_FLAG_RCPT_TO_PRESENT)
+ {
+ stream.set_extra_data(p->flow, p, config->xtra_rcptto_id);
+ }
+
+ if (mime_ssn->log_flags & MIME_FLAG_EMAIL_HDRS_PRESENT)
+ {
+ stream.set_extra_data(p->flow, p, config->xtra_ehdrs_id);
+ }
+}
+
+#ifdef DEBUG_MSGS
+char smtp_print_buffer[65537];
+
+const char* SMTP_PrintBuffer(Packet* p)
+{
+ const uint8_t* ptr = NULL;
+ int len = 0;
+ int iorig, inew;
+
+ if (smtp_normalizing)
+ {
+ ptr = DecodeBuf.data;
+ len = DecodeBuf.len;
+ }
+ else
+ {
+ ptr = p->data;
+ len = p->dsize;
+ }
+
+ for (iorig = 0, inew = 0; iorig < len; iorig++, inew++)
+ {
+ if ((isascii((int)ptr[iorig]) && isprint((int)ptr[iorig])) || (ptr[iorig] == '\n'))
+ {
+ smtp_print_buffer[inew] = ptr[iorig];
+ }
+ else if (ptr[iorig] == '\r' &&
+ ((iorig + 1) < len) && (ptr[iorig + 1] == '\n'))
+ {
+ iorig++;
+ smtp_print_buffer[inew] = '\n';
+ }
+ else if (isspace((int)ptr[iorig]))
+ {
+ smtp_print_buffer[inew] = ' ';
+ }
+ else
+ {
+ smtp_print_buffer[inew] = '.';
+ }
+ }
+
+ smtp_print_buffer[inew] = '\0';
+
+ return &smtp_print_buffer[0];
+}
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+/*************************************************************************
+ *
+ * smtp_util.h
+ *
+ * Author: Andy Mullican
+ * Author: Todd Wease
+ *
+ *************************************************************************/
+
+#ifndef SMTP_UTIL_H
+#define SMTP_UTIL_H
+
+#include "smtp_config.h"
+#include "protocols/packet.h"
+
+void SMTP_GetEOL(const uint8_t*, const uint8_t*, const uint8_t**, const uint8_t**);
+int SMTP_CopyToAltBuffer(Packet*, const uint8_t*, int);
+int SMTP_CopyEmailHdrs(const uint8_t*, int, MAIL_LogState* log_state);
+int SMTP_CopyEmailID(const uint8_t*, int, int, MAIL_LogState* log_state);
+void SMTP_LogFuncs(SMTP_PROTO_CONF* config, Packet* p, MimeState* mime_ssn);
+void SMTP_ResetAltBuffer(void);
+
+#ifdef DEBUG_MSGS
+const char* SMTP_PrintBuffer(Packet*);
+#endif
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+
+/************************************************************************
+ *
+ * smtp_xlink2state.c
+ *
+ * Author: Andy Mullican
+ *
+ * Description:
+ *
+ * This file handles the X-Link2State vulnerability.
+ *
+ * Entry point function:
+ *
+ * ParseXLink2State()
+ *
+ *
+ ************************************************************************/
+
+#include "smtp_xlink2state.h"
+
+#ifndef WIN32
+#include <strings.h>
+#endif
+
+#include <ctype.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "smtp_util.h"
+#include "smtp_module.h"
+
+#include "packet_io/active.h"
+
+#define XLINK_OTHER 1
+#define XLINK_FIRST 2
+#define XLINK_CHUNK 3
+
+#define XLINK_LEN 12 /* strlen("X-LINK2STATE") */
+
+/* X-Link2State overlong length */
+#define XLINK2STATE_MAX_LEN 520
+
+/* Prototypes */
+static uint32_t get_xlink_hex_value(const uint8_t*, const uint8_t*);
+static char get_xlink_keyword(const uint8_t*, const uint8_t*);
+
+/*
+ * Extract a number from a string
+ *
+ * @param buf pointer to beginning of buffer to parse
+ * @param end end pointer of buffer to parse
+ *
+ * @return unsigned long value of number extracted
+ *
+ * @note this could be more efficient, but the search buffer should be pretty short
+ */
+static uint32_t get_xlink_hex_value(const uint8_t* buf, const uint8_t* end)
+{
+ char c;
+ uint32_t value = 0;
+ const uint8_t* hex_end;
+
+ if ((end - buf) < 8)
+ return 0;
+
+ hex_end = buf + 8;
+
+ while (buf < hex_end)
+ {
+ c = toupper((int)*buf);
+
+ /* Make sure it is a number or hex char; if not return with what we have */
+ if (isdigit((int)c))
+ {
+ c = c - '0';
+ }
+ else if (c >= 'A' && c <= 'F')
+ {
+ c = (c - 'A') + 10;
+ }
+ else
+ {
+ return value;
+ }
+
+ value = (value * 16) + c;
+
+ buf++;
+ }
+
+ return value;
+}
+
+/*
+ * Check for X-LINK2STATE keywords FIRST or CHUNK
+ *
+ *
+ * @param x pointer to "X-LINK2STATE" in buffer
+ * @param x_len length of buffer after x
+ *
+ * @retval int identifies which keyword found, if any
+ */
+static char get_xlink_keyword(const uint8_t* ptr, const uint8_t* end)
+{
+ int len;
+
+ if (ptr == NULL || end == NULL)
+ return XLINK_OTHER;
+
+ ptr += XLINK_LEN;
+ if (ptr >= end)
+ return XLINK_OTHER;
+
+ /* Skip over spaces */
+ while (ptr < end && isspace((int)*ptr))
+ {
+ ptr++;
+ }
+
+ len = end - ptr;
+
+ if (len > 5 && strncasecmp((const char*)ptr, "FIRST", 5) == 0)
+ {
+ return XLINK_FIRST;
+ }
+ else if (len > 5 && strncasecmp((const char*)ptr, "CHUNK", 5) == 0)
+ {
+ return XLINK_CHUNK;
+ }
+
+ return XLINK_OTHER;
+}
+
+/*
+ * Handle X-Link2State vulnerability
+ *
+ * From Lurene Grenier:
+
+ The X-LINK2STATE command always takes the following form:
+
+ X-LINK2STATE [FIRST|NEXT|LAST] CHUNK=<SOME DATA>
+
+ The overwrite occurs when three criteria are met:
+
+ No chunk identifier exists - ie neither FIRST, NEXT, or LAST are specified
+ No previous FIRST chunk was sent
+ <SOME DATA> has a length greater than 520 bytes
+
+ Normally you send a FIRST chunk, then some intermediary chunks marked with
+ either NEXT or not marked, then finally a LAST chunk. If no first chunk is
+ sent, and a chunk with no specifier is sent, it assumes it must append to
+ something, but it has nothing to append to, so an overwrite occurs. Sending out
+ of order chunks WITH specifiers results in an exception.
+
+ So simply:
+
+ if (gotFirstChunk)
+ next; # chunks came with proper first chunk specified
+ if (/X-LINK2STATE [FIRST|NEXT|LAST] CHUNK/) {
+ if (/X-LINK2STATE FIRST CHUNK/) gotFirstChunk = TRUE;
+ next; # some specifier is marked
+ }
+ if (chunkLen > 520)
+ attempt = TRUE; # Gotcha!
+
+ Usually it takes more than one unspecified packet in a row, but I think this is
+ just a symptom of the fact that we're triggering a heap overwrite, and not a
+ condition of the bug. However, if we're still getting FPs this might be an
+ avenue to try.
+
+ *
+ * @param p standard Packet structure
+ * @param x pointer to "X-LINK2STATE" in buffer
+ *
+ * @retval 1 if alert raised
+ * @retval 0 if no alert raised
+ */
+int ParseXLink2State(SMTP_PROTO_CONF* config, Packet* p, SMTPData* smtp_ssn, const uint8_t* ptr)
+{
+ uint8_t* lf = NULL;
+ uint32_t len = 0;
+ char x_keyword;
+ const uint8_t* end;
+
+ if (p == NULL || ptr == NULL)
+ return 0;
+
+ /* If we got a FIRST chunk on this stream, this is not an exploit */
+ if (smtp_ssn->session_flags & SMTP_FLAG_XLINK2STATE_GOTFIRSTCHUNK)
+ return 0;
+
+ /* Calculate length from pointer to end of packet data */
+ end = p->data + p->dsize;
+ if (ptr >= end)
+ return 0;
+
+ /* Check for "FIRST" or "CHUNK" after X-LINK2STATE */
+ x_keyword = get_xlink_keyword(ptr, end);
+ if (x_keyword != XLINK_CHUNK)
+ {
+ if (x_keyword == XLINK_FIRST)
+ smtp_ssn->session_flags |= SMTP_FLAG_XLINK2STATE_GOTFIRSTCHUNK;
+
+ return 0;
+ }
+
+ ptr = (uint8_t*)memchr((char*)ptr, '=', end - ptr);
+ if (ptr == NULL)
+ return 0;
+
+ /* move past '=' and make sure we're within bounds */
+ ptr++;
+ if (ptr >= end)
+ return 0;
+
+ /* Look for one of two patterns:
+ *
+ * ... CHUNK={0000006d} MULTI (5) ({00000000051} ...
+ * ... CHUNK=AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n
+ */
+
+ if (*ptr == '{')
+ {
+ /* move past '{' and make sure we're within bounds */
+ ptr++;
+ if ((ptr + 8) >= end)
+ return 0;
+
+ /* Get length - can we always trust it? */
+ len = get_xlink_hex_value(ptr, end);
+ }
+ else
+ {
+ lf = (uint8_t*)memchr((char*)ptr, '\n', end - ptr);
+ if (lf == NULL)
+ return 0;
+
+ len = lf - ptr;
+ }
+
+ if (len > XLINK2STATE_MAX_LEN)
+ {
+ /* Need to drop the packet if we're told to
+ * (outside of whether its thresholded). */
+ if (config->xlink2state == DROP_XLINK2STATE)
+ {
+ Active_DropSession(p);
+ }
+
+ SnortEventqAdd(GID_SMTP, SMTP_XLINK2STATE_OVERFLOW);
+ smtp_ssn->session_flags |= SMTP_FLAG_XLINK2STATE_ALERTED;
+
+ return 1;
+ }
+
+ /* Check for more than one command in packet */
+ ptr = (uint8_t*)memchr((char*)ptr, '\n', end - ptr);
+ if (ptr == NULL)
+ return 0;
+
+ /* move past '\n' */
+ ptr++;
+
+ if (ptr < end)
+ {
+ ParseXLink2State(config, p, smtp_ssn, ptr);
+ }
+
+ return 0;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+
+/*************************************************************************
+ * smtp_xlink2state.h
+ *
+ * Author: Andy Mullican
+ *
+ *************************************************************************/
+
+#ifndef SMTP_XLINK2STATE_H
+#define SMTP_XLINK2STATE_H
+
+#include "protocols/packet.h"
+#include "smtp.h"
+#include "smtp_config.h"
+
+int ParseXLink2State(SMTP_PROTO_CONF*, Packet*, SMTPData*, const uint8_t*);
+
+#endif
+
pps_dns.cc
pps_pop.cc
pps_imap.cc
+ pps_smtp.cc
pps_sfportscan.cc
pps_stream5_ip.cc
pps_stream5_global.cc
pps_dns.cc \
pps_pop.cc \
pps_imap.cc \
+pps_smtp.cc \
pps_sfportscan.cc \
pps_stream5_ip.cc \
pps_stream5_global.cc \
else if (!keyword.compare("memcap"))
{
table_api.add_deleted_comment("memcap");
+ data_stream >> keyword;
}
else if (!keyword.compare("max_mime_mem"))
{
table_api.add_deleted_comment("max_mime_mem");
+ data_stream >> keyword;
}
else if (!keyword.compare("b64_decode_depth"))
else if (!keyword.compare("memcap"))
{
table_api.add_deleted_comment("memcap");
+ data_stream >> keyword;
}
else if (!keyword.compare("max_mime_mem"))
{
table_api.add_deleted_comment("max_mime_mem");
+ data_stream >> keyword;
}
else if (!keyword.compare("b64_decode_depth"))
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// pps_smtp.cc author Bhagya Bantwal <bbantwal@cisco.com>
+
+#include <sstream>
+#include <vector>
+
+#include "conversion_state.h"
+#include "helpers/s2l_util.h"
+#include "helpers/util_binder.h"
+
+namespace preprocessors
+{
+namespace
+{
+class Smtp : public ConversionState
+{
+public:
+ Smtp(Converter& c) : ConversionState(c) { }
+ virtual ~Smtp() { }
+ virtual bool convert(std::istringstream& data_stream);
+
+private:
+ struct Command
+ {
+ std::string name;
+ std::string format;
+ int length;
+
+ inline bool operator==(Command c)
+ { return (!name.compare(c.name)); }
+
+ Command() : name(std::string()),
+ format(std::string()),
+ length(command_default_len) { }
+ };
+
+ const static int command_default_len = -1;
+ std::vector<Command> commands;
+
+ bool parse_alt_max_cmd(std::istringstream& data_stream);
+ std::vector<Command>::iterator get_command(std::string cmd_name,
+ std::vector<Smtp::Command>::iterator it);
+};
+} // namespace
+
+std::vector<Smtp::Command>::iterator Smtp::get_command(
+ std::string cmd_name,
+ std::vector<Smtp::Command>::iterator it)
+{
+ for (; it != commands.end(); ++it)
+ if (!cmd_name.compare((*it).name))
+ return it;
+
+ return commands.end();
+}
+
+bool Smtp::parse_alt_max_cmd(std::istringstream& stream)
+{
+ int len;
+ std::string elem;
+ std::string format = std::string();
+
+ if (!(stream >> len))
+ return false;
+
+ if (!(stream >> elem) || (elem.compare("{")))
+ return false;
+
+ while (stream >> elem && elem.compare("}"))
+ {
+
+ auto it = get_command(elem, commands.begin());
+ if (it == commands.end())
+ {
+ Command c;
+ c.name = std::string(elem);
+ c.length = len;
+ commands.push_back(c);
+ }
+ else
+ {
+ // change the length for every command
+ do
+ {
+ if ((*it).length < len)
+ (*it).length = len;
+
+ it = get_command(elem, ++it);
+ }
+ while (it != commands.end());
+ }
+ }
+
+ if (!elem.compare("}"))
+ return true;
+ return false;
+}
+
+bool Smtp::convert(std::istringstream& data_stream)
+{
+ std::string keyword;
+ bool retval = true;
+ bool ports_set = false;
+ Binder bind(table_api);
+
+ bind.set_when_proto("tcp");
+ bind.set_use_type("smtp");
+
+ table_api.open_table("smtp");
+
+ // parse the file configuration
+ while (data_stream >> keyword)
+ {
+ bool tmpval = true;
+
+ if (!keyword.compare("disabled"))
+ {
+ table_api.add_deleted_comment("disabled");
+ }
+ else if (!keyword.compare("inspection_type"))
+ {
+ table_api.add_deleted_comment("inspection_type");
+ data_stream >> keyword;
+ }
+ else if (!keyword.compare("enable_mime_decoding"))
+ {
+ table_api.add_deleted_comment("enable_mime_decoding");
+ }
+ else if (!keyword.compare("max_mime_depth"))
+ {
+ table_api.add_deleted_comment("max_mime_depth");
+ data_stream >> keyword;
+ }
+ else if (!keyword.compare("no_alerts"))
+ {
+ table_api.add_deleted_comment("no_alerts");
+ }
+ else if (!keyword.compare("print_cmds"))
+ {
+ table_api.add_deleted_comment("print_cmds");
+ }
+ else if (!keyword.compare("alert_unknown_cmds"))
+ {
+ table_api.add_deleted_comment("alert_unknown_cmds");
+ }
+ else if (!keyword.compare("memcap"))
+ {
+ table_api.add_deleted_comment("memcap");
+ data_stream >> keyword;
+ }
+ else if (!keyword.compare("max_mime_mem"))
+ {
+ table_api.add_deleted_comment("max_mime_mem");
+ data_stream >> keyword;
+ }
+ else if (!keyword.compare("b64_decode_depth"))
+ {
+ tmpval = parse_int_option("b64_decode_depth", data_stream, false);
+ }
+ else if (!keyword.compare("qp_decode_depth"))
+ {
+ tmpval = parse_int_option("qp_decode_depth", data_stream, false);
+ }
+ else if (!keyword.compare("bitenc_decode_depth"))
+ {
+ tmpval = parse_int_option("bitenc_decode_depth", data_stream, false);
+ }
+ else if (!keyword.compare("uu_decode_depth"))
+ {
+ tmpval = parse_int_option("uu_decode_depth", data_stream, false);
+ }
+ else if (!keyword.compare("alt_max_command_line_len"))
+ {
+ tmpval = parse_alt_max_cmd(data_stream);
+ }
+ else if (!keyword.compare("ignore_data"))
+ {
+ tmpval = table_api.add_option("ignore_data", true);
+ }
+ else if (!keyword.compare("ignore_tls_data"))
+ {
+ tmpval = table_api.add_option("ignore_tls_data", true);
+ }
+ else if (!keyword.compare("log_filename"))
+ {
+ tmpval = table_api.add_option("log_filename", true);
+ }
+ else if (!keyword.compare("log_mailfrom"))
+ {
+ tmpval = table_api.add_option("log_mailfrom", true);
+ }
+ else if (!keyword.compare("log_rcptto"))
+ {
+ tmpval = table_api.add_option("log_rcptto", true);
+ }
+ else if (!keyword.compare("log_email_hdrs"))
+ {
+ tmpval = table_api.add_option("log_email_hdrs", true);
+ }
+ else if (!keyword.compare("email_hdrs_log_depth"))
+ {
+ tmpval = parse_int_option("email_hdrs_log_depth", data_stream, false);
+ }
+ else if (!keyword.compare("max_command_line_len"))
+ {
+ tmpval = parse_int_option("max_command_line_len", data_stream, false);
+ }
+ else if (!keyword.compare("max_header_line_len"))
+ {
+ tmpval = parse_int_option("max_header_line_len", data_stream, false);
+ }
+ else if (!keyword.compare("max_response_line_len"))
+ {
+ tmpval = parse_int_option("max_response_line_len", data_stream, false);
+ }
+ else if (!keyword.compare("normalize"))
+ {
+ std::string norm_type;
+
+ if (!(data_stream >> norm_type))
+ data_api.failed_conversion(data_stream, "smtp: normalize <missing_arg>");
+
+ else if (!norm_type.compare("none"))
+ table_api.add_option("normalize", "none");
+ else if (!norm_type.compare("all"))
+ table_api.add_option("normalize", "all");
+ else if (!norm_type.compare("cmds"))
+ table_api.add_option("normalize", "cmds");
+ else
+ {
+ data_api.failed_conversion(data_stream, "smtp: normalize " + norm_type);
+ }
+ }
+ else if (!keyword.compare("xlink2state"))
+ {
+ if ((data_stream >> keyword) && !keyword.compare("{"))
+ {
+ std::string state_type;
+
+ if (!(data_stream >> state_type))
+ data_api.failed_conversion(data_stream, "smtp: xlink2state <missing_arg>");
+
+ else if (!state_type.compare("disable"))
+ table_api.add_option("xlink2state", "disable");
+ else if (!state_type.compare("enabled"))
+ table_api.add_option("xlink2state", "alert");
+ else if (!state_type.compare("drop"))
+ table_api.add_option("xlink2state", "drop");
+ else
+ {
+ data_api.failed_conversion(data_stream, "smtp: xlink2state " + state_type);
+ }
+ if ((data_stream >> keyword) && keyword.compare("}"))
+ {
+ data_api.failed_conversion(data_stream, "smtp: xlink2state " + state_type);
+ }
+ }
+ else
+ {
+ data_api.failed_conversion(data_stream, "smtp: xlink2state " + keyword);
+ }
+ }
+ else if (!keyword.compare("auth_cmds"))
+ {
+ tmpval = parse_curly_bracket_list("auth_cmds", data_stream);
+ }
+ else if (!keyword.compare("binary_data_cmds"))
+ {
+ tmpval = parse_curly_bracket_list("binary_data_cmds", data_stream);
+ }
+ else if (!keyword.compare("data_cmds"))
+ {
+ tmpval = parse_curly_bracket_list("data_cmds", data_stream);
+ }
+ else if (!keyword.compare("normalize_cmds"))
+ {
+ tmpval = parse_curly_bracket_list("normalize_cmds", data_stream);
+ }
+ else if (!keyword.compare("invalid_cmds"))
+ {
+ tmpval = parse_curly_bracket_list("invalid_cmds", data_stream);
+ }
+ else if (!keyword.compare("valid_cmds"))
+ {
+ tmpval = parse_curly_bracket_list("valid_cmds", data_stream);
+ }
+ else if (!keyword.compare("ports"))
+ {
+ std::string tmp = "";
+ table_api.add_diff_option_comment("ports", "bindings");
+
+ if ((data_stream >> keyword) && !keyword.compare("{"))
+ {
+ while (data_stream >> keyword && keyword.compare("}"))
+ {
+ ports_set = true;
+ bind.add_when_port(keyword);
+ }
+ }
+ else
+ {
+ data_api.failed_conversion(data_stream, "ports <bracketed_port_list>");
+ retval = false;
+ }
+ }
+ else
+ {
+ tmpval = false;
+ }
+
+ if (!tmpval)
+ {
+ data_api.failed_conversion(data_stream, keyword);
+ retval = false;
+ }
+ }
+
+ if (!commands.empty())
+ {
+ table_api.open_table("alt_max_command_line_len");
+
+ for (auto c : commands)
+ {
+ table_api.open_table();
+ bool tmpval1 = table_api.add_option("command", c.name);
+ bool tmpval2 = true;
+
+ if (c.length != command_default_len)
+ tmpval2 = table_api.add_option("length", c.length);
+
+ table_api.close_table();
+
+ if (!tmpval1 || !tmpval2 )
+ retval = false;
+ }
+
+ table_api.close_table();
+ }
+
+ if (!ports_set)
+ bind.add_when_port("25");
+ bind.add_when_port("465");
+ bind.add_when_port("587");
+ bind.add_when_port("691");
+
+ return retval;
+}
+
+/**************************
+ ******* A P I ***********
+ **************************/
+
+static ConversionState* ctor(Converter& c)
+{
+ return new Smtp(c);
+}
+
+static const ConvertMap preprocessor_smtp =
+{
+ "smtp",
+ ctor,
+};
+
+const ConvertMap* smtp_map = &preprocessor_smtp;
+}
+
extern const ConvertMap* dns_map;
extern const ConvertMap* pop_map;
extern const ConvertMap* imap_map;
+extern const ConvertMap* smtp_map;
extern const ConvertMap* sfportscan_map;
extern const ConvertMap* stream_ip_map;
extern const ConvertMap* stream_global_map;
dns_map,
pop_map,
imap_map,
+ smtp_map,
sfportscan_map,
stream_ip_map,
stream_global_map,