If input pairs is NULL, return NOOP
[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         if (!input_pairs) {
297                 RDEBUG2("WARNING: Possible parse error in %s",
298                         inst->input);
299                 return RLM_MODULE_NOOP;
300         }
301
302         /*
303          *      It points to the attribute list, but the attribute
304          *      list is empty.
305          */
306         if (!*input_pairs) {
307                 RDEBUG2("WARNING! Input pairs are empty.  No attributes will be passed to the script");
308         }
309
310         /*
311          *      This function does it's own xlat of the input program
312          *      to execute.
313          *
314          *      FIXME: if inst->program starts with %{, then
315          *      do an xlat ourselves.  This will allow us to do
316          *      program = %{Exec-Program}, which this module
317          *      xlat's into it's string value, and then the
318          *      exec program function xlat's it's string value
319          *      into something else.
320          */
321         result = radius_exec_program(inst->program, request,
322                                      inst->wait, NULL, 0,
323                                      *input_pairs, &answer, inst->shell_escape);
324         if (result < 0) {
325                 radlog(L_ERR, "rlm_exec (%s): External script failed",
326                        inst->xlat_name);
327                 return RLM_MODULE_FAIL;
328         }
329
330         /*
331          *      Move the answer over to the output pairs.
332          *
333          *      If we're not waiting, then there are no output pairs.
334          */
335         if (output_pairs) pairmove(output_pairs, &answer);
336
337         pairfree(&answer);
338
339         if (result == 0) {
340                 return RLM_MODULE_OK;
341         }
342         if (result > RLM_MODULE_NUMCODES) {
343                 return RLM_MODULE_FAIL;
344         }
345         return result-1;
346 }
347
348
349 /*
350  *      First, look for Exec-Program && Exec-Program-Wait.
351  *
352  *      Then, call exec_dispatch.
353  */
354 static int exec_postauth(void *instance, REQUEST *request)
355 {
356         int result;
357         int exec_wait = 0;
358         VALUE_PAIR *vp, *tmp;
359         rlm_exec_t *inst = (rlm_exec_t *) instance;
360
361         vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM);
362         if (vp) {
363                 exec_wait = 0;
364
365         } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT)) != NULL) {
366                 exec_wait = 1;
367         }
368         if (!vp) goto dispatch;
369
370         tmp = NULL;
371         result = radius_exec_program(vp->vp_strvalue, request, exec_wait,
372                                      NULL, 0, request->packet->vps, &tmp,
373                                      inst->shell_escape);
374
375         /*
376          *      Always add the value-pairs to the reply.
377          */
378         pairmove(&request->reply->vps, &tmp);
379         pairfree(&tmp);
380
381         if (result < 0) {
382                 /*
383                  *      Error. radius_exec_program() returns -1 on
384                  *      fork/exec errors.
385                  */
386                 tmp = pairmake("Reply-Message", "Access denied (external check failed)", T_OP_SET);
387                 pairadd(&request->reply->vps, tmp);
388
389                 RDEBUG2("Login incorrect (external check failed)");
390
391                 request->reply->code = PW_AUTHENTICATION_REJECT;
392                 return RLM_MODULE_REJECT;
393         }
394         if (result > 0) {
395                 /*
396                  *      Reject. radius_exec_program() returns >0
397                  *      if the exec'ed program had a non-zero
398                  *      exit status.
399                  */
400                 request->reply->code = PW_AUTHENTICATION_REJECT;
401                 RDEBUG2("Login incorrect (external check said so)");
402                 return RLM_MODULE_REJECT;
403         }
404
405  dispatch:
406         if (!inst->program) return RLM_MODULE_NOOP;
407
408         return exec_dispatch(instance, request);
409 }
410
411 /*
412  *      The module name should be the only globally exported symbol.
413  *      That is, everything else should be 'static'.
414  *
415  *      If the module needs to temporarily modify it's instantiation
416  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
417  *      The server will then take care of ensuring that the module
418  *      is single-threaded.
419  */
420 module_t rlm_exec = {
421         RLM_MODULE_INIT,
422         "exec",                         /* Name */
423         RLM_TYPE_CHECK_CONFIG_SAFE,     /* type */
424         exec_instantiate,               /* instantiation */
425         exec_detach,                    /* detach */
426         {
427                 exec_dispatch,          /* authentication */
428                 exec_dispatch,          /* authorization */
429                 exec_dispatch,          /* pre-accounting */
430                 exec_dispatch,          /* accounting */
431                 NULL,                   /* check simul */
432                 exec_dispatch,          /* pre-proxy */
433                 exec_dispatch,          /* post-proxy */
434                 exec_postauth           /* post-auth */
435         },
436 };