Expansions: mask_n operator
[exim.git] / src / src / expand.c
index e8e97dab8c4c5b9dd6944401360ab847fc581f94..83c0ad051f4daef1ea36e08d8db500922ed91bf7 100644 (file)
@@ -216,7 +216,6 @@ static uschar *op_table_main[] = {
   US"base62d",
   US"base64",
   US"base64d",
-  US"bless",
   US"domain",
   US"escape",
   US"escape8bit",
@@ -264,7 +263,6 @@ enum {
   EOP_BASE62D,
   EOP_BASE64,
   EOP_BASE64D,
-  EOP_BLESS,
   EOP_DOMAIN,
   EOP_ESCAPE,
   EOP_ESCAPE8BIT,
@@ -4303,6 +4301,98 @@ return FALSE;    /* should not happen */
 }
 
 
+/* Expand a named list.  Return false on failure. */
+static gstring *
+expand_listnamed(gstring * yield, const uschar * name, const uschar * listtype)
+{
+tree_node *t = NULL;
+const uschar * list;
+int sep = 0;
+uschar * item;
+uschar * suffix = US"";
+BOOL needsep = FALSE;
+#define LISTNAMED_BUF_SIZE 256
+uschar b[LISTNAMED_BUF_SIZE];
+uschar * buffer = b;
+
+if (*name == '+') name++;
+if (!listtype)         /* no-argument version */
+  {
+  if (  !(t = tree_search(addresslist_anchor, name))
+     && !(t = tree_search(domainlist_anchor,  name))
+     && !(t = tree_search(hostlist_anchor,    name)))
+    t = tree_search(localpartlist_anchor, name);
+  }
+else switch(*listtype) /* specific list-type version */
+  {
+  case 'a': t = tree_search(addresslist_anchor,   name); suffix = US"_a"; break;
+  case 'd': t = tree_search(domainlist_anchor,    name); suffix = US"_d"; break;
+  case 'h': t = tree_search(hostlist_anchor,      name); suffix = US"_h"; break;
+  case 'l': t = tree_search(localpartlist_anchor, name); suffix = US"_l"; break;
+  default:
+    expand_string_message = US"bad suffix on \"list\" operator";
+    return yield;
+  }
+
+if(!t)
+  {
+  expand_string_message = string_sprintf("\"%s\" is not a %snamed list",
+    name, !listtype?""
+      : *listtype=='a'?"address "
+      : *listtype=='d'?"domain "
+      : *listtype=='h'?"host "
+      : *listtype=='l'?"localpart "
+      : 0);
+  return yield;
+  }
+
+list = ((namedlist_block *)(t->data.ptr))->string;
+
+/* The list could be quite long so we (re)use a buffer for each element
+rather than getting each in new memory */
+
+if (is_tainted(list)) buffer = store_get(LISTNAMED_BUF_SIZE, TRUE);
+while ((item = string_nextinlist(&list, &sep, buffer, LISTNAMED_BUF_SIZE)))
+  {
+  uschar * buf = US" : ";
+  if (needsep)
+    yield = string_catn(yield, buf, 3);
+  else
+    needsep = TRUE;
+
+  if (*item == '+')    /* list item is itself a named list */
+    {
+    yield = expand_listnamed(yield, item, listtype);
+    if (expand_string_message)
+      return yield;
+    }
+
+  else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */
+    {
+    char tok[3];
+    tok[0] = sep; tok[1] = ':'; tok[2] = 0;
+
+    for(char * cp; cp = strpbrk(CCS item, tok); item = US cp)
+      {
+      yield = string_catn(yield, item, cp - CS item);
+      if (*cp++ == ':')        /* colon in a non-colon-sep list item, needs doubling */
+       yield = string_catn(yield, US"::", 2);
+      else             /* sep in item; should already be doubled; emit once */
+       {
+       yield = string_catn(yield, US tok, 1);
+       if (*cp == sep) cp++;
+       }
+      }
+    yield = string_cat(yield, item);
+    }
+  else
+    yield = string_cat(yield, item);
+  }
+return yield;
+}
+
+
+
 /*************************************************
 *                 Expand string                  *
 *************************************************/
@@ -4393,13 +4483,13 @@ DEBUG(D_expand)
 f.expand_string_forcedfail = FALSE;
 expand_string_message = US"";
 
-if (is_tainted(string))
+{ uschar *m;
+if ((m = is_tainted2(string, LOG_MAIN|LOG_PANIC, "Tainted string '%s' in expansion", s)))
   {
-  expand_string_message =
-    string_sprintf("attempt to expand tainted string '%s'", s);
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+  expand_string_message = m;
   goto EXPAND_FAILED;
   }
+}
 
 while (*s)
   {
@@ -6341,13 +6431,10 @@ while (*s)
       condition for real. For EITEM_MAP and EITEM_REDUCE, do the same, using
       the normal internal expansion function. */
 
-      if (item_type == EITEM_FILTER)
-        {
-        if ((temp = eval_condition(expr, &resetok, NULL)))
-         s = temp;
-        }
-      else
+      if (item_type != EITEM_FILTER)
         temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
+      else
+        if ((temp = eval_condition(expr, &resetok, NULL))) s = temp;
 
       if (!temp)
         {
@@ -6356,7 +6443,7 @@ while (*s)
         goto EXPAND_FAILED;
         }
 
-      Uskip_whitespace(&s);
+      Uskip_whitespace(&s);                            /*{*/
       if (*s++ != '}')
         {                                              /*{*/
         expand_string_message = string_sprintf("missing } at end of condition "
@@ -7020,20 +7107,6 @@ while (*s)
         continue;
         }
 
-      case EOP_BLESS:
-       /* This is purely for the convenience of the test harness.  Do not enable
-       it otherwise as it defeats the taint-checking security. */
-
-       if (f.running_in_test_harness)
-         yield = string_cat(yield, is_tainted(sub)
-                                   ? string_copy_taint(sub, FALSE) : sub);
-       else
-         {
-         DEBUG(D_expand) debug_printf_indent("bless operator not supported\n");
-         yield = string_cat(yield, sub);
-         }
-       continue;
-
       case EOP_EXPAND:
         {
         uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok);
@@ -7232,10 +7305,10 @@ while (*s)
 
       case EOP_LISTCOUNT:
         {
-       int cnt = 0;
-       int sep = 0;
+       int cnt = 0, sep = 0;
+       uschar * buf = store_get(2, is_tainted(sub));
 
-       while (string_nextinlist(CUSS &sub, &sep, NULL, 0)) cnt++;
+       while (string_nextinlist(CUSS &sub, &sep, buf, 1)) cnt++;
        yield = string_fmt_append(yield, "%d", cnt);
         continue;
         }
@@ -7244,92 +7317,11 @@ while (*s)
       /* handles nested named lists; requotes as colon-sep list */
 
       case EOP_LISTNAMED:
-       {
-       tree_node *t = NULL;
-       const uschar * list;
-       int sep = 0;
-       uschar * item;
-       uschar * suffix = US"";
-       BOOL needsep = FALSE;
-#define LISTNAMED_BUF_SIZE 256
-       uschar b[LISTNAMED_BUF_SIZE];
-       uschar * buffer = b;
-
-       if (*sub == '+') sub++;
-       if (!arg)               /* no-argument version */
-         {
-         if (!(t = tree_search(addresslist_anchor, sub)) &&
-             !(t = tree_search(domainlist_anchor,  sub)) &&
-             !(t = tree_search(hostlist_anchor,    sub)))
-           t = tree_search(localpartlist_anchor, sub);
-         }
-       else switch(*arg)       /* specific list-type version */
-         {
-         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 = US"bad suffix on \"list\" operator";
-           goto EXPAND_FAILED;
-         }
-
-       if(!t)
-         {
-          expand_string_message = string_sprintf("\"%s\" is not a %snamed list",
-            sub, !arg?""
-             : *arg=='a'?"address "
-             : *arg=='d'?"domain "
-             : *arg=='h'?"host "
-             : *arg=='l'?"localpart "
-             : 0);
+       expand_string_message = NULL;
+       yield = expand_listnamed(yield, sub, arg);
+       if (expand_string_message)
          goto EXPAND_FAILED;
-         }
-
-       list = ((namedlist_block *)(t->data.ptr))->string;
-
-       /* The list could be quite long so we (re)use a buffer for each element
-       rather than getting each in new memory */
-
-       if (is_tainted(list)) buffer = store_get(LISTNAMED_BUF_SIZE, TRUE);
-       while ((item = string_nextinlist(&list, &sep, buffer, LISTNAMED_BUF_SIZE)))
-         {
-         uschar * buf = US" : ";
-         if (needsep)
-           yield = string_catn(yield, buf, 3);
-         else
-           needsep = TRUE;
-
-         if (*item == '+')     /* list item is itself a named list */
-           {
-           uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item);
-           item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE, &resetok);
-           }
-         else if (sep != ':')  /* item from non-colon-sep list, re-quote for colon list-separator */
-           {
-           char * cp;
-           char tok[3];
-           tok[0] = sep; tok[1] = ':'; tok[2] = 0;
-           while ((cp= strpbrk(CCS item, tok)))
-             {
-              yield = string_catn(yield, item, cp - CS item);
-             if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
-               {
-                yield = string_catn(yield, US"::", 2);
-               item = US cp;
-               }
-             else              /* sep in item; should already be doubled; emit once */
-               {
-                yield = string_catn(yield, US tok, 1);
-               if (*cp == sep) cp++;
-               item = US cp;
-               }
-             }
-           }
-          yield = string_cat(yield, item);
-         }
         continue;
-       }
 
       /* quote a list-item for the given list-separator */
 
@@ -7341,11 +7333,11 @@ while (*s)
         int count;
         uschar *endptr;
         int binary[4];
-        int mask, maskoffset;
-        int type = string_is_ip_address(sub, &maskoffset);
+        int type, mask, maskoffset;
+       BOOL normalised;
         uschar buffer[64];
 
-        if (type == 0)
+        if ((type = string_is_ip_address(sub, &maskoffset)) == 0)
           {
           expand_string_message = string_sprintf("\"%s\" is not an IP address",
            sub);
@@ -7361,13 +7353,18 @@ while (*s)
 
         mask = Ustrtol(sub + maskoffset + 1, &endptr, 10);
 
-        if (*endptr != 0 || mask < 0 || mask > ((type == 4)? 32 : 128))
+        if (*endptr || mask < 0 || mask > (type == 4 ? 32 : 128))
           {
           expand_string_message = string_sprintf("mask value too big in \"%s\"",
             sub);
           goto EXPAND_FAILED;
           }
 
+       /* If an optional 'n' was given, ipv6 gets normalised output:
+       colons rather than dots, and zero-compressed. */
+
+       normalised = arg && *arg == 'n';
+
         /* Convert the address to binary integer(s) and apply the mask */
 
         sub[maskoffset] = 0;
@@ -7376,8 +7373,14 @@ while (*s)
 
         /* Convert to masked textual format and add to output. */
 
-        yield = string_catn(yield, buffer,
-          host_nmtoa(count, binary, mask, buffer, '.'));
+       if (type == 4 || !normalised)
+         yield = string_catn(yield, buffer,
+           host_nmtoa(count, binary, mask, buffer, '.'));
+       else
+         {
+         ipv6_nmtoa(binary, buffer);
+         yield = string_fmt_append(yield, "%s/%d", buffer, mask);
+         }
         continue;
         }
 
@@ -7653,10 +7656,12 @@ while (*s)
        /* Manually track tainting, as we deal in individual chars below */
 
        if (is_tainted(sub))
+          {
          if (yield->s && yield->ptr)
            gstring_rebuffer(yield);
          else
            yield->s = store_get(yield->size = Ustrlen(sub), TRUE);
+          }
 
        /* Check the UTF-8, byte-by-byte */
 
@@ -8217,6 +8222,7 @@ that is a bad idea, because expand_string_message is in dynamic store. */
 EXPAND_FAILED:
 if (left) *left = s;
 DEBUG(D_expand)
+  {
   DEBUG(D_noutf8)
     {
     debug_printf_indent("|failed to expand: %s\n", string);
@@ -8236,6 +8242,7 @@ DEBUG(D_expand)
     if (f.expand_string_forcedfail)
       debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
     }
+  }
 if (resetok_p && !resetok) *resetok_p = FALSE;
 expand_level--;
 return NULL;
@@ -8630,6 +8637,7 @@ debug_selector = D_v;
 debug_file = stderr;
 debug_fd = fileno(debug_file);
 big_buffer = malloc(big_buffer_size);
+store_init();
 
 for (int i = 1; i < argc; i++)
   {