Check for lost connection or unexpected input at end of message.
[exim.git] / src / src / tls-gnu.c
index 98aea4451ffe7aa1f04eb1e3c09173bbda93614d..e66c2e448b767aa35332553616bfd2152c4c51ef 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/tls-gnu.c,v 1.13 2006/10/16 10:58:40 ph10 Exp $ */
+/* $Cambridge: exim/src/src/tls-gnu.c,v 1.19 2007/04/13 15:13:47 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* 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,7 +22,7 @@ functions from the GnuTLS library. */
 
 
 #define UNKNOWN_NAME "unknown"
-#define DH_BITS      768
+#define DH_BITS      1024
 #define PARAM_SIZE 2*1024
 
 
@@ -46,17 +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,
   0 };
 
+static int kx_priority[16];
+
 static int default_cipher_priority[16] = {
   GNUTLS_CIPHER_AES_256_CBC,
   GNUTLS_CIPHER_AES_128_CBC,
@@ -66,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,
@@ -102,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                 *
@@ -230,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.
@@ -250,7 +296,7 @@ Returns:     OK/DEFER/FAIL
 */
 
 static int
-init_rsa_dh(host_item *host)
+init_dh(host_item *host)
 {
 int fd;
 int ret;
@@ -269,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)
@@ -294,7 +337,7 @@ if (fd >= 0)
 
   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");
+  DEBUG(D_tls) debug_printf("read D-H parameters from file\n");
 
   free(m.data);
   }
@@ -404,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 */
@@ -515,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.
@@ -528,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++)
   {
@@ -548,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
@@ -562,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++;
@@ -577,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      *
 *************************************************/
@@ -594,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 */
@@ -673,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);
@@ -742,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
@@ -751,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 */
 
@@ -777,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
@@ -793,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);
 
@@ -812,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);
@@ -867,6 +976,7 @@ receive_getc = tls_getc;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
+receive_smtp_buffered = tls_smtp_buffered;
 
 tls_active = fileno(smtp_out);
 
@@ -885,13 +995,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),
@@ -901,10 +1014,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;
@@ -916,10 +1033,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);
 
@@ -1053,6 +1175,7 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     receive_ungetc = smtp_ungetc;
     receive_feof = smtp_feof;
     receive_ferror = smtp_ferror;
+    receive_smtp_buffered = smtp_buffered;
 
     gnutls_deinit(tls_session);
     tls_session = NULL;