Merge pull request #3329 from efuss/fix-3311
[collectd.git] / src / exec.c
index e0d1f89..f8707d4 100644 (file)
 #define _DEFAULT_SOURCE
 #define _BSD_SOURCE /* For setgroups */
 
+/* _GNU_SOURCE is needed in Linux to use execvpe */
+#define _GNU_SOURCE
+
 #include "collectd.h"
 
-#include "common.h"
 #include "plugin.h"
+#include "utils/common/common.h"
 
-#include "utils_cmd_putnotif.h"
-#include "utils_cmd_putval.h"
+#include "utils/cmds/putnotif.h"
+#include "utils/cmds/putval.h"
 
 #include <grp.h>
 #include <pwd.h>
@@ -43,6 +46,8 @@
 #include <sys/capability.h>
 #endif
 
+extern char **environ;
+
 #define PL_NORMAL 0x01
 #define PL_NOTIF_ACTION 0x02
 
@@ -85,7 +90,7 @@ const long int MAX_GRBUF_SIZE = 65536;
 /*
  * Private variables
  */
-static program_list_t *pl_head = NULL;
+static program_list_t *pl_head;
 static pthread_mutex_t pl_lock = PTHREAD_MUTEX_INITIALIZER;
 
 /*
@@ -113,26 +118,26 @@ static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
 
   if (ci->children_num != 0) {
     WARNING("exec plugin: The config option `%s' may not be a block.", ci->key);
-    return (-1);
+    return -1;
   }
   if (ci->values_num < 2) {
     WARNING("exec plugin: The config option `%s' needs at least two "
             "arguments.",
             ci->key);
-    return (-1);
+    return -1;
   }
   if ((ci->values[0].type != OCONFIG_TYPE_STRING) ||
       (ci->values[1].type != OCONFIG_TYPE_STRING)) {
     WARNING("exec plugin: The first two arguments to the `%s' option must "
             "be string arguments.",
             ci->key);
-    return (-1);
+    return -1;
   }
 
   pl = calloc(1, sizeof(*pl));
   if (pl == NULL) {
     ERROR("exec plugin: calloc failed.");
-    return (-1);
+    return -1;
   }
 
   if (strcasecmp("NotificationExec", ci->key) == 0)
@@ -144,7 +149,7 @@ static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
   if (pl->user == NULL) {
     ERROR("exec plugin: strdup failed.");
     sfree(pl);
-    return (-1);
+    return -1;
   }
 
   pl->group = strchr(pl->user, ':');
@@ -158,7 +163,7 @@ static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
     ERROR("exec plugin: strdup failed.");
     sfree(pl->user);
     sfree(pl);
-    return (-1);
+    return -1;
   }
 
   pl->argv = calloc(ci->values_num, sizeof(*pl->argv));
@@ -167,7 +172,7 @@ static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
     sfree(pl->exec);
     sfree(pl->user);
     sfree(pl);
-    return (-1);
+    return -1;
   }
 
   {
@@ -184,7 +189,7 @@ static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
     sfree(pl->exec);
     sfree(pl->user);
     sfree(pl);
-    return (-1);
+    return -1;
   }
 
   for (i = 1; i < (ci->values_num - 1); i++) {
@@ -192,8 +197,7 @@ static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
       pl->argv[i] = strdup(ci->values[i + 1].value.string);
     } else {
       if (ci->values[i + 1].type == OCONFIG_TYPE_NUMBER) {
-        ssnprintf(buffer, sizeof(buffer), "%lf",
-                  ci->values[i + 1].value.number);
+        snprintf(buffer, sizeof(buffer), "%lf", ci->values[i + 1].value.number);
       } else {
         if (ci->values[i + 1].value.boolean)
           sstrncpy(buffer, "true", sizeof(buffer));
@@ -218,7 +222,7 @@ static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
     sfree(pl->exec);
     sfree(pl->user);
     sfree(pl);
-    return (-1);
+    return -1;
   }
 
   for (i = 0; pl->argv[i] != NULL; i++) {
@@ -228,7 +232,7 @@ static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
   pl->next = pl_head;
   pl_head = pl;
 
-  return (0);
+  return 0;
 } /* int exec_config_exec }}} */
 
 static int exec_config(oconfig_item_t *ci) /* {{{ */
@@ -243,35 +247,14 @@ static int exec_config(oconfig_item_t *ci) /* {{{ */
     }
   } /* for (i) */
 
-  return (0);
+  return 0;
 } /* int exec_config }}} */
 
-static void set_environment(void) /* {{{ */
-{
-  char buffer[1024];
-
-#ifdef HAVE_SETENV
-  ssnprintf(buffer, sizeof(buffer), "%.3f",
-            CDTIME_T_TO_DOUBLE(plugin_get_interval()));
-  setenv("COLLECTD_INTERVAL", buffer, /* overwrite = */ 1);
-
-  sstrncpy(buffer, hostname_g, sizeof(buffer));
-  setenv("COLLECTD_HOSTNAME", buffer, /* overwrite = */ 1);
-#else
-  ssnprintf(buffer, sizeof(buffer), "COLLECTD_INTERVAL=%.3f",
-            CDTIME_T_TO_DOUBLE(plugin_get_interval()));
-  putenv(buffer);
-
-  ssnprintf(buffer, sizeof(buffer), "COLLECTD_HOSTNAME=%s", hostname_g);
-  putenv(buffer);
-#endif
-} /* }}} void set_environment */
-
-__attribute__((noreturn)) static void exec_child(program_list_t *pl, int uid,
-                                                 int gid, int egid) /* {{{ */
+__attribute__((noreturn)) static void exec_child(program_list_t *pl,
+                                                 char **envp, int uid, int gid,
+                                                 int egid) /* {{{ */
 {
   int status;
-  char errbuf[1024];
 
 #if HAVE_SETGROUPS
   if (getuid() == 0) {
@@ -292,31 +275,32 @@ __attribute__((noreturn)) static void exec_child(program_list_t *pl, int uid,
 
   status = setgid(gid);
   if (status != 0) {
-    ERROR("exec plugin: setgid (%i) failed: %s", gid,
-          sstrerror(errno, errbuf, sizeof(errbuf)));
+    ERROR("exec plugin: setgid (%i) failed: %s", gid, STRERRNO);
     exit(-1);
   }
 
   if (egid != -1) {
     status = setegid(egid);
     if (status != 0) {
-      ERROR("exec plugin: setegid (%i) failed: %s", egid,
-            sstrerror(errno, errbuf, sizeof(errbuf)));
+      ERROR("exec plugin: setegid (%i) failed: %s", egid, STRERRNO);
       exit(-1);
     }
   }
 
   status = setuid(uid);
   if (status != 0) {
-    ERROR("exec plugin: setuid (%i) failed: %s", uid,
-          sstrerror(errno, errbuf, sizeof(errbuf)));
+    ERROR("exec plugin: setuid (%i) failed: %s", uid, STRERRNO);
     exit(-1);
   }
 
+#ifdef HAVE_EXECVPE
+  execvpe(pl->exec, pl->argv, envp);
+#else
+  environ = envp;
   execvp(pl->exec, pl->argv);
+#endif
 
-  ERROR("exec plugin: Failed to execute ``%s'': %s", pl->exec,
-        sstrerror(errno, errbuf, sizeof(errbuf)));
+  ERROR("exec plugin: Failed to execute ``%s'': %s", pl->exec, STRERRNO);
   exit(-1);
 } /* void exec_child }}} */
 
@@ -330,14 +314,12 @@ static void reset_signal_mask(void) /* {{{ */
 
 static int create_pipe(int fd_pipe[2]) /* {{{ */
 {
-  char errbuf[1024];
   int status;
 
   status = pipe(fd_pipe);
   if (status != 0) {
-    ERROR("exec plugin: pipe failed: %s",
-          sstrerror(errno, errbuf, sizeof(errbuf)));
-    return (-1);
+    ERROR("exec plugin: pipe failed: %s", STRERRNO);
+    return -1;
   }
 
   return 0;
@@ -401,9 +383,7 @@ static int getegr_id(program_list_t *pl, int gid) /* {{{ */
     } else if (errno == ERANGE) {
       grbuf_size += grbuf_size; // increment buffer size and try again
     } else {
-      char errbuf[1024];
-      ERROR("exec plugin: getegr_id failed %s",
-            sstrerror(errno, errbuf, sizeof(errbuf)));
+      ERROR("exec plugin: getegr_id failed %s", STRERRNO);
       sfree(grbuf);
       return -2;
     }
@@ -411,7 +391,7 @@ static int getegr_id(program_list_t *pl, int gid) /* {{{ */
   ERROR("exec plugin: getegr_id Max grbuf size reached  for %s", pl->group);
   sfree(grbuf);
   return -2;
-} /* }}} */
+}
 
 /*
  * Creates three pipes (one for reading, one for writing and one for errors),
@@ -425,7 +405,6 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
   int fd_pipe_in[2] = {-1, -1};
   int fd_pipe_out[2] = {-1, -1};
   int fd_pipe_err[2] = {-1, -1};
-  char errbuf[1024];
   int status;
   int pid;
 
@@ -437,7 +416,7 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
   struct passwd sp;
 
   if (pl->pid != 0)
-    return (-1);
+    return -1;
 
   long int nambuf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
   if (nambuf_size <= 0)
@@ -454,7 +433,7 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
   status = getpwnam_r(pl->user, &sp, nambuf, sizeof(nambuf), &sp_ptr);
   if (status != 0) {
     ERROR("exec plugin: Failed to get user information for user ``%s'': %s",
-          pl->user, sstrerror(status, errbuf, sizeof(errbuf)));
+          pl->user, STRERROR(status));
     goto failed;
   }
 
@@ -470,21 +449,49 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
     goto failed;
   }
 
+  /* The group configured in the configfile is set as effective group, because
+   * this way the forked process can (re-)gain the user's primary group. */
   egid = getegr_id(pl, gid);
   if (egid == -2) {
     goto failed;
   }
 
+  double interval = CDTIME_T_TO_DOUBLE(plugin_get_interval());
+
   pid = fork();
   if (pid < 0) {
-    ERROR("exec plugin: fork failed: %s",
-          sstrerror(errno, errbuf, sizeof(errbuf)));
+    ERROR("exec plugin: fork failed: %s", STRERRNO);
     goto failed;
   } else if (pid == 0) {
-    int fd_num;
+    char interval_buf[128];
+    snprintf(interval_buf, sizeof(interval_buf), "COLLECTD_INTERVAL=%.3f",
+             interval);
+
+    /* max hostname len is 255, so this should be enough */
+    char hostname_buf[300];
+    snprintf(hostname_buf, sizeof(hostname_buf), "COLLECTD_HOSTNAME=%s",
+             hostname_g);
+
+    size_t env_size = 0;
+    while (environ[env_size] != NULL) {
+      ++env_size;
+    }
+
+    /* Copy the environment variables */
+    char *envp[env_size + 3];
+    size_t envp_idx;
+    for (envp_idx = 0; environ[envp_idx] != NULL && envp_idx < env_size;
+         ++envp_idx) {
+      envp[envp_idx] = environ[envp_idx];
+    }
+
+    /* Add the collectd environment variables */
+    envp[envp_idx++] = interval_buf;
+    envp[envp_idx++] = hostname_buf;
+    envp[envp_idx++] = NULL;
 
     /* Close all file descriptors but the pipe end we need. */
-    fd_num = getdtablesize();
+    int fd_num = getdtablesize();
     for (int fd = 0; fd < fd_num; fd++) {
       if ((fd == fd_pipe_in[0]) || (fd == fd_pipe_out[1]) ||
           (fd == fd_pipe_err[1]))
@@ -510,12 +517,10 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
       close(fd_pipe_err[1]);
     }
 
-    set_environment();
-
     /* Unblock all signals */
     reset_signal_mask();
 
-    exec_child(pl, uid, gid, egid);
+    exec_child(pl, envp, uid, gid, egid);
     /* does not return */
   }
 
@@ -538,26 +543,26 @@ static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
   else
     close(fd_pipe_err[0]);
 
-  return (pid);
+  return pid;
 
 failed:
   close_pipe(fd_pipe_in);
   close_pipe(fd_pipe_out);
   close_pipe(fd_pipe_err);
 
-  return (-1);
+  return -1;
 } /* int fork_child }}} */
 
 static int parse_line(char *buffer) /* {{{ */
 {
   if (strncasecmp("PUTVAL", buffer, strlen("PUTVAL")) == 0)
-    return (cmd_handle_putval(stdout, buffer));
+    return cmd_handle_putval(stdout, buffer);
   else if (strncasecmp("PUTNOTIF", buffer, strlen("PUTNOTIF")) == 0)
-    return (handle_putnotif(stdout, buffer));
+    return handle_putnotif(stdout, buffer);
   else {
     ERROR("exec plugin: Unable to parse command, ignoring line: \"%s\"",
           buffer);
-    return (-1);
+    return -1;
   }
 } /* int parse_line }}} */
 
@@ -706,7 +711,7 @@ static void *exec_read_one(void *arg) /* {{{ */
     close(fd_err);
 
   pthread_exit((void *)0);
-  return (NULL);
+  return NULL;
 } /* void *exec_read_one }}} */
 
 static void *exec_notification_one(void *arg) /* {{{ */
@@ -727,9 +732,7 @@ static void *exec_notification_one(void *arg) /* {{{ */
 
   fh = fdopen(fd, "w");
   if (fh == NULL) {
-    char errbuf[1024];
-    ERROR("exec plugin: fdopen (%i) failed: %s", fd,
-          sstrerror(errno, errbuf, sizeof(errbuf)));
+    ERROR("exec plugin: fdopen (%i) failed: %s", fd, STRERRNO);
     kill(pid, SIGTERM);
     close(fd);
     sfree(arg);
@@ -742,8 +745,9 @@ static void *exec_notification_one(void *arg) /* {{{ */
   else if (n->severity == NOTIF_OKAY)
     severity = "OKAY";
 
-  fprintf(fh, "Severity: %s\n"
-              "Time: %.3f\n",
+  fprintf(fh,
+          "Severity: %s\n"
+          "Time: %.3f\n",
           severity, CDTIME_T_TO_DOUBLE(n->time));
 
   /* Print the optional fields */
@@ -788,7 +792,7 @@ static void *exec_notification_one(void *arg) /* {{{ */
   n->meta = NULL;
   sfree(arg);
   pthread_exit((void *)0);
-  return (NULL);
+  return NULL;
 } /* void *exec_notification_one }}} */
 
 static int exec_init(void) /* {{{ */
@@ -814,7 +818,7 @@ static int exec_init(void) /* {{{ */
   }
 #endif
 
-  return (0);
+  return 0;
 } /* int exec_init }}} */
 
 static int exec_read(void) /* {{{ */
@@ -846,7 +850,7 @@ static int exec_read(void) /* {{{ */
     pthread_attr_destroy(&attr);
   } /* for (pl) */
 
-  return (0);
+  return 0;
 } /* int exec_read }}} */
 
 static int exec_notification(const notification_t *n, /* {{{ */
@@ -889,7 +893,7 @@ static int exec_notification(const notification_t *n, /* {{{ */
     pthread_attr_destroy(&attr);
   } /* for (pl) */
 
-  return (0);
+  return 0;
 } /* }}} int exec_notification */
 
 static int exec_shutdown(void) /* {{{ */
@@ -906,6 +910,11 @@ static int exec_shutdown(void) /* {{{ */
       INFO("exec plugin: Sent SIGTERM to %hu", (unsigned short int)pl->pid);
     }
 
+    for (int i = 0; pl->argv[i] != NULL; i++) {
+      sfree(pl->argv[i]);
+    }
+    sfree(pl->argv);
+    sfree(pl->exec);
     sfree(pl->user);
     sfree(pl);
 
@@ -913,7 +922,7 @@ static int exec_shutdown(void) /* {{{ */
   } /* while (pl) */
   pl_head = NULL;
 
-  return (0);
+  return 0;
 } /* int exec_shutdown }}} */
 
 void module_register(void) {
@@ -924,7 +933,3 @@ void module_register(void) {
                                /* user_data = */ NULL);
   plugin_register_shutdown("exec", exec_shutdown);
 } /* void module_register */
-
-/*
- * vim:shiftwidth=2:softtabstop=2:tabstop=8:fdm=marker
- */