Explicit IPv6 addresses for mysql and pgsql
[exim.git] / src / src / routers / queryprogram.c
index c9b409e78238f7994c911e25b30ad1afa2d0527b..9433759125c383e641bc7117bebb678c6bd5d554 100644 (file)
@@ -1,13 +1,15 @@
-/* $Cambridge: exim/src/src/routers/queryprogram.c,v 1.7 2006/02/07 11:19:02 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* 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_QUERYPROGRAM     /* Remainder of file */
 #include "rf_functions.h"
 #include "queryprogram.h"
 
 
 optionlist queryprogram_router_options[] = {
   { "*expand_command_group", opt_bool | opt_hidden,
-      (void *)(offsetof(queryprogram_router_options_block, expand_cmd_gid)) },
+      OPT_OFF(queryprogram_router_options_block, expand_cmd_gid) },
   { "*expand_command_user", opt_bool | opt_hidden,
-      (void *)(offsetof(queryprogram_router_options_block, expand_cmd_uid)) },
+      OPT_OFF(queryprogram_router_options_block, expand_cmd_uid) },
   { "*set_command_group",   opt_bool | opt_hidden,
-      (void *)(offsetof(queryprogram_router_options_block, cmd_gid_set)) },
+      OPT_OFF(queryprogram_router_options_block, cmd_gid_set) },
   { "*set_command_user",    opt_bool | opt_hidden,
-      (void *)(offsetof(queryprogram_router_options_block, cmd_uid_set)) },
+      OPT_OFF(queryprogram_router_options_block, cmd_uid_set) },
   { "command",      opt_stringptr,
-      (void *)(offsetof(queryprogram_router_options_block, command)) },
+      OPT_OFF(queryprogram_router_options_block, command) },
   { "command_group",opt_expand_gid,
-      (void *)(offsetof(queryprogram_router_options_block, cmd_gid)) },
+      OPT_OFF(queryprogram_router_options_block, cmd_gid) },
   { "command_user", opt_expand_uid,
-      (void *)(offsetof(queryprogram_router_options_block, cmd_uid)) },
+      OPT_OFF(queryprogram_router_options_block, cmd_uid) },
   { "current_directory", opt_stringptr,
-      (void *)(offsetof(queryprogram_router_options_block, current_directory)) },
+      OPT_OFF(queryprogram_router_options_block, current_directory) },
   { "timeout",      opt_time,
-      (void *)(offsetof(queryprogram_router_options_block, timeout)) }
+      OPT_OFF(queryprogram_router_options_block, timeout) }
 };
 
 /* Size of the options list. An extern variable has to be used so that its
@@ -42,6 +44,20 @@ address can appear in the tables drtables.c. */
 int queryprogram_router_options_count =
   sizeof(queryprogram_router_options)/sizeof(optionlist);
 
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+queryprogram_router_options_block queryprogram_router_option_defaults = {0};
+void queryprogram_router_init(driver_instance *rblock) {}
+int queryprogram_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 queryprogram router. */
 
 queryprogram_router_options_block queryprogram_router_option_defaults = {
@@ -66,20 +82,20 @@ queryprogram_router_options_block queryprogram_router_option_defaults = {
 consistency checks to be done, or anything else that needs to be set up. */
 
 void
-queryprogram_router_init(router_instance *rblock)
+queryprogram_router_init(driver_instance * rblock)
 {
 queryprogram_router_options_block *ob =
   (queryprogram_router_options_block *)(rblock->options_block);
 
 /* A command must be given */
 
-if (ob->command == NULL)
+if (!ob->command)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
     "a command specification is required", rblock->name);
 
 /* A uid/gid must be supplied */
 
-if (!ob->cmd_uid_set && ob->expand_cmd_uid == NULL)
+if (!ob->cmd_uid_set && !ob->expand_cmd_uid)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
     "command_user must be specified", rblock->name);
 }
@@ -111,21 +127,26 @@ add_generated(router_instance *rblock, address_item **addr_new,
 {
 while (generated != NULL)
   {
+  BOOL ignore_error = addr->prop.ignore_error;
   address_item *next = generated;
+
   generated = next->next;
 
   next->parent = addr;
-  orflag(next, addr, af_propagate);
-  next->p = *addr_prop;
+  next->prop = *addr_prop;
+  next->prop.ignore_error = next->prop.ignore_error || ignore_error;
   next->start_router = rblock->redirect_router;
 
   next->next = *addr_new;
   *addr_new = next;
 
+  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++;
 
   DEBUG(D_route)
-    debug_printf("%s router generated %s\n", rblock->name, next->address);
+    debug_printf("%s router generated %s\n", rblock->drinst.name, next->address);
   }
 }
 
@@ -191,88 +212,100 @@ int fd_in, fd_out, len, rc;
 pid_t pid;
 struct passwd *upw = NULL;
 uschar buffer[1024];
-uschar **argvptr;
+const uschar **argvptr;
 uschar *rword, *rdata, *s;
 address_item_propagated addr_prop;
-queryprogram_router_options_block *ob =
-  (queryprogram_router_options_block *)(rblock->options_block);
+queryprogram_router_options_block * ob =
+  (queryprogram_router_options_block *)(rblock->drinst.options_block);
 uschar *current_directory = ob->current_directory;
 ugid_block ugid;
+uid_t curr_uid = getuid();
+gid_t curr_gid = getgid();
 uid_t uid = ob->cmd_uid;
 gid_t gid = ob->cmd_gid;
+uid_t *puid = &uid;
+gid_t *pgid = &gid;
 
 DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
-  rblock->name, addr->address, addr->domain);
+  rblock->drinst.name, addr->address, addr->domain);
 
 ugid.uid_set = ugid.gid_set = FALSE;
 
 /* Set up the propagated data block with the current address_data and the
 errors address and extra header stuff. */
 
+bzero(&addr_prop, sizeof(addr_prop));
 addr_prop.address_data = deliver_address_data;
+tree_dup((tree_node **)&addr_prop.variables, addr->prop.variables);
 
-rc = rf_get_errors_address(addr, rblock, verify, &(addr_prop.errors_address));
+rc = rf_get_errors_address(addr, rblock, verify, &addr_prop.errors_address);
 if (rc != OK) return rc;
 
-rc = rf_get_munge_headers(addr, rblock, &(addr_prop.extra_headers),
-  &(addr_prop.remove_headers));
+rc = rf_get_munge_headers(addr, rblock, &addr_prop.extra_headers,
+  &addr_prop.remove_headers);
 if (rc != OK) return rc;
 
 /* Get the fixed or expanded uid under which the command is to run
 (initialization ensures that one or the other is set). */
 
-if (!ob->cmd_uid_set)
-  {
-  if (!route_find_expanded_user(ob->expand_cmd_uid, rblock->name, US"router",
-      &upw, &uid, &(addr->message)))
+if (  !ob->cmd_uid_set
+   && !route_find_expanded_user(ob->expand_cmd_uid, rblock->drinst.name,
+       US"router", &upw, &uid, &(addr->message)))
     return DEFER;
-  }
 
 /* Get the fixed or expanded gid, or take the gid from the passwd entry. */
 
 if (!ob->cmd_gid_set)
-  {
-  if (ob->expand_cmd_gid != NULL)
+  if (ob->expand_cmd_gid)
     {
-    if (route_find_expanded_group(ob->expand_cmd_gid, rblock->name,
+    if (route_find_expanded_group(ob->expand_cmd_gid, rblock->drinst.name,
         US"router", &gid, &(addr->message)))
       return DEFER;
     }
-  else if (upw != NULL)
-    {
+  else if (upw)
     gid = upw->pw_gid;
-    }
   else
     {
     addr->message = string_sprintf("command_user set without command_group "
-      "for %s router", rblock->name);
+      "for %s router", rblock->drinst.name);
     return DEFER;
     }
-  }
 
-DEBUG(D_route) debug_printf("uid=%ld gid=%ld current_directory=%s\n",
+DEBUG(D_route) debug_printf("requires uid=%ld gid=%ld current_directory=%s\n",
   (long int)uid, (long int)gid, current_directory);
 
+/* If we are not running as root, we will not be able to change uid/gid. */
+
+if (curr_uid != root_uid && (uid != curr_uid || gid != curr_gid))
+  {
+  DEBUG(D_route)
+    {
+    debug_printf("not running as root: cannot change uid/gid\n");
+    debug_printf("subprocess will run with uid=%ld gid=%ld\n",
+      (long int)curr_uid, (long int)curr_gid);
+    }
+  puid = pgid = NULL;
+  }
+
+/* Set up the command to run */
+
+GET_OPTION("command");
 if (!transport_set_up_command(&argvptr, /* anchor for arg list */
     ob->command,                        /* raw command */
-    TRUE,                               /* expand the arguments */
+    TSUC_EXPAND_ARGS,                   /* arguments expanded but must not be tainted */
     0,                                  /* not relevant when... */
     NULL,                               /* no transporting address */
     US"queryprogram router",            /* for error messages */
-    &(addr->message)))                  /* where to put error message */
-  {
+    &addr->message))                    /* where to put error message */
   return DEFER;
-  }
 
 /* Create the child process, making it a group leader. */
 
-pid = child_open_uid(argvptr, NULL, 0077, &uid, &gid, &fd_in, &fd_out,
-  current_directory, TRUE);
-
-if (pid < 0)
+if ((pid = child_open_uid(argvptr, NULL, 0077, puid, pgid, &fd_in, &fd_out,
+                         current_directory, TRUE, US"queryprogram-cmd")) < 0)
   {
   addr->message = string_sprintf("%s router couldn't create child process: %s",
-    rblock->name, strerror(errno));
+    rblock->drinst.name, strerror(errno));
   return DEFER;
   }
 
@@ -287,22 +320,22 @@ if ((rc = child_close(pid, ob->timeout)) != 0)
   {
   if (rc > 0)
     addr->message = string_sprintf("%s router: command returned non-zero "
-      "code %d", rblock->name, rc);
+      "code %d", rblock->drinst.name, rc);
 
   else if (rc == -256)
     {
     addr->message = string_sprintf("%s router: command timed out",
-      rblock->name);
+      rblock->drinst.name);
     killpg(pid, SIGKILL);       /* Kill the whole process group */
     }
 
   else if (rc == -257)
     addr->message = string_sprintf("%s router: wait() failed: %s",
-      rblock->name, strerror(errno));
+      rblock->drinst.name, strerror(errno));
 
   else
     addr->message = string_sprintf("%s router: command killed by signal %d",
-      rblock->name, -rc);
+      rblock->drinst.name, -rc);
 
   return DEFER;
   }
@@ -317,7 +350,7 @@ len = read(fd_out, buffer, sizeof(buffer) - 1);
 if (len <= 0)
   {
   addr->message = string_sprintf("%s router: command failed to return data",
-    rblock->name);
+    rblock->drinst.name);
   return DEFER;
   }
 
@@ -330,10 +363,10 @@ buffer[len] = 0;
 DEBUG(D_route) debug_printf("command wrote: %s\n", buffer);
 
 rword = buffer;
-while (isspace(*rword)) rword++;
+Uskip_whitespace(&rword);
 rdata = rword;
-while (*rdata != 0 && !isspace(*rdata)) rdata++;
-if (*rdata != 0) *rdata++ = 0;
+Uskip_nonwhite(&rdata);
+if (*rdata) *rdata++ = '\0';
 
 /* The word must be a known yield name. If it is "REDIRECT", the rest of the
 line is redirection data, as for a .forward file. It may not contain filter
@@ -355,15 +388,13 @@ if (strcmpic(rword, US"REDIRECT") == 0)
       RDO_INCLUDE |              /* forbid :include: */
       RDO_REWRITE,               /* rewrite generated addresses */
     NULL,                        /* :include: directory not relevant */
-    NULL,                        /* sieve vacation directory not relevant */
-    NULL,                        /* sieve useraddress not relevant */
-    NULL,                        /* sieve subaddress not relevant */
+    NULL,                        /* sieve info not relevant */
     &ugid,                       /* uid/gid (but not set) */
     &generated,                  /* where to hang the results */
-    &(addr->message),            /* where to put messages */
+    &addr->message,              /* where to put messages */
     NULL,                        /* don't skip syntax errors */
     &filtertype,                 /* not used; will always be FILTER_FORWARD */
-    string_sprintf("%s router", rblock->name));
+    string_sprintf("%s router", rblock->drinst.name));
 
   switch (rc)
     {
@@ -372,28 +403,28 @@ if (strcmpic(rword, US"REDIRECT") == 0)
     response after verifying. */
 
     case FF_DEFER:
-    if (addr->message == NULL) addr->message = US"forced defer";
+      if (!addr->message) addr->message = US"forced defer";
       else addr->user_message = addr->message;
-    return DEFER;
+      return DEFER;
 
     case FF_FAIL:
-    add_generated(rblock, addr_new, addr, generated, &addr_prop);
-    if (addr->message == NULL) addr->message = US"forced rejection";
+      add_generated(rblock, addr_new, addr, generated, &addr_prop);
+      if (!addr->message) addr->message = US"forced rejection";
       else addr->user_message = addr->message;
-    return FAIL;
+      return FAIL;
 
     case FF_DELIVERED:
-    break;
+      break;
 
     case FF_NOTDELIVERED:    /* an empty redirection list is bad */
-    addr->message = US"no addresses supplied";
+      addr->message = US"no addresses supplied";
     /* Fall through */
 
     case FF_ERROR:
     default:
-    addr->basic_errno = ERRNO_BADREDIRECT;
-    addr->message = string_sprintf("error in redirect data: %s", addr->message);
-    return DEFER;
+      addr->basic_errno = ERRNO_BADREDIRECT;
+      addr->message = string_sprintf("error in redirect data: %s", addr->message);
+      return DEFER;
     }
 
   /* Handle the generated addresses, if any. */
@@ -425,35 +456,30 @@ if (strcmpic(rword, US"accept") != 0)
   else if (strcmpic(rword, US"defer") != 0)
     {
     addr->message = string_sprintf("bad command yield: %s %s", rword, rdata);
-    log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message);
+    log_write(0, LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
     }
   return DEFER;
   }
 
 /* The command yielded "ACCEPT". The rest of the string is a number of keyed
-fields from which we can fish out values using the "extract" expansion
-function. To use this feature, we must put the string into the $value variable,
-i.e. set lookup_value. */
-
-lookup_value = rdata;
-s = expand_string(US"${extract{data}{$value}}");
-if (*s != 0) addr_prop.address_data = string_copy(s);
+fields from which we can fish out values using the equivalent of the "extract"
+expansion function. */
 
-s = expand_string(US"${extract{transport}{$value}}");
-lookup_value = NULL;
+if ((s = expand_getkeyed(US"data", rdata)) && *s)
+  addr_prop.address_data = string_copy(s);
 
 /* If we found a transport name, find the actual transport */
 
-if (*s != 0)
+if ((s = expand_getkeyed(US"transport", rdata)) && *s)
   {
-  transport_instance *transport;
-  for (transport = transports; transport != NULL; transport = transport->next)
-    if (Ustrcmp(transport->name, s) == 0) break;
-  if (transport == NULL)
+  transport_instance * transport;
+  for (transport = transports; transport; transport = transport->drinst.next)
+    if (Ustrcmp(transport->drinst.name, s) == 0) break;
+  if (!transport)
     {
     addr->message = string_sprintf("unknown transport name %s yielded by "
       "command", s);
-    log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message);
+    log_write(0, LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
     return DEFER;
     }
   addr->transport = transport;
@@ -465,32 +491,28 @@ the last argument not being NULL. */
 
 else
   {
-  if (!rf_get_transport(rblock->transport_name, &(rblock->transport), addr,
-       rblock->name, US"transport"))
+  if (!rf_get_transport(rblock->transport_name, &rblock->transport, addr,
+       rblock->drinst.name, US"transport"))
     return DEFER;
   addr->transport = rblock->transport;
   }
 
 /* See if a host list is given, and if so, look up the addresses. */
 
-lookup_value = rdata;
-s = expand_string(US"${extract{hosts}{$value}}");
-
-if (*s != 0)
+if ((s = expand_getkeyed(US"hosts", rdata)) && *s)
   {
-  int lookup_type = lk_default;
-  uschar *ss = expand_string(US"${extract{lookup}{$value}}");
-  lookup_value = NULL;
+  int lookup_type = LK_DEFAULT;
+  uschar * ss = expand_getkeyed(US"lookup", rdata);
 
-  if (*ss != 0)
+  if (ss && *ss)
     {
-    if (Ustrcmp(ss, "byname") == 0) lookup_type = lk_byname;
-    else if (Ustrcmp(ss, "bydns") == 0) lookup_type = lk_bydns;
+    if (Ustrcmp(ss, "byname") == 0) lookup_type = LK_BYNAME;
+    else if (Ustrcmp(ss, "bydns") == 0) lookup_type = LK_BYDNS;
     else
       {
       addr->message = string_sprintf("bad lookup type \"%s\" yielded by "
         "command", ss);
-      log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message);
+      log_write(0, LOG_PANIC, "%s router: %s", rblock->drinst.name, addr->message);
       return DEFER;
       }
     }
@@ -505,12 +527,37 @@ lookup_value = NULL;
 
 /* Put the errors address, extra headers, and address_data into this address */
 
-addr->p = addr_prop;
+addr->prop = addr_prop;
 
 /* Queue the address for local or remote delivery. */
 
-return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
-  OK : DEFER;
+return rf_queue_add(addr, addr_local, addr_remote, rblock, pw) ? OK : DEFER;
 }
 
+
+
+# ifdef DYNLOOKUP
+#  define queryprogram_router_info _router_info
+# endif
+
+router_info queryprogram_router_info =
+{
+.drinfo = {
+  .driver_name =      US"queryprogram",
+  .options =          queryprogram_router_options,
+  .options_count =    &queryprogram_router_options_count,
+  .options_block =    &queryprogram_router_option_defaults,
+  .options_len =      sizeof(queryprogram_router_options_block),
+  .init =             queryprogram_router_init,
+# ifdef DYNLOOKUP
+  .dyn_magic =       ROUTER_MAGIC,
+# endif
+  },
+.code =               queryprogram_router_entry,
+.tidyup =             NULL,     /* no tidyup entry */
+.ri_flags =           0
+};
+
+#endif /*!MACRO_PREDEF*/
+#endif /*ROUTER_QUERYPROGRAM*/
 /* End of routers/queryprogram.c */