Frederik Vermeulen 19990829 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.4 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 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. Optional: - when DEBUG is defined, some extra SSL info will be logged - 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. Daily replacement can be done by crontab: 01 01 * * * /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 Copyright: Same terms as qmail Links with OpenSSL Inspiration from examples in SSLeay (E. Young and T. Hudson ) and stunnel (M. Trojnara ) Debug code from Jean-Philippe Donnio Openssl usage consulting from Bodo M"oller 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: 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 Sun Aug 29 15:08:47 1999 @@ -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,10 @@ 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/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 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 Aug 27 17:49:36 1999 @@ -1,3 +1,3 @@ -cc -O2 +cc -O2 -I/usr/local/ssl/include This will be used to compile .c files. 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 Sun Aug 29 15:09:09 1999 @@ -1,3 +1,4 @@ +#define TLS #include #include #include @@ -26,8 +27,15 @@ #include "tcpto.h" #include "readwrite.h" #include "timeoutconn.h" +#ifndef TLS #include "timeoutread.h" #include "timeoutwrite.h" +#endif + +#ifdef TLS +#include +SSL *ssl = NULL; +#endif #define HUGESMTPTEXT 5000 @@ -107,17 +115,57 @@ 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) r = SSL_read(ssl,buf,n); 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) r = SSL_write(ssl,buf,n); else r = write(fd,buf,n); + saveerrno = errno; + alarm(0); + if (flagtimedout) { errno = error_timeout; return -1; } + errno = saveerrno; + return r; +} +#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; } @@ -179,6 +227,35 @@ char *prepend; char *append; { +/* 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); + Free(str); + X509_free(peer); + } + out(";\n"); + } +#endif + substdio_putsflush(&smtpto,"QUIT\r\n"); /* waiting for remote side is just too ridiculous */ out(prepend); @@ -221,15 +298,100 @@ unsigned long code; int flagbother; int i; - +#ifdef TLS + SSL_CTX *ctx; + int saveerrno, r; +#ifdef DEBUG + char buf[1024]; +#endif +#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) + { +#ifdef DEBUG + SSL_load_error_strings(); +#endif + SSLeay_add_ssl_algorithms(); + if(!(ctx=SSL_CTX_new(SSLv23_client_method()))) + {out("ZTLS not available: error initializing ctx"); +#ifdef DEBUG + out(": "); + out(ERR_error_string(ERR_get_error(), buf)); +#endif + out("\n"); + zerodie();} + + SSL_CTX_use_certificate_file(ctx, "control/cert.pem", SSL_FILETYPE_PEM); + + if(!(ssl=SSL_new(ctx))) + {out("ZTLS not available: error initializing ssl"); +#ifdef DEBUG + out(": "); + out(ERR_error_string(ERR_get_error(), buf)); +#endif + out("\n"); + 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) + {out("ZTLS not available: connect failed"); +#ifdef DEBUG + out(": "); + out(ERR_error_string(ERR_get_error(), buf)); +#endif + out("\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"); + } + } + } +#endif + substdio_puts(&smtpto,"MAIL FROM:<"); substdio_put(&smtpto,sender.s,sender.len); substdio_puts(&smtpto,">\r\n"); @@ -338,7 +500,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(); 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 Fri Aug 27 19:10:43 1999 @@ -1,3 +1,4 @@ +#define TLS #include "sig.h" #include "readwrite.h" #include "stralloc.h" @@ -20,18 +21,60 @@ #include "now.h" #include "exit.h" #include "rcpthosts.h" +#ifndef TLS #include "timeoutread.h" #include "timeoutwrite.h" +#endif #include "commands.h" +#ifdef TLS +#include +SSL *ssl = NULL; +#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) r = SSL_read(ssl,buf,n); 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) r = SSL_write(ssl,buf,n); 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; } @@ -229,7 +272,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() @@ -269,7 +318,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 +422,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 +433,17 @@ 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_ready(&protocolinfo,protocolinfo.len + 16)) die_nomem(); + byte_copy(protocolinfo.s+protocolinfo.len,16," encrypted SMTP"); + protocolinfo.len += 16; + } 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 +459,53 @@ out("\r\n"); } +#ifdef TLS +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;} + + 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;} + SSL_CTX_set_tmp_rsa_callback(ctx, tmp_rsa_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 +515,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 +525,7 @@ void main() { + sig_alarmcatch(sigalrm); sig_pipeignore(); if (chdir(auto_qmail) == -1) die_control(); setup();