]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
bhagya - ported smtp preprocessor from Snort
authorRuss Combs <rucombs@cisco.com>
Fri, 15 May 2015 14:48:11 +0000 (10:48 -0400)
committerRuss Combs <rucombs@cisco.com>
Fri, 15 May 2015 14:48:11 +0000 (10:48 -0400)
34 files changed:
ChangeLog
configure.ac
lua/snort.lua
src/CMakeLists.txt
src/Makefile.am
src/file_api/file_api.h
src/file_api/file_mime_process.cc
src/file_api/file_mime_process.h
src/service_inspectors/CMakeLists.txt
src/service_inspectors/Makefile.am
src/service_inspectors/imap/imap.cc
src/service_inspectors/pop/pop.cc
src/service_inspectors/service_inspectors.cc
src/service_inspectors/smtp/CMakeLists.txt [new file with mode: 0644]
src/service_inspectors/smtp/Makefile.am [new file with mode: 0644]
src/service_inspectors/smtp/smtp.cc [new file with mode: 0644]
src/service_inspectors/smtp/smtp.h [new file with mode: 0644]
src/service_inspectors/smtp/smtp_config.h [new file with mode: 0644]
src/service_inspectors/smtp/smtp_module.cc [new file with mode: 0644]
src/service_inspectors/smtp/smtp_module.h [new file with mode: 0644]
src/service_inspectors/smtp/smtp_normalize.cc [new file with mode: 0644]
src/service_inspectors/smtp/smtp_normalize.h [new file with mode: 0644]
src/service_inspectors/smtp/smtp_paf.cc [new file with mode: 0644]
src/service_inspectors/smtp/smtp_paf.h [new file with mode: 0644]
src/service_inspectors/smtp/smtp_util.cc [new file with mode: 0644]
src/service_inspectors/smtp/smtp_util.h [new file with mode: 0644]
src/service_inspectors/smtp/smtp_xlink2state.cc [new file with mode: 0644]
src/service_inspectors/smtp/smtp_xlink2state.h [new file with mode: 0644]
tools/snort2lua/preprocessor_states/CMakeLists.txt
tools/snort2lua/preprocessor_states/Makefile.am
tools/snort2lua/preprocessor_states/pps_imap.cc
tools/snort2lua/preprocessor_states/pps_pop.cc
tools/snort2lua/preprocessor_states/pps_smtp.cc [new file with mode: 0644]
tools/snort2lua/preprocessor_states/preprocessor_api.cc

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