X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Facl.c;h=f0716e0168693adeeab61e8c2f25a5eb8d7fa009;hp=77fdc56271b3ceda37ff9a0354dbaa1963767868;hb=2ad7897851d62bc690844f416d4ca2fabedf9459;hpb=3399bb60b83537c54021e5d0553612fe2f2cde39 diff --git a/src/src/acl.c b/src/src/acl.c index 77fdc5627..f0716e016 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for handling Access Control Lists (ACLs) */ @@ -21,8 +21,13 @@ enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE, /* ACL verbs */ -static uschar *verbs[] = - { US"accept", US"defer", US"deny", US"discard", US"drop", US"require", +static uschar *verbs[] = { + US"accept", + US"defer", + US"deny", + US"discard", + US"drop", + US"require", US"warn" }; /* For each verb, the conditions for which "message" or "log_message" are used @@ -66,6 +71,9 @@ enum { ACLC_ACL, #ifndef DISABLE_DKIM ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, +#endif +#ifdef EXPERIMENTAL_DMARC + ACLC_DMARC_STATUS, #endif ACLC_DNSLISTS, ACLC_DOMAINS, @@ -88,6 +96,7 @@ enum { ACLC_ACL, #ifdef WITH_CONTENT_SCAN ACLC_REGEX, #endif + ACLC_REMOVE_HEADER, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, @@ -98,6 +107,7 @@ enum { ACLC_ACL, ACLC_SPF, ACLC_SPF_GUESS, #endif + ACLC_UDPSEND, ACLC_VERIFY }; /* ACL conditions/modifiers: "delay", "control", "continue", "endpass", @@ -128,6 +138,9 @@ static uschar *conditions[] = { #ifndef DISABLE_DKIM US"dkim_signers", US"dkim_status", +#endif +#ifdef EXPERIMENTAL_DMARC + US"dmarc_status", #endif US"dnslists", US"domains", @@ -150,6 +163,7 @@ static uschar *conditions[] = { #ifdef WITH_CONTENT_SCAN US"regex", #endif + US"remove_header", US"sender_domains", US"senders", US"set", #ifdef WITH_CONTENT_SCAN US"spam", @@ -158,6 +172,7 @@ static uschar *conditions[] = { US"spf", US"spf_guess", #endif + US"udpsend", US"verify" }; @@ -173,9 +188,15 @@ enum { #ifndef DISABLE_DKIM CONTROL_DKIM_VERIFY, #endif + #ifdef EXPERIMENTAL_DMARC + CONTROL_DMARC_VERIFY, + CONTROL_DMARC_FORENSIC, + #endif + CONTROL_DSCP, CONTROL_ERROR, CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART, + CONTROL_CUTTHROUGH_DELIVERY, CONTROL_ENFORCE_SYNC, CONTROL_NO_ENFORCE_SYNC, CONTROL_FREEZE, @@ -207,9 +228,15 @@ static uschar *controls[] = { #ifndef DISABLE_DKIM US"dkim_disable_verify", #endif + #ifdef EXPERIMENTAL_DMARC + US"dmarc_disable_verify", + US"dmarc_enable_forensic", + #endif + US"dscp", US"error", US"caseful_local_part", US"caselower_local_part", + US"cutthrough_delivery", US"enforce_sync", US"no_enforce_sync", US"freeze", @@ -232,7 +259,7 @@ at the outer level. In the other cases, expansion already occurs in the checking functions. */ static uschar cond_expand_at_top[] = { - TRUE, /* acl */ + FALSE, /* acl */ TRUE, /* add_header */ FALSE, /* authenticated */ #ifdef EXPERIMENTAL_BRIGHTMAIL @@ -254,6 +281,9 @@ static uschar cond_expand_at_top[] = { #ifndef DISABLE_DKIM TRUE, /* dkim_signers */ TRUE, /* dkim_status */ +#endif +#ifdef EXPERIMENTAL_DMARC + TRUE, /* dmarc_status */ #endif TRUE, /* dnslists */ FALSE, /* domains */ @@ -276,6 +306,7 @@ static uschar cond_expand_at_top[] = { #ifdef WITH_CONTENT_SCAN TRUE, /* regex */ #endif + TRUE, /* remove_header */ FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ @@ -286,6 +317,7 @@ static uschar cond_expand_at_top[] = { TRUE, /* spf */ TRUE, /* spf_guess */ #endif + TRUE, /* udpsend */ TRUE /* verify */ }; @@ -314,6 +346,9 @@ static uschar cond_modifiers[] = { #ifndef DISABLE_DKIM FALSE, /* dkim_signers */ FALSE, /* dkim_status */ +#endif +#ifdef EXPERIMENTAL_DMARC + FALSE, /* dmarc_status */ #endif FALSE, /* dnslists */ FALSE, /* domains */ @@ -336,6 +371,7 @@ static uschar cond_modifiers[] = { #ifdef WITH_CONTENT_SCAN FALSE, /* regex */ #endif + TRUE, /* remove_header */ FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ @@ -346,6 +382,7 @@ static uschar cond_modifiers[] = { FALSE, /* spf */ FALSE, /* spf_guess */ #endif + TRUE, /* udpsend */ FALSE /* verify */ }; @@ -360,6 +397,9 @@ static unsigned int cond_forbids[] = { (unsigned int) ~((1<text; + while ((cp = Ustrchr(s, '\n')) != NULL) + { + if (cp[1] == '\0') break; + + /* contains embedded newline; needs doubling */ + ret = string_cat(ret, &size, &ptr, s, cp-s+1); + ret = string_cat(ret, &size, &ptr, US"\n", 1); + s = cp+1; + } + /* last bit of header */ + + ret = string_cat(ret, &size, &ptr, s, cp-s+1); /* newline-sep list */ + } +while((h = h->next)); + +ret[ptr-1] = '\0'; /* overwrite last newline */ +return ret; +} + + +/************************************************* +* Set up removed header line(s) * +*************************************************/ + +/* This function is called by the remove_header modifier. The argument is +treated as a sequence of header names which are added to a colon separated +list, provided there isn't an identical one already there. + +Argument: string of header names +Returns: nothing +*/ + +static void +setup_remove_header(uschar *hnames) +{ +if (*hnames != 0) + { + if (acl_removed_headers == NULL) + acl_removed_headers = hnames; + else + acl_removed_headers = string_sprintf("%s : %s", acl_removed_headers, hnames); + } +} + + /************************************************* * Handle warnings * @@ -1365,7 +1550,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); assertion: legitimate SMTP clients are all explicitly authorized with CSA SRV records of their own. */ - if (found != domain) + if (Ustrcmp(found, domain) != 0) { if (port & 1) return t->data.val = CSA_FAIL_EXPLICIT; @@ -1429,6 +1614,7 @@ else DNS_LOOKUP_AGAIN: #endif +lookup_dnssec_authenticated = NULL; switch (dns_lookup(&dnsa, target, type, NULL)) { /* If something bad happened (most commonly DNS_AGAIN), defer. */ @@ -1464,6 +1650,62 @@ switch (dns_lookup(&dnsa, target, type, NULL)) * Handle verification (address & other) * *************************************************/ +enum { VERIFY_REV_HOST_LKUP, VERIFY_CERT, VERIFY_HELO, VERIFY_CSA, VERIFY_HDR_SYNTAX, + VERIFY_NOT_BLIND, VERIFY_HDR_SNDR, VERIFY_SNDR, VERIFY_RCPT, + VERIFY_HDR_NAMES_ASCII + }; +typedef struct { + uschar * name; + int value; + unsigned where_allowed; /* bitmap */ + BOOL no_options; /* Never has /option(s) following */ + unsigned alt_opt_sep; /* >0 Non-/ option separator (custom parser) */ + } verify_type_t; +static verify_type_t verify_type_list[] = { + { US"reverse_host_lookup", VERIFY_REV_HOST_LKUP, ~0, FALSE, 0 }, + { US"certificate", VERIFY_CERT, ~0, TRUE, 0 }, + { US"helo", VERIFY_HELO, ~0, TRUE, 0 }, + { US"csa", VERIFY_CSA, ~0, FALSE, 0 }, + { US"header_syntax", VERIFY_HDR_SYNTAX, (1<alt_opt_sep ? strncmpic(ss, vp->name, vp->alt_opt_sep) == 0 + : strcmpic (ss, vp->name) == 0) + break; +if ((char *)vp >= (char *)verify_type_list + sizeof(verify_type_list)) + goto BAD_VERIFY; + +if (vp->no_options && slash != NULL) { - if (slash != NULL) goto NO_OPTIONS; - if (sender_host_address == NULL) return OK; - return acl_verify_reverse(user_msgptr, log_msgptr); + *log_msgptr = string_sprintf("unexpected '/' found in \"%s\" " + "(this verify item has no options)", arg); + return ERROR; } - -/* TLS certificate verification is done at STARTTLS time; here we just -test whether it was successful or not. (This is for optional verification; for -mandatory verification, the connection doesn't last this long.) */ - -if (strcmpic(ss, US"certificate") == 0) +if (!(vp->where_allowed & (1<name, acl_wherenames[where]); + return ERROR; } - -/* We can test the result of optional HELO verification that might have -occurred earlier. If not, we can attempt the verification now. */ - -if (strcmpic(ss, US"helo") == 0) +switch(vp->value) { - if (slash != NULL) goto NO_OPTIONS; - if (!helo_verified && !helo_verify_failed) smtp_verify_helo(); - return helo_verified? OK : FAIL; - } + case VERIFY_REV_HOST_LKUP: + if (sender_host_address == NULL) return OK; + if ((rc = acl_verify_reverse(user_msgptr, log_msgptr)) == DEFER) + while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) + if (strcmpic(ss, US"defer_ok") == 0) + return OK; + return rc; + + case VERIFY_CERT: + /* TLS certificate verification is done at STARTTLS time; here we just + test whether it was successful or not. (This is for optional verification; for + mandatory verification, the connection doesn't last this long.) */ + + if (tls_in.certificate_verified) return OK; + *user_msgptr = US"no verified certificate"; + return FAIL; -/* Do Client SMTP Authorization checks in a separate function, and turn the -result code into user-friendly strings. */ + case VERIFY_HELO: + /* We can test the result of optional HELO verification that might have + occurred earlier. If not, we can attempt the verification now. */ -if (strcmpic(ss, US"csa") == 0) - { - rc = acl_verify_csa(list); - *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s", - csa_reason_string[rc]); - csa_status = csa_status_string[rc]; - DEBUG(D_acl) debug_printf("CSA result %s\n", csa_status); - return csa_return_code[rc]; - } + if (!helo_verified && !helo_verify_failed) smtp_verify_helo(); + return helo_verified? OK : FAIL; -/* Check that all relevant header lines have the correct syntax. If there is -a syntax error, we return details of the error to the sender if configured to -send out full details. (But a "message" setting on the ACL can override, as -always). */ + case VERIFY_CSA: + /* Do Client SMTP Authorization checks in a separate function, and turn the + result code into user-friendly strings. */ -if (strcmpic(ss, US"header_syntax") == 0) - { - if (slash != NULL) goto NO_OPTIONS; - if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP) goto WRONG_ACL; - rc = verify_check_headers(log_msgptr); - if (rc != OK && smtp_return_error_details && *log_msgptr != NULL) - *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr); - return rc; - } + rc = acl_verify_csa(list); + *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s", + csa_reason_string[rc]); + csa_status = csa_status_string[rc]; + DEBUG(D_acl) debug_printf("CSA result %s\n", csa_status); + return csa_return_code[rc]; + + case VERIFY_HDR_SYNTAX: + /* Check that all relevant header lines have the correct syntax. If there is + a syntax error, we return details of the error to the sender if configured to + send out full details. (But a "message" setting on the ACL can override, as + always). */ + + rc = verify_check_headers(log_msgptr); + if (rc != OK && smtp_return_error_details && *log_msgptr != NULL) + *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr); + return rc; -/* Check that no recipient of this message is "blind", that is, every envelope -recipient must be mentioned in either To: or Cc:. */ + case VERIFY_HDR_NAMES_ASCII: + /* Check that all header names are true 7 bit strings + See RFC 5322, 2.2. and RFC 6532, 3. */ -if (strcmpic(ss, US"not_blind") == 0) - { - if (slash != NULL) goto NO_OPTIONS; - if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP) goto WRONG_ACL; - rc = verify_check_notblind(); - if (rc != OK) - { - *log_msgptr = string_sprintf("bcc recipient detected"); - if (smtp_return_error_details) + rc = verify_check_header_names_ascii(log_msgptr); + if (rc != OK && smtp_return_error_details && *log_msgptr != NULL) *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr); - } - return rc; - } - -/* The remaining verification tests check recipient and sender addresses, -either from the envelope or from the header. There are a number of -slash-separated options that are common to all of them. */ + return rc; + case VERIFY_NOT_BLIND: + /* Check that no recipient of this message is "blind", that is, every envelope + recipient must be mentioned in either To: or Cc:. */ -/* Check that there is at least one verifiable sender address in the relevant -header lines. This can be followed by callout and defer options, just like -sender and recipient. */ + rc = verify_check_notblind(); + if (rc != OK) + { + *log_msgptr = string_sprintf("bcc recipient detected"); + if (smtp_return_error_details) + *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr); + } + return rc; -if (strcmpic(ss, US"header_sender") == 0) - { - if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP) goto WRONG_ACL; - verify_header_sender = TRUE; - } + /* The remaining verification tests check recipient and sender addresses, + either from the envelope or from the header. There are a number of + slash-separated options that are common to all of them. */ -/* Otherwise, first item in verify argument must be "sender" or "recipient". -In the case of a sender, this can optionally be followed by an address to use -in place of the actual sender (rare special-case requirement). */ + case VERIFY_HDR_SNDR: + verify_header_sender = TRUE; + break; -else if (strncmpic(ss, US"sender", 6) == 0) - { - uschar *s = ss + 6; - if (where > ACL_WHERE_NOTSMTP) - { - *log_msgptr = string_sprintf("cannot verify sender in ACL for %s " - "(only possible for MAIL, RCPT, PREDATA, or DATA)", - acl_wherenames[where]); - return ERROR; - } - if (*s == 0) - verify_sender_address = sender_address; - else + case VERIFY_SNDR: + /* In the case of a sender, this can optionally be followed by an address to use + in place of the actual sender (rare special-case requirement). */ { - while (isspace(*s)) s++; - if (*s++ != '=') goto BAD_VERIFY; - while (isspace(*s)) s++; - verify_sender_address = string_copy(s); - } - } -else - { - if (strcmpic(ss, US"recipient") != 0) goto BAD_VERIFY; - if (addr == NULL) - { - *log_msgptr = string_sprintf("cannot verify recipient in ACL for %s " - "(only possible for RCPT)", acl_wherenames[where]); - return ERROR; + uschar *s = ss + 6; + if (*s == 0) + verify_sender_address = sender_address; + else + { + while (isspace(*s)) s++; + if (*s++ != '=') goto BAD_VERIFY; + while (isspace(*s)) s++; + verify_sender_address = string_copy(s); + } } + break; + + case VERIFY_RCPT: + break; } + + /* Remaining items are optional; they apply to sender and recipient verification, including "header sender" verification. */ @@ -1680,112 +1920,60 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)) uschar buffer[256]; while (isspace(*ss)) ss++; - /* This callout option handling code has become a mess as new options - have been added in an ad hoc manner. It should be tidied up into some - kind of table-driven thing. */ - while ((opt = string_nextinlist(&ss, &optsep, buffer, sizeof(buffer))) != NULL) { - if (strcmpic(opt, US"defer_ok") == 0) callout_defer_ok = TRUE; - else if (strcmpic(opt, US"no_cache") == 0) - verify_options |= vopt_callout_no_cache; - else if (strcmpic(opt, US"random") == 0) - verify_options |= vopt_callout_random; - else if (strcmpic(opt, US"use_sender") == 0) - verify_options |= vopt_callout_recipsender; - else if (strcmpic(opt, US"use_postmaster") == 0) - verify_options |= vopt_callout_recippmaster; - else if (strcmpic(opt, US"postmaster") == 0) pm_mailfrom = US""; - else if (strcmpic(opt, US"fullpostmaster") == 0) - { - pm_mailfrom = US""; - verify_options |= vopt_callout_fullpm; - } + callout_opt_t * op; + double period = 1.0F; - else if (strncmpic(opt, US"mailfrom", 8) == 0) - { - if (!verify_header_sender) - { - *log_msgptr = string_sprintf("\"mailfrom\" is allowed as a " - "callout option only for verify=header_sender (detected in ACL " - "condition \"%s\")", arg); - return ERROR; - } - opt += 8; - while (isspace(*opt)) opt++; - if (*opt++ != '=') - { - *log_msgptr = string_sprintf("'=' expected after " - "\"mailfrom\" in ACL condition \"%s\"", arg); - return ERROR; - } - while (isspace(*opt)) opt++; - se_mailfrom = string_copy(opt); - } + for (op= callout_opt_list; op->name; op++) + if (strncmpic(opt, op->name, Ustrlen(op->name)) == 0) + break; - else if (strncmpic(opt, US"postmaster_mailfrom", 19) == 0) - { - opt += 19; - while (isspace(*opt)) opt++; - if (*opt++ != '=') - { - *log_msgptr = string_sprintf("'=' expected after " - "\"postmaster_mailfrom\" in ACL condition \"%s\"", arg); - return ERROR; - } - while (isspace(*opt)) opt++; - pm_mailfrom = string_copy(opt); - } - - else if (strncmpic(opt, US"maxwait", 7) == 0) - { - opt += 7; - while (isspace(*opt)) opt++; - if (*opt++ != '=') - { - *log_msgptr = string_sprintf("'=' expected after \"maxwait\" in " - "ACL condition \"%s\"", arg); - return ERROR; - } - while (isspace(*opt)) opt++; - callout_overall = readconf_readtime(opt, 0, FALSE); - if (callout_overall < 0) - { - *log_msgptr = string_sprintf("bad time value in ACL condition " - "\"verify %s\"", arg); - return ERROR; - } - } - else if (strncmpic(opt, US"connect", 7) == 0) - { - opt += 7; + verify_options |= op->flag; + if (op->has_option) + { + opt += Ustrlen(op->name); while (isspace(*opt)) opt++; if (*opt++ != '=') { *log_msgptr = string_sprintf("'=' expected after " - "\"callout_overaall\" in ACL condition \"%s\"", arg); + "\"%s\" in ACL verify condition \"%s\"", op->name, arg); return ERROR; } while (isspace(*opt)) opt++; - callout_connect = readconf_readtime(opt, 0, FALSE); - if (callout_connect < 0) + } + if (op->timeval) + { + period = readconf_readtime(opt, 0, FALSE); + if (period < 0) { *log_msgptr = string_sprintf("bad time value in ACL condition " "\"verify %s\"", arg); return ERROR; } - } - else /* Plain time is callout connect/command timeout */ - { - callout = readconf_readtime(opt, 0, FALSE); - if (callout < 0) - { - *log_msgptr = string_sprintf("bad time value in ACL condition " - "\"verify %s\"", arg); - return ERROR; - } - } + } + + switch(op->value) + { + case CALLOUT_DEFER_OK: callout_defer_ok = TRUE; break; + case CALLOUT_POSTMASTER: pm_mailfrom = US""; break; + case CALLOUT_FULLPOSTMASTER: pm_mailfrom = US""; break; + case CALLOUT_MAILFROM: + if (!verify_header_sender) + { + *log_msgptr = string_sprintf("\"mailfrom\" is allowed as a " + "callout option only for verify=header_sender (detected in ACL " + "condition \"%s\")", arg); + return ERROR; + } + se_mailfrom = string_copy(opt); + break; + case CALLOUT_POSTMASTER_MAILFROM: pm_mailfrom = string_copy(opt); break; + case CALLOUT_MAXWAIT: callout_overall = period; break; + case CALLOUT_CONNECT: callout_connect = period; break; + case CALLOUT_TIME: callout = period; break; + } } } else @@ -2030,24 +2218,10 @@ return rc; BAD_VERIFY: *log_msgptr = string_sprintf("expected \"sender[=address]\", \"recipient\", " - "\"helo\", \"header_syntax\", \"header_sender\" or " - "\"reverse_host_lookup\" at start of ACL condition " + "\"helo\", \"header_syntax\", \"header_sender\", \"header_names_ascii\" " + "or \"reverse_host_lookup\" at start of ACL condition " "\"verify %s\"", arg); return ERROR; - -/* Options supplied when not allowed come here */ - -NO_OPTIONS: -*log_msgptr = string_sprintf("unexpected '/' found in \"%s\" " - "(this verify item has no options)", arg); -return ERROR; - -/* Calls in the wrong ACL come here */ - -WRONG_ACL: -*log_msgptr = string_sprintf("cannot check header contents in ACL for %s " - "(only possible in ACL for DATA)", acl_wherenames[where]); -return ERROR; } @@ -2120,7 +2294,7 @@ uschar buffer[STRING_SPRINTF_BUFFER_SIZE]; va_start(ap, format); if (!string_vformat(buffer, sizeof(buffer), format, ap)) log_write(0, LOG_MAIN|LOG_PANIC_DIE, - "string_sprintf expansion was longer than %d", sizeof(buffer)); + "string_sprintf expansion was longer than " SIZE_T_FMT, sizeof(buffer)); va_end(ap); *log_msgptr = string_sprintf( "error in arguments to \"ratelimit\" condition: %s", buffer); @@ -2182,7 +2356,10 @@ rate measurement as opposed to rate limiting. */ sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0); if (sender_rate_limit == NULL) + { limit = -1.0; + ss = NULL; /* compiler quietening */ + } else { limit = Ustrtod(sender_rate_limit, &ss); @@ -2255,7 +2432,7 @@ while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)) else if (strcmpic(ss, US"per_addr") == 0) { RATE_SET(mode, PER_RCPT); - if (where != ACL_WHERE_RCPT) badacl = TRUE, unique = "*"; + if (where != ACL_WHERE_RCPT) badacl = TRUE, unique = US"*"; else unique = string_sprintf("%s@%s", deliver_localpart, deliver_domain); } else if (strncmpic(ss, US"count=", 6) == 0) @@ -2664,6 +2841,110 @@ return rc; +/************************************************* +* The udpsend ACL modifier * +*************************************************/ + +/* Called by acl_check_condition() below. + +Arguments: + arg the option string for udpsend= + log_msgptr for error messages + +Returns: OK - Completed. + DEFER - Problem with DNS lookup. + ERROR - Syntax error in options. +*/ + +static int +acl_udpsend(uschar *arg, uschar **log_msgptr) +{ +int sep = 0; +uschar *hostname; +uschar *portstr; +uschar *portend; +host_item *h; +int portnum; +int len; +int r, s; +uschar * errstr; + +hostname = string_nextinlist(&arg, &sep, NULL, 0); +portstr = string_nextinlist(&arg, &sep, NULL, 0); + +if (hostname == NULL) + { + *log_msgptr = US"missing destination host in \"udpsend\" modifier"; + return ERROR; + } +if (portstr == NULL) + { + *log_msgptr = US"missing destination port in \"udpsend\" modifier"; + return ERROR; + } +if (arg == NULL) + { + *log_msgptr = US"missing datagram payload in \"udpsend\" modifier"; + return ERROR; + } +portnum = Ustrtol(portstr, &portend, 10); +if (*portend != '\0') + { + *log_msgptr = US"bad destination port in \"udpsend\" modifier"; + return ERROR; + } + +/* Make a single-item host list. */ +h = store_get(sizeof(host_item)); +memset(h, 0, sizeof(host_item)); +h->name = hostname; +h->port = portnum; +h->mx = MX_NONE; + +if (string_is_ip_address(hostname, NULL)) + h->address = hostname, r = HOST_FOUND; +else + r = host_find_byname(h, NULL, 0, NULL, FALSE); +if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN) + { + *log_msgptr = US"DNS lookup failed in \"udpsend\" modifier"; + return DEFER; + } + +HDEBUG(D_acl) + debug_printf("udpsend [%s]:%d %s\n", h->address, portnum, arg); + +r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum, + 1, NULL, &errstr); +if (r < 0) goto defer; +len = Ustrlen(arg); +r = send(s, arg, len, 0); +if (r < 0) + { + errstr = US strerror(errno); + close(s); + goto defer; + } +close(s); +if (r < len) + { + *log_msgptr = + string_sprintf("\"udpsend\" truncated from %d to %d octets", len, r); + return DEFER; + } + +HDEBUG(D_acl) + debug_printf("udpsend %d bytes\n", r); + +return OK; + +defer: +*log_msgptr = string_sprintf("\"udpsend\" failed: %s", errstr); +return DEFER; +} + + + /************************************************* * Handle conditions/modifiers on an ACL item * *************************************************/ @@ -2705,7 +2986,7 @@ uschar *debug_opts = NULL; uschar *p = NULL; int rc = OK; #ifdef WITH_CONTENT_SCAN -int sep = '/'; +int sep = -'/'; #endif for (; cb != NULL; cb = cb->next) @@ -2718,12 +2999,14 @@ for (; cb != NULL; cb = cb->next) if (cb->type == ACLC_MESSAGE) { + HDEBUG(D_acl) debug_printf(" message: %s\n", cb->arg); user_message = cb->arg; continue; } if (cb->type == ACLC_LOG_MESSAGE) { + HDEBUG(D_acl) debug_printf("l_message: %s\n", cb->arg); log_message = cb->arg; continue; } @@ -2799,14 +3082,14 @@ for (; cb != NULL; cb = cb->next) "discard" verb. */ case ACLC_ACL: - rc = acl_check_internal(where, addr, arg, level+1, user_msgptr, log_msgptr); - if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD) - { - *log_msgptr = string_sprintf("nested ACL returned \"discard\" for " - "\"%s\" command (only allowed with \"accept\" or \"discard\")", - verbs[verb]); - return ERROR; - } + rc = acl_check_wargs(where, addr, arg, level+1, user_msgptr, log_msgptr); + if (rc == DISCARD && verb != ACL_ACCEPT && verb != ACL_DISCARD) + { + *log_msgptr = string_sprintf("nested ACL returned \"discard\" for " + "\"%s\" command (only allowed with \"accept\" or \"discard\")", + verbs[verb]); + return ERROR; + } break; case ACLC_AUTHENTICATED: @@ -2830,7 +3113,9 @@ for (; cb != NULL; cb = cb->next) /* The true/false parsing here should be kept in sync with that used in expand.c when dealing with ECOND_BOOL so that we don't have too many different definitions of what can be a boolean. */ - if (Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */ + if (*arg == '-' + ? Ustrspn(arg+1, "0123456789") == Ustrlen(arg+1) /* Negative number */ + : Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */ rc = (Uatoi(arg) == 0)? FAIL : OK; else rc = (strcmpic(arg, US"no") == 0 || @@ -2871,9 +3156,64 @@ for (; cb != NULL; cb = cb->next) #ifndef DISABLE_DKIM case CONTROL_DKIM_VERIFY: dkim_disable_verify = TRUE; + #ifdef EXPERIMENTAL_DMARC + /* Since DKIM was blocked, skip DMARC too */ + dmarc_disable_verify = TRUE; + dmarc_enable_forensic = FALSE; + #endif break; #endif + #ifdef EXPERIMENTAL_DMARC + case CONTROL_DMARC_VERIFY: + dmarc_disable_verify = TRUE; + break; + + case CONTROL_DMARC_FORENSIC: + dmarc_enable_forensic = TRUE; + break; + #endif + + case CONTROL_DSCP: + if (*p == '/') + { + int fd, af, level, optname, value; + /* If we are acting on stdin, the setsockopt may fail if stdin is not + a socket; we can accept that, we'll just debug-log failures anyway. */ + fd = fileno(smtp_in); + af = ip_get_address_family(fd); + if (af < 0) + { + HDEBUG(D_acl) + debug_printf("smtp input is probably not a socket [%s], not setting DSCP\n", + strerror(errno)); + break; + } + if (dscp_lookup(p+1, af, &level, &optname, &value)) + { + if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0) + { + HDEBUG(D_acl) debug_printf("failed to set input DSCP[%s]: %s\n", + p+1, strerror(errno)); + } + else + { + HDEBUG(D_acl) debug_printf("set input DSCP to \"%s\"\n", p+1); + } + } + else + { + *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg); + return ERROR; + } + } + else + { + *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); + return ERROR; + } + break; + case CONTROL_ERROR: return ERROR; @@ -2915,8 +3255,9 @@ for (; cb != NULL; cb = cb->next) disable_callout_flush = TRUE; break; - case CONTROL_FAKEDEFER: case CONTROL_FAKEREJECT: + cancel_cutthrough_connection("fakereject"); + case CONTROL_FAKEDEFER: fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; if (*p == '/') { @@ -2946,10 +3287,12 @@ for (; cb != NULL; cb = cb->next) *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); return ERROR; } + cancel_cutthrough_connection("item frozen"); break; case CONTROL_QUEUE_ONLY: queue_only_policy = TRUE; + cancel_cutthrough_connection("queueing forced"); break; case CONTROL_SUBMISSION: @@ -3013,6 +3356,30 @@ for (; cb != NULL; cb = cb->next) case CONTROL_SUPPRESS_LOCAL_FIXUPS: suppress_local_fixups = TRUE; break; + + case CONTROL_CUTTHROUGH_DELIVERY: + if (prdr_requested) + /* Too hard to think about for now. We might in future cutthrough + the case where both sides handle prdr and this-node prdr acl + is "accept" */ + *log_msgptr = string_sprintf(US"PRDR on %s reception\n", arg); + else + { + if (deliver_freeze) + *log_msgptr = US"frozen"; + else if (queue_only_policy) + *log_msgptr = US"queue-only"; + else if (fake_response == FAIL) + *log_msgptr = US"fakereject"; + else + { + if (rcpt_count == 1) cutthrough.delivery = TRUE; + break; + } + *log_msgptr = string_sprintf("\"control=%s\" on %s item", + arg, *log_msgptr); + } + return ERROR; } break; @@ -3110,6 +3477,18 @@ for (; cb != NULL; cb = cb->next) break; #endif + #ifdef EXPERIMENTAL_DMARC + case ACLC_DMARC_STATUS: + if (!dmarc_has_been_checked) + dmarc_process(); + dmarc_has_been_checked = TRUE; + /* 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); + break; + #endif + case ACLC_DNSLISTS: rc = verify_check_dnsbl(&arg); break; @@ -3126,11 +3505,11 @@ for (; cb != NULL; cb = cb->next) writing is poorly documented. */ case ACLC_ENCRYPTED: - if (tls_cipher == NULL) rc = FAIL; else + if (tls_in.cipher == NULL) rc = FAIL; else { uschar *endcipher = NULL; - uschar *cipher = Ustrchr(tls_cipher, ':'); - if (cipher == NULL) cipher = tls_cipher; else + uschar *cipher = Ustrchr(tls_in.cipher, ':'); + if (cipher == NULL) cipher = tls_in.cipher; else { endcipher = Ustrchr(++cipher, ':'); if (endcipher != NULL) *endcipher = 0; @@ -3216,21 +3595,28 @@ for (; cb != NULL; cb = cb->next) break; #ifdef WITH_CONTENT_SCAN - case ACLC_MALWARE: + case ACLC_MALWARE: /* Run the malware backend. */ { /* Separate the regular expression and any optional parameters. */ uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size); - /* Run the malware backend. */ - rc = malware(&ss); - /* Modify return code based upon the existance of options. */ - while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)) - != NULL) { - if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) - { - /* FAIL so that the message is passed to the next ACL */ - rc = FAIL; - } - } + uschar *opt; + BOOL defer_ok = FALSE; + int timeout = 0; + + while ((opt = string_nextinlist(&arg, &sep, NULL, 0))) + if (strcmpic(opt, US"defer_ok") == 0) + defer_ok = TRUE; + else if ( strncmpic(opt, US"tmo=", 4) == 0 + && (timeout = readconf_readtime(opt+4, '\0', FALSE)) < 0 + ) + { + *log_msgptr = string_sprintf("bad timeout value in '%s'", opt); + return ERROR; + } + + rc = malware(ss, timeout); + if (rc == DEFER && defer_ok) + rc = FAIL; /* FAIL so that the message is passed to the next ACL */ } break; @@ -3254,6 +3640,10 @@ for (; cb != NULL; cb = cb->next) break; #endif + case ACLC_REMOVE_HEADER: + setup_remove_header(arg); + break; + case ACLC_SENDER_DOMAINS: { uschar *sdomain; @@ -3309,6 +3699,10 @@ for (; cb != NULL; cb = cb->next) break; #endif + case ACLC_UDPSEND: + rc = acl_udpsend(arg, log_msgptr); + break; + /* If the verb is WARN, discard any user message from verification, because such messages are SMTP responses, not header additions. The latter come only from explicit "message" modifiers. However, put the user message into @@ -3708,7 +4102,7 @@ while (acl != NULL) switch (cond) { case DEFER: - HDEBUG(D_acl) debug_printf("%s: condition test deferred\n", verbs[acl->verb]); + HDEBUG(D_acl) debug_printf("%s: condition test deferred in %s\n", verbs[acl->verb], acl_name); if (basic_errno != ERRNO_CALLOUTDEFER) { if (search_error_message != NULL && *search_error_message != 0) @@ -3724,29 +4118,29 @@ while (acl != NULL) default: /* Paranoia */ case ERROR: - HDEBUG(D_acl) debug_printf("%s: condition test error\n", verbs[acl->verb]); + HDEBUG(D_acl) debug_printf("%s: condition test error in %s\n", verbs[acl->verb], acl_name); return ERROR; case OK: - HDEBUG(D_acl) debug_printf("%s: condition test succeeded\n", - verbs[acl->verb]); + HDEBUG(D_acl) debug_printf("%s: condition test succeeded in %s\n", + verbs[acl->verb], acl_name); break; case FAIL: - HDEBUG(D_acl) debug_printf("%s: condition test failed\n", verbs[acl->verb]); + HDEBUG(D_acl) debug_printf("%s: condition test failed in %s\n", verbs[acl->verb], acl_name); break; /* DISCARD and DROP can happen only from a nested ACL condition, and DISCARD can happen only for an "accept" or "discard" verb. */ case DISCARD: - HDEBUG(D_acl) debug_printf("%s: condition test yielded \"discard\"\n", - verbs[acl->verb]); + HDEBUG(D_acl) debug_printf("%s: condition test yielded \"discard\" in %s\n", + verbs[acl->verb], acl_name); break; case FAIL_DROP: - HDEBUG(D_acl) debug_printf("%s: condition test yielded \"drop\"\n", - verbs[acl->verb]); + HDEBUG(D_acl) debug_printf("%s: condition test yielded \"drop\" in %s\n", + verbs[acl->verb], acl_name); break; } @@ -3757,7 +4151,11 @@ while (acl != NULL) switch(acl->verb) { case ACL_ACCEPT: - if (cond == OK || cond == DISCARD) return cond; + if (cond == OK || cond == DISCARD) + { + HDEBUG(D_acl) debug_printf("end of %s: ACCEPT\n", acl_name); + return cond; + } if (endpass_seen) { HDEBUG(D_acl) debug_printf("accept: endpass encountered - denying access\n"); @@ -3768,17 +4166,26 @@ while (acl != NULL) case ACL_DEFER: if (cond == OK) { + HDEBUG(D_acl) debug_printf("end of %s: DEFER\n", acl_name); acl_temp_details = TRUE; return DEFER; } break; case ACL_DENY: - if (cond == OK) return FAIL; + if (cond == OK) + { + HDEBUG(D_acl) debug_printf("end of %s: DENY\n", acl_name); + return FAIL; + } break; case ACL_DISCARD: - if (cond == OK || cond == DISCARD) return DISCARD; + if (cond == OK || cond == DISCARD) + { + HDEBUG(D_acl) debug_printf("end of %s: DISCARD\n", acl_name); + return DISCARD; + } if (endpass_seen) { HDEBUG(D_acl) debug_printf("discard: endpass encountered - denying access\n"); @@ -3787,11 +4194,19 @@ while (acl != NULL) break; case ACL_DROP: - if (cond == OK) return FAIL_DROP; + if (cond == OK) + { + HDEBUG(D_acl) debug_printf("end of %s: DROP\n", acl_name); + return FAIL_DROP; + } break; case ACL_REQUIRE: - if (cond != OK) return cond; + if (cond != OK) + { + HDEBUG(D_acl) debug_printf("end of %s: not OK\n", acl_name); + return cond; + } break; case ACL_WARN: @@ -3823,10 +4238,97 @@ return FAIL; } + + +/* Same args as acl_check_internal() above, but the string s is +the name of an ACL followed optionally by up to 9 space-separated arguments. +The name and args are separately expanded. Args go into $acl_arg globals. */ +static int +acl_check_wargs(int where, address_item *addr, uschar *s, int level, + uschar **user_msgptr, uschar **log_msgptr) +{ +uschar * tmp; +uschar * tmp_arg[9]; /* must match acl_arg[] */ +uschar * sav_arg[9]; /* must match acl_arg[] */ +int sav_narg; +uschar * name; +int i; +int ret; + +if (!(tmp = string_dequote(&s)) || !(name = expand_string(tmp))) + goto bad; + +for (i = 0; i < 9; i++) + { + while (*s && isspace(*s)) s++; + if (!*s) break; + if (!(tmp = string_dequote(&s)) || !(tmp_arg[i] = expand_string(tmp))) + { + tmp = name; + goto bad; + } + } + +sav_narg = acl_narg; +acl_narg = i; +for (i = 0; i < acl_narg; i++) + { + sav_arg[i] = acl_arg[i]; + acl_arg[i] = tmp_arg[i]; + } +while (i < 9) + { + sav_arg[i] = acl_arg[i]; + acl_arg[i++] = NULL; + } + +ret = acl_check_internal(where, addr, name, level, user_msgptr, log_msgptr); + +acl_narg = sav_narg; +for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i]; +return ret; + +bad: +if (expand_string_forcedfail) return ERROR; +*log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", + tmp, expand_string_message); +return search_find_defer?DEFER:ERROR; +} + + + /************************************************* * Check access using an ACL * *************************************************/ +/* Alternate interface for ACL, used by expansions */ +int +acl_eval(int where, uschar *s, uschar **user_msgptr, uschar **log_msgptr) +{ +address_item adb; +address_item *addr = NULL; + +*user_msgptr = *log_msgptr = NULL; +sender_verified_failed = NULL; +ratelimiters_cmd = NULL; +log_reject_target = LOG_MAIN|LOG_REJECT; + +if (where == ACL_WHERE_RCPT) + { + adb = address_defaults; + addr = &adb; + addr->address = expand_string(US"$local_part@$domain"); + addr->domain = deliver_domain; + addr->local_part = deliver_localpart; + addr->cc_local_part = deliver_localpart; + addr->lc_local_part = deliver_localpart; + } + +return acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr); +} + + + /* This is the external interface for ACL checks. It sets up an address and the expansions for $domain and $local_part when called after RCPT, then calls acl_check_internal() to do the actual work. @@ -3845,6 +4347,7 @@ Returns: OK access is granted by an ACCEPT verb DEFER can't tell at the moment ERROR disaster */ +int acl_where = ACL_WHERE_UNKNOWN; int acl_check(int where, uschar *recipient, uschar *s, uschar **user_msgptr, @@ -3859,7 +4362,11 @@ sender_verified_failed = NULL; ratelimiters_cmd = NULL; log_reject_target = LOG_MAIN|LOG_REJECT; +#ifndef DISABLE_PRDR +if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR) +#else if (where == ACL_WHERE_RCPT) +#endif { adb = address_defaults; addr = &adb; @@ -3873,7 +4380,54 @@ if (where == ACL_WHERE_RCPT) deliver_localpart = addr->local_part; } +acl_where = where; rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr); +acl_where = ACL_WHERE_UNKNOWN; + +/* Cutthrough - if requested, +and WHERE_RCPT and not yet opened conn as result of recipient-verify, +and rcpt acl returned accept, +and first recipient (cancel on any subsequents) +open one now and run it up to RCPT acceptance. +A failed verify should cancel cutthrough request. + +Initial implementation: dual-write to spool. +Assume the rxd datastream is now being copied byte-for-byte to an open cutthrough connection. + +Cease cutthrough copy on rxd final dot; do not send one. + +On a data acl, if not accept and a cutthrough conn is open, hard-close it (no SMTP niceness). + +On data acl accept, terminate the dataphase on an open cutthrough conn. If accepted or +perm-rejected, reflect that to the original sender - and dump the spooled copy. +If temp-reject, close the conn (and keep the spooled copy). +If conn-failure, no action (and keep the spooled copy). +*/ +switch (where) +{ +case ACL_WHERE_RCPT: +#ifndef DISABLE_PRDR +case ACL_WHERE_PRDR: +#endif + if (rc == OK && cutthrough.delivery && rcpt_count > cutthrough.nrcpt) + open_cutthrough_connection(addr); + break; + +case ACL_WHERE_PREDATA: + if( rc == OK ) + cutthrough_predata(); + else + cancel_cutthrough_connection("predata acl not ok"); + break; + +case ACL_WHERE_QUIT: +case ACL_WHERE_NOTQUIT: + cancel_cutthrough_connection("quit or notquit"); + break; + +default: + break; +} deliver_domain = deliver_localpart = deliver_address_data = sender_address_data = NULL; @@ -3912,7 +4466,6 @@ return rc; } - /************************************************* * Create ACL variable * *************************************************/ @@ -3971,4 +4524,6 @@ FILE *f = (FILE *)ctx; fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value); } +/* vi: aw ai sw=2 +*/ /* End of acl.c */