Rearranged the code to do:
[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,2001,2002,2003,2004  The FreeRADIUS server project
21  */
22 static const char rcsid[] = "$Id$";
23
24 #include "autoconf.h"
25
26 #include <sys/file.h>
27
28 #include <stdlib.h>
29 #include <string.h>
30 #include <fcntl.h>
31 #include <ctype.h>
32 #include <signal.h>
33
34 #ifdef HAVE_SYS_WAIT_H
35 #       include <sys/wait.h>
36 #endif
37 #ifndef WEXITSTATUS
38 #       define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
39 #endif
40 #ifndef WIFEXITED
41 #       define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
42 #endif
43
44 #include "radiusd.h"
45 #include "rad_assert.h"
46
47 /*
48  *      Copy a quoted string.
49  */
50 static int copy_string(const char *from, char *to)
51 {
52         int length = 0;
53         char quote = *from;
54
55         do {
56                 if (*from == '\\') {
57                         *(to++) = *(from++);
58                         length++;
59                 }
60                 *(to++) = *(from++);
61                 length++;
62         } while (*from && (*from != quote));
63
64         if (*from != quote) return -1; /* not properly quoted */
65
66         *(to++) = quote;
67         length++;
68         *to = '\0';
69
70         return length;
71 }
72
73
74 /*
75  *      Copy a %{} string.
76  */
77 static int copy_var(const char *from, char *to)
78 {
79         int length = 0;
80         int sublen;
81
82         *(to++) = *(from++);
83         length++;
84
85         while (*from) {
86                 switch (*from) {
87                 case '"':
88                 case '\'':
89                         sublen = copy_string(from, to);
90                         if (sublen < 0) return sublen;
91                         from += sublen;
92                         to += sublen;
93                         break;
94
95                 case '}':       /* end of variable expansion */
96                         *(to++) = *(from++);
97                         *to = '\0';
98                         length++;
99                         return length; /* proper end of variable */
100
101                 case '\\':
102                         *(to++) = *(from++);
103                         *(to++) = *(from++);
104                         length += 2;
105                         break;
106
107                 case '%':       /* start of variable expansion */
108                         if (from[1] == '{') {
109                                 *(to++) = *(from++);
110                                 length++;
111                                 
112                                 sublen = copy_var(from, to);
113                                 if (sublen < 0) return sublen;
114                                 from += sublen;
115                                 to += sublen;
116                                 length += sublen;
117                         } /* else FIXME: catch %%{ ?*/
118
119                         /* FALL-THROUGH */
120                         break;
121
122                 default:
123                         *(to++) = *(from++);
124                         length++;
125                         break;
126                 }
127         } /* loop over the input string */
128
129         /*
130          *      We ended the string before a trailing '}'
131          */
132
133         return -1;
134 }
135
136
137 /*
138  *      Execute a program on successful authentication.
139  *      Return 0 if exec_wait == 0.
140  *      Return the exit code of the called program if exec_wait != 0.
141  *      Return -1 on fork/other errors in the parent process.
142  */
143 int radius_exec_program(const char *cmd, REQUEST *request,
144                         int exec_wait,
145                         char *user_msg, int msg_len,
146                         VALUE_PAIR *input_pairs,
147                         VALUE_PAIR **output_pairs)
148 {
149         VALUE_PAIR *vp;
150         char mycmd[1024];
151         char answer[4096];
152         char *argv[256];
153         const char *from;
154         char *p, *to;
155         int pd[2];
156         pid_t pid, child_pid;
157         int argc = -1;
158         int comma = 0;
159         int status;
160         int i;
161         int n, left, done;
162
163         if (user_msg) *user_msg = '\0';
164         if (output_pairs) *output_pairs = NULL;
165
166         if (strlen(cmd) > (sizeof(mycmd) - 1)) {
167                 radlog(L_ERR|L_CONS, "Command line is too long");
168                 return -1;
169         }
170
171         /*
172          *      Check for bad escapes
173          */
174         if (cmd[strlen(mycmd)] == '\\') {
175                 radlog(L_ERR|L_CONS, "Command line has final backslash, without a following character");
176                 return -1;
177         }
178
179         strNcpy(mycmd, cmd, sizeof(mycmd));
180
181         /*
182          *      Split the string into argv's BEFORE doing radius_xlat...
183          */
184         from = cmd;
185         to = mycmd; 
186         argc = 0;
187         while (*from) {
188                 int length;
189
190                 /*
191                  *      Skip spaces.
192                  */
193                 if ((*from == ' ') || (*from == '\t')) {
194                         from++;
195                         continue;
196                 }
197
198                 argv[argc] = to;
199                 argc++;
200                 
201                 /*
202                  *      Copy the argv over to our buffer.
203                  */
204                 while (*from && (*from != ' ') && (*from != '\t')) {
205                         switch (*from) {
206                         case '"':
207                         case '\'':
208                                 length = copy_string(from, to);
209                                 if (length < 0) {
210                                         radlog(L_ERR|L_CONS, "Invalid string passed as argument for external program");
211                                         return -1;
212                                 }
213                                 from += length;
214                                 to += length;
215                                 break;
216
217                         case '%':
218                                 if (from[1] == '{') {
219                                         *(to++) = *(from++);
220                                         
221                                         length = copy_var(from, to);
222                                         if (length < 0) {
223                                                 radlog(L_ERR|L_CONS, "Invalid variable expansion passed as argument for external program");
224                                                 return -1;
225                                         }
226                                         from += length;
227                                         to += length;
228                                 } else { /* FIXME: catch %%{ ? */
229                                         *(to++) = *(from++);
230                                 }
231                                 break;
232
233                         default:
234                                 *(to++) = *(from++);
235                         }
236                 } /* end of string, or found a space */
237
238                 *(to++) = '\0'; /* terminate the string. */
239         }
240
241         /*
242          *      We have to have SOMETHING, at least.
243          */
244         if (argc <= 0) {
245                 radlog(L_ERR, "Exec-Program: empty command line.");
246                 return -1;
247         }
248
249         /*
250          *      Expand each string, as appropriate
251          */
252         to = answer;
253         left = sizeof(answer);
254         for (i = 0; i < argc; i++) {
255                 int sublen;
256
257                 /*
258                  *      Don't touch argv's which won't be translated.
259                  */
260                 if (strchr(argv[i], '%') == NULL) continue;
261
262                 sublen = radius_xlat(to, left - 1, argv[i], request, NULL);
263                 if (sublen <= 0) {
264                         /*
265                          *      Fail to be backwards compatible.
266                          *
267                          *      It's yucky, but it won't break anything,
268                          *      and it won't cause security problems.
269                          */
270                         sublen = 0;
271                 }
272
273                 argv[i] = to;
274                 to += sublen;
275                 *(to++) = '\0';
276                 left -= sublen;
277                 left--;
278
279                 if (left <= 0) {
280                         radlog(L_ERR, "Exec-Program: Ran out of space while expanding arguments.");
281                         return -1;
282                 }
283         }
284
285         /*
286          *      Open a pipe for child/parent communication, if
287          *      necessary.
288          */
289         if (exec_wait) {
290                 if (pipe(pd) != 0) {
291                         radlog(L_ERR|L_CONS, "Couldn't open pipe: %s",
292                                strerror(errno));
293                         return -1;
294                 }
295         } else {
296                 /*
297                  *      We're not waiting, so we don't look for a
298                  *      message, or VP's.
299                  */
300                 user_msg = NULL;
301                 output_pairs = NULL;
302         }
303
304         if ((pid = rad_fork(exec_wait)) == 0) {
305 #define MAX_ENVP 1024
306                 int devnull;
307                 char *envp[MAX_ENVP];
308                 int envlen;
309                 char buffer[1024];
310
311                 /*
312                  *      Child process.
313                  *
314                  *      We try to be fail-safe here.  So if ANYTHING
315                  *      goes wrong, we exit with status 1.
316                  */
317
318                 /*
319                  *      Open STDIN to /dev/null
320                  */
321                 devnull = open("/dev/null", O_RDWR);
322                 if (devnull < 0) {
323                         radlog(L_ERR|L_CONS, "Failed opening /dev/null: %s\n",
324                                strerror(errno));
325                         exit(1);
326                 }
327                 dup2(devnull, STDIN_FILENO);
328
329                 /*
330                  *      Only massage the pipe handles if the parent
331                  *      has created them.
332                  */
333                 if (exec_wait) {
334                         /*
335                          *      pd[0] is the FD the child will read from,
336                          *      which we don't want.
337                          */
338                         if (close(pd[0]) != 0) {
339                                 radlog(L_ERR|L_CONS, "Can't close pipe: %s",
340                                        strerror(errno));
341                                 exit(1);
342                         }
343
344                         /*
345                          *      pd[1] is the FD that the child will write to,
346                          *      so we make it STDOUT.
347                          */
348                         if (dup2(pd[1], STDOUT_FILENO) != 1) {
349                                 radlog(L_ERR|L_CONS, "Can't dup stdout: %s",
350                                        strerror(errno));
351                                 exit(1);
352                         }
353
354                 } else {        /* no pipe, STDOUT should be /dev/null */
355                         dup2(devnull, STDOUT_FILENO);
356                 }
357
358                 /*
359                  *      If we're not debugging, then we can't do
360                  *      anything with the error messages, so we throw
361                  *      them away.
362                  *
363                  *      If we are debugging, then we want the error
364                  *      messages to go to the STDERR of the server.
365                  */
366                 if (debug_flag == 0) {
367                         dup2(devnull, STDERR_FILENO);
368                 }
369                 close(devnull);
370
371                 /*
372                  *      The server may have MANY FD's open.  We don't
373                  *      want to leave dangling FD's for the child process
374                  *      to play funky games with, so we close them.
375                  */
376                 for (i = 3; i < 256; i++) {
377                         close(i);
378                 }
379
380                 /*
381                  *      Set up the environment variables.
382                  *      We're in the child, and it will exit in 4 lines
383                  *      anyhow, so memory allocation isn't an issue.
384                  */
385                 envlen = 0;
386
387                 for (vp = input_pairs; vp != NULL; vp = vp->next) {
388                         /*
389                          *      Hmm... maybe we shouldn't pass the
390                          *      user's password in an environment
391                          *      variable...
392                          */
393                         snprintf(buffer, sizeof(buffer), "%s=", vp->name);
394                         for (p = buffer; *p != '='; p++) {
395                                 if (*p == '-') {
396                                         *p = '_';
397                                 } else if (isalpha((int) *p)) {
398                                         *p = toupper(*p);
399                                 }
400                         }
401
402                         n = strlen(buffer);
403                         vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
404
405                         envp[envlen++] = strdup(buffer);
406                 }
407                 envp[envlen] = NULL;
408                 execve(argv[0], argv, envp);
409                 radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
410                        argv[0], strerror(errno));
411                 exit(1);
412         }
413
414         /*
415          *      Parent process.
416          */
417         if (pid < 0) {
418                 radlog(L_ERR|L_CONS, "Couldn't fork %s: %s",
419                        argv[0], strerror(errno));
420                 return -1;
421         }
422
423         /*
424          *      We're not waiting, exit, and ignore any child's
425          *      status.
426          */
427         if (!exec_wait) {
428                 return 0;
429         }
430
431         /*
432          *      Close the FD to which the child writes it's data.
433          *
434          *      If we can't close it, then we close pd[0], and return an
435          *      error.
436          */
437         if (close(pd[1]) != 0) {
438                 radlog(L_ERR|L_CONS, "Can't close pipe: %s", strerror(errno));
439                 close(pd[0]);
440                 return -1;
441         }
442
443         /*
444          *      Read from the pipe until we doesn't get any more or
445          *      until the message is full.
446          */
447         done = 0;
448         left = sizeof(answer) - 1;
449         while (1) {
450                 status = read(pd[0], answer + done, left);
451                 /*
452                  *      Nothing more to read: stop.
453                  */
454                 if (status == 0) {
455                         break;
456                 }
457
458                 /*
459                  *      Error: See if we have to continue.
460                  */
461                 if (status < 0) {
462                         /*
463                          *      We were interrupted: continue reading.
464                          */
465                         if (errno == EINTR) {
466                                 continue;
467                         }
468
469                         /*
470                          *      There was another error.  Most likely
471                          *      The child process has finished, and
472                          *      exited.
473                          */
474                         break;
475                 }
476
477                 done += status;
478                 left -= status;
479                 if (left <= 0) break;
480         }
481         answer[done] = 0;
482
483         /*
484          *      Make sure that the writer can't block while writing to
485          *      a pipe that no one is reading from anymore.
486          */
487         close(pd[0]);
488
489         DEBUG2("Exec-Program output: %s", answer);
490
491         /*
492          *      Parse the output, if any.
493          */
494         if (done) {
495                 n = T_INVALID;
496                 if (output_pairs) {
497                         /*
498                          *      For backwards compatibility, first check
499                          *      for plain text (user_msg).
500                          */
501                         vp = NULL;
502                         n = userparse(answer, &vp);
503                         if (vp) {
504                                 pairfree(&vp);
505                         }
506                 }
507
508                 if (n == T_INVALID) {
509                         DEBUG("Exec-Program-Wait: plaintext: %s", answer);
510                         if (user_msg) {
511                                 strNcpy(user_msg, answer, msg_len);
512                         }
513                 } else {
514                         /*
515                          *      HACK: Replace '\n' with ',' so that
516                          *      userparse() can parse the buffer in
517                          *      one go (the proper way would be to
518                          *      fix userparse(), but oh well).
519                          */
520                         for (p = answer; *p; p++) {
521                                 if (*p == '\n') {
522                                         *p = comma ? ' ' : ',';
523                                         p++;
524                                         comma = 0;
525                                 }
526                                 if (*p == ',') comma++;
527                         }
528
529                         /*
530                          *  Replace any trailing comma by a NUL.
531                          */
532                         if (answer[strlen(answer) - 1] == ',') {
533                                 answer[strlen(answer) - 1] = '\0';
534                         }
535
536                         radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
537                         if (userparse(answer, &vp) == T_INVALID) {
538                                 radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
539
540                         } else {
541                                 /*
542                                  *      Tell the caller about the value
543                                  *      pairs.
544                                  */
545                                 *output_pairs = vp;
546                         }
547                 } /* else the answer was a set of VP's, not a text message */
548         } /* else we didn't read anything from the child. */
549
550         /*
551          *      Call rad_waitpid (should map to waitpid on non-threaded
552          *      or single-server systems).
553          */
554         child_pid = rad_waitpid(pid, &status, 0);
555         if (child_pid == pid) {
556                 if (WIFEXITED(status)) {
557                         status = WEXITSTATUS(status);
558                         radlog(L_DBG, "Exec-Program: returned: %d", status);
559                         return status;
560                 }
561         }
562
563         radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit: %s",
564                strerror(errno));
565         return 1;
566 }