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