/src/freeradius-server/src/lib/server/trigger.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: 0f90fb463f65684425384f7a803ba6d919276c3b $ |
19 | | * |
20 | | * @file trigger.c |
21 | | * @brief Execute scripts when a server event occurs. |
22 | | * |
23 | | * @copyright 2015 The FreeRADIUS server project |
24 | | */ |
25 | | RCSID("$Id: 0f90fb463f65684425384f7a803ba6d919276c3b $") |
26 | | |
27 | | #include <freeradius-devel/protocol/freeradius/freeradius.internal.h> |
28 | | #include <freeradius-devel/server/cf_file.h> |
29 | | #include <freeradius-devel/server/cf_parse.h> |
30 | | #include <freeradius-devel/server/exec.h> |
31 | | #include <freeradius-devel/server/main_loop.h> |
32 | | #include <freeradius-devel/server/map.h> |
33 | | #include <freeradius-devel/server/pair.h> |
34 | | #include <freeradius-devel/server/request_data.h> |
35 | | #include <freeradius-devel/server/trigger.h> |
36 | | #include <freeradius-devel/unlang/function.h> |
37 | | #include <freeradius-devel/unlang/subrequest.h> |
38 | | #include <freeradius-devel/unlang/tmpl.h> |
39 | | |
40 | | |
41 | | #include <sys/wait.h> |
42 | | |
43 | | /** Whether triggers are enabled globally |
44 | | * |
45 | | */ |
46 | | static CONF_SECTION const *trigger_cs; |
47 | | static fr_rb_tree_t *trigger_last_fired_tree; |
48 | | static pthread_mutex_t *trigger_mutex; |
49 | | |
50 | | /** Describes a rate limiting entry for a trigger |
51 | | * |
52 | | */ |
53 | | typedef struct { |
54 | | fr_rb_node_t node; //!< Entry in the trigger last fired tree. |
55 | | CONF_ITEM *ci; //!< Config item this rate limit counter is associated with. |
56 | | fr_time_t last_fired; //!< When this trigger last fired. |
57 | | } trigger_last_fired_t; |
58 | | |
59 | | static fr_dict_t const *dict_freeradius; |
60 | | extern fr_dict_autoload_t trigger_dict[]; |
61 | | fr_dict_autoload_t trigger_dict[] = { |
62 | | { .out = &dict_freeradius, .proto = "freeradius" }, |
63 | | DICT_AUTOLOAD_TERMINATOR |
64 | | }; |
65 | | |
66 | | static fr_dict_attr_t const *attr_trigger_name; |
67 | | extern fr_dict_attr_autoload_t trigger_dict_attr[]; |
68 | | fr_dict_attr_autoload_t trigger_dict_attr[] = { |
69 | | { .out = &attr_trigger_name, .name = "Trigger-Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, |
70 | | DICT_AUTOLOAD_TERMINATOR |
71 | | }; |
72 | | |
73 | | static void _trigger_last_fired_free(void *data) |
74 | 0 | { |
75 | 0 | talloc_free(data); |
76 | 0 | } |
77 | | |
78 | | /** Compares two last fired structures |
79 | | * |
80 | | * @param one first pointer to compare. |
81 | | * @param two second pointer to compare. |
82 | | * @return CMP(one, two) |
83 | | */ |
84 | | static int8_t _trigger_last_fired_cmp(void const *one, void const *two) |
85 | 0 | { |
86 | 0 | trigger_last_fired_t const *a = one, *b = two; |
87 | |
|
88 | 0 | return CMP(a->ci, b->ci); |
89 | 0 | } |
90 | | |
91 | | /** Return whether triggers are enabled |
92 | | * |
93 | | */ |
94 | | bool trigger_enabled(void) |
95 | 0 | { |
96 | 0 | return (trigger_cs != NULL); |
97 | 0 | } |
98 | | |
99 | | typedef struct { |
100 | | fr_value_box_list_t out; //!< result of the xlap (which we ignore) |
101 | | unlang_result_t result; //!< the result of expansion |
102 | | tmpl_t *vpt; //!< the template to execute |
103 | | int exec_status; //!< Result of the program (if the trigger is a tmpl) |
104 | | } fr_trigger_t; |
105 | | |
106 | | /** Execute a trigger - call an executable to process an event |
107 | | * |
108 | | * A trigger ties a state change (e.g. connection up) in a module to an action |
109 | | * (e.g. send an SNMP trap) defined in triggers.conf or in the trigger |
110 | | * section of a module. There's no setup for triggers, the triggering code |
111 | | * just calls this function with the name of the trigger to run, and an optional |
112 | | * interpreter if the trigger should run asynchronously. |
113 | | * |
114 | | * If no interpreter is passed in, the trigger runs synchronously, which is |
115 | | * useful when the server is shutting down and we want to ensure that the |
116 | | * trigger has completed before the server exits. |
117 | | * |
118 | | * If an interpreter is passed in, the trigger runs asynchronously in that |
119 | | * interpreter, allowing the server to continue processing packets while the |
120 | | * trigger runs. |
121 | | * |
122 | | * The name of each trigger is based on the module or portion of the server |
123 | | * which runs the trigger, and is usually taken from the state when the module |
124 | | * has a state change. |
125 | | * |
126 | | * Triggers are separate from logs, because log messages are generally |
127 | | * informational, are not time sensitive, and usually require log files to be |
128 | | * parsed and filtered in order to find relevant information. |
129 | | * |
130 | | * In contrast, triggers are something specific which the administrator needs |
131 | | * to be notified about immediately and can't wait to post-process a log file. |
132 | | * |
133 | | * @note Calls to this function will be ignored if #trigger_init has not been called. |
134 | | * |
135 | | * @param[in] intp Interpreter to run the trigger with. If this is NULL the |
136 | | * trigger will be executed synchronously. |
137 | | * @param[in] cs to search for triggers in. |
138 | | * If cs is not NULL, the portion after the last '.' in name is used for the trigger. |
139 | | * If cs is NULL, the entire name is used to find the trigger in the global trigger |
140 | | * section. |
141 | | * @param[in,out] trigger_cp Optional pointer to a CONF_PAIR pointer. If populated and the pointer is not |
142 | | * NULL, this CONF_PAIR will be used rather than searching. |
143 | | * If populated, and the pointer is NULL, the search will happen and the pointer |
144 | | * will be populated with the found CONF_PAIR. |
145 | | * @param[in] name the path relative to the global trigger section ending in the trigger name |
146 | | * e.g. module.ldap.pool.start. |
147 | | * @param[in] rate_limit whether to rate limit triggers. |
148 | | * @param[in] args to populate the trigger's request list with. |
149 | | * @return |
150 | | * - 0 on success. |
151 | | * - -1 if the trigger is not defined. |
152 | | * - -2 if the trigger was rate limited. |
153 | | * - -3 on failure. |
154 | | */ |
155 | | int trigger(unlang_interpret_t *intp, CONF_SECTION const *cs, CONF_PAIR **trigger_cp, |
156 | | char const *name, bool rate_limit, fr_pair_list_t *args) |
157 | 0 | { |
158 | 0 | CONF_ITEM *ci; |
159 | 0 | CONF_PAIR *cp; |
160 | |
|
161 | 0 | char const *attr; |
162 | 0 | char const *value; |
163 | |
|
164 | 0 | request_t *request; |
165 | 0 | fr_trigger_t *trigger; |
166 | 0 | ssize_t slen; |
167 | |
|
168 | 0 | fr_event_list_t *el; |
169 | 0 | tmpl_rules_t t_rules; |
170 | | |
171 | | /* |
172 | | * noop if trigger_init was never called, or if |
173 | | * we're just checking the configuration. |
174 | | */ |
175 | 0 | if (!trigger_cs || check_config) return 0; |
176 | | |
177 | | /* |
178 | | * Do we have a cached conf pair? |
179 | | */ |
180 | 0 | if (trigger_cp && *trigger_cp) { |
181 | 0 | cp = *trigger_cp; |
182 | 0 | ci = cf_pair_to_item(cp); |
183 | 0 | goto cp_found; |
184 | 0 | } |
185 | | |
186 | | /* |
187 | | * A module can have a local "trigger" section. In which |
188 | | * case that is used in preference to the global one. |
189 | | * |
190 | | * @todo - we should really allow triggers via @trigger, |
191 | | * so that all of the triggers are in one location. And |
192 | | * then we can have different triggers for different |
193 | | * module instances. |
194 | | */ |
195 | 0 | if (cs) { |
196 | 0 | cs = cf_section_find(cs, "trigger", NULL); |
197 | 0 | if (!cs) goto use_global; |
198 | | |
199 | | /* |
200 | | * If a local trigger{...} section exists, then |
201 | | * use the local part of the name, rather than |
202 | | * the full path. |
203 | | */ |
204 | 0 | attr = strrchr(name, '.'); |
205 | 0 | if (attr) { |
206 | 0 | attr++; |
207 | 0 | } else { |
208 | 0 | attr = name; |
209 | 0 | } |
210 | 0 | } else { |
211 | 0 | use_global: |
212 | 0 | cs = trigger_cs; |
213 | 0 | attr = name; |
214 | 0 | } |
215 | | |
216 | | /* |
217 | | * Find the trigger. Note that we do NOT allow searching |
218 | | * from the root of the tree. Triggers MUST be in a |
219 | | * trigger{...} section. |
220 | | */ |
221 | 0 | ci = cf_reference_item(cs, cs, attr); |
222 | 0 | if (!ci) { |
223 | 0 | if (cs != trigger_cs) goto use_global; /* not found locally, try to find globally */ |
224 | | |
225 | 0 | DEBUG3("Failed finding trigger '%s'", attr); |
226 | 0 | return -1; |
227 | 0 | } |
228 | | |
229 | 0 | if (!cf_item_is_pair(ci)) { |
230 | 0 | ERROR("Trigger is not a configuration variable: %s", attr); |
231 | 0 | return -1; |
232 | 0 | } |
233 | | |
234 | 0 | cp = cf_item_to_pair(ci); |
235 | 0 | if (!cp) return -1; |
236 | | |
237 | 0 | if (trigger_cp) *trigger_cp = cp; |
238 | |
|
239 | 0 | cp_found: |
240 | 0 | value = cf_pair_value(cp); |
241 | 0 | if (!value) { |
242 | 0 | DEBUG3("Trigger has no value: %s", name); |
243 | 0 | return -1; |
244 | 0 | } |
245 | | |
246 | | /* |
247 | | * Perform periodic rate_limiting. |
248 | | */ |
249 | 0 | if (rate_limit) { |
250 | 0 | trigger_last_fired_t find, *found; |
251 | 0 | fr_time_t now = fr_time(); |
252 | |
|
253 | 0 | find.ci = ci; |
254 | |
|
255 | 0 | pthread_mutex_lock(trigger_mutex); |
256 | |
|
257 | 0 | found = fr_rb_find(trigger_last_fired_tree, &find); |
258 | 0 | if (!found) { |
259 | 0 | MEM(found = talloc(NULL, trigger_last_fired_t)); |
260 | 0 | found->ci = ci; |
261 | | /* |
262 | | * Initialise last_fired to 2 seconds ago so |
263 | | * the trigger fires on the first occurrence |
264 | | */ |
265 | 0 | found->last_fired = fr_time_wrap(NSEC * -2); |
266 | |
|
267 | 0 | fr_rb_insert(trigger_last_fired_tree, found); |
268 | 0 | } |
269 | | |
270 | | /* |
271 | | * Send the rate_limited traps at most once per second. |
272 | | * |
273 | | * @todo - make this configurable for longer periods of time. |
274 | | */ |
275 | 0 | if (fr_time_to_sec(found->last_fired) == fr_time_to_sec(now)) { |
276 | 0 | pthread_mutex_unlock(trigger_mutex); |
277 | 0 | return -2; |
278 | 0 | } |
279 | | |
280 | 0 | found->last_fired = now; |
281 | 0 | pthread_mutex_unlock(trigger_mutex); |
282 | 0 | } |
283 | | |
284 | | /* |
285 | | * Allocate a request to run asynchronously in the interpreter. |
286 | | */ |
287 | 0 | request = request_local_alloc_internal(NULL, (&(request_init_args_t){ .detachable = true })); |
288 | 0 | request->name = talloc_typed_asprintf(request, "trigger-%s", name); |
289 | |
|
290 | 0 | if (args) { |
291 | 0 | fr_pair_t *vp; |
292 | |
|
293 | 0 | if (fr_pair_list_copy(request->request_ctx, &request->request_pairs, args) < 0) { |
294 | 0 | PERROR("Failed copying trigger arguments"); |
295 | |
|
296 | 0 | fail: |
297 | 0 | talloc_free(request); |
298 | 0 | return -3; |
299 | 0 | } |
300 | | |
301 | | /* |
302 | | * Add the trigger name to the request data |
303 | | */ |
304 | 0 | MEM(pair_append_request(&vp, attr_trigger_name) >= 0); |
305 | 0 | fr_pair_value_strdup(vp, cf_pair_attr(cp), false); |
306 | 0 | } |
307 | | |
308 | 0 | MEM(trigger = talloc_zero(request, fr_trigger_t)); |
309 | 0 | fr_value_box_list_init(&trigger->out); |
310 | |
|
311 | 0 | el = unlang_interpret_event_list(request); |
312 | 0 | if (!el) el = main_loop_event_list(); |
313 | | |
314 | | /* |
315 | | * During shutdown there may be no event list, so nothing much can be done. |
316 | | */ |
317 | 0 | if (unlikely(!el)) goto fail; |
318 | | |
319 | | |
320 | 0 | t_rules = (tmpl_rules_t) { |
321 | 0 | .attr = { |
322 | 0 | .dict_def = request->local_dict, /* we can use local attributes */ |
323 | 0 | .list_def = request_attr_request, |
324 | 0 | }, |
325 | 0 | .xlat = { |
326 | 0 | .runtime_el = el, |
327 | 0 | }, |
328 | 0 | .at_runtime = true, |
329 | 0 | }; |
330 | |
|
331 | 0 | slen = tmpl_afrom_substr(trigger, &trigger->vpt, &FR_SBUFF_IN(value, talloc_strlen(value)), |
332 | 0 | cf_pair_value_quote(cp), NULL, &t_rules); |
333 | 0 | if (slen <= 0) { |
334 | 0 | char *spaces, *text; |
335 | |
|
336 | 0 | fr_canonicalize_error(trigger, &spaces, &text, slen, value); |
337 | |
|
338 | 0 | cf_log_err(cp, "Failed parsing trigger expression"); |
339 | 0 | cf_log_err(cp, "%s", text); |
340 | 0 | cf_log_perr(cp, "%s^", spaces); |
341 | |
|
342 | 0 | goto fail; |
343 | 0 | } |
344 | | |
345 | 0 | if (!tmpl_is_exec(trigger->vpt) && !tmpl_is_xlat(trigger->vpt)) { |
346 | | /* |
347 | | * We only support exec and xlat templates. |
348 | | * Anything else is an error. |
349 | | */ |
350 | 0 | cf_log_err(cp, "Trigger must be an \"expr\" or `exec`"); |
351 | 0 | goto fail; |
352 | 0 | } |
353 | | |
354 | 0 | fr_assert(trigger->vpt != NULL); |
355 | |
|
356 | 0 | if (unlang_tmpl_push(trigger, &trigger->result, &trigger->out, request, trigger->vpt, |
357 | 0 | &(unlang_tmpl_args_t) { |
358 | 0 | .type = UNLANG_TMPL_ARGS_TYPE_EXEC, |
359 | 0 | .exec = { |
360 | 0 | .status_out = &trigger->exec_status, |
361 | 0 | .timeout = fr_time_delta_from_sec(5), |
362 | 0 | }, |
363 | 0 | }, UNLANG_TOP_FRAME) < 0) { |
364 | 0 | goto fail; |
365 | 0 | } |
366 | | |
367 | | /* |
368 | | * An interpreter was passed in, we can run the expansion |
369 | | * asynchronously in that interpreter. And then the |
370 | | * worker cleans up the detached request. |
371 | | */ |
372 | 0 | if (intp) { |
373 | 0 | unlang_interpret_set(request, intp); |
374 | | |
375 | | /* |
376 | | * Don't allow the expansion to run for a long time. |
377 | | * |
378 | | * @todo - make the timeout configurable. |
379 | | */ |
380 | 0 | if (unlang_interpret_set_timeout(request, fr_time_delta_from_sec(1)) < 0) { |
381 | 0 | DEBUG("Failed setting timeout on trigger %s", value); |
382 | 0 | goto fail; |
383 | 0 | } |
384 | | |
385 | 0 | if (unlang_subrequest_child_push_and_detach(request) < 0) { |
386 | 0 | PERROR("Running trigger failed"); |
387 | 0 | goto fail; |
388 | 0 | } |
389 | 0 | } else { |
390 | | /* |
391 | | * No interpreter, we MUST be running from the |
392 | | * main loop. We then run the expansion |
393 | | * synchronously. This allows the expansion / |
394 | | * notification to finish before the server shuts |
395 | | * down. |
396 | | * |
397 | | * If the expansion was async, then it may be |
398 | | * possible for the server to exit before the |
399 | | * expansion finishes. Arguably the worker |
400 | | * thread should ensure that the server doesn't |
401 | | * exit until all requests have acknowledged that |
402 | | * they've exited. |
403 | | * |
404 | | * But those exits may be advisory. i.e. "please |
405 | | * finish the request". This one here is |
406 | | * mandatary to finish before the server exits. |
407 | | */ |
408 | 0 | unlang_interpret_synchronous(NULL, request); |
409 | 0 | talloc_free(request); |
410 | 0 | } |
411 | | |
412 | 0 | return 0; |
413 | 0 | } |
414 | | |
415 | | /** Create trigger arguments to describe the server the pool connects to |
416 | | * |
417 | | * @note #trigger_init must be called before calling this function, |
418 | | * else it will return NULL. |
419 | | * |
420 | | * @param[in] ctx to allocate fr_pair_t s in. |
421 | | * @param[out] list to append Pool-Server and Pool-Port pairs to |
422 | | * @param[in] server we're connecting to. |
423 | | * @param[in] port on that server. |
424 | | */ |
425 | | void trigger_args_afrom_server(TALLOC_CTX *ctx, fr_pair_list_t *list, char const *server, uint16_t port) |
426 | | { |
427 | | fr_dict_attr_t const *server_da; |
428 | | fr_dict_attr_t const *port_da; |
429 | | fr_pair_t *vp; |
430 | | |
431 | | server_da = fr_dict_attr_child_by_num(fr_dict_root(fr_dict_internal()), FR_CONNECTION_POOL_SERVER); |
432 | | if (!server_da) { |
433 | | ERROR("Incomplete dictionary: Missing definition for \"Connection-Pool-Server\""); |
434 | | return; |
435 | | } |
436 | | |
437 | | port_da = fr_dict_attr_child_by_num(fr_dict_root(fr_dict_internal()), FR_CONNECTION_POOL_PORT); |
438 | | if (!port_da) { |
439 | | ERROR("Incomplete dictionary: Missing definition for \"Connection-Pool-Port\""); |
440 | | return; |
441 | | } |
442 | | |
443 | | MEM(vp = fr_pair_afrom_da(ctx, server_da)); |
444 | | fr_pair_value_strdup(vp, server, false); |
445 | | fr_pair_append(list, vp); |
446 | | |
447 | | MEM(vp = fr_pair_afrom_da(ctx, port_da)); |
448 | | vp->vp_uint16 = port; |
449 | | fr_pair_append(list, vp); |
450 | | } |
451 | | |
452 | | /** Callback to verify that trigger_args map is valid |
453 | | */ |
454 | | static int trigger_args_validate(map_t *map, UNUSED void *uctx) |
455 | 0 | { |
456 | 0 | if (map->lhs->type != TMPL_TYPE_ATTR) { |
457 | 0 | cf_log_err(map->ci, "%s is not an internal attribute reference", map->lhs->name); |
458 | 0 | return -1; |
459 | 0 | } |
460 | | |
461 | 0 | switch (map->rhs->type) { |
462 | 0 | case TMPL_TYPE_DATA_UNRESOLVED: |
463 | 0 | if (tmpl_resolve(map->rhs, NULL) < 0) { |
464 | 0 | cf_log_err(map->ci, "Invalid data %s", map->rhs->name); |
465 | 0 | return -1; |
466 | 0 | } |
467 | 0 | break; |
468 | 0 | case TMPL_TYPE_DATA: |
469 | 0 | break; |
470 | | |
471 | 0 | default: |
472 | 0 | cf_log_err(map->ci, "Right hand side of trigger_args must be literal, not %s", tmpl_type_to_str(map->rhs->type)); |
473 | 0 | return -1; |
474 | 0 | } |
475 | | |
476 | 0 | return 0; |
477 | 0 | } |
478 | | |
479 | 0 | #define BUILD_ATTR(_name, _value) if (_value) { \ |
480 | 0 | da = fr_dict_attr_by_name(NULL, fr_dict_root(fr_dict_internal()), _name); \ |
481 | 0 | if (!da) { \ |
482 | 0 | ERROR("Incomplete dictionary: Missing definition for \"" _name "\""); \ |
483 | 0 | return -1; \ |
484 | 0 | } \ |
485 | 0 | MEM(vp = fr_pair_afrom_da(ctx, da)); \ |
486 | 0 | fr_pair_value_strdup(vp, _value, false); \ |
487 | 0 | fr_pair_append(list, vp); \ |
488 | 0 | } |
489 | | |
490 | | /** Build trigger args pair list for modules |
491 | | * |
492 | | * @param[in] ctx to allocate pairs in. |
493 | | * @param[in] list to populate. |
494 | | * @param[in] cs CONF_SECTION to search for a "trigger_args" section |
495 | | * @param[in] args Common module data which will populate default pairs |
496 | | */ |
497 | | int module_trigger_args_build(TALLOC_CTX *ctx, fr_pair_list_t *list, CONF_SECTION const *cs, module_trigger_args_t *args) |
498 | 0 | { |
499 | 0 | map_list_t *maps; |
500 | 0 | map_t *map = NULL; |
501 | 0 | fr_pair_t *vp; |
502 | 0 | fr_dict_attr_t const *da; |
503 | 0 | tmpl_rules_t t_rules = (tmpl_rules_t){ |
504 | 0 | .attr = { |
505 | 0 | .dict_def = fr_dict_internal(), |
506 | 0 | .list_def = request_attr_request, |
507 | 0 | }, |
508 | 0 | }; |
509 | | |
510 | | /* |
511 | | * Build the default pairs from the module data |
512 | | */ |
513 | 0 | BUILD_ATTR("Module-Name", args->module) |
514 | 0 | BUILD_ATTR("Module-Instance", args->name) |
515 | 0 | BUILD_ATTR("Connection-Pool-Server", args->server) |
516 | 0 | da = fr_dict_attr_child_by_num(fr_dict_root(fr_dict_internal()), FR_CONNECTION_POOL_PORT); |
517 | 0 | if (!da) { |
518 | 0 | ERROR("Incomplete dictionary: Missing definition for \"Connection-Pool-Port\""); |
519 | 0 | return -1; |
520 | 0 | } |
521 | 0 | MEM(vp = fr_pair_afrom_da(ctx, da)); |
522 | 0 | vp->vp_uint16 = args->port; |
523 | 0 | fr_pair_append(list, vp); |
524 | | |
525 | | /* |
526 | | * If a CONF_SECTION has been passed in, look for a "trigger_args" |
527 | | * sub section and parse that as a map to create additional pairs. |
528 | | */ |
529 | 0 | if (!cs) return 0; |
530 | 0 | cs = cf_section_find(cs, "trigger_args", NULL); |
531 | 0 | if (!cs) return 0; |
532 | | |
533 | 0 | MEM(maps = talloc(NULL, map_list_t)); |
534 | 0 | map_list_init(maps); |
535 | 0 | if (map_afrom_cs(maps, maps, cs, &t_rules, &t_rules, trigger_args_validate, NULL, 256) < 0) { |
536 | 0 | fail: |
537 | 0 | talloc_free(maps); |
538 | 0 | return -1; |
539 | 0 | } |
540 | | |
541 | 0 | while ((map = map_list_next(maps, map))) { |
542 | 0 | MEM(vp = fr_pair_afrom_da_nested(ctx, list, tmpl_attr_tail_da(map->lhs))); |
543 | 0 | if (fr_value_box_cast(vp, &vp->data, vp->da->type, vp->da, &map->rhs->data.literal) < 0) goto fail; |
544 | 0 | } |
545 | 0 | talloc_free(maps); |
546 | 0 | return 0; |
547 | 0 | } |
548 | | |
549 | | static int _mutex_free(pthread_mutex_t *mutex) |
550 | 0 | { |
551 | 0 | pthread_mutex_destroy(mutex); |
552 | 0 | return 0; |
553 | 0 | } |
554 | | |
555 | | /** Free trigger resources |
556 | | * |
557 | | */ |
558 | | static int _trigger_free(UNUSED void *uctx) |
559 | 0 | { |
560 | 0 | fr_dict_autofree(trigger_dict); |
561 | 0 | TALLOC_FREE(trigger_last_fired_tree); |
562 | 0 | TALLOC_FREE(trigger_mutex); |
563 | |
|
564 | 0 | return 0; |
565 | 0 | } |
566 | | |
567 | | /** Set the global trigger section trigger will search in, and register xlats |
568 | | * |
569 | | * This function exists because triggers are used by the connection pool, which |
570 | | * is used in the server library which may not have the mainconfig available. |
571 | | * Additionally, utilities may want to set their own root config sections. |
572 | | * |
573 | | * We don't register the trigger xlat here, as we may inadvertently initialise |
574 | | * the xlat code, which is annoying when this is called from a utility. |
575 | | * |
576 | | * @param[in] cs_arg to use as global trigger section. |
577 | | * @return |
578 | | * - 0 on success. |
579 | | * - -1 on failure. |
580 | | */ |
581 | | static int _trigger_init(void *cs_arg) |
582 | 0 | { |
583 | 0 | CONF_SECTION *cs; |
584 | |
|
585 | 0 | if (unlikely(fr_dict_autoload(trigger_dict) < 0)) { |
586 | 0 | PERROR("Failed loading trigger dictionaries"); |
587 | 0 | return -1; |
588 | 0 | } |
589 | 0 | if (unlikely(fr_dict_attr_autoload(trigger_dict_attr) < 0)) { |
590 | 0 | PERROR("Failed loading trigger attributes"); |
591 | 0 | return -1; |
592 | 0 | } |
593 | | |
594 | 0 | cs = talloc_get_type_abort(cs_arg, CONF_SECTION); |
595 | 0 | if (!cs) { |
596 | 0 | ERROR("%s - Pointer to main_config was NULL", __FUNCTION__); |
597 | 0 | return -1; |
598 | 0 | } |
599 | | |
600 | 0 | trigger_cs = cf_section_find(cs, "trigger", NULL); |
601 | 0 | if (!trigger_cs) { |
602 | 0 | WARN("trigger { ... } subsection not found, triggers will be disabled"); |
603 | 0 | return 0; |
604 | 0 | } |
605 | | |
606 | 0 | MEM(trigger_last_fired_tree = fr_rb_inline_talloc_alloc(talloc_null_ctx(), |
607 | 0 | trigger_last_fired_t, node, |
608 | 0 | _trigger_last_fired_cmp, _trigger_last_fired_free)); |
609 | |
|
610 | 0 | trigger_mutex = talloc(talloc_null_ctx(), pthread_mutex_t); |
611 | 0 | if (pthread_mutex_init(trigger_mutex, 0) != 0) { |
612 | 0 | PERROR("Failed to initialize trigger mutex"); |
613 | 0 | talloc_free(trigger_mutex); |
614 | 0 | return -1; |
615 | 0 | } |
616 | | |
617 | 0 | talloc_set_destructor(trigger_mutex, _mutex_free); |
618 | |
|
619 | 0 | return 0; |
620 | 0 | } |
621 | | |
622 | | int trigger_init(CONF_SECTION const *cs) |
623 | 0 | { |
624 | 0 | int ret; |
625 | |
|
626 | 0 | fr_atexit_global_once_ret(&ret, _trigger_init, _trigger_free, UNCONST(CONF_SECTION *, cs)); |
627 | |
|
628 | 0 | return ret; |
629 | 0 | } |