From: Russ Combs Date: Fri, 15 May 2015 14:48:11 +0000 (-0400) Subject: bhagya - ported smtp preprocessor from Snort X-Git-Tag: 3.0.0-233~977 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=510cb7e3129132ff14def2ee630fd6847a08ff09;p=thirdparty%2Fsnort3.git bhagya - ported smtp preprocessor from Snort --- diff --git a/ChangeLog b/ChangeLog index 22ce30ec1..7036d459a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ Pending - build 152 +-- ported smtp inspector from Snort -- static analysis fix for new_http_inspect 15/05/08 - build 151 diff --git a/configure.ac b/configure.ac index 95c17c529..6efe3e270 100644 --- a/configure.ac +++ b/configure.ac @@ -975,6 +975,7 @@ src/service_inspectors/imap/Makefile \ 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 \ diff --git a/lua/snort.lua b/lua/snort.lua index c37fabe33..937e84aee 100644 --- a/lua/snort.lua +++ b/lua/snort.lua @@ -66,6 +66,7 @@ arp_spoof = { } back_orifice = { } dns = { } imap = { } +smtp = { } perf_monitor = { } pop = { } port_scan = { } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 424ef8a1c..d5db97dfd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -61,6 +61,7 @@ if (STATIC_INSPECTORS) nhttp_inspect pop rpc_decode + smtp ssh wizard ) diff --git a/src/Makefile.am b/src/Makefile.am index f63195ed6..eed74a744 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,6 +16,7 @@ service_inspectors/imap/libimap.a \ 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 diff --git a/src/file_api/file_api.h b/src/file_api/file_api.h index 9bd928dd8..65186b116 100644 --- a/src/file_api/file_api.h +++ b/src/file_api/file_api.h @@ -47,6 +47,13 @@ #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; @@ -56,7 +63,6 @@ struct FILE_LogState struct MAIL_LogState { - void* log_hdrs_bkt; unsigned char* emailHdrs; uint32_t log_depth; uint32_t hdrs_logged; @@ -115,9 +121,9 @@ struct MimeDataPafInfo 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); @@ -153,8 +159,7 @@ struct MimeState DecodeConfig* decode_conf; MAIL_LogConfig* log_config; MAIL_LogState* log_state; - void* decode_bkt; - void* log_mempool; + void *config; MimeMethods* methods; }; diff --git a/src/file_api/file_mime_process.cc b/src/file_api/file_mime_process.cc index 555db9bc6..ce643aeda 100644 --- a/src/file_api/file_mime_process.cc +++ b/src/file_api/file_mime_process.cc @@ -219,11 +219,11 @@ int set_log_buffers(MAIL_LogState** log_state, MAIL_LogConfig* conf) || 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; @@ -550,7 +550,7 @@ static const uint8_t* process_mime_header( 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; @@ -771,7 +771,7 @@ const uint8_t* process_mime_data_paf(void* packet, const uint8_t* start, const u * 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; } @@ -820,7 +820,7 @@ const uint8_t* process_mime_data_paf(void* packet, const uint8_t* start, const u 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 diff --git a/src/file_api/file_mime_process.h b/src/file_api/file_mime_process.h index 3211a96c1..8553ac247 100644 --- a/src/file_api/file_mime_process.h +++ b/src/file_api/file_mime_process.h @@ -53,9 +53,6 @@ /* 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; diff --git a/src/service_inspectors/CMakeLists.txt b/src/service_inspectors/CMakeLists.txt index f4e4f640b..786708d7f 100644 --- a/src/service_inspectors/CMakeLists.txt +++ b/src/service_inspectors/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(imap) add_subdirectory(nhttp_inspect) add_subdirectory(pop) add_subdirectory(rpc_decode) +add_subdirectory(smtp) add_subdirectory(ssh) add_subdirectory(wizard) @@ -19,6 +20,7 @@ if (STATIC_INSPECTORS) nhttp_inspect pop rpc_decode + smtp ssh wizard ) diff --git a/src/service_inspectors/Makefile.am b/src/service_inspectors/Makefile.am index f744eb7a0..2ade44f5d 100644 --- a/src/service_inspectors/Makefile.am +++ b/src/service_inspectors/Makefile.am @@ -25,6 +25,7 @@ imap \ nhttp_inspect \ pop \ rpc_decode \ +smtp \ ssh \ wizard diff --git a/src/service_inspectors/imap/imap.cc b/src/service_inspectors/imap/imap.cc index bd5ad1667..c5b002cf8 100644 --- a/src/service_inspectors/imap/imap.cc +++ b/src/service_inspectors/imap/imap.cc @@ -159,6 +159,7 @@ IMAPData* SetNewIMAPData(IMAP_PROTO_CONF* config, Packet* p) 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; diff --git a/src/service_inspectors/pop/pop.cc b/src/service_inspectors/pop/pop.cc index 4cb511053..e17f306e0 100644 --- a/src/service_inspectors/pop/pop.cc +++ b/src/service_inspectors/pop/pop.cc @@ -112,6 +112,7 @@ POPData* SetNewPOPData(POP_PROTO_CONF* config, Packet* p) 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; diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index b0d8879b5..d2ab393f5 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -37,6 +37,7 @@ extern const BaseApi* sin_imap; 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; @@ -57,6 +58,7 @@ const BaseApi* service_inspectors[] = sin_nhttp, sin_pop, sin_rpc_decode, + sin_smtp, sin_ssh, sin_telnet, sin_wizard, diff --git a/src/service_inspectors/smtp/CMakeLists.txt b/src/service_inspectors/smtp/CMakeLists.txt new file mode 100644 index 000000000..3a1827f57 --- /dev/null +++ b/src/service_inspectors/smtp/CMakeLists.txt @@ -0,0 +1,22 @@ + +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) diff --git a/src/service_inspectors/smtp/Makefile.am b/src/service_inspectors/smtp/Makefile.am new file mode 100644 index 000000000..5fd0bccf3 --- /dev/null +++ b/src/service_inspectors/smtp/Makefile.am @@ -0,0 +1,30 @@ +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@ + diff --git a/src/service_inspectors/smtp/smtp.cc b/src/service_inspectors/smtp/smtp.cc new file mode 100644 index 000000000..8ec8e497e --- /dev/null +++ b/src/service_inspectors/smtp/smtp.cc @@ -0,0 +1,1762 @@ +//-------------------------------------------------------------------------- +// 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 +#include +#include +#include + +#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 + */ + { "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 + diff --git a/src/service_inspectors/smtp/smtp.h b/src/service_inspectors/smtp/smtp.h new file mode 100644 index 000000000..b8bfe8581 --- /dev/null +++ b/src/service_inspectors/smtp/smtp.h @@ -0,0 +1,179 @@ +//-------------------------------------------------------------------------- +// 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 + */ + +#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 */ + diff --git a/src/service_inspectors/smtp/smtp_config.h b/src/service_inspectors/smtp/smtp_config.h new file mode 100644 index 000000000..2b03dad6b --- /dev/null +++ b/src/service_inspectors/smtp/smtp_config.h @@ -0,0 +1,145 @@ +//-------------------------------------------------------------------------- +// 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 + diff --git a/src/service_inspectors/smtp/smtp_module.cc b/src/service_inspectors/smtp/smtp_module.cc new file mode 100644 index 000000000..07121bd05 --- /dev/null +++ b/src/service_inspectors/smtp/smtp_module.cc @@ -0,0 +1,377 @@ +//-------------------------------------------------------------------------- +// 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 + +#include "smtp_module.h" +#include +#include +#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; +} + diff --git a/src/service_inspectors/smtp/smtp_module.h b/src/service_inspectors/smtp/smtp_module.h new file mode 100644 index 000000000..ae8f2537d --- /dev/null +++ b/src/service_inspectors/smtp/smtp_module.h @@ -0,0 +1,107 @@ +//-------------------------------------------------------------------------- +// 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 + +#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 cmds; + std::string names; + int number; +}; + +#endif + diff --git a/src/service_inspectors/smtp/smtp_normalize.cc b/src/service_inspectors/smtp/smtp_normalize.cc new file mode 100644 index 000000000..dcd834809 --- /dev/null +++ b/src/service_inspectors/smtp/smtp_normalize.cc @@ -0,0 +1,169 @@ +//-------------------------------------------------------------------------- +// 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 + +#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; +} + diff --git a/src/service_inspectors/smtp/smtp_normalize.h b/src/service_inspectors/smtp/smtp_normalize.h new file mode 100644 index 000000000..6fcd09817 --- /dev/null +++ b/src/service_inspectors/smtp/smtp_normalize.h @@ -0,0 +1,27 @@ +//-------------------------------------------------------------------------- +// 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 + diff --git a/src/service_inspectors/smtp/smtp_paf.cc b/src/service_inspectors/smtp/smtp_paf.cc new file mode 100644 index 000000000..abf8f8cc3 --- /dev/null +++ b/src/service_inspectors/smtp/smtp_paf.cc @@ -0,0 +1,376 @@ +//-------------------------------------------------------------------------- +// 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 +#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; +} + diff --git a/src/service_inspectors/smtp/smtp_paf.h b/src/service_inspectors/smtp/smtp_paf.h new file mode 100644 index 000000000..d64e3f916 --- /dev/null +++ b/src/service_inspectors/smtp/smtp_paf.h @@ -0,0 +1,79 @@ +//-------------------------------------------------------------------------- +// 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 + diff --git a/src/service_inspectors/smtp/smtp_util.cc b/src/service_inspectors/smtp/smtp_util.cc new file mode 100644 index 000000000..ca9313069 --- /dev/null +++ b/src/service_inspectors/smtp/smtp_util.cc @@ -0,0 +1,318 @@ +//-------------------------------------------------------------------------- +// 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 +#include +#include +#include + +#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 + diff --git a/src/service_inspectors/smtp/smtp_util.h b/src/service_inspectors/smtp/smtp_util.h new file mode 100644 index 000000000..8314186ab --- /dev/null +++ b/src/service_inspectors/smtp/smtp_util.h @@ -0,0 +1,46 @@ +//-------------------------------------------------------------------------- +// 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 + diff --git a/src/service_inspectors/smtp/smtp_xlink2state.cc b/src/service_inspectors/smtp/smtp_xlink2state.cc new file mode 100644 index 000000000..fbb77767d --- /dev/null +++ b/src/service_inspectors/smtp/smtp_xlink2state.cc @@ -0,0 +1,292 @@ +//-------------------------------------------------------------------------- +// 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 +#endif + +#include +#include + +#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= + + 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 + 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; +} + diff --git a/src/service_inspectors/smtp/smtp_xlink2state.h b/src/service_inspectors/smtp/smtp_xlink2state.h new file mode 100644 index 000000000..503c35c07 --- /dev/null +++ b/src/service_inspectors/smtp/smtp_xlink2state.h @@ -0,0 +1,37 @@ +//-------------------------------------------------------------------------- +// 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 + diff --git a/tools/snort2lua/preprocessor_states/CMakeLists.txt b/tools/snort2lua/preprocessor_states/CMakeLists.txt index 1dcc22ad0..fb7e1615b 100644 --- a/tools/snort2lua/preprocessor_states/CMakeLists.txt +++ b/tools/snort2lua/preprocessor_states/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(preprocessor_states pps_dns.cc pps_pop.cc pps_imap.cc + pps_smtp.cc pps_sfportscan.cc pps_stream5_ip.cc pps_stream5_global.cc diff --git a/tools/snort2lua/preprocessor_states/Makefile.am b/tools/snort2lua/preprocessor_states/Makefile.am index 48a4ca0a5..0d190f55f 100644 --- a/tools/snort2lua/preprocessor_states/Makefile.am +++ b/tools/snort2lua/preprocessor_states/Makefile.am @@ -20,6 +20,7 @@ pps_ssh.cc \ pps_dns.cc \ pps_pop.cc \ pps_imap.cc \ +pps_smtp.cc \ pps_sfportscan.cc \ pps_stream5_ip.cc \ pps_stream5_global.cc \ diff --git a/tools/snort2lua/preprocessor_states/pps_imap.cc b/tools/snort2lua/preprocessor_states/pps_imap.cc index 58661effc..543660378 100644 --- a/tools/snort2lua/preprocessor_states/pps_imap.cc +++ b/tools/snort2lua/preprocessor_states/pps_imap.cc @@ -64,11 +64,13 @@ bool Imap::convert(std::istringstream& data_stream) 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")) diff --git a/tools/snort2lua/preprocessor_states/pps_pop.cc b/tools/snort2lua/preprocessor_states/pps_pop.cc index 14be29ebd..dbb79627a 100644 --- a/tools/snort2lua/preprocessor_states/pps_pop.cc +++ b/tools/snort2lua/preprocessor_states/pps_pop.cc @@ -64,11 +64,13 @@ bool Pop::convert(std::istringstream& data_stream) 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")) diff --git a/tools/snort2lua/preprocessor_states/pps_smtp.cc b/tools/snort2lua/preprocessor_states/pps_smtp.cc new file mode 100644 index 000000000..da6e9fd1c --- /dev/null +++ b/tools/snort2lua/preprocessor_states/pps_smtp.cc @@ -0,0 +1,382 @@ +//-------------------------------------------------------------------------- +// 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 + +#include +#include + +#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 commands; + + bool parse_alt_max_cmd(std::istringstream& data_stream); + std::vector::iterator get_command(std::string cmd_name, + std::vector::iterator it); +}; +} // namespace + +std::vector::iterator Smtp::get_command( + std::string cmd_name, + std::vector::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 "); + + 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 "); + + 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 "); + 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; +} + diff --git a/tools/snort2lua/preprocessor_states/preprocessor_api.cc b/tools/snort2lua/preprocessor_states/preprocessor_api.cc index 68a7e675c..5001ab469 100644 --- a/tools/snort2lua/preprocessor_states/preprocessor_api.cc +++ b/tools/snort2lua/preprocessor_states/preprocessor_api.cc @@ -42,6 +42,7 @@ extern const ConvertMap* ssh_map; 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; @@ -71,6 +72,7 @@ const std::vector preprocessor_api = dns_map, pop_map, imap_map, + smtp_map, sfportscan_map, stream_ip_map, stream_global_map,