Coverage Report

Created: 2026-10-18 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/unlang/call_env.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/**
18
 * $Id: aa27546bb6a8ace73f7ebe5f49b8a026ac54be6a $
19
 *
20
 * @file unlang/call_env.c
21
 * @brief Call environment parsing functions
22
 *
23
 * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
24
 */
25
26
RCSID("$Id: aa27546bb6a8ace73f7ebe5f49b8a026ac54be6a $")
27
28
#include <freeradius-devel/server/log.h>
29
#include <freeradius-devel/server/cf_util.h>
30
#include <freeradius-devel/server/tmpl.h>
31
#include <freeradius-devel/server/section.h>
32
#include <freeradius-devel/unlang/tmpl.h>
33
#include <freeradius-devel/unlang/function.h>
34
35
#include "call_env.h"
36
37
struct call_env_parsed_s {
38
  call_env_parsed_entry_t   entry;    //!< Entry in list of parsed call_env_parsers.
39
40
  union {
41
    tmpl_t const      *tmpl;    //!< Tmpl produced from parsing conf pair.
42
    fr_value_box_t const    *vb;    //!< Value box produced from parsing conf pair.
43
    void const      *ptr;   //!< Data produced from parsing conf pair.
44
  } data;
45
46
  size_t        count;    //!< Number of CONF_PAIRs found, matching the #call_env_parser_t.
47
  size_t        multi_index;  //!< Array index for this instance.
48
  call_env_parser_t const   *rule;    //!< Used to produce this.
49
};
50
0
FR_DLIST_FUNCS(call_env_parsed, call_env_parsed_t, entry)
51
52
#if defined(DEBUG_CALL_ENV)
53
#  define CALL_ENV_DEBUG(_ci, fmt, ...) cf_log_debug(_ci, fmt, ##__VA_ARGS__)
54
#else
55
#  define CALL_ENV_DEBUG(_ci, ...)
56
#endif
57
58
/** Parse the result of call_env tmpl expansion
59
 */
60
static inline CC_HINT(always_inline)
61
call_env_result_t call_env_result(TALLOC_CTX *ctx, request_t *request, void *out, call_env_parsed_t const *env,
62
          fr_value_box_list_t *tmpl_expanded)
63
0
{
64
0
  fr_value_box_t  *vb;
65
66
0
  vb = fr_value_box_list_head(tmpl_expanded);
67
0
  if (!vb) {
68
0
    if (!call_env_nullable(env->rule->flags)) {
69
0
      RPEDEBUG("Failed to evaluate required module option %s = %s", env->rule->name, env->data.tmpl->name);
70
0
      return CALL_ENV_MISSING;
71
0
    }
72
0
    return CALL_ENV_SUCCESS;
73
0
  }
74
75
  /*
76
   *  Concatenate multiple boxes if needed
77
   */
78
0
  if ((call_env_concat(env->rule->flags) || call_env_attribute(env->rule->flags)) &&
79
0
      (env->rule->pair.cast_type != FR_TYPE_VOID) &&
80
0
      fr_value_box_list_concat_in_place(vb, vb, tmpl_expanded, env->rule->pair.cast_type,
81
0
                FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0 ) {
82
0
    RPEDEBUG("Failed concatenating values for %s", env->rule->name);
83
0
    return CALL_ENV_INVALID;
84
0
  }
85
86
0
  if (call_env_single(env->rule->flags) && (fr_value_box_list_num_elements(tmpl_expanded) > 1)) {
87
0
    RPEDEBUG("%u values found for %s.  Only one is allowed",
88
0
       fr_value_box_list_num_elements(tmpl_expanded), env->rule->name);
89
0
    return CALL_ENV_INVALID;
90
0
  }
91
92
0
  while ((vb = fr_value_box_list_pop_head(tmpl_expanded))) {
93
0
    switch (env->rule->pair.type) {
94
0
    case CALL_ENV_RESULT_TYPE_VALUE_BOX:
95
0
      fr_value_box_copy_shallow(ctx, (fr_value_box_t *)(out), vb);
96
0
      break;
97
98
0
    case CALL_ENV_RESULT_TYPE_VALUE_BOX_LIST:
99
0
      if (!fr_value_box_list_initialised((fr_value_box_list_t *)out)) fr_value_box_list_init((fr_value_box_list_t *)out);
100
0
      fr_value_box_list_insert_tail((fr_value_box_list_t *)out, vb);
101
0
      break;
102
103
0
    default:
104
0
      fr_assert(0);
105
0
      break;
106
0
    }
107
0
  }
108
109
0
  return CALL_ENV_SUCCESS;
110
0
}
111
112
/** Context to keep track of expansion of call environments
113
 *
114
 */
115
typedef struct {
116
  call_env_result_t     *result;    //!< Where to write the return code of callenv expansion.
117
  unlang_result_t       expansion_result; //!< The result of calling the call env expansions functions.
118
  call_env_t const      *call_env;    //!< Call env being expanded.
119
  call_env_parsed_t const     *last_expanded;   //!< The last expanded tmpl.
120
  fr_value_box_list_t     tmpl_expanded;    //!< List to write value boxes to as tmpls are expanded.
121
  void          **data;     //!< Final destination structure for value boxes.
122
} call_env_rctx_t;
123
124
static unlang_action_t call_env_expand_repeat(UNUSED unlang_result_t *p_result, request_t *request, void *uctx);
125
126
/** Start the expansion of a call environment tmpl.
127
 *
128
 */
129
static unlang_action_t call_env_expand_start(UNUSED unlang_result_t *p_result, request_t *request, void *uctx)
130
0
{
131
0
  call_env_rctx_t *call_env_rctx = talloc_get_type_abort(uctx, call_env_rctx_t);
132
0
  TALLOC_CTX  *ctx;
133
0
  call_env_parsed_t const *env = NULL;
134
0
  void    **out;
135
136
0
again:
137
0
  while ((call_env_rctx->last_expanded = call_env_parsed_next(&call_env_rctx->call_env->parsed, call_env_rctx->last_expanded))) {
138
0
    env = call_env_rctx->last_expanded;
139
0
    fr_assert(env != NULL);
140
141
    /*
142
     *  Subsections are expanded during parsing to produce a list of
143
     *  call_env_parsed_t.  They are not expanded at runtime.
144
     */
145
0
    fr_assert_msg(call_env_is_subsection(env->rule->flags) == false, "Subsections cannot be expanded at runtime");
146
147
    /*
148
     *  If there's an offset to copy the output to, do that.
149
     *  We may also need to expand the tmpl_t and write out the result
150
     *  to the pair offset.
151
     */
152
0
    if (env->rule->pair.parsed.offset >= 0) {
153
      /*
154
       *  If we only need the tmpl or data, just set the pointer and move the next.
155
       */
156
0
      out = (void **)((uint8_t *)*call_env_rctx->data + env->rule->pair.parsed.offset);
157
158
      /*
159
       *  For multi pair options, the pointers need to go into a new array.
160
       *  When processing the first expansion, allocate the array, and for
161
       *  all expansions adjust the `out` pointer to write to.
162
       */
163
0
      if (call_env_multi(env->rule->flags)) {
164
0
        void **array;
165
0
        if (env->multi_index == 0) {
166
          /*
167
           *  Coverity thinks talloc_zero_array being called with the type `void *`
168
           *  is a size mismatch.  This works round the false positive.
169
           */
170
0
          MEM(array = _talloc_zero_array((*call_env_rctx->data), sizeof(uint8_t *),
171
0
                  env->count, "void *"));
172
0
          *out = array;
173
0
        }
174
0
        array = (void **)(*out);
175
0
        out = (void **)((uint8_t *)array + sizeof(void *) * env->multi_index);
176
0
      }
177
178
0
      switch (env->rule->pair.parsed.type) {
179
0
      case CALL_ENV_PARSE_TYPE_TMPL:
180
0
        *out = UNCONST(tmpl_t *, env->data.tmpl);
181
0
        break;
182
183
0
      case CALL_ENV_PARSE_TYPE_VALUE_BOX:
184
0
        *out = UNCONST(fr_value_box_t *, env->data.vb);
185
0
        continue; /* Can't evaluate these */
186
187
0
      case CALL_ENV_PARSE_TYPE_VOID:
188
0
        *out = UNCONST(void *, env->data.ptr);
189
0
        continue; /* Can't evaluate these */
190
0
      }
191
0
    }
192
193
    /*
194
     *  If this is not parse_only, we need to expand the tmpl.
195
     */
196
0
    if ((env->rule->pair.parsed.type == CALL_ENV_PARSE_TYPE_TMPL) && !call_env_parse_only(env->rule->flags)) break;
197
0
  }
198
199
0
  if (!call_env_rctx->last_expanded) { /* No more! */
200
0
    if (call_env_rctx->result) *call_env_rctx->result = CALL_ENV_SUCCESS;
201
0
    return UNLANG_ACTION_CALCULATE_RESULT;
202
0
  }
203
204
0
  ctx = *call_env_rctx->data;
205
206
0
  fr_assert(env != NULL);
207
208
  /*
209
   *  Multi pair options should allocate boxes in the context of the array
210
   */
211
0
  if (call_env_multi(env->rule->flags)) {
212
0
    out = (void **)((uint8_t *)(*call_env_rctx->data) + env->rule->pair.offset);
213
214
    /*
215
     *  For multi pair options, allocate the array before expanding the first entry.
216
     */
217
0
    if (env->multi_index == 0) {
218
0
      void *array;
219
0
      MEM(array = _talloc_zero_array((*call_env_rctx->data), env->rule->pair.size,
220
0
                   env->count, env->rule->pair.type_name));
221
0
      *out = array;
222
0
    }
223
0
    ctx = *out;
224
0
  }
225
226
  /*
227
   *  If the tmpl is already data, we can just copy the data to the right place.
228
   */
229
0
  if (tmpl_is_data(call_env_rctx->last_expanded->data.tmpl)) {
230
0
    fr_value_box_t    *vb;
231
0
    call_env_result_t result;
232
0
    void      *box_out;
233
234
0
    MEM(vb = fr_value_box_acopy(ctx, &call_env_rctx->last_expanded->data.tmpl->data.literal));
235
0
    fr_value_box_list_insert_tail(&call_env_rctx->tmpl_expanded, vb);
236
237
0
    box_out = ((uint8_t*)(*call_env_rctx->data)) + env->rule->pair.offset;
238
239
0
    if (call_env_multi(env->rule->flags)) {
240
0
      void *array = *(void **)box_out;
241
0
      box_out = ((uint8_t *)array) + env->rule->pair.size * env->multi_index;
242
0
    }
243
244
0
    result = call_env_result(*call_env_rctx->data, request, box_out, env, &call_env_rctx->tmpl_expanded);
245
0
    if (result != CALL_ENV_SUCCESS) {
246
0
      if (call_env_rctx->result) *call_env_rctx->result = result;
247
0
      return UNLANG_ACTION_FAIL;
248
0
    }
249
0
    goto again;
250
0
  }
251
252
0
  if (unlang_tmpl_push(ctx, &call_env_rctx->expansion_result, &call_env_rctx->tmpl_expanded, request,
253
0
           call_env_rctx->last_expanded->data.tmpl,
254
0
           NULL, UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_FAIL;
255
256
0
  return UNLANG_ACTION_PUSHED_CHILD;
257
0
}
258
259
/** Extract expanded call environment tmpl and store in env_data
260
 *
261
 * If there are more call environments to evaluate, push the next one.
262
 */
263
static unlang_action_t call_env_expand_repeat(UNUSED unlang_result_t *p_result, request_t *request, void *uctx)
264
0
{
265
0
  void      *out = NULL;
266
0
  call_env_rctx_t   *call_env_rctx = talloc_get_type_abort(uctx, call_env_rctx_t);
267
0
  call_env_parsed_t const *env;
268
0
  call_env_result_t result;
269
270
  /*
271
   *  Something went wrong expanding the call env
272
   *  return fail.
273
   *
274
   *  The module should not be executed.
275
   */
276
0
  if (call_env_rctx->expansion_result.rcode == RLM_MODULE_FAIL) return UNLANG_ACTION_FAIL;
277
278
0
  env = call_env_rctx->last_expanded;
279
0
  if (!env) return UNLANG_ACTION_CALCULATE_RESULT;
280
281
  /*
282
   *  Find the location of the output
283
   */
284
0
  out = ((uint8_t*)(*call_env_rctx->data)) + env->rule->pair.offset;
285
286
  /*
287
   *  If this is a multi pair option, the output is an array.
288
   *  Find the correct offset in the array
289
   */
290
0
  if (call_env_multi(env->rule->flags)) {
291
0
    void *array = *(void **)out;
292
0
    out = ((uint8_t *)array) + env->rule->pair.size * env->multi_index;
293
0
  }
294
295
0
  result = call_env_result(*call_env_rctx->data, request, out, env, &call_env_rctx->tmpl_expanded);
296
0
  if (result != CALL_ENV_SUCCESS) {
297
0
    if (call_env_rctx->result) *call_env_rctx->result = result;
298
0
    return UNLANG_ACTION_FAIL;
299
0
  }
300
301
0
  if (!call_env_parsed_next(&call_env_rctx->call_env->parsed, env)) {
302
0
    if (call_env_rctx->result) *call_env_rctx->result = CALL_ENV_SUCCESS;
303
0
    return UNLANG_ACTION_CALCULATE_RESULT;
304
0
  }
305
306
0
  return unlang_function_push_with_result(&call_env_rctx->expansion_result,
307
0
            request,
308
0
            call_env_expand_start,
309
0
            call_env_expand_repeat,
310
0
            NULL,
311
0
            FR_SIGNAL_NONE, UNLANG_SUB_FRAME,
312
0
            call_env_rctx);
313
0
}
314
315
/** Initialise the expansion of a call environment
316
 *
317
 * @param[in] ctx   in which to allocate destination structure for resulting value boxes.
318
 * @param[in] request   Current request.
319
 * @param[out] env_result Where to write the result of the callenv expansion.  May be NULL
320
 * @param[in,out] env_data  Where the destination structure should be created.
321
 * @param[in] call_env    Call environment being expanded.
322
 */
323
unlang_action_t call_env_expand(TALLOC_CTX *ctx, request_t *request, call_env_result_t *env_result, void **env_data,
324
        call_env_t const *call_env)
325
0
{
326
0
  call_env_rctx_t *call_env_rctx;
327
328
0
  MEM(call_env_rctx = talloc_zero(ctx, call_env_rctx_t));
329
0
  MEM(*env_data = talloc_zero_array(ctx, uint8_t, call_env->method->inst_size));
330
0
  talloc_set_name_const(*env_data, call_env->method->inst_type);
331
0
  call_env_rctx->result = env_result;
332
0
  if (env_result) *env_result = CALL_ENV_INVALID; /* Make sure we ran to completion*/
333
0
  call_env_rctx->data = env_data;
334
0
  call_env_rctx->call_env = call_env;
335
0
  fr_value_box_list_init(&call_env_rctx->tmpl_expanded);
336
337
0
  return unlang_function_push_with_result(&call_env_rctx->expansion_result,
338
0
            request,
339
0
            call_env_expand_start,
340
0
            call_env_expand_repeat,
341
0
            NULL,
342
0
            FR_SIGNAL_NONE, UNLANG_SUB_FRAME,
343
0
            call_env_rctx);
344
0
}
345
346
/** Allocates a new call env parsed struct
347
 *
348
 */
349
static inline CC_HINT(always_inline)
350
call_env_parsed_t *call_env_parsed_alloc(TALLOC_CTX *ctx, call_env_parser_t const *rule)
351
{
352
  call_env_parsed_t *call_env_parsed;
353
354
  MEM(call_env_parsed = talloc_zero(ctx, call_env_parsed_t));
355
  call_env_parsed->rule = rule;
356
  call_env_parsed->count = 1;
357
  call_env_parsed->multi_index = 0;
358
359
  return call_env_parsed;
360
}
361
362
static inline CC_HINT(always_inline)
363
int call_env_parsed_valid(call_env_parsed_t const *parsed, CONF_ITEM const *ci, call_env_parser_t const *rule)
364
0
{
365
0
  tmpl_t const *tmpl;
366
367
0
  if (rule->pair.parsed.type != CALL_ENV_PARSE_TYPE_TMPL) return 0;
368
369
0
  tmpl = parsed->data.tmpl;
370
0
  switch (tmpl->type) {
371
    /*
372
     *  These can't be created from a call_env flag which is marked as an attribute.
373
     */
374
0
  case TMPL_TYPE_DATA:
375
0
  case TMPL_TYPE_EXEC:
376
0
  case TMPL_TYPE_XLAT:
377
0
    fr_assert(!call_env_attribute(rule->flags));
378
0
    break;
379
380
    /*
381
     *  This can be created from multiple types of flags, not just an attribute one.
382
     */
383
0
  case TMPL_TYPE_ATTR:
384
0
    break;
385
386
0
  default:
387
0
    cf_log_err(ci, "'%s' expands to invalid tmpl type %s", tmpl->name,
388
0
         tmpl_type_to_str(tmpl->type));
389
0
    return -1;
390
0
  }
391
392
0
  return 0;
393
0
}
394
395
/** Standard function we use for parsing call env pairs
396
 *
397
 * @note This is called where no custom pair parsing function is provided, but may be called by custom functions to avoid
398
 *       duplicating the standard parsing code.
399
 *
400
 * @param[in] ctx   to allocate any data in.
401
 * @param[out] out    Where to write the result of parsing.
402
 * @param[in] t_rules   we're parsing attributes with.  Contains the default dictionary and nested 'caller' tmpl_rules_t.
403
 * @param[in] ci    The #CONF_SECTION or #CONF_PAIR to parse.
404
 * @param[in] cec   information about the call.
405
 * @param[in] rule    Parse rules - How the #CONF_PAIR or #CONF_SECTION should be converted.
406
 * @return
407
 *  - 0 on success.
408
 *  - -1 on failure.
409
 */
410
int call_env_parse_pair(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
411
      UNUSED call_env_ctx_t const *cec, call_env_parser_t const *rule)
412
0
{
413
0
  CONF_PAIR const *to_parse = cf_item_to_pair(ci);
414
0
  tmpl_t    *parsed_tmpl;
415
0
  fr_token_t  quote = cf_pair_value_quote(to_parse);
416
417
  /*
418
   *  If it's marked as containing an attribute reference,
419
   *  then always parse it as an attribute reference.
420
   */
421
0
  if (call_env_attribute(rule->flags) ||
422
0
      ((quote == T_BARE_WORD) && call_env_bare_word_attribute(rule->flags))) {
423
0
    if (tmpl_afrom_attr_str(ctx, NULL, &parsed_tmpl, cf_pair_value(to_parse), t_rules) <= 0) {
424
0
      return -1;
425
0
    }
426
0
  } else {
427
0
    if (tmpl_afrom_substr(ctx, &parsed_tmpl,
428
0
              &FR_SBUFF_IN(cf_pair_value(to_parse), talloc_strlen(cf_pair_value(to_parse))),
429
0
              quote, value_parse_rules_quoted[cf_pair_value_quote(to_parse)],
430
0
              t_rules) < 0) {
431
0
      return -1;
432
0
    }
433
0
  }
434
0
  *(void **)out = parsed_tmpl;
435
436
  /*
437
   *  All attributes and functions should be resolved at this point
438
   */
439
0
  return tmpl_resolve(parsed_tmpl, NULL);
440
0
}
441
442
/** Parse per call env
443
 *
444
 * Used for config options which must be parsed in the context in which
445
 * the module is being called.
446
 *
447
 * @param[in] ctx   To allocate parsed environment in.
448
 * @param[out] parsed   Where to write parsed environment.
449
 * @param[in] name    Module name for error messages.
450
 * @param[in] t_rules   controlling how the call env is parsed.
451
 * @param[in] cs    Module config.
452
 * @param[in] cec   information about the call.
453
 * @param[in] rule    to parse.
454
 * @return
455
 *  - 0 on success;
456
 *  - <0 on failure;
457
 */
458
int call_env_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *parsed, char const *name, tmpl_rules_t const *t_rules,
459
        CONF_SECTION const *cs,
460
0
        call_env_ctx_t const *cec, call_env_parser_t const *rule) {
461
0
  CONF_PAIR const   *cp, *next;
462
0
  call_env_parsed_t *call_env_parsed = NULL;
463
0
  ssize_t     count, multi_index;
464
0
  call_env_parser_t const *rule_p = rule;
465
466
0
  while (rule_p->name) {
467
0
    CALL_ENV_DEBUG(cs, "%s: Parsing call env data for %s", name, section_name_str(rule_p->name));
468
469
0
    if (call_env_is_subsection(rule_p->flags)) {
470
0
      CONF_SECTION const *subcs;
471
0
      subcs = cf_section_find(cs, rule_p->name, rule_p->section.name2);
472
0
      if (!subcs) {
473
        /*
474
         *  No CONF_SECTION, but it's required.  That's an error.
475
         */
476
0
        if (call_env_required(rule_p->flags)) {
477
0
          cf_log_err(cs, "Module %s missing required section \"%s\"", name, rule_p->name);
478
0
          return -1;
479
0
        }
480
481
        /*
482
         *  No flag saying "do callback even if subcs is missing", just skip the
483
         *  callbacks.
484
         */
485
0
        if (!call_env_parse_missing(rule_p->flags)) goto next;
486
0
      }
487
488
      /*
489
       *  Hand off to custom parsing function if there is one...
490
       */
491
0
      if (rule_p->section.func) {
492
        /*
493
         *  Record our position so we can process any new entries
494
         *  after the callback returns.
495
         */
496
0
        call_env_parsed_t *last = call_env_parsed_tail(parsed);
497
498
0
        CALL_ENV_DEBUG(cs, "%s: Calling subsection callback %p", name, rule_p->section.func);
499
500
0
        if (rule_p->section.func(ctx, parsed, t_rules, cf_section_to_item(subcs), cec, rule_p) < 0) {
501
0
          cf_log_perr(cs, "Failed parsing configuration section %s",
502
0
                rule_p->name == CF_IDENT_ANY ? cf_section_name(cs) : rule_p->name);
503
0
          return -1;
504
0
        }
505
506
0
        CALL_ENV_DEBUG(subcs, "%s: Callback returned %u parsed call envs", name,
507
0
                 call_env_parsed_num_elements(parsed));
508
509
        /*
510
         *  We _could_ fix up count and multi_index on behalf of
511
         *  the callback, but there's no guarantee that all call_env_parsed_t
512
         *  are related to each other, so we don't.
513
         */
514
0
        call_env_parsed = last;
515
0
        while ((call_env_parsed = call_env_parsed_next(parsed, call_env_parsed))) {
516
0
          CALL_ENV_DEBUG(subcs, "%s: Checking parsed env %p", name, rule_p->section.func);
517
0
          if (call_env_parsed_valid(call_env_parsed, cf_section_to_item(subcs), rule_p) < 0) {
518
0
            cf_log_err(cf_section_to_item(subcs), "Invalid data produced by %s",
519
0
                 rule_p->name == CF_IDENT_ANY ? cf_section_name(cs) : rule_p->name);
520
0
            return -1;
521
0
          }
522
0
        }
523
0
        goto next;
524
0
      }
525
526
0
      if (call_env_parse(ctx, parsed, name, t_rules, subcs, cec, rule_p->section.subcs) < 0) {
527
0
        CALL_ENV_DEBUG(cs, "%s: Recursive call failed", name);
528
0
        return -1;
529
0
      }
530
0
      goto next;
531
0
    }
532
533
0
    cp = cf_pair_find(cs, rule_p->name);
534
535
0
    if (!cp && !rule_p->pair.dflt) {
536
0
      if (!call_env_required(rule_p->flags)) goto next;
537
538
0
      cf_log_err(cs, "Missing required config item '%s'", rule_p->name);
539
0
      return -1;
540
0
    }
541
542
    /*
543
     *  Check for additional conf pairs and error
544
     *  if there is one and multi is not allowed.
545
     */
546
0
    if (!call_env_multi(rule_p->flags) && ((next = cf_pair_find_next(cs, cp, rule_p->name)))) {
547
0
      cf_log_err(cf_pair_to_item(next), "Invalid duplicate configuration item '%s'", rule_p->name);
548
0
      return -1;
549
0
    }
550
551
0
    count = cf_pair_count(cs, rule_p->name);
552
0
    if (count == 0) count = 1;
553
554
0
    for (multi_index = 0; multi_index < count; multi_index++) {
555
0
      CONF_PAIR   *tmp_cp = NULL;
556
0
      CONF_PAIR const   *to_parse;
557
0
      tmpl_rules_t    our_rules = {};
558
0
      fr_type_t     type = rule_p->pair.cast_type;
559
0
      call_env_parse_pair_t func = rule_p->pair.func ? rule_p->pair.func : call_env_parse_pair;
560
561
0
      if (t_rules) {
562
0
        our_rules.parent = t_rules->parent;
563
0
        our_rules.attr.dict_def = t_rules->attr.dict_def;
564
0
        our_rules.escape = rule_p->pair.escape; /* Escape rules will now get embedded in the tmpl_t and used at evaluation */
565
0
      }
566
567
0
      our_rules.attr.list_def = request_attr_request;
568
0
      our_rules.cast = ((type == FR_TYPE_VOID) ? FR_TYPE_NULL : type);
569
0
      our_rules.literals_safe_for = rule_p->pair.literals_safe_for;
570
571
0
      call_env_parsed = call_env_parsed_alloc(ctx, rule_p);
572
0
      call_env_parsed->count = count;
573
0
      call_env_parsed->multi_index = multi_index;
574
575
      /*
576
       *  With the conf_parser code we can add default pairs
577
       *  if they don't exist, but as the same CONF_SECTIONs
578
       *  are evaluated multiple times for each module call
579
       *  we can't do that here.
580
       */
581
0
      if (cp) {
582
0
        if (call_env_force_quote(rule_p->flags)) {
583
0
          to_parse = tmp_cp = cf_pair_alloc(NULL,
584
0
                          cf_pair_attr(cp), cf_pair_value(cp), cf_pair_operator(cp),
585
0
                    cf_pair_attr_quote(cp),
586
0
                    call_env_force_quote(rule_p->flags) ? rule_p->pair.dflt_quote : cf_pair_value_quote(cp));
587
0
        } else {
588
0
          to_parse = cp;
589
0
        }
590
0
      } else {
591
0
        to_parse = tmp_cp = cf_pair_alloc(NULL,
592
0
                  rule_p->name, rule_p->pair.dflt, T_OP_EQ,
593
0
                  T_BARE_WORD, rule_p->pair.dflt_quote);
594
0
      }
595
596
      /*
597
       *  The parsing function can either produce a tmpl_t as tmpl_afrom_substr
598
       *  would, or produce a custom structure, which will be copied into the
599
       *  result structure.
600
       */
601
0
      if (unlikely(func(ctx, &call_env_parsed->data, &our_rules, cf_pair_to_item(to_parse), cec, rule_p) < 0)) {
602
0
      error:
603
0
        cf_log_perr(to_parse, "Failed to parse configuration item '%s = %s'", rule_p->name, cf_pair_value(to_parse));
604
0
        talloc_free(call_env_parsed);
605
0
        talloc_free(tmp_cp);
606
0
        return -1;
607
0
      }
608
0
      if (!call_env_parsed->data.ptr) {
609
0
        talloc_free(call_env_parsed);
610
0
        goto next_pair;
611
0
      }
612
613
      /*
614
       *  Ensure only valid data is produced.
615
       */
616
0
      if (call_env_parsed_valid(call_env_parsed, cf_pair_to_item(to_parse), rule_p) < 0) goto error;
617
618
0
      call_env_parsed_insert_tail(parsed, call_env_parsed);
619
0
    next_pair:
620
0
      talloc_free(tmp_cp);
621
0
      cp = cf_pair_find_next(cs, cp, rule_p->name);
622
0
    }
623
0
  next:
624
0
    rule_p++;
625
0
  }
626
627
0
  CALL_ENV_DEBUG(cs, "Returning after processing %u rules", (unsigned int)(rule_p - rule));
628
629
0
  return 0;
630
0
}
631
632
/**  Perform a quick assessment of how many parsed call env will be produced.
633
 *
634
 * @param[in,out] names_len Where to write the sum of bytes required to represent
635
 *        the strings which will be parsed as tmpls.  This is required
636
 *        to pre-allocate space for the tmpl name buffers.
637
 * @param[in] cs    Conf section to search for pairs.
638
 * @param[in] call_env    to parse.
639
 * @return Number of parsed_call_env expected to be required.
640
 */
641
static size_t call_env_count(size_t *names_len, CONF_SECTION const *cs, call_env_parser_t const *call_env)
642
0
{
643
0
  size_t  pair_count, tmpl_count = 0;
644
0
  CONF_PAIR const *cp;
645
646
0
  while (call_env->name) {
647
0
    if (call_env_is_subsection(call_env->flags)) {
648
0
      CONF_SECTION const *subcs;
649
0
      subcs = cf_section_find(cs, call_env->name, call_env->section.name2);
650
0
      if (!subcs) goto next;
651
652
      /*
653
       *  May only be a callback...
654
       */
655
0
      if (call_env->section.subcs) tmpl_count += call_env_count(names_len, subcs, call_env->section.subcs);
656
0
      goto next;
657
0
    }
658
0
    pair_count = 0;
659
0
    cp = NULL;
660
0
    while ((cp = cf_pair_find_next(cs, cp, call_env->name))) {
661
0
      pair_count++;
662
0
      *names_len += talloc_array_length(cf_pair_value(cp));
663
0
    }
664
0
    if (!pair_count && call_env->pair.dflt) {
665
0
      pair_count = 1;
666
0
      *names_len += strlen(call_env->pair.dflt);
667
0
    }
668
0
    tmpl_count += pair_count;
669
0
  next:
670
0
    call_env++;
671
0
  }
672
673
0
  return tmpl_count;
674
0
}
675
676
/** Allocate a new call_env_parsed_t structure and add it to the list of parsed call envs
677
 *
678
 * @note tmpl_t and void * should be allocated in the context of the call_env_parsed_t
679
 *
680
 * @param[in] ctx to allocate the new call_env_parsed_t in.
681
 * @param[out] head to add the new call_env_parsed_t to.
682
 * @param[in] rule  to base call_env_parsed_t around.  MUST NOT BE THE RULE PASSED TO THE CALLBACK.
683
 *      The rule passed to the callback describes how to parse a subsection, but the
684
 *      subsection callback is adding rules describing how to parse its children.
685
 * @return    The new call_env_parsed_t.
686
 */
687
call_env_parsed_t *call_env_parsed_add(TALLOC_CTX *ctx, call_env_parsed_head_t *head, call_env_parser_t const *rule)
688
{
689
  call_env_parsed_t *call_env_parsed;
690
  call_env_parser_t *our_rules;
691
692
  fr_assert_msg(call_env_is_subsection(rule->flags) == false, "Rules added by subsection callbacks cannot be subsections themselves");
693
694
  MEM(call_env_parsed = call_env_parsed_alloc(ctx, rule));
695
696
  /*
697
   *  Copy the rule the callback provided, there's no guarantee
698
   *  it's not stack allocated, or in some way ephemeral.
699
   */
700
  MEM(our_rules = talloc(call_env_parsed, call_env_parser_t));
701
  memcpy(our_rules, rule, sizeof(*our_rules));
702
  call_env_parsed->rule = our_rules;
703
  call_env_parsed_insert_tail(head, call_env_parsed);
704
705
  return call_env_parsed;
706
}
707
708
/** Assign a tmpl to a call_env_parsed_t
709
 *
710
 * @note Intended to be used by subsection callbacks to add a tmpl to be
711
 *  evaluated during the call.
712
 *
713
 * @param[in] parsed    to assign the tmpl to.
714
 * @param[in] tmpl    to assign.
715
 */
716
void call_env_parsed_set_tmpl(call_env_parsed_t *parsed, tmpl_t const *tmpl)
717
0
{
718
0
  fr_assert_msg(parsed->rule->pair.parsed.type == CALL_ENV_PARSE_TYPE_TMPL, "Rule must indicate parsed output is a tmpl_t");
719
0
  parsed->data.tmpl = tmpl;
720
0
}
721
722
/** Assign a value box to a call_env_parsed_t
723
 *
724
 * @note Intended to be used by subsection callbacks to set a static boxed
725
 *  value to be written out to the result structure.
726
 *
727
 * @param[in] parsed    to assign the tmpl to.
728
 * @param[in] vb    to assign.
729
 */
730
void call_env_parsed_set_value(call_env_parsed_t *parsed, fr_value_box_t const *vb)
731
0
{
732
0
  fr_assert_msg(parsed->rule->pair.parsed.type == CALL_ENV_PARSE_TYPE_VALUE_BOX, "Rule must indicate parsed output is a value box");
733
0
  parsed->data.vb = vb;
734
0
}
735
736
/** Assign data to a call_env_parsed_t
737
 *
738
 * @note Intended to be used by subsection callbacks to set arbitrary data
739
 *       to be written out to the result structure.
740
 *
741
 * @param[in] parsed    to assign the tmpl to.
742
 * @param[in] data    to assign.
743
 */
744
void call_env_parsed_set_data(call_env_parsed_t *parsed, void const *data)
745
0
{
746
0
  fr_assert_msg(parsed->rule->pair.parsed.type == CALL_ENV_PARSE_TYPE_VOID, "Rule must indicate parsed output is a void *");
747
0
  parsed->data.ptr = data;
748
0
}
749
750
/** Assign a count and index to a call_env_parsed_t
751
 *
752
 * @note Intended to be used by subsection callbacks to indicate related
753
 *  call_env_parsed_t.
754
 *
755
 * @param[in] parsed    to modify metadata of.
756
 * @param[in] count   to assign.
757
 * @param[in] index   to assign.
758
 */
759
void call_env_parsed_set_multi_index(call_env_parsed_t *parsed, size_t count, size_t index)
760
0
{
761
0
  fr_assert_msg(call_env_multi(parsed->rule->flags), "Rule must indicate parsed output is a multi pair");
762
0
  parsed->multi_index = index;
763
0
  parsed->count = count;
764
0
}
765
766
/** Remove a call_env_parsed_t from the list of parsed call envs
767
 *
768
 * @note Intended to be used by subsection callbacks to remove a call_env_parsed_t
769
 *  from the list of parsed call envs (typically on error).
770
 *
771
 * @param[in] parsed    to remove parsed data from.
772
 * @param[in] ptr   to remove.
773
 */
774
void call_env_parsed_free(call_env_parsed_head_t *parsed, call_env_parsed_t *ptr)
775
0
{
776
0
  call_env_parsed_remove(parsed, ptr);
777
0
  talloc_free(ptr);
778
0
}
779
780
/** Given a call_env_method, parse all call_env_pair_t in the context of a specific call to an xlat or module method
781
 *
782
 * @param[in] ctx   to allocate the call_env_t in.
783
 * @param[in] name    Module name for error messages.
784
 * @param[in] call_env_method containing the call_env_pair_t to evaluate against the specified CONF_SECTION.
785
 * @param[in] t_rules   that control how call_env_pair_t are parsed.
786
 * @param[in] cs    to parse in the context of the call.
787
 * @param[in] cec   information about how the call is being made.
788
 * @return
789
 *  - A new call_env_t on success.
790
 *  - NULL on failure.
791
 */
792
call_env_t *call_env_alloc(TALLOC_CTX *ctx, char const *name, call_env_method_t const *call_env_method,
793
         tmpl_rules_t const *t_rules, CONF_SECTION *cs, call_env_ctx_t const *cec)
794
0
{
795
0
  unsigned int  count;
796
0
  size_t    names_len = 0;
797
0
  call_env_t  *call_env;
798
799
  /*
800
   *  Only used if caller doesn't use a more specific assert
801
   */
802
0
  fr_assert_msg(call_env_method->inst_size, "inst_size 0 for %s, method_env (%p)", name, call_env_method);
803
804
  /*
805
   *  Firstly assess how many parsed env there will be and create a talloc pool to hold them.
806
   *  The pool size is a rough estimate based on each tmpl also allocating at least two children,
807
   *  for which we allow twice the length of the value to be parsed.
808
   */
809
0
  count = call_env_count(&names_len, cs, call_env_method->env);
810
811
  /*
812
   *  Pre-allocated headers:
813
   *  1 header for the call_env_pair_parsed_t, 1 header for the tmpl_t, 1 header for the name,
814
   *  one header for the value.
815
   *
816
   *  Pre-allocated memory:
817
   *  ((sizeof(call_env_pair_parsed_t) + sizeof(tmpl_t)) * count) + (names of tmpls * 2)... Not sure what
818
   *  the * 2 is for, maybe for slop?
819
   */
820
0
  MEM(call_env = talloc_pooled_object(ctx, call_env_t, count * 4, (sizeof(call_env_parser_t) + sizeof(tmpl_t)) * count + names_len * 2));
821
0
  call_env->method = call_env_method;
822
0
  call_env_parsed_init(&call_env->parsed);
823
0
  if (call_env_parse(call_env, &call_env->parsed, name, t_rules, cs, cec, call_env_method->env) < 0) {
824
0
    talloc_free(call_env);
825
0
    return NULL;
826
0
  }
827
828
0
  return call_env;
829
0
}
830
831
/** Bit-pos indexed table of `CALL_ENV_FLAG_*` names. */
832
static fr_table_num_indexed_bit_pos_t const call_env_flag_table[] = {
833
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_REQUIRED),
834
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_CONCAT),
835
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_SINGLE),
836
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_MULTI),
837
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_NULLABLE),
838
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_FORCE_QUOTE),
839
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_PARSE_ONLY),
840
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_ATTRIBUTE),
841
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_SUBSECTION),
842
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_PARSE_MISSING),
843
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_SECRET),
844
  FR_TABLE_INDEXED_BIT_POS_ENTRY(CALL_ENV_FLAG_BARE_WORD_ATTRIBUTE),
845
};
846
static size_t call_env_flag_table_len = NUM_ELEMENTS(call_env_flag_table);
847
848
char const *call_env_flag_to_enum_str(call_env_flags_t mask)
849
0
{
850
0
  return fr_table_str_by_value(call_env_flag_table, mask, NULL);
851
0
}
852
853
static fr_table_num_indexed_t const call_env_parse_type_table[] = {
854
  FR_TABLE_INDEXED_ENTRY(CALL_ENV_PARSE_TYPE_TMPL),
855
  FR_TABLE_INDEXED_ENTRY(CALL_ENV_PARSE_TYPE_VALUE_BOX),
856
  FR_TABLE_INDEXED_ENTRY(CALL_ENV_PARSE_TYPE_VOID),
857
};
858
static size_t call_env_parse_type_table_len = NUM_ELEMENTS(call_env_parse_type_table);
859
860
char const *call_env_parse_type_to_enum_str(call_env_parse_type_t t)
861
0
{
862
0
  return fr_table_str_by_value(call_env_parse_type_table, t, "CALL_ENV_PARSE_TYPE_VOID");
863
0
}
864
865
static fr_table_num_indexed_t const call_env_result_type_table[] = {
866
  FR_TABLE_INDEXED_ENTRY(CALL_ENV_RESULT_TYPE_VALUE_BOX),
867
  FR_TABLE_INDEXED_ENTRY(CALL_ENV_RESULT_TYPE_VALUE_BOX_LIST),
868
};
869
static size_t call_env_result_type_table_len = NUM_ELEMENTS(call_env_result_type_table);
870
871
char const *call_env_result_type_to_enum_str(call_env_result_type_t t)
872
0
{
873
0
  return fr_table_str_by_value(call_env_result_type_table, t, "CALL_ENV_RESULT_TYPE_VALUE_BOX");
874
0
}
875