#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>
#include <sys/capability.h>
#endif
+extern char **environ;
+
#define PL_NORMAL 0x01
#define PL_NOTIF_ACTION 0x02
} program_list_and_notification_t;
/*
+ * constants
+ */
+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;
/*
return 0;
} /* int exec_config }}} */
-static void set_environment(void) /* {{{ */
-{
- char buffer[1024];
-
-#ifdef HAVE_SETENV
- snprintf(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
- snprintf(buffer, sizeof(buffer), "COLLECTD_INTERVAL=%.3f",
- CDTIME_T_TO_DOUBLE(plugin_get_interval()));
- putenv(buffer);
-
- snprintf(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) {
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 }}} */
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)));
+ ERROR("exec plugin: pipe failed: %s", STRERRNO);
return -1;
}
} /* }}} void close_pipe */
/*
+ * Get effective group ID from group name.
+ * Input arguments:
+ * pl :program list struct with group name
+ * gid :group id to fallback in case egid cannot be determined.
+ * Returns:
+ * egid effective group id if successfull,
+ * -1 if group is not defined/not found.
+ * -2 for any buffer allocation error.
+ */
+static int getegr_id(program_list_t *pl, int gid) /* {{{ */
+{
+ if (pl->group == NULL) {
+ return -1;
+ }
+ if (strcmp(pl->group, "") == 0) {
+ return gid;
+ }
+ struct group *gr_ptr = NULL;
+ struct group gr;
+
+ long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX);
+ if (grbuf_size <= 0)
+ grbuf_size = sysconf(_SC_PAGESIZE);
+ if (grbuf_size <= 0)
+ grbuf_size = 4096;
+
+ char *temp = NULL;
+ char *grbuf = NULL;
+
+ do {
+ temp = realloc(grbuf, grbuf_size);
+ if (temp == NULL) {
+ ERROR("exec plugin: getegr_id for %s: realloc buffer[%ld] failed ",
+ pl->group, grbuf_size);
+ sfree(grbuf);
+ return -2;
+ }
+ grbuf = temp;
+ if (getgrnam_r(pl->group, &gr, grbuf, grbuf_size, &gr_ptr) == 0) {
+ sfree(grbuf);
+ if (gr_ptr == NULL) {
+ ERROR("exec plugin: No such group: `%s'", pl->group);
+ return -1;
+ }
+ return gr.gr_gid;
+ } else if (errno == ERANGE) {
+ grbuf_size += grbuf_size; // increment buffer size and try again
+ } else {
+ ERROR("exec plugin: getegr_id failed %s", STRERRNO);
+ sfree(grbuf);
+ return -2;
+ }
+ } while (grbuf_size <= MAX_GRBUF_SIZE);
+ 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),
* forks a child, sets up the pipes so that fd_in is connected to STDIN of
* the child and fd_out is connected to STDOUT and fd_err is connected to STDERR
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;
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;
}
/* 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 = -1;
- if (pl->group != NULL) {
- if (*pl->group != '\0') {
- struct group *gr_ptr = NULL;
- struct group gr;
-
- long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX);
- if (grbuf_size <= 0)
- grbuf_size = sysconf(_SC_PAGESIZE);
- if (grbuf_size <= 0)
- grbuf_size = 4096;
- char grbuf[grbuf_size];
-
- status = getgrnam_r(pl->group, &gr, grbuf, sizeof(grbuf), &gr_ptr);
- if (status != 0) {
- ERROR("exec plugin: Failed to get group information "
- "for group ``%s'': %s",
- pl->group, sstrerror(status, errbuf, sizeof(errbuf)));
- goto failed;
- }
- if (gr_ptr == NULL) {
- ERROR("exec plugin: No such group: `%s'", pl->group);
- goto failed;
- }
+ egid = getegr_id(pl, gid);
+ if (egid == -2) {
+ goto failed;
+ }
- egid = gr.gr_gid;
- } else {
- egid = gid;
- }
- } /* if (pl->group == NULL) */
+ 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]))
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 */
}
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);
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 */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- plugin_thread_create(&t, &attr, exec_read_one, (void *)pl, "exec read");
+ int status =
+ plugin_thread_create(&t, &attr, exec_read_one, (void *)pl, "exec read");
+ if (status != 0) {
+ ERROR("exec plugin: plugin_thread_create failed.");
+ }
pthread_attr_destroy(&attr);
} /* for (pl) */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- plugin_thread_create(&t, &attr, exec_notification_one, (void *)pln,
- "exec notify");
+ int status = plugin_thread_create(&t, &attr, exec_notification_one,
+ (void *)pln, "exec notify");
+ if (status != 0) {
+ ERROR("exec plugin: plugin_thread_create failed.");
+ }
pthread_attr_destroy(&attr);
} /* for (pl) */
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);