b052a3a0814469465f4bf56e468f89365db9678f
[freeradius.git] / src / main / exec.c
1 /*
2  * exec.c       Execute external programs.
3  *
4  * Version:     $Id$
5  *
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.
10  *
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.
15  *
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
19  *
20  * Copyright 2000  The FreeRADIUS server project
21  * Copyright 2000  Michael J. Hartwick <hartwick@hartwick.com>
22  */
23 static const char rcsid[] = "$Id$";
24
25 #include "autoconf.h"
26
27 #include <sys/file.h>
28
29 #include <stdlib.h>
30 #include <string.h>
31 #include <fcntl.h>
32 #include <ctype.h>
33 #include <signal.h>
34
35 #if HAVE_SYS_WAIT_H
36 #       include <sys/wait.h>
37 #endif
38 #ifndef WEXITSTATUS
39 #       define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
40 #endif
41 #ifndef WIFEXITED
42 #       define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
43 #endif
44
45 #include "radiusd.h"
46 #include "rad_assert.h"
47
48 #ifndef HAVE_PTHREAD_H
49 #define rad_fork(n) fork()
50 #define rad_waitpid waitpid
51 #endif
52
53 /*
54  *      Execute a program on successful authentication.
55  *      Return 0 if exec_wait == 0.
56  *      Return the exit code of the called program if exec_wait != 0.
57  *      Return -1 on fork/other errors in the parent process.
58  */
59 int radius_exec_program(const char *cmd, REQUEST *request,
60                         int exec_wait, const char **user_msg)
61 {
62         VALUE_PAIR *vp;
63         static char message[256];
64         char answer[4096];
65         char *argv[256];
66         char *buf, *p;
67         int pd[2];
68         pid_t pid, child_pid;
69         int argc = -1;
70         int comma = 0;
71         int status;
72         int n, left, done;
73
74         /*
75          *      Open a pipe for child/parent communication, if
76          *      necessary.
77          */
78         if (exec_wait) {
79                 if (pipe(pd) != 0) {
80                         radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
81                                strerror(errno));
82                         return -1;
83                 }
84         }
85
86         /*
87          *      Do the translation (as the parent) of the command to
88          *      execute.  This MAY involve calling other modules, so
89          *      we want to do it in the parent.
90          */
91         radius_xlat(answer, sizeof(answer), cmd, request, NULL);
92         buf = answer;
93         
94         /*
95          *      Log the command if we are debugging something
96          */
97         DEBUG("Exec-Program: %s", buf);
98         
99         /*
100          *      Build vector list of arguments and execute.
101          *
102          *      FIXME: This parsing gets excited over spaces in
103          *      the translated strings, e.g. User-Name = "aa bb"
104          *      is passed as two seperate arguments, instead of one.
105          *
106          *      What we SHOULD do instead is to split the exec program
107          *      buffer first, and then do the translation on every
108          *      subsequent string.
109          */
110         p = strtok(buf, " \t");
111         if (p) do {
112                 argv[++argc] = p;
113                 p = strtok(NULL, " \t");
114         } while(p != NULL);
115
116         argv[++argc] = p;
117         if (argc == 0) {
118                 radlog(L_ERR, "Exec-Program: empty command line.");
119                 return -1;
120         }
121
122         if ((pid = rad_fork(exec_wait)) == 0) {
123 #define MAX_ENVP 1024
124                 int i, devnull;
125                 char *envp[MAX_ENVP];
126                 int envlen;
127                 char buffer[1024];
128
129                 /*      
130                  *      Child process.
131                  *
132                  *      We try to be fail-safe here.  So if ANYTHING
133                  *      goes wrong, we exit with status 1.
134                  */
135
136                 /*
137                  *      Open STDIN to /dev/null
138                  */
139                 devnull = open("/dev/null", O_RDWR);
140                 if (devnull < 0) {
141                         radlog(L_ERR|L_CONS, "Failed opening /dev/null: %s\n",
142                                strerror(errno));
143                         exit(1);
144                 }
145                 dup2(devnull, STDIN_FILENO);
146
147                 /*
148                  *      Only massage the pipe handles if the parent
149                  *      has created them.
150                  */
151                 if (exec_wait) {
152                         /*
153                          *      pd[0] is the FD the child will read from,
154                          *      which we don't want.
155                          */
156                         if (close(pd[0]) != 0) {
157                                 radlog(L_ERR|L_CONS, "Can't close pipe: %s",
158                                        strerror(errno));
159                                 exit(1);
160                         }
161                         
162                         /*
163                          *      pd[1] is the FD that the child will write to,
164                          *      so we make it STDOUT.
165                          */
166                         if (dup2(pd[1], STDOUT_FILENO) != 1) {
167                                 radlog(L_ERR|L_CONS, "Can't dup stdout: %s",
168                                        strerror(errno));
169                                 exit(1);
170                         }
171
172                 } else {        /* no pipe, STDOUT should be /dev/null */
173                         dup2(devnull, STDOUT_FILENO);
174                 }
175
176                 /*
177                  *      If we're not debugging, then we can't do
178                  *      anything with the error messages, so we throw
179                  *      them away.
180                  *
181                  *      If we are debugging, then we want the error
182                  *      messages to go to the STDERR of the server.
183                  */
184                 if (debug_flag == 0) {
185                         dup2(devnull, STDERR_FILENO);
186                 }
187                 close(devnull);
188
189                 /*
190                  *      The server may have MANY FD's open.  We don't
191                  *      want to leave dangling FD's for the child process
192                  *      to play funky games with, so we close them.
193                  */
194                 for (i = 3; i < 256; i++) {
195                         close(i);
196                 }
197
198                 /*
199                  *      Set up the environment variables.
200                  *      We're in the child, and it will exit in 4 lines
201                  *      anyhow, so memory allocation isn't an issue.
202                  */
203                 envlen = 0;
204
205                 for (vp = request->packet->vps; vp->next; vp = vp->next) {
206                         /*
207                          *      Hmm... maybe we shouldn't pass the
208                          *      user's password in an environment
209                          *      variable...
210                          */
211                         snprintf(buffer, sizeof(buffer), "%s=", vp->name);
212                         for (p = buffer; *p != '='; p++) {
213                                 if (*p == '-') {
214                                         *p = '_';
215                                 } else if (isalpha(*p)) {
216                                         *p = toupper(*p);
217                                 }
218                         }
219
220                         n = strlen(buffer);
221                         vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
222
223                         envp[envlen++] = strdup(buffer);
224                 }
225                 envp[envlen] = NULL;
226
227                 execve(argv[0], argv, envp);
228
229                 radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
230                        argv[0], strerror(errno));
231                 exit(1);
232         }
233
234         /*
235          *      Parent process.
236          */
237         if (pid < 0) {
238                 radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
239                        argv[0], strerror(errno));
240                 return -1;
241         }
242
243         /*
244          *      We're not waiting, exit, and ignore any child's
245          *      status.
246          */
247         if (!exec_wait) {
248                 return 0;
249         }
250
251         /*
252          *      Close the FD to which the child writes it's data.
253          *
254          *      If we can't close it, then we close pd[0], and return an
255          *      error.
256          */
257         if (close(pd[1]) != 0) {
258                 radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
259                 close(pd[0]);
260                 return -1;
261         }
262
263         /*
264          *      Read from the pipe until we doesn't get any more or
265          *      until the message is full.
266          */
267         done = 0;
268         left = sizeof(answer) - 1;
269         while (1) {
270                 status = read(pd[0], answer + done, left);
271                 /*
272                  *      Nothing more to read: stop.
273                  */
274                 if (status == 0) {
275                         break;
276                 }
277
278                 /*
279                  *      Error: See if we have to continue.
280                  */
281                 if (status < 0) {
282                         /*
283                          *      We were interrupted: continue reading.
284                          */
285                         if (errno == EINTR) {
286                                 continue;
287                         }
288
289                         /*
290                          *      There was another error.  Most likely
291                          *      The child process has finished, and
292                          *      exited.
293                          */
294                         break;
295                 }
296
297                 done += status;
298                 left -= status;
299                 if (left <= 0) break;
300         }
301         answer[done] = 0;
302
303         /*
304          *      Make sure that the writer can't block while writing to
305          *      a pipe that no one is reading from anymore.
306          */
307         close(pd[0]);
308
309         /*
310          *      Parse the output, if any.
311          */
312         if (done) {
313                 /*
314                  *      For backwards compatibility, first check
315                  *      for plain text (user_msg).
316                  */
317                 vp = NULL;
318                 n = userparse(answer, &vp);
319                 if (vp) {
320                         pairfree(&vp);
321                 }
322
323                 if (n < 0) {
324                         radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
325                         if (user_msg) {
326                                 strncpy(message, answer, sizeof(message));
327                                 message[sizeof(message) - 1] = 0;
328                                 *user_msg = message;
329                         }
330                 } else {
331                         /*
332                          *      HACK: Replace '\n' with ',' so that
333                          *      userparse() can parse the buffer in
334                          *      one go (the proper way would be to
335                          *      fix userparse(), but oh well).
336                          */
337                         for (p = answer; *p; p++) {
338                                 if (*p == '\n') {
339                                         *p = comma ? ' ' : ',';
340                                         p++;
341                                         comma = 0;
342                                 }
343                                 if (*p == ',') comma++;
344                         }
345
346                         /*
347                          *  Replace any trailing comma by a NUL.
348                          */
349                         if (answer[strlen(answer) - 1] == ',') {
350                                 answer[strlen(answer) - 1] = '\0';
351                         }
352
353                         radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
354                         if (userparse(answer, &vp) < 0) {
355                                 radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
356
357                         } else {
358                                 /*
359                                  *      Add the attributes to the reply.
360                                  */
361                                 pairmove(&request->reply->vps, &vp);
362                                 pairfree(&vp);
363                         }
364                 } /* else the answer was a set of VP's, not a text message */
365         } /* else we didn't read anything from the child. */
366
367         /*
368          *      Call rad_waitpid (should map to waitpid on non-threaded
369          *      or single-server systems).
370          */
371         child_pid = rad_waitpid(pid, &status, 0);
372         if (child_pid == pid) {
373                 if (WIFEXITED(status)) {
374                         status = WEXITSTATUS(status);
375                         radlog(L_DBG, "Exec-Program: returned: %d", status);
376                         return status;
377                 }
378         }
379
380         radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit");
381         return 1;
382 }