Advertisement

suricata smtp协议解析源码注释三

阅读量:

本篇文章开始对主要函数的代码做注释,有的部分语句尚未理解,没有做注释。

一。行数据处理函数SMTPGetLine

SMTPParse-> SMTPGetLine

这个函数就是在收到的数据中查找\r\n,分成行,(SMTPState*)state->current_line指针指向每行的第一个字符,每行数据不包含\r\n,state->current_line_len保存解析到的行数据长度,不包含\r\n,SMTPProcessRequest和SMTPProcessReply处理时就处理这两个变量,如果找不到\r\n,说明数据不够,进行缓存,如此反复。

二。客户端到服务器请求的入口函数SMTPProcessRequest

SMTPParse->SMTPProcessRequest

复制代码
 static int SMTPProcessRequest(SMTPState *state, Flow *f,

    
                           AppLayerParserState *pstate)
    
 {
    
     SCEnter();
    
     //state->curr_tx始终指向当前的tmtp事务结构体指针
    
     SMTPTransaction *tx = state->curr_tx;
    
  
    
     //第一次解析则生成事务结构体SMTPTransaction
    
     if (state->curr_tx == NULL || (state->curr_tx->done && !NoNewTx(state))) {
    
     tx = SMTPTransactionCreate();
    
     if (tx == NULL)
    
         return -1;
    
     state->curr_tx = tx;
    
     TAILQ_INSERT_TAIL(&state->tx_list, tx, next);
    
     tx->tx_id = state->tx_cnt++; //事务ID自增
    
     }
    
  
    
     //这个标志说明是否看到了服务器返回第一个响应,这个响应指的是连接服务器后返回的欢迎信息,
    
     //在客户端收到服务器响应时解析并设置这标志,如果没有设置则认为服务器没有欢迎信息,
    
     //于是设置解析事件标志
    
     if (!(state->parser_state & SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) {
    
     SMTPSetEvent(state, SMTP_DECODER_EVENT_NO_SERVER_WELCOME_MESSAGE);
    
     }
    
  
    
     //如果不是data命令模式,也就是还没遇到data命令,这时就去查找普通交互命令,
    
     //这段代码比较简单,不用多说,就是字符串匹配smtp的命令,只需要注意匹配命令后
    
     //的处理,比如设置state->current_command为当前解析到的命令标识,
    
     //设置解析状态state->parser_state
    
     /* there are 2 commands that can push it into this COMMAND_DATA mode -
    
      * STARTTLS and DATA */
    
     if (!(state->parser_state & SMTP_PARSER_STATE_COMMAND_DATA_MODE)) {
    
     int r = 0;
    
  
    
     if (state->current_line_len >= 8 &&
    
         SCMemcmpLowercase("starttls", state->current_line, 8) == 0) {
    
         state->current_command = SMTP_COMMAND_STARTTLS;
    
     } else if (state->current_line_len >= 4 &&
    
                SCMemcmpLowercase("data", state->current_line, 4) == 0) {
    
         //这个分支匹配到data命令后的处理,设置当前命令和解析状态,设置当前命令在
    
         //客户端响应数据解析时会使用
    
         state->current_command = SMTP_COMMAND_DATA;
    
         //如果配置了允许mime解码才进行mime解码
    
         if (smtp_config.decode_mime) {
    
             if (tx->mime_state) {
    
                 //这几行代码不是太明白,为啥重新分配结构体
    
                 /* We have 2 chained mails and did not detect the end
    
                  * of first one. So we start a new transaction. */
    
                 tx->mime_state->state_flag = PARSE_ERROR;
    
                 SMTPSetEvent(state, SMTP_DECODER_EVENT_UNPARSABLE_CONTENT);
    
                 tx = SMTPTransactionCreate();
    
                 if (tx == NULL)
    
                     return -1;
    
                 state->curr_tx = tx;
    
                 TAILQ_INSERT_TAIL(&state->tx_list, tx, next);
    
                 tx->tx_id = state->tx_cnt++;
    
             }
    
             //初始化mime_state,所有解析数据都存放在这里,一个mime_state存放一个
    
             //smtp信件的所有数据和状态
    
             tx->mime_state = MimeDecInitParser(f, SMTPProcessDataChunk);
    
             if (tx->mime_state == NULL) {
    
                 SCLogError(SC_ERR_MEM_ALLOC, "MimeDecInitParser() failed to "
    
                         "allocate data");
    
                 return MIME_DEC_ERR_MEM;
    
             }
    
  
    
             //msg_head指向信件体链表头指针,通过它可以遍历所有信件体,
    
             //msg_tail就是尾指针了。
    
             /* Add new MIME message to end of list */
    
             if (tx->msg_head == NULL) {
    
                 tx->msg_head = tx->mime_state->msg;
    
                 tx->msg_tail = tx->mime_state->msg;
    
             }
    
             else {
    
                 tx->msg_tail->next = tx->mime_state->msg;
    
                 tx->msg_tail = tx->mime_state->msg;
    
             }
    
         }
    
         //这个标志是在客户收到服务器响应时,如果ehlo命令的响应数据中有250-PIPELINING
    
         //则设置该标志,目前还不知道250-PIPELINING的含义,
    
         //猜测就是有这个标志就可以立即发送数据了,所以设置解析状态为数据模式:
    
         //SMTP_PARSER_STATE_COMMAND_DATA_MODE
    
         /* Enter immediately data mode without waiting for server reply */
    
         if (state->parser_state & SMTP_PARSER_STATE_PIPELINING_SERVER) {
    
             state->parser_state |= SMTP_PARSER_STATE_COMMAND_DATA_MODE;
    
         }
    
     } else if (state->current_line_len >= 4 &&
    
                SCMemcmpLowercase("bdat", state->current_line, 4) == 0) {
    
         r = SMTPParseCommandBDAT(state);
    
         if (r == -1) {
    
             SCReturnInt(-1);
    
         }
    
         state->current_command = SMTP_COMMAND_BDAT;
    
         state->parser_state |= SMTP_PARSER_STATE_COMMAND_DATA_MODE;
    
     } else if (state->current_line_len >= 4 &&
    
                ((SCMemcmpLowercase("helo", state->current_line, 4) == 0) ||
    
                 SCMemcmpLowercase("ehlo", state->current_line, 4) == 0))  {
    
         r = SMTPParseCommandHELO(state);
    
         if (r == -1) {
    
             SCReturnInt(-1);
    
         }
    
         state->current_command = SMTP_COMMAND_OTHER_CMD;
    
     } else if (state->current_line_len >= 9 &&
    
                SCMemcmpLowercase("mail from", state->current_line, 9) == 0) {
    
         r = SMTPParseCommandMAILFROM(state);
    
         if (r == -1) {
    
             SCReturnInt(-1);
    
         }
    
         state->current_command = SMTP_COMMAND_OTHER_CMD;
    
     } else if (state->current_line_len >= 7 &&
    
                SCMemcmpLowercase("rcpt to", state->current_line, 7) == 0) {
    
         r = SMTPParseCommandRCPTTO(state);
    
         if (r == -1) {
    
             SCReturnInt(-1);
    
         }
    
         state->current_command = SMTP_COMMAND_OTHER_CMD;
    
     } else if (state->current_line_len >= 4 &&
    
                SCMemcmpLowercase("rset", state->current_line, 4) == 0) {
    
         // Resets chunk index in case of connection reuse
    
         state->bdat_chunk_idx = 0;
    
         state->curr_tx->done = 1;
    
     } else {
    
         state->current_command = SMTP_COMMAND_OTHER_CMD;
    
     }
    
  
    
     //这个函数是把当前命令存储到一个命令数组中,客户端收到响应时,根据这个命令数据
    
     //解析应该判断什么样的响应码以及不同命令的逻辑处理,客户端处理一个命令则将
    
     //cmd_idx加一,加一后就等于命令数组的个数cmd_cnt,
    
     //如果相等表示这个命令的响应处理完成且结束,将这两个变量清零,等下一个命令进行同样的处理
    
     /* Every command is inserted into a command buffer, to be matched
    
      * against reply(ies) sent by the server */
    
     if (SMTPInsertCommandIntoCommandBuffer(state->current_command,
    
                                            state, f) == -1) {
    
         SCReturnInt(-1);
    
     }
    
  
    
     SCReturnInt(r);
    
     }
    
  
    
     //根据命令码做响应数据处理,主要是处理data命令的SMTPProcessCommandDATA函数
    
     switch (state->current_command) {
    
     case SMTP_COMMAND_STARTTLS:
    
         return SMTPProcessCommandSTARTTLS(state, f, pstate);
    
  
    
     case SMTP_COMMAND_DATA:
    
         return SMTPProcessCommandDATA(state, f, pstate);
    
  
    
     case SMTP_COMMAND_BDAT:
    
         return SMTPProcessCommandBDAT(state, f, pstate);
    
  
    
     default:
    
         /* we have nothing to do with any other command at this instant.
    
          * Just let it go through */
    
         SCReturnInt(0);
    
     }
    
 }
    
    
    
    

三。服务器到客户端的响应入口函数SMTPProcessReply

SMTPParse->SMTPProcessReply

函数主要解析服务器的响应数据,设置第一次看见服务器的欢迎信息标志,设置多行响应标志(ehlo命令返回的响应数据为多行),根据SMTPState中的命令数组对响应码进行判断,不同命令返回的响应码不一样,处理完一行数据后,如果不是多行则把cmds_idx加一,多行数据视为一个命令的响应,不能重复加一,如果cmds_idx等于cmds_cnt则表示当前命令数组对应的命令的响应数据解析完成,把这个两个命令清零。

ehlo命令响应示例:

ehlo
250-Welcome to my smtp server(EQManager V3.5)
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN PLAIN
250-PIPELINING
250-SIZE 10485760
250 8BITMIME

复制代码
 static int SMTPProcessReply(SMTPState *state, Flow *f,

    
                         AppLayerParserState *pstate,
    
                         SMTPThreadCtx *td)
    
 {
    
     SCEnter();
    
  
    
     //临时变量,存放响应码
    
     uint64_t reply_code = 0;
    
  
    
     //每行响应数据至少3位响应码,长度小于3视为非法数据,设置解析标志
    
     /* the reply code has to contain at least 3 bytes, to hold the 3 digit
    
      * reply code */
    
     if (state->current_line_len < 3) {
    
     /* decoder event */
    
     SMTPSetEvent(state, SMTP_DECODER_EVENT_INVALID_REPLY);
    
     return -1;
    
     }
    
  
    
     //这段代码目的就是判断是否是多行响应数据,因为ehlo的响应可能是多行的,多行格式参看上述示例,
    
     //多行的话,第三个字符是一个短横线,如果是最后一行,第三个字符是一个空格,
    
     //设置多行标志的目的还是为了标明本次数据仍然属于同一个命令,命令索引不能加一,
    
     //因为一个命令对应一个响应
    
     if (state->current_line_len >= 4) {
    
     if (state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY) {
    
         if (state->current_line[3] != '-') {
    
             state->parser_state &= ~SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY;
    
         }
    
     } else {
    
         //ehlo响应的最后一行第三个字符是空格,会执行这里的代码
    
         if (state->current_line[3] == '-') {
    
             state->parser_state |= SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY;
    
         }
    
     }
    
     } else {
    
     //如果只有3位的响应码,肯定不是多行的数据格式,不是多行就去掉多行标志,
    
     //实际上就是说多行数据解析完了,本次数据和上次的多行不是一回事儿了。
    
     if (state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY) {
    
         state->parser_state &= ~SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY;
    
     }
    
     }
    
  
    
     //这块是个多模式匹配,似乎是检查响应码是否合法
    
     /* I don't like this pmq reset here.  We'll devise a method later, that
    
      * should make the use of the mpm very efficient */
    
     PmqReset(td->pmq);
    
     int mpm_cnt = mpm_table[SMTP_MPM].Search(smtp_mpm_ctx, td->smtp_mpm_thread_ctx,
    
                                          td->pmq, state->current_line,
    
                                          3);
    
     if (mpm_cnt == 0) {
    
     /* set decoder event - reply code invalid */
    
     SMTPSetEvent(state, SMTP_DECODER_EVENT_INVALID_REPLY);
    
     SCLogDebug("invalid reply code %02x %02x %02x",
    
             state->current_line[0], state->current_line[1], state->current_line[2]);
    
     SCReturnInt(-1);
    
     }
    
     //获取响应码
    
     reply_code = smtp_reply_map[td->pmq->rule_id_array[0]].enum_value;
    
  
    
     //如果命令索引和命令个数相同,这个情况就是发生错误和服务器返回欢迎信息的时候才会出现
    
     if (state->cmds_idx == state->cmds_cnt) {
    
     //如果没有看到服务器返回的第一个消息
    
     if (!(state->parser_state & SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) {
    
         /* the first server reply can be a multiline message. Let's
    
          * flag the fact that we have seen the first reply only at the end
    
          * of a multiline reply
    
          */
    
         //如果没有解析到多行响应数据,则设置看到服务器返回的第一个消息标志,其实这个标志
    
         //也没啥用,就是smtp交互过程中的一个状态跟踪
    
         if (!(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY))
    
             state->parser_state |= SMTP_PARSER_STATE_FIRST_REPLY_SEEN;
    
         //如果响应码不是220,说明是个错误的响应,欢迎信息才会返回220后跟空格和一串欢迎信息
    
         if (reply_code == SMTP_REPLY_220)
    
             SCReturnInt(0);
    
         else {
    
             SMTPSetEvent(state, SMTP_DECODER_EVENT_INVALID_REPLY);
    
             SCReturnInt(0);
    
         }
    
     } else {
    
         //发送错误了
    
         /* decoder event - unable to match reply with request */
    
         SCLogDebug("unable to match reply with request");
    
         SCReturnInt(0);
    
     }
    
     }
    
   86.     //命令个数等于0,说明没有存储命令,还没有收到客户端的命令,
    
     //就收到了服务器的响应,肯定错误了。
    
     if (state->cmds_cnt == 0) {
    
     /* reply but not a command we have stored, fall through */
    
     } else if (state->cmds[state->cmds_idx] == SMTP_COMMAND_STARTTLS) {
    
     if (reply_code == SMTP_REPLY_220) {
    
         /* we are entering STARRTTLS data mode */
    
         state->parser_state |= SMTP_PARSER_STATE_COMMAND_DATA_MODE;
    
         AppLayerRequestProtocolTLSUpgrade(f);
    
         state->curr_tx->done = 1;
    
     } else {
    
         /* decoder event */
    
         SMTPSetEvent(state, SMTP_DECODER_EVENT_TLS_REJECTED);
    
     }
    
     } else if (state->cmds[state->cmds_idx] == SMTP_COMMAND_DATA) {
    
     //主要看这个data命令,代码也简单,响应码354正常,设置进入data命令模式
    
     //即将发送邮件数据
    
     if (reply_code == SMTP_REPLY_354) {
    
         /* Next comes the mail for the DATA command in toserver direction */
    
         state->parser_state |= SMTP_PARSER_STATE_COMMAND_DATA_MODE;
    
     } else {
    
         /* decoder event */
    
         SMTPSetEvent(state, SMTP_DECODER_EVENT_DATA_COMMAND_REJECTED);
    
     }
    
     } else {
    
     /* we don't care for any other command for now */
    
     /* check if reply falls in the valid list of replies for SMTP.  If not
    
      * decoder event */
    
     }
    
  
    
     //如果不是多行数据则命令索引加一,多行数据不能加一,因为多行数据属于同一个命令
    
     /* if it is a multi-line reply, we need to move the index only once for all
    
      * the line of the reply.  We unset the multiline flag on the last
    
      * line of the multiline reply, following which we increment the index */
    
     if (!(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY)) {
    
     state->cmds_idx++;
    
     } else if (state->parser_state & SMTP_PARSER_STATE_FIRST_REPLY_SEEN) {
    
     //如果仍然是多行数据,且看到过服务器返回的第一个消息就是欢迎信息(判断这有啥用)
    
     //则判断ehlo的多行数据中pipelining字符串,存在则设置标志,暂时不明白pipelining的含义
    
     /* we check if the server is indicating pipelining support */
    
     if (reply_code == SMTP_REPLY_250 && state->current_line_len == 14 &&
    
         SCMemcmpLowercase("pipelining", state->current_line+4, 10) == 0) {
    
         state->parser_state |= SMTP_PARSER_STATE_PIPELINING_SERVER;
    
     }
    
     }
    
  
    
     //如果命令索引等于命令个数,说明处理完了所有命令,则清零
    
     //命令索引为0表示第一个命令,命令个数为1,说明命令数组有一个命令,
    
     //这个大函数处理行数据后,不是多行,则命令索引加一,说处理完了,命令索引为0的响应,
    
     //于是清零,进入下一个命令处理,在服务器收到客户端命令时
    
     //将命令保存到命令数据,再对命令总数cmds_cnt加一
    
     /* if we have matched all the buffered commands, reset the cnt and index */
    
     if (state->cmds_idx == state->cmds_cnt) {
    
     state->cmds_cnt = 0;
    
     state->cmds_idx = 0;
    
     }
    
  
    
     return 0;
    
 }
    
  
    
    
    
    

全部评论 (0)

还没有任何评论哟~