From: Arran Cudbard-Bell Date: Sat, 17 Feb 2024 22:04:29 +0000 (-0600) Subject: Do a better job of marking up literals passed to tmpl_tokenize with safe_for values X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6a12e26537f6738762243e60b942b765f0329ebe;p=thirdparty%2Ffreeradius-server.git Do a better job of marking up literals passed to tmpl_tokenize with safe_for values Fix the uri functions to use safe_for instead of tainted Allow a safe_for value to be passed in for maps too --- diff --git a/src/bin/unit_test_attribute.c b/src/bin/unit_test_attribute.c index 0af736626fa..15b404eec48 100644 --- a/src/bin/unit_test_attribute.c +++ b/src/bin/unit_test_attribute.c @@ -2788,7 +2788,8 @@ static size_t command_xlat_normalise(command_result_t *result, command_file_ctx_ .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved }, .xlat = cc->tmpl_rules.xlat, - }); + }, + 0); if (dec_len <= 0) { fr_strerror_printf_push_head("ERROR offset %d", (int) -dec_len); diff --git a/src/bin/unit_test_module.c b/src/bin/unit_test_module.c index 43996b7e4f2..233f3b911e0 100644 --- a/src/bin/unit_test_module.c +++ b/src/bin/unit_test_module.c @@ -489,7 +489,7 @@ static bool do_xlats(fr_event_list_t *el, request_t *request, char const *filena }; - slen = xlat_tokenize(xlat_ctx, &head, &line, &p_rules, &t_rules); + slen = xlat_tokenize(xlat_ctx, &head, &line, &p_rules, &t_rules, 0); if (slen <= 0) { talloc_free(xlat_ctx); fr_sbuff_in_sprintf(&out, "ERROR offset %d '%s'", (int) -slen, fr_strerror()); @@ -882,7 +882,7 @@ int main(int argc, char *argv[]) EXIT_WITH_FAILURE; } - if (map_proc_register(NULL, "test-fail", mod_map_proc, map_proc_verify, 0) < 0) { + if (map_proc_register(NULL, "test-fail", mod_map_proc, map_proc_verify, 0, 0) < 0) { EXIT_WITH_FAILURE; } diff --git a/src/lib/server/cf_parse.c b/src/lib/server/cf_parse.c index a5e7063de9c..c603b47cdae 100644 --- a/src/lib/server/cf_parse.c +++ b/src/lib/server/cf_parse.c @@ -1284,7 +1284,7 @@ int cf_section_parse_pass2(void *base, CONF_SECTION *cs) .allow_unresolved = false, .allow_foreign = (dict == NULL) }, - }); + }, 0); if (slen < 0) { char *spaces, *text; diff --git a/src/lib/server/map_proc.c b/src/lib/server/map_proc.c index 8e19454006e..3452d679c08 100644 --- a/src/lib/server/map_proc.c +++ b/src/lib/server/map_proc.c @@ -24,11 +24,13 @@ * @copyright 2015 Arran Cudbard-bell (a.cudbardb@freeradius.org) */ + RCSID("$Id$") #include #include #include +#include static fr_rb_tree_t *map_proc_root = NULL; @@ -101,13 +103,14 @@ map_proc_t *map_proc_find(char const *name) * @param[in] evaluate Module's map processor function. * @param[in] instantiate function (optional). * @param[in] inst_size of talloc chunk to allocate for instance data (optional). + * @param[in] literals_safe_for What safe_for value to assign to literals. * @return * - 0 on success. * - -1 on failure. */ int map_proc_register(void *mod_inst, char const *name, map_proc_func_t evaluate, - map_proc_instantiate_t instantiate, size_t inst_size) + map_proc_instantiate_t instantiate, size_t inst_size, fr_value_box_safe_for_t literals_safe_for) { map_proc_t *proc; @@ -145,6 +148,7 @@ int map_proc_register(void *mod_inst, char const *name, proc->evaluate = evaluate; proc->instantiate = instantiate; proc->inst_size = inst_size; + proc->literals_safe_for = literals_safe_for; return 0; } diff --git a/src/lib/server/map_proc.h b/src/lib/server/map_proc.h index 62f1bf2e0dc..c75c3d72039 100644 --- a/src/lib/server/map_proc.h +++ b/src/lib/server/map_proc.h @@ -40,6 +40,7 @@ typedef struct map_proc_inst map_proc_inst_t; #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -81,7 +82,7 @@ map_proc_t *map_proc_find(char const *name); void map_proc_free(void); int map_proc_register(void *mod_inst, char const *name, map_proc_func_t evaluate, - map_proc_instantiate_t instantiate, size_t inst_size); + map_proc_instantiate_t instantiate, size_t inst_size, fr_value_box_safe_for_t safe_for); map_proc_inst_t *map_proc_instantiate(TALLOC_CTX *ctx, map_proc_t const *proc, CONF_SECTION *cs, tmpl_t const *src, map_list_t const *maps); diff --git a/src/lib/server/map_proc_priv.h b/src/lib/server/map_proc_priv.h index a6037237b0f..4b333bfdc02 100644 --- a/src/lib/server/map_proc_priv.h +++ b/src/lib/server/map_proc_priv.h @@ -28,6 +28,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -44,6 +45,7 @@ struct map_proc { map_proc_func_t evaluate; //!< Module's map processor function. map_proc_instantiate_t instantiate; //!< Callback to create new instance struct. size_t inst_size; //!< Size of map_proc instance data to allocate. + fr_value_box_safe_for_t literals_safe_for; //!< Safe for values to be set for literals in the map source. }; /** Map processor instance diff --git a/src/lib/server/tmpl.h b/src/lib/server/tmpl.h index e4842da4040..83f15f5dc9c 100644 --- a/src/lib/server/tmpl.h +++ b/src/lib/server/tmpl.h @@ -335,32 +335,27 @@ struct tmpl_xlat_rules_s { bool new_functions; //!< new function syntax }; -struct tmpl_literal_rules_s { - fr_value_box_safe_for_t safe_for; //!< What should literals be marked up safe for -}; - /** Optional arguments passed to vp_tmpl functions * */ struct tmpl_rules_s { - tmpl_rules_t const *parent; //!< for parent / child relationships - - tmpl_attr_rules_t attr; //!< Rules/data for parsing attribute references. - tmpl_xlat_rules_t xlat; //!< Rules/data for parsing xlats. - - fr_dict_attr_t const *enumv; //!< Enumeration attribute used to resolve enum values. + tmpl_rules_t const *parent; //!< for parent / child relationships - fr_type_t cast; //!< Whether there was an explicit cast. - ///< Used to determine if barewords or other values - ///< should be converted to an internal data type. + tmpl_attr_rules_t attr; //!< Rules/data for parsing attribute references. + tmpl_xlat_rules_t xlat; //!< Rules/data for parsing xlats. - bool at_runtime; //!< Produce an ephemeral/runtime tmpl. - ///< Instantiated xlats are not added to the global - ///< trees, regexes are not JIT'd. + fr_dict_attr_t const *enumv; //!< Enumeration attribute used to resolve enum values. - tmpl_escape_t escape; //!< How escaping should be handled during evaluation. + fr_type_t cast; //!< Whether there was an explicit cast. + ///< Used to determine if barewords or other values + ///< should be converted to an internal data type. - tmpl_literal_rules_t literal; //!< Rules for parsing literals. + bool at_runtime; //!< Produce an ephemeral/runtime tmpl. + ///< Instantiated xlats are not added to the global + ///< trees, regexes are not JIT'd. + fr_value_box_safe_for_t literals_safe_for; //!< safe_for value assigned to literal values in + ///< xlats, execs, and data. + tmpl_escape_t escape; //!< How escaping should be handled during evaluation. }; /** Similar to tmpl_rules_t, but used to specify parameters that may change during subsequent resolution passes diff --git a/src/lib/server/tmpl_tokenize.c b/src/lib/server/tmpl_tokenize.c index 689e4628eb5..6d6d65a2648 100644 --- a/src/lib/server/tmpl_tokenize.c +++ b/src/lib/server/tmpl_tokenize.c @@ -37,6 +37,7 @@ RCSID("$Id$") #include #include +#include #include @@ -2414,6 +2415,7 @@ static fr_slen_t tmpl_afrom_value_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff talloc_free(vpt); FR_SBUFF_ERROR_RETURN(&our_in); } + fr_value_box_mark_safe_for(&tmp, t_rules->literals_safe_for); tmpl_init(vpt, TMPL_TYPE_DATA, quote, fr_sbuff_start(&our_in), fr_sbuff_used(&our_in), t_rules); @@ -3080,7 +3082,7 @@ fr_slen_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, xlat_exp_head_t *head = NULL; vpt = tmpl_alloc_null(ctx); - slen = xlat_tokenize(vpt, &head, &our_in, p_rules, t_rules); + slen = xlat_tokenize(vpt, &head, &our_in, p_rules, t_rules, t_rules->literals_safe_for); if (slen <= 0) FR_SBUFF_ERROR_RETURN(&our_in); if (xlat_needs_resolving(head)) UNRESOLVED_SET(&type); @@ -3112,8 +3114,7 @@ fr_slen_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, my_t_rules.cast = my_t_rules.enumv->type; - return tmpl_afrom_value_substr(ctx, out, in, quote, - &my_t_rules, true, p_rules); + return tmpl_afrom_value_substr(ctx, out, in, quote, &my_t_rules, true, p_rules); } /* @@ -3263,7 +3264,7 @@ fr_slen_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, vpt = tmpl_alloc_null(ctx); - slen = xlat_tokenize(vpt, &head, &our_in, p_rules, t_rules); + slen = xlat_tokenize(vpt, &head, &our_in, p_rules, t_rules, t_rules->literals_safe_for); if (slen < 0) FR_SBUFF_ERROR_RETURN(&our_in); /* @@ -3331,7 +3332,7 @@ fr_slen_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, * Ensure any xlats produced are bootstrapped * so that their instance data will be created. */ - if (xlat_finalize(head, t_rules) < 0) { + if (xlat_finalize(head, t_rules->xlat.runtime_el) < 0) { fr_strerror_const("Failed to bootstrap xlat"); FR_SBUFF_ERROR_RETURN(&our_in); } @@ -3356,7 +3357,7 @@ fr_slen_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, vpt = tmpl_alloc_null(ctx); - slen = xlat_tokenize(vpt, &head, &our_in, p_rules, t_rules); + slen = xlat_tokenize(vpt, &head, &our_in, p_rules, t_rules, t_rules->literals_safe_for); if (slen < 0) FR_SBUFF_ERROR_RETURN(&our_in); /* diff --git a/src/lib/server/trigger.c b/src/lib/server/trigger.c index b3cf109b7c3..82d354c97f6 100644 --- a/src/lib/server/trigger.c +++ b/src/lib/server/trigger.c @@ -461,12 +461,7 @@ int trigger_exec(unlang_interpret_t *intp, } } - if (xlat_finalize(trigger->xlat, &(tmpl_rules_t ) { - .xlat = { - .runtime_el = intp ? unlang_interpret_event_list(request) : main_loop_event_list(), - }, - .at_runtime = true - }) < 0) { + if (xlat_finalize(trigger->xlat, intp ? unlang_interpret_event_list(request) : main_loop_event_list()) < 0) { fr_strerror_const("Failed performing ephemeral instantiation for xlat"); talloc_free(request); return -1; diff --git a/src/lib/unlang/call_env.c b/src/lib/unlang/call_env.c index 776bf1231e4..c93658e89e2 100644 --- a/src/lib/unlang/call_env.c +++ b/src/lib/unlang/call_env.c @@ -430,6 +430,7 @@ static int call_env_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *parsed, char our_rules.attr.list_def = request_attr_request; our_rules.cast = ((type == FR_TYPE_VOID) ? FR_TYPE_NULL : type); + our_rules.literals_safe_for = rule->pair.literals_safe_for; call_env_parsed = call_env_parsed_alloc(ctx, rule); call_env_parsed->count = count; diff --git a/src/lib/unlang/call_env.h b/src/lib/unlang/call_env.h index f7438ae4763..9d1af0b249d 100644 --- a/src/lib/unlang/call_env.h +++ b/src/lib/unlang/call_env.h @@ -23,6 +23,7 @@ * * @copyright 2023 Network RADIUS SAS (legal@networkradius.com) */ +#include "lib/util/value.h" RCSIDH(call_env_h, "$Id$") #ifdef __cplusplus @@ -193,6 +194,7 @@ struct call_env_parser_s { call_env_parse_type_t type; //!< What type of output the parsing phase is expected to produce. } parsed; + fr_value_box_safe_for_t literals_safe_for; //!< What safe_for value to assign any literals that are arguments to the tmpl_t. tmpl_escape_t escape; //!< Escape method to use when evaluating tmpl_t. } pair; diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index 688ff62b457..61177f8a0aa 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -855,6 +855,7 @@ static unlang_t *compile_map(unlang_t *parent, unlang_compile_t *unlang_ctx, CON cf_log_err(cs, "Failed to find map processor '%s'", name2); return NULL; } + t_rules.literals_safe_for = proc->literals_safe_for; g = group_allocate(parent, cs, &map_ext); if (!g) return NULL; diff --git a/src/lib/unlang/xlat.h b/src/lib/unlang/xlat.h index dbc62b69349..a59317cacb9 100644 --- a/src/lib/unlang/xlat.h +++ b/src/lib/unlang/xlat.h @@ -398,7 +398,8 @@ fr_slen_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_ fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, bool comma, bool allow_attr); fr_slen_t xlat_tokenize(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, - fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules); + fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, + fr_value_box_safe_for_t literals_safe_for); fr_slen_t xlat_print(fr_sbuff_t *in, xlat_exp_head_t const *node, fr_sbuff_escape_rules_t const *e_rules); @@ -464,7 +465,7 @@ int xlat_instance_unregister_func(xlat_exp_t *node); int xlat_instance_register_func(xlat_exp_t *node); -int xlat_finalize(xlat_exp_head_t *head, tmpl_rules_t const *t_rules); /* xlat_instance_register() or xlat_instantiate_ephemeral() */ +int xlat_finalize(xlat_exp_head_t *head, fr_event_list_t *runtime_el); /* xlat_instance_register() or xlat_instantiate_ephemeral() */ void xlat_instances_free(void); diff --git a/src/lib/unlang/xlat_builtin.c b/src/lib/unlang/xlat_builtin.c index 2ca7046eefc..677570e0bf5 100644 --- a/src/lib/unlang/xlat_builtin.c +++ b/src/lib/unlang/xlat_builtin.c @@ -1494,7 +1494,7 @@ static xlat_action_t xlat_func_eval(TALLOC_CTX *ctx, fr_dcursor_t *out, .runtime_el = unlang_interpret_event_list(request), }, .at_runtime = true - }) < 0) { + }, 0) < 0) { RPEDEBUG("Failed parsing expansion"); error: talloc_free(rctx); diff --git a/src/lib/unlang/xlat_eval.c b/src/lib/unlang/xlat_eval.c index 8f76e66819d..91ac371a226 100644 --- a/src/lib/unlang/xlat_eval.c +++ b/src/lib/unlang/xlat_eval.c @@ -1425,7 +1425,7 @@ ssize_t _xlat_eval(TALLOC_CTX *ctx, char **out, size_t outlen, request_t *reques .runtime_el = unlang_interpret_event_list(request), }, .at_runtime = true, - }); + }, 0); if (len == 0) { if (*out) { **out = '\0'; diff --git a/src/lib/unlang/xlat_expr.c b/src/lib/unlang/xlat_expr.c index b3fcc15cde7..559ad09f06e 100644 --- a/src/lib/unlang/xlat_expr.c +++ b/src/lib/unlang/xlat_expr.c @@ -3005,7 +3005,7 @@ static fr_slen_t xlat_tokenize_expression_internal(TALLOC_CTX *ctx, xlat_exp_hea * Add nodes that need to be bootstrapped to * the registry. */ - if (xlat_finalize(head, t_rules) < 0) { + if (xlat_finalize(head, t_rules->xlat.runtime_el) < 0) { talloc_free(head); return -1; } @@ -3015,13 +3015,13 @@ static fr_slen_t xlat_tokenize_expression_internal(TALLOC_CTX *ctx, xlat_exp_hea } fr_slen_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t *in, - fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules) + fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules) { return xlat_tokenize_expression_internal(ctx, out, in, p_rules, t_rules, false); } fr_slen_t xlat_tokenize_condition(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t *in, - fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules) + fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules) { return xlat_tokenize_expression_internal(ctx, out, in, p_rules, t_rules, true); } diff --git a/src/lib/unlang/xlat_inst.c b/src/lib/unlang/xlat_inst.c index 156b5e2c2b7..ee016f658c5 100644 --- a/src/lib/unlang/xlat_inst.c +++ b/src/lib/unlang/xlat_inst.c @@ -679,16 +679,18 @@ static inline CC_HINT(always_inline) int xlat_instance_register(xlat_exp_head_t /** Bootstrap static xlats, or instantiate ephemeral ones. * - * @param[in] head of xlat tree to create instance data for. - * @param[in] t_rules parsing rules with #fr_event_list_t + * @param[in] head of xlat tree to create instance data for. + * @param[in] runtime_el determines whether we do ephemeral or static instantiation. + * If NULL, we perform static instantiation, otherwise + * will perform ephemeral instantiation passing the el to + * the instantiation functions. */ -int xlat_finalize(xlat_exp_head_t *head, tmpl_rules_t const *t_rules) +int xlat_finalize(xlat_exp_head_t *head, fr_event_list_t *runtime_el) { - if (!t_rules || !t_rules->at_runtime) { - fr_assert(!t_rules || !t_rules->xlat.runtime_el); + if (!runtime_el) { return xlat_instance_register(head); } - return xlat_instantiate_ephemeral(head, t_rules->xlat.runtime_el); + return xlat_instantiate_ephemeral(head, runtime_el); } /** Walk over all registered instance data and free them explicitly diff --git a/src/lib/unlang/xlat_tokenize.c b/src/lib/unlang/xlat_tokenize.c index 423d9012aec..c6747504bc9 100644 --- a/src/lib/unlang/xlat_tokenize.c +++ b/src/lib/unlang/xlat_tokenize.c @@ -25,6 +25,7 @@ * @copyright 2000,2006 The FreeRADIUS server project */ +#include "lib/util/event.h" #include "lib/util/value.h" RCSID("$Id$") @@ -897,7 +898,8 @@ check_for_attr: * - -1 on failure. */ static int xlat_tokenize_input(xlat_exp_head_t *head, fr_sbuff_t *in, - fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, fr_value_box_safe_for_t safe_for) + fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, + fr_value_box_safe_for_t safe_for) { xlat_exp_t *node = NULL; fr_slen_t slen; @@ -1611,12 +1613,17 @@ fr_slen_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t /** Tokenize an xlat expansion * - * @param[in] ctx to allocate dynamic buffers in. - * @param[out] out the head of the xlat list / tree structure. - * @param[in] in the format string to expand. - * @param[in] p_rules controlling how the string containing the xlat - * expansions should be parsed. - * @param[in] t_rules controlling how attribute references are parsed. + * @param[in] ctx to allocate dynamic buffers in. + * @param[out] out the head of the xlat list / tree structure. + * @param[in] in the format string to expand. + * @param[in] p_rules controlling how the string containing the xlat + * expansions should be parsed. + * @param[in] t_rules controlling how attribute references are parsed. + * Do NOT alter this function to take tmpl_rules_t + * as this provides another value for literals_safe_for + * and this gets very confusing. + * @param[in] literals_safe_for the safe_for value to assign to any literals occurring at the + * top level of the expansion. * @return * - >0 on success. * - 0 and *head == NULL - Parse failure on first char. @@ -1624,19 +1631,17 @@ fr_slen_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t * - < 0 the negative offset of the parse failure. */ fr_slen_t xlat_tokenize(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t *in, - fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules) + fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, + fr_value_box_safe_for_t literals_safe_for) { fr_sbuff_t our_in = FR_SBUFF(in); xlat_exp_head_t *head; - MEM(head = xlat_exp_head_alloc(ctx)); - if (t_rules) { - fr_assert(!t_rules->at_runtime || t_rules->xlat.runtime_el); /* if it's at runtime, we need an event list */ - } + MEM(head = xlat_exp_head_alloc(ctx)); fr_strerror_clear(); /* Clear error buffer */ - if (xlat_tokenize_input(head, &our_in, p_rules, t_rules, t_rules ? t_rules->literal.safe_for : 0) < 0) { + if (xlat_tokenize_input(head, &our_in, p_rules, t_rules, literals_safe_for) < 0) { talloc_free(head); FR_SBUFF_ERROR_RETURN(&our_in); } @@ -1645,7 +1650,7 @@ fr_slen_t xlat_tokenize(TALLOC_CTX *ctx, xlat_exp_head_t **out, fr_sbuff_t *in, * Add nodes that need to be bootstrapped to * the registry. */ - if (xlat_finalize(head, t_rules) < 0) { + if (xlat_finalize(head, t_rules->xlat.runtime_el) < 0) { talloc_free(head); return 0; } diff --git a/src/lib/util/uri.c b/src/lib/util/uri.c index 50c57c4067e..c54d54b96ef 100644 --- a/src/lib/util/uri.c +++ b/src/lib/util/uri.c @@ -20,6 +20,9 @@ * * @copyright 2021 The FreeRADIUS server project */ +#include "lib/util/sbuff.h" +#include "lib/util/table.h" +#include "lib/util/value.h" RCSID("$Id$") #include "uri.h" @@ -28,6 +31,9 @@ RCSID("$Id$") * * @note This function has a signature compatible with fr_uri_escape_func_t. * + * @note This function may modify the type of boxes, as all boxes in the list are + * cast to strings before parsing. + * * @param[in,out] uri_vb to escape * @param[in] uctx A fr_uri_escape_ctx_t containing the initial fr_uri_part_t * and the uctx to pass to the escaping function. @@ -41,11 +47,6 @@ int fr_uri_escape(fr_value_box_t *uri_vb, void *uctx) fr_sbuff_t sbuff; uint8_t adv; - if (uri_vb->tainted && !ctx->uri_part->tainted_allowed) { - fr_strerror_printf_push("Tainted value not allowed for %s", ctx->uri_part->name); - return -1; - } - /* * Ensure boxes are strings before attempting to escape. */ @@ -59,7 +60,7 @@ int fr_uri_escape(fr_value_box_t *uri_vb, void *uctx) /* * Tainted boxes can only belong to a single part of the URI */ - if (uri_vb->tainted) { + if ((ctx->uri_part->safe_for > 0) && !fr_value_box_is_safe_for(uri_vb, ctx->uri_part->safe_for)) { if (ctx->uri_part->func) { /* * Escaping often ends up breaking the vb's list pointers @@ -71,7 +72,11 @@ int fr_uri_escape(fr_value_box_t *uri_vb, void *uctx) fr_strerror_printf_push("Unable to escape tainted input %pV", uri_vb); return -1; } + fr_value_box_mark_safe_for(uri_vb, ctx->uri_part->safe_for); uri_vb->entry = entry; + } else { + fr_strerror_printf_push("Unsafe input \"%pV\" not allowed in URI part %s", uri_vb, ctx->uri_part->name); + return -1; } return 0; } @@ -120,6 +125,9 @@ int fr_uri_escape(fr_value_box_t *uri_vb, void *uctx) * definition in uri_parts. Tainted values, where allowed, are escaped * using the function specified for the uri part. * + * @note This function may modify the type of boxes, as all boxes in the list are + * cast to strings before parsing. + * * @param uri to parse. A list of string type value boxes containing * fragments of a URI. * @param uri_parts definition of URI structure. Should point to the start @@ -144,3 +152,48 @@ int fr_uri_escape_list(fr_value_box_list_t *uri, fr_uri_part_t const *uri_parts, return 0; } + +/** Searches for a matching scheme in the table of schemes, using a list of value boxes representing the URI + * + * @note Unlikel + * + * @param uri to parse. A list of string type value boxes containing + * fragments of a URI. + * @param schemes Table of schemes to search. + * @param schemes_len Number of schemes in the table. + * @param def Default scheme to use if none is found. + * @return The matching scheme, or def if none is found. + */ +int fr_uri_has_scheme(fr_value_box_list_t *uri, fr_table_num_sorted_t const *schemes, size_t schemes_len, int def) +{ + char scheme_buff[20]; /* hopefully no schemes over 20 bytes */ + fr_sbuff_t sbuff = FR_SBUFF_OUT(scheme_buff, sizeof(scheme_buff)); + + /* + * Fill the scheme buffer with at most sizeof(scheme_buff) - 1 bytes of string data. + */ + fr_value_box_list_foreach_safe(uri, vb) { + fr_value_box_t tmp; + int ret; + + if (unlikely(vb->type != FR_TYPE_STRING)) { + if (unlikely(fr_value_box_cast(NULL, &tmp, FR_TYPE_STRING, vb->enumv, vb) < 0)) { + fr_strerror_printf_push("Unable to cast %pV to a string", vb); + return 0; + } + ret = fr_sbuff_in_bstrncpy(&sbuff, tmp.vb_strvalue, + fr_sbuff_remaining(&sbuff) > tmp.vb_length ? tmp.vb_length : fr_sbuff_remaining(&sbuff)); + fr_value_box_clear_value(&tmp); + } else { + ret = fr_sbuff_in_bstrncpy(&sbuff, vb->vb_strvalue, + fr_sbuff_remaining(&sbuff) > vb->vb_length ? vb->vb_length : fr_sbuff_remaining(&sbuff)); + } + + if (unlikely(ret < 0)) return -1; + }} + + /* + * Ensure the first box is a valid scheme + */ + return fr_table_value_by_longest_prefix(NULL, schemes, fr_sbuff_start(&sbuff), fr_sbuff_used(&sbuff), def); +} diff --git a/src/lib/util/uri.h b/src/lib/util/uri.h index 14ce1b8fcf6..ec9fc03acb9 100644 --- a/src/lib/util/uri.h +++ b/src/lib/util/uri.h @@ -44,13 +44,13 @@ typedef int (*fr_uri_escape_func_t)(fr_value_box_t *vb, void *uctx); * */ typedef struct { - char const *name; //!< Name of this part of the URI - fr_sbuff_term_t const *terminals; //!< Characters that mark the end of this part. - uint8_t const part_adv[UINT8_MAX + 1]; //!< How many parts to advance for a specific terminal - size_t extra_skip; //!< How many additional characters to skip after - ///< the terminal - bool tainted_allowed; //!< Do we accept tainted values for this part - fr_uri_escape_func_t func; //!< Function to use to escape tainted values + char const *name; //!< Name of this part of the URI + fr_sbuff_term_t const *terminals; //!< Characters that mark the end of this part. + uint8_t const part_adv[UINT8_MAX + 1]; //!< How many parts to advance for a specific terminal + size_t extra_skip; //!< How many additional characters to skip after + ///< the terminal + fr_value_box_safe_for_t safe_for; //!< What type of value is safe for this part + fr_uri_escape_func_t func; //!< Function to use to escape tainted values } fr_uri_part_t; /** uctx to pass to fr_uri_escape @@ -63,12 +63,14 @@ typedef struct { void *uctx; //!< to pass to fr_uri_escape_func_t. } fr_uri_escape_ctx_t; -#define XLAT_URI_PART_TERMINATOR { .name = NULL, .terminals = NULL, .tainted_allowed = false, .func = NULL } +#define XLAT_URI_PART_TERMINATOR { .name = NULL, .terminals = NULL, .safe_for = 0, .func = NULL } int fr_uri_escape_list(fr_value_box_list_t *uri, fr_uri_part_t const *uri_parts, void *uctx); int fr_uri_escape(fr_value_box_t *uri_vb, void *uctx); +int fr_uri_has_scheme(fr_value_box_list_t *uri, fr_table_num_sorted_t const *schemes, size_t schemes_len, int def); + #ifdef __cplusplus } #endif diff --git a/src/modules/rlm_client/rlm_client.c b/src/modules/rlm_client/rlm_client.c index dad5cad6ab2..a6d3d24ae3a 100644 --- a/src/modules/rlm_client/rlm_client.c +++ b/src/modules/rlm_client/rlm_client.c @@ -357,7 +357,7 @@ static int mod_load(void) if (unlikely((xlat = xlat_func_register(NULL, "client", xlat_client, FR_TYPE_STRING)) == NULL)) return -1; xlat_func_args_set(xlat, xlat_client_args); - map_proc_register(NULL, "client", map_proc_client, NULL, 0); + map_proc_register(NULL, "client", map_proc_client, NULL, 0, 0); return 0; } diff --git a/src/modules/rlm_csv/rlm_csv.c b/src/modules/rlm_csv/rlm_csv.c index 2dc964bc8a6..063ba4291c2 100644 --- a/src/modules/rlm_csv/rlm_csv.c +++ b/src/modules/rlm_csv/rlm_csv.c @@ -737,12 +737,11 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) /* * And register the `map csv { ... }` function. */ - map_proc_register(inst, mctx->inst->name, mod_map_proc, csv_maps_verify, 0); + map_proc_register(inst, mctx->inst->name, mod_map_proc, csv_maps_verify, 0, 0); return 0; } - /** Instantiate the module * * Creates a new instance of the module reading parameters from a configuration section. diff --git a/src/modules/rlm_json/rlm_json.c b/src/modules/rlm_json/rlm_json.c index 2276522a81d..584b9966968 100644 --- a/src/modules/rlm_json/rlm_json.c +++ b/src/modules/rlm_json/rlm_json.c @@ -584,7 +584,7 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) fr_json_format_verify(format, true); if (map_proc_register(inst, "json", mod_map_proc, - mod_map_proc_instantiate, sizeof(rlm_json_jpath_cache_t)) < 0) return -1; + mod_map_proc_instantiate, sizeof(rlm_json_jpath_cache_t), 0) < 0) return -1; return 0; } diff --git a/src/modules/rlm_ldap/rlm_ldap.c b/src/modules/rlm_ldap/rlm_ldap.c index e62ea9173a4..bdf3881d697 100644 --- a/src/modules/rlm_ldap/rlm_ldap.c +++ b/src/modules/rlm_ldap/rlm_ldap.c @@ -33,7 +33,9 @@ RCSID("$Id$") USES_APPLE_DEPRECATED_API #include +#include #include +#include #include #include @@ -346,8 +348,25 @@ typedef struct { fr_ldap_map_exp_t expanded; } ldap_map_ctx_t; +typedef enum { + LDAP_SCHEME_UNIX = 0, + LDAP_SCHEME_TCP, + LDAP_SCHEME_TCP_SSL +} ldap_schemes_t; + +static fr_table_num_sorted_t const ldap_uri_scheme_table[] = { + { L("ldap://"), LDAP_SCHEME_UNIX }, + { L("ldapi://"), LDAP_SCHEME_TCP }, + { L("ldaps://"), LDAP_SCHEME_TCP_SSL }, +}; +static size_t ldap_uri_scheme_table_len = NUM_ELEMENTS(ldap_uri_scheme_table); + +/** This is the common function that actually ends up doing all the URI escaping + */ +#define LDAP_URI_SAFE_FOR (fr_value_box_safe_for_t)fr_ldap_escape_func + static xlat_arg_parser_t const ldap_escape_xlat_arg[] = { - { .required = true, .concat = true, .type = FR_TYPE_STRING }, + { .required = true, .concat = true, .type = FR_TYPE_STRING, .safe_for = LDAP_URI_SAFE_FOR }, XLAT_ARG_PARSER_TERMINATOR }; @@ -396,7 +415,6 @@ static xlat_action_t ldap_escape_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, */ fr_sbuff_trim_talloc(&sbuff, len); fr_value_box_strdup_shallow(vb, NULL, fr_sbuff_buff(&sbuff), in_vb->tainted); - fr_value_box_mark_safe_for(vb, ldap_escape_xlat); fr_dcursor_append(out, vb); return XLAT_ACTION_DONE; @@ -443,17 +461,12 @@ static xlat_action_t ldap_unescape_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, /** Escape function for a part of an LDAP URI * */ -static int uri_part_escape(fr_value_box_t *vb, UNUSED void *uctx) +static int ldap_uri_part_escape(fr_value_box_t *vb, UNUSED void *uctx) { fr_sbuff_t sbuff; fr_sbuff_uctx_talloc_t sbuff_ctx; size_t len; - /* - * If it's already safe, don't do anything. - */ - if (fr_value_box_is_safe_for(vb, uri_part_escape)) return 0; - /* * Maximum space needed for output would be 3 times the input if every * char needed escaping @@ -471,7 +484,6 @@ static int uri_part_escape(fr_value_box_t *vb, UNUSED void *uctx) fr_sbuff_trim_talloc(&sbuff, len); fr_value_box_clear_value(vb); fr_value_box_strdup_shallow(vb, NULL, fr_sbuff_buff(&sbuff), vb->tainted); - fr_value_box_mark_safe_for(vb, uri_part_escape); return 0; } @@ -563,28 +575,32 @@ static void ldap_xlat_signal(xlat_ctx_t const *xctx, request_t *request, UNUSED fr_trunk_request_signal_cancel(query->treq); } - +/* + * If a part doesn't have an escaping function, parsing will fail unless the input + * was marked up with a safe_for value by the ldap arg parsing, i.e. was a literal + * input argument to the xlat. + * + * This is equivalent to the old "tainted_allowed" flag. + */ static fr_uri_part_t const ldap_uri_parts[] = { - { .name = "scheme", .terminals = &FR_SBUFF_TERMS(L(":")), .part_adv = { [':'] = 1 }, - .tainted_allowed = false, .extra_skip = 2 }, - { .name = "host", .terminals = &FR_SBUFF_TERMS(L(":"), L("/")), .part_adv = { [':'] = 1, ['/'] = 2 }, - .tainted_allowed = false }, - { .name = "port", .terminals = &FR_SBUFF_TERMS(L("/")), .part_adv = { ['/'] = 1 }, - .tainted_allowed = false }, - { .name = "dn", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }, - .tainted_allowed = true, .func = uri_part_escape }, - { .name = "attrs", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }, - .tainted_allowed = false }, - { .name = "scope", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }, - .tainted_allowed = true, .func = uri_part_escape }, - { .name = "filter", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1}, - .tainted_allowed = true, .func = uri_part_escape }, - { .name = "exts", .tainted_allowed = true, .func = uri_part_escape }, + { .name = "scheme", .safe_for = LDAP_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L(":")), .part_adv = { [':'] = 1 }, .extra_skip = 2 }, + { .name = "host", .safe_for = LDAP_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L(":"), L("/")), .part_adv = { [':'] = 1, ['/'] = 2 } }, + { .name = "port", .safe_for = LDAP_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("/")), .part_adv = { ['/'] = 1 } }, + { .name = "dn", .safe_for = LDAP_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }, .func = ldap_uri_part_escape }, + { .name = "attrs", .safe_for = LDAP_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }}, + { .name = "scope", .safe_for = LDAP_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }, .func = ldap_uri_part_escape }, + { .name = "filter", .safe_for = LDAP_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1}, .func = ldap_uri_part_escape }, + { .name = "exts", .safe_for = LDAP_URI_SAFE_FOR, .func = ldap_uri_part_escape }, + XLAT_URI_PART_TERMINATOR +}; + +static fr_uri_part_t const ldap_dn_parts[] = { + { .name = "dn", .safe_for = LDAP_URI_SAFE_FOR , .func = ldap_uri_part_escape }, XLAT_URI_PART_TERMINATOR }; static xlat_arg_parser_t const ldap_xlat_arg[] = { - { .required = true, .type = FR_TYPE_STRING }, + { .required = true, .type = FR_TYPE_STRING, .safe_for = LDAP_URI_SAFE_FOR }, XLAT_ARG_PARSER_TERMINATOR }; @@ -829,7 +845,7 @@ static xlat_action_t ldap_memberof_xlat_resume(TALLOC_CTX *ctx, fr_dcursor_t *ou } static xlat_arg_parser_t const ldap_memberof_xlat_arg[] = { - { .required = true, .concat = true, .type = FR_TYPE_STRING }, + { .required = true, .concat = true, .type = FR_TYPE_STRING, .safe_for = LDAP_URI_SAFE_FOR }, XLAT_ARG_PARSER_TERMINATOR }; @@ -982,18 +998,32 @@ static xlat_action_t ldap_profile_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor char const *filter; int scope; + bool is_dn; + XLAT_ARGS(in, &uri_components); - if (fr_uri_escape_list(&uri_components->vb_group, ldap_uri_parts, NULL) < 0) { - RPERROR("Failed to escape LDAP URI"); - return XLAT_ACTION_FAIL; + is_dn = (fr_uri_has_scheme(&uri_components->vb_group, ldap_uri_scheme_table, ldap_uri_scheme_table_len, -1) < 0); + + /* + * Apply different escaping rules based on whether the first + * arg lookgs like a URI or a DN. + */ + if (is_dn) { + if (fr_uri_escape_list(&uri_components->vb_group, ldap_dn_parts, NULL) < 0) { + RPERROR("Failed to escape LDAP DN"); + return XLAT_ACTION_FAIL; + } + } else { + if (fr_uri_escape_list(&uri_components->vb_group, ldap_uri_parts, NULL) < 0) { + RPERROR("Failed to escape LDAP URI"); + return XLAT_ACTION_FAIL; + } } /* * Smush everything into the first URI box */ uri = fr_value_box_list_head(&uri_components->vb_group); - if (fr_value_box_list_concat_in_place(uri, uri, &uri_components->vb_group, FR_TYPE_STRING, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) { REDEBUG("Failed concattenating input"); @@ -1006,7 +1036,7 @@ static xlat_action_t ldap_profile_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor MEM(xlat_ctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), ldap_xlat_profile_ctx_t)); talloc_set_destructor(xlat_ctx, ldap_xlat_profile_ctx_free); - if (!ldap_is_ldap_url(uri->vb_strvalue)) { + if (is_dn) { host_url = handle_config->server; dn = talloc_typed_strdup_buffer(xlat_ctx, uri->vb_strvalue); filter = env_data->profile_filter.vb_strvalue; @@ -1229,11 +1259,13 @@ static unlang_action_t mod_map_proc(rlm_rcode_t *p_result, void *mod_inst, UNUSE ldap_map_ctx_t *map_ctx; char *host_url, *host = NULL; - if (fr_uri_escape_list(url, ldap_uri_parts, NULL) < 0) { - RPERROR("Failed to escape LDAP URI"); - RETURN_MODULE_FAIL; - } - + /* FIXME - We need a way of markup up literal */ +/* + * if (fr_uri_escape_list(url, ldap_uri_parts, NULL) < 0) { + * RPERROR("Failed to escape LDAP URI"); + * RETURN_MODULE_FAIL; + * } + */ url_head = fr_value_box_list_head(url); if (!url_head) { REDEBUG("LDAP URL cannot be (null)"); @@ -2273,7 +2305,7 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) xlat_func_args_set(xlat, ldap_xlat_arg); xlat_func_call_env_set(xlat, &xlat_profile_method_env); - map_proc_register(inst, mctx->inst->name, mod_map_proc, ldap_map_verify, 0); + map_proc_register(inst, mctx->inst->name, mod_map_proc, ldap_map_verify, 0, LDAP_URI_SAFE_FOR); return 0; } @@ -2564,6 +2596,8 @@ static int mod_load(void) if (unlikely(!(xlat = xlat_func_register(NULL, "ldap.escape", ldap_escape_xlat, FR_TYPE_STRING)))) return -1; xlat_func_mono_set(xlat, ldap_escape_xlat_arg); xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_PURE); + xlat_func_safe_for_set(xlat, LDAP_URI_SAFE_FOR); /* Used for all LDAP escaping */ + if (unlikely(!(xlat = xlat_func_register(NULL, "ldap.unescape", ldap_unescape_xlat, FR_TYPE_STRING)))) return -1; xlat_func_mono_set(xlat, ldap_escape_xlat_arg); xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_PURE); diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c index aa7992e0cc6..1dc539bb3b7 100644 --- a/src/modules/rlm_rest/rlm_rest.c +++ b/src/modules/rlm_rest/rlm_rest.c @@ -49,19 +49,17 @@ RCSID("$Id$") #include "rest.h" -static int uri_part_escape(fr_value_box_t *vb, void *uctx); -static void *uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *uctx); +static int rest_uri_part_escape(fr_value_box_t *vb, void *uctx); +static void *rest_uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *uctx); + +#define REST_URI_SAFE_FOR (fr_value_box_safe_for_t)fr_curl_xlat_uri_escape static fr_uri_part_t const rest_uri_parts[] = { - { .name = "scheme", .terminals = &FR_SBUFF_TERMS(L(":")), .part_adv = { [':'] = 1 }, - .tainted_allowed = false, .extra_skip = 2 }, - { .name = "host", .terminals = &FR_SBUFF_TERMS(L(":"), L("/")), .part_adv = { [':'] = 1, ['/'] = 2 }, - .tainted_allowed = true, .func = uri_part_escape }, - { .name = "port", .terminals = &FR_SBUFF_TERMS(L("/")), .part_adv = { ['/'] = 1 }, - .tainted_allowed = false }, - { .name = "method", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }, - .tainted_allowed = true, .func = uri_part_escape }, - { .name = "param", .tainted_allowed = true, .func = uri_part_escape }, + { .name = "scheme", .safe_for = REST_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L(":")), .part_adv = { [':'] = 1 }, .extra_skip = 2 }, + { .name = "host", .safe_for = REST_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L(":"), L("/")), .part_adv = { [':'] = 1, ['/'] = 2 }, .func = rest_uri_part_escape }, + { .name = "port", .safe_for = REST_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("/")), .part_adv = { ['/'] = 1 } }, + { .name = "method", .safe_for = REST_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }, .func = rest_uri_part_escape }, + { .name = "param", .safe_for = REST_URI_SAFE_FOR, .func = rest_uri_part_escape }, XLAT_URI_PART_TERMINATOR }; @@ -212,13 +210,14 @@ static const call_env_method_t _var = { \ .mode = TMPL_ESCAPE_PRE_CONCAT, \ .uctx = { \ .func = { \ - .alloc = uri_part_escape_uctx_alloc, \ + .alloc = rest_uri_part_escape_uctx_alloc, \ .uctx = rest_uri_parts \ } , \ .type = TMPL_ESCAPE_UCTX_ALLOC_FUNC\ }, \ - .safe_for = (fr_value_box_safe_for_t)fr_curl_xlat_uri_escape \ - }}, /* Do not concat */ \ + .safe_for = REST_URI_SAFE_FOR \ + }, \ + .pair.literals_safe_for = REST_URI_SAFE_FOR}, /* Do not concat */ \ REST_CALL_ENV_REQUEST_COMMON(_dflt_username, _dflt_password) \ CALL_ENV_TERMINATOR \ })) }, \ @@ -334,7 +333,7 @@ static int rlm_rest_status_update(request_t *request, void *handle) return 0; } -static int _uri_part_escape_uctx_free(void *uctx) +static int _rest_uri_part_escape_uctx_free(void *uctx) { return talloc_free(uctx); } @@ -345,7 +344,7 @@ static int _uri_part_escape_uctx_free(void *uctx) * @param[in] uctx pointer to the start of the uri_parts array. * @return A new fr_uri_escape_ctx_t. */ -static void *uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *uctx) +static void *rest_uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *uctx) { static _Thread_local fr_uri_escape_ctx_t *t_ctx; @@ -353,7 +352,7 @@ static void *uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *u fr_uri_escape_ctx_t *ctx; MEM(ctx = talloc_zero(NULL, fr_uri_escape_ctx_t)); - fr_atexit_thread_local(t_ctx, _uri_part_escape_uctx_free, ctx); + fr_atexit_thread_local(t_ctx, _rest_uri_part_escape_uctx_free, ctx); } else { memset(t_ctx, 0, sizeof(*t_ctx)); } @@ -369,7 +368,7 @@ static void *uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *u * - 0 on success * - -1 on failure */ -static int uri_part_escape(fr_value_box_t *vb, UNUSED void *uctx) +static int rest_uri_part_escape(fr_value_box_t *vb, UNUSED void *uctx) { char *escaped; @@ -498,7 +497,7 @@ finish: } static xlat_arg_parser_t const rest_xlat_args[] = { - { .required = true, .type = FR_TYPE_STRING }, + { .required = true, .safe_for = REST_URI_SAFE_FOR, .type = FR_TYPE_STRING }, { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_STRING }, XLAT_ARG_PARSER_TERMINATOR }; @@ -1379,9 +1378,33 @@ static int mod_load(void) fr_json_version_print(); #endif + { + xlat_t *xlat; + + xlat = xlat_func_register(NULL, "rest.escape", fr_curl_xlat_uri_escape, FR_TYPE_STRING); + if (unlikely(!xlat)) { + ERROR("Failed registering \"rest.escape\" xlat"); + return -1; + } + xlat_func_args_set(xlat, fr_curl_xlat_uri_args); + xlat_func_safe_for_set(xlat, REST_URI_SAFE_FOR); /* Each instance of the uri_escape xlat has its own safe_for value */ + xlat = xlat_func_register(NULL, "rest.unescape", fr_curl_xlat_uri_unescape, FR_TYPE_STRING); + if (unlikely(!xlat)) { + ERROR("Failed registering \"rest.unescape\" xlat"); + return -1; + } + xlat_func_args_set(xlat, fr_curl_xlat_uri_args); + } + return 0; } +static void mod_unload(void) +{ + xlat_func_unregister("rest.escape"); + xlat_func_unregister("rest.unescape"); +} + /* * The module name should be the only globally exported symbol. * That is, everything else should be 'static'. @@ -1401,6 +1424,7 @@ module_rlm_t rlm_rest = { .thread_inst_size = sizeof(rlm_rest_thread_t), .config = module_config, .onload = mod_load, + .unload = mod_unload, .bootstrap = mod_bootstrap, .instantiate = instantiate, .thread_instantiate = mod_thread_instantiate, diff --git a/src/modules/rlm_sql/rlm_sql.c b/src/modules/rlm_sql/rlm_sql.c index 3c4c4ac16e1..f30b5a96585 100644 --- a/src/modules/rlm_sql/rlm_sql.c +++ b/src/modules/rlm_sql/rlm_sql.c @@ -1619,7 +1619,7 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) /* * Register the SQL map processor function */ - if (inst->driver->sql_fields) map_proc_register(inst, mctx->inst->name, mod_map_proc, sql_map_verify, 0); + if (inst->driver->sql_fields) map_proc_register(inst, mctx->inst->name, mod_map_proc, sql_map_verify, 0, (fr_value_box_safe_for_t)inst->driver); return 0; } diff --git a/src/modules/rlm_sqlcounter/rlm_sqlcounter.c b/src/modules/rlm_sqlcounter/rlm_sqlcounter.c index 25cdc24a02a..fee8783fff8 100644 --- a/src/modules/rlm_sqlcounter/rlm_sqlcounter.c +++ b/src/modules/rlm_sqlcounter/rlm_sqlcounter.c @@ -527,7 +527,7 @@ static int call_env_query_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const * ['%'] = '%', ['\\'] = '\\', }, - }}, t_rules) < 0) { + }}, t_rules, 0) < 0) { talloc_free(query); return -1; } diff --git a/src/modules/rlm_sqlippool/rlm_sqlippool.c b/src/modules/rlm_sqlippool/rlm_sqlippool.c index 2d92fd285ce..1e1357d2dc9 100644 --- a/src/modules/rlm_sqlippool/rlm_sqlippool.c +++ b/src/modules/rlm_sqlippool/rlm_sqlippool.c @@ -612,7 +612,7 @@ static int call_env_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rule */ our_rules.escape.uctx.func.uctx = sql; our_rules.escape.safe_for = (fr_value_box_safe_for_t)sql->driver; - our_rules.literal.safe_for = (fr_value_box_safe_for_t)sql->driver; + our_rules.literals_safe_for = (fr_value_box_safe_for_t)sql->driver; if (tmpl_afrom_substr(ctx, &parsed_tmpl, &FR_SBUFF_IN(cf_pair_value(to_parse), talloc_array_length(cf_pair_value(to_parse)) - 1), diff --git a/src/modules/rlm_winbind/rlm_winbind.c b/src/modules/rlm_winbind/rlm_winbind.c index cbbd4f9dac5..b436a9d27c9 100644 --- a/src/modules/rlm_winbind/rlm_winbind.c +++ b/src/modules/rlm_winbind/rlm_winbind.c @@ -447,7 +447,8 @@ static int domain_call_env_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const if (strlen(cf_pair_value(to_parse)) > 0) { if (tmpl_afrom_substr(ctx, &parsed_tmpl, &FR_SBUFF_IN(cf_pair_value(to_parse), talloc_array_length(cf_pair_value(to_parse)) - 1), - cf_pair_value_quote(to_parse), NULL, t_rules) < 0) return -1; + cf_pair_value_quote(to_parse), 0, + NULL, t_rules) < 0) return -1; } else { /* * If the domain has not been specified, try and find diff --git a/src/tests/modules/ldap/xlat_profile.unlang b/src/tests/modules/ldap/xlat_profile.unlang index 96317e89431..09cd526cc5b 100644 --- a/src/tests/modules/ldap/xlat_profile.unlang +++ b/src/tests/modules/ldap/xlat_profile.unlang @@ -14,4 +14,33 @@ if (!%ldap.profile('cn=suspended,ou=profiles,dc=example,dc=com')) { test_fail } +# Verify that DN based profiles allow dynamic expansions +group { + string user + + &user := 'suspended' + + if (!%ldap.profile("ldap:///cn=%{user},ou=profiles,dc=example,dc=com")) { + test_fail + } + + if (&reply.Reply-Message != 'User-Suspended') { + test_fail + } + + &control := {} + &reply := {} + + if (!%ldap.profile("cn=%{user},ou=profiles,dc=example,dc=com")) { + test_fail + } + + if (&reply.Reply-Message != 'User-Suspended') { + test_fail + } + + &control := {} + &reply := {} +} + test_pass diff --git a/src/tests/modules/rest/rest_xlat.unlang b/src/tests/modules/rest/rest_xlat.unlang index 360583c1219..465e22eee7a 100644 --- a/src/tests/modules/rest/rest_xlat.unlang +++ b/src/tests/modules/rest/rest_xlat.unlang @@ -13,7 +13,7 @@ string result_string &test_string := 'notfound' # Retrieve a plain text file -&result_string := %rest('GET', "http://%{server_host}:%{server_port}/test.txt") +&result_string := %rest('GET', "http://%{server_host}:%rest.escape(%{server_port})/test.txt") if (!(&REST-HTTP-Status-Code == 200)) { test_fail @@ -24,7 +24,7 @@ if (!(&result_string == "Sample text response\n")) { } # Take host from incoming packet -&result_string := %rest("http://%{Login-IP-Host}:%{server_port}/test.txt") +&result_string := %rest("http://%{Login-IP-Host}:%rest.escape(%{server_port})/test.txt") if (!(&REST-HTTP-Status-Code == 200) || !(&result_string == "Sample text response\n")) { test_fail @@ -33,19 +33,19 @@ if (!(&REST-HTTP-Status-Code == 200) || !(&result_string == "Sample text respons # Port is not allowed from incoming packet &result_string := %rest("http://%{server_host}:%{NAS-Port}/test.txt") -if (!(&Module-Failure-Message == "Failed escaping URI: Tainted value not allowed for port") || &result_string) { +if (!(&Module-Failure-Message == "Failed escaping URI: Unsafe input \"8080\" not allowed in URI part port") || &result_string) { test_fail } # Check a "not found" gives a 404 status code -&result_string := %rest('GET', "http://%{server_host}:%{server_port}/%{test_string}") +&result_string := %rest('GET', "http://%{server_host}:%rest.escape(%{server_port})/%{test_string}") if (!(&REST-HTTP-Status-Code == 404)) { test_fail } # GET with URL parameters -&test_string := %rest('GET', "http://%{server_host}:%{server_port}/user/%{User-Name}/mac/%{Called-Station-Id}") +&test_string := %rest('GET', "http://%{server_host}:%rest.escape(%{server_port})/user/%{User-Name}/mac/%{Called-Station-Id}") if (!(&REST-HTTP-Status-Code == 200)) { test_fail @@ -67,7 +67,7 @@ if (!(&control.User-Name == "Bob")) { &control.User-Name := 'dummy' # Directly use json map and prepend the returned value -map json %rest('GET', "http://%{server_host}:%{server_port}/user/%{User-Name}/mac/%{Called-Station-Id}") { +map json %rest('GET', "http://%{server_host}:%rest.escape(%{server_port})/user/%{User-Name}/mac/%{Called-Station-Id}") { &control.User-Name ^= '$.control\.User-Name.value' } @@ -78,7 +78,7 @@ if (!(&control.User-Name[0] == 'Bob') || !(&control.User-Name[1] == 'dummy')) { &test_string := %json.encode('&request.NAS-IP-Address') # POST to https with JSON body data -&result_string := %rest('POST', "https://%{server_host}:%{server_ssl_port}/user/%{User-Name}/mac/%{Called-Station-Id}?section=accounting", %{test_string}) +&result_string := %rest('POST', "https://%{server_host}:%rest.escape(%{server_ssl_port})/user/%{User-Name}/mac/%{Called-Station-Id}?section=accounting", %{test_string}) if (!(&REST-HTTP-Status-Code == 200)) { test_fail @@ -105,7 +105,7 @@ if (!(&control.NAS-IP-Address == "192.168.1.1")) { &result_string := "NAS=%{NAS-IP-Address}&user=%{User-Name}" # POST to https with POST body data -&result_string := %rest('POST', "https://%{server_host}:%{server_ssl_port}/post/test?section=dummy", %{result_string}) +&result_string := %rest('POST', "https://%{server_host}:%rest.escape(%{server_ssl_port})/post/test?section=dummy", %{result_string}) if (!(&REST-HTTP-Status-Code == 200)) { test_fail @@ -122,7 +122,7 @@ group { string arguments string body - map json %rest('POST', "http://%{server_host}:%{server_port}/user/%{User-Name}/reflect/?station=%{Calling-Station-Id}", "{\"test\":\"foo\"}") { + map json %rest('POST', "http://%{server_host}:%rest.escape(%{server_port})/user/%{User-Name}/reflect/?station=%{Calling-Station-Id}", "{\"test\":\"foo\"}") { &headers := '$.reply\.Reply-Message.value[0]' &arguments := '$.reply\.Reply-Message.value[1]' &body := '$.reply\.Reply-Message.value[2]' @@ -160,7 +160,7 @@ group { &test_string := "" - map json %rest(http://%{server_host}:%{server_port}/user/%{User-Name}/reflect/%{test_string}?station=%{User-Name}) { + map json %rest(http://%{server_host}:%rest.escape(%{server_port})/user/%{User-Name}/reflect/%{test_string}?station=%{User-Name}) { &headers := '$.reply\.Reply-Message.value[0]' &arguments := '$.reply\.Reply-Message.value[1]' } @@ -190,7 +190,7 @@ group { group { string arguments - map json %rest("http://%{server_host}:%{server_port}/user/%{User-Name}/reflect/%{NAS-Identifier}?station=%{Called-Station-Id}") { + map json %rest("http://%{server_host}:%rest.escape(%{server_port})/user/%{User-Name}/reflect/%{NAS-Identifier}?station=%{Called-Station-Id}") { &arguments := '$.reply\.Reply-Message.value[1]' } @@ -202,7 +202,7 @@ group { } # Test against endpoint which will time out -&result_string := %restshorttimeout("http://%{server_host}:%{server_port}/delay") +&result_string := %restshorttimeout("http://%{server_host}:%rest.escape(%{server_port})/delay") if (&REST-HTTP-Status-Code) { test_fail