25 RCSID(
"$Id: e2192ab223e0dcd3db0176a4f724983517120861 $")
27 #define LOG_PREFIX mctx->inst->name
29 #include <freeradius-devel/server/base.h>
30 #include <freeradius-devel/server/module_rlm.h>
31 #include <freeradius-devel/server/modpriv.h>
32 #include <freeradius-devel/server/dl_module.h>
33 #include <freeradius-devel/server/rcode.h>
34 #include <freeradius-devel/server/tmpl.h>
35 #include <freeradius-devel/util/debug.h>
36 #include <freeradius-devel/util/types.h>
37 #include <freeradius-devel/util/value.h>
38 #include <freeradius-devel/unlang/xlat_func.h>
39 #include <freeradius-devel/unlang/call_env.h>
106 char const *section_name1,
char const *section_name2,
void const *
data,
118 if (
unlikely((ret = func(ctx, &key_tmpl, t_rules, ci, section_name1, section_name2,
119 inst->driver_submodule->dl_inst->data, rule)) < 0))
return ret;
126 if (
inst->driver->key_parse)
return 0;
136 cf_log_err(ci,
"Driver only allows key type '%s', got '%s'",
142 cf_log_perr(ci,
"Can't convert key type '%s' to '%s'",
155 if (!
inst->driver->acquire) {
160 return inst->driver->acquire(
out, &
inst->config,
inst->driver_submodule->dl_inst->data, request);
168 if (!
inst->driver->release)
return;
169 if (!handle || !*handle)
return;
171 inst->driver->release(&
inst->config,
inst->driver_submodule->dl_inst->data, request, *handle);
182 return inst->driver->reconnect(handle, &
inst->config,
inst->driver_submodule->dl_inst->data, request);
195 if (
inst->driver->alloc)
return inst->driver->alloc(&
inst->config,
inst->driver_submodule->dl_inst->data, request);
216 if (!c || !*c || !
inst->driver->free)
return;
218 inst->driver->free(*c);
235 RDEBUG2(
"Merging cache entry into request");
237 while ((map = map_list_next(&c->
maps, map))) {
257 if (
inst->config.stats) {
286 ret =
inst->driver->find(&c, &
inst->config,
inst->driver_submodule->dl_inst->data, request, *handle, key);
297 RDEBUG2(
"No cache entry found for \"%pV\"", key);
313 RDEBUG2(
"Found entry for \"%pV\", but it expired %pV ago at %pV (packet received %pV). Removing it",
320 inst->driver->expire(&
inst->config,
inst->driver_submodule->dl_inst->data, request, handle, key);
326 RDEBUG2(
"Found entry for \"%pV\", but it was created before the current epoch. Removing it",
330 RDEBUG2(
"Found entry for \"%pV\"", key);
349 RDEBUG2(
"Expiring cache entry");
350 for (;;)
switch (
inst->driver->expire(&
inst->config,
inst->driver_submodule->dl_inst->data, request, *handle, key)) {
377 map_t const *map = NULL;
386 if ((
inst->config.max_entries > 0) &&
inst->driver->count &&
387 (
inst->driver->count(&
inst->config,
inst->driver_submodule->dl_inst->data, request, handle) >
inst->config.max_entries)) {
388 RWDEBUG(
"Cache is full: %d entries",
inst->config.max_entries);
396 map_list_init(&c->
maps);
398 RERROR(
"Failed copying key");
409 RDEBUG2(
"Creating new cache entry");
415 if (!maps)
goto skip_maps;
421 pool = talloc_pool(NULL, 2048);
422 while ((map = map_list_next(maps, map))) {
432 if (
map_to_vp(pool, &to_cache, request, map, NULL) < 0) {
446 case FR_CACHE_STATUS_ONLY:
447 case FR_CACHE_MERGE_NEW:
448 case FR_CACHE_ENTRY_HITS:
461 map_list_init(&c_map->
child);
466 switch (map->
lhs->type) {
495 REDEBUG(
"Failed copying attribute value");
507 map_list_insert_tail(&c->
maps, c_map);
509 talloc_free_children(pool);
519 if (
vp &&
vp->vp_bool) merge =
true;
526 ret =
inst->driver->insert(&
inst->config,
inst->driver_submodule->dl_inst->data, request, *handle, c);
557 if (!
inst->driver->set_ttl)
for (;;) {
560 ret =
inst->driver->insert(&
inst->config,
inst->driver_submodule->dl_inst->data, request, *handle, c);
582 ret =
inst->driver->set_ttl(&
inst->config,
inst->driver_submodule->dl_inst->data, request, *handle, c);
617 bool merge =
true, insert =
true, expire =
false, set_ttl =
false;
624 if (env->
key->vb_length == 0) {
625 REDEBUG(
"Zero length key string is invalid");
634 if (
vp &&
vp->vp_bool) {
656 if (
vp) merge =
vp->vp_bool;
659 if (
vp) insert =
vp->vp_bool;
663 if (
vp->vp_int32 == 0) {
665 }
else if (
vp->vp_int32 < 0) {
676 RDEBUG3(
"merge : %s", merge ?
"yes" :
"no");
677 RDEBUG3(
"insert : %s", insert ?
"yes" :
"no");
678 RDEBUG3(
"expire : %s", expire ?
"yes" :
"no");
718 if (expire && ((exists == -1) || (exists == 1))) {
752 if ((exists < 0) && (insert || set_ttl)) {
779 if (set_ttl && (exists == 1)) {
808 if (insert && (exists == 0)) {
846 switch (
vp->
da->attr) {
848 case FR_CACHE_STATUS_ONLY:
849 case FR_CACHE_ALLOW_MERGE:
850 case FR_CACHE_ALLOW_INSERT:
851 case FR_CACHE_MERGE_NEW:
897 .dict_def = request->dict,
898 .list_def = request_attr_request,
899 .prefix = TMPL_ATTR_REF_PREFIX_AUTO
923 while ((map = map_list_next(&c->maps, map))) {
1007 switch (
vp->
da->attr) {
1009 case FR_CACHE_STATUS_ONLY:
1010 case FR_CACHE_ALLOW_MERGE:
1011 case FR_CACHE_ALLOW_INSERT:
1012 case FR_CACHE_MERGE_NEW:
1038 if (env->
key->vb_length == 0) {
1039 REDEBUG(
"Zero length key string is invalid");
1076 if (env->
key->vb_length == 0) {
1077 REDEBUG(
"Zero length key string is invalid");
1090 WARN(
"Entry not found to load");
1116 bool expire =
false;
1121 if (env->
key->vb_length == 0) {
1122 REDEBUG(
"Zero length key string is invalid");
1132 ttl =
inst->config.ttl;
1135 if (
vp->vp_int32 == 0) {
1137 }
else if (
vp->vp_int32 < 0) {
1172 DEBUG3(
"Expiring cache entry");
1208 if (env->
key->vb_length == 0) {
1209 REDEBUG(
"Zero length key string is invalid");
1218 ttl =
inst->config.ttl;
1220 if (
vp && (
vp->vp_int32 > 0)) {
1275 if (env->
key->vb_length == 0) {
1276 REDEBUG(
"Zero length key string is invalid");
1289 WARN(
"Entry not found to delete");
1321 if (env->
key->vb_length == 0) {
1322 REDEBUG(
"Zero length key string is invalid");
1332 ttl =
inst->config.ttl;
1335 if (
vp->vp_int32 < 0) {
1385 talloc_free_children(
inst);
1398 cf_log_err(map->
ci,
"Destination must be an attribute ref or a list");
1416 MEM(maps = talloc(parsed, map_list_t));
1417 map_list_init(maps);
1426 if (map_list_empty(maps)) {
1427 cf_log_err(update,
"Update section must not be empty");
1449 if (
inst->config.epoch != 0) {
1450 cf_log_err(
conf,
"Must not set 'epoch' in the configuration files");
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
static int const char char buffer[256]
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
void call_env_parsed_free(call_env_parsed_head_t *parsed, call_env_parsed_t *ptr)
Remove a call_env_parsed_t from the list of parsed call envs.
call_env_parsed_t * call_env_parsed_add(TALLOC_CTX *ctx, call_env_parsed_head_t *head, call_env_parser_t const *rule)
Allocate a new call_env_parsed_t structure and add it to the list of parsed call envs.
void call_env_parsed_set_data(call_env_parsed_t *parsed, void const *data)
Assign data to a call_env_parsed_t.
int call_env_parse_pair(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, UNUSED char const *section_name1, UNUSED char const *section_name2, UNUSED void const *data, UNUSED call_env_parser_t const *rule)
Standard function we use for parsing call env pairs.
#define CALL_ENV_TERMINATOR
#define FR_CALL_ENV_METHOD_OUT(_inst)
Helper macro for populating the size/type fields of a call_env_method_t from the output structure typ...
int(* call_env_parse_pair_t)(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule)
Callback for performing custom parsing of a CONF_PAIR.
#define FR_CALL_ENV_SUBSECTION_FUNC(_name, _ident2, _flags, _func)
Specify a call_env_parser_t which parses a subsection using a callback function.
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field)
Specify a call_env_parser_t which writes out runtime results to the specified field.
#define FR_CALL_ENV_PARSE_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _parse_field)
Specify a call_env_parser_t which writes out the result of the parsing phase to the field specified.
#define CONF_PARSER_TERMINATOR
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
#define FR_CONF_OFFSET_TYPE_FLAGS(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Defines a CONF_PAIR to C data type mapping.
Common header for all CONF_* types.
A section grouping multiple CONF_PAIR.
CONF_SECTION * cf_item_to_section(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_SECTION.
#define cf_log_err(_cf, _fmt,...)
#define cf_log_perr(_cf, _fmt,...)
static void * fr_dcursor_remove(fr_dcursor_t *cursor)
Remove the current item.
static int fr_dcursor_append(fr_dcursor_t *cursor, void *v)
Insert a single item at the end of the list.
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
static void * fr_dcursor_current(fr_dcursor_t *cursor)
Return the item the cursor current points to.
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
static bool fr_dict_attr_is_top_level(fr_dict_attr_t const *da)
Return true if this attribute is parented directly off the dictionary root.
Specifies an attribute which must be present for the module to function.
Specifies a dictionary which must be loaded/loadable for the module to function.
char const *_CONST name
Instance name.
void *_CONST data
Module instance's parsed configuration.
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
CONF_SECTION *_CONST conf
Module's instance configuration.
dl_module_inst_t const * inst
static xlat_action_t cache_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
Allow single attribute values to be retrieved from the cache.
#define REXDENT()
Exdent (unindent) R* messages by one level.
#define RPEDEBUG(fmt,...)
#define RINDENT()
Indent R* messages by one level.
int map_to_vp(TALLOC_CTX *ctx, fr_pair_list_t *out, request_t *request, map_t const *map, UNUSED void *uctx)
Convert a map to a fr_pair_t.
int map_afrom_cs(TALLOC_CTX *ctx, map_list_t *out, CONF_SECTION *cs, tmpl_rules_t const *lhs_rules, tmpl_rules_t const *rhs_rules, map_validate_t validate, void *uctx, unsigned int max)
Convert a config section into an attribute map.
int map_to_request(request_t *request, map_t const *map, radius_map_getvalue_t func, void *ctx)
Convert map_t to fr_pair_t (s) and add them to a request_t.
ssize_t map_print(fr_sbuff_t *out, map_t const *map)
Print a map to a string.
void map_debug_log(request_t *request, map_t const *map, fr_pair_t const *vp)
@ FR_TYPE_TIME_DELTA
A period of time measured in nanoseconds.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_INT32
32 Bit signed integer.
@ FR_TYPE_BOOL
A truth value.
static bool is_printable(void const *value, size_t len)
Check whether the string is made up of printable UTF8 chars.
int unlang_fixup_update(map_t *map, void *ctx)
Validate and fixup a map that's part of an update section.
void * env_data
Per call environment data.
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Temporary structure to hold arguments for module calls.
Temporary structure to hold arguments for instantiation calls.
Specifies a module method identifier.
int module_rlm_submodule_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
Generic conf_parser_t func for loading drivers.
module_t common
Common fields presented by all modules.
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
static const conf_parser_t config[]
#define pair_update_request(_attr, _da)
#define RDEBUG_ENABLED2()
#define RETURN_MODULE_RCODE(_rcode)
#define RETURN_MODULE_INVALID
rlm_rcode_t
Return codes indicating the result of the module call.
@ RLM_MODULE_OK
The module is OK, continue.
@ RLM_MODULE_FAIL
Module failed, don't reply.
@ RLM_MODULE_NOTFOUND
User not found.
@ RLM_MODULE_UPDATED
OK (pairs modified).
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
#define RETURN_MODULE_NOTFOUND
static int cache_acquire(rlm_cache_handle_t **out, rlm_cache_t const *inst, request_t *request)
Get exclusive use of a handle to access the cache.
static int mod_detach(module_detach_ctx_t const *mctx)
Free any memory allocated under the instance.
static unlang_action_t mod_method_update(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Create, or update a cache entry.
static fr_dict_attr_t const * attr_cache_allow_merge
fr_value_box_t * key
To lookup the cache entry with.
static unlang_action_t mod_method_ttl(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Change the TTL on an existing entry.
static fr_dict_attr_t const * attr_cache_ttl
static fr_dict_attr_t const * attr_cache_merge_new
static void cache_release(rlm_cache_t const *inst, request_t *request, rlm_cache_handle_t **handle)
Release a handle we previously acquired.
static fr_dict_t const * dict_freeradius
static int cache_verify(map_t *map, void *uctx)
Verify that a map in the cache section makes sense.
static rlm_cache_entry_t * cache_alloc(rlm_cache_t const *inst, request_t *request)
Allocate a cache entry.
map_list_t * maps
Attribute map applied to cache entries.
static unlang_action_t cache_set_ttl(rlm_rcode_t *p_result, rlm_cache_t const *inst, request_t *request, rlm_cache_handle_t **handle, rlm_cache_entry_t *c)
Update the TTL of an entry.
static unlang_action_t mod_cache_it(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Do caching checks.
static fr_dict_attr_t const * attr_cache_allow_insert
static unlang_action_t cache_insert(rlm_rcode_t *p_result, rlm_cache_t const *inst, request_t *request, rlm_cache_handle_t **handle, fr_value_box_t const *key, map_list_t const *maps, fr_time_delta_t ttl)
Create and insert a cache entry.
static int mod_bootstrap(module_inst_ctx_t const *mctx)
Register module xlats.
static fr_dict_attr_t const * attr_cache_status_only
static void cache_free(rlm_cache_t const *inst, rlm_cache_entry_t **c)
Free memory associated with a cache entry.
static unlang_action_t mod_method_status(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Get the status by ${key} (without load)
static unlang_action_t mod_method_store(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Create, or update a cache entry.
static fr_dict_attr_t const * attr_cache_entry_hits
static unlang_action_t mod_method_load(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Load the avps by ${key}.
static int cache_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule)
static xlat_arg_parser_t const cache_xlat_args[]
static int cache_reconnect(rlm_cache_handle_t **handle, rlm_cache_t const *inst, request_t *request)
Reconnect an suspected inviable handle.
static const call_env_method_t cache_method_env
fr_dict_attr_autoload_t rlm_cache_dict_attr[]
fr_dict_autoload_t rlm_cache_dict[]
static xlat_action_t cache_ttl_get_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, UNUSED fr_value_box_list_t *in)
static rlm_rcode_t cache_merge(rlm_cache_t const *inst, request_t *request, rlm_cache_entry_t *c)
Merge a cached entry into a request_t.
static void cache_unref(request_t *request, rlm_cache_t const *inst, rlm_cache_entry_t *entry, rlm_cache_handle_t *handle)
Release the allocated resources and cleanup the avps.
static const conf_parser_t module_config[]
static unlang_action_t cache_expire(rlm_rcode_t *p_result, rlm_cache_t const *inst, request_t *request, rlm_cache_handle_t **handle, fr_value_box_t const *key)
Expire a cache entry (removing it from the datastore)
static unlang_action_t mod_method_clear(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Delete the entries by ${key}.
static unlang_action_t cache_find(rlm_rcode_t *p_result, rlm_cache_entry_t **out, rlm_cache_t const *inst, request_t *request, rlm_cache_handle_t **handle, fr_value_box_t const *key)
Find a cached entry.
static int cache_key_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule)
static int mod_instantiate(module_inst_ctx_t const *mctx)
Create a new rlm_cache_instance.
fr_value_box_t key
Key used to identify entry.
map_list_t maps
Head of the maps list.
fr_unix_time_t created
When the entry was created.
long long int hits
How many times the entry has been retrieved.
@ CACHE_RECONNECT
Handle needs to be reconnected.
@ CACHE_OK
Cache entry found/updated.
@ CACHE_MISS
Cache entry notfound.
fr_unix_time_t expires
When the entry expires.
Configuration for the rlm_cache module.
static int instantiate(module_inst_ctx_t const *mctx)
#define FR_SBUFF_IN(_start, _len_or_end)
#define FR_SBUFF_OUT(_start, _len_or_end)
#define MODULE_NAME_TERMINATOR
#define tmpl_value(_tmpl)
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
#define tmpl_is_attr(vpt)
@ TMPL_TYPE_ATTR
Reference to one or more attributes.
@ TMPL_TYPE_DATA
Value in native boxed format.
int tmpl_attr_afrom_list(TALLOC_CTX *ctx, tmpl_t **out, tmpl_t const *list, fr_dict_attr_t const *da)
Create a new tmpl from a list tmpl and a da.
static bool tmpl_attr_tail_da_is_structural(tmpl_t const *vpt)
Return true if the the last attribute reference is a structural attribute.
static bool tmpl_is_list(tmpl_t const *vpt)
ssize_t tmpl_afrom_attr_substr(TALLOC_CTX *ctx, tmpl_attr_error_t *err, tmpl_t **out, fr_sbuff_t *name, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Parse a string into a TMPL_TYPE_ATTR_* type tmpl_t.
static fr_type_t tmpl_cast_get(tmpl_t *vpt)
static fr_dict_attr_t const * tmpl_list(tmpl_t const *vpt)
tmpl_t * tmpl_alloc(TALLOC_CTX *ctx, tmpl_type_t type, fr_token_t quote, char const *name, ssize_t len)
Create a new heap allocated tmpl_t.
int tmpl_cast_set(tmpl_t *vpt, fr_type_t type)
Set a cast for a tmpl.
fr_type_t tmpl_expanded_type(tmpl_t const *vpt)
Return the native data type of the expression.
Optional arguments passed to vp_tmpl functions.
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
fr_token_t op
The operator that controls insertion of the dst attribute.
tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
map_list_t child
parent map, for nested ones
tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
CONF_ITEM * ci
Config item that the map was created from.
Stores an attribute, a value and various bits of other data.
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
#define talloc_get_type_abort_const
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
#define fr_time_delta_ispos(_a)
static fr_unix_time_t fr_unix_time_from_sec(int64_t sec)
#define fr_unix_time_sub(_a, _b)
Subtract one time from another.
static fr_unix_time_t fr_time_to_unix_time(fr_time_t when)
Convert an fr_time_t (internal time) to our version of unix time (wallclock time)
#define fr_unix_time_lt(_a, _b)
#define fr_unix_time_add(_a, _b)
Add a time/time delta together.
A time delta, a difference in time measured in nanoseconds.
bool required
Argument must be present, and non-empty.
#define XLAT_ARG_PARSER_TERMINATOR
@ XLAT_ACTION_FAIL
An xlat function failed.
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition for a single argument consumend by an xlat function.
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
fr_pair_t * fr_pair_list_next(fr_pair_list_t const *list, fr_pair_t const *item))
Get the next item in a valuepair list after a specific entry.
#define fr_pair_dcursor_init(_cursor, _list)
Initialises a special dcursor with callbacks that will maintain the attr sublists correctly.
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
#define fr_box_time_delta(_val)
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
#define fr_box_time(_val)
static size_t char ** out
#define fr_box_date(_val)
module_ctx_t const * mctx
Synthesised module calling ctx.
void * env_data
Expanded call env data.
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
void xlat_func_call_env_set(xlat_t *x, call_env_method_t const *env_method)
Register call environment of an xlat.
xlat_t * xlat_func_register_module(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx, char const *name, xlat_func_t func, fr_type_t return_type)
Register an xlat function for a module.