Frederik Vermeulen 19990324 http://www.esat.kuleuven.ac.be/~vermeule/qmail/tls.patch This patch implements RFC2487 (starttls) in qmail (qmail-smtpd as server, qmail-remote as client). This means you can get SSL or TLS encrypted SMTP between the MTAs and between MTA and an MUA like Netscape4.5. The code is considered experimental. Usage: - install OpenSSL 0.9.3 or newer http://www.openssl.org/ - apply patches to qmail 1.01 or newer (warning: the provided ones were made against an already patched qmail. Jonathan Ruano mailed me a patch against qmail-1.03: http://www.esat.kuleuven.ac.be/~vermeule/qmail/qmail-1.03.tls.patch) 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 certificate in /var/qmail/control/cert.pem. "make cert" should make a self-signed certificate. - replace qmail-smtpd and/or qmail-remote binary - verify operation (header information should show something 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@linux.student.kuleuven.ac.be, which will bounce your mail. Copyright: Same terms as qmail Links with OpenSSL Inspiration from examples in SSLeay (E. Young and T. Hudson ) and stunnel (M. Trojnara ) Interoperability: - Netscape 4.5x - Postfix/TLS http://www.aet.TU-Cottbus.DE/personen/jaenicke/pfixtls/ - Test results with Netscape Messenger and Microsoft Exchange are welcome Patches: mailto: --- Makefile.old Fri Mar 19 17:48:16 1999 +++ Makefile Sun Mar 21 23:43:20 1999 @@ -1414,7 +1414,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 @@ -1527,7 +1528,7 @@ constmap.o received.o date822fmt.o now.o qmail.o fd.a \ wait.a datetime.a open.a getln.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` + auto_qmail.o `cat socket.lib` -L/usr/local/ssl/lib -lssl -lcrypto qmail-smtpd.0: \ qmail-smtpd.8 @@ -2179,3 +2180,10 @@ wait_pid.o: \ compile wait_pid.c wait_pid.c wait_pid.c error.h wait_pid.c ./compile wait_pid.c + +cert: + /usr/local/ssl/bin/openssl req -new -x509 -nodes \ + -out /var/qmail/control/cert.pem -days 366 \ + -keyout /var/qmail/control/cert.pem + chmod 640 /var/qmail/control/cert.pem + chown qmaild.qmail /var/qmail/control/cert.pem --- conf-cc.old Fri Mar 19 18:50:52 1999 +++ conf-cc Sun Mar 21 23:42:30 1999 @@ -1,3 +1,3 @@ -cc -O2 +cc -O2 -I/usr/local/ssl/include This will be used to compile .c files. --- qmail-smtpd.c.notls Mon May 25 16:55:50 1998 +++ qmail-smtpd.c Sun Mar 21 23:42:38 1999 @@ -22,6 +21,15 @@ #include "env.h" #include "now.h" #include "exit.h" +#define TLS +#ifdef TLS +/*#include this was a SSLeay bug */ +#include +#include +#include +#include +SSL *ssl = NULL; +#endif #define MAXHOPS 30 int timeout = 1200; @@ -37,7 +46,12 @@ int r; int saveerrno; flush(); alarm(timeout); - r = read(fd,buf,n); saveerrno = errno; +#ifdef TLS + if (ssl) r = SSL_read(ssl,buf,n); else r = read(fd,buf,n); +#else + r = read(fd,buf,n); +#endif + saveerrno = errno; alarm(0); errno = saveerrno; return r; } @@ -306,7 +320,13 @@ 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"); } void smtp_helo(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 ? arg : ""); } void smtp_rset() { @@ -359,6 +379,9 @@ void smtp_data() { int hops; int r; unsigned long qp; +#ifdef TLS + stralloc protocolinfo = {0}; +#endif if (!seenmail) { err_wantmail(); return; } if (!rcptto.len) { err_wantrcpt(); return; } seenmail = 0; @@ -366,7 +389,17 @@ qp = qmail_qp(&qqt); out("354 go ahead\r\n"); - received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,case_diffs(remotehost,helohost.s) ? helohost.s : 0); +#ifdef TLS + if(ssl){ + if (!stralloc_copys(&protocolinfo, SSL_CIPHER_get_name(SSL_get_current_cipher(ssl)))) outofmem(); + if (!stralloc_ready(&protocolinfo,protocolinfo.len + 16)) outofmem(); + byte_copy(protocolinfo.s+protocolinfo.len,16," encrypted SMTP"); + protocolinfo.len += 16; + } else if (!stralloc_copyb(&protocolinfo,"SMTP",5)) outofmem(); + received(&qqt,protocolinfo.s,local,remoteip,remotehost,remoteinfo,case_diffs(remotehost,helohost.s) ? helohost.s : 0); +#else + received(&qqt,"SMTP", local,remoteip,remotehost,remoteinfo,case_diffs(remotehost,helohost.s) ? helohost.s : 0); +#endif blast(&ssin,&hops); hops = (hops >= MAXHOPS); if (hops) qmail_fail(&qqt); @@ -391,6 +424,41 @@ default: out("451 qq internal bug (#4.3.0)\r\n"); return; } } +#ifdef TLS +void smtp_tls(arg) char *arg; { + SSL_CTX *ctx; + + if (arg) + {out("501 Syntax error (no parameters allowed) (#5.5.4)\r\n"); + return;} + + SSLeay_add_ssl_algorithms(); + 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/cert.pem", SSL_FILETYPE_PEM)) + {out("454 TLS not available: missing RSA private key (#4.3.0)\r\n"); + return;} + if(!SSL_CTX_use_certificate_file(ctx, "control/cert.pem", SSL_FILETYPE_PEM)) + {out("454 TLS not available: missing certificate (#4.3.0)\r\n"); + return;} + if(SSL_CTX_need_tmp_RSA(ctx)) + if(!SSL_CTX_set_tmp_rsa(ctx,RSA_generate_key(512,RSA_F4,NULL,NULL))) + {out("454 TLS not available: error generating RSA temp key (#4.3.0)\r\n"); + return;} + + out("220 ready for tls\r\n"); flush(); + + if(!(ssl=SSL_new(ctx))) die(); + SSL_set_fd(ssl,0); + if(SSL_accept(ssl)<=0) die(); + substdio_fdbuf(&ssout,SSL_write,ssl,ssoutbuf,sizeof(ssoutbuf)); + + remotehost = env_get("TCPREMOTEHOST"); + if (!remotehost) remotehost = "unknown"; + dohelo(remotehost); +} +#endif static struct { void (*fun)(); char *text; int flagflush; } smtpcmd[] = { { smtp_rcpt, "rcpt", 0 } @@ -401,6 +469,9 @@ , { smtp_helo, "ehlo", 1 } , { smtp_rset, "rset", 0 } , { smtp_help, "help", 1 } +#ifdef TLS +, { smtp_tls, "starttls", 1 } +#endif , { err_noop, "noop", 1 } , { err_vrfy, "vrfy", 1 } , { 0, 0, 0 } --- qmail-remote.c.notls Sun Mar 21 23:55:28 1999 +++ qmail-remote.c Wed Mar 24 12:32:13 1999 @@ -29,6 +29,19 @@ #include "timeoutread.h" #include "timeoutwrite.h" +#define TLS +#ifdef TLS +#include "select.h" /*timeval*/ +/*#include this was a SSLeay bug */ +#include +#include +#include +#include +SSL *ssl = NULL; +int ssl_timeoutread(); +int ssl_timeoutwrite(); +#endif + #define HUGESMTPTEXT 5000 #define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */ @@ -216,6 +229,9 @@ unsigned long code; int flaganyrecipok; int i; +#ifdef TLS + SSL_CTX *ctx; +#endif substdio_fdbuf(&ssto,timeoutwrite,TIMEOUTWRITE(timeout,fd),smtptobuf,sizeof(smtptobuf)); substdio_fdbuf(&ssfrom,timeoutread,TIMEOUTREAD(timeout,fd),smtpfrombuf,sizeof(smtpfrombuf)); @@ -226,16 +242,81 @@ quit(&ssto,&ssfrom); } +#ifdef TLS + if (substdio_puts(&ssto,"EHLO ") == -1) writeerr(); +#else if (substdio_puts(&ssto,"HELO ") == -1) writeerr(); +#endif if (substdio_put(&ssto,helohost.s,helohost.len) == -1) writeerr(); if (substdio_puts(&ssto,"\r\n") == -1) writeerr(); if (substdio_flush(&ssto) == -1) writeerr(); if (smtpcode(&ssfrom) != 250) +#ifdef TLS + { + if (substdio_puts(&ssto,"HELO ") == -1) writeerr(); + if (substdio_put(&ssto,helohost.s,helohost.len) == -1) writeerr(); + if (substdio_puts(&ssto,"\r\n") == -1) writeerr(); + if (substdio_flush(&ssto) == -1) writeerr(); + if (smtpcode(&ssfrom) != 250) + { + out("ZConnected to "); outhost(); out(" but my name was rejected.\n"); + quit(&ssto,&ssfrom); + } + } + +#else { out("ZConnected to "); outhost(); out(" but my name was rejected.\n"); quit(&ssto,&ssfrom); } +#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) + { + if (substdio_puts(&ssto,"STARTTLS\r\n") == -1) writeerr(); + if (substdio_flush(&ssto) == -1) writeerr(); + if (smtpcode(&ssfrom) == 220) + { + SSLeay_add_ssl_algorithms(); + if(!(ctx=SSL_CTX_new(SSLv23_client_method()))) + {out("ZTLS not available: error initializing ctx\n"); + quit(&ssto,&ssfrom);} + + SSL_CTX_use_RSAPrivateKey_file(ctx, "control/cert.pem", SSL_FILETYPE_PEM); + SSL_CTX_use_certificate_file(ctx, "control/cert.pem", SSL_FILETYPE_PEM); + if(SSL_CTX_need_tmp_RSA(ctx)) + if(!SSL_CTX_set_tmp_rsa(ctx,RSA_generate_key(512,RSA_F4,NULL,NULL))) + {out("ZTLS not available: error generating RSA temp key\n"); + quit(&ssto,&ssfrom);} + + if(!(ssl=SSL_new(ctx))) + {out("ZTLS not available: error initializing ctx\n"); + quit(&ssto,&ssfrom);} + SSL_set_fd(ssl,fd); + if(SSL_connect(ssl)<=0) + {out("ZTLS not available: connect failed\n"); + quit(&ssto,&ssfrom);} + substdio_fdbuf(&ssto,ssl_timeoutwrite,ssl,smtptobuf,sizeof(smtptobuf)); + substdio_fdbuf(&ssfrom,ssl_timeoutread,ssl,smtpfrombuf,sizeof(smtpfrombuf)); + + if (substdio_puts(&ssto,"EHLO ") == -1) writeerr(); + if (substdio_put(&ssto,helohost.s,helohost.len) == -1) writeerr(); + if (substdio_puts(&ssto,"\r\n") == -1) writeerr(); + if (substdio_flush(&ssto) == -1) writeerr(); + + if (smtpcode(&ssfrom) != 250) + { + out("ZTLS connected to "); outhost(); out(" but my name was rejected.\n"); + quit(&ssto,&ssfrom); + } + } + } +#endif if (substdio_puts(&ssto,"MAIL FROM:<") == -1) writeerr(); if (substdio_put(&ssto,sender.s,sender.len) == -1) writeerr(); @@ -479,3 +560,47 @@ temp_noconn(); } + +#ifdef TLS +int ssl_timeoutread(ssl,buf,len) SSL *ssl; char *buf; int len; +{ + fd_set rfds; + struct timeval tv; + int fd; + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + fd = SSL_get_fd(ssl); + FD_ZERO(&rfds); + FD_SET(fd,&rfds); + + if (select(fd + 1,&rfds,(fd_set *) 0,(fd_set *) 0,&tv) == -1) return -1; + if (FD_ISSET(fd,&rfds)) return SSL_read(ssl,buf,len); + + shutdown(fd,0); + errno = error_timeout; + return -1; +} + +int ssl_timeoutwrite(ssl,buf,len) SSL *ssl; char *buf; int len; +{ + fd_set wfds; + struct timeval tv; + int fd; + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + fd = SSL_get_fd(ssl); + FD_ZERO(&wfds); + FD_SET(fd,&wfds); + + if (select(fd + 1,(fd_set *) 0,&wfds,(fd_set *) 0,&tv) == -1) return -1; + if (FD_ISSET(fd,&wfds)) return SSL_write(ssl,buf,len); + + shutdown(fd,1); + errno = error_timeout; + return -1; +} +#endif