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