[exim-cvs] Certificate variables and field-extractor expansi…

Startseite
Nachricht löschen
Nachricht beantworten
Autor: Exim Git Commits Mailing List
Datum:  
To: exim-cvs
Betreff: [exim-cvs] Certificate variables and field-extractor expansions. Bug 1358
Gitweb: http://git.exim.org/exim.git/commitdiff/9d1c15ef45fcc8809349378922de20ae9a774c75
Commit:     9d1c15ef45fcc8809349378922de20ae9a774c75
Parent:     9d9c374678ae4b04869c90bc5980acfcfb68c336
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Fri May 2 18:50:34 2014 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Fri May 2 20:05:30 2014 +0100


    Certificate variables and field-extractor expansions.  Bug 1358
---
 doc/doc-docbook/spec.xfpt               |   69 ++++
 doc/doc-txt/ChangeLog                   |    3 +
 doc/doc-txt/NewStuff                    |    3 +
 src/OS/Makefile-Base                    |    2 +-
 src/scripts/MakeLinks                   |    2 +
 src/src/deliver.c                       |  108 +++++--
 src/src/expand.c                        |  536 ++++++++++++++++++++-----------
 src/src/functions.h                     |   19 ++
 src/src/globals.c                       |    6 +
 src/src/globals.h                       |    2 +
 src/src/smtp_in.c                       |    2 +
 src/src/spool_in.c                      |   10 +
 src/src/spool_out.c                     |   16 +-
 src/src/structs.h                       |    2 +
 src/src/tls-gnu.c                       |  125 +++++---
 src/src/tls-openssl.c                   |   23 ++-
 src/src/tls.c                           |    2 +
 src/src/tlscert-gnu.c                   |  325 +++++++++++++++++++
 src/src/tlscert-openssl.c               |  293 +++++++++++++++++
 src/src/transports/smtp.c               |    8 +-
 test/confs/2002                         |   24 ++-
 test/confs/2102                         |   24 ++-
 test/confs/5750                         |   95 ++++++
 test/confs/5760                         |   95 ++++++
 test/log/2002                           |   18 +-
 test/log/2102                           |   19 +-
 test/log/5750                           |   46 +++
 test/log/5760                           |   47 +++
 test/mail/2002.CALLER                   |    2 +-
 test/mail/2102.CALLER                   |    2 +-
 test/scripts/2000-GnuTLS/2002           |    4 +-
 test/scripts/2100-OpenSSL/2102          |    4 +-
 test/scripts/5750-GnuTLS-TPDA/5750      |   13 +
 test/scripts/5750-GnuTLS-TPDA/REQUIRES  |    2 +
 test/scripts/5760-OpenSSL-TPDA/5760     |   13 +
 test/scripts/5760-OpenSSL-TPDA/REQUIRES |    2 +
 test/stdout/2002                        |    4 +-
 test/stdout/2102                        |    4 +-
 38 files changed, 1686 insertions(+), 288 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index afc15d4..ec93675 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -8875,6 +8875,41 @@ the expansion result is an empty string.
If the ACL returns defer the result is a forced-fail. Otherwise the expansion fails.


+.new
+.vitem "&*${certextract{*&<&'field'&>&*}{*&<&'certificate'&>&*}&&&
+       {*&<&'string2'&>&*}{*&<&'string3'&>&*}}*&"
+.cindex "expansion" "extracting cerificate fields"
+.cindex "&%certextract%&" "certificate fields"
+.cindex "certificate" "extracting fields"
+The <&'certificate'&> must be a variable of type certificate.
+The field name is expanded and used to retrive the relevant field from
+the certificate.  Supported fields are:
+.display
+version
+serial_number
+subject
+issuer
+notbefore
+notafter
+signature_algorithm
+signature
+subject_altname
+ocsp_uri
+crl_uri
+.endd
+If the field is found,
+<&'string2'&> is expanded, and replaces the whole item;
+otherwise <&'string3'&> is used. During the expansion of <&'string2'&> the
+variable &$value$& contains the value that has been extracted. Afterwards, it
+is restored to any previous value it might have had.
+
+If {<&'string3'&>} is omitted, the item is replaced by an empty string if the
+key is not found. If {<&'string2'&>} is also omitted, the value that was
+extracted is used.
+
+Field values are presented in human-readable form.
+.wen
+
 .vitem "&*${dlfunc{*&<&'file'&>&*}{*&<&'function'&>&*}{*&<&'arg'&>&*}&&&
        {*&<&'arg'&>&*}...}*&"
 .cindex &%dlfunc%&
@@ -12253,6 +12288,40 @@ on an outbound SMTP connection; the meaning of
 this depends upon the TLS implementation used.
 If TLS has not been negotiated, the value will be 0.


+.new
+.vitem &$tls_in_ourcert$&
+.vindex "&$tls_in_ourcert$&"
+This variable refers to the certificate presented to the peer of an
+inbound connection when the message was received.
+It is only useful as the argument of a
+&%certextract%& expansion item or the name for a &%def%& expansion condition.
+.wen
+
+.new
+.vitem &$tls_in_peercert$&
+.vindex "&$tls_in_peercert$&"
+This variable refers to the certificate presented by the peer of an
+inbound connection when the message was received.
+It is only useful as the argument of a
+&%certextract%& expansion item or the name for a &%def%& expansion condition.
+.wen
+
+.new
+.vitem &$tls_out_ourcert$&
+.vindex "&$tls_out_ourcert$&"
+This variable refers to the certificate presented to the peer of an
+outbound connection.  It is only useful as the argument of a
+&%certextract%& expansion item or the name for a &%def%& expansion condition.
+.wen
+
+.new
+.vitem &$tls_out_peercert$&
+.vindex "&$tls_out_peercert$&"
+This variable refers to the certificate presented by the peer of an
+outbound connection.  It is only useful as the argument of a
+&%certextract%& expansion item or the name for a &%def%& expansion condition.
+.wen
+
 .vitem &$tls_in_certificate_verified$&
 .vindex "&$tls_in_certificate_verified$&"
 This variable is set to &"1"& if a TLS certificate was verified when the
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 1727485..c985288 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -104,6 +104,9 @@ TL/10 Bugzilla 1454: New -oMm option to pass message reference to Exim.
       Requires trusted mode and valid format message id, aborts otherwise.
       Patch contributed by Heiko Schlichting.


+JH/20 New expansion variables tls_(in,out)_(our,peer)cert, and expansion item
+      certextract with support for various fields.  Bug 1358.
+


Exim version 4.82
-----------------
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 33c66ce..b6fc576 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -44,6 +44,9 @@ Version 4.83

9. Support for DNSSEC on outbound connections.

+10. New variables "tls_(in,out)_(our,peer)cert" and expansion item
+    "certextract" to extract fields from them.
+


 Version 4.82
 ------------
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 8209969..0caf860 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -577,7 +577,7 @@ spool_out.o:     $(HDRS) spool_out.c
 std-crypto.o:    $(HDRS) std-crypto.c
 store.o:         $(HDRS) store.c
 string.o:        $(HDRS) string.c
-tls.o:           $(HDRS) tls.c tls-gnu.c tls-openssl.c
+tls.o:           $(HDRS) tls.c tls-gnu.c tlscert-gnu.c tls-openssl.c tlscert-openssl.c
 tod.o:           $(HDRS) tod.c
 transport.o:     $(HDRS) transport.c
 tree.o:          $(HDRS) tree.c
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index 2eb8a96..01cd21f 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -233,6 +233,8 @@ ln -s ../src/std-crypto.c      std-crypto.c
 ln -s ../src/store.c           store.c
 ln -s ../src/string.c          string.c
 ln -s ../src/tls.c             tls.c
+ln -s ../src/tlscert-gnu.c     tlscert-gnu.c
+ln -s ../src/tlscert-openssl.c tlscert-openssl.c
 ln -s ../src/tls-gnu.c         tls-gnu.c
 ln -s ../src/tls-openssl.c     tls-openssl.c
 ln -s ../src/tod.c             tod.c
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 1e7a8a1..fff0e2f 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -1057,7 +1057,7 @@ if (addr->return_file >= 0 && addr->return_filename != NULL)
   (void)close(addr->return_file);
   }


-/* The sucess case happens only after delivery by a transport. */
+/* The success case happens only after delivery by a transport. */

if (result == OK)
{
@@ -1073,10 +1073,8 @@ if (result == OK)
DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address);

   if (addr->parent == NULL)
-    {
     deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address,
       driver_name, driver_kind);
-    }
   else
     {
     deliver_msglog("%s %s <%s>: %s%s succeeded\n", now, addr->address,
@@ -1084,7 +1082,28 @@ if (result == OK)
     child_done(addr, now);
     }


+  /* Certificates for logging (via TPDA) */
+  #ifdef SUPPORT_TLS
+  tls_out.ourcert = addr->ourcert;
+  addr->ourcert = NULL;
+  tls_out.peercert = addr->peercert;
+  addr->peercert = NULL;
+  #endif
+
   delivery_log(LOG_MAIN, addr, logchar, NULL);
+
+  #ifdef SUPPORT_TLS
+  if (tls_out.ourcert)
+    {
+    tls_free_cert(tls_out.ourcert);
+    tls_out.ourcert = NULL;
+    }
+  if (tls_out.peercert)
+    {
+    tls_free_cert(tls_out.peercert);
+    tls_out.peercert = NULL;
+    }
+  #endif
   }



@@ -2957,27 +2976,51 @@ while (!done)

     #ifdef SUPPORT_TLS
     case 'X':
-    if (addr == NULL) goto ADDR_MISMATCH;            /* Below, in 'A' handler */
-    addr->cipher = (*ptr)? string_copy(ptr) : NULL;
-    while (*ptr++);
-    addr->peerdn = (*ptr)? string_copy(ptr) : NULL;
+    if (addr == NULL) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
+    switch (*ptr++)
+      {
+      case '1':
+      addr->cipher = NULL;
+      addr->peerdn = NULL;
+
+      if (*ptr)
+    addr->cipher = string_copy(ptr);
+      while (*ptr++);
+      if (*ptr)
+    {
+    addr->peerdn = string_copy(ptr);
+    }
+      break;
+
+      case '2':
+      addr->peercert = NULL;
+      if (*ptr)
+    (void) tls_import_cert(ptr, &addr->peercert);
+      break;
+
+      case '3':
+      addr->ourcert = NULL;
+      if (*ptr)
+    (void) tls_import_cert(ptr, &addr->ourcert);
+      break;
+      }
     while (*ptr++);
     break;
     #endif


     case 'C':    /* client authenticator information */
     switch (*ptr++)
-    {
-    case '1':
-      addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
-      break;
-    case '2':
-      addr->auth_id = (*ptr)? string_copy(ptr) : NULL;
-      break;
-    case '3':
-      addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL;
-      break;
-    }
+      {
+      case '1':
+    addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
+    break;
+      case '2':
+    addr->auth_id = (*ptr)? string_copy(ptr) : NULL;
+    break;
+      case '3':
+    addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL;
+    break;
+      }
     while (*ptr++);
     break;


@@ -4054,18 +4097,41 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
       /* Use an X item only if there's something to send */


       #ifdef SUPPORT_TLS
-      if (addr->cipher != NULL)
+      if (addr->cipher)
         {
         ptr = big_buffer;
-        sprintf(CS ptr, "X%.128s", addr->cipher);
+        sprintf(CS ptr, "X1%.128s", addr->cipher);
         while(*ptr++);
-        if (addr->peerdn == NULL) *ptr++ = 0; else
+        if (!addr->peerdn)
+      *ptr++ = 0;
+    else
           {
           sprintf(CS ptr, "%.512s", addr->peerdn);
           while(*ptr++);
           }
+
         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
         }
+      if (addr->peercert)
+    {
+        ptr = big_buffer;
+    *ptr++ = 'X'; *ptr++ = '2';
+    if (!tls_export_cert(ptr, big_buffer_size-2, addr->peercert))
+      while(*ptr++);
+    else
+      *ptr++ = 0;
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+    }
+      if (addr->ourcert)
+    {
+        ptr = big_buffer;
+    *ptr++ = 'X'; *ptr++ = '3';
+    if (!tls_export_cert(ptr, big_buffer_size-2, addr->ourcert))
+      while(*ptr++);
+    else
+      *ptr++ = 0;
+        rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+    }
       #endif


       if (client_authenticator)
diff --git a/src/src/expand.c b/src/src/expand.c
index 54b3abc..34fb034 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -93,6 +93,9 @@ bcrypt ({CRYPT}$2a$).




+#ifndef nelements
+# define nelements(arr) (sizeof(arr) / sizeof(*arr))
+#endif

 /*************************************************
 *            Local statics and tables            *
@@ -103,6 +106,7 @@ alphabetical order. */


static uschar *item_table[] = {
US"acl",
+ US"certextract",
US"dlfunc",
US"extract",
US"filter",
@@ -127,6 +131,7 @@ static uschar *item_table[] = {

 enum {
   EITEM_ACL,
+  EITEM_CERTEXTRACT,
   EITEM_DLFUNC,
   EITEM_EXTRACT,
   EITEM_FILTER,
@@ -387,7 +392,8 @@ enum {
   vtype_host_lookup,    /* value not used; get host name */
   vtype_load_avg,       /* value not used; result is int from os_getloadavg */
   vtype_pspace,         /* partition space; value is T/F for spool/log */
-  vtype_pinodes         /* partition inodes; value is T/F for spool/log */
+  vtype_pinodes,        /* partition inodes; value is T/F for spool/log */
+  vtype_cert        /* SSL certificate */
   #ifndef DISABLE_DKIM
   ,vtype_dkim           /* Lookup of value in DKIM signature */
   #endif
@@ -665,6 +671,8 @@ static var_entry var_table[] = {
   { "tls_in_bits",         vtype_int,         &tls_in.bits },
   { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified },
   { "tls_in_cipher",       vtype_stringptr,   &tls_in.cipher },
+  { "tls_in_ourcert",      vtype_cert,        &tls_in.ourcert },
+  { "tls_in_peercert",     vtype_cert,        &tls_in.peercert },
   { "tls_in_peerdn",       vtype_stringptr,   &tls_in.peerdn },
 #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
   { "tls_in_sni",          vtype_stringptr,   &tls_in.sni },
@@ -672,6 +680,8 @@ static var_entry var_table[] = {
   { "tls_out_bits",        vtype_int,         &tls_out.bits },
   { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
   { "tls_out_cipher",      vtype_stringptr,   &tls_out.cipher },
+  { "tls_out_ourcert",     vtype_cert,        &tls_out.ourcert },
+  { "tls_out_peercert",    vtype_cert,        &tls_out.peercert },
   { "tls_out_peerdn",      vtype_stringptr,   &tls_out.peerdn },
 #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
   { "tls_out_sni",         vtype_stringptr,   &tls_out.sni },
@@ -1065,6 +1075,23 @@ return NULL;




+static var_entry *
+find_var_ent(uschar * name)
+{
+int first = 0;
+int last = var_table_size;
+
+while (last > first)
+ {
+ int middle = (first + last)/2;
+ int c = Ustrcmp(name, var_table[middle].name);
+
+ if (c > 0) { first = middle + 1; continue; }
+ if (c < 0) { last = middle; continue; }
+ return &var_table[middle];
+ }
+return NULL;
+}

 /*************************************************
 *   Extract numbered subfield from string        *
@@ -1140,7 +1167,7 @@ return fieldtext;



static uschar *
-expand_getlistele (int field, uschar *list)
+expand_getlistele(int field, uschar * list)
{
uschar * tlist= list;
int sep= 0;
@@ -1156,6 +1183,68 @@ while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
return string_nextinlist(&list, &sep, NULL, 0);
}

+
+/* Certificate fields, by name.  Worry about by-OID later */
+
+#ifdef SUPPORT_TLS
+typedef struct
+{
+uschar * name;
+uschar * (*getfn)(void * cert);
+} certfield;
+static certfield certfields[] =
+{            /* linear search; no special order */
+  { US"version",    &tls_cert_version },
+  { US"serial_number",    &tls_cert_serial_number },
+  { US"subject",    &tls_cert_subject },
+  { US"notbefore",    &tls_cert_not_before },
+  { US"notafter",    &tls_cert_not_after },
+  { US"issuer",        &tls_cert_issuer },
+  { US"signature",    &tls_cert_signature },
+  { US"signature_algorithm",    &tls_cert_signature_algorithm },
+  { US"subject_altname",    &tls_cert_subject_altname },
+  { US"ocsp_uri",    &tls_cert_ocsp_uri },
+  { US"crl_uri",    &tls_cert_crl_uri },
+};
+
+static uschar *
+expand_getcertele(uschar * field, uschar * certvar)
+{
+var_entry * vp;
+certfield * cp;
+
+if (!(vp = find_var_ent(certvar)))
+  {
+  expand_string_message = 
+    string_sprintf("no variable named \"%s\"", certvar);
+  return NULL;          /* Unknown variable name */
+  }
+/* NB this stops us passing certs around in variable.  Might
+want to do that in future */
+if (vp->type != vtype_cert)
+  {
+  expand_string_message = 
+    string_sprintf("\"%s\" is not a certificate", certvar);
+  return NULL;          /* Unknown variable name */
+  }
+if (!*(void **)vp->value)
+  return NULL;
+
+if (*field >= '0' && *field <= '9')
+  return tls_cert_ext_by_oid(*(void **)vp->value, field, 0);
+
+for(cp = certfields;
+    cp < certfields + nelements(certfields);
+    cp++)
+  if (Ustrcmp(cp->name, field) == 0)
+    return (*cp->getfn)( *(void **)vp->value );
+
+expand_string_message = 
+  string_sprintf("bad field selector \"%s\" for certextract", field);
+return NULL;
+}
+#endif    /*SUPPORT_TLS*/
+
 /*************************************************
 *        Extract a substring from a string       *
 *************************************************/
@@ -1551,8 +1640,10 @@ Returns:        NULL if the variable does not exist, or
 static uschar *
 find_variable(uschar *name, BOOL exists_only, BOOL skipping, int *newsize)
 {
-int first = 0;
-int last = var_table_size;
+var_entry * vp;
+uschar *s, *domain;
+uschar **ss;
+void * val;


/* Handle ACL variables, whose names are of the form acl_cxxx or acl_mxxx.
Originally, xxx had to be a number in the range 0-9 (later 0-19), but from
@@ -1585,203 +1676,198 @@ if (Ustrncmp(name, "auth", 4) == 0)

/* For all other variables, search the table */

-while (last > first)
-  {
-  uschar *s, *domain;
-  uschar **ss;
-  int middle = (first + last)/2;
-  int c = Ustrcmp(name, var_table[middle].name);
-
-  if (c > 0) { first = middle + 1; continue; }
-  if (c < 0) { last = middle; continue; }
+if (!(vp = find_var_ent(name)))
+  return NULL;          /* Unknown variable name */


- /* Found an existing variable. If in skipping state, the value isn't needed,
- and we want to avoid processing (such as looking up the host name). */
+/* Found an existing variable. If in skipping state, the value isn't needed,
+and we want to avoid processing (such as looking up the host name). */

- if (skipping) return US"";
+if (skipping)
+ return US"";

-  switch (var_table[middle].type)
+val = vp->value;
+switch (vp->type)
+  {
+  case vtype_filter_int:
+  if (!filter_running) return NULL;
+  /* Fall through */
+  /* VVVVVVVVVVVV */
+  case vtype_int:
+  sprintf(CS var_buffer, "%d", *(int *)(val)); /* Integer */
+  return var_buffer;
+
+  case vtype_ino:
+  sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(val))); /* Inode */
+  return var_buffer;
+
+  case vtype_gid:
+  sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(val))); /* gid */
+  return var_buffer;
+
+  case vtype_uid:
+  sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(val))); /* uid */
+  return var_buffer;
+
+  case vtype_bool:
+  sprintf(CS var_buffer, "%s", *(BOOL *)(val) ? "yes" : "no"); /* bool */
+  return var_buffer;
+
+  case vtype_stringptr:                      /* Pointer to string */
+  s = *((uschar **)(val));
+  return (s == NULL)? US"" : s;
+
+  case vtype_pid:
+  sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */
+  return var_buffer;
+
+  case vtype_load_avg:
+  sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */
+  return var_buffer;
+
+  case vtype_host_lookup:                    /* Lookup if not done so */
+  if (sender_host_name == NULL && sender_host_address != NULL &&
+      !host_lookup_failed && host_name_lookup() == OK)
+    host_build_sender_fullhost();
+  return (sender_host_name == NULL)? US"" : sender_host_name;
+
+  case vtype_localpart:                      /* Get local part from address */
+  s = *((uschar **)(val));
+  if (s == NULL) return US"";
+  domain = Ustrrchr(s, '@');
+  if (domain == NULL) return s;
+  if (domain - s > sizeof(var_buffer) - 1)
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT
+    " in string expansion", sizeof(var_buffer));
+  Ustrncpy(var_buffer, s, domain - s);
+  var_buffer[domain - s] = 0;
+  return var_buffer;
+
+  case vtype_domain:                         /* Get domain from address */
+  s = *((uschar **)(val));
+  if (s == NULL) return US"";
+  domain = Ustrrchr(s, '@');
+  return (domain == NULL)? US"" : domain + 1;
+
+  case vtype_msgheaders:
+  return find_header(NULL, exists_only, newsize, FALSE, NULL);
+
+  case vtype_msgheaders_raw:
+  return find_header(NULL, exists_only, newsize, TRUE, NULL);
+
+  case vtype_msgbody:                        /* Pointer to msgbody string */
+  case vtype_msgbody_end:                    /* Ditto, the end of the msg */
+  ss = (uschar **)(val);
+  if (*ss == NULL && deliver_datafile >= 0)  /* Read body when needed */
     {
-    case vtype_filter_int:
-    if (!filter_running) return NULL;
-    /* Fall through */
-    /* VVVVVVVVVVVV */
-    case vtype_int:
-    sprintf(CS var_buffer, "%d", *(int *)(var_table[middle].value)); /* Integer */
-    return var_buffer;
-
-    case vtype_ino:
-    sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(var_table[middle].value))); /* Inode */
-    return var_buffer;
-
-    case vtype_gid:
-    sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(var_table[middle].value))); /* gid */
-    return var_buffer;
-
-    case vtype_uid:
-    sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(var_table[middle].value))); /* uid */
-    return var_buffer;
-
-    case vtype_bool:
-    sprintf(CS var_buffer, "%s", *(BOOL *)(var_table[middle].value) ? "yes" : "no"); /* bool */
-    return var_buffer;
-
-    case vtype_stringptr:                      /* Pointer to string */
-    s = *((uschar **)(var_table[middle].value));
-    return (s == NULL)? US"" : s;
-
-    case vtype_pid:
-    sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */
-    return var_buffer;
-
-    case vtype_load_avg:
-    sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */
-    return var_buffer;
-
-    case vtype_host_lookup:                    /* Lookup if not done so */
-    if (sender_host_name == NULL && sender_host_address != NULL &&
-        !host_lookup_failed && host_name_lookup() == OK)
-      host_build_sender_fullhost();
-    return (sender_host_name == NULL)? US"" : sender_host_name;
-
-    case vtype_localpart:                      /* Get local part from address */
-    s = *((uschar **)(var_table[middle].value));
-    if (s == NULL) return US"";
-    domain = Ustrrchr(s, '@');
-    if (domain == NULL) return s;
-    if (domain - s > sizeof(var_buffer) - 1)
-      log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT
-          " in string expansion", sizeof(var_buffer));
-    Ustrncpy(var_buffer, s, domain - s);
-    var_buffer[domain - s] = 0;
-    return var_buffer;
-
-    case vtype_domain:                         /* Get domain from address */
-    s = *((uschar **)(var_table[middle].value));
-    if (s == NULL) return US"";
-    domain = Ustrrchr(s, '@');
-    return (domain == NULL)? US"" : domain + 1;
-
-    case vtype_msgheaders:
-    return find_header(NULL, exists_only, newsize, FALSE, NULL);
-
-    case vtype_msgheaders_raw:
-    return find_header(NULL, exists_only, newsize, TRUE, NULL);
-
-    case vtype_msgbody:                        /* Pointer to msgbody string */
-    case vtype_msgbody_end:                    /* Ditto, the end of the msg */
-    ss = (uschar **)(var_table[middle].value);
-    if (*ss == NULL && deliver_datafile >= 0)  /* Read body when needed */
+    uschar *body;
+    off_t start_offset = SPOOL_DATA_START_OFFSET;
+    int len = message_body_visible;
+    if (len > message_size) len = message_size;
+    *ss = body = store_malloc(len+1);
+    body[0] = 0;
+    if (vp->type == vtype_msgbody_end)
       {
-      uschar *body;
-      off_t start_offset = SPOOL_DATA_START_OFFSET;
-      int len = message_body_visible;
-      if (len > message_size) len = message_size;
-      *ss = body = store_malloc(len+1);
-      body[0] = 0;
-      if (var_table[middle].type == vtype_msgbody_end)
-        {
-        struct stat statbuf;
-        if (fstat(deliver_datafile, &statbuf) == 0)
-          {
-          start_offset = statbuf.st_size - len;
-          if (start_offset < SPOOL_DATA_START_OFFSET)
-            start_offset = SPOOL_DATA_START_OFFSET;
-          }
-        }
-      lseek(deliver_datafile, start_offset, SEEK_SET);
-      len = read(deliver_datafile, body, len);
-      if (len > 0)
-        {
-        body[len] = 0;
-        if (message_body_newlines)   /* Separate loops for efficiency */
-          {
-          while (len > 0)
-            { if (body[--len] == 0) body[len] = ' '; }
-          }
-        else
-          {
-          while (len > 0)
-            { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
-          }
-        }
+      struct stat statbuf;
+      if (fstat(deliver_datafile, &statbuf) == 0)
+    {
+    start_offset = statbuf.st_size - len;
+    if (start_offset < SPOOL_DATA_START_OFFSET)
+      start_offset = SPOOL_DATA_START_OFFSET;
+    }
+      }
+    lseek(deliver_datafile, start_offset, SEEK_SET);
+    len = read(deliver_datafile, body, len);
+    if (len > 0)
+      {
+      body[len] = 0;
+      if (message_body_newlines)   /* Separate loops for efficiency */
+    {
+    while (len > 0)
+      { if (body[--len] == 0) body[len] = ' '; }
+    }
+      else
+    {
+    while (len > 0)
+      { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
+    }
       }
-    return (*ss == NULL)? US"" : *ss;
+    }
+  return (*ss == NULL)? US"" : *ss;


-    case vtype_todbsdin:                       /* BSD inbox time of day */
-    return tod_stamp(tod_bsdin);
+  case vtype_todbsdin:                       /* BSD inbox time of day */
+  return tod_stamp(tod_bsdin);


-    case vtype_tode:                           /* Unix epoch time of day */
-    return tod_stamp(tod_epoch);
+  case vtype_tode:                           /* Unix epoch time of day */
+  return tod_stamp(tod_epoch);


-    case vtype_todel:                          /* Unix epoch/usec time of day */
-    return tod_stamp(tod_epoch_l);
+  case vtype_todel:                          /* Unix epoch/usec time of day */
+  return tod_stamp(tod_epoch_l);


-    case vtype_todf:                           /* Full time of day */
-    return tod_stamp(tod_full);
+  case vtype_todf:                           /* Full time of day */
+  return tod_stamp(tod_full);


-    case vtype_todl:                           /* Log format time of day */
-    return tod_stamp(tod_log_bare);            /* (without timezone) */
+  case vtype_todl:                           /* Log format time of day */
+  return tod_stamp(tod_log_bare);            /* (without timezone) */


-    case vtype_todzone:                        /* Time zone offset only */
-    return tod_stamp(tod_zone);
+  case vtype_todzone:                        /* Time zone offset only */
+  return tod_stamp(tod_zone);


-    case vtype_todzulu:                        /* Zulu time */
-    return tod_stamp(tod_zulu);
+  case vtype_todzulu:                        /* Zulu time */
+  return tod_stamp(tod_zulu);


-    case vtype_todlf:                          /* Log file datestamp tod */
-    return tod_stamp(tod_log_datestamp_daily);
+  case vtype_todlf:                          /* Log file datestamp tod */
+  return tod_stamp(tod_log_datestamp_daily);


-    case vtype_reply:                          /* Get reply address */
-    s = find_header(US"reply-to:", exists_only, newsize, TRUE,
-      headers_charset);
-    if (s != NULL) while (isspace(*s)) s++;
-    if (s == NULL || *s == 0)
-      {
-      *newsize = 0;                            /* For the *s==0 case */
-      s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset);
-      }
-    if (s != NULL)
-      {
-      uschar *t;
-      while (isspace(*s)) s++;
-      for (t = s; *t != 0; t++) if (*t == '\n') *t = ' ';
-      while (t > s && isspace(t[-1])) t--;
-      *t = 0;
-      }
-    return (s == NULL)? US"" : s;
+  case vtype_reply:                          /* Get reply address */
+  s = find_header(US"reply-to:", exists_only, newsize, TRUE,
+    headers_charset);
+  if (s != NULL) while (isspace(*s)) s++;
+  if (s == NULL || *s == 0)
+    {
+    *newsize = 0;                            /* For the *s==0 case */
+    s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset);
+    }
+  if (s != NULL)
+    {
+    uschar *t;
+    while (isspace(*s)) s++;
+    for (t = s; *t != 0; t++) if (*t == '\n') *t = ' ';
+    while (t > s && isspace(t[-1])) t--;
+    *t = 0;
+    }
+  return (s == NULL)? US"" : s;


-    case vtype_string_func:
-      {
-      uschar * (*fn)() = var_table[middle].value;
-      return fn();
-      }
+  case vtype_string_func:
+    {
+    uschar * (*fn)() = val;
+    return fn();
+    }


-    case vtype_pspace:
-      {
-      int inodes;
-      sprintf(CS var_buffer, "%d",
-        receive_statvfs(var_table[middle].value == (void *)TRUE, &inodes));
-      }
-    return var_buffer;
+  case vtype_pspace:
+    {
+    int inodes;
+    sprintf(CS var_buffer, "%d",
+      receive_statvfs(val == (void *)TRUE, &inodes));
+    }
+  return var_buffer;


-    case vtype_pinodes:
-      {
-      int inodes;
-      (void) receive_statvfs(var_table[middle].value == (void *)TRUE, &inodes);
-      sprintf(CS var_buffer, "%d", inodes);
-      }
-    return var_buffer;
+  case vtype_pinodes:
+    {
+    int inodes;
+    (void) receive_statvfs(val == (void *)TRUE, &inodes);
+    sprintf(CS var_buffer, "%d", inodes);
+    }
+  return var_buffer;


-    #ifndef DISABLE_DKIM
-    case vtype_dkim:
-    return dkim_exim_expand_query((int)(long)var_table[middle].value);
-    #endif
+  case vtype_cert:
+  return *(void **)val ? US"<cert>" : US"";


-    }
-  }
+  #ifndef DISABLE_DKIM
+  case vtype_dkim:
+  return dkim_exim_expand_query((int)(long)val);
+  #endif


-return NULL;          /* Unknown variable name */
+  }
 }



@@ -1790,21 +1876,8 @@ return NULL;          /* Unknown variable name */
 void
 modify_variable(uschar *name, void * value)
 {
-int first = 0;
-int last = var_table_size;
-
-while (last > first)
-  {
-  int middle = (first + last)/2;
-  int c = Ustrcmp(name, var_table[middle].name);
-
-  if (c > 0) { first = middle + 1; continue; }
-  if (c < 0) { last = middle; continue; }
-
-  /* Found an existing variable; change the item it refers to */
-  var_table[middle].value = value;
-  return;
-  }
+var_entry * vp;
+if ((vp = find_var_ent(name))) vp->value = value;
 return;          /* Unknown variable name, fail silently */
 }


@@ -5279,6 +5352,79 @@ while (*s != 0)
       continue;
       }


+#ifdef SUPPORT_TLS
+    case EITEM_CERTEXTRACT:
+      {
+      int i;
+      int field_number = 1;
+      uschar *save_lookup_value = lookup_value;
+      uschar *sub[2];
+      int save_expand_nmax =
+        save_expand_strings(save_expand_nstring, save_expand_nlength);
+
+      /* Read the field argument */
+      while (isspace(*s)) s++;
+      if (*s != '{')                    /*}*/
+    goto EXPAND_FAILED_CURLY;
+      sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+      if (!sub[0])     goto EXPAND_FAILED;        /*{*/
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+      /* strip spaces fore & aft */
+      {
+      int len;
+      int x = 0;
+      uschar *p = sub[0];
+
+      while (isspace(*p)) p++;
+      sub[0] = p;
+
+      len = Ustrlen(p);
+      while (len > 0 && isspace(p[len-1])) len--;
+      p[len] = 0;
+      }
+
+      /* inspect the cert argument */
+      while (isspace(*s)) s++;
+      if (*s != '{')                    /*}*/
+    goto EXPAND_FAILED_CURLY;
+      if (*++s != '$')
+        {
+    expand_string_message = US"second argument of \"certextract\" must "
+      "be a certificate variable";
+    goto EXPAND_FAILED;
+    }
+      sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok);
+      if (!sub[1])     goto EXPAND_FAILED;        /*{*/
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      if (skipping)
+    lookup_value = NULL;
+      else
+    {
+    lookup_value = expand_getcertele(sub[0], sub[1]);
+    if (*expand_string_message) goto EXPAND_FAILED;
+    }
+      switch(process_yesno(
+               skipping,                     /* were previously skipping */
+               lookup_value != NULL,         /* success/failure indicator */
+               save_lookup_value,            /* value to reset for string2 */
+               &s,                           /* input pointer */
+               &yield,                       /* output pointer */
+               &size,                        /* output size */
+               &ptr,                         /* output current point */
+               US"extract",                  /* condition type */
+           &resetok))
+        {
+        case 1: goto EXPAND_FAILED;          /* when all is well, the */
+        case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
+        }
+
+      restore_expand_strings(save_expand_nmax, save_expand_nstring,
+        save_expand_nlength);
+      continue;
+      }
+#endif    /*SUPPORT_TLS*/
+
     /* Handle list operations */


     case EITEM_FILTER:
diff --git a/src/src/functions.h b/src/src/functions.h
index 599afd2..8751a00 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -25,6 +25,20 @@ extern const char *
                std_dh_prime_default(void);
 extern const char *
                std_dh_prime_named(const uschar *);
+
+extern uschar * tls_cert_crl_uri(void *);
+extern uschar * tls_cert_ext_by_oid(void *, uschar *, int);
+extern uschar * tls_cert_issuer(void *);
+extern uschar * tls_cert_not_before(void *);
+extern uschar * tls_cert_not_after(void *);
+extern uschar * tls_cert_ocsp_uri(void *);
+extern uschar * tls_cert_serial_number(void *);
+extern uschar * tls_cert_signature(void *);
+extern uschar * tls_cert_signature_algorithm(void *);
+extern uschar * tls_cert_subject(void *);
+extern uschar * tls_cert_subject_altname(void *);
+extern uschar * tls_cert_version(void *);
+
 extern int     tls_client_start(int, host_item *, address_item *,
                  uschar *, uschar *, uschar *, uschar *, uschar *, uschar *,
 # ifdef EXPERIMENTAL_OCSP
@@ -32,9 +46,12 @@ extern int     tls_client_start(int, host_item *, address_item *,
 # endif
                  int, int, uschar *, uschar *);
 extern void    tls_close(BOOL, BOOL);
+extern int     tls_export_cert(uschar *, size_t, void *);
 extern int     tls_feof(void);
 extern int     tls_ferror(void);
+extern void    tls_free_cert(void *);
 extern int     tls_getc(void);
+extern int     tls_import_cert(const uschar *, void **);
 extern int     tls_read(BOOL, uschar *, size_t);
 extern int     tls_server_start(const uschar *);
 extern BOOL    tls_smtp_buffered(void);
@@ -421,4 +438,6 @@ extern void    version_init(void);


extern ssize_t write_to_fd_buf(int, const uschar *, size_t);

+/* vi: aw
+*/
 /* End of functions.h */
diff --git a/src/src/globals.c b/src/src/globals.c
index da81b8d..7b591e4 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -106,6 +106,8 @@ tls_support tls_in = {
  NULL, /* tls_cipher */
  FALSE,/* tls_on_connect */
  NULL, /* tls_on_connect_ports */
+ NULL, /* tls_ourcert */
+ NULL, /* tls_peercert */
  NULL, /* tls_peerdn */
  NULL  /* tls_sni */
 };
@@ -116,6 +118,8 @@ tls_support tls_out = {
  NULL, /* tls_cipher */
  FALSE,/* tls_on_connect */
  NULL, /* tls_on_connect_ports */
+ NULL, /* tls_ourcert */
+ NULL, /* tls_peercert */
  NULL, /* tls_peerdn */
  NULL  /* tls_sni */
 };
@@ -332,6 +336,8 @@ address_item address_defaults = {
   NULL,                 /* shadow_message */
   #ifdef SUPPORT_TLS
   NULL,                 /* cipher */
+  NULL,            /* ourcert */
+  NULL,            /* peercert */
   NULL,                 /* peerdn */
   #endif
   NULL,            /* authenticator */
diff --git a/src/src/globals.h b/src/src/globals.h
index 79bf38c..584d1bd 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -85,6 +85,8 @@ typedef struct {
   uschar *cipher;             /* Cipher used */
   BOOL    on_connect;         /* For older MTAs that don't STARTTLS */
   uschar *on_connect_ports;   /* Ports always tls-on-connect */
+  void   *ourcert;            /* Certificate we presented, binary */
+  void     *peercert;           /* Certificate of peer, binary */
   uschar *peerdn;             /* DN from peer */
   uschar *sni;                /* Server Name Indication */
 } tls_support;
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 27ff137..6810d25 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1818,6 +1818,8 @@ authenticated_by = NULL;


#ifdef SUPPORT_TLS
tls_in.cipher = tls_in.peerdn = NULL;
+tls_in.ourcert = tls_in.peercert = NULL;
+tls_in.sni = NULL;
tls_advertised = FALSE;
#endif

diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index a546b65..2006e1b 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -285,6 +285,8 @@ dkim_collect_input = FALSE;
 #ifdef SUPPORT_TLS
 tls_in.certificate_verified = FALSE;
 tls_in.cipher = NULL;
+tls_in.ourcert = NULL;
+tls_in.peercert = NULL;
 tls_in.peerdn = NULL;
 tls_in.sni = NULL;
 #endif
@@ -548,6 +550,12 @@ for (;;)
       tls_in.certificate_verified = TRUE;
     else if (Ustrncmp(p, "ls_cipher", 9) == 0)
       tls_in.cipher = string_copy(big_buffer + 12);
+#ifndef COMPILE_UTILITY
+    else if (Ustrncmp(p, "ls_ourcert", 10) == 0)
+      (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
+    else if (Ustrncmp(p, "ls_peercert", 11) == 0)
+      (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+#endif
     else if (Ustrncmp(p, "ls_peerdn", 9) == 0)
       tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
     else if (Ustrncmp(p, "ls_sni", 6) == 0)
@@ -799,4 +807,6 @@ errno = ERRNO_SPOOLFORMAT;
 return inheader? spool_read_hdrerror : spool_read_enverror;
 }


+/* vi: aw ai sw=2
+*/
/* End of spool_in.c */
diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index ce25a56..7bbd42d 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -229,9 +229,19 @@ if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts);

 #ifdef SUPPORT_TLS
 if (tls_in.certificate_verified) fprintf(f, "-tls_certificate_verified\n");
-if (tls_in.cipher != NULL)       fprintf(f, "-tls_cipher %s\n", tls_in.cipher);
-if (tls_in.peerdn != NULL)       fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
-if (tls_in.sni != NULL)         fprintf(f, "-tls_sni %s\n",    string_printing(tls_in.sni));
+if (tls_in.cipher)       fprintf(f, "-tls_cipher %s\n", tls_in.cipher);
+if (tls_in.peercert)
+  {
+  (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert);
+  fprintf(f, "-tls_peercert %s\n", CS big_buffer);
+  }
+if (tls_in.peerdn)       fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
+if (tls_in.sni)         fprintf(f, "-tls_sni %s\n",    string_printing(tls_in.sni));
+if (tls_in.ourcert)
+  {
+  (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.ourcert);
+  fprintf(f, "-tls_ourcert %s\n", CS big_buffer);
+  }
 #endif


/* To complete the envelope, write out the tree of non-recipients, followed by
diff --git a/src/src/structs.h b/src/src/structs.h
index eb43085..a6c78f4 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -540,6 +540,8 @@ typedef struct address_item {

   #ifdef SUPPORT_TLS
   uschar *cipher;                 /* Cipher used for transport */
+  void   *ourcert;                /* Certificate offered to peer, binary */
+  void   *peercert;               /* Certificate from peer, binary */
   uschar *peerdn;                 /* DN of server's certificate */
   #endif


diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index ace5963..880aaeb 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -74,19 +74,20 @@ Not handled here: global tls_channelbinding_b64.
*/

 typedef struct exim_gnutls_state {
-  gnutls_session_t session;
+  gnutls_session_t    session;
   gnutls_certificate_credentials_t x509_cred;
-  gnutls_priority_t priority_cache;
+  gnutls_priority_t    priority_cache;
   enum peer_verify_requirement verify_requirement;
-  int fd_in;
-  int fd_out;
-  BOOL peer_cert_verified;
-  BOOL trigger_sni_changes;
-  BOOL have_set_peerdn;
+  int            fd_in;
+  int            fd_out;
+  BOOL            peer_cert_verified;
+  BOOL            trigger_sni_changes;
+  BOOL            have_set_peerdn;
   const struct host_item *host;
-  uschar *peerdn;
-  uschar *ciphersuite;
-  uschar *received_sni;
+  gnutls_x509_crt_t     peercert;
+  uschar        *peerdn;
+  uschar        *ciphersuite;
+  uschar        *received_sni;


   const uschar *tls_certificate;
   const uschar *tls_privatekey;
@@ -288,12 +289,34 @@ tls_error(when, msg, state->host);
 *        Set various Exim expansion vars         *
 *************************************************/


+#define exim_gnutls_cert_err(Label) do { \
+  if (rc != GNUTLS_E_SUCCESS) { \
+    DEBUG(D_tls) debug_printf("TLS: cert problem: %s: %s\n", (Label), gnutls_strerror(rc)); \
+    return rc; } } while (0)
+
+static int
+import_cert(const gnutls_datum * cert, gnutls_x509_crt_t * crtp)
+{
+int rc;
+
+rc = gnutls_x509_crt_init(crtp);
+exim_gnutls_cert_err(US"gnutls_x509_crt_init (crt)");
+
+rc = gnutls_x509_crt_import(*crtp, cert, GNUTLS_X509_FMT_DER);
+exim_gnutls_cert_err(US"failed to import certificate [gnutls_x509_crt_import(cert)]");
+
+return rc;
+}
+
+#undef exim_gnutls_cert_err
+
+
 /* We set various Exim global variables from the state, once a session has
 been established.  With TLS callouts, may need to change this to stack
 variables, or just re-call it with the server state after client callout
 has finished.


-Make sure anything set here is inset in tls_getc().
+Make sure anything set here is unset in tls_getc().

 Sets:
   tls_active                fd
@@ -301,15 +324,17 @@ Sets:
   tls_certificate_verified  bool indicator
   tls_channelbinding_b64    for some SASL mechanisms
   tls_cipher                a string
+  tls_peercert              pointer to library internal
   tls_peerdn                a string
   tls_sni                   a (UTF-8) string
+  tls_ourcert               pointer to library internal


 Argument:
   state      the relevant exim_gnutls_state_st *
 */


static void
-extract_exim_vars_from_tls_state(exim_gnutls_state_st *state, BOOL is_server)
+extract_exim_vars_from_tls_state(exim_gnutls_state_st * state)
{
gnutls_cipher_algorithm_t cipher;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
@@ -317,18 +342,19 @@ int old_pool;
int rc;
gnutls_datum_t channel;
#endif
+tls_support * tlsp = state->tlsp;

-state->tlsp->active = state->fd_out;
+tlsp->active = state->fd_out;

cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */
-state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
+tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;

-state->tlsp->cipher = state->ciphersuite;
+tlsp->cipher = state->ciphersuite;

DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);

-state->tlsp->certificate_verified = state->peer_cert_verified;
+tlsp->certificate_verified = state->peer_cert_verified;

/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
@@ -349,8 +375,17 @@ if (rc) {
}
#endif

-state->tlsp->peerdn = state->peerdn;
-state->tlsp->sni =    state->received_sni;
+/* peercert is set in peer_status() */
+tlsp->peerdn = state->peerdn;
+tlsp->sni =    state->received_sni;
+
+/* record our certificate */
+  {
+  const gnutls_datum * cert = gnutls_certificate_get_ours(state->session);
+  gnutls_x509_crt_t crt;
+
+  tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL;
+  }
 }



@@ -1099,7 +1134,6 @@ return OK;



-
 /*************************************************
 *            Extract peer information            *
 *************************************************/
@@ -1205,11 +1239,11 @@ if (ct != GNUTLS_CRT_X509)
     if (state->verify_requirement == VERIFY_REQUIRED) { return tls_error((Label), gnutls_strerror(rc), state->host); } \
     return OK; } } while (0)


-rc = gnutls_x509_crt_init(&crt);
-exim_gnutls_peer_err(US"gnutls_x509_crt_init (crt)");
+rc = import_cert(&cert_list[0], &crt);
+exim_gnutls_peer_err(US"cert 0");
+
+state->tlsp->peercert = state->peercert = crt;

-rc = gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER);
-exim_gnutls_peer_err(US"failed to import certificate [gnutls_x509_crt_import(cert 0)]");
sz = 0;
rc = gnutls_x509_crt_get_dn(crt, NULL, &sz);
if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
@@ -1220,6 +1254,7 @@ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
dn_buf = store_get_perm(sz);
rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
+
state->peerdn = dn_buf;

 return OK;
@@ -1484,7 +1519,7 @@ mode, the fflush() happens when smtp_getc() is called. */
 if (!state->tlsp->on_connect)
   {
   smtp_printf("220 TLS go ahead\r\n");
-  fflush(smtp_out);        /*XXX JGH */
+  fflush(smtp_out);
   }


/* Now negotiate the TLS session. We put our own timer on it, since it seems
@@ -1526,22 +1561,17 @@ DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");

/* Verify after the fact */

-if (state->verify_requirement != VERIFY_NONE)
+if (  state->verify_requirement != VERIFY_NONE
+   && !verify_certificate(state, &error))
   {
-  if (!verify_certificate(state, &error))
+  if (state->verify_requirement != VERIFY_OPTIONAL)
     {
-    if (state->verify_requirement == VERIFY_OPTIONAL)
-      {
-      DEBUG(D_tls)
-        debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
-            error);
-      }
-    else
-      {
-      tls_error(US"certificate verification failed", error, NULL);
-      return FAIL;
-      }
+    tls_error(US"certificate verification failed", error, NULL);
+    return FAIL;
     }
+  DEBUG(D_tls)
+    debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
+    error);
   }


/* Figure out peer DN, and if authenticated, etc. */
@@ -1551,7 +1581,7 @@ if (rc != OK) return rc;

/* Sets various Exim expansion variables; always safe within server */

-extract_exim_vars_from_tls_state(state, TRUE);
+extract_exim_vars_from_tls_state(state);

/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize appropriately. */
@@ -1664,16 +1694,22 @@ else
}

 #ifdef EXPERIMENTAL_OCSP    /* since GnuTLS 3.1.3 */
-if (require_ocsp &&
-    (rc = gnutls_ocsp_status_request_enable_client(state->session, NULL, 0, NULL))
-    != OK)
-  return tls_error(US"cert-status-req", gnutls_strerror(rc), state->host);
+if (require_ocsp)
+  {
+  DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
+  rc = gnutls_ocsp_status_request_enable_client(state->session,
+            NULL, 0, NULL);
+  if (rc != OK)
+    return tls_error(US"cert-status-req",
+            gnutls_strerror(rc), state->host);
+  }
 #endif


gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd);
state->fd_in = fd;
state->fd_out = fd;

+DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
/* There doesn't seem to be a built-in timeout on connection. */

sigalrm_seen = FALSE;
@@ -1732,7 +1768,7 @@ if ((rc = peer_status(state)) != OK)

/* Sets various Exim expansion variables; may need to adjust for ACL callouts */

-extract_exim_vars_from_tls_state(state, FALSE);
+extract_exim_vars_from_tls_state(state);

 return OK;
 }
@@ -1830,8 +1866,9 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
     state->tlsp->active = -1;
     state->tlsp->bits = 0;
     state->tlsp->certificate_verified = FALSE;
-    tls_channelbinding_b64 = NULL;    /*XXX JGH */
+    tls_channelbinding_b64 = NULL;
     state->tlsp->cipher = NULL;
+    state->tlsp->peercert = NULL;
     state->tlsp->peerdn = NULL;


     return smtp_getc();
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index bdf910a..2f08e43 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -276,7 +276,11 @@ if (state == 0)
     txt);
   tlsp->certificate_verified = FALSE;
   *calledp = TRUE;
-  if (!*optionalp) return 0;    /* reject */
+  if (!*optionalp)
+    {
+    tlsp->peercert = X509_dup(x509ctx->current_cert);
+    return 0;                /* reject */
+    }
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
   return 1;                          /* accept */
@@ -303,6 +307,7 @@ else
   DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
     *calledp ? "" : " authenticated", txt);
   tlsp->peerdn = txt;
+  tlsp->peercert = X509_dup(x509ctx->current_cert);
   }


 /*XXX JGH: this looks bogus - we set "verified" first time through, which
@@ -1433,6 +1438,11 @@ DEBUG(D_tls)
     debug_printf("Shared ciphers: %s\n", buf);
   }


+/* Record the certificate we presented */
+ {
+ X509 * crt = SSL_get_certificate(server_ssl);
+ tls_in.ourcert = crt ? X509_dup(crt) : NULL;
+ }

 /* Only used by the server-side tls (tls_in), including tls_getc.
    Client-side (tls_out) reads (seem to?) go via
@@ -1597,12 +1607,13 @@ if (rc <= 0)
 DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");


 /* Beware anonymous ciphers which lead to server_cert being NULL */
+/*XXX server_cert is never freed... use X509_free() */
 server_cert = SSL_get_peer_certificate (client_ssl);
 if (server_cert)
   {
   tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
     CS txt, sizeof(txt));
-  tls_out.peerdn = txt;
+  tls_out.peerdn = txt;        /*XXX a static buffer... */
   }
 else
   tls_out.peerdn = NULL;
@@ -1610,6 +1621,12 @@ else
 construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
 tls_out.cipher = cipherbuf;


+/* Record the certificate we presented */
+ {
+ X509 * crt = SSL_get_certificate(client_ssl);
+ tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+ }
+
tls_out.active = fd;
return OK;
}
@@ -2250,4 +2267,6 @@ for (s=option_spec; *s != '\0'; /**/)
return TRUE;
}

+/* vi: aw ai sw=2
+*/
/* End of tls-openssl.c */
diff --git a/src/src/tls.c b/src/src/tls.c
index 9727852..ad7fe60 100644
--- a/src/src/tls.c
+++ b/src/src/tls.c
@@ -85,6 +85,7 @@ return TRUE;

#ifdef USE_GNUTLS
#include "tls-gnu.c"
+#include "tlscert-gnu.c"

#define ssl_xfer_buffer (state_server.xfer_buffer)
#define ssl_xfer_buffer_lwm (state_server.xfer_buffer_lwm)
@@ -94,6 +95,7 @@ return TRUE;

#else
#include "tls-openssl.c"
+#include "tlscert-openssl.c"
#endif


diff --git a/src/src/tlscert-gnu.c b/src/src/tlscert-gnu.c
new file mode 100644
index 0000000..649e93a
--- /dev/null
+++ b/src/src/tlscert-gnu.c
@@ -0,0 +1,325 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2014 */
+
+/* This file provides TLS/SSL support for Exim using the GnuTLS library,
+one of the available supported implementations.  This file is #included into
+tls.c when USE_GNUTLS has been set.
+*/
+
+#include <gnutls/gnutls.h>
+/* needed for cert checks in verification and DN extraction: */
+#include <gnutls/x509.h>
+/* needed to disable PKCS11 autoload unless requested */
+#if GNUTLS_VERSION_NUMBER >= 0x020c00
+# include <gnutls/pkcs11.h>
+#endif
+
+
+/*****************************************************
+*  Export/import a certificate, binary/printable
+*****************************************************/
+int
+tls_export_cert(uschar * buf, size_t buflen, void * cert)
+{
+size_t sz = buflen;
+void * reset_point = store_get(0);
+int fail = 0;
+uschar * cp;
+
+if (gnutls_x509_crt_export((gnutls_x509_crt_t)cert,
+    GNUTLS_X509_FMT_PEM, buf, &sz))
+  return 1;
+if ((cp = string_printing(buf)) != buf)
+  {
+  Ustrncpy(buf, cp, buflen);
+  if (buf[buflen-1])
+    fail = 1;
+  }
+store_reset(reset_point);
+return fail;
+}
+
+int
+tls_import_cert(const uschar * buf, void ** cert)
+{
+void * reset_point = store_get(0);
+gnutls_datum_t datum;
+gnutls_x509_crt_t crt;
+int fail = 0;
+
+gnutls_global_init();
+gnutls_x509_crt_init(&crt);
+
+datum.data = string_unprinting(US buf);
+datum.size = Ustrlen(datum.data);
+if (gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM))
+  fail = 1;
+else
+  *cert = (void *)crt;
+
+store_reset(reset_point);
+return fail;
+}
+
+void
+tls_free_cert(void * cert)
+{
+gnutls_x509_crt_deinit((gnutls_x509_crt_t) cert);
+gnutls_global_deinit();
+}
+
+/*****************************************************
+*  Certificate field extraction routines
+*****************************************************/
+static uschar *
+time_copy(time_t t)
+{
+uschar * cp = store_get(32);
+struct tm * tp = gmtime(&t);
+size_t len = strftime(CS cp, 32, "%b %e %T %Y %Z", tp);
+return len > 0 ? cp : NULL;
+}
+
+/**/
+
+uschar *
+tls_cert_issuer(void * cert)
+{
+uschar txt[256];
+size_t sz = sizeof(txt);
+return ( gnutls_x509_crt_get_issuer_dn(cert, CS txt, &sz) == 0 )
+  ? string_copy(txt) : NULL;
+}
+
+uschar *
+tls_cert_not_after(void * cert)
+{
+return time_copy(
+  gnutls_x509_crt_get_expiration_time((gnutls_x509_crt_t)cert));
+}
+
+uschar *
+tls_cert_not_before(void * cert)
+{
+return time_copy(
+  gnutls_x509_crt_get_activation_time((gnutls_x509_crt_t)cert));
+}
+
+uschar *
+tls_cert_serial_number(void * cert)
+{
+uschar bin[50], txt[150];
+size_t sz = sizeof(bin);
+uschar * sp;
+uschar * dp;
+
+if (gnutls_x509_crt_get_serial((gnutls_x509_crt_t)cert,
+    bin, &sz) || sz > sizeof(bin))
+  return NULL;
+for(dp = txt, sp = bin; sz; dp += 2, sp++, sz--)
+  sprintf(dp, "%.2x", *sp);
+for(sp = txt; sp[0]=='0' && sp[1]; ) sp++;    /* leading zeroes */
+return string_copy(sp);
+}
+
+uschar *
+tls_cert_signature(void * cert)
+{
+uschar * cp1;
+uschar * cp2;
+uschar * cp3;
+size_t len = 0;
+int ret;
+
+if ((ret = gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, cp1, &len)) !=
+    GNUTLS_E_SHORT_MEMORY_BUFFER)
+  {
+  fprintf(stderr, "%s: gs0 fail: %s\n", __FUNCTION__, gnutls_strerror(ret));
+  return NULL;
+  }
+
+cp1 = store_get(len*4+1);
+
+if (gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, cp1, &len) != 0)
+  {
+  fprintf(stderr, "%s: gs1 fail\n", __FUNCTION__);
+  return NULL;
+  }
+
+for(cp3 = cp2 = cp1+len; cp1 < cp2; cp3 += 3, cp1++)
+  sprintf(cp3, "%.2x ", *cp1);
+cp3[-1]= '\0';
+
+return cp2;
+}
+
+uschar *
+tls_cert_signature_algorithm(void * cert)
+{
+gnutls_sign_algorithm_t algo =
+  gnutls_x509_crt_get_signature_algorithm((gnutls_x509_crt_t)cert);
+return algo < 0 ? NULL : string_copy(gnutls_sign_get_name(algo));
+}
+
+uschar *
+tls_cert_subject(void * cert)
+{
+static uschar txt[256];
+size_t sz = sizeof(txt);
+return ( gnutls_x509_crt_get_dn(cert, CS txt, &sz) == 0 )
+  ? string_copy(txt) : NULL;
+}
+
+uschar *
+tls_cert_version(void * cert)
+{
+return string_sprintf("%d", gnutls_x509_crt_get_version(cert));
+}
+
+uschar *
+tls_cert_ext_by_oid(void * cert, uschar * oid, int idx)
+{
+uschar * cp1 = NULL;
+uschar * cp2;
+uschar * cp3;
+size_t siz = 0;
+unsigned int crit;
+int ret;
+
+ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
+  oid, idx, cp1, &siz, &crit);
+if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER)
+  {
+  fprintf(stderr, "%s: ge0 fail: %s\n", __FUNCTION__, gnutls_strerror(ret));
+  return NULL;
+  }
+
+cp1 = store_get(siz*4 + 1);
+
+ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
+  oid, idx, cp1, &siz, &crit);
+if (ret < 0)
+  {
+  fprintf(stderr, "%s: ge1 fail: %s\n", __FUNCTION__, gnutls_strerror(ret));
+  return NULL;
+  }
+
+/* binary data, DER encoded */
+
+/* just dump for now */
+for(cp3 = cp2 = cp1+siz; cp1 < cp2; cp3 += 3, cp1++)
+  sprintf(cp3, "%.2x ", *cp1);
+cp3[-1]= '\0';
+
+return cp2;
+}
+
+uschar *
+tls_cert_subject_altname(void * cert)
+{
+uschar * cp = NULL;
+size_t siz = 0;
+unsigned int crit;
+int ret;
+
+ret = gnutls_x509_crt_get_subject_alt_name ((gnutls_x509_crt_t)cert,
+  0, cp, &siz, &crit);
+switch(ret)
+  {
+  case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
+    return NULL;
+  case GNUTLS_E_SHORT_MEMORY_BUFFER:
+    break;
+  default:
+    expand_string_message = 
+      string_sprintf("%s: gs0 fail: %d %s\n", __FUNCTION__,
+    ret, gnutls_strerror(ret));
+    return NULL;
+  }
+
+cp = store_get(siz+1);
+ret = gnutls_x509_crt_get_subject_alt_name ((gnutls_x509_crt_t)cert,
+  0, cp, &siz, &crit);
+if (ret < 0)
+  {
+  expand_string_message = 
+    string_sprintf("%s: gs1 fail: %d %s\n", __FUNCTION__,
+      ret, gnutls_strerror(ret));
+  return NULL;
+  }
+cp[siz] = '\0';
+return cp;
+}
+
+uschar *
+tls_cert_ocsp_uri(void * cert)
+{
+#if GNUTLS_VERSION_NUMBER >= 0x030000
+gnutls_datum_t uri;
+unsigned int crit;
+int ret = gnutls_x509_crt_get_authority_info_access((gnutls_x509_crt_t)cert,
+    0, GNUTLS_IA_OCSP_URI, &uri, &crit);
+
+if (ret >= 0)
+  return string_copyn(uri.data, uri.size);
+
+if (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+  expand_string_message = 
+    string_sprintf("%s: gai fail: %d %s\n", __FUNCTION__,
+      ret, gnutls_strerror(ret));
+
+return NULL;
+
+#else
+
+expand_string_message = 
+  string_sprintf("%s: OCSP support with GnuTLS requires version 3.0.0\n",
+    __FUNCTION__);
+return NULL;
+
+#endif
+}
+
+uschar *
+tls_cert_crl_uri(void * cert)
+{
+int ret;
+uschar * cp = NULL;
+size_t siz = 0;
+
+ret = gnutls_x509_crt_get_crl_dist_points ((gnutls_x509_crt_t)cert,
+  0, cp, &siz, NULL, NULL);
+switch(ret)
+  {
+  case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
+    return NULL;
+  case GNUTLS_E_SHORT_MEMORY_BUFFER:
+    break;
+  default:
+    expand_string_message = 
+      string_sprintf("%s: gc0 fail: %d %s\n", __FUNCTION__,
+    ret, gnutls_strerror(ret));
+    return NULL;
+  }
+
+cp = store_get(siz+1);
+ret = gnutls_x509_crt_get_crl_dist_points ((gnutls_x509_crt_t)cert,
+  0, cp, &siz, NULL, NULL);
+if (ret < 0)
+  {
+  expand_string_message = 
+    string_sprintf("%s: gs1 fail: %d %s\n", __FUNCTION__,
+      ret, gnutls_strerror(ret));
+  return NULL;
+  }
+cp[siz] = '\0';
+return cp;
+}
+
+
+/* vi: aw ai sw=2
+*/
+/* End of tlscert-gnu.c */
diff --git a/src/src/tlscert-openssl.c b/src/src/tlscert-openssl.c
new file mode 100644
index 0000000..008cf54
--- /dev/null
+++ b/src/src/tlscert-openssl.c
@@ -0,0 +1,293 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2014 */
+
+/* This module provides TLS (aka SSL) support for Exim using the OpenSSL
+library. It is #included into the tls.c file when that library is used.
+*/
+
+
+/* Heading stuff */
+
+#include <openssl/lhash.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+
+/*****************************************************
+*  Export/import a certificate, binary/printable
+*****************************************************/
+int
+tls_export_cert(uschar * buf, size_t buflen, void * cert)
+{
+BIO * bp = BIO_new(BIO_s_mem());
+int fail;
+
+if ((fail = PEM_write_bio_X509(bp, (X509 *)cert) ? 0 : 1))
+  log_write(0, LOG_MAIN, "TLS error in certificate export: %s",
+    ERR_error_string(ERR_get_error(), NULL));
+else
+  {
+  char * cp = CS buf;
+  int n;
+  buflen -= 2;
+  for(;;)
+    {
+    if ((n = BIO_gets(bp, cp, (int)buflen)) <= 0) break;
+    cp += n+1;
+    buflen -= n+1;
+    cp[-2] = '\\'; cp[-1] = 'n'; /* newline->"\n" */
+    }                 /* compat with string_printing() */
+  *cp = '\0';
+  }
+
+BIO_free(bp);
+return fail;
+}
+
+int
+tls_import_cert(const uschar * buf, void ** cert)
+{
+void * reset_point = store_get(0);
+const uschar * cp = string_unprinting(US buf);
+BIO * bp;
+X509 * x;
+
+bp = BIO_new_mem_buf(US cp, -1);
+x = PEM_read_bio_X509(bp, NULL, 0, NULL);
+int fail = 0;
+if (!x)
+  fail = 1;
+else
+  *cert = (void *)x;
+BIO_free(bp);
+store_reset(reset_point);
+return fail;
+}
+
+void
+tls_free_cert(void * cert)
+{
+X509_free((X509 *)cert);
+}
+
+/*****************************************************
+*  Certificate field extraction routines
+*****************************************************/
+static uschar *
+bio_string_copy(BIO * bp, int len)
+{
+uschar * cp = "";
+len = len > 0 ? (int) BIO_get_mem_data(bp, &cp) : 0;
+cp = string_copyn(cp, len);
+BIO_free(bp);
+return cp;
+}
+
+static uschar *
+asn1_time_copy(const ASN1_TIME * time)
+{
+BIO * bp = BIO_new(BIO_s_mem());
+int len = ASN1_TIME_print(bp, time);
+return bio_string_copy(bp, len);
+}
+
+static uschar *
+x509_name_copy(X509_NAME * name)
+{
+BIO * bp = BIO_new(BIO_s_mem());
+int len_good =
+  X509_NAME_print_ex(bp, name, 0, XN_FLAG_RFC2253) >= 0
+  ? 1 : 0;
+return bio_string_copy(bp, len_good);
+}
+
+/**/
+
+uschar *
+tls_cert_issuer(void * cert)
+{
+return x509_name_copy(X509_get_issuer_name((X509 *)cert));
+}
+
+uschar *
+tls_cert_not_before(void * cert)
+{
+return asn1_time_copy(X509_get_notBefore((X509 *)cert));
+}
+
+uschar *
+tls_cert_not_after(void * cert)
+{
+return asn1_time_copy(X509_get_notAfter((X509 *)cert));
+}
+
+uschar *
+tls_cert_serial_number(void * cert)
+{
+uschar txt[256];
+BIO * bp = BIO_new(BIO_s_mem());
+int len = i2a_ASN1_INTEGER(bp, X509_get_serialNumber((X509 *)cert));
+
+if (len < sizeof(txt))
+  BIO_read(bp, txt, len);
+else
+  len = 0;
+BIO_free(bp);
+return string_copynlc(txt, len);    /* lowercase */
+}
+
+uschar *
+tls_cert_signature(void * cert)
+{
+BIO * bp = BIO_new(BIO_s_mem());
+uschar * cp = NULL;
+
+if (X509_print_ex(bp, (X509 *)cert, 0,
+  X509_FLAG_NO_HEADER | X509_FLAG_NO_VERSION | X509_FLAG_NO_SERIAL | 
+  X509_FLAG_NO_SIGNAME | X509_FLAG_NO_ISSUER | X509_FLAG_NO_VALIDITY | 
+  X509_FLAG_NO_SUBJECT | X509_FLAG_NO_PUBKEY | X509_FLAG_NO_EXTENSIONS | 
+  /* X509_FLAG_NO_SIGDUMP is the missing one */
+  X509_FLAG_NO_AUX) == 1)
+  {
+  long len = BIO_get_mem_data(bp, &cp);
+  cp = string_copyn(cp, len);
+  }
+BIO_free(bp);
+return cp;
+}
+
+uschar *
+tls_cert_signature_algorithm(void * cert)
+{
+return string_copy(OBJ_nid2ln(X509_get_signature_type((X509 *)cert)));
+}
+
+uschar *
+tls_cert_subject(void * cert)
+{
+return x509_name_copy(X509_get_subject_name((X509 *)cert));
+}
+
+uschar *
+tls_cert_version(void * cert)
+{
+return string_sprintf("%d", X509_get_version((X509 *)cert));
+}
+
+uschar *
+tls_cert_ext_by_oid(void * cert, uschar * oid, int idx)
+{
+int nid = OBJ_create(oid, "", "");
+int nidx = X509_get_ext_by_NID((X509 *)cert, nid, idx);
+X509_EXTENSION * ex = X509_get_ext((X509 *)cert, nidx);
+ASN1_OCTET_STRING * adata = X509_EXTENSION_get_data(ex);
+BIO * bp = BIO_new(BIO_s_mem());
+long len;
+uschar * cp1;
+uschar * cp2;
+uschar * cp3;
+
+M_ASN1_OCTET_STRING_print(bp, adata);
+/* binary data, DER encoded */
+
+/* just dump for now */
+len = BIO_get_mem_data(bp, &cp1);
+cp3 = cp2 = store_get(len*3+1);
+
+while(len)
+  {
+  sprintf(cp2, "%.2x ", *cp1++);
+  cp2 += 3;
+  len--;
+  }
+cp2[-1] = '\0';
+
+return cp3;
+}
+
+uschar *
+tls_cert_subject_altname(void * cert)
+{
+uschar * cp;
+STACK_OF(GENERAL_NAME) * san = (STACK_OF(GENERAL_NAME) *)
+  X509_get_ext_d2i((X509 *)cert, NID_subject_alt_name, NULL, NULL);
+
+if (!san) return NULL;
+
+while (sk_GENERAL_NAME_num(san) > 0)
+  {
+  GENERAL_NAME * namePart = sk_GENERAL_NAME_pop(san);
+  switch (namePart->type)
+    {
+    case GEN_URI:
+      cp = string_sprintf("URI=%s",
+        ASN1_STRING_data(namePart->d.uniformResourceIdentifier));
+      return cp;
+    case GEN_EMAIL:
+      cp = string_sprintf("email=%s",
+        ASN1_STRING_data(namePart->d.rfc822Name));
+      return cp;
+    default:
+      cp = string_sprintf("Unrecognisable");
+      return cp;
+    }
+  }
+
+/* sk_GENERAL_NAME_pop_free(gen_names, GENERAL_NAME_free);  ??? */
+return cp;
+}
+
+uschar *
+tls_cert_ocsp_uri(void * cert)
+{
+STACK_OF(ACCESS_DESCRIPTION) * ads = (STACK_OF(ACCESS_DESCRIPTION) *)
+  X509_get_ext_d2i((X509 *)cert, NID_info_access, NULL, NULL);
+int adsnum = sk_ACCESS_DESCRIPTION_num(ads);
+int i;
+
+for (i = 0; i < adsnum; i++)
+  {
+  ACCESS_DESCRIPTION * ad = sk_ACCESS_DESCRIPTION_value(ads, i);
+
+  if (ad && OBJ_obj2nid(ad->method) == NID_ad_OCSP)
+    return string_copy( ASN1_STRING_data(ad->location->d.ia5) );
+  }
+
+return NULL;
+}
+
+uschar *
+tls_cert_crl_uri(void * cert)
+{
+STACK_OF(DIST_POINT) * dps = (STACK_OF(DIST_POINT) *)
+  X509_get_ext_d2i((X509 *)cert,  NID_crl_distribution_points,
+    NULL, NULL);
+DIST_POINT * dp;
+int dpsnum = sk_DIST_POINT_num(dps);
+int i;
+
+if (dps) for (i = 0; i < dpsnum; i++)
+  if ((dp = sk_DIST_POINT_value(dps, i)))
+    {
+    STACK_OF(GENERAL_NAME) * names = dp->distpoint->name.fullname;
+    GENERAL_NAME * np;
+    int nnum = sk_GENERAL_NAME_num(names);
+    int j;
+
+    for (j = 0; j < nnum; j++)
+      if (  (np = sk_GENERAL_NAME_value(names, j))
+     && np->type == GEN_URI
+     )
+    return string_copy(ASN1_STRING_data(
+      np->d.uniformResourceIdentifier));
+    }
+return NULL;
+}
+
+/* vi: aw ai sw=2
+*/
+/* End of tlscert-openssl.c */
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 9e0ab15..16c2b60 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -1221,6 +1221,8 @@ outblock.authenticating = FALSE;


 tls_out.bits = 0;
 tls_out.cipher = NULL;    /* the one we may use for this transport */
+tls_out.ourcert = NULL;
+tls_out.peercert = NULL;
 tls_out.peerdn = NULL;
 #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
 tls_out.sni = NULL;
@@ -1480,6 +1482,8 @@ if (tls_offered && !suppress_tls &&
       if (addr->transport_return == PENDING_DEFER)
         {
         addr->cipher = tls_out.cipher;
+        addr->ourcert = tls_out.ourcert;
+        addr->peercert = tls_out.peercert;
         addr->peerdn = tls_out.peerdn;
         }
       }
@@ -2417,8 +2421,6 @@ tls_close(FALSE, TRUE);
 #endif


/* Close the socket, and return the appropriate value, first setting
-continue_transport and continue_hostname NULL to prevent any other addresses
-that may include the host from trying to re-use a continuation socket. This
works because the NULL setting is passed back to the calling process, and
remote_max_parallel is forced to 1 when delivering over an existing connection,

@@ -2519,6 +2521,8 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
addr->message = NULL;
#ifdef SUPPORT_TLS
addr->cipher = NULL;
+ addr->ourcert = NULL;
+ addr->peercert = NULL;
addr->peerdn = NULL;
#endif
}
diff --git a/test/confs/2002 b/test/confs/2002
index e8358da..b4d0348 100644
--- a/test/confs/2002
+++ b/test/confs/2002
@@ -20,11 +20,11 @@ queue_run_in_order

tls_advertise_hosts = 127.0.0.1 : HOSTIPV4

-tls_certificate = DIR/aux-fixed/cert1
-tls_privatekey = DIR/aux-fixed/cert1
+tls_certificate = DIR/aux-fixed/exim-ca/example.com/server1.example.com/server1.example.com.pem
+tls_privatekey = DIR/aux-fixed/exim-ca/example.com/server1.example.com/server1.example.com.unlocked.key

tls_verify_hosts = HOSTIPV4
-tls_verify_certificates = DIR/aux-fixed/cert2
+tls_verify_certificates = DIR/aux-fixed/exim-ca/example.com/server2.example.com/ca_chain.pem


 # ------ ACL ------
@@ -41,7 +41,23 @@ check_recipient:
                       DHE_RSA_AES_256_CBC_SHA1 : \
                       DHE_RSA_3DES_EDE_CBC_SHA : \
                       RSA_AES_256_CBC_SHA1
-  accept
+  warn    logwrite =  ${if def:tls_in_ourcert \
+        {Our cert SN: <${certextract{subject}{$tls_in_ourcert}}>} \
+        {We did not present a cert}}
+  accept  condition = ${if !def:tls_in_peercert}
+      logwrite =  Peer did not present a cert
+  accept  logwrite =  Peer cert:
+          logwrite =  ver ${certextract {version}{$tls_in_peercert}}
+      logwrite =  SR  <${certextract {serial_number}{$tls_in_peercert}}>
+      logwrite =  SN  <${certextract {subject}    {$tls_in_peercert}}>
+          logwrite =  IN  <${certextract {issuer}    {$tls_in_peercert}}>
+          logwrite =  NB  <${certextract {notbefore}    {$tls_in_peercert}}>
+          logwrite =  NA  <${certextract {notafter}    {$tls_in_peercert}}>
+          logwrite =  SA  <${certextract {signature_algorithm}{$tls_in_peercert}}>
+          logwrite =  SG  <${certextract {signature}    {$tls_in_peercert}}>
+      logwrite =       ${certextract {subject_altname}{$tls_in_peercert} {SAN <$value>}{(no SAN)}}
+#      logwrite =       ${certextract {ocsp_uri}    {$tls_in_peercert} {OCU <$value>}{(no OCU)}}
+      logwrite =       ${certextract {crl_uri}    {$tls_in_peercert} {CRU <$value>}{(no CRU)}}



# ----- Routers -----
diff --git a/test/confs/2102 b/test/confs/2102
index 7f5771c..5332801 100644
--- a/test/confs/2102
+++ b/test/confs/2102
@@ -20,11 +20,11 @@ queue_run_in_order

tls_advertise_hosts = 127.0.0.1 : HOSTIPV4

-tls_certificate = DIR/aux-fixed/cert1
-tls_privatekey = DIR/aux-fixed/cert1
+tls_certificate = DIR/aux-fixed/exim-ca/example.com/server1.example.com/server1.example.com.pem
+tls_privatekey = DIR/aux-fixed/exim-ca/example.com/server1.example.com/server1.example.com.unlocked.key

tls_verify_hosts = HOSTIPV4
-tls_verify_certificates = DIR/aux-fixed/cert2
+tls_verify_certificates = DIR/aux-fixed/exim-ca/example.com/server2.example.com/ca_chain.pem


 # ------ ACL ------
@@ -42,7 +42,23 @@ check_recipient:
               DHE-RSA-AES256-GCM-SHA384 : \
                       DHE_RSA_AES_256_CBC_SHA1 : \
                       DHE_RSA_3DES_EDE_CBC_SHA
-  accept
+  warn    logwrite =  ${if def:tls_in_ourcert \
+        {Our cert SN: <${certextract{subject}{$tls_in_ourcert}}>} \
+        {We did not present a cert}}
+  accept  condition = ${if !def:tls_in_peercert}
+      logwrite =  Peer did not present a cert
+  accept  logwrite =  Peer cert:
+          logwrite =  ver ${certextract {version}{$tls_in_peercert}}
+      logwrite =  SR  <${certextract {serial_number}{$tls_in_peercert}}>
+      logwrite =  SN  <${certextract {subject}    {$tls_in_peercert}}>
+          logwrite =  IN  <${certextract {issuer}    {$tls_in_peercert}}>
+          logwrite =  NB  <${certextract {notbefore}    {$tls_in_peercert}}>
+          logwrite =  NA  <${certextract {notafter}    {$tls_in_peercert}}>
+          logwrite =  SA  <${certextract {signature_algorithm}{$tls_in_peercert}}>
+          logwrite =  SG  <${certextract {signature}    {$tls_in_peercert}}>
+      logwrite =       ${certextract {subject_altname}{$tls_in_peercert} {SAN <$value>}{(no SAN)}}
+      logwrite =       ${certextract {ocsp_uri}    {$tls_in_peercert} {OCU <$value>}{(no OCU)}}
+      logwrite =       ${certextract {crl_uri}    {$tls_in_peercert} {CRU <$value>}{(no CRU)}}



 # ----- Routers -----
diff --git a/test/confs/5750 b/test/confs/5750
new file mode 100644
index 0000000..a4762bd
--- /dev/null
+++ b/test/confs/5750
@@ -0,0 +1,95 @@
+# Exim test configuration 5750 (dup of 5760)
+# $tls_out_peercert - GnuTLS
+
+SERVER=
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+rfc1413_query_timeout = 0s
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/SERVER%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+
+log_selector =  +tls_peerdn
+
+queue_only
+queue_run_in_order
+
+tls_advertise_hosts = *
+
+tls_certificate = DIR/aux-fixed/exim-ca/example.com/server1.example.com/server1.example.com.pem
+tls_privatekey = DIR/aux-fixed/exim-ca/example.com/server1.example.com/server1.example.com.unlocked.key
+
+tls_verify_hosts = *
+tls_verify_certificates = DIR/aux-fixed/exim-ca/example.com/server2.example.com/ca_chain.pem
+
+#
+
+begin acl
+logger:
+  warn   logwrite =  $acl_arg1 $tpda_delivery_local_part
+  warn   logwrite =  ${if !def:tls_out_ourcert \
+        {NO CLENT CERT presented} \
+        {Our cert SN: ${certextract{subject}{$tls_out_ourcert}}}}
+  accept condition = ${if !def:tls_out_peercert}
+     logwrite =  No Peer cert
+  accept logwrite = Peer cert:
+     logwrite =  ver <${certextract {version}    {$tls_out_peercert}}>
+     logwrite =  SN  <${certextract {subject}    {$tls_out_peercert}}>
+         logwrite =  IN  <${certextract {issuer}    {$tls_out_peercert}}>
+         logwrite =  NB  <${certextract {notbefore}    {$tls_out_peercert}}>
+         logwrite =  NA  <${certextract {notafter}    {$tls_out_peercert}}>
+         logwrite =  SA  <${certextract {signature_algorithm}{$tls_out_peercert}}>
+         logwrite =  SG  <${certextract {signature}    {$tls_out_peercert}}>
+     logwrite =       ${certextract {subject_altname}{$tls_out_peercert}{SAN <$value>}{(no SAN)}}
+     logwrite =       ${certextract {ocsp_uri}    {$tls_out_peercert} {OCU <$value>}{(no OCU)}}
+     logwrite =       ${certextract {crl_uri}    {$tls_out_peercert} {CRU <$value>}{(no CRU)}}
+
+
+# ----- Routers -----
+
+begin routers
+
+client:
+  driver = accept
+  condition = ${if eq {SERVER}{server}{no}{yes}}
+  retry_use_local_part
+  transport = send_to_server
+
+
+# ----- Transports -----
+
+begin transports
+
+send_to_server:
+  driver = smtp
+  allow_localhost
+  hosts = 127.0.0.1
+  port = PORT_D
+
+  tls_certificate = DIR/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.pem
+  tls_privatekey = DIR/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.unlocked.key
+
+  tls_verify_certificates = DIR/aux-fixed/exim-ca/\
+       ${if eq {$local_part}{good}\
+{example.com/server1.example.com/ca_chain.pem}\
+{example.net/server1.example.net/ca_chain.pem}}
+
+  tpda_delivery_action =   ${acl {logger} {delivery} {$domain} }
+  tpda_host_defer_action = ${acl {logger} {deferral} {$domain} }
+
+# ----- Retry -----
+
+
+begin retry
+
+* * F,5d,10s
+
+
+# End
diff --git a/test/confs/5760 b/test/confs/5760
new file mode 100644
index 0000000..0e11ab0
--- /dev/null
+++ b/test/confs/5760
@@ -0,0 +1,95 @@
+# Exim test configuration 5760 (dup of 5750)
+# $tls_out_peercert - OpenSSL
+
+SERVER=
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+rfc1413_query_timeout = 0s
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/SERVER%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+
+log_selector =  +tls_peerdn
+
+queue_only
+queue_run_in_order
+
+tls_advertise_hosts = *
+
+tls_certificate = DIR/aux-fixed/exim-ca/example.com/server1.example.com/server1.example.com.pem
+tls_privatekey = DIR/aux-fixed/exim-ca/example.com/server1.example.com/server1.example.com.unlocked.key
+
+tls_verify_hosts = *
+tls_verify_certificates = DIR/aux-fixed/exim-ca/example.com/server2.example.com/ca_chain.pem
+
+#
+
+begin acl
+logger:
+  warn   logwrite =  $acl_arg1 $tpda_delivery_local_part
+  warn   logwrite =  ${if !def:tls_out_ourcert \
+        {NO CLENT CERT presented} \
+        {Our cert SN: ${certextract{subject}{$tls_out_ourcert}}}}
+  accept condition = ${if !def:tls_out_peercert}
+     logwrite =  No Peer cert
+  accept logwrite = Peer cert:
+     logwrite =  ver <${certextract {version}       {$tls_out_peercert}}>
+     logwrite =  SN  <${certextract {subject}       {$tls_out_peercert}}>
+         logwrite =  IN  <${certextract {issuer}    {$tls_out_peercert}}>
+         logwrite =  NB  <${certextract {notbefore}    {$tls_out_peercert}}>
+         logwrite =  NA  <${certextract {notafter}    {$tls_out_peercert}}>
+         logwrite =  SA  <${certextract {signature_algorithm}{$tls_out_peercert}}>
+         logwrite =  SG  <${certextract {signature}    {$tls_out_peercert}}>
+     logwrite =       ${certextract {subject_altname}{$tls_out_peercert}{SAN <$value>}{(no SAN)}}
+     logwrite =       ${certextract {ocsp_uri}    {$tls_out_peercert} {OCU <$value>}{(no OCU)}}
+     logwrite =       ${certextract {crl_uri}    {$tls_out_peercert} {CRU <$value>}{(no CRU)}}
+
+
+# ----- Routers -----
+
+begin routers
+
+client:
+  driver = accept
+  condition = ${if eq {SERVER}{server}{no}{yes}}
+  retry_use_local_part
+  transport = send_to_server
+
+
+# ----- Transports -----
+
+begin transports
+
+send_to_server:
+  driver = smtp
+  allow_localhost
+  hosts = 127.0.0.1
+  port = PORT_D
+
+  tls_certificate = DIR/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.pem
+  tls_privatekey = DIR/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.unlocked.key
+
+  tls_verify_certificates = DIR/aux-fixed/exim-ca/\
+       ${if eq {$local_part}{good}\
+{example.com/server1.example.com/ca_chain.pem}\
+{example.net/server1.example.net/ca_chain.pem}}
+
+  tpda_delivery_action =   ${acl {logger} {delivery} {$domain} }
+  tpda_host_defer_action = ${acl {logger} {deferral} {$domain} }
+
+# ----- Retry -----
+
+
+begin retry
+
+* * F,5d,10s
+
+
+# End
diff --git a/test/log/2002 b/test/log/2002
index 7744955..e2777b4 100644
--- a/test/log/2002
+++ b/test/log/2002
@@ -1,8 +1,24 @@
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 Our cert SN: <CN=server1.example.com>
+1999-03-02 09:44:33 Peer did not present a cert
 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? H=[127.0.0.1] P=smtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 S=sss
+1999-03-02 09:44:33 Our cert SN: <CN=server1.example.com>
+1999-03-02 09:44:33 Peer did not present a cert
 1999-03-02 09:44:33 10HmaY-0005vi-00 <= "name with spaces"@??? H=[127.0.0.1] P=smtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 S=sss
 1999-03-02 09:44:33 TLS error on connection from (rhu.barb) [ip4.ip4.ip4.ip4] (gnutls_handshake): The peer did not send any certificate.
-1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? H=[ip4.ip4.ip4.ip4] P=smtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 DN="C=UK,O=The Exim Maintainers,OU=Test Suite,CN=Phil Pennock" S=sss
+1999-03-02 09:44:33 Our cert SN: <CN=server1.example.com>
+1999-03-02 09:44:33 Peer cert:
+1999-03-02 09:44:33 ver 3
+1999-03-02 09:44:33 SR  <c9>
+1999-03-02 09:44:33 SN  <CN=server2.example.com>
+1999-03-02 09:44:33 IN  <O=example.com,CN=clica Signing Cert>
+1999-03-02 09:44:33 NB  <Nov  1 12:34:06 2012 GMT>
+1999-03-02 09:44:33 NA  <Jan  1 12:34:06 2038 GMT>
+1999-03-02 09:44:33 SA  <RSA-SHA1>
+1999-03-02 09:44:33 SG  <6c 37 41 26 4d 5d f4 b5 31 10 67 ca fb 64 b6 22 98 62 f7 1e 95 7b 6c e6 74 47 21 f4 5e 89 36 3e b9 9c 8a c5 52 bb c4 af 12 93 26 3b d7 3d e0 56 71 1e 1d 21 20 02 ed f0 4e d5 5e 45 42 fd 3c 38 41 54 83 86 0b 3b bf c5 47 39 ff 15 ea 93 dc fd c7 3d 18 58 59 ca dd 2a d8 b9 f9 2f b9 76 93 f4 ae e3 91 56 80 2f 8c 04 2f ad 57 ef d2 51 19 f4 b4 ef 32 9c ac 3a 7c 0d b8 39 db b1 e3 30 73 1a>
+1999-03-02 09:44:33 SAN <server2.example.com>
+1999-03-02 09:44:33 CRU <http://crl.example.com/latest.crl>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? H=[ip4.ip4.ip4.ip4] P=smtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 DN="CN=server2.example.com" S=sss
 1999-03-02 09:44:33 Start queue run: pid=pppp -qf
 1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER <CALLER@???> R=abc T=local_delivery
 1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
diff --git a/test/log/2102 b/test/log/2102
index da4ee49..6e0713f 100644
--- a/test/log/2102
+++ b/test/log/2102
@@ -1,9 +1,26 @@
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 Our cert SN: <CN=server1.example.com>
+1999-03-02 09:44:33 Peer did not present a cert
 1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? H=[127.0.0.1] P=smtps X=TLSv1:AES256-SHA:256 S=sss
+1999-03-02 09:44:33 Our cert SN: <CN=server1.example.com>
+1999-03-02 09:44:33 Peer did not present a cert
 1999-03-02 09:44:33 10HmaY-0005vi-00 <= "name with spaces"@??? H=[127.0.0.1] P=smtps X=TLSv1:AES256-SHA:256 S=sss
 1999-03-02 09:44:33 TLS error on connection from (rhu.barb) [ip4.ip4.ip4.ip4] (SSL_accept): error: <<detail omitted>>
 1999-03-02 09:44:33 TLS client disconnected cleanly (rejected our certificate?)
-1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? H=[ip4.ip4.ip4.ip4] P=smtps X=TLSv1:AES256-SHA:256 DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" S=sss
+1999-03-02 09:44:33 Our cert SN: <CN=server1.example.com>
+1999-03-02 09:44:33 Peer cert:
+1999-03-02 09:44:33 ver 2
+1999-03-02 09:44:33 SR  <c9>
+1999-03-02 09:44:33 SN  <CN=server2.example.com>
+1999-03-02 09:44:33 IN  <CN=clica Signing Cert,O=example.com>
+1999-03-02 09:44:33 NB  <Nov  1 12:34:06 2012 GMT>
+1999-03-02 09:44:33 NA  <Jan  1 12:34:06 2038 GMT>
+1999-03-02 09:44:33 SA  <undefined>
+1999-03-02 09:44:33 SG  <    Signature Algorithm: sha1WithRSAEncryption\n         6c:37:41:26:4d:5d:f4:b5:31:10:67:ca:fb:64:b6:22:98:62:\n         f7:1e:95:7b:6c:e6:74:47:21:f4:5e:89:36:3e:b9:9c:8a:c5:\n         52:bb:c4:af:12:93:26:3b:d7:3d:e0:56:71:1e:1d:21:20:02:\n         ed:f0:4e:d5:5e:45:42:fd:3c:38:41:54:83:86:0b:3b:bf:c5:\n         47:39:ff:15:ea:93:dc:fd:c7:3d:18:58:59:ca:dd:2a:d8:b9:\n         f9:2f:b9:76:93:f4:ae:e3:91:56:80:2f:8c:04:2f:ad:57:ef:\n         d2:51:19:f4:b4:ef:32:9c:ac:3a:7c:0d:b8:39:db:b1:e3:30:\n         73:1a\n>
+1999-03-02 09:44:33 SAN <Unrecognisable>
+1999-03-02 09:44:33 OCU <http://oscp/example.com/>
+1999-03-02 09:44:33 CRU <http://crl.example.com/latest.crl>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? H=[ip4.ip4.ip4.ip4] P=smtps X=TLSv1:AES256-SHA:256 DN="/CN=server2.example.com" S=sss
 1999-03-02 09:44:33 Start queue run: pid=pppp -qf
 1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER <CALLER@???> R=abc T=local_delivery
 1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
diff --git a/test/log/5750 b/test/log/5750
new file mode 100644
index 0000000..8c98b5b
--- /dev/null
+++ b/test/log/5750
@@ -0,0 +1,46 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 Start queue run: pid=pppp -qf
+1999-03-02 09:44:33 10HmaX-0005vi-00 TLS error on connection to 127.0.0.1 [127.0.0.1] (certificate verification failed): certificate invalid
+1999-03-02 09:44:33 10HmaX-0005vi-00 deferral bad
+1999-03-02 09:44:33 10HmaX-0005vi-00 NO CLENT CERT presented
+1999-03-02 09:44:33 10HmaX-0005vi-00 Peer cert:
+1999-03-02 09:44:33 10HmaX-0005vi-00 ver <3>
+1999-03-02 09:44:33 10HmaX-0005vi-00 SN  <CN=server1.example.com>
+1999-03-02 09:44:33 10HmaX-0005vi-00 IN  <O=example.com,CN=clica Signing Cert>
+1999-03-02 09:44:33 10HmaX-0005vi-00 NB  <Nov  1 12:34:05 2012 GMT>
+1999-03-02 09:44:33 10HmaX-0005vi-00 NA  <Jan  1 12:34:05 2038 GMT>
+1999-03-02 09:44:33 10HmaX-0005vi-00 SA  <RSA-SHA1>
+1999-03-02 09:44:33 10HmaX-0005vi-00 SG  <56 3a a4 3c cb eb b8 27 c2 90 08 74 13 88 dc 48 c6 b5 2c e5 26 be 5b 91 d4 67 e7 3c 49 12 d7 47 30 df 98 db 58 ed 18 a8 7d 4b db 97 48 f5 5c 7f 70 b9 37 63 33 f1 24 62 72 92 60 f5 6e da b6 bc 73 c8 c2 dc d6 95 9a bd 16 16 a2 ef 0a f1 d7 41 68 f6 ad 98 5a d0 ff d9 1b 51 9f 59 ce 2f 3d 84 d0 ee e8 2b eb 9b 32 1a 0e 02 3e cc 30 89 44 09 2a 75 81 46 a7 b6 ed 7d 41 eb 5a 63 fa 9c 58 ef>
+1999-03-02 09:44:33 10HmaX-0005vi-00 SAN <alternatename.server1.example.com>
+1999-03-02 09:44:33 10HmaX-0005vi-00 OCU <http://oscp/example.com/>
+1999-03-02 09:44:33 10HmaX-0005vi-00 CRU <http://crl.example.com/latest.crl>
+1999-03-02 09:44:33 10HmaX-0005vi-00 TLS session failure: delivering unencrypted to 127.0.0.1 [127.0.0.1] (not in hosts_require_tls)
+1999-03-02 09:44:33 10HmaX-0005vi-00 => bad@??? R=client T=send_to_server H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmaZ-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 delivery bad
+1999-03-02 09:44:33 10HmaX-0005vi-00 NO CLENT CERT presented
+1999-03-02 09:44:33 10HmaX-0005vi-00 No Peer cert
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 => good@??? R=client T=send_to_server H=127.0.0.1 [127.0.0.1] X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 DN="CN=server1.example.com" C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaY-0005vi-00 delivery good
+1999-03-02 09:44:33 10HmaY-0005vi-00 Our cert SN: CN=server2.example.com
+1999-03-02 09:44:33 10HmaY-0005vi-00 Peer cert:
+1999-03-02 09:44:33 10HmaY-0005vi-00 ver <3>
+1999-03-02 09:44:33 10HmaY-0005vi-00 SN  <CN=server1.example.com>
+1999-03-02 09:44:33 10HmaY-0005vi-00 IN  <O=example.com,CN=clica Signing Cert>
+1999-03-02 09:44:33 10HmaY-0005vi-00 NB  <Nov  1 12:34:05 2012 GMT>
+1999-03-02 09:44:33 10HmaY-0005vi-00 NA  <Jan  1 12:34:05 2038 GMT>
+1999-03-02 09:44:33 10HmaY-0005vi-00 SA  <RSA-SHA1>
+1999-03-02 09:44:33 10HmaY-0005vi-00 SG  <56 3a a4 3c cb eb b8 27 c2 90 08 74 13 88 dc 48 c6 b5 2c e5 26 be 5b 91 d4 67 e7 3c 49 12 d7 47 30 df 98 db 58 ed 18 a8 7d 4b db 97 48 f5 5c 7f 70 b9 37 63 33 f1 24 62 72 92 60 f5 6e da b6 bc 73 c8 c2 dc d6 95 9a bd 16 16 a2 ef 0a f1 d7 41 68 f6 ad 98 5a d0 ff d9 1b 51 9f 59 ce 2f 3d 84 d0 ee e8 2b eb 9b 32 1a 0e 02 3e cc 30 89 44 09 2a 75 81 46 a7 b6 ed 7d 41 eb 5a 63 fa 9c 58 ef>
+1999-03-02 09:44:33 10HmaY-0005vi-00 SAN <alternatename.server1.example.com>
+1999-03-02 09:44:33 10HmaY-0005vi-00 OCU <http://oscp/example.com/>
+1999-03-02 09:44:33 10HmaY-0005vi-00 CRU <http://crl.example.com/latest.crl>
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp -qf
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 TLS error on connection from localhost [127.0.0.1] (recv): A TLS fatal alert has been received.: Certificate is bad
+1999-03-02 09:44:33 TLS error on connection from localhost [127.0.0.1] (send): The specified session has been invalidated for some reason.
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? H=localhost (myhost.test.ex) [127.0.0.1] P=esmtp S=sss id=E10HmaX-0005vi-00@???
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 DN="CN=server2.example.com" S=sss id=E10HmaY-0005vi-00@???
diff --git a/test/log/5760 b/test/log/5760
new file mode 100644
index 0000000..0b74e24
--- /dev/null
+++ b/test/log/5760
@@ -0,0 +1,47 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@??? U=CALLER P=local S=sss
+1999-03-02 09:44:33 Start queue run: pid=pppp -qf
+1999-03-02 09:44:33 10HmaX-0005vi-00 SSL verify error: depth=2 error=self signed certificate in certificate chain cert=/O=example.com/CN=clica CA
+1999-03-02 09:44:33 10HmaX-0005vi-00 TLS error on connection to 127.0.0.1 [127.0.0.1] (SSL_connect): error: <<detail omitted>>
+1999-03-02 09:44:33 10HmaX-0005vi-00 deferral bad
+1999-03-02 09:44:33 10HmaX-0005vi-00 NO CLENT CERT presented
+1999-03-02 09:44:33 10HmaX-0005vi-00 Peer cert:
+1999-03-02 09:44:33 10HmaX-0005vi-00 ver <2>
+1999-03-02 09:44:33 10HmaX-0005vi-00 SN  <CN=clica CA,O=example.com>
+1999-03-02 09:44:33 10HmaX-0005vi-00 IN  <CN=clica CA,O=example.com>
+1999-03-02 09:44:33 10HmaX-0005vi-00 NB  <Nov  1 12:34:04 2012 GMT>
+1999-03-02 09:44:33 10HmaX-0005vi-00 NA  <Jan  1 12:34:04 2038 GMT>
+1999-03-02 09:44:33 10HmaX-0005vi-00 SA  <undefined>
+1999-03-02 09:44:33 10HmaX-0005vi-00 SG  <    Signature Algorithm: sha1WithRSAEncryption\n         89:fd:fb:cb:b2:42:d6:aa:f2:c0:44:a2:14:e5:ab:22:50:41:\n         e6:64:e7:1c:5a:20:b6:0f:fe:b0:88:c5:cf:b3:e5:f8:0e:87:\n         eb:ac:07:d6:9d:6a:20:f6:dd:13:ee:b8:3f:cf:d9:cd:d4:a8:\n         72:50:5a:a2:14:4e:ee:3a:78:e2:a7:f4:ae:d7:ee:77:48:1f:\n         75:a7:68:2f:ee:e2:7c:ac:2f:e4:88:02:e8:3b:db:f9:35:04:\n         05:46:35:0b:f2:35:03:21:b6:1e:82:7d:94:e0:63:4b:60:71:\n         2d:19:45:21:f2:85:b4:c3:d0:77:a2:24:32:36:f3:50:68:38:\n         98:e6\n>
+1999-03-02 09:44:33 10HmaX-0005vi-00 (no SAN)
+1999-03-02 09:44:33 10HmaX-0005vi-00 (no OCU)
+1999-03-02 09:44:33 10HmaX-0005vi-00 (no CRU)
+1999-03-02 09:44:33 10HmaX-0005vi-00 TLS session failure: delivering unencrypted to 127.0.0.1 [127.0.0.1] (not in hosts_require_tls)
+1999-03-02 09:44:33 10HmaX-0005vi-00 => bad@??? R=client T=send_to_server H=127.0.0.1 [127.0.0.1] C="250 OK id=10HmaZ-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 delivery bad
+1999-03-02 09:44:33 10HmaX-0005vi-00 NO CLENT CERT presented
+1999-03-02 09:44:33 10HmaX-0005vi-00 No Peer cert
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 => good@??? R=client T=send_to_server H=127.0.0.1 [127.0.0.1] X=TLSv1:AES256-SHA:256 DN="/CN=server1.example.com" C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaY-0005vi-00 delivery good
+1999-03-02 09:44:33 10HmaY-0005vi-00 Our cert SN: CN=server2.example.com
+1999-03-02 09:44:33 10HmaY-0005vi-00 Peer cert:
+1999-03-02 09:44:33 10HmaY-0005vi-00 ver <2>
+1999-03-02 09:44:33 10HmaY-0005vi-00 SN  <CN=server1.example.com>
+1999-03-02 09:44:33 10HmaY-0005vi-00 IN  <CN=clica Signing Cert,O=example.com>
+1999-03-02 09:44:33 10HmaY-0005vi-00 NB  <Nov  1 12:34:05 2012 GMT>
+1999-03-02 09:44:33 10HmaY-0005vi-00 NA  <Jan  1 12:34:05 2038 GMT>
+1999-03-02 09:44:33 10HmaY-0005vi-00 SA  <undefined>
+1999-03-02 09:44:33 10HmaY-0005vi-00 SG  <    Signature Algorithm: sha1WithRSAEncryption\n         56:3a:a4:3c:cb:eb:b8:27:c2:90:08:74:13:88:dc:48:c6:b5:\n         2c:e5:26:be:5b:91:d4:67:e7:3c:49:12:d7:47:30:df:98:db:\n         58:ed:18:a8:7d:4b:db:97:48:f5:5c:7f:70:b9:37:63:33:f1:\n         24:62:72:92:60:f5:6e:da:b6:bc:73:c8:c2:dc:d6:95:9a:bd:\n         16:16:a2:ef:0a:f1:d7:41:68:f6:ad:98:5a:d0:ff:d9:1b:51:\n         9f:59:ce:2f:3d:84:d0:ee:e8:2b:eb:9b:32:1a:0e:02:3e:cc:\n         30:89:44:09:2a:75:81:46:a7:b6:ed:7d:41:eb:5a:63:fa:9c:\n         58:ef\n>
+1999-03-02 09:44:33 10HmaY-0005vi-00 SAN <Unrecognisable>
+1999-03-02 09:44:33 10HmaY-0005vi-00 OCU <http://oscp/example.com/>
+1999-03-02 09:44:33 10HmaY-0005vi-00 CRU <http://crl.example.com/latest.crl>
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp -qf
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 TLS error on connection from localhost (myhost.test.ex) [127.0.0.1] (SSL_accept): error: <<detail omitted>>
+1999-03-02 09:44:33 TLS client disconnected cleanly (rejected our certificate?)
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@??? H=localhost (myhost.test.ex) [127.0.0.1] P=esmtp S=sss id=E10HmaX-0005vi-00@???
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@??? H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 DN="/CN=server2.example.com" S=sss id=E10HmaY-0005vi-00@???
diff --git a/test/mail/2002.CALLER b/test/mail/2002.CALLER
index a4e0dd5..23b5f61 100644
--- a/test/mail/2002.CALLER
+++ b/test/mail/2002.CALLER
@@ -30,7 +30,7 @@ Received: from [ip4.ip4.ip4.ip4]
     id 10HmaZ-0005vi-00
     for CALLER@???; Tue, 2 Mar 1999 09:44:33 +0000
 tls-certificate-verified: 1
-TLS: cipher=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 peerdn=C=UK,O=The Exim Maintainers,OU=Test Suite,CN=Phil Pennock
+TLS: cipher=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 peerdn=CN=server2.example.com


This is a test encrypted message from a verified host.

diff --git a/test/mail/2102.CALLER b/test/mail/2102.CALLER
index e4be6a3..42c189f 100644
--- a/test/mail/2102.CALLER
+++ b/test/mail/2102.CALLER
@@ -30,7 +30,7 @@ Received: from [ip4.ip4.ip4.ip4]
     id 10HmaZ-0005vi-00
     for CALLER@???; Tue, 2 Mar 1999 09:44:33 +0000
 tls-certificate-verified: 1
-TLS: cipher=TLSv1:AES256-SHA:256 peerdn=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock
+TLS: cipher=TLSv1:AES256-SHA:256 peerdn=/CN=server2.example.com


This is a test encrypted message from a verified host.

diff --git a/test/scripts/2000-GnuTLS/2002 b/test/scripts/2000-GnuTLS/2002
index 06a7b31..49f841e 100644
--- a/test/scripts/2000-GnuTLS/2002
+++ b/test/scripts/2000-GnuTLS/2002
@@ -1,4 +1,4 @@
-# TLS server: general
+# TLS server: general ops and certificate extractions
gnutls
exim -DSERVER=server -bd -oX PORT_D
****
@@ -60,7 +60,7 @@ ehlo rhu.barb
starttls
??? 220
****
-client-gnutls HOSTIPV4 PORT_D DIR/aux-fixed/cert2 DIR/aux-fixed/cert2
+client-gnutls HOSTIPV4 PORT_D DIR/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.pem DIR/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.unlocked.key
??? 220
ehlo rhu.barb
??? 250-
diff --git a/test/scripts/2100-OpenSSL/2102 b/test/scripts/2100-OpenSSL/2102
index 2e7dca0..cbb9ce3 100644
--- a/test/scripts/2100-OpenSSL/2102
+++ b/test/scripts/2100-OpenSSL/2102
@@ -1,4 +1,4 @@
-# TLS server: general
+# TLS server: general ops and certificate extractions
exim -DSERVER=server -bd -oX PORT_D
****
client-ssl 127.0.0.1 PORT_D
@@ -59,7 +59,7 @@ ehlo rhu.barb
starttls
??? 220
****
-client-ssl HOSTIPV4 PORT_D DIR/aux-fixed/cert2 DIR/aux-fixed/cert2
+client-ssl HOSTIPV4 PORT_D DIR/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.pem DIR/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.unlocked.key
??? 220
ehlo rhu.barb
??? 250-
diff --git a/test/scripts/5750-GnuTLS-TPDA/5750 b/test/scripts/5750-GnuTLS-TPDA/5750
new file mode 100644
index 0000000..903c795
--- /dev/null
+++ b/test/scripts/5750-GnuTLS-TPDA/5750
@@ -0,0 +1,13 @@
+# TLS client: GnuTLS $tls_out_peercert
+exim -DSERVER=server -bd -oX PORT_D
+****
+exim bad@???
+Testing
+****
+exim good@???
+Testing
+****
+exim -qf
+****
+killdaemon
+no_msglog_check
diff --git a/test/scripts/5750-GnuTLS-TPDA/REQUIRES b/test/scripts/5750-GnuTLS-TPDA/REQUIRES
new file mode 100644
index 0000000..af1eb46
--- /dev/null
+++ b/test/scripts/5750-GnuTLS-TPDA/REQUIRES
@@ -0,0 +1,2 @@
+support Experimental_TPDA
+support GnuTLS
diff --git a/test/scripts/5760-OpenSSL-TPDA/5760 b/test/scripts/5760-OpenSSL-TPDA/5760
new file mode 100644
index 0000000..8fa8bd0
--- /dev/null
+++ b/test/scripts/5760-OpenSSL-TPDA/5760
@@ -0,0 +1,13 @@
+# TLS client: OpenSSL certificates and extractions
+exim -DSERVER=server -bd -oX PORT_D
+****
+exim bad@???
+Testing
+****
+exim good@???
+Testing
+****
+exim -qf
+****
+killdaemon
+no_msglog_check
diff --git a/test/scripts/5760-OpenSSL-TPDA/REQUIRES b/test/scripts/5760-OpenSSL-TPDA/REQUIRES
new file mode 100644
index 0000000..5b48920
--- /dev/null
+++ b/test/scripts/5760-OpenSSL-TPDA/REQUIRES
@@ -0,0 +1,2 @@
+support Experimental_TPDA
+support OpenSSL
diff --git a/test/stdout/2002 b/test/stdout/2002
index a248be7..ec3c1f9 100644
--- a/test/stdout/2002
+++ b/test/stdout/2002
@@ -97,8 +97,8 @@ Attempting to start TLS
Failed to start TLS
End of script
Connecting to ip4.ip4.ip4.ip4 port 1225 ... connected
-Certificate file = TESTSUITE/aux-fixed/cert2
-Key file = TESTSUITE/aux-fixed/cert2
+Certificate file = TESTSUITE/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.pem
+Key file = TESTSUITE/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.unlocked.key
??? 220
<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
>>> ehlo rhu.barb

diff --git a/test/stdout/2102 b/test/stdout/2102
index 23c39cd..77ae109 100644
--- a/test/stdout/2102
+++ b/test/stdout/2102
@@ -145,8 +145,8 @@ pppp:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure:s
Failed to start TLS
End of script
Connecting to ip4.ip4.ip4.ip4 port 1225 ... connected
-Certificate file = TESTSUITE/aux-fixed/cert2
-Key file = TESTSUITE/aux-fixed/cert2
+Certificate file = TESTSUITE/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.pem
+Key file = TESTSUITE/aux-fixed/exim-ca/example.com/server2.example.com/server2.example.com.unlocked.key
??? 220
<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
>>> ehlo rhu.barb