X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/d45b1de81aa47cef4ca098a4b2725d855b154162..194cc0e4ae3487900036c6bd208c0784d4e6e814:/src/src/expand.c diff --git a/src/src/expand.c b/src/src/expand.c index 1517c0625..d049466f1 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/expand.c,v 1.60 2006/09/18 14:49:23 ph10 Exp $ */ +/* $Cambridge: exim/src/src/expand.c,v 1.70 2006/11/13 12:07:46 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -274,7 +274,8 @@ enum { vtype_stringptr, /* value is address of pointer to string */ vtype_msgbody, /* as stringptr, but read when first required */ vtype_msgbody_end, /* ditto, the end of the message */ - vtype_msgheaders, /* the message's headers */ + vtype_msgheaders, /* the message's headers, processed */ + vtype_msgheaders_raw, /* the message's headers, unprocessed */ vtype_localpart, /* extract local part from string */ vtype_domain, /* extract domain from string */ vtype_recipients, /* extract recipients from recipients list */ @@ -300,6 +301,8 @@ enum { /* This table must be kept in alphabetical order. */ static var_entry var_table[] = { + /* WARNING: Do not invent variables whose names start acl_c or acl_m because + they will be confused with user-creatable ACL variables. */ { "acl_verify_message", vtype_stringptr, &acl_verify_message }, { "address_data", vtype_stringptr, &deliver_address_data }, { "address_file", vtype_stringptr, &address_file }, @@ -383,6 +386,7 @@ static var_entry var_table[] = { { "message_body_size", vtype_int, &message_body_size }, { "message_exim_id", vtype_stringptr, &message_id }, { "message_headers", vtype_msgheaders, NULL }, + { "message_headers_raw", vtype_msgheaders_raw, NULL }, { "message_id", vtype_stringptr, &message_id }, { "message_linecount", vtype_int, &message_linecount }, { "message_size", vtype_int, &message_size }, @@ -432,6 +436,8 @@ static var_entry var_table[] = { { "rcpt_fail_count", vtype_int, &rcpt_fail_count }, { "received_count", vtype_int, &received_count }, { "received_for", vtype_stringptr, &received_for }, + { "received_ip_address", vtype_stringptr, &interface_address }, + { "received_port", vtype_int, &interface_port }, { "received_protocol", vtype_stringptr, &received_protocol }, { "received_time", vtype_int, &received_time }, { "recipient_data", vtype_stringptr, &recipient_data }, @@ -1076,7 +1082,8 @@ Arguments: newsize return the size of memory block that was obtained; may be NULL if exists_only is TRUE want_raw TRUE if called for $rh_ or $rheader_ variables; no processing, - other than concatenating, will be done on the header + other than concatenating, will be done on the header. Also used + for $message_headers_raw. charset name of charset to translate MIME words to; used only if want_raw is false; if NULL, no translation is done (this is used for $bh_ and $bheader_) @@ -1119,6 +1126,12 @@ for (i = 0; i < 2; i++) while (isspace(*t)) t++; /* remove leading white space */ ilen = h->slen - (t - h->text); /* length to insert */ + /* Unless wanted raw, remove trailing whitespace, including the + newline. */ + + if (!want_raw) + while (ilen > 0 && isspace(t[ilen-1])) ilen--; + /* Set comma = 1 if handling a single header and it's one of those that contains an address list, except when asked for raw headers. Only need to do this once. */ @@ -1130,7 +1143,7 @@ for (i = 0; i < 2; i++) /* First pass - compute total store needed; second pass - compute total store used, including this header. */ - size += ilen + comma; + size += ilen + comma + 1; /* +1 for the newline */ /* Second pass - concatentate the data, up to a maximum. Note that the loop stops when size hits the limit. */ @@ -1139,14 +1152,19 @@ for (i = 0; i < 2; i++) { if (size > header_insert_maxlen) { - ilen -= size - header_insert_maxlen; + ilen -= size - header_insert_maxlen - 1; comma = 0; } Ustrncpy(ptr, t, ilen); ptr += ilen; - if (comma != 0 && ilen > 0) + + /* For a non-raw header, put in the comma if needed, then add + back the newline we removed above, provided there was some text in + the header. */ + + if (!want_raw && ilen > 0) { - ptr[-1] = ','; + if (comma != 0) *ptr++ = ','; *ptr++ = '\n'; } } @@ -1154,8 +1172,9 @@ for (i = 0; i < 2; i++) } } - /* At end of first pass, truncate size if necessary, and get the buffer - to hold the data, returning the buffer size. */ + /* At end of first pass, return NULL if no header found. Then truncate size + if necessary, and get the buffer to hold the data, returning the buffer size. + */ if (i == 0) { @@ -1166,10 +1185,6 @@ for (i = 0; i < 2; i++) } } -/* Remove a redundant added comma if present */ - -if (comma != 0 && ptr > yield) ptr -= 2; - /* That's all we do for raw header expansion. */ if (want_raw) @@ -1177,15 +1192,16 @@ if (want_raw) *ptr = 0; } -/* Otherwise, we remove trailing whitespace, including newlines. Then we do RFC -2047 decoding, translating the charset if requested. The rfc2047_decode2() +/* Otherwise, remove a final newline and a redundant added comma. Then we do +RFC 2047 decoding, translating the charset if requested. The rfc2047_decode2() function can return an error with decoded data if the charset translation fails. If decoding fails, it returns NULL. */ else { uschar *decoded, *error; - while (ptr > yield && isspace(ptr[-1])) ptr--; + if (ptr > yield && ptr[-1] == '\n') ptr--; + if (ptr > yield && comma != 0 && ptr[-1] == ',') ptr--; *ptr = 0; decoded = rfc2047_decode2(yield, check_rfc2047_length, charset, '?', NULL, newsize, &error); @@ -1231,37 +1247,26 @@ find_variable(uschar *name, BOOL exists_only, BOOL skipping, int *newsize) int first = 0; int last = var_table_size; -/* Handle ACL variables, which are not in the table because their number may -vary depending on a build-time setting. If the variable's name is not of the -form acl_mddd or acl_cddd, where the d's are digits, fall through to look for -other names that start with acl_. */ +/* Handle ACL variables, whose names are of the form acl_cxxx or acl_mxxx. +Originally, xxx had to be a number in the range 0-9 (later 0-19), but from +release 4.64 onwards arbitrary names are permitted, as long as the first 5 +characters are acl_c or acl_m and the sixth is either a digit or an underscore +(this gave backwards compatibility at the changeover). There may be built-in +variables whose names start acl_ but they should never start in this way. This +slightly messy specification is a consequence of the history, needless to say. -if (Ustrncmp(name, "acl_", 4) == 0) - { - uschar *endptr; - int offset = -1; - int max = 0; - - if (name[4] == 'm') - { - offset = ACL_CVARS; - max = ACL_MVARS; - } - else if (name[4] == 'c') - { - offset = 0; - max = ACL_CVARS; - } +If an ACL variable does not exist, treat it as empty, unless strict_acl_vars is +set, in which case give an error. */ - if (offset >= 0) - { - int n = Ustrtoul(name + 5, &endptr, 10); - if (*endptr == 0 && n < max) - return (acl_var[offset + n] == NULL)? US"" : acl_var[offset + n]; - } +if ((Ustrncmp(name, "acl_c", 5) == 0 || Ustrncmp(name, "acl_m", 5) == 0) && + !isalpha(name[5])) + { + tree_node *node = + tree_search((name[4] == 'c')? acl_var_c : acl_var_m, name + 4); + return (node == NULL)? (strict_acl_vars? NULL : US"") : node->data.ptr; } -/* Similarly for $auth variables. */ +/* Handle $auth variables. */ if (Ustrncmp(name, "auth", 4) == 0) { @@ -1394,6 +1399,9 @@ while (last > first) case vtype_msgheaders: return find_header(NULL, exists_only, newsize, FALSE, NULL); + case vtype_msgheaders_raw: + return find_header(NULL, exists_only, newsize, TRUE, NULL); + case vtype_msgbody: /* Pointer to msgbody string */ case vtype_msgbody_end: /* Ditto, the end of the msg */ ss = (uschar **)(var_table[middle].value); @@ -1575,6 +1583,33 @@ return 0; +/************************************************* +* Elaborate message for bad variable * +*************************************************/ + +/* For the "unknown variable" message, take a look at the variable's name, and +give additional information about possible ACL variables. The extra information +is added on to expand_string_message. + +Argument: the name of the variable +Returns: nothing +*/ + +static void +check_variable_error_message(uschar *name) +{ +if (Ustrncmp(name, "acl_", 4) == 0) + expand_string_message = string_sprintf("%s (%s)", expand_string_message, + (name[4] == 'c' || name[4] == 'm')? + (isalpha(name[5])? + US"6th character of a user-defined ACL variable must be a digit or underscore" : + US"strict_acl_vars is set" /* Syntax is OK, it has to be this */ + ) : + US"user-defined ACL variables must start acl_c or acl_m"); +} + + + /************************************************* * Read and evaluate a condition * *************************************************/ @@ -1656,7 +1691,9 @@ switch(cond_type) s = read_name(name, 256, s+1, US"_"); - /* Test for a header's existence */ + /* 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 + omitted. Set a flag to adjust a subsequent error message in this case. */ if (Ustrncmp(name, "h_", 2) == 0 || Ustrncmp(name, "rh_", 3) == 0 || @@ -1666,6 +1703,7 @@ switch(cond_type) Ustrncmp(name, "bheader_", 8) == 0) { s = read_header_name(name, 256, s); + if (Ustrchr(name, '}') != NULL) malformed_header = TRUE; if (yield != NULL) *yield = (find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor; } @@ -1681,6 +1719,7 @@ switch(cond_type) expand_string_message = (name[0] == 0)? string_sprintf("variable name omitted after \"def:\"") : string_sprintf("unknown variable \"%s\" after \"def:\"", name); + check_variable_error_message(name); return NULL; } if (yield != NULL) *yield = (value[0] != 0) == testfor; @@ -2682,63 +2721,53 @@ return yield; * Evaluate numeric expression * *************************************************/ -/* This is a set of mutually recursive functions that evaluate a simple -arithmetic expression involving only + - * / and parentheses. The only one that -is called from elsewhere is eval_expr, whose interface is: +/* This is a set of mutually recursive functions that evaluate an arithmetic +expression involving + - * / % & | ^ ~ << >> and parentheses. The only one of +these functions that is called from elsewhere is eval_expr, whose interface is: Arguments: - sptr pointer to the pointer to the string - gets updated - decimal TRUE if numbers are to be assumed decimal - error pointer to where to put an error message - must be NULL on input - endket TRUE if ')' must terminate - FALSE for external call + sptr pointer to the pointer to the string - gets updated + decimal TRUE if numbers are to be assumed decimal + error pointer to where to put an error message - must be NULL on input + endket TRUE if ')' must terminate - FALSE for external call - -Returns: on success: the value of the expression, with *error still NULL - on failure: an undefined value, with *error = a message +Returns: on success: the value of the expression, with *error still NULL + on failure: an undefined value, with *error = a message */ -static int eval_sumterm(uschar **, BOOL, uschar **); +static int eval_op_or(uschar **, BOOL, uschar **); + static int eval_expr(uschar **sptr, BOOL decimal, uschar **error, BOOL endket) { uschar *s = *sptr; -int x = eval_sumterm(&s, decimal, error); +int x = eval_op_or(&s, decimal, error); if (*error == NULL) { - while (*s == '+' || *s == '-') + if (endket) { - int op = *s++; - int y = eval_sumterm(&s, decimal, error); - if (*error != NULL) break; - if (op == '+') x += y; else x -= y; - } - if (*error == NULL) - { - if (endket) - { - if (*s != ')') - *error = US"expecting closing parenthesis"; - else - while (isspace(*(++s))); - } - else if (*s != 0) *error = US"expecting + or -"; + if (*s != ')') + *error = US"expecting closing parenthesis"; + else + while (isspace(*(++s))); } + else if (*s != 0) *error = US"expecting operator"; } - *sptr = s; return x; } + static int -eval_term(uschar **sptr, BOOL decimal, uschar **error) +eval_number(uschar **sptr, BOOL decimal, uschar **error) { register int c; int n; uschar *s = *sptr; while (isspace(*s)) s++; c = *s; -if (isdigit(c) || ((c == '-' || c == '+') && isdigit(s[1]))) +if (isdigit(c)) { int count; (void)sscanf(CS s, (decimal? "%d%n" : "%i%n"), &n, &count); @@ -2761,16 +2790,38 @@ else return n; } -static int eval_sumterm(uschar **sptr, BOOL decimal, uschar **error) + +static int eval_op_unary(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; -int x = eval_term(&s, decimal, error); +int x; +while (isspace(*s)) s++; +if (*s == '+' || *s == '-' || *s == '~') + { + int op = *s++; + x = eval_op_unary(&s, decimal, error); + if (op == '-') x = -x; + else if (op == '~') x = ~x; + } +else + { + x = eval_number(&s, decimal, error); + } +*sptr = s; +return x; +} + + +static int eval_op_mult(uschar **sptr, BOOL decimal, uschar **error) +{ +uschar *s = *sptr; +int x = eval_op_unary(&s, decimal, error); if (*error == NULL) { while (*s == '*' || *s == '/' || *s == '%') { int op = *s++; - int y = eval_term(&s, decimal, error); + int y = eval_op_unary(&s, decimal, error); if (*error != NULL) break; if (op == '*') x *= y; else if (op == '/') x /= y; @@ -2782,6 +2833,105 @@ return x; } +static int eval_op_sum(uschar **sptr, BOOL decimal, uschar **error) +{ +uschar *s = *sptr; +int x = eval_op_mult(&s, decimal, error); +if (*error == NULL) + { + while (*s == '+' || *s == '-') + { + int op = *s++; + int y = eval_op_mult(&s, decimal, error); + if (*error != NULL) break; + if (op == '+') x += y; else x -= y; + } + } +*sptr = s; +return x; +} + + +static int eval_op_shift(uschar **sptr, BOOL decimal, uschar **error) +{ +uschar *s = *sptr; +int x = eval_op_sum(&s, decimal, error); +if (*error == NULL) + { + while ((*s == '<' || *s == '>') && s[1] == s[0]) + { + int y; + int op = *s++; + s++; + y = eval_op_sum(&s, decimal, error); + if (*error != NULL) break; + if (op == '<') x <<= y; else x >>= y; + } + } +*sptr = s; +return x; +} + + +static int eval_op_and(uschar **sptr, BOOL decimal, uschar **error) +{ +uschar *s = *sptr; +int x = eval_op_shift(&s, decimal, error); +if (*error == NULL) + { + while (*s == '&') + { + int y; + s++; + y = eval_op_shift(&s, decimal, error); + if (*error != NULL) break; + x &= y; + } + } +*sptr = s; +return x; +} + + +static int eval_op_xor(uschar **sptr, BOOL decimal, uschar **error) +{ +uschar *s = *sptr; +int x = eval_op_and(&s, decimal, error); +if (*error == NULL) + { + while (*s == '^') + { + int y; + s++; + y = eval_op_and(&s, decimal, error); + if (*error != NULL) break; + x ^= y; + } + } +*sptr = s; +return x; +} + + +static int eval_op_or(uschar **sptr, BOOL decimal, uschar **error) +{ +uschar *s = *sptr; +int x = eval_op_xor(&s, decimal, error); +if (*error == NULL) + { + while (*s == '|') + { + int y; + s++; + y = eval_op_xor(&s, decimal, error); + if (*error != NULL) break; + x |= y; + } + } +*sptr = s; +return x; +} + /************************************************* @@ -2939,7 +3089,7 @@ while (*s != 0) value = find_header(name, FALSE, &newsize, want_raw, charset); /* If we didn't find the header, and the header contains a closing brace - characters, this may be a user error where the terminating colon + character, this may be a user error where the terminating colon has been omitted. Set a flag to adjust the error message in this case. But there is no error here - nothing gets inserted. */ @@ -2959,6 +3109,7 @@ while (*s != 0) { expand_string_message = string_sprintf("unknown variable name \"%s\"", name); + check_variable_error_message(name); goto EXPAND_FAILED; } } @@ -3722,7 +3873,8 @@ while (*s != 0) else { shost.name = server_name; - if (host_find_byname(&shost, NULL, NULL, FALSE) != HOST_FOUND) + if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, + FALSE) != HOST_FOUND) { expand_string_message = string_sprintf("no IP address found for host %s", shost.name); @@ -3797,6 +3949,14 @@ while (*s != 0) } } + /* Shut down the sending side of the socket. This helps some servers to + recognise that it is their turn to do some work. Just in case some + system doesn't have this function, make it conditional. */ + + #ifdef SHUT_WR + shutdown(fd, SHUT_WR); + #endif + /* Now we need to read from the socket, under a timeout. The function that reads a file can be used. */ @@ -5118,6 +5278,7 @@ while (*s != 0) { expand_string_message = string_sprintf("unknown variable in \"${%s}\"", name); + check_variable_error_message(name); goto EXPAND_FAILED; } len = Ustrlen(value);