DEBUG -> RDEBUG
[freeradius.git] / src / modules / rlm_exec / rlm_exec.c
1 /*
2  * rlm_exec.c
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2002,2006  The FreeRADIUS server project
21  * Copyright 2002  Alan DeKok <aland@ox.org>
22  */
23
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29
30 /*
31  *      Define a structure for our module configuration.
32  */
33 typedef struct rlm_exec_t {
34         char    *xlat_name;
35         int     wait;
36         char    *program;
37         char    *input;
38         char    *output;
39         char    *packet_type;
40         unsigned int    packet_code;
41         int     shell_escape;
42 } rlm_exec_t;
43
44 /*
45  *      A mapping of configuration file names to internal variables.
46  *
47  *      Note that the string is dynamically allocated, so it MUST
48  *      be freed.  When the configuration file parse re-reads the string,
49  *      it free's the old one, and strdup's the new one, placing the pointer
50  *      to the strdup'd string into 'config.string'.  This gets around
51  *      buffer over-flows.
52  */
53 static const CONF_PARSER module_config[] = {
54         { "wait", PW_TYPE_BOOLEAN,  offsetof(rlm_exec_t,wait), NULL, "yes" },
55         { "program",  PW_TYPE_STRING_PTR,
56           offsetof(rlm_exec_t,program), NULL, NULL },
57         { "input_pairs", PW_TYPE_STRING_PTR,
58           offsetof(rlm_exec_t,input), NULL, "request" },
59         { "output_pairs",  PW_TYPE_STRING_PTR,
60           offsetof(rlm_exec_t,output), NULL, NULL },
61         { "packet_type", PW_TYPE_STRING_PTR,
62           offsetof(rlm_exec_t,packet_type), NULL, NULL },
63         { "shell_escape", PW_TYPE_BOOLEAN,  offsetof(rlm_exec_t,shell_escape), NULL, "yes" },
64         { NULL, -1, 0, NULL, NULL }             /* end the list */
65 };
66
67
68 /*
69  *      Decode the configuration file string to a pointer to
70  *      a value-pair list in the REQUEST data structure.
71  */
72 static VALUE_PAIR **decode_string(REQUEST *request, const char *string)
73 {
74         if (!string) return NULL;
75
76         /*
77          *      Yuck.  We need a 'switch' over character strings
78          *      in C.
79          */
80         if (strcmp(string, "request") == 0) {
81                 return &request->packet->vps;
82         }
83
84         if (strcmp(string, "reply") == 0) {
85                 if (!request->reply) return NULL;
86
87                 return &request->reply->vps;
88         }
89
90         if (strcmp(string, "proxy-request") == 0) {
91                 if (!request->proxy) return NULL;
92
93                 return &request->proxy->vps;
94         }
95
96         if (strcmp(string, "proxy-reply") == 0) {
97                 if (!request->proxy_reply) return NULL;
98
99                 return &request->proxy_reply->vps;
100         }
101
102         if (strcmp(string, "config") == 0) {
103                 return &request->config_items;
104         }
105
106         if (strcmp(string, "none") == 0) {
107                 return NULL;
108         }
109
110         return NULL;
111 }
112
113
114 /*
115  *      Do xlat of strings.
116  */
117 static size_t exec_xlat(void *instance, REQUEST *request,
118                      char *fmt, char *out, size_t outlen,
119                      UNUSED RADIUS_ESCAPE_STRING func)
120 {
121         int             result;
122         rlm_exec_t      *inst = instance;
123         VALUE_PAIR      **input_pairs;
124         char *p;
125
126         input_pairs = decode_string(request, inst->input);
127         if (!input_pairs) {
128                 radlog(L_ERR, "rlm_exec (%s): Failed to find input pairs for xlat",
129                        inst->xlat_name);
130                 out[0] = '\0';
131                 return 0;
132         }
133
134         /*
135          *      FIXME: Do xlat of program name?
136          */
137         RDEBUG2("Executing %s", fmt);
138         result = radius_exec_program(fmt, request, inst->wait,
139                                      out, outlen, *input_pairs, NULL, inst->shell_escape);
140         RDEBUG2("result %d", result);
141         if (result != 0) {
142                 out[0] = '\0';
143                 return 0;
144         }
145
146         for (p = out; *p != '\0'; p++) {
147                 if (*p < ' ') *p = ' ';
148         }
149
150         return strlen(out);
151 }
152
153
154 /*
155  *      Detach an instance and free it's data.
156  */
157 static int exec_detach(void *instance)
158 {
159         rlm_exec_t      *inst = instance;
160
161         if (inst->xlat_name) {
162                 xlat_unregister(inst->xlat_name, exec_xlat);
163                 free(inst->xlat_name);
164         }
165
166         free(inst);
167         return 0;
168 }
169
170
171 /*
172  *      Do any per-module initialization that is separate to each
173  *      configured instance of the module.  e.g. set up connections
174  *      to external databases, read configuration files, set up
175  *      dictionary entries, etc.
176  *
177  *      If configuration information is given in the config section
178  *      that must be referenced in later calls, store a handle to it
179  *      in *instance otherwise put a null pointer there.
180  */
181 static int exec_instantiate(CONF_SECTION *conf, void **instance)
182 {
183         rlm_exec_t      *inst;
184         const char      *xlat_name;
185
186         /*
187          *      Set up a storage area for instance data
188          */
189
190         inst = rad_malloc(sizeof(rlm_exec_t));
191         if (!inst)
192                 return -1;
193         memset(inst, 0, sizeof(rlm_exec_t));
194
195         /*
196          *      If the configuration parameters can't be parsed, then
197          *      fail.
198          */
199         if (cf_section_parse(conf, inst, module_config) < 0) {
200                 radlog(L_ERR, "rlm_exec: Failed parsing the configuration");
201                 exec_detach(inst);
202                 return -1;
203         }
204
205         /*
206          *      No input pairs defined.  Why are we executing a program?
207          */
208         if (!inst->input) {
209                 radlog(L_ERR, "rlm_exec: Must define input pairs for external program.");
210                 exec_detach(inst);
211                 return -1;
212         }
213
214         /*
215          *      Sanity check the config.  If we're told to NOT wait,
216          *      then the output pairs must not be defined.
217          */
218         if (!inst->wait &&
219             (inst->output != NULL)) {
220                 radlog(L_ERR, "rlm_exec: Cannot read output pairs if wait=no");
221                 exec_detach(inst);
222                 return -1;
223         }
224
225         /*
226          *      Get the packet type on which to execute
227          */
228         if (!inst->packet_type) {
229                 inst->packet_code = 0;
230         } else {
231                 DICT_VALUE      *dval;
232
233                 dval = dict_valbyname(PW_PACKET_TYPE, inst->packet_type);
234                 if (!dval) {
235                         radlog(L_ERR, "rlm_exec: Unknown packet type %s: See list of VALUEs for Packet-Type in share/dictionary", inst->packet_type);
236                         exec_detach(inst);
237                         return -1;
238                 }
239                 inst->packet_code = dval->value;
240         }
241
242         xlat_name = cf_section_name2(conf);
243         if (xlat_name == NULL)
244                 xlat_name = cf_section_name1(conf);
245         if (xlat_name){
246                 inst->xlat_name = strdup(xlat_name);
247                 xlat_register(xlat_name, exec_xlat, inst);
248         }
249
250         *instance = inst;
251
252         return 0;
253 }
254
255
256 /*
257  *  Dispatch an exec method
258  */
259 static int exec_dispatch(void *instance, REQUEST *request)
260 {
261         int result;
262         VALUE_PAIR **input_pairs, **output_pairs;
263         VALUE_PAIR *answer;
264         rlm_exec_t *inst = (rlm_exec_t *) instance;
265
266         /*
267          *      We need a program to execute.
268          */
269         if (!inst->program) {
270                 radlog(L_ERR, "rlm_exec (%s): We require a program to execute",
271                        inst->xlat_name);
272                 return RLM_MODULE_FAIL;
273         }
274
275         /*
276          *      See if we're supposed to execute it now.
277          */
278         if (!((inst->packet_code == 0) ||
279               (request->packet->code == inst->packet_code) ||
280               (request->reply->code == inst->packet_code) ||
281               (request->proxy &&
282                (request->proxy->code == inst->packet_code)) ||
283               (request->proxy_reply &&
284                (request->proxy_reply->code == inst->packet_code)))) {
285                 RDEBUG2("Packet type is not %s.  Not executing.",
286                        inst->packet_type);
287                 return RLM_MODULE_NOOP;
288         }
289
290         /*
291          *      Decide what input/output the program takes.
292          */
293         input_pairs = decode_string(request, inst->input);
294         output_pairs = decode_string(request, inst->output);
295
296         /*
297          *      It points to the attribute list, but the attribute
298          *      list is empty.
299          */
300         if (input_pairs && !*input_pairs) {
301                 RDEBUG2("WARNING! Input pairs are empty.  No attributes will be passed to the script");
302         }
303
304         /*
305          *      This function does it's own xlat of the input program
306          *      to execute.
307          *
308          *      FIXME: if inst->program starts with %{, then
309          *      do an xlat ourselves.  This will allow us to do
310          *      program = %{Exec-Program}, which this module
311          *      xlat's into it's string value, and then the
312          *      exec program function xlat's it's string value
313          *      into something else.
314          */
315         result = radius_exec_program(inst->program, request,
316                                      inst->wait, NULL, 0,
317                                      *input_pairs, &answer, inst->shell_escape);
318         if (result < 0) {
319                 radlog(L_ERR, "rlm_exec (%s): External script failed",
320                        inst->xlat_name);
321                 return RLM_MODULE_FAIL;
322         }
323
324         /*
325          *      Move the answer over to the output pairs.
326          *
327          *      If we're not waiting, then there are no output pairs.
328          */
329         if (output_pairs) pairmove(output_pairs, &answer);
330
331         pairfree(&answer);
332
333         if (result == 0) {
334                 return RLM_MODULE_OK;
335         }
336         if (result > RLM_MODULE_NUMCODES) {
337                 return RLM_MODULE_FAIL;
338         }
339         return result-1;
340 }
341
342
343 /*
344  *      First, look for Exec-Program && Exec-Program-Wait.
345  *
346  *      Then, call exec_dispatch.
347  */
348 static int exec_postauth(void *instance, REQUEST *request)
349 {
350         int result;
351         int exec_wait = 0;
352         VALUE_PAIR *vp, *tmp;
353         rlm_exec_t *inst = (rlm_exec_t *) instance;
354
355         vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM);
356         if (vp) {
357                 exec_wait = 0;
358
359         } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT)) != NULL) {
360                 exec_wait = 1;
361         }
362         if (!vp) goto dispatch;
363
364         tmp = NULL;
365         result = radius_exec_program(vp->vp_strvalue, request, exec_wait,
366                                      NULL, 0, request->packet->vps, &tmp,
367                                      inst->shell_escape);
368
369         /*
370          *      Always add the value-pairs to the reply.
371          */
372         pairmove(&request->reply->vps, &tmp);
373         pairfree(&tmp);
374
375         if (result < 0) {
376                 /*
377                  *      Error. radius_exec_program() returns -1 on
378                  *      fork/exec errors.
379                  */
380                 tmp = pairmake("Reply-Message", "Access denied (external check failed)", T_OP_SET);
381                 pairadd(&request->reply->vps, tmp);
382
383                 RDEBUG2("Login incorrect (external check failed)");
384
385                 request->reply->code = PW_AUTHENTICATION_REJECT;
386                 return RLM_MODULE_REJECT;
387         }
388         if (result > 0) {
389                 /*
390                  *      Reject. radius_exec_program() returns >0
391                  *      if the exec'ed program had a non-zero
392                  *      exit status.
393                  */
394                 request->reply->code = PW_AUTHENTICATION_REJECT;
395                 RDEBUG2("Login incorrect (external check said so)");
396                 return RLM_MODULE_REJECT;
397         }
398
399  dispatch:
400         if (!inst->program) return RLM_MODULE_NOOP;
401
402         return exec_dispatch(instance, request);
403 }
404
405 /*
406  *      The module name should be the only globally exported symbol.
407  *      That is, everything else should be 'static'.
408  *
409  *      If the module needs to temporarily modify it's instantiation
410  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
411  *      The server will then take care of ensuring that the module
412  *      is single-threaded.
413  */
414 module_t rlm_exec = {
415         RLM_MODULE_INIT,
416         "exec",                         /* Name */
417         RLM_TYPE_CHECK_CONFIG_SAFE,     /* type */
418         exec_instantiate,               /* instantiation */
419         exec_detach,                    /* detach */
420         {
421                 exec_dispatch,          /* authentication */
422                 exec_dispatch,          /* authorization */
423                 exec_dispatch,          /* pre-accounting */
424                 exec_dispatch,          /* accounting */
425                 NULL,                   /* check simul */
426                 exec_dispatch,          /* pre-proxy */
427                 exec_dispatch,          /* post-proxy */
428                 exec_postauth           /* post-auth */
429         },
430 };