Home | History | Annotate | Line # | Download | only in smtp
      1 /*	$NetBSD: smtp_tlsrpt.c,v 1.3 2026/05/09 18:49:20 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	smtp_tlsrpt 3
      6 /* SUMMARY
      7 /*	TLSRPT support for the SMTP protocol engine
      8 /* SYNOPSIS
      9 /*	#include <smtp_tlsrpt.h>
     10 /*
     11 /*	int	smtp_tlsrpt_post_jail(
     12 /*	const char *sockname_pname,
     13 /*	const char *sockname_pval)
     14 /*
     15 /*	void	smtp_tlsrpt_create_wrapper(
     16 /*	SMTP_STATE *state,
     17 /*	const char *domain)
     18 /*
     19 /*	void	smtp_tlsrpt_set_tls_policy(
     20 /*	SMTP_STATE *state)
     21 /*
     22 /*	void	smtp_tlsrpt_set_tcp_connection(
     23 /*	SMTP_STATE *state)
     24 /*
     25 /*	void	smtp_tlsrpt_set_ehlo_resp(
     26 /*	SMTP_STATE *state,
     27 /*	const char *ehlo_resp)
     28 /* DESCRIPTION
     29 /*	This module populates a TLSRPT_WRAPPER object with  a)
     30 /*	remote TLSRPT policy information, b) remote TLSA or STS policy
     31 /*	information, and c) selected SMTP connection information. This
     32 /*	object is passed to a TLS protocol engine, which may run in a
     33 /*	different process than the SMTP protocol engine. The TLS protocol
     34 /*	engine uses the TLSRPT_WRAPPER object to report a TLS handshake
     35 /*	error to a TLSRPT library. The SMTP protocol engine uses the
     36 /*	object to report a TLS handshake error or success.
     37 /*
     38 /*	smtp_tls_post_jail() does configuration sanity checks and returns
     39 /*	0 if successful, i.e. TLSRPT support is properly
     40 /*	configured. Otherwise it returns -1 and logs a warning. Arguments:
     41 /* .IP sockname_pname
     42 /*	The name of a configuration parameter for the endpoint that
     43 /*	is managed by TLSRPT infrastructure. This name is used in a
     44 /*	diagnostic message.
     45 /* .IP sockname_pval
     46 /*	The value of said parameter.
     47 /* .PP
     48 /*	smtp_tlsrpt_create_wrapper() destroys a TLSRPT_WRAPPER referenced
     49 /*	by state->tlsrpt, and looks for a TLSRPT policy for the specified
     50 /*	domain. If one policy exists, smtp_tlsrpt_create_wrapper()
     51 /*	attaches a TLSRPT_WRAPPER instance to state->tlsrpt. Otherwise,
     52 /*	state->tlsrpt will be null, and other smtp_tlsrpt_* calls must not
     53 /*	be made. The TLSRPT_WRAPPER instance may be reused for different
     54 /*	SMTP connections for the same TLSRPT policy domain. Arguments:
     55 /* .IP domain
     56 /*	The name of a domain that may publish a TLSRPT policy. An
     57 /*	internationalized domain name may be in U-label or A-label form
     58 /*	(the U-label form will be converted to A-label internally).
     59 /* .PP
     60 /*	smtp_tlsrpt_set_tls_policy() updates the TLSRPT_WRAPPER
     61 /*	object with DANE or STS TLS policy information, and clears
     62 /*	information that was added with smtp_tlsrpt_set_tcp_connection()
     63 /*	or smtp_tlsrpt_set_ehlo_resp().
     64 /* .PP
     65 /*	smtp_tlsrpt_set_tcp_connection() updates the TLSRPT_WRAPPER
     66 /*	object with TCP connection properties.
     67 /* .PP
     68 /*	smtp_tlsrpt_set_ehlo_resp() updates the TLSRPT_WRAPPER object
     69 /*	with the SMTP server's EHLO response.
     70 /* BUGS
     71 /*	This module inherits all limitations from tlsrpt_wrapper(3).
     72 /* SEE ALSO
     73 /*	tlsrpt_wrapper(3) TLSRPT support for the TLS protocol engine.
     74 /* LICENSE
     75 /* .ad
     76 /* .fi
     77 /*	The Secure Mailer license must be distributed with this software.
     78 /* AUTHOR(S)
     79 /*	Wietse Venema
     80 /*	porcupine.org
     81 /*--*/
     82 
     83  /*
     84   * System library.
     85   */
     86 #include <sys_defs.h>
     87 #include <sys/socket.h>
     88 
     89  /*
     90   * Utility library.
     91   */
     92 #include <hex_code.h>
     93 #include <midna_domain.h>
     94 #include <msg.h>
     95 #include <myaddrinfo.h>
     96 #include <name_code.h>
     97 #include <stringops.h>
     98 
     99  /*
    100   * Global library.
    101   */
    102 #include <mail_params.h>
    103 
    104  /*
    105   * TLS library.
    106   */
    107 #include <tls.h>
    108 #include <tlsrpt_wrapper.h>
    109 
    110  /*
    111   * Application-specific.
    112   */
    113 #include <smtp.h>
    114 
    115 #if defined(USE_TLS) && defined(USE_TLSRPT)
    116 
    117 static const char smtp_tlsrpt_support[] = "TLSRPT support";
    118 
    119 /* smtp_tlsrpt_post_jail - post-jail configuration sanity check */
    120 
    121 int     smtp_tlsrpt_post_jail(const char *sockname_pname,
    122 			              const char *sockname_pval)
    123 {
    124     if (smtp_dns_support == SMTP_DNS_DISABLED) {
    125 	msg_warn("Cannot enable %s: DNS is disabled", smtp_tlsrpt_support);
    126 	return (-1);
    127     }
    128     if (*sockname_pval == 0) {
    129 	msg_warn("%s: parameter %s has empty value -- %s will be disabled",
    130 		 smtp_tlsrpt_support, sockname_pname, smtp_tlsrpt_support);
    131 	return (-1);
    132     }
    133     return (0);
    134 }
    135 
    136 /* smtp_tlsrpt_find_policy - look up TLSRPT policy and verify version ID */
    137 
    138 static DNS_RR *smtp_tlsrpt_find_policy(const char *adomain)
    139 {
    140     VSTRING *why = vstring_alloc(100);
    141     VSTRING *qname = vstring_alloc(100);
    142     DNS_RR *rr_list = 0;
    143     DNS_RR *rr_result = 0;
    144     DNS_RR *rr;
    145     DNS_RR *next;
    146     int     res_opt = 0;
    147     int     dns_status;
    148 
    149     /*
    150      * Preliminaries.
    151      */
    152     if (smtp_dns_support == SMTP_DNS_DNSSEC)
    153 	res_opt |= RES_USE_DNSSEC;
    154 
    155     /*
    156      * Lexical features: As specified in RFC 8460, a TLSRPT policy record
    157      * must start with a version field ("v=TLSRPTv1") followed by *WSP;*WSP
    158      * and at least one other field (we must not assume that the second field
    159      * will be "rua"). We leave further validation to the code that actually
    160      * needs it.
    161      */
    162 #define TLSRPTv1_MAGIC		"v=TLSRPTv1"
    163 #define TLSRPTv1_MAGIC_LEN	(sizeof(TLSRPTv1_MAGIC) - 1)
    164 #define RFC5234_WSP		" \t"
    165 
    166     /*
    167      * Look up TXT records. Ignore records that don't start with the expected
    168      * version ID, and require that there is exactly one such DNS record.
    169      */
    170     vstring_sprintf(qname, "_smtp._tls.%s", adomain);
    171     dns_status = dns_lookup(STR(qname), T_TXT, res_opt, &rr_list,
    172 			    (VSTRING *) 0, why);
    173     vstring_free(qname);
    174     if (dns_status != DNS_OK) {
    175 	switch (dns_status) {
    176 	case DNS_NOTFOUND:
    177 	case DNS_POLICY:
    178 	    /* Expected results. */
    179 	    break;
    180 	default:
    181 	    /* Unexpected results. */
    182 	    msg_warn("%s: policy lookup failed for %s: %s",
    183 		     smtp_tlsrpt_support, adomain, STR(why));
    184 	}
    185     } else {
    186 	for (rr = rr_list; rr; rr = next) {
    187 	    char   *cp;
    188 
    189 	    next = rr->next;
    190 	    if (strncmp(rr->data, TLSRPTv1_MAGIC, TLSRPTv1_MAGIC_LEN) != 0)
    191 		/* Ignore non-TLSRPTv1 info. */
    192 		continue;
    193 	    cp = rr->data + TLSRPTv1_MAGIC_LEN;
    194 
    195 	    /*
    196 	     * Should the TLSRPT library validate the entire policy for us?
    197 	     */
    198 	    if (cp[strspn(cp, RFC5234_WSP)] != ';') {
    199 		msg_warn("%s: ignoring malformed policy for %s:, \"%s\"",
    200 			 smtp_tlsrpt_support, adomain, rr->data);
    201 		continue;
    202 	    }
    203 	    if (rr_result) {
    204 		msg_warn("%s: Too many TLSRPT policies for %s",
    205 			 smtp_tlsrpt_support, adomain);
    206 		dns_rr_free(rr_result);
    207 		rr_result = 0;
    208 		break;
    209 	    }
    210 	    rr_result = rr;
    211 	    rr_list = dns_rr_detach(rr_list, rr);
    212 	}
    213     }
    214     vstring_free(why);
    215     if (rr_list)
    216 	dns_rr_free(rr_list);
    217     return (rr_result);
    218 }
    219 
    220 /* smtp_tlsrpt_create_wrapper - look up policy and attach TLSRPT_WRAPPER */
    221 
    222 void    smtp_tlsrpt_create_wrapper(SMTP_STATE *state, const char *domain)
    223 {
    224     const char *adomain;
    225     DNS_RR *rr;
    226 
    227     /*
    228      * TODO(wietse): document in a suitable place that state->tlsrpt exists
    229      * only if the next-hop domain announces a TLSRPT policy.
    230      */
    231     if (state->tlsrpt) {
    232 	trw_free(state->tlsrpt);
    233 	state->tlsrpt = 0;
    234     }
    235 
    236     /*
    237      * IDNA support. An internationalized domain name must be in A-label form
    238      * 1) for TLSRPT summaries and 2) for DNS lookups. The A-label lookup
    239      * result comes from a limited-size in-process cache, so it does not
    240      * matter that the SMTP client requests the same mapping later.
    241      */
    242 #ifndef NO_EAI
    243     if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) {
    244 	if (msg_verbose)
    245 	    msg_info("%s: internationalized domain %s asciified to %s",
    246 		     smtp_tlsrpt_support, domain, adomain);
    247     } else
    248 #endif
    249 	adomain = domain;
    250 
    251     if ((rr = smtp_tlsrpt_find_policy(adomain)) != 0) {
    252 	if (msg_verbose)
    253 	    msg_info("%s: domain %s has policy %.100s",
    254 		     smtp_tlsrpt_support, domain, rr->data);
    255 	if (warn_compat_break_smtp_tlsrpt_skip_reused_hs) {
    256 	    msg_info("using using backwards-compatible default setting "
    257 		     VAR_SMTP_TLSRPT_SKIP_REUSED_HS "=yes");
    258 	    var_smtp_tlsrpt_skip_reused_hs = 1;
    259 	    warn_compat_break_smtp_tlsrpt_skip_reused_hs = 0;
    260 	}
    261 	state->tlsrpt = trw_create(
    262 			    /* rpt_socket_name= */ var_smtp_tlsrpt_sockname,
    263 				    /* rpt_policy_domain= */ adomain,
    264 				    /* rpt_policy_string= */ rr->data,
    265 		     /* skip_reused_hs = */ var_smtp_tlsrpt_skip_reused_hs);
    266 	dns_rr_free(rr);
    267     } else {
    268 	if (msg_verbose)
    269 	    msg_info("%s: no policy for domain %s",
    270 		     smtp_tlsrpt_support, domain);
    271     }
    272 }
    273 
    274 /* smtp_tlsrpt_set_no_policy - no policy found */
    275 
    276 static void smtp_tlsrpt_set_no_policy(SMTP_STATE *state)
    277 {
    278     trw_set_tls_policy(state->tlsrpt, TLSRPT_NO_POLICY_FOUND,
    279 		        /* tls_policy_strings= */ (const char *const *) 0,
    280 		        /* tls_policy_domain= */ (char *) 0,
    281 		        /* mx_host_patterns= */ (const char *const *) 0);
    282 }
    283 
    284 /* smtp_tlsrpt_set_dane_policy - add DANE policy properties */
    285 
    286 static void smtp_tlsrpt_set_dane_policy(SMTP_STATE *state)
    287 {
    288     VSTRING *buf = vstring_alloc(200);
    289     ARGV   *argv = argv_alloc(10);
    290     TLS_DANE *dane = state->tls->dane;
    291     TLS_TLSA *tlsa;
    292 
    293     for (tlsa = dane->tlsa; tlsa != 0; tlsa = tlsa->next) {
    294 	vstring_sprintf(buf, "%d %d %d ", tlsa->usage,
    295 			tlsa->selector, tlsa->mtype);
    296 	hex_encode_opt(buf, (char *) tlsa->data, tlsa->length,
    297 		       HEX_ENCODE_FLAG_APPEND);
    298 	argv_add(argv, STR(buf), (char *) 0);
    299     }
    300     trw_set_tls_policy(state->tlsrpt, TLSRPT_POLICY_TLSA,
    301 		       (const char *const *) argv->argv, dane->base_domain,
    302 		        /* mx_host_patterns= */ (const char *const *) 0);
    303     argv_free(argv);
    304     vstring_free(buf);
    305 }
    306 
    307 /* smtp_tlsrpt_set_ext_policy - add external policy from smtp_tls_policy_maps */
    308 
    309 static void smtp_tlsrpt_set_ext_policy(SMTP_STATE *state)
    310 {
    311     SMTP_TLS_POLICY *tls = state->tls;
    312     tlsrpt_policy_type_t policy_type_val;
    313 
    314     if (tls->ext_policy_type == 0)
    315 	msg_panic("smtp_tlsrpt_set_ext_policy: no policy type");
    316 
    317 #define ARGV_OR_NULL(ap) ((ap) ? (ap)->argv : 0)
    318 
    319     switch (policy_type_val =
    320 	    convert_tlsrpt_policy_type(tls->ext_policy_type)) {
    321     case TLSRPT_POLICY_STS:
    322 	trw_set_tls_policy(state->tlsrpt, policy_type_val,
    323 		(const char *const *) ARGV_OR_NULL(tls->ext_policy_strings),
    324 			   tls->ext_policy_domain,
    325 	     (const char *const *) ARGV_OR_NULL(tls->ext_mx_host_patterns));
    326 	break;
    327     case TLSRPT_NO_POLICY_FOUND:
    328 	smtp_tlsrpt_set_no_policy(state);
    329 	break;
    330     default:
    331 	/* Policy type must be validated in smtp_tls_policy_maps parser. */
    332 	msg_panic("unexpected policy type: \"%s\"",
    333 		  tls->ext_policy_type);
    334     }
    335 
    336     /*
    337      * TODO(wietse) propagate tls->policy_failure to force policy enforcement
    338      * to fail with the indicated error, and prevent a false positive match
    339      * when a certificate would satisfy conventional PKI constraints.
    340      */
    341 }
    342 
    343 /* smtp_tlsrpt_set_tls_policy - set built-in or external policy */
    344 
    345 void    smtp_tlsrpt_set_tls_policy(SMTP_STATE *state)
    346 {
    347     SMTP_TLS_POLICY *tls = state->tls;
    348 
    349     if (TLS_DANE_BASED(tls->level)) {		/* Desired by local policy */
    350 	if (tls->dane != 0)			/* Actual policy */
    351 	    smtp_tlsrpt_set_dane_policy(state);
    352 	else					/* No policy */
    353 	    smtp_tlsrpt_set_no_policy(state);
    354     } else if (tls->ext_policy_type) {
    355 	smtp_tlsrpt_set_ext_policy(state);
    356     } else {
    357 	smtp_tlsrpt_set_no_policy(state);
    358     }
    359 }
    360 
    361 /* smtp_tlsrpt_set_tcp_connection - set TCP connection info from SMTP_STATE */
    362 
    363 void    smtp_tlsrpt_set_tcp_connection(SMTP_STATE *state)
    364 {
    365     SMTP_ITERATOR *iter = state->iterator;
    366     SMTP_SESSION *session = state->session;
    367     MAI_HOSTADDR_STR client_addr;
    368     struct sockaddr_storage addr_storage;
    369     SOCKADDR_SIZE addr_storage_len = sizeof(addr_storage);
    370     int     aierr;
    371 
    372     /*
    373      * Get the IP client address string. The Postfix SMTP_ITERATOR already
    374      * contains strings with server-side connection information.
    375      */
    376     if (getsockname(vstream_fileno(session->stream),
    377 		    (struct sockaddr *) &addr_storage,
    378 		    &addr_storage_len) < 0) {
    379 	msg_warn("%s: getsockname() failed (%m)"
    380 		 " skipping the ignoring client-side IP address",
    381 		 smtp_tlsrpt_support);
    382 	client_addr.buf[0] = 0;
    383     } else if ((aierr = sane_sockaddr_to_hostaddr(
    384 					  (struct sockaddr *) &addr_storage,
    385 					    &addr_storage_len, &client_addr,
    386 						  (MAI_SERVPORT_STR *) 0,
    387 						  SOCK_STREAM)) != 0) {
    388 	msg_warn("%s: cannot convert IP address to string (%s)"
    389 		 " -- skipping the client-side IP address",
    390 		 smtp_tlsrpt_support, MAI_STRERROR(aierr));
    391 	client_addr.buf[0] = 0;
    392     }
    393     trw_set_tcp_connection(state->tlsrpt, client_addr.buf, STR(iter->host),
    394 			   STR(iter->addr));
    395 }
    396 
    397 /* smtp_tlsrpt_set_ehlo_resp - format and set EHLO response */
    398 
    399 void    smtp_tlsrpt_set_ehlo_resp(SMTP_STATE *state, const char *reply)
    400 {
    401     ARGV   *argv;
    402     VSTRING *buf;
    403     char  **cpp;
    404 
    405     /*
    406      * Generate SMTP-style line breaks ("\r\n") for a multiline response.
    407      * Internally, smtp_chat_resp() returns a multiline response as text
    408      * separated with "\n". This is because Postfix by design removes
    409      * protocol-specific line endings on input, uses its own internal form to
    410      * represent text lines, and generates protocol-specific line endings on
    411      * output. The conversion to "\r\n" below is such an output conversion.
    412      */
    413     buf = vstring_alloc(100);
    414     argv = argv_split(reply, "\n");
    415     for (cpp = argv->argv; *cpp; cpp++) {
    416 	vstring_strcat(buf, *cpp);
    417 	if (cpp[1])
    418 	    vstring_strcat(buf, "\r\n");
    419     }
    420     argv_free(argv);
    421     trw_set_ehlo_resp(state->tlsrpt, STR(buf));
    422     vstring_free(buf);
    423 }
    424 
    425 #endif					/* USE_TLSRPT  && USE_TLS */
    426