Add dbmjz lookup type
[users/jgh/exim.git] / src / src / tls-openssl.c
index 80ac0ffe78d07d78cc2ea1b63242272a33670096..2104711bb96230013a2c27948abd6ded697c6533 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/tls-openssl.c,v 1.20 2009/10/19 11:25:31 nm4 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module provides the TLS (aka SSL) support for Exim using the OpenSSL
@@ -73,13 +71,13 @@ tls_error(uschar *prefix, host_item *host, uschar *msg)
 if (msg == NULL)
   {
   ERR_error_string(ERR_get_error(), ssl_errstring);
-  msg = ssl_errstring;
+  msg = (uschar *)ssl_errstring;
   }
 
 if (host == NULL)
   {
   uschar *conn_info = smtp_get_connection_info();
-  if (strncmp(conn_info, "SMTP ", 5) == 0)
+  if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
     conn_info += 5;
   log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
     conn_info, prefix, msg);
@@ -253,7 +251,7 @@ if (dhexpanded == NULL) return TRUE;
 if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL)
   {
   tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
-    host, strerror(errno));
+    host, (uschar *)strerror(errno));
   yield = FALSE;
   }
 else
@@ -302,11 +300,14 @@ static int
 tls_init(host_item *host, uschar *dhparam, uschar *certificate,
   uschar *privatekey, address_item *addr)
 {
+long init_options;
+BOOL okay;
+
 SSL_load_error_strings();          /* basic set up */
 OpenSSL_add_ssl_algorithms();
 
 #if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
-/* SHA256 is becoming ever moar popular. This makes sure it gets added to the
+/* SHA256 is becoming ever more popular. This makes sure it gets added to the
 list of available digests. */
 EVP_add_digest(EVP_sha256());
 #endif
@@ -338,7 +339,7 @@ if (!RAND_status())
 
   if (!RAND_status())
     return tls_error(US"RAND_status", host,
-      "unable to seed random number generator");
+      US"unable to seed random number generator");
   }
 
 /* Set up the information callback, which outputs if debugging is at a suitable
@@ -346,21 +347,28 @@ level. */
 
 SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
 
-/* The following patch was supplied by Robert Roselius */
+/* Apply administrator-supplied work-arounds.
+Historically we applied just one requested option,
+SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, but when bug 994 requested a second, we
+moved to an administrator-controlled list of options to specify and
+grandfathered in the first one as the default value for "openssl_options".
 
-#if OPENSSL_VERSION_NUMBER > 0x00906040L
-/* Enable client-bug workaround.
-   Versions of OpenSSL as of 0.9.6d include a "CBC countermeasure" feature,
-   which causes problems with some clients (such as the Certicom SSL Plus
-   library used by Eudora).  This option, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS,
-   disables the coutermeasure allowing Eudora to connect.
-   Some poppers and MTAs use SSL_OP_ALL, which enables all such bug
-   workarounds. */
-/* XXX (Silently?) ignore failure here? XXX*/
+No OpenSSL version number checks: the options we accept depend upon the
+availability of the option value macros from OpenSSL.  */
 
-if (!(SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)))
-  return tls_error(US"SSL_CTX_set_option", host, NULL);
-#endif
+okay = tls_openssl_options_parse(openssl_options, &init_options);
+if (!okay)
+  return tls_error(US"openssl_options parsing failed", host, NULL);
+
+if (init_options)
+  {
+  DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
+  if (!(SSL_CTX_set_options(ctx, init_options)))
+    return tls_error(string_sprintf(
+          "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
+  }
+else
+  DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
 
 /* Initialize with DH parameters if supplied */
 
@@ -428,9 +436,11 @@ static void
 construct_cipher_name(SSL *ssl)
 {
 static uschar cipherbuf[256];
-SSL_CIPHER *c;
+/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
+yet reflect that.  It should be a safe change anyway, even 0.9.8 versions have
+the accessor functions use const in the prototype. */
+const SSL_CIPHER *c;
 uschar *ver;
-int bits;
 
 switch (ssl->session->ssl_version)
   {
@@ -450,11 +460,11 @@ switch (ssl->session->ssl_version)
   ver = US"UNKNOWN";
   }
 
-c = SSL_get_current_cipher(ssl);
-SSL_CIPHER_get_bits(c, &bits);
+c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
+SSL_CIPHER_get_bits(c, &tls_bits);
 
 string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
-  SSL_CIPHER_get_name(c), bits);
+  SSL_CIPHER_get_name(c), tls_bits);
 tls_cipher = cipherbuf;
 
 DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
@@ -621,7 +631,7 @@ uschar *expciphers;
 
 if (tls_active >= 0)
   {
-  tls_error("STARTTLS received after TLS started", NULL, "");
+  tls_error(US"STARTTLS received after TLS started", NULL, US"");
   smtp_printf("554 Already in TLS\r\n");
   return FAIL;
   }
@@ -702,6 +712,9 @@ alarm(0);
 if (rc <= 0)
   {
   tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL);
+  if (ERR_get_error() == 0)
+    log_write(0, LOG_MAIN,
+        "TLS client disconnected cleanly (rejected our certificate?)");
   return FAIL;
   }
 
@@ -820,10 +833,16 @@ if (rc <= 0)
 
 DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
 
+/* Beware anonymous ciphers which lead to server_cert being NULL */
 server_cert = SSL_get_peer_certificate (ssl);
-tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
-  CS txt, sizeof(txt));
-tls_peerdn = txt;
+if (server_cert)
+  {
+  tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
+    CS txt, sizeof(txt));
+  tls_peerdn = txt;
+  }
+else
+  tls_peerdn = NULL;
 
 construct_cipher_name(ssl);   /* Sets tls_cipher */
 
@@ -887,6 +906,14 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
 
   /* Handle genuine errors */
 
+  else if (error == SSL_ERROR_SSL)
+    {
+    ERR_error_string(ERR_get_error(), ssl_errstring);
+    log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
+    ssl_xfer_error = 1;
+    return EOF;
+    }
+
   else if (error != SSL_ERROR_NONE)
     {
     DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
@@ -1051,8 +1078,10 @@ Returns:     nothing
 void
 tls_version_report(FILE *f)
 {
-fprintf(f, "OpenSSL compile-time version: %s\n", OPENSSL_VERSION_TEXT);
-fprintf(f, "OpenSSL runtime version: %s\n", SSLeay_version(SSLEAY_VERSION));
+fprintf(f, "Library version: OpenSSL: Compile: %s\n"
+           "                          Runtime: %s\n",
+           OPENSSL_VERSION_TEXT,
+           SSLeay_version(SSLEAY_VERSION));
 }
 
 
@@ -1123,4 +1152,199 @@ smooth distribution and cares enough then they should submit a patch then. */
 return r % max;
 }
 
+
+
+
+/*************************************************
+*        OpenSSL option parse                    *
+*************************************************/
+
+/* Parse one option for tls_openssl_options_parse below
+
+Arguments:
+  name    one option name
+  value   place to store a value for it
+Returns   success or failure in parsing
+*/
+
+struct exim_openssl_option {
+  uschar *name;
+  long    value;
+};
+/* We could use a macro to expand, but we need the ifdef and not all the
+options document which version they were introduced in.  Policylet: include
+all options unless explicitly for DTLS, let the administrator choose which
+to apply.
+
+This list is current as of:
+  ==>  1.0.0c  <==  */
+static struct exim_openssl_option exim_openssl_options[] = {
+/* KEEP SORTED ALPHABETICALLY! */
+#ifdef SSL_OP_ALL
+  { US"all", SSL_OP_ALL },
+#endif
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+  { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+  { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
+#endif
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+  { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
+#endif
+#ifdef SSL_OP_EPHEMERAL_RSA
+  { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
+#endif
+#ifdef SSL_OP_LEGACY_SERVER_CONNECT
+  { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT },
+#endif
+#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+  { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER },
+#endif
+#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
+  { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG },
+#endif
+#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
+  { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING },
+#endif
+#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
+  { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG },
+#endif
+#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+  { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
+#endif
+#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+  { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_NO_SSLv2
+  { US"no_sslv2", SSL_OP_NO_SSLv2 },
+#endif
+#ifdef SSL_OP_NO_SSLv3
+  { US"no_sslv3", SSL_OP_NO_SSLv3 },
+#endif
+#ifdef SSL_OP_NO_TICKET
+  { US"no_ticket", SSL_OP_NO_TICKET },
+#endif
+#ifdef SSL_OP_NO_TLSv1
+  { US"no_tlsv1", SSL_OP_NO_TLSv1 },
+#endif
+#ifdef SSL_OP_SINGLE_DH_USE
+  { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
+#endif
+#ifdef SSL_OP_SINGLE_ECDH_USE
+  { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE },
+#endif
+#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+  { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG },
+#endif
+#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+  { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG },
+#endif
+#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
+  { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG },
+#endif
+#ifdef SSL_OP_TLS_D5_BUG
+  { US"tls_d5_bug", SSL_OP_TLS_D5_BUG },
+#endif
+#ifdef SSL_OP_TLS_ROLLBACK_BUG
+  { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
+#endif
+};
+static int exim_openssl_options_size =
+  sizeof(exim_openssl_options)/sizeof(struct exim_openssl_option);
+
+static BOOL
+tls_openssl_one_option_parse(uschar *name, long *value)
+{
+int first = 0;
+int last = exim_openssl_options_size;
+while (last > first)
+  {
+  int middle = (first + last)/2;
+  int c = Ustrcmp(name, exim_openssl_options[middle].name);
+  if (c == 0)
+    {
+    *value = exim_openssl_options[middle].value;
+    return TRUE;
+    }
+  else if (c > 0)
+    first = middle + 1;
+  else
+    last = middle;
+  }
+return FALSE;
+}
+
+
+
+
+/*************************************************
+*        OpenSSL option parsing logic            *
+*************************************************/
+
+/* OpenSSL has a number of compatibility options which an administrator might
+reasonably wish to set.  Interpret a list similarly to decode_bits(), so that
+we look like log_selector.
+
+Arguments:
+  option_spec  the administrator-supplied string of options
+  results      ptr to long storage for the options bitmap
+Returns        success or failure
+*/
+
+BOOL
+tls_openssl_options_parse(uschar *option_spec, long *results)
+{
+long result, item;
+uschar *s, *end;
+uschar keep_c;
+BOOL adding, item_parsed;
+
+result = 0L;
+/* We grandfather in as default the one option which we used to set always. */
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+result |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+#endif
+
+if (option_spec == NULL)
+  {
+  *results = result;
+  return TRUE;
+  }
+
+for (s=option_spec; *s != '\0'; /**/)
+  {
+  while (isspace(*s)) ++s;
+  if (*s == '\0')
+    break;
+  if (*s != '+' && *s != '-')
+    {
+    DEBUG(D_tls) debug_printf("malformed openssl option setting: "
+        "+ or - expected but found \"%s\"\n", s);
+    return FALSE;
+    }
+  adding = *s++ == '+';
+  for (end = s; (*end != '\0') && !isspace(*end); ++end) /**/ ;
+  keep_c = *end;
+  *end = '\0';
+  item_parsed = tls_openssl_one_option_parse(s, &item);
+  if (!item_parsed)
+    {
+    DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);
+    return FALSE;
+    }
+  DEBUG(D_tls) debug_printf("openssl option, %s from %lx: %lx (%s)\n",
+      adding ? "adding" : "removing", result, item, s);
+  if (adding)
+    result |= item;
+  else
+    result &= ~item;
+  *end = keep_c;
+  s = end;
+  }
+
+*results = result;
+return TRUE;
+}
+
 /* End of tls-openssl.c */