lukasfeiler.com-bigqmail-20080210.patch
=======================================

This is a combined patch. It consists of the following patches:
- netqmail-1.05-tls-smtpauth-20070417.patch
- validrcptto.cdb patch
- big-concurrency.patch
- doublebounce-trim.patch
- big-ext-todo (EXTTODO patch & big-todo patch)
- outgoingips.patch
- dns patch to handle DNS responses larger than 512 bytes

Slight modifications were necessary to make all the patches go together.

Here is a short description of each patch:
- netqmail-1.05-tls-smtpauth (written by Bill Shupp)
  implements SMTP-Auth over TLS (remained unchanged)
  see http://shupp.org/smtp-auth-tls/
- validrcptto.cdb patch (written by John Simpson)
  by verifying RCPT TOs at SMTP time you can dramatically reduce
  backscatter spam; see http://qmail.jms1.net/patches/validrcptto.cdb.shtml
- big-concurrency.patch (written by Johannes Erdfelt/SUSE)
  removes the 120 concurrency limit (setting it to 65536)
  http://qmail.org/big-concurrency.patch
- doublebounce-trim.patch (written by Charles Cazabon)
  eliminates double bounces if /var/qmail/control/doublebounceto
  exists but contains a blank line
  see http://www.qmail.org/doublebounce-trim.patch
- big-ext-todo (EXTTODO patch & big-todo patch)
  This patch is a combination of two patches that address
  two different performance problems contained in qmail.
  * Andr Oppermann's EXTTODO patch fixes the "silly qmail syndrome"
    see http://www.nrg4u.com/qmail/ext_todo-20030105.patch and
    http://qmail.jms1.net/silly-qmail.shtml for some background information
  * the big-todo patch (written by Russell Nelson and updated by Dave Smith)
    modifies qmail-queue and qmail-send to support a hashed directory
    structure for the intd and todo subdirectories just like the
    mess/local/remote/info directories
    see http://www.qmail.org/big-todo.103.patch
- Alberto Brealey-Guzman's outgoingips patch allows one to define
  on a per virtual-domain basis the IP to be used by qmail-remote.
  Please see http://rno-consultores.com./mail/qmail/qmail-1.03_outgoingips.patch
- Christopher K. Davis' DNS patch handles a problem qmail has with DNS responses
  larger than the standard 512 bytes. Please see
  http://www.ckdhr.com/ckd/qmail-103.patch and
  http://web.infoave.net/~dsill/lwq.html#dns-patches
  for further information.

Changes since lukasfeiler.com-bigqmail-20070526.patch:
- netqmail-1.05-tls-smtpauth.patch: using the new
  20070417 instead of 20060105 version
- outgoingips patch: was not included in prior releases
- Christopher K. Davis' DNS patch: was not included in prior releases


Apply the patch like this:

wget http://qmail.org/netqmail-1.05.tar.gz
tar -xzf netqmail-1.05.tar.gz
cd netqmail-1.05
./collate.sh
cd netqmail-1.05
patch < ../../lukasfeiler.com-bigqmail-20080210.patch
# set the concurrency limit as needed (1000 might be too high)
vi conf-spawn

make
make setup check
make cert
make tmprsadh


diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/base64.c ./base64.c
--- ../../netqmail-1.05-orig/netqmail-1.05/base64.c	1969-12-31 19:00:00.000000000 -0500
+++ ./base64.c	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,124 @@
+#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 p = 0;
+  int n;
+  unsigned int x;
+  int i, j;
+  char *s;
+  unsigned char b[3];
+
+  if (l == 0)
+  {
+    if (!stralloc_copys(out,"")) return -1;
+    return 0;
+  }
+
+  while(in[l-1] == B64PAD) {
+    p ++;
+    l--;
+  }
+
+  n = (l + p) / 4;
+  i = (n * 3) - p;
+  if (!stralloc_ready(out,i)) return -1;
+  out->len = i;
+  s = out->s;
+
+  for(i = 0; i < n - 1 ; i++) {
+    x = 0;
+    for(j = 0; j < 4; j++) {
+      if(in[j] >= 'A' && in[j] <= 'Z')
+        x = (x << 6) + (unsigned int)(in[j] - 'A' + 0);
+      else if(in[j] >= 'a' && in[j] <= 'z')
+        x = (x << 6) + (unsigned int)(in[j] - 'a' + 26);
+      else if(in[j] >= '0' && in[j] <= '9')
+        x = (x << 6) + (unsigned int)(in[j] - '0' + 52);
+      else if(in[j] == '+')
+        x = (x << 6) + 62;
+      else if(in[j] == '/')
+        x = (x << 6) + 63;
+      else if(in[j] == '=')
+        x = (x << 6);
+    }
+
+    s[2] = (unsigned char)(x & 255); x >>= 8;
+    s[1] = (unsigned char)(x & 255); x >>= 8;
+    s[0] = (unsigned char)(x & 255); x >>= 8;
+    s += 3; in += 4;
+  }
+
+  x = 0;
+  for(j = 0; j < 4; j++) {
+    if(in[j] >= 'A' && in[j] <= 'Z')
+      x = (x << 6) + (unsigned int)(in[j] - 'A' + 0);
+    else if(in[j] >= 'a' && in[j] <= 'z')
+      x = (x << 6) + (unsigned int)(in[j] - 'a' + 26);
+    else if(in[j] >= '0' && in[j] <= '9')
+      x = (x << 6) + (unsigned int)(in[j] - '0' + 52);
+    else if(in[j] == '+')
+      x = (x << 6) + 62;
+    else if(in[j] == '/')
+      x = (x << 6) + 63;
+    else if(in[j] == '=')
+      x = (x << 6);
+  }
+
+  b[2] = (unsigned char)(x & 255); x >>= 8;
+  b[1] = (unsigned char)(x & 255); x >>= 8;
+  b[0] = (unsigned char)(x & 255); x >>= 8;
+
+  for(i = 0; i < 3 - p; i++)
+    s[i] = b[i];
+
+  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;
+  }
+
+  i = in->len / 3 * 4 + 4;   
+  if (!stralloc_ready(out,i)) 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;
+}
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/base64.h ./base64.h
--- ../../netqmail-1.05-orig/netqmail-1.05/base64.h	1969-12-31 19:00:00.000000000 -0500
+++ ./base64.h	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,7 @@
+#ifndef BASE64_H
+#define BASE64_H
+
+extern int b64decode();
+extern int b64encode();
+
+#endif
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/case_startb.c ./case_startb.c
--- ../../netqmail-1.05-orig/netqmail-1.05/case_startb.c	1969-12-31 19:00:00.000000000 -0500
+++ ./case_startb.c	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,21 @@
+#include "case.h"
+
+int case_startb(s,len,t)
+register char *s;
+unsigned int len;
+register char *t;
+{
+  register unsigned char x;
+  register unsigned char y;
+
+  for (;;) {
+    y = *t++ - 'A';
+    if (y <= 'Z' - 'A') y += 'a'; else y += 'A';
+    if (!y) return 1;
+    if (!len) return 0;
+    --len;
+    x = *s++ - 'A';
+    if (x <= 'Z' - 'A') x += 'a'; else x += 'A';
+    if (x != y) return 0;
+  }
+}
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/chkspawn.c ./chkspawn.c
--- ../../netqmail-1.05-orig/netqmail-1.05/chkspawn.c	1998-06-15 06:53:16.000000000 -0400
+++ ./chkspawn.c	2008-02-10 08:29:48.000000000 -0500
@@ -22,8 +22,8 @@
     _exit(1);
   }
 
-  if (auto_spawn > 255) {
-    substdio_puts(subfderr,"Oops. You have set conf-spawn higher than 255.\n");
+  if (auto_spawn > 65000) {
+    substdio_puts(subfderr,"Oops. You have set conf-spawn higher than 65000.\n");
     substdio_flush(subfderr);
     _exit(1);
   }
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/conf-cc ./conf-cc
--- ../../netqmail-1.05-orig/netqmail-1.05/conf-cc	1998-06-15 06:53:16.000000000 -0400
+++ ./conf-cc	2008-02-10 08:29:47.000000000 -0500
@@ -1,3 +1,3 @@
-cc -O2
+cc -O2 -DTLS=20070408 -I/usr/local/ssl/include
 
 This will be used to compile .c files.
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/conf-spawn ./conf-spawn
--- ../../netqmail-1.05-orig/netqmail-1.05/conf-spawn	1998-06-15 06:53:16.000000000 -0400
+++ ./conf-spawn	2008-02-10 08:29:48.000000000 -0500
@@ -1,4 +1,4 @@
-120
+1000
 
 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
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/dns.c ./dns.c
--- ../../netqmail-1.05-orig/netqmail-1.05/dns.c	2008-02-10 08:30:51.000000000 -0500
+++ ./dns.c	2008-02-10 08:29:53.000000000 -0500
@@ -19,10 +19,12 @@
 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 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];
@@ -43,18 +45,33 @@
  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 (!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;
   }
- if (responselen >= sizeof(response))
-   responselen = sizeof(response);
  responseend = response.buf + responselen;
  responsepos = response.buf + sizeof(HEADER);
- n = ntohs(response.hdr.qdcount);
+ n = ntohs(((HEADER *)response.buf)->qdcount);
  while (n-- > 0)
   {
    i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
@@ -64,7 +81,7 @@
    if (i < QFIXEDSZ) return DNS_SOFT;
    responsepos += QFIXEDSZ;
   }
- numanswers = ntohs(response.hdr.ancount);
+ numanswers = ntohs(((HEADER *)response.buf)->ancount);
  return 0;
 }
 
@@ -267,12 +284,11 @@
 int pref;
 {
  int r;
- struct ip_mx ix;
+ struct ip_mx ix = {0};
 
  if (!stralloc_copy(&glue,sa)) return DNS_MEM;
  if (!stralloc_0(&glue)) return DNS_MEM;
  if (glue.s[0]) {
-   ix.pref = 0;
    if (!glue.s[ip_scan(glue.s,&ix.ip)] || !glue.s[ip_scanbracket(glue.s,&ix.ip)])
     {
      if (!ipalloc_append(ia,&ix)) return DNS_MEM;
@@ -291,9 +307,16 @@
    ix.ip = ip;
    ix.pref = pref;
    if (r == DNS_SOFT) return DNS_SOFT;
-   if (r == 1)
+   if (r == 1) {
+#ifdef IX_FQDN
+     ix.fqdn = glue.s;
+#endif
      if (!ipalloc_append(ia,&ix)) return DNS_MEM;
   }
+  }
+#ifdef IX_FQDN
+ glue.s = 0;
+#endif
  return 0;
 }
 
@@ -313,7 +336,7 @@
 {
  int r;
  struct mx { stralloc sa; unsigned short p; } *mx;
- struct ip_mx ix;
+ struct ip_mx ix = {0};
  int nummx;
  int i;
  int j;
@@ -325,7 +348,6 @@
  if (!stralloc_copy(&glue,sa)) return DNS_MEM;
  if (!stralloc_0(&glue)) return DNS_MEM;
  if (glue.s[0]) {
-   ix.pref = 0;
    if (!glue.s[ip_scan(glue.s,&ix.ip)] || !glue.s[ip_scanbracket(glue.s,&ix.ip)])
     {
      if (!ipalloc_append(ia,&ix)) return DNS_MEM;
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/EXTTODO ./EXTTODO
--- ../../netqmail-1.05-orig/netqmail-1.05/EXTTODO	1969-12-31 19:00:00.000000000 -0500
+++ ./EXTTODO	2008-02-10 08:29:49.000000000 -0500
@@ -0,0 +1,114 @@
+EXTTODO by Claudio Jeker <jeker@n-r-g.com> and 
+Andre Oppermann <opi@nrg4u.com>
+(c) 1998,1999,2000,2001,2002 Internet Business Solutions Ltd.
+
+The EXTTODO patch is a part of the qmail-ldap patch.
+This patches for qmail come with NO WARRANTY.
+
+These patches are under the BSD license.
+
+RELEASE: 5. Jan. 2003
+
+EXTTODO:
+======================
+
+TOC:
+ WHAT DOES IT DO
+ INSTALL
+ CONFIG FILES
+ SETUP
+ BIG PICTURE
+
+NEWS:
+ 
+ This is the first release of the EXTTODO patch.
+
+================================================================================
+
+WHAT DOES IT DO
+
+ The exttodo patch addresses a problem known as the silly qmail (queue)
+ problem. This problem is found only on system with high injection rates.
+
+ qmail with a big local and remote concurrency could deliver a tremendous 
+ amount of messages but normally this can not be achieved because qmail-send
+ becomes a bottleneck on those high volumes servers.
+ qmail-send preprocesses all new messages before distributing them for local
+ or remote delivering. In one run qmail-send does one todo run but has the 
+ ability to close multiple jobs. Because of this layout qmail-send can not 
+ feed all the new available (local/remote) delivery slots and therefor it is 
+ not possible to achieve the maximum throughput.
+ This would be a minor problem if one qmail-send run could be done in extreme
+ short time but because of many file system calls (fsync and (un)link) a todo
+ run is expensive and throttles the throughput.
+
+ The exttodo patch tries to solve the problem by moving the todo routine into 
+ an external program. This reduces the run time in qmail-send.
+
+ exttodo adds a new program to qmail called qmail-todo. qmail-todo prepares
+ incoming messages for local and remote delivering (by creating info/<messid>
+ local/<messid> and remote/<messid> and removing todo/<messid>). See also
+ INTERNALS. As next qmail-todo transmits the <messid> to qmail-send which will
+ add this message into the priority queue which schedules the message for 
+ delivery. 
+
+INSTALL
+
+ To enable the exttodo patch you need to define EXTERNAL_TODO while compiling
+ qmail(-ldap) this can be done with the -D flag of cc (e.g. cc -DEXTERNAL_TODO).
+
+ NOTE: the exttodo patch can also be used on qmail systems without the 
+ qmail-ldap patch.
+ 
+================================================================================
+
+CONFIG FILES
+
+ No additional control files are used or needed.
+
+================================================================================
+
+SETUP
+
+ qmail-todo will be started by qmail-start and therefor no additional setup
+ is needed.
+
+ To verify that exttodo is running just check if qmail-todo is running.
+
+================================================================================
+
+BIG PICTURE
+
+               +-------+   +-------+
+               | clean |   | clean |
+               +--0-1--+   +--0-1--+       +-----------+
+         trigger  ^ |         ^ |        +->0,1 lspawn |
+            |     | v         | v       /  +-----------+
+ +-------+  v  +--2-3--+   +--5-6--+   /
+ |       |  |  |       0<--7     1,2<-+
+ | queue |--+--| todo  |   | send  |
+ |       |  |  |       1-->8     3,4<-+
+ +-------+     +-------+   +---0---+   \
+                               |        \  +-----------+
+                               v         +->0,1 rspwan |
+                           +---0---+       +-----------+
+                           | logger|
+                           +-------+
+
+Communication between qmail-send and qmail-todo
+
+todo -> send:
+   D[LRB]<mesgid>\0
+          Start delivery for new message with id <messid>.
+          the character L, R or B defines the type
+          of delivery, local, remote or both respectively.
+   L<string>\0
+          Dump string to the logger without adding additional \n or similar.
+send -> todo:
+   H      Got a SIGHUP reread ~/control/locals and ~/control/virtualdomains
+   X      Quit ASAP.
+
+qmail-todo sends "\0" terminated messages whereas qmail-send just send one
+character to qmail-todo.
+
+
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/EXTTODO-INFO ./EXTTODO-INFO
--- ../../netqmail-1.05-orig/netqmail-1.05/EXTTODO-INFO	1969-12-31 19:00:00.000000000 -0500
+++ ./EXTTODO-INFO	2008-02-10 08:29:49.000000000 -0500
@@ -0,0 +1,11 @@
+Files modified:
+Makefile
+EXTTODO
+FILES
+TARGETS
+qmail-send.c
+qmail-todo.c
+qmail-start.c
+hier.c
+install-big.c
+
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/FILES ./FILES
--- ../../netqmail-1.05-orig/netqmail-1.05/FILES	2008-02-10 08:30:51.000000000 -0500
+++ ./FILES	2008-02-10 08:29:49.000000000 -0500
@@ -432,3 +432,4 @@
 tcp-environ.5
 constmap.h
 constmap.c
+qmail-todo.c
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/FILES.auth ./FILES.auth
--- ../../netqmail-1.05-orig/netqmail-1.05/FILES.auth	1969-12-31 19:00:00.000000000 -0500
+++ ./FILES.auth	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,18 @@
+The qmail-smtpd Auth patch modifies the following QMAIL 1.03 files:
+
+= TARGETS
+= Makefile
+= qmail-smtpd.c
+= qmail-smtpd.8
+
+Added files:
+
++ base64.c
++ base64.h
++ case_startb.c
+
+Informational files:
+
+% install_smtpd-auth.sh  (Installation shell script)
+% README.auth
+% README.auth.old (old description of SMTP Auth)
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/hier.c ./hier.c
--- ../../netqmail-1.05-orig/netqmail-1.05/hier.c	1998-06-15 06:53:16.000000000 -0400
+++ ./hier.c	2008-02-10 08:29:49.000000000 -0500
@@ -55,6 +55,8 @@
   d(auto_qmail,"queue/bounce",auto_uids,auto_gidq,0700);
 
   dsplit("queue/mess",auto_uidq,0750);
+  dsplit("queue/todo",auto_uidq,0750);
+  dsplit("queue/intd",auto_uidq,0700);
   dsplit("queue/info",auto_uids,0700);
   dsplit("queue/local",auto_uids,0700);
   dsplit("queue/remote",auto_uids,0700);
@@ -108,6 +110,9 @@
   c(auto_qmail,"bin","qmail-rspawn",auto_uido,auto_gidq,0711);
   c(auto_qmail,"bin","qmail-clean",auto_uido,auto_gidq,0711);
   c(auto_qmail,"bin","qmail-send",auto_uido,auto_gidq,0711);
+#ifdef EXTERNAL_TODO
+  c(auto_qmail,"bin","qmail-todo",auto_uido,auto_gidq,0711);
+#endif
   c(auto_qmail,"bin","splogger",auto_uido,auto_gidq,0711);
   c(auto_qmail,"bin","qmail-newu",auto_uido,auto_gidq,0700);
   c(auto_qmail,"bin","qmail-newmrh",auto_uido,auto_gidq,0700);
@@ -143,6 +148,9 @@
   c(auto_qmail,"bin","qail",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","elq",auto_uido,auto_gidq,0755);
   c(auto_qmail,"bin","pinq",auto_uido,auto_gidq,0755);
+#ifdef TLS
+  c(auto_qmail,"bin","update_tmprsadh",auto_uido,auto_gidq,0755);
+#endif
 
   c(auto_qmail,"man/man5","addresses.5",auto_uido,auto_gidq,0644);
   c(auto_qmail,"man/cat5","addresses.0",auto_uido,auto_gidq,0644);
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/install_auth.sh ./install_auth.sh
--- ../../netqmail-1.05-orig/netqmail-1.05/install_auth.sh	1969-12-31 19:00:00.000000000 -0500
+++ ./install_auth.sh	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,99 @@
+#!/bin/sh
+#
+# qmail-smtpd AUTH (UN)INSTALL Script (install_auth.sh)
+# -----------------------------------------------------
+#
+# Purpose:      To install and uninstall the qmail-smtpd Authentication Patch
+#
+# Parameters:   -u (uninstall)
+#	        VRF (Version to be uninstalled)
+#
+# Usage:        ./install_auth.sh [-u] [Version]
+#
+#		Installation: 	./install_auth.sh
+# 		Uninstallation: ./install_auth.sh -u 105
+#
+# Return Codes: 0 - Patches applied successfully
+#		1 - Original QMAIL files not found (Patch not extracted in QMAIL source directory)
+#		2 - Patch files not found 
+#
+# Output:	install_auth.log
+#
+# History:      1.0.0 - Erwin Hoffmann - Initial release
+#		1.0.1 - 	       - grep fix; Gentoo fix
+#		1.0.2 -			 removed '-v' optio for cp
+#
+#---------------------------------------------------------------------------------------
+#
+DATE=$(date)
+LOCDIR=${PWD}
+QMAILHOME=$(head -n 1 conf-qmail)
+SOLARIS=$(sh ./find-systype.sh | grep -ci "SunOS")
+LOGFILE=auth.log
+TARGETS=FILES.auth
+IFSKEEP=${IFS}
+REL=057 # Should be identical to qmail-smtpd AUTH level
+BUILD=2005024212941
+
+
+if [ $# -eq 0 ] ; then
+
+	echo "Installing qmail-smtpd AUTH $REL (Build $BUILD) at $DATE <<<" | tee -a $LOGFILE 2>&1 
+
+	for FILE in $(grep "^= " ${TARGETS} | awk '{print $2}'); do
+		echo "Targeting file $FILE ..." | tee -a $LOGFILE 2>&1
+		if [ -s ${FILE} ] ; then
+			cp ${FILE} ${FILE}.$REL | tee -a $LOGFILE 2>&1
+			echo "--> ${FILE} copied to ${FILE}.$REL" | tee -a $LOGFILE 2>&1
+		else
+			echo "${FILE} not found !"
+			exit 1
+		fi
+		if [ -s ${FILE}.patch ] ; then
+			if [ ${SOLARIS} -gt 0 ]; then
+				echo "--> Patching qmail source file ${FILE} for Solaris ...." | tee -a $LOGFILE 2>&1
+				patch -i ${FILE}.patch ${FILE} 2>&1 | tee -a $LOGFILE
+			else
+				echo "--> Patching qmail source file ${FILE}  ...." | tee -a $LOGFILE 2>&1
+				patch ${FILE} ${FILE}.patch 2>&1 | tee -a $LOGFILE
+			fi
+		else
+			echo "!! ${FILE}.patch not found !"
+			exit 2
+		fi
+	done 
+
+
+	echo "Copying documentation and samples to ${QMAILHOME}/doc/ ..." | tee -a $LOGFILE 2>&1 
+
+	cp README.auth* ${QMAILHOME}/doc/ | tee -a $LOGFILE 2>&1
+	echo ""
+	echo "If you dont wont CRAM-MD5 suport disable '#define CRAM_MD5' in qmail-smtpd !"
+	echo "Installation of qmail-smtpd AUTH $REL (Build $BUILD) finished at $DATE <<<" | tee -a $LOGFILE 2>&1 
+
+# Now go for the uninstallation....
+
+elif [ "$1" = "-u" ] ; then
+
+# Get the Version Number from INPUT 
+
+	if [ $# -eq 2 ] ; then
+		REL=$2
+	fi
+
+	echo "De-installing qmail-smtpd AUTH $REL (Build $BUILD) at $DATE <<<" | tee -a $LOGFILE 2>&1 
+
+	for FILE in $(grep "^= " ${TARGETS} | awk '{print $2}'); do
+		echo "Targeting file $FILE ..." | tee -a $LOGFILE 2>&1
+		if [ -s ${FILE}.$REL ] ; then
+			mv ${FILE}.$REL ${FILE} | tee -a $LOGFILE 2>&1
+			touch ${FILE}
+			echo "--> ${FILE}.$REL moved to ${FILE}" | tee -a $LOGFILE 2>&1
+		else
+			echo "!! ${FILE}.$REL not found !"
+		fi
+	done
+	echo "De-installation of qmail-smtpd AUTH $REL (Build $BUILD) finished at $DATE <<<" | tee -a $LOGFILE 2>&1 
+fi
+
+exit 0
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/install-big.c ./install-big.c
--- ../../netqmail-1.05-orig/netqmail-1.05/install-big.c	1998-06-15 06:53:16.000000000 -0400
+++ ./install-big.c	2008-02-10 08:29:49.000000000 -0500
@@ -108,6 +108,9 @@
   c(auto_qmail,"bin","qmail-rspawn",auto_uido,auto_gidq,0711);
   c(auto_qmail,"bin","qmail-clean",auto_uido,auto_gidq,0711);
   c(auto_qmail,"bin","qmail-send",auto_uido,auto_gidq,0711);
+#ifdef EXTERNAL_TODO
+  c(auto_qmail,"bin","qmail-todo",auto_uido,auto_gidq,0711);
+#endif
   c(auto_qmail,"bin","splogger",auto_uido,auto_gidq,0711);
   c(auto_qmail,"bin","qmail-newu",auto_uido,auto_gidq,0700);
   c(auto_qmail,"bin","qmail-newmrh",auto_uido,auto_gidq,0700);
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/ipalloc.h ./ipalloc.h
--- ../../netqmail-1.05-orig/netqmail-1.05/ipalloc.h	1998-06-15 06:53:16.000000000 -0400
+++ ./ipalloc.h	2008-02-10 08:29:47.000000000 -0500
@@ -3,7 +3,15 @@
 
 #include "ip.h"
 
+#ifdef TLS
+# define IX_FQDN 1
+#endif
+
+#ifdef IX_FQDN
+struct ip_mx { struct ip_address ip; int pref; char *fqdn; } ;
+#else
 struct ip_mx { struct ip_address ip; int pref; } ;
+#endif
 
 #include "gen_alloc.h"
 
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/Makefile ./Makefile
--- ../../netqmail-1.05-orig/netqmail-1.05/Makefile	2008-02-10 08:30:51.000000000 -0500
+++ ./Makefile	2008-02-10 08:29:49.000000000 -0500
@@ -1,5 +1,7 @@
 # Don't edit Makefile! Use conf-* for configuration.
 
+DEFINES=-DEXTERNAL_TODO # use to enable external todo
+
 SHELL=/bin/sh
 
 default: it
@@ -136,6 +138,10 @@
 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 \
@@ -703,7 +709,7 @@
 
 hier.o: \
 compile hier.c auto_qmail.h auto_split.h auto_uids.h fmt.h fifo.h
-	./compile hier.c
+	./compile $(DEFINES) hier.c
 
 home: \
 home.sh conf-qmail
@@ -755,7 +761,7 @@
 install-big.o: \
 compile install-big.c auto_qmail.h auto_split.h auto_uids.h fmt.h \
 fifo.h
-	./compile install-big.c
+	./compile $(DEFINES) install-big.c
 
 install.o: \
 compile install.c substdio.h strerr.h error.h open.h readwrite.h \
@@ -808,7 +814,7 @@
 forward preline condredirect bouncesaying except maildirmake \
 maildir2mbox maildirwatch qail elq pinq idedit install-big install \
 instcheck home home+df proc proc+df binm1 binm1+df binm2 binm2+df \
-binm3 binm3+df
+binm3 binm3+df update_tmprsadh qmail-todo
 
 load: \
 make-load warn-auto.sh systype
@@ -1444,6 +1450,7 @@
 substdio.a error.a str.a fs.a auto_qmail.o dns.lib socket.lib
 	./load qmail-remote control.o constmap.o timeoutread.o \
 	timeoutwrite.o timeoutconn.o tcpto.o now.o dns.o ip.o \
+	tls.o ssl_timeoutio.o -L/usr/local/ssl/lib -lssl -lcrypto \
 	ipalloc.o ipme.o quote.o ndelay.a case.a sig.a open.a \
 	lock.a seek.a getln.a stralloc.a alloc.a substdio.a error.a \
 	str.a fs.a auto_qmail.o  `cat dns.lib` `cat socket.lib`
@@ -1509,7 +1516,7 @@
 scan.h case.h auto_qmail.h trigger.h newfield.h stralloc.h quote.h \
 qmail.h substdio.h qsutil.h prioq.h datetime.h gen_alloc.h constmap.h \
 fmtqfn.h readsubdir.h direntry.h
-	./compile qmail-send.c
+	./compile $(DEFINES) qmail-send.c
 
 qmail-showctl: \
 load qmail-showctl.o auto_uids.o control.o open.a getln.a stralloc.a \
@@ -1535,13 +1542,14 @@
 load qmail-smtpd.o 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 socket.lib
+open.a sig.a case.a env.a stralloc.a alloc.a strerr.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 \
+	tls.o ssl_timeoutio.o ndelay.a -L/usr/local/ssl/lib -lssl -lcrypto \
 	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 \
+	alloc.a strerr.a substdio.a error.a str.a fs.a auto_qmail.o base64.o  `cat \
 	socket.lib`
 
 qmail-smtpd.0: \
@@ -1553,7 +1561,7 @@
 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
+exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h base64.h cdb.h uint32.h
 	./compile qmail-smtpd.c
 
 qmail-start: \
@@ -1574,7 +1582,7 @@
 
 qmail-start.o: \
 compile qmail-start.c fd.h prot.h exit.h fork.h auto_uids.h
-	./compile qmail-start.c
+	./compile $(DEFINES) qmail-start.c
 
 qmail-tcpok: \
 load qmail-tcpok.o open.a lock.a strerr.a substdio.a error.a str.a \
@@ -1606,6 +1614,20 @@
 fmt.h ip.h lock.h error.h exit.h datetime.h now.h datetime.h
 	./compile qmail-tcpto.c
 
+qmail-todo: \
+load qmail-todo.o control.o constmap.o trigger.o fmtqfn.o now.o \
+readsubdir.o case.a ndelay.a getln.a sig.a open.a stralloc.a alloc.a \
+substdio.a error.a str.a fs.a auto_qmail.o auto_split.o
+	./load qmail-todo control.o constmap.o trigger.o fmtqfn.o now.o \
+	readsubdir.o case.a ndelay.a getln.a sig.a open.a stralloc.a \
+	alloc.a substdio.a error.a str.a fs.a auto_qmail.o auto_split.o
+
+qmail-todo.o: \
+compile alloc.h auto_qmail.h byte.h constmap.h control.h direntry.h error.h \
+exit.h fmt.h fmtqfn.h getln.h open.h ndelay.h now.h readsubdir.h readwrite.h \
+scan.h select.h str.h stralloc.h substdio.h trigger.h
+	./compile $(DEFINES) qmail-todo.c
+
 qmail-upq: \
 warn-auto.sh qmail-upq.sh conf-qmail conf-break conf-split
 	cat warn-auto.sh qmail-upq.sh \
@@ -1827,7 +1849,8 @@
 ipalloc.h ipalloc.c select.h1 select.h2 trysysel.c ndelay.h ndelay.c \
 ndelay_off.c direntry.3 direntry.h1 direntry.h2 trydrent.c prot.h \
 prot.c chkshsgr.c warn-shsgr tryshsgr.c ipme.h ipme.c trysalen.c \
-maildir.5 maildir.h maildir.c tcp-environ.5 constmap.h constmap.c
+maildir.5 maildir.h maildir.c tcp-environ.5 constmap.h constmap.c \
+update_tmprsadh
 	shar -m `cat FILES` > shar
 	chmod 400 shar
 
@@ -2108,6 +2131,19 @@
 compile timeoutwrite.c timeoutwrite.h select.h error.h readwrite.h
 	./compile timeoutwrite.c
 
+qmail-smtpd: tls.o ssl_timeoutio.o ndelay.a
+qmail-remote: tls.o ssl_timeoutio.o
+qmail-smtpd.o: tls.h ssl_timeoutio.h
+qmail-remote.o: tls.h ssl_timeoutio.h
+
+tls.o: \
+compile tls.c exit.h error.h
+	./compile tls.c
+
+ssl_timeoutio.o: \
+compile ssl_timeoutio.c ssl_timeoutio.h select.h error.h ndelay.h
+	./compile ssl_timeoutio.c
+
 token822.o: \
 compile token822.c stralloc.h gen_alloc.h alloc.h str.h token822.h \
 gen_alloc.h gen_allocdefs.h
@@ -2139,3 +2175,26 @@
 wait_pid.o: \
 compile wait_pid.c error.h haswaitp.h
 	./compile wait_pid.c
+
+cert cert-req: \
+Makefile-cert
+	@$(MAKE) -sf $< $@
+
+Makefile-cert: \
+conf-qmail conf-users conf-groups Makefile-cert.mk
+	@cat Makefile-cert.mk \
+	| sed s}QMAIL}"`head -1 conf-qmail`"}g \
+	> $@
+
+update_tmprsadh: \
+conf-qmail conf-users conf-groups update_tmprsadh.sh
+	@cat update_tmprsadh.sh\
+	| sed s}UGQMAILD}"`head -2 conf-users|tail -1`:`head -1 conf-groups`"}g \
+	| sed s}QMAIL}"`head -1 conf-qmail`"}g \
+	> $@
+	chmod 755 update_tmprsadh 
+
+tmprsadh: \
+update_tmprsadh
+	echo "Creating new temporary RSA and DH parameters"
+	./update_tmprsadh
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/Makefile-cert.mk ./Makefile-cert.mk
--- ../../netqmail-1.05-orig/netqmail-1.05/Makefile-cert.mk	1969-12-31 19:00:00.000000000 -0500
+++ ./Makefile-cert.mk	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,21 @@
+cert-req: req.pem
+cert cert-req: QMAIL/control/clientcert.pem
+	@:
+
+QMAIL/control/clientcert.pem: QMAIL/control/servercert.pem
+	ln -s $< $@
+
+QMAIL/control/servercert.pem:
+	PATH=$$PATH:/usr/local/ssl/bin \
+		openssl req -new -x509 -nodes -days 366 -out $@ -keyout $@
+	chmod 640 $@
+	chown `head -2 conf-users | tail -1`:`head -1 conf-groups` $@
+
+req.pem:
+	PATH=$$PATH:/usr/local/ssl/bin openssl req \
+		-new -nodes -out $@ -keyout QMAIL/control/servercert.pem
+	chmod 640 QMAIL/control/servercert.pem
+	chown `head -2 conf-users | tail -1`:`head -1 conf-groups` QMAIL/control/servercert.pem
+	@echo
+	@echo "Send req.pem to your CA to obtain signed_req.pem, and do:"
+	@echo "cat signed_req.pem >> QMAIL/control/servercert.pem"
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-clean.c ./qmail-clean.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-clean.c	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-clean.c	2008-02-10 08:29:49.000000000 -0500
@@ -73,22 +73,26 @@
    if (line.len < 7) { respond("x"); continue; }
    if (line.len > 100) { respond("x"); continue; }
    if (line.s[line.len - 1]) { respond("x"); continue; } /* impossible */
-   for (i = 5;i < line.len - 1;++i)
+   for (i = line.len - 2;i > 4;--i)
+    {
+     if (line.s[i] == '/') break;
      if ((unsigned char) (line.s[i] - '0') > 9)
       { respond("x"); continue; }
-   if (!scan_ulong(line.s + 5,&id)) { respond("x"); continue; }
+    }
+   if (line.s[i] == '/')
+     if (!scan_ulong(line.s + i + 1,&id)) { respond("x"); continue; }
    if (byte_equal(line.s,5,"foop/"))
     {
 #define U(prefix,flag) fmtqfn(fnbuf,prefix,id,flag); \
 if (unlink(fnbuf) == -1) if (errno != error_noent) { respond("!"); continue; }
-     U("intd/",0)
+     U("intd/",1)
      U("mess/",1)
      respond("+");
     }
    else if (byte_equal(line.s,4,"todo/"))
     {
-     U("intd/",0)
-     U("todo/",0)
+     U("intd/",1)
+     U("todo/",1)
      respond("+");
     }
    else
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-control.9 ./qmail-control.9
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-control.9	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-control.9	2008-02-10 08:29:50.000000000 -0500
@@ -43,11 +43,15 @@
 .I badmailfrom	\fR(none)	\fRqmail-smtpd
 .I bouncefrom	\fRMAILER-DAEMON	\fRqmail-send
 .I bouncehost	\fIme	\fRqmail-send
+.I clientca.pem	\fR(none)	\fRqmail-smtpd
+.I clientcert.pem	\fR(none)	\fRqmail-remote
 .I concurrencylocal	\fR10	\fRqmail-send
 .I concurrencyremote	\fR20	\fRqmail-send
 .I defaultdomain	\fIme	\fRqmail-inject
 .I defaulthost	\fIme	\fRqmail-inject
 .I databytes	\fR0	\fRqmail-smtpd
+.I dh1024.pem	\fR(none)	\fRqmail-smtpd
+.I dh512.pem	\fR(none)	\fRqmail-smtpd
 .I doublebouncehost	\fIme	\fRqmail-send
 .I doublebounceto	\fRpostmaster	\fRqmail-send
 .I envnoathost	\fIme	\fRqmail-send
@@ -56,16 +60,23 @@
 .I localiphost	\fIme	\fRqmail-smtpd
 .I locals	\fIme	\fRqmail-send
 .I morercpthosts	\fR(none)	\fRqmail-smtpd
+.I outgoingips	\fR(none)	\fRqmail-remote
 .I percenthack	\fR(none)	\fRqmail-send
 .I plusdomain	\fIme	\fRqmail-inject
 .I qmqpservers	\fR(none)	\fRqmail-qmqpc
 .I queuelifetime	\fR604800	\fRqmail-send
 .I rcpthosts	\fR(none)	\fRqmail-smtpd
+.I rsa512.pem	\fR(none)	\fRqmail-smtpd
+.I servercert.pem	\fR(none)	\fRqmail-smtpd
 .I smtpgreeting	\fIme	\fRqmail-smtpd
 .I smtproutes	\fR(none)	\fRqmail-remote
 .I timeoutconnect	\fR60	\fRqmail-remote
 .I timeoutremote	\fR1200	\fRqmail-remote
 .I timeoutsmtpd	\fR1200	\fRqmail-smtpd
+.I tlsclients	\fR(none)	\fRqmail-smtpd
+.I tlsclientciphers	\fR(none)	\fRqmail-remote
+.I tlshosts/FQDN.pem	\fR(none)	\fRqmail-remote
+.I tlsserverciphers	\fR(none)	\fRqmail-smtpd
 .I virtualdomains	\fR(none)	\fRqmail-send
 .fi
 .RE
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-qstat.sh ./qmail-qstat.sh
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-qstat.sh	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-qstat.sh	2008-02-10 08:29:49.000000000 -0500
@@ -1,7 +1,7 @@
 cd QMAIL
 messdirs=`echo queue/mess/* | wc -w`
 messfiles=`find queue/mess/* -print | wc -w`
-tododirs=`echo queue/todo | wc -w`
-todofiles=`find queue/todo -print | wc -w`
+tododirs=`echo queue/todo/* | wc -w`
+todofiles=`find queue/todo/* -print | wc -w`
 echo messages in queue: `expr $messfiles - $messdirs`
 echo messages in queue but not yet preprocessed: `expr $todofiles - $tododirs`
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-queue.c ./qmail-queue.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-queue.c	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-queue.c	2008-02-10 08:29:49.000000000 -0500
@@ -180,8 +180,8 @@
 
  messnum = pidst.st_ino;
  messfn = fnnum("mess/",1);
- todofn = fnnum("todo/",0);
- intdfn = fnnum("intd/",0);
+ todofn = fnnum("todo/",1);
+ intdfn = fnnum("intd/",1);
 
  if (link(pidfn,messfn) == -1) die(64);
  if (unlink(pidfn) == -1) die(63);
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-remote.8 ./qmail-remote.8
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-remote.8	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-remote.8	2008-02-10 08:29:50.000000000 -0500
@@ -114,6 +114,10 @@
 always exits zero.
 .SH "CONTROL FILES"
 .TP 5
+.I clientcert.pem
+SSL certificate that is used to authenticate with the remote server
+during a TLS session.
+.TP 5
 .I helohost
 Current host name,
 for use solely in saying hello to the remote SMTP server.
@@ -123,6 +127,33 @@
 otherwise
 .B qmail-remote
 refuses to run.
+
+.TP 5
+.I notlshosts/<FQDN>
+.B qmail-remote
+will not try TLS on servers for which this file exists
+.RB ( <FQDN>
+is the fully-qualified domain name of the server). 
+.IR (tlshosts/<FQDN>.pem 
+takes precedence over this file however).
+
+.TP 5
+.I outgoingips
+IP addresses to be used on outgoing connections.
+Each line has the form
+.IR fromdomain\fB:\fIlocalip ,
+without any extra spaces.
+If
+.I domain
+matches the domain part in
+.IR sender ,
+.B qmail-remote
+will bind to
+.IR localip 
+when connecting to 
+.IR host .
+If it matches, it will also set the HELO string to the domain part of
+.IR sender .
 .TP 5
 .I smtproutes
 Artificial SMTP routes.
@@ -156,6 +187,8 @@
 this tells
 .B qmail-remote
 to look up MX records as usual.
+.I port 
+value of 465 (deprecated smtps port) causes TLS session to be started.
 .I smtproutes
 may include wildcards:
 
@@ -195,6 +228,33 @@
 .B qmail-remote
 will wait for each response from the remote SMTP server.
 Default: 1200.
+
+.TP 5
+.I tlsclientciphers
+A set of OpenSSL client cipher strings. Multiple ciphers
+contained in a string should be separated by a colon.
+
+.TP 5
+.I tlshosts/<FQDN>.pem
+.B qmail-remote
+requires TLS authentication from servers for which this file exists
+.RB ( <FQDN>
+is the fully-qualified domain name of the server). One of the
+.I dNSName
+or the
+.I CommonName
+attributes have to match. The file contains the trusted CA certificates.
+
+.B WARNING:
+this option may cause mail to be delayed, bounced, doublebounced, or lost.
+
+.TP 5
+.I tlshosts/exhaustivelist
+if this file exists
+no TLS will be tried on hosts other than those for which a file
+.B tlshosts/<FQDN>.pem
+exists.
+
 .SH "SEE ALSO"
 addresses(5),
 envelopes(5),
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-remote.c ./qmail-remote.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-remote.c	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-remote.c	2008-02-10 08:29:50.000000000 -0500
@@ -39,6 +39,9 @@
 static stralloc sauninit = {0};
 
 stralloc helohost = {0};
+stralloc outdomain = {0};
+stralloc outgoingips = {0};
+struct constmap mapoutgoingips;
 stralloc routes = {0};
 struct constmap maproutes;
 stralloc host = {0};
@@ -47,6 +50,18 @@
 saa reciplist = {0};
 
 struct ip_address partner;
+struct ip_address outgoingip;
+
+#ifdef TLS
+# include <sys/stat.h>
+# include "tls.h"
+# include "ssl_timeoutio.h"
+# include <openssl/x509v3.h>
+# define EHLO 1
+
+int tls_init();
+const char *ssl_err_str = 0;
+#endif 
 
 void out(s) char *s; { if (substdio_puts(subfdoutsmall,s) == -1) _exit(0); }
 void zero() { if (substdio_put(subfdoutsmall,"\0",1) == -1) _exit(0); }
@@ -56,6 +71,7 @@
 ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126) ch = '?';
 if (substdio_put(subfdoutsmall,&ch,1) == -1) _exit(0); } }
 
+void temp_noip() { out("Zinvalid ipaddr in control/outgoingips (#4.3.0)\n"); zerodie(); }
 void temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); }
 void temp_oserr() { out("Z\
 System resources temporarily unavailable. (#4.3.0)\n"); zerodie(); }
@@ -99,6 +115,9 @@
   outhost();
   out(" but connection died. ");
   if (flagcritical) out("Possible duplicate! ");
+#ifdef TLS
+  if (ssl_err_str) { out(ssl_err_str); out(" "); }
+#endif
   out("(#4.4.2)\n");
   zerodie();
 }
@@ -110,6 +129,12 @@
 int saferead(fd,buf,len) int fd; char *buf; int len;
 {
   int r;
+#ifdef TLS
+  if (ssl) {
+    r = ssl_timeoutread(timeout, smtpfd, smtpfd, ssl, buf, len);
+    if (r < 0) ssl_err_str = ssl_error_str();
+  } else
+#endif
   r = timeoutread(timeout,smtpfd,buf,len);
   if (r <= 0) dropped();
   return r;
@@ -117,6 +142,12 @@
 int safewrite(fd,buf,len) int fd; char *buf; int len;
 {
   int r;
+#ifdef TLS
+  if (ssl) {
+    r = ssl_timeoutwrite(timeout, smtpfd, smtpfd, ssl, buf, len);
+    if (r < 0) ssl_err_str = ssl_error_str();
+  } else
+#endif 
   r = timeoutwrite(timeout,smtpfd,buf,len);
   if (r <= 0) dropped();
   return r;
@@ -163,6 +194,65 @@
   return code;
 }
 
+#ifdef EHLO
+saa ehlokw = {0}; /* list of EHLO keywords and parameters */
+int maxehlokwlen = 0;
+
+unsigned long ehlo()
+{
+  stralloc *sa;
+  char *s, *e, *p;
+  unsigned long code;
+
+  if (ehlokw.len > maxehlokwlen) maxehlokwlen = ehlokw.len;
+  ehlokw.len = 0;
+
+# ifdef MXPS
+  if (type == 's') return 0;
+# endif
+
+  substdio_puts(&smtpto, "EHLO ");
+  substdio_put(&smtpto, helohost.s, helohost.len);
+  substdio_puts(&smtpto, "\r\n");
+  substdio_flush(&smtpto);
+
+  code = smtpcode();
+  if (code != 250) return code;
+
+  s = smtptext.s;
+  while (*s++ != '\n') ; /* skip the first line: contains the domain */
+
+  e = smtptext.s + smtptext.len - 6; /* 250-?\n */
+  while (s <= e)
+  {
+    int wasspace = 0;
+
+    if (!saa_readyplus(&ehlokw, 1)) temp_nomem();
+    sa = ehlokw.sa + ehlokw.len++;
+    if (ehlokw.len > maxehlokwlen) *sa = sauninit; else sa->len = 0;
+
+     /* smtptext is known to end in a '\n' */
+     for (p = (s += 4); ; ++p)
+       if (*p == '\n' || *p == ' ' || *p == '\t') {
+         if (!wasspace)
+           if (!stralloc_catb(sa, s, p - s) || !stralloc_0(sa)) temp_nomem();
+         if (*p == '\n') break;
+         wasspace = 1;
+       } else if (wasspace == 1) {
+         wasspace = 0;
+         s = p;
+       }
+    s = ++p;
+
+    /* keyword should consist of alpha-num and '-'
+     * broken AUTH might use '=' instead of space */
+    for (p = sa->s; *p; ++p) if (*p == '=') { *p = 0; break; }
+  }
+
+  return 250;
+}
+#endif
+
 void outsmtptext()
 {
   int i; 
@@ -179,6 +269,11 @@
 char *prepend;
 char *append;
 {
+#ifdef TLS
+  /* shouldn't talk to the client unless in an appropriate state */
+  int state = ssl ? ssl->state : SSL_ST_BEFORE;
+  if (state & SSL_ST_OK || (!smtps && state & SSL_ST_BEFORE))
+#endif
   substdio_putsflush(&smtpto,"QUIT\r\n");
   /* waiting for remote side is just too ridiculous */
   out(prepend);
@@ -186,6 +281,30 @@
   out(append);
   out(".\n");
   outsmtptext();
+
+#if defined(TLS) && defined(DEBUG)
+  if (ssl) {
+    X509 *peercert;
+
+    out("STARTTLS proto="); out(SSL_get_version(ssl));
+    out("; cipher="); out(SSL_get_cipher(ssl));
+
+    /* we want certificate details */
+    if (peercert = SSL_get_peer_certificate(ssl)) {
+      char *str;
+
+      str = X509_NAME_oneline(X509_get_subject_name(peercert), NULL, 0);
+      out("; subject="); out(str); OPENSSL_free(str);
+
+      str = X509_NAME_oneline(X509_get_issuer_name(peercert), NULL, 0);
+      out("; issuer="); out(str); OPENSSL_free(str);
+
+      X509_free(peercert);
+    }
+    out(";\n");
+  }
+#endif
+
   zerodie();
 }
 
@@ -214,6 +333,194 @@
   substdio_flush(&smtpto);
 }
 
+#ifdef TLS
+char *partner_fqdn = 0;
+
+# define TLS_QUIT quit(ssl ? "; connected to " : "; connecting to ", "")
+void tls_quit(const char *s1, const char *s2)
+{
+  out(s1); if (s2) { out(": "); out(s2); } TLS_QUIT;
+}
+# define tls_quit_error(s) tls_quit(s, ssl_error())
+
+int match_partner(const char *s, int len)
+{
+  if (!case_diffb(partner_fqdn, len, s) && !partner_fqdn[len]) return 1;
+  /* we also match if the name is *.domainname */
+  if (*s == '*') {
+    const char *domain = partner_fqdn + str_chr(partner_fqdn, '.');
+    if (!case_diffb(domain, --len, ++s) && !domain[len]) return 1;
+  }
+  return 0;
+}
+
+/* don't want to fail handshake if certificate can't be verified */
+int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; }
+
+int tls_init()
+{
+  int i;
+  SSL *myssl;
+  SSL_CTX *ctx;
+  stralloc saciphers = {0};
+  const char *ciphers, *servercert = 0;
+
+  if (partner_fqdn) {
+    struct stat st;
+    stralloc tmp = {0};
+    if (!stralloc_copys(&tmp, "control/tlshosts/")
+      || !stralloc_catb(&tmp, partner_fqdn, str_len(partner_fqdn))
+      || !stralloc_catb(&tmp, ".pem", 5)) temp_nomem();
+    if (stat(tmp.s, &st) == 0) 
+      servercert = tmp.s;
+    else {
+      if (!stralloc_copys(&tmp, "control/notlshosts/")
+        || !stralloc_catb(&tmp, partner_fqdn, str_len(partner_fqdn)+1))
+        temp_nomem();
+      if ((stat("control/tlshosts/exhaustivelist", &st) == 0) ||
+	  (stat(tmp.s, &st) == 0)) {
+         alloc_free(tmp.s);
+         return 0;
+      }
+      alloc_free(tmp.s);
+    }
+  }
+ 
+  if (!smtps) {
+    stralloc *sa = ehlokw.sa;
+    unsigned int len = ehlokw.len;
+    /* look for STARTTLS among EHLO keywords */
+    for ( ; len && case_diffs(sa->s, "STARTTLS"); ++sa, --len) ;
+    if (!len) {
+      if (!servercert) return 0;
+      out("ZNo TLS achieved while "); out(servercert);
+      out(" exists"); smtptext.len = 0; TLS_QUIT;
+    }
+  }
+
+  SSL_library_init();
+  ctx = SSL_CTX_new(SSLv23_client_method());
+  if (!ctx) {
+    if (!smtps && !servercert) return 0;
+    smtptext.len = 0;
+    tls_quit_error("ZTLS error initializing ctx");
+  }
+
+  if (servercert) {
+    if (!SSL_CTX_load_verify_locations(ctx, servercert, NULL)) {
+      SSL_CTX_free(ctx);
+      smtptext.len = 0;
+      out("ZTLS unable to load "); tls_quit_error(servercert);
+    }
+    /* set the callback here; SSL_set_verify didn't work before 0.9.6c */
+    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb);
+  }
+
+  /* let the other side complain if it needs a cert and we don't have one */
+# define CLIENTCERT "control/clientcert.pem"
+  if (SSL_CTX_use_certificate_chain_file(ctx, CLIENTCERT))
+    SSL_CTX_use_RSAPrivateKey_file(ctx, CLIENTCERT, SSL_FILETYPE_PEM);
+# undef CLIENTCERT
+
+  myssl = SSL_new(ctx);
+  SSL_CTX_free(ctx);
+  if (!myssl) {
+    if (!smtps && !servercert) return 0;
+    smtptext.len = 0;
+    tls_quit_error("ZTLS error initializing ssl");
+  }
+
+  if (!smtps) substdio_putsflush(&smtpto, "STARTTLS\r\n");
+
+  /* while the server is preparing a responce, do something else */
+  if (control_readfile(&saciphers, "control/tlsclientciphers", 0) == -1)
+    { SSL_free(myssl); temp_control(); }
+  if (saciphers.len) {
+    for (i = 0; i < saciphers.len - 1; ++i)
+      if (!saciphers.s[i]) saciphers.s[i] = ':';
+    ciphers = saciphers.s;
+  }
+  else ciphers = "DEFAULT";
+  SSL_set_cipher_list(myssl, ciphers);
+  alloc_free(saciphers.s);
+
+  /* SSL_set_options(myssl, SSL_OP_NO_TLSv1); */
+  SSL_set_fd(myssl, smtpfd);
+
+  /* read the responce to STARTTLS */
+  if (!smtps) {
+    if (smtpcode() != 220) {
+      SSL_free(myssl);
+      if (!servercert) return 0;
+      out("ZSTARTTLS rejected while ");
+      out(servercert); out(" exists"); TLS_QUIT;
+    }
+    smtptext.len = 0;
+  }
+
+  ssl = myssl;
+  if (ssl_timeoutconn(timeout, smtpfd, smtpfd, ssl) <= 0)
+    tls_quit("ZTLS connect failed", ssl_error_str());
+
+  if (servercert) {
+    X509 *peercert;
+    STACK_OF(GENERAL_NAME) *gens;
+
+    int r = SSL_get_verify_result(ssl);
+    if (r != X509_V_OK) {
+      out("ZTLS unable to verify server with ");
+      tls_quit(servercert, X509_verify_cert_error_string(r));
+    }
+    alloc_free(servercert);
+
+    peercert = SSL_get_peer_certificate(ssl);
+    if (!peercert) {
+      out("ZTLS unable to verify server ");
+      tls_quit(partner_fqdn, "no certificate provided");
+    }
+
+    /* RFC 2595 section 2.4: find a matching name
+     * first find a match among alternative names */
+    gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0);
+    if (gens) {
+      for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i)
+      {
+        const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+        if (gn->type == GEN_DNS)
+          if (match_partner(gn->d.ia5->data, gn->d.ia5->length)) break;
+      }
+      sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
+    }
+
+    /* no alternative name matched, look up commonName */
+    if (!gens || i >= r) {
+      stralloc peer = {0};
+      X509_NAME *subj = X509_get_subject_name(peercert);
+      i = X509_NAME_get_index_by_NID(subj, NID_commonName, -1);
+      if (i >= 0) {
+        const ASN1_STRING *s = X509_NAME_get_entry(subj, i)->value;
+        if (s) { peer.len = s->length; peer.s = s->data; }
+      }
+      if (peer.len <= 0) {
+        out("ZTLS unable to verify server ");
+        tls_quit(partner_fqdn, "certificate contains no valid commonName");
+      }
+      if (!match_partner(peer.s, peer.len)) {
+        out("ZTLS unable to verify server "); out(partner_fqdn);
+        out(": received certificate for "); outsafe(&peer); TLS_QUIT;
+      }
+    }
+
+    X509_free(peercert);
+  }
+
+  if (smtps) if (smtpcode() != 220)
+    quit("ZTLS Connected to "," but greeting failed");
+
+  return 1;
+}
+#endif
+
 stralloc recip = {0};
 
 void smtp()
@@ -221,15 +528,54 @@
   unsigned long code;
   int flagbother;
   int i;
+
+#ifndef PORT_SMTP
+  /* the qmtpc patch uses smtp_port and undefines PORT_SMTP */
+# define port smtp_port
+#endif
+
+#ifdef TLS
+# ifdef MXPS
+  if (type == 'S') smtps = 1;
+  else if (type != 's')
+# endif
+    if (port == 465) smtps = 1;
+  if (!smtps)
+#endif
  
   if (smtpcode() != 220) quit("ZConnected to "," but greeting failed");
  
+#ifdef EHLO
+# ifdef TLS
+  if (!smtps)
+# endif
+  code = ehlo();
+
+# ifdef TLS
+  if (tls_init())
+    /* RFC2487 says we should issue EHLO (even if we might not need
+     * extensions); at the same time, it does not prohibit a server
+     * to reject the EHLO and make us fallback to HELO */
+    code = ehlo();
+# endif
+
+  if (code == 250) {
+    /* add EHLO response checks here */
+
+    /* and if EHLO failed, use HELO */
+  } else {
+#endif
+ 
   substdio_puts(&smtpto,"HELO ");
   substdio_put(&smtpto,helohost.s,helohost.len);
   substdio_puts(&smtpto,"\r\n");
   substdio_flush(&smtpto);
   if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected");
  
+#ifdef EHLO
+  }
+#endif
+ 
   substdio_puts(&smtpto,"MAIL FROM:<");
   substdio_put(&smtpto,sender.s,sender.len);
   substdio_puts(&smtpto,">\r\n");
@@ -324,6 +670,14 @@
     case 1:
       if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break;
   }
+  switch(control_readfile(&outgoingips,"control/outgoingips",0)) {
+    case -1:
+      temp_control();
+    case 0:
+      if (!constmap_init(&mapoutgoingips,"",0,1)) temp_nomem(); break;
+    case 1:
+      if (!constmap_init(&mapoutgoingips,outgoingips.s,outgoingips.len,1)) temp_nomem(); break;
+  }
 }
 
 void main(argc,argv)
@@ -338,6 +692,7 @@
   int flagallaliases;
   int flagalias;
   char *relayhost;
+  char *localip;
  
   sig_pipeignore();
   if (argc < 4) perm_usage();
@@ -366,6 +721,22 @@
 
   addrmangle(&sender,argv[2],&flagalias,0);
  
+  if (!stralloc_copy(&outdomain,&canonhost)) temp_nomem();  /* is there a better way? */
+  localip = 0;
+  for (i = 0;i <= outdomain.len;++i)
+    if ((i == 0) || (i == outdomain.len) || (outdomain.s[i] == '.'))
+      if (localip = constmap(&mapoutgoingips,outdomain.s + i,outdomain.len - i))
+	break;
+  if (localip && !*localip) localip = 0;
+
+  if (localip) {
+    if (!ip_scan(localip,&outgoingip)) temp_noip();
+    if (!stralloc_copy(&helohost,&outdomain)) temp_nomem(); /* could be in control file */
+  }
+  else
+    outgoingip.d[0] = outgoingip.d[1] = outgoingip.d[2] = outgoingip.d[3] = (unsigned long) 0;
+ 
+  
   if (!saa_readyplus(&reciplist,0)) temp_nomem();
   if (ipme_init() != 1) temp_oserr();
  
@@ -414,9 +785,12 @@
     smtpfd = socket(AF_INET,SOCK_STREAM,0);
     if (smtpfd == -1) temp_oserr();
  
-    if (timeoutconn(smtpfd,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0) {
+    if (timeoutconn(smtpfd,&ip.ix[i].ip,&outgoingip,(unsigned int) port,timeoutconnect) == 0) {
       tcpto_err(&ip.ix[i].ip,0);
       partner = ip.ix[i].ip;
+#ifdef TLS
+      partner_fqdn = ip.ix[i].fqdn;
+#endif
       smtp(); /* does not return */
     }
     tcpto_err(&ip.ix[i].ip,errno == error_timeout);
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-send.9 ./qmail-send.9
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-send.9	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-send.9	2008-02-10 08:29:49.000000000 -0500
@@ -115,6 +115,10 @@
 (If that bounces,
 .B qmail-send
 gives up.)
+As a special case, if the first line of
+.IR doublebounceto
+is blank (contains a single linefeed), qmail-send will not queue
+the double-bounce at all.
 .TP 5
 .I envnoathost
 Presumed domain name for addresses without @ signs.
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-send.c ./qmail-send.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-send.c	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-send.c	2008-02-10 08:29:49.000000000 -0500
@@ -96,7 +96,7 @@
 }
 
 void fnmake_info(id) unsigned long id; { fn.len = fmtqfn(fn.s,"info/",id,1); }
-void fnmake_todo(id) unsigned long id; { fn.len = fmtqfn(fn.s,"todo/",id,0); }
+void fnmake_todo(id) unsigned long id; { fn.len = fmtqfn(fn.s,"todo/",id,1); }
 void fnmake_mess(id) unsigned long id; { fn.len = fmtqfn(fn.s,"mess/",id,1); }
 void fnmake_foop(id) unsigned long id; { fn.len = fmtqfn(fn.s,"foop/",id,0); }
 void fnmake_split(id) unsigned long id; { fn.len = fmtqfn(fn.s,"",id,1); }
@@ -262,6 +262,8 @@
  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();
@@ -683,6 +685,8 @@
   }
  if (str_equal(sender.s,"#@[]"))
    log3("triple bounce: discarding ",fn2.s,"\n");
+ else if (!*sender.s && *doublebounceto.s == '@')
+   log3("double bounce: discarding ",fn2.s,"\n");
  else
   {
    if (qmail_open(&qqt) == -1)
@@ -906,41 +910,42 @@
      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))
+   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[1] == 'Z')
+       if (dline[c].s[2] == 'Z')
 	 if (jo[d[c][delnum].j].flagdying)
 	  {
-	   dline[c].s[1] = 'D';
+	   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[1])
+       switch(dline[c].s[2])
 	{
 	 case 'K':
 	   log3("delivery ",strnum3,": success: ");
-	   logsafe(dline[c].s + 2);
+	   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 + 2);
+	   logsafe(dline[c].s + 3);
 	   log1("\n");
 	   break;
 	 case 'D':
 	   log3("delivery ",strnum3,": failure: ");
-	   logsafe(dline[c].s + 2);
+	   logsafe(dline[c].s + 3);
 	   log1("\n");
-	   addbounce(jo[d[c][delnum].j].id,d[c][delnum].recip.s,dline[c].s + 2);
+	   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;
@@ -1215,8 +1220,10 @@
 
 /* this file is too long ---------------------------------------------- TODO */
 
+#ifndef EXTERNAL_TODO
 datetime_sec nexttodorun;
-DIR *tododir; /* if 0, have to opendir again */
+int flagtododir = 0; /* if 0, have to readsubdir_init again */
+readsubdir todosubdir;
 stralloc todoline = {0};
 char todobuf[SUBSTDIO_INSIZE];
 char todobufinfo[512];
@@ -1224,7 +1231,7 @@
 
 void todo_init()
 {
- tododir = 0;
+ flagtododir = 0;
  nexttodorun = now();
  trigger_set();
 }
@@ -1236,7 +1243,7 @@
 {
  if (flagexitasap) return;
  trigger_selprep(nfds,rfds);
- if (tododir) *wakeup = 0;
+ if (flagtododir) *wakeup = 0;
  if (*wakeup > nexttodorun) *wakeup = nexttodorun;
 }
 
@@ -1253,8 +1260,7 @@
  char ch;
  int match;
  unsigned long id;
- unsigned int len;
- direntry *d;
+ int z;
  int c;
  unsigned long uid;
  unsigned long pid;
@@ -1265,32 +1271,26 @@
 
  if (flagexitasap) return;
 
- if (!tododir)
+ if (!flagtododir)
   {
    if (!trigger_pulled(rfds))
      if (recent < nexttodorun)
        return;
    trigger_set();
-   tododir = opendir("todo");
-   if (!tododir)
-    {
-     pausedir("todo");
-     return;
-    }
+   readsubdir_init(&todosubdir, "todo", pausedir);
+   flagtododir = 1;
    nexttodorun = recent + SLEEP_TODO;
   }
 
- d = readdir(tododir);
- if (!d)
+ switch(readsubdir_next(&todosubdir, &id))
   {
-   closedir(tododir);
-   tododir = 0;
+    case 1:
+      break;
+    case 0:
+      flagtododir = 0;
+    default:
    return;
   }
- if (str_equal(d->d_name,".")) return;
- if (str_equal(d->d_name,"..")) return;
- len = scan_ulong(d->d_name,&id);
- if (!len || d->d_name[len]) return;
 
  fnmake_todo(id);
 
@@ -1438,6 +1438,143 @@
    if (fdchan[c] != -1) close(fdchan[c]);
 }
 
+#endif
+
+/* this file is too long ------------------------------------- EXTERNAL TODO */
+
+#ifdef EXTERNAL_TODO
+stralloc todoline = {0};
+char todobuf[2048];
+int todofdin;
+int todofdout;
+int flagtodoalive;
+
+void tododied() { log1("alert: oh no! lost qmail-todo connection! dying...\n");
+ flagexitasap = 1; flagtodoalive = 0; }
+
+void todo_init()
+{
+  todofdout = 7;
+  todofdin = 8;
+  flagtodoalive = 1;
+  /* sync with external todo */
+  if (write(todofdout, "S", 1) != 1) tododied();
+  
+  return;
+}
+
+void todo_selprep(nfds,rfds,wakeup)
+int *nfds;
+fd_set *rfds;
+datetime_sec *wakeup;
+{
+  if (flagexitasap) {
+    if (flagtodoalive) {
+      write(todofdout, "X", 1);
+    }
+  }
+  if (flagtodoalive) {
+    FD_SET(todofdin,rfds);
+    if (*nfds <= todofdin)
+      *nfds = todofdin + 1;
+  }
+}
+
+void todo_del(char* s)
+{
+ int flagchan[CHANNELS];
+ struct prioq_elt pe;
+ unsigned long id;
+ unsigned int len;
+ int c;
+
+ for (c = 0;c < CHANNELS;++c) flagchan[c] = 0;
+ switch(*s++) {
+  case 'L':
+    flagchan[0] = 1;
+    break;
+  case 'R':
+    flagchan[1] = 1;
+    break;
+  case 'B':
+    flagchan[0] = 1;
+    flagchan[1] = 1;
+    break;
+  case 'X':
+    break;
+  default:
+    log1("warning: qmail-send unable to understand qmail-todo\n");
+    return;
+ }
+ 
+ len = scan_ulong(s,&id);
+ if (!len || s[len]) {
+  log1("warning: qmail-send unable to understand qmail-todo\n");
+  return;
+ }
+
+ pe.id = id; pe.dt = now();
+ for (c = 0;c < CHANNELS;++c)
+   if (flagchan[c])
+     while (!prioq_insert(&pqchan[c],&pe)) nomem();
+
+ for (c = 0;c < CHANNELS;++c) if (flagchan[c]) break;
+ if (c == CHANNELS)
+   while (!prioq_insert(&pqdone,&pe)) nomem();
+
+ return;
+}
+
+void todo_do(rfds)
+fd_set *rfds;
+{
+  int r;
+  char ch;
+  int i;
+  
+  if (!flagtodoalive) return;
+  if (!FD_ISSET(todofdin,rfds)) return;
+
+  r = read(todofdin,todobuf,sizeof(todobuf));
+  if (r == -1) return;
+  if (r == 0) {
+    if (flagexitasap)
+      flagtodoalive = 0;
+    else
+      tododied();
+    return;
+  }
+  for (i = 0;i < r;++i) {
+    ch = todobuf[i];
+    while (!stralloc_append(&todoline,&ch)) nomem();
+    if (todoline.len > REPORTMAX)
+      todoline.len = REPORTMAX;
+      /* qmail-todo is responsible for keeping it short */
+    if (!ch && (todoline.len > 1)) {
+      switch (todoline.s[0]) {
+	case 'D':
+	  if (flagexitasap) break;
+	  todo_del(todoline.s + 1);
+	  break;
+	case 'L':
+	  log1(todoline.s + 1);
+	  break;
+	case 'X':
+	  if (flagexitasap)
+	    flagtodoalive = 0;
+	  else
+	    tododied();
+	  break;
+	default:
+	  log1("warning: qmail-send unable to understand qmail-todo: report mangled\n");
+	  break;
+      }
+      todoline.len = 0;
+    }
+  }
+}
+
+#endif
 
 /* this file is too long ---------------------------------------------- MAIN */
 
@@ -1504,6 +1641,9 @@
    log1("alert: unable to reread controls: unable to switch to home directory\n");
    return;
   }
+#ifdef EXTERNAL_TODO
+ write(todofdout, "H", 1);
+#endif
  regetcontrols();
  while (chdir("queue") == -1)
   {
@@ -1544,7 +1684,7 @@
  numjobs = 0;
  for (c = 0;c < CHANNELS;++c)
   {
-   char ch;
+   char ch, ch1;
    int u;
    int r;
    do
@@ -1552,7 +1692,13 @@
    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];
   }
@@ -1568,7 +1714,11 @@
  todo_init();
  cleanup_init();
 
+#ifdef EXTERNAL_TODO
+ while (!flagexitasap || !del_canexit() || flagtodoalive)
+#else
  while (!flagexitasap || !del_canexit())
+#endif
   {
    recent = now();
 
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-showctl.c ./qmail-showctl.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-showctl.c	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-showctl.c	2008-02-10 08:29:50.000000000 -0500
@@ -230,6 +230,7 @@
   do_str("localiphost",1,"localiphost","Local IP address becomes ");
   do_lst("locals","Messages for me are delivered locally.","Messages for "," are delivered locally.");
   do_str("me",0,"undefined! Uh-oh","My name is ");
+  do_lst("outgoingips","No outgoing ip mappings defined.","Map from sender domain part to local ip: ","");
   do_lst("percenthack","The percent hack is not allowed.","The percent hack is allowed for user%host@",".");
   do_str("plusdomain",1,"plusdomain","Plus domain name is ");
   do_lst("qmqpservers","No QMQP servers.","QMQP server: ",".");
@@ -296,6 +297,7 @@
     if (str_equal(d->d_name,"timeoutremote")) continue;
     if (str_equal(d->d_name,"timeoutsmtpd")) continue;
     if (str_equal(d->d_name,"virtualdomains")) continue;
+    if (str_equal(d->d_name,"outgoingips")) continue;
     substdio_puts(subfdout,"\n");
     substdio_puts(subfdout,d->d_name);
     substdio_puts(subfdout,": I have no idea what this file does.\n");
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-smtpd.8 ./qmail-smtpd.8
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-smtpd.8	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-smtpd.8	2008-02-10 08:29:47.000000000 -0500
@@ -14,6 +14,15 @@
 see
 .BR tcp-environ(5) .
 
+If the environment variable
+.B SMTPS
+is non-empty,
+.B qmail-smtpd
+starts a TLS session (to support the deprecated SMTPS protocol,
+normally on port 465). Otherwise,
+.B qmail-smtpd
+offers the STARTTLS extension to ESMTP.
+
 .B qmail-smtpd
 is responsible for counting hops.
 It rejects any message with 100 or more 
@@ -23,7 +32,30 @@
 header fields.
 
 .B qmail-smtpd
-supports ESMTP, including the 8BITMIME and PIPELINING options.
+supports ESMTP, including the 8BITMIME, DATA, PIPELINING, SIZE, and AUTH options.
+.B qmail-smtpd
+includes a \'MAIL FROM:\' parameter parser and obeys \'Auth\' and \'Size\' advertisements.
+.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 CRAM-MD5 digest/response derived from the SMTP client,
+another 0 byte, a CRAM-MD5 challenge (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
@@ -49,6 +81,19 @@
 .BR @\fIhost ,
 meaning every address at
 .IR host .
+
+.TP 5
+.I clientca.pem
+A list of Certifying Authority (CA) certificates that are used to verify
+the client-presented certificates during a TLS-encrypted session.
+
+.TP 5
+.I clientcrl.pem
+A list of Certificate Revocation Lists (CRLs). If present it
+should contain the CRLs of the CAs in 
+.I clientca.pem 
+and client certs will be checked for revocation.
+
 .TP 5
 .I databytes
 Maximum number of bytes allowed in a message,
@@ -76,6 +121,18 @@
 .B DATABYTES
 is set, it overrides
 .IR databytes .
+
+.TP 5
+.I dh1024.pem
+If these 1024 bit DH parameters are provided,
+.B qmail-smtpd
+will use them for TLS sessions instead of generating one on-the-fly 
+(which is very timeconsuming).
+.TP 5
+.I dh512.pem
+512 bit counterpart for 
+.B dh1024.pem. 
+
 .TP 5
 .I localiphost
 Replacement host name for local IP addresses.
@@ -151,6 +208,19 @@
 
 Envelope recipient addresses without @ signs are
 always allowed through.
+
+.TP 5
+.I rsa512.pem
+If this 512 bit RSA key is provided,
+.B qmail-smtpd
+will use it for TLS sessions instead of generating one on-the-fly.
+
+.TP 5
+.I servercert.pem
+SSL certificate to be presented to clients in TLS-encrypted sessions. 
+Should contain both the certificate and the private key. Certifying Authority
+(CA) and intermediate certificates can be added at the end of the file.
+
 .TP 5
 .I smtpgreeting
 SMTP greeting message.
@@ -169,6 +239,24 @@
 .B qmail-smtpd
 will wait for each new buffer of data from the remote SMTP client.
 Default: 1200.
+
+.TP 5
+.I tlsclients
+A list of email addresses. When relay rules would reject an incoming message,
+.B qmail-smtpd
+can allow it if the client presents a certificate that can be verified against
+the CA list in
+.I clientca.pem
+and the certificate email address is in
+.IR tlsclients .
+
+.TP 5
+.I tlsserverciphers
+A set of OpenSSL cipher strings. Multiple ciphers contained in a
+string should be separated by a colon. If the environment variable
+.B TLSCIPHERS
+is set to such a string, it takes precedence.
+
 .SH "SEE ALSO"
 tcp-env(1),
 tcp-environ(5),
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-smtpd.c ./qmail-smtpd.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-smtpd.c	2008-02-10 08:30:51.000000000 -0500
+++ ./qmail-smtpd.c	2008-02-10 08:29:48.000000000 -0500
@@ -23,14 +23,36 @@
 #include "timeoutread.h"
 #include "timeoutwrite.h"
 #include "commands.h"
+#include "strerr.h"
+#include "cdb.h"
+#include "wait.h"
+
+#define CRAM_MD5
+#define AUTHSLEEP 5
 
 #define MAXHOPS 100
 unsigned int databytes = 0;
 int timeout = 1200;
 
+#ifdef TLS
+#include <sys/stat.h>
+#include "tls.h"
+#include "ssl_timeoutio.h"
+
+void tls_init();
+int tls_verify();
+void tls_nogateway();
+int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */
+#endif
+
 int safewrite(fd,buf,len) int fd; char *buf; int len;
 {
   int r;
+#ifdef TLS
+  if (ssl && fd == ssl_wfd)
+    r = ssl_timeoutwrite(timeout, ssl_rfd, ssl_wfd, ssl, buf, len);
+  else
+#endif
   r = timeoutwrite(timeout,fd,buf,len);
   if (r <= 0) _exit(1);
   return r;
@@ -49,8 +71,18 @@
 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_size() { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); }
 void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }
+#ifndef TLS
 void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
+#else
+void err_nogateway()
+{
+  out("553 sorry, that domain isn't in my list of allowed rcpthosts");
+  tls_nogateway();
+  out(" (#5.7.1)\r\n");
+}
+#endif
 void err_unimpl(arg) char *arg; { 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"); }
@@ -59,6 +91,19 @@
 void err_vrfy(arg) char *arg; { 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 canceled (#5.0.0)\r\n"); return -1; }
+int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; }
+void err_authfail() { out("535 authentication failed (#5.7.1)\r\n"); }
+
+void err_vrt() { out("553 sorry, this recipient is not in my validrcptto list (#5.7.1)\r\n"); }
+void die_vrt() { out("421 too many invalid addresses, goodbye (#4.3.0)\r\n"); flush(); _exit(1); }
 
 stralloc greeting = {0};
 
@@ -76,6 +121,7 @@
   smtp_greet("221 "); out("\r\n"); flush(); _exit(0);
 }
 
+char *protocol;
 char *remoteip;
 char *remotehost;
 char *remoteinfo;
@@ -97,6 +143,11 @@
 stralloc bmf = {0};
 struct constmap mapbmf;
 
+int vrtfd = -1;
+int vrtcount = 0;
+int vrtlimit = 10;
+int vrtlog_do = 0;
+
 void setup()
 {
   char *x;
@@ -109,19 +160,22 @@
   if (liphostok == -1) die_control();
   if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control();
   if (timeout <= 0) timeout = 1;
-
   if (rcpthosts_init() == -1) die_control();
 
   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();
+
+    vrtfd = open_read("control/validrcptto.cdb");
+    if (-1 == vrtfd) if (errno != error_noent) die_control();
  
   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;
  
+  protocol = "SMTP";
   remoteip = env_get("TCPREMOTEIP");
   if (!remoteip) remoteip = "unknown";
   local = env_get("TCPLOCALHOST");
@@ -131,6 +185,11 @@
   if (!remotehost) remotehost = "unknown";
   remoteinfo = env_get("TCPREMOTEINFO");
   relayclient = env_get("RELAYCLIENT");
+
+#ifdef TLS
+  if (env_get("SMTPS")) { smtps = 1; tls_init(); }
+  else
+#endif
   dohelo(remotehost);
 }
 
@@ -208,28 +267,175 @@
   return 0;
 }
 
+void vrtlog(a,b)
+const char *a;
+const char *b;
+{
+  if (vrtlog_do) strerr_warn2(a,b,0);
+}
+
+int vrtcheck()
+{
+  static char *rcptto = "qmail-smtpd: validrcptto RCPT TO: ";
+  static char *found = "qmail-smtpd: validrcptto found: ";
+  int j,k,r;
+  uint32 dlen;
+  stralloc laddr = {0};
+
+  stralloc user = {0};
+  stralloc adom = {0};
+  stralloc utry = {0};
+
+  if (-1 == vrtfd) return 1;
+
+  /* lowercase whatever we were sent */
+  if (!stralloc_copy(&laddr,&addr)) die_nomem() ;
+  case_lowerb(laddr.s,laddr.len);
+
+  vrtlog(rcptto,laddr.s,0);
+
+  /* exact match? */
+  r = cdb_seek(vrtfd,laddr.s,laddr.len-1,&dlen) ;
+  if (r>0) { vrtlog(found,laddr.s,0); return r; }
+
+  j = byte_rchr(laddr.s,laddr.len,'@');
+  if (j < laddr.len)
+  {
+    /* start "-default" search loop */
+    stralloc_copyb(&user,laddr.s,j) ;
+    stralloc_copyb(&adom,laddr.s+j,laddr.len-j-1);
+
+    while(1)
+    {
+      k = byte_rchr(user.s,user.len,'-');
+      if (k >= user.len) break ;
+
+      user.len = k+1;
+      stralloc_cats(&user,"default");
+
+      stralloc_copy(&utry,&user);
+      stralloc_cat (&utry,&adom);
+      stralloc_0(&utry);
+
+      r = cdb_seek(vrtfd,utry.s,utry.len-1,&dlen);
+      if (r>0) { vrtlog(found,utry.s,0); return r; }
+
+      user.len = k ;
+    }
+
+    /* try "@domain" */
+
+    r = cdb_seek(vrtfd,laddr.s+j,laddr.len-j-1,&dlen) ;
+    if (r>0) { vrtlog(found,laddr.s+j,0); return r; }
+  }
+
+  return 0;
+}
+
 int addrallowed()
 {
   int r;
   r = rcpthosts(addr.s,str_len(addr.s));
   if (r == -1) die_control();
+#ifdef TLS
+  if (r == 0) if (tls_verify()) r = -2;
+#endif
   return r;
 }
 
 
 int seenmail = 0;
 int flagbarf; /* defined if seenmail */
+int flagsize;
 stralloc mailfrom = {0};
 stralloc rcptto = {0};
+stralloc fuser = {0};
+stralloc mfparms = {0};
+
+int mailfrom_size(arg) char *arg;
+{
+  long r;
+  unsigned long sizebytes = 0;
+
+  scan_ulong(arg,&r);
+  sizebytes = r;
+  if (databytes) if (sizebytes > databytes) return 1;
+  return 0;
+}
+
+void mailfrom_auth(arg,len) 
+char *arg; 
+int len;
+{
+  int j;
+
+  if (!stralloc_copys(&fuser,"")) die_nomem();
+  if (case_starts(arg,"<>")) { if (!stralloc_cats(&fuser,"unknown")) die_nomem(); }
+  else 
+    while (len) {
+      if (*arg == '+') {
+        if (case_starts(arg,"+3D")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"=")) die_nomem(); }
+        if (case_starts(arg,"+2B")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"+")) die_nomem(); }
+      }
+      else
+        if (!stralloc_catb(&fuser,arg,1)) die_nomem();
+      arg++; len--;
+    }
+  if(!stralloc_0(&fuser)) die_nomem();
+  if (!remoteinfo) {
+    remoteinfo = fuser.s;
+    if (!env_unset("TCPREMOTEINFO")) die_read();
+    if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem();
+  }
+}
+
+void mailfrom_parms(arg) char *arg;
+{
+  int i;
+  int len;
+
+    len = str_len(arg);
+    if (!stralloc_copys(&mfparms,"")) die_nomem;
+    i = byte_chr(arg,len,'>');
+    if (i > 4 && i < len) {
+      while (len) {
+        arg++; len--; 
+        if (*arg == ' ' || *arg == '\0' ) {
+           if (case_starts(mfparms.s,"SIZE=")) if (mailfrom_size(mfparms.s+5)) { flagsize = 1; return; }
+           if (case_starts(mfparms.s,"AUTH=")) mailfrom_auth(mfparms.s+5,mfparms.len-5);  
+           if (!stralloc_copys(&mfparms,"")) die_nomem;
+        }
+        else
+          if (!stralloc_catb(&mfparms,arg,1)) die_nomem; 
+      }
+    }
+}
 
 void smtp_helo(arg) char *arg;
 {
   smtp_greet("250 "); out("\r\n");
   seenmail = 0; dohelo(arg);
 }
+/* ESMTP extensions are published here */
 void smtp_ehlo(arg) char *arg;
 {
-  smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
+#ifdef TLS
+  struct stat st;
+#endif
+  char size[FMT_ULONG];
+  smtp_greet("250-"); 
+#ifdef TLS
+  if (!ssl && (stat("control/servercert.pem",&st) == 0))
+    out("\r\n250-STARTTLS");
+#endif
+  size[fmt_ulong(size,(unsigned int) databytes)] = 0;
+  out("\r\n250-PIPELINING\r\n250-8BITMIME\r\n");
+  out("250-SIZE "); out(size); out("\r\n");
+#ifdef CRAM_MD5
+  out("250 AUTH LOGIN PLAIN CRAM-MD5\r\n");
+#else
+  out("250 AUTH LOGIN PLAIN\r\n");
+#endif
   seenmail = 0; dohelo(arg);
 }
 void smtp_rset(arg) char *arg;
@@ -240,6 +446,9 @@
 void smtp_mail(arg) char *arg;
 {
   if (!addrparse(arg)) { err_syntax(); return; }
+  flagsize = 0;
+  mailfrom_parms(arg);
+  if (flagsize) { err_size(); return; }
   flagbarf = bmfcheck();
   seenmail = 1;
   if (!stralloc_copys(&rcptto,"")) die_nomem();
@@ -258,6 +467,17 @@
   }
   else
     if (!addrallowed()) { err_nogateway(); return; }
+  if (!env_get("RELAYCLIENT") && !vrtcheck()) {
+    strerr_warn4("qmail-smtpd: not in validrcptto: ",addr.s,
+      " at ",remoteip,0);
+    if(vrtlimit && (++vrtcount >= vrtlimit)) {
+      strerr_warn2("qmail-smtpd: excessive validrcptto violations,"
+        " hanging up on ",remoteip,0);
+      die_vrt();
+    }
+    err_vrt();
+    return;
+  }
   if (!stralloc_cats(&rcptto,"T")) die_nomem();
   if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
   if (!stralloc_0(&rcptto)) die_nomem();
@@ -269,6 +489,11 @@
 {
   int r;
   flush();
+#ifdef TLS
+  if (ssl && fd == ssl_rfd)
+    r = ssl_timeoutread(timeout, ssl_rfd, ssl_wfd, ssl, buf, len);
+  else
+#endif
   r = timeoutread(timeout,fd,buf,len);
   if (r == -1) if (errno == error_timeout) die_alarm();
   if (r <= 0) die_read();
@@ -378,7 +603,7 @@
   qp = qmail_qp(&qqt);
   out("354 go ahead\r\n");
  
-  received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);
+  received(&qqt,protocol,local,remoteip,remotehost,remoteinfo,fakehelo);
   blast(&hops);
   hops = (hops >= MAXHOPS);
   if (hops) qmail_fail(&qqt);
@@ -388,30 +613,504 @@
   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 (databytes) if (!bytestooverflow) { err_size(); return; }
   if (*qqx == 'D') out("554 "); else out("451 ");
   out(qqx + 1);
   out("\r\n");
 }
 
+/* this file is too long ----------------------------------------- SMTP AUTH */
+
+char unique[FMT_ULONG + FMT_ULONG + 3];
+static stralloc authin = {0};   /* input from SMTP client */
+static stralloc user = {0};     /* authorization user-id */
+static stralloc pass = {0};     /* plain passwd or digest */
+static stralloc resp = {0};     /* b64 response */
+#ifdef CRAM_MD5
+static stralloc chal = {0};     /* plain challenge */
+static stralloc slop = {0};     /* b64 challenge */
+#endif
+
+int flagauth = 0;
+char **childargs;
+char ssauthbuf[512];
+substdio ssauth = SUBSTDIO_FDBUF(safewrite,3,ssauthbuf,sizeof(ssauthbuf));
+
+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();
+#ifdef CRAM_MD5
+  if (!stralloc_0(&chal)) die_nomem();
+#endif
+
+  if (pipe(pi) == -1) return err_pipe();
+  switch(child = fork()) {
+    case -1:
+      return err_fork();
+    case 0:
+      close(pi[1]);
+      if(fd_copy(3,pi[0]) == -1) return err_pipe();
+      sig_pipedefault();
+        execvp(*childargs, childargs);
+      _exit(1);
+  }
+  close(pi[0]);
+
+  substdio_fdbuf(&ssauth,write,pi[1],ssauthbuf,sizeof ssauthbuf);
+  if (substdio_put(&ssauth,user.s,user.len) == -1) return err_write();
+  if (substdio_put(&ssauth,pass.s,pass.len) == -1) return err_write();
+#ifdef CRAM_MD5
+  if (substdio_put(&ssauth,chal.s,chal.len) == -1) return err_write();
+#endif
+  if (substdio_flush(&ssauth) == -1) return err_write();
+
+  close(pi[1]);
+#ifdef CRAM_MD5
+  if (!stralloc_copys(&chal,"")) die_nomem();
+  if (!stralloc_copys(&slop,"")) die_nomem();
+#endif
+  byte_zero(ssauthbuf,sizeof ssauthbuf);
+  if (wait_pid(&wstat,child) == -1) return err_child();
+  if (wait_crashed(wstat)) return err_child();
+  if (wait_exitcode(wstat)) { sleep(AUTHSLEEP); 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),&resp) == 1) return err_input();
+  }
+  else {
+    out("334 \r\n"); flush();
+    if (authgetl() < 0) return -1;
+    if (r = b64decode(authin.s,authin.len,&resp) == 1) return err_input();
+  }
+  if (r == -1 || !stralloc_0(&resp)) die_nomem();
+  while (resp.s[id]) id++;                       /* "authorize-id\0userid\0passwd\0" */
+
+  if (resp.len > id + 1)
+    if (!stralloc_copys(&user,resp.s + id + 1)) die_nomem();
+  if (resp.len > id + user.len + 2)
+    if (!stralloc_copys(&pass,resp.s + id + user.len + 2)) die_nomem();
+
+  if (!user.len || !pass.len) return err_input();
+  return authenticate();
+}
+
+#ifdef CRAM_MD5
+int auth_cram()
+{
+  int i, r;
+  char *s;
+
+  s = unique;                                           /* generate challenge */
+  s += fmt_uint(s,getpid());
+  *s++ = '.';
+  s += fmt_ulong(s,(unsigned long) now());
+  *s++ = '@';
+  *s++ = 0;
+  if (!stralloc_copys(&chal,"<")) die_nomem();
+  if (!stralloc_cats(&chal,unique)) die_nomem();
+  if (!stralloc_cats(&chal,local)) die_nomem();
+  if (!stralloc_cats(&chal,">")) die_nomem();
+  if (b64encode(&chal,&slop) < 0) die_nomem();
+  if (!stralloc_0(&slop)) die_nomem();
+
+  out("334 ");                                          /* "334 base64_challenge \r\n" */
+  out(slop.s);
+  out("\r\n");
+  flush();
+
+  if (authgetl() < 0) return -1;                        /* got response */
+  if (r = b64decode(authin.s,authin.len,&resp) == 1) return err_input();
+  if (r == -1 || !stralloc_0(&resp)) die_nomem();
+
+  i = str_chr(resp.s,' ');
+  s = resp.s + i;
+  while (*s == ' ') ++s;
+  resp.s[i] = 0;
+  if (!stralloc_copys(&user,resp.s)) die_nomem();       /* userid */
+  if (!stralloc_copys(&pass,s)) die_nomem();            /* digest */
+
+  if (!user.len || !pass.len) return err_input();
+  return authenticate();
+}
+#endif
+
+struct authcmd {
+  char *text;
+  int (*fun)();
+} authcmds[] = {
+  { "login",auth_login }
+,  { "plain",auth_plain }
+#ifdef CRAM_MD5
+, { "cram-md5",auth_cram }
+#endif
+, { 0,err_noauth }
+};
+
+void smtp_auth(arg)
+char *arg;
+{
+  int i;
+  char *cmd = arg;
+
+  if (!*childargs) { out("503 auth not available (#5.3.3)\r\n"); return; }
+  if (flagauth) { 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();
+#ifdef CRAM_MD5
+  if (!stralloc_copys(&chal,"")) die_nomem();
+#endif
+
+  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:
+      flagauth = 1;
+      protocol = "ESMTPA";
+      relayclient = "";
+      remoteinfo = user.s;
+      if (!env_unset("TCPREMOTEINFO")) die_read();
+      if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem();
+      if (!env_put2("RELAYCLIENT",relayclient)) die_nomem();
+      out("235 ok, go ahead (#2.0.0)\r\n");
+      break;
+    case 1:
+      err_authfail(user.s,authcmds[i].text);
+  }
+}
+
+
+/* this file is too long --------------------------------------------- GO ON */
+
+#ifdef TLS
+stralloc proto = {0};
+int ssl_verified = 0;
+const char *ssl_verify_err = 0;
+
+void smtp_tls(char *arg)
+{
+  if (ssl) err_unimpl();
+  else if (*arg) out("501 Syntax error (no parameters allowed) (#5.5.4)\r\n");
+  else tls_init();
+}
+
+RSA *tmp_rsa_cb(SSL *ssl, int export, int keylen)
+{
+  if (!export) keylen = 512;
+  if (keylen == 512) {
+    FILE *in = fopen("control/rsa512.pem", "r");
+    if (in) {
+      RSA *rsa = PEM_read_RSAPrivateKey(in, NULL, NULL, NULL);
+      fclose(in);
+      if (rsa) return rsa;
+    }
+  }
+  return RSA_generate_key(keylen, RSA_F4, NULL, NULL);
+}
+
+DH *tmp_dh_cb(SSL *ssl, int export, int keylen)
+{
+  if (!export) keylen = 1024;
+  if (keylen == 512) {
+    FILE *in = fopen("control/dh512.pem", "r");
+    if (in) {
+      DH *dh = PEM_read_DHparams(in, NULL, NULL, NULL);
+      fclose(in);
+      if (dh) return dh;
+    }
+  }
+  if (keylen == 1024) {
+    FILE *in = fopen("control/dh1024.pem", "r");
+    if (in) {
+      DH *dh = PEM_read_DHparams(in, NULL, NULL, NULL);
+      fclose(in);
+      if (dh) return dh;
+    }
+  }
+  return DH_generate_parameters(keylen, DH_GENERATOR_2, NULL, NULL);
+} 
+
+/* don't want to fail handshake if cert isn't verifiable */
+int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; }
+
+void tls_nogateway()
+{
+  /* there may be cases when relayclient is set */
+  if (!ssl || relayclient) return;
+  out("; no valid cert for gatewaying");
+  if (ssl_verify_err) { out(": "); out(ssl_verify_err); }
+}
+void tls_out(const char *s1, const char *s2)
+{
+  out("454 TLS "); out(s1);
+  if (s2) { out(": "); out(s2); }
+  out(" (#4.3.0)\r\n"); flush();
+}
+void tls_err(const char *s) { tls_out(s, ssl_error()); if (smtps) die_read(); }
+
+# define CLIENTCA "control/clientca.pem"
+# define CLIENTCRL "control/clientcrl.pem"
+# define SERVERCERT "control/servercert.pem"
+
+int tls_verify()
+{
+  stralloc clients = {0};
+  struct constmap mapclients;
+
+  if (!ssl || relayclient || ssl_verified) return 0;
+  ssl_verified = 1; /* don't do this twice */
+
+  /* request client cert to see if it can be verified by one of our CAs
+   * and the associated email address matches an entry in tlsclients */
+  switch (control_readfile(&clients, "control/tlsclients", 0))
+  {
+  case 1:
+    if (constmap_init(&mapclients, clients.s, clients.len, 0)) {
+      /* if CLIENTCA contains all the standard root certificates, a
+       * 0.9.6b client might fail with SSL_R_EXCESSIVE_MESSAGE_SIZE;
+       * it is probably due to 0.9.6b supporting only 8k key exchange
+       * data while the 0.9.6c release increases that limit to 100k */
+      STACK_OF(X509_NAME) *sk = SSL_load_client_CA_file(CLIENTCA);
+      if (sk) {
+        SSL_set_client_CA_list(ssl, sk);
+        SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, NULL);
+        break;
+      }
+      constmap_free(&mapclients);
+    }
+  case 0: alloc_free(clients.s); return 0;
+  case -1: die_control();
+  }
+
+  if (ssl_timeoutrehandshake(timeout, ssl_rfd, ssl_wfd, ssl) <= 0) {
+    const char *err = ssl_error_str();
+    tls_out("rehandshake failed", err); die_read();
+  }
+
+  do { /* one iteration */
+    X509 *peercert;
+    X509_NAME *subj;
+    stralloc email = {0};
+
+    int n = SSL_get_verify_result(ssl);
+    if (n != X509_V_OK)
+      { ssl_verify_err = X509_verify_cert_error_string(n); break; }
+    peercert = SSL_get_peer_certificate(ssl);
+    if (!peercert) break;
+
+    subj = X509_get_subject_name(peercert);
+    n = X509_NAME_get_index_by_NID(subj, NID_pkcs9_emailAddress, -1);
+    if (n >= 0) {
+      const ASN1_STRING *s = X509_NAME_get_entry(subj, n)->value;
+      if (s) { email.len = s->length; email.s = s->data; }
+    }
+
+    if (email.len <= 0)
+      ssl_verify_err = "contains no email address";
+    else if (!constmap(&mapclients, email.s, email.len))
+      ssl_verify_err = "email address not in my list of tlsclients";
+    else {
+      /* add the cert email to the proto if it helped allow relaying */
+      --proto.len;
+      if (!stralloc_cats(&proto, "\n  (cert ") /* continuation line */
+        || !stralloc_catb(&proto, email.s, email.len)
+        || !stralloc_cats(&proto, ")")
+        || !stralloc_0(&proto)) die_nomem();
+      relayclient = "";
+      protocol = proto.s;
+    }
+
+    X509_free(peercert);
+  } while (0);
+  constmap_free(&mapclients); alloc_free(clients.s);
+
+  /* we are not going to need this anymore: free the memory */
+  SSL_set_client_CA_list(ssl, NULL);
+  SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
+
+  return relayclient ? 1 : 0;
+}
+
+void tls_init()
+{
+  SSL *myssl;
+  SSL_CTX *ctx;
+  const char *ciphers;
+  stralloc saciphers = {0};
+  X509_STORE *store;
+  X509_LOOKUP *lookup;
+
+  SSL_library_init();
+
+  /* a new SSL context with the bare minimum of options */
+  ctx = SSL_CTX_new(SSLv23_server_method());
+  if (!ctx) { tls_err("unable to initialize ctx"); return; }
+
+  if (!SSL_CTX_use_certificate_chain_file(ctx, SERVERCERT))
+    { SSL_CTX_free(ctx); tls_err("missing certificate"); return; }
+  SSL_CTX_load_verify_locations(ctx, CLIENTCA, NULL);
+
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+  /* crl checking */
+  store = SSL_CTX_get_cert_store(ctx);
+  if ((lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file())) &&
+      (X509_load_crl_file(lookup, CLIENTCRL, X509_FILETYPE_PEM) == 1))
+    X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
+                                X509_V_FLAG_CRL_CHECK_ALL);
+#endif
+
+  /* set the callback here; SSL_set_verify didn't work before 0.9.6c */
+  SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, verify_cb);
+
+  /* a new SSL object, with the rest added to it directly to avoid copying */
+  myssl = SSL_new(ctx);
+  SSL_CTX_free(ctx);
+  if (!myssl) { tls_err("unable to initialize ssl"); return; }
+
+  /* this will also check whether public and private keys match */
+  if (!SSL_use_RSAPrivateKey_file(myssl, SERVERCERT, SSL_FILETYPE_PEM))
+    { SSL_free(myssl); tls_err("no valid RSA private key"); return; }
+
+  ciphers = env_get("TLSCIPHERS");
+  if (!ciphers) {
+    if (control_readfile(&saciphers, "control/tlsserverciphers", 0) == -1)
+      { SSL_free(myssl); die_control(); }
+    if (saciphers.len) { /* convert all '\0's except the last one to ':' */
+      int i;
+      for (i = 0; i < saciphers.len - 1; ++i)
+        if (!saciphers.s[i]) saciphers.s[i] = ':';
+      ciphers = saciphers.s;
+    }
+  }
+  if (!ciphers || !*ciphers) ciphers = "DEFAULT";
+  SSL_set_cipher_list(myssl, ciphers);
+  alloc_free(saciphers.s);
+
+  SSL_set_tmp_rsa_callback(myssl, tmp_rsa_cb);
+  SSL_set_tmp_dh_callback(myssl, tmp_dh_cb);
+  SSL_set_rfd(myssl, ssl_rfd = substdio_fileno(&ssin));
+  SSL_set_wfd(myssl, ssl_wfd = substdio_fileno(&ssout));
+
+  if (!smtps) { out("220 ready for tls\r\n"); flush(); }
+
+  if (ssl_timeoutaccept(timeout, ssl_rfd, ssl_wfd, myssl) <= 0) {
+    /* neither cleartext nor any other response here is part of a standard */
+    const char *err = ssl_error_str();
+    ssl_free(myssl); tls_out("connection failed", err); die_read();
+  }
+  ssl = myssl;
+
+  /* populate the protocol string, used in Received */
+  if (!stralloc_copys(&proto, "ESMTPS (")
+    || !stralloc_cats(&proto, SSL_get_cipher(ssl))
+    || !stralloc_cats(&proto, " encrypted)")) die_nomem();
+  if (!stralloc_0(&proto)) die_nomem();
+  protocol = proto.s;
+
+  /* have to discard the pre-STARTTLS HELO/EHLO argument, if any */
+  dohelo(remotehost);
+}
+
+# undef SERVERCERT
+# undef CLIENTCA
+
+#endif
+
 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 }
 , { "rset", smtp_rset, 0 }
 , { "help", smtp_help, flush }
+#ifdef TLS
+, { "starttls", smtp_tls, flush }
+#endif
 , { "noop", err_noop, flush }
 , { "vrfy", err_vrfy, flush }
 , { 0, err_unimpl, flush }
 } ;
 
-void main()
+void main(argc,argv)
+int argc;
+char **argv;
 {
+  char *x ;
+  uint32 u;
+  childargs = argv + 1;
   sig_pipeignore();
   if (chdir(auto_qmail) == -1) die_control();
+
+  x = env_get("VALIDRCPTTO_LIMIT");
+  if(x) { scan_ulong(x,&u); vrtlimit = (int) u; }
+  x = env_get("VALIDRCPTTO_LOG");
+  if(x) { scan_ulong(x,&u); vrtlog_do = (int) u; }
+
   setup();
   if (ipme_init() != 1) die_ipme();
   smtp_greet("220 ");
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-start.c ./qmail-start.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-start.c	1998-06-15 06:53:16.000000000 -0400
+++ ./qmail-start.c	2008-02-10 08:29:49.000000000 -0500
@@ -8,6 +8,9 @@
 char *(qcargs[]) = { "qmail-clean", 0 };
 char *(qlargs[]) = { "qmail-lspawn", "./Mailbox", 0 };
 char *(qrargs[]) = { "qmail-rspawn", 0 };
+#ifdef EXTERNAL_TODO
+char *(qtargs[]) = { "qmail-todo", 0};
+#endif
 
 void die() { _exit(111); }
 
@@ -18,13 +21,28 @@
 int pi4[2];
 int pi5[2];
 int pi6[2];
-
-void close23456() { close(2); close(3); close(4); close(5); close(6); }
+#ifdef EXTERNAL_TODO
+int pi7[2];
+int pi8[2];
+int pi9[2];
+int pi10[2];
+#endif
+
+void close23456() { 
+  close(2); close(3); close(4); close(5); close(6); 
+#ifdef EXTERNAL_TODO
+  close(7); close(8);
+#endif
+}
 
 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]);
+#ifdef EXTERNAL_TODO
+  close(pi7[0]); close(pi7[1]); close(pi8[0]); close(pi8[1]);
+	close(pi9[0]); close(pi9[1]); close(pi10[0]); close(pi10[1]);
+#endif
 }
 
 void main(argc,argv)
@@ -40,6 +58,10 @@
   if (fd_copy(4,0) == -1) die();
   if (fd_copy(5,0) == -1) die();
   if (fd_copy(6,0) == -1) die();
+#ifdef EXTERNAL_TODO
+  if (fd_copy(7,0) == -1) die();
+  if (fd_copy(8,0) == -1) die();
+#endif
 
   if (argv[1]) {
     qlargs[1] = argv[1];
@@ -70,6 +92,12 @@
   if (pipe(pi4) == -1) die();
   if (pipe(pi5) == -1) die();
   if (pipe(pi6) == -1) die();
+#ifdef EXTERNAL_TODO
+  if (pipe(pi7) == -1) die();
+  if (pipe(pi8) == -1) die();
+  if (pipe(pi9) == -1) die();
+  if (pipe(pi10) == -1) die();
+#endif
  
   switch(fork()) {
     case -1: die();
@@ -106,6 +134,34 @@
       die();
   }
  
+#ifdef EXTERNAL_TODO
+  switch(fork()) {
+    case -1: die();
+    case 0:
+      if (prot_uid(auto_uids) == -1) die();
+      if (fd_copy(0,pi7[0]) == -1) die();
+      if (fd_copy(1,pi8[1]) == -1) die();
+      close23456();
+      if (fd_copy(2,pi9[1]) == -1) die();
+      if (fd_copy(3,pi10[0]) == -1) die();
+      closepipes();
+      execvp(*qtargs,qtargs);
+      die();
+  }
+
+  switch(fork()) {
+    case -1: die();
+    case 0:
+      if (prot_uid(auto_uidq) == -1) die();
+      if (fd_copy(0,pi9[0]) == -1) die();
+      if (fd_copy(1,pi10[1]) == -1) die();
+      close23456();
+      closepipes();
+      execvp(*qcargs,qcargs);
+      die();
+  }
+#endif
+ 
   if (prot_uid(auto_uids) == -1) die();
   if (fd_copy(0,1) == -1) die();
   if (fd_copy(1,pi1[1]) == -1) die();
@@ -114,6 +170,10 @@
   if (fd_copy(4,pi4[0]) == -1) die();
   if (fd_copy(5,pi5[1]) == -1) die();
   if (fd_copy(6,pi6[0]) == -1) die();
+#ifdef EXTERNAL_TODO
+  if (fd_copy(7,pi7[1]) == -1) die();
+  if (fd_copy(8,pi8[0]) == -1) die();
+#endif
   closepipes();
   execvp(*qsargs,qsargs);
   die();
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/qmail-todo.c ./qmail-todo.c
--- ../../netqmail-1.05-orig/netqmail-1.05/qmail-todo.c	1969-12-31 19:00:00.000000000 -0500
+++ ./qmail-todo.c	2008-02-10 08:29:49.000000000 -0500
@@ -0,0 +1,703 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "alloc.h"
+#include "auto_qmail.h"
+#include "byte.h"
+#include "constmap.h"
+#include "control.h"
+#include "direntry.h"
+#include "error.h"
+#include "exit.h"
+#include "fmt.h"
+#include "fmtqfn.h"
+#include "getln.h"
+#include "open.h"
+#include "ndelay.h"
+#include "now.h"
+#include "readsubdir.h"
+#include "readwrite.h"
+#include "scan.h"
+#include "select.h"
+#include "str.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "trigger.h"
+
+/* critical timing feature #1: if not triggered, do not busy-loop */
+/* critical timing feature #2: if triggered, respond within fixed time */
+/* important timing feature: when triggered, respond instantly */
+#define SLEEP_TODO 1500 /* check todo/ every 25 minutes in any case */
+#define SLEEP_FUZZ 1 /* slop a bit on sleeps to avoid zeno effect */
+#define SLEEP_FOREVER 86400 /* absolute maximum time spent in select() */
+#define SLEEP_SYSFAIL 123
+
+stralloc percenthack = {0};
+struct constmap mappercenthack;
+stralloc locals = {0};
+struct constmap maplocals;
+stralloc vdoms = {0};
+struct constmap mapvdoms;
+stralloc envnoathost = {0};
+
+char strnum[FMT_ULONG];
+
+/* XXX not good, if qmail-send.c changes this has to be updated */
+#define CHANNELS 2
+char *chanaddr[CHANNELS] = { "local/", "remote/" };
+
+datetime_sec recent;
+
+void log1(char *x);
+void log3(char* x, char* y, char* z);
+
+int flagstopasap = 0;
+void sigterm(void)
+{
+  if (flagstopasap == 0)
+    log1("status: qmail-todo stop processing asap\n");
+  flagstopasap = 1;
+}
+
+int flagreadasap = 0; void sighup(void) { flagreadasap = 1; }
+int flagsendalive = 1; void senddied(void) { flagsendalive = 0; }
+
+void nomem() { log1("alert: out of memory, sleeping...\n"); sleep(10); }
+void pausedir(dir) char *dir;
+{ log3("alert: unable to opendir ",dir,", sleeping...\n"); sleep(10); }
+
+void cleandied()
+{ 
+  log1("alert: qmail-todo: oh no! lost qmail-clean connection! dying...\n");
+  flagstopasap = 1;
+}
+
+
+/* this file is not so long ------------------------------------- FILENAMES */
+
+stralloc fn = {0};
+
+void fnmake_init(void)
+{
+ while (!stralloc_ready(&fn,FMTQFN)) nomem();
+}
+
+void fnmake_info(unsigned long id) { fn.len = fmtqfn(fn.s,"info/",id,1); }
+void fnmake_todo(unsigned long id) { fn.len = fmtqfn(fn.s,"todo/",id,1); }
+void fnmake_mess(unsigned long id) { fn.len = fmtqfn(fn.s,"mess/",id,1); }
+void fnmake_chanaddr(unsigned long id, int c)
+{ fn.len = fmtqfn(fn.s,chanaddr[c],id,1); }
+
+
+/* this file is not so long ------------------------------------- REWRITING */
+
+stralloc rwline = {0};
+
+/* 1 if by land, 2 if by sea, 0 if out of memory. not allowed to barf. */
+/* may trash recip. must set up rwline, between a T and a \0. */
+int rewrite(char *recip)
+{
+  int i;
+  int j;
+  char *x;
+  static stralloc addr = {0};
+  int at;
+
+  if (!stralloc_copys(&rwline,"T")) return 0;
+  if (!stralloc_copys(&addr,recip)) return 0;
+
+  i = byte_rchr(addr.s,addr.len,'@');
+  if (i == addr.len) {
+    if (!stralloc_cats(&addr,"@")) return 0;
+    if (!stralloc_cat(&addr,&envnoathost)) return 0;
+  }
+
+  while (constmap(&mappercenthack,addr.s + i + 1,addr.len - i - 1)) {
+    j = byte_rchr(addr.s,i,'%');
+    if (j == i) break;
+    addr.len = i;
+    i = j;
+    addr.s[i] = '@';
+  }
+
+  at = byte_rchr(addr.s,addr.len,'@');
+
+  if (constmap(&maplocals,addr.s + at + 1,addr.len - at - 1)) {
+    if (!stralloc_cat(&rwline,&addr)) return 0;
+    if (!stralloc_0(&rwline)) return 0;
+    return 1;
+  }
+
+  for (i = 0;i <= addr.len;++i)
+    if (!i || (i == at + 1) || (i == addr.len) || ((i > at) && (addr.s[i] == '.')))
+      if (x = constmap(&mapvdoms,addr.s + i,addr.len - i)) {
+        if (!*x) break;
+        if (!stralloc_cats(&rwline,x)) return 0;
+        if (!stralloc_cats(&rwline,"-")) return 0;
+        if (!stralloc_cat(&rwline,&addr)) return 0;
+        if (!stralloc_0(&rwline)) return 0;
+        return 1;
+      }
+ 
+  if (!stralloc_cat(&rwline,&addr)) return 0;
+  if (!stralloc_0(&rwline)) return 0;
+  return 2;
+}
+
+/* this file is not so long --------------------------------- COMMUNICATION */
+
+substdio sstoqc; char sstoqcbuf[1024];
+substdio ssfromqc; char ssfromqcbuf[1024];
+stralloc comm_buf = {0};
+int comm_pos;
+int fdout = -1;
+int fdin = -1;
+
+void comm_init(void)
+{
+ substdio_fdbuf(&sstoqc,write,2,sstoqcbuf,sizeof(sstoqcbuf));
+ substdio_fdbuf(&ssfromqc,read,3,ssfromqcbuf,sizeof(ssfromqcbuf));
+
+ fdout = 1; /* stdout */
+ fdin = 0;  /* stdin */
+ if (ndelay_on(fdout) == -1)
+ /* this is so stupid: NDELAY semantics should be default on write */
+   senddied(); /* drastic, but better than risking deadlock */
+
+ while (!stralloc_ready(&comm_buf,1024)) nomem();
+}
+
+int comm_canwrite(void)
+{
+ /* XXX: could allow a bigger buffer; say 10 recipients */
+ /* XXX: returns true if there is something in the buffer */
+ if (!flagsendalive) return 0;
+ if (comm_buf.s && comm_buf.len) return 1;
+ return 0;
+}
+
+void log1(char* x)
+{
+  int pos;
+  
+  pos = comm_buf.len;
+  if (!stralloc_cats(&comm_buf,"L")) goto fail;
+  if (!stralloc_cats(&comm_buf,x)) goto fail;
+  if (!stralloc_0(&comm_buf)) goto fail;
+  return;
+  
+fail:
+  /* either all or nothing */
+  comm_buf.len = pos;
+}
+
+void log3(char* x, char *y, char *z)
+{
+  int pos;
+  
+  pos = comm_buf.len;
+  if (!stralloc_cats(&comm_buf,"L")) goto fail;
+  if (!stralloc_cats(&comm_buf,x)) goto fail;
+  if (!stralloc_cats(&comm_buf,y)) goto fail;
+  if (!stralloc_cats(&comm_buf,z)) goto fail;
+  if (!stralloc_0(&comm_buf)) goto fail;
+  return;
+  
+fail:
+  /* either all or nothing */
+  comm_buf.len = pos;
+}
+
+void comm_write(unsigned long id, int local, int remote)
+{
+  int pos;
+  char *s;
+  
+  if(local && remote) s="B";
+  else if(local) s="L";
+  else if(remote) s="R";
+  else s="X";
+  
+  pos = comm_buf.len;
+  strnum[fmt_ulong(strnum,id)] = 0;
+  if (!stralloc_cats(&comm_buf,"D")) goto fail;
+  if (!stralloc_cats(&comm_buf,s)) goto fail;
+  if (!stralloc_cats(&comm_buf,strnum)) goto fail;
+  if (!stralloc_0(&comm_buf)) goto fail;
+  return;
+  
+fail:
+  /* either all or nothing */
+  comm_buf.len = pos;
+}
+
+static int issafe(char ch)
+{
+ if (ch == '%') return 0; /* general principle: allman's code is crap */
+ if (ch < 33) return 0;
+ if (ch > 126) return 0;
+ return 1;
+}
+
+void comm_info(unsigned long id, unsigned long size, char* from, unsigned long pid, unsigned long uid)
+{
+  int pos;
+  int i;
+  
+  pos = comm_buf.len;
+  if (!stralloc_cats(&comm_buf,"Linfo msg ")) goto fail;
+  strnum[fmt_ulong(strnum,id)] = 0;
+  if (!stralloc_cats(&comm_buf,strnum)) goto fail;
+  if (!stralloc_cats(&comm_buf,": bytes ")) goto fail;
+  strnum[fmt_ulong(strnum,size)] = 0;
+  if (!stralloc_cats(&comm_buf,strnum)) goto fail;
+  if (!stralloc_cats(&comm_buf," from <")) goto fail;
+  i = comm_buf.len;
+  if (!stralloc_cats(&comm_buf,from)) goto fail;
+  for (;i < comm_buf.len;++i)
+    if (comm_buf.s[i] == '\n')
+      comm_buf.s[i] = '/';
+    else
+      if (!issafe(comm_buf.s[i]))
+	comm_buf.s[i] = '_';
+  if (!stralloc_cats(&comm_buf,"> qp ")) goto fail;
+  strnum[fmt_ulong(strnum,pid)] = 0;
+  if (!stralloc_cats(&comm_buf,strnum)) goto fail;
+  if (!stralloc_cats(&comm_buf," uid ")) goto fail;
+  strnum[fmt_ulong(strnum,uid)] = 0;
+  if (!stralloc_cats(&comm_buf,strnum)) goto fail;
+  if (!stralloc_cats(&comm_buf,"\n")) goto fail;
+  if (!stralloc_0(&comm_buf)) goto fail;
+  return;
+  
+fail:
+  /* either all or nothing */
+  comm_buf.len = pos;
+}
+
+void comm_exit(void)
+{
+  int w;
+  
+  /* if it fails exit, we have already stoped */
+  if (!stralloc_cats(&comm_buf,"X")) _exit(1);
+  if (!stralloc_0(&comm_buf)) _exit(1);
+}
+
+void comm_selprep(int *nfds, fd_set *wfds, fd_set *rfds)
+{
+  if (flagsendalive) {
+    if (flagstopasap && comm_canwrite() == 0)
+      comm_exit();
+    if (comm_canwrite()) {
+      FD_SET(fdout,wfds);
+      if (*nfds <= fdout)
+	*nfds = fdout + 1;
+    }
+    FD_SET(fdin,rfds);
+    if (*nfds <= fdin)
+      *nfds = fdin + 1;
+  }
+}
+
+void comm_do(fd_set *wfds, fd_set *rfds)
+{
+  /* first write then read */
+  if (flagsendalive)
+    if (comm_canwrite())
+      if (FD_ISSET(fdout,wfds)) {
+	int w;
+	int len;
+	len = comm_buf.len;
+	w = write(fdout,comm_buf.s + comm_pos,len - comm_pos);
+	if (w <= 0) {
+	  if ((w == -1) && (errno == error_pipe))
+	    senddied();
+	} else {
+	  comm_pos += w;
+	  if (comm_pos == len) {
+	    comm_buf.len = 0;
+	    comm_pos = 0;
+	  }
+	}
+      }
+  if (flagsendalive)
+    if (FD_ISSET(fdin,rfds)) {
+      /* there are only two messages 'H' and 'X' */
+      char c;
+      int r;
+      r = read(fdin, &c, 1);
+      if (r <= 0) {
+	if ((r == -1) && (errno != error_intr))
+	  senddied();
+      } else {
+	switch(c) {
+	  case 'H':
+	    sighup();
+	    break;
+	  case 'X':
+	    sigterm();
+	    break;
+	  default:
+	    log1("warning: qmail-todo: qmail-send speaks an obscure dialect\n");
+	    break;
+	}
+      }
+    }
+}
+
+/* this file is not so long ------------------------------------------ TODO */
+
+datetime_sec nexttodorun;
+/* DIR *tododir;  if 0, have to opendir again */
+int flagtododir = 0; /* if 0, have to readsubdir_init again */
+readsubdir todosubdir;
+stralloc todoline = {0};
+char todobuf[SUBSTDIO_INSIZE];
+char todobufinfo[512];
+char todobufchan[CHANNELS][1024];
+
+void todo_init(void)
+{
+/*  tododir = 0; */
+ flagtododir = 0;
+ nexttodorun = now();
+ trigger_set();
+}
+
+void todo_selprep(int *nfds, fd_set *rfds, datetime_sec *wakeup)
+{
+ if (flagstopasap) return;
+ trigger_selprep(nfds,rfds);
+/*  if (tododir) *wakeup = 0; */
+ if (flagtododir) *wakeup = 0;
+ if (*wakeup > nexttodorun) *wakeup = nexttodorun;
+}
+
+void todo_do(fd_set *rfds)
+{
+ struct stat st;
+ substdio ss; int fd;
+ substdio ssinfo; int fdinfo;
+ substdio sschan[CHANNELS];
+ int fdchan[CHANNELS];
+ int flagchan[CHANNELS];
+ char ch;
+ int match;
+ unsigned long id;
+/* unsigned int len;
+ direntry *d; */
+ int z;
+ int c;
+ unsigned long uid;
+ unsigned long pid;
+
+ fd = -1;
+ fdinfo = -1;
+ for (c = 0;c < CHANNELS;++c) fdchan[c] = -1;
+
+ if (flagstopasap) return;
+
+/* if (!tododir) */
+ if (!flagtododir)
+  {
+   if (!trigger_pulled(rfds))
+     if (recent < nexttodorun)
+       return;
+   trigger_set();
+/*   tododir = opendir("todo");
+   if (!tododir)
+    {
+     pausedir("todo");
+     return;
+    } */
+   readsubdir_init(&todosubdir, "todo", pausedir);
+   flagtododir = 1;
+   nexttodorun = recent + SLEEP_TODO;
+  }
+
+/* d = readdir(tododir);
+ if (!d) */
+ switch(readsubdir_next(&todosubdir, &id))
+  {
+/*   closedir(tododir);
+   tododir = 0;
+   return; */
+	case 1:
+        break;
+       case 0:
+	flagtododir = 0;
+       default:
+	 return;
+  }
+/* if (str_equal(d->d_name,".")) return;
+ if (str_equal(d->d_name,"..")) return;
+ len = scan_ulong(d->d_name,&id);
+ if (!len || d->d_name[len]) return;
+*/
+ fnmake_todo(id);
+
+ fd = open_read(fn.s);
+ if (fd == -1) { log3("warning: qmail-todo: unable to open ",fn.s,"\n"); return; }
+
+ fnmake_mess(id);
+ /* just for the statistics */
+ if (stat(fn.s,&st) == -1)
+  { log3("warning: qmail-todo: unable to stat ",fn.s,"\n"); goto fail; }
+
+ for (c = 0;c < CHANNELS;++c)
+  {
+   fnmake_chanaddr(id,c);
+   if (unlink(fn.s) == -1) if (errno != error_noent)
+    { log3("warning: qmail-todo: unable to unlink ",fn.s,"\n"); goto fail; }
+  }
+
+ fnmake_info(id);
+ if (unlink(fn.s) == -1) if (errno != error_noent)
+  { log3("warning: qmail-todo: unable to unlink ",fn.s,"\n"); goto fail; }
+
+ fdinfo = open_excl(fn.s);
+ if (fdinfo == -1)
+  { log3("warning: qmail-todo: unable to create ",fn.s,"\n"); goto fail; }
+
+ strnum[fmt_ulong(strnum,id)] = 0;
+ log3("new msg ",strnum,"\n");
+
+ for (c = 0;c < CHANNELS;++c) flagchan[c] = 0;
+
+ substdio_fdbuf(&ss,read,fd,todobuf,sizeof(todobuf));
+ substdio_fdbuf(&ssinfo,write,fdinfo,todobufinfo,sizeof(todobufinfo));
+
+ uid = 0;
+ pid = 0;
+
+ for (;;)
+  {
+   if (getln(&ss,&todoline,&match,'\0') == -1)
+    {
+     /* perhaps we're out of memory, perhaps an I/O error */
+     fnmake_todo(id);
+     log3("warning: qmail-todo: trouble reading ",fn.s,"\n"); goto fail;
+    }
+   if (!match) break;
+
+   switch(todoline.s[0])
+    {
+     case 'u':
+       scan_ulong(todoline.s + 1,&uid);
+       break;
+     case 'p':
+       scan_ulong(todoline.s + 1,&pid);
+       break;
+     case 'F':
+       if (substdio_putflush(&ssinfo,todoline.s,todoline.len) == -1)
+	{
+	 fnmake_info(id);
+         log3("warning: qmail-todo: trouble writing to ",fn.s,"\n"); goto fail;
+	}
+	comm_info(id, (unsigned long) st.st_size, todoline.s + 1, pid, uid);
+       break;
+     case 'T':
+       switch(rewrite(todoline.s + 1))
+	{
+	 case 0: nomem(); goto fail;
+	 case 2: c = 1; break;
+	 default: c = 0; break;
+        }
+       if (fdchan[c] == -1)
+	{
+	 fnmake_chanaddr(id,c);
+	 fdchan[c] = open_excl(fn.s);
+	 if (fdchan[c] == -1)
+          { log3("warning: qmail-todo: unable to create ",fn.s,"\n"); goto fail; }
+	 substdio_fdbuf(&sschan[c]
+	   ,write,fdchan[c],todobufchan[c],sizeof(todobufchan[c]));
+	 flagchan[c] = 1;
+	}
+       if (substdio_bput(&sschan[c],rwline.s,rwline.len) == -1)
+        {
+	 fnmake_chanaddr(id,c);
+         log3("warning: qmail-todo: trouble writing to ",fn.s,"\n"); goto fail;
+        }
+       break;
+     default:
+       fnmake_todo(id);
+       log3("warning: qmail-todo: unknown record type in ",fn.s,"\n"); goto fail;
+    }
+  }
+
+ close(fd); fd = -1;
+
+ fnmake_info(id);
+ if (substdio_flush(&ssinfo) == -1)
+  { log3("warning: qmail-todo: trouble writing to ",fn.s,"\n"); goto fail; }
+ if (fsync(fdinfo) == -1)
+  { log3("warning: qmail-todo: trouble fsyncing ",fn.s,"\n"); goto fail; }
+ close(fdinfo); fdinfo = -1;
+
+ for (c = 0;c < CHANNELS;++c)
+   if (fdchan[c] != -1)
+    {
+     fnmake_chanaddr(id,c);
+     if (substdio_flush(&sschan[c]) == -1)
+      { log3("warning: qmail-todo: trouble writing to ",fn.s,"\n"); goto fail; }
+     if (fsync(fdchan[c]) == -1)
+      { log3("warning: qmail-todo: trouble fsyncing ",fn.s,"\n"); goto fail; }
+     close(fdchan[c]); fdchan[c] = -1;
+    }
+
+ fnmake_todo(id);
+ if (substdio_putflush(&sstoqc,fn.s,fn.len) == -1) { cleandied(); return; }
+ if (substdio_get(&ssfromqc,&ch,1) != 1) { cleandied(); return; }
+ if (ch != '+')
+  {
+   log3("warning: qmail-clean unable to clean up ",fn.s,"\n");
+   return;
+  }
+
+ comm_write(id, flagchan[0], flagchan[1]);
+ 
+ return;
+ 
+ fail:
+ if (fd != -1) close(fd);
+ if (fdinfo != -1) close(fdinfo);
+ for (c = 0;c < CHANNELS;++c)
+   if (fdchan[c] != -1) close(fdchan[c]);
+}
+
+/* this file is too long ---------------------------------------------- MAIN */
+
+int getcontrols(void)
+{
+ if (control_init() == -1) return 0;
+ if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) return 0;
+ if (control_readfile(&locals,"control/locals",1) != 1) return 0;
+ if (!constmap_init(&maplocals,locals.s,locals.len,0)) return 0;
+ switch(control_readfile(&percenthack,"control/percenthack",0))
+  {
+   case -1: return 0;
+   case 0: if (!constmap_init(&mappercenthack,"",0,0)) return 0; break;
+   case 1: if (!constmap_init(&mappercenthack,percenthack.s,percenthack.len,0)) return 0; break;
+  }
+ switch(control_readfile(&vdoms,"control/virtualdomains",0))
+  {
+   case -1: return 0;
+   case 0: if (!constmap_init(&mapvdoms,"",0,1)) return 0; break;
+   case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) return 0; break;
+  }
+ return 1;
+}
+
+stralloc newlocals = {0};
+stralloc newvdoms = {0};
+
+void regetcontrols(void)
+{
+ int r;
+
+ if (control_readfile(&newlocals,"control/locals",1) != 1)
+  { log1("alert: qmail-todo: unable to reread control/locals\n"); return; }
+ r = control_readfile(&newvdoms,"control/virtualdomains",0);
+ if (r == -1)
+  { log1("alert: qmail-todo: unable to reread control/virtualdomains\n"); return; }
+
+ constmap_free(&maplocals);
+ constmap_free(&mapvdoms);
+
+ while (!stralloc_copy(&locals,&newlocals)) nomem();
+ while (!constmap_init(&maplocals,locals.s,locals.len,0)) nomem();
+
+ if (r)
+  {
+   while (!stralloc_copy(&vdoms,&newvdoms)) nomem();
+   while (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) nomem();
+  }
+ else
+   while (!constmap_init(&mapvdoms,"",0,1)) nomem();
+}
+
+void reread(void)
+{
+ if (chdir(auto_qmail) == -1)
+  {
+   log1("alert: qmail-todo: unable to reread controls: unable to switch to home directory\n");
+   return;
+  }
+ regetcontrols();
+ while (chdir("queue") == -1)
+  {
+   log1("alert: qmail-todo: unable to switch back to queue directory; HELP! sleeping...\n");
+   sleep(10);
+  }
+}
+
+void main()
+{
+ datetime_sec wakeup;
+ fd_set rfds;
+ fd_set wfds;
+ int nfds;
+ struct timeval tv;
+ int r;
+ char c;
+
+ if (chdir(auto_qmail) == -1)
+  { log1("alert: qmail-todo: cannot start: unable to switch to home directory\n"); _exit(111); }
+ if (!getcontrols())
+  { log1("alert: qmail-todo: cannot start: unable to read controls\n"); _exit(111); }
+ if (chdir("queue") == -1)
+  { log1("alert: qmail-todo: cannot start: unable to switch to queue directory\n"); _exit(111); }
+ sig_pipeignore();
+ umask(077);
+
+ fnmake_init();
+
+ todo_init();
+ comm_init();
+ 
+ do {
+   r = read(fdin, &c, 1);
+   if ((r == -1) && (errno != error_intr))
+     _exit(100); /* read failed probably qmail-send died */
+ } while (r =! 1); /* we assume it is a 'S' */
+ 
+ for (;;)
+  {
+   recent = now();
+
+   if (flagreadasap) { flagreadasap = 0; reread(); }
+   if (!flagsendalive) {
+     /* qmail-send finaly exited, so do the same. */
+     if (flagstopasap) _exit(0);
+     /* qmail-send died. We can not log and we can not work therefor _exit(1). */
+     _exit(1);
+   }
+
+   wakeup = recent + SLEEP_FOREVER;
+   FD_ZERO(&rfds);
+   FD_ZERO(&wfds);
+   nfds = 1;
+
+   todo_selprep(&nfds,&rfds,&wakeup);
+   comm_selprep(&nfds,&wfds,&rfds);
+
+   if (wakeup <= recent) tv.tv_sec = 0;
+   else tv.tv_sec = wakeup - recent + SLEEP_FUZZ;
+   tv.tv_usec = 0;
+
+   if (select(nfds,&rfds,&wfds,(fd_set *) 0,&tv) == -1)
+     if (errno == error_intr)
+       ;
+     else
+       log1("warning: qmail-todo: trouble in select\n");
+   else
+    {
+     recent = now();
+
+     todo_do(&rfds);
+     comm_do(&wfds, &rfds);
+    }
+  }
+  /* NOTREACHED */
+}
+
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/README.auth ./README.auth
--- ../../netqmail-1.05-orig/netqmail-1.05/README.auth	1969-12-31 19:00:00.000000000 -0500
+++ ./README.auth	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,67 @@
+README qmail-smtpd SMTP Authentication
+======================================
+
+
+History:
+--------
+
+This patch is based on Krzysztof Dabrowski's qmail-smtpd-auth-0.31 patch 
+which itself uses "Mrs. Brisby's" initial code. 
+Version 0.41 of this patch fixes the "CAPS-LOCK" typo announcing
+'CRAM_MD5' instead of 'CRAM-MD5' (german keyboard) - tx to Mike Garrison.
+Version 0.42 fixes the '421 unable to read controls (#4.3.0)' problem
+(can't read control/morercpthosts.cdb) because FD 3 was already closed - tx Richard Lyons.
+Version 0.43 fixes the ba64decode() failure in case CRAM_MD5 is not enabled - tx Vladimir Zidar.
+Version 0.51 includes the evaluation of the 'Auth' and the 'Size' parameter in the 'Mail From:' command.
+Version 0.52 uses DJB functions to copy FDs.
+Version 0.56 corrects some minor mistakes displaying the 'Auth' userid.
+Version 0.57 uses keyword "ESMTPA" in Received header in case of authentication to comply with RFC 3848.
+Version 0.58 fixes a potential problem with cc -O2 optimization within base64.c - tx John Simpson.
+
+
+Scope:
+------
+
+This patch supports RFC 2554 "SMTP Service Extension for Authentication" for qmail-smtpd.
+Additionally, RFC 1870 is honoured ("SMTP Service Extension for Message Size Declaration").
+For more technical details see: http://www.fehcom.de/qmail/docu/smtpauth.html.
+
+
+Installation:
+-------------
+
+* Untar the source in the qmail-1.03 home direcotry.
+* Run ./install_auth.
+* Modify the compile time option "#define CRAM_MD5" to your needs.
+* Re-make qmail.
+
+
+Setup:
+------
+
+In order to use SMTP Authentication you have to use a 'Pluggable Authentication Module'
+PAM to be called by qmail-smtpd; typically
+
+	/var/qmail/bin/qmail-smtpd /bin/checkpassword true 2>&1
+
+Since qmail-smtpd does not run as root, checkpassword has to be made sticky.
+There is no need to include additionally the hostname in the call.
+In order to compute the CRAM-MD5 challenge, qmail-smtpd uses the 'tcplocalhost' information.
+
+
+Changes wrt. Krysztof Dabrowski's patch:
+----------------------------------------
+
+* Avoid the 'hostname' in the call of the PAM.
+* Confirm to Dan Bernstein's checkpassword interface even for CRAM-MD5.
+* Doesn't close FD 2; thus not inhibiting logging to STDERR.
+* Fixed bugs in base64.c.
+* Modified unconditional close of FD 3 in order to sustain reading of 'control/morecpthosts.cdb'.
+* Evaluation of the (informational) Mail From: < > Auth=username.
+* Additional support for the advertised "Size" via 'Mail From: <return-path> SIZE=123456780' (RFC 1870).
+* RFC 3848 conformance for Received header in case of SMTP Auth.
+
+
+Erwin Hoffmann - Cologne 2006-12-28 (www.fehcom.de)
+
+
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/remoteinfo.c ./remoteinfo.c
--- ../../netqmail-1.05-orig/netqmail-1.05/remoteinfo.c	1998-06-15 06:53:16.000000000 -0400
+++ ./remoteinfo.c	2008-02-10 08:29:50.000000000 -0500
@@ -44,12 +44,12 @@
   s = socket(AF_INET,SOCK_STREAM,0);
   if (s == -1) return 0;
  
-  byte_zero(&sin,sizeof(sin));
+/*  byte_zero(&sin,sizeof(sin));
   sin.sin_family = AF_INET;
   byte_copy(&sin.sin_addr,4,ipl);
   sin.sin_port = 0;
-  if (bind(s,(struct sockaddr *) &sin,sizeof(sin)) == -1) { close(s); return 0; }
-  if (timeoutconn(s,ipr,113,timeout) == -1) { close(s); return 0; }
+  if (bind(s,(struct sockaddr *) &sin,sizeof(sin)) == -1) { close(s); return 0; } */
+  if (timeoutconn(s,ipr,ipl,113,timeout) == -1) { close(s); return 0; }
   fcntl(s,F_SETFL,fcntl(s,F_GETFL,0) & ~O_NDELAY);
  
   len = 0;
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/spawn.c ./spawn.c
--- ../../netqmail-1.05-orig/netqmail-1.05/spawn.c	2008-02-10 08:30:51.000000000 -0500
+++ ./spawn.c	2008-02-10 08:29:48.000000000 -0500
@@ -64,7 +64,7 @@
 int flagreading = 1;
 char outbuf[1024]; substdio ssout;
 
-int stage = 0; /* reading 0:delnum 1:messid 2:sender 3:recip */
+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};
@@ -74,6 +74,7 @@
 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);
 }
 
@@ -156,16 +157,19 @@
     {
      case 0:
        delnum = (unsigned int) (unsigned char) ch;
-       messid.len = 0; stage = 1; break;
+       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 = 2; break;
-     case 2:
+       sender.len = 0; stage = 3; break;
+     case 3:
        if (!stralloc_append(&sender,&ch)) flagabort = 1;
        if (ch) break;
-       recip.len = 0; stage = 3; break;
-     case 3:
+       recip.len = 0; stage = 4; break;
+     case 4:
        if (!stralloc_append(&recip,&ch)) flagabort = 1;
        if (ch) break;
        docmd();
@@ -202,7 +206,8 @@
 
  initialize(argc,argv);
 
- ch = auto_spawn; substdio_putflush(&ssout,&ch,1);
+ 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; }
 
@@ -237,7 +242,8 @@
 	   continue; /* read error on a readable pipe? be serious */
 	 if (r == 0)
 	  {
-           ch = i; substdio_put(&ssout,&ch,1);
+           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);
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/ssl_timeoutio.c ./ssl_timeoutio.c
--- ../../netqmail-1.05-orig/netqmail-1.05/ssl_timeoutio.c	1969-12-31 19:00:00.000000000 -0500
+++ ./ssl_timeoutio.c	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,95 @@
+#include "select.h"
+#include "error.h"
+#include "ndelay.h"
+#include "now.h"
+#include "ssl_timeoutio.h"
+
+int ssl_timeoutio(int (*fun)(),
+  int t, int rfd, int wfd, SSL *ssl, char *buf, int len)
+{
+  int n;
+  const datetime_sec end = (datetime_sec)t + now();
+
+  do {
+    fd_set fds;
+    struct timeval tv;
+
+    const int r = buf ? fun(ssl, buf, len) : fun(ssl);
+    if (r > 0) return r;
+
+    t = end - now();
+    if (t < 0) break;
+    tv.tv_sec = (time_t)t; tv.tv_usec = 0;
+
+    FD_ZERO(&fds);
+    switch (SSL_get_error(ssl, r))
+    {
+    default: return r; /* some other error */
+    case SSL_ERROR_WANT_READ:
+      FD_SET(rfd, &fds); n = select(rfd + 1, &fds, NULL, NULL, &tv);
+      break;
+    case SSL_ERROR_WANT_WRITE:
+      FD_SET(wfd, &fds); n = select(wfd + 1, NULL, &fds, NULL, &tv);
+      break;
+    }
+
+    /* n is the number of descriptors that changed status */
+  } while (n > 0);
+
+  if (n != -1) errno = error_timeout;
+  return -1;
+}
+
+int ssl_timeoutaccept(int t, int rfd, int wfd, SSL *ssl)
+{
+  int r;
+
+  /* if connection is established, keep NDELAY */
+  if (ndelay_on(rfd) == -1 || ndelay_on(wfd) == -1) return -1;
+  r = ssl_timeoutio(SSL_accept, t, rfd, wfd, ssl, NULL, 0);
+
+  if (r <= 0) { ndelay_off(rfd); ndelay_off(wfd); }
+  else SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+  return r;
+}
+
+int ssl_timeoutconn(int t, int rfd, int wfd, SSL *ssl)
+{
+  int r;
+
+  /* if connection is established, keep NDELAY */
+  if (ndelay_on(rfd) == -1 || ndelay_on(wfd) == -1) return -1;
+  r = ssl_timeoutio(SSL_connect, t, rfd, wfd, ssl, NULL, 0);
+
+  if (r <= 0) { ndelay_off(rfd); ndelay_off(wfd); }
+  else SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+  return r;
+}
+
+int ssl_timeoutrehandshake(int t, int rfd, int wfd, SSL *ssl)
+{
+  int r;
+
+  SSL_renegotiate(ssl);
+  r = ssl_timeoutio(SSL_do_handshake, t, rfd, wfd, ssl, NULL, 0);
+  if (r <= 0 || ssl->type == SSL_ST_CONNECT) return r;
+
+  /* this is for the server only */
+  ssl->state = SSL_ST_ACCEPT;
+  return ssl_timeoutio(SSL_do_handshake, t, rfd, wfd, ssl, NULL, 0);
+}
+
+int ssl_timeoutread(int t, int rfd, int wfd, SSL *ssl, char *buf, int len)
+{
+  if (!buf) return 0;
+  if (SSL_pending(ssl)) return SSL_read(ssl, buf, len);
+  return ssl_timeoutio(SSL_read, t, rfd, wfd, ssl, buf, len);
+}
+
+int ssl_timeoutwrite(int t, int rfd, int wfd, SSL *ssl, char *buf, int len)
+{
+  if (!buf) return 0;
+  return ssl_timeoutio(SSL_write, t, rfd, wfd, ssl, buf, len);
+}
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/ssl_timeoutio.h ./ssl_timeoutio.h
--- ../../netqmail-1.05-orig/netqmail-1.05/ssl_timeoutio.h	1969-12-31 19:00:00.000000000 -0500
+++ ./ssl_timeoutio.h	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,21 @@
+#ifndef SSL_TIMEOUTIO_H
+#define SSL_TIMEOUTIO_H
+
+#include <openssl/ssl.h>
+
+/* the version is like this: 0xMNNFFPPS: major minor fix patch status */
+#if OPENSSL_VERSION_NUMBER < 0x00906000L
+# error "Need OpenSSL version at least 0.9.6"
+#endif
+
+int ssl_timeoutconn(int t, int rfd, int wfd, SSL *ssl);
+int ssl_timeoutaccept(int t, int rfd, int wfd, SSL *ssl);
+int ssl_timeoutrehandshake(int t, int rfd, int wfd, SSL *ssl);
+
+int ssl_timeoutread(int t, int rfd, int wfd, SSL *ssl, char *buf, int len);
+int ssl_timeoutwrite(int t, int rfd, int wfd, SSL *ssl, char *buf, int len);
+
+int ssl_timeoutio(
+  int (*fun)(), int t, int rfd, int wfd, SSL *ssl, char *buf, int len);
+
+#endif
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/TARGETS ./TARGETS
--- ../../netqmail-1.05-orig/netqmail-1.05/TARGETS	1998-06-15 06:53:16.000000000 -0400
+++ ./TARGETS	2008-02-10 08:29:49.000000000 -0500
@@ -10,6 +10,7 @@
 qmail.o
 quote.o
 now.o
+base64.o
 gfrom.o
 myctime.o
 slurpclose.o
@@ -168,6 +169,8 @@
 constmap.o
 timeoutread.o
 timeoutwrite.o
+tls.o
+ssl_timeoutio.o
 timeoutconn.o
 tcpto.o
 dns.o
@@ -320,6 +323,7 @@
 binm2+df
 binm3
 binm3+df
+Makefile-cert
 it
 qmail-local.0
 qmail-lspawn.0
@@ -385,3 +389,6 @@
 man
 setup
 check
+update_tmprsadh
+qmail-todo.o
+qmail-todo
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/timeoutconn.c ./timeoutconn.c
--- ../../netqmail-1.05-orig/netqmail-1.05/timeoutconn.c	1998-06-15 06:53:16.000000000 -0400
+++ ./timeoutconn.c	2008-02-10 08:29:50.000000000 -0500
@@ -10,14 +10,16 @@
 #include "byte.h"
 #include "timeoutconn.h"
 
-int timeoutconn(s,ip,port,timeout)
+int timeoutconn(s,ip,localip,port,timeout)
 int s;
 struct ip_address *ip;
+struct ip_address *localip;
 unsigned int port;
 int timeout;
 {
   char ch;
   struct sockaddr_in sin;
+  struct sockaddr_in sin_local;
   char *x;
   fd_set wfds;
   struct timeval tv;
@@ -30,7 +32,11 @@
  
   if (ndelay_on(s) == -1) return -1;
  
-  /* XXX: could bind s */
+  byte_zero(&sin_local,sizeof(sin_local));
+  byte_copy(&sin_local.sin_addr,4,localip);
+  sin_local.sin_family = AF_INET;
+
+  if (bind(s,(struct sockaddr *) &sin_local,sizeof(sin_local)) == -1) return -1;
  
   if (connect(s,(struct sockaddr *) &sin,sizeof(sin)) == 0) {
     ndelay_off(s);
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/tls.c ./tls.c
--- ../../netqmail-1.05-orig/netqmail-1.05/tls.c	1969-12-31 19:00:00.000000000 -0500
+++ ./tls.c	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,25 @@
+#include "exit.h"
+#include "error.h"
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+int smtps = 0;
+SSL *ssl = NULL;
+
+void ssl_free(SSL *myssl) { SSL_shutdown(myssl); SSL_free(myssl); }
+void ssl_exit(int status) { if (ssl) ssl_free(ssl); _exit(status); }
+
+const char *ssl_error()
+{
+  int r = ERR_get_error();
+  if (!r) return NULL;
+  SSL_load_error_strings();
+  return ERR_error_string(r, NULL);
+}
+const char *ssl_error_str()
+{
+  const char *err = ssl_error();
+  if (err) return err;
+  if (!errno) return 0;
+  return (errno == error_timeout) ? "timed out" : error_str(errno);
+}
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/tls.h ./tls.h
--- ../../netqmail-1.05-orig/netqmail-1.05/tls.h	1969-12-31 19:00:00.000000000 -0500
+++ ./tls.h	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,16 @@
+#ifndef TLS_H
+#define TLS_H
+
+#include <openssl/ssl.h>
+
+extern int smtps;
+extern SSL *ssl;
+
+void ssl_free(SSL *myssl);
+void ssl_exit(int status);
+# define _exit ssl_exit
+
+const char *ssl_error();
+const char *ssl_error_str();
+
+#endif
diff -ruN ../../netqmail-1.05-orig/netqmail-1.05/update_tmprsadh.sh ./update_tmprsadh.sh
--- ../../netqmail-1.05-orig/netqmail-1.05/update_tmprsadh.sh	1969-12-31 19:00:00.000000000 -0500
+++ ./update_tmprsadh.sh	2008-02-10 08:29:47.000000000 -0500
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+# Update temporary RSA and DH keys
+# Frederik Vermeulen 2004-05-31 GPL
+
+umask 0077 || exit 0
+
+export PATH="$PATH:/usr/local/bin/ssl:/usr/sbin"
+
+openssl genrsa -out QMAIL/control/rsa512.new 512 &&
+chmod 600 QMAIL/control/rsa512.new &&
+chown UGQMAILD QMAIL/control/rsa512.new &&
+mv -f QMAIL/control/rsa512.new QMAIL/control/rsa512.pem
+echo
+
+openssl dhparam -2 -out QMAIL/control/dh512.new 512 &&
+chmod 600 QMAIL/control/dh512.new &&
+chown UGQMAILD QMAIL/control/dh512.new &&
+mv -f QMAIL/control/dh512.new QMAIL/control/dh512.pem
+echo
+
+openssl dhparam -2 -out QMAIL/control/dh1024.new 1024 &&
+chmod 600 QMAIL/control/dh1024.new &&
+chown UGQMAILD QMAIL/control/dh1024.new &&
+mv -f QMAIL/control/dh1024.new QMAIL/control/dh1024.pem
