/*++
/* NAME
/* smtp_tls_policy 3
/* SUMMARY
/* SMTP_TLS_POLICY structure management
/* SYNOPSIS
/* #include "smtp.h"
/*
/* void smtp_tls_list_init()
/*
/* int smtp_tls_policy_cache_query(why, tls, iter)
/* DSN_BUF *why;
/* SMTP_TLS_POLICY *tls;
/* SMTP_ITERATOR *iter;
/*
/* void smtp_tls_policy_dummy(tls)
/* SMTP_TLS_POLICY *tls;
/*
/* void smtp_tls_policy_cache_flush()
/*
/* int smtp_tls_authorize_mx_hostname(tls, qname)
/* SMTP_TLS_POLICY *tls;
/* const char *qname;
/* DESCRIPTION
/* smtp_tls_list_init() initializes lookup tables used by the TLS
/* policy engine.
/*
/* smtp_tls_policy_cache_query() returns a shallow copy of the
/* cached SMTP_TLS_POLICY structure for the iterator's
/* destination, host, port and DNSSEC validation status.
/* This copy is guaranteed to be valid until the next
/* smtp_tls_policy_cache_query() or smtp_tls_policy_cache_flush()
/* call. The caller can override the TLS security level without
/* corrupting the policy cache.
/* When any required table or DNS lookups fail, the TLS level
/* is set to TLS_LEV_INVALID, the "why" argument is updated
/* with the error reason and the result value is zero (false).
/* When var_smtp_tls_enf_sts_mx_pat is not null, and a policy plugin
/* specifies a policy_type "sts" plus one or more mx_host_pattern
/* instances, transform the policy as follows: allow only MX hosts
/* that match an mx_host_pattern instance, and match a server
/* certificate against the server hostname.
/*
/* smtp_tls_policy_dummy() initializes a trivial, non-cached,
/* policy with TLS disabled.
/*
/* smtp_tls_policy_cache_flush() destroys the TLS policy cache
/* and contents.
/*
/* smtp_tls_authorize_mx_hostname() authorizes an MX host if the
/* name used for host lookup satisfies a TLS policy MX name
/* constraint (for example, an STS policy MX pattern), or if the
/* TLS policy has no name constraint.
/*
/* Arguments:
/* .IP why
/* A pointer to a DSN_BUF which holds error status information when
/* the TLS policy lookup fails.
/* .IP tls
/* Pointer to TLS policy storage.
/* .IP iter
/* The literal next-hop or fall-back destination including
/* the optional [] and including the :port or :service;
/* the name of the remote host after MX and CNAME expansions
/* (see smtp_cname_overrides_servername for the handling
/* of hostnames that resolve to a CNAME record);
/* the printable address of the remote host;
/* the remote port in network byte order;
/* the DNSSEC validation status of the host name lookup after
/* MX and CNAME expansions.
/* LICENSE
/* .ad
/* .fi
/* This software is free. You can do with it whatever you want.
/* The original author kindly requests that you acknowledge
/* the use of his software.
/* AUTHOR(S)
/* TLS support originally by:
/* Lutz Jaenicke
/* BTU Cottbus
/* Allgemeine Elektrotechnik
/* Universitaetsplatz 3-4
/* D-03044 Cottbus, Germany
/*
/* Updated by:
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*
/* Viktor Dukhovni
/*--*/
/* System library. */
#include <sys_defs.h>
#ifdef USE_TLS
#include <netinet/in.h> /* ntohs() for Solaris or BSD */
#include <arpa/inet.h> /* ntohs() for Linux or BSD */
#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 <sane_strtol.h>
#include <stringops.h>
#include <valid_hostname.h>
#include <valid_utf8_hostname.h>
#include <ctable.h>
#include <midna_domain.h>
/* Global library. */
#include <mail_params.h>
#include <maps.h>
#include <dsn_buf.h>
/* TLS library. */
#include <tlsrpt_wrapper.h>
/* DNS library. */
#include <dns.h>
/* Application-specific. */
#include "smtp.h"
/* XXX Cache size should scale with [sl]mtp_mx_address_limit. */
#define CACHE_SIZE 20
static CTABLE *policy_cache;
static int global_tls_level(void );
static void dane_init(SMTP_TLS_POLICY *, SMTP_ITERATOR *);
static MAPS *tls_policy; /* lookup table(s) */
static MAPS *tls_per_site; /* lookup table(s) */
/* match_sts_mx_host_pattern - match hostname against STS policy MX pattern */
static int match_sts_mx_host_pattern(const char *pattern, const char *qname)
{
const char *first_dot_in_qname;
/* Caller guarantees that inputs are in ASCII form. */
return (strcasecmp(qname, pattern) == 0
|| (pattern[0 ] == '*' && pattern[1 ] == '.' && pattern[2 ] != 0
&& (first_dot_in_qname = strchr(qname, '.' )) != 0
&& first_dot_in_qname > qname
&& strcasecmp(first_dot_in_qname + 1 , pattern + 2 ) == 0 ));
}
/* smtp_tls_authorize_mx_hostname - enforce applicable MX hostname policy */
int smtp_tls_authorize_mx_hostname(SMTP_TLS_POLICY *tls, const char *name)
{
#define SAFE_FOR_SMTP_TLS_ENF_STS_MX_PAT(tls) (var_smtp_tls_enf_sts_mx_pat \
&& (tls)->ext_policy_type != 0 \
&& strcasecmp((tls)->ext_policy_type, "sts" ) == 0 \
&& (tls)->matchargv != 0 && (tls)->ext_mx_host_patterns != 0 )
/* Enforce STS policy MX patterns. */
if (SAFE_FOR_SMTP_TLS_ENF_STS_MX_PAT(tls)) {
const char *aname;
char **pattp;
#ifndef NO_EAI
if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0 ) {
if (msg_verbose)
msg_info("%s asciified to %s" , name, aname);
} else
#endif
aname = name;
for (pattp = tls->ext_mx_host_patterns->argv; *pattp; pattp++) {
if (match_sts_mx_host_pattern(*pattp, aname)) {
if (msg_verbose)
msg_info("MX name '%s' matches STS MX pattern for '%s'" ,
aname, tls->ext_policy_domain ? tls->ext_policy_domain : "" );
return (1 );
}
}
msg_warn("MX name '%s' does not match STS MX pattern for '%s'" ,
aname, tls->ext_policy_domain ? tls->ext_policy_domain : "" );
return (0 );
}
/* No applicable policy name patterns. */
return (1 );
}
/* smtp_tls_list_init - initialize per-site policy lists */
void smtp_tls_list_init(void )
{
if (*var_smtp_tls_policy) {
tls_policy = maps_create(VAR_LMTP_SMTP(TLS_POLICY),
var_smtp_tls_policy,
DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
| DICT_FLAG_UTF8_REQUEST);
if (*var_smtp_tls_per_site)
msg_warn("%s ignored when %s is not empty." ,
VAR_LMTP_SMTP(TLS_PER_SITE), VAR_LMTP_SMTP(TLS_POLICY));
return ;
}
if (*var_smtp_tls_per_site) {
tls_per_site = maps_create(VAR_LMTP_SMTP(TLS_PER_SITE),
var_smtp_tls_per_site,
DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
| DICT_FLAG_UTF8_REQUEST);
}
}
/* 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;
}
#define MARK_INVALID(why, levelp) do { \
dsb_simple((why), "4.7.5" , "client TLS configuration problem" ); \
*(levelp) = TLS_LEV_INVALID; } while (0 )
/* tls_site_lookup - look up per-site TLS security level */
static void tls_site_lookup(SMTP_TLS_POLICY *tls, 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("%s: unknown TLS policy '%s' for %s %s" ,
tls_per_site->title, lookup, site_class, site_name);
MARK_INVALID(tls->why, site_level);
return ;
}
} else if (tls_per_site->error) {
msg_warn("%s: %s \" %s\": per-site table lookup error" ,
tls_per_site->title, site_class, site_name);
dsb_simple(tls->why, "4.3.0" , "Temporary lookup error" );
*site_level = TLS_LEV_INVALID;
return ;
}
return ;
}
/* tls_policy_lookup_one - look up destination TLS policy */
static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
const char *site_name,
const char *site_class)
{
const char *lookup;
char *policy;
char *saved_policy = 0 ;
char *tok;
char *name;
char *val;
static VSTRING *cbuf;
char *free_me = 0 ;
#undef FREE_RETURN
#define FREE_RETURN do { \
if (saved_policy) \
myfree(saved_policy); \
if (free_me) \
myfree(free_me); \
return ; \
} while (0 )
#define INVALID_RETURN(why, levelp) do { \
MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0 )
#define WHERE \
STR(vstring_sprintf(cbuf, "%s, %s \" %s\"" , \
tls_policy->title, site_class, site_name))
if (cbuf == 0 )
cbuf = vstring_alloc(10 );
if ((lookup = maps_find(tls_policy, site_name, 0 )) == 0 ) {
if (tls_policy->error) {
msg_warn("%s: policy table lookup error" , WHERE);
MARK_INVALID(tls->why, site_level);
}
return ;
}
saved_policy = policy = mystrdup(lookup);
if ((tok = mystrtokq(&policy, CHARS_COMMA_SP, CHARS_BRACE)) == 0 ) {
msg_warn("%s: invalid empty policy" , WHERE);
INVALID_RETURN(tls->why, site_level);
}
*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);
INVALID_RETURN(tls->why, site_level);
}
/*
* Warn about ignored attributes when TLS is disabled .
*/
if (*site_level < TLS_LEV_MAY) {
while ((tok = mystrtokq(&policy, CHARS_COMMA_SP, CHARS_BRACE)) != 0 )
msg_warn("%s: ignoring attribute \" %s\" with TLS disabled" ,
WHERE, tok);
FREE_RETURN;
}
/*
* Errors in attributes may have security consequences , don ' t ignore
* errors that can degrade security .
*
* Caution : normalize whitespace , to neutralize line break etc . characters
* inside the value portion of { name = value } .
*/
while ((tok = mystrtokq(&policy, CHARS_COMMA_SP, CHARS_BRACE)) != 0 ) {
const char *err;
#define EXTPAR_OPT (EXTPAR_FLAG_STRIP | EXTPAR_FLAG_NORMAL_WS)
if ((tok[0 ] == CHARS_BRACE[0 ]
&& (err = free_me = extpar(&tok, CHARS_BRACE, EXTPAR_OPT)) != 0 )
|| (err = split_nameval(tok, &name, &val)) != 0 ) {
msg_warn("%s: malformed attribute/value pair \" %s\": %s" ,
WHERE, tok, err);
INVALID_RETURN(tls->why, site_level);
}
/* Only one instance per policy. */
if (!strcasecmp(name, "ciphers" )) {
if (*val == 0 ) {
msg_warn("%s: attribute \" %s\" has empty value" , WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
if (tls->grade) {
msg_warn("%s: attribute \" %s\" is specified multiple times" ,
WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
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);
INVALID_RETURN(tls->why, site_level);
}
tls->protocols = mystrdup(val);
continue ;
}
/* Only one instance per policy. */
if (!strcasecmp(name, "servername" )) {
if (tls->sni) {
msg_warn("%s: attribute \" %s\" is specified multiple times" ,
WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
if (valid_hostname(val, DONT_GRIPE))
tls->sni = mystrdup(val);
else {
msg_warn("%s: \" %s=%s\" specifies an invalid hostname" ,
WHERE, name, val);
INVALID_RETURN(tls->why, site_level);
}
continue ;
}
/* Multiple instances per policy. */
if (!strcasecmp(name, "match" )) {
if (*val == 0 ) {
msg_warn("%s: attribute \" %s\" has empty value" , WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
switch (*site_level) {
default :
msg_warn("%s: attribute \" %s\" invalid at security level "
"\" %s\"" , WHERE, name, policy_name(*site_level));
INVALID_RETURN(tls->why, site_level);
break ;
case TLS_LEV_FPRINT:
if (tls->matchargv == 0 )
tls->matchargv = argv_split(val, "|" );
else
argv_split_append(tls->matchargv, val, "|" );
break ;
case TLS_LEV_VERIFY:
case TLS_LEV_SECURE:
if (tls->matchargv == 0 )
tls->matchargv = argv_split(val, ":" );
else
argv_split_append(tls->matchargv, val, ":" );
break ;
}
continue ;
}
/* Only one instance per policy. */
if (!strcasecmp(name, "exclude" )) {
if (tls->exclusions) {
msg_warn("%s: attribute \" %s\" is specified multiple times" ,
WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
tls->exclusions = vstring_strcpy(vstring_alloc(10 ), val);
continue ;
}
/* Multiple instances per policy. */
if (!strcasecmp(name, "tafile" )) {
/* Only makes sense if we're using CA-based trust */
if (!TLS_MUST_PKIX(*site_level)) {
msg_warn("%s: attribute \" %s\" invalid at security level"
" \" %s\"" , WHERE, name, policy_name(*site_level));
INVALID_RETURN(tls->why, site_level);
}
if (*val == 0 ) {
msg_warn("%s: attribute \" %s\" has empty value" , WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
if (!tls->dane)
tls->dane = tls_dane_alloc();
if (!tls_dane_load_trustfile(tls->dane, val)) {
INVALID_RETURN(tls->why, site_level);
}
continue ;
}
/* Last one wins. */
if (!strcasecmp(name, "connection_reuse" )) {
if (strcasecmp(val, "yes" ) == 0 ) {
tls->conn_reuse = 1 ;
} else if (strcasecmp(val, "no" ) == 0 ) {
tls->conn_reuse = 0 ;
} else {
msg_warn("%s: attribute \" %s\" has bad value: \" %s\"" ,
WHERE, name, val);
INVALID_RETURN(tls->why, site_level);
}
continue ;
}
/* Last one wins. */
if (!strcasecmp(name, "enable_rpk" )) {
/* Ultimately ignored at some security levels */
if (strcasecmp(val, "yes" ) == 0 ) {
tls->enable_rpk = 1 ;
} else if (strcasecmp(val, "no" ) == 0 ) {
tls->enable_rpk = 0 ;
} else {
msg_warn("%s: attribute \" %s\" has bad value: \" %s\"" ,
WHERE, name, val);
INVALID_RETURN(tls->why, site_level);
}
continue ;
}
/* Only one instance per policy. */
if (!strcasecmp(name, EXT_POLICY_TTL)) {
char *end;
long lval;
if (tls->ext_policy_ttl != EXT_POLICY_TTL_UNSET) {
msg_warn("%s: attribute \" %s\" is specified multiple times" ,
WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
if (!alldig(val) || ((lval = sane_strtol(val, &end, 10 )),
((tls->ext_policy_ttl = lval) != lval))
|| *end != 0 ) {
msg_warn("%s: attribute \" %s\" has a malformed value: \" %s\"" ,
WHERE, name, val);
INVALID_RETURN(tls->why, site_level);
}
continue ;
}
/* Only one instance per policy. */
if (!strcasecmp(name, EXT_POLICY_TYPE)) {
if (tls->ext_policy_type) {
msg_warn("%s: attribute \" %s\" is specified multiple times" ,
WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
if (!valid_tlsrpt_policy_type(val)) {
msg_warn("%s: attribute \" %s\" has an unexpected value: \" %s\"" ,
WHERE, name, val);
INVALID_RETURN(tls->why, site_level);
}
tls->ext_policy_type = mystrdup(val);
continue ;
}
/* Only one instance per policy. */
if (!strcasecmp(name, EXT_POLICY_DOMAIN)) {
if (tls->ext_policy_domain) {
msg_warn("%s: attribute \" %s\" is specified multiple times" ,
WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
if (!valid_hostname(val, DO_GRIPE)) {
msg_warn("%s: attribute \" %s\" has a malformed value: \" %s\"" ,
WHERE, name, val);
INVALID_RETURN(tls->why, site_level);
}
tls->ext_policy_domain = mystrdup(val);
continue ;
}
/* Multiple instances per policy are allowed. */
if (!strcasecmp(name, EXT_POLICY_STRING)) {
if (tls->ext_policy_strings == 0 )
tls->ext_policy_strings = argv_alloc(1 );
argv_add(tls->ext_policy_strings, val, (char *) 0 );
continue ;
}
/* Multiple instances per policy are allowed. */
if (!strcasecmp(name, EXT_MX_HOST_PATTERN)) {
if (tls->ext_mx_host_patterns == 0 )
tls->ext_mx_host_patterns = argv_alloc(1 );
argv_add(tls->ext_mx_host_patterns, val, (char *) 0 );
continue ;
}
/* Only one instance per policy. */
if (!strcasecmp(name, EXT_POLICY_FAILURE)) {
if (tls->ext_policy_failure != 0 ) {
msg_warn("%s: attribute \" %s\" is specified multiple times" ,
WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
if (!valid_tlsrpt_policy_failure(val)) {
msg_warn("%s: attribute \" %s\" has an unexpected value: \" %s\"" ,
WHERE, name, val);
INVALID_RETURN(tls->why, site_level);
}
tls->ext_policy_failure = mystrdup(val);
continue ;
}
msg_warn("%s: invalid attribute name: \" %s\"" , WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
if (tls->ext_policy_type == 0 ) {
if (tls->ext_policy_ttl != EXT_POLICY_TTL_UNSET
|| tls->ext_policy_strings
|| tls->ext_policy_domain || tls->ext_mx_host_patterns
|| tls->ext_policy_failure) {
msg_warn("%s: built-in policy has unexpected attribute "
"policy_ttl, policy_domain, policy_string, "
"mx_host_pattern or policy_failure" , WHERE);
INVALID_RETURN(tls->why, site_level);
}
}
if (SAFE_FOR_SMTP_TLS_ENF_STS_MX_PAT(tls)) {
argv_truncate(tls->matchargv, 0 );
argv_add(tls->matchargv, "hostname" , (char *) 0 );
}
FREE_RETURN;
}
/* tls_policy_lookup - look up destination TLS policy */
static void tls_policy_lookup(SMTP_TLS_POLICY *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 .
*
* XXX UNIX - domain connections query with the pathname as destination .
*/
if (!valid_utf8_hostname(var_smtputf8_enable, site_name, DONT_GRIPE)) {
tls_policy_lookup_one(tls, site_level, site_name, site_class);
return ;
}
do {
tls_policy_lookup_one(tls, site_level, site_name, site_class);
} while (*site_level == TLS_LEV_NOTFOUND
&& (site_name = strchr(site_name + 1 , '.' )) != 0 );
}
/* load_tas - load one or more ta files */
static int load_tas(TLS_DANE *dane, const char *files)
{
int ret = 0 ;
char *save = mystrdup(files);
char *buf = save;
char *file;
do {
if ((file = mystrtok(&buf, CHARS_COMMA_SP)) != 0 )
ret = tls_dane_load_trustfile(dane, file);
} while (file && ret);
myfree(save);
return (ret);
}
/* set_cipher_grade - Set cipher grade and exclusions */
static void set_cipher_grade(SMTP_TLS_POLICY *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_HALF_DANE:
case TLS_LEV_DANE:
case TLS_LEV_DANE_ONLY:
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);
}
/* policy_create - create SMTP TLS policy cache object (ctable call-back) */
static void *policy_create(const char *unused_key, void *context)
{
SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context;
int site_level;
const char *dest = STR(iter->dest);
const char *host = STR(iter->host);
/*
* Prepare a pristine policy object .
*/
SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof (*tls));
smtp_tls_policy_init(tls, dsb_create());
tls->conn_reuse = var_smtp_tls_conn_reuse;
tls->enable_rpk = var_smtp_tls_enable_rpk;
/*
* 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 .
*
* When " TLS - Required : no " is in effect , skip TLS policy lookup and limit
* the security level to " may " . Do not reset the security level after
* policy lookup , as that would result in errors . For example , when TLSA
* records are looked up for security level " dane " , and then the security
* level is reset to " may " , the activation of those TLSA records will
* fail .
*/
tls->level = global_tls_level();
site_level = TLS_LEV_NOTFOUND;
if (STATE_TLS_NOT_REQUIRED(iter->parent)) {
if (msg_verbose)
msg_info("%s: no tls policy lookup" , __func__);
if (var_smtp_tls_wrappermode) {
if (tls->level > TLS_LEV_ENCRYPT)
tls->level = TLS_LEV_ENCRYPT;
} else {
if (tls->level > TLS_LEV_MAY)
tls->level = TLS_LEV_MAY;
}
} else if (tls_policy) {
tls_policy_lookup(tls, &site_level, dest, "next-hop destination" );
} else if (tls_per_site) {
tls_site_lookup(tls, &site_level, dest, "next-hop destination" );
if (site_level != TLS_LEV_INVALID
&& strcasecmp_utf8(dest, host) != 0 )
tls_site_lookup(tls, &site_level, host, "server hostname" );
/*
* 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 && tls->level > TLS_LEV_MAY)
site_level = tls->level;
}
switch (site_level) {
default :
tls->level = site_level;
/* FALLTHROUGH */
case TLS_LEV_NOTFOUND:
break ;
case TLS_LEV_INVALID:
tls->level = site_level;
return ((void *) tls);
}
/*
* DANE initialization may change the security level to something else ,
* so do this early , so that we use the right level below . Note that
* " dane - only " changes to " dane " once we obtain the requisite TLSA
* records .
*/
if (TLS_DANE_BASED(tls->level))
dane_init(tls, iter);
if (tls->level == TLS_LEV_INVALID)
return ((void *) tls);
/*
* Use main . cf protocols and SNI settings 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);
if (tls->level > TLS_LEV_NONE && tls->sni == 0 ) {
if (!*var_smtp_tls_sni || valid_hostname(var_smtp_tls_sni, DONT_GRIPE))
tls->sni = mystrdup(var_smtp_tls_sni);
else {
msg_warn("\" %s = %s\" specifies an invalid hostname" ,
VAR_LMTP_SMTP(TLS_SNI), var_smtp_tls_sni);
MARK_INVALID(tls->why, &tls->level);
return ((void *) tls);
}
}
/*
* 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);
/*
* Even when soliciting raw public keys , synthesize TLSA RRs that also match
* certificates . Though this is fragile , it maintains compatibility with
* servers that never return RPKs .
*/
#define DONT_SUPPRESS_CERT_MATCH 0
/*
* 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_HALF_DANE:
case TLS_LEV_DANE:
case TLS_LEV_DANE_ONLY:
break ;
case TLS_LEV_FPRINT:
if (tls->dane == 0 )
tls->dane = tls_dane_alloc();
/* Process the specified fingerprint match patterns */
if (tls->matchargv) {
int i;
for (i = 0 ; i < tls->matchargv->argc; ++i) {
tls_dane_add_fpt_digests(tls->dane, DONT_SUPPRESS_CERT_MATCH,
tls->matchargv->argv[i], "" ,
smtp_mode);
}
} else {
tls_dane_add_fpt_digests(tls->dane, DONT_SUPPRESS_CERT_MATCH,
var_smtp_tls_fpt_cmatch, CHARS_COMMA_SP,
smtp_mode);
}
if (tls->dane->tlsa == 0 ) {
msg_warn("nexthop domain %s: configured at fingerprint "
"security level, but with no fingerprints to match." ,
dest);
MARK_INVALID(tls->why, &tls->level);
return ((void *) tls);
}
break ;
case TLS_LEV_VERIFY:
case TLS_LEV_SECURE:
if (tls->matchargv == 0 )
tls->matchargv =
argv_split(tls->level == TLS_LEV_VERIFY ?
var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch,
CHARS_COMMA_SP ":" );
if (*var_smtp_tls_tafile) {
if (tls->dane == 0 )
tls->dane = tls_dane_alloc();
if (tls->dane->tlsa == 0
&& !load_tas(tls->dane, var_smtp_tls_tafile)) {
MARK_INVALID(tls->why, &tls->level);
return ((void *) tls);
}
}
break ;
default :
msg_panic("unexpected TLS security level: %d" , tls->level);
}
if (msg_verbose && tls->level != global_tls_level())
msg_info("%s TLS level: %s" , "effective" , policy_name(tls->level));
return ((void *) tls);
}
/* policy_delete - free no longer cached policy (ctable call-back) */
static void policy_delete(void *item, void *unused_context)
{
SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) item;
if (tls->protocols)
myfree(tls->protocols);
if (tls->sni)
myfree(tls->sni);
if (tls->grade)
myfree(tls->grade);
if (tls->exclusions)
vstring_free(tls->exclusions);
if (tls->matchargv)
argv_free(tls->matchargv);
if (tls->dane)
tls_dane_free(tls->dane);
dsb_free(tls->why);
if (tls->ext_policy_type)
myfree(tls->ext_policy_type);
if (tls->ext_policy_domain)
myfree(tls->ext_policy_domain);
if (tls->ext_policy_strings)
argv_free(tls->ext_policy_strings);
if (tls->ext_mx_host_patterns)
argv_free(tls->ext_mx_host_patterns);
if (tls->ext_policy_failure)
myfree(tls->ext_policy_failure);
myfree((void *) tls);
}
/* smtp_tls_policy_cache_query - cached lookup of TLS policy */
int smtp_tls_policy_cache_query(DSN_BUF *why, SMTP_TLS_POLICY *tls,
SMTP_ITERATOR *iter)
{
VSTRING *key;
/*
* Create an empty TLS Policy cache on the fly .
*/
if (policy_cache == 0 )
policy_cache =
ctable_create(CACHE_SIZE, policy_create, policy_delete, (void *) 0 );
/*
* Query the TLS Policy cache , with a search key that reflects our shared
* values that also appear in other cache and table search keys .
*/
key = vstring_alloc(100 );
smtp_key_prefix(key, ":" , iter, SMTP_KEY_FLAG_CUR_NEXTHOP
| SMTP_KEY_FLAG_HOSTNAME
| SMTP_KEY_FLAG_PORT);
ctable_newcontext(policy_cache, (void *) iter);
*tls = *(SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key));
vstring_free(key);
/*
* Report errors . Both error and non - error results are cached . We must
* therefore copy the cached DSN buffer content to the caller ' s buffer .
*/
if (tls->level == TLS_LEV_INVALID) {
/* XXX Simplify this by implementing a "copy" primitive. */
dsb_update(why,
STR(tls->why->status), STR(tls->why->action),
STR(tls->why->mtype), STR(tls->why->mname),
STR(tls->why->dtype), STR(tls->why->dtext),
"%s" , STR(tls->why->reason));
return (0 );
} else {
return (1 );
}
}
/* smtp_tls_policy_cache_flush - flush TLS policy cache */
void smtp_tls_policy_cache_flush(void )
{
if (policy_cache != 0 ) {
ctable_free(policy_cache);
policy_cache = 0 ;
}
}
/* global_tls_level - parse and cache var_smtp_tls_level */
static int global_tls_level(void )
{
static int l = TLS_LEV_NOTFOUND;
if (l != TLS_LEV_NOTFOUND)
return l;
/*
* 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 .
*
* We require that the global level is valid on startup .
*/
if (*var_smtp_tls_level) {
if ((l = tls_level_lookup(var_smtp_tls_level)) == TLS_LEV_INVALID)
msg_fatal("invalid tls security level: \" %s\"" , var_smtp_tls_level);
} else if (var_smtp_enforce_tls)
l = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT;
else
l = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE;
if (msg_verbose)
msg_info("%s TLS level: %s" , "global" , policy_name(l));
return l;
}
#define NONDANE_CONFIG 0 /* Administrator's fault */
#define NONDANE_DEST 1 /* Remote server's fault */
#define DANE_CANTAUTH 2 /* Remote server's fault */
static void PRINTFLIKE(4 , 5 ) dane_incompat(SMTP_TLS_POLICY *tls,
SMTP_ITERATOR *iter,
int errtype,
const char *fmt,...)
{
va_list ap;
va_start(ap, fmt);
if (tls->level == TLS_LEV_DANE) {
tls->level = (errtype == DANE_CANTAUTH) ? TLS_LEV_ENCRYPT : TLS_LEV_MAY;
if (errtype == NONDANE_CONFIG)
vmsg_warn(fmt, ap);
else if (msg_verbose)
vmsg_info(fmt, ap);
} else { /* dane-only */
if (errtype == NONDANE_CONFIG) {
vmsg_warn(fmt, ap);
MARK_INVALID(tls->why, &tls->level);
} else {
tls->level = TLS_LEV_INVALID;
vdsb_simple(tls->why, "4.7.5" , fmt, ap);
}
}
va_end(ap);
}
/* dane_init - special initialization for "dane" security level */
static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
{
TLS_DANE *dane;
if (!iter->port) {
msg_warn("%s: the \" dane\" security level is invalid for delivery via"
" unix-domain sockets" , STR(iter->dest));
MARK_INVALID(tls->why, &tls->level);
return ;
}
if (!tls_dane_avail()) {
dane_incompat(tls, iter, NONDANE_CONFIG,
"%s: %s configured, but no requisite library support" ,
STR(iter->dest), policy_name(tls->level));
return ;
}
if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS)
|| smtp_dns_support != SMTP_DNS_DNSSEC) {
dane_incompat(tls, iter, NONDANE_CONFIG,
"%s: %s configured with dnssec lookups disabled" ,
STR(iter->dest), policy_name(tls->level));
return ;
}
/*
* If we ignore MX lookup errors , we also ignore DNSSEC security problems
* and thus avoid any reasonable expectation that we get the right DANE
* key material .
*/
if (smtp_mode && var_ign_mx_lookup_err) {
dane_incompat(tls, iter, NONDANE_CONFIG,
"%s: %s configured with MX lookup errors ignored" ,
STR(iter->dest), policy_name(tls->level));
return ;
}
/*
* This is not optional , code in tls_dane . c assumes that the nexthop
* qname is already an fqdn . If we ' re using these flags to go from qname
* to rname , the assumption is invalid . Likewise we cannot add the qname
* to certificate name checks , . . .
*/
if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) {
dane_incompat(tls, iter, NONDANE_CONFIG,
"%s: dns resolver options incompatible with %s TLS" ,
STR(iter->dest), policy_name(tls->level));
return ;
}
/*
* When the MX name is present and insecure , DANE may not apply , we then
* either fail if DANE is mandatory or use regular opportunistic TLS if
* the insecure MX level is " may " .
*/
if (iter->mx && !iter->mx->dnssec_valid
&& (tls->level == TLS_LEV_DANE_ONLY ||
smtp_tls_insecure_mx_policy <= TLS_LEV_MAY)) {
dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination" );
return ;
}
/* When TLSA lookups fail, we defer the message */
if ((dane = tls_dane_resolve(iter->port, "tcp" , iter->rr,
var_smtp_tls_force_tlsa)) == 0 ) {
tls->level = TLS_LEV_INVALID;
dsb_simple(tls->why, "4.7.5" , "TLSA lookup error for %s:%u" ,
STR(iter->host), ntohs(iter->port));
return ;
}
if (tls_dane_notfound(dane)) {
dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found" );
tls_dane_free(dane);
return ;
}
/*
* Some TLSA records found , but none usable , per
*
* https : //tools.ietf.org/html/draft-ietf-dane-srv-02#section-4
*
* we MUST use TLS , and SHALL use full PKIX certificate checks . The latter
* would be unwise for SMTP : no human present to " click ok " and risk of
* non - delivery in most cases exceeds risk of interception .
*
* We also have a form of Goedel ' s incompleteness theorem in play : any list
* of public root CA certs is either incomplete or inconsistent ( for any
* given verifier some of the CAs are surely not trustworthy ) .
*/
if (tls_dane_unusable(dane)) {
dane_incompat(tls, iter, DANE_CANTAUTH, "TLSA records unusable" );
tls_dane_free(dane);
return ;
}
/*
* Perhaps downgrade to " encrypt " if MX is insecure .
*/
if (iter->mx && !iter->mx->dnssec_valid) {
if (smtp_tls_insecure_mx_policy == TLS_LEV_ENCRYPT) {
dane_incompat(tls, iter, DANE_CANTAUTH,
"Verification not possible, MX RRset is insecure" );
tls_dane_free(dane);
return ;
}
if (tls->level != TLS_LEV_DANE
|| smtp_tls_insecure_mx_policy != TLS_LEV_DANE)
msg_panic("wrong state for insecure MX host DANE policy" );
/* For correct logging in tls_client_start() */
tls->level = TLS_LEV_HALF_DANE;
}
/*
* With DANE trust anchors , peername matching is not configurable .
*/
if (dane->tlsa != 0 ) {
tls->matchargv = argv_alloc(2 );
argv_add(tls->matchargv, dane->base_domain, ARGV_END);
if (iter->mx) {
if (strcmp(iter->mx->qname, iter->mx->rname) == 0 )
argv_add(tls->matchargv, iter->mx->qname, ARGV_END);
else
argv_add(tls->matchargv, iter->mx->rname,
iter->mx->qname, ARGV_END);
}
} else
msg_panic("empty DANE match list" );
tls->dane = dane;
return ;
}
#endif
Messung V0.5 in Prozent C=71 H=94 G=83
¤ Dauer der Verarbeitung: 0.35 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland