/************************************************* * Exim - an Internet mail transport agent * *************************************************/ /* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "../exim.h" #ifdef ROUTER_REDIRECT /* Remainder of file */ #include "rf_functions.h" #include "redirect.h" /* Options specific to the redirect router. */ #define LOFF(field) OPT_OFF(redirect_router_options_block, field) optionlist redirect_router_options[] = { { "allow_defer", opt_bit | (RDON_DEFER << 16), LOFF(bit_options) }, { "allow_fail", opt_bit | (RDON_FAIL << 16), LOFF(bit_options) }, { "allow_filter", opt_bit | (RDON_FILTER << 16), LOFF(bit_options) }, { "allow_freeze", opt_bit | (RDON_FREEZE << 16), LOFF(bit_options) }, { "check_ancestor", opt_bool, LOFF(check_ancestor) }, { "check_group", opt_bool, LOFF(check_group) }, { "check_owner", opt_bool, LOFF(check_owner) }, { "data", opt_stringptr, LOFF(data) }, { "directory_transport",opt_stringptr, LOFF(directory_transport_name) }, { "file", opt_stringptr, LOFF(file) }, { "file_transport", opt_stringptr, LOFF(file_transport_name) }, { "filter_prepend_home",opt_bit | (RDON_PREPEND_HOME << 16), LOFF(bit_options) }, { "forbid_blackhole", opt_bit | (RDON_BLACKHOLE << 16), LOFF(bit_options) }, { "forbid_exim_filter", opt_bit | (RDON_EXIM_FILTER << 16), LOFF(bit_options) }, { "forbid_file", opt_bool, LOFF(forbid_file) }, { "forbid_filter_dlfunc", opt_bit | (RDON_DLFUNC << 16), LOFF(bit_options) }, { "forbid_filter_existstest", opt_bit | (RDON_EXISTS << 16), LOFF(bit_options) }, { "forbid_filter_logwrite",opt_bit | (RDON_LOG << 16), LOFF(bit_options) }, { "forbid_filter_lookup", opt_bit | (RDON_LOOKUP << 16), LOFF(bit_options) }, { "forbid_filter_perl", opt_bit | (RDON_PERL << 16), LOFF(bit_options) }, { "forbid_filter_readfile", opt_bit | (RDON_READFILE << 16), LOFF(bit_options) }, { "forbid_filter_readsocket", opt_bit | (RDON_READSOCK << 16), LOFF(bit_options) }, { "forbid_filter_reply",opt_bool, LOFF(forbid_filter_reply) }, { "forbid_filter_run", opt_bit | (RDON_RUN << 16), LOFF(bit_options) }, { "forbid_include", opt_bit | (RDON_INCLUDE << 16), LOFF(bit_options) }, { "forbid_pipe", opt_bool, LOFF(forbid_pipe) }, { "forbid_sieve_filter",opt_bit | (RDON_SIEVE_FILTER << 16), LOFF(bit_options) }, { "forbid_smtp_code", opt_bool, LOFF(forbid_smtp_code) }, { "hide_child_in_errmsg", opt_bool, LOFF( hide_child_in_errmsg) }, { "ignore_eacces", opt_bit | (RDON_EACCES << 16), LOFF(bit_options) }, { "ignore_enotdir", opt_bit | (RDON_ENOTDIR << 16), LOFF(bit_options) }, { "include_directory", opt_stringptr, LOFF( include_directory) }, { "modemask", opt_octint, LOFF(modemask) }, { "one_time", opt_bool, LOFF(one_time) }, { "owners", opt_uidlist, LOFF(owners) }, { "owngroups", opt_gidlist, LOFF(owngroups) }, { "pipe_transport", opt_stringptr, LOFF(pipe_transport_name) }, { "qualify_domain", opt_stringptr, LOFF(qualify_domain) }, { "qualify_preserve_domain", opt_bool, LOFF(qualify_preserve_domain) }, { "repeat_use", opt_bool | opt_public, OPT_OFF(router_instance, repeat_use) }, { "reply_transport", opt_stringptr, LOFF(reply_transport_name) }, { "rewrite", opt_bit | (RDON_REWRITE << 16), LOFF(bit_options) }, { "sieve_enotify_mailto_owner", opt_stringptr, LOFF(sieve_enotify_mailto_owner) }, { "sieve_inbox", opt_stringptr, LOFF(sieve_inbox) }, { "sieve_subaddress", opt_stringptr, LOFF(sieve_subaddress) }, { "sieve_useraddress", opt_stringptr, LOFF(sieve_useraddress) }, { "sieve_vacation_directory", opt_stringptr, LOFF(sieve_vacation_directory) }, { "skip_syntax_errors", opt_bool, LOFF(skip_syntax_errors) }, { "syntax_errors_text", opt_stringptr, LOFF(syntax_errors_text) }, { "syntax_errors_to", opt_stringptr, LOFF(syntax_errors_to) } }; /* Size of the options list. An extern variable has to be used so that its address can appear in the tables drtables.c. */ int redirect_router_options_count = sizeof(redirect_router_options)/sizeof(optionlist); #ifdef MACRO_PREDEF /* Dummy entries */ redirect_router_options_block redirect_router_option_defaults = {0}; void redirect_router_init(driver_instance *rblock) {} int redirect_router_entry(router_instance *rblock, address_item *addr, struct passwd *pw, int verify, address_item **addr_local, address_item **addr_remote, address_item **addr_new, address_item **addr_succeed) {return 0;} #else /*!MACRO_PREDEF*/ /* Default private options block for the redirect router. Unlisted elements are 0/NULL/FALSE */ redirect_router_options_block redirect_router_option_defaults = { .modemask = 022, .bit_options = RDO_REWRITE | RDO_PREPEND_HOME, .check_owner = TRUE_UNSET, .check_group = TRUE_UNSET, }; /************************************************* * Initialization entry point * *************************************************/ /* Called for each instance, after its options have been read, to enable consistency checks to be done, or anything else that needs to be set up. */ void redirect_router_init(driver_instance * r) { router_instance * rblock = (router_instance *)r; redirect_router_options_block * ob = (redirect_router_options_block *)(r->options_block); /* Either file or data must be set, but not both */ if ((ob->file == NULL) == (ob->data == NULL)) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " "%sone of \"file\" or \"data\" must be specified", r->name, ob->file ? "only " : ""); /* Onetime aliases can only be real addresses. Headers can't be manipulated. The combination of one_time and unseen is not allowed. We can't check the expansion of "unseen" here, but we assume that if it is set to anything other than false, there is likely to be a problem. */ if (ob->one_time) { ob->forbid_pipe = ob->forbid_file = ob->forbid_filter_reply = TRUE; if (rblock->extra_headers || rblock->remove_headers) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " "\"headers_add\" and \"headers_remove\" are not permitted with " "\"one_time\"", r->name); if (rblock->unseen || rblock->expand_unseen) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " "\"unseen\" may not be used with \"one_time\"", r->name); } /* The defaults for check_owner and check_group depend on other settings. The defaults are: Check the owner if check_local_user or owners is set; check the group if check_local_user is set without a restriction on the group write bit, or if owngroups is set. */ if (ob->check_owner == TRUE_UNSET) ob->check_owner = rblock->check_local_user || (ob->owners && ob->owners[0] != 0); if (ob->check_group == TRUE_UNSET) ob->check_group = (rblock->check_local_user && (ob->modemask & 020) == 0) || (ob->owngroups != NULL && ob->owngroups[0] != 0); /* If explicit qualify domain set, the preserve option is locked out */ if (ob->qualify_domain && ob->qualify_preserve_domain) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " "only one of \"qualify_domain\" or \"qualify_preserve_domain\" must be set", r->name); /* If allow_filter is set, either user or check_local_user must be set. */ if (!rblock->check_local_user && !rblock->uid_set && rblock->expand_uid == NULL && (ob->bit_options & RDO_FILTER) != 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " "\"user\" or \"check_local_user\" must be set with \"allow_filter\"", r->name); } /************************************************* * Get errors address and header mods * *************************************************/ /* This function is called when new addresses are generated, in order to sort out errors address and header modifications. We put the errors address into the parent address (even though it is never used from there because that address is never transported) so that it can be retrieved if any of the children gets routed by an "unseen" router. The clone of the child that is passed on must have the original errors_address value. Arguments: rblock the router control block addr the address being routed verify v_none/v_recipient/v_sender/v_expn addr_prop point to the propagated block, which is where the new values are to be placed Returns: the result of rf_get_errors_address() or rf_get_munge_headers(), which is either OK or DEFER */ static int sort_errors_and_headers(router_instance *rblock, address_item *addr, int verify, address_item_propagated *addr_prop) { int frc = rf_get_errors_address(addr, rblock, verify, &addr_prop->errors_address); if (frc != OK) return frc; addr->prop.errors_address = addr_prop->errors_address; return rf_get_munge_headers(addr, rblock, &addr_prop->extra_headers, &addr_prop->remove_headers); } /************************************************* * Process a set of generated new addresses * *************************************************/ /* This function sets up a set of newly generated child addresses and puts them on the new address chain. Copy in the uid, gid and permission flags for use by pipes and files, set the parent, and "or" its af_ignore_error flag. Also record the setting for any starting router. If the generated address is the same as one of its ancestors, and the check_ancestor flag is set, do not use this generated address, but replace it with a copy of the input address. This is to cope with cases where A is aliased to B and B has a .forward file pointing to A, though it is usually set on the forwardfile rather than the aliasfile. We can't just pass on the old address by returning FAIL, because it must act as a general parent for generated addresses, and only get marked "done" when all its children are delivered. Arguments: rblock router block addr_new new address chain addr original address generated list of generated addresses addr_prop the propagated block, containing the errors_address, header modification stuff, and address_data ugidptr points to uid/gid data for files, pipes, autoreplies pw password entry, set if ob->check_local_user is TRUE Returns: nothing */ static void add_generated(router_instance *rblock, address_item **addr_new, address_item *addr, address_item *generated, address_item_propagated *addr_prop, ugid_block *ugidptr, struct passwd *pw) { redirect_router_options_block * ob = (redirect_router_options_block *)(rblock->drinst.options_block); while (generated) { address_item * next = generated, * parent; const uschar * errors_address = next->prop.errors_address; generated = next->next; next->parent = addr; next->start_router = rblock->redirect_router; if (addr->child_count == USHRT_MAX) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d " "child addresses for <%s>", rblock->drinst.name, USHRT_MAX, addr->address); addr->child_count++; next->next = *addr_new; *addr_new = next; /* Don't do the "one_time" thing for the first pass of a 2-stage queue run. */ if (ob->one_time && !f.queue_2stage) { for (parent = addr; parent->parent; parent = parent->parent) ; next->onetime_parent = parent->address; } if (ob->hide_child_in_errmsg) setflag(next, af_hide_child); /* If check_ancestor is set, we want to know if any ancestor of this address is the address we are about to generate. The check must be done caselessly unless the ancestor was routed by a case-sensitive router. */ if (ob->check_ancestor) for (parent = addr; parent; parent = parent->parent) if ((parent->router && parent->router->caseful_local_part ? Ustrcmp(next->address, parent->address) : strcmpic(next->address, parent->address) ) == 0) { DEBUG(D_route) debug_printf("generated parent replaced by child\n"); next->address = string_copy(addr->address); break; } /* A user filter may, under some circumstances, set up an errors address. If so, we must take care to re-instate it when we copy in the propagated data so that it overrides any errors_to setting on the router. */ { BOOL ignore_error = next->prop.ignore_error; next->prop = *addr_prop; next->prop.ignore_error = ignore_error || addr->prop.ignore_error; } if (errors_address) next->prop.errors_address = errors_address; /* For pipes, files, and autoreplies, record this router as handling them, because they don't go through the routing process again. Then set up uid, gid, home and current directories for transporting. */ if (testflag(next, af_pfr)) { next->router = rblock; rf_set_ugid(next, ugidptr); /* Will contain pw values if not overridden */ /* When getting the home directory out of the password information, wrap it in \N...\N to avoid expansion later. In Cygwin, home directories can contain $ characters. */ if (rblock->home_directory) next->home_dir = rblock->home_directory; else if (rblock->check_local_user) next->home_dir = string_sprintf("\\N%s\\N", pw->pw_dir); else if (rblock->router_home_directory && testflag(addr, af_home_expanded)) { next->home_dir = deliver_home; setflag(next, af_home_expanded); } next->current_dir = rblock->current_directory; /* Permission options */ if (!ob->forbid_pipe) setflag(next, af_allow_pipe); if (!ob->forbid_file) setflag(next, af_allow_file); if (!ob->forbid_filter_reply) setflag(next, af_allow_reply); /* If the transport setting fails, the error gets picked up at the outer level from the setting of basic_errno in the address. */ if (next->address[0] == '|') { address_pipe = next->address; GET_OPTION("pipe_transport"); if (rf_get_transport(ob->pipe_transport_name, &ob->pipe_transport, next, rblock->drinst.name, US"pipe_transport")) next->transport = ob->pipe_transport; address_pipe = NULL; } else if (next->address[0] == '>') { GET_OPTION("reply_transport"); if (rf_get_transport(ob->reply_transport_name, &ob->reply_transport, next, rblock->drinst.name, US"reply_transport")) next->transport = ob->reply_transport; } else /* must be file or directory */ { int len = Ustrlen(next->address); address_file = next->address; if (next->address[len-1] == '/') { GET_OPTION("directory_transport"); if (rf_get_transport(ob->directory_transport_name, &ob->directory_transport, next, rblock->drinst.name, US"directory_transport")) next->transport = ob->directory_transport; } else { GET_OPTION("file_transport"); if (rf_get_transport(ob->file_transport_name, &ob->file_transport, next, rblock->drinst.name, US"file_transport")) next->transport = ob->file_transport; } address_file = NULL; } } #ifdef SUPPORT_I18N if (!next->prop.utf8_msg) next->prop.utf8_msg = string_is_utf8(next->address) || (sender_address && string_is_utf8(sender_address)); #endif DEBUG(D_route) { debug_printf("%s router generated %s\n %serrors_to=%s transport=%s\n", rblock->drinst.name, next->address, testflag(next, af_pfr)? "pipe, file, or autoreply\n " : "", next->prop.errors_address, next->transport ? next->transport->drinst.name : US"NULL"); if (testflag(next, af_uid_set)) debug_printf(" uid=%ld ", (long int)(next->uid)); else debug_printf(" uid=unset "); if (testflag(next, af_gid_set)) debug_printf("gid=%ld ", (long int)(next->gid)); else debug_printf("gid=unset "); #ifdef SUPPORT_I18N if (next->prop.utf8_msg) debug_printf("utf8 "); #endif debug_printf("home=%s\n", next->home_dir); } } } /************************************************* * Main entry point * *************************************************/ /* See local README for interface description. This router returns: DECLINE . empty address list, or filter did nothing significant DEFER . verifying the errors address caused a deferment or a big disaster such as an expansion failure (rf_get_errors_address) . expanding a headers_{add,remove} string caused a deferment or another expansion error (rf_get_munge_headers) . :defer: or "freeze" in a filter . error in address list or filter . skipped syntax errors, but failed to send the message DISCARD . address was :blackhole:d or "seen finish"ed FAIL . :fail: OK . new addresses added to addr_new */ int redirect_router_entry( router_instance *rblock, /* data for this instantiation */ address_item *addr, /* address we are working on */ struct passwd *pw, /* passwd entry after check_local_user */ int verify, /* v_none/v_recipient/v_sender/v_expn */ address_item **addr_local, /* add it to this if it's local */ address_item **addr_remote, /* add it to this if it's remote */ address_item **addr_new, /* put new addresses on here */ address_item **addr_succeed) /* put old address here on success */ { redirect_router_options_block * ob = (redirect_router_options_block *)(rblock->drinst.options_block); address_item *generated = NULL; const uschar *save_qualify_domain_recipient = qualify_domain_recipient; uschar *discarded = US"discarded"; address_item_propagated addr_prop; error_block *eblock = NULL; ugid_block ugid; redirect_block redirect; sieve_block sieve; int filtertype = FILTER_UNSET; int yield = OK; int options = ob->bit_options; int frc = 0; int xrc = 0; /* Initialize the data to be propagated to the children */ addr_prop.address_data = deliver_address_data; addr_prop.domain_data = deliver_domain_data; addr_prop.localpart_data = deliver_localpart_data; addr_prop.errors_address = NULL; addr_prop.extra_headers = NULL; addr_prop.remove_headers = NULL; addr_prop.variables = NULL; tree_dup((tree_node **)&addr_prop.variables, addr->prop.variables); #ifdef SUPPORT_I18N addr_prop.utf8_msg = addr->prop.utf8_msg; addr_prop.utf8_downcvt = addr->prop.utf8_downcvt; addr_prop.utf8_downcvt_maybe = addr->prop.utf8_downcvt_maybe; #endif /* When verifying and testing addresses, the "logwrite" command in filters must be bypassed. */ if (verify == v_none && !f.address_test_mode) options |= RDO_REALLOG; /* Sort out the fixed or dynamic uid/gid. This uid is used (a) for reading the file (and interpreting a filter) and (b) for running the transports for generated file and pipe addresses. It is not (necessarily) the same as the uids that may own the file. Exim panics if an expanded string is not a number and can't be found in the password file. Other errors set the freezing bit. */ if (!rf_get_ugid(rblock, addr, &ugid)) return DEFER; if (!ugid.uid_set && pw) { ugid.uid = pw->pw_uid; ugid.uid_set = TRUE; } if (!ugid.gid_set && pw) { ugid.gid = pw->pw_gid; ugid.gid_set = TRUE; } /* Call the function that interprets redirection data, either inline or from a file. This is a separate function so that the system filter can use it. It will run the function in a subprocess if necessary. If qualify_preserve_domain is set, temporarily reset qualify_domain_recipient to the current domain so that any unqualified addresses get qualified with the same domain as the incoming address. Otherwise, if a local qualify_domain is provided, set that up. */ if (ob->qualify_preserve_domain) qualify_domain_recipient = addr->domain; else { GET_OPTION("qualify_domain"); if (ob->qualify_domain) { uschar *new_qdr = rf_expand_data(addr, ob->qualify_domain, &xrc); if (!new_qdr) return xrc; qualify_domain_recipient = new_qdr; } } redirect.owners = ob->owners; redirect.owngroups = ob->owngroups; redirect.modemask = ob->modemask; redirect.check_owner = ob->check_owner; redirect.check_group = ob->check_group; redirect.pw = pw; redirect.string = (redirect.isfile = (ob->file != NULL)) ? ob->file : ob->data; sieve.inbox = ob->sieve_inbox; sieve.subaddress = ob->sieve_subaddress; sieve.vacation_dir = ob->sieve_vacation_directory; sieve.useraddress = ob->sieve_useraddress; sieve.enotify_mailto_owner = ob->sieve_enotify_mailto_owner; frc = rda_interpret(&redirect, options, ob->include_directory, &sieve, &ugid, &generated, &addr->message, ob->skip_syntax_errors ? &eblock : NULL, &filtertype, string_sprintf("%s router (recipient is %s)", rblock->drinst.name, addr->address)); qualify_domain_recipient = save_qualify_domain_recipient; /* Handle exceptional returns from filtering or processing an address list. For FAIL and FREEZE we honour any previously set up deliveries by a filter. */ switch (frc) { case FF_NONEXIST: addr->message = addr->user_message = NULL; return DECLINE; case FF_BLACKHOLE: DEBUG(D_route) debug_printf("address :blackhole:d\n"); generated = NULL; discarded = US":blackhole:"; frc = FF_DELIVERED; break; /* FF_DEFER and FF_FAIL can arise only as a result of explicit commands (:defer: or :fail: in an alias file or "fail" in a filter). If a configured message was supplied, allow it to be included in an SMTP response after verifying. Remove any SMTP code if it is not allowed. */ case FF_DEFER: yield = DEFER; goto SORT_MESSAGE; case FF_FAIL: if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK) return xrc; add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw); yield = FAIL; SORT_MESSAGE: if (!addr->message) addr->message = yield == FAIL ? US"forced rejection" : US"forced defer"; else { uschar * matched; if ( ob->forbid_smtp_code && regex_match(regex_smtp_code, addr->message, -1, &matched)) { DEBUG(D_route) debug_printf("SMTP code at start of error message " "is ignored because forbid_smtp_code is set\n"); addr->message += Ustrlen(matched); } addr->user_message = addr->message; setflag(addr, af_pass_message); } return yield; /* As in the case of a system filter, a freeze does not happen after a manual thaw. In case deliveries were set up by the filter, we set the child count high so that their completion does not mark the original address done. */ case FF_FREEZE: if (!f.deliver_manual_thaw) { if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK) return xrc; add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw); if (addr->message == NULL) addr->message = US"frozen by filter"; addr->special_action = SPECIAL_FREEZE; addr->child_count = 9999; return DEFER; } frc = FF_NOTDELIVERED; break; /* Handle syntax errors and :include: failures and lookup defers */ case FF_ERROR: case FF_INCLUDEFAIL: /* If filtertype is still FILTER_UNSET, it means that the redirection data was never inspected, so the error was an expansion failure or failure to open the file, or whatever. In these cases, the existing error message is probably sufficient. */ if (filtertype == FILTER_UNSET) return DEFER; /* If it was a filter and skip_syntax_errors is set, we want to set up the error message so that it can be logged and mailed to somebody. */ if (filtertype != FILTER_FORWARD && ob->skip_syntax_errors) { eblock = store_get(sizeof(error_block), GET_UNTAINTED); eblock->next = NULL; eblock->text1 = addr->message; eblock->text2 = NULL; addr->message = addr->user_message = NULL; } /* Otherwise set up the error for the address and defer. */ else { addr->basic_errno = ERRNO_BADREDIRECT; addr->message = string_sprintf("error in %s %s: %s", filtertype == FILTER_FORWARD ? "redirect" : "filter", ob->data ? "data" : "file", addr->message); return DEFER; } } /* Yield is either FF_DELIVERED (significant action) or FF_NOTDELIVERED (no significant action). Before dealing with these, however, we must handle the effect of skip_syntax_errors. If skip_syntax_errors was set and there were syntax errors in an address list, error messages will be present in eblock. Log them and send a message if so configured. We cannot do this earlier, because the error message must not be sent as the local user. If there were no valid addresses, generated will be NULL. In this case, the router declines. For a filter file, the error message has been fudged into an eblock. After dealing with it, the router declines. */ if (eblock != NULL) { if (!moan_skipped_syntax_errors( rblock->drinst.name, /* For message content */ eblock, /* Ditto */ (verify != v_none || f.address_test_mode)? NULL : ob->syntax_errors_to, /* Who to mail */ generated != NULL, /* True if not all failed */ ob->syntax_errors_text)) /* Custom message */ return DEFER; if (filtertype != FILTER_FORWARD || !generated) { addr->message = US"syntax error in redirection data"; return DECLINE; } } /* Sort out the errors address and any header modifications, and handle the generated addresses, if any. If there are no generated addresses, we must avoid calling sort_errors_and_headers() in case this router declines - that function may modify the errors_address field in the current address, and we don't want to do that for a decline. */ if (generated) { if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK) return xrc; add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw); } /* FF_DELIVERED with no generated addresses is what we get when an address list contains :blackhole: or a filter contains "seen finish" without having generated anything. Log what happened to this address, and return DISCARD. */ if (frc == FF_DELIVERED) { if (generated == NULL && verify == v_none && !f.address_test_mode) { log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address, rblock->drinst.name); yield = DISCARD; } } /* For an address list, FF_NOTDELIVERED always means that no addresses were generated. For a filter, addresses may or may not have been generated. If none were, it's the same as an empty address list, and the router declines. However, if addresses were generated, we can't just decline because successful delivery of the base address gets it marked "done", so deferred generated addresses never get tried again. We have to generate a new version of the base address, as if there were a "deliver" command in the filter file, with the original address as parent. */ else { address_item *next; if (generated == NULL) return DECLINE; next = deliver_make_addr(addr->address, FALSE); next->parent = addr; addr->child_count++; next->next = *addr_new; *addr_new = next; /* Set the data that propagates. */ next->prop = addr_prop; DEBUG(D_route) debug_printf("%s router autogenerated %s\n%s%s%s", rblock->drinst.name, next->address, (addr_prop.errors_address != NULL)? " errors to " : "", (addr_prop.errors_address != NULL)? addr_prop.errors_address : US"", (addr_prop.errors_address != NULL)? "\n" : ""); } /* Control gets here only when the address has been completely handled. Put the original address onto the succeed queue so that any retry items that get attached to it get processed. */ addr->next = *addr_succeed; *addr_succeed = addr; return yield; } # ifdef DYNLOOKUP # define redirect_router_info _router_info # endif router_info redirect_router_info = { .drinfo = { .driver_name = US"redirect", .options = redirect_router_options, .options_count = &redirect_router_options_count, .options_block = &redirect_router_option_defaults, .options_len = sizeof(redirect_router_options_block), .init = redirect_router_init, # ifdef DYNLOOKUP .dyn_magic = ROUTER_MAGIC, # endif }, .code = redirect_router_entry, .tidyup = NULL, /* no tidyup entry */ .ri_flags = ri_notransport }; #endif /*!MACRO_PREDEF*/ #endif /*ROUTER_REDIRECT*/ /* End of routers/redirect.c */