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

Programmer:夜未眠
Date:Apr 28,2003
Comefrom: ChongQing Gearbox co.,ltd

这份文件还不完善,如果您完善了它请发一份给我: beggar110@163.com
这份文件是给想深入了解qmail和想hacker qmail的人读的,如果你只是想建立一个能够运作的mail服务器,没有必要读下去了。它将浪费你很多的时间。

如果你对qmail控制文件还不是很了解,阅读这份文件之前,请先阅读rainbow的《qmail控制文件详解》
在这里你可以找到http://blog.csdn.net/Borenbao/archive/2006/08/31/1148788.aspx


好的。开始我们qmail内部的漫游吧!!!Let's go!

[code:1:d803669f6f]
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                  <Maildir>  <----qmail-pop3d
                                                              |
                                                              |
                                                        vchkpw
                                                              |
                                                              |
                                                     qmail-popup
                                                              |
                                                              |
                                              tcpserver--+

[/code:1:d803669f6f]



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


[code:1:d803669f6f]
#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 http://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: http://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="<email@eg.org>",或 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 <email@eg.org>命令,则有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<NULL>"
  //多次执行rcpt命令效果会是rcptto="Ttest@eg.org<NULL>Ttwo@eg.org<NULL>"
  if (!stralloc_cats(&rcptto,"T")) die_nomem();
  if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
  if (!stralloc_0(&rcptto)) die_nomem();
  out("250 ok/r/n");
}


//saferead,从网络读len个字节到buf缓冲区
//返回实际读到的字节数.
//超时值为control/timeoutsmtpd文件中指定的值。见setup()函数.(默认值1200秒)
int saferead(fd,buf,len) int fd; char *buf; int len;
{
  int r;
  flush();
  r = timeoutread(timeout,fd,buf,len);
  if (r == -1) if (errno == error_timeout) die_alarm();
  if (r <= 0) die_read();
  return r;
}

char ssinbuf[1024];
substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof ssinbuf);

struct qmail qqt;
unsigned int bytestooverflow = 0;

void put(ch)
char *ch;
{
  if (bytestooverflow)
    if (!--bytestooverflow)
      qmail_fail(&qqt);
  qmail_put(&qqt,ch,1);
}

void blast(hops)
int *hops;
{
  char ch;
  int state;
  int flaginheader;
  int pos; /* number of bytes since most recent /n, if fih */
  int flagmaybex; /* 1 if this line might match RECEIVED, if fih */
  int flagmaybey; /* 1 if this line might match /r/n, if fih */
  int flagmaybez; /* 1 if this line might match DELIVERED, if fih */
 
  state = 1;
  *hops = 0;
  flaginheader = 1;
  pos = 0; flagmaybex = flagmaybey = flagmaybez = 1;
  for (;;) {
    substdio_get(&ssin,&ch,1);//从标准输入(也就是网络)读邮件内容直到读到仅有一个点的行.
    if (flaginheader) {
      if (pos < 9) {
        if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0;
        if (flagmaybez) if (pos == 8) ++*hops;
        if (pos < 8)
          if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0;
        if (flagmaybex) if (pos == 7) ++*hops;
        if (pos < 2) if (ch != "/r/n"[pos]) flagmaybey = 0;
        if (flagmaybey) if (pos == 1) flaginheader = 0;
      }
      ++pos;
      if (ch == '/n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; }
    }
    switch(state) {
      case 0:
        if (ch == '/n') straynewline();
        if (ch == '/r') { state = 4; continue; }
        break;
      case 1: /* /r/n */
        if (ch == '/n') straynewline();
        if (ch == '.') { state = 2; continue; }
        if (ch == '/r') { state = 4; continue; }
        state = 0;
        break;
      case 2: /* /r/n + . */
        if (ch == '/n') straynewline();
        if (ch == '/r') { state = 3; continue; }
        state = 0;
        break;
      case 3: /* /r/n + ./r */
        if (ch == '/n') return;
        put(".");
        put("/r");
        if (ch == '/r') { state = 4; continue; }
        state = 0;
        break;
      case 4: /* + /r */
        if (ch == '/n') { state = 1; break; }
        if (ch != '/r') { put("/r"); state = 0; }
    }
    put(&ch);
  }
}

char accept_buf[FMT_ULONG];
void acceptmessage(qp) unsigned long qp;
{
  datetime_sec when;
  when = now();
  out("250 ok ");
  accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0;
  out(accept_buf);
  out(" qp ");
  accept_buf[fmt_ulong(accept_buf,qp)] = 0;
  out(accept_buf);
  out("/r/n");
}


//data 命令解释程式
//完成向qmail-queue投递邮件
void smtp_data() {
  int hops;
  unsigned long qp;
  char *qqx;
 
  if (!seenmail) { err_wantmail(); return; } //如果没有执行过mail命令,出错返回
  if (!rcptto.len) { err_wantrcpt(); return; } //如果没有执行rcpt命令,出错返回
  seenmail = 0;//将mail命令标志失效,
  //databytes 邮件最大长度,如果没有指定那么它的值将是0
  if (databytes) bytestooverflow = databytes + 1;
  if (qmail_open(&qqt) == -1) { err_qqt(); return; }//建立子进程执行qmail-queue
  qp = qmail_qp(&qqt); //qp 为qmail-queue process缩写,it's a process id.
  out("354 go ahead/r/n");
 
  //向新建立的进程传送邮件头
  received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);
  blast(&hops);
  hops = (hops >= MAXHOPS);
  if (hops) qmail_fail(&qqt);
  //向qmail-queue传送邮件头信息.
  //如果hong@hg.org 向 lyx@hg.org发送邮件,那么向qmail-queue传送的字符串将是
  // Fhong@hg.org<NULL>Tlyx@hg.org<NULL>
  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();
}
[/code:1:d803669f6f]

==完==

 夜未眠 回复于:2003-05-03 20:59:25
qmail-pop3d源代码分析

Programmer:夜未眠
Comefrom: ChongQing Gearbox co.,ltd


关键数据结构
队列: --> prioq
这个数据结构在很多qmail很多程式中都有用到,最好记下来
[code:1:c3936d2617]
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;
[/code:1:c3936d2617]
消息块: --> message   我把它叫作消息块是因为他并不包含消息内容,也许这样称呼它并不确切
[code:1:c3936d2617]
struct message {
int flagdeleted;      //删除标记,在qmail-pop3d程式退出时进行实际删除动作
unsigned long size;   //消息文件大小
char *fn;             //消息文件名
} *m;
[/code:1:c3936d2617]

主要功能:
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命令进行相应的处理.具体见代码分析.

[code:1:c3936d2617]
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 filenames = {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,&filenames,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 = filenames.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();
}
[/code:1:c3936d2617]

==自此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节.

 haohaoo 回复于:2003-05-02 18:41:40
能不能修改一下,把源文件用code包起来啊
这样里面有些东西被认成图标了

 夜未眠 回复于:2003-05-02 18:49:23
我用code包了,还是这样,我晕死,如果你需要我可以email给你

 haohaoo 回复于:2003-05-02 18:54:28
好的
谢谢
haohaoo@21cn.com

 gadfly 回复于:2003-05-02 19:34:09
很不错,我改了一下,禁止了表情符号,允许了bbcode.


无眠,你的bbcode缺省是关闭的,内置tag都起不了作用,改一下你的profile的配置吧

如果能先介绍一下流程和关键机制或技术就更好了.

 夜未眠 回复于:2003-05-02 22:22:58
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/<N>/<INODE>  //邮件正文
queue/intd/<INODE> 用户id,进程id,mailfrom,rcptto
queue/todo/<INODE> 是intd目录下<INODE>文件的复本.
3.写命名管道lock/trigger通知新邮件

[code:1:3fd2ae8256]
#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]<NULL>p[pid]<NULL>F[mailfrom]<NULL>T[rcptto1][rcptto2][rcptton]<NULL>
例如:lyx@hg.org向hong@hg.org和beggar@hg.org发邮件可能会有如下内容
u6027<NULL>p34234<NULL>Flyx@hg.org<NULL>Thong@hg.org<NULL>Tbeggar@hg.org<NULL>
 */

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); //退出
}

[/code:1:3fd2ae8256]

==完==

 夜未眠 回复于:2003-05-03 00:14:19
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密码的检验流程。

几个程式间的关系是:
[code:1:1477526f27]
tcpserver---->qmail-popup---->vchkpw----认证成功--->qmail-pop3d
   |                                          |
   |                                          | 
   <---------- 认证失败-----------+
[/code:1:1477526f27]

==========================

[code:1:1477526f27]
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();
}
[/code:1:1477526f27]

 夜未眠 回复于:2003-05-03 01:09:07
qmail-start.c 分析

Programmer:夜未眠
Comefrom:ChongQing Gearbox co.,ltd

qmail-start 是很简单的一个程式,他完成qmail-send,qmail-clean,qmail-lspawn,qmail-rspawn,splogger的启动,并通过管道将他们联系在一起,当然不是网状连接.具体如下
[code:1:e7358d3ff6]
=====================================
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
=====================================
[/code:1:e7358d3ff6]
理解他们之间的关系(注意方向)对于理解qmail-send源代码非常重要。仔细再看一次。

因为其比较简单,所以这里就不对他的源代码作过细的分析:

[code:1:e7358d3ff6]
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      <pipe>
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();
}
[/code:1:e7358d3ff6]

==完==

 gadfly 回复于:2003-05-03 10:16:18
很不错。Qmail总图中的主干基本上都涉及到了。每个程序的功能都描述的
很清楚。

不过如果能将qmail-clean, qmail-lspawn,qmail-rspawn和splogger的
大概功能说明一下。可能就更清晰了。

欢迎各位参与讨论。

如此精彩,只能替你设置一次精华,可惜。

 夜未眠 回复于:2003-05-03 13:24:42
感谢gadfly如此评价,我是有这个想法的。把全部源代码的分析都贴出来。但时间有限啊。而且qmail源代码关联性很强,最近全泡在这些abc中,头都大了许多。要是有人帮我作成html链接形式就好了。最好是参与进来维护这份文档。呵呵。

 gadfly 回复于:2003-05-04 01:29:09其实qmail-smtpd和qmail-pop3d也是很好的rfc的实现实例,大家如果想写标准网络应用,可以参考这两个源代码和相应的rfc文档

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
E-mail(Electronic Mail)是一种电子邮件服务,它可以在网络上进行邮件的传输和交换。在实际应用中,经常需要使用E-mail服务器来实现邮件的发送和接收。下面将介绍E-mail服务器的配置与使用。 一、E-mail服务器的配置 1. 配置SMTP服务器 SMTP(Simple Mail Transfer Protocol)是一种邮件传输协议,它用于发送邮件。在配置E-mail服务器之前,需要先配置SMTP服务器。可以使用一些常见的SMTP服务器软件,例如Postfix、Sendmail、Qmail等。以Postfix为例,可以按照以下步骤进行配置: - 安装Postfix软件,可以使用以下命令安装: ``` sudo apt-get install postfix ``` - 在安装过程中,需要输入一些基本的配置信息,例如E-mail服务器的域名、E-mail服务器的类型(Internet Site或者Internet with smarthost)等。 - 完成安装后,可以使用以下命令检查Postfix是否正常运行: ``` sudo systemctl status postfix ``` 2. 配置POP3/IMAP服务器 POP3(Post Office Protocol version 3)和IMAP(Internet Message Access Protocol)是两种接收邮件的协议。在配置E-mail服务器之前,需要选择一种协议,并配置相应的POP3/IMAP服务器。可以使用一些常见的POP3/IMAP服务器软件,例如Dovecot、Courier等。以Dovecot为例,可以按照以下步骤进行配置: - 安装Dovecot软件,可以使用以下命令安装: ``` sudo apt-get install dovecot-core dovecot-imapd dovecot-pop3d ``` - 在安装过程中,需要输入一些基本的配置信息,例如E-mail服务器的域名、E-mail服务器的类型(Internet Site或者Internet with smarthost)等。 - 完成安装后,可以使用以下命令检查Dovecot是否正常运行: ``` sudo systemctl status dovecot ``` 3. 配置SSL证书(可选) 为了保证E-mail服务器的安全性,可以使用SSL证书对E-mail服务器进行加密。可以使用以下命令生成SSL证书: ``` sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/mailserver.key -out /etc/ssl/certs/mailserver.crt ``` 其中,/etc/ssl/private/mailserver.key是SSL证书的私钥文件,/etc/ssl/certs/mailserver.crt是SSL证书的公钥文件。 二、E-mail服务器的使用 1. 发送邮件 可以使用E-mail客户端软件,例如Thunderbird、Outlook等,连接到SMTP服务器,并发送邮件。以Thunderbird为例,可以按照以下步骤发送邮件: - 打开Thunderbird软件,点击Write按钮编写邮件。 - 输入收件人的E-mail地址、主题、邮件内容等信息。 - 点击Send按钮发送邮件。 2. 接收邮件 可以使用E-mail客户端软件,例如Thunderbird、Outlook等,连接到POP3/IMAP服务器,并接收邮件。以Thunderbird为例,可以按照以下步骤接收邮件: - 打开Thunderbird软件,点击Get Messages按钮接收邮件。 - 在左侧窗口中选择需要接收邮件的E-mail账号。 - 在右侧窗口中查看已经接收到的邮件。 3. 删除邮件 可以使用E-mail客户端软件,例如Thunderbird、Outlook等,连接到POP3/IMAP服务器,并删除邮件。以Thunderbird为例,可以按照以下步骤删除邮件: - 打开Thunderbird软件,选择需要删除的邮件。 - 点击Delete按钮删除邮件,或者将邮件移动到Trash文件夹中。 以上就是E-mail服务器配置与使用的简要介绍。需要注意的是,在实际应用中,应当保证E-mail服务器的安全性,并设置合适的权限,以防止邮件泄露和非法操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值