下面是个程序员眼中的qmail(qmail源代码分析)教程,撑握了其技术要点,学起来就简单多了。赶紧跟着图老师小编一起来看看吧!
【 tulaoshi.com - 编程语言 】
很多人对qmail smtp的认证机制,环境变量,执行顺序不太了解。 仔细看完这一大篇代码后相信你会明白很多你过去不太明白的问题。 当然你要有一点点c语言基础。也只要一点点。 Come from: ChongQing Gearbox co.,ltd 这份文件还不完善,假如您完善了它请发一份给我: beggar110@163.com 这份文件是给想深入了解qmail和想hacker qmail的人读的,假如你只是想建立一个能够运作的mail服务器,没有必要读下去了。它将浪费你很多的时间。 假如你对qmail控制文件还不是很了解,阅读这份文件之前,请先阅读rainbow的《qmail控制文件详解》 在这里你可以找到www.chinaunix.net/forum/viewtopic.php?t=1126 好的。开始我们qmail内部的漫游吧!!!Let's go! 代码: 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 ----qmail-pop3d vchkpw qmail-popup tcpserver--+ 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 代码: #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 = 0) _exit(1); return r; } char ssoutbuf[512]; substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf); void flush() { substdio_flush(&ssout); } void out(s) char *s; { substdio_puts(&ssout,s); } //错误处理函数 void die_read() { _exit(1); } void die_alarm() { out("451 timeout (#4.4.2)"); flush(); _exit(1); } void die_nomem() { out("421 out of memory (#4.3.0)"); flush(); _exit(1); } void die_control() { out("421 unable to read controls (#4.3.0)"); flush(); _exit(1); } void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)"); flush(); _exit(1); } void straynewline() { out("451 See pobox.com/~djb/docs/smtplf.Html."); flush(); _exit(1); } void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)"); } void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)"); } void err_unimpl() { out("502 unimplemented (#5.5.1)"); } void err_syntax() { out("555 syntax error (#5.5.4)"); } void err_wantmail() { out("503 MAIL first (#5.5.1)"); } void err_wantrcpt() { out("503 RCPT first (#5.5.1)"); } void err_noop() { out("250 ok"); } void err_vrfy() { out("252 send some mail, i'll try my best"); } void err_QQt() { out("451 qqt failure (#4.3.0)"); } stralloc greeting = {0}; //输出提示信息*code void smtp_greet(code) char *code; { substdio_puts(&ssout,code); substdio_put(&ssout,greeting.s,greeting.len); } void smtp_help() { out("214 qmail home page: voidpobox.com/~djb/qmail.html"); } void smtp_quit() { smtp_greet("221 "); out(""); 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(&helohost,arg)) die_nomem(); if (!stralloc_0(&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(&greeting,"control/smtpgreeting",1,(char *) 0) != 1) die_control(); //读入localiphost,假如文件不存在则从me文件复制 liphostok = control_rldef(&liphost,"control/localiphost",1,(char *) 0); if (liphostok == -1) die_control(); //读control/timeoutsmtpd存入timeout,用于控制超时的情况. if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control(); if (timeout = 0) timeout = 1; if (rcpthosts_init() == -1) die_control(); //读入badmailfrom文件存入 bmf bmfok = control_readfile(&bmf,"control/badmailfrom",0); if (bmfok == -1) die_control(); if (bmfok) if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem(); //读入databytes文件存入 databytes,假如该文件不存在,则将 //databytes的值设为0. if (control_readint(&databytes,"control/databytes") == -1) die_control(); x = env_get("DATABYTES"); if (x) { scan_ulong(x,&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(&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(&addr,&ch)) die_nomem(); flagesc = 0; } else { if (!flagquoted && (ch == terminator)) break; switch(ch) { case '': flagesc = 1; break; case '"': flagquoted = !flagquoted; break; default: if (!stralloc_append(&addr,&ch)) die_nomem(); } } } /* could check for termination failure here, but why bother? */ if (!stralloc_append(&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 addr.len) /* if not, partner should go read rfc 821 */ if (addr.s[i + 1] == '[')//比较是否是用[]括起来的IP地址 if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)]) if (ipme_is(&ip)) { addr.len = i + 1; if (!stralloc_cat(&addr,&liphost)) die_nomem(); if (!stralloc_0(&addr)) die_nomem(); } } if (addr.len 900) return 0; //地址太长,出错返回 return 1;//成功返回 } //简单的垃圾邮件检查 //检查全局缓冲区addr中的地址是否有在badmailfrom中定义, //假如有则返回 1,否则返回 0. int bmfcheck() { int j; if (!bmfok) return 0; if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1; j = byte_rchr(addr.s,addr.len,'@'); if (j addr.len) if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1; return 0; } //检查全局缓存addr中的邮件地址是否要进行转发(依据control/rcpthosts文件) //可以进行转发返回1 //拒绝转发返回0 int 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(""); seenmail = 0; dohelo(arg); } void smtp_ehlo(arg) char *arg; { smtp_greet("250-"); out("250-PIPELINING250 8BITMIME"); seenmail = 0; dohelo(arg); } //重新初始化 //调用helo或ehlo命令都会完成相同的功能 void smtp_rset() { seenmail = 0; out("250 flushed"); } //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(&rcptto,"")) die_nomem();//分配rcptto缓冲区 if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();//复制mail命令中指定的地址到mailfrom if (!stralloc_0(&mailfrom)) die_nomem(); out("250 ok"); } //rcpt命令解释程式. 重要变量: [ rcptto /全局] void smtp_rcpt(arg) char *arg; { if (!seenmail) { err_wantmail(); return; }//mail命令是否已执行? if (!addrparse(arg)) { err_syntax(); return; }//分离邮件地址参数存入全局缓存addr if (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(&addr,relayclient)) die_nomem(); if (!stralloc_0(&addr)) die_nomem(); } else//假如没有指定RELAYCLIENT变量,则由control/rcpthosts决定是否进行转发 if (!addrallowed()) { err_nogateway(); return; } //生成头连接到全局缓存rcptto: //例如地址'rcpt test@eg.org' 命令将产生 rcptto="Temail@eg.org" //多次执行rcpt命令效果会是rcptto="Ttest@eg.orgTtwo@eg.org" if (!stralloc_cats(&rcptto,"T")) die_nomem(); if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); if (!stralloc_0(&rcptto)) die_nomem(); out("250 ok"); } //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 (r = 0) die_read(); return 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(&qqt); qmail_put(&qqt,ch,1); } void blast(hops) int *hops; { char ch; int state; int flaginheader; int pos; /* number of bytes since most recent , if fih */ int flagmaybex; /* 1 if this line might match RECEIVED, if fih */ int flagmaybey; /* 1 if this line might match , 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(&ssin,&ch,1);//从标准输入(也就是网络)读邮件内容直到读到仅有一个点的行. if (flaginheader) { if (pos 9) { if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0; if (flagmaybez) if (pos == 8) ++*hops; if (pos 8) if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0; if (flagmaybex) if (pos == 7) ++*hops; if (pos 2) if (ch != ""[pos]) flagmaybey = 0; if (flagmaybey) if (pos == 1) flaginheader = 0; } ++pos; if (ch == '') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; } } switch(state) { case 0: if (ch == '') straynewline(); if (ch == '') { state = 4; continue; } break; case 1: /* */ if (ch == '') straynewline(); if (ch == '.') { state = 2; continue; } if (ch == '') { state = 4; continue; } state = 0; break; case 2: /* + . */ if (ch == '') straynewline(); if (ch == '') { state = 3; continue; } state = 0; break; case 3: /* + . */ if (ch == '') return; put("."); put(""); if (ch == '') { state = 4; continue; } state = 0; break; case 4: /* + */ if (ch == '') { state = 1; break; } if (ch != '') { put(""); state = 0; } } put(&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(""); } //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 邮件最大长度,假如没有指定那么它的值将是0 if (databytes) bytestooverflow = databytes + 1; if (qmail_open(&qqt) == -1) { err_qqt(); return; }//建立子进程执行qmail-queue qp = qmail_qp(&qqt); //qp 为qmail-queue process缩写,it's a process id. out("354 go ahead"); //向新建立的进程传送邮件头 received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo); blast(&hops); hops = (hops = MAXHOPS); if (hops) qmail_fail(&qqt); //向qmail-queue传送邮件头信息. //假如hong@hg.org 向 lyx@hg.org发送邮件,那么向qmail-queue传送的字符串将是 // Fhong@hg.orgTlyx@hg.org qmail_from(&qqt,mailfrom.s); qmail_put(&qqt,rcptto.s,rcptto.len); qqx = qmail_close(&qqt); if (!*qqx) { acceptmessage(qp); return; }//假如接收成功 if (hops) { out("554 too many hops, this message is looping (#5.4.6)"); return; } if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)"); return; } if (*qqx == 'D') out("554 "); else out("451 "); out(qqx + 1); out(""); } //smtp命令处理函数表 struct commands smtpcommands[] = { { "rcpt", smtp_rcpt, 0 } , { "mail", smtp_mail, 0 } , { "data", smtp_data, flush } //建立子进程执行qamil-queue,并向其传送邮件. , { "quit", smtp_quit, flush } , { "helo", smtp_helo, flush } , { "ehlo", smtp_ehlo, flush } , { "rset", smtp_rset, 0 } , { "help", smtp_help, flush } , { "noop", err_noop, flush } //实际上未实现的命令, { "vrfy", err_vrfy, flush } //实际上未实现的命令, { 0, err_unimpl, flush } //命令错误 } ; /* 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"); //从标准输入(网络连接)读入smtp命令. if (commands(&ssin,&smtpcommands) == 0) die_read(); die_nomem(); } ==完== qmail-queue源代码分析 Programmer:夜未眠 Comefrom:ChongQing Gearbox co.,ltd 程序主要完成的功能是: 1.生成自已的邮件首部,也就是你在邮件头中见到的类似下面的东西 Recevied (qmail 855 invoked by uid 0); 2 May 2003 12:18:09 -0000 2.建立3个文件 queue/mess// //邮件正文 queue/intd/ 用户id,进程id,mailfrom,rcptto queue/todo/ 是intd目录下文件的复本. 3.写命名管道lock/trigger通知新邮件 代码: #define DEATH 86400 /* 24 hours; _must_ be below q-s's OSSIFIED (36 hours) */ #define ADDR 1003 char inbuf[2048]; struct substdio ssin; char outbuf[256]; struct substdio ssout; datetime_sec starttime; struct datetime dt; unsigned long mypid; unsigned long uid; char *pidfn; struct stat pidst; unsigned long messnum; char *messfn; char *todofn; char *intdfn; int messfd; int intdfd; int flagmademess = 0; int flagmadeintd = 0; //错误清理 void cleanup() { if (flagmadeintd) { seek_trunc(intdfd,0); if (unlink(intdfn) == -1) return; } if (flagmademess) { seek_trunc(messfd,0); if (unlink(messfn) == -1) return; } } void die(e) int e; { _exit(e); } void die_write() { cleanup(); die(53); } void die_read() { cleanup(); die(54); } void sigalrm() { /* thou shalt not clean up here */ die(52); } void sigbug() { die(81); } unsigned int receivedlen; char *received; static unsigned int receivedfmt(s) char *s; { unsigned int i; unsigned int len; len = 0; /*生成 /* "Received: (qmail-queue invoked by alias); 26 Sep 1995 04:46:54 -0000" */ [日 月 年 时 分 秒] 的形式. */ i = fmt_str(s,"Received: (qmail "); len += i; if (s) s += i; i = fmt_ulong(s,mypid); len += i; if (s) s += i; i = fmt_str(s," invoked "); len += i; if (s) s += i; if (uid == auto_uida) { i = fmt_str(s,"by alias"); len += i; if (s) s += i; } else if (uid == auto_uidd) { i = fmt_str(s,"from network"); len += i; if (s) s += i; } else if (uid == auto_uids) { i = fmt_str(s,"for bounce"); len += i; if (s) s += i; } else { i = fmt_str(s,"by uid "); len += i; if (s) s += i; i = fmt_ulong(s,uid); len += i; if (s) s += i; } i = fmt_str(s,"); "); len += i; if (s) s += i; i = date822fmt(s,&dt); len += i; if (s) s += i; return len; } void received_setup() { receivedlen = receivedfmt((char *) 0); received = alloc(receivedlen + 1); if (!received) die(51); receivedfmt(received); } unsigned int pidfmt(s,seq) char *s; unsigned long seq; { unsigned int i; unsigned int len; //生成类型pid/3434.34242424.1的字符串到s中 //这个字符串实际上就是/var/qmail/queue/pid目录下一个文件名。指示当前进程的pid. len = 0; i = fmt_str(s,"pid/"); len += i; if (s) s += i; i = fmt_ulong(s,mypid); len += i; if (s) s += i; i = fmt_str(s,"."); len += i; if (s) s += i; i = fmt_ulong(s,starttime); len += i; if (s) s += i; i = fmt_str(s,"."); len += i; if (s) s += i; i = fmt_ulong(s,seq); len += i; if (s) s += i; ++len; if (s) *s++ = 0; return len; } char *fnnum(dirslash,flagsplit) char *dirslash; int flagsplit; { char *s; s = alloc(fmtqfn((char *) 0,dirslash,messnum,flagsplit)); if (!s) die(51); fmtqfn(s,dirslash,messnum,flagsplit); return s; } void pidopen() //建立类似/var/run/inet.pid之类的进程id文件. { unsigned int len; unsigned long seq; seq = 1; len = pidfmt((char *) 0,seq); pidfn = alloc(len); if (!pidfn) die(51); for (seq = 1;seq 10;++seq) { if (pidfmt((char *) 0,seq) len) die(81); /* paranoia */ pidfmt(pidfn,seq); messfd = open_excl(pidfn); if (messfd != -1) return; } die(63); } char tmp[FMT_ULONG]; void main() { unsigned int len; char ch; sig_blocknone(); umask(033); if (chdir(auto_qmail) == -1) die(61); if (chdir("queue") == -1) die(62);//改变工作目录到/var/qmail/queue mypid = getpid(); uid = getuid(); starttime = now(); datetime_tai(&dt,starttime);//将起始时间转换为可读年月日时分秒的形式 //生成自已的邮件头存入缓存reseived中 //例如: received="Received: (qmail 3434 invoked by 34434); Apr 27 2003 14:55:34" received_setup(); sig_pipeignore(); sig_miscignore(); sig_alarmcatch(sigalrm);//捕捉alarm信号,控制超时 sig_bugcatch(sigbug); alarm(DEATH); //超时秒数,缺省值是86400(24小时) 后错误返回52 pidopen();//建立进程id文件 if (fstat(messfd,&pidst) == -1) die(63); messnum = pidst.st_ino; //进程id文件的inode节点号 /*生成将要建立的文件的文件名 几个文件都是根据刚才建立的pid文件的inode节点号命名的.inode不可能被两个文件同时占用,这保证了邮件唯一性。 其中mess目录下的文件放置有一个%23的问题, tips: 因为是%23所以该目录名最大的可能只有22,明白queue/mess目录下目录为什么最大只22了吧 比如说inode节点号为3455,那么3455%23=5,那么将生成/var/qmail/queue/mess/5/3455 这样一个文件来存放邮件。 /var/qmail/queue/todo/3455与/var/qmail/queue/intd/3455是相同的,都是保存用户id,进程id,mailfrom,rcptto的。 */ messfn = fnnum("mess/",1); //解释为message file name todofn = fnnum("todo/",0); //todo file name intdfn = fnnum("intd/",0); //intd file name if (link(pidfn,messfn) == -1) die(64); if (unlink(pidfn) == -1) die(63); //进程id文件使命很快结束,死掉了 //所以你不应该想在queue/pid目录中找到进程id文件。 //另外,qmail-clean也将定期清理queue/pid目录下的pid文件,说定期其实也不是,qmail-clean会在每收到30个清理邮件的请求后清理pid目录一次.这在分析qmail-clean时我们将会看到. flagmademess = 1; //fd1关联到写mess/下新建的文件。 通过管道连接--------qmail-smtp 的 qqt-fde //也就是说qmail-smtpd进程写它的qqt-fde,那就相当于写mess/下新建立的邮件 //注重是关联不是正式写 substdio_fdbuf(&ssout,write,messfd,outbuf,sizeof(outbuf)); //fd0关联到读标准输入到缓存区inbuf 通过管道连接 ---------qmail-smtp 的 qqt-fdm //也就是说读ssin将从qmail-smtpd的qqt-fdm端读 substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); //向mess/下的邮件文件写qmail-queue的头部信息 if (substdio_bput(&ssout,received,receivedlen) == -1) die_write(); //从fd1读smtpd设置的邮件首部 switch(substdio_copy(&ssout,&ssin)) { case -2: die_read(); case -3: die_write(); } if (substdio_flush(&ssout) == -1) die_write(); if (fsync(messfd) == -1) die_write(); intdfd = open_excl(intdfn); if (intdfd == -1) die(65); flagmadeintd = 1; //fd1关联到写intd/下新建立的文件 fd0关联到读inbuff缓冲区 substdio_fdbuf(&ssout,write,intdfd,outbuf,sizeof(outbuf)); substdio_fdbuf(&ssin,read,1,inbuf,sizeof(inbuf)); /* 向intd下新建立的文件写如下格式内容 这些内容来自于qmail-smtpd.c中的data命令的解释函数。 u[uid]p[pid]F[mailfrom]T[rcptto1][rcptto2][rcptton] 例如:lyx@hg.org向hong@hg.org和beggar@hg.org发邮件可能会有如下内容 u6027p34234Flyx@hg.orgThong@hg.orgTbeggar@hg.org */ if (substdio_bput(&ssout,"u",1) == -1) die_write(); if (substdio_bput(&ssout,tmp,fmt_ulong(tmp,uid)) == -1) die_write(); if (substdio_bput(&ssout,"",1) == -1) die_write(); if (substdio_bput(&ssout,"p",1) == -1) die_write(); if (substdio_bput(&ssout,tmp,fmt_ulong(tmp,mypid)) == -1) die_write(); if (substdio_bput(&ssout,"",1) == -1) die_write(); if (substdio_get(&ssin,&ch,1) 1) die_read(); if (ch != 'F') die(91); if (substdio_bput(&ssout,&ch,1) == -1) die_write(); for (len = 0;len ADDR;++len) { if (substdio_get(&ssin,&ch,1) 1) die_read(); if (substdio_put(&ssout,&ch,1) == -1) die_write(); if (!ch) break; } //如有多个邮件接收人时,这些接收人的地址总不长度不能超过1023字节,假如每个邮件地址约为15个字节的话, //大约可能指定65个 if (len = ADDR) die(11); if (substdio_bput(&ssout,QUEUE_EXTRA,QUEUE_EXTRALEN) == -1) die_write(); for (;;) { if (substdio_get(&ssin,&ch,1) 1) die_read(); if (!ch) break; if (ch != 'T') die(91); if (substdio_bput(&ssout,&ch,1) == -1) die_write(); for (len = 0;len ADDR;++len) { if (substdio_get(&ssin,&ch,1) 1) die_read(); if (substdio_bput(&ssout,&ch,1) == -1) die_write(); if (!ch) break; } if (len = ADDR) die(11); } if (substdio_flush(&ssout) == -1) die_write(); if (fsync(intdfd) == -1) die_write(); //复制intdfn到todofn 由此可见这两个是相同的文件 if (link(intdfn,todofn) == -1) die(66); triggerpull(); //向命名管道 /var/qmail/queue/lock/trigger写一个字节(写的是0),通知有新的邮件 die(0); //退出 } ==完== qmail-popup.c分析 Programmer:夜未眠 Come from:ChongQing Gearbox co.,ltd qmail -popup也是由tcpserver或tcp-env之类的程式启动。这些程式是通过管道与qmail-popup通信的。这也是qmail 的美妙之处,总观整个qmail源代码,除少量dns代码外。基本上没有使用网络编程。各个进程间大部分都是通管道通信。把监听,读写网络部分交给 inetd或tcpserver来作。使得qmail代码相当轻易阅读理解。 主要功能: 1.从网络读pop3命令,进行相应处理。 2.调用子进程(vchkpw或checkpassWord,具体是哪一个由你在运行参数中指定,当然,仔细分析完doanddie函数后你也许就能编写自己的checkpw了,呵呵)完成检验密码,启动qmail-pop3d的工作 重要的函数是doanddie. 理解这个函数基本上就能理解qmail pop密码的检验流程。 几个程式间的关系是: 代码: tcpserver----qmail-popup----vchkpw----认证成功---qmail-pop3d ---------- 认证失败-----------+ ========================== 代码: void die() { _exit(1); } int saferead(fd,buf,len) int fd; char *buf; int len; { int r; r = timeoutread(1200,fd,buf,len); if (r = 0) die(); return r; } int safewrite(fd,buf,len) int fd; char *buf; int len; { int r; r = timeoutwrite(1200,fd,buf,len); if (r = 0) die(); return r; } char ssoutbuf[128]; substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf); char ssinbuf[128]; substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof ssinbuf); void puts(s) char *s; { substdio_puts(&ssout,s); } void flush() { substdio_flush(&ssout); } void err(s) char *s; { puts("-ERR "); puts(s); puts(""); flush(); } void die_usage() { err("usage: popup hostname subprogram"); die(); } void die_nomem() { err("out of memory"); die(); } void die_pipe() { err("unable to open pipe"); die(); } void die_write() { err("unable to write pipe"); die(); } void die_fork() { err("unable to fork"); die(); } void die_childcrashed() { err("aack, child crashed"); } void die_badauth() { err("authorization failed"); } void err_syntax() { err("syntax error"); } void err_wantuser() { err("USER first"); } void err_authoriz() { err("authorization first"); } void okay() { puts("+OK "); flush(); } void pop3_quit() { okay(); die(); } //FMT_ULONG 40 /* enough space to hold 2^128 - 1 in decimal, plus来源:http://www.tulaoshi.com/n/20160219/1626578.html
看过《程序员眼中的qmail(qmail源代码分析)》的人还看了以下文章 更多>>