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