程序員眼中的qmail(qmail源代碼分析)

很多人對qmail smtp的認證機制,環境變量,執行順序不太了解。

仔細看完這一大篇代碼後相信你會明白很多你過去不太明白的問題。

當然你要有一點點c語言基礎。也只要一點點。

Come from: 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!

代碼:

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--+

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

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://例如地址'rcpt test@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傳送的字符串將是// 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) { acceptmessage(qp); return; }//如果接收成功if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; }if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); return; }if (*qqx == 'D') out("554 "); else out("451 ");out(qqx + 1);out("\r\n");}//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\r\n");//從標准輸入(網絡連接)讀入smtp命令.if (commands(%26amp;ssin,%26amp;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 -00002.建立3個文件queue/mess////郵件正文queue/intd/用戶id,進程id,mailfrom,rcpttoqueue/todo/是intd目錄下文件的複本.3.寫命名管道lock/trigger通知新郵件代碼:#define DEATH 86400 /* 24 hours; _must_ be below q-s's OSSIFIED (36 hours) */#define ADDR 1003char 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\n" */[日 月 年 時 分 秒]的形式.*/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,%26amp;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{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/queuemypid = getpid();uid = getuid();starttime = now();datetime_tai(%26amp;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小時) 後錯誤返回52pidopen();//建立進程id文件if (fstat(messfd,%26amp;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 nametodofn = fnnum("todo/",0); //todo file nameintdfn = fnnum("intd/",0); //intd file nameif (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/下新建的文件。 通過管道連接fde//也就是說qmail-smtpd進程寫它的qqt-fde,那就相當于寫mess/下新建立的郵件//注意是關聯不是正式寫substdio_fdbuf(%26amp;ssout,write,messfd,outbuf,sizeof(outbuf));//fd0關聯到讀標准輸入到緩存區inbuf 通過管道連接fdm//也就是說讀ssin將從qmail-smtpd的qqt-fdm端讀substdio_fdbuf(%26amp;ssin,read,0,inbuf,sizeof(inbuf));//向mess/下的郵件文件寫qmail-queue的頭部信息if (substdio_bput(%26amp;ssout,received,receivedlen) == -1) die_write();//從fd1讀smtpd設置的郵件首部switch(substdio_copy(%26amp;ssout,%26amp;ssin)){case -2: die_read();case -3: die_write();}if (substdio_flush(%26amp;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(%26amp;ssout,write,intdfd,outbuf,sizeof(outbuf));substdio_fdbuf(%26amp;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(%26amp;ssout,"u",1) == -1) die_write();if (substdio_bput(%26amp;ssout,tmp,fmt_ulong(tmp,uid)) == -1) die_write();if (substdio_bput(%26amp;ssout,"",1) == -1) die_write();if (substdio_bput(%26amp;ssout,"p",1) == -1) die_write();if (substdio_bput(%26amp;ssout,tmp,fmt_ulong(tmp,mypid)) == -1) die_write();if (substdio_bput(%26amp;ssout,"",1) == -1) die_write();if (substdio_get(%26amp;ssin,%26amp;ch,1)if (ch != 'F') die(91);if (substdio_bput(%26amp;ssout,%26amp;ch,1) == -1) die_write();for (len = 0;len{if (substdio_get(%26amp;ssin,%26amp;ch,1)if (substdio_put(%26amp;ssout,%26amp;ch,1) == -1) die_write();if (!ch) break;}//如有多個郵件接收人時,這些接收人的地址總不長度不能超過1023字節,如果每個郵件地址約爲15個字節的話,//大約可能指定65個if (len= ADDR) die(11);if (substdio_bput(%26amp;ssout,QUEUE_EXTRA,QUEUE_EXTRALEN) == -1) die_write();for (;;){if (substdio_get(%26amp;ssin,%26amp;ch,1)if (!ch) break;if (ch != 'T') die(91);if (substdio_bput(%26amp;ssout,%26amp;ch,1) == -1) die_write();for (len = 0;len{if (substdio_get(%26amp;ssin,%26amp;ch,1)if (substdio_bput(%26amp;ssout,%26amp;ch,1) == -1) die_write();if (!ch) break;}if (len= ADDR) die(11);}if (substdio_flush(%26amp;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.,ltdqmail-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 (rreturn r;}int safewrite(fd,buf,len) int fd; char *buf; int len;{int r;r = timeoutwrite(1200,fd,buf,len);if (rreturn 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(%26amp;ssout,s);}void flush(){substdio_flush(%26amp;ssout);}void err(s) char *s;{puts("-ERR ");puts(s);puts("\r\n");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 \r\n"); flush(); }void pop3_quit() { okay(); die(); }//FMT_ULONG 40 /* enough space to hold 2^128 - 1 in decimal, plus \0 */char unique[FMT_ULONG + FMT_ULONG + 3];char *hostname;stralloc username = {0};int seenuser = 0;char **childargs;substdio ssup;char upbuf[128];void doanddie(user,userlen,pass)char *user;unsigned int userlen; /* including 0 byte */char *pass;{int child;int wstat;int pi[2];if (fd_copy(2,1) == -1) die_pipe();//關閉出錯(fd2),將標准輸出(fd1),定向到標准出錯(fd2)close(3);if (pipe(pi) == -1) die_pipe();if (pi[0] != 3) die_pipe(); //確保向子進程能夠讀到硬編碼的fd 3switch(child = fork()) { //建立子進程執行subprogram給出的程式,一般是一個檢驗用戶名和密碼的程式case -1:die_fork();case 0:close(pi[1]);sig_pipedefault();//子進程執行checkpassword或vchkpw之類的程式,檢驗密碼,如果認證通過execvp(*childargs,childargs);//這些再調用qmail-pop3d_exit(1);}//父進程向子進程的fd3傳送用戶名及密碼,這是一個約定。如果你要寫自已的檢驗密碼的程式,記得//從fd3讀密碼哦。close(pi[0]);substdio_fdbuf(%26amp;ssup,write,pi[1],upbuf,sizeof upbuf);if (substdio_put(%26amp;ssup,user,userlen) == -1) die_write();if (substdio_put(%26amp;ssup,pass,str_len(pass) + 1) == -1) die_write();//父進程向子進程傳送if (substdio_puts(%26amp;ssup,"if (substdio_puts(%26amp;ssup,unique) == -1) die_write();if (substdio_puts(%26amp;ssup,hostname) == -1) die_write();if (substdio_put(%26amp;ssup,"",2) == -1) die_write();if (substdio_flush(%26amp;ssup) == -1) die_write();close(pi[1]);//清除密碼及用戶名緩沖區byte_zero(pass,str_len(pass));byte_zero(upbuf,sizeof upbuf);if (wait_pid(%26amp;wstat,child) == -1) die();//等待子進程結束if (wait_crashed(wstat)) die_childcrashed();if (wait_exitcode(wstat)) die_badauth();//完成一次pop3對話退出die();}//顯示歡迎信息void pop3_greet(){char *s;s = unique;s += fmt_uint(s,getpid());*s++ = '.';s += fmt_ulong(s,(unsigned long) now());*s++ = '@';*s++ = 0;puts("+OKputs(unique);puts(hostname);puts("\r\n");flush();}//設置標志,初始化用戶名變量void pop3_user(arg) char *arg;{if (!*arg) { err_syntax(); return; }okay();seenuser = 1; //user命令已經執行的標志if (!stralloc_copys(%26amp;username,arg)) die_nomem(); //將參數存入usernameif (!stralloc_0(%26amp;username)) die_nomem();}void pop3_pass(arg) char *arg;{if (!seenuser) { err_wantuser(); return; }//如果沒有執行user命令,返回if (!*arg) { err_syntax(); return; }doanddie(username.s,username.len,arg);//調用子進程驗正密碼並等待它完成}void pop3_apop(arg) char *arg;//用戶名及密碼在一個命令中給出的情況,見user,pass{char *space;space = arg + str_chr(arg,' ');if (!*space) { err_syntax(); return; }*space++ = 0;doanddie(arg,space - arg,space);}struct commands pop3commands[] = {//命令及相應的處理函數表{ "user", pop3_user, 0 }, { "pass", pop3_pass, 0 }, { "apop", pop3_apop, 0 }, { "quit", pop3_quit, 0 }, { "noop", okay, 0 }, { 0, err_authoriz, 0 }} ;void main(argc,argv)int argc;char **argv;{sig_alarmcatch(die);//捕獲sigalrm信號sig_pipeignore();//忽略pipe信號hostname = argv[1]; //hostname 指向 程式的第一個參數if (!hostname) die_usage();childargs = argv + 2;if (!*childargs) die_usage();pop3_greet();//顯示歡迎信息後進入命令循環,等待用戶命令commands(%26amp;ssin,pop3commands);die();}qmail-start.c 分析Programmer:夜未眠Comefrom:ChongQing Gearbox co.,ltdqmail-start 是很簡單的一個程式,他完成qmail-send,qmail-clean,qmail-lspawn,qmail-rspawn,splogger的啓動,並通過管道將他們聯系在一起,當然不是網狀連接.具體如下代碼:=====================================qmail-lspawn fd0qmail-lspawn fd1 ------ qmail-send fd2qmail-rspawn fd0qmail-rspawn fd1 ------ qmail-send fd4qmail-clean fd0qmail-clean fd1 ------ qmail-send fd6=====================================理解他們之間的關系(注意方向)對于理解qmail-send源代碼非常重要。仔細再看一次。因爲其比較簡單,所以這裏就不對他的源代碼作過細的分析:代碼:char *(qsargs[]) = { "qmail-send", 0 };char *(qcargs[]) = { "qmail-clean", 0 };char *(qlargs[]) = { "qmail-lspawn", "./Mailbox", 0 };char *(qrargs[]) = { "qmail-rspawn", 0 };void die() { _exit(111); }int pi0[2]; //splogger qmailint pi1[2]; //qmail-lspawn fd0int pi2[2]; //qmail-lspawn fd1 ------ qmail-send fd2int pi3[2]; //qmail-rspawn fd0int pi4[2]; //qmail-rspawn fd1 ------ qmail-send fd4int pi5[2]; //qmail-clean fd0int pi6[2]; //qmail-clean fd1 ------ qmail-send fd6void close23456() { close(2); close(3); close(4); close(5); close(6); }//****************////因爲沒有關閉pi0.//所以所有的子進程都可以通過寫pi0來記錄maillog.void closepipes() {close(pi1[0]); close(pi1[1]); close(pi2[0]); close(pi2[1]);close(pi3[0]); close(pi3[1]); close(pi4[0]); close(pi4[1]);close(pi5[0]); close(pi5[1]); close(pi6[0]); close(pi6[1]);}void main(argc,argv)int argc;char **argv;{if (chdir("/") == -1) die();umask(077);if (prot_gid(auto_gidq) == -1) die();if (fd_copy(2,0) == -1) die();if (fd_copy(3,0) == -1) die();if (fd_copy(4,0) == -1) die();if (fd_copy(5,0) == -1) die();if (fd_copy(6,0) == -1) die();if (argv[1]) {qlargs[1] = argv[1];++argv;}if (argv[1]) {if (pipe(pi0) == -1) die();switch(fork()) {case -1:die();case 0:if (prot_gid(auto_gidn) == -1) die();if (prot_uid(auto_uidl) == -1) die();close(pi0[1]);if (fd_move(0,pi0[0]) == -1) die();//重定向pi0[0]到splogger的fd0close23456();execvp(argv[1],argv + 1);//啓動sploggerdie();}close(pi0[0]);if (fd_move(1,pi0[1]) == -1) die();}if (pipe(pi1) == -1) die();if (pipe(pi2) == -1) die();if (pipe(pi3) == -1) die();if (pipe(pi4) == -1) die();if (pipe(pi5) == -1) die();if (pipe(pi6) == -1) die();switch(fork()) {//啓動qmail-lspawncase -1: die();case 0:if (fd_copy(0,pi1[0]) == -1) die();if (fd_copy(1,pi2[1]) == -1) die();close23456();closepipes();execvp(*qlargs,qlargs);die();}switch(fork()) {//啓動qmail-rspawncase -1: die();case 0:if (prot_uid(auto_uidr) == -1) die();if (fd_copy(0,pi3[0]) == -1) die();if (fd_copy(1,pi4[1]) == -1) die();close23456();closepipes();execvp(*qrargs,qrargs);die();}switch(fork()) {//啓動qmail-cleancase -1: die();case 0:if (prot_uid(auto_uidq) == -1) die();if (fd_copy(0,pi5[0]) == -1) die();if (fd_copy(1,pi6[1]) == -1) die();close23456();closepipes();execvp(*qcargs,qcargs);die();}if (prot_uid(auto_uids) == -1) die();if (fd_copy(0,1) == -1) die(); //重定向管道,把qmail-send 與上面各進程聯系起來。if (fd_copy(1,pi1[1]) == -1) die();if (fd_copy(2,pi2[0]) == -1) die();if (fd_copy(3,pi3[1]) == -1) die();if (fd_copy(4,pi4[0]) == -1) die();if (fd_copy(5,pi5[1]) == -1) die();if (fd_copy(6,pi6[0]) == -1) die();closepipes();execvp(*qsargs,qsargs);//最後啓動qmail-senddie();}==完==qmail-pop3d源代碼分析Programmer:夜未眠Comefrom: ChongQing Gearbox co.,ltd關鍵數據結構隊列: prioq這個數據結構在很多qmail很多程式中都有用到,最好記下來代碼:struct prioq_elt {datetime_sec dt;//時間戳,優先級unsigned long id;//郵件唯一id,你可以把它同qmail-queue分析中介紹中pid文件inode聯系起來} ;prioq在prioq.h中prioq是這樣定義的GEN_ALLOC_typedef(prioq,struct prioq_elt,p,len,a)展開後實際上定義爲typedef struct prioq{struct prioq_elt *p; // 指針unsigned int len; //隊列的長度unsigned int a;}prioq;消息塊: message 我把它叫作消息塊是因爲他並不包含消息內容,也許這樣稱呼它並不確切代碼:struct message {int flagdeleted; //刪除標記,在qmail-pop3d程式退出時進行實際刪除動作unsigned long size; //消息文件大小char *fn; //消息文件名} *m;主要功能:qmail-pop3d是則vchkpw或checkpassword之類的程式啓動的。這些程式(vchkpw)會更改環境變量USER,HOME,SHELL等等,並在啓動qmail-pop3d前將工作目錄改變到$HOME下.qmail-pop3d在啓動時首先檢查./Maildir/tmp(./Maildir是在argv中指定的)下最後訪問時間超過36小時的文件,如果存在就將其刪除。也正是由于qmail-pop3d在啓動時就有chdir的動作,所以qmail-pop3d不支持mailbox形式的pop.掃描Maildir/cur及Maildir/new目錄構造一個消息塊數組 m(首先是構造一個臨時隊列pq,然後根據這個隊列來構造消息塊數組),輸出+OK,進入命令循環,等待用戶輸入pop命令進行相應的處理.具體見代碼分析.代碼:void die() { _exit(0); }//超時讀,超時時間爲20分鍾,正常返回讀到的字節數,否則程式失敗die()int saferead(fd,buf,len) int fd; char *buf; int len;{int r;r = timeoutread(1200,fd,buf,len);if (rreturn r;}//超時寫,超時時間爲20分鍾,正常返回寫的字節數,否則程式失敗die()int safewrite(fd,buf,len) int fd; char *buf; int len;{int r;r = timeoutwrite(1200,fd,buf,len);if (rreturn r;}/*定義ssout爲向fd1寫,超時時間爲20分鍾定義ssin爲從fd0讀,超時時間爲20分鍾由于tcpserver或inetd已經重定向了fd1,fd0到網絡,所以這就等同于向網絡讀寫*/char ssoutbuf[1024];substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf);char ssinbuf[128];substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof ssinbuf);void put(buf,len) char *buf; int len;{substdio_put(%26amp;ssout,buf,len);//將buf緩存中的內容向網絡寫}void puts(s) char *s;{substdio_puts(%26amp;ssout,s);//將s的內容向網絡寫,這個函數實際上是調用的substdio_put}void flush() //確保輸出緩存中已經沒有內容。{substdio_flush(%26amp;ssout);}void err(s) char *s;{puts("-ERR ");puts(s);puts("\r\n");flush();}//錯誤處理函數void die_nomem() { err("out of memory"); die(); }void die_nomaildir() { err("this user has no $HOME/Maildir"); die(); }void die_scan() { err("unable to scan $HOME/Maildir"); die(); }void err_syntax() { err("syntax error"); }void err_unimpl() { err("unimplemented"); }void err_deleted() { err("already deleted"); }void err_nozero() { err("messages are counted from 1"); }void err_toobig() { err("not that many messages"); }void err_nosuch() { err("unable to open that message"); }void err_nounlink() { err("unable to unlink all deleted messages"); }void okay() { puts("+OK \r\n"); flush(); }void printfn(fn) char *fn;{fn += 4;put(fn,str_chr(fn,':'));}char strnum[FMT_ULONG];stralloc line = {0};void blast(ssfrom,limit)//從ssfrom讀數據輸出到fd1,一次一行(用全局緩存line)substdio *ssfrom;unsigned long limit;//除開消息頭部信息,最多讀limit行,limit爲0將全部讀完{int match;int inheaders = 1;for (;;) {if (getln(ssfrom,%26amp;line,%26amp;match,'\n') != 0) die();if (!match %26amp;%26amp; !line.len) break;if (match) --line.len; /* no way to pass this info over POP */if (limit) if (!inheaders) if (!--limit) break;if (!line.len)inheaders = 0;elseif (line.s[0] == '.')put(".",1);put(line.s,line.len);put("\r\n",2);if (!match) break;}put("\r\n.\r\n",5);flush();}stralloc filenames = {0};prioq pq = {0};struct message {int flagdeleted; //刪除標記,在程式退出時進行實際刪除動作unsigned long size; //文件大小char *fn; //文件名} *m;int numm;//全局變量記錄隊列長度int last = 0;void getlist(){struct prioq_elt pe;struct stat st;int i;maildir_clean(%26amp;line);//清除Maildir/tmp/目錄下最後訪問時間超過 36小時的文件if (maildir_scan(%26amp;pq,%26amp;filenames,1,1) == -1) die_scan();numm = pq.p ? pq.len : 0; //記錄下隊列長度//通過隊列pq構造消息塊數組,構建結束後隊列pq刪除m = (struct message *) alloc(numm * sizeof(struct message));//分配消息塊if (!m) die_nomem();for (i = 0;iif (!prioq_min(%26amp;pq,%26amp;pe)) { numm = i; break; }prioq_delmin(%26amp;pq);m[i].fn = filenames.s + pe.id;m[i].flagdeleted = 0;if (stat(m[i].fn,%26amp;st) == -1)m[i].size = 0;elsem[i].size = st.st_size;}}void pop3_stat() //打印類似 +OK{ //如 +OK 3 3555表示總共有3條消息,占用空間3555(通過stat取得的)int i;unsigned long total;total = 0;for (i = 0;iputs("+OK ");put(strnum,fmt_uint(strnum,numm));puts(" ");put(strnum,fmt_ulong(strnum,total));puts("\r\n");flush();}void pop3_rset()//重置pop對話,清除所有刪除標記{int i;for (i = 0;ilast = 0;okay();}void pop3_last()//顯示最後一個消息塊{puts("+OK ");put(strnum,fmt_uint(strnum,last));puts("\r\n");flush();}void pop3_quit()//結束一次pop對話,刪除所有刪除標記設置的消息,將new下的消息移到cur下{int i;for (i = 0;iif (m[i].flagdeleted) {if (unlink(m[i].fn) == -1) err_nounlink();}elseif (str_start(m[i].fn,"new/")) {if (!stralloc_copys(%26amp;line,"cur/")) die_nomem();if (!stralloc_cats(%26amp;line,m[i].fn + 4)) die_nomem();if (!stralloc_cats(%26amp;line,":2,")) die_nomem();if (!stralloc_0(%26amp;line)) die_nomem();rename(m[i].fn,line.s); /* if it fails, bummer */}okay();die();}//檢查消息塊是否存在。或消息塊的刪除標記是否已經設置了//成功返回消息塊的位置int型//失敗返回-1int msgno(arg) char *arg;{unsigned long u;if (!scan_ulong(arg,%26amp;u)) { err_syntax(); return -1; }if (!u) { err_nozero(); return -1; }--u;if (u= numm) { err_toobig(); return -1; }if (m[u].flagdeleted) { err_deleted(); return -1; }return u;}void pop3_dele(arg) char *arg;//將arg指定消息塊設置刪除標記,實際刪除動作將在pop3退出時進行{int i;i = msgno(arg);if (i == -1) return;m[i].flagdeleted = 1;if (i + 1last) last = i + 1;okay();}void list(i,flaguidl)int i;int flaguidl;{//顯示消息塊的內容,如果flaguidl設置,輸出消息文件名,否則消息大小put(strnum,fmt_uint(strnum,i + 1));puts(" ");if (flaguidl) printfn(m[i].fn);else put(strnum,fmt_ulong(strnum,m[i].size));puts("\r\n");}//如果指定了參數arg那麽列出arg指定的消息塊的內容,否則列出全部消息void dolisting(arg,flaguidl) char *arg; int flaguidl;{unsigned int i;if (*arg) {i = msgno(arg);if (i == -1) return;puts("+OK ");list(i,flaguidl);}else {okay();for (i = 0;iif (!m[i].flagdeleted)list(i,flaguidl);puts(".\r\n");}flush();}void pop3_uidl(arg) char *arg; { dolisting(arg,1); }void pop3_list(arg) char *arg; { dolisting(arg,0); }substdio ssmsg; char ssmsgbuf[1024];void pop3_top(arg) char *arg;//顯示指定消息的內容{int i;unsigned long limit;int fd;i = msgno(arg);//郵件號if (i == -1) return;arg += scan_ulong(arg,%26amp;limit);//顯示幾行,如果未指定那麽limit爲0(balst函數打印全部內容)while (*arg == ' ') ++arg;if (scan_ulong(arg,%26amp;limit)) ++limit; else limit = 0;fd = open_read(m[i].fn);if (fd == -1) { err_nosuch(); return; }okay();//關系ssmsg爲從指定的消息文件中讀substdio_fdbuf(%26amp;ssmsg,read,fd,ssmsgbuf,sizeof(ssmsgbuf));//從ssmsg中讀到fd1,如果limit大于0將只讀取除消息頭外的limit行,如果等于0讀全部郵件blast(%26amp;ssmsg,limit);close(fd);}struct commands pop3commands[] = { //pop3命令及處理函數表{ "quit", pop3_quit, 0 }, { "stat", pop3_stat, 0 }, { "list", pop3_list, 0 }//顯示消息大小, { "uidl", pop3_uidl, 0 }//顯示消息文件名, { "dele", pop3_dele, 0 }, { "retr", pop3_top, 0 }//取一條消息的內容,與top實現是一樣的, { "rset", pop3_rset, 0 }//重置pop對話,清除所有刪除標記, { "last", pop3_last, 0 }, { "top", pop3_top, 0 }, { "noop", okay, 0 }, { 0, err_unimpl, 0 }} ;/*qmail-pop3d由vchkpw或checkpassword之類的程式起動,只有認證通過後才能執行本程式提供各種pop3命令*/void main(argc,argv)int argc;char **argv;{sig_alarmcatch(die);sig_pipeignore();if (!argv[1]) die_nomaildir();//由于vchkpw或checkpassword之類的程式在啓動pop3之前已經將工作目錄改變到HOME下了.//所以這裏直接進入arg指定的Maildir目錄.也是由于這個改變目錄原因。qamil-pop3d不支持Mailbox.if (chdir(argv[1]) == -1) die_nomaildir();getlist(); //這裏構造了我們前面提到了消息塊數組*mokay();//進入命令循環commands(%26amp;ssin,pop3commands);die();}==自此qmail的pop3部分分析基本結束==小結Maildir/cur 只要用戶進行了一次連接,qmail-pop3d就會將new下所有郵件移動這個目錄下來(quit命令解釋程式中有體現.)Maildir/new 用戶還沒看過新郵件可見qmail的pop3部分只與Maildir有聯系,與smtp基本無關。也許有人會問怎麽pop3代碼都完了,怎麽沒看見有使用 Maildir/tmp目錄的地方呢?(只見刪除)其實這個tmp目錄是qmail-local用來保證可靠的轉發所用的臨時文件目錄。如果你想知道具體怎麽可靠法可以看qmail-local的源代碼分析或者man maildir 的HOW A MESSAGE IS DELIVERED節.

 
程序員眼中的qmail(qmail源代碼分析)
  很多人對qmail smtp的認證機制,環境變量,執行順序不太了解。仔細看完這一大篇代碼後相信你會明白很多你過去不太明白的問題。當然你要有一點點c語言基礎。也只要一點點。Come from: ChongQing Gearbox co.,ltd這...查看完整版>>程序員眼中的qmail(qmail源代碼分析)
 
程序員眼中的qmail(qmail源代碼分析)
  很多人對qmail smtp的認證機制,環境變量,執行順序不太了解。  仔細看完這一大篇代碼後相信你會明白很多你過去不太明白的問題。  當然你要有一點點c語言基礎。也只要一點點。  Come from: ChongQing Gear...查看完整版>>程序員眼中的qmail(qmail源代碼分析)
 
程序員眼中的qmail(qmail源代碼分析)
很多人對qmail smtp的認證機制,環境變量,執行順序不太了解。 仔細看完這一大篇代碼後相信你會明白很多你過去不太明白的問題。 當然你要有一點點c語言基礎。也只要一點點。 Programmer:夜未眠 Date:Apr 28,2003 Com...查看完整版>>程序員眼中的qmail(qmail源代碼分析)
 
qmail源代碼分析之qmail-pop3d
PRogrammer:夜未眠 Comefrom: ChongQing Gearbox co.,ltd 關鍵數據結構 隊列: prioq 這個數據結構在很多qmail很多程式中都有用到,最好記下來 struct prioq_elt { datetime_sec dt;//時間戳,優先級 unsigned long i...查看完整版>>qmail源代碼分析之qmail-pop3d
 
qmail源代碼分析之qmail-smtpd.c
多人對qmail smtp的認證機制,環境變量,執行順序不太了解。 仔細看完這一大篇代碼後相信你會明白很多你過去不太明白的問題。 當然你要有一點點c語言基礎。也只要一點點。 PRogrammer:夜未眠 Date:Apr 28,2003 Comef...查看完整版>>qmail源代碼分析之qmail-smtpd.c