+Pending - build 150
+
+-- pop and imap inspectors ported
+
15/04/28 - build 149
-- fixed build issue with extras
src/service_inspectors/dns/Makefile \
src/service_inspectors/ftp_telnet/Makefile \
src/service_inspectors/http_inspect/Makefile \
+src/service_inspectors/imap/Makefile \
src/service_inspectors/nhttp_inspect/Makefile \
+src/service_inspectors/pop/Makefile \
src/service_inspectors/rpc_decode/Makefile \
src/service_inspectors/ssh/Makefile \
src/service_inspectors/wizard/Makefile \
arp_spoof = { }
back_orifice = { }
dns = { }
+imap = { }
perf_monitor = { }
+pop = { }
port_scan = { }
rpc_decode = { }
ssh = { }
back_orifice
dns
ftp_telnet
+ imap
nhttp_inspect
+ pop
rpc_decode
ssh
wizard
service_inspectors/back_orifice/libback_orifice.a \
service_inspectors/dns/libdns.a \
service_inspectors/ftp_telnet/libftp_telnet.a \
+service_inspectors/imap/libimap.a \
service_inspectors/nhttp_inspect/libnhttp_inspect.a \
+service_inspectors/pop/libpop.a \
service_inspectors/rpc_decode/librpc_decode.a \
service_inspectors/ssh/libssh.a \
service_inspectors/wizard/libwizard.a
uint32_t email_hdrs_log_depth;
};
+/* State tracker for data */
+enum MimeDataState
+{
+ MIME_PAF_FINDING_BOUNDARY_STATE,
+ MIME_PAF_FOUND_BOUNDARY_STATE
+};
+
+/* State tracker for Boundary Signature */
+enum MimeBoundaryState
+{
+ MIME_PAF_BOUNDARY_UNKNOWN = 0, /* UNKNOWN */
+ MIME_PAF_BOUNDARY_LF, /* '\n' */
+ MIME_PAF_BOUNDARY_HYPEN_FIRST, /* First '-' */
+ MIME_PAF_BOUNDARY_HYPEN_SECOND /* Second '-' */
+};
+
+/* State tracker for end of pop/smtp command */
+enum DataEndState
+{
+ PAF_DATA_END_UNKNOWN, /* Start or UNKNOWN */
+ PAF_DATA_END_FIRST_CR, /* First '\r' */
+ PAF_DATA_END_FIRST_LF, /* First '\n' */
+ PAF_DATA_END_DOT, /* '.' */
+ PAF_DATA_END_SECOND_CR, /* Second '\r' */
+ PAF_DATA_END_SECOND_LF /* Second '\n' */
+};
+
#define MAX_MIME_BOUNDARY_LEN 70 /* Max length of boundary string, defined in RFC 2046 */
-struct MimeBoundary
+struct MimeDataPafInfo
{
- char boundary[2 + MAX_MIME_BOUNDARY_LEN + 1]; /* '--' + MIME boundary string + '\0' */
+ MimeDataState data_state;
+ char boundary[ MAX_MIME_BOUNDARY_LEN + 1]; /* MIME boundary string + '\0' */
int boundary_len;
- class SearchTool* boundary_search;
+ char* boundary_search;
+ MimeBoundaryState boundary_state;
+};
+
+typedef int (* Handle_header_line_func)(void* pkt, const uint8_t* ptr, const uint8_t* eol, int
+ max_header_len, void* mime_ssn);
+typedef int (* Normalize_data_func)(void* pkt, const uint8_t* ptr, const uint8_t* data_end);
+typedef void (* Decode_alert_func)(void* decode_state);
+typedef void (* Reset_state_func)(void *ssn);
+typedef bool (* Is_end_of_data_func)(void* ssn);
+
+struct MimeMethods
+{
+ Handle_header_line_func handle_header_line;
+ Normalize_data_func normalize_data;
+ Decode_alert_func decode_alert;
+ Reset_state_func reset_state;
+ Is_end_of_data_func is_end_of_data;
};
struct DecodeConfig
{
+ bool ignore_data;
int max_mime_mem;
int max_depth;
int b64_depth;
int state_flags;
int log_flags;
void* decode_state;
- MimeBoundary mime_boundary;
+ MimeDataPafInfo mime_boundary;
DecodeConfig* decode_conf;
MAIL_LogConfig* log_config;
MAIL_LogState* log_state;
void* decode_bkt;
void* log_mempool;
+ MimeMethods* methods;
};
#define FILE_API_VERSION5 2
typedef void (* Set_mime_log_config_defaults_func)(MAIL_LogConfig* log_config);
typedef int (* Parse_mime_decode_args_func)(DecodeConfig* decode_conf, char* arg, const
char* preproc_name);
+typedef void (* Check_decode_config_func)(DecodeConfig* decode_conf);
typedef const uint8_t* (* Process_mime_data_func)(void* packet, const uint8_t* start, const
uint8_t* end,
- const uint8_t* data_end_marker, uint8_t* data_end, MimeState* mime_ssn, bool upload);
+ MimeState* mime_ssn, bool upload, bool paf_enabled);
typedef void (* Free_mime_session_func)(MimeState* mime_ssn);
typedef bool (* Is_decoding_enabled_func)(DecodeConfig* decode_conf);
typedef bool (* Is_decoding_conf_changed_func)(DecodeConfig* configNext, DecodeConfig* config,
FilePosition* position);
typedef File_Verdict (* Get_file_verdict_func)(Flow* flow);
typedef void (* Render_block_verdict_func)(void* ctx, void* p);
+typedef bool (*Check_paf_abort_func)(void* ssn);
+typedef FilePosition (*GetFilePosition)(void *pkt);
+typedef void (*Reset_mime_paf_state_func)(MimeDataPafInfo *data_info);
+/* Process data boundary and flush each file based on boundary*/
+typedef bool (*Process_mime_paf_data_func)(MimeDataPafInfo *data_info, uint8_t data);
+typedef bool (*Check_data_end_func)(void *end_state, uint8_t data);
+
typedef struct _file_api
{
int version;
Set_mime_decode_config_defaults_func set_mime_decode_config_defauts;
Set_mime_log_config_defaults_func set_mime_log_config_defauts;
Parse_mime_decode_args_func parse_mime_decode_args;
+ Check_decode_config_func check_decode_config;
Process_mime_data_func process_mime_data;
Free_mime_session_func free_mime_session;
Is_decoding_enabled_func is_decoding_enabled;
Is_decoding_conf_changed_func is_decoding_conf_changed;
Is_mime_log_enabled_func is_mime_log_enabled;
Finalize_mime_position_func finalize_mime_position;
+ Reset_mime_paf_state_func reset_mime_paf_state;
+ Process_mime_paf_data_func process_mime_paf_data;
+ Check_data_end_func check_data_end;
+ Check_paf_abort_func check_paf_abort;
+ GetFilePosition get_file_position;
Get_file_verdict_func get_file_verdict;
Render_block_verdict_func render_block_verdict;
return ((position == SNORT_FILE_END) || (position == SNORT_FILE_FULL));
}
+static inline bool scanning_boundary(MimeDataPafInfo* mime_info, uint32_t boundary_start,
+ uint32_t* fp)
+{
+ if (boundary_start &&
+ mime_info->data_state == MIME_PAF_FOUND_BOUNDARY_STATE &&
+ mime_info->boundary_state != MIME_PAF_BOUNDARY_UNKNOWN)
+ {
+ *fp = boundary_start;
+ return true;
+ }
+
+ return false;
+}
+
#endif /* FILE_API_H */
return ret;
}
+void check_decode_config(DecodeConfig *currentConfig)
+{
+ int max = -1;
+
+ if (!currentConfig->max_mime_mem)
+ currentConfig->max_mime_mem = DEFAULT_MAX_MIME_MEM;
+
+ if(!currentConfig->b64_depth || !currentConfig->qp_depth
+ || !currentConfig->uu_depth || !currentConfig->bitenc_depth)
+ {
+ currentConfig->max_depth = MAX_DEPTH;
+ }
+ else
+ {
+ if(max < currentConfig->b64_depth)
+ max = currentConfig->b64_depth;
+
+ if(max < currentConfig->qp_depth)
+ max = currentConfig->qp_depth;
+
+ if(max < currentConfig->bitenc_depth)
+ max = currentConfig->bitenc_depth;
+
+ if(max < currentConfig->uu_depth)
+ max = currentConfig->uu_depth;
+
+ currentConfig->max_depth = max;
+ }
+ return;
+}
+
bool is_mime_log_enabled(MAIL_LogConfig* log_config);
bool is_decoding_conf_changed(DecodeConfig* configNext, DecodeConfig* config, const
char* preproc_name);
+void check_decode_config(DecodeConfig *currentConfig);
#endif
SearchTool* mime_hdr_search_mpse = nullptr;
MIMESearch mime_hdr_search[HDR_LAST];
MIMESearch* mime_current_search = NULL;
+static const char* boundary_str = "boundary=";
/* Extract the filename from the header */
static inline int extract_file_name(const char** start, int length, bool* disp_cont)
}
}
-/*
- * Initialize run-time boundary search, this should be called for every transaction
- */
-static int init_boundary_search(MimeBoundary* mime_boundary)
-{
- if (mime_boundary->boundary_search != NULL)
- delete mime_boundary->boundary_search;
-
- mime_boundary->boundary_search = new SearchTool;
-
- if (mime_boundary->boundary_search == NULL)
- return -1;
-
- mime_boundary->boundary_search->add(
- mime_boundary->boundary, mime_boundary->boundary_len, BOUNDARY);
-
- mime_boundary->boundary_search->prep();
-
- return 0;
-}
-
-/*
- * Update boundary search string when found
- */
-static int get_boundary(const char* data, int data_len, MimeBoundary* mime_boundary)
-{
- int result;
- int ovector[9];
- int ovecsize = 9;
- const char* boundary;
- int boundary_len;
- int ret;
- char* mime_boundary_str;
- int* mime_boundary_len;
-
- mime_boundary_str = &mime_boundary->boundary[0];
- mime_boundary_len = &mime_boundary->boundary_len;
-
- /* result will be the number of matches (including submatches) */
- result = pcre_exec(mime_boundary_pcre.re, mime_boundary_pcre.pe,
- data, data_len, 0, 0, ovector, ovecsize);
- if (result < 0)
- return -1;
-
- result = pcre_get_substring(data, ovector, result, 1, &boundary);
- if (result < 0)
- return -1;
-
- boundary_len = strlen(boundary);
- if (boundary_len > MAX_MIME_BOUNDARY_LEN)
- {
- /* XXX should we alert? breaking the law of RFC */
- boundary_len = MAX_MIME_BOUNDARY_LEN;
- }
-
- mime_boundary_str[0] = '-';
- mime_boundary_str[1] = '-';
- ret = SafeMemcpy(mime_boundary_str + 2, boundary, boundary_len,
- mime_boundary_str + 2, mime_boundary_str + 2 + MAX_MIME_BOUNDARY_LEN);
-
- pcre_free_substring(boundary);
-
- if (ret != SAFEMEM_SUCCESS)
- {
- return -1;
- }
-
- *mime_boundary_len = 2 + boundary_len;
- mime_boundary_str[*mime_boundary_len] = '\0';
-
- return 0;
-}
-
void get_mime_eol(const uint8_t* ptr, const uint8_t* end,
const uint8_t** eol, const uint8_t** eolm)
{
return 1;
}
-/*
- * Callback function for boundary search
- *
- * @param id id in array of search strings
- * @param index index in array of search strings
- * @param data buffer passed in to search function
- *
- * @return response
- * @retval 1 commands caller to stop searching
- */
-static int boundary_str_found(void* id, void*, int index, void*, void*)
-{
- int boundary_id = (int)(uintptr_t)id;
-
- mime_search_info.id = boundary_id;
- mime_search_info.index = index;
- //mime_search_info.length = mime_ssn->mime_boundary.boundary_len;
-
- return 1;
-}
-
static inline int is_decoding_enabled(DecodeConfig* pPolicyConfig)
{
if ( (pPolicyConfig->b64_depth > -1) || (pPolicyConfig->qp_depth > -1)
* @return i index into p->payload where we stopped looking at data
*/
static const uint8_t* process_mime_header(
- Packet*, const uint8_t* ptr,
+ Packet* p, const uint8_t* ptr,
const uint8_t* data_end_marker, MimeState* mime_ssn)
{
const uint8_t* eol = data_end_marker;
const uint8_t* cont_trans_enc = NULL;
const uint8_t* cont_disp = NULL;
int header_found;
- int ret;
const uint8_t* start_hdr;
start_hdr = ptr;
while (ptr < data_end_marker)
{
+ int header_name_len;
+ int max_header_name_len = 0;
get_mime_eol(ptr, data_end_marker, &eol, &eolm);
/* got a line with only end of line marker should signify end of header */
colon++;
}
+ /* Check for Exim 4.32 exploit where number of chars before colon is greater than 64 */
+ header_name_len = colon - ptr;
+ if ((mime_ssn->data_state != STATE_DATA_UNKNOWN) &&
+ (colon < eolm) && (header_name_len > MAX_HEADER_NAME_LEN))
+ {
+ max_header_name_len = header_name_len;
+ }
+
/* If the end on line marker and end of line are the same, assume
* header was truncated, so stay in data header state */
if ((eolm != eol) &&
mime_ssn->state_flags &= ~MIME_FLAG_DATA_HEADER_CONT;
}
+ if (mime_ssn->methods && mime_ssn->methods->handle_header_line)
+ {
+ int ret = mime_ssn->methods->handle_header_line(p, ptr, eol, max_header_name_len,
+ mime_ssn);
+ if (ret < 0)
+ return NULL;
+ else if (ret > 0)
+ {
+ /* assume we guessed wrong and are in the body */
+ mime_ssn->data_state = STATE_DATA_BODY;
+ mime_ssn->state_flags &=
+ ~(MIME_FLAG_FOLDING | MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_DATA_HEADER_CONT
+ | MIME_FLAG_IN_CONT_TRANS_ENC | MIME_FLAG_IN_CONT_DISP);
+ return ptr;
+ }
+ }
+
/* check for folding
* if char on next line is a space and not \n or \r\n, we are folding */
if ((eol < data_end_marker) && isspace((int)eol[0]) && (eol[0] != '\n'))
if ((mime_ssn->state_flags &
(MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_FOLDING)) == MIME_FLAG_IN_CONTENT_TYPE)
{
- if (mime_ssn->data_state != STATE_MIME_HEADER)
- {
- /* we got the full content-type header - look for boundary string */
- ret = get_boundary((const char*)content_type_ptr, eolm - content_type_ptr,
- &(mime_ssn->mime_boundary));
- if (ret != -1)
- {
- ret = init_boundary_search(&(mime_ssn->mime_boundary));
- if (ret != -1)
- {
- DEBUG_WRAP(DebugMessage(DEBUG_FILE, "Got mime boundary: %s\n",
- mime_ssn->mime_boundary.boundary); );
-
- mime_ssn->state_flags |= MIME_FLAG_GOT_BOUNDARY;
- }
- }
- }
- else if (!(mime_ssn->state_flags & MIME_FLAG_EMAIL_ATTACH))
+ if ((mime_ssn->data_state == STATE_MIME_HEADER) && !(mime_ssn->state_flags &
+ MIME_FLAG_EMAIL_ATTACH))
{
setup_decode((const char*)content_type_ptr, (eolm - content_type_ptr), false,
mime_ssn);
return ptr;
}
+/* Get the end of data body (excluding boundary)*/
+static const uint8_t* GetDataEnd(const uint8_t* data_start,
+ const uint8_t* data_end_marker)
+{
+ /* '\r\n' + '--' + MIME boundary string */
+ const int Max_Search = 4 + MAX_MIME_BOUNDARY_LEN;
+ uint8_t* start;
+ /*Exclude 2 bytes because either \r\n or '--' at the end */
+ uint8_t* end = (uint8_t*)data_end_marker - 2;
+
+ /*Search for the start of boundary, should be less than boundary length*/
+ if (end > data_start + Max_Search)
+ start = end - Max_Search;
+ else
+ start = (uint8_t*)data_start;
+
+ while (end > start)
+ {
+ if (*(--end) != '\n')
+ continue;
+
+ if ((*(end+1) == '-') && (*(end+2) == '-'))
+ {
+ if ((end > start) && (*(end-1) == '\r'))
+ return (end - 1);
+ else
+ return end;
+ }
+ break;
+ }
+ return data_end_marker;
+}
+
/*
* Handle DATA_BODY state
* @param packet standard Packet structure
* @param i index into p->payload buffer to start looking at data
* @return i index into p->payload where we stopped looking at data
*/
-static const uint8_t* process_mime_body(
- Packet*, const uint8_t* ptr,
- const uint8_t* data_end_marker, MimeState* mime_ssn)
+static const uint8_t* process_mime_body(Packet*, const uint8_t* ptr,
+ const uint8_t* data_end, MimeState* mime_ssn, bool is_data_end)
{
- int boundary_found = 0;
- const uint8_t* boundary_ptr = NULL;
- const uint8_t* attach_start = NULL;
- const uint8_t* attach_end = NULL;
Email_DecodeState* decode_state = (Email_DecodeState*)(mime_ssn->decode_state);
- if ( mime_ssn->state_flags & MIME_FLAG_EMAIL_ATTACH )
- attach_start = ptr;
- /* look for boundary */
- if (mime_ssn->state_flags & MIME_FLAG_GOT_BOUNDARY)
+ if (mime_ssn->state_flags & MIME_FLAG_EMAIL_ATTACH)
{
- boundary_found = mime_ssn->mime_boundary.boundary_search->find(
- (const char*)ptr, data_end_marker - ptr, boundary_str_found);
+ const uint8_t* attach_start = ptr;
+ const uint8_t* attach_end;
- mime_search_info.length = mime_ssn->mime_boundary.boundary_len;
-
- if (boundary_found > 0)
+ if (is_data_end )
{
- boundary_ptr = ptr + mime_search_info.index;
-
- /* should start at beginning of line */
- if ((boundary_ptr == ptr) || (*(boundary_ptr - 1) == '\n'))
- {
- const uint8_t* eol;
- const uint8_t* eolm;
- const uint8_t* tmp;
-
- if (mime_ssn->state_flags & MIME_FLAG_EMAIL_ATTACH )
- {
- attach_end = boundary_ptr-1;
- mime_ssn->state_flags &= ~MIME_FLAG_EMAIL_ATTACH;
- if (attach_start < attach_end)
- {
- if (EmailDecode(attach_start, attach_end, decode_state) < DECODE_SUCCESS )
- {
- // MIME_DecodeAlert();
- }
- }
- }
-
- /* Check for end boundary */
- tmp = boundary_ptr + mime_search_info.length;
- if (((tmp + 1) < data_end_marker) && (tmp[0] == '-') && (tmp[1] == '-'))
- {
- DEBUG_WRAP(DebugMessage(DEBUG_FILE, "Mime boundary end found: %s--\n",
- (char*)mime_ssn->mime_boundary.boundary); );
-
- /* no more MIME */
- mime_ssn->state_flags &= ~MIME_FLAG_GOT_BOUNDARY;
- mime_ssn->state_flags |= MIME_FLAG_MIME_END;
-
- /* free boundary search */
- delete mime_ssn->mime_boundary.boundary_search;
- mime_ssn->mime_boundary.boundary_search = NULL;
- }
- else
- {
- DEBUG_WRAP(DebugMessage(DEBUG_FILE, "Mime boundary found: %s\n",
- (char*)mime_ssn->mime_boundary.boundary); );
-
- mime_ssn->data_state = STATE_MIME_HEADER;
- }
-
- /* get end of line - there could be spaces after boundary before eol */
- get_mime_eol(boundary_ptr + mime_search_info.length, data_end_marker, &eol, &eolm);
-
- return eol;
- }
+ attach_end = GetDataEnd(ptr, data_end);
+ }
+ else
+ {
+ attach_end = data_end;
}
- }
- if ( mime_ssn->state_flags & MIME_FLAG_EMAIL_ATTACH )
- {
- attach_end = data_end_marker;
- if (attach_start < attach_end)
+ if ( attach_start < attach_end )
{
if (EmailDecode(attach_start, attach_end, decode_state) < DECODE_SUCCESS )
{
- // MIME_DecodeAlert();
+ if (mime_ssn->methods && mime_ssn->methods->decode_alert)
+ mime_ssn->methods->decode_alert(mime_ssn->decode_state);
}
}
}
- return data_end_marker;
+ if (is_data_end)
+ {
+ mime_ssn->data_state = STATE_MIME_HEADER;
+ mime_ssn->state_flags &= ~MIME_FLAG_EMAIL_ATTACH;
+ }
+
+ return data_end;
}
/*
{
Email_DecodeState* decode_state = (Email_DecodeState*)(mime_ssn->decode_state);
- if (mime_ssn->mime_boundary.boundary_search != NULL)
- {
- delete mime_ssn->mime_boundary.boundary_search;
- mime_ssn->mime_boundary.boundary_search = NULL;
- }
-
mime_ssn->data_state = STATE_DATA_INIT;
mime_ssn->state_flags = 0;
ClearEmailDecodeState(decode_state);
- memset(&mime_ssn->mime_boundary, 0, sizeof(MimeBoundary));
-}
-
-#if 0
-static inline FilePosition getFilePoistion(Packet* p)
-{
- FilePosition position = SNORT_FILE_POSITION_UNKNOWN;
-
- if (PacketHasFullPDU(p))
- position = SNORT_FILE_FULL;
- else if (PacketHasStartOfPDU(p))
- position = SNORT_FILE_START;
- else if (p->packet_flags & PKT_PDU_TAIL)
- position = SNORT_FILE_END;
- else if (file_api->get_file_processed_size(p->flow))
- position = SNORT_FILE_MIDDLE;
-
- return position;
}
-#endif
-
/*
* Main function for mime processing
*
* This should be called when mime data is available
*/
-const uint8_t* process_mime_data(void* packet, const uint8_t* start, const uint8_t* end,
- const uint8_t* data_end_marker, uint8_t* data_end, MimeState* mime_ssn, bool upload)
+const uint8_t* process_mime_data_paf(void* packet, const uint8_t* start, const uint8_t* end,
+ MimeState* mime_ssn, bool upload, FilePosition position)
{
Packet* p = (Packet*)packet;
- FilePosition position = SNORT_FILE_START;
+ bool done_data = false;
+
+ if (mime_ssn->methods && mime_ssn->methods->is_end_of_data)
+ {
+ done_data = mime_ssn->methods->is_end_of_data(p->flow);
+ }
/* if we've just entered the data state, check for a dot + end of line
* if found, no data */
{
/* if we're normalizing and not ignoring data copy data end marker
* and dot to alt buffer */
+ if (mime_ssn->methods && mime_ssn->methods->normalize_data)
+ {
+ if (mime_ssn->methods->normalize_data(p, start, end) < 0)
+ return NULL;
+ }
reset_mime_state(mime_ssn);
* in the body which seems more reasonable. */
}
- /* get end of data body
- * TODO check last bytes of previous packet to see if we had a partial
- * end of data */
- /* mime_current_search = &mime_data_end_search[0];
- data_end_found = mime_data_search_mpse->find(
- (const char *)start, end - start, search_str_found);
-
- if (data_end_found > 0)
- {
- data_end_marker = start + mime_search_info.index;
- data_end = data_end_marker + mime_search_info.length;
- }
- else
- {
- data_end_marker = data_end = end;
- }
- */
-
- set_file_data((uint8_t*)start, (data_end - start));
+ if ( mime_ssn->decode_conf && !mime_ssn->decode_conf->ignore_data)
+ set_file_data((uint8_t*)start, (end - start));
if ((mime_ssn->data_state == STATE_DATA_HEADER) ||
(mime_ssn->data_state == STATE_DATA_UNKNOWN))
}
#endif
- start = process_mime_header(p, start, data_end_marker, mime_ssn);
+ start = process_mime_header(p, start, end, mime_ssn);
if (start == NULL)
return NULL;
}
+ if (mime_ssn->methods && mime_ssn->methods->normalize_data)
+ {
+ if (mime_ssn->methods->normalize_data(p, start, end) < 0)
+ return NULL;
+ }
/* now we shouldn't have to worry about copying any data to the alt buffer
- * only mime headers if we find them and only if we're ignoring data */
- initFilePosition(&position, file_api->get_file_processed_size(p->flow));
+ * * only mime headers if we find them and only if we're ignoring data */
- while ((start != NULL) && (start < data_end_marker))
+ while ((start != NULL) && (start < end))
{
- /* multiple MIME attachments in one single packet.
- * Pipeline the MIME decoded data.*/
- if ( mime_ssn->state_flags & MIME_FLAG_MULTIPLE_EMAIL_ATTACH)
- {
- DecodeConfig* conf= mime_ssn->decode_conf;
- int detection_size = getDetectionSize(conf->b64_depth, conf->qp_depth,
- conf->uu_depth, conf->bitenc_depth, (Email_DecodeState*)(mime_ssn->decode_state) );
-
- set_file_data(((Email_DecodeState*)(mime_ssn->decode_state))->decodePtr,
- detection_size);
- /*Process file type/file signature*/
- if (file_api->file_process(p,
- (uint8_t*)((Email_DecodeState*)(mime_ssn->decode_state))->decodePtr,
- (uint16_t)((Email_DecodeState*)(mime_ssn->decode_state))->decoded_bytes, position,
- upload, false)
- && (isFileStart(position)) && mime_ssn->log_state)
- {
- file_api->set_file_name_from_log(&(mime_ssn->log_state->file_log), p->flow);
- }
- updateFilePosition(&position, file_api->get_file_processed_size(p->flow));
- get_data_bus().publish(PACKET_EVENT, p);
- mime_ssn->state_flags &= ~MIME_FLAG_MULTIPLE_EMAIL_ATTACH;
- ResetEmailDecodeState((Email_DecodeState*)(mime_ssn->decode_state));
- p->packet_flags |= PKT_ALLOW_MULTIPLE_DETECT;
- /* Reset the log count when a packet goes through detection multiple times */
- DetectReset();
- }
switch (mime_ssn->data_state)
{
case STATE_MIME_HEADER:
DEBUG_WRAP(DebugMessage(DEBUG_FILE, "MIME HEADER STATE ~~~~~~~~~~~~~~~~~~~~~~\n"); );
- start = process_mime_header(p, start, data_end_marker, mime_ssn);
- file_api->finalize_mime_position(p->flow, mime_ssn->decode_state, &position);
+ start = process_mime_header(p, start, end, mime_ssn);
break;
case STATE_DATA_BODY:
DEBUG_WRAP(DebugMessage(DEBUG_FILE, "DATA BODY STATE ~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
- start = process_mime_body(p, start, data_end_marker, mime_ssn);
+ start = process_mime_body(p, start, end, mime_ssn, isFileEnd(position) );
break;
}
}
if ((mime_ssn->decode_state) != NULL)
{
- if ((position == SNORT_FILE_START) || (position == SNORT_FILE_FULL))
+ DecodeConfig* conf= mime_ssn->decode_conf;
+ Email_DecodeState* ds = (Email_DecodeState*)(mime_ssn->decode_state);
+ if (conf)
{
- DecodeConfig* conf= mime_ssn->decode_conf;
int detection_size = getDetectionSize(conf->b64_depth, conf->qp_depth,
- conf->uu_depth, conf->bitenc_depth, (Email_DecodeState*)(mime_ssn->decode_state) );
- set_file_data(((Email_DecodeState*)(mime_ssn->decode_state))->decodePtr,
- detection_size);
- }
- else
- {
- set_file_data(((Email_DecodeState*)(mime_ssn->decode_state))->decodePtr, 0);
- }
- if ((data_end_marker != end)||(mime_ssn->state_flags & MIME_FLAG_MIME_END))
- {
- finalFilePosition(&position);
+ conf->uu_depth, conf->bitenc_depth, ds);
+ set_file_data(ds->decodePtr, (uint16_t)detection_size);
}
+
/*Process file type/file signature*/
- if (file_api->file_process(p,
- (uint8_t*)((Email_DecodeState*)(mime_ssn->decode_state))->decodePtr,
- (uint16_t)((Email_DecodeState*)(mime_ssn->decode_state))->decoded_bytes, position,
- upload, false)
+ if (file_api->file_process(p, (uint8_t*)ds->decodePtr,
+ (uint16_t)ds->decoded_bytes, position, upload, false)
&& (isFileStart(position))&& mime_ssn->log_state)
{
file_api->set_file_name_from_log(&(mime_ssn->log_state->file_log), p->flow);
}
/* if we got the data end reset state, otherwise we're probably still in the data
- * to expect more data in next packet */
- if (data_end_marker != end)
+ * * to expect more data in next packet */
+ if (done_data)
{
reset_mime_state(mime_ssn);
+ if (mime_ssn->methods && mime_ssn->methods->reset_state)
+ mime_ssn->methods->reset_state(p->flow);
}
- return data_end;
+ return end;
+}
+
+/*
+ * * Main function for mime processing
+ * *
+ * * This should be called when mime data is available
+ * */
+const uint8_t* process_mime_data(void* packet, const uint8_t* start,
+ const uint8_t* data_end_marker, MimeState* mime_ssn, bool upload, bool paf_enabled)
+{
+ const uint8_t* attach_start = start;
+ const uint8_t* attach_end;
+ Packet* p = (Packet*)packet;
+ FilePosition position = SNORT_FILE_START;
+
+ if (paf_enabled)
+ {
+ position = file_api->get_file_position(p);
+ process_mime_data_paf(packet, attach_start, data_end_marker,
+ mime_ssn, upload, position);
+ return data_end_marker;
+ }
+
+ initFilePosition(&position, file_api->get_file_processed_size(p->flow));
+ /* look for boundary */
+ while (start < data_end_marker)
+ {
+ /*Found the boundary, start processing data*/
+ if (process_mime_paf_data(&(mime_ssn->mime_boundary), *start))
+ {
+ attach_end = start;
+ finalFilePosition(&position);
+ process_mime_data_paf(packet, attach_start, attach_end,
+ mime_ssn, upload, position);
+ position = SNORT_FILE_START;
+ attach_start = start + 1;
+ }
+
+ start++;
+ }
+
+ if ((start == data_end_marker) && (attach_start < data_end_marker))
+ {
+ updateFilePosition(&position, file_api->get_file_processed_size(p->flow));
+ process_mime_data_paf(packet, attach_start, data_end_marker,
+ mime_ssn, upload, position);
+ }
+
+ return data_end_marker;
}
/*
*/
void init_mime(void)
{
- const char* error;
- int erroffset;
const MimeToken* tmp;
/* Header search */
}
mime_hdr_search_mpse->prep();
-
- /* create regex for finding boundary string - since it can be cut across multiple
- * lines, a straight search won't do. Shouldn't be too slow since it will most
- * likely only be acting on a small portion of data */
- mime_boundary_pcre.re = pcre_compile("boundary\\s*=\\s*\"?([^\\s\"]+)\"?",
- PCRE_CASELESS | PCRE_DOTALL,
- &error, &erroffset, NULL);
- if (mime_boundary_pcre.re == NULL)
- {
- FatalError("Failed to compile pcre regex for getting boundary "
- "in a multipart message: %s\n", error);
- }
-
- mime_boundary_pcre.pe = pcre_study(mime_boundary_pcre.re, 0, &error);
-
- if (error != NULL)
- {
- FatalError("Failed to study pcre regex for getting boundary "
- "in a multipart message: %s\n", error);
- }
}
/*
{
if (mime_hdr_search_mpse != NULL)
delete mime_hdr_search_mpse;
-
- if (mime_boundary_pcre.re )
- pcre_free(mime_boundary_pcre.re);
-
- if (mime_boundary_pcre.pe )
- pcre_free(mime_boundary_pcre.pe);
}
void free_mime_session(MimeState* mime_ssn)
if (!mime_ssn)
return;
- if (mime_ssn->mime_boundary.boundary_search != NULL)
- {
- delete mime_ssn->mime_boundary.boundary_search;
- mime_ssn->mime_boundary.boundary_search = NULL;
- }
-
if (mime_ssn->decode_state != NULL)
{
free(mime_ssn->decode_state);
finalFilePosition(position);
}
+/* Save the bounday string into paf state*/
+static inline bool store_boundary(MimeDataPafInfo* data_info, uint8_t val)
+{
+ if (!data_info->boundary_search)
+ {
+ if ((val == '.') || isspace (val))
+ data_info->boundary_search = (char*)&boundary_str[0];
+ return 0;
+ }
+
+ if ((*(data_info->boundary_search) == '='))
+ {
+ /*Skip spaces for the end of boundary*/
+ if (val == '=')
+ data_info->boundary_search++;
+ else if (!isspace(val))
+ data_info->boundary_search = NULL;
+ }
+ else if (*(data_info->boundary_search) == '\0')
+ {
+ /*get boundary string*/
+ if (isspace(val) || (val == '"'))
+ {
+ if (!data_info->boundary_len)
+ return 0;
+ else
+ {
+ /*Found boundary string*/
+ data_info->boundary[data_info->boundary_len] = '\0';
+ return 1;
+ }
+ }
+
+ if (data_info->boundary_len < (int)sizeof(data_info->boundary))
+ {
+ data_info->boundary[data_info->boundary_len++] = val;
+ }
+ else
+ {
+ /*Found boundary string*/
+ data_info->boundary[data_info->boundary_len -1] = '\0';
+ return 1;
+ }
+ }
+ else if ((val == *(data_info->boundary_search))
+ || (val == *(data_info->boundary_search) - 'a' + 'A'))
+ {
+ data_info->boundary_search++;
+ }
+ else
+ {
+ if ((val == '.') || isspace (val))
+ data_info->boundary_search = (char*)&boundary_str[0];
+ else
+ data_info->boundary_search = NULL;
+ }
+
+ return 0;
+}
+
+/* check the bounday string in the mail body*/
+static inline bool check_boundary(MimeDataPafInfo* data_info, uint8_t data)
+{
+ /* Search for boundary signature "--"*/
+ switch (data_info->boundary_state)
+ {
+ case MIME_PAF_BOUNDARY_UNKNOWN:
+ if (data == '\n')
+ data_info->boundary_state = MIME_PAF_BOUNDARY_LF;
+ break;
+
+ case MIME_PAF_BOUNDARY_LF:
+ if (data == '-')
+ data_info->boundary_state = MIME_PAF_BOUNDARY_HYPEN_FIRST;
+ else if (data != '\n')
+ data_info->boundary_state = MIME_PAF_BOUNDARY_UNKNOWN;
+ break;
+
+ case MIME_PAF_BOUNDARY_HYPEN_FIRST:
+ if (data == '-')
+ {
+ data_info->boundary_state = MIME_PAF_BOUNDARY_HYPEN_SECOND;
+ data_info->boundary_search = data_info->boundary;
+ }
+ else if (data == '\n')
+ data_info->boundary_state = MIME_PAF_BOUNDARY_LF;
+ else
+ data_info->boundary_state = MIME_PAF_BOUNDARY_UNKNOWN;
+ break;
+
+ case MIME_PAF_BOUNDARY_HYPEN_SECOND:
+ /* Compare with boundary string stored */
+ if (*(data_info->boundary_search) == '\0')
+ {
+ if (data == '\n')
+ {
+ /*reset boundary search etc.*/
+ data_info->boundary_state = MIME_PAF_BOUNDARY_UNKNOWN;
+ return 1;
+ }
+ else if ((data != '\r') && ((data != '-')))
+ data_info->boundary_state = MIME_PAF_BOUNDARY_UNKNOWN;
+ }
+ else if (*(data_info->boundary_search) == data)
+ data_info->boundary_search++;
+ else
+ data_info->boundary_state = MIME_PAF_BOUNDARY_UNKNOWN;
+
+ break;
+ }
+
+ return 0;
+}
+
+void reset_mime_paf_state(MimeDataPafInfo* data_info)
+{
+ data_info->boundary_search = NULL;
+ data_info->boundary_len = 0;
+ data_info->boundary[0] = '\0';
+ data_info->boundary_state = MIME_PAF_BOUNDARY_UNKNOWN;
+ data_info->data_state = MIME_PAF_FINDING_BOUNDARY_STATE;
+}
+
+/* Process data boundary and flush each file based on boundary*/
+bool process_mime_paf_data(MimeDataPafInfo* data_info, uint8_t data)
+{
+ switch (data_info->data_state)
+ {
+ case MIME_PAF_FINDING_BOUNDARY_STATE:
+ /* Search for boundary
+ Store bounday string in PAF state*/
+ if (store_boundary(data_info, data))
+ {
+ /* End of boundary, move to MIME_PAF_FOUND_BOUNDARY_STATE*/
+ DEBUG_WRAP(DebugMessage(DEBUG_FILE, "Create boudary string: %s\n",
+ data_info->boundary); );
+ data_info->data_state = MIME_PAF_FOUND_BOUNDARY_STATE;
+ }
+
+ break;
+ case MIME_PAF_FOUND_BOUNDARY_STATE:
+ if (check_boundary(data_info, data))
+ {
+ /* End of boundary, move to MIME_PAF_FOUND_BOUNDARY_STATE*/
+ DEBUG_WRAP(DebugMessage(DEBUG_FILE, "Found Boudary string: %s\n",
+ data_info->boundary); );
+ return 1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+bool check_data_end(void* data_end_state, uint8_t val)
+{
+ DataEndState state = *((DataEndState*)data_end_state);
+
+ switch (state)
+ {
+ case PAF_DATA_END_UNKNOWN:
+ if (val == '\n')
+ {
+ state = PAF_DATA_END_FIRST_LF;
+ }
+ break;
+
+ case PAF_DATA_END_FIRST_LF:
+ if (val == '.')
+ {
+ state = PAF_DATA_END_DOT;
+ }
+ else if ((val != '\r') && (val != '\n'))
+ {
+ state = PAF_DATA_END_UNKNOWN;
+ }
+ break;
+ case PAF_DATA_END_DOT:
+ if (val == '\n')
+ {
+ *((DataEndState*)data_end_state) = PAF_DATA_END_UNKNOWN;
+ return 1;
+ }
+ else if (val != '\r')
+ {
+ state = PAF_DATA_END_UNKNOWN;
+ }
+ break;
+
+ default:
+ state = PAF_DATA_END_UNKNOWN;
+ break;
+ }
+
+ *((DataEndState*)data_end_state) = state;
+ return 0;
+}
+
#define STATE_MIME_HEADER 3 /* MIME header section within data section */
#define STATE_DATA_UNKNOWN 4
+/* Maximum length of header chars before colon, based on Exim 4.32 exploit */
+#define MAX_HEADER_NAME_LEN 64
+
/* log flags */
#define MIME_FLAG_FILENAME_PRESENT 0x00000004
int set_log_buffers(MAIL_LogState** log_state, MAIL_LogConfig* conf);
void init_mime(void);
void free_mime(void);
-const uint8_t* process_mime_data(void* packet, const uint8_t* start, const uint8_t* end,
- const uint8_t* data_end_marker, uint8_t* data_end, MimeState* mime_ssn, bool upload);
+const uint8_t* process_mime_data(void *packet, const uint8_t *start, const uint8_t *end,
+ MimeState *mime_ssn, bool upload, bool paf_enabled);
void free_mime_session(MimeState* mime_ssn);
void finalize_mime_position(Flow* flow, void* decode_state, FilePosition* position);
+void reset_mime_paf_state(MimeDataPafInfo *data_info);
+/* Process data boundary and flush each file based on boundary*/
+bool process_mime_paf_data(MimeDataPafInfo *data_info, uint8_t val);
+bool check_data_end(void *end_state, uint8_t val);
+
#endif
static inline void finish_signature_lookup(FileContext* context, Flow* flow);
static File_Verdict get_file_verdict(Flow* flow);
static void render_block_verdict(void* ctx, void* p);
+static FilePosition get_file_position(void* pkt);
+static bool check_paf_abort(void* ssn);
FileAPI fileAPI;
FileAPI* file_api = NULL;
fileAPI.set_mime_decode_config_defauts = &set_mime_decode_config_defauts;
fileAPI.set_mime_log_config_defauts = &set_mime_log_config_defauts;
fileAPI.parse_mime_decode_args = &parse_mime_decode_args;
+ fileAPI.check_decode_config = &check_decode_config;
fileAPI.process_mime_data = &process_mime_data;
fileAPI.free_mime_session = &free_mime_session;
fileAPI.is_decoding_enabled = &is_decoding_enabled;
fileAPI.finalize_mime_position = &finalize_mime_position;
fileAPI.get_file_verdict = &get_file_verdict;
fileAPI.render_block_verdict = &render_block_verdict;
+ fileAPI.get_file_position = &get_file_position;
+ fileAPI.reset_mime_paf_state = &reset_mime_paf_state;
+ fileAPI.process_mime_paf_data = &process_mime_paf_data;
+ fileAPI.check_data_end = check_data_end;
+ fileAPI.check_paf_abort = &check_paf_abort;
+
file_api = &fileAPI;
init_mime();
FileFlowData::init();
}
}
+static FilePosition get_file_position(void* pkt)
+{
+ FilePosition position = SNORT_FILE_POSITION_UNKNOWN;
+ Packet* p = (Packet*)pkt;
+
+ if (PacketHasFullPDU(p))
+ position = SNORT_FILE_FULL;
+ else if (PacketHasStartOfPDU(p))
+ position = SNORT_FILE_START;
+ else if (p->packet_flags & PKT_PDU_TAIL)
+ position = SNORT_FILE_END;
+ else if (get_file_processed_size(p->flow))
+ position = SNORT_FILE_MIDDLE;
+
+ return position;
+}
+
+/*
+* This function determines whether we shold abort PAF. Will return
+* true if the current packet is midstream, or unestablisted session
+*
+* PARAMS:
+* uint32_t - session flags passed in to callback.
+*
+* RETURNS:
+* true - if we should abort paf
+* false - if we should continue using paf
+*/
+static bool check_paf_abort(void* ssn)
+{
+ uint32_t flags = stream.get_session_flags((Flow*)ssn);
+ if (flags & SSNFLAG_MIDSTREAM)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_FILE,
+ "Aborting PAF because of midstream pickup.\n"));
+ return true;
+ }
+ else if (!(flags & SSNFLAG_ESTABLISHED))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_FILE,
+ "Aborting PAF because of unestablished session.\n"));
+ return true;
+ }
+ return false;
+}
+
static uint32_t str_to_hash(uint8_t* str, int length)
{
uint32_t a,b,c,tmp;
j = 0;
}
}
- final(a,b,c);
+ final (a,b,c);
return c;
}
packet.h
packet_manager.h
protocol_ids.h
+ ssl.h
tcp.h
tcp_options.h
teredo.h
packet.cc
ip.cc
ipv4_options.cc
+ ssl.cc
tcp_options.cc
packet_manager.cc
)
packet.h \
packet_manager.h \
protocol_ids.h \
+ssl.h \
tcp.h \
tcp_options.h \
teredo.h \
packet.cc \
ip.cc \
ipv4_options.cc \
-tcp_options.cc
+tcp_options.cc \
+ssl.cc
AM_CXXFLAGS = @AM_CXXFLAGS@
--- /dev/null
+/*
+ * Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation. You may not use, modify or
+ * distribute this program under any other version of the GNU General
+ * Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+*/
+
+/*
+ * Adam Keeton
+ * ssl.c
+ * 10/09/07
+*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef WIN32
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include "ssl.h"
+#include "snort_types.h"
+#include "packet.h"
+
+#define THREE_BYTE_LEN(x) (x[2] | x[1] << 8 | x[0] << 16)
+
+#define SSL_ERROR_FLAGS \
+ (SSL_BOGUS_HS_DIR_FLAG | \
+ SSL_BAD_VER_FLAG | \
+ SSL_BAD_TYPE_FLAG | \
+ SSL_UNKNOWN_FLAG)
+
+#define SSL3_FIRST_BYTE 0x16
+#define SSL3_SECOND_BYTE 0x03
+#define SSL2_CHELLO_BYTE 0x01
+#define SSL2_SHELLO_BYTE 0x04
+
+/* very simplistic - just enough to say this is binary data - the rules will make a final
+* judgement. Should maybe add an option to the imap configuration to enable the
+* continuing of command inspection like ftptelnet. */
+bool IsTlsClientHello(const uint8_t* ptr, const uint8_t* end)
+{
+ /* at least 3 bytes of data - see below */
+ if ((end - ptr) < 3)
+ return false;
+
+ if ((ptr[0] == SSL3_FIRST_BYTE) && (ptr[1] == SSL3_SECOND_BYTE))
+ {
+ /* TLS v1 or SSLv3 */
+ return true;
+ }
+ else if ((ptr[2] == SSL2_CHELLO_BYTE) || (ptr[3] == SSL2_CHELLO_BYTE))
+ {
+ /* SSLv2 */
+ return true;
+ }
+
+ return false;
+}
+
+/* this may at least tell us whether the server accepted the client hello by the presence
+ * of binary data */
+
+bool IsTlsServerHello(const uint8_t* ptr, const uint8_t* end)
+{
+ /* at least 3 bytes of data - see below */
+ if ((end - ptr) < 3)
+ return false;
+
+ if ((ptr[0] == SSL3_FIRST_BYTE) && (ptr[1] == SSL3_SECOND_BYTE))
+ {
+ /* TLS v1 or SSLv3 */
+ return true;
+ }
+ else if (ptr[2] == SSL2_SHELLO_BYTE)
+ {
+ /* SSLv2 */
+ return true;
+ }
+
+ return false;
+}
+
+bool IsSSL(const uint8_t* ptr, int len, int pkt_flags)
+{
+ uint32_t ssl_flags = SSL_decode(ptr, len, pkt_flags, 0, NULL, NULL, 0);
+
+ if ((ssl_flags != SSL_ARG_ERROR_FLAG) &&
+ !(ssl_flags & SSL_ERROR_FLAGS))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+static uint32_t SSL_decode_version_v3(uint8_t major, uint8_t minor)
+{
+ /* Should only be called internally and by functions which have previously
+ * validated their arguments */
+
+ if (major == 3)
+ {
+ /* Minor version */
+ switch (minor)
+ {
+ case 0:
+ return SSL_VER_SSLV3_FLAG;
+ break;
+ case 1:
+ return SSL_VER_TLS10_FLAG;
+ break;
+ case 2:
+ return SSL_VER_TLS11_FLAG;
+ break;
+ case 3:
+ return SSL_VER_TLS12_FLAG;
+ break;
+ default:
+ return SSL_BAD_VER_FLAG;
+ }
+ }
+ /* This is a special case. Technically, major == 0, minor == 2 is SSLv2.
+ * But if this traffic was SSLv2, this code path would not have been
+ * exercised. */
+ else if (minor == 2)
+ {
+ return SSL_BAD_VER_FLAG;
+ }
+
+ return SSL_BAD_VER_FLAG;
+}
+
+static uint32_t SSL_decode_handshake_v3(const uint8_t* pkt, int size,
+ uint32_t cur_flags, uint32_t pkt_flags)
+{
+ SSL_handshake_t* handshake;
+ SSL_handshake_hello_t* hello;
+ uint32_t hs_len;
+ uint32_t retval = 0;
+
+ while (size > 0)
+ {
+ if (size < (int)SSL_HS_PAYLOAD_OFFSET)
+ {
+ retval |= SSL_TRUNCATED_FLAG;
+ break;
+ }
+
+ /* Note, handhshake version field is optional depending on type
+ Will recast to different type as necessary. */
+ handshake = (SSL_handshake_t*)pkt;
+ pkt += SSL_HS_PAYLOAD_OFFSET;
+ size -= SSL_HS_PAYLOAD_OFFSET;
+
+ /* The code below effectively implements the following:
+ * hs_len = 0;
+ * memcpy(&hs_len, handshake->length, 3);
+ * hs_len = ntohl(hs_len);
+ * It was written this way for performance */
+ hs_len = THREE_BYTE_LEN(handshake->length);
+
+ switch (handshake->type)
+ {
+ case SSL_HS_CHELLO:
+ if (pkt_flags & PKT_FROM_SERVER)
+ retval |= SSL_BOGUS_HS_DIR_FLAG;
+ else
+ retval |= SSL_CLIENT_HELLO_FLAG | SSL_CUR_CLIENT_HELLO_FLAG;
+
+ /* This type of record contains a version string.
+ Make sure there is room for a version. */
+ if (size < (int)sizeof(uint16_t))
+ {
+ retval |= SSL_TRUNCATED_FLAG;
+ break;
+ }
+
+ hello = (SSL_handshake_hello_t*)handshake;
+ retval |= SSL_decode_version_v3(hello->major, hello->minor);
+
+ /* Compare version of record with version of handshake */
+ if ((cur_flags & SSL_VERFLAGS) != (retval & SSL_VERFLAGS))
+ retval |= SSL_BAD_VER_FLAG;
+
+ break;
+
+ case SSL_HS_SHELLO:
+ if (pkt_flags & PKT_FROM_SERVER)
+ retval |= SSL_SERVER_HELLO_FLAG | SSL_CUR_SERVER_HELLO_FLAG;
+ else
+ retval |= SSL_BOGUS_HS_DIR_FLAG;
+
+ /* This type of record contains a version string. */
+ if (size < (int)sizeof(uint16_t))
+ {
+ retval |= SSL_TRUNCATED_FLAG;
+ break;
+ }
+
+ hello = (SSL_handshake_hello_t*)handshake;
+ retval |= SSL_decode_version_v3(hello->major, hello->minor);
+
+ /* Compare version of record with version of handshake */
+ if ((cur_flags & SSL_VERFLAGS) != (retval & SSL_VERFLAGS))
+ retval |= SSL_BAD_VER_FLAG;
+
+ break;
+
+ case SSL_HS_SHELLO_DONE:
+ if (pkt_flags & PKT_FROM_SERVER)
+ retval |= SSL_HS_SDONE_FLAG;
+ else
+ retval |= SSL_BOGUS_HS_DIR_FLAG;
+ break;
+
+ case SSL_HS_SKEYX:
+ if (pkt_flags & PKT_FROM_SERVER)
+ retval |= SSL_SERVER_KEYX_FLAG | SSL_CUR_SERVER_KEYX_FLAG;
+ else
+ retval |= SSL_BOGUS_HS_DIR_FLAG;
+ break;
+
+ case SSL_HS_CKEYX:
+ if (pkt_flags & PKT_FROM_SERVER)
+ retval |= SSL_BOGUS_HS_DIR_FLAG;
+ else
+ retval |= SSL_CLIENT_KEYX_FLAG | SSL_CUR_CLIENT_KEYX_FLAG;
+ break;
+
+ case SSL_HS_CERT:
+ retval |= SSL_CERTIFICATE_FLAG;
+ break;
+
+ /* The following types are not presently of interest */
+ case SSL_HS_HELLO_REQ:
+ case SSL_HS_CERT_VERIFY:
+ case SSL_HS_CERT_REQ:
+ case SSL_CERT_URL: /* RFC 3546 */
+ case SSL_CERT_STATUS: /* RFC 3546 */
+ break;
+
+ /* Will never see this since it's always encrypted */
+ case SSL_HS_FINISHED:
+ default:
+ /* Could be either a bad type or an encrypted handshake record
+ If the record is encrypted, the type will likely appear bogus. */
+ return SSL_POSSIBLE_HS_FLAG | SSL_POSSIBLY_ENC_FLAG;
+ }
+
+ size -= hs_len;
+ pkt += hs_len;
+ }
+
+ if (size < 0)
+ retval |= SSL_TRUNCATED_FLAG;
+
+ return retval;
+}
+
+static uint32_t SSL_decode_v3(const uint8_t* pkt, int size, uint32_t pkt_flags,
+ uint8_t* alert_flags, uint16_t* partial_rec_len, int max_hb_len)
+{
+ SSL_record_t* record;
+ uint32_t retval = 0;
+ uint16_t reclen;
+ uint16_t hblen;
+ int ccs = 0; /* Set if we see a Change Cipher Spec and reset after the next record */
+ SSL_heartbeat* heartbeat;
+ uint16_t psize = 0;
+
+ if ( size && partial_rec_len && *partial_rec_len > 0)
+ {
+ if (size < (int)(*partial_rec_len))
+ {
+ *partial_rec_len = *partial_rec_len - size;
+ retval |= SSL_TRUNCATED_FLAG;
+ return retval;
+ }
+ else
+ {
+ pkt += *partial_rec_len;
+ size -= *partial_rec_len;
+ }
+ *partial_rec_len = 0;
+ }
+
+ while (size > 0)
+ {
+ if (size < (int)SSL_REC_PAYLOAD_OFFSET)
+ {
+ retval |= SSL_TRUNCATED_FLAG;
+ break;
+ }
+
+ record = (SSL_record_t*)pkt;
+ pkt += SSL_REC_PAYLOAD_OFFSET;
+ size -= SSL_REC_PAYLOAD_OFFSET;
+
+ retval |= SSL_decode_version_v3(record->major, record->minor);
+
+ reclen = ntohs(record->length);
+
+ psize = (size < reclen) ? (reclen - size) : 0;
+
+ switch (record->type)
+ {
+ case SSL_CHANGE_CIPHER_REC:
+ retval |= SSL_CHANGE_CIPHER_FLAG;
+
+ /* If there is another record, mark it as possibly encrypted */
+ if ((size - (int)reclen) > 0)
+ retval |= SSL_POSSIBLY_ENC_FLAG;
+
+ ccs = 1;
+ break;
+
+ case SSL_ALERT_REC:
+ retval |= SSL_ALERT_FLAG;
+ ccs = 0;
+ break;
+ case SSL_HEARTBEAT_REC:
+ retval |= SSL_HEARTBEAT_SEEN;
+ ccs = 0;
+ if ((size < (int)sizeof(SSL_heartbeat)) || !max_hb_len || !alert_flags)
+ break;
+ heartbeat = (SSL_heartbeat*)pkt;
+ if ((heartbeat->type) == SSL_HEARTBEAT_REQUEST)
+ {
+ hblen = ntohs(heartbeat->length);
+ if (hblen > max_hb_len)
+ *alert_flags = SSL_HEARTBLEED_REQUEST;
+ }
+ else if ((heartbeat->type) == SSL_HEARTBEAT_RESPONSE)
+ {
+ if (reclen > max_hb_len )
+ *alert_flags = SSL_HEARTBLEED_RESPONSE;
+ }
+ else if (!(retval & SSL_BAD_VER_FLAG))
+ {
+ if (reclen > max_hb_len )
+ *alert_flags = SSL_HEARTBLEED_UNKNOWN;
+ }
+ break;
+
+ case SSL_HANDSHAKE_REC:
+ /* If the CHANGE_CIPHER_FLAG is set, the following handshake
+ * record should be encrypted */
+ if (!(retval & SSL_CHANGE_CIPHER_FLAG))
+ {
+ int hsize = size < (int)reclen ? size : (int)reclen;
+ retval |= SSL_decode_handshake_v3(pkt, hsize, retval, pkt_flags);
+ }
+ else if (ccs)
+ {
+ /* If we just got a change cipher spec, the next record must
+ * be a finished encrypted, which has no type, so it will fall
+ * into this default case, but it's good and we still need to
+ * see client and server app data */
+ retval |= SSL_HS_SDONE_FLAG;
+ }
+
+ ccs = 0;
+ break;
+
+ case SSL_APPLICATION_REC:
+ if (pkt_flags & PKT_FROM_SERVER)
+ retval |= SSL_SAPP_FLAG;
+ else
+ retval |= SSL_CAPP_FLAG;
+ ccs = 0;
+ break;
+
+ default:
+ retval |= SSL_BAD_TYPE_FLAG;
+ ccs = 0;
+ break;
+ }
+
+ size -= reclen;
+ pkt += reclen;
+ }
+
+ if (size < 0)
+ retval |= SSL_TRUNCATED_FLAG;
+
+ if (!(retval & SSL_VERFLAGS) || (retval & SSL_BAD_VER_FLAG))
+ {
+ psize = 0;
+ retval = retval | SSL_UNKNOWN_FLAG;
+ }
+
+ if (partial_rec_len)
+ *partial_rec_len = psize;
+
+ return retval;
+}
+
+// See RFCs 6101, 2246, 4346 and 5246 for SSL 3.0, TLS 1.0, 1.1 and 1.2 respectively
+// Appendix E. Backward Compatibility With SSL
+static inline bool SSL_v3_back_compat_v2(SSLv2_chello_t* chello)
+{
+ if ((chello->major == 3) && (chello->minor <= 3))
+ return true;
+ return false;
+}
+
+static uint32_t SSL_decode_v2(const uint8_t* pkt, int size, uint32_t pkt_flags)
+{
+ uint16_t reclen;
+ SSLv2_chello_t* chello;
+ SSLv2_shello_t* shello;
+ uint32_t retval = 0;
+ SSLv2_record_t* record = (SSLv2_record_t*)pkt;
+
+ while (size > 0)
+ {
+ if (size < SSL_V2_MIN_LEN)
+ {
+ retval |= SSL_TRUNCATED_FLAG | SSL_UNKNOWN_FLAG;
+ break;
+ }
+
+ /* Note: top bit has special meaning and is not included
+ * with the length */
+ reclen = ntohs(record->length) & 0x7fff;
+
+ switch (record->type)
+ {
+ case SSL_V2_CHELLO:
+ if (pkt_flags & PKT_FROM_SERVER)
+ retval |= SSL_BOGUS_HS_DIR_FLAG;
+ else
+ retval |= SSL_CLIENT_HELLO_FLAG | SSL_CUR_CLIENT_HELLO_FLAG;
+
+ if (size < (int)sizeof(SSLv2_chello_t))
+ {
+ retval |= SSL_TRUNCATED_FLAG | SSL_UNKNOWN_FLAG;
+ break;
+ }
+
+ chello = (SSLv2_chello_t*)pkt;
+
+ // Check for SSLv3/TLS backward compatibility
+ if (SSL_v3_back_compat_v2(chello))
+ retval |= SSL_V3_BACK_COMPAT_V2;
+ else if (chello->minor != 2)
+ retval |= SSL_BAD_VER_FLAG | SSL_UNKNOWN_FLAG;
+
+ break;
+
+ case SSL_V2_SHELLO:
+ if (pkt_flags & PKT_FROM_CLIENT)
+ retval |= SSL_BOGUS_HS_DIR_FLAG;
+ else
+ retval |= SSL_SERVER_HELLO_FLAG | SSL_CUR_SERVER_HELLO_FLAG;
+
+ if (size < (int)sizeof(SSLv2_shello_t))
+ {
+ retval |= SSL_TRUNCATED_FLAG | SSL_UNKNOWN_FLAG;
+ break;
+ }
+
+ shello = (SSLv2_shello_t*)pkt;
+
+ if (shello->minor != 2)
+ {
+ retval |= SSL_BAD_VER_FLAG | SSL_UNKNOWN_FLAG;
+ break;
+ }
+
+ break;
+
+ case SSL_V2_CKEY:
+ retval |= SSL_CLIENT_KEYX_FLAG | SSL_CUR_CLIENT_KEYX_FLAG;
+ break;
+
+ default:
+ return retval | SSL_BAD_TYPE_FLAG | SSL_UNKNOWN_FLAG;
+ }
+
+ size -= (reclen + 2);
+ pkt += (reclen + 2);
+ }
+
+ if (size < 0)
+ retval |= SSL_TRUNCATED_FLAG;
+
+ return retval | SSL_VER_SSLV2_FLAG;
+}
+
+uint32_t SSL_decode(const uint8_t* pkt, int size, uint32_t pkt_flags, uint32_t prev_flags,
+ uint8_t* alert_flags, uint16_t* partial_rec_len, int max_hb_len)
+{
+ SSL_record_t* record;
+ uint16_t reclen;
+ uint32_t datalen;
+
+ if (!pkt || !size)
+ return SSL_ARG_ERROR_FLAG;
+
+ if (size < (int)SSL_REC_PAYLOAD_OFFSET)
+ return SSL_TRUNCATED_FLAG | SSL_UNKNOWN_FLAG;
+
+ if (!( prev_flags & SSL_HS_SDONE_FLAG ))
+ {
+ /* Determine the protocol type. */
+
+ /* Only SSL v2 will have these bits set */
+ if (((pkt[0] & 0x80) || (pkt[0] & 0x40)) && !(partial_rec_len && *partial_rec_len))
+ return SSL_decode_v2(pkt, size, pkt_flags);
+
+ /* If this packet is only 5 bytes, it inconclusive whether its SSLv2 or TLS.
+ * If it is v2, it's definitely truncated anyway. By decoding a 5 byte
+ * SSLv2 as TLS,the decoder will either catch a bad type, bad version, or
+ * indicate that it is truncated. */
+ if (size == 5)
+ return SSL_decode_v3(pkt, size, pkt_flags, alert_flags, partial_rec_len, max_hb_len);
+
+ /* At this point, 'size' has to be > 5 */
+
+ /* If the field below contains a 2, it's either an SSLv2 client hello or
+ * it is TLS and is containing a server hello. */
+ if (pkt[4] == 2)
+ {
+ /* This could be a TLS server hello. Check for a TLS version string */
+ if (size >= 10)
+ {
+ if (pkt[9] == 3)
+ {
+ /* Saw a TLS version, but this could also be an SSHv2 length.
+ * If it is, check if a hypothetical TLS record-data length agress
+ * with its record length */
+ datalen = THREE_BYTE_LEN( (pkt+6) );
+
+ record = (SSL_record_t*)pkt;
+ reclen = ntohs(record->length);
+
+ /* If these lengths match, it's v3
+ Otherwise, it's v2 */
+ if (reclen - SSL_HS_PAYLOAD_OFFSET != datalen)
+ return SSL_decode_v2(pkt, size, pkt_flags);
+ }
+ }
+ }
+ /* Check if it's possibly a SSLv2 server-hello, in which case the version
+ * is at byte 7 */
+ else if (size >= 8 && pkt[7] == 2)
+ {
+ /* A version of '2' at byte 7 overlaps with TLS record-data length.
+ * Check if a hypothetical TLS record-data length agress with its
+ * record length */
+ datalen = THREE_BYTE_LEN( (pkt+6) );
+
+ record = (SSL_record_t*)pkt;
+ reclen = ntohs(record->length);
+
+ /* If these lengths match, it's v3
+ Otherwise, it's v2 */
+ if (reclen - SSL_HS_PAYLOAD_OFFSET != datalen)
+ return SSL_decode_v2(pkt, size, pkt_flags);
+ }
+ }
+
+ return SSL_decode_v3(pkt, size, pkt_flags, alert_flags, partial_rec_len, max_hb_len);
+}
+
--- /dev/null
+/*
+ * Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation. You may not use, modify or
+ * distribute this program under any other version of the GNU General
+ * Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+*/
+
+/*
+ * Adam Keeton
+ * ssl.h
+ * 10/09/07
+*/
+
+#ifndef SSL_H
+#define SSL_H
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#define SSL_NO_FLAG 0x00000000
+
+/* SSL record type flags */
+#define SSL_CHANGE_CIPHER_FLAG 0x00000001
+#define SSL_ALERT_FLAG 0x00000002
+#define SSL_POSSIBLE_HS_FLAG 0x00000004 /* For handshakes in TLSv3 that are encrypted */
+#define SSL_CLIENT_HELLO_FLAG 0x00000008
+#define SSL_SERVER_HELLO_FLAG 0x00000010
+#define SSL_CERTIFICATE_FLAG 0x00000020
+#define SSL_SERVER_KEYX_FLAG 0x00000040
+#define SSL_CLIENT_KEYX_FLAG 0x00000080
+#define SSL_CIPHER_SPEC_FLAG 0x00000100
+#define SSL_SFINISHED_FLAG 0x00000200
+#define SSL_SAPP_FLAG 0x00000400
+#define SSL_CAPP_FLAG 0x00000800
+#define SSL_HS_SDONE_FLAG 0x00001000
+#define SSL_HEARTBEAT_SEEN 0x00002000
+
+/* Misc state flag */
+#define SSL_POSSIBLY_ENC_FLAG 0x00004000
+
+/* Version flags */
+#define SSL_VER_SSLV2_FLAG 0x00008000
+#define SSL_VER_SSLV3_FLAG 0x00010000
+#define SSL_VER_TLS10_FLAG 0x00020000
+#define SSL_VER_TLS11_FLAG 0x00040000
+#define SSL_VER_TLS12_FLAG 0x00080000
+
+#define SSL_VERFLAGS \
+ (SSL_VER_SSLV2_FLAG | SSL_VER_SSLV3_FLAG | \
+ SSL_VER_TLS10_FLAG | SSL_VER_TLS11_FLAG | \
+ SSL_VER_TLS12_FLAG)
+
+#define SSL_V3_SERVER_HELLO(x) \
+ (((x) & SSL_CUR_SERVER_HELLO_FLAG) \
+ && ((x) & SSL_VERFLAGS) && (((x) & SSL_VERFLAGS) != SSL_VER_SSLV2_FLAG))
+
+/* For rule state matching. These are only set when presently valid,
+ * and do not stay set across packets. */
+#define SSL_CUR_CLIENT_HELLO_FLAG 0x00100000
+#define SSL_CUR_SERVER_HELLO_FLAG 0x00200000
+#define SSL_CUR_SERVER_KEYX_FLAG 0x00400000
+#define SSL_CUR_CLIENT_KEYX_FLAG 0x00800000
+#define SSL_ENCRYPTED_FLAG 0x01000000 /* Provided for external use */
+#define SSL_UNKNOWN_FLAG 0x02000000 /* Set when we decoded mostly garbage */
+
+#define SSL_STATEFLAGS \
+ (SSL_CUR_CLIENT_HELLO_FLAG | SSL_CUR_SERVER_HELLO_FLAG | \
+ SSL_CUR_SERVER_KEYX_FLAG | SSL_CUR_CLIENT_KEYX_FLAG | \
+ SSL_UNKNOWN_FLAG)
+
+// Flag set when a client uses SSLv3/TLS backward compatibility and sends a
+// SSLv2 Hello specifying an SSLv3/TLS version.
+#define SSL_V3_BACK_COMPAT_V2 0x04000000
+
+/* Error flags */
+#define SSL_BOGUS_HS_DIR_FLAG 0x08000000 /* Record type disagrees with direction */
+#define SSL_TRAILING_GARB_FLAG 0x10000000
+#define SSL_BAD_TYPE_FLAG 0x20000000
+#define SSL_BAD_VER_FLAG 0x40000000
+#define SSL_TRUNCATED_FLAG 0x80000000
+#define SSL_ARG_ERROR_FLAG 0x00000000 /* Note: overloaded with SSL_NO_FLAG */
+
+/* The following flags are not presently of interest:
+* #define SSL_CERT_URL_FLAG (RFC 3546)
+* #define SSL_CERT_STATUS_FLAG (RFC 3546)
+* #define SSL_CFINISHED_FLAG This is contained in encrypted data
+* #define SSL_HS_FINISHED_FLAG Ignored for our purposes
+*/
+
+/* The constants used below are from RFC 2246 */
+
+/* SSLv3 & TLS Record types */
+#define SSL_CHANGE_CIPHER_REC 20
+#define SSL_ALERT_REC 21
+#define SSL_HANDSHAKE_REC 22
+#define SSL_APPLICATION_REC 23
+#define SSL_HEARTBEAT_REC 24
+
+/* SSLv3 heartbeat types */
+#define SSL_HEARTBEAT_REQUEST 1
+#define SSL_HEARTBEAT_RESPONSE 2
+
+/* SSLv3 & TLS handshake types */
+#define SSL_HS_HELLO_REQ 0
+#define SSL_HS_CHELLO 1
+#define SSL_HS_SHELLO 2
+#define SSL_HS_CERT 11
+#define SSL_HS_SKEYX 12
+#define SSL_HS_CERT_REQ 13
+#define SSL_HS_SHELLO_DONE 14
+#define SSL_HS_CERT_VERIFY 15
+#define SSL_HS_CKEYX 16
+#define SSL_HS_FINISHED 20
+#define SSL_CERT_URL 21
+#define SSL_CERT_STATUS 22
+
+/* SSLv2 handshake types */
+#define SSL_V2_CHELLO 1
+#define SSL_V2_CKEY 2
+#define SSL_V2_SHELLO 4
+
+#ifdef WIN32
+#pragma pack(push,ssl_hdrs,1)
+#else
+#pragma pack(1)
+#endif
+
+typedef struct _SSL_record
+{
+ uint8_t type;
+ uint8_t major;
+ uint8_t minor;
+ uint16_t length;
+} SSL_record_t;
+
+#define SSL_REC_PAYLOAD_OFFSET (sizeof(uint8_t) * 5)
+
+typedef struct _SSL_heartbeat
+{
+ uint8_t type;
+ uint16_t length;
+} SSL_heartbeat;
+
+typedef struct _SSL_handshake
+{
+ uint8_t type;
+ uint8_t length[3];
+} SSL_handshake_t;
+
+typedef struct _SSL_handshake_hello
+{
+ uint8_t type;
+ uint8_t length[3];
+ uint8_t major;
+ uint8_t minor;
+} SSL_handshake_hello_t;
+
+// http://www.mozilla.org/projects/security/pki/nss/ssl/draft02.html
+typedef struct _SSLv2_record
+{
+ uint16_t length;
+ uint8_t type;
+} SSLv2_record_t;
+
+typedef struct _SSLv2_chello
+{
+ uint16_t length;
+ uint8_t type;
+ uint8_t major;
+ uint8_t minor;
+} SSLv2_chello_t;
+
+typedef struct _SSLv2_shello
+{
+ uint16_t length;
+ uint8_t type;
+ uint8_t ssnid;
+ uint8_t certtype;
+ uint8_t major;
+ uint8_t minor;
+} SSLv2_shello_t;
+
+#define SSL_V2_MIN_LEN 5
+
+#ifdef WIN32
+#pragma pack(pop,ssl_hdrs)
+#else
+#pragma pack()
+#endif
+
+#define SSL_HS_PAYLOAD_OFFSET (sizeof(uint8_t) * 4) /* Type and length fields */
+
+#define SSL_BAD_HS(x) (x & SSL_BOGUS_HS_DIR_FLAG)
+#define SSL_IS_HANDSHAKE(x) \
+ (x & (SSL_CLIENT_HELLO_FLAG | SSL_SERVER_HELLO_FLAG | \
+ SSL_CERTIFICATE_FLAG | SSL_SERVER_KEYX_FLAG | \
+ SSL_CLIENT_KEYX_FLAG | SSL_CIPHER_SPEC_FLAG))
+#define SSL_IS_CHELLO(x) (x & SSL_CLIENT_HELLO_FLAG)
+#define SSL_IS_SHELLO(x) (x & SSL_SERVER_HELLO_FLAG)
+#define SSL_IS_CKEYX(x) (x & SSL_CLIENT_KEYX_FLAG)
+#define SSL_IS_APP(x) ((x & SSL_SAPP_FLAG) || (x & SSL_CAPP_FLAG))
+#define SSL_IS_ALERT(x) (x & SSL_ALERT_FLAG)
+#define SSL_CLEAR_TEMPORARY_FLAGS(x) x &= ~SSL_STATEFLAGS;
+/* Verifies that the error flags haven't been triggered */
+#define SSL_IS_CLEAN(x) \
+ !(x & (SSL_BOGUS_HS_DIR_FLAG | SSL_TRUNCATED_FLAG | \
+ SSL_BAD_VER_FLAG | SSL_BAD_TYPE_FLAG | \
+ SSL_TRAILING_GARB_FLAG | SSL_UNKNOWN_FLAG))
+
+#define SSL_HEARTBLEED_REQUEST 0x01
+#define SSL_HEARTBLEED_RESPONSE 0x02
+#define SSL_HEARTBLEED_UNKNOWN 0x03
+
+uint32_t SSL_decode(const uint8_t* pkt, int size, uint32_t pktflags, uint32_t prevflags,
+ uint8_t* alert_flags, uint16_t* partial_rec_len, int hblen);
+bool IsTlsClientHello(const uint8_t* ptr, const uint8_t* end);
+bool IsTlsServerHello(const uint8_t* ptr, const uint8_t* end);
+bool IsSSL(const uint8_t* ptr, int len, int pkt_flags);
+
+#endif
+
add_subdirectory(ftp_telnet)
add_subdirectory(dns)
add_subdirectory(http_inspect)
+add_subdirectory(imap)
add_subdirectory(nhttp_inspect)
+add_subdirectory(pop)
add_subdirectory(rpc_decode)
add_subdirectory(ssh)
add_subdirectory(wizard)
back_orifice
ftp_telnet
dns
+ imap
nhttp_inspect
+ pop
rpc_decode
ssh
wizard
dns \
ftp_telnet \
http_inspect \
+imap \
nhttp_inspect \
+pop \
rpc_decode \
ssh \
wizard
if (hsd->mime_ssn)
{
uint8_t* end = ( uint8_t*)(p->data) + p->dsize;
- file_api->process_mime_data(p, p->data, end, end, end, hsd->mime_ssn, 1);
+ file_api->process_mime_data(p, p->data, end, hsd->mime_ssn, 1, false);
}
else if (file_api->get_file_processed_size(p->flow) >0)
{
end = (uint8_t*)(session->client.request.post_raw +
session->client.request.post_raw_size);
- file_api->process_mime_data(p, start, end, end, end, hsd->mime_ssn, 1);
+ file_api->process_mime_data(p, start, end, hsd->mime_ssn, 1, false);
}
else
{
if (hsd->mime_ssn)
{
uint8_t* end = ( uint8_t*)(p->data) + p->dsize;
- file_api->process_mime_data(p, p->data, end, end, end, hsd->mime_ssn, 1);
+ file_api->process_mime_data(p, p->data, end, hsd->mime_ssn, 1, false);
}
else if (file_api->get_file_processed_size(p->flow) >0)
{
--- /dev/null
+
+set( FILE_LIST
+ imap.cc
+ imap.h
+ imap_paf.cc
+ imap_paf.h
+ imap_config.h
+ imap_module.cc
+ imap_module.h
+)
+
+if (STATIC_INSPECTORS)
+ add_library( imap STATIC ${FILE_LIST})
+
+else (STATIC_INSPECTORS)
+ add_shared_library(imap inspectors ${FILE_LIST})
+
+endif (STATIC_INSPECTORS)
--- /dev/null
+AUTOMAKE_OPTIONS=foreign no-dependencies
+
+file_list = \
+imap_config.h \
+imap.cc \
+imap.h \
+imap_paf.cc \
+imap_paf.h \
+imap_module.cc \
+imap_module.h
+
+if STATIC_INSPECTORS
+noinst_LIBRARIES = libimap.a
+libimap_a_SOURCES = $(file_list)
+else
+shlibdir = $(pkglibdir)/inspectors
+shlib_LTLIBRARIES = libimap.la
+libimap_la_CXXFLAGS = $(AM_CXXFLAGS) -DBUILDING_SO
+libimap_la_LDFLAGS = -export-dynamic -shared
+libimap_la_SOURCES = $(file_list)
+endif
+
+AM_CXXFLAGS = @AM_CXXFLAGS@
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+
+/*
+ * IMAP preprocessor
+ * Author: Bhagyashree Bantwal <bbantwal@cisco.com>
+ *
+ *
+ */
+#include "imap.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "snort_types.h"
+#include "snort_debug.h"
+
+#include "imap_module.h"
+#include "profiler.h"
+#include "stream/stream_api.h"
+#include "file_api/file_api.h"
+#include "parser.h"
+#include "framework/inspector.h"
+#include "utils/sfsnprintfappend.h"
+#include "target_based/sftarget_protocol_reference.h"
+#include "imap_paf.h"
+#include "search_engines/search_tool.h"
+#include "sf_email_attach_decode.h"
+#include "protocols/ssl.h"
+
+THREAD_LOCAL ProfileStats imapPerfStats;
+THREAD_LOCAL SimpleStats imapstats;
+
+IMAPToken imap_known_cmds[] =
+{
+ { "APPEND", 6, CMD_APPEND },
+ { "AUTHENTICATE", 12, CMD_AUTHENTICATE },
+ { "CAPABILITY", 10, CMD_CAPABILITY },
+ { "CHECK", 5, CMD_CHECK },
+ { "CLOSE", 5, CMD_CLOSE },
+ { "COMPARATOR", 10, CMD_COMPARATOR },
+ { "COMPRESS", 8, CMD_COMPRESS },
+ { "CONVERSIONS", 11, CMD_CONVERSIONS },
+ { "COPY", 4, CMD_COPY },
+ { "CREATE", 6, CMD_CREATE },
+ { "DELETE", 6, CMD_DELETE },
+ { "DELETEACL", 9, CMD_DELETEACL },
+ { "DONE", 4, CMD_DONE },
+ { "EXAMINE", 7, CMD_EXAMINE },
+ { "EXPUNGE", 7, CMD_EXPUNGE },
+ { "FETCH", 5, CMD_FETCH },
+ { "GETACL", 6, CMD_GETACL },
+ { "GETMETADATA", 11, CMD_GETMETADATA },
+ { "GETQUOTA", 8, CMD_GETQUOTA },
+ { "GETQUOTAROOT", 12, CMD_GETQUOTAROOT },
+ { "IDLE", 4, CMD_IDLE },
+ { "LIST", 4, CMD_LIST },
+ { "LISTRIGHTS", 10, CMD_LISTRIGHTS },
+ { "LOGIN", 5, CMD_LOGIN },
+ { "LOGOUT", 6, CMD_LOGOUT },
+ { "LSUB", 4, CMD_LSUB },
+ { "MYRIGHTS", 8, CMD_MYRIGHTS },
+ { "NOOP", 4, CMD_NOOP },
+ { "NOTIFY", 6, CMD_NOTIFY },
+ { "RENAME", 6, CMD_RENAME },
+ { "SEARCH", 6, CMD_SEARCH },
+ { "SELECT", 6, CMD_SELECT },
+ { "SETACL", 6, CMD_SETACL },
+ { "SETMETADATA", 11, CMD_SETMETADATA },
+ { "SETQUOTA", 8, CMD_SETQUOTA },
+ { "SORT", 4, CMD_SORT },
+ { "STARTTLS", 8, CMD_STARTTLS },
+ { "STATUS", 6, CMD_STATUS },
+ { "STORE", 5, CMD_STORE },
+ { "SUBSCRIBE", 9, CMD_SUBSCRIBE },
+ { "THREAD", 6, CMD_THREAD },
+ { "UID", 3, CMD_UID },
+ { "UNSELECT", 8, CMD_UNSELECT },
+ { "UNSUBSCRIBE", 11, CMD_UNSUBSCRIBE },
+ { "X", 1, CMD_X },
+ { NULL, 0, 0 }
+};
+
+IMAPToken imap_resps[] =
+{
+ { "CAPABILITY", 10, RESP_CAPABILITY },
+ { "LIST", 4, RESP_LIST },
+ { "LSUB", 4, RESP_LSUB },
+ { "STATUS", 6, RESP_STATUS },
+ { "SEARCH", 6, RESP_SEARCH },
+ { "FLAGS", 5, RESP_FLAGS },
+ { "EXISTS", 6, RESP_EXISTS },
+ { "RECENT", 6, RESP_RECENT },
+ { "EXPUNGE", 7, RESP_EXPUNGE },
+ { "FETCH", 5, RESP_FETCH },
+ { "BAD", 3, RESP_BAD },
+ { "BYE", 3, RESP_BYE },
+ { "NO", 2, RESP_NO },
+ { "OK", 2, RESP_OK },
+ { "PREAUTH", 7, RESP_PREAUTH },
+ { "ENVELOPE", 8, RESP_ENVELOPE },
+ { "UID", 3, RESP_UID },
+ { NULL, 0, 0 }
+};
+
+SearchTool* imap_resp_search_mpse = nullptr;
+SearchTool* imap_cmd_search_mpse = nullptr;
+
+IMAPSearch imap_resp_search[RESP_LAST];
+IMAPSearch imap_cmd_search[CMD_LAST];
+THREAD_LOCAL const IMAPSearch* imap_current_search = NULL;
+THREAD_LOCAL IMAPSearchInfo imap_search_info;
+
+static void snort_imap(IMAP_PROTO_CONF* GlobalConf, Packet* p);
+static void IMAP_ResetState(void*);
+void IMAP_DecodeAlert(void* ds);
+
+MimeMethods imap_mime_methods = { NULL, NULL, IMAP_DecodeAlert, IMAP_ResetState, imap_is_data_end };
+
+unsigned ImapFlowData::flow_id = 0;
+static IMAPData* get_session_data(Flow* flow)
+{
+ ImapFlowData* fd = (ImapFlowData*)flow->get_application_data(
+ ImapFlowData::flow_id);
+
+ return fd ? &fd->session : NULL;
+}
+
+IMAPData* SetNewIMAPData(IMAP_PROTO_CONF* config, Packet* p)
+{
+ IMAPData* imap_ssn;
+ ImapFlowData* fd = new ImapFlowData;
+
+ p->flow->set_application_data(fd);
+ imap_ssn = &fd->session;
+
+ imap_ssn->mime_ssn.log_config = &(config->log_config);
+ imap_ssn->mime_ssn.decode_conf = &(config->decode_conf);
+ imap_ssn->mime_ssn.methods = &(imap_mime_methods);
+ if (file_api->set_log_buffers(&(imap_ssn->mime_ssn.log_state), &(config->log_config)) < 0)
+ {
+ return NULL;
+ }
+
+ if (p->packet_flags & SSNFLAG_MIDSTREAM)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Got midstream packet - "
+ "setting state to unknown\n"); );
+ imap_ssn->state = STATE_UNKNOWN;
+ }
+
+ imap_ssn->body_read = imap_ssn->body_len = 0;
+
+ return imap_ssn;
+}
+
+void IMAP_DecodeAlert(void* ds)
+{
+ Email_DecodeState* decode_state = (Email_DecodeState*)ds;
+ switch ( decode_state->decode_type )
+ {
+ case DECODE_B64:
+ SnortEventqAdd(GID_IMAP, IMAP_B64_DECODING_FAILED);
+ break;
+ case DECODE_QP:
+ SnortEventqAdd(GID_IMAP, IMAP_QP_DECODING_FAILED);
+ break;
+ case DECODE_UU:
+ SnortEventqAdd(GID_IMAP, IMAP_UU_DECODING_FAILED);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void IMAP_SearchInit(void)
+{
+ const IMAPToken* tmp;
+ imap_cmd_search_mpse = new SearchTool();
+ if (imap_cmd_search_mpse == NULL)
+ {
+ FatalError("Could not allocate memory for IMAP Command search.\n");
+ }
+ for (tmp = &imap_known_cmds[0]; tmp->name != NULL; tmp++)
+ {
+ imap_cmd_search[tmp->search_id].name = tmp->name;
+ imap_cmd_search[tmp->search_id].name_len = tmp->name_len;
+ imap_cmd_search_mpse->add(tmp->name, tmp->name_len, tmp->search_id);
+ }
+ imap_cmd_search_mpse->prep();
+
+ imap_resp_search_mpse = new SearchTool();
+ if (imap_resp_search_mpse == NULL)
+ {
+ FatalError("Could not allocate memory for IMAP Response search.\n");
+ }
+ for (tmp = &imap_resps[0]; tmp->name != NULL; tmp++)
+ {
+ imap_resp_search[tmp->search_id].name = tmp->name;
+ imap_resp_search[tmp->search_id].name_len = tmp->name_len;
+ imap_resp_search_mpse->add(tmp->name, tmp->name_len, tmp->search_id);
+ }
+ imap_resp_search_mpse->prep();
+}
+
+void IMAP_SearchFree(void)
+{
+ if (imap_cmd_search_mpse != NULL)
+ delete imap_cmd_search_mpse;
+
+ if (imap_resp_search_mpse != NULL)
+ delete imap_resp_search_mpse;
+}
+
+/*
+* Reset IMAP session state
+*
+* @param none
+*
+* @return none
+*/
+static void IMAP_ResetState(void* ssn)
+{
+ IMAPData* imap_ssn = get_session_data((Flow*)ssn);
+ imap_ssn->state = STATE_COMMAND;
+ imap_ssn->state_flags = 0;
+ imap_ssn->body_read = imap_ssn->body_len = 0;
+}
+
+void IMAP_GetEOL(const uint8_t* ptr, const uint8_t* end,
+ const uint8_t** eol, const uint8_t** eolm)
+{
+ const uint8_t* tmp_eol;
+ const uint8_t* tmp_eolm;
+
+ /* XXX maybe should fatal error here since none of these
+ * * pointers should be NULL */
+ if (ptr == NULL || end == NULL || eol == NULL || eolm == NULL)
+ return;
+
+ tmp_eol = (uint8_t*)memchr(ptr, '\n', end - ptr);
+ if (tmp_eol == NULL)
+ {
+ tmp_eol = end;
+ tmp_eolm = end;
+ }
+ else
+ {
+ /* end of line marker (eolm) should point to marker and
+ * * end of line (eol) should point to end of marker */
+ if ((tmp_eol > ptr) && (*(tmp_eol - 1) == '\r'))
+ {
+ tmp_eolm = tmp_eol - 1;
+ }
+ else
+ {
+ tmp_eolm = tmp_eol;
+ }
+
+ /* move past newline */
+ tmp_eol++;
+ }
+
+ *eol = tmp_eol;
+ *eolm = tmp_eolm;
+}
+
+static void PrintImapConf(IMAP_PROTO_CONF* config)
+{
+ if (config == NULL)
+ return;
+
+ LogMessage("IMAP config: \n");
+
+ if (config->decode_conf.b64_depth > -1)
+ {
+ switch (config->decode_conf.b64_depth)
+ {
+ case 0:
+ LogMessage(" Base64 Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Base64 Decoding Depth: %d\n", config->decode_conf.b64_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Base64 Decoding: %s\n", "Disabled");
+
+ if (config->decode_conf.qp_depth > -1)
+ {
+ switch (config->decode_conf.qp_depth)
+ {
+ case 0:
+ LogMessage(" Quoted-Printable Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Quoted-Printable Decoding Depth: %d\n", config->decode_conf.qp_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Quoted-Printable Decoding: %s\n", "Disabled");
+ if (config->decode_conf.uu_depth > -1)
+ {
+ switch (config->decode_conf.uu_depth)
+ {
+ case 0:
+ LogMessage(" Unix-to-Unix Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Unix-to-Unix Decoding Depth: %d\n", config->decode_conf.uu_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Unix-to-Unix Decoding: %s\n", "Disabled");
+
+ if (config->decode_conf.bitenc_depth > -1)
+ {
+ switch (config->decode_conf.bitenc_depth)
+ {
+ case 0:
+ LogMessage(" Non-Encoded MIME attachment Extraction Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Non-Encoded MIME attachment Extraction Depth: %d\n",
+ config->decode_conf.bitenc_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Non-Encoded MIME attachment Extraction: %s\n", "Disabled");
+
+ LogMessage("\n");
+}
+
+static inline int InspectPacket(Packet* p)
+{
+ return PacketHasPAFPayload(p);
+}
+
+static int IMAP_Setup(Packet* p, IMAPData* ssn)
+{
+ int pkt_dir;
+
+ /* Get the direction of the packet. */
+ if ( p->packet_flags & PKT_FROM_SERVER )
+ pkt_dir = IMAP_PKT_FROM_SERVER;
+ else
+ pkt_dir = IMAP_PKT_FROM_CLIENT;
+
+ if (!(ssn->session_flags & IMAP_FLAG_CHECK_SSL))
+ ssn->session_flags |= IMAP_FLAG_CHECK_SSL;
+ /* Check to see if there is a reassembly gap. If so, we won't know
+ * * what state we're in when we get the _next_ reassembled packet */
+ if ((pkt_dir != IMAP_PKT_FROM_SERVER) &&
+ (p->packet_flags & PKT_REBUILT_STREAM))
+ {
+ int missing_in_rebuilt =
+ stream.missing_in_reassembled(p->flow, SSN_DIR_FROM_CLIENT);
+
+ if (ssn->session_flags & IMAP_FLAG_NEXT_STATE_UNKNOWN)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Found gap in previous reassembly buffer - "
+ "set state to unknown\n"); );
+ ssn->state = STATE_UNKNOWN;
+ ssn->session_flags &= ~IMAP_FLAG_NEXT_STATE_UNKNOWN;
+ }
+
+ if (missing_in_rebuilt == SSN_MISSING_BEFORE)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Found missing packets before "
+ "in reassembly buffer - set state to unknown\n"); );
+ ssn->state = STATE_UNKNOWN;
+ }
+ }
+
+ return pkt_dir;
+}
+
+static int IMAP_SearchStrFound(void* id, void* , int index, void* , void* )
+{
+ int search_id = (int)(uintptr_t)id;
+
+ imap_search_info.id = search_id;
+ imap_search_info.index = index;
+ imap_search_info.length = imap_current_search[search_id].name_len;
+
+ /* Returning non-zero stops search, which is okay since we only look for one at a time */
+ return 1;
+}
+
+/*
+ * Handle COMMAND state
+ *
+ * @param p standard Packet structure
+ * @param ptr pointer into p->data buffer to start looking at data
+ * @param end points to end of p->data buffer
+ *
+ * @return pointer into p->data where we stopped looking at data
+ * will be end of line or end of packet
+ */
+static const uint8_t* IMAP_HandleCommand(Packet* p, IMAPData* imap_ssn, const uint8_t* ptr, const
+ uint8_t* end)
+{
+ const uint8_t* eol; /* end of line */
+ const uint8_t* eolm; /* end of line marker */
+ int cmd_found;
+
+ /* get end of line and end of line marker */
+ IMAP_GetEOL(ptr, end, &eol, &eolm);
+
+ /* TODO If the end of line marker coincides with the end of data we can't be
+ * sure that we got a command and not a substring which we could tell through
+ * inspection of the next packet. Maybe a command pending state where the first
+ * char in the next packet is checked for a space and end of line marker */
+
+ /* do not confine since there could be space chars before command */
+ imap_current_search = &imap_cmd_search[0];
+ cmd_found = imap_cmd_search_mpse->find(
+ (const char*)ptr, eolm - ptr, IMAP_SearchStrFound);
+
+ /* if command not found, alert and move on */
+ if (!cmd_found)
+ {
+ if (imap_ssn->state == STATE_UNKNOWN)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Command not found, but state is "
+ "unknown - checking for SSL\n"); );
+
+ /* check for encrypted */
+
+ if ((imap_ssn->session_flags & IMAP_FLAG_CHECK_SSL) &&
+ (IsSSL(ptr, end - ptr, p->packet_flags)))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Packet is SSL encrypted\n"); );
+
+ imap_ssn->state = STATE_TLS_DATA;
+
+ /* Ignore data */
+ return end;
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Not SSL - try data state\n"); );
+ /* don't check for ssl again in this packet */
+ if (imap_ssn->session_flags & IMAP_FLAG_CHECK_SSL)
+ imap_ssn->session_flags &= ~IMAP_FLAG_CHECK_SSL;
+
+ imap_ssn->state = STATE_DATA;
+ //imap_ssn->data_state = STATE_DATA_UNKNOWN;
+
+ return ptr;
+ }
+ }
+ else
+ {
+ SnortEventqAdd(GID_IMAP, IMAP_UNKNOWN_CMD);
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "No known command found\n"); );
+ return eol;
+ }
+ }
+ else
+ {
+ if (imap_ssn->state == STATE_UNKNOWN)
+ imap_ssn->state = STATE_COMMAND;
+ }
+
+ if (imap_search_info.id == CMD_STARTTLS)
+ {
+ if (eol == end)
+ imap_ssn->state = STATE_TLS_CLIENT_PEND;
+ }
+
+ return eol;
+}
+
+/*
+ * Process client packet
+ *
+ * @param packet standard Packet structure
+ *
+ * @return none
+ */
+static void IMAP_ProcessClientPacket(Packet* p, IMAPData* imap_ssn)
+{
+ const uint8_t* ptr = p->data;
+ const uint8_t* end = p->data + p->dsize;
+
+ ptr = IMAP_HandleCommand(p, imap_ssn, ptr, end);
+}
+
+/*
+ * Process server packet
+ *
+ * @param packet standard Packet structure
+ *
+ */
+static void IMAP_ProcessServerPacket(Packet* p, IMAPData* imap_ssn)
+{
+ int resp_found;
+ const uint8_t *ptr;
+ const uint8_t *end;
+ const uint8_t *data_end;
+ const uint8_t *eolm;
+ const uint8_t *eol;
+ int resp_line_len;
+ const char *tmp = NULL;
+ uint8_t *body_start = NULL;
+ char *eptr;
+ uint32_t len = 0;
+
+ ptr = p->data;
+ end = p->data + p->dsize;
+
+ while (ptr < end)
+ {
+ if (imap_ssn->state == STATE_DATA)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "DATA STATE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
+ if ( imap_ssn->body_len > imap_ssn->body_read)
+ {
+ len = imap_ssn->body_len - imap_ssn->body_read;
+ if ( (uint32_t)(end - ptr) < len )
+ {
+ data_end = end;
+ len = data_end - ptr;
+ }
+ else
+ data_end = ptr + len;
+ ptr = file_api->process_mime_data(p, ptr, end, &(imap_ssn->mime_ssn), 0, true);
+ if ( ptr < data_end)
+ len = len - (data_end - ptr);
+
+ imap_ssn->body_read += len;
+
+ continue;
+ }
+ else
+ {
+ imap_ssn->body_len = imap_ssn->body_read = 0;
+ IMAP_ResetState(p->flow);
+ }
+ }
+ IMAP_GetEOL(ptr, end, &eol, &eolm);
+
+ resp_line_len = eol - ptr;
+
+ /* Check for response code */
+ imap_current_search = &imap_resp_search[0];
+ resp_found = imap_resp_search_mpse->find(
+ (const char*)ptr, resp_line_len, IMAP_SearchStrFound);
+
+ if (resp_found > 0)
+ {
+ const uint8_t* cmd_start = ptr + imap_search_info.index;
+ switch (imap_search_info.id)
+ {
+ case RESP_FETCH:
+ imap_ssn->body_len = imap_ssn->body_read = 0;
+ imap_ssn->state = STATE_DATA;
+ tmp = SnortStrcasestr((const char*)cmd_start, (eol - cmd_start), "BODY");
+ if (tmp != NULL)
+ imap_ssn->state = STATE_DATA;
+ else
+ {
+ tmp = SnortStrcasestr((const char*)cmd_start, (eol - cmd_start), "RFC822");
+ if (tmp != NULL)
+ imap_ssn->state = STATE_DATA;
+ else
+ imap_ssn->state = STATE_UNKNOWN;
+ }
+ break;
+ default:
+ break;
+ }
+ if (imap_ssn->state == STATE_DATA)
+ {
+ body_start = (uint8_t*)memchr((char*)ptr, '{', (eol - ptr));
+ if ( body_start == NULL )
+ {
+ imap_ssn->state = STATE_UNKNOWN;
+ }
+ else
+ {
+ if ( (body_start + 1) < (uint8_t*)eol )
+ {
+ len = (uint32_t)SnortStrtoul((const char*)(body_start + 1), &eptr, 10);
+ if (*eptr != '}')
+ {
+ imap_ssn->state = STATE_UNKNOWN;
+ }
+ else
+ imap_ssn->body_len = len;
+
+ len = 0;
+ }
+ else
+ imap_ssn->state = STATE_UNKNOWN;
+ }
+ }
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP,
+ "Server response not found - see if it's SSL data\n"); );
+
+ if ((imap_ssn->session_flags & IMAP_FLAG_CHECK_SSL) &&
+ (IsSSL(ptr, end - ptr, p->packet_flags)))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Server response is an SSL packet\n"); );
+
+ imap_ssn->state = STATE_TLS_DATA;
+
+ return;
+ }
+ else if (imap_ssn->session_flags & IMAP_FLAG_CHECK_SSL)
+ {
+ imap_ssn->session_flags &= ~IMAP_FLAG_CHECK_SSL;
+ }
+ if ( (*ptr != '*') && (*ptr !='+') && (*ptr != '\r') && (*ptr != '\n') )
+ {
+ SnortEventqAdd(GID_IMAP, IMAP_UNKNOWN_RESP);
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Server response not found\n"); );
+ }
+ }
+
+ ptr = eol;
+ }
+}
+
+/* Main runtime entry point for IMAP preprocessor.
+ * Analyzes IMAP packets for anomalies/exploits.
+ *
+ * PARAMETERS:
+ *
+ * p: Pointer to current packet to process.
+ * contextp: Pointer to context block, not used.
+ *
+ * RETURNS: Nothing.
+ */
+static void snort_imap(IMAP_PROTO_CONF* config, Packet* p)
+{
+ IMAPData* imap_ssn = NULL;
+ int pkt_dir;
+
+ /* Attempt to get a previously allocated IMAP block. */
+ imap_ssn = get_session_data(p->flow);
+
+ if (imap_ssn == NULL)
+ {
+ /* Check the stream session. If it does not currently
+ * have our IMAP data-block attached, create one.
+ */
+ imap_ssn = SetNewIMAPData(config, p);
+
+ if ( !imap_ssn )
+ {
+ /* Could not get/create the session data for this packet. */
+ return;
+ }
+ }
+
+ pkt_dir = IMAP_Setup(p, imap_ssn);
+
+ if (pkt_dir == IMAP_PKT_FROM_CLIENT)
+ {
+ /* This packet should be a tls client hello */
+ if (imap_ssn->state == STATE_TLS_CLIENT_PEND)
+ {
+ if (IsTlsClientHello(p->data, p->data + p->dsize))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP,
+ "TLS DATA STATE ~~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
+
+ imap_ssn->state = STATE_TLS_SERVER_PEND;
+ return;
+ }
+ else
+ {
+ /* reset state - server may have rejected STARTTLS command */
+ imap_ssn->state = STATE_UNKNOWN;
+ }
+ }
+ if ((imap_ssn->state == STATE_TLS_DATA)
+ || (imap_ssn->state == STATE_TLS_SERVER_PEND))
+ {
+ return;
+ }
+ IMAP_ProcessClientPacket(p, imap_ssn);
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP client packet\n"); );
+ }
+ else
+ {
+ if (imap_ssn->state == STATE_TLS_SERVER_PEND)
+ {
+ if (IsTlsServerHello(p->data, p->data + p->dsize))
+ {
+ imap_ssn->state = STATE_TLS_DATA;
+ }
+ else if (!(stream.get_session_flags(p->flow) & SSNFLAG_MIDSTREAM)
+ && !stream.missed_packets(p->flow, SSN_DIR_BOTH))
+ {
+ /* revert back to command state - assume server didn't accept STARTTLS */
+ imap_ssn->state = STATE_UNKNOWN;
+ }
+ else
+ return;
+ }
+
+ if (imap_ssn->state == STATE_TLS_DATA)
+ {
+ return;
+ }
+ if ( !InspectPacket(p))
+ {
+ /* Packet will be rebuilt, so wait for it */
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Client packet will be reassembled\n"));
+ return;
+ }
+ else if (!(p->packet_flags & PKT_REBUILT_STREAM))
+ {
+ /* If this isn't a reassembled packet and didn't get
+ * inserted into reassembly buffer, there could be a
+ * problem. If we miss syn or syn-ack that had window
+ * scaling this packet might not have gotten inserted
+ * into reassembly buffer because it fell outside of
+ * window, because we aren't scaling it */
+ imap_ssn->session_flags |= IMAP_FLAG_GOT_NON_REBUILT;
+ imap_ssn->state = STATE_UNKNOWN;
+ }
+ else if (imap_ssn->session_flags & IMAP_FLAG_GOT_NON_REBUILT)
+ {
+ /* This is a rebuilt packet. If we got previous packets
+ * that were not rebuilt, state is going to be messed up
+ * so set state to unknown. It's likely this was the
+ * beginning of the conversation so reset state */
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "Got non-rebuilt packets before "
+ "this rebuilt packet\n"); );
+
+ imap_ssn->state = STATE_UNKNOWN;
+ imap_ssn->session_flags &= ~IMAP_FLAG_GOT_NON_REBUILT;
+ }
+ /* Process as a server packet */
+ IMAP_ProcessServerPacket(p, imap_ssn);
+ }
+}
+
+//-------------------------------------------------------------------------
+// class stuff
+//-------------------------------------------------------------------------
+
+class Imap : public Inspector
+{
+public:
+ Imap(IMAP_PROTO_CONF*);
+ ~Imap();
+
+ bool configure(SnortConfig*) override;
+ void show(SnortConfig*) override;
+ void eval(Packet*) override;
+
+ StreamSplitter* get_splitter(bool c2s) override
+ { return new ImapSplitter(c2s); }
+
+private:
+ IMAP_PROTO_CONF* config;
+};
+
+Imap::Imap(IMAP_PROTO_CONF* pc)
+{
+ config = pc;
+}
+
+Imap::~Imap()
+{
+ if ( config )
+ delete config;
+}
+
+bool Imap::configure(SnortConfig*)
+{
+ config->decode_conf.file_depth = file_api->get_max_file_depth();
+
+ if (config->decode_conf.file_depth > 0)
+ config->log_config.log_filename = 1;
+
+ if (file_api->is_decoding_enabled(&config->decode_conf) )
+ {
+ updateMaxDepth(config->decode_conf.file_depth,
+ &config->decode_conf.max_depth);
+ }
+ file_api->check_decode_config(&config->decode_conf);
+
+ return true;
+}
+
+void Imap::show(SnortConfig*)
+{
+ PrintImapConf(config);
+}
+
+void Imap::eval(Packet* p)
+{
+ PROFILE_VARS;
+ // precondition - what we registered for
+ assert(p->is_tcp() && p->dsize && p->data);
+
+ ++imapstats.total_packets;
+
+ MODULE_PROFILE_START(imapPerfStats);
+
+ snort_imap(config, p);
+
+ MODULE_PROFILE_END(imapPerfStats);
+}
+
+//-------------------------------------------------------------------------
+// api stuff
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{ return new ImapModule; }
+
+static void mod_dtor(Module* m)
+{ delete m; }
+
+static void imap_init()
+{
+ ImapFlowData::init();
+ IMAP_SearchInit();
+}
+
+static void imap_term()
+{
+ IMAP_SearchFree();
+}
+
+static Inspector* imap_ctor(Module* m)
+{
+ ImapModule* mod = (ImapModule*)m;
+ return new Imap(mod->get_data());
+}
+
+static void imap_dtor(Inspector* p)
+{
+ delete p;
+}
+
+const InspectApi imap_api =
+{
+ {
+ PT_INSPECTOR,
+ sizeof(InspectApi),
+ INSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ IMAP_NAME,
+ IMAP_HELP,
+ mod_ctor,
+ mod_dtor
+ },
+ IT_SERVICE,
+ (uint16_t)PktType::TCP,
+ nullptr, // buffers
+ "imap",
+ imap_init,
+ imap_term, // pterm
+ nullptr, // tinit
+ nullptr, // tterm
+ imap_ctor,
+ imap_dtor,
+ nullptr, // ssn
+ nullptr // reset
+};
+
+#ifdef BUILDING_SO
+SO_PUBLIC const BaseApi* snort_plugins[] =
+{
+ &imap_api.base,
+ nullptr
+};
+#else
+const BaseApi* sin_imap = &imap_api.base;
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+
+/*
+ * imap.h: Definitions, structs, function prototype(s) for
+ * Author: Bhagyashree Bantwal <bbantwal@cisco.com>
+ */
+
+#ifndef IMAP_H
+#define IMAP_H
+
+#include "protocols/packet.h"
+#include "stream/stream_api.h"
+#include "profiler.h"
+#include "imap_config.h"
+/* Direction packet is coming from, if we can figure it out */
+#define IMAP_PKT_FROM_UNKNOWN 0
+#define IMAP_PKT_FROM_CLIENT 1
+#define IMAP_PKT_FROM_SERVER 2
+
+#define SEARCH_CMD 0
+#define SEARCH_RESP 1
+#define SEARCH_HDR 2
+#define SEARCH_DATA_END 3
+#define NUM_SEARCHES 4
+
+#define BOUNDARY 0
+
+#define STATE_DATA 0 /* Data state */
+#define STATE_TLS_CLIENT_PEND 1 /* Got STARTTLS */
+#define STATE_TLS_SERVER_PEND 2 /* Got STARTTLS */
+#define STATE_TLS_DATA 3 /* Successful handshake, TLS encrypted data */
+#define STATE_COMMAND 4
+#define STATE_UNKNOWN 5
+
+#define STATE_DATA_INIT 0
+#define STATE_DATA_HEADER 1 /* Data header section of data state */
+#define STATE_DATA_BODY 2 /* Data body section of data state */
+#define STATE_MIME_HEADER 3 /* MIME header section within data section */
+#define STATE_DATA_UNKNOWN 4
+
+/* session flags */
+#define IMAP_FLAG_NEXT_STATE_UNKNOWN 0x00000004
+#define IMAP_FLAG_GOT_NON_REBUILT 0x00000008
+#define IMAP_FLAG_CHECK_SSL 0x00000010
+
+/* Maximum length of header chars before colon, based on Exim 4.32 exploit */
+#define MAX_HEADER_NAME_LEN 64
+typedef enum _IMAPCmdEnum
+{
+ CMD_APPEND = 0,
+ CMD_AUTHENTICATE,
+ CMD_CAPABILITY,
+ CMD_CHECK,
+ CMD_CLOSE,
+ CMD_COMPARATOR,
+ CMD_COMPRESS,
+ CMD_CONVERSIONS,
+ CMD_COPY,
+ CMD_CREATE,
+ CMD_DELETE,
+ CMD_DELETEACL,
+ CMD_DONE,
+ CMD_EXAMINE,
+ CMD_EXPUNGE,
+ CMD_FETCH,
+ CMD_GETACL,
+ CMD_GETMETADATA,
+ CMD_GETQUOTA,
+ CMD_GETQUOTAROOT,
+ CMD_IDLE,
+ CMD_LIST,
+ CMD_LISTRIGHTS,
+ CMD_LOGIN,
+ CMD_LOGOUT,
+ CMD_LSUB,
+ CMD_MYRIGHTS,
+ CMD_NOOP,
+ CMD_NOTIFY,
+ CMD_RENAME,
+ CMD_SEARCH,
+ CMD_SELECT,
+ CMD_SETACL,
+ CMD_SETMETADATA,
+ CMD_SETQUOTA,
+ CMD_SORT,
+ CMD_STARTTLS,
+ CMD_STATUS,
+ CMD_STORE,
+ CMD_SUBSCRIBE,
+ CMD_THREAD,
+ CMD_UID,
+ CMD_UNSELECT,
+ CMD_UNSUBSCRIBE,
+ CMD_X,
+ CMD_LAST
+} IMAPCmdEnum;
+
+typedef enum _IMAPRespEnum
+{
+ RESP_CAPABILITY = 0,
+ RESP_LIST,
+ RESP_LSUB,
+ RESP_STATUS,
+ RESP_SEARCH,
+ RESP_FLAGS,
+ RESP_EXISTS,
+ RESP_RECENT,
+ RESP_EXPUNGE,
+ RESP_FETCH,
+ RESP_BAD,
+ RESP_BYE,
+ RESP_NO,
+ RESP_OK,
+ RESP_PREAUTH,
+ RESP_ENVELOPE,
+ RESP_UID,
+ RESP_LAST
+} IMAPRespEnum;
+
+typedef enum _IMAPHdrEnum
+{
+ HDR_CONTENT_TYPE = 0,
+ HDR_CONT_TRANS_ENC,
+ HDR_CONT_DISP,
+ HDR_LAST
+} IMAPHdrEnum;
+struct IMAPSearch
+{
+ const char* name;
+ int name_len;
+};
+
+struct IMAPToken
+{
+ const char* name;
+ int name_len;
+ int search_id;
+};
+
+struct IMAPCmdConfig
+{
+ char alert; /* 1 if alert when seen */
+ char normalize; /* 1 if we should normalize this command */
+ int max_line_len; /* Max length of this particular command */
+};
+
+struct IMAPSearchInfo
+{
+ int id;
+ int index;
+ int length;
+};
+
+struct IMAPData
+{
+ int state;
+ int state_flags;
+ int session_flags;
+ uint32_t body_len;
+ uint32_t body_read;
+ MimeState mime_ssn;
+};
+
+class ImapFlowData : public FlowData
+{
+public:
+ ImapFlowData() : FlowData(flow_id)
+ { memset(&session, 0, sizeof(session)); }
+
+ ~ImapFlowData() { }
+
+ static void init()
+ { flow_id = FlowData::get_flow_id(); }
+
+public:
+ static unsigned flow_id;
+ IMAPData session;
+};
+
+#endif /* IMAP_H */
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+
+#ifndef IMAP_CONFIG_H
+#define IMAP_CONFIG_H
+
+#include "file_api/file_api.h"
+
+struct IMAP_PROTO_CONF
+{
+ uint32_t memcap;
+ DecodeConfig decode_conf;
+ MAIL_LogConfig log_config;
+};
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// imap_module.cc author Bhagyashree Bantwal <bbantwal@cisco.com>
+
+#include "imap_module.h"
+#include <assert.h>
+#include <sstream>
+#include "main/snort_config.h"
+
+using namespace std;
+
+#define IMAP_UNKNOWN_CMD_STR "Unknown IMAP3 command"
+#define IMAP_UNKNOWN_RESP_STR "Unknown IMAP3 response"
+#define IMAP_B64_DECODING_FAILED_STR "Base64 Decoding failed."
+#define IMAP_QP_DECODING_FAILED_STR "Quoted-Printable Decoding failed."
+#define IMAP_UU_DECODING_FAILED_STR "Unix-to-Unix Decoding failed."
+
+static const Parameter s_params[] =
+{
+ { "b64_decode_depth", Parameter::PT_INT, "-1:65535", "1460",
+ " base64 decoding depth" },
+
+ { "bitenc_decode_depth", Parameter::PT_INT, "-1:65535", "1460",
+ " Non-Encoded MIME attachment extraction depth" },
+
+ { "qp_decode_depth", Parameter::PT_INT, "-1:65535", "1460",
+ " Quoted Printable decoding depth" },
+
+ { "uu_decode_depth", Parameter::PT_INT, "-1:65535", "1460",
+ " Unix-to-Unix decoding depth" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+static const RuleMap imap_rules[] =
+{
+ { IMAP_UNKNOWN_CMD, IMAP_UNKNOWN_CMD_STR },
+ { IMAP_UNKNOWN_RESP, IMAP_UNKNOWN_RESP_STR },
+ { IMAP_B64_DECODING_FAILED, IMAP_B64_DECODING_FAILED_STR },
+ { IMAP_QP_DECODING_FAILED, IMAP_QP_DECODING_FAILED_STR },
+ { IMAP_UU_DECODING_FAILED, IMAP_UU_DECODING_FAILED_STR },
+
+ { 0, nullptr }
+};
+
+//-------------------------------------------------------------------------
+// imap module
+//-------------------------------------------------------------------------
+
+ImapModule::ImapModule() : Module(IMAP_NAME, IMAP_HELP, s_params)
+{
+ config = nullptr;
+}
+
+ImapModule::~ImapModule()
+{
+ if ( config )
+ delete config;
+}
+
+const RuleMap* ImapModule::get_rules() const
+{ return imap_rules; }
+
+const PegInfo* ImapModule::get_pegs() const
+{ return simple_pegs; }
+
+PegCount* ImapModule::get_counts() const
+{ return (PegCount*)&imapstats; }
+
+ProfileStats* ImapModule::get_profile() const
+{ return &imapPerfStats; }
+
+bool ImapModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( v.is("b64_decode_depth") )
+ {
+ int decode_depth = v.get_long();
+
+ if ((decode_depth > 0) && (decode_depth & 3))
+ {
+ decode_depth += 4 - (decode_depth & 3);
+ if (decode_depth > 65535 )
+ {
+ decode_depth = decode_depth - 4;
+ }
+ LogMessage("WARNING: IMAP: 'b64_decode_depth' is not a multiple of 4. "
+ "Rounding up to the next multiple of 4. The new 'b64_decode_depth' is %d.\n",
+ decode_depth);
+ }
+ config->decode_conf.b64_depth = decode_depth;
+ }
+ else if ( v.is("bitenc_decode_depth") )
+ config->decode_conf.bitenc_depth = v.get_long();
+
+ else if ( v.is("qp_decode_depth") )
+ config->decode_conf.qp_depth = v.get_long();
+
+ else if ( v.is("uu_decode_depth") )
+ config->decode_conf.uu_depth = v.get_long();
+
+ else
+ return false;
+
+ return true;
+}
+
+IMAP_PROTO_CONF* ImapModule::get_data()
+{
+ IMAP_PROTO_CONF* tmp = config;
+ config = nullptr;
+ return tmp;
+}
+
+bool ImapModule::begin(const char*, int, SnortConfig*)
+{
+ config = new IMAP_PROTO_CONF;
+ file_api->set_mime_decode_config_defauts(&(config->decode_conf));
+ file_api->set_mime_log_config_defauts(&(config->log_config));
+
+ return true;
+}
+
+bool ImapModule::end(const char*, int, SnortConfig*)
+{
+ return true;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// imap_module.h author Bhagyashree Bantwal <bbantwal@cisco.com>
+
+#ifndef IMAP_MODULE_H
+#define IMAP_MODULE_H
+
+#include "framework/module.h"
+#include "framework/bits.h"
+#include "main/thread.h"
+#include "imap_config.h"
+
+#define GID_IMAP 141
+
+#define IMAP_UNKNOWN_CMD 1
+#define IMAP_UNKNOWN_RESP 2
+#define IMAP_B64_DECODING_FAILED 4
+#define IMAP_QP_DECODING_FAILED 5
+#define IMAP_UU_DECODING_FAILED 7
+
+#define IMAP_NAME "imap"
+#define IMAP_HELP "imap inspection"
+
+struct SnortConfig;
+
+extern THREAD_LOCAL SimpleStats imapstats;
+extern THREAD_LOCAL ProfileStats imapPerfStats;
+
+class ImapModule : public Module
+{
+public:
+ ImapModule();
+ ~ImapModule();
+
+ bool set(const char*, Value&, SnortConfig*) override;
+ bool begin(const char*, int, SnortConfig*) override;
+ bool end(const char*, int, SnortConfig*) override;
+
+ unsigned get_gid() const override
+ { return GID_IMAP; }
+
+ const RuleMap* get_rules() const override;
+ const PegInfo* get_pegs() const override;
+ PegCount* get_counts() const override;
+ ProfileStats* get_profile() const override;
+
+ IMAP_PROTO_CONF* get_data();
+
+private:
+ IMAP_PROTO_CONF* config;
+};
+
+#endif
+
--- /dev/null
+/****************************************************************************
+ * Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation. You may not use, modify or
+ * distribute this program under any other version of the GNU General
+ * Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ ****************************************************************************/
+
+#include <sys/types.h>
+#include "snort_types.h"
+#include "snort_debug.h"
+
+#include "imap_paf.h"
+#include "imap.h"
+
+extern IMAPToken imap_resps[];
+
+static inline ImapPafData* get_state(Flow* flow, bool c2s)
+{
+ if ( !flow )
+ return nullptr;
+
+ ImapSplitter* s = (ImapSplitter*)stream.get_splitter(flow, c2s);
+ return s ? &s->state : nullptr;
+}
+
+static inline void reset_data_states(ImapPafData* pfdata)
+{
+ // reset MIME info
+ file_api->reset_mime_paf_state(&(pfdata->mime_info));
+
+ // reset server info
+ pfdata->imap_state = IMAP_PAF_CMD_IDENTIFIER;
+
+ // reset fetch data information information
+ pfdata->imap_data_info.paren_cnt = 0;
+ pfdata->imap_data_info.next_letter = 0;
+ pfdata->imap_data_info.length = 0;
+}
+
+static inline bool is_untagged(const uint8_t ch)
+{
+ return (ch == '*' || ch == '+');
+}
+
+static bool parse_literal_length(const uint8_t ch, uint32_t* len)
+{
+ uint32_t length = *len;
+
+ if (isdigit(ch))
+ {
+ uint64_t tmp_len = (10 * length) + (ch - '0');
+ if (tmp_len < UINT32_MAX)
+ {
+ *len = (uint32_t)tmp_len;
+ return false;
+ }
+ else
+ {
+ *len = 0;
+ }
+ }
+ else if (ch != '}')
+ *len = 0; // ALERT!! charachter should be a digit or ''}''
+
+ return true;
+}
+
+static void parse_fetch_header(const uint8_t ch, ImapPafData* pfdata)
+{
+ if (pfdata->imap_data_info.esc_nxt_char)
+ {
+ pfdata->imap_data_info.esc_nxt_char = false;
+ }
+ else
+ {
+ switch (ch)
+ {
+ case '{':
+ pfdata->imap_state = IMAP_PAF_DATA_LEN_STATE;
+ break;
+ case '(':
+ pfdata->imap_data_info.paren_cnt++;
+ break;
+
+ case ')':
+ if (pfdata->imap_data_info.paren_cnt > 0)
+ pfdata->imap_data_info.paren_cnt--;
+ break;
+
+ case '\n':
+ if (pfdata->imap_data_info.paren_cnt)
+ {
+ pfdata->imap_state = IMAP_PAF_DATA_STATE;
+ }
+ else
+ {
+ reset_data_states(pfdata);
+ }
+ break;
+
+ case '\\':
+ pfdata->imap_data_info.esc_nxt_char = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/*
+ * Statefully search for the single line termination sequence LF ("\n").
+ *
+ * PARAMS:
+ * const uint8_t ch - the next character to analyze.
+ * ImapPafData *pfdata - the struct containing all imap paf information
+ *
+ * RETURNS:
+ * false - if termination sequence not found
+ * true - if termination sequence found
+ */
+static bool find_data_end_single_line(const uint8_t ch, ImapPafData* pfdata)
+{
+ if (ch == '\n')
+ {
+ reset_data_states(pfdata);
+ return true;
+ }
+ return false;
+}
+
+/* Flush based on data length*/
+static inline bool literal_complete(ImapPafData* pfdata)
+{
+ if (pfdata->imap_data_info.length)
+ {
+ pfdata->imap_data_info.length--;
+ if (pfdata->imap_data_info.length)
+ return false;
+ }
+
+ return true;
+}
+
+static bool check_imap_data_end(ImapDataEnd* data_end_state, uint8_t val)
+{
+ switch (*data_end_state)
+ {
+ case IMAP_PAF_DATA_END_UNKNOWN:
+ if (val == ')')
+ *data_end_state = IMAP_PAF_DATA_END_PAREN;
+ break;
+
+ case IMAP_PAF_DATA_END_PAREN:
+ if (val == '\n')
+ {
+ *data_end_state = IMAP_PAF_DATA_END_UNKNOWN;
+ return true;
+ }
+ else if (val != '\r')
+ {
+ *data_end_state = IMAP_PAF_DATA_END_UNKNOWN;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/*
+ * Statefully search for the data termination sequence or a MIME boundary.
+ *
+ * PARAMS:
+ * const uint8_t ch - the next character to analyze.
+ * ImapPafData *pfdata - the struct containing all imap paf information
+ *
+ * RETURNS:
+ * false - if termination sequence not found
+ * true - if termination sequence found
+ */
+static bool find_data_end_mime_data(const uint8_t ch, ImapPafData* pfdata)
+{
+ if (literal_complete(pfdata)
+ && check_imap_data_end(&(pfdata->data_end_state), ch))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: End of Data!\n"); );
+ reset_data_states(pfdata);
+ return true;
+ }
+
+ // check for mime flush point
+ if (file_api->process_mime_paf_data(&(pfdata->mime_info), ch))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: Mime Boundary found."
+ " Flushing data!\n"); );
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Initial command processing function. Determine if this command
+ * may be analyzed irregularly ( which currently means if emails
+ * and email attachments need to be analyzed).
+ *
+ * PARAMS:
+ * const uint8_t ch - the next character to analyze.
+ * ImapPafData *pfdata - the struct containing all imap paf information
+ */
+static inline void init_command_search(const uint8_t ch, ImapPafData* pfdata)
+{
+ switch (ch)
+ {
+ case 'F':
+ case 'f':
+ // may be a FETCH response
+ pfdata->imap_data_info.next_letter = &(imap_resps[RESP_FETCH].name[1]);
+ break;
+
+ default:
+ // this is not a data command. Search for regular end of line.
+ pfdata->imap_state = IMAP_PAF_REG_STATE;
+ }
+}
+
+/*
+ * Confirms every character in the current sequence is part of the expected
+ * command. After confirmation is complete, IMAP PAF will begin searching
+ * for data. If any character is unexpected, searches for the default
+ * termination sequence.
+ *
+ * PARAMS:
+ * const uint8_t ch - the next character to analyze.
+ * ImapPafData *pfdata - the struct containing all imap paf information
+ */
+static inline void parse_command(const uint8_t ch, ImapPafData* pfdata)
+{
+ char val = *(pfdata->imap_data_info.next_letter);
+
+ if (val == '\0' && isblank(ch))
+ pfdata->imap_state = IMAP_PAF_DATA_HEAD_STATE;
+
+ else if (toupper(ch) == toupper(val))
+ pfdata->imap_data_info.next_letter++;
+
+ else
+ pfdata->imap_state = IMAP_PAF_REG_STATE;
+}
+
+/*
+ * Wrapper function for the command parser. Determines whether this is the
+ * first letter being processed and calls the appropriate processing
+ * function.
+ *
+ * PARAMS:
+ * const uint8_t ch - the next character to analyze.
+ * ImapPafData *pfdata - the struct containing all imap paf information
+ */
+static inline void process_command(const uint8_t ch, ImapPafData* pfdata)
+{
+ if (pfdata->imap_data_info.next_letter)
+ parse_command(ch, pfdata);
+ else
+ init_command_search(ch, pfdata);
+}
+
+/*
+ * This function only does something when the character is a blank or a CR/LF.
+ * In those specific cases, this function will set the appropriate next
+ * state information
+ *
+ * PARAMS:
+ * const uint8_t ch - the next character to analyze.
+ * ImapPafData *pfdata - the struct containing all imap paf information
+ * ImapPafData base_state - if a space is not found, revert to this state
+ * ImapPafData next_state - if a space is found, go to this state
+ * RETURNS:
+ * true - if the status has been eaten
+ * false - if a CR or LF has been found
+ */
+static inline void eat_character(const uint8_t ch, ImapPafData* pfdata,
+ ImapPafState base_state, ImapPafState next_state)
+{
+ switch (ch)
+ {
+ case ' ':
+ case '\t':
+ pfdata->imap_state = next_state;
+ break;
+
+ case '\r':
+ case '\n':
+ pfdata->imap_state = base_state;
+ break;
+ }
+}
+
+/*
+ * defined above in the eat_character function
+ *
+ * Keeping the next two functions to ease any future development
+ * where these cases will no longer be simple or identical
+ */
+static inline void eat_second_argument(const uint8_t ch, ImapPafData* pfdata)
+{
+ eat_character(ch, pfdata, IMAP_PAF_REG_STATE, IMAP_PAF_CMD_SEARCH);
+}
+
+/* explanation in 'eat_second_argument' above */
+static inline void eat_response_identifier(const uint8_t ch, ImapPafData* pfdata)
+{
+ eat_character(ch, pfdata, IMAP_PAF_REG_STATE, IMAP_PAF_CMD_STATUS);
+}
+
+/*
+ * Analyzes the current data for a correct flush point. Flushes when
+ * a command is complete or a MIME boundary is found.
+ *
+ * PARAMS:
+ * ImapPafData *pfdata - ImapPaf state tracking structure
+ * const uint8_t *data - payload data to inspect
+ * uint32_t len - length of payload data
+ * uint32_t * fp- pointer to set flush point
+ *
+ * RETURNS:
+ * StreamSplitter::Status - StreamSplitter::FLUSH if flush point found,
+ * StreamSplitter::SEARCH otherwise
+ */
+static StreamSplitter::Status imap_paf_server(ImapPafData* pfdata,
+ const uint8_t* data, uint32_t len, uint32_t* fp)
+{
+ uint32_t i;
+ uint32_t flush_len = 0;
+ uint32_t boundary_start = 0;
+
+ pfdata->end_of_data = false;
+
+ for (i = 0; i < len; i++)
+ {
+ uint8_t ch = data[i];
+ switch (pfdata->imap_state)
+ {
+ case IMAP_PAF_CMD_IDENTIFIER:
+ // can be '+', '*', or a tag
+ if (is_untagged(ch))
+ {
+ // continue checking for fetch command
+ pfdata->imap_state = IMAP_PAF_CMD_TAG;
+ }
+ else
+ {
+ // end of a command. flush at end of line.
+ pfdata->imap_state = IMAP_PAF_FLUSH_STATE;
+ }
+ break;
+
+ case IMAP_PAF_CMD_TAG:
+ eat_response_identifier(ch, pfdata);
+ break;
+
+ case IMAP_PAF_CMD_STATUS:
+ // can be a command name, msg sequence number, msg count, etc...
+ // since we are only interested in fetch, eat this argument
+ eat_second_argument(ch, pfdata);
+ break;
+
+ case IMAP_PAF_CMD_SEARCH:
+ process_command(ch, pfdata);
+ find_data_end_single_line(ch, pfdata);
+ break;
+
+ case IMAP_PAF_REG_STATE:
+ find_data_end_single_line(ch, pfdata); // data reset when end of line hit
+ break;
+
+ case IMAP_PAF_DATA_HEAD_STATE:
+ parse_fetch_header(ch, pfdata); // function will change state
+ break;
+
+ case IMAP_PAF_DATA_LEN_STATE:
+ if (parse_literal_length(ch, &(pfdata->imap_data_info.length)))
+ {
+ pfdata->imap_state = IMAP_PAF_DATA_HEAD_STATE;
+ }
+ break;
+
+ case IMAP_PAF_DATA_STATE:
+ if (find_data_end_mime_data(ch, pfdata))
+ {
+ // if not a boundary, wait for end of
+ // the server's response before flushing
+ if (pfdata->imap_state == IMAP_PAF_DATA_STATE)
+ {
+ *fp = i + 1;
+ return StreamSplitter::FLUSH;
+ }
+ }
+ if (pfdata->mime_info.boundary_state == MIME_PAF_BOUNDARY_UNKNOWN)
+ boundary_start = i;
+ break;
+
+ case IMAP_PAF_FLUSH_STATE:
+ if (find_data_end_single_line(ch, pfdata))
+ {
+ flush_len = i +1;
+ }
+ break;
+ }
+ }
+
+ if (flush_len)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: flushing data!\n"); );
+
+ // flush at the final termination sequence
+ *fp = flush_len;
+ return StreamSplitter::FLUSH;
+ }
+
+ if ( scanning_boundary(&pfdata->mime_info, boundary_start, fp) )
+ return StreamSplitter::LIMIT;
+
+ return StreamSplitter::SEARCH;
+}
+
+/*
+ * Searches through the current data for a LF. All client
+ * commands end with this termination sequence
+ *
+ * PARAMS:
+ * ImapPafData *pfdata - ImapPaf state tracking structure
+ * const uint8_t *data - payload data to inspect
+ * uint32_t len - length of payload data
+ * uint32_t * fp- pointer to set flush point
+ *
+ * RETURNS:
+ * StreamSplitter::Status - StreamSplitter::FLUSH if flush point found,
+ * StreamSplitter::SEARCH otherwise
+ */
+static StreamSplitter::Status imap_paf_client(const uint8_t* data, uint32_t len, uint32_t* fp)
+{
+ const char* pch;
+
+ pch = (char *)memchr (data, '\n', len);
+
+ if (pch != NULL)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: Flushing client"
+ " data!\n"); );
+ *fp = (uint32_t)(pch - (const char*)data) + 1;
+ return StreamSplitter::FLUSH;
+ }
+ return StreamSplitter::SEARCH;
+}
+
+//--------------------------------------------------------------------
+// callback for stateful scanning of in-order raw payload
+//--------------------------------------------------------------------
+
+ImapSplitter::ImapSplitter(bool c2s) : StreamSplitter(c2s)
+{
+ memset(&state, 0, sizeof(state));
+ reset_data_states(&state);
+}
+
+ImapSplitter::~ImapSplitter() { }
+
+/* Function: imap_paf()
+
+ Purpose: IMAP PAF callback.
+ Inspects imap traffic. Checks client traffic for the current command
+ and sets correct server termination sequence. Client side data will
+ flush after receiving CRLF ("\r\n"). Server data flushes after
+ finding set termination sequence.
+
+ Arguments:
+ void * - stream5 session pointer
+ void ** - IMAP state tracking structure
+ const uint8_t * - payload data to inspect
+ uint32_t - length of payload data
+ uint32_t - flags to check whether client or server
+ uint32_t * - pointer to set flush point
+
+ Returns:
+ StreamSplitter::Status - StreamSplitter::FLUSH if flush point found, StreamSplitter::SEARCH otherwise
+*/
+
+StreamSplitter::Status ImapSplitter::scan(
+ Flow* , const uint8_t* data, uint32_t len,
+ uint32_t flags, uint32_t* fp)
+{
+ ImapPafData* pfdata = &state;
+
+ if (flags & PKT_FROM_SERVER)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "PAF: From server.\n"); );
+ return imap_paf_server(pfdata, data, len, fp);
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "PAF: From client.\n"); );
+ return imap_paf_client(data, len, fp);
+ }
+}
+
+bool imap_is_data_end(void* session)
+{
+ Flow* ssn = (Flow*)session;
+ ImapPafData* s = get_state(ssn, true);
+ return s->end_of_data;
+}
+
--- /dev/null
+/****************************************************************************
+ * Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation. You may not use, modify or
+ * distribute this program under any other version of the GNU General
+ * Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ ****************************************************************************/
+
+#ifndef IMAP_PAF_H
+#define IMAP_PAF_H
+
+#include "snort_types.h"
+#include "stream/stream_api.h"
+#include "stream/stream_splitter.h"
+#include "file_api/file_api.h"
+
+struct ImapDataInfo
+{
+ int paren_cnt; /* The open parentheses count in fetch */
+ const char* next_letter; /* The current command in fetch */
+ bool found_len;
+ uint32_t length;
+ bool esc_nxt_char; /* true if the next charachter has been escaped */
+};
+
+/* State tracker for SMTP PAF */
+typedef enum _ImapPafState
+{
+ IMAP_PAF_REG_STATE, /* default state. eat until LF */
+ IMAP_PAF_DATA_HEAD_STATE, /* parses the fetch header */
+ IMAP_PAF_DATA_LEN_STATE, /* parse the literal length */
+ IMAP_PAF_DATA_STATE, /* search for and flush on MIME boundaries */
+ IMAP_PAF_FLUSH_STATE, /* flush if a termination sequence is found */
+ IMAP_PAF_CMD_IDENTIFIER, /* determine the line identifier ('+', '*', tag) */
+ IMAP_PAF_CMD_TAG, /* currently analyzing tag . identifier*/
+ IMAP_PAF_CMD_STATUS, /* currently parsing second argument */
+ IMAP_PAF_CMD_SEARCH /* currently searching data for a command */
+} ImapPafState;
+
+typedef enum _ImapDataEnd
+{
+ IMAP_PAF_DATA_END_UNKNOWN,
+ IMAP_PAF_DATA_END_PAREN
+} ImapDataEnd;
+
+/* State tracker for IMAP PAF */
+struct ImapPafData
+{
+ MimeDataPafInfo mime_info; /* Mime response information */
+ ImapPafState imap_state; /* The current IMAP paf stat */
+ ImapDataInfo imap_data_info; /* Used for parsing data */
+ ImapDataEnd data_end_state;
+ bool end_of_data;
+};
+
+class ImapSplitter : public StreamSplitter
+{
+public:
+ ImapSplitter(bool c2s);
+ ~ImapSplitter();
+
+ Status scan(Flow*, const uint8_t* data, uint32_t len,
+ uint32_t flags, uint32_t* fp) override;
+
+ virtual bool is_paf() override { return true; }
+
+public:
+ ImapPafData state;
+};
+
+bool imap_is_data_end(void* ssn);
+
+#endif
+
--- /dev/null
+
+set( FILE_LIST
+ pop.cc
+ pop.h
+ pop_paf.cc
+ pop_paf.h
+ pop_config.h
+ pop_module.cc
+ pop_module.h
+)
+
+if (STATIC_INSPECTORS)
+ add_library( pop STATIC ${FILE_LIST})
+
+else (STATIC_INSPECTORS)
+ add_shared_library(pop inspectors ${FILE_LIST})
+
+endif (STATIC_INSPECTORS)
--- /dev/null
+AUTOMAKE_OPTIONS=foreign no-dependencies
+
+file_list = \
+pop_config.h \
+pop.cc \
+pop.h \
+pop_paf.cc \
+pop_paf.h \
+pop_module.cc \
+pop_module.h
+
+if STATIC_INSPECTORS
+noinst_LIBRARIES = libpop.a
+libpop_a_SOURCES = $(file_list)
+else
+shlibdir = $(pkglibdir)/inspectors
+shlib_LTLIBRARIES = libpop.la
+libpop_la_CXXFLAGS = $(AM_CXXFLAGS) -DBUILDING_SO
+libpop_la_LDFLAGS = -export-dynamic -shared
+libpop_la_SOURCES = $(file_list)
+endif
+
+AM_CXXFLAGS = @AM_CXXFLAGS@
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+
+/*
+ * POP preprocessor
+ * Author: Bhagyashree Bantwal < bbantwal@cisco.com>
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "snort_types.h"
+#include "snort_debug.h"
+
+#include "pop.h"
+#include "pop_module.h"
+#include "profiler.h"
+#include "stream/stream_api.h"
+#include "file_api/file_api.h"
+#include "parser.h"
+#include "framework/inspector.h"
+#include "utils/sfsnprintfappend.h"
+#include "target_based/sftarget_protocol_reference.h"
+#include "pop_paf.h"
+#include "search_engines/search_tool.h"
+#include "sf_email_attach_decode.h"
+#include "protocols/ssl.h"
+
+THREAD_LOCAL ProfileStats popPerfStats;
+THREAD_LOCAL SimpleStats popstats;
+
+POPToken pop_known_cmds[] =
+{
+ { "APOP", 4, CMD_APOP },
+ { "AUTH", 4, CMD_AUTH },
+ { "CAPA", 4, CMD_CAPA },
+ { "DELE", 4, CMD_DELE },
+ { "LIST", 4, CMD_LIST },
+ { "NOOP", 4, CMD_NOOP },
+ { "PASS", 4, CMD_PASS },
+ { "QUIT", 4, CMD_QUIT },
+ { "RETR", 4, CMD_RETR },
+ { "RSET", 4, CMD_RSET },
+ { "STAT", 4, CMD_STAT },
+ { "STLS", 4, CMD_STLS },
+ { "TOP", 3, CMD_TOP },
+ { "UIDL", 4, CMD_UIDL },
+ { "USER", 4, CMD_USER },
+ { NULL, 0, 0 }
+};
+
+POPToken pop_resps[] =
+{
+ { "+OK", 3, RESP_OK }, /* SUCCESS */
+ { "-ERR", 4, RESP_ERR }, /* FAILURE */
+ { NULL, 0, 0 }
+};
+
+SearchTool* pop_resp_search_mpse = nullptr;
+SearchTool* pop_cmd_search_mpse = nullptr;
+
+POPSearch pop_resp_search[RESP_LAST];
+POPSearch pop_cmd_search[CMD_LAST];
+THREAD_LOCAL const POPSearch* pop_current_search = NULL;
+THREAD_LOCAL POPSearchInfo pop_search_info;
+
+static void snort_pop(POP_PROTO_CONF* GlobalConf, Packet* p);
+static void POP_ResetState(void*);
+void POP_DecodeAlert(void* ds);
+
+MimeMethods pop_mime_methods = { NULL, NULL, POP_DecodeAlert, POP_ResetState, pop_is_data_end };
+
+unsigned PopFlowData::flow_id = 0;
+static POPData* get_session_data(Flow* flow)
+{
+ PopFlowData* fd = (PopFlowData*)flow->get_application_data(
+ PopFlowData::flow_id);
+
+ return fd ? &fd->session : NULL;
+}
+
+POPData* SetNewPOPData(POP_PROTO_CONF* config, Packet* p)
+{
+ POPData* pop_ssn;
+ PopFlowData* fd = new PopFlowData;
+
+ p->flow->set_application_data(fd);
+ pop_ssn = &fd->session;
+
+ pop_ssn->mime_ssn.log_config = &(config->log_config);
+ pop_ssn->mime_ssn.decode_conf = &(config->decode_conf);
+ pop_ssn->mime_ssn.methods = &(pop_mime_methods);
+ if (file_api->set_log_buffers(&(pop_ssn->mime_ssn.log_state), &(config->log_config)) < 0)
+ {
+ return NULL;
+ }
+
+ if (p->packet_flags & SSNFLAG_MIDSTREAM)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Got midstream packet - "
+ "setting state to unknown\n"); );
+ pop_ssn->state = STATE_UNKNOWN;
+ }
+
+ return pop_ssn;
+}
+
+void POP_DecodeAlert(void* ds)
+{
+ Email_DecodeState* decode_state = (Email_DecodeState*)ds;
+ switch ( decode_state->decode_type )
+ {
+ case DECODE_B64:
+ SnortEventqAdd(GID_POP, POP_B64_DECODING_FAILED);
+ break;
+ case DECODE_QP:
+ SnortEventqAdd(GID_POP, POP_QP_DECODING_FAILED);
+ break;
+ case DECODE_UU:
+ SnortEventqAdd(GID_POP, POP_UU_DECODING_FAILED);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void POP_SearchInit(void)
+{
+ const POPToken* tmp;
+ pop_cmd_search_mpse = new SearchTool();
+ if (pop_cmd_search_mpse == NULL)
+ {
+ FatalError("Could not allocate memory for POP Command search.\n");
+ }
+ for (tmp = &pop_known_cmds[0]; tmp->name != NULL; tmp++)
+ {
+ pop_cmd_search[tmp->search_id].name = tmp->name;
+ pop_cmd_search[tmp->search_id].name_len = tmp->name_len;
+ pop_cmd_search_mpse->add(tmp->name, tmp->name_len, tmp->search_id);
+ }
+ pop_cmd_search_mpse->prep();
+
+ pop_resp_search_mpse = new SearchTool();
+ if (pop_resp_search_mpse == NULL)
+ {
+ FatalError("Could not allocate memory for POP Response search.\n");
+ }
+ for (tmp = &pop_resps[0]; tmp->name != NULL; tmp++)
+ {
+ pop_resp_search[tmp->search_id].name = tmp->name;
+ pop_resp_search[tmp->search_id].name_len = tmp->name_len;
+ pop_resp_search_mpse->add(tmp->name, tmp->name_len, tmp->search_id);
+ }
+ pop_resp_search_mpse->prep();
+}
+
+void POP_SearchFree(void)
+{
+ if (pop_cmd_search_mpse != NULL)
+ delete pop_cmd_search_mpse;
+
+ if (pop_resp_search_mpse != NULL)
+ delete pop_resp_search_mpse;
+}
+
+/*
+* Reset POP session state
+*
+* @param none
+*
+* @return none
+*/
+static void POP_ResetState(void* ssn)
+{
+ POPData* pop_ssn = get_session_data((Flow*)ssn);
+ pop_ssn->state = STATE_COMMAND;
+ pop_ssn->prev_response = 0;
+ pop_ssn->state_flags = 0;
+}
+
+void POP_GetEOL(const uint8_t* ptr, const uint8_t* end,
+ const uint8_t** eol, const uint8_t** eolm)
+{
+ const uint8_t* tmp_eol;
+ const uint8_t* tmp_eolm;
+
+ /* XXX maybe should fatal error here since none of these
+ * * pointers should be NULL */
+ if (ptr == NULL || end == NULL || eol == NULL || eolm == NULL)
+ return;
+
+ tmp_eol = (uint8_t*)memchr(ptr, '\n', end - ptr);
+ if (tmp_eol == NULL)
+ {
+ tmp_eol = end;
+ tmp_eolm = end;
+ }
+ else
+ {
+ /* end of line marker (eolm) should point to marker and
+ * * end of line (eol) should point to end of marker */
+ if ((tmp_eol > ptr) && (*(tmp_eol - 1) == '\r'))
+ {
+ tmp_eolm = tmp_eol - 1;
+ }
+ else
+ {
+ tmp_eolm = tmp_eol;
+ }
+
+ /* move past newline */
+ tmp_eol++;
+ }
+
+ *eol = tmp_eol;
+ *eolm = tmp_eolm;
+}
+
+static void PrintPopConf(POP_PROTO_CONF* config)
+{
+ if (config == NULL)
+ return;
+
+ LogMessage("POP config: \n");
+
+ if (config->decode_conf.b64_depth > -1)
+ {
+ switch (config->decode_conf.b64_depth)
+ {
+ case 0:
+ LogMessage(" Base64 Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Base64 Decoding Depth: %d\n", config->decode_conf.b64_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Base64 Decoding: %s\n", "Disabled");
+
+ if (config->decode_conf.qp_depth > -1)
+ {
+ switch (config->decode_conf.qp_depth)
+ {
+ case 0:
+ LogMessage(" Quoted-Printable Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Quoted-Printable Decoding Depth: %d\n", config->decode_conf.qp_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Quoted-Printable Decoding: %s\n", "Disabled");
+ if (config->decode_conf.uu_depth > -1)
+ {
+ switch (config->decode_conf.uu_depth)
+ {
+ case 0:
+ LogMessage(" Unix-to-Unix Decoding Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Unix-to-Unix Decoding Depth: %d\n", config->decode_conf.uu_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Unix-to-Unix Decoding: %s\n", "Disabled");
+
+ if (config->decode_conf.bitenc_depth > -1)
+ {
+ switch (config->decode_conf.bitenc_depth)
+ {
+ case 0:
+ LogMessage(" Non-Encoded MIME attachment Extraction Depth: %s\n", "Unlimited");
+ break;
+ default:
+ LogMessage(" Non-Encoded MIME attachment Extraction Depth: %d\n",
+ config->decode_conf.bitenc_depth);
+ break;
+ }
+ }
+ else
+ LogMessage(" Non-Encoded MIME attachment Extraction: %s\n", "Disabled");
+
+ LogMessage("\n");
+}
+
+static inline int InspectPacket(Packet* p)
+{
+ return PacketHasPAFPayload(p);
+}
+
+static int POP_Setup(Packet* p, POPData* ssn)
+{
+ int pkt_dir;
+
+ /* Get the direction of the packet. */
+ if ( p->packet_flags & PKT_FROM_SERVER )
+ pkt_dir = POP_PKT_FROM_SERVER;
+ else
+ pkt_dir = POP_PKT_FROM_CLIENT;
+
+ if (!(ssn->session_flags & POP_FLAG_CHECK_SSL))
+ ssn->session_flags |= POP_FLAG_CHECK_SSL;
+ /* Check to see if there is a reassembly gap. If so, we won't know
+ * * what state we're in when we get the _next_ reassembled packet */
+ if ((pkt_dir != POP_PKT_FROM_SERVER) &&
+ (p->packet_flags & PKT_REBUILT_STREAM))
+ {
+ int missing_in_rebuilt =
+ stream.missing_in_reassembled(p->flow, SSN_DIR_FROM_CLIENT);
+
+ if (ssn->session_flags & POP_FLAG_NEXT_STATE_UNKNOWN)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Found gap in previous reassembly buffer - "
+ "set state to unknown\n"); );
+ ssn->state = STATE_UNKNOWN;
+ ssn->session_flags &= ~POP_FLAG_NEXT_STATE_UNKNOWN;
+ }
+
+ if (missing_in_rebuilt == SSN_MISSING_BEFORE)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Found missing packets before "
+ "in reassembly buffer - set state to unknown\n"); );
+ ssn->state = STATE_UNKNOWN;
+ }
+ }
+
+ return pkt_dir;
+}
+
+static int POP_SearchStrFound(void* id, void* , int index, void* , void* )
+{
+ int search_id = (int)(uintptr_t)id;
+
+ pop_search_info.id = search_id;
+ pop_search_info.index = index;
+ pop_search_info.length = pop_current_search[search_id].name_len;
+
+ /* Returning non-zero stops search, which is okay since we only look for one at a time */
+ return 1;
+}
+
+/*
+ * Handle COMMAND state
+ *
+ * @param p standard Packet structure
+ * @param ptr pointer into p->data buffer to start looking at data
+ * @param end points to end of p->data buffer
+ *
+ * @return pointer into p->data where we stopped looking at data
+ * will be end of line or end of packet
+ */
+static const uint8_t* POP_HandleCommand(Packet* p, POPData* pop_ssn, const uint8_t* ptr, const
+ uint8_t* end)
+{
+ const uint8_t* eol; /* end of line */
+ const uint8_t* eolm; /* end of line marker */
+ int cmd_found;
+
+ /* get end of line and end of line marker */
+ POP_GetEOL(ptr, end, &eol, &eolm);
+
+ /* TODO If the end of line marker coincides with the end of data we can't be
+ * sure that we got a command and not a substring which we could tell through
+ * inspection of the next packet. Maybe a command pending state where the first
+ * char in the next packet is checked for a space and end of line marker */
+
+ /* do not confine since there could be space chars before command */
+ pop_current_search = &pop_cmd_search[0];
+ cmd_found = pop_cmd_search_mpse->find(
+ (const char*)ptr, eolm - ptr, POP_SearchStrFound);
+ /* see if we actually found a command and not a substring */
+ if (cmd_found > 0)
+ {
+ const uint8_t* tmp = ptr;
+ const uint8_t* cmd_start = ptr + pop_search_info.index;
+ const uint8_t* cmd_end = cmd_start + pop_search_info.length;
+
+ /* move past spaces up until start of command */
+ while ((tmp < cmd_start) && isspace((int)*tmp))
+ tmp++;
+
+ /* if not all spaces before command, we found a
+ * substring */
+ if (tmp != cmd_start)
+ cmd_found = 0;
+
+ /* if we're before the end of line marker and the next
+ * character is not whitespace, we found a substring */
+ if ((cmd_end < eolm) && !isspace((int)*cmd_end))
+ cmd_found = 0;
+
+ /* there is a chance that end of command coincides with the end of data
+ * in which case, it could be a substring, but for now, we will treat it as found */
+ }
+
+ /* if command not found, alert and move on */
+ if (!cmd_found)
+ {
+ if (pop_ssn->state == STATE_UNKNOWN)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Command not found, but state is "
+ "unknown - checking for SSL\n"); );
+
+ /* check for encrypted */
+
+ if ((pop_ssn->session_flags & POP_FLAG_CHECK_SSL) &&
+ (IsSSL(ptr, end - ptr, p->packet_flags)))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Packet is SSL encrypted\n"); );
+
+ pop_ssn->state = STATE_TLS_DATA;
+
+ /* Ignore data */
+ return end;
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Not SSL - try data state\n"); );
+ /* don't check for ssl again in this packet */
+ if (pop_ssn->session_flags & POP_FLAG_CHECK_SSL)
+ pop_ssn->session_flags &= ~POP_FLAG_CHECK_SSL;
+
+ pop_ssn->state = STATE_DATA;
+ //pop_ssn->data_state = STATE_DATA_UNKNOWN;
+
+ return ptr;
+ }
+ }
+ else
+ {
+ SnortEventqAdd(GID_POP, POP_UNKNOWN_CMD);
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "No known command found\n"); );
+ return eol;
+ }
+ }
+ else if (pop_search_info.id == CMD_TOP)
+ {
+ pop_ssn->state = STATE_DATA;
+ }
+ else
+ {
+ if (pop_ssn->state == STATE_UNKNOWN)
+ pop_ssn->state = STATE_COMMAND;
+ }
+
+ if (pop_search_info.id == CMD_STLS)
+ {
+ if (eol == end)
+ pop_ssn->state = STATE_TLS_CLIENT_PEND;
+ }
+
+ return eol;
+}
+
+/*
+ * Process client packet
+ *
+ * @param packet standard Packet structure
+ *
+ * @return none
+ */
+static void POP_ProcessClientPacket(Packet* p, POPData* pop_ssn)
+{
+ const uint8_t* ptr = p->data;
+ const uint8_t* end = p->data + p->dsize;
+
+ ptr = POP_HandleCommand(p, pop_ssn, ptr, end);
+}
+
+/*
+ * Process server packet
+ *
+ * @param packet standard Packet structure
+ *
+ */
+static void POP_ProcessServerPacket(Packet* p, POPData* pop_ssn)
+{
+ int resp_found;
+ const uint8_t* ptr;
+ const uint8_t* end;
+ const uint8_t* eolm;
+ const uint8_t* eol;
+ int resp_line_len;
+ const char* tmp = NULL;
+
+ ptr = p->data;
+ end = p->data + p->dsize;
+
+ while (ptr < end)
+ {
+ if (pop_ssn->state == STATE_DATA)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "DATA STATE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
+ //ptr = POP_HandleData(p, ptr, end);
+ ptr = file_api->process_mime_data(p, ptr, end, &(pop_ssn->mime_ssn), 0, true);
+ continue;
+ }
+ POP_GetEOL(ptr, end, &eol, &eolm);
+
+ resp_line_len = eol - ptr;
+
+ /* Check for response code */
+ pop_current_search = &pop_resp_search[0];
+ resp_found = pop_resp_search_mpse->find(
+ (const char*)ptr, resp_line_len, POP_SearchStrFound);
+
+ if (resp_found > 0)
+ {
+ const uint8_t* cmd_start = ptr + pop_search_info.index;
+ switch (pop_search_info.id)
+ {
+ case RESP_OK:
+ tmp = SnortStrcasestr((const char*)cmd_start, (eol - cmd_start), "octets");
+ if (tmp != NULL)
+ pop_ssn->state = STATE_DATA;
+ else
+ {
+ pop_ssn->prev_response = RESP_OK;
+ pop_ssn->state = STATE_UNKNOWN;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP,
+ "Server response not found - see if it's SSL data\n"); );
+
+ if ((pop_ssn->session_flags & POP_FLAG_CHECK_SSL) &&
+ (IsSSL(ptr, end - ptr, p->packet_flags)))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Server response is an SSL packet\n"); );
+
+ pop_ssn->state = STATE_TLS_DATA;
+
+ return;
+ }
+ else if (pop_ssn->session_flags & POP_FLAG_CHECK_SSL)
+ {
+ pop_ssn->session_flags &= ~POP_FLAG_CHECK_SSL;
+ }
+ if (pop_ssn->prev_response == RESP_OK)
+ {
+ {
+ pop_ssn->state = STATE_DATA;
+ pop_ssn->prev_response = 0;
+ continue;
+ }
+ }
+ else if (*ptr == '+')
+ {
+ SnortEventqAdd(GID_POP, POP_UNKNOWN_RESP);
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Server response not found\n"); );
+ }
+ }
+
+ ptr = eol;
+ }
+}
+
+/* Main runtime entry point for POP preprocessor.
+ * Analyzes POP packets for anomalies/exploits.
+ *
+ * PARAMETERS:
+ *
+ * p: Pointer to current packet to process.
+ * contextp: Pointer to context block, not used.
+ *
+ * RETURNS: Nothing.
+ */
+static void snort_pop(POP_PROTO_CONF* config, Packet* p)
+{
+ POPData* pop_ssn = NULL;
+ int pkt_dir;
+
+ /* Attempt to get a previously allocated POP block. */
+ pop_ssn = get_session_data(p->flow);
+
+ if (pop_ssn == NULL)
+ {
+ /* Check the stream session. If it does not currently
+ * have our POP data-block attached, create one.
+ */
+ pop_ssn = SetNewPOPData(config, p);
+
+ if ( !pop_ssn )
+ {
+ /* Could not get/create the session data for this packet. */
+ return;
+ }
+ }
+
+ pkt_dir = POP_Setup(p, pop_ssn);
+
+ if (pkt_dir == POP_PKT_FROM_CLIENT)
+ {
+ /* This packet should be a tls client hello */
+ if (pop_ssn->state == STATE_TLS_CLIENT_PEND)
+ {
+ if (IsTlsClientHello(p->data, p->data + p->dsize))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP,
+ "TLS DATA STATE ~~~~~~~~~~~~~~~~~~~~~~~~~\n"); );
+
+ pop_ssn->state = STATE_TLS_SERVER_PEND;
+ return;
+ }
+ else
+ {
+ /* reset state - server may have rejected STARTTLS command */
+ pop_ssn->state = STATE_UNKNOWN;
+ }
+ }
+ if ((pop_ssn->state == STATE_TLS_DATA)
+ || (pop_ssn->state == STATE_TLS_SERVER_PEND))
+ {
+ return;
+ }
+ POP_ProcessClientPacket(p, pop_ssn);
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "POP client packet\n"); );
+ }
+ else
+ {
+ if (pop_ssn->state == STATE_TLS_SERVER_PEND)
+ {
+ if (IsTlsServerHello(p->data, p->data + p->dsize))
+ {
+ pop_ssn->state = STATE_TLS_DATA;
+ }
+ else if (!(stream.get_session_flags(p->flow) & SSNFLAG_MIDSTREAM)
+ && !stream.missed_packets(p->flow, SSN_DIR_BOTH))
+ {
+ /* revert back to command state - assume server didn't accept STARTTLS */
+ pop_ssn->state = STATE_UNKNOWN;
+ }
+ else
+ return;
+ }
+
+ if (pop_ssn->state == STATE_TLS_DATA)
+ {
+ return;
+ }
+ if ( !InspectPacket(p))
+ {
+ /* Packet will be rebuilt, so wait for it */
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Client packet will be reassembled\n"));
+ return;
+ }
+ else if (!(p->packet_flags & PKT_REBUILT_STREAM))
+ {
+ /* If this isn't a reassembled packet and didn't get
+ * inserted into reassembly buffer, there could be a
+ * problem. If we miss syn or syn-ack that had window
+ * scaling this packet might not have gotten inserted
+ * into reassembly buffer because it fell outside of
+ * window, because we aren't scaling it */
+ pop_ssn->session_flags |= POP_FLAG_GOT_NON_REBUILT;
+ pop_ssn->state = STATE_UNKNOWN;
+ }
+ else if (pop_ssn->session_flags & POP_FLAG_GOT_NON_REBUILT)
+ {
+ /* This is a rebuilt packet. If we got previous packets
+ * that were not rebuilt, state is going to be messed up
+ * so set state to unknown. It's likely this was the
+ * beginning of the conversation so reset state */
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Got non-rebuilt packets before "
+ "this rebuilt packet\n"); );
+
+ pop_ssn->state = STATE_UNKNOWN;
+ pop_ssn->session_flags &= ~POP_FLAG_GOT_NON_REBUILT;
+ }
+ /* Process as a server packet */
+ POP_ProcessServerPacket(p, pop_ssn);
+ }
+}
+
+//-------------------------------------------------------------------------
+// class stuff
+//-------------------------------------------------------------------------
+
+class Pop : public Inspector
+{
+public:
+ Pop(POP_PROTO_CONF*);
+ ~Pop();
+
+ bool configure(SnortConfig*) override;
+ void show(SnortConfig*) override;
+ void eval(Packet*) override;
+
+ StreamSplitter* get_splitter(bool c2s) override
+ { return new PopSplitter(c2s); }
+
+private:
+ POP_PROTO_CONF* config;
+};
+
+Pop::Pop(POP_PROTO_CONF* pc)
+{
+ config = pc;
+}
+
+Pop::~Pop()
+{
+ if ( config )
+ delete config;
+}
+
+bool Pop::configure(SnortConfig* )
+{
+ config->decode_conf.file_depth = file_api->get_max_file_depth();
+
+ if (config->decode_conf.file_depth > 0)
+ config->log_config.log_filename = 1;
+
+ if (file_api->is_decoding_enabled(&config->decode_conf) )
+ {
+ updateMaxDepth(config->decode_conf.file_depth,
+ &config->decode_conf.max_depth);
+ }
+ file_api->check_decode_config(&config->decode_conf);
+ return true;
+}
+
+void Pop::show(SnortConfig*)
+{
+ PrintPopConf(config);
+}
+
+void Pop::eval(Packet* p)
+{
+ PROFILE_VARS;
+ // precondition - what we registered for
+ assert(p->is_tcp() && p->dsize && p->data);
+
+ ++popstats.total_packets;
+
+ MODULE_PROFILE_START(popPerfStats);
+
+ snort_pop(config, p);
+
+ MODULE_PROFILE_END(popPerfStats);
+}
+
+//-------------------------------------------------------------------------
+// api stuff
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{ return new PopModule; }
+
+static void mod_dtor(Module* m)
+{ delete m; }
+
+static void pop_init()
+{
+ PopFlowData::init();
+ POP_SearchInit();
+}
+
+static void pop_term()
+{
+ POP_SearchFree();
+}
+
+static Inspector* pop_ctor(Module* m)
+{
+ PopModule* mod = (PopModule*)m;
+ return new Pop(mod->get_data());
+}
+
+static void pop_dtor(Inspector* p)
+{
+ delete p;
+}
+
+const InspectApi pop_api =
+{
+ {
+ PT_INSPECTOR,
+ sizeof(InspectApi),
+ INSAPI_VERSION,
+ 0,
+ API_RESERVED,
+ API_OPTIONS,
+ POP_NAME,
+ POP_HELP,
+ mod_ctor,
+ mod_dtor
+ },
+ IT_SERVICE,
+ (uint16_t)PktType::TCP,
+ nullptr, // buffers
+ "pop",
+ pop_init,
+ pop_term, // pterm
+ nullptr, // tinit
+ nullptr, // tterm
+ pop_ctor,
+ pop_dtor,
+ nullptr, // ssn
+ nullptr // reset
+};
+
+#ifdef BUILDING_SO
+SO_PUBLIC const BaseApi* snort_plugins[] =
+{
+ &pop_api.base,
+ nullptr
+};
+#else
+const BaseApi* sin_pop = &pop_api.base;
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+
+/*
+ * pop.h: Definitions, structs, function prototype(s) for
+ * the POP service inspectors.
+ * Author: Bhagyashree Bantwal <bbantwal@cisco.com>
+ */
+
+#ifndef POP_H
+#define POP_H
+
+#include "protocols/packet.h"
+#include "stream/stream_api.h"
+#include "profiler.h"
+#include "pop_config.h"
+/* Direction packet is coming from, if we can figure it out */
+#define POP_PKT_FROM_UNKNOWN 0
+#define POP_PKT_FROM_CLIENT 1
+#define POP_PKT_FROM_SERVER 2
+
+#define SEARCH_CMD 0
+#define SEARCH_RESP 1
+#define SEARCH_HDR 2
+#define SEARCH_DATA_END 3
+#define NUM_SEARCHES 4
+
+#define BOUNDARY 0
+
+#define STATE_DATA 0 /* Data state */
+#define STATE_TLS_CLIENT_PEND 1 /* Got STARTTLS */
+#define STATE_TLS_SERVER_PEND 2 /* Got STARTTLS */
+#define STATE_TLS_DATA 3 /* Successful handshake, TLS encrypted data */
+#define STATE_COMMAND 4
+#define STATE_UNKNOWN 5
+
+#define STATE_DATA_INIT 0
+#define STATE_DATA_HEADER 1 /* Data header section of data state */
+#define STATE_DATA_BODY 2 /* Data body section of data state */
+#define STATE_MIME_HEADER 3 /* MIME header section within data section */
+#define STATE_DATA_UNKNOWN 4
+
+/* session flags */
+#define POP_FLAG_NEXT_STATE_UNKNOWN 0x00000004
+#define POP_FLAG_GOT_NON_REBUILT 0x00000008
+#define POP_FLAG_CHECK_SSL 0x00000010
+
+/* Maximum length of header chars before colon, based on Exim 4.32 exploit */
+#define MAX_HEADER_NAME_LEN 64
+typedef enum _POPCmdEnum
+{
+ CMD_APOP = 0,
+ CMD_AUTH,
+ CMD_CAPA,
+ CMD_DELE,
+ CMD_LIST,
+ CMD_NOOP,
+ CMD_PASS,
+ CMD_QUIT,
+ CMD_RETR,
+ CMD_RSET,
+ CMD_STAT,
+ CMD_STLS,
+ CMD_TOP,
+ CMD_UIDL,
+ CMD_USER,
+ CMD_LAST
+} POPCmdEnum;
+
+typedef enum _POPRespEnum
+{
+ RESP_OK = 1,
+ RESP_ERR,
+ RESP_LAST
+} POPRespEnum;
+
+typedef enum _POPHdrEnum
+{
+ HDR_CONTENT_TYPE = 0,
+ HDR_CONT_TRANS_ENC,
+ HDR_CONT_DISP,
+ HDR_LAST
+} POPHdrEnum;
+
+struct POPSearch
+{
+ const char* name;
+ int name_len;
+};
+
+struct POPToken
+{
+ const char* name;
+ int name_len;
+ int search_id;
+};
+
+struct POPCmdConfig
+{
+ char alert; /* 1 if alert when seen */
+ char normalize; /* 1 if we should normalize this command */
+ int max_line_len; /* Max length of this particular command */
+};
+
+struct POPSearchInfo
+{
+ int id;
+ int index;
+ int length;
+};
+
+struct POPData
+{
+ int state;
+ int prev_response;
+ int state_flags;
+ int session_flags;
+ MimeState mime_ssn;
+};
+
+class PopFlowData : public FlowData
+{
+public:
+ PopFlowData() : FlowData(flow_id)
+ { memset(&session, 0, sizeof(session)); }
+
+ ~PopFlowData() { }
+
+ static void init()
+ { flow_id = FlowData::get_flow_id(); }
+
+public:
+ static unsigned flow_id;
+ POPData session;
+};
+
+#endif /* POP_H */
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+
+#ifndef POP_CONFIG_H
+#define POP_CONFIG_H
+
+#include "file_api/file_api.h"
+
+struct POP_PROTO_CONF
+{
+ uint32_t memcap;
+ DecodeConfig decode_conf;
+ MAIL_LogConfig log_config;
+};
+
+#endif
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// pop_module.cc author Bhagyashree Bantwal <bbantwal@cisco.com>
+
+#include "pop_module.h"
+#include <assert.h>
+#include <sstream>
+#include "main/snort_config.h"
+
+using namespace std;
+
+#define POP_UNKNOWN_CMD_STR "Unknown POP3 command"
+#define POP_UNKNOWN_RESP_STR "Unknown POP3 response"
+#define POP_B64_DECODING_FAILED_STR "Base64 Decoding failed."
+#define POP_QP_DECODING_FAILED_STR "Quoted-Printable Decoding failed."
+#define POP_UU_DECODING_FAILED_STR "Unix-to-Unix Decoding failed."
+
+static const Parameter s_params[] =
+{
+ { "b64_decode_depth", Parameter::PT_INT, "-1:65535", "1460",
+ " base64 decoding depth" },
+
+ { "bitenc_decode_depth", Parameter::PT_INT, "-1:65535", "1460",
+ " Non-Encoded MIME attachment extraction depth" },
+
+ { "qp_decode_depth", Parameter::PT_INT, "-1:65535", "1460",
+ " Quoted Printable decoding depth" },
+
+ { "uu_decode_depth", Parameter::PT_INT, "-1:65535", "1460",
+ " Unix-to-Unix decoding depth" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+static const RuleMap pop_rules[] =
+{
+ { POP_UNKNOWN_CMD, POP_UNKNOWN_CMD_STR },
+ { POP_UNKNOWN_RESP, POP_UNKNOWN_RESP_STR },
+ { POP_B64_DECODING_FAILED, POP_B64_DECODING_FAILED_STR },
+ { POP_QP_DECODING_FAILED, POP_QP_DECODING_FAILED_STR },
+ { POP_UU_DECODING_FAILED, POP_UU_DECODING_FAILED_STR },
+
+ { 0, nullptr }
+};
+
+//-------------------------------------------------------------------------
+// pop module
+//-------------------------------------------------------------------------
+
+PopModule::PopModule() : Module(POP_NAME, POP_HELP, s_params)
+{
+ config = nullptr;
+}
+
+PopModule::~PopModule()
+{
+ if ( config )
+ delete config;
+}
+
+const RuleMap* PopModule::get_rules() const
+{ return pop_rules; }
+
+const PegInfo* PopModule::get_pegs() const
+{ return simple_pegs; }
+
+PegCount* PopModule::get_counts() const
+{ return (PegCount*)&popstats; }
+
+ProfileStats* PopModule::get_profile() const
+{ return &popPerfStats; }
+
+bool PopModule::set(const char*, Value& v, SnortConfig*)
+{
+ if ( v.is("b64_decode_depth") )
+ {
+ int decode_depth = v.get_long();
+
+ if ((decode_depth > 0) && (decode_depth & 3))
+ {
+ decode_depth += 4 - (decode_depth & 3);
+ if (decode_depth > 65535 )
+ {
+ decode_depth = decode_depth - 4;
+ }
+ LogMessage("WARNING: POP: 'b64_decode_depth' is not a multiple of 4. "
+ "Rounding up to the next multiple of 4. The new 'b64_decode_depth' is %d.\n",
+ decode_depth);
+ }
+ config->decode_conf.b64_depth = decode_depth;
+ }
+ else if ( v.is("bitenc_decode_depth") )
+ config->decode_conf.bitenc_depth = v.get_long();
+
+ else if ( v.is("qp_decode_depth") )
+ config->decode_conf.qp_depth = v.get_long();
+
+ else if ( v.is("uu_decode_depth") )
+ config->decode_conf.uu_depth = v.get_long();
+
+ else
+ return false;
+
+ return true;
+}
+
+POP_PROTO_CONF* PopModule::get_data()
+{
+ POP_PROTO_CONF* tmp = config;
+ config = nullptr;
+ return tmp;
+}
+
+bool PopModule::begin(const char*, int, SnortConfig*)
+{
+ config = new POP_PROTO_CONF;
+ file_api->set_mime_decode_config_defauts(&(config->decode_conf));
+ file_api->set_mime_log_config_defauts(&(config->log_config));
+
+ return true;
+}
+
+bool PopModule::end(const char*, int, SnortConfig*)
+{
+ return true;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// pop_module.h author Bhagyashree Bantwal <bbantwal@cisco.com>
+
+#ifndef POP_MODULE_H
+#define POP_MODULE_H
+
+#include "framework/module.h"
+#include "framework/bits.h"
+#include "main/thread.h"
+#include "pop_config.h"
+
+#define GID_POP 142
+
+#define POP_UNKNOWN_CMD 1
+#define POP_UNKNOWN_RESP 2
+#define POP_B64_DECODING_FAILED 4
+#define POP_QP_DECODING_FAILED 5
+#define POP_UU_DECODING_FAILED 7
+
+#define POP_NAME "pop"
+#define POP_HELP "pop inspection"
+
+struct SnortConfig;
+
+extern THREAD_LOCAL SimpleStats popstats;
+extern THREAD_LOCAL ProfileStats popPerfStats;
+
+class PopModule : public Module
+{
+public:
+ PopModule();
+ ~PopModule();
+
+ bool set(const char*, Value&, SnortConfig*) override;
+ bool begin(const char*, int, SnortConfig*) override;
+ bool end(const char*, int, SnortConfig*) override;
+
+ unsigned get_gid() const override
+ { return GID_POP; }
+
+ const RuleMap* get_rules() const override;
+ const PegInfo* get_pegs() const override;
+ PegCount* get_counts() const override;
+ ProfileStats* get_profile() const override;
+
+ POP_PROTO_CONF* get_data();
+
+private:
+ POP_PROTO_CONF* config;
+};
+
+#endif
+
--- /dev/null
+/****************************************************************************
+ * Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation. You may not use, modify or
+ * distribute this program under any other version of the GNU General
+ * Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ ****************************************************************************/
+
+#include <sys/types.h>
+#include "snort_types.h"
+#include "snort_debug.h"
+
+#include "pop_paf.h"
+#include "pop.h"
+
+extern POPToken pop_known_cmds[];
+
+static inline PopPafData* get_state(Flow* flow, bool c2s)
+{
+ if ( !flow )
+ return nullptr;
+
+ PopSplitter* s = (PopSplitter*)stream.get_splitter(flow, c2s);
+ return s ? &s->state : nullptr;
+}
+
+/*
+ * read process_command() description below
+ */
+static bool search_for_command(PopPafData* pfdata, const uint8_t ch)
+{
+ char val = *(pfdata->cmd_state.next_letter);
+
+ // if end of command && data contains a space or newline
+ if (val == '\0' && (isblank(ch) || ch == '\r' || ch == '\n'))
+ {
+ if (pfdata->cmd_state.exp_resp == POP_PAF_HAS_ARG)
+ {
+ pfdata->cmd_state.status = POP_CMD_ARG;
+ }
+ else
+ {
+ pfdata->cmd_state.status = POP_CMD_FIN;
+ pfdata->pop_state = pfdata->cmd_state.exp_resp;
+ return true;
+ }
+ }
+ else if (toupper(ch) == toupper(val) )
+ {
+ pfdata->cmd_state.next_letter++;
+ }
+ else
+ {
+ pfdata->cmd_state.status = POP_CMD_FIN;
+ }
+
+ return false;
+}
+
+/*
+ * read process_command() description below
+ */
+static bool init_command_search(PopPafData* pfdata, const uint8_t ch)
+{
+ pfdata->pop_state = POP_PAF_SINGLE_LINE_STATE;
+
+ switch (ch)
+ {
+ case 'c':
+ case 'C':
+ pfdata->cmd_state.exp_resp = POP_PAF_MULTI_LINE_STATE;
+ pfdata->cmd_state.next_letter = &(pop_known_cmds[CMD_CAPA].name[1]);
+ break;
+ case 'l':
+ case 'L':
+ pfdata->cmd_state.exp_resp = POP_PAF_HAS_ARG;
+ pfdata->cmd_state.next_letter = &(pop_known_cmds[CMD_LIST].name[1]);
+ break;
+ case 'r':
+ case 'R':
+ pfdata->cmd_state.exp_resp = POP_PAF_DATA_STATE;
+ pfdata->cmd_state.next_letter = &(pop_known_cmds[CMD_RETR].name[1]);
+ break;
+ case 't':
+ case 'T':
+ pfdata->cmd_state.exp_resp = POP_PAF_DATA_STATE;
+ pfdata->cmd_state.next_letter = &(pop_known_cmds[CMD_TOP].name[1]);
+ break;
+ case 'u':
+ case 'U':
+ pfdata->cmd_state.exp_resp = POP_PAF_HAS_ARG;
+ pfdata->cmd_state.next_letter = &(pop_known_cmds[CMD_UIDL].name[1]);
+ break;
+ default:
+ pfdata->cmd_state.status = POP_CMD_FIN;
+ }
+
+ return false;
+}
+
+/*
+ * Attempts to determine the current command based upon the given character
+ * If another character is required to determine the current command,
+ * sets the function pointer to the correct next state
+ *
+ * PARAMS:
+ * pop_cmd - a pointer to the struct containing all of the
+ * relevant parsing info
+ * ch - the first character from the clients command
+ * RETURNS
+ * true - if the expected response is NOT a single line
+ * false - otherwise
+ */
+static inline bool process_command(PopPafData* pfdata, const uint8_t ch)
+{
+ if (pfdata->cmd_state.next_letter)
+ return search_for_command(pfdata, ch);
+ else
+ return init_command_search(pfdata, ch);
+}
+
+static inline void reset_data_states(PopPafData* pfdata)
+{
+ // reset MIME info
+ file_api->reset_mime_paf_state(&(pfdata->data_info));
+
+ // reset general pop fields
+ pfdata->cmd_continued = false;
+ pfdata->end_state = PAF_DATA_END_UNKNOWN;
+ pfdata->pop_state = POP_PAF_SINGLE_LINE_STATE;
+}
+
+/*
+ * Checks if the current data is a valid response.
+ * According to RFC 1939, every response begins with either
+ * +OK
+ * -ERR.
+ *
+ * RETURNS:
+ * true - if the character is a +
+ * false - if the character is anything else
+ */
+static inline int valid_response(const uint8_t data)
+{
+ return (data == '+');
+}
+
+/*
+ * Client PAF calls this command to set the server's state. This is the
+ * function which ensure's the server know the correct expected
+ * DATA
+ */
+static inline void set_server_state(Flow* ssn, PopExpectedResp state)
+{
+ PopPafData* server_data = get_state(ssn, false);
+
+ // ERROR IF SERVER DATA DOES NOT EXIST!! SHOULD NOT BE POSSIBLE!!
+ if (server_data)
+ {
+ reset_data_states(server_data);
+ server_data->end_of_data = false;
+ server_data->pop_state = state;
+ }
+}
+
+/*
+ * A helper function to reset the client's command parsing
+ * information
+ */
+static inline void reset_client_cmd_info(PopPafData* pfdata)
+{
+ pfdata->cmd_state.next_letter = '\0';
+ pfdata->cmd_state.status = POP_CMD_SEARCH;
+}
+
+/*
+ * Statefully search for the termination sequence CRCL.CRLF ("\r\n.\r\n").
+ *
+ * PARAMS:
+ * mime_data : true if this is mime_data.
+ *
+ * RETURNS:
+ * 0 - if termination sequence not found
+ * 1 - if termination sequence found
+ */
+static bool find_data_end_multi_line(PopPafData* pfdata, const uint8_t ch, bool mime_data)
+{
+ // TODO: This will currently flush on MIME boundary, and one line later at end of PDU
+
+ if (file_api->check_data_end(&(pfdata->end_state), ch))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "End of Multi-line response found\n"); );
+ pfdata->end_of_data = true;
+ pfdata->pop_state = POP_PAF_SINGLE_LINE_STATE;
+ reset_data_states(pfdata);
+ return true;
+ }
+
+ // if this is a data command, search for MIME ending
+ if (mime_data)
+ {
+ if (file_api->process_mime_paf_data(&(pfdata->data_info), ch))
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "Mime Boundary found. Flushing data!\n"); );
+ pfdata->cmd_continued = true;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Statefully search for the termination sequence LF ("\n"). Will also
+ * set the correct response state.
+ *
+ * PARAMS:
+ *
+ * RETURNS:
+ * 0 - if terminatino sequence not found
+ * 1 - if termination sequence found
+ */
+static inline bool find_data_end_single_line(PopPafData* pfdata, const uint8_t ch, bool client)
+{
+ if (ch == '\n')
+ {
+ // reset the correct information
+ if (client)
+ reset_client_cmd_info(pfdata);
+ else
+ reset_data_states(pfdata);
+
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "End of single-line response "
+ "found. Flushing data!\n"); );
+ return true;
+ }
+
+ return false;
+}
+
+static StreamSplitter::Status pop_paf_server(PopPafData* pfdata,
+ const uint8_t* data, uint32_t len, uint32_t* fp)
+{
+ uint32_t i;
+ uint32_t boundary_start = 0;
+
+ // if a negative response was received, it will be a one line response.
+ if (!pfdata->cmd_continued && !valid_response(*data))
+ pfdata->pop_state = POP_PAF_SINGLE_LINE_STATE;
+
+ for (i = 0; i < len; i++)
+ {
+ uint8_t ch = data[i];
+
+ // find the termination sequence based upon the current state
+ switch (pfdata->pop_state)
+ {
+ case POP_PAF_MULTI_LINE_STATE:
+ if ( find_data_end_multi_line(pfdata, ch, false) )
+ {
+ *fp = i + 1;
+ return StreamSplitter::FLUSH;
+ }
+ break;
+
+ case POP_PAF_DATA_STATE:
+ // TODO --> statefully get length
+ if ( find_data_end_multi_line(pfdata, ch, true) )
+ {
+ *fp = i + 1;
+ return StreamSplitter::FLUSH;
+ }
+
+ if (pfdata->data_info.boundary_state == MIME_PAF_BOUNDARY_UNKNOWN)
+ boundary_start = i;
+
+ break;
+
+ case POP_PAF_SINGLE_LINE_STATE:
+ default:
+ if ( find_data_end_single_line(pfdata, ch, false) )
+ {
+ *fp = i + 1;
+ return StreamSplitter::FLUSH;
+ }
+ break;
+ }
+ }
+
+ pfdata->cmd_continued = true;
+
+ if ( scanning_boundary(&pfdata->data_info, boundary_start, fp) )
+ return StreamSplitter::LIMIT;
+
+ return StreamSplitter::SEARCH;
+}
+
+/*
+ * Determine the Client's command and set the response state.
+ * Flush data when "\r\n" is received
+ */
+static StreamSplitter::Status pop_paf_client(Flow* ssn, PopPafData* pfdata,
+ const uint8_t* data, uint32_t len, uint32_t* fp)
+{
+ uint32_t i;
+
+ // TODO ... ensure current command is smaller than max command length
+
+ for (i = 0; i < len; i++)
+ {
+ uint8_t ch = data[i];
+
+ switch (pfdata->cmd_state.status)
+ {
+ case POP_CMD_SEARCH:
+ if (process_command(pfdata, ch) )
+ {
+ set_server_state(ssn, pfdata->pop_state);
+ }
+
+ //break; DO NOT UNCOMMENT!! both cases should check for a LF.
+
+ case POP_CMD_FIN:
+ if (find_data_end_single_line(pfdata, ch, true) )
+ {
+ // reset command parsing data
+ *fp = i + 1;
+ return StreamSplitter::FLUSH;
+ }
+ break;
+
+ case POP_CMD_ARG:
+ if (find_data_end_single_line(pfdata, ch, true))
+ {
+ set_server_state(ssn, POP_PAF_MULTI_LINE_STATE);
+ *fp = i + 1;
+ return StreamSplitter::FLUSH;
+ }
+ else if (isdigit(ch))
+ {
+ pfdata->cmd_state.status = POP_CMD_FIN;
+ }
+ }
+ }
+
+ return StreamSplitter::SEARCH;
+}
+
+//--------------------------------------------------------------------
+// callback for stateful scanning of in-order raw payload
+//--------------------------------------------------------------------
+
+PopSplitter::PopSplitter(bool c2s) : StreamSplitter(c2s)
+{
+ memset(&state, 0, sizeof(state));
+ reset_data_states(&state);
+}
+
+PopSplitter::~PopSplitter() { }
+
+/* Function: pop_paf()
+
+ Purpose: POP PAF callback.
+ Inspects pop traffic. Checks client traffic for the current command
+ and sets correct server termination sequence. Client side data will
+ flush after receiving CRLF ("\r\n"). Server data flushes after
+ finding set termination sequence.
+
+ Arguments:
+ void * - stream5 session pointer
+ void ** - DNP3 state tracking structure
+ const uint8_t * - payload data to inspect
+ uint32_t - length of payload data
+ uint32_t - flags to check whether client or server
+ uint32_t * - pointer to set flush point
+
+ Returns:
+ StreamSplitter::Status - StreamSplitter::FLUSH if flush point found, StreamSplitter::SEARCH otherwise
+*/
+
+StreamSplitter::Status PopSplitter::scan(
+ Flow* ssn, const uint8_t* data, uint32_t len,
+ uint32_t flags, uint32_t* fp)
+{
+ PopPafData* pfdata = &state;
+
+ if (flags & PKT_FROM_SERVER)
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "PAF: From server.\n"); );
+ return pop_paf_server(pfdata, data, len, fp);
+ }
+ else
+ {
+ DEBUG_WRAP(DebugMessage(DEBUG_POP, "PAF: From client.\n"); );
+ return pop_paf_client(ssn, pfdata, data, len, fp);
+ }
+}
+
+bool pop_is_data_end(void* session)
+{
+ Flow* ssn = (Flow*)session;
+ PopPafData* s = get_state(ssn, false);
+ return s->end_of_data;
+}
+
--- /dev/null
+/****************************************************************************
+ * Copyright (C) 2015 Cisco and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation. You may not use, modify or
+ * distribute this program under any other version of the GNU General
+ * Public License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ ****************************************************************************/
+
+#ifndef POP_PAF_H
+#define POP_PAF_H
+
+#include "snort_types.h"
+#include "stream/stream_api.h"
+#include "stream/stream_splitter.h"
+#include "file_api/file_api.h"
+
+/* Structure used to record expected server termination sequence */
+enum PopExpectedResp
+{
+ POP_PAF_SINGLE_LINE_STATE, /* server response will end with \r\n */
+ POP_PAF_MULTI_LINE_STATE, /* server response will end with \r\n.\r\n */
+ POP_PAF_DATA_STATE, /* Indicated MIME will be contained in response */
+ POP_PAF_HAS_ARG /* Intermediate state when parsing LIST */
+};
+
+enum PopParseCmdState
+{
+ POP_CMD_SEARCH, /* Search for Command */
+ POP_CMD_FIN, /* Found space. Finished parsing Command */
+ POP_CMD_ARG /* Parsing command with multi-line response iff arg given */
+};
+
+/* saves data when parsing client commands */
+struct PopPafParseCmd
+{
+ const char* next_letter; /* a pointer to the current commands data */
+ PopExpectedResp exp_resp; /* the expected termination sequence for this command */
+ PopParseCmdState status; /* whether the current has already been found */
+};
+
+/* State tracker for POP PAF */
+struct PopPafData
+{
+ PopExpectedResp pop_state; /* The current POP PAF state. */
+ PopPafParseCmd cmd_state; /* all of the command parsing data */
+ DataEndState end_state; /* Current termination sequence state */
+ MimeDataPafInfo data_info; /* Mime Information */
+ bool cmd_continued; /* data continued from previous packet? */
+ bool end_of_data;
+};
+
+class PopSplitter : public StreamSplitter
+{
+public:
+ PopSplitter(bool c2s);
+ ~PopSplitter();
+
+ Status scan(Flow*, const uint8_t* data, uint32_t len,
+ uint32_t flags, uint32_t* fp) override;
+
+ virtual bool is_paf() override { return true; }
+
+public:
+ PopPafData state;
+};
+
+bool pop_is_data_end(void* ssn);
+
+#endif
+
extern const BaseApi* sin_ftp_client;
extern const BaseApi* sin_ftp_server;
extern const BaseApi* sin_ftp_data;
+extern const BaseApi* sin_imap;
extern const BaseApi* sin_nhttp;
+extern const BaseApi* sin_pop;
extern const BaseApi* sin_rpc_decode;
extern const BaseApi* sin_ssh;
extern const BaseApi* sin_telnet;
sin_ftp_client,
sin_ftp_server,
sin_ftp_data,
+ sin_imap,
sin_nhttp,
+ sin_pop,
sin_rpc_decode,
sin_ssh,
sin_telnet,
pps_rpc_decode.cc
pps_ssh.cc
pps_dns.cc
+ pps_pop.cc
+ pps_imap.cc
pps_sfportscan.cc
pps_stream5_ip.cc
pps_stream5_global.cc
pps_rpc_decode.cc \
pps_ssh.cc \
pps_dns.cc \
+pps_pop.cc \
+pps_imap.cc \
pps_sfportscan.cc \
pps_stream5_ip.cc \
pps_stream5_global.cc \
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// pps_imap.cc author Bhagya Bantwal <bbantwal@cisco.com>
+
+#include <sstream>
+#include <vector>
+
+#include "conversion_state.h"
+#include "helpers/s2l_util.h"
+#include "helpers/util_binder.h"
+
+namespace preprocessors
+{
+namespace
+{
+class Imap : public ConversionState
+{
+public:
+ Imap(Converter& c) : ConversionState(c) { }
+ virtual ~Imap() { }
+ virtual bool convert(std::istringstream& data_stream);
+
+};
+} // namespace
+
+bool Imap::convert(std::istringstream& data_stream)
+{
+ std::string keyword;
+ bool retval = true;
+ bool ports_set = false;
+ Binder bind(table_api);
+
+ bind.set_when_proto("tcp");
+ bind.set_use_type("imap");
+
+ table_api.open_table("imap");
+
+
+ // parse the file configuration
+ while (data_stream >> keyword)
+ {
+ bool tmpval = true;
+
+ if (!keyword.compare("disabled"))
+ {
+ table_api.add_deleted_comment("disabled");
+ }
+
+ else if (!keyword.compare("memcap"))
+ {
+ table_api.add_deleted_comment("memcap");
+ }
+
+ else if (!keyword.compare("max_mime_mem"))
+ {
+ table_api.add_deleted_comment("max_mime_mem");
+ }
+
+ else if (!keyword.compare("b64_decode_depth"))
+ {
+ tmpval = parse_int_option("b64_decode_depth", data_stream, false);
+ }
+
+ else if (!keyword.compare("qp_decode_depth"))
+ {
+ tmpval = parse_int_option("qp_decode_depth", data_stream, false);
+ }
+
+ else if (!keyword.compare("bitenc_decode_depth"))
+ {
+ tmpval = parse_int_option("bitenc_decode_depth", data_stream, false);
+ }
+
+ else if (!keyword.compare("uu_decode_depth"))
+ {
+ tmpval = parse_int_option("uu_decode_depth", data_stream, false);
+ }
+
+ else if (!keyword.compare("ports"))
+ {
+ std::string tmp = "";
+ table_api.add_diff_option_comment("ports", "bindings");
+
+ if ((data_stream >> keyword) && !keyword.compare("{"))
+ {
+ while (data_stream >> keyword && keyword.compare("}"))
+ {
+ ports_set = true;
+ bind.add_when_port(keyword);
+ }
+ }
+ else
+ {
+ data_api.failed_conversion(data_stream, "ports <bracketed_port_list>");
+ retval = false;
+ }
+ }
+
+ else
+ {
+ tmpval = false;
+ }
+
+ if (!tmpval)
+ {
+ data_api.failed_conversion(data_stream, keyword);
+ retval = false;
+ }
+ }
+
+ if (!ports_set)
+ bind.add_when_port("143");
+
+ return retval;
+}
+
+/**************************
+ ******* A P I ***********
+ **************************/
+
+static ConversionState* ctor(Converter& c)
+{
+ return new Imap(c);
+}
+
+static const ConvertMap preprocessor_imap =
+{
+ "imap",
+ ctor,
+};
+
+const ConvertMap* imap_map = &preprocessor_imap;
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-2015 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// pps_pop.cc author Bhagya Bantwal <bbantwal@cisco.com>
+
+#include <sstream>
+#include <vector>
+
+#include "conversion_state.h"
+#include "helpers/s2l_util.h"
+#include "helpers/util_binder.h"
+
+namespace preprocessors
+{
+namespace
+{
+class Pop : public ConversionState
+{
+public:
+ Pop(Converter& c) : ConversionState(c) { }
+ virtual ~Pop() { }
+ virtual bool convert(std::istringstream& data_stream);
+
+};
+} // namespace
+
+bool Pop::convert(std::istringstream& data_stream)
+{
+ std::string keyword;
+ bool retval = true;
+ bool ports_set = false;
+ Binder bind(table_api);
+
+ bind.set_when_proto("tcp");
+ bind.set_use_type("pop");
+
+ table_api.open_table("pop");
+
+
+ // parse the file configuration
+ while (data_stream >> keyword)
+ {
+ bool tmpval = true;
+
+ if (!keyword.compare("disabled"))
+ {
+ table_api.add_deleted_comment("disabled");
+ }
+
+ else if (!keyword.compare("memcap"))
+ {
+ table_api.add_deleted_comment("memcap");
+ }
+
+ else if (!keyword.compare("max_mime_mem"))
+ {
+ table_api.add_deleted_comment("max_mime_mem");
+ }
+
+ else if (!keyword.compare("b64_decode_depth"))
+ {
+ tmpval = parse_int_option("b64_decode_depth", data_stream, false);
+ }
+
+ else if (!keyword.compare("qp_decode_depth"))
+ {
+ tmpval = parse_int_option("qp_decode_depth", data_stream, false);
+ }
+
+ else if (!keyword.compare("bitenc_decode_depth"))
+ {
+ tmpval = parse_int_option("bitenc_decode_depth", data_stream, false);
+ }
+
+ else if (!keyword.compare("uu_decode_depth"))
+ {
+ tmpval = parse_int_option("uu_decode_depth", data_stream, false);
+ }
+
+ else if (!keyword.compare("ports"))
+ {
+ std::string tmp = "";
+ table_api.add_diff_option_comment("ports", "bindings");
+
+ if ((data_stream >> keyword) && !keyword.compare("{"))
+ {
+ while (data_stream >> keyword && keyword.compare("}"))
+ {
+ ports_set = true;
+ bind.add_when_port(keyword);
+ }
+ }
+ else
+ {
+ data_api.failed_conversion(data_stream, "ports <bracketed_port_list>");
+ retval = false;
+ }
+ }
+
+ else
+ {
+ tmpval = false;
+ }
+
+ if (!tmpval)
+ {
+ data_api.failed_conversion(data_stream, keyword);
+ retval = false;
+ }
+ }
+
+ if (!ports_set)
+ bind.add_when_port("110");
+
+ return retval;
+}
+
+/**************************
+ ******* A P I ***********
+ **************************/
+
+static ConversionState* ctor(Converter& c)
+{
+ return new Pop(c);
+}
+
+static const ConvertMap preprocessor_pop =
+{
+ "pop",
+ ctor,
+};
+
+const ConvertMap* pop_map = &preprocessor_pop;
+}
+
extern const ConvertMap* rpc_decode_map;
extern const ConvertMap* ssh_map;
extern const ConvertMap* dns_map;
+extern const ConvertMap* pop_map;
+extern const ConvertMap* imap_map;
extern const ConvertMap* sfportscan_map;
extern const ConvertMap* stream_ip_map;
extern const ConvertMap* stream_global_map;
rpc_decode_map,
ssh_map,
dns_map,
+ pop_map,
+ imap_map,
sfportscan_map,
stream_ip_map,
stream_global_map,