[Exim] Bigger, better TLS patch for 4.10: session caching

Página Inicial
Delete this message
Reply to this message
Autor: Steve Haslam
Data:  
Para: exim-users
Assunto: [Exim] Bigger, better TLS patch for 4.10: session caching
--
Hi,

OK, I'm going to be distracted from Exim TLS hacking for a bit, so here's
the current state of the changes I've made.

I've got TLS session caching basically working, although it currently has a
nasty OpenSSL bug workaround. Also, the session cache is keyed entirely on
IP address when acting as a client, and it may be desirable to extend this
to include other things like the transport name, or just to allow the
session cache key to be specified on the transport (for expansion). The
sessions are kept in a new DB file called tls_sessions, and I've updated
exim_dumpdb and tidydb to work with the new record types.

I've also been steadily moving things out of verify_callback() (this is a
requirement for session caching, actually), which makes the flow a bit
clearer (I think).

And this has the tls_certificate_verified patch already folded in.

Regarding using GNU TLS, I've started writing things for it. I've come
across a problem with getting client certificate requests working, but apart
from that, it seems OK, but nothing to go into Exim yet.

SRH
--
Steve Haslam      Reading, UK                           araqnid@???
Debian GNU/Linux Maintainer                               araqnid@???
but I won't admit to needing you
I'll never say that's true, not to you                  [sister machine gun]
--
--- src/acl.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/acl.c    18 Sep 2002 00:14:32 -0000    1.1.2.1
@@ -436,8 +436,8 @@
 mandatory verification, the connection doesn't last this long.) */


if (strcmpic(ss, US"certificate") == 0)
- return tls_certificate_verified? OK : FAIL;
+ return (tls_certificate_verified == 1)? OK : FAIL;

/* We can test the result of optional HELO verification */

--- src/daemon.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/daemon.c    18 Sep 2002 03:27:39 -0000    1.1.2.2
@@ -42,6 +42,17 @@
 static smtp_slot *smtp_slots;



+/*************************************************
+*             SIGALRM Handler                    *
+*************************************************/
+
+static void close_fds(void)
+{
+  int fd;
+  for (fd = 0; fd < 255; fd++) {
+    close(fd);
+  }
+}


 /*************************************************
 *             SIGALRM Handler                    *
@@ -129,7 +140,7 @@
   string_sprintf(": %s", strerror(was_errno));
 log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
 if (smtp_out != NULL)
-  smtp_printf("421 %s: %s\r\n", primary_hostname, smtp_msg, emsg);
+  smtp_printf("421 %s: %s\r\n", primary_hostname, smtp_msg);
 }



@@ -655,10 +666,8 @@
   {
   log_close_all();  /* Just in case anything was logged earlier */
   search_tidyup();  /* Just in case any were used in reading the config. */
-  close(0);         /* Get rid of stdin/stdout/stderr */
-  close(1);
-  close(2);
+  close_fds();
   log_stderr = NULL;  /* So no attempt to copy paniclog output */


   /* If the parent process of this one has pid == 1, we are re-initializing the
--- src/dbstuff.h    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/dbstuff.h    18 Sep 2002 03:27:54 -0000    1.1.2.2
@@ -569,6 +569,36 @@
   int    count;           /* Reserved for possible connection count */
 } dbdata_serialize;


+/* TLS session database records */
+
+/* There are two types of record stored in the tls_sessions database.
+
+ The first type is a serialised SSL_SESSION object, which has a key
+ of "=" followed by the hex form of the session ID. (See
+ format_session_key() in tls.c)
+
+ The second is a pointer to a session ID from a given hostname,
+ which has a key of "@" followed by the hostname. This is used when
+ the smtp transport is starting up TLS, to get a session ID to
+ reuse, and to deserialise the session object.
+
+ For session records, the session data length must be determined
+ using the record length from the DBM file, whereas for pointer
+ records, the session id length is stored. Inconsistent, sorry.
+*/
+
+typedef struct dbdata_tlssession {
+ time_t time_stamp;
+ time_t expire;
+ uschar session[4096]; /* Serialised session data */
+} dbdata_tlssession;
+
+typedef struct dbdata_tlssessionptr {
+ time_t time_stamp;
+ time_t expire;
+ int session_id_length;
+ uschar session_id[0]; /* Session ID (packed) */
+} dbdata_tlssessionptr;

 /* End of dbstuff.h */
--- src/exim.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/exim.c    18 Sep 2002 00:14:33 -0000    1.1.2.1
@@ -12,7 +12,9 @@


#include "exim.h"

-
+#ifdef SUPPORT_TLS
+#include <openssl/opensslv.h>
+#endif

 /*************************************************
 *      Function interface to store functions     *
@@ -1153,7 +1155,10 @@
         version_cnumber, version_date);
       printf("%s\n", version_copyright);
       version_printed = TRUE;
+#ifdef SUPPORT_TLS
+      DEBUG(D_tls) printf("TLS support: %s\n", OPENSSL_VERSION_TEXT);
+#endif
       }


     else badarg = TRUE;
--- src/exim.h    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/exim.h    18 Sep 2002 00:14:33 -0000    1.1.2.1
@@ -310,7 +310,18 @@
 extern int ferror(FILE *);
 #endif


+/* If we are using gcc, we can use __attribute__ to hint attributes
+ about variables/functions to the compiler, so gcc can do things
+ like check printf formats in debug_printf() et al. If we are not
+ using gcc, then silently remove them. */
+
+#ifdef __GNUC__
+#define __ATTR(x) __attribute__(x)
+#else
+#define __ATTR(x)
+#endif
+
/* The header from the PCRE regex package */

 #include "pcre/pcre.h"
--- src/exim_dbutil.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/exim_dbutil.c    18 Sep 2002 03:42:15 -0000    1.1.2.3
@@ -16,9 +16,10 @@
 In all cases, the first argument is the name of the spool directory. The second
 argument is the name of the database file. The available names are:


-  retry:      retry delivery information
-  misc:       miscellaneous hints data
-  wait-<t>:   message waiting information; <t> is a transport name
+  retry:          retry delivery information
+  misc:           miscellaneous hints data
+  wait-<t>:       message waiting information; <t> is a transport name
+  tls_sessions:   TLS session cache


There are a number of common subroutines, followed by three main programs,
whose inclusion is controlled by -D on the compilation command. */
@@ -62,7 +63,7 @@
#define type_retry 1
#define type_wait 2
#define type_misc 3
-
+#define type_tls 4



@@ -89,7 +90,7 @@
 usage(uschar *name, uschar *options)
 {
 printf("Usage: exim_%s%s  <spool-directory> <database-name>\n", name, options);
-printf("       <database-name> = retry | misc | wait-<transport-name>\n");
+printf("       <database-name> = retry | misc | tls_sessions | wait-<transport-name>\n");
 exit(1);
 }


@@ -110,6 +111,7 @@
   if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
   if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
   if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
+  if (Ustrcmp(argv[2], "tls_sessions") == 0) return type_tls;
   }
 usage(name, options);
 return -1;              /* Never obeyed */
@@ -164,6 +166,31 @@
 }



+#ifndef EXIM_FIXDB
+/*************************************************
+*        Format TLS session id into a DB key     *
+*************************************************/
+
+static uschar *format_session_key(void *id, int idlen)
+{
+  static const uschar hexdig[] = "0123456789abcdef";
+  const uschar *d = id;
+  uschar *s = (uschar*) malloc(idlen*2+2);
+  int i, j;
+
+  s[0] = '=';
+
+  for (i = 0, j = 1; i < idlen; i++) {
+    s[j++] = hexdig[(d[i] & 0xf0)>>4];
+    s[j++] = hexdig[d[i] & 0x0f];
+  }
+  s[j] = '\0';
+
+  return s;
+}
+#endif
+
+
 #ifdef EXIM_FIXDB
 /*************************************************
 *                Read time value                 *
@@ -573,6 +600,23 @@
       printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
         keybuffer);
       break;
+
+      case type_tls:
+    if (keybuffer[0] == '=') {
+      /* Session record */
+      printf("%s Session %s\n", print_time(((dbdata_tlssession *)value)->time_stamp), keybuffer+1);
+      printf(" Expires %s\n",  print_time(((dbdata_tlssession *)value)->expire));
+    }
+    else if (keybuffer[0] == '@') {
+      /* Pointer record */
+      uschar *key = format_session_key(((dbdata_tlssessionptr *)value)->session_id, ((dbdata_tlssessionptr *)value)->session_id_length);
+      printf("%s Pointer \"%s\" => %s\n", print_time(((dbdata_tlssessionptr *)value)->time_stamp), keybuffer+1, key+1);
+      printf(" Expires %s\n",  print_time(((dbdata_tlssessionptr *)value)->expire));
+      free(key);
+    }
+    else {
+      printf("%s JUNK %s\n", print_time(((dbdata_generic *)value)->time_stamp), keybuffer);
+    }
       }
     store_reset(value);
     }
@@ -759,6 +803,10 @@
             case type_misc:
             printf("Can't change contents of misc database record\n");
             break;
+
+            case type_tls:
+            printf("Can't change contents of TLS sessions cache record\n");
+            break;
             }


           dbfn_write(dbm, name, record, length);
@@ -841,8 +889,10 @@
       break;


       case type_misc:
+      case type_tls:
       printf("%s\n", print_time(((dbdata_generic *)record)->time_stamp));
       break;
+
       }
     }


@@ -1121,7 +1171,31 @@
       {
       dbfn_delete(dbm, key);
       printf("deleted %s (no message)\n", key);
+      }
+    }
+  else if (dbdata_type == type_tls)
+    {
+      /* if a pointer record, see if the session pointed to exists; if
+     not, delete the pointer.
+         for both pointers and sessions, delete expired records.
+     delete records that are neither pointers nor sessions.
+      */
+      if ((key[0] == '@' || key[0] == '=') && ((dbdata_tlssession *)value)->expire < time(NULL)) {
+    dbfn_delete(dbm, key);
+    printf("deleted %s (expired)\n", key);
+      }
+      else if (key[0] == '@') {
+    char *sesskey = format_session_key(((dbdata_tlssessionptr *)value)->session_id, ((dbdata_tlssessionptr *)value)->session_id_length);
+    void *sessvalue = dbfn_read(dbm, sesskey);
+    if (!sessvalue) {
+      dbfn_delete(dbm, sesskey);
+      printf("deleted %s (referenced session not found)\n", key);
+    }
+      }
+      else if (key[0] != '=') {
+    dbfn_delete(dbm, key);
+    printf("deleted %s (neither pointer nor session)\n", key);
       }
     }
   }
--- src/expand.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/expand.c    18 Sep 2002 00:14:32 -0000    1.1.2.1
@@ -246,8 +246,10 @@
   { "spool_directory",     vtype_stringptr,   &spool_directory },
   { "thisaddress",         vtype_stringptr,   &filter_thisaddress },
 #ifdef SUPPORT_TLS
+  { "tls_certificate_verified",vtype_int,     &tls_certificate_verified },
   { "tls_cipher",          vtype_stringptr,   &tls_cipher },
+  { "tls_peercn",          vtype_stringptr,   &tls_peercn },
   { "tls_peerdn",          vtype_stringptr,   &tls_peerdn },
 #endif
   { "tod_bsdinbox",        vtype_todbsdin,    NULL },
--- src/functions.h    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/functions.h    18 Sep 2002 00:14:32 -0000    1.1.2.1
@@ -22,7 +22,7 @@


 #ifdef SUPPORT_TLS
 extern BOOL    tls_client_start(int, host_item *, address_item *, uschar *,
-                 uschar *, uschar *, uschar *, uschar *, int);
+                 uschar *, uschar *, uschar *, uschar *, int, int);
 extern void    tls_close(BOOL);
 extern int     tls_feof(void);
 extern int     tls_ferror(void);
@@ -50,13 +50,12 @@
 extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
 extern pid_t   child_open(uschar **, uschar **, int, uid_t *, gid_t *, int *,
                  int *, uschar *, BOOL);
-
 extern void    daemon_go(void);
-extern void    debug_printf(char *, ...);
+extern void    debug_printf(char *, ...) __ATTR((format(printf,1,2)));
+extern void    debug_vprintf(char *, va_list) __ATTR((format(printf,1,0)));
 extern void    debug_print_argv(uschar **);
 extern void    debug_print_ids(uschar *);
 extern void    debug_print_string(uschar *);
-extern void    debug_vprintf(char *, va_list);
 extern address_item *deliver_make_addr(uschar *, BOOL);
 extern int     deliver_message(uschar *, BOOL, BOOL);
 extern void    deliver_msglog(const char *, ...);
@@ -208,8 +207,8 @@
 extern BOOL    smtp_get_port(uschar *, address_item *, int *, uschar *);
 extern int     smtp_getc(void);
 extern void    smtp_handle_acl_fail(int, int, uschar *, uschar *);
-extern void    smtp_printf(char *, ...);
+extern void    smtp_printf(char *, ...) __ATTR((format(printf,1,2)));
 extern BOOL    smtp_read_response(smtp_inblock *, uschar *, int, int, int);
 extern void    smtp_respond(int, BOOL, uschar *);
 extern void    smtp_send_prohibition_message(int, uschar *);
--- src/globals.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/globals.c    18 Sep 2002 00:14:33 -0000    1.1.2.1
@@ -83,7 +83,7 @@
 them. */


 BOOL    tls_active             = -1;
-BOOL    tls_certificate_verified = FALSE;
+int     tls_certificate_verified = 0;
 uschar *tls_cipher             = NULL;


 #ifdef SUPPORT_TLS
@@ -94,8 +94,10 @@
 BOOL    tls_offered            = FALSE;
 BOOL    tls_on_connect         = FALSE;
 uschar *tls_peerdn             = NULL;
+uschar *tls_peercn             = NULL;
 uschar *tls_privatekey         = NULL;
+uschar *tls_prngd_pool         = NULL;
 uschar *tls_try_verify_hosts   = NULL;
 uschar *tls_verify_certificates= NULL;
 uschar *tls_verify_hosts       = NULL;
--- src/globals.h    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/globals.h    18 Sep 2002 00:14:33 -0000    1.1.2.1
@@ -48,7 +48,7 @@
 them. */


 extern int     tls_active;             /* fd/socket when in a TLS session */
-extern BOOL    tls_certificate_verified; /* Client certificate verified */
+extern int     tls_certificate_verified; /* Client certificate verified */
 extern uschar *tls_cipher;             /* Cipher used */


 #ifdef SUPPORT_TLS
@@ -59,8 +59,10 @@
 extern BOOL    tls_offered;            /* Server offered TLS */
 extern BOOL    tls_on_connect;         /* For older MTAs that don't STARTTLS */
 extern uschar *tls_peerdn;             /* DN from peer */
+extern uschar *tls_peercn;             /* Just the commonName part of the peer DN */
 extern uschar *tls_privatekey;         /* Private key file */
+extern uschar *tls_prngd_pool;         /* PRNGD/EGD socket path */
 extern uschar *tls_try_verify_hosts;   /* Optional client verification */
 extern uschar *tls_verify_certificates;/* Path for certificates to check */
 extern uschar *tls_verify_hosts;       /* Mandatory client verification */
--- src/readconf.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/readconf.c    18 Sep 2002 00:14:32 -0000    1.1.2.1
@@ -199,7 +199,8 @@
   { "tls_certificate",          opt_stringptr,   &tls_certificate },
   { "tls_dhparam",              opt_stringptr,   &tls_dhparam },
   { "tls_privatekey",           opt_stringptr,   &tls_privatekey },
+  { "tls_prngd_pool",           opt_stringptr,   &tls_prngd_pool },
   { "tls_try_verify_hosts",     opt_stringptr,   &tls_try_verify_hosts },
   { "tls_verify_certificates",  opt_stringptr,   &tls_verify_certificates },
   { "tls_verify_hosts",         opt_stringptr,   &tls_verify_hosts },
--- src/spool_in.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/spool_in.c    18 Sep 2002 00:14:32 -0000    1.1.2.1
@@ -366,7 +366,9 @@
       tls_cipher = string_copy(big_buffer + 12);
     else if (Ustrncmp(big_buffer, "-tls_peerdn", 11) == 0)
       tls_peerdn = string_copy(big_buffer + 12);
+    else if (Ustrncmp(big_buffer, "-tls_peercn", 11) == 0)
+      tls_peerdn = string_copy(big_buffer + 12);
     #endif


     /* We now record the port number after the address, separated by a
--- src/spool_out.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/spool_out.c    18 Sep 2002 00:14:33 -0000    1.1.2.1
@@ -206,7 +206,8 @@
 #ifdef SUPPORT_TLS
 if (tls_cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_cipher);
 if (tls_peerdn != NULL) fprintf(f, "-tls_peerdn %s\n", tls_peerdn);
+if (tls_peercn != NULL) fprintf(f, "-tls_peercn %s\n", tls_peercn);
 #endif


 /* To complete the envelope, write out the tree of non-recipients, followed by
--- src/tls.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/tls.c    18 Sep 2002 03:29:25 -0000    1.1.2.2
@@ -10,6 +10,8 @@
 program by Michal Trojnara. No cryptographic code is included in Exim. All this
 module does is to call functions from the OpenSSL library. */


+/* Session caching code "written while looking at the source of"
+ mod_ssl by Ralf S. Engelschall. -SRH */

#include "exim.h"

@@ -29,6 +31,7 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
+#include <openssl/x509v3.h>

/* Structure for collecting random data for seeding. */

@@ -43,7 +46,7 @@

static int session_timeout = 200;
static BOOL verify_optional = FALSE;
-static BOOL verify_callback_called = FALSE;
+static const char *verify_check_hostname = NULL;

static const uschar *sid_ctx = US"exim";
static uschar *ssl_xfer_buffer = NULL;
@@ -56,9 +59,27 @@
static SSL_CTX *ctx = NULL;
static SSL *ssl = NULL;

+static int tls_ugly_shutdown_in_progress = 0;


+/*************************************************
+*               Format ASN.1 time value          *
+*************************************************/
+
+/* May be called when debugging and displaying times as embedded in
+   certificates */
+
+static uschar *ASN1_TIME_to_string(ASN1_TIME *a, uschar *buf, size_t buflen)
+{
+  BIO *bio = BIO_new(BIO_s_mem());


+ ASN1_TIME_print(bio, a);
+ BIO_gets(bio, buf, buflen);
+
+ BIO_free(bio);
+
+ return buf;
+}

 /*************************************************
 *               Handle TLS error                 *
@@ -133,7 +154,355 @@
 }



+/*************************************************
+*        Format session id into a DB key         *
+*************************************************/
+
+static uschar *format_session_key(void *id, int idlen)
+{
+  static const uschar hexdig[] = "0123456789abcdef";
+  const uschar *d = id;
+  uschar *s = (uschar*) malloc(idlen*2+2);
+  int i, j;
+
+  s[0] = '=';
+
+  for (i = 0, j = 1; i < idlen; i++) {
+    s[j++] = hexdig[(d[i] & 0xf0)>>4];
+    s[j++] = hexdig[d[i] & 0x0f];
+  }
+  s[j] = '\0';
+
+  return s;
+}
+
+/*************************************************
+*        Find a session from the client side     *
+*************************************************/
+
+/* This is called by tls_client_init() to find the session to use when
+   connecting to a remote server
+
+   We do this by looking in the tls_sessions DB file for the address
+   and port of the remote server. If this gives us a session ID, then
+   we pull that session from the DB file and deserialise it.
+*/
+
+void tls_get_client_session(SSL *ssl, host_item *host)
+{
+  open_db *dbm_file;
+  open_db dbblock;
+  dbdata_tlssessionptr *ptr_record;
+  dbdata_tlssession *sess_record;
+  uschar ptr_key[256];
+  uschar *session_key;
+  uschar timebuf[36];
+  int recordlen;
+  uschar *dataptr;
+  SSL_SESSION *sess;
+
+  if ((strlen(host->address)+2) > sizeof(ptr_key)) {
+    DEBUG(D_tls) debug_printf("tls_get_client_session: INTERNAL ERROR: host name too long for key buffer\n");
+    return;
+  }
+  ptr_key[0] = '@';
+  strcpy(ptr_key+1, host->address);
+
+  dbm_file = dbfn_open(US"tls_sessions", O_RDWR|O_CREAT, &dbblock, TRUE);
+  if (!dbm_file) {
+    DEBUG(D_tls) debug_printf("tls_get_client_session: failed to open dbm file\n");
+    return;
+  }
+
+  ptr_record = dbfn_read(dbm_file, ptr_key);
+  if (!ptr_record) {
+    DEBUG(D_tls) debug_printf("tls_get_client_session: \"%s\" not found in dbm file, create a new session\n", ptr_key);
+    dbfn_close(dbm_file);
+    return;
+  }
+
+  strftime(timebuf, sizeof(timebuf), "%d %B %Y %H:%M:%S %Z", localtime(&ptr_record->expire));
+
+  if (time(NULL) > ptr_record->expire) {
+    DEBUG(D_tls) debug_printf("tls_get_client_session: \"%s\" expired %s\n", ptr_key, timebuf);
+    dbfn_close(dbm_file);
+    return;
+  }
+
+  session_key = format_session_key(ptr_record->session_id, ptr_record->session_id_length);
+
+  DEBUG(D_tls) debug_printf("DBM file record \"%s\" suggests \"%s\" as a session id record\n", ptr_key, session_key);
+
+  sess_record = dbfn_read_with_length(dbm_file, session_key, &recordlen); /* This invalidates *ptr_record, I think -SRH */
+
+  if (!sess_record) {
+    DEBUG(D_tls) debug_printf("tls_get_client_session: \"%s\" not found in dbm file, create a new session\n", session_key);
+    free(session_key);
+    dbfn_close(dbm_file);
+    return;
+  }
+
+  strftime(timebuf, sizeof(timebuf), "%d %B %Y %H:%M:%S %Z", localtime(&sess_record->expire));
+
+  if (time(NULL) > sess_record->expire) {
+    DEBUG(D_tls) debug_printf("tls_get_client_session: \"%s\" expired %s\n", session_key, timebuf);
+    free(session_key);
+    dbfn_close(dbm_file);
+    return;
+  }
+
+  /* whew. we now have a session record in sess_record. deserialise it and associate it with the SSL connection */
+  recordlen -= 2*sizeof(time_t);
+  dataptr = sess_record->session;
+
+  sess = d2i_SSL_SESSION(NULL, &dataptr, recordlen);
+
+  if (!sess) {
+    DEBUG(D_tls) debug_printf("tls_get_client_session: session from \"%s\" failed to deserialise\n", session_key);
+    free(session_key);
+    dbfn_close(dbm_file);
+    return;
+  }
+
+  if (!SSL_set_session(ssl, sess)) {
+    ERR_error_string(ERR_get_error(), ssl_errstring);
+    DEBUG(D_tls) debug_printf("tls_get_client_session: SSL_set_session() failed: %s\n", ssl_errstring);
+    free(session_key);
+    dbfn_close(dbm_file);
+    return;
+  }
+
+  /* Hack around OpenSSL bug */
+  /* SSLv2/SSLv3 confusion not handled, it's after 4am -SRH */
+  if (sess->cipher_id && !sess->cipher) {
+    char buffer[4];
+    buffer[0] = (sess->cipher_id & 0xff00UL) >> 8;
+    buffer[1] = sess->cipher_id & 0xffUL;
+    sess->cipher = ssl->method->get_cipher_by_char(buffer);
+  }
+
+  DEBUG(D_tls) debug_printf("tls_get_client_session: got session record \"%s\" from DBM file; attempting to reuse session\n", session_key);
+  free(session_key);
+  dbfn_close(dbm_file);
+
+  return;
+}
+
+/*************************************************
+*        Store client-side session pointer       *
+*************************************************/
+
+/* This is called by tls_client_init() when an SSL connection is
+   established without reusing session data.
+
+   We store a pointer record in the tls_sessions DBM file so that we
+   can find the session we just negotiated next time we connect to
+   this host.
+*/
+
+void tls_set_client_session(SSL *ssl, host_item *host)
+{
+  open_db *dbm_file;
+  open_db dbblock;
+  SSL_SESSION *sess = SSL_get0_session(ssl);
+  dbdata_tlssessionptr *ptr_record;
+  char *key;
+
+  dbm_file = dbfn_open(US"tls_sessions", O_RDWR|O_CREAT, &dbblock, TRUE);
+  if (!dbm_file) {
+    DEBUG(D_tls) debug_printf("tls_set_client_session: failed to open dbm file\n");
+    return;
+  }
+
+  ptr_record = malloc(sizeof(dbdata_tlssessionptr) + sess->session_id_length);
+  ptr_record->time_stamp = time(NULL);
+  ptr_record->expire = time(NULL)+session_timeout;
+  ptr_record->session_id_length = sess->session_id_length;
+  memcpy(ptr_record->session_id, sess->session_id, sess->session_id_length);
+
+  key = malloc(strlen(host->address)+2);
+  key[0] = '@';
+  strcpy(key+1, host->address);
+
+  if (dbfn_write(dbm_file, key, ptr_record, sizeof(dbdata_tlssessionptr) + sess->session_id_length)) {
+    DEBUG(D_tls) debug_printf("tls_set_client_session: failed to write pointer record to dbm file\n");
+    free(key);
+    free(ptr_record);
+    return;
+  }
+
+  DEBUG(D_tls) debug_printf("tls_set_client_session: saved \"%s\" as pointer to session\n", key);
+
+  dbfn_close(dbm_file);
+  free(key);
+  free(ptr_record);
+}
+
+/*************************************************
+*        Callback for new session                *
+*************************************************/
+
+/* The OpenSSL library calls this callback function when a new session
+   is created (i.e. a connection is established without reusing a
+   session).
+
+   ssl: SSL connection object
+   sess: SSL session object that OpenSSL has created
+
+   returning 0 causes the session to be removed from OpenSSL's
+   internal cache, so we can keep the internal cache and DB file in sync.
+
+   see SSL_CTX_sess_set_new_cb(3)
+*/
+static int tls_callback_newsessioncacheentry(SSL *ssl, SSL_SESSION *sess)
+{
+  open_db *dbm_file;
+  open_db dbblock;
+  dbdata_tlssession data;
+  int serialised_len;
+  uschar *dataptr;
+  uschar *key;
+
+  memset(&data, 0, sizeof(data));
+
+  SSL_set_timeout(sess, session_timeout);
+  data.expire = SSL_get_time(sess) + session_timeout;
+
+  serialised_len = i2d_SSL_SESSION(sess, NULL);
+  if (serialised_len > sizeof(data.session)) {
+    DEBUG(D_tls) debug_printf("TLS: SCACHE(new): NO serialised length (%d) greater than buffer size\n", serialised_len);
+    return 0;
+  }
+  dataptr = data.session;
+  data.time_stamp = time(NULL);
+  i2d_SSL_SESSION(sess, &dataptr);
+
+  dbm_file = dbfn_open(US"tls_sessions", O_RDWR|O_CREAT, &dbblock, TRUE);
+  if (!dbm_file) {
+    DEBUG(D_tls) debug_printf("TLS: SCACHE(new): NO failed to open dbm file\n");
+    return 0;
+  }
+
+  key = format_session_key(sess->session_id, sess->session_id_length);
+  dbfn_write(dbm_file, key, &data, dataptr-(uschar*)&data);
+
+  dbfn_close(dbm_file);
+
+  DEBUG(D_tls) debug_printf("TLS: SCACHE(new): OK wrote session to dbm file, key=%s\n", key);
+
+  free(key);
+  return 1;
+}
+
+/*************************************************
+*        Callback for getting session data       *
+*************************************************/
+
+/* The OpenSSL library calls this callback function when it has
+   determined the session ID, and it is not in the internal cache.  We
+   look up the session ID in the DB file-- if we find an entry, we
+   deserialise the session and return it.  Otherwise, we return NULL,
+   in which case OpenSSL will perform certificate exchange and create
+   a new session.
+
+   ssl: SSL connection object
+   id, idlen: Session ID
+   pcopy: ? set to increment reference count of the session object?
+
+   return the new session object, or NULL.
+
+   see SSL_CTX_sess_set_get_cb(3)
+*/
+SSL_SESSION *tls_callback_getsessioncacheentry(SSL *ssl, uschar *id, int idlen, int *pcopy)
+{
+  open_db *dbm_file;
+  open_db dbblock;
+  uschar *key = format_session_key(id, idlen);
+  dbdata_tlssession *record;
+  SSL_SESSION *sess;
+  uschar *dataptr;
+  int datalen;
+  char timebuf[36];
+
+  dbm_file = dbfn_open(US"tls_sessions", O_RDWR|O_CREAT, &dbblock, TRUE);
+  if (!dbm_file) {
+    DEBUG(D_tls) debug_printf("TLS: SCACHE(get): NO failed to open dbm file\n");
+    free(key);
+    return NULL;
+  }
+
+  record = dbfn_read_with_length(dbm_file, key, &datalen);
+  if (!record) {
+    DEBUG(D_tls) debug_printf("TLS: SCACHE(get): NO session id not found, key=%s\n", key);
+    free(key);
+    dbfn_close(dbm_file);
+    return NULL;
+  }
+  strftime(timebuf, sizeof(timebuf), "%d %B %Y %H:%M:%S %Z", localtime(&record->expire));
+
+  dbfn_close(dbm_file);
+
+  if (time(NULL) > record->expire) {
+    DEBUG(D_tls) debug_printf("TLS: SCACHE(get): NO session expired, key=%s, expire=%s\n", key, timebuf);
+    free(key);
+    return NULL;
+  }
+
+  datalen -= 2*sizeof(time_t);
+  dataptr = record->session;


+  sess = d2i_SSL_SESSION(NULL, &dataptr, datalen);
+
+  if (!sess) {
+    DEBUG(D_tls) debug_printf("TLS: SCACHE(get): NO session failed to deserialise, key=%s, expire=%s\n", key, timebuf);
+    free(key);
+    return NULL;
+  }
+
+  DEBUG(D_tls) debug_printf("TLS: SCACHE(get): OK session=%x, key=%s, expire=%s\n", (int) sess, key, timebuf);
+  return sess;
+}
+
+/*************************************************
+*        Callback for deleting session data      *
+*************************************************/
+
+/* The OpenSSL library calls this callback function when a session
+   should be destroyed, which happens when the session expires or the
+   connection is shut down uncleanly. We delete the session ID from
+   the DB file.
+
+   ctx: The SSL context object
+   sess: The SSL session object
+
+   see SSL_CTX_set_remove_cb(3)
+*/
+void tls_callback_removesessioncacheentry(SSL_CTX *ctx, SSL_SESSION *sess)
+{
+  open_db *dbm_file;
+  open_db dbblock;
+  uschar *key;
+
+  if (tls_ugly_shutdown_in_progress) {
+    DEBUG(D_tls) debug_printf("TLS: SCACHE(del): NO ugly shutdown in progress, don't invalidate session\n");
+    return;
+  }
+
+  dbm_file = dbfn_open(US"tls_sessions", O_RDWR|O_CREAT, &dbblock, TRUE);
+  if (!dbm_file) {
+    DEBUG(D_tls) debug_printf("TLS: SCACHE(del): NO failed to open dbm file\n");
+    return;
+  }
+
+  key = format_session_key(sess->session_id, sess->session_id_length);
+  dbfn_delete(dbm_file, key);
+
+  dbfn_close(dbm_file);
+
+  DEBUG(D_tls) debug_printf("TLS: SCACHE(del): OK session %s deleted whether it already existed or not\n", key);
+
+  free(key);
+}


 /*************************************************
 *        Callback for verification               *
@@ -147,9 +516,12 @@
 If verification is optional, we change the state to yes, but still log the
 verification error. For some reason (it really would help to have proper
 documentation of OpenSSL), this callback function then gets called again, this
-time with state = 1. In fact, that's useful, because we can set up the peerdn
-value, but we must take care not to set the private verified flag on the second
-time through.
+time with state = 1.
+
+We used to set tls_peerdn etc. from within the callback, but this has
+been moved outside, since this callback is not used if restoring from
+a session. (I think). We now use SSL_get_verify_result() after the
+connection has been established. -SRH


 Note: this function is not called if the client fails to present a certificate
 when asked. We get here only if a certificate has been received. Handling of
@@ -166,40 +538,184 @@
 static int
 verify_callback(int state, X509_STORE_CTX *x509ctx)
 {
-static uschar txt[256];
+  uschar subject[512];
+  X509 *err_cert;
+  int err,depth;
+
+  err_cert = X509_STORE_CTX_get_current_cert(x509ctx);
+  err      = X509_STORE_CTX_get_error(x509ctx);
+  depth    = X509_STORE_CTX_get_error_depth(x509ctx);
+
+  X509_NAME_oneline(X509_get_subject_name(err_cert), CS subject, sizeof(subject));
+
+  if (state == 0) {
+    /* there was a verification problem with this certificate */
+    if (verify_optional) {
+      DEBUG(D_tls) debug_printf("X.509 VERIFY %2d IGN \"%s\" [Err#%d: %s] (overridden)\n", depth, subject, err, X509_verify_cert_error_string(err));
+      log_write(0, LOG_MAIN, "SSL verify error: "
+        " (overridden)"
+        "depth=%d error=%s cert=%s",
+        depth,
+        X509_verify_cert_error_string(err),
+        subject);
+      state = 1;
+    }
+    else {
+      DEBUG(D_tls) debug_printf("X.509 VERIFY %2d ERR \"%s\" [Err#%d: %s]\n", depth, subject, err, X509_verify_cert_error_string(err));
+      log_write(0, LOG_MAIN, "SSL verify error: "
+        "depth=%d error=%s cert=%s",
+        depth,
+        X509_verify_cert_error_string(err),
+        subject);
+    }
+
+    /* Show some extra information about the certificate when debugging */
+    DEBUG(D_tls) {
+      uschar issuer[512];
+      uschar timebuf[256];
+
+      switch (err) {
+      case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+    X509_NAME_oneline(X509_get_subject_name(err_cert), CS issuer, sizeof(issuer));
+    debug_printf(" Issuer: %s\n", issuer);
+    break;
+
+      case X509_V_ERR_CERT_NOT_YET_VALID:
+      case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+    debug_printf(" notBefore=%s\n", ASN1_TIME_to_string(X509_get_notBefore(err_cert), timebuf, sizeof(timebuf)));
+    break;
+
+      case X509_V_ERR_CERT_HAS_EXPIRED:
+      case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+    debug_printf(" notAfter=%s\n", ASN1_TIME_to_string(X509_get_notAfter(err_cert), timebuf, sizeof(timebuf)));
+    break;
+
+      case X509_V_ERR_INVALID_PURPOSE:
+    if (err_cert->ex_flags & EXFLAG_NSCERT) {
+      debug_printf(" nsCertType: ");
+
+      if (err_cert->ex_nscert & NS_SSL_CLIENT)
+        debug_printf("sslClient ");
+      if (err_cert->ex_nscert & NS_SSL_SERVER)
+        debug_printf("sslServer ");
+
+      if (err_cert->ex_nscert & ~(NS_SSL_SERVER|NS_SSL_CLIENT))
+        debug_printf("...");
+
+      debug_printf("\n");
+    }
+
+    if (err_cert->ex_flags & EXFLAG_KUSAGE) {
+      debug_printf(" keyUsage: ");
+
+      if (err_cert->ex_kusage & KU_DIGITAL_SIGNATURE)
+        debug_printf("digitalSignature ");
+      if (err_cert->ex_kusage & KU_KEY_ENCIPHERMENT)
+        debug_printf("keyEncipherment ");
+
+      if (err_cert->ex_kusage & ~(KU_KEY_ENCIPHERMENT|KU_DIGITAL_SIGNATURE))
+        debug_printf("...");
+
+      debug_printf("\n");
+    }
+
+    if (err_cert->ex_flags & EXFLAG_XKUSAGE) {
+      debug_printf(" extendedKeyUsage: ");
+
+      if (err_cert->ex_xkusage & XKU_SSL_SERVER)
+        debug_printf("sslServer ");
+      if (err_cert->ex_xkusage & XKU_SSL_CLIENT)
+        debug_printf("sslClient ");


-X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert),
-  CS txt, sizeof(txt));
+      if (err_cert->ex_xkusage & ~(XKU_SSL_SERVER|XKU_SSL_CLIENT))
+        debug_printf("...");


-if (state == 0)
-  {
-  log_write(0, LOG_MAIN, "SSL verify error: depth=%d error=%s cert=%s",
-    x509ctx->error_depth,
-    X509_verify_cert_error_string(x509ctx->error),
-    txt);
-  verify_callback_called = TRUE;
-  if (!verify_optional) return 0;    /* reject */
-  DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
-    "tls_try_verify_hosts)\n");
-  return 1;                          /* accept */
-  }
+      debug_printf("\n");
+    }


-if (x509ctx->error_depth != 0)
-  {
-  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d cert=%s\n",
-     x509ctx->error_depth, txt);
-  }
-else
-  {
-  DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
-    verify_callback_called? "" : " authenticated", txt);
-  tls_peerdn = txt;
+        break;
+      }
+    }
   }
+  else {
+    DEBUG(D_tls) debug_printf("X.509 VERIFY %2d OK  \"%s\"\n", depth, subject);
+  }
+
+  if (state == 1 && depth == 0 && verify_check_hostname) {
+    char peer_CN[256];
+    X509_NAME_get_text_by_NID(X509_get_subject_name(err_cert),
+                  NID_commonName, peer_CN, sizeof(peer_CN));
+
+    DEBUG(D_tls) debug_printf("Authenticating server hostname \"%s\"\n", verify_check_hostname);
+    if (strcasecmp(peer_CN, verify_check_hostname) == 0) {
+      DEBUG(D_tls) debug_printf("Verified server certificate using common name\n");
+    }
+    else {
+      int n = 0;
+      int found_altname = 0;
+
+      while ((n = X509_get_ext_by_NID(err_cert, NID_subject_alt_name, n)) > 0) {
+    X509_EXTENSION *ex;
+    X509V3_EXT_METHOD *method;
+    char *ext_str = NULL;
+    unsigned char *p;
+    ex = X509_get_ext(err_cert, n);
+    method = X509V3_EXT_get(ex);
+
+    DEBUG(D_tls) debug_printf("Found subjectAltName extension, critical=%d\n", ex->critical);
+    if (!method->i2v)
+      continue; /* subjectAltName not multi-valued? */
+
+    p = ex->value->data;
+    ext_str = method->d2i(NULL, &p, ex->value->length);
+    if (ext_str) {
+      STACK_OF(CONF_VALUE) *nval;
+      nval = method->i2v(method, ext_str, NULL);
+      if (nval) {
+        int i;
+        DEBUG(D_tls) debug_printf(" multi-valued num=%d\n", sk_CONF_VALUE_num(nval));
+        for (i = 0; i < sk_CONF_VALUE_num(nval); i++) {
+          CONF_VALUE *innervalue = sk_CONF_VALUE_value(nval, i);
+          DEBUG(D_tls) {
+        debug_printf(" %2d: ", i);
+        if (innervalue->section) {
+          debug_printf("section=\"%s\" ", innervalue->section);
+        }
+        if (innervalue->name) {
+          debug_printf("name=\"%s\" ", innervalue->name);
+        }
+        if (innervalue->value) {
+          debug_printf("value=\"%s\" ", innervalue->value);
+        }
+        debug_printf("\n");
+          }
+          if (strcmp(innervalue->name, "DNS") == 0){
+        if (strcmp(innervalue->value, verify_check_hostname) == 0) {
+          found_altname = 1;
+        }
+        /* else { wildcard matches... } */
+          }
+          /* else { IP address matches... } */
+        }
+        sk_CONF_VALUE_pop_free(nval, X509V3_conf_free);
+      }
+      method->ext_free(ext_str);
+    }
+      }


-if (!verify_callback_called) tls_certificate_verified = TRUE;
-verify_callback_called = TRUE;
+      if (found_altname) {
+    DEBUG(D_tls) debug_printf("Authenticated host name using subject alternate name\n");
+      }
+      else {
+    log_write(0, LOG_MAIN, "TLS authentication error: "
+          "Certificate common name (\"%s\") does not match hostname",
+          peer_CN);
+    state = 0;
+      }
+    }
+  }


-return 1; /* accept */
+ return state; /* 1 = accept; 0 = raise error */
}


@@ -223,9 +739,7 @@
static void
info_callback(SSL *s, int where, int ret)
{
-where = where;
-ret = ret;
-DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s));
+ DEBUG(D_tls) debug_printf("SSL info: %s where=%d ret=%d\n", SSL_state_string_long(s), where, ret);
}


@@ -367,8 +881,31 @@
there. Otherwise, we have to make something up as best we can. Double check
afterwards. */

+ if (tls_prngd_pool) {
+   if (RAND_egd_bytes(tls_prngd_pool, 255) < 255) {
+     if (host == NULL) {
+       log_write(0, LOG_MAIN, "TLS error on connection from %s: "
+         "unable to get entropy from egd/prngd: %s",
+         sender_fullhost ? sender_fullhost : US"local process",
+         strerror(errno)
+         );
+       smtp_printf("454 TLS currently unavailable\r\n");
+     }
+     else {
+       log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: "
+         "unable to get entropy from egd/prngd: %s",
+         host->name, host->address,
+         strerror(errno)
+         );
+     }
+     return FALSE;
+   }
+ }
+
 if (!RAND_status())
   {
+    /* SRH: make people use /dev/urandom or EGD/PRNGD instead of this */
+#if 0
   randstuff r;
   r.t = time(NULL);
   r.p = getpid();
@@ -379,6 +916,7 @@


   if (!RAND_status())
     {
+#endif
     if (host == NULL)
       {
       log_write(0, LOG_MAIN, "TLS error on connection from %s: "
@@ -394,7 +932,9 @@
       }
     return FALSE;
     }
+#if 0
   }
+#endif


/* Set up the information callback, which outputs if debugging is at a suitable
level. */
@@ -437,6 +977,13 @@

SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);

+/* Set up the session cache callbacks */
+
+SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
+SSL_CTX_sess_set_new_cb(ctx, tls_callback_newsessioncacheentry);
+SSL_CTX_sess_set_get_cb(ctx, tls_callback_getsessioncacheentry);
+SSL_CTX_sess_set_remove_cb(ctx, tls_callback_removesessioncacheentry);
+
/* Finally, set the timeout, and we are done */

 SSL_CTX_set_timeout(ctx, session_timeout);
@@ -550,6 +1097,11 @@
     SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
     verify_callback);
   }
+ else {
+   /* Do basic verification of the peer certificate even if we aren't
+      checking against a CA */
+   SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, verify_callback);
+ }


return TRUE;
}
@@ -577,6 +1129,7 @@
tls_server_start(void)
{
int rc;
+X509 *peer_cert;

/* Check for previous activation */

@@ -599,7 +1152,6 @@
optional, set up appropriately. */

tls_certificate_verified = FALSE;
-verify_callback_called = FALSE;

if (verify_check_host(&tls_verify_hosts) == OK)
{
@@ -611,6 +1163,7 @@
if (!setup_certs(tls_verify_certificates, NULL, TRUE)) return FALSE;
verify_optional = TRUE;
}
+verify_check_hostname = NULL;

/* Prepare for new connection */

@@ -665,6 +1218,35 @@

construct_cipher_name(ssl);

+ peer_cert = SSL_get_peer_certificate(ssl);
+ if (peer_cert) {
+   static uschar subject[512];
+   static uschar peer_CN[256];
+
+   /* Get the peer cert's subject into tls_peerdn */
+   X509_NAME_oneline(X509_get_subject_name(peer_cert),
+             CS subject, sizeof(subject));
+   tls_peerdn = CS subject;
+   DEBUG(D_tls) debug_printf("Client certificate = %s\n", tls_peerdn);
+
+   /* Get the CN part of the subject */
+   /* Administrator may want to verify the CN matches the hostname
+      (leave this to be configured in an ACL?) */
+   X509_NAME_get_text_by_NID(X509_get_subject_name(peer_cert),
+                 NID_commonName, peer_CN, sizeof(peer_CN));
+   DEBUG(D_tls) debug_printf(" commonName = \"%s\"\n", peer_CN);
+   tls_peercn = CS peer_CN;
+
+   if (!verify_optional && SSL_get_verify_result(ssl) == X509_V_OK) {
+     DEBUG(D_tls) debug_printf("Certificate verified OK; setting tls_certificate_verified\n");
+     tls_certificate_verified = TRUE;
+   }
+ }
+ else {
+   tls_peerdn = tls_peercn = NULL;
+   DEBUG(D_tls) debug_printf("Client did not present a certificate\n");
+ }
+
 DEBUG(D_tls)
   {
   uschar buf[2048];
@@ -711,9 +1293,8 @@
 BOOL
 tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
   uschar *certificate, uschar *privatekey, uschar *verify_certs,
-  uschar *require_ciphers, int timeout)
+  uschar *require_ciphers, int verify_hostname, int timeout)
 {
-static uschar txt[256];
 uschar *expciphers;
 X509* server_cert;
 int rc;
@@ -730,12 +1311,15 @@
   }


if (!setup_certs(verify_certs, host, FALSE)) return FALSE;
+verify_check_hostname = verify_hostname ? host->name : NULL;

if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host);
SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
SSL_set_fd(ssl, fd);
SSL_set_connect_state(ssl);

+tls_get_client_session(ssl, host);
+
/* There doesn't seem to be a built-in timeout on connection. */

sigalrm_seen = FALSE;
@@ -756,13 +1340,34 @@
else return tls_error(US"SSL_connect", host);
}

-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 = SSL_get_peer_certificate (ssl)) != NULL) {
+   static uschar subject[256];
+
+   X509_NAME_oneline(X509_get_subject_name(server_cert),
+             CS subject, sizeof(subject));
+   tls_peerdn = subject;
+   DEBUG(D_tls) debug_printf("Server certificate = %s\n", tls_peerdn);
+
+   if (SSL_get_verify_result(ssl) == X509_V_OK) {
+     DEBUG(D_tls) debug_printf("Certificate verified OK; setting tls_certificate_verified\n");
+     tls_certificate_verified = TRUE;
+   }
+ }
+ else {
+   /* This really shouldn't happen */
+   tls_peerdn = tls_peercn = NULL;
+ }


construct_cipher_name(ssl); /* Sets tls_cipher */

+ if (!SSL_session_reused(ssl)) {
+   DEBUG(D_tls) debug_printf("calling tls_set_client_session()\n");
+   tls_set_client_session(ssl, host);
+ }
+ else {
+   DEBUG(D_tls) debug_printf("Reused client session; no tls_sessions update necessary\n");
+ }
+
 tls_active = fd;
 return TRUE;
 }
@@ -1020,7 +1625,11 @@
   DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
   SSL_shutdown(ssl);
   }
+ else
+   {
+     tls_ugly_shutdown_in_progress = 1;
+   }


 SSL_free(ssl);
 ssl = NULL;
--- src/transports/smtp.c    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/transports/smtp.c    18 Sep 2002 00:14:34 -0000    1.1.2.1
@@ -95,7 +95,9 @@
   { "tls_tempfail_tryclear", opt_bool,
       (void *)offsetof(smtp_transport_options_block, tls_tempfail_tryclear) },
   { "tls_verify_certificates", opt_stringptr,
-      (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) }
+      (void *)offsetof(smtp_transport_options_block, tls_verify_certificates) },
+  { "tls_verify_hostname",   opt_bool,
+      (void *)offsetof(smtp_transport_options_block, tls_verify_hostname) }
   #endif
 };


@@ -142,7 +144,8 @@
   NULL,                /* tls_privatekey */
   NULL,                /* tls_require_ciphers */
   NULL,                /* tls_verify_certificates */
-  TRUE                 /* tls_tempfail_tryclear */
+  TRUE,                /* tls_tempfail_tryclear */
+  FALSE /*??-SRH*/     /* tls_verify_hostname */
   #endif
 };


@@ -865,7 +868,8 @@
       ob->tls_privatekey,
       ob->tls_verify_certificates,
       ob->tls_require_ciphers,
+      ob->tls_verify_hostname,
       ob->command_timeout);


     /* TLS negotiation failed; give an error. From outside, this function may
--- src/transports/smtp.h    18 Sep 2002 00:13:34 -0000    1.1.1.1
+++ src/transports/smtp.h    18 Sep 2002 00:14:34 -0000    1.1.2.1
@@ -43,6 +43,7 @@
   uschar *tls_require_ciphers;
   uschar *tls_verify_certificates;
   BOOL    tls_tempfail_tryclear;
+  BOOL    tls_verify_hostname;
   #endif
 } smtp_transport_options_block;


--