From fa7b17bdbc8c055c475a50791627cd75d257f4f3 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Mon, 8 Jul 2019 17:34:47 +0100 Subject: [PATCH] Routers: named variables --- doc/doc-docbook/spec.xfpt | 35 +++++++++++++++++++++++ doc/doc-txt/NewStuff | 3 ++ doc/doc-txt/OptionLists.txt | 1 + src/src/deliver.c | 42 +++++++++++++++++++++++++++ src/src/expand.c | 9 ++++-- src/src/globals.c | 3 ++ src/src/globals.h | 1 + src/src/route.c | 52 ++++++++++++++++++++++++++++++++-- src/src/routers/queryprogram.c | 1 + src/src/routers/redirect.c | 1 + src/src/structs.h | 2 ++ src/src/verify.c | 2 ++ test/confs/0620 | 43 ++++++++++++++++++++++++++++ test/log/0620 | 3 ++ test/mail/0620.b | 13 +++++++++ test/scripts/0000-Basic/0620 | 2 ++ test/stdout/0147 | 1 + test/stdout/0442 | 1 + 18 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 test/confs/0620 create mode 100644 test/log/0620 create mode 100644 test/mail/0620.b create mode 100644 test/scripts/0000-Basic/0620 diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index b2f9dccc0..a073730c6 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -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" diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index b0ae9c132..a416b8c1f 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -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 -------------- diff --git a/doc/doc-txt/OptionLists.txt b/doc/doc-txt/OptionLists.txt index 09045a40d..1622467ed 100644 --- a/doc/doc-txt/OptionLists.txt +++ b/doc/doc-txt/OptionLists.txt @@ -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 diff --git a/src/src/deliver.c b/src/src/deliver.c index 62daff0df..7b794720f 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -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 */ diff --git a/src/src/expand.c b/src/src/expand.c index 2ddd22aa6..74267ab0c 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -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""; } diff --git a/src/src/globals.c b/src/src/globals.c index e70e38538..a7b0234b9 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -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; diff --git a/src/src/globals.h b/src/src/globals.h index 83d29ba9b..18aaad918 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -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} */ diff --git a/src/src/route.c b/src/src/route.c index ede153041..74466733e 100644 --- a/src/src/route.c +++ b/src/src/route.c @@ -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) { diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c index b4d229cd7..02ada2950 100644 --- a/src/src/routers/queryprogram.c +++ b/src/src/routers/queryprogram.c @@ -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; diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c index 938db3600..920a74a14 100644 --- a/src/src/routers/redirect.c +++ b/src/src/routers/redirect.c @@ -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; diff --git a/src/src/structs.h b/src/src/structs.h index 0b01d5880..1925c4932 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -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 */ diff --git a/src/src/verify.c b/src/src/verify.c index 5026a417c..bf91a8388 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -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 index 000000000..b1f48c40e --- /dev/null +++ b/test/confs/0620 @@ -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 index 000000000..8f37c5fc7 --- /dev/null +++ b/test/log/0620 @@ -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 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 index 000000000..11db13d23 --- /dev/null +++ b/test/mail/0620.b @@ -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 ) + id 10HmaX-0005vi-00 + for a@test.ex; Tue, 2 Mar 1999 09:44:33 +0000 +Message-Id: +From: CALLER_NAME +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 index 000000000..0f662f153 --- /dev/null +++ b/test/scripts/0000-Basic/0620 @@ -0,0 +1,2 @@ +# router variables +exim -odi a diff --git a/test/stdout/0147 b/test/stdout/0147 index 45e48244b..32ccd8944 100644 --- a/test/stdout/0147 +++ b/test/stdout/0147 @@ -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 = diff --git a/test/stdout/0442 b/test/stdout/0442 index 34c6510fd..0b3a5a62a 100644 --- a/test/stdout/0442 +++ b/test/stdout/0442 @@ -38,6 +38,7 @@ no_retry_use_local_part router_home_directory = self = freeze senders = +set = transport = t1 transport_current_directory = transport_home_directory = -- 2.25.1