*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for handling Access Control Lists (ACLs) */
#define CALLOUT_TIMEOUT_DEFAULT 30
+/* Default quota cache TTLs */
+
+#define QUOTA_POS_DEFAULT (5*60)
+#define QUOTA_NEG_DEFAULT (60*60)
+
+
/* ACL verb codes - keep in step with the table of verbs that follows */
enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE,
{ US"no_delay_flush", FALSE,
ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
-
+
[CONTROL_NO_ENFORCE_SYNC] =
{ US"no_enforce_sync", FALSE,
ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
int v, c;
BOOL negated = FALSE;
uschar *saveline = s;
- uschar name[64];
+ uschar name[EXIM_DRIVERNAME_MAX];
/* Conditions (but not verbs) are allowed to be negated by an initial
exclamation mark. */
{
int rc;
-user_msgptr = user_msgptr; /* stop compiler warning */
-
/* Previous success */
if (sender_host_name != NULL) return OK;
tree_node *t;
const uschar *found;
int priority, weight, port;
-dns_answer * dnsa = store_get_dns_answer();
+dns_answer * dnsa;
dns_scan dnss;
dns_record *rr;
-int rc, type;
-uschar target[256];
+int rc, type, yield;
+#define TARGET_SIZE 256
+uschar * target = store_get(TARGET_SIZE, TRUE);
/* Work out the domain we are using for the CSA lookup. The default is the
client's HELO domain. If the client has not said HELO, use its IP address
while (isspace(*domain) && *domain != '\0') ++domain;
if (*domain == '\0') domain = sender_helo_name;
-if (domain == NULL) domain = sender_host_address;
-if (sender_host_address == NULL) return CSA_UNKNOWN;
+if (!domain) domain = sender_host_address;
+if (!sender_host_address) return CSA_UNKNOWN;
/* If we have an address literal, strip off the framing ready for turning it
into a domain. The framing consists of matched square brackets possibly
for this domain. The name is filled in now, and the value is filled in when
we return from this function. */
-t = tree_search(csa_cache, domain);
-if (t != NULL) return t->data.val;
+if ((t = tree_search(csa_cache, domain)))
+ return t->data.val;
t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), is_tainted(domain));
Ustrcpy(t->name, domain);
/* Now we are ready to do the actual DNS lookup(s). */
found = domain;
+dnsa = store_get_dns_answer();
switch (dns_special_lookup(dnsa, domain, T_CSA, &found))
{
/* If something bad happened (most commonly DNS_AGAIN), defer. */
default:
- return t->data.val = CSA_DEFER_SRV;
+ yield = CSA_DEFER_SRV;
+ goto out;
/* If we found nothing, the client's authorization is unknown. */
case DNS_NOMATCH:
case DNS_NODATA:
- return t->data.val = CSA_UNKNOWN;
+ yield = CSA_UNKNOWN;
+ goto out;
/* We got something! Go on to look at the reply in more detail. */
case DNS_SUCCEED:
- break;
+ break;
}
/* Scan the reply for well-formed CSA SRV records. */
SRV records of their own. */
if (Ustrcmp(found, domain) != 0)
- return t->data.val = port & 1 ? CSA_FAIL_EXPLICIT : CSA_UNKNOWN;
+ {
+ yield = port & 1 ? CSA_FAIL_EXPLICIT : CSA_UNKNOWN;
+ goto out;
+ }
/* This CSA SRV record refers directly to our domain, so we check the value
in the weight field to work out the domain's authorization. 0 and 1 are
address in order to authenticate it, so we treat it as unknown; values
greater than 3 are undefined. */
- if (weight < 2) return t->data.val = CSA_FAIL_DOMAIN;
+ if (weight < 2)
+ {
+ yield = CSA_FAIL_DOMAIN;
+ goto out;
+ }
if (weight > 2) continue;
target hostname then break to scan the additional data for its addresses. */
(void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p,
- (DN_EXPAND_ARG4_TYPE)target, sizeof(target));
+ (DN_EXPAND_ARG4_TYPE)target, TARGET_SIZE);
DEBUG(D_acl) debug_printf_indent("CSA target is %s\n", target);
/* If we didn't break the loop then no appropriate records were found. */
-if (!rr) return t->data.val = CSA_UNKNOWN;
+if (!rr)
+ {
+ yield = CSA_UNKNOWN;
+ goto out;
+ }
/* Do not check addresses if the target is ".", in accordance with RFC 2782.
A target of "." indicates there are no valid addresses, so the client cannot
equivalent to weight=1, but we check for it in order to keep load off the
root name servers.) Note that dn_expand() turns "." into "". */
-if (Ustrcmp(target, "") == 0) return t->data.val = CSA_FAIL_NOADDR;
+if (Ustrcmp(target, "") == 0)
+ {
+ yield = CSA_FAIL_NOADDR;
+ goto out;
+ }
/* Scan the additional section of the CSA SRV reply for addresses belonging
to the target. If the name server didn't return any additional data (e.g.
to obtain the target addresses; otherwise we have a definitive result. */
rc = acl_verify_csa_address(dnsa, &dnss, RESET_ADDITIONAL, target);
-if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
+if (rc != CSA_FAIL_NOADDR)
+ {
+ yield = rc;
+ goto out;
+ }
/* The DNS lookup type corresponds to the IP version used by the client. */
/* If something bad happened (most commonly DNS_AGAIN), defer. */
default:
- return t->data.val = CSA_DEFER_ADDR;
+ yield = CSA_DEFER_ADDR;
+ break;
/* If the query succeeded, scan the addresses and return the result. */
case DNS_SUCCEED:
rc = acl_verify_csa_address(dnsa, &dnss, RESET_ANSWERS, target);
- if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
+ if (rc != CSA_FAIL_NOADDR)
+ {
+ yield = rc;
+ break;
+ }
/* else fall through */
/* If the target has no IP addresses, the client cannot have an authorized
case DNS_NOMATCH:
case DNS_NODATA:
- return t->data.val = CSA_FAIL_NOADDR;
+ yield = CSA_FAIL_NOADDR;
+ break;
}
+
+out:
+
+store_free_dns_answer(dnsa);
+return t->data.val = yield;
}
{ US"certificate", VERIFY_CERT, (unsigned)~0, TRUE, 0 },
{ US"helo", VERIFY_HELO, (unsigned)~0, TRUE, 0 },
{ US"csa", VERIFY_CSA, (unsigned)~0, FALSE, 0 },
- { US"header_syntax", VERIFY_HDR_SYNTAX, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
- { US"not_blind", VERIFY_NOT_BLIND, ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 },
- { US"header_sender", VERIFY_HDR_SNDR, ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 },
+ { US"header_syntax", VERIFY_HDR_SYNTAX, ACL_BITS_HAVEDATA, TRUE, 0 },
+ { US"not_blind", VERIFY_NOT_BLIND, ACL_BITS_HAVEDATA, FALSE, 0 },
+ { US"header_sender", VERIFY_HDR_SNDR, ACL_BITS_HAVEDATA, FALSE, 0 },
{ US"sender", VERIFY_SNDR, ACL_BIT_MAIL | ACL_BIT_RCPT
- |ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
+ | ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
FALSE, 6 },
{ US"recipient", VERIFY_RCPT, ACL_BIT_RCPT, FALSE, 0 },
- { US"header_names_ascii", VERIFY_HDR_NAMES_ASCII, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+ { US"header_names_ascii", VERIFY_HDR_NAMES_ASCII, ACL_BITS_HAVEDATA, TRUE, 0 },
#ifdef EXPERIMENTAL_ARC
{ US"arc", VERIFY_ARC, ACL_BIT_DATA, FALSE , 0 },
#endif
+static int
+v_period(const uschar * s, const uschar * arg, uschar ** log_msgptr)
+{
+int period;
+if ((period = readconf_readtime(s, 0, FALSE)) < 0)
+ {
+ *log_msgptr = string_sprintf("bad time value in ACL condition "
+ "\"verify %s\"", arg);
+ }
+return period;
+}
+
+
+
/* This function implements the "verify" condition. It is called when
encountered in any ACL, because some tests are almost always permitted. Some
just don't make sense, and always fail (for example, an attempt to test a host
BOOL callout_defer_ok = FALSE;
BOOL no_details = FALSE;
BOOL success_on_redirect = FALSE;
+BOOL quota = FALSE;
+int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT;
address_item *sender_vaddr = NULL;
uschar *verify_sender_address = NULL;
uschar *pm_mailfrom = NULL;
in place of the actual sender (rare special-case requirement). */
{
uschar *s = ss + 6;
- if (*s == 0)
+ if (!*s)
verify_sender_address = sender_address;
else
{
/* Remaining items are optional; they apply to sender and recipient
verification, including "header sender" verification. */
-while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
{
if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE;
else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE;
else if (strncmpic(ss, US"callout", 7) == 0)
{
callout = CALLOUT_TIMEOUT_DEFAULT;
- ss += 7;
- if (*ss != 0)
+ if (*(ss += 7))
{
while (isspace(*ss)) ss++;
if (*ss++ == '=')
{
const uschar * sublist = ss;
int optsep = ',';
- uschar buffer[256];
- uschar * opt;
while (isspace(*sublist)) sublist++;
- while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer))))
+ for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
{
callout_opt_t * op;
double period = 1.0F;
}
while (isspace(*opt)) opt++;
}
- if (op->timeval && (period = readconf_readtime(opt, 0, FALSE)) < 0)
- {
- *log_msgptr = string_sprintf("bad time value in ACL condition "
- "\"verify %s\"", arg);
+ if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0)
return ERROR;
- }
switch(op->value)
{
}
}
+ /* The quota option has sub-options, comma-separated */
+
+ else if (strncmpic(ss, US"quota", 5) == 0)
+ {
+ quota = TRUE;
+ if (*(ss += 5))
+ {
+ while (isspace(*ss)) ss++;
+ if (*ss++ == '=')
+ {
+ const uschar * sublist = ss;
+ int optsep = ',';
+ int period;
+
+ while (isspace(*sublist)) sublist++;
+ for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
+ if (Ustrncmp(opt, "cachepos=", 9) == 0)
+ if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
+ return ERROR;
+ else
+ quota_pos_cache = period;
+ else if (Ustrncmp(opt, "cacheneg=", 9) == 0)
+ if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
+ return ERROR;
+ else
+ quota_neg_cache = period;
+ else if (Ustrcmp(opt, "no_cache") == 0)
+ quota_pos_cache = quota_neg_cache = 0;
+ }
+ }
+ }
+
/* Option not recognized */
else
return ERROR;
}
+/* Handle quota verification */
+if (quota)
+ {
+ if (vp->value != VERIFY_RCPT)
+ {
+ *log_msgptr = US"can only verify quota of recipient";
+ return ERROR;
+ }
+
+ if ((rc = verify_quota_call(addr->address,
+ quota_pos_cache, quota_neg_cache, log_msgptr)) != OK)
+ {
+ *basic_errno = errno;
+ if (smtp_return_error_details)
+ {
+ if (!*user_msgptr && *log_msgptr)
+ *user_msgptr = string_sprintf("Rejected after %s: %s",
+ smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]],
+ *log_msgptr);
+ if (rc == DEFER) f.acl_temp_details = TRUE;
+ }
+ }
+
+ return rc;
+ }
+
/* Handle sender-in-header verification. Default the user message to the log
message if giving out verification details. */
}
sender_vaddr = verify_checked_sender(verify_sender_address);
- if (sender_vaddr != NULL && /* Previously checked */
- callout <= 0) /* No callout needed this time */
+ if ( sender_vaddr /* Previously checked */
+ && callout <= 0) /* No callout needed this time */
{
/* If the "routed" flag is set, it means that routing worked before, so
this check can give OK (the saved return code value, if set, belongs to a
*basic_errno = sender_vaddr->basic_errno;
else
DEBUG(D_acl)
- {
if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0)
debug_printf_indent("sender %s verified ok as %s\n",
verify_sender_address, sender_vaddr->address);
else
debug_printf_indent("sender %s verified ok\n",
verify_sender_address);
- }
}
else
rc = OK; /* Null sender */
*basic_errno = addr2.basic_errno;
*log_msgptr = addr2.message;
- *user_msgptr = (addr2.user_message != NULL)?
- addr2.user_message : addr2.message;
+ *user_msgptr = addr2.user_message ? addr2.user_message : addr2.message;
/* Allow details for temporary error if the address is so flagged. */
if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE;
/* We have a result from the relevant test. Handle defer overrides first. */
-if (rc == DEFER && (defer_ok ||
- (callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER)))
+if ( rc == DEFER
+ && ( defer_ok
+ || callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER
+ ) )
{
HDEBUG(D_acl) debug_printf_indent("verify defer overridden by %s\n",
defer_ok? "defer_ok" : "callout_defer_ok");
/* If we've failed a sender, set up a recipient message, and point
sender_verified_failed to the address item that actually failed. */
-if (rc != OK && verify_sender_address != NULL)
+if (rc != OK && verify_sender_address)
{
if (rc != DEFER)
*log_msgptr = *user_msgptr = US"Sender verify failed";
/* Verifying an address messes up the values of $domain and $local_part,
so reset them before returning if this is a RCPT ACL. */
-if (addr != NULL)
+if (addr)
{
deliver_domain = addr->domain;
deliver_localpart = addr->local_part;
/* Parse the other options. */
-while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)))
+while ((ss = string_nextinlist(&arg, &sep, NULL, 0)))
{
if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
{
const uschar *pp = p + 6;
while (*pp) pp++;
- submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
- big_buffer, big_buffer_size));
+ submission_name = parse_fix_phrase(p+6, pp-p-6);
p = pp;
}
else break;
the case where both sides handle prdr and this-node prdr acl
is "accept" */
ignored = US"PRDR active";
+ else if (f.deliver_freeze)
+ ignored = US"frozen";
+ else if (f.queue_only_policy)
+ ignored = US"queue-only";
+ else if (fake_response == FAIL)
+ ignored = US"fakereject";
+ else if (rcpt_count != 1)
+ ignored = US"nonfirst rcpt";
+ else if (cutthrough.delivery)
+ ignored = US"repeated";
+ else if (cutthrough.callout_hold_only)
+ {
+ DEBUG(D_acl)
+ debug_printf_indent(" cutthrough request upgrades callout hold\n");
+ cutthrough.callout_hold_only = FALSE;
+ cutthrough.delivery = TRUE; /* control accepted */
+ }
else
{
- if (f.deliver_freeze)
- ignored = US"frozen";
- else if (f.queue_only_policy)
- ignored = US"queue-only";
- else if (fake_response == FAIL)
- ignored = US"fakereject";
- else
+ cutthrough.delivery = TRUE; /* control accepted */
+ while (*p == '/')
{
- if (rcpt_count == 1)
+ const uschar * pp = p+1;
+ if (Ustrncmp(pp, "defer=", 6) == 0)
{
- cutthrough.delivery = TRUE; /* control accepted */
- while (*p == '/')
- {
- const uschar * pp = p+1;
- if (Ustrncmp(pp, "defer=", 6) == 0)
- {
- pp += 6;
- if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
- /* else if (Ustrncmp(pp, "spool") == 0) ; default */
- }
- else
- while (*pp && *pp != '/') pp++;
- p = pp;
- }
+ pp += 6;
+ if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
+ /* else if (Ustrncmp(pp, "spool") == 0) ; default */
}
else
- ignored = US"nonfirst rcpt";
+ while (*pp && *pp != '/') pp++;
+ p = pp;
}
}
+
DEBUG(D_acl) if (ignored)
debug_printf(" cutthrough request ignored on %s item\n", ignored);
}
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+ uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
/* Run the dcc backend. */
rc = dcc_process(&ss);
/* Modify return code based upon the existence of options. */
- while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
rc = FAIL; /* FAIL so that the message is passed to the next ACL */
}
case ACLC_DKIM_SIGNER:
if (dkim_cur_signer)
rc = match_isinlist(dkim_cur_signer,
- &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
else
rc = FAIL;
break;
case ACLC_DKIM_STATUS:
rc = match_isinlist(dkim_verify_status,
- &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
break;
#endif
/* used long way of dmarc_exim_expand_query() in case we need more
* view into the process in the future. */
rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
- &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
break;
#endif
{
uschar *endcipher = NULL;
uschar *cipher = Ustrchr(tls_in.cipher, ':');
- if (cipher == NULL) cipher = tls_in.cipher; else
+ if (!cipher) cipher = tls_in.cipher; else
{
endcipher = Ustrchr(++cipher, ':');
- if (endcipher != NULL) *endcipher = 0;
+ if (endcipher) *endcipher = 0;
}
rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
- if (endcipher != NULL) *endcipher = ':';
+ if (endcipher) *endcipher = ':';
}
break;
case ACLC_HOSTS:
rc = verify_check_this_host(&arg, sender_host_cache, NULL,
- (sender_host_address == NULL)? US"" : sender_host_address,
- CUSS &host_data);
+ sender_host_address ? sender_host_address : US"", CUSS &host_data);
if (rc == DEFER) *log_msgptr = search_error_message;
if (host_data) host_data = string_copy_perm(host_data, TRUE);
break;
int sep = 0;
const uschar *s = arg;
uschar * ss;
- while ((ss = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)))
+ while ((ss = string_nextinlist(&s, &sep, NULL, 0)))
{
if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC;
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar * ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+ uschar * ss = string_nextinlist(&list, &sep, NULL, 0);
uschar * opt;
BOOL defer_ok = FALSE;
int timeout = 0;
#endif
case ACLC_QUEUE:
- if (is_tainted(arg))
{
- *log_msgptr = string_sprintf("Tainted name '%s' for queue not permitted",
- arg);
- return ERROR;
- }
- if (Ustrchr(arg, '/'))
- {
- *log_msgptr = string_sprintf(
- "Directory separator not permitted in queue name: '%s'", arg);
- return ERROR;
+ uschar *m;
+ if ((m = is_tainted2(arg, 0, "Tainted name '%s' for queue not permitted", arg)))
+ {
+ *log_msgptr = m;
+ return ERROR;
+ }
+ if (Ustrchr(arg, '/'))
+ {
+ *log_msgptr = string_sprintf(
+ "Directory separator not permitted in queue name: '%s'", arg);
+ return ERROR;
+ }
+ queue_name = string_copy_perm(arg, FALSE);
+ break;
}
- queue_name = string_copy_perm(arg, FALSE);
- break;
case ACLC_RATELIMIT:
rc = acl_ratelimit(arg, where, log_msgptr);
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+ uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
rc = spam(CUSS &ss);
/* Modify return code based upon the existence of options. */
- while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
rc = FAIL; /* FAIL so that the message is passed to the next ACL */
}
acl_text = ss;
+if ( !f.running_in_test_harness
+ && is_tainted2(acl_text, LOG_MAIN|LOG_PANIC,
+ "Tainted ACL text \"%s\"", acl_text))
+ {
+ /* Avoid leaking info to an attacker */
+ *log_msgptr = US"internal configuration error";
+ return ERROR;
+ }
+
/* Handle the case of a string that does not contain any spaces. Look for a
named ACL among those read from the configuration, or a previously read file.
It is possible that the pointer to the ACL is NULL if the configuration
else if (*ss == '/')
{
struct stat statbuf;
- if (is_tainted(ss))
+ if (is_tainted2(ss, LOG_MAIN|LOG_PANIC, "Tainted ACL file name '%s'", ss))
{
- log_write(0, LOG_MAIN|LOG_PANIC,
- "attempt to open tainted ACL file name \"%s\"", ss);
/* Avoid leaking info to an attacker */
*log_msgptr = US"internal configuration error";
return ERROR;
/* Drop cutthrough conns, and drop heldopen verify conns if
the previous was not DATA */
{
- uschar prev = smtp_connection_had[smtp_ch_index-2];
+ uschar prev =
+ smtp_connection_had[SMTP_HBUFF_PREV(SMTP_HBUFF_PREV(smtp_ch_index))];
BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT);
cancel_cutthrough_connection(dropverify, US"quit or conndrop");