Add gnutls_require_{kx,mac,protocols}.
[exim.git] / src / src / tls-gnu.c
index 944fb076296d76853b7d4645e91e9827b3acc870..ee57659fa7bf174f86d1c0e0c35b21159cc51ac8 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/tls-gnu.c,v 1.6 2005/03/08 11:38:21 ph10 Exp $ */
+/* $Cambridge: exim/src/src/tls-gnu.c,v 1.18 2007/01/18 15:35:42 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module provides TLS (aka SSL) support for Exim using the GnuTLS
@@ -22,8 +22,7 @@ functions from the GnuTLS library. */
 
 
 #define UNKNOWN_NAME "unknown"
-#define DH_BITS      768
-#define RSA_BITS     512
+#define DH_BITS      1024
 #define PARAM_SIZE 2*1024
 
 
@@ -37,7 +36,6 @@ enum { INITIALIZED_NOT, INITIALIZED_SERVER, INITIALIZED_CLIENT };
 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;
@@ -48,18 +46,24 @@ static char ssl_errstring[256];
 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 protocol_priority[16] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
+static const int default_proto_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,
@@ -69,21 +73,50 @@ static int default_cipher_priority[16] = {
 
 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,
@@ -105,6 +138,16 @@ static pri_item cipher_index[] = {
 };
 
 
+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                 *
@@ -233,10 +276,10 @@ return TRUE;                            /* accept */
 
 
 /*************************************************
-*          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.
@@ -253,18 +296,15 @@ Returns:     OK/DEFER/FAIL
 */
 
 static int
-init_rsa_dh(host_item *host)
+init_dh(host_item *host)
 {
 int fd;
-int ret = -1;
+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);
 
@@ -275,10 +315,7 @@ if (!string_format(filename, sizeof(filename), "%s/gnutls-params",
   return tls_error(US"overlong filename", host, 0);
 
 /* Open the cache file for reading and if successful, read it and set up the
-parameters. If we can't set up the RSA parameters, assume that we are dealing
-with an old-style cache file that is in another format, and fall through to
-compute new values. However, if we correctly get RSA parameters, a failure to
-set up D-H parameters is treated as an error. */
+parameters. */
 
 fd = Uopen(filename, O_RDONLY, 0);
 if (fd >= 0)
@@ -298,19 +335,9 @@ if (fd >= 0)
     return tls_error(US"TLS cache read failed", host, 0);
   (void)close(fd);
 
-  ret = gnutls_rsa_params_import_pkcs1(rsa_params, &m, GNUTLS_X509_FMT_PEM);
-  if (ret < 0)
-    {
-    DEBUG(D_tls)
-      debug_printf("RSA params import failed: assume old-style cache file\n");
-    }
-  else
-    {
-    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);
-    DEBUG(D_tls) debug_printf("read RSA and D-H parameters from file\n");
-    }
+  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);
+  DEBUG(D_tls) debug_printf("read D-H parameters from file\n");
 
   free(m.data);
   }
@@ -318,7 +345,13 @@ if (fd >= 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. */
 
-else if (errno != ENOENT)
+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, 0);
 
@@ -332,10 +365,6 @@ if (ret < 0)
   {
   uschar tempfilename[sizeof(filename) + 10];
 
-  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);
-
   DEBUG(D_tls) debug_printf("generating %d bit Diffie-Hellman key...\n",
     DH_BITS);
   ret = gnutls_dh_params_generate2(dh_params, DH_BITS);
@@ -355,9 +384,7 @@ if (ret < 0)
    * certtool or other programs.
    *
    * The commands for certtool are:
-   * $ certtool --generate-privkey --bits 512 >params
-   * $ echo "" >>params
-   * $ certtool --generate-dh-params --bits 1024 >> params
+   * $ certtool --generate-dh-params --bits 1024 > params
    */
 
   m.size = PARAM_SIZE;
@@ -365,16 +392,6 @@ if (ret < 0)
   if (m.data == NULL)
     return tls_error(US"memory allocation failed", host, 0);
 
-  ret = gnutls_rsa_params_export_pkcs1(rsa_params, GNUTLS_X509_FMT_PEM,
-    m.data, &m.size);
-  if (ret < 0) return tls_error(US"RSA params export", host, ret);
-
-  /* Do not write the null termination byte. */
-
-  m.size = Ustrlen(m.data);
-  if (write(fd, m.data, m.size) != m.size || write(fd, "\n", 1) != 1)
-    return tls_error(US"TLS cache write failed", host, 0);
-
   m.size = PARAM_SIZE;
   ret = gnutls_dh_params_export_pkcs3(dh_params, GNUTLS_X509_FMT_PEM, m.data,
     &m.size);
@@ -391,10 +408,10 @@ if (ret < 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");
+  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;
 }
 
@@ -430,10 +447,10 @@ initialized = (host == NULL)? INITIALIZED_SERVER : INITIALIZED_CLIENT;
 rc = gnutls_global_init();
 if (rc < 0) return tls_error(US"tls-init", host, 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 */
@@ -447,12 +464,19 @@ 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 */
 
@@ -499,8 +523,8 @@ if (cas != NULL)
     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. */
 
@@ -525,7 +549,6 @@ if (cas != NULL)
 /* Associate the parameters with the x509 credentials structure. */
 
 gnutls_certificate_set_dh_params(x509_cred, dh_params);
-gnutls_certificate_set_rsa_export_params(x509_cred, rsa_params);
 
 DEBUG(D_tls) debug_printf("initialized certificate stuff\n");
 return OK;
@@ -535,7 +558,7 @@ return OK;
 
 
 /*************************************************
-*        Remove ciphers from priority list       *
+*           Remove from a priority list          *
 *************************************************/
 
 /* Cautiously written so that it will remove duplicates if present.
@@ -548,7 +571,7 @@ Returns:       nothing
 */
 
 static void
-remove_ciphers(int *list, int *remove_list)
+remove_priority(int *list, int *remove_list)
 {
 for (; *remove_list != 0; remove_list++)
   {
@@ -568,7 +591,7 @@ for (; *remove_list != 0; remove_list++)
 
 
 /*************************************************
-*        Add ciphers to priority list            *
+*            Add to a priority list              *
 *************************************************/
 
 /* Cautiously written to check the list size
@@ -582,7 +605,7 @@ Returns:       TRUE if OK; FALSE if list overflows
 */
 
 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++;
@@ -597,6 +620,78 @@ return TRUE;
 
 
 
+/*************************************************
+*          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      *
 *************************************************/
@@ -614,78 +709,60 @@ cipher.
 
 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 */
@@ -693,7 +770,7 @@ if (expciphers != NULL)
 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);
@@ -762,7 +839,10 @@ the STARTTLS command. It must respond to that command, and then negotiate
 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
@@ -771,11 +851,15 @@ Returns:           OK on success
 */
 
 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;
 uschar *expciphers = NULL;
+uschar *expmac = NULL;
+uschar *expkx = NULL;
+uschar *expproto = NULL;
 
 /* Check for previous activation */
 
@@ -797,7 +881,10 @@ rc = tls_init(NULL, tls_certificate, tls_privatekey, tls_verify_certificates,
   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
@@ -813,7 +900,8 @@ else if (verify_check_host(&tls_try_verify_hosts) == OK)
 
 /* 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);
 
@@ -832,7 +920,8 @@ if (!tls_on_connect)
 /* 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);
@@ -856,8 +945,8 @@ if (rc < 0)
 
   if (!sigalrm_seen)
     {
-    fclose(smtp_out);
-    fclose(smtp_in);
+    (void)fclose(smtp_out);
+    (void)fclose(smtp_in);
     }
 
   return FAIL;
@@ -905,13 +994,16 @@ return OK;
 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),
@@ -921,10 +1013,14 @@ 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 *expmac = NULL;
+uschar *expkx = NULL;
+uschar *expproto = NULL;
 uschar *error;
 unsigned int server_certs_size;
 int rc;
@@ -936,10 +1032,15 @@ 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);