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