Routers: named variables
authorJeremy Harris <jgh146exb@wizmail.org>
Mon, 8 Jul 2019 16:34:47 +0000 (17:34 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Mon, 8 Jul 2019 16:34:47 +0000 (17:34 +0100)
18 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
doc/doc-txt/OptionLists.txt
src/src/deliver.c
src/src/expand.c
src/src/globals.c
src/src/globals.h
src/src/route.c
src/src/routers/queryprogram.c
src/src/routers/redirect.c
src/src/structs.h
src/src/verify.c
test/confs/0620 [new file with mode: 0644]
test/log/0620 [new file with mode: 0644]
test/mail/0620.b [new file with mode: 0644]
test/scripts/0000-Basic/0620 [new file with mode: 0644]
test/stdout/0147
test/stdout/0442

index b2f9dcc..a073730 100644 (file)
@@ -12759,6 +12759,16 @@ or if not set, the value of &$qualify_domain$&.
 .cindex queues named
 The name of the spool queue in use; empty for the default queue.
 
+.new
+.vitem &$r_...$&
+.vindex &$r_...$&
+.cindex router variables
+Values can be placed in these variables by the &%set%& option of a router.
+They can be given any name that starts with &$r_$&.
+The values persist for the address being handled through subsequent routers
+and the eventual transport.
+.wen
+
 .vitem &$rcpt_count$&
 .vindex "&$rcpt_count$&"
 When a message is being received by SMTP, this variable contains the number of
@@ -18078,6 +18088,10 @@ file = ${extract{mailbox}{$address_data}}
 This makes the configuration file less messy, and also reduces the number of
 lookups (though Exim does cache lookups).
 
+.new
+See also the &%set%& option below.
+.wen
+
 .vindex "&$sender_address_data$&"
 .vindex "&$address_data$&"
 The &%address_data%& facility is also useful as a means of passing information
@@ -18992,6 +19006,27 @@ SMTP VRFY command is enabled, it must be used after MAIL if the sender address
 matters.
 
 
+.new
+.option set routers string unset
+.cindex router variables
+This option may be used multiple times on a router.
+Each string given must be of the form $"name = value"$
+and the names used must start with the string &"r_"&.
+Strings are accumulated for each router which is run.
+When a router runs, the strings are evaluated in order,
+to create variables.
+The variable is set with the expansion of the value.
+The variables can be used by the router options
+(not including any preconditions)
+and by the transport.
+Later definitions of a given named variable will override former ones.
+Varible use is via the usual &$r_...$& syntax.
+
+This is similar to the &%address_data%& option, except that
+many independent variables can be used, with choice of naming.
+.wen
+
+
 .option translate_ip_address routers string&!! unset
 .cindex "IP address" "translating"
 .cindex "packet radio"
index b0ae9c1..a416b8c 100644 (file)
@@ -28,6 +28,9 @@ Version 4.93
 
  8. Expansion operator ${sha2_N:} for N=256, 384, 512.
 
+ 9. Router variables, $r_... settable from router options and usable in routers
+    and transports.
+
 
 Version 4.92
 --------------
index 09045a4..1622467 100644 (file)
@@ -512,6 +512,7 @@ server_scram_salt                    string*         unset         gsasl
 server_secret                        string*         unset         cram_md5          3.10
 server_service                       string          "smtp"  cyrus_sasl,gsasl,heimdal_gssapi  (cyrus-only) 4.80 (others)
 server_set_id                        string*         unset         authenticators    3.10
+set                                 string*         unset         routers           4.93
 shadow_condition                     string*         unset         transports
 shadow_transport                     string          unset         transports
 size_addition                        integer         1024          smtp              1.91
index 62daff0..7b79472 100644 (file)
@@ -155,6 +155,47 @@ return addr;
 
 
 
+/************************************************/
+/* Set router-assigned variables, forgetting any previous.
+Return FALSE on failure */
+
+static BOOL
+set_router_vars(gstring * g_varlist)
+{
+const uschar * varlist;
+int sep = 0;
+
+router_var = NULL;
+if (!g_varlist) return TRUE;
+varlist = CUS string_from_gstring(g_varlist);
+
+/* 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);
+  tree_node * node, ** root = &router_var;
+
+  /* Variable name must exist and start "r_". */
+
+  if (!name || name[0] != 'r' || name[1] != '_' || !name[2])
+    return FALSE;
+  name += 2;
+
+  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 assignment;
+  }
+return TRUE;
+}
+
+
 /*************************************************
 *     Set expansion values for an address        *
 *************************************************/
@@ -198,6 +239,7 @@ deliver_recipients = addr;
 deliver_address_data = addr->prop.address_data;
 deliver_domain_data = addr->prop.domain_data;
 deliver_localpart_data = addr->prop.localpart_data;
+set_router_vars(addr->prop.set);       /*XXX failure cases? */
 
 /* These may be unset for multiple addresses */
 
index 2ddd22a..74267ab 100644 (file)
@@ -1773,8 +1773,13 @@ 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);
+  tree_node * node =
+    tree_search(name[4] == 'c' ? acl_var_c : acl_var_m, name + 4);
+  return node ? node->data.ptr : strict_acl_vars ? NULL : US"";
+  }
+else if (Ustrncmp(name, "r_", 2) == 0)
+  {
+  tree_node * node = tree_search(router_var, name + 2);
   return node ? node->data.ptr : strict_acl_vars ? NULL : US"";
   }
 
index e70e385..a7b0234 100644 (file)
@@ -585,6 +585,7 @@ address_item address_defaults = {
     .errors_address =  NULL,
     .extra_headers =   NULL,
     .remove_headers =  NULL,
+    .set =             NULL,
 #ifdef EXPERIMENTAL_SRS
     .srs_sender =      NULL,
 #endif
@@ -1340,6 +1341,7 @@ router_instance  router_defaults = {
     .retry_use_local_part =    TRUE_UNSET,
     .same_domain_copy_routing =        FALSE,
     .self_rewrite =            FALSE,
+    .set =                     NULL,
     .suffix_optional =         FALSE,
     .verify_only =             FALSE,
     .verify_recipient =                TRUE,
@@ -1361,6 +1363,7 @@ router_instance  router_defaults = {
 };
 
 uschar *router_name            = NULL;
+tree_node *router_var         = NULL;
 
 ip_address_item *running_interfaces = NULL;
 
index 83d29ba..18aaad9 100644 (file)
@@ -859,6 +859,7 @@ extern router_info routers_available[];/* Vector of available routers */
 extern router_instance *routers;       /* Chain of instantiated routers */
 extern router_instance router_defaults;/* Default values */
 extern uschar *router_name;            /* Name of router last started */
+extern tree_node *router_var;         /* Variables set by router */
 extern ip_address_item *running_interfaces; /* Host's running interfaces */
 extern uschar *running_status;         /* Flag string for testing */
 extern int     runrc;                  /* rc from ${run} */
index ede1530..7446673 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,7 @@ 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.set  = addr->prop.set;
 new->dsn_flags = addr->dsn_flags;
 new->dsn_orcpt = addr->dsn_orcpt;
 
@@ -1602,6 +1605,52 @@ 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 list 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. */
+
+  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);
+      }
+    }
+
   /* 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 +1659,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)
         {
index b4d229c..02ada29 100644 (file)
@@ -232,6 +232,7 @@ errors address and extra header stuff. */
 
 bzero(&addr_prop, sizeof(addr_prop));
 addr_prop.address_data = deliver_address_data;
+addr_prop.set = addr->prop.set;
 
 rc = rf_get_errors_address(addr, rblock, verify, &addr_prop.errors_address);
 if (rc != OK) return rc;
index 938db36..920a74a 100644 (file)
@@ -563,6 +563,7 @@ addr_prop.localpart_data = deliver_localpart_data;
 addr_prop.errors_address = NULL;
 addr_prop.extra_headers = NULL;
 addr_prop.remove_headers = NULL;
+addr_prop.set = addr->prop.set;
 
 #ifdef EXPERIMENTAL_SRS
 addr_prop.srs_sender = NULL;
index 0b01d58..1925c49 100644 (file)
@@ -333,6 +333,7 @@ typedef struct router_instance {
   BOOL    retry_use_local_part;   /* Just what it says */
   BOOL    same_domain_copy_routing; /* TRUE => copy routing for same domain */
   BOOL    self_rewrite;           /* TRUE to rewrite headers if making local */
+  uschar *set;                   /* Variable = value to set; list */
   BOOL    suffix_optional;        /* As it says */
   BOOL    verify_only;            /* Skip this router if not verifying */
   BOOL    verify_recipient;       /* Use this router when verifying a recipient*/
@@ -510,6 +511,7 @@ typedef struct address_item_propagated {
   uschar *errors_address;         /* where to send errors (NULL => sender) */
   header_line *extra_headers;     /* additional headers */
   uschar *remove_headers;         /* list of those to remove */
+  gstring *set;                          /* list of variables, with values */
 
   #ifdef EXPERIMENTAL_SRS
   uschar *srs_sender;             /* Change return path when delivering */
index 5026a41..bf91a83 100644 (file)
@@ -1529,6 +1529,7 @@ if (addr != vaddr)
   vaddr->basic_errno = addr->basic_errno;
   vaddr->more_errno = addr->more_errno;
   vaddr->prop.address_data = addr->prop.address_data;
+  vaddr->prop.set = addr->prop.set;
   copyflag(vaddr, addr, af_pass_message);
   }
 return yield;
@@ -2089,6 +2090,7 @@ while (addr_new)
       of $address_data to be that of the child */
 
       vaddr->prop.address_data = addr->prop.address_data;
+      vaddr->prop.set = addr->prop.set;
 
       /* If stopped because more than one new address, cannot cutthrough */
 
diff --git a/test/confs/0620 b/test/confs/0620
new file mode 100644 (file)
index 0000000..b1f48c4
--- /dev/null
@@ -0,0 +1,43 @@
+# Exim test configuration 0166
+
+.include DIR/aux-var/std_conf_prefix
+
+
+# ----- Main settings -----
+
+domainlist local_domains = test.ex
+qualify_domain = test.ex
+
+
+# ----- Routers -----
+
+begin routers
+
+alias:
+  driver =     redirect
+  debug_print = DEBUG: $r_r1 $r_r2
+  data =       b
+  set =                r_r1 = $local_part
+
+user:
+  driver =     accept
+  debug_print = DEBUG: $r_r1 $r_r2
+  set =                r_r1 = $local_part
+  set =                r_r2 = $local_part
+  transport =  local_delivery
+
+
+# ----- Transports -----
+
+begin transports
+
+local_delivery:
+  driver = appendfile
+  envelope_to_add
+  file = DIR/test-mail/$local_part
+  user = CALLER
+  debug_print = DEBUG: $r_r1 $r_r2
+  headers_add =        X-r1: $r_r1\nX-r2: $r_r2
+
+
+# End
diff --git a/test/log/0620 b/test/log/0620
new file mode 100644 (file)
index 0000000..8f37c5f
--- /dev/null
@@ -0,0 +1,3 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => b <a@test.ex> R=user T=local_delivery
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
diff --git a/test/mail/0620.b b/test/mail/0620.b
new file mode 100644 (file)
index 0000000..11db13d
--- /dev/null
@@ -0,0 +1,13 @@
+From CALLER@test.ex Tue Mar 02 09:44:33 1999
+Envelope-to: a@test.ex
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+       (envelope-from <CALLER@test.ex>)
+       id 10HmaX-0005vi-00
+       for a@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaX-0005vi-00@the.local.host.name>
+From: CALLER_NAME <CALLER@test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+X-r1:  b
+X-r2:  b
+
+
diff --git a/test/scripts/0000-Basic/0620 b/test/scripts/0000-Basic/0620
new file mode 100644 (file)
index 0000000..0f662f1
--- /dev/null
@@ -0,0 +1,2 @@
+# router variables
+exim -odi a
index 45e4824..32ccd89 100644 (file)
@@ -53,6 +53,7 @@ no_retry_use_local_part
 router_home_directory = new macro2 + 1234
 self = freeze
 senders = 
+set = 
 transport = T1
 transport_current_directory = 
 transport_home_directory = 
index 34c6510..0b3a5a6 100644 (file)
@@ -38,6 +38,7 @@ no_retry_use_local_part
 router_home_directory = 
 self = freeze
 senders = 
+set = 
 transport = t1
 transport_current_directory = 
 transport_home_directory =