Use sane macro names for codes. PW_CODE_AUTHENTICATION_ACK, PW_CODE_AUTHENTICATION_RE...
[freeradius.git] / src / modules / rlm_exec / rlm_exec.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15
16 /**
17  * $Id$
18  * @file rlm_exec.c
19  * @brief Execute commands and parse the results.
20  *
21  * @copyright 2002,2006  The FreeRADIUS server project
22  * @copyright 2002  Alan DeKok <aland@ox.org>
23  */
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/rad_assert.h>
29
30 /*
31  *      Define a structure for our module configuration.
32  */
33 typedef struct rlm_exec_t {
34         char const      *xlat_name;
35         int             bare;
36         bool            wait;
37         char const      *program;
38         char const      *input;
39         char const      *output;
40         pair_lists_t    input_list;
41         pair_lists_t    output_list;
42         char const      *packet_type;
43         unsigned int    packet_code;
44         bool            shell_escape;
45         uint32_t        timeout;
46 } rlm_exec_t;
47
48 /*
49  *      A mapping of configuration file names to internal variables.
50  *
51  *      Note that the string is dynamically allocated, so it MUST
52  *      be freed.  When the configuration file parse re-reads the string,
53  *      it free's the old one, and strdup's the new one, placing the pointer
54  *      to the strdup'd string into 'config.string'.  This gets around
55  *      buffer over-flows.
56  */
57 static const CONF_PARSER module_config[] = {
58         { "wait", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_exec_t, wait), "yes" },
59         { "program", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_exec_t, program), NULL },
60         { "input_pairs", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_exec_t, input), NULL },
61         { "output_pairs", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_exec_t, output), NULL },
62         { "packet_type", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_exec_t, packet_type), NULL },
63         { "shell_escape", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_exec_t, shell_escape), "yes" },
64         { "timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_exec_t, timeout), NULL },
65
66         { NULL, -1, 0, NULL, NULL }             /* end the list */
67 };
68
69 static char const special[] = "\\'\"`<>|; \t\r\n()[]?#$^&*=";
70
71 /*
72  *      Escape special characters
73  */
74 static size_t rlm_exec_shell_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *in,
75                                     UNUSED void *inst)
76 {
77         char *q, *end;
78         char const *p;
79
80         q = out;
81         end = out + outlen;
82         p = in;
83
84         while (*p) {
85                 if ((q + 3) >= end) break;
86
87                 if (strchr(special, *p) != NULL) {
88                         *(q++) = '\\';
89                 }
90                 *(q++) = *(p++);
91         }
92
93         *q = '\0';
94         return q - out;
95 }
96
97 /** Process the exit code returned by one of the exec functions
98  *
99  * @param request Current request.
100  * @param answer Output string from exec call.
101  * @param len length of data in answer.
102  * @param status code returned by exec call.
103  * @return One of the RLM_MODULE_* values.
104  */
105 static rlm_rcode_t rlm_exec_status2rcode(REQUEST *request, char *answer, size_t len, int status)
106 {
107         if (status < 0) {
108                 return RLM_MODULE_FAIL;
109         }
110
111         /*
112          *      Exec'd programs are meant to return exit statuses that correspond
113          *      to the standard RLM_MODULE_* + 1.
114          *
115          *      This frees up 0, for success where it'd normally be reject.
116          */
117         if (status == 0) {
118                 RDEBUG("Program executed successfully");
119
120                 return RLM_MODULE_OK;
121         }
122
123         if (status > RLM_MODULE_NUMCODES) {
124                 REDEBUG("Program returned invalid code (greater than max rcode) (%i > %i): %s",
125                         status, RLM_MODULE_NUMCODES, answer);
126                 goto fail;
127
128                 return RLM_MODULE_FAIL;
129         }
130
131         status--;       /* Lets hope no one ever re-enumerates RLM_MODULE_* */
132
133         if (status == RLM_MODULE_FAIL) {
134                 fail:
135
136                 if (len > 0) {
137                         char *p = &answer[len - 1];
138
139                         /*
140                          *      Trim off trailing returns
141                          */
142                         while((p > answer) && ((*p == '\r') || (*p == '\n'))) {
143                                 *p-- = '\0';
144                         }
145
146                         module_failure_msg(request, "%s", answer);
147                 }
148
149                 return RLM_MODULE_FAIL;
150         }
151
152         return status;
153 }
154
155 /*
156  *      Do xlat of strings.
157  */
158 static ssize_t exec_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
159 {
160         int             result;
161         rlm_exec_t      *inst = instance;
162         VALUE_PAIR      **input_pairs = NULL;
163         char *p;
164
165         if (!inst->wait) {
166                 REDEBUG("'wait' must be enabled to use exec xlat");
167                 *out = '\0';
168                 return -1;
169         }
170
171         if (inst->input_list) {
172                 input_pairs = radius_list(request, inst->input_list);
173                 if (!input_pairs) {
174                         REDEBUG("Failed to find input pairs for xlat");
175                         *out = '\0';
176                         return -1;
177                 }
178         }
179
180         /*
181          *      This function does it's own xlat of the input program
182          *      to execute.
183          */
184         result = radius_exec_program(request, fmt, inst->wait, inst->shell_escape,
185                                      out, outlen, inst->timeout,
186                                      input_pairs ? *input_pairs : NULL, NULL);
187         if (result != 0) {
188                 out[0] = '\0';
189                 return -1;
190         }
191
192         for (p = out; *p != '\0'; p++) {
193                 if (*p < ' ') *p = ' ';
194         }
195
196         return strlen(out);
197 }
198
199 /*
200  *      Do any per-module initialization that is separate to each
201  *      configured instance of the module.  e.g. set up connections
202  *      to external databases, read configuration files, set up
203  *      dictionary entries, etc.
204  *
205  *      If configuration information is given in the config section
206  *      that must be referenced in later calls, store a handle to it
207  *      in *instance otherwise put a null pointer there.
208  */
209 static int mod_instantiate(CONF_SECTION *conf, void *instance)
210 {
211         char const *p;
212         rlm_exec_t      *inst = instance;
213
214         inst->xlat_name = cf_section_name2(conf);
215         if (!inst->xlat_name) {
216                 inst->xlat_name = cf_section_name1(conf);
217                 inst->bare = 1;
218         }
219
220         xlat_register(inst->xlat_name, exec_xlat, rlm_exec_shell_escape, inst);
221
222         if (inst->input) {
223                 p = inst->input;
224                 inst->input_list = radius_list_name(&p, PAIR_LIST_UNKNOWN);
225                 if ((inst->input_list == PAIR_LIST_UNKNOWN) || (*p != '\0')) {
226                         cf_log_err_cs(conf, "Invalid input list '%s'", inst->input);
227                         return -1;
228                 }
229         }
230
231         if (inst->output) {
232                 p = inst->output;
233                 inst->output_list = radius_list_name(&p, PAIR_LIST_UNKNOWN);
234                 if ((inst->output_list == PAIR_LIST_UNKNOWN) || (*p != '\0')) {
235                         cf_log_err_cs(conf, "Invalid output list '%s'", inst->output);
236                         return -1;
237                 }
238         }
239
240         /*
241          *      Sanity check the config.  If we're told to NOT wait,
242          *      then the output pairs must not be defined.
243          */
244         if (!inst->wait &&
245             (inst->output != NULL)) {
246                 cf_log_err_cs(conf, "Cannot read output pairs if wait = no");
247                 return -1;
248         }
249
250         /*
251          *      Get the packet type on which to execute
252          */
253         if (!inst->packet_type) {
254                 inst->packet_code = 0;
255         } else {
256                 DICT_VALUE      *dval;
257
258                 dval = dict_valbyname(PW_PACKET_TYPE, 0, inst->packet_type);
259                 if (!dval) {
260                         cf_log_err_cs(conf, "Unknown packet type %s: See list of VALUEs for Packet-Type in "
261                                       "share/dictionary", inst->packet_type);
262                         return -1;
263                 }
264                 inst->packet_code = dval->value;
265         }
266
267         /*
268          *      Get the time to wait before killing the child
269          */
270         if (!inst->timeout) {
271                 inst->timeout = EXEC_TIMEOUT;
272         }
273         if (inst->timeout < 1) {
274                 cf_log_err_cs(conf, "Timeout '%d' is too small (minimum: 1)", inst->timeout);
275                 return -1;
276         }
277         /*
278          *      Blocking a request longer than 30 seconds isn't going to help anyone.
279          */
280         if (inst->timeout > 30) {
281                 cf_log_err_cs(conf, "Timeout '%d' is too large (maximum: 30)", inst->timeout);
282                 return -1;
283         }
284
285         return 0;
286 }
287
288
289 /*
290  *  Dispatch an exec method
291  */
292 static rlm_rcode_t CC_HINT(nonnull) mod_exec_dispatch(void *instance, REQUEST *request)
293 {
294         rlm_exec_t      *inst = (rlm_exec_t *)instance;
295         rlm_rcode_t     rcode;
296         int             status;
297
298         VALUE_PAIR      **input_pairs = NULL, **output_pairs = NULL;
299         VALUE_PAIR      *answer = NULL;
300         char            out[1024];
301
302         /*
303          *      We need a program to execute.
304          */
305         if (!inst->program) {
306                 ERROR("rlm_exec (%s): We require a program to execute", inst->xlat_name);
307                 return RLM_MODULE_FAIL;
308         }
309
310         /*
311          *      See if we're supposed to execute it now.
312          */
313         if (!((inst->packet_code == 0) || (request->packet->code == inst->packet_code) ||
314               (request->reply->code == inst->packet_code)
315 #ifdef WITH_PROXY
316               || (request->proxy && (request->proxy->code == inst->packet_code)) ||
317               (request->proxy_reply && (request->proxy_reply->code == inst->packet_code))
318 #endif
319                     )) {
320                 RDEBUG2("Packet type is not %s. Not executing.", inst->packet_type);
321
322                 return RLM_MODULE_NOOP;
323         }
324
325         /*
326          *      Decide what input/output the program takes.
327          */
328         if (inst->input) {
329                 input_pairs = radius_list(request, inst->input_list);
330                 if (!input_pairs) {
331                         return RLM_MODULE_INVALID;
332                 }
333         }
334
335         if (inst->output) {
336                 output_pairs = radius_list(request, inst->output_list);
337                 if (!output_pairs) {
338                         return RLM_MODULE_INVALID;
339                 }
340         }
341
342         /*
343          *      This function does it's own xlat of the input program
344          *      to execute.
345          */
346         status = radius_exec_program(request, inst->program, inst->wait, inst->shell_escape,
347                                      out, sizeof(out), inst->timeout,
348                                      inst->input ? *input_pairs : NULL,
349                                      inst->output ? &answer : NULL);
350         rcode = rlm_exec_status2rcode(request, out, strlen(out), status);
351
352         /*
353          *      Move the answer over to the output pairs.
354          *
355          *      If we're not waiting, then there are no output pairs.
356          */
357         if (inst->output) {
358                 pairmove(request, output_pairs, &answer);
359         }
360         pairfree(&answer);
361
362         return rcode;
363 }
364
365
366 /*
367  *      First, look for Exec-Program && Exec-Program-Wait.
368  *
369  *      Then, call exec_dispatch.
370  */
371 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
372 {
373         rlm_exec_t      *inst = (rlm_exec_t *) instance;
374         rlm_rcode_t     rcode;
375         int             status;
376
377         char            out[1024];
378         bool            we_wait = false;
379         VALUE_PAIR      *vp, *tmp;
380
381         vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM, 0, TAG_ANY);
382         if (vp) {
383                 we_wait = false;
384         } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT, 0, TAG_ANY)) != NULL) {
385                 we_wait = true;
386         }
387         if (!vp) {
388                 if (!inst->program) {
389                         return RLM_MODULE_NOOP;
390                 }
391
392                 rcode = mod_exec_dispatch(instance, request);
393                 goto finish;
394         }
395
396         tmp = NULL;
397         status = radius_exec_program(request, vp->vp_strvalue, we_wait, inst->shell_escape,
398                                      out, sizeof(out), inst->timeout,
399                                      request->packet->vps, &tmp);
400         rcode = rlm_exec_status2rcode(request, out, strlen(out), status);
401
402         /*
403          *      Always add the value-pairs to the reply.
404          */
405         pairmove(request->reply, &request->reply->vps, &tmp);
406         pairfree(&tmp);
407
408         finish:
409         switch (rcode) {
410                 case RLM_MODULE_FAIL:
411                 case RLM_MODULE_INVALID:
412                 case RLM_MODULE_REJECT:
413                         request->reply->code = PW_CODE_ACCESS_REJECT;
414                         break;
415                 default:
416                         break;
417         }
418
419         return rcode;
420 }
421
422 /*
423  *      First, look for Exec-Program && Exec-Program-Wait.
424  *
425  *      Then, call exec_dispatch.
426  */
427 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
428 {
429         rlm_exec_t      *inst = (rlm_exec_t *) instance;
430         int             status;
431
432         char            out[1024];
433         bool            we_wait = false;
434         VALUE_PAIR      *vp;
435
436         /*
437          *      The "bare" exec module takes care of handling
438          *      Exec-Program and Exec-Program-Wait.
439          */
440         if (!inst->bare) {
441                 return mod_exec_dispatch(instance, request);
442         }
443
444         vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM, 0, TAG_ANY);
445         if (vp) {
446                 we_wait = true;
447         } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT, 0, TAG_ANY)) != NULL) {
448                 we_wait = false;
449         }
450         if (!vp) {
451                 return RLM_MODULE_NOOP;
452         }
453
454         status = radius_exec_program(request, vp->vp_strvalue, we_wait, inst->shell_escape,
455                                      out, sizeof(out), inst->timeout,
456                                      request->packet->vps, NULL);
457         return rlm_exec_status2rcode(request, out, strlen(out), status);
458 }
459
460 /*
461  *      The module name should be the only globally exported symbol.
462  *      That is, everything else should be 'static'.
463  *
464  *      If the module needs to temporarily modify it's instantiation
465  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
466  *      The server will then take care of ensuring that the module
467  *      is single-threaded.
468  */
469 module_t rlm_exec = {
470         RLM_MODULE_INIT,
471         "exec",                         /* Name */
472         RLM_TYPE_THREAD_SAFE,           /* type */
473         sizeof(rlm_exec_t),
474         module_config,
475         mod_instantiate,                /* instantiation */
476         NULL,                           /* detach */
477         {
478                 mod_exec_dispatch,      /* authentication */
479                 mod_exec_dispatch,      /* authorization */
480                 mod_exec_dispatch,      /* pre-accounting */
481                 mod_accounting,         /* accounting */
482                 NULL,                   /* check simul */
483                 mod_exec_dispatch,      /* pre-proxy */
484                 mod_exec_dispatch,      /* post-proxy */
485                 mod_post_auth           /* post-auth */
486 #ifdef WITH_COA
487                 , mod_exec_dispatch,
488                 mod_exec_dispatch
489 #endif
490         },
491 };