]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
SMTP MIME Email Message decoder
authorDavid Abarbanel <david.abarbanel@baesystems.com>
Tue, 6 Nov 2012 14:45:36 +0000 (09:45 -0500)
committerVictor Julien <victor@inliniac.net>
Thu, 30 Oct 2014 12:33:53 +0000 (13:33 +0100)
21 files changed:
rules/files.rules
rules/smtp-events.rules
src/Makefile.am
src/app-layer-smtp.c
src/app-layer-smtp.h
src/detect-engine-file.c
src/detect-engine-file.h
src/detect-fileext.c
src/detect-filemagic.c
src/detect-filemd5.c
src/detect-filename.c
src/detect-filesize.c
src/detect-filestore.c
src/log-file.c
src/log-filestore.c
src/mime-decode.c [new file with mode: 0644]
src/mime-decode.h [new file with mode: 0644]
src/suricata.c
src/util-base64.c [new file with mode: 0644]
src/util-base64.h [new file with mode: 0644]
suricata.yaml.in

index 0f770902f8d95b9385bdc469383db588db08a9ba..467735ee0461356cc9deba40957aab28823d3228 100644 (file)
@@ -43,3 +43,5 @@
 # Alert and store pdf attachment but not pdf file
 #alert http any any -> any any (msg:"FILE pdf claimed, but not pdf"; flow:established,to_client; fileext:"pdf"; filemagic:!"PDF document"; filestore; sid:22; rev:1;)
 
+# Alert and store files over SMTP
+#alert smtp any any -> any any (msg:"File Found over SMTP and stored"; filestore; sid:27; rev:1;)
index a20c6155a1675ca395e9d5ea77e2e007fc93f549..52b4647f9618cd57b3b637635816e23930b8f856 100644 (file)
@@ -16,3 +16,12 @@ alert smtp any any -> any any (msg:"SURICATA SMTP no server welcome message"; fl
 alert smtp any any -> any any (msg:"SURICATA SMTP tls rejected"; flow:established; app-layer-event:smtp.tls_rejected; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220007; rev:1;)
 alert smtp any any -> any any (msg:"SURICATA SMTP data command rejected"; flow:established,to_client; app-layer-event:smtp.data_command_rejected; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220008; rev:1;)
 
+# SMTP MIME events
+#alert smtp any any -> any any (msg:"SURICATA SMTP Mime parser failed"; flow:established; app-layer-event:smtp.mime_parse_failed; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220009; rev:1;)
+#alert smtp any any -> any any (msg:"SURICATA SMTP Mime malformed message found"; flow:established; app-layer-event:smtp.mime_malformed_msg; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220010; rev:1;)
+#alert smtp any any -> any any (msg:"SURICATA SMTP Mime base64-decoding failed"; flow:established; app-layer-event:smtp.mime_invalid_base64; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220011; rev:1;)
+#alert smtp any any -> any any (msg:"SURICATA SMTP Mime header name len exceeded"; flow:established; app-layer-event:smtp.mime_long_header_name; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220012; rev:1;)
+#alert smtp any any -> any any (msg:"SURICATA SMTP Mime header value len exceeded"; flow:established; app-layer-event:smtp.mime_long_header_value; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220013; rev:1;)
+#alert smtp any any -> any any (msg:"SURICATA SMTP Mime quoted-printable-decoding failed"; flow:established; app-layer-event:smtp.mime_invalid_qp; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220014; rev:1;)
+#alert smtp any any -> any any (msg:"SURICATA SMTP Mime line len exceeded"; flow:established; app-layer-event:smtp.mime_long_line; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220015; rev:1;)
+#alert smtp any any -> any any (msg:"SURICATA SMTP Mime encoded line len exceeded"; flow:established; app-layer-event:smtp.mime_long_enc_line; flowint:smtp.anomaly.count,+,1; classtype:protocol-command-decode; sid:2220016; rev:1;)
index ee9b654e239432020ce185e74fc9639bededdd80..6d6be901fae609b8adf805b3baec73779b436e3f 100644 (file)
@@ -214,6 +214,7 @@ log-httplog.c log-httplog.h \
 log-pcap.c log-pcap.h \
 log-tcp-data.c log-tcp-data.h \
 log-tlslog.c log-tlslog.h \
+mime-decode.c mime-decode.h \
 output.c output.h \
 output-file.c output-file.h \
 output-filedata.c output-filedata.h \
@@ -283,6 +284,7 @@ tm-threads.c tm-threads.h tm-threads-common.h \
 unix-manager.c unix-manager.h \
 util-action.c util-action.h \
 util-atomic.c util-atomic.h \
+util-base64.c util-base64.h \
 util-bloomfilter-counting.c util-bloomfilter-counting.h \
 util-bloomfilter.c util-bloomfilter.h \
 util-buffer.c util-buffer.h \
index fc3b9f7dadb4511eebb3eb9b80a070705e9b5c49..0d5ef040e8e7d797ea6a20f18c3475a6964542c3 100644 (file)
@@ -113,6 +113,25 @@ SCEnumCharMap smtp_decoder_event_table[ ] = {
       SMTP_DECODER_EVENT_TLS_REJECTED },
     { "DATA_COMMAND_REJECTED",
       SMTP_DECODER_EVENT_DATA_COMMAND_REJECTED },
+
+    /* MIME Events */
+    { "MIME_PARSE_FAILED",
+      SMTP_DECODER_EVENT_MIME_PARSE_FAILED },
+    { "MIME_MALFORMED_MSG",
+      SMTP_DECODER_EVENT_MIME_MALFORMED_MSG },
+    { "MIME_INVALID_BASE64",
+      SMTP_DECODER_EVENT_MIME_INVALID_BASE64 },
+    { "MIME_INVALID_QP",
+      SMTP_DECODER_EVENT_MIME_INVALID_QP },
+    { "MIME_LONG_LINE",
+      SMTP_DECODER_EVENT_MIME_LONG_LINE },
+    { "MIME_LONG_ENC_LINE",
+      SMTP_DECODER_EVENT_MIME_LONG_ENC_LINE },
+    { "MIME_LONG_HEADER_NAME",
+      SMTP_DECODER_EVENT_MIME_LONG_HEADER_NAME },
+    { "MIME_LONG_HEADER_VALUE",
+      SMTP_DECODER_EVENT_MIME_LONG_HEADER_VALUE },
+
     { NULL,                      -1 },
 };
 
@@ -183,10 +202,185 @@ SCEnumCharMap smtp_reply_map[ ] = {
     { "555", SMTP_REPLY_555 },
     {  NULL,  -1 },
 };
-//static void SMTPParserReset(void)
-//{
-//    return;
-//}
+
+typedef struct SMTPConfig {
+
+    int decode_mime;
+    MimeDecConfig mime_config;
+
+} SMTPConfig;
+
+/* Create SMTP config structure */
+static SMTPConfig smtp_config = { 0, { 0, 0, 0, 0 } };
+
+/**
+ * \brief Configure SMTP Mime Decoder by parsing out 'smtp-mime' section of YAML
+ * config file
+ *
+ * \return none
+ */
+static void SMTPConfigure(void) {
+
+    SCEnter();
+    int ret = 0, val;
+    intmax_t imval;
+
+    ConfNode *config = ConfGetNode("smtp-mime");
+    if (config != NULL) {
+
+        ret = ConfGetChildValueBool(config, "decode-mime", &val);
+        if (ret) {
+            smtp_config.decode_mime = val;
+        }
+
+        ret = ConfGetChildValueBool(config, "decode-base64", &val);
+        if (ret) {
+            smtp_config.mime_config.decode_base64 = val;
+        }
+
+        ret = ConfGetChildValueBool(config, "decode-quoted-printable", &val);
+        if (ret) {
+            smtp_config.mime_config.decode_quoted_printable = val;
+        }
+
+        ret = ConfGetChildValueInt(config, "header-value-depth", &imval);
+        if (ret) {
+            smtp_config.mime_config.header_value_depth = (uint32_t) imval;
+        }
+
+        ret = ConfGetChildValueBool(config, "extract-urls", &val);
+        if (ret) {
+            smtp_config.mime_config.extract_urls = val;
+        }
+    }
+
+    /* Pass mime config data to MimeDec API */
+    MimeDecSetConfig(&smtp_config.mime_config);
+
+    SCReturn;
+}
+
+static int ProcessDataChunk(const uint8_t *chunk, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    Flow *flow = (Flow *) state->data;
+    SMTPState *smtp_state = (SMTPState *) flow->alstate;
+    MimeDecEntity *entity = (MimeDecEntity *) state->stack->top->data;
+    FileContainer *files = NULL;
+    uint16_t flags = 0;
+
+    /* Set flags */
+    if (flow->flags & FLOW_FILE_NO_STORE_TS) {
+        flags |= FILE_NOSTORE;
+    }
+
+    if (flow->flags & FLOW_FILE_NO_MAGIC_TS) {
+        flags |= FILE_NOMAGIC;
+    }
+
+    if (flow->flags & FLOW_FILE_NO_MD5_TS) {
+        flags |= FILE_NOMD5;
+    }
+
+    /* Determine whether to process files */
+    if ((flags & (FILE_NOSTORE | FILE_NOMAGIC | FILE_NOMD5)) ==
+            (FILE_NOSTORE | FILE_NOMAGIC | FILE_NOMD5)) {
+        SCLogDebug("File content ignored");
+        return 0;
+    }
+
+    /* Find file */
+    if (entity->ctnt_flags & CTNT_IS_ATTACHMENT) {
+
+        /* Make sure file container allocated */
+        if (smtp_state->files_ts == NULL) {
+            smtp_state->files_ts = FileContainerAlloc();
+            if (smtp_state->files_ts == NULL) {
+                ret = MIME_DEC_ERR_MEM;
+                SCLogError(SC_ERR_MEM_ALLOC, "Could not create file container");
+                goto end;
+            }
+        }
+        files = smtp_state->files_ts;
+
+        /* Open file if necessary */
+        if (state->body_begin) {
+
+            if (SCLogDebugEnabled()) {
+                SCLogDebug("Opening file...%u bytes", len);
+                printf("File - ");
+                for (uint32_t i = 0; i < entity->filename_len; i++) {
+                    printf("%c", entity->filename[i]);
+                }
+                printf("\n");
+            }
+
+            /* Set storage flag if applicable since only the first file in the
+             * flow seems to be processed by the 'filestore' detector */
+            if (files->head != NULL && (files->head->flags & FILE_STORE)) {
+                flags |= FILE_STORE;
+            }
+
+            if (FileOpenFile(files, (uint8_t *) entity->filename, entity->filename_len,
+                    (uint8_t *) chunk, len, flags) == NULL) {
+                ret = MIME_DEC_ERR_DATA;
+                SCLogDebug("FileOpenFile() failed");
+            }
+
+            /* If close in the same chunk, then pass in empty bytes */
+            if (state->body_end) {
+
+                SCLogDebug("Closing file...%u bytes", len);
+
+                if (files->tail->state == FILE_STATE_OPENED) {
+                    ret = FileCloseFile(files, (uint8_t *) NULL, 0, flags);
+                    if (ret != 0) {
+                        SCLogDebug("FileCloseFile() failed: %d", ret);
+                    }
+                } else {
+                    SCLogDebug("File already closed");
+                }
+            }
+        } else if (state->body_end) {
+            /* Close file */
+            SCLogDebug("Closing file...%u bytes", len);
+
+            if (files->tail->state == FILE_STATE_OPENED) {
+                ret = FileCloseFile(files, (uint8_t *) chunk, len, flags);
+                if (ret != 0) {
+                    SCLogDebug("FileCloseFile() failed: %d", ret);
+                }
+            } else {
+                SCLogDebug("File already closed");
+            }
+        } else {
+            /* Append data chunk to file */
+            SCLogDebug("Appending file...%u bytes", len);
+
+            /* 0 is ok, -2 is not stored, -1 is error */
+            ret = FileAppendData(files, (uint8_t *) chunk, len);
+            if (ret == -2) {
+                ret = 0;
+                SCLogDebug("FileAppendData() - file no longer being extracted");
+            } else if (ret < 0) {
+                SCLogDebug("FileAppendData() failed: %d", ret);
+            }
+        }
+
+        if (ret == MIME_DEC_OK) {
+            SCLogDebug("Successfully processed file data!");
+        }
+    } else {
+        SCLogDebug("Body not a Ctnt_attachment");
+    }
+
+    if (files != NULL) {
+        FilePrune(files);
+    }
+end:
+    SCReturnInt(ret);
+}
 
 /**
  * \internal
@@ -496,6 +690,53 @@ static int SMTPProcessCommandDATA(SMTPState *state, Flow *f,
          * the command buffer to be used by the reply handler to match
          * the reply received */
         SMTPInsertCommandIntoCommandBuffer(SMTP_COMMAND_DATA_MODE, state, f);
+
+        if (smtp_config.decode_mime) {
+            /* Complete parsing task */
+            int ret = MimeDecParseComplete(state->mime_state);
+            if (ret != MIME_DEC_OK) {
+
+                AppLayerDecoderEventsSetEvent(f, SMTP_DECODER_EVENT_MIME_PARSE_FAILED);
+                SCLogDebug("MimeDecParseComplete() function failed");
+            }
+
+            /* Generate decoder events */
+            MimeDecEntity *msg = state->mime_state->msg;
+            if (msg->anomaly_flags & ANOM_INVALID_BASE64) {
+                AppLayerDecoderEventsSetEvent(f, SMTP_DECODER_EVENT_MIME_INVALID_BASE64);
+            }
+            if (msg->anomaly_flags & ANOM_INVALID_QP) {
+                AppLayerDecoderEventsSetEvent(f, SMTP_DECODER_EVENT_MIME_INVALID_QP);
+            }
+            if (msg->anomaly_flags & ANOM_LONG_LINE) {
+                AppLayerDecoderEventsSetEvent(f, SMTP_DECODER_EVENT_MIME_LONG_LINE);
+            }
+            if (msg->anomaly_flags & ANOM_LONG_ENC_LINE) {
+                AppLayerDecoderEventsSetEvent(f, SMTP_DECODER_EVENT_MIME_LONG_ENC_LINE);
+            }
+            if (msg->anomaly_flags & ANOM_LONG_HEADER_NAME) {
+                AppLayerDecoderEventsSetEvent(f, SMTP_DECODER_EVENT_MIME_LONG_HEADER_NAME);
+            }
+            if (msg->anomaly_flags & ANOM_LONG_HEADER_VALUE) {
+                AppLayerDecoderEventsSetEvent(f, SMTP_DECODER_EVENT_MIME_LONG_HEADER_VALUE);
+            }
+            if (msg->anomaly_flags & ANOM_MALFORMED_MSG) {
+                AppLayerDecoderEventsSetEvent(f, SMTP_DECODER_EVENT_MIME_MALFORMED_MSG);
+            }
+        }
+    }
+
+    /* If DATA, then parse out a MIME message */
+    if (state->current_command == SMTP_COMMAND_DATA &&
+            (state->parser_state & SMTP_PARSER_STATE_COMMAND_DATA_MODE)) {
+
+        if (smtp_config.decode_mime) {
+            int ret = MimeDecParseLine((const char *) state->current_line,
+                    state->current_line_len, state->mime_state);
+            if (ret != MIME_DEC_OK) {
+                SCLogDebug("MimeDecParseLine() function returned an error code: %d", ret);
+            }
+        }
     }
 
     return 0;
@@ -665,6 +906,30 @@ static int SMTPProcessRequest(SMTPState *state, Flow *f,
         } else if (state->current_line_len >= 4 &&
                    SCMemcmpLowercase("data", state->current_line, 4) == 0) {
             state->current_command = SMTP_COMMAND_DATA;
+
+            if (smtp_config.decode_mime) {
+                /* Re-init the MIME parser */
+                if (state->mime_state != NULL) {
+                    MimeDecDeInitParser(state->mime_state);
+                }
+                state->mime_state = MimeDecInitParser(f, ProcessDataChunk);
+                if (state->mime_state == NULL) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "MimeDecInitParser() failed to "
+                            "allocate data");
+                    return MIME_DEC_ERR_MEM;
+                }
+
+                /* Add new MIME message to end of list */
+                if (state->msg_head == NULL) {
+                    state->msg_head = state->mime_state->msg;
+                    state->msg_tail = state->mime_state->msg;
+                }
+                else {
+                    state->msg_tail->next = state->mime_state->msg;
+                    state->msg_tail = state->mime_state->msg;
+                }
+            }
+
         } else if (state->current_line_len >= 4 &&
                    SCMemcmpLowercase("bdat", state->current_line, 4) == 0) {
             r = SMTPParseCommandBDAT(state);
@@ -821,6 +1086,16 @@ static void SMTPStateFree(void *p)
         SCFree(smtp_state->tc_db);
     }
 
+    FileContainerFree(smtp_state->files_ts);
+
+    /* Free MIME parser */
+    if (smtp_state->mime_state != NULL) {
+        MimeDecDeInitParser(smtp_state->mime_state);
+    }
+
+    /* Free list of MIME message recursively */
+    MimeDecFreeEntity(smtp_state->msg_head);
+
     SCFree(smtp_state);
 
     return;
@@ -927,6 +1202,8 @@ void RegisterSMTPParsers(void)
 
     SMTPSetMpmState();
 
+    SMTPConfigure();
+
 #ifdef UNITTESTS
     AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_SMTP, SMTPParserRegisterTests);
 #endif
@@ -3614,6 +3891,677 @@ end:
     return result;
 }
 
+/**
+ * \test Test DATA command w/MIME message.
+ */
+int SMTPParserTest14(void)
+{
+    int result = 0;
+    Flow f;
+    int r = 0;
+
+    /* 220 mx.google.com ESMTP d15sm986283wfl.6<CR><LF> */
+    uint8_t welcome_reply[] = {
+            0x32, 0x32, 0x30, 0x20, 0x6d, 0x78, 0x2e, 0x67,
+            0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
+            0x6d, 0x20, 0x45, 0x53, 0x4d, 0x54, 0x50, 0x20,
+            0x64, 0x31, 0x35, 0x73, 0x6d, 0x39, 0x38, 0x36,
+            0x32, 0x38, 0x33, 0x77, 0x66, 0x6c, 0x2e, 0x36,
+            0x0d, 0x0a
+    };
+    uint32_t welcome_reply_len = sizeof(welcome_reply);
+
+    /* EHLO boo.com<CR><LF> */
+    uint8_t request1[] = {
+            0x45, 0x48, 0x4c, 0x4f, 0x20, 0x62, 0x6f, 0x6f,
+            0x2e, 0x63, 0x6f, 0x6d, 0x0d, 0x0a
+    };
+    uint32_t request1_len = sizeof(request1);
+    /* 250-mx.google.com at your service, [117.198.115.50]<CR><LF>
+     * 250-SIZE 35882577<CR><LF>
+     * 250-8BITMIME<CR><LF>
+     * 250-STARTTLS<CR><LF>
+     * 250 ENHANCEDSTATUSCODES<CR><LF>
+     */
+    uint8_t reply1[] = {
+            0x32, 0x35, 0x30, 0x2d, 0x70, 0x6f, 0x6f, 0x6e,
+            0x61, 0x5f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x5f,
+            0x76, 0x6d, 0x31, 0x2e, 0x6c, 0x6f, 0x63, 0x61,
+            0x6c, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x0d,
+            0x0a, 0x32, 0x35, 0x30, 0x2d, 0x50, 0x49, 0x50,
+            0x45, 0x4c, 0x49, 0x4e, 0x49, 0x4e, 0x47, 0x0d,
+            0x0a, 0x32, 0x35, 0x30, 0x2d, 0x53, 0x49, 0x5a,
+            0x45, 0x20, 0x31, 0x30, 0x32, 0x34, 0x30, 0x30,
+            0x30, 0x30, 0x0d, 0x0a, 0x32, 0x35, 0x30, 0x2d,
+            0x56, 0x52, 0x46, 0x59, 0x0d, 0x0a, 0x32, 0x35,
+            0x30, 0x2d, 0x45, 0x54, 0x52, 0x4e, 0x0d, 0x0a,
+            0x32, 0x35, 0x30, 0x2d, 0x45, 0x4e, 0x48, 0x41,
+            0x4e, 0x43, 0x45, 0x44, 0x53, 0x54, 0x41, 0x54,
+            0x55, 0x53, 0x43, 0x4f, 0x44, 0x45, 0x53, 0x0d,
+            0x0a, 0x32, 0x35, 0x30, 0x2d, 0x38, 0x42, 0x49,
+            0x54, 0x4d, 0x49, 0x4d, 0x45, 0x0d, 0x0a, 0x32,
+            0x35, 0x30, 0x20, 0x44, 0x53, 0x4e, 0x0d, 0x0a
+    };
+    uint32_t reply1_len = sizeof(reply1);
+
+    /* MAIL FROM:asdff@asdf.com<CR><LF> */
+    uint8_t request2[] = {
+            0x4d, 0x41, 0x49, 0x4c, 0x20, 0x46, 0x52, 0x4f,
+            0x4d, 0x3a, 0x61, 0x73, 0x64, 0x66, 0x66, 0x40,
+            0x61, 0x73, 0x64, 0x66, 0x2e, 0x63, 0x6f, 0x6d,
+            0x0d, 0x0a
+    };
+    uint32_t request2_len = sizeof(request2);
+    /* 250 2.1.0 Ok<CR><LF> */
+    uint8_t reply2[] = {
+            0x32, 0x35, 0x30, 0x20, 0x32, 0x2e, 0x31, 0x2e,
+            0x30, 0x20, 0x4f, 0x6b, 0x0d, 0x0a
+    };
+    uint32_t reply2_len = sizeof(reply2);
+
+    /* RCPT TO:bimbs@gmail.com<CR><LF> */
+    uint8_t request3[] = {
+            0x52, 0x43, 0x50, 0x54, 0x20, 0x54, 0x4f, 0x3a,
+            0x62, 0x69, 0x6d, 0x62, 0x73, 0x40, 0x67, 0x6d,
+            0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x0d,
+            0x0a
+    };
+    uint32_t request3_len = sizeof(request3);
+    /* 250 2.1.5 Ok<CR><LF> */
+    uint8_t reply3[] = {
+            0x32, 0x35, 0x30, 0x20, 0x32, 0x2e, 0x31, 0x2e,
+            0x35, 0x20, 0x4f, 0x6b, 0x0d, 0x0a
+    };
+    uint32_t reply3_len = sizeof(reply3);
+
+    /* DATA<CR><LF> */
+    uint8_t request4[] = {
+            0x44, 0x41, 0x54, 0x41, 0x0d, 0x0a
+    };
+    uint32_t request4_len = sizeof(request4);
+    /* 354 End data with <CR><LF>.<CR><LF>|<CR><LF>| */
+    uint8_t reply4[] = {
+            0x33, 0x35, 0x34, 0x20, 0x45, 0x6e, 0x64, 0x20,
+            0x64, 0x61, 0x74, 0x61, 0x20, 0x77, 0x69, 0x74,
+            0x68, 0x20, 0x3c, 0x43, 0x52, 0x3e, 0x3c, 0x4c,
+            0x46, 0x3e, 0x2e, 0x3c, 0x43, 0x52, 0x3e, 0x3c,
+            0x4c, 0x46, 0x3e, 0x0d, 0x0a
+    };
+    uint32_t reply4_len = sizeof(reply4);
+
+    /* MIME_MSG */
+    uint64_t filesize = 133;
+    uint8_t request4_msg[] = {
+            0x4D, 0x49, 0x4D, 0x45, 0x2D, 0x56, 0x65, 0x72,
+            0x73, 0x69, 0x6F, 0x6E, 0x3A, 0x20, 0x31, 0x2E,
+            0x30, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65,
+            0x6E, 0x74, 0x2D, 0x54, 0x79, 0x70, 0x65, 0x3A,
+            0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61,
+            0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x6F, 0x63, 0x74,
+            0x65, 0x74, 0x2D, 0x73, 0x74, 0x72, 0x65, 0x61,
+            0x6D, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65,
+            0x6E, 0x74, 0x2D, 0x54, 0x72, 0x61, 0x6E, 0x73,
+            0x66, 0x65, 0x72, 0x2D, 0x45, 0x6E, 0x63, 0x6F,
+            0x64, 0x69, 0x6E, 0x67, 0x3A, 0x20, 0x62, 0x61,
+            0x73, 0x65, 0x36, 0x34, 0x0D, 0x0A, 0x43, 0x6F,
+            0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x44, 0x69,
+            0x73, 0x70, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F,
+            0x6E, 0x3A, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63,
+            0x68, 0x6D, 0x65, 0x6E, 0x74, 0x3B, 0x20, 0x66,
+            0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x3D,
+            0x22, 0x74, 0x65, 0x73, 0x74, 0x2E, 0x65, 0x78,
+            0x65, 0x22, 0x3B, 0x0D, 0x0A, 0x0D, 0x0A, 0x54,
+            0x56, 0x6F, 0x41, 0x41, 0x46, 0x42, 0x46, 0x41,
+            0x41, 0x42, 0x4D, 0x41, 0x51, 0x45, 0x41, 0x61,
+            0x69, 0x70, 0x59, 0x77, 0x77, 0x41, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42,
+            0x41, 0x41, 0x44, 0x41, 0x51, 0x73, 0x42, 0x43,
+            0x41, 0x41, 0x42, 0x41, 0x41, 0x43, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x48, 0x6B, 0x41, 0x41,
+            0x41, 0x41, 0x4D, 0x41, 0x41, 0x41, 0x41, 0x65,
+            0x51, 0x41, 0x41, 0x41, 0x41, 0x77, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x45, 0x41, 0x41, 0x42,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x51, 0x41, 0x41,
+            0x41, 0x42, 0x30, 0x41, 0x41, 0x41, 0x41, 0x49,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x51, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42,
+            0x41, 0x45, 0x41, 0x41, 0x49, 0x67, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+            0x67, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+            0x41, 0x42, 0x63, 0x58, 0x44, 0x59, 0x32, 0x4C,
+            0x6A, 0x6B, 0x7A, 0x4C, 0x6A, 0x59, 0x34, 0x4C,
+            0x6A, 0x5A, 0x63, 0x65, 0x67, 0x41, 0x41, 0x4F,
+            0x41, 0x3D, 0x3D, 0x0D,0x0A    };
+    uint32_t request4_msg_len = sizeof(request4_msg);
+
+    /* DATA COMPLETED */
+    uint8_t request4_end[] = {
+            0x0d, 0x0a, 0x2e, 0x0d, 0x0a
+    };
+    uint32_t request4_end_len = sizeof(request4_end);
+    /* 250 2.0.0 Ok: queued as 6A1AF20BF2<CR><LF> */
+    uint8_t reply4_end[] = {
+            0x32, 0x35, 0x30, 0x20, 0x32, 0x2e, 0x30, 0x2e,
+            0x30, 0x20, 0x4f, 0x6b, 0x3a, 0x20, 0x71, 0x75,
+            0x65, 0x75, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20,
+            0x36, 0x41, 0x31, 0x41, 0x46, 0x32, 0x30, 0x42,
+            0x46, 0x32, 0x0d, 0x0a
+    };
+    uint32_t reply4_end_len = sizeof(reply4_end);
+
+    /* QUIT<CR><LF> */
+    uint8_t request5[] = {
+            0x51, 0x55, 0x49, 0x54, 0x0d, 0x0a
+    };
+    uint32_t request5_len = sizeof(request5);
+    /* 221 2.0.0 Bye<CR><LF> */
+    uint8_t reply5[] = {
+            0x32, 0x32, 0x31, 0x20, 0x32, 0x2e, 0x30, 0x2e,
+            0x30, 0x20, 0x42, 0x79, 0x65, 0x0d, 0x0a
+    };
+    uint32_t reply5_len = sizeof(reply5);
+
+    TcpSession ssn;
+
+    memset(&f, 0, sizeof(f));
+    memset(&ssn, 0, sizeof(ssn));
+
+    FLOW_INITIALIZE(&f);
+    f.protoctx = (void *)&ssn;
+
+    StreamTcpInitConfig(TRUE);
+    void *thread_local_data = SMTPLocalStorageAlloc();
+
+    /* EHLO Request */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOSERVER,
+            request1, request1_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    SMTPState *smtp_state = f.alstate;
+    if (smtp_state == NULL) {
+        printf("no smtp state: ");
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 1 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->cmds[0] != SMTP_COMMAND_OTHER_CMD ||
+            smtp_state->parser_state != 0) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* Welcome reply */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOCLIENT,
+            welcome_reply, welcome_reply_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 1 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->cmds[0] != SMTP_COMMAND_OTHER_CMD ||
+            smtp_state->parser_state != SMTP_PARSER_STATE_FIRST_REPLY_SEEN) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* EHLO Reply */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOCLIENT,
+            reply1, reply1_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 0 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->parser_state != SMTP_PARSER_STATE_FIRST_REPLY_SEEN) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* MAIL FROM Request */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOSERVER,
+            request2, request2_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 1 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->cmds[0] != SMTP_COMMAND_OTHER_CMD ||
+            smtp_state->parser_state != SMTP_PARSER_STATE_FIRST_REPLY_SEEN) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* MAIL FROM Reply */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOCLIENT,
+            reply2, reply2_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 0 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->parser_state != (SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* RCPT TO Request */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOSERVER,
+            request3, request3_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 1 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->cmds[0] != SMTP_COMMAND_OTHER_CMD ||
+            smtp_state->parser_state != SMTP_PARSER_STATE_FIRST_REPLY_SEEN) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* RCPT TO Reply */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOCLIENT,
+            reply3, reply3_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 0 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->parser_state != (SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* Enable mime decoding */
+    smtp_config.decode_mime = 1;
+    smtp_config.mime_config.decode_base64 = 1;
+    smtp_config.mime_config.decode_quoted_printable = 1;
+    MimeDecSetConfig(&smtp_config.mime_config);
+
+    /* DATA request */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOSERVER,
+            request4, request4_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 1 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->cmds[0] != SMTP_COMMAND_DATA ||
+            smtp_state->parser_state != SMTP_PARSER_STATE_FIRST_REPLY_SEEN) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* Data reply */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOCLIENT,
+            reply4, reply4_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 0 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->parser_state != (SMTP_PARSER_STATE_FIRST_REPLY_SEEN |
+                    SMTP_PARSER_STATE_COMMAND_DATA_MODE)) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* DATA message */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOSERVER,
+            request4_msg, request4_msg_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 0 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->mime_state == NULL || smtp_state->msg_head == NULL || /* MIME data structures */
+            smtp_state->parser_state != (SMTP_PARSER_STATE_FIRST_REPLY_SEEN |
+                    SMTP_PARSER_STATE_COMMAND_DATA_MODE)) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* DATA . request */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOSERVER,
+            request4_end, request4_end_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 1 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->cmds[0] != SMTP_COMMAND_DATA_MODE ||
+            smtp_state->mime_state == NULL || smtp_state->msg_head == NULL || /* MIME data structures */
+            smtp_state->parser_state != (SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    SMTPState *state = (SMTPState *) f.alstate;
+    FileContainer *files = state->files_ts;
+    if (files != NULL && files->head != NULL) {
+        File *file = files->head;
+
+        if(strncmp((const char *)file->name, "test.exe", 8) != 0){
+            printf("smtp-mime file name is incorrect");
+            goto end;
+        }
+        if(file->size != filesize){
+            printf("smtp-mime file size %lu is incorrect", file->size);
+            goto end;
+        }
+        uint8_t org_binary[] = {0x4D, 0x5A, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00,
+                0x4C, 0x01, 0x01, 0x00, 0x6A, 0x2A, 0x58, 0xC3,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x04, 0x00, 0x03, 0x01, 0x0B, 0x01, 0x08, 0x00,
+                0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+                0x79, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+                0x79, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00,
+                0x04, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00,
+                0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00,
+                0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x5C, 0x5C, 0x36, 0x36,
+                0x2E, 0x39, 0x33, 0x2E, 0x36, 0x38, 0x2E, 0x36,
+                0x5C, 0x7A, 0x00, 0x00, 0x38,};
+        uint64_t z;
+        for (z=0; z <= filesize; z++){
+            if(org_binary[z] != file->chunks_head->data[z]){
+                printf("smtp-mime file data incorrect\n");
+                goto end;
+            }
+        }
+    }
+
+    /* DATA . reply */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOCLIENT,
+            reply4_end, reply4_end_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 0 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->parser_state != (SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* QUIT Request */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOSERVER,
+            request5, request5_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 1 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->cmds[0] != SMTP_COMMAND_OTHER_CMD ||
+            smtp_state->parser_state != SMTP_PARSER_STATE_FIRST_REPLY_SEEN) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    /* QUIT Reply */
+    r = AppLayerParse(thread_local_data, &f, ALPROTO_SMTP, STREAM_TOCLIENT,
+            reply5, reply5_len);
+    if (r != 0) {
+        printf("smtp check returned %" PRId32 ", expected 0: ", r);
+        goto end;
+    }
+    if (smtp_state->input_len != 0 ||
+            smtp_state->cmds_cnt != 0 ||
+            smtp_state->cmds_idx != 0 ||
+            smtp_state->parser_state != (SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) {
+        printf("smtp parser in inconsistent state\n");
+        goto end;
+    }
+
+    result = 1;
+    end:
+    StreamTcpFreeConfig(TRUE);
+    FLOW_DESTROY(&f);
+    SMTPLocalStorageFree(thread_local_data);
+    return result;
+}
+
+int SMTPProcessDataChunkTest01(void){
+    Flow f;
+    FLOW_INITIALIZE(&f);
+    f.flags = FLOW_FILE_NO_STORE_TS;
+    MimeDecParseState *state = MimeDecInitParser(&f, NULL);
+    int ret;
+    ret = ProcessDataChunk(NULL, 0, state);
+
+    return ret;
+}
+
+
+int SMTPProcessDataChunkTest02(void){
+    char mimemsg[] = {0x4D, 0x49, 0x4D, 0x45, 0x2D, 0x56, 0x65, 0x72,
+            0x73, 0x69, 0x6F, 0x6E, 0x3A, 0x20, 0x31, 0x2E,
+            0x30, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65,
+            0x6E, 0x74, 0x2D, 0x54, 0x79, 0x70, 0x65, 0x3A,
+            0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61,
+            0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x6F, 0x63, 0x74,
+            0x65, 0x74, 0x2D, 0x73, 0x74, 0x72, 0x65, 0x61,
+            0x6D, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65,
+            0x6E, 0x74, 0x2D, 0x54, 0x72, 0x61, 0x6E, 0x73,
+            0x66, 0x65, 0x72, 0x2D, 0x45, 0x6E, 0x63, 0x6F,
+            0x64, 0x69, 0x6E, 0x67, 0x3A, 0x20, 0x62, 0x61,
+            0x73, 0x65, 0x36, 0x34, 0x0D, 0x0A, 0x43, 0x6F,
+            0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2D, 0x44, 0x69,
+            0x73, 0x70, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F,
+            0x6E, 0x3A, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63,
+            0x68, 0x6D, 0x65, 0x6E, 0x74, 0x3B, 0x20, 0x66,
+            0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x3D,
+            0x22, 0x74, 0x65, 0x73, 0x74, 0x2E, 0x65, 0x78,
+            0x65, 0x22, 0x3B, 0x0D, 0x0A, 0x0D, 0x0A, 0x54,
+            0x56, 0x6F, 0x41, 0x41, 0x46, 0x42, 0x46, 0x41,
+            0x41, 0x42, 0x4D, 0x41, 0x51, 0x45, 0x41, 0x61,
+            0x69, 0x70, 0x59, 0x77, 0x77, 0x41, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42,
+            0x41, 0x41, 0x44, 0x41, 0x51, 0x73, 0x42, 0x43,
+            0x41, 0x41, 0x42, 0x41, 0x41, 0x43, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x48, 0x6B, 0x41, 0x41,
+            0x41, 0x41, 0x4D, 0x41, 0x41, 0x41, 0x41, 0x65,
+            0x51, 0x41, 0x41, 0x41, 0x41, 0x77, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x45, 0x41, 0x41, 0x42,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x51, 0x41, 0x41,
+            0x41, 0x42, 0x30, 0x41, 0x41, 0x41, 0x41, 0x49,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x51, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42,
+            0x41, 0x45, 0x41, 0x41, 0x49, 0x67, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+            0x67, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+            0x41, 0x42, 0x63, 0x58, 0x44, 0x59, 0x32, 0x4C,
+            0x6A, 0x6B, 0x7A, 0x4C, 0x6A, 0x59, 0x34, 0x4C,
+            0x6A, 0x5A, 0x63, 0x65, 0x67, 0x41, 0x41, 0x4F,
+            0x41, 0x3D, 0x3D, 0x0D, 0x0A,};
+
+    Flow f;
+    FLOW_INITIALIZE(&f);
+    f.alstate = SMTPStateAlloc();
+    MimeDecParseState *state = MimeDecInitParser(&f, NULL);
+    ((MimeDecEntity *)state->stack->top->data)->ctnt_flags = CTNT_IS_ATTACHMENT;
+    state->body_begin = 1;
+    int ret;
+    ret = ProcessDataChunk((uint8_t *)mimemsg, sizeof(mimemsg), state);
+
+
+    return ret;
+}
+
+
+
+int SMTPProcessDataChunkTest03(void){
+    char mimemsg[] = {0x4D, 0x49, 0x4D, 0x45, 0x2D, 0x56, 0x65, 0x72, };
+    char mimemsg2[] = {0x73, 0x69, 0x6F, 0x6E, 0x3A, 0x20, 0x31, 0x2E, };
+    char mimemsg3[] = {0x30, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65, };
+    char mimemsg4[] = {0x6E, 0x74, 0x2D, 0x54, 0x79, 0x70, 0x65, 0x3A, };
+    char mimemsg5[] = {0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, };
+    char mimemsg6[] = {0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x6F, 0x63, 0x74, };
+    char mimemsg7[] = {0x65, 0x74, 0x2D, 0x73, 0x74, 0x72, 0x65, 0x61, };
+    char mimemsg8[] = {0x6D, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65, };
+    char mimemsg9[] = {0x6E, 0x74, 0x2D, 0x54, 0x72, 0x61, 0x6E, 0x73, };
+    char mimemsg10[] = {0x66, 0x65, 0x72, 0x2D, 0x45, 0x6E, 0x63, 0x6F, };
+    char mimemsg11[] = {0x64, 0x69, 0x6E, 0x67, 0x3A, 0x20, 0x62, 0x61, };
+    char mimemsg12[] = {0x73, 0x65, 0x36, 0x34, 0x0D, 0x0A, 0x43, 0x6F, };
+
+    Flow f;
+    FLOW_INITIALIZE(&f);
+    f.alstate = SMTPStateAlloc();
+    MimeDecParseState *state = MimeDecInitParser(&f, NULL);
+    ((MimeDecEntity *)state->stack->top->data)->ctnt_flags = CTNT_IS_ATTACHMENT;
+    int ret;
+
+    state->body_begin = 1;
+    ret = ProcessDataChunk((uint8_t *)mimemsg, sizeof(mimemsg), state);
+    if(ret) goto end;
+    state->body_begin = 0;
+    ret = ProcessDataChunk((uint8_t *)mimemsg2, sizeof(mimemsg2), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg3, sizeof(mimemsg3), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg4, sizeof(mimemsg4), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg5, sizeof(mimemsg5), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg6, sizeof(mimemsg6), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg7, sizeof(mimemsg7), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg8, sizeof(mimemsg8), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg9, sizeof(mimemsg9), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg10, sizeof(mimemsg10), state);
+    if(ret) goto end;
+    ret = ProcessDataChunk((uint8_t *)mimemsg11, sizeof(mimemsg11), state);
+    if(ret) goto end;
+    state->body_end = 1;
+    ret = ProcessDataChunk((uint8_t *)mimemsg12, sizeof(mimemsg12), state);
+    if(ret) goto end;
+
+    end:
+    return ret;
+}
+
+
+int SMTPProcessDataChunkTest04(void){
+    char mimemsg[] = {0x4D, 0x49, 0x4D, 0x45, 0x2D, 0x56, 0x65, 0x72, };
+    char mimemsg2[] = {0x73, 0x69, 0x6F, 0x6E, 0x3A, 0x20, 0x31, 0x2E, };
+    char mimemsg3[] = {0x30, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65, };
+    char mimemsg4[] = {0x6E, 0x74, 0x2D, 0x54, 0x79, 0x70, 0x65, 0x3A, };
+    char mimemsg5[] = {0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, };
+    char mimemsg6[] = {0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x6F, 0x63, 0x74, };
+    char mimemsg7[] = {0x65, 0x74, 0x2D, 0x73, 0x74, 0x72, 0x65, 0x61, };
+    char mimemsg8[] = {0x6D, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65, };
+    char mimemsg9[] = {0x6E, 0x74, 0x2D, 0x54, 0x72, 0x61, 0x6E, 0x73, };
+    char mimemsg10[] = {0x66, 0x65, 0x72, 0x2D, 0x45, 0x6E, 0x63, 0x6F, };
+    char mimemsg11[] = {0x64, 0x69, 0x6E, 0x67, 0x3A, 0x20, 0x62, 0x61, };
+
+    Flow f;
+    FLOW_INITIALIZE(&f);
+    f.alstate = SMTPStateAlloc();
+    MimeDecParseState *state = MimeDecInitParser(&f, NULL);
+    ((MimeDecEntity *)state->stack->top->data)->ctnt_flags = CTNT_IS_ATTACHMENT;
+    int ret = MIME_DEC_OK;
+
+    state->body_begin = 1;
+    if(ProcessDataChunk((uint8_t *)mimemsg, sizeof(mimemsg), state) != 0) goto end;
+    if(ProcessDataChunk((uint8_t *)mimemsg2, sizeof(mimemsg2), state) != 0) goto end;
+    if(ProcessDataChunk((uint8_t *)mimemsg3, sizeof(mimemsg3), state) != 0) goto end;
+    if(ProcessDataChunk((uint8_t *)mimemsg4, sizeof(mimemsg4), state) != 0) goto end;
+    if(ProcessDataChunk((uint8_t *)mimemsg5, sizeof(mimemsg5), state) != 0) goto end;
+    if(ProcessDataChunk((uint8_t *)mimemsg6, sizeof(mimemsg6), state) != 0) goto end;
+    if(ProcessDataChunk((uint8_t *)mimemsg7, sizeof(mimemsg7), state) != 0) goto end;
+    state->body_begin = 0;
+    state->body_end = 1;
+    if(ProcessDataChunk((uint8_t *)mimemsg8, sizeof(mimemsg8), state) != 0) goto end;
+    state->body_end = 0;
+    if(ProcessDataChunk((uint8_t *)mimemsg9, sizeof(mimemsg9), state) != 0) goto end;
+    if(ProcessDataChunk((uint8_t *)mimemsg10, sizeof(mimemsg10), state) != 0) goto end;
+    if(ProcessDataChunk((uint8_t *)mimemsg11, sizeof(mimemsg11), state) != 0) goto end;
+
+    end:
+    return ret;
+}
+
+int SMTPProcessDataChunkTest05(void){
+    char mimemsg[] = {0x4D, 0x49, 0x4D, 0x45, 0x2D, 0x56, 0x65, 0x72,
+            0x73, 0x69, 0x6F, 0x6E, 0x3A, 0x20, 0x31, 0x2E,
+            0x30, 0x0D, 0x0A, 0x43, 0x6F, 0x6E, 0x74, 0x65,
+            0x6E, 0x74, 0x2D, 0x54, 0x79, 0x70, 0x65, 0x3A,
+            0x6A, 0x6B, 0x7A, 0x4C, 0x6A, 0x59, 0x34, 0x4C,
+            0x6A, 0x5A, 0x63, 0x65, 0x67, 0x41, 0x41, 0x4F,
+            0x41, 0x3D, 0x3D, 0x0D, 0x0A,};
+
+    Flow f;
+    FLOW_INITIALIZE(&f);
+    f.alstate = SMTPStateAlloc();
+    MimeDecParseState *state = MimeDecInitParser(&f, NULL);
+    ((MimeDecEntity *)state->stack->top->data)->ctnt_flags = CTNT_IS_ATTACHMENT;
+    state->body_begin = 1;
+    int ret;
+    uint64_t file_size = 0;
+    ret = ProcessDataChunk((uint8_t *)mimemsg, sizeof(mimemsg), state);
+    state->body_begin = 0;
+    if(ret){goto end;}
+    SMTPState *smtp_state = (SMTPState *)((Flow *)state->data)->alstate;
+    FileContainer *files = smtp_state->files_ts;
+    File *file = files->head;
+    file_size = file->size;
+
+    FileDisableStoring(&f, STREAM_TOSERVER);
+    FileDisableMagic(&f, STREAM_TOSERVER);
+    FileDisableMd5(&f, STREAM_TOSERVER);
+    ret = ProcessDataChunk((uint8_t *)mimemsg, sizeof(mimemsg), state);
+    if(ret){goto end;}
+    printf("%u\t%u\n", (uint32_t) file->size, (uint32_t) file_size);
+    if(file->size == file_size){
+        return 0;
+    }else{
+        return 1;
+    }
+
+    end:
+    return ret;
+}
+
 #endif /* UNITTESTS */
 
 void SMTPParserRegisterTests(void)
@@ -3632,6 +4580,12 @@ void SMTPParserRegisterTests(void)
     UtRegisterTest("SMTPParserTest11", SMTPParserTest11, 1);
     UtRegisterTest("SMTPParserTest12", SMTPParserTest12, 1);
     UtRegisterTest("SMTPParserTest13", SMTPParserTest13, 1);
+    UtRegisterTest("SMTPParserTest14", SMTPParserTest14, 1);
+    UtRegisterTest("SMTPProcessDataChunkTest01", SMTPProcessDataChunkTest01, 0);
+    UtRegisterTest("SMTPProcessDataChunkTest02", SMTPProcessDataChunkTest02, 0);
+    UtRegisterTest("SMTPProcessDataChunkTest03", SMTPProcessDataChunkTest03, 0);
+    UtRegisterTest("SMTPProcessDataChunkTest04", SMTPProcessDataChunkTest04, 0);
+    UtRegisterTest("SMTPProcessDataChunkTest05", SMTPProcessDataChunkTest05, 0);
 #endif /* UNITTESTS */
 
     return;
index 0a29a35b3705f9a5b46c8ff798a3a6da2214fd5b..94d8eb840484611b46918fcd54f62b954db5e135 100644 (file)
@@ -25,6 +25,7 @@
 #define __APP_LAYER_SMTP_H__
 
 #include "decode-events.h"
+#include "mime-decode.h"
 
 enum {
     SMTP_DECODER_EVENT_INVALID_REPLY,
@@ -36,6 +37,16 @@ enum {
     SMTP_DECODER_EVENT_NO_SERVER_WELCOME_MESSAGE,
     SMTP_DECODER_EVENT_TLS_REJECTED,
     SMTP_DECODER_EVENT_DATA_COMMAND_REJECTED,
+
+    /* MIME Events */
+    SMTP_DECODER_EVENT_MIME_PARSE_FAILED,
+    SMTP_DECODER_EVENT_MIME_MALFORMED_MSG,
+    SMTP_DECODER_EVENT_MIME_INVALID_BASE64,
+    SMTP_DECODER_EVENT_MIME_INVALID_QP,
+    SMTP_DECODER_EVENT_MIME_LONG_LINE,
+    SMTP_DECODER_EVENT_MIME_LONG_ENC_LINE,
+    SMTP_DECODER_EVENT_MIME_LONG_HEADER_NAME,
+    SMTP_DECODER_EVENT_MIME_LONG_HEADER_VALUE,
 };
 
 typedef struct SMTPState_ {
@@ -89,6 +100,16 @@ typedef struct SMTPState_ {
      *  handler */
     uint16_t cmds_idx;
 
+    /* SMTP Mime decoding and file extraction */
+    /** the list of files sent to the server */
+    FileContainer *files_ts;
+    /** the first message contained in the session */
+    MimeDecEntity *msg_head;
+    /** the last message contained in the session */
+    MimeDecEntity *msg_tail;
+    /** the mime decoding parser state */
+    MimeDecParseState *mime_state;
+
 } SMTPState;
 
 void RegisterSMTPParsers(void);
index ca502c9b758c237c524a8f7b59a34de494d3e0f9..655adffcaad864ce644358cb48800d6bacf93547 100644 (file)
@@ -51,6 +51,7 @@
 #include "app-layer-smb.h"
 #include "app-layer-dcerpc-common.h"
 #include "app-layer-dcerpc.h"
+#include "app-layer-smtp.h"
 
 #include "util-unittest.h"
 #include "util-unittest-helper.h"
@@ -240,3 +241,48 @@ int DetectFileInspectHttp(ThreadVars *tv,
 
     return r;
 }
+
+/**
+ *  \brief Inspect the file inspecting keywords against the SMTP transactions.
+ *
+ *  \param tv thread vars
+ *  \param det_ctx detection engine thread ctx
+ *  \param f flow
+ *  \param s signature to inspect
+ *  \param alstate state
+ *  \param flags direction flag
+ *
+ *  \retval 0 no match
+ *  \retval 1 match
+ *  \retval 2 can't match
+ *  \retval 3 can't match filestore signature
+ *
+ *  \note flow is not locked at this time
+ */
+int DetectFileInspectSmtp(ThreadVars *tv,
+                          DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+                          Signature *s, Flow *f, uint8_t flags, void *alstate,
+                          void *tx, uint64_t tx_id)
+{
+    SCEnter();
+
+    int r = 0;
+    SMTPState *smtp_state = NULL;
+    FileContainer *ffc;
+
+    smtp_state = (SMTPState *)alstate;
+    if (smtp_state == NULL) {
+        SCLogDebug("no SMTP state");
+        goto end;
+    }
+
+    if (flags & STREAM_TOSERVER)
+        ffc = smtp_state->files_ts;
+    else
+        goto end;
+
+    r = DetectFileInspect(tv, det_ctx, f, s, flags, ffc);
+
+end:
+    SCReturnInt(r);
+}
index 91e2f21ebf1d88381ff380028e6622d52cc9f7e0..365ed8a99fb7bddc8625227b46f0a43db2c36420 100644 (file)
@@ -28,4 +28,10 @@ int DetectFileInspectHttp(ThreadVars *tv,
                           DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
                           Signature *s, Flow *f, uint8_t flags, void *alstate,
                           void *tx, uint64_t tx_id);
+
+int DetectFileInspectSmtp(ThreadVars *tv, DetectEngineCtx *de_ctx,
+                          DetectEngineThreadCtx *det_ctx, Signature *s,
+                          Flow *f, uint8_t flags, void *alstate,
+                          void *tx, uint64_t tx_id);
+
 #endif /* __DETECT_ENGINE_FILE_H__ */
index 98b8bcb90f0591cedb9478624ed6a1c20534f559..7b5e9103d4f38cded0be93a7bf3011354a922e14 100644 (file)
@@ -211,14 +211,15 @@ static int DetectFileextSetup (DetectEngineCtx *de_ctx, Signature *s, char *str)
 
     SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_FILEMATCH);
 
-
-    if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP) {
+    if (s->alproto != ALPROTO_HTTP && s->alproto != ALPROTO_SMTP) {
         SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords.");
         goto error;
     }
 
-    AppLayerHtpNeedFileInspection();
-    s->alproto = ALPROTO_HTTP;
+    if (s->alproto == ALPROTO_HTTP) {
+        AppLayerHtpNeedFileInspection();
+    }
+
     s->file_flags |= (FILE_SIG_NEED_FILE|FILE_SIG_NEED_FILENAME);
     return 0;
 
index 9edecd2cfc9c8d1d73fe2b898520dda4b4242ae8..5dd28ec8195b27ae3df0a87b2bc2066a75a0b12f 100644 (file)
@@ -392,15 +392,14 @@ static int DetectFilemagicSetup (DetectEngineCtx *de_ctx, Signature *s, char *st
 
     SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_FILEMATCH);
 
-    if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP) {
+    if (s->alproto != ALPROTO_HTTP && s->alproto != ALPROTO_SMTP) {
         SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords.");
         goto error;
     }
 
-    AppLayerHtpNeedFileInspection();
-
-    /** \todo remove this once we support more than http */
-    s->alproto = ALPROTO_HTTP;
+    if (s->alproto == ALPROTO_HTTP) {
+        AppLayerHtpNeedFileInspection();
+    }
 
     s->file_flags |= (FILE_SIG_NEED_FILE|FILE_SIG_NEED_MAGIC);
     return 0;
index a81ba25d6d5bfe5fb82568d3d162451acc5c7448..1bb0cf3ac88b420f2fcffbb19ce17d1d4dd39efb 100644 (file)
@@ -324,15 +324,14 @@ static int DetectFileMd5Setup (DetectEngineCtx *de_ctx, Signature *s, char *str)
 
     SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_FILEMATCH);
 
-    if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP) {
+    if (s->alproto != ALPROTO_HTTP && s->alproto != ALPROTO_SMTP) {
         SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords.");
         goto error;
     }
 
-    AppLayerHtpNeedFileInspection();
-
-    /** \todo remove this once we support more than http */
-    s->alproto = ALPROTO_HTTP;
+    if (s->alproto == ALPROTO_HTTP) {
+        AppLayerHtpNeedFileInspection();
+    }
 
     s->file_flags |= (FILE_SIG_NEED_FILE|FILE_SIG_NEED_MD5);
     return 0;
index c8cea7f3590fab78d64ba1fea5d69a5ea108a2a2..06ccc70bd0d62371cc69fbc6ef862d39890c20dc 100644 (file)
@@ -215,14 +215,14 @@ static int DetectFilenameSetup (DetectEngineCtx *de_ctx, Signature *s, char *str
 
     SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_FILEMATCH);
 
-    if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP) {
+    if (s->alproto != ALPROTO_HTTP && s->alproto != ALPROTO_SMTP) {
         SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords.");
         goto error;
     }
 
-    AppLayerHtpNeedFileInspection();
-
-    s->alproto = ALPROTO_HTTP;
+    if (s->alproto == ALPROTO_HTTP) {
+        AppLayerHtpNeedFileInspection();
+    }
 
     s->file_flags |= (FILE_SIG_NEED_FILE|FILE_SIG_NEED_FILENAME);
     return 0;
index d8c49bcf78887e789dd4b1a0d6b05e88848d841d..e826e3c9b5f4042f7df13ad30cf3bd47e47c9c64 100644 (file)
@@ -310,15 +310,14 @@ static int DetectFilesizeSetup (DetectEngineCtx *de_ctx, Signature *s, char *str
 
     SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_FILEMATCH);
 
-    if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP) {
+    if (s->alproto != ALPROTO_HTTP && s->alproto != ALPROTO_SMTP) {
         SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords.");
         goto error;
     }
 
-    AppLayerHtpNeedFileInspection();
-
-    /** \todo remove this once we support more than http */
-    s->alproto = ALPROTO_HTTP;
+    if (s->alproto == ALPROTO_HTTP) {
+        AppLayerHtpNeedFileInspection();
+    }
 
     s->file_flags |= (FILE_SIG_NEED_FILE|FILE_SIG_NEED_SIZE);
     SCReturnInt(0);
index f9bfdf55c25b01d923c31b6a38c6307efc1e2fcd..e88c95eeb6887eaeb7df4d37a3b114b7a2405026 100644 (file)
@@ -414,14 +414,14 @@ static int DetectFilestoreSetup (DetectEngineCtx *de_ctx, Signature *s, char *st
     SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_FILEMATCH);
     s->filestore_sm = sm;
 
-    if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP) {
+    if (s->alproto != ALPROTO_HTTP && s->alproto != ALPROTO_SMTP) {
         SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords.");
         goto error;
     }
 
-    AppLayerHtpNeedFileInspection();
-
-    s->alproto = ALPROTO_HTTP;
+    if (s->alproto == ALPROTO_HTTP) {
+        AppLayerHtpNeedFileInspection();
+    }
 
     s->flags |= SIG_FLAG_FILESTORE;
     return 0;
index 8cb7a662bb2c803c604bbdbdf7c235c10825b860..92b066df544aecbda6d76e60eeef6b78d7b8de0f 100644 (file)
@@ -55,6 +55,8 @@
 #include "util-logopenfile.h"
 
 #include "app-layer-htp.h"
+#include "app-layer-smtp.h"
+#include "mime-decode.h"
 #include "util-memcmp.h"
 #include "stream-tcp-reassemble.h"
 
@@ -144,6 +146,31 @@ static void LogFileMetaGetUserAgent(FILE *fp, const Packet *p, const File *ff)
     fprintf(fp, "<unknown>");
 }
 
+static void LogFileMetaGetSmtp(FILE *fp, const Packet *p, const File *ff) {
+
+    SMTPState *state = (SMTPState *) p->flow->alstate;
+    if (state != NULL && state->msg_tail != NULL) {
+
+        /* Message Id */
+        if (state->msg_tail->msg_id != NULL) {
+
+            fprintf(fp, "\"message-id\": \"");
+            PrintRawJsonFp(fp, (uint8_t *) state->msg_tail->msg_id,
+                    (int) state->msg_tail->msg_id_len);
+            fprintf(fp, "\", ");
+        }
+
+        /* Sender */
+        MimeDecField *field = MimeDecFindField(state->msg_tail, "From");
+        if (field != NULL) {
+            fprintf(fp, "\"sender\": \"");
+            PrintRawJsonFp(fp, (uint8_t *) field->value,
+                    (int) field->value_len);
+            fprintf(fp, "\", ");
+        }
+    }
+}
+
 /**
  *  \internal
  *  \brief Write meta data on a single line json record
@@ -231,6 +258,9 @@ static void LogFileWriteJsonRecord(LogFileLogThread *aft, const Packet *p, const
         fprintf(fp, "\"http_user_agent\": \"");
         LogFileMetaGetUserAgent(fp, p, ff);
         fprintf(fp, "\", ");
+    } else if (p->flow->alproto == ALPROTO_SMTP) {
+        /* Only applicable to SMTP */
+        LogFileMetaGetSmtp(fp, p, ff);
     }
 
     fprintf(fp, "\"filename\": \"");
index 989b51dee512fab4a0f4686ff2360dc32bbe4c73..771cc8105dbd0fb7b7cade57c2d5e7c8b4c89e3b 100644 (file)
@@ -53,6 +53,8 @@
 #include "util-logopenfile.h"
 
 #include "app-layer-htp.h"
+#include "app-layer-smtp.h"
+#include "mime-decode.h"
 #include "util-memcmp.h"
 #include "stream-tcp-reassemble.h"
 
@@ -139,8 +141,29 @@ static void LogFilestoreMetaGetUserAgent(FILE *fp, const Packet *p, const File *
     fprintf(fp, "<unknown>");
 }
 
-static void LogFilestoreLogCreateMetaFile(const Packet *p, const File *ff, const char *filename, int ipver)
-{
+static void LogFilestoreMetaGetSmtp(FILE *fp, Packet *p, File *ff) {
+
+    SMTPState *state = (SMTPState *) p->flow->alstate;
+    if (state != NULL && state->msg_tail != NULL) {
+
+        /* Message Id */
+        if (state->msg_tail->msg_id != NULL) {
+            fprintf(fp, "MESSAGE-ID:        ");
+            PrintRawUriFp(fp, (uint8_t *) state->msg_tail->msg_id, state->msg_tail->msg_id_len);
+            fprintf(fp, "\n");
+        }
+
+        /* Sender */
+        MimeDecField *field = MimeDecFindField(state->msg_tail, "From");
+        if (field != NULL) {
+            fprintf(fp, "SENDER:            ");
+            PrintRawUriFp(fp, (uint8_t *) field->value, field->value_len);
+            fprintf(fp, "\n");
+        }
+    }
+}
+
+static void LogFilestoreLogCreateMetaFile(const Packet *p, const File *ff, char *filename, int ipver) {
     char metafilename[PATH_MAX] = "";
     snprintf(metafilename, sizeof(metafilename), "%s.meta", filename);
     FILE *fp = fopen(metafilename, "w+");
@@ -180,18 +203,26 @@ static void LogFilestoreLogCreateMetaFile(const Packet *p, const File *ff, const
             fprintf(fp, "SRC PORT:          %" PRIu16 "\n", sp);
             fprintf(fp, "DST PORT:          %" PRIu16 "\n", dp);
         }
-        fprintf(fp, "HTTP URI:          ");
-        LogFilestoreMetaGetUri(fp, p, ff);
-        fprintf(fp, "\n");
-        fprintf(fp, "HTTP HOST:         ");
-        LogFilestoreMetaGetHost(fp, p, ff);
-        fprintf(fp, "\n");
-        fprintf(fp, "HTTP REFERER:      ");
-        LogFilestoreMetaGetReferer(fp, p, ff);
-        fprintf(fp, "\n");
-        fprintf(fp, "HTTP USER AGENT:   ");
-        LogFilestoreMetaGetUserAgent(fp, p, ff);
-        fprintf(fp, "\n");
+
+        /* Only applicable to HTTP traffic */
+        if (ff->txid != 0) {
+            fprintf(fp, "HTTP URI:          ");
+            LogFilestoreMetaGetUri(fp, p, ff);
+            fprintf(fp, "\n");
+            fprintf(fp, "HTTP HOST:         ");
+            LogFilestoreMetaGetHost(fp, p, ff);
+            fprintf(fp, "\n");
+            fprintf(fp, "HTTP REFERER:      ");
+            LogFilestoreMetaGetReferer(fp, p, ff);
+            fprintf(fp, "\n");
+            fprintf(fp, "HTTP USER AGENT:   ");
+            LogFilestoreMetaGetUserAgent(fp, p, ff);
+            fprintf(fp, "\n");
+        } else if (p->flow->alproto == ALPROTO_SMTP) {
+            /* Only applicable to SMTP */
+            LogFilestoreMetaGetSmtp(fp, p, ff);
+        }
+
         fprintf(fp, "FILENAME:          ");
         PrintRawUriFp(fp, ff->name, ff->name_len);
         fprintf(fp, "\n");
diff --git a/src/mime-decode.c b/src/mime-decode.c
new file mode 100644 (file)
index 0000000..fdca4f0
--- /dev/null
@@ -0,0 +1,2841 @@
+/* Copyright (C) 2012 BAE Systems
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * 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
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author David Abarbanel <david.abarbanel@baesystems.com>
+ *
+ */
+
+#include <string.h>
+#include <arpa/inet.h>
+#include "mime-decode.h"
+
+#include "util-spm-bs.h"
+#include "util-unittest.h"
+
+/* Character constants */
+#ifndef CR
+#define CR  13
+#define LF  10
+#endif
+
+#define CRLF             "\r\n"
+#define COLON             58
+#define DASH              45
+#define PRINTABLE_START   33
+#define PRINTABLE_END    126
+#define UC_START          65
+#define UC_END            90
+#define LC_START          97
+#define LC_END           122
+#define UC_LC_DIFF        32
+#define EOL_LEN            2
+
+/* Base-64 constants */
+#define BASE64_STR        "Base64"
+
+/* Mime Constants */
+#define MAX_LINE_LEN       998 /* Def in RFC 2045, excluding CRLF sequence */
+#define MAX_ENC_LINE_LEN    76 /* Def in RFC 2045, excluding CRLF sequence */
+#define MAX_HEADER_NAME     75 /* 75 + ":" = 76 */
+#define MAX_HEADER_VALUE  2000 /* Default - arbitrary limit */
+#define BOUNDARY_BUF       256
+#define CTNT_TYPE_STR     "Content-Type"
+#define CTNT_DISP_STR     "Content-Disposition"
+#define CTNT_TRAN_STR     "Content-Transfer-Encoding"
+#define MSG_ID_STR        "Message-ID"
+#define BND_START_STR     "boundary=\""
+#define TOK_END_STR       "\""
+#define MSG_STR           "message/"
+#define MULTIPART_STR     "multipart/"
+#define QP_STR            "quoted-printable"
+#define TXT_STR           "text/plain"
+#define HTML_STR          "text/html"
+#define URL_STR           "http://"
+
+/* Memory Usage Constants */
+#define STACK_FREE_NODES  10
+
+/* Other Constants */
+#define MAX_IP4_CHARS  15
+#define MAX_IP6_CHARS  39
+
+/* Globally hold configuration data */
+static MimeDecConfig mime_dec_config = { 1, 1, 1, MAX_HEADER_VALUE };
+
+/* Mime Parser String translation */
+static const char *StateFlags[] = { "NONE",
+        "HEADER_READY",
+        "HEADER_STARTED",
+        "HEADER_DONE",
+        "BODY_STARTED",
+        "BODY_DONE",
+        "BODY_END_BOUND",
+        "PARSE_DONE",
+        "PARSE_ERROR",
+        NULL };
+
+/* URL executable file extensions */
+static const char *UrlExeExts[] = { ".exe",
+        ".vbs",
+        ".bin",
+        ".cmd",
+        ".bat",
+        ".jar",
+        ".js",
+        NULL };
+
+/**
+ * \brief Function used to print character strings that are not null-terminated
+ *
+ * \param log_level The logging level in which to print
+ * \param label A label for the string to print
+ * \param src The source string
+ * \param len The length of the string
+ *
+ * \return none
+ */
+void PrintChars(int log_level, char *label, char *src, uint32_t len) {
+
+    if (log_level <= sc_log_global_log_level) {
+
+        static uint32_t max_len = 100;
+        char tempbuf[max_len];
+
+        if (len == 0) {
+            SCLog(log_level, "PrintChars() - length is 0\n");
+        } else {
+            uint32_t llen = strlen(label);
+
+            /* Add label and source */
+            int num = snprintf(tempbuf, llen +4 < max_len ? llen + 4 : max_len, "[%s] ", label);
+            strncpy(tempbuf + num, src, len + num < max_len ? len + num : max_len - num - 1);
+
+            SCLog(log_level, "%s", tempbuf);
+        }
+    }
+}
+
+/**
+ * \brief Set global config policy
+ *
+ * \param config Config policy to set
+ * \return none
+ */
+void MimeDecSetConfig(MimeDecConfig *config) {
+
+    if (config != NULL) {
+        mime_dec_config = *config;
+
+        /* Set to default */
+        if (mime_dec_config.header_value_depth == 0) {
+            mime_dec_config.header_value_depth = MAX_HEADER_VALUE;
+        }
+    } else {
+        SCLogWarning(SC_ERR_MISSING_CONFIG_PARAM, "Invalid null configuration parameters");
+    }
+}
+
+/**
+ * \brief Get global config policy
+ *
+ * \return config data structure
+ */
+MimeDecConfig * MimeDecGetConfig(void) {
+    return &mime_dec_config;
+}
+
+/**
+ * \brief Recursively frees a mime entity tree
+ *
+ * \param entity The root entity
+ *
+ * \return none
+ *
+ */
+void MimeDecFreeEntity (MimeDecEntity *entity) {
+
+    if (entity != NULL) {
+
+        MimeDecFreeField(entity->field_list);
+        MimeDecFreeUrl(entity->url_list);
+        SCFree(entity->filename);
+
+        /* Use recursion */
+        MimeDecFreeEntity(entity->child);
+        MimeDecFreeEntity(entity->next);
+
+        SCFree(entity);
+    }
+}
+
+/**
+ * \brief Iteratively frees a header field entry list
+ *
+ * \param field The header field
+ *
+ * \return none
+ *
+ */
+void MimeDecFreeField(MimeDecField *field) {
+
+    MimeDecField *temp, *curr;
+
+    if (field != NULL) {
+
+        curr = field;
+        while (curr != NULL) {
+            temp = curr;
+            curr = curr->next;
+
+            /* Free contents of node */
+            SCFree(temp->name);
+            SCFree(temp->value);
+
+            /* Now free node data */
+            SCFree(temp);
+        }
+    }
+}
+
+/**
+ * \brief Iteratively frees a URL entry list
+ *
+ * \param url The url entry
+ *
+ * \return none
+ *
+ */
+void MimeDecFreeUrl(MimeDecUrl *url) {
+
+    MimeDecUrl *temp, *curr;
+
+    if (url != NULL) {
+
+        curr = url;
+        while (curr != NULL) {
+            temp = curr;
+            curr = curr->next;
+
+            /* Now free node data */
+            SCFree(temp->url);
+            SCFree(temp);
+        }
+    }
+}
+
+/**
+ * \brief Creates and adds a header field entry to an entity
+ *
+ * The entity is optional.  If NULL is specified, than a new stand-alone field
+ * is created.
+ *
+ * \param entity The parent entity
+ *
+ * \return The field object, or NULL if the operation fails
+ *
+ */
+MimeDecField * MimeDecAddField(MimeDecEntity *entity) {
+
+    MimeDecField *node = SCMalloc(sizeof(MimeDecField));
+    if (unlikely(node == NULL)) {
+        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+        return NULL;
+    }
+    memset(node, 0x00, sizeof(MimeDecField));
+
+    if (entity != NULL) {
+
+        /* If list is empty, then set as head of list */
+        if (entity->field_list == NULL) {
+            entity->field_list = node;
+        } else {
+            /* Otherwise add to beginning of list since these are out-of-order in
+             * the message */
+            node->next = entity->field_list;
+            entity->field_list = node;
+        }
+    }
+
+    return node;
+}
+
+/**
+ * \brief Searches for a header field with the specified name (case insensitive)
+ *
+ * \param entity The entity to search
+ * \param name The header name (case insensitive)
+ *
+ * \return The field object, or NULL if not found
+ *
+ */
+MimeDecField * MimeDecFindField(const MimeDecEntity *entity, const char *name) {
+    MimeDecField *curr = entity->field_list;
+
+    while (curr != NULL) {
+
+        if (strncasecmp(curr->name, name, curr->name_len) == 0) {
+            break;
+        }
+        curr = curr->next;
+    }
+
+    return curr;
+}
+
+/**
+ * \brief Creates and adds a URL entry to the specified entity
+ *
+ * The entity is optional and if NULL is specified, then a new list will be created.
+ *
+ * \param entity The entity
+ *
+ * \return URL entry or NULL if the operation fails
+ *
+ */
+MimeDecUrl * MimeDecAddUrl(MimeDecEntity *entity) {
+
+    MimeDecUrl *node = SCMalloc(sizeof(MimeDecUrl));
+    if (unlikely(node == NULL)) {
+        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+        return NULL;
+    }
+    memset(node, 0x00, sizeof(MimeDecUrl));
+
+    if (entity != NULL) {
+
+        /* If list is empty, then set as head of list */
+        if (entity->url_list == NULL) {
+            entity->url_list = node;
+        } else {
+            /* Otherwise add to beginning of list since these are out-of-order in
+             * the message */
+            node->next = entity->url_list;
+            entity->url_list = node;
+        }
+    }
+
+    return node;
+}
+
+/**
+ * \brief Creates and adds a child entity to the specified parent entity
+ *
+ * \param parent The parent entity
+ *
+ * \return The child entity, or NULL if the operation fails
+ *
+ */
+MimeDecEntity * MimeDecAddEntity(MimeDecEntity *parent) {
+
+    MimeDecEntity *curr, *node = SCMalloc(sizeof(MimeDecEntity));
+    if (unlikely(node == NULL)) {
+        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+        return NULL;
+    }
+    memset(node, 0x00, sizeof(MimeDecEntity));
+
+    /* If parent is NULL then just return the new pointer */
+    if (parent != NULL) {
+        if (parent->child == NULL) {
+            parent->child = node;
+        } else {
+            curr = parent->child;
+            while (curr->next != NULL) {
+                curr = curr->next;
+            }
+            curr->next = node;
+        }
+    }
+
+    return node;
+}
+
+/**
+ * \brief Creates a mime header field and fills in its values and adds it to the
+ * specified entity
+ *
+ * \param entity Entity in which to add the field
+ * \param name String containing the name
+ * \param nlen Length of the name
+ * \param value String containing the value
+ * \param vlen Length of the value
+ * \param copy_name_value Flag indicating whether to make copies of the name and
+ * value or just point to them
+ *
+ * \return The field or NULL if the operation fails
+ */
+MimeDecField * MimeDecFillField(MimeDecEntity *entity, const char *name,
+        uint32_t nlen, const char *value, uint32_t vlen, int copy_name_value) {
+
+    MimeDecField *field = MimeDecAddField(entity);
+    if (unlikely(field == NULL)) {
+        return NULL;
+    }
+
+    if (nlen > 0) {
+        if (copy_name_value) {
+            field->name = SCMalloc(nlen);
+            if (unlikely(field->name == NULL)) {
+                SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+                SCFree(field);
+                return NULL;
+            }
+            strncpy(field->name, name, nlen);
+        } else {
+            field->name = (char *) name;
+        }
+        field->name_len = nlen;
+    }
+
+    if (vlen > 0) {
+        if (copy_name_value) {
+            field->value = SCMalloc(vlen);
+            if (unlikely(field->value == NULL)) {
+                SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+                SCFree(field);
+                return NULL;
+            }
+            strncpy(field->value, value, vlen);
+        } else {
+            field->value = (char *) value;
+        }
+        field->value_len = vlen;
+    }
+
+    return field;
+}
+
+/**
+ * \brief Pushes a node onto a stack and returns the new node.
+ *
+ * \param stack The top of the stack
+ *
+ * \return pointer to a new node, otherwise NULL if it fails
+ */
+static MimeDecStackNode * PushStack(MimeDecStack *stack) {
+
+    /* Attempt to pull from free nodes list */
+    MimeDecStackNode *node = stack->free_nodes;
+    if (node == NULL) {
+        node = SCMalloc(sizeof(MimeDecStackNode));
+        if (unlikely(node == NULL)) {
+            SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+            return NULL;
+        }
+    } else {
+        /* Move free nodes pointer over */
+        stack->free_nodes = stack->free_nodes->next;
+        stack->free_nodes_cnt--;
+    }
+    memset(node, 0x00, sizeof(MimeDecStackNode));
+
+    /* Push to top of stack */
+    node->next = stack->top;
+    stack->top = node;
+
+    /* Return a pointer to the top of the stack */
+    return node;
+}
+
+/**
+ * \brief Pops the top node from the stack and returns the next node.
+ *
+ * \param stack The top of the stack
+ *
+ * \return pointer to the next node, otherwise NULL if no nodes remain
+ */
+static MimeDecStackNode * PopStack(MimeDecStack *stack) {
+
+    /* Move stack pointer to next item */
+    MimeDecStackNode *curr = stack->top;
+    if (curr != NULL) {
+        curr = curr->next;
+    }
+
+    /* Always free alloc'd memory */
+    SCFree(stack->top->bdef);
+
+    /* Now move head to free nodes list */
+    if (stack->free_nodes_cnt < STACK_FREE_NODES) {
+        stack->top->next = stack->free_nodes;
+        stack->free_nodes = stack->top;
+        stack->free_nodes_cnt++;
+    } else {
+        SCFree(stack->top);
+    }
+    stack->top = curr;
+
+    /* Return a pointer to the top of the stack */
+    return curr;
+}
+
+/**
+ * \brief Frees the stack along with the free-nodes list
+ *
+ * \param stack The stack pointer
+ *
+ * \return none
+ */
+static void FreeMimeDecStack(MimeDecStack *stack) {
+
+    MimeDecStackNode *temp, *curr;
+
+    if (stack != NULL) {
+
+        /* Top of stack */
+        curr = stack->top;
+        while (curr != NULL) {
+            temp = curr;
+            curr = curr->next;
+
+            /* Now free node */
+            SCFree(temp->bdef);
+            SCFree(temp);
+        }
+
+        /* Free nodes */
+        curr = stack->free_nodes;
+        while (curr != NULL) {
+            temp = curr;
+            curr = curr->next;
+
+            /* Now free node */
+            SCFree(temp);
+        }
+
+        SCFree(stack);
+    }
+}
+
+/**
+ * \brief Adds a data value to the data values linked list
+ *
+ * \param dv The head of the linked list (NULL if new list)
+ *
+ * \return pointer to a new node, otherwise NULL if it fails
+ */
+static DataValue * AddDataValue(DataValue *dv) {
+
+    DataValue *curr, *node = SCMalloc(sizeof(DataValue));
+    if (unlikely(node == NULL)) {
+        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+        return NULL;
+    }
+    memset(node, 0x00, sizeof(DataValue));
+
+    if (dv != NULL) {
+        curr = dv;
+        while (curr->next != NULL) {
+            curr = curr->next;
+        }
+
+        curr->next = node;
+    }
+
+    return node;
+}
+
+/**
+ * \brief Frees a linked list of data values starting at the head
+ *
+ * \param dv The head of the linked list
+ *
+ * \return none
+ */
+static void FreeDataValue(DataValue *dv) {
+    DataValue *temp, *curr;
+
+    if (dv != NULL) {
+        curr = dv;
+        while (curr != NULL) {
+            temp = curr;
+            curr = curr->next;
+
+            /* Now free node */
+            SCFree(temp->value);
+            SCFree(temp);
+        }
+    }
+}
+
+/**
+ * \brief Converts a list of data values into a single value (returns dynamically
+ * allocated memory)
+ *
+ * \param dv The head of the linked list (NULL if new list)
+ * \param len The output length of the single value
+ *
+ * \return pointer to a single value, otherwise NULL if it fails or is zero-length
+ */
+static char * GetFullValue(DataValue *dv, uint32_t *len) {
+
+    DataValue *curr;
+    uint32_t offset = 0;
+    char *val = NULL;
+
+    /* First calculate total length */
+    *len = 0;
+    curr = dv;
+    while (curr != NULL) {
+        *len += curr->value_len;
+
+        /* Add CRLF except on last one */
+        if (curr->next != NULL) {
+            *len += 2;
+        }
+        curr = curr->next;
+    }
+
+    /* Must have at least one character in the value */
+    if (*len > 0) {
+        val = SCMalloc(*len);
+        if (unlikely(val == NULL)) {
+            SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+            *len = 0;
+            return NULL;
+        }
+
+        curr = dv;
+        while (curr != NULL) {
+            strncpy(val + offset, (char *) curr->value, curr->value_len);
+            offset += curr->value_len;
+
+            /* Add CRLF except on last one */
+            if (curr->next != NULL) {
+                strncpy(val + offset, CRLF, 2);
+                offset += 2;
+            }
+            curr = curr->next;
+        }
+    }
+
+    return val;
+}
+
+/**
+ * \brief Find a string while searching up to N characters within a source
+ * string
+ *
+ * \param src The source string (not null-terminated)
+ * \param len The length of the source string
+ * \param find The string to find (null-terminated)
+ *
+ * \return Pointer to the position it was found, otherwise NULL if not found
+ */
+static inline char * FindString(const char *src, uint32_t len, const char *find) {
+
+    /* Use utility search function */
+    return (char *) BasicSearchNocase((uint8_t *) src, len, (uint8_t *) find, strlen(find));
+}
+
+/**
+ * \brief Get a line (CRLF or just CR or LF) from a buffer (similar to GetToken)
+ *
+ * \param buf The input buffer (not null-terminated)
+ * \param blen The length of the input buffer
+ * \param remainPtr Pointer to remaining after tokenizing iteration
+ * \param tokLen Output token length (if non-null line)
+ *
+ * \return Pointer to line
+ */
+static char * GetLine(char *buf, uint32_t blen, char **remainPtr,
+        uint32_t *tokLen) {
+
+    uint32_t i;
+    char *tok;
+
+    /* So that it can be used just like strtok_r */
+    if (buf == NULL) {
+        buf = *remainPtr;
+    } else {
+        *remainPtr = buf;
+    }
+    tok = buf;
+
+    /* length must be specified */
+    for (i = 0; i < blen && buf[i] != 0; i++) {
+
+        /* Found delimiter */
+        if (buf[i] == CR || buf[i] == LF) {
+
+            /* Add another if we find either CRLF or LFCR */
+            *remainPtr += (i + 1);
+            if ((i + 1 < blen) && buf[i] != buf[i + 1] &&
+                    (buf[i + 1] == CR || buf[i + 1] == LF)) {
+                (*remainPtr)++;
+            }
+            break;
+        }
+    }
+
+    /* If no delimiter found, then point to end of buffer */
+    if (buf == *remainPtr) {
+        (*remainPtr) += i;
+    }
+
+    /* Calculate token length */
+    *tokLen = (buf + i) - tok;
+
+    return tok;
+}
+
+/**
+ * \brief Get token from buffer and return pointer to it
+ *
+ * \param buf The input buffer (not null-terminated)
+ * \param blen The length of the input buffer
+ * \param delims Character delimiters (null-terminated)
+ * \param remainPtr Pointer to remaining after tokenizing iteration
+ * \param tokLen Output token length (if non-null line)
+ *
+ * \return Pointer to token, or NULL if not found
+ */
+static char * GetToken(char *buf, uint32_t blen, const char *delims,
+        char **remainPtr, uint32_t *tokenLen) {
+
+    uint32_t i, j, delimFound = 0;
+    char *tok = NULL;
+
+    /* So that it can be used just like strtok_r */
+    if (buf == NULL) {
+        buf = *remainPtr;
+    } else {
+        *remainPtr = buf;
+    }
+
+    /* Must specify length */
+    for (i = 0; i < blen && buf[i] != 0; i++) {
+
+        /* Look for delimiters */
+        for (j = 0; delims[j] != 0; j++) {
+            if (buf[i] == delims[j]) {
+                /* Data must be found before delimiter matters */
+                if (tok != NULL) {
+                    (*remainPtr) += (i + 1);
+                }
+                delimFound = 1;
+                break;
+            }
+        }
+
+        /* If at least one non-delimiter found, then a token is found */
+        if (tok == NULL && !delimFound) {
+            tok = buf + i;
+        } else {
+            /* Reset delimiter */
+            delimFound = 0;
+        }
+
+        /* If delimiter found, then break out of loop */
+        if (buf != *remainPtr) {
+            break;
+        }
+    }
+
+    /* Make sure remaining points to end of buffer if delimiters not found */
+    if (tok != NULL) {
+        if (buf == *remainPtr) {
+            (*remainPtr) += i;
+        }
+
+        /* Calculate token length */
+        *tokenLen = (buf + i) - tok;
+    }
+
+    return tok;
+}
+
+/**
+ * \brief Stores the final MIME header value into the current entity on the
+ * stack.
+ *
+ * \param state The parser state
+ *
+ * \return MIME_DEC_OK if stored, otherwise a negative number indicating error
+ */
+static int StoreMimeHeader(MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK, stored = 0;
+    char *val;
+    uint32_t vlen;
+
+    /* Lets save the most recent header */
+    if (state->hname != NULL || state->hvalue != NULL) {
+        SCLogDebug("Storing last header");
+        val = GetFullValue(state->hvalue, &vlen);
+        if (val != NULL) {
+
+            if (state->hname == NULL) {
+                SCLogDebug("Error: Invalid parser state - header value without"
+                        " name");
+                ret = MIME_DEC_ERR_PARSE;
+            } else if (state->stack->top != NULL) {
+                /* Store each header name and value */
+                if (MimeDecFillField(state->stack->top->data, state->hname,
+                        state->hlen, val, vlen, 0) == NULL) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "MimeDecFillField() function failed");
+                    ret = MIME_DEC_ERR_MEM;
+                } else {
+                    stored = 1;
+                }
+            } else {
+                SCLogDebug("Error: Stack pointer missing");
+                ret = MIME_DEC_ERR_DATA;
+            }
+        } else if (state->hvalue != NULL) {
+            /* Memory allocation must have failed since val is NULL */
+            SCLogError(SC_ERR_MEM_ALLOC, "GetFullValue() function failed");
+            ret = MIME_DEC_ERR_MEM;
+        }
+
+        /* Do cleanup here */
+        if (!stored) {
+            SCFree(state->hname);
+        }
+        state->hname = NULL;
+        FreeDataValue(state->hvalue);
+        state->hvalue = NULL;
+        state->hvlen = 0;
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Function determines whether a url string points to an executable
+ * based on file extension only.
+ *
+ * \param url The url string
+ * \param len The url string length
+ *
+ * \retval 1 The url points to an EXE
+ * \retval 0 The url does NOT point to an EXE
+ */
+static int IsExeUrl(char *url, uint32_t len) {
+
+    int isExeUrl = 0;
+    uint32_t i, extLen;
+    char *ext;
+
+    /* Now check for executable extensions and if not found, cut off at first '/' */
+    for (i = 0; UrlExeExts[i] != NULL; i++) {
+        extLen = strlen(UrlExeExts[i]);
+        ext = FindString(url, len, UrlExeExts[i]);
+        if (ext != NULL && (ext + extLen - url == len || ext[extLen] == '?')) {
+            isExeUrl = 1;
+            break;
+        }
+    }
+
+    return isExeUrl;
+}
+
+/**
+ * \brief Function determines whether a host string is a numeric IP v4 address
+ *
+ * \param urlhost The host string
+ * \param len The host string length
+ *
+ * \retval 1 The host is a numeric IP
+ * \retval 0 The host is NOT a numeric IP
+ */
+static int IsIpv4Host(char *urlhost, uint32_t len) {
+
+    struct sockaddr_in sa;
+
+    char tempIp[MAX_IP4_CHARS + 1];
+
+    /* Cut off at '/'  */
+    uint32_t i = 0;
+    for (i = 0; i < len && urlhost[i] != 0; i++) {
+
+        if (urlhost[i] == '/') {
+            break;
+        }
+    }
+
+    /* Too many chars */
+    if (i > MAX_IP4_CHARS) {
+        return 0;
+    }
+
+    /* Create null-terminated string */
+    strncpy(tempIp, urlhost, i);
+    tempIp[i] = 0;
+
+    return inet_pton(AF_INET, tempIp, &(sa.sin_addr));
+}
+
+/**
+ * \brief Function determines whether a host string is a numeric IP v6 address
+ *
+ * \param urlhost The host string
+ * \param len The host string length
+ *
+ * \retval 1 The host is a numeric IP
+ * \retval 0 The host is NOT a numeric IP
+ */
+static int IsIpv6Host(char *urlhost, uint32_t len) {
+
+    struct sockaddr_in sa;
+
+    char tempIp[MAX_IP6_CHARS + 1];
+
+    /* Cut off at '/'  */
+    uint32_t i = 0;
+    for (i = 0; i < len && urlhost[i] != 0; i++) {
+
+        if (urlhost[i] == '/') {
+            break;
+        }
+    }
+
+    /* Too many chars */
+    if (i > MAX_IP6_CHARS) {
+        return 0;
+    }
+
+    /* Create null-terminated string */
+    strncpy(tempIp, urlhost, i);
+    tempIp[i] = 0;
+
+    return inet_pton(AF_INET6, tempIp, &(sa.sin_addr));
+}
+
+/**
+ * \brief Traverses through the list of URLs for an exact match of the specified
+ * string
+ *
+ * \param entity The MIME entity
+ * \param url The matching URL string
+ * \param url_len The matching URL string length
+ *
+ * \return URL object or NULL if not found
+ */
+static MimeDecUrl * FindExistingUrl(MimeDecEntity *entity, char *url,
+        uint32_t url_len) {
+
+    uint32_t len;
+    MimeDecUrl *curr = entity->url_list;
+
+    while (curr != NULL) {
+
+        /* Get smaller length */
+        len = curr->url_len < url_len ? curr->url_len : url_len;
+        if (strncasecmp(curr->url, url, len) == 0) {
+            break;
+        }
+
+        curr = curr->next;
+    }
+
+    return curr;
+}
+
+/**
+ * \brief This function searches a text or html line for a URL string
+ *
+ * URLS are generally truncated to the 'host.domain' format because
+ * some email messages contain dozens or even hundreds of URLs with
+ * the same host, but with only small variations in path.
+ *
+ * The exception is that URLs with executable file extensions are stored
+ * with the full path.
+ *
+ * Numeric IPs, malformed numeric IPs, and URLs pointing to executables are
+ * also flagged as URLs of interest.
+ *
+ * \param line the line
+ * \param len the line length
+ * \param state The current parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int FindUrlStrings(const char *line, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    MimeDecEntity *entity = (MimeDecEntity *) state->stack->top->data;
+    char *fptr, *remptr, *tok = NULL, *tempUrl;
+    uint32_t tokLen, i, tempUrlLen;
+    uint8_t urlStrLen = 0, flags = 0;
+
+    remptr = (char *) line;
+    do {
+        SCLogDebug("Looking for URL String starting with: %s", URL_STR);
+
+        /* Check for token definition */
+        fptr = FindString(remptr, len - (remptr - line), URL_STR);
+        if (fptr != NULL) {
+
+            urlStrLen = strlen(URL_STR);
+            fptr += urlStrLen; /* Start at end of start string */
+            tok = GetToken(fptr, len - (fptr - line), " \"\'<>]\t", &remptr,
+                    &tokLen);
+            if (tok == fptr) {
+                SCLogDebug("Found url string");
+
+                /* First copy to temp URL string */
+                tempUrl = SCMalloc(urlStrLen + tokLen);
+                if (unlikely(tempUrl == NULL)) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed");
+                    return MIME_DEC_ERR_MEM;
+                }
+
+                PrintChars(SC_LOG_DEBUG, "RAW URL", tok, tokLen);
+
+                /* Copy over to temp URL while decoding */
+                tempUrlLen = 0;
+                for (i = 0; i < tokLen && tok[i] != 0; i++) {
+
+                    // URL decoding would probably go here
+
+                    tempUrl[tempUrlLen] = tok[i];
+                    tempUrlLen++;
+                }
+
+                /* Determine if URL points to an EXE */
+                if (IsExeUrl(tempUrl, tempUrlLen)) {
+                    flags |= URL_IS_EXE;
+
+                    PrintChars(SC_LOG_DEBUG, "EXE URL", tempUrl, tempUrlLen);
+                } else {
+                    /* Not an EXE URL */
+                    /* Cut off length at first '/' */
+                    tok = FindString(tempUrl, tempUrlLen, "/");
+                    if (tok != NULL) {
+                        tempUrlLen = tok - tempUrl;
+                    }
+                }
+
+                /* Make sure remaining URL exists */
+                if (tempUrlLen > 0) {
+
+                    /* Now look for numeric IP */
+                    if (IsIpv4Host(tempUrl, tempUrlLen)) {
+                        flags |= URL_IS_IP4;
+
+                        PrintChars(SC_LOG_DEBUG, "IP URL4", tempUrl, tempUrlLen);
+                    } else if (IsIpv6Host(tempUrl, tempUrlLen)) {
+                        flags |= URL_IS_IP6;
+
+                        PrintChars(SC_LOG_DEBUG, "IP URL6", tempUrl, tempUrlLen);
+                    }
+
+                    /* Update URL list */
+                    MimeDecUrl *url = FindExistingUrl(entity, tempUrl,
+                            tempUrlLen);
+                    if (url == NULL) {
+
+                        /* Add URL list item */
+                        url = MimeDecAddUrl(entity);
+                        url->url = tempUrl;
+                        url->url_len = tempUrlLen;
+                        url->url_flags |= flags;
+                    } else {
+                        SCFree(tempUrl);
+                    }
+
+                    /* Increment counter */
+                    url->url_cnt++;
+                } else {
+                    SCFree(tempUrl);
+                }
+            }
+        }
+    } while (fptr != NULL);
+
+    return ret;
+}
+
+/**
+ * \brief This function is a pre-processor for handling decoded data chunks that
+ * then invokes the caller's callback function for further processing
+ *
+ * \param chunk The decoded chunk
+ * \param len The decoded chunk length (varies)
+ * \param state The current parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessDecodedDataChunk(const uint8_t *chunk, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    MimeDecEntity *entity = (MimeDecEntity *) state->stack->top->data;
+    char *remainPtr, *tok;
+    uint32_t tokLen;
+
+    MimeDecConfig *mdcfg = MimeDecGetConfig();
+    if (mdcfg != NULL && mdcfg->extract_urls) {
+        /* If plain text or html, then look for URLs */
+        if ((entity->ctnt_flags & CTNT_IS_TEXT) ||
+                (entity->ctnt_flags & CTNT_IS_HTML)) {
+
+            /* Remainder from previous line */
+            if (state->linerem_len > 0) {
+                // TODO
+            } else {
+                /* No remainder from previous line */
+                /* Parse each line one by one */
+                remainPtr = (char *) chunk;
+                do {
+                    tok = GetLine(remainPtr, len - (remainPtr - (char *) chunk),
+                            &remainPtr, &tokLen);
+                    if (tok != remainPtr) {
+                        // DEBUG - ADDED
+                        /* If last token found without CR/LF delimiter, then save
+                         * and reconstruct with next chunk
+                         */
+                        if (tok + tokLen - (char *) chunk == len) {
+                            PrintChars(SC_LOG_DEBUG, "LAST CHUNK LINE - CUTOFF",
+                                    tok, tokLen);
+                            SCLogDebug("\nCHUNK CUTOFF CHARS: %d delim %ld\n", tokLen, len - (tok + tokLen - (char *) chunk));
+                        } else {
+                            /* Search line for URL */
+                            ret = FindUrlStrings(tok, tokLen, state);
+                            if (ret != MIME_DEC_OK) {
+                                SCLogDebug("Error: FindUrlStrings() function"
+                                        " failed: %d", ret);
+                                break;
+                            }
+                        }
+                    }
+                } while (tok != remainPtr && remainPtr - (char *) chunk < len);
+            }
+        }
+    }
+
+    /* Now invoke callback */
+    if (state->dataChunkProcessor != NULL) {
+        ret = state->dataChunkProcessor(chunk, len, state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: state->dataChunkProcessor() callback function"
+                    " failed");
+        }
+    }
+
+    /* Reset data chunk buffer */
+    state->data_chunk_len = 0;
+
+    /* Mark body / file as no longer at beginning */
+    state->body_begin = 0;
+
+    return ret;
+}
+
+/**
+ * \brief Processes a remainder (line % 4 = remainder) from the previous line
+ * such that all base64 decoding attempts are divisible by 4
+ *
+ * \param buf The current line
+ * \param len The length of the line
+ * \param state The current parser state
+ * \param force Flag indicating whether decoding should always occur
+ *
+ * \return Number of bytes pulled from the current buffer
+ */
+static uint8_t ProcessBase64Remainder(const char *buf, uint32_t len,
+        MimeDecParseState *state, int force) {
+
+    uint32_t ret;
+    uint8_t remainder = 0, remdec = 0;
+
+    SCLogDebug("Base64 line remainder found: %u", state->bvr_len);
+
+    /* Fill in block with first few bytes of current line */
+    remainder = B64_BLOCK - state->bvr_len;
+    remainder = remainder < len ? remainder : len;
+    strncpy(state->bvremain + state->bvr_len, buf, remainder);
+    state->bvr_len += remainder;
+
+    /* If data chunk buffer will be full, then clear it now */
+    if (DATA_CHUNK_SIZE - state->data_chunk_len < ASCII_BLOCK) {
+
+        /* Invoke pre-processor and callback */
+        ret = ProcessDecodedDataChunk(state->data_chunk, state->data_chunk_len,
+                state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessDecodedDataChunk() function failed");
+        }
+    }
+
+    /* Only decode if divisible by 4 */
+    if (state->bvr_len == B64_BLOCK || force) {
+        remdec = DecodeBase64(state->data_chunk + state->data_chunk_len,
+                state->bvremain, state->bvr_len);
+        if (remdec > 0) {
+
+            /* Track decoded length */
+            state->stack->top->data->decoded_body_len += remdec;
+
+            /* Update length */
+            state->data_chunk_len += remdec;
+
+            /* If data chunk buffer is now full, then clear */
+            if (DATA_CHUNK_SIZE - state->data_chunk_len < ASCII_BLOCK) {
+
+                /* Invoke pre-processor and callback */
+                ret = ProcessDecodedDataChunk(state->data_chunk,
+                        state->data_chunk_len, state);
+                if (ret != MIME_DEC_OK) {
+                    SCLogDebug("Error: ProcessDecodedDataChunk() function "
+                            "failed");
+                }
+            }
+        } else {
+            /* Track failed base64 */
+            state->stack->top->data->anomaly_flags |= ANOM_INVALID_BASE64;
+            state->msg->anomaly_flags |= ANOM_INVALID_BASE64;
+            SCLogDebug("Error: DecodeBase64() function failed");
+            PrintChars(SC_LOG_DEBUG, "Base64 failed string", state->bvremain, state->bvr_len);
+        }
+
+        /* Reset remaining */
+        state->bvr_len = 0;
+    }
+
+    return remainder;
+}
+
+/**
+ * \brief Processes a body line by base64-decoding and passing to the data chunk
+ * processing callback function when the buffer is read
+ *
+ * \param buf The current line
+ * \param len The length of the line
+ * \param state The current parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessBase64BodyLine(const char *buf, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    uint8_t rem1 = 0, rem2 = 0;
+    uint32_t numDecoded, remaining, offset, avail, tobuf;
+
+    /* Track long line */
+    if (len > MAX_ENC_LINE_LEN) {
+        state->stack->top->data->anomaly_flags |= ANOM_LONG_ENC_LINE;
+        state->msg->anomaly_flags |= ANOM_LONG_ENC_LINE;
+        SCLogDebug("Error: Max encoded input line length exceeded %u > %u",
+                len, MAX_ENC_LINE_LEN);
+    }
+
+    /* First process remaining from previous line */
+    if (state->bvr_len > 0) {
+
+        SCLogDebug("Base64 line remainder found: %u", state->bvr_len);
+
+        /* Process remainder and return number of bytes pulled from current buffer */
+        rem1 = ProcessBase64Remainder(buf, len, state, 0);
+    }
+
+    /* No error and at least some more data needs to be decoded */
+    if ((int) (len - rem1) > 0) {
+
+        /* Determine whether we need to save a remainder if not divisible by 4 */
+        rem2 = (len - rem1) % B64_BLOCK;
+        if (rem2 > 0) {
+
+            SCLogDebug("Base64 saving remainder: %u", rem2);
+
+            strncpy(state->bvremain, buf + (len - rem2), rem2);
+            state->bvr_len = rem2;
+        }
+
+        /* Process remaining in loop in case buffer fills up */
+        remaining = len - rem1 - rem2;
+        offset = rem1;
+        while (remaining > 0) {
+
+            /* Determine amount to add to buffer */
+            avail = (DATA_CHUNK_SIZE - state->data_chunk_len) * B64_BLOCK / ASCII_BLOCK;
+            tobuf = avail > remaining ? remaining : avail;
+            while (tobuf % 4 != 0) {
+                tobuf--;
+            }
+
+            if (tobuf < B64_BLOCK) {
+                SCLogDebug("Error: Invalid state for decoding base-64 block");
+                return MIME_DEC_ERR_PARSE;
+            }
+
+            SCLogDebug("Decoding: %u", len - rem1 - rem2);
+
+            numDecoded = DecodeBase64(state->data_chunk + state->data_chunk_len,
+                    buf + offset, tobuf);
+            if (numDecoded > 0) {
+
+                /* Track decoded length */
+                state->stack->top->data->decoded_body_len += numDecoded;
+
+                /* Update length */
+                state->data_chunk_len += numDecoded;
+
+                if ((int) (DATA_CHUNK_SIZE - state->data_chunk_len) < 0) {
+                    SCLogDebug("Error: Invalid Chunk length: %u",
+                            state->data_chunk_len);
+                    ret = MIME_DEC_ERR_PARSE;
+                    break;
+                }
+
+                /* If buffer full, then invoke callback */
+                if (DATA_CHUNK_SIZE - state->data_chunk_len < ASCII_BLOCK) {
+
+                    /* Invoke pre-processor and callback */
+                    ret = ProcessDecodedDataChunk(state->data_chunk,
+                            state->data_chunk_len, state);
+                    if (ret != MIME_DEC_OK) {
+                        SCLogDebug("Error: ProcessDecodedDataChunk() "
+                                "function failed");
+                    }
+                }
+            } else {
+                /* Track failed base64 */
+                state->stack->top->data->anomaly_flags |= ANOM_INVALID_BASE64;
+                state->msg->anomaly_flags |= ANOM_INVALID_BASE64;
+                SCLogDebug("Error: DecodeBase64() function failed");
+                PrintChars(SC_LOG_DEBUG, "Base64 failed string", (char *) (buf + offset), tobuf);
+            }
+
+            /* Update counts */
+            remaining -= tobuf;
+            offset += tobuf;
+        }
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Decoded a hex character into its equivalent byte value for
+ * quoted-printable decoding
+ *
+ * \param h The hex char
+ *
+ * \return byte value on success, -1 if failed
+ **/
+static int16_t DecodeQPChar(char h) {
+    uint16_t res = 0;
+
+    /* 0-9 */
+    if (h >= 48 && h <= 57) {
+        res = h - 48;
+    } else if (h >= 65 && h <= 70) {
+        /* A-F */
+        res = h - 55;
+    } else {
+        /* Invalid */
+        res = -1;
+    }
+
+    return res;
+
+}
+
+/**
+ * \brief Processes a quoted-printable encoded body line by decoding and passing
+ * to the data chunk processing callback function when the buffer is read
+ *
+ * \param buf The current line
+ * \param len The length of the line
+ * \param state The current parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessQuotedPrintableBodyLine(const char *buf, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    uint32_t remaining, offset;
+    MimeDecEntity *entity = (MimeDecEntity *) state->stack->top->data;
+    char c, h1, h2, val;
+    int16_t res;
+
+    /* Track long line */
+    if (len > MAX_ENC_LINE_LEN) {
+        state->stack->top->data->anomaly_flags |= ANOM_LONG_ENC_LINE;
+        state->msg->anomaly_flags |= ANOM_LONG_ENC_LINE;
+        SCLogDebug("Error: Max encoded input line length exceeded %u > %u",
+                len, MAX_ENC_LINE_LEN);
+    }
+
+    remaining = len;
+    offset = 0;
+    while (remaining > 0) {
+
+        c = *(buf + offset);
+
+        /* Copy over normal character */
+        if (c != '=') {
+            state->data_chunk[state->data_chunk_len] = c;
+            state->data_chunk_len++;
+            entity->decoded_body_len += 1;
+
+            /* Add CRLF sequence if end of line */
+            if (remaining == 1) {
+                memcpy(state->data_chunk + state->data_chunk_len, CRLF, EOL_LEN);
+                state->data_chunk_len += EOL_LEN;
+                entity->decoded_body_len += EOL_LEN;
+            }
+        } else if (remaining > 1) {
+            /* If last character handle as soft line break by ignoring,
+                       otherwise process as escaped '=' character */
+
+            /* Not enough characters */
+            if (remaining < 3) {
+                entity->anomaly_flags |= ANOM_INVALID_QP;
+                state->msg->anomaly_flags |= ANOM_INVALID_QP;
+                SCLogDebug("Error: Quoted-printable decoding failed");
+            } else {
+                h1 = *(buf + offset + 1);
+                res = DecodeQPChar(h1);
+                if (res < 0) {
+                    entity->anomaly_flags |= ANOM_INVALID_QP;
+                    state->msg->anomaly_flags |= ANOM_INVALID_QP;
+                    SCLogDebug("Error: Quoted-printable decoding failed");
+                } else {
+                    val = (res << 4); /* Shift result left */
+                    h2 = *(buf + offset + 2);
+                    res = DecodeQPChar(h2);
+                    if (res < 0) {
+                        entity->anomaly_flags |= ANOM_INVALID_QP;
+                        state->msg->anomaly_flags |= ANOM_INVALID_QP;
+                        SCLogDebug("Error: Quoted-printable decoding failed");
+                    } else {
+                        /* Decoding sequence succeeded */
+                        val += res;
+
+                        state->data_chunk[state->data_chunk_len] = val;
+                        state->data_chunk_len++;
+                        entity->decoded_body_len++;
+
+                        /* Add CRLF sequence if end of line */
+                        if (remaining == 3) {
+                            memcpy(state->data_chunk + state->data_chunk_len,
+                                    CRLF, EOL_LEN);
+                            state->data_chunk_len += EOL_LEN;
+                            entity->decoded_body_len += EOL_LEN;
+                        }
+
+                        /* Account for extra 2 characters in 3-characted QP
+                         * sequence */
+                        remaining -= 2;
+                        offset += 2;
+                    }
+                }
+            }
+        }
+
+        /* Change by 1 */
+        remaining--;
+        offset++;
+
+        /* If buffer full, then invoke callback */
+        if (DATA_CHUNK_SIZE - state->data_chunk_len < EOL_LEN + 1) {
+
+            /* Invoke pre-processor and callback */
+            ret = ProcessDecodedDataChunk(state->data_chunk, state->data_chunk_len,
+                    state);
+            if (ret != MIME_DEC_OK) {
+                SCLogDebug("Error: ProcessDecodedDataChunk() function "
+                        "failed");
+            }
+        }
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Processes a body line by base64-decoding (if applicable) and passing to
+ * the data chunk processing callback function
+ *
+ * \param buf The current line
+ * \param len The length of the line
+ * \param state The current parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessBodyLine(const char *buf, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    uint32_t remaining, offset, avail, tobuf;
+    MimeDecEntity *entity = (MimeDecEntity *) state->stack->top->data;
+
+    SCLogDebug("Processing body line");
+
+    /* Track length */
+    entity->body_len += len + 2; /* With CRLF */
+
+    /* Process base-64 content if enabled */
+    MimeDecConfig *mdcfg = MimeDecGetConfig();
+    if (mdcfg != NULL && mdcfg->decode_base64 &&
+            (entity->ctnt_flags & CTNT_IS_BASE64)) {
+
+        ret = ProcessBase64BodyLine(buf, len, state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessBase64BodyLine() function failed");
+        }
+    } else if (mdcfg != NULL && mdcfg->decode_quoted_printable &&
+            (entity->ctnt_flags & CTNT_IS_QP)) {
+        /* Process quoted-printable content if enabled */
+        ret = ProcessQuotedPrintableBodyLine(buf, len, state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessQuotedPrintableBodyLine() function "
+                    "failed");
+        }
+    } else {
+        /* Process non-decoded content */
+        remaining = len;
+        offset = 0;
+        while (remaining > 0) {
+
+            /* Plan to add CRLF to the end of each line */
+            avail = DATA_CHUNK_SIZE - state->data_chunk_len;
+            tobuf = avail > remaining + EOL_LEN ? remaining : avail - EOL_LEN;
+
+            /* Copy over to buffer */
+            memcpy(state->data_chunk + state->data_chunk_len, buf + offset, tobuf);
+            state->data_chunk_len += tobuf;
+
+            /* Now always add a CRLF to the end */
+            if (tobuf == remaining) {
+                memcpy(state->data_chunk + state->data_chunk_len, CRLF, EOL_LEN);
+                state->data_chunk_len += EOL_LEN;
+            }
+
+            if ((int) (DATA_CHUNK_SIZE - state->data_chunk_len) < 0) {
+                SCLogDebug("Error: Invalid Chunk length: %u",
+                        state->data_chunk_len);
+                ret = MIME_DEC_ERR_PARSE;
+                break;
+            }
+
+            /* If buffer full, then invoke callback */
+            if (DATA_CHUNK_SIZE - state->data_chunk_len < EOL_LEN + 1) {
+
+                /* Invoke pre-processor and callback */
+                ret = ProcessDecodedDataChunk(state->data_chunk,
+                        state->data_chunk_len, state);
+                if (ret != MIME_DEC_OK) {
+                    SCLogDebug("Error: ProcessDecodedDataChunk() function "
+                            "failed");
+                }
+            }
+
+            remaining -= tobuf;
+            offset += tobuf;
+        }
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Find the start of a header name on the current line
+ *
+ * \param buf The input line (not null-terminated)
+ * \param blen The length of the input line
+ * \param glen The output length of the header name
+ *
+ * \return Pointer to header name, or NULL if not found
+ */
+static char * FindMimeHeaderStart(const char *buf, uint32_t blen, uint32_t *hlen) {
+
+    uint32_t i, valid = 0;
+    char *hname = NULL;
+
+    /* Init */
+    *hlen = 0;
+
+    /* Look for sequence of printable characters followed by ':', or
+       CRLF then printable characters followed by ':' */
+    for (i = 0; i < blen && buf[i] != 0; i++) {
+
+        /* If ready for printable characters and found one, then increment */
+        if (buf[i] != COLON && buf[i] >= PRINTABLE_START &&
+                buf[i] <= PRINTABLE_END) {
+            valid++;
+        } else if (valid > 0 && buf[i] == COLON) {
+            /* If ready for printable characters, found some, and found colon
+             * delimiter, then a match is found */
+            hname = (char *) buf + i - valid;
+            *hlen = valid;
+            break;
+        } else {
+            /* Otherwise reset and quit */
+            break;
+        }
+    }
+
+    return hname;
+}
+
+/**
+ * \brief Find full header name and value on the current line based on the
+ * current state
+ *
+ * \param buf The current line (no CRLF)
+ * \param blen The length of the current line
+ * \param state The current state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int FindMimeHeader(const char *buf, uint32_t blen,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    char *hname, *hval = NULL;
+    DataValue *dv;
+    uint32_t hlen, vlen;
+    int finish_header = 0, new_header = 0;
+    MimeDecConfig *mdcfg = MimeDecGetConfig();
+
+    /* Find first header */
+    hname = FindMimeHeaderStart(buf, blen, &hlen);
+    if (hname != NULL) {
+
+        /* Warn and track but don't do anything yet */
+        if (hlen > MAX_HEADER_NAME) {
+            state->stack->top->data->anomaly_flags |= ANOM_LONG_HEADER_NAME;
+            state->msg->anomaly_flags |= ANOM_LONG_HEADER_NAME;
+            SCLogDebug("Error: Header name exceeds limit (%u > %u)",
+                    hlen, MAX_HEADER_NAME);
+        }
+
+        /* Value starts after 'header:' (normalize spaces) */
+        hval = hname + hlen + 1;
+        if (hval - buf >= blen) {
+            SCLogDebug("No Header value found");
+            hval = NULL;
+        } else {
+            while (hval[0] == ' ') {
+
+                /* If last character before end of bounds, set to NULL */
+                if (hval - buf >= blen - 1) {
+                    SCLogDebug("No Header value found");
+                    hval = NULL;
+                    break;
+                }
+
+                hval++;
+            }
+        }
+
+        /* If new header found, then previous header is finished */
+        if (state->state_flag == HEADER_STARTED) {
+            finish_header = 1;
+        }
+
+        /* Now process new header */
+        new_header = 1;
+
+        /* Must wait for next line to determine if finished */
+        state->state_flag = HEADER_STARTED;
+    } else if (blen == 0) {
+        /* Found body */
+        /* No more headers */
+        state->state_flag = HEADER_DONE;
+
+        finish_header = 1;
+
+        SCLogDebug("All Header processing finished");
+    } else if (state->state_flag == HEADER_STARTED) {
+        /* Found multi-line value (ie. Received header) */
+        /* If max header value exceeded, flag it */
+        vlen = blen;
+        if ((mdcfg != NULL) && (state->hvlen + vlen > mdcfg->header_value_depth)) {
+            SCLogDebug("Error: Header value of length (%u) is too long",
+                    state->hvlen + vlen);
+            vlen = mdcfg->header_value_depth - state->hvlen;
+            state->stack->top->data->anomaly_flags |= ANOM_LONG_HEADER_VALUE;
+            state->msg->anomaly_flags |= ANOM_LONG_HEADER_VALUE;
+        }
+        if (vlen > 0) {
+            dv = AddDataValue(state->hvalue);
+            if (dv == NULL) {
+                SCLogError(SC_ERR_MEM_ALLOC, "AddDataValue() function failed");
+                return MIME_DEC_ERR_MEM;
+            }
+            if (state->hvalue == NULL) {
+                state->hvalue = dv;
+            }
+
+            dv->value = SCMalloc(vlen);
+            if (unlikely(dv->value == NULL)) {
+                SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed");
+                return MIME_DEC_ERR_MEM;
+            }
+            strncpy(dv->value, (char *) buf, vlen);
+            dv->value_len = vlen;
+            state->hvlen += vlen;
+        }
+    } else {
+        /* Likely a body without headers */
+        SCLogDebug("No headers found");
+
+        state->state_flag = BODY_STARTED;
+
+        /* Flag beginning of body */
+        state->body_begin = 1;
+        state->body_end = 0;
+
+        ret = ProcessBodyLine(buf, blen, state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessBodyLine() function failed");
+            return ret;
+        }
+    }
+
+    /* If we need to finish a header, then do so below and then cleanup */
+    if (finish_header) {
+
+        /* Store the header value */
+        ret = StoreMimeHeader(state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: StoreMimeHeader() function failed");
+            return ret;
+        }
+    }
+
+    /* When next header is found, we always create a new one */
+    if (new_header) {
+
+        /* Copy name and value to state */
+        state->hname = SCMalloc(hlen);
+        if (unlikely(state->hname == NULL)) {
+            SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed");
+            return MIME_DEC_ERR_MEM;
+        }
+        strncpy(state->hname, hname, hlen);
+        state->hlen = hlen;
+
+        if (state->hvalue != NULL) {
+            SCLogDebug("Error: Parser failed due to unexpected header "
+                    "value");
+            return MIME_DEC_ERR_DATA;
+        }
+
+        if (hval != NULL) {
+
+            /* If max header value exceeded, flag it */
+            vlen = blen - (hval - buf);
+            if ((mdcfg != NULL) && (state->hvlen + vlen > mdcfg->header_value_depth)) {
+                SCLogDebug("Error: Header value of length (%u) is too long",
+                        state->hvlen + vlen);
+                vlen = mdcfg->header_value_depth - state->hvlen;
+                state->stack->top->data->anomaly_flags |= ANOM_LONG_HEADER_VALUE;
+                state->msg->anomaly_flags |= ANOM_LONG_HEADER_VALUE;
+            }
+            if (vlen > 0) {
+
+                state->hvalue = AddDataValue(NULL);
+                if (state->hvalue == NULL) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "AddDataValue() function failed");
+                    return MIME_DEC_ERR_MEM;
+                }
+                state->hvalue->value = SCMalloc(vlen);
+                if (unlikely(state->hvalue->value == NULL)) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed");
+                    return MIME_DEC_ERR_MEM;
+                }
+                strncpy(state->hvalue->value, hval, vlen);
+                state->hvalue->value_len = vlen;
+                state->hvlen += vlen;
+            }
+        }
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Finds a mime header token within the specified field
+ *
+ * \param field The current field
+ * \param search_start The start of the search (ie. boundary=\")
+ * \param search_end The end of the search (ie. \")
+ * \param tlen The output length of the token (if found)
+ *
+ * \return A pointer to the token if found, otherwise NULL if not found
+ */
+static char * FindMimeHeaderToken(MimeDecField *field, char *search_start,
+        char *search_end, uint32_t *tlen) {
+
+    char *fptr, *tptr = NULL, *tok = NULL;
+
+    SCLogDebug("Looking for token: %s", search_start);
+
+    /* Check for token definition */
+    fptr = FindString(field->value, field->value_len, search_start);
+    if (fptr != NULL) {
+        fptr += strlen(search_start); /* Start at end of start string */
+        tok = GetToken(fptr, field->value_len - (fptr - field->value), search_end,
+                &tptr, tlen);
+        if (tok != NULL) {
+            SCLogDebug("Found mime token");
+        }
+    }
+
+    return tok;
+}
+
+/**
+ * \brief Processes the current line for mime headers and also does post-processing
+ * when all headers found
+ *
+ * \param buf The current line
+ * \param len The length of the line
+ * \param state The current parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessMimeHeaders(const char *buf, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    MimeDecField *field;
+    char *bptr = NULL, *rptr = NULL;
+    uint32_t blen = 0;
+    MimeDecEntity *entity = (MimeDecEntity *) state->stack->top->data;
+
+    /* Look for mime header in current line */
+    ret = FindMimeHeader(buf, len, state);
+    if (ret != MIME_DEC_OK) {
+        SCLogDebug("Error: FindMimeHeader() function failed: %d", ret);
+        return ret;
+    }
+
+    /* Post-processing after all headers done */
+    if (state->state_flag == HEADER_DONE) {
+
+        /* First determine encoding by looking at Content-Transfer-Encoding */
+        field = MimeDecFindField(entity, CTNT_TRAN_STR);
+        if (field != NULL) {
+            /* Look for base64 */
+            if (FindString(field->value, field->value_len, BASE64_STR)) {
+                SCLogDebug("Base64 encoding found");
+                entity->ctnt_flags |= CTNT_IS_BASE64;
+            } else if (FindString(field->value, field->value_len, QP_STR)) {
+                /* Look for quoted-printable */
+                SCLogDebug("quoted-printable encoding found");
+                entity->ctnt_flags |= CTNT_IS_QP;
+            }
+        }
+
+        /* Check for file attachment in content disposition */
+        field = MimeDecFindField(entity, CTNT_DISP_STR);
+        if (field != NULL) {
+            bptr = FindMimeHeaderToken(field, "filename=\"", TOK_END_STR, &blen);
+            if (bptr != NULL) {
+                SCLogDebug("File attachment found in disposition");
+                entity->ctnt_flags |= CTNT_IS_ATTACHMENT;
+
+                /* Copy over using dynamic memory */
+                entity->filename = SCMalloc(blen);
+                if (unlikely(entity->filename == NULL)) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+                    return MIME_DEC_ERR_MEM;
+                }
+                strncpy(entity->filename, bptr, blen);
+                entity->filename_len = blen;
+            }
+        }
+
+        /* Check for boundary, encapsulated message, and file name in Content-Type */
+        field = MimeDecFindField(entity, CTNT_TYPE_STR);
+        if (field != NULL) {
+
+            /* Check if child entity boundary definition found */
+            bptr = FindMimeHeaderToken(field, BND_START_STR, TOK_END_STR, &blen);
+            if (bptr != NULL) {
+                state->found_child = 1;
+
+                entity->ctnt_flags |= CTNT_IS_MULTIPART;
+
+                /* Store boundary in parent node */
+                state->stack->top->bdef = SCMalloc(blen);
+                if (unlikely(state->stack->top->bdef == NULL)) {
+                    SCLogError(SC_ERR_MEM_ALLOC, "Memory allocation failed");
+                    return MIME_DEC_ERR_MEM;
+                }
+                strncpy(state->stack->top->bdef, bptr, blen);
+                state->stack->top->bdef_len = blen;
+            }
+
+            /* Look for file name (if not already found) */
+            if (!(entity->ctnt_flags & CTNT_IS_ATTACHMENT)) {
+                bptr = FindMimeHeaderToken(field, "name=\"", TOK_END_STR, &blen);
+                if (bptr != NULL) {
+                    SCLogDebug("File attachment found");
+                    entity->ctnt_flags |= CTNT_IS_ATTACHMENT;
+
+                    /* Copy over using dynamic memory */
+                    entity->filename = SCMalloc(blen);
+                    if (unlikely(entity->filename == NULL)) {
+                        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+                        return MIME_DEC_ERR_MEM;
+                    }
+                    strncpy(entity->filename, bptr, blen);
+                    entity->filename_len = blen;
+                }
+            }
+
+            /* Pull out short-hand content type */
+            entity->ctnt_type = GetToken(field->value, field->value_len, " \r\n;",
+                    &rptr, &entity->ctnt_type_len);
+            if (entity->ctnt_type != NULL) {
+
+                /* Check for encapsulated message */
+                if (FindString(entity->ctnt_type, entity->ctnt_type_len, MSG_STR)) {
+                    SCLogDebug("Found encapsulated message entity");
+
+                    entity->ctnt_flags |= CTNT_IS_ENV;
+
+                    /* Create and push child to stack */
+                    MimeDecEntity *child = MimeDecAddEntity(entity);
+                    child->ctnt_flags |= (CTNT_IS_ENCAP | CTNT_IS_MSG);
+                    PushStack(state->stack);
+                    state->stack->top->data = child;
+
+                    /* Mark as encapsulated child */
+                    state->stack->top->is_encap = 1;
+
+                    /* Ready to parse headers */
+                    state->state_flag = HEADER_READY;
+                } else if (FindString(entity->ctnt_type, entity->ctnt_type_len,
+                        MULTIPART_STR)) {
+                    /* Check for multipart */
+                    SCLogDebug("Found multipart entity");
+                    entity->ctnt_flags |= CTNT_IS_MULTIPART;
+                } else if (FindString(entity->ctnt_type, entity->ctnt_type_len,
+                        TXT_STR)) {
+                    /* Check for plain text */
+                    SCLogDebug("Found plain text entity");
+                    entity->ctnt_flags |= CTNT_IS_TEXT;
+                } else if (FindString(entity->ctnt_type, entity->ctnt_type_len,
+                        HTML_STR)) {
+                    /* Check for html */
+                    SCLogDebug("Found html entity");
+                    entity->ctnt_flags |= CTNT_IS_HTML;
+                }
+            }
+        }
+
+        /* Store pointer to Message-ID */
+        field = MimeDecFindField(entity, MSG_ID_STR);
+        if (field != NULL) {
+            entity->msg_id = field->value;
+            entity->msg_id_len = field->value_len;
+        }
+
+        /* Flag beginning of body */
+        state->body_begin = 1;
+        state->body_end = 0;
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Indicates to the parser that the body of an entity has completed
+ * processing on the previous line
+ *
+ * \param state The current parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessBodyComplete(MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+
+    SCLogDebug("Process body complete called");
+
+    /* Mark the file as hitting the end */
+    state->body_end = 1;
+
+    if (state->bvr_len > 0) {
+        SCLogDebug("Found (%u) remaining base64 bytes not processed",
+                state->bvr_len);
+
+        /* Process the remainder */
+        ret = ProcessBase64Remainder(NULL, 0, state, 1);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessBase64BodyLine() function failed");
+        }
+    }
+
+    /* Invoke pre-processor and callback with remaining data */
+    ret = ProcessDecodedDataChunk(state->data_chunk, state->data_chunk_len, state);
+    if (ret != MIME_DEC_OK) {
+        SCLogDebug("Error: ProcessDecodedDataChunk() function failed");
+    }
+
+    /* Now reset */
+    state->body_begin = 0;
+    state->body_end = 0;
+
+    return ret;
+}
+
+/**
+ * \brief When a mime boundary is found, look for end boundary and also do stack
+ * management
+ *
+ * \param buf The current line
+ * \param len The length of the line
+ * \param bdef_len The length of the current boundary
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessMimeBoundary(const char *buf, uint32_t len, uint32_t bdef_len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    char *rptr;
+    MimeDecEntity *child;
+
+    SCLogDebug("PROCESSING BOUNDARY - START: %d",
+            state->state_flag);
+
+    /* If previous line was not an end boundary, then we process the body as
+     * completed */
+    if (state->state_flag != BODY_END_BOUND) {
+
+        /* First lets complete the body */
+        ret = ProcessBodyComplete(state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessBodyComplete() function failed");
+            return ret;
+        }
+    } else {
+        /* If last line was an end boundary, then now we are ready to parse
+         * headers again */
+        state->state_flag = HEADER_READY;
+    }
+
+    /* Update remaining buffer */
+    rptr = (char *) buf + bdef_len + 2;
+
+    /* If entity is encapsulated and current and parent didn't define the boundary,
+     * then pop out */
+    if (state->stack->top->is_encap && state->stack->top->bdef_len == 0) {
+
+        if (state->stack->top->next == NULL) {
+            SCLogDebug("Error: Missing parent entity from stack");
+            return MIME_DEC_ERR_DATA;
+        }
+
+        if (state->stack->top->next->bdef_len == 0) {
+
+            SCLogDebug("POPPED ENCAPSULATED CHILD FROM STACK: %p=%p",
+                    state->stack->top, state->stack->top->data);
+
+            /* If end of boundary found, pop the child off the stack */
+            PopStack(state->stack);
+            if (state->stack->top == NULL) {
+                SCLogDebug("Error: Message is malformed");
+                return MIME_DEC_ERR_DATA;
+            }
+        }
+    }
+
+    /* Now check for end of nested boundary */
+    if (len - (rptr - buf) > 1 && rptr[0] == DASH && rptr[1] == DASH) {
+
+        SCLogDebug("FOUND END BOUNDARY, POPPING: %p=%p",
+                state->stack->top, state->stack->top->data);
+
+        /* If end of boundary found, pop the child off the stack */
+        PopStack(state->stack);
+        if (state->stack->top == NULL) {
+            SCLogDebug("Error: Message is malformed");
+            return MIME_DEC_ERR_DATA;
+        }
+
+        /* If current is an encapsulated message with a boundary definition,
+         * then pop him as well */
+        if (state->stack->top->is_encap && state->stack->top->bdef_len != 0) {
+
+            SCLogDebug("FOUND END BOUNDARY AND ENCAP, POPPING: %p=%p",
+                    state->stack->top, state->stack->top->data);
+
+            PopStack(state->stack);
+            if (state->stack->top == NULL) {
+                SCLogDebug("Error: Message is malformed");
+                return MIME_DEC_ERR_DATA;
+            }
+        }
+
+        state->state_flag = BODY_END_BOUND;
+    } else if (state->found_child) {
+        /* Otherwise process new child */
+        SCLogDebug("Child entity created");
+
+        /* Create and push child to stack */
+        child = MimeDecAddEntity(state->stack->top->data);
+        child->ctnt_flags |= CTNT_IS_BODYPART;
+        PushStack(state->stack);
+        state->stack->top->data = child;
+
+        /* Reset flag */
+        state->found_child = 0;
+    } else {
+        /* Otherwise process sibling */
+        if (state->stack->top->next == NULL) {
+            SCLogDebug("Error: Missing parent entity from stack");
+            return MIME_DEC_ERR_DATA;
+        }
+
+        SCLogDebug("SIBLING CREATED, POPPING PARENT: %p=%p",
+                state->stack->top, state->stack->top->data);
+
+        /* First pop current to get access to parent */
+        PopStack(state->stack);
+        if (state->stack->top == NULL) {
+            SCLogDebug("Error: Message is malformed");
+            return MIME_DEC_ERR_DATA;
+        }
+
+        /* Create and push child to stack */
+        child = MimeDecAddEntity(state->stack->top->data);
+        child->ctnt_flags |= CTNT_IS_BODYPART;
+        PushStack(state->stack);
+        state->stack->top->data = child;
+    }
+
+    /* After boundary look for headers */
+    if (state->state_flag != BODY_END_BOUND) {
+        state->state_flag = HEADER_READY;
+    }
+
+    SCLogDebug("PROCESSING BOUNDARY - END: %d", state->state_flag);
+
+    return ret;
+}
+
+/**
+ * \brief Processes the MIME Entity body based on the input line and current
+ * state of the parser
+ *
+ * \param buf The current line
+ * \param len The length of the line
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessMimeBody(const char *buf, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+    char temp[BOUNDARY_BUF];
+    char *bstart;
+    int body_found = 0;
+    uint32_t tlen;
+
+    /* Ignore empty lines */
+    if (len == 0) {
+        return ret;
+    }
+
+    /* First look for boundary */
+    MimeDecStackNode *node = state->stack->top;
+    if (node == NULL) {
+        SCLogDebug("Error: Invalid stack state");
+        return MIME_DEC_ERR_PARSE;
+    }
+
+    /* Traverse through stack to find a boundary definition */
+    if (state->state_flag == BODY_END_BOUND || node->bdef == NULL) {
+
+        /* If not found, then use parent's boundary */
+        node = node->next;
+        while (node != NULL && node->bdef == NULL) {
+            SCLogDebug("Traversing through stack for node with boundary");
+            node = node->next;
+        }
+    }
+
+    /* This means no boundary / parent w/boundary was found so we are in the body */
+    if (node == NULL) {
+        body_found = 1;
+    } else {
+
+        /* Now look for start of boundary */
+        if (len > 1 && buf[0] == '-' && buf[1] == '-') {
+
+            tlen = node->bdef_len + 2;
+            strncpy(temp, "--", 2);
+            strncpy(temp + 2, node->bdef, node->bdef_len);
+            temp[tlen] = 0;
+
+            SCLogDebug("Boundary to search: [%s]", temp);
+
+            /* Find either next boundary or end boundary */
+            bstart = FindString(buf, len, temp);
+            if (bstart != NULL) {
+                ret = ProcessMimeBoundary(buf, len, node->bdef_len, state);
+                if (ret != MIME_DEC_OK) {
+                    SCLogDebug("Error: ProcessMimeBoundary() function "
+                            "failed");
+                    return ret;
+                }
+            } else {
+                /* Otherwise add value to body */
+                body_found = 1;
+            }
+        } else {
+            /* Otherwise add value to body */
+            body_found = 1;
+        }
+    }
+
+    /* Process body line */
+    if (body_found) {
+        state->state_flag = BODY_STARTED;
+
+        ret = ProcessBodyLine(buf, len, state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessBodyLine() function failed");
+            return ret;
+        }
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Processes the MIME Entity based on the input line and current state of
+ * the parser
+ *
+ * \param buf The current line
+ * \param len The length of the line
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+static int ProcessMimeEntity(const char *buf, uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+
+    SCLogDebug("START FLAG: %s", StateFlags[state->state_flag]);
+
+    /* Track long line */
+    if (len > MAX_LINE_LEN) {
+        state->stack->top->data->anomaly_flags |= ANOM_LONG_LINE;
+        state->msg->anomaly_flags |= ANOM_LONG_LINE;
+        SCLogDebug("Error: Max input line length exceeded %u > %u", len,
+                MAX_LINE_LEN);
+    }
+
+    /* Looking for headers */
+    if (state->state_flag == HEADER_READY ||
+            state->state_flag == HEADER_STARTED) {
+
+        SCLogDebug("Processing Headers");
+
+        /* Process message headers */
+        ret = ProcessMimeHeaders(buf, len, state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessMimeHeaders() function failed: %d",
+                    ret);
+            return ret;
+        }
+    } else {
+        /* Processing body */
+        SCLogDebug("Processing Body of: %p", state->stack->top);
+
+        ret = ProcessMimeBody(buf, len, state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: ProcessMimeBody() function failed: %d",
+                    ret);
+            return ret;
+        }
+    }
+
+    SCLogDebug("END FLAG: %s", StateFlags[state->state_flag]);
+
+    return ret;
+}
+
+/**
+ * \brief Init the parser by allocating memory for the state and top-level entity
+ *
+ * \param data A caller-specified pointer to data for access within the data chunk
+ * processor callback function
+ * \param dcpfunc The data chunk processor callback function
+ *
+ * \return A pointer to the state object, or NULL if the operation fails
+ */
+MimeDecParseState * MimeDecInitParser(void *data,
+        int (*dcpfunc)(const uint8_t *chunk, uint32_t len,
+                MimeDecParseState *state)) {
+
+    MimeDecParseState *state;
+    MimeDecEntity *mimeMsg;
+
+    state = SCMalloc(sizeof(MimeDecParseState));
+    if (unlikely(state == NULL)) {
+        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+        return NULL;
+    }
+    memset(state, 0x00, sizeof(MimeDecParseState));
+
+    state->stack = SCMalloc(sizeof(MimeDecStack));
+    if (unlikely(state->stack == NULL)) {
+        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+        return NULL;
+    }
+    memset(state->stack, 0x00, sizeof(MimeDecStack));
+
+    mimeMsg = SCMalloc(sizeof(MimeDecEntity));
+    if (unlikely(mimeMsg == NULL)) {
+        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+        return NULL;
+    }
+    memset(mimeMsg, 0x00, sizeof(MimeDecEntity));
+    mimeMsg->ctnt_flags |= CTNT_IS_MSG;
+
+    /* Init state */
+    state->msg = mimeMsg;
+    PushStack(state->stack);
+    if (state->stack->top == NULL) {
+        SCLogError(SC_ERR_MEM_ALLOC, "memory allocation failed");
+        return NULL;
+    }
+    state->stack->top->data = mimeMsg;
+    state->state_flag = HEADER_READY;
+    state->data = data;
+    state->dataChunkProcessor = dcpfunc;
+
+    return state;
+}
+
+/**
+ * \brief De-Init parser by freeing up any residual memory
+ *
+ * \param state The parser state
+ *
+ * \return none
+ */
+void MimeDecDeInitParser(MimeDecParseState *state) {
+
+    uint32_t cnt = 0;
+
+    while (state->stack->top != NULL) {
+
+        SCLogDebug("Remaining on stack: [%p]=>[%p]",
+                state->stack->top, state->stack->top->data);
+
+        PopStack(state->stack);
+        cnt++;
+    }
+
+    if (cnt > 1) {
+        state->msg->anomaly_flags |= ANOM_MALFORMED_MSG;
+        SCLogDebug("Warning: Stack is not empty upon completion of "
+                "processing (%u items remaining)", cnt);
+    }
+
+    SCFree(state->hname);
+    FreeDataValue(state->hvalue);
+    FreeMimeDecStack(state->stack);
+    SCFree(state);
+}
+
+/**
+ * \brief Called to indicate that the last message line has been processed and
+ * the parsing operation is complete
+ *
+ * This function should be called directly by the caller.
+ *
+ * \param state The parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+int MimeDecParseComplete(MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+
+    SCLogDebug("Parsing flagged as completed");
+
+    /* Store the header value */
+    ret = StoreMimeHeader(state);
+    if (ret != MIME_DEC_OK) {
+        SCLogDebug("Error: StoreMimeHeader() function failed");
+        return ret;
+    }
+
+    /* Lets complete the body */
+    ret = ProcessBodyComplete(state);
+    if (ret != MIME_DEC_OK) {
+        SCLogDebug("Error: ProcessBodyComplete() function failed");
+        return ret;
+    }
+
+    if (state->stack->top == NULL) {
+        state->msg->anomaly_flags |= ANOM_MALFORMED_MSG;
+        SCLogDebug("Error: Message is malformed");
+        return MIME_DEC_ERR_DATA;
+    }
+
+    /* If encapsulated, pop off the stack */
+    if (state->stack->top->is_encap) {
+        PopStack(state->stack);
+        if (state->stack->top == NULL) {
+            state->msg->anomaly_flags |= ANOM_MALFORMED_MSG;
+            SCLogDebug("Error: Message is malformed");
+            return MIME_DEC_ERR_DATA;
+        }
+    }
+
+    /* Look extra stack items remaining */
+    if (state->stack->top->next != NULL) {
+        state->msg->anomaly_flags |= ANOM_MALFORMED_MSG;
+        SCLogDebug("Warning: Message has unclosed message part boundary");
+    }
+
+    state->state_flag = PARSE_DONE;
+
+    return ret;
+}
+
+/**
+ * \brief Parse a line of a MIME message and update the parser state
+ *
+ * \param line A string representing the line (w/out CRLF)
+ * \param len The length of the line
+ * \param state The parser state
+ *
+ * \return MIME_DEC_OK on success, otherwise < 0 on failure
+ */
+int MimeDecParseLine(const char *line, const uint32_t len,
+        MimeDecParseState *state) {
+
+    int ret = MIME_DEC_OK;
+
+    /* For debugging purposes */
+    if (len > 0) {
+        PrintChars(SC_LOG_DEBUG, "\nSMTP LINE", (char *) line, len);
+    } else {
+        SCLogDebug("\nSMTP LINE - EMPTY");
+    }
+
+    /* Process the entity */
+    ret = ProcessMimeEntity(line, len, state);
+    if (ret != MIME_DEC_OK) {
+        state->state_flag = PARSE_ERROR;
+        SCLogDebug("Error: ProcessMimeEntity() function failed: %d", ret);
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Parses an entire message when available in its entirety (wraps the
+ * line-based parsing functions)
+ *
+ * \param buf Buffer pointing to the full message
+ * \param blen Length of the buffer
+ * \param data Caller data to be available in callback
+ * \param dcpfunc Callback for processing each decoded body data chunk
+ *
+ * \return A pointer to the decoded MIME message, or NULL if the operation fails
+ */
+MimeDecEntity * MimeDecParseFullMsg(const char *buf, uint32_t blen, void *data,
+        int (*dcpfunc)(const uint8_t *chunk, uint32_t len,
+                MimeDecParseState *state)) {
+
+    int ret = MIME_DEC_OK;
+    char *remainPtr, *tok;
+    uint32_t tokLen;
+
+    MimeDecParseState *state = MimeDecInitParser(data, dcpfunc);
+    if (state == NULL) {
+        SCLogDebug("Error: MimeDecInitParser() function failed to create "
+                "state");
+        return NULL;
+    }
+
+    MimeDecEntity *msg = state->msg;
+
+    /* Parse each line one by one */
+    remainPtr = (char *) buf;
+    char *line = NULL;
+    do {
+        tok = GetLine(remainPtr, blen - (remainPtr - buf), &remainPtr, &tokLen);
+        if (tok != remainPtr) {
+
+            line = tok;
+
+            /* Parse the line */
+            ret = MimeDecParseLine(line, tokLen, state);
+            if (ret != MIME_DEC_OK) {
+                SCLogDebug("Error: MimeDecParseLine() function failed: %d",
+                        ret);
+                break;
+            }
+        }
+
+    } while (tok != remainPtr && remainPtr - buf < blen);
+
+    if (ret == MIME_DEC_OK) {
+        SCLogDebug("Message parser was successful");
+
+        /* Now complete message */
+        ret = MimeDecParseComplete(state);
+        if (ret != MIME_DEC_OK) {
+            SCLogDebug("Error: MimeDecParseComplete() function failed");
+        }
+    }
+
+    /* De-allocate memory for parser */
+    MimeDecDeInitParser(state);
+
+    if (ret != MIME_DEC_OK) {
+        MimeDecFreeEntity(msg);
+        msg = NULL;
+    }
+
+    return msg;
+}
+
+#ifdef UNITTESTS
+
+/* Helper body chunk callback function */
+static int TestDataChunkCallback(const uint8_t *chunk, uint32_t len,
+        MimeDecParseState *state) {
+
+    uint32_t *line_count = (uint32_t *) state->data;
+
+    if (state->body_begin) {
+        SCLogDebug("Body begin (len=%u)", len);
+    }
+
+    /* Add up the line counts */
+    if (len > 0) {
+
+        char *remainPtr;
+        char *tok;
+        uint32_t tokLen;
+
+        PrintChars(SC_LOG_DEBUG, (char *) "CHUNK", (char *) chunk, len);
+
+        /* Parse each line one by one */
+        remainPtr = (char *) chunk;
+        do {
+            tok = GetLine(remainPtr, len - (remainPtr - (char *) chunk),
+                    &remainPtr, &tokLen);
+            if (tok != NULL && tok != remainPtr) {
+                (*line_count)++;
+            }
+
+        } while (tok != NULL && tok != remainPtr &&
+                remainPtr - (char *) chunk < len);
+
+        SCLogDebug("line count (len=%u): %u", len, *line_count);
+    }
+
+    if (state->body_end) {
+        SCLogDebug("Body end (len=%u)", len);
+    }
+
+    return MIME_DEC_OK;
+}
+
+/* Test simple case of line counts */
+static int MimeDecParseLineTest01(void) {
+    int ret = MIME_DEC_OK;
+
+    uint32_t expected_count = 3;
+    uint32_t line_count = 0;
+
+    /* Init parser */
+    MimeDecParseState *state = MimeDecInitParser(&line_count,
+            TestDataChunkCallback);
+
+    char str[256];
+    strcpy(str, "From: Sender1");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "To: Recipient1");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "Content-Type: text/plain");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "A simple message line 1");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "A simple message line 2");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "A simple message line 3");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    /* Completed */
+    ret = MimeDecParseComplete(state);
+    if (ret != MIME_DEC_OK) {
+        return ret;
+    }
+
+    MimeDecEntity *msg = state->msg;
+    if (msg->next != NULL || msg->child != NULL) {
+        SCLogInfo("Error: Invalid sibling or child message");
+        return -1;
+    }
+
+    MimeDecFreeEntity(msg);
+
+    /* De Init parser */
+    MimeDecDeInitParser(state);
+
+    SCLogInfo("LINE COUNT FINISHED: %d", line_count);
+
+    if (expected_count != line_count) {
+        SCLogInfo("Error: Line count is invalid: expected - %d actual - %d",
+                expected_count, line_count);
+        return -1;
+    }
+
+    return ret;
+}
+
+/* Test simple case of EXE URL extraction */
+static int MimeDecParseLineTest02(void) {
+    int ret = MIME_DEC_OK;
+
+    uint32_t expected_count = 2;
+    uint32_t line_count = 0;
+
+    MimeDecGetConfig()->decode_base64 = 1;
+    MimeDecGetConfig()->decode_quoted_printable = 1;
+    MimeDecGetConfig()->extract_urls = 1;
+
+    /* Init parser */
+    MimeDecParseState *state = MimeDecInitParser(&line_count,
+            TestDataChunkCallback);
+
+    char str[256];
+    strcpy(str, "From: Sender1");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "To: Recipient1");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "Content-Type: text/plain");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "A simple message line 1");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    strcpy(str, "A simple message line 2 click on http://www.test.com/malware.exe?"
+            "hahah hopefully you click this link");
+    ret = MimeDecParseLine(str, strlen(str), state);
+
+    /* Completed */
+    ret = MimeDecParseComplete(state);
+    if (ret != MIME_DEC_OK) {
+        return ret;
+    }
+
+    MimeDecEntity *msg = state->msg;
+    if (msg->url_list == NULL || (msg->url_list != NULL &&
+            !(msg->url_list->url_flags & URL_IS_EXE))) {
+        SCLogInfo("Warning: Expected EXE URL not found");
+        return -1;
+    }
+
+    MimeDecFreeEntity(msg);
+
+    /* De Init parser */
+    MimeDecDeInitParser(state);
+
+    SCLogInfo("LINE COUNT FINISHED: %d", line_count);
+
+    if (expected_count != line_count) {
+        SCLogInfo("Warning: Line count is invalid: expected - %d actual - %d",
+                expected_count, line_count);
+        return -1;
+    }
+
+    return ret;
+}
+
+/* Test full message with linebreaks */
+static int MimeDecParseFullMsgTest01(void) {
+    int ret = MIME_DEC_OK;
+
+    uint32_t expected_count = 3;
+    uint32_t line_count = 0;
+
+    char msg[] = "From: Sender1\r\n"
+            "To: Recipient1\r\n"
+            "Content-Type: text/plain\r\n"
+            "\r\n"
+            "Line 1\r\n"
+            "Line 2\r\n"
+            "Line 3\r\n";
+
+    MimeDecEntity *entity = MimeDecParseFullMsg(msg, strlen(msg), &line_count,
+            TestDataChunkCallback);
+    if (entity == NULL) {
+        SCLogInfo("Warning: Message failed to parse");
+        return -1;
+    }
+
+    if (expected_count != line_count) {
+        SCLogInfo("Warning: Line count is invalid: expected - %d actual - %d",
+                expected_count, line_count);
+        return -1;
+    }
+
+    return ret;
+}
+
+static int MimeBase64DecodeTest01(void){
+
+    int ret = -1;
+
+    char *msg = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890@"
+            "#$%^&*()-=_+,./;'[]<>?:";
+    char *base64msg = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QU"
+            "VJTVFVWV1hZWjEyMzQ1Njc4OTBAIyQlXiYqKCktPV8rLC4vOydbXTw+Pzo=";
+
+    char *dst;
+    dst = (char *)malloc(strlen(msg)-1);
+
+    ret = DecodeBase64((uint8_t *)dst, base64msg, strlen(base64msg));
+
+    if(strcmp(dst, msg) == 0){
+        ret = 0;
+    }
+
+    return ret;
+}
+
+static int MimeIsExeURLTest01(void){
+    int ret = -1;
+    char *url1 = "http://www.google.com/";
+    char *url2 = "http://www.google.com/test.exe";
+
+    if(IsExeUrl(url1, strlen(url1)) != 0){
+        SCLogDebug("Debug: URL1 error");
+        goto end;
+    }
+    if(IsExeUrl(url2, strlen(url2)) != 1){
+        SCLogDebug("Debug: URL2 error");
+        goto end;
+    }
+    ret = 0;
+
+    end:
+
+    return ret;
+}
+
+static int MimeIsIpv4HostTest01(void) {
+
+    if(IsIpv4Host("192.168.1.1", 11) != 1) {
+        return 1;
+    }
+
+    if(IsIpv4Host("999.oogle.com", 14) != 0) {
+        return 1;
+    }
+
+    if(IsIpv4Host("0:0:0:0:0:0:0:0", 15) != 0) {
+        return 1;
+    }
+
+    if (IsIpv4Host("192.168.255.255", 15) != 1) {
+        return 1;
+    }
+
+    if (IsIpv4Host("192.168.255.255/testurl.html", 28) != 1) {
+        return 1;
+    }
+
+    if (IsIpv4Host("www.google.com", 14) != 0) {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int MimeIsIpv6HostTest01(void) {
+
+    if(IsIpv6Host("0:0:0:0:0:0:0:0", 19) != 1) {
+        return 1;
+    }
+
+    if(IsIpv6Host("0000:0000:0000:0000:0000:0000:0000:0000", 39) != 1) {
+        return 1;
+    }
+
+    if(IsIpv6Host("0:0:0:0:0:0:0:0", 19) != 1) {
+        return 1;
+    }
+
+    if(IsIpv6Host("192:168:1:1:0:0:0:0", 19) != 1) {
+        return 1;
+    }
+
+    if(IsIpv6Host("999.oogle.com", 14) != 0) {
+        return 1;
+    }
+
+    if (IsIpv6Host("192.168.255.255", 15) != 0) {
+        return 1;
+    }
+
+    if (IsIpv6Host("192.168.255.255/testurl.html", 28) != 0) {
+        return 1;
+    }
+
+    if (IsIpv6Host("www.google.com", 14) != 0) {
+        return 1;
+    }
+
+    return 0;
+}
+
+#endif /* UNITTESTS */
+
+void MimeDecRegisterTests(void) {
+#ifdef UNITTESTS
+    UtRegisterTest("MimeDecParseLineTest01", MimeDecParseLineTest01, 0);
+    UtRegisterTest("MimeDecParseLineTest02", MimeDecParseLineTest02, 0);
+    UtRegisterTest("MimeDecParseFullMsgTest01", MimeDecParseFullMsgTest01, 0);
+    UtRegisterTest("MimeBase64DecodeTest01", MimeBase64DecodeTest01, 0);
+    UtRegisterTest("MimeIsExeURLTest01", MimeIsExeURLTest01, 0);
+    UtRegisterTest("MimeIsIpv4HostTest01", MimeIsIpv4HostTest01, 0);
+    UtRegisterTest("MimeIsIpv6HostTest01", MimeIsIpv6HostTest01, 0);
+#endif /* UNITTESTS */
+}
diff --git a/src/mime-decode.h b/src/mime-decode.h
new file mode 100644 (file)
index 0000000..381907c
--- /dev/null
@@ -0,0 +1,240 @@
+/* Copyright (C) 2012 BAE Systems
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * 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
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author David Abarbanel <david.abarbanel@baesystems.com>
+ *
+ */
+
+#ifndef MIME_DECODE_H_
+#define MIME_DECODE_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "suricata.h"
+#include "util-base64.h"
+#include "util-debug.h"
+
+/* Header Flags */
+
+/* Content Flags */
+#define CTNT_IS_MSG           1
+#define CTNT_IS_ENV           2
+#define CTNT_IS_ENCAP         4
+#define CTNT_IS_BODYPART      8
+#define CTNT_IS_MULTIPART    16
+#define CTNT_IS_ATTACHMENT   32
+#define CTNT_IS_BASE64       64
+#define CTNT_IS_QP          128
+#define CTNT_IS_TEXT        256
+#define CTNT_IS_HTML        512
+
+/* URL Flags */
+#define URL_IS_IP4          1
+#define URL_IS_IP6          2
+#define URL_IS_EXE          4
+
+/* Anomaly Flags */
+#define ANOM_INVALID_BASE64      1  /* invalid base64 chars */
+#define ANOM_INVALID_QP          2  /* invalid qouted-printable chars */
+#define ANOM_LONG_HEADER_NAME    4  /* header is abnormally long */
+#define ANOM_LONG_HEADER_VALUE   8  /* header value is abnormally long
+                                     * (includes multi-line) */
+#define ANOM_LONG_LINE          16  /* Lines that exceed 998 octets */
+#define ANOM_LONG_ENC_LINE      32  /* Lines that exceed 76 octets */
+#define ANOM_MALFORMED_MSG      64  /* Misc msg format errors found */
+
+/* Pubicly exposed size constants */
+#define DATA_CHUNK_SIZE  3072  /* Should be divisible by 3 */
+#define LINEREM_SIZE      256
+
+/* Mime Parser Constants */
+#define HEADER_READY    0x01
+#define HEADER_STARTED  0x02
+#define HEADER_DONE     0x03
+#define BODY_STARTED    0x04
+#define BODY_DONE       0x05
+#define BODY_END_BOUND  0x06
+#define PARSE_DONE      0x07
+#define PARSE_ERROR     0x08
+
+/**
+ * \brief Mime Decoder Error Codes
+ */
+typedef enum MimeDecRetCode {
+    MIME_DEC_OK = 0,
+    MIME_DEC_MORE = 1,
+    MIME_DEC_ERR_DATA = -1,
+    MIME_DEC_ERR_MEM = -2,
+    MIME_DEC_ERR_PARSE = -3
+} MimeDecRetCode;
+
+/**
+ * \brief Structure for containing configuration options
+ *
+ */
+typedef struct MimeDecConfig {
+    int decode_base64;  /**< Decode base64 bodies */
+    int decode_quoted_printable;  /**< Decode quoted-printable bodies */
+    int extract_urls;  /**< Extract and store URLs in data structure */
+    uint32_t header_value_depth;  /**< Depth of which to store header values
+                                       (Default is 2000) */
+} MimeDecConfig;
+
+/**
+ * \brief This represents a header field name and associated value
+ */
+typedef struct MimeDecField {
+    char *name;  /**< Name of the header field */
+    uint32_t name_len;  /**< Length of the name */
+    char *value;  /**< Value of the header field */
+    uint32_t value_len;  /**< Length of the value */
+    struct MimeDecField *next;  /**< Pointer to next field */
+} MimeDecField;
+
+/**
+ * \brief This represents a URL value node in a linked list
+ *
+ * Since HTML can sometimes contain a high number of URLs, this
+ * structure only features the URL host name/IP or those that are
+ * pointing to an executable file (see url_flags to determine which).
+ */
+typedef struct MimeDecUrl {
+    char *url;  /**< String representation of full or partial URL */
+    uint32_t url_len;  /**< Length of the URL string */
+    uint32_t url_flags;  /**< Flags indicating type of URL */
+    uint32_t url_cnt;  /**< Count of URLs with same value */
+    struct MimeDecUrl *next;  /**< Pointer to next URL */
+} MimeDecUrl;
+
+/**
+ * \brief This represents the MIME Entity (or also top level message) in a
+ * child-sibling tree
+ */
+typedef struct MimeDecEntity {
+    MimeDecField *field_list;  /**< Pointer to list of header fields */
+    MimeDecUrl *url_list;  /**< Pointer to list of URLs */
+    uint32_t body_len;  /**< Length of body (prior to any decoding) */
+    uint32_t decoded_body_len;  /**< Length of body after decoding */
+    uint32_t header_flags; /**< Flags indicating header characteristics */
+    uint32_t ctnt_flags;  /**< Flags indicating type of content */
+    uint32_t anomaly_flags;  /**< Flags indicating an anomaly in the message */
+    char *filename;  /**< Name of file attachment */
+    uint32_t filename_len;  /**< Length of file attachment name */
+    char *ctnt_type;  /**< Quick access pointer to short-hand content type field */
+    uint32_t ctnt_type_len;  /**< Length of content type field value */
+    char *msg_id;  /**< Quick access pointer to message Id */
+    uint32_t msg_id_len;  /**< Quick access pointer to message Id */
+    struct MimeDecEntity *next;  /**< Pointer to list of sibling entities */
+    struct MimeDecEntity *child;  /**< Pointer to list of child entities */
+} MimeDecEntity;
+
+/**
+ * \brief Structure contains boundary and entity for the current node (entity)
+ * in the stack
+ *
+ */
+typedef struct MimeDecStackNode {
+    MimeDecEntity *data;  /**< Pointer to the entity data structure */
+    char *bdef;  /**< Copy of boundary definition for child entity */
+    uint32_t bdef_len;  /**< Boundary length for child entity */
+    int is_encap;  /**< Flag indicating entity is encapsulated in message */
+    struct MimeDecStackNode *next;  /**< Pointer to next item on the stack */
+} MimeDecStackNode;
+
+/**
+ * \brief Structure holds the top of the stack along with some free reusable nodes
+ *
+ */
+typedef struct MimeDecStack {
+    MimeDecStackNode *top;  /**< Pointer to the top of the stack */
+    MimeDecStackNode *free_nodes;  /**< Pointer to the list of free nodes */
+    uint32_t free_nodes_cnt;  /**< Count of free nodes in the list */
+} MimeDecStack;
+
+/**
+ * \brief Structure contains a list of value and lengths for robust data processing
+ *
+ */
+typedef struct DataValue {
+    char *value;  /**< Copy of data value */
+    uint32_t value_len;  /**< Length of data value */
+    struct DataValue *next;  /**< Pointer to next value in the list */
+} DataValue;
+
+/**
+ * \brief Structure contains the current state of the MIME parser
+ *
+ */
+typedef struct MimeDecParseState {
+    MimeDecEntity *msg;  /**< Pointer to the top-level message entity */
+    MimeDecStack *stack;  /**< Pointer to the top of the entity stack */
+    char *hname;  /**< Copy of the last known header name */
+    uint32_t hlen;  /**< Length of the last known header name */
+    DataValue *hvalue;  /**< Pointer to the incomplete header value list */
+    uint32_t hvlen; /**< Total length of value list */
+    char linerem[LINEREM_SIZE];  /**< Remainder from previous line (for URL extraction) */
+    uint16_t linerem_len;  /**< Length of remainder from previous line */
+    char bvremain[B64_BLOCK];  /**< Remainder from base64-decoded line */
+    uint8_t bvr_len;  /**< Length of remainder from base64-decoded line */
+    uint8_t data_chunk[DATA_CHUNK_SIZE];  /**< Buffer holding data chunk */
+    uint32_t data_chunk_len;  /**< Length of data chunk */
+    int found_child;  /**< Flag indicating a child entity was found */
+    int body_begin;  /**< Currently at beginning of body */
+    int body_end;  /**< Currently at end of body */
+    uint8_t state_flag;  /**<  Flag representing current state of parser */
+    void *data;  /**< Pointer to data specific to the caller */
+    int (*dataChunkProcessor) (const uint8_t *chunk, uint32_t len,
+            struct MimeDecParseState *state);  /**< Data chunk processing function callback */
+} MimeDecParseState;
+
+/* Config functions */
+void MimeDecSetConfig(MimeDecConfig *config);
+MimeDecConfig * MimeDecGetConfig(void);
+
+/* Memory functions */
+void MimeDecFreeEntity(MimeDecEntity *entity);
+void MimeDecFreeField(MimeDecField *field);
+void MimeDecFreeUrl(MimeDecUrl *url);
+
+/* List functions */
+MimeDecField * MimeDecAddField(MimeDecEntity *entity);
+MimeDecField * MimeDecFindField(const MimeDecEntity *entity, const char *name);
+MimeDecUrl * MimeDecAddUrl(MimeDecEntity *entity);
+MimeDecEntity * MimeDecAddEntity(MimeDecEntity *parent);
+
+/* Helper functions */
+MimeDecField * MimeDecFillField(MimeDecEntity *entity, const char *name,
+        uint32_t nlen, const char *value, uint32_t vlen, int copy_name_value);
+
+/* Parser functions */
+MimeDecParseState * MimeDecInitParser(void *data, int (*dcpfunc)(const uint8_t *chunk,
+        uint32_t len, MimeDecParseState *state));
+void MimeDecDeInitParser(MimeDecParseState *state);
+int MimeDecParseComplete(MimeDecParseState *state);
+int MimeDecParseLine(const char *line, const uint32_t len, MimeDecParseState *state);
+MimeDecEntity * MimeDecParseFullMsg(const char *buf, uint32_t blen, void *data,
+        int (*dcpfunc)(const uint8_t *chunk, uint32_t len, MimeDecParseState *state));
+
+/* Test functions */
+void MimeDecRegisterTests(void);
+
+#endif
index 5d756eb6d4c6dcc1587d7957e34a2f53f3cc9c7a..13cdd8ea1fe72d10d229e05361c0049388ba568b 100644 (file)
 
 #include "util-coredump-config.h"
 
+#include "mime-decode.h"
+
 #include "defrag.h"
 
 #include "runmodes.h"
diff --git a/src/util-base64.c b/src/util-base64.c
new file mode 100644 (file)
index 0000000..40194d3
--- /dev/null
@@ -0,0 +1,146 @@
+/* Copyright (C) 2007-2012 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * 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
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author David Abarbanel <david.abarbanel@baesystems.com>
+ *
+ */
+
+#include "util-base64.h"
+
+/* Constants */
+#define BASE64_TABLE_MAX  122
+
+/* Base64 character to index conversion table */
+/* Characters are mapped as "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" */
+static const int b64table[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, 62, -1, -1, -1, 63, 52, 53,
+        54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
+        -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
+        5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+        15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+        25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
+        29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+        39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+        49, 50, 51 };
+
+/**
+ * \brief Gets a base64-decoded value from an encoded character
+ *
+ * \param c The encoded character
+ *
+ * \return The decoded value (0 or above), or -1 if the parameter is invalid
+ */
+static inline int GetBase64Value(char c) {
+
+    int val = -1;
+
+    /* Pull from conversion table */
+    if (c <= BASE64_TABLE_MAX) {
+        val = b64table[(int) c];
+    }
+
+    return val;
+}
+
+/**
+ * \brief Decodes a 4-byte base64-encoded block into a 3-byte ascii-encoded block
+ *
+ * \param ascii the 3-byte ascii output block
+ * \param b64 the 4-byte base64 input block
+ *
+ * \return none
+ */
+static inline void DecodeBase64Block(uint8_t ascii[ASCII_BLOCK], uint8_t b64[B64_BLOCK]) {
+
+    ascii[0] = (uint8_t) (b64[0] << 2) | (b64[1] >> 4);
+    ascii[1] = (uint8_t) (b64[1] << 4) | (b64[2] >> 2);
+    ascii[2] = (uint8_t) (b64[2] << 6) | (b64[3]);
+}
+
+/**
+ * \brief Decodes a base64-encoded string buffer into an ascii-encoded byte buffer
+ *
+ * \param dest The destination byte buffer
+ * \param src The source string
+ * \param len The length of the source string
+ *
+ * \return Number of bytes decoded, or 0 if no data is decoded or it fails
+ */
+uint32_t DecodeBase64(uint8_t *dest, const char *src, uint32_t len) {
+
+    int val;
+    uint32_t padding = 0, numDecoded = 0, bbidx = 0, valid = 1, i;
+    uint8_t *dptr = dest;
+    uint8_t b64[B64_BLOCK];
+
+    /* Traverse through each alpha-numeric letter in the source array */
+    for(i = 0; i < len && src[i] != 0; i++) {
+
+        /* Get decimal representation */
+        val = GetBase64Value(src[i]);
+        if (val < 0) {
+
+            /* Invalid character found, so decoding fails */
+            if (src[i] != '=') {
+                valid = 0;
+                numDecoded = 0;
+                break;
+            }
+            padding++;
+        }
+
+        /* For each alpha-numeric letter in the source array, find the numeric
+         * value */
+        b64[bbidx++] = (val > 0 ? val : 0);
+
+        /* Decode every 4 base64 bytes into 3 ascii bytes */
+        if (bbidx == B64_BLOCK) {
+
+            /* For every 4 bytes, add 3 bytes but deduct the '=' padded blocks */
+            numDecoded += ASCII_BLOCK - (padding < B64_BLOCK ?
+                    padding : ASCII_BLOCK);
+
+            /* Decode base-64 block into ascii block and move pointer */
+            DecodeBase64Block(dptr, b64);
+            dptr += ASCII_BLOCK;
+
+            /* Reset base-64 block and index */
+            bbidx = 0;
+            padding = 0;
+        }
+    }
+
+    /* Finish remaining b64 bytes by padding */
+    if (valid && bbidx > 0) {
+
+        /* Decode remaining */
+        numDecoded += ASCII_BLOCK - (B64_BLOCK - bbidx);
+        DecodeBase64Block(dptr, b64);
+    }
+
+    if (numDecoded == 0) {
+        SCLogDebug("base64 decoding failed");
+    }
+
+    return numDecoded;
+}
diff --git a/src/util-base64.h b/src/util-base64.h
new file mode 100644 (file)
index 0000000..5e7c39c
--- /dev/null
@@ -0,0 +1,54 @@
+/* Copyright (C) 2007-2012 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * 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
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author David Abarbanel <david.abarbanel@baesystems.com>
+ *
+ */
+
+#ifndef __UTIL_BASE64_H_
+#define __UTIL_BASE64_H_
+
+#include "suricata-common.h"
+#include "threads.h"
+#include "debug.h"
+#include "decode.h"
+
+#include "detect.h"
+#include "detect-parse.h"
+
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-state.h"
+
+#include "flow.h"
+#include "flow-var.h"
+#include "flow-util.h"
+
+#include "util-debug.h"
+#include "util-spm-bm.h"
+
+/* Constants */
+#define ASCII_BLOCK         3
+#define B64_BLOCK           4
+
+/* Function prototypes */
+uint32_t DecodeBase64(uint8_t *dest, const char *src, uint32_t len);
+
+#endif
index 715c53e4cc49ec488c72638f744a2d1d9d821871..f1da99ba07080de489185c5b85ea9d7977d689db 100644 (file)
@@ -1294,6 +1294,23 @@ app-layer:
            #    double-decode-path: no
            #    double-decode-query: no
 
+# Configure SMTP-MIME Decoder enhancements
+smtp-mime:
+
+  # Decode MIME messages from SMTP transactions (may be resource intensive)
+  # This field supercedes all others because it turns the entire process on or off
+  decode-mime: no
+
+  # Decode MIME entity bodies (ie. base64, quoted-printable, etc.)
+  decode-base64: yes
+  decode-quoted-printable: yes
+
+  # Maximum bytes per header data value stored in the data structure (default is 2000)
+  header-value-depth: 2000
+
+  # Extract URLs and save in state data structure
+  extract-urls: yes
+
 # Profiling settings. Only effective if Suricata has been built with the
 # the --enable-profiling configure flag.
 #