diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 08:42:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 08:42:27 +0000 |
commit | 95f5f6d1c3aec1cb62525f5162e71a4157aca717 (patch) | |
tree | 8633546094df32b27d719c7578537e6062aa52e3 /src/smtp | |
parent | Releasing progress-linux version 3.8.6-1~progress7.99u1. (diff) | |
download | postfix-95f5f6d1c3aec1cb62525f5162e71a4157aca717.tar.xz postfix-95f5f6d1c3aec1cb62525f5162e71a4157aca717.zip |
Merging upstream version 3.9.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/smtp')
-rw-r--r-- | src/smtp/lmtp_params.c | 2 | ||||
-rw-r--r-- | src/smtp/smtp.c | 136 | ||||
-rw-r--r-- | src/smtp/smtp.h | 2 | ||||
-rw-r--r-- | src/smtp/smtp_addr.c | 2 | ||||
-rw-r--r-- | src/smtp/smtp_params.c | 2 | ||||
-rw-r--r-- | src/smtp/smtp_proto.c | 2 | ||||
-rw-r--r-- | src/smtp/smtp_sasl_glue.c | 4 | ||||
-rw-r--r-- | src/smtp/smtp_tls_policy.c | 56 |
8 files changed, 141 insertions, 65 deletions
diff --git a/src/smtp/lmtp_params.c b/src/smtp/lmtp_params.c index bca7cd4..385c81f 100644 --- a/src/smtp/lmtp_params.c +++ b/src/smtp/lmtp_params.c @@ -4,6 +4,7 @@ VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, + VAR_LMTP_SASL_PASSWD_RES_DELIM, DEF_LMTP_SASL_PASSWD_RES_DELIM, &var_smtp_sasl_passwd_res_delim, 1, 1, VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, VAR_LMTP_SASL_PATH, DEF_LMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0, #ifdef USE_TLS @@ -120,6 +121,7 @@ VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, + VAR_LMTP_TLS_ENABLE_RPK, DEF_LMTP_TLS_ENABLE_RPK, &var_smtp_tls_enable_rpk, #endif VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth, diff --git a/src/smtp/smtp.c b/src/smtp/smtp.c index f7f2fc1..51b2e6d 100644 --- a/src/smtp/smtp.c +++ b/src/smtp/smtp.c @@ -1,17 +1,21 @@ /*++ /* NAME -/* smtp 8 +/* smtp, lmtp 8 /* SUMMARY /* Postfix SMTP+LMTP client /* SYNOPSIS /* \fBsmtp\fR [generic Postfix daemon options] [flags=DORX] +/* +/* \fBlmtp\fR [generic Postfix daemon options] [flags=DORX] /* DESCRIPTION /* The Postfix SMTP+LMTP client implements the SMTP and LMTP mail /* delivery protocols. It processes message delivery requests from /* the queue manager. Each request specifies a queue file, a sender /* address, a domain or host to deliver to, and recipient information. /* This program expects to be run from the \fBmaster\fR(8) process -/* manager. +/* manager. The process name, \fBsmtp\fR or \fBlmtp\fR, controls +/* the protocol, and the names of the configuration parameters +/* that will be used. /* /* The SMTP+LMTP client updates the queue file and marks recipients /* as finished, or it informs the queue manager that delivery should @@ -19,13 +23,9 @@ /* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as /* appropriate. /* -/* The SMTP+LMTP client looks up a list of mail exchanger addresses for -/* the destination host, sorts the list by preference, and connects -/* to each listed address until it finds a server that responds. -/* -/* When a server is not reachable, or when mail delivery fails due -/* to a recoverable error condition, the SMTP+LMTP client will try to -/* deliver the mail to an alternate host. +/* The server lookup strategy is different for SMTP and LMTP, +/* as described in the sections "SMTP SERVER LOOKUP" and "LMTP +/* SERVER LOOKUP". /* /* After a successful mail transaction, a connection may be saved /* to the \fBscache\fR(8) connection cache server, so that it @@ -35,44 +35,58 @@ /* destinations that have a high volume of mail in the active /* queue. Connection caching can be enabled permanently for /* specific destinations. -/* SMTP DESTINATION SYNTAX +/* SMTP SERVER LOOKUP /* .ad /* .fi -/* The Postfix SMTP+LMTP client supports multiple destinations +/* The Postfix SMTP client supports multiple destinations /* separated by comma or whitespace (Postfix 3.5 and later). +/* Each destination is tried in the specified order. +/* /* SMTP destinations have the following form: /* .IP \fIdomainname\fR -/* .IP \fIdomainname\fR:\fIport\fR +/* .IP \fIdomainname\fR:\fIservice\fR /* Look up the mail exchangers for the specified domain, and -/* connect to the specified port (default: \fBsmtp\fR). +/* connect to the specified service (default: \fBsmtp\fR). +/* Optionally, mail exchangers may be looked up with SRV queries +/* instead of MX; this requires that \fIservice\fR is given +/* in symbolic form. /* .IP [\fIhostname\fR] -/* .IP [\fIhostname\fR]:\fIport\fR -/* Look up the address(es) of the specified host, and connect to -/* the specified port (default: \fBsmtp\fR). +/* .IP [\fIhostname\fR]:\fIservice\fR +/* Look up the address(es) for the specified host, and connect to +/* the specified service (default: \fBsmtp\fR). /* .IP [\fIaddress\fR] -/* .IP [\fIaddress\fR]:\fIport\fR +/* .IP [\fIaddress\fR]:\fIservice\fR /* Connect to the host at the specified address, and connect -/* to the specified port (default: \fBsmtp\fR). An IPv6 address +/* to the specified service (default: \fBsmtp\fR). An IPv6 address /* must be formatted as [\fBipv6\fR:\fIaddress\fR]. -/* LMTP DESTINATION SYNTAX +/* LMTP SERVER LOOKUP /* .ad /* .fi -/* The Postfix SMTP+LMTP client supports multiple destinations +/* The Postfix LMTP client supports multiple destinations /* separated by comma or whitespace (Postfix 3.5 and later). +/* Each destination is tried in the specified order. +/* /* LMTP destinations have the following form: /* .IP \fBunix\fR:\fIpathname\fR /* Connect to the local UNIX-domain server that is bound to the specified /* \fIpathname\fR. If the process runs chrooted, an absolute pathname /* is interpreted relative to the Postfix queue directory. +/* .IP \fBinet\fR:\fIdomainname\fR +/* .IP \fBinet\fR:\fIdomainname\fR:\fIservice\fR +/* Look up the LMTP servers for the specified domain and service +/* (default: \fBlmtp\fR). +/* This form is supported when SRV lookups are enabled, and +/* requires that \fIservice\fR is in symbolic form. /* .IP \fBinet\fR:\fIhostname\fR -/* .IP \fBinet\fR:\fIhostname\fR:\fIport\fR +/* .IP \fBinet\fR:\fIhostname\fR:\fIservice\fR +/* Look up the address(es) for the specified host, and connect to +/* the specified service (default: \fBlmtp\fR). When SRV lookups +/* are enabled, use the form \fB[\fIhostname\fB]\fR to force +/* address lookups. /* .IP \fBinet\fR:[\fIaddress\fR] -/* .IP \fBinet\fR:[\fIaddress\fR]:\fIport\fR -/* Connect to the specified TCP port on the specified local or -/* remote host. If no port is specified, connect to the port defined as -/* \fBlmtp\fR in \fBservices\fR(4). -/* If no such service is found, the \fBlmtp_tcp_port\fR configuration -/* parameter (default value of 24) will be used. +/* .IP \fBinet\fR:[\fIaddress\fR]:\fIservice\fR +/* Connect to the specified local or remote host and service +/* (default: \fBlmtp\fR). /* An IPv6 address must be formatted as [\fBipv6\fR:\fIaddress\fR]. /* SINGLE-RECIPIENT DELIVERY /* .ad @@ -130,6 +144,8 @@ /* This feature is available as of Postfix 3.5. /* .RE /* SECURITY +/* .ad +/* .fi /* The SMTP+LMTP client is moderately security-sensitive. It /* talks to SMTP or LMTP servers and to DNS servers on the /* network. The SMTP+LMTP client can be run chrooted at fixed @@ -175,11 +191,10 @@ /* CONFIGURATION PARAMETERS /* .ad /* .fi -/* Before Postfix version 2.3, the LMTP client is a separate -/* program that implements only a subset of the functionality -/* available with SMTP: there is no support for TLS, and -/* connections are cached in-process, making it ineffective -/* when the client is used for multiple domains. +/* Postfix versions 2.3 and later implement the SMTP and LMTP +/* client with the same program, and choose the protocol and +/* configuration parameters based on the process name, \fBsmtp\fR +/* or \fBlmtp\fR. /* /* Most smtp_\fIxxx\fR configuration parameters have an /* lmtp_\fIxxx\fR "mirror" parameter for the equivalent LMTP @@ -432,6 +447,11 @@ /* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR" /* Whether or not to append the "AUTH=<>" option to the MAIL /* FROM command in SASL-authenticated SMTP sessions. +/* .PP +/* Available in Postfix version 3.9 and later: +/* .IP "\fBsmtp_sasl_password_result_delimiter (:)\fR" +/* The delimiter between username and password in sasl_passwd_maps lookup +/* results. /* STARTTLS SUPPORT CONTROLS /* .ad /* .fi @@ -532,7 +552,7 @@ /* certificate fingerprints. /* .PP /* Available in Postfix version 2.6 and later: -/* .IP "\fBsmtp_tls_protocols (see postconf -d output)\fR" +/* .IP "\fBsmtp_tls_protocols (see 'postconf -d' output)\fR" /* TLS protocols that the Postfix SMTP client will use with /* opportunistic TLS encryption. /* .IP "\fBsmtp_tls_ciphers (medium)\fR" @@ -613,6 +633,11 @@ /* .IP "\fBtls_config_name (empty)\fR" /* The application name passed by Postfix to OpenSSL library /* initialization functions. +/* .PP +/* Available in Postfix version 3.9 and later: +/* .IP "\fBsmtp_tls_enable_rpk (no)\fR" +/* Request that remote SMTP servers send an RFC7250 raw public key +/* instead of an X.509 certificate. /* OBSOLETE STARTTLS CONTROLS /* .ad /* .fi @@ -799,9 +824,9 @@ /* .IP "\fBdisable_dns_lookups (no)\fR" /* Disable DNS lookups in the Postfix SMTP and LMTP clients. /* .IP "\fBinet_interfaces (all)\fR" -/* The local network interface addresses that this mail system receives -/* mail on. -/* .IP "\fBinet_protocols (see 'postconf -d output')\fR" +/* The local network interface addresses that this mail system +/* receives mail on. +/* .IP "\fBinet_protocols (see 'postconf -d' output)\fR" /* The Internet protocols Postfix will attempt to use when making /* or accepting connections. /* .IP "\fBipc_timeout (3600s)\fR" @@ -1020,6 +1045,7 @@ int var_smtp_never_ehlo; char *var_smtp_sasl_opts; char *var_smtp_sasl_path; char *var_smtp_sasl_passwd; +char *var_smtp_sasl_passwd_res_delim; bool var_smtp_sasl_enable; char *var_smtp_sasl_mechs; char *var_smtp_sasl_type; @@ -1090,6 +1116,7 @@ char *var_smtp_tls_sni; bool var_smtp_tls_blk_early_mail_reply; bool var_smtp_tls_force_tlsa; char *var_smtp_tls_insecure_mx_policy; +bool var_smtp_tls_enable_rpk; #endif @@ -1117,8 +1144,8 @@ bool var_smtp_balance_inet_proto; bool var_smtp_req_deadline; int var_smtp_min_data_rate; char *var_use_srv_lookup; -bool var_ign_srv_lookup_err; -bool var_allow_srv_fallback; +bool var_ign_srv_lookup_err; +bool var_allow_srv_fallback; /* Special handling of 535 AUTH errors. */ char *var_smtp_sasl_auth_cache_name; @@ -1126,7 +1153,7 @@ int var_smtp_sasl_auth_cache_time; bool var_smtp_sasl_auth_soft_bounce; char *var_hfrom_format; -bool var_smtp_bind_addr_enforce; +bool var_smtp_bind_addr_enforce; /* * Global variables. @@ -1459,6 +1486,19 @@ static void pre_init(char *unused_name, char **unused_argv) }; /* + * The process name, "smtp" or "lmtp", determines the configuration + * parameters to use, protocol, DSN server reply type, SASL service + * information lookup, and more. We peeked at the name in the main() + * function before logging was initialized. Here, we detect and report an + * invalid process name. + */ + if (strcmp(var_procname, MAIL_PROC_NAME_SMTP) != 0 + && strcmp(var_procname, MAIL_PROC_NAME_LMTP) != 0) + msg_fatal("unexpected process name \"%s\" - " + "specify \"%s\" or \"%s\"", var_procname, + MAIL_PROC_NAME_SMTP, MAIL_PROC_NAME_LMTP); + + /* * Turn on per-peer debugging. */ debug_peer_init(); @@ -1649,21 +1689,15 @@ int main(int argc, char **argv) MAIL_VERSION_STAMP_ALLOCATE; /* - * XXX At this point, var_procname etc. are not initialized. - * - * The process name, "smtp" or "lmtp", determines the protocol, the DSN - * server reply type, SASL service information lookup, and more. Prepare - * for the possibility there may be another personality. + * XXX The process name, "smtp" or "lmtp", determines what configuration + * parameter settings to use, and more. However, at this point, logging + * and var_procname are not initialized. Here, we peek at the process + * name to determine what configuration parameter settings to use. Later, + * we detect and report an invalid process name. */ sane_procname = sane_basename((VSTRING *) 0, argv[0]); - if (strcmp(sane_procname, "smtp") == 0) + if (strcmp(sane_procname, MAIL_PROC_NAME_SMTP) == 0) smtp_mode = 1; - else if (strcmp(sane_procname, "lmtp") == 0) - smtp_mode = 0; - else - /* TODO: logging is not initialized. */ - msg_fatal("unexpected process name \"%s\" - " - "specify \"smtp\" or \"lmtp\"", var_procname); /* * Initialize with the LMTP or SMTP parameter name space. diff --git a/src/smtp/smtp.h b/src/smtp/smtp.h index f8c8f58..60c68f8 100644 --- a/src/smtp/smtp.h +++ b/src/smtp/smtp.h @@ -107,6 +107,7 @@ typedef struct SMTP_TLS_POLICY { TLS_DANE *dane; /* DANE TLSA digests */ char *sni; /* Optional SNI name when not DANE */ int conn_reuse; /* enable connection reuse */ + int enable_rpk; /* Enable server->client RPK */ } SMTP_TLS_POLICY; /* @@ -142,6 +143,7 @@ extern void smtp_tls_policy_cache_flush(void); _tls_policy_init_tmp->dane = 0; \ _tls_policy_init_tmp->sni = 0; \ _tls_policy_init_tmp->conn_reuse = 0; \ + _tls_policy_init_tmp->enable_rpk = 0; \ } while (0) #endif diff --git a/src/smtp/smtp_addr.c b/src/smtp/smtp_addr.c index f30ce73..8c384fc 100644 --- a/src/smtp/smtp_addr.c +++ b/src/smtp/smtp_addr.c @@ -262,7 +262,7 @@ static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt, msg_fatal("host %s: conversion error for address family " "%d: %m", host, res0->ai_addr->sa_family); addr_list = dns_rr_append(addr_list, addr); - if (DNS_RR_IS_TRUNCATED(addr_list)) + if (DNS_RR_IS_TRUNCATED(addr_list)) break; if (msg_verbose) { MAI_HOSTADDR_STR hostaddr_str; diff --git a/src/smtp/smtp_params.c b/src/smtp/smtp_params.c index 22f4709..cebff93 100644 --- a/src/smtp/smtp_params.c +++ b/src/smtp/smtp_params.c @@ -4,6 +4,7 @@ VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, + VAR_SMTP_SASL_PASSWD_RES_DELIM, DEF_SMTP_SASL_PASSWD_RES_DELIM, &var_smtp_sasl_passwd_res_delim, 1, 1, VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, VAR_SMTP_SASL_PATH, DEF_SMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0, #ifdef USE_TLS @@ -124,6 +125,7 @@ VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, + VAR_SMTP_TLS_ENABLE_RPK, DEF_SMTP_TLS_ENABLE_RPK, &var_smtp_tls_enable_rpk, #endif VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth, diff --git a/src/smtp/smtp_proto.c b/src/smtp/smtp_proto.c index 097d518..e022bc2 100644 --- a/src/smtp/smtp_proto.c +++ b/src/smtp/smtp_proto.c @@ -929,6 +929,7 @@ static int smtp_start_tls(SMTP_STATE *state) TLS_PROXY_CLIENT_START_PROPS(&start_props, timeout = var_smtp_starttls_tmout, tls_level = state->tls->level, + enable_rpk = state->tls->enable_rpk, nexthop = session->tls_nexthop, host = STR(iter->host), namaddr = session->namaddrport, @@ -1051,6 +1052,7 @@ static int smtp_start_tls(SMTP_STATE *state) fd = -1, timeout = var_smtp_starttls_tmout, tls_level = state->tls->level, + enable_rpk = state->tls->enable_rpk, nexthop = session->tls_nexthop, host = STR(iter->host), namaddr = session->namaddrport, diff --git a/src/smtp/smtp_sasl_glue.c b/src/smtp/smtp_sasl_glue.c index ef8e8c4..cce5ef7 100644 --- a/src/smtp/smtp_sasl_glue.c +++ b/src/smtp/smtp_sasl_glue.c @@ -200,7 +200,9 @@ int smtp_sasl_passwd_lookup(SMTP_SESSION *session) if (session->sasl_username) myfree(session->sasl_username); session->sasl_username = mystrdup(value); - passwd = split_at(session->sasl_username, ':'); + /* Historically, the delimiter may appear in the password. */ + passwd = split_at(session->sasl_username, + *var_smtp_sasl_passwd_res_delim); if (session->sasl_passwd) myfree(session->sasl_passwd); session->sasl_passwd = mystrdup(passwd ? passwd : ""); diff --git a/src/smtp/smtp_tls_policy.c b/src/smtp/smtp_tls_policy.c index 92a231d..f407d65 100644 --- a/src/smtp/smtp_tls_policy.c +++ b/src/smtp/smtp_tls_policy.c @@ -334,9 +334,10 @@ static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, INVALID_RETURN(tls->why, site_level); break; case TLS_LEV_FPRINT: - if (!tls->dane) - tls->dane = tls_dane_alloc(); - tls_dane_add_fpt_digests(tls->dane, val, "|", smtp_mode); + 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: @@ -390,6 +391,19 @@ static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, } continue; } + 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; + } msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); INVALID_RETURN(tls->why, site_level); } @@ -518,6 +532,7 @@ static void *policy_create(const char *unused_key, void *context) 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 @@ -602,6 +617,13 @@ static void *policy_create(const char *unused_key, void *context) */ 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. */ @@ -617,16 +639,26 @@ static void *policy_create(const char *unused_key, void *context) case TLS_LEV_FPRINT: if (tls->dane == 0) tls->dane = tls_dane_alloc(); - if (tls->dane->tlsa == 0) { - tls_dane_add_fpt_digests(tls->dane, 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); + /* 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: |