summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.starttls10
-rw-r--r--qmail-control.91
-rw-r--r--qmail-remote.c49
-rw-r--r--qmail-smtpd.810
-rw-r--r--qmail-smtpd.c177
-rw-r--r--ssl_timeoutio.c13
-rw-r--r--ssl_timeoutio.h1
-rw-r--r--tls.h1
8 files changed, 125 insertions, 137 deletions
diff --git a/README.starttls b/README.starttls
index 07ee275..6168c6d 100644
--- a/README.starttls
+++ b/README.starttls
@@ -42,16 +42,6 @@ Optional: - when DEBUG is defined, some extra TLS info will be logged
42 an exhaustive list of hosts TLS is tried on. 42 an exhaustive list of hosts TLS is tried on.
43 If /var/qmail/control/notlshosts/host.dom.ain is present, 43 If /var/qmail/control/notlshosts/host.dom.ain is present,
44 no TLS is tried on this host. 44 no TLS is tried on this host.
45 - client authentication:
46 when relay rules would reject an incoming mail,
47 qmail-smtpd can allow the mail based on a presented cert.
48 Certs are verified against a CA list in
49 /var/qmail/control/clientca.pem (eg. http://www.modssl.org/
50 source/cvs/exp/mod_ssl/pkg.mod_ssl/pkg.sslcfg/ca-bundle.crt)
51 and the cert email-address has to match a line in
52 /var/qmail/control/tlsclients. This email-address is logged
53 in the headers. CRLs can be provided through
54 /var/qmail/control/clientcrl.pem.
55 - cipher selection: 45 - cipher selection:
56 qmail-remote: 46 qmail-remote:
57 openssl cipher string (`man ciphers`) read from 47 openssl cipher string (`man ciphers`) read from
diff --git a/qmail-control.9 b/qmail-control.9
index 9c6edd8..6898643 100644
--- a/qmail-control.9
+++ b/qmail-control.9
@@ -78,7 +78,6 @@ control default used by
78.I timeoutconnect \fR60 \fRqmail-remote 78.I timeoutconnect \fR60 \fRqmail-remote
79.I timeoutremote \fR1200 \fRqmail-remote 79.I timeoutremote \fR1200 \fRqmail-remote
80.I timeoutsmtpd \fR1200 \fRqmail-smtpd 80.I timeoutsmtpd \fR1200 \fRqmail-smtpd
81.I tlsclients \fR(none) \fRqmail-smtpd
82.I tlsclientciphers \fR(none) \fRqmail-remote 81.I tlsclientciphers \fR(none) \fRqmail-remote
83.I tlshosts/FQDN.pem \fR(none) \fRqmail-remote 82.I tlshosts/FQDN.pem \fR(none) \fRqmail-remote
84.I tlsserverciphers \fR(none) \fRqmail-smtpd 83.I tlsserverciphers \fR(none) \fRqmail-smtpd
diff --git a/qmail-remote.c b/qmail-remote.c
index d2412aa..94bb69f 100644
--- a/qmail-remote.c
+++ b/qmail-remote.c
@@ -302,8 +302,8 @@ void smtp_quit()
302{ 302{
303#ifdef TLS 303#ifdef TLS
304 /* shouldn't talk to the client unless in an appropriate state */ 304 /* shouldn't talk to the client unless in an appropriate state */
305 int state = ssl ? ssl->state : SSL_ST_BEFORE; 305 if ((!smtps && !ssl) || (ssl && SSL_is_init_finished(ssl))
306 if (state & SSL_ST_OK || (!smtps && state & SSL_ST_BEFORE)) 306 || (!smtps && ssl && SSL_in_before(ssl)))
307#endif 307#endif
308 substdio_putsflush(&smtpto,"QUIT\r\n"); 308 substdio_putsflush(&smtpto,"QUIT\r\n");
309 /* waiting for remote side is just too ridiculous */ 309 /* waiting for remote side is just too ridiculous */
@@ -539,6 +539,41 @@ int tls_init()
539 SSL_set_cipher_list(myssl, ciphers); 539 SSL_set_cipher_list(myssl, ciphers);
540 alloc_free(saciphers.s); 540 alloc_free(saciphers.s);
541 541
542#if OPENSSL_VERSION_NUMBER >= 0x10100005L
543 stralloc opensslconf = {0};
544 if (control_readfile(&opensslconf, "control/opensslconf", 0) == -1)
545 { SSL_free(myssl); temp_control(); }
546 if (opensslconf.len) {
547 SSL_CONF_CTX *cctx = SSL_CONF_CTX_new();
548 SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE);
549 SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CLIENT);
550 SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE);
551 SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS);
552 SSL_CONF_CTX_set_ssl(cctx, myssl);
553
554 int i, j, next = 0;
555 char *cmd, * arg;
556 for (i = 0; i < opensslconf.len; i += next) {
557 cmd = opensslconf.s + i;
558 next = str_len(cmd) + 1;
559
560 j = str_chr(cmd, ' ');
561 arg = cmd + j;
562 while (*arg == ' ') ++arg;
563 cmd[j] = 0;
564
565 if (SSL_CONF_cmd(cctx, cmd, arg) <= 0) {
566 SSL_free(myssl);
567 out("Zopensslconf \""); out(cmd); out(" "); out(arg);
568 out("\" failed: "); out(ssl_error());
569 TLS_QUIT;
570 }
571 }
572
573 (void)SSL_CONF_CTX_finish(cctx);
574 }
575#endif
576
542 /* set SNI hostname */ 577 /* set SNI hostname */
543 if (partner_fqdn) 578 if (partner_fqdn)
544 SSL_set_tlsext_host_name(myssl, partner_fqdn); 579 SSL_set_tlsext_host_name(myssl, partner_fqdn);
@@ -614,8 +649,12 @@ int tls_init()
614 X509_NAME *subj = X509_get_subject_name(peercert); 649 X509_NAME *subj = X509_get_subject_name(peercert);
615 i = X509_NAME_get_index_by_NID(subj, NID_commonName, -1); 650 i = X509_NAME_get_index_by_NID(subj, NID_commonName, -1);
616 if (i >= 0) { 651 if (i >= 0) {
617 const ASN1_STRING *s = X509_NAME_get_entry(subj, i)->value; 652 X509_NAME_ENTRY *entry = X509_NAME_get_entry(subj, i);
618 if (s) { peer.len = s->length; peer.s = s->data; } 653 ASN1_STRING *s = X509_NAME_ENTRY_get_data(entry);
654#if OPENSSL_VERSION_NUMBER < 0x10100005L
655#define ASN1_STRING_get0_data ASN1_STRING_data
656#endif
657 if (s) { peer.len = ASN1_STRING_length(s); peer.s = (unsigned char *)ASN1_STRING_get0_data(s); }
619 } 658 }
620 if (peer.len <= 0) { 659 if (peer.len <= 0) {
621 out("ZTLS unable to verify server "); 660 out("ZTLS unable to verify server ");
@@ -668,7 +707,7 @@ int utf8received()
668 if (r == 0) break; 707 if (r == 0) break;
669 if (r == -1) temp_read(); 708 if (r == -1) temp_read();
670 709
671 if (ch == '\n') { 710 if (ch == '\n' && receivedline.len) {
672 if (!stralloc_append(&header,"\r")) temp_nomem(); /* received.c does not add '\r' */ 711 if (!stralloc_append(&header,"\r")) temp_nomem(); /* received.c does not add '\r' */
673 if (!stralloc_append(&header,"\n")) temp_nomem(); 712 if (!stralloc_append(&header,"\n")) temp_nomem();
674 if (case_startb(receivedline.s,5,"Date:")) return 0; /* header to quit asap */ 713 if (case_startb(receivedline.s,5,"Date:")) return 0; /* header to quit asap */
diff --git a/qmail-smtpd.8 b/qmail-smtpd.8
index 05d1239..5920dd9 100644
--- a/qmail-smtpd.8
+++ b/qmail-smtpd.8
@@ -295,16 +295,6 @@ will wait for each new buffer of data from the remote SMTP client.
295Default: 1200. 295Default: 1200.
296 296
297.TP 5 297.TP 5
298.I tlsclients
299A list of email addresses. When relay rules would reject an incoming message,
300.B qmail-smtpd
301can allow it if the client presents a certificate that can be verified against
302the CA list in
303.I clientca.pem
304and the certificate email address is in
305.IR tlsclients .
306
307.TP 5
308.I tlsserverciphers 298.I tlsserverciphers
309A set of OpenSSL cipher strings. Multiple ciphers contained in a 299A set of OpenSSL cipher strings. Multiple ciphers contained in a
310string should be separated by a colon. If the environment variable 300string should be separated by a colon. If the environment variable
diff --git a/qmail-smtpd.c b/qmail-smtpd.c
index 18795bc..f40a4c5 100644
--- a/qmail-smtpd.c
+++ b/qmail-smtpd.c
@@ -71,7 +71,6 @@ char *relayclient;
71# define SERVERCERT "control/servercert.pem" 71# define SERVERCERT "control/servercert.pem"
72 72
73void tls_init(); 73void tls_init();
74int tls_verify();
75void tls_nogateway(); 74void tls_nogateway();
76int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */ 75int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */
77stralloc proto = {0}; 76stralloc proto = {0};
@@ -508,9 +507,6 @@ int addrallowed()
508 int r; 507 int r;
509 r = rcpthosts(addr.s,str_len(addr.s)); 508 r = rcpthosts(addr.s,str_len(addr.s));
510 if (r == -1) die_control(); 509 if (r == -1) die_control();
511#ifdef TLS
512 if (r == 0) if (tls_verify()) r = -2;
513#endif
514 return r; 510 return r;
515} 511}
516 512
@@ -1261,19 +1257,44 @@ void smtp_tls(char *arg)
1261 * Grab well-defined DH parameters from OpenSSL, see the get_rfc* 1257 * Grab well-defined DH parameters from OpenSSL, see the get_rfc*
1262 * functions in <openssl/bn.h> for all available primes. 1258 * functions in <openssl/bn.h> for all available primes.
1263 */ 1259 */
1264static DH *make_dh_params(BIGNUM *(*prime)(BIGNUM *), const char *gen) 1260#if OPENSSL_VERSION_NUMBER < 0x10100005L
1261static int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
1265{ 1262{
1266 DH *dh = DH_new(); 1263 /* q is optional */
1267 if (!dh) 1264 if (p == NULL || g == NULL)
1268 return NULL; 1265 return 0;
1269 1266 BN_free(dh->p);
1270 dh->p = prime(NULL); 1267 BN_free(dh->q);
1271 BN_dec2bn(&dh->g, gen); 1268 BN_free(dh->g);
1272 if (!dh->p || !dh->g) { 1269 dh->p = p;
1273 DH_free(dh); 1270 dh->q = q;
1274 return NULL; 1271 dh->g = g;
1275 } 1272
1276 return dh; 1273 if (q != NULL)
1274 dh->length = BN_num_bits(q);
1275
1276 return 1;
1277}
1278#endif
1279
1280static DH *make_dh_params(BIGNUM *(*prime)(BIGNUM *))
1281{
1282 BIGNUM *p, *g;
1283 DH *dh = DH_new();
1284 if (!dh)
1285 return NULL;
1286
1287 p = prime(NULL);
1288 g = BN_new();
1289 if (g != NULL)
1290 BN_set_word(g, 2);
1291 if (!p || !g || !DH_set0_pqg(dh, p, NULL, g)) {
1292 DH_free(dh);
1293 BN_free(p);
1294 BN_free(g);
1295 return NULL;
1296 }
1297 return dh;
1277} 1298}
1278 1299
1279/* Storage and initialization for DH parameters. */ 1300/* Storage and initialization for DH parameters. */
@@ -1300,7 +1321,7 @@ static struct dhparam {
1300static DH *tmp_dh_cb(SSL *ssl, int export, int keylen) 1321static DH *tmp_dh_cb(SSL *ssl, int export, int keylen)
1301{ 1322{
1302 EVP_PKEY *pkey = SSL_get_privatekey(ssl); 1323 EVP_PKEY *pkey = SSL_get_privatekey(ssl);
1303 int type = pkey ? EVP_PKEY_type(pkey->type) : EVP_PKEY_NONE; 1324 int type = pkey ? EVP_PKEY_base_id(pkey) : EVP_PKEY_NONE;
1304 unsigned n; 1325 unsigned n;
1305 1326
1306 /* 1327 /*
@@ -1321,7 +1342,7 @@ static DH *tmp_dh_cb(SSL *ssl, int export, int keylen)
1321 for (n = 0; n < sizeof(dhparams)/sizeof(dhparams[0]); n++) { 1342 for (n = 0; n < sizeof(dhparams)/sizeof(dhparams[0]); n++) {
1322 if (keylen >= dhparams[n].min) { 1343 if (keylen >= dhparams[n].min) {
1323 if (dhparams[n].dh == NULL) 1344 if (dhparams[n].dh == NULL)
1324 dhparams[n].dh = make_dh_params(dhparams[n].prime, "2"); 1345 dhparams[n].dh = make_dh_params(dhparams[n].prime);
1325 return dhparams[n].dh; 1346 return dhparams[n].dh;
1326 } 1347 }
1327 } 1348 }
@@ -1338,6 +1359,8 @@ static DH *ssl_dh_GetParamFromFile(const char *file)
1338 return NULL; 1359 return NULL;
1339 dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); 1360 dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
1340 BIO_free(bio); 1361 BIO_free(bio);
1362 if (!dh)
1363 (void)ERR_get_error();
1341 return dh; 1364 return dh;
1342} 1365}
1343 1366
@@ -1351,6 +1374,8 @@ static EC_GROUP *ssl_ec_GetParamFromFile(const char *file)
1351 return NULL; 1374 return NULL;
1352 group = PEM_read_bio_ECPKParameters(bio, NULL, NULL, NULL); 1375 group = PEM_read_bio_ECPKParameters(bio, NULL, NULL, NULL);
1353 BIO_free(bio); 1376 BIO_free(bio);
1377 if (!group)
1378 (void)ERR_get_error();
1354 return group; 1379 return group;
1355} 1380}
1356 1381
@@ -1378,85 +1403,6 @@ void tls_out(const char *s1, const char *s2)
1378} 1403}
1379void tls_err(const char *s) { tls_out(s, ssl_error()); if (smtps) die_read(); } 1404void tls_err(const char *s) { tls_out(s, ssl_error()); if (smtps) die_read(); }
1380 1405
1381int tls_verify()
1382{
1383 stralloc clients = {0};
1384 struct constmap mapclients;
1385
1386 if (!ssl || relayclient || ssl_verified) return 0;
1387 ssl_verified = 1; /* don't do this twice */
1388
1389 /* request client cert to see if it can be verified by one of our CAs
1390 * and the associated email address matches an entry in tlsclients */
1391 switch (control_readfile(&clients, "control/tlsclients", 0))
1392 {
1393 case 1:
1394 if (constmap_init(&mapclients, clients.s, clients.len, 0)) {
1395 /* if CLIENTCA contains all the standard root certificates, a
1396 * 0.9.6b client might fail with SSL_R_EXCESSIVE_MESSAGE_SIZE;
1397 * it is probably due to 0.9.6b supporting only 8k key exchange
1398 * data while the 0.9.6c release increases that limit to 100k */
1399 STACK_OF(X509_NAME) *sk = SSL_load_client_CA_file(CLIENTCA);
1400 if (sk) {
1401 SSL_set_client_CA_list(ssl, sk);
1402 SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, NULL);
1403 break;
1404 }
1405 constmap_free(&mapclients);
1406 }
1407 case 0: alloc_free(clients.s); return 0;
1408 case -1: die_control();
1409 }
1410
1411 if (ssl_timeoutrehandshake(timeout, ssl_rfd, ssl_wfd, ssl) <= 0) {
1412 const char *err = ssl_error_str();
1413 tls_out("rehandshake failed", err); die_read();
1414 }
1415
1416 do { /* one iteration */
1417 X509 *peercert;
1418 X509_NAME *subj;
1419 stralloc email = {0};
1420
1421 int n = SSL_get_verify_result(ssl);
1422 if (n != X509_V_OK)
1423 { ssl_verify_err = X509_verify_cert_error_string(n); break; }
1424 peercert = SSL_get_peer_certificate(ssl);
1425 if (!peercert) break;
1426
1427 subj = X509_get_subject_name(peercert);
1428 n = X509_NAME_get_index_by_NID(subj, NID_pkcs9_emailAddress, -1);
1429 if (n >= 0) {
1430 const ASN1_STRING *s = X509_NAME_get_entry(subj, n)->value;
1431 if (s) { email.len = s->length; email.s = s->data; }
1432 }
1433
1434 if (email.len <= 0)
1435 ssl_verify_err = "contains no email address";
1436 else if (!constmap(&mapclients, email.s, email.len))
1437 ssl_verify_err = "email address not in my list of tlsclients";
1438 else {
1439 /* add the cert email to the proto if it helped allow relaying */
1440 --proto.len;
1441 if (!stralloc_cats(&proto, "\n (cert ") /* continuation line */
1442 || !stralloc_catb(&proto, email.s, email.len)
1443 || !stralloc_cats(&proto, ")")
1444 || !stralloc_0(&proto)) die_nomem();
1445 relayclient = "";
1446 protocol = proto.s;
1447 }
1448
1449 X509_free(peercert);
1450 } while (0);
1451 constmap_free(&mapclients); alloc_free(clients.s);
1452
1453 /* we are not going to need this anymore: free the memory */
1454 SSL_set_client_CA_list(ssl, NULL);
1455 SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
1456
1457 return relayclient ? 1 : 0;
1458}
1459
1460void tls_init() 1406void tls_init()
1461{ 1407{
1462 SSL *myssl; 1408 SSL *myssl;
@@ -1525,6 +1471,7 @@ void tls_init()
1525 SSL_set_cipher_list(myssl, ciphers); 1471 SSL_set_cipher_list(myssl, ciphers);
1526 alloc_free(saciphers.s); 1472 alloc_free(saciphers.s);
1527 1473
1474 //TODO: we shouldn't use hardcoded DH: see https://weakdh.org/
1528 SSL_set_tmp_dh_callback(myssl, tmp_dh_cb); 1475 SSL_set_tmp_dh_callback(myssl, tmp_dh_cb);
1529 1476
1530 /* try to read DH parameters from certificate */ 1477 /* try to read DH parameters from certificate */
@@ -1550,6 +1497,42 @@ void tls_init()
1550 EC_KEY_free(eckey); 1497 EC_KEY_free(eckey);
1551#endif 1498#endif
1552 1499
1500#if OPENSSL_VERSION_NUMBER >= 0x10100005L
1501 stralloc opensslconf = {0};
1502 if (control_readfile(&opensslconf, "control/opensslconf", 0) == -1)
1503 { SSL_free(myssl); die_control(); }
1504 if (opensslconf.len) {
1505 SSL_CONF_CTX *cctx = SSL_CONF_CTX_new();
1506 SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE);
1507 SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER);
1508 SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE);
1509 SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS);
1510 SSL_CONF_CTX_set_ssl(cctx, myssl);
1511
1512 int i, j, next = 0;
1513 char *cmd, * arg;
1514 for (i = 0; i < opensslconf.len; i += next) {
1515 cmd = opensslconf.s + i;
1516 next = str_len(cmd) + 1;
1517
1518 j = str_chr(cmd, ' ');
1519 arg = cmd + j;
1520 while (*arg == ' ') ++arg;
1521 cmd[j] = 0;
1522
1523 if (SSL_CONF_cmd(cctx, cmd, arg) <= 0) {
1524 enew(); eout("opensslconf \""); eout(cmd); eout(" "); eout(arg);
1525 eout("\" failed: "); eout(ssl_error()); eout("\n"); eflush();
1526 tls_out("OpenSSL", "unable to initialize ssl");
1527 SSL_free(myssl);
1528 die_read();
1529 }
1530 }
1531
1532 (void)SSL_CONF_CTX_finish(cctx);
1533 }
1534#endif
1535
1553 SSL_set_rfd(myssl, ssl_rfd = substdio_fileno(&ssin)); 1536 SSL_set_rfd(myssl, ssl_rfd = substdio_fileno(&ssin));
1554 SSL_set_wfd(myssl, ssl_wfd = substdio_fileno(&ssout)); 1537 SSL_set_wfd(myssl, ssl_wfd = substdio_fileno(&ssout));
1555 1538
diff --git a/ssl_timeoutio.c b/ssl_timeoutio.c
index 5b2dc9d..30025d5 100644
--- a/ssl_timeoutio.c
+++ b/ssl_timeoutio.c
@@ -68,19 +68,6 @@ int ssl_timeoutconn(int t, int rfd, int wfd, SSL *ssl)
68 return r; 68 return r;
69} 69}
70 70
71int ssl_timeoutrehandshake(int t, int rfd, int wfd, SSL *ssl)
72{
73 int r;
74
75 SSL_renegotiate(ssl);
76 r = ssl_timeoutio(SSL_do_handshake, t, rfd, wfd, ssl, NULL, 0);
77 if (r <= 0 || ssl->type == SSL_ST_CONNECT) return r;
78
79 /* this is for the server only */
80 ssl->state = SSL_ST_ACCEPT;
81 return ssl_timeoutio(SSL_do_handshake, t, rfd, wfd, ssl, NULL, 0);
82}
83
84int ssl_timeoutread(int t, int rfd, int wfd, SSL *ssl, char *buf, int len) 71int ssl_timeoutread(int t, int rfd, int wfd, SSL *ssl, char *buf, int len)
85{ 72{
86 if (!buf) return 0; 73 if (!buf) return 0;
diff --git a/ssl_timeoutio.h b/ssl_timeoutio.h
index 073cb67..fff3859 100644
--- a/ssl_timeoutio.h
+++ b/ssl_timeoutio.h
@@ -10,7 +10,6 @@
10 10
11int ssl_timeoutconn(int t, int rfd, int wfd, SSL *ssl); 11int ssl_timeoutconn(int t, int rfd, int wfd, SSL *ssl);
12int ssl_timeoutaccept(int t, int rfd, int wfd, SSL *ssl); 12int ssl_timeoutaccept(int t, int rfd, int wfd, SSL *ssl);
13int ssl_timeoutrehandshake(int t, int rfd, int wfd, SSL *ssl);
14 13
15int ssl_timeoutread(int t, int rfd, int wfd, SSL *ssl, char *buf, int len); 14int ssl_timeoutread(int t, int rfd, int wfd, SSL *ssl, char *buf, int len);
16int ssl_timeoutwrite(int t, int rfd, int wfd, SSL *ssl, char *buf, int len); 15int ssl_timeoutwrite(int t, int rfd, int wfd, SSL *ssl, char *buf, int len);
diff --git a/tls.h b/tls.h
index 808b115..3046c4e 100644
--- a/tls.h
+++ b/tls.h
@@ -2,6 +2,7 @@
2#define TLS_H 2#define TLS_H
3 3
4#include <openssl/ssl.h> 4#include <openssl/ssl.h>
5#include <openssl/err.h>
5 6
6/* ECC: make sure we have at least 1.0.0 */ 7/* ECC: make sure we have at least 1.0.0 */
7#if !defined(OPENSSL_NO_EC) && defined(TLSEXT_ECPOINTFORMAT_uncompressed) 8#if !defined(OPENSSL_NO_EC) && defined(TLSEXT_ECPOINTFORMAT_uncompressed)