X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Facl.c;h=22610ceaa66e0e97b6c184bd397bed4a6b49b472;hp=7284831a6ef881952d4a1c184329b7d83d6157a7;hb=0d7a24c68f30ce73df5f0859b2de6a39e74b92bc;hpb=163144aab02a47427340d0ecc75e2abde675f4c9 diff --git a/src/src/acl.c b/src/src/acl.c index 7284831a6..22610ceaa 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -112,7 +112,8 @@ enum { ACLC_ACL, /* ACL conditions/modifiers: "delay", "control", "continue", "endpass", "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are modifiers that look like conditions but always return TRUE. They are used for -their side effects. */ +their side effects. Do not invent new modifier names that result in one name +being the prefix of another; the binary-search in the list will go wrong. */ typedef struct condition_def { uschar *name; @@ -366,7 +367,7 @@ enum { CONTROL_NO_MULTILINE, CONTROL_NO_PIPELINING, - CONTROL_QUEUE_ONLY, + CONTROL_QUEUE, CONTROL_SUBMISSION, CONTROL_SUPPRESS_LOCAL_FIXUPS, #ifdef SUPPORT_I18N @@ -502,8 +503,8 @@ static control_def controls_list[] = { ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START }, -[CONTROL_QUEUE_ONLY] = - { US"queue_only", FALSE, +[CONTROL_QUEUE] = + { US"queue", TRUE, (unsigned) ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA | ACL_BIT_DATA | @@ -511,7 +512,6 @@ static control_def controls_list[] = { ACL_BIT_NOTSMTP | ACL_BIT_MIME) }, - [CONTROL_SUBMISSION] = { US"submission", TRUE, (unsigned) @@ -732,7 +732,7 @@ uschar * s; *error = NULL; -while ((s = (*func)()) != NULL) +while ((s = (*func)())) { int v, c; BOOL negated = FALSE; @@ -742,8 +742,7 @@ while ((s = (*func)()) != NULL) /* Conditions (but not verbs) are allowed to be negated by an initial exclamation mark. */ - while (isspace(*s)) s++; - if (*s == '!') + if (Uskip_whitespace(&s) == '!') { negated = TRUE; s++; @@ -859,7 +858,7 @@ while ((s = (*func)()) != NULL) } cond->u.varname = string_copyn(s, 18); s = endptr; - while (isspace(*s)) s++; + Uskip_whitespace(&s); } else #endif @@ -895,7 +894,7 @@ while ((s = (*func)()) != NULL) cond->u.varname = string_copyn(s + 4, endptr - s - 4); s = endptr; - while (isspace(*s)) s++; + Uskip_whitespace(&s); } /* For "set", we are now positioned for the data. For the others, only @@ -909,7 +908,7 @@ while ((s = (*func)()) != NULL) conditions[c].is_modifier ? US"modifier" : US"condition"); return NULL; } - while (isspace(*s)) s++; + Uskip_whitespace(&s); cond->arg = string_copy(s); } } @@ -1022,8 +1021,8 @@ for (p = q; *p; p = q) if (!*hptr) { /* The header_line struct itself is not tainted, though it points to - tainted data. */ - header_line *h = store_get(sizeof(header_line), FALSE); + possibly tainted data. */ + header_line * h = store_get(sizeof(header_line), FALSE); h->text = hdr; h->next = NULL; h->type = newtype; @@ -1601,7 +1600,7 @@ an error if options are given for items that don't expect them. uschar *slash = Ustrchr(arg, '/'); const uschar *list = arg; -uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size); +uschar *ss = string_nextinlist(&list, &sep, NULL, 0); verify_type_t * vp; if (!ss) goto BAD_VERIFY; @@ -2113,7 +2112,9 @@ return ERROR; * Check argument for control= modifier * *************************************************/ -/* Called from acl_check_condition() below +/* Called from acl_check_condition() below. +To handle the case "queue_only" we accept an _ in the +initial / option-switch position. Arguments: arg the argument string for control= @@ -2129,10 +2130,11 @@ decode_control(const uschar *arg, const uschar **pptr, int where, uschar **log_m { int idx, len; control_def * d; +uschar c; if ( (idx = find_control(arg, controls_list, nelem(controls_list))) < 0 - || ( arg[len = Ustrlen((d = controls_list+idx)->name)] != 0 - && (!d->has_option || arg[len] != '/') + || ( (c = arg[len = Ustrlen((d = controls_list+idx)->name)]) != 0 + && (!d->has_option || c != '/' && c != '_') ) ) { *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); @@ -2258,7 +2260,7 @@ count = 1.0; /* 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; @@ -3018,193 +3020,196 @@ for (; cb; cb = cb->next) switch(control_type) { case CONTROL_AUTH_UNADVERTISED: - f.allow_auth_unadvertised = TRUE; - break; + f.allow_auth_unadvertised = TRUE; + break; - #ifdef EXPERIMENTAL_BRIGHTMAIL +#ifdef EXPERIMENTAL_BRIGHTMAIL case CONTROL_BMI_RUN: - bmi_run = 1; - break; - #endif + bmi_run = 1; + break; +#endif #ifndef DISABLE_DKIM case CONTROL_DKIM_VERIFY: - f.dkim_disable_verify = TRUE; + f.dkim_disable_verify = TRUE; # ifdef SUPPORT_DMARC - /* Since DKIM was blocked, skip DMARC too */ - f.dmarc_disable_verify = TRUE; - f.dmarc_enable_forensic = FALSE; + /* Since DKIM was blocked, skip DMARC too */ + f.dmarc_disable_verify = TRUE; + f.dmarc_enable_forensic = FALSE; # endif break; #endif #ifdef SUPPORT_DMARC case CONTROL_DMARC_VERIFY: - f.dmarc_disable_verify = TRUE; - break; + f.dmarc_disable_verify = TRUE; + break; case CONTROL_DMARC_FORENSIC: - f.dmarc_enable_forensic = TRUE; - break; + f.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) + if (*p == '/') { - HDEBUG(D_acl) - debug_printf_indent("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) + 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); + if ((af = ip_get_address_family(fd)) < 0) { - HDEBUG(D_acl) debug_printf_indent("failed to set input DSCP[%s]: %s\n", - p+1, strerror(errno)); + HDEBUG(D_acl) + debug_printf_indent("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_indent("failed to set input DSCP[%s]: %s\n", + p+1, strerror(errno)); + } + else + { + HDEBUG(D_acl) debug_printf_indent("set input DSCP to \"%s\"\n", p+1); + } else { - HDEBUG(D_acl) debug_printf_indent("set input DSCP to \"%s\"\n", p+1); + *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg); + return ERROR; } } else { - *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg); + *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); return ERROR; } - } - else - { - *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); - return ERROR; - } - break; + break; case CONTROL_ERROR: - return ERROR; + return ERROR; case CONTROL_CASEFUL_LOCAL_PART: - deliver_localpart = addr->cc_local_part; - break; + deliver_localpart = addr->cc_local_part; + break; case CONTROL_CASELOWER_LOCAL_PART: - deliver_localpart = addr->lc_local_part; - break; + deliver_localpart = addr->lc_local_part; + break; case CONTROL_ENFORCE_SYNC: - smtp_enforce_sync = TRUE; - break; + smtp_enforce_sync = TRUE; + break; case CONTROL_NO_ENFORCE_SYNC: - smtp_enforce_sync = FALSE; - break; + smtp_enforce_sync = FALSE; + break; - #ifdef WITH_CONTENT_SCAN +#ifdef WITH_CONTENT_SCAN case CONTROL_NO_MBOX_UNSPOOL: - f.no_mbox_unspool = TRUE; - break; - #endif + f.no_mbox_unspool = TRUE; + break; +#endif case CONTROL_NO_MULTILINE: - f.no_multiline_responses = TRUE; - break; + f.no_multiline_responses = TRUE; + break; case CONTROL_NO_PIPELINING: - f.pipelining_enable = FALSE; - break; + f.pipelining_enable = FALSE; + break; case CONTROL_NO_DELAY_FLUSH: - f.disable_delay_flush = TRUE; - break; + f.disable_delay_flush = TRUE; + break; case CONTROL_NO_CALLOUT_FLUSH: - f.disable_callout_flush = TRUE; - break; + f.disable_callout_flush = TRUE; + break; case CONTROL_FAKEREJECT: - cancel_cutthrough_connection(TRUE, US"fakereject"); - case CONTROL_FAKEDEFER: - fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; - if (*p == '/') - { - const uschar *pp = p + 1; - while (*pp) pp++; - fake_response_text = expand_string(string_copyn(p+1, pp-p-1)); - p = pp; - } - else - { - /* Explicitly reset to default string */ - fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s)."; - } - break; + cancel_cutthrough_connection(TRUE, US"fakereject"); + case CONTROL_FAKEDEFER: + fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; + if (*p == '/') + { + const uschar *pp = p + 1; + while (*pp) pp++; + fake_response_text = expand_string(string_copyn(p+1, pp-p-1)); + p = pp; + } + else /* Explicitly reset to default string */ + fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s)."; + break; case CONTROL_FREEZE: - f.deliver_freeze = TRUE; - deliver_frozen_at = time(NULL); - freeze_tell = freeze_tell_config; /* Reset to configured value */ - if (Ustrncmp(p, "/no_tell", 8) == 0) - { - p += 8; - freeze_tell = NULL; - } - if (*p != 0) - { - *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); - return ERROR; - } - cancel_cutthrough_connection(TRUE, US"item frozen"); - break; + f.deliver_freeze = TRUE; + deliver_frozen_at = time(NULL); + freeze_tell = freeze_tell_config; /* Reset to configured value */ + if (Ustrncmp(p, "/no_tell", 8) == 0) + { + p += 8; + freeze_tell = NULL; + } + if (*p) + { + *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); + return ERROR; + } + cancel_cutthrough_connection(TRUE, US"item frozen"); + break; - case CONTROL_QUEUE_ONLY: - f.queue_only_policy = TRUE; - cancel_cutthrough_connection(TRUE, US"queueing forced"); - break; + case CONTROL_QUEUE: + f.queue_only_policy = TRUE; + if (Ustrcmp(p, "_only") == 0) + p += 5; + else while (*p == '/') + if (Ustrncmp(p, "/only", 5) == 0) + { p += 5; f.queue_smtp = FALSE; } + else if (Ustrncmp(p, "/first_pass_route", 17) == 0) + { p += 17; f.queue_smtp = TRUE; } + else + break; + cancel_cutthrough_connection(TRUE, US"queueing forced"); + break; case CONTROL_SUBMISSION: - originator_name = US""; - f.submission_mode = TRUE; - while (*p == '/') - { - if (Ustrncmp(p, "/sender_retain", 14) == 0) - { - p += 14; - f.active_local_sender_retain = TRUE; - f.active_local_from_check = FALSE; - } - else if (Ustrncmp(p, "/domain=", 8) == 0) + originator_name = US""; + f.submission_mode = TRUE; + while (*p == '/') { - const uschar *pp = p + 8; - while (*pp && *pp != '/') pp++; - submission_domain = string_copyn(p+8, pp-p-8); - p = pp; + if (Ustrncmp(p, "/sender_retain", 14) == 0) + { + p += 14; + f.active_local_sender_retain = TRUE; + f.active_local_from_check = FALSE; + } + else if (Ustrncmp(p, "/domain=", 8) == 0) + { + const uschar *pp = p + 8; + while (*pp && *pp != '/') pp++; + submission_domain = string_copyn(p+8, pp-p-8); + p = pp; + } + /* The name= option must be last, because it swallows the rest of + the string. */ + else if (Ustrncmp(p, "/name=", 6) == 0) + { + 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)); + p = pp; + } + else break; } - /* The name= option must be last, because it swallows the rest of - the string. */ - else if (Ustrncmp(p, "/name=", 6) == 0) + if (*p) { - 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)); - p = pp; + *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); + return ERROR; } - else break; - } - if (*p != 0) - { - *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); - return ERROR; - } - break; + break; case CONTROL_DEBUG: { @@ -3239,99 +3244,99 @@ for (; cb; cb = cb->next) debug_logging_stop(); else debug_logging_activate(debug_tag, debug_opts); + break; } - break; case CONTROL_SUPPRESS_LOCAL_FIXUPS: - f.suppress_local_fixups = TRUE; - break; + f.suppress_local_fixups = TRUE; + break; case CONTROL_CUTTHROUGH_DELIVERY: - { - uschar * ignored = NULL; + { + uschar * ignored = NULL; #ifndef DISABLE_PRDR - if (prdr_requested) + if (prdr_requested) #else - if (0) + if (0) #endif - /* 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" */ - 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"; + /* 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" */ + ignored = US"PRDR active"; else { - if (rcpt_count == 1) + 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 == '/') { - pp += 6; - if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE; - /* else if (Ustrncmp(pp, "spool") == 0) ; default */ + 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; } - else - while (*pp && *pp != '/') pp++; - p = pp; } + else + ignored = US"nonfirst rcpt"; } - else - ignored = US"nonfirst rcpt"; } + DEBUG(D_acl) if (ignored) + debug_printf(" cutthrough request ignored on %s item\n", ignored); } - DEBUG(D_acl) if (ignored) - debug_printf(" cutthrough request ignored on %s item\n", ignored); - } break; #ifdef SUPPORT_I18N case CONTROL_UTF8_DOWNCONVERT: - if (*p == '/') - { - if (p[1] == '1') + if (*p == '/') { - message_utf8_downconvert = 1; - addr->prop.utf8_downcvt = TRUE; - addr->prop.utf8_downcvt_maybe = FALSE; - p += 2; - break; + if (p[1] == '1') + { + message_utf8_downconvert = 1; + addr->prop.utf8_downcvt = TRUE; + addr->prop.utf8_downcvt_maybe = FALSE; + p += 2; + break; + } + if (p[1] == '0') + { + message_utf8_downconvert = 0; + addr->prop.utf8_downcvt = FALSE; + addr->prop.utf8_downcvt_maybe = FALSE; + p += 2; + break; + } + if (p[1] == '-' && p[2] == '1') + { + message_utf8_downconvert = -1; + addr->prop.utf8_downcvt = FALSE; + addr->prop.utf8_downcvt_maybe = TRUE; + p += 3; + break; + } + *log_msgptr = US"bad option value for control=utf8_downconvert"; } - if (p[1] == '0') + else { - message_utf8_downconvert = 0; - addr->prop.utf8_downcvt = FALSE; + message_utf8_downconvert = 1; + addr->prop.utf8_downcvt = TRUE; addr->prop.utf8_downcvt_maybe = FALSE; - p += 2; break; } - if (p[1] == '-' && p[2] == '1') - { - message_utf8_downconvert = -1; - addr->prop.utf8_downcvt = FALSE; - addr->prop.utf8_downcvt_maybe = TRUE; - p += 3; - break; - } - *log_msgptr = US"bad option value for control=utf8_downconvert"; - } - else - { - message_utf8_downconvert = 1; - addr->prop.utf8_downcvt = TRUE; - addr->prop.utf8_downcvt_maybe = FALSE; - break; - } - return ERROR; + return ERROR; #endif } @@ -3472,13 +3477,13 @@ for (; cb; cb = cb->next) { 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; @@ -3491,8 +3496,7 @@ for (; cb; cb = cb->next) 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; @@ -3590,6 +3594,12 @@ for (; cb; cb = cb->next) #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( @@ -3831,16 +3841,16 @@ uschar *yield; for(;;) { - while (isspace(*acl_text)) acl_text++; /* Leading spaces/empty lines */ - if (*acl_text == 0) return NULL; /* No more data */ - yield = acl_text; /* Potential data line */ + Uskip_whitespace(&acl_text); /* Leading spaces/empty lines */ + if (!*acl_text) return NULL; /* No more data */ + yield = acl_text; /* Potential data line */ while (*acl_text && *acl_text != '\n') acl_text++; /* If we hit the end before a newline, we have the whole logical line. If it's a comment, there's no more data to be given. Otherwise, yield it. */ - if (*acl_text == 0) return (*yield == '#')? NULL : yield; + if (!*acl_text) return *yield == '#' ? NULL : yield; /* After reaching a newline, end this loop if the physical line does not start with '#'. If it does, it's a comment, and the loop continues. */