Matt Simerson    3/26/2001
mpsimerson@hostpro.com

This patch is merely the combination of other patches. First, to closely 
resemble the FreeBSD qmail port, we include the dns (AOL) patch to accept
>512 dns entries. For the same reason, we also change qmails nofiles group
to qnofiles. On top of that we install the big-concurrency.patch (see
README.big-concurrency) which raises the number of simultaneous outgoing 
connections we can make. We also add Chris Johnson's smtp tarpitting patch
(see README.tarpit) and finally Eric Johnson's SMTP-AUTH patch (see the
README.auth or 'man qmail-smtpd' after installing). 

*** Makefile.orig	Mon Jun 15 03:53:16 1998
--- Makefile	Mon Mar 26 16:51:47 2001
***************
*** 136,141 ****
--- 136,145 ----
  compile auto_usera.c
  	./compile auto_usera.c
  
+ base64.o: \
+ compile base64.c base64.h stralloc.h substdio.h str.h
+ 	./compile base64.c
+ 
  binm1: \
  binm1.sh conf-qmail
  	cat binm1.sh \
***************
*** 1536,1547 ****
  timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \
  date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \
  open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \
! fs.a auto_qmail.o socket.lib
  	./load qmail-smtpd rcpthosts.o commands.o timeoutread.o \
  	timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \
  	received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \
  	datetime.a getln.a open.a sig.a case.a env.a stralloc.a \
! 	alloc.a substdio.a error.a str.a fs.a auto_qmail.o  `cat \
  	socket.lib`
  
  qmail-smtpd.0: \
--- 1540,1551 ----
  timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \
  date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \
  open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \
! fs.a auto_qmail.o base64.o socket.lib
  	./load qmail-smtpd rcpthosts.o commands.o timeoutread.o \
  	timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \
  	received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \
  	datetime.a getln.a open.a sig.a case.a env.a stralloc.a \
! 	alloc.a substdio.a error.a str.a fs.a auto_qmail.o base64.o  `cat \
  	socket.lib`
  
  qmail-smtpd.0: \
***************
*** 1553,1559 ****
  substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \
  error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \
  substdio.h str.h fmt.h scan.h byte.h case.h env.h now.h datetime.h \
! exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h
  	./compile qmail-smtpd.c
  
  qmail-start: \
--- 1557,1564 ----
  substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \
  error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \
  substdio.h str.h fmt.h scan.h byte.h case.h env.h now.h datetime.h \
! exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h wait.h \
! fd.h base64.h
  	./compile qmail-smtpd.c
  
  qmail-start: \
*** TARGETS.orig	Mon Jun 15 03:53:16 1998
--- TARGETS	Mon Mar 26 16:51:47 2001
***************
*** 250,255 ****
--- 250,256 ----
  qmail-qmtpd.o
  rcpthosts.o
  qmail-qmtpd
+ base64.o
  qmail-smtpd.o
  qmail-smtpd
  sendmail.o
*** dns.c.orig	Mon Jun 15 03:53:16 1998
--- dns.c	Mon Mar 26 16:51:40 2001
***************
*** 21,30 ****
  static unsigned short getshort(c) unsigned char *c;
  { unsigned short u; u = c[0]; return (u << 8) + c[1]; }
  
! static union { HEADER hdr; unsigned char buf[PACKETSZ]; } response;
  static int responselen;
  static unsigned char *responseend;
  static unsigned char *responsepos;
  
  static int numanswers;
  static char name[MAXDNAME];
--- 21,32 ----
  static unsigned short getshort(c) unsigned char *c;
  { unsigned short u; u = c[0]; return (u << 8) + c[1]; }
  
! static struct { unsigned char *buf; } response;
! static int responsebuflen = 0;
  static int responselen;
  static unsigned char *responseend;
  static unsigned char *responsepos;
+ static u_long saveresoptions;
  
  static int numanswers;
  static char name[MAXDNAME];
***************
*** 45,62 ****
   errno = 0;
   if (!stralloc_copy(&glue,domain)) return DNS_MEM;
   if (!stralloc_0(&glue)) return DNS_MEM;
!  responselen = lookup(glue.s,C_IN,type,response.buf,sizeof(response));
   if (responselen <= 0)
    {
     if (errno == ECONNREFUSED) return DNS_SOFT;
     if (h_errno == TRY_AGAIN) return DNS_SOFT;
     return DNS_HARD;
    }
-  if (responselen >= sizeof(response))
-    responselen = sizeof(response);
   responseend = response.buf + responselen;
   responsepos = response.buf + sizeof(HEADER);
!  n = ntohs(response.hdr.qdcount);
   while (n-- > 0)
    {
     i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
--- 47,79 ----
   errno = 0;
   if (!stralloc_copy(&glue,domain)) return DNS_MEM;
   if (!stralloc_0(&glue)) return DNS_MEM;
!  if (!responsebuflen)
!   if (response.buf = (unsigned char *)alloc(PACKETSZ+1))
!    responsebuflen = PACKETSZ+1;
!   else return DNS_MEM;
! 
!  responselen = lookup(glue.s,C_IN,type,response.buf,responsebuflen);
!  if ((responselen >= responsebuflen) ||
!      (responselen > 0 && (((HEADER *)response.buf)->tc)))
!   {
!    if (responsebuflen < 65536)
!     if (alloc_re(&response.buf, responsebuflen, 65536))
!      responsebuflen = 65536;
!     else return DNS_MEM;
!     saveresoptions = _res.options;
!     _res.options |= RES_USEVC;
!     responselen = lookup(glue.s,C_IN,type,response.buf,responsebuflen);
!     _res.options = saveresoptions;
!   }
   if (responselen <= 0)
    {
     if (errno == ECONNREFUSED) return DNS_SOFT;
     if (h_errno == TRY_AGAIN) return DNS_SOFT;
     return DNS_HARD;
    }
   responseend = response.buf + responselen;
   responsepos = response.buf + sizeof(HEADER);
!  n = ntohs(((HEADER *)response.buf)->qdcount);
   while (n-- > 0)
    {
     i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
***************
*** 66,72 ****
     if (i < QFIXEDSZ) return DNS_SOFT;
     responsepos += QFIXEDSZ;
    }
!  numanswers = ntohs(response.hdr.ancount);
   return 0;
  }
  
--- 83,89 ----
     if (i < QFIXEDSZ) return DNS_SOFT;
     responsepos += QFIXEDSZ;
    }
!  numanswers = ntohs(((HEADER *)response.buf)->ancount);
   return 0;
  }
  
*** qmail-smtpd.c.orig	Mon Jun 15 03:53:16 1998
--- qmail-smtpd.c	Mon Mar 26 17:14:29 2001
***************
*** 23,28 ****
--- 23,30 ----
  #include "timeoutread.h"
  #include "timeoutwrite.h"
  #include "commands.h"
+ #include "wait.h"
+ #include "fd.h"
  
  #define MAXHOPS 100
  unsigned int databytes = 0;
***************
*** 59,64 ****
--- 61,75 ----
  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"); }
  
+ int err_child() { out("454 oops, problem with child and I can't auth (#4.3.0)\r\n"); return -1; }
+ int err_fork() { out("454 oops, child won't start and I can't auth (#4.3.0)\r\n"); return -1; }
+ int err_pipe() { out("454 oops, unable to open pipe and I can't auth (#4.3.0)\r\n"); return -1; }
+ int err_write() { out("454 oops, unable to write pipe and I can't auth (#4.3.0)\r\n"); return -1; }
+ void err_authd() { out("503 you're already authenticated (#5.5.0)\r\n"); }
+ void err_authmail() { out("503 no auth during mail transaction (#5.5.0)\r\n"); }
+ int err_noauth() { out("504 auth type unimplemented (#5.5.1)\r\n"); return -1; }
+ int err_authabrt() { out("501 auth exchange cancelled (#5.0.0)\r\n"); return -1; }
+ int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; }
  
  stralloc greeting = {0};
  
***************
*** 96,101 ****
--- 107,114 ----
  int bmfok = 0;
  stralloc bmf = {0};
  struct constmap mapbmf;
+ int tarpitcount = 0;
+ int tarpitdelay = 5;
  
  void setup()
  {
***************
*** 110,115 ****
--- 123,136 ----
    if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control();
    if (timeout <= 0) timeout = 1;
  
+   if (control_readint(&tarpitcount,"control/tarpitcount") == -1) die_control();
+   if (tarpitcount < 0) tarpitcount = 0;
+   x = env_get("TARPITCOUNT");
+   if (x) { scan_ulong(x,&u); tarpitcount = u; };
+   if (control_readint(&tarpitdelay,"control/tarpitdelay") == -1) die_control();
+   if (tarpitdelay < 0) tarpitdelay = 0;
+   x = env_get("TARPITDELAY");
+   if (x) { scan_ulong(x,&u); tarpitdelay = u; };
    if (rcpthosts_init() == -1) die_control();
  
    bmfok = control_readfile(&bmf,"control/badmailfrom",0);
***************
*** 221,226 ****
--- 242,248 ----
  int flagbarf; /* defined if seenmail */
  stralloc mailfrom = {0};
  stralloc rcptto = {0};
+ int rcptcount;
  
  void smtp_helo(arg) char *arg;
  {
***************
*** 229,235 ****
  }
  void smtp_ehlo(arg) char *arg;
  {
!   smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
    seenmail = 0; dohelo(arg);
  }
  void smtp_rset()
--- 251,260 ----
  }
  void smtp_ehlo(arg) char *arg;
  {
!   smtp_greet("250-");
!   out("\r\n250-AUTH LOGIN CRAM-MD5 PLAIN");
!   out("\r\n250-AUTH=LOGIN CRAM-MD5 PLAIN");
!   out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
    seenmail = 0; dohelo(arg);
  }
  void smtp_rset()
***************
*** 245,250 ****
--- 270,276 ----
    if (!stralloc_copys(&rcptto,"")) die_nomem();
    if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();
    if (!stralloc_0(&mailfrom)) die_nomem();
+   rcptcount = 0;
    out("250 ok\r\n");
  }
  void smtp_rcpt(arg) char *arg; {
***************
*** 261,266 ****
--- 287,293 ----
    if (!stralloc_cats(&rcptto,"T")) die_nomem();
    if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
    if (!stralloc_0(&rcptto)) die_nomem();
+   if (tarpitcount && ++rcptcount >= tarpitcount) while (sleep(tarpitdelay)); 
    out("250 ok\r\n");
  }
  
***************
*** 394,403 ****
--- 421,640 ----
    out("\r\n");
  }
  
+ 
+ char unique[FMT_ULONG + FMT_ULONG + 3];
+ static stralloc authin = {0};
+ static stralloc user = {0};
+ static stralloc pass = {0};
+ static stralloc resp = {0};
+ static stralloc slop = {0};
+ char *hostname;
+ char **childargs;
+ substdio ssup;
+ char upbuf[128];
+ int authd = 0;
+ 
+ int authgetl(void) {
+   int i;
+ 
+   if (!stralloc_copys(&authin, "")) die_nomem();
+ 
+   for (;;) {
+     if (!stralloc_readyplus(&authin,1)) die_nomem(); /* XXX */
+     i = substdio_get(&ssin,authin.s + authin.len,1);
+     if (i != 1) die_read();
+     if (authin.s[authin.len] == '\n') break;
+     ++authin.len;
+   }
+ 
+   if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len;
+   authin.s[authin.len] = 0;
+ 
+   if (*authin.s == '*' && *(authin.s + 1) == 0) { return err_authabrt(); }
+   if (authin.len == 0) { return err_input(); }
+   return authin.len;
+ }
+ 
+ int authenticate(void)
+ {
+   int child;
+   int wstat;
+   int pi[2];
+ 
+   if (!stralloc_0(&user)) die_nomem();
+   if (!stralloc_0(&pass)) die_nomem();
+   if (!stralloc_0(&resp)) die_nomem();
+ 
+   if (fd_copy(2,1) == -1) return err_pipe();
+   close(3);
+   if (pipe(pi) == -1) return err_pipe();
+   if (pi[0] != 3) return err_pipe();
+   switch(child = fork()) {
+     case -1:
+       return err_fork();
+     case 0:
+       close(pi[1]);
+       sig_pipedefault();
+       execvp(*childargs, childargs);
+       _exit(1);
+   }
+   close(pi[0]);
+ 
+   substdio_fdbuf(&ssup,write,pi[1],upbuf,sizeof upbuf);
+   if (substdio_put(&ssup,user.s,user.len) == -1) return err_write();
+   if (substdio_put(&ssup,pass.s,pass.len) == -1) return err_write();
+   if (substdio_put(&ssup,resp.s,resp.len) == -1) return err_write();
+   if (substdio_flush(&ssup) == -1) return err_write();
+ 
+   close(pi[1]);
+   byte_zero(pass.s,pass.len);
+   byte_zero(upbuf,sizeof upbuf);
+   if (wait_pid(&wstat,child) == -1) return err_child();
+   if (wait_crashed(wstat)) return err_child();
+   if (wait_exitcode(wstat)) { sleep(5); return 1; } /* no */
+   return 0; /* yes */
+ }
+ 
+ int auth_login(arg) char *arg;
+ {
+   int r;
+ 
+   if (*arg) {
+     if (r = b64decode(arg,str_len(arg),&user) == 1) return err_input();
+   }
+   else {
+     out("334 VXNlcm5hbWU6\r\n"); flush(); /* Username: */
+     if (authgetl() < 0) return -1;
+     if (r = b64decode(authin.s,authin.len,&user) == 1) return err_input();
+   }
+   if (r == -1) die_nomem();
+ 
+   out("334 UGFzc3dvcmQ6\r\n"); flush(); /* Password: */
+ 
+   if (authgetl() < 0) return -1;
+   if (r = b64decode(authin.s,authin.len,&pass) == 1) return err_input();
+   if (r == -1) die_nomem();
+ 
+   if (!user.len || !pass.len) return err_input();
+   return authenticate();  
+ }
+ 
+ int auth_plain(arg) char *arg;
+ {
+   int r, id = 0;
+ 
+   if (*arg) {
+     if (r = b64decode(arg,str_len(arg),&slop) == 1) return err_input();
+   }
+   else {
+     out("334 ok, go on\r\n"); flush();
+     if (authgetl() < 0) return -1;
+     if (r = b64decode(authin.s,authin.len,&slop) == 1) return err_input();
+   }
+   if (r == -1 || !stralloc_0(&slop)) die_nomem();
+   while (slop.s[id]) id++; /* ignore authorize-id */
+ 
+   if (slop.len > id + 1)
+     if (!stralloc_copys(&user,slop.s + id + 1)) die_nomem();
+   if (slop.len > id + user.len + 2)
+     if (!stralloc_copys(&pass,slop.s + id + user.len + 2)) die_nomem();
+ 
+   if (!user.len || !pass.len) return err_input();
+   return authenticate();
+ }
+ 
+ int auth_cram()
+ {
+   int i, r;
+   char *s;
+ 
+   s = unique;
+   s += fmt_uint(s,getpid());
+   *s++ = '.';
+   s += fmt_ulong(s,(unsigned long) now());
+   *s++ = '@';
+   *s++ = 0;
+ 
+   if (!stralloc_copys(&pass,"<")) die_nomem();
+   if (!stralloc_cats(&pass,unique)) die_nomem();
+   if (!stralloc_cats(&pass,hostname)) die_nomem();
+   if (!stralloc_cats(&pass,">")) die_nomem();
+   if (b64encode(&pass,&slop) < 0) die_nomem();
+   if (!stralloc_0(&slop)) die_nomem();
+ 
+   out("334 ");
+   out(slop.s);
+   out("\r\n");
+   flush();
+ 
+   if (authgetl() < 0) return -1;
+   if (r = b64decode(authin.s,authin.len,&slop) == 1) return err_input();
+   if (r == -1 || !stralloc_0(&slop)) die_nomem();
+ 
+   i = str_chr(slop.s,' ');
+   s = slop.s + i;
+   while (*s == ' ') ++s;
+   slop.s[i] = 0;
+   if (!stralloc_copys(&user,slop.s)) die_nomem();
+   if (!stralloc_copys(&resp,s)) die_nomem();
+ 
+   if (!user.len || !resp.len) return err_input();
+   return authenticate();
+ }
+ 
+ struct authcmd {
+   char *text;
+   int (*fun)();
+ } authcmds[] = {
+   { "login", auth_login }
+ , { "plain", auth_plain }
+ , { "cram-md5", auth_cram }
+ , { 0, err_noauth }
+ };
+ 
+ void smtp_auth(arg)
+ char *arg;
+ {
+   int i;
+   char *cmd = arg;
+ 
+   if (!hostname || !*childargs)
+   {
+     out("503 auth not available (#5.3.3)\r\n");
+     return;
+   }
+   if (authd) { err_authd(); return; }
+   if (seenmail) { err_authmail(); return; }
+ 
+   if (!stralloc_copys(&user,"")) die_nomem();
+   if (!stralloc_copys(&pass,"")) die_nomem();
+   if (!stralloc_copys(&resp,"")) die_nomem();
+ 
+   i = str_chr(cmd,' ');   
+   arg = cmd + i;
+   while (*arg == ' ') ++arg;
+   cmd[i] = 0;
+ 
+   for (i = 0;authcmds[i].text;++i)
+     if (case_equals(authcmds[i].text,cmd)) break;
+ 
+   switch (authcmds[i].fun(arg)) {
+     case 0:
+       authd = 1;
+       relayclient = "";
+       remoteinfo = user.s;
+       out("235 ok, go ahead (#2.0.0)\r\n");
+       break;
+     case 1:
+       out("535 authorization failed (#5.7.0)\r\n");
+   }
+ }
+ 
  struct commands smtpcommands[] = {
    { "rcpt", smtp_rcpt, 0 }
  , { "mail", smtp_mail, 0 }
  , { "data", smtp_data, flush }
+ , { "auth", smtp_auth, flush }
  , { "quit", smtp_quit, flush }
  , { "helo", smtp_helo, flush }
  , { "ehlo", smtp_ehlo, flush }
***************
*** 408,415 ****
  , { 0, err_unimpl, flush }
  } ;
  
! void main()
  {
    sig_pipeignore();
    if (chdir(auto_qmail) == -1) die_control();
    setup();
--- 645,657 ----
  , { 0, err_unimpl, flush }
  } ;
  
! void main(argc,argv)
! int argc;
! char **argv;
  {
+   hostname = argv[1];
+   childargs = argv + 2;
+ 
    sig_pipeignore();
    if (chdir(auto_qmail) == -1) die_control();
    setup();
*** qmail-smtpd.8.orig	Mon Jun 15 03:53:16 1998
--- qmail-smtpd.8	Mon Mar 26 17:20:20 2001
***************
*** 3,8 ****
--- 3,13 ----
  qmail-smtpd \- receive mail via SMTP
  .SH SYNOPSIS
  .B qmail-smtpd
+ [
+ .I hostname
+ .I checkprogram
+ .I subprogram
+ ]
  .SH DESCRIPTION
  .B qmail-smtpd
  receives mail messages via the Simple Mail Transfer Protocol (SMTP)
***************
*** 23,29 ****
  header fields.
  
  .B qmail-smtpd
! supports ESMTP, including the 8BITMIME and PIPELINING options.
  .SH TRANSPARENCY
  .B qmail-smtpd
  converts the SMTP newline convention into the UNIX newline convention
--- 28,56 ----
  header fields.
  
  .B qmail-smtpd
! supports ESMTP, including the 8BITMIME, PIPELINING, and AUTH options.
! 
! .B qmail-smtpd
! can accept LOGIN, PLAIN, and CRAM-MD5 AUTH types.  It invokes
! .IR checkprogram ,
! which reads on file descriptor 3 the username, a 0 byte, the password
! or challenge derived from
! .IR hostname ,
! another 0 byte, a CRAM-MD5 response (if applicable to the AUTH type),
! and a final 0 byte.
! .I checkprogram
! invokes
! .I subprogram
! upon successful authentication, which should in turn return 0 to
! .BR qmail-smtpd ,
! effectively setting the environment variables RELAYCLIENT and TCPREMOTEINFO
! (any supplied value replaced with the authenticated username).
! .B qmail-smtpd
! will reject the authentication attempt if it receives a nonzero return
! value from
! .I checkprogram
! or
! .IR subprogram .
  .SH TRANSPARENCY
  .B qmail-smtpd
  converts the SMTP newline convention into the UNIX newline convention
***************
*** 177,179 ****
--- 204,209 ----
  qmail-newmrh(8),
  qmail-queue(8),
  qmail-remote(8)
+ .SH "HISTORY"
+ The patch enabling the ESMTP AUTH option is not part of the standard
+ qmail-1.03 distribution.
*** base64.c.orig	Mon Mar 26 17:31:42 2001
--- base64.c	Mon Mar 26 17:30:34 2001
***************
*** 0 ****
--- 1,90 ----
+ #include "base64.h"
+ #include "stralloc.h"
+ #include "substdio.h"
+ #include "str.h"
+ 
+ static char *b64alpha =
+   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ #define B64PAD '='
+ 
+ /* returns 0 ok, 1 illegal, -1 problem */
+ 
+ int b64decode(in,l,out)
+ const unsigned char *in;
+ int l;
+ stralloc *out; /* not null terminated */
+ {
+   int i, j;
+   unsigned char a[4];
+   unsigned char b[3];
+   char *s;
+ 
+   if (l == 0)
+   {
+     if (!stralloc_copys(out,"")) return -1;
+     return 0;
+   }
+ 
+   if (!stralloc_ready(out,l + 2)) return -1; /* XXX generous */
+   s = out->s;
+ 
+   for (i = 0;i < l;i += 4) {
+     for (j = 0;j < 4;j++)
+       if ((i + j) < l && in[i + j] != B64PAD)
+       {
+         a[j] = str_chr(b64alpha,in[i + j]);
+         if (a[j] > 63) return 1;
+       }
+       else a[j] = 0;
+ 
+     b[0] = (a[0] << 2) | (a[1] >> 4);
+     b[1] = (a[1] << 4) | (a[2] >> 2);
+     b[2] = (a[2] << 6) | (a[3]);
+ 
+     *s++ = b[0];
+ 
+     if (in[i + 1] == B64PAD) break;
+     *s++ = b[1];
+ 
+     if (in[i + 2] == B64PAD) break;
+     *s++ = b[2];
+   }
+   out->len = s - out->s;
+   while (out->len && !out->s[out->len - 1]) --out->len; /* XXX avoid? */
+   return 0;
+ }
+ 
+ int b64encode(in,out)
+ stralloc *in;
+ stralloc *out; /* not null terminated */
+ {
+   unsigned char a, b, c;
+   int i;
+   char *s;
+ 
+   if (in->len == 0)
+   {
+     if (!stralloc_copys(out,"")) return -1;
+     return 0;
+   }
+ 
+   if (!stralloc_ready(out,in->len / 3 * 4 + 4)) return -1;
+   s = out->s;
+ 
+   for (i = 0;i < in->len;i += 3) {
+     a = in->s[i];
+     b = i + 1 < in->len ? in->s[i + 1] : 0;
+     c = i + 2 < in->len ? in->s[i + 2] : 0;
+ 
+     *s++ = b64alpha[a >> 2];
+     *s++ = b64alpha[((a & 3 ) << 4) | (b >> 4)];
+ 
+     if (i + 1 >= in->len) *s++ = B64PAD;
+     else *s++ = b64alpha[((b & 15) << 2) | (c >> 6)];
+ 
+     if (i + 2 >= in->len) *s++ = B64PAD;
+     else *s++ = b64alpha[c & 63];
+   }
+   out->len = s - out->s;
+   return 0;
+ }
*** base64.h.orig	Mon Mar 26 17:31:45 2001
--- base64.h	Mon Mar 26 17:30:37 2001
***************
*** 0 ****
--- 1,7 ----
+ #ifndef BASE64_H
+ #define BASE64_H
+ 
+ extern int b64decode();
+ extern int b64encode();
+ 
+ #endif
*** conf-groups.orig	Mon Mar 26 17:36:43 2001
--- conf-groups	Mon Mar 26 17:36:52 2001
***************
*** 1,5 ****
  qmail
! nofiles
  
  These are the qmail groups. The second group should not have access to
  any files, but it must be usable for processes; this requirement
--- 1,5 ----
  qmail
! qnofiles
  
  These are the qmail groups. The second group should not have access to
  any files, but it must be usable for processes; this requirement
*** README.auth.orig	Mon Mar 26 17:43:28 2001
--- README.auth	Mon Mar 26 17:43:14 2001
***************
*** 0 ****
--- 1,90 ----
+ qmail-smtpd AUTH patch
+ 20010105
+ Eric M. Johnston, emj@postal.net
+ 
+ This patch adds the ESMTP AUTH option to qmail-1.03, allowing the
+ LOGIN, PLAIN, and CRAM-MD5 AUTH types. An appropriate checkpassword
+ tool is necessary to support the authentication.  See
+ http://cr.yp.to/checkpwd.html for more information on the interface.
+ Note that the checkpassword tool should support all of the AUTH types
+ advertised by qmail-smtpd.
+ 
+ As reflected in the modified qmail-smtpd(8) man page, qmail-smtpd must
+ be invoked with three arguments: hostname, checkprogram, and
+ subprogram.  If these arguments are missing, qmail-smtpd will still
+ advertise availability of AUTH, but will fail with a permanent error
+ when AUTH is used.
+ 
+ hostname is simply used to form the CRAM-MD5 challenge.  qmail-smtpd
+ invokes checkprogram, feeding it the username and password, in the
+ case of LOGIN or PLAIN, or the username, challenge, and response, in
+ the case of CRAM-MD5.  If the user checks out, checkprogram invokes
+ subprogram, which just has to exit with a status of 0 for the user to
+ be authenticated.  Otherwise, checkprogram exits with a non-zero
+ status.  subprogram can usually be /usr/bin/true (or /bin/true,
+ depending on your flavor of OS).
+ 
+ If the user is successfully authenticated, the RELAYCLIENT
+ environment variable is effectively set for the SMTP session, and
+ the TCPREMOTEINFO environment variable is effectively set to the
+ authenticated username, overriding any value that tcpserver may have
+ set.  The value of TCPREMOTEINFO is reflected in a Received header.
+ 
+ 
+ Caveats
+ 
+ Please note that as authentication needs vary wildly across
+ installations, no effort has been made to make this patch work ``out
+ of the box.''  You'll have to procure or develop your own
+ checkpassword program.  Also note that CRAM-MD5 will require you to
+ keep plaintext passwords.  You'll probably want to disable this AUTH
+ type if you're just using /etc/passwd (keeping in mind that PLAIN and
+ LOGIN aren't quite as safe over the wire).
+ 
+ You may be interested in Krzysztof Dabrowski's cmd5checkpw tool at
+ http://www.elysium.pl/members/brush/cmd5checkpw/ which supports the
+ three AUTH types included in this patch.
+ 
+ This patch has been generated against the stock qmail 1.03
+ distribution.  The results of combining this patch with others are
+ unknown.
+ 
+ 
+ Compatibility
+ 
+ MUAs and AUTH types tested with this software are:
+ 
+ Netscape Communicator 4.76 - LOGIN & PLAIN
+ Microsoft Outlook 2000 - LOGIN
+ Microsoft Outlook Express 5 - LOGIN
+ Eudora 5.0.2 - CRAM-MD5
+ 
+ Please report bugs, incompatibilities, successes, etc. to
+ emj@postal.net.  The latest version of this software will be posted
+ (Webmaster willing) at http://www.qmail.org/.
+ 
+ 
+ Acknowledgments
+ 
+ This patch is based on work by Krzysztof Dabrowski at
+ http://members.elysium.pl/brush/qmail-smtpd-auth/ and ``Mrs. Brisby''
+ at http://www.nimh.org/hacks/qmail-smtpd.c.
+ 
+ 
+ Changes
+ 
+ 20010105: initial release
+ 
+ ---
+ 
+ THIS SOFTWARE IS IN THE PUBLIC DOMAIN, IS PROVIDED BY THE AUTHOR
+ ``AS IS,'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*** README.tarpit.orig	Mon Mar 26 18:16:18 2001
--- README.tarpit	Mon Mar 26 18:16:10 2001
***************
*** 0 ****
--- 1,25 ----
+ Chris Johnson
+ cjohnson-qmail@palomine.net
+ 
+ What's tarpitting? It's the practice of inserting a small sleep in an SMTP
+ session for each RCPT TO after some set number of RCPT TOs. The idea is to
+ thwart spammers who would hand your SMTP server a single message with a long
+ list of RCPT TOs. If a spammer were to attempt to use your server to relay a
+ message with, say, 10,000 recipients, and you inserted a five-second delay for
+ each recipient after the fiftieth, the spammer would be "tarpitted" and would
+ likely assume that his connection had stalled and give up.
+ 
+ The subject originally came up in a discussion on the qmail mailing list of
+ ways to run an open relay safely (I didn't suggest it, and I don't do that kind
+ of thing), but it could also be useful in keeping your own dial-up customers
+ from using you as a spam relay.
+ 
+ This patch will allow qmail-smtpd to do tarpitting. There are two control files
+ involved: control/tarpitcount and control/tarpitdelay. tarpitcount is the
+ number of RCPT TOs you accept before you start tarpitting, and tarpitdelay is
+ the number of seconds of delay to introduce after each subsequent RCPT TO.
+ tarpitcount defaults to 0 (which means no tarpitting), and tarpitdelay defaults
+ to 5. You can override both tarpitcount and tarpitdelay by setting TARPITCOUNT
+ and TARPITDELAY in qmail-smtpd's environment (with tcpserver). If you used the
+ earlier version of this patch, note that this version no longer uses the
+ NOTARPIT environment variable; set TARPITCOUNT to 0 to achieve the same effect.
*** chkspawn.c.orig	Mon Jun 15 03:53:16 1998
--- chkspawn.c	Mon Mar 26 18:45:04 2001
***************
*** 22,29 ****
      _exit(1);
    }
  
!   if (auto_spawn > 255) {
!     substdio_puts(subfderr,"Oops. You have set conf-spawn higher than 255.\n");
      substdio_flush(subfderr);
      _exit(1);
    }
--- 22,29 ----
      _exit(1);
    }
  
!   if (auto_spawn > 65000) {
!     substdio_puts(subfderr,"Oops. You have set conf-spawn higher than 65000.\n");
      substdio_flush(subfderr);
      _exit(1);
    }
*** conf-spawn.orig	Mon Jun 15 03:53:16 1998
--- conf-spawn	Mon Mar 26 18:45:04 2001
***************
*** 1,4 ****
! 120
  
  This is a silent concurrency limit. You can't set it above 255. On some
  systems you can't set it above 125. qmail will refuse to compile if the
--- 1,4 ----
! 400
  
  This is a silent concurrency limit. You can't set it above 255. On some
  systems you can't set it above 125. qmail will refuse to compile if the
*** conf-split.orig	Mon Mar 26 19:03:31 2001
--- conf-split	Mon Mar 26 19:03:48 2001
***************
*** 1,3 ****
! 23
  
  This is the queue subdirectory split.
--- 1,3 ----
! 239
  
  This is the queue subdirectory split.
*** qmail-send.c.orig	Mon Jun 15 03:53:16 1998
--- qmail-send.c	Mon Mar 26 18:59:31 2001
***************
*** 262,267 ****
--- 262,269 ----
   while (!stralloc_copys(&comm_buf[c],"")) nomem();
   ch = delnum;
   while (!stralloc_append(&comm_buf[c],&ch)) nomem();
+  ch = delnum >> 8;
+  while (!stralloc_append(&comm_buf[c],&ch)) nomem();
   fnmake_split(id);
   while (!stralloc_cats(&comm_buf[c],fn.s)) nomem();
   while (!stralloc_0(&comm_buf[c])) nomem();
***************
*** 906,946 ****
       dline[c].len = REPORTMAX;
       /* qmail-lspawn and qmail-rspawn are responsible for keeping it short */
       /* but from a security point of view, we don't trust rspawn */
!    if (!ch && (dline[c].len > 1))
      {
       delnum = (unsigned int) (unsigned char) dline[c].s[0];
       if ((delnum < 0) || (delnum >= concurrency[c]) || !d[c][delnum].used)
         log1("warning: internal error: delivery report out of range\n");
       else
        {
         strnum3[fmt_ulong(strnum3,d[c][delnum].delid)] = 0;
!        if (dline[c].s[1] == 'Z')
  	 if (jo[d[c][delnum].j].flagdying)
  	  {
! 	   dline[c].s[1] = 'D';
  	   --dline[c].len;
  	   while (!stralloc_cats(&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem();
  	   while (!stralloc_0(&dline[c])) nomem();
  	  }
!        switch(dline[c].s[1])
  	{
  	 case 'K':
  	   log3("delivery ",strnum3,": success: ");
! 	   logsafe(dline[c].s + 2);
  	   log1("\n");
  	   markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos);
  	   --jo[d[c][delnum].j].numtodo;
  	   break;
  	 case 'Z':
  	   log3("delivery ",strnum3,": deferral: ");
! 	   logsafe(dline[c].s + 2);
  	   log1("\n");
  	   break;
  	 case 'D':
  	   log3("delivery ",strnum3,": failure: ");
! 	   logsafe(dline[c].s + 2);
  	   log1("\n");
! 	   addbounce(jo[d[c][delnum].j].id,d[c][delnum].recip.s,dline[c].s + 2);
  	   markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos);
  	   --jo[d[c][delnum].j].numtodo;
  	   break;
--- 908,949 ----
       dline[c].len = REPORTMAX;
       /* qmail-lspawn and qmail-rspawn are responsible for keeping it short */
       /* but from a security point of view, we don't trust rspawn */
!    if (!ch && (dline[c].len > 2))
      {
       delnum = (unsigned int) (unsigned char) dline[c].s[0];
+      delnum += (unsigned int) ((unsigned int) dline[c].s[1]) << 8;
       if ((delnum < 0) || (delnum >= concurrency[c]) || !d[c][delnum].used)
         log1("warning: internal error: delivery report out of range\n");
       else
        {
         strnum3[fmt_ulong(strnum3,d[c][delnum].delid)] = 0;
!        if (dline[c].s[2] == 'Z')
  	 if (jo[d[c][delnum].j].flagdying)
  	  {
! 	   dline[c].s[2] = 'D';
  	   --dline[c].len;
  	   while (!stralloc_cats(&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem();
  	   while (!stralloc_0(&dline[c])) nomem();
  	  }
!        switch(dline[c].s[2])
  	{
  	 case 'K':
  	   log3("delivery ",strnum3,": success: ");
! 	   logsafe(dline[c].s + 3);
  	   log1("\n");
  	   markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos);
  	   --jo[d[c][delnum].j].numtodo;
  	   break;
  	 case 'Z':
  	   log3("delivery ",strnum3,": deferral: ");
! 	   logsafe(dline[c].s + 3);
  	   log1("\n");
  	   break;
  	 case 'D':
  	   log3("delivery ",strnum3,": failure: ");
! 	   logsafe(dline[c].s + 3);
  	   log1("\n");
! 	   addbounce(jo[d[c][delnum].j].id,d[c][delnum].recip.s,dline[c].s + 3);
  	   markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos);
  	   --jo[d[c][delnum].j].numtodo;
  	   break;
***************
*** 1544,1550 ****
   numjobs = 0;
   for (c = 0;c < CHANNELS;++c)
    {
!    char ch;
     int u;
     int r;
     do
--- 1547,1553 ----
   numjobs = 0;
   for (c = 0;c < CHANNELS;++c)
    {
!    char ch, ch1;
     int u;
     int r;
     do
***************
*** 1552,1558 ****
--- 1555,1567 ----
     while ((r == -1) && (errno == error_intr));
     if (r < 1)
      { log1("alert: cannot start: hath the daemon spawn no fire?\n"); _exit(111); }
+    do
+      r = read(chanfdin[c],&ch1,1);
+    while ((r == -1) && (errno == error_intr));
+    if (r < 1)
+     { log1("alert: cannot start: hath the daemon spawn no fire?\n"); _exit(111); }
     u = (unsigned int) (unsigned char) ch;
+    u += (unsigned int) ((unsigned char) ch1) << 8;
     if (concurrency[c] > u) concurrency[c] = u;
     numjobs += concurrency[c];
    }
*** spawn.c.orig	Mon Jun 15 03:53:16 1998
--- spawn.c	Mon Mar 26 18:52:33 2001
***************
*** 63,69 ****
  int flagreading = 1;
  char outbuf[1024]; substdio ssout;
  
! int stage = 0; /* reading 0:delnum 1:messid 2:sender 3:recip */
  int flagabort = 0; /* if 1, everything except delnum is garbage */
  int delnum;
  stralloc messid = {0};
--- 63,69 ----
  int flagreading = 1;
  char outbuf[1024]; substdio ssout;
  
! int stage = 0; /* reading 0:delnum 1:delnum2 2:messid 3:sender 4:recip */
  int flagabort = 0; /* if 1, everything except delnum is garbage */
  int delnum;
  stralloc messid = {0};
***************
*** 73,78 ****
--- 73,79 ----
  void err(s) char *s;
  {
   char ch; ch = delnum; substdio_put(&ssout,&ch,1);
+  ch = delnum >> 8; substdio_put(&ssout,&ch,1);
   substdio_puts(&ssout,s); substdio_putflush(&ssout,"",1);
  }
  
***************
*** 155,170 ****
      {
       case 0:
         delnum = (unsigned int) (unsigned char) ch;
!        messid.len = 0; stage = 1; break;
       case 1:
         if (!stralloc_append(&messid,&ch)) flagabort = 1;
         if (ch) break;
!        sender.len = 0; stage = 2; break;
!      case 2:
         if (!stralloc_append(&sender,&ch)) flagabort = 1;
         if (ch) break;
!        recip.len = 0; stage = 3; break;
!      case 3:
         if (!stralloc_append(&recip,&ch)) flagabort = 1;
         if (ch) break;
         docmd();
--- 156,174 ----
      {
       case 0:
         delnum = (unsigned int) (unsigned char) ch;
!        stage = 1; break;
       case 1:
+        delnum += (unsigned int) ((unsigned int) ch) << 8;
+        messid.len = 0; stage = 2; break;
+      case 2:
         if (!stralloc_append(&messid,&ch)) flagabort = 1;
         if (ch) break;
!        sender.len = 0; stage = 3; break;
!      case 3:
         if (!stralloc_append(&sender,&ch)) flagabort = 1;
         if (ch) break;
!        recip.len = 0; stage = 4; break;
!      case 4:
         if (!stralloc_append(&recip,&ch)) flagabort = 1;
         if (ch) break;
         docmd();
***************
*** 201,207 ****
  
   initialize(argc,argv);
  
!  ch = auto_spawn; substdio_putflush(&ssout,&ch,1);
  
   for (i = 0;i < auto_spawn;++i) { d[i].used = 0; d[i].output.s = 0; }
  
--- 205,212 ----
  
   initialize(argc,argv);
  
!  ch = auto_spawn; substdio_put(&ssout,&ch,1);
!  ch = auto_spawn >> 8; substdio_putflush(&ssout,&ch,1);
  
   for (i = 0;i < auto_spawn;++i) { d[i].used = 0; d[i].output.s = 0; }
  
***************
*** 236,242 ****
  	   continue; /* read error on a readable pipe? be serious */
  	 if (r == 0)
  	  {
!            ch = i; substdio_put(&ssout,&ch,1);
  	   report(&ssout,d[i].wstat,d[i].output.s,d[i].output.len);
  	   substdio_put(&ssout,"",1);
  	   substdio_flush(&ssout);
--- 241,248 ----
  	   continue; /* read error on a readable pipe? be serious */
  	 if (r == 0)
  	  {
!            char ch; ch = i; substdio_put(&ssout,&ch,1);
!            ch = i >> 8; substdio_put(&ssout,&ch,1);
  	   report(&ssout,d[i].wstat,d[i].output.s,d[i].output.len);
  	   substdio_put(&ssout,"",1);
  	   substdio_flush(&ssout);
*** README.big-concurrency.orig	Mon Mar 26 19:21:26 2001
--- README.big-concurrency	Mon Mar 26 19:21:15 2001
***************
*** 0 ****
--- 1,73 ----
+ From: "Johannes Erdfelt" <jerdfelt@suse.com>
+ To: qmail@list.cr.yp.to
+ Subject: Re: mail volume
+ Date: 4 Aug 1999 20:41:00 -0700
+ 
+ On Thu, Aug 05, 1999, richard@illuin.demon.co.uk <richard@illuin.demon.co.uk> wrote:
+ > On Wed, 4 Aug 1999, Daemeon Reiydelle wrote:
+ > 
+ > > (2.6 or later). There may be limitations within e.g. qmail-[lr]spawn
+ > > about how many children it can manage. I am not working with that code
+ > > right know so I don't know. Anyone?
+ > 
+ > This is what people have been trying to say -- the protocol between
+ > qmail-Xspawn and qmail-send only passes a single byte for the delivery
+ > attempt back in the status messages. if you want to increase the maximum
+ > number above 256 one has to modify qmail-send and the common code in
+ > qmail-Xspawn. making it a short should allow up to 2**16 concurrency
+ > remotes.
+ > 
+ > **CAUTION** if you do this one should realise that qmail-send might try to
+ > open 64K connections to the /same/ host because it doesn't maintain a
+ > per-domain concurrency. this is distinctly Unfriendly. I produced some
+ > code for qmail to do this, but when I asked my ISP if i could open >>1024
+ > connections to one of their mail relays for testing they were less than
+ > enthusiastic... (the code is on my desktop system somewhere between here
+ > and Austin where I'm moving to next week, so I can't email it, and without
+ > testing it I won't email it. the changes to up the concurrency are fairly 
+ > straightforward, the once for a per-domain concurrency are non-trivial)
+ 
+ This is the patch that I use at suse.com. We do almost 1 million
+ messages a day with this patch and concurrencyremote set to 400.
+ 
+ This patch comes with the standard disclaimer. No warranty, it may not
+ work, etc. But it works for me :)
+ 
+ It's also not pretty. It's against qmail-1.03+verh-0.02 (the ezmlm patch
+ l and h patch). So the offsets may be off a little bit.
+ 
+ JE
+ 
+ 
+ From: "Fred Lindberg" <lindberg@id.wustl.edu>
+ To: "nelson@crynwr.com" <nelson@crynwr.com>
+ Cc: "qmail@list.cr.yp.to" <qmail@list.cr.yp.to>
+ Subject: Solved: qmail-bigrem limits linux
+ Date: Thu, 12 Aug 1999 13:41:18 -0500
+ 
+ Dear Russell,
+ 
+ Thanks for your input. The limiting factor for my redhat linux 2.2.5
+ installation was a per user process limit of 256. The reason I
+ sometimes got 256 or 257 concurrency is that reporting is not exactly
+ synchronous with forking.
+ 
+ Thus, in addition to increasing the number of file handles, you need to
+ rebuild the kernel after editing:
+ 
+ /usr/src/linux/include/linux/tasks.h NR_TASKS from 512 to e.g. 2048.
+ Per-user limit defined to half this on the line below.
+ 
+ There also appears to be a bug that limits the number of per process
+ file handles to 1024. There are "unofficial" patches for this, but
+ AFAIK, not integrated into the official kernel.
+ 
+ If anyone has more insight into this, I'd love to hear it. With the
+ patch, our P100/64MB does very well at a concurrencyremote of 400.
+ Since performance was limited by concurrency (many slow/dead clients)
+ we got considerably better throughput.
+ 
+ 
+ -Sincerely, Fred
+ 
+ (Frederik Lindberg, Infectious Diseases, WashU, St. Louis, MO, USA)
