"Fossies" - the Fresh Open Source Software Archive

Member "snort-2.9.17/src/dynamic-preprocessors/imap/imap_paf.c" (16 Oct 2020, 17921 Bytes) of package /linux/misc/snort-2.9.17.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "imap_paf.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.9.16.1_vs_2.9.17.

    1 /****************************************************************************
    2  * Copyright (C) 2014-2020 Cisco and/or its affiliates. All rights reserved.
    3  * Copyright (C) 2011-2013 Sourcefire, Inc.
    4  *
    5  * This program is free software; you can redistribute it and/or modify
    6  * it under the terms of the GNU General Public License Version 2 as
    7  * published by the Free Software Foundation.  You may not use, modify or
    8  * distribute this program under any other version of the GNU General
    9  * Public License.
   10  *
   11  * This program is distributed in the hope that it will be useful,
   12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14  * GNU General Public License for more details.
   15  *
   16  * You should have received a copy of the GNU General Public License
   17  * along with this program; if not, write to the Free Software
   18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   19  *
   20  ****************************************************************************/
   21 
   22 
   23 #include <sys/types.h>
   24 #include "sf_types.h"
   25 #include "imap_paf.h"
   26 #include "spp_imap.h"
   27 #include "sf_dynamic_preprocessor.h"
   28 #include "stream_api.h"
   29 #include "snort_imap.h"
   30 #include "imap_config.h"
   31 #include "file_api.h"
   32 
   33 
   34 extern const IMAPToken imap_resps[];
   35 
   36 static uint8_t imap_paf_id = 0;
   37 
   38 typedef struct _ImapDataInfo
   39 {
   40     int paren_cnt;        /* The open parentheses count in fetch */
   41     bool found_len;
   42     bool esc_nxt_char;    /* true if the next charachter has been escaped */
   43     char *next_letter;    /* The current command in fetch */
   44     uint32_t length;
   45 } ImapDataInfo;
   46 
   47 
   48 /* State tracker for SMTP PAF */
   49 typedef enum _ImapPafState
   50 {
   51     IMAP_PAF_REG_STATE,       /* default state. eat until LF */
   52     IMAP_PAF_DATA_HEAD_STATE, /* parses the fetch header */
   53     IMAP_PAF_DATA_LEN_STATE,  /* parse the literal length */
   54     IMAP_PAF_DATA_STATE,      /* search for and flush on MIME boundaries */
   55     IMAP_PAF_FLUSH_STATE,     /* flush if a termination sequence is found */
   56     IMAP_PAF_CMD_IDENTIFIER,  /* determine the line identifier ('+', '*', tag) */
   57     IMAP_PAF_CMD_TAG,         /* currently analyzing tag . identifier */
   58     IMAP_PAF_CMD_STATUS,      /* currently parsing second argument */
   59     IMAP_PAF_CMD_SEARCH       /* currently searching data for a command */
   60 } ImapPafState;
   61 
   62 typedef enum _ImapDataEnd
   63 {
   64     IMAP_PAF_DATA_END_UNKNOWN,
   65     IMAP_PAF_DATA_END_PAREN
   66 } ImapDataEnd;
   67 
   68 /* State tracker for POP PAF */
   69 typedef struct _ImapPafData
   70 {
   71     MimeDataPafInfo mime_info;    /* Mime response information */
   72     ImapPafState imap_state;      /* The current IMAP paf stat */
   73     bool end_of_data;
   74     ImapDataInfo imap_data_info;  /* Used for parsing data */
   75     ImapDataEnd data_end_state;
   76 //    uint32_t length;            TODO -- parse and add literal length
   77 } ImapPafData;
   78 
   79 static inline void reset_data_states(ImapPafData *pfdata)
   80 {
   81     // reset MIME info
   82     _dpd.fileAPI->reset_mime_paf_state(&(pfdata->mime_info));
   83 
   84     // reset server info
   85     pfdata->imap_state = IMAP_PAF_CMD_IDENTIFIER;
   86 
   87     // reset fetch data information information
   88     pfdata->imap_data_info.paren_cnt = 0;
   89     pfdata->imap_data_info.next_letter = 0;
   90     pfdata->imap_data_info.length = 0;
   91 }
   92 
   93 static inline bool is_untagged(const uint8_t ch)
   94 {
   95     return (ch == '*' || ch == '+');
   96 }
   97 
   98 static bool parse_literal_length(const uint8_t ch, uint32_t *len)
   99 {
  100     uint32_t length = *len;
  101 
  102     if (isdigit(ch))
  103     {
  104         uint64_t tmp_len = (10 * length)  + (ch - '0');
  105         if (tmp_len < UINT32_MAX)
  106         {
  107             *len = (uint32_t) tmp_len;
  108             return false;
  109         }
  110         else
  111         {
  112             *len = 0;
  113         }
  114     } 
  115     else if (ch != '}')
  116         *len = 0;  //  ALERT!!  charachter should be a digit or ''}'' 
  117 
  118     return true;  
  119 }
  120 
  121 static void parse_fetch_header(const uint8_t ch, ImapPafData *pfdata)
  122 {
  123     if (pfdata->imap_data_info.esc_nxt_char)
  124     {
  125         pfdata->imap_data_info.esc_nxt_char = false;
  126     }
  127     else
  128     {
  129         switch(ch)
  130         {
  131         case '{':
  132             pfdata->imap_state = IMAP_PAF_DATA_LEN_STATE;
  133             break;
  134         case '(':
  135             pfdata->imap_data_info.paren_cnt++;
  136             break;
  137 
  138         case ')':
  139             if (pfdata->imap_data_info.paren_cnt > 0)
  140                 pfdata->imap_data_info.paren_cnt--;
  141             break;
  142 
  143         case '\n':
  144             if (pfdata->imap_data_info.paren_cnt)
  145             {
  146                 pfdata->imap_state = IMAP_PAF_DATA_STATE;
  147             }
  148             else
  149             {
  150                 reset_data_states(pfdata);
  151             }
  152             break;
  153 
  154         case '\\':
  155             pfdata->imap_data_info.esc_nxt_char = true;
  156             break;
  157 
  158         default:
  159             break;
  160         }
  161     }
  162 }
  163 
  164 /* 
  165  * Statefully search for the single line termination sequence LF ("\n").
  166  *
  167  * PARAMS:
  168  *        const uint8_t ch - the next character to analyze.
  169  *        ImapPafData *pfdata - the struct containing all imap paf information
  170  *
  171  * RETURNS:
  172  *        false - if termination sequence not found 
  173  *        true - if termination sequence found
  174  */
  175 static bool find_data_end_single_line(const uint8_t ch, ImapPafData *pfdata)
  176 {
  177     if (ch == '\n')
  178     {
  179         reset_data_states(pfdata);
  180         return true;
  181     }
  182     return false;
  183 }
  184 
  185 /* Flush based on data length*/
  186 static inline bool literal_complete(ImapPafData *pfdata)
  187 {
  188     if (pfdata->imap_data_info.length)
  189     {
  190         pfdata->imap_data_info.length--;
  191         if (pfdata->imap_data_info.length)
  192             return false;
  193     }
  194 
  195     return true;
  196 }
  197 
  198 static bool check_imap_data_end(ImapDataEnd *data_end_state,  uint8_t val)
  199 {
  200     switch(*data_end_state)
  201     {
  202 
  203     
  204     case IMAP_PAF_DATA_END_UNKNOWN:
  205         if (val == ')')
  206             *data_end_state = (ImapDataEnd) IMAP_PAF_DATA_END_PAREN;
  207         break;
  208 
  209     case IMAP_PAF_DATA_END_PAREN:
  210         if (val == '\n')
  211         {
  212             *data_end_state = (ImapDataEnd) PAF_DATA_END_UNKNOWN;
  213             return true;
  214         }
  215         else if (val != '\r')
  216         {
  217             *data_end_state = (ImapDataEnd) PAF_DATA_END_UNKNOWN;
  218         }
  219         break;
  220     
  221     default:
  222         break;
  223     }
  224 
  225     return false;
  226 }
  227 
  228 /* 
  229  * Statefully search for the data termination sequence or a MIME boundary.
  230  *
  231  * PARAMS:
  232  *        const uint8_t ch - the next character to analyze.
  233  *        ImapPafData *pfdata - the struct containing all imap paf information
  234  *
  235  * RETURNS:
  236  *        false - if termination sequence not found 
  237  *        true - if termination sequence found
  238  */
  239 static bool find_data_end_mime_data(const uint8_t ch, ImapPafData *pfdata)
  240 {
  241     if (literal_complete(pfdata)
  242          && check_imap_data_end(&(pfdata->data_end_state), ch))
  243     {
  244         DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: End of Data!\n"););
  245         reset_data_states(pfdata);
  246         return true;
  247     }
  248 
  249 
  250     // check for mime flush point
  251     if (_dpd.fileAPI->process_mime_paf_data(&(pfdata->mime_info), ch))
  252     {
  253         DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: Mime Boundary found."
  254                     " Flushing data!\n"););
  255         return true;
  256     }
  257 
  258     return false;
  259 }
  260 
  261 
  262 /*
  263  * Initial command processing function.  Determine if this command
  264  * may be analyzed irregularly ( which currently means if emails
  265  * and email attachments need to be analyzed).
  266  * 
  267  * PARAMS: 
  268  *        const uint8_t ch - the next character to analyze.
  269  *        ImapPafData *pfdata - the struct containing all imap paf information
  270  */
  271 static inline void init_command_search(const uint8_t ch, ImapPafData *pfdata)
  272 {
  273     switch(ch)
  274     {
  275     case 'F':
  276     case 'f':
  277         // may be a FETCH response
  278         pfdata->imap_data_info.next_letter = &(imap_resps[RESP_FETCH].name[1]);
  279         break;
  280 
  281     default:
  282         // this is not a data command. Search for regular end of line.
  283         pfdata->imap_state = IMAP_PAF_REG_STATE;
  284     }
  285 }
  286 
  287 
  288 /*
  289  * Confirms every character in the current sequence is part of the expected 
  290  * command. After confirmation is complete, IMAP PAF will begin searching
  291  * for data. If any character is unexpected, searches for the default 
  292  * termination sequence. 
  293  * 
  294  * PARAMS: 
  295  *        const uint8_t ch - the next character to analyze.
  296  *        ImapPafData *pfdata - the struct containing all imap paf information
  297  */
  298 static inline void parse_command(const uint8_t ch, ImapPafData *pfdata)
  299 {
  300     char val = *(pfdata->imap_data_info.next_letter);
  301 
  302     if (val == '\0' && isblank(ch))
  303         pfdata->imap_state = IMAP_PAF_DATA_HEAD_STATE;
  304 
  305     else if (toupper(ch) == toupper(val))
  306         pfdata->imap_data_info.next_letter++;
  307 
  308     else
  309         pfdata->imap_state = IMAP_PAF_REG_STATE;
  310 }
  311 
  312 
  313 /*
  314  * Wrapper function for the command parser.  Determines whether this is the
  315  * first letter being processed and calls the appropriate processing 
  316  * function.
  317  * 
  318  * PARAMS: 
  319  *        const uint8_t ch - the next character to analyze.
  320  *        ImapPafData *pfdata - the struct containing all imap paf information
  321  */
  322 static inline void process_command(const uint8_t ch, ImapPafData *pfdata)
  323 {
  324     if (pfdata->imap_data_info.next_letter)
  325         parse_command(ch, pfdata);
  326     else
  327         init_command_search(ch, pfdata);
  328 }
  329 
  330 /*
  331  * This function only does something when the character is a blank or a CR/LF.
  332  * In those specific cases, this function will set the appropriate next
  333  * state information
  334  *
  335  * PARAMS:
  336  *        const uint8_t ch - the next character to analyze.
  337  *        ImapPafData *pfdata - the struct containing all imap paf information
  338  *        ImapPafData base_state - if a space is not found, revert to this state
  339  *        ImapPafData next_state - if a space is found, go to this state
  340  * RETURNS:
  341  *        true - if the status has been eaten
  342  *        false - if a CR or LF has been found
  343  */
  344 static inline void eat_character(const uint8_t ch, ImapPafData *pfdata, 
  345                 ImapPafState base_state, ImapPafState next_state)
  346 {
  347     switch(ch)
  348     {
  349     case ' ':
  350     case '\t':
  351         pfdata->imap_state = next_state;
  352         break;;
  353 
  354     case '\r': 
  355     case '\n':
  356         pfdata->imap_state = base_state;
  357         break;
  358     }
  359 
  360 }
  361 
  362 
  363 /*
  364  *  defined above in the eat_character function
  365  *
  366  * Keeping the next two functions to ease any future development 
  367  * where these cases will no longer be simple or identical
  368  */
  369 static inline void eat_second_argument(const uint8_t ch, ImapPafData *pfdata)
  370 {
  371     eat_character(ch, pfdata, IMAP_PAF_REG_STATE, IMAP_PAF_CMD_SEARCH);
  372 }
  373 
  374 /* explanation in 'eat_second_argument' above */
  375 static inline void eat_response_identifier(const uint8_t ch, ImapPafData *pfdata)
  376 {
  377     eat_character(ch, pfdata, IMAP_PAF_REG_STATE, IMAP_PAF_CMD_STATUS);
  378 }
  379 
  380 
  381 /*
  382  * Analyzes the current data for a correct flush point.  Flushes when 
  383  * a command is complete or a MIME boundary is found.
  384  *
  385  * PARAMS:
  386  *    ImapPafData *pfdata - ImapPaf state tracking structure
  387  *    const uint8_t *data - payload data to inspect
  388  *    uint32_t len - length of payload data
  389  *    uint32_t * fp- pointer to set flush point
  390  *
  391  * RETURNS:
  392  *    PAF_Status - PAF_FLUSH if flush point found, 
  393  *    PAF_SEARCH otherwise
  394  */
  395 static PAF_Status imap_paf_server(ImapPafData *pfdata,
  396         const uint8_t* data, uint32_t len, uint32_t* fp)
  397 {
  398     uint32_t i;
  399     uint32_t flush_len = 0;
  400     uint32_t boundary_start = 0;
  401 
  402     pfdata->end_of_data = false;
  403 
  404     for (i = 0; i < len; i++)
  405     {
  406         uint8_t ch = data[i];
  407         switch(pfdata->imap_state)
  408         {
  409         case IMAP_PAF_CMD_IDENTIFIER:
  410             // can be '+', '*', or a tag
  411             if (is_untagged(ch))
  412             {
  413                 // continue checking for fetch command
  414                 pfdata->imap_state = IMAP_PAF_CMD_TAG;
  415             }
  416             else
  417             {
  418                 // end of a command.  flush at end of line.
  419                 pfdata->imap_state = IMAP_PAF_FLUSH_STATE;
  420             }
  421             break;
  422 
  423         case IMAP_PAF_CMD_TAG:
  424             eat_response_identifier(ch, pfdata);
  425             break;
  426 
  427         case IMAP_PAF_CMD_STATUS:
  428             // can be a command name, msg sequence number, msg count, etc...
  429             // since we are only interested in fetch, eat this argument
  430             eat_second_argument(ch, pfdata);
  431             break;
  432 
  433         case IMAP_PAF_CMD_SEARCH:
  434             process_command(ch, pfdata);
  435             find_data_end_single_line(ch, pfdata);
  436             break;
  437 
  438         case IMAP_PAF_REG_STATE:
  439             find_data_end_single_line(ch, pfdata); // data reset when end of line hit
  440             break;
  441 
  442         case IMAP_PAF_DATA_HEAD_STATE:
  443             parse_fetch_header(ch, pfdata); // function will change state
  444             break;
  445 
  446         case IMAP_PAF_DATA_LEN_STATE:
  447             if (parse_literal_length(ch, &(pfdata->imap_data_info.length)))
  448             {
  449                 pfdata->imap_state = IMAP_PAF_DATA_HEAD_STATE;
  450             }
  451             break;
  452 
  453         case IMAP_PAF_DATA_STATE:
  454             if (find_data_end_mime_data(ch, pfdata))
  455             {
  456                 // if not a boundary, wait for end of
  457                 // the server's response before flushing
  458                 if (pfdata->imap_state == IMAP_PAF_DATA_STATE)
  459                 {
  460                     *fp = i + 1;
  461                     return PAF_FLUSH;
  462                 }
  463             }
  464             if (pfdata->mime_info.boundary_state == MIME_PAF_BOUNDARY_UNKNOWN)
  465                 boundary_start = i;
  466             break;
  467 
  468         case IMAP_PAF_FLUSH_STATE:
  469             if (find_data_end_single_line(ch, pfdata))
  470             {
  471                 flush_len = i +1;
  472             }
  473             break;
  474         }
  475 
  476     }
  477 
  478     if (flush_len)
  479     {
  480         DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: flushing data!\n"););
  481 
  482         // flush at the final termination sequence
  483         *fp = flush_len;
  484         return PAF_FLUSH;
  485     }
  486 
  487     if ( scanning_boundary(&pfdata->mime_info, boundary_start, fp) )
  488         return PAF_LIMIT;
  489 
  490     return PAF_SEARCH;
  491 }
  492 
  493 /*
  494  * Searches through the current data for a LF.  All client
  495  * commands end with this termination sequence
  496  *
  497  * PARAMS:
  498  *    ImapPafData *pfdata - ImapPaf state tracking structure
  499  *    const uint8_t *data - payload data to inspect
  500  *    uint32_t len - length of payload data
  501  *    uint32_t * fp- pointer to set flush point
  502  *
  503  * RETURNS:
  504  *    PAF_Status - PAF_FLUSH if flush point found, 
  505  *    PAF_SEARCH otherwise
  506  */
  507 static PAF_Status imap_paf_client(ImapPafData *pfdata,
  508         const uint8_t* data, uint32_t len, uint32_t* fp)
  509 {
  510     const char *pch;
  511 
  512     pch = memchr (data, '\n', len);
  513 
  514     if (pch != NULL)
  515     {
  516         DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: Flushing client"
  517                                     " data!\n"););
  518         *fp = (uint32_t)(pch - (const char*)data) + 1;
  519         return PAF_FLUSH;
  520     }
  521     return PAF_SEARCH;
  522 }
  523 
  524 /* Function: imap_paf()
  525  *
  526  *  Purpose: IMAP PAF callback.
  527  *           Inspects imap traffic.  All client traffic will be flushed after 
  528  *           every single line termination sequence (LF == '\n'). When 
  529  *           analyzing server traffic, will flush after the final termination
  530  *           sequence in the current PDU.  However, if the server is returning
  531  *           an email, a flush will occur after every MIME boundary and at the 
  532  *           end of the email.
  533  *
  534  *  Arguments:
  535  *    void *ssn - stream5 session pointer
  536  *    void **ps - ImapPaf state tracking structure
  537  *    const uint8_t *data - payload data to inspect
  538  *    uint32_t len - length of payload data
  539  *    uint32_t flags- flags to check whether client or server
  540  *    uint32_t * fp- pointer to set flush point
  541  *    uint32_t * fp_eoh - pointer to set header flush point
  542  *
  543  * Returns:
  544  *   PAF_Status - PAF_FLUSH if flush point found, PAF_SEARCH otherwise
  545  */
  546 
  547 static PAF_Status imap_paf(void* ssn, void** ps, const uint8_t* data,
  548         uint32_t len, uint64_t *flags, uint32_t* fp, uint32_t* fp_eoh)
  549 {
  550 
  551     ImapPafData *pfdata = *(ImapPafData **)ps;
  552 
  553     if (pfdata == NULL)
  554     {
  555         /* IMAP has long running sessions, avoid the paf check*/
  556         //if (_dpd.fileAPI->check_paf_abort(ssn))
  557           //  return PAF_ABORT;
  558 
  559         pfdata = _dpd.snortAlloc(1, sizeof(*pfdata), PP_IMAP, 0);
  560         if (pfdata == NULL)
  561         {
  562             return PAF_ABORT;
  563         }
  564 
  565         reset_data_states(pfdata);
  566         *ps = pfdata;
  567     }
  568 
  569 
  570     if (*flags & FLAG_FROM_SERVER)
  571     {
  572         DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: From server.\n"););
  573         return imap_paf_server(pfdata, data, len, fp);
  574     }
  575     else
  576     {
  577         DEBUG_WRAP(DebugMessage(DEBUG_IMAP, "IMAP PAF: From client.\n"););
  578         return imap_paf_client(pfdata, data, len, fp);
  579     }
  580 }
  581 
  582 bool is_data_end (void* ssn)
  583 {
  584     if ( ssn )
  585     {
  586         ImapPafData** s = (ImapPafData **)_dpd.streamAPI->get_paf_user_data(ssn, 1, imap_paf_id);
  587 
  588         if ( s && (*s) )
  589             return ((*s)->end_of_data);
  590     }
  591 
  592     return false;
  593 }
  594 
  595 void imap_paf_cleanup(void *pafData)
  596 {
  597    if(pafData) {
  598       _dpd.snortFree(pafData, sizeof(ImapPafData), PP_IMAP, 0);
  599    }
  600 }
  601 
  602 #ifdef TARGET_BASED
  603 void register_imap_paf_service (struct _SnortConfig *sc, int16_t app, tSfPolicyId policy)
  604 {
  605     if (_dpd.isPafEnabled())
  606     {
  607         imap_paf_id = _dpd.streamAPI->register_paf_service(sc, policy, app, true, imap_paf, true);
  608         imap_paf_id = _dpd.streamAPI->register_paf_service(sc, policy, app, false,imap_paf, true);
  609         _dpd.streamAPI->register_paf_free(imap_paf_id, imap_paf_cleanup);
  610     }
  611 }
  612 #endif
  613 
  614 
  615 void register_imap_paf_port(struct _SnortConfig *sc, unsigned int i, tSfPolicyId policy)
  616 {
  617     if (_dpd.isPafEnabled())
  618     {
  619         imap_paf_id = _dpd.streamAPI->register_paf_port(sc, policy, (uint16_t)i, true, imap_paf, true);
  620         imap_paf_id = _dpd.streamAPI->register_paf_port(sc, policy, (uint16_t)i, false, imap_paf, true);
  621         _dpd.streamAPI->register_paf_free(imap_paf_id, imap_paf_cleanup);
  622     }
  623 }