Panic-die if a system filter or router generates more then 32767 child
[exim.git] / src / src / routers / redirect.c
index e33a5fe49fec8adbd424f1f6adedb88685c5ad64..2c515d8facca8642e2fbd6e5cf4cc6783592e640 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/routers/redirect.c,v 1.1 2004/10/07 13:10:02 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 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -39,20 +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),
@@ -65,6 +69,10 @@ optionlist redirect_router_options[] = {
       (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_pipe",        opt_bool,
       (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),
@@ -93,10 +101,26 @@ 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,
       (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_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) },
   { "syntax_errors_to",   opt_stringptr,
@@ -124,20 +148,30 @@ 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 */
   NULL,        /* qualify_domain */
   NULL,        /* owners */
   NULL,        /* owngroups */
+#ifdef EXPERIMENTAL_SRS
+  NULL,        /* srs */
+  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 */
@@ -165,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)
   {
@@ -174,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
@@ -223,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
 
@@ -233,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));
@@ -294,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;
@@ -464,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 */
@@ -497,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
@@ -522,6 +569,95 @@ if (!ugid.gid_set && pw != NULL)
   ugid.gid_set = TRUE;
   }
 
+#ifdef EXPERIMENTAL_SRS
+  /* Perform SRS on recipient/return-path as required  */
+
+  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)
+    {
+      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)
+      {
+        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_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;
+      }
+
+      /* 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();
+
+        if(n_srs != OK)
+          return n_srs;
+      }
+    }
+  }
+#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
@@ -557,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;
 
@@ -580,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
@@ -668,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)
@@ -702,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);