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.
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.
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
19 * @brief Execute commands and parse the results.
21 * @copyright 2002,2006 The FreeRADIUS server project
22 * @copyright 2002 Alan DeKok <aland@ox.org>
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/rad_assert.h>
31 * Define a structure for our module configuration.
33 typedef struct rlm_exec_t {
34 char const *xlat_name;
40 pair_lists_t input_list;
41 pair_lists_t output_list;
43 unsigned int packet_code;
48 * A mapping of configuration file names to internal variables.
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
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" },
64 { NULL, -1, 0, NULL, NULL } /* end the list */
67 static char const special[] = "\\'\"`<>|; \t\r\n()[]?#$^&*=";
70 * Escape special characters
72 static size_t rlm_exec_shell_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *in,
83 if ((q + 3) >= end) break;
85 if (strchr(special, *p) != NULL) {
95 /** Process the exit code returned by one of the exec functions
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.
103 static rlm_rcode_t rlm_exec_status2rcode(REQUEST *request, char *answer, size_t len, int status)
106 return RLM_MODULE_FAIL;
110 * Exec'd programs are meant to return exit statuses that correspond
111 * to the standard RLM_MODULE_* + 1.
113 * This frees up 0, for success where it'd normally be reject.
116 RDEBUG("Program executed successfully");
118 return RLM_MODULE_OK;
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);
126 return RLM_MODULE_FAIL;
129 status--; /* Lets hope no one ever re-enumerates RLM_MODULE_* */
131 if (status == RLM_MODULE_FAIL) {
135 char *p = &answer[len - 1];
138 * Trim off trailing returns
140 while((p > answer) && ((*p == '\r') || (*p == '\n'))) {
144 module_failure_msg(request, "%s", answer);
147 return RLM_MODULE_FAIL;
154 * Do xlat of strings.
156 static ssize_t exec_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
159 rlm_exec_t *inst = instance;
160 VALUE_PAIR **input_pairs = NULL;
164 REDEBUG("'wait' must be enabled to use exec xlat");
169 if (inst->input_list) {
170 input_pairs = radius_list(request, inst->input_list);
172 REDEBUG("Failed to find input pairs for xlat");
179 * FIXME: Do xlat of program name?
181 result = radius_exec_program(request, fmt, inst->wait, inst->shell_escape,
182 out, outlen, EXEC_TIMEOUT,
183 input_pairs ? *input_pairs : NULL, NULL);
189 for (p = out; *p != '\0'; p++) {
190 if (*p < ' ') *p = ' ';
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.
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.
206 static int mod_instantiate(CONF_SECTION *conf, void *instance)
209 rlm_exec_t *inst = instance;
211 inst->xlat_name = cf_section_name2(conf);
212 if (!inst->xlat_name) {
213 inst->xlat_name = cf_section_name1(conf);
217 xlat_register(inst->xlat_name, exec_xlat, rlm_exec_shell_escape, inst);
220 * Check whether program actually exists
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);
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);
242 * Sanity check the config. If we're told to NOT wait,
243 * then the output pairs must not be defined.
246 (inst->output != NULL)) {
247 cf_log_err_cs(conf, "Cannot read output pairs if wait = no");
252 * Get the packet type on which to execute
254 if (!inst->packet_type) {
255 inst->packet_code = 0;
259 dval = dict_valbyname(PW_PACKET_TYPE, 0, inst->packet_type);
261 cf_log_err_cs(conf, "Unknown packet type %s: See list of VALUEs for Packet-Type in "
262 "share/dictionary", inst->packet_type);
265 inst->packet_code = dval->value;
273 * Dispatch an exec method
275 static rlm_rcode_t exec_dispatch(void *instance, REQUEST *request)
277 rlm_exec_t *inst = (rlm_exec_t *)instance;
281 VALUE_PAIR **input_pairs = NULL, **output_pairs = NULL;
282 VALUE_PAIR *answer = NULL;
286 * We need a program to execute.
288 if (!inst->program) {
289 ERROR("rlm_exec (%s): We require a program to execute", inst->xlat_name);
290 return RLM_MODULE_FAIL;
294 * See if we're supposed to execute it now.
296 if (!((inst->packet_code == 0) || (request->packet->code == inst->packet_code) ||
297 (request->reply->code == inst->packet_code)
299 || (request->proxy && (request->proxy->code == inst->packet_code)) ||
300 (request->proxy_reply && (request->proxy_reply->code == inst->packet_code))
303 RDEBUG2("Packet type is not %s. Not executing.", inst->packet_type);
305 return RLM_MODULE_NOOP;
309 * Decide what input/output the program takes.
312 input_pairs = radius_list(request, inst->input_list);
314 return RLM_MODULE_INVALID;
319 output_pairs = radius_list(request, inst->output_list);
321 return RLM_MODULE_INVALID;
326 * This function does it's own xlat of the input program
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.
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);
342 * Move the answer over to the output pairs.
344 * If we're not waiting, then there are no output pairs.
347 pairmove(request, output_pairs, &answer);
356 * First, look for Exec-Program && Exec-Program-Wait.
358 * Then, call exec_dispatch.
360 static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request)
362 rlm_exec_t *inst = (rlm_exec_t *) instance;
367 bool we_wait = false;
368 VALUE_PAIR *vp, *tmp;
370 vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM, 0, TAG_ANY);
373 } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT, 0, TAG_ANY)) != NULL) {
377 if (!inst->program) {
378 return RLM_MODULE_NOOP;
381 rcode = exec_dispatch(instance, request);
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);
392 * Always add the value-pairs to the reply.
394 pairmove(request->reply, &request->reply->vps, &tmp);
399 case RLM_MODULE_FAIL:
400 case RLM_MODULE_INVALID:
401 case RLM_MODULE_REJECT:
402 request->reply->code = PW_AUTHENTICATION_REJECT;
412 * First, look for Exec-Program && Exec-Program-Wait.
414 * Then, call exec_dispatch.
416 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request)
418 rlm_exec_t *inst = (rlm_exec_t *) instance;
422 bool we_wait = false;
426 * The "bare" exec module takes care of handling
427 * Exec-Program and Exec-Program-Wait.
430 return exec_dispatch(instance, request);
433 vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM, 0, TAG_ANY);
436 } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT, 0, TAG_ANY)) != NULL) {
440 return RLM_MODULE_NOOP;
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);
450 * The module name should be the only globally exported symbol.
451 * That is, everything else should be 'static'.
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.
458 module_t rlm_exec = {
461 RLM_TYPE_CHECK_CONFIG_SAFE, /* type */
464 mod_instantiate, /* instantiation */
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 */