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_UNUSABLE) ? 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);
}
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;
}
Exemple #3
0
/* 
 * Verifies the given certificate against a certificate list of
 * trusted CAs.
 *
 * Returns only 0 or 1. If 1 it means that the certificate 
 * was successfuly verified.
 *
 * 'flags': an OR of the gnutls_certificate_verify_flags enumeration.
 *
 * Output will hold some extra information about the verification
 * procedure.
 */
static unsigned
verify_crt(gnutls_x509_crt_t cert,
			    const gnutls_x509_crt_t * trusted_cas,
			    int tcas_size, unsigned int flags,
			    unsigned int *output,
			    verify_state_st *vparams,
			    unsigned end_cert)
{
	gnutls_datum_t cert_signed_data = { NULL, 0 };
	gnutls_datum_t cert_signature = { NULL, 0 };
	gnutls_x509_crt_t issuer = NULL;
	int issuer_version, hash_algo;
	unsigned result = 1;
	const mac_entry_st * me;
	unsigned int out = 0, usage;
	int sigalg, ret;

	if (output)
		*output = 0;

	if (vparams->max_path == 0) {
		MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
		/* bail immediately, to avoid inconistency  */
		goto cleanup;
	}
	vparams->max_path--;

	if (tcas_size >= 1)
		issuer = find_issuer(cert, trusted_cas, tcas_size);

	ret =
	    _gnutls_x509_get_signed_data(cert->cert, &cert->der, "tbsCertificate",
					 &cert_signed_data);
	if (ret < 0) {
		MARK_INVALID(0);
		cert_signed_data.data = NULL;
	}

	ret =
	    _gnutls_x509_get_signature(cert->cert, "signature",
				       &cert_signature);
	if (ret < 0) {
		MARK_INVALID(0);
		cert_signature.data = NULL;
	}

	ret =
	    _gnutls_x509_get_signature_algorithm(cert->cert,
						 "signatureAlgorithm.algorithm");
	if (ret < 0) {
		MARK_INVALID(0);
	}
	sigalg = ret;

	/* issuer is not in trusted certificate
	 * authorities.
	 */
	if (issuer == NULL) {
		MARK_INVALID(GNUTLS_CERT_SIGNER_NOT_FOUND);
	} else {
		if (vparams->nc != NULL) {
			/* append the issuer's constraints */
			ret = gnutls_x509_crt_get_name_constraints(issuer, vparams->nc, 
				GNUTLS_NAME_CONSTRAINTS_FLAG_APPEND, NULL);
			if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
				MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
				goto nc_done;
			}

			/* only check name constraints in server certificates, not CAs */
			if (end_cert != 0) {
				ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_DNSNAME, cert);
				if (ret == 0) {
					MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
					goto nc_done;
				}

				ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_RFC822NAME, cert);
				if (ret == 0) {
					MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
					goto nc_done;
				}

				ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_DN, cert);
				if (ret == 0) {
					MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
					goto nc_done;
				}

				ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_URI, cert);
				if (ret == 0) {
					MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
					goto nc_done;
				}

				ret = gnutls_x509_name_constraints_check_crt(vparams->nc, GNUTLS_SAN_IPADDRESS, cert);
				if (ret == 0) {
					MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
					goto nc_done;
				}
			}
		}

 nc_done:
		if (vparams->tls_feat != NULL) {
			/* append the issuer's constraints */
			ret = gnutls_x509_crt_get_tlsfeatures(issuer, vparams->tls_feat, GNUTLS_EXT_FLAG_APPEND, NULL);
			if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
				MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
				goto feat_done;
			}

			ret = gnutls_x509_tlsfeatures_check_crt(vparams->tls_feat, cert);
			if (ret == 0) {
				MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
				goto feat_done;
			}
		}

 feat_done:
		issuer_version = gnutls_x509_crt_get_version(issuer);

		if (issuer_version < 0) {
			MARK_INVALID(0);
		} else if (!(flags & GNUTLS_VERIFY_DISABLE_CA_SIGN) &&
			   ((flags & GNUTLS_VERIFY_DO_NOT_ALLOW_X509_V1_CA_CRT)
			    || issuer_version != 1)) {
			if (check_if_ca(cert, issuer, &vparams->max_path, flags) != 1) {
				MARK_INVALID(GNUTLS_CERT_SIGNER_NOT_CA);
			}

			ret =
			    gnutls_x509_crt_get_key_usage(issuer, &usage, NULL);
			if (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
				if (ret < 0) {
					MARK_INVALID(0);
				} else if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) {
					MARK_INVALID(GNUTLS_CERT_SIGNER_CONSTRAINTS_FAILURE);
				}
			}
		}

		if (sigalg >= 0) {
			hash_algo = gnutls_sign_get_hash_algorithm(sigalg);
			me = mac_to_entry(hash_algo);
		} else {
			me = NULL;
		}

		if (me == NULL) {
			MARK_INVALID(0);
		} else if (cert_signed_data.data != NULL &&
			   cert_signature.data != NULL) {
			ret =
			    _gnutls_x509_verify_data(me,
						     &cert_signed_data,
						     &cert_signature,
						     issuer);
			if (ret == GNUTLS_E_PK_SIG_VERIFY_FAILED) {
				MARK_INVALID(GNUTLS_CERT_SIGNATURE_FAILURE);
			} else if (ret < 0) {
				MARK_INVALID(0);
			}
		}
	}

	/* we always check the issuer for unsupported critical extensions */
	if (issuer && check_for_unknown_exts(issuer) != 0) {
		if (!(flags & GNUTLS_VERIFY_IGNORE_UNKNOWN_CRIT_EXTENSIONS)) {
			MARK_INVALID(GNUTLS_CERT_UNKNOWN_CRIT_EXTENSIONS);
		}
	}

	/* we only check the end-certificate for critical extensions; that
	 * way do not perform this check twice on the certificates when
	 * verifying a large list */
	if (end_cert && check_for_unknown_exts(cert) != 0) {
		if (!(flags & GNUTLS_VERIFY_IGNORE_UNKNOWN_CRIT_EXTENSIONS)) {
			MARK_INVALID(GNUTLS_CERT_UNKNOWN_CRIT_EXTENSIONS);
		}
	}

	if (sigalg >= 0) {
		if (is_level_acceptable(cert, issuer, sigalg, flags) == 0) {
			MARK_INVALID(GNUTLS_CERT_INSECURE_ALGORITHM);
		}

		/* If the certificate is not self signed check if the algorithms
		 * used are secure. If the certificate is self signed it doesn't
		 * really matter.
		 */
		if (gnutls_sign_is_secure(sigalg) == 0 &&
		    _gnutls_is_broken_sig_allowed(sigalg, flags) == 0 &&
		    is_issuer(cert, cert) == 0) {
			MARK_INVALID(GNUTLS_CERT_INSECURE_ALGORITHM);
		}
	}

	/* Check activation/expiration times
	 */
	if (!(flags & GNUTLS_VERIFY_DISABLE_TIME_CHECKS)) {
		/* check the time of the issuer first */
		if (issuer != NULL &&
		    !(flags & GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS)) {
			out |= check_time_status(issuer, vparams->now);
			if (out != 0) {
				gnutls_assert();
				result = 0;
			}
		}

		out |= check_time_status(cert, vparams->now);
		if (out != 0) {
			gnutls_assert();
			result = 0;
		}
	}

      cleanup:
	if (output)
		*output |= out;

	if (vparams->func) {
		if (result == 0) {
			out |= GNUTLS_CERT_INVALID;
		}
		vparams->func(cert, issuer, NULL, out);
	}
	_gnutls_free_datum(&cert_signed_data);
	_gnutls_free_datum(&cert_signature);

	return result;
}
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 does not apply. */
    if (iter->mx && !iter->mx->dnssec_valid) {
	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_UNUSABLE, "TLSA records unusable");
	tls_dane_free(dane);
	return;
    }

    /*
     * With DANE trust anchors, peername matching is not configurable.
     */
    if (TLS_DANE_HASTA(dane)) {
	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 if (!TLS_DANE_HASEE(dane))
	msg_panic("empty DANE match list");
    tls->dane = dane;
    tls->level = TLS_LEV_DANE;
    return;
}
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());

    /*
     * 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.
     */
    tls->level = global_tls_level();
    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(tls, &site_level, dest, "next-hop destination");
	if (site_level != TLS_LEV_INVALID
	    && strcasecmp(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;
    case TLS_LEV_NOTFOUND:
	break;
    case TLS_LEV_INVALID:
	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->level == TLS_LEV_DANE || tls->level == TLS_LEV_DANE_ONLY)
	dane_init(tls, iter);
    if (tls->level == TLS_LEV_INVALID)
	return ((void *) tls);

    /*
     * 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->dane == 0)
	    tls->dane = tls_dane_alloc();
	if (!TLS_DANE_HASEE(tls->dane)) {
	    tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst,
				    var_smtp_tls_fpt_cmatch, "\t\n\r, ");
	    if (!TLS_DANE_HASEE(tls->dane)) {
		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,
			   "\t\n\r, :");
	if (*var_smtp_tls_tafile) {
	    if (tls->dane == 0)
		tls->dane = tls_dane_alloc();
	    if (!TLS_DANE_HASTA(tls->dane)
		&& !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);
}
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;
    char   *tok;
    const char *err;
    char   *name;
    char   *val;
    static VSTRING *cbuf;

#undef FREE_RETURN
#define FREE_RETURN do { myfree(saved_policy); 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 = mystrtok(&policy, "\t\n\r ,")) == 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 = mystrtok(&policy, "\t\n\r ,")) != 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.
     */
    while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) {
	if ((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;
	}
	/* 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->dane)
		    tls->dane = tls_dane_alloc();
		tls_dane_add_ee_digests(tls->dane,
					var_smtp_tls_fpt_dgst, 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;
	}
	msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
	INVALID_RETURN(tls->why, site_level);
    }

    FREE_RETURN;
}