/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ /* * $Id$ * * @file exec.c * @brief Execute external programs. * * @copyright 2000-2004,2006 The FreeRADIUS server project */ RCSID("$Id$") #include #include #include #include #include #ifdef HAVE_SYS_WAIT_H # include #endif #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif #ifndef WIFEXITED # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif #define MAX_ARGV (256) /** Start a process * * @param cmd Command to execute. This is parsed into argv[] parts, * then each individual argv part is xlat'ed. * @param request Current reuqest * @param exec_wait set to 1 if you want to read from or write to child * @param[in,out] input_fd pointer to int, receives the stdin file. * descriptor. Set to NULL and the child will have /dev/null on stdin * @param[in,out] output_fd pinter to int, receives the stdout file * descriptor. Set to NULL and child will have /dev/null on stdout. * @param input_pairs list of value pairs - these will be put into * the environment variables of the child. * @param shell_escape values before passing them as arguments. * @return PID of the child process, -1 on error. */ pid_t radius_start_program(char const *cmd, REQUEST *request, bool exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, bool shell_escape) { #ifndef __MINGW32__ VALUE_PAIR *vp; int n; int to_child[2] = {-1, -1}; int from_child[2] = {-1, -1}; pid_t pid; #endif int argc; int i; char const **argv_p; char *argv[MAX_ARGV], **argv_start = argv; char argv_buf[4096]; #define MAX_ENVP 1024 char *envp[MAX_ENVP]; int envlen = 0; /* * Stupid array decomposition... * * If we do memcpy(&argv_p, &argv, sizeof(argv_p)) src ends up being a char ** * pointing to the value of the first element. */ memcpy(&argv_p, &argv_start, sizeof(argv_p)); argc = rad_expand_xlat(request, cmd, MAX_ARGV, argv_p, true, sizeof(argv_buf), argv_buf); if (argc <= 0) { DEBUG("invalid command line '%s'.", cmd); return -1; } #ifndef NDEBUG if (rad_debug_lvl > 2) { DEBUG3("executing cmd %s", cmd); for (i = 0; i < argc; i++) { DEBUG3("\t[%d] %s", i, argv[i]); } } #endif #ifndef __MINGW32__ /* * Open a pipe for child/parent communication, if necessary. */ if (exec_wait) { if (input_fd) { if (pipe(to_child) != 0) { DEBUG("Couldn't open pipe to child: %s", fr_syserror(errno)); return -1; } } if (output_fd) { if (pipe(from_child) != 0) { DEBUG("Couldn't open pipe from child: %s", fr_syserror(errno)); /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); return -1; } } } envp[0] = NULL; if (input_pairs) { vp_cursor_t cursor; char buffer[1024]; /* * Set up the environment variables in the * parent, so we don't call libc functions that * hold mutexes. They might be locked when we fork, * and will remain locked in the child. */ for (vp = fr_cursor_init(&cursor, &input_pairs); vp; vp = fr_cursor_next(&cursor)) { /* * Hmm... maybe we shouldn't pass the * user's password in an environment * variable... */ snprintf(buffer, sizeof(buffer), "%s=", vp->da->name); if (shell_escape) { char *p; for (p = buffer; *p != '='; p++) { if (*p == '-') { *p = '_'; } else if (isalpha((uint8_t) *p)) { *p = toupper((uint8_t) *p); } } } n = strlen(buffer); vp_prints_value(buffer + n, sizeof(buffer) - n, vp, shell_escape ? '"' : 0); envp[envlen++] = strdup(buffer); /* * Don't add too many attributes. */ if (envlen == (MAX_ENVP - 1)) break; /* * NULL terminate for execve */ envp[envlen] = NULL; } } if (exec_wait) { pid = rad_fork(); /* remember PID */ } else { pid = fork(); /* don't wait */ } if (pid == 0) { int devnull; /* * Child process. * * We try to be fail-safe here. So if ANYTHING * goes wrong, we exit with status 1. */ /* * Open STDIN to /dev/null */ devnull = open("/dev/null", O_RDWR); if (devnull < 0) { DEBUG("Failed opening /dev/null: %s\n", fr_syserror(errno)); /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Only massage the pipe handles if the parent * has created them. */ if (exec_wait) { if (input_fd) { close(to_child[1]); dup2(to_child[0], STDIN_FILENO); } else { dup2(devnull, STDIN_FILENO); } if (output_fd) { close(from_child[0]); dup2(from_child[1], STDOUT_FILENO); } else { dup2(devnull, STDOUT_FILENO); } } else { /* no pipe, STDOUT should be /dev/null */ dup2(devnull, STDIN_FILENO); dup2(devnull, STDOUT_FILENO); } /* * If we're not debugging, then we can't do * anything with the error messages, so we throw * them away. * * If we are debugging, then we want the error * messages to go to the STDERR of the server. */ if (rad_debug_lvl == 0) { dup2(devnull, STDERR_FILENO); } close(devnull); /* * The server may have MANY FD's open. We don't * want to leave dangling FD's for the child process * to play funky games with, so we close them. */ closefrom(3); /* * I swear the signature for execve is wrong and should * take 'char const * const argv[]'. * * Note: execve(), unlike system(), treats all the space * delimited arguments as literals, so there's no need * to perform additional escaping. */ execve(argv[0], argv, envp); printf("Failed to execute \"%s\": %s", argv[0], fr_syserror(errno)); /* fork output will be captured */ /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Free child environment variables */ for (i = 0; i < envlen; i++) { free(envp[i]); } /* * Parent process. */ if (pid < 0) { DEBUG("Couldn't fork %s: %s", argv[0], fr_syserror(errno)); if (exec_wait) { /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); close(from_child[0]); close(from_child[1]); } return -1; } /* * We're not waiting, exit, and ignore any child's status. */ if (exec_wait) { /* * Close the ends of the pipe(s) the child is using * return the ends of the pipe(s) our caller wants * */ if (input_fd) { *input_fd = to_child[1]; close(to_child[0]); } if (output_fd) { *output_fd = from_child[0]; close(from_child[1]); } } return pid; #else if (exec_wait) { DEBUG("Wait is not supported"); return -1; } { /* * The _spawn and _exec families of functions are * found in Windows compiler libraries for * portability from UNIX. There is a variety of * functions, including the ability to pass * either a list or array of parameters, to * search in the PATH or otherwise, and whether * or not to pass an environment (a set of * environment variables). Using _spawn, you can * also specify whether you want the new process * to close your program (_P_OVERLAY), to wait * until the new process is finished (_P_WAIT) or * for the two to run concurrently (_P_NOWAIT). * _spawn and _exec are useful for instances in * which you have simple requirements for running * the program, don't want the overhead of the * Windows header file, or are interested * primarily in portability. */ /* * FIXME: check return code... what is it? */ _spawnve(_P_NOWAIT, argv[0], argv, envp); } return 0; #endif } /** Read from the child process. * * @param fd file descriptor to read from. * @param pid pid of child, will be reaped if it dies. * @param timeout amount of time to wait, in seconds. * @param answer buffer to write into. * @param left length of buffer. * @return -1 on error, or length of output. */ int radius_readfrom_program(int fd, pid_t pid, int timeout, char *answer, int left) { int done = 0; #ifndef __MINGW32__ int status; struct timeval start; #ifdef O_NONBLOCK bool nonblock = true; #endif #ifdef O_NONBLOCK /* * Try to set it non-blocking. */ do { int flags; if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) { nonblock = false; break; } flags |= O_NONBLOCK; if( fcntl(fd, F_SETFL, flags) < 0) { nonblock = false; break; } } while (0); #endif /* * Read from the pipe until we doesn't get any more or * until the message is full. */ gettimeofday(&start, NULL); while (1) { int rcode; fd_set fds; struct timeval when, elapsed, wake; FD_ZERO(&fds); FD_SET(fd, &fds); gettimeofday(&when, NULL); rad_tv_sub(&when, &start, &elapsed); if (elapsed.tv_sec >= timeout) goto too_long; when.tv_sec = timeout; when.tv_usec = 0; rad_tv_sub(&when, &elapsed, &wake); rcode = select(fd + 1, &fds, NULL, NULL, &wake); if (rcode == 0) { too_long: DEBUG("Child PID %u is taking too much time: forcing failure and killing child.", (unsigned int) pid); kill(pid, SIGTERM); close(fd); /* should give SIGPIPE to child, too */ /* * Clean up the child entry. */ rad_waitpid(pid, &status); return -1; } if (rcode < 0) { if (errno == EINTR) continue; break; } #ifdef O_NONBLOCK /* * Read as many bytes as possible. The kernel * will return the number of bytes available. */ if (nonblock) { status = read(fd, answer + done, left); } else #endif /* * There's at least 1 byte ready: read it. */ status = read(fd, answer + done, 1); /* * Nothing more to read: stop. */ if (status == 0) { break; } /* * Error: See if we have to continue. */ if (status < 0) { /* * We were interrupted: continue reading. */ if (errno == EINTR) { continue; } /* * There was another error. Most likely * The child process has finished, and * exited. */ break; } done += status; left -= status; if (left <= 0) break; } #endif /* __MINGW32__ */ /* Strip trailing new lines */ while ((done > 0) && (answer[done - 1] == '\n')) { answer[--done] = '\0'; } return done; } /** Execute a program. * * @param[in,out] ctx to allocate new VALUE_PAIR (s) in. * @param[out] out buffer to append plaintext (non valuepair) output. * @param[in] outlen length of out buffer. * @param[out] output_pairs list of value pairs - child stdout will be parsed and added into this list * of value pairs. * @param[in] request Current request (may be NULL). * @param[in] cmd Command to execute. This is parsed into argv[] parts, then each individual argv part * is xlat'ed. * @param[in] input_pairs list of value pairs - these will be available in the environment of the child. * @param[in] exec_wait set to 1 if you want to read from or write to child. * @param[in] shell_escape values before passing them as arguments. * @param[in] timeout amount of time to wait, in seconds. * @return 0 if exec_wait==0, exit code if exec_wait!=0, -1 on error. */ int radius_exec_program(TALLOC_CTX *ctx, char *out, size_t outlen, VALUE_PAIR **output_pairs, REQUEST *request, char const *cmd, VALUE_PAIR *input_pairs, bool exec_wait, bool shell_escape, int timeout) { pid_t pid; int from_child; #ifndef __MINGW32__ char *p; pid_t child_pid; int comma = 0; int status, ret = 0; ssize_t len; char answer[4096]; #endif RDEBUG2("Executing: %s:", cmd); if (out) *out = '\0'; pid = radius_start_program(cmd, request, exec_wait, NULL, &from_child, input_pairs, shell_escape); if (pid < 0) { return -1; } if (!exec_wait) { return 0; } #ifndef __MINGW32__ len = radius_readfrom_program(from_child, pid, timeout, answer, sizeof(answer)); if (len < 0) { /* * Failure - radius_readfrom_program will * have called close(from_child) for us */ RERROR("Failed to read from child output"); return -1; } answer[len] = '\0'; /* * Make sure that the writer can't block while writing to * a pipe that no one is reading from anymore. */ close(from_child); if (len == 0) { goto wait; } /* * Parse the output, if any. */ if (output_pairs) { /* * HACK: Replace '\n' with ',' so that * fr_pair_list_afrom_str() can parse the buffer in * one go (the proper way would be to * fix fr_pair_list_afrom_str(), but oh well). */ for (p = answer; *p; p++) { if (*p == '\n') { *p = comma ? ' ' : ','; p++; comma = 0; } if (*p == ',') { comma++; } } /* * Replace any trailing comma by a NUL. */ if (answer[len - 1] == ',') { answer[--len] = '\0'; } if (fr_pair_list_afrom_str(ctx, answer, output_pairs) == T_INVALID) { RERROR("Failed parsing output from: %s: %s", cmd, fr_strerror()); if (out) strlcpy(out, answer, len); ret = -1; } VERIFY_REQUEST(request); /* * We've not been told to extract output pairs, * just copy the programs output to the out * buffer. */ } else if (out) { strlcpy(out, answer, outlen); } /* * Call rad_waitpid (should map to waitpid on non-threaded * or single-server systems). */ wait: child_pid = rad_waitpid(pid, &status); if (child_pid == 0) { RERROR("Timeout waiting for child"); return -2; } if (child_pid == pid) { if (WIFEXITED(status)) { status = WEXITSTATUS(status); if ((status != 0) || (ret < 0)) { RERROR("Program returned code (%d) and output '%s'", status, answer); } else { RDEBUG2("Program returned code (%d) and output '%s'", status, answer); } return ret < 0 ? ret : status; } } RERROR("Abnormal child exit: %s", fr_syserror(errno)); #endif /* __MINGW32__ */ return -1; }