2 * exec.c Execute external programs.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2000,2001,2002,2003,2004 The FreeRADIUS server project
22 static const char rcsid[] = "$Id$";
34 #ifdef HAVE_SYS_WAIT_H
35 # include <sys/wait.h>
38 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
41 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
45 #include "rad_assert.h"
48 * Copy a quoted string.
50 static int copy_string(const char *from, char *to)
62 } while (*from && (*from != quote));
64 if (*from != quote) return -1; /* not properly quoted */
77 static int copy_var(const char *from, char *to)
89 sublen = copy_string(from, to);
90 if (sublen < 0) return sublen;
95 case '}': /* end of variable expansion */
99 return length; /* proper end of variable */
107 case '%': /* start of variable expansion */
108 if (from[1] == '{') {
112 sublen = copy_var(from, to);
113 if (sublen < 0) return sublen;
117 } /* else FIXME: catch %%{ ?*/
127 } /* loop over the input string */
130 * We ended the string before a trailing '}'
138 * Execute a program on successful authentication.
139 * Return 0 if exec_wait == 0.
140 * Return the exit code of the called program if exec_wait != 0.
141 * Return -1 on fork/other errors in the parent process.
143 int radius_exec_program(const char *cmd, REQUEST *request,
145 char *user_msg, int msg_len,
146 VALUE_PAIR *input_pairs,
147 VALUE_PAIR **output_pairs)
156 pid_t pid, child_pid;
163 if (user_msg) *user_msg = '\0';
164 if (output_pairs) *output_pairs = NULL;
166 if (strlen(cmd) > (sizeof(mycmd) - 1)) {
167 radlog(L_ERR|L_CONS, "Command line is too long");
172 * Check for bad escapes
174 if (cmd[strlen(mycmd)] == '\\') {
175 radlog(L_ERR|L_CONS, "Command line has final backslash, without a following character");
179 strNcpy(mycmd, cmd, sizeof(mycmd));
182 * Split the string into argv's BEFORE doing radius_xlat...
193 if ((*from == ' ') || (*from == '\t')) {
202 * Copy the argv over to our buffer.
204 while (*from && (*from != ' ') && (*from != '\t')) {
208 length = copy_string(from, to);
210 radlog(L_ERR|L_CONS, "Invalid string passed as argument for external program");
218 if (from[1] == '{') {
221 length = copy_var(from, to);
223 radlog(L_ERR|L_CONS, "Invalid variable expansion passed as argument for external program");
228 } else { /* FIXME: catch %%{ ? */
236 } /* end of string, or found a space */
238 *(to++) = '\0'; /* terminate the string. */
242 * We have to have SOMETHING, at least.
245 radlog(L_ERR, "Exec-Program: empty command line.");
250 * Expand each string, as appropriate
253 left = sizeof(answer);
254 for (i = 0; i < argc; i++) {
258 * Don't touch argv's which won't be translated.
260 if (strchr(argv[i], '%') == NULL) continue;
262 sublen = radius_xlat(to, left - 1, argv[i], request, NULL);
265 * Fail to be backwards compatible.
267 * It's yucky, but it won't break anything,
268 * and it won't cause security problems.
280 radlog(L_ERR, "Exec-Program: Ran out of space while expanding arguments.");
286 * Open a pipe for child/parent communication, if
291 radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
297 * We're not waiting, so we don't look for a
304 if ((pid = rad_fork(exec_wait)) == 0) {
305 #define MAX_ENVP 1024
307 char *envp[MAX_ENVP];
314 * We try to be fail-safe here. So if ANYTHING
315 * goes wrong, we exit with status 1.
319 * Open STDIN to /dev/null
321 devnull = open("/dev/null", O_RDWR);
323 radlog(L_ERR|L_CONS, "Failed opening /dev/null: %s\n",
327 dup2(devnull, STDIN_FILENO);
330 * Only massage the pipe handles if the parent
335 * pd[0] is the FD the child will read from,
336 * which we don't want.
338 if (close(pd[0]) != 0) {
339 radlog(L_ERR|L_CONS, "Can't close pipe: %s",
345 * pd[1] is the FD that the child will write to,
346 * so we make it STDOUT.
348 if (dup2(pd[1], STDOUT_FILENO) != 1) {
349 radlog(L_ERR|L_CONS, "Can't dup stdout: %s",
354 } else { /* no pipe, STDOUT should be /dev/null */
355 dup2(devnull, STDOUT_FILENO);
359 * If we're not debugging, then we can't do
360 * anything with the error messages, so we throw
363 * If we are debugging, then we want the error
364 * messages to go to the STDERR of the server.
366 if (debug_flag == 0) {
367 dup2(devnull, STDERR_FILENO);
372 * The server may have MANY FD's open. We don't
373 * want to leave dangling FD's for the child process
374 * to play funky games with, so we close them.
376 for (i = 3; i < 256; i++) {
381 * Set up the environment variables.
382 * We're in the child, and it will exit in 4 lines
383 * anyhow, so memory allocation isn't an issue.
387 for (vp = input_pairs; vp != NULL; vp = vp->next) {
389 * Hmm... maybe we shouldn't pass the
390 * user's password in an environment
393 snprintf(buffer, sizeof(buffer), "%s=", vp->name);
394 for (p = buffer; *p != '='; p++) {
397 } else if (isalpha((int) *p)) {
403 vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
405 envp[envlen++] = strdup(buffer);
408 execve(argv[0], argv, envp);
409 radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
410 argv[0], strerror(errno));
418 radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
419 argv[0], strerror(errno));
424 * We're not waiting, exit, and ignore any child's
432 * Close the FD to which the child writes it's data.
434 * If we can't close it, then we close pd[0], and return an
437 if (close(pd[1]) != 0) {
438 radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
444 * Read from the pipe until we doesn't get any more or
445 * until the message is full.
448 left = sizeof(answer) - 1;
450 status = read(pd[0], answer + done, left);
452 * Nothing more to read: stop.
459 * Error: See if we have to continue.
463 * We were interrupted: continue reading.
465 if (errno == EINTR) {
470 * There was another error. Most likely
471 * The child process has finished, and
479 if (left <= 0) break;
484 * Make sure that the writer can't block while writing to
485 * a pipe that no one is reading from anymore.
489 DEBUG2("Exec-Program output: %s", answer);
492 * Parse the output, if any.
498 * For backwards compatibility, first check
499 * for plain text (user_msg).
502 n = userparse(answer, &vp);
508 if (n == T_INVALID) {
509 DEBUG("Exec-Program-Wait: plaintext: %s", answer);
511 strNcpy(user_msg, answer, msg_len);
515 * HACK: Replace '\n' with ',' so that
516 * userparse() can parse the buffer in
517 * one go (the proper way would be to
518 * fix userparse(), but oh well).
520 for (p = answer; *p; p++) {
522 *p = comma ? ' ' : ',';
526 if (*p == ',') comma++;
530 * Replace any trailing comma by a NUL.
532 if (answer[strlen(answer) - 1] == ',') {
533 answer[strlen(answer) - 1] = '\0';
536 radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
537 if (userparse(answer, &vp) == T_INVALID) {
538 radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
542 * Tell the caller about the value
547 } /* else the answer was a set of VP's, not a text message */
548 } /* else we didn't read anything from the child. */
551 * Call rad_waitpid (should map to waitpid on non-threaded
552 * or single-server systems).
554 child_pid = rad_waitpid(pid, &status, 0);
555 if (child_pid == pid) {
556 if (WIFEXITED(status)) {
557 status = WEXITSTATUS(status);
558 radlog(L_DBG, "Exec-Program: returned: %d", status);
563 radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit: %s",