Delete trailing whitespace.
[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 int 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
125         input_pairs = decode_string(request, inst->input);
126         if (!input_pairs) {
127                 radlog(L_ERR, "rlm_exec (%s): Failed to find input pairs for xlat",
128                        inst->xlat_name);
129                 out[0] = '\0';
130                 return 0;
131         }
132
133         /*
134          *      FIXME: Do xlat of program name?
135          */
136         DEBUG2("rlm_exec (%s): Executing %s", inst->xlat_name, fmt);
137         result = radius_exec_program(fmt, request, inst->wait,
138                                      out, outlen, *input_pairs, NULL, inst->shell_escape);
139         DEBUG2("rlm_exec (%s): result %d", inst->xlat_name, result);
140         if (result != 0) {
141                 out[0] = '\0';
142                 return 0;
143         }
144
145         return strlen(out);
146 }
147
148
149 /*
150  *      Detach an instance and free it's data.
151  */
152 static int exec_detach(void *instance)
153 {
154         rlm_exec_t      *inst = instance;
155
156         if (inst->xlat_name) {
157                 xlat_unregister(inst->xlat_name, exec_xlat);
158                 free(inst->xlat_name);
159         }
160
161         free(inst);
162         return 0;
163 }
164
165
166 /*
167  *      Do any per-module initialization that is separate to each
168  *      configured instance of the module.  e.g. set up connections
169  *      to external databases, read configuration files, set up
170  *      dictionary entries, etc.
171  *
172  *      If configuration information is given in the config section
173  *      that must be referenced in later calls, store a handle to it
174  *      in *instance otherwise put a null pointer there.
175  */
176 static int exec_instantiate(CONF_SECTION *conf, void **instance)
177 {
178         rlm_exec_t      *inst;
179         const char      *xlat_name;
180
181         /*
182          *      Set up a storage area for instance data
183          */
184
185         inst = rad_malloc(sizeof(rlm_exec_t));
186         if (!inst)
187                 return -1;
188         memset(inst, 0, sizeof(rlm_exec_t));
189
190         /*
191          *      If the configuration parameters can't be parsed, then
192          *      fail.
193          */
194         if (cf_section_parse(conf, inst, module_config) < 0) {
195                 radlog(L_ERR, "rlm_exec: Failed parsing the configuration");
196                 exec_detach(inst);
197                 return -1;
198         }
199
200         /*
201          *      No input pairs defined.  Why are we executing a program?
202          */
203         if (!inst->input) {
204                 radlog(L_ERR, "rlm_exec: Must define input pairs for external program.");
205                 exec_detach(inst);
206                 return -1;
207         }
208
209         /*
210          *      Sanity check the config.  If we're told to NOT wait,
211          *      then the output pairs must not be defined.
212          */
213         if (!inst->wait &&
214             (inst->output != NULL)) {
215                 radlog(L_ERR, "rlm_exec: Cannot read output pairs if wait=no");
216                 exec_detach(inst);
217                 return -1;
218         }
219
220         /*
221          *      Sanity check the config.  If we're told to wait,
222          *      then the output pairs should be defined.
223          */
224         if (inst->wait &&
225             (inst->output == NULL)) {
226                 radlog(L_INFO, "rlm_exec: wait=yes but no output defined. Did you mean output=none?");
227         }
228
229         /*
230          *      Get the packet type on which to execute
231          */
232         if (!inst->packet_type) {
233                 inst->packet_code = 0;
234         } else {
235                 DICT_VALUE      *dval;
236
237                 dval = dict_valbyname(PW_PACKET_TYPE, inst->packet_type);
238                 if (!dval) {
239                         radlog(L_ERR, "rlm_exec: Unknown packet type %s: See list of VALUEs for Packet-Type in share/dictionary", inst->packet_type);
240                         exec_detach(inst);
241                         return -1;
242                 }
243                 inst->packet_code = dval->value;
244         }
245
246         xlat_name = cf_section_name2(conf);
247         if (xlat_name == NULL)
248                 xlat_name = cf_section_name1(conf);
249         if (xlat_name){
250                 inst->xlat_name = strdup(xlat_name);
251                 xlat_register(xlat_name, exec_xlat, inst);
252         }
253
254         *instance = inst;
255
256         return 0;
257 }
258
259
260 /*
261  *  Dispatch an exec method
262  */
263 static int exec_dispatch(void *instance, REQUEST *request)
264 {
265         int result;
266         VALUE_PAIR **input_pairs, **output_pairs;
267         VALUE_PAIR *answer;
268         rlm_exec_t *inst = (rlm_exec_t *) instance;
269
270         /*
271          *      We need a program to execute.
272          */
273         if (!inst->program) {
274                 radlog(L_ERR, "rlm_exec (%s): We require a program to execute",
275                        inst->xlat_name);
276                 return RLM_MODULE_FAIL;
277         }
278
279         /*
280          *      See if we're supposed to execute it now.
281          */
282         if (!((inst->packet_code == 0) ||
283               (request->packet->code == inst->packet_code) ||
284               (request->reply->code == inst->packet_code) ||
285               (request->proxy &&
286                (request->proxy->code == inst->packet_code)) ||
287               (request->proxy_reply &&
288                (request->proxy_reply->code == inst->packet_code)))) {
289                 DEBUG2("  rlm_exec (%s): Packet type is not %s.  Not executing.",
290                        inst->xlat_name, inst->packet_type);
291                 return RLM_MODULE_NOOP;
292         }
293
294         /*
295          *      Decide what input/output the program takes.
296          */
297         input_pairs = decode_string(request, inst->input);
298         output_pairs = decode_string(request, inst->output);
299
300         /*
301          *      It points to the attribute list, but the attribute
302          *      list is empty.
303          */
304         if (input_pairs && !*input_pairs) {
305                 DEBUG2("rlm_exec (%s): WARNING! Input pairs are empty.  No attributes will be passed to the script", inst->xlat_name);
306         }
307
308         /*
309          *      This function does it's own xlat of the input program
310          *      to execute.
311          *
312          *      FIXME: if inst->program starts with %{, then
313          *      do an xlat ourselves.  This will allow us to do
314          *      program = %{Exec-Program}, which this module
315          *      xlat's into it's string value, and then the
316          *      exec program function xlat's it's string value
317          *      into something else.
318          */
319         result = radius_exec_program(inst->program, request,
320                                      inst->wait, NULL, 0,
321                                      *input_pairs, &answer, inst->shell_escape);
322         if (result < 0) {
323                 radlog(L_ERR, "rlm_exec (%s): External script failed",
324                        inst->xlat_name);
325                 return RLM_MODULE_FAIL;
326         }
327
328         /*
329          *      Move the answer over to the output pairs.
330          *
331          *      If we're not waiting, then there are no output pairs.
332          */
333         if (output_pairs) pairmove(output_pairs, &answer);
334
335         pairfree(&answer);
336
337         if (result == 0) {
338                 return RLM_MODULE_OK;
339         }
340         if (result > RLM_MODULE_NUMCODES) {
341                 return RLM_MODULE_FAIL;
342         }
343         return result-1;
344 }
345
346
347 /*
348  *      First, look for Exec-Program && Exec-Program-Wait.
349  *
350  *      Then, call exec_dispatch.
351  */
352 static int exec_postauth(void *instance, REQUEST *request)
353 {
354         int result;
355         int exec_wait = 0;
356         VALUE_PAIR *vp, *tmp;
357         rlm_exec_t *inst = (rlm_exec_t *) instance;
358
359         vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM);
360         if (vp) {
361                 exec_wait = 0;
362
363         } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT)) != NULL) {
364                 exec_wait = 1;
365         }
366         if (!vp) goto dispatch;
367
368         tmp = NULL;
369         result = radius_exec_program(vp->vp_strvalue, request, exec_wait,
370                                      NULL, 0, request->packet->vps, &tmp,
371                                      inst->shell_escape);
372
373         /*
374          *      Always add the value-pairs to the reply.
375          */
376         pairmove(&request->reply->vps, &tmp);
377         pairfree(&tmp);
378
379         if (result < 0) {
380                 /*
381                  *      Error. radius_exec_program() returns -1 on
382                  *      fork/exec errors.
383                  */
384                 tmp = pairmake("Reply-Message", "Access denied (external check failed)", T_OP_SET);
385                 pairadd(&request->reply->vps, tmp);
386
387                 DEBUG2("Login incorrect (external check failed)");
388
389                 request->reply->code = PW_AUTHENTICATION_REJECT;
390                 return RLM_MODULE_REJECT;
391         }
392         if (result > 0) {
393                 /*
394                  *      Reject. radius_exec_program() returns >0
395                  *      if the exec'ed program had a non-zero
396                  *      exit status.
397                  */
398                 request->reply->code = PW_AUTHENTICATION_REJECT;
399                 DEBUG2("Login incorrect (external check said so)");
400                 return RLM_MODULE_REJECT;
401         }
402
403  dispatch:
404         if (!inst->program) return RLM_MODULE_NOOP;
405
406         return exec_dispatch(instance, request);
407 }
408
409 /*
410  *      The module name should be the only globally exported symbol.
411  *      That is, everything else should be 'static'.
412  *
413  *      If the module needs to temporarily modify it's instantiation
414  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
415  *      The server will then take care of ensuring that the module
416  *      is single-threaded.
417  */
418 module_t rlm_exec = {
419         RLM_MODULE_INIT,
420         "exec",                         /* Name */
421         RLM_TYPE_THREAD_SAFE,           /* type */
422         exec_instantiate,               /* instantiation */
423         exec_detach,                    /* detach */
424         {
425                 exec_dispatch,          /* authentication */
426                 exec_dispatch,          /* authorization */
427                 exec_dispatch,          /* pre-accounting */
428                 exec_dispatch,          /* accounting */
429                 NULL,                   /* check simul */
430                 exec_dispatch,          /* pre-proxy */
431                 exec_dispatch,          /* post-proxy */
432                 exec_postauth           /* post-auth */
433         },
434 };