Implemented callbacks for escape function.
[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 #include "libradius.h"
27
28 #include <sys/file.h>
29
30 #include <stdlib.h>
31 #include <string.h>
32 #include <fcntl.h>
33 #include <ctype.h>
34 #include <signal.h>
35
36 #if HAVE_SYS_WAIT_H
37 #       include <sys/wait.h>
38 #endif
39 #ifndef WEXITSTATUS
40 #       define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
41 #endif
42 #ifndef WIFEXITED
43 #       define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
44 #endif
45
46 #include "radiusd.h"
47
48 /*
49  *      Escape magic shell characters
50  */
51 static int escape_shell(char *out, int outlen, const char *in)
52 {
53         char *p = out;
54
55         DEBUG2("in = %s", in);
56
57         for ( ; outlen > 1 && *in; in++) {
58                 switch (*in) {
59                 default:
60                         *(p++) = *in;
61                         outlen--;
62                         break;
63
64                         /*
65                          *      Escape magic shell characters.
66                          */
67                 case '\\':
68                 case '\'':
69                 case '"':
70                 case '*':
71                 case '!':
72                 case '(':
73                 case ')':
74                 case '&':
75                 case '$':
76                 case '>':
77                 case '<':
78                 case '?':
79                 case '[':
80                 case ']':
81                 case '`':
82                 case ';':
83                         *(p++) = '\\';
84                         *(p++) = *in;
85                         outlen -= 2;
86                         break;
87                 }
88         }
89         *p = '\0';
90
91         DEBUG2("out = %s", out);
92
93         return strlen(out);
94 }
95
96 /*
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.
100  *
101  */
102 int radius_exec_program(const char *cmd, REQUEST *request,
103                         int exec_wait, const char **user_msg)
104 {
105         VALUE_PAIR *vp;
106         static char message[256];
107         char answer[4096];
108         char *argv[32];
109         char *buf, *p;
110         int pd[2];
111         pid_t pid;
112         int argc = -1;
113         int comma = 0;
114         int status;
115         int n, left, done;
116         void (*oldsig)(int) = NULL;
117
118         /*
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
125          *                gone children ...
126          */     
127         if (exec_wait) {
128                 if (pipe(pd) != 0) {
129                         radlog(L_ERR|L_CONS, "Couldn't open pipe: %m");
130                         pd[0] = pd[1] = 0;
131                 }
132                 if ((oldsig = signal(SIGCHLD, SIG_DFL)) == SIG_ERR) {
133                         radlog(L_ERR|L_CONS, "Can't reset SIGCHLD: %m");
134                         oldsig = NULL;
135                 }
136         }
137
138         if ((pid = fork()) == 0) {
139 #define MAX_ENVP 1024
140                 char *envp[MAX_ENVP];
141                 int envlen;
142                 char buffer[1024];
143
144                 /*      
145                  *      Child
146                  */
147                 radius_xlat(answer, sizeof(answer), cmd, request, escape_shell);
148                 buf = answer;
149
150                 /*
151                  *      Log the command if we are debugging something
152                  */
153                 DEBUG("Exec-Program: %s", buf);
154
155                 /*
156                  *      Build vector list and execute.
157                  */
158                 p = strtok(buf, " \t");
159                 if (p) do {
160                         argv[++argc] = p;
161                         p = strtok(NULL, " \t");
162                 } while(p != NULL);
163                 argv[++argc] = p;
164                 if (argc == 0) {
165                         radlog(L_ERR, "Exec-Program: empty command line.");
166                         exit(1);
167                 }
168
169                 if (exec_wait) {
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");
174                 }
175
176                 /*
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.
180                  */
181                 envlen = 0;
182
183                 for (vp = request->packet->vps; vp->next; vp = vp->next) {
184                         /*
185                          *      Hmm... maybe we shouldn't pass the
186                          *      user's password in an environment
187                          *      variable...
188                          */
189                         snprintf(buffer, sizeof(buffer), "%s=", vp->name);
190                         for (p = buffer; *p != '='; p++) {
191                                 if (*p == '-') {
192                                         *p = '_';
193                                 } else if (isalpha(*p)) {
194                                         *p = toupper(*p);
195                                 }
196                         }
197
198                         n = strlen(buffer);
199                         vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
200
201                         envp[envlen++] = strdup(buffer);
202                 }
203
204                 envp[envlen] = NULL;
205                 
206
207
208
209                 for(n = 32; n >= 3; n--)
210                         close(n);
211
212                 execve(argv[0], argv, envp);
213
214                 radlog(L_ERR, "Exec-Program: %s: %m", argv[0]);
215                 exit(1);
216         }
217
218         /*
219          *      Parent 
220          */
221         if (pid < 0) {
222                 radlog(L_ERR|L_CONS, "Couldn't fork: %m");
223                 return -1;
224         }
225         if (!exec_wait)
226                 return 0;
227
228         /*
229          *      (hs) Do we have a pipe?
230          *      --> Close the write side of the pipe 
231          *      --> Read from it.
232          */
233         done = 0;
234         if (pd[0] || pd[1]) {
235                 if (close(pd[1]) != 0)
236                         radlog(L_ERR|L_CONS, "Can't close pipe: %m");
237
238                 /*
239                  *      (hs) Read until we doesn't get any more
240                  *      or until the message is full.
241                  */
242                 done = 0;
243                 left = sizeof(answer) - 1;
244                 while ((n = read(pd[0], answer + done, left)) > 0) {
245                         done += n;
246                         left -= n;
247                         if (left <= 0) break;
248                 }
249                 answer[done] = 0;
250
251                 /*
252                  *      (hs) Make sure that the writer can't block
253                  *      while writing in a pipe that isn't read anymore.
254                  */
255                 close(pd[0]);
256         }
257
258         /*
259          *      Parse the output, if any.
260          */
261         if (done) {
262                 /*
263                  *      For backwards compatibility, first check
264                  *      for plain text (user_msg).
265                  */
266                 vp = NULL;
267                 n = userparse(answer, &vp);
268                 if (vp)
269                         pairfree(&vp);
270
271                 if (n != 0) {
272                         radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
273                         if (user_msg) {
274                                 strncpy(message, answer, sizeof(message));
275                                 message[sizeof(message) - 1] = 0;
276                                 *user_msg = message;
277                         }
278                 } else {
279                         /*
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).
284                          */
285                         for (p = answer; *p; p++) {
286                                 if (*p == '\n') {
287                                         *p = comma ? ' ' : ',';
288                                         p++;
289                                         comma = 0;
290                                 }
291                                 if (*p == ',') comma++;
292                         }
293
294                         /*
295                          *  Replace any trailing comma by a NUL.
296                          */
297                         if (answer[strlen(answer) - 1] == ',')
298                                 answer[strlen(answer) - 1] = '\0';
299
300                         radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
301                         if (userparse(answer, &vp) != 0)
302                                 radlog(L_ERR,
303                 "Exec-Program-Wait: %s: unparsable reply", cmd);
304                         else {
305                                 pairmove(&request->reply->vps, &vp);
306                                 pairfree(&vp);
307                         }
308                 }
309         }
310
311         while(waitpid(pid, &status, 0) != pid)
312                 ;
313
314         /*
315          *      (hs) Now we let our cleanup_sig handler take care for
316          *      all signals that will arise.
317          */
318         if (oldsig && (signal(SIGCHLD, oldsig) == SIG_ERR))
319                 radlog(L_ERR|L_CONS,
320                         "Can't set SIGCHLD to the cleanup handler: %m");
321         sig_cleanup(SIGCHLD);
322
323         if (WIFEXITED(status)) {
324                 status = WEXITSTATUS(status);
325                 radlog(L_INFO, "Exec-Program: returned: %d", status);
326                 return status;
327         }
328         radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit (killed or coredump)");
329
330         return 1;
331 }
332