-/* $Cambridge: exim/src/src/tls-gnu.c,v 1.3 2004/12/21 09:26:31 ph10 Exp $ */
+/* $Cambridge: exim/src/src/tls-gnu.c,v 1.24 2009/11/16 19:50:37 nm4 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
/* See the file NOTICE for conditions of use and distribution. */
/* This module provides TLS (aka SSL) support for Exim using the GnuTLS
#define UNKNOWN_NAME "unknown"
-#define DH_BITS 768
-#define RSA_BITS 512
+#define DH_BITS 1024
+#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_rsa_params rsa_params = NULL;
static gnutls_dh_params dh_params = NULL;
static gnutls_certificate_server_credentials x509_cred = NULL;
static int ssl_session_timeout = 200;
static int verify_requirement;
-/* Priorities for TLS algorithms to use. At present, only the cipher priority
-vector can be altered. */
+/* Priorities for TLS algorithms to use. In each case there's a default table,
+and space into which it can be copied and altered. */
+
+static const int default_proto_priority[16] = {
+ GNUTLS_TLS1,
+ GNUTLS_SSL3,
+ 0 };
-static const int protocol_priority[16] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
+static int proto_priority[16];
-static const int kx_priority[16] = {
+static const int default_kx_priority[16] = {
GNUTLS_KX_RSA,
GNUTLS_KX_DHE_DSS,
GNUTLS_KX_DHE_RSA,
- GNUTLS_KX_RSA_EXPORT,
0 };
+static int kx_priority[16];
+
static int default_cipher_priority[16] = {
GNUTLS_CIPHER_AES_256_CBC,
GNUTLS_CIPHER_AES_128_CBC,
static int cipher_priority[16];
-static const int mac_priority[16] = {
+static const int default_mac_priority[16] = {
GNUTLS_MAC_SHA,
GNUTLS_MAC_MD5,
0 };
+static int mac_priority[16];
+
+/* These two are currently not changeable. */
+
static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
static const int cert_type_priority[16] = { GNUTLS_CRT_X509, 0 };
-/* Tables of cipher names and equivalent numbers */
+/* Tables of priority names and equivalent numbers */
typedef struct pri_item {
uschar *name;
int *values;
} pri_item;
+
+static int tls1_codes[] = { GNUTLS_TLS1, 0 };
+static int ssl3_codes[] = { GNUTLS_SSL3, 0 };
+
+static pri_item proto_index[] = {
+ { US"TLS1", tls1_codes },
+ { US"SSL3", ssl3_codes }
+};
+
+
+static int kx_rsa_codes[] = { GNUTLS_KX_RSA,
+ GNUTLS_KX_DHE_RSA, 0 };
+static int kx_dhe_codes[] = { GNUTLS_KX_DHE_DSS,
+ GNUTLS_KX_DHE_RSA, 0 };
+static int kx_dhe_dss_codes[] = { GNUTLS_KX_DHE_DSS, 0 };
+static int kx_dhe_rsa_codes[] = { GNUTLS_KX_DHE_RSA, 0 };
+
+static pri_item kx_index[] = {
+ { US"DHE_DSS", kx_dhe_dss_codes },
+ { US"DHE_RSA", kx_dhe_rsa_codes },
+ { US"RSA", kx_rsa_codes },
+ { US"DHE", kx_dhe_codes }
+};
+
+
static int arcfour_128_codes[] = { GNUTLS_CIPHER_ARCFOUR_128, 0 };
static int arcfour_40_codes[] = { GNUTLS_CIPHER_ARCFOUR_40, 0 };
static int arcfour_codes[] = { GNUTLS_CIPHER_ARCFOUR_128,
};
+static int mac_sha_codes[] = { GNUTLS_MAC_SHA, 0 };
+static int mac_md5_codes[] = { GNUTLS_MAC_MD5, 0 };
+
+static pri_item mac_index[] = {
+ { US"SHA", mac_sha_codes },
+ { US"SHA1", mac_sha_codes },
+ { US"MD5", mac_md5_codes }
+};
+
+
/*************************************************
* Handle TLS error *
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): "
-
-/*************************************************
-* Write/read datum to/from file *
-*************************************************/
-
-/* These functions are used for saving and restoring the RSA and D-H parameters
-for use by all Exim processes. Data that is read is placed in malloc'd store
-because that's what happens for newly generated data.
-
-Arguments:
- fd the file descriptor
- d points to the datum
-
-returns: FALSE on error (errno set)
-*/
-
-static BOOL
-write_datum(int fd, gnutls_datum *d)
-{
-if (write(fd, &(d->size), sizeof(d->size)) != sizeof(d->size)) return FALSE;
-if (write(fd, d->data, d->size) != d->size) return FALSE;
-return TRUE;
-}
-
-
-static BOOL
-read_datum(int fd, gnutls_datum *d)
-{
-if (read(fd, &(d->size), sizeof(d->size)) != sizeof(d->size)) return FALSE;
-d->data = malloc(d->size);
-if (d->data == NULL) return FALSE;
-if (read(fd, d->data, d->size) != d->size) return FALSE;
-return TRUE;
-}
-
-
-
/*************************************************
-* Setup up RSA and DH parameters *
+* Setup up DH parameters *
*************************************************/
-/* Generating the RSA and D-H parameters takes a long time. They only need to
+/* Generating the D-H parameters may take a long time. They only need to
be re-generated every so often, depending on security policy. What we do is to
keep these parameters in a file in the spool directory. If the file does not
exist, we generate them. This means that it is easy to cause a regeneration.
*/
static int
-init_rsa_dh(host_item *host)
+init_dh(host_item *host)
{
-int fd, ret;
-gnutls_datum m, e, d, p, q, u, prime, generator;
+int fd;
+int ret;
+gnutls_datum m;
uschar filename[200];
/* Initialize the data structures for holding the parameters */
-ret = gnutls_rsa_params_init(&rsa_params);
-if (ret < 0) return tls_error(US"init rsa_params", host, ret);
-
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. If this fails because of a non-existent
-file, compute a new set of parameters, write them to a temporary file, and then
-rename that file as the cache file. Other opening errors are bad. */
+/* Open the cache file for reading and if successful, read it and set up the
+parameters. */
fd = Uopen(filename, O_RDONLY, 0);
-if (fd < 0)
+if (fd >= 0)
{
- unsigned int rsa_bits = RSA_BITS;
- unsigned int dh_bits = DH_BITS;
- uschar tempfilename[sizeof(filename) + 10];
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) < 0)
+ {
+ (void)close(fd);
+ 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, strerror(errno));
+ errno = 0;
+ if (read(fd, m.data, m.size) != m.size)
+ 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, gnutls_strerror(ret));
+ DEBUG(D_tls) debug_printf("read D-H parameters from file\n");
+
+ free(m.data);
+ }
- if (errno != ENOENT)
- return tls_error(string_open_failed(errno, "%s for reading", filename),
- host, 0);
+/* If the file does not exist, fall through to compute new data and cache it.
+If there was any other opening error, it is serious. */
- DEBUG(D_tls) debug_printf("generating %d bit RSA key...\n", RSA_BITS);
- ret = gnutls_rsa_params_generate2(rsa_params, RSA_BITS);
- if (ret < 0) return tls_error(US"RSA key generation", host, ret);
+else if (errno == ENOENT)
+ {
+ ret = -1;
+ DEBUG(D_tls)
+ debug_printf("parameter cache file %s does not exist\n", filename);
+ }
+else
+ return tls_error(string_open_failed(errno, "%s for reading", filename),
+ 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
+release of Exim in which the data was stored in a different format. We don't
+try to be clever and support both formats; we just regenerate new data in this
+case. */
+
+if (ret < 0)
+ {
+ uschar tempfilename[sizeof(filename) + 10];
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 */
- ret = gnutls_rsa_params_export_raw(rsa_params, &m, &e, &d, &p, &q, &u,
- &rsa_bits);
- if (ret < 0) return tls_error(US"RSA params export", host, ret);
-
- ret = gnutls_dh_params_export_raw(dh_params, &prime, &generator, &dh_bits);
- if (ret < 0) return tls_error(US"DH params export", host, ret);
-
- if (!write_datum(fd, &m) ||
- !write_datum(fd, &e) ||
- !write_datum(fd, &d) ||
- !write_datum(fd, &p) ||
- !write_datum(fd, &q) ||
- !write_datum(fd, &u) ||
- !write_datum(fd, &prime) ||
- !write_datum(fd, &generator))
- return tls_error(US"TLS cache write failed", host, 0);
-
+ /* export the parameters in a format that can be generated using GNUTLS'
+ * certtool or other programs.
+ *
+ * The commands for certtool are:
+ * $ certtool --generate-dh-params --bits 1024 > params
+ */
+
+ m.size = PARAM_SIZE;
+ m.data = malloc(m.size);
+ if (m.data == NULL)
+ 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, 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, 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);
-
- DEBUG(D_tls) debug_printf("wrote RSA and D-H parameters to file\n");
- }
-
-/* File opened for reading; get the data */
-
-else
- {
- if (!read_datum(fd, &m) ||
- !read_datum(fd, &e) ||
- !read_datum(fd, &d) ||
- !read_datum(fd, &p) ||
- !read_datum(fd, &q) ||
- !read_datum(fd, &u) ||
- !read_datum(fd, &prime) ||
- !read_datum(fd, &generator))
- return tls_error(US"TLS cache read failed", host, 0);
-
- (void)close(fd);
-
- ret = gnutls_rsa_params_import_raw(rsa_params, &m, &e, &d, &p, &q, &u);
- if (ret < 0) return tls_error(US"RSA params import", host, ret);
+ return tls_error(string_sprintf("failed to rename %s as %s",
+ tempfilename, filename), host, strerror(errno));
- ret = gnutls_dh_params_import_raw(dh_params, &prime, &generator);
- if (ret < 0) return tls_error(US"DH params import", host, ret);
-
- DEBUG(D_tls) debug_printf("read RSA and D-H parameters from file\n");
+ DEBUG(D_tls) debug_printf("wrote D-H parameters to file %s\n", filename);
}
-DEBUG(D_tls) debug_printf("initialized RSA and D-H parameters\n");
+DEBUG(D_tls) debug_printf("initialized D-H parameters\n");
return OK;
}
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 RSA and D-H parameters, or read them from the cache file. This
-function does its own SMTP error messaging. */
+/* Create D-H parameters, or read them from the cache file. This function does
+its own SMTP error messaging. */
-rc = init_rsa_dh(host);
+rc = init_dh(host);
if (rc != OK) return rc;
/* 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. */
if (!expand_check(certificate, US"tls_certificate", &cert_expanded))
return DEFER;
+key_expanded = NULL;
if (privatekey != NULL)
{
if (!expand_check(privatekey, US"tls_privatekey", &key_expanded))
return DEFER;
}
-else key_expanded = cert_expanded;
+
+/* If expansion was forced to fail, key_expanded will be NULL. If the result of
+the expansion is an empty string, ignore it also, and assume that the private
+key is in the same file as the certificate. */
+
+if (key_expanded == NULL || *key_expanded == 0)
+ key_expanded = cert_expanded;
/* Set the certificate and private keys */
cert_expanded, key_expanded);
rc = gnutls_certificate_set_x509_key_file(x509_cred, CS cert_expanded,
CS key_expanded, GNUTLS_X509_FMT_PEM);
- if (rc < 0)
+ if (rc < 0)
{
uschar *msg = string_sprintf("cert/key setup: cert=%s key=%s",
- cert_expanded, key_expanded);
- return tls_error(msg, host, rc);
- }
+ cert_expanded, key_expanded);
+ return tls_error(msg, host, gnutls_strerror(rc));
+ }
}
/* A certificate is mandatory in a server, but not in a client */
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");
}
return DEFER;
}
- DEBUG(D_tls) debug_printf("verify certificates = %s size=%d\n",
- cas_expanded, (int)statbuf.st_size);
+ DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
+ cas_expanded, statbuf.st_size);
/* If the cert file is empty, there's no point in loading the CRL file. */
{
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));
}
}
}
/* Associate the parameters with the x509 credentials structure. */
gnutls_certificate_set_dh_params(x509_cred, dh_params);
-gnutls_certificate_set_rsa_params(x509_cred, rsa_params);
DEBUG(D_tls) debug_printf("initialized certificate stuff\n");
return OK;
/*************************************************
-* Remove ciphers from priority list *
+* Remove from a priority list *
*************************************************/
/* Cautiously written so that it will remove duplicates if present.
*/
static void
-remove_ciphers(int *list, int *remove_list)
+remove_priority(int *list, int *remove_list)
{
for (; *remove_list != 0; remove_list++)
{
/*************************************************
-* Add ciphers to priority list *
+* Add to a priority list *
*************************************************/
/* Cautiously written to check the list size
*/
static BOOL
-add_ciphers(int *list, int list_max, int *add_list)
+add_priority(int *list, int list_max, int *add_list)
{
int next = 0;
while (list[next] != 0) next++;
+/*************************************************
+* Adjust a priority list *
+*************************************************/
+
+/* This function is called to adjust the lists of cipher algorithms, MAC
+algorithms, key-exchange methods, and protocols.
+
+Arguments:
+ plist the appropriate priority list
+ psize the length of the list
+ s the configuation string
+ index the index of recognized strings
+ isize the length of the index
+
+
+ which text for an error message
+
+Returns: FALSE if the table overflows, else TRUE
+*/
+
+static BOOL
+set_priority(int *plist, int psize, uschar *s, pri_item *index, int isize,
+ uschar *which)
+{
+int sep = 0;
+BOOL first = TRUE;
+uschar *t;
+
+while ((t = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)) != NULL)
+ {
+ int i;
+ BOOL exclude = t[0] == '!';
+ if (first && !exclude) plist[0] = 0;
+ first = FALSE;
+ for (i = 0; i < isize; i++)
+ {
+ uschar *ss = strstric(t, index[i].name, FALSE);
+ if (ss != NULL)
+ {
+ uschar *endss = ss + Ustrlen(index[i].name);
+ if ((ss == t || !isalnum(ss[-1])) && !isalnum(*endss))
+ {
+ if (exclude)
+ remove_priority(plist, index[i].values);
+ else
+ {
+ if (!add_priority(plist, psize, index[i].values))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "GnuTLS init failed: %s "
+ "priority table overflow", which);
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+ }
+
+DEBUG(D_tls)
+ {
+ int *ptr = plist;
+ debug_printf("adjusted %s priorities:", which);
+ while (*ptr != 0) debug_printf(" %d", *ptr++);
+ debug_printf("\n");
+ }
+
+return TRUE;
+}
+
+
+
+
/*************************************************
* Initialize a single GNUTLS session *
*************************************************/
Arguments:
side one of GNUTLS_SERVER, GNUTLS_CLIENT
- expciphers expanded ciphers list
+ expciphers expanded ciphers list or NULL
+ expmac expanded MAC list or NULL
+ expkx expanded key-exchange list or NULL
+ expproto expanded protocol list or NULL
Returns: a gnutls_session, or NULL if there is a problem
*/
static gnutls_session
-tls_session_init(int side, uschar *expciphers)
+tls_session_init(int side, uschar *expciphers, uschar *expmac, uschar *expkx,
+ uschar *expproto)
{
gnutls_session session;
gnutls_init(&session, side);
-/* Handle the list of permitted ciphers */
+/* Initialize the lists of permitted protocols, key-exchange methods, ciphers,
+and MACs. */
memcpy(cipher_priority, default_cipher_priority, sizeof(cipher_priority));
+memcpy(mac_priority, default_mac_priority, sizeof(mac_priority));
+memcpy(kx_priority, default_kx_priority, sizeof(kx_priority));
+memcpy(proto_priority, default_proto_priority, sizeof(proto_priority));
+
+/* The names OpenSSL uses in tls_require_ciphers are of the form DES-CBC3-SHA,
+using hyphen separators. GnuTLS uses underscore separators. So that I can use
+either form for tls_require_ciphers in my tests, and also for general
+convenience, we turn hyphens into underscores before scanning the list. */
if (expciphers != NULL)
{
- int sep = 0;
- BOOL first = TRUE;
- uschar *cipher;
-
- /* The names OpenSSL uses are of the form DES-CBC3-SHA, using hyphen
- separators. GnuTLS uses underscore separators. So that I can use either form
- in my tests, and also for general convenience, we turn hyphens into
- underscores before scanning the list. */
-
uschar *s = expciphers;
while (*s != 0) { if (*s == '-') *s = '_'; s++; }
+ }
- while ((cipher = string_nextinlist(&expciphers, &sep, big_buffer,
- big_buffer_size)) != NULL)
- {
- int i;
- BOOL exclude = cipher[0] == '!';
- if (first && !exclude) cipher_priority[0] = 0;
- first = FALSE;
-
- for (i = 0; i < sizeof(cipher_index)/sizeof(pri_item); i++)
- {
- uschar *ss = strstric(cipher, cipher_index[i].name, FALSE);
- if (ss != NULL)
- {
- uschar *endss = ss + Ustrlen(cipher_index[i].name);
- if ((ss == cipher || !isalnum(ss[-1])) && !isalnum(*endss))
- {
- if (exclude)
- remove_ciphers(cipher_priority, cipher_index[i].values);
- else
- {
- if (!add_ciphers(cipher_priority,
- sizeof(cipher_priority)/sizeof(pri_item),
- cipher_index[i].values))
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "GnuTLS init failed: cipher "
- "priority table overflow");
- gnutls_deinit(session);
- return NULL;
- }
- }
- }
- }
- }
- }
-
- DEBUG(D_tls)
- {
- int *ptr = cipher_priority;
- debug_printf("adjusted cipher priorities:");
- while (*ptr != 0) debug_printf(" %d", *ptr++);
- debug_printf("\n");
- }
+if ((expciphers != NULL &&
+ !set_priority(cipher_priority, sizeof(cipher_priority)/sizeof(int),
+ expciphers, cipher_index, sizeof(cipher_index)/sizeof(pri_item),
+ US"cipher")) ||
+ (expmac != NULL &&
+ !set_priority(mac_priority, sizeof(mac_priority)/sizeof(int),
+ expmac, mac_index, sizeof(mac_index)/sizeof(pri_item),
+ US"MAC")) ||
+ (expkx != NULL &&
+ !set_priority(kx_priority, sizeof(kx_priority)/sizeof(int),
+ expkx, kx_index, sizeof(kx_index)/sizeof(pri_item),
+ US"key-exchange")) ||
+ (expproto != NULL &&
+ !set_priority(proto_priority, sizeof(proto_priority)/sizeof(int),
+ expproto, proto_index, sizeof(proto_index)/sizeof(pri_item),
+ US"protocol")))
+ {
+ gnutls_deinit(session);
+ return NULL;
}
/* Define the various priorities */
gnutls_cipher_set_priority(session, cipher_priority);
gnutls_compression_set_priority(session, comp_priority);
gnutls_kx_set_priority(session, kx_priority);
-gnutls_protocol_set_priority(session, protocol_priority);
+gnutls_protocol_set_priority(session, proto_priority);
gnutls_mac_set_priority(session, mac_priority);
gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
gnutls_db_set_cache_expiration(session, ssl_session_timeout);
+/* Reduce security in favour of increased compatibility, if the admin
+decides to make that trade-off. */
+if (gnutls_compat_mode)
+ {
+#if LIBGNUTLS_VERSION_NUMBER >= 0x020104
+ DEBUG(D_tls) debug_printf("lowering GnuTLS security, compatibility mode\n");
+ gnutls_session_enable_compatibility_mode(session);
+#else
+ DEBUG(D_tls) debug_printf("Unable to set gnutls_compat_mode - GnuTLS version too old\n");
+#endif
+ }
+
DEBUG(D_tls) debug_printf("initialized GnuTLS session\n");
return session;
}
a TLS session.
Arguments:
- require_ciphers list of allowed ciphers
+ require_ciphers list of allowed ciphers or NULL
+ require_mac list of allowed MACs or NULL
+ require_kx list of allowed key_exchange methods or NULL
+ require_proto list of allowed protocols or NULL
Returns: OK on success
DEFER for errors before the start of the negotiation
*/
int
-tls_server_start(uschar *require_ciphers)
+tls_server_start(uschar *require_ciphers, uschar *require_mac,
+ uschar *require_kx, uschar *require_proto)
{
int rc;
-uschar *error;
+const char *error;
uschar *expciphers = NULL;
+uschar *expmac = NULL;
+uschar *expkx = NULL;
+uschar *expproto = NULL;
/* Check for previous activation */
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_crl);
if (rc != OK) return rc;
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers) ||
+ !expand_check(require_mac, US"gnutls_require_mac", &expmac) ||
+ !expand_check(require_kx, US"gnutls_require_kx", &expkx) ||
+ !expand_check(require_proto, US"gnutls_require_proto", &expproto))
return FAIL;
/* If this is a host for which certificate verification is mandatory or
/* Prepare for new connection */
-tls_session = tls_session_init(GNUTLS_SERVER, expciphers);
+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
/* Now negotiate the TLS session. We put our own timer on it, since it seems
that the GnuTLS library doesn't. */
-gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)fileno(smtp_out));
+gnutls_transport_set_ptr2(tls_session, (gnutls_transport_ptr)fileno(smtp_in),
+ (gnutls_transport_ptr)fileno(smtp_out));
sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
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 (!sigalrm_seen)
{
- fclose(smtp_out);
- fclose(smtp_in);
+ (void)fclose(smtp_out);
+ (void)fclose(smtp_in);
}
return FAIL;
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;
}
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
receive_ferror = tls_ferror;
+receive_smtp_buffered = tls_smtp_buffered;
tls_active = fileno(smtp_out);
Arguments:
fd the fd of the connection
host connected host (for messages)
- addr
+ addr the first address (not used)
dhparam DH parameter file
certificate certificate file
privatekey private key file
verify_certs file for certificate verify
verify_crl CRL for verify
- require_ciphers list of allowed ciphers
+ require_ciphers list of allowed ciphers or NULL
+ require_mac list of allowed MACs or NULL
+ require_kx list of allowed key_exchange methods or NULL
+ require_proto list of allowed protocols or NULL
timeout startup timeout
Returns: OK/DEFER/FAIL (because using common functions),
int
tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
uschar *certificate, uschar *privatekey, uschar *verify_certs,
- uschar *verify_crl, uschar *require_ciphers, int timeout)
+ uschar *verify_crl, uschar *require_ciphers, uschar *require_mac,
+ uschar *require_kx, uschar *require_proto, int timeout)
{
const gnutls_datum *server_certs;
uschar *expciphers = NULL;
-uschar *error;
+uschar *expmac = NULL;
+uschar *expkx = NULL;
+uschar *expproto = NULL;
+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;
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers) ||
+ !expand_check(require_mac, US"gnutls_require_mac", &expmac) ||
+ !expand_check(require_kx, US"gnutls_require_kx", &expkx) ||
+ !expand_check(require_proto, US"gnutls_require_proto", &expproto))
return FAIL;
-tls_session = tls_session_init(GNUTLS_CLIENT, expciphers);
+tls_session = tls_session_init(GNUTLS_CLIENT, expciphers, expmac, expkx,
+ 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);
}
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
+ receive_smtp_buffered = smtp_buffered;
gnutls_deinit(tls_session);
tls_session = NULL;
ssl_xfer_error = 1;
return EOF;
}
-
+#ifndef DISABLE_DKIM
+ dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+#endif
ssl_xfer_buffer_hwm = inbytes;
ssl_xfer_buffer_lwm = 0;
}
tls_active = -1;
}
+
+
+
+/*************************************************
+* Report the library versions. *
+*************************************************/
+
+/* See a description in tls-openssl.c for an explanation of why this exists.
+
+Arguments: a FILE* to print the results to
+Returns: nothing
+*/
+
+void
+tls_version_report(FILE *f)
+{
+fprintf(f, "GnuTLS compile-time version: %s\n", LIBGNUTLS_VERSION);
+fprintf(f, "GnuTLS runtime version: %s\n", gnutls_check_version(NULL));
+}
+
/* End of tls-gnu.c */