Frederik Vermeulen <jos-tls@kotnet.org> 20020526 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-21). Usage: - install OpenSSL-0.9.6c 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 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 tag-ping@tbs-internet.com, 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 vary 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). Qmail.org has a link to a combined tls+auth patch. - some broken servers have a problem with TLSv1 compatibility. Uncomment the line where we set the SSL_OP_NO_TLSv1 option. - needs working /dev/urandom for seeding random number generator. - packagers should make sure that installing without a valid servercert is impossible Copyright: GPL 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 10:53:16 1998 +++ qmail-1.03-tls/Makefile Sun May 26 20:41:54 2002 @@ -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: + 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 -ur qmail-1.03/conf-cc qmail-1.03-tls/conf-cc --- qmail-1.03/conf-cc Mon Jun 15 10:53:16 1998 +++ qmail-1.03-tls/conf-cc Fri Dec 24 15: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 10:53:16 1998 +++ qmail-1.03-tls/dns.c Thu Aug 24 17: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 10:53:16 1998 +++ qmail-1.03-tls/ipalloc.h Fri Dec 24 14: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 10:53:16 1998 +++ qmail-1.03-tls/qmail-remote.c Sun May 26 20:42:28 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"); @@ -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 10:53:16 1998 +++ qmail-1.03-tls/qmail-smtpd.c Wed Jun 27 21: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();