Frederik Vermeulen <jos-tls@kotnet.org> 20010627
http://www.esat.kuleuven.ac.be/~vermeule/qmail/tls.patch

This patch implements RFC2487 in qmail. This means you can 
get SSL or TLS encrypted and authenticated SMTP between 
the MTAs and between MTA and an MUA like Netscape. 
The code is considered experimental (but has worked for
many since its first release on 1999-03-22).

Usage: - install OpenSSL-0.9.6a http://www.openssl.org/
       - apply patch to qmail-1.03 http://www.qmail.org/ 
         Makefile and conf-cc were patched for appropriate
         linking. Apart from that, the patches to qmail-remote.c
         and qmail-smtpd.c can be applied separately.
       - provide a server certificate in /var/qmail/control/servercert.pem.
         "make cert" makes a self-signed certificate.
         "make cert-req" makes a certificate request.
         Note: you can add the CA certificate and intermediate
         certs to the end of servercert.pem.
       - replace qmail-smtpd and/or qmail-remote binary
       - verify operation (header information should show
         somothing like
         "Received [..] with DES-CBC3-SHA encrypted SMTP;")
         If you don't have a server to test with, you can test
         by sending mail to ping@mail.linux.student.kuleuven.ac.be,
         which will bounce your mail.

Optional: - when DEBUG is defined, some extra TLS info will be logged
	  - qmail-remote will authenticate with the certificate in
	    /var/qmail/control/clientcert.pem. By preference this is
	    the same as servercert.pem, where nsCertType should be 
	    == server,client or be a generic certificate (no usage specified). 
          - when a 512 RSA key is provided in /var/qmail/control/rsa512.pem,
            this key will be used instead of on-the-fly generation by
       	    qmail-smtpd. Periodical replacement can be done by crontab:
       	    01 01 * * *  umask 0077; /usr/local/ssl/bin/openssl genrsa \
       	     -out /var/qmail/control/rsa512.new 512 > /dev/null 2>&1;\
       	     chmod 600 /var/qmail/control/rsa512.new; chown qmaild.qmail \
       	     /var/qmail/control/rsa512.new; /bin/mv -f \
       	     /var/qmail/control/rsa512.new /var/qmail/control/rsa512.pem
          - server authentication:
	    qmail-remote requires authentication from servers for which
	    /var/qmail/control/tlshosts/host.dom.ain.pem exists.
	    The .pem file contains the validating CA certificates
	    (or self-signed server certificate with openssl-0.9.5).
	    CommonName has to match.
            WARNING: this option may cause mail to be delayed, bounced,
	    doublebounced, and lost.
          - client authentication:
	    when relay rules would reject an incoming mail, 
	    qmail-smtpd can allow the mail based on a presented cert.
	    Certs are verified against a CA list in 
	    /var/qmail/control/clientca.pem (eg. http://www.modssl.org/
	    source/cvs/exp/mod_ssl/pkg.mod_ssl/pkg.sslcfg/ca-bundle.crt)
            and the cert email-address has to match a line in
	    /var/qmail/control/tlsclients. This email-address is logged
	    in the headers.
          - cipher selection:
	    qmail-remote: 
	      openssl cipher string read from 
	      /var/qmail/control/tlsclientciphers
	    qmail-smtpd: 
	      openssl cipher string read from TLSCIPHERS environment variable
	      (can be different based on client IP address e.g.)
	      or if that is not available /var/qmail/control/tlsserverciphers

Caveats: - from this version on the server cert is in servercert.pem.
	 - binaries dynamically linked with current openssl versions need
	   recompilation when the shared openssl libs are upgraded.
         - this patch could conflict with other patches (notably those
	   replacing \n with \r\n, which is a bad idea on encrypted links).
         - some broken servers have a problem with TLSv1 compatibility.
           Uncomment the line where we set the SSL_OP_NO_TLSv1 option.

Copyright: Same terms as qmail
           Links with OpenSSL
           Inspiration and code from examples in SSLeay (E. Young
           <eay@cryptsoft.com> and T. Hudson <tjh@cryptsoft.com>),
           stunnel (M. Trojnara <mtrojnar@ddc.daewoo.com.pl>),
	   Postfix/TLS (L. Jaenicke <Lutz.Jaenicke@aet.tu-cottbus.de>),
	   and modssl (R. Engelschall <rse@engelschall.com>).
           Debug code, tlscipher selection, many feature suggestions,
	   French docs https://www.TBS-internet.com/ssl/qmail-tls.html 
	   from Jean-Philippe Donnio <tag-ssl@tbs-internet.com>
           Openssl usage consulting from Bodo M"oller <bmoeller@acm.org>
           Bug report from Andy Dustman <adustman@comstar.net>

Bug reports: mailto:<jos-tls@kotnet.org>


diff -ur qmail-1.03/Makefile qmail-1.03-tls/Makefile
--- qmail-1.03/Makefile	Mon Jun 15 12:53:16 1998
+++ qmail-1.03-tls/Makefile	Wed Jun 27 23:16:52 2001
@@ -1446,7 +1446,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` \
+	-L/usr/local/ssl/lib -lssl -lcrypto
 
 qmail-remote.0: \
 qmail-remote.8
@@ -1542,7 +1543,7 @@
 	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`
+	socket.lib` -L/usr/local/ssl/lib -lssl -lcrypto
 
 qmail-smtpd.0: \
 qmail-smtpd.8
@@ -2139,3 +2140,22 @@
 wait_pid.o: \
 compile wait_pid.c error.h haswaitp.h
 	./compile wait_pid.c
+
+cert:
+	/usr/local/ssl/bin/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:
+	/usr/local/ssl/bin/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 -ur qmail-1.03/conf-cc qmail-1.03-tls/conf-cc
--- qmail-1.03/conf-cc	Mon Jun 15 12:53:16 1998
+++ qmail-1.03-tls/conf-cc	Fri Dec 24 16:04:51 1999
@@ -1,3 +1,3 @@
-cc -O2
+cc -O2 -DTLS -I/usr/local/ssl/include
 
 This will be used to compile .c files.
diff -ur qmail-1.03/dns.c qmail-1.03-tls/dns.c
--- qmail-1.03/dns.c	Mon Jun 15 12:53:16 1998
+++ qmail-1.03-tls/dns.c	Thu Aug 24 19:38:32 2000
@@ -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 -ur qmail-1.03/ipalloc.h qmail-1.03-tls/ipalloc.h
--- qmail-1.03/ipalloc.h	Mon Jun 15 12:53:16 1998
+++ qmail-1.03-tls/ipalloc.h	Fri Dec 24 15:39:42 1999
@@ -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 -ur qmail-1.03/qmail-remote.c qmail-1.03-tls/qmail-remote.c
--- qmail-1.03/qmail-remote.c	Mon Jun 15 12:53:16 1998
+++ qmail-1.03-tls/qmail-remote.c	Wed Jun 27 23:47:17 2001
@@ -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);
+   Free(str);
+   str=ONELINE_NAME(X509_get_issuer_name(peer));
+   out("; issuer="); out(str);
+   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");
@@ -324,6 +577,11 @@
     case 1:
       if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break;
   }
+#ifdef TLS
+  if (control_rldef(&tlsclientciphers,"control/tlsclientciphers",0,"DEFAULT") != 1)
+    temp_control();
+  if(!stralloc_0(&tlsclientciphers)) temp_nomem();
+#endif
 }
 
 void main(argc,argv)
@@ -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 -ur qmail-1.03/qmail-smtpd.c qmail-1.03-tls/qmail-smtpd.c
--- qmail-1.03/qmail-smtpd.c	Mon Jun 15 12:53:16 1998
+++ qmail-1.03-tls/qmail-smtpd.c	Wed Jun 27 23:45:40 2001
@@ -20,18 +20,69 @@
 #include "now.h"
 #include "exit.h"
 #include "rcpthosts.h"
+#ifndef TLS
 #include "timeoutread.h"
 #include "timeoutwrite.h"
+#endif
 #include "commands.h"
+#ifdef TLS
+#include <openssl/ssl.h>
+SSL *ssl = NULL;
+
+stralloc clientcert = {0};
+stralloc tlsserverciphers = {0};
+#endif
 
 #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 +102,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"); }
@@ -81,6 +135,9 @@
 char *remoteinfo;
 char *local;
 char *relayclient;
+#ifdef TLS
+char *tlsciphers;
+#endif
 
 stralloc helohost = {0};
 char *fakehelo; /* pointer into helohost, or 0 */
@@ -101,7 +158,10 @@
 {
   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)
     die_control();
@@ -131,6 +191,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 +300,13 @@
 }
 void smtp_ehlo(arg) char *arg;
 {
-  smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
+  smtp_greet("250-"); 
+#ifdef TLS
+  if (ssl) out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
+  else out("\r\n250-PIPELINING\r\n250-STARTTLS\r\n250 8BITMIME\r\n");
+#else
+  out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
+#endif
   seenmail = 0; dohelo(arg);
 }
 void smtp_rset()
@@ -247,6 +324,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 +340,55 @@
     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 +400,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 +504,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 +515,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);
@@ -394,6 +544,56 @@
   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
+
 struct commands smtpcommands[] = {
   { "rcpt", smtp_rcpt, 0 }
 , { "mail", smtp_mail, 0 }
@@ -403,6 +603,9 @@
 , { "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 }
@@ -410,6 +613,9 @@
 
 void main()
 {
+#ifdef TLS
+  sig_alarmcatch(sigalrm);
+#endif
   sig_pipeignore();
   if (chdir(auto_qmail) == -1) die_control();
   setup();
