qmail源代码分析之qmail-queue

12/3/2006来源:Qmail人气:6952

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,%26amp;dt); len += i; if (s) s += i;
return len;
}

void received_setup()
{
receivedlen = receivedfmt((char *) 0);
received = alloc(receivedlen + 1);
if (!received) die(51);
receivedfmt(received);
}

unsigned int pidfmt(s,seq)
char *s;
unsigned long seq;
{
unsigned int i;
unsigned int len;

//生成类型pid/3434.34242424.1的字符串到s中
//这个字符串实际上就是/var/qmail/queue/pid目录下一个文件名。指示当前进程的pid.
len = 0;
i = fmt_str(s,"pid/"); len += i; if (s) s += i;
i = fmt_ulong(s,mypid); len += i; if (s) s += i;
i = fmt_str(s,"."); len += i; if (s) s += i;
i = fmt_ulong(s,starttime); len += i; if (s) s += i;
i = fmt_str(s,"."); len += i; if (s) s += i;
i = fmt_ulong(s,seq); len += i; if (s) s += i;
++len; if (s) *s++ = 0;

return len;
}

char *fnnum(dirslash,flagsplit)
char *dirslash;
int flagsplit;
{
char *s;

s = alloc(fmtqfn((char *) 0,dirslash,messnum,flagsplit));
if (!s) die(51);
fmtqfn(s,dirslash,messnum,flagsplit);
return s;
}

void pidopen() //建立类似/var/run/inet.pid之类的进程id文件.
{
unsigned int len;
unsigned long seq;

seq = 1;
len = pidfmt((char *) 0,seq);
pidfn = alloc(len);
if (!pidfn) die(51);

for (seq = 1;seq < 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(%26amp;dt,starttime);//将起始时间转换为可读年月日时分秒的形式
//生成自已的邮件头存入缓存reseived中
//例如: received="Received: (qmail 3434 invoked by 34434); Apr 27 2003 14:55:34"
received_setup();

sig_pipeignore();
sig_miscignore();
sig_alarmcatch(sigalrm);//捕捉alarm信号,控制超时
sig_bugcatch(sigbug);

alarm(DEATH); //超时秒数,缺省值是86400(24小时) 后错误返回52

pidopen();//建立进程id文件
if (fstat(messfd,%26amp;pidst) == -1) die(63);

messnum = pidst.st_ino; //进程id文件的inode节点号


/*生成将要建立的文件的文件名
几个文件都是根据刚才建立的pid文件的inode节点号命名的.inode不可能被两个文件同时占用,这保证了邮件唯一性。
其中mess目录下的文件放置有一个%23的问题,
tips: 因为是%23所以该目录名最大的可能只有22,明白queue/mess目录下目录为什么最大只22了吧
比如说inode节点号为3455,那么3455%23=5,那么将生成/var/qmail/queue/mess/5/3455 这样一个文件来存放邮件。
/var/qmail/queue/todo/3455与/var/qmail/queue/intd/3455是相同的,都是保存用户id,进程id,mailfrom,rcptto的。
*/
messfn = fnnum("mess/",1); //解释为message file 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(%26amp;ssout,write,messfd,outbuf,sizeof(outbuf));

//fd0关联到读标准输入到缓存区inbuf 通过管道连接 <---------qmail-smtp 的 qqt->fdm
//也就是说读ssin将从qmail-smtpd的qqt->fdm端读
substdio_fdbuf(%26amp;ssin,read,0,inbuf,sizeof(inbuf));

//向mess/下的邮件文件写qmail-queue的头部信息
if (substdio_bput(%26amp;ssout,received,receivedlen) == -1) die_write();

//从fd1读smtpd设置的邮件首部
switch(substdio_copy(%26amp;ssout,%26amp;ssin))
{
case -2: die_read();
case -3: die_write();
}

if (substdio_flush(%26amp;ssout) == -1) die_write();
if (fsync(messfd) == -1) die_write();

intdfd = open_excl(intdfn);
if (intdfd == -1) die(65);
flagmadeintd = 1;

//fd1关联到写intd/下新建立的文件 fd0关联到读inbuff缓冲区
substdio_fdbuf(%26amp;ssout,write,intdfd,outbuf,sizeof(outbuf));
substdio_fdbuf(%26amp;ssin,read,1,inbuf,sizeof(inbuf));

/*
向intd下新建立的文件写如下格式内容
这些内容来自于qmail-smtpd.c中的data命令的解释函数。
u[uid]p[pid]F[mailfrom]T[rcptto1][rcptto2][rcptton]
例如:lyx@hg.org向hong@hg.org和beggar@hg.org发邮件可能会有如下内容
u6027p34234Flyx@hg.orgThong@hg.orgTbeggar@hg.org
*/

if (substdio_bput(%26amp;ssout,"u",1) == -1) die_write();
if (substdio_bput(%26amp;ssout,tmp,fmt_ulong(tmp,uid)) == -1) die_write();
if (substdio_bput(%26amp;ssout,"",1) == -1) die_write();

if (substdio_bput(%26amp;ssout,"p",1) == -1) die_write();
if (substdio_bput(%26amp;ssout,tmp,fmt_ulong(tmp,mypid)) == -1) die_write();
if (substdio_bput(%26amp;ssout,"",1) == -1) die_write();

if (substdio_get(%26amp;ssin,%26amp;ch,1) < 1) die_read();
if (ch != 'F') die(91);
if (substdio_bput(%26amp;ssout,%26amp;ch,1) == -1) die_write();
for (len = 0;len < ADDR;++len)
{
if (substdio_get(%26amp;ssin,%26amp;ch,1) < 1) die_read();
if (substdio_put(%26amp;ssout,%26amp;ch,1) == -1) die_write();
if (!ch) break;
}

//如有多个邮件接收人时,这些接收人的地址总不长度不能超过1023字节,如果每个邮件地址约为15个字节的话,
//大约可能指定65个
if (len >= ADDR) die(11);

if (substdio_bput(%26amp;ssout,QUEUE_EXTRA,QUEUE_EXTRALEN) == -1) die_write();

for (;
{
if (substdio_get(%26amp;ssin,%26amp;ch,1) < 1) die_read();
if (!ch) break;
if (ch != 'T') die(91);
if (substdio_bput(%26amp;ssout,%26amp;ch,1) == -1) die_write();
for (len = 0;len < ADDR;++len)
{
if (substdio_get(%26amp;ssin,%26amp;ch,1) < 1) die_read();
if (substdio_bput(%26amp;ssout,%26amp;ch,1) == -1) die_write();
if (!ch) break;
}
if (len >= ADDR) die(11);
}

if (substdio_flush(%26amp;ssout) == -1) die_write();
if (fsync(intdfd) == -1) die_write();

//复制intdfn到todofn 由此可见这两个是相同的文件
if (link(intdfn,todofn) == -1) die(66);

triggerpull(); //向命名管道 /var/qmail/queue/lock/trigger写一个字节(写的是0),通知有新的邮件
die(0); //退出
}