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