分享
 
 
 

qmail源代码分析之qmail-smtpd.c

王朝other·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

多人对qmail smtp的认证机制,环境变量,执行顺序不太了解。

仔细看完这一大篇代码后相信你会明白很多你过去不太明白的问题。

当然你要有一点点c语言基础。也只要一点点。

PRogrammer:夜未眠

Date:Apr 28,2003

Comefrom: ChongQing Gearbox co.,ltd

这份文件还不完善,如果您完善了它请发一份给我: beggar110@163.com

这份文件是给想深入了解qmail和想hacker qmail的人读的,如果你只是想建立一个能够运作的mail服务器,没有必要读下去了。它将浪费你很多的时间。

如果你对qmail控制文件还不是很了解,阅读这份文件之前,请先阅读rainbow的《qmail控制文件详解》

在这里你可以找到http://www.chinaunix.net/forum/viewtopic.php?t=1126

好的。开始我们qmail内部的漫游吧!!!Let's go!

[code:1:d803669f6f]

qmail 总览

tcpserver MUA

| |

V V

qmail-smtpd qmail-inject

| |

+---------qmail-queue

|

|

qmail-send

|

+------------+------------+

| |

V V

qmail-rspawn qmail-lspawn

| |

V V

qmail-remote qmail-local

| |

| |

V V

INTERNET

|

|

vchkpw

|

|

qmail-popup

|

|

tcpserver--+

[/code:1:d803669f6f]

qmail-smtpd.c源代码分析(去掉了所有include)

qmail-smtpd是由tcpserver或由tcp-env启动。tcpserver负责监听端口,如果指定了-x rule.cbd,tcpserver会先决断是断开连接还是启动qmail子进程。如果没有指定-x参数启动tcpserver,那么直接启动qmail-smtpd.启动qmail-smtpd之前将来自网络的数据连接重定向到qmail-smtpd的fd0,fd1.还会初始化一些qmail-smtpd需要的环境变量,如TCPREMOTEip.

tcp-env只会初始化qmail-smtpd的环境变量,不负责监听端口及重定向网络连接。所以tcp-env要和inetd配合使用。当然,由于初始化环境变量的工作tcpserver也会作,所以没有必要tcpserver和tcp-env配合使用.

qmail-smtpd完成邮件smtp命令的接收,并调用相应的处理程序。

检查mail 中的地址是否在control/badmailfrom中定义(MAIL命令)

检查是否设置了RELAYCLIENT环境变量或 rcpt 中的地址是否是control/rcpthosts中定义(RCPT命令)

需要明确的是qmail-smtpd只是简单的接收邮件内容传送给qmail-queue,并不对邮件进行转发(DATA命令)。

当然还要向qmail-queue传送mailfrom,mailto

[code:1:d803669f6f]

#define MAXHOPS 100

unsigned int databytes = 0; //邮件最大长度:0=无限

int timeout = 1200; //默认超时20分钟

//向网络写,超时值为control/timeoutsmtpd指定的值。没有这个文件则取默认值20分钟

int safewrite(fd,buf,len) int fd; char *buf; int len;

{

int r;

r = timeoutwrite(timeout,fd,buf,len);

if (r

return r;

}

char ssoutbuf[512];

substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf);

void flush() { substdio_flush(%26amp;ssout); }

void out(s) char *s; { substdio_puts(%26amp;ssout,s); }

//错误处理函数

void die_read() { _exit(1); }

void die_alarm() { out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); }

void die_nomem() { out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); }

void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); }

void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); }

void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); }

void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }

void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }

void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); }

void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); }

void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); }

void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); }

void err_noop() { out("250 ok\r\n"); }

void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); }

void err_QQt() { out("451 qqt failure (#4.3.0)\r\n"); }

stralloc greeting = {0};

//输出提示信息*code

void smtp_greet(code) char *code;

{

substdio_puts(%26amp;ssout,code);

substdio_put(%26amp;ssout,greeting.s,greeting.len);

}

void smtp_help()

{

out("214 qmail home page: http://pobox.com/~djb/qmail.html\r\n");

}

void smtp_quit()

{

smtp_greet("221 "); out("\r\n"); flush(); _exit(0);

}

char *remoteip; //远端ip地址

char *remotehost; //远端主机名

char *remoteinfo; //远端信息

char *local; //本地主机

char *relayclient; //是否检查rcpthosts文件

stralloc helohost = {0};

char *fakehelo; /* pointer into helohost, or 0 */

void dohelo(arg) char *arg; {

if (!stralloc_copys(%26amp;helohost,arg)) die_nomem();

if (!stralloc_0(%26amp;helohost)) die_nomem();

//fakehelo变量,如果helo 参数指定的主机名与TCPREMOTEHOST环境变量中的主机名不同则

//fakehelo的值为helo命令的参数指定的主机名.如果两者相同则fekehelo为NULL;

//data命令处理程式用到这个变量

fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0;

}

int liphostok = 0;

stralloc liphost = {0};

int bmfok = 0;

stralloc bmf = {0};

struct constmap mapbmf;

void setup()

{

char *x;

unsigned long u;

if (control_init() == -1) die_control(); //control/me

//读入欢迎信息greeting,如果不存在则从me文件复制

if (control_rldef(%26amp;greeting,"control/smtpgreeting",1,(char *) 0) != 1)

die_control();

//读入localiphost,如果文件不存在则从me文件复制

liphostok = control_rldef(%26amp;liphost,"control/localiphost",1,(char *) 0);

if (liphostok == -1) die_control();

//读control/timeoutsmtpd存入timeout,用于控制超时的情况.

if (control_readint(%26amp;timeout,"control/timeoutsmtpd") == -1) die_control();

if (timeout

if (rcpthosts_init() == -1) die_control();

//读入badmailfrom文件存入 bmf

bmfok = control_readfile(%26amp;bmf,"control/badmailfrom",0);

if (bmfok == -1) die_control();

if (bmfok)

if (!constmap_init(%26amp;mapbmf,bmf.s,bmf.len,0)) die_nomem();

//读入databytes文件存入 databytes,如果该文件不存在,则将

//databytes的值设为0.

if (control_readint(%26amp;databytes,"control/databytes") == -1) die_control();

x = env_get("DATABYTES");

if (x) { scan_ulong(x,%26amp;u); databytes = u; }

if (!(databytes + 1)) --databytes;

//取tcp-environ环境变量,如果环境变量没有设置,将它的值设置为unknow.

//这些信息来自tcpserver,或tcp-env之类的程式

remoteip = env_get("TCPREMOTEIP");

if (!remoteip) remoteip = "unknown";

local = env_get("TCPLOCALHOST");

if (!local) local = env_get("TCPLOCALIP");

if (!local) local = "unknown";

remotehost = env_get("TCPREMOTEHOST");

if (!remotehost) remotehost = "unknown";

remoteinfo = env_get("TCPREMOTEINFO");

//从环境变量RELAYCLIENT读入.

//如果RELAYCLIENT变量没有设置那么relayclient将会是NULL.

relayclient = env_get("RELAYCLIENT");

dohelo(remotehost);

}

stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */

//对命令参数arg进行邮件地址分析

//并将分离出的email地址存入全局缓存addr

//成功返回值为1,失败返回0

int addrparse(arg)

char *arg;

{

int i;

char ch;

char terminator;

struct ip_address ip;

int flagesc;

int flagquoted;

//分离出邮件地址

//例如: arg="",或 arg=": email@eg.org "

//执行下面这段程式后arg="email@eg.org"

terminator = '';

i = str_chr(arg,'

if (arg[i])

arg += i + 1;

else { /* partner should go read rfc 821 */

terminator = ' ';

arg += str_chr(arg,':');

if (*arg == ':') ++arg;

while (*arg == ' ') ++arg;

}

/* strip source route */

if (*arg == '@') while (*arg) if (*arg++ == ':') break;

if (!stralloc_copys(%26amp;addr,"")) die_nomem();

flagesc = 0;

flagquoted = 0;

for (i = 0;ch = arg[i];++i) { /* copy arg to addr, stripping quotes */

if (flagesc) {

if (!stralloc_append(%26amp;addr,%26amp;ch)) die_nomem();

flagesc = 0;

}

else {

if (!flagquoted %26amp;%26amp; (ch == terminator)) break;

switch(ch) {

case '\\': flagesc = 1; break;

case '"': flagquoted = !flagquoted; break;

default: if (!stralloc_append(%26amp;addr,%26amp;ch)) die_nomem();

}

}

}

/* could check for termination failure here, but why bother? */

if (!stralloc_append(%26amp;addr,"")) die_nomem();

//将ip地址转换为主机名:

//如 test@[10.0.6.21] 转换为 test@host.mydomain.org

//依据是control/localiphost文件中有host.mydomain.org

if (liphostok) {

i = byte_rchr(addr.s,addr.len,'@');

if (i if (addr.s[i + 1] == '[')//比较是否是用[]括起来的IP地址if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,%26amp;ip)])if (ipme_is(%26amp;ip)) {addr.len = i + 1;if (!stralloc_cat(%26amp;addr,%26amp;liphost)) die_nomem();if (!stralloc_0(%26amp;addr)) die_nomem();}}if (addr.len900) return 0; //地址太长,出错返回return 1;//成功返回}//简单的垃圾邮件检查//检查全局缓冲区addr中的地址是否有在badmailfrom中定义,//如果有则返回 1,否则返回 0.int bmfcheck(){int j;if (!bmfok) return 0;if (constmap(%26amp;mapbmf,addr.s,addr.len - 1)) return 1;j = byte_rchr(addr.s,addr.len,'@');if (jif (constmap(%26amp;mapbmf,addr.s + j,addr.len - j - 1)) return 1;return 0;}//检查全局缓存addr中的邮件地址是否要进行转发(依据control/rcpthosts文件)//可以进行转发返回1//拒绝转发返回0int addrallowed(){int r;r = rcpthosts(addr.s,str_len(addr.s));if (r == -1) die_control();return r;}int seenmail = 0;int flagbarf; /* defined if seenmail */stralloc mailfrom = {0};stralloc rcptto = {0};void smtp_helo(arg) char *arg;{smtp_greet("250 "); out("\r\n");seenmail = 0; dohelo(arg);}void smtp_ehlo(arg) char *arg;{smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");seenmail = 0; dohelo(arg);}//重新初始化//调用helo或ehlo命令都会完成相同的功能void smtp_rset(){seenmail = 0;out("250 flushed\r\n");}//mail命令解释程式. 重要变量: [mailfrom /全局]//该函数完成检查mailfrom是否在badmailfrom中定义//设置标志指明mail命令已经执行void smtp_mail(arg) char *arg;{if (!addrparse(arg)) { err_syntax(); return; }flagbarf = bmfcheck(); //检查是否badmailfrom,如果是设置相应标志,这个标志在rcpt命令的处理程式中才起作用seenmail = 1;//指示已经执行过mail命令.if (!stralloc_copys(%26amp;rcptto,"")) die_nomem();//分配rcptto缓冲区if (!stralloc_copys(%26amp;mailfrom,addr.s)) die_nomem();//复制mail命令中指定的地址到mailfromif (!stralloc_0(%26amp;mailfrom)) die_nomem();out("250 ok\r\n");}//rcpt命令解释程式. 重要变量: [ rcptto /全局]void smtp_rcpt(arg) char *arg; {if (!seenmail) { err_wantmail(); return; }//mail命令是否已执行?if (!addrparse(arg)) { err_syntax(); return; }//分离邮件地址参数存入全局缓存addrif (flagbarf) { err_bmf(); return; }//如果mail命令中的地址在control/badmailfrom中有定义,返回//至此addr缓存中包含了rcpt命令指定的email地址.//如果rcpt命令,则有addr="email@eg.org".这个变量是在addrparse函数中符值的//如果 RELAYCLIENT 环境变量设置将不进行rcpthosts,morercpthosts.cdb的比较//注意,打过smtp认证补丁,如果通过认证后会设置relayclient=""if (relayclient) {--addr.len;if (!stralloc_cats(%26amp;addr,relayclient)) die_nomem();if (!stralloc_0(%26amp;addr)) die_nomem();}else//如果没有指定RELAYCLIENT变量,则由control/rcpthosts决定是否进行转发if (!addrallowed()) { err_nogateway(); return; }//生成头连接到全局缓存rcptto://例如地址'rcpttest@eg.org' 命令将产生 rcptto="Temail@eg.org"//多次执行rcpt命令效果会是rcptto="Ttest@eg.orgTtwo@eg.org"if (!stralloc_cats(%26amp;rcptto,"T")) die_nomem();if (!stralloc_cats(%26amp;rcptto,addr.s)) die_nomem();if (!stralloc_0(%26amp;rcptto)) die_nomem();out("250 ok\r\n");}//saferead,从网络读len个字节到buf缓冲区//返回实际读到的字节数.//超时值为control/timeoutsmtpd文件中指定的值。见setup()函数.(默认值1200秒)int saferead(fd,buf,len) int fd; char *buf; int len;{int r;flush();r = timeoutread(timeout,fd,buf,len);if (r == -1) if (errno == error_timeout) die_alarm();if (rreturn r;}char ssinbuf[1024];substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof ssinbuf);struct qmail qqt;unsigned int bytestooverflow = 0;void put(ch)char *ch;{if (bytestooverflow)if (!--bytestooverflow)qmail_fail(%26amp;qqt);qmail_put(%26amp;qqt,ch,1);}void blast(hops)int *hops;{char ch;int state;int flaginheader;int pos; /* number of bytes since most recent \n, if fih */int flagmaybex; /* 1 if this line might match RECEIVED, if fih */int flagmaybey; /* 1 if this line might match \r\n, if fih */int flagmaybez; /* 1 if this line might match DELIVERED, if fih */state = 1;*hops = 0;flaginheader = 1;pos = 0; flagmaybex = flagmaybey = flagmaybez = 1;for (;

{substdio_get(%26amp;ssin,%26amp;ch,1);//从标准输入(也就是网络)读邮件内容直到读到仅有一个点的行.if (flaginheader) {if (posif (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0;if (flagmaybez) if (pos == 8) ++*hops;if (posif (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0;if (flagmaybex) if (pos == 7) ++*hops;if (posif (flagmaybey) if (pos == 1) flaginheader = 0;}++pos;if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; }}switch(state) {case 0:if (ch == '\n') straynewline();if (ch == '\r') { state = 4; continue; }break;case 1: /* \r\n */if (ch == '\n') straynewline();if (ch == '.') { state = 2; continue; }if (ch == '\r') { state = 4; continue; }state = 0;break;case 2: /* \r\n + . */if (ch == '\n') straynewline();if (ch == '\r') { state = 3; continue; }state = 0;break;case 3: /* \r\n + .\r */if (ch == '\n') return;put(".");put("\r");if (ch == '\r') { state = 4; continue; }state = 0;break;case 4: /* + \r */if (ch == '\n') { state = 1; break; }if (ch != '\r') { put("\r"); state = 0; }}put(%26amp;ch);}}char accept_buf[FMT_ULONG];void acceptmessage(qp) unsigned long qp;{datetime_sec when;when = now();out("250 ok ");accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0;out(accept_buf);out(" qp ");accept_buf[fmt_ulong(accept_buf,qp)] = 0;out(accept_buf);out("\r\n");}//data 命令解释程式//完成向qmail-queue投递邮件void smtp_data() {int hops;unsigned long qp;char *qqx;if (!seenmail) { err_wantmail(); return; } //如果没有执行过mail命令,出错返回if (!rcptto.len) { err_wantrcpt(); return; } //如果没有执行rcpt命令,出错返回seenmail = 0; //将mail命令标志失效,//databytes 邮件最大长度,如果没有指定那么它的值将是0if (databytes) bytestooverflow = databytes + 1;if (qmail_open(%26amp;qqt) == -1) { err_qqt(); return; }//建立子进程执行qmail-queueqp = qmail_qp(%26amp;qqt); //qp 为qmail-queue process缩写,it's a process id.out("354 go ahead\r\n");//向新建立的进程传送邮件头received(%26amp;qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);blast(%26amp;hops);hops = (hops= MAXHOPS);if (hops) qmail_fail(%26amp;qqt);//向qmail-queue传送邮件头信息.//如果hong@hg.org 向lyx@hg.org发送邮件,那么向qmail-queue传送的字符串将是// Tlyx@hg.org"Fhong@hg.orgTlyx@hg.orgqmail_from(%26amp;qqt,mailfrom.s);qmail_put(%26amp;qqt,rcptto.s,rcptto.len);qqx = qmail_close(%26amp;qqt);if (!*qqx) //如果接收成功if (hops) if (databytes) if (!bytestooverflow) if (*qqx == 'D') out("554 "); else out("451 ");out(qqx + 1);out("\r\n");}//smtp命令处理函数表struct commands smtpcommands[] = {, , //建立子进程执行qamil-queue,并向其传送邮件., , , , , , //实际上未实现的命令, //实际上未实现的命令, //命令错误} ;/*qmail-smtpd 是由tcpserver,或tcp-env之类的程式启动tcpserver,tcp-env将来自网络的连接重定向到qmail-smtpd的标准输入及标准输出.这些程式建立一些环境变量(如TCPREMOTEHOST,TCPREMOTEIP)将由setup()函数使用*/void main(){sig_pipeignore();//忽略信号.if (chdir(auto_qmail) == -1) die_control();//改变当前目录到 /var/qmail.setup();//读控制文件及相应的环境变量.if (ipme_init() != 1) die_ipme(); //取本地接口的IP地址:smtp_greet("220 "); //显示欢迎信息.out(" ESMTP\r\n");//从标准输入(网络连接)读入smtp命令.if (commands(%26amp;ssin,%26amp;smtpcommands) == 0) die_read();die_nomem();}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有