
A word of warning: the TLS part of this patch is not type-safe at
at least one point (hey, I didn't write it.)  I don't think this
causes problems on i386 architectures, but it made qmail-smtpd
crash frequently on an Alpha.  Commenting out the substdio_fdbuf(...);
call in qmail-smtpd appears to fix the issue.

diff -urP qmail-1.03-vanilla/Makefile qmail-1.03-tls-auth/Makefile
--- qmail-1.03-vanilla/Makefile	Mon Jun 15 05:53:16 1998
+++ qmail-1.03-tls-auth/Makefile	Wed Jun 19 16:09:58 2002
@@ -136,6 +136,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 \
@@ -1446,7 +1450,8 @@
 	timeoutwrite.o timeoutconn.o tcpto.o now.o dns.o ip.o \
 	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`
+	str.a fs.a auto_qmail.o  `cat dns.lib` `cat socket.lib` \
+	-lssl -lcrypto
 
 qmail-remote.0: \
 qmail-remote.8
@@ -1536,13 +1541,13 @@
 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
+fs.a auto_qmail.o base64.o socket.lib
 	./load qmail-smtpd rcpthosts.o commands.o timeoutread.o \
 	timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \
 	received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \
 	datetime.a getln.a open.a sig.a case.a env.a stralloc.a \
-	alloc.a substdio.a error.a str.a fs.a auto_qmail.o  `cat \
-	socket.lib`
+	alloc.a substdio.a error.a str.a fs.a auto_qmail.o base64.o  `cat \
+	socket.lib` -lssl -lcrypto
 
 qmail-smtpd.0: \
 qmail-smtpd.8
@@ -1553,7 +1558,8 @@
 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 wait.h \
+fd.h base64.h
 	./compile qmail-smtpd.c
 
 qmail-start: \
@@ -2139,3 +2145,23 @@
 wait_pid.o: \
 compile wait_pid.c error.h haswaitp.h
 	./compile wait_pid.c
+
+cert:
+	openssl req -new -x509 -nodes \
+	-out /var/qmail/control/servercert.pem -days 366 \
+	-keyout /var/qmail/control/servercert.pem
+	chmod 640 /var/qmail/control/servercert.pem
+	chown qmaild.qmail /var/qmail/control/servercert.pem
+	ln -s /var/qmail/control/servercert.pem /var/qmail/control/clientcert.pem
+
+cert-req:
+	openssl req -new -nodes \
+	-out req.pem \
+	-keyout /var/qmail/control/servercert.pem
+	chmod 640 /var/qmail/control/servercert.pem
+	chown qmaild.qmail /var/qmail/control/servercert.pem
+	ln -s /var/qmail/control/servercert.pem /var/qmail/control/clientcert.pem
+	@echo
+	@echo "Send req.pem to your CA to obtain signed_req.pem, and do:"
+	@echo "cat signed_req.pem >> /var/qmail/control/servercert.pem"
+
diff -urP qmail-1.03-vanilla/README.auth qmail-1.03-tls-auth/README.auth
--- qmail-1.03-vanilla/README.auth	Wed Dec 31 18:00:00 1969
+++ qmail-1.03-tls-auth/README.auth	Wed Jun 19 15:31:37 2002
@@ -0,0 +1,175 @@
+*** Warning! Cuidado! Vorsicht! ***
+===================================
+*** Version 0.30 of the patch changes the arguments which must be
+*** passed to qmail-smtpd.  If you are upgrading from a previous
+*** version of the patch, take care to ensure your invocation of
+*** qmail-smtpd uses the correct arguments.  Otherwise, your server
+*** may run as an open relay!
+===================================
+*** Warning! Cuidado! Vorsicht! ***
+
+
+This patch adds ESMTP AUTH authentication protocol support to
+qmail-1.03.  It's originally based on Mrs. Brisby's smtp-auth patch
+with many enhancements from Krzysztof Dabrowski <brush@elysium.pl>.
+
+Beginning with version 0.30, the patch was completely rewritten to
+use only djb's string functions by Eric M. Johnston <emj@postal.net>.
+
+You can always get the newest version from:
+http://members.elysium.pl/brush/qmail-smtpd-auth/
+
+To use all of it's functionality you will also have to obtain and
+install Krzysztof's cmd5checkpw utility available at:
+http://members.elysium.pl/brush/cmd5checkpw/
+
+If you need more information about SMTP-AUTH itself and the
+client/server support and configuration, visit:
+http://members.elysium.pl/brush/smtp-auth/
+
+---
+
+Detailed patch information:
+
+This patch adds the ESMTP AUTH option to qmail-1.03, allowing the
+LOGIN, PLAIN, and CRAM-MD5 AUTH types. An appropriate checkpassword
+tool is necessary to support the authentication.  See
+http://cr.yp.to/checkpwd.html for more information on the interface.
+Note that the checkpassword tool should support all of the AUTH types
+advertised by qmail-smtpd.
+
+As reflected in the modified qmail-smtpd(8) man page, qmail-smtpd
+must be invoked with three arguments: hostname, checkprogram, and
+subprogram.  If these arguments are missing, qmail-smtpd will still
+advertise availability of AUTH, but will fail with a permanent error
+when AUTH is used.
+
+hostname is simply used to form the CRAM-MD5 challenge.  qmail-smtpd
+invokes checkprogram, feeding it the username and password, in the
+case of LOGIN or PLAIN, or the username, challenge, and response, in
+the case of CRAM-MD5.  If the user is permitted, checkprogram invokes
+subprogram, which just has to exit with a status of 0 for the user to
+be authenticated.  Otherwise, checkprogram exits with a non-zero
+status.  subprogram can usually be /usr/bin/true (or /bin/true,
+depending on your flavor of OS).
+
+If the user is successfully authenticated, the RELAYCLIENT
+environment variable is effectively set for the SMTP session, and
+the TCPREMOTEINFO environment variable is set to the authenticated
+username, overriding any value that tcpserver may have set.  The
+value of TCPREMOTEINFO is reflected in a Received header.
+
+
+How to install it:
+
+Simply patch your qmail-1.03 distribution with the included patch
+file and recompile & install like usual.
+
+The steps to do this are as follows (assuming your virgin
+qmail-1.03 install is in "../qmail-1.03"):
+
+  cp README.auth base64.c base64.h ../qmail-1.03
+  patch -d ../qmail-1.03 < auth.patch
+
+Install qmail normally, with the exception of the new arguments
+to qmail-smtpd described elsewhere in this file.
+
+Also obtain, unpack, compile and install the cmd5checkpw utility
+(or some other checkpassword utility) and add a sample account to
+/etc/poppasswd file.  This file must be readable by the qmail-smtpd
+user, usually qmaild.
+
+
+How to use it:
+
+*** Warning: In version 0.30 the arguments have changed from
+*** previous versions of qmail-smtpd-auth.  Take care to make sure
+*** you update your startup scripts if updating!
+
+If you're running qmail-smtpd from inetd, you'll want to do the
+following:
+
+smtp stream tcp nowait qmaild /var/qmail/bin/tcp-env tcp-env \
+/var/qmail/bin/qmail-smtpd mail.acme.com /bin/cmd5checkpw /bin/true
+
+Replace mail.acme.com with your hostname.  The second argument to
+qmail-smtpd is your checkpassword utility (preferably cmd5checkpw
+or some alternative that can handle CRAM-MD5).  The third argument
+is the executable that the checkpassword utility execs when
+authentication is successful.  (Note that the location of "true"
+is OS dependent: you may need /usr/bin/true.)
+
+Invocations using tcpserver will require analagous changes.  Give
+your inetd a kill -HUP or restart tcpserver and away you go.
+
+
+Caveats:
+
+Please note that as authentication needs vary wildly across
+installations, no effort has been made to make this patch work ``out
+of the box.''  You'll have to procure or develop your own
+checkpassword program.  Also note that CRAM-MD5 will require you to
+keep plaintext passwords.  You'll probably want to disable this AUTH
+type if you're just using /etc/passwd (keeping in mind that PLAIN and
+LOGIN aren't quite as safe over the wire) -- just undefine AUTHCRAM
+in qmail-smtpd.
+
+Krzysztof Dabrowski's cmd5checkpw tool used as an example in this
+document supports the three AUTH types included in this patch.
+It's available at http://www.elysium.pl/members/brush/cmd5checkpw/.
+
+This patch has been generated against the stock qmail 1.03
+distribution.  The results of combining this patch with others are
+unknown.
+
+
+Features:
+
+This patch supports the following auth methods: LOGIN, PLAIN and
+CRAM-MD5.
+
+
+Compatibility:
+
+The following MUA's are confirmed to work with this patch:
+
+Eudora 4.2.2		-	CRAM-MD5
+Eudora 5.0.2 		- 	CRAM-MD5
+The Bat 1.39		-	LOGIN & CRAM-MD5
+Outlook Express 4	- 	LOGIN
+Outlook Express 5	-	LOGIN
+Outlook 2000 		- 	LOGIN
+Netscape 4.x		-	LOGIN & PLAIN
+Netscape 4.0x		-	LOGIN
+Pegasus Mail 3.1x	-	CRAM-MD5
+
+
+Various compatibility issues:
+
+Testing with Pegasus Mail 3.1 revealed that it requires the new style
+(RFC recommended) greeting message.  Both styles are now enabled to
+maintain the highest degree of compatibility with various clients.
+This fix was suggested by David Harris <David.Harris@pmail.gen.nz>,
+the developer of Pegasus Mail.
+
+
+Acknowledgments:
+
+This patch is based on work by Krzysztof Dabrowski at
+http://members.elysium.pl/brush/qmail-smtpd-auth/ and ``Mrs. Brisby''
+at http://www.nimh.org/hacks/qmail-smtpd.c which has been further
+developed by Eric M. Johnston <emj@postal.net>.
+
+---
+
+THIS SOFTWARE IS IN THE PUBLIC DOMAIN, IS PROVIDED BY THE AUTHOR
+``AS IS,'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff -urP qmail-1.03-vanilla/TARGETS qmail-1.03-tls-auth/TARGETS
--- qmail-1.03-vanilla/TARGETS	Mon Jun 15 05:53:16 1998
+++ qmail-1.03-tls-auth/TARGETS	Wed Jun 19 15:30:20 2002
@@ -250,6 +250,7 @@
 qmail-qmtpd.o
 rcpthosts.o
 qmail-qmtpd
+base64.o
 qmail-smtpd.o
 qmail-smtpd
 sendmail.o
diff -urP qmail-1.03-vanilla/base64.c qmail-1.03-tls-auth/base64.c
--- qmail-1.03-vanilla/base64.c	Wed Dec 31 18:00:00 1969
+++ qmail-1.03-tls-auth/base64.c	Wed Jun 19 15:29:53 2002
@@ -0,0 +1,90 @@
+#include "base64.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "str.h"
+
+static char *b64alpha =
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+#define B64PAD '='
+
+/* returns 0 ok, 1 illegal, -1 problem */
+
+int b64decode(in,l,out)
+const unsigned char *in;
+int l;
+stralloc *out; /* not null terminated */
+{
+  int i, j;
+  unsigned char a[4];
+  unsigned char b[3];
+  char *s;
+
+  if (l == 0)
+  {
+    if (!stralloc_copys(out,"")) return -1;
+    return 0;
+  }
+
+  if (!stralloc_ready(out,l + 2)) return -1; /* XXX generous */
+  s = out->s;
+
+  for (i = 0;i < l;i += 4) {
+    for (j = 0;j < 4;j++)
+      if ((i + j) < l && in[i + j] != B64PAD)
+      {
+        a[j] = str_chr(b64alpha,in[i + j]);
+        if (a[j] > 63) return 1;
+      }
+      else a[j] = 0;
+
+    b[0] = (a[0] << 2) | (a[1] >> 4);
+    b[1] = (a[1] << 4) | (a[2] >> 2);
+    b[2] = (a[2] << 6) | (a[3]);
+
+    *s++ = b[0];
+
+    if (in[i + 1] == B64PAD) break;
+    *s++ = b[1];
+
+    if (in[i + 2] == B64PAD) break;
+    *s++ = b[2];
+  }
+  out->len = s - out->s;
+  while (out->len && !out->s[out->len - 1]) --out->len; /* XXX avoid? */
+  return 0;
+}
+
+int b64encode(in,out)
+stralloc *in;
+stralloc *out; /* not null terminated */
+{
+  unsigned char a, b, c;
+  int i;
+  char *s;
+
+  if (in->len == 0)
+  {
+    if (!stralloc_copys(out,"")) return -1;
+    return 0;
+  }
+
+  if (!stralloc_ready(out,in->len / 3 * 4 + 4)) return -1;
+  s = out->s;
+
+  for (i = 0;i < in->len;i += 3) {
+    a = in->s[i];
+    b = i + 1 < in->len ? in->s[i + 1] : 0;
+    c = i + 2 < in->len ? in->s[i + 2] : 0;
+
+    *s++ = b64alpha[a >> 2];
+    *s++ = b64alpha[((a & 3 ) << 4) | (b >> 4)];
+
+    if (i + 1 >= in->len) *s++ = B64PAD;
+    else *s++ = b64alpha[((b & 15) << 2) | (c >> 6)];
+
+    if (i + 2 >= in->len) *s++ = B64PAD;
+    else *s++ = b64alpha[c & 63];
+  }
+  out->len = s - out->s;
+  return 0;
+}
diff -urP qmail-1.03-vanilla/base64.h qmail-1.03-tls-auth/base64.h
--- qmail-1.03-vanilla/base64.h	Wed Dec 31 18:00:00 1969
+++ qmail-1.03-tls-auth/base64.h	Wed Jun 19 15:29:53 2002
@@ -0,0 +1,7 @@
+#ifndef BASE64_H
+#define BASE64_H
+
+extern int b64decode();
+extern int b64encode();
+
+#endif
diff -urP qmail-1.03-vanilla/conf-cc qmail-1.03-tls-auth/conf-cc
--- qmail-1.03-vanilla/conf-cc	Mon Jun 15 05:53:16 1998
+++ qmail-1.03-tls-auth/conf-cc	Wed Jun 19 15:35:59 2002
@@ -1,3 +1,3 @@
-cc -O2
+cc -O2 -DTLS -I/usr/local/ssl/include
 
 This will be used to compile .c files.
diff -urP qmail-1.03-vanilla/dns.c qmail-1.03-tls-auth/dns.c
--- qmail-1.03-vanilla/dns.c	Mon Jun 15 05:53:16 1998
+++ qmail-1.03-tls-auth/dns.c	Wed Jun 19 15:36:06 2002
@@ -270,6 +270,14 @@
 {
  int r;
  struct ip_mx ix;
+#ifdef TLS
+ stralloc fqdn = {0};
+
+ if (!stralloc_copy(&fqdn,sa)) return DNS_MEM;
+ if (!stralloc_0(&fqdn)) return DNS_MEM;
+ ix.fqdn = fqdn.s;
+ alloc_free(fqdn);
+#endif
 
  if (!stralloc_copy(&glue,sa)) return DNS_MEM;
  if (!stralloc_0(&glue)) return DNS_MEM;
@@ -330,6 +338,9 @@
    ix.pref = 0;
    if (!glue.s[ip_scan(glue.s,&ix.ip)] || !glue.s[ip_scanbracket(glue.s,&ix.ip)])
     {
+#ifdef TLS
+     ix.fqdn = NULL;
+#endif
      if (!ipalloc_append(ia,&ix)) return DNS_MEM;
      return 0;
     }
diff -urP qmail-1.03-vanilla/ipalloc.h qmail-1.03-tls-auth/ipalloc.h
--- qmail-1.03-vanilla/ipalloc.h	Mon Jun 15 05:53:16 1998
+++ qmail-1.03-tls-auth/ipalloc.h	Wed Jun 19 15:36:15 2002
@@ -3,7 +3,12 @@
 
 #include "ip.h"
 
+#ifdef TLS
+#include "stralloc.h"
+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 -urP qmail-1.03-vanilla/qmail-remote.c qmail-1.03-tls-auth/qmail-remote.c
--- qmail-1.03-vanilla/qmail-remote.c	Mon Jun 15 05:53:16 1998
+++ qmail-1.03-tls-auth/qmail-remote.c	Wed Jun 19 15:36:38 2002
@@ -26,8 +26,18 @@
 #include "tcpto.h"
 #include "readwrite.h"
 #include "timeoutconn.h"
+#ifndef TLS
 #include "timeoutread.h"
 #include "timeoutwrite.h"
+#endif
+
+#ifdef TLS
+#include <sys/stat.h>
+#include <openssl/ssl.h>
+SSL *ssl = NULL;
+
+stralloc tlsclientciphers = {0};
+#endif
 
 #define HUGESMTPTEXT 5000
 
@@ -107,17 +117,94 @@
 int smtpfd;
 int timeout = 1200;
 
+#ifdef TLS
+int flagtimedout = 0;
+void sigalrm()
+{
+ flagtimedout = 1;
+}
+
+int ssl_timeoutread(timeout,fd,buf,n) int timeout; int fd; char *buf; int n;
+{
+ int r; int saveerrno;
+ if (flagtimedout) { errno = error_timeout; return -1; }
+ alarm(timeout);
+ if (ssl) {
+   while(((r = SSL_read(ssl,buf,n)) <= 0)
+         && (SSL_get_error(ssl, r) == SSL_ERROR_WANT_READ));
+   if (SSL_get_error(ssl, r) != SSL_ERROR_NONE)
+    {char buf[1024];
+ 
+     out("ZTLS connection to "); outhost(); out(" died: ");
+     SSL_load_error_strings();
+     out(ERR_error_string(ERR_get_error(), buf)); out("\n");
+     SSL_shutdown(ssl);
+     zerodie();
+    }
+ }else r = read(fd,buf,n);
+ saveerrno = errno;
+ alarm(0);
+ if (flagtimedout) { errno = error_timeout; return -1; }
+ errno = saveerrno;
+ return r;
+}
+
+int ssl_timeoutwrite(timeout,fd,buf,n) int timeout; int fd; char *buf; int n;
+{
+ int r; int saveerrno;
+ if (flagtimedout) { errno = error_timeout; return -1; }
+ alarm(timeout);
+ if (ssl) {
+   while(((r = SSL_write(ssl,buf,n)) <= 0)
+         && (SSL_get_error(ssl, r) == SSL_ERROR_WANT_WRITE));
+   if (SSL_get_error(ssl, r) != SSL_ERROR_NONE)
+    {char buf[1024];
+
+     out("ZTLS connection to "); outhost(); out(" died: ");
+     SSL_load_error_strings();
+     out(ERR_error_string(ERR_get_error(), buf)); out("\n");
+     SSL_shutdown(ssl);
+     zerodie();
+    }
+ }else r = write(fd,buf,n);
+ saveerrno = errno;
+ alarm(0);
+ if (flagtimedout) { errno = error_timeout; return -1; }
+ errno = saveerrno;
+ return r;
+}
+
+static int client_cert_cb(SSL *s,X509 **x509, EVP_PKEY **pkey)
+{
+ out("ZTLS found no client cert in control/clientcert.pem\n");
+ zerodie(NULL,NULL);
+}
+
+static int verify_cb(int ok, X509_STORE_CTX * ctx)
+{
+  return (1);
+}
+#endif 
+
 int saferead(fd,buf,len) int fd; char *buf; int len;
 {
   int r;
+#ifdef TLS
+  r = ssl_timeoutread(timeout,smtpfd,buf,len);
+#else
   r = timeoutread(timeout,smtpfd,buf,len);
+#endif
   if (r <= 0) dropped();
   return r;
 }
 int safewrite(fd,buf,len) int fd; char *buf; int len;
 {
   int r;
+#ifdef TLS
+  r = ssl_timeoutwrite(timeout,smtpfd,buf,len);
+#else
   r = timeoutwrite(timeout,smtpfd,buf,len);
+#endif
   if (r <= 0) dropped();
   return r;
 }
@@ -186,6 +273,34 @@
   out(append);
   out(".\n");
   outsmtptext();
+
+/* TAG */
+#if defined(TLS) && defined(DEBUG)
+#define ONELINE_NAME(X) X509_NAME_oneline(X,NULL,0)
+
+ if(ssl){
+ X509 *peer;
+
+  out("STARTTLS proto="); out(SSL_get_version(ssl));
+  out("; cipher="); out(SSL_CIPHER_get_name(SSL_get_current_cipher(ssl)));
+
+  /* we want certificate details */
+  peer=SSL_get_peer_certificate(ssl);
+  if (peer != NULL) {
+   char *str;
+
+   str=ONELINE_NAME(X509_get_subject_name(peer));
+   out("; subject="); out(str);
+   OPENSSL_free(str);
+   str=ONELINE_NAME(X509_get_issuer_name(peer));
+   out("; issuer="); out(str);
+   OPENSSL_free(str);
+   X509_free(peer);
+  }
+  out(";\n");
+ }
+#endif
+
   zerodie();
 }
 
@@ -216,20 +331,158 @@
 
 stralloc recip = {0};
 
+#ifdef TLS
+void smtp(fqdn)
+char *fqdn;
+#else
 void smtp()
+#endif
 {
   unsigned long code;
   int flagbother;
   int i;
- 
+#ifdef TLS
+  int needtlsauth = 0;
+  SSL_CTX *ctx;
+  int saveerrno, r;
+
+  stralloc servercert = {0};
+  struct stat st;
+  if(fqdn){
+   if(!stralloc_copys(&servercert, "control/tlshosts/")) temp_nomem();
+   if(!stralloc_catb(&servercert, fqdn, str_len(fqdn))) temp_nomem();
+   if(!stralloc_catb(&servercert, ".pem", 4)) temp_nomem();
+   if(!stralloc_0(&servercert)) temp_nomem();
+   if (stat(servercert.s,&st) == 0)  needtlsauth = 1;
+  }
+#endif
+
   if (smtpcode() != 220) quit("ZConnected to "," but greeting failed");
  
+#ifdef TLS
+  substdio_puts(&smtpto,"EHLO ");
+#else
   substdio_puts(&smtpto,"HELO ");
+#endif
   substdio_put(&smtpto,helohost.s,helohost.len);
   substdio_puts(&smtpto,"\r\n");
   substdio_flush(&smtpto);
+#ifdef TLS
+  if (smtpcode() != 250){
+   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");
+  }
+#else
   if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected");
- 
+#endif
+
+#ifdef TLS
+  i = 0; 
+  while((i += str_chr(smtptext.s+i,'\n') + 1) && (i+12 < smtptext.len) &&
+        str_diffn(smtptext.s+i+4,"STARTTLS\n",9));
+  if (i+12 < smtptext.len)
+   {
+    substdio_puts(&smtpto,"STARTTLS\r\n");
+    substdio_flush(&smtpto);
+    if (smtpcode() == 220)
+     {
+      SSL_library_init();
+      if(!(ctx=SSL_CTX_new(SSLv23_client_method())))
+       {char buf[1024];
+
+        out("ZTLS not available: error initializing ctx: ");
+        SSL_load_error_strings();
+        out(ERR_error_string(ERR_get_error(), buf));
+        out("\n");
+        SSL_shutdown(ssl);
+        zerodie();
+      }
+      if((stat("control/clientcert.pem", &st) == 0) &&
+         ((SSL_CTX_use_RSAPrivateKey_file(ctx, "control/clientcert.pem", SSL_FILETYPE_PEM) <= 0) ||
+         (SSL_CTX_use_certificate_chain_file(ctx, "control/clientcert.pem") <= 0) ||
+         (SSL_CTX_check_private_key(ctx) <= 0)))
+        /* if there is a cert and it is bad, I fail
+           if there is no cert, I leave it to the other side to complain */
+        SSL_CTX_set_client_cert_cb(ctx, client_cert_cb);
+ 
+      /*SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);*/
+      SSL_CTX_set_cipher_list(ctx,tlsclientciphers.s);
+ 
+      if (needtlsauth){
+        if (!SSL_CTX_load_verify_locations(ctx, servercert.s, NULL))
+          {out("ZTLS unable to load "); out(servercert.s); out("\n");
+           zerodie();}
+        SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb);
+      }
+     
+      if(!(ssl=SSL_new(ctx)))
+        {char buf[1024];
+
+         out("ZTLS not available: error initializing ssl: "); 
+         SSL_load_error_strings();
+         out(ERR_error_string(ERR_get_error(), buf));
+         out("\n");
+         SSL_shutdown(ssl);
+         zerodie();
+        }
+      SSL_set_fd(ssl,smtpfd);
+
+      alarm(timeout);
+      r = SSL_connect(ssl); saveerrno = errno;
+      alarm(0); 
+      if (flagtimedout) 
+       {out("ZTLS not available: connect timed out\n");
+        zerodie();}
+      errno = saveerrno;
+      if (r<=0)
+        {char buf[1024];
+
+         out("ZTLS not available: connect failed: ");
+         SSL_load_error_strings();
+         out(ERR_error_string(ERR_get_error(), buf));
+         out("\n");
+         SSL_shutdown(ssl);
+         zerodie();
+        }
+      if (needtlsauth)
+       /* should also check alternate names */
+       {char commonName[256];
+
+        if ((r=SSL_get_verify_result(ssl)) != X509_V_OK)
+         {out("ZTLS unable to verify server with ");
+          out(servercert.s); out(": ");
+          out(X509_verify_cert_error_string(r)); out("\n");
+          zerodie();
+         }
+        X509_NAME_get_text_by_NID(X509_get_subject_name(
+                                   SSL_get_peer_certificate(ssl)),
+                                   NID_commonName, commonName, 256);
+        if (strcasecmp(fqdn,commonName)){
+         out("ZTLS connection to "); out(fqdn);
+         out(" wanted, certificate for "); out(commonName);
+         out(" received\n");
+         zerodie();}
+        }
+
+      substdio_puts(&smtpto,"EHLO ");
+      substdio_put(&smtpto,helohost.s,helohost.len);
+      substdio_puts(&smtpto,"\r\n");
+      substdio_flush(&smtpto);
+
+      if (smtpcode() != 250)
+       {
+        quit("ZTLS connected to "," but my name was rejected");
+       }
+     } 
+   }
+  if ((!ssl) && needtlsauth)
+   {out("ZNo TLS achieved while "); out(servercert.s); out(" exists.\n");
+    quit();}
+#endif
+
   substdio_puts(&smtpto,"MAIL FROM:<");
   substdio_put(&smtpto,sender.s,sender.len);
   substdio_puts(&smtpto,">\r\n");
@@ -311,6 +564,11 @@
 void getcontrols()
 {
   if (control_init() == -1) temp_control();
+  #ifdef TLS
+    if (control_rldef(&tlsclientciphers,"control/tlsclientciphers",0,"DEFAULT") != 1)
+      temp_control();
+    if(!stralloc_0(&tlsclientciphers)) temp_nomem();
+  #endif
   if (control_readint(&timeout,"control/timeoutremote") == -1) temp_control();
   if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1)
     temp_control();
@@ -338,7 +596,10 @@
   int flagallaliases;
   int flagalias;
   char *relayhost;
- 
+
+#ifdef TLS
+  sig_alarmcatch(sigalrm);
+#endif
   sig_pipeignore();
   if (argc < 4) perm_usage();
   if (chdir(auto_qmail) == -1) temp_chdir();
@@ -417,7 +678,11 @@
     if (timeoutconn(smtpfd,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0) {
       tcpto_err(&ip.ix[i].ip,0);
       partner = ip.ix[i].ip;
+#ifdef TLS
+      smtp(ip.ix[i].fqdn); /* does not return */
+#else
       smtp(); /* does not return */
+#endif
     }
     tcpto_err(&ip.ix[i].ip,errno == error_timeout);
     close(smtpfd);
diff -urP qmail-1.03-vanilla/qmail-smtpd.8 qmail-1.03-tls-auth/qmail-smtpd.8
--- qmail-1.03-vanilla/qmail-smtpd.8	Mon Jun 15 05:53:16 1998
+++ qmail-1.03-tls-auth/qmail-smtpd.8	Wed Jun 19 15:30:20 2002
@@ -3,6 +3,11 @@
 qmail-smtpd \- receive mail via SMTP
 .SH SYNOPSIS
 .B qmail-smtpd
+[
+.I hostname
+.I checkprogram
+.I subprogram
+]
 .SH DESCRIPTION
 .B qmail-smtpd
 receives mail messages via the Simple Mail Transfer Protocol (SMTP)
@@ -23,7 +28,29 @@
 header fields.
 
 .B qmail-smtpd
-supports ESMTP, including the 8BITMIME and PIPELINING options.
+supports ESMTP, including the 8BITMIME, PIPELINING, and AUTH options.
+
+.B qmail-smtpd
+can accept LOGIN, PLAIN, and CRAM-MD5 AUTH types.  It invokes
+.IR checkprogram ,
+which reads on file descriptor 3 the username, a 0 byte, the password
+or challenge derived from
+.IR hostname ,
+another 0 byte, a CRAM-MD5 response (if applicable to the AUTH type),
+and a final 0 byte.
+.I checkprogram
+invokes
+.I subprogram
+upon successful authentication, which should in turn return 0 to
+.BR qmail-smtpd ,
+effectively setting the environment variables RELAYCLIENT and TCPREMOTEINFO
+(any supplied value replaced with the authenticated username).
+.B qmail-smtpd
+will reject the authentication attempt if it receives a nonzero return
+value from
+.I checkprogram
+or
+.IR subprogram .
 .SH TRANSPARENCY
 .B qmail-smtpd
 converts the SMTP newline convention into the UNIX newline convention
@@ -177,3 +204,6 @@
 qmail-newmrh(8),
 qmail-queue(8),
 qmail-remote(8)
+.SH "HISTORY"
+The patch enabling the ESMTP AUTH option is not part of the standard
+qmail-1.03 distribution.
diff -urP qmail-1.03-vanilla/qmail-smtpd.c qmail-1.03-tls-auth/qmail-smtpd.c
--- qmail-1.03-vanilla/qmail-smtpd.c	Mon Jun 15 05:53:16 1998
+++ qmail-1.03-tls-auth/qmail-smtpd.c	Wed Jun 19 16:05:56 2002
@@ -20,18 +20,75 @@
 #include "now.h"
 #include "exit.h"
 #include "rcpthosts.h"
+#ifndef TLS
 #include "timeoutread.h"
 #include "timeoutwrite.h"
+#endif
 #include "commands.h"
+#include "wait.h"
+#include "fd.h"
 
+#ifdef TLS
+#include <openssl/ssl.h>
+SSL *ssl = NULL;
+
+stralloc clientcert = {0};
+stralloc tlsserverciphers = {0};
+#endif
+
+#define AUTHCRAM
 #define MAXHOPS 100
 unsigned int databytes = 0;
 int timeout = 1200;
 
+#ifdef TLS
+int flagtimedout = 0;
+void sigalrm()
+{
+ flagtimedout = 1;
+}
+int ssl_timeoutread(timeout,fd,buf,n) int timeout; int fd; char *buf; int n;
+{
+ int r; int saveerrno;
+  if (flagtimedout) { errno = error_timeout; return -1; }
+  alarm(timeout);
+  if (ssl) {
+    while(((r = SSL_read(ssl,buf,n)) <= 0)
+          && (SSL_get_error(ssl, r) == SSL_ERROR_WANT_READ));
+  }else r = read(fd,buf,n);
+ saveerrno = errno;
+ alarm(0);
+ if (flagtimedout) { errno = error_timeout; return -1; }
+ errno = saveerrno;
+ return r;
+}
+
+
+int ssl_timeoutwrite(timeout,fd,buf,n) int timeout; int fd; char *buf; int n;
+{
+ int r; int saveerrno;
+ if (flagtimedout) { errno = error_timeout; return -1; }
+ alarm(timeout);
+ if (ssl) {
+    while(((r = SSL_write(ssl,buf,n)) <= 0)
+          && (SSL_get_error(ssl, r) == SSL_ERROR_WANT_WRITE));
+ }else r = write(fd,buf,n);
+ saveerrno = errno;
+ alarm(0);
+ if (flagtimedout) { errno = error_timeout; return -1; }
+ errno = saveerrno;
+ return r;
+}
+#endif
+
 int safewrite(fd,buf,len) int fd; char *buf; int len;
 {
   int r;
+#ifdef TLS
+  r = ssl_timeoutwrite(timeout,fd,buf,len);
+#else
   r = timeoutwrite(timeout,fd,buf,len);
+#endif
   if (r <= 0) _exit(1);
   return r;
 }
@@ -51,6 +108,9 @@
 
 void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }
 void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
+#ifdef TLS
+void err_nogwcert() { out("553 no valid cert for gatewaying (#5.7.1)\r\n"); }
+#endif
 void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); }
 void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); }
 void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); }
@@ -59,6 +119,15 @@
 void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); }
 void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); }
 
+int err_child() { out("454 oops, problem with child and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_fork() { out("454 oops, child won't start and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_pipe() { out("454 oops, unable to open pipe and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_write() { out("454 oops, unable to write pipe and I can't auth (#4.3.0)\r\n"); return -1; }
+void err_authd() { out("503 you're already authenticated (#5.5.0)\r\n"); }
+void err_authmail() { out("503 no auth during mail transaction (#5.5.0)\r\n"); }
+int err_noauth() { out("504 auth type unimplemented (#5.5.1)\r\n"); return -1; }
+int err_authabrt() { out("501 auth exchange cancelled (#5.0.0)\r\n"); return -1; }
+int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; }
 
 stralloc greeting = {0};
 
@@ -81,6 +150,9 @@
 char *remoteinfo;
 char *local;
 char *relayclient;
+#ifdef TLS
+char *tlsciphers;
+#endif
 
 stralloc helohost = {0};
 char *fakehelo; /* pointer into helohost, or 0 */
@@ -101,6 +173,9 @@
 {
   char *x;
   unsigned long u;
+#ifdef TLS
+  char *tlsciphers;
+#endif
  
   if (control_init() == -1) die_control();
   if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1)
@@ -131,6 +206,17 @@
   if (!remotehost) remotehost = "unknown";
   remoteinfo = env_get("TCPREMOTEINFO");
   relayclient = env_get("RELAYCLIENT");
+#ifdef TLS
+  if (tlsciphers = env_get("TLSCIPHERS")){
+    if (!stralloc_copys(&tlsserverciphers,tlsciphers)) die_nomem();
+  }
+  else {
+    if (control_rldef(&tlsserverciphers,"control/tlsserverciphers",0,"DEFAULT") != 1)
+      die_control();
+  }
+  if (!stralloc_0(&tlsserverciphers)) die_nomem();
+#endif
+
   dohelo(remotehost);
 }
 
@@ -229,7 +315,18 @@
 }
 void smtp_ehlo(arg) char *arg;
 {
-  smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
+  smtp_greet("250-");
+#ifdef AUTHCRAM
+  out("\r\n250-AUTH LOGIN CRAM-MD5 PLAIN");
+  out("\r\n250-AUTH=LOGIN CRAM-MD5 PLAIN");
+#else
+  out("\r\n250-AUTH LOGIN PLAIN");
+  out("\r\n250-AUTH=LOGIN PLAIN");
+#endif
+#ifdef TLS
+  if (!ssl) out("\r\n250-STARTTLS");
+#endif
+  out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
   seenmail = 0; dohelo(arg);
 }
 void smtp_rset()
@@ -247,6 +344,12 @@
   if (!stralloc_0(&mailfrom)) die_nomem();
   out("250 ok\r\n");
 }
+#ifdef TLS
+static int verify_cb(int ok, X509_STORE_CTX * ctx)
+{
+  return (1);
+}
+#endif
 void smtp_rcpt(arg) char *arg; {
   if (!seenmail) { err_wantmail(); return; }
   if (!addrparse(arg)) { err_syntax(); return; }
@@ -257,7 +360,54 @@
     if (!stralloc_0(&addr)) die_nomem();
   }
   else
+#ifndef TLS
     if (!addrallowed()) { err_nogateway(); return; }
+#else
+    if (!addrallowed())
+     {
+      if (ssl)
+      { STACK_OF(X509_NAME) *sk;
+        X509 *peercert;
+        stralloc tlsclients = {0};
+        struct constmap maptlsclients;
+        int r;
+
+        SSL_set_verify(ssl,
+                       SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE,
+                       verify_cb);
+        if ((sk = SSL_load_client_CA_file("control/clientca.pem")) == NULL)
+         { err_nogateway(); return; }
+        SSL_set_client_CA_list(ssl, sk);
+        if((control_readfile(&tlsclients,"control/tlsclients",0) != 1) ||
+           !constmap_init(&maptlsclients,tlsclients.s,tlsclients.len,0))
+          { err_nogateway(); return; }
+
+        SSL_renegotiate(ssl);
+        SSL_do_handshake(ssl);
+        ssl->state = SSL_ST_ACCEPT;
+        SSL_do_handshake(ssl);
+        if ((r = SSL_get_verify_result(ssl)) != X509_V_OK)
+         {out("553 no valid cert for gatewaying: ");
+          out(X509_verify_cert_error_string(r));
+          out(" (#5.7.1)\r\n");
+          return;
+         }
+
+        if (peercert = SSL_get_peer_certificate(ssl))
+         {char emailAddress[256];
+
+          X509_NAME_get_text_by_NID(X509_get_subject_name(
+                                     SSL_get_peer_certificate(ssl)),
+                                     NID_pkcs9_emailAddress, emailAddress, 256);          if (!stralloc_copys(&clientcert, emailAddress)) die_nomem();
+          if (!constmap(&maptlsclients,clientcert.s,clientcert.len))
+            { err_nogwcert(); return; }
+          relayclient = "";
+         }
+          else { err_nogwcert(); return; }
+       }
+      else { err_nogateway(); return; }
+     }
+#endif
   if (!stralloc_cats(&rcptto,"T")) die_nomem();
   if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
   if (!stralloc_0(&rcptto)) die_nomem();
@@ -269,7 +419,11 @@
 {
   int r;
   flush();
+#ifdef TLS
+  r = ssl_timeoutread(timeout,fd,buf,len);
+#else
   r = timeoutread(timeout,fd,buf,len);
+#endif
   if (r == -1) if (errno == error_timeout) die_alarm();
   if (r <= 0) die_read();
   return r;
@@ -369,6 +523,9 @@
   int hops;
   unsigned long qp;
   char *qqx;
+#ifdef TLS
+  stralloc protocolinfo = {0};
+#endif
  
   if (!seenmail) { err_wantmail(); return; }
   if (!rcptto.len) { err_wantrcpt(); return; }
@@ -377,8 +534,20 @@
   if (qmail_open(&qqt) == -1) { err_qqt(); return; }
   qp = qmail_qp(&qqt);
   out("354 go ahead\r\n");
- 
+#ifdef TLS
+  if(ssl){
+   if (!stralloc_copys(&protocolinfo, SSL_CIPHER_get_name(SSL_get_current_cipher(ssl)))) die_nomem();
+   if (!stralloc_catb(&protocolinfo, " encrypted SMTP", 15)) die_nomem();
+   if (clientcert.len){
+     if (!stralloc_catb(&protocolinfo," cert ", 6)) die_nomem();
+     if (!stralloc_catb(&protocolinfo,clientcert.s, clientcert.len)) die_nomem();
+   }
+   if (!stralloc_0(&protocolinfo)) die_nomem();
+  } else if (!stralloc_copyb(&protocolinfo,"SMTP",5)) die_nomem();
+  received(&qqt,protocolinfo.s,local,remoteip,remotehost,remoteinfo,case_diffs(remotehost,helohost.s) ? helohost.s : 0);
+#else
   received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);
+#endif
   blast(&hops);
   hops = (hops >= MAXHOPS);
   if (hops) qmail_fail(&qqt);
@@ -393,23 +562,299 @@
   out(qqx + 1);
   out("\r\n");
 }
+#ifdef TLS
+static RSA *tmp_rsa_cb(ssl,export,keylength) SSL *ssl; int export; int keylength; 
+{
+  RSA* rsa;
+  BIO* in;
+
+  if (!export || keylength == 512)
+   if (in=BIO_new(BIO_s_file_internal()))
+    if (BIO_read_filename(in,"control/rsa512.pem") > 0)
+     if (rsa=PEM_read_bio_RSAPrivateKey(in,NULL,NULL,NULL))
+      return rsa;
+  return (RSA_generate_key(export?keylength:512,RSA_F4,NULL,NULL));
+}
+
+void smtp_tls(arg) char *arg;
+{
+  SSL_CTX *ctx;
+
+  if (*arg)
+   {out("501 Syntax error (no parameters allowed) (#5.5.4)\r\n");
+    return;}
+
+  SSL_library_init();
+  if(!(ctx=SSL_CTX_new(SSLv23_server_method())))
+   {out("454 TLS not available: unable to initialize ctx (#4.3.0)\r\n");
+    return;}
+  if(!SSL_CTX_use_RSAPrivateKey_file(ctx, "control/servercert.pem", SSL_FILETYPE_PEM))
+   {out("454 TLS not available: missing RSA private key (#4.3.0)\r\n");
+    return;}
+  if(!SSL_CTX_use_certificate_chain_file(ctx, "control/servercert.pem"))
+   {out("454 TLS not available: missing certificate (#4.3.0)\r\n");
+    return;}
+  SSL_CTX_set_tmp_rsa_callback(ctx, tmp_rsa_cb);
+  SSL_CTX_set_cipher_list(ctx,tlsserverciphers.s);
+  SSL_CTX_load_verify_locations(ctx, "control/clientca.pem",NULL);
+  SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, verify_cb);
+
+  out("220 ready for tls\r\n"); flush();
+
+  if(!(ssl=SSL_new(ctx))) die_read();
+  SSL_set_fd(ssl,0);
+  if(SSL_accept(ssl)<=0) die_read();
+  substdio_fdbuf(&ssout,SSL_write,ssl,ssoutbuf,sizeof(ssoutbuf));
+
+  remotehost = env_get("TCPREMOTEHOST");
+  if (!remotehost) remotehost = "unknown";
+  dohelo(remotehost);
+}
+#endif
+
+
+char unique[FMT_ULONG + FMT_ULONG + 3];
+static stralloc authin = {0};
+static stralloc user = {0};
+static stralloc pass = {0};
+static stralloc resp = {0};
+static stralloc slop = {0};
+char *hostname;
+char **childargs;
+substdio ssup;
+char upbuf[128];
+int authd = 0;
+
+int authgetl(void) {
+  int i;
+
+  if (!stralloc_copys(&authin, "")) die_nomem();
+
+  for (;;) {
+    if (!stralloc_readyplus(&authin,1)) die_nomem(); /* XXX */
+    i = substdio_get(&ssin,authin.s + authin.len,1);
+    if (i != 1) die_read();
+    if (authin.s[authin.len] == '\n') break;
+    ++authin.len;
+  }
+
+  if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len;
+  authin.s[authin.len] = 0;
+
+  if (*authin.s == '*' && *(authin.s + 1) == 0) { return err_authabrt(); }
+  if (authin.len == 0) { return err_input(); }
+  return authin.len;
+}
+
+int authenticate(void)
+{
+  int child;
+  int wstat;
+  int pi[2];
+
+  if (!stralloc_0(&user)) die_nomem();
+  if (!stralloc_0(&pass)) die_nomem();
+  if (!stralloc_0(&resp)) die_nomem();
+
+  if (fd_copy(2,1) == -1) return err_pipe();
+  close(3);
+  if (pipe(pi) == -1) return err_pipe();
+  if (pi[0] != 3) return err_pipe();
+  switch(child = fork()) {
+    case -1:
+      return err_fork();
+    case 0:
+      close(pi[1]);
+      sig_pipedefault();
+      execvp(*childargs, childargs);
+      _exit(1);
+  }
+  close(pi[0]);
+
+  substdio_fdbuf(&ssup,write,pi[1],upbuf,sizeof upbuf);
+  if (substdio_put(&ssup,user.s,user.len) == -1) return err_write();
+  if (substdio_put(&ssup,pass.s,pass.len) == -1) return err_write();
+  if (substdio_put(&ssup,resp.s,resp.len) == -1) return err_write();
+  if (substdio_flush(&ssup) == -1) return err_write();
+
+  close(pi[1]);
+  byte_zero(pass.s,pass.len);
+  byte_zero(upbuf,sizeof upbuf);
+  if (wait_pid(&wstat,child) == -1) return err_child();
+  if (wait_crashed(wstat)) return err_child();
+  if (wait_exitcode(wstat)) { sleep(5); return 1; } /* no */
+  return 0; /* yes */
+}
+
+int auth_login(arg) char *arg;
+{
+  int r;
+
+  if (*arg) {
+    if (r = b64decode(arg,str_len(arg),&user) == 1) return err_input();
+  }
+  else {
+    out("334 VXNlcm5hbWU6\r\n"); flush(); /* Username: */
+    if (authgetl() < 0) return -1;
+    if (r = b64decode(authin.s,authin.len,&user) == 1) return err_input();
+  }
+  if (r == -1) die_nomem();
+
+  out("334 UGFzc3dvcmQ6\r\n"); flush(); /* Password: */
+
+  if (authgetl() < 0) return -1;
+  if (r = b64decode(authin.s,authin.len,&pass) == 1) return err_input();
+  if (r == -1) die_nomem();
+
+  if (!user.len || !pass.len) return err_input();
+  return authenticate();  
+}
+
+int auth_plain(arg) char *arg;
+{
+  int r, id = 0;
+
+  if (*arg) {
+    if (r = b64decode(arg,str_len(arg),&slop) == 1) return err_input();
+  }
+  else {
+    out("334 \r\n"); flush();
+    if (authgetl() < 0) return -1;
+    if (r = b64decode(authin.s,authin.len,&slop) == 1) return err_input();
+  }
+  if (r == -1 || !stralloc_0(&slop)) die_nomem();
+  while (slop.s[id]) id++; /* ignore authorize-id */
+
+  if (slop.len > id + 1)
+    if (!stralloc_copys(&user,slop.s + id + 1)) die_nomem();
+  if (slop.len > id + user.len + 2)
+    if (!stralloc_copys(&pass,slop.s + id + user.len + 2)) die_nomem();
+
+  if (!user.len || !pass.len) return err_input();
+  return authenticate();
+}
+
+#ifdef AUTHCRAM
+int auth_cram()
+{
+  int i, r;
+  char *s;
+
+  s = unique;
+  s += fmt_uint(s,getpid());
+  *s++ = '.';
+  s += fmt_ulong(s,(unsigned long) now());
+  *s++ = '@';
+  *s++ = 0;
+
+  if (!stralloc_copys(&pass,"<")) die_nomem();
+  if (!stralloc_cats(&pass,unique)) die_nomem();
+  if (!stralloc_cats(&pass,hostname)) die_nomem();
+  if (!stralloc_cats(&pass,">")) die_nomem();
+  if (b64encode(&pass,&slop) < 0) die_nomem();
+  if (!stralloc_0(&slop)) die_nomem();
+
+  out("334 ");
+  out(slop.s);
+  out("\r\n");
+  flush();
+
+  if (authgetl() < 0) return -1;
+  if (r = b64decode(authin.s,authin.len,&slop) == 1) return err_input();
+  if (r == -1 || !stralloc_0(&slop)) die_nomem();
+
+  i = str_chr(slop.s,' ');
+  s = slop.s + i;
+  while (*s == ' ') ++s;
+  slop.s[i] = 0;
+  if (!stralloc_copys(&user,slop.s)) die_nomem();
+  if (!stralloc_copys(&resp,s)) die_nomem();
+
+  if (!user.len || !resp.len) return err_input();
+  return authenticate();
+}
+#endif
+
+struct authcmd {
+  char *text;
+  int (*fun)();
+} authcmds[] = {
+  { "login", auth_login }
+, { "plain", auth_plain }
+#ifdef AUTHCRAM
+, { "cram-md5", auth_cram }
+#endif
+, { 0, err_noauth }
+};
+
+void smtp_auth(arg)
+char *arg;
+{
+  int i;
+  char *cmd = arg;
+
+  if (!hostname || !*childargs)
+  {
+    out("503 auth not available (#5.3.3)\r\n");
+    return;
+  }
+  if (authd) { err_authd(); return; }
+  if (seenmail) { err_authmail(); return; }
+
+  if (!stralloc_copys(&user,"")) die_nomem();
+  if (!stralloc_copys(&pass,"")) die_nomem();
+  if (!stralloc_copys(&resp,"")) die_nomem();
+
+  i = str_chr(cmd,' ');   
+  arg = cmd + i;
+  while (*arg == ' ') ++arg;
+  cmd[i] = 0;
+
+  for (i = 0;authcmds[i].text;++i)
+    if (case_equals(authcmds[i].text,cmd)) break;
+
+  switch (authcmds[i].fun(arg)) {
+    case 0:
+      authd = 1;
+      relayclient = "";
+      remoteinfo = user.s;
+      if (!env_unset("TCPREMOTEINFO")) die_read();
+      if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem();
+      out("235 ok, go ahead (#2.0.0)\r\n");
+      break;
+    case 1:
+      out("535 authorization failed (#5.7.0)\r\n");
+  }
+}
 
 struct commands smtpcommands[] = {
   { "rcpt", smtp_rcpt, 0 }
 , { "mail", smtp_mail, 0 }
 , { "data", smtp_data, flush }
+, { "auth", smtp_auth, flush }
 , { "quit", smtp_quit, flush }
 , { "helo", smtp_helo, flush }
 , { "ehlo", smtp_ehlo, flush }
 , { "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;
+{
+  hostname = argv[1];
+  childargs = argv + 2;
+
+#ifdef TLS
+  sig_alarmcatch(sigalrm);
+#endif
   sig_pipeignore();
   if (chdir(auto_qmail) == -1) die_control();
   setup();
