The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 6c0e2a62d2c78ba4fec1de10c285283e2c0f6b49 $
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  */
25 RCSID("$Id: 6c0e2a62d2c78ba4fec1de10c285283e2c0f6b49 $")
26 
27 #define LOG_PREFIX mctx->inst->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/value.h>
39 
40 #include <talloc.h>
41 
42 #include "rest.h"
43 
44 /** Table of encoder/decoder support.
45  *
46  * Indexes in this table match the http_body_type_t enum, and should be
47  * updated if additional enum values are added.
48  *
49  * @see http_body_type_t
50  */
52  REST_HTTP_BODY_UNKNOWN, // REST_HTTP_BODY_UNKNOWN
53  REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_UNSUPPORTED
54  REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_UNAVAILABLE
55  REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_INVALID
56  REST_HTTP_BODY_NONE, // REST_HTTP_BODY_NONE
57  REST_HTTP_BODY_CUSTOM, // REST_HTTP_BODY_CUSTOM
58  REST_HTTP_BODY_POST, // REST_HTTP_BODY_POST
59 #ifdef HAVE_JSON
60  REST_HTTP_BODY_JSON, // REST_HTTP_BODY_JSON
61 #else
63 #endif
64  REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_XML
65  REST_HTTP_BODY_UNSUPPORTED, // REST_HTTP_BODY_YAML
66  REST_HTTP_BODY_INVALID, // REST_HTTP_BODY_HTML
67  REST_HTTP_BODY_PLAIN // REST_HTTP_BODY_PLAIN
68 };
69 
70 /*
71  * Lib CURL doesn't define symbols for unsupported auth methods
72  */
73 #ifndef CURLOPT_TLSAUTH_SRP
74 # define CURLOPT_TLSAUTH_SRP 0
75 #endif
76 #ifndef CURLAUTH_BASIC
77 # define CURLAUTH_BASIC 0
78 #endif
79 #ifndef CURLAUTH_DIGEST
80 # define CURLAUTH_DIGEST 0
81 #endif
82 #ifndef CURLAUTH_DIGEST_IE
83 # define CURLAUTH_DIGEST_IE 0
84 #endif
85 #ifndef CURLAUTH_GSSNEGOTIATE
86 # define CURLAUTH_GSSNEGOTIATE 0
87 #endif
88 #ifndef CURLAUTH_NTLM
89 # define CURLAUTH_NTLM 0
90 #endif
91 #ifndef CURLAUTH_NTLM_WB
92 # define CURLAUTH_NTLM_WB 0
93 #endif
94 
95 /*
96  * CURL headers do:
97  *
98  * #define curl_easy_setopt(handle,opt,param) curl_easy_setopt(handle,opt,param)
99  */
102  [REST_HTTP_AUTH_NONE] = 0,
110  [REST_HTTP_AUTH_ANY] = CURLAUTH_ANY,
111  [REST_HTTP_AUTH_ANY_SAFE] = CURLAUTH_ANYSAFE
112 };
113 
114 /** Conversion table for method config values.
115  *
116  * HTTP verb strings for http_method_t enum values. Used by libcurl in the
117  * status line of the outgoing HTTP header, by rest_response_header for decoding
118  * incoming HTTP responses, and by the configuration parser.
119  *
120  * @note must be kept in sync with http_method_t enum.
121  *
122  * @see http_method_t
123  * @see fr_table_value_by_str
124  * @see fr_table_str_by_value
125  */
127  { L("DELETE"), REST_HTTP_METHOD_DELETE },
128  { L("GET"), REST_HTTP_METHOD_GET },
129  { L("PATCH"), REST_HTTP_METHOD_PATCH },
130  { L("POST"), REST_HTTP_METHOD_POST },
131  { L("PUT"), REST_HTTP_METHOD_PUT },
132  { L("UNKNOWN"), REST_HTTP_METHOD_UNKNOWN }
133 };
135 
136 /** Conversion table for type config values.
137  *
138  * Textual names for http_body_type_t enum values, used by the
139  * configuration parser.
140  *
141  * @see http_body_Type_t
142  * @see fr_table_value_by_str
143  * @see fr_table_str_by_value
144  */
146  { L("html"), REST_HTTP_BODY_HTML },
147  { L("invalid"), REST_HTTP_BODY_INVALID },
148  { L("json"), REST_HTTP_BODY_JSON },
149  { L("none"), REST_HTTP_BODY_NONE },
150  { L("plain"), REST_HTTP_BODY_PLAIN },
151  { L("post"), REST_HTTP_BODY_POST },
152  { L("unavailable"), REST_HTTP_BODY_UNAVAILABLE },
153  { L("unknown"), REST_HTTP_BODY_UNKNOWN },
154  { L("unsupported"), REST_HTTP_BODY_UNSUPPORTED },
155  { L("xml"), REST_HTTP_BODY_XML },
156  { L("yaml"), REST_HTTP_BODY_YAML }
157 };
159 
161  { L("any"), REST_HTTP_AUTH_ANY },
162  { L("basic"), REST_HTTP_AUTH_BASIC },
163  { L("digest"), REST_HTTP_AUTH_DIGEST },
164  { L("digest-ie"), REST_HTTP_AUTH_DIGEST_IE },
165  { L("gss-negotiate"), REST_HTTP_AUTH_GSSNEGOTIATE },
166  { L("none"), REST_HTTP_AUTH_NONE },
167  { L("ntlm"), REST_HTTP_AUTH_NTLM },
168  { L("ntlm-winbind"), REST_HTTP_AUTH_NTLM_WB },
169  { L("safe"), REST_HTTP_AUTH_ANY_SAFE },
170  { L("srp"), REST_HTTP_AUTH_TLS_SRP }
171 };
173 
174 /** Conversion table for "Content-Type" header values.
175  *
176  * Used by rest_response_header for parsing incoming headers.
177  *
178  * Values we expect to see in the 'Content-Type:' header of the incoming
179  * response.
180  *
181  * Some data types (like YAML) do no have standard MIME types defined,
182  * so multiple types, are listed here.
183  *
184  * @see http_body_Type_t
185  * @see fr_table_value_by_str
186  * @see fr_table_str_by_value
187  */
189  { L("application/json"), REST_HTTP_BODY_JSON },
190  { L("application/x-www-form-urlencoded"), REST_HTTP_BODY_POST },
191  { L("application/x-yaml"), REST_HTTP_BODY_YAML },
192  { L("application/yaml"), REST_HTTP_BODY_YAML },
193  { L("text/html"), REST_HTTP_BODY_HTML },
194  { L("text/plain"), REST_HTTP_BODY_PLAIN },
195  { L("text/x-yaml"), REST_HTTP_BODY_YAML },
196  { L("text/xml"), REST_HTTP_BODY_XML },
197  { L("text/yaml"), REST_HTTP_BODY_YAML }
198 };
200 
201 /*
202  * Encoder specific structures.
203  * @todo split encoders/decoders into submodules.
204  */
205 typedef struct {
206  char const *start; //!< Start of the buffer.
207  char const *p; //!< how much text we've sent so far.
208  size_t len; //!< Length of data
210 
211 #ifdef HAVE_JSON
212 /** Flags to control the conversion of JSON values to fr_pair_ts.
213  *
214  * These fields are set when parsing the expanded format for value pairs in
215  * JSON, and control how json_pair_alloc_leaf and json_pair_alloc convert the JSON
216  * value, and move the new fr_pair_t into an attribute list.
217  *
218  * @see json_pair_alloc
219  * @see json_pair_alloc_leaf
220  */
221 typedef struct {
222  int do_xlat; //!< If true value will be expanded with xlat.
223  int is_json; //!< If true value will be inserted as raw JSON
224  // (multiple values not supported).
225  fr_token_t op; //!< The operator that determines how the new VP
226  // is processed. @see fr_tokens_table
227 } json_flags_t;
228 #endif
229 
230 /** Frees a libcurl handle, and any additional memory used by context data.
231  *
232  * @param[in] randle fr_curl_io_request_t to close and free.
233  * @return returns true.
234  */
236 {
237  curl_easy_cleanup(randle->candle);
238 
239  return 0;
240 }
241 
242 /** Creates a new connection handle for use by the FR connection API.
243  *
244  * Matches the fr_pool_connection_create_t function prototype, is passed to
245  * fr_pool_init, and called when a new connection is required by the
246  * connection pool API.
247  *
248  * Creates an instances of fr_curl_io_request_t, and rlm_rest_curl_context_t
249  * which hold the context data required for generating requests and parsing
250  * responses.
251  *
252  * If instance->connect_uri is not NULL libcurl will attempt to open a
253  * TCP socket to the server specified in the URI. This is done so that when the
254  * socket is first used, there will already be a cached TCP connection to the
255  * REST server associated with the curl handle.
256  *
257  * @see fr_pool_init
258  * @see fr_pool_connection_create_t
259  * @see connection.c
260  */
261 void *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  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  */
296 static 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  */
355 static 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 */
375  if (ctx->state == READ_STATE_INIT) ctx->state = READ_STATE_ATTR_BEGIN;
376 
377  while (freespace > 0) {
378  vp = fr_dcursor_current(&ctx->cursor);
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  */
532 static 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 
618  talloc_free(buff);
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  */
631 static 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  */
698 static 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  .prefix = TMPL_ATTR_REF_PREFIX_NO,
744  .dict_def = request->dict,
745  .list_def = request_attr_reply
746  }
747  }) <= 0) {
748  RPWDEBUG("Failed parsing attribute (skipping)");
749  talloc_free(dst);
750  goto skip;
751  }
752 
753  if (tmpl_request_ptr(&current, tmpl_request(dst)) < 0) {
754  RWDEBUG("Attribute name refers to outer request but not in a tunnel (skipping)");
755  talloc_free(dst);
756  goto skip;
757  }
758 
759  vps = tmpl_list_head(current, tmpl_list(dst));
760  if (!vps) {
761  RWDEBUG("List not valid in this context (skipping)");
762  talloc_free(dst);
763  goto skip;
764  }
765  ctx = tmpl_list_ctx(current, tmpl_list(dst));
766  da = tmpl_attr_tail_da(dst);
767 
768  fr_assert(vps);
769 
770  RINDENT();
771  RDEBUG3("Type : %s", fr_type_to_str(da->type));
772 
773  q = strchr(p, '&');
774  len = (!q) ? (rawlen - (p - raw)) : (unsigned)(q - p);
775 
776  value = curl_easy_unescape(candle, p, len, &curl_len);
777 
778  /*
779  * If we found a delimiter we want to skip over it,
780  * if we didn't we do *NOT* want to skip over the end
781  * of the buffer...
782  */
783  p += (!q) ? len : (len + 1);
784 
785  RDEBUG3("Length : %i", curl_len);
786  RDEBUG3("Value : \"%s\"", value);
787  REXDENT();
788 
789  talloc_free(dst); /* Free our temporary tmpl */
790 
791  RDEBUG2("Performing xlat expansion of response value");
792 
793  if (xlat_aeval(request, &expanded, request, value, NULL, NULL) < 0) goto skip;
794 
795  fr_assert(expanded);
796 
797  MEM(vp = fr_pair_afrom_da(ctx, da));
798  if (!vp) {
799  REDEBUG("Failed creating valuepair");
800  talloc_free(expanded);
801 
802  curl_free(name);
803  curl_free(value);
804 
805  return count;
806  }
807 
808  ret = fr_pair_value_from_str(vp, expanded, strlen(value), NULL, true);
809  TALLOC_FREE(expanded);
810  if (ret < 0) {
811  RWDEBUG("Incompatible value assignment, skipping");
812  talloc_free(vp);
813  goto skip;
814  }
815 
816  fr_pair_append(vps, vp);
817 
818  count++;
819 
820  skip:
821  curl_free(name);
822  curl_free(value);
823 
824  continue;
825  }
826 
827  if (!count) REDEBUG("Malformed POST data \"%s\"", raw);
828 
829  return count;
830 
831 }
832 
833 #ifdef HAVE_JSON
834 /** Converts JSON "value" key into fr_pair_t.
835  *
836  * If leaf is not in fact a leaf node, but contains JSON data, the data will
837  * written to the attribute in JSON string format.
838  *
839  * @param[in] instance configuration data.
840  * @param[in] section configuration data.
841  * @param[in] ctx to allocate new fr_pair_ts in.
842  * @param[in] request Current request.
843  * @param[in] da Attribute to create.
844  * @param[in] flags containing the operator other flags controlling value
845  * expansion.
846  * @param[in] leaf object containing the fr_pair_t value.
847  * @return
848  * - #fr_pair_t just created.
849  * - NULL on error.
850  */
852  TALLOC_CTX *ctx, request_t *request,
853  fr_dict_attr_t const *da, json_flags_t *flags, json_object *leaf)
854 {
855  char const *value;
856  char *expanded = NULL;
857  int ret;
858 
859  fr_pair_t *vp;
860 
862 
863  if (json_object_is_type(leaf, json_type_null)) {
864  RDEBUG3("Got null value for attribute \"%s\" (skipping)", da->name);
865  return NULL;
866  }
867 
868  MEM(vp = fr_pair_afrom_da(ctx, da));
869  if (!vp) {
870  RWDEBUG("Failed creating valuepair for attribute \"%s\" (skipping)", da->name);
871  return NULL;
872  }
873 
875 
876  switch (json_object_get_type(leaf)) {
877  case json_type_int:
878  if (flags->do_xlat) RWDEBUG("Ignoring do_xlat on 'int', attribute \"%s\"", da->name);
879  fr_value_box(&src, (int32_t)json_object_get_int(leaf), true);
880  break;
881 
882  case json_type_double:
883  if (flags->do_xlat) RWDEBUG("Ignoring do_xlat on 'double', attribute \"%s\"", da->name);
884  fr_value_box(&src, (double)json_object_get_double(leaf), true);
885  break;
886 
887  case json_type_string:
888  value = json_object_get_string(leaf);
889  if (flags->do_xlat && memchr(value, '%', json_object_get_string_len(leaf))) {
890  if (xlat_aeval(request, &expanded, request, value, NULL, NULL) < 0) {
891  talloc_free(vp);
892  return NULL;
893  }
894  fr_value_box_bstrndup_shallow(&src, NULL, expanded,
895  talloc_array_length(expanded) - 1, true);
896  } else {
898  json_object_get_string_len(leaf), true);
899  }
900  break;
901 
902  default:
903  {
904  char const *str;
905  if (flags->do_xlat) RWDEBUG("Ignoring do_xlat on 'object', attribute \"%s\"", da->name);
906 
907  /*
908  * Should encode any nested JSON structures into JSON strings.
909  *
910  * "I knew you liked JSON so I put JSON in your JSON!"
911  */
912  str = json_object_get_string(leaf);
913  if (!str) {
914  RWDEBUG("Failed getting string value for attribute \"%s\" (skipping)", da->name);
915  talloc_free(vp);
916  return NULL;
917  }
918  fr_value_box_strdup_shallow(&src, NULL, str, true);
919  }
920  }
921 
922  ret = fr_value_box_cast(vp, &vp->data, da->type, da, &src);
923  talloc_free(expanded);
924  if (ret < 0) {
925  RWDEBUG("Failed parsing value for attribute \"%s\" (skipping)", da->name);
926  talloc_free(vp);
927  return NULL;
928  }
929 
930  vp->op = flags->op;
931 
932  return vp;
933 }
934 
935 /** Processes JSON response and converts it into multiple fr_pair_ts
936  *
937  * Processes JSON attribute declarations in the format below. Will recurse when
938  * processing nested attributes. When processing nested attributes flags and
939  * operators from previous attributes are not inherited.
940  *
941  * JSON response format is:
942 @verbatim
943 {
944  "<attribute0>":{
945  "do_xlat":<bool>,
946  "is_json":<bool>,
947  "op":"<operator>",
948  "value":[<value0>,<value1>,<valueN>]
949  },
950  "<attribute1>":{
951  "value":{
952  "<nested-attribute0>":{
953  "op":"<operator>",
954  "value":<value0>
955  }
956  }
957  },
958  "<attribute2>":"<value0>",
959  "<attributeN>":[<value0>,<value1>,<valueN>]
960 }
961 @endverbatim
962  *
963  * JSON valuepair flags:
964  * - do_xlat (optional) Controls xlat expansion of values. Defaults to true.
965  * - is_json (optional) If true, any nested JSON data will be copied to the
966  * fr_pair_t in string form. Defaults to true.
967  * - op (optional) Controls how the attribute is inserted into
968  * the target list. Defaults to ':=' (T_OP_SET).
969  *
970  * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
971  * second and subsequent values in multivalued attributes. This does not work
972  * between multiple attribute declarations.
973  *
974  * @see fr_tokens_table
975  *
976  * @param[in] instance configuration data.
977  * @param[in] section configuration data.
978  * @param[in] request Current request.
979  * @param[in] object containing root node, or parent node.
980  * @param[in] level Current nesting level.
981  * @param[in] max counter, decremented after each fr_pair_t is created,
982  * when 0 no more attributes will be processed.
983  * @return
984  * - Number of attributes created.
985  * - < 0 on error.
986  */
987 static int json_pair_alloc(rlm_rest_t const *instance, rlm_rest_section_t const *section,
988  request_t *request, json_object *object, UNUSED int level, int max)
989 {
990  int max_attrs = max;
991  tmpl_t *dst = NULL;
992 
993  if (!json_object_is_type(object, json_type_object)) {
994 #ifdef HAVE_JSON_TYPE_TO_NAME
995  REDEBUG("Can't process VP container, expected JSON object"
996  "got \"%s\" (skipping)",
997  json_type_to_name(json_object_get_type(object)));
998 #else
999  REDEBUG("Can't process VP container, expected JSON object"
1000  " (skipping)");
1001 #endif
1002  return -1;
1003  }
1004 
1005  /*
1006  * Process VP container
1007  */
1008  {
1009  json_object_object_foreach(object, name, value) {
1010  int i = 0, elements;
1011  struct json_object *element, *tmp;
1012  TALLOC_CTX *ctx;
1013 
1014  json_flags_t flags = {
1015  .op = T_OP_SET,
1016  .do_xlat = 1,
1017  .is_json = 0
1018  };
1019 
1020  request_t *current = request;
1021  fr_pair_list_t *vps;
1022  fr_pair_t *vp = NULL;
1023 
1024  TALLOC_FREE(dst);
1025 
1026  /*
1027  * Resolve attribute name to a dictionary entry and pairlist.
1028  */
1029  RDEBUG2("Parsing attribute \"%s\"", name);
1030 
1031  if (tmpl_afrom_attr_str(request, NULL, &dst, name,
1032  &(tmpl_rules_t){
1033  .attr = {
1034  .prefix = TMPL_ATTR_REF_PREFIX_NO,
1035  .dict_def = request->dict,
1036  .list_def = request_attr_reply
1037  }
1038  }) <= 0) {
1039  RPWDEBUG("Failed parsing attribute (skipping)");
1040  continue;
1041  }
1042 
1043  if (tmpl_request_ptr(&current, tmpl_request(dst)) < 0) {
1044  RWDEBUG("Attribute name refers to outer request but not in a tunnel (skipping)");
1045  continue;
1046  }
1047 
1048  vps = tmpl_list_head(current, tmpl_list(dst));
1049  if (!vps) {
1050  RWDEBUG("List not valid in this context (skipping)");
1051  continue;
1052  }
1053  ctx = tmpl_list_ctx(current, tmpl_list(dst));
1054 
1055  /*
1056  * Alternative JSON structure which allows operator,
1057  * and other flags to be specified.
1058  *
1059  * "<name>":{
1060  * "do_xlat":<bool>,
1061  * "is_json":<bool>,
1062  * "op":"<op>",
1063  * "value":<value>
1064  * }
1065  *
1066  * Where value is a:
1067  * - [] Multivalued array
1068  * - {} Nested Valuepair
1069  * - * Integer or string value
1070  */
1071  if (json_object_is_type(value, json_type_object)) {
1072  /*
1073  * Process operator if present.
1074  */
1075  if (json_object_object_get_ex(value, "op", &tmp)) {
1076  flags.op = fr_table_value_by_str(fr_tokens_table, json_object_get_string(tmp), 0);
1077  if (!flags.op) {
1078  RWDEBUG("Invalid operator value \"%s\" (skipping)",
1079  json_object_get_string(tmp));
1080  continue;
1081  }
1082  }
1083 
1084  /*
1085  * Process optional do_xlat bool.
1086  */
1087  if (json_object_object_get_ex(value, "do_xlat", &tmp)) {
1088  flags.do_xlat = json_object_get_boolean(tmp);
1089  }
1090 
1091  /*
1092  * Process optional is_json bool.
1093  */
1094  if (json_object_object_get_ex(value, "is_json", &tmp)) {
1095  flags.is_json = json_object_get_boolean(tmp);
1096  }
1097 
1098  /*
1099  * Value key must be present if were using the expanded syntax.
1100  */
1101  if (!json_object_object_get_ex(value, "value", &tmp)) {
1102  RWDEBUG("Value key missing (skipping)");
1103  continue;
1104  }
1105 
1106  /*
1107  * The value field now becomes the key we're operating on
1108  */
1109  value = tmp;
1110  }
1111 
1112  /*
1113  * Setup fr_pair_afrom_da / recursion loop.
1114  */
1115  if (!flags.is_json && json_object_is_type(value, json_type_array)) {
1116  elements = json_object_array_length(value);
1117  if (!elements) {
1118  RWDEBUG("Zero length value array (skipping)");
1119  continue;
1120  }
1121  element = json_object_array_get_idx(value, 0);
1122  } else {
1123  elements = 1;
1124  element = value;
1125  }
1126 
1127  /*
1128  * A JSON 'value' key, may have multiple elements, iterate
1129  * over each of them, creating a new fr_pair_t.
1130  */
1131  do {
1132  fr_pair_list_t tmp_list;
1133 
1134  if (max_attrs-- <= 0) {
1135  RWDEBUG("At maximum attribute limit");
1136  talloc_free(dst);
1137  return max;
1138  }
1139 
1140  /*
1141  * Automagically switch the op for multivalued attributes.
1142  */
1143  if (((flags.op == T_OP_SET) || (flags.op == T_OP_EQ)) && (i >= 1)) {
1144  flags.op = T_OP_ADD_EQ;
1145  }
1146 
1147  if (json_object_is_type(element, json_type_object) && !flags.is_json) {
1148  /* TODO: Insert nested VP into VP structure...*/
1149  RWDEBUG("Found nested VP, these are not yet supported (skipping)");
1150 
1151  continue;
1152 
1153  /*
1154  vp = json_pair_alloc(instance, section,
1155  request, value,
1156  level + 1, max_attrs);*/
1157  } else {
1158  vp = json_pair_alloc_leaf(instance, section, ctx, request,
1159  tmpl_attr_tail_da(dst), &flags, element);
1160  if (!vp) continue;
1161  }
1162  RINDENT();
1163  RDEBUG2("&%s:%pP", tmpl_list_name(tmpl_list(dst), ""), vp);
1164  REXDENT();
1165 
1166  fr_pair_list_init(&tmp_list);
1167  fr_pair_append(&tmp_list, vp);
1168  radius_pairmove(current, vps, &tmp_list);
1169  /*
1170  * If we call json_object_array_get_idx on something that's not an array
1171  * the behaviour appears to be to occasionally segfault.
1172  */
1173  } while ((++i < elements) && (element = json_object_array_get_idx(value, i)));
1174  }
1175  }
1176 
1177  talloc_free(dst);
1178 
1179  return max - max_attrs;
1180 }
1181 
1182 /** Converts JSON response into fr_pair_ts and adds them to the request.
1183  *
1184  * Converts the raw JSON string into a json-c object tree and passes it to
1185  * json_pair_alloc. After the tree has been parsed json_object_put is called
1186  * which decrements the reference count of the root node by one, and frees
1187  * the entire tree.
1188  *
1189  * @see rest_encode_json
1190  * @see json_pair_alloc
1191  *
1192  * @param[in] instance configuration data.
1193  * @param[in] section configuration data.
1194  * @param[in,out] request Current request.
1195  * @param[in] randle REST handle.
1196  * @param[in] raw buffer containing JSON data.
1197  * @param[in] rawlen Length of data in raw buffer.
1198  * @return
1199  * - The number of #fr_pair_t processed.
1200  * - -1 on unrecoverable error.
1201  */
1202 static int rest_decode_json(rlm_rest_t const *instance, rlm_rest_section_t const *section,
1203  request_t *request, UNUSED fr_curl_io_request_t *randle, char *raw, UNUSED size_t rawlen)
1204 {
1205  char const *p = raw;
1206 
1207  struct json_object *json;
1208 
1209  int ret;
1210 
1211  /*
1212  * Empty response?
1213  */
1214  fr_skip_whitespace(p);
1215  if (*p == '\0') return 0;
1216 
1217  json = json_tokener_parse(p);
1218  if (!json) {
1219  REDEBUG("Malformed JSON data \"%s\"", raw);
1220  return -1;
1221  }
1222 
1223  ret = json_pair_alloc(instance, section, request, json, 0, REST_BODY_MAX_ATTRS);
1224 
1225  /*
1226  * Decrement reference count for root object, should free entire JSON tree.
1227  */
1228  json_object_put(json);
1229 
1230  return ret;
1231 }
1232 #endif
1233 
1234 /** Processes incoming HTTP header data from libcurl.
1235  *
1236  * Processes the status line, and Content-Type headers from the incoming HTTP
1237  * response.
1238  *
1239  * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1240  * by libcurl.
1241  *
1242  * @param[in] in Char buffer where inbound header data is written.
1243  * @param[in] size Multiply by nmemb to get the length of ptr.
1244  * @param[in] nmemb Multiply by size to get the length of ptr.
1245  * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1246  * @return
1247  * - Length of data processed.
1248  * - 0 on error.
1249  */
1250 static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *userdata)
1251 {
1252  rlm_rest_response_t *ctx = userdata;
1253  request_t *request = ctx->request; /* Used by RDEBUG */
1254 
1255  char const *start = (char *)in, *p = start, *end = p + (size * nmemb);
1256  char *q;
1257  size_t len;
1258 
1260 
1261 #ifndef NDEBUG
1262  if (ctx->instance->fail_header_decode) {
1263  REDEBUG("Forcing header decode failure");
1264  return 0;
1265  }
1266 #endif
1267 
1268  /*
1269  * This seems to be curl's indication there are no more header lines.
1270  */
1271  if (((end - p) == 2) && ((p[0] == '\r') && (p[1] == '\n'))) {
1272  /*
1273  * If we got a 100 Continue, we need to send additional payload data.
1274  * reset the state to WRITE_STATE_INIT, so that when were called again
1275  * we overwrite previous header data with that from the proper header.
1276  */
1277  if (ctx->code == 100) {
1278  RDEBUG2("Continuing...");
1279  ctx->state = WRITE_STATE_INIT;
1280  }
1281 
1282  return (end - start);
1283  }
1284 
1285  switch (ctx->state) {
1286  case WRITE_STATE_INIT:
1287  RDEBUG2("Processing response header");
1288 
1289  /*
1290  * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1291  *
1292  * "HTTP/1.1 " (9) + "100" (3) + "\r\n" (2) = 14
1293  * "HTTP/2 " (7) + "100" (3) + "\r\n" (2) = 12
1294  */
1295  if ((end - p) < 12) {
1296  REDEBUG("Malformed HTTP header: Status line too short");
1297  malformed:
1298  REDEBUG("Received %zu bytes of invalid header data: %pV",
1299  (end - start), fr_box_strvalue_len(in, (end - start)));
1300  ctx->code = 0;
1301 
1302  /*
1303  * Indicate we parsed the entire line, otherwise
1304  * bad things seem to happen internally with
1305  * libcurl when we try and use it with asynchronous
1306  * I/O handlers.
1307  */
1308  return (end - start);
1309  }
1310  /*
1311  * Check start of header matches...
1312  */
1313  if (strncasecmp("HTTP/", p, 5) != 0) {
1314  REDEBUG("Malformed HTTP header: Missing HTTP version");
1315  goto malformed;
1316  }
1317  p += 5;
1318 
1319  /*
1320  * Skip the version field, next space should mark start of reason_code.
1321  */
1322  q = memchr(p, ' ', (end - p));
1323  if (!q) {
1324  REDEBUG("Malformed HTTP header: Missing reason code");
1325  goto malformed;
1326  }
1327 
1328  p = q;
1329 
1330  /*
1331  * Process reason_code.
1332  *
1333  * " 100" (4) + "\r\n" (2) = 6
1334  */
1335  if ((end - p) < 6) {
1336  REDEBUG("Malformed HTTP header: Reason code too short");
1337  goto malformed;
1338  }
1339  p++;
1340 
1341  /*
1342  * "xxx( |\r)" status code and terminator.
1343  */
1344  if (!isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || !((p[3] == ' ') || (p[3] == '\r'))) {
1345  REDEBUG("Malformed HTTP header: Reason code malformed. "
1346  "Expected three digits then space or end of header, got \"%pV\"",
1347  fr_box_strvalue_len(p, 4));
1348  goto malformed;
1349  }
1350 
1351  /*
1352  * Convert status code into an integer value
1353  */
1354  q = NULL;
1355  ctx->code = (int)strtoul(p, &q, 10);
1356  fr_assert(q == (p + 3)); /* We check this above */
1357  p = q;
1358 
1359  /*
1360  * Process reason_phrase (if present).
1361  */
1362  RINDENT();
1363  if (*p == ' ') {
1364  p++;
1365  q = memchr(p, '\r', (end - p));
1366  if (!q) goto malformed;
1367  RDEBUG2("Status : %i (%pV)", ctx->code, fr_box_strvalue_len(p, q - p));
1368  } else {
1369  RDEBUG2("Status : %i", ctx->code);
1370  }
1371  REXDENT();
1372 
1374 
1375  break;
1376 
1378  if (((end - p) >= 14) &&
1379  (strncasecmp("Content-Type: ", p, 14) == 0)) {
1380  p += 14;
1381 
1382  /*
1383  * Check to see if there's a parameter separator.
1384  */
1385  q = memchr(p, ';', (end - p));
1386 
1387  /*
1388  * If there's not, find the end of this header.
1389  */
1390  if (!q) q = memchr(p, '\r', (end - p));
1391 
1392  len = (size_t)(!q ? (end - p) : (q - p));
1394 
1395  RINDENT();
1396  RDEBUG2("Type : %s (%pV)", fr_table_str_by_value(http_body_type_table, type, "<INVALID>"),
1397  fr_box_strvalue_len(p, len));
1398  REXDENT();
1399 
1400  /*
1401  * Assume the force_to value has already been validated.
1402  */
1403  if (ctx->force_to != REST_HTTP_BODY_UNKNOWN) {
1404  if (ctx->force_to != ctx->type) {
1405  RDEBUG3("Forcing body type to \"%s\"",
1407  ctx->type = ctx->force_to;
1408  }
1409  /*
1410  * Figure out if the type is supported by one of the decoders.
1411  */
1412  } else {
1414  switch (ctx->type) {
1416  RWDEBUG("Couldn't determine type, using the request's type \"%s\".",
1418  break;
1419 
1421  REDEBUG("Type \"%s\" is currently unsupported",
1423  break;
1424 
1426  REDEBUG("Type \"%s\" is unavailable, please rebuild this module with the required "
1427  "library", fr_table_str_by_value(http_body_type_table, type, "<INVALID>"));
1428  break;
1429 
1431  REDEBUG("Type \"%s\" is not a valid web API data markup format",
1433  break;
1434 
1435  /* supported type */
1436  default:
1437  break;
1438  }
1439  }
1440  }
1441  break;
1442 
1443  default:
1444  break;
1445  }
1446 
1447  return (end - start);
1448 }
1449 
1450 /** Processes incoming HTTP body data from libcurl.
1451  *
1452  * Writes incoming body data to an intermediary buffer for later parsing by
1453  * one of the decode functions.
1454  *
1455  * @param[in] in Char buffer where inbound header data is written
1456  * @param[in] size Multiply by nmemb to get the length of ptr.
1457  * @param[in] nmemb Multiply by size to get the length of ptr.
1458  * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1459  * @return
1460  * - Length of data processed.
1461  * - 0 on error.
1462  */
1463 static size_t rest_response_body(void *in, size_t size, size_t nmemb, void *userdata)
1464 {
1465  rlm_rest_response_t *ctx = userdata;
1466  request_t *request = ctx->request; /* Used by RDEBUG */
1467 
1468  char const *start = in, *p = start, *end = p + (size * nmemb);
1469  char *q;
1470 
1471  size_t needed;
1472 
1473  if (start == end) return 0; /* Nothing to process */
1474 
1475 #ifndef NDEBUG
1476  if (ctx->instance->fail_body_decode) {
1477  REDEBUG("Forcing body read failure");
1478  return 0;
1479  }
1480 #endif
1481 
1482  /*
1483  * Any post processing of headers should go here...
1484  */
1486 
1487  switch (ctx->type) {
1491  while ((q = memchr(p, '\n', (end - p)))) {
1492  REDEBUG("%pV", fr_box_strvalue_len(p, q - p));
1493  p = q + 1;
1494  }
1495 
1496  if (p != end) REDEBUG("%pV", fr_box_strvalue_len(p, end - p));
1497  break;
1498 
1499  case REST_HTTP_BODY_NONE:
1500  while ((q = memchr(p, '\n', (end - p)))) {
1501  RDEBUG3("%pV", fr_box_strvalue_len(p, q - p));
1502  p = q + 1;
1503  }
1504 
1505  if (p != end) RDEBUG3("%pV", fr_box_strvalue_len(p, end - p));
1506  break;
1507 
1508  default:
1509  {
1510  char *out_p;
1511 
1512  if ((ctx->section->response.max_body_in > 0) && ((ctx->used + (end - p)) > ctx->section->response.max_body_in)) {
1513  REDEBUG("Incoming data (%zu bytes) exceeds max_body_in (%zu bytes). "
1514  "Forcing body to type 'invalid'", ctx->used + (end - p), ctx->section->response.max_body_in);
1516  TALLOC_FREE(ctx->buffer);
1517  break;
1518  }
1519 
1520  needed = ROUND_UP(ctx->used + (end - p), REST_BODY_ALLOC_CHUNK);
1521  if (needed > ctx->alloc) {
1522  MEM(ctx->buffer = talloc_bstr_realloc(NULL, ctx->buffer, needed));
1523  ctx->alloc = needed;
1524  }
1525 
1526  out_p = ctx->buffer + ctx->used;
1527  memcpy(out_p, p, (end - p));
1528  out_p += (end - p);
1529  *out_p = '\0';
1530  ctx->used += (end - p);
1531  }
1532  break;
1533  }
1534 
1535  return (end - start);
1536 }
1537 
1538 /** Print out the response text as error lines
1539  *
1540  * @param request The Current request.
1541  * @param handle fr_curl_io_request_t used to execute the previous request.
1542  */
1544 {
1545  char const *p, *end;
1546  char *q;
1547  size_t len;
1548 
1549  len = rest_get_handle_data(&p, handle);
1550  if (len == 0) return;
1551 
1552  end = p + len;
1553 
1554  RERROR("Server returned:");
1555  while ((q = memchr(p, '\n', (end - p)))) {
1556  RERROR("%pV", fr_box_strvalue_len(p, q - p));
1557  p = q + 1;
1558  }
1559 
1560  if (p != end) RERROR("%pV", fr_box_strvalue_len(p, end - p));
1561 }
1562 
1563 /** Print out the response text
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 *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  RDEBUG3("Server returned:");
1580  while ((q = memchr(p, '\n', (end - p)))) {
1581  RDEBUG3("%pV", fr_box_strvalue_len(p, q - p));
1582  p = q + 1;
1583  }
1584 
1585  if (p != end) RDEBUG3("%pV", fr_box_strvalue_len(p, end - p));
1586 }
1587 
1588 /** (Re-)Initialises the data in a rlm_rest_response_t.
1589  *
1590  * This resets the values of the a rlm_rest_response_t to their defaults.
1591  * Must be called between encoding sessions.
1592  *
1593  * @see rest_response_body
1594  * @see rest_response_header
1595  *
1596  * @param[in] section that created the request.
1597  * @param[in] request Current request.
1598  * @param[in] ctx data to initialise.
1599  * @param[in] type Default http_body_type to use when decoding raw data, may be
1600  * overwritten by rest_response_header.
1601  * @param[in] header Where to write out headers, may be NULL.
1602  */
1603 static void rest_response_init(rlm_rest_section_t const *section,
1604  request_t *request, rlm_rest_response_t *ctx, http_body_type_t type, tmpl_t *header)
1605 {
1606  ctx->section = section;
1607  ctx->request = request;
1608  ctx->type = type;
1609  ctx->state = WRITE_STATE_INIT;
1610  ctx->alloc = 0;
1611  ctx->used = 0;
1612  ctx->code = 0;
1613  ctx->header = header;
1614  TALLOC_FREE(ctx->buffer);
1615 }
1616 
1617 /** Extracts pointer to buffer containing response data
1618  *
1619  * @param[out] out Where to write the pointer to the buffer.
1620  * @param[in] randle used for the last request.
1621  * @return
1622  * - 0 if no data i available.
1623  * - > 0 if data is available.
1624  */
1625 size_t rest_get_handle_data(char const **out, fr_curl_io_request_t *randle)
1626 {
1627  rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1628 
1629  if (!ctx->response.buffer) return 0;
1630 
1631  *out = ctx->response.buffer;
1632  return ctx->response.used;
1633 }
1634 
1635 /** Configures body specific curlopts.
1636  *
1637  * Configures libcurl handle to use either chunked mode, where the request
1638  * data will be sent using multiple HTTP requests, or contiguous mode where
1639  * the request data will be sent in a single HTTP request.
1640  *
1641  * @param[in] mctx Call data.
1642  * @param[in] section configuration data.
1643  * @param[in] request Current request.
1644  * @param[in] randle fr_curl_io_request_t to configure.
1645  * @param[in] func to pass to libcurl for chunked.
1646  * transfers (NULL if not using chunked mode).
1647  * @return
1648  * - 0 on success.
1649  * - -1 on failure.
1650  */
1651 static int rest_request_config_body(module_ctx_t const *mctx, rlm_rest_section_t const *section,
1652  request_t *request, fr_curl_io_request_t *randle, rest_read_t func)
1653 {
1654  rlm_rest_t const *inst = talloc_get_type_abort(mctx->inst->data, rlm_rest_t);
1655  rlm_rest_curl_context_t *uctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1656  ssize_t len;
1657 
1658  /*
1659  * We were provided with no read function, assume this means
1660  * no body should be sent.
1661  */
1662  if (!func) {
1663  FR_CURL_REQUEST_SET_OPTION(CURLOPT_POSTFIELDSIZE, 0);
1664  return 0;
1665  }
1666 
1667  /*
1668  * Chunked transfer encoding means the body will be sent in
1669  * multiple parts.
1670  */
1671  if (section->request.chunk > 0) {
1672  FR_CURL_REQUEST_SET_OPTION(CURLOPT_READDATA, &uctx->request);
1673  FR_CURL_REQUEST_SET_OPTION(CURLOPT_READFUNCTION, func);
1674 
1675  return 0;
1676  }
1677 
1678  /*
1679  * If were not doing chunked encoding then we read the entire
1680  * body into a buffer, and send it in one go.
1681  */
1682  len = rest_request_encode_wrapper(&uctx->body, inst, func, REST_BODY_MAX_LEN, &uctx->request);
1683  if (len <= 0) {
1684  REDEBUG("Failed creating HTTP body content");
1685  return -1;
1686  }
1687  RDEBUG2("Content-Length will be %zu bytes", len);
1688 
1689  fr_assert((len == 0) || (talloc_array_length(uctx->body) >= (size_t)len));
1690  FR_CURL_REQUEST_SET_OPTION(CURLOPT_POSTFIELDS, uctx->body);
1691  FR_CURL_REQUEST_SET_OPTION(CURLOPT_POSTFIELDSIZE, len);
1692 
1693  return 0;
1694 
1695 error:
1696  return -1;
1697 }
1698 
1699 /** Configures request curlopts.
1700  *
1701  * Configures libcurl handle setting various curlopts for things like local
1702  * client time, Content-Type, and other FreeRADIUS custom headers.
1703  *
1704  * Current FreeRADIUS custom headers are:
1705  * - X-FreeRADIUS-Section The module section being processed.
1706  * - X-FreeRADIUS-Server The current virtual server the request_t is
1707  * passing through.
1708  *
1709  * Sets up callbacks for all response processing (buffers and body data).
1710  *
1711  * @param[in] mctx call data.
1712  * @param[in] section configuration data.
1713  * @param[in] randle to configure.
1714  * @param[in] request Current request.
1715  * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1716  * @param[in] type Content-Type for request encoding, also sets
1717  * the default for decoding.
1718  * @param[in] uri buffer containing the expanded URI to send the request to.
1719  * @param[in] body_data (optional) custom body data. Must persist whilst we're
1720  * writing data out to the socket. Must be a talloced buffer
1721  * which is \0 terminated.
1722  * @return
1723  * - 0 on success (all opts configured).
1724  * - -1 on failure.
1725  */
1726 int rest_request_config(module_ctx_t const *mctx, rlm_rest_section_t const *section,
1727  request_t *request, fr_curl_io_request_t *randle, http_method_t method,
1729  char const *uri, char const *body_data)
1730 {
1731  rlm_rest_t const *inst = talloc_get_type_abort(mctx->inst->data, rlm_rest_t);
1732  rlm_rest_call_env_t *call_env = talloc_get_type_abort(mctx->env_data, rlm_rest_call_env_t);
1733  rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
1734  CURL *candle = randle->candle;
1736 
1737  http_auth_type_t auth = section->request.auth;
1738 
1739  CURLcode ret = CURLE_OK;
1740  char const *option;
1741 
1742  char buffer[512];
1743  bool content_type_set = false;
1744 
1745  fr_assert(candle);
1746 
1747  buffer[(sizeof(buffer) - 1)] = '\0';
1748 
1749  /*
1750  * Control which HTTP version we're going to use
1751  */
1752  if (inst->http_negotiation != CURL_HTTP_VERSION_NONE) FR_CURL_REQUEST_SET_OPTION(CURLOPT_HTTP_VERSION, inst->http_negotiation);
1753 
1754  /*
1755  * Setup any header options and generic headers.
1756  */
1757  FR_CURL_REQUEST_SET_OPTION(CURLOPT_URL, uri);
1758 #if CURL_AT_LEAST_VERSION(7,85,0)
1759  FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS_STR, "http,https");
1760 #else
1761  FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
1762 #endif
1763  if (section->request.proxy) {
1764  if (section->request.proxy == rest_no_proxy) {
1765  FR_CURL_REQUEST_SET_OPTION(CURLOPT_NOPROXY, "*");
1766  } else {
1767  FR_CURL_REQUEST_SET_OPTION(CURLOPT_PROXY, section->request.proxy);
1768  }
1769  }
1770  FR_CURL_REQUEST_SET_OPTION(CURLOPT_NOSIGNAL, 1L);
1771  FR_CURL_REQUEST_SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING);
1772 
1773  timeout = inst->conn_config.connect_timeout;
1774  RDEBUG3("Connect timeout is %pVs, request timeout is %pVs",
1776  FR_CURL_REQUEST_SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, fr_time_delta_to_msec(timeout));
1777  FR_CURL_REQUEST_SET_OPTION(CURLOPT_TIMEOUT_MS, fr_time_delta_to_msec(section->timeout));
1778 
1779  /*
1780  * FreeRADIUS custom headers
1781  */
1782  RDEBUG3("Adding custom headers:");
1783  snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Section: %s", section->name);
1784 
1785  RINDENT();
1786  RDEBUG3("%s", buffer);
1787  REXDENT();
1788  ctx->headers = curl_slist_append(ctx->headers, buffer);
1789  if (!ctx->headers) goto error_header;
1790 
1791  snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Server: %s", cf_section_name2(unlang_call_current(request)));
1792  RINDENT();
1793  RDEBUG3("%s", buffer);
1794  REXDENT();
1795  ctx->headers = curl_slist_append(ctx->headers, buffer);
1796  if (!ctx->headers) goto error_header;
1797 
1798  /*
1799  * Add in the section headers
1800  */
1801  if (call_env->request.header) {
1802  size_t len = talloc_array_length(call_env->request.header), i;
1803  for (i = 0; i < len; i++) {
1804  fr_value_box_list_foreach(&call_env->request.header[i], header) {
1805  RINDENT();
1806  RDEBUG3("%pV", header);
1807  REXDENT();
1808 
1809  ctx->headers = curl_slist_append(ctx->headers, header->vb_strvalue);
1810  }
1811  }
1812  }
1813 
1814  /*
1815  * Add in dynamic headers from the request
1816  */
1817  {
1818  fr_pair_t *header;
1819  fr_dcursor_t headers;
1820 
1821  for (header = fr_pair_dcursor_by_da_init(&headers, &request->control_pairs, attr_rest_http_header);
1822  header;
1823  header = fr_dcursor_current(&headers)) {
1824  header = fr_dcursor_remove(&headers);
1825  if (!strchr(header->vp_strvalue, ':')) {
1826  RWDEBUG("Invalid HTTP header \"%s\" must be in format '<attribute>: <value>'. Skipping...",
1827  header->vp_strvalue);
1828  talloc_free(header);
1829  continue;
1830  }
1831  RINDENT();
1832  RDEBUG3("%pV", &header->data);
1833  REXDENT();
1834 
1835  ctx->headers = curl_slist_append(ctx->headers, header->vp_strvalue);
1836 
1837  /*
1838  * Set content-type based on a corresponding REST-HTTP-Header attribute, if provided.
1839  */
1840  if (!content_type_set && (strncasecmp(header->vp_strvalue, "content-type:", sizeof("content-type:") - 1) == 0)) {
1841  char const *content_type = header->vp_strvalue + (sizeof("content-type:") - 1);
1842 
1843  while (isspace((uint8_t)*content_type)) content_type++;
1844 
1845  RDEBUG3("Request body content-type provided as \"%s\"", content_type);
1846 
1847  content_type_set = true;
1848  }
1849 
1850  talloc_free(header);
1851  }
1852  }
1853 
1854  if (!content_type_set) {
1855  /*
1856  * HTTP/1.1 doesn't require a content type so only set it
1857  * if where body type requires it, and we haven't set one
1858  * already from attributes.
1859  */
1860  if (type != REST_HTTP_BODY_NONE) {
1861  char const *content_type = fr_table_str_by_value(http_content_type_table, type, section->request.body_str);
1862  snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type);
1863  ctx->headers = curl_slist_append(ctx->headers, buffer);
1864  if (!ctx->headers) {
1865  error_header:
1866  REDEBUG("Failed creating header");
1867  return -1;
1868  }
1869 
1870  RDEBUG3("Request body content-type will be \"%s\"", content_type);
1871  }
1872  }
1873 
1874  /*
1875  * Configure HTTP verb (GET, POST, PUT, PATCH, DELETE, other...)
1876  */
1877  switch (method) {
1878  case REST_HTTP_METHOD_GET:
1879  FR_CURL_REQUEST_SET_OPTION(CURLOPT_HTTPGET, 1L);
1880  break;
1881 
1882  case REST_HTTP_METHOD_POST:
1883  FR_CURL_REQUEST_SET_OPTION(CURLOPT_POST, 1L);
1884  break;
1885 
1886  case REST_HTTP_METHOD_PUT:
1887  /*
1888  * Do not set CURLOPT_PUT, this will cause libcurl
1889  * to ignore CURLOPT_POSTFIELDs and attempt to read
1890  * whatever was set with CURLOPT_READDATA, which by
1891  * default is stdin.
1892  *
1893  * This is many cases will cause the server to block,
1894  * indefinitely.
1895  */
1896  FR_CURL_REQUEST_SET_OPTION(CURLOPT_CUSTOMREQUEST, "PUT");
1897  break;
1898 
1900  FR_CURL_REQUEST_SET_OPTION(CURLOPT_CUSTOMREQUEST, "PATCH");
1901  break;
1902 
1904  FR_CURL_REQUEST_SET_OPTION(CURLOPT_CUSTOMREQUEST, "DELETE");
1905  break;
1906 
1908  FR_CURL_REQUEST_SET_OPTION(CURLOPT_CUSTOMREQUEST, section->request.method_str);
1909  break;
1910 
1911  default:
1912  fr_assert(0);
1913  break;
1914  }
1915 
1916  /*
1917  * Set user based authentication parameters
1918  */
1919  if (auth > REST_HTTP_AUTH_NONE) {
1920 
1921 #define SET_AUTH_OPTION(_x, _y)\
1922 do {\
1923  if ((ret = curl_easy_setopt(candle, _x, _y)) != CURLE_OK) {\
1924  option = STRINGIFY(_x);\
1925  REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret); \
1926  goto error;\
1927  }\
1928 } while (0)
1929  RDEBUG3("Configuring HTTP auth type %s, user \"%pV\", password \"%pV\"",
1930  fr_table_str_by_value(http_auth_table, auth, "<INVALID>"),
1931  call_env->request.username ? call_env->request.username : fr_box_strvalue("(none)"),
1932  call_env->request.password ? call_env->request.password : fr_box_strvalue("(none)"));
1933 
1934  /*
1935  * FIXME - We probably want to escape \0s here...
1936  */
1937  if ((auth >= REST_HTTP_AUTH_BASIC) && (auth <= REST_HTTP_AUTH_ANY_SAFE)) {
1938  SET_AUTH_OPTION(CURLOPT_HTTPAUTH, http_curl_auth[auth]);
1939  if (call_env->request.username) SET_AUTH_OPTION(CURLOPT_USERNAME, call_env->request.username->vb_strvalue);
1940  if (call_env->request.password) SET_AUTH_OPTION(CURLOPT_PASSWORD, call_env->request.password->vb_strvalue);
1941  } else if (auth == REST_HTTP_AUTH_TLS_SRP) {
1942  SET_AUTH_OPTION(CURLOPT_TLSAUTH_TYPE, http_curl_auth[auth]);
1943  if (call_env->request.username) SET_AUTH_OPTION(CURLOPT_TLSAUTH_USERNAME, call_env->request.username->vb_strvalue);
1944  if (call_env->request.password) SET_AUTH_OPTION(CURLOPT_TLSAUTH_PASSWORD, call_env->request.password->vb_strvalue);
1945  }
1946  }
1947 
1948  /*
1949  * Set SSL/TLS authentication parameters
1950  */
1951  fr_curl_easy_tls_init(randle, &section->tls);
1952 
1953  /*
1954  * Tell CURL how to get HTTP body content, and how to process incoming data.
1955  */
1956  rest_response_init(section, request, &ctx->response, type, call_env->response.header);
1957 
1958  FR_CURL_REQUEST_SET_OPTION(CURLOPT_HEADERFUNCTION, rest_response_header);
1959  FR_CURL_REQUEST_SET_OPTION(CURLOPT_HEADERDATA, &ctx->response);
1960  FR_CURL_REQUEST_SET_OPTION(CURLOPT_WRITEFUNCTION, rest_response_body);
1961  FR_CURL_REQUEST_SET_OPTION(CURLOPT_WRITEDATA, &ctx->response);
1962 
1963  /*
1964  * Force parsing the body text as a particular encoding.
1965  */
1966  ctx->response.force_to = section->response.force_to;
1967 
1968  switch (method) {
1969  case REST_HTTP_METHOD_GET:
1971  RDEBUG3("Using a HTTP method which does not require a body. Forcing request body type to \"none\"");
1972  goto finish;
1973 
1974  case REST_HTTP_METHOD_POST:
1975  case REST_HTTP_METHOD_PUT:
1978  if (section->request.chunk > 0) {
1979  ctx->request.chunk = section->request.chunk;
1980 
1981  ctx->headers = curl_slist_append(ctx->headers, "Expect:");
1982  if (!ctx->headers) goto error_header;
1983 
1984  ctx->headers = curl_slist_append(ctx->headers, "Transfer-Encoding: chunked");
1985  if (!ctx->headers) goto error_header;
1986  }
1987 
1988  break;
1989 
1990  default:
1991  fr_assert(0);
1992  }
1993 
1994  /*
1995  * Setup encoder specific options
1996  */
1997  switch (type) {
1998  case REST_HTTP_BODY_NONE:
1999  if (rest_request_config_body(mctx, section, request, randle, NULL) < 0) return -1;
2000 
2001  break;
2002 
2003  case REST_HTTP_BODY_CUSTOM:
2004  {
2006 
2007  data = talloc_zero(request, rest_custom_data_t);
2008  data->p = data->start = body_data;
2009  data->len = talloc_strlen(body_data);
2010 
2011  /* Use the encoder specific pointer to store the data we need to encode */
2012  ctx->request.encoder = data;
2013  if (rest_request_config_body(mctx, section, request, randle, rest_encode_custom) < 0) {
2014  TALLOC_FREE(ctx->request.encoder);
2015  return -1;
2016  }
2017  }
2018  break;
2019 
2020 #ifdef HAVE_JSON
2021  case REST_HTTP_BODY_JSON:
2022  {
2024 
2025  data = talloc_zero(request, rest_custom_data_t);
2026  ctx->request.encoder = data;
2027 
2028  rest_request_init(section, request, &ctx->request);
2029 
2030  if (rest_request_config_body(mctx, section, request, randle, rest_encode_json) < 0) return -1;
2031  }
2032 
2033  break;
2034 #endif
2035 
2036  case REST_HTTP_BODY_POST:
2037  rest_request_init(section, request, &ctx->request);
2038  fr_pair_dcursor_init(&(ctx->request.cursor), &request->request_pairs);
2039 
2040  if (rest_request_config_body(mctx, section, request, randle, rest_encode_post) < 0) return -1;
2041 
2042  break;
2043 
2044  default:
2045  fr_assert(0);
2046  }
2047 
2048 
2049 finish:
2050  FR_CURL_REQUEST_SET_OPTION(CURLOPT_HTTPHEADER, ctx->headers);
2051 
2052  return 0;
2053 
2054 error:
2055  return -1;
2056 }
2057 
2058 /** Sends the response to the correct decode function.
2059  *
2060  * Uses the Content-Type information written in rest_response_header to
2061  * determine the correct decode function to use. The decode function will
2062  * then convert the raw received data into fr_pair_ts.
2063  *
2064  * @param[in] instance configuration data.
2065  * @param[in] section configuration data.
2066  * @param[in] request Current request.
2067  * @param[in] randle to use.
2068  * @return
2069  * - 0 on success.
2070  * - -1 on failure.
2071  */
2072 int rest_response_decode(rlm_rest_t const *instance, rlm_rest_section_t const *section,
2073  request_t *request, fr_curl_io_request_t *randle)
2074 {
2075  rlm_rest_curl_context_t *ctx = talloc_get_type_abort(randle->uctx, rlm_rest_curl_context_t);
2076 
2077  int ret = -1; /* -Wsometimes-uninitialized */
2078 
2079  if (!ctx->response.buffer) {
2080  RDEBUG2("Skipping attribute processing, no valid body data received");
2081  return 0;
2082  }
2083 
2084  switch (ctx->response.type) {
2085  case REST_HTTP_BODY_NONE:
2086  return 0;
2087 
2088  case REST_HTTP_BODY_PLAIN:
2089  ret = rest_decode_plain(instance, section, request, randle, ctx->response.buffer, ctx->response.used);
2090  break;
2091 
2092  case REST_HTTP_BODY_POST:
2093  ret = rest_decode_post(instance, section, request, randle, ctx->response.buffer, ctx->response.used);
2094  break;
2095 
2096 #ifdef HAVE_JSON
2097  case REST_HTTP_BODY_JSON:
2098  ret = rest_decode_json(instance, section, request, randle, ctx->response.buffer, ctx->response.used);
2099  break;
2100 #endif
2101 
2105  return -1;
2106 
2107  default:
2108  fr_assert(0);
2109  }
2110 
2111  return ret;
2112 }
2113 
2114 /** URL encodes a string.
2115  *
2116  * Encode special chars as per RFC 3986 section 4.
2117  *
2118  * @param[in] request Current request.
2119  * @param[out] out Where to write escaped string.
2120  * @param[in] outlen Size of out buffer.
2121  * @param[in] raw string to be urlencoded.
2122  * @param[in] arg pointer, gives context for escaping.
2123  * @return length of data written to out (excluding NULL).
2124  */
2125 size_t rest_uri_escape(UNUSED request_t *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
2126 {
2127  char *escaped;
2128 
2129  escaped = curl_escape(raw, 0);
2130  strlcpy(out, escaped, outlen);
2131  curl_free(escaped);
2132 
2133  return strlen(out);
2134 }
2135 
2136 /** Unescapes the host portion of a URI string
2137  *
2138  * This is required because the xlat functions which operate on the input string
2139  * cannot distinguish between host and path components.
2140  *
2141  * @param[out] out Where to write the pointer to the new
2142  * buffer containing the escaped URI.
2143  * @param[in] inst of rlm_rest.
2144  * @param[in] request Current request
2145  * @param[in] randle to use.
2146  * @param[in] uri configuration data.
2147  * @return
2148  * - Length of data written to buffer (excluding NULL).
2149  * - < 0 if an error occurred.
2150  */
2152  fr_curl_io_request_t *randle, char const *uri)
2153 {
2154  CURL *candle = randle->candle;
2155 
2156  char const *p, *q;
2157 
2158  char *scheme;
2159 
2160  ssize_t len;
2161 
2162  p = uri;
2163 
2164  /*
2165  * All URLs must contain at least <scheme>://<server>/
2166  */
2167  p = strchr(p, ':');
2168  if (!p || (*++p != '/') || (*++p != '/')) {
2169  malformed:
2170  REDEBUG("URI \"%s\" is malformed, can't find start of path", uri);
2171  return -1;
2172  }
2173  p = strchr(p + 1, '/');
2174  if (!p) {
2175  goto malformed;
2176  }
2177 
2178  len = (p - uri);
2179 
2180  /*
2181  * Unescape any special sequences in the first part of the URI
2182  */
2183  scheme = curl_easy_unescape(candle, uri, len, NULL);
2184  if (!scheme) {
2185  REDEBUG("Error unescaping host");
2186  return -1;
2187  }
2188 
2189  /*
2190  * URIs can't contain spaces, so anything after the space must
2191  * be something else.
2192  */
2193  q = strchr(p, ' ');
2194  *out = q ? talloc_typed_asprintf(request, "%s%.*s", scheme, (int)(q - p), p) :
2195  talloc_typed_asprintf(request, "%s%s", scheme, p);
2196 
2197  MEM(*out);
2198  curl_free(scheme);
2199 
2200  return talloc_array_length(*out) - 1; /* array_length includes \0 */
2201 }
static int const char char buffer[256]
Definition: acutest.h:574
#define RCSID(id)
Definition: build.h:444
#define L(_str)
Helper for initialising arrays of string literals.
Definition: build.h:207
#define UNUSED
Definition: build.h:313
#define NUM_ELEMENTS(_t)
Definition: build.h:335
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:1126
#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:530
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_remove(fr_dcursor_t *cursor)
Remove the current item.
Definition: dcursor.h:479
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
Definition: dcursor.h:287
static void * fr_dcursor_current(fr_dcursor_t *cursor)
Return the item the cursor current points to.
Definition: dcursor.h:336
#define RADIUSD_VERSION_STRING
Definition: dependency.h:39
static fr_time_delta_t timeout
Definition: dhcpclient.c:54
static fr_slen_t in
Definition: dict.h:645
Test enumeration values.
Definition: dict_test.h:92
void *_CONST data
Module instance's parsed configuration.
Definition: dl_module.h:165
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:1239
int fr_curl_easy_tls_init(fr_curl_io_request_t *randle, fr_curl_tls_t const *conf)
Definition: base.c:90
#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
@ TMPL_ATTR_REF_PREFIX_NO
Attribute refs have no '&' prefix.
Definition: merged_model.c:230
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
unsigned long int size_t
Definition: merged_model.c:25
static size_t used
#define fr_skip_whitespace(_p)
Skip whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition: misc.h:59
int strncasecmp(char *s1, char *s2, int n)
Definition: missing.c:36
void * env_data
Per call environment data.
Definition: module_ctx.h:44
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:42
Temporary structure to hold arguments for module calls.
Definition: module_ctx.h:41
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:278
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:1340
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:2781
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, bool tainted)
Convert string value to native attribute value.
Definition: pair.c:2586
void radius_pairmove(request_t *request, fr_pair_list_t *to, fr_pair_list_t *from)
Definition: pairmove.c:46
static rc_request_t * current
Definition: radclient-ng.c:97
#define pair_update_request(_attr, _da)
Definition: radclient-ng.c:60
#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:42
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:160
size_t len
Length of data.
Definition: rest.c:208
char const * start
Start of the buffer.
Definition: rest.c:206
#define CURLAUTH_DIGEST_IE
Definition: rest.c:83
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:1726
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:2151
int do_xlat
If true value will be expanded with xlat.
Definition: rest.c:222
static size_t rest_encode_custom(void *out, size_t size, size_t nmemb, void *userdata)
Copies a pre-expanded xlat string to the output buffer.
Definition: rest.c:296
fr_table_num_sorted_t const http_body_type_table[]
Conversion table for type config values.
Definition: rest.c:145
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:74
fr_token_t op
The operator that determines how the new VP.
Definition: rest.c:225
#define CURLAUTH_BASIC
Definition: rest.c:77
fr_table_num_sorted_t const http_method_table[]
Conversion table for method config values.
Definition: rest.c:126
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:851
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:1625
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:987
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:1202
void rest_response_debug(request_t *request, fr_curl_io_request_t *handle)
Print out the response text.
Definition: rest.c:1568
#define CURLAUTH_NTLM
Definition: rest.c:89
fr_table_num_sorted_t const http_content_type_table[]
Conversion table for "Content-Type" header values.
Definition: rest.c:188
#define SET_AUTH_OPTION(_x, _y)
int is_json
If true value will be inserted as raw JSON.
Definition: rest.c:223
const unsigned long http_curl_auth[REST_HTTP_AUTH_NUM_ENTRIES]
Definition: rest.c:100
#define CURLAUTH_NTLM_WB
Definition: rest.c:92
const http_body_type_t http_body_type_supported[REST_HTTP_BODY_NUM_ENTRIES]
Table of encoder/decoder support.
Definition: rest.c:51
#define CURLAUTH_GSSNEGOTIATE
Definition: rest.c:86
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:2125
void rest_response_error(request_t *request, fr_curl_io_request_t *handle)
Print out the response text as error lines.
Definition: rest.c:1543
size_t http_body_type_table_len
Definition: rest.c:158
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:1651
static int _mod_conn_free(fr_curl_io_request_t *randle)
Frees a libcurl handle, and any additional memory used by context data.
Definition: rest.c:235
static void rest_response_init(rlm_rest_section_t const *section, request_t *request, rlm_rest_response_t *ctx, http_body_type_t type, tmpl_t *header)
(Re-)Initialises the data in a rlm_rest_response_t.
Definition: rest.c:1603
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:1463
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:207
size_t http_auth_table_len
Definition: rest.c:172
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:1250
size_t http_content_type_table_len
Definition: rest.c:199
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
#define CURLAUTH_DIGEST
Definition: rest.c:80
size_t http_method_table_len
Definition: rest.c:134
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:2072
Flags to control the conversion of JSON values to fr_pair_ts.
Definition: rest.c:221
Function prototypes and datatypes for the REST (HTTP) transport.
rlm_rest_t const * instance
This instance of rlm_rest.
Definition: rest.h:229
read_state_t state
Encoder state.
Definition: rest.h:215
HIDDEN fr_dict_attr_t const * attr_rest_http_header
Definition: rlm_rest.c:280
http_auth_type_t auth
HTTP auth type.
Definition: rest.h:119
struct curl_slist * headers
Any HTTP headers which will be sent with the request.
Definition: rest.h:253
tmpl_t * header
Where to create pairs representing HTTP response headers.
Definition: rest.h:243
request_t * request
Current request.
Definition: rest.h:232
char * buffer
Raw incoming HTTP data.
Definition: rest.h:235
int code
HTTP Status Code.
Definition: rest.h:239
write_state_t state
Decoder state.
Definition: rest.h:233
bool fail_header_decode
Force header decoding to fail for debugging purposes.
Definition: rest.h:169
char const * proxy
Send request via this proxy.
Definition: rest.h:109
size_t used
Space used in buffer.
Definition: rest.h:237
http_body_type_t type
HTTP Content Type.
Definition: rest.h:240
char * body
Pointer to the buffer which contains body data/ Only used when not performing chunked encoding.
Definition: rest.h:256
fr_curl_tls_t tls
Definition: rest.h:145
#define REST_BODY_MAX_ATTRS
Definition: rest.h:41
bool fail_body_decode
Force body decoding to fail for debugging purposes.
Definition: rest.h:170
http_body_type_t force_to
Override the Content-Type header in the response to force decoding as a particular type.
Definition: rest.h:128
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:131
struct rlm_rest_call_env_t::@157 request
fr_dcursor_t cursor
Cursor pointing to the start of the list to encode.
Definition: rest.h:217
http_body_type_t force_to
Force decoding the body type as a particular encoding.
Definition: rest.h:241
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:66
@ REST_HTTP_BODY_UNKNOWN
Definition: rest.h:54
@ REST_HTTP_BODY_NONE
Definition: rest.h:58
@ REST_HTTP_BODY_UNAVAILABLE
Definition: rest.h:56
char const * method_str
The string version of the HTTP method.
Definition: rest.h:111
@ READ_STATE_ATTR_CONT
Definition: rest.h:193
@ READ_STATE_ATTR_BEGIN
Definition: rest.h:192
@ READ_STATE_END
Definition: rest.h:194
@ READ_STATE_INIT
Definition: rest.h:191
rlm_rest_section_t const * section
Section configuration.
Definition: rest.h:230
char const * body_str
The string version of the encoding/content type.
Definition: rest.h:114
#define REST_BODY_ALLOC_CHUNK
Definition: rest.h:40
HIDDEN fr_dict_attr_t const * attr_rest_http_body
Definition: rlm_rest.c:279
rlm_rest_section_request_t request
Request configuration.
Definition: rest.h:142
rlm_rest_response_t response
Response context data.
Definition: rest.h:260
size_t(* rest_read_t)(void *ptr, size_t size, size_t nmemb, void *userdata)
Definition: rest.h:295
#define REST_BODY_MAX_LEN
Definition: rest.h:39
rlm_rest_section_t const * section
Section configuration.
Definition: rest.h:212
void * encoder
Encoder specific data.
Definition: rest.h:221
rlm_rest_request_t request
Request context data.
Definition: rest.h:259
rlm_rest_section_response_t response
Response configuration.
Definition: rest.h:143
fr_time_delta_t timeout
Timeout timeval.
Definition: rest.h:140
@ WRITE_STATE_INIT
Definition: rest.h:201
@ WRITE_STATE_PARSE_HEADERS
Definition: rest.h:202
@ WRITE_STATE_PARSE_CONTENT
Definition: rest.h:203
char const * name
Section name.
Definition: rest.h:138
http_auth_type_t
Definition: rest.h:69
@ REST_HTTP_AUTH_NTLM_WB
Definition: rest.h:78
@ REST_HTTP_AUTH_NUM_ENTRIES
Definition: rest.h:81
@ REST_HTTP_AUTH_BASIC
Definition: rest.h:73
@ REST_HTTP_AUTH_NTLM
Definition: rest.h:77
@ REST_HTTP_AUTH_DIGEST
Definition: rest.h:74
@ REST_HTTP_AUTH_TLS_SRP
Definition: rest.h:72
@ REST_HTTP_AUTH_UNKNOWN
Definition: rest.h:70
@ REST_HTTP_AUTH_GSSNEGOTIATE
Definition: rest.h:76
@ REST_HTTP_AUTH_ANY
Definition: rest.h:79
@ REST_HTTP_AUTH_NONE
Definition: rest.h:71
@ REST_HTTP_AUTH_DIGEST_IE
Definition: rest.h:75
@ REST_HTTP_AUTH_ANY_SAFE
Definition: rest.h:80
size_t alloc
Space allocated for buffer.
Definition: rest.h:236
request_t * request
Current request.
Definition: rest.h:214
struct rlm_rest_call_env_t::@158 response
rlm_rest_t const * instance
This instance of rlm_rest.
Definition: rest.h:211
char const * rest_no_proxy
Magic pointer value for determining if we should disable proxying.
Definition: rlm_rest.c:93
uint32_t chunk
Max chunk-size (mainly for testing the encoders)
Definition: rest.h:123
size_t chunk
Chunk size.
Definition: rest.h:219
static char const * name
#define FR_SBUFF_OUT(_start, _len_or_end)
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:114
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:910
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition: tmpl.h:796
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:74
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:167
static fr_dict_attr_t const * tmpl_list(tmpl_t const *vpt)
Definition: tmpl.h:899
Optional arguments passed to vp_tmpl functions.
Definition: tmpl.h:341
static char buff[sizeof("18446744073709551615")+3]
Definition: size_tests.c:41
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:689
return count
Definition: module.c:175
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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:134
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition: table.h:253
#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:174
An element in a lexicographically sorted array of name to num mappings.
Definition: table.h:45
char * talloc_bstr_realloc(TALLOC_CTX *ctx, char *in, size_t inlen)
Trim a bstr (char) buffer.
Definition: talloc.c:516
char * talloc_typed_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition: talloc.c:380
Functions which we wish were included in the standard talloc distribution.
#define talloc_get_type_abort_const
Definition: talloc.h:270
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
Definition: talloc.h:277
Simple time functions.
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
Definition: time.h:635
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: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:1470
#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:627
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:40
#define fr_pair_dcursor_init(_cursor, _list)
Initialises a special dcursor with callbacks that will maintain the attr sublists correctly.
Definition: pair.h:590
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition: types.h:433
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:3301
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:3985
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:4181
static fr_slen_t data
Definition: value.h:1259
#define FR_VALUE_BOX_INITIALISER_NULL(_vb)
A static initialiser for stack/globally allocated boxes.
Definition: value.h:475
#define fr_box_strvalue_len(_val, _len)
Definition: value.h:279
#define fr_value_box_init_null(_vb)
Initialise an empty/null box that will be filled later.
Definition: value.h:580
#define fr_box_strvalue(_val)
Definition: value.h:278
#define fr_box_time_delta(_val)
Definition: value.h:336
#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:858
#define fr_value_box_list_foreach(_list_head, _iter)
Definition: value.h:199
static size_t char ** out
Definition: value.h:984