exec plugin: Use `setgroups' to set the list of supplementary group IDs.
[collectd.git] / src / exec.c
1 /**
2  * collectd - src/exec.c
3  * Copyright (C) 2007  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "utils_cmd_putval.h"
26
27 #include <sys/types.h>
28 #include <pwd.h>
29 #include <grp.h>
30 #include <signal.h>
31
32 #include <pthread.h>
33
34 /*
35  * Private data types
36  */
37 struct program_list_s;
38 typedef struct program_list_s program_list_t;
39 struct program_list_s
40 {
41   char           *user;
42   char           *group;
43   char           *exec;
44   int             pid;
45   program_list_t *next;
46 };
47
48 /*
49  * Private variables
50  */
51 static const char *config_keys[] =
52 {
53   "Exec"
54 };
55 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
56
57 static program_list_t *pl_head = NULL;
58
59 /*
60  * Functions
61  */
62 static int exec_config (const char *key, const char *value)
63 {
64   if (strcasecmp ("Exec", key) == 0)
65   {
66     program_list_t *pl;
67     pl = (program_list_t *) malloc (sizeof (program_list_t));
68     if (pl == NULL)
69       return (1);
70     memset (pl, '\0', sizeof (program_list_t));
71
72     pl->user = strdup (value);
73     if (pl->user == NULL)
74     {
75       sfree (pl);
76       return (1);
77     }
78
79     pl->exec = strchr (pl->user, ' ');
80     if (pl->exec == NULL)
81     {
82       sfree (pl->user);
83       sfree (pl);
84       return (1);
85     }
86     while (*pl->exec == ' ')
87     {
88       *pl->exec = '\0';
89       pl->exec++;
90     }
91
92     if (*pl->exec == '\0')
93     {
94       sfree (pl->user);
95       sfree (pl);
96       return (1);
97     }
98
99     pl->next = pl_head;
100     pl_head = pl;
101
102     pl->group = strchr (pl->user, ':');
103     if (NULL != pl->group) {
104       *pl->group = '\0';
105       pl->group++;
106     }
107   }
108   else
109   {
110     return (-1);
111   }
112
113   return (0);
114 } /* int exec_config */
115
116 static void exec_child (program_list_t *pl)
117 {
118   int status;
119   int uid;
120   int gid;
121   int egid;
122   char *arg0;
123
124   struct passwd *sp_ptr;
125   struct passwd sp;
126   char nambuf[2048];
127   char errbuf[1024];
128
129   sp_ptr = NULL;
130   status = getpwnam_r (pl->user, &sp, nambuf, sizeof (nambuf), &sp_ptr);
131   if (status != 0)
132   {
133     ERROR ("exec plugin: getpwnam_r failed: %s",
134         sstrerror (errno, errbuf, sizeof (errbuf)));
135     exit (-1);
136   }
137   if (sp_ptr == NULL)
138   {
139     ERROR ("exec plugin: No such user: `%s'", pl->user);
140     exit (-1);
141   }
142
143   uid = sp.pw_uid;
144   gid = sp.pw_gid;
145   if (uid == 0)
146   {
147     ERROR ("exec plugin: Cowardly refusing to exec program as root.");
148     exit (-1);
149   }
150
151   /* The group configured in the configfile is set as effective group, because
152    * this way the forked process can (re-)gain the user's primary group. */
153   egid = -1;
154   if (NULL != pl->group)
155   {
156     if ('\0' != *pl->group) {
157       struct group *gr_ptr = NULL;
158       struct group gr;
159
160       status = getgrnam_r (pl->group, &gr, nambuf, sizeof (nambuf), &gr_ptr);
161       if (0 != status)
162       {
163         ERROR ("exec plugin: getgrnam_r failed: %s",
164             sstrerror (errno, errbuf, sizeof (errbuf)));
165         exit (-1);
166       }
167       if (NULL == gr_ptr)
168       {
169         ERROR ("exec plugin: No such group: `%s'", pl->group);
170         exit (-1);
171       }
172
173       egid = gr.gr_gid;
174     }
175     else
176     {
177       egid = gid;
178     }
179   } /* if (pl->group == NULL) */
180
181 #if HAVE_SETGROUPS
182   if (getuid () == 0)
183   {
184     gid_t  glist[2];
185     size_t glist_len;
186
187     glist[0] = gid;
188     glist_len = 1;
189
190     if (gid != egid)
191     {
192       glist[1] = egid;
193       glist_len = 2;
194     }
195
196     setgroups (glist_len, glist);
197   }
198 #endif /* HAVE_SETGROUPS */
199
200   status = setgid (gid);
201   if (status != 0)
202   {
203     ERROR ("exec plugin: setgid (%i) failed: %s",
204         gid, sstrerror (errno, errbuf, sizeof (errbuf)));
205     exit (-1);
206   }
207
208   if (egid != -1)
209   {
210     status = setegid (egid);
211     if (status != 0)
212     {
213       ERROR ("exec plugin: setegid (%i) failed: %s",
214           egid, sstrerror (errno, errbuf, sizeof (errbuf)));
215       exit (-1);
216     }
217   }
218
219   status = setuid (uid);
220   if (status != 0)
221   {
222     ERROR ("exec plugin: setuid (%i) failed: %s",
223         uid, sstrerror (errno, errbuf, sizeof (errbuf)));
224     exit (-1);
225   }
226
227   arg0 = strrchr (pl->exec, '/');
228   if (arg0 != NULL)
229     arg0++;
230   if ((arg0 == NULL) || (*arg0 == '\0'))
231     arg0 = pl->exec;
232
233   status = execlp (pl->exec, arg0, (char *) 0);
234
235   ERROR ("exec plugin: exec failed: %s",
236       sstrerror (errno, errbuf, sizeof (errbuf)));
237   exit (-1);
238 } /* void exec_child */
239
240 static int fork_child (program_list_t *pl)
241 {
242   int fd_pipe[2];
243   int status;
244
245   if (pl->pid != 0)
246     return (-1);
247
248   status = pipe (fd_pipe);
249   if (status != 0)
250   {
251     char errbuf[1024];
252     ERROR ("exec plugin: pipe failed: %s",
253         sstrerror (errno, errbuf, sizeof (errbuf)));
254     return (-1);
255   }
256
257   pl->pid = fork ();
258   if (pl->pid < 0)
259   {
260     char errbuf[1024];
261     ERROR ("exec plugin: fork failed: %s",
262         sstrerror (errno, errbuf, sizeof (errbuf)));
263     return (-1);
264   }
265   else if (pl->pid == 0)
266   {
267     close (fd_pipe[0]);
268
269     /* Connect the pipe to STDOUT and STDERR */
270     if (fd_pipe[1] != STDOUT_FILENO)
271       dup2 (fd_pipe[1], STDOUT_FILENO);
272     if (fd_pipe[1] != STDERR_FILENO)
273       dup2 (fd_pipe[1], STDERR_FILENO);
274     if ((fd_pipe[1] != STDOUT_FILENO) && (fd_pipe[1] != STDERR_FILENO))
275       close (fd_pipe[1]);
276
277     exec_child (pl);
278     /* does not return */
279   }
280
281   close (fd_pipe[1]);
282   return (fd_pipe[0]);
283 } /* int fork_child */
284
285 static int parse_line (char *buffer)
286 {
287   char *fields[256];
288   int fields_num;
289
290   fields[0] = "PUTVAL";
291   fields_num = strsplit (buffer, &fields[1], STATIC_ARRAY_SIZE(fields) - 1);
292
293   handle_putval (stdout, fields, fields_num + 1);
294   return (0);
295 } /* int parse_line */
296
297 static void *exec_read_one (void *arg)
298 {
299   program_list_t *pl = (program_list_t *) arg;
300   int fd;
301   FILE *fh;
302   char buffer[1024];
303
304   fd = fork_child (pl);
305   if (fd < 0)
306     pthread_exit ((void *) 1);
307
308   assert (pl->pid != 0);
309
310   fh = fdopen (fd, "r");
311   if (fh == NULL)
312   {
313     char errbuf[1024];
314     ERROR ("exec plugin: fdopen (%i) failed: %s", fd,
315         sstrerror (errno, errbuf, sizeof (errbuf)));
316     kill (pl->pid, SIGTERM);
317     pl->pid = 0;
318     close (fd);
319     pthread_exit ((void *) 1);
320   }
321
322   while (fgets (buffer, sizeof (buffer), fh) != NULL)
323   {
324     int len;
325
326     len = strlen (buffer);
327
328     /* Remove newline from end. */
329     while ((len > 0) && ((buffer[len - 1] == '\n')
330           || (buffer[len - 1] == '\r')))
331       buffer[--len] = '\0';
332
333     DEBUG ("exec plugin: exec_read_one: buffer = %s", buffer);
334
335     parse_line (buffer);
336   } /* while (fgets) */
337
338   fclose (fh);
339   pl->pid = 0;
340
341   pthread_exit ((void *) 0);
342   return (NULL);
343 } /* void *exec_read_one */
344
345 static int exec_read (void)
346 {
347   program_list_t *pl;
348
349   for (pl = pl_head; pl != NULL; pl = pl->next)
350   {
351     pthread_t t;
352     pthread_attr_t attr;
353
354     if (pl->pid != 0)
355       continue;
356
357     pthread_attr_init (&attr);
358     pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
359     pthread_create (&t, &attr, exec_read_one, (void *) pl);
360   } /* for (pl) */
361
362   return (0);
363 } /* int exec_read */
364
365 static int exec_shutdown (void)
366 {
367   program_list_t *pl;
368   program_list_t *next;
369
370   pl = pl_head;
371   while (pl != NULL)
372   {
373     next = pl->next;
374
375     if (pl->pid > 0)
376     {
377       kill (pl->pid, SIGTERM);
378       INFO ("exec plugin: Sent SIGTERM to %hu", (unsigned short int) pl->pid);
379     }
380
381     sfree (pl->user);
382     sfree (pl);
383
384     pl = next;
385   } /* while (pl) */
386   pl_head = NULL;
387
388   return (0);
389 } /* int exec_shutdown */
390
391 void module_register (void)
392 {
393   plugin_register_config ("exec", exec_config, config_keys, config_keys_num);
394   plugin_register_read ("exec", exec_read);
395   plugin_register_shutdown ("exec", exec_shutdown);
396 } /* void module_register */
397
398 /*
399  * vim:shiftwidth=2:softtabstop=2:tabstop=8
400  */