| 導購 | 订阅 | 在线投稿
分享
 
 
 

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

來源:互聯網  2008-06-01 02:08:34  評論

很多人對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)\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 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(&ssout,code);

substdio_put(&ssout,greeting.s,greeting.len);

}

void smtp_help()

{

out("214 qmail home page:

void>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(&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("\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(&rcptto,"")) die_nomem();//分配rcptto緩沖區

if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();//複制mail命令中指定的地址到mailfrom

if (!stralloc_0(&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; }//分離郵件地址參數存入全局緩存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\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 (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 \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(&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 != "\r\n"[pos]) flagmaybey = 0;

if (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(&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 郵件最大長度,假如沒有指定那麽它的值將是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\r\n");

//向新建立的進程傳送郵件頭

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)\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(&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\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,&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("\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 3

switch(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(&ssup,write,pi[1],upbuf,sizeof upbuf);

if (substdio_put(&ssup,user,userlen) == -1) die_write();

if (substdio_put(&ssup,pass,str_len(pass) + 1) == -1) die_write();

//父進程向子進程傳送<進程ID.當前時間@主機名>

if (substdio_puts(&ssup,"<") == -1) die_write();

if (substdio_puts(&ssup,unique) == -1) die_write();

if (substdio_puts(&ssup,hostname) == -1) die_write();

if (substdio_put(&ssup,">",2) == -1) die_write();

if (substdio_flush(&ssup) == -1) die_write();

close(pi[1]);

//清除密碼及用戶名緩沖區

byte_zero(pass,str_len(pass));

byte_zero(upbuf,sizeof upbuf);

if (wait_pid(&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("+OK <");

puts(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(&username,arg)) die_nomem(); //將參數存入username

if (!stralloc_0(&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(&ssin,pop3commands);

die();

}

qmail-start.c 分析

Programmer:夜未眠

Comefrom:ChongQing Gearbox co.,ltd

qmail-start 是很簡單的一個程式,他完成qmail-send,qmail-clean,qmail-lspawn,qmail-rspawn,splogger的啓動,並通過管道將他們聯系在一起,當然不是網狀連接.具體如下

代碼:

=====================================

qmail-lspawn fd0 <-------- qmail-send fd1

qmail-lspawn fd1 --------> qmail-send fd2

qmail-rspawn fd0 <-------- qmail-send fd3

qmail-rspawn fd1 --------> qmail-send fd4

qmail-clean fd0 <-------- qmail-send fd5

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

int pi1[2]; //qmail-lspawn fd0 <-------- qmail-send fd1

int pi2[2]; //qmail-lspawn fd1 --------> qmail-send fd2

int pi3[2]; //qmail-rspawn fd0 <-------- qmail-send fd3

int pi4[2]; //qmail-rspawn fd1 --------> qmail-send fd4

int pi5[2]; //qmail-clean fd0 <-------- qmail-send fd5

int pi6[2]; //qmail-clean fd1 --------> qmail-send fd6

void 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的fd0

close23456();

execvp(argv[1],argv + 1);//啓動splogger

die();

}

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

case -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-rspawn

case -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-clean

case -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-send

die();

}

==完==

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 (r <= 0) die();

return r;

}

//超時寫,超時時間爲20分鍾,正常返回寫的字節數,否則程式失敗die()

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;

}

/*定義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(&ssout,buf,len);//將buf緩存中的內容向網絡寫

}

void puts(s) char *s;

{

substdio_puts(&ssout,s);//將s的內容向網絡寫,這個函數實際上是調用的substdio_put

}

void flush() //確保輸出緩存中已經沒有內容。

{

substdio_flush(&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,&line,&match,'\n') != 0) die();

if (!match && !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;

else

if (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 2006830231942.htms = {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(&line);//清除Maildir/tmp/目錄下最後訪問時間超過 36小時的文件

if (maildir_scan(&pq,&2006830231942.htms,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;i < numm;++i) {

if (!prioq_min(&pq,&pe)) { numm = i; break; }

prioq_delmin(&pq);

m[i].fn = 2006830231942.htms.s + pe.id;

m[i].flagdeleted = 0;

if (stat(m[i].fn,&st) == -1)

m[i].size = 0;

else

m[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;i < numm;++i) if (!m[i].flagdeleted) total += m[i].size;

puts("+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;i < numm;++i) m[i].flagdeleted = 0;

last = 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;i < numm;++i)

if (m[i].flagdeleted) {

if (unlink(m[i].fn) == -1) err_nounlink();

}

else

if (str_start(m[i].fn,"new/")) {

if (!stralloc_copys(&line,"cur/")) die_nomem();

if (!stralloc_cats(&line,m[i].fn + 4)) die_nomem();

if (!stralloc_cats(&line,":2,")) die_nomem();

if (!stralloc_0(&line)) die_nomem();

rename(m[i].fn,line.s); /* if it fails, bummer */

}

okay();

die();

}

//檢查消息塊是否存在。或消息塊的刪除標記是否已經設置了

//成功返回消息塊的位置int型

//失敗返回-1

int msgno(arg) char *arg;

{

unsigned long u;

if (!scan_ulong(arg,&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 + 1 > last) 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;i < numm;++i)

if (!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,&limit);//顯示幾行,假如未指定那麽limit爲0(balst函數打印全部內容)

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

if (scan_ulong(arg,&limit)) ++limit; else limit = 0;

fd = open_read(m[i].fn);

if (fd == -1) { err_nosuch(); return; }

okay();

//關系ssmsg爲從指定的消息文件中讀

substdio_fdbuf(&ssmsg,read,fd,ssmsgbuf,sizeof(ssmsgbuf));

//從ssmsg中讀到fd1,假如limit大于0將只讀取除消息頭外的limit行,假如等于0讀全部郵件

blast(&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(); //這裏構造了我們前面提到了消息塊數組*m

okay();

//進入命令循環

commands(&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節.

原文鏈接:http://www.5dmail.net/html/2006-8-30/2006830231942.htm

  很多人對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)\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 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(&ssout,code);   substdio_put(&ssout,greeting.s,greeting.len);   }   void smtp_help()   {   out("214 qmail home page:   void>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(&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("\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(&rcptto,"")) die_nomem();//分配rcptto緩沖區   if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();//複制mail命令中指定的地址到mailfrom   if (!stralloc_0(&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; }//分離郵件地址參數存入全局緩存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\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 (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 \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(&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 != "\r\n"[pos]) flagmaybey = 0;   if (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(&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 郵件最大長度,假如沒有指定那麽它的值將是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\r\n");   //向新建立的進程傳送郵件頭   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)\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(&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\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,&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("\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 3   switch(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(&ssup,write,pi[1],upbuf,sizeof upbuf);   if (substdio_put(&ssup,user,userlen) == -1) die_write();   if (substdio_put(&ssup,pass,str_len(pass) + 1) == -1) die_write();   //父進程向子進程傳送<進程ID.當前時間@主機名>   if (substdio_puts(&ssup,"<") == -1) die_write();   if (substdio_puts(&ssup,unique) == -1) die_write();   if (substdio_puts(&ssup,hostname) == -1) die_write();   if (substdio_put(&ssup,">",2) == -1) die_write();   if (substdio_flush(&ssup) == -1) die_write();   close(pi[1]);   //清除密碼及用戶名緩沖區   byte_zero(pass,str_len(pass));   byte_zero(upbuf,sizeof upbuf);   if (wait_pid(&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("+OK <");   puts(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(&username,arg)) die_nomem(); //將參數存入username   if (!stralloc_0(&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(&ssin,pop3commands);   die();   }   qmail-start.c 分析   Programmer:夜未眠   Comefrom:ChongQing Gearbox co.,ltd   qmail-start 是很簡單的一個程式,他完成qmail-send,qmail-clean,qmail-lspawn,qmail-rspawn,splogger的啓動,並通過管道將他們聯系在一起,當然不是網狀連接.具體如下   代碼:   =====================================   qmail-lspawn fd0 <-------- qmail-send fd1   qmail-lspawn fd1 --------> qmail-send fd2   qmail-rspawn fd0 <-------- qmail-send fd3   qmail-rspawn fd1 --------> qmail-send fd4   qmail-clean fd0 <-------- qmail-send fd5   qmail-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 qmail   int pi1[2]; //qmail-lspawn fd0 <-------- qmail-send fd1   int pi2[2]; //qmail-lspawn fd1 --------> qmail-send fd2   int pi3[2]; //qmail-rspawn fd0 <-------- qmail-send fd3   int pi4[2]; //qmail-rspawn fd1 --------> qmail-send fd4   int pi5[2]; //qmail-clean fd0 <-------- qmail-send fd5   int pi6[2]; //qmail-clean fd1 --------> qmail-send fd6   void 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的fd0   close23456();   execvp(argv[1],argv + 1);//啓動splogger   die();   }   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-lspawn   case -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-rspawn   case -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-clean   case -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-send   die();   }   ==完==   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 (r <= 0) die();   return r;   }   //超時寫,超時時間爲20分鍾,正常返回寫的字節數,否則程式失敗die()   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;   }   /*定義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(&ssout,buf,len);//將buf緩存中的內容向網絡寫   }   void puts(s) char *s;   {   substdio_puts(&ssout,s);//將s的內容向網絡寫,這個函數實際上是調用的substdio_put   }   void flush() //確保輸出緩存中已經沒有內容。   {   substdio_flush(&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,&line,&match,'\n') != 0) die();   if (!match && !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;   else   if (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 2006830231942.htms = {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(&line);//清除Maildir/tmp/目錄下最後訪問時間超過 36小時的文件   if (maildir_scan(&pq,&2006830231942.htms,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;i < numm;++i) {   if (!prioq_min(&pq,&pe)) { numm = i; break; }   prioq_delmin(&pq);   m[i].fn = 2006830231942.htms.s + pe.id;   m[i].flagdeleted = 0;   if (stat(m[i].fn,&st) == -1)   m[i].size = 0;   else   m[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;i < numm;++i) if (!m[i].flagdeleted) total += m[i].size;   puts("+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;i < numm;++i) m[i].flagdeleted = 0;   last = 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;i < numm;++i)   if (m[i].flagdeleted) {   if (unlink(m[i].fn) == -1) err_nounlink();   }   else   if (str_start(m[i].fn,"new/")) {   if (!stralloc_copys(&line,"cur/")) die_nomem();   if (!stralloc_cats(&line,m[i].fn + 4)) die_nomem();   if (!stralloc_cats(&line,":2,")) die_nomem();   if (!stralloc_0(&line)) die_nomem();   rename(m[i].fn,line.s); /* if it fails, bummer */   }   okay();   die();   }   //檢查消息塊是否存在。或消息塊的刪除標記是否已經設置了   //成功返回消息塊的位置int型   //失敗返回-1   int msgno(arg) char *arg;   {   unsigned long u;   if (!scan_ulong(arg,&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 + 1 > last) 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;i < numm;++i)   if (!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,&limit);//顯示幾行,假如未指定那麽limit爲0(balst函數打印全部內容)   while (*arg == ' ') ++arg;   if (scan_ulong(arg,&limit)) ++limit; else limit = 0;   fd = open_read(m[i].fn);   if (fd == -1) { err_nosuch(); return; }   okay();   //關系ssmsg爲從指定的消息文件中讀   substdio_fdbuf(&ssmsg,read,fd,ssmsgbuf,sizeof(ssmsgbuf));   //從ssmsg中讀到fd1,假如limit大于0將只讀取除消息頭外的limit行,假如等于0讀全部郵件   blast(&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(); //這裏構造了我們前面提到了消息塊數組*m   okay();   //進入命令循環   commands(&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節. 原文鏈接:http://www.5dmail.net/html/2006-8-30/2006830231942.htm
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有