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)
还没有任何评论哟~
