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