X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fsrc%2Ftls-gnu.c;h=383a00f4ededfc6b0aee181d9815f15f76259099;hb=60d10ce7e68a5f2cf771a5c079521c8e4f18d157;hp=5a1cdd40d5f215103db1e667eccbc842989e8e51;hpb=b2ba1b4ca0fa7023323c1911ccfebb24333f007e;p=exim.git diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 5a1cdd40d..383a00f4e 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -42,6 +42,7 @@ require current GnuTLS, then we'll drop support for the ancient libraries). /* needed to disable PKCS11 autoload unless requested */ #if GNUTLS_VERSION_NUMBER >= 0x020c00 # include +# define SUPPORT_PARAM_TO_PK_BITS #endif #if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP) # warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile" @@ -565,8 +566,7 @@ if (fd >= 0) (void)close(fd); return tls_error(US"TLS cache not a file", NULL, NULL); } - fp = fdopen(fd, "rb"); - if (!fp) + if (!(fp = fdopen(fd, "rb"))) { saved_errno = errno; (void)close(fd); @@ -575,14 +575,12 @@ if (fd >= 0) } m.size = statbuf.st_size; - m.data = malloc(m.size); - if (m.data == NULL) + if (!(m.data = malloc(m.size))) { fclose(fp); return tls_error(US"malloc failed", strerror(errno), NULL); } - sz = fread(m.data, m.size, 1, fp); - if (!sz) + if (!(sz = fread(m.data, m.size, 1, fp))) { saved_errno = errno; fclose(fp); @@ -664,9 +662,9 @@ if (rc < 0) if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing"); m.size = sz; - m.data = malloc(m.size); - if (m.data == NULL) + if (!(m.data = malloc(m.size))) return tls_error(US"memory allocation failed", strerror(errno), NULL); + /* this will return a size 1 less than the allocation size above */ rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM, m.data, &sz); @@ -709,6 +707,74 @@ return OK; +/* Create and install a selfsigned certificate, for use in server mode */ + +static int +tls_install_selfsign(exim_gnutls_state_st * state) +{ +gnutls_x509_crt_t cert = NULL; +time_t now; +gnutls_x509_privkey_t pkey = NULL; +const uschar * where; +int rc; + +where = US"initialising pkey"; +if ((rc = gnutls_x509_privkey_init(&pkey))) goto err; + +where = US"initialising cert"; +if ((rc = gnutls_x509_crt_init(&cert))) goto err; + +where = US"generating pkey"; +if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA, +#ifdef SUPPORT_PARAM_TO_PK_BITS + gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_LOW), +#else + 1024, +#endif + 0))) + goto err; + +where = US"configuring cert"; +now = 0; +if ( (rc = gnutls_x509_crt_set_version(cert, 3)) + || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now))) + || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL))) + || (rc = gnutls_x509_crt_set_expiration_time(cert, now + 60 * 60)) /* 1 hr */ + || (rc = gnutls_x509_crt_set_key(cert, pkey)) + + || (rc = gnutls_x509_crt_set_dn_by_oid(cert, + GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2)) + || (rc = gnutls_x509_crt_set_dn_by_oid(cert, + GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Exim Developers", 15)) + || (rc = gnutls_x509_crt_set_dn_by_oid(cert, + GNUTLS_OID_X520_COMMON_NAME, 0, + smtp_active_hostname, Ustrlen(smtp_active_hostname))) + ) + goto err; + +where = US"signing cert"; +if ((rc = gnutls_x509_crt_sign(cert, cert, pkey))) goto err; + +where = US"installing selfsign cert"; + /* Since: 2.4.0 */ +if ((rc = gnutls_certificate_set_x509_key(state->x509_cred, &cert, 1, pkey))) + goto err; + +rc = OK; + +out: + if (cert) gnutls_x509_crt_deinit(cert); + if (pkey) gnutls_x509_privkey_deinit(pkey); + return rc; + +err: + rc = tls_error(where, gnutls_strerror(rc), NULL); + goto out; +} + + + + /************************************************* * Variables re-expanded post-SNI * *************************************************/ @@ -741,7 +807,6 @@ int cert_count; /* We check for tls_sni *before* expansion. */ if (!host) /* server */ - { if (!state->received_sni) { if (state->tls_certificate && @@ -762,7 +827,6 @@ if (!host) /* server */ saved_tls_verify_certificates = state->exp_tls_verify_certificates; saved_tls_crl = state->exp_tls_crl; } - } rc = gnutls_certificate_allocate_credentials(&state->x509_cred); exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials"); @@ -779,14 +843,13 @@ if (!expand_check_tlsvar(tls_certificate)) /* certificate is mandatory in server, optional in client */ -if ((state->exp_tls_certificate == NULL) || - (*state->exp_tls_certificate == '\0')) - { +if ( !state->exp_tls_certificate + || !*state->exp_tls_certificate + ) if (!host) - return tls_error(US"no TLS server certificate is specified", NULL, NULL); + return tls_install_selfsign(state); else DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); - } if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey)) return DEFER; @@ -806,9 +869,9 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) state->exp_tls_certificate, state->exp_tls_privatekey); if (state->received_sni) - { - if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) && - (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0)) + if ( Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0 + && Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0 + ) { DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n"); } @@ -816,7 +879,6 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) { DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n"); } - } rc = gnutls_certificate_set_x509_key_file(state->x509_cred, CS state->exp_tls_certificate, CS state->exp_tls_privatekey, @@ -911,7 +973,7 @@ else but who knows if someone has some weird FIFO which always dumps some certs, or other weirdness. The thing we really want to check is that it's not a directory, since while OpenSSL supports that, GnuTLS does not. - So s/!S_ISREG/S_ISDIR/ and change some messsaging ... */ + So s/!S_ISREG/S_ISDIR/ and change some messaging ... */ if (S_ISDIR(statbuf.st_mode)) { DEBUG(D_tls) @@ -1448,7 +1510,7 @@ else int sep = 0; const uschar * list = state->exp_tls_verify_cert_hostnames; uschar * name; - while (name = string_nextinlist(&list, &sep, NULL, 0)) + while ((name = string_nextinlist(&list, &sep, NULL, 0))) if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name)) break; if (!name) @@ -1761,24 +1823,28 @@ state->fd_out = fileno(smtp_out); sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); do - { rc = gnutls_handshake(state->session); - } while ((rc == GNUTLS_E_AGAIN) || - (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen)); +while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen); alarm(0); if (rc != GNUTLS_E_SUCCESS) { - tls_error(US"gnutls_handshake", - sigalrm_seen ? "timed out" : gnutls_strerror(rc), NULL); /* It seems that, except in the case of a timeout, we have to close the connection right here; otherwise if the other end is running OpenSSL it hangs until the server times out. */ - if (!sigalrm_seen) + if (sigalrm_seen) + tls_error(US"gnutls_handshake", "timed out", NULL); + else { + tls_error(US"gnutls_handshake", gnutls_strerror(rc), NULL); + gnutls_alert_send_appropriate(state->session, rc); + millisleep(500); + shutdown(fileno(smtp_out), SHUT_WR); + for (rc = 1024; fgetc(smtp_in) != EOF && rc > 0; ) rc--; /* drain skt */ (void)fclose(smtp_out); (void)fclose(smtp_in); + smtp_out = smtp_in = NULL; } return FAIL; @@ -1803,8 +1869,7 @@ if ( state->verify_requirement != VERIFY_NONE /* Figure out peer DN, and if authenticated, etc. */ -rc = peer_status(state); -if (rc != OK) return rc; +if ((rc = peer_status(state)) != OK) return rc; /* Sets various Exim expansion variables; always safe within server */ @@ -1816,6 +1881,7 @@ and initialize appropriately. */ state->xfer_buffer = store_malloc(ssl_xfer_buffer_size); receive_getc = tls_getc; +receive_get_cache = tls_get_cache; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; @@ -1979,8 +2045,13 @@ do alarm(0); if (rc != GNUTLS_E_SUCCESS) - return tls_error(US"gnutls_handshake", - sigalrm_seen ? "timed out" : gnutls_strerror(rc), state->host); + if (sigalrm_seen) + { + gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED); + return tls_error(US"gnutls_handshake", "timed out", state->host); + } + else + return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host); DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n"); @@ -2057,7 +2128,7 @@ if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */ if (shutdown) { - DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n"); + DEBUG(D_tls) debug_printf("tls_close() from '%s': shutting down TLS\n"); gnutls_bye(state->session, GNUTLS_SHUT_WR); } @@ -2107,15 +2178,24 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) ssl_xfer_buffer_size); alarm(0); - /* A zero-byte return appears to mean that the TLS session has been + /* Timeouts do not get this far; see command_timeout_handler(). + A zero-byte return appears to mean that the TLS session has been closed down, not that the socket itself has been closed down. Revert to non-TLS handling. */ - if (inbytes == 0) + if (sigalrm_seen) + { + DEBUG(D_tls) debug_printf("Got tls read timeout\n"); + state->xfer_error = 1; + return EOF; + } + + else if (inbytes == 0) { DEBUG(D_tls) debug_printf("Got TLS_EOF\n"); receive_getc = smtp_getc; + receive_get_cache = smtp_get_cache; receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; @@ -2154,6 +2234,17 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) return state->xfer_buffer[state->xfer_buffer_lwm++]; } +void +tls_get_cache() +{ +#ifndef DISABLE_DKIM +exim_gnutls_state_st * state = &state_server; +int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm; +if (n > 0) + dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n); +#endif +} +