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 * Execute a program on successful authentication.
51 * Return 0 if exec_wait == 0.
52 * Return the exit code of the called program if exec_wait != 0.
53 * Return -1 on fork/other errors in the parent process.
55 int radius_exec_program(const char *cmd, REQUEST *request,
57 char *user_msg, int msg_len,
58 VALUE_PAIR *input_pairs,
59 VALUE_PAIR **output_pairs)
72 if (user_msg) *user_msg = '\0';
73 if (output_pairs) *output_pairs = NULL;
76 * Open a pipe for child/parent communication, if
81 radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
87 * We're not waiting, so we don't look for a
95 * Do the translation (as the parent) of the command to
96 * execute. This MAY involve calling other modules, so
97 * we want to do it in the parent.
99 radius_xlat(answer, sizeof(answer), cmd, request, NULL);
103 * Log the command if we are debugging something
105 DEBUG("Exec-Program: %s", buf);
108 * Build vector list of arguments and execute.
110 * FIXME: This parsing gets excited over spaces in
111 * the translated strings, e.g. User-Name = "aa bb"
112 * is passed as two seperate arguments, instead of one.
114 * What we SHOULD do instead is to split the exec program
115 * buffer first, and then do the translation on every
118 p = strtok(buf, " \t");
121 p = strtok(NULL, " \t");
126 radlog(L_ERR, "Exec-Program: empty command line.");
130 if ((pid = rad_fork(exec_wait)) == 0) {
131 #define MAX_ENVP 1024
133 char *envp[MAX_ENVP];
140 * We try to be fail-safe here. So if ANYTHING
141 * goes wrong, we exit with status 1.
145 * Open STDIN to /dev/null
147 devnull = open("/dev/null", O_RDWR);
149 radlog(L_ERR|L_CONS, "Failed opening /dev/null: %s\n",
153 dup2(devnull, STDIN_FILENO);
156 * Only massage the pipe handles if the parent
161 * pd[0] is the FD the child will read from,
162 * which we don't want.
164 if (close(pd[0]) != 0) {
165 radlog(L_ERR|L_CONS, "Can't close pipe: %s",
171 * pd[1] is the FD that the child will write to,
172 * so we make it STDOUT.
174 if (dup2(pd[1], STDOUT_FILENO) != 1) {
175 radlog(L_ERR|L_CONS, "Can't dup stdout: %s",
180 } else { /* no pipe, STDOUT should be /dev/null */
181 dup2(devnull, STDOUT_FILENO);
185 * If we're not debugging, then we can't do
186 * anything with the error messages, so we throw
189 * If we are debugging, then we want the error
190 * messages to go to the STDERR of the server.
192 if (debug_flag == 0) {
193 dup2(devnull, STDERR_FILENO);
198 * The server may have MANY FD's open. We don't
199 * want to leave dangling FD's for the child process
200 * to play funky games with, so we close them.
205 * Set up the environment variables.
206 * We're in the child, and it will exit in 4 lines
207 * anyhow, so memory allocation isn't an issue.
211 for (vp = input_pairs; vp != NULL; vp = vp->next) {
213 * Hmm... maybe we shouldn't pass the
214 * user's password in an environment
217 snprintf(buffer, sizeof(buffer), "%s=", vp->name);
218 for (p = buffer; *p != '='; p++) {
221 } else if (isalpha((int) *p)) {
227 vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
229 envp[envlen++] = strdup(buffer);
232 * Don't add too many attributes.
234 if (envlen == (MAX_ENVP - 1)) break;
237 execve(argv[0], argv, envp);
238 radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
239 argv[0], strerror(errno));
247 radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
248 argv[0], strerror(errno));
253 * We're not waiting, exit, and ignore any child's
261 * Close the FD to which the child writes it's data.
263 * If we can't close it, then we close pd[0], and return an
266 if (close(pd[1]) != 0) {
267 radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
273 * Read from the pipe until we doesn't get any more or
274 * until the message is full.
277 left = sizeof(answer) - 1;
279 status = read(pd[0], answer + done, left);
281 * Nothing more to read: stop.
288 * Error: See if we have to continue.
292 * We were interrupted: continue reading.
294 if (errno == EINTR) {
299 * There was another error. Most likely
300 * The child process has finished, and
308 if (left <= 0) break;
313 * Make sure that the writer can't block while writing to
314 * a pipe that no one is reading from anymore.
318 DEBUG2("Exec-Program output: %s", answer);
321 * Parse the output, if any.
327 * For backwards compatibility, first check
328 * for plain text (user_msg).
331 n = userparse(answer, &vp);
337 if (n == T_INVALID) {
338 radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
340 strNcpy(user_msg, answer, msg_len);
344 * HACK: Replace '\n' with ',' so that
345 * userparse() can parse the buffer in
346 * one go (the proper way would be to
347 * fix userparse(), but oh well).
349 for (p = answer; *p; p++) {
351 *p = comma ? ' ' : ',';
355 if (*p == ',') comma++;
359 * Replace any trailing comma by a NUL.
361 if (answer[strlen(answer) - 1] == ',') {
362 answer[strlen(answer) - 1] = '\0';
365 radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
366 if (userparse(answer, &vp) == T_INVALID) {
367 radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
371 * Tell the caller about the value
376 } /* else the answer was a set of VP's, not a text message */
377 } /* else we didn't read anything from the child. */
380 * Call rad_waitpid (should map to waitpid on non-threaded
381 * or single-server systems).
383 child_pid = rad_waitpid(pid, &status, 0);
384 if (child_pid == pid) {
385 if (WIFEXITED(status)) {
386 status = WEXITSTATUS(status);
387 radlog(L_DBG, "Exec-Program: returned: %d", status);
392 radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit: %s",