/src/freeradius-server/src/lib/unlang/call.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: 74c2d43ab850058288a10774fc241c6e6cffa5ba $ |
19 | | * |
20 | | * @file unlang/call.c |
21 | | * @brief Unlang "call" keyword evaluation. Used for calling virtual servers. |
22 | | * |
23 | | * @copyright 2006-2019 The FreeRADIUS server project |
24 | | */ |
25 | | RCSID("$Id: 74c2d43ab850058288a10774fc241c6e6cffa5ba $") |
26 | | |
27 | | #include <freeradius-devel/server/rcode.h> |
28 | | #include <freeradius-devel/server/state.h> |
29 | | #include <freeradius-devel/server/pair.h> |
30 | | |
31 | | #include "call_priv.h" |
32 | | |
33 | | static unlang_action_t unlang_call_resume(UNUSED unlang_result_t *p_result, request_t *request, |
34 | | unlang_stack_frame_t *frame) |
35 | 0 | { |
36 | 0 | unlang_group_t *g = unlang_generic_to_group(frame->instruction); |
37 | 0 | unlang_call_t *gext = unlang_group_to_call(g); |
38 | 0 | fr_pair_t *packet_type_vp = NULL; |
39 | 0 | unlang_frame_state_call_t *state = frame->state; |
40 | |
|
41 | 0 | switch (pair_update_reply(&packet_type_vp, gext->attr_packet_type)) { |
42 | 0 | case 0: |
43 | 0 | packet_type_vp->vp_uint32 = request->reply->code; |
44 | 0 | break; |
45 | | |
46 | 0 | case 1: |
47 | 0 | break; /* Don't change */ |
48 | | |
49 | 0 | default: |
50 | 0 | request->module = state->module; |
51 | 0 | RETURN_UNLANG_FAIL; |
52 | 0 | } |
53 | | |
54 | 0 | LOG_PACKET_DEBUG(request, request->reply, &request->reply_pairs, gext->attr_packet_type, false, (request->reply->id >= 0)); |
55 | |
|
56 | 0 | request->module = state->module; |
57 | |
|
58 | 0 | return UNLANG_ACTION_CALCULATE_RESULT; |
59 | 0 | } |
60 | | |
61 | | static unlang_action_t unlang_call_children(UNUSED unlang_result_t *p_result, request_t *request, |
62 | | unlang_stack_frame_t *frame) |
63 | 0 | { |
64 | 0 | frame_repeat(frame, unlang_call_resume); |
65 | | |
66 | | /* |
67 | | * Push the contents of the call { } section onto the stack. |
68 | | * This gets executed after the server returns. |
69 | | */ |
70 | 0 | return unlang_interpret_push_children(NULL, request, RLM_MODULE_NOT_SET, UNLANG_NEXT_SIBLING); |
71 | 0 | } |
72 | | |
73 | | |
74 | | static unlang_action_t unlang_call_frame_init(unlang_result_t *p_result, request_t *request, |
75 | | unlang_stack_frame_t *frame) |
76 | 0 | { |
77 | 0 | unlang_group_t *g; |
78 | 0 | unlang_call_t *gext; |
79 | 0 | fr_dict_enum_value_t const *type_enum; |
80 | 0 | fr_pair_t *packet_type_vp = NULL; |
81 | 0 | unlang_stack_t *stack = request->stack; |
82 | | |
83 | | /* |
84 | | * Do not check for children here. |
85 | | * |
86 | | * Call shouldn't require children to execute as there |
87 | | * can still be side effects from executing the virtual |
88 | | * server. |
89 | | */ |
90 | 0 | g = unlang_generic_to_group(frame->instruction); |
91 | 0 | gext = unlang_group_to_call(g); |
92 | | |
93 | | /* |
94 | | * Work out the current request type. |
95 | | */ |
96 | 0 | type_enum = fr_dict_enum_by_value(gext->attr_packet_type, fr_box_uint32(request->packet->code)); |
97 | 0 | if (!type_enum) { |
98 | 0 | packet_type_vp = fr_pair_find_by_da(&request->request_pairs, NULL, gext->attr_packet_type); |
99 | 0 | if (!packet_type_vp) { |
100 | 0 | bad_packet_type: |
101 | 0 | REDEBUG("No such value '%u' of attribute 'Packet-Type' for server %s", |
102 | 0 | request->packet->code, cf_section_name2(gext->server_cs)); |
103 | 0 | error: |
104 | 0 | RETURN_UNLANG_FAIL; |
105 | 0 | } |
106 | 0 | type_enum = fr_dict_enum_by_value(packet_type_vp->da, &packet_type_vp->data); |
107 | 0 | if (!type_enum) goto bad_packet_type; |
108 | | |
109 | | /* |
110 | | * Sync up packet->code |
111 | | */ |
112 | 0 | request->packet->code = packet_type_vp->vp_uint32; |
113 | 0 | } |
114 | | |
115 | | /* |
116 | | * Sync up packet codes and attributes |
117 | | * |
118 | | * Fixme - packet->code needs to die... |
119 | | */ |
120 | 0 | if (!packet_type_vp) switch (pair_update_request(&packet_type_vp, gext->attr_packet_type)) { |
121 | 0 | case 0: |
122 | 0 | packet_type_vp->vp_uint32 = request->packet->code; |
123 | 0 | break; |
124 | | |
125 | 0 | case 1: |
126 | 0 | request->packet->code = packet_type_vp->vp_uint32; |
127 | 0 | break; |
128 | | |
129 | 0 | default: |
130 | 0 | goto error; |
131 | 0 | } |
132 | | |
133 | 0 | if (unlang_list_empty(&g->children)) { |
134 | 0 | frame_repeat(frame, unlang_call_resume); |
135 | 0 | } else { |
136 | 0 | frame_repeat(frame, unlang_call_children); |
137 | 0 | } |
138 | 0 | frame->prev.frame_call = stack->depth; |
139 | |
|
140 | 0 | if (virtual_server_push(NULL, request, virtual_server_from_cs(gext->server_cs), UNLANG_SUB_FRAME) < 0) goto error; |
141 | | |
142 | 0 | LOG_PACKET_DEBUG(request, request->packet, &request->request_pairs, gext->attr_packet_type, true, (request->packet->id >= 0)); |
143 | |
|
144 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
145 | 0 | } |
146 | | |
147 | | /** Push a virtual server #CONF_SECTION as a call frame onto the stack |
148 | | * |
149 | | * This should be used instead of virtual_server_push in the majority of the code |
150 | | */ |
151 | | unlang_action_t unlang_call_push(unlang_result_t *p_result, request_t *request, CONF_SECTION *server_cs, bool top_frame) |
152 | 0 | { |
153 | 0 | unlang_stack_t *stack = request->stack; |
154 | 0 | unlang_call_t *c; |
155 | 0 | char const *name; |
156 | 0 | fr_dict_t const *dict; |
157 | 0 | fr_dict_attr_t const *attr_packet_type; |
158 | 0 | unlang_stack_frame_t *frame; |
159 | 0 | unlang_frame_state_call_t *state; |
160 | | |
161 | | /* |
162 | | * Temporary hack until packet->code is removed |
163 | | */ |
164 | 0 | dict = virtual_server_dict_by_cs(server_cs); |
165 | 0 | if (!dict) { |
166 | 0 | REDEBUG("Virtual server \"%s\" not compiled", cf_section_name2(server_cs)); |
167 | 0 | return UNLANG_ACTION_FAIL; |
168 | 0 | } |
169 | | |
170 | 0 | attr_packet_type = virtual_server_packet_type_by_cs(server_cs); |
171 | 0 | if (!attr_packet_type) { |
172 | 0 | REDEBUG("No Packet-Type attribute available"); |
173 | 0 | return UNLANG_ACTION_FAIL; |
174 | 0 | } |
175 | | |
176 | | /* |
177 | | * We need to have a unlang_module_t to push on the |
178 | | * stack. The only sane way to do it is to attach it to |
179 | | * the frame state. |
180 | | */ |
181 | 0 | name = talloc_asprintf(request, "server %s", cf_section_name2(server_cs)); |
182 | 0 | MEM(c = talloc(stack, unlang_call_t)); /* Free at the same time as the state */ |
183 | 0 | *c = (unlang_call_t){ |
184 | 0 | .group = { |
185 | 0 | .self = { |
186 | 0 | .type = UNLANG_TYPE_CALL, |
187 | 0 | .name = name, |
188 | 0 | .debug_name = name, |
189 | 0 | .ci = CF_TO_ITEM(server_cs), |
190 | 0 | .actions = MOD_ACTIONS_FAIL_TIMEOUT_RETURN, |
191 | 0 | .add_filename = true, |
192 | 0 | }, |
193 | |
|
194 | 0 | .cs = server_cs, |
195 | 0 | }, |
196 | 0 | .server_cs = server_cs, |
197 | 0 | .attr_packet_type = attr_packet_type |
198 | 0 | }; |
199 | |
|
200 | 0 | unlang_group_type_init(&c->group.self, NULL, UNLANG_TYPE_CALL); |
201 | | |
202 | | /* |
203 | | * Push a new call frame onto the stack |
204 | | */ |
205 | 0 | if (unlang_interpret_push(p_result, request, unlang_call_to_generic(c), |
206 | 0 | FRAME_CONF(RLM_MODULE_NOT_SET, top_frame), UNLANG_NEXT_STOP) < 0) { |
207 | 0 | talloc_free(c); |
208 | 0 | return UNLANG_ACTION_FAIL; |
209 | 0 | } |
210 | | |
211 | 0 | frame = &stack->frame[stack->depth]; |
212 | 0 | state = frame->state; |
213 | 0 | state->module = request->module; |
214 | 0 | request->module = NULL; |
215 | |
|
216 | 0 | return UNLANG_ACTION_PUSHED_CHILD; |
217 | 0 | } |
218 | | |
219 | | /** Return the last virtual server that was called |
220 | | * |
221 | | * @param[in] request To return virtual server for. |
222 | | * @return |
223 | | * - A virtual server CONF_SECTION on success. |
224 | | * - NULL on failure. |
225 | | */ |
226 | | CONF_SECTION *unlang_call_current(request_t *request) |
227 | 0 | { |
228 | 0 | unlang_stack_t *stack = request->stack; |
229 | 0 | unlang_stack_frame_t *frame; |
230 | |
|
231 | 0 | #ifndef NDEBUG |
232 | 0 | unsigned int depth; |
233 | | |
234 | | /* |
235 | | * Work back from the deepest frame |
236 | | * looking for modules. |
237 | | */ |
238 | 0 | for (depth = stack_depth_current(request); depth > 0; depth--) { |
239 | 0 | frame = &stack->frame[depth]; |
240 | | |
241 | | /* |
242 | | * Look at the module frames, |
243 | | * trying to find one that represents |
244 | | * a process state machine. |
245 | | */ |
246 | 0 | if (frame->instruction->type != UNLANG_TYPE_CALL) continue; |
247 | | |
248 | 0 | fr_assert(stack->frame[stack->depth].prev.frame_call == depth); |
249 | |
|
250 | 0 | return unlang_group_to_call(unlang_generic_to_group(frame->instruction))->server_cs; |
251 | 0 | } |
252 | | |
253 | 0 | return NULL; |
254 | | #else |
255 | | frame = &stack->frame[stack->depth]; |
256 | | if (!frame->prev.frame_call) return NULL; |
257 | | |
258 | | frame = &stack->frame[frame->prev.frame_call]; |
259 | | return unlang_group_to_call(unlang_generic_to_group(frame->instruction))->server_cs; |
260 | | #endif |
261 | 0 | } |
262 | | |
263 | | static unlang_t *unlang_compile_call(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci) |
264 | 0 | { |
265 | 0 | CONF_SECTION *cs = cf_item_to_section(ci); |
266 | 0 | virtual_server_t const *vs; |
267 | 0 | unlang_t *c; |
268 | |
|
269 | 0 | unlang_group_t *g; |
270 | 0 | unlang_call_t *gext; |
271 | |
|
272 | 0 | fr_token_t type; |
273 | 0 | char const *server; |
274 | 0 | CONF_SECTION *server_cs; |
275 | 0 | fr_dict_t const *dict; |
276 | 0 | fr_dict_attr_t const *attr_packet_type; |
277 | |
|
278 | 0 | server = cf_section_name2(cs); |
279 | 0 | if (!server) { |
280 | 0 | cf_log_err(cs, "You MUST specify a server name for 'call <server> { ... }'"); |
281 | 0 | print_url: |
282 | 0 | cf_log_err(ci, DOC_KEYWORD_REF(call)); |
283 | 0 | return NULL; |
284 | 0 | } |
285 | | |
286 | 0 | type = cf_section_name2_quote(cs); |
287 | 0 | if (type != T_BARE_WORD) { |
288 | 0 | cf_log_err(cs, "The arguments to 'call' cannot be a quoted string or a dynamic value"); |
289 | 0 | goto print_url; |
290 | 0 | } |
291 | | |
292 | 0 | vs = virtual_server_find(server); |
293 | 0 | if (!vs) { |
294 | 0 | cf_log_err(cs, "Unknown virtual server '%s'", server); |
295 | 0 | return NULL; |
296 | 0 | } |
297 | | |
298 | 0 | server_cs = virtual_server_cs(vs); |
299 | | |
300 | | /* |
301 | | * The dictionaries are not compatible, forbid it. |
302 | | */ |
303 | 0 | dict = virtual_server_dict_by_name(server); |
304 | 0 | if (!dict) { |
305 | 0 | cf_log_err(cs, "Cannot call virtual server '%s', failed retrieving its namespace", |
306 | 0 | server); |
307 | 0 | return NULL; |
308 | 0 | } |
309 | 0 | if ((dict != fr_dict_internal()) && fr_dict_internal() && |
310 | 0 | unlang_ctx->rules->attr.dict_def && !fr_dict_compatible(unlang_ctx->rules->attr.dict_def, dict)) { |
311 | 0 | cf_log_err(cs, "Cannot call server %s with namespace '%s' from namespaces '%s' - they have incompatible protocols", |
312 | 0 | server, fr_dict_root(dict)->name, fr_dict_root(unlang_ctx->rules->attr.dict_def)->name); |
313 | 0 | return NULL; |
314 | 0 | } |
315 | | |
316 | 0 | attr_packet_type = virtual_server_packet_type_by_cs(server_cs); |
317 | 0 | if (!attr_packet_type) { |
318 | 0 | cf_log_err(cs, "Cannot call server %s with namespace '%s' - it has no Packet-Type attribute", |
319 | 0 | server, fr_dict_root(dict)->name); |
320 | 0 | return NULL; |
321 | 0 | } |
322 | | |
323 | 0 | c = unlang_compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_CALL); |
324 | 0 | if (!c) return NULL; |
325 | | |
326 | | /* |
327 | | * Set the virtual server name, which tells unlang_call() |
328 | | * which virtual server to call. |
329 | | */ |
330 | 0 | g = unlang_generic_to_group(c); |
331 | 0 | gext = unlang_group_to_call(g); |
332 | 0 | gext->server_cs = server_cs; |
333 | 0 | gext->attr_packet_type = attr_packet_type; |
334 | |
|
335 | 0 | return c; |
336 | 0 | } |
337 | | |
338 | | void unlang_call_init(void) |
339 | 4 | { |
340 | 4 | unlang_register(&(unlang_op_t){ |
341 | 4 | .name = "call", |
342 | 4 | .flag = UNLANG_OP_FLAG_RCODE_SET | UNLANG_OP_FLAG_DEBUG_BRACES, |
343 | 4 | .type = UNLANG_TYPE_CALL, |
344 | | |
345 | 4 | .compile = unlang_compile_call, |
346 | 4 | .interpret = unlang_call_frame_init, |
347 | | |
348 | 4 | .unlang_size = sizeof(unlang_call_t), |
349 | 4 | .unlang_name = "unlang_call_t", |
350 | | |
351 | 4 | .frame_state_size = sizeof(unlang_frame_state_call_t), |
352 | 4 | .frame_state_type = "unlang_frame_state_call_t", |
353 | 4 | }); |
354 | 4 | } |