Fix redis xlats to keep argument order and not crash when empty values are provided
*/
typedef int (*xlat_escape_func_t)(request_t *request, fr_value_box_t *vb, void *uctx);
+typedef enum {
+ XLAT_ARG_VARIADIC_DISABLED = 0,
+ XLAT_ARG_VARIADIC_EMPTY_SQUASH = 1, //!< Empty argument groups are removed.
+ XLAT_ARG_VARIADIC_EMPTY_KEEP = 2, //!< Empty argument groups are left alone,
+ ///< and either passed through as empty groups
+ ///< or null boxes.
+} xlat_arg_parser_variadic_t;
+
/** Definition for a single argument consumend by an xlat function
*
*/
typedef struct {
- bool required; //!< Argument must be present.
- bool concat; //!< Concat boxes together.
- bool single; //!< Argument must only contain a single box
- bool variadic; //!< All additional boxes should be processed
- ///< using this definition.
- bool always_escape; //!< Pass all arguments to escape function not just
- ///< tainted ones.
- fr_type_t type; //!< Type to cast argument to.
- xlat_escape_func_t func; //!< Function to handle tainted values.
- void *uctx; //!< Argument to pass to escape callback.
+ bool required; //!< Argument must be present, and non-empty.
+ bool concat; //!< Concat boxes together.
+ bool single; //!< Argument must only contain a single box
+ xlat_arg_parser_variadic_t variadic; //!< All additional boxes should be processed
+ ///< using this definition.
+ bool always_escape; //!< Pass all arguments to escape function not just
+ ///< tainted ones.
+ fr_type_t type; //!< Type to cast argument to.
+ xlat_escape_func_t func; //!< Function to handle tainted values.
+ void *uctx; //!< Argument to pass to escape callback.
} xlat_arg_parser_t;
typedef struct {
static xlat_arg_parser_t const xlat_func_cast_args[] = {
{ .required = true, .single = true, .type = FR_TYPE_VOID },
- { .required = true, .variadic = true, .type = FR_TYPE_VOID },
+ { .required = true, .type = FR_TYPE_VOID },
+ { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_VOID },
XLAT_ARG_PARSER_TERMINATOR
};
}
static xlat_arg_parser_t const xlat_func_join_args[] = {
- { .required = true, .variadic = true, .type = FR_TYPE_VOID },
+ { .required = true, .type = FR_TYPE_VOID },
+ { .variadic = XLAT_ARG_VARIADIC_EMPTY_SQUASH, .type = FR_TYPE_VOID },
XLAT_ARG_PARSER_TERMINATOR
};
/** Join a series of arguments to form a single list
*
+ * null boxes are not preserved.
*/
static xlat_action_t xlat_func_join(UNUSED TALLOC_CTX *ctx, fr_dcursor_t *out,
UNUSED xlat_ctx_t const *xctx,
UNUSED request_t *request, fr_value_box_list_t *in)
{
- fr_value_box_t *arg = NULL, *vb, *p;
-
- while ((arg = fr_value_box_list_next(in, arg))) {
+ fr_value_box_list_foreach(in, arg) {
fr_assert(arg->type == FR_TYPE_GROUP);
- vb = fr_value_box_list_head(&arg->vb_group);
- while (vb) {
- p = fr_value_box_list_remove(&arg->vb_group, vb);
+
+ fr_value_box_list_foreach_safe(&arg->vb_group, vb) {
+ fr_value_box_list_remove(&arg->vb_group, vb);
fr_dcursor_append(out, vb);
- vb = fr_value_box_list_next(&arg->vb_group, p);
- }
+ }}
}
return XLAT_ACTION_DONE;
}
}
static xlat_arg_parser_t const xlat_func_length_args[] = {
- { .single = true, .variadic = true, .type = FR_TYPE_VOID },
+ { .single = true, .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_VOID },
XLAT_ARG_PARSER_TERMINATOR
};
/** Return the on-the-wire size of the boxes in bytes
+ *
+ * skips null values
*
* Example:
@verbatim
UNUSED request_t *request, fr_value_box_list_t *in)
{
- fr_value_box_t *vb = NULL;
-
- while ((vb = fr_value_box_list_next(in, vb))) {
+ fr_value_box_list_foreach(in, vb) {
fr_value_box_t *my;
MEM(my = fr_value_box_alloc(ctx, FR_TYPE_SIZE, NULL, false));
- my->vb_size = fr_value_box_network_length(vb);
+ if (!fr_type_is_null(vb->type)) my->vb_size = fr_value_box_network_length(vb);
fr_dcursor_append(out, my);
}
}
static xlat_arg_parser_t const protocol_decode_xlat_args[] = {
- { .single = true, .variadic = true, .type = FR_TYPE_VOID },
+ { .single = true, .variadic = XLAT_ARG_VARIADIC_EMPTY_SQUASH, .type = FR_TYPE_VOID },
XLAT_ARG_PARSER_TERMINATOR
};
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/util/types.h>
#include <freeradius-devel/util/sbuff.h>
+#include <freeradius-devel/util/value.h>
#include <freeradius-devel/unlang/unlang_priv.h> /* Remove when everything uses new xlat API */
*/
if (!func->args) return XLAT_ACTION_DONE;
-
/*
* xlat needs no input processing just return.
*/
* This argument doesn't exist. That might be OK, or it may be a fatal error.
*/
if (fr_value_box_list_empty(&vb->vb_group)) {
+ /*
+ * Variadic rules deal with empty boxes differently...
+ */
+ switch (arg_p->variadic) {
+ case XLAT_ARG_VARIADIC_EMPTY_SQUASH:
+ fr_value_box_list_talloc_free_head(list);
+ continue;
+
+ case XLAT_ARG_VARIADIC_EMPTY_KEEP:
+ goto empty_ok;
+
+ case XLAT_ARG_VARIADIC_DISABLED:
+ break;
+ }
+
/*
* Empty groups for optional arguments are OK, we can just stop processing the list.
*/
if (!arg_p->required) {
- fr_assert(!next);
-
/*
* If the caller doesn't care about the type, then we leave the
* empty group there.
* accessing the box as vb_uint8, hoping that it's being passed
* the right thing.
*/
- fr_value_box_list_remove(list, vb);
- talloc_free(vb);
+ fr_value_box_list_talloc_free_head(list);
break;
}
if (arg_p->type != FR_TYPE_VOID) goto missing;
}
+ empty_ok:
/*
* In some cases we replace the current argument with the head of the group.
*
* xlat_process_arg_list() has already done concatenations for us.
*/
if (arg_p->single || arg_p->concat) {
- fr_value_box_list_replace(list, vb, fr_value_box_list_pop_head(&vb->vb_group));
+ fr_value_box_t *head = fr_value_box_list_pop_head(&vb->vb_group);
+
+ /*
+ * If we're meant to be smashing the argument
+ * to a single box, but the group was empty,
+ * add a null box instead so ordering is maintained
+ * for subsequent boxes.
+ */
+ if (!head) head = fr_value_box_alloc_null(ctx);
+ fr_value_box_list_replace(list, vb, head);
talloc_free(vb);
}
fr_value_box_list_t *in)
{
int decoded = 0;
- fr_value_box_t *vb = NULL;
fr_pair_t *vp = NULL;
fr_dict_attr_t const *parent = fr_dict_root(request->dict);
fr_pair_list_t head;
fr_pair_list_init(&head);
- while ((vb = fr_value_box_list_next(in, vb))) {
+ fr_value_box_list_foreach(in, vb) {
ssize_t len;
if (vb->type != FR_TYPE_OCTETS) {
#include <freeradius-devel/tls/strerror.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/unlang/xlat_func.h>
+#include <freeradius-devel/unlang/xlat.h>
#include <freeradius-devel/tls/openssl_user_macros.h>
#include <openssl/crypto.h>
}
static xlat_arg_parser_t const cipher_rsa_verify_xlat_arg[] = {
- { .required = true, .concat = true, .single = false, .variadic = true, .type = FR_TYPE_STRING,
- .func = NULL, .uctx = NULL },
{ .required = true, .concat = false, .single = true, .type = FR_TYPE_VOID },
+ { .required = true, .concat = true, .type = FR_TYPE_STRING },
+ { .variadic = XLAT_ARG_VARIADIC_EMPTY_SQUASH, .concat = true, .type = FR_TYPE_STRING },
XLAT_ARG_PARSER_TERMINATOR
};
static xlat_arg_parser_t const exec_xlat_args[] = {
{ .required = true, .type = FR_TYPE_STRING },
- { .variadic = true, .type = FR_TYPE_VOID},
+ { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_VOID},
XLAT_ARG_PARSER_TERMINATOR
};
#include <freeradius-devel/server/module_rlm.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/unlang/xlat_func.h>
+#include <freeradius-devel/unlang/xlat.h>
#include <freeradius-devel/radius/radius.h>
DIAG_OFF(DIAG_UNKNOWN_PRAGMAS)
static xlat_arg_parser_t const perl_xlat_args[] = {
{ .required = true, .single = true, .type = FR_TYPE_STRING },
- { .variadic = true, .type = FR_TYPE_VOID },
+ { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_VOID },
XLAT_ARG_PARSER_TERMINATOR
};
xlat_action_t ret = XLAT_ACTION_FAIL;
STRLEN n_a;
fr_value_box_t *func = fr_value_box_list_pop_head(in);
- fr_value_box_t *arg = NULL, *child;
+ fr_value_box_t *child;
SV *sv;
AV *av;
fr_value_box_list_t list, sub_list;
PUSHMARK(SP);
- while ((arg = fr_value_box_list_next(in, arg))) {
+ fr_value_box_list_foreach(in, arg) {
fr_assert(arg->type == FR_TYPE_GROUP);
if (fr_value_box_list_empty(&arg->vb_group)) continue;
/*
* If we're status checking OR already zombie, don't go to zombie
- *
*/
if (h->status_checking || h->zombie_ev) return true;
if (h->inst->parent->synchronous && fr_time_gt(last_sent, fr_time_wrap(0)) &&
(fr_time_lt(fr_time_add(last_sent, h->inst->parent->response_window), now))) return false;
- /*
- * Mark the connection as inactive, but keep sending
- * packets on it.
- */
WARN("%s - Entering Zombie state - connection %s", h->module_name, h->name);
- fr_trunk_connection_signal_inactive(tconn);
-
if (h->inst->parent->status_check) {
h->status_checking = true;
static xlat_arg_parser_t const redis_lua_func_args[] = {
{ .required = true, .single = true, .type = FR_TYPE_UINT64 }, /* key count */
- { .variadic = true, .concat = true, .type = FR_TYPE_STRING }, /* keys and args */
+ { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .concat = true, .type = FR_TYPE_STRING }, /* keys and args */
XLAT_ARG_PARSER_TERMINATOR
};
REXDENT();
return XLAT_ACTION_FAIL;
}
+
+ /*
+ * Fixup null or empty arguments to be
+ * zero length strings so that the position
+ * of subsequent arguments are maintained.
+ */
+ if (!fr_type_is_string(vb->type)) {
+ argv[argc] = "";
+ arg_len[argc++] = 0;
+ continue;
+ }
+
argv[argc] = vb->vb_strvalue;
- arg_len[argc] = vb->vb_length;
- argc++;
+ arg_len[argc++] = vb->vb_length;
}
/*
}
static xlat_arg_parser_t const redis_args[] = {
- { .required = true, .variadic = true, .concat = true, .type = FR_TYPE_STRING },
+ { .required = true, .concat = true, .type = FR_TYPE_STRING },
+ { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .concat = true, .type = FR_TYPE_STRING },
XLAT_ARG_PARSER_TERMINATOR
};
/** Xlat to make calls to redis
*
@verbatim
-%{redis:<redis command>}
+%(redis:<redis command>)
@endverbatim
*
* @ingroup xlat_functions
goto fail;
}
+ /*
+ * Fixup null or empty arguments to be
+ * zero length strings so that the position
+ * of subsequent arguments are maintained.
+ */
+ if (!fr_type_is_string(vb->type)) {
+ argv[argc] = "";
+ arg_len[argc++] = 0;
+ continue;
+ }
+
argv[argc] = vb->vb_strvalue;
- arg_len[argc] = vb->vb_length;
- argc++;
+ arg_len[argc++] = vb->vb_length;
}
RDEBUG2("Executing command: %pV", fr_value_box_list_head(in));
};
static xlat_arg_parser_t const rest_xlat_args[] = {
- { .required = true, .variadic = true, .type = FR_TYPE_STRING },
+ { .required = true, .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_STRING },
XLAT_ARG_PARSER_TERMINATOR
};
static xlat_arg_parser_t const test_xlat_args[] = {
- { .required = true, .concat = true, .variadic = true, .type = FR_TYPE_STRING },
+ { .required = true, .concat = true, .type = FR_TYPE_STRING },
+ { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .concat = true, .type = FR_TYPE_STRING },
XLAT_ARG_PARSER_TERMINATOR
};
UNUSED xlat_ctx_t const *xctx, UNUSED request_t *request,
fr_value_box_list_t *in)
{
- fr_value_box_t *vb_p = NULL;
fr_value_box_t *vb;
- while ((vb_p = fr_value_box_list_next(in, vb_p))) {
+ fr_value_box_list_foreach(in, vb_p) {
MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_STRING, NULL, false));
if (fr_value_box_copy(ctx, vb, vb_p) < 0) {
# ...and again using an argument that would produce a null result
# this is a regression test where the arg parser would require all
# arguments to be non-null if the first argument was
-if (!(%(redis.hello_world:0 %{Framed-IP-Address}) == 'hello world')) {
+if (!(%(redis.hello_world:0 %{Tmp-String-9}) == 'hello world')) {
test_fail
}
test_fail
}
+# Concat with an empty argument. This is a regression test
+if (!(%(redis.concat_args_keys:1 foo %{Tmp-String-9} baz) == 'foo,,baz')) {
+ test_fail
+}
+
if (!(%(redis.multiline:0 0) == 0)) {
test_fail
}