很多人對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
|
|
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"
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
//如果 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.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.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/
queue/todo/
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]
例如:lyx@hg.org向hong@hg.org和beggar@hg.org發郵件可能會有如下內容
u6027
*/
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(qmail源代碼分析)已經有721次圍觀