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 b2f9dccc07cc1ae4829fcd7a604af6f6e78e6a69..a073730c61e99427d4b613128c5244fe206c4c5c 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 b0ae9c132d44fc67349396599657d19aa2c6a106..a416b8c1fdc67bdbcc21393df6eaa8ecf0c5dbcd 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 09045a40ddb3e91775e97ce988af2781fe5a75a8..1622467edf28b650af9e1c237cca3ae3b7411b8a 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 62daff0dfbe9e3f70e4687bfb2643a84b4799ae6..7b794720fecf47536eb6458eeb46669104009ab6 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 2ddd22aa61af6cbb821dc056ce307db5be000984..74267ab0cd99dfab64e4df5f81c103cccbec58e7 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 e70e38538b6e33bdbed429c4169d57fa56646158..a7b0234b92123b0df057f37fc5620abfac8d2559 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 83d29ba9b12e22bd285a0f5029c865482c463375..18aaad9184906c64bc75aa2407dbdaafd3c7c5f2 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 ede15304159b651bd38842af62efe2e1216e4e3b..74466733e0cab1dc800d0243e088b6c5b60fa144 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 b4d229cd71c01c3ae2cfc15ca2d761198ee5548f..02ada2950ee1dd682f795c84940cbd7f61d78498 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 938db36008da6569961ef502e3a1820c1c108734..920a74a140b2139df6875862344930f7685e6df2 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 0b01d5880b4d0513d53e5649a61b0d4389be3d61..1925c49323d99b352c0e1986c1aa9dcf38f834d8 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 5026a417cea16d28753b367efc74b22f503d9da4..bf91a838873d32c0432d3f2a3fb861e4e1ae1a50 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 45e48244b940cad275c70c1c182929f0a400534a..32ccd89447737bbc12f379be2ad08fafb86eb602 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 34c6510fdce3466b092df448a6c46de638528f3d..0b3a5a62acfbcb280d806f00fd28483f51a482df 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 =