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$";
26 #include "libradius.h"
37 # include <sys/wait.h>
40 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
43 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
49 * Escape magic shell characters
51 static int escape_shell(char *out, int outlen, const char *in)
55 DEBUG2("in = %s", in);
57 for ( ; outlen > 1 && *in; in++) {
65 * Escape magic shell characters.
91 DEBUG2("out = %s", out);
97 * Execute a program on successful authentication.
98 * Return 0 if exec_wait == 0.
99 * Return the exit code of the called program if exec_wait != 0.
102 int radius_exec_program(const char *cmd, REQUEST *request,
103 int exec_wait, const char **user_msg)
106 static char message[256];
116 void (*oldsig)(int) = NULL;
119 * (hs) - Open a pipe for child/parent communication.
120 * - Reset the signal handler for SIGCHLD, so
121 * we have a chance to notice the dead child here and
122 * not in some signal handler.
123 * This has to be done for the exec_wait case only, since
124 * if we don't wait we aren't interested in any
129 radlog(L_ERR|L_CONS, "Couldn't open pipe: %m");
132 if ((oldsig = signal(SIGCHLD, SIG_DFL)) == SIG_ERR) {
133 radlog(L_ERR|L_CONS, "Can't reset SIGCHLD: %m");
138 if ((pid = fork()) == 0) {
139 #define MAX_ENVP 1024
140 char *envp[MAX_ENVP];
147 radius_xlat(answer, sizeof(answer), cmd, request, escape_shell);
151 * Log the command if we are debugging something
153 DEBUG("Exec-Program: %s", buf);
156 * Build vector list and execute.
158 p = strtok(buf, " \t");
161 p = strtok(NULL, " \t");
165 radlog(L_ERR, "Exec-Program: empty command line.");
170 if (close(pd[0]) != 0)
171 radlog(L_ERR|L_CONS, "Can't close pipe: %m");
172 if (dup2(pd[1], 1) != 1)
173 radlog(L_ERR|L_CONS, "Can't dup stdout: %m");
177 * Set up the environment variables.
178 * We're in the child, and it will exit in 4 lines
179 * anyhow, so memory allocation isn't an issue.
183 for (vp = request->packet->vps; vp->next; vp = vp->next) {
185 * Hmm... maybe we shouldn't pass the
186 * user's password in an environment
189 snprintf(buffer, sizeof(buffer), "%s=", vp->name);
190 for (p = buffer; *p != '='; p++) {
193 } else if (isalpha(*p)) {
199 vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
201 envp[envlen++] = strdup(buffer);
209 for(n = 32; n >= 3; n--)
212 execve(argv[0], argv, envp);
214 radlog(L_ERR, "Exec-Program: %s: %m", argv[0]);
222 radlog(L_ERR|L_CONS, "Couldn't fork: %m");
229 * (hs) Do we have a pipe?
230 * --> Close the write side of the pipe
234 if (pd[0] || pd[1]) {
235 if (close(pd[1]) != 0)
236 radlog(L_ERR|L_CONS, "Can't close pipe: %m");
239 * (hs) Read until we doesn't get any more
240 * or until the message is full.
243 left = sizeof(answer) - 1;
244 while ((n = read(pd[0], answer + done, left)) > 0) {
247 if (left <= 0) break;
252 * (hs) Make sure that the writer can't block
253 * while writing in a pipe that isn't read anymore.
259 * Parse the output, if any.
263 * For backwards compatibility, first check
264 * for plain text (user_msg).
267 n = userparse(answer, &vp);
272 radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
274 strncpy(message, answer, sizeof(message));
275 message[sizeof(message) - 1] = 0;
280 * HACK: Replace '\n' with ',' so that
281 * userparse() can parse the buffer in
282 * one go (the proper way would be to
283 * fix userparse(), but oh well).
285 for (p = answer; *p; p++) {
287 *p = comma ? ' ' : ',';
291 if (*p == ',') comma++;
295 * Replace any trailing comma by a NUL.
297 if (answer[strlen(answer) - 1] == ',')
298 answer[strlen(answer) - 1] = '\0';
300 radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
301 if (userparse(answer, &vp) != 0)
303 "Exec-Program-Wait: %s: unparsable reply", cmd);
305 pairmove(&request->reply->vps, &vp);
311 while(waitpid(pid, &status, 0) != pid)
315 * (hs) Now we let our cleanup_sig handler take care for
316 * all signals that will arise.
318 if (oldsig && (signal(SIGCHLD, oldsig) == SIG_ERR))
320 "Can't set SIGCHLD to the cleanup handler: %m");
321 sig_cleanup(SIGCHLD);
323 if (WIFEXITED(status)) {
324 status = WEXITSTATUS(status);
325 radlog(L_INFO, "Exec-Program: returned: %d", status);
328 radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit (killed or coredump)");