not logged in | [Login]
Always use radiusd -X
when debugging!
xlat expansions refer to the alternation and interpolation format used in all versions of the server. xlat expansions the server to dynamically construct strings from the values of attributes and the output of various xlat C functions. These xlat C functions allow data to be retrieved from various sources or transformed (escaped, unescaped, encoded, decoded etc...).
When used in the server configuration xlat expansions usually look like this %{My-Attribute}
or in the case of functions %{my_func:%{My-Attribute}}
. xlat expansions can be nested to any depth.
You should not need to alter the xlat evaluation or parsing code itself as the xlat framework is extensible via runtime registered xlat functions.
In v3 the base64 encoding function looked like this:
static ssize_t base64_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
REQUEST *request, char const *fmt)
{
size_t inlen;
uint8_t *p;
TALLOC_CTX *tmp_ctx = NULL;
ssize_t ret;
VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
/*
* We can accurately calculate the length of the output string
* if it's larger than outlen, the output would be useless so abort.
*/
if ((FR_BASE64_ENC_LENGTH(inlen) + 1) > outlen) {
REDEBUG("xlat failed");
talloc_free(tmp_ctx);
return -1;
}
ret = fr_base64_encode(*out, outlen, p, inlen);
talloc_free(tmp_ctx);
return ret;
}
The xlat functions are passed the instance data of the module that registered them or NULL or the xlat function was registered internally.
There is no per-use instantiation. The xlat_inst
argument was envisaged to be used for this purpose, but the necessary changes were never made to the xlat framework, it is currently unused by all xlat functions.
When the xlat function is called it is passed the result of any nested expansions. The results of these nested expansions are concatenated together in the fmt
argument. This is a normal C buffer and is not binary safe.
Any nested xlat expansions that involve dynamic expansions i.e. reading the value of an attribute or calling another xlat function, will be passed through the escaping function that was registered at the same time as the xlat function when it was passed to xlat_register()
. If no escaping function was registered, the nested values will be provided to the xlat function untouched.
There are two primary limitations of this method.
%{base64:&Attr-To-Encode}
which are then expanded by the xlat function itself. That's what the VALUE_FROM_FMT(tmp_ctx, p, inlen, request, fmt);
macro does in the above base64
code.In v3 output is either to a pre-allocated buffer (the length of which was determined at the time of registration) or alternatively, the xlat function can allocate its own output buffers. Unfortunately, in both these cases, the xlat evaluation code wouldn't necessarily treat the output buffers in a binary safe way, so xlat functions could not output binary data, this has lead to horrible hacks like xlat expansions creating attributes directly.
When xlat functions finish they return the number of bytes they wrote to the output buffer, or a negative integer if an error occurred. Unfortunately, this change was introduced quite late in the v3.0.x branch and the xlat evaluation code was never updated to take advantage of it.
static xlat_action_t xlat_base64(TALLOC_CTX *ctx, fr_cursor_t *out,
REQUEST *request, UNUSED void const *xlat_inst, UNUSED void *xlat_thread_inst,
fr_value_box_t **in)
{
size_t alen;
ssize_t elen;
char *buff;
fr_value_box_t *vb;
/*
* If there's no input, there's no output
*/
if (!in) return XLAT_ACTION_DONE;
if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_OCTETS, true) < 0) {
RPEDEBUG("Failed concatenating input");
return XLAT_ACTION_FAIL;
}
MEM(vb = fr_value_box_alloc_null(ctx));
alen = FR_BASE64_ENC_LENGTH((*in)->vb_length);
MEM(buff = talloc_array(ctx, char, alen + 1));
elen = fr_base64_encode(buff, alen + 1, (*in)->vb_octets, (*in)->vb_length);
if (elen < 0) {
RPEDEBUG("Base64 encoding failed");
talloc_free(vb);
return XLAT_ACTION_FAIL;
}
rad_assert(elen <= alen);
if (fr_value_box_strsnteal(vb, vb, NULL, &buff, elen, false) < 0) {
RPEDEBUG("Failed assigning encoded data buffer to box");
talloc_free(vb);
return XLAT_ACTION_FAIL;
}
fr_cursor_append(out, vb);
return XLAT_ACTION_DONE;
}
In FreeRADIUS <= 3 xlat functions were created and registered primarily by modules so it made sense to pass module instance data to the xlats. This has changed in v4 and module instance data is not provided directly to xlat expansions. If module instance data is still required it can be passed to the xlat_register_async()
function and stored by the per-use instantiation function in per-use instance data.
At registration time per-use global, and per-use thread-specific instantiation functions can be provided, these are used to create per-use instance data, which can be used by xlat functions to pre-compile literal components of input data, or store handles to things like stored procedures.
For the majority of internal xlat functions the per-use xlat instance data is not used and can be marked as UNUSED
.
v4 xlat functions get a linked list of boxed values (fr_value_box_t
) as input. These value boxes are basically a C union and a bit of additional data like taint state, and length (for variable length buffers). They're a convenient way of emulating dynamic typing. The individual chunks of input data passed into the xlat function are not concatenated by the calling code. This is pretty useful for a few reasons.
Because many functions operate with a single argument there's a function provided to concatenate all the input arguments to a string if required. It does this in place for efficiency reasons, and the xlat evaluation code does not expect input arguments to be in a sane/usable state once the xlat function has returned. See the call to fr_value_box_list_concat
in the above base64 code for an example.
After concatenation you can access the input data as a single string with (*in)->vb_strvalue
and get the length with (*in)->vb_length
. You must only use binary safe functions to operate on these strings as they could contain embedded \0
bytes.
If an xlat function wants to process the individual discreet chunks of input it can either traverse each box's ->next
pointer manually or use a fr_cursor_t
and the associated functions.
fr_cursor_t cursor;
fr_value_box_t *vb;
vb = fr_cursor_init(&cursor, in);
if (!vb) {
// Missing first argument
}
vb = fr_cursor_next(&cursor);
if (!vb) {
// Missing second argument
}
An example of using a cursor to traverse input values
Note: The discreet boxes don't actually map to arguments. This is likely to change in future where literals will be broken out into separate boxes on whitespace by the xlat evaluation code.
In v4 all xlat functions must allocate their own output memory. A talloc context ctx
is provided for this.
Function output should be zero or more fr_value_box_t
. A cursor out
is provided to insert additional values into. When the xlat function is called the cursor will be at the end of any previously output. You can use the full range of functions on this cursor, but it's recommended not to advance it, and to fr_cursor_append(&cursor, <vb>)
any values your function produces. If xlat function errors during its execution you can then call fr_cursor_list_free(&cursor)
to free any values that have already been added to the output cursor so execution is idempotent.
See src/lib/util/value.c
and src/lib/util/cursor.c
for value box and cursor manpulation functions and documentation.
xlat functions should return XLAT_ACTION_DONE
on success or XLAT_ACTION_FAIL
on failure. Exactly what happens on failure depends on where the expansion was called. For example, if an xlat expansion is called as part of an update block and it fails, then the entire update block is rolled back.
There are many xlat functions that need to be converted from the old style to the new style. If you want to help out, pick an unconverted function in src/main/xlat_func.c
, and send a pull request.
Here's an example commit for converting the base64 xlat.
Last edited by Arran Cudbard-Bell (arr2036), 2018-05-17 10:11:03
Sponsored by Network RADIUS