exec plugin: Call `setgid' first so argitary groups can be configured.
[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   char *arg0;
122
123   struct passwd *sp_ptr;
124   struct passwd sp;
125   char nambuf[2048];
126   char errbuf[1024];
127
128   sp_ptr = NULL;
129   status = getpwnam_r (pl->user, &sp, nambuf, sizeof (nambuf), &sp_ptr);
130   if (status != 0)
131   {
132     ERROR ("exec plugin: getpwnam_r failed: %s",
133         sstrerror (errno, errbuf, sizeof (errbuf)));
134     exit (-1);
135   }
136   if (sp_ptr == NULL)
137   {
138     ERROR ("exec plugin: No such user: `%s'", pl->user);
139     exit (-1);
140   }
141
142   uid = sp.pw_uid;
143   if (uid == 0)
144   {
145     ERROR ("exec plugin: Cowardly refusing to exec program as root.");
146     exit (-1);
147   }
148
149   if (NULL != pl->group)
150   {
151     if ('\0' != *pl->group) {
152       struct group *gr_ptr = NULL;
153       struct group gr;
154
155       status = getgrnam_r (pl->group, &gr, nambuf, sizeof (nambuf), &gr_ptr);
156       if (0 != status)
157       {
158         ERROR ("exec plugin: getgrnam_r failed: %s",
159             sstrerror (errno, errbuf, sizeof (errbuf)));
160         exit (-1);
161       }
162       if (NULL == gr_ptr)
163       {
164         ERROR ("exec plugin: No such group: `%s'", pl->group);
165         exit (-1);
166       }
167
168       gid = gr.gr_gid;
169     }
170     else
171     {
172       gid = sp.pw_gid;
173     }
174
175     status = setgid (gid);
176     if (0 != status)
177     {
178       ERROR ("exec plugin: setgid failed: %s",
179           sstrerror (errno, errbuf, sizeof (errbuf)));
180       exit (-1);
181     }
182   } /* if (pl->group == NULL) */
183
184   status = setuid (uid);
185   if (status != 0)
186   {
187     ERROR ("exec plugin: setuid failed: %s",
188         sstrerror (errno, errbuf, sizeof (errbuf)));
189     exit (-1);
190   }
191
192   arg0 = strrchr (pl->exec, '/');
193   if (arg0 != NULL)
194     arg0++;
195   if ((arg0 == NULL) || (*arg0 == '\0'))
196     arg0 = pl->exec;
197
198   status = execlp (pl->exec, arg0, (char *) 0);
199
200   ERROR ("exec plugin: exec failed: %s",
201       sstrerror (errno, errbuf, sizeof (errbuf)));
202   exit (-1);
203 } /* void exec_child */
204
205 static int fork_child (program_list_t *pl)
206 {
207   int fd_pipe[2];
208   int status;
209
210   if (pl->pid != 0)
211     return (-1);
212
213   status = pipe (fd_pipe);
214   if (status != 0)
215   {
216     char errbuf[1024];
217     ERROR ("exec plugin: pipe failed: %s",
218         sstrerror (errno, errbuf, sizeof (errbuf)));
219     return (-1);
220   }
221
222   pl->pid = fork ();
223   if (pl->pid < 0)
224   {
225     char errbuf[1024];
226     ERROR ("exec plugin: fork failed: %s",
227         sstrerror (errno, errbuf, sizeof (errbuf)));
228     return (-1);
229   }
230   else if (pl->pid == 0)
231   {
232     close (fd_pipe[0]);
233
234     /* Connect the pipe to STDOUT and STDERR */
235     if (fd_pipe[1] != STDOUT_FILENO)
236       dup2 (fd_pipe[1], STDOUT_FILENO);
237     if (fd_pipe[1] != STDERR_FILENO)
238       dup2 (fd_pipe[1], STDERR_FILENO);
239     if ((fd_pipe[1] != STDOUT_FILENO) && (fd_pipe[1] != STDERR_FILENO))
240       close (fd_pipe[1]);
241
242     exec_child (pl);
243     /* does not return */
244   }
245
246   close (fd_pipe[1]);
247   return (fd_pipe[0]);
248 } /* int fork_child */
249
250 static int parse_line (char *buffer)
251 {
252   char *fields[256];
253   int fields_num;
254
255   fields[0] = "PUTVAL";
256   fields_num = strsplit (buffer, &fields[1], STATIC_ARRAY_SIZE(fields) - 1);
257
258   handle_putval (stdout, fields, fields_num + 1);
259   return (0);
260 } /* int parse_line */
261
262 static void *exec_read_one (void *arg)
263 {
264   program_list_t *pl = (program_list_t *) arg;
265   int fd;
266   FILE *fh;
267   char buffer[1024];
268
269   fd = fork_child (pl);
270   if (fd < 0)
271     pthread_exit ((void *) 1);
272
273   assert (pl->pid != 0);
274
275   fh = fdopen (fd, "r");
276   if (fh == NULL)
277   {
278     char errbuf[1024];
279     ERROR ("exec plugin: fdopen (%i) failed: %s", fd,
280         sstrerror (errno, errbuf, sizeof (errbuf)));
281     kill (pl->pid, SIGTERM);
282     close (fd);
283     pthread_exit ((void *) 1);
284   }
285
286   while (fgets (buffer, sizeof (buffer), fh) != NULL)
287   {
288     int len;
289
290     len = strlen (buffer);
291
292     /* Remove newline from end. */
293     while ((len > 0) && ((buffer[len - 1] == '\n')
294           || (buffer[len - 1] == '\r')))
295       buffer[--len] = '\0';
296
297     DEBUG ("exec plugin: exec_read_one: buffer = %s", buffer);
298
299     parse_line (buffer);
300   } /* while (fgets) */
301
302   fclose (fh);
303   pl->pid = 0;
304
305   pthread_exit ((void *) 0);
306   return (NULL);
307 } /* void *exec_read_one */
308
309 static int exec_read (void)
310 {
311   program_list_t *pl;
312
313   for (pl = pl_head; pl != NULL; pl = pl->next)
314   {
315     pthread_t t;
316     pthread_attr_t attr;
317
318     if (pl->pid != 0)
319       continue;
320
321     pthread_attr_init (&attr);
322     pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
323     pthread_create (&t, &attr, exec_read_one, (void *) pl);
324   } /* for (pl) */
325
326   return (0);
327 } /* int exec_read */
328
329 static int exec_shutdown (void)
330 {
331   program_list_t *pl;
332   program_list_t *next;
333
334   pl = pl_head;
335   while (pl != NULL)
336   {
337     next = pl->next;
338
339     if (pl->pid > 0)
340     {
341       kill (pl->pid, SIGTERM);
342       INFO ("exec plugin: Sent SIGTERM to %hu", (unsigned short int) pl->pid);
343     }
344
345     sfree (pl->user);
346     sfree (pl);
347
348     pl = next;
349   } /* while (pl) */
350   pl_head = NULL;
351
352   return (0);
353 } /* int exec_shutdown */
354
355 void module_register (void)
356 {
357   plugin_register_config ("exec", exec_config, config_keys, config_keys_num);
358   plugin_register_read ("exec", exec_read);
359   plugin_register_shutdown ("exec", exec_shutdown);
360 } /* void module_register */
361
362 /*
363  * vim:shiftwidth=2:softtabstop=2:tabstop=8
364  */