Coverage Report

Created: 2025-11-09 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/net-snmp/agent/mibgroup/utilities/execute.c
Line
Count
Source
1
/*
2
 * Utility routines to assist with the running of sub-commands
3
 */
4
5
#include <net-snmp/net-snmp-config.h>
6
7
#ifdef HAVE_IO_H
8
#include <io.h>
9
#endif
10
#include <stdio.h>
11
#ifdef HAVE_STDLIB_H
12
#include <stdlib.h>
13
#endif
14
#ifdef HAVE_UNISTD_H
15
#include <unistd.h>
16
#endif
17
#ifdef HAVE_MALLOC_H
18
#include <malloc.h>
19
#endif
20
#include <sys/types.h>
21
#include <ctype.h>
22
#ifdef HAVE_FCNTL_H
23
#include <fcntl.h>
24
#endif
25
#ifdef HAVE_SYS_WAIT_H
26
#include <sys/wait.h>
27
#endif
28
#ifdef 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
62
run_shell_command(const char *command, const char *input,
63
                  char *output, int *out_len)
64
0
{
65
0
#ifdef HAVE_SYSTEM
66
0
    int         result;    /* and the return value of the command */
67
68
0
    if (!command)
69
0
        return -1;
70
71
0
    DEBUGMSGTL(("run_shell_command", "running %s\n", command));
72
0
    DEBUGMSGTL(("run:shell", "running '%s'\n", command));
73
74
0
    result = -1;
75
76
    /*
77
     * Set up the command and run it.
78
     */
79
0
    if (input) {
80
0
        if (output) {
81
0
            const char *ifname;
82
0
            const char *ofname;    /* Filename for output redirection */
83
0
            char        shellline[STRMAX];   /* The full command to run */
84
0
            FILE       *file;
85
86
0
            ifname = netsnmp_mktemp();
87
0
            if(NULL == ifname)
88
0
                return -1;
89
0
            file = fopen(ifname, "w");
90
0
            if(NULL == file) {
91
0
                snmp_log(LOG_ERR,"couldn't open temporary file %s\n", ifname);
92
0
                unlink(ifname);
93
0
                return -1;
94
0
            }
95
0
            fprintf(file, "%s", input);
96
0
            fclose( file );
97
98
0
            ofname = netsnmp_mktemp();
99
0
            if(NULL == ofname) {
100
0
                if(ifname)
101
0
                    unlink(ifname);
102
0
                return -1;
103
0
            }
104
0
            snprintf( shellline, sizeof(shellline), "(%s) < \"%s\" > \"%s\"",
105
0
                      command, ifname, ofname );
106
0
            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
0
            if (out_len && *out_len != 0) {
112
0
                int         fd;        /* For processing any output */
113
0
                int         len = 0;
114
115
0
                fd = open(ofname, O_RDONLY);
116
0
                if(fd >= 0)
117
0
                    len = read(fd, output, *out_len - 1);
118
0
                *out_len = len;
119
0
                if (len >= 0)
120
0
                    output[len] = 0;
121
0
                else
122
0
                    output[0] = 0;
123
0
                if (fd >= 0)
124
0
                    close(fd);
125
0
            }
126
0
            unlink(ofname);
127
0
            unlink(ifname);
128
0
        } else {
129
0
            FILE       *file;
130
131
0
            file = popen(command, "w");
132
0
            if (file) {
133
0
                fwrite(input, 1, strlen(input), file);
134
0
                result = pclose(file);
135
0
            }
136
0
        }
137
0
    } else {
138
0
        if (output) {
139
0
            FILE* file;
140
141
0
            file = popen(command, "r");
142
0
            if (file) {
143
0
                *out_len = fread(output, 1, *out_len - 1, file);
144
0
                if (*out_len >= 0)
145
0
                    output[*out_len] = 0;
146
0
                else
147
0
                    output[0] = 0;
148
0
                result = pclose(file);
149
0
            }
150
0
        } else {
151
0
            result = system(command);
152
0
        }
153
0
    }
154
155
0
    return result;
156
#else
157
    return -1;
158
#endif
159
0
}
160
161
#ifdef HAVE_EXECV
162
/*
163
 * Split the given command up into separate tokens,
164
 * ready to be passed to 'execv'
165
 */
166
static char **
167
tokenize_exec_command(const char *command, int *argc)
168
0
{
169
0
    char ctmp[STRMAX];
170
0
    const char *cp = command;
171
0
    char **argv;
172
0
    int  i;
173
174
0
    argv = calloc(100, sizeof(char *));
175
0
    if (!argv)
176
0
        return argv;
177
178
0
    for (i = 0; cp && i + 2 < 100; i++) {
179
0
        cp = copy_nword_const(cp, ctmp, sizeof(ctmp));
180
0
        argv[i] = strdup(ctmp);
181
0
    }
182
0
    if (cp)
183
0
        argv[i++] = strdup(cp);
184
0
    argv[i] = NULL;
185
0
    *argc = i;
186
187
0
    return argv;
188
0
}
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
204
run_exec_command(const char *command, const char *input,
205
                 char *output, int *out_len)
206
0
{
207
0
#ifdef HAVE_EXECV
208
0
    int ipipe[2];
209
0
    int opipe[2];
210
0
    int i;
211
0
    int pid;
212
0
    int result;
213
0
    char **argv;
214
0
    int argc;
215
216
0
    DEBUGMSGTL(("run:exec", "running '%s'\n", command));
217
0
    if (pipe(ipipe) < 0) {
218
0
        snmp_log_perror("pipe");
219
0
        return -1;
220
0
    }
221
0
    if (pipe(opipe) < 0) {
222
0
        snmp_log_perror("pipe");
223
0
        close(ipipe[0]);
224
0
        close(ipipe[1]);
225
0
        return -1;
226
0
    }
227
0
    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
0
        if (dup2(ipipe[0], STDIN_FILENO) < 0) {
237
0
            snmp_log_perror("dup2(STDIN_FILENO)");
238
0
            exit(1);
239
0
        }
240
0
        close(ipipe[0]);
241
0
        close(ipipe[1]);
242
243
0
        if (dup2(opipe[1], STDOUT_FILENO) < 0) {
244
0
            snmp_log_perror("dup2(STDOUT_FILENO)");
245
0
            exit(1);
246
0
        }
247
0
        close(opipe[0]);
248
0
        close(opipe[1]);
249
250
0
        if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) {
251
0
            snmp_log_perror("dup2(STDERR_FILENO)");
252
0
            exit(1);
253
0
        }
254
255
0
        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
0
        argv = tokenize_exec_command(command, &argc);
263
0
        if (!argv)
264
0
            exit(1);
265
0
        execv(argv[0], argv);
266
0
        snmp_log_perror(argv[0]);
267
0
        for (i = 0; i < argc; i++)
268
0
            free(argv[i]);
269
0
        free(argv);
270
0
        exit(1);        /* End of child */
271
272
0
    } else if (pid > 0) {
273
0
        char            cache[NETSNMP_MAXCACHESIZE];
274
0
        char           *cache_ptr;
275
0
        ssize_t         count, cache_size, offset = 0;
276
0
        int             waited = 0, numfds;
277
0
        fd_set          readfds;
278
0
        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
0
        close(ipipe[0]);
290
0
        close(opipe[1]);
291
0
        if (input && write(ipipe[1], input, strlen(input)) < 0)
292
0
            snmp_log_perror("write() to input pipe");
293
0
        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
0
        if (output && ((NULL == out_len) || (0 == *out_len))) {
301
0
            DEBUGMSGTL(("run:exec",
302
0
                        "invalid params; no output will be returned\n"));
303
0
            output = NULL;
304
0
        }
305
0
        if (output) {
306
0
            cache_ptr = output;
307
0
            cache_size = *out_len - 1;
308
0
        } else {
309
0
            cache_ptr = cache;
310
0
            cache_size = sizeof(cache);
311
0
        }
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
0
        DEBUGMSGTL(("verbose:run:exec","  waiting for child %d...\n", pid));
319
0
        numfds = opipe[0] + 1;
320
0
        i = NETSNMP_MAXREADCOUNT;
321
0
        for (; i; --i) {
322
            /*
323
             * set up data for select
324
             */
325
0
            FD_ZERO(&readfds);
326
0
            FD_SET(opipe[0], &readfds);
327
0
            timeout.tv_sec = 1;
328
0
            timeout.tv_usec = 0;
329
330
0
            DEBUGMSGTL(("verbose:run:exec", "    calling select\n"));
331
0
            count = select(numfds, &readfds, NULL, NULL, &timeout);
332
0
            if (count == -1) {
333
0
                if (EAGAIN == errno) {
334
0
                    continue;
335
0
                } else {
336
0
                    DEBUGMSGTL(("verbose:run:exec", "      errno %d\n",
337
0
                                errno));
338
0
                    snmp_log_perror("read");
339
0
                    break;
340
0
                }
341
0
            } else if (0 == count) {
342
0
                DEBUGMSGTL(("verbose:run:exec", "      timeout\n"));
343
0
                continue;
344
0
            }
345
346
0
            if (!FD_ISSET(opipe[0], &readfds)) {
347
0
                DEBUGMSGTL(("verbose:run:exec", "    fd not ready!\n"));
348
0
                continue;
349
0
            }
350
351
            /*
352
             * read data from the pipe, optionally saving to output buffer
353
             */
354
0
            count = read(opipe[0], &cache_ptr[offset], cache_size);
355
0
            DEBUGMSGTL(("verbose:run:exec",
356
0
                        "    read %d bytes\n", (int)count));
357
0
            if (0 == count) {
358
0
                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
0
                DEBUGMSGTL(("verbose:run:exec", "    no data!\n"));
365
0
                if ((rc = waitpid(pid, &result, WNOHANG)) <= 0) {
366
0
                    if (rc < 0) {
367
0
                        snmp_log_perror("waitpid");
368
0
                        break;
369
0
                    } else
370
0
                        DEBUGMSGTL(("verbose:run:exec",
371
0
                                    "      child not done!?!\n"));
372
0
                } else {
373
0
                    DEBUGMSGTL(("verbose:run:exec", "      child done\n"));
374
0
                    waited = 1; /* don't wait again */
375
0
                    break;
376
0
                }
377
0
            } else if (count > 0) {
378
                /*
379
                 * got some data. fix up offset, if needed.
380
                 */
381
0
                if(output) {
382
0
                    offset += count;
383
0
                    cache_size -= count;
384
0
                    if (cache_size <= 0) {
385
0
                        DEBUGMSGTL(("verbose:run:exec",
386
0
                                    "      output full\n"));
387
0
                        break;
388
0
                    }
389
0
                    DEBUGMSGTL(("verbose:run:exec",
390
0
                                "    %d left in buffer\n", (int)cache_size));
391
0
                }
392
0
            } else if (count == -1 && EAGAIN != errno) {
393
                /*
394
                 * if error, break
395
                 */
396
0
                DEBUGMSGTL(("verbose:run:exec", "      errno %d\n",
397
0
                            errno));
398
0
                snmp_log_perror("read");
399
0
                break;
400
0
            }
401
0
        }
402
0
        DEBUGMSGTL(("verbose:run:exec", "  done reading\n"));
403
0
        if (output)
404
0
            DEBUGMSGTL(("run:exec", "  got %d bytes\n", *out_len));
405
            
406
        /*
407
         * close pipe to signal that we aren't listening any more.
408
         */
409
0
        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
0
        if (!waited && waitpid(pid, &result, 0) < 0) {
418
0
            snmp_log_perror("waitpid");
419
0
            return -1;
420
0
        }
421
422
        /*
423
         * null terminate any output
424
         */
425
0
        if (output) {
426
0
            output[offset] = 0;
427
0
            *out_len = offset;
428
0
        }
429
0
        DEBUGMSGTL(("run:exec","  child %d finished. result=%d\n",
430
0
                    pid,result));
431
432
0
        return WEXITSTATUS(result);
433
434
0
    } else {
435
        /*
436
         * Parent process - fork failed
437
         */
438
0
        snmp_log_perror("fork");
439
0
        close(ipipe[0]);
440
0
        close(ipipe[1]);
441
0
        close(opipe[0]);
442
0
        close(opipe[1]);
443
0
        return -1;
444
0
    }
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
0
}