X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/3634fc257bd0667daef14d72005cd87c735bbb24..de6135a0cbbeb4fbae7233a40563a241de1c237b:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index 8af564308..f61d2dfdf 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 - 2012 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for handling Access Control Lists (ACLs) */ @@ -88,6 +88,7 @@ enum { ACLC_ACL, #ifdef WITH_CONTENT_SCAN ACLC_REGEX, #endif + ACLC_REMOVE_HEADER, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, @@ -150,6 +151,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", @@ -173,9 +175,11 @@ enum { #ifndef DISABLE_DKIM CONTROL_DKIM_VERIFY, #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 +211,11 @@ static uschar *controls[] = { #ifndef DISABLE_DKIM US"dkim_disable_verify", #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 +238,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 @@ -276,6 +282,7 @@ static uschar cond_expand_at_top[] = { #ifdef WITH_CONTENT_SCAN TRUE, /* regex */ #endif + TRUE, /* remove_header */ FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ @@ -336,6 +343,7 @@ static uschar cond_modifiers[] = { #ifdef WITH_CONTENT_SCAN FALSE, /* regex */ #endif + TRUE, /* remove_header */ FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ @@ -360,6 +368,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 * @@ -1464,6 +1602,60 @@ 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 + }; +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, TRUE, 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; + return acl_verify_reverse(user_msgptr, log_msgptr); -/* Do Client SMTP Authorization checks in a separate function, and turn the -result code into user-friendly strings. */ + 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 (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 (tls_in.certificate_verified) return OK; + *user_msgptr = US"no verified certificate"; + return 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_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"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; - } + if (!helo_verified && !helo_verify_failed) smtp_verify_helo(); + return helo_verified? OK : FAIL; -/* Check that no recipient of this message is "blind", that is, every envelope -recipient must be mentioned in either To: or Cc:. */ + 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"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 = 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; - } + 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. */ + 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:. */ + 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; -/* 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. */ - -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 - { - 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) + 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). */ { - *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 +1857,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; + verify_options |= op->flag; + if (op->has_option) + { + opt += Ustrlen(op->name); 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); + "\"%s\" in ACL verify condition \"%s\"", op->name, arg); return ERROR; } while (isspace(*opt)) opt++; - callout_overall = readconf_readtime(opt, 0, FALSE); - if (callout_overall < 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 if (strncmpic(opt, US"connect", 7) == 0) - { - opt += 7; - while (isspace(*opt)) opt++; - if (*opt++ != '=') - { - *log_msgptr = string_sprintf("'=' expected after " - "\"callout_overaall\" in ACL condition \"%s\"", arg); - return ERROR; - } - while (isspace(*opt)) opt++; - callout_connect = readconf_readtime(opt, 0, FALSE); - if (callout_connect < 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 @@ -2034,20 +2159,6 @@ BAD_VERIFY: "\"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 +2231,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); @@ -2255,7 +2366,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) @@ -2340,6 +2451,7 @@ case RATE_PER_RCPT: anchor = &ratelimiters_cmd; break; default: + anchor = NULL; /* silence an "unused" complaint */ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown ratelimit mode %d", mode); break; @@ -2798,14 +2910,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: @@ -2873,6 +2985,46 @@ for (; cb != NULL; cb = cb->next) 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; @@ -3012,6 +3164,20 @@ for (; cb != NULL; cb = cb->next) case CONTROL_SUPPRESS_LOCAL_FIXUPS: suppress_local_fixups = TRUE; break; + + case CONTROL_CUTTHROUGH_DELIVERY: + if (deliver_freeze) + { + *log_msgptr = string_sprintf("\"control=%s\" on frozen item", arg); + return ERROR; + } + if (queue_only_policy) + { + *log_msgptr = string_sprintf("\"control=%s\" on queue-only item", arg); + return ERROR; + } + cutthrough_delivery = TRUE; + break; } break; @@ -3125,11 +3291,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; @@ -3253,6 +3419,10 @@ for (; cb != NULL; cb = cb->next) break; #endif + case ACLC_REMOVE_HEADER: + setup_remove_header(arg); + break; + case ACLC_SENDER_DOMAINS: { uschar *sdomain; @@ -3707,7 +3877,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) @@ -3723,29 +3893,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; } @@ -3822,10 +3992,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. @@ -3844,6 +4101,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, @@ -3858,7 +4116,11 @@ sender_verified_failed = NULL; ratelimiters_cmd = NULL; log_reject_target = LOG_MAIN|LOG_REJECT; -if (where == ACL_WHERE_RCPT) +#ifdef EXPERIMENTAL_PRDR +if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR ) +#else +if (where == ACL_WHERE_RCPT ) +#endif { adb = address_defaults; addr = &adb; @@ -3872,7 +4134,56 @@ 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: +#ifdef EXPERIMENTAL_PRDR +case ACL_WHERE_PRDR: +#endif + if( rcpt_count > 1 ) + cancel_cutthrough_connection("more than one recipient"); + else if (rc == OK && cutthrough_delivery && cutthrough_fd < 0) + 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; @@ -3911,7 +4222,6 @@ return rc; } - /************************************************* * Create ACL variable * *************************************************/