From: Arran Cudbard-Bell Date: Wed, 10 Jan 2024 20:50:01 +0000 (-0500) Subject: Add support for escaping call_env results X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5cd828f2389d127497e52fe56361dd36ae865e11;p=thirdparty%2Ffreeradius-server.git Add support for escaping call_env results Add async support to rlm_linelog Allow call_env code to expand the header instead of doing it the legacy way Ass escaping back --- diff --git a/scripts/ci/ldap-setup.sh b/scripts/ci/ldap-setup.sh index 7dc06082d27..a63d62f2c2f 100755 --- a/scripts/ci/ldap-setup.sh +++ b/scripts/ci/ldap-setup.sh @@ -38,12 +38,23 @@ suffix=$(echo "${0##*/}" | sed -E 's/^ldap(.*)-setup.sh$/\1/') # Kill any old processes [ -e "/tmp/slapd${suffix}.pid" ] && kill $(cat /tmp/slapd${suffix}.pid) +# Set the default basedir based on the suffix of $0 base_dir="/tmp/ldap${suffix}" +# Default FreeRADIUS schemas +default_aux_schema_src=( + "doc/schemas/ldap/openldap/freeradius-policy.schema" + "doc/schemas/ldap/openldap/freeradius-radius.schema" + "doc/schemas/ldap/openldap/freeradius-clients.schema" +) + +# Start with no aux schemas, if the user doesn't specify any we copy the default schemas +aux_schema_src=() + # # Command line options to override the default template values # -while getopts 's:b:' opt; do +while getopts 'b:s:S:' opt; do case "$opt" in b) base_dir="$OPTARG" @@ -53,8 +64,12 @@ while getopts 's:b:' opt; do socket_path="$OPTARG" ;; + S) + schema_src+=("$OPTARG") + ;; + *) - error "Usage: $0 [-b base_dir] [-s socket_path]" + error "Usage: $0 [-b base_dir] [-s socket_path] [-S schema_src]" exit 1 ;; esac @@ -65,6 +80,8 @@ cert_dir="${base_dir}/certs" data_dir="${base_dir}/db" schema_dir="${base_dir}/schema" [ -z ${socket_path+x} ] && socket_path="${base_dir}/socket" +[ ${#aux_schema_src[@]} -eq 0 ] && aux_schema_src=(${default_aux_schema_src[*]}) + socket_url=ldapi://$(urlencode "${socket_path}") debug "base_dir \"${base_dir}\"" @@ -91,25 +108,37 @@ sed -i -e "s/\/var\/lib\/ldap/\/tmp\/ldap${suffix}\/db/" src/tests/salt-test-ser if [ -d "${schema_dir}" ]; then echo "Schema dir already linked" # Debian -elif [ -d /etc/ldap/schema ]; then - ln -fs /etc/ldap/schema "${schema_dir}" +if [ -d /etc/ldap/schema ]; then + schema_src_dir="/etc/ldap/schema" # Symas packages elif [ -d /opt/symas/etc/openldap/schema ]; then - ln -fs /opt/symas/etc/openldap/schema "${schema_dir}" + schema_src_dir="/opt/symas/etc/openldap/schema" # Redhat elif [ -d /etc/openldap/schema ]; then - ln -fs /etc/openldap/schema "${schema_dir}" + schema_src_dir="/etc/openldap/schema" # macOS (homebrew x86) elif [ -d /usr/local/etc/openldap/schema ]; then - ln -fs /usr/local/etc/openldap/schema "${schema_dir}" + schema_src_dir="/usr/local/etc/openldap/schema" # macOS (homebrew ARM) elif [ -d /opt/homebrew/opt/openldap/schema ]; then - ln -fs /opt/homebrew/opt/openldap/schema "${schema_dir}" + schema_src_dir="/opt/homebrew/opt/openldap/schema" else echo "Can't locate OpenLDAP schema dir" exit 1 fi +# Copy all schemas over to the schema dir, so they're all available for inclusion +# we don't overwrite any existing files to avoid overwriting developer changes. +for i in ${schema_src_dir}/*; do + # Only copy in schema files that don't already exist + cp -n "${i}" "${schema_dir}/" +done + +# Copy over the auxilliary schemas +for i in "${aux_schema_src[@]}" do + cp -n "${i}" "${schema_dir}/" +done + # Ensure we have some certs generated make -C raddb/certs @@ -128,7 +157,7 @@ fi # Copy the config over to the base_dir. There seems to be some issues with actions runners # not allowing file access outside of /etc/ldap, so we copy the config to the specified base_dir. -cp "scripts/ci/ldap/slapd${suffix}.conf" "${base_dir}/slapd.conf" +cp -n "scripts/ci/ldap/slapd${suffix}.conf" "${base_dir}/slapd.conf" # Start slapd slapd -d any -h "ldap://127.0.0.1:${ldap_port}/ ldaps://127.0.0.1:${ldaps_port}/ ${socket_url}" -f "${base_dir}/slapd.conf" 2>&1 > ${base_dir}/slapd.log & diff --git a/src/lib/server/tmpl.h b/src/lib/server/tmpl.h index 19be4209f9c..d4c58b47d87 100644 --- a/src/lib/server/tmpl.h +++ b/src/lib/server/tmpl.h @@ -75,6 +75,55 @@ extern "C" { #include #include +#include + +/** When to apply escaping + */ +typedef enum { + TMPL_ESCAPE_PRE_CONCAT = 0, //!< Pre-concatenation escaping is useful for + ///< DSLs where elements of the expansion are + ///< static, specified by the user, and other parts + ///< are dynamic, which may or may not need to be + ///< escaped based on which "safe" flags are applied + ///< to the box. When using this mode, the escape + ///< function must be able to handle boxes of a type + ///< other than the cast type, possibly performing + ///< a cast itself if necessary. + + TMPL_ESCAPE_POST_CONCAT //!< Post-concatenation escaping is useful for when + ///< we don't want to allow the user to bypass escaping + ///< for any part of the value. + ///< Here all boxes are guaranteed to be of the cast type. +} tmpl_escape_mode_t; + +/** Escaping rules for tmpls + * + */ +typedef struct { + fr_value_box_escape_t func; //!< How to escape when returned from evaluation. + ///< Currently only used for async evaluation. + void *uctx; //!< User context for escape function. + + tmpl_escape_mode_t mode; //!< Whether to apply escape function after + ///< concatenation, i.e. to the final output + ///< of the tmpl. If false, then the escaping + ///< is performed on each value box + ///< individually prior to concatenation and + ///< prior to casting. + ///< If no concatenation is performed, then + ///< the escaping is performed on each box individually. + +} tmpl_escape_t; + +/** See if we should perform output escaping before concatenation + * + */ +#define tmpl_escape_pre_concat(_tmpl) ((_tmpl)->rules.escape.func && ((_tmpl)->rules.escape.mode == TMPL_ESCAPE_PRE_CONCAT)) + +/** See if we should perform output escaping after concatenation + * + */ +#define tmpl_escape_post_concat(_tmpl) ((_tmpl)->rules.escape.func && ((_tmpl)->rules.escape.mode == TMPL_ESCAPE_POST_CONCAT)) /** The maximum number of request references allowed * @@ -349,6 +398,8 @@ struct tmpl_rules_s { bool at_runtime; //!< Produce an ephemeral/runtime tmpl. ///< Instantiated xlats are not added to the global ///< trees, regexes are not JIT'd. + + 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 @@ -578,9 +629,9 @@ struct tmpl_s { }; } data; - tmpl_rules_t _CONST rules; //!< The rules that were used when creating the tmpl. - ///< These are useful for multiple resolution passes as - ///< they ensure the correct parsing rules are applied. + tmpl_rules_t _CONST rules; //!< The rules that were used when creating the tmpl. + ///< These are useful for multiple resolution passes as + ///< they ensure the correct parsing rules are applied. }; /** Describes the current extents of a pair tree in relation to the tree described by a tmpl_t @@ -1148,6 +1199,8 @@ void tmpl_set_name(tmpl_t *vpt, fr_token_t quote, char const *name, ssize_t le void tmpl_set_dict_def(tmpl_t *vpt, fr_dict_t const *dict) CC_HINT(nonnull); +void tmpl_set_escape(tmpl_t *vpt, tmpl_escape_t const *escape) CC_HINT(nonnull); + void tmpl_set_xlat(tmpl_t *vpt, xlat_exp_head_t *xlat) CC_HINT(nonnull); int tmpl_afrom_value_box(TALLOC_CTX *ctx, tmpl_t **out, fr_value_box_t *data, bool steal) CC_HINT(nonnull); diff --git a/src/lib/server/tmpl_eval.c b/src/lib/server/tmpl_eval.c index acfa32df0fc..041e08434d8 100644 --- a/src/lib/server/tmpl_eval.c +++ b/src/lib/server/tmpl_eval.c @@ -1276,6 +1276,7 @@ done: int tmpl_eval_cast_in_place(fr_value_box_list_t *list, tmpl_t const *vpt) { fr_type_t cast = tmpl_rules_cast(vpt); + bool did_concat = false; if (fr_type_is_structural(cast)) { fr_strerror_printf("Cannot cast to structural type '%s'", fr_type_to_str(cast)); @@ -1299,6 +1300,10 @@ int tmpl_eval_cast_in_place(fr_value_box_list_t *list, tmpl_t const *vpt) vb = fr_value_box_list_head(list); if (!vb) return 0; + if (tmpl_escape_pre_concat(vpt)) { + fr_value_box_list_escape_in_place(list, vpt->rules.escape.func, vpt->rules.escape.uctx); + } + slen = fr_value_box_list_concat_in_place(vb, vb, list, FR_TYPE_STRING, FR_VALUE_BOX_LIST_FREE_BOX, true, SIZE_MAX); if (slen < 0) return -1; @@ -1312,6 +1317,8 @@ int tmpl_eval_cast_in_place(fr_value_box_list_t *list, tmpl_t const *vpt) * result. */ if (fr_type_is_null(cast) || fr_type_is_string(cast)) return 0; + + did_concat = true; } break; @@ -1329,6 +1336,17 @@ int tmpl_eval_cast_in_place(fr_value_box_list_t *list, tmpl_t const *vpt) fr_value_box_list_foreach_safe(list, vb) { if (fr_value_box_cast_in_place(vb, vb, cast, NULL) < 0) return -1; }} + + /* + * ...and finally, apply the escape function + * if necessary. This is done last so that + * the escape function gets boxes of the type + * it expects. + */ + if ((!did_concat && tmpl_escape_pre_concat(vpt)) || tmpl_escape_post_concat(vpt)) { + fr_value_box_list_escape_in_place(list, vpt->rules.escape.func, vpt->rules.escape.uctx); + } + VALUE_BOX_LIST_VERIFY(list); return 0; diff --git a/src/lib/server/tmpl_tokenize.c b/src/lib/server/tmpl_tokenize.c index 5d382f012d1..e89bbe201ae 100644 --- a/src/lib/server/tmpl_tokenize.c +++ b/src/lib/server/tmpl_tokenize.c @@ -810,6 +810,16 @@ void tmpl_set_dict_def(tmpl_t *vpt, fr_dict_t const *dict) vpt->rules.attr.dict_def = dict; } +/** Set escape parameters for the tmpl output + * + * @parma[in] vpt to alter. + * @param[in] escape to set. + */ +void tmpl_set_escape(tmpl_t *vpt, tmpl_escape_t const *escape) +{ + vpt->rules.escape = *escape; +} + /** Change the default dictionary in the tmpl's resolution rules * * @param[in] vpt to alter. @@ -2387,16 +2397,19 @@ static fr_slen_t tmpl_afrom_value_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff fr_sbuff_t our_in = FR_SBUFF(in); fr_value_box_t tmp; tmpl_t *vpt; + fr_type_t cast = FR_TYPE_STRING; + + if (!fr_type_is_null(t_rules->cast)) cast = t_rules->cast; - if (!fr_type_is_leaf(t_rules->cast)) { + if (!fr_type_is_leaf(cast)) { fr_strerror_printf("%s is not a valid cast type", - fr_type_to_str(t_rules->cast)); + fr_type_to_str(cast)); FR_SBUFF_ERROR_RETURN(&our_in); } vpt = tmpl_alloc_null(ctx); if (fr_value_box_from_substr(vpt, &tmp, - t_rules->cast, allow_enum ? t_rules->enumv : NULL, + cast, allow_enum ? t_rules->enumv : NULL, &our_in, p_rules, false) < 0) { talloc_free(vpt); FR_SBUFF_ERROR_RETURN(&our_in); @@ -2408,7 +2421,7 @@ static fr_slen_t tmpl_afrom_value_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff *out = vpt; - if (tmpl_rules_cast(vpt) == tmpl_value_type(vpt)) vpt->rules.cast = FR_TYPE_NULL; + if (cast == tmpl_value_type(vpt)) vpt->rules.cast = FR_TYPE_NULL; TMPL_VERIFY(vpt); diff --git a/src/lib/unlang/call_env.c b/src/lib/unlang/call_env.c index cbc7232606b..78c28e7c239 100644 --- a/src/lib/unlang/call_env.c +++ b/src/lib/unlang/call_env.c @@ -424,6 +424,7 @@ static int call_env_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *parsed, char if (t_rules) { our_rules.parent = t_rules->parent; our_rules.attr.dict_def = t_rules->attr.dict_def; + our_rules.escape = rule->pair.escape; /* Escape rules will now get embedded in the tmpl_t and used at evaluation */ } our_rules.attr.list_def = request_attr_request; diff --git a/src/lib/unlang/call_env.h b/src/lib/unlang/call_env.h index 5e0c85e142e..eaf2d9ee2ad 100644 --- a/src/lib/unlang/call_env.h +++ b/src/lib/unlang/call_env.h @@ -188,6 +188,8 @@ struct call_env_parser_s { call_env_parse_type_t type; //!< What type of output the parsing phase is expected to produce. } parsed; + + tmpl_escape_t escape; //!< Escape method to use when evaluating tmpl_t. } pair; struct { diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index 081b3421b35..2603baabd5d 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -516,15 +516,17 @@ static int unlang_fixup_map(map_t *map, UNUSED void *ctx) } switch (map->rhs->type) { - case TMPL_TYPE_DATA_UNRESOLVED: case TMPL_TYPE_XLAT_UNRESOLVED: + case TMPL_TYPE_DATA_UNRESOLVED: + case TMPL_TYPE_DATA: case TMPL_TYPE_XLAT: case TMPL_TYPE_ATTR: case TMPL_TYPE_EXEC: break; default: - cf_log_err(map->ci, "Right side of map must be an attribute, literal, xlat or exec"); + cf_log_err(map->ci, "Right side of map must be an attribute, literal, xlat or exec, got type %s", + tmpl_type_to_str(map->rhs->type)); return -1; } @@ -1438,7 +1440,8 @@ static int unlang_fixup_edit(map_t *map, void *ctx) break; default: - cf_log_err(map->ci, "Right side of map must be an attribute, literal, xlat or exec"); + cf_log_err(map->ci, "Right side of map must be an attribute, literal, xlat or exec, got type %s", + tmpl_type_to_str(map->rhs->type)); return -1; } diff --git a/src/lib/unlang/tmpl.c b/src/lib/unlang/tmpl.c index 6877a98becb..51292c33b85 100644 --- a/src/lib/unlang/tmpl.c +++ b/src/lib/unlang/tmpl.c @@ -23,12 +23,12 @@ * @copyright 2021 Arran Cudbard-Bell * @copyright 2020 Network RADIUS SAS (legal@networkradius.com) */ - RCSID("$Id$") #include #include #include +#include #include "tmpl_priv.h" #include @@ -253,8 +253,8 @@ push: * in which case the result is discarded. * @param[in] request The current request. * @param[in] tmpl the tmpl to expand - * @param[in] args where the status of exited programs will be stored. - * Used only for #TMPL_TYPE_EXEC. + * @param[in] args additional controls for expanding #TMPL_TYPE_EXEC, + * and where the status of exited programs will be stored. */ int unlang_tmpl_push(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request, tmpl_t const *tmpl, unlang_tmpl_args_t *args) @@ -286,7 +286,7 @@ int unlang_tmpl_push(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *reque }; if (tmpl_needs_resolving(tmpl)) { - REDEBUG("Expansion %s needs to be resolved before it is used.", tmpl->name); + REDEBUG("Expansion \"%pV\" needs to be resolved before it is used", fr_box_strvalue_len(tmpl->name, tmpl->len)); return -1; } diff --git a/src/lib/util/talloc.h b/src/lib/util/talloc.h index 582e493a6b8..1d7cc13b2df 100644 --- a/src/lib/util/talloc.h +++ b/src/lib/util/talloc.h @@ -204,6 +204,7 @@ void **talloc_array_null_strip(void **array); fr_slen_t talloc_array_concat(fr_sbuff_t *out, char const * const *array, char const *sep); + /** Free const'd memory * * @param[in] ptr to free. @@ -269,6 +270,16 @@ static inline void *_talloc_list_get_type_abort(void *head, size_t offset, char # define talloc_get_type_abort_const talloc_get_type_abort #endif +/** Returns the length of a talloc array containing a string + * + * @param[in] s to return the length of. + */ +static inline size_t talloc_strlen(char const *s) +{ + char const *our_s = talloc_get_type_abort_const(s, char); + return talloc_array_length(our_s) - 1; +} + TALLOC_CTX *talloc_autofree_context_global(void); TALLOC_CTX *talloc_autofree_context_thread_local(void); diff --git a/src/lib/util/value.c b/src/lib/util/value.c index d35b8f696a1..91825f35791 100644 --- a/src/lib/util/value.c +++ b/src/lib/util/value.c @@ -3973,6 +3973,8 @@ int fr_value_box_asprintf(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t c } /** Assign a buffer containing a nul terminated string to a box, but don't copy it + * + * @note Input string will not be duplicated. * * @param[in] dst to assign string to. * @param[in] enumv Aliases for values. @@ -3987,6 +3989,21 @@ void fr_value_box_strdup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enum dst->vb_length = strlen(src); } +/** Free the existing buffer (if talloced) associated with the valuebox, and replace it with a new one + * + * @note Input string will not be duplicated. + * + * @param[in] vb to replace string in. + * @param[in] src to assign string from. + * @param[in] len of src. + */ +void fr_value_box_strdup_shallow_replace(fr_value_box_t *vb, char const *src, ssize_t len) +{ + fr_value_box_clear_value(vb); + vb->vb_strvalue = src; + vb->vb_length = len < 0 ? strlen(src) : len; +} + /** Alloc and assign an empty \0 terminated string to a #fr_value_box_t * * @param[in] ctx to allocate any new buffers in. @@ -5860,6 +5877,42 @@ int fr_value_box_list_concat_in_place(TALLOC_CTX *ctx, return 0; } +/** Escape a single value box in place + * + * @note Applies recurssively to the children of group boxes. + * + * @param[in] vb to escape. + * @param[in] escape function to apply to the value box. + * @param[in] uctx user context to pass to the escape function. + */ +void fr_value_box_escape_in_place(fr_value_box_t *vb, fr_value_box_escape_t escape, void *uctx) +{ + switch (vb->type) { + case FR_TYPE_GROUP: + fr_value_box_list_escape_in_place(&vb->vb_group, escape, uctx); + break; + + default: + escape(vb, uctx); + break; + } +} + +/** Escape a list of value boxes in place + * + * @note Applies recurssively to the children of group boxes. + * + * @param[in] list to escape. + * @param[in] escape function to apply to the value box. + * @param[in] uctx user context to pass to the escape function. + */ +void fr_value_box_list_escape_in_place(fr_value_box_list_t *list, fr_value_box_escape_t escape, void *uctx) +{ + fr_value_box_list_foreach(list, vb) { + fr_value_box_escape_in_place(vb, escape, uctx); + } +} + /** Removes a single layer of nesting, moving all children into the parent list * * @param[in] ctx to reparent children in if steal is true. diff --git a/src/lib/util/value.h b/src/lib/util/value.h index 46f2680010d..dea9ccfa618 100644 --- a/src/lib/util/value.h +++ b/src/lib/util/value.h @@ -616,6 +616,26 @@ fr_value_box_t *_fr_value_box_alloc(NDEBUG_LOCATION_ARGS TALLOC_CTX *ctx, fr_typ /** @} */ +/** @name Escape functions + * + * Apply a tranformation to a value box or list of value boxes. + * + * @{ + */ + + /** Escape a value box + * + * @param[in] vb to escape. + * @param[in] uctx user context to pass to the escape function. + */ +typedef void (*fr_value_box_escape_t)(fr_value_box_t *vb, void *uctx); + +void fr_value_box_escape_in_place(fr_value_box_t *vb, fr_value_box_escape_t escape, void *uctx) + CC_HINT(nonnull(1,2)); +void fr_value_box_list_escape_in_place(fr_value_box_list_t *list, fr_value_box_escape_t escape, void *uctx) + CC_HINT(nonnull(1,2)); +/** @} */ + /** @name Convenience functions * * These macros and inline functions simplify working @@ -1072,6 +1092,9 @@ int fr_value_box_asprintf(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t void fr_value_box_strdup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted) CC_HINT(nonnull(1,3)); + +void fr_value_box_strdup_shallow_replace(fr_value_box_t *vb, char const *src, ssize_t len) + CC_HINT(nonnull); /** @} */ /** @name Assign and manipulate binary-safe strings diff --git a/src/modules/rlm_linelog/rlm_linelog.c b/src/modules/rlm_linelog/rlm_linelog.c index 01553c5eb03..efa6585bc79 100644 --- a/src/modules/rlm_linelog/rlm_linelog.c +++ b/src/modules/rlm_linelog/rlm_linelog.c @@ -27,9 +27,18 @@ RCSID("$Id$") #include #include #include +#include +#include +#include +#include +#include + #include #include #include +#include +#include +#include #include @@ -54,6 +63,8 @@ RCSID("$Id$") #include +static void linelog_escape_func(fr_value_box_t *vb, UNUSED void *uctx); + typedef enum { LINELOG_DST_INVALID = 0, LINELOG_DST_FILE, //!< Log to a file. @@ -188,30 +199,29 @@ static const conf_parser_t module_config[] = { }; typedef struct { - tmpl_t const *fmt_tmpl; - fr_value_box_t reference; - tmpl_t const *ref_tmpl; - tmpl_t const *head_tmpl; + tmpl_t *log_src; //!< Source of log messages. + + fr_value_box_t *log_ref; //!< Path to a #CONF_PAIR (to use as the source of + ///< log messages). + + fr_value_box_t *log_head; //!< Header to add to each new log file. + } linelog_call_env_t; static const call_env_method_t linelog_method_env = { FR_CALL_ENV_METHOD_OUT(linelog_call_env_t), .env = (call_env_parser_t[]) { - { FR_CALL_ENV_PARSE_ONLY_OFFSET("format", FR_TYPE_STRING, CALL_ENV_FLAG_NONE, linelog_call_env_t, fmt_tmpl) }, - { FR_CALL_ENV_PARSE_OFFSET("reference", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, linelog_call_env_t, reference, ref_tmpl) }, - { FR_CALL_ENV_PARSE_ONLY_OFFSET("header", FR_TYPE_STRING, CALL_ENV_FLAG_NONE, linelog_call_env_t, head_tmpl) }, + { FR_CALL_ENV_PARSE_ONLY_OFFSET("format", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT | CALL_ENV_FLAG_PARSE_ONLY, linelog_call_env_t, log_src), .pair.escape.func = linelog_escape_func }, + { FR_CALL_ENV_OFFSET("reference",FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, linelog_call_env_t, log_ref), .pair.escape.func = linelog_escape_func }, + { FR_CALL_ENV_OFFSET("header", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, linelog_call_env_t, log_head), .pair.escape.func = linelog_escape_func }, CALL_ENV_TERMINATOR } }; -typedef struct { - tmpl_t const *head_tmpl; -} linelog_xlat_call_env_t; - static const call_env_method_t linelog_xlat_method_env = { - FR_CALL_ENV_METHOD_OUT(linelog_xlat_call_env_t), + FR_CALL_ENV_METHOD_OUT(linelog_call_env_t), .env = (call_env_parser_t[]) { - { FR_CALL_ENV_PARSE_ONLY_OFFSET("header", FR_TYPE_STRING, CALL_ENV_FLAG_NONE, linelog_xlat_call_env_t, head_tmpl) }, + { FR_CALL_ENV_OFFSET("header", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, linelog_call_env_t, log_head), .pair.escape.func = linelog_escape_func }, CALL_ENV_TERMINATOR } }; @@ -309,28 +319,19 @@ static void *mod_conn_create(TALLOC_CTX *ctx, void *instance, fr_time_delta_t ti * - Return is escaped as ``\\r``. * - All other unprintables are escaped as @verbatim \ @endverbatim. * - * @param request The current request. - * @param out Where to write the escaped string. - * @param outlen Length of the output buffer. - * @param in String to escape. - * @param arg unused. + * @param vb Value box to escape. */ /* * Escape unprintable characters. */ -static size_t linelog_escape_func(UNUSED request_t *request, - char *out, size_t outlen, char const *in, - UNUSED void *arg) +static void linelog_escape_func(fr_value_box_t *vb, UNUSED void *uctx) { - if (outlen == 0) return 0; - - if (outlen == 1) { - *out = '\0'; - return 0; - } + char *escaped; + if (vb->vb_length == 0) return; - return fr_snprint(out, outlen, in, -1, 0); + MEM(escaped = fr_asprint(vb, vb->vb_strvalue, vb->vb_length, 0)); + fr_value_box_strdup_shallow_replace(vb, escaped, talloc_strlen(escaped)); } static void linelog_hexdump(request_t *request, struct iovec *vector_p, size_t vector_len, char const *msg) @@ -343,8 +344,7 @@ static void linelog_hexdump(request_t *request, struct iovec *vector_p, size_t v RHEXDUMP3(fr_dbuff_start(agg), fr_dbuff_used(agg), "%s", msg); } -static int linelog_write(rlm_linelog_t const *inst, request_t *request, struct iovec *vector_p, size_t vector_len, - bool with_delim, tmpl_t const *head_tmpl) +static int linelog_write(rlm_linelog_t const *inst, linelog_call_env_t const *call_env, request_t *request, struct iovec *vector_p, size_t vector_len, bool with_delim) { int ret = 0; linelog_conn_t *conn; @@ -360,7 +360,6 @@ static int linelog_write(rlm_linelog_t const *inst, request_t *request, struct i char path[2048]; off_t offset; char *p; - ssize_t slen; if (xlat_eval(path, sizeof(path), request, inst->file.name, inst->file.escape_func, NULL) < 0) { ret = -1; @@ -395,22 +394,12 @@ static int linelog_write(rlm_linelog_t const *inst, request_t *request, struct i * of the file then expand the format and write it out before * writing the actual log entries. */ - if (head_tmpl && (offset == 0)) { - char head[4096]; - char *head_value; + if (call_env->log_head && (offset == 0)) { struct iovec head_vector_s[2]; size_t head_vector_len; - slen = tmpl_expand(&head_value, head, sizeof(head), request, head_tmpl, - linelog_escape_func, NULL); - if (slen < 0) { - exfile_close(inst->file.ef, fd); - ret = -1; - goto finish; - } - - memcpy(&head_vector_s[0].iov_base, &head_value, sizeof(head_vector_s[0].iov_base)); - head_vector_s[0].iov_len = slen; + memcpy(&head_vector_s[0].iov_base, &call_env->log_head->vb_strvalue, sizeof(head_vector_s[0].iov_base)); + head_vector_s[0].iov_len = call_env->log_head->vb_length; if (!with_delim) { head_vector_len = 1; @@ -572,13 +561,14 @@ static xlat_action_t linelog_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *args) { - rlm_linelog_t const *inst = talloc_get_type_abort_const(xctx->mctx->inst->data, rlm_linelog_t); - linelog_xlat_call_env_t *call_env = talloc_get_type_abort(xctx->env_data, linelog_xlat_call_env_t); - struct iovec vector[2]; - size_t i = 0; - bool with_delim; - fr_value_box_t *msg, *wrote; - ssize_t slen; + rlm_linelog_t const *inst = talloc_get_type_abort_const(xctx->mctx->inst->data, rlm_linelog_t); + linelog_call_env_t const *call_env = talloc_get_type_abort(xctx->env_data, linelog_call_env_t); + + struct iovec vector[2]; + size_t i = 0; + bool with_delim; + fr_value_box_t *msg, *wrote; + ssize_t slen; XLAT_ARGS(args, &msg); @@ -592,7 +582,7 @@ static xlat_action_t linelog_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, vector[i].iov_len = inst->delimiter_len; i++; } - slen = linelog_write(inst, request, vector, i, with_delim, call_env->head_tmpl); + slen = linelog_write(inst, call_env, request, vector, i, with_delim); if (slen < 0) return XLAT_ACTION_FAIL; MEM(wrote = fr_value_box_alloc(ctx, FR_TYPE_SIZE, NULL)); @@ -603,6 +593,67 @@ static xlat_action_t linelog_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, return XLAT_ACTION_DONE; } +typedef struct { + fr_value_box_list_t expanded; //!< The result of expanding the fmt tmpl + bool with_delim; //!< Whether to add a delimiter +} rlm_linelog_rctx_t; + +static unlang_action_t CC_HINT(nonnull) mod_do_linelog_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + rlm_linelog_t const *inst = talloc_get_type_abort_const(mctx->inst->data, rlm_linelog_t); + linelog_call_env_t const *call_env = talloc_get_type_abort(mctx->env_data, linelog_call_env_t); + rlm_linelog_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, rlm_linelog_rctx_t); + struct iovec *vector; + struct iovec *vector_p; + size_t vector_len; + + vector_len = fr_value_box_list_num_elements(&rctx->expanded); + if (vector_len == 0) { + RDEBUG2("No data to write"); + RETURN_MODULE_NOOP; + } + + /* + * Add extra space for the delimiter + */ + if (rctx->with_delim) vector_len *= 2; + + MEM(vector = vector_p = talloc_array(rctx, struct iovec, vector_len)); + fr_value_box_list_foreach(&rctx->expanded, vb) { + switch(vb->type) { + default: + if (unlikely(fr_value_box_cast_in_place(rctx, vb, FR_TYPE_STRING, vb->enumv) < 0)) { + REDEBUG("Failed casting value to string"); + RETURN_MODULE_FAIL; + } + FALL_THROUGH; + + case FR_TYPE_STRING: + vector_p->iov_base = UNCONST(char *, vb->vb_strvalue); + vector_p->iov_len = vb->vb_length; + vector_p++; + break; + + case FR_TYPE_OCTETS: + vector_p->iov_base = UNCONST(char *, vb->vb_octets); + vector_p->iov_len = vb->vb_length; + vector_p++; + break; + } + + /* + * Don't add the delim for the last element + */ + if (rctx->with_delim) { + memcpy(&vector_p->iov_base, &(inst->delimiter), sizeof(vector_p->iov_base)); + vector_p->iov_len = inst->delimiter_len; + vector_p++; + } + } + + RETURN_MODULE_RCODE(linelog_write(inst, call_env, request, vector, vector_len, rctx->with_delim) < 0 ? RLM_MODULE_FAIL : RLM_MODULE_OK); +} + /** Write a linelog message * * Write a log message to syslog or a flat file. @@ -617,24 +668,18 @@ static xlat_action_t linelog_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, static unlang_action_t CC_HINT(nonnull) mod_do_linelog(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) { rlm_linelog_t const *inst = talloc_get_type_abort_const(mctx->inst->data, rlm_linelog_t); - linelog_call_env_t *call_env = talloc_get_type_abort(mctx->env_data, linelog_call_env_t); + linelog_call_env_t const *call_env = talloc_get_type_abort(mctx->env_data, linelog_call_env_t); CONF_SECTION *conf = mctx->inst->conf; char buff[4096]; - char *p = buff; - char const *value; - tmpl_t empty, *vpt = NULL; - tmpl_t const *vpt_p = NULL; - rlm_rcode_t rcode = RLM_MODULE_OK; + tmpl_t empty, *vpt = NULL, *vpt_p = NULL; ssize_t slen; - - struct iovec vector_s[2]; - struct iovec *vector = NULL, *vector_p; - size_t vector_len; bool with_delim; - if (!call_env->fmt_tmpl && !call_env->ref_tmpl) { + TALLOC_CTX *frame_ctx = unlang_interpret_frame_talloc_ctx(request); + + if (!call_env->log_src && !call_env->log_ref) { cf_log_err(conf, "A 'format', or 'reference' configuration item must be set to call this module"); RETURN_MODULE_FAIL; } @@ -644,15 +689,16 @@ static unlang_action_t CC_HINT(nonnull) mod_do_linelog(rlm_rcode_t *p_result, mo buff[2] = '\0'; /* - * 'reference' expanded to a value. Use as a config path, using the module + * Expand log_ref to a config path, using the module * configuration section as the root. */ - if (call_env->reference.type == FR_TYPE_STRING) { + if (call_env->log_ref) { CONF_ITEM *ci; CONF_PAIR *cp; char const *tmpl_str; - strlcpy(buff + 1, call_env->reference.vb_strvalue, sizeof(buff) - 1); + memcpy(buff + 1, call_env->log_ref->vb_strvalue, call_env->log_ref->vb_length); + buff[call_env->log_ref->vb_length + 1] = '\0'; if (buff[1] == '.') p++; @@ -689,7 +735,7 @@ static unlang_action_t CC_HINT(nonnull) mod_do_linelog(rlm_rcode_t *p_result, mo * Alloc a template from the value of the CONF_PAIR * using request as the context (which will hopefully avoid an alloc). */ - slen = tmpl_afrom_substr(request, &vpt, + slen = tmpl_afrom_substr(frame_ctx, &vpt, &FR_SBUFF_IN(tmpl_str, talloc_array_length(tmpl_str) - 1), cf_pair_value_quote(cp), NULL, @@ -709,13 +755,18 @@ static unlang_action_t CC_HINT(nonnull) mod_do_linelog(rlm_rcode_t *p_result, mo REMARKER(tmpl_str, -slen, "%s", fr_strerror()); RETURN_MODULE_FAIL; } + if (tmpl_resolve(vpt, NULL) < 0) { + RPERROR("Runtime resolution of tmpl failed"); + talloc_free(vpt); + RETURN_MODULE_FAIL; + } vpt_p = vpt; } else { default_msg: /* * Use the default format string */ - if (!call_env->fmt_tmpl) { + if (!call_env->log_src) { RDEBUG2("No default message configured"); RETURN_MODULE_NOOP; } @@ -723,7 +774,7 @@ static unlang_action_t CC_HINT(nonnull) mod_do_linelog(rlm_rcode_t *p_result, mo * Use the pre-parsed format template */ RDEBUG2("Using default message"); - vpt_p = call_env->fmt_tmpl; + vpt_p = call_env->log_src; } build_vector: @@ -740,8 +791,11 @@ build_vector: tmpl_dcursor_ctx_t cc; fr_pair_t *vp; int alloced = VECTOR_INCREMENT, i; + struct iovec *vector = NULL, *vector_p; + size_t vector_len; + rlm_rcode_t rcode = RLM_MODULE_OK; - MEM(vector = talloc_array(request, struct iovec, alloced)); + MEM(vector = talloc_array(frame_ctx, struct iovec, alloced)); for (vp = tmpl_dcursor_init(NULL, NULL, &cc, &cursor, request, vpt_p), i = 0; vp; vp = fr_dcursor_next(&cursor), i++) { @@ -749,7 +803,7 @@ build_vector: if ((with_delim && ((i + 1) >= alloced)) || (i >= alloced)) { alloced += VECTOR_INCREMENT; - MEM(vector = talloc_realloc(request, vector, struct iovec, alloced)); + MEM(vector = talloc_realloc(frame_ctx, vector, struct iovec, alloced)); } switch (vp->vp_type) { @@ -777,47 +831,34 @@ build_vector: tmpl_dcursor_clear(&cc); vector_p = vector; vector_len = i; - } - break; - /* - * Log a single thing. - */ - default: - slen = tmpl_expand(&value, buff, sizeof(buff), request, vpt_p, linelog_escape_func, NULL); - if (slen < 0) { - rcode = RLM_MODULE_FAIL; - goto finish; - } - - /* iov_base is not declared as const *sigh* */ - memcpy(&vector_s[0].iov_base, &value, sizeof(vector_s[0].iov_base)); - vector_s[0].iov_len = slen; - - if (!with_delim) { - vector_len = 1; + if (vector_len == 0) { + RDEBUG2("No data to write"); + rcode = RLM_MODULE_NOOP; } else { - memcpy(&vector_s[1].iov_base, &(inst->delimiter), sizeof(vector_s[1].iov_base)); - vector_s[1].iov_len = inst->delimiter_len; - vector_len = 2; + rcode = linelog_write(inst, call_env, request, vector_p, vector_len, with_delim) < 0 ? RLM_MODULE_FAIL : RLM_MODULE_OK; } - vector_p = &vector_s[0]; - } + talloc_free(vpt); + talloc_free(vector); - if (vector_len == 0) { - RDEBUG2("No data to write"); - rcode = RLM_MODULE_NOOP; - goto finish; + RETURN_MODULE_RCODE(rcode); } - rcode = linelog_write(inst, request, vector_p, vector_len, with_delim, call_env->head_tmpl) < 0 ? RLM_MODULE_FAIL : RLM_MODULE_OK; + /* + * Log a format string. We need to yield as this might contain asynchronous expansions. + */ + default: + { + rlm_linelog_rctx_t *rctx; -finish: - talloc_free(vpt); - talloc_free(vector); + MEM(rctx = talloc(frame_ctx, rlm_linelog_rctx_t)); + fr_value_box_list_init(&rctx->expanded); + rctx->with_delim = with_delim; - RETURN_MODULE_RCODE(rcode); + return unlang_module_yield_to_tmpl(rctx, &rctx->expanded, request, vpt_p, NULL, mod_do_linelog_resume, NULL, 0, rctx); + } + } } @@ -966,7 +1007,7 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) xlat = xlat_func_register_module(inst, mctx, mctx->inst->name, linelog_xlat, FR_TYPE_SIZE); xlat_func_mono_set(xlat, linelog_xlat_args); - xlat_func_call_env_set(xlat, &linelog_xlat_method_env); + xlat_func_call_env_set(xlat, &linelog_xlat_method_env ); return 0; } @@ -986,8 +1027,7 @@ module_rlm_t rlm_linelog = { .detach = mod_detach }, .method_names = (module_method_name_t[]){ - { .name1 = CF_IDENT_ANY, .name2 = CF_IDENT_ANY, .method = mod_do_linelog, - .method_env = &linelog_method_env }, + { .name1 = CF_IDENT_ANY, .name2 = CF_IDENT_ANY, .method = mod_do_linelog, .method_env = &linelog_method_env }, MODULE_NAME_TERMINATOR } };