X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/942f0be6c2cd3ec8c39ca234a449561d9d3c1075..e2e0f812212b81ac029f90b57c63ee88455f6a12:/src/src/expand.c diff --git a/src/src/expand.c b/src/src/expand.c index 839821ef7..88d4e756f 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -216,7 +216,6 @@ static uschar *op_table_main[] = { US"base62d", US"base64", US"base64d", - US"bless", US"domain", US"escape", US"escape8bit", @@ -264,7 +263,6 @@ enum { EOP_BASE62D, EOP_BASE64, EOP_BASE64D, - EOP_BLESS, EOP_DOMAIN, EOP_ESCAPE, EOP_ESCAPE8BIT, @@ -1856,7 +1854,7 @@ Returns: NULL if the variable does not exist, or something non-NULL if exists_only is TRUE */ -static uschar * +static const uschar * find_variable(uschar *name, BOOL exists_only, BOOL skipping, int *newsize) { var_entry * vp; @@ -1894,15 +1892,15 @@ if (Ustrncmp(name, "auth", 4) == 0) { uschar *endptr; int n = Ustrtoul(name + 4, &endptr, 10); - if (*endptr == 0 && n != 0 && n <= AUTH_VARS) - return !auth_vars[n-1] ? US"" : auth_vars[n-1]; + if (!*endptr && n != 0 && n <= AUTH_VARS) + return auth_vars[n-1] ? auth_vars[n-1] : US""; } else if (Ustrncmp(name, "regex", 5) == 0) { uschar *endptr; int n = Ustrtoul(name + 5, &endptr, 10); - if (*endptr == 0 && n != 0 && n <= REGEX_VARS) - return !regex_vars[n-1] ? US"" : regex_vars[n-1]; + if (!*endptr && n != 0 && n <= REGEX_VARS) + return regex_vars[n-1] ? regex_vars[n-1] : US""; } /* For all other variables, search the table */ @@ -2545,16 +2543,13 @@ BOOL tempcond, combined_cond; BOOL *subcondptr; BOOL sub2_honour_dollar = TRUE; BOOL is_forany, is_json, is_jsons; -int rc, cond_type, roffset; +int rc, cond_type; int_eximarith_t num[2]; struct stat statbuf; uschar * opname; uschar name[256]; const uschar *sub[10]; -const pcre *re; -const uschar *rerror; - for (;;) if (Uskip_whitespace(&s) == '!') { testfor = !testfor; s++; } else break; @@ -2565,7 +2560,7 @@ switch(cond_type = identify_operator(&s, &opname)) case ECOND_DEF: { - uschar * t; + const uschar * t; if (*s != ':') { @@ -2976,15 +2971,24 @@ switch(cond_type = identify_operator(&s, &opname)) break; case ECOND_MATCH: /* Regular expression match */ - if (!(re = pcre_compile(CS sub[1], PCRE_COPT, CCSS &rerror, - &roffset, NULL))) { - expand_string_message = string_sprintf("regular expression error in " - "\"%s\": %s at offset %d", sub[1], rerror, roffset); - return NULL; + const pcre2_code * re; + PCRE2_SIZE offset; + int err; + + if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED, + PCRE_COPT, &err, &offset, pcre_cmp_ctx))) + { + uschar errbuf[128]; + pcre2_get_error_message(err, errbuf, sizeof(errbuf)); + expand_string_message = string_sprintf("regular expression error in " + "\"%s\": %s at offset %d", sub[1], errbuf, offset); + return NULL; + } + + tempcond = regex_match_and_setup(re, sub[0], 0, -1); + break; } - tempcond = regex_match_and_setup(re, sub[0], 0, -1); - break; case ECOND_MATCH_ADDRESS: /* Match in an address list */ rc = match_address_list(sub[0], TRUE, FALSE, &(sub[1]), NULL, -1, 0, NULL); @@ -3450,9 +3454,10 @@ switch(cond_type = identify_operator(&s, &opname)) /* ${if inbound_srs {local_part}{secret} {yes}{no}} */ { uschar * sub[2]; - const pcre * re; - int ovec[3*(4+1)]; - int n, quoting = 0; + const pcre2_code * re; + pcre2_match_data * md; + PCRE2_SIZE * ovec; + int quoting = 0; uschar cksum[4]; BOOL boolvalue = FALSE; @@ -3468,12 +3473,14 @@ switch(cond_type = identify_operator(&s, &opname)) re = regex_must_compile(US"^(?i)SRS0=([^=]+)=([A-Z2-7]+)=([^=]*)=(.*)$", TRUE, FALSE); - if (pcre_exec(re, NULL, CS sub[0], Ustrlen(sub[0]), 0, PCRE_EOPT, - ovec, nelem(ovec)) < 0) + md = pcre2_match_data_create(4+1, pcre_gen_ctx); + if (pcre2_match(re, sub[0], PCRE2_ZERO_TERMINATED, 0, PCRE_EOPT, + md, pcre_mtc_ctx) < 0) { DEBUG(D_expand) debug_printf("no match for SRS'd local-part pattern\n"); goto srs_result; } + ovec = pcre2_get_ovector_pointer(md); if (sub[0][0] == '"') quoting = 1; @@ -3505,6 +3512,7 @@ switch(cond_type = identify_operator(&s, &opname)) struct timeval now; uschar * ss = sub[0] + ovec[4]; /* substring 2, the timestamp */ long d; + int n; gettimeofday(&now, NULL); now.tv_sec /= 86400; /* days since epoch */ @@ -3598,7 +3606,7 @@ Returns: the value of expand max to save */ static int -save_expand_strings(uschar **save_expand_nstring, int *save_expand_nlength) +save_expand_strings(const uschar **save_expand_nstring, int *save_expand_nlength) { for (int i = 0; i <= expand_nmax; i++) { @@ -3625,7 +3633,7 @@ Returns: nothing */ static void -restore_expand_strings(int save_expand_nmax, uschar **save_expand_nstring, +restore_expand_strings(int save_expand_nmax, const uschar **save_expand_nstring, int *save_expand_nlength) { expand_nmax = save_expand_nmax; @@ -4303,6 +4311,98 @@ return FALSE; /* should not happen */ } +/* Expand a named list. Return false on failure. */ +static gstring * +expand_listnamed(gstring * yield, const uschar * name, const uschar * listtype) +{ +tree_node *t = NULL; +const uschar * list; +int sep = 0; +uschar * item; +uschar * suffix = US""; +BOOL needsep = FALSE; +#define LISTNAMED_BUF_SIZE 256 +uschar b[LISTNAMED_BUF_SIZE]; +uschar * buffer = b; + +if (*name == '+') name++; +if (!listtype) /* no-argument version */ + { + if ( !(t = tree_search(addresslist_anchor, name)) + && !(t = tree_search(domainlist_anchor, name)) + && !(t = tree_search(hostlist_anchor, name))) + t = tree_search(localpartlist_anchor, name); + } +else switch(*listtype) /* specific list-type version */ + { + case 'a': t = tree_search(addresslist_anchor, name); suffix = US"_a"; break; + case 'd': t = tree_search(domainlist_anchor, name); suffix = US"_d"; break; + case 'h': t = tree_search(hostlist_anchor, name); suffix = US"_h"; break; + case 'l': t = tree_search(localpartlist_anchor, name); suffix = US"_l"; break; + default: + expand_string_message = US"bad suffix on \"list\" operator"; + return yield; + } + +if(!t) + { + expand_string_message = string_sprintf("\"%s\" is not a %snamed list", + name, !listtype?"" + : *listtype=='a'?"address " + : *listtype=='d'?"domain " + : *listtype=='h'?"host " + : *listtype=='l'?"localpart " + : 0); + return yield; + } + +list = ((namedlist_block *)(t->data.ptr))->string; + +/* The list could be quite long so we (re)use a buffer for each element +rather than getting each in new memory */ + +if (is_tainted(list)) buffer = store_get(LISTNAMED_BUF_SIZE, TRUE); +while ((item = string_nextinlist(&list, &sep, buffer, LISTNAMED_BUF_SIZE))) + { + uschar * buf = US" : "; + if (needsep) + yield = string_catn(yield, buf, 3); + else + needsep = TRUE; + + if (*item == '+') /* list item is itself a named list */ + { + yield = expand_listnamed(yield, item, listtype); + if (expand_string_message) + return yield; + } + + else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */ + { + char tok[3]; + tok[0] = sep; tok[1] = ':'; tok[2] = 0; + + for(char * cp; cp = strpbrk(CCS item, tok); item = US cp) + { + yield = string_catn(yield, item, cp - CS item); + if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */ + yield = string_catn(yield, US"::", 2); + else /* sep in item; should already be doubled; emit once */ + { + yield = string_catn(yield, US tok, 1); + if (*cp == sep) cp++; + } + } + yield = string_cat(yield, item); + } + else + yield = string_cat(yield, item); + } +return yield; +} + + + /************************************************* * Expand string * *************************************************/ @@ -4374,7 +4474,7 @@ rmark reset_point = store_mark(); gstring * yield = string_get(Ustrlen(string) + 64); int item_type; const uschar *s = string; -uschar *save_expand_nstring[EXPAND_MAXN+1]; +const uschar *save_expand_nstring[EXPAND_MAXN+1]; int save_expand_nlength[EXPAND_MAXN+1]; BOOL resetok = TRUE; @@ -4393,17 +4493,16 @@ DEBUG(D_expand) f.expand_string_forcedfail = FALSE; expand_string_message = US""; -if (is_tainted(string)) +{ uschar *m; +if ((m = is_tainted2(string, LOG_MAIN|LOG_PANIC, "Tainted string '%s' in expansion", s))) { - expand_string_message = - string_sprintf("attempt to expand tainted string '%s'", s); - log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message); + expand_string_message = m; goto EXPAND_FAILED; } +} while (*s) { - uschar *value; uschar name[256]; /* \ escapes the next character, which must exist, or else @@ -4461,6 +4560,7 @@ while (*s) if (isalpha((*(++s)))) { + const uschar * value; int len; int newsize = 0; gstring * g = NULL; @@ -4503,7 +4603,7 @@ while (*s) But there is no error here - nothing gets inserted. */ if (!value) - { + { /*{*/ if (Ustrchr(name, '}')) malformed_header = TRUE; continue; } @@ -4533,7 +4633,7 @@ while (*s) yield = g; yield->size = newsize; yield->ptr = len; - yield->s = value; + yield->s = US value; /* known to be in new store i.e. a copy, so deconst safe */ } else yield = string_catn(yield, value, len); @@ -5099,7 +5199,7 @@ while (*s) { uschar *sub_arg[3]; gstring * g; - const pcre *re; + const pcre2_code *re; uschar *p; /* TF: Ugliness: We want to expand parameter 1 first, then set @@ -5739,11 +5839,11 @@ while (*s) case EITEM_SG: { - const pcre *re; + const pcre2_code * re; int moffset, moffsetextra, slen; - int roffset; - int emptyopt; - const uschar *rerror; + PCRE2_SIZE roffset; + pcre2_match_data * md; + int err, emptyopt; uschar *subject; uschar *sub[3]; int save_expand_nmax = @@ -5758,13 +5858,16 @@ while (*s) /* Compile the regular expression */ - if (!(re = pcre_compile(CS sub[1], PCRE_COPT, CCSS &rerror, - &roffset, NULL))) + if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED, + PCRE_COPT, &err, &roffset, pcre_cmp_ctx))) { + uschar errbuf[128]; + pcre2_get_error_message(err, errbuf, sizeof(errbuf)); expand_string_message = string_sprintf("regular expression error in " - "\"%s\": %s at offset %d", sub[1], rerror, roffset); + "\"%s\": %s at offset %ld", sub[1], errbuf, (long)roffset); goto EXPAND_FAILED; } + md = pcre2_match_data_create(EXPAND_MAXN + 1, pcre_gen_ctx); /* Now run a loop to do the substitutions as often as necessary. It ends when there are no more matches. Take care over matches of the null string; @@ -5777,9 +5880,9 @@ while (*s) for (;;) { - int ovector[3*(EXPAND_MAXN+1)]; - int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra, - PCRE_EOPT | emptyopt, ovector, nelem(ovector)); + PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); + int n = pcre2_match(re, (PCRE2_SPTR)subject, slen, moffset + moffsetextra, + PCRE_EOPT | emptyopt, md, pcre_mtc_ctx); uschar *insert; /* No match - if we previously set PCRE_NOTEMPTY after a null match, this @@ -5807,19 +5910,19 @@ while (*s) expand_nmax = 0; for (int nn = 0; nn < n*2; nn += 2) { - expand_nstring[expand_nmax] = subject + ovector[nn]; - expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn]; + expand_nstring[expand_nmax] = subject + ovec[nn]; + expand_nlength[expand_nmax++] = ovec[nn+1] - ovec[nn]; } expand_nmax--; /* Copy the characters before the match, plus the expanded insertion. */ - yield = string_catn(yield, subject + moffset, ovector[0] - moffset); + yield = string_catn(yield, subject + moffset, ovec[0] - moffset); if (!(insert = expand_string(sub[2]))) goto EXPAND_FAILED; yield = string_cat(yield, insert); - moffset = ovector[1]; + moffset = ovec[1]; moffsetextra = 0; emptyopt = 0; @@ -5830,10 +5933,10 @@ while (*s) string at the same point. If this fails (picked up above) we advance to the next character. */ - if (ovector[0] == ovector[1]) + if (ovec[0] == ovec[1]) { - if (ovector[0] == slen) break; - emptyopt = PCRE_NOTEMPTY | PCRE_ANCHORED; + if (ovec[0] == slen) break; + emptyopt = PCRE2_NOTEMPTY | PCRE2_ANCHORED; } } @@ -6341,13 +6444,10 @@ while (*s) condition for real. For EITEM_MAP and EITEM_REDUCE, do the same, using the normal internal expansion function. */ - if (item_type == EITEM_FILTER) - { - if ((temp = eval_condition(expr, &resetok, NULL))) - s = temp; - } - else + if (item_type != EITEM_FILTER) temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok); + else + if ((temp = eval_condition(expr, &resetok, NULL))) s = temp; if (!temp) { @@ -6356,7 +6456,7 @@ while (*s) goto EXPAND_FAILED; } - Uskip_whitespace(&s); + Uskip_whitespace(&s); /*{*/ if (*s++ != '}') { /*{*/ expand_string_message = string_sprintf("missing } at end of condition " @@ -7020,20 +7120,6 @@ while (*s) continue; } - case EOP_BLESS: - /* This is purely for the convenience of the test harness. Do not enable - it otherwise as it defeats the taint-checking security. */ - - if (f.running_in_test_harness) - yield = string_cat(yield, is_tainted(sub) - ? string_copy_taint(sub, FALSE) : sub); - else - { - DEBUG(D_expand) debug_printf_indent("bless operator not supported\n"); - yield = string_cat(yield, sub); - } - continue; - case EOP_EXPAND: { uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok); @@ -7232,10 +7318,10 @@ while (*s) case EOP_LISTCOUNT: { - int cnt = 0; - int sep = 0; + int cnt = 0, sep = 0; + uschar * buf = store_get(2, is_tainted(sub)); - while (string_nextinlist(CUSS &sub, &sep, NULL, 0)) cnt++; + while (string_nextinlist(CUSS &sub, &sep, buf, 1)) cnt++; yield = string_fmt_append(yield, "%d", cnt); continue; } @@ -7244,86 +7330,11 @@ while (*s) /* handles nested named lists; requotes as colon-sep list */ case EOP_LISTNAMED: - { - tree_node *t = NULL; - const uschar * list; - int sep = 0; - uschar * item; - uschar * suffix = US""; - BOOL needsep = FALSE; - uschar buffer[256]; - - if (*sub == '+') sub++; - if (!arg) /* no-argument version */ - { - if (!(t = tree_search(addresslist_anchor, sub)) && - !(t = tree_search(domainlist_anchor, sub)) && - !(t = tree_search(hostlist_anchor, sub))) - t = tree_search(localpartlist_anchor, sub); - } - else switch(*arg) /* specific list-type version */ - { - case 'a': t = tree_search(addresslist_anchor, sub); suffix = US"_a"; break; - case 'd': t = tree_search(domainlist_anchor, sub); suffix = US"_d"; break; - case 'h': t = tree_search(hostlist_anchor, sub); suffix = US"_h"; break; - case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break; - default: - expand_string_message = US"bad suffix on \"list\" operator"; - goto EXPAND_FAILED; - } - - if(!t) - { - expand_string_message = string_sprintf("\"%s\" is not a %snamed list", - sub, !arg?"" - : *arg=='a'?"address " - : *arg=='d'?"domain " - : *arg=='h'?"host " - : *arg=='l'?"localpart " - : 0); + expand_string_message = NULL; + yield = expand_listnamed(yield, sub, arg); + if (expand_string_message) goto EXPAND_FAILED; - } - - list = ((namedlist_block *)(t->data.ptr))->string; - - while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))) - { - uschar * buf = US" : "; - if (needsep) - yield = string_catn(yield, buf, 3); - else - needsep = TRUE; - - if (*item == '+') /* list item is itself a named list */ - { - uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item); - item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE, &resetok); - } - else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */ - { - char * cp; - char tok[3]; - tok[0] = sep; tok[1] = ':'; tok[2] = 0; - while ((cp= strpbrk(CCS item, tok))) - { - yield = string_catn(yield, item, cp - CS item); - if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */ - { - yield = string_catn(yield, US"::", 2); - item = US cp; - } - else /* sep in item; should already be doubled; emit once */ - { - yield = string_catn(yield, US tok, 1); - if (*cp == sep) cp++; - item = US cp; - } - } - } - yield = string_cat(yield, item); - } continue; - } /* quote a list-item for the given list-separator */ @@ -7335,11 +7346,11 @@ while (*s) int count; uschar *endptr; int binary[4]; - int mask, maskoffset; - int type = string_is_ip_address(sub, &maskoffset); + int type, mask, maskoffset; + BOOL normalised; uschar buffer[64]; - if (type == 0) + if ((type = string_is_ip_address(sub, &maskoffset)) == 0) { expand_string_message = string_sprintf("\"%s\" is not an IP address", sub); @@ -7355,13 +7366,18 @@ while (*s) mask = Ustrtol(sub + maskoffset + 1, &endptr, 10); - if (*endptr != 0 || mask < 0 || mask > ((type == 4)? 32 : 128)) + if (*endptr || mask < 0 || mask > (type == 4 ? 32 : 128)) { expand_string_message = string_sprintf("mask value too big in \"%s\"", sub); goto EXPAND_FAILED; } + /* If an optional 'n' was given, ipv6 gets normalised output: + colons rather than dots, and zero-compressed. */ + + normalised = arg && *arg == 'n'; + /* Convert the address to binary integer(s) and apply the mask */ sub[maskoffset] = 0; @@ -7370,8 +7386,14 @@ while (*s) /* Convert to masked textual format and add to output. */ - yield = string_catn(yield, buffer, - host_nmtoa(count, binary, mask, buffer, '.')); + if (type == 4 || !normalised) + yield = string_catn(yield, buffer, + host_nmtoa(count, binary, mask, buffer, '.')); + else + { + ipv6_nmtoa(binary, buffer); + yield = string_fmt_append(yield, "%s/%d", buffer, mask); + } continue; } @@ -7647,10 +7669,12 @@ while (*s) /* Manually track tainting, as we deal in individual chars below */ if (is_tainted(sub)) + { if (yield->s && yield->ptr) gstring_rebuffer(yield); else yield->s = store_get(yield->size = Ustrlen(sub), TRUE); + } /* Check the UTF-8, byte-by-byte */ @@ -8090,6 +8114,7 @@ while (*s) /*{*/ if (*s++ == '}') { + const uschar * value; int len; int newsize = 0; gstring * g = NULL; @@ -8116,7 +8141,7 @@ while (*s) yield = g; yield->size = newsize; yield->ptr = len; - yield->s = value; + yield->s = US value; /* known to be in new store i.e. a copy, so deconst safe */ } else yield = string_catn(yield, value, len); @@ -8211,6 +8236,7 @@ that is a bad idea, because expand_string_message is in dynamic store. */ EXPAND_FAILED: if (left) *left = s; DEBUG(D_expand) + { DEBUG(D_noutf8) { debug_printf_indent("|failed to expand: %s\n", string); @@ -8230,6 +8256,7 @@ DEBUG(D_expand) if (f.expand_string_forcedfail) debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n"); } + } if (resetok_p && !resetok) *resetok_p = FALSE; expand_level--; return NULL; @@ -8537,6 +8564,7 @@ typedef struct { const uschar *var_data; } err_ctx; +/* Called via tree_walk, which allows nonconst name/data. Our usage is const. */ static void assert_variable_notin(uschar * var_name, uschar * var_data, void * ctx) { @@ -8558,13 +8586,14 @@ err_ctx e = { .region_start = ptr, .region_end = US ptr + len, tree_walk(acl_var_c, assert_variable_notin, &e); tree_walk(acl_var_m, assert_variable_notin, &e); -/* check auth variables */ +/* check auth variables. +assert_variable_notin() treats as const, so deconst is safe. */ for (int i = 0; i < AUTH_VARS; i++) if (auth_vars[i]) - assert_variable_notin(US"auth", auth_vars[i], &e); + assert_variable_notin(US"auth", US auth_vars[i], &e); -/* check regex variables */ +/* check regex variables. assert_variable_notin() treats as const. */ for (int i = 0; i < REGEX_VARS; i++) if (regex_vars[i]) - assert_variable_notin(US"regex", regex_vars[i], &e); + assert_variable_notin(US"regex", US regex_vars[i], &e); /* check known-name variables */ for (var_entry * v = var_table; v < var_table + var_table_size; v++) @@ -8595,11 +8624,11 @@ if (e.var_name) BOOL -regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup) +regex_match_and_setup(const pcre2_code *re, uschar *subject, int options, int setup) { -int ovector[3*(EXPAND_MAXN+1)]; +int ovec[3*(EXPAND_MAXN+1)]; int n = pcre_exec(re, NULL, subject, Ustrlen(subject), 0, PCRE_EOPT|options, - ovector, nelem(ovector)); + ovec, nelem(ovec)); BOOL yield = n >= 0; if (n == 0) n = EXPAND_MAXN + 1; if (yield) @@ -8607,8 +8636,8 @@ if (yield) expand_nmax = setup < 0 ? 0 : setup + 1; for (int nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2) { - expand_nstring[expand_nmax] = subject + ovector[nn]; - expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn]; + expand_nstring[expand_nmax] = subject + ovec[nn]; + expand_nlength[expand_nmax++] = ovec[nn+1] - ovec[nn]; } expand_nmax--; } @@ -8624,6 +8653,7 @@ debug_selector = D_v; debug_file = stderr; debug_fd = fileno(debug_file); big_buffer = malloc(big_buffer_size); +store_init(); for (int i = 1; i < argc; i++) {