分类:
2009-03-18 14:04:29
Mail queue 邮件队列默认路径在/var/qmail/queue目录下。
在这个目录下,创建以下子目录分别存放邮件的不同部分。每个邮件消息做为一个单独的文件存储,用它的inode号做为文件名。Inode是在pid目录根据 [pid+createtime+seq]创建的消息文件,成功情况下获取的inode号。除pid目录,其他子目录都是根据inode号进行命名。
目录 |
描述 |
操作模块 |
Pid |
存放新接收的邮件消息 |
Qmail-queue |
Mess |
包含要投递的邮件消息 |
Qmail-queue |
Intd |
包含由qmail-queue重新组织的构造的消息头 |
Qmail-queue |
Todo |
包含完整的消息头,包括发送方和接收方 |
Qmail-queue Qmail-send |
info |
包含邮件接收方地址 |
Qmail-send |
Local |
包含本地接收方地址 |
Qmail-send |
Remote |
包含远程接收方地址 |
Qmail-send |
lock |
包含用来指示何时消息能被qmail-send读取,针对todo目录 |
Qmail-queue Qmail-send |
Bounce |
包含已经永久投递失败的信息 |
Qmail-send |
每个message,不论是qmail-inject创建还是qmail-smtpd创建,均是将消息体传递给qmail-queue进程处理。每开始处理一个message,都会创建一个新的qmail-queue进程进行处理。
qmail-queue从标准输入0读取发送过来的消息体数据,将消息写入消息队列,并进行相关预处理。
Qmail-send从消息队列中获取待发送的消息,进行消息分拣处理后,交给对应的发送程序进行发送。
消息体在整个消息队列中的状态如下图:
Qmail-queue将启动后就会在queue/pid子目录下创建一个唯一的文件,根据该文件的inode号保证两个消息没有重名。至于pid下的文件是否保存消息体内容,代码上实现上看是没有的。
此时该状态尚未结束,qmail-queue根据inode号在mess子目下创建消息文件后,状态结束。
Qmail-queue根据inode号在queue/mess子目下创建消息存储文件fd将消息体存储在该文件下。并同时在intd子目下创建inode号命名的文件。
此时该状态结束。
Qmail-queue扫描mess下消息文件,将消息头信息复制到intd下消息文件中存储。
并将intd下消息文件link到todo子目录下。通知qmail-send进程。
此时该状态结束。
Qmail-send一直在后台运行,不停的扫描todo目录,等待待处理的邮件消息。一旦有新的邮件,qmail-send检查info,local, remote子目录是否存在同名文件,如果有删除掉。
接下来就将读取todo下的邮件文件,将接收地址信息存入info目录下的同名文件中。
Qmail-send在根据info目录下的邮件文件中的接收地址信息,在local或者remote子目录中创建同名文件,将接收地址写入。
此时该状态结束。
Qmail-send处理local和remote子目录下的文件。每个文件都包含接收者的地址信息。Qmail-send读取接收者地址信息,并试图将mess子目录下的邮件正文投递给指定的接收方。Qmail-send将每个接收方根据投递的状态标以DONE与NOT DONE。
一旦所有接收方都标以DONE,该文件被删除,邮件发送成功。
如果投送收到一个临时性错误,接收方将标以NOT DONE,等待再次处理。
如果投送收到永久性失败,接收方标以DONE并在bounce子目录下的文件中添加一行记录。
Bounce目录下的文件将和mess目录的文件一起做为新的消息被重新交给qmail-queue处理,接收者为原邮件的发送者。一旦消息创建了,bounce目录下的文件就被删除。
队列里的每条消息都由唯一的号码标识,假定某条消息是的标识码是457。队列被组织成几个目录,每个目录都可能包含和这条消息相关的文件:
mess/457: 消息正文
todo/457: 信封: 消息的来源地址和目的地址。实际是指向intd/457的链接。
intd/457: 信封,由qmail-queue生成。
info/457: 信封上的发送者地址,预处理后生成。
local/457: 本地接受地址,预处理后生成。
remote/457: 远程接受地址,预处理后生成。
bounce/457: 传输错误信息。
以下是一条消息所有可能的状态。“+”号表示该目录下文件存在,“-”表示该目录下文件不存在,“?”表示未知。
S1. -mess -intd -todo -info -local -remote -bounce
S2. +mess -intd -todo -info -local -remote -bounce
S3. +mess +intd -todo -info -local -remote -bounce
S4. +mess ?intd +todo ?info ?local ?remote -bounce (queued,表示已经进入队列)
S5. +mess -intd -todo +info ?local ?remote ?bounce (preprocessed,表示已通过预处理)
代码参考:
struct qmail { int flagerr; unsigned long pid;/* qmail-queue pid*/ int fdm; /*mail message fd*/ int fde; /* envelope information fd*/ substdio ss; char buf[1024]; } ; extern int qmail_open(); extern void qmail_put(); extern void qmail_puts(); extern void qmail_from(); extern void qmail_to(); extern void qmail_fail(); extern char *qmail_close(); extern unsigned long qmail_qp(); |
标准输入0 上数据格式:
Mime 邮件体信息。 从Data命令中获取。
标准输出1 上数据格式:
信封信息。从Mail From和 Rcpt to 命令获取。格式为:
Fxxxxxxxxxxxxx[mail from输入]
Txxxxxxxxxxxxx[rcpt to 输入]
整个qmail-queue都是通过该结构进行操作。理清该结构的接口,也就对整个qmail-queue进程环境搞清了。
代码参考: qmail-queue.c
按照邮件状态分析
l 消息插入
参考代码
/*创建唯一文件 pidfn = qmail_dir/queue/pid/xxx[mypid].xxxxx[datetime].x[seq]*/ pidopen(); if (fstat(messfd,&pidst) == -1) die(63); /*生成将要建立的文件的文件名 几个文件都是根据刚才建立的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的。 */ messnum = pidst.st_ino; messfn = fnnum("mess/",1); todofn = fnnum("todo/",0); intdfn = fnnum("intd/",0); /*创建mess子目录下邮件消息文件*/ if (link(pidfn,messfn) == -1) die(64); /*删除pid下临时文件*/ if (unlink(pidfn) == -1) die(63); flagmademess = 1; |
此时邮件队列状态如下:
S1. +mess -intd -todo -info -local -remote -bounce
l 消息存储
参考代码
/*设置messfd为标准输出fd[ssout]*/ substdio_fdbuf(&ssout,write,messfd,outbuf,sizeof(outbuf));
/*设置0为标准输入fd[ssin], 0 读入的是 message stream*/ substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); /*qmail-queue对邮件消息体头上添加的自己的信息 信息格式: .生成自已的邮件首部,也就是你在邮件头中见到的类似下面的东西 Recevied (qmail 855 invoked by uid 0); 2 May 2003 12:18:09 -0000 */ if (substdio_bput(&ssout,received,receivedlen) == -1) die_write(); /*将输入的数据拷贝到输出缓冲中*/ switch(substdio_copy(&ssout,&ssin)) { case -2: die_read(); case -3: die_write(); } /*输出flush 到 messfd*/ if (substdio_flush(&ssout) == -1) die_write(); if (open_sync && fsync(messfd) == -1) die_write(); /*创建intdfn*/ intdfd = open_excl(intdfn); if (intdfd == -1) die(65); flagmadeintd = 1; |
此时邮件队列状态如下:
S2. +mess +intd -todo -info -local -remote -bounce
l 消息头提取
参考代码
/*向initfd写入: uxxx[uid] pxxx[pid] */ 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; } 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 (open_sync && fsync(intdfd) == -1) die_write(); /*todofd link to intfd*/ if (link(intdfn,todofn) == -1) die(66); triggerpull(); |
顺序执行以上三个状态后,qmail-queue成功退出。
此时邮件队列状态如下:
S3. +mess +intd +todo -info -local -remote -bounce