The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
subrequest_child.c
Go to the documentation of this file.
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: 9e8bd9f9758cdd3f69e2a8802fdd5a33bf9e489a $
19  *
20  * @file unlang/subrequest.c
21  * @brief Unlang "subrequest" and "detach" keyword evaluation.
22  *
23  * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24  */
25 #include <freeradius-devel/server/state.h>
26 #include "interpret_priv.h"
27 #include "subrequest_child_priv.h"
28 
29 /** Holds a synthesised instruction that we insert into the parent request
30  *
31  */
33 
34 static fr_dict_t const *dict_freeradius;
35 
38  { .out = &dict_freeradius, .proto = "freeradius" },
39  { NULL }
40 };
41 
43 
46  { .out = &request_attr_request_lifetime, .name = "Request-Lifetime", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
47  { NULL }
48 };
49 
50 /** Event handler to free a detached child
51  *
52  */
54 {
55  request_t *request = talloc_get_type_abort(uctx, request_t);
56 
57  RDEBUG("Reached Request-Lifetime. Forcibly stopping request");
58 
59  unlang_interpret_signal(request, FR_SIGNAL_CANCEL); /* Request should now be freed */
60 }
61 
62 /** Initialize a detached child
63  *
64  * Detach it from the parent, set up it's lifetime, and mark it as
65  * runnable.
66  */
68 {
69  fr_pair_t *vp;
70 
71  /*
72  * Set Request Lifetime
73  */
74  vp = fr_pair_find_by_da(&request->control_pairs, NULL, request_attr_request_lifetime);
75  if (!vp || (vp->vp_uint32 > 0)) {
77  const fr_event_timer_t **ev_p;
78 
79  if (!vp) {
80  when = fr_time_delta_add(when, fr_time_delta_from_sec(30)); /* default to 30s if not set */
81 
82  } else if (vp->vp_uint32 > 3600) {
83  RWDEBUG("Request-Timeout can be no more than 3600 seconds");
84  when = fr_time_delta_add(when, fr_time_delta_from_sec(3600));
85 
86  } else if (vp->vp_uint32 < 5) {
87  RWDEBUG("Request-Timeout can be no less than 5 seconds");
89 
90  } else {
91  when = fr_time_delta_from_sec(vp->vp_uint32);
92  }
93 
94  ev_p = talloc_size(request, sizeof(*ev_p));
95  memset(ev_p, 0, sizeof(*ev_p));
96 
97  if (fr_event_timer_in(request, unlang_interpret_event_list(request), ev_p, when,
98  unlang_detached_max_request_time, request) < 0) {
99  talloc_free(ev_p);
100  return -1;
101  }
102  }
103 
104  return 0;
105 }
106 
107 /** Process a detach signal in the child
108  *
109  * This processes any detach signals the child receives
110  * The child doesn't actually do the detaching
111  */
112 static void unlang_subrequest_child_signal(request_t *request, fr_signal_t action, UNUSED void *uctx)
113 {
115 
116  /*
117  * We're already detached so we don't
118  * need to notify the parent we're
119  * waking up, and we don't need to detach
120  * again...
121  */
122  if (!request->parent) return;
123 
124  state = talloc_get_type_abort(frame_current(request->parent)->state, unlang_frame_state_subrequest_t);
125 
126  /*
127  * Ignore signals which aren't detach, and ar
128  * and ignore the signal if we have no parent.
129  */
130  switch (action) {
131  case FR_SIGNAL_DETACH:
132  /*
133  * Place child's state back inside the parent
134  */
135  if (state->session.enable) fr_state_store_in_parent(request,
136  state->session.unique_ptr,
137  state->session.unique_int);
138 
139  if (!fr_cond_assert(unlang_subrequest_lifetime_set(request) == 0)) {
140  REDEBUG("Child could not be detached");
141  return;
142  }
143  FALL_THROUGH;
144 
145  case FR_SIGNAL_CANCEL:
146  /*
147  * Indicate to the parent there's no longer a child
148  */
149  state->child = NULL;
150 
151  /*
152  * Tell the parent to resume
153  */
154  unlang_interpret_mark_runnable(request->parent);
155  break;
156 
157  default:
158  return;
159  }
160 }
161 
162 /** When the child is done, tell the parent that we've exited.
163  *
164  * This is pushed as a frame at the top of the child's stack, so when
165  * the child is done executing, it runs this to inform the parent
166  * that its done.
167  */
169  UNUSED int *p_priority, request_t *request, void *uctx)
170 {
172 
173  /*
174  * Child was detached, nothing more to do.
175  *
176  * This frame was left on the stack when the child
177  * detached. It's normally meant to trigger a
178  * resume in the parent, but as there is no parent
179  * it just becomes a noop and gets popped.
180  *
181  * This is cheaper/less complex then rooting it
182  * out at detach time and unsetting the repeat
183  * function.
184  */
185  if (!request->parent) return UNLANG_ACTION_CALCULATE_RESULT;
186 
187  /*
188  * 'state' in this context, is the frame state
189  * in the parent request, so we cannot examine it
190  * until AFTER we've determined there is still
191  * a parent, else the memory could've been freed.
192  *
193  * i.e. don't move the get_type_abort call onto
194  * the same line as the state variable declaration.
195  */
196  state = talloc_get_type_abort(uctx, unlang_frame_state_subrequest_t);
197 
198  /*
199  * Place child state back inside the parent
200  */
201  if (state->session.enable) fr_state_store_in_parent(request,
202  state->session.unique_ptr,
203  state->session.unique_int);
204  /*
205  * Record the child's result
206  */
207  if (state->p_result) *state->p_result = *p_result;
208 
209  /*
210  * Resume the parent
211  */
212  unlang_interpret_mark_runnable(request->parent);
213 
215 }
216 
217 /** Push a resumption frame onto a child's stack
218  *
219  * This is necessary so that the child informs its parent when it's done/detached
220  * and so that the child responds to detach signals.
221  */
223 {
224  /*
225  * Push a resume frame into the child
226  */
227  if (unlang_function_push(child, NULL,
230  ~(FR_SIGNAL_DETACH | FR_SIGNAL_CANCEL),
232  state) < 0) return -1;
233 
234  return_point_set(frame_current(child)); /* Stop return going through the resumption frame */
235 
236  return 0;
237 }
238 
239 /** Function to run in the context of the parent on resumption
240  *
241  */
243  unlang_stack_frame_t *frame)
244 {
245  unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
246 
248 
250 }
251 
252 /** Function called by the unlang interpreter to start the child running
253  *
254  * The reason why we do this on the unlang stack is so that _this_ frame
255  * is marked as resumable in the parent, not whatever frame was previously
256  * being processed by the interpreter when the parent was called.
257  *
258  * i.e. after calling unlang_subrequest_child_push, the code in the parent
259  * can call UNLANG_ACTION_PUSHED_CHILD, which will result in _this_ frame
260  * being executed, and _this_ frame can yield.
261  */
263  unlang_stack_frame_t *frame)
264 {
265  unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
266  request_t *child = state->child;
267 
268  /*
269  * No parent means this is a pre-detached child
270  * so the parent should continue executing.
271  */
272  if (!child || !child->parent) return UNLANG_ACTION_CALCULATE_RESULT;
273 
274 
275  /*
276  * Ensure we restore the session state information
277  * into the child.
278  */
279  if (state->session.enable) fr_state_restore_to_child(child,
280  state->session.unique_ptr,
281  state->session.unique_int);
282  /*
283  * Ensures the child is setup correctly and adds
284  * it into the runnable queue of whatever owns
285  * the interpreter.
286  */
287  interpret_child_init(child);
288 
289  /*
290  * Don't run this function again on resumption
291  */
293  repeatable_set(frame);
294 
295  return UNLANG_ACTION_YIELD;
296 }
297 
298 /** Push a pre-existing child back onto the stack as a subrequest
299  *
300  * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something
301  * that calls it.
302  *
303  * After the child is no longer required it *MUST* be freed with #unlang_subrequest_detach_and_free.
304  * It's not enough to free it with talloc_free.
305  *
306  * This function should be called _before_ pushing any additional frames onto the child's
307  * stack for it to execute.
308  *
309  * The parent should return UNLANG_ACTION_PUSHED_CHILD, when it's done setting up the
310  * child request. It should NOT return UNLANG_ACTION_YIELD.
311  *
312  * @param[in] out Where to write the result of the subrequest.
313  * @param[in] child to push.
314  * @param[in] session control values. Whether we restore/store session info.
315  * @param[in] free_child automatically free the child when it's finished executing.
316  * This is useful if extracting the result from the child is
317  * done using the child's stack, and so the parent never needs
318  * to access it.
319  * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return.
320  * Set to UNLANG_SUB_FRAME if the interprer should continue.
321  * @return
322  * - 0 on success.
323  * - -1 on failure.
324  */
326  unlang_subrequest_session_t const *session,
327  bool free_child, bool top_frame)
328 {
330  unlang_stack_frame_t *frame;
331 
332  /*
333  * Push a new subrequest frame onto the stack
334  *
335  * This allocates memory for the frame state
336  * which we fill in below.
337  */
339  RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, top_frame) < 0) {
340  return -1;
341  }
342 
343  frame = frame_current(child->parent);
345 
346  /*
347  * Setup the state for the subrequest
348  */
349  state = talloc_get_type_abort(frame_current(child->parent)->state, unlang_frame_state_subrequest_t);
350  state->p_result = out;
351  state->child = child;
352  state->session = *session;
353  state->free_child = free_child;
354 
355  if (!fr_cond_assert_msg(stack_depth_current(child) == 0,
356  "Child stack depth must be 0 (not %u), when subrequest_child_push is called",
357  stack_depth_current(child))) return -1;
358 
359  /*
360  * Push a resumption frame onto the stack
361  * so the child calls its parent when it's
362  * complete.
363  */
364  if (unlang_subrequest_child_push_resume(child, state) < 0) return -1;
365 
366  return 0;
367 }
368 
370 {
371  /*
372  * Ensures the child is setup correctly and adds
373  * it into the runnable queue of whatever owns
374  * the interpreter.
375  */
376  interpret_child_init(request);
377 
378  if ((unlang_subrequest_lifetime_set(request) < 0) || (request_detach(request) < 0)) {
379  RPEDEBUG("Failed detaching request");
380  return -1;
381  }
382 
383  return 0;
384 }
385 
387 {
389  PERROR("%s", __FUNCTION__);
390  return -1;
391  }
393  PERROR("%s", __FUNCTION__);
394  return -1;
395  }
396 
397  /*
398  * Needs to be dynamically allocated
399  * so that talloc_get_type works
400  * correctly.
401  */
404  .group = {
405  .self = {
407  .name = "subrequest",
408  .debug_name = "subrequest",
409  .actions = {
410  .actions = {
411  [RLM_MODULE_REJECT] = 0,
412  [RLM_MODULE_FAIL] = 0,
413  [RLM_MODULE_OK] = 0,
414  [RLM_MODULE_HANDLED] = 0,
415  [RLM_MODULE_INVALID] = 0,
416  [RLM_MODULE_DISALLOW] = 0,
417  [RLM_MODULE_NOTFOUND] = 0,
418  [RLM_MODULE_NOOP] = 0,
419  [RLM_MODULE_UPDATED] = 0
420  },
421  .retry = RETRY_INIT,
422  },
423  }
424  }
425  };
426 
427  return 0;
428 }
429 
431 {
434 }
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition: action.h:37
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition: action.h:42
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition: build.h:320
#define UNUSED
Definition: build.h:313
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:137
#define fr_cond_assert_msg(_x, _fmt,...)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:154
#define fr_dict_autofree(_to_free)
Definition: dict.h:674
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition: dict.h:250
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:263
int fr_dict_attr_autoload(fr_dict_attr_autoload_t const *to_load)
Process a dict_attr_autoload element to load/verify a dictionary attribute.
Definition: dict_util.c:3647
#define fr_dict_autoload(_to_load)
Definition: dict.h:671
Specifies an attribute which must be present for the module to function.
Definition: dict.h:249
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition: dict.h:262
#define fr_event_timer_in(...)
Definition: event.h:255
#define unlang_function_push(_request, _func, _repeat, _signal, _sigmask, _top_frame, _uctx)
Push a generic function onto the unlang stack.
Definition: function.h:111
fr_event_list_t * unlang_interpret_event_list(request_t *request)
Get the event list for the current interpreter.
Definition: interpret.c:1745
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition: interpret.c:1340
int unlang_interpret_push(request_t *request, unlang_t const *instruction, rlm_rcode_t default_rcode, bool do_next_sibling, bool top_frame)
Push a new frame onto the stack.
Definition: interpret.c:161
void unlang_interpret_signal(request_t *request, fr_signal_t action)
Send a signal (usually stop) to a request.
Definition: interpret.c:1184
#define UNLANG_TOP_FRAME
Definition: interpret.h:35
Private declarations for the unlang interpreter.
static void interpret_child_init(request_t *request)
#define PERROR(_fmt,...)
Definition: log.h:228
#define RWDEBUG(fmt,...)
Definition: log.h:361
#define RPEDEBUG(fmt,...)
Definition: log.h:376
talloc_free(reap)
Stores all information relating to an event list.
Definition: event.c:411
A timer event.
Definition: event.c:102
@ FR_TYPE_UINT32
32 Bit unsigned integer.
Definition: merged_model.c:99
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition: pair.c:688
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define RDEBUG(fmt,...)
Definition: radclient.h:53
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
@ RLM_MODULE_INVALID
The module considers the request invalid.
Definition: rcode.h:45
@ RLM_MODULE_OK
The module is OK, continue.
Definition: rcode.h:43
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition: rcode.h:42
@ RLM_MODULE_DISALLOW
Reject the request (user is locked out).
Definition: rcode.h:46
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition: rcode.h:41
@ RLM_MODULE_NOTFOUND
User not found.
Definition: rcode.h:47
@ RLM_MODULE_UPDATED
OK (pairs modified).
Definition: rcode.h:49
@ RLM_MODULE_NOT_SET
Error resolving rcode (should not be returned by modules).
Definition: rcode.h:51
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition: rcode.h:48
@ RLM_MODULE_HANDLED
The module handled the request, so stop.
Definition: rcode.h:44
int request_detach(request_t *child)
Unlink a subrequest from its parent.
Definition: request.c:664
void fr_state_restore_to_child(request_t *child, void const *unique_ptr, int unique_int)
Restore subrequest data from a parent request.
Definition: state.c:858
void fr_state_store_in_parent(request_t *child, void const *unique_ptr, int unique_int)
Store subrequest's session-state list and persistable request data in its parent.
Definition: state.c:812
fr_signal_t
Definition: signal.h:48
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition: pair.h:68
void unlang_subrequest_detach_and_free(request_t **child)
Free a child request, detaching it from its parent and freeing allocated memory.
Definition: subrequest.c:251
int unique_int
Session unique int identifier.
Definition: subrequest.h:37
void const * unique_ptr
Session unique ptr identifier.
Definition: subrequest.h:36
bool enable
Whether we should store/restore sessions.
Definition: subrequest.h:35
void unlang_subrequest_child_op_free(void)
static void unlang_subrequest_child_signal(request_t *request, fr_signal_t action, UNUSED void *uctx)
Process a detach signal in the child.
fr_dict_attr_autoload_t subrequest_dict_attr[]
int unlang_subrequest_lifetime_set(request_t *request)
Initialize a detached child.
int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *state)
Push a resumption frame onto a child's stack.
static void unlang_detached_max_request_time(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
Event handler to free a detached child.
static fr_dict_t const * dict_freeradius
static unlang_subrequest_t * subrequest_instruction
Holds a synthesised instruction that we insert into the parent request.
static fr_dict_attr_t const * request_attr_request_lifetime
int unlang_subrequest_child_push_and_detach(request_t *request)
fr_dict_autoload_t subrequest_dict[]
int unlang_subrequest_child_op_init(void)
static unlang_action_t unlang_subrequest_calculate_result(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request, unlang_stack_frame_t *frame)
Function to run in the context of the parent on resumption.
unlang_action_t unlang_subrequest_child_run(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request, unlang_stack_frame_t *frame)
Function called by the unlang interpreter to start the child running.
int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child, unlang_subrequest_session_t const *session, bool free_child, bool top_frame)
Push a pre-existing child back onto the stack as a subrequest.
static unlang_action_t unlang_subrequest_child_done(rlm_rcode_t *p_result, UNUSED int *p_priority, request_t *request, void *uctx)
When the child is done, tell the parent that we've exited.
bool free_child
Whether we should free the child after it completes.
unlang_subrequest_session_t session
Session configuration.
request_t * child
Pre-allocated child request.
unlang_group_t group
rlm_rcode_t * p_result
Where to store the result.
Parameters for initialising the subrequest (parent's frame state)
static fr_time_delta_t fr_time_delta_add(fr_time_delta_t a, fr_time_delta_t b)
Definition: time.h:255
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition: time.h:588
#define fr_time_delta_wrap(_time)
Definition: time.h:152
A time delta, a difference in time measured in nanoseconds.
Definition: time.h:80
"server local" time.
Definition: time.h:69
static fr_event_list_t * el
void * state
Stack frame specialisations.
Definition: unlang_priv.h:304
#define UNLANG_NEXT_STOP
Definition: unlang_priv.h:102
static void return_point_set(unlang_stack_frame_t *frame)
Definition: unlang_priv.h:356
static int stack_depth_current(request_t *request)
Definition: unlang_priv.h:401
@ UNLANG_TYPE_SUBREQUEST
create a child subrequest
Definition: unlang_priv.h:74
unlang_t self
Definition: unlang_priv.h:156
static void repeatable_set(unlang_stack_frame_t *frame)
Definition: unlang_priv.h:353
unlang_process_t process
function to call for interpreting this stack frame
Definition: unlang_priv.h:292
unlang_type_t type
The specialisation of this node.
Definition: unlang_priv.h:127
static unlang_stack_frame_t * frame_current(request_t *request)
Definition: unlang_priv.h:394
Our interpreter stack, as distinct from the C stack.
Definition: unlang_priv.h:288
#define RETRY_INIT
Definition: retry.h:39
static size_t char ** out
Definition: value.h:984