X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/fa7b17bdbc8c055c475a50791627cd75d257f4f3..7ef88aa0c4c0608ee54ed2ff90b4b34c518d9bb5:/src/src/expand.c diff --git a/src/src/expand.c b/src/src/expand.c index 74267ab0c..e30756123 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -129,6 +129,9 @@ static uschar *item_table[] = { US"run", US"sg", US"sort", +#ifdef EXPERIMENTAL_SRS_NATIVE + US"srs_encode", +#endif US"substr", US"tr" }; @@ -160,6 +163,9 @@ enum { EITEM_RUN, EITEM_SG, EITEM_SORT, +#ifdef EXPERIMENTAL_SRS_NATIVE + EITEM_SRS_ENCODE, +#endif EITEM_SUBSTR, EITEM_TR }; @@ -323,6 +329,9 @@ static uschar *cond_table[] = { US"gei", US"gt", US"gti", +#ifdef EXPERIMENTAL_SRS_NATIVE + US"inbound_srs", +#endif US"inlist", US"inlisti", US"isip", @@ -373,6 +382,9 @@ enum { ECOND_STR_GEI, ECOND_STR_GT, ECOND_STR_GTI, +#ifdef EXPERIMENTAL_SRS_NATIVE + ECOND_INBOUND_SRS, +#endif ECOND_INLIST, ECOND_INLISTI, ECOND_ISIP, @@ -451,6 +463,7 @@ typedef struct { } alblock; static uschar * fn_recipients(void); +typedef uschar * stringptr_fn_t(void); /* This table must be kept in alphabetical order. */ @@ -472,7 +485,7 @@ static var_entry var_table[] = { { "address_file", vtype_stringptr, &address_file }, { "address_pipe", vtype_stringptr, &address_pipe }, #ifdef EXPERIMENTAL_ARC - { "arc_domains", vtype_string_func, &fn_arc_domains }, + { "arc_domains", vtype_string_func, (void *) &fn_arc_domains }, { "arc_oldest_pass", vtype_int, &arc_oldest_pass }, { "arc_state", vtype_stringptr, &arc_state }, { "arc_state_reason", vtype_stringptr, &arc_state_reason }, @@ -529,7 +542,7 @@ static var_entry var_table[] = { { "dkim_verify_reason", vtype_stringptr, &dkim_verify_reason }, { "dkim_verify_status", vtype_stringptr, &dkim_verify_status }, #endif -#ifdef EXPERIMENTAL_DMARC +#ifdef SUPPORT_DMARC { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy }, { "dmarc_status", vtype_stringptr, &dmarc_status }, { "dmarc_status_text", vtype_stringptr, &dmarc_status_text }, @@ -553,7 +566,7 @@ static var_entry var_table[] = { { "exim_path", vtype_stringptr, &exim_path }, { "exim_uid", vtype_uid, &exim_uid }, { "exim_version", vtype_stringptr, &version_string }, - { "headers_added", vtype_string_func, &fn_hdrs_added }, + { "headers_added", vtype_string_func, (void *) &fn_hdrs_added }, { "home", vtype_stringptr, &deliver_home }, { "host", vtype_stringptr, &deliver_host }, { "host_address", vtype_stringptr, &deliver_host_address }, @@ -664,7 +677,7 @@ static var_entry var_table[] = { { "received_time", vtype_int, &received_time.tv_sec }, { "recipient_data", vtype_stringptr, &recipient_data }, { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure }, - { "recipients", vtype_string_func, &fn_recipients }, + { "recipients", vtype_string_func, (void *) &fn_recipients }, { "recipients_count", vtype_int, &recipients_count }, #ifdef WITH_CONTENT_SCAN { "regex_match_string", vtype_stringptr, ®ex_match_string }, @@ -699,7 +712,7 @@ static var_entry var_table[] = { { "smtp_active_hostname", vtype_stringptr, &smtp_active_hostname }, { "smtp_command", vtype_stringptr, &smtp_cmd_buffer }, { "smtp_command_argument", vtype_stringptr, &smtp_cmd_argument }, - { "smtp_command_history", vtype_string_func, &smtp_cmd_hist }, + { "smtp_command_history", vtype_string_func, (void *) &smtp_cmd_hist }, { "smtp_count_at_connection_start", vtype_int, &smtp_accept_count }, { "smtp_notquit_reason", vtype_stringptr, &smtp_notquit_reason }, { "sn0", vtype_filter_int, &filter_sn[0] }, @@ -735,7 +748,11 @@ static var_entry var_table[] = { { "srs_db_key", vtype_stringptr, &srs_db_key }, { "srs_orig_recipient", vtype_stringptr, &srs_orig_recipient }, { "srs_orig_sender", vtype_stringptr, &srs_orig_sender }, +#endif +#if defined(EXPERIMENTAL_SRS) || defined(EXPERIMENTAL_SRS_NATIVE) { "srs_recipient", vtype_stringptr, &srs_recipient }, +#endif +#ifdef EXPERIMENTAL_SRS { "srs_status", vtype_stringptr, &srs_status }, #endif { "thisaddress", vtype_stringptr, &filter_thisaddress }, @@ -1856,22 +1873,17 @@ switch (vp->type) return sender_host_name ? sender_host_name : US""; case vtype_localpart: /* Get local part from address */ - s = *((uschar **)(val)); - if (s == NULL) return US""; - domain = Ustrrchr(s, '@'); - if (domain == NULL) return s; + if (!(s = *((uschar **)(val)))) return US""; + if (!(domain = Ustrrchr(s, '@'))) return s; if (domain - s > sizeof(var_buffer) - 1) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT " in string expansion", sizeof(var_buffer)); - Ustrncpy(var_buffer, s, domain - s); - var_buffer[domain - s] = 0; - return var_buffer; + return string_copyn(s, domain - s); case vtype_domain: /* Get domain from address */ - s = *((uschar **)(val)); - if (s == NULL) return US""; + if (!(s = *((uschar **)(val)))) return US""; domain = Ustrrchr(s, '@'); - return (domain == NULL)? US"" : domain + 1; + return domain ? domain + 1 : US""; case vtype_msgheaders: return find_header(NULL, newsize, exists_only ? FH_EXISTS_ONLY : 0, NULL); @@ -1966,7 +1978,7 @@ switch (vp->type) case vtype_string_func: { - uschar * (*fn)() = val; + stringptr_fn_t * fn = (stringptr_fn_t *) val; return fn(); } @@ -2246,6 +2258,177 @@ return US item; +/************************************************/ +/* Return offset in ops table, or -1 if not found. +Repoint to just after the operator in the string. + +Argument: + ss string representation of operator + opname split-out operator name +*/ + +static int +identify_operator(const uschar ** ss, uschar ** opname) +{ +const uschar * s = *ss; +uschar name[256]; + +/* Numeric comparisons are symbolic */ + +if (*s == '=' || *s == '>' || *s == '<') + { + int p = 0; + name[p++] = *s++; + if (*s == '=') + { + name[p++] = '='; + s++; + } + name[p] = 0; + } + +/* All other conditions are named */ + +else + s = read_name(name, sizeof(name), s, US"_"); +*ss = s; + +/* If we haven't read a name, it means some non-alpha character is first. */ + +if (!name[0]) + { + expand_string_message = string_sprintf("condition name expected, " + "but found \"%.16s\"", s); + return -1; + } +if (opname) + *opname = string_copy(name); + +return chop_match(name, cond_table, nelem(cond_table)); +} + + +/************************************************* +* Handle MD5 or SHA-1 computation for HMAC * +*************************************************/ + +/* These are some wrapping functions that enable the HMAC code to be a bit +cleaner. A good compiler will spot the tail recursion. + +Arguments: + type HMAC_MD5 or HMAC_SHA1 + remaining are as for the cryptographic hash functions + +Returns: nothing +*/ + +static void +chash_start(int type, void * base) +{ +if (type == HMAC_MD5) + md5_start((md5 *)base); +else + sha1_start((hctx *)base); +} + +static void +chash_mid(int type, void * base, const uschar * string) +{ +if (type == HMAC_MD5) + md5_mid((md5 *)base, string); +else + sha1_mid((hctx *)base, string); +} + +static void +chash_end(int type, void * base, const uschar * string, int length, + uschar * digest) +{ +if (type == HMAC_MD5) + md5_end((md5 *)base, string, length, digest); +else + sha1_end((hctx *)base, string, length, digest); +} + + + + +/* Do an hmac_md5. The result is _not_ nul-terminated, and is sized as +the smaller of a full hmac_md5 result (16 bytes) or the supplied output buffer. + +Arguments: + key encoding key, nul-terminated + src data to be hashed, nul-terminated + buf output buffer + len size of output buffer +*/ + +static void +hmac_md5(const uschar * key, const uschar * src, uschar * buf, unsigned len) +{ +md5 md5_base; +const uschar * keyptr; +uschar * p; +unsigned int keylen; + +#define MD5_HASHLEN 16 +#define MD5_HASHBLOCKLEN 64 + +uschar keyhash[MD5_HASHLEN]; +uschar innerhash[MD5_HASHLEN]; +uschar finalhash[MD5_HASHLEN]; +uschar innerkey[MD5_HASHBLOCKLEN]; +uschar outerkey[MD5_HASHBLOCKLEN]; + +keyptr = key; +keylen = Ustrlen(keyptr); + +/* If the key is longer than the hash block length, then hash the key +first */ + +if (keylen > MD5_HASHBLOCKLEN) + { + chash_start(HMAC_MD5, &md5_base); + chash_end(HMAC_MD5, &md5_base, keyptr, keylen, keyhash); + keyptr = keyhash; + keylen = MD5_HASHLEN; + } + +/* Now make the inner and outer key values */ + +memset(innerkey, 0x36, MD5_HASHBLOCKLEN); +memset(outerkey, 0x5c, MD5_HASHBLOCKLEN); + +for (int i = 0; i < keylen; i++) + { + innerkey[i] ^= keyptr[i]; + outerkey[i] ^= keyptr[i]; + } + +/* Now do the hashes */ + +chash_start(HMAC_MD5, &md5_base); +chash_mid(HMAC_MD5, &md5_base, innerkey); +chash_end(HMAC_MD5, &md5_base, src, Ustrlen(src), innerhash); + +chash_start(HMAC_MD5, &md5_base); +chash_mid(HMAC_MD5, &md5_base, outerkey); +chash_end(HMAC_MD5, &md5_base, innerhash, MD5_HASHLEN, finalhash); + +/* Encode the final hash as a hex string, limited by output buffer size */ + +p = buf; +for (int i = 0, j = len; i < MD5_HASHLEN; i++) + { + if (j-- <= 0) break; + *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; + if (j-- <= 0) break; + *p++ = hex_digits[finalhash[i] & 0x0f]; + } +return; +} + + /************************************************* * Read and evaluate a condition * *************************************************/ @@ -2276,6 +2459,7 @@ BOOL is_forany, is_json, is_jsons; int rc, cond_type, roffset; int_eximarith_t num[2]; struct stat statbuf; +uschar * opname; uschar name[256]; const uschar *sub[10]; @@ -2288,37 +2472,7 @@ for (;;) if (*s == '!') { testfor = !testfor; s++; } else break; } -/* Numeric comparisons are symbolic */ - -if (*s == '=' || *s == '>' || *s == '<') - { - int p = 0; - name[p++] = *s++; - if (*s == '=') - { - name[p++] = '='; - s++; - } - name[p] = 0; - } - -/* All other conditions are named */ - -else s = read_name(name, 256, s, US"_"); - -/* If we haven't read a name, it means some non-alpha character is first. */ - -if (name[0] == 0) - { - expand_string_message = string_sprintf("condition name expected, " - "but found \"%.16s\"", s); - return NULL; - } - -/* Find which condition we are dealing with, and switch on it */ - -cond_type = chop_match(name, cond_table, nelem(cond_table)); -switch(cond_type) +switch(cond_type = identify_operator(&s, &opname)) { /* def: tests for a non-empty variable, or for the existence of a header. If yield == NULL we are in a skipping state, and don't care about the answer. */ @@ -2333,7 +2487,7 @@ switch(cond_type) return NULL; } - s = read_name(name, 256, s+1, US"_"); + s = read_name(name, sizeof(name), s+1, US"_"); /* Test for a header's existence. If the name contains a closing brace character, this may be a user error where the terminating colon has been @@ -2345,7 +2499,7 @@ switch(cond_type) && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0) ) { - s = read_header_name(name, 256, s); + s = read_header_name(name, sizeof(name), s); /* {-for-text-editors */ if (Ustrchr(name, '}') != NULL) malformed_header = TRUE; if (yield) *yield = @@ -2359,9 +2513,9 @@ switch(cond_type) { if (!(t = find_variable(name, TRUE, yield == NULL, NULL))) { - expand_string_message = (name[0] == 0)? - string_sprintf("variable name omitted after \"def:\"") : - string_sprintf("unknown variable \"%s\" after \"def:\"", name); + expand_string_message = name[0] + ? string_sprintf("unknown variable \"%s\" after \"def:\"", name) + : US"variable name omitted after \"def:\""; check_variable_error_message(name); return NULL; } @@ -2517,8 +2671,9 @@ switch(cond_type) if (yield != NULL) { + int rc; *resetok = FALSE; /* eval_acl() might allocate; do not reclaim */ - switch(eval_acl(sub, nelem(sub), &user_msg)) + switch(rc = eval_acl(sub, nelem(sub), &user_msg)) { case OK: cond = TRUE; @@ -2533,7 +2688,8 @@ switch(cond_type) f.expand_string_forcedfail = TRUE; /*FALLTHROUGH*/ default: - expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]); + expand_string_message = string_sprintf("%s from acl \"%s\"", + rc_names[rc], sub[0]); return NULL; } } @@ -2637,7 +2793,7 @@ switch(cond_type) { if (i == 0) goto COND_FAILED_CURLY_START; expand_string_message = string_sprintf("missing 2nd string in {} " - "after \"%s\"", name); + "after \"%s\"", opname); return NULL; } if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL, @@ -2652,7 +2808,7 @@ switch(cond_type) conditions that compare numbers do not start with a letter. This just saves checking for them individually. */ - if (!isalpha(name[0]) && yield != NULL) + if (!isalpha(opname[0]) && yield != NULL) if (sub[i][0] == 0) { num[i] = 0; @@ -2964,7 +3120,7 @@ switch(cond_type) uschar *save_iterate_item = iterate_item; int (*compare)(const uschar *, const uschar *); - DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", name, sub[0]); + DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", opname, sub[0]); tempcond = FALSE; compare = cond_type == ECOND_INLISTI @@ -3006,14 +3162,14 @@ switch(cond_type) if (*s != '{') /* }-for-text-editors */ { expand_string_message = string_sprintf("each subcondition " - "inside an \"%s{...}\" condition must be in its own {}", name); + "inside an \"%s{...}\" condition must be in its own {}", opname); return NULL; } if (!(s = eval_condition(s+1, resetok, subcondptr))) { expand_string_message = string_sprintf("%s inside \"%s{...}\" condition", - expand_string_message, name); + expand_string_message, opname); return NULL; } while (isspace(*s)) s++; @@ -3023,7 +3179,7 @@ switch(cond_type) { /* {-for-text-editors */ expand_string_message = string_sprintf("missing } at end of condition " - "inside \"%s\" group", name); + "inside \"%s\" group", opname); return NULL; } @@ -3061,7 +3217,7 @@ switch(cond_type) int sep = 0; uschar *save_iterate_item = iterate_item; - DEBUG(D_expand) debug_printf_indent("condition: %s\n", name); + DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname); while (isspace(*s)) s++; if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ @@ -3082,7 +3238,7 @@ switch(cond_type) if (!(s = eval_condition(sub[1], resetok, NULL))) { expand_string_message = string_sprintf("%s inside \"%s\" condition", - expand_string_message, name); + expand_string_message, opname); return NULL; } while (isspace(*s)) s++; @@ -3092,7 +3248,7 @@ switch(cond_type) { /* {-for-text-editors */ expand_string_message = string_sprintf("missing } at end of condition " - "inside \"%s\"", name); + "inside \"%s\"", opname); return NULL; } @@ -3112,15 +3268,15 @@ switch(cond_type) return NULL; } - DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, iterate_item); + DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", opname, iterate_item); if (!eval_condition(sub[1], resetok, &tempcond)) { expand_string_message = string_sprintf("%s inside \"%s\" condition", - expand_string_message, name); + expand_string_message, opname); iterate_item = save_iterate_item; return NULL; } - DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", name, + DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", opname, tempcond? "true":"false"); if (yield) *yield = (tempcond == testfor); @@ -3210,22 +3366,117 @@ switch(cond_type) return s; } +#ifdef EXPERIMENTAL_SRS_NATIVE + case ECOND_INBOUND_SRS: + /* ${if inbound_srs {local_part}{secret} {yes}{no}} */ + { + uschar * sub[2]; + const pcre * re; + int ovec[3*(4+1)]; + int n; + uschar cksum[4]; + BOOL boolvalue = FALSE; + + switch(read_subs(sub, 2, 2, CUSS &s, yield == NULL, FALSE, US"inbound_srs", resetok)) + { + case 1: expand_string_message = US"too few arguments or bracketing " + "error for inbound_srs"; + case 2: + case 3: return NULL; + } + + /* Match the given local_part against the SRS-encoded pattern */ + + 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) + { + DEBUG(D_expand) debug_printf("no match for SRS'd local-part pattern\n"); + goto srs_result; + } + + /* Side-effect: record the decoded recipient */ + + srs_recipient = string_sprintf("%.*S@%.*S", /* lowercased */ + ovec[9]-ovec[8], sub[0] + ovec[8], /* substring 4 */ + ovec[7]-ovec[6], sub[0] + ovec[6]); /* substring 3 */ + + /* If a zero-length secret was given, we're done. Otherwise carry on + and validate the given SRS local_part againt our secret. */ + + if (!*sub[1]) + { + boolvalue = TRUE; + goto srs_result; + } + + /* check the timestamp */ + { + struct timeval now; + uschar * ss = sub[0] + ovec[4]; /* substring 2, the timestamp */ + long d; + + gettimeofday(&now, NULL); + now.tv_sec /= 86400; /* days since epoch */ + + /* Decode substring 2 from base32 to a number */ + + for (d = 0, n = ovec[5]-ovec[4]; n; n--) + { + uschar * t = Ustrchr(base32_chars, *ss++); + d = d * 32 + (t - base32_chars); + } + + if (((now.tv_sec - d) & 0x3ff) > 10) /* days since SRS generated */ + { + DEBUG(D_expand) debug_printf("SRS too old\n"); + goto srs_result; + } + } + + /* check length of substring 1, the offered checksum */ + + if (ovec[3]-ovec[2] != 4) + { + DEBUG(D_expand) debug_printf("SRS checksum wrong size\n"); + goto srs_result; + } + + /* Hash the address with our secret, and compare that computed checksum + with the one extracted from the arg */ + + hmac_md5(sub[1], srs_recipient, cksum, sizeof(cksum)); + if (Ustrncmp(cksum, sub[0] + ovec[2], 4) != 0) + { + DEBUG(D_expand) debug_printf("SRS checksum mismatch\n"); + goto srs_result; + } + boolvalue = TRUE; + +srs_result: + if (yield) *yield = (boolvalue == testfor); + return s; + } +#endif /*EXPERIMENTAL_SRS_NATIVE*/ + /* Unknown condition */ default: - expand_string_message = string_sprintf("unknown condition \"%s\"", name); - return NULL; + if (!expand_string_message || !*expand_string_message) + expand_string_message = string_sprintf("unknown condition \"%s\"", opname); + return NULL; } /* End switch on condition type */ /* Missing braces at start and end of data */ COND_FAILED_CURLY_START: -expand_string_message = string_sprintf("missing { after \"%s\"", name); +expand_string_message = string_sprintf("missing { after \"%s\"", opname); return NULL; COND_FAILED_CURLY_END: expand_string_message = string_sprintf("missing } at end of \"%s\" condition", - name); + opname); return NULL; /* A condition requires code that is not compiled */ @@ -3235,7 +3486,7 @@ return NULL; !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET) COND_FAILED_NOT_COMPILED: expand_string_message = string_sprintf("support for \"%s\" not compiled", - name); + opname); return NULL; #endif } @@ -3481,51 +3732,6 @@ FAILED: -/************************************************* -* Handle MD5 or SHA-1 computation for HMAC * -*************************************************/ - -/* These are some wrapping functions that enable the HMAC code to be a bit -cleaner. A good compiler will spot the tail recursion. - -Arguments: - type HMAC_MD5 or HMAC_SHA1 - remaining are as for the cryptographic hash functions - -Returns: nothing -*/ - -static void -chash_start(int type, void *base) -{ -if (type == HMAC_MD5) - md5_start((md5 *)base); -else - sha1_start((hctx *)base); -} - -static void -chash_mid(int type, void *base, uschar *string) -{ -if (type == HMAC_MD5) - md5_mid((md5 *)base, string); -else - sha1_mid((hctx *)base, string); -} - -static void -chash_end(int type, void *base, uschar *string, int length, uschar *digest) -{ -if (type == HMAC_MD5) - md5_end((md5 *)base, string, length, digest); -else - sha1_end((hctx *)base, string, length, digest); -} - - - - - /******************************************************** * prvs: Get last three digits of days since Jan 1, 1970 * ********************************************************/ @@ -3546,7 +3752,7 @@ Returns: pointer to string containing the last three static uschar * prvs_daystamp(int day_offset) { -uschar *days = store_get(32); /* Need at least 24 for cases */ +uschar *days = store_get(32, FALSE); /* Need at least 24 for cases */ (void)string_format(days, 32, TIME_T_FMT, /* where TIME_T_FMT is %lld */ (time(NULL) + day_offset*86400)/86400); return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : US"100"; @@ -3583,7 +3789,7 @@ uschar innerhash[20]; uschar finalhash[20]; uschar innerkey[64]; uschar outerkey[64]; -uschar *finalhash_hex = store_get(40); +uschar *finalhash_hex; if (key_num == NULL) key_num = US"0"; @@ -3616,7 +3822,9 @@ chash_start(HMAC_SHA1, &h); chash_mid(HMAC_SHA1, &h, outerkey); chash_end(HMAC_SHA1, &h, innerhash, 20, finalhash); -p = finalhash_hex; +/* Hashing is deemed sufficient to de-taint any input data */ + +p = finalhash_hex = store_get(40, FALSE); for (int i = 0; i < 3; i++) { *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; @@ -3959,6 +4167,56 @@ return x; +/************************************************/ +/* Comparison operation for sort expansion. We need to avoid +re-expanding the fields being compared, so need a custom routine. + +Arguments: + cond_type Comparison operator code + leftarg, rightarg Arguments for comparison + +Return true iff (leftarg compare rightarg) +*/ + +static BOOL +sortsbefore(int cond_type, BOOL alpha_cond, + const uschar * leftarg, const uschar * rightarg) +{ +int_eximarith_t l_num, r_num; + +if (!alpha_cond) + { + l_num = expanded_string_integer(leftarg, FALSE); + if (expand_string_message) return FALSE; + r_num = expanded_string_integer(rightarg, FALSE); + if (expand_string_message) return FALSE; + + switch (cond_type) + { + case ECOND_NUM_G: return l_num > r_num; + case ECOND_NUM_GE: return l_num >= r_num; + case ECOND_NUM_L: return l_num < r_num; + case ECOND_NUM_LE: return l_num <= r_num; + default: break; + } + } +else + switch (cond_type) + { + case ECOND_STR_LT: return Ustrcmp (leftarg, rightarg) < 0; + case ECOND_STR_LTI: return strcmpic(leftarg, rightarg) < 0; + case ECOND_STR_LE: return Ustrcmp (leftarg, rightarg) <= 0; + case ECOND_STR_LEI: return strcmpic(leftarg, rightarg) <= 0; + case ECOND_STR_GT: return Ustrcmp (leftarg, rightarg) > 0; + case ECOND_STR_GTI: return strcmpic(leftarg, rightarg) > 0; + case ECOND_STR_GE: return Ustrcmp (leftarg, rightarg) >= 0; + case ECOND_STR_GEI: return strcmpic(leftarg, rightarg) >= 0; + default: break; + } +return FALSE; /* should not happen */ +} + + /************************************************* * Expand string * *************************************************/ @@ -4026,6 +4284,7 @@ static uschar * expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left, BOOL skipping, BOOL honour_dollar, BOOL *resetok_p) { +rmark reset_point = store_mark(); gstring * yield = string_get(Ustrlen(string) + 64); int item_type; const uschar *s = string; @@ -4048,6 +4307,14 @@ DEBUG(D_expand) f.expand_string_forcedfail = FALSE; expand_string_message = US""; +if (is_tainted(string)) + { + expand_string_message = + string_sprintf("attempt to expand tainted string '%s'", s); + log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message); + goto EXPAND_FAILED; + } + while (*s != 0) { uschar *value; @@ -4119,12 +4386,13 @@ while (*s != 0) buffer. */ if (!yield) - g = store_get(sizeof(gstring)); + g = store_get(sizeof(gstring), FALSE); else if (yield->ptr == 0) { - if (resetok) store_reset(yield); + if (resetok) reset_point = store_reset(reset_point); yield = NULL; - g = store_get(sizeof(gstring)); /* alloc _before_ calling find_variable() */ + reset_point = store_mark(); + g = store_get(sizeof(gstring), FALSE); /* alloc _before_ calling find_variable() */ } /* Header */ @@ -4250,6 +4518,7 @@ while (*s != 0) { uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */ uschar *user_msg; + int rc; switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, US"acl", &resetok)) @@ -4261,7 +4530,7 @@ while (*s != 0) if (skipping) continue; resetok = FALSE; - switch(eval_acl(sub, nelem(sub), &user_msg)) + switch(rc = eval_acl(sub, nelem(sub), &user_msg)) { case OK: case FAIL: @@ -4275,7 +4544,8 @@ while (*s != 0) f.expand_string_forcedfail = TRUE; /*FALLTHROUGH*/ default: - expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]); + expand_string_message = string_sprintf("%s from acl \"%s\"", + rc_names[rc], sub[0]); goto EXPAND_FAILED; } } @@ -4306,7 +4576,7 @@ while (*s != 0) #ifndef DISABLE_DKIM yield = authres_dkim(yield); #endif -#ifdef EXPERIMENTAL_DMARC +#ifdef SUPPORT_DMARC yield = authres_dmarc(yield); #endif #ifdef EXPERIMENTAL_ARC @@ -5116,7 +5386,7 @@ while (*s != 0) #endif /* Allow sequencing of test actions */ - if (f.running_in_test_harness) millisleep(100); + testharness_pause_ms(100); /* Write the request string, if not empty or already done */ @@ -5144,7 +5414,7 @@ while (*s != 0) if (!do_tls && do_shutdown) shutdown(cctx.sock, SHUT_WR); #endif - if (f.running_in_test_harness) millisleep(100); + testharness_pause_ms(100); /* Now we need to read from the socket, under a timeout. The function that reads a file can be used. */ @@ -5304,7 +5574,7 @@ while (*s != 0) { if (sigalrm_seen || runrc == -256) { - expand_string_message = string_sprintf("command timed out"); + expand_string_message = US"command timed out"; killpg(pid, SIGKILL); /* Kill the whole process group */ } @@ -5846,8 +6116,8 @@ while (*s != 0) while (isspace(*s)) s++; if (*s != ':') { - expand_string_message = string_sprintf( - "missing object value-separator for extract json"); + expand_string_message = + US"missing object value-separator for extract json"; goto EXPAND_FAILED_CURLY; } s++; @@ -6280,9 +6550,10 @@ while (*s != 0) case EITEM_SORT: { + int cond_type; int sep = 0; const uschar *srclist, *cmp, *xtract; - uschar *srcitem; + uschar * opname, * srcitem; const uschar *dstlist = NULL, *dstkeylist = NULL; uschar * tmp; uschar *save_iterate_item = iterate_item; @@ -6317,6 +6588,25 @@ while (*s != 0) goto EXPAND_FAILED_CURLY; } + if ((cond_type = identify_operator(&cmp, &opname)) == -1) + { + if (!expand_string_message) + expand_string_message = string_sprintf("unknown condition \"%s\"", s); + goto EXPAND_FAILED; + } + switch(cond_type) + { + case ECOND_NUM_L: case ECOND_NUM_LE: + case ECOND_NUM_G: case ECOND_NUM_GE: + case ECOND_STR_GE: case ECOND_STR_GEI: case ECOND_STR_GT: case ECOND_STR_GTI: + case ECOND_STR_LE: case ECOND_STR_LEI: case ECOND_STR_LT: case ECOND_STR_LTI: + break; + + default: + expand_string_message = US"comparator not handled for sort"; + goto EXPAND_FAILED; + } + while (isspace(*s)) s++; if (*s++ != '{') { @@ -6325,8 +6615,8 @@ while (*s != 0) } xtract = s; - tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok); - if (!tmp) goto EXPAND_FAILED; + if (!(tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok))) + goto EXPAND_FAILED; xtract = string_copyn(xtract, s - xtract); if (*s++ != '}') @@ -6344,11 +6634,10 @@ while (*s != 0) if (skipping) continue; while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0))) - { - uschar * dstitem; + { + uschar * srcfield, * dstitem; gstring * newlist = NULL; gstring * newkeylist = NULL; - uschar * srcfield; DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem); @@ -6369,25 +6658,15 @@ while (*s != 0) while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0))) { uschar * dstfield; - uschar * expr; - BOOL before; /* field for comparison */ if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0))) goto sort_mismatch; - /* build and run condition string */ - expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield); - - DEBUG(D_expand) debug_printf_indent("%s: cond = \"%s\"\n", name, expr); - if (!eval_condition(expr, &resetok, &before)) - { - expand_string_message = string_sprintf("comparison in sort: %s", - expr); - goto EXPAND_FAILED; - } + /* String-comparator names start with a letter; numeric names do not */ - if (before) + if (sortsbefore(cond_type, isalpha(opname[0]), + srcfield, dstfield)) { /* New-item sorts before this dst-item. Append new-item, then dst-item, then remainder of dst list. */ @@ -6399,6 +6678,7 @@ while (*s != 0) newlist = string_append_listele(newlist, sep, dstitem); newkeylist = string_append_listele(newkeylist, sep, dstfield); +/*XXX why field-at-a-time copy? Why not just dup the rest of the list? */ while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0))) { if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0))) @@ -6494,7 +6774,7 @@ while (*s != 0) log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message); goto EXPAND_FAILED; } - t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0])); + t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), is_tainted(argv[0])); Ustrcpy(t->name, argv[0]); t->data.ptr = handle; (void)tree_insertnode(&dlobj_anchor, t); @@ -6574,6 +6854,62 @@ while (*s != 0) } continue; } + +#ifdef EXPERIMENTAL_SRS_NATIVE + case EITEM_SRS_ENCODE: + /* ${srs_encode {secret} {return_path} {orig_domain}} */ + { + uschar * sub[3]; + uschar cksum[4]; + + switch (read_subs(sub, 3, 3, CUSS &s, skipping, TRUE, name, &resetok)) + { + case 1: goto EXPAND_FAILED_CURLY; + case 2: + case 3: goto EXPAND_FAILED; + } + + yield = string_catn(yield, US"SRS0=", 5); + + /* ${l_4:${hmac{md5}{SRS_SECRET}{${lc:$return_path}}}}= */ + hmac_md5(sub[0], string_copylc(sub[1]), cksum, sizeof(cksum)); + yield = string_catn(yield, cksum, sizeof(cksum)); + yield = string_catn(yield, US"=", 1); + + /* ${base32:${eval:$tod_epoch/86400&0x3ff}}= */ + { + struct timeval now; + unsigned long i; + gstring * g = NULL; + + gettimeofday(&now, NULL); + for (unsigned long i = (now.tv_sec / 86400) & 0x3ff; i; i >>= 5) + g = string_catn(g, &base32_chars[i & 0x1f], 1); + if (g) while (g->ptr > 0) + yield = string_catn(yield, &g->s[--g->ptr], 1); + } + yield = string_catn(yield, US"=", 1); + + /* ${domain:$return_path}=${local_part:$return_path} */ + { + int start, end, domain; + uschar * t = parse_extract_address(sub[1], &expand_string_message, + &start, &end, &domain, FALSE); + if (!t) + goto EXPAND_FAILED; + + if (domain > 0) yield = string_cat(yield, t + domain); + yield = string_catn(yield, US"=", 1); + yield = domain > 0 + ? string_catn(yield, t, domain - 1) : string_cat(yield, t); + } + + /* @$original_domain */ + yield = string_catn(yield, US"@", 1); + yield = string_cat(yield, sub[2]); + continue; + } +#endif /*EXPERIMENTAL_SRS_NATIVE*/ } /* EITEM_* switch */ /* Control reaches here if the name is not recognized as one of the more @@ -6966,7 +7302,7 @@ while (*s != 0) 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 = string_sprintf("bad suffix on \"list\" operator"); + expand_string_message = US"bad suffix on \"list\" operator"; goto EXPAND_FAILED; } @@ -7112,11 +7448,12 @@ while (*s != 0) uschar * t = parse_extract_address(sub, &error, &start, &end, &domain, FALSE); if (t) - yield = c == EOP_DOMAIN - ? string_cat(yield, t + domain) - : c == EOP_LOCAL_PART && domain > 0 - ? string_catn(yield, t, domain - 1 ) - : string_cat(yield, t); + if (c != EOP_DOMAIN) + yield = c == EOP_LOCAL_PART && domain > 0 + ? string_catn(yield, t, domain - 1) + : string_cat(yield, t); + else if (domain > 0) + yield = string_cat(yield, t + domain); continue; } @@ -7790,12 +8127,13 @@ while (*s != 0) gstring * g = NULL; if (!yield) - g = store_get(sizeof(gstring)); + g = store_get(sizeof(gstring), FALSE); else if (yield->ptr == 0) { - if (resetok) store_reset(yield); + if (resetok) reset_point = store_reset(reset_point); yield = NULL; - g = store_get(sizeof(gstring)); /* alloc _before_ calling find_variable() */ + reset_point = store_mark(); + g = store_get(sizeof(gstring), FALSE); /* alloc _before_ calling find_variable() */ } if (!(value = find_variable(name, FALSE, skipping, &newsize))) { @@ -7849,15 +8187,20 @@ if (left) *left = s; In many cases the final string will be the first one that was got and so there will be optimal store usage. */ -if (resetok) store_reset(yield->s + (yield->size = yield->ptr + 1)); +if (resetok) gstring_release_unused(yield); else if (resetok_p) *resetok_p = FALSE; DEBUG(D_expand) + { + BOOL tainted = is_tainted(yield->s); DEBUG(D_noutf8) { debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string); debug_printf_indent("%sresult: %s\n", skipping ? "|-----" : "\\_____", yield->s); + if (tainted) + debug_printf_indent("%s \\__(tainted)\n", + skipping ? "| " : " "); if (skipping) debug_printf_indent("\\___skipping: result is not used\n"); } @@ -7866,15 +8209,19 @@ DEBUG(D_expand) debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ "expanding: %.*s\n", (int)(s - string), string); - debug_printf_indent("%s" - UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ + debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "result: %s\n", skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT, yield->s); + if (tainted) + debug_printf_indent("%s(tainted)\n", + skipping + ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); if (skipping) debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "skipping: result is not used\n"); } + } expand_level--; return yield->s; @@ -8372,15 +8719,15 @@ if (opt_perl_startup != NULL) } #endif /* EXIM_PERL */ +/* Thie deliberately regards the input as untainted, so that it can be +expanded; only reasonable since this is a test for string-expansions. */ + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { - void *reset_point = store_get(0); + rmark reset_point = store_mark(); uschar *yield = expand_string(buffer); - if (yield != NULL) - { + if (yield) printf("%s\n", yield); - store_reset(reset_point); - } else { if (f.search_find_defer) printf("search_find deferred\n"); @@ -8388,6 +8735,7 @@ while (fgets(buffer, sizeof(buffer), stdin) != NULL) if (f.expand_string_forcedfail) printf("Forced failure\n"); printf("\n"); } + store_reset(reset_point); } search_tidyup();