SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / routers / redirect.c
index 65b5e40994cc75fed58eafcdc8595a2d031a4a5d..6126555fe0737e182d36e777107cc576abc89a26 100644 (file)
@@ -1,11 +1,11 @@
-/* $Cambridge: exim/src/src/routers/redirect.c,v 1.7 2005/03/22 14:11:55 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-only */
 
 
 #include "../exim.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),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "allow_fail",         opt_bit | (RDON_FAIL << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "allow_filter",       opt_bit | (RDON_FILTER << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "allow_freeze",       opt_bit | (RDON_FREEZE << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
-  { "check_ancestor",     opt_bool,
-      (void *)offsetof(redirect_router_options_block, check_ancestor) },
-  { "check_group",        opt_bool,
-      (void *)offsetof(redirect_router_options_block, check_group) },
-  { "check_owner",        opt_bool,
-      (void *)offsetof(redirect_router_options_block, check_owner) },
-  { "data",               opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, data) },
-  { "directory_transport",opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, directory_transport_name) },
-  { "file",               opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, file) },
-  { "file_transport",     opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, file_transport_name) },
+      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),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "forbid_exim_filter", opt_bit | (RDON_EXIM_FILTER << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "forbid_file",        opt_bool,
-      (void *)offsetof(redirect_router_options_block, forbid_file) },
-  #ifdef EXPAND_DLFUNC
+      LOFF(forbid_file) },
   { "forbid_filter_dlfunc", opt_bit | (RDON_DLFUNC << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
-  #endif
+      LOFF(bit_options) },
   { "forbid_filter_existstest",  opt_bit | (RDON_EXISTS << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "forbid_filter_logwrite",opt_bit | (RDON_LOG << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "forbid_filter_lookup", opt_bit | (RDON_LOOKUP << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
-  #ifdef EXIM_PERL
+      LOFF(bit_options) },
   { "forbid_filter_perl", opt_bit | (RDON_PERL << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
-  #endif
+      LOFF(bit_options) },
   { "forbid_filter_readfile", opt_bit | (RDON_READFILE << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "forbid_filter_readsocket", opt_bit | (RDON_READSOCK << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "forbid_filter_reply",opt_bool,
-      (void *)offsetof(redirect_router_options_block, forbid_filter_reply) },
+      LOFF(forbid_filter_reply) },
   { "forbid_filter_run",  opt_bit | (RDON_RUN << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "forbid_include",     opt_bit | (RDON_INCLUDE << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "forbid_pipe",        opt_bool,
-      (void *)offsetof(redirect_router_options_block, forbid_pipe) },
+      LOFF(forbid_pipe) },
   { "forbid_sieve_filter",opt_bit | (RDON_SIEVE_FILTER << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
+  { "forbid_smtp_code",     opt_bool,
+      LOFF(forbid_smtp_code) },
   { "hide_child_in_errmsg", opt_bool,
-      (void *)offsetof(redirect_router_options_block,  hide_child_in_errmsg) },
+      LOFF( hide_child_in_errmsg) },
   { "ignore_eacces",      opt_bit | (RDON_EACCES << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
+      LOFF(bit_options) },
   { "ignore_enotdir",     opt_bit | (RDON_ENOTDIR << 16),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
-  { "include_directory",  opt_stringptr,
-      (void *)offsetof(redirect_router_options_block,  include_directory) },
-  { "modemask",           opt_octint,
-      (void *)offsetof(redirect_router_options_block, modemask) },
-  { "one_time",           opt_bool,
-      (void *)offsetof(redirect_router_options_block, one_time) },
-  { "owners",             opt_uidlist,
-      (void *)offsetof(redirect_router_options_block, owners) },
-  { "owngroups",          opt_gidlist,
-      (void *)offsetof(redirect_router_options_block, owngroups) },
-  { "pipe_transport",     opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, pipe_transport_name) },
-  { "qualify_domain",     opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, qualify_domain) },
-  { "qualify_preserve_domain", opt_bool,
-      (void *)offsetof(redirect_router_options_block, qualify_preserve_domain) },
-  { "repeat_use",         opt_bool | opt_public,
-      (void *)offsetof(router_instance, repeat_use) },
-  { "reply_transport",    opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, reply_transport_name) },
+      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),
-      (void *)offsetof(redirect_router_options_block, bit_options) },
-  { "sieve_vacation_directory", opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, sieve_vacation_directory) },
-  { "skip_syntax_errors", opt_bool,
-      (void *)offsetof(redirect_router_options_block, skip_syntax_errors) },
-#ifdef EXPERIMENTAL_SRS
-  { "srs",                opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, srs) },
-  { "srs_alias",          opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, srs_alias) },
-  { "srs_condition",      opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, srs_condition) },
-  { "srs_db",             opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, srs_db) },
-#endif
-  { "syntax_errors_text", opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, syntax_errors_text) },
-  { "syntax_errors_to",   opt_stringptr,
-      (void *)offsetof(redirect_router_options_block, syntax_errors_to) }
+      LOFF(bit_options) },
+
+  { "sieve_enotify_mailto_owner", opt_stringptr, LOFF(sieve_enotify_mailto_owner) },
+  { "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
@@ -127,6 +104,21 @@ 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(router_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. */
 
 redirect_router_options_block redirect_router_option_defaults = {
@@ -142,26 +134,24 @@ redirect_router_options_block redirect_router_option_defaults = {
   NULL,        /* include_directory */
   NULL,        /* pipe_transport_name */
   NULL,        /* reply_transport_name */
+  NULL,        /* sieve_subaddress */
+  NULL,        /* sieve_useraddress */
   NULL,        /* sieve_vacation_directory */
+  NULL,        /* sieve_enotify_mailto_owner */
   NULL,        /* syntax_errors_text */
   NULL,        /* syntax_errors_to */
   NULL,        /* qualify_domain */
   NULL,        /* owners */
   NULL,        /* owngroups */
-#ifdef EXPERIMENTAL_SRS
-  NULL,        /* srs */
-  NULL,        /* srs_condition */
-  NULL,        /* srs_db */
-  NULL,        /* srs_alias */
-#endif
   022,         /* modemask */
-  RDO_REWRITE, /* bit_options */
+  RDO_REWRITE | RDO_PREPEND_HOME, /* bit_options */
   FALSE,       /* check_ancestor */
   TRUE_UNSET,  /* check_owner */
   TRUE_UNSET,  /* check_group */
   FALSE,       /* forbid_file */
   FALSE,       /* forbid_filter_reply */
   FALSE,       /* forbid_pipe */
+  FALSE,       /* forbid_smtp_code */
   FALSE,       /* hide_child_in_errmsg */
   FALSE,       /* one_time */
   FALSE,       /* qualify_preserve_domain */
@@ -197,11 +187,11 @@ 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 != NULL || rblock->remove_headers != NULL)
+  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\"", rblock->name);
-  if (rblock->unseen || rblock->expand_unseen != NULL)
+  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\"", rblock->name);
   }
@@ -213,7 +203,7 @@ or if owngroups is set. */
 
 if (ob->check_owner == TRUE_UNSET)
   ob->check_owner = rblock->check_local_user ||
-                    (ob->owners != NULL && ob->owners[0] != 0);
+                    (ob->owners && ob->owners[0] != 0);
 
 if (ob->check_group == TRUE_UNSET)
   ob->check_group = (rblock->check_local_user && (ob->modemask & 020) == 0) ||
@@ -221,7 +211,7 @@ if (ob->check_group == TRUE_UNSET)
 
 /* If explicit qualify domain set, the preserve option is locked out */
 
-if (ob->qualify_domain != NULL && ob->qualify_preserve_domain)
+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",
     rblock->name);
@@ -253,7 +243,7 @@ passed on must have the original errors_address value.
 Arguments:
   rblock               the router control block
   addr                 the address being routed
-  verify               true if verifying
+  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
 
@@ -263,14 +253,14 @@ Returns:    the result of rf_get_errors_address() or rf_get_munge_headers(),
 
 static int
 sort_errors_and_headers(router_instance *rblock, address_item *addr,
-  BOOL verify, address_item_propagated *addr_prop)
+  int verify, address_item_propagated *addr_prop)
 {
 int frc = rf_get_errors_address(addr, rblock, verify,
-  &(addr_prop->errors_address));
+  &addr_prop->errors_address);
 if (frc != OK) return frc;
-addr->p.errors_address = addr_prop->errors_address;
-return rf_get_munge_headers(addr, rblock, &(addr_prop->extra_headers),
-  &(addr_prop->remove_headers));
+addr->prop.errors_address = addr_prop->errors_address;
+return rf_get_munge_headers(addr, rblock, &addr_prop->extra_headers,
+  &addr_prop->remove_headers);
 }
 
 
@@ -314,16 +304,18 @@ add_generated(router_instance *rblock, address_item **addr_new,
 redirect_router_options_block *ob =
   (redirect_router_options_block *)(rblock->options_block);
 
-while (generated != NULL)
+while (generated)
   {
   address_item *parent;
   address_item *next = generated;
-  uschar *errors_address = next->p.errors_address;
+  uschar *errors_address = next->prop.errors_address;
 
   generated = next->next;
   next->parent = addr;
-  orflag(next, addr, af_ignore_error);
   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->name, USHRT_MAX, addr->address);
   addr->child_count++;
 
   next->next = *addr_new;
@@ -331,9 +323,9 @@ while (generated != NULL)
 
   /* Don't do the "one_time" thing for the first pass of a 2-stage queue run. */
 
-  if (ob->one_time && !queue_2stage)
+  if (ob->one_time && !f.queue_2stage)
     {
-    for (parent = addr; parent->parent != NULL; parent = parent->parent);
+    for (parent = addr; parent->parent; parent = parent->parent) ;
     next->onetime_parent = parent->address;
     }
 
@@ -344,28 +336,27 @@ while (generated != NULL)
   unless the ancestor was routed by a case-sensitive router. */
 
   if (ob->check_ancestor)
-    {
-    for (parent = addr; parent != NULL; parent = parent->parent)
-      {
-      if (((parent->router != NULL && parent->router->caseful_local_part)?
-           Ustrcmp(next->address, parent->address)
-           :
-           strcmpic(next->address, parent->address)
+    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. */
 
-  next->p = *addr_prop;
-  if (errors_address != NULL) next->p.errors_address = errors_address;
+    {
+    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,
@@ -405,14 +396,14 @@ while (generated != NULL)
     if (next->address[0] == '|')
       {
       address_pipe = next->address;
-      if (rf_get_transport(ob->pipe_transport_name, &(ob->pipe_transport),
+      if (rf_get_transport(ob->pipe_transport_name, &ob->pipe_transport,
           next, rblock->name, US"pipe_transport"))
         next->transport = ob->pipe_transport;
       address_pipe = NULL;
       }
     else if (next->address[0] == '>')
       {
-      if (rf_get_transport(ob->reply_transport_name, &(ob->reply_transport),
+      if (rf_get_transport(ob->reply_transport_name, &ob->reply_transport,
           next, rblock->name, US"reply_transport"))
         next->transport = ob->reply_transport;
       }
@@ -428,22 +419,27 @@ while (generated != NULL)
           next->transport = ob->directory_transport;
         }
       else
-        {
-        if (rf_get_transport(ob->file_transport_name, &(ob->file_transport),
+        if (rf_get_transport(ob->file_transport_name, &ob->file_transport,
             next, rblock->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->name,
       next->address,
       testflag(next, af_pfr)? "pipe, file, or autoreply\n  " : "",
-      next->p.errors_address,
+      next->prop.errors_address,
       (next->transport == NULL)? US"NULL" : next->transport->name);
 
     if (testflag(next, af_uid_set))
@@ -456,6 +452,10 @@ while (generated != NULL)
     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);
     }
   }
@@ -494,7 +494,7 @@ 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 */
-  BOOL verify,                    /* TRUE when verifying */
+  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 */
@@ -503,7 +503,7 @@ int redirect_router_entry(
 redirect_router_options_block *ob =
   (redirect_router_options_block *)(rblock->options_block);
 address_item *generated = NULL;
-uschar *save_qualify_domain_recipient = qualify_domain_recipient;
+const uschar *save_qualify_domain_recipient = qualify_domain_recipient;
 uschar *discarded = US"discarded";
 address_item_propagated addr_prop;
 error_block *eblock = NULL;
@@ -515,9 +515,6 @@ int options = ob->bit_options;
 int frc = 0;
 int xrc = 0;
 
-addr_local = addr_local;     /* Keep picky compilers happy */
-addr_remote = addr_remote;
-
 /* Initialize the data to be propagated to the children */
 
 addr_prop.address_data = deliver_address_data;
@@ -526,11 +523,20 @@ 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 && !address_test_mode) options |= RDO_REALLOG;
+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
@@ -552,36 +558,6 @@ if (!ugid.gid_set && pw != NULL)
   ugid.gid_set = TRUE;
   }
 
-#ifdef EXPERIMENTAL_SRS
-  /* For reverse SRS, fill the srs_recipient expandsion variable,
-  on failure, return decline/fail as relevant */
-  if(ob->srs != NULL)
-  {
-    BOOL usesrs = TRUE;
-
-    if(ob->srs_condition != NULL)
-      usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL);
-
-    if(usesrs)
-      if(Ustrcmp(ob->srs, "reverse") == 0 || Ustrcmp(ob->srs, "reverseandforward") == 0)
-      {
-        uschar *res;
-        int n_srs;
-
-        srs_orig_recipient = addr->address;
-        eximsrs_init();
-        if(ob->srs_db)
-          eximsrs_db_set(TRUE, ob->srs_db);
-        if((n_srs = eximsrs_reverse(&res, addr->address)) != OK)
-          return n_srs;
-        srs_recipient = res;
-        eximsrs_done();
-        DEBUG(D_any)
-          debug_printf("SRS: Recipient '%s' rewritten to '%s'\n", srs_orig_recipient, srs_recipient);
-      }
-  }
-#endif
-
 /* 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
@@ -591,10 +567,10 @@ address. Otherwise, if a local qualify_domain is provided, set that up. */
 
 if (ob->qualify_preserve_domain)
   qualify_domain_recipient = addr->domain;
-else if (ob->qualify_domain != NULL)
+else if (ob->qualify_domain)
   {
   uschar *new_qdr = rf_expand_data(addr, ob->qualify_domain, &xrc);
-  if (new_qdr == NULL) return xrc;
+  if (!new_qdr) return xrc;
   qualify_domain_recipient = new_qdr;
   }
 
@@ -605,20 +581,13 @@ redirect.check_owner = ob->check_owner;
 redirect.check_group = ob->check_group;
 redirect.pw = pw;
 
-if (ob->file != NULL)
-  {
-  redirect.string = ob->file;
-  redirect.isfile = TRUE;
-  }
-else
-  {
-  redirect.string = ob->data;
-  redirect.isfile = FALSE;
-  }
+redirect.string = (redirect.isfile = (ob->file != NULL))
+  ? ob->file : ob->data;
 
 frc = rda_interpret(&redirect, options, ob->include_directory,
-  ob->sieve_vacation_directory, &ugid, &generated, &(addr->message),
-  ob->skip_syntax_errors? &eblock : NULL, &filtertype,
+  ob->sieve_vacation_directory, ob->sieve_enotify_mailto_owner,
+  ob->sieve_useraddress, ob->sieve_subaddress, &ugid, &generated,
+  &addr->message, ob->skip_syntax_errors? &eblock : NULL, &filtertype,
   string_sprintf("%s router (recipient is %s)", rblock->name, addr->address));
 
 qualify_domain_recipient = save_qualify_domain_recipient;
@@ -629,86 +598,102 @@ 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;
+    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;
+    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
-  (: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. */
+    /* 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:
-  if (addr->message == NULL) addr->message = US"forced defer";
-    else addr->user_message = addr->message;
-  return 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);
-  if (addr->message == NULL) addr->message = US"forced rejection";
-    else addr->user_message = addr->message;
-  return 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;
 
-  /* 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. */
+    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 (!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;
+    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 */
+    /* 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 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 (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 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));
-    eblock->next = NULL;
-    eblock->text1 = addr->message;
-    eblock->text2 = NULL;
-    addr->message = addr->user_message = NULL;
-    }
+    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. */
+    /* 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)? "filter" : "redirect",
-      (ob->data == NULL)? "file" : "data",
-      addr->message);
-    return 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;
+      }
   }
 
 
@@ -728,12 +713,12 @@ dealing with it, the router declines. */
 if (eblock != NULL)
   {
   if (!moan_skipped_syntax_errors(
-        rblock->name,                           /* For message content */
-        eblock,                                 /* Ditto */
-        (verify || address_test_mode)?
-          NULL : ob->syntax_errors_to,          /* Who to mail */
-        generated != NULL,                      /* True if not all failed */
-        ob->syntax_errors_text))                /* Custom message */
+        rblock->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 == NULL)
@@ -762,7 +747,7 @@ generated anything. Log what happened to this address, and return DISCARD. */
 
 if (frc == FF_DELIVERED)
   {
-  if (generated == NULL && !verify && !address_test_mode)
+  if (generated == NULL && verify == v_none && !f.address_test_mode)
     {
     log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
       rblock->name);
@@ -791,11 +776,9 @@ else
   next->next = *addr_new;
   *addr_new = next;
 
-  /* Copy relevant flags (af_propagate is a name for the set), and set the
-  data that propagates. */
+  /* Set the data that propagates. */
 
-  copyflag(next, addr, af_propagate);
-  next->p = addr_prop;
+  next->prop = addr_prop;
 
   DEBUG(D_route) debug_printf("%s router autogenerated %s\n%s%s%s",
     rblock->name,
@@ -805,39 +788,6 @@ else
     (addr_prop.errors_address != NULL)? "\n" : "");
   }
 
-#ifdef EXPERIMENTAL_SRS
-  /* On successful redirection, check for SRS forwarding and adjust sender */
-  if(ob->srs != NULL)
-  {
-    BOOL usesrs = TRUE;
-
-    if(ob->srs_condition != NULL)
-      usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL);
-
-    if(usesrs)
-      if((Ustrcmp(ob->srs, "forward") == 0 || Ustrcmp(ob->srs, "reverseandforward") == 0) && !verify)
-      {
-        uschar *res;
-        uschar *usedomain;
-        int n_srs;
-
-        srs_orig_sender = sender_address;
-        eximsrs_init();
-        if(ob->srs_db)
-          eximsrs_db_set(FALSE, ob->srs_db);
-
-        if(ob->srs_alias != NULL ? (usedomain = expand_string(ob->srs_alias)) == NULL : 1)
-          usedomain = deliver_domain;
-
-        if((n_srs = eximsrs_forward(&res, sender_address, usedomain)) != OK)
-          return n_srs;
-        sender_address = res;
-        DEBUG(D_any)
-          debug_printf("SRS: Sender '%s' rewritten to '%s'\n", srs_orig_sender, sender_address);
-    }
-  }
-#endif
-
 /* 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. */
@@ -848,4 +798,5 @@ addr->next = *addr_succeed;
 return yield;
 }
 
+#endif   /*!MACRO_PREDEF*/
 /* End of routers/redirect.c */