X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Fexpand.c;h=22f7d9a66ff0c366d7cafc085caf85c3906913df;hp=47453dc6d183b665bab2cf4c8167a6cfd4aa31bd;hb=7be682ca5ebd9571a01b762195b11c34cd231830;hpb=2df588c9eec886db81da1ff981946d2c91ae789d diff --git a/src/src/expand.c b/src/src/expand.c index 47453dc6d..22f7d9a66 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/expand.c,v 1.103 2009/10/15 08:27:37 tom Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2007 */ +/* Copyright (c) University of Cambridge 1995 - 2009 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -15,7 +13,7 @@ /* Recursively called function */ -static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL); +static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL); #ifdef STAND_ALONE #ifndef SUPPORT_CRYPTEQ @@ -156,6 +154,7 @@ static uschar *op_table_underscore[] = { US"from_utf8", US"local_part", US"quote_local_part", + US"reverse_ip", US"time_eval", US"time_interval"}; @@ -163,6 +162,7 @@ enum { EOP_FROM_UTF8, EOP_LOCAL_PART, EOP_QUOTE_LOCAL_PART, + EOP_REVERSE_IP, EOP_TIME_EVAL, EOP_TIME_INTERVAL }; @@ -187,6 +187,7 @@ static uschar *op_table_main[] = { US"nh", US"nhash", US"quote", + US"randint", US"rfc2047", US"rfc2047d", US"rxquote", @@ -219,6 +220,7 @@ enum { EOP_NH, EOP_NHASH, EOP_QUOTE, + EOP_RANDINT, EOP_RFC2047, EOP_RFC2047D, EOP_RXQUOTE, @@ -243,6 +245,7 @@ static uschar *cond_table[] = { US">=", US"and", US"bool", + US"bool_lax", US"crypteq", US"def", US"eq", @@ -255,6 +258,8 @@ static uschar *cond_table[] = { US"gei", US"gt", US"gti", + US"inlist", + US"inlisti", US"isip", US"isip4", US"isip6", @@ -285,6 +290,7 @@ enum { ECOND_NUM_GE, ECOND_AND, ECOND_BOOL, + ECOND_BOOL_LAX, ECOND_CRYPTEQ, ECOND_DEF, ECOND_STR_EQ, @@ -297,6 +303,8 @@ enum { ECOND_STR_GEI, ECOND_STR_GT, ECOND_STR_GTI, + ECOND_INLIST, + ECOND_INLISTI, ECOND_ISIP, ECOND_ISIP4, ECOND_ISIP6, @@ -322,9 +330,9 @@ enum { /* Type for main variable table */ typedef struct { - char *name; - int type; - void *value; + const char *name; + int type; + void *value; } var_entry; /* Type for entries pointing to address/length pairs. Not currently @@ -383,6 +391,9 @@ static var_entry var_table[] = { { "authenticated_id", vtype_stringptr, &authenticated_id }, { "authenticated_sender",vtype_stringptr, &authenticated_sender }, { "authentication_failed",vtype_int, &authentication_failed }, +#ifdef WITH_CONTENT_SCAN + { "av_failed", vtype_int, &av_failed }, +#endif #ifdef EXPERIMENTAL_BRIGHTMAIL { "bmi_alt_location", vtype_stringptr, &bmi_alt_location }, { "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict }, @@ -600,9 +611,13 @@ static var_entry var_table[] = { { "srs_status", vtype_stringptr, &srs_status }, #endif { "thisaddress", vtype_stringptr, &filter_thisaddress }, + { "tls_bits", vtype_int, &tls_bits }, { "tls_certificate_verified", vtype_int, &tls_certificate_verified }, { "tls_cipher", vtype_stringptr, &tls_cipher }, { "tls_peerdn", vtype_stringptr, &tls_peerdn }, +#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) + { "tls_sni", vtype_stringptr, &tls_sni }, +#endif { "tod_bsdinbox", vtype_todbsdin, NULL }, { "tod_epoch", vtype_tode, NULL }, { "tod_full", vtype_todf, NULL }, @@ -626,9 +641,9 @@ static BOOL malformed_header; /* For textual hashes */ -static char *hashcodes = "abcdefghijklmnopqrtsuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789"; +static const char *hashcodes = "abcdefghijklmnopqrtsuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; enum { HMAC_MD5, HMAC_SHA1 }; @@ -730,6 +745,8 @@ or "false" value. Failure of the expansion yields FALSE; logged unless it was a forced fail or lookup defer. All store used by the function can be released on exit. +The actual false-value tests should be replicated for ECOND_BOOL_LAX. + Arguments: condition the condition string m1 text to be incorporated in panic error @@ -759,6 +776,75 @@ return rc; +/************************************************* +* Pseudo-random number generation * +*************************************************/ + +/* Pseudo-random number generation. The result is not "expected" to be +cryptographically strong but not so weak that someone will shoot themselves +in the foot using it as a nonce in some email header scheme or whatever +weirdness they'll twist this into. The result should ideally handle fork(). + +However, if we're stuck unable to provide this, then we'll fall back to +appallingly bad randomness. + +If SUPPORT_TLS is defined and OpenSSL is used, then this will not be used. +The GNUTLS randomness functions found do not seem amenable to extracting +random numbers outside of a TLS context. Any volunteers? + +Arguments: + max range maximum +Returns a random number in range [0, max-1] +*/ + +#if !defined(SUPPORT_TLS) || defined(USE_GNUTLS) +int +pseudo_random_number(int max) +{ + static pid_t pid = 0; + pid_t p2; +#if defined(HAVE_SRANDOM) && !defined(HAVE_SRANDOMDEV) + struct timeval tv; +#endif + + p2 = getpid(); + if (p2 != pid) + { + if (pid != 0) + { + +#ifdef HAVE_ARC4RANDOM + /* cryptographically strong randomness, common on *BSD platforms, not + so much elsewhere. Alas. */ + arc4random_stir(); +#elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV) +#ifdef HAVE_SRANDOMDEV + /* uses random(4) for seeding */ + srandomdev(); +#else + gettimeofday(&tv, NULL); + srandom(tv.tv_sec | tv.tv_usec | getpid()); +#endif +#else + /* Poor randomness and no seeding here */ +#endif + + } + pid = p2; + } + +#ifdef HAVE_ARC4RANDOM + return arc4random() % max; +#elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV) + return random() % max; +#else + /* This one returns a 16-bit number, definitely not crypto-strong */ + return random_number(max); +#endif +} + +#endif + /************************************************* * Pick out a name from a string * *************************************************/ @@ -1510,7 +1596,7 @@ while (last > first) return tod_stamp(tod_zulu); case vtype_todlf: /* Log file datestamp tod */ - return tod_stamp(tod_log_datestamp); + return tod_stamp(tod_log_datestamp_daily); case vtype_reply: /* Get reply address */ s = find_header(US"reply-to:", exists_only, newsize, TRUE, @@ -1621,7 +1707,7 @@ for (i = 0; i < n; i++) sub[i] = NULL; break; } - sub[i] = expand_string_internal(s+1, TRUE, &s, skipping); + sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); if (sub[i] == NULL) return 3; if (*s++ != '}') return 1; while (isspace(*s)) s++; @@ -1693,6 +1779,7 @@ eval_condition(uschar *s, BOOL *yield) BOOL testfor = TRUE; BOOL tempcond, combined_cond; BOOL *subcondptr; +BOOL sub2_honour_dollar = TRUE; int i, rc, cond_type, roffset; int num[2]; struct stat statbuf; @@ -1825,7 +1912,7 @@ switch(cond_type) while (isspace(*s)) s++; if (*s != '{') goto COND_FAILED_CURLY_START; - sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL); + sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE); if (sub[0] == NULL) return NULL; if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -1938,22 +2025,30 @@ switch(cond_type) /* symbolic operators for numeric and string comparison, and a number of other operators, all requiring two arguments. + crypteq: encrypts plaintext and compares against an encrypted text, + using crypt(), crypt16(), MD5 or SHA-1 + inlist/inlisti: checks if first argument is in the list of the second match: does a regular expression match and sets up the numerical variables if it succeeds match_address: matches in an address list match_domain: matches in a domain list match_ip: matches a host list that is restricted to IP addresses match_local_part: matches in a local part list - crypteq: encrypts plaintext and compares against an encrypted text, - using crypt(), crypt16(), MD5 or SHA-1 */ - case ECOND_MATCH: case ECOND_MATCH_ADDRESS: case ECOND_MATCH_DOMAIN: case ECOND_MATCH_IP: case ECOND_MATCH_LOCAL_PART: +#ifndef EXPAND_LISTMATCH_RHS + sub2_honour_dollar = FALSE; +#endif + /* FALLTHROUGH */ + case ECOND_CRYPTEQ: + case ECOND_INLIST: + case ECOND_INLISTI: + case ECOND_MATCH: case ECOND_NUM_L: /* Numerical comparisons */ case ECOND_NUM_LE: @@ -1975,6 +2070,13 @@ switch(cond_type) for (i = 0; i < 2; i++) { + /* Sometimes, we don't expand substrings; too many insecure configurations + created using match_address{}{} and friends, where the second param + includes information from untrustworthy sources. */ + BOOL honour_dollar = TRUE; + if ((i > 0) && !sub2_honour_dollar) + honour_dollar = FALSE; + while (isspace(*s)) s++; if (*s != '{') { @@ -1983,7 +2085,8 @@ switch(cond_type) "after \"%s\"", name); return NULL; } - sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL); + sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL, + honour_dollar); if (sub[i] == NULL) return NULL; if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -2242,6 +2345,7 @@ switch(cond_type) } else /* {crypt} or {crypt16} and non-{ at start */ + /* }-for-text-editors */ { int which = 0; uschar *coded; @@ -2288,6 +2392,30 @@ switch(cond_type) } break; #endif /* SUPPORT_CRYPTEQ */ + + case ECOND_INLIST: + case ECOND_INLISTI: + { + int sep = 0; + BOOL found = FALSE; + uschar *save_iterate_item = iterate_item; + int (*compare)(const uschar *, const uschar *); + + if (cond_type == ECOND_INLISTI) + compare = strcmpic; + else + compare = (int (*)(const uschar *, const uschar *)) strcmp; + + while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL) + if (compare(sub[0], iterate_item) == 0) + { + found = TRUE; + break; + } + iterate_item = save_iterate_item; + *yield = found; + } + } /* Switch for comparison conditions */ return s; /* End of comparison conditions */ @@ -2359,7 +2487,7 @@ switch(cond_type) while (isspace(*s)) s++; if (*s++ != '{') goto COND_FAILED_CURLY_START; - sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL)); + sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE); if (sub[0] == NULL) return NULL; if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -2418,19 +2546,25 @@ switch(cond_type) interpretation, where general data can be used and only a few values map to FALSE. Note that readconf.c boolean matching, for boolean configuration options, - only matches true/yes/false/no. */ + only matches true/yes/false/no. + The bool_lax{} condition matches the Router logic, which is much more + liberal. */ case ECOND_BOOL: + case ECOND_BOOL_LAX: { uschar *sub_arg[1]; - uschar *t; + uschar *t, *t2; + uschar *ourname; size_t len; BOOL boolvalue = FALSE; while (isspace(*s)) s++; if (*s != '{') goto COND_FAILED_CURLY_START; - switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, US"bool")) + ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool"; + switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname)) { - case 1: expand_string_message = US"too few arguments or bracketing " - "error for bool"; + case 1: expand_string_message = string_sprintf( + "too few arguments or bracketing error for %s", + ourname); /*FALLTHROUGH*/ case 2: case 3: return NULL; @@ -2438,23 +2572,44 @@ switch(cond_type) t = sub_arg[0]; while (isspace(*t)) t++; len = Ustrlen(t); + if (len) + { + /* trailing whitespace: seems like a good idea to ignore it too */ + t2 = t + len - 1; + while (isspace(*t2)) t2--; + if (t2 != (t + len)) + { + *++t2 = '\0'; + len = t2 - t; + } + } DEBUG(D_expand) - debug_printf("considering bool: %s\n", len ? t : US""); + debug_printf("considering %s: %s\n", ourname, len ? t : US""); + /* logic for the lax case from expand_check_condition(), which also does + expands, and the logic is both short and stable enough that there should + be no maintenance burden from replicating it. */ if (len == 0) boolvalue = FALSE; else if (Ustrspn(t, "0123456789") == len) + { boolvalue = (Uatoi(t) == 0) ? FALSE : TRUE; + /* expand_check_condition only does a literal string "0" check */ + if ((cond_type == ECOND_BOOL_LAX) && (len > 1)) + boolvalue = TRUE; + } else if (strcmpic(t, US"true") == 0 || strcmpic(t, US"yes") == 0) boolvalue = TRUE; else if (strcmpic(t, US"false") == 0 || strcmpic(t, US"no") == 0) boolvalue = FALSE; + else if (cond_type == ECOND_BOOL_LAX) + boolvalue = TRUE; else { expand_string_message = string_sprintf("unrecognised boolean " "value \"%s\"", t); return NULL; } - if (yield != NULL) *yield = (boolvalue != 0); + if (yield != NULL) *yield = (boolvalue == testfor); return s; } @@ -2614,7 +2769,7 @@ if (*s++ != '{') goto FAILED_CURLY; want this string. Set skipping in the call in the fail case (this will always be the case if we were already skipping). */ -sub1 = expand_string_internal(s, TRUE, &s, !yes); +sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE); if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED; expand_string_forcedfail = FALSE; if (*s++ != '}') goto FAILED_CURLY; @@ -2639,7 +2794,7 @@ already skipping. */ while (isspace(*s)) s++; if (*s == '{') { - sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping); + sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE); if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED; expand_string_forcedfail = FALSE; if (*s++ != '}') goto FAILED_CURLY; @@ -3002,9 +3157,47 @@ if (*error == NULL) int op = *s++; int y = eval_op_unary(&s, decimal, error); if (*error != NULL) break; - if (op == '*') x *= y; - else if (op == '/') x /= y; - else x %= y; + /* SIGFPE both on div/mod by zero and on INT_MIN / -1, which would give + * a value of INT_MAX+1. Note that INT_MIN * -1 gives INT_MIN for me, which + * is a bug somewhere in [gcc 4.2.1, FreeBSD, amd64]. In fact, -N*-M where + * -N*M is INT_MIN will yielf INT_MIN. + * Since we don't support floating point, this is somewhat simpler. + * Ideally, we'd return an error, but since we overflow for all other + * arithmetic, consistency suggests otherwise, but what's the correct value + * to use? There is none. + * The C standard guarantees overflow for unsigned arithmetic but signed + * overflow invokes undefined behaviour; in practice, this is overflow + * except for converting INT_MIN to INT_MAX+1. We also can't guarantee + * that long/longlong larger than int are available, or we could just work + * with larger types. We should consider whether to guarantee 32bit eval + * and 64-bit working variables, with errors returned. For now ... + * So, the only SIGFPEs occur with a non-shrinking div/mod, thus -1; we + * can just let the other invalid results occur otherwise, as they have + * until now. For this one case, we can coerce. + */ + if (y == -1 && x == INT_MIN && op != '*') + { + DEBUG(D_expand) + debug_printf("Integer exception dodging: %d%c-1 coerced to %d\n", + INT_MIN, op, INT_MAX); + x = INT_MAX; + continue; + } + if (op == '*') + x *= y; + else + { + if (y == 0) + { + *error = (op == '/') ? US"divide by zero" : US"modulo by zero"; + x = 0; + break; + } + if (op == '/') + x /= y; + else + x %= y; + } } } *sptr = s; @@ -3164,6 +3357,8 @@ Arguments: expansion is placed here (typically used with ket_ends) skipping TRUE for recursive calls when the value isn't actually going to be used (to allow for optimisation) + honour_dollar TRUE if $ is to be expanded, + FALSE if it's just another character Returns: NULL if expansion fails: expand_string_forcedfail is set TRUE if failure was forced @@ -3173,7 +3368,7 @@ Returns: NULL if expansion fails: static uschar * expand_string_internal(uschar *string, BOOL ket_ends, uschar **left, - BOOL skipping) + BOOL skipping, BOOL honour_dollar) { int ptr = 0; int size = Ustrlen(string)+ 64; @@ -3229,7 +3424,7 @@ while (*s != 0) if (ket_ends && *s == '}') break; - if (*s != '$') + if (*s != '$' || !honour_dollar) { yield = string_cat(yield, &size, &ptr, s++, 1); continue; @@ -3445,7 +3640,7 @@ while (*s != 0) while (isspace(*s)) s++; if (*s == '{') { - key = expand_string_internal(s+1, TRUE, &s, skipping); + key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); if (key == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; @@ -3511,7 +3706,7 @@ while (*s != 0) first. */ if (*s != '{') goto EXPAND_FAILED_CURLY; - filename = expand_string_internal(s+1, TRUE, &s, skipping); + filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); if (filename == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; @@ -3571,8 +3766,8 @@ while (*s != 0) if (search_find_defer) { expand_string_message = - string_sprintf("lookup of \"%s\" gave DEFER: %s", key, - search_error_message); + string_sprintf("lookup of \"%s\" gave DEFER: %s", + string_printing2(key, FALSE), search_error_message); goto EXPAND_FAILED; } if (expand_setup > 0) expand_nmax = expand_setup; @@ -4180,7 +4375,7 @@ while (*s != 0) if (*s == '{') { - if (expand_string_internal(s+1, TRUE, &s, TRUE) == NULL) + if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE) == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; @@ -4195,7 +4390,7 @@ while (*s != 0) SOCK_FAIL: if (*s != '{') goto EXPAND_FAILED; DEBUG(D_any) debug_printf("%s\n", expand_string_message); - arg = expand_string_internal(s+1, TRUE, &s, FALSE); + arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE); if (arg == NULL) goto EXPAND_FAILED; yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg)); if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4224,7 +4419,7 @@ while (*s != 0) while (isspace(*s)) s++; if (*s != '{') goto EXPAND_FAILED_CURLY; - arg = expand_string_internal(s+1, TRUE, &s, skipping); + arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); if (arg == NULL) goto EXPAND_FAILED; while (isspace(*s)) s++; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4261,13 +4456,24 @@ while (*s != 0) (void)close(fd_in); + /* Read the pipe to get the command's output into $value (which is kept + in lookup_value). Read during execution, so that if the output exceeds + the OS pipe buffer limit, we don't block forever. */ + + f = fdopen(fd_out, "rb"); + sigalrm_seen = FALSE; + alarm(60); + lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL); + alarm(0); + (void)fclose(f); + /* Wait for the process to finish, applying the timeout, and inspect its return code for serious disasters. Simple non-zero returns are passed on. */ - if ((runrc = child_close(pid, 60)) < 0) + if (sigalrm_seen == TRUE || (runrc = child_close(pid, 30)) < 0) { - if (runrc == -256) + if (sigalrm_seen == TRUE || runrc == -256) { expand_string_message = string_sprintf("command timed out"); killpg(pid, SIGKILL); /* Kill the whole process group */ @@ -4283,14 +4489,6 @@ while (*s != 0) goto EXPAND_FAILED; } - - /* Read the pipe to get the command's output into $value (which is kept - in lookup_value). */ - - f = fdopen(fd_out, "rb"); - lookup_value = NULL; - lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL); - (void)fclose(f); } /* Process the yes/no strings; $value may be useful in both cases */ @@ -4652,7 +4850,7 @@ while (*s != 0) while (isspace(*s)) s++; if (*s == '{') { - sub[i] = expand_string_internal(s+1, TRUE, &s, skipping); + sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); if (sub[i] == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4747,7 +4945,7 @@ while (*s != 0) while (isspace(*s)) s++; if (*s++ != '{') goto EXPAND_FAILED_CURLY; - list = expand_string_internal(s, TRUE, &s, skipping); + list = expand_string_internal(s, TRUE, &s, skipping, TRUE); if (list == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4755,7 +4953,7 @@ while (*s != 0) { while (isspace(*s)) s++; if (*s++ != '{') goto EXPAND_FAILED_CURLY; - temp = expand_string_internal(s, TRUE, &s, skipping); + temp = expand_string_internal(s, TRUE, &s, skipping, TRUE); if (temp == NULL) goto EXPAND_FAILED; lookup_value = temp; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4779,7 +4977,7 @@ while (*s != 0) } else { - temp = expand_string_internal(s, TRUE, &s, TRUE); + temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE); } if (temp == NULL) @@ -4838,7 +5036,7 @@ while (*s != 0) else { - temp = expand_string_internal(expr, TRUE, NULL, skipping); + temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE); if (temp == NULL) { iterate_item = save_iterate_item; @@ -5020,7 +5218,7 @@ while (*s != 0) { int c; uschar *arg = NULL; - uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping); + uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE); if (sub == NULL) goto EXPAND_FAILED; s++; @@ -5095,7 +5293,7 @@ while (*s != 0) case EOP_EXPAND: { - uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping); + uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE); if (expanded == NULL) { expand_string_message = @@ -5402,8 +5600,8 @@ while (*s != 0) goto EXPAND_FAILED; } - if (lookup_list[n].quote != NULL) - sub = (lookup_list[n].quote)(sub, opt); + if (lookup_list[n]->quote != NULL) + sub = (lookup_list[n]->quote)(sub, opt); else if (opt != NULL) sub = NULL; if (sub == NULL) @@ -5704,6 +5902,40 @@ while (*s != 0) continue; } + /* pseudo-random number less than N */ + + case EOP_RANDINT: + { + int max; + uschar *s; + + max = expand_string_integer(sub, TRUE); + if (expand_string_message != NULL) + goto EXPAND_FAILED; + s = string_sprintf("%d", pseudo_random_number(max)); + yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + continue; + } + + /* Reverse IP, including IPv6 to dotted-nibble */ + + case EOP_REVERSE_IP: + { + int family, maskptr; + uschar reversed[128]; + + family = string_is_ip_address(sub, &maskptr); + if (family == 0) + { + expand_string_message = string_sprintf( + "reverse_ip() not given an IP address [%s]", sub); + goto EXPAND_FAILED; + } + invert_address(reversed, sub); + yield = string_cat(yield, &size, &ptr, reversed, Ustrlen(reversed)); + continue; + } + /* Unknown operator */ default: @@ -5829,7 +6061,7 @@ expand_string(uschar *string) search_find_defer = FALSE; malformed_header = FALSE; return (Ustrpbrk(string, "$\\") == NULL)? string : - expand_string_internal(string, FALSE, NULL, FALSE); + expand_string_internal(string, FALSE, NULL, FALSE, TRUE); }