diff options
Diffstat (limited to 'qmail-smtpd.c')
| -rw-r--r-- | qmail-smtpd.c | 177 |
1 files changed, 80 insertions, 97 deletions
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 | ||
| 73 | void tls_init(); | 73 | void tls_init(); |
| 74 | int tls_verify(); | ||
| 75 | void tls_nogateway(); | 74 | void tls_nogateway(); |
| 76 | int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */ | 75 | int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */ |
| 77 | stralloc proto = {0}; | 76 | stralloc 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 | */ |
| 1264 | static DH *make_dh_params(BIGNUM *(*prime)(BIGNUM *), const char *gen) | 1260 | #if OPENSSL_VERSION_NUMBER < 0x10100005L |
| 1261 | static 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 | |||
| 1280 | static 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 { | |||
| 1300 | static DH *tmp_dh_cb(SSL *ssl, int export, int keylen) | 1321 | static 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 | } |
| 1379 | void tls_err(const char *s) { tls_out(s, ssl_error()); if (smtps) die_read(); } | 1404 | void tls_err(const char *s) { tls_out(s, ssl_error()); if (smtps) die_read(); } |
| 1380 | 1405 | ||
| 1381 | int 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 | |||
| 1460 | void tls_init() | 1406 | void 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 | ||
