removed old 'radius_xlate' function, and replaced all references
[freeradius.git] / src / main / exec.c
1 /*
2  * exec.c       Execute external programs.
3  *
4  * Version:     $Id$
5  *
6  */
7 static const char rcsid[] = "$Id$";
8
9 #include        "autoconf.h"
10 #include        "libradius.h"
11
12 #include        <sys/file.h>
13
14 #include        <stdlib.h>
15 #include        <string.h>
16 #include        <fcntl.h>
17 #include        <ctype.h>
18 #include        <signal.h>
19
20 #if HAVE_SYS_WAIT_H
21 # include <sys/wait.h>
22 #endif
23 #ifndef WEXITSTATUS
24 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
25 #endif
26 #ifndef WIFEXITED
27 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
28 #endif
29
30 #include        "radiusd.h"
31
32 /*
33  *      Execute a program on successful authentication.
34  *      Return 0 if exec_wait == 0.
35  *      Return the exit code of the called program if exec_wait != 0.
36  *
37  */
38 int radius_exec_program(const char *cmd, REQUEST *request,
39                         int exec_wait, const char **user_msg)
40 {
41         VALUE_PAIR      *vp;
42         static char     message[256];
43         char            answer[4096];
44         char            *argv[32];
45         char            *buf, *p;
46         int             pd[2];
47         pid_t           pid;
48         int             argc = -1;
49         int             comma = 0;
50         int             status;
51         int             n, left, done;
52         void            (*oldsig)(int) = NULL;
53
54         /*
55          *      (hs)    - Open a pipe for child/parent communication.
56          *              - Reset the signal handler for SIGCHLD, so
57          *                we have a chance to notice the dead child here and
58          *                not in some signal handler.
59          *                This has to be done for the exec_wait case only, since
60          *                if we don't wait we aren't interested in any
61          *                gone children ...
62          */     
63         if (exec_wait) {
64                 if (pipe(pd) != 0) {
65                         radlog(L_ERR|L_CONS, "Couldn't open pipe: %m");
66                         pd[0] = pd[1] = 0;
67                 }
68                 if ((oldsig = signal(SIGCHLD, SIG_DFL)) == SIG_ERR) {
69                         radlog(L_ERR|L_CONS, "Can't reset SIGCHLD: %m");
70                         oldsig = NULL;
71                 }
72         }
73
74         if ((pid = fork()) == 0) {
75 #define MAX_ENVP 1024
76                 char            *envp[MAX_ENVP];
77                 int             envlen;
78                 char            buffer[1024];
79
80                 /*      
81                  *      Child
82                  */
83                 buf = radius_xlat2(answer, sizeof(answer), cmd, request);
84
85                 /*
86                  *      XXX FIXME: This is debugging info.
87                  */
88                 radlog(L_INFO, "Exec-Program: %s", buf);
89
90                 /*
91                  *      Build vector list and execute.
92                  */
93                 p = strtok(buf, " \t");
94                 if (p) do {
95                         argv[++argc] = p;
96                         p = strtok(NULL, " \t");
97                 } while(p != NULL);
98                 argv[++argc] = p;
99                 if (argc == 0) {
100                         radlog(L_ERR, "Exec-Program: empty command line.");
101                         exit(1);
102                 }
103
104                 if (exec_wait) {
105                         if (close(pd[0]) != 0)
106                                 radlog(L_ERR|L_CONS, "Can't close pipe: %m");
107                         if (dup2(pd[1], 1) != 1)
108                                 radlog(L_ERR|L_CONS, "Can't dup stdout: %m");
109                 }
110
111                 /*
112                  *      Set up the environment variables.
113                  *      We're in the child, and it will exit in 4 lines
114                  *      anyhow, so memory allocation isn't an issue.
115                  */
116                 envlen = 0;
117
118                 for (vp = request->packet->vps; vp->next; vp = vp->next) {
119                         /*
120                          *      Hmm... maybe we shouldn't pass the
121                          *      user's password in an environment
122                          *      variable...
123                          */
124                         snprintf(buffer, sizeof(buffer), "%s=", vp->name);
125                         for (p = buffer; *p != '='; p++) {
126                           if (*p == '-') {
127                             *p = '_';
128                           } else if (isalpha(*p)) {
129                             *p = toupper(*p);
130                           }
131                         }
132
133                         n = strlen(buffer);
134                         vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
135
136                         envp[envlen++] = strdup(buffer);
137                 }
138
139                 envp[envlen] = NULL;
140                 
141
142
143
144                 for(n = 32; n >= 3; n--)
145                         close(n);
146
147                 execve(argv[0], argv, envp);
148
149                 radlog(L_ERR, "Exec-Program: %s: %m", argv[0]);
150                 exit(1);
151         }
152
153         /*
154          *      Parent 
155          */
156         if (pid < 0) {
157                 radlog(L_ERR|L_CONS, "Couldn't fork: %m");
158                 return -1;
159         }
160         if (!exec_wait)
161                 return 0;
162
163         /*
164          *      (hs) Do we have a pipe?
165          *      --> Close the write side of the pipe 
166          *      --> Read from it.
167          */
168         done = 0;
169         if (pd[0] || pd[1]) {
170                 if (close(pd[1]) != 0)
171                         radlog(L_ERR|L_CONS, "Can't close pipe: %m");
172
173                 /*
174                  *      (hs) Read until we doesn't get any more
175                  *      or until the message is full.
176                  */
177                 done = 0;
178                 left = sizeof(answer) - 1;
179                 while ((n = read(pd[0], answer + done, left)) > 0) {
180                         done += n;
181                         left -= n;
182                         if (left <= 0) break;
183                 }
184                 answer[done] = 0;
185
186                 /*
187                  *      (hs) Make sure that the writer can't block
188                  *      while writing in a pipe that isn't read anymore.
189                  */
190                 close(pd[0]);
191         }
192
193         /*
194          *      Parse the output, if any.
195          */
196         if (done) {
197                 /*
198                  *      For backwards compatibility, first check
199                  *      for plain text (user_msg).
200                  */
201                 vp = NULL;
202                 n = userparse(answer, &vp);
203                 if (vp) pairfree(vp);
204                 vp = NULL;
205
206                 if (n != 0) {
207                         radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
208                         if (user_msg) {
209                                 strncpy(message, answer, sizeof(message));
210                                 message[sizeof(message) - 1] = 0;
211                                 *user_msg = message;
212                         }
213                 } else {
214                         /*
215                          *      HACK: Replace '\n' with ',' so that
216                          *      userparse() can parse the buffer in
217                          *      one go (the proper way would be to
218                          *      fix userparse(), but oh well).
219                          */
220                         for (p = answer; *p; p++) {
221                                 if (*p == '\n') {
222                                         *p = comma ? ' ' : ',';
223                                         p++;
224                                         comma = 0;
225                                 }
226                                 if (*p == ',') comma++;
227                         }
228
229                         /*
230                          *  Replace any trailing comma by a NUL.
231                          */
232                         if (answer[strlen(answer) - 1] == ',')
233                                 answer[strlen(answer) - 1] = '\0';
234
235                         radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
236                         if (userparse(answer, &vp) != 0)
237                                 radlog(L_ERR,
238                 "Exec-Program-Wait: %s: unparsable reply", cmd);
239                         else {
240                                 pairmove(&request->reply->vps, &vp);
241                                 pairfree(vp);
242                         }
243                 }
244         }
245
246         while(waitpid(pid, &status, 0) != pid)
247                 ;
248
249         /*
250          *      (hs) Now we let our cleanup_sig handler take care for
251          *      all signals that will arise.
252          */
253         if (oldsig && (signal(SIGCHLD, oldsig) == SIG_ERR))
254                 radlog(L_ERR|L_CONS,
255                         "Can't set SIGCHLD to the cleanup handler: %m");
256         sig_cleanup(SIGCHLD);
257
258         if (WIFEXITED(status)) {
259                 status = WEXITSTATUS(status);
260                 radlog(L_INFO, "Exec-Program: returned: %d", status);
261                 return status;
262         }
263         radlog(L_ERR|L_CONS, "Exec-Program: Abnormal child exit (killed or coredump)");
264
265         return 1;
266 }
267