]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add support for escaping call_env results
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 10 Jan 2024 20:50:01 +0000 (15:50 -0500)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 10 Jan 2024 20:50:01 +0000 (15:50 -0500)
Add async support to rlm_linelog

Allow call_env code to expand the header instead of doing it the legacy way

Ass escaping back

12 files changed:
scripts/ci/ldap-setup.sh
src/lib/server/tmpl.h
src/lib/server/tmpl_eval.c
src/lib/server/tmpl_tokenize.c
src/lib/unlang/call_env.c
src/lib/unlang/call_env.h
src/lib/unlang/compile.c
src/lib/unlang/tmpl.c
src/lib/util/talloc.h
src/lib/util/value.c
src/lib/util/value.h
src/modules/rlm_linelog/rlm_linelog.c

index 7dc06082d2755eed458bd75c331beede924ebdd3..a63d62f2c2f49001055c62c2d5260d9ce591ccdc 100755 (executable)
@@ -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 &
index 19be4209f9cdb939479e5ad081aa7af05e248778..d4c58b47d87ebc7e2b36c0a3e9c4eeec1eeed037 100644 (file)
@@ -75,6 +75,55 @@ extern "C" {
 
 #include <freeradius-devel/util/table.h>
 #include <freeradius-devel/util/dlist.h>
+#include <freeradius-devel/util/value.h>
+
+/** 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);
index acfa32df0fc9daabb4c3b9eee708ca16ac43fcf9..041e08434d80bb37620867cbd84fff694d08bffb 100644 (file)
@@ -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;
index 5d382f012d1606e0373bdcf7cf54bc68c6d1c559..e89bbe201aebde152121bd8feb6f2c7015b902fc 100644 (file)
@@ -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);
 
index cbc7232606badabd08e01069cba9c1099ac552d1..78c28e7c239c0d66acd98c93082dc3eb8c36fb93 100644 (file)
@@ -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;
index 5e0c85e142e0e43d2b56f9c741955b4ad55d4cc3..eaf2d9ee2adb0ccf784fd99fbc587699f6eb5137 100644 (file)
@@ -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 {
index 081b3421b35d8d9bf5cd4dc18d11d413cf0db82e..2603baabd5d92c69b1271f3b7504fca040cfe3b4 100644 (file)
@@ -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;
        }
 
index 6877a98becbda702dd5f3cd83b6980cf42b91548..51292c33b859059bb086032cc827c03544779b2f 100644 (file)
  * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
  * @copyright 2020 Network RADIUS SAS (legal@networkradius.com)
  */
-
 RCSID("$Id$")
 
 #include <freeradius-devel/unlang/tmpl.h>
 #include <freeradius-devel/server/exec.h>
 #include <freeradius-devel/util/syserror.h>
+#include <freeradius-devel/util/value.h>
 #include "tmpl_priv.h"
 #include <signal.h>
 
@@ -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;
        }
 
index 582e493a6b8335086e0f4e259a906be131c91ff4..1d7cc13b2df104e1dda30f82c3a15c5816e72fc9 100644 (file)
@@ -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);
 
index d35b8f696a1fd253730b2e7e1ac9119514a5c676..91825f35791b57d636c38ce89cbde1dafc1fc0c8 100644 (file)
@@ -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.
index 46f2680010ddeba1ef35e91d1e85ef301ee6dbb7..dea9ccfa6188731deb5e154309ed949d10157158 100644 (file)
@@ -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
index 01553c5eb03eefac60eebafeaf51c6c5d12905a2..efa6585bc798db807e256a56264193bcb545f25e 100644 (file)
@@ -27,9 +27,18 @@ RCSID("$Id$")
 #include <freeradius-devel/server/exfile.h>
 #include <freeradius-devel/server/module_rlm.h>
 #include <freeradius-devel/server/tmpl_dcursor.h>
+#include <freeradius-devel/server/rcode.h>
+#include <freeradius-devel/server/tmpl.h>
+#include <freeradius-devel/unlang/call_env.h>
+#include <freeradius-devel/unlang/tmpl.h>
+#include <freeradius-devel/unlang/module.h>
+
 #include <freeradius-devel/util/debug.h>
 #include <freeradius-devel/util/iovec.h>
 #include <freeradius-devel/util/perm.h>
+#include <freeradius-devel/util/print.h>
+#include <freeradius-devel/util/value.h>
+#include <freeradius-devel/util/types.h>
 
 #include <freeradius-devel/unlang/xlat_func.h>
 
@@ -54,6 +63,8 @@ RCSID("$Id$")
 
 #include <sys/uio.h>
 
+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 \<oct><oct><oct> @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
        }
 };