/src/freeradius-server/src/lib/server/virtual_servers.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: d3fed13facfdd0b1a3de45cafe9c88488a83a550 $ |
19 | | * |
20 | | * @file virtual_servers.c |
21 | | * @brief Defines functions for virtual_server initialisation. |
22 | | * |
23 | | * @copyright 2003,2006 The FreeRADIUS server project |
24 | | * @copyright 2000 Alan DeKok (aland@freeradius.org) |
25 | | * @copyright 2000 Alan Curry (pacman@world.std.com) |
26 | | */ |
27 | | RCSID("$Id: d3fed13facfdd0b1a3de45cafe9c88488a83a550 $") |
28 | | |
29 | | #include <freeradius-devel/protocol/freeradius/freeradius.internal.h> |
30 | | #include <freeradius-devel/server/base.h> |
31 | | #include <freeradius-devel/server/process_types.h> |
32 | | |
33 | | #include <freeradius-devel/unlang/compile.h> |
34 | | #include <freeradius-devel/unlang/function.h> |
35 | | #include <freeradius-devel/unlang/finally.h> |
36 | | |
37 | | #include <freeradius-devel/io/application.h> |
38 | | #include <freeradius-devel/io/master.h> |
39 | | #include <freeradius-devel/io/listen.h> |
40 | | |
41 | | #include <freeradius-devel/util/debug.h> |
42 | | |
43 | | typedef struct { |
44 | | module_instance_t *proto_mi; //!< The proto_* module for a listen section. |
45 | | fr_app_t const *proto_module; //!< Public interface to the proto_mi. |
46 | | ///< cached for convenience. |
47 | | } virtual_server_listen_t; |
48 | | |
49 | | struct virtual_server_s { |
50 | | CONF_SECTION *server_cs; //!< The server section. |
51 | | virtual_server_listen_t **listeners; //!< Listeners in this virtual server. |
52 | | |
53 | | module_instance_t *process_mi; //!< The process_* module for a virtual server. |
54 | | ///< Contains the dictionary used by the virtual |
55 | | ///< server and the entry point for the state machine. |
56 | | fr_process_module_t const *process_module; //!< Public interface to the process_mi. |
57 | | ///< cached for convenience. |
58 | | |
59 | | fr_rb_tree_t *sections; //!< List of sections that need to be compiled. |
60 | | |
61 | | fr_log_t *log; //!< log destination |
62 | | char const *log_name; //!< name of log destination |
63 | | |
64 | | void **finally_by_packet_type; //!< Finalise instruction by packet type. |
65 | | void *finally_default; //!< Default finally instruction. |
66 | | }; |
67 | | |
68 | | static fr_dict_t const *dict_freeradius; |
69 | | |
70 | | static fr_dict_attr_t const *attr_auth_type; |
71 | | |
72 | | extern fr_dict_autoload_t virtual_server_dict_autoload[]; |
73 | | fr_dict_autoload_t virtual_server_dict_autoload[] = { |
74 | | { .out = &dict_freeradius, .proto = "freeradius" }, |
75 | | DICT_AUTOLOAD_TERMINATOR |
76 | | }; |
77 | | |
78 | | extern fr_dict_attr_autoload_t virtual_server_dict_attr_autoload[]; |
79 | | fr_dict_attr_autoload_t virtual_server_dict_attr_autoload[] = { |
80 | | { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius }, |
81 | | |
82 | | DICT_AUTOLOAD_TERMINATOR |
83 | | }; |
84 | | |
85 | | /** List of process modules we've loaded |
86 | | * |
87 | | * This is global for all virtual servers. Must be initialised |
88 | | * _before_ the configuration is loaded. |
89 | | */ |
90 | | static module_list_t *process_modules; |
91 | | |
92 | | /** List of proto modules we've loaded |
93 | | * |
94 | | * This is global for all virtual servers. Must be initialised |
95 | | * _before_ the configuration is loaded. |
96 | | */ |
97 | | static module_list_t *proto_modules; |
98 | | |
99 | | /** Top level structure holding all virtual servers |
100 | | * |
101 | | */ |
102 | | static virtual_server_t **virtual_servers; |
103 | | |
104 | | /** CONF_SECTION holding all the virtual servers |
105 | | * |
106 | | * Set during the call to virtual_server_bootstrap and used by |
107 | | * other virtual server functions. |
108 | | */ |
109 | | static CONF_SECTION *virtual_server_root; |
110 | | |
111 | | static fr_rb_tree_t *listen_addr_root = NULL; |
112 | | |
113 | | static int namespace_on_read(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule); |
114 | | |
115 | | static int namespace_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule); |
116 | | static int listen_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule); |
117 | | static int server_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED conf_parser_t const *rule); |
118 | | |
119 | | static const conf_parser_t server_on_read_config[] = { |
120 | | { FR_CONF_OFFSET_TYPE_FLAGS("namespace", FR_TYPE_VOID, CONF_FLAG_REQUIRED, virtual_server_t, process_mi), |
121 | | .on_read = namespace_on_read }, |
122 | | |
123 | | CONF_PARSER_TERMINATOR |
124 | | }; |
125 | | |
126 | | const conf_parser_t virtual_servers_on_read_config[] = { |
127 | | /* |
128 | | * Not really ok if it's missing but we want to |
129 | | * let logic elsewhere handle the issue. |
130 | | */ |
131 | | { FR_CONF_POINTER("server", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OK_MISSING | CONF_FLAG_MULTI, &virtual_servers), |
132 | | .subcs_size = sizeof(virtual_server_t), .subcs_type = "virtual_server_t", |
133 | | .subcs = (void const *) server_on_read_config, .name2 = CF_IDENT_ANY, |
134 | | .on_read = cf_null_on_read }, |
135 | | |
136 | | CONF_PARSER_TERMINATOR |
137 | | }; |
138 | | |
139 | | static const conf_parser_t server_config[] = { |
140 | | { FR_CONF_OFFSET_TYPE_FLAGS("namespace", FR_TYPE_VOID, CONF_FLAG_REQUIRED, virtual_server_t, process_mi), |
141 | | .func = namespace_parse }, |
142 | | |
143 | | { FR_CONF_OFFSET_TYPE_FLAGS("listen", FR_TYPE_VOID, CONF_FLAG_SUBSECTION | CONF_FLAG_OK_MISSING | CONF_FLAG_MULTI, |
144 | | virtual_server_t, listeners), |
145 | | .name2 = CF_IDENT_ANY, |
146 | | .subcs_size = sizeof(virtual_server_listen_t), .subcs_type = "virtual_server_listen_t", |
147 | | .func = listen_parse }, |
148 | | |
149 | | { FR_CONF_OFFSET("log", virtual_server_t, log_name), }, |
150 | | |
151 | | CONF_PARSER_TERMINATOR |
152 | | }; |
153 | | |
154 | | const conf_parser_t virtual_servers_config[] = { |
155 | | /* |
156 | | * Not really ok if it's missing but we want to |
157 | | * let logic elsewhere handle the issue. |
158 | | */ |
159 | | { FR_CONF_POINTER("server", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OK_MISSING | CONF_FLAG_MULTI, &virtual_servers), |
160 | | .subcs_size = sizeof(virtual_server_t), .subcs_type = "virtual_server_t", |
161 | | .subcs = (void const *) server_config, .name2 = CF_IDENT_ANY, |
162 | | .func = server_parse }, |
163 | | |
164 | | CONF_PARSER_TERMINATOR |
165 | | }; |
166 | | |
167 | | /** Print all the loaded listener instances |
168 | | * |
169 | | */ |
170 | | void virtual_server_listen_debug(void) |
171 | 0 | { |
172 | 0 | module_list_debug(proto_modules); |
173 | 0 | } |
174 | | |
175 | | /** Print all the loaded process module instances |
176 | | * |
177 | | */ |
178 | | void virtual_server_process_debug(void) |
179 | 0 | { |
180 | 0 | module_list_debug(process_modules); |
181 | 0 | } |
182 | | |
183 | | /** Resolve proto data to a module instance |
184 | | * |
185 | | * @param[in] data Pointer to the proto data. |
186 | | * @return |
187 | | * - The module instance for the proto data. |
188 | | * - NULL if no data matches. |
189 | | */ |
190 | | module_instance_t *virtual_server_listener_by_data(void const *data) |
191 | 0 | { |
192 | 0 | return module_instance_by_data(proto_modules, data); |
193 | 0 | } |
194 | | |
195 | | /** Generic conf_parser_t func for loading drivers |
196 | | * |
197 | | */ |
198 | | int virtual_server_listen_transport_parse(TALLOC_CTX *ctx, void *out, void *parent, |
199 | | CONF_ITEM *ci, conf_parser_t const *rule) |
200 | 0 | { |
201 | 0 | conf_parser_t our_rule = *rule; |
202 | |
|
203 | 0 | our_rule.uctx = &proto_modules; |
204 | |
|
205 | 0 | return module_submodule_parse(ctx, out, parent, ci, &our_rule); |
206 | 0 | } |
207 | | |
208 | | /** Parse a "namespace" parameter |
209 | | * |
210 | | * We need to load the process module before continuing to parse the virtual server contents |
211 | | * as we need to know the namespace so that we can resolve attribute names. |
212 | | * |
213 | | * We also need the compilation list from the proto module to figure out which sections we |
214 | | * need to compile. |
215 | | * |
216 | | * @param[in] ctx to allocate data in. |
217 | | * @param[out] out always NULL |
218 | | * @param[in] parent Base structure address. |
219 | | * @param[in] ci #CONF_SECTION containing the listen section. |
220 | | * @param[in] rule unused. |
221 | | * @return |
222 | | * - 0 on success. |
223 | | * - -1 on failure. |
224 | | */ |
225 | | static int namespace_on_read(TALLOC_CTX *ctx, UNUSED void *out, UNUSED void *parent, |
226 | | CONF_ITEM *ci, UNUSED conf_parser_t const *rule) |
227 | 0 | { |
228 | 0 | CONF_PAIR *cp = cf_item_to_pair(ci); |
229 | 0 | CONF_SECTION *server_cs = cf_item_to_section(cf_parent(ci)); |
230 | 0 | module_instance_t *mi; |
231 | 0 | char const *namespace; |
232 | 0 | char *module_name, *p, *end; |
233 | 0 | char const *inst_name; |
234 | 0 | fr_process_module_t const *process; |
235 | |
|
236 | 0 | fr_cond_assert_msg(process_modules, |
237 | 0 | "virtual_servers_init MUST be called before reading virtual server config"); |
238 | |
|
239 | 0 | namespace = cf_pair_value(cp); |
240 | 0 | module_name = talloc_strdup(ctx, namespace); |
241 | | |
242 | | /* |
243 | | * Smush all hyphens to underscores for module names |
244 | | */ |
245 | 0 | for (p = module_name, end = module_name + talloc_strlen(module_name); |
246 | 0 | p < end; |
247 | 0 | p++) if (*p == '-') *p = '_'; |
248 | |
|
249 | 0 | if (module_instance_name_from_conf(&inst_name, server_cs) < 0) return -1; |
250 | | |
251 | | /* |
252 | | * The module being loaded is the namespace with all '-' |
253 | | * transformed to '_'. |
254 | | * |
255 | | * The instance name is the virtual server name. |
256 | | */ |
257 | 0 | mi = module_instance_alloc(process_modules, NULL, DL_MODULE_TYPE_PROCESS, |
258 | 0 | module_name, inst_name, |
259 | 0 | MODULE_INSTANCE_NONE); |
260 | 0 | talloc_free(module_name); |
261 | 0 | if (mi == NULL) { |
262 | 0 | error: |
263 | 0 | cf_log_perr(ci, "Failed loading process module"); |
264 | 0 | return -1; |
265 | 0 | } |
266 | 0 | if (unlikely(module_instance_conf_parse(mi, mi->conf) < 0)) { |
267 | 0 | talloc_free(mi); |
268 | 0 | goto error; |
269 | 0 | } |
270 | | |
271 | 0 | process = (fr_process_module_t const *)mi->module->exported; |
272 | 0 | if (!*(process->dict)) { |
273 | 0 | cf_log_err(ci, "Process module is invalid - missing namespace dictionary"); |
274 | 0 | talloc_free(mi); |
275 | 0 | return -1; |
276 | 0 | } |
277 | 0 | cf_data_add(server_cs, mi, "process_module", false); |
278 | 0 | cf_data_add(server_cs, *(process->dict), "dict", false); |
279 | |
|
280 | 0 | return 0; |
281 | 0 | } |
282 | | |
283 | | static inline CC_HINT(always_inline) |
284 | | int add_compile_list(virtual_server_t *vs, CONF_SECTION *cs, virtual_server_compile_t const *compile_list, char const *name) |
285 | 0 | { |
286 | 0 | int i; |
287 | 0 | virtual_server_compile_t const *list = compile_list; |
288 | |
|
289 | 0 | if (!compile_list) return 0; |
290 | | |
291 | 0 | for (i = 0; list[i].section; i++) { |
292 | 0 | #ifndef NDEBUG |
293 | | /* |
294 | | * We can't have a wildcard for name1. It MUST be a real name. |
295 | | * |
296 | | * The wildcard was allowed previously for ideas which later didn't turn out. |
297 | | */ |
298 | 0 | if (list[i].section->name1 == CF_IDENT_ANY) { |
299 | 0 | fr_assert(0); |
300 | 0 | continue; |
301 | 0 | } |
302 | | |
303 | 0 | #endif |
304 | 0 | if (virtual_server_section_register(vs, &list[i]) < 0) { |
305 | 0 | cf_log_err(cs, "Failed registering processing section name %s for %s", |
306 | 0 | list[i].section->name1, name); |
307 | 0 | return -1; |
308 | 0 | } |
309 | 0 | } |
310 | | |
311 | 0 | return 0; |
312 | 0 | } |
313 | | |
314 | | /** dl_open a process_* module |
315 | | * |
316 | | * @param[in] ctx to allocate data in. |
317 | | * @param[out] out Where to our listen configuration. Is a #fr_virtual_listen_t structure. |
318 | | * @param[in] parent Base structure address. |
319 | | * @param[in] ci #CONF_SECTION containing the listen section. |
320 | | * @param[in] rule unused. |
321 | | * @return |
322 | | * - 0 on success. |
323 | | * - -1 on failure. |
324 | | */ |
325 | | static int namespace_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED conf_parser_t const *rule) |
326 | 0 | { |
327 | 0 | CONF_PAIR *cp = cf_item_to_pair(ci); |
328 | 0 | CONF_SECTION *server_cs = cf_item_to_section(cf_parent(ci)); |
329 | 0 | CONF_SECTION *process_cs; |
330 | 0 | virtual_server_t *server = talloc_get_type_abort(((uint8_t *) out) - offsetof(virtual_server_t, process_mi), virtual_server_t); |
331 | 0 | char const *namespace = cf_pair_value(cp); |
332 | 0 | module_instance_t *mi = cf_data_value(cf_data_find(server_cs, module_instance_t, "process_module")); |
333 | | |
334 | | /* |
335 | | * We don't have access to virtual_server_t |
336 | | * in the onread callback, so we need to do the |
337 | | * fixups here. |
338 | | */ |
339 | 0 | server->process_mi = mi; |
340 | 0 | server->process_module = (fr_process_module_t const *)mi->module->exported; |
341 | |
|
342 | 0 | *(module_instance_t const **)out = mi; |
343 | | |
344 | | /* |
345 | | * Enforce that the protocol process configuration is in |
346 | | * a subsection named for the protocol. We don't know if |
347 | | * the process code needs this, so we just mark it as |
348 | | * parsed, in order to avoid spurious complaints. |
349 | | */ |
350 | 0 | process_cs = cf_section_find(server_cs, namespace, NULL); |
351 | 0 | if (!process_cs) { |
352 | 0 | MEM(process_cs = cf_section_alloc(server_cs, server_cs, namespace, NULL)); |
353 | 0 | cf_item_mark_parsed(process_cs); |
354 | 0 | } |
355 | |
|
356 | 0 | if (module_instance_conf_parse(mi, process_cs) < 0) { |
357 | 0 | cf_log_perr(ci, "Failed bootstrapping process module"); |
358 | 0 | cf_data_remove(server_cs, module_instance_t, "process_module"); |
359 | 0 | cf_data_remove(server_cs, fr_dict_t, "dict"); |
360 | 0 | TALLOC_FREE(server->process_mi); |
361 | 0 | return -1; |
362 | 0 | } |
363 | | |
364 | | /* |
365 | | * Pull the list of sections we need to compile out of |
366 | | * the process module's public struct. |
367 | | */ |
368 | 0 | return add_compile_list(server, server->process_mi->conf, server->process_module->compile_list, namespace); |
369 | 0 | } |
370 | | |
371 | | /** dl_open a proto_* module |
372 | | * |
373 | | * @param[in] ctx to allocate data in. |
374 | | * @param[out] out Where to our listen configuration. Is a #fr_virtual_listen_t structure. |
375 | | * @param[in] parent Base structure address. |
376 | | * @param[in] ci #CONF_SECTION containing the listen section. |
377 | | * @param[in] rule unused. |
378 | | * @return |
379 | | * - 0 on success. |
380 | | * - -1 on failure. |
381 | | */ |
382 | | static int listen_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED conf_parser_t const *rule) |
383 | 0 | { |
384 | 0 | virtual_server_listen_t *listener = talloc_get_type_abort(out, virtual_server_listen_t); /* Pre-allocated for us */ |
385 | 0 | CONF_SECTION *listener_cs = cf_item_to_section(ci); |
386 | 0 | CONF_SECTION *server_cs = cf_item_to_section(cf_parent(ci)); |
387 | 0 | CONF_PAIR *namespace = cf_pair_find(server_cs, "namespace"); |
388 | |
|
389 | 0 | CONF_PAIR *cp; |
390 | 0 | char const *module, *name2, *p; |
391 | 0 | fr_sbuff_t *agg; |
392 | |
|
393 | 0 | module_instance_t *mi; |
394 | |
|
395 | 0 | fr_cond_assert_msg(proto_modules, |
396 | 0 | "virtual_servers_init MUST be called before reading virtual server config"); |
397 | |
|
398 | 0 | if (!namespace) { |
399 | 0 | cf_log_err(server_cs, "No 'namespace' set for virtual server"); |
400 | 0 | cf_log_err(server_cs, "Please add 'namespace = <protocol>' inside of the 'server %s { ... }' section", |
401 | 0 | cf_section_name2(server_cs)); |
402 | 0 | return -1; |
403 | 0 | } |
404 | | |
405 | | /* |
406 | | * The module we load comes from the 'handler' or 'proto' configuration. This configuration item |
407 | | * allows us to have multi-protocol listen module, such as "detail". |
408 | | * |
409 | | * If there isn't a multi-protocol listen module, then load the one for this namespace, such as |
410 | | * "radius". |
411 | | */ |
412 | 0 | cp = cf_pair_find(listener_cs, "handler"); |
413 | 0 | if (!cp) cp = cf_pair_find(listener_cs, "proto"); |
414 | 0 | if (cp) { |
415 | 0 | module = cf_pair_value(cp); |
416 | 0 | if (!module) { |
417 | 0 | cf_log_err(cp, "Missing value"); |
418 | 0 | return -1; |
419 | 0 | } |
420 | 0 | } else { |
421 | 0 | module = cf_pair_value(namespace); |
422 | 0 | } |
423 | | |
424 | | /* |
425 | | * The proto modules go into a global tree, so we qualify their name (second name) with the name |
426 | | * of the virtual server, followed by the transport, followed by any transport-specific |
427 | | * configuration. |
428 | | */ |
429 | 0 | FR_SBUFF_TALLOC_THREAD_LOCAL(&agg, 64, MODULE_INSTANCE_LEN_MAX); |
430 | |
|
431 | 0 | name2 = cf_section_name2(listener_cs); |
432 | | |
433 | | /* |
434 | | * A 'listen foo' in this virtual server helps it to be unique. We load the "radius" module, |
435 | | * instance "default.foo". This naming scheme is a little counter-intuitive, but it is required |
436 | | * due to the listen module list being global, and loading the leading module name e.g. "radius". |
437 | | * |
438 | | * Since the virtual server names are unique, we don't have to qualify the listen section names |
439 | | * by anything else. |
440 | | */ |
441 | 0 | if (name2) { |
442 | | /* |
443 | | * We do some duplicate checks here, because that gives better errors than if we use the |
444 | | * mangled names below. |
445 | | */ |
446 | 0 | if (module_instance_name_valid(name2) < 0) { |
447 | 0 | cf_log_perr(listener_cs, "Invalid name in 'listen %s'", name2); |
448 | 0 | return -1; |
449 | 0 | } |
450 | | |
451 | | /* |
452 | | * Load "radius", instance "default.foo". |
453 | | */ |
454 | 0 | if ((fr_sbuff_in_char(agg, '.') < 0) || (fr_sbuff_in_strcpy(agg, name2) < 0)) goto name_too_long; |
455 | 0 | } |
456 | | |
457 | | /* |
458 | | * Further qualify the name by transport and port. This helps it to be more unique. |
459 | | */ |
460 | 0 | cp = cf_pair_find(listener_cs, "transport"); |
461 | 0 | if (!cp || ((p = cf_pair_value(cp)) == NULL)) { |
462 | 0 | cf_log_err(listener_cs, "Invalid 'listen' section - No 'transport = ...' definition was found."); |
463 | 0 | return -1; |
464 | 0 | } |
465 | 0 | if ((fr_sbuff_in_char(agg, '.') < 0) || (fr_sbuff_in_strcpy(agg, p) < 0)) { |
466 | 0 | name_too_long: |
467 | 0 | cf_log_err(listener_cs, "Listener name is too long"); |
468 | 0 | return -1; |
469 | 0 | } |
470 | | |
471 | 0 | cp = cf_pair_find(listener_cs, "port"); |
472 | 0 | if (cp && ((p = cf_pair_value(cp)) != NULL)) { |
473 | 0 | if ((fr_sbuff_in_char(agg, '.') < 0) || (fr_sbuff_in_strcpy(agg, p) < 0)) goto name_too_long; |
474 | 0 | } |
475 | | |
476 | 0 | if (module_instance_name_valid(fr_sbuff_start(agg)) < 0) { |
477 | 0 | cf_log_perr(listener_cs, "Failed loading listen section."); |
478 | 0 | return -1; |
479 | 0 | } |
480 | | |
481 | 0 | mi = module_instance_alloc(proto_modules, NULL, DL_MODULE_TYPE_PROTO, module, fr_sbuff_start(agg), MODULE_INSTANCE_NONE); |
482 | 0 | if (!mi) { |
483 | 0 | cf_log_perr(listener_cs, "Failed creating listen section"); |
484 | 0 | return -1; |
485 | 0 | } |
486 | | |
487 | 0 | if (unlikely(module_instance_conf_parse(mi, listener_cs) < 0)) { |
488 | 0 | talloc_free(mi); |
489 | 0 | cf_log_perr(listener_cs, "Failed parsing listen section"); |
490 | 0 | return -1; |
491 | 0 | } |
492 | | |
493 | 0 | listener->proto_mi = mi; |
494 | 0 | listener->proto_module = (fr_app_t const *)listener->proto_mi->module->exported; |
495 | 0 | cf_data_add(listener_cs, mi, "proto_module", false); |
496 | | |
497 | | /* |
498 | | * The listener doesn't have to contain a 'type = foo' configuration. But if it does, there MUST |
499 | | * also be a 'recv foo' section. |
500 | | */ |
501 | 0 | for (cp = cf_pair_find(listener_cs, "type"); |
502 | 0 | cp != NULL; |
503 | 0 | cp = cf_pair_find_next(listener_cs, cp, "type")) { |
504 | 0 | char const *type = cf_pair_value(cp); |
505 | |
|
506 | 0 | if (!type) continue; |
507 | | |
508 | 0 | if (!cf_section_find(server_cs, "recv", type)) { |
509 | 0 | cf_log_err(cp, "Listener has 'type = %s', but there is no 'recv %s { ... }' section defined.", |
510 | 0 | type, type); |
511 | 0 | return -1; |
512 | 0 | } |
513 | 0 | } |
514 | | |
515 | 0 | return 0; |
516 | 0 | } |
517 | | |
518 | | static fr_cmp_ret_t virtual_server_compile_name_cmp(void const *a, void const *b) |
519 | 0 | { |
520 | 0 | virtual_server_compile_t const *sa = a; |
521 | 0 | virtual_server_compile_t const *sb = b; |
522 | |
|
523 | 0 | return section_name_cmp(sa->section, sb->section); |
524 | 0 | } |
525 | | |
526 | | /** Callback to validate the server section |
527 | | * |
528 | | * @param[in] ctx to allocate data in. |
529 | | * @param[out] out Where to our listen configuration. Is a #virtual_server_t structure. |
530 | | * @param[in] parent Base structure address. |
531 | | * @param[in] ci #CONF_SECTION containing the listen section. |
532 | | * @param[in] rule unused. |
533 | | * @return |
534 | | * - 0 on success. |
535 | | * - -1 on failure. |
536 | | */ |
537 | | static int server_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, |
538 | | CONF_ITEM *ci, UNUSED conf_parser_t const *rule) |
539 | 0 | { |
540 | 0 | virtual_server_t *server = talloc_get_type_abort(out, virtual_server_t); |
541 | 0 | CONF_SECTION *server_cs = cf_item_to_section(ci); |
542 | 0 | CONF_PAIR *namespace; |
543 | |
|
544 | 0 | namespace = cf_pair_find(server_cs, "namespace"); |
545 | 0 | if (!namespace) { |
546 | 0 | cf_log_err(server_cs, "virtual server %s MUST contain a 'namespace' option", |
547 | 0 | cf_section_name2(server_cs)); |
548 | 0 | return -1; |
549 | 0 | } |
550 | | |
551 | 0 | MEM(server->sections = fr_rb_alloc(server, virtual_server_compile_name_cmp, NULL)); |
552 | 0 | server->server_cs = server_cs; |
553 | | |
554 | | /* |
555 | | * Now parse the listeners |
556 | | */ |
557 | 0 | if (cf_section_parse(out, server, server_cs) < 0) return -1; |
558 | | |
559 | | /* |
560 | | * And cache this struct for later referencing. |
561 | | */ |
562 | 0 | cf_data_add(server_cs, server, NULL, false); |
563 | |
|
564 | 0 | return 0; |
565 | 0 | } |
566 | | |
567 | | /** Return the namespace for the named virtual server |
568 | | * |
569 | | * @param[in] virtual_server to look for namespace in. |
570 | | * @return |
571 | | * - NULL on error. |
572 | | * - Namespace on success. |
573 | | */ |
574 | | fr_dict_t const *virtual_server_dict_by_name(char const *virtual_server) |
575 | 0 | { |
576 | 0 | virtual_server_t const *vs; |
577 | |
|
578 | 0 | vs = virtual_server_find(virtual_server); |
579 | 0 | if (!vs) return NULL; |
580 | | |
581 | 0 | return virtual_server_dict_by_cs(vs->server_cs); |
582 | 0 | } |
583 | | |
584 | | /** Return the namespace for specified CONF_SECTION |
585 | | * |
586 | | * @param[in] cs to look for namespace in. |
587 | | * @return |
588 | | * - NULL on error. |
589 | | * - Namespace on success. |
590 | | */ |
591 | | fr_dict_t const *virtual_server_dict_by_cs(CONF_SECTION const *cs) |
592 | 0 | { |
593 | 0 | CONF_DATA const *cd; |
594 | 0 | fr_dict_t *dict; |
595 | |
|
596 | 0 | cd = cf_data_find(cs, fr_dict_t, "dict"); |
597 | 0 | if (!cd) return NULL; |
598 | | |
599 | 0 | dict = cf_data_value(cd); |
600 | 0 | (void) talloc_get_type_abort(dict, fr_dict_t); |
601 | |
|
602 | 0 | return dict; |
603 | 0 | } |
604 | | |
605 | | /** Return the namespace for a given virtual server specified by a CONF_ITEM within the virtual server |
606 | | * |
607 | | * @param[in] ci to look for namespace in. |
608 | | * @return |
609 | | * - NULL on error. |
610 | | * - Namespace on success. |
611 | | */ |
612 | | fr_dict_t const *virtual_server_dict_by_child_ci(CONF_ITEM const *ci) |
613 | 0 | { |
614 | 0 | CONF_DATA const *cd; |
615 | 0 | fr_dict_t *dict; |
616 | |
|
617 | 0 | cd = cf_data_find_in_parent(ci, fr_dict_t, "dict"); |
618 | 0 | if (!cd) return NULL; |
619 | | |
620 | 0 | dict = cf_data_value(cd); |
621 | 0 | (void) talloc_get_type_abort(dict, fr_dict_t); |
622 | |
|
623 | 0 | return dict; |
624 | 0 | } |
625 | | |
626 | | /** Verify that a given virtual_server exists and is of a particular namespace |
627 | | * |
628 | | * Mostly used by modules to check virtual servers specified by their configs. |
629 | | * |
630 | | * @param[out] out we found. May be NULL if just checking for existence. |
631 | | * @param[in] virtual_server to check. |
632 | | * @param[in] namespace the virtual server must belong to. |
633 | | * @param[in] ci to log errors against. May be NULL if caller |
634 | | * doesn't want errors logged. |
635 | | * @return |
636 | | * - 0 on success. |
637 | | * - -1 if no virtual server could be found. |
638 | | * - -2 if virtual server is not of the correct namespace. |
639 | | */ |
640 | | int virtual_server_has_namespace(CONF_SECTION **out, |
641 | | char const *virtual_server, fr_dict_t const *namespace, CONF_ITEM *ci) |
642 | 0 | { |
643 | 0 | virtual_server_t const *vs; |
644 | 0 | CONF_SECTION *server_cs; |
645 | 0 | fr_dict_t const *dict; |
646 | |
|
647 | 0 | if (out) *out = NULL; |
648 | |
|
649 | 0 | vs = virtual_server_find(virtual_server); |
650 | 0 | if (!vs) { |
651 | 0 | if (ci) cf_log_err(ci, "Can't find virtual server \"%s\"", virtual_server); |
652 | 0 | return -1; |
653 | 0 | } |
654 | 0 | server_cs = virtual_server_cs(vs); |
655 | |
|
656 | 0 | dict = virtual_server_dict_by_name(virtual_server); |
657 | 0 | if (!dict) { |
658 | | /* |
659 | | * Not sure this is even a valid state? |
660 | | */ |
661 | 0 | if (ci) cf_log_err(ci, "No namespace found in virtual server \"%s\"", virtual_server); |
662 | 0 | return -2; |
663 | 0 | } |
664 | | |
665 | 0 | if (dict != namespace) { |
666 | 0 | if (ci) { |
667 | 0 | cf_log_err(ci, |
668 | 0 | "Expected virtual server \"%s\" to be of namespace \"%s\", got namespace \"%s\"", |
669 | 0 | virtual_server, fr_dict_root(namespace)->name, fr_dict_root(dict)->name); |
670 | 0 | } |
671 | 0 | return -2; |
672 | 0 | } |
673 | | |
674 | 0 | if (out) *out = server_cs; |
675 | |
|
676 | 0 | return 0; |
677 | 0 | } |
678 | | |
679 | | /* |
680 | | * If we pushed a log destination, we need to pop it. |
681 | | */ |
682 | | static unlang_action_t server_remove_log_destination(UNUSED unlang_result_t *p_result, |
683 | | request_t *request, void *uctx) |
684 | 0 | { |
685 | 0 | virtual_server_t *server = uctx; |
686 | |
|
687 | 0 | request_log_prepend(request, server->log, L_DBG_LVL_DISABLE); |
688 | |
|
689 | 0 | return UNLANG_ACTION_CALCULATE_RESULT; |
690 | 0 | } |
691 | | |
692 | | static void server_signal_remove_log_destination(request_t *request, UNUSED fr_signal_t action, void *uctx) |
693 | 0 | { |
694 | 0 | virtual_server_t *server = uctx; |
695 | |
|
696 | 0 | request_log_prepend(request, server->log, L_DBG_LVL_DISABLE); |
697 | 0 | } |
698 | | |
699 | | /** Push a finally section onto the stack |
700 | | * |
701 | | * The finally instruction cannot be cancelled and will always execute as the stack is unwound. |
702 | | * |
703 | | * @param[in] request request to push the finally section onto. |
704 | | * @param[in] vs virtual server to push the finally section for. |
705 | | * @param[in,out] top_frame mutate the top frame value to indicate that future |
706 | | * pushes should be subframes. |
707 | | * @return |
708 | | * - 0 on success. |
709 | | * - -1 on failure. |
710 | | */ |
711 | | static inline CC_HINT(always_inline) int virtual_server_push_finally(request_t *request, virtual_server_t const *vs, bool *top_frame) |
712 | 0 | { |
713 | | /* |
714 | | * Don't bother finding the packet type unless there |
715 | | * are finally sections registered. |
716 | | */ |
717 | 0 | if (vs->finally_by_packet_type) { |
718 | 0 | fr_pair_t *vp = fr_pair_find_by_da(&request->request_pairs, NULL, |
719 | 0 | *(vs->process_module->packet_type)); |
720 | 0 | fr_value_box_t key; |
721 | 0 | void *instruction; |
722 | |
|
723 | 0 | if (!vp) goto check_default; /* No packet type found, still allow default finally section */ |
724 | | |
725 | 0 | if (fr_value_box_cast(NULL, &key, FR_TYPE_UINT16, NULL, &vp->data) < 0) return -1; |
726 | | |
727 | 0 | if (key.vb_uint16 >= talloc_array_length(vs->finally_by_packet_type) || |
728 | 0 | !(instruction = vs->finally_by_packet_type[key.vb_uint16])) goto check_default; |
729 | | |
730 | 0 | if (unlikely(unlang_finally_push_instruction(request, instruction, |
731 | 0 | fr_time_delta_from_sec(5), *top_frame) < 0)) return -1; |
732 | | |
733 | 0 | *top_frame = UNLANG_SUB_FRAME; /* switch to SUB_FRAME after the first instruction */ |
734 | |
|
735 | 0 | return 0; |
736 | 0 | } |
737 | | |
738 | 0 | check_default: |
739 | 0 | if (vs->finally_default) { |
740 | 0 | if (unlikely(unlang_finally_push_instruction(request, vs->finally_default, |
741 | 0 | fr_time_delta_from_sec(5), *top_frame) < 0)) return -1; |
742 | | |
743 | 0 | *top_frame = UNLANG_SUB_FRAME; /* switch to SUB_FRAME after the first instruction */ |
744 | 0 | } |
745 | | |
746 | 0 | return 0; |
747 | 0 | } |
748 | | |
749 | | /** Set the request processing function. |
750 | | * |
751 | | * Short-term hack |
752 | | */ |
753 | | unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *request, virtual_server_t const *vs, bool top_frame) |
754 | 0 | { |
755 | | /* |
756 | | * Add a log destination specific to this virtual server. |
757 | | * |
758 | | * If we add a log destination, make sure to remove it when we walk back up the stack. |
759 | | * But ONLY if we're not at the top of the stack. |
760 | | * |
761 | | * When a brand new request comes in, it has a "call" frame pushed, and then this function is |
762 | | * called. So if we're at the top of the stack, we don't need to pop any logging function, |
763 | | * because the request will die immediately after the top "call" frame is popped. |
764 | | * |
765 | | * However, if we're being reached from a "call" frame in the middle of the stack, then |
766 | | * we do have to pop the log destination when we return. |
767 | | */ |
768 | 0 | if (vs->log) { |
769 | 0 | request_log_prepend(request, vs->log, fr_debug_lvl); |
770 | |
|
771 | 0 | if (unlang_interpret_stack_depth(request) > 1) { |
772 | 0 | unlang_action_t action; |
773 | |
|
774 | 0 | action = unlang_function_push_with_result(unlang_interpret_result(request), /* transparent */ |
775 | 0 | request, |
776 | 0 | NULL, /* don't call it immediately */ |
777 | 0 | server_remove_log_destination, /* but when we pop the frame */ |
778 | 0 | server_signal_remove_log_destination, FR_SIGNAL_ALLOW(FR_SIGNAL_CANCEL), |
779 | 0 | top_frame, |
780 | 0 | UNCONST(void *, vs)); |
781 | 0 | if (action != UNLANG_ACTION_PUSHED_CHILD) return action; |
782 | | |
783 | 0 | top_frame = UNLANG_SUB_FRAME; /* switch to SUB_FRAME after the first instruction */ |
784 | 0 | } |
785 | 0 | } |
786 | | |
787 | | /* |
788 | | * Push an uncancellable finally instruction to the stack. |
789 | | */ |
790 | 0 | if (virtual_server_push_finally(request, vs, &top_frame) < 0) return UNLANG_ACTION_FAIL; |
791 | | |
792 | | /* |
793 | | * Bootstrap the stack with a module instance. |
794 | | */ |
795 | 0 | if (unlang_module_push(p_result, request, vs->process_mi, |
796 | 0 | vs->process_module->process, top_frame) < 0) return UNLANG_ACTION_FAIL; |
797 | | |
798 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
799 | 0 | } |
800 | | |
801 | | static int cmd_show_server_list(FILE *fp, UNUSED FILE *fp_err, UNUSED void *ctx, UNUSED fr_cmd_info_t const *info) |
802 | 0 | { |
803 | 0 | size_t i, server_cnt = virtual_servers ? talloc_array_length(virtual_servers) : 0; |
804 | |
|
805 | 0 | if (!server_cnt) return 0; |
806 | | |
807 | 0 | for (i = 0; i < server_cnt; i++) { |
808 | 0 | fprintf(fp, "%-30snamespace = %s\n", cf_section_name2(virtual_servers[i]->server_cs), |
809 | 0 | fr_dict_root(*(virtual_servers[i]->process_module->dict))->name); |
810 | 0 | } |
811 | |
|
812 | 0 | return 0; |
813 | 0 | } |
814 | | |
815 | | static fr_cmd_table_t cmd_table[] = { |
816 | | { |
817 | | .parent = "show", |
818 | | .name = "server", |
819 | | .help = "Show virtual server settings.", |
820 | | .read_only = true, |
821 | | }, |
822 | | |
823 | | { |
824 | | .parent = "show server", |
825 | | .name = "list", |
826 | | .func = cmd_show_server_list, |
827 | | .help = "Show the list of virtual servers loaded in the server.", |
828 | | .read_only = true, |
829 | | }, |
830 | | |
831 | | CMD_TABLE_END |
832 | | |
833 | | }; |
834 | | |
835 | | /** Compare listeners by app_io_addr |
836 | | * |
837 | | * Only works for IP addresses, and will blow up on file names |
838 | | */ |
839 | | static fr_cmp_ret_t listen_addr_cmp(void const *one, void const *two) |
840 | 0 | { |
841 | 0 | fr_listen_instance_t const *a = one; |
842 | 0 | fr_listen_instance_t const *b = two; |
843 | 0 | fr_ipaddr_t aip, bip; |
844 | 0 | int ret; |
845 | | |
846 | | /* |
847 | | * The caller must ensure that the address field is set. |
848 | | */ |
849 | 0 | if (!a->app_io_addr && !b->app_io_addr) return CMP_EQ; |
850 | 0 | if (!a->app_io_addr && b->app_io_addr) return CMP_LT; |
851 | 0 | if (a->app_io_addr && !b->app_io_addr) return CMP_GT; |
852 | | |
853 | | /* |
854 | | * Address family |
855 | | */ |
856 | 0 | CMP_RETURN(a, b, app_io_addr->af); |
857 | | |
858 | 0 | fr_assert((a->app_io_addr->af == AF_INET) || ((a->app_io_addr->af == AF_INET6))); |
859 | | |
860 | | /* |
861 | | * UDP vs TCP |
862 | | */ |
863 | 0 | CMP_RETURN(a, b, app_io_addr->type); |
864 | | |
865 | | /* |
866 | | * Check ports. |
867 | | */ |
868 | 0 | CMP_RETURN(a, b, app_io_addr->inet.src_port); |
869 | | |
870 | | /* |
871 | | * Don't call fr_ipaddr_cmp(), as we need to do our own |
872 | | * checks here. We have various wildcard checks which |
873 | | * aren't globally applicable. |
874 | | */ |
875 | | |
876 | | /* |
877 | | * Different address families. |
878 | | */ |
879 | 0 | CMP_RETURN(a, b, app_io_addr->inet.src_ipaddr.af); |
880 | | |
881 | | /* |
882 | | * If both are bound to interfaces, AND the interfaces |
883 | | * are different, then there is no conflict. |
884 | | */ |
885 | 0 | if (a->app_io_addr->inet.src_ipaddr.scope_id && b->app_io_addr->inet.src_ipaddr.scope_id) { |
886 | 0 | CMP_RETURN(a, b, app_io_addr->inet.src_ipaddr.scope_id); |
887 | 0 | } |
888 | | |
889 | 0 | ret = a->app_io_addr->inet.src_ipaddr.prefix - b->app_io_addr->inet.src_ipaddr.prefix; |
890 | 0 | aip = a->app_io_addr->inet.src_ipaddr; |
891 | 0 | bip = b->app_io_addr->inet.src_ipaddr; |
892 | | |
893 | | /* |
894 | | * Mask out the longer prefix to match the shorter |
895 | | * prefix. |
896 | | */ |
897 | 0 | if (ret < 0) { |
898 | 0 | fr_ipaddr_mask(&bip, a->app_io_addr->inet.src_ipaddr.prefix); |
899 | |
|
900 | 0 | } else if (ret > 0) { |
901 | 0 | fr_ipaddr_mask(&aip, b->app_io_addr->inet.src_ipaddr.prefix); |
902 | |
|
903 | 0 | } |
904 | |
|
905 | 0 | return fr_ipaddr_cmp(&aip, &bip); |
906 | 0 | } |
907 | | |
908 | | /** See if another global listener is using a particular IP / port |
909 | | * |
910 | | */ |
911 | | fr_listen_instance_t *listen_find_any(fr_listen_instance_t *li) |
912 | 0 | { |
913 | 0 | fr_listen_instance_t *found; |
914 | |
|
915 | 0 | if (!listen_addr_root) return NULL; |
916 | | |
917 | 0 | fr_rb_find((void **)&found, listen_addr_root, li); |
918 | |
|
919 | 0 | return found; |
920 | 0 | } |
921 | | |
922 | | |
923 | | /** Record that we're listening on a particular IP / port |
924 | | * |
925 | | */ |
926 | | bool listen_record(fr_listen_instance_t *li) |
927 | 0 | { |
928 | 0 | if (!listen_addr_root) return false; |
929 | | |
930 | 0 | if (!li->app_io_addr) return true; |
931 | | |
932 | 0 | if (listen_find_any(li) != NULL) return false; |
933 | | |
934 | 0 | return fr_rb_insert(listen_addr_root, li) == 0; |
935 | 0 | } |
936 | | |
937 | | /** Return the configuration section for a virtual server |
938 | | * |
939 | | * @param[in] vs to return conf section for |
940 | | * @return |
941 | | * - The CONF_SECTION of the virtual server. |
942 | | */ |
943 | | CONF_SECTION *virtual_server_cs(virtual_server_t const *vs) |
944 | 0 | { |
945 | 0 | return vs->server_cs; |
946 | 0 | } |
947 | | |
948 | | /** Resolve a CONF_SECTION to a virtual server |
949 | | * |
950 | | */ |
951 | | virtual_server_t const *virtual_server_from_cs(CONF_SECTION const *server_cs) |
952 | 0 | { |
953 | 0 | virtual_server_t *vs; |
954 | |
|
955 | 0 | vs = cf_data_value(cf_data_find(server_cs, virtual_server_t, NULL)); |
956 | 0 | if (!vs) { |
957 | 0 | cf_log_err(server_cs, "server_cs does not contain virtual server data"); |
958 | 0 | return NULL; |
959 | 0 | } |
960 | | |
961 | 0 | return vs; |
962 | 0 | } |
963 | | |
964 | | /** Return virtual server matching the specified name |
965 | | * |
966 | | * @note May be called in bootstrap or instantiate as all servers should be present. |
967 | | * |
968 | | * @param[in] name of virtual server. |
969 | | * @return |
970 | | * - NULL if no virtual server was found. |
971 | | * - The CONF_SECTION of the named virtual server. |
972 | | */ |
973 | | virtual_server_t const *virtual_server_find(char const *name) |
974 | 0 | { |
975 | 0 | CONF_SECTION *server_cs = cf_section_find(virtual_server_root, "server", name); |
976 | 0 | CONF_DATA const *cd; |
977 | |
|
978 | 0 | if (unlikely(server_cs == NULL)) return NULL; |
979 | | |
980 | 0 | cd = cf_data_find(server_cs, virtual_server_t, NULL); |
981 | 0 | if (unlikely(cd == NULL)) return NULL; |
982 | | |
983 | 0 | return cf_data_value(cd); |
984 | 0 | } |
985 | | |
986 | | /** Find a virtual server using one of its sections |
987 | | * |
988 | | * @param[in] ci to find parent virtual server for. |
989 | | * @return |
990 | | * - The virtual server section on success. |
991 | | * - NULL if the child isn't associated with any virtual server section. |
992 | | */ |
993 | | virtual_server_t const *virtual_server_by_child(CONF_ITEM const *ci) |
994 | 0 | { |
995 | 0 | CONF_SECTION *cs; |
996 | 0 | CONF_DATA const *cd; |
997 | |
|
998 | 0 | cs = cf_section_find_parent(ci, "server", CF_IDENT_ANY); |
999 | 0 | if (unlikely(!cs)) { |
1000 | 0 | cf_log_err(ci, "Child section is not associated with a virtual server"); |
1001 | 0 | return NULL; |
1002 | 0 | } |
1003 | | |
1004 | 0 | cd = cf_data_find(cs, virtual_server_t, NULL); |
1005 | 0 | if (unlikely(!cd)) { |
1006 | 0 | cf_log_err(ci, "Virtual server section missing virtual_server_t data"); |
1007 | 0 | return NULL; |
1008 | 0 | } |
1009 | | |
1010 | 0 | return cf_data_value(cd); |
1011 | 0 | } |
1012 | | |
1013 | | /** Return the packet type attribute for a virtual server specified by a config section |
1014 | | * |
1015 | | * @param[in] server_cs to look for packet type attribute in. |
1016 | | * @return |
1017 | | * - NULL on error. |
1018 | | * - packet type dict attr on success. |
1019 | | */ |
1020 | | fr_dict_attr_t const *virtual_server_packet_type_by_cs(CONF_SECTION const *server_cs) |
1021 | 0 | { |
1022 | 0 | virtual_server_t const *vs = virtual_server_from_cs(server_cs); |
1023 | |
|
1024 | 0 | if (unlikely(!vs || !vs->process_module || !vs->process_module->packet_type)) return NULL; |
1025 | 0 | return *vs->process_module->packet_type; |
1026 | 0 | } |
1027 | | |
1028 | | /** Wrapper for the config parser to allow pass1 resolution of virtual servers |
1029 | | * |
1030 | | */ |
1031 | | int virtual_server_cf_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, |
1032 | | CONF_ITEM *ci, conf_parser_t const *rule) |
1033 | 0 | { |
1034 | 0 | virtual_server_t const *vs; |
1035 | 0 | virtual_server_cf_parse_uctx_t const *uctx = rule->uctx; |
1036 | |
|
1037 | 0 | vs = virtual_server_find(cf_pair_value(cf_item_to_pair(ci))); |
1038 | 0 | if (!vs) { |
1039 | 0 | cf_log_err(ci, "virtual-server \"%s\" not found", cf_pair_value(cf_item_to_pair(ci))); |
1040 | 0 | return -1; |
1041 | 0 | } |
1042 | | |
1043 | | /* |
1044 | | * Validation checks |
1045 | | */ |
1046 | 0 | if (uctx) { |
1047 | | /* |
1048 | | * Check the module name |
1049 | | * |
1050 | | * FIXME: ...at some point in the distance future. Names |
1051 | | * are icky, we should compare based on dl_module_t, but |
1052 | | * ordering issues make this difficult currently. |
1053 | | */ |
1054 | 0 | if (uctx->process_module_name) { |
1055 | | /* catch users doing stupid things */ |
1056 | 0 | if (strcmp(uctx->process_module_name, vs->process_mi->module->name) != 0) { |
1057 | 0 | cf_log_err(ci, "virtual server \"%s\" must be of type \"%s\", " |
1058 | 0 | "got type \"%s\"", |
1059 | 0 | cf_pair_value(cf_item_to_pair(ci)), |
1060 | 0 | uctx->process_module_name, vs->process_mi->module->name); |
1061 | 0 | return -1; |
1062 | 0 | } |
1063 | 0 | } |
1064 | | |
1065 | | /* |
1066 | | * It's theoretically possible for the same module to be used |
1067 | | * with multiple namespaces, so we need this check too. |
1068 | | */ |
1069 | 0 | if (uctx->required_dict) { |
1070 | 0 | fr_dict_t *required_dict = *uctx->required_dict; |
1071 | |
|
1072 | 0 | if (!fr_cond_assert_msg(required_dict != NULL, |
1073 | 0 | "dict not resolved before virtual server reference")) { |
1074 | 0 | return -1; |
1075 | 0 | } |
1076 | | |
1077 | 0 | if (required_dict != *vs->process_module->dict) { |
1078 | 0 | cf_log_err(ci, "virtual server \"%s\" must use namespace \"%s\", " |
1079 | 0 | "got namespace \"%s\"", |
1080 | 0 | cf_pair_value(cf_item_to_pair(ci)), |
1081 | 0 | fr_dict_root(required_dict)->name, |
1082 | 0 | fr_dict_root(*vs->process_module->dict)->name); |
1083 | 0 | return -1; |
1084 | 0 | } |
1085 | 0 | } |
1086 | 0 | } |
1087 | | |
1088 | 0 | *((virtual_server_t const **)out) = vs; |
1089 | |
|
1090 | 0 | return 0; |
1091 | 0 | } |
1092 | | |
1093 | | static inline CC_HINT(always_inline) int virtual_server_compile_finally_sections(virtual_server_t *vs, tmpl_rules_t const *rules, int *found) |
1094 | 0 | { |
1095 | 0 | static unlang_mod_actions_t const mod_actions_finally = { |
1096 | 0 | .actions = { |
1097 | 0 | [RLM_MODULE_REJECT] = MOD_PRIORITY_3, |
1098 | 0 | [RLM_MODULE_FAIL] = MOD_PRIORITY_1, |
1099 | 0 | [RLM_MODULE_OK] = MOD_PRIORITY_4, |
1100 | 0 | [RLM_MODULE_HANDLED] = MOD_ACTION_RETURN, |
1101 | 0 | [RLM_MODULE_INVALID] = MOD_PRIORITY_2, |
1102 | 0 | [RLM_MODULE_DISALLOW] = MOD_PRIORITY_5, |
1103 | 0 | [RLM_MODULE_NOTFOUND] = MOD_PRIORITY_6, |
1104 | 0 | [RLM_MODULE_NOOP] = MOD_PRIORITY_8, |
1105 | 0 | [RLM_MODULE_UPDATED] = MOD_PRIORITY_7 |
1106 | 0 | }, |
1107 | 0 | .retry = RETRY_INIT, |
1108 | 0 | }; |
1109 | |
|
1110 | 0 | fr_dict_attr_t const *da; |
1111 | 0 | fr_dict_attr_t const **da_p = vs->process_module->packet_type; |
1112 | 0 | CONF_SECTION *subcs; |
1113 | |
|
1114 | 0 | if (!da_p || !*da_p) { |
1115 | 0 | subcs = cf_section_find(vs->server_cs, "finally", CF_IDENT_ANY); |
1116 | 0 | if (!subcs) return 0; |
1117 | | |
1118 | 0 | cf_log_err(subcs, "Invalid 'finally' section - virtual server does not define a 'packet_type'"); |
1119 | 0 | return -1; |
1120 | 0 | } |
1121 | | |
1122 | 0 | da = *da_p; |
1123 | | |
1124 | | /* |
1125 | | * Iterate over all the finally sections, trying to resolve |
1126 | | * the name2 to packet types. |
1127 | | */ |
1128 | 0 | for (subcs = cf_section_find(vs->server_cs, "finally", CF_IDENT_ANY); |
1129 | 0 | subcs; |
1130 | 0 | subcs = cf_section_find_next(vs->server_cs, subcs, "finally", CF_IDENT_ANY)) { |
1131 | 0 | char const *packet_type = cf_section_name2(subcs); |
1132 | 0 | fr_dict_enum_value_t const *ev; |
1133 | 0 | fr_value_box_t key; |
1134 | 0 | int ret; |
1135 | 0 | void *instruction; |
1136 | |
|
1137 | 0 | if (!packet_type) { |
1138 | 0 | if (vs->finally_default) { |
1139 | 0 | cf_log_err(subcs, "Duplicate 'finally { ... }' section"); |
1140 | 0 | return -1; |
1141 | 0 | } |
1142 | | |
1143 | 0 | ret = unlang_compile(vs, subcs, &mod_actions_finally, rules, &instruction); |
1144 | 0 | if (ret < 0) return -1; |
1145 | | |
1146 | 0 | vs->finally_default = instruction; |
1147 | 0 | (*found)++; |
1148 | |
|
1149 | 0 | continue; |
1150 | 0 | } |
1151 | | |
1152 | | /* |
1153 | | * FIXME: This would allow response packet types too |
1154 | | * We don't have anything that lists request/response |
1155 | | * types, so we can't check that here. |
1156 | | */ |
1157 | 0 | ev = fr_dict_enum_by_name(da, packet_type, talloc_strlen(packet_type)); |
1158 | 0 | if (!ev) { |
1159 | 0 | cf_log_err(subcs, "Invalid 'finally %s { ... }' section, " |
1160 | 0 | "does not match any Packet-Type value", packet_type); |
1161 | 0 | return -1; |
1162 | 0 | } |
1163 | | |
1164 | | /* |
1165 | | * This is... unlikely, as protocols usually use small |
1166 | | * packet types. |
1167 | | */ |
1168 | 0 | if (!fr_type_is_integer_except_bool(ev->value->type)) { |
1169 | 0 | forbid: |
1170 | 0 | fr_assert(da); |
1171 | 0 | cf_log_err(subcs, "'finally %s { ... }' section, " |
1172 | 0 | "not supported for this protocol. %s is a %s", packet_type, |
1173 | 0 | da->name, fr_type_to_str(ev->value->type)); |
1174 | 0 | return -1; |
1175 | 0 | } |
1176 | | |
1177 | 0 | if (fr_value_box_cast(NULL, &key, FR_TYPE_UINT16, NULL, ev->value) < 0) { |
1178 | 0 | goto forbid; |
1179 | 0 | } |
1180 | | |
1181 | 0 | if (key.vb_uint16 >= talloc_array_length(vs->finally_by_packet_type)) { |
1182 | 0 | MEM(vs->finally_by_packet_type = talloc_realloc_zero(vs, vs->finally_by_packet_type, |
1183 | 0 | void *, key.vb_uint16 + 1)); |
1184 | 0 | } |
1185 | |
|
1186 | 0 | if (vs->finally_by_packet_type[key.vb_uint16]) { |
1187 | 0 | cf_log_err(subcs, "Duplicate 'finally %s { ... }' section", packet_type); |
1188 | 0 | return -1; |
1189 | 0 | } |
1190 | | |
1191 | 0 | ret = unlang_compile(vs, subcs, &mod_actions_finally, rules, &instruction); |
1192 | 0 | if (ret < 0) return -1; |
1193 | | |
1194 | 0 | vs->finally_by_packet_type[key.vb_uint16] = instruction; |
1195 | |
|
1196 | 0 | (*found)++; |
1197 | 0 | } |
1198 | | |
1199 | 0 | return 0; |
1200 | 0 | } |
1201 | | |
1202 | | /* |
1203 | | * Check for 'send FOO' and 'recv BAR' which are unused. |
1204 | | */ |
1205 | | static bool virtual_server_warn_unused(CONF_SECTION *server, virtual_server_compile_t const *list) |
1206 | 0 | { |
1207 | 0 | bool fail = false; |
1208 | |
|
1209 | 0 | cf_section_foreach(server, subcs) { |
1210 | 0 | char const *name, *name2; |
1211 | |
|
1212 | 0 | if (cf_item_is_parsed(subcs)) continue; |
1213 | | |
1214 | 0 | name = cf_section_name1(subcs); |
1215 | | |
1216 | | /* |
1217 | | * Allow them to "comment out" an entire block by prefixing the name with "-", ala |
1218 | | * "-sql". |
1219 | | */ |
1220 | 0 | if (*name == '-') continue; |
1221 | | |
1222 | | /* |
1223 | | * Local clients are parsed by the listener after the virtual server is bootstrapped. So |
1224 | | * we just ignore them here. |
1225 | | */ |
1226 | 0 | if (strcmp(name, "client") == 0) continue; |
1227 | | |
1228 | 0 | name2 = cf_section_name2(subcs); |
1229 | | |
1230 | | /* |
1231 | | * When checking the configuration, it is an error to have an unused "send FOO" or "recv |
1232 | | * BAR" section. |
1233 | | */ |
1234 | 0 | if (check_config && ((strcmp(name, "recv") == 0) || (strcmp(name, "send") == 0))) { |
1235 | 0 | if (!name2) { |
1236 | 0 | cf_log_err(subcs, "Unused processing section' %s {'", name); |
1237 | 0 | cf_log_err(subcs, "If this is intentional, please rename it to '-%s { ...'", name); |
1238 | 0 | } else { |
1239 | 0 | cf_log_err(subcs, "Unused processing section '%s %s {'", name, name2); |
1240 | 0 | cf_log_err(subcs, "If this is intentional, please rename it to '-%s %s { ...'", |
1241 | 0 | name, name2); |
1242 | 0 | } |
1243 | |
|
1244 | 0 | fail = true; |
1245 | 0 | continue; |
1246 | 0 | } |
1247 | | |
1248 | | /* |
1249 | | * Print warnings if it's unused. If there's an issue with case sensitivity, then that's |
1250 | | * an error, even if we're not running check_config. |
1251 | | */ |
1252 | 0 | name2 = cf_section_name2(subcs); |
1253 | 0 | if (!name2) { |
1254 | 0 | cf_log_warn(subcs, "Ignoring '%s {' - it is unused", name); |
1255 | |
|
1256 | 0 | } else if (list) { |
1257 | 0 | int i; |
1258 | 0 | const char *realname = NULL; |
1259 | | |
1260 | | /* |
1261 | | * See if it's a case-sensitive issue. |
1262 | | * |
1263 | | * The dictionaries are case insensitive. The configuration files are |
1264 | | * case sensitive. |
1265 | | */ |
1266 | 0 | for (i = 0; list[i].section; i++) { |
1267 | 0 | if (list[i].section->name2 == CF_IDENT_ANY) continue; |
1268 | | |
1269 | 0 | if (strcasecmp(list[i].section->name2, name2) == 0) { |
1270 | 0 | realname = list[i].section->name2; |
1271 | 0 | break; |
1272 | 0 | } |
1273 | 0 | } |
1274 | |
|
1275 | 0 | if (realname) { |
1276 | 0 | cf_log_err(subcs, "Unused processing section '%s %s {'", name, name2); |
1277 | 0 | cf_log_err(subcs, "Do you mean '%s %s { ...' ? ", name, realname); |
1278 | 0 | fail = true; |
1279 | 0 | break; |
1280 | 0 | } |
1281 | | |
1282 | 0 | cf_log_warn(subcs, "Ignoring %s %s { - it is unused", name, name2); |
1283 | 0 | } |
1284 | 0 | } |
1285 | |
|
1286 | 0 | return fail; |
1287 | 0 | } |
1288 | | |
1289 | | /** Compile sections for a virtual server. |
1290 | | * |
1291 | | * When the "proto_foo" module calls fr_app_process_instantiate(), it |
1292 | | * loads the compile list from the #fr_app_worker_t, and calls this |
1293 | | * function. |
1294 | | * |
1295 | | * This function walks down the registration table, compiling each |
1296 | | * named section. |
1297 | | * |
1298 | | * @param[in] vs to compile sections for. |
1299 | | * @param[in] rules to apply for pass1. |
1300 | | */ |
1301 | | static int virtual_server_compile_sections(virtual_server_t *vs, tmpl_rules_t const *rules) |
1302 | 0 | { |
1303 | 0 | virtual_server_compile_t const *list = vs->process_module->compile_list; |
1304 | 0 | void *instance = vs->process_mi->data; |
1305 | 0 | CONF_SECTION *server = vs->server_cs; |
1306 | 0 | int i, found; |
1307 | 0 | bool fail; |
1308 | 0 | CONF_SECTION *subcs = NULL; |
1309 | 0 | char const *name, *name2; |
1310 | |
|
1311 | 0 | found = 0; |
1312 | | |
1313 | | /* |
1314 | | * Complain about v3 things being used in v4. |
1315 | | * |
1316 | | * Don't complain when running in normal mode, because the server will just ignore the new |
1317 | | * sections. But the check_config stuff is generally run before the service starts, and we |
1318 | | * definitely want to tell people when running in debug mode. |
1319 | | */ |
1320 | 0 | if (check_config || DEBUG_ENABLED) { |
1321 | 0 | fail = false; |
1322 | |
|
1323 | 0 | while ((subcs = cf_section_next(server, subcs)) != NULL) { |
1324 | 0 | if (cf_section_name2(subcs) != NULL) continue; |
1325 | | |
1326 | 0 | name = cf_section_name1(subcs); |
1327 | 0 | if ((strcmp(name, "authorize") == 0) || |
1328 | 0 | (strcmp(name, "authenticate") == 0) || |
1329 | 0 | (strcmp(name, "post-auth") == 0) || |
1330 | 0 | (strcmp(name, "preacct") == 0) || |
1331 | 0 | (strcmp(name, "accounting") == 0) || |
1332 | 0 | (strcmp(name, "pre-proxy") == 0) || |
1333 | 0 | (strcmp(name, "post-proxy") == 0)) { |
1334 | 0 | cf_log_err(subcs, "Version 3 processing section '%s' is not valid in version 4.", |
1335 | 0 | name); |
1336 | 0 | fail = true; |
1337 | 0 | } |
1338 | 0 | } |
1339 | | |
1340 | | /* |
1341 | | * Complain about _all_ of the sections, and not just the first one. |
1342 | | */ |
1343 | 0 | if (fail) return -1; |
1344 | 0 | } |
1345 | | |
1346 | | /* |
1347 | | * The sections are in trees, so this isn't as bad as it |
1348 | | * looks. It's not O(n^2), but O(n logn). But it could |
1349 | | * still be improved. |
1350 | | */ |
1351 | 0 | for (i = 0; list[i].section; i++) { |
1352 | 0 | int rcode; |
1353 | 0 | CONF_SECTION *bad; |
1354 | | |
1355 | | /* |
1356 | | * We are looking for a specific subsection. |
1357 | | * Warn if it isn't found, or compile it if |
1358 | | * found. |
1359 | | */ |
1360 | 0 | if (list[i].section->name2 != CF_IDENT_ANY) { |
1361 | 0 | void *instruction = NULL; |
1362 | |
|
1363 | 0 | subcs = cf_section_find(server, list[i].section->name1, list[i].section->name2); |
1364 | 0 | if (!subcs) { |
1365 | 0 | DEBUG3("Warning: Skipping %s %s { ... } as it was not found.", |
1366 | 0 | list[i].section->name1, list[i].section->name2); |
1367 | | /* |
1368 | | * Initialise CONF_SECTION pointer for missing section |
1369 | | */ |
1370 | 0 | if ((instance) && !list[i].dont_cache) { |
1371 | 0 | *(CONF_SECTION **) (((uint8_t *) instance) + list[i].offset) = NULL; |
1372 | 0 | } |
1373 | 0 | continue; |
1374 | 0 | } |
1375 | | |
1376 | | /* |
1377 | | * Duplicate sections are forbidden. |
1378 | | */ |
1379 | 0 | bad = cf_section_find_next(server, subcs, list[i].section->name1, list[i].section->name2); |
1380 | 0 | if (bad) { |
1381 | 0 | forbidden: |
1382 | 0 | cf_log_err(bad, "Duplicate sections are forbidden."); |
1383 | 0 | cf_log_err(subcs, "Previous definition occurs here."); |
1384 | 0 | return -1; |
1385 | 0 | } |
1386 | | |
1387 | 0 | rcode = unlang_compile(vs, subcs, list[i].actions, rules, &instruction); |
1388 | 0 | if (rcode < 0) return -1; |
1389 | | |
1390 | | /* |
1391 | | * Cache the CONF_SECTION which was found. |
1392 | | */ |
1393 | 0 | if (instance) { |
1394 | 0 | if (!list[i].dont_cache) { |
1395 | 0 | *(CONF_SECTION **) (((uint8_t *) instance) + list[i].offset) = subcs; |
1396 | 0 | } |
1397 | 0 | if (list[i].instruction > 0) { |
1398 | 0 | *(void **) (((uint8_t *) instance) + list[i].instruction) = instruction; |
1399 | 0 | } |
1400 | 0 | } |
1401 | |
|
1402 | 0 | found++; |
1403 | 0 | continue; |
1404 | 0 | } |
1405 | | |
1406 | | /* |
1407 | | * Reset this so that we start from the beginning |
1408 | | * again, instead of starting from the last "send |
1409 | | * foo" block. |
1410 | | */ |
1411 | 0 | subcs = NULL; |
1412 | | |
1413 | | /* |
1414 | | * Find all subsections with the given first name |
1415 | | * and compile them. |
1416 | | */ |
1417 | 0 | while ((subcs = cf_section_find_next(server, subcs, list[i].section->name1, CF_IDENT_ANY))) { |
1418 | 0 | name2 = cf_section_name2(subcs); |
1419 | 0 | if (!name2) { |
1420 | 0 | cf_log_err(subcs, "Invalid '%s { ... }' section, it must have a name", list[i].section->name1); |
1421 | 0 | return -1; |
1422 | 0 | } |
1423 | | |
1424 | | /* |
1425 | | * Duplicate sections are forbidden. |
1426 | | */ |
1427 | 0 | bad = cf_section_find_next(server, subcs, list[i].section->name1, name2); |
1428 | 0 | if (bad) goto forbidden; |
1429 | | |
1430 | 0 | rcode = unlang_compile(vs, subcs, list[i].actions, rules, NULL); |
1431 | 0 | if (rcode < 0) return -1; |
1432 | | |
1433 | | /* |
1434 | | * Note that we don't store the |
1435 | | * CONF_SECTION here, as it's a wildcard. |
1436 | | * |
1437 | | * @todo - count number of subsections |
1438 | | * and store them in an array? |
1439 | | */ |
1440 | 0 | found++; |
1441 | 0 | } |
1442 | 0 | } |
1443 | | |
1444 | | /* |
1445 | | * Compile finally sections, these are special |
1446 | | * sections and always execute before the request |
1447 | | * exits from the virtual server. |
1448 | | */ |
1449 | 0 | if (unlikely(virtual_server_compile_finally_sections(vs, rules, &found) < 0)) { |
1450 | 0 | cf_log_err(server, "Failed to compile finally sections"); |
1451 | 0 | return -1; |
1452 | 0 | } |
1453 | | |
1454 | | /* |
1455 | | * Didn't find any processing sections. Note that we don't count "finally" sections here. They |
1456 | | * are only run after the packet has been processed through the normal sections. A virtual |
1457 | | * server with only a "finally" section doesn't make sense. |
1458 | | * |
1459 | | * i.e. if people want to accept packets without responding, they need a "recv foo" section which |
1460 | | * contains a "do_not_respond" policy. |
1461 | | */ |
1462 | 0 | if (!found) { |
1463 | 0 | cf_log_err(server, "No processing sections are defined for this virtual server"); |
1464 | 0 | cf_log_err(server, "The server WILL NOT be able to process packets until the configuration is fixed"); |
1465 | 0 | return -1; |
1466 | 0 | } |
1467 | | |
1468 | 0 | if (!check_config && !DEBUG_ENABLED) return found; |
1469 | | |
1470 | 0 | fail = virtual_server_warn_unused(server, list); |
1471 | | |
1472 | | /* |
1473 | | * Be nice to people, and complaining about ALL unused processing sections. That way they don't |
1474 | | * have to run the server many, many, times to see all of the errors. |
1475 | | */ |
1476 | 0 | if (fail) return -1; |
1477 | | |
1478 | 0 | return found; |
1479 | 0 | } |
1480 | | |
1481 | | /** Register name1 / name2 as allowed processing sections |
1482 | | * |
1483 | | * This function is called from the virtual server bootstrap routine, |
1484 | | * which happens before module_bootstrap(); |
1485 | | */ |
1486 | | int virtual_server_section_register(virtual_server_t *vs, virtual_server_compile_t const *entry) |
1487 | 0 | { |
1488 | 0 | virtual_server_compile_t *old; |
1489 | |
|
1490 | 0 | fr_rb_find((void **)&old, vs->sections, entry); |
1491 | 0 | if (old) return 0; |
1492 | | |
1493 | 0 | #ifndef NDEBUG |
1494 | | /* |
1495 | | * Catch stupid programmers. |
1496 | | * |
1497 | | * Processing sections can't allow "*" for module |
1498 | | * methods, because otherwise you would be allowed to run |
1499 | | * DHCP things in a RADIUS accounting section. And that |
1500 | | * would be bad. |
1501 | | */ |
1502 | 0 | if (entry->methods) { |
1503 | 0 | int i; |
1504 | |
|
1505 | 0 | for (i = 0; entry->methods[i]; i++) { |
1506 | 0 | if (entry->methods[i]->name1 == CF_IDENT_ANY) { |
1507 | 0 | ERROR("Processing sections cannot allow \"*\""); |
1508 | 0 | return -1; |
1509 | 0 | } |
1510 | | |
1511 | 0 | if (entry->methods[i]->name2 == CF_IDENT_ANY) { |
1512 | 0 | ERROR("Processing sections cannot allow \"%s *\"", |
1513 | 0 | entry->methods[i]->name1); |
1514 | 0 | return -1; |
1515 | 0 | } |
1516 | 0 | } |
1517 | 0 | } |
1518 | 0 | #endif |
1519 | | |
1520 | 0 | if (fr_rb_insert(vs->sections, entry) != 0) { |
1521 | 0 | fr_strerror_const("Failed inserting entry into internal tree"); |
1522 | 0 | return -1; |
1523 | 0 | } |
1524 | | |
1525 | 0 | return 0; |
1526 | 0 | } |
1527 | | |
1528 | | /** Find the component for a section |
1529 | | * |
1530 | | */ |
1531 | | section_name_t const **virtual_server_section_methods(virtual_server_t const *vs, section_name_t const *section) |
1532 | 0 | { |
1533 | 0 | virtual_server_compile_t *entry; |
1534 | | |
1535 | | /* |
1536 | | * Look up the specific name first. That way we can |
1537 | | * define both "accounting on", and "accounting *". |
1538 | | */ |
1539 | 0 | if (section->name2 != CF_IDENT_ANY) { |
1540 | 0 | fr_rb_find((void **)&entry, vs->sections, |
1541 | 0 | &(virtual_server_compile_t) { |
1542 | 0 | .section = section |
1543 | 0 | }); |
1544 | 0 | if (entry) return entry->methods; |
1545 | 0 | } |
1546 | | |
1547 | | /* |
1548 | | * Then look up the wildcard, if we didn't find any matching name2. |
1549 | | */ |
1550 | 0 | fr_rb_find((void **)&entry, vs->sections, |
1551 | 0 | &(virtual_server_compile_t) { |
1552 | 0 | .section = SECTION_NAME(section->name1, CF_IDENT_ANY) |
1553 | 0 | }); |
1554 | 0 | if (!entry) return NULL; |
1555 | | |
1556 | 0 | return entry->methods; |
1557 | 0 | } |
1558 | | |
1559 | | /** Define a values for Auth-Type attributes by the sections present in a virtual-server |
1560 | | * |
1561 | | * The.name2 value of any sections found will be converted into values of the specified da. |
1562 | | * |
1563 | | * @param[in] server_cs The virtual server containing the sections. |
1564 | | * @param[in] subcs_name of the subsection to search for. |
1565 | | * @param[in] da to add enumeration values for. |
1566 | | * @return |
1567 | | * - 0 all values added successfully. |
1568 | | * - -1 an error occurred. |
1569 | | */ |
1570 | | int virtual_server_section_attribute_define(CONF_SECTION *server_cs, char const *subcs_name, fr_dict_attr_t const *da) |
1571 | 0 | { |
1572 | 0 | int rcode = 0; |
1573 | 0 | CONF_SECTION *subcs = NULL; |
1574 | |
|
1575 | 0 | fr_assert(strcmp(cf_section_name1(server_cs), "server") == 0); |
1576 | |
|
1577 | 0 | while ((subcs = cf_section_find_next(server_cs, subcs, subcs_name, CF_IDENT_ANY))) { |
1578 | 0 | char const *name2; |
1579 | 0 | fr_dict_enum_value_t const *dv; |
1580 | |
|
1581 | 0 | name2 = cf_section_name2(subcs); |
1582 | 0 | if (!name2) { |
1583 | 0 | cf_log_err(subcs, "Invalid '%s { ... }' section, it must have a name", subcs_name); |
1584 | 0 | return -1; |
1585 | 0 | } |
1586 | | |
1587 | | /* |
1588 | | * If the value already exists, don't |
1589 | | * create it again. |
1590 | | */ |
1591 | 0 | dv = fr_dict_enum_by_name(da, name2, -1); |
1592 | 0 | if (dv) continue; |
1593 | | |
1594 | 0 | cf_log_debug(subcs, "Creating %s = %s", da->name, name2); |
1595 | | |
1596 | | /* |
1597 | | * Create a new unique value with a meaningless |
1598 | | * number. You can't look at it from outside of |
1599 | | * this code, so it doesn't matter. The only |
1600 | | * requirement is that it's unique. |
1601 | | */ |
1602 | 0 | if (fr_dict_enum_add_name_next(fr_dict_attr_unconst(da), name2) < 0) { |
1603 | 0 | PERROR("Failed adding section value"); |
1604 | 0 | return -1; |
1605 | 0 | } |
1606 | | |
1607 | 0 | rcode = 1; |
1608 | 0 | } |
1609 | | |
1610 | 0 | return rcode; |
1611 | 0 | } |
1612 | | |
1613 | | static int define_server_values(CONF_SECTION *cs, fr_dict_attr_t *parent) |
1614 | 0 | { |
1615 | 0 | char const *ref; |
1616 | 0 | fr_dict_attr_t const *da; |
1617 | 0 | CONF_ITEM *ci = NULL; |
1618 | |
|
1619 | 0 | ref = cf_section_name2(cs); |
1620 | 0 | if (!ref) { |
1621 | 0 | cf_log_err(cs, "Expected 'values <name> { ... }'"); |
1622 | 0 | return -1; |
1623 | 0 | } |
1624 | | |
1625 | 0 | da = fr_dict_attr_by_name(NULL, parent, ref); |
1626 | 0 | if (!da) { |
1627 | 0 | cf_log_err(cs, "No such attribute \"%s\"", ref); |
1628 | 0 | return -1; |
1629 | 0 | } |
1630 | | |
1631 | 0 | if (fr_type_is_structural(da->type)) { |
1632 | 0 | cf_log_err(cs, "Cannot define value for structural attribute \"%s\"", ref); |
1633 | 0 | return -1; |
1634 | 0 | } |
1635 | | |
1636 | | /* |
1637 | | * This both does not make any sense, and does not get |
1638 | | * parsed correctly if the string contains backslashes. |
1639 | | */ |
1640 | 0 | if (da->type == FR_TYPE_STRING) { |
1641 | 0 | cf_log_err(cs, "Cannot define value for 'string' attribute \"%s\"", ref); |
1642 | 0 | return -1; |
1643 | 0 | } |
1644 | | |
1645 | 0 | while ((ci = cf_item_next(cs, ci))) { |
1646 | 0 | fr_slen_t slen; |
1647 | 0 | ssize_t len; |
1648 | 0 | char const *attr, *value; |
1649 | 0 | CONF_PAIR *cp; |
1650 | 0 | fr_dict_enum_value_t const *dv; |
1651 | 0 | fr_value_box_t box; |
1652 | |
|
1653 | 0 | if (cf_item_is_section(ci)) { |
1654 | 0 | cf_log_err(ci, "Unexpected subsection"); |
1655 | 0 | return -1; |
1656 | 0 | } |
1657 | | |
1658 | 0 | if (!cf_item_is_pair(ci)) continue; |
1659 | | |
1660 | 0 | cp = cf_item_to_pair(ci); |
1661 | 0 | fr_assert(cp != NULL); |
1662 | | |
1663 | | /* |
1664 | | * =* is a hack by the cf parser to say "no operator" |
1665 | | */ |
1666 | 0 | if ((cf_pair_operator(cp) != T_OP_EQ) || |
1667 | 0 | (cf_pair_attr_quote(cp) != T_BARE_WORD)) { |
1668 | 0 | cf_log_err(ci, "Definition is not in 'name = value' format"); |
1669 | 0 | return -1; |
1670 | 0 | } |
1671 | | |
1672 | 0 | attr = cf_pair_attr(cp); |
1673 | 0 | value = cf_pair_value(cp); |
1674 | |
|
1675 | 0 | dv = fr_dict_enum_by_name(da, attr, talloc_strlen(attr)); |
1676 | 0 | if (dv) { |
1677 | 0 | cf_log_err(cp, "Duplicate value name"); |
1678 | 0 | return -1; |
1679 | 0 | } |
1680 | | |
1681 | 0 | fr_value_box_init_null(&box); |
1682 | |
|
1683 | 0 | len = talloc_strlen(value); |
1684 | | |
1685 | | /* |
1686 | | * @todo - unescape for double quoted strings. Whoops. |
1687 | | */ |
1688 | 0 | slen = fr_value_box_from_str(NULL, &box, da->type, da, value, len, NULL); |
1689 | 0 | if (slen < 0) { |
1690 | 0 | cf_log_err(cp, "Failed parsing value - %s", fr_strerror()); |
1691 | 0 | return -1; |
1692 | 0 | } |
1693 | | |
1694 | 0 | if (slen != len) { |
1695 | 0 | cf_log_err(cp, "Unexpected text after value"); |
1696 | 0 | fr_value_box_clear(&box); |
1697 | 0 | return -1; |
1698 | 0 | } |
1699 | | |
1700 | 0 | if (fr_dict_enum_add_name(UNCONST(fr_dict_attr_t *, da), attr, &box, false, false) < 0) { |
1701 | 0 | cf_log_err(cp, "Failed adding value - %s", fr_strerror()); |
1702 | 0 | fr_value_box_clear(&box); |
1703 | 0 | return -1; |
1704 | 0 | } |
1705 | | |
1706 | 0 | fr_value_box_clear(&box); |
1707 | 0 | } |
1708 | | |
1709 | 0 | return 0; |
1710 | 0 | } |
1711 | | |
1712 | | |
1713 | | static int define_server_attrs(CONF_SECTION *cs, fr_dict_t *dict, fr_dict_attr_t *parent, fr_dict_attr_t const *root) |
1714 | 0 | { |
1715 | 0 | CONF_ITEM *ci = NULL; |
1716 | |
|
1717 | 0 | fr_dict_attr_flags_t flags = { |
1718 | 0 | .internal = true, |
1719 | 0 | .name_only = true, |
1720 | 0 | .local = true, |
1721 | 0 | }; |
1722 | |
|
1723 | 0 | fr_assert(dict != NULL); |
1724 | 0 | fr_assert(parent != NULL); |
1725 | |
|
1726 | 0 | while ((ci = cf_item_next(cs, ci))) { |
1727 | 0 | fr_type_t type; |
1728 | 0 | char const *attr, *value; |
1729 | 0 | CONF_PAIR *cp; |
1730 | 0 | CONF_SECTION *subcs = NULL; |
1731 | |
|
1732 | 0 | if (cf_item_is_section(ci)) { |
1733 | 0 | subcs = cf_item_to_section(ci); |
1734 | 0 | fr_assert(subcs != NULL); |
1735 | |
|
1736 | 0 | attr = cf_section_name1(subcs); |
1737 | |
|
1738 | 0 | if (strcmp(attr, "values") == 0) { |
1739 | 0 | if (define_server_values(subcs, parent) < 0) return -1; |
1740 | 0 | continue; |
1741 | 0 | } |
1742 | | |
1743 | 0 | if (strcmp(attr, "tlv") != 0) goto invalid_type; |
1744 | | |
1745 | 0 | value = cf_section_name2(subcs); |
1746 | 0 | if (!value) { |
1747 | 0 | cf_log_err(ci, "Definition is not in 'tlv name { ... }' format"); |
1748 | 0 | return -1; |
1749 | 0 | } |
1750 | | |
1751 | 0 | type = FR_TYPE_TLV; |
1752 | 0 | goto check_for_dup; |
1753 | 0 | } |
1754 | | |
1755 | 0 | if (!cf_item_is_pair(ci)) continue; |
1756 | | |
1757 | 0 | cp = cf_item_to_pair(ci); |
1758 | 0 | fr_assert(cp != NULL); |
1759 | | |
1760 | | /* |
1761 | | * =* is a hack by the cf parser to say "no operator" |
1762 | | */ |
1763 | 0 | if ((cf_pair_operator(cp) != T_OP_CMP_TRUE) || |
1764 | 0 | (cf_pair_attr_quote(cp) != T_BARE_WORD) || |
1765 | 0 | (cf_pair_value_quote(cp) != T_BARE_WORD)) { |
1766 | 0 | cf_log_err(ci, "Definition is not in 'type name' format"); |
1767 | 0 | return -1; |
1768 | 0 | } |
1769 | | |
1770 | 0 | attr = cf_pair_attr(cp); |
1771 | 0 | value = cf_pair_value(cp); |
1772 | |
|
1773 | 0 | type = fr_table_value_by_str(fr_type_table, attr, FR_TYPE_NULL); |
1774 | 0 | if (type == FR_TYPE_NULL) { |
1775 | 0 | invalid_type: |
1776 | 0 | cf_log_err(ci, "Invalid data type '%s'", attr); |
1777 | 0 | return -1; |
1778 | 0 | } |
1779 | | |
1780 | | /* |
1781 | | * Leaf and group are OK. TLV, Vendor, Struct, VSA, etc. are not as variable definitions. |
1782 | | */ |
1783 | 0 | if (!(fr_type_is_leaf(type) || (type == FR_TYPE_GROUP))) goto invalid_type; |
1784 | | |
1785 | | /* |
1786 | | * No duplicates are allowed. |
1787 | | */ |
1788 | 0 | check_for_dup: |
1789 | 0 | if (root && (fr_dict_attr_by_name(NULL, root, value) != NULL)) { |
1790 | 0 | cf_log_err(ci, "Local variable '%s' duplicates a dictionary attribute.", value); |
1791 | 0 | return -1; |
1792 | 0 | } |
1793 | | |
1794 | 0 | if (fr_dict_attr_by_name(NULL, parent, value) != NULL) { |
1795 | 0 | cf_log_err(ci, "Local variable '%s' duplicates a previous local attribute.", value); |
1796 | 0 | return -1; |
1797 | 0 | } |
1798 | | |
1799 | 0 | if (fr_dict_attr_add_name_only(dict, parent, value, type, &flags) < 0) { |
1800 | 0 | cf_log_err(ci, "Failed adding local variable '%s' - %s", value, fr_strerror()); |
1801 | 0 | return -1; |
1802 | 0 | } |
1803 | | |
1804 | 0 | if (type == FR_TYPE_TLV) { |
1805 | 0 | fr_dict_attr_t const *da; |
1806 | |
|
1807 | 0 | if (!subcs) return -1; /* shouldn't happen, but shut up compiler */ |
1808 | | |
1809 | 0 | da = fr_dict_attr_by_name(NULL, parent, value); |
1810 | 0 | fr_assert(da != NULL); |
1811 | |
|
1812 | 0 | if (define_server_attrs(subcs, dict, UNCONST(fr_dict_attr_t *, da), NULL) < 0) return -1; |
1813 | 0 | } |
1814 | 0 | } |
1815 | | |
1816 | 0 | return 0; |
1817 | 0 | } |
1818 | | |
1819 | | static fr_dict_t const *virtual_server_local_dict(CONF_SECTION *server_cs, fr_dict_t const *dict_def) |
1820 | 0 | { |
1821 | 0 | fr_dict_t *dict; |
1822 | 0 | CONF_SECTION *cs; |
1823 | |
|
1824 | 0 | cs = cf_section_find(server_cs, "dictionary", NULL); |
1825 | 0 | if (!cs) return dict_def; |
1826 | | |
1827 | 0 | dict = fr_dict_protocol_alloc(dict_def); |
1828 | 0 | if (!dict) { |
1829 | 0 | cf_log_err(cs, "Failed allocating local dictionary"); |
1830 | 0 | return NULL; |
1831 | 0 | } |
1832 | | |
1833 | 0 | if (define_server_attrs(cs, dict, UNCONST(fr_dict_attr_t *, fr_dict_root(dict)), fr_dict_root(dict_def)) < 0){ |
1834 | 0 | talloc_free(dict); |
1835 | 0 | return NULL; |
1836 | 0 | } |
1837 | | |
1838 | | /* |
1839 | | * Replace the original dictionary with the new one. |
1840 | | */ |
1841 | 0 | cf_data_remove(server_cs, fr_dict_t, "dict"); |
1842 | 0 | cf_data_add(server_cs, dict, "dict", false); |
1843 | 0 | cf_item_mark_parsed(cs); |
1844 | |
|
1845 | 0 | return dict; |
1846 | 0 | } |
1847 | | |
1848 | | |
1849 | | /** Open all the listen sockets |
1850 | | * |
1851 | | * @param[in] sc Scheduler to add I/O paths to. |
1852 | | * @return |
1853 | | * - 0 on success. |
1854 | | * - -1 on failure. |
1855 | | */ |
1856 | | int virtual_servers_open(fr_schedule_t *sc) |
1857 | 0 | { |
1858 | 0 | size_t i, server_cnt = virtual_servers ? talloc_array_length(virtual_servers) : 0; |
1859 | 0 | int opened = 0; |
1860 | |
|
1861 | 0 | fr_assert(virtual_servers); |
1862 | |
|
1863 | 0 | DEBUG2("#### Opening listener interfaces ####"); |
1864 | 0 | fr_strerror_clear(); |
1865 | |
|
1866 | 0 | for (i = 0; i < server_cnt; i++) { |
1867 | 0 | virtual_server_listen_t **listeners; |
1868 | 0 | size_t j, listener_cnt; |
1869 | |
|
1870 | 0 | listeners = virtual_servers[i]->listeners; |
1871 | 0 | if (!listeners) continue; /* servers can have no listeners */ |
1872 | | |
1873 | 0 | listener_cnt = talloc_array_length(listeners); |
1874 | |
|
1875 | 0 | for (j = 0; j < listener_cnt; j++) { |
1876 | 0 | int ret; |
1877 | 0 | virtual_server_listen_t *listener = listeners[j]; |
1878 | |
|
1879 | 0 | fr_assert(listener != NULL); |
1880 | 0 | fr_assert(listener->proto_mi != NULL); |
1881 | 0 | fr_assert(listener->proto_module != NULL); |
1882 | |
|
1883 | 0 | fr_assert(listener->proto_module->open != NULL); |
1884 | | |
1885 | | /* |
1886 | | * The socket is opened with app_instance, |
1887 | | * but all subsequent calls (network.c, etc.) use app_io_instance. |
1888 | | * |
1889 | | * The reason is that we call (for example) proto_radius to |
1890 | | * open the socket, and proto_radius is responsible for setting up |
1891 | | * proto_radius_udp, and then calling proto_radius_udp->open. |
1892 | | * |
1893 | | * Even then, proto_radius usually calls fr_master_io_listen() in order |
1894 | | * to create the fr_listen_instance_t structure. |
1895 | | */ |
1896 | | |
1897 | | /* |
1898 | | * Sometimes the open function needs to modify instance |
1899 | | * data, so we need to temporarily remove the protection. |
1900 | | */ |
1901 | 0 | module_instance_data_unprotect(listener->proto_mi); |
1902 | 0 | ret = listener->proto_module->open(listener->proto_mi->data, sc, |
1903 | 0 | listener->proto_mi->conf); |
1904 | 0 | module_instance_data_protect(listener->proto_mi); |
1905 | 0 | if (unlikely(ret < 0)) { |
1906 | 0 | return -1; |
1907 | 0 | } |
1908 | | |
1909 | 0 | opened++; |
1910 | | |
1911 | | /* |
1912 | | * Socket information is printed out by |
1913 | | * the socket handlers. e.g. proto_radius_udp |
1914 | | */ |
1915 | 0 | DEBUG3("Opened listener for %s", listener->proto_module->common.name); |
1916 | 0 | } |
1917 | 0 | } |
1918 | | |
1919 | 0 | if (!opened) { |
1920 | 0 | ERROR("There are no 'listen' sections defined."); |
1921 | 0 | ERROR("Refusing to start, as the server will never process any packets."); |
1922 | 0 | return -1; |
1923 | 0 | } |
1924 | | |
1925 | 0 | return 0; |
1926 | 0 | } |
1927 | | |
1928 | | /** Free thread-specific data for all process modules and listeners |
1929 | | * |
1930 | | */ |
1931 | | void virtual_servers_thread_detach(void) |
1932 | 0 | { |
1933 | 0 | modules_thread_detach(proto_modules); |
1934 | 0 | modules_thread_detach(process_modules); |
1935 | 0 | } |
1936 | | |
1937 | | /** Perform thread instantiation for all process modules and listeners |
1938 | | * |
1939 | | */ |
1940 | | int virtual_servers_thread_instantiate(TALLOC_CTX *ctx, fr_event_list_t *el) |
1941 | 0 | { |
1942 | 0 | if (modules_thread_instantiate(ctx, process_modules, el) < 0) return -1; |
1943 | 0 | if (modules_thread_instantiate(ctx, proto_modules, el) < 0) { |
1944 | 0 | modules_thread_detach(process_modules); |
1945 | 0 | return -1; |
1946 | 0 | } |
1947 | 0 | return 0; |
1948 | 0 | } |
1949 | | |
1950 | | /** Instantiate all the virtual servers |
1951 | | * |
1952 | | * @return |
1953 | | * - 0 on success. |
1954 | | * - -1 on failure. |
1955 | | */ |
1956 | | int virtual_servers_instantiate(void) |
1957 | 0 | { |
1958 | 0 | size_t i, server_cnt; |
1959 | 0 | CONF_SECTION *cs; |
1960 | 0 | fr_rb_iter_inorder_t iter; |
1961 | | |
1962 | | /* |
1963 | | * User didn't specify any "server" sections |
1964 | | */ |
1965 | 0 | if (unlikely(!virtual_servers)) { |
1966 | 0 | ERROR("No virtual servers configured"); |
1967 | 0 | return -1; |
1968 | 0 | } |
1969 | | |
1970 | 0 | server_cnt = talloc_array_length(virtual_servers); |
1971 | |
|
1972 | 0 | DEBUG2("#### Instantiating listeners ####"); |
1973 | |
|
1974 | 0 | if (fr_command_register_hook(NULL, NULL, virtual_server_root, cmd_table) < 0) { |
1975 | 0 | PERROR("Failed registering radmin commands for virtual servers"); |
1976 | 0 | return -1; |
1977 | 0 | } |
1978 | | |
1979 | 0 | for (i = 0; i < server_cnt; i++) { |
1980 | 0 | CONF_SECTION *server_cs = virtual_servers[i]->server_cs; |
1981 | 0 | fr_dict_t const *dict; |
1982 | 0 | virtual_server_t *vs = virtual_servers[i]; |
1983 | 0 | fr_process_module_t const *process = (fr_process_module_t const *) |
1984 | 0 | vs->process_mi->module->exported; |
1985 | | |
1986 | | /* |
1987 | | * Set up logging before doing anything else. |
1988 | | */ |
1989 | 0 | if (vs->log_name) { |
1990 | 0 | vs->log = log_dst_by_name(vs->log_name); |
1991 | 0 | if (!vs->log) { |
1992 | 0 | CONF_PAIR *cp = cf_pair_find(server_cs, "log"); |
1993 | |
|
1994 | 0 | if (cp) { |
1995 | 0 | cf_log_err(cp, "Unknown log destination '%s'", vs->log_name); |
1996 | 0 | } else { |
1997 | 0 | cf_log_err(server_cs, "Unknown log destination '%s'", vs->log_name); |
1998 | 0 | } |
1999 | |
|
2000 | 0 | return -1; |
2001 | 0 | } |
2002 | 0 | } |
2003 | | |
2004 | 0 | dict = virtual_server_local_dict(server_cs, *(process)->dict); |
2005 | 0 | if (!dict) return -1; |
2006 | | |
2007 | 0 | DEBUG("Compiling policies in server %s { ... }", cf_section_name2(server_cs)); |
2008 | |
|
2009 | 0 | fr_assert(virtual_servers[i]->process_mi); |
2010 | | |
2011 | | /* |
2012 | | * Compile the processing sections indicated by |
2013 | | * the process module. This must be done before |
2014 | | * module_instantiate is called, as the instance |
2015 | | * data is protected after this call. |
2016 | | */ |
2017 | 0 | if (process->compile_list) { |
2018 | 0 | tmpl_rules_t parse_rules = { |
2019 | 0 | .attr = { |
2020 | 0 | .dict_def = dict, |
2021 | 0 | .list_def = request_attr_request, |
2022 | 0 | }, |
2023 | |
|
2024 | 0 | .literals_safe_for = FR_VALUE_BOX_SAFE_FOR_ANY, |
2025 | 0 | }; |
2026 | |
|
2027 | 0 | fr_assert(parse_rules.attr.dict_def != NULL); |
2028 | |
|
2029 | 0 | if (virtual_server_compile_sections(virtual_servers[i], &parse_rules) < 0) { |
2030 | 0 | return -1; |
2031 | 0 | } |
2032 | 0 | } |
2033 | | |
2034 | 0 | (void) virtual_server_warn_unused(server_cs, process->compile_list); |
2035 | 0 | } |
2036 | | |
2037 | | /* |
2038 | | * Iterate over virtual modules to see if we can compile them. |
2039 | | */ |
2040 | 0 | for (cs = module_rlm_virtual_iter_init(&iter); |
2041 | 0 | cs != NULL; |
2042 | 0 | cs = module_rlm_virtual_iter_next(&iter)) { |
2043 | 0 | fr_dict_t const *dict; |
2044 | |
|
2045 | 0 | dict = virtual_server_dict_by_cs(cs); /* cheating! */ |
2046 | 0 | if (!dict) continue; |
2047 | | |
2048 | 0 | if (unlang_compile_virtual_module(cs, dict) < 0) return -1; |
2049 | 0 | } |
2050 | | |
2051 | 0 | if (modules_instantiate(process_modules) < 0) { |
2052 | 0 | PERROR("Failed instantiating process modules"); |
2053 | 0 | return -1; |
2054 | 0 | } |
2055 | 0 | if (modules_instantiate(proto_modules) < 0) { |
2056 | 0 | PERROR("Failed instantiating protocol modules"); |
2057 | 0 | return -1; |
2058 | 0 | } |
2059 | | |
2060 | 0 | return 0; |
2061 | 0 | } |
2062 | | |
2063 | | /** Load protocol modules and call their bootstrap methods |
2064 | | * |
2065 | | * @param[in] config section containing the virtual servers to bootstrap. |
2066 | | * @return |
2067 | | * - 0 on success. |
2068 | | * - -1 on failure. |
2069 | | */ |
2070 | | int virtual_servers_bootstrap(CONF_SECTION *config) |
2071 | 0 | { |
2072 | 0 | virtual_server_root = config; |
2073 | | |
2074 | | /* |
2075 | | * Ensure any libraries the modules depend on are instantiated |
2076 | | */ |
2077 | 0 | if (global_lib_instantiate() < 0) return -1; |
2078 | | |
2079 | 0 | if (modules_bootstrap(process_modules) < 0) { |
2080 | 0 | PERROR("Failed bootstrapping process modules"); |
2081 | 0 | return -1; |
2082 | 0 | } |
2083 | 0 | if (modules_bootstrap(proto_modules) < 0) { |
2084 | 0 | PERROR("Failed bootstrapping protocol modules"); |
2085 | 0 | return -1; |
2086 | 0 | } |
2087 | | |
2088 | 0 | return 0; |
2089 | 0 | } |
2090 | | |
2091 | | int virtual_servers_free(void) |
2092 | 0 | { |
2093 | 0 | if (talloc_free(listen_addr_root) < 0) return -1; |
2094 | 0 | listen_addr_root = NULL; |
2095 | 0 | if (talloc_free(process_modules) < 0) return -1; |
2096 | 0 | process_modules = NULL; |
2097 | 0 | if (talloc_free(proto_modules) < 0) return -1; |
2098 | 0 | proto_modules = NULL; |
2099 | 0 | if (fr_dict_autofree(virtual_server_dict_autoload) < 0) return -1; |
2100 | | |
2101 | 0 | return 0; |
2102 | 0 | } |
2103 | | |
2104 | | static int _virtual_servers_atexit(UNUSED void *uctx) |
2105 | 0 | { |
2106 | 0 | return virtual_servers_free(); |
2107 | 0 | } |
2108 | | |
2109 | | /** Performs global initialisation for the virtual server code |
2110 | | * |
2111 | | * This has to be done separately and explicitly, because the above code makes |
2112 | | * use of "onread" callbacks. |
2113 | | * |
2114 | | * Will automatically free module lists on exit, but all modules should have |
2115 | | * been removed from this list by the point that happens. |
2116 | | */ |
2117 | | int virtual_servers_init(void) |
2118 | | { |
2119 | | if (fr_dict_autoload(virtual_server_dict_autoload) < 0) { |
2120 | | PERROR("%s", __FUNCTION__); |
2121 | | return -1; |
2122 | | } |
2123 | | if (fr_dict_attr_autoload(virtual_server_dict_attr_autoload) < 0) { |
2124 | | PERROR("%s", __FUNCTION__); |
2125 | | fr_dict_autofree(virtual_server_dict_autoload); |
2126 | | return -1; |
2127 | | } |
2128 | | |
2129 | | MEM(process_modules = module_list_alloc(NULL, &module_list_type_global, "process", true)); |
2130 | | |
2131 | | /* |
2132 | | * FIXME - We should be able to turn on write protection, |
2133 | | * but there are too many proto modules that hang things |
2134 | | * off of their instance data. |
2135 | | */ |
2136 | | MEM(proto_modules = module_list_alloc(NULL, &module_list_type_global, "protocol", false)); |
2137 | | MEM(listen_addr_root = fr_rb_inline_alloc(NULL, fr_listen_instance_t, virtual_server_node, listen_addr_cmp, |
2138 | | NULL)); |
2139 | | |
2140 | | /* |
2141 | | * Create a list to hold all the proto_* modules |
2142 | | * that get loaded during startup. |
2143 | | */ |
2144 | | fr_atexit_global(_virtual_servers_atexit, NULL); |
2145 | | |
2146 | | return 0; |
2147 | | } |