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