From: Russ Combs Date: Wed, 29 Apr 2015 17:36:48 +0000 (-0400) Subject: bhagya - pop and imap inspectors ported X-Git-Tag: 3.0.0-233~990 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2e13e2eabe500d06ae6f1bc02b486950b4c31d4b;p=thirdparty%2Fsnort3.git bhagya - pop and imap inspectors ported --- diff --git a/ChangeLog b/ChangeLog index 5bcb6b78c..89b3559cd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Pending - build 150 + +-- pop and imap inspectors ported + 15/04/28 - build 149 -- fixed build issue with extras diff --git a/configure.ac b/configure.ac index 5aa0c8d01..95c17c529 100644 --- a/configure.ac +++ b/configure.ac @@ -971,7 +971,9 @@ src/service_inspectors/back_orifice/Makefile \ 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 \ diff --git a/lua/snort.lua b/lua/snort.lua index 62d9e743b..c37fabe33 100644 --- a/lua/snort.lua +++ b/lua/snort.lua @@ -65,7 +65,9 @@ stream_udp = { } arp_spoof = { } back_orifice = { } dns = { } +imap = { } perf_monitor = { } +pop = { } port_scan = { } rpc_decode = { } ssh = { } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 85ffc2d57..424ef8a1c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,7 +57,9 @@ if (STATIC_INSPECTORS) back_orifice dns ftp_telnet + imap nhttp_inspect + pop rpc_decode ssh wizard diff --git a/src/Makefile.am b/src/Makefile.am index 432892c4f..f63195ed6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,7 +12,9 @@ network_inspectors/arp_spoof/libarp_spoof.a \ 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 diff --git a/src/file_api/file_api.h b/src/file_api/file_api.h index 6aaeab924..9bd928dd8 100644 --- a/src/file_api/file_api.h +++ b/src/file_api/file_api.h @@ -77,17 +77,63 @@ struct MAIL_LogConfig 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; @@ -103,12 +149,13 @@ struct MimeState 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 @@ -151,9 +198,10 @@ typedef void (* Set_mime_decode_config_defaults_func)(DecodeConfig* decode_conf) 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, @@ -163,6 +211,13 @@ typedef void (* Finalize_mime_position_func)(Flow* flow, void* decode_state, 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; @@ -197,12 +252,18 @@ typedef struct _file_api 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; @@ -247,5 +308,19 @@ static inline bool isFileEnd(FilePosition position) 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 */ diff --git a/src/file_api/file_mime_config.cc b/src/file_api/file_mime_config.cc index f2f3af86a..7beb7842c 100644 --- a/src/file_api/file_mime_config.cc +++ b/src/file_api/file_mime_config.cc @@ -286,3 +286,34 @@ int parse_mime_decode_args(DecodeConfig* decode_conf, char* arg, const char* pre 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; +} + diff --git a/src/file_api/file_mime_config.h b/src/file_api/file_mime_config.h index d8d9a750d..6ae30fff8 100644 --- a/src/file_api/file_mime_config.h +++ b/src/file_api/file_mime_config.h @@ -36,5 +36,6 @@ bool is_decoding_enabled(DecodeConfig* decode_conf); 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 diff --git a/src/file_api/file_mime_process.cc b/src/file_api/file_mime_process.cc index 88b2de386..d3225a575 100644 --- a/src/file_api/file_mime_process.cc +++ b/src/file_api/file_mime_process.cc @@ -81,6 +81,7 @@ MIMESearchInfo mime_search_info; 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) @@ -255,79 +256,6 @@ static void set_mime_buffers(MimeState* ssn) } } -/* - * 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) { @@ -391,27 +319,6 @@ static int search_str_found(void* id, void*, int index, void*, void*) 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) @@ -498,7 +405,7 @@ static inline void setup_decode(const char* data, int size, bool cnt_xf, MimeSta * @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; @@ -508,7 +415,6 @@ static const uint8_t* process_mime_header( 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; @@ -526,6 +432,8 @@ static const uint8_t* process_mime_header( 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 */ @@ -571,6 +479,14 @@ static const uint8_t* process_mime_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) && @@ -632,6 +548,23 @@ static const uint8_t* process_mime_header( 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')) @@ -658,24 +591,8 @@ static const uint8_t* process_mime_header( 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); @@ -729,100 +646,81 @@ static const uint8_t* process_mime_header( 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; } /* @@ -832,47 +730,26 @@ static void reset_mime_state(MimeState* mime_ssn) { 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 */ @@ -892,6 +769,11 @@ const uint8_t* process_mime_data(void* packet, const uint8_t* start, const uint8 { /* 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); @@ -914,25 +796,8 @@ const uint8_t* process_mime_data(void* packet, const uint8_t* start, const uint8 * 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)) @@ -948,54 +813,30 @@ const uint8_t* process_mime_data(void* packet, const uint8_t* start, const uint8 } #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; } } @@ -1004,27 +845,18 @@ const uint8_t* process_mime_data(void* packet, const uint8_t* start, const uint8 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); @@ -1033,13 +865,64 @@ const uint8_t* process_mime_data(void* packet, const uint8_t* start, const uint8 } /* 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; } /* @@ -1048,8 +931,6 @@ const uint8_t* process_mime_data(void* packet, const uint8_t* start, const uint8 */ void init_mime(void) { - const char* error; - int erroffset; const MimeToken* tmp; /* Header search */ @@ -1070,26 +951,6 @@ void init_mime(void) } 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); - } } /* @@ -1103,12 +964,6 @@ void free_mime(void) { 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) @@ -1116,12 +971,6 @@ 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); @@ -1146,3 +995,203 @@ void finalize_mime_position(Flow* flow, void* decode_state, FilePosition* positi 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; +} + diff --git a/src/file_api/file_mime_process.h b/src/file_api/file_mime_process.h index 695e99a23..3211a96c1 100644 --- a/src/file_api/file_mime_process.h +++ b/src/file_api/file_mime_process.h @@ -50,6 +50,9 @@ #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 @@ -63,9 +66,14 @@ int log_file_name(const uint8_t* start, int length, FILE_LogState* log_state, bo 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 diff --git a/src/file_api/file_service.cc b/src/file_api/file_service.cc index c9c9943ff..ac9852f9b 100644 --- a/src/file_api/file_service.cc +++ b/src/file_api/file_service.cc @@ -92,6 +92,8 @@ static void file_signature_lookup(void* p, bool is_retransmit); 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; @@ -166,6 +168,7 @@ void FileAPIInit(void) 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; @@ -174,6 +177,12 @@ void FileAPIInit(void) 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(); @@ -838,6 +847,52 @@ static void set_file_name_from_log(FILE_LogState* log_state, void* pv) } } +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; @@ -875,7 +930,7 @@ static uint32_t str_to_hash(uint8_t* str, int length) j = 0; } } - final(a,b,c); + final (a,b,c); return c; } diff --git a/src/protocols/CMakeLists.txt b/src/protocols/CMakeLists.txt index 79fdbb0f5..046a173fe 100644 --- a/src/protocols/CMakeLists.txt +++ b/src/protocols/CMakeLists.txt @@ -16,6 +16,7 @@ set (PROTOCOL_HEADERS packet.h packet_manager.h protocol_ids.h + ssl.h tcp.h tcp_options.h teredo.h @@ -31,6 +32,7 @@ add_library (protocols STATIC packet.cc ip.cc ipv4_options.cc + ssl.cc tcp_options.cc packet_manager.cc ) diff --git a/src/protocols/Makefile.am b/src/protocols/Makefile.am index 0eda8325a..c70075774 100644 --- a/src/protocols/Makefile.am +++ b/src/protocols/Makefile.am @@ -21,6 +21,7 @@ mpls.h \ packet.h \ packet_manager.h \ protocol_ids.h \ +ssl.h \ tcp.h \ tcp_options.h \ teredo.h \ @@ -35,7 +36,8 @@ packet_manager.cc \ packet.cc \ ip.cc \ ipv4_options.cc \ -tcp_options.cc +tcp_options.cc \ +ssl.cc AM_CXXFLAGS = @AM_CXXFLAGS@ diff --git a/src/protocols/ssl.cc b/src/protocols/ssl.cc new file mode 100644 index 000000000..8426e42c2 --- /dev/null +++ b/src/protocols/ssl.cc @@ -0,0 +1,582 @@ +/* + * 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 +#endif + +#ifndef WIN32 +#include +#include +#include +#include +#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); +} + diff --git a/src/protocols/ssl.h b/src/protocols/ssl.h new file mode 100644 index 000000000..643a41adf --- /dev/null +++ b/src/protocols/ssl.h @@ -0,0 +1,234 @@ +/* + * 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 +#include + +#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 + diff --git a/src/service_inspectors/CMakeLists.txt b/src/service_inspectors/CMakeLists.txt index df448bbc1..c071cdcf8 100644 --- a/src/service_inspectors/CMakeLists.txt +++ b/src/service_inspectors/CMakeLists.txt @@ -3,7 +3,9 @@ add_subdirectory(back_orifice) 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) @@ -13,7 +15,9 @@ if (STATIC_INSPECTORS) back_orifice ftp_telnet dns + imap nhttp_inspect + pop rpc_decode ssh wizard diff --git a/src/service_inspectors/Makefile.am b/src/service_inspectors/Makefile.am index 067ee9aab..f744eb7a0 100644 --- a/src/service_inspectors/Makefile.am +++ b/src/service_inspectors/Makefile.am @@ -21,7 +21,9 @@ back_orifice \ dns \ ftp_telnet \ http_inspect \ +imap \ nhttp_inspect \ +pop \ rpc_decode \ ssh \ wizard diff --git a/src/service_inspectors/http_inspect/hi_main.cc b/src/service_inspectors/http_inspect/hi_main.cc index 50339a0a8..a017c6682 100644 --- a/src/service_inspectors/http_inspect/hi_main.cc +++ b/src/service_inspectors/http_inspect/hi_main.cc @@ -653,7 +653,7 @@ int HttpInspectMain(HTTPINSPECT_CONF* conf, Packet* p) 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) { @@ -785,7 +785,7 @@ int HttpInspectMain(HTTPINSPECT_CONF* conf, Packet* p) 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 { @@ -821,7 +821,7 @@ int HttpInspectMain(HTTPINSPECT_CONF* conf, Packet* p) 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) { diff --git a/src/service_inspectors/imap/CMakeLists.txt b/src/service_inspectors/imap/CMakeLists.txt new file mode 100644 index 000000000..2aaf1cc19 --- /dev/null +++ b/src/service_inspectors/imap/CMakeLists.txt @@ -0,0 +1,18 @@ + +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) diff --git a/src/service_inspectors/imap/Makefile.am b/src/service_inspectors/imap/Makefile.am new file mode 100644 index 000000000..53a7bf344 --- /dev/null +++ b/src/service_inspectors/imap/Makefile.am @@ -0,0 +1,24 @@ +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@ + diff --git a/src/service_inspectors/imap/imap.cc b/src/service_inspectors/imap/imap.cc new file mode 100644 index 000000000..e0e726679 --- /dev/null +++ b/src/service_inspectors/imap/imap.cc @@ -0,0 +1,911 @@ +//-------------------------------------------------------------------------- +// 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 + * + * + */ +#include "imap.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#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 + diff --git a/src/service_inspectors/imap/imap.h b/src/service_inspectors/imap/imap.h new file mode 100644 index 000000000..5ee11eb02 --- /dev/null +++ b/src/service_inspectors/imap/imap.h @@ -0,0 +1,198 @@ +//-------------------------------------------------------------------------- +// 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 + */ + +#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 */ + diff --git a/src/service_inspectors/imap/imap_config.h b/src/service_inspectors/imap/imap_config.h new file mode 100644 index 000000000..40b1bc50e --- /dev/null +++ b/src/service_inspectors/imap/imap_config.h @@ -0,0 +1,33 @@ +//-------------------------------------------------------------------------- +// 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 + diff --git a/src/service_inspectors/imap/imap_module.cc b/src/service_inspectors/imap/imap_module.cc new file mode 100644 index 000000000..2aad99180 --- /dev/null +++ b/src/service_inspectors/imap/imap_module.cc @@ -0,0 +1,143 @@ +//-------------------------------------------------------------------------- +// 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 + +#include "imap_module.h" +#include +#include +#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; +} + diff --git a/src/service_inspectors/imap/imap_module.h b/src/service_inspectors/imap/imap_module.h new file mode 100644 index 000000000..ad250b09f --- /dev/null +++ b/src/service_inspectors/imap/imap_module.h @@ -0,0 +1,70 @@ +//-------------------------------------------------------------------------- +// 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 + +#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 + diff --git a/src/service_inspectors/imap/imap_paf.cc b/src/service_inspectors/imap/imap_paf.cc new file mode 100644 index 000000000..f583a6bf3 --- /dev/null +++ b/src/service_inspectors/imap/imap_paf.cc @@ -0,0 +1,529 @@ +/**************************************************************************** + * 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 +#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; +} + diff --git a/src/service_inspectors/imap/imap_paf.h b/src/service_inspectors/imap/imap_paf.h new file mode 100644 index 000000000..b38174719 --- /dev/null +++ b/src/service_inspectors/imap/imap_paf.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * 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 + diff --git a/src/service_inspectors/pop/CMakeLists.txt b/src/service_inspectors/pop/CMakeLists.txt new file mode 100644 index 000000000..c1e2ba3f6 --- /dev/null +++ b/src/service_inspectors/pop/CMakeLists.txt @@ -0,0 +1,18 @@ + +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) diff --git a/src/service_inspectors/pop/Makefile.am b/src/service_inspectors/pop/Makefile.am new file mode 100644 index 000000000..a044e4932 --- /dev/null +++ b/src/service_inspectors/pop/Makefile.am @@ -0,0 +1,24 @@ +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@ + diff --git a/src/service_inspectors/pop/pop.cc b/src/service_inspectors/pop/pop.cc new file mode 100644 index 000000000..d0797e56b --- /dev/null +++ b/src/service_inspectors/pop/pop.cc @@ -0,0 +1,844 @@ +//-------------------------------------------------------------------------- +// 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 +#include +#include +#include + +#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 + diff --git a/src/service_inspectors/pop/pop.h b/src/service_inspectors/pop/pop.h new file mode 100644 index 000000000..61aa049e1 --- /dev/null +++ b/src/service_inspectors/pop/pop.h @@ -0,0 +1,154 @@ +//-------------------------------------------------------------------------- +// 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 + */ + +#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 */ + diff --git a/src/service_inspectors/pop/pop_config.h b/src/service_inspectors/pop/pop_config.h new file mode 100644 index 000000000..f7e65fda7 --- /dev/null +++ b/src/service_inspectors/pop/pop_config.h @@ -0,0 +1,33 @@ +//-------------------------------------------------------------------------- +// 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 + diff --git a/src/service_inspectors/pop/pop_module.cc b/src/service_inspectors/pop/pop_module.cc new file mode 100644 index 000000000..95735a7be --- /dev/null +++ b/src/service_inspectors/pop/pop_module.cc @@ -0,0 +1,143 @@ +//-------------------------------------------------------------------------- +// 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 + +#include "pop_module.h" +#include +#include +#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; +} + diff --git a/src/service_inspectors/pop/pop_module.h b/src/service_inspectors/pop/pop_module.h new file mode 100644 index 000000000..20b95fd6e --- /dev/null +++ b/src/service_inspectors/pop/pop_module.h @@ -0,0 +1,70 @@ +//-------------------------------------------------------------------------- +// 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 + +#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 + diff --git a/src/service_inspectors/pop/pop_paf.cc b/src/service_inspectors/pop/pop_paf.cc new file mode 100644 index 000000000..83b0305dc --- /dev/null +++ b/src/service_inspectors/pop/pop_paf.cc @@ -0,0 +1,417 @@ +/**************************************************************************** + * 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 +#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; +} + diff --git a/src/service_inspectors/pop/pop_paf.h b/src/service_inspectors/pop/pop_paf.h new file mode 100644 index 000000000..21d16864a --- /dev/null +++ b/src/service_inspectors/pop/pop_paf.h @@ -0,0 +1,82 @@ +/**************************************************************************** + * 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 + diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index ae4ae7461..b0d8879b5 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -33,7 +33,9 @@ extern const BaseApi* sin_dns; 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; @@ -51,7 +53,9 @@ const BaseApi* service_inspectors[] = sin_ftp_client, sin_ftp_server, sin_ftp_data, + sin_imap, sin_nhttp, + sin_pop, sin_rpc_decode, sin_ssh, sin_telnet, diff --git a/tools/snort2lua/preprocessor_states/CMakeLists.txt b/tools/snort2lua/preprocessor_states/CMakeLists.txt index 67f5a04f5..1dcc22ad0 100644 --- a/tools/snort2lua/preprocessor_states/CMakeLists.txt +++ b/tools/snort2lua/preprocessor_states/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(preprocessor_states 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 diff --git a/tools/snort2lua/preprocessor_states/Makefile.am b/tools/snort2lua/preprocessor_states/Makefile.am index d88d0f06e..48a4ca0a5 100644 --- a/tools/snort2lua/preprocessor_states/Makefile.am +++ b/tools/snort2lua/preprocessor_states/Makefile.am @@ -18,6 +18,8 @@ pps_perfmonitor.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 \ diff --git a/tools/snort2lua/preprocessor_states/pps_imap.cc b/tools/snort2lua/preprocessor_states/pps_imap.cc new file mode 100644 index 000000000..58661effc --- /dev/null +++ b/tools/snort2lua/preprocessor_states/pps_imap.cc @@ -0,0 +1,149 @@ +//-------------------------------------------------------------------------- +// 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 + +#include +#include + +#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 "); + 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; +} + diff --git a/tools/snort2lua/preprocessor_states/pps_pop.cc b/tools/snort2lua/preprocessor_states/pps_pop.cc new file mode 100644 index 000000000..14be29ebd --- /dev/null +++ b/tools/snort2lua/preprocessor_states/pps_pop.cc @@ -0,0 +1,149 @@ +//-------------------------------------------------------------------------- +// 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 + +#include +#include + +#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 "); + 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; +} + diff --git a/tools/snort2lua/preprocessor_states/preprocessor_api.cc b/tools/snort2lua/preprocessor_states/preprocessor_api.cc index c8e77a4c9..68a7e675c 100644 --- a/tools/snort2lua/preprocessor_states/preprocessor_api.cc +++ b/tools/snort2lua/preprocessor_states/preprocessor_api.cc @@ -40,6 +40,8 @@ extern const ConvertMap* perfmonitor_map; 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; @@ -67,6 +69,8 @@ const std::vector preprocessor_api = rpc_decode_map, ssh_map, dns_map, + pop_map, + imap_map, sfportscan_map, stream_ip_map, stream_global_map,