The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rest.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: cce613871858ea3db9c8b374e7800ef91b23ba70 $
19 *
20 * @brief Functions and datatypes for the REST (HTTP) transport.
21 * @file rest.c
22 *
23 * @copyright 2012-2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24 */
25RCSID("$Id: cce613871858ea3db9c8b374e7800ef91b23ba70 $")
26
27#define LOG_PREFIX mctx->mi->name
28
29#include <ctype.h>
30#include <string.h>
31#include <time.h>
32
33#include <freeradius-devel/server/base.h>
34#include <freeradius-devel/unlang/call.h>
35#include <freeradius-devel/util/skip.h>
36
37
38#include "rest.h"
39
40/** Table of encoder/decoder support.
41 *
42 * Indexes in this table match the http_body_type_t enum, and should be
43 * updated if additional enum values are added.
44 *
45 * @see http_body_type_t
46 */
48 REST_HTTP_BODY_UNKNOWN, // REST_HTTP_BODY_UNKNOWN
49 REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_UNSUPPORTED
50 REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_UNAVAILABLE
51 REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_INVALID
52 REST_HTTP_BODY_NONE, // REST_HTTP_BODY_NONE
53 REST_HTTP_BODY_CUSTOM, // REST_HTTP_BODY_CUSTOM
54 REST_HTTP_BODY_POST, // REST_HTTP_BODY_POST
55#ifdef HAVE_JSON
56 REST_HTTP_BODY_JSON, // REST_HTTP_BODY_JSON
57#else
59#endif
60 REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_XML
61 REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_YAML
62 REST_HTTP_BODY_INVALID, // REST_HTTP_BODY_HTML
63 REST_HTTP_BODY_PLAIN, // REST_HTTP_BODY_PLAIN
64 REST_HTTP_BODY_PLAIN // REST_HTTP_BODY_CRL
65};
66
67/*
68 * Lib CURL doesn't define symbols for unsupported auth methods
69 */
70#ifndef CURLOPT_TLSAUTH_SRP
71# define CURLOPT_TLSAUTH_SRP 0
72#endif
73#ifndef CURLAUTH_BASIC
74# define CURLAUTH_BASIC 0
75#endif
76#ifndef CURLAUTH_DIGEST
77# define CURLAUTH_DIGEST 0
78#endif
79#ifndef CURLAUTH_DIGEST_IE
80# define CURLAUTH_DIGEST_IE 0
81#endif
82#ifndef CURLAUTH_GSSNEGOTIATE
83# define CURLAUTH_GSSNEGOTIATE 0
84#endif
85#ifndef CURLAUTH_NTLM
86# define CURLAUTH_NTLM 0
87#endif
88#ifndef CURLAUTH_NTLM_WB
89# define CURLAUTH_NTLM_WB 0
90#endif
91
92/*
93 * CURL headers do:
94 *
95 * #define curl_easy_setopt(handle,opt,param) curl_easy_setopt(handle,opt,param)
96 */
110
111/** Conversion table for method config values.
112 *
113 * HTTP verb strings for http_method_t enum values. Used by libcurl in the
114 * status line of the outgoing HTTP header, by rest_response_header for decoding
115 * incoming HTTP responses, and by the configuration parser.
116 *
117 * @note must be kept in sync with http_method_t enum.
118 *
119 * @see http_method_t
120 * @see fr_table_value_by_str
121 * @see fr_table_str_by_value
122 */
124 { L("DELETE"), REST_HTTP_METHOD_DELETE },
125 { L("GET"), REST_HTTP_METHOD_GET },
126 { L("PATCH"), REST_HTTP_METHOD_PATCH },
127 { L("POST"), REST_HTTP_METHOD_POST },
128 { L("PUT"), REST_HTTP_METHOD_PUT },
129 { L("UNKNOWN"), REST_HTTP_METHOD_UNKNOWN }
130};
132
133/** Conversion table for type config values.
134 *
135 * Textual names for http_body_type_t enum values, used by the
136 * configuration parser.
137 *
138 * @see http_body_Type_t
139 * @see fr_table_value_by_str
140 * @see fr_table_str_by_value
141 */
143 { L("crl"), REST_HTTP_BODY_CRL },
144 { L("html"), REST_HTTP_BODY_HTML },
145 { L("invalid"), REST_HTTP_BODY_INVALID },
146 { L("json"), REST_HTTP_BODY_JSON },
147 { L("none"), REST_HTTP_BODY_NONE },
148 { L("plain"), REST_HTTP_BODY_PLAIN },
149 { L("post"), REST_HTTP_BODY_POST },
150 { L("unavailable"), REST_HTTP_BODY_UNAVAILABLE },
151 { L("unknown"), REST_HTTP_BODY_UNKNOWN },
152 { L("unsupported"), REST_HTTP_BODY_UNSUPPORTED },
153 { L("xml"), REST_HTTP_BODY_XML },
154 { L("yaml"), REST_HTTP_BODY_YAML }
155};
157
159 { L("any"), REST_HTTP_AUTH_ANY },
160 { L("basic"), REST_HTTP_AUTH_BASIC },
161 { L("digest"), REST_HTTP_AUTH_DIGEST },
162 { L("digest-ie"), REST_HTTP_AUTH_DIGEST_IE },
163 { L("gss-negotiate"), REST_HTTP_AUTH_GSSNEGOTIATE },
164 { L("none"), REST_HTTP_AUTH_NONE },
165 { L("ntlm"), REST_HTTP_AUTH_NTLM },
166 { L("ntlm-winbind"), REST_HTTP_AUTH_NTLM_WB },
167 { L("safe"), REST_HTTP_AUTH_ANY_SAFE },
168 { L("srp"), REST_HTTP_AUTH_TLS_SRP }
169};
171
172/** Conversion table for "Content-Type" header values.
173 *
174 * Used by rest_response_header for parsing incoming headers.
175 *
176 * Values we expect to see in the 'Content-Type:' header of the incoming
177 * response.
178 *
179 * Some data types (like YAML) do no have standard MIME types defined,
180 * so multiple types, are listed here.
181 *
182 * @see http_body_Type_t
183 * @see fr_table_value_by_str
184 * @see fr_table_str_by_value
185 */
187 { L("application/json"), REST_HTTP_BODY_JSON },
188 { L("application/pkix-crl"), REST_HTTP_BODY_CRL },
189 { L("application/x-pkcs7-crl"), REST_HTTP_BODY_CRL },
190 { L("application/x-www-form-urlencoded"), REST_HTTP_BODY_POST },
191 { L("application/x-yaml"), REST_HTTP_BODY_YAML },
192 { L("application/yaml"), REST_HTTP_BODY_YAML },
193 { L("text/html"), REST_HTTP_BODY_HTML },
194 { L("text/plain"), REST_HTTP_BODY_PLAIN },
195 { L("text/x-yaml"), REST_HTTP_BODY_YAML },
196 { L("text/xml"), REST_HTTP_BODY_XML },
197 { L("text/yaml"), REST_HTTP_BODY_YAML }
198};
200
201/*
202 * Encoder specific structures.
203 * @todo split encoders/decoders into submodules.
204 */
205typedef struct {
206 char const *start; //!< Start of the buffer.
207 char const *p; //!< how much text we've sent so far.
208 size_t len; //!< Length of data
210
211#ifdef HAVE_JSON
212/** Flags to control the conversion of JSON values to fr_pair_ts.
213 *
214 * These fields are set when parsing the expanded format for value pairs in
215 * JSON, and control how json_pair_alloc_leaf and json_pair_alloc convert the JSON
216 * value, and move the new fr_pair_t into an attribute list.
217 *
218 * @see json_pair_alloc
219 * @see json_pair_alloc_leaf
220 */
221typedef struct {
222 int do_xlat; //!< If true value will be expanded with xlat.
223 int is_json; //!< If true value will be inserted as raw JSON
224 // (multiple values not supported).
225 fr_token_t op; //!< The operator that determines how the new VP
226 // is processed. @see fr_tokens_table
228#endif
229
230/** Frees a libcurl handle, and any additional memory used by context data.
231 *
232 * @param[in] randle fr_curl_io_request_t to close and free.
233 * @return returns true.
234 */
236{
237 curl_easy_cleanup(randle->candle);
238
239 return 0;
240}
241
242/** Creates a new connection handle for use by the FR connection API.
243 *
244 * Matches the fr_pool_connection_create_t function prototype, is passed to
245 * fr_pool_init, and called when a new connection is required by the
246 * connection pool API.
247 *
248 * Creates an instances of fr_curl_io_request_t, and rlm_rest_curl_context_t
249 * which hold the context data required for generating requests and parsing
250 * responses.
251 *
252 * @see fr_pool_init
253 * @see fr_pool_connection_create_t
254 * @see connection.c
255 */
256void *rest_mod_conn_create(TALLOC_CTX *ctx, void *instance, UNUSED fr_time_delta_t timeout)
257{
259
260 fr_curl_io_request_t *randle = NULL;
261 rlm_rest_curl_context_t *curl_ctx = NULL;
262
263 /*
264 * Allocate memory for the connection handle abstraction.
265 */
266 randle = fr_curl_io_request_alloc(ctx);
267 if (!randle) return NULL;
268
269 MEM(curl_ctx = talloc_zero(randle, rlm_rest_curl_context_t));
270
271 curl_ctx->headers = NULL; /* CURL needs this to be NULL */
272 curl_ctx->request.instance = inst;
273 curl_ctx->response.instance = inst;
274
275 randle->uctx = curl_ctx;
276 talloc_set_destructor(randle, _mod_conn_free);
277
278 return randle;
279}
280
281/** Copies a pre-expanded xlat string to the output buffer
282 *
283 * @param[out] out Char buffer to write encoded data to.
284 * @param[in] size Multiply by nmemb to get the length of ptr.
285 * @param[in] nmemb Multiply by size to get the length of ptr.
286 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
287 * @return
288 * - Length of data (including NULL) written to ptr.
289 * - 0 if no more data to write.
290 */
291static size_t rest_encode_custom(void *out, size_t size, size_t nmemb, void *userdata)
292{
293 rlm_rest_request_t *ctx = userdata;
295
296 size_t freespace = (size * nmemb) - 1;
297 size_t len;
298 size_t to_copy;
299
300 /*
301 * Special case for empty body
302 */
303 if (data->len == 0) return 0;
304
305 /*
306 * If len > 0 then we must have these set.
307 */
308 fr_assert(data->start);
309 fr_assert(data->p);
310
311 to_copy = data->len - (data->p - data->start);
312 len = to_copy > freespace ? freespace : to_copy;
313 if (len == 0) return 0;
314
315 memcpy(out, data->p, len);
316 data->p += len;
317
318 return len;
319}
320
321/** Encodes fr_pair_t linked list in POST format
322 *
323 * This is a stream function matching the rest_read_t prototype. Multiple
324 * successive calls will return additional encoded fr_pair_ts.
325 * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
326 * will be written to the ptr buffer.
327 *
328 * POST request format is:
329 * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
330 *
331 * All attributes and values are url encoded. There is currently no support for
332 * nested attributes, or attribute qualifiers.
333 *
334 * Nested attributes may be added in the future using
335 * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
336 * to denotate nesting.
337 *
338 * Requires libcurl for url encoding.
339 *
340 * @see rest_decode_post
341 *
342 * @param[out] out Char buffer to write encoded data to.
343 * @param[in] size Multiply by nmemb to get the length of ptr.
344 * @param[in] nmemb Multiply by size to get the length of ptr.
345 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
346 * @return
347 * - Length of data (including NULL) written to ptr.
348 * - 0 if no more data to write.
349 */
350static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userdata)
351{
352 rlm_rest_request_t *ctx = userdata;
353 request_t *request = ctx->request; /* Used by RDEBUG */
354 fr_pair_t *vp;
355
356 size_t len = 0;
357 ssize_t slen;
358 size_t freespace = (size * nmemb) - 1;
359
360 char *p = out; /* Position in buffer */
361 char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
362 char *escaped; /* Pointer to current URL escaped data */
363
364 /* Allow manual chunking */
365 if ((ctx->chunk) && (ctx->chunk <= freespace)) freespace = (ctx->chunk - 1);
366
367 if (ctx->state == READ_STATE_END) return 0;
368
369 /* Post data requires no headers */
371
372 while (freespace > 0) {
374 if (!vp) {
375 ctx->state = READ_STATE_END;
376
377 break;
378 }
379
380 RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
381
382 if (ctx->state == READ_STATE_ATTR_BEGIN) {
383 escaped = curl_escape(vp->da->name, 0);
384 if (!escaped) {
385 REDEBUG("Failed escaping string \"%s\"", vp->da->name);
386 return 0;
387 }
388
389 len = strlen(escaped);
390 if (freespace < (1 + len)) {
391 curl_free(escaped);
392 /*
393 * Cleanup for error conditions
394 */
395 no_space:
396 *encoded = '\0';
397
398 len = encoded - (char *)out;
399
400 RDEBUG3("POST Data: %pV", fr_box_strvalue_len(out, len));
401
402 /*
403 * The buffer wasn't big enough to encode a single attribute chunk.
404 */
405 if (len == 0) {
406 REDEBUG("Failed encoding attribute");
407 } else {
408 RDEBUG3("Returning %zd bytes of POST data "
409 "(buffer full or chunk exceeded)", len);
410 }
411
412 return len;
413 }
414
415 len = snprintf(p, freespace, "%s=", escaped);
416 curl_free(escaped);
417 p += len;
418 freespace -= len;
419
420 /*
421 * We wrote the attribute header, record progress.
422 */
423 encoded = p;
425 }
426
427 /*
428 * Write out single attribute string.
429 */
430 slen = fr_pair_print_value_quoted(&FR_SBUFF_OUT(p, freespace), vp, T_BARE_WORD);
431 if (slen < 0) return 0;
432
433 RINDENT();
434 RDEBUG3("Length : %zd", (size_t)slen);
435 REXDENT();
436 if (slen > 0) {
437 escaped = curl_escape(p, (size_t)slen);
438 if (!escaped) {
439 REDEBUG("Failed escaping string \"%s\"", vp->da->name);
440 return 0;
441 }
442 len = strlen(escaped);
443
444 if (freespace < len) {
445 curl_free(escaped);
446 goto no_space;
447 }
448
449 len = strlcpy(p, escaped, len + 1);
450
451 curl_free(escaped);
452
453 RINDENT();
454 RDEBUG3("Value : %s", p);
455 REXDENT();
456
457 p += len;
458 freespace -= len;
459 }
460
461 /*
462 * there are more attributes, insert a separator
463 */
464 if (fr_dcursor_next(&ctx->cursor)) {
465 if (freespace < 1) goto no_space;
466 *p++ = '&';
467 freespace--;
468 }
469
470 /*
471 * We wrote one full attribute value pair, record progress.
472 */
473 encoded = p;
474
476 }
477
478 *p = '\0';
479
480 len = p - (char *)out;
481
482 RDEBUG3("POST Data: %s", (char *)out);
483 RDEBUG3("Returning %zd bytes of POST data", len);
484
485 return len;
486}
487
488#ifdef HAVE_JSON
489/** Encodes fr_pair_t linked list in JSON format
490 *
491 * This is a stream function matching the rest_read_t prototype. Multiple
492 * successive calls will return additional encoded fr_pair_ts.
493 *
494 * Only complete attribute headers
495 * @verbatim "<name>":{"type":"<type>","value":[' @endverbatim
496 * and complete attribute values will be written to ptr.
497 *
498 * If an attribute occurs multiple times in the request the attribute values
499 * will be concatenated into a single value array.
500 *
501 * JSON request format is:
502@verbatim
503{
504 "<attribute0>":{
505 "type":"<type0>",
506 "value":[<value0>,<value1>,<valueN>]
507 },
508 "<attribute1>":{
509 "type":"<type1>",
510 "value":[...]
511 },
512 "<attributeN>":{
513 "type":"<typeN>",
514 "value":[...]
515 },
516}
517@endverbatim
518 *
519 * @param[out] out Char buffer to write encoded data to.
520 * @param[in] size Multiply by nmemb to get the length of ptr.
521 * @param[in] nmemb Multiply by size to get the length of ptr.
522 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
523 * @return
524 * - Length of data (including NULL) written to ptr.
525 * - 0 if no more data to write.
526 */
527static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userdata)
528{
529 rlm_rest_request_t *ctx = userdata;
530 request_t *request = ctx->request;
532
533 size_t freespace = (size * nmemb) - 1; /* account for the \0 byte here */
534 size_t len;
535 size_t to_copy;
536 const char *encoded;
537
538 fr_assert(freespace > 0);
539
540 if (ctx->state == READ_STATE_INIT) {
541 encoded = fr_json_afrom_pair_list(data, &request->request_pairs, NULL);
542 if (!encoded) return -1;
543
544 data->start = data->p = encoded;
545 data->len = strlen(encoded);
546
547 RDEBUG3("JSON Data: %s", encoded);
548 RDEBUG3("Returning %zd bytes of JSON data", data->len);
549
551 }
552
553 to_copy = data->len - (data->p - data->start);
554 len = to_copy > freespace ? freespace : to_copy;
555
556 if (len == 0) return 0;
557
558 memcpy(out, data->p, len);
559 data->p += len;
560 return len;
561}
562#endif
563
564/** Emulates successive libcurl calls to an encoding function
565 *
566 * This function is used when the request will be sent to the HTTP server as one
567 * contiguous entity. A buffer of REST_BODY_ALLOC_CHUNK bytes is allocated and passed
568 * to the stream encoding function.
569 *
570 * If the stream function does not return 0, a new buffer is allocated which is
571 * the size of the previous buffer + REST_BODY_ALLOC_CHUNK bytes, the data from the
572 * previous buffer is copied, and freed, and another call is made to the stream
573 * function, passing a pointer into the new buffer at the end of the previously
574 * written data.
575 *
576 * This process continues until the stream function signals (by returning 0)
577 * that it has no more data to write.
578 *
579 * @param[out] out where the pointer to the alloced buffer should
580 * be written.
581 * @param[in] inst of rlm_rest.
582 * @param[in] func Stream function.
583 * @param[in] limit Maximum buffer size to alloc.
584 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls to
585 * stream function.
586 * @return
587 * - Length of the data written to the buffer (excluding NULL).
588 * - -1 if alloc >= limit.
589 */
591 rest_read_t func, size_t limit, void *userdata)
592{
593 char *buff = NULL;
594 size_t needed = REST_BODY_ALLOC_CHUNK; /* Size of buffer to alloc */
595 size_t used = 0; /* Size of data written */
596 size_t len = 0;
597
598 buff = talloc_array(NULL, char, needed);
599 for (;;) {
600 len = func(buff + used, needed - used, 1, userdata);
601 used += len;
602 if (!len) {
603 *out = buff;
604 return used;
605 }
606
607 needed *= 2;
608 if (needed > limit) break;
609
610 MEM(buff = talloc_realloc(NULL, buff, char, needed));
611 }
612
614
615 return -1;
616}
617
618/** (Re-)Initialises the data in a rlm_rest_request_t.
619 *
620 * Resets the values of a rlm_rest_request_t to their defaults.
621 *
622 * @param[in] section configuration data.
623 * @param[in] request Current request.
624 * @param[in] ctx to initialise.
625 */
626static void rest_request_init(rlm_rest_section_t const *section,
627 request_t *request, rlm_rest_request_t *ctx)
628{
629 /*
630 * Setup stream read data
631 */
632 ctx->section = section;
633 ctx->request = request;
634 ctx->state = READ_STATE_INIT;
635}
636
637/** Converts plain response into a single fr_pair_t
638 *
639 * @param[in] inst configuration data.
640 * @param[in] section configuration data.
641 * @param[in] randle fr_curl_io_request_t to use.
642 * @param[in] request Current request.
643 * @param[in] raw buffer containing POST data.
644 * @param[in] rawlen Length of data in raw buffer.
645 * @return
646 * - Number of fr_pair_t processed.
647 * - -1 on unrecoverable error.
648 */
650 request_t *request, UNUSED fr_curl_io_request_t *randle, char *raw, size_t rawlen)
651{
652 fr_pair_t *vp;
653
654 /*
655 * Empty response?
656 */
657 if (*raw == '\0') return 0;
658
659 /*
660 * Use rawlen to protect against overrun, and to cope with any binary data
661 */
663 fr_pair_value_bstrndup(vp, raw, rawlen, true);
664
665 RDEBUG2("%pP", vp);
666
667 return 1;
668}
669
670/** Converts POST response into fr_pair_ts and adds them to the request
671 *
672 * Accepts fr_pair_tS in the same format as rest_encode_post, but with the
673 * addition of optional attribute list qualifiers as part of the attribute name
674 * string.
675 *
676 * If no qualifiers are specified, will default to the request list.
677 *
678 * POST response format is:
679 * @verbatim [outer.][<list>.]<attribute0>=<value0>&[outer.][<list>.]<attribute1>=<value1>&[outer.][<list>.]<attributeN>=<valueN> @endverbatim
680 *
681 * @see rest_encode_post
682 *
683 * @param[in] instance configuration data.
684 * @param[in] section configuration data.
685 * @param[in] randle fr_curl_io_request_t to use.
686 * @param[in] request Current request.
687 * @param[in] raw buffer containing POST data.
688 * @param[in] rawlen Length of data in raw buffer.
689 * @return
690 * - Number of fr_pair_ts processed.
691 * - -1 on unrecoverable error.
692 */
693static int rest_decode_post(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t const *section,
694 request_t *request, fr_curl_io_request_t *randle, char *raw, size_t rawlen)
695{
696 CURL *candle = randle->candle;
697
698 char const *p = raw, *q;
699
700 int count = 0;
701 int ret;
702
703 /*
704 * Empty response?
705 */
707 if (*p == '\0') return 0;
708
709 while (((q = strchr(p, '=')) != NULL) && (count < REST_BODY_MAX_ATTRS)) {
710 tmpl_t *dst;
711 request_t *current;
712 fr_pair_list_t *vps;
713 TALLOC_CTX *ctx;
714 fr_dict_attr_t const *da;
715 fr_pair_t *vp;
716
717 char *name = NULL;
718 char *value = NULL;
719
720 char *expanded = NULL;
721
722 size_t len;
723 int curl_len; /* Length from last curl_easy_unescape call */
724
725 current = request;
726
727 name = curl_easy_unescape(candle, p, (q - p), &curl_len);
728 p = (q + 1);
729
730 /*
731 * Resolve attribute name to a dictionary entry and pairlist.
732 */
733 RDEBUG2("Parsing attribute \"%pV\"", fr_box_strvalue_len(name, curl_len));
734
735 if (tmpl_afrom_attr_str(request, NULL, &dst, name,
736 &(tmpl_rules_t){
737 .attr = {
738 .dict_def = request->local_dict,
739 .list_def = request_attr_reply
740 }
741 }) <= 0) {
742 RPWDEBUG("Failed parsing attribute (skipping)");
743 talloc_free(dst);
744 goto skip;
745 }
746
747 if (tmpl_request_ptr(&current, tmpl_request(dst)) < 0) {
748 RWDEBUG("Attribute name refers to outer request but not in a tunnel (skipping)");
749 talloc_free(dst);
750 goto skip;
751 }
752
753 vps = tmpl_list_head(current, tmpl_list(dst));
754 if (!vps) {
755 RWDEBUG("List not valid in this context (skipping)");
756 talloc_free(dst);
757 goto skip;
758 }
759 ctx = tmpl_list_ctx(current, tmpl_list(dst));
760 da = tmpl_attr_tail_da(dst);
761
762 fr_assert(vps);
763
764 RINDENT();
765 RDEBUG3("Type : %s", fr_type_to_str(da->type));
766
767 q = strchr(p, '&');
768 len = (!q) ? (rawlen - (p - raw)) : (unsigned)(q - p);
769
770 value = curl_easy_unescape(candle, p, len, &curl_len);
771
772 /*
773 * If we found a delimiter we want to skip over it,
774 * if we didn't we do *NOT* want to skip over the end
775 * of the buffer...
776 */
777 p += (!q) ? len : (len + 1);
778
779 RDEBUG3("Length : %i", curl_len);
780 RDEBUG3("Value : \"%s\"", value);
781 REXDENT();
782
783 talloc_free(dst); /* Free our temporary tmpl */
784
785 RDEBUG2("Performing xlat expansion of response value");
786
787 if (xlat_aeval(request, &expanded, request, value, NULL, NULL) < 0) goto skip;
788
789 fr_assert(expanded);
790
791 MEM(vp = fr_pair_afrom_da(ctx, da));
792 if (!vp) {
793 REDEBUG("Failed creating valuepair");
794 talloc_free(expanded);
795
796 curl_free(name);
797 curl_free(value);
798
799 return count;
800 }
801
802 ret = fr_pair_value_from_str(vp, expanded, strlen(value), NULL, true);
803 TALLOC_FREE(expanded);
804 if (ret < 0) {
805 RWDEBUG("Incompatible value assignment, skipping");
807 goto skip;
808 }
809
810 fr_pair_append(vps, vp);
811
812 count++;
813
814 skip:
815 curl_free(name);
816 curl_free(value);
817
818 continue;
819 }
820
821 if (!count) REDEBUG("Malformed POST data \"%s\"", raw);
822
823 return count;
824
825}
826
827#ifdef HAVE_JSON
828/** Converts JSON "value" key into fr_pair_t.
829 *
830 * If leaf is not in fact a leaf node, but contains JSON data, the data will
831 * written to the attribute in JSON string format.
832 *
833 * @param[in] instance configuration data.
834 * @param[in] section configuration data.
835 * @param[in] ctx to allocate new fr_pair_ts in.
836 * @param[in] request Current request.
837 * @param[in] da Attribute to create.
838 * @param[in] flags containing the operator other flags controlling value
839 * expansion.
840 * @param[in] leaf object containing the fr_pair_t value.
841 * @return
842 * - #fr_pair_t just created.
843 * - NULL on error.
844 */
846 TALLOC_CTX *ctx, request_t *request,
847 fr_dict_attr_t const *da, json_flags_t *flags, json_object *leaf)
848{
849 char const *value;
850 char *expanded = NULL;
851 int ret;
852
853 fr_pair_t *vp;
854
856
857 if (json_object_is_type(leaf, json_type_null)) {
858 RDEBUG3("Got null value for attribute \"%s\" (skipping)", da->name);
859 return NULL;
860 }
861
862 MEM(vp = fr_pair_afrom_da(ctx, da));
863 if (!vp) {
864 RWDEBUG("Failed creating valuepair for attribute \"%s\" (skipping)", da->name);
865 return NULL;
866 }
867
869
870 switch (json_object_get_type(leaf)) {
871 case json_type_int:
872 if (flags->do_xlat) RWDEBUG("Ignoring do_xlat on 'int', attribute \"%s\"", da->name);
873 fr_value_box(&src, (int32_t)json_object_get_int(leaf), true);
874 break;
875
876 case json_type_double:
877 if (flags->do_xlat) RWDEBUG("Ignoring do_xlat on 'double', attribute \"%s\"", da->name);
878 fr_value_box(&src, (double)json_object_get_double(leaf), true);
879 break;
880
881 case json_type_string:
882 value = json_object_get_string(leaf);
883 if (flags->do_xlat && memchr(value, '%', json_object_get_string_len(leaf))) {
884 if (xlat_aeval(request, &expanded, request, value, NULL, NULL) < 0) {
886 return NULL;
887 }
888 fr_value_box_bstrndup_shallow(&src, NULL, expanded,
889 talloc_strlen(expanded), true);
890 } else {
892 json_object_get_string_len(leaf), true);
893 }
894 break;
895
896 default:
897 {
898 char const *str;
899 if (flags->do_xlat) RWDEBUG("Ignoring do_xlat on 'object', attribute \"%s\"", da->name);
900
901 /*
902 * Should encode any nested JSON structures into JSON strings.
903 *
904 * "I knew you liked JSON so I put JSON in your JSON!"
905 */
906 str = json_object_get_string(leaf);
907 if (!str) {
908 RWDEBUG("Failed getting string value for attribute \"%s\" (skipping)", da->name);
910 return NULL;
911 }
912 fr_value_box_strdup_shallow(&src, NULL, str, true);
913 }
914 }
915
916 ret = fr_value_box_cast(vp, &vp->data, da->type, da, &src);
917 talloc_free(expanded);
918 if (ret < 0) {
919 RWDEBUG("Failed parsing value for attribute \"%s\" (skipping)", da->name);
921 return NULL;
922 }
923
924 vp->op = flags->op;
925
926 return vp;
927}
928
929/** Processes JSON response and converts it into multiple fr_pair_ts
930 *
931 * Processes JSON attribute declarations in the format below. Will recurse when
932 * processing nested attributes. When processing nested attributes flags and
933 * operators from previous attributes are not inherited.
934 *
935 * JSON response format is:
936@verbatim
937{
938 "<attribute0>":{
939 "do_xlat":<bool>,
940 "is_json":<bool>,
941 "op":"<operator>",
942 "value":[<value0>,<value1>,<valueN>]
943 },
944 "<attribute1>":{
945 "value":{
946 "<nested-attribute0>":{
947 "op":"<operator>",
948 "value":<value0>
949 }
950 }
951 },
952 "<attribute2>":"<value0>",
953 "<attributeN>":[<value0>,<value1>,<valueN>]
954}
955@endverbatim
956 *
957 * JSON valuepair flags:
958 * - do_xlat (optional) Controls xlat expansion of values. Defaults to true.
959 * - is_json (optional) If true, any nested JSON data will be copied to the
960 * fr_pair_t in string form. Defaults to true.
961 * - op (optional) Controls how the attribute is inserted into
962 * the target list. Defaults to ':=' (T_OP_SET).
963 *
964 * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
965 * second and subsequent values in multivalued attributes. This does not work
966 * between multiple attribute declarations.
967 *
968 * @see fr_tokens_table
969 *
970 * @param[in] instance configuration data.
971 * @param[in] section configuration data.
972 * @param[in] request Current request.
973 * @param[in] object containing root node, or parent node.
974 * @param[in] level Current nesting level.
975 * @param[in] max counter, decremented after each fr_pair_t is created,
976 * when 0 no more attributes will be processed.
977 * @return
978 * - Number of attributes created.
979 * - < 0 on error.
980 */
981static int json_pair_alloc(rlm_rest_t const *instance, rlm_rest_section_t const *section,
982 request_t *request, json_object *object, UNUSED int level, int max)
983{
984 int max_attrs = max;
985 tmpl_t *dst = NULL;
986
987 if (!json_object_is_type(object, json_type_object)) {
988#ifdef HAVE_JSON_TYPE_TO_NAME
989 REDEBUG("Can't process VP container, expected JSON object"
990 "got \"%s\" (skipping)",
991 json_type_to_name(json_object_get_type(object)));
992#else
993 REDEBUG("Can't process VP container, expected JSON object"
994 " (skipping)");
995#endif
996 return -1;
997 }
998
999 /*
1000 * Process VP container
1001 */
1002 {
1003 json_object_object_foreach(object, name, value) {
1004 int i = 0, elements;
1005 struct json_object *element, *tmp;
1006 TALLOC_CTX *ctx;
1007
1008 json_flags_t flags = {
1009 .op = T_OP_SET,
1010 .do_xlat = 1,
1011 .is_json = 0
1012 };
1013
1014 request_t *current = request;
1015 fr_pair_list_t *vps;
1016 fr_pair_t *vp = NULL;
1017
1018 TALLOC_FREE(dst);
1019
1020 /*
1021 * Resolve attribute name to a dictionary entry and pairlist.
1022 */
1023 RDEBUG2("Parsing attribute \"%s\"", name);
1024
1025 if (tmpl_afrom_attr_str(request, NULL, &dst, name,
1026 &(tmpl_rules_t){
1027 .attr = {
1028 .dict_def = request->local_dict,
1029 .list_def = request_attr_reply
1030 }
1031 }) <= 0) {
1032 RPWDEBUG("Failed parsing attribute (skipping)");
1033 continue;
1034 }
1035
1036 if (tmpl_request_ptr(&current, tmpl_request(dst)) < 0) {
1037 RWDEBUG("Attribute name refers to outer request but not in a tunnel (skipping)");
1038 continue;
1039 }
1040
1041 vps = tmpl_list_head(current, tmpl_list(dst));
1042 if (!vps) {
1043 RWDEBUG("List not valid in this context (skipping)");
1044 continue;
1045 }
1046 ctx = tmpl_list_ctx(current, tmpl_list(dst));
1047
1048 /*
1049 * Alternative JSON structure which allows operator,
1050 * and other flags to be specified.
1051 *
1052 * "<name>":{
1053 * "do_xlat":<bool>,
1054 * "is_json":<bool>,
1055 * "op":"<op>",
1056 * "value":<value>
1057 * }
1058 *
1059 * Where value is a:
1060 * - [] Multivalued array
1061 * - {} Nested Valuepair
1062 * - * Integer or string value
1063 */
1064 if (json_object_is_type(value, json_type_object)) {
1065 /*
1066 * Process operator if present.
1067 */
1068 if (json_object_object_get_ex(value, "op", &tmp)) {
1069 flags.op = fr_table_value_by_str(fr_tokens_table, json_object_get_string(tmp), 0);
1070 if (!flags.op) {
1071 RWDEBUG("Invalid operator value \"%s\" (skipping)",
1072 json_object_get_string(tmp));
1073 continue;
1074 }
1075 }
1076
1077 /*
1078 * Process optional do_xlat bool.
1079 */
1080 if (json_object_object_get_ex(value, "do_xlat", &tmp)) {
1081 flags.do_xlat = json_object_get_boolean(tmp);
1082 }
1083
1084 /*
1085 * Process optional is_json bool.
1086 */
1087 if (json_object_object_get_ex(value, "is_json", &tmp)) {
1088 flags.is_json = json_object_get_boolean(tmp);
1089 }
1090
1091 /*
1092 * Value key must be present if were using the expanded syntax.
1093 */
1094 if (!json_object_object_get_ex(value, "value", &tmp)) {
1095 RWDEBUG("Value key missing (skipping)");
1096 continue;
1097 }
1098
1099 /*
1100 * The value field now becomes the key we're operating on
1101 */
1102 value = tmp;
1103 }
1104
1105 /*
1106 * Setup fr_pair_afrom_da / recursion loop.
1107 */
1108 if (!flags.is_json && json_object_is_type(value, json_type_array)) {
1109 elements = json_object_array_length(value);
1110 if (!elements) {
1111 RWDEBUG("Zero length value array (skipping)");
1112 continue;
1113 }
1114 element = json_object_array_get_idx(value, 0);
1115 } else {
1116 elements = 1;
1117 element = value;
1118 }
1119
1120 /*
1121 * A JSON 'value' key, may have multiple elements, iterate
1122 * over each of them, creating a new fr_pair_t.
1123 */
1124 do {
1125 fr_pair_list_t tmp_list;
1126
1127 if (max_attrs-- <= 0) {
1128 RWDEBUG("At maximum attribute limit");
1129 talloc_free(dst);
1130 return max;
1131 }
1132
1133 /*
1134 * Automagically switch the op for multivalued attributes.
1135 */
1136 if (((flags.op == T_OP_SET) || (flags.op == T_OP_EQ)) && (i >= 1)) {
1137 flags.op = T_OP_ADD_EQ;
1138 }
1139
1140 if (json_object_is_type(element, json_type_object) && !flags.is_json) {
1141 /* TODO: Insert nested VP into VP structure...*/
1142 RWDEBUG("Found nested VP, these are not yet supported (skipping)");
1143
1144 continue;
1145
1146 /*
1147 vp = json_pair_alloc(instance, section,
1148 request, value,
1149 level + 1, max_attrs);*/
1150 } else {
1151 vp = json_pair_alloc_leaf(instance, section, ctx, request,
1152 tmpl_attr_tail_da(dst), &flags, element);
1153 if (!vp) continue;
1154 }
1155 RINDENT();
1156 RDEBUG2("%s:%pP", tmpl_list_name(tmpl_list(dst), ""), vp);
1157 REXDENT();
1158
1159 fr_pair_list_init(&tmp_list);
1160 fr_pair_append(&tmp_list, vp);
1161 radius_pairmove(current, vps, &tmp_list);
1162 /*
1163 * If we call json_object_array_get_idx on something that's not an array
1164 * the behaviour appears to be to occasionally segfault.
1165 */
1166 } while ((++i < elements) && (element = json_object_array_get_idx(value, i)));
1167 }
1168 }
1169
1170 talloc_free(dst);
1171
1172 return max - max_attrs;
1173}
1174
1175/** Converts JSON response into fr_pair_ts and adds them to the request.
1176 *
1177 * Converts the raw JSON string into a json-c object tree and passes it to
1178 * json_pair_alloc. After the tree has been parsed json_object_put is called
1179 * which decrements the reference count of the root node by one, and frees
1180 * the entire tree.
1181 *
1182 * @see rest_encode_json
1183 * @see json_pair_alloc
1184 *
1185 * @param[in] instance configuration data.
1186 * @param[in] section configuration data.
1187 * @param[in,out] request Current request.
1188 * @param[in] randle REST handle.
1189 * @param[in] raw buffer containing JSON data.
1190 * @param[in] rawlen Length of data in raw buffer.
1191 * @return
1192 * - The number of #fr_pair_t processed.
1193 * - -1 on unrecoverable error.
1194 */
1195static int rest_decode_json(rlm_rest_t const *instance, rlm_rest_section_t const *section,
1196 request_t *request, UNUSED fr_curl_io_request_t *randle, char *raw, UNUSED size_t rawlen)
1197{
1198 char const *p = raw;
1199
1200 struct json_object *json;
1201
1202 int ret;
1203
1204 /*
1205 * Empty response?
1206 */
1208 if (*p == '\0') return 0;
1209
1210 json = json_tokener_parse(p);
1211 if (!json) {
1212 REDEBUG("Malformed JSON data \"%s\"", raw);
1213 return -1;
1214 }
1215
1216 ret = json_pair_alloc(instance, section, request, json, 0, REST_BODY_MAX_ATTRS);
1217
1218 /*
1219 * Decrement reference count for root object, should free entire JSON tree.
1220 */
1221 json_object_put(json);
1222
1223 return ret;
1224}
1225#endif
1226
1227/** Processes incoming HTTP header data from libcurl.
1228 *
1229 * Processes the status line, and Content-Type headers from the incoming HTTP
1230 * response.
1231 *
1232 * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1233 * by libcurl.
1234 *
1235 * @param[in] in Char buffer where inbound header data is written.
1236 * @param[in] size Multiply by nmemb to get the length of ptr.
1237 * @param[in] nmemb Multiply by size to get the length of ptr.
1238 * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1239 * @return
1240 * - Length of data processed.
1241 * - 0 on error.
1242 */
1243static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *userdata)
1244{
1245 rlm_rest_response_t *ctx = userdata;
1246 request_t *request = ctx->request; /* Used by RDEBUG */
1247
1248 char const *start = (char *)in, *p = start, *end = p + (size * nmemb);
1249 char *q;
1250 size_t len;
1251
1253
1254#ifndef NDEBUG
1255 if (ctx->instance->fail_header_decode) {
1256 REDEBUG("Forcing header decode failure");
1257 return 0;
1258 }
1259#endif
1260
1261 /*
1262 * This seems to be curl's indication there are no more header lines.
1263 */
1264 if (((end - p) == 2) && ((p[0] == '\r') && (p[1] == '\n'))) {
1265 /*
1266 * If we got a 100 Continue, we need to send additional payload data.
1267 * reset the state to WRITE_STATE_INIT, so that when were called again
1268 * we overwrite previous header data with that from the proper header.
1269 */
1270 if (ctx->code == 100) {
1271 RDEBUG2("Continuing...");
1272 ctx->state = WRITE_STATE_INIT;
1273 }
1274
1275 return (end - start);
1276 }
1277
1278 switch (ctx->state) {
1279 case WRITE_STATE_INIT:
1280 RDEBUG2("Processing response header");
1281
1282 /*
1283 * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1284 *
1285 * "HTTP/1.1 " (9) + "100" (3) + "\r\n" (2) = 14
1286 * "HTTP/2 " (7) + "100" (3) + "\r\n" (2) = 12
1287 */
1288 if ((end - p) < 12) {
1289 REDEBUG("Malformed HTTP header: Status line too short");
1290 malformed:
1291 REDEBUG("Received %zu bytes of invalid header data: %pV",
1292 (end - start), fr_box_strvalue_len(in, (end - start)));
1293 ctx->code = 0;
1294
1295 /*
1296 * Indicate we parsed the entire line, otherwise
1297 * bad things seem to happen internally with
1298 * libcurl when we try and use it with asynchronous
1299 * I/O handlers.
1300 */
1301 return (end - start);
1302 }
1303 /*
1304 * Check start of header matches...
1305 */
1306 if (strncasecmp("HTTP/", p, 5) != 0) {
1307 REDEBUG("Malformed HTTP header: Missing HTTP version");
1308 goto malformed;
1309 }
1310 p += 5;
1311
1312 /*
1313 * Skip the version field, next space should mark start of reason_code.
1314 */
1315 q = memchr(p, ' ', (end - p));
1316 if (!q) {
1317 REDEBUG("Malformed HTTP header: Missing reason code");
1318 goto malformed;
1319 }
1320
1321 p = q;
1322
1323 /*
1324 * Process reason_code.
1325 *
1326 * " 100" (4) + "\r\n" (2) = 6
1327 */
1328 if ((end - p) < 6) {
1329 REDEBUG("Malformed HTTP header: Reason code too short");
1330 goto malformed;
1331 }
1332 p++;
1333
1334 /*
1335 * "xxx( |\r)" status code and terminator.
1336 */
1337 if (!isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || !((p[3] == ' ') || (p[3] == '\r'))) {
1338 REDEBUG("Malformed HTTP header: Reason code malformed. "
1339 "Expected three digits then space or end of header, got \"%pV\"",
1340 fr_box_strvalue_len(p, 4));
1341 goto malformed;
1342 }
1343
1344 /*
1345 * Convert status code into an integer value
1346 */
1347 q = NULL;
1348 ctx->code = (int)strtoul(p, &q, 10);
1349 fr_assert(q == (p + 3)); /* We check this above */
1350 p = q;
1351
1352 /*
1353 * Process reason_phrase (if present).
1354 */
1355 RINDENT();
1356 if (*p == ' ') {
1357 p++;
1358 q = memchr(p, '\r', (end - p));
1359 if (!q) goto malformed;
1360 RDEBUG2("Status : %i (%pV)", ctx->code, fr_box_strvalue_len(p, q - p));
1361 } else {
1362 RDEBUG2("Status : %i", ctx->code);
1363 }
1364 REXDENT();
1365
1367
1368 break;
1369
1371 if (((end - p) >= 14) &&
1372 (strncasecmp("Content-Type: ", p, 14) == 0)) {
1373 p += 14;
1374
1375 /*
1376 * Check to see if there's a parameter separator.
1377 */
1378 q = memchr(p, ';', (end - p));
1379
1380 /*
1381 * If there's not, find the end of this header.
1382 */
1383 if (!q) q = memchr(p, '\r', (end - p));
1384
1385 len = (size_t)(!q ? (end - p) : (q - p));
1387
1388 RINDENT();
1389 RDEBUG2("Type : %s (%pV)", fr_table_str_by_value(http_body_type_table, type, "<INVALID>"),
1390 fr_box_strvalue_len(p, len));
1391 REXDENT();
1392
1393 /*
1394 * Assume the force_to value has already been validated.
1395 */
1396 if (ctx->force_to != REST_HTTP_BODY_UNKNOWN) {
1397 if (ctx->force_to != ctx->type) {
1398 RDEBUG3("Forcing body type to \"%s\"",
1400 ctx->type = ctx->force_to;
1401 }
1402 /*
1403 * Figure out if the type is supported by one of the decoders.
1404 */
1405 } else {
1407
1408 /*
1409 * No need to check supported types if we accept everything.
1410 */
1411 if (ctx->section->response.accept_all) break;
1412
1413 switch (ctx->type) {
1415 RWDEBUG("Couldn't determine type, using the request's type \"%s\".",
1417 break;
1418
1420 REDEBUG("Type \"%s\" is currently unsupported",
1422 break;
1423
1425 REDEBUG("Type \"%s\" is unavailable, please rebuild this module with the required "
1426 "library", fr_table_str_by_value(http_body_type_table, type, "<INVALID>"));
1427 break;
1428
1430 REDEBUG("Type \"%s\" is not a valid web API data markup format",
1432 break;
1433
1434 /* supported type */
1435 default:
1436 break;
1437 }
1438 }
1439 }
1440 break;
1441
1442 default:
1443 break;
1444 }
1445
1446 return (end - start);
1447}
1448
1449/** Processes incoming HTTP body data from libcurl.
1450 *
1451 * Writes incoming body data to an intermediary buffer for later parsing by
1452 * one of the decode functions.
1453 *
1454 * @param[in] in Char buffer where inbound header data is written
1455 * @param[in] size Multiply by nmemb to get the length of ptr.
1456 * @param[in] nmemb Multiply by size to get the length of ptr.
1457 * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1458 * @return
1459 * - Length of data processed.
1460 * - 0 on error.
1461 */
1462static size_t rest_response_body(void *in, size_t size, size_t nmemb, void *userdata)
1463{
1464 rlm_rest_response_t *ctx = userdata;
1465 request_t *request = ctx->request; /* Used by RDEBUG */
1466
1467 char const *start = in, *p = start, *end = p + (size * nmemb);
1468 char *q;
1469
1470 size_t needed;
1471
1472 if (start == end) return 0; /* Nothing to process */
1473
1474#ifndef NDEBUG
1475 if (ctx->instance->fail_body_decode) {
1476 REDEBUG("Forcing body read failure");
1477 return 0;
1478 }
1479#endif
1480
1481 /*
1482 * Any post processing of headers should go here...
1483 */
1485
1486 switch (ctx->type) {
1490 if (ctx->section->response.accept_all) goto accept_body;
1491
1492 while ((q = memchr(p, '\n', (end - p)))) {
1493 REDEBUG("%pV", fr_box_strvalue_len(p, q - p));
1494 p = q + 1;
1495 }
1496
1497 if (p != end) REDEBUG("%pV", fr_box_strvalue_len(p, end - p));
1498 break;
1499
1501 while ((q = memchr(p, '\n', (end - p)))) {
1502 RDEBUG3("%pV", fr_box_strvalue_len(p, q - p));
1503 p = q + 1;
1504 }
1505
1506 if (p != end) RDEBUG3("%pV", fr_box_strvalue_len(p, end - p));
1507 break;
1508
1509 default:
1510 {
1511 char *out_p;
1512 accept_body:
1513 if ((ctx->section->response.max_body_in > 0) && ((ctx->used + (end - p)) > ctx->section->response.max_body_in)) {
1514 REDEBUG("Incoming data (%zu bytes) exceeds max_body_in (%zu bytes). "
1515 "Forcing body to type 'invalid'", ctx->used + (end - p), ctx->section->response.max_body_in);
1517 TALLOC_FREE(ctx->buffer);
1518 break;
1519 }
1520
1521 needed = ROUND_UP(ctx->used + (end - p), REST_BODY_ALLOC_CHUNK);
1522 if (needed > ctx->alloc) {
1523 MEM(ctx->buffer = talloc_bstr_realloc(NULL, ctx->buffer, needed));
1524 ctx->alloc = needed;
1525 }
1526
1527 out_p = ctx->buffer + ctx->used;
1528 memcpy(out_p, p, (end - p));
1529 out_p += (end - p);
1530 *out_p = '\0';
1531 ctx->used += (end - p);
1532 }
1533 break;
1534 }
1535
1536 return (end - start);
1537}
1538
1539/** Print out the response text as error lines
1540 *
1541 * @param request The Current request.
1542 * @param handle fr_curl_io_request_t used to execute the previous request.
1543 */
1545{
1546 char const *p, *end;
1547 char *q;
1548 size_t len;
1549
1550 len = rest_get_handle_data(&p, handle);
1551 if (len == 0) return;
1552
1553 end = p + len;
1554
1555 RERROR("Server returned:");
1556 while ((q = memchr(p, '\n', (end - p)))) {
1557 RERROR("%pV", fr_box_strvalue_len(p, q - p));
1558 p = q + 1;
1559 }
1560
1561 if (p != end) RERROR("%pV", fr_box_strvalue_len(p, end - p));
1562}
1563
1564/** Print out the response text
1565 *
1566 * @param request The Current request.
1567 * @param handle fr_curl_io_request_t used to execute the previous request.
1568 */
1570{
1571 char const *p, *end;
1572 char *q;
1573 size_t len;
1574
1575 len = rest_get_handle_data(&p, handle);
1576 if (len == 0) return;
1577
1578 end = p + len;
1579
1580 RDEBUG3("Server returned:");
1581 while ((q = memchr(p, '\n', (end - p)))) {
1582 RDEBUG3("%pV", fr_box_strvalue_len(p, q - p));
1583 p = q + 1;
1584 }
1585
1586 if (p != end) RDEBUG3("%pV", fr_box_strvalue_len(p, end - p));
1587}
1588
1589/** (Re-)Initialises the data in a rlm_rest_response_t.
1590 *
1591 * This resets the values of the a rlm_rest_response_t to their defaults.
1592 * Must be called between encoding sessions.
1593 *
1594 * @see rest_response_body
1595 * @see rest_response_header
1596 *
1597 * @param[in] section that created the request.
1598 * @param[in] request Current request.
1599 * @param[in] ctx data to initialise.
1600 * @param[in] type Default http_body_type to use when decoding raw data, may be
1601 * overwritten by rest_response_header.
1602 * @param[in] header Where to write out headers, may be NULL.
1603 */
1604static void rest_response_init(rlm_rest_section_t const *section,
1605 request_t *request, rlm_rest_response_t *ctx, http_body_type_t type, tmpl_t *header)
1606{
1607 ctx->section = section;
1608 ctx->request = request;
1609 ctx->type = type;
1610 ctx->state = WRITE_STATE_INIT;
1611 ctx->alloc = 0;
1612 ctx->used = 0;
1613 ctx->code = 0;
1614 ctx->header = header;
1615 TALLOC_FREE(ctx->buffer);
1616}
1617
1618/** Extracts pointer to buffer containing response data
1619 *
1620 * @param[out] out Where to write the pointer to the buffer.
1621 * @param[in] randle used for the last request.
1622 * @return
1623 * - 0 if no data i available.
1624 * - > 0 if data is available.
1625 */
1626size_t rest_get_handle_data(char const **out, fr_curl_io_request_t *randle)
1627{
1628 rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1629
1630 if (!ctx->response.buffer) return 0;
1631
1632 *out = ctx->response.buffer;
1633 return ctx->response.used;
1634}
1635
1636/** Configures body specific curlopts.
1637 *
1638 * Configures libcurl handle to use either chunked mode, where the request
1639 * data will be sent using multiple HTTP requests, or contiguous mode where
1640 * the request data will be sent in a single HTTP request.
1641 *
1642 * @param[in] mctx Call data.
1643 * @param[in] section configuration data.
1644 * @param[in] request Current request.
1645 * @param[in] randle fr_curl_io_request_t to configure.
1646 * @param[in] func to pass to libcurl for chunked.
1647 * transfers (NULL if not using chunked mode).
1648 * @return
1649 * - 0 on success.
1650 * - -1 on failure.
1651 */
1652static int rest_request_config_body(module_ctx_t const *mctx, rlm_rest_section_t const *section,
1653 request_t *request, fr_curl_io_request_t *randle, rest_read_t func)
1654{
1655 rlm_rest_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_rest_t);
1656 rlm_rest_curl_context_t *uctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1657 ssize_t len;
1658
1659 /*
1660 * We were provided with no read function, assume this means
1661 * no body should be sent.
1662 */
1663 if (!func) {
1664 FR_CURL_REQUEST_SET_OPTION(CURLOPT_POSTFIELDSIZE, 0);
1665 return 0;
1666 }
1667
1668 /*
1669 * Chunked transfer encoding means the body will be sent in
1670 * multiple parts.
1671 */
1672 if (section->request.chunk > 0) {
1673 FR_CURL_REQUEST_SET_OPTION(CURLOPT_READDATA, &uctx->request);
1674 FR_CURL_REQUEST_SET_OPTION(CURLOPT_READFUNCTION, func);
1675
1676 return 0;
1677 }
1678
1679 /*
1680 * If were not doing chunked encoding then we read the entire
1681 * body into a buffer, and send it in one go.
1682 */
1683 len = rest_request_encode_wrapper(&uctx->body, inst, func, REST_BODY_MAX_LEN, &uctx->request);
1684 if (len <= 0) {
1685 REDEBUG("Failed creating HTTP body content");
1686 return -1;
1687 }
1688 RDEBUG2("Content-Length will be %zu bytes", len);
1689
1690 fr_assert((len == 0) || (talloc_array_length(uctx->body) >= (size_t)len));
1691 FR_CURL_REQUEST_SET_OPTION(CURLOPT_POSTFIELDS, uctx->body);
1692 FR_CURL_REQUEST_SET_OPTION(CURLOPT_POSTFIELDSIZE, len);
1693
1694 return 0;
1695
1696error:
1697 return -1;
1698}
1699
1700/** Adds an additional header to a handle to use in the next reques
1701 *
1702 * @param[in] request Current request.
1703 * @param[in] randle used for the next request.
1704 * @param[in] header to add.
1705 * @param[in] validate whether to perform basic checks on the header
1706 * @return
1707 * - 0 on success.
1708 * - -1 on failure.
1709 */
1710int rest_request_config_add_header(request_t *request, fr_curl_io_request_t *randle, char const *header, bool validate)
1711{
1712 rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1713 struct curl_slist *headers;
1714
1715 if (validate && !strchr(header, ':')) {
1716 RWDEBUG("Invalid HTTP header \"%s\" must be in format '<attribute>: <value>'. Skipping...",
1717 header);
1718 return -1;
1719 }
1720
1721 RINDENT();
1722 RDEBUG3("%s", header);
1723 REXDENT();
1724
1725 headers = curl_slist_append(ctx->headers, header);
1726 if (unlikely(!headers)) {
1727 REDEBUG("Failed to add header \"%s\"", header);
1728 return -1;
1729 }
1730 ctx->headers = headers;
1731
1732 return 0;
1733}
1734
1735/** See if the list of headers already contains a header
1736 *
1737 * @note Only compares the header name, not the value.
1738 *
1739 * @param[in] randle to check headers for.
1740 * @param[in] header to find.
1741 * @return
1742 * - true if yes
1743 * - false if no
1744 */
1745static bool rest_request_contains_header(fr_curl_io_request_t *randle, char const *header)
1746{
1747 rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1748 struct curl_slist *headers = ctx->headers;
1749 char const *sep;
1750 size_t cmp_len;
1751
1752 sep = strchr(header, ':');
1753 cmp_len = sep ? (size_t)(sep - header) : strlen(header);
1754
1755 while (headers) {
1756 if (strncasecmp(headers->data, header, cmp_len) == 0) return true;
1757 headers = headers->next;
1758 }
1759
1760 return false;
1761}
1762
1763/** Configures request curlopts.
1764 *
1765 * Configures libcurl handle setting various curlopts for things like local
1766 * client time, Content-Type, and other FreeRADIUS custom headers.
1767 *
1768 * Current FreeRADIUS custom headers are:
1769 * - X-FreeRADIUS-Section The module section being processed.
1770 * - X-FreeRADIUS-Server The current virtual server the request_t is
1771 * passing through.
1772 *
1773 * Sets up callbacks for all response processing (buffers and body data).
1774 *
1775 * @param[in] mctx call data.
1776 * @param[in] section configuration data.
1777 * @param[in] randle to configure.
1778 * @param[in] request Current request.
1779 * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1780 * @param[in] type Content-Type for request encoding, also sets
1781 * the default for decoding.
1782 * @param[in] uri buffer containing the expanded URI to send the request to.
1783 * @param[in] body_data (optional) custom body data. Must persist whilst we're
1784 * writing data out to the socket. Must be a talloced buffer
1785 * which is \0 terminated.
1786 * @return
1787 * - 0 on success (all opts configured).
1788 * - -1 on failure.
1789 */
1791 request_t *request, fr_curl_io_request_t *randle, http_method_t method,
1793 char const *uri, char const *body_data)
1794{
1795 rlm_rest_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_rest_t);
1796 rlm_rest_call_env_t *call_env = talloc_get_type_abort(mctx->env_data, rlm_rest_call_env_t);
1797 rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1798 CURL *candle = randle->candle;
1799 fr_time_delta_t timeout;
1800
1801 http_auth_type_t auth = section->request.auth;
1802
1803 CURLcode ret = CURLE_OK;
1804 char const *option;
1805
1806 char buffer[512];
1807
1808 fr_assert(candle);
1809
1810 buffer[(sizeof(buffer) - 1)] = '\0';
1811
1812 /*
1813 * Control which HTTP version we're going to use
1814 */
1815 if (inst->http_negotiation != CURL_HTTP_VERSION_NONE) FR_CURL_REQUEST_SET_OPTION(CURLOPT_HTTP_VERSION, inst->http_negotiation);
1816
1817 /*
1818 * Setup any header options and generic headers.
1819 */
1820 FR_CURL_REQUEST_SET_OPTION(CURLOPT_URL, uri);
1821#if CURL_AT_LEAST_VERSION(7,85,0)
1822 FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS_STR, "http,https");
1823#else
1824 FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
1825#endif
1826 if (section->request.proxy) {
1827 if (section->request.proxy == rest_no_proxy) {
1828 FR_CURL_REQUEST_SET_OPTION(CURLOPT_NOPROXY, "*");
1829 } else {
1830 FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROXY, section->request.proxy);
1831 }
1832 }
1833 FR_CURL_REQUEST_SET_OPTION(CURLOPT_NOSIGNAL, 1L);
1834 FR_CURL_REQUEST_SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING);
1835
1836 timeout = inst->conn_config.connect_timeout;
1837 RDEBUG3("Connect timeout is %pVs, request timeout is %pVs",
1838 fr_box_time_delta(timeout), fr_box_time_delta(section->timeout));
1839 FR_CURL_REQUEST_SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, fr_time_delta_to_msec(timeout));
1840 FR_CURL_REQUEST_SET_OPTION(CURLOPT_TIMEOUT_MS, fr_time_delta_to_msec(section->timeout));
1841
1842 /*
1843 * FreeRADIUS custom headers
1844 */
1845 RDEBUG3("Adding custom headers:");
1846 snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Section: %s", section->name);
1847 if (unlikely(rest_request_config_add_header(request, randle, buffer, false) < 0)) return -1;
1848
1849 snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Server: %s", cf_section_name2(unlang_call_current(request)));
1850 if (unlikely(rest_request_config_add_header(request, randle, buffer, false) < 0)) return -1;
1851
1852 /*
1853 * Add in the section headers
1854 */
1855 if (call_env->request.header) {
1856 size_t len = talloc_array_length(call_env->request.header), i;
1857 for (i = 0; i < len; i++) {
1858 fr_value_box_list_foreach(&call_env->request.header[i], header) {
1859 if (unlikely(rest_request_config_add_header(request, randle, header->vb_strvalue, true) < 0)) return -1;
1860 }
1861 }
1862 }
1863
1864 /*
1865 * Add in dynamic headers from the request
1866 */
1867 {
1868 fr_pair_t *header;
1869 fr_dcursor_t header_cursor;
1870
1871 for (header = fr_pair_dcursor_by_da_init(&header_cursor, &request->control_pairs, attr_rest_http_header);
1872 header;
1873 header = fr_dcursor_current(&header_cursor)) {
1874 header = fr_dcursor_remove(&header_cursor);
1875 if (unlikely(rest_request_config_add_header(request, randle, header->vp_strvalue, true) < 0)) return -1;
1876
1877 talloc_free(header);
1878 }
1879 }
1880
1881 if (!rest_request_contains_header(randle, "Content-Type:")) {
1882 /*
1883 * HTTP/1.1 doesn't require a content type so only set it
1884 * if where body type requires it, and we haven't set one
1885 * already from attributes.
1886 */
1887 if (type != REST_HTTP_BODY_NONE) {
1888 char const *content_type = fr_table_str_by_value(http_content_type_table, type, section->request.body_str);
1889 snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type);
1890 if (unlikely(rest_request_config_add_header(request, randle, buffer, false) < 0)) return -1;
1891
1892 RDEBUG3("Request body content-type will be \"%s\"", content_type);
1893 }
1894 }
1895
1896 /*
1897 * Configure HTTP verb (GET, POST, PUT, PATCH, DELETE, other...)
1898 */
1899 switch (method) {
1901 FR_CURL_REQUEST_SET_OPTION(CURLOPT_HTTPGET, 1L);
1902 break;
1903
1905 FR_CURL_REQUEST_SET_OPTION(CURLOPT_POST, 1L);
1906 break;
1907
1909 /*
1910 * Do not set CURLOPT_PUT, this will cause libcurl
1911 * to ignore CURLOPT_POSTFIELDs and attempt to read
1912 * whatever was set with CURLOPT_READDATA, which by
1913 * default is stdin.
1914 *
1915 * This is many cases will cause the server to block,
1916 * indefinitely.
1917 */
1918 FR_CURL_REQUEST_SET_OPTION(CURLOPT_CUSTOMREQUEST, "PUT");
1919 break;
1920
1922 FR_CURL_REQUEST_SET_OPTION(CURLOPT_CUSTOMREQUEST, "PATCH");
1923 break;
1924
1926 FR_CURL_REQUEST_SET_OPTION(CURLOPT_CUSTOMREQUEST, "DELETE");
1927 break;
1928
1930 FR_CURL_REQUEST_SET_OPTION(CURLOPT_CUSTOMREQUEST, section->request.method_str);
1931 break;
1932
1933 default:
1934 fr_assert(0);
1935 break;
1936 }
1937
1938 /*
1939 * Set user based authentication parameters
1940 */
1941 if (auth > REST_HTTP_AUTH_NONE) {
1942
1943#define SET_AUTH_OPTION(_x, _y)\
1944do {\
1945 if ((ret = curl_easy_setopt(candle, _x, _y)) != CURLE_OK) {\
1946 option = STRINGIFY(_x);\
1947 REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret); \
1948 goto error;\
1949 }\
1950} while (0)
1951 RDEBUG3("Configuring HTTP auth type %s, user \"%pV\", password \"%pV\"",
1952 fr_table_str_by_value(http_auth_table, auth, "<INVALID>"),
1953 call_env->request.username ? call_env->request.username : fr_box_strvalue("(none)"),
1954 call_env->request.password ? call_env->request.password : fr_box_strvalue("(none)"));
1955
1956 /*
1957 * FIXME - We probably want to escape \0s here...
1958 */
1959 if ((auth >= REST_HTTP_AUTH_BASIC) && (auth <= REST_HTTP_AUTH_ANY_SAFE)) {
1960 SET_AUTH_OPTION(CURLOPT_HTTPAUTH, http_curl_auth[auth]);
1961 if (call_env->request.username) SET_AUTH_OPTION(CURLOPT_USERNAME, call_env->request.username->vb_strvalue);
1962 if (call_env->request.password) SET_AUTH_OPTION(CURLOPT_PASSWORD, call_env->request.password->vb_strvalue);
1963 } else if (auth == REST_HTTP_AUTH_TLS_SRP) {
1964 SET_AUTH_OPTION(CURLOPT_TLSAUTH_TYPE, "SRP");
1965 if (call_env->request.username) SET_AUTH_OPTION(CURLOPT_TLSAUTH_USERNAME, call_env->request.username->vb_strvalue);
1966 if (call_env->request.password) SET_AUTH_OPTION(CURLOPT_TLSAUTH_PASSWORD, call_env->request.password->vb_strvalue);
1967 }
1968 }
1969
1970 /*
1971 * Set SSL/TLS authentication parameters
1972 */
1973 fr_curl_easy_tls_init(randle, &section->tls);
1974
1975 /*
1976 * Tell CURL how to get HTTP body content, and how to process incoming data.
1977 */
1978 rest_response_init(section, request, &ctx->response, type, call_env->response.header);
1979
1980 FR_CURL_REQUEST_SET_OPTION(CURLOPT_HEADERFUNCTION, rest_response_header);
1981 FR_CURL_REQUEST_SET_OPTION(CURLOPT_HEADERDATA, &ctx->response);
1982 FR_CURL_REQUEST_SET_OPTION(CURLOPT_WRITEFUNCTION, rest_response_body);
1983 FR_CURL_REQUEST_SET_OPTION(CURLOPT_WRITEDATA, &ctx->response);
1984
1985 /*
1986 * Force parsing the body text as a particular encoding.
1987 */
1988 ctx->response.force_to = section->response.force_to;
1989
1990 switch (method) {
1993 RDEBUG3("Using a HTTP method which does not require a body. Forcing request body type to \"none\"");
1994 goto finish;
1995
2000 if (section->request.chunk > 0) {
2001 ctx->request.chunk = section->request.chunk;
2002
2003 if (unlikely(rest_request_config_add_header(request, randle, "Transfer-Encoding: chunked", false) < 0)) return -1;
2004 }
2005
2006 break;
2007
2008 default:
2009 fr_assert(0);
2010 }
2011
2012 /*
2013 * Setup encoder specific options
2014 */
2015 switch (type) {
2017 if (rest_request_config_body(mctx, section, request, randle, NULL) < 0) return -1;
2018
2019 break;
2020
2022 {
2024
2025 data = talloc_zero(request, rest_custom_data_t);
2026 data->p = data->start = body_data;
2027 data->len = talloc_strlen(body_data);
2028
2029 /* Use the encoder specific pointer to store the data we need to encode */
2030 ctx->request.encoder = data;
2031 if (rest_request_config_body(mctx, section, request, randle, rest_encode_custom) < 0) {
2032 TALLOC_FREE(ctx->request.encoder);
2033 return -1;
2034 }
2035 }
2036 break;
2037
2038#ifdef HAVE_JSON
2040 {
2042
2043 data = talloc_zero(request, rest_custom_data_t);
2044 ctx->request.encoder = data;
2045
2046 rest_request_init(section, request, &ctx->request);
2047
2048 if (rest_request_config_body(mctx, section, request, randle, rest_encode_json) < 0) return -1;
2049 }
2050
2051 break;
2052#endif
2053
2055 rest_request_init(section, request, &ctx->request);
2056 fr_pair_dcursor_init(&(ctx->request.cursor), &request->request_pairs);
2057
2058 if (rest_request_config_body(mctx, section, request, randle, rest_encode_post) < 0) return -1;
2059
2060 break;
2061
2062 default:
2063 fr_assert(0);
2064 }
2065
2066
2067finish:
2068 FR_CURL_REQUEST_SET_OPTION(CURLOPT_HTTPHEADER, ctx->headers);
2069
2070 return 0;
2071
2072error:
2073 return -1;
2074}
2075
2076/** Sends the response to the correct decode function.
2077 *
2078 * Uses the Content-Type information written in rest_response_header to
2079 * determine the correct decode function to use. The decode function will
2080 * then convert the raw received data into fr_pair_ts.
2081 *
2082 * @param[in] instance configuration data.
2083 * @param[in] section configuration data.
2084 * @param[in] request Current request.
2085 * @param[in] randle to use.
2086 * @return
2087 * - 0 on success.
2088 * - -1 on failure.
2089 */
2090int rest_response_decode(rlm_rest_t const *instance, rlm_rest_section_t const *section,
2091 request_t *request, fr_curl_io_request_t *randle)
2092{
2093 rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
2094
2095 int ret = -1; /* -Wsometimes-uninitialized */
2096
2097 if (!ctx->response.buffer) {
2098 RDEBUG2("Skipping attribute processing, no valid body data received");
2099 return 0;
2100 }
2101
2102 switch (ctx->response.type) {
2104 return 0;
2105
2107 ret = rest_decode_plain(instance, section, request, randle, ctx->response.buffer, ctx->response.used);
2108 break;
2109
2111 ret = rest_decode_post(instance, section, request, randle, ctx->response.buffer, ctx->response.used);
2112 break;
2113
2114#ifdef HAVE_JSON
2116 ret = rest_decode_json(instance, section, request, randle, ctx->response.buffer, ctx->response.used);
2117 break;
2118#endif
2119
2123 return -1;
2124
2125 default:
2126 fr_assert(0);
2127 }
2128
2129 return ret;
2130}
2131
2132/** URL encodes a string.
2133 *
2134 * Encode special chars as per RFC 3986 section 4.
2135 *
2136 * @param[in] request Current request.
2137 * @param[out] out Where to write escaped string.
2138 * @param[in] outlen Size of out buffer.
2139 * @param[in] raw string to be urlencoded.
2140 * @param[in] arg pointer, gives context for escaping.
2141 * @return length of data written to out (excluding NULL).
2142 */
2143size_t rest_uri_escape(UNUSED request_t *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
2144{
2145 char *escaped;
2146
2147 escaped = curl_escape(raw, 0);
2148 strlcpy(out, escaped, outlen);
2149 curl_free(escaped);
2150
2151 return strlen(out);
2152}
2153
2154/** Unescapes the host portion of a URI string
2155 *
2156 * This is required because the xlat functions which operate on the input string
2157 * cannot distinguish between host and path components.
2158 *
2159 * @param[out] out Where to write the pointer to the new
2160 * buffer containing the escaped URI.
2161 * @param[in] inst of rlm_rest.
2162 * @param[in] request Current request
2163 * @param[in] randle to use.
2164 * @param[in] uri configuration data.
2165 * @return
2166 * - Length of data written to buffer (excluding NULL).
2167 * - < 0 if an error occurred.
2168 */
2170 fr_curl_io_request_t *randle, char const *uri)
2171{
2172 CURL *candle = randle->candle;
2173
2174 char const *p, *q;
2175
2176 char *scheme;
2177
2178 ssize_t len;
2179
2180 p = uri;
2181
2182 /*
2183 * All URLs must contain at least <scheme>://<server>/
2184 */
2185 p = strchr(p, ':');
2186 if (!p || (*++p != '/') || (*++p != '/')) {
2187 malformed:
2188 REDEBUG("URI \"%s\" is malformed, can't find start of path", uri);
2189 return -1;
2190 }
2191 p = strchr(p + 1, '/');
2192 if (!p) {
2193 goto malformed;
2194 }
2195
2196 len = (p - uri);
2197
2198 /*
2199 * Unescape any special sequences in the first part of the URI
2200 */
2201 scheme = curl_easy_unescape(candle, uri, len, NULL);
2202 if (!scheme) {
2203 REDEBUG("Error unescaping host");
2204 return -1;
2205 }
2206
2207 /*
2208 * URIs can't contain spaces, so anything after the space must
2209 * be something else.
2210 */
2211 q = strchr(p, ' ');
2212 *out = q ? talloc_typed_asprintf(request, "%s%.*s", scheme, (int)(q - p), p) :
2213 talloc_typed_asprintf(request, "%s%s", scheme, p);
2214
2215 MEM(*out);
2216 curl_free(scheme);
2217
2218 return talloc_strlen(*out); /* array_length includes \0 */
2219}
static int const char char buffer[256]
Definition acutest.h:576
#define RCSID(id)
Definition build.h:506
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:228
#define unlikely(_x)
Definition build.h:402
#define UNUSED
Definition build.h:336
#define NUM_ELEMENTS(_t)
Definition build.h:358
CONF_SECTION * unlang_call_current(request_t *request)
Return the last virtual server that was called.
Definition call.c:214
char const * cf_section_name2(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
Definition cf_util.c:1184
#define FR_CURL_REQUEST_SET_OPTION(_x, _y)
Definition base.h:67
fr_curl_io_request_t * fr_curl_io_request_alloc(TALLOC_CTX *ctx)
Allocate a new curl easy request and wrapper struct.
Definition io.c:542
void * uctx
Private data for the module using the API.
Definition base.h:105
CURL * candle
Request specific handle.
Definition base.h:102
Structure representing an individual request being passed to curl for processing.
Definition base.h:101
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
Definition dcursor.h:288
static void * fr_dcursor_remove(fr_dcursor_t *cursor)
Remove the current item.
Definition dcursor.h:480
static void * fr_dcursor_current(fr_dcursor_t *cursor)
Return the item the cursor current points to.
Definition dcursor.h:337
#define MEM(x)
Definition debug.h:46
#define RADIUSD_VERSION_STRING
Definition dependency.h:39
static fr_slen_t in
Definition dict.h:882
Test enumeration values.
Definition dict_test.h:92
talloc_free(hp)
char * fr_json_afrom_pair_list(TALLOC_CTX *ctx, fr_pair_list_t *vps, fr_json_format_t const *format)
Returns a JSON string of a list of value pairs.
Definition json.c:1321
int fr_curl_easy_tls_init(fr_curl_io_request_t *randle, fr_curl_tls_t const *conf)
Definition base.c:139
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition log.h:455
#define RWDEBUG(fmt,...)
Definition log.h:373
#define RDEBUG3(fmt,...)
Definition log.h:355
#define RERROR(fmt,...)
Definition log.h:310
#define RPWDEBUG(fmt,...)
Definition log.h:378
#define RINDENT()
Indent R* messages by one level.
Definition log.h:442
#define ROUND_UP(_num, _mul)
Round up - Works in all cases, but is slower.
Definition math.h:206
long int ssize_t
unsigned long int size_t
static size_t used
int strncasecmp(char *s1, char *s2, int n)
Definition missing.c:35
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
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, UNUSED bool tainted)
Convert string value to native attribute value.
Definition pair.c:2617
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1352
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:290
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
int fr_pair_value_bstrndup(fr_pair_t *vp, char const *src, size_t len, bool tainted)
Copy data into a "string" type value pair.
Definition pair.c:2812
void radius_pairmove(request_t *request, fr_pair_list_t *to, fr_pair_list_t *from)
Definition pairmove.c:44
#define fr_assert(_expr)
Definition rad_assert.h:37
#define pair_update_request(_attr, _da)
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
fr_dict_attr_t const * request_attr_reply
Definition request.c:44
static int rest_decode_post(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t const *section, request_t *request, fr_curl_io_request_t *randle, char *raw, size_t rawlen)
Converts POST response into fr_pair_ts and adds them to the request.
Definition rest.c:693
static int rest_decode_plain(UNUSED rlm_rest_t const *inst, UNUSED rlm_rest_section_t const *section, request_t *request, UNUSED fr_curl_io_request_t *randle, char *raw, size_t rawlen)
Converts plain response into a single fr_pair_t.
Definition rest.c:649
fr_table_num_sorted_t const http_auth_table[]
Definition rest.c:158
size_t len
Length of data.
Definition rest.c:208
char const * start
Start of the buffer.
Definition rest.c:206
#define CURLAUTH_DIGEST_IE
Definition rest.c:80
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:1790
ssize_t rest_uri_host_unescape(char **out, UNUSED rlm_rest_t const *inst, request_t *request, fr_curl_io_request_t *randle, char const *uri)
Unescapes the host portion of a URI string.
Definition rest.c:2169
int do_xlat
If true value will be expanded with xlat.
Definition rest.c:222
static size_t rest_encode_custom(void *out, size_t size, size_t nmemb, void *userdata)
Copies a pre-expanded xlat string to the output buffer.
Definition rest.c:291
fr_table_num_sorted_t const http_body_type_table[]
Conversion table for type config values.
Definition rest.c:142
static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userdata)
Encodes fr_pair_t linked list in JSON format.
Definition rest.c:527
#define CURLOPT_TLSAUTH_SRP
Definition rest.c:71
fr_token_t op
The operator that determines how the new VP.
Definition rest.c:225
#define CURLAUTH_BASIC
Definition rest.c:74
fr_table_num_sorted_t const http_method_table[]
Conversion table for method config values.
Definition rest.c:123
void * rest_mod_conn_create(TALLOC_CTX *ctx, void *instance, UNUSED fr_time_delta_t timeout)
Creates a new connection handle for use by the FR connection API.
Definition rest.c:256
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:1626
static bool rest_request_contains_header(fr_curl_io_request_t *randle, char const *header)
See if the list of headers already contains a header.
Definition rest.c:1745
static int json_pair_alloc(rlm_rest_t const *instance, rlm_rest_section_t const *section, request_t *request, json_object *object, UNUSED int level, int max)
Processes JSON response and converts it into multiple fr_pair_ts.
Definition rest.c:981
static void rest_request_init(rlm_rest_section_t const *section, request_t *request, rlm_rest_request_t *ctx)
(Re-)Initialises the data in a rlm_rest_request_t.
Definition rest.c:626
static ssize_t rest_request_encode_wrapper(char **out, UNUSED rlm_rest_t const *inst, rest_read_t func, size_t limit, void *userdata)
Emulates successive libcurl calls to an encoding function.
Definition rest.c:590
static int rest_decode_json(rlm_rest_t const *instance, rlm_rest_section_t const *section, request_t *request, UNUSED fr_curl_io_request_t *randle, char *raw, UNUSED size_t rawlen)
Converts JSON response into fr_pair_ts and adds them to the request.
Definition rest.c:1195
void rest_response_debug(request_t *request, fr_curl_io_request_t *handle)
Print out the response text.
Definition rest.c:1569
#define CURLAUTH_NTLM
Definition rest.c:86
static fr_pair_t * json_pair_alloc_leaf(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t const *section, TALLOC_CTX *ctx, request_t *request, fr_dict_attr_t const *da, json_flags_t *flags, json_object *leaf)
Converts JSON "value" key into fr_pair_t.
Definition rest.c:845
fr_table_num_sorted_t const http_content_type_table[]
Conversion table for "Content-Type" header values.
Definition rest.c:186
#define SET_AUTH_OPTION(_x, _y)
int is_json
If true value will be inserted as raw JSON.
Definition rest.c:223
const unsigned long http_curl_auth[REST_HTTP_AUTH_NUM_ENTRIES]
Definition rest.c:97
#define CURLAUTH_NTLM_WB
Definition rest.c:89
const http_body_type_t http_body_type_supported[REST_HTTP_BODY_NUM_ENTRIES]
Table of encoder/decoder support.
Definition rest.c:47
#define CURLAUTH_GSSNEGOTIATE
Definition rest.c:83
size_t rest_uri_escape(UNUSED request_t *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
URL encodes a string.
Definition rest.c:2143
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:1710
void rest_response_error(request_t *request, fr_curl_io_request_t *handle)
Print out the response text as error lines.
Definition rest.c:1544
size_t http_body_type_table_len
Definition rest.c:156
static int rest_request_config_body(module_ctx_t const *mctx, rlm_rest_section_t const *section, request_t *request, fr_curl_io_request_t *randle, rest_read_t func)
Configures body specific curlopts.
Definition rest.c:1652
static int _mod_conn_free(fr_curl_io_request_t *randle)
Frees a libcurl handle, and any additional memory used by context data.
Definition rest.c:235
static void rest_response_init(rlm_rest_section_t const *section, request_t *request, rlm_rest_response_t *ctx, http_body_type_t type, tmpl_t *header)
(Re-)Initialises the data in a rlm_rest_response_t.
Definition rest.c:1604
static size_t rest_response_body(void *in, size_t size, size_t nmemb, void *userdata)
Processes incoming HTTP body data from libcurl.
Definition rest.c:1462
static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userdata)
Encodes fr_pair_t linked list in POST format.
Definition rest.c:350
char const * p
how much text we've sent so far.
Definition rest.c:207
size_t http_auth_table_len
Definition rest.c:170
static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *userdata)
Processes incoming HTTP header data from libcurl.
Definition rest.c:1243
size_t http_content_type_table_len
Definition rest.c:199
#define CURLAUTH_DIGEST
Definition rest.c:77
size_t http_method_table_len
Definition rest.c:131
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:2090
Flags to control the conversion of JSON values to fr_pair_ts.
Definition rest.c:221
Function prototypes and datatypes for the REST (HTTP) transport.
rlm_rest_t const * instance
This instance of rlm_rest.
Definition rest.h:239
read_state_t state
Encoder state.
Definition rest.h:225
HIDDEN fr_dict_attr_t const * attr_rest_http_header
Definition rlm_rest.c:252
http_auth_type_t auth
HTTP auth type.
Definition rest.h:120
struct curl_slist * headers
Any HTTP headers which will be sent with the request.
Definition rest.h:263
tmpl_t * header
Where to create pairs representing HTTP response headers.
Definition rest.h:253
request_t * request
Current request.
Definition rest.h:242
char * buffer
Raw incoming HTTP data.
Definition rest.h:245
int code
HTTP Status Code.
Definition rest.h:249
write_state_t state
Decoder state.
Definition rest.h:243
bool fail_header_decode
Force header decoding to fail for debugging purposes.
Definition rest.h:179
char const * proxy
Send request via this proxy.
Definition rest.h:110
size_t used
Space used in buffer.
Definition rest.h:247
http_body_type_t type
HTTP Content Type.
Definition rest.h:250
char * body
Pointer to the buffer which contains body data/ Only used when not performing chunked encoding.
Definition rest.h:266
fr_curl_tls_t tls
Definition rest.h:147
#define REST_BODY_MAX_ATTRS
Definition rest.h:41
bool fail_body_decode
Force body decoding to fail for debugging purposes.
Definition rest.h:180
http_body_type_t force_to
Override the Content-Type header in the response to force decoding as a particular type.
Definition rest.h:129
http_method_t
Definition rest.h:43
@ REST_HTTP_METHOD_PATCH
Definition rest.h:48
@ REST_HTTP_METHOD_DELETE
Definition rest.h:49
@ REST_HTTP_METHOD_PUT
Definition rest.h:47
@ REST_HTTP_METHOD_POST
Definition rest.h:46
@ REST_HTTP_METHOD_UNKNOWN
Definition rest.h:44
@ REST_HTTP_METHOD_CUSTOM
Must always come last, should not be in method table.
Definition rest.h:50
@ REST_HTTP_METHOD_GET
Definition rest.h:45
size_t max_body_in
Maximum size of incoming data.
Definition rest.h:133
fr_dcursor_t cursor
Cursor pointing to the start of the list to encode.
Definition rest.h:227
http_body_type_t force_to
Force decoding the body type as a particular encoding.
Definition rest.h:251
http_body_type_t
Definition rest.h:53
@ REST_HTTP_BODY_HTML
Definition rest.h:64
@ REST_HTTP_BODY_PLAIN
Definition rest.h:65
@ REST_HTTP_BODY_JSON
Definition rest.h:61
@ REST_HTTP_BODY_INVALID
Definition rest.h:57
@ REST_HTTP_BODY_XML
Definition rest.h:62
@ REST_HTTP_BODY_UNSUPPORTED
Definition rest.h:55
@ REST_HTTP_BODY_YAML
Definition rest.h:63
@ REST_HTTP_BODY_POST
Definition rest.h:60
@ REST_HTTP_BODY_CUSTOM
Definition rest.h:59
@ REST_HTTP_BODY_NUM_ENTRIES
Definition rest.h:67
@ REST_HTTP_BODY_CRL
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:112
@ READ_STATE_ATTR_CONT
Definition rest.h:203
@ READ_STATE_ATTR_BEGIN
Definition rest.h:202
@ READ_STATE_END
Definition rest.h:204
@ READ_STATE_INIT
Definition rest.h:201
rlm_rest_section_t const * section
Section configuration.
Definition rest.h:240
char const * body_str
The string version of the encoding/content type.
Definition rest.h:115
#define REST_BODY_ALLOC_CHUNK
Definition rest.h:40
HIDDEN fr_dict_attr_t const * attr_rest_http_body
Definition rlm_rest.c:251
rlm_rest_section_request_t request
Request configuration.
Definition rest.h:144
struct rlm_rest_call_env_t::@190 response
rlm_rest_response_t response
Response context data.
Definition rest.h:270
size_t(* rest_read_t)(void *ptr, size_t size, size_t nmemb, void *userdata)
Definition rest.h:306
#define REST_BODY_MAX_LEN
Definition rest.h:39
rlm_rest_section_t const * section
Section configuration.
Definition rest.h:222
struct rlm_rest_call_env_t::@189 request
void * encoder
Encoder specific data.
Definition rest.h:231
bool accept_all
Accept all content types.
Definition rest.h:131
rlm_rest_request_t request
Request context data.
Definition rest.h:269
rlm_rest_section_response_t response
Response configuration.
Definition rest.h:145
fr_time_delta_t timeout
Timeout timeval.
Definition rest.h:142
@ WRITE_STATE_PARSE_HEADERS
Definition rest.h:212
@ WRITE_STATE_PARSE_CONTENT
Definition rest.h:213
char const * name
Section name.
Definition rest.h:140
http_auth_type_t
Definition rest.h:70
@ REST_HTTP_AUTH_NTLM_WB
Definition rest.h:79
@ REST_HTTP_AUTH_NUM_ENTRIES
Definition rest.h:82
@ REST_HTTP_AUTH_BASIC
Definition rest.h:74
@ REST_HTTP_AUTH_NTLM
Definition rest.h:78
@ REST_HTTP_AUTH_DIGEST
Definition rest.h:75
@ REST_HTTP_AUTH_TLS_SRP
Definition rest.h:73
@ REST_HTTP_AUTH_UNKNOWN
Definition rest.h:71
@ REST_HTTP_AUTH_GSSNEGOTIATE
Definition rest.h:77
@ REST_HTTP_AUTH_ANY
Definition rest.h:80
@ REST_HTTP_AUTH_NONE
Definition rest.h:72
@ REST_HTTP_AUTH_DIGEST_IE
Definition rest.h:76
@ REST_HTTP_AUTH_ANY_SAFE
Definition rest.h:81
size_t alloc
Space allocated for buffer.
Definition rest.h:246
request_t * request
Current request.
Definition rest.h:224
rlm_rest_t const * instance
This instance of rlm_rest.
Definition rest.h:221
char const * rest_no_proxy
Magic pointer value for determining if we should disable proxying.
Definition rlm_rest.c:78
uint32_t chunk
Max chunk-size (mainly for testing the encoders)
Definition rest.h:124
size_t chunk
Chunk size.
Definition rest.h:229
@ WRITE_STATE_INIT
Definition rlm_ftp.c:61
static char const * name
#define FR_SBUFF_OUT(_start, _len_or_end)
void * data
Module's instance data.
Definition module.h:293
static fr_dict_attr_t const * tmpl_list(tmpl_t const *vpt)
Definition tmpl.h:904
ssize_t tmpl_afrom_attr_str(TALLOC_CTX *ctx, tmpl_attr_error_t *err, tmpl_t **out, char const *name, tmpl_rules_t const *rules))
Parse a string into a TMPL_TYPE_ATTR_* type tmpl_t.
fr_pair_list_t * tmpl_list_head(request_t *request, fr_dict_attr_t const *list)
Resolve attribute fr_pair_list_t value to an attribute list.
Definition tmpl_eval.c:70
TALLOC_CTX * tmpl_list_ctx(request_t *request, fr_dict_attr_t const *list)
Return the correct TALLOC_CTX to alloc fr_pair_t in, for a list.
Definition tmpl_eval.c:110
int tmpl_request_ptr(request_t **request, FR_DLIST_HEAD(tmpl_request_list) const *rql)
Resolve a tmpl_request_ref_t to a request_t.
Definition tmpl_eval.c:163
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:801
static char const * tmpl_list_name(fr_dict_attr_t const *list, char const *def)
Return the name of a tmpl list or def if list not provided.
Definition tmpl.h:915
Optional arguments passed to vp_tmpl functions.
Definition tmpl.h:336
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:41
#define fr_skip_whitespace(_p)
Skip whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition skip.h:36
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition snprintf.c:689
return count
Definition module.c:155
eap_aka_sim_process_conf_t * inst
fr_aka_sim_id_type_t type
fr_pair_t * vp
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition strlcpy.c:34
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
#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_bstr_realloc(TALLOC_CTX *ctx, char *in, size_t inlen)
Trim a bstr (char) buffer.
Definition talloc.c:681
char * talloc_typed_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition talloc.c:545
#define talloc_get_type_abort_const
Definition talloc.h:110
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
Definition talloc.h:136
Simple time functions.
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
Definition time.h:637
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
fr_table_num_ordered_t const fr_tokens_table[]
Definition token.c:33
enum fr_token fr_token_t
@ T_BARE_WORD
Definition token.h:118
@ T_OP_EQ
Definition token.h:81
@ T_OP_SET
Definition token.h:82
@ T_OP_ADD_EQ
Definition token.h:67
ssize_t xlat_aeval(TALLOC_CTX *ctx, char **out, request_t *request, char const *fmt, xlat_escape_legacy_t escape, void const *escape_ctx))
Definition xlat_eval.c:1869
#define fr_pair_dcursor_by_da_init(_cursor, _list, _da)
Initialise a cursor that will return only attributes matching the specified fr_dict_attr_t.
Definition pair.h:639
ssize_t fr_pair_print_value_quoted(fr_sbuff_t *out, fr_pair_t const *vp, fr_token_t quote)
Print the value of an attribute to a string.
Definition pair_print.c:59
#define fr_pair_dcursor_init(_cursor, _list)
Initialises a special dcursor with callbacks that will maintain the attr sublists correctly.
Definition pair.h:604
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition types.h:454
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition value.c:3931
void fr_value_box_strdup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted)
Assign a buffer containing a nul terminated string to a box, but don't copy it.
Definition value.c:4714
void fr_value_box_bstrndup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Assign a string to to a fr_value_box_t.
Definition value.c:4910
static fr_slen_t data
Definition value.h:1340
#define FR_VALUE_BOX_INITIALISER_NULL(_vb)
A static initialiser for stack/globally allocated boxes.
Definition value.h:511
#define fr_box_strvalue_len(_val, _len)
Definition value.h:309
#define fr_value_box_init_null(_vb)
Initialise an empty/null box that will be filled later.
Definition value.h:616
#define fr_box_strvalue(_val)
Definition value.h:308
#define fr_box_time_delta(_val)
Definition value.h:366
#define fr_value_box(_box, _var, _tainted)
Automagically fill in a box, determining the value type from the type of the C variable.
Definition value.h:904
#define fr_value_box_list_foreach(_list_head, _iter)
Definition value.h:224
static size_t char ** out
Definition value.h:1030