Coverage Report

Created: 2026-06-30 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/unlang/map.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: 451180effff56044b5011a657f9105526b2b55ea $
19
 *
20
 * @brief map and unlang integration.
21
 * @brief Unlang "map" keyword evaluation.
22
 *
23
 * @ingroup AVP
24
 *
25
 * @copyright 2018 The FreeRADIUS server project
26
 * @copyright 2018 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
27
 */
28
RCSID("$Id: 451180effff56044b5011a657f9105526b2b55ea $")
29
30
#include <freeradius-devel/server/base.h>
31
#include <freeradius-devel/unlang/tmpl.h>
32
#include <freeradius-devel/unlang/map.h>
33
34
#include "map_priv.h"
35
36
/** State of a map block
37
 *
38
 */
39
typedef struct {
40
  fr_value_box_list_t   src_result;   //!< Result of expanding the map source.
41
42
  /** @name Resumption and signalling
43
   * @{
44
   */
45
  void        *rctx;      //!< for resume / signal
46
  map_proc_func_t     resume;     //!< resumption handler
47
  unlang_map_signal_t   signal;     //!< for signal handlers
48
  fr_signal_t     sigmask;    //!< Signals to block.
49
50
  /** @} */
51
} unlang_frame_state_map_proc_t;
52
53
/** Wrapper to create a map_ctx_t as a compound literal
54
 *
55
 * @param[in] _mod_inst of the module being called.
56
 * @param[in] _map_inst of the map being called.
57
 * @param[in] _rctx Resume ctx (if any).
58
 */
59
0
#define MAP_CTX(_mod_inst, _map_inst, _rctx) &(map_ctx_t){ .moi = _mod_inst, .mpi = _map_inst, .rctx = _rctx }
60
61
static unlang_action_t map_proc_resume(unlang_result_t *p_result, request_t *request,
62
#ifdef WITH_VERIFY_PTR
63
               unlang_stack_frame_t *frame
64
#else
65
               UNUSED unlang_stack_frame_t *frame
66
#endif
67
              )
68
0
{
69
0
  unlang_frame_state_map_proc_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_map_proc_t);
70
0
  unlang_frame_state_map_proc_t *map_proc_state = talloc_get_type_abort(frame->state, unlang_frame_state_map_proc_t);
71
0
  map_proc_func_t     resume;
72
0
  unlang_group_t      *g = unlang_generic_to_group(frame->instruction);
73
0
  unlang_map_t      *gext = unlang_group_to_map(g);
74
0
  map_proc_inst_t     *inst = gext->proc_inst;
75
0
  unlang_action_t     ua = UNLANG_ACTION_CALCULATE_RESULT;
76
77
0
#ifdef WITH_VERIFY_PTR
78
0
  VALUE_BOX_LIST_VERIFY(&map_proc_state->src_result);
79
0
#endif
80
0
  resume = state->resume;
81
0
  state->resume = NULL;
82
83
  /*
84
   *  Call any map resume function
85
   */
86
0
  if (resume) ua = resume(p_result, MAP_CTX(inst->proc->mod_inst, inst->data, state->rctx),
87
0
        request, &map_proc_state->src_result, inst->maps);
88
0
  return ua;
89
0
}
90
91
/** Yield a request back to the interpreter from within a module
92
 *
93
 * This passes control of the request back to the unlang interpreter, setting
94
 * callbacks to execute when the request is 'signalled' asynchronously, or whatever
95
 * timer or I/O event the module was waiting for occurs.
96
 *
97
 * @note The module function which calls #unlang_module_yield should return control
98
 *  of the C stack to the unlang interpreter immediately after calling #unlang_module_yield.
99
 *  A common pattern is to use ``return unlang_module_yield(...)``.
100
 *
101
 * @param[in] request   The current request.
102
 * @param[in] resume    Called on unlang_interpret_mark_runnable().
103
 * @param[in] signal    Called on unlang_action().
104
 * @param[in] sigmask   Set of signals to block.
105
 * @param[in] rctx    to pass to the callbacks.
106
 * @return
107
 *  - UNLANG_ACTION_YIELD.
108
 */
109
unlang_action_t unlang_map_yield(request_t *request,
110
         map_proc_func_t resume, unlang_map_signal_t signal, fr_signal_t sigmask, void *rctx)
111
0
{
112
0
  unlang_stack_t      *stack = request->stack;
113
0
  unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
114
0
  unlang_frame_state_map_proc_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_map_proc_t);
115
116
0
  REQUEST_VERIFY(request);  /* Check the yielded request is sane */
117
118
0
  state->rctx = rctx;
119
0
  state->resume = resume;
120
0
  state->signal = signal;
121
0
  state->sigmask = sigmask;
122
123
  /*
124
   *  We set the repeatable flag here,
125
   *  so that the resume function is always
126
   *  called going back up the stack.
127
   */
128
0
  frame_repeat(frame, map_proc_resume);
129
130
0
  return UNLANG_ACTION_YIELD;
131
0
}
132
133
static unlang_action_t map_proc_apply(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
134
0
{
135
0
  unlang_group_t      *g = unlang_generic_to_group(frame->instruction);
136
0
  unlang_map_t      *gext = unlang_group_to_map(g);
137
138
0
  map_proc_inst_t     *inst = gext->proc_inst;
139
0
  unlang_frame_state_map_proc_t *map_proc_state = talloc_get_type_abort(frame->state, unlang_frame_state_map_proc_t);
140
141
0
  RDEBUG2("MAP %s \"%pM\"", inst->proc->name, &map_proc_state->src_result);
142
143
0
  VALUE_BOX_LIST_VERIFY(&map_proc_state->src_result);
144
0
  frame_repeat(frame, map_proc_resume);
145
146
0
  return inst->proc->evaluate(p_result, MAP_CTX(inst->proc->mod_inst, inst->data, NULL),
147
0
            request, &map_proc_state->src_result, inst->maps);
148
0
}
149
150
static unlang_action_t unlang_map_state_init(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
151
0
{
152
0
  unlang_group_t      *g = unlang_generic_to_group(frame->instruction);
153
0
  unlang_map_t      *gext = unlang_group_to_map(g);
154
0
  map_proc_inst_t     *inst = gext->proc_inst;
155
0
  unlang_frame_state_map_proc_t *map_proc_state = talloc_get_type_abort(frame->state, unlang_frame_state_map_proc_t);
156
157
  /*
158
   *  Initialise the frame state
159
   */
160
0
  repeatable_set(frame);
161
162
0
  fr_value_box_list_init(&map_proc_state->src_result);
163
  /*
164
   *  Set this BEFORE doing anything else, as we will be
165
   *  called again after unlang_xlat_push() returns.
166
   */
167
0
  frame->process = map_proc_apply;
168
169
  /*
170
   *  Expand the map source
171
   */
172
0
  if (inst->src) switch (inst->src->type) {
173
0
  default:
174
0
  {
175
0
    fr_value_box_t *src_result = NULL;
176
0
    if (tmpl_aexpand(frame->state, &src_result,
177
0
         request, inst->src, NULL, NULL) < 0) {
178
0
      REDEBUG("Failed expanding map src");
179
0
    error:
180
0
      RETURN_UNLANG_FAIL;
181
0
    }
182
0
    fr_value_box_list_insert_head(&map_proc_state->src_result, src_result);
183
0
    break;
184
0
  }
185
0
  case TMPL_TYPE_EXEC:
186
0
    if (unlang_tmpl_push(map_proc_state, NULL, &map_proc_state->src_result,
187
0
             request, inst->src, NULL, UNLANG_SUB_FRAME) < 0) {
188
0
      RETURN_UNLANG_ACTION_FATAL;
189
0
    }
190
0
    return UNLANG_ACTION_PUSHED_CHILD;
191
192
0
  case TMPL_TYPE_XLAT:
193
0
    if (unlang_xlat_push(map_proc_state, NULL, &map_proc_state->src_result,
194
0
             request, tmpl_xlat(inst->src), false) < 0) {
195
0
      RETURN_UNLANG_ACTION_FATAL;
196
0
    }
197
0
    return UNLANG_ACTION_PUSHED_CHILD;
198
199
200
0
  case TMPL_TYPE_REGEX:
201
0
  case TMPL_TYPE_REGEX_UNCOMPILED:
202
0
  case TMPL_TYPE_REGEX_XLAT:
203
0
  case TMPL_TYPE_REGEX_XLAT_UNRESOLVED:
204
0
  case TMPL_TYPE_XLAT_UNRESOLVED:
205
0
    fr_assert(0);
206
0
    goto error;
207
0
  }
208
209
0
  return map_proc_apply(p_result, request, frame);
210
0
}
211
212
static int compile_map_name(unlang_group_t *g)
213
0
{
214
0
  unlang_map_t  *gext = unlang_group_to_map(g);
215
216
  /*
217
   *  map <module-name> <arg>
218
   */
219
0
  if (gext->vpt) {
220
0
    char  quote;
221
0
    size_t  quoted_len;
222
0
    char  *quoted_str;
223
224
0
    switch (cf_section_argv_quote(g->cs, 0)) {
225
0
    case T_DOUBLE_QUOTED_STRING:
226
0
      quote = '"';
227
0
      break;
228
229
0
    case T_SINGLE_QUOTED_STRING:
230
0
      quote = '\'';
231
0
      break;
232
233
0
    case T_BACK_QUOTED_STRING:
234
0
      quote = '`';
235
0
      break;
236
237
0
    default:
238
0
      quote = '\0';
239
0
      break;
240
0
    }
241
242
0
    quoted_len = fr_snprint_len(gext->vpt->name, gext->vpt->len, quote);
243
0
    quoted_str = talloc_array(g, char, quoted_len);
244
0
    fr_snprint(quoted_str, quoted_len, gext->vpt->name, gext->vpt->len, quote);
245
246
0
    g->self.name = talloc_typed_asprintf(g, "map %s %s", cf_section_name2(g->cs), quoted_str);
247
0
    g->self.debug_name = g->self.name;
248
0
    talloc_free(quoted_str);
249
250
0
    return 0;
251
0
  }
252
253
0
  g->self.name = talloc_typed_asprintf(g, "map %s", cf_section_name2(g->cs));
254
0
  g->self.debug_name = g->self.name;
255
256
0
  return 0;
257
0
}
258
259
/** Validate and fixup a map that's part of an map section.
260
 *
261
 * @param map to validate.
262
 * @param ctx data to pass to fixup function (currently unused).
263
 * @return 0 if valid else -1.
264
 */
265
static int fixup_map_cb(map_t *map, UNUSED void *ctx)
266
0
{
267
0
  switch (map->lhs->type) {
268
0
  case TMPL_TYPE_ATTR:
269
0
  case TMPL_TYPE_XLAT_UNRESOLVED:
270
0
  case TMPL_TYPE_XLAT:
271
0
    break;
272
273
0
  default:
274
0
    cf_log_err(map->ci, "Left side of map must be an attribute "
275
0
               "or an xlat (that expands to an attribute), not a %s",
276
0
               tmpl_type_to_str(map->lhs->type));
277
0
    return -1;
278
0
  }
279
280
0
  switch (map->rhs->type) {
281
0
  case TMPL_TYPE_XLAT_UNRESOLVED:
282
0
  case TMPL_TYPE_DATA_UNRESOLVED:
283
0
  case TMPL_TYPE_DATA:
284
0
  case TMPL_TYPE_XLAT:
285
0
  case TMPL_TYPE_ATTR:
286
0
  case TMPL_TYPE_EXEC:
287
0
    break;
288
289
0
  default:
290
0
    cf_log_err(map->ci, "Right side of map must be an attribute, literal, xlat or exec, got type %s",
291
0
               tmpl_type_to_str(map->rhs->type));
292
0
    return -1;
293
0
  }
294
295
0
  if (!fr_assignment_op[map->op] && !fr_comparison_op[map->op]) {
296
0
    cf_log_err(map->ci, "Invalid operator \"%s\" in map section.  "
297
0
         "Only assignment or filter operators are allowed",
298
0
         fr_table_str_by_value(fr_tokens_table, map->op, "<INVALID>"));
299
0
    return -1;
300
0
  }
301
302
0
  return 0;
303
0
}
304
305
static unlang_t *unlang_compile_map(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
306
0
{
307
0
  CONF_SECTION    *cs = cf_item_to_section(ci);
308
0
  int     rcode;
309
310
0
  unlang_group_t    *g;
311
0
  unlang_map_t  *gext;
312
313
0
  unlang_t    *c;
314
0
  CONF_SECTION    *modules;
315
0
  char const    *tmpl_str;
316
317
0
  tmpl_t      *vpt = NULL;
318
319
0
  map_proc_t    *proc;
320
0
  map_proc_inst_t   *proc_inst;
321
322
0
  char const    *name2 = cf_section_name2(cs);
323
324
0
  tmpl_rules_t    t_rules;
325
326
  /*
327
   *  The RHS is NOT resolved in the context of the LHS.
328
   */
329
0
  t_rules = *(unlang_ctx->rules);
330
0
  t_rules.attr.disallow_rhs_resolve = true;
331
0
  RULES_VERIFY(&t_rules);
332
333
0
  modules = cf_section_find(cf_root(cs), "modules", NULL);
334
0
  if (!modules) {
335
0
    cf_log_err(cs, "'map' sections require a 'modules' section");
336
0
    return NULL;
337
0
  }
338
339
0
  proc = map_proc_find(name2);
340
0
  if (!proc) {
341
0
    cf_log_err(cs, "Failed to find map processor '%s'", name2);
342
0
    return NULL;
343
0
  }
344
0
  t_rules.literals_safe_for = map_proc_literals_safe_for(proc);
345
346
0
  g = unlang_group_allocate(parent, cs, UNLANG_TYPE_MAP);
347
0
  if (!g) return NULL;
348
349
0
  gext = unlang_group_to_map(g);
350
351
  /*
352
   *  If there's a third string, it's the map src.
353
   *
354
   *  Convert it into a template.
355
   */
356
0
  tmpl_str = cf_section_argv(cs, 0); /* AFTER name1, name2 */
357
0
  if (tmpl_str) {
358
0
    fr_token_t type;
359
360
0
    type = cf_section_argv_quote(cs, 0);
361
362
    /*
363
     *  Try to parse the template.
364
     */
365
0
    (void) tmpl_afrom_substr(gext, &vpt,
366
0
           &FR_SBUFF_IN(tmpl_str, talloc_strlen(tmpl_str)),
367
0
           type,
368
0
           NULL,
369
0
           &t_rules);
370
0
    if (!vpt) {
371
0
      cf_log_perr(cs, "Failed parsing map");
372
0
    error:
373
0
      talloc_free(g);
374
0
      return NULL;
375
0
    }
376
377
    /*
378
     *  Limit the allowed template types.
379
     */
380
0
    switch (vpt->type) {
381
0
    case TMPL_TYPE_DATA_UNRESOLVED:
382
0
    case TMPL_TYPE_ATTR:
383
0
    case TMPL_TYPE_ATTR_UNRESOLVED:
384
0
    case TMPL_TYPE_XLAT:
385
0
    case TMPL_TYPE_XLAT_UNRESOLVED:
386
0
    case TMPL_TYPE_EXEC:
387
0
    case TMPL_TYPE_EXEC_UNRESOLVED:
388
0
    case TMPL_TYPE_DATA:
389
0
      break;
390
391
0
    default:
392
0
      talloc_free(vpt);
393
0
      cf_log_err(cs, "Invalid third argument for map");
394
0
      return NULL;
395
0
    }
396
0
  }
397
398
  /*
399
   *  This looks at cs->name2 to determine which list to update
400
   */
401
0
  map_list_init(&gext->map);
402
0
  rcode = map_afrom_cs(gext, &gext->map, cs, unlang_ctx->rules, &t_rules, fixup_map_cb, NULL, 256);
403
0
  if (rcode < 0) return NULL; /* message already printed */
404
0
  if (map_list_empty(&gext->map)) {
405
0
    cf_log_err(cs, "'map' sections cannot be empty");
406
0
    goto error;
407
0
  }
408
409
410
  /*
411
   *  Call the map's instantiation function to validate
412
   *  the map and perform any caching required.
413
   */
414
0
  proc_inst = map_proc_instantiate(gext, proc, cs, vpt, &gext->map);
415
0
  if (!proc_inst) {
416
0
    cf_log_err(cs, "Failed instantiating map function '%s'", name2);
417
0
    goto error;
418
0
  }
419
0
  c = unlang_group_to_generic(g);
420
421
0
  gext->vpt = vpt;
422
0
  gext->proc_inst = proc_inst;
423
424
0
  compile_map_name(g);
425
426
  /*
427
   *  Cache the module in the unlang_group_t struct.
428
   *
429
   *  Ensure that the module has a "map" entry in its module
430
   *  header?  Or ensure that the map is registered in the
431
   *  "bootstrap" phase, so that it's always available here.
432
   */
433
0
  if (!pass2_fixup_map_rhs(g, unlang_ctx->rules)) goto error;
434
435
0
  return c;
436
0
}
437
438
439
void unlang_map_init(void)
440
4
{
441
4
  unlang_register(&(unlang_op_t){
442
4
      .name = "map",
443
4
      .type = UNLANG_TYPE_MAP,
444
4
      .flag = UNLANG_OP_FLAG_RCODE_SET,
445
446
4
      .compile = unlang_compile_map,
447
4
      .interpret = unlang_map_state_init,
448
449
4
      .unlang_size = sizeof(unlang_map_t),
450
4
      .unlang_name = "unlang_map_t",
451
452
4
      .frame_state_size = sizeof(unlang_frame_state_map_proc_t),
453
4
      .frame_state_type = "unlang_frame_state_map_proc_t",
454
4
    });
455
4
}