1 /*****************************************************************************
3 * Monitoring Plugins popen
6 * Copyright (c) 2005-2024 Monitoring Plugins Development Team
10 * A safe alternative to popen
12 * Provides spopen and spclose
14 * FILE * spopen(const char *);
15 * int spclose(FILE *);
17 * Code taken with little modification from "Advanced Programming for the Unix
18 * Environment" by W. Richard Stevens
20 * This is considered safe in that no shell is spawned, and the environment
21 * and path passed to the exec'd program are essentially empty. (popen create
22 * a shell and passes the environment to it).
25 * This program is free software: you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation, either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program. If not, see <http://www.gnu.org/licenses/>.
39 *****************************************************************************/
43 #include "../lib/maxfd.h"
45 /* extern so plugin has pid to kill exec'd process on timeouts */
46 extern pid_t
*childpid
;
47 extern int *child_stderr_array
;
48 extern FILE *child_process
;
50 FILE *spopen(const char * /*cmdstring*/);
51 int spclose(FILE * /*fp*/);
52 #ifdef REDHAT_SPOPEN_ERROR
53 void popen_sigchld_handler(int);
55 void popen_timeout_alarm_handler(int /*signo*/);
57 #include <stdarg.h> /* ANSI C header file */
61 #include <sys/resource.h>
63 #ifdef HAVE_SYS_WAIT_H
64 # include <sys/wait.h>
68 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
72 # define WIFEXITED(stat_val) (((stat_val)&255) == 0)
75 /* 4.3BSD Reno <signal.h> doesn't define SIG_ERR */
76 #if defined(SIG_IGN) && !defined(SIG_ERR)
77 # define SIG_ERR ((Sigfunc *)-1)
80 char *pname
= NULL
; /* caller can set this from argv[0] */
82 #ifdef REDHAT_SPOPEN_ERROR
83 static volatile int childtermd
= 0;
86 FILE *spopen(const char *cmdstring
) {
88 /* do not leave core files */
90 getrlimit(RLIMIT_CORE
, &limit
);
92 setrlimit(RLIMIT_CORE
, &limit
);
96 env
[0] = strdup("LC_ALL=C");
99 /* if no command was passed, return with no error */
100 if (cmdstring
== NULL
)
104 /* make copy of command string so strtok() doesn't silently modify it */
105 /* (the calling program may want to access it later) */
106 cmd
= malloc(strlen(cmdstring
) + 1);
109 strcpy(cmd
, cmdstring
);
111 /* This is not a shell, so we don't handle "???" */
112 if (strstr(cmdstring
, "\""))
115 /* allow single quotes, but only if non-whitesapce doesn't occur on both sides */
116 if (strstr(cmdstring
, " ' ") || strstr(cmdstring
, "'''"))
121 /* there cannot be more args than characters */
122 argc
= strlen(cmdstring
) + 1; /* add 1 for NULL termination */
123 argv
= malloc(sizeof(char *) * argc
);
126 printf("%s\n", _("Could not malloc argv array in popen()"));
132 /* loop to get arguments to command */
135 str
+= strspn(str
, " \t\r\n"); /* trim any leading whitespace */
138 printf("%s\n", _("CRITICAL - You need more args!!!"));
142 if (strstr(str
, "'") == str
) { /* handle SIMPLE quoted strings */
144 if (!strstr(str
, "'"))
145 return NULL
; /* balanced? */
146 cmd
= 1 + strstr(str
, "'");
147 str
[strcspn(str
, "'")] = 0;
148 } else if (strcspn(str
, "'") < strcspn(str
, " \t\r\n")) {
149 /* handle --option='foo bar' strings */
150 char *tmp
= str
+ strcspn(str
, "'") + 1;
151 if (!strstr(tmp
, "'"))
152 return NULL
; /* balanced? */
153 tmp
+= strcspn(tmp
, "'") + 1;
157 if (strpbrk(str
, " \t\r\n")) {
158 cmd
= 1 + strpbrk(str
, " \t\r\n");
159 str
[strcspn(str
, " \t\r\n")] = 0;
165 if (cmd
&& strlen(cmd
) == strspn(cmd
, " \t\r\n"))
172 long maxfd
= mp_open_max();
174 if (childpid
== NULL
) { /* first time through */
175 if ((childpid
= calloc((size_t)maxfd
, sizeof(pid_t
))) == NULL
)
179 if (child_stderr_array
== NULL
) { /* first time through */
180 if ((child_stderr_array
= calloc((size_t)maxfd
, sizeof(int))) == NULL
)
186 return (NULL
); /* errno set by pipe() */
189 if (pipe(pfderr
) < 0)
190 return (NULL
); /* errno set by pipe() */
192 #ifdef REDHAT_SPOPEN_ERROR
193 if (signal(SIGCHLD
, popen_sigchld_handler
) == SIG_ERR
) {
194 usage4(_("Cannot catch SIGCHLD"));
199 if ((pid
= fork()) < 0)
200 return (NULL
); /* errno set by fork() */
202 if (pid
== 0) { /* child */
204 if (pfd
[1] != STDOUT_FILENO
) {
205 dup2(pfd
[1], STDOUT_FILENO
);
209 if (pfderr
[1] != STDERR_FILENO
) {
210 dup2(pfderr
[1], STDERR_FILENO
);
213 /* close all descriptors in childpid[] */
214 for (i
= 0; i
< maxfd
; i
++)
218 execve(argv
[0], argv
, env
);
222 close(pfd
[1]); /* parent */
223 if ((child_process
= fdopen(pfd
[0], "r")) == NULL
)
227 childpid
[fileno(child_process
)] = pid
; /* remember child pid for this fd */
228 child_stderr_array
[fileno(child_process
)] = pfderr
[0]; /* remember STDERR */
229 return (child_process
);
232 int spclose(FILE *fp
) {
233 if (childpid
== NULL
)
234 return (1); /* popen() has never been called */
238 if ((pid
= childpid
[fd
]) == 0)
239 return (1); /* fp wasn't opened by popen() */
242 if (fclose(fp
) == EOF
)
245 #ifdef REDHAT_SPOPEN_ERROR
247 ; /* wait until SIGCHLD */
251 while (waitpid(pid
, &status
, 0) < 0)
253 return (1); /* error other than EINTR from waitpid() */
255 if (WIFEXITED(status
))
256 return (WEXITSTATUS(status
)); /* return child's termination status */
261 #ifdef REDHAT_SPOPEN_ERROR
262 void popen_sigchld_handler(int signo
) {
263 if (signo
== SIGCHLD
)
268 void popen_timeout_alarm_handler(int signo
) {
269 if (signo
== SIGALRM
) {
270 if (child_process
!= NULL
) {
271 int fh
= fileno(child_process
);
273 kill(childpid
[fh
], SIGKILL
);
275 printf(_("CRITICAL - Plugin timed out after %d seconds\n"), timeout_interval
);
277 printf("%s\n", _("CRITICAL - popen timeout received, but no child process"));
279 exit(STATE_CRITICAL
);