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