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;
42 char const *packet_type;
43 unsigned int packet_code;
49 * A mapping of configuration file names to internal variables.
51 * Note that the string is dynamically allocated, so it MUST
52 * be freed. When the configuration file parse re-reads the string,
53 * it free's the old one, and strdup's the new one, placing the pointer
54 * to the strdup'd string into 'config.string'. This gets around
57 static const CONF_PARSER module_config[] = {
58 { "wait", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_exec_t, wait), "yes" },
59 { "program", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_exec_t, program), NULL },
60 { "input_pairs", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_exec_t, input), NULL },
61 { "output_pairs", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_exec_t, output), NULL },
62 { "packet_type", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_exec_t, packet_type), NULL },
63 { "shell_escape", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_exec_t, shell_escape), "yes" },
64 { "timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_exec_t, timeout), NULL },
66 { NULL, -1, 0, NULL, NULL } /* end the list */
69 static char const special[] = "\\'\"`<>|; \t\r\n()[]?#$^&*=";
72 * Escape special characters
74 static size_t rlm_exec_shell_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *in,
85 if ((q + 3) >= end) break;
87 if (strchr(special, *p) != NULL) {
97 /** Process the exit code returned by one of the exec functions
99 * @param request Current request.
100 * @param answer Output string from exec call.
101 * @param len length of data in answer.
102 * @param status code returned by exec call.
103 * @return One of the RLM_MODULE_* values.
105 static rlm_rcode_t rlm_exec_status2rcode(REQUEST *request, char *answer, size_t len, int status)
108 return RLM_MODULE_FAIL;
112 * Exec'd programs are meant to return exit statuses that correspond
113 * to the standard RLM_MODULE_* + 1.
115 * This frees up 0, for success where it'd normally be reject.
118 RDEBUG("Program executed successfully");
120 return RLM_MODULE_OK;
123 if (status > RLM_MODULE_NUMCODES) {
124 REDEBUG("Program returned invalid code (greater than max rcode) (%i > %i): %s",
125 status, RLM_MODULE_NUMCODES, answer);
128 return RLM_MODULE_FAIL;
131 status--; /* Lets hope no one ever re-enumerates RLM_MODULE_* */
133 if (status == RLM_MODULE_FAIL) {
137 char *p = &answer[len - 1];
140 * Trim off trailing returns
142 while((p > answer) && ((*p == '\r') || (*p == '\n'))) {
146 module_failure_msg(request, "%s", answer);
149 return RLM_MODULE_FAIL;
156 * Do xlat of strings.
158 static ssize_t exec_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
161 rlm_exec_t *inst = instance;
162 VALUE_PAIR **input_pairs = NULL;
166 REDEBUG("'wait' must be enabled to use exec xlat");
171 if (inst->input_list) {
172 input_pairs = radius_list(request, inst->input_list);
174 REDEBUG("Failed to find input pairs for xlat");
181 * This function does it's own xlat of the input program
184 result = radius_exec_program(request, fmt, inst->wait, inst->shell_escape,
185 out, outlen, inst->timeout,
186 input_pairs ? *input_pairs : NULL, NULL);
192 for (p = out; *p != '\0'; p++) {
193 if (*p < ' ') *p = ' ';
200 * Do any per-module initialization that is separate to each
201 * configured instance of the module. e.g. set up connections
202 * to external databases, read configuration files, set up
203 * dictionary entries, etc.
205 * If configuration information is given in the config section
206 * that must be referenced in later calls, store a handle to it
207 * in *instance otherwise put a null pointer there.
209 static int mod_instantiate(CONF_SECTION *conf, void *instance)
212 rlm_exec_t *inst = instance;
214 inst->xlat_name = cf_section_name2(conf);
215 if (!inst->xlat_name) {
216 inst->xlat_name = cf_section_name1(conf);
220 xlat_register(inst->xlat_name, exec_xlat, rlm_exec_shell_escape, inst);
224 inst->input_list = radius_list_name(&p, PAIR_LIST_UNKNOWN);
225 if ((inst->input_list == PAIR_LIST_UNKNOWN) || (*p != '\0')) {
226 cf_log_err_cs(conf, "Invalid input list '%s'", inst->input);
233 inst->output_list = radius_list_name(&p, PAIR_LIST_UNKNOWN);
234 if ((inst->output_list == PAIR_LIST_UNKNOWN) || (*p != '\0')) {
235 cf_log_err_cs(conf, "Invalid output list '%s'", inst->output);
241 * Sanity check the config. If we're told to NOT wait,
242 * then the output pairs must not be defined.
245 (inst->output != NULL)) {
246 cf_log_err_cs(conf, "Cannot read output pairs if wait = no");
251 * Get the packet type on which to execute
253 if (!inst->packet_type) {
254 inst->packet_code = 0;
258 dval = dict_valbyname(PW_PACKET_TYPE, 0, inst->packet_type);
260 cf_log_err_cs(conf, "Unknown packet type %s: See list of VALUEs for Packet-Type in "
261 "share/dictionary", inst->packet_type);
264 inst->packet_code = dval->value;
268 * Get the time to wait before killing the child
270 if (!inst->timeout) {
271 inst->timeout = EXEC_TIMEOUT;
273 if (inst->timeout < 1) {
274 cf_log_err_cs(conf, "Timeout '%d' is too small (minimum: 1)", inst->timeout);
278 * Blocking a request longer than 30 seconds isn't going to help anyone.
280 if (inst->timeout > 30) {
281 cf_log_err_cs(conf, "Timeout '%d' is too large (maximum: 30)", inst->timeout);
290 * Dispatch an exec method
292 static rlm_rcode_t CC_HINT(nonnull) mod_exec_dispatch(void *instance, REQUEST *request)
294 rlm_exec_t *inst = (rlm_exec_t *)instance;
298 VALUE_PAIR **input_pairs = NULL, **output_pairs = NULL;
299 VALUE_PAIR *answer = NULL;
303 * We need a program to execute.
305 if (!inst->program) {
306 ERROR("rlm_exec (%s): We require a program to execute", inst->xlat_name);
307 return RLM_MODULE_FAIL;
311 * See if we're supposed to execute it now.
313 if (!((inst->packet_code == 0) || (request->packet->code == inst->packet_code) ||
314 (request->reply->code == inst->packet_code)
316 || (request->proxy && (request->proxy->code == inst->packet_code)) ||
317 (request->proxy_reply && (request->proxy_reply->code == inst->packet_code))
320 RDEBUG2("Packet type is not %s. Not executing.", inst->packet_type);
322 return RLM_MODULE_NOOP;
326 * Decide what input/output the program takes.
329 input_pairs = radius_list(request, inst->input_list);
331 return RLM_MODULE_INVALID;
336 output_pairs = radius_list(request, inst->output_list);
338 return RLM_MODULE_INVALID;
343 * This function does it's own xlat of the input program
346 status = radius_exec_program(request, inst->program, inst->wait, inst->shell_escape,
347 out, sizeof(out), inst->timeout,
348 inst->input ? *input_pairs : NULL,
349 inst->output ? &answer : NULL);
350 rcode = rlm_exec_status2rcode(request, out, strlen(out), status);
353 * Move the answer over to the output pairs.
355 * If we're not waiting, then there are no output pairs.
358 pairmove(request, output_pairs, &answer);
367 * First, look for Exec-Program && Exec-Program-Wait.
369 * Then, call exec_dispatch.
371 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
373 rlm_exec_t *inst = (rlm_exec_t *) instance;
378 bool we_wait = false;
379 VALUE_PAIR *vp, *tmp;
381 vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM, 0, TAG_ANY);
384 } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT, 0, TAG_ANY)) != NULL) {
388 if (!inst->program) {
389 return RLM_MODULE_NOOP;
392 rcode = mod_exec_dispatch(instance, request);
397 status = radius_exec_program(request, vp->vp_strvalue, we_wait, inst->shell_escape,
398 out, sizeof(out), inst->timeout,
399 request->packet->vps, &tmp);
400 rcode = rlm_exec_status2rcode(request, out, strlen(out), status);
403 * Always add the value-pairs to the reply.
405 pairmove(request->reply, &request->reply->vps, &tmp);
410 case RLM_MODULE_FAIL:
411 case RLM_MODULE_INVALID:
412 case RLM_MODULE_REJECT:
413 request->reply->code = PW_CODE_ACCESS_REJECT;
423 * First, look for Exec-Program && Exec-Program-Wait.
425 * Then, call exec_dispatch.
427 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
429 rlm_exec_t *inst = (rlm_exec_t *) instance;
433 bool we_wait = false;
437 * The "bare" exec module takes care of handling
438 * Exec-Program and Exec-Program-Wait.
441 return mod_exec_dispatch(instance, request);
444 vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM, 0, TAG_ANY);
447 } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT, 0, TAG_ANY)) != NULL) {
451 return RLM_MODULE_NOOP;
454 status = radius_exec_program(request, vp->vp_strvalue, we_wait, inst->shell_escape,
455 out, sizeof(out), inst->timeout,
456 request->packet->vps, NULL);
457 return rlm_exec_status2rcode(request, out, strlen(out), status);
461 * The module name should be the only globally exported symbol.
462 * That is, everything else should be 'static'.
464 * If the module needs to temporarily modify it's instantiation
465 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
466 * The server will then take care of ensuring that the module
467 * is single-threaded.
469 module_t rlm_exec = {
472 RLM_TYPE_THREAD_SAFE, /* type */
475 mod_instantiate, /* instantiation */
478 mod_exec_dispatch, /* authentication */
479 mod_exec_dispatch, /* authorization */
480 mod_exec_dispatch, /* pre-accounting */
481 mod_accounting, /* accounting */
482 NULL, /* check simul */
483 mod_exec_dispatch, /* pre-proxy */
484 mod_exec_dispatch, /* post-proxy */
485 mod_post_auth /* post-auth */