您的位置:首页 > 产品设计 > UI/UE

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

2016-04-07 10:14 405 查看
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.c 124/*smtpd_chat_query-receiveandrecordanSMTPrequest*/ 125 126voidsmtpd_chat_query(SMTPD_STATE*state) 127{ 128intlast_char; 129 130/* 131*Wecan'tparseorstoreinputthatexceedsvar_line_limit,soweskip 132*overittoavoidlossofsynchronization. 133*/ 134last_char=smtp_get(state->buffer,state->client,var_line_limit, 135SMTP_GET_FLAG_SKIP); 136smtp_chat_append(state,"In:",STR(state->buffer)); 137if(last_char!='\n') 138msg_warn("%s:requestlongerthan%d:%.30s...", 139state->namaddr,var_line_limit, 140printable(STR(state->buffer),'?')); 141 142if(msg_verbose) 143msg_info("<%s:%s",state->namaddr,STR(state->buffer)); 144}smtpd_chat_query的实现较为简单,调用smtp_get从流得到行,调用smtp_chat_append函数记录历史数据:/smtpd/smtpd_chat.c 107/*smtp_chat_append-appendrecordtoSMTPtransactionlog*/ 108 109staticvoidsmtp_chat_append(SMTPD_STATE*state,char*direction, 110constchar*text) 111{ 112char*line; 113 114if(state->notify_mask==0) 115return; 116 117if(state->history==0) 118state->history=argv_alloc(10); 119line=concatenate(direction,text,(char*)0); 120argv_add(state->history,line,(char*)0); 121myfree(line); 122}smtpd_chat_replay函数则较为复杂:/smtpd/smtp_chat.c146/*smtpd_chat_reply-format,sendandrecordanSMTPresponse*/147148voidsmtpd_chat_reply(SMTPD_STATE*state,constchar*format,...)149{156/*157*Slowdownclientsthatmakeerrors.Sleep-on-anythingslowsdown158*clientsthatmakeanexcessivenumberoferrorswithinasession.159*/160if(state->error_count>=var_smtpd_soft_erlim)161sleep(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函数不仅仅是会给出响应代码,它同时控制着服务器对客户端的反应行为。167if(*var_smtpd_rej_footer168&&(*(cp=STR(state->buffer))=='4'||*cp=='5'))169smtp_reply_footer(state->buffer,0,var_smtpd_rej_footer,170STR(smtpd_expand_filter),smtpd_expand_lookup,171(void*)state);167smtpd_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.Forassistance,call800-555-0101.
Pleaseprovidethefollowinginformationinyourproblemreport:
time($localtime),client($client_address)andserver
($server_name).
Serverresponse:
550-5.5.1<user@example>Recipientaddressrejected:User
unknown.Forassistance,call800-555-0101.Pleaseprovidethe
followinginformationinyourproblemreport:time(Jan415:42:00),
client(192.168.1.248)andserver(mail1.example.com).
172173/*All5xxrepliesmusthavea5.xx.xxdetailcode.*/174for(cp=STR(state->buffer),end=cp+strlen(STR(state->buffer));;){175if(var_soft_bounce){176if(cp[0]=='5'){177cp[0]='4';178if(cp[4]=='5')179cp[4]='4';180}181}
175var_soft_bounce即参数soft_bounce,postfix使用此参数测试内部ACL限制的生效情况。以5开头的smtp响应码为不可恢复错误,以4开头的为可恢复错误。设置soft_bounce=yes后,将把以5开头的smtp响应码改为以4开头,以方便测试ACL限制条件。
182/*Thisiswhyweusestrlen()aboveinsteadofVSTRING_LEN().*/183if((next=strstr(cp,"\r\n"))!=0){184*next=0;185if(next[2]!=0)186cp[3]='-';/*contactfooterkludge*/187else188next=end;/*striptrailing\r\n*/189}else{190next=end;191}192smtp_chat_append(state,"Out:",cp);
192记录响应历史数据。
193194if(msg_verbose)195msg_info(">%s:%s",state->namaddr,cp);196197smtp_fputs(cp,next-cp,state->client);
197调用smtp_fputs向客户端返回一行。
198if(next<end)199cp=next+2;200else201break;202}203204/*205*FlushunsentoutputifnoI/Ohappenedforawhile.Thisavoids206*timeoutswithpipelinedSMTPsessionsthathavelotsofserver-side207*delays(tarpitdelaysorDNSlookupsforUCErestrictions).208*/209if(delay||time((time_t*)0)-vstream_ftime(state->client)>10)210vstream_fflush(state->client);211212/*213*Abortimmediatelyiftheconnectionisbroken.214*/215if(vstream_ftimeout(state->client))216vstream_longjmp(state->client,SMTP_ERR_TIME);217if(vstream_ferror(state->client))218vstream_longjmp(state->client,SMTP_ERR_EOF);
215-218如果出现SMTP_ERR_TIME或SMTP_ERR_EOF错误,使用siglongjmp跳到出错处理语句执行。这也是处理smtp会话中错误的一处例子。
219220/*221*Orderlydisconnectincaseof421or521reply.222*/223if(strncmp(STR(state->buffer),"421",3)==0224||strncmp(STR(state->buffer),"521",3)==0)225state->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里实现。
                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: