Add commented-braces for ease of brace-matching editor use
[exim.git] / src / src / expand.c
index 7803862738722ac63bff8210155aff94eb865342..c5aadea3578d3fe3c31f8a1a229661c205cfb274 100644 (file)
@@ -181,6 +181,7 @@ static uschar *op_table_main[] = {
   US"h",
   US"hash",
   US"hex2b64",
+  US"hexquote",
   US"l",
   US"lc",
   US"length",
@@ -216,6 +217,7 @@ enum {
   EOP_H,
   EOP_HASH,
   EOP_HEX2B64,
+  EOP_HEXQUOTE,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
@@ -367,9 +369,7 @@ enum {
   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 */
-                        /* (available only in system filters, ACLs, and */
-                        /* local_scan()) */
+  vtype_string_func,   /* value is string returned by given function */
   vtype_todbsdin,       /* value not used; generate BSD inbox tod */
   vtype_tode,           /* value not used; generate tod in epoch format */
   vtype_todel,          /* value not used; generate tod in epoch/usec format */
@@ -389,6 +389,8 @@ enum {
   #endif
   };
 
+static uschar * fn_recipients(void);
+
 /* This table must be kept in alphabetical order. */
 
 static var_entry var_table[] = {
@@ -408,6 +410,7 @@ static var_entry var_table[] = {
   { "address_data",        vtype_stringptr,   &deliver_address_data },
   { "address_file",        vtype_stringptr,   &address_file },
   { "address_pipe",        vtype_stringptr,   &address_pipe },
+  { "authenticated_fail_id",vtype_stringptr,  &authenticated_fail_id },
   { "authenticated_id",    vtype_stringptr,   &authenticated_id },
   { "authenticated_sender",vtype_stringptr,   &authenticated_sender },
   { "authentication_failed",vtype_int,        &authentication_failed },
@@ -458,6 +461,12 @@ static var_entry var_table[] = {
   { "dkim_signers",        vtype_stringptr,   &dkim_signers },
   { "dkim_verify_reason",  vtype_dkim,        (void *)DKIM_VERIFY_REASON },
   { "dkim_verify_status",  vtype_dkim,        (void *)DKIM_VERIFY_STATUS},
+#endif
+#ifdef EXPERIMENTAL_DMARC
+  { "dmarc_ar_header",     vtype_stringptr,   &dmarc_ar_header },
+  { "dmarc_status",        vtype_stringptr,   &dmarc_status },
+  { "dmarc_status_text",   vtype_stringptr,   &dmarc_status_text },
+  { "dmarc_used_domain",   vtype_stringptr,   &dmarc_used_domain },
 #endif
   { "dnslist_domain",      vtype_stringptr,   &dnslist_domain },
   { "dnslist_matched",     vtype_stringptr,   &dnslist_matched },
@@ -471,6 +480,7 @@ static var_entry var_table[] = {
 #ifdef WITH_OLD_DEMIME
   { "found_extension",     vtype_stringptr,   &found_extension },
 #endif
+  { "headers_added",       vtype_string_func, &fn_hdrs_added },
   { "home",                vtype_stringptr,   &deliver_home },
   { "host",                vtype_stringptr,   &deliver_host },
   { "host_address",        vtype_stringptr,   &deliver_host_address },
@@ -562,7 +572,7 @@ static var_entry var_table[] = {
   { "received_time",       vtype_int,         &received_time },
   { "recipient_data",      vtype_stringptr,   &recipient_data },
   { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
-  { "recipients",          vtype_recipients,  NULL },
+  { "recipients",          vtype_string_func, &fn_recipients },
   { "recipients_count",    vtype_int,         &recipients_count },
 #ifdef WITH_CONTENT_SCAN
   { "regex_match_string",  vtype_stringptr,   &regex_match_string },
@@ -570,6 +580,7 @@ static var_entry var_table[] = {
   { "reply_address",       vtype_reply,       NULL },
   { "return_path",         vtype_stringptr,   &return_path },
   { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
+  { "router_name",         vtype_stringptr,   &router_name },
   { "runrc",               vtype_int,         &runrc },
   { "self_hostname",       vtype_stringptr,   &self_hostname },
   { "sender_address",      vtype_stringptr,   &sender_address },
@@ -666,6 +677,17 @@ static var_entry var_table[] = {
   { "tod_logfile",         vtype_todlf,       NULL },
   { "tod_zone",            vtype_todzone,     NULL },
   { "tod_zulu",            vtype_todzulu,     NULL },
+#ifdef EXPERIMENTAL_TPDA
+  { "tpda_defer_errno",     vtype_int,         &tpda_defer_errno },
+  { "tpda_defer_errstr",    vtype_stringptr,   &tpda_defer_errstr },
+  { "tpda_delivery_confirmation", vtype_stringptr,   &tpda_delivery_confirmation },
+  { "tpda_delivery_domain", vtype_stringptr,   &tpda_delivery_domain },
+  { "tpda_delivery_fqdn",   vtype_stringptr,   &tpda_delivery_fqdn },
+  { "tpda_delivery_ip",     vtype_stringptr,   &tpda_delivery_ip },
+  { "tpda_delivery_local_part",vtype_stringptr,&tpda_delivery_local_part },
+  { "tpda_delivery_port",   vtype_int,         &tpda_delivery_port },
+#endif
+  { "transport_name",      vtype_stringptr,   &transport_name },
   { "value",               vtype_stringptr,   &lookup_value },
   { "version_number",      vtype_stringptr,   &version_string },
   { "warn_message_delay",  vtype_stringptr,   &warnmsg_delay },
@@ -783,8 +805,11 @@ return -1;
 
 /* This function is called to expand a string, and test the result for a "true"
 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.
+forced fail or lookup defer.
+
+We used to release all store used, but this is not not safe due
+to ${dlfunc } and ${acl }.  In any case expand_string_internal()
+is reasonably careful to release what it can.
 
 The actual false-value tests should be replicated for ECOND_BOOL_LAX.
 
@@ -800,7 +825,6 @@ BOOL
 expand_check_condition(uschar *condition, uschar *m1, uschar *m2)
 {
 int rc;
-void *reset_point = store_get(0);
 uschar *ss = expand_string(condition);
 if (ss == NULL)
   {
@@ -811,7 +835,6 @@ if (ss == NULL)
   }
 rc = ss[0] != 0 && Ustrcmp(ss, "0") != 0 && strcmpic(ss, US"no") != 0 &&
   strcmpic(ss, US"false") != 0;
-store_reset(reset_point);
 return rc;
 }
 
@@ -1446,6 +1469,34 @@ return yield;
 
 
 
+/*************************************************
+*               Return list of recipients        *
+*************************************************/
+/* A recipients list is available only during system message filtering,
+during ACL processing after DATA, and while expanding pipe commands
+generated from a system filter, but not elsewhere. */
+
+static uschar *
+fn_recipients(void)
+{
+if (!enable_dollar_recipients) return NULL; else
+  {
+  int size = 128;
+  int ptr = 0;
+  int i;
+  uschar * s = store_get(size);
+  for (i = 0; i < recipients_count; i++)
+    {
+    if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
+    s = string_cat(s, &size, &ptr, recipients_list[i].address,
+      Ustrlen(recipients_list[i].address));
+    }
+  s[ptr] = 0;     /* string_cat() leaves room */
+  return s;
+  }
+}
+
+
 /*************************************************
 *               Find value of a variable         *
 *************************************************/
@@ -1671,26 +1722,11 @@ while (last > first)
       }
     return (s == NULL)? US"" : s;
 
-    /* A recipients list is available only during system message filtering,
-    during ACL processing after DATA, and while expanding pipe commands
-    generated from a system filter, but not elsewhere. */
-
-    case vtype_recipients:
-    if (!enable_dollar_recipients) return NULL; else
+    case vtype_string_func:
       {
-      int size = 128;
-      int ptr = 0;
-      int i;
-      s = store_get(size);
-      for (i = 0; i < recipients_count; i++)
-        {
-        if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
-        s = string_cat(s, &size, &ptr, recipients_list[i].address,
-          Ustrlen(recipients_list[i].address));
-        }
-      s[ptr] = 0;     /* string_cat() leaves room */
+      uschar * (*fn)() = var_table[middle].value;
+      return fn();
       }
-    return s;
 
     case vtype_pspace:
       {
@@ -1838,6 +1874,7 @@ if (Ustrncmp(name, "acl_", 4) == 0)
 
 /*
 Load args from sub array to globals, and call acl_check().
+Sub array will be corrupted on return.
 
 Returns:       OK         access is granted by an ACCEPT verb
                DISCARD    access is granted by a DISCARD verb
@@ -1850,21 +1887,38 @@ static int
 eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
 {
 int i;
-uschar *dummy_log_msg;
+uschar *tmp;
+int sav_narg = acl_narg;
+int ret;
+extern int acl_where;
 
-for (i = 1; i < nsub && sub[i]; i++)
-  acl_arg[i-1] = sub[i];
-acl_narg = i-1;
+if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
+for (i = 0; i < nsub && sub[i+1]; i++)
+  {
+  tmp = acl_arg[i];
+  acl_arg[i] = sub[i+1];       /* place callers args in the globals */
+  sub[i+1] = tmp;              /* stash the old args using our caller's storage */
+  }
+acl_narg = i;
 while (i < nsub)
-  acl_arg[i++ - 1] = NULL;
+  {
+  sub[i+1] = acl_arg[i];
+  acl_arg[i++] = NULL;
+  }
 
 DEBUG(D_expand)
   debug_printf("expanding: acl: %s  arg: %s%s\n",
     sub[0],
-    acl_narg>0 ? sub[1]   : US"<none>",
-    acl_narg>1 ? " +more" : "");
+    acl_narg>0 ? acl_arg[0] : US"<none>",
+    acl_narg>1 ? " +more"   : "");
+
+ret = acl_eval(acl_where, sub[0], user_msgp, &tmp);
 
-return acl_check(ACL_WHERE_EXPANSION, NULL, sub[0], user_msgp, &dummy_log_msg);
+for (i = 0; i < nsub; i++)
+  acl_arg[i] = sub[i+1];       /* restore old args */
+acl_narg = sav_narg;
+
+return ret;
 }
 
 
@@ -2115,14 +2169,13 @@ switch(cond_type)
   case ECOND_ACL:
     /* ${if acl {{name}{arg1}{arg2}...}  {yes}{no}} */
     {
-    uschar *nameargs;
     uschar *user_msg;
     BOOL cond = FALSE;
     int size = 0;
     int ptr = 0;
 
     while (isspace(*s)) s++;
-    if (*s++ != '{') goto COND_FAILED_CURLY_START;
+    if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
     switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
       &s, yield == NULL, TRUE, US"acl"))
@@ -3357,12 +3410,12 @@ if (*error == NULL)
      * 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 == LLONG_MIN && op != '*')
+    if (y == -1 && x == EXIM_ARITH_MIN && op != '*')
       {
       DEBUG(D_expand)
         debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n",
-            LLONG_MIN, op, LLONG_MAX);
-      x = LLONG_MAX;
+            EXIM_ARITH_MIN, op, EXIM_ARITH_MAX);
+      x = EXIM_ARITH_MAX;
       continue;
       }
     if (op == '*')
@@ -3534,8 +3587,8 @@ $message_headers which can get very long.
 There's a problem if a ${dlfunc item has side-effects that cause allocation,
 since resetting the store at the end of the expansion will free store that was
 allocated by the plugin code as well as the slop after the expanded string. So
-we skip any resets if ${dlfunc has been used. This is an unfortunate
-consequence of string expansion becoming too powerful.
+we skip any resets if ${dlfunc has been used. The same applies for ${acl. This
+is an unfortunate consequence of string expansion becoming too powerful.
 
 Arguments:
   string         the string to be expanded
@@ -3606,9 +3659,11 @@ while (*s != 0)
     continue;
     }
 
+  /*{*/
   /* Anything other than $ is just copied verbatim, unless we are
   looking for a terminating } character. */
 
+  /*{*/
   if (ket_ends && *s == '}') break;
 
   if (*s != '$' || !honour_dollar)
@@ -3623,7 +3678,7 @@ while (*s != 0)
   names can contain any printing characters except space and colon.
   For those that don't like typing this much, "$h_" is a synonym for
   "$header_". A non-existent header yields a NULL value; nothing is
-  inserted. */
+  inserted. */ /*}*/
 
   if (isalpha((*(++s))))
     {
@@ -3710,11 +3765,11 @@ while (*s != 0)
     continue;
     }
 
-  /* Otherwise, if there's no '{' after $ it's an error. */
+  /* Otherwise, if there's no '{' after $ it's an error. */            /*}*/
 
-  if (*s != '{')
+  if (*s != '{')                                                       /*}*/
     {
-    expand_string_message = US"$ not followed by letter, digit, or {";
+    expand_string_message = US"$ not followed by letter, digit, or {"; /*}*/
     goto EXPAND_FAILED;
     }
 
@@ -3724,9 +3779,9 @@ while (*s != 0)
   if (isdigit((*(++s))))
     {
     int n;
-    s = read_number(&n, s);
+    s = read_number(&n, s);            /*{*/
     if (*s++ != '}')
-      {
+      {                                        /*{*/
       expand_string_message = US"} expected after number";
       goto EXPAND_FAILED;
       }
@@ -3738,7 +3793,7 @@ while (*s != 0)
 
   if (!isalpha(*s))
     {
-    expand_string_message = US"letter or digit expected after ${";
+    expand_string_message = US"letter or digit expected after ${";     /*}*/
     goto EXPAND_FAILED;
     }
 
@@ -3757,6 +3812,7 @@ while (*s != 0)
     acl_check_internal() directly and get a current level from somewhere.
     See also the acl expansion condition ECOND_ACL and the traditional
     acl modifier ACLC_ACL.
+    Assume that the function has side-effects on the store that must be preserved.
     */
 
     case EITEM_ACL:
@@ -3773,6 +3829,7 @@ while (*s != 0)
         }
       if (skipping) continue;
 
+      resetok = FALSE;
       switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
        {
        case OK:
@@ -3863,10 +3920,10 @@ while (*s != 0)
       Otherwise set the key NULL pro-tem. */
 
       while (isspace(*s)) s++;
-      if (*s == '{')
+      if (*s == '{')                                   /*}*/
         {
         key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
-        if (key == NULL) goto EXPAND_FAILED;
+        if (key == NULL) goto EXPAND_FAILED;           /*{*/
         if (*s++ != '}') goto EXPAND_FAILED_CURLY;
         while (isspace(*s)) s++;
         }
@@ -3882,9 +3939,9 @@ while (*s != 0)
 
       /* The type is a string that may contain special characters of various
       kinds. Allow everything except space or { to appear; the actual content
-      is checked by search_findtype_partial. */
+      is checked by search_findtype_partial. */                /*}*/
 
-      while (*s != 0 && *s != '{' && !isspace(*s))
+      while (*s != 0 && *s != '{' && !isspace(*s))     /*}*/
         {
         if (nameptr < sizeof(name) - 1) name[nameptr++] = *s;
         s++;
@@ -4032,7 +4089,7 @@ while (*s != 0)
 
     case EITEM_PERL:
     #ifndef EXIM_PERL
-    expand_string_message = US"\"${perl\" encountered, but this facility "
+    expand_string_message = US"\"${perl\" encountered, but this facility "     /*}*/
       "is not included in this binary";
     goto EXPAND_FAILED;
 
@@ -4781,7 +4838,7 @@ while (*s != 0)
       uschar *sub[3];
 
       /* "length" takes only 2 arguments whereas the others take 2 or 3.
-      Ensure that sub[2] is set in the ${length case. */
+      Ensure that sub[2] is set in the ${length case. */
 
       sub[2] = NULL;
       switch(read_subs(sub, (item_type == EITEM_LENGTH)? 2:3, 2, &s, skipping,
@@ -5214,15 +5271,15 @@ while (*s != 0)
 
       while (isspace(*s)) s++;
       if (*s++ != '}')
-        {
+        {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of condition "
           "or expression inside \"%s\"", name);
         goto EXPAND_FAILED;
         }
 
-      while (isspace(*s)) s++;
+      while (isspace(*s)) s++;                         /*{*/
       if (*s++ != '}')
-        {
+        {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of \"%s\"",
           name);
         goto EXPAND_FAILED;
@@ -5332,7 +5389,7 @@ while (*s != 0)
       }
 
 
-    /* If ${dlfunc support is configured, handle calling dynamically-loaded
+    /* If ${dlfunc support is configured, handle calling dynamically-loaded
     functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}}
     or ${dlfunc{file}{func}{arg}} or ${dlfunc{file}{func}{arg1}{arg2}} or up to
     a maximum of EXPAND_DLFUNC_MAX_ARGS arguments (defined below). */
@@ -5341,7 +5398,7 @@ while (*s != 0)
 
     case EITEM_DLFUNC:
     #ifndef EXPAND_DLFUNC
-    expand_string_message = US"\"${dlfunc\" encountered, but this facility "
+    expand_string_message = US"\"${dlfunc\" encountered, but this facility "   /*}*/
       "is not included in this binary";
     goto EXPAND_FAILED;
 
@@ -5432,7 +5489,7 @@ while (*s != 0)
         }
       }
     #endif /* EXPAND_DLFUNC */
-    }
+    }  /* EITEM_* switch */
 
   /* Control reaches here if the name is not recognized as one of the more
   complicated expansion items. Check for the "operator" syntax (name terminated
@@ -5622,6 +5679,22 @@ while (*s != 0)
         continue;
         }
 
+      /* Convert octets outside 0x21..0x7E to \xXX form */
+
+      case EOP_HEXQUOTE:
+       {
+        uschar *t = sub - 1;
+        while (*(++t) != 0)
+          {
+          if (*t < 0x21 || 0x7E < *t)
+            yield = string_cat(yield, &size, &ptr,
+             string_sprintf("\\x%02x", *t), 4);
+         else
+           yield = string_cat(yield, &size, &ptr, t, 1);
+          }
+       continue;
+       }
+
       /* count the number of list elements */
 
       case EOP_LISTCOUNT:
@@ -5646,7 +5719,7 @@ while (*s != 0)
        uschar * list;
        int sep = 0;
        uschar * item;
-       uschar * suffix = "";
+       uschar * suffix = US"";
        BOOL needsep = FALSE;
        uschar buffer[256];
 
@@ -5660,10 +5733,10 @@ while (*s != 0)
          }
        else switch(*arg)       /* specific list-type version */
          {
-         case 'a': t = tree_search(addresslist_anchor,   sub); suffix = "_a"; break;
-         case 'd': t = tree_search(domainlist_anchor,    sub); suffix = "_d"; break;
-         case 'h': t = tree_search(hostlist_anchor,      sub); suffix = "_h"; break;
-         case 'l': t = tree_search(localpartlist_anchor, sub); suffix = "_l"; break;
+         case 'a': t = tree_search(addresslist_anchor,   sub); suffix = US"_a"; break;
+         case 'd': t = tree_search(domainlist_anchor,    sub); suffix = US"_d"; break;
+         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");
            goto EXPAND_FAILED;
@@ -5707,13 +5780,13 @@ while (*s != 0)
              if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
                {
                 yield = string_cat(yield, &size, &ptr, US"::", 2);
-               item = cp;
+               item = (uschar *)cp;
                }
              else              /* sep in item; should already be doubled; emit once */
                {
                 yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
                if (*cp == sep) cp++;
-               item = cp;
+               item = (uschar *)cp;
                }
              }
            }
@@ -6276,7 +6349,7 @@ while (*s != 0)
   store instead of copying. Many expansion strings contain just one reference,
   so this is a useful optimization, especially for humungous headers
   ($message_headers). */
-
+                                               /*{*/
   if (*s++ == '}')
     {
     int len;
@@ -6480,17 +6553,17 @@ else
     default:
       break;
     case 'k':
-      if (value > LLONG_MAX/1024 || value < LLONG_MIN/1024) errno = ERANGE;
+      if (value > EXIM_ARITH_MAX/1024 || value < EXIM_ARITH_MIN/1024) errno = ERANGE;
       else value *= 1024;
       endptr++;
       break;
     case 'm':
-      if (value > LLONG_MAX/(1024*1024) || value < LLONG_MIN/(1024*1024)) errno = ERANGE;
+      if (value > EXIM_ARITH_MAX/(1024*1024) || value < EXIM_ARITH_MIN/(1024*1024)) errno = ERANGE;
       else value *= 1024*1024;
       endptr++;
       break;
     case 'g':
-      if (value > LLONG_MAX/(1024*1024*1024) || value < LLONG_MIN/(1024*1024*1024)) errno = ERANGE;
+      if (value > EXIM_ARITH_MAX/(1024*1024*1024) || value < EXIM_ARITH_MIN/(1024*1024*1024)) errno = ERANGE;
       else value *= 1024*1024*1024;
       endptr++;
       break;
@@ -6500,7 +6573,7 @@ else
   else
     {
     while (isspace(*endptr)) endptr++;
-    if (*endptr == 0) return (int)value;
+    if (*endptr == 0) return value;
     }
   }
 
@@ -6573,6 +6646,9 @@ for (i = 1; i < argc; i++)
       #ifdef LOOKUP_PGSQL
       pgsql_servers = argv[i];
       #endif
+      #ifdef EXPERIMENTAL_REDIS
+      redis_servers = argv[i];
+      #endif
       }
   #ifdef EXIM_PERL
   else opt_perl_startup = argv[i];