-/* $Cambridge: exim/src/src/expand.c,v 1.33 2005/06/20 13:58:22 ph10 Exp $ */
+/* $Cambridge: exim/src/src/expand.c,v 1.62 2006/09/19 14:31:07 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
/* See the file NOTICE for conditions of use and distribution. */
US"from_utf8",
US"local_part",
US"quote_local_part",
+ US"time_eval",
US"time_interval"};
enum {
EOP_FROM_UTF8,
EOP_LOCAL_PART,
EOP_QUOTE_LOCAL_PART,
+ EOP_TIME_EVAL,
EOP_TIME_INTERVAL };
static uschar *op_table_main[] = {
US"match",
US"match_address",
US"match_domain",
+ US"match_ip",
US"match_local_part",
US"or",
US"pam",
ECOND_MATCH,
ECOND_MATCH_ADDRESS,
ECOND_MATCH_DOMAIN,
+ ECOND_MATCH_IP,
ECOND_MATCH_LOCAL_PART,
ECOND_OR,
ECOND_PAM,
/* This table must be kept in alphabetical order. */
static var_entry var_table[] = {
- { "acl_c0", vtype_stringptr, &acl_var[0] },
- { "acl_c1", vtype_stringptr, &acl_var[1] },
- { "acl_c2", vtype_stringptr, &acl_var[2] },
- { "acl_c3", vtype_stringptr, &acl_var[3] },
- { "acl_c4", vtype_stringptr, &acl_var[4] },
- { "acl_c5", vtype_stringptr, &acl_var[5] },
- { "acl_c6", vtype_stringptr, &acl_var[6] },
- { "acl_c7", vtype_stringptr, &acl_var[7] },
- { "acl_c8", vtype_stringptr, &acl_var[8] },
- { "acl_c9", vtype_stringptr, &acl_var[9] },
- { "acl_m0", vtype_stringptr, &acl_var[10] },
- { "acl_m1", vtype_stringptr, &acl_var[11] },
- { "acl_m2", vtype_stringptr, &acl_var[12] },
- { "acl_m3", vtype_stringptr, &acl_var[13] },
- { "acl_m4", vtype_stringptr, &acl_var[14] },
- { "acl_m5", vtype_stringptr, &acl_var[15] },
- { "acl_m6", vtype_stringptr, &acl_var[16] },
- { "acl_m7", vtype_stringptr, &acl_var[17] },
- { "acl_m8", vtype_stringptr, &acl_var[18] },
- { "acl_m9", vtype_stringptr, &acl_var[19] },
+ /* 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 },
{ "message_body", vtype_msgbody, &message_body },
{ "message_body_end", vtype_msgbody_end, &message_body_end },
{ "message_body_size", vtype_int, &message_body_size },
+ { "message_exim_id", vtype_stringptr, &message_id },
{ "message_headers", vtype_msgheaders, NULL },
{ "message_id", vtype_stringptr, &message_id },
{ "message_linecount", vtype_int, &message_linecount },
{ "sender_rcvhost", vtype_stringptr, &sender_rcvhost },
{ "sender_verify_failure",vtype_stringptr, &sender_verify_failure },
{ "smtp_active_hostname", vtype_stringptr, &smtp_active_hostname },
- { "smtp_command_argument", vtype_stringptr, &smtp_command_argument },
+ { "smtp_command", vtype_stringptr, &smtp_cmd_buffer },
+ { "smtp_command_argument", vtype_stringptr, &smtp_cmd_argument },
{ "sn0", vtype_filter_int, &filter_sn[0] },
{ "sn1", vtype_filter_int, &filter_sn[1] },
{ "sn2", vtype_filter_int, &filter_sn[2] },
uschar *decoded, *error;
while (ptr > yield && isspace(ptr[-1])) ptr--;
*ptr = 0;
- decoded = rfc2047_decode2(yield, TRUE, charset, '?', NULL, newsize, &error);
+ decoded = rfc2047_decode2(yield, check_rfc2047_length, charset, '?', NULL,
+ newsize, &error);
if (error != NULL)
{
DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
int first = 0;
int last = var_table_size;
+/* 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 an ACL variable does not exist, treat it as empty, unless strict_acl_vars is
+set, in which case give an error. */
+
+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;
+ }
+
+/* Handle $auth<n> variables. */
+
+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] == NULL)? US"" : auth_vars[n-1];
+ }
+
+/* For all other variables, search the table */
+
while (last > first)
{
uschar *s, *domain;
if (c < 0) { last = middle; continue; }
/* Found an existing variable. If in skipping state, the value isn't needed,
- and we want to avoid processing (such as looking up up the host name). */
+ and we want to avoid processing (such as looking up the host name). */
if (skipping) return US"";
return tod_stamp(tod_log_datestamp);
case vtype_reply: /* Get reply address */
- s = find_header(US"reply-to:", exists_only, newsize, FALSE,
+ s = find_header(US"reply-to:", exists_only, newsize, TRUE,
headers_charset);
+ if (s != NULL) while (isspace(*s)) s++;
if (s == NULL || *s == 0)
- s = find_header(US"from:", exists_only, newsize, FALSE, headers_charset);
+ {
+ *newsize = 0; /* For the *s==0 case */
+ s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset);
+ }
+ if (s != NULL)
+ {
+ uschar *t;
+ while (isspace(*s)) s++;
+ for (t = s; *t != 0; t++) if (*t == '\n') *t = ' ';
+ while (t > s && isspace(t[-1])) t--;
+ *t = 0;
+ }
return (s == NULL)? US"" : s;
/* A recipients list is available only during system message filtering,
+/*************************************************
+* 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 *
*************************************************/
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;
case ECOND_ISIP4:
case ECOND_ISIP6:
rc = string_is_ip_address(sub[0], NULL);
- *yield = ((cond_type == ECOND_ISIP)? (rc > 0) :
+ *yield = ((cond_type == ECOND_ISIP)? (rc != 0) :
(cond_type == ECOND_ISIP4)? (rc == 4) : (rc == 6)) == testfor;
break;
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:
case ECOND_CRYPTEQ:
if (!isalpha(name[0]))
{
- uschar *endptr;
- num[i] = (int)Ustrtol((const uschar *)sub[i], &endptr, 10);
- if (tolower(*endptr) == 'k')
- {
- num[i] *= 1024;
- endptr++;
- }
- else if (tolower(*endptr) == 'm')
- {
- num[i] *= 1024*1024;
- endptr++;
- }
- while (isspace(*endptr)) endptr++;
- if (*endptr != 0)
- {
- expand_string_message = string_sprintf("\"%s\" is not a number",
- sub[i]);
- return NULL;
- }
+ num[i] = expand_string_integer(sub[i], FALSE);
+ if (expand_string_message != NULL) return NULL;
}
}
MCL_DOMAIN + MCL_NOEXPAND, TRUE, NULL);
goto MATCHED_SOMETHING;
+ case ECOND_MATCH_IP: /* Match IP address in a host list */
+ if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0)
+ {
+ expand_string_message = string_sprintf("\"%s\" is not an IP address",
+ sub[0]);
+ return NULL;
+ }
+ else
+ {
+ unsigned int *nullcache = NULL;
+ check_host_block cb;
+
+ cb.host_name = US"";
+ cb.host_address = sub[0];
+
+ /* If the host address starts off ::ffff: it is an IPv6 address in
+ IPv4-compatible mode. Find the IPv4 part for checking against IPv4
+ addresses. */
+
+ cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)?
+ cb.host_address + 7 : cb.host_address;
+
+ rc = match_check_list(
+ &sub[1], /* the list */
+ 0, /* separator character */
+ &hostlist_anchor, /* anchor pointer */
+ &nullcache, /* cache pointer */
+ check_host, /* function for testing */
+ &cb, /* argument for function */
+ MCL_HOST, /* type of check */
+ sub[0], /* text for debugging */
+ NULL); /* where to pass back data */
+ }
+ goto MATCHED_SOMETHING;
+
case ECOND_MATCH_LOCAL_PART:
rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL,
MCL_LOCALPART + MCL_NOEXPAND, TRUE, NULL);
static uschar *
prvs_daystamp(int day_offset)
{
-uschar *days = store_get(16);
-(void)string_format(days, 16, TIME_T_FMT,
+uschar *days = store_get(32); /* 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";
}
int x = eval_term(&s, decimal, error);
if (*error == NULL)
{
- while (*s == '*' || *s == '/')
+ while (*s == '*' || *s == '/' || *s == '%')
{
int op = *s++;
int y = eval_term(&s, decimal, error);
if (*error != NULL) break;
- if (op == '*') x *= y; else x /= y;
+ if (op == '*') x *= y;
+ else if (op == '/') x /= y;
+ else x %= y;
}
}
*sptr = s;
{
expand_string_message =
string_sprintf("unknown variable name \"%s\"", name);
+ check_variable_error_message(name);
goto EXPAND_FAILED;
}
}
/* Check that a key was provided for those lookup types that need it,
and was not supplied for those that use the query style. */
- if (!mac_islookup(stype, lookup_querystyle))
+ if (!mac_islookup(stype, lookup_querystyle|lookup_absfilequery))
{
if (key == NULL)
{
}
/* Get the next string in brackets and expand it. It is the file name for
- single-key+file lookups, and the whole query otherwise. */
+ single-key+file lookups, and the whole query otherwise. In the case of
+ queries that also require a file name (e.g. sqlite), the file name comes
+ first. */
if (*s != '{') goto EXPAND_FAILED_CURLY;
filename = expand_string_internal(s+1, TRUE, &s, skipping);
while (isspace(*s)) s++;
/* If this isn't a single-key+file lookup, re-arrange the variables
- to be appropriate for the search_ functions. */
+ to be appropriate for the search_ functions. For query-style lookups,
+ there is just a "key", and no file name. For the special query-style +
+ file types, the query (i.e. "key") starts with a file name. */
if (key == NULL)
{
+ while (isspace(*filename)) filename++;
key = filename;
- filename = NULL;
+
+ if (mac_islookup(stype, lookup_querystyle))
+ {
+ filename = NULL;
+ }
+ else
+ {
+ if (*filename != '/')
+ {
+ expand_string_message = string_sprintf(
+ "absolute file name expected for \"%s\" lookup", name);
+ goto EXPAND_FAILED;
+ }
+ while (*key != 0 && !isspace(*key)) key++;
+ if (*key != 0) *key++ = 0;
+ }
}
/* If skipping, don't do the next bit - just lookup_value == NULL, as if
domain = Ustrrchr(sub_arg[0],'@');
if ( (domain == NULL) || (domain == sub_arg[0]) || (Ustrlen(domain) == 1) )
{
- expand_string_message = US"first parameter must be a qualified email address";
+ expand_string_message = US"prvs first argument must be a qualified email address";
+ goto EXPAND_FAILED;
+ }
+
+ /* Calculate the hash. The second argument must be a single-digit
+ key number, or unset. */
+
+ if (sub_arg[2] != NULL &&
+ (!isdigit(sub_arg[2][0]) || sub_arg[2][1] != 0))
+ {
+ expand_string_message = US"prvs second argument must be a single digit";
goto EXPAND_FAILED;
}
- /* Calculate the hash */
p = prvs_hmac_sha1(sub_arg[0],sub_arg[1],sub_arg[2],prvs_daystamp(7));
if (p == NULL)
{
- expand_string_message = US"hmac-sha1 conversion failed";
+ expand_string_message = US"prvs hmac-sha1 conversion failed";
goto EXPAND_FAILED;
}
int mysize = 0, myptr = 0;
const pcre *re;
uschar *p;
- /* Ugliness: We want to expand parameter 1 first, then set
+
+ /* TF: Ugliness: We want to expand parameter 1 first, then set
up expansion variables that are used in the expansion of
parameter 2. So we clone the string for the first
- expansion, where we only expand paramter 1. */
- uschar *s_backup = string_copy(s);
+ expansion, where we only expand parameter 1.
+
+ PH: Actually, that isn't necessary. The read_subs() function is
+ designed to work this way for the ${if and ${lookup expansions. I've
+ tidied the code.
+ */
/* Reset expansion variables */
prvscheck_result = NULL;
prvscheck_address = NULL;
prvscheck_keynum = NULL;
- switch(read_subs(sub_arg, 1, 1, &s_backup, skipping, FALSE, US"prvs"))
+ switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs"))
{
case 1: goto EXPAND_FAILED_CURLY;
case 2:
re = regex_must_compile(US"^prvs\\=(.+)\\/([0-9])([0-9]{3})([A-F0-9]{6})\\@(.+)$",
TRUE,FALSE);
- if (regex_match_and_setup(re,sub_arg[0],0,-1)) {
+ if (regex_match_and_setup(re,sub_arg[0],0,-1))
+ {
uschar *local_part = string_copyn(expand_nstring[1],expand_nlength[1]);
uschar *key_num = string_copyn(expand_nstring[2],expand_nlength[2]);
uschar *daystamp = string_copyn(expand_nstring[3],expand_nlength[3]);
prvscheck_address[myptr] = '\0';
prvscheck_keynum = string_copy(key_num);
- /* Now re-expand all arguments in the usual manner */
- switch(read_subs(sub_arg, 3, 3, &s, skipping, TRUE, US"prvs"))
+ /* Now expand the second argument */
+ switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs"))
{
case 1: goto EXPAND_FAILED_CURLY;
case 2:
case 3: goto EXPAND_FAILED;
}
- if (*sub_arg[2] == '\0')
- yield = string_cat(yield,&size,&ptr,prvscheck_address,Ustrlen(prvscheck_address));
- else
- yield = string_cat(yield,&size,&ptr,sub_arg[2],Ustrlen(sub_arg[2]));
-
/* Now we have the key and can check the address. */
- p = prvs_hmac_sha1(prvscheck_address, sub_arg[1], prvscheck_keynum, daystamp);
+
+ p = prvs_hmac_sha1(prvscheck_address, sub_arg[0], prvscheck_keynum,
+ daystamp);
+
if (p == NULL)
{
expand_string_message = US"hmac-sha1 conversion failed";
DEBUG(D_expand) debug_printf("prvscheck: received hash is %s\n", hash);
DEBUG(D_expand) debug_printf("prvscheck: own hash is %s\n", p);
+
if (Ustrcmp(p,hash) == 0)
{
/* Success, valid BATV address. Now check the expiry date. */
uschar *now = prvs_daystamp(0);
unsigned int inow = 0,iexpire = 1;
- sscanf(CS now,"%u",&inow);
- sscanf(CS daystamp,"%u",&iexpire);
+ (void)sscanf(CS now,"%u",&inow);
+ (void)sscanf(CS daystamp,"%u",&iexpire);
/* When "iexpire" is < 7, a "flip" has occured.
Adjust "inow" accordingly. */
prvscheck_result = NULL;
DEBUG(D_expand) debug_printf("prvscheck: hash failure, $pvrs_result unset\n");
}
- }
+
+ /* Now expand the final argument. We leave this till now so that
+ it can include $prvscheck_result. */
+
+ switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, US"prvs"))
+ {
+ case 1: goto EXPAND_FAILED_CURLY;
+ case 2:
+ case 3: goto EXPAND_FAILED;
+ }
+
+ if (sub_arg[0] == NULL || *sub_arg[0] == '\0')
+ yield = string_cat(yield,&size,&ptr,prvscheck_address,Ustrlen(prvscheck_address));
+ else
+ yield = string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0]));
+
+ /* Reset the "internal" variables afterwards, because they are in
+ dynamic store that will be reclaimed if the expansion succeeded. */
+
+ prvscheck_address = NULL;
+ prvscheck_keynum = NULL;
+ }
else
{
/* Does not look like a prvs encoded address, return the empty string.
- We need to make sure all subs are expanded first. */
- switch(read_subs(sub_arg, 3, 3, &s, skipping, TRUE, US"prvs"))
+ We need to make sure all subs are expanded first, so as to skip over
+ the entire item. */
+
+ switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"prvs"))
{
case 1: goto EXPAND_FAILED_CURLY;
case 2:
}
yield = cat_file(f, yield, &size, &ptr, sub_arg[1]);
- fclose(f);
+ (void)fclose(f);
continue;
}
}
else sub_arg[3] = NULL; /* No eol if no timeout */
- /* If skipping, we don't actually do anything */
+ /* If skipping, we don't actually do anything. Otherwise, arrange to
+ connect to either an IP or a Unix socket. */
if (!skipping)
{
- /* Make a connection to the socket */
+ /* Handle an IP (internet) domain */
- if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+ if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
{
- expand_string_message = string_sprintf("failed to create socket: %s",
- strerror(errno));
- goto SOCK_FAIL;
+ BOOL connected = FALSE;
+ int namelen, port;
+ host_item shost;
+ host_item *h;
+ uschar *server_name = sub_arg[0] + 5;
+ uschar *port_name = Ustrrchr(server_name, ':');
+
+ /* Sort out the port */
+
+ if (port_name == NULL)
+ {
+ expand_string_message =
+ string_sprintf("missing port for readsocket %s", sub_arg[0]);
+ goto EXPAND_FAILED;
+ }
+ *port_name++ = 0; /* Terminate server name */
+
+ if (isdigit(*port_name))
+ {
+ uschar *end;
+ port = Ustrtol(port_name, &end, 0);
+ if (end != port_name + Ustrlen(port_name))
+ {
+ expand_string_message =
+ string_sprintf("invalid port number %s", port_name);
+ goto EXPAND_FAILED;
+ }
+ }
+ else
+ {
+ struct servent *service_info = getservbyname(CS port_name, "tcp");
+ if (service_info == NULL)
+ {
+ expand_string_message = string_sprintf("unknown port \"%s\"",
+ port_name);
+ goto EXPAND_FAILED;
+ }
+ port = ntohs(service_info->s_port);
+ }
+
+ /* Sort out the server. */
+
+ shost.next = NULL;
+ shost.address = NULL;
+ shost.port = port;
+ shost.mx = -1;
+
+ namelen = Ustrlen(server_name);
+
+ /* Anything enclosed in [] must be an IP address. */
+
+ if (server_name[0] == '[' &&
+ server_name[namelen - 1] == ']')
+ {
+ server_name[namelen - 1] = 0;
+ server_name++;
+ if (string_is_ip_address(server_name, NULL) == 0)
+ {
+ expand_string_message =
+ string_sprintf("malformed IP address \"%s\"", server_name);
+ goto EXPAND_FAILED;
+ }
+ shost.name = shost.address = server_name;
+ }
+
+ /* Otherwise check for an unadorned IP address */
+
+ else if (string_is_ip_address(server_name, NULL) != 0)
+ shost.name = shost.address = server_name;
+
+ /* Otherwise lookup IP address(es) from the name */
+
+ else
+ {
+ shost.name = server_name;
+ if (host_find_byname(&shost, NULL, NULL, FALSE) != HOST_FOUND)
+ {
+ expand_string_message =
+ string_sprintf("no IP address found for host %s", shost.name);
+ goto EXPAND_FAILED;
+ }
+ }
+
+ /* Try to connect to the server - test each IP till one works */
+
+ for (h = &shost; h != NULL; h = h->next)
+ {
+ int af = (Ustrchr(h->address, ':') != 0)? AF_INET6 : AF_INET;
+ if ((fd = ip_socket(SOCK_STREAM, af)) == -1)
+ {
+ expand_string_message = string_sprintf("failed to create socket: "
+ "%s", strerror(errno));
+ goto SOCK_FAIL;
+ }
+
+ if (ip_connect(fd, af, h->address, port, timeout) == 0)
+ {
+ connected = TRUE;
+ break;
+ }
+ }
+
+ if (!connected)
+ {
+ expand_string_message = string_sprintf("failed to connect to "
+ "socket %s: couldn't connect to any host", sub_arg[0],
+ strerror(errno));
+ goto SOCK_FAIL;
+ }
}
- sockun.sun_family = AF_UNIX;
- sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
- sub_arg[0]);
- if(connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun)) == -1)
+ /* Handle a Unix domain socket */
+
+ else
{
- expand_string_message = string_sprintf("failed to connect to socket "
- "%s: %s", sub_arg[0], strerror(errno));
- goto SOCK_FAIL;
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+ {
+ expand_string_message = string_sprintf("failed to create socket: %s",
+ strerror(errno));
+ goto SOCK_FAIL;
+ }
+
+ sockun.sun_family = AF_UNIX;
+ sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
+ sub_arg[0]);
+ if(connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun)) == -1)
+ {
+ expand_string_message = string_sprintf("failed to connect to socket "
+ "%s: %s", sub_arg[0], strerror(errno));
+ goto SOCK_FAIL;
+ }
}
+
DEBUG(D_expand) debug_printf("connected to socket %s\n", sub_arg[0]);
/* Write the request string, if not empty */
alarm(timeout);
yield = cat_file(f, yield, &size, &ptr, sub_arg[3]);
alarm(0);
- fclose(f);
+ (void)fclose(f);
/* After a timeout, we restore the pointer in the result, that is,
make sure we add nothing from the socket. */
if (sigalrm_seen)
{
ptr = save_ptr;
- expand_string_message = US"socket read timed out";
+ expand_string_message = US "socket read timed out";
goto SOCK_FAIL;
}
}
/* Nothing is written to the standard input. */
- close(fd_in);
+ (void)close(fd_in);
/* Wait for the process to finish, applying the timeout, and inspect its
return code for serious disasters. Simple non-zero returns are passed on.
f = fdopen(fd_out, "rb");
lookup_value = NULL;
lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
- fclose(f);
+ (void)fclose(f);
}
/* Process the yes/no strings; $value may be useful in both cases */
continue;
}
+ /* Note that for Darwin and Cygwin, BASE_62 actually has the value 36 */
+
case EOP_BASE62D:
{
uschar buf[16];
if (t == NULL)
{
expand_string_message = string_sprintf("argument for base62d "
- "operator is \"%s\", which is not a base 62 number", sub);
+ "operator is \"%s\", which is not a base %d number", sub,
+ BASE_62);
goto EXPAND_FAILED;
}
- n = n * 62 + (t - base62_chars);
+ n = n * BASE_62 + (t - base62_chars);
}
(void)sprintf(CS buf, "%ld", n);
yield = string_cat(yield, &size, &ptr, buf, Ustrlen(buf));
{
uschar buffer[2048];
uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
- buffer, sizeof(buffer));
+ buffer, sizeof(buffer), FALSE);
yield = string_cat(yield, &size, &ptr, string, Ustrlen(string));
continue;
}
/* Handle time period formating */
+ case EOP_TIME_EVAL:
+ {
+ int n = readconf_readtime(sub, 0, FALSE);
+ if (n < 0)
+ {
+ expand_string_message = string_sprintf("string \"%s\" is not an "
+ "Exim time interval in \"%s\" operator", sub, name);
+ goto EXPAND_FAILED;
+ }
+ sprintf(CS var_buffer, "%d", n);
+ yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
+ continue;
+ }
+
case EOP_TIME_INTERVAL:
{
int n;
mode_t mode;
struct stat st;
+ if ((expand_forbid & RDO_EXISTS) != 0)
+ {
+ expand_string_message = US"Use of the stat() expansion is not permitted";
+ goto EXPAND_FAILED;
+ }
+
if (stat(CS sub, &st) < 0)
{
expand_string_message = string_sprintf("stat(%s) failed: %s",
{
expand_string_message =
string_sprintf("unknown variable in \"${%s}\"", name);
+ check_variable_error_message(name);
goto EXPAND_FAILED;
}
len = Ustrlen(value);
/* Expand a string, and convert the result into an integer.
-Argument: the string to be expanded
+Arguments:
+ string the string to be expanded
+ isplus TRUE if a non-negative number is expected
Returns: the integer value, or
-1 for an expansion error ) in both cases, message in
-2 for an integer interpretation error ) expand_string_message
-
+ expand_string_message is set NULL for an OK integer
*/
int
-expand_string_integer(uschar *string)
+expand_string_integer(uschar *string, BOOL isplus)
{
long int value;
uschar *s = expand_string(string);
uschar *msg = US"invalid integer \"%s\"";
uschar *endptr;
+/* If expansion failed, expand_string_message will be set. */
+
if (s == NULL) return -1;
/* On an overflow, strtol() returns LONG_MAX or LONG_MIN, and sets errno
systems, so we set it zero ourselves. */
errno = 0;
+expand_string_message = NULL; /* Indicates no error */
value = strtol(CS s, CSS &endptr, 0);
if (endptr == s)
{
msg = US"integer expected but \"%s\" found";
}
+else if (value < 0 && isplus)
+ {
+ msg = US"non-negative integer expected but \"%s\" found";
+ }
else
{
/* Ensure we can cast this down to an int */