summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/doc-docbook/spec.xfpt69
-rw-r--r--doc/doc-txt/ChangeLog3
-rw-r--r--doc/doc-txt/NewStuff3
-rw-r--r--src/OS/Makefile-Base2
-rwxr-xr-xsrc/scripts/MakeLinks2
-rw-r--r--src/src/deliver.c108
-rw-r--r--src/src/expand.c536
-rw-r--r--src/src/functions.h19
-rw-r--r--src/src/globals.c6
-rw-r--r--src/src/globals.h2
-rw-r--r--src/src/smtp_in.c2
-rw-r--r--src/src/spool_in.c10
-rw-r--r--src/src/spool_out.c16
-rw-r--r--src/src/structs.h2
-rw-r--r--src/src/tls-gnu.c125
-rw-r--r--src/src/tls-openssl.c23
-rw-r--r--src/src/tls.c2
-rw-r--r--src/src/tlscert-gnu.c325
-rw-r--r--src/src/tlscert-openssl.c293
-rw-r--r--src/src/transports/smtp.c8
-rw-r--r--test/confs/200224
-rw-r--r--test/confs/210224
-rw-r--r--test/confs/575095
-rw-r--r--test/confs/576095
-rw-r--r--test/log/200218
-rw-r--r--test/log/210219
-rw-r--r--test/log/575046
-rw-r--r--test/log/576047
-rw-r--r--test/mail/2002.CALLER2
-rw-r--r--test/mail/2102.CALLER2
-rw-r--r--test/scripts/2000-GnuTLS/20024
-rw-r--r--test/scripts/2100-OpenSSL/21024
-rw-r--r--test/scripts/5750-GnuTLS-TPDA/575013
-rw-r--r--test/scripts/5750-GnuTLS-TPDA/REQUIRES2
-rw-r--r--test/scripts/5760-OpenSSL-TPDA/576013
-rw-r--r--test/scripts/5760-OpenSSL-TPDA/REQUIRES2
-rw-r--r--test/stdout/20024
-rw-r--r--test/stdout/21024
38 files changed, 1686 insertions, 288 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index afc15d433..ec9367582 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 172748584..c98528884 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 33c66ceb9..b6fc576bd 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 8209969f6..0caf8604b 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 2eb8a967e..01cd21f1c 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 1e7a8a18a..fff0e2fd0 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 54b3abc54..34fb0346e 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 599afd206..8751a006e 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 da81b8db5..7b591e42a 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 79bf38caa..584d1bd09 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 27ff13785..6810d25e3 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 a546b6521..2006e1b02 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 ce25a564e..7bbd42df0 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 eb430851d..a6c78f4fc 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 ace59633a..880aaeb14 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 bdf910acc..2f08e43c6 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 972785284..ad7fe609c 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 000000000..649e93a35
--- /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 000000000..008cf54b9
--- /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 9e0ab1556..16c2b6011 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 e8358da25..b4d0348ca 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 7f5771c0e..5332801dc 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 000000000..a4762bd19
--- /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 000000000..0e11ab0d3
--- /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 774495514..e2777b459 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@test.ex 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"@test.ex 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@test.ex 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@test.ex 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@test.ex> 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 da4ee49d7..6e0713f41 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@test.ex 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"@test.ex 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@test.ex 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@test.ex 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@test.ex> 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 000000000..8c98b5b95
--- /dev/null
+++ b/test/log/5750
@@ -0,0 +1,46 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex 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@test.ex 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@test.ex 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@myhost.test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtp S=sss id=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex 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@myhost.test.ex
diff --git a/test/log/5760 b/test/log/5760
new file mode 100644
index 000000000..0b74e243d
--- /dev/null
+++ b/test/log/5760
@@ -0,0 +1,47 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex 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@test.ex 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@test.ex 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@myhost.test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtp S=sss id=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex 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@myhost.test.ex
diff --git a/test/mail/2002.CALLER b/test/mail/2002.CALLER
index a4e0dd526..23b5f61a5 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@test.ex; 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 e4be6a342..42c189f78 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@test.ex; 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 06a7b31d0..49f841e56 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 2e7dca0a6..cbb9ce393 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 000000000..903c79525
--- /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@test.ex
+Testing
+****
+exim good@test.ex
+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 000000000..af1eb46f7
--- /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 000000000..8fa8bd04b
--- /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@test.ex
+Testing
+****
+exim good@test.ex
+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 000000000..5b4892059
--- /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 a248be7c0..ec3c1f954 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 23c39cdf4..77ae109b2 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