Coverage Report

Created: 2026-08-08 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}