-/* $Cambridge: exim/src/src/tls-gnu.c,v 1.19 2007/04/13 15:13:47 ph10 Exp $ */
+/* $Cambridge: exim/src/src/tls-gnu.c,v 1.20 2008/09/03 18:53:29 fanf2 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
#define PARAM_SIZE 2*1024
-/* Values for verify_requirment and initialized */
+/* Values for verify_requirment */
enum { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
-enum { INITIALIZED_NOT, INITIALIZED_SERVER, INITIALIZED_CLIENT };
/* Local static variables for GNUTLS */
-static BOOL initialized = INITIALIZED_NOT;
static host_item *client_host;
static gnutls_dh_params dh_params = NULL;
prefix text to include in the logged error
host NULL if setting up a server;
the connected host if setting up a client
- err a GnuTLS error number, or 0 if local error
+ msg additional error string (may be NULL)
+ usually obtained from gnutls_strerror()
Returns: OK/DEFER/FAIL
*/
static int
-tls_error(uschar *prefix, host_item *host, int err)
+tls_error(uschar *prefix, host_item *host, const char *msg)
{
-uschar *errtext = US"";
-if (err != 0) errtext = string_sprintf(": %s", gnutls_strerror(err));
if (host == NULL)
{
- log_write(0, LOG_MAIN, "TLS error on connection from %s (%s)%s",
- (sender_fullhost != NULL)? sender_fullhost : US "local process",
- prefix, errtext);
+ uschar *conn_info = smtp_get_connection_info();
+ if (strncmp(conn_info, "SMTP ", 5) == 0)
+ conn_info += 5;
+ log_write(0, LOG_MAIN, "TLS error on %s (%s)%s%s",
+ conn_info, prefix, msg ? ": " : "", msg ? msg : "");
return DEFER;
}
else
{
- log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s",
- host->name, host->address, prefix, errtext);
+ log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s",
+ host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
return FAIL;
}
}
*/
static BOOL
-verify_certificate(gnutls_session session, uschar **error)
+verify_certificate(gnutls_session session, const char **error)
{
int verify;
uschar *dn_string = US"";
{
DEBUG(D_tls) debug_printf("no peer certificate supplied\n");
verify = GNUTLS_CERT_INVALID;
- *error = US"not supplied";
+ *error = "not supplied";
}
/* Handle the result of verification. INVALID seems to be set as well
{
tls_certificate_verified = FALSE;
if (*error == NULL) *error = ((verify & GNUTLS_CERT_REVOKED) != 0)?
- US"revoked" : US"invalid";
+ "revoked" : "invalid";
if (verify_requirement == VERIFY_REQUIRED)
{
DEBUG(D_tls) debug_printf("TLS certificate verification failed (%s): "
/* Initialize the data structures for holding the parameters */
ret = gnutls_dh_params_init(&dh_params);
-if (ret < 0) return tls_error(US"init dh_params", host, ret);
+if (ret < 0) return tls_error(US"init dh_params", host, gnutls_strerror(ret));
/* Set up the name of the cache file */
if (!string_format(filename, sizeof(filename), "%s/gnutls-params",
spool_directory))
- return tls_error(US"overlong filename", host, 0);
+ return tls_error(US"overlong filename", host, NULL);
/* Open the cache file for reading and if successful, read it and set up the
parameters. */
if (fstat(fd, &statbuf) < 0)
{
(void)close(fd);
- return tls_error(US"TLS cache stat failed", host, 0);
+ return tls_error(US"TLS cache stat failed", host, strerror(errno));
}
m.size = statbuf.st_size;
m.data = malloc(m.size);
if (m.data == NULL)
- return tls_error(US"memory allocation failed", host, 0);
+ return tls_error(US"memory allocation failed", host, strerror(errno));
+ errno = 0;
if (read(fd, m.data, m.size) != m.size)
- return tls_error(US"TLS cache read failed", host, 0);
+ return tls_error(US"TLS cache read failed", host, strerror(errno));
(void)close(fd);
ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM);
- if (ret < 0) return tls_error(US"DH params import", host, ret);
+ if (ret < 0)
+ return tls_error(US"DH params import", host, gnutls_strerror(ret));
DEBUG(D_tls) debug_printf("read D-H parameters from file\n");
free(m.data);
}
else
return tls_error(string_open_failed(errno, "%s for reading", filename),
- host, 0);
+ host, NULL);
/* If ret < 0, either the cache file does not exist, or the data it contains
is not useful. One particular case of this is when upgrading from an older
DEBUG(D_tls) debug_printf("generating %d bit Diffie-Hellman key...\n",
DH_BITS);
ret = gnutls_dh_params_generate2(dh_params, DH_BITS);
- if (ret < 0) return tls_error(US"D-H key generation", host, ret);
+ if (ret < 0) return tls_error(US"D-H key generation", host, gnutls_strerror(ret));
/* Write the parameters to a file in the spool directory so that we
can use them from other Exim processes. */
fd = Uopen(tempfilename, O_WRONLY|O_CREAT, 0400);
if (fd < 0)
return tls_error(string_open_failed(errno, "%s for writing", filename),
- host, 0);
+ host, NULL);
(void)fchown(fd, exim_uid, exim_gid); /* Probably not necessary */
/* export the parameters in a format that can be generated using GNUTLS'
m.size = PARAM_SIZE;
m.data = malloc(m.size);
if (m.data == NULL)
- return tls_error(US"memory allocation failed", host, 0);
+ return tls_error(US"memory allocation failed", host, strerror(errno));
m.size = PARAM_SIZE;
ret = gnutls_dh_params_export_pkcs3(dh_params, GNUTLS_X509_FMT_PEM, m.data,
&m.size);
- if (ret < 0) return tls_error(US"DH params export", host, ret);
+ if (ret < 0)
+ return tls_error(US"DH params export", host, gnutls_strerror(ret));
m.size = Ustrlen(m.data);
+ errno = 0;
if (write(fd, m.data, m.size) != m.size || write(fd, "\n", 1) != 1)
- return tls_error(US"TLS cache write failed", host, 0);
+ return tls_error(US"TLS cache write failed", host, strerror(errno));
free(m.data);
(void)close(fd);
if (rename(CS tempfilename, CS filename) < 0)
- return tls_error(string_sprintf("failed to rename %s as %s: %s",
- tempfilename, filename, strerror(errno)), host, 0);
+ return tls_error(string_sprintf("failed to rename %s as %s",
+ tempfilename, filename), host, strerror(errno));
DEBUG(D_tls) debug_printf("wrote D-H parameters to file %s\n", filename);
}
int rc;
uschar *cert_expanded, *key_expanded, *cas_expanded, *crl_expanded;
-initialized = (host == NULL)? INITIALIZED_SERVER : INITIALIZED_CLIENT;
+client_host = host;
rc = gnutls_global_init();
-if (rc < 0) return tls_error(US"tls-init", host, rc);
+if (rc < 0) return tls_error(US"tls-init", host, gnutls_strerror(rc));
/* Create D-H parameters, or read them from the cache file. This function does
its own SMTP error messaging. */
/* Create the credentials structure */
rc = gnutls_certificate_allocate_credentials(&x509_cred);
-if (rc < 0) return tls_error(US"certificate_allocate_credentials", host, rc);
+if (rc < 0)
+ return tls_error(US"certificate_allocate_credentials",
+ host, gnutls_strerror(rc));
/* This stuff must be done for each session, because different certificates
may be required for different sessions. */
{
uschar *msg = string_sprintf("cert/key setup: cert=%s key=%s",
cert_expanded, key_expanded);
- return tls_error(msg, host, rc);
+ return tls_error(msg, host, gnutls_strerror(rc));
}
}
else
{
if (host == NULL)
- return tls_error(US"no TLS server certificate is specified", host, 0);
+ return tls_error(US"no TLS server certificate is specified", NULL, NULL);
DEBUG(D_tls) debug_printf("no TLS client certificate is specified\n");
}
{
rc = gnutls_certificate_set_x509_trust_file(x509_cred, CS cas_expanded,
GNUTLS_X509_FMT_PEM);
- if (rc < 0) return tls_error(US"setup_certs", host, rc);
+ if (rc < 0) return tls_error(US"setup_certs", host, gnutls_strerror(rc));
if (crl != NULL && *crl != 0)
{
DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl_expanded);
rc = gnutls_certificate_set_x509_crl_file(x509_cred, CS crl_expanded,
GNUTLS_X509_FMT_PEM);
- if (rc < 0) return tls_error(US"CRL setup", host, rc);
+ if (rc < 0) return tls_error(US"CRL setup", host, gnutls_strerror(rc));
}
}
}
uschar *require_kx, uschar *require_proto)
{
int rc;
-uschar *error;
+const char *error;
uschar *expciphers = NULL;
uschar *expmac = NULL;
uschar *expkx = NULL;
if (tls_active >= 0)
{
- log_write(0, LOG_MAIN, "STARTTLS received in already encrypted "
- "connection from %s",
- (sender_fullhost != NULL)? sender_fullhost : US"local process");
+ tls_error("STARTTLS received after TLS started", NULL, "");
smtp_printf("554 Already in TLS\r\n");
return FAIL;
}
tls_session = tls_session_init(GNUTLS_SERVER, expciphers, expmac, expkx,
expproto);
if (tls_session == NULL)
- return tls_error(US"tls_session_init", NULL, GNUTLS_E_MEMORY_ERROR);
+ return tls_error(US"tls_session_init", NULL,
+ gnutls_strerror(GNUTLS_E_MEMORY_ERROR));
/* Set context and tell client to go ahead, except in the case of TLS startup
on connection, where outputting anything now upsets the clients and tends to
if (rc < 0)
{
- if (sigalrm_seen)
- Ustrcpy(ssl_errstring, "timed out");
- else
- Ustrcpy(ssl_errstring, gnutls_strerror(rc));
- log_write(0, LOG_MAIN,
- "TLS error on connection from %s (gnutls_handshake): %s",
- (sender_fullhost != NULL)? sender_fullhost : US"local process",
- ssl_errstring);
+ tls_error(US"gnutls_handshake", NULL,
+ sigalrm_seen ? "timed out" : gnutls_strerror(rc));
/* 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
if (verify_requirement != VERIFY_NONE &&
!verify_certificate(tls_session, &error))
{
- log_write(0, LOG_MAIN,
- "TLS error on connection from %s: certificate verification failed (%s)",
- (sender_fullhost != NULL)? sender_fullhost : US"local process", error);
+ tls_error(US"certificate verification failed", NULL, error);
return FAIL;
}
uschar *expmac = NULL;
uschar *expkx = NULL;
uschar *expproto = NULL;
-uschar *error;
+const char *error;
unsigned int server_certs_size;
int rc;
DEBUG(D_tls) debug_printf("initializing GnuTLS as a client\n");
-client_host = host;
verify_requirement = (verify_certs == NULL)? VERIFY_NONE : VERIFY_REQUIRED;
rc = tls_init(host, certificate, privatekey, verify_certs, verify_crl);
if (rc != OK) return rc;
expproto);
if (tls_session == NULL)
- return tls_error(US "tls_session_init", host, GNUTLS_E_MEMORY_ERROR);
+ return tls_error(US "tls_session_init", host,
+ gnutls_strerror(GNUTLS_E_MEMORY_ERROR));
gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)fd);
alarm(0);
if (rc < 0)
- {
- if (sigalrm_seen)
- {
- log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: "
- "gnutls_handshake timed out", host->name, host->address);
- return FAIL;
- }
- else return tls_error(US "gnutls_handshake", host, rc);
- }
+ return tls_error(US "gnutls_handshake", host,
+ sigalrm_seen ? "timed out" : gnutls_strerror(rc));
server_certs = gnutls_certificate_get_peers(tls_session, &server_certs_size);
if (verify_requirement != VERIFY_NONE &&
!verify_certificate(tls_session, &error))
- {
- log_write(0, LOG_MAIN,
- "TLS error on connection to %s [%s]: certificate verification failed (%s)",
- host->name, host->address, error);
- return FAIL;
- }
+ return tls_error(US"certificate verification failed", host, error);
construct_cipher_name(tls_session); /* Sets tls_cipher */
tls_active = fd;
static void
record_io_error(int ec, uschar *when, uschar *text)
{
-uschar *additional = US"";
+const char *msg;
if (ec == GNUTLS_E_FATAL_ALERT_RECEIVED)
- additional = string_sprintf(": %s",
+ msg = string_sprintf("%s: %s", gnutls_strerror(ec),
gnutls_alert_get_name(gnutls_alert_get(tls_session)));
-
-if (initialized == INITIALIZED_SERVER)
- log_write(0, LOG_MAIN, "TLS %s error on connection from %s: %s%s", when,
- (sender_fullhost != NULL)? sender_fullhost : US "local process",
- (ec == 0)? text : US gnutls_strerror(ec), additional);
-
else
- log_write(0, LOG_MAIN, "TLS %s error on connection to %s [%s]: %s%s", when,
- client_host->name, client_host->address,
- (ec == 0)? text : US gnutls_strerror(ec), additional);
+ msg = gnutls_strerror(ec);
+
+tls_error(when, client_host, msg);
}
-/* $Cambridge: exim/src/src/tls-openssl.c,v 1.12 2007/04/13 15:13:47 ph10 Exp $ */
+/* $Cambridge: exim/src/src/tls-openssl.c,v 1.13 2008/09/03 18:53:29 fanf2 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
prefix text to include in the logged error
host NULL if setting up a server;
the connected host if setting up a client
+ msg error message or NULL if we should ask OpenSSL
Returns: OK/DEFER/FAIL
*/
static int
-tls_error(uschar *prefix, host_item *host)
+tls_error(uschar *prefix, host_item *host, uschar *msg)
{
-ERR_error_string(ERR_get_error(), ssl_errstring);
+if (msg == NULL)
+ {
+ ERR_error_string(ERR_get_error(), ssl_errstring);
+ msg = ssl_errstring;
+ }
+
if (host == NULL)
{
- log_write(0, LOG_MAIN, "TLS error on connection from %s (%s): %s",
- (sender_fullhost != NULL)? sender_fullhost : US"local process",
- prefix, ssl_errstring);
+ uschar *conn_info = smtp_get_connection_info();
+ if (strncmp(conn_info, "SMTP ", 5) == 0)
+ conn_info += 5;
+ log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
+ conn_info, prefix, msg);
return DEFER;
}
else
{
log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s): %s",
- host->name, host->address, prefix, ssl_errstring);
+ host->name, host->address, prefix, msg);
return FAIL;
}
}
Arguments:
dhparam DH parameter file
+ host connected host, if client; NULL if server
Returns: TRUE if OK (nothing to set up, or setup worked)
*/
static BOOL
-init_dh(uschar *dhparam)
+init_dh(uschar *dhparam, host_item *host)
{
BOOL yield = TRUE;
BIO *bio;
if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL)
{
- log_write(0, LOG_MAIN, "DH: could not read %s: %s", dhexpanded,
- strerror(errno));
+ tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
+ host, strerror(errno));
yield = FALSE;
}
else
{
if ((dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL)
{
- log_write(0, LOG_MAIN, "DH: could not load params from %s",
- dhexpanded);
+ tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
+ host, NULL);
yield = FALSE;
}
else
ctx = SSL_CTX_new((host == NULL)?
SSLv23_server_method() : SSLv23_client_method());
-if (ctx == NULL) return tls_error(US"SSL_CTX_new", host);
+if (ctx == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
/* It turns out that we need to seed the random number generator this early in
order to get the full complement of ciphers to work. It took me roughly a day
if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr));
if (!RAND_status())
- {
- if (host == NULL)
- {
- log_write(0, LOG_MAIN, "TLS error on connection from %s: "
- "unable to seed random number generator",
- (sender_fullhost != NULL)? sender_fullhost : US"local process");
- return DEFER;
- }
- else
- {
- log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: "
- "unable to seed random number generator",
- host->name, host->address);
- return FAIL;
- }
- }
+ return tls_error(US"RAND_status", host,
+ "unable to seed random number generator");
}
/* Set up the information callback, which outputs if debugging is at a suitable
/* XXX (Silently?) ignore failure here? XXX*/
if (!(SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)))
- return tls_error(US"SSL_CTX_set_option", host);
+ return tls_error(US"SSL_CTX_set_option", host, NULL);
#endif
/* Initialize with DH parameters if supplied */
-if (!init_dh(dhparam)) return DEFER;
+if (!init_dh(dhparam, host)) return DEFER;
/* Set up certificate and key */
DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
if (!SSL_CTX_use_certificate_chain_file(ctx, CS expanded))
return tls_error(string_sprintf(
- "SSL_CTX_use_certificate_chain_file file=%s", expanded), host);
+ "SSL_CTX_use_certificate_chain_file file=%s", expanded), host, NULL);
}
if (privatekey != NULL &&
DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
if (!SSL_CTX_use_PrivateKey_file(ctx, CS expanded, SSL_FILETYPE_PEM))
return tls_error(string_sprintf(
- "SSL_CTX_use_PrivateKey_file file=%s", expanded), host);
+ "SSL_CTX_use_PrivateKey_file file=%s", expanded), host, NULL);
}
}
{
struct stat statbuf;
if (!SSL_CTX_set_default_verify_paths(ctx))
- return tls_error(US"SSL_CTX_set_default_verify_paths", host);
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
if (Ustat(expcerts, &statbuf) < 0)
{
if ((file == NULL || statbuf.st_size > 0) &&
!SSL_CTX_load_verify_locations(ctx, CS file, CS dir))
- return tls_error(US"SSL_CTX_load_verify_locations", host);
+ return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
if (file != NULL)
{
DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file);
}
if (X509_STORE_load_locations(cvstore, CS file, CS dir) == 0)
- return tls_error(US"X509_STORE_load_locations", host);
+ return tls_error(US"X509_STORE_load_locations", host, NULL);
/* setting the flags to check against the complete crl chain */
if (tls_active >= 0)
{
- log_write(0, LOG_MAIN, "STARTTLS received in already encrypted "
- "connection from %s",
- (sender_fullhost != NULL)? sender_fullhost : US"local process");
+ tls_error("STARTTLS received after TLS started", NULL, "");
smtp_printf("554 Already in TLS\r\n");
return FAIL;
}
while (*s != 0) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
- return tls_error(US"SSL_CTX_set_cipher_list", NULL);
+ return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
}
/* If this is a host for which certificate verification is mandatory or
/* Prepare for new connection */
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL);
+if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
SSL_clear(ssl);
/* Set context and tell client to go ahead, except in the case of TLS startup
if (rc <= 0)
{
- if (sigalrm_seen) Ustrcpy(ssl_errstring, "timed out");
- else ERR_error_string(ERR_get_error(), ssl_errstring);
- log_write(0, LOG_MAIN, "TLS error on connection from %s (SSL_accept): %s",
- (sender_fullhost != NULL)? sender_fullhost : US"local process",
- ssl_errstring);
+ tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL);
return FAIL;
}
while (*s != 0) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
- return tls_error(US"SSL_CTX_set_cipher_list", host);
+ return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
}
rc = setup_certs(verify_certs, crl, host, FALSE);
if (rc != OK) return rc;
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host);
+if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
SSL_set_fd(ssl, fd);
SSL_set_connect_state(ssl);
alarm(0);
if (rc <= 0)
- {
- if (sigalrm_seen)
- {
- log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: "
- "SSL_connect timed out", host->name, host->address);
- return FAIL;
- }
- else return tls_error(US"SSL_connect", host);
- }
+ return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");