summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2014-05-02 18:50:34 +0100
committerJeremy Harris <jgh146exb@wizmail.org>2014-05-02 20:05:30 +0100
commit9d1c15ef45fcc8809349378922de20ae9a774c75 (patch)
treeeea880b5d958191479639e41302db6513cfc9698 /src
parent9d9c374678ae4b04869c90bc5980acfcfb68c336 (diff)
Certificate variables and field-extractor expansions. Bug 1358
Diffstat (limited to 'src')
-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
17 files changed, 1213 insertions, 268 deletions
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
}