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

程序员眼中的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
󰈣󰈤
 
 
 
>>返回首頁<<
 
 
 
 
 
 熱帖排行
 
 
王朝网络微信公众号
微信扫码关注本站公众号 wangchaonetcn
 
  免责声明:本文仅代表作者个人观点,与王朝网络无关。王朝网络登载此文出于传递更多信息之目的,并不意味著赞同其观点或证实其描述,其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有