/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 | } |