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