# 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;)
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;)
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 \
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 \
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 },
};
{ "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
* 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;
} 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);
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;
SMTPSetMpmState();
+ SMTPConfigure();
+
#ifdef UNITTESTS
AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_SMTP, SMTPParserRegisterTests);
#endif
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)
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;
#define __APP_LAYER_SMTP_H__
#include "decode-events.h"
+#include "mime-decode.h"
enum {
SMTP_DECODER_EVENT_INVALID_REPLY,
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_ {
* 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);
#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"
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);
+}
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__ */
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;
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;
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;
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;
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);
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;
#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"
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
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\": \"");
#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"
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+");
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");
--- /dev/null
+/* 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 */
+}
--- /dev/null
+/* 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
#include "util-coredump-config.h"
+#include "mime-decode.h"
+
#include "defrag.h"
#include "runmodes.h"
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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
# 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.
#