diff options
author | Jeremy Harris <jgh146exb@wizmail.org> | 2014-05-02 18:50:34 +0100 |
---|---|---|
committer | Jeremy Harris <jgh146exb@wizmail.org> | 2014-05-02 20:05:30 +0100 |
commit | 9d1c15ef45fcc8809349378922de20ae9a774c75 (patch) | |
tree | eea880b5d958191479639e41302db6513cfc9698 /src | |
parent | 9d9c374678ae4b04869c90bc5980acfcfb68c336 (diff) |
Certificate variables and field-extractor expansions. Bug 1358
Diffstat (limited to 'src')
-rw-r--r-- | src/OS/Makefile-Base | 2 | ||||
-rwxr-xr-x | src/scripts/MakeLinks | 2 | ||||
-rw-r--r-- | src/src/deliver.c | 108 | ||||
-rw-r--r-- | src/src/expand.c | 536 | ||||
-rw-r--r-- | src/src/functions.h | 19 | ||||
-rw-r--r-- | src/src/globals.c | 6 | ||||
-rw-r--r-- | src/src/globals.h | 2 | ||||
-rw-r--r-- | src/src/smtp_in.c | 2 | ||||
-rw-r--r-- | src/src/spool_in.c | 10 | ||||
-rw-r--r-- | src/src/spool_out.c | 16 | ||||
-rw-r--r-- | src/src/structs.h | 2 | ||||
-rw-r--r-- | src/src/tls-gnu.c | 125 | ||||
-rw-r--r-- | src/src/tls-openssl.c | 23 | ||||
-rw-r--r-- | src/src/tls.c | 2 | ||||
-rw-r--r-- | src/src/tlscert-gnu.c | 325 | ||||
-rw-r--r-- | src/src/tlscert-openssl.c | 293 | ||||
-rw-r--r-- | src/src/transports/smtp.c | 8 |
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 } |