Coverage Report

Created: 2026-03-31 06:21

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: 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
}