找回密码
 注册
搜索

邮件服务器准备——Linux 环境

Real-King 2009-11-12 10:37 显示全部楼层 阅读模式 来自: 中国辽宁大连
Linux 环境
回复

使用道具 举报

大神点评14

 楼主| Real-King 2009-11-12 10:39 显示全部楼层 来自: 中国辽宁大连

分布式的Qmail邮件系统存储

一. 设计目的
    适应多用户、大容量的邮件系统,易扩展,提供mail服务冗余特性。
    二. 配置环境
    我的测试环境采用了三台PC Server,均采用RedHat 6.2,openldap2.0.7和qmail-1.03 以及qmail-ldap,分别运行smtp/pop3服务,具体 配置如下。
    192.168.0.19 omni1.i100.com.cn 主smtp/pop3 server,dns MX记录,邮件存储主机,qmqp server 192.168.0.5 cocoon.i100.com.cn LDAP server,邮件存储主机 192.168.0.2 gw2.i100.com.cn 邮件存储主机,qmqp server,同时是次smtp/pop3 server 在192.168.0.19的/data/vuser/目录下,存储johnny的邮件。 在192.168.0.5的/data/vuser/目录下,存储jacky的邮件。 在192.168.0.2的/var/qmail/vuser/目录下,存储denny的邮件。 以上配置在实践中,可以配置成邮件存储和ldap以及smtp server分别位于不同机器。
    三. 软件 openldap-2.0.7.tgz
    qmail-1.03.tar.gz
    qmail-ldap-1.03-20010301.patch

    ucspi-tcp-0.84.tar.gz
    编译qmail的时候,记得把对cluster的支持编译进去。Edit the Makefile to reflect your setup. You can change the following values: QLDAP-CLUSTER Compiles the clustering code in. Note: this doesn't mean clustering is on, it just means you _can_ turn on clustering. 缺 省qmail是支持cluster结构的。
    四. LDAP和qmail的安装
    关于LDAP和qmail的安装,在这里,我给出我的LDAP的ldif文件:dn: dc=i100, dc=com, dc=cn
    objectclass: top
    objectclass: orgnization

    dn: cn=Manager, dc=i100, dc=com, dc=cn
    uid: Manager
    objectclass: qmailUser
    mail: johnnys@cn.solution100.com

    dn: cn=johnny, dc=i100, dc=com, dc=cn
    cn: johnny
    sn: johnny
    objectclass: top
    objectclass: person

    objectclass: inetOrgPerson
    objectclass: qmailUser
    mail: johnny@i100.com.cn
    mailhost: omni1.i100.com.cn
    mailalternateaddress: johnnys@i100.com.cn
    mailmessagestore: /data/vuser/johnny/
    mailquota: 51200
    uid: johnny
    userpassword: hSAMdaZcsdAOI

    dn: cn=jacky, dc=i100, dc=com, dc=cn
    cn: jacky
    sn: jacky
    objectclass: top
    objectclass: person
    objectclass: inetOrgPerson
    objectclass: qmailUser
    mail: jacky@i100.com.cn
    mailhost: cocoon.i100.com.cn
    mailalternateaddress: jacky@i100.com.cn
    mailmessagestore: /data/vuser/jacky/
    mailquota: 51200
    uid: jacky
    userpassword: hSAMdaZcsdAOI

    dn: cn=denny, dc=i100, dc=com, dc=cn
    cn: denny

    sn: denny
    objectclass: top
    objectclass: person
    objectclass: inetOrgPerson
    objectclass: qmailUser
    mail: denny@i100.com.cn
    mailhost: gw2.i100.com.cn
    mailalternateaddress: denny@i100.com.cn
    mailmessagestore: /var/qmail/vuser/denny/
    mailquota: 51200
    uid: denny
    userpassword: hSAMdaZcsdAOI 以上大家可以看到,我把三个用户的邮件分别存到了不同的机器上。如果用户多,可以把a-n和o-z开头的用户分别存储到不同的机器上。
    五. 基本系统配置
    在三台机器上分别install qmail with qmail-ldap patch. 需要特别配置的是/var/qmail/control下的相关文件。 ldapuid ldapgid 这是可以读写用户邮件目录的系统用户的uid和gid,在不同的机器上,会有不同的配置;确保此用户有读写用户邮件目 录的权限。 ldapserver 此文件指定LDAP
服务器的ip地址;如果您想ldap有冗余作用,在主LDAP server down了的时候,启用次LDAP server,可以在 该文件中指定。
    如:ldap1.i100.com.cn:389 ldap2.i100.com.cn:389 :) ldapserver 此文件内容是0或者1,表示是否启动qmail cluster模式。我们当然要启动了。 echo 1 > /var/qmail/control/ldapserver
    六. Qmail Cluster工作原理
    在允许cluster的邮件环境中,每台主机都可以处理该cluster声明的域的邮件;当一个mail到达主smtp server的时候,Qmail查询LDAP server中有关该用户的mailhost属性,如果mailhost属性指定的不是此server的/var/qmail/control/me中定义的的主机,此邮件被通过qmqp协 议转发到mailhost定义的主机上。
    注:所有主机名必须是合法的dns主机名。
    七. 详细配置
    在每台运行qmail的系统上运行qmail-qmqpd进程,才可实现邮件转发。需要设置tcp.qmqp
    文件,此文件中定义可信任的mailhost. 如: 192.168.0.:allow :deny 然后用tcprules生成tcp.qmqp.cdb文件。 运行 /usr/local/bin/tcpserver -v -x /var/qmail/control/tcp.qmqp.cdb -u 502 -g 501 0 628 /var/qmail/bin/qmail-qmqpd 2>&1 | /var/qmail/bin/splogger qmqpd & 启动qmail-qmqpd进程,监听在628 port。 在客户端,使用oe将pop3 server设置为主smtp/pop3 server地址(这里是192.168.0.19),就可以收到存储在192.168.0.5和192.168.0.2 上用户的邮件。 :)
    八. 有关qmail-qmqpd server
    以上实现了邮件的分布式存储,但是随着用户的增加,主smtp/pop3 server会成为整个系统的瓶颈;而且一旦主smtp/pop3 server down机 ,整个系统将不再有效。
    解决办法是增加qmqpd server。
    现在主smtp/pop3 server是192.168.0.19,我测试环境是把192.168.0.2同时也作为qmqpd server;由此,一旦192.168.0.19 down机, 192.168.0.2仍然可以继续提供smtp/pop3服务。 我简单的采用手工down掉192.168.0.19,而把pop3和smtp server设置成192.168.0.2的方式,以证实此方法的可行性;并且192.168.0.2并 不是此域声明的MX记录。 以上需要在两台qmqpd server的/var/qmail/control目录中,增加qmqpservers文件,在其中每行写入一台qmqpd server的ip地址。 :)
    九. 其他
    如果需要webmail功能,可以和sqwebmail进行整合。此方案不提供对pop3存储的冗余措施,如果可能,可以采用Raid、NFS或者SAN的解决方案
回复 支持 反对

使用道具 举报

 楼主| Real-King 2009-11-12 10:40 显示全部楼层 来自: 中国辽宁大连

QMAIL:邮件存储模式篇——Courier

传统的IMAP并不支持Maildir邮件存储格式,为给Qmail用户提供IMAP协议,必须安装Courier-IMAP。Courier-IMAP 是一个提供IMAP协议访问Maildir的服务器,它不再支持传统的Mailbox邮件存储方式。   前面已经介绍过,“Maildir” 是Qmail为了解决用户邮件存储问题而提出的新的邮件存储方式。每个邮件以单独的一个文件保存在用户个人的邮件目录下,不再像过去每个用户的邮件都保存在/var/mail下面的文件中。多个应用程序可以同时访问同一个邮件目录,避免了加锁问题的困扰,特别适合于缺乏文件锁定机制的NFS存储方式。
   Courier-IMAP主要特点
   * 小巧而高效;
   * 提供多种用户认证模块和方式;
   * 支持虚拟邮箱;
   * 可限制IMAP同时登录的总数目及同一个IP地址同时登录的数目,能有效保护系统在受到拒绝服务(Denial-of-service)攻击时不致因超载而瘫痪;
   * 支持共享文件夹(Shared folder)。
   下载与安装
   目前的最新版本是:courier-imap-0.32-tar.gz。Courier-IMAP会自动监听来自客户端的连接请求,所以还需要将/etc/inetd.conf文件中的IMAP服务一行注释掉。完整的安装过程如下:
   $ ./configure [ options ]
   $ make
   $ make check  
   $ make install
   用以下命令启动Courier-IMAP服务:
   $ /usr/lib/courier-imap/
   libexec/imapd.rc start
   假定Courier-IMAP安装在/usr/lib/courier-imap中,可通过以下命令停止Courier-IMAP服务:
   $ /usr/lib/courier-imap/
   libexec/imapd.rc stop
   你可以将这两条命令加到系统的启动和关闭脚本中。
   Courier-IMAP的使用
   与POP3协议相比,IMAP 协议的好处在于可将用户个人的邮件分门别类地保存在邮件服务器的个人目录中,而不用下载到本地硬盘中,尤其适用于那些没有固定个人电脑或者经常出差在外的用户。作为邮件服务的提供者,我们应同时提供这两种服务,以满足用户的不同需要。
   Courier-IMAP支持各种标准的IMAP客户端软件,如Netscape Messenger、Pine、Microsoft Outlook Express、Eudora等,用户可选择自己喜爱的客户端邮件软件(很遗憾,目前最受用户推崇的FoxMail尚不支持IMAP协议)。需要注意的是,使用虚拟域和虚拟用户时,由于用户权限上的问题,好象无法在InBox之外建立个人目录,好在这不是个大问题。
回复 支持 反对

使用道具 举报

 楼主| Real-King 2009-11-12 10:42 显示全部楼层 来自: 中国辽宁大连

程序员眼中的qmail(qmail源代码分析)

很多人对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)
  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) { /* copy arg to addr, stripping quotes */
回复 支持 反对

使用道具 举报

 楼主| Real-King 2009-11-12 10:43 显示全部楼层 来自: 中国辽宁大连
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 + . */
回复 支持 反对

使用道具 举报

 楼主| Real-King 2009-11-12 10:44 显示全部楼层 来自: 中国辽宁大连
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;
  //错误清理
回复 支持 反对

使用道具 举报

 楼主| Real-King 2009-11-12 10:45 显示全部楼层 来自: 中国辽宁大连
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);
回复 支持 反对

使用道具 举报

 楼主| Real-King 2009-11-12 10:46 显示全部楼层 来自: 中国辽宁大连
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个
回复 支持 反对

使用道具 举报

 楼主| Real-King 2009-11-12 10:47 显示全部楼层 来自: 中国辽宁大连
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();
回复 支持 反对

使用道具 举报

 楼主| Real-King 2009-11-12 10:49 显示全部楼层 来自: 中国辽宁大连
 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很多程式中都有用到,最好记下来
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

  • 0 关注
  • 2 粉丝
  • 15 帖子
 

天健社区APP