Skip to content

Commit

Permalink
Add support for call_env callbacks
Browse files Browse the repository at this point in the history
These can either emit a tmpl_t which'll later be processed during evaluation, or emit a custom structure, which the module or xlat can access at runtime.
  • Loading branch information
arr2036 committed Nov 25, 2023
1 parent a84044b commit 48563c3
Show file tree
Hide file tree
Showing 11 changed files with 495 additions and 201 deletions.
361 changes: 274 additions & 87 deletions src/lib/unlang/call_env.c

Large diffs are not rendered by default.

201 changes: 149 additions & 52 deletions src/lib/unlang/call_env.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,19 @@ typedef enum {
CALL_ENV_INVALID = -2
} call_env_result_t;

/** What type of structure is produced by the parsing phase
*/
typedef enum {
CALL_ENV_TYPE_VALUE_BOX = 1,
CALL_ENV_TYPE_VALUE_BOX_LIST,
CALL_ENV_TYPE_TMPL_ONLY
} call_env_dst_t;
CALL_ENV_PARSE_TYPE_TMPL = 1, //!< Output of the parsing phase is a tmpl_t.
CALL_ENV_PARSE_TYPE_VOID //!< Output of the parsing phase is undefined (a custom structure).
} call_env_parse_type_t;

/** What type of structure is produced by the evaluation phase
*/
typedef enum {
CALL_ENV_RESULT_TYPE_VALUE_BOX = 1, //!< Output of the evaluation phase is a single value box.
CALL_ENV_RESULT_TYPE_VALUE_BOX_LIST, //!< Output of the evaluation phase is a list of value boxes.
} call_env_result_type_t;

DIAG_OFF(attributes)
typedef enum CC_HINT(flag_enum) {
Expand All @@ -69,8 +77,9 @@ typedef enum CC_HINT(flag_enum) {
///< where tmpls should always be parsed with a particular quoting
///< regardless of how they are in the config file. E.g. the `program`
///< option of `rlm_exec` should always be parsed as T_BACK_QUOTED_STRING.
CALL_ENV_FLAG_ATTRIBUTE = (1 << 6), //!< Tmpl must contain an attribute reference.
CALL_ENV_FLAG_SUBSECTION = (1 << 7) //!< This is a subsection.
CALL_ENV_FLAG_PARSE_ONLY = (1 << 6), //!< The result of parsing will not be evaluated at runtime.
CALL_ENV_FLAG_ATTRIBUTE = (1 << 7), //!< Tmpl must contain an attribute reference.
CALL_ENV_FLAG_SUBSECTION = (1 << 8) //!< This is a subsection.
} call_env_flags_t;
DIAG_ON(attributes)

Expand Down Expand Up @@ -103,11 +112,43 @@ DIAG_ON(attributes)

#define call_env_force_quote(_flags) ((_flags) & CALL_ENV_FLAG_FORCE_QUOTE)

#define call_env_parse_only(_flags) ((_flags) & CALL_ENV_FLAG_PARSE_ONLY)

#define call_env_attribute(_flags) ((_flags) & CALL_ENV_FLAG_ATTRIBUTE)

#define call_env_is_subsection(_flags) ((_flags) & CALL_ENV_FLAG_SUBSECTION)
/** @} */

/** Callback for performing custom parsing of a #CONF_PAIR
*
* @param[in] ctx to allocate any data in.
* @param[out] out Where to write the result of parsing.
* @param[in] namespace we're operating in.
* @param[in] ci The #CONF_SECTION or #CONF_PAIR to parse.
* @param[in] rule Parse rules - How the #CONF_PAIR or #CONF_SECTION should be converted.
* @return
* - 0 on success.
* - -1 on failure.
*/
typedef int (*call_env_parse_pair_t)(TALLOC_CTX *ctx, void *out, fr_dict_t const *namespace, CONF_ITEM *ci, call_env_parser_t const *rule);

/** Callback for performing custom parsing of a #CONF_SECTION
*
* The callback function is expected to call call_env_parsed_add to allocate a new
* call_env_parsed_t, and either call_env_parsed_set_tmpl, or call_env_parsed_set_data to populate
* the call env_parsed_t structure.
*
* @param[in] ctx to allocate any data in.
* @param[out] out Where to write the result of parsing.
* @param[in] namespace we're operating in.
* @param[in] ci The #CONF_SECTION or #CONF_PAIR to parse.
* @param[in] rule Parse rules - How the #CONF_PAIR or #CONF_SECTION should be converted.
* @return
* - 0 on success.
* - -1 on failure.
*/
typedef int (*call_env_parse_section_t)(TALLOC_CTX *ctx, call_env_parsed_head_t *out, fr_dict_t const *namespace, CONF_ITEM *ci, call_env_parser_t const *rule);

/** Per method call config
*
* Similar to a conf_parser_t used to hold details of conf pairs
Expand All @@ -120,44 +161,40 @@ struct call_env_parser_s {
char const *name; //!< Of conf pair to pass to tmpl_tokenizer.
call_env_flags_t flags; //!< Flags controlling parser behaviour.

ssize_t parsed_offset; //!< Where to write the result of the parsing phase.
///< This is usually a tmpl_t, but could be other things when a callback
///< function is used to parse the CONF_SECTION or CONF_PAIR.

union {
struct {
fr_type_t cast_type; //!< To cast boxes to. Also contains flags controlling parser behaviour.
fr_type_t cast_type; //!< To cast boxes to. Also contains flags controlling parser behaviour.

call_env_result_type_t type; //!< Type of structure boxes will be written to.
size_t size; //!< Size of structure boxes will be written to.
char const *type_name; //!< Name of structure type boxes will be written to.
size_t offset; //!< Where to write the result of evaluating the tmpl_t produced in the parsing phase.

char const *dflt; //!< Default string to pass to the tmpl_tokenizer if no CONF_PAIR found.
fr_token_t dflt_quote; //!< Quoting for the default string.

call_env_dst_t type; //!< Type of structure boxes will be written to.
size_t size; //!< Size of structure boxes will be written to.
char const *type_name; //!< Name of structure type boxes will be written to.
size_t result_offset; //!< Where to write the result of evaluating the tmpl_t produced in the parsing phase.
call_env_parse_pair_t func; //!< Callback for parsing a CONF_PAIR

char const *dflt; //!< Default string to pass to the tmpl_tokenizer if no CONF_PAIR found.
fr_token_t dflt_quote; //!< Quoting for the default string.
struct {
ssize_t offset; //!< Where to write the result of the parsing phase.
///< This is usually a tmpl_t, but could be other things when a callback
///< function is used to parse the CONF_SECTION or CONF_PAIR.

call_env_parse_type_t type; //!< What type of output the parsing phase is expected to produce.
} parsed;
} pair;

struct {
char const *ident2; //!< Second identifier for a section
call_env_parser_t const *subcs; //!< Nested definitions for subsection.
char const *ident2; //!< Second identifier for a section
call_env_parser_t const *subcs; //!< Nested definitions for subsection.

call_env_parse_section_t func; //!< Callback for parsing CONF_SECTION.
} section;
};
};

#define CALL_ENV_TERMINATOR { NULL }

struct call_env_parsed_s {
call_env_parsed_entry_t entry; //!< Entry in list of parsed call_env_parsers.
tmpl_t *tmpl; //!< Tmpl produced from parsing conf pair.
size_t count; //!< Number of CONF_PAIRs found, matching the #call_env_parser_t.
size_t multi_index; //!< Array index for this instance.
call_env_parser_t const *rule; //!< Used to produce this.
bool tmpl_only; //!< Don't evaluate before module / xlat call.
///< Only the tmpl reference is needed.
};

FR_DLIST_FUNCS(call_env_parsed, call_env_parsed_t, entry)

/** Helper macro for populating the size/type fields of a #call_env_method_t from the output structure type
*/
#define FR_CALL_ENV_METHOD_OUT(_inst) \
Expand All @@ -177,14 +214,23 @@ struct call_env_s {
call_env_method_t const *method; //!< The method this call env is for.
};

/** Where we're specifying a parsing phase output field, determine its type
*/
#define CALL_ENV_PARSE_TYPE(_s, _f) \
_Generic((((_s *)NULL)->_f), \
tmpl_t * : CALL_ENV_PARSE_TYPE_TMPL, \
default : CALL_ENV_PARSE_TYPE_VOID \
)

/** Derive whether tmpl can only emit a single box.
*/
#define FR_CALL_ENV_SINGLE(_s, _f, _c) \
_Generic((((_s *)NULL)->_f), \
fr_value_box_t : __builtin_choose_expr(_c, CALL_ENV_FLAG_NONE, CALL_ENV_FLAG_SINGLE), \
fr_value_box_t * : __builtin_choose_expr(_c, CALL_ENV_FLAG_NONE, CALL_ENV_FLAG_SINGLE), \
fr_value_box_list_t : CALL_ENV_FLAG_NONE, \
fr_value_box_list_t * : CALL_ENV_FLAG_SINGLE \
fr_value_box_list_t * : CALL_ENV_FLAG_SINGLE, \
default : CALL_ENV_FLAG_NONE \
)

/** Derive whether multi conf pairs are allowed from target field type.
Expand All @@ -208,26 +254,28 @@ __builtin_choose_expr(_c, (void)0, false)))
*/
#define FR_CALL_ENV_DST_TYPE(_s, _f) \
_Generic((((_s *)NULL)->_f), \
fr_value_box_t : CALL_ENV_TYPE_VALUE_BOX, \
fr_value_box_t * : CALL_ENV_TYPE_VALUE_BOX, \
fr_value_box_list_t : CALL_ENV_TYPE_VALUE_BOX_LIST, \
fr_value_box_list_t * : CALL_ENV_TYPE_VALUE_BOX_LIST \
fr_value_box_t : CALL_ENV_RESULT_TYPE_VALUE_BOX, \
fr_value_box_t * : CALL_ENV_RESULT_TYPE_VALUE_BOX, \
fr_value_box_list_t : CALL_ENV_RESULT_TYPE_VALUE_BOX_LIST, \
fr_value_box_list_t * : CALL_ENV_RESULT_TYPE_VALUE_BOX_LIST \
)

#define FR_CALL_ENV_DST_SIZE(_s, _f) \
_Generic((((_s *)NULL)->_f), \
fr_value_box_t : sizeof(fr_value_box_t), \
fr_value_box_t * : sizeof(fr_value_box_t), \
fr_value_box_list_t : sizeof(fr_value_box_list_t), \
fr_value_box_list_t * : sizeof(fr_value_box_list_t) \
fr_value_box_list_t * : sizeof(fr_value_box_list_t), \
default : 0 \
)

#define FR_CALL_ENV_DST_TYPE_NAME(_s, _f) \
#define FR_CALL_ENV_RESULT_TYPE_NAME(_s, _f) \
_Generic((((_s *)NULL)->_f), \
fr_value_box_t : "fr_value_box_t", \
fr_value_box_t * : "fr_value_box_t", \
fr_value_box_list_t : "fr_value_box_list_t", \
fr_value_box_list_t * : "fr_value_box_list_t" \
fr_value_box_list_t * : "fr_value_box_list_t", \
default : NULL \
)

typedef void _mismatch_flags; //!< Dummy type used to indicate bad flags.
Expand All @@ -238,43 +286,76 @@ typedef void _mismatch_flags; //!< Dummy type used to indicate bad flags.
FR_CALL_ENV_MULTI(_struct, _field) |\
((_flags) & ~CALL_ENV_FLAG_CONCAT)) \

/** Specify a call_env_parser_t which writes out runtime results to the specified field
*
* @param[in] _name of the conf pair to parse.
* @param[in] _cast_type Cast any value boxes produced to this type.
* @param[in] _flags controlling parser behaviour.
* @param[in] _struct which contains the field to write the result of the evaluation phase to.
* @param[in] _field where to write the result.
*/
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field) \
.name = _name, \
.flags = CALL_ENV_FLAGS(_cast_type, _flags, _struct, _field), \
.parsed_offset = -1, \
.pair = { \
.cast_type = _cast_type, \
.type = FR_CALL_ENV_DST_TYPE(_struct, _field), \
.size = FR_CALL_ENV_DST_SIZE(_struct, _field), \
.type_name = FR_CALL_ENV_DST_TYPE_NAME(_struct, _field), \
.result_offset = offsetof(_struct, _field), \
.type_name = FR_CALL_ENV_RESULT_TYPE_NAME(_struct, _field), \
.offset = offsetof(_struct, _field), \
.parsed = { \
.offset = -1, \
.type = CALL_ENV_PARSE_TYPE_TMPL \
} \
}

/** Version of the above which sets optional field for pointer to tmpl
/** Specify a call_env_parser_t which writes out runtime results and the result of the parsing phase to the fields specified
*
* @param[in] _name of the conf pair to parse.
* @param[in] _cast_type Cast any value boxes produced to this type.
* @param[in] _flags controlling parser behaviour.
* @param[in] _struct which contains the field to write the result of the evaluation phase to.
* @param[in] _field where to write the result.
* @param[in] _parse_field where to write the result of the parsing phase.
* This must be a field in the specified _struct.
*/
#define FR_CALL_ENV_TMPL_OFFSET(_name, _cast_type, _flags, _struct, _field, _tmpl_field) \
#define FR_CALL_ENV_PARSE_OFFSET(_name, _cast_type, _flags, _struct, _field, _parse_field) \
.name = _name, \
.flags = CALL_ENV_FLAGS(_cast_type, _flags, _struct, _field), \
.parsed_offset = offsetof(_struct, _tmpl_field), \
.pair = { \
.cast_type = _cast_type, \
.type = FR_CALL_ENV_DST_TYPE(_struct, _field), \
.size = FR_CALL_ENV_DST_SIZE(_struct, _field), \
.type_name = FR_CALL_ENV_DST_TYPE_NAME(_struct, _field), \
.result_offset = offsetof(_struct, _field), \
.type_name = FR_CALL_ENV_RESULT_TYPE_NAME(_struct, _field), \
.offset = offsetof(_struct, _field), \
.parsed = { \
.offset = offsetof(_struct, _parse_field), \
.type = CALL_ENV_PARSE_TYPE(_struct, _parse_field) \
} \
}

/** Version of the above which only sets the field for a pointer to the tmpl
/** Specify a call_env_parser_t which writes out the result of the parsing phase to the field specified
*
* @param[in] _name of the conf pair to parse.
* @param[in] _cast_type Sets the cast used by the tmpl.
* @param[in] _flags controlling parser behaviour.
* @param[in] _struct which contains the field to write the result of the evaluation phase to.
* @param[in] _parse_field where to write the result of the parsing phase.
* This must be a field in the specified _struct.
*/
#define FR_CALL_ENV_TMPL_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _tmpl_field) \
#define FR_CALL_ENV_PARSE_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _parse_field) \
.name = _name, \
.flags = _flags, \
.parsed_offset = offsetof(_struct, _tmpl_field), \
.flags = (_flags) | CALL_ENV_FLAG_PARSE_ONLY, \
.pair = { \
.cast_type = _cast_type, \
.type = CALL_ENV_TYPE_TMPL_ONLY \
.parsed = { \
.offset = offsetof(_struct, _parse_field), \
.type = CALL_ENV_PARSE_TYPE(_struct, _parse_field) \
} \
}

/** Specify a call_env_parser_t which defines a nested subsection
*/
#define FR_CALL_ENV_SUBSECTION(_name, _ident2, _flags, _subcs ) \
.name = _name, \
.flags = CALL_ENV_FLAG_SUBSECTION | (_flags), \
Expand All @@ -283,8 +364,24 @@ typedef void _mismatch_flags; //!< Dummy type used to indicate bad flags.
.subcs = _subcs, \
}

/** Specify a call_env_parser_t which parses a subsection using a callback function
*/
#define FR_CALL_ENV_SUBSECTION_FUNC(_name, _ident2, _flags, _func) \
.name = _name, \
.flags = CALL_ENV_FLAG_SUBSECTION | (_flags), \
.section = { \
.ident2 = _ident2, \
.func = _func \
}

unlang_action_t call_env_expand(TALLOC_CTX *ctx, request_t *request, call_env_result_t *result, void **env_data, call_env_t const *call_env);

call_env_parsed_t *call_env_parsed_add(TALLOC_CTX *ctx, call_env_parsed_head_t *head, call_env_parser_t const *rule);

void call_env_parsed_set_tmpl(call_env_parsed_t *parsed, tmpl_t *tmpl);

void call_env_parsed_set_data(call_env_parsed_t *parsed, void *data);

call_env_t *call_env_alloc(TALLOC_CTX *ctx, char const *name, call_env_method_t const *call_env_method,
fr_dict_t const *namespace, CONF_SECTION *cs) CC_HINT(nonnull(3,4,5));

Expand Down
Loading

0 comments on commit 48563c3

Please sign in to comment.