1 /*
2  * Utility routines to assist with the running of sub-commands
3  */
4 
5 #include <net-snmp/net-snmp-config.h>
6 
7 #if HAVE_IO_H
8 #include <io.h>
9 #endif
10 #include <stdio.h>
11 #if HAVE_STDLIB_H
12 #include <stdlib.h>
13 #endif
14 #if HAVE_UNISTD_H
15 #include <unistd.h>
16 #endif
17 #if HAVE_MALLOC_H
18 #include <malloc.h>
19 #endif
20 #include <sys/types.h>
21 #include <ctype.h>
22 #if HAVE_FCNTL_H
23 #include <fcntl.h>
24 #endif
25 #if HAVE_SYS_WAIT_H
26 #include <sys/wait.h>
27 #endif
28 #if HAVE_SYS_SELECT_H
29 #include <sys/select.h>
30 #endif
31 
32 #include <errno.h>
33 
34 #include <net-snmp/net-snmp-includes.h>
35 #include <net-snmp/agent/net-snmp-agent-includes.h>
36 #include <ucd-snmp/errormib.h>
37 
38 #include <net-snmp/agent/netsnmp_close_fds.h>
39 
40 #include "execute.h"
41 #include "struct.h"
42 
43 #ifdef _MSC_VER
44 #define popen  _popen
45 #define pclose _pclose
46 #endif
47 
48 
49 /**
50  * Run a shell command by calling system() or popen().
51  *
52  * @command: Shell command to run.
53  * @input:   Data to send to stdin. May be NULL.
54  * @output:  Buffer in which to store the output written to stdout. May be NULL.
55  * @out_len: Size of the output buffer. The actual number of bytes written is
56  *           stored in *@out_len.
57  *
58  * @return >= 0 if the command has been executed; -1 if the command could not
59  *           be executed.
60  */
61 int
run_shell_command(const char * command,const char * input,char * output,int * out_len)62 run_shell_command(const char *command, const char *input,
63                   char *output, int *out_len)
64 {
65 #if HAVE_SYSTEM
66     int         result;    /* and the return value of the command */
67 
68     if (!command)
69         return -1;
70 
71     DEBUGMSGTL(("run_shell_command", "running %s\n", command));
72     DEBUGMSGTL(("run:shell", "running '%s'\n", command));
73 
74     result = -1;
75 
76     /*
77      * Set up the command and run it.
78      */
79     if (input) {
80         if (output) {
81             const char *ifname;
82             const char *ofname;    /* Filename for output redirection */
83             char        shellline[STRMAX];   /* The full command to run */
84             FILE       *file;
85 
86             ifname = netsnmp_mktemp();
87             if(NULL == ifname)
88                 return -1;
89             file = fopen(ifname, "w");
90             if(NULL == file) {
91                 snmp_log(LOG_ERR,"couldn't open temporary file %s\n", ifname);
92                 unlink(ifname);
93                 return -1;
94             }
95             fprintf(file, "%s", input);
96             fclose( file );
97 
98             ofname = netsnmp_mktemp();
99             if(NULL == ofname) {
100                 if(ifname)
101                     unlink(ifname);
102                 return -1;
103             }
104             snprintf( shellline, sizeof(shellline), "(%s) < \"%s\" > \"%s\"",
105                       command, ifname, ofname );
106             result = system(shellline);
107             /*
108              * If output was requested, then retrieve & return it.
109              * Tidy up, and return the result of the command.
110              */
111             if (out_len && *out_len != 0) {
112                 int         fd;        /* For processing any output */
113                 int         len = 0;
114 
115                 fd = open(ofname, O_RDONLY);
116                 if(fd >= 0)
117                     len = read(fd, output, *out_len - 1);
118                 *out_len = len;
119                 if (len >= 0)
120                     output[len] = 0;
121                 else
122                     output[0] = 0;
123                 if (fd >= 0)
124                     close(fd);
125             }
126             unlink(ofname);
127             unlink(ifname);
128         } else {
129             FILE       *file;
130 
131             file = popen(command, "w");
132             if (file) {
133                 fwrite(input, 1, strlen(input), file);
134                 result = pclose(file);
135             }
136         }
137     } else {
138         if (output) {
139             FILE* file;
140 
141             file = popen(command, "r");
142             if (file) {
143                 *out_len = fread(output, 1, *out_len - 1, file);
144                 if (*out_len >= 0)
145                     output[*out_len] = 0;
146                 else
147                     output[0] = 0;
148                 result = pclose(file);
149             }
150         } else {
151             result = system(command);
152         }
153     }
154 
155     return result;
156 #else
157     return -1;
158 #endif
159 }
160 
161 #if HAVE_EXECV
162 /*
163  * Split the given command up into separate tokens,
164  * ready to be passed to 'execv'
165  */
166 static char **
tokenize_exec_command(const char * command,int * argc)167 tokenize_exec_command(const char *command, int *argc)
168 {
169     char ctmp[STRMAX];
170     const char *cp = command;
171     char **argv;
172     int  i;
173 
174     argv = calloc(100, sizeof(char *));
175     if (!argv)
176         return argv;
177 
178     for (i = 0; cp && i + 2 < 100; i++) {
179         cp = copy_nword_const(cp, ctmp, sizeof(ctmp));
180         argv[i] = strdup(ctmp);
181     }
182     if (cp)
183         argv[i++] = strdup(cp);
184     argv[i] = NULL;
185     *argc = i;
186 
187     return argv;
188 }
189 #endif
190 
191 /**
192  * Run a command by calling execv().
193  *
194  * @command: Shell command to run.
195  * @input:   Data to send to stdin. May be NULL.
196  * @output:  Buffer in which to store the output written to stdout. May be NULL.
197  * @out_len: Size of the output buffer. The actual number of bytes written is
198  *           stored in *@out_len.
199  *
200  * @return >= 0 if the command has been executed; -1 if the command could not
201  *           be executed.
202  */
203 int
run_exec_command(const char * command,const char * input,char * output,int * out_len)204 run_exec_command(const char *command, const char *input,
205                  char *output, int *out_len)
206 {
207 #if HAVE_EXECV
208     int ipipe[2];
209     int opipe[2];
210     int i;
211     int pid;
212     int result;
213     char **argv;
214     int argc;
215 
216     DEBUGMSGTL(("run:exec", "running '%s'\n", command));
217     if (pipe(ipipe) < 0) {
218         snmp_log_perror("pipe");
219         return -1;
220     }
221     if (pipe(opipe) < 0) {
222         snmp_log_perror("pipe");
223         close(ipipe[0]);
224         close(ipipe[1]);
225         return -1;
226     }
227     if ((pid = fork()) == 0) {
228         /*
229          * Child process
230          */
231 
232         /*
233          * Set stdin/out/err to use the pipe
234          *   and close everything else
235          */
236         if (dup2(ipipe[0], STDIN_FILENO) < 0) {
237             snmp_log_perror("dup2(STDIN_FILENO)");
238             exit(1);
239         }
240         close(ipipe[0]);
241         close(ipipe[1]);
242 
243         if (dup2(opipe[1], STDOUT_FILENO) < 0) {
244             snmp_log_perror("dup2(STDOUT_FILENO)");
245             exit(1);
246         }
247         close(opipe[0]);
248         close(opipe[1]);
249 
250         if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) {
251             snmp_log_perror("dup2(STDERR_FILENO)");
252             exit(1);
253         }
254 
255         netsnmp_close_fds(2);
256 
257         /*
258          * Set up the argv array and execute it
259          * This is being run in the child process,
260          *   so will release resources when it terminates.
261          */
262         argv = tokenize_exec_command(command, &argc);
263         if (!argv)
264             exit(1);
265         execv(argv[0], argv);
266         snmp_log_perror(argv[0]);
267         for (i = 0; i < argc; i++)
268             free(argv[i]);
269         free(argv);
270         exit(1);        /* End of child */
271 
272     } else if (pid > 0) {
273         char            cache[NETSNMP_MAXCACHESIZE];
274         char           *cache_ptr;
275         ssize_t         count, cache_size, offset = 0;
276         int             waited = 0, numfds;
277         fd_set          readfds;
278         struct timeval  timeout;
279 
280         /*
281          * Parent process
282          */
283 
284         /*
285          * Pass the input message (if any) to the child,
286          * wait for the child to finish executing, and read
287          *    any output into the output buffer (if provided)
288          */
289         close(ipipe[0]);
290         close(opipe[1]);
291         if (input && write(ipipe[1], input, strlen(input)) < 0)
292             snmp_log_perror("write() to input pipe");
293         close(ipipe[1]);
294 
295         /*
296          * child will block if it writes a lot of data and
297          * fills up the pipe before exiting, so we read data
298          * to keep the pipe empty.
299          */
300         if (output && ((NULL == out_len) || (0 == *out_len))) {
301             DEBUGMSGTL(("run:exec",
302                         "invalid params; no output will be returned\n"));
303             output = NULL;
304         }
305         if (output) {
306             cache_ptr = output;
307             cache_size = *out_len - 1;
308         } else {
309             cache_ptr = cache;
310             cache_size = sizeof(cache);
311         }
312 
313         /*
314          * xxx: some of this code was lifted from get_exec_output
315          * in util_funcs.c. Probably should be moved to a common
316          * routine for both to use.
317          */
318         DEBUGMSGTL(("verbose:run:exec","  waiting for child %d...\n", pid));
319         numfds = opipe[0] + 1;
320         i = NETSNMP_MAXREADCOUNT;
321         for (; i; --i) {
322             /*
323              * set up data for select
324              */
325             FD_ZERO(&readfds);
326             FD_SET(opipe[0], &readfds);
327             timeout.tv_sec = 1;
328             timeout.tv_usec = 0;
329 
330             DEBUGMSGTL(("verbose:run:exec", "    calling select\n"));
331             count = select(numfds, &readfds, NULL, NULL, &timeout);
332             if (count == -1) {
333                 if (EAGAIN == errno) {
334                     continue;
335                 } else {
336                     DEBUGMSGTL(("verbose:run:exec", "      errno %d\n",
337                                 errno));
338                     snmp_log_perror("read");
339                     break;
340                 }
341             } else if (0 == count) {
342                 DEBUGMSGTL(("verbose:run:exec", "      timeout\n"));
343                 continue;
344             }
345 
346             if (!FD_ISSET(opipe[0], &readfds)) {
347                 DEBUGMSGTL(("verbose:run:exec", "    fd not ready!\n"));
348                 continue;
349             }
350 
351             /*
352              * read data from the pipe, optionally saving to output buffer
353              */
354             count = read(opipe[0], &cache_ptr[offset], cache_size);
355             DEBUGMSGTL(("verbose:run:exec",
356                         "    read %d bytes\n", (int)count));
357             if (0 == count) {
358                 int rc;
359                 /*
360                  * we shouldn't get no data, because select should
361                  * wait til the fd is ready. before we go back around,
362                  * check to see if the child exited.
363                  */
364                 DEBUGMSGTL(("verbose:run:exec", "    no data!\n"));
365                 if ((rc = waitpid(pid, &result, WNOHANG)) <= 0) {
366                     if (rc < 0) {
367                         snmp_log_perror("waitpid");
368                         break;
369                     } else
370                         DEBUGMSGTL(("verbose:run:exec",
371                                     "      child not done!?!\n"));
372                 } else {
373                     DEBUGMSGTL(("verbose:run:exec", "      child done\n"));
374                     waited = 1; /* don't wait again */
375                     break;
376                 }
377             } else if (count > 0) {
378                 /*
379                  * got some data. fix up offset, if needed.
380                  */
381                 if(output) {
382                     offset += count;
383                     cache_size -= count;
384                     if (cache_size <= 0) {
385                         DEBUGMSGTL(("verbose:run:exec",
386                                     "      output full\n"));
387                         break;
388                     }
389                     DEBUGMSGTL(("verbose:run:exec",
390                                 "    %d left in buffer\n", (int)cache_size));
391                 }
392             } else if (count == -1 && EAGAIN != errno) {
393                 /*
394                  * if error, break
395                  */
396                 DEBUGMSGTL(("verbose:run:exec", "      errno %d\n",
397                             errno));
398                 snmp_log_perror("read");
399                 break;
400             }
401         }
402         DEBUGMSGTL(("verbose:run:exec", "  done reading\n"));
403         if (output)
404             DEBUGMSGTL(("run:exec", "  got %d bytes\n", *out_len));
405 
406         /*
407          * close pipe to signal that we aren't listenting any more.
408          */
409         close(opipe[0]);
410 
411         /*
412          * if we didn't wait successfully above, wait now.
413          * xxx-rks: seems like this is a waste of the agent's
414          * time. maybe start a time to wait(WNOHANG) once a second,
415          * and late the agent continue?
416          */
417         if (!waited && waitpid(pid, &result, 0) < 0) {
418             snmp_log_perror("waitpid");
419             return -1;
420         }
421 
422         /*
423          * null terminate any output
424          */
425         if (output) {
426             output[offset] = 0;
427             *out_len = offset;
428         }
429         DEBUGMSGTL(("run:exec","  child %d finished. result=%d\n",
430                     pid,result));
431 
432         return WEXITSTATUS(result);
433 
434     } else {
435         /*
436          * Parent process - fork failed
437          */
438         snmp_log_perror("fork");
439         close(ipipe[0]);
440         close(ipipe[1]);
441         close(opipe[0]);
442         close(opipe[1]);
443         return -1;
444     }
445 
446 #else
447     /*
448      * If necessary, fall back to using 'system'
449      */
450     DEBUGMSGTL(("run:exec", "running shell command '%s'\n", command));
451     return run_shell_command( command, input, output, out_len );
452 #endif
453 }
454