The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
rlm_rest.c
Go to the documentation of this file.
1 /*
2  * This program is 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 (at
5  * 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: 7dbe316f97d44d8a301a68d8bc7dc7e6ff0bbb75 $
19  * @file rlm_rest.c
20  * @brief Integrate FreeRADIUS with RESTfull APIs
21  *
22  * @copyright 2012-2019,2024 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23  */
24 
25 #include <talloc.h>
26 RCSID("$Id: 7dbe316f97d44d8a301a68d8bc7dc7e6ff0bbb75 $")
27 
28 #include <freeradius-devel/curl/base.h>
29 #include <freeradius-devel/curl/xlat.h>
30 #include <freeradius-devel/server/base.h>
31 #include <freeradius-devel/server/cf_parse.h>
32 #include <freeradius-devel/server/cf_util.h>
33 
34 #include <freeradius-devel/server/global_lib.h>
35 #include <freeradius-devel/server/module_rlm.h>
36 #include <freeradius-devel/server/tmpl.h>
37 #include <freeradius-devel/server/tmpl_escape.h>
38 #include <freeradius-devel/server/pairmove.h>
39 #include <freeradius-devel/server/log.h>
40 #include <freeradius-devel/tls/base.h>
41 #include <freeradius-devel/util/atexit.h>
42 #include <freeradius-devel/util/debug.h>
43 #include <freeradius-devel/util/table.h>
44 #include <freeradius-devel/util/uri.h>
45 #include <freeradius-devel/util/value.h>
46 #include <freeradius-devel/unlang/call_env.h>
47 #include <freeradius-devel/unlang/xlat_func.h>
48 #include <freeradius-devel/unlang/xlat.h>
49 
50 #include "rest.h"
51 
52 static int rest_uri_part_escape(fr_value_box_t *vb, void *uctx);
53 static void *rest_uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *uctx);
54 
55 static fr_uri_part_t const rest_uri_parts[] = {
56  { .name = "scheme", .safe_for = CURL_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L(":")), .part_adv = { [':'] = 1 }, .extra_skip = 2 },
57  { .name = "host", .safe_for = CURL_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L(":"), L("/")), .part_adv = { [':'] = 1, ['/'] = 2 }, .func = rest_uri_part_escape },
58  { .name = "port", .safe_for = CURL_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("/")), .part_adv = { ['/'] = 1 } },
59  { .name = "method", .safe_for = CURL_URI_SAFE_FOR, .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 }, .func = rest_uri_part_escape },
60  { .name = "param", .safe_for = CURL_URI_SAFE_FOR, .func = rest_uri_part_escape },
62 };
63 
65 
66  { L("1.0"), CURL_HTTP_VERSION_1_0 }, //!< Enforce HTTP 1.0 requests.
67  { L("1.1"), CURL_HTTP_VERSION_1_1 }, //!< Enforce HTTP 1.1 requests.
68 /*
69  * These are all enum values
70  */
71 #if CURL_AT_LEAST_VERSION(7,49,0)
72  { L("2.0"), CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE }, //!< Enforce HTTP 2.0 requests.
73 #endif
74 #if CURL_AT_LEAST_VERSION(7,33,0)
75  { L("2.0+auto"), CURL_HTTP_VERSION_2_0 }, //!< Attempt HTTP 2 requests. libcurl will fall back
76  ///< to HTTP 1.1 if HTTP 2 can't be negotiated with the
77  ///< server. (Added in 7.33.0)
78 #endif
79 #if CURL_AT_LEAST_VERSION(7,47,0)
80  { L("2.0+tls"), CURL_HTTP_VERSION_2TLS }, //!< Attempt HTTP 2 over TLS (HTTPS) only.
81  ///< libcurl will fall back to HTTP 1.1 if HTTP 2
82  ///< can't be negotiated with the HTTPS server.
83  ///< For clear text HTTP servers, libcurl will use 1.1.
84 #endif
85  { L("default"), CURL_HTTP_VERSION_NONE } //!< We don't care about what version the library uses.
86  ///< libcurl will use whatever it thinks fit.
87 };
89 
90 /** Unique pointer used to determine if we should explicitly disable proxying
91  *
92  */
93 char const *rest_no_proxy = "*";
94 
95 static int rest_proxy_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent,
96  CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
97 {
98  static fr_table_num_sorted_t const disable_proxy_table[] = {
99  { L("no"), 1 },
100  { L("false"), 1 },
101  { L("none"), 1 }
102  };
103  static size_t disable_proxy_table_len = NUM_ELEMENTS(disable_proxy_table);
104  char const *value = cf_pair_value(cf_item_to_pair(ci));
105 
106  if (fr_table_value_by_str(disable_proxy_table, value, 0) == 1) {
107  *((char const **)out) = rest_no_proxy;
108  } else {
109  *((char const **)out) = value;
110  }
111  return 0;
112 }
113 
114 #define SECTION_REQUEST_COMMON \
115  { FR_CONF_OFFSET("body", rlm_rest_section_request_t, body_str), .dflt = "none" }, \
116  /* User authentication */ \
117  { FR_CONF_OFFSET_IS_SET("auth", FR_TYPE_VOID, 0, rlm_rest_section_request_t, auth), \
118  .func = cf_table_parse_int, .uctx = &(cf_table_parse_ctx_t){ .table = http_auth_table, .len = &http_auth_table_len }, .dflt = "none" }, \
119  { FR_CONF_OFFSET("require_auth", rlm_rest_section_request_t, require_auth), .dflt = "no" }, \
120  { FR_CONF_OFFSET("chunk", rlm_rest_section_request_t, chunk), .dflt = "0" } \
121 
123  { FR_CONF_OFFSET("proxy", rlm_rest_section_request_t, proxy), .func = rest_proxy_parse },
124  { FR_CONF_OFFSET("method", rlm_rest_section_request_t, method_str), .dflt = "GET" },
127 };
128 
130  { FR_CONF_OFFSET("force_to", rlm_rest_section_response_t, force_to_str) }, \
131  { FR_CONF_OFFSET_TYPE_FLAGS("max_body_in", FR_TYPE_SIZE, 0, rlm_rest_section_response_t, max_body_in), .dflt = "16k" },
133 };
134 
135 static const conf_parser_t section_config[] = {
138 
139  /* Transfer configuration */
140  { FR_CONF_OFFSET("timeout", rlm_rest_section_t, timeout), .dflt = "4.0" },
141 
142  /* TLS Parameters */
145 };
146 
150 };
151 
152 static const conf_parser_t xlat_config[] = {
155 
156  /* Transfer configuration */
157  { FR_CONF_OFFSET("timeout", rlm_rest_section_t, timeout), .dflt = "4.0" },
158 
159  /* TLS Parameters */
162 };
163 
164 static const conf_parser_t module_config[] = {
165  { FR_CONF_DEPRECATED("connect_timeout", rlm_rest_t, connect_timeout) },
166  { FR_CONF_OFFSET("connect_proxy", rlm_rest_t, connect_proxy), .func = rest_proxy_parse },
167  { FR_CONF_OFFSET("http_negotiation", rlm_rest_t, http_negotiation),
168  .func = cf_table_parse_int,
170  .dflt = "default" },
171 
172  { FR_CONF_OFFSET_SUBSECTION("connection", 0, rlm_rest_t, conn_config, fr_curl_conn_config) },
173 
174 #ifdef CURLPIPE_MULTIPLEX
175  { FR_CONF_OFFSET("multiplex", rlm_rest_t, multiplex), .dflt = "yes" },
176 #endif
177 
178 #ifndef NDEBUG
179  { FR_CONF_OFFSET("fail_header_decode", rlm_rest_t, fail_header_decode), .dflt = "no" },
180  { FR_CONF_OFFSET("fail_body_decode", rlm_rest_t, fail_body_decode), .dflt = "no" },
181 #endif
182 
184 };
185 
186 #define REST_CALL_ENV_REQUEST_COMMON(_dflt_username, _dflt_password) \
187  { FR_CALL_ENV_OFFSET("header", FR_TYPE_STRING, CALL_ENV_FLAG_MULTI, rlm_rest_call_env_t, request.header) }, \
188  { FR_CALL_ENV_OFFSET("data", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, rlm_rest_call_env_t, request.data) }, \
189  { FR_CALL_ENV_OFFSET("username", FR_TYPE_STRING, CALL_ENV_FLAG_SINGLE | CALL_ENV_FLAG_NULLABLE, \
190  rlm_rest_call_env_t, request.username), .pair.dflt_quote = T_BARE_WORD, _dflt_username }, \
191  { FR_CALL_ENV_OFFSET("password", FR_TYPE_STRING, CALL_ENV_FLAG_SINGLE | CALL_ENV_FLAG_NULLABLE | CALL_ENV_FLAG_SECRET, \
192  rlm_rest_call_env_t, request.password), .pair.dflt_quote = T_BARE_WORD, _dflt_password }, \
193 
194 #define REST_CALL_ENV_RESPONSE_COMMON \
195  { FR_CALL_ENV_PARSE_ONLY_OFFSET("header", FR_TYPE_STRING, CALL_ENV_FLAG_ATTRIBUTE, rlm_rest_call_env_t, response.header) }, \
196 
197 #define REST_CALL_ENV_SECTION(_var, _section, _dflt_username, _dflt_password) \
198 static const call_env_method_t _var = { \
199  FR_CALL_ENV_METHOD_OUT(rlm_rest_call_env_t), \
200  .env = (call_env_parser_t[]){ \
201  { FR_CALL_ENV_SUBSECTION(_section, NULL, CALL_ENV_FLAG_NONE, \
202  ((call_env_parser_t[]) { \
203  { FR_CALL_ENV_SUBSECTION("request", NULL, CALL_ENV_FLAG_NONE, \
204  ((call_env_parser_t[]) { \
205  { FR_CALL_ENV_OFFSET("uri", FR_TYPE_STRING, CALL_ENV_FLAG_REQUIRED | CALL_ENV_FLAG_CONCAT, rlm_rest_call_env_t, request.uri), \
206  .pair.escape = { \
207  .func = fr_uri_escape, \
208  .mode = TMPL_ESCAPE_PRE_CONCAT, \
209  .uctx = { \
210  .func = { \
211  .alloc = rest_uri_part_escape_uctx_alloc, \
212  .uctx = rest_uri_parts \
213  } , \
214  .type = TMPL_ESCAPE_UCTX_ALLOC_FUNC\
215  }, \
216  .safe_for = CURL_URI_SAFE_FOR \
217  }, \
218  .pair.literals_safe_for = CURL_URI_SAFE_FOR}, /* Do not concat */ \
219  REST_CALL_ENV_REQUEST_COMMON(_dflt_username, _dflt_password) \
220  CALL_ENV_TERMINATOR \
221  })) }, \
222  { FR_CALL_ENV_SUBSECTION("response", NULL, CALL_ENV_FLAG_NONE, \
223  ((call_env_parser_t[]) { \
224  REST_CALL_ENV_RESPONSE_COMMON \
225  CALL_ENV_TERMINATOR \
226  })) }, \
227  CALL_ENV_TERMINATOR \
228  }) \
229  ) }, \
230  CALL_ENV_TERMINATOR \
231  } \
232 }
233 
234 REST_CALL_ENV_SECTION(rest_call_env_authorize, "authorize",,);
235 REST_CALL_ENV_SECTION(rest_call_env_authenticate, "authenticate", .pair.dflt = "&User-Name", .pair.dflt = "&User-Password");
236 REST_CALL_ENV_SECTION(rest_call_env_post_auth, "post-auth",,);
237 REST_CALL_ENV_SECTION(rest_call_env_accounting, "accounting",,);
238 
239 /*
240  * xlat call env doesn't have the same set of config items as the other sections
241  * because some values come from the xlat call itself.
242  *
243  * If someone can figure out a non-fuggly way of omitting the uri from the
244  * configuration, please do, and use the REST_CALL_ENV_SECTION macro for this
245  * too.
246  */
248  FR_CALL_ENV_METHOD_OUT(rlm_rest_call_env_t), \
249  .env = (call_env_parser_t[]){ \
250  { FR_CALL_ENV_SUBSECTION("xlat", NULL, CALL_ENV_FLAG_NONE, \
251  ((call_env_parser_t[]) { \
252  { FR_CALL_ENV_SUBSECTION("request", NULL, CALL_ENV_FLAG_NONE, \
253  ((call_env_parser_t[]) { \
254  REST_CALL_ENV_REQUEST_COMMON(,) \
255  CALL_ENV_TERMINATOR \
256  })) }, \
257  { FR_CALL_ENV_SUBSECTION("response", NULL, CALL_ENV_FLAG_NONE, \
258  ((call_env_parser_t[]) { \
259  REST_CALL_ENV_RESPONSE_COMMON \
260  CALL_ENV_TERMINATOR \
261  })) }, \
262  CALL_ENV_TERMINATOR \
263  }) \
264  ) }, \
265  CALL_ENV_TERMINATOR \
266  } \
267 };
268 
270 static fr_dict_t const *dict_radius;
271 
274  { .out = &dict_freeradius, .proto = "freeradius" },
275  { .out = &dict_radius, .proto = "radius" },
276  { NULL }
277 };
278 
284 
287  { .out = &attr_rest_http_body, .name = "REST-HTTP-Body", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
288  { .out = &attr_rest_http_header, .name = "REST-HTTP-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
289  { .out = &attr_rest_http_status_code, .name = "REST-HTTP-Status-Code", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
290  { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius },
291  { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius },
292  { NULL }
293 };
294 
295 extern global_lib_autoinst_t const * const rlm_rest_lib[];
299 };
300 
301 /** Update the status attribute
302  *
303  * @param[in] request The current request.
304  * @param[in] handle rest handle.
305  * @return
306  * - 0 if status was updated successfully.
307  * - -1 if status was not updated successfully.
308  */
309 static int rlm_rest_status_update(request_t *request, void *handle)
310 {
311  int code;
312  fr_pair_t *vp;
313 
314  RDEBUG2("Updating result attribute(s)");
315 
316  RINDENT();
317  code = rest_get_handle_code(handle);
318  if (!code) {
320  RDEBUG2("&request.REST-HTTP-Status-Code !* ANY");
321  REXDENT();
322  return -1;
323  }
324 
325  RDEBUG2("&request.REST-HTTP-Status-Code := %i", code);
326 
328  vp->vp_uint32 = code;
329  REXDENT();
330 
331  return 0;
332 }
333 
334 static int _rest_uri_part_escape_uctx_free(void *uctx)
335 {
336  return talloc_free(uctx);
337 }
338 
339 /** Allocate an escape uctx to pass to fr_uri_escape
340  *
341  * @param[in] request UNUSED.
342  * @param[in] uctx pointer to the start of the uri_parts array.
343  * @return A new fr_uri_escape_ctx_t.
344  */
345 static void *rest_uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *uctx)
346 {
347  static _Thread_local fr_uri_escape_ctx_t *t_ctx;
348 
349  if (unlikely(t_ctx == NULL)) {
350  fr_uri_escape_ctx_t *ctx;
351 
352  MEM(ctx = talloc_zero(NULL, fr_uri_escape_ctx_t));
354  } else {
355  memset(t_ctx, 0, sizeof(*t_ctx));
356  }
357  t_ctx->uri_part = uctx;
358  return t_ctx;
359 }
360 
361 /** URL escape a single box forming part of a URL
362  *
363  * @param[in] vb to escape
364  * @param[in] uctx UNUSED context containing CURL handle
365  * @return
366  * - 0 on success
367  * - -1 on failure
368  */
369 static int rest_uri_part_escape(fr_value_box_t *vb, UNUSED void *uctx)
370 {
371  char *escaped;
372 
373  escaped = curl_easy_escape(fr_curl_tmp_handle(), vb->vb_strvalue, vb->vb_length);
374  if (!escaped) return -1;
375 
376  /*
377  * Returned string the same length - nothing changed
378  */
379  if (strlen(escaped) == vb->vb_length) {
380  curl_free(escaped);
381  return 0;
382  }
383 
385  fr_value_box_strdup(vb, vb, NULL, escaped, vb->tainted);
386 
387  curl_free(escaped);
388 
389  return 0;
390 }
391 
392 static int rlm_rest_perform(module_ctx_t const *mctx,
393  rlm_rest_section_t const *section, fr_curl_io_request_t *randle,
394  request_t *request)
395 {
396  rlm_rest_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_rest_thread_t);
397  rlm_rest_call_env_t *call_env = talloc_get_type_abort(mctx->env_data, rlm_rest_call_env_t);
398  int ret;
399 
400  RDEBUG2("Sending HTTP %s to \"%pV\"",
401  fr_table_str_by_value(http_method_table, section->request.method, NULL), call_env->request.uri);
402 
403  /*
404  * Configure various CURL options, and initialise the read/write
405  * context data.
406  */
407  ret = rest_request_config(mctx, section, request, randle, section->request.method, section->request.body,
408  call_env->request.uri->vb_strvalue,
409  call_env->request.data ? call_env->request.data->vb_strvalue : NULL);
410  if (ret < 0) return -1;
411 
412  /*
413  * Send the CURL request, pre-parse headers, aggregate incoming
414  * HTTP body data into a single contiguous buffer.
415  */
416  ret = fr_curl_io_request_enqueue(t->mhandle, request, randle);
417  if (ret < 0) return -1;
418 
419  return 0;
420 }
421 
423  xlat_ctx_t const *xctx,
424  request_t *request, UNUSED fr_value_box_list_t *in)
425 {
426  rlm_rest_xlat_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, rlm_rest_xlat_rctx_t);
427  int hcode;
428  ssize_t len;
429  char const *body;
431 
432  fr_curl_io_request_t *handle = talloc_get_type_abort(rctx->handle, fr_curl_io_request_t);
433  rlm_rest_section_t *section = &rctx->section;
434 
435  if (section->tls.extract_cert_attrs) fr_curl_response_certinfo(request, handle);
436 
437  if (rlm_rest_status_update(request, handle) < 0) {
438  xa = XLAT_ACTION_FAIL;
439  goto finish;
440  }
441 
442  hcode = rest_get_handle_code(handle);
443  switch (hcode) {
444  case 404:
445  case 410:
446  case 403:
447  case 401:
448  {
449  xa = XLAT_ACTION_FAIL;
450 error:
451  rest_response_error(request, handle);
452  goto finish;
453  }
454  case 204:
455  goto finish;
456 
457  default:
458  /*
459  * Attempt to parse content if there was any.
460  */
461  if ((hcode >= 200) && (hcode < 300)) {
462  break;
463  } else if (hcode < 500) {
464  xa = XLAT_ACTION_FAIL;
465  goto error;
466  } else {
467  xa = XLAT_ACTION_FAIL;
468  goto error;
469  }
470  }
471 
472 finish:
473  /*
474  * Always output the xlat data.
475  *
476  * The user can check REST-HTTP-Status-Code to figure out what happened.
477  *
478  * Eventually we should just emit two boxes, one with the response code
479  * and one with the body.
480  */
481  len = rest_get_handle_data(&body, handle);
482  if (len > 0) {
483  fr_value_box_t *vb;
484 
485  MEM(vb = fr_value_box_alloc_null(ctx));
486  fr_value_box_bstrndup(vb, vb, NULL, body, len, true);
487  fr_dcursor_insert(out, vb);
488  }
489 
490  rest_slab_release(handle);
491 
492  talloc_free(rctx);
493 
494  return xa;
495 }
496 
498  { .required = true, .safe_for = CURL_URI_SAFE_FOR, .type = FR_TYPE_STRING },
499  { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_STRING },
501 };
502 
503 /** Simple xlat to read text data from a URL
504  *
505  * Example:
506 @verbatim
507 %rest(http://example.com/)
508 @endverbatim
509  *
510  * @ingroup xlat_functions
511  */
513  xlat_ctx_t const *xctx, request_t *request,
514  fr_value_box_list_t *in)
515 {
517  rlm_rest_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_rest_thread_t);
518 
519  fr_curl_io_request_t *randle = NULL;
520  int ret;
521  http_method_t method;
522  fr_value_box_t *in_vb = fr_value_box_list_pop_head(in), *uri_vb = NULL;
523 
524  /* There are no configurable parameters other than the URI */
525  rlm_rest_xlat_rctx_t *rctx;
526  rlm_rest_section_t *section;
527 
528  MEM(rctx = talloc(request, rlm_rest_xlat_rctx_t));
529  section = &rctx->section;
530 
531  /*
532  * Section gets modified, so we need our own copy.
533  */
534  memcpy(&rctx->section, &inst->xlat, sizeof(*section));
535 
536  fr_assert(in_vb->type == FR_TYPE_GROUP);
537 
538  /*
539  * If we have more than 1 argument, then the first is the method
540  */
541  if ((fr_value_box_list_head(in))) {
542  uri_vb = fr_value_box_list_head(&in_vb->vb_group);
544  uri_vb, &in_vb->vb_group, FR_TYPE_STRING,
546  SIZE_MAX) < 0) {
547  REDEBUG("Failed concatenating argument");
548  return XLAT_ACTION_FAIL;
549  }
550  method = fr_table_value_by_substr(http_method_table, uri_vb->vb_strvalue, -1, REST_HTTP_METHOD_UNKNOWN);
551 
552  if (method != REST_HTTP_METHOD_UNKNOWN) {
553  section->request.method = method;
554  /*
555  * If the method is unknown, it's a custom verb
556  */
557  } else {
559  MEM(section->request.method_str = talloc_bstrndup(rctx, uri_vb->vb_strvalue, uri_vb->vb_length));
560  }
561  /*
562  * Move to next argument
563  */
564  in_vb = fr_value_box_list_pop_head(in);
565  } else {
567  }
568 
569  /*
570  * We get a connection from the pool here as the CURL object
571  * is needed to use curl_easy_escape() for escaping
572  */
573  randle = rctx->handle = rest_slab_reserve(t->slab);
574  if (!randle) return XLAT_ACTION_FAIL;
575 
576  /*
577  * Walk the incoming boxes, assessing where each is in the URI,
578  * escaping tainted ones where needed. Following each space in the
579  * input a new VB group is started.
580  */
581 
582  fr_assert(in_vb->type == FR_TYPE_GROUP);
583 
584  randle->request = request; /* Populate the request pointer for escape callbacks */
585 
586  if (fr_uri_escape_list(&in_vb->vb_group, rest_uri_parts, NULL) < 0) {
587  RPEDEBUG("Failed escaping URI");
588 
589  error:
590  rest_slab_release(randle);
591  talloc_free(section);
592 
593  return XLAT_ACTION_FAIL;
594  }
595 
596  uri_vb = fr_value_box_list_head(&in_vb->vb_group);
598  uri_vb, &in_vb->vb_group, FR_TYPE_STRING,
600  SIZE_MAX) < 0) {
601  REDEBUG("Concatenating URI");
602  goto error;
603  }
604 
605  /*
606  * Any additional arguments are freeform data
607  */
608  if ((in_vb = fr_value_box_list_head(in))) {
610  in_vb, in, FR_TYPE_STRING,
612  SIZE_MAX) < 0) {
613  REDEBUG("Failed to concatenate freeform data");
614  goto error;
615  }
617  }
618 
619  RDEBUG2("Sending HTTP %s to \"%pV\"",
620  (section->request.method == REST_HTTP_METHOD_CUSTOM) ?
622  uri_vb);
623 
624  /*
625  * Configure various CURL options, and initialise the read/write
626  * context data.
627  *
628  * @todo We could extract the User-Name and password from the URL string.
629  */
631  section, request, randle, section->request.method,
632  section->request.body,
633  uri_vb->vb_strvalue, in_vb ? in_vb->vb_strvalue : NULL);
634  if (ret < 0) goto error;
635 
636  /*
637  * Send the CURL request, pre-parse headers, aggregate incoming
638  * HTTP body data into a single contiguous buffer.
639  *
640  * @fixme need to pass in thread to all xlat functions
641  */
642  ret = fr_curl_io_request_enqueue(t->mhandle, request, randle);
643  if (ret < 0) goto error;
644 
645  return unlang_xlat_yield(request, rest_xlat_resume, rest_io_xlat_signal, ~FR_SIGNAL_CANCEL, rctx);
646 }
647 
649 {
651  rlm_rest_section_t const *section = &inst->authenticate;
652  fr_curl_io_request_t *handle = talloc_get_type_abort(mctx->rctx, fr_curl_io_request_t);
653 
654  int hcode;
655  rlm_rcode_t rcode = RLM_MODULE_OK;
656  int ret;
657 
658  if (section->tls.extract_cert_attrs) fr_curl_response_certinfo(request, handle);
659 
660  if (rlm_rest_status_update(request, handle) < 0) {
661  rcode = RLM_MODULE_FAIL;
662  goto finish;
663  }
664 
665  hcode = rest_get_handle_code(handle);
666  switch (hcode) {
667  case 404:
668  case 410:
669  rcode = RLM_MODULE_NOTFOUND;
670  break;
671 
672  case 403:
673  rcode = RLM_MODULE_DISALLOW;
674  break;
675 
676  case 401:
677  /*
678  * Attempt to parse content if there was any.
679  */
680  ret = rest_response_decode(inst, section, request, handle);
681  if (ret < 0) {
682  rcode = RLM_MODULE_FAIL;
683  break;
684  }
685 
686  rcode = RLM_MODULE_REJECT;
687  break;
688 
689  case 204:
690  rcode = RLM_MODULE_OK;
691  break;
692 
693  default:
694  /*
695  * Attempt to parse content if there was any.
696  */
697  if ((hcode >= 200) && (hcode < 300)) {
698  ret = rest_response_decode(inst, section, request, handle);
699  if (ret < 0) rcode = RLM_MODULE_FAIL;
700  else if (ret == 0) rcode = RLM_MODULE_OK;
701  else rcode = RLM_MODULE_UPDATED;
702  break;
703  } else if (hcode < 500) {
704  rcode = RLM_MODULE_INVALID;
705  } else {
706  rcode = RLM_MODULE_FAIL;
707  }
708  }
709 
710  switch (rcode) {
711  case RLM_MODULE_INVALID:
712  case RLM_MODULE_FAIL:
713  case RLM_MODULE_DISALLOW:
714  rest_response_error(request, handle);
715  break;
716 
717  default:
718  rest_response_debug(request, handle);
719  break;
720  }
721 
722 finish:
723  rest_slab_release(handle);
724 
725  RETURN_MODULE_RCODE(rcode);
726 }
727 
728 /*
729  * Find the named user in this modules database. Create the set
730  * of attribute-value pairs to check and reply with for this user
731  * from the database. The authentication code only needs to check
732  * the password, the rest is done here.
733  */
734 static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
735 {
737  rlm_rest_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_rest_thread_t);
738  rlm_rest_section_t const *section = &inst->authorize;
739 
740  void *handle;
741  int ret;
742 
743  if (!section->name) {
744  RDEBUG2("No authorize section configured");
746  }
747 
748  handle = rest_slab_reserve(t->slab);
749  if (!handle) RETURN_MODULE_FAIL;
750 
751  ret = rlm_rest_perform(mctx, section, handle, request);
752  if (ret < 0) {
753  rest_slab_release(handle);
754 
756  }
757 
758  return unlang_module_yield(request, mod_authorize_result, rest_io_module_signal, ~FR_SIGNAL_CANCEL, handle);
759 }
760 
762  module_ctx_t const *mctx, request_t *request)
763 {
765  rlm_rest_section_t const *section = &inst->authenticate;
766  fr_curl_io_request_t *handle = talloc_get_type_abort(mctx->rctx, fr_curl_io_request_t);
767 
768  int hcode;
769  int rcode = RLM_MODULE_OK;
770  int ret;
771 
772  if (section->tls.extract_cert_attrs) fr_curl_response_certinfo(request, handle);
773 
774  if (rlm_rest_status_update(request, handle) < 0) {
775  rcode = RLM_MODULE_FAIL;
776  goto finish;
777  }
778 
779  hcode = rest_get_handle_code(handle);
780  switch (hcode) {
781  case 404:
782  case 410:
783  rcode = RLM_MODULE_NOTFOUND;
784  break;
785 
786  case 403:
787  rcode = RLM_MODULE_DISALLOW;
788  break;
789 
790  case 401:
791  /*
792  * Attempt to parse content if there was any.
793  */
794  ret = rest_response_decode(inst, section, request, handle);
795  if (ret < 0) {
796  rcode = RLM_MODULE_FAIL;
797  break;
798  }
799 
800  rcode = RLM_MODULE_REJECT;
801  break;
802 
803  case 204:
804  rcode = RLM_MODULE_OK;
805  break;
806 
807  default:
808  /*
809  * Attempt to parse content if there was any.
810  */
811  if ((hcode >= 200) && (hcode < 300)) {
812  ret = rest_response_decode(inst, section, request, handle);
813  if (ret < 0) rcode = RLM_MODULE_FAIL;
814  else if (ret == 0) rcode = RLM_MODULE_OK;
815  else rcode = RLM_MODULE_UPDATED;
816  break;
817  } else if (hcode < 500) {
818  rcode = RLM_MODULE_INVALID;
819  } else {
820  rcode = RLM_MODULE_FAIL;
821  }
822  }
823 
824  switch (rcode) {
825  case RLM_MODULE_INVALID:
826  case RLM_MODULE_FAIL:
827  case RLM_MODULE_DISALLOW:
828  rest_response_error(request, handle);
829  break;
830 
831  default:
832  rest_response_debug(request, handle);
833  break;
834  }
835 
836 finish:
837  rest_slab_release(handle);
838 
839  RETURN_MODULE_RCODE(rcode);
840 }
841 
842 /*
843  * Authenticate the user with the given password.
844  */
845 static unlang_action_t CC_HINT(nonnull) mod_authenticate(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
846 {
848  rlm_rest_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_rest_thread_t);
849  rlm_rest_call_env_t *call_env = talloc_get_type_abort(mctx->env_data, rlm_rest_call_env_t);
850  rlm_rest_section_t const *section = &inst->authenticate;
851  fr_curl_io_request_t *handle;
852 
853  int ret;
854 
855  if (!section->name) {
856  RDEBUG2("No authentication section configured");
858  }
859 
860  /*
861  * We can only authenticate user requests which HAVE
862  * a User-Name attribute.
863  */
864  if (!call_env->request.username) {
865  REDEBUG("Attribute \"User-Name\" is required for authentication");
867  }
868 
869  if (!call_env->request.password) {
870  REDEBUG("Attribute \"User-Password\" is required for authentication");
872  }
873 
874  /*
875  * Make sure the supplied password isn't empty
876  */
877  if (call_env->request.password->vb_length == 0) {
878  REDEBUG("User-Password must not be empty");
880  }
881 
882  /*
883  * Log the password
884  */
885  if (RDEBUG_ENABLED3) {
886  RDEBUG("Login attempt with password \"%pV\"", &call_env->request.password);
887  } else {
888  RDEBUG2("Login attempt with password");
889  }
890 
891  handle = rest_slab_reserve(t->slab);
892  if (!handle) RETURN_MODULE_FAIL;
893 
894  ret = rlm_rest_perform(mctx, section, handle, request);
895  if (ret < 0) {
896  rest_slab_release(handle);
897 
899  }
900 
901  return unlang_module_yield(request, mod_authenticate_result, rest_io_module_signal, ~FR_SIGNAL_CANCEL, handle);
902 }
903 
905 {
907  rlm_rest_section_t const *section = &inst->authenticate;
908  fr_curl_io_request_t *handle = talloc_get_type_abort(mctx->rctx, fr_curl_io_request_t);
909 
910  int hcode;
911  int rcode = RLM_MODULE_OK;
912  int ret;
913 
914  if (section->tls.extract_cert_attrs) fr_curl_response_certinfo(request, handle);
915 
916  if (rlm_rest_status_update(request, handle) < 0) {
917  rcode = RLM_MODULE_FAIL;
918  goto finish;
919  }
920 
921  hcode = rest_get_handle_code(handle);
922  if (hcode >= 500) {
923  rcode = RLM_MODULE_FAIL;
924  } else if (hcode == 204) {
925  rcode = RLM_MODULE_OK;
926  } else if ((hcode >= 200) && (hcode < 300)) {
927  ret = rest_response_decode(inst, section, request, handle);
928  if (ret < 0) rcode = RLM_MODULE_FAIL;
929  else if (ret == 0) rcode = RLM_MODULE_OK;
930  else rcode = RLM_MODULE_UPDATED;
931  } else {
932  rcode = RLM_MODULE_INVALID;
933  }
934 
935  switch (rcode) {
936  case RLM_MODULE_INVALID:
937  case RLM_MODULE_FAIL:
938  rest_response_error(request, handle);
939  break;
940 
941  default:
942  rest_response_debug(request, handle);
943  break;
944  }
945 
946 finish:
947  rest_slab_release(handle);
948 
949  RETURN_MODULE_RCODE(rcode);
950 }
951 
952 /*
953  * Send accounting info to a REST API endpoint
954  */
955 static unlang_action_t CC_HINT(nonnull) mod_accounting(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
956 {
958  rlm_rest_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_rest_thread_t);
959  rlm_rest_section_t const *section = &inst->accounting;
960 
961  void *handle;
962  int ret;
963 
964  if (!section->name) {
965  RDEBUG2("No accounting section configured");
967  }
968 
969  handle = rest_slab_reserve(t->slab);
970  if (!handle) RETURN_MODULE_FAIL;
971 
972  ret = rlm_rest_perform(mctx, section, handle, request);
973  if (ret < 0) {
974  rest_slab_release(handle);
975 
977  }
978 
979  return unlang_module_yield(request, mod_accounting_result, rest_io_module_signal, ~FR_SIGNAL_CANCEL, handle);
980 }
981 
983 {
985  rlm_rest_section_t const *section = &inst->authenticate;
986  fr_curl_io_request_t *handle = talloc_get_type_abort(mctx->rctx, fr_curl_io_request_t);
987 
988  int hcode;
989  int rcode = RLM_MODULE_OK;
990  int ret;
991 
992  if (section->tls.extract_cert_attrs) fr_curl_response_certinfo(request, handle);
993 
994  if (rlm_rest_status_update(request, handle) < 0) {
995  rcode = RLM_MODULE_FAIL;
996  goto finish;
997  }
998 
999  hcode = rest_get_handle_code(handle);
1000  if (hcode >= 500) {
1001  rcode = RLM_MODULE_FAIL;
1002  } else if (hcode == 204) {
1003  rcode = RLM_MODULE_OK;
1004  } else if ((hcode >= 200) && (hcode < 300)) {
1005  ret = rest_response_decode(inst, section, request, handle);
1006  if (ret < 0) rcode = RLM_MODULE_FAIL;
1007  else if (ret == 0) rcode = RLM_MODULE_OK;
1008  else rcode = RLM_MODULE_UPDATED;
1009  } else {
1010  rcode = RLM_MODULE_INVALID;
1011  }
1012 
1013  switch (rcode) {
1014  case RLM_MODULE_INVALID:
1015  case RLM_MODULE_FAIL:
1016  rest_response_error(request, handle);
1017  break;
1018 
1019  default:
1020  rest_response_debug(request, handle);
1021  break;
1022  }
1023 
1024 finish:
1025  rest_slab_release(handle);
1026 
1027  RETURN_MODULE_RCODE(rcode);
1028 }
1029 
1030 /*
1031  * Send post-auth info to a REST API endpoint
1032  */
1033 static unlang_action_t CC_HINT(nonnull) mod_post_auth(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
1034 {
1036  rlm_rest_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_rest_thread_t);
1037  rlm_rest_section_t const *section = &inst->post_auth;
1038 
1039  void *handle;
1040  int ret;
1041 
1042  if (!section->name) {
1043  RDEBUG2("No post-auth section configured");
1045  }
1046 
1047  handle = rest_slab_reserve(t->slab);
1048  if (!handle) RETURN_MODULE_FAIL;
1049 
1050  ret = rlm_rest_perform(mctx, section, handle, request);
1051  if (ret < 0) {
1052  rest_slab_release(handle);
1053 
1055  }
1056 
1057  return unlang_module_yield(request, mod_post_auth_result, rest_io_module_signal, ~FR_SIGNAL_CANCEL, handle);
1058 }
1059 
1061  rlm_rest_section_t *config, char const *name)
1062 {
1063  CONF_SECTION *cs, *request_cs;
1064 
1065  cs = cf_section_find(parent, name, NULL);
1066  if (!cs) {
1067  config->name = NULL;
1068  return 0;
1069  }
1070 
1071  if (cf_section_rules_push(cs, config_items) < 0) return -1;
1072  if (cf_section_parse(inst, config, cs) < 0) {
1073  config->name = NULL;
1074  return -1;
1075  }
1076 
1077  /*
1078  * Add section name (Maybe add to headers later?).
1079  */
1080  config->name = name;
1081 
1082  /*
1083  * Convert HTTP method auth and body type strings into their integer equivalents.
1084  */
1085  if ((config->request.auth != REST_HTTP_AUTH_NONE) && !http_curl_auth[config->request.auth]) {
1086  cf_log_err(cs, "Unsupported HTTP auth type \"%s\", check libcurl version, OpenSSL build "
1087  "configuration, then recompile this module",
1088  fr_table_str_by_value(http_auth_table, config->request.auth, "<INVALID>"));
1089 
1090  return -1;
1091  }
1092  config->request.method = fr_table_value_by_str(http_method_table, config->request.method_str, REST_HTTP_METHOD_CUSTOM);
1093 
1094  /*
1095  * Custom hackery to figure out if data was set we can't do it any other way because we can't
1096  * parse the tmpl_t except within a call_env.
1097  *
1098  * We have custom body data so we set REST_HTTP_BODY_CUSTOM, but also need to try and
1099  * figure out what content-type to use. So if they've used the canonical form we
1100  * need to convert it back into a proper HTTP content_type value.
1101  */
1102  if ((strcmp(name, "xlat") == 0) || ((request_cs = cf_section_find(cs, "request", NULL)) && cf_pair_find(request_cs, "data"))) {
1103  http_body_type_t body;
1104 
1105  config->request.body = REST_HTTP_BODY_CUSTOM;
1106 
1108  if (body != REST_HTTP_BODY_UNKNOWN) {
1109  config->request.body_str = fr_table_str_by_value(http_content_type_table, body, config->request.body_str);
1110  }
1111  /*
1112  * We don't have any custom user data, so we need to select the right encoder based
1113  * on the body type.
1114  *
1115  * To make this slightly more/less confusing, we accept both canonical body_types,
1116  * and content_types.
1117  */
1118  } else {
1119  config->request.body = fr_table_value_by_str(http_body_type_table, config->request.body_str, REST_HTTP_BODY_UNKNOWN);
1120  if (config->request.body == REST_HTTP_BODY_UNKNOWN) {
1122  }
1123 
1124  if (config->request.body == REST_HTTP_BODY_UNKNOWN) {
1125  cf_log_err(cs, "Unknown HTTP body type '%s'", config->request.body_str);
1126  return -1;
1127  }
1128 
1129  switch (http_body_type_supported[config->request.body]) {
1131  cf_log_err(cs, "Unsupported HTTP body type \"%s\", please submit patches",
1132  config->request.body_str);
1133  return -1;
1134 
1136  cf_log_err(cs, "Invalid HTTP body type. \"%s\" is not a valid web API data "
1137  "markup format", config->request.body_str);
1138  return -1;
1139 
1141  cf_log_err(cs, "Unavailable HTTP body type. \"%s\" is not available in this "
1142  "build", config->request.body_str);
1143  return -1;
1144 
1145  default:
1146  break;
1147  }
1148  }
1149 
1150  if (config->response.force_to_str) {
1151  config->response.force_to = fr_table_value_by_str(http_body_type_table, config->response.force_to_str, REST_HTTP_BODY_UNKNOWN);
1152  if (config->response.force_to == REST_HTTP_BODY_UNKNOWN) {
1153  config->response.force_to = fr_table_value_by_str(http_content_type_table, config->response.force_to_str, REST_HTTP_BODY_UNKNOWN);
1154  }
1155 
1156  if (config->response.force_to == REST_HTTP_BODY_UNKNOWN) {
1157  cf_log_err(cs, "Unknown forced response body type '%s'", config->response.force_to_str);
1158  return -1;
1159  }
1160 
1161  switch (http_body_type_supported[config->response.force_to]) {
1163  cf_log_err(cs, "Unsupported forced response body type \"%s\", please submit patches",
1164  config->response.force_to_str);
1165  return -1;
1166 
1168  cf_log_err(cs, "Invalid HTTP forced response body type. \"%s\" is not a valid web API data "
1169  "markup format", config->response.force_to_str);
1170  return -1;
1171 
1172  default:
1173  break;
1174  }
1175  }
1176 
1177  return 0;
1178 }
1179 
1181 {
1182  curl_easy_cleanup(randle->candle);
1183  return 0;
1184 }
1185 
1186 /** Cleans up after a REST request.
1187  *
1188  * Resets all options associated with a CURL handle, and frees any headers
1189  * associated with it.
1190  *
1191  * Calls rest_read_ctx_free and rest_response_free to free any memory used by
1192  * context data.
1193  *
1194  * @param[in] randle to cleanup.
1195  * @param[in] uctx unused.
1196  */
1197 static int rest_request_cleanup(fr_curl_io_request_t *randle, UNUSED void *uctx)
1198 {
1199  rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1200  CURL *candle = randle->candle;
1201 
1202  /*
1203  * Clear any previously configured options
1204  */
1205  curl_easy_reset(candle);
1206 
1207  /*
1208  * Free header list
1209  */
1210  if (ctx->headers != NULL) {
1211  curl_slist_free_all(ctx->headers);
1212  ctx->headers = NULL;
1213  }
1214 
1215  /*
1216  * Free response data
1217  */
1218  TALLOC_FREE(ctx->body);
1219  TALLOC_FREE(ctx->response.buffer);
1220  TALLOC_FREE(ctx->request.encoder);
1221  TALLOC_FREE(ctx->response.decoder);
1222  ctx->response.header = NULL; /* This is owned by the parsed call env and must not be freed */
1223 
1224  randle->request = NULL;
1225  return 0;
1226 }
1227 
1228 static int rest_conn_alloc(fr_curl_io_request_t *randle, void *uctx)
1229 {
1230  rlm_rest_t const *inst = talloc_get_type_abort(uctx, rlm_rest_t);
1231  rlm_rest_curl_context_t *curl_ctx = NULL;
1232 
1233  randle->candle = curl_easy_init();
1234  if (unlikely(!randle->candle)) {
1235  fr_strerror_printf("Unable to initialise CURL handle");
1236  return -1;
1237  }
1238 
1239  MEM(curl_ctx = talloc_zero(randle, rlm_rest_curl_context_t));
1240  curl_ctx->headers = NULL;
1241  curl_ctx->request.instance = inst;
1242  curl_ctx->response.instance = inst;
1243 
1244  randle->uctx = curl_ctx;
1245  talloc_set_destructor(randle, _mod_conn_free);
1246 
1247  rest_slab_element_set_destructor(randle, rest_request_cleanup, NULL);
1248 
1249  return 0;
1250 }
1251 
1252 /** Create a thread specific multihandle
1253  *
1254  * Easy handles representing requests are added to the curl multihandle
1255  * with the multihandle used for mux/demux.
1256  *
1257  * @param[in] mctx Thread instantiation data.
1258  * @return
1259  * - 0 on success.
1260  * - -1 on failure.
1261  */
1263 {
1264  rlm_rest_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_rest_t);
1265  rlm_rest_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_rest_thread_t);
1266  fr_curl_handle_t *mhandle;
1267 
1268  t->inst = inst;
1269 
1270  if (!(t->slab = rest_slab_list_alloc(t, mctx->el, &inst->conn_config.reuse,
1271  rest_conn_alloc, NULL, inst, false, false))) {
1272  ERROR("Connection handle pool instantiation failed");
1273  return -1;
1274  }
1275 
1276  mhandle = fr_curl_io_init(t, mctx->el, inst->multiplex);
1277  if (!mhandle) return -1;
1278 
1279  t->mhandle = mhandle;
1280 
1281  return 0;
1282 }
1283 
1284 /** Cleanup all outstanding requests associated with this thread
1285  *
1286  * Destroys all curl easy handles, and then the multihandle associated
1287  * with this thread.
1288  *
1289  * @param[in] mctx data to destroy.
1290  * @return 0
1291  */
1293 {
1294  rlm_rest_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_rest_thread_t);
1295 
1296  talloc_free(t->mhandle); /* Ensure this is shutdown before the pool */
1297  talloc_free(t->slab);
1298 
1299  return 0;
1300 }
1301 
1302 /*
1303  * Do any per-module initialization that is separate to each
1304  * configured instance of the module. e.g. set up connections
1305  * to external databases, read configuration files, set up
1306  * dictionary entries, etc.
1307  *
1308  * If configuration information is given in the config section
1309  * that must be referenced in later calls, store a handle to it
1310  * in *instance otherwise put a null pointer there.
1311  */
1312 static int instantiate(module_inst_ctx_t const *mctx)
1313 {
1314  rlm_rest_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_rest_t);
1315  CONF_SECTION *conf = mctx->inst->conf;
1316 
1317  inst->xlat.request.method_str = "GET";
1318  inst->xlat.request.body = REST_HTTP_BODY_NONE;
1319  inst->xlat.request.body_str = "application/x-www-form-urlencoded";
1320  inst->xlat.response.force_to_str = "plain";
1321 
1322  /*
1323  * Parse sub-section configs.
1324  */
1325  if (
1326  (parse_sub_section(inst, conf, xlat_config, &inst->xlat, "xlat") < 0) ||
1327  (parse_sub_section(inst, conf, section_config, &inst->authorize,
1329  (parse_sub_section(inst, conf, section_config, &inst->authenticate,
1331  (parse_sub_section(inst, conf, section_config, &inst->accounting,
1333  (parse_sub_section(inst, conf, section_config, &inst->post_auth,
1335  {
1336  return -1;
1337  }
1338 
1339  inst->conn_config.reuse.num_children = 1;
1340  inst->conn_config.reuse.child_pool_size = sizeof(rlm_rest_curl_context_t);
1341 
1342  return 0;
1343 }
1344 
1345 static int mod_bootstrap(module_inst_ctx_t const *mctx)
1346 {
1347  rlm_rest_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_rest_t);
1348  xlat_t *xlat;
1349 
1353 
1354  return 0;
1355 }
1356 
1357 /** Initialises libcurl.
1358  *
1359  * Allocates global variables and memory required for libcurl to function.
1360  * MUST only be called once per module instance.
1361  *
1362  * mod_unload must not be called if mod_load fails.
1363  *
1364  * @see mod_unload
1365  *
1366  * @return
1367  * - 0 on success.
1368  * - -1 on failure.
1369  */
1370 static int mod_load(void)
1371 {
1372  /* developer sanity */
1374 
1375 #ifdef HAVE_JSON
1377 #endif
1378 
1379  return 0;
1380 }
1381 
1382 /*
1383  * The module name should be the only globally exported symbol.
1384  * That is, everything else should be 'static'.
1385  *
1386  * If the module needs to temporarily modify it's instantiation
1387  * data, the type should be changed to MODULE_TYPE_THREAD_UNSAFE.
1388  * The server will then take care of ensuring that the module
1389  * is single-threaded.
1390  */
1391 extern module_rlm_t rlm_rest;
1393  .common = {
1394  .magic = MODULE_MAGIC_INIT,
1395  .name = "rest",
1396  .flags = MODULE_TYPE_THREAD_SAFE,
1397  .inst_size = sizeof(rlm_rest_t),
1398  .thread_inst_size = sizeof(rlm_rest_thread_t),
1399  .config = module_config,
1400  .onload = mod_load,
1401  .bootstrap = mod_bootstrap,
1402  .instantiate = instantiate,
1403  .thread_instantiate = mod_thread_instantiate,
1404  .thread_detach = mod_thread_detach
1405  },
1406  .method_names = (module_method_name_t[]){
1407  /*
1408  * Hack to support old configurations
1409  */
1410  { .name1 = "authorize", .name2 = CF_IDENT_ANY, .method = mod_authorize, .method_env = &rest_call_env_authorize },
1411 
1412  { .name1 = "recv", .name2 = "accounting-request", .method = mod_accounting, .method_env = &rest_call_env_accounting },
1413  { .name1 = "recv", .name2 = CF_IDENT_ANY, .method = mod_authorize, .method_env = &rest_call_env_authorize },
1414  { .name1 = "accounting", .name2 = CF_IDENT_ANY, .method = mod_accounting, .method_env = &rest_call_env_accounting },
1415  { .name1 = "authenticate", .name2 = CF_IDENT_ANY, .method = mod_authenticate, .method_env = &rest_call_env_authenticate },
1416  { .name1 = "send", .name2 = CF_IDENT_ANY, .method = mod_post_auth, .method_env = &rest_call_env_post_auth },
1418  }
1419 };
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
#define fr_atexit_thread_local(_name, _free, _uctx)
Definition: atexit.h:221
#define RCSID(id)
Definition: build.h:444
#define L(_str)
Helper for initialising arrays of string literals.
Definition: build.h:207
#define unlikely(_x)
Definition: build.h:378
#define UNUSED
Definition: build.h:313
#define NUM_ELEMENTS(_t)
Definition: build.h:335
call_env_parser_t const * env
Parsing rules for call method env.
Definition: call_env.h:223
@ CALL_ENV_FLAG_NONE
Definition: call_env.h:72
#define FR_CALL_ENV_SUBSECTION(_name, _ident2, _flags, _subcs)
Specify a call_env_parser_t which defines a nested subsection.
Definition: call_env.h:378
Per method call config.
Definition: call_env.h:171
int cf_section_parse(TALLOC_CTX *ctx, void *base, CONF_SECTION *cs)
Parse a configuration section into user-supplied variables.
Definition: cf_parse.c:985
int cf_table_parse_int(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
Generic function for parsing conf pair values as int.
Definition: cf_parse.c:1474
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#define FR_CONF_DEPRECATED(_name, _struct, _field)
conf_parser_t entry which raises an error if a matching CONF_PAIR is found
Definition: cf_parse.h:385
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition: cf_parse.h:268
#define cf_section_rules_push(_cs, _rule)
Definition: cf_parse.h:658
#define FR_CONF_OFFSET_SUBSECTION(_name, _flags, _struct, _field, _subcs)
conf_parser_t which populates a sub-struct using a CONF_SECTION
Definition: cf_parse.h:297
#define FR_CONF_OFFSET_TYPE_FLAGS(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition: cf_parse.h:241
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:563
Common header for all CONF_* types.
Definition: cf_priv.h:49
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:89
CONF_PAIR * cf_item_to_pair(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_PAIR.
Definition: cf_util.c:629
CONF_SECTION * cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2)
Find a CONF_SECTION with name1 and optionally name2.
Definition: cf_util.c:970
CONF_PAIR * cf_pair_find(CONF_SECTION const *cs, char const *attr)
Search for a CONF_PAIR with a specific name.
Definition: cf_util.c:1356
char const * cf_pair_value(CONF_PAIR const *pair)
Return the value of a CONF_PAIR.
Definition: cf_util.c:1511
#define cf_log_err(_cf, _fmt,...)
Definition: cf_util.h:265
#define CF_IDENT_ANY
Definition: cf_util.h:78
@ MOD_POST_AUTH
7 methods index for postauth section.
Definition: components.h:37
@ MOD_AUTHORIZE
1 methods index for authorize section.
Definition: components.h:34
@ MOD_ACCOUNTING
3 methods index for accounting section.
Definition: components.h:36
@ MOD_AUTHENTICATE
0 methods index for authenticate section.
Definition: components.h:33
fr_curl_handle_t * fr_curl_io_init(TALLOC_CTX *ctx, fr_event_list_t *el, bool multiplex)
bool extract_cert_attrs
Definition: base.h:119
request_t * request
Current request.
Definition: base.h:104
void * uctx
Private data for the module using the API.
Definition: base.h:105
int fr_curl_io_request_enqueue(fr_curl_handle_t *mhandle, request_t *request, fr_curl_io_request_t *creq)
Sends a request using libcurl.
Definition: io.c:471
CURL * candle
Request specific handle.
Definition: base.h:102
Uctx data for timer and I/O functions.
Definition: base.h:91
Structure representing an individual request being passed to curl for processing.
Definition: base.h:101
#define CURL_URI_SAFE_FOR
safe for value suitable for all users of the curl library
Definition: xlat.h:36
static int fr_dcursor_insert(fr_dcursor_t *cursor, void *v)
Insert directly after the current item.
Definition: dcursor.h:434
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
static fr_time_delta_t timeout
Definition: dhcpclient.c:54
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
static fr_slen_t in
Definition: dict.h:645
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
Test enumeration values.
Definition: dict_test.h:92
dl_module_inst_t const * dl_module_instance_by_data(void const *data)
Lookup a dl_module_inst_t via instance data.
Definition: dl_module.c:215
void *_CONST data
Module instance's parsed configuration.
Definition: dl_module.h:165
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:65
CONF_SECTION *_CONST conf
Module's instance configuration.
Definition: dl_module.h:166
#define GLOBAL_LIB_TERMINATOR
Definition: global_lib.h:51
Structure to define how to initialise libraries with global configuration.
Definition: global_lib.h:38
static xlat_action_t rest_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
Simple xlat to read text data from a URL.
Definition: rlm_rest.c:512
void fr_json_version_print(void)
Print JSON-C version.
Definition: json.c:459
int fr_curl_response_certinfo(request_t *request, fr_curl_io_request_t *randle)
Definition: base.c:114
global_lib_autoinst_t fr_curl_autoinst
Definition: base.c:342
conf_parser_t fr_curl_conn_config[]
Definition: base.c:84
CURL * fr_curl_tmp_handle(void)
Return a thread local curl easy handle.
Definition: base.c:222
conf_parser_t fr_curl_tls_config[]
Definition: base.c:56
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:443
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition: log.h:335
#define RPEDEBUG(fmt,...)
Definition: log.h:376
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:430
talloc_free(reap)
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_UINT32
32 Bit unsigned integer.
Definition: merged_model.c:99
@ FR_TYPE_SIZE
Unsigned integer capable of representing any memory address on the local system.
Definition: merged_model.c:115
@ FR_TYPE_GROUP
A grouping of other attributes.
Definition: merged_model.c:124
long int ssize_t
Definition: merged_model.c:24
void * env_data
Per call environment data.
Definition: module_ctx.h:44
void * thread
Thread specific instance data.
Definition: module_ctx.h:43
void * rctx
Resume ctx that a module previously set.
Definition: module_ctx.h:45
fr_event_list_t * el
Event list to register any IO handlers and timers against.
Definition: module_ctx.h:63
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:52
void * thread
Thread instance data.
Definition: module_ctx.h:62
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:42
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:59
#define MODULE_CTX(_dl_inst, _thread, _env_data, _rctx)
Wrapper to create a module_ctx_t as a compound literal.
Definition: module_ctx.h:123
Temporary structure to hold arguments for module calls.
Definition: module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition: module_ctx.h:51
Temporary structure to hold arguments for thread_instantiation calls.
Definition: module_ctx.h:58
Specifies a module method identifier.
Definition: module_method.c:36
char const * section_type_value[MOD_COUNT]
Definition: module_rlm.c:64
module_t common
Common fields presented by all modules.
Definition: module_rlm.h:37
void rest_io_module_signal(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
Handle asynchronous cancellation of a request.
Definition: io.c:33
void rest_io_xlat_signal(xlat_ctx_t const *xctx, request_t *request, fr_signal_t action)
Handle asynchronous cancellation of a request.
Definition: io.c:56
static const conf_parser_t config[]
Definition: base.c:188
#define pair_update_request(_attr, _da)
Definition: radclient-ng.c:60
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
#define RDEBUG(fmt,...)
Definition: radclient.h:53
static rs_t * conf
Definition: radsniff.c:53
#define RETURN_MODULE_NOOP
Definition: rcode.h:62
#define RETURN_MODULE_RCODE(_rcode)
Definition: rcode.h:64
#define RETURN_MODULE_INVALID
Definition: rcode.h:59
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
fr_table_num_sorted_t const http_auth_table[]
Definition: rest.c:160
int rest_request_config(module_ctx_t const *mctx, rlm_rest_section_t const *section, request_t *request, fr_curl_io_request_t *randle, http_method_t method, http_body_type_t type, char const *uri, char const *body_data)
Configures request curlopts.
Definition: rest.c:1726
fr_table_num_sorted_t const http_body_type_table[]
Conversion table for type config values.
Definition: rest.c:145
fr_table_num_sorted_t const http_method_table[]
Conversion table for method config values.
Definition: rest.c:126
size_t rest_get_handle_data(char const **out, fr_curl_io_request_t *randle)
Extracts pointer to buffer containing response data.
Definition: rest.c:1625
void rest_response_debug(request_t *request, fr_curl_io_request_t *handle)
Print out the response text.
Definition: rest.c:1568
fr_table_num_sorted_t const http_content_type_table[]
Conversion table for "Content-Type" header values.
Definition: rest.c:188
const unsigned long http_curl_auth[REST_HTTP_AUTH_NUM_ENTRIES]
Definition: rest.c:100
const http_body_type_t http_body_type_supported[REST_HTTP_BODY_NUM_ENTRIES]
Table of encoder/decoder support.
Definition: rest.c:51
void rest_response_error(request_t *request, fr_curl_io_request_t *handle)
Print out the response text as error lines.
Definition: rest.c:1543
int rest_response_decode(rlm_rest_t const *instance, rlm_rest_section_t const *section, request_t *request, fr_curl_io_request_t *randle)
Sends the response to the correct decode function.
Definition: rest.c:2072
Function prototypes and datatypes for the REST (HTTP) transport.
rlm_rest_t const * instance
This instance of rlm_rest.
Definition: rest.h:229
struct curl_slist * headers
Any HTTP headers which will be sent with the request.
Definition: rest.h:253
tmpl_t * header
Where to create pairs representing HTTP response headers.
Definition: rest.h:243
#define rest_get_handle_code(_handle)
Definition: rest.h:316
char * buffer
Raw incoming HTTP data.
Definition: rest.h:235
fr_curl_handle_t * mhandle
Thread specific multi handle.
Definition: rest.h:183
struct rlm_rest_call_env_t::@161 request
char * body
Pointer to the buffer which contains body data/ Only used when not performing chunked encoding.
Definition: rest.h:256
fr_curl_tls_t tls
Definition: rest.h:145
http_method_t
Definition: rest.h:43
@ REST_HTTP_METHOD_UNKNOWN
Definition: rest.h:44
@ REST_HTTP_METHOD_CUSTOM
Must always come last, should not be in method table.
Definition: rest.h:50
@ REST_HTTP_METHOD_GET
Definition: rest.h:45
http_body_type_t
Definition: rest.h:53
@ REST_HTTP_BODY_INVALID
Definition: rest.h:57
@ REST_HTTP_BODY_UNSUPPORTED
Definition: rest.h:55
@ REST_HTTP_BODY_CUSTOM
Definition: rest.h:59
@ REST_HTTP_BODY_NUM_ENTRIES
Definition: rest.h:66
@ REST_HTTP_BODY_UNKNOWN
Definition: rest.h:54
@ REST_HTTP_BODY_NONE
Definition: rest.h:58
@ REST_HTTP_BODY_UNAVAILABLE
Definition: rest.h:56
char const * method_str
The string version of the HTTP method.
Definition: rest.h:111
rlm_rest_section_request_t request
Request configuration.
Definition: rest.h:142
rlm_rest_response_t response
Response context data.
Definition: rest.h:260
void * decoder
Decoder specific data.
Definition: rest.h:246
void * encoder
Encoder specific data.
Definition: rest.h:221
rlm_rest_request_t request
Request context data.
Definition: rest.h:259
http_method_t method
What HTTP method should be used, GET, POST etc...
Definition: rest.h:112
rlm_rest_section_t section
Our mutated section config.
Definition: rest.h:267
rlm_rest_t const * inst
Instance of rlm_rest.
Definition: rest.h:181
char const * name
Section name.
Definition: rest.h:138
@ REST_HTTP_AUTH_NONE
Definition: rest.h:71
rest_slab_list_t * slab
Slab list for connection handles.
Definition: rest.h:182
http_body_type_t body
What encoding type should be used.
Definition: rest.h:115
fr_curl_io_request_t * handle
curl easy handle servicing our request.
Definition: rest.h:268
rlm_rest_t const * instance
This instance of rlm_rest.
Definition: rest.h:211
Thread specific rlm_rest instance data.
Definition: rest.h:180
Stores the state of a yielded xlat.
Definition: rest.h:266
static char const * name
static const call_env_method_t rest_call_env_xlat
Definition: rlm_rest.c:247
static int rlm_rest_status_update(request_t *request, void *handle)
Update the status attribute.
Definition: rlm_rest.c:309
static int mod_load(void)
Initialises libcurl.
Definition: rlm_rest.c:1370
static fr_dict_attr_t const * attr_user_password
Definition: rlm_rest.c:283
static int rest_conn_alloc(fr_curl_io_request_t *randle, void *uctx)
Definition: rlm_rest.c:1228
static int _rest_uri_part_escape_uctx_free(void *uctx)
Definition: rlm_rest.c:334
static const conf_parser_t xlat_config[]
Definition: rlm_rest.c:152
static xlat_action_t rest_xlat_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, UNUSED fr_value_box_list_t *in)
Definition: rlm_rest.c:422
fr_dict_t const * dict_freeradius
Definition: rlm_rest.c:269
static int rest_request_cleanup(fr_curl_io_request_t *randle, UNUSED void *uctx)
Cleans up after a REST request.
Definition: rlm_rest.c:1197
static int rlm_rest_perform(module_ctx_t const *mctx, rlm_rest_section_t const *section, fr_curl_io_request_t *randle, request_t *request)
Definition: rlm_rest.c:392
static unlang_action_t mod_post_auth_result(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_rest.c:982
static const conf_parser_t xlat_request_config[]
Definition: rlm_rest.c:147
static fr_dict_t const * dict_radius
Definition: rlm_rest.c:270
static fr_table_num_sorted_t const http_negotiation_table[]
Definition: rlm_rest.c:64
static int instantiate(module_inst_ctx_t const *mctx)
Definition: rlm_rest.c:1312
static fr_uri_part_t const rest_uri_parts[]
Definition: rlm_rest.c:55
static unlang_action_t mod_authenticate(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_rest.c:845
static int mod_bootstrap(module_inst_ctx_t const *mctx)
Definition: rlm_rest.c:1345
fr_dict_attr_t const * attr_rest_http_header
Definition: rlm_rest.c:280
fr_dict_autoload_t rlm_rest_dict[]
Definition: rlm_rest.c:273
static const conf_parser_t section_request_config[]
Definition: rlm_rest.c:122
static const conf_parser_t section_config[]
Definition: rlm_rest.c:135
#define SECTION_REQUEST_COMMON
Definition: rlm_rest.c:114
static int rest_uri_part_escape(fr_value_box_t *vb, void *uctx)
#define REST_CALL_ENV_SECTION(_var, _section, _dflt_username, _dflt_password)
Definition: rlm_rest.c:197
static int rest_proxy_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
Definition: rlm_rest.c:95
fr_dict_attr_t const * attr_rest_http_status_code
Definition: rlm_rest.c:281
static unlang_action_t mod_accounting(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_rest.c:955
static unlang_action_t mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_rest.c:734
static size_t http_negotiation_table_len
Definition: rlm_rest.c:88
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
Create a thread specific multihandle.
Definition: rlm_rest.c:1262
module_rlm_t rlm_rest
Definition: rlm_rest.c:1392
fr_dict_attr_t const * attr_rest_http_body
Definition: rlm_rest.c:279
static unlang_action_t mod_authenticate_result(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_rest.c:761
static fr_dict_attr_t const * attr_user_name
Definition: rlm_rest.c:282
static unlang_action_t mod_accounting_result(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_rest.c:904
fr_dict_attr_autoload_t rlm_rest_dict_attr[]
Definition: rlm_rest.c:286
static const conf_parser_t section_response_config[]
Definition: rlm_rest.c:129
static unlang_action_t mod_authorize_result(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_rest.c:648
static const conf_parser_t module_config[]
Definition: rlm_rest.c:164
static int _mod_conn_free(fr_curl_io_request_t *randle)
Definition: rlm_rest.c:1180
static xlat_arg_parser_t const rest_xlat_args[]
Definition: rlm_rest.c:497
static int mod_thread_detach(module_thread_inst_ctx_t const *mctx)
Cleanup all outstanding requests associated with this thread.
Definition: rlm_rest.c:1292
static int parse_sub_section(rlm_rest_t *inst, CONF_SECTION *parent, conf_parser_t const *config_items, rlm_rest_section_t *config, char const *name)
Definition: rlm_rest.c:1060
static void * rest_uri_part_escape_uctx_alloc(UNUSED request_t *request, void const *uctx)
Allocate an escape uctx to pass to fr_uri_escape.
Definition: rlm_rest.c:345
global_lib_autoinst_t const *const rlm_rest_lib[]
Definition: rlm_rest.c:296
char const * rest_no_proxy
Unique pointer used to determine if we should explicitly disable proxying.
Definition: rlm_rest.c:93
static unlang_action_t mod_post_auth(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition: rlm_rest.c:1033
#define FR_SBUFF_TERMS(...)
Initialise a terminal structure with a list of sorted strings.
Definition: sbuff.h:167
@ MODULE_TYPE_THREAD_SAFE
Module is threadsafe.
Definition: module.h:49
#define MODULE_NAME_TERMINATOR
Definition: module.h:135
#define pair_delete_request(_pair_or_da)
Delete a fr_pair_t in the request list.
Definition: pair.h:172
unlang_action_t unlang_module_yield(request_t *request, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition: module.c:575
RETURN_MODULE_FAIL
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition: pair.h:68
#define fr_table_value_by_str(_table, _name, _def)
Convert a string to a value using a sorted or ordered table.
Definition: table.h:134
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition: table.h:253
#define fr_table_value_by_substr(_table, _name, _name_len, _def)
Convert a partial string to a value using an ordered or sorted table.
Definition: table.h:174
An element in a lexicographically sorted array of name to num mappings.
Definition: table.h:45
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition: talloc.c:452
Functions which we wish were included in the standard talloc distribution.
#define talloc_get_type_abort_const
Definition: talloc.h:270
xlat_action_t unlang_xlat_yield(request_t *request, xlat_func_t resume, xlat_func_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition: xlat.c:561
bool required
Argument must be present, and non-empty.
Definition: xlat.h:146
@ XLAT_ARG_VARIADIC_EMPTY_KEEP
Empty argument groups are left alone, and either passed through as empty groups or null boxes.
Definition: xlat.h:137
#define XLAT_ARG_PARSER_TERMINATOR
Definition: xlat.h:166
xlat_action_t
Definition: xlat.h:35
@ XLAT_ACTION_FAIL
An xlat function failed.
Definition: xlat.h:42
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition: xlat.h:41
Definition for a single argument consumend by an xlat function.
Definition: xlat.h:145
int fr_uri_escape_list(fr_value_box_list_t *uri, fr_uri_part_t const *uri_parts, void *uctx)
Parse a list of value boxes representing a URI.
Definition: uri.c:140
#define XLAT_URI_PART_TERMINATOR
Definition: uri.h:66
char const * name
Name of this part of the URI.
Definition: uri.h:47
uctx to pass to fr_uri_escape
Definition: uri.h:60
Definition for a single part of a URI.
Definition: uri.h:46
static fr_slen_t parent
Definition: pair.h:844
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition: strerror.h:64
void fr_value_box_clear_value(fr_value_box_t *data)
Clear/free any existing value.
Definition: value.c:3630
int fr_value_box_strdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted)
Copy a nul terminated string to a fr_value_box_t.
Definition: value.c:3876
int fr_value_box_bstrndup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Copy a string to to a fr_value_box_t.
Definition: value.c:4097
int fr_value_box_list_concat_in_place(TALLOC_CTX *ctx, fr_value_box_t *out, fr_value_box_list_t *list, fr_type_t type, fr_value_box_list_action_t proc_action, bool flatten, size_t max_size)
Concatenate a list of value boxes.
Definition: value.c:5725
@ FR_VALUE_BOX_LIST_FREE
Definition: value.h:214
int nonnull(2, 5))
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition: value.h:619
static size_t char ** out
Definition: value.h:984
void * rctx
Resume context.
Definition: xlat_ctx.h:47
module_ctx_t const * mctx
Synthesised module calling ctx.
Definition: xlat_ctx.h:45
void * env_data
Expanded call env data.
Definition: xlat_ctx.h:46
An xlat calling ctx.
Definition: xlat_ctx.h:42
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
Definition: xlat_func.c:360
void xlat_func_call_env_set(xlat_t *x, call_env_method_t const *env_method)
Register call environment of an xlat.
Definition: xlat_func.c:405
xlat_t * xlat_func_register_module(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx, char const *name, xlat_func_t func, fr_type_t return_type)
Register an xlat function for a module.
Definition: xlat_func.c:274