X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b0b9dbb191f8c60bf7233338a9a0a14c72f1f7a0..4c04137d73637107669e02b21f890387aaa2ef34:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index ec1505bf0..64b310968 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -46,7 +46,7 @@ static int msgcond[] = { }; /* ACL condition and modifier codes - keep in step with the table that -follows, and the cond_expand_at_top and uschar cond_modifiers tables lower +follows. down. */ enum { ACLC_ACL, @@ -113,75 +113,247 @@ enum { ACLC_ACL, modifiers that look like conditions but always return TRUE. They are used for their side effects. */ -static uschar *conditions[] = { - US"acl", - US"add_header", - US"authenticated", +typedef struct condition_def { + uschar *name; + +/* Flag to indicate the condition/modifier has a string expansion done +at the outer level. In the other cases, expansion already occurs in the +checking functions. */ + BOOL expand_at_top:1; + + BOOL is_modifier:1; + +/* Bit map vector of which conditions and modifiers are not allowed at certain +times. For each condition and modifier, there's a bitmap of dis-allowed times. +For some, it is easier to specify the negation of a small number of allowed +times. */ + unsigned forbids; + +} condition_def; + +static condition_def conditions[] = { + { US"acl", FALSE, FALSE, 0 }, + + { US"add_header", TRUE, TRUE, + (unsigned int) + ~((1< first) + { + int middle = (first + last)/2; + uschar * s = ol[middle].name; + int c = Ustrncmp(name, s, Ustrlen(s)); + if (c == 0) return middle; + else if (c > 0) first = middle + 1; + else last = middle; + } +return -1; +} + + + +/************************************************* +* Pick out condition from list * +*************************************************/ + +/* Use a binary chop method + +Arguments: + name name to find + list list of conditions + end size of list + +Returns: offset in list, or -1 if not found +*/ + +static int +acl_checkcondition(uschar * name, condition_def * list, int end) +{ +int start = 0; +while (start < end) + { + int mid = (start + end)/2; + int c = Ustrcmp(name, list[mid].name); + if (c == 0) return mid; + if (c < 0) end = mid; + else start = mid + 1; + } +return -1; +} + + /************************************************* * Pick out name from list * *************************************************/ @@ -896,8 +726,7 @@ while ((s = (*func)()) != NULL) /* If a verb is unrecognized, it may be another condition or modifier that continues the previous verb. */ - v = acl_checkname(name, verbs, sizeof(verbs)/sizeof(char *)); - if (v < 0) + if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0) { if (this == NULL) { @@ -934,8 +763,7 @@ while ((s = (*func)()) != NULL) /* Handle a condition or modifier. */ - c = acl_checkname(name, conditions, sizeof(conditions)/sizeof(char *)); - if (c < 0) + if ((c = acl_checkcondition(name, conditions, nelem(conditions))) < 0) { *error = string_sprintf("unknown ACL condition/modifier in \"%s\"", saveline); @@ -944,10 +772,10 @@ while ((s = (*func)()) != NULL) /* The modifiers may not be negated */ - if (negated && cond_modifiers[c]) + if (negated && conditions[c].is_modifier) { *error = string_sprintf("ACL error: negation is not allowed with " - "\"%s\"", conditions[c]); + "\"%s\"", conditions[c].name); return NULL; } @@ -958,7 +786,7 @@ while ((s = (*func)()) != NULL) this->verb != ACL_DISCARD) { *error = string_sprintf("ACL error: \"%s\" is not allowed with \"%s\"", - conditions[c], verbs[this->verb]); + conditions[c].name, verbs[this->verb]); return NULL; } @@ -1025,7 +853,7 @@ while ((s = (*func)()) != NULL) if (*s++ != '=') { *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name, - cond_modifiers[c]? US"modifier" : US"condition"); + conditions[c].is_modifier ? US"modifier" : US"condition"); return NULL; } while (isspace(*s)) s++; @@ -1373,7 +1201,7 @@ an odd configuration - why didn't the SRV record have a weight of 1 instead?) Arguments: dnsa the DNS answer block dnss a DNS scan block for us to use - reset option specifing what portion to scan, as described above + reset option specifying what portion to scan, as described above target the target hostname to use for matching RR names Returns: CSA_OK successfully authorized @@ -1455,7 +1283,6 @@ acl_verify_csa(const uschar *domain) { tree_node *t; const uschar *found; -uschar *p; int priority, weight, port; dns_answer dnsa; dns_scan dnss; @@ -1533,14 +1360,13 @@ switch (dns_special_lookup(&dnsa, domain, T_CSA, &found)) /* Scan the reply for well-formed CSA SRV records. */ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); - rr != NULL; - rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) + rr; + rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV) { - if (rr->type != T_SRV) continue; + const uschar * p = rr->data; /* Extract the numerical SRV fields (p is incremented) */ - p = rr->data; GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); @@ -1559,12 +1385,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); SRV records of their own. */ if (Ustrcmp(found, domain) != 0) - { - if (port & 1) - return t->data.val = CSA_FAIL_EXPLICIT; - else - return t->data.val = CSA_UNKNOWN; - } + return t->data.val = port & 1 ? CSA_FAIL_EXPLICIT : CSA_UNKNOWN; /* 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 @@ -2012,15 +1833,15 @@ message if giving out verification details. */ if (verify_header_sender) { int verrno; - rc = verify_check_header_address(user_msgptr, log_msgptr, callout, + + if ((rc = verify_check_header_address(user_msgptr, log_msgptr, callout, callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options, - &verrno); - if (rc != OK) + &verrno)) != OK) { *basic_errno = verrno; if (smtp_return_error_details) { - if (*user_msgptr == NULL && *log_msgptr != NULL) + if (!*user_msgptr && *log_msgptr) *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr); if (rc == DEFER) acl_temp_details = TRUE; } @@ -2042,10 +1863,9 @@ Therefore, we always do a full sender verify when any kind of callout is specified. Caching elsewhere, for instance in the DNS resolver and in the callout handling, should ensure that this is not terribly inefficient. */ -else if (verify_sender_address != NULL) +else if (verify_sender_address) { - if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster)) - != 0) + if ((verify_options & (vopt_callout_recipsender|vopt_callout_recippmaster))) { *log_msgptr = US"use_sender or use_postmaster cannot be used for a " "sender verify callout"; @@ -2061,7 +1881,9 @@ else if (verify_sender_address != NULL) callout that was done previously). If the "routed" flag is not set, routing must have failed, so we use the saved return code. */ - if (testflag(sender_vaddr, af_verify_routed)) rc = OK; else + if (testflag(sender_vaddr, af_verify_routed)) + rc = OK; + else { rc = sender_vaddr->special_action; *basic_errno = sender_vaddr->basic_errno; @@ -2115,22 +1937,21 @@ else if (verify_sender_address != NULL) HDEBUG(D_acl) debug_printf("----------- end verify ------------\n"); - if (rc == OK) - { - if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0) - { - DEBUG(D_acl) debug_printf("sender %s verified ok as %s\n", - verify_sender_address, sender_vaddr->address); - } - else - { - DEBUG(D_acl) debug_printf("sender %s verified ok\n", - verify_sender_address); - } - } - else *basic_errno = sender_vaddr->basic_errno; + if (rc != OK) + *basic_errno = sender_vaddr->basic_errno; + else + DEBUG(D_acl) + { + if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0) + debug_printf("sender %s verified ok as %s\n", + verify_sender_address, sender_vaddr->address); + else + debug_printf("sender %s verified ok\n", + verify_sender_address); + } } - else rc = OK; /* Null sender */ + else + rc = OK; /* Null sender */ /* Cache the result code */ @@ -2251,26 +2072,20 @@ Returns: CONTROL_xxx value static int decode_control(const uschar *arg, const uschar **pptr, int where, uschar **log_msgptr) { -int len; -control_def *d; +int idx, len; +control_def * d; -for (d = controls_list; - d < controls_list + sizeof(controls_list)/sizeof(control_def); - d++) - { - len = Ustrlen(d->name); - if (Ustrncmp(d->name, arg, len) == 0) break; - } - -if (d >= controls_list + sizeof(controls_list)/sizeof(control_def) || - (arg[len] != 0 && (!d->has_option || arg[len] != '/'))) +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] != '/') + ) ) { *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); return CONTROL_ERROR; } *pptr = arg + len; -return d->value; +return idx; } @@ -2425,7 +2240,7 @@ while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)) else if (strcmpic(ss, US"per_byte") == 0) { /* If we have not yet received the message data and there was no SIZE - declaration on the MAIL comand, then it's safe to just use a value of + declaration on the MAIL command, then it's safe to just use a value of zero and let the recorded rate decay as if nothing happened. */ RATE_SET(mode, PER_MAIL); if (where > ACL_WHERE_NOTSMTP) badacl = TRUE; @@ -2791,7 +2606,7 @@ else size of the event per the period size, ignoring the lack of events outside the current period and regardless of where the event falls in the period. So, if the interval was so long that the calculated rate is unhelpfully small, we - re-intialize the rate. In the absence of higher-rate bursts, the condition + re-initialize the rate. In the absence of higher-rate bursts, the condition below is true if the interval is greater than the period. */ if (dbd->rate < count) dbd->rate = count; @@ -2874,17 +2689,17 @@ uschar * errstr; hostname = string_nextinlist(&arg, &sep, NULL, 0); portstr = string_nextinlist(&arg, &sep, NULL, 0); -if (hostname == NULL) +if (!hostname) { *log_msgptr = US"missing destination host in \"udpsend\" modifier"; return ERROR; } -if (portstr == NULL) +if (!portstr) { *log_msgptr = US"missing destination port in \"udpsend\" modifier"; return ERROR; } -if (arg == NULL) +if (!arg) { *log_msgptr = US"missing datagram payload in \"udpsend\" modifier"; return ERROR; @@ -2983,8 +2798,6 @@ acl_check_condition(int verb, acl_condition_block *cb, int where, { uschar *user_message = NULL; uschar *log_message = NULL; -uschar *debug_tag = NULL; -uschar *debug_opts = NULL; int rc = OK; #ifdef WITH_CONTENT_SCAN int sep = -'/'; @@ -3025,7 +2838,7 @@ for (; cb != NULL; cb = cb->next) of them, but not for all, because expansion happens down in some lower level checking functions in some cases. */ - if (cond_expand_at_top[cb->type]) + if (conditions[cb->type].expand_at_top) { arg = expand_string(cb->arg); if (arg == NULL) @@ -3044,8 +2857,8 @@ for (; cb != NULL; cb = cb->next) { int lhswidth = 0; debug_printf("check %s%s %n", - (!cond_modifiers[cb->type] && cb->u.negated)? "!":"", - conditions[cb->type], &lhswidth); + (!conditions[cb->type].is_modifier && cb->u.negated)? "!":"", + conditions[cb->type].name, &lhswidth); if (cb->type == ACLC_SET) { @@ -3062,11 +2875,11 @@ for (; cb != NULL; cb = cb->next) /* Check that this condition makes sense at this time */ - if ((cond_forbids[cb->type] & (1 << where)) != 0) + if ((conditions[cb->type].forbids & (1 << where)) != 0) { *log_msgptr = string_sprintf("cannot %s %s condition in %s ACL", - cond_modifiers[cb->type]? "use" : "test", - conditions[cb->type], acl_wherenames[where]); + conditions[cb->type].is_modifier ? "use" : "test", + conditions[cb->type].name, acl_wherenames[where]); return ERROR; } @@ -3137,10 +2950,10 @@ for (; cb != NULL; cb = cb->next) /* Check if this control makes sense at this time */ - if ((control_forbids[control_type] & (1 << where)) != 0) + if (controls_list[control_type].forbids & (1 << where)) { *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL", - controls[control_type], acl_wherenames[where]); + controls_list[control_type].name, acl_wherenames[where]); return ERROR; } @@ -3336,24 +3149,39 @@ for (; cb != NULL; cb = cb->next) break; case CONTROL_DEBUG: - while (*p == '/') { - if (Ustrncmp(p, "/tag=", 5) == 0) - { - const uschar *pp = p + 5; - while (*pp != '\0' && *pp != '/') pp++; - debug_tag = string_copyn(p+5, pp-p-5); - p = pp; - } - else if (Ustrncmp(p, "/opts=", 6) == 0) + uschar * debug_tag = NULL; + uschar * debug_opts = NULL; + BOOL kill = FALSE; + + while (*p == '/') { - const uschar *pp = p + 6; - while (*pp != '\0' && *pp != '/') pp++; - debug_opts = string_copyn(p+6, pp-p-6); + const uschar * pp = p+1; + if (Ustrncmp(pp, "tag=", 4) == 0) + { + for (pp += 4; *pp && *pp != '/';) pp++; + debug_tag = string_copyn(p+5, pp-p-5); + } + else if (Ustrncmp(pp, "opts=", 5) == 0) + { + for (pp += 5; *pp && *pp != '/';) pp++; + debug_opts = string_copyn(p+6, pp-p-6); + } + else if (Ustrncmp(pp, "kill", 4) == 0) + { + for (pp += 4; *pp && *pp != '/';) pp++; + kill = TRUE; + } + else + while (*pp && *pp != '/') pp++; p = pp; } + + if (kill) + debug_logging_stop(); + else + debug_logging_activate(debug_tag, debug_opts); } - debug_logging_activate(debug_tag, debug_opts); break; case CONTROL_SUPPRESS_LOCAL_FIXUPS: @@ -3380,7 +3208,23 @@ for (; cb != NULL; cb = cb->next) *log_msgptr = US"fakereject"; else { - if (rcpt_count == 1) cutthrough.delivery = TRUE; + if (rcpt_count == 1) + { + cutthrough.delivery = TRUE; + 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; + } + } break; } *log_msgptr = string_sprintf("\"control=%s\" on %s item", @@ -3435,12 +3279,12 @@ for (; cb != NULL; cb = cb->next) #ifdef EXPERIMENTAL_DCC case ACLC_DCC: { - /* Seperate the regular expression and any optional parameters. */ + /* Separate the regular expression and any optional parameters. */ const uschar * list = arg; uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size); /* Run the dcc backend. */ rc = dcc_process(&ss); - /* Modify return code based upon the existance of options. */ + /* Modify return code based upon the existence of options. */ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) rc = FAIL; /* FAIL so that the message is passed to the next ACL */ @@ -3741,12 +3585,12 @@ for (; cb != NULL; cb = cb->next) #ifdef WITH_CONTENT_SCAN case ACLC_SPAM: { - /* Seperate the regular expression and any optional parameters. */ + /* Separate the regular expression and any optional parameters. */ const uschar * list = arg; uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size); /* Run the spam backend. */ rc = spam(CUSS &ss); - /* Modify return code based upon the existance of options. */ + /* Modify return code based upon the existence of options. */ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)) != NULL) { if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) @@ -3793,11 +3637,9 @@ for (; cb != NULL; cb = cb->next) /* If a condition was negated, invert OK/FAIL. */ - if (!cond_modifiers[cb->type] && cb->u.negated) - { + if (!conditions[cb->type].is_modifier && cb->u.negated) if (rc == OK) rc = FAIL; - else if (rc == FAIL || rc == FAIL_DROP) rc = OK; - } + else if (rc == FAIL || rc == FAIL_DROP) rc = OK; if (rc != OK) break; /* Conditions loop */ } @@ -4305,7 +4147,7 @@ HDEBUG(D_acl) debug_printf("end of %s: implicit DENY\n", acl_name); return FAIL; badquit: - *log_msgptr = string_sprintf("QUIT or not-QUIT teplevel ACL may not fail " + *log_msgptr = string_sprintf("QUIT or not-QUIT toplevel ACL may not fail " "('%s' verb used incorrectly)", verbs[acl->verb]); return ERROR; } @@ -4484,32 +4326,50 @@ 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: + { + case ACL_WHERE_RCPT: #ifndef DISABLE_PRDR -case ACL_WHERE_PRDR: + case ACL_WHERE_PRDR: #endif - if (host_checking_callout) /* -bhc mode */ - cancel_cutthrough_connection("host-checking mode"); - else if (rc == OK && cutthrough.delivery && rcpt_count > cutthrough.nrcpt) - rc = open_cutthrough_connection(addr); - break; + if (host_checking_callout) /* -bhc mode */ + cancel_cutthrough_connection("host-checking mode"); + + else if ( rc == OK + && cutthrough.delivery + && rcpt_count > cutthrough.nrcpt + && (rc = open_cutthrough_connection(addr)) == DEFER + ) + if (cutthrough.defer_pass) + { + uschar * s = addr->message; + /* Horrid kludge to recover target's SMTP message */ + while (*s) s++; + do --s; while (!isdigit(*s)); + if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s; + acl_temp_details = TRUE; + } + else + { + HDEBUG(D_acl) debug_printf("cutthrough defer; will spool\n"); + rc = OK; + } + break; -case ACL_WHERE_PREDATA: - if (rc == OK) - cutthrough_predata(); - else - cancel_cutthrough_connection("predata acl not ok"); - 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; + case ACL_WHERE_QUIT: + case ACL_WHERE_NOTQUIT: + cancel_cutthrough_connection("quit or notquit"); + break; -default: - break; -} + default: + break; + } deliver_domain = deliver_localpart = deliver_address_data = sender_address_data = NULL;