Back to index

nagios-plugins  1.4.16
popen.c
Go to the documentation of this file.
00001 /*****************************************************************************
00002 * 
00003 * Nagios plugins popen
00004 * 
00005 * License: GPL
00006 * Copyright (c) 2005-2007 Nagios Plugins Development Team
00007 * 
00008 * Description:
00009 * 
00010 * A safe alternative to popen
00011 * 
00012 * Provides spopen and spclose
00013 * 
00014 * FILE * spopen(const char *);
00015 * int spclose(FILE *);
00016 * 
00017 * Code taken with liitle modification from "Advanced Programming for the Unix
00018 * Environment" by W. Richard Stevens
00019 * 
00020 * This is considered safe in that no shell is spawned, and the environment
00021 * and path passed to the exec'd program are essentially empty. (popen create
00022 * a shell and passes the environment to it).
00023 * 
00024 * 
00025 * This program is free software: you can redistribute it and/or modify
00026 * it under the terms of the GNU General Public License as published by
00027 * the Free Software Foundation, either version 3 of the License, or
00028 * (at your option) any later version.
00029 * 
00030 * This program is distributed in the hope that it will be useful,
00031 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00032 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00033 * GNU General Public License for more details.
00034 * 
00035 * You should have received a copy of the GNU General Public License
00036 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00037 * 
00038 * 
00039 *****************************************************************************/
00040 
00041 #include "common.h"
00042 
00043 /* extern so plugin has pid to kill exec'd process on timeouts */
00044 extern int timeout_interval;
00045 extern pid_t *childpid;
00046 extern int *child_stderr_array;
00047 extern FILE *child_process;
00048 
00049 FILE *spopen (const char *);
00050 int spclose (FILE *);
00051 #ifdef REDHAT_SPOPEN_ERROR
00052 RETSIGTYPE popen_sigchld_handler (int);
00053 #endif
00054 RETSIGTYPE popen_timeout_alarm_handler (int);
00055 
00056 #include <stdarg.h>                                            /* ANSI C header file */
00057 #include <fcntl.h>
00058 
00059 #include <limits.h>
00060 #include <sys/resource.h>
00061 
00062 #ifdef HAVE_SYS_WAIT_H
00063 #include <sys/wait.h>
00064 #endif
00065 
00066 #ifndef WEXITSTATUS
00067 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
00068 #endif
00069 
00070 #ifndef WIFEXITED
00071 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
00072 #endif
00073 
00074 /* 4.3BSD Reno <signal.h> doesn't define SIG_ERR */
00075 #if defined(SIG_IGN) && !defined(SIG_ERR)
00076 #define       SIG_ERR       ((Sigfunc *)-1)
00077 #endif
00078 
00079 #define       min(a,b)      ((a) < (b) ? (a) : (b))
00080 #define       max(a,b)      ((a) > (b) ? (a) : (b))
00081 int open_max (void);                                    /* {Prog openmax} */
00082 static void err_sys (const char *, ...) __attribute__((noreturn,format(printf, 1, 2)));
00083 char *rtrim (char *, const char *);
00084 
00085 char *pname = NULL;                                            /* caller can set this from argv[0] */
00086 
00087 /*int *childerr = NULL;*//* ptr to array allocated at run-time */
00088 /*extern pid_t *childpid = NULL; *//* ptr to array allocated at run-time */
00089 static int maxfd;                                                     /* from our open_max(), {Prog openmax} */
00090 
00091 #ifdef REDHAT_SPOPEN_ERROR
00092 static volatile int childtermd = 0;
00093 #endif
00094 
00095 FILE *
00096 spopen (const char *cmdstring)
00097 {
00098        char *env[2];
00099        char *cmd = NULL;
00100        char **argv = NULL;
00101        char *str, *tmp;
00102        int argc;
00103 
00104        int i = 0, pfd[2], pfderr[2];
00105        pid_t pid;
00106 
00107 #ifdef        RLIMIT_CORE
00108        /* do not leave core files */
00109        struct rlimit limit;
00110        getrlimit (RLIMIT_CORE, &limit);
00111        limit.rlim_cur = 0;
00112        setrlimit (RLIMIT_CORE, &limit);
00113 #endif
00114 
00115        env[0] = strdup("LC_ALL=C");
00116        env[1] = '\0';
00117 
00118        /* if no command was passed, return with no error */
00119        if (cmdstring == NULL)
00120               return (NULL);
00121 
00122        /* make copy of command string so strtok() doesn't silently modify it */
00123        /* (the calling program may want to access it later) */
00124        cmd = malloc (strlen (cmdstring) + 1);
00125        if (cmd == NULL)
00126               return NULL;
00127        strcpy (cmd, cmdstring);
00128 
00129        /* This is not a shell, so we don't handle "???" */
00130        if (strstr (cmdstring, "\""))
00131               return NULL;
00132 
00133        /* allow single quotes, but only if non-whitesapce doesn't occur on both sides */
00134        if (strstr (cmdstring, " ' ") || strstr (cmdstring, "'''"))
00135               return NULL;
00136 
00137        /* there cannot be more args than characters */
00138        argc = strlen (cmdstring) + 1;     /* add 1 for NULL termination */
00139        argv = malloc (sizeof(char*)*argc);
00140 
00141        if (argv == NULL) {
00142               printf ("%s\n", _("Could not malloc argv array in popen()"));
00143               return NULL;
00144        }
00145 
00146        /* loop to get arguments to command */
00147        while (cmd) {
00148               str = cmd;
00149               str += strspn (str, " \t\r\n");    /* trim any leading whitespace */
00150 
00151               if (i >= argc - 2) {
00152                      printf ("%s\n",_("CRITICAL - You need more args!!!"));
00153                      return (NULL);
00154               }
00155 
00156               if (strstr (str, "'") == str) {    /* handle SIMPLE quoted strings */
00157                      str++;
00158                      if (!strstr (str, "'"))
00159                             return NULL;                                     /* balanced? */
00160                      cmd = 1 + strstr (str, "'");
00161                      str[strcspn (str, "'")] = 0;
00162               }
00163               else if (strcspn(str,"'") < strcspn (str, " \t\r\n")) {
00164                                                                       /* handle --option='foo bar' strings */
00165                      tmp = str + strcspn(str, "'") + 1;
00166                      if (!strstr (tmp, "'"))
00167                             return NULL;                                     /* balanced? */
00168                      tmp += strcspn(tmp,"'") + 1;
00169                      *tmp = 0;
00170                      cmd = tmp + 1;
00171               } else {
00172                      if (strpbrk (str, " \t\r\n")) {
00173                             cmd = 1 + strpbrk (str, " \t\r\n");
00174                             str[strcspn (str, " \t\r\n")] = 0;
00175                      }
00176                      else {
00177                             cmd = NULL;
00178                      }
00179               }
00180 
00181               if (cmd && strlen (cmd) == strspn (cmd, " \t\r\n"))
00182                      cmd = NULL;
00183 
00184               argv[i++] = str;
00185 
00186        }
00187        argv[i] = NULL;
00188 
00189        if (childpid == NULL) {                          /* first time through */
00190               maxfd = open_max ();                      /* allocate zeroed out array for child pids */
00191               if ((childpid = calloc ((size_t)maxfd, sizeof (pid_t))) == NULL)
00192                      return (NULL);
00193        }
00194 
00195        if (child_stderr_array == NULL) {  /* first time through */
00196               maxfd = open_max ();                      /* allocate zeroed out array for child pids */
00197               if ((child_stderr_array = calloc ((size_t)maxfd, sizeof (int))) == NULL)
00198                      return (NULL);
00199        }
00200 
00201        if (pipe (pfd) < 0)
00202               return (NULL);                                                 /* errno set by pipe() */
00203 
00204        if (pipe (pfderr) < 0)
00205               return (NULL);                                                 /* errno set by pipe() */
00206 
00207 #ifdef REDHAT_SPOPEN_ERROR
00208        if (signal (SIGCHLD, popen_sigchld_handler) == SIG_ERR) {
00209               usage4 (_("Cannot catch SIGCHLD"));
00210        }
00211 #endif
00212 
00213        if ((pid = fork ()) < 0)
00214               return (NULL);                                                 /* errno set by fork() */
00215        else if (pid == 0) {                             /* child */
00216               close (pfd[0]);
00217               if (pfd[1] != STDOUT_FILENO) {
00218                      dup2 (pfd[1], STDOUT_FILENO);
00219                      close (pfd[1]);
00220               }
00221               close (pfderr[0]);
00222               if (pfderr[1] != STDERR_FILENO) {
00223                      dup2 (pfderr[1], STDERR_FILENO);
00224                      close (pfderr[1]);
00225               }
00226               /* close all descriptors in childpid[] */
00227               for (i = 0; i < maxfd; i++)
00228                      if (childpid[i] > 0)
00229                             close (i);
00230 
00231               execve (argv[0], argv, env);
00232               _exit (0);
00233        }
00234 
00235        close (pfd[1]);                                                       /* parent */
00236        if ((child_process = fdopen (pfd[0], "r")) == NULL)
00237               return (NULL);
00238        close (pfderr[1]);
00239 
00240        childpid[fileno (child_process)] = pid;   /* remember child pid for this fd */
00241        child_stderr_array[fileno (child_process)] = pfderr[0]; /* remember STDERR */
00242        return (child_process);
00243 }
00244 
00245 int
00246 spclose (FILE * fp)
00247 {
00248        int fd, status;
00249        pid_t pid;
00250 
00251        if (childpid == NULL)
00252               return (1);                                                    /* popen() has never been called */
00253 
00254        fd = fileno (fp);
00255        if ((pid = childpid[fd]) == 0)
00256               return (1);                                                    /* fp wasn't opened by popen() */
00257 
00258        childpid[fd] = 0;
00259        if (fclose (fp) == EOF)
00260               return (1);
00261 
00262 #ifdef REDHAT_SPOPEN_ERROR
00263        while (!childtermd);                                                  /* wait until SIGCHLD */
00264 #endif
00265 
00266        while (waitpid (pid, &status, 0) < 0)
00267               if (errno != EINTR)
00268                      return (1);                                             /* error other than EINTR from waitpid() */
00269 
00270        if (WIFEXITED (status))
00271               return (WEXITSTATUS (status));     /* return child's termination status */
00272 
00273        return (1);
00274 }
00275 
00276 #ifdef OPEN_MAX
00277 static int openmax = OPEN_MAX;
00278 #else
00279 static int openmax = 0;
00280 #endif
00281 
00282 #define       OPEN_MAX_GUESS       256                  /* if OPEN_MAX is indeterminate */
00283                             /* no guarantee this is adequate */
00284 
00285 #ifdef REDHAT_SPOPEN_ERROR
00286 RETSIGTYPE
00287 popen_sigchld_handler (int signo)
00288 {
00289        if (signo == SIGCHLD)
00290               childtermd = 1;
00291 }
00292 #endif
00293 
00294 RETSIGTYPE
00295 popen_timeout_alarm_handler (int signo)
00296 {
00297        int fh;
00298        if (signo == SIGALRM) {
00299               if (child_process != NULL) {
00300                      fh=fileno (child_process);
00301                      if(fh >= 0){
00302                             kill (childpid[fh], SIGKILL);
00303                      }
00304                      printf (_("CRITICAL - Plugin timed out after %d seconds\n"),
00305                                           timeout_interval);
00306               } else {
00307                      printf ("%s\n", _("CRITICAL - popen timeout received, but no child process"));
00308               }
00309               exit (STATE_CRITICAL);
00310        }
00311 }
00312 
00313 
00314 int
00315 open_max (void)
00316 {
00317        if (openmax == 0) {                                     /* first time through */
00318               errno = 0;
00319               if ((openmax = sysconf (_SC_OPEN_MAX)) < 0) {
00320                      if (errno == 0)
00321                             openmax = OPEN_MAX_GUESS;   /* it's indeterminate */
00322                      else
00323                             err_sys (_("sysconf error for _SC_OPEN_MAX"));
00324               }
00325        }
00326        return (openmax);
00327 }
00328 
00329 
00330 /* Fatal error related to a system call.
00331  * Print a message and die. */
00332 
00333 #define MAXLINE 2048
00334 static void
00335 err_sys (const char *fmt, ...)
00336 {
00337        int errnoflag = 1;
00338        int errno_save;
00339        char buf[MAXLINE];
00340 
00341        va_list ap;
00342 
00343        va_start (ap, fmt);
00344        /* err_doit (1, fmt, ap); */
00345        errno_save = errno;                                     /* value caller might want printed */
00346        vsprintf (buf, fmt, ap);
00347        if (errnoflag)
00348               sprintf (buf + strlen (buf), ": %s", strerror (errno_save));
00349        strcat (buf, "\n");
00350        fflush (stdout);                                               /* in case stdout and stderr are the same */
00351        fputs (buf, stderr);
00352        fflush (NULL);                                                        /* flushes all stdio output streams */
00353        va_end (ap);
00354        exit (1);
00355 }
00356 
00357 char *
00358 rtrim (char *str, const char *tok)
00359 {
00360        int i = 0;
00361        int j = sizeof (str);
00362 
00363        while (str != NULL && i < j) {
00364               if (*(str + i) == *tok) {
00365                      sprintf (str + i, "%s", "\0");
00366                      return str;
00367               }
00368               i++;
00369        }
00370        return str;
00371 }