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$";
35 #ifdef HAVE_SYS_WAIT_H
36 # include <sys/wait.h>
39 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
42 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
46 #include "rad_assert.h"
49 * Execute a program on successful authentication.
50 * Return 0 if exec_wait == 0.
51 * Return the exit code of the called program if exec_wait != 0.
52 * Return -1 on fork/other errors in the parent process.
54 int radius_exec_program(const char *cmd, REQUEST *request,
56 char *user_msg, int msg_len,
57 VALUE_PAIR *input_pairs,
58 VALUE_PAIR **output_pairs)
71 if (user_msg) *user_msg = '\0';
72 if (output_pairs) *output_pairs = NULL;
75 * Open a pipe for child/parent communication, if
80 radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
86 * We're not waiting, so we don't look for a
94 * Do the translation (as the parent) of the command to
95 * execute. This MAY involve calling other modules, so
96 * we want to do it in the parent.
98 radius_xlat(answer, sizeof(answer), cmd, request, NULL);
102 * Log the command if we are debugging something
104 DEBUG("Exec-Program: %s", buf);
107 * Build vector list of arguments and execute.
109 * FIXME: This parsing gets excited over spaces in
110 * the translated strings, e.g. User-Name = "aa bb"
111 * is passed as two seperate arguments, instead of one.
113 * What we SHOULD do instead is to split the exec program
114 * buffer first, and then do the translation on every
117 p = strtok(buf, " \t");
120 p = strtok(NULL, " \t");
125 radlog(L_ERR, "Exec-Program: empty command line.");
129 if ((pid = rad_fork(exec_wait)) == 0) {
130 #define MAX_ENVP 1024
132 char *envp[MAX_ENVP];
139 * We try to be fail-safe here. So if ANYTHING
140 * goes wrong, we exit with status 1.
144 * Open STDIN to /dev/null
146 devnull = open("/dev/null", O_RDWR);
148 radlog(L_ERR|L_CONS, "Failed opening /dev/null: %s\n",
152 dup2(devnull, STDIN_FILENO);
155 * Only massage the pipe handles if the parent
160 * pd[0] is the FD the child will read from,
161 * which we don't want.
163 if (close(pd[0]) != 0) {
164 radlog(L_ERR|L_CONS, "Can't close pipe: %s",
170 * pd[1] is the FD that the child will write to,
171 * so we make it STDOUT.
173 if (dup2(pd[1], STDOUT_FILENO) != 1) {
174 radlog(L_ERR|L_CONS, "Can't dup stdout: %s",
179 } else { /* no pipe, STDOUT should be /dev/null */
180 dup2(devnull, STDOUT_FILENO);
184 * If we're not debugging, then we can't do
185 * anything with the error messages, so we throw
188 * If we are debugging, then we want the error
189 * messages to go to the STDERR of the server.
191 if (debug_flag == 0) {
192 dup2(devnull, STDERR_FILENO);
197 * The server may have MANY FD's open. We don't
198 * want to leave dangling FD's for the child process
199 * to play funky games with, so we close them.
201 for (i = 3; i < 256; i++) {
206 * Set up the environment variables.
207 * We're in the child, and it will exit in 4 lines
208 * anyhow, so memory allocation isn't an issue.
212 for (vp = input_pairs; vp != NULL; vp = vp->next) {
214 * Hmm... maybe we shouldn't pass the
215 * user's password in an environment
218 snprintf(buffer, sizeof(buffer), "%s=", vp->name);
219 for (p = buffer; *p != '='; p++) {
222 } else if (isalpha((int) *p)) {
228 vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
230 envp[envlen++] = strdup(buffer);
233 execve(argv[0], argv, envp);
234 radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
235 argv[0], strerror(errno));
243 radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
244 argv[0], strerror(errno));
249 * We're not waiting, exit, and ignore any child's
257 * Close the FD to which the child writes it's data.
259 * If we can't close it, then we close pd[0], and return an
262 if (close(pd[1]) != 0) {
263 radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
269 * Read from the pipe until we doesn't get any more or
270 * until the message is full.
273 left = sizeof(answer) - 1;
275 status = read(pd[0], answer + done, left);
277 * Nothing more to read: stop.
284 * Error: See if we have to continue.
288 * We were interrupted: continue reading.
290 if (errno == EINTR) {
295 * There was another error. Most likely
296 * The child process has finished, and
304 if (left <= 0) break;
309 * Make sure that the writer can't block while writing to
310 * a pipe that no one is reading from anymore.
314 DEBUG2("Exec-Program output: %s", answer);
317 * Parse the output, if any.
323 * For backwards compatibility, first check
324 * for plain text (user_msg).
327 n = userparse(answer, &vp);
333 if (n == T_INVALID) {
334 radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
336 strNcpy(user_msg, answer, msg_len);
340 * HACK: Replace '\n' with ',' so that
341 * userparse() can parse the buffer in
342 * one go (the proper way would be to
343 * fix userparse(), but oh well).
345 for (p = answer; *p; p++) {
347 *p = comma ? ' ' : ',';
351 if (*p == ',') comma++;
355 * Replace any trailing comma by a NUL.
357 if (answer[strlen(answer) - 1] == ',') {
358 answer[strlen(answer) - 1] = '\0';
361 radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
362 if (userparse(answer, &vp) == T_INVALID) {
363 radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
367 * Tell the caller about the value
372 } /* else the answer was a set of VP's, not a text message */
373 } /* else we didn't read anything from the child. */
376 * Call rad_waitpid (should map to waitpid on non-threaded
377 * or single-server systems).
379 child_pid = rad_waitpid(pid, &status, 0);
380 if (child_pid == pid) {
381 if (WIFEXITED(status)) {
382 status = WEXITSTATUS(status);
383 radlog(L_DBG, "Exec-Program: returned: %d", status);
388 radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit: %s",