Collectd::Unixsock: Added license information.
[collectd.git] / src / exec.c
1 /**
2  * collectd - src/exec.c
3  * Copyright (C) 2007,2008  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
26 #include "utils_cmd_putval.h"
27 #include "utils_cmd_putnotif.h"
28
29 #include <sys/types.h>
30 #include <pwd.h>
31 #include <grp.h>
32 #include <signal.h>
33
34 #include <pthread.h>
35
36 #define PL_NORMAL        0x01
37 #define PL_NOTIF_ACTION  0x02
38 #define PL_NAGIOS_PLUGIN 0x04
39
40 #define PL_RUNNING       0x10
41
42 /*
43  * Private data types
44  */
45 /*
46  * Access to this structure is serialized using the `pl_lock' lock and the
47  * `PL_RUNNING' flag. The execution of notifications is *not* serialized, so
48  * all functions used to handle notifications MUST NOT write to this structure.
49  * The `pid' and `status' fields are thus unused if the `PL_NOTIF_ACTION' flag
50  * is set.
51  * The `PL_RUNNING' flag is set in `exec_read' and unset in `exec_read_one'.
52  */
53 struct program_list_s;
54 typedef struct program_list_s program_list_t;
55 struct program_list_s
56 {
57   char           *user;
58   char           *group;
59   char           *exec;
60   char          **argv;
61   int             pid;
62   int             status;
63   int             flags;
64   program_list_t *next;
65 };
66
67 typedef struct program_list_and_notification_s
68 {
69   program_list_t *pl;
70   notification_t n;
71 } program_list_and_notification_t;
72
73 /*
74  * Private variables
75  */
76 static program_list_t *pl_head = NULL;
77 static pthread_mutex_t pl_lock = PTHREAD_MUTEX_INITIALIZER;
78
79 /*
80  * Functions
81  */
82 static void sigchld_handler (int signal) /* {{{ */
83 {
84   pid_t pid;
85   int status;
86   while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
87   {
88     program_list_t *pl;
89     for (pl = pl_head; pl != NULL; pl = pl->next)
90       if (pl->pid == pid)
91         break;
92     if (pl != NULL)
93       pl->status = status;
94   } /* while (waitpid) */
95 } /* void sigchld_handler }}} */
96
97 static int exec_config_exec (oconfig_item_t *ci) /* {{{ */
98 {
99   program_list_t *pl;
100   char buffer[128];
101   int i;
102
103   if (ci->children_num != 0)
104   {
105     WARNING ("exec plugin: The config option `%s' may not be a block.",
106         ci->key);
107     return (-1);
108   }
109   if (ci->values_num < 2)
110   {
111     WARNING ("exec plugin: The config option `%s' needs at least two "
112         "arguments.", ci->key);
113     return (-1);
114   }
115   if ((ci->values[0].type != OCONFIG_TYPE_STRING)
116       || (ci->values[1].type != OCONFIG_TYPE_STRING))
117   {
118     WARNING ("exec plugin: The first two arguments to the `%s' option must "
119         "be string arguments.", ci->key);
120     return (-1);
121   }
122
123   pl = (program_list_t *) malloc (sizeof (program_list_t));
124   if (pl == NULL)
125   {
126     ERROR ("exec plugin: malloc failed.");
127     return (-1);
128   }
129   memset (pl, '\0', sizeof (program_list_t));
130
131   if (strcasecmp ("NagiosExec", ci->key) == 0)
132     pl->flags |= PL_NAGIOS_PLUGIN;
133   else if (strcasecmp ("NotificationExec", ci->key) == 0)
134     pl->flags |= PL_NOTIF_ACTION;
135   else
136     pl->flags |= PL_NORMAL;
137
138   pl->user = strdup (ci->values[0].value.string);
139   if (pl->user == NULL)
140   {
141     ERROR ("exec plugin: strdup failed.");
142     sfree (pl);
143     return (-1);
144   }
145
146   pl->group = strchr (pl->user, ':');
147   if (pl->group != NULL)
148   {
149     *pl->group = '\0';
150     pl->group++;
151   }
152
153   pl->exec = strdup (ci->values[1].value.string);
154   if (pl->exec == NULL)
155   {
156     ERROR ("exec plugin: strdup failed.");
157     sfree (pl->user);
158     sfree (pl);
159     return (-1);
160   }
161
162   pl->argv = (char **) malloc (ci->values_num * sizeof (char *));
163   if (pl->argv == NULL)
164   {
165     ERROR ("exec plugin: malloc failed.");
166     sfree (pl->exec);
167     sfree (pl->user);
168     sfree (pl);
169     return (-1);
170   }
171   memset (pl->argv, '\0', ci->values_num * sizeof (char *));
172
173   {
174     char *tmp = strrchr (ci->values[1].value.string, '/');
175     if (tmp == NULL)
176       strncpy (buffer, ci->values[1].value.string, sizeof (buffer));
177     else
178       strncpy (buffer, tmp + 1, sizeof (buffer));
179     buffer[sizeof (buffer) - 1] = '\0';
180   }
181   pl->argv[0] = strdup (buffer);
182   if (pl->argv[0] == NULL)
183   {
184     ERROR ("exec plugin: malloc failed.");
185     sfree (pl->argv);
186     sfree (pl->exec);
187     sfree (pl->user);
188     sfree (pl);
189     return (-1);
190   }
191
192   for (i = 1; i < (ci->values_num - 1); i++)
193   {
194     if (ci->values[i + 1].type == OCONFIG_TYPE_STRING)
195     {
196       pl->argv[i] = strdup (ci->values[i + 1].value.string);
197     }
198     else
199     {
200       if (ci->values[i + 1].type == OCONFIG_TYPE_NUMBER)
201       {
202         snprintf (buffer, sizeof (buffer), "%lf",
203             ci->values[i + 1].value.number);
204       }
205       else
206       {
207         if (ci->values[i + 1].value.boolean)
208           strncpy (buffer, "true", sizeof (buffer));
209         else
210           strncpy (buffer, "false", sizeof (buffer));
211       }
212       buffer[sizeof (buffer) - 1] = '\0';
213
214       pl->argv[i] = strdup (buffer);
215     }
216
217     if (pl->argv[i] == NULL)
218     {
219       ERROR ("exec plugin: strdup failed.");
220       break;
221     }
222   } /* for (i) */
223
224   if (i < (ci->values_num - 1))
225   {
226     while ((--i) >= 0)
227     {
228       sfree (pl->argv[i]);
229     }
230     sfree (pl->argv);
231     sfree (pl->exec);
232     sfree (pl->user);
233     sfree (pl);
234     return (-1);
235   }
236
237   for (i = 0; pl->argv[i] != NULL; i++)
238   {
239     DEBUG ("exec plugin: argv[%i] = %s", i, pl->argv[i]);
240   }
241
242   pl->next = pl_head;
243   pl_head = pl;
244
245   return (0);
246 } /* int exec_config_exec }}} */
247
248 static int exec_config (oconfig_item_t *ci) /* {{{ */
249 {
250   int i;
251
252   for (i = 0; i < ci->children_num; i++)
253   {
254     oconfig_item_t *child = ci->children + i;
255     if ((strcasecmp ("Exec", child->key) == 0)
256         || (strcasecmp ("NagiosExec", child->key) == 0)
257         || (strcasecmp ("NotificationExec", child->key) == 0))
258       exec_config_exec (child);
259     else
260     {
261       WARNING ("exec plugin: Unknown config option `%s'.", child->key);
262     }
263   } /* for (i) */
264
265   return (0);
266 } /* int exec_config }}} */
267
268 static void exec_child (program_list_t *pl) /* {{{ */
269 {
270   int status;
271   int uid;
272   int gid;
273   int egid;
274
275   struct passwd *sp_ptr;
276   struct passwd sp;
277   char nambuf[2048];
278   char errbuf[1024];
279
280   sp_ptr = NULL;
281   status = getpwnam_r (pl->user, &sp, nambuf, sizeof (nambuf), &sp_ptr);
282   if (status != 0)
283   {
284     ERROR ("exec plugin: getpwnam_r failed: %s",
285         sstrerror (errno, errbuf, sizeof (errbuf)));
286     exit (-1);
287   }
288   if (sp_ptr == NULL)
289   {
290     ERROR ("exec plugin: No such user: `%s'", pl->user);
291     exit (-1);
292   }
293
294   uid = sp.pw_uid;
295   gid = sp.pw_gid;
296   if (uid == 0)
297   {
298     ERROR ("exec plugin: Cowardly refusing to exec program as root.");
299     exit (-1);
300   }
301
302   /* The group configured in the configfile is set as effective group, because
303    * this way the forked process can (re-)gain the user's primary group. */
304   egid = -1;
305   if (NULL != pl->group)
306   {
307     if ('\0' != *pl->group) {
308       struct group *gr_ptr = NULL;
309       struct group gr;
310
311       status = getgrnam_r (pl->group, &gr, nambuf, sizeof (nambuf), &gr_ptr);
312       if (0 != status)
313       {
314         ERROR ("exec plugin: getgrnam_r failed: %s",
315             sstrerror (errno, errbuf, sizeof (errbuf)));
316         exit (-1);
317       }
318       if (NULL == gr_ptr)
319       {
320         ERROR ("exec plugin: No such group: `%s'", pl->group);
321         exit (-1);
322       }
323
324       egid = gr.gr_gid;
325     }
326     else
327     {
328       egid = gid;
329     }
330   } /* if (pl->group == NULL) */
331
332   status = setgid (gid);
333   if (status != 0)
334   {
335     ERROR ("exec plugin: setgid (%i) failed: %s",
336         gid, sstrerror (errno, errbuf, sizeof (errbuf)));
337     exit (-1);
338   }
339
340   if (egid != -1)
341   {
342     status = setegid (egid);
343     if (status != 0)
344     {
345       ERROR ("exec plugin: setegid (%i) failed: %s",
346           egid, sstrerror (errno, errbuf, sizeof (errbuf)));
347       exit (-1);
348     }
349   }
350
351   status = setuid (uid);
352   if (status != 0)
353   {
354     ERROR ("exec plugin: setuid (%i) failed: %s",
355         uid, sstrerror (errno, errbuf, sizeof (errbuf)));
356     exit (-1);
357   }
358
359   status = execvp (pl->exec, pl->argv);
360
361   ERROR ("exec plugin: exec failed: %s",
362       sstrerror (errno, errbuf, sizeof (errbuf)));
363   exit (-1);
364 } /* void exec_child }}} */
365
366 /*
367  * Creates two pipes (one for reading, ong for writing), forks a child, sets up
368  * the pipes so that fd_in is connected to STDIN of the child and fd_out is
369  * connected to STDOUT and STDERR of the child. Then is calls `exec_child'.
370  */
371 static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
372 {
373   int fd_pipe_in[2];
374   int fd_pipe_out[2];
375   int status;
376   int pid;
377
378   if (pl->pid != 0)
379     return (-1);
380
381   status = pipe (fd_pipe_in);
382   if (status != 0)
383   {
384     char errbuf[1024];
385     ERROR ("exec plugin: pipe failed: %s",
386         sstrerror (errno, errbuf, sizeof (errbuf)));
387     return (-1);
388   }
389
390   status = pipe (fd_pipe_out);
391   if (status != 0)
392   {
393     char errbuf[1024];
394     ERROR ("exec plugin: pipe failed: %s",
395         sstrerror (errno, errbuf, sizeof (errbuf)));
396     return (-1);
397   }
398
399   pid = fork ();
400   if (pid < 0)
401   {
402     char errbuf[1024];
403     ERROR ("exec plugin: fork failed: %s",
404         sstrerror (errno, errbuf, sizeof (errbuf)));
405     return (-1);
406   }
407   else if (pid == 0)
408   {
409     close (fd_pipe_in[1]);
410     close (fd_pipe_out[0]);
411
412     /* If the `out' pipe has the filedescriptor STDIN we have to be careful
413      * with the `dup's below. So, if this is the case we have to handle the
414      * `out' pipe first. */
415     if (fd_pipe_out[1] == STDIN_FILENO)
416     {
417       int new_fileno = (fd_pipe_in[0] == STDOUT_FILENO)
418         ? STDERR_FILENO : STDOUT_FILENO;
419       dup2 (fd_pipe_out[1], new_fileno);
420       close (fd_pipe_out[1]);
421       fd_pipe_out[1] = new_fileno;
422     }
423     /* Now `fd_pipe_out[1]' is either `STDOUT' or `STDERR', but definitely not
424      * `STDIN_FILENO'. */
425
426     /* Connect the `in' pipe to STDIN */
427     if (fd_pipe_in[0] != STDIN_FILENO)
428     {
429       dup2 (fd_pipe_in[0], STDIN_FILENO);
430       close (fd_pipe_in[0]);
431       fd_pipe_in[0] = STDIN_FILENO;
432     }
433
434     /* Now connect the `out' pipe to STDOUT and STDERR */
435     if (fd_pipe_out[1] != STDOUT_FILENO)
436       dup2 (fd_pipe_out[1], STDOUT_FILENO);
437     if (fd_pipe_out[1] != STDERR_FILENO)
438       dup2 (fd_pipe_out[1], STDERR_FILENO);
439
440     /* If the pipe has some FD that's something completely different, close it
441      * now. */
442     if ((fd_pipe_out[1] != STDOUT_FILENO) && (fd_pipe_out[1] != STDERR_FILENO))
443     {
444       close (fd_pipe_out[1]);
445       fd_pipe_out[1] = STDOUT_FILENO;
446     }
447
448     exec_child (pl);
449     /* does not return */
450   }
451
452   close (fd_pipe_in[0]);
453   close (fd_pipe_out[1]);
454
455   if (fd_in != NULL)
456     *fd_in = fd_pipe_in[1];
457   else
458     close (fd_pipe_in[1]);
459
460   if (fd_out != NULL)
461     *fd_out = fd_pipe_out[0];
462   else
463     close (fd_pipe_out[0]);
464
465   return (pid);
466 } /* int fork_child }}} */
467
468 static int parse_line (char *buffer) /* {{{ */
469 {
470   char *fields[256];
471   int fields_num;
472
473   fields[0] = "PUTVAL";
474   fields_num = strsplit (buffer, fields + 1, STATIC_ARRAY_SIZE(fields) - 1);
475
476   if (strcasecmp (fields[1], "putval") == 0)
477     return (handle_putval (stdout, fields + 1, fields_num));
478   else if (strcasecmp (fields[1], "putnotif") == 0)
479     return (handle_putnotif (stdout, fields + 1, fields_num));
480
481   /* compatibility code */
482   return (handle_putval (stdout, fields, fields_num + 1));
483 } /* int parse_line }}} */
484
485 static void *exec_read_one (void *arg) /* {{{ */
486 {
487   program_list_t *pl = (program_list_t *) arg;
488   int fd;
489   FILE *fh;
490   char buffer[1024];
491   int status;
492
493   status = fork_child (pl, NULL, &fd);
494   if (status < 0)
495     pthread_exit ((void *) 1);
496   pl->pid = status;
497
498   assert (pl->pid != 0);
499
500   fh = fdopen (fd, "r");
501   if (fh == NULL)
502   {
503     char errbuf[1024];
504     ERROR ("exec plugin: fdopen (%i) failed: %s", fd,
505         sstrerror (errno, errbuf, sizeof (errbuf)));
506     kill (pl->pid, SIGTERM);
507     pl->pid = 0;
508     close (fd);
509     pthread_exit ((void *) 1);
510   }
511
512   buffer[0] = '\0';
513   while (fgets (buffer, sizeof (buffer), fh) != NULL)
514   {
515     int len;
516
517     len = strlen (buffer);
518
519     /* Remove newline from end. */
520     while ((len > 0) && ((buffer[len - 1] == '\n')
521           || (buffer[len - 1] == '\r')))
522       buffer[--len] = '\0';
523
524     DEBUG ("exec plugin: exec_read_one: buffer = %s", buffer);
525
526     if (pl->flags & PL_NAGIOS_PLUGIN)
527       break;
528
529     parse_line (buffer);
530   } /* while (fgets) */
531
532   fclose (fh);
533
534   if (waitpid (pl->pid, &status, 0) > 0)
535     pl->status = status;
536
537   DEBUG ("exec plugin: Child %i exited with status %i.",
538       (int) pl->pid, pl->status);
539
540   if (pl->flags & PL_NAGIOS_PLUGIN)
541   {
542     notification_t n;
543
544     memset (&n, '\0', sizeof (n));
545     
546     n.severity = NOTIF_FAILURE;
547     if (pl->status == 0)
548       n.severity = NOTIF_OKAY;
549     else if (pl->status == 1)
550       n.severity = NOTIF_WARNING;
551
552     strncpy (n.message, buffer, sizeof (n.message));
553     n.message[sizeof (n.message) - 1] = '\0';
554
555     n.time = time (NULL);
556
557     strncpy (n.host, hostname_g, sizeof (n.host));
558     n.host[sizeof (n.host) - 1] = '\0';
559
560     plugin_dispatch_notification (&n);
561   }
562
563   pl->pid = 0;
564
565   pthread_mutex_lock (&pl_lock);
566   pl->flags &= ~PL_RUNNING;
567   pthread_mutex_unlock (&pl_lock);
568
569   pthread_exit ((void *) 0);
570   return (NULL);
571 } /* void *exec_read_one }}} */
572
573 static void *exec_notification_one (void *arg) /* {{{ */
574 {
575   program_list_t *pl = ((program_list_and_notification_t *) arg)->pl;
576   const notification_t *n = &((program_list_and_notification_t *) arg)->n;
577   int fd;
578   FILE *fh;
579   int pid;
580   int status;
581   const char *severity;
582
583   pid = fork_child (pl, &fd, NULL);
584   if (pid < 0)
585     pthread_exit ((void *) 1);
586
587   fh = fdopen (fd, "w");
588   if (fh == NULL)
589   {
590     char errbuf[1024];
591     ERROR ("exec plugin: fdopen (%i) failed: %s", fd,
592         sstrerror (errno, errbuf, sizeof (errbuf)));
593     kill (pl->pid, SIGTERM);
594     pl->pid = 0;
595     close (fd);
596     pthread_exit ((void *) 1);
597   }
598
599   severity = "FAILURE";
600   if (n->severity == NOTIF_WARNING)
601     severity = "WARNING";
602   else if (n->severity == NOTIF_OKAY)
603     severity = "OKAY";
604
605   fprintf (fh, "Severity: %s\n"
606       "Time: %u\n"
607       "Message: %s\n",
608       severity, (unsigned int) n->time, n->message);
609
610   /* Print the optional fields */
611   if (strlen (n->host) > 0)
612     fprintf (fh, "Host: %s\n", n->host);
613   if (strlen (n->plugin) > 0)
614     fprintf (fh, "Plugin: %s\n", n->plugin);
615   if (strlen (n->plugin_instance) > 0)
616     fprintf (fh, "PluginInstance: %s\n", n->plugin_instance);
617   if (strlen (n->type) > 0)
618     fprintf (fh, "Type: %s\n", n->type);
619   if (strlen (n->type_instance) > 0)
620     fprintf (fh, "TypeInstance: %s\n", n->type_instance);
621
622   /* Newline signalling end of data */
623   fprintf (fh, "\n");
624
625   fflush (fh);
626   fclose (fh);
627
628   waitpid (pid, &status, 0);
629
630   DEBUG ("exec plugin: Child %i exited with status %i.",
631       pid, status);
632
633   sfree (arg);
634   pthread_exit ((void *) 0);
635   return (NULL);
636 } /* void *exec_notification_one }}} */
637
638 static int exec_init (void) /* {{{ */
639 {
640   struct sigaction sa;
641
642   memset (&sa, '\0', sizeof (sa));
643   sa.sa_handler = sigchld_handler;
644   sigaction (SIGCHLD, &sa, NULL);
645
646   return (0);
647 } /* int exec_init }}} */
648
649 static int exec_read (void) /* {{{ */
650 {
651   program_list_t *pl;
652
653   for (pl = pl_head; pl != NULL; pl = pl->next)
654   {
655     pthread_t t;
656     pthread_attr_t attr;
657
658     /* Only execute `normal' and `nagios' style executables here. */
659     if ((pl->flags & (PL_NAGIOS_PLUGIN | PL_NORMAL)) == 0)
660       continue;
661
662     pthread_mutex_lock (&pl_lock);
663     /* Skip if a child is already running. */
664     if ((pl->flags & PL_RUNNING) != 0)
665     {
666       pthread_mutex_unlock (&pl_lock);
667       continue;
668     }
669     pl->flags |= PL_RUNNING;
670     pthread_mutex_unlock (&pl_lock);
671
672     pthread_attr_init (&attr);
673     pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
674     pthread_create (&t, &attr, exec_read_one, (void *) pl);
675   } /* for (pl) */
676
677   return (0);
678 } /* int exec_read }}} */
679
680 static int exec_notification (const notification_t *n)
681 {
682   program_list_t *pl;
683   program_list_and_notification_t *pln;
684
685   for (pl = pl_head; pl != NULL; pl = pl->next)
686   {
687     pthread_t t;
688     pthread_attr_t attr;
689
690     /* Only execute `notification' style executables here. */
691     if ((pl->flags & PL_NOTIF_ACTION) == 0)
692       continue;
693
694     /* Skip if a child is already running. */
695     if (pl->pid != 0)
696       continue;
697
698     pln = (program_list_and_notification_t *) malloc (sizeof
699         (program_list_and_notification_t));
700     if (pln == NULL)
701     {
702       ERROR ("exec plugin: malloc failed.");
703       continue;
704     }
705
706     pln->pl = pl;
707     memcpy (&pln->n, n, sizeof (notification_t));
708
709     pthread_attr_init (&attr);
710     pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
711     pthread_create (&t, &attr, exec_notification_one, (void *) pln);
712   } /* for (pl) */
713
714   return (0);
715 } /* int exec_notification */
716
717 static int exec_shutdown (void) /* {{{ */
718 {
719   program_list_t *pl;
720   program_list_t *next;
721
722   pl = pl_head;
723   while (pl != NULL)
724   {
725     next = pl->next;
726
727     if (pl->pid > 0)
728     {
729       kill (pl->pid, SIGTERM);
730       INFO ("exec plugin: Sent SIGTERM to %hu", (unsigned short int) pl->pid);
731     }
732
733     sfree (pl->user);
734     sfree (pl);
735
736     pl = next;
737   } /* while (pl) */
738   pl_head = NULL;
739
740   return (0);
741 } /* int exec_shutdown }}} */
742
743 void module_register (void)
744 {
745   plugin_register_complex_config ("exec", exec_config);
746   plugin_register_init ("exec", exec_init);
747   plugin_register_read ("exec", exec_read);
748   plugin_register_notification ("exec", exec_notification);
749   plugin_register_shutdown ("exec", exec_shutdown);
750 } /* void module_register */
751
752 /*
753  * vim:shiftwidth=2:softtabstop=2:tabstop=8:fdm=marker
754  */