-TSMTP_SASL_AUTH_CACHE
-TSMTP_SESSION
-TSMTP_STATE
+-TSMTP_TLS_SESS
-TSMTP_TLS_SITE_POLICY
-TSM_STATE
-TSOCKADDR_SIZE
Export tls_fprint() and tls_digest_encode() for use in DANE.
Viktor Dukhovni. Files: src/tls/tls.h, src/tls/tls_fprint.c.
+
+20130331
+
+ Refactoring: TLS verification callback processing in
+ preparation for DANE support. Viktor Dukhovni. Files:
+ src/tls/tls.h, src/tls/tls_client.c, src/tls/tls_misc.c,
+ src/tls/tls_verify.c.
+
+ Refactoring: split off SMTP client per-session TLS policy
+ data and code in preparation for DANE support. Viktor
+ Dukhovni. Files: src/smtp/Makefile.in, src/smtp/smtp.h,
+ src/smtp/smtp_connect.c, src/smtp/smtp_proto.c,
+ src/smtp/smtp_reuse.c, src/smtp/smtp_session.c,
+ src/smtp/smtp_tls_sess.c.
+
+ Cleanup: "zero time limit" corner case in read_wait() and
+ write_wait() emulation. Files: util/poll_fd.c, util/iostuff.h.
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20130327"
+#define MAIL_RELEASE_DATE "20130331"
#define MAIL_VERSION_NUMBER "2.11"
#ifdef SNAPSHOT
SHELL = /bin/sh
SRCS = smtp.c smtp_connect.c smtp_proto.c smtp_chat.c smtp_session.c \
- smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c \
+ smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c smtp_tls_sess.c \
smtp_sasl_proto.c smtp_sasl_glue.c smtp_reuse.c smtp_map11.c \
smtp_sasl_auth_cache.c
OBJS = smtp.o smtp_connect.o smtp_proto.o smtp_chat.o smtp_session.o \
- smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o \
+ smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o smtp_tls_sess.o \
smtp_sasl_proto.o smtp_sasl_glue.o smtp_reuse.o smtp_map11.o \
smtp_sasl_auth_cache.o
HDRS = smtp.h smtp_sasl.h smtp_addr.h smtp_reuse.h smtp_sasl_auth_cache.h
smtp_state.o: smtp.h
smtp_state.o: smtp_sasl.h
smtp_state.o: smtp_state.c
+smtp_tls_sess.o: ../../include/argv.h
+smtp_tls_sess.o: ../../include/attr.h
+smtp_tls_sess.o: ../../include/deliver_request.h
+smtp_tls_sess.o: ../../include/dict.h
+smtp_tls_sess.o: ../../include/dsn.h
+smtp_tls_sess.o: ../../include/dsn_buf.h
+smtp_tls_sess.o: ../../include/header_body_checks.h
+smtp_tls_sess.o: ../../include/header_opts.h
+smtp_tls_sess.o: ../../include/htable.h
+smtp_tls_sess.o: ../../include/mail_params.h
+smtp_tls_sess.o: ../../include/maps.h
+smtp_tls_sess.o: ../../include/match_list.h
+smtp_tls_sess.o: ../../include/mime_state.h
+smtp_tls_sess.o: ../../include/msg.h
+smtp_tls_sess.o: ../../include/msg_stats.h
+smtp_tls_sess.o: ../../include/myflock.h
+smtp_tls_sess.o: ../../include/mymalloc.h
+smtp_tls_sess.o: ../../include/name_code.h
+smtp_tls_sess.o: ../../include/name_mask.h
+smtp_tls_sess.o: ../../include/recipient_list.h
+smtp_tls_sess.o: ../../include/resolve_clnt.h
+smtp_tls_sess.o: ../../include/scache.h
+smtp_tls_sess.o: ../../include/string_list.h
+smtp_tls_sess.o: ../../include/stringops.h
+smtp_tls_sess.o: ../../include/sys_defs.h
+smtp_tls_sess.o: ../../include/tls.h
+smtp_tls_sess.o: ../../include/tok822.h
+smtp_tls_sess.o: ../../include/valid_hostname.h
+smtp_tls_sess.o: ../../include/vbuf.h
+smtp_tls_sess.o: ../../include/vstream.h
+smtp_tls_sess.o: ../../include/vstring.h
+smtp_tls_sess.o: smtp.h
+smtp_tls_sess.o: smtp_tls_sess.c
smtp_trouble.o: ../../include/argv.h
smtp_trouble.o: ../../include/attr.h
smtp_trouble.o: ../../include/bounce.h
/*
* smtp_session.c
*/
+#ifdef USE_TLS
+typedef struct SMTP_TLS_SESS {
+ int level; /* TLS enforcement level */
+ char *protocols; /* Acceptable SSL protocols */
+ char *grade; /* Cipher grade: "export", ... */
+ VSTRING *exclusions; /* Excluded SSL ciphers */
+ ARGV *matchargv; /* Cert match patterns */
+} SMTP_TLS_SESS;
+
+#endif
+
typedef struct SMTP_SESSION {
VSTREAM *stream; /* network connection */
char *dest; /* nexthop or fallback */
* TLS related state, don't forget to initialize in session_tls_init()!
*/
#ifdef USE_TLS
- TLS_SESS_STATE *tls_context; /* TLS session state */
+ TLS_SESS_STATE *tls_context; /* TLS library session state */
char *tls_nexthop; /* Nexthop domain for cert checks */
- int tls_level; /* TLS enforcement level */
int tls_retry_plain; /* Try plain when TLS handshake fails */
- char *tls_protocols; /* Acceptable SSL protocols */
- char *tls_grade; /* Cipher grade: "export", ... */
- VSTRING *tls_exclusions; /* Excluded SSL ciphers */
- ARGV *tls_matchargv; /* Cert match patterns */
+ SMTP_TLS_SESS *tls; /* SMTP session TLS policy */
#endif
SMTP_STATE *state; /* back link */
#ifdef USE_TLS
extern void smtp_tls_list_init(void);
+ /*
+ * smtp_tls_sess.c
+ */
+extern SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *, const char *,
+ unsigned, int);
+extern SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *);
+
#endif
/*
session->state = state;
#ifdef USE_TLS
session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */
- if (session->tls_level == TLS_LEV_MAY) {
+ if (session->tls->level == TLS_LEV_MAY) {
msg_warn("%s: opportunistic TLS encryption is not appropriate "
"for unix-domain destinations.", myname);
- session->tls_level = TLS_LEV_NONE;
+ session->tls->level = TLS_LEV_NONE;
}
#endif
/* All delivery errors bounce or defer. */
#ifdef USE_TLS
/* Disable TLS when retrying after a handshake failure */
if (retry_plain) {
- if (session->tls_level >= TLS_LEV_ENCRYPT)
+ if (session->tls->level >= TLS_LEV_ENCRYPT)
msg_panic("Plain-text retry wrong for mandatory TLS");
- session->tls_level = TLS_LEV_NONE;
+ session->tls->level = TLS_LEV_NONE;
retry_plain = 0;
}
session->tls_nexthop = domain; /* for TLS_LEV_SECURE */
* now.
*/
#ifdef USE_TLS
- if (session->tls_level == TLS_LEV_INVALID)
+ if (session->tls->level == TLS_LEV_INVALID)
/* Warning is already logged. */
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.0"),
*/
if ((session->features & SMTP_FEATURE_STARTTLS) &&
var_smtp_tls_note_starttls_offer &&
- session->tls_level <= TLS_LEV_NONE)
+ session->tls->level <= TLS_LEV_NONE)
msg_info("Host offered STARTTLS: [%s]", session->host);
/*
* Decide whether or not to send STARTTLS.
*/
if ((session->features & SMTP_FEATURE_STARTTLS) != 0
- && smtp_tls_ctx != 0 && session->tls_level >= TLS_LEV_MAY) {
+ && smtp_tls_ctx != 0 && session->tls->level >= TLS_LEV_MAY) {
/*
* Prepare for disaster.
* although support for it was announced in the EHLO response.
*/
session->features &= ~SMTP_FEATURE_STARTTLS;
- if (session->tls_level >= TLS_LEV_ENCRYPT)
+ if (session->tls->level >= TLS_LEV_ENCRYPT)
return (smtp_site_fail(state, session->host, resp,
"TLS is required, but host %s refused to start TLS: %s",
session->namaddr,
* block. When TLS is required we must never, ever, end up in
* plain-text mode.
*/
- if (session->tls_level >= TLS_LEV_ENCRYPT) {
+ if (session->tls->level >= TLS_LEV_ENCRYPT) {
if (!(session->features & SMTP_FEATURE_STARTTLS)) {
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.4"),
ctx = smtp_tls_ctx,
stream = session->stream,
timeout = var_smtp_starttls_tmout,
- tls_level = session->tls_level,
+ tls_level = session->tls->level,
nexthop = session->tls_nexthop,
host = session->host,
namaddr = session->namaddrport,
serverid = vstring_str(serverid),
helo = session->helo,
- protocols = session->tls_protocols,
- cipher_grade = session->tls_grade,
+ protocols = session->tls->protocols,
+ cipher_grade = session->tls->grade,
cipher_exclusions
- = vstring_str(session->tls_exclusions),
- matchargv = session->tls_matchargv,
+ = vstring_str(session->tls->exclusions),
+ matchargv = session->tls->matchargv,
mdalg = var_smtp_tls_fpt_dgst);
vstring_free(serverid);
* plaintext connections, then we don't want delivery to fail with
* "relay access denied".
*/
- if (session->tls_level == TLS_LEV_MAY
+ if (session->tls->level == TLS_LEV_MAY
#ifdef USE_SASL_AUTH
&& !(var_smtp_sasl_enable
&& *var_smtp_sasl_passwd
* See src/tls/tls_level.c. Levels above encrypt require matching. Levels >=
* verify require CA trust.
*/
- if (session->tls_level >= TLS_LEV_VERIFY)
+ if (session->tls->level >= TLS_LEV_VERIFY)
if (!TLS_CERT_IS_TRUSTED(session->tls_context))
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.5"),
"Server certificate not trusted"));
- if (session->tls_level >= TLS_LEV_DANE)
+ if (session->tls->level >= TLS_LEV_DANE)
if (!TLS_CERT_IS_MATCHED(session->tls_context))
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.5"),
* lookups, because we can compute the TLS policy much earlier.
*/
#ifdef USE_TLS
- if (session->tls_level >= TLS_LEV_ENCRYPT) {
+ if (session->tls->level >= TLS_LEV_ENCRYPT) {
if (msg_verbose)
msg_info("%s: skipping plain-text cached session to %s",
myname, label);
#include "smtp.h"
#include "smtp_sasl.h"
-#ifdef USE_TLS
-
-static MAPS *tls_policy; /* lookup table(s) */
-static MAPS *tls_per_site; /* lookup table(s) */
-
-/* smtp_tls_list_init - initialize per-site policy lists */
-
-void smtp_tls_list_init(void)
-{
- if (*var_smtp_tls_policy) {
- tls_policy = maps_create(SMTP_X(TLS_POLICY), var_smtp_tls_policy,
- DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
- if (*var_smtp_tls_per_site)
- msg_warn("%s ignored when %s is not empty.",
- SMTP_X(TLS_PER_SITE), SMTP_X(TLS_POLICY));
- return;
- }
- if (*var_smtp_tls_per_site) {
- tls_per_site = maps_create(SMTP_X(TLS_PER_SITE), var_smtp_tls_per_site,
- DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
- }
-}
-
-/* policy_name - printable tls policy level */
-
-static const char *policy_name(int tls_level)
-{
- const char *name = str_tls_level(tls_level);
-
- if (name == 0)
- name = "unknown";
- return name;
-}
-
-/* tls_site_lookup - look up per-site TLS security level */
-
-static void tls_site_lookup(int *site_level, const char *site_name,
- const char *site_class)
-{
- const char *lookup;
-
- /*
- * Look up a non-default policy. In case of multiple lookup results, the
- * precedence order is a permutation of the TLS enforcement level order:
- * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more
- * specific policy including NONE, otherwise we choose the stronger
- * enforcement level.
- */
- if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) {
- if (!strcasecmp(lookup, "NONE")) {
- /* NONE overrides MAY or NOTFOUND. */
- if (*site_level <= TLS_LEV_MAY)
- *site_level = TLS_LEV_NONE;
- } else if (!strcasecmp(lookup, "MAY")) {
- /* MAY overrides NOTFOUND but not NONE. */
- if (*site_level < TLS_LEV_NONE)
- *site_level = TLS_LEV_MAY;
- } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) {
- if (*site_level < TLS_LEV_ENCRYPT)
- *site_level = TLS_LEV_ENCRYPT;
- } else if (!strcasecmp(lookup, "MUST")) {
- if (*site_level < TLS_LEV_VERIFY)
- *site_level = TLS_LEV_VERIFY;
- } else {
- msg_warn("Table %s: ignoring unknown TLS policy '%s' for %s %s",
- var_smtp_tls_per_site, lookup, site_class, site_name);
- }
- } else if (tls_per_site->error) {
- msg_fatal("%s lookup error for %s", tls_per_site->title, site_name);
- }
-}
-
-/* tls_policy_lookup_one - look up destination TLS policy */
-
-static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level,
- const char *site_name,
- const char *site_class)
-{
- const char *lookup;
- char *policy;
- char *saved_policy;
- char *tok;
- const char *err;
- char *name;
- char *val;
- static VSTRING *cbuf;
-
-#undef FREE_RETURN
-#define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0)
-
- if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
- if (tls_policy->error) {
- msg_fatal("%s: %s lookup error for %s",
- session->state->request->queue_id,
- tls_policy->title, site_name);
- /* XXX session->stream has no longjmp context yet. */
- }
- return (0);
- }
- if (cbuf == 0)
- cbuf = vstring_alloc(10);
-
-#define WHERE \
- vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \
- site_class, site_name))
-
- saved_policy = policy = mystrdup(lookup);
-
- if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) {
- msg_warn("%s: invalid empty policy", WHERE);
- *site_level = TLS_LEV_INVALID;
- FREE_RETURN(1); /* No further lookups */
- }
- *site_level = tls_level_lookup(tok);
- if (*site_level == TLS_LEV_INVALID) {
- /* tls_level_lookup() logs no warning. */
- msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
- FREE_RETURN(1); /* No further lookups */
- }
-
- /*
- * Warn about ignored attributes when TLS is disabled.
- */
- if (*site_level < TLS_LEV_MAY) {
- while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
- msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
- WHERE, tok);
- FREE_RETURN(1);
- }
-
- /*
- * Errors in attributes may have security consequences, don't ignore
- * errors that can degrade security.
- */
- while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) {
- if ((err = split_nameval(tok, &name, &val)) != 0) {
- *site_level = TLS_LEV_INVALID;
- msg_warn("%s: malformed attribute/value pair \"%s\": %s",
- WHERE, tok, err);
- break;
- }
- /* Only one instance per policy. */
- if (!strcasecmp(name, "ciphers")) {
- if (*val == 0) {
- msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
- *site_level = TLS_LEV_INVALID;
- break;
- }
- if (session->tls_grade) {
- msg_warn("%s: attribute \"%s\" is specified multiple times",
- WHERE, name);
- *site_level = TLS_LEV_INVALID;
- break;
- }
- session->tls_grade = mystrdup(val);
- continue;
- }
- /* Only one instance per policy. */
- if (!strcasecmp(name, "protocols")) {
- if (session->tls_protocols) {
- msg_warn("%s: attribute \"%s\" is specified multiple times",
- WHERE, name);
- *site_level = TLS_LEV_INVALID;
- break;
- }
- session->tls_protocols = mystrdup(val);
- continue;
- }
- /* Multiple instance(s) per policy. */
- if (!strcasecmp(name, "match")) {
- char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":";
-
- if (*site_level <= TLS_LEV_ENCRYPT) {
- msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"",
- WHERE, name, policy_name(*site_level));
- *site_level = TLS_LEV_INVALID;
- break;
- }
- if (*val == 0) {
- msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
- *site_level = TLS_LEV_INVALID;
- break;
- }
- if (session->tls_matchargv == 0)
- session->tls_matchargv = argv_split(val, delim);
- else
- argv_split_append(session->tls_matchargv, val, delim);
- continue;
- }
- /* Only one instance per policy. */
- if (!strcasecmp(name, "exclude")) {
- if (session->tls_exclusions) {
- msg_warn("%s: attribute \"%s\" is specified multiple times",
- WHERE, name);
- *site_level = TLS_LEV_INVALID;
- break;
- }
- session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val);
- continue;
- } else {
- msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
- *site_level = TLS_LEV_INVALID;
- break;
- }
- }
- FREE_RETURN(1);
-}
-
-/* tls_policy_lookup - look up destination TLS policy */
-
-static void tls_policy_lookup(SMTP_SESSION *session, int *site_level,
- const char *site_name,
- const char *site_class)
-{
-
- /*
- * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These
- * are never the domain part of localpart@domain, rather they are
- * explicit nexthops from transport:nexthop, and match only the
- * corresponding policy. Parent domain matching (below) applies only to
- * sub-domains of the recipient domain.
- */
- if (!valid_hostname(site_name, DONT_GRIPE)) {
- tls_policy_lookup_one(session, site_level, site_name, site_class);
- return;
- }
-
- /*
- * XXX For clarity consider using ``do { .. } while'', instead of using
- * ``while { .. }'' with loop control at the bottom.
- */
- while (1) {
- /* Try the given domain */
- if (tls_policy_lookup_one(session, site_level, site_name, site_class))
- return;
- /* Re-try with parent domain */
- if ((site_name = strchr(site_name + 1, '.')) == 0)
- return;
- }
-}
-
-/* set_cipher_grade - Set cipher grade and exclusions */
-
-static void set_cipher_grade(SMTP_SESSION *session)
-{
- const char *mand_exclude = "";
- const char *also_exclude = "";
-
- /*
- * Use main.cf cipher level if no per-destination value specified. With
- * mandatory encryption at least encrypt, and with mandatory verification
- * at least authenticate!
- */
- switch (session->tls_level) {
- case TLS_LEV_INVALID:
- case TLS_LEV_NONE:
- return;
-
- case TLS_LEV_MAY:
- if (session->tls_grade == 0)
- session->tls_grade = mystrdup(var_smtp_tls_ciph);
- break;
-
- case TLS_LEV_ENCRYPT:
- if (session->tls_grade == 0)
- session->tls_grade = mystrdup(var_smtp_tls_mand_ciph);
- mand_exclude = var_smtp_tls_mand_excl;
- also_exclude = "eNULL";
- break;
-
- case TLS_LEV_FPRINT:
- case TLS_LEV_VERIFY:
- case TLS_LEV_SECURE:
- if (session->tls_grade == 0)
- session->tls_grade = mystrdup(var_smtp_tls_mand_ciph);
- mand_exclude = var_smtp_tls_mand_excl;
- also_exclude = "aNULL";
- break;
- }
-
-#define ADD_EXCLUDE(vstr, str) \
- do { \
- if (*(str)) \
- vstring_sprintf_append((vstr), "%s%s", \
- VSTRING_LEN(vstr) ? " " : "", (str)); \
- } while (0)
-
- /*
- * The "exclude" policy table attribute overrides main.cf exclusion
- * lists.
- */
- if (session->tls_exclusions == 0) {
- session->tls_exclusions = vstring_alloc(10);
- ADD_EXCLUDE(session->tls_exclusions, var_smtp_tls_excl_ciph);
- ADD_EXCLUDE(session->tls_exclusions, mand_exclude);
- }
- ADD_EXCLUDE(session->tls_exclusions, also_exclude);
-}
-
-/* session_tls_init - session TLS parameters */
-
-static void session_tls_init(SMTP_SESSION *session, const char *dest,
- const char *host, int flags)
-{
- const char *myname = "session_tls_init";
- int global_level;
- int site_level;
-
- /*
- * Initialize all TLS related session properties.
- */
- session->tls_context = 0;
- session->tls_nexthop = 0;
- session->tls_level = TLS_LEV_NONE;
- session->tls_retry_plain = 0;
- session->tls_protocols = 0;
- session->tls_grade = 0;
- session->tls_exclusions = 0;
- session->tls_matchargv = 0;
-
- /*
- * Compute the global TLS policy. This is the default policy level when
- * no per-site policy exists. It also is used to override a wild-card
- * per-site policy.
- */
- if (*var_smtp_tls_level) {
- /* Require that var_smtp_tls_level is sanitized upon startup. */
- global_level = tls_level_lookup(var_smtp_tls_level);
- if (global_level == TLS_LEV_INVALID)
- msg_panic("%s: invalid TLS security level: \"%s\"",
- myname, var_smtp_tls_level);
- } else if (var_smtp_enforce_tls) {
- global_level = var_smtp_tls_enforce_peername ?
- TLS_LEV_VERIFY : TLS_LEV_ENCRYPT;
- } else {
- global_level = var_smtp_use_tls ?
- TLS_LEV_MAY : TLS_LEV_NONE;
- }
- if (msg_verbose)
- msg_info("%s TLS level: %s", "global", policy_name(global_level));
-
- /*
- * Compute the per-site TLS enforcement level. For compatibility with the
- * original TLS patch, this algorithm is gives equal precedence to host
- * and next-hop policies.
- */
- site_level = TLS_LEV_NOTFOUND;
-
- if (tls_policy) {
- tls_policy_lookup(session, &site_level, dest, "next-hop destination");
- } else if (tls_per_site) {
- tls_site_lookup(&site_level, dest, "next-hop destination");
- if (strcasecmp(dest, host) != 0)
- tls_site_lookup(&site_level, host, "server hostname");
- if (msg_verbose)
- msg_info("%s TLS level: %s", "site", policy_name(site_level));
-
- /*
- * Override a wild-card per-site policy with a more specific global
- * policy.
- *
- * With the original TLS patch, 1) a per-site ENCRYPT could not override
- * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy
- * produced inconsistent results: it changed a global VERIFY into
- * NONE, while producing MAY with all weaker global policy settings.
- *
- * With the current implementation, a combined per-site (NONE+MAY)
- * consistently overrides global policy with NONE, and global policy
- * can override only a per-site MAY wildcard. That is, specific
- * policies consistently override wildcard policies, and
- * (non-wildcard) per-site policies consistently override global
- * policies.
- */
- if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY)
- site_level = global_level;
- }
- if (site_level == TLS_LEV_NOTFOUND)
- session->tls_level = global_level;
- else
- session->tls_level = site_level;
-
- /*
- * Use main.cf protocols setting if not set in per-destination table.
- */
- if (session->tls_level > TLS_LEV_NONE && session->tls_protocols == 0)
- session->tls_protocols =
- mystrdup((session->tls_level == TLS_LEV_MAY) ?
- var_smtp_tls_proto : var_smtp_tls_mand_proto);
-
- /*
- * Compute cipher grade (if set in per-destination table, else
- * set_cipher() uses main.cf settings) and security level dependent
- * cipher exclusion list.
- */
- set_cipher_grade(session);
-
- /*
- * Use main.cf cert_match setting if not set in per-destination table.
- */
- if (session->tls_matchargv == 0) {
- switch (session->tls_level) {
- case TLS_LEV_INVALID:
- case TLS_LEV_NONE:
- case TLS_LEV_MAY:
- case TLS_LEV_ENCRYPT:
- break;
- case TLS_LEV_FPRINT:
- session->tls_matchargv =
- argv_split(var_smtp_tls_fpt_cmatch, "\t\n\r, |");
- break;
- case TLS_LEV_VERIFY:
- session->tls_matchargv =
- argv_split(var_smtp_tls_vfy_cmatch, "\t\n\r, :");
- break;
- case TLS_LEV_SECURE:
- session->tls_matchargv =
- argv_split(var_smtp_tls_sec_cmatch, "\t\n\r, :");
- break;
- default:
- msg_panic("unexpected TLS security level: %d",
- session->tls_level);
- }
- }
- if (msg_verbose && (tls_policy || tls_per_site))
- msg_info("%s TLS level: %s", "effective",
- policy_name(session->tls_level));
-}
-
-#endif
-
/* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */
SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, const char *dest,
smtp_sasl_connect(session);
#endif
- /*
- * Need to pass the session as a parameter when the new-style per-nexthop
- * policies can specify not only security level thresholds, but also how
- * security levels are defined.
- */
#ifdef USE_TLS
- session_tls_init(session, dest, host, flags);
+ session->tls_context = 0;
+ session->tls_retry_plain = 0;
+ session->tls_nexthop = 0;
+ session->tls =
+ smtp_tls_sess_alloc(dest, host, port, flags & SMTP_MISC_FLAG_TLSA_HOST);
#endif
session->state = 0;
debug_peer_check(host, addr);
tls_client_stop(smtp_tls_ctx, session->stream,
var_smtp_starttls_tmout, 0, session->tls_context);
}
- if (session->tls_protocols)
- myfree(session->tls_protocols);
- if (session->tls_grade)
- myfree(session->tls_grade);
- if (session->tls_exclusions)
- vstring_free(session->tls_exclusions);
- if (session->tls_matchargv)
- argv_free(session->tls_matchargv);
+ if (session->tls)
+ (void) smtp_tls_sess_free(session->tls);
#endif
if (session->stream)
vstream_fclose(session->stream);
--- /dev/null
+/*++
+/* NAME
+/* smtp_tls_sess 3
+/* SUMMARY
+/* SMTP_TLS_SESS structure management
+/* SYNOPSIS
+/* #include "smtp.h"
+/*
+/* void smtp_tls_list_init()
+/*
+/* SMTP_TLS_SESS *smtp_tls_sess_alloc(dest, host, port, valid)
+/* char *dest;
+/* char *host;
+/* unsigned port;
+/* int valid;
+/*
+/* SMTP_TLS_SESS *smtp_tls_sess_free(tls)
+/* SMTP_TLS_SESS *tls;
+/* DESCRIPTION
+/* smtp_tls_list_init() initializes lookup tables used by the TLS
+/* policy engine.
+/*
+/* smtp_tls_sess_alloc() allocates memory for an SMTP_TLS_SESS structure
+/* and initializes it based on the given information. NOTE: the
+/* port is in network byte order.
+/*
+/* smtp_tls_sess_free() destroys an SMTP_TLS_SESS structure and its
+/* members. A null pointer is returned for convenience.
+/*
+/* Arguments:
+/* .IP dest
+/* The unmodified next-hop or fall-back destination including
+/* the optional [] and including the optional port or service.
+/* .IP host
+/* The name of the host that we are connected to. If the name was
+/* obtained via an MX lookup and/or CNAME expansion (any indirection),
+/* all those lookups must also have been validated. If the hostname
+/* is validated, it must be the final name after all CNAME expansion.
+/* Otherwise, it is generally the original name or that returned by
+/* the MX lookup (see smtp_cname_overrides_servername).
+/* .IP port
+/* The remote port, network byte order.
+/* .IP valid
+/* The DNSSEC validation status of the host name.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Viktor Dukhovni
+/*
+/* TLS support originally by:
+/* Lutz Jaenicke
+/* BTU Cottbus
+/* Allgemeine Elektrotechnik
+/* Universitaetsplatz 3-4
+/* D-03044 Cottbus, Germany
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <maps.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+static MAPS *tls_policy; /* lookup table(s) */
+static MAPS *tls_per_site; /* lookup table(s) */
+
+/* smtp_tls_list_init - initialize per-site policy lists */
+
+void smtp_tls_list_init(void)
+{
+ if (*var_smtp_tls_policy) {
+ tls_policy = maps_create(SMTP_X(TLS_POLICY), var_smtp_tls_policy,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+ if (*var_smtp_tls_per_site)
+ msg_warn("%s ignored when %s is not empty.",
+ SMTP_X(TLS_PER_SITE), SMTP_X(TLS_POLICY));
+ return;
+ }
+ if (*var_smtp_tls_per_site) {
+ tls_per_site = maps_create(SMTP_X(TLS_PER_SITE), var_smtp_tls_per_site,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+ }
+}
+
+/* policy_name - printable tls policy level */
+
+static const char *policy_name(int tls_level)
+{
+ const char *name = str_tls_level(tls_level);
+
+ if (name == 0)
+ name = "unknown";
+ return name;
+}
+
+/* tls_site_lookup - look up per-site TLS security level */
+
+static void tls_site_lookup(int *site_level, const char *site_name,
+ const char *site_class)
+{
+ const char *lookup;
+
+ /*
+ * Look up a non-default policy. In case of multiple lookup results, the
+ * precedence order is a permutation of the TLS enforcement level order:
+ * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more
+ * specific policy including NONE, otherwise we choose the stronger
+ * enforcement level.
+ */
+ if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) {
+ if (!strcasecmp(lookup, "NONE")) {
+ /* NONE overrides MAY or NOTFOUND. */
+ if (*site_level <= TLS_LEV_MAY)
+ *site_level = TLS_LEV_NONE;
+ } else if (!strcasecmp(lookup, "MAY")) {
+ /* MAY overrides NOTFOUND but not NONE. */
+ if (*site_level < TLS_LEV_NONE)
+ *site_level = TLS_LEV_MAY;
+ } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) {
+ if (*site_level < TLS_LEV_ENCRYPT)
+ *site_level = TLS_LEV_ENCRYPT;
+ } else if (!strcasecmp(lookup, "MUST")) {
+ if (*site_level < TLS_LEV_VERIFY)
+ *site_level = TLS_LEV_VERIFY;
+ } else {
+ msg_warn("Table %s: ignoring unknown TLS policy '%s' for %s %s",
+ var_smtp_tls_per_site, lookup, site_class, site_name);
+ }
+ } else if (tls_per_site->error) {
+ msg_fatal("%s lookup error for %s", tls_per_site->title, site_name);
+ }
+}
+
+/* tls_policy_lookup_one - look up destination TLS policy */
+
+static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
+ const char *site_name,
+ const char *site_class)
+{
+ const char *lookup;
+ char *policy;
+ char *saved_policy;
+ char *tok;
+ const char *err;
+ char *name;
+ char *val;
+ static VSTRING *cbuf;
+
+#undef FREE_RETURN
+#define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0)
+
+ if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
+ if (tls_policy->error) {
+ msg_fatal("%s lookup error for %s",
+ tls_policy->title, site_name);
+ /* XXX session->stream has no longjmp context yet. */
+ }
+ return (0);
+ }
+ if (cbuf == 0)
+ cbuf = vstring_alloc(10);
+
+#define WHERE \
+ vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \
+ site_class, site_name))
+
+ saved_policy = policy = mystrdup(lookup);
+
+ if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) {
+ msg_warn("%s: invalid empty policy", WHERE);
+ *site_level = TLS_LEV_INVALID;
+ FREE_RETURN(1); /* No further lookups */
+ }
+ *site_level = tls_level_lookup(tok);
+ if (*site_level == TLS_LEV_INVALID) {
+ /* tls_level_lookup() logs no warning. */
+ msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
+ FREE_RETURN(1); /* No further lookups */
+ }
+
+ /*
+ * Warn about ignored attributes when TLS is disabled.
+ */
+ if (*site_level < TLS_LEV_MAY) {
+ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
+ msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
+ WHERE, tok);
+ FREE_RETURN(1);
+ }
+
+ /*
+ * Errors in attributes may have security consequences, don't ignore
+ * errors that can degrade security.
+ */
+ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) {
+ if ((err = split_nameval(tok, &name, &val)) != 0) {
+ *site_level = TLS_LEV_INVALID;
+ msg_warn("%s: malformed attribute/value pair \"%s\": %s",
+ WHERE, tok, err);
+ break;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "ciphers")) {
+ if (*val == 0) {
+ msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ *site_level = TLS_LEV_INVALID;
+ break;
+ }
+ if (tls->grade) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ *site_level = TLS_LEV_INVALID;
+ break;
+ }
+ tls->grade = mystrdup(val);
+ continue;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "protocols")) {
+ if (tls->protocols) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ *site_level = TLS_LEV_INVALID;
+ break;
+ }
+ tls->protocols = mystrdup(val);
+ continue;
+ }
+ /* Multiple instances per policy. */
+ if (!strcasecmp(name, "match")) {
+ char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":";
+
+ if (*site_level <= TLS_LEV_ENCRYPT) {
+ msg_warn("%s: attribute \"%s\" invalid at security level "
+ "\"%s\"", WHERE, name, policy_name(*site_level));
+ *site_level = TLS_LEV_INVALID;
+ break;
+ }
+ if (*val == 0) {
+ msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ *site_level = TLS_LEV_INVALID;
+ break;
+ }
+ if (tls->matchargv == 0)
+ tls->matchargv = argv_split(val, delim);
+ else
+ argv_split_append(tls->matchargv, val, delim);
+ continue;
+ }
+ /* Only one instance per policy. */
+ if (!strcasecmp(name, "exclude")) {
+ if (tls->exclusions) {
+ msg_warn("%s: attribute \"%s\" is specified multiple times",
+ WHERE, name);
+ *site_level = TLS_LEV_INVALID;
+ break;
+ }
+ tls->exclusions = vstring_strcpy(vstring_alloc(10), val);
+ continue;
+ } else {
+ msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
+ *site_level = TLS_LEV_INVALID;
+ break;
+ }
+ }
+ FREE_RETURN(1);
+}
+
+/* tls_policy_lookup - look up destination TLS policy */
+
+static void tls_policy_lookup(SMTP_TLS_SESS *tls, int *site_level,
+ const char *site_name,
+ const char *site_class)
+{
+
+ /*
+ * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These
+ * are never the domain part of localpart@domain, rather they are
+ * explicit nexthops from transport:nexthop, and match only the
+ * corresponding policy. Parent domain matching (below) applies only to
+ * sub-domains of the recipient domain.
+ */
+ if (!valid_hostname(site_name, DONT_GRIPE)) {
+ tls_policy_lookup_one(tls, site_level, site_name, site_class);
+ return;
+ }
+
+ /*
+ * XXX For clarity consider using ``do { .. } while'', instead of using
+ * ``while { .. }'' with loop control at the bottom.
+ */
+ while (1) {
+ /* Try the given domain */
+ if (tls_policy_lookup_one(tls, site_level, site_name, site_class))
+ return;
+ /* Re-try with parent domain */
+ if ((site_name = strchr(site_name + 1, '.')) == 0)
+ return;
+ }
+}
+
+/* set_cipher_grade - Set cipher grade and exclusions */
+
+static void set_cipher_grade(SMTP_TLS_SESS *tls)
+{
+ const char *mand_exclude = "";
+ const char *also_exclude = "";
+
+ /*
+ * Use main.cf cipher level if no per-destination value specified. With
+ * mandatory encryption at least encrypt, and with mandatory verification
+ * at least authenticate!
+ */
+ switch (tls->level) {
+ case TLS_LEV_INVALID:
+ case TLS_LEV_NONE:
+ return;
+
+ case TLS_LEV_MAY:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_ciph);
+ break;
+
+ case TLS_LEV_ENCRYPT:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_mand_ciph);
+ mand_exclude = var_smtp_tls_mand_excl;
+ also_exclude = "eNULL";
+ break;
+
+ case TLS_LEV_FPRINT:
+ case TLS_LEV_VERIFY:
+ case TLS_LEV_SECURE:
+ if (tls->grade == 0)
+ tls->grade = mystrdup(var_smtp_tls_mand_ciph);
+ mand_exclude = var_smtp_tls_mand_excl;
+ also_exclude = "aNULL";
+ break;
+ }
+
+#define ADD_EXCLUDE(vstr, str) \
+ do { \
+ if (*(str)) \
+ vstring_sprintf_append((vstr), "%s%s", \
+ VSTRING_LEN(vstr) ? " " : "", (str)); \
+ } while (0)
+
+ /*
+ * The "exclude" policy table attribute overrides main.cf exclusion
+ * lists.
+ */
+ if (tls->exclusions == 0) {
+ tls->exclusions = vstring_alloc(10);
+ ADD_EXCLUDE(tls->exclusions, var_smtp_tls_excl_ciph);
+ ADD_EXCLUDE(tls->exclusions, mand_exclude);
+ }
+ ADD_EXCLUDE(tls->exclusions, also_exclude);
+}
+
+/* smtp_tls_sess_alloc - session TLS policy parameters */
+
+SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *dest, const char *host,
+ unsigned port, int valid)
+{
+ const char *myname = "session_tls_init";
+ int global_level;
+ int site_level;
+ SMTP_TLS_SESS *tls = (SMTP_TLS_SESS *) mymalloc(sizeof(*tls));
+
+ tls->level = TLS_LEV_NONE;
+ tls->protocols = 0;
+ tls->grade = 0;
+ tls->exclusions = 0;
+ tls->matchargv = 0;
+
+ /*
+ * Compute the global TLS policy. This is the default policy level when
+ * no per-site policy exists. It also is used to override a wild-card
+ * per-site policy.
+ */
+ if (*var_smtp_tls_level) {
+ /* Require that var_smtp_tls_level is sanitized upon startup. */
+ global_level = tls_level_lookup(var_smtp_tls_level);
+ if (global_level == TLS_LEV_INVALID)
+ msg_panic("%s: invalid TLS security level: \"%s\"",
+ myname, var_smtp_tls_level);
+ } else if (var_smtp_enforce_tls) {
+ global_level = var_smtp_tls_enforce_peername ?
+ TLS_LEV_VERIFY : TLS_LEV_ENCRYPT;
+ } else {
+ global_level = var_smtp_use_tls ?
+ TLS_LEV_MAY : TLS_LEV_NONE;
+ }
+ if (msg_verbose)
+ msg_info("%s TLS level: %s", "global", policy_name(global_level));
+
+ /*
+ * Compute the per-site TLS enforcement level. For compatibility with the
+ * original TLS patch, this algorithm is gives equal precedence to host
+ * and next-hop policies.
+ */
+ site_level = TLS_LEV_NOTFOUND;
+
+ if (tls_policy) {
+ tls_policy_lookup(tls, &site_level, dest, "next-hop destination");
+ } else if (tls_per_site) {
+ tls_site_lookup(&site_level, dest, "next-hop destination");
+ if (strcasecmp(dest, host) != 0)
+ tls_site_lookup(&site_level, host, "server hostname");
+ if (msg_verbose)
+ msg_info("%s TLS level: %s", "site", policy_name(site_level));
+
+ /*
+ * Override a wild-card per-site policy with a more specific global
+ * policy.
+ *
+ * With the original TLS patch, 1) a per-site ENCRYPT could not override
+ * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy
+ * produced inconsistent results: it changed a global VERIFY into
+ * NONE, while producing MAY with all weaker global policy settings.
+ *
+ * With the current implementation, a combined per-site (NONE+MAY)
+ * consistently overrides global policy with NONE, and global policy
+ * can override only a per-site MAY wildcard. That is, specific
+ * policies consistently override wildcard policies, and
+ * (non-wildcard) per-site policies consistently override global
+ * policies.
+ */
+ if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY)
+ site_level = global_level;
+ }
+ if (site_level == TLS_LEV_NOTFOUND)
+ tls->level = global_level;
+ else
+ tls->level = site_level;
+
+ /*
+ * Use main.cf protocols setting if not set in per-destination table.
+ */
+ if (tls->level > TLS_LEV_NONE && tls->protocols == 0)
+ tls->protocols =
+ mystrdup((tls->level == TLS_LEV_MAY) ?
+ var_smtp_tls_proto : var_smtp_tls_mand_proto);
+
+ /*
+ * Compute cipher grade (if set in per-destination table, else
+ * set_cipher() uses main.cf settings) and security level dependent
+ * cipher exclusion list.
+ */
+ set_cipher_grade(tls);
+
+ /*
+ * Use main.cf cert_match setting if not set in per-destination table.
+ */
+ switch (tls->level) {
+ case TLS_LEV_INVALID:
+ case TLS_LEV_NONE:
+ case TLS_LEV_MAY:
+ case TLS_LEV_ENCRYPT:
+ case TLS_LEV_DANE:
+ break;
+ case TLS_LEV_FPRINT:
+ if (tls->matchargv == 0)
+ tls->matchargv =
+ argv_split(var_smtp_tls_fpt_cmatch, "\t\n\r, |");
+ break;
+ case TLS_LEV_VERIFY:
+ if (tls->matchargv == 0)
+ tls->matchargv =
+ argv_split(var_smtp_tls_vfy_cmatch, "\t\n\r, :");
+ break;
+ case TLS_LEV_SECURE:
+ if (tls->matchargv == 0)
+ tls->matchargv =
+ argv_split(var_smtp_tls_sec_cmatch, "\t\n\r, :");
+ break;
+ default:
+ msg_panic("unexpected TLS security level: %d",
+ tls->level);
+ }
+
+ if (msg_verbose && (tls_policy || tls_per_site))
+ msg_info("%s TLS level: %s", "effective", policy_name(tls->level));
+
+ return (tls);
+}
+
+/* smtp_sess_tls_free - free and return null pointer of same type */
+
+SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *tls)
+{
+
+ if (tls->protocols)
+ myfree(tls->protocols);
+ if (tls->grade)
+ myfree(tls->grade);
+ if (tls->exclusions)
+ vstring_free(tls->exclusions);
+ if (tls->matchargv)
+ argv_free(tls->matchargv);
+
+ myfree((char *) tls);
+ return (0);
+}
+
+#endif
const char *mdalg; /* default message digest algorithm */
/* Built-in vs external SSL_accept/read/write/shutdown support. */
VSTREAM *stream; /* Blocking-mode SMTP session */
+ int errordepth; /* Chain depth of error cert */
+ int errorcode; /* First error at error depth */
+ X509 *errorcert; /* Error certificate closest to leaf */
} TLS_SESS_STATE;
/*
extern char *tls_issuer_CN(X509 *, const TLS_SESS_STATE *);
extern const char *tls_dns_name(const GENERAL_NAME *, const TLS_SESS_STATE *);
extern int tls_verify_certificate_callback(int, X509_STORE_CTX *);
+extern void tls_log_verify_error(TLS_SESS_STATE *);
/*
* tls_fprint.c
TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext);
/*
- * Give them a clue. Problems with trust chain verification were logged
- * when the session was first negotiated, before the session was stored
+ * Give them a clue. Problems with trust chain verification are logged
+ * when the session is first negotiated, before the session is stored
* into the cache. We don't want mystery failures, so log the fact the
* real problem is to be found in the past.
*/
- if (TLScontext->session_reused
- && !TLS_CERT_IS_TRUSTED(TLScontext)
- && (TLScontext->log_mask & TLS_LOG_UNTRUSTED))
- msg_info("%s: re-using session with untrusted certificate, "
- "look for details earlier in the log", props->namaddr);
+ if (!TLS_CERT_IS_TRUSTED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
+ msg_info("%s: re-using session with untrusted certificate, "
+ "look for details earlier in the log", props->namaddr);
+ }
}
/* verify_extract_print - extract and verify peer fingerprint */
for (cpp = props->matchargv->argv; *cpp; ++cpp) {
if (strcasecmp(TLScontext->peer_fingerprint, *cpp) == 0
|| strcasecmp(TLScontext->peer_pkey_fprint, *cpp) == 0) {
- TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
+ TLScontext->peer_status |=
+ TLS_CERT_FLAG_TRUSTED | TLS_CERT_FLAG_MATCHED;
break;
}
}
const EVP_MD *md;
unsigned char md_buf[EVP_MAX_MD_SIZE];
unsigned int md_len;
- int i;
int ok = 1;
- char *result = 0;
/* Previously available in "init" routine. */
if ((md = EVP_get_digestbyname(mdalg)) == 0)
TLScontext->log_mask = log_mask;
TLScontext->namaddr = lowercase(mystrdup(namaddr));
TLScontext->mdalg = 0; /* Alias for props->mdalg */
+ TLScontext->errordepth = -1;
+ TLScontext->errorcode = X509_V_OK;
+ TLScontext->errorcert = 0;
return (TLScontext);
}
myfree(TLScontext->peer_fingerprint);
if (TLScontext->peer_pkey_fprint)
myfree(TLScontext->peer_pkey_fprint);
+ if (TLScontext->errorcert)
+ X509_free(TLScontext->errorcert);
myfree((char *) TLScontext);
}
/* #define TLS_INTERNAL
/* #include <tls.h>
/*
+/* int tls_verify_certificate_callback(ok, ctx)
+/* int ok;
+/* X509_STORE_CTX *ctx;
+/*
+/* int tls_log_verify_error(TLScontext)
+/* TLS_SESS_STATE *TLScontext;
+/*
/* char *tls_peer_CN(peercert, TLScontext)
/* X509 *peercert;
/* TLS_SESS_STATE *TLScontext;
/* const char *tls_dns_name(gn, TLScontext)
/* const GENERAL_NAME *gn;
/* TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/* tls_verify_certificate_callback() is called several times (directly
+/* or indirectly) from crypto/x509/x509_vfy.c. It collects errors
+/* and trust information at each element of the trust chain.
+/* The last call at depth 0 sets the verification status based
+/* on the cumulative winner (lowest depth) of errors vs. trust.
+/* We always return 1 (continue the handshake) and handle trust
+/* and peer-name verification problems at the application level.
/*
+/* tls_log_verify_error() (called only when we care about the
+/* peer certificate, that is not when opportunistic) logs the
+/* reason why the certificate failed to be verified.
/*
-/* int tls_verify_certificate_callback(ok, ctx)
-/* int ok;
-/* X509_STORE_CTX *ctx;
-/* DESCRIPTION
/* tls_peer_CN() returns the text CommonName for the peer
/* certificate subject, or an empty string if no CommonName was
/* found. The result is allocated with mymalloc() and must be
/* are found, a null string is returned instead. Further sanity
/* checks may be added if the need arises.
/*
-/* tls_verify_callback() is called several times (directly or
-/* indirectly) from crypto/x509/x509_vfy.c. It is called as
-/* a final check, and if it returns "0", the handshake is
-/* immediately shut down and the connection fails.
-/*
-/* Postfix/TLS has two modes, the "opportunistic" mode and
-/* the "enforce" mode:
-/*
-/* In the "opportunistic" mode we never want the connection
-/* to fail just because there is something wrong with the
-/* peer's certificate. After all, we would have sent or received
-/* the mail even if TLS weren't available. Therefore the
-/* return value is always "1".
-/*
-/* The SMTP client or server may require TLS (e.g. to protect
-/* passwords), while peer certificates are optional. In this
-/* case we must return "1" even when we are unhappy with the
-/* peer certificate. Only when peer certificates are required,
-/* certificate verification failure will result in immediate
-/* termination (return 0).
-/*
-/* The only error condition not handled inside the OpenSSL
-/* library is the case of a too-long certificate chain. We
-/* test for this condition only if "ok = 1", that is, if
-/* verification didn't fail because of some earlier problem.
-/*
/* Arguments:
/* .IP ok
/* Result of prior verification: non-zero means success. In
#define TLS_INTERNAL
#include <tls.h>
+/* update_error_state - safely stash away error state */
+
+static void update_error_state(TLS_SESS_STATE *TLScontext, int depth,
+ X509 *errorcert, int errorcode)
+{
+ /* No news is good news */
+ if (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth)
+ return;
+
+ /*
+ * The certificate pointer is stable during the verification callback,
+ * but may be freed after the callback returns. Since we delay error
+ * reporting till later, we bump the refcount so we can rely on it still
+ * being there until later.
+ */
+ if (TLScontext->errorcert != 0)
+ X509_free(TLScontext->errorcert);
+ if (errorcert != 0)
+ CRYPTO_add(&errorcert->references, 1, CRYPTO_LOCK_X509);
+ TLScontext->errorcert = errorcert;
+ TLScontext->errorcode = errorcode;
+}
+
/* tls_verify_certificate_callback - verify peer certificate info */
int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
X509 *cert;
int err;
int depth;
+ int max_depth;
SSL *con;
TLS_SESS_STATE *TLScontext;
- depth = X509_STORE_CTX_get_error_depth(ctx);
+ /* May be NULL as of OpenSSL 1.0, thanks for the API change! */
cert = X509_STORE_CTX_get_current_cert(ctx);
+ err = X509_STORE_CTX_get_error(ctx);
con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
TLScontext = SSL_get_ex_data(con, TLScontext_index);
/*
- * The callback function is called repeatedly, first with the root
- * certificate, and then with each intermediate certificate ending with
- * the peer certificate.
- *
- * With each call, the validity of the current certificate (usage bits,
- * attributes, expiration, ... checked by the OpenSSL library) is
- * available in the "ok" argument. Error details are available via
- * X509_STORE_CTX API.
- *
- * We never terminate the SSL handshake in the verification callback, rather
- * we allow the TLS handshake to continue, but mark the session as
- * unverified. The application is responsible for closing any sessions
- * with unverified credentials.
- *
- * Certificate chain depth limit violations are mis-reported by the OpenSSL
- * library, from SSL_CTX_set_verify(3):
+ * Certificate chain depth limit violations are mis-reported by the
+ * OpenSSL library, from SSL_CTX_set_verify(3):
*
* The certificate verification depth set with SSL[_CTX]_verify_depth()
* stops the verification at a certain depth. The error message produced
* present at this depth. This disambiguates trust chain truncation from
* an incomplete trust chain.
*/
- if (depth >= SSL_get_verify_depth(con)) {
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ max_depth = SSL_get_verify_depth(con) - 1;
+
+ /*
+ * We never terminate the SSL handshake in the verification callback,
+ * rather we allow the TLS handshake to continue, but mark the session as
+ * unverified. The application is responsible for closing any sessions
+ * with unverified credentials.
+ *
+ * When we have an explicit list of trusted CA fingerprints, record the
+ * smallest depth at which we find a trusted certificate. If this below
+ * the smallest error depth we win and the chain is trusted. Otherwise,
+ * the chain is untrusted. We make this decision *each* time we are
+ * called with depth == 0 (yes we may be called more than once).
+ */
+ if (max_depth >= 0 && depth > max_depth) {
+ update_error_state(TLScontext, depth, cert,
+ X509_V_ERR_CERT_CHAIN_TOO_LONG);
ok = 0;
- X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG);
+ }
+ if (ok == 0)
+ update_error_state(TLScontext, depth, cert, err);
+
+ /*
+ * The final depth zero call sets the verification status.
+ */
+ if (depth == 0) {
+ ok = TLScontext->errordepth < 0 ? 1 : 0;
+ X509_STORE_CTX_set_error(ctx, ok ? X509_V_OK : TLScontext->errorcode);
}
if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
- X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
- msg_info("%s: certificate verification depth=%d verify=%d subject=%s",
+ if (cert)
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+ else
+ strcpy(buf, "<unknown>");
+ msg_info("%s: depth=%d verify=%d subject=%s",
TLScontext->namaddr, depth, ok, printable(buf, '?'));
}
+ return (1);
+}
- /*
- * If no errors, or we are not logging verification errors, we are done.
- */
- if (ok || (TLScontext->log_mask & TLS_LOG_UNTRUSTED) == 0)
- return (1);
+/* tls_log_verify_error - Report final verification error status */
- /*
- * One counter-example is enough.
- */
- TLScontext->log_mask &= ~TLS_LOG_UNTRUSTED;
+void tls_log_verify_error(TLS_SESS_STATE *TLScontext)
+{
+ char buf[CCERT_BUFSIZ];
+ int err = TLScontext->errorcode;
+ X509 *cert = TLScontext->errorcert;
+ int depth = TLScontext->errordepth;
#define PURPOSE ((depth>0) ? "CA": TLScontext->am_server ? "client": "server")
+ if (err == X509_V_OK)
+ return;
+
/*
* Specific causes for verification failure.
*/
- switch (err = X509_STORE_CTX_get_error(ctx)) {
+ switch (err) {
+ case X509_V_ERR_CERT_UNTRUSTED:
+
+ /*
+ * We expect the error cert to be the leaf, but it is likely
+ * sufficient to omit it from the log, even less user confusion.
+ */
+ msg_info("certificate verification failed for %s: "
+ "not trusted by local or TLSA policy", TLScontext->namaddr);
+ break;
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
msg_info("certificate verification failed for %s: "
"self-signed certificate", TLScontext->namaddr);
* provided, but not found in CAfile/CApath. Either way, we don't
* trust it.
*/
- X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert),
- buf, sizeof(buf));
+ if (cert)
+ X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
+ else
+ strcpy(buf, "<unknown>");
msg_info("certificate verification failed for %s: untrusted issuer %s",
TLScontext->namaddr, printable(buf, '?'));
break;
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
msg_info("certificate verification failed for %s: "
"certificate chain longer than limit(%d)",
- TLScontext->namaddr, SSL_get_verify_depth(con) - 1);
+ TLScontext->namaddr, depth - 1);
break;
default:
msg_info("%s certificate verification failed for %s: num=%d:%s",
X509_verify_cert_error_string(err));
break;
}
-
- return (1);
}
#ifndef DONT_GRIPE
extern int non_blocking(int, int);
extern int close_on_exec(int, int);
extern int open_limit(int);
-extern int poll_fd(int, int, int, int);
+extern int poll_fd(int, int, int, int, int);
extern off_t get_file_limit(void);
extern void set_file_limit(off_t);
extern ssize_t peekfd(int);
extern ssize_t dummy_read(int, void *, size_t, int, void *);
extern ssize_t dummy_write(int, void *, size_t, int, void *);
-#define readable(fd) poll_fd((fd), POLL_FD_READ, 0, 1)
-#define writable(fd) poll_fd((fd), POLL_FD_WRITE, 0, 1)
+#define readable(fd) poll_fd((fd), POLL_FD_READ, 0, 1, 0)
+#define writable(fd) poll_fd((fd), POLL_FD_WRITE, 0, 1, 0)
-#define read_wait(fd, time_limit) poll_fd((fd), POLL_FD_READ, (time_limit), 0)
-#define write_wait(fd, time_limit) poll_fd((fd), POLL_FD_WRITE, (time_limit), 0)
+#define read_wait(fd, timeout) poll_fd((fd), POLL_FD_READ, (timeout), 0, -1)
+#define write_wait(fd, timeout) poll_fd((fd), POLL_FD_WRITE, (timeout), 0, -1)
extern int inet_windowsize;
extern void set_inet_windowsize(int, int);
/* int fd;
/* int time_limit;
/*
-/* int poll_fd(fd, request, time_limit, success_val)
+/* int poll_fd(fd, request, time_limit, true_res, false_res)
/* int fd;
/* int request;
/* int time_limit;
-/* int success_val;
+/* int true_res;
+/* int false_res;
/* DESCRIPTION
/* The functions in this module are macros that provide a
/* convenient interface to poll_fd().
/* value effects a poll (return immediately). A negative value
/* means wait until the requested POLL_FD_READ or POLL_FD_WRITE
/* condition becomes true.
-/* .IP success_val
+/* .IP true_res
/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE
/* condition is true.
+/* .IP false_res
+/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE
+/* condition is false.
/* DIAGNOSTICS
/* Panic: interface violation. All system call errors are fatal
/* unless specified otherwise.
/*
/* readable() and writable() return 1 when the requested
-/* condition is true, zero when it is false. They never return
-/* an error indication.
+/* POLL_FD_READ or POLL_FD_WRITE condition is true, zero when
+/* it is false. They never return an error indication.
/*
-/* read_wait() and write_wait() return zero when successful,
-/* -1 with errno set to ETIMEDOUT when the time limit was
-/* reached.
+/* read_wait() and write_wait() return zero when the requested
+/* POLL_FD_READ or POLL_FD_WRITE condition is true, -1 with
+/* errno set to ETIMEDOUT when it is false.
/*
-/* poll_fd() returns -1 with errno set to ETIMEDOUT when the
-/* time limit was reached, success_val if the requested
-/* POLL_FD_READ or POLL_FD_WRITE condition is true, and returns
-/* zero otherwise.
+/* poll_fd() returns true_res when the requested POLL_FD_READ
+/* or POLL_FD_WRITE condition is true, false_res when it is
+/* false. When poll_fd() returns a false_res value < 0, it
+/* also sets errno to ETIMEDOUT.
/* LICENSE
/* .ad
/* .fi
#define poll_fd_sysv poll_fd
#define USE_SYSV_POLL
#define USE_BSD_SELECT
-int poll_fd_bsd(int, int, int, int);
+int poll_fd_bsd(int, int, int, int, int);
/*
* Use select() only.
/* poll_fd_bsd - block with time_limit until file descriptor is ready */
-int poll_fd_bsd(int fd, int request, int time_limit, int success_val)
+int poll_fd_bsd(int fd, int request, int time_limit,
+ int true_res, int false_res)
{
fd_set req_fds;
fd_set *read_fds;
case 0:
if (temp_fd != -1)
(void) close(temp_fd);
- if (time_limit == 0) {
- return (0);
- } else {
+ if (false_res < 0)
errno = ETIMEDOUT;
- return (-1);
- }
+ return (false_res);
default:
if (temp_fd != -1)
(void) close(temp_fd);
- return (success_val);
+ return (true_res);
}
}
}
#ifdef USE_SYSV_POLL
#ifdef USE_SYSV_POLL_THEN_SELECT
-#define HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, success_val) \
- return (poll_fd_bsd((fd), (request), (time_limit), (success_val)))
+#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \
+ return (poll_fd_bsd((fd), (req), (time_limit), (true_res), (false_res)))
#else
-#define HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, success_val) \
+#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \
msg_fatal("poll: %m")
#endif
/* poll_fd_sysv - block with time_limit until file descriptor is ready */
-int poll_fd_sysv(int fd, int request, int time_limit, int success_val)
+int poll_fd_sysv(int fd, int request, int time_limit,
+ int true_res, int false_res)
{
struct pollfd pollfd;
WAIT_FOR_EVENT : time_limit * 1000)) {
case -1:
if (errno != EINTR)
- HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, success_val);
+ HANDLE_SYSV_POLL_ERROR(fd, request, time_limit,
+ true_res, false_res);
continue;
case 0:
- if (time_limit == 0) {
- return (0);
- } else {
+ if (false_res < 0)
errno = ETIMEDOUT;
- return (-1);
- }
+ return (false_res);
default:
if (pollfd.revents & POLLNVAL)
- HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, success_val);
- return (success_val);
+ HANDLE_SYSV_POLL_ERROR(fd, request, time_limit,
+ true_res, false_res);
+ return (true_res);
}
}
}