亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

程序員眼中的qmail(qmail源代碼分析)

2019-11-17 05:00:48
字體:
來源:轉載
供稿:網友
  很多人對qmail smtp的認證機制,環境變量,執行順序不太了解。  仔細看完這一大篇代碼后相信你會明白很多你過去不太明白的問題?! ‘斎荒阋幸稽c點c語言基礎。也只要一點點?! ome from: ChongQing Gearbox co.,ltd  這份文件還不完善,假如您完善了它請發一份給我: beggar110@163.com  這份文件是給想深入了解qmail和想hacker qmail的人讀的,假如你只是想建立一個能夠運作的mail服務器,沒有必要讀下去了。它將浪費你很多的時間?! 〖偃缒銓mail控制文件還不是很了解,閱讀這份文件之前,請先閱讀rainbow的《qmail控制文件詳解》  在這里你可以找到www.chinaunix.net/forum/viewtopic.php?t=1126  好的。開始我們qmail內部的漫游吧!?。et'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命令的接收,并調用相應的處理程序?! z查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不可能被兩個文件同時占用,這保證了郵件唯一性?! ∑渲衜ess目錄下的文件放置有一個%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命令,進行相應處理?! ?.調用子進程(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();  }  //檢查消息塊是否存在?;蛳K的刪除標記是否已經設置了  //成功返回消息塊的位置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



發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
97精品国产97久久久久久免费| 国产精品九九久久久久久久| 中文字幕亚洲在线| 色小说视频一区| 久久夜精品va视频免费观看| 成人做爽爽免费视频| 午夜精品在线观看| 亚洲色图美腿丝袜| 久久资源免费视频| 日韩欧美一区二区三区久久| 欧美性高潮在线| 久久6免费高清热精品| 欧美激情欧美狂野欧美精品| 91九色蝌蚪国产| 97涩涩爰在线观看亚洲| 黄色成人在线播放| 色偷偷偷综合中文字幕;dd| 亚洲最新av在线| 久久久精品美女| 中文字幕亚洲综合久久| 久久综合久久八八| 成人xxxx视频| 亚洲精品99久久久久| 精品久久中文字幕久久av| 91免费精品国偷自产在线| 久久伊人色综合| 日韩成人在线免费观看| 亚洲国产另类久久精品| 一本色道久久88精品综合| 一本色道久久综合狠狠躁篇怎么玩| 欧美日韩午夜激情| 精品露脸国产偷人在视频| 91国内产香蕉| 粗暴蹂躏中文一区二区三区| 成人av电影天堂| 中文字幕v亚洲ⅴv天堂| 精品国产视频在线| 国产精品永久免费在线| 亚洲精品在线观看www| 久久精品成人欧美大片古装| 久久精品成人欧美大片古装| 国产99久久精品一区二区永久免费| 亚洲人成网站在线播| 欧美一级片久久久久久久| 日产日韩在线亚洲欧美| 久久免费国产精品1| 亚洲在线观看视频| 中文字幕视频一区二区在线有码| 日本精品va在线观看| 精品亚洲国产视频| 亚洲美女在线看| 欧美大成色www永久网站婷| 欧美国产日韩一区二区在线观看| 国产精品欧美风情| 色综合91久久精品中文字幕| 国产精品久久久久av免费| 欧美性猛交xxxx免费看久久久| 欧美成人sm免费视频| 亚洲精品资源在线| 在线亚洲国产精品网| 亚洲欧美日韩国产精品| 一区二区三区久久精品| 91豆花精品一区| 国产精品专区h在线观看| 日韩有码在线电影| 精品福利在线看| 亚洲色图av在线| 亚洲女性裸体视频| 亚洲理论片在线观看| 2021久久精品国产99国产精品| 日韩精品视频免费| 亚洲第一页中文字幕| 中文字幕亚洲情99在线| 有码中文亚洲精品| 欧美肥臀大乳一区二区免费视频| 亚洲加勒比久久88色综合| 姬川优奈aav一区二区| 久久久女人电视剧免费播放下载| 97国产精品久久| 久久99国产精品久久久久久久久| 国产深夜精品福利| 国产精品丝袜一区二区三区| 国产91色在线|| 欧美寡妇偷汉性猛交| 亚洲人成网站免费播放| 日韩美女激情视频| 欧美裸体xxxx极品少妇软件| 国产精品h片在线播放| 国产精品美女免费| 亚洲国产精品专区久久| 亚洲精品视频播放| 麻豆乱码国产一区二区三区| 亚洲欧美精品伊人久久| 中文在线资源观看视频网站免费不卡| 欧美电影免费观看大全| 亚洲精品视频播放| 一区二区成人精品| 国产在线视频2019最新视频| 日韩精品在线第一页| 国产视频久久久久| 精品视频偷偷看在线观看| 97视频在线看| 欧美午夜激情小视频| 在线日韩中文字幕| 在线视频日韩精品| 中文字幕精品—区二区| 欧美一级黑人aaaaaaa做受| 久久精品国产亚洲一区二区| 日韩高清a**址| 欧美一区二区大胆人体摄影专业网站| 亚洲影院在线看| 日韩国产中文字幕| 精品国内产的精品视频在线观看| 亚洲成人久久电影| 欧美另类极品videosbest最新版本| 欧美激情视频在线免费观看 欧美视频免费一| 欧美精品国产精品日韩精品| 91在线观看免费网站| 久久精品亚洲精品| 欧洲亚洲免费在线| 日韩av一区二区在线| 久久夜色撩人精品| 日本精品视频在线播放| 国产一区在线播放| 亚洲一品av免费观看| 国产综合在线看| 亚洲级视频在线观看免费1级| 成人av在线天堂| 亚洲自拍另类欧美丝袜| 国产精品福利观看| 日韩经典中文字幕在线观看| 久久精品久久久久久| www.国产精品一二区| 欧美裸体xxxx极品少妇| 亚洲视频777| 在线国产精品播放| 高清欧美电影在线| 亚洲第一福利网| 欧美性xxxx| 亚洲国产精品yw在线观看| 欧美日韩中文字幕日韩欧美| 久久国产一区二区三区| 97avcom| 国产精品一区二区在线| 欧美俄罗斯性视频| 91在线视频精品| 国产精品91久久久| 精品国内自产拍在线观看| 亚洲欧洲在线播放| www.日韩不卡电影av| 91精品国产综合久久男男| 91免费国产网站| 欧美激情精品久久久久久大尺度| 成人性生交大片免费看视频直播| 国产精品久久久久免费a∨| 日韩欧美在线一区| 欧美疯狂性受xxxxx另类| 国内精品400部情侣激情| xx视频.9999.com| 美女999久久久精品视频| 亚洲色图色老头| 久久影院模特热| 日本久久中文字幕| 亚洲一区二区三区香蕉|