Initial revision
[freeradius.git] / src / main / exec.c
1 /*
2  * exec.c       Execute external programs.
3  *
4  * Version:     @(#)exec.c  1.83  07-Aug-1999  miquels@cistron.nl
5  *
6  */
7 char exec_sccsid[] =
8 "@(#)exec.c     1.83 Copyright 1999 Cistron Internet Services B.V."; 
9
10 #include        "autoconf.h"
11
12 #include        <sys/types.h>
13 #include        <sys/time.h>
14 #include        <sys/file.h>
15
16 #include        <stdio.h>
17 #include        <stdlib.h>
18 #include        <string.h>
19 #include        <fcntl.h>
20 #include        <time.h>
21 #include        <ctype.h>
22 #include        <unistd.h>
23 #include        <signal.h>
24 #include        <errno.h>
25 #include        <sys/wait.h>
26
27 #include        "radiusd.h"
28
29 /*
30  *      Replace %<whatever> in a string.
31  *
32  *      %p   Port number
33  *      %n   NAS IP address
34  *      %f   Framed IP address
35  *      %u   User name
36  *      %c   Callback-Number
37  *      %t   MTU
38  *      %a   Protocol (SLIP/PPP)
39  *      %s   Speed (PW_CONNECT_INFO)
40  *      %i   Calling Station ID
41  *
42  */
43 char *radius_xlate(char *str, VALUE_PAIR *request, VALUE_PAIR *reply)
44 {
45         static char buf[2048];
46         int n, i = 0, c;
47         char *p;
48         VALUE_PAIR *tmp;
49
50         for (p = str; *p; p++) {
51                 c = *p;
52                 if (c != '%') {
53                         buf[i++] = *p;
54                         continue;
55                 }
56                 if (*++p == 0) break;
57                 if (c == '%') switch(*p) {
58                         case '%':
59                                 buf[i++] = *p;
60                                 break;
61                         case 'f': /* Framed IP address */
62                                 n = 0;
63                                 if ((tmp = pairfind(reply,
64                                      PW_FRAMED_IP_ADDRESS)) != NULL) {
65                                         n = tmp->lvalue;
66                                 }
67                                 ip_ntoa(buf + i, n);
68                                 i += strlen(buf + i);
69                                 break;
70                         case 'n': /* NAS IP address */
71                                 n = 0;
72                                 if ((tmp = pairfind(request,
73                                      PW_NAS_IP_ADDRESS)) != NULL) {
74                                         n = tmp->lvalue;
75                                 }
76                                 ip_ntoa(buf + i, n);
77                                 i += strlen(buf + i);
78                                 break;
79                         case 't': /* MTU */
80                                 n = 0;
81                                 if ((tmp = pairfind(reply,
82                                      PW_FRAMED_MTU)) != NULL) {
83                                         n = tmp->lvalue;
84                                 }
85                                 sprintf(buf + i, "%d", n);
86                                 i += strlen(buf + i);
87                                 break;
88                         case 'p': /* Port number */
89                                 n = 0;
90                                 if ((tmp = pairfind(request,
91                                      PW_NAS_PORT_ID)) != NULL) {
92                                         n = tmp->lvalue;
93                                 }
94                                 sprintf(buf + i, "%d", n);
95                                 i += strlen(buf + i);
96                                 break;
97                         case 'u': /* User name */
98                                 if ((tmp = pairfind(request,
99                                      PW_USER_NAME)) != NULL)
100                                         strcpy(buf + i, tmp->strvalue);
101                                 else
102                                         strcpy(buf + i, "unknown");
103                                 i += strlen(buf + i);
104                                 break;
105                         case 'i': /* Calling station ID */
106                                 if ((tmp = pairfind(request,
107                                      PW_CALLING_STATION_ID)) != NULL)
108                                         strcpy(buf + i, tmp->strvalue);
109                                 else
110                                         strcpy(buf + i, "unknown");
111                                 i += strlen(buf + i);
112                                 break;
113                         case 'c': /* Callback-Number */
114                                 if ((tmp = pairfind(reply,
115                                      PW_CALLBACK_NUMBER)) != NULL)
116                                         strcpy(buf + i, tmp->strvalue);
117                                 else
118                                         strcpy(buf + i, "unknown");
119                                 i += strlen(buf + i);
120                                 break;
121                         case 'a': /* Protocol: SLIP/PPP */
122                                 if ((tmp = pairfind(reply,
123                                      PW_FRAMED_PROTOCOL)) != NULL)
124                 strcpy(buf + i, tmp->lvalue == PW_PPP ? "PPP" : "SLIP");
125                                 else
126                                         strcpy(buf + i, "unknown");
127                                 i += strlen(buf + i);
128                                 break;
129                         case 's': /* Speed */
130                                 if ((tmp = pairfind(request,
131                                      PW_CONNECT_INFO)) != NULL)
132                                         strcpy(buf + i, tmp->strvalue);
133                                 else
134                                         strcpy(buf + i, "unknown");
135                                 i += strlen(buf + i);
136                                 break;
137                         default:
138                                 buf[i++] = '%';
139                                 buf[i++] = *p;
140                                 break;
141                 }
142         }
143         if (i >= MAX_STRING_LEN)
144                 i = MAX_STRING_LEN - 1;
145         buf[i++] = 0;
146
147         return buf;
148 }
149
150 /*
151  *      Execute a program on successful authentication.
152  *      Return 0 if exec_wait == 0.
153  *      Return the exit code of the called program if exec_wait != 0.
154  *
155  */
156 int radius_exec_program(char *cmd, VALUE_PAIR *request, VALUE_PAIR **reply,
157                 int exec_wait, char **user_msg)
158 {
159         VALUE_PAIR      *vp;
160         static char     message[256];
161         char            answer[4096];
162         char            *argv[32];
163         char            *buf, *p;
164         int             pd[2];
165         pid_t           pid;
166         int             argc = -1;
167         int             comma = 0;
168         int             status;
169         int             n, left, done;
170         void            (*oldsig)(int) = NULL;
171         
172
173         /*
174          *      (hs)    - Open a pipe for child/parent communication.
175          *              - Reset the signal handler for SIGCHLD, so
176          *                we have a chance to notice the dead child here and
177          *                not in some signal handler.
178          *                This has to be done for the exec_wait case only, since
179          *                if we don't wait we aren't interested in any
180          *                gone children ...
181          */     
182         if (exec_wait) {
183                 if (pipe(pd) != 0) {
184                         log(L_ERR|L_CONS, "Couldn't open pipe: %m");
185                         pd[0] = pd[1] = 0;
186                 }
187                 if ((oldsig = signal(SIGCHLD, SIG_DFL)) == SIG_ERR) {
188                         log(L_ERR|L_CONS, "Can't reset SIGCHLD: %m");
189                         oldsig = NULL;
190                 }
191         }
192
193         if ((pid = fork()) == 0) {
194                 /*      
195                  *      Child
196                  */
197                 buf = radius_xlate(cmd, request, *reply);
198
199                 /*
200                  *      XXX FIXME: This is debugging info.
201                  */
202                 log(L_INFO, "Exec-Program: %s", buf);
203
204                 /*
205                  *      Build vector list and execute.
206                  */
207                 p = strtok(buf, " \t");
208                 if (p) do {
209                         argv[++argc] = p;
210                         p = strtok(NULL, " \t");
211                 } while(p != NULL);
212                 argv[++argc] = p;
213                 if (argc == 0) {
214                         log(L_ERR, "Exec-Program: empty command line.");
215                         exit(1);
216                 }
217
218                 if (exec_wait) {
219                         if (close(pd[0]) != 0)
220                                 log(L_ERR|L_CONS, "Can't close pipe: %m");
221                         if (dup2(pd[1], 1) != 1)
222                                 log(L_ERR|L_CONS, "Can't dup stdout: %m");
223                 }
224
225                 for(n = 32; n >= 3; n--)
226                         close(n);
227
228                 execvp(argv[0], argv);
229
230                 log(L_ERR, "Exec-Program: %s: %m", argv[0]);
231                 exit(1);
232         }
233
234         /*
235          *      Parent 
236          */
237         if (pid < 0) {
238                 log(L_ERR|L_CONS, "Couldn't fork: %m");
239                 return -1;
240         }
241         if (!exec_wait)
242                 return 0;
243
244         /*
245          *      (hs) Do we have a pipe?
246          *      --> Close the write side of the pipe 
247          *      --> Read from it.
248          */
249         done = 0;
250         if (pd[0] || pd[1]) {
251                 if (close(pd[1]) != 0)
252                         log(L_ERR|L_CONS, "Can't close pipe: %m");
253
254                 /*
255                  *      (hs) Read until we doesn't get any more
256                  *      or until the message is full.
257                  */
258                 done = 0;
259                 left = sizeof(answer) - 1;
260                 while ((n = read(pd[0], answer + done, left)) > 0) {
261                         done += n;
262                         left -= n;
263                         if (left <= 0) break;
264                 }
265                 answer[done] = 0;
266
267                 /*
268                  *      (hs) Make sure that the writer can't block
269                  *      while writing in a pipe that isn't read anymore.
270                  */
271                 close(pd[0]);
272         }
273
274         /*
275          *      Parse the output, if any.
276          */
277         if (done) {
278                 /*
279                  *      For backwards compatibility, first check
280                  *      for plain text (user_msg).
281                  */
282                 vp = NULL;
283                 n = userparse(answer, &vp);
284                 if (vp) pairfree(vp);
285                 vp = NULL;
286
287                 if (n != 0) {
288                         log(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
289                         if (user_msg) {
290                                 strncpy(message, answer, sizeof(message));
291                                 message[sizeof(message) - 1] = 0;
292                                 *user_msg = message;
293                         }
294                 } else {
295                         /*
296                          *      HACK: Replace '\n' with ',' so that
297                          *      userparse() can parse the buffer in
298                          *      one go (the proper way would be to
299                          *      fix userparse(), but oh well).
300                          */
301                         for (p = answer; *p; p++) {
302                                 if (*p == '\n') {
303                                         *p = comma ? ' ' : ',';
304                                         comma = 0;
305                                 }
306                                 if (*p == ',') comma++;
307                         }
308
309                         log(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
310                         if (userparse(answer, &vp) != 0)
311                                 log(L_ERR,
312                 "Exec-Program-Wait: %s: unparsable reply", cmd);
313                         else {
314                                 pairmove(reply, &vp);
315                                 pairfree(vp);
316                         }
317                 }
318         }
319
320         while(waitpid(pid, &status, 0) != pid)
321                 ;
322
323         /*
324          *      (hs) Now we let our cleanup_sig handler take care for
325          *      all signals that will arise.
326          */
327         if (oldsig && (signal(SIGCHLD, oldsig) == SIG_ERR))
328                 log(L_ERR|L_CONS,
329                         "Can't set SIGCHLD to the cleanup handler: %m");
330         sig_cleanup(SIGCHLD);
331
332         if (WIFEXITED(status)) {
333                 status = WEXITSTATUS(status);
334                 log(L_INFO, "Exec-Program: returned: %d", status);
335                 return status;
336         }
337         log(L_ERR|L_CONS, "Exec-Program: Abnormal child exit (killed or coredump)");
338
339         return 1;
340 }
341