/* * Utility routines to assist with the running of sub-commands */ #include #if HAVE_IO_H #include #endif #include #if HAVE_STDLIB_H #include #endif #if HAVE_UNISTD_H #include #endif #if HAVE_MALLOC_H #include #endif #include #include #if HAVE_FCNTL_H #include #endif #if HAVE_SYS_WAIT_H #include #endif #if HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include "execute.h" #include "struct.h" #ifdef _MSC_VER #define popen _popen #define pclose _pclose #endif /** * Run a shell command by calling system() or popen(). * * @command: Shell command to run. * @input: Data to send to stdin. May be NULL. * @output: Buffer in which to store the output written to stdout. May be NULL. * @out_len: Size of the output buffer. The actual number of bytes written is * stored in *@out_len. * * @return >= 0 if the command has been executed; -1 if the command could not * be executed. */ int run_shell_command(const char *command, const char *input, char *output, int *out_len) { #if HAVE_SYSTEM int result; /* and the return value of the command */ if (!command) return -1; DEBUGMSGTL(("run_shell_command", "running %s\n", command)); DEBUGMSGTL(("run:shell", "running '%s'\n", command)); result = -1; /* * Set up the command and run it. */ if (input) { if (output) { const char *ifname; const char *ofname; /* Filename for output redirection */ char shellline[STRMAX]; /* The full command to run */ FILE *file; ifname = netsnmp_mktemp(); if(NULL == ifname) return -1; file = fopen(ifname, "w"); if(NULL == file) { snmp_log(LOG_ERR,"couldn't open temporary file %s\n", ifname); unlink(ifname); return -1; } fprintf(file, "%s", input); fclose( file ); ofname = netsnmp_mktemp(); if(NULL == ofname) { if(ifname) unlink(ifname); return -1; } snprintf( shellline, sizeof(shellline), "(%s) < \"%s\" > \"%s\"", command, ifname, ofname ); result = system(shellline); /* * If output was requested, then retrieve & return it. * Tidy up, and return the result of the command. */ if (out_len && *out_len != 0) { int fd; /* For processing any output */ int len = 0; fd = open(ofname, O_RDONLY); if(fd >= 0) len = read(fd, output, *out_len - 1); *out_len = len; if (len >= 0) output[len] = 0; else output[0] = 0; if (fd >= 0) close(fd); } unlink(ofname); unlink(ifname); } else { FILE *file; file = popen(command, "w"); if (file) { fwrite(input, 1, strlen(input), file); result = pclose(file); } } } else { if (output) { FILE* file; file = popen(command, "r"); if (file) { *out_len = fread(output, 1, *out_len - 1, file); if (*out_len >= 0) output[*out_len] = 0; else output[0] = 0; result = pclose(file); } } else { result = system(command); } } return result; #else return -1; #endif } #if HAVE_EXECV /* * Split the given command up into separate tokens, * ready to be passed to 'execv' */ static char ** tokenize_exec_command(const char *command, int *argc) { char ctmp[STRMAX]; const char *cp = command; char **argv; int i; argv = calloc(100, sizeof(char *)); if (!argv) return argv; for (i = 0; cp && i + 2 < 100; i++) { cp = copy_nword_const(cp, ctmp, sizeof(ctmp)); argv[i] = strdup(ctmp); } if (cp) argv[i++] = strdup(cp); argv[i] = NULL; *argc = i; return argv; } #endif /** * Run a command by calling execv(). * * @command: Shell command to run. * @input: Data to send to stdin. May be NULL. * @output: Buffer in which to store the output written to stdout. May be NULL. * @out_len: Size of the output buffer. The actual number of bytes written is * stored in *@out_len. * * @return >= 0 if the command has been executed; -1 if the command could not * be executed. */ int run_exec_command(const char *command, const char *input, char *output, int *out_len) { #if HAVE_EXECV int ipipe[2]; int opipe[2]; int i; int pid; int result; char **argv; int argc; DEBUGMSGTL(("run:exec", "running '%s'\n", command)); if (pipe(ipipe) < 0) { snmp_log_perror("pipe"); return -1; } if (pipe(opipe) < 0) { snmp_log_perror("pipe"); close(ipipe[0]); close(ipipe[1]); return -1; } if ((pid = fork()) == 0) { /* * Child process */ /* * Set stdin/out/err to use the pipe * and close everything else */ if (dup2(ipipe[0], STDIN_FILENO) < 0) { snmp_log_perror("dup2(STDIN_FILENO)"); exit(1); } close(ipipe[0]); close(ipipe[1]); if (dup2(opipe[1], STDOUT_FILENO) < 0) { snmp_log_perror("dup2(STDOUT_FILENO)"); exit(1); } close(opipe[0]); close(opipe[1]); if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) { snmp_log_perror("dup2(STDERR_FILENO)"); exit(1); } netsnmp_close_fds(2); /* * Set up the argv array and execute it * This is being run in the child process, * so will release resources when it terminates. */ argv = tokenize_exec_command(command, &argc); if (!argv) exit(1); execv(argv[0], argv); snmp_log_perror(argv[0]); for (i = 0; i < argc; i++) free(argv[i]); free(argv); exit(1); /* End of child */ } else if (pid > 0) { char cache[NETSNMP_MAXCACHESIZE]; char *cache_ptr; ssize_t count, cache_size, offset = 0; int waited = 0, numfds; fd_set readfds; struct timeval timeout; /* * Parent process */ /* * Pass the input message (if any) to the child, * wait for the child to finish executing, and read * any output into the output buffer (if provided) */ close(ipipe[0]); close(opipe[1]); if (input && write(ipipe[1], input, strlen(input)) < 0) snmp_log_perror("write() to input pipe"); close(ipipe[1]); /* * child will block if it writes a lot of data and * fills up the pipe before exiting, so we read data * to keep the pipe empty. */ if (output && ((NULL == out_len) || (0 == *out_len))) { DEBUGMSGTL(("run:exec", "invalid params; no output will be returned\n")); output = NULL; } if (output) { cache_ptr = output; cache_size = *out_len - 1; } else { cache_ptr = cache; cache_size = sizeof(cache); } /* * xxx: some of this code was lifted from get_exec_output * in util_funcs.c. Probably should be moved to a common * routine for both to use. */ DEBUGMSGTL(("verbose:run:exec"," waiting for child %d...\n", pid)); numfds = opipe[0] + 1; i = NETSNMP_MAXREADCOUNT; for (; i; --i) { /* * set up data for select */ FD_ZERO(&readfds); FD_SET(opipe[0], &readfds); timeout.tv_sec = 1; timeout.tv_usec = 0; DEBUGMSGTL(("verbose:run:exec", " calling select\n")); count = select(numfds, &readfds, NULL, NULL, &timeout); if (count == -1) { if (EAGAIN == errno) { continue; } else { DEBUGMSGTL(("verbose:run:exec", " errno %d\n", errno)); snmp_log_perror("read"); break; } } else if (0 == count) { DEBUGMSGTL(("verbose:run:exec", " timeout\n")); continue; } if (!FD_ISSET(opipe[0], &readfds)) { DEBUGMSGTL(("verbose:run:exec", " fd not ready!\n")); continue; } /* * read data from the pipe, optionally saving to output buffer */ count = read(opipe[0], &cache_ptr[offset], cache_size); DEBUGMSGTL(("verbose:run:exec", " read %d bytes\n", (int)count)); if (0 == count) { int rc; /* * we shouldn't get no data, because select should * wait til the fd is ready. before we go back around, * check to see if the child exited. */ DEBUGMSGTL(("verbose:run:exec", " no data!\n")); if ((rc = waitpid(pid, &result, WNOHANG)) <= 0) { if (rc < 0) { snmp_log_perror("waitpid"); break; } else DEBUGMSGTL(("verbose:run:exec", " child not done!?!\n")); } else { DEBUGMSGTL(("verbose:run:exec", " child done\n")); waited = 1; /* don't wait again */ break; } } else if (count > 0) { /* * got some data. fix up offset, if needed. */ if(output) { offset += count; cache_size -= count; if (cache_size <= 0) { DEBUGMSGTL(("verbose:run:exec", " output full\n")); break; } DEBUGMSGTL(("verbose:run:exec", " %d left in buffer\n", (int)cache_size)); } } else if (count == -1 && EAGAIN != errno) { /* * if error, break */ DEBUGMSGTL(("verbose:run:exec", " errno %d\n", errno)); snmp_log_perror("read"); break; } } DEBUGMSGTL(("verbose:run:exec", " done reading\n")); if (output) DEBUGMSGTL(("run:exec", " got %d bytes\n", *out_len)); /* * close pipe to signal that we aren't listenting any more. */ close(opipe[0]); /* * if we didn't wait successfully above, wait now. * xxx-rks: seems like this is a waste of the agent's * time. maybe start a time to wait(WNOHANG) once a second, * and late the agent continue? */ if (!waited && waitpid(pid, &result, 0) < 0) { snmp_log_perror("waitpid"); return -1; } /* * null terminate any output */ if (output) { output[offset] = 0; *out_len = offset; } DEBUGMSGTL(("run:exec"," child %d finished. result=%d\n", pid,result)); return WEXITSTATUS(result); } else { /* * Parent process - fork failed */ snmp_log_perror("fork"); close(ipipe[0]); close(ipipe[1]); close(opipe[0]); close(opipe[1]); return -1; } #else /* * If necessary, fall back to using 'system' */ DEBUGMSGTL(("run:exec", "running shell command '%s'\n", command)); return run_shell_command( command, input, output, out_len ); #endif }