-/* $Cambridge: exim/src/src/expand.c,v 1.104 2009/10/16 09:10:40 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. */
/* 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
US"from_utf8",
US"local_part",
US"quote_local_part",
+ US"reverse_ip",
US"time_eval",
US"time_interval"};
EOP_FROM_UTF8,
EOP_LOCAL_PART,
EOP_QUOTE_LOCAL_PART,
+ EOP_REVERSE_IP,
EOP_TIME_EVAL,
EOP_TIME_INTERVAL };
US">=",
US"and",
US"bool",
+ US"bool_lax",
US"crypteq",
US"def",
US"eq",
US"gei",
US"gt",
US"gti",
+ US"inlist",
+ US"inlisti",
US"isip",
US"isip4",
US"isip6",
ECOND_NUM_GE,
ECOND_AND,
ECOND_BOOL,
+ ECOND_BOOL_LAX,
ECOND_CRYPTEQ,
ECOND_DEF,
ECOND_STR_EQ,
ECOND_STR_GEI,
ECOND_STR_GT,
ECOND_STR_GTI,
+ ECOND_INLIST,
+ ECOND_INLISTI,
ECOND_ISIP,
ECOND_ISIP4,
ECOND_ISIP6,
/* 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
{ "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 },
{ "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 },
/* For textual hashes */
-static char *hashcodes = "abcdefghijklmnopqrtsuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789";
+static const char *hashcodes = "abcdefghijklmnopqrtsuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
enum { HMAC_MD5, HMAC_SHA1 };
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
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,
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++;
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;
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;
/* 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:
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 != '{')
{
"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;
}
else /* {crypt} or {crypt16} and non-{ at start */
+ /* }-for-text-editors */
{
int which = 0;
uschar *coded;
}
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 */
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;
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;
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"<empty>");
+ debug_printf("considering %s: %s\n", ourname, len ? t : US"<empty>");
+ /* 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;
}
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;
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;
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;
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
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;
if (ket_ends && *s == '}') break;
- if (*s != '$')
+ if (*s != '$' || !honour_dollar)
{
yield = string_cat(yield, &size, &ptr, s++, 1);
continue;
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++;
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++;
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;
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++;
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;
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;
(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 */
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 */
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;
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;
{
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;
}
else
{
- temp = expand_string_internal(s, TRUE, &s, TRUE);
+ temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE);
}
if (temp == NULL)
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;
{
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++;
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 =
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)
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:
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);
}