Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  smtp_tls_policy.c

  Sprache: C
 

/*++
/* 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(45) 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






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002