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