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 The FreeRADIUS server project
21 * Copyright 2000 Michael J. Hartwick <hartwick@hartwick.com>
23 static const char rcsid[] = "$Id$";
36 #ifdef HAVE_SYS_WAIT_H
37 # include <sys/wait.h>
40 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
43 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
47 #include "rad_assert.h"
50 * Copy a quoted string.
52 static int rad_copy_string(char *to, const char *from)
64 } while (*from && (*from != quote));
66 if (*from != quote) return -1; /* not properly quoted */
79 static int rad_copy_variable(char *to, const char *from)
91 sublen = rad_copy_string(to, from);
92 if (sublen < 0) return sublen;
97 case '}': /* end of variable expansion */
101 return length; /* proper end of variable */
109 case '%': /* start of variable expansion */
110 if (from[1] == '{') {
114 sublen = rad_copy_variable(to, from);
115 if (sublen < 0) return sublen;
119 } /* else FIXME: catch %%{ ?*/
129 } /* loop over the input string */
132 * We ended the string before a trailing '}'
138 #define MAX_ARGV (256)
140 * Execute a program on successful authentication.
141 * Return 0 if exec_wait == 0.
142 * Return the exit code of the called program if exec_wait != 0.
143 * Return -1 on fork/other errors in the parent process.
145 int radius_exec_program(const char *cmd, REQUEST *request,
147 char *user_msg, int msg_len,
148 VALUE_PAIR *input_pairs,
149 VALUE_PAIR **output_pairs)
155 char *argv[MAX_ARGV];
159 pid_t pid, child_pid;
166 if (user_msg) *user_msg = '\0';
167 if (output_pairs) *output_pairs = NULL;
169 if (strlen(cmd) > (sizeof(mycmd) - 1)) {
170 radlog(L_ERR|L_CONS, "Command line is too long");
175 * Check for bad escapes.
177 if (cmd[strlen(cmd) - 1] == '\\') {
178 radlog(L_ERR|L_CONS, "Command line has final backslash, without a following character");
182 strNcpy(mycmd, cmd, sizeof(mycmd));
185 * Split the string into argv's BEFORE doing radius_xlat...
196 if ((*from == ' ') || (*from == '\t')) {
204 if (argc >= (MAX_ARGV - 1)) break;
207 * Copy the argv over to our buffer.
209 while (*from && (*from != ' ') && (*from != '\t')) {
210 if (to >= mycmd + sizeof(mycmd) - 1) {
211 return -1; /* ran out of space */
217 length = rad_copy_string(to, from);
219 radlog(L_ERR|L_CONS, "Invalid string passed as argument for external program");
227 if (from[1] == '{') {
230 length = rad_copy_variable(to, from);
232 radlog(L_ERR|L_CONS, "Invalid variable expansion passed as argument for external program");
237 } else { /* FIXME: catch %%{ ? */
245 } /* end of string, or found a space */
247 *(to++) = '\0'; /* terminate the string */
251 * We have to have SOMETHING, at least.
254 radlog(L_ERR, "Exec-Program: empty command line.");
259 * Expand each string, as appropriate.
262 left = sizeof(argv_buf);
263 for (i = 0; i < argc; i++) {
267 * Don't touch argv's which won't be translated.
269 if (strchr(argv[i], '%') == NULL) continue;
271 sublen = radius_xlat(to, left - 1, argv[i], request, NULL);
274 * Fail to be backwards compatible.
276 * It's yucky, but it won't break anything,
277 * and it won't cause security problems.
289 radlog(L_ERR, "Exec-Program: Ran out of space while expanding arguments.");
296 * Open a pipe for child/parent communication, if
301 radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
307 * We're not waiting, so we don't look for a
315 pid = rad_fork(); /* remember PID */
317 pid = fork(); /* don't wait */
321 #define MAX_ENVP 1024
323 char *envp[MAX_ENVP];
330 * We try to be fail-safe here. So if ANYTHING
331 * goes wrong, we exit with status 1.
335 * Open STDIN to /dev/null
337 devnull = open("/dev/null", O_RDWR);
339 radlog(L_ERR|L_CONS, "Failed opening /dev/null: %s\n",
343 dup2(devnull, STDIN_FILENO);
346 * Only massage the pipe handles if the parent
351 * pd[0] is the FD the child will read from,
352 * which we don't want.
354 if (close(pd[0]) != 0) {
355 radlog(L_ERR|L_CONS, "Can't close pipe: %s",
361 * pd[1] is the FD that the child will write to,
362 * so we make it STDOUT.
364 if (dup2(pd[1], STDOUT_FILENO) != 1) {
365 radlog(L_ERR|L_CONS, "Can't dup stdout: %s",
370 } else { /* no pipe, STDOUT should be /dev/null */
371 dup2(devnull, STDOUT_FILENO);
375 * If we're not debugging, then we can't do
376 * anything with the error messages, so we throw
379 * If we are debugging, then we want the error
380 * messages to go to the STDERR of the server.
382 if (debug_flag == 0) {
383 dup2(devnull, STDERR_FILENO);
388 * The server may have MANY FD's open. We don't
389 * want to leave dangling FD's for the child process
390 * to play funky games with, so we close them.
395 * Set up the environment variables.
396 * We're in the child, and it will exit in 4 lines
397 * anyhow, so memory allocation isn't an issue.
401 for (vp = input_pairs; vp != NULL; vp = vp->next) {
403 * Hmm... maybe we shouldn't pass the
404 * user's password in an environment
407 snprintf(buffer, sizeof(buffer), "%s=", vp->name);
408 for (p = buffer; *p != '='; p++) {
411 } else if (isalpha((int) *p)) {
417 vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
419 envp[envlen++] = strdup(buffer);
422 * Don't add too many attributes.
424 if (envlen == (MAX_ENVP - 1)) break;
427 execve(argv[0], argv, envp);
428 radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
429 argv[0], strerror(errno));
437 radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
438 argv[0], strerror(errno));
443 * We're not waiting, exit, and ignore any child's
451 * Close the FD to which the child writes it's data.
453 * If we can't close it, then we close pd[0], and return an
456 if (close(pd[1]) != 0) {
457 radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
463 * Read from the pipe until we doesn't get any more or
464 * until the message is full.
467 left = sizeof(answer) - 1;
469 status = read(pd[0], answer + done, left);
471 * Nothing more to read: stop.
478 * Error: See if we have to continue.
482 * We were interrupted: continue reading.
484 if (errno == EINTR) {
489 * There was another error. Most likely
490 * The child process has finished, and
498 if (left <= 0) break;
503 * Make sure that the writer can't block while writing to
504 * a pipe that no one is reading from anymore.
508 DEBUG2("Exec-Program output: %s", answer);
511 * Parse the output, if any.
517 * For backwards compatibility, first check
518 * for plain text (user_msg).
521 n = userparse(answer, &vp);
527 if (n == T_INVALID) {
528 radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
530 strNcpy(user_msg, answer, msg_len);
534 * HACK: Replace '\n' with ',' so that
535 * userparse() can parse the buffer in
536 * one go (the proper way would be to
537 * fix userparse(), but oh well).
539 for (p = answer; *p; p++) {
541 *p = comma ? ' ' : ',';
545 if (*p == ',') comma++;
549 * Replace any trailing comma by a NUL.
551 if (answer[strlen(answer) - 1] == ',') {
552 answer[strlen(answer) - 1] = '\0';
555 radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
556 if (userparse(answer, &vp) == T_INVALID) {
557 radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
561 * Tell the caller about the value
566 } /* else the answer was a set of VP's, not a text message */
567 } /* else we didn't read anything from the child. */
570 * Call rad_waitpid (should map to waitpid on non-threaded
571 * or single-server systems).
573 child_pid = rad_waitpid(pid, &status);
574 if (child_pid == 0) {
575 radlog(L_DBG, "Exec-Program: Timeout waiting for child");
579 if (child_pid == pid) {
580 if (WIFEXITED(status)) {
581 status = WEXITSTATUS(status);
582 radlog(L_DBG, "Exec-Program: returned: %d", status);
587 radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit: %s",