BUGFIX: forced-fail smtp option tls_sni would dereference NULL
authorPhil Pennock <pdp@exim.org>
Wed, 6 Jun 2012 23:51:44 +0000 (19:51 -0400)
committerPhil Pennock <pdp@exim.org>
Wed, 6 Jun 2012 23:51:44 +0000 (19:51 -0400)
1  2 
doc/doc-txt/ChangeLog
src/src/tls-openssl.c

diff --combined doc/doc-txt/ChangeLog
index 71d239288b808422aa6bb71805a10602d9a29e57,6c0554b5afc66ae78dd279bcd94eb8773c29557a..66fb1ca322f4fe27110aa28a947b5d4236671b71
@@@ -1,42 -1,6 +1,45 @@@
  Change log file for Exim from version 4.21
  -------------------------------------------
  
 +Exim version 4.81
 +-----------------
 +
 +PP/01 Add -bI: framework, and -bI:sieve for querying sieve capabilities.
 +
 +PP/02 Make -n do something, by making it not do something.
 +      When combined with -bP, the name of an option is not output.
 +
 +PP/03 Added tls_dh_min_bits SMTP transport driver option, only honoured
 +      by GnuTLS.
 +
 +PP/04 First step towards DNSSEC, provide $sender_host_dnssec for
 +      $sender_host_name and config options to manage this, and basic check
 +      routines.
 +
 +PP/05 DSCP support for outbound connections and control modifier for inbound.
 +
 +PP/06 Cyrus SASL: set local and remote IP;port properties for driver.
 +      (Only plugin which currently uses this is kerberos4, which nobody should
 +      be using, but we should make it available and other future plugins might
 +      conceivably use it, even though it would break NAT; stuff *should* be
 +      using channel bindings instead).
 +
 +PP/07 Handle "exim -L <tag>" to indicate to use syslog with tag as the process
 +      name; added for Sendmail compatibility; requires admin caller.
 +      Handle -G as equivalent to "control = suppress_local_fixups" (we used to
 +      just ignore it); requires trusted caller.
 +      Also parse but ignore: -Ac -Am -X<logfile>
 +      Bugzilla 1117.
 +
 +TL/01 Bugzilla 1258 - Refactor MAIL FROM optional args processing.
 +
 +JH/01 Bugzilla 1201 & 304 - New cutthrough-delivery feature, with TLS support.
 +
 +JH/02 Support "G" suffix to numbers in ${if comparisons.
 +
++PP/08 Handle smtp transport tls_sni option forced-fail for OpenSSL.
++
++
  Exim version 4.80
  -----------------
  
@@@ -808,7 -772,7 +811,7 @@@ NM/32 Bugzilla 889: Change all instance
  NM/33 Bugzilla 898: Transport filter timeout fix.
        Patch by Todd Rinaldo.
  
 -NM/34 Bugzilla 901: Fix sign/unsigned and UTF mistmatches.
 +NM/34 Bugzilla 901: Fix sign/unsigned and UTF mismatches.
        Patch by Serge Demonchaux.
  
  NM/35 Bugzilla 39: Base64 decode bug fixes.
diff --combined src/src/tls-openssl.c
index a8a62fe8cbbc2ad7e263abe9342f7a9c585af0ee,17cc72133daf5e1afd9f2aed0678c59d37a52eb2..64aa689fb74ab727a0cee93b0c1538d720afb645
@@@ -42,25 -42,19 +42,25 @@@ typedef struct randstuff 
  
  /* Local static variables */
  
 -static BOOL verify_callback_called = FALSE;
 +static BOOL client_verify_callback_called = FALSE;
 +static BOOL server_verify_callback_called = FALSE;
  static const uschar *sid_ctx = US"exim";
  
 -static SSL_CTX *ctx = NULL;
 +static SSL_CTX *client_ctx = NULL;
 +static SSL_CTX *server_ctx = NULL;
 +static SSL     *client_ssl = NULL;
 +static SSL     *server_ssl = NULL;
 +
  #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 -static SSL_CTX *ctx_sni = NULL;
 +static SSL_CTX *client_sni = NULL;
 +static SSL_CTX *server_sni = NULL;
  #endif
 -static SSL *ssl = NULL;
  
  static char ssl_errstring[256];
  
  static int  ssl_session_timeout = 200;
 -static BOOL verify_optional = FALSE;
 +static BOOL client_verify_optional = FALSE;
 +static BOOL server_verify_optional = FALSE;
  
  static BOOL    reexpand_tls_files_for_sni = FALSE;
  
@@@ -83,11 -77,10 +83,11 @@@ typedef struct tls_ext_ctx_cb 
  /* should figure out a cleanup of API to handle state preserved per
  implementation, for various reasons, which can be void * in the APIs.
  For now, we hack around it. */
 -tls_ext_ctx_cb *static_cbinfo = NULL;
 +tls_ext_ctx_cb *client_static_cbinfo = NULL;
 +tls_ext_ctx_cb *server_static_cbinfo = NULL;
  
  static int
 -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional);
 +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client);
  
  /* Callbacks */
  #ifdef EXIM_HAVE_OPENSSL_TLSEXT
@@@ -203,31 -196,14 +203,31 @@@ setting SSL_VERIFY_FAIL_IF_NO_PEER_CER
  Arguments:
    state      current yes/no state as 1/0
    x509ctx    certificate information.
 +  client     TRUE for client startup, FALSE for server startup
  
  Returns:     1 if verified, 0 if not
  */
  
  static int
 -verify_callback(int state, X509_STORE_CTX *x509ctx)
 +verify_callback(int state, X509_STORE_CTX *x509ctx, BOOL client)
  {
  static uschar txt[256];
 +tls_support * tlsp;
 +BOOL * calledp;
 +BOOL * optionalp;
 +
 +if (client)
 +  {
 +  tlsp= &tls_out;
 +  calledp= &client_verify_callback_called;
 +  optionalp= &client_verify_optional;
 +  }
 +else
 +  {
 +  tlsp= &tls_in;
 +  calledp= &server_verify_callback_called;
 +  optionalp= &server_verify_optional;
 +  }
  
  X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert),
    CS txt, sizeof(txt));
@@@ -238,9 -214,9 +238,9 @@@ if (state == 0
      x509ctx->error_depth,
      X509_verify_cert_error_string(x509ctx->error),
      txt);
 -  tls_certificate_verified = FALSE;
 -  verify_callback_called = TRUE;
 -  if (!verify_optional) return 0;    /* reject */
 +  tlsp->certificate_verified = FALSE;
 +  *calledp = TRUE;
 +  if (!*optionalp) return 0;    /* reject */
    DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
      "tls_try_verify_hosts)\n");
    return 1;                          /* accept */
@@@ -254,28 -230,16 +254,28 @@@ if (x509ctx->error_depth != 0
  else
    {
    DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
 -    verify_callback_called? "" : " authenticated", txt);
 -  tls_peerdn = txt;
 +    *calledp ? "" : " authenticated", txt);
 +  tlsp->peerdn = txt;
    }
  
 -if (!verify_callback_called) tls_certificate_verified = TRUE;
 -verify_callback_called = TRUE;
 +if (!*calledp) tlsp->certificate_verified = TRUE;
 +*calledp = TRUE;
  
  return 1;   /* accept */
  }
  
 +static int
 +verify_callback_client(int state, X509_STORE_CTX *x509ctx)
 +{
 +return verify_callback(state, x509ctx, TRUE);
 +}
 +
 +static int
 +verify_callback_server(int state, X509_STORE_CTX *x509ctx)
 +{
 +return verify_callback(state, x509ctx, FALSE);
 +}
 +
  
  
  /*************************************************
@@@ -543,10 -507,7 +543,10 @@@ uschar *expanded
  if (cbinfo->certificate == NULL)
    return OK;
  
 -if (Ustrstr(cbinfo->certificate, US"tls_sni"))
 +if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
 +    Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
 +    Ustrstr(cbinfo->certificate, US"tls_out_sni")
 +   )
    reexpand_tls_files_for_sni = TRUE;
  
  if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
@@@ -638,7 -599,7 +638,7 @@@ DEBUG(D_tls) debug_printf("Received TL
  
  /* Make the extension value available for expansion */
  store_pool = POOL_PERM;
 -tls_sni = string_copy(US servername);
 +tls_in.sni = string_copy(US servername);
  store_pool = old_pool;
  
  if (!reexpand_tls_files_for_sni)
  not confident that memcpy wouldn't break some internal reference counting.
  Especially since there's a references struct member, which would be off. */
  
 -ctx_sni = SSL_CTX_new(SSLv23_server_method());
 -if (!ctx_sni)
 +server_sni = SSL_CTX_new(SSLv23_server_method());
 +if (!server_sni)
    {
    ERR_error_string(ERR_get_error(), ssl_errstring);
    DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
  /* Not sure how many of these are actually needed, since SSL object
  already exists.  Might even need this selfsame callback, for reneg? */
  
 -SSL_CTX_set_info_callback(ctx_sni, SSL_CTX_get_info_callback(ctx));
 -SSL_CTX_set_mode(ctx_sni, SSL_CTX_get_mode(ctx));
 -SSL_CTX_set_options(ctx_sni, SSL_CTX_get_options(ctx));
 -SSL_CTX_set_timeout(ctx_sni, SSL_CTX_get_timeout(ctx));
 -SSL_CTX_set_tlsext_servername_callback(ctx_sni, tls_servername_cb);
 -SSL_CTX_set_tlsext_servername_arg(ctx_sni, cbinfo);
 +SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx));
 +SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx));
 +SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx));
 +SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx));
 +SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
 +SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
  if (cbinfo->server_cipher_list)
 -  SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list);
 +  SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
  #ifdef EXPERIMENTAL_OCSP
  if (cbinfo->ocsp_file)
    {
 -  SSL_CTX_set_tlsext_status_cb(ctx_sni, tls_stapling_cb);
 +  SSL_CTX_set_tlsext_status_cb(server_sni, tls_stapling_cb);
    SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
    }
  #endif
  
 -rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE);
 +rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE);
  if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
  
  /* do this after setup_certs, because this can require the certs for verifying
  OCSP information. */
 -rc = tls_expand_session_files(ctx_sni, cbinfo);
 +rc = tls_expand_session_files(server_sni, cbinfo);
  if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
  
 -rc = init_dh(ctx_sni, cbinfo->dhparam, NULL);
 +rc = init_dh(server_sni, cbinfo->dhparam, NULL);
  if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
  
  DEBUG(D_tls) debug_printf("Switching SSL context.\n");
 -SSL_set_SSL_CTX(s, ctx_sni);
 +SSL_set_SSL_CTX(s, server_sni);
  
  return SSL_TLSEXT_ERR_OK;
  }
@@@ -753,12 -714,12 +753,12 @@@ Returns:          OK/DEFER/FAI
  */
  
  static int
 -tls_init(host_item *host, uschar *dhparam, uschar *certificate,
 +tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
    uschar *privatekey,
  #ifdef EXPERIMENTAL_OCSP
    uschar *ocsp_file,
  #endif
 -  address_item *addr)
 +  address_item *addr, tls_ext_ctx_cb ** cbp)
  {
  long init_options;
  int rc;
@@@ -791,10 -752,10 +791,10 @@@ when OpenSSL is built without SSLv2 sup
  By disabling with openssl_options, we can let admins re-enable with the
  existing knob. */
  
 -ctx = SSL_CTX_new((host == NULL)?
 +*ctxp = SSL_CTX_new((host == NULL)?
    SSLv23_server_method() : SSLv23_client_method());
  
 -if (ctx == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
 +if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
  
  /* It turns out that we need to seed the random number generator this early in
  order to get the full complement of ciphers to work. It took me roughly a day
@@@ -822,10 -783,10 +822,10 @@@ if (!RAND_status()
  /* Set up the information callback, which outputs if debugging is at a suitable
  level. */
  
 -SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
 +SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
  
  /* Automatically re-try reads/writes after renegotiation. */
 -(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
 +(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
  
  /* Apply administrator-supplied work-arounds.
  Historically we applied just one requested option,
@@@ -843,7 -804,7 +843,7 @@@ if (!okay
  if (init_options)
    {
    DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
 -  if (!(SSL_CTX_set_options(ctx, init_options)))
 +  if (!(SSL_CTX_set_options(*ctxp, init_options)))
      return tls_error(string_sprintf(
            "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
    }
@@@ -852,11 -813,11 +852,11 @@@ els
  
  /* Initialize with DH parameters if supplied */
  
 -if (!init_dh(ctx, dhparam, host)) return DEFER;
 +if (!init_dh(*ctxp, dhparam, host)) return DEFER;
  
  /* Set up certificate and key (and perhaps OCSP info) */
  
 -rc = tls_expand_session_files(ctx, cbinfo);
 +rc = tls_expand_session_files(*ctxp, cbinfo);
  if (rc != OK) return rc;
  
  /* If we need to handle SNI, do so */
@@@ -876,21 -837,21 +876,21 @@@ if (host == NULL
  #endif
    /* We always do this, so that $tls_sni is available even if not used in
    tls_certificate */
 -  SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
 -  SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
 +  SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
 +  SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
    }
  #endif
  
  /* Set up the RSA callback */
  
 -SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
 +SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
  
  /* Finally, set the timeout, and we are done */
  
 -SSL_CTX_set_timeout(ctx, ssl_session_timeout);
 +SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
  DEBUG(D_tls) debug_printf("Initialized TLS\n");
  
 -static_cbinfo = cbinfo;
 +*cbp = cbinfo;
  
  return OK;
  }
  *           Get name of cipher in use            *
  *************************************************/
  
 -/* The answer is left in a static buffer, and tls_cipher is set to point
 -to it.
 -
 +/*
  Argument:   pointer to an SSL structure for the connection
 +            buffer to use for answer
 +            size of buffer
 +          pointer to number of bits for cipher
  Returns:    nothing
  */
  
  static void
 -construct_cipher_name(SSL *ssl)
 +construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
  {
 -static uschar cipherbuf[256];
  /* 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. */
@@@ -950,10 -911,11 +950,10 @@@ switch (ssl->session->ssl_version
    }
  
  c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
 -SSL_CIPHER_get_bits(c, &tls_bits);
 +SSL_CIPHER_get_bits(c, bits);
  
 -string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
 -  SSL_CIPHER_get_name(c), tls_bits);
 -tls_cipher = cipherbuf;
 +string_format(cipherbuf, bsize, "%s:%s:%u", ver,
 +  SSL_CIPHER_get_name(c), *bits);
  
  DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
  }
@@@ -975,13 -937,12 +975,13 @@@ Arguments
    host          NULL in a server; the remote host in a client
    optional      TRUE if called from a server for a host in tls_try_verify_hosts;
                  otherwise passed as FALSE
 +  client        TRUE if called for client startup, FALSE for server startup
  
  Returns:        OK/DEFER/FAIL
  */
  
  static int
 -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional)
 +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client)
  {
  uschar *expcerts, *expcrl;
  
@@@ -1080,7 -1041,7 +1080,7 @@@ if (expcerts != NULL
  
    SSL_CTX_set_verify(sctx,
      SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
 -    verify_callback);
 +    client ? verify_callback_client : verify_callback_server);
    }
  
  return OK;
@@@ -1111,11 -1072,10 +1111,11 @@@ tls_server_start(const uschar *require_
  int rc;
  uschar *expciphers;
  tls_ext_ctx_cb *cbinfo;
 +static uschar cipherbuf[256];
  
  /* Check for previous activation */
  
 -if (tls_active >= 0)
 +if (tls_in.active >= 0)
    {
    tls_error(US"STARTTLS received after TLS started", NULL, US"");
    smtp_printf("554 Already in TLS\r\n");
  /* Initialize the SSL library. If it fails, it will already have logged
  the error. */
  
 -rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey,
 +rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
  #ifdef EXPERIMENTAL_OCSP
      tls_ocsp_file,
  #endif
 -    NULL);
 +    NULL, &server_static_cbinfo);
  if (rc != OK) return rc;
 -cbinfo = static_cbinfo;
 +cbinfo = server_static_cbinfo;
  
  if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
    return FAIL;
@@@ -1146,7 -1106,7 +1146,7 @@@ if (expciphers != NULL
    uschar *s = expciphers;
    while (*s != 0) { if (*s == '_') *s = '-'; s++; }
    DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
 -  if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
 +  if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
      return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
    cbinfo->server_cipher_list = expciphers;
    }
  /* If this is a host for which certificate verification is mandatory or
  optional, set up appropriately. */
  
 -tls_certificate_verified = FALSE;
 -verify_callback_called = FALSE;
 +tls_in.certificate_verified = FALSE;
 +server_verify_callback_called = FALSE;
  
  if (verify_check_host(&tls_verify_hosts) == OK)
    {
 -  rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE);
 +  rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE);
    if (rc != OK) return rc;
 -  verify_optional = FALSE;
 +  server_verify_optional = FALSE;
    }
  else if (verify_check_host(&tls_try_verify_hosts) == OK)
    {
 -  rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE);
 +  rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, TRUE, FALSE);
    if (rc != OK) return rc;
 -  verify_optional = TRUE;
 +  server_verify_optional = TRUE;
    }
  
  /* Prepare for new connection */
  
 -if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
 +if ((server_ssl = SSL_new(server_ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
  
  /* Warning: we used to SSL_clear(ssl) here, it was removed.
   *
@@@ -1193,8 -1153,8 +1193,8 @@@ make them disconnect. We need to have a
  the response. Other smtp_printf() calls do not need it, because in non-TLS
  mode, the fflush() happens when smtp_getc() is called. */
  
 -SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
 -if (!tls_on_connect)
 +SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
 +if (!tls_in.on_connect)
    {
    smtp_printf("220 TLS go ahead\r\n");
    fflush(smtp_out);
  /* Now negotiate the TLS session. We put our own timer on it, since it seems
  that the OpenSSL library doesn't. */
  
 -SSL_set_wfd(ssl, fileno(smtp_out));
 -SSL_set_rfd(ssl, fileno(smtp_in));
 -SSL_set_accept_state(ssl);
 +SSL_set_wfd(server_ssl, fileno(smtp_out));
 +SSL_set_rfd(server_ssl, fileno(smtp_in));
 +SSL_set_accept_state(server_ssl);
  
  DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
  
  sigalrm_seen = FALSE;
  if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 -rc = SSL_accept(ssl);
 +rc = SSL_accept(server_ssl);
  alarm(0);
  
  if (rc <= 0)
@@@ -1228,22 -1188,16 +1228,22 @@@ DEBUG(D_tls) debug_printf("SSL_accept w
  /* TLS has been set up. Adjust the input functions to read via TLS,
  and initialize things. */
  
 -construct_cipher_name(ssl);
 +construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
 +tls_in.cipher = cipherbuf;
  
  DEBUG(D_tls)
    {
    uschar buf[2048];
 -  if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf)) != NULL)
 +  if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL)
      debug_printf("Shared ciphers: %s\n", buf);
    }
  
  
 +/* Only used by the server-side tls (tls_in), including tls_getc.
 +   Client-side (tls_out) reads (seem to?) go via
 +   smtp_read_response()/ip_recv().
 +   Hence no need to duplicate for _in and _out.
 + */
  ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
  ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
  ssl_xfer_eof = ssl_xfer_error = 0;
@@@ -1254,7 -1208,7 +1254,7 @@@ receive_feof = tls_feof
  receive_ferror = tls_ferror;
  receive_smtp_buffered = tls_smtp_buffered;
  
 -tls_active = fileno(smtp_out);
 +tls_in.active = fileno(smtp_out);
  return OK;
  }
  
@@@ -1279,8 -1233,6 +1279,8 @@@ Argument
    verify_certs     file for certificate verify
    crl              file containing CRL
    require_ciphers  list of allowed ciphers
 +  dh_min_bits      minimum number of bits acceptable in server's DH prime
 +                   (unused in OpenSSL)
    timeout          startup timeout
  
  Returns:           OK on success
  tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
    uschar *certificate, uschar *privatekey, uschar *sni,
    uschar *verify_certs, uschar *crl,
 -  uschar *require_ciphers, int timeout)
 +  uschar *require_ciphers, int dh_min_bits ARG_UNUSED, int timeout)
  {
  static uschar txt[256];
  uschar *expciphers;
  X509* server_cert;
  int rc;
 +static uschar cipherbuf[256];
  
 -rc = tls_init(host, dhparam, certificate, privatekey,
 +rc = tls_init(&client_ctx, host, dhparam, certificate, privatekey,
  #ifdef EXPERIMENTAL_OCSP
      NULL,
  #endif
 -    addr);
 +    addr, &client_static_cbinfo);
  if (rc != OK) return rc;
  
 -tls_certificate_verified = FALSE;
 -verify_callback_called = FALSE;
 +tls_out.certificate_verified = FALSE;
 +client_verify_callback_called = FALSE;
  
  if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
    return FAIL;
@@@ -1322,29 -1273,33 +1322,33 @@@ if (expciphers != NULL
    uschar *s = expciphers;
    while (*s != 0) { if (*s == '_') *s = '-'; s++; }
    DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
 -  if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
 +  if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
      return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
    }
  
 -rc = setup_certs(ctx, verify_certs, crl, host, FALSE);
 +rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE, TRUE);
  if (rc != OK) return rc;
  
 -if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
 -SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
 -SSL_set_fd(ssl, fd);
 -SSL_set_connect_state(ssl);
 +if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
 +SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
 +SSL_set_fd(client_ssl, fd);
 +SSL_set_connect_state(client_ssl);
  
  if (sni)
    {
 -  if (!expand_check(sni, US"tls_sni", &tls_sni))
 +  if (!expand_check(sni, US"tls_sni", &tls_out.sni))
      return FAIL;
-   if (!Ustrlen(tls_out.sni))
 -  if (tls_sni == NULL)
++  if (tls_out.sni == NULL)
+     {
+     DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
+     }
 -  else if (!Ustrlen(tls_sni))
 -    tls_sni = NULL;
++  else if (!Ustrlen(tls_out.sni))
 +    tls_out.sni = NULL;
    else
      {
  #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 -    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni);
 -    SSL_set_tlsext_host_name(ssl, tls_sni);
 +    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
 +    SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
  #else
      DEBUG(D_tls)
        debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
  DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
  sigalrm_seen = FALSE;
  alarm(timeout);
 -rc = SSL_connect(ssl);
 +rc = SSL_connect(client_ssl);
  alarm(0);
  
  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);
 +server_cert = SSL_get_peer_certificate (client_ssl);
  if (server_cert)
    {
 -  tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
 +  tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
      CS txt, sizeof(txt));
 -  tls_peerdn = txt;
 +  tls_out.peerdn = txt;
    }
  else
 -  tls_peerdn = NULL;
 +  tls_out.peerdn = NULL;
  
 -construct_cipher_name(ssl);   /* Sets tls_cipher */
 +construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
 +tls_out.cipher = cipherbuf;
  
 -tls_active = fd;
 +tls_out.active = fd;
  return OK;
  }
  
@@@ -1397,8 -1351,6 +1401,8 @@@ it refills the buffer via the SSL readi
  
  Arguments:  none
  Returns:    the next character or EOF
 +
 +Only used by the server-side TLS.
  */
  
  int
@@@ -1409,12 -1361,12 +1413,12 @@@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buf
    int error;
    int inbytes;
  
 -  DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
 +  DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
      ssl_xfer_buffer, ssl_xfer_buffer_size);
  
    if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 -  inbytes = SSL_read(ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
 -  error = SSL_get_error(ssl, inbytes);
 +  inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size);
 +  error = SSL_get_error(server_ssl, inbytes);
    alarm(0);
  
    /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
      receive_ferror = smtp_ferror;
      receive_smtp_buffered = smtp_buffered;
  
 -    SSL_free(ssl);
 -    ssl = NULL;
 -    tls_active = -1;
 -    tls_bits = 0;
 -    tls_cipher = NULL;
 -    tls_peerdn = NULL;
 -    tls_sni = NULL;
 +    SSL_free(server_ssl);
 +    server_ssl = NULL;
 +    tls_in.active = -1;
 +    tls_in.bits = 0;
 +    tls_in.cipher = NULL;
 +    tls_in.peerdn = NULL;
 +    tls_in.sni = NULL;
  
      return smtp_getc();
      }
@@@ -1484,14 -1436,11 +1488,14 @@@ Arguments
  
  Returns:    the number of bytes read
              -1 after a failed read
 +
 +Only used by the client-side TLS.
  */
  
  int
 -tls_read(uschar *buff, size_t len)
 +tls_read(BOOL is_server, uschar *buff, size_t len)
  {
 +SSL *ssl = is_server ? server_ssl : client_ssl;
  int inbytes;
  int error;
  
@@@ -1524,23 -1473,19 +1528,23 @@@ return inbytes
  
  /*
  Arguments:
 +  is_server channel specifier
    buff      buffer of data
    len       number of bytes
  
  Returns:    the number of bytes after a successful write,
              -1 after a failed write
 +
 +Used by both server-side and client-side TLS.
  */
  
  int
 -tls_write(const uschar *buff, size_t len)
 +tls_write(BOOL is_server, const uschar *buff, size_t len)
  {
  int outbytes;
  int error;
  int left = len;
 +SSL *ssl = is_server ? server_ssl : client_ssl;
  
  DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
  while (left > 0)
      log_write(0, LOG_MAIN, "SSL channel closed on write");
      return -1;
  
 +    case SSL_ERROR_SYSCALL:
 +    log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
 +      sender_fullhost ? sender_fullhost : US"<unknown>",
 +      strerror(errno));
 +
      default:
      log_write(0, LOG_MAIN, "SSL_write error %d", error);
      return -1;
@@@ -1590,28 -1530,23 +1594,28 @@@ would tamper with the SSL session in th
  
  Arguments:   TRUE if SSL_shutdown is to be called
  Returns:     nothing
 +
 +Used by both server-side and client-side TLS.
  */
  
  void
 -tls_close(BOOL shutdown)
 +tls_close(BOOL is_server, BOOL shutdown)
  {
 -if (tls_active < 0) return;  /* TLS was not active */
 +SSL **sslp = is_server ? &server_ssl : &client_ssl;
 +int *fdp = is_server ? &tls_in.active : &tls_out.active;
 +
 +if (*fdp < 0) return;  /* TLS was not active */
  
  if (shutdown)
    {
    DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
 -  SSL_shutdown(ssl);
 +  SSL_shutdown(*sslp);
    }
  
 -SSL_free(ssl);
 -ssl = NULL;
 +SSL_free(*sslp);
 +*sslp = NULL;
  
 -tls_active = -1;
 +*fdp = -1;
  }