Panic-die if a system filter or router generates more then 32767 child
[exim.git] / src / src / routers / redirect.c
index 90ec9de6af7ff22ffd86d61852fe7e50f6eb5552..2c515d8facca8642e2fbd6e5cf4cc6783592e640 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/routers/redirect.c,v 1.5 2005/02/17 11:58:27 ph10 Exp $ */
+/* $Cambridge: exim/src/src/routers/redirect.c,v 1.19 2007/01/02 11:25:00 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -39,22 +39,24 @@ optionlist redirect_router_options[] = {
       (void *)offsetof(redirect_router_options_block, file) },
   { "file_transport",     opt_stringptr,
       (void *)offsetof(redirect_router_options_block, file_transport_name) },
+  { "filter_prepend_home",opt_bit | (RDON_PREPEND_HOME << 16),
+      (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_blackhole",   opt_bit | (RDON_BLACKHOLE << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_exim_filter", opt_bit | (RDON_EXIM_FILTER << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_file",        opt_bool,
       (void *)offsetof(redirect_router_options_block, forbid_file) },
+  { "forbid_filter_dlfunc", opt_bit | (RDON_DLFUNC << 16),
+      (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_filter_existstest",  opt_bit | (RDON_EXISTS << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_filter_logwrite",opt_bit | (RDON_LOG << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_filter_lookup", opt_bit | (RDON_LOOKUP << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
-  #ifdef EXIM_PERL
   { "forbid_filter_perl", opt_bit | (RDON_PERL << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
-  #endif
   { "forbid_filter_readfile", opt_bit | (RDON_READFILE << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_filter_readsocket", opt_bit | (RDON_READSOCK << 16),
@@ -69,6 +71,8 @@ optionlist redirect_router_options[] = {
       (void *)offsetof(redirect_router_options_block, forbid_pipe) },
   { "forbid_sieve_filter",opt_bit | (RDON_SIEVE_FILTER << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
+  { "forbid_smtp_code",     opt_bool,
+      (void *)offsetof(redirect_router_options_block, forbid_smtp_code) },
   { "hide_child_in_errmsg", opt_bool,
       (void *)offsetof(redirect_router_options_block,  hide_child_in_errmsg) },
   { "ignore_eacces",      opt_bit | (RDON_EACCES << 16),
@@ -97,6 +101,10 @@ optionlist redirect_router_options[] = {
       (void *)offsetof(redirect_router_options_block, reply_transport_name) },
   { "rewrite",            opt_bit | (RDON_REWRITE << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
+  { "sieve_subaddress", opt_stringptr,
+      (void *)offsetof(redirect_router_options_block, sieve_subaddress) },
+  { "sieve_useraddress", opt_stringptr,
+      (void *)offsetof(redirect_router_options_block, sieve_useraddress) },
   { "sieve_vacation_directory", opt_stringptr,
       (void *)offsetof(redirect_router_options_block, sieve_vacation_directory) },
   { "skip_syntax_errors", opt_bool,
@@ -108,8 +116,10 @@ optionlist redirect_router_options[] = {
       (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) },
+  { "srs_dbinsert",       opt_stringptr,
+      (void *)offsetof(redirect_router_options_block, srs_dbinsert) },
+  { "srs_dbselect",       opt_stringptr,
+      (void *)offsetof(redirect_router_options_block, srs_dbselect) },
 #endif
   { "syntax_errors_text", opt_stringptr,
       (void *)offsetof(redirect_router_options_block, syntax_errors_text) },
@@ -138,6 +148,8 @@ 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,        /* syntax_errors_text */
   NULL,        /* syntax_errors_to */
@@ -146,18 +158,20 @@ redirect_router_options_block redirect_router_option_defaults = {
   NULL,        /* owngroups */
 #ifdef EXPERIMENTAL_SRS
   NULL,        /* srs */
-  NULL,        /* srs_condition */
-  NULL,        /* srs_db */
   NULL,        /* srs_alias */
+  NULL,        /* srs_condition */
+  NULL,        /* srs_dbinsert */
+  NULL,        /* srs_dbselect */
 #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 */
@@ -185,7 +199,10 @@ if ((ob->file == NULL) == (ob->data == NULL))
     "%sone of \"file\" or \"data\" must be specified",
     rblock->name, (ob->file == NULL)? "" : "only ");
 
-/* Onetime aliases can only be real addresses. Headers can't be manipulated. */
+/* 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)
   {
@@ -194,6 +211,9 @@ if (ob->one_time)
     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)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
+      "\"unseen\" may not be used with \"one_time\"", rblock->name);
   }
 
 /* The defaults for check_owner and check_group depend on other settings. The
@@ -243,7 +263,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
 
@@ -253,7 +273,7 @@ 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));
@@ -314,6 +334,9 @@ while (generated != NULL)
   next->parent = addr;
   orflag(next, addr, af_ignore_error);
   next->start_router = rblock->redirect_router;
+  if (addr->child_count == SHRT_MAX)
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
+      "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address);
   addr->child_count++;
 
   next->next = *addr_new;
@@ -484,7 +507,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 */
@@ -517,10 +540,14 @@ addr_prop.errors_address = NULL;
 addr_prop.extra_headers = NULL;
 addr_prop.remove_headers = NULL;
 
+#ifdef EXPERIMENTAL_SRS
+addr_prop.srs_sender = NULL;
+#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 && !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
@@ -543,8 +570,8 @@ if (!ugid.gid_set && pw != NULL)
   }
 
 #ifdef EXPERIMENTAL_SRS
-  /* For reverse SRS, fill the srs_recipient expandsion variable,
-  on failure, return decline/fail as relevant */
+  /* Perform SRS on recipient/return-path as required  */
+
   if(ob->srs != NULL)
   {
     BOOL usesrs = TRUE;
@@ -553,22 +580,81 @@ if (!ugid.gid_set && pw != 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)
+    {
+      int srs_action = 0, n_srs;
+      uschar *res;
+      uschar *usedomain;
+
+      /* What are we doing? */
+      if(Ustrcmp(ob->srs, "forward") == 0)
+        srs_action = 1;
+      else if(Ustrcmp(ob->srs, "reverseandforward") == 0)
       {
-        uschar *res;
-        int n_srs;
+        srs_action = 3;
+
+        if((ob->srs_dbinsert == NULL) ^ (ob->srs_dbselect == NULL))
+          return DEFER;
+      }
+      else if(Ustrcmp(ob->srs, "reverse") == 0)
+        srs_action = 2;
 
+      /* Reverse SRS */
+      if(srs_action & 2)
+      {
         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)
+        if(ob->srs_dbselect)
+          eximsrs_db_set(TRUE, ob->srs_dbselect);
+/* Comment this out for now...
+//        else
+//          eximsrs_db_set(TRUE, NULL);
+*/
+
+        if((n_srs = eximsrs_reverse(&res, addr->address)) == OK)
+        {
+          srs_recipient = res;
+          DEBUG(D_any)
+            debug_printf("SRS (reverse): Recipient '%s' rewritten to '%s'\n", srs_orig_recipient, srs_recipient);
+        }
+
+        eximsrs_done();
+
+        if(n_srs != OK)
           return n_srs;
-        srs_recipient = res;
+      }
+
+      /* Forward SRS */
+      /* No point in actually performing SRS if we are just verifying a recipient */
+      if((srs_action & 1) && verify == v_none &&
+         (sender_address ? sender_address[0] != 0 : FALSE))
+      {
+
+        srs_orig_sender = sender_address;
+        eximsrs_init();
+        if(ob->srs_dbinsert)
+          eximsrs_db_set(FALSE, ob->srs_dbinsert);
+/* Comment this out for now...
+//        else
+//          eximsrs_db_set(FALSE, NULL);
+*/
+
+        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)
+        {
+          addr_prop.srs_sender = res;
+          DEBUG(D_any)
+            debug_printf("SRS (forward): Sender '%s' rewritten to '%s'\n", srs_orig_sender, res);
+        }
+
         eximsrs_done();
-        DEBUG(D_any)
-          debug_printf("SRS: Recipient '%s' rewritten to '%s'\n", srs_orig_recipient, srs_recipient);
+
+        if(n_srs != OK)
+          return n_srs;
       }
+    }
   }
 #endif
 
@@ -607,9 +693,10 @@ else
   }
 
 frc = rda_interpret(&redirect, options, ob->include_directory,
-  ob->sieve_vacation_directory, &ugid, &generated, &(addr->message),
-  ob->skip_syntax_errors? &eblock : NULL, &filtertype,
-  string_sprintf("%s router (recipient is %s)", rblock->name, addr->address));
+  ob->sieve_vacation_directory, 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;
 
@@ -630,21 +717,39 @@ switch (frc)
   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. */
+  (: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;
+  yield = FAIL;
+
+  SORT_MESSAGE:
+  if (addr->message == NULL)
+    addr->message = (yield == FAIL)? US"forced rejection" : US"forced defer";
+  else
+    {
+    int ovector[3];
+    if (ob->forbid_smtp_code &&
+        pcre_exec(regex_smtp_code, NULL, CS addr->message,
+          Ustrlen(addr->message), 0, PCRE_EOPT,
+          ovector, sizeof(ovector)/sizeof(int)) >= 0)
+      {
+      DEBUG(D_route) debug_printf("SMTP code at start of error message "
+        "is ignored because forbid_smtp_code is set\n");
+      addr->message += ovector[1];
+      }
+    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
@@ -718,12 +823,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 || 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)
@@ -752,7 +857,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 && !address_test_mode)
     {
     log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
       rblock->name);
@@ -795,39 +900,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. */