3.8.2 smtpd_chat_query和smtpd_chat_replay:与smtp客户端交互

来源:互联网 发布:js获取鼠标点击次数 编辑:程序博客网 时间:2024/05/20 09:07

VSTRING和VSTREAM用来支持postfix的上层逻辑。我们来看两个应用场景:

 

第一个场景:smtpd.c利用smtpd_chat_query和smtpd_chat_replay函数与客户端通信。

 

         在smtpd.c中的smtp协议解析过程中,与客户端的通信在两个函数中完成:smtpd_chat_query和smtpd_chat_replay。也就是从客户端取得和回应一行。这两个函数为smtp会话过程封装了通信技术细节。它们通过逐步封装VSTING和VSTREAM实现。在协议解析过程中,我们主要关注于业务而非底层操作,VSTRING和VSTREAM可以为我们屏蔽底层操作细节。与smtp服务器通信的客户可能来自网络或控制台,但在协议解析中我们不必区分这些细节——VSTREAM已经帮我们封装了这些底层差别,他们都是“流”。

 

         我们来看一下这里的“层层封装”的过程:

 

(1)smtpd_chat_query和smtpd_chat_replay调用smtp_get和smtp_fputs函数实现,同时提供自己的处理。

         /smtpd/smtpd_chat.c中的smtpd_chat_query和smtpd_chat_replay函数通过/global/smtp_stream.c中的smtp_get和smtp_fputs函数实现。

/smtpd/smtpd_chat.c124 /* smtpd_chat_query - receive andrecord an SMTP request */125126 void   smtpd_chat_query(SMTPD_STATE *state)127 {128    int     last_char;129130    /*131     * We can't parse or store input that exceeds var_line_limit, so we skip132     * over it to avoid loss of synchronization.133     */134    last_char = smtp_get(state->buffer, state->client, var_line_limit,135                          SMTP_GET_FLAG_SKIP);136    smtp_chat_append(state, "In: ", STR(state->buffer));137    if (last_char != '\n')138        msg_warn("%s: request longer than %d: %.30s...",139                  state->namaddr,var_line_limit,140                 printable(STR(state->buffer), '?'));141142    if (msg_verbose)143        msg_info("< %s: %s", state->namaddr,STR(state->buffer));144 }


smtpd_chat_query的实现较为简单,调用smtp_get从流得到行,调用smtp_chat_append函数记录历史数据:

/smtpd/smtpd_chat.c107 /* smtp_chat_append - append record toSMTP transaction log */108109 static voidsmtp_chat_append(SMTPD_STATE *state, char *direction,110                                      const char *text)111 {112    char   *line;113114    if (state->notify_mask == 0)115        return;116117    if (state->history == 0)118        state->history = argv_alloc(10);119    line = concatenate(direction, text, (char *) 0);120    argv_add(state->history, line, (char *) 0);121    myfree(line);122 }

smtpd_chat_replay函数则较为复杂:

/smtpd/smtp_chat.c146 /* smtpd_chat_reply - format, send andrecord an SMTP response */147148 void   smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)149 {156    /*157     * Slow down clients that make errors. Sleep-on-anything slows down158     * clients that make an excessive number of errors within a session.159     */160    if (state->error_count >= var_smtpd_soft_erlim)161        sleep(delay = var_smtpd_err_sleep);

160 变量var_smtpd_soft_erlim,即在smtpd.c中main函数中初始化的postfix参数smtpd_soft_error_limit,默认值为10

 

由于网络上针对邮件系统的恶意客户端很多,所以postfix设置了很多保护自己安全的方式。smtpd_soft_error_limit和smtpd_hard_error_limit就是其中两个参数:如果客户端出错过多,表明客户端可能有问题或者有攻击倾向,如果出错数大于smtpd_soft_error_limit所设定的值,且不超过smtpd_hard_error_limit(默认20)的值,则服务器等待(sleep)smtp_error_sleep_time参数所定义的时间(默认一秒)。如果错误数大于smtpd_hard_error_limit的值,则切断连接。

 

postfix没有简单粗暴的对客户端出错直接断开连接,所以用户通过telnet进行客户端会话时,如果出现输入错误,依然有重试命令的机会。这两个参数的效果可以在命令行模式的smtp会话中通过重复输入错误命令来查看,在默认参数下,重复10次输入错误命令后,可能用户感觉不到这个1秒的延迟,但重复输入20次错误命令后,服务器将断开客户连接。

 

如果客户端违反postfix的ACL规则,将累加SMTPD_STATE->error_count。

 

也就是smtpd_chat_reply函数不仅仅是会给出响应代码,它同时控制着服务器对客户端的反应行为。

167    if (*var_smtpd_rej_footer168        && (*(cp = STR(state->buffer)) == '4' || *cp == '5'))169        smtp_reply_footer(state->buffer, 0, var_smtpd_rej_footer,170                          STR(smtpd_expand_filter), smtpd_expand_lookup,171                           (void *) state);

 

167 smtpd_reject_footer是postfix2.8新增的一个参数,用来在输出smtp出错信息的同时(响应码第一位为4或5时表示出错),输出一个“页脚”,也就是一些附加帮助信息。

我们看一下官方文档上的例子:

http://www.postfix.org/postconf.5.html#smtpd_reject_footer 

 

/etc/postfix/main.cf:
    smtpd_reject_footer = \c. For assistance, call 800-555-0101.
     Please provide the following information in your problem report:
     time ($localtime), client ($client_address) and server
     ($server_name).

Server response:

    550-5.5.1 <user@example> Recipient address rejected: User
    unknown. For assistance, call 800-555-0101. Please provide the
    following information in your problem report: time (Jan 4 15:42:00),
client (192.168.1.248) and server (mail1.example.com).

172173    /* All 5xx replies must have a 5.xx.xx detail code. */174    for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;){175        if (var_soft_bounce) {176             if (cp[0] == '5') {177                 cp[0] = '4';178                 if (cp[4] == '5')179                     cp[4] = '4';180             }181        }

 

175 var_soft_bounce即参数soft_bounce,postfix使用此参数测试内部ACL限制的生效情况。以5开头的smtp响应码为不可恢复错误,以4开头的为可恢复错误。设置soft_bounce=yes后,将把以5开头的smtp响应码改为以4开头,以方便测试ACL限制条件。

182        /* This is why we use strlen() above instead of VSTRING_LEN(). */183        if ((next = strstr(cp, "\r\n")) != 0) {184             *next = 0;185             if (next[2] != 0)186                 cp[3] = '-';                    /* contact footer kludge */187             else188                 next = end;                     /* strip trailing \r\n */189        } else {190             next = end;191        }192        smtp_chat_append(state, "Out: ", cp);

 

192 记录响应历史数据。

193194        if (msg_verbose)195             msg_info("> %s: %s",state->namaddr, cp);196197        smtp_fputs(cp, next - cp, state->client);

 

197 调用smtp_fputs向客户端返回一行。

198        if (next < end)199             cp = next + 2;200        else201             break;202    }203204    /*205     * Flush unsent output if no I/O happened for a while. This avoids206     * timeouts with pipelined SMTPsessions that have lots of server-side207     * delays (tarpit delays or DNS lookups for UCE restrictions).208     */209    if (delay || time((time_t *) 0) - vstream_ftime(state->client) >10)210        vstream_fflush(state->client);211212    /*213     * Abort immediately if the connection is broken.214     */215    if (vstream_ftimeout(state->client))216        vstream_longjmp(state->client, SMTP_ERR_TIME);217    if (vstream_ferror(state->client))218         vstream_longjmp(state->client,SMTP_ERR_EOF);

 

215-218 如果出现SMTP_ERR_TIME或SMTP_ERR_EOF错误,使用siglongjmp跳到出错处理语句执行。这也是处理smtp会话中错误的一处例子。

219220    /*221     * Orderly disconnect in case of 421 or 521 reply.222     */223    if (strncmp(STR(state->buffer), "421", 3) == 0224        || strncmp(STR(state->buffer), "521", 3) == 0)225        state->flags |= SMTPD_FLAG_HANGUP;226 }


223-225 对421(服务未就绪或临时禁止远端ip连接)和521(拒绝远端ip访问)错误设置SMTPD_FLAG_HANGUP标记,切断连接。
 

接着逐级封装如下:

(2)smtp_get和smtp_fputs函数调用vstring_get和vstring_fputs实现,同时提供了换行符(CRLF)的处理。

(3) vstring_get和vstring_fput实现了真正的从流中取出行的操作。

(4)vstring_get和vstring_fput的操作最终在其VBUF里实现。
0 0