Frederik Vermeulen 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
and T. Hudson ),
stunnel (M. Trojnara ),
Postfix/TLS (L. Jaenicke ),
and modssl (R. Engelschall ).
Debug code, tlscipher selection, many feature suggestions,
French docs https://www.TBS-internet.com/ssl/qmail-tls.html
from Jean-Philippe Donnio
Openssl usage consulting from Bodo M"oller
Bug report from Andy Dustman
Bug reports: mailto:
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
+#include
+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
+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();