Keep router-variables separate on addrs, to avoid taint contamination
[exim.git] / src / src / route.c
index ede15304159b651bd38842af62efe2e1216e4e3b..0817a4eda62a784ba66229de589339ba68442714 100644 (file)
@@ -116,6 +116,8 @@ optionlist optionlist_routers[] = {
                  (void *)(offsetof(router_instance, self)) },
   { "senders",            opt_stringptr|opt_public,
                  (void *)offsetof(router_instance, senders) },
+  { "set",                opt_stringptr|opt_public|opt_rep_str,
+                 (void *)offsetof(router_instance, set) },
   #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
   { "translate_ip_address", opt_stringptr|opt_public,
                  (void *)offsetof(router_instance, translate_ip_address) },
@@ -1361,6 +1363,8 @@ new->prop.errors_address = parent->prop.errors_address;
 
 new->prop.ignore_error = addr->prop.ignore_error;
 new->prop.address_data = addr->prop.address_data;
+new->prop.variables = NULL;
+tree_dup((tree_node **)&new->prop.variables, addr->prop.variables);
 new->dsn_flags = addr->dsn_flags;
 new->dsn_orcpt = addr->dsn_orcpt;
 
@@ -1403,6 +1407,79 @@ if (addr->transport && tree_search(tree_nonrecipients, addr->unique))
 
 
 
+/************************************************/
+/* Add router-assigned variables
+Return OK/DEFER/FAIL/PASS */
+
+static int
+set_router_vars(address_item * addr, const router_instance * r)
+{
+const uschar * varlist = r->set;
+tree_node ** root = (tree_node **) &addr->prop.variables;
+int sep = 0;
+
+if (!varlist) return OK;
+
+/* Walk the varlist, creating variables */
+
+for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
+  {
+  const uschar * assignment = ele;
+  int esep = '=';
+  uschar * name = string_nextinlist(&assignment, &esep, NULL, 0);
+  uschar * val;
+  tree_node * node;
+
+  /* Variable name must exist and start "r_". */
+
+  if (!name || name[0] != 'r' || name[1] != '_' || !name[2])
+    return FAIL;
+  name += 2;
+
+  if (!(val = expand_string(US assignment)))
+    if (f.expand_string_forcedfail)
+      {
+      int yield;
+      BOOL more;
+      DEBUG(D_route) debug_printf("forced failure in expansion of \"%s\" "
+         "(router variable): decline action taken\n", ele);
+
+      /* Expand "more" if necessary; DEFER => an expansion failed */
+
+      yield = exp_bool(addr, US"router", r->name, D_route,
+                     US"more", r->more, r->expand_more, &more);
+      if (yield != OK) return yield;
+
+      if (!more)
+       {
+       DEBUG(D_route)
+         debug_printf("\"more\"=false: skipping remaining routers\n");
+       router_name = NULL;
+       r = NULL;
+       return FAIL;
+       }
+      return PASS;
+      }
+    else
+      {
+      addr->message = string_sprintf("expansion of \"%s\" failed "
+       "in %s router: %s", ele, r->name, expand_string_message);
+      return DEFER;
+      }
+
+  if (!(node = tree_search(*root, name)))
+    {
+    node = store_get(sizeof(tree_node) + Ustrlen(name));
+    Ustrcpy(node->name, name);
+    (void)tree_insertnode(root, node);
+    }
+  node->data.ptr = US val;
+  DEBUG(D_route) debug_printf("set r_%s = '%s'\n", name, val);
+  }
+return OK;
+}
+
+
 /*************************************************
 *                 Route one address              *
 *************************************************/
@@ -1602,6 +1679,61 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   search_error_message = NULL;
 
+  /* Add any variable-settings that are on the router, to the set on the
+  addr. Expansion is done here and not later when the addr is used.  There may
+  be multiple settings, gathered during readconf; this code gathers them during
+  router traversal.  On the addr string they are held as a variable tree, so
+  as to maintain the post-expansion taints separate. */
+
+  if ((yield = set_router_vars(addr, r)) != OK)
+    if (yield == PASS)
+      continue;                /* with next router */
+    else
+      goto ROUTE_EXIT;
+
+#ifdef notdef
+  if (r->set)
+    {
+    const uschar * list = r->set;
+    int sep = 0;
+    for (uschar * ele; (ele = string_nextinlist(&list, &sep, NULL, 0)); )
+      {
+      uschar * ee;
+      if (!(ee = expand_string(ele)))
+       if (f.expand_string_forcedfail)
+         {
+         DEBUG(D_route) debug_printf("forced failure in expansion of \"%s\" "
+             "(router variable): decline action taken\n", ele);
+
+         /* Expand "more" if necessary; DEFER => an expansion failed */
+
+         yield = exp_bool(addr, US"router", r->name, D_route,
+                         US"more", r->more, r->expand_more, &more);
+         if (yield != OK) goto ROUTE_EXIT;
+
+         if (!more)
+           {
+           DEBUG(D_route)
+             debug_printf("\"more\"=false: skipping remaining routers\n");
+           router_name = NULL;
+           r = NULL;
+           break;
+           }
+         else continue;    /* With next router */
+         }
+       else
+         {
+         addr->message = string_sprintf("expansion of \"%s\" failed "
+           "in %s router: %s", ele, r->name, expand_string_message);
+         yield = DEFER;
+         goto ROUTE_EXIT;
+         }
+
+      addr->prop.set = string_append_listele(addr->prop.set, ':', ee);
+      }
+    }
+#endif
+
   /* Finally, expand the address_data field in the router. Forced failure
   behaves as if the router declined. Any other failure is more serious. On
   success, the string is attached to the address for all subsequent processing.
@@ -1610,8 +1742,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   if (r->address_data)
     {
     DEBUG(D_route) debug_printf("processing address_data\n");
-    deliver_address_data = expand_string(r->address_data);
-    if (!deliver_address_data)
+    if (!(deliver_address_data = expand_string(r->address_data)))
       {
       if (f.expand_string_forcedfail)
         {