All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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: 5c4810ac4606eba6928ea5d90565d4975ed15989 $
19  *
20  * @brief Functions and datatypes for the REST (HTTP) transport.
21  * @file rest.c
22  *
23  * @copyright 2012-2014 Arran Cudbard-Bell <a.cudbard-bell@freeradius.org>
24  */
25 
26 RCSID("$Id: 5c4810ac4606eba6928ea5d90565d4975ed15989 $")
27 
28 #include <ctype.h>
29 #include <string.h>
30 #include <time.h>
31 
32 #include <freeradius-devel/rad_assert.h>
33 #include <freeradius-devel/radiusd.h>
34 #include <freeradius-devel/libradius.h>
35 #include <freeradius-devel/connection.h>
36 
37 #include "rest.h"
38 
39 /** Table of encoder/decoder support.
40  *
41  * Indexes in this table match the http_body_type_t enum, and should be
42  * updated if additional enum values are added.
43  *
44  * @see http_body_type_t
45  */
47  HTTP_BODY_UNKNOWN, // HTTP_BODY_UNKNOWN
48  HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNSUPPORTED
49  HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNAVAILABLE
50  HTTP_BODY_UNSUPPORTED, // HTTP_BODY_INVALID
51  HTTP_BODY_NONE, // HTTP_BODY_NONE
52  HTTP_BODY_CUSTOM_XLAT, // HTTP_BODY_CUSTOM_XLAT
53  HTTP_BODY_CUSTOM_LITERAL, // HTTP_BODY_CUSTOM_LITERAL
54  HTTP_BODY_POST, // HTTP_BODY_POST
55 #ifdef HAVE_JSON
56  HTTP_BODY_JSON, // HTTP_BODY_JSON
57 #else
59 #endif
60  HTTP_BODY_UNSUPPORTED, // HTTP_BODY_XML
61  HTTP_BODY_UNSUPPORTED, // HTTP_BODY_YAML
62  HTTP_BODY_INVALID, // HTTP_BODY_HTML
63  HTTP_BODY_PLAIN // HTTP_BODY_PLAIN
64 };
65 
66 /*
67  * Lib CURL doesn't define symbols for unsupported auth methods
68  */
69 #ifndef CURLOPT_TLSAUTH_SRP
70 # define CURLOPT_TLSAUTH_SRP 0
71 #endif
72 #ifndef CURLAUTH_BASIC
73 # define CURLAUTH_BASIC 0
74 #endif
75 #ifndef CURLAUTH_DIGEST
76 # define CURLAUTH_DIGEST 0
77 #endif
78 #ifndef CURLAUTH_DIGEST_IE
79 # define CURLAUTH_DIGEST_IE 0
80 #endif
81 #ifndef CURLAUTH_GSSNEGOTIATE
82 # define CURLAUTH_GSSNEGOTIATE 0
83 #endif
84 #ifndef CURLAUTH_NTLM
85 # define CURLAUTH_NTLM 0
86 #endif
87 #ifndef CURLAUTH_NTLM_WB
88 # define CURLAUTH_NTLM_WB 0
89 #endif
90 
91 /*
92  * CURL headers do:
93  *
94  * #define curl_easy_setopt(handle,opt,param) curl_easy_setopt(handle,opt,param)
95  */
97 DIAG_OFF(disabled-macro-expansion)
98 #define SET_OPTION(_x, _y)\
99 do {\
100  if ((ret = curl_easy_setopt(candle, _x, _y)) != CURLE_OK) {\
101  option = STRINGIFY(_x);\
102  goto error;\
103  }\
104 } while (0)
105 
106 const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
107  0, // HTTP_AUTH_UNKNOWN
108  0, // HTTP_AUTH_NONE
109  CURLOPT_TLSAUTH_SRP, // HTTP_AUTH_TLS_SRP
110  CURLAUTH_BASIC, // HTTP_AUTH_BASIC
111  CURLAUTH_DIGEST, // HTTP_AUTH_DIGEST
112  CURLAUTH_DIGEST_IE, // HTTP_AUTH_DIGEST_IE
113  CURLAUTH_GSSNEGOTIATE, // HTTP_AUTH_GSSNEGOTIATE
114  CURLAUTH_NTLM, // HTTP_AUTH_NTLM
115  CURLAUTH_NTLM_WB, // HTTP_AUTH_NTLM_WB
116  CURLAUTH_ANY, // HTTP_AUTH_ANY
117  CURLAUTH_ANYSAFE // HTTP_AUTH_ANY_SAFE
118 };
119 
120 
121 /** Conversion table for method config values.
122  *
123  * HTTP verb strings for http_method_t enum values. Used by libcurl in the
124  * status line of the outgoing HTTP header, by rest_response_header for decoding
125  * incoming HTTP responses, and by the configuration parser.
126  *
127  * @note must be kept in sync with http_method_t enum.
128  *
129  * @see http_method_t
130  * @see fr_str2int
131  * @see fr_int2str
132  */
134  { "UNKNOWN", HTTP_METHOD_UNKNOWN },
135  { "GET", HTTP_METHOD_GET },
136  { "POST", HTTP_METHOD_POST },
137  { "PUT", HTTP_METHOD_PUT },
138  { "PATCH", HTTP_METHOD_PATCH },
139  { "DELETE", HTTP_METHOD_DELETE },
140 
141  { NULL , -1 }
142 };
143 
144 /** Conversion table for type config values.
145  *
146  * Textual names for http_body_type_t enum values, used by the
147  * configuration parser.
148  *
149  * @see http_body_Type_t
150  * @see fr_str2int
151  * @see fr_int2str
152  */
154  { "unknown", HTTP_BODY_UNKNOWN },
155  { "unsupported", HTTP_BODY_UNSUPPORTED },
156  { "unavailable", HTTP_BODY_UNAVAILABLE },
157  { "invalid", HTTP_BODY_INVALID },
158  { "none", HTTP_BODY_NONE },
159  { "post", HTTP_BODY_POST },
160  { "json", HTTP_BODY_JSON },
161  { "xml", HTTP_BODY_XML },
162  { "yaml", HTTP_BODY_YAML },
163  { "html", HTTP_BODY_HTML },
164  { "plain", HTTP_BODY_PLAIN },
165 
166  { NULL , -1 }
167 };
168 
170  { "none", HTTP_AUTH_NONE },
171  { "srp", HTTP_AUTH_TLS_SRP },
172  { "basic", HTTP_AUTH_BASIC },
173  { "digest", HTTP_AUTH_DIGEST },
174  { "digest-ie", HTTP_AUTH_DIGEST_IE },
175  { "gss-negotiate", HTTP_AUTH_GSSNEGOTIATE },
176  { "ntlm", HTTP_AUTH_NTLM },
177  { "ntlm-winbind", HTTP_AUTH_NTLM_WB },
178  { "any", HTTP_AUTH_ANY },
179  { "safe", HTTP_AUTH_ANY_SAFE },
180 
181  { NULL , -1 }
182 };
183 
184 /** Conversion table for "Content-Type" header values.
185  *
186  * Used by rest_response_header for parsing incoming headers.
187  *
188  * Values we expect to see in the 'Content-Type:' header of the incoming
189  * response.
190  *
191  * Some data types (like YAML) do no have standard MIME types defined,
192  * so multiple types, are listed here.
193  *
194  * @see http_body_Type_t
195  * @see fr_str2int
196  * @see fr_int2str
197  */
199  { "application/x-www-form-urlencoded", HTTP_BODY_POST },
200  { "application/json", HTTP_BODY_JSON },
201  { "text/html", HTTP_BODY_HTML },
202  { "text/plain", HTTP_BODY_PLAIN },
203  { "text/xml", HTTP_BODY_XML },
204  { "text/yaml", HTTP_BODY_YAML },
205  { "text/x-yaml", HTTP_BODY_YAML },
206  { "application/yaml", HTTP_BODY_YAML },
207  { "application/x-yaml", HTTP_BODY_YAML },
208 
209  { NULL , -1 }
210 };
211 
212 /*
213  * Encoder specific structures.
214  * @todo split encoders/decoders into submodules.
215  */
216 typedef struct rest_custom_data {
217  char const *start; //!< Start of the buffer.
218  char const *p; //!< how much text we've sent so far.
219  size_t len; //!< Length of data
221 
222 #ifdef HAVE_JSON
223 /** Flags to control the conversion of JSON values to VALUE_PAIRs.
224  *
225  * These fields are set when parsing the expanded format for value pairs in
226  * JSON, and control how json_pair_make_leaf and json_pair_make convert the JSON
227  * value, and move the new VALUE_PAIR into an attribute list.
228  *
229  * @see json_pair_make
230  * @see json_pair_make_leaf
231  */
232 typedef struct json_flags {
233  int do_xlat; //!< If true value will be expanded with xlat.
234  int is_json; //!< If true value will be inserted as raw JSON
235  // (multiple values not supported).
236  FR_TOKEN op; //!< The operator that determines how the new VP
237  // is processed. @see fr_tokens_table
238 } json_flags_t;
239 #endif
240 
241 /** Initialises libcurl.
242  *
243  * Allocates global variables and memory required for libcurl to function.
244  * MUST only be called once per module instance.
245  *
246  * rest_cleanup must not be called if rest_init fails.
247  *
248  * @see rest_cleanup
249  *
250  * @param[in] instance configuration data.
251  * @return
252  * - 0 if init succeeded.
253  * - -1 if it failed.
254  */
255 int rest_init(rlm_rest_t *instance)
256 {
257  static bool version_done;
258  CURLcode ret;
259 
260  /* developer sanity */
261  rad_assert((sizeof(http_body_type_supported) / sizeof(*http_body_type_supported)) == HTTP_BODY_NUM_ENTRIES);
262 
263  ret = curl_global_init(CURL_GLOBAL_ALL);
264  if (ret != CURLE_OK) {
265  ERROR("rlm_rest (%s): CURL init returned error: %i - %s",
266  instance->xlat_name,
267  ret, curl_easy_strerror(ret));
268 
269  curl_global_cleanup();
270  return -1;
271  }
272 
273  if (!version_done) {
274  curl_version_info_data *curlversion;
275 
276  version_done = true;
277 
278  curlversion = curl_version_info(CURLVERSION_NOW);
279  if (strcmp(LIBCURL_VERSION, curlversion->version) != 0) {
280  WARN("rlm_rest: libcurl version changed since the server was built");
281  WARN("rlm_rest: linked: %s built: %s", curlversion->version, LIBCURL_VERSION);
282  }
283 
284  INFO("rlm_rest: libcurl version: %s", curl_version());
285  }
286 
287  return 0;
288 }
289 
290 /** Cleans up after libcurl.
291  *
292  * Wrapper around curl_global_cleanup, frees any memory allocated by rest_init.
293  * Must only be called once per call of rest_init.
294  *
295  * @see rest_init
296  */
297 void rest_cleanup(void)
298 {
299  curl_global_cleanup();
300 }
301 
302 
303 /** Frees a libcurl handle, and any additional memory used by context data.
304  *
305  * @param[in] randle rlm_rest_handle_t to close and free.
306  * @return returns true.
307  */
309 {
310  curl_easy_cleanup(randle->handle);
311 
312  return 0;
313 }
314 
315 /** Creates a new connection handle for use by the FR connection API.
316  *
317  * Matches the fr_connection_create_t function prototype, is passed to
318  * fr_connection_pool_init, and called when a new connection is required by the
319  * connection pool API.
320  *
321  * Creates an instances of rlm_rest_handle_t, and rlm_rest_curl_context_t
322  * which hold the context data required for generating requests and parsing
323  * responses.
324  *
325  * If instance->connect_uri is not NULL libcurl will attempt to open a
326  * TCP socket to the server specified in the URI. This is done so that when the
327  * socket is first used, there will already be a cached TCP connection to the
328  * REST server associated with the curl handle.
329  *
330  * @see fr_connection_pool_init
331  * @see fr_connection_create_t
332  * @see connection.c
333  */
334 void *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout)
335 {
336  rlm_rest_t *inst = instance;
337 
338  rlm_rest_handle_t *randle = NULL;
339  rlm_rest_curl_context_t *curl_ctx = NULL;
340 
341  CURL *candle = curl_easy_init();
342 
343  CURLcode ret = CURLE_OK;
344  char const *option = "unknown";
345 
346  if (!candle) {
347  ERROR("rlm_rest (%s): Failed to create CURL handle", inst->xlat_name);
348  return NULL;
349  }
350 
351  SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, FR_TIMEVAL_TO_MS(timeout));
352 
353  if (inst->connect_uri) {
354  /*
355  * re-establish TCP connection to webserver. This would usually be
356  * done on the first request, but we do it here to minimise
357  * latency.
358  */
359  SET_OPTION(CURLOPT_SSL_VERIFYPEER, 0);
360  SET_OPTION(CURLOPT_SSL_VERIFYHOST, 0);
361  SET_OPTION(CURLOPT_CONNECT_ONLY, 1);
362  SET_OPTION(CURLOPT_URL, inst->connect_uri);
363  SET_OPTION(CURLOPT_NOSIGNAL, 1);
364  if (inst->connect_proxy) SET_OPTION(CURLOPT_PROXY, inst->connect_proxy);
365 
366  DEBUG("rlm_rest (%s): Connecting to \"%s\"", inst->xlat_name, inst->connect_uri);
367 
368  ret = curl_easy_perform(candle);
369  if (ret != CURLE_OK) {
370  ERROR("rlm_rest (%s): Connection failed: %i - %s", inst->xlat_name, ret, curl_easy_strerror(ret));
371 
372  goto connection_error;
373  }
374  } else {
375  DEBUG2("rlm_rest (%s): Skipping pre-connect, connect_uri not specified", inst->xlat_name);
376  }
377 
378  /*
379  * Allocate memory for the connection handle abstraction.
380  */
381  randle = talloc_zero(ctx, rlm_rest_handle_t);
382  curl_ctx = talloc_zero(randle, rlm_rest_curl_context_t);
383 
384  curl_ctx->headers = NULL; /* CURL needs this to be NULL */
385  curl_ctx->request.instance = inst;
386 
387  randle->ctx = curl_ctx;
388  randle->handle = candle;
389  talloc_set_destructor(randle, _mod_conn_free);
390 
391  /*
392  * Clear any previously configured options for the first request.
393  */
394  curl_easy_reset(candle);
395 
396  return randle;
397 
398  /*
399  * Cleanup for error conditions.
400  */
401 error:
402  ERROR("rlm_rest (%s): Failed setting curl option %s: %s (%i)", inst->xlat_name, option,
403  curl_easy_strerror(ret), ret);
404 
405  /*
406  * So we don't leak CURL handles.
407  */
408 connection_error:
409  curl_easy_cleanup(candle);
410  if (randle) talloc_free(randle);
411 
412  return NULL;
413 }
414 
415 /** Verifies that the last TCP socket associated with a handle is still active.
416  *
417  * Quieries libcurl to try and determine if the TCP socket associated with a
418  * connection handle is still viable.
419  *
420  * @param[in] instance configuration data.
421  * @param[in] handle to check.
422  * @returns
423  * - False if the last socket is dead, or if the socket state couldn't be determined.
424  * - True if TCP socket is still alive.
425  */
426 int mod_conn_alive(void *instance, void *handle)
427 {
428  rlm_rest_t *inst = instance;
429  rlm_rest_handle_t *randle = handle;
430  CURL *candle = randle->handle;
431 
432  long last_socket;
433  CURLcode ret;
434 
435  ret = curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
436  if (ret != CURLE_OK) {
437  ERROR("rlm_rest (%s): Couldn't determine socket state: %i - %s", inst->xlat_name, ret,
438  curl_easy_strerror(ret));
439 
440  return false;
441  }
442 
443  if (last_socket == -1) {
444  return false;
445  }
446 
447  return true;
448 }
449 
450 /** Copies a pre-expanded xlat string to the output buffer
451  *
452  * @param[out] out Char buffer to write encoded data to.
453  * @param[in] size Multiply by nmemb to get the length of ptr.
454  * @param[in] nmemb Multiply by size to get the length of ptr.
455  * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
456  * @return
457  * - Length of data (including NULL) written to ptr.
458  * - 0 if no more data to write.
459  */
460 static size_t rest_encode_custom(void *out, size_t size, size_t nmemb, void *userdata)
461 {
462  rlm_rest_request_t *ctx = userdata;
464 
465  size_t freespace = (size * nmemb) - 1;
466  size_t len;
467  size_t to_copy;
468 
469  /*
470  * Special case for empty body
471  */
472  if (data->len == 0) return 0;
473 
474  /*
475  * If len > 0 then we must have these set.
476  */
477  rad_assert(data->start);
478  rad_assert(data->p);
479 
480  to_copy = data->len - (data->p - data->start);
481  len = to_copy > freespace ? freespace : to_copy;
482  if (len == 0) return 0;
483 
484  memcpy(out, data->p, to_copy);
485  data->p += len;
486 
487  return len;
488 }
489 
490 /** Encodes VALUE_PAIR linked list in POST format
491  *
492  * This is a stream function matching the rest_read_t prototype. Multiple
493  * successive calls will return additional encoded VALUE_PAIRs.
494  * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
495  * will be written to the ptr buffer.
496  *
497  * POST request format is:
498  * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
499  *
500  * All attributes and values are url encoded. There is currently no support for
501  * nested attributes, or attribute qualifiers.
502  *
503  * Nested attributes may be added in the future using
504  * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
505  * to denotate nesting.
506  *
507  * Requires libcurl for url encoding.
508  *
509  * @see rest_decode_post
510  *
511  * @param[out] out Char buffer to write encoded data to.
512  * @param[in] size Multiply by nmemb to get the length of ptr.
513  * @param[in] nmemb Multiply by size to get the length of ptr.
514  * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
515  * @return
516  * - Length of data (including NULL) written to ptr.
517  * - 0 if no more data to write.
518  */
519 static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userdata)
520 {
521  rlm_rest_request_t *ctx = userdata;
522  REQUEST *request = ctx->request; /* Used by RDEBUG */
523  VALUE_PAIR *vp;
524 
525  char *p = out; /* Position in buffer */
526  char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
527  char *escaped; /* Pointer to current URL escaped data */
528 
529  size_t len = 0;
530  size_t freespace = (size * nmemb) - 1;
531 
532  /* Allow manual chunking */
533  if ((ctx->chunk) && (ctx->chunk <= freespace)) {
534  freespace = (ctx->chunk - 1);
535  }
536 
537  if (ctx->state == READ_STATE_END) return 0;
538 
539  /* Post data requires no headers */
540  if (ctx->state == READ_STATE_INIT) ctx->state = READ_STATE_ATTR_BEGIN;
541 
542  while (freespace > 0) {
543  vp = fr_cursor_current(&ctx->cursor);
544  if (!vp) {
545  ctx->state = READ_STATE_END;
546 
547  break;
548  }
549 
550  RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
551 
552  if (ctx->state == READ_STATE_ATTR_BEGIN) {
553  escaped = curl_escape(vp->da->name, strlen(vp->da->name));
554  if (!escaped) {
555  REDEBUG("Failed escaping string \"%s\"", vp->da->name);
556  return 0;
557  }
558 
559  len = strlen(escaped);
560  if (freespace < (1 + len)) {
561  curl_free(escaped);
562  goto no_space;
563  }
564 
565  len = sprintf(p, "%s=", escaped);
566  curl_free(escaped);
567  p += len;
568  freespace -= len;
569 
570  /*
571  * We wrote the attribute header, record progress.
572  */
573  encoded = p;
575  }
576 
577  /*
578  * Write out single attribute string.
579  */
580  len = fr_pair_value_snprint(p, freespace, vp, 0);
581  if (is_truncated(len, freespace)) goto no_space;
582 
583  RINDENT();
584  RDEBUG3("Length : %zd", len);
585  REXDENT();
586  if (len > 0) {
587  escaped = curl_escape(p, len);
588  if (!escaped) {
589  REDEBUG("Failed escaping string \"%s\"", vp->da->name);
590  return 0;
591  }
592  len = strlen(escaped);
593 
594  if (freespace < len) {
595  curl_free(escaped);
596  goto no_space;
597  }
598 
599  len = strlcpy(p, escaped, len + 1);
600 
601  curl_free(escaped);
602 
603  RINDENT();
604  RDEBUG3("Value : %s", p);
605  REXDENT();
606 
607  p += len;
608  freespace -= len;
609  }
610 
611  /*
612  * there are more attributes, insert a separator
613  */
614  if (fr_cursor_next(&ctx->cursor)) {
615  if (freespace < 1) goto no_space;
616  *p++ = '&';
617  freespace--;
618  }
619 
620  /*
621  * We wrote one full attribute value pair, record progress.
622  */
623  encoded = p;
624 
626  }
627 
628  *p = '\0';
629 
630  len = p - (char *)out;
631 
632  RDEBUG3("POST Data: %s", (char *)out);
633  RDEBUG3("Returning %zd bytes of POST data", len);
634 
635  return len;
636 
637  /*
638  * Cleanup for error conditions
639  */
640 no_space:
641  *encoded = '\0';
642 
643  len = encoded - (char *)out;
644 
645  RDEBUG3("POST Data: %s", (char *)out);
646 
647  /*
648  * The buffer wasn't big enough to encode a single attribute chunk.
649  */
650  if (len == 0) {
651  REDEBUG("Failed encoding attribute");
652  } else {
653  RDEBUG3("Returning %zd bytes of POST data (buffer full or chunk exceeded)", len);
654  }
655 
656  return len;
657 }
658 
659 #ifdef HAVE_JSON
660 /** Encodes VALUE_PAIR linked list in JSON format
661  *
662  * This is a stream function matching the rest_read_t prototype. Multiple
663  * successive calls will return additional encoded VALUE_PAIRs.
664  *
665  * Only complete attribute headers
666  * @verbatim "<name>":{"type":"<type>","value":[' @endverbatim
667  * and complete attribute values will be written to ptr.
668  *
669  * If an attribute occurs multiple times in the request the attribute values
670  * will be concatenated into a single value array.
671  *
672  * JSON request format is:
673 @verbatim
674 {
675  "<attribute0>":{
676  "type":"<type0>",
677  "value":[<value0>,<value1>,<valueN>]
678  },
679  "<attribute1>":{
680  "type":"<type1>",
681  "value":[...]
682  },
683  "<attributeN>":{
684  "type":"<typeN>",
685  "value":[...]
686  },
687 }
688 @endverbatim
689  *
690  * @param[out] out Char buffer to write encoded data to.
691  * @param[in] size Multiply by nmemb to get the length of ptr.
692  * @param[in] nmemb Multiply by size to get the length of ptr.
693  * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
694  * @return
695  * - Length of data (including NULL) written to ptr.
696  * - 0 if no more data to write.
697  */
698 static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userdata)
699 {
700  rlm_rest_request_t *ctx = userdata;
701  REQUEST *request = ctx->request; /* Used by RDEBUG */
702  VALUE_PAIR *vp, *next;
703 
704  char *p = out; /* Position in buffer */
705  char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
706 
707  char const *type;
708 
709  size_t len = 0;
710  size_t freespace = (size * nmemb) - 1; /* account for the \0 byte here */
711 
712  rad_assert(freespace > 0);
713 
714  /* Allow manual chunking */
715  if ((ctx->chunk) && (ctx->chunk <= freespace)) {
716  freespace = (ctx->chunk - 1);
717  }
718 
719  if (ctx->state == READ_STATE_END) return 0;
720 
721  if (ctx->state == READ_STATE_INIT) {
723 
724  if (freespace < 1) goto no_space;
725  *p++ = '{';
726  freespace--;
727  }
728 
729  for (;;) {
730  vp = fr_cursor_current(&ctx->cursor);
731 
732  /*
733  * We've encoded all the VPs
734  *
735  * The check for READ_STATE_ATTR_BEGIN is needed as we might be in
736  * READ_STATE_ATTR_END, and need to close out the current attribute
737  * array.
738  */
739  if (!vp && (ctx->state == READ_STATE_ATTR_BEGIN)) {
740  if (freespace < 1) goto no_space;
741  *p++ = '}';
742  freespace--;
743 
744  ctx->state = READ_STATE_END;
745 
746  break;
747  }
748 
749  if (ctx->state == READ_STATE_ATTR_BEGIN) {
750  /*
751  * New attribute, write name, type, and beginning of value array.
752  */
753  RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
754 
755  type = fr_int2str(dict_attr_types, vp->da->type, "<INVALID>");
756 
757  len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type);
758  if (len >= freespace) goto no_space;
759  p += len;
760  freespace -= len;
761 
762  RINDENT();
763  RDEBUG3("Type : %s", type);
764  REXDENT();
765  /*
766  * We wrote the attribute header, record progress
767  */
768  encoded = p;
770  }
771 
772  if (ctx->state == READ_STATE_ATTR_CONT) {
773  for (;;) {
774  size_t attr_space;
775 
776  rad_assert(vp); /* coverity */
777 
778  /*
779  * We need at least a single byte to write out the
780  * shortest attribute value.
781  */
782  if (freespace < 1) goto no_space;
783 
784  /*
785  * Code below expects length of the buffer, so we
786  * add +1 to freespace.
787  *
788  * If we know we need a comma after the value, we
789  * need to -1 to make sure we have enough room to
790  * write that out.
791  */
792  attr_space = fr_cursor_next_peek(&ctx->cursor) ? freespace - 1 : freespace;
793  len = fr_json_from_pair(p, attr_space + 1, vp);
794  if (is_truncated(len, attr_space + 1)) goto no_space;
795 
796  /*
797  * Show actual value length minus quotes
798  */
799  RINDENT();
800  RDEBUG3("Length : %zu", (size_t) (*p == '"') ? (len - 2) : len);
801  RDEBUG3("Value : %s", p);
802  REXDENT();
803 
804  p += len;
805  freespace -= len;
806  encoded = p;
807 
808  /*
809  * Multivalued attribute, we sorted all the attributes earlier, so multiple
810  * instances should occur in a contiguous block.
811  */
812  if ((next = fr_cursor_next(&ctx->cursor)) && (vp->da == next->da)) {
813  rad_assert(freespace >= 1);
814  *p++ = ',';
815  freespace--;
816 
817  /*
818  * We wrote one attribute value, record progress.
819  */
820  encoded = p;
821  vp = next;
822  continue;
823  }
824  break;
825  }
826  ctx->state = READ_STATE_ATTR_END;
827  }
828 
829  if (ctx->state == READ_STATE_ATTR_END) {
830  next = fr_cursor_current(&ctx->cursor);
831  if (freespace < 2) goto no_space;
832  *p++ = ']';
833  *p++ = '}';
834  freespace -= 2;
835 
836  if (next) {
837  if (freespace < 1) goto no_space;
838  *p++ = ',';
839  freespace--;
840  }
841 
842  /*
843  * We wrote one full attribute value pair, record progress.
844  */
845  encoded = p;
847  }
848  }
849 
850  *p = '\0';
851 
852  len = p - (char *)out;
853 
854  RDEBUG3("JSON Data: %s", (char *)out);
855  RDEBUG3("Returning %zd bytes of JSON data", len);
856 
857  return len;
858 
859  /*
860  * Were out of buffer space
861  */
862 no_space:
863  *encoded = '\0';
864 
865  len = encoded - (char *)out;
866 
867  RDEBUG3("JSON Data: %s", (char *)out);
868 
869  /*
870  * The buffer wasn't big enough to encode a single attribute chunk.
871  */
872  if (len == 0) {
873  REDEBUG("AVP exceeds buffer length or chunk");
874  } else {
875  RDEBUG2("Returning %zd bytes of JSON data (buffer full or chunk exceeded)", len);
876  }
877 
878  return len;
879 }
880 #endif
881 
882 /** Emulates successive libcurl calls to an encoding function
883  *
884  * This function is used when the request will be sent to the HTTP server as one
885  * contiguous entity. A buffer of REST_BODY_INIT bytes is allocated and passed
886  * to the stream encoding function.
887  *
888  * If the stream function does not return 0, a new buffer is allocated which is
889  * the size of the previous buffer + REST_BODY_INIT bytes, the data from the
890  * previous buffer is copied, and freed, and another call is made to the stream
891  * function, passing a pointer into the new buffer at the end of the previously
892  * written data.
893  *
894  * This process continues until the stream function signals (by returning 0)
895  * that it has no more data to write.
896  *
897  * @param[out] out where the pointer to the alloced buffer should
898  * be written.
899  * @param[in] func Stream function.
900  * @param[in] limit Maximum buffer size to alloc.
901  * @param[in] userdata rlm_rest_request_t to keep encoding state between calls to
902  * stream function.
903  * @return
904  * - Length of the data written to the buffer (excluding NULL).
905  * - -1 if alloc >= limit.
906  */
907 static ssize_t rest_request_encode_wrapper(char **out, rest_read_t func, size_t limit, void *userdata)
908 {
909  char *buff = NULL;
910 
911  size_t alloc = REST_BODY_INIT; /* Size of buffer to alloc */
912  size_t used = 0; /* Size of data written */
913  size_t len = 0;
914 
915  buff = talloc_array(NULL, char, alloc);
916  for (;;) {
917  len = func(buff + used, alloc - used, 1, userdata);
918  used += len;
919  if (!len) {
920  *out = buff;
921  return used;
922  }
923 
924  alloc = alloc * 2;
925  if (alloc > limit) break;
926 
927  buff = talloc_realloc(NULL, buff, char, alloc);
928  if (!buff) return -1;
929  };
930 
931  talloc_free(buff);
932 
933  return -1;
934 }
935 
936 /** (Re-)Initialises the data in a rlm_rest_request_t.
937  *
938  * Resets the values of a rlm_rest_request_t to their defaults.
939  *
940  * @param[in] request Current request.
941  * @param[in] ctx to initialise.
942  * @param[in] sort If true VALUE_PAIRs will be sorted within the VALUE_PAIR
943  * pointer array.
944  */
945 static void rest_request_init(REQUEST *request, rlm_rest_request_t *ctx, bool sort)
946 {
947  /*
948  * Setup stream read data
949  */
950  ctx->request = request;
951  ctx->state = READ_STATE_INIT;
952 
953  /*
954  * Sorts pairs in place, oh well...
955  */
956  if (sort) {
958  }
959  fr_cursor_init(&ctx->cursor, &request->packet->vps);
960 }
961 
962 /** Converts plain response into a single VALUE_PAIR
963  *
964  * @param[in] instance configuration data.
965  * @param[in] section configuration data.
966  * @param[in] handle rlm_rest_handle_t to use.
967  * @param[in] request Current request.
968  * @param[in] raw buffer containing POST data.
969  * @param[in] rawlen Length of data in raw buffer.
970  * @return
971  * - Number of VALUE_PAIR processed.
972  * - -1 on unrecoverable error.
973  */
974 static int rest_decode_plain(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section,
975  REQUEST *request, UNUSED void *handle, char *raw, size_t rawlen)
976 {
977  VALUE_PAIR *vp;
978 
979  /*
980  * Empty response?
981  */
982  if (*raw == '\0') return 0;
983 
984  /*
985  * Use rawlen to protect against overrun, and to cope with any binary data
986  */
987  vp = pair_make_reply("REST-HTTP-Body", NULL, T_OP_ADD);
988  fr_pair_value_bstrncpy(vp, raw, rawlen);
989 
990  RDEBUG2("Adding reply:REST-HTTP-Body += \"%s\"", vp->vp_strvalue);
991 
992  return 1;
993 }
994 
995 /** Converts POST response into VALUE_PAIRs and adds them to the request
996  *
997  * Accepts VALUE_PAIRS in the same format as rest_encode_post, but with the
998  * addition of optional attribute list qualifiers as part of the attribute name
999  * string.
1000  *
1001  * If no qualifiers are specified, will default to the request list.
1002  *
1003  * POST response format is:
1004  * @verbatim [outer.][<list>:]<attribute0>=<value0>&[outer.][<list>:]<attribute1>=<value1>&[outer.][<list>:]<attributeN>=<valueN> @endverbatim
1005  *
1006  * @see rest_encode_post
1007  *
1008  * @param[in] instance configuration data.
1009  * @param[in] section configuration data.
1010  * @param[in] handle rlm_rest_handle_t to use.
1011  * @param[in] request Current request.
1012  * @param[in] raw buffer containing POST data.
1013  * @param[in] rawlen Length of data in raw buffer.
1014  * @return
1015  * - Number of VALUE_PAIRs processed.
1016  * - -1 on unrecoverable error.
1017  */
1018 static int rest_decode_post(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section,
1019  REQUEST *request, void *handle, char *raw, size_t rawlen)
1020 {
1021  rlm_rest_handle_t *randle = handle;
1022  CURL *candle = randle->handle;
1023 
1024  char const *p = raw, *q;
1025 
1026  char const *attribute;
1027  char *name = NULL;
1028  char *value = NULL;
1029 
1030  char *expanded = NULL;
1031 
1032  fr_dict_attr_t const *da;
1033  VALUE_PAIR *vp;
1034 
1035  pair_lists_t list_name;
1036  request_refs_t request_name;
1037  REQUEST *reference = request;
1038  VALUE_PAIR **vps;
1039  TALLOC_CTX *ctx;
1040 
1041  size_t len;
1042  int curl_len; /* Length from last curl_easy_unescape call */
1043 
1044  int count = 0;
1045  int ret;
1046 
1047  /*
1048  * Empty response?
1049  */
1050  while (isspace(*p)) p++;
1051  if (*p == '\0') return 0;
1052 
1053  while (((q = strchr(p, '=')) != NULL) && (count < REST_BODY_MAX_ATTRS)) {
1054  reference = request;
1055 
1056  name = curl_easy_unescape(candle, p, (q - p), &curl_len);
1057  p = (q + 1);
1058 
1059  RDEBUG2("Parsing attribute \"%s\"", name);
1060 
1061  /*
1062  * The attribute pointer is updated to point to the portion of
1063  * the string after the list qualifier.
1064  */
1065  attribute = name;
1066  attribute += radius_request_name(&request_name, attribute, REQUEST_CURRENT);
1067  if (request_name == REQUEST_UNKNOWN) {
1068  RWDEBUG("Invalid request qualifier, skipping");
1069 
1070  curl_free(name);
1071 
1072  continue;
1073  }
1074 
1075  if (radius_request(&reference, request_name) < 0) {
1076  RWDEBUG("Attribute name refers to outer request but not in a tunnel, skipping");
1077 
1078  curl_free(name);
1079 
1080  continue;
1081  }
1082 
1083  attribute += radius_list_name(&list_name, attribute, PAIR_LIST_REPLY);
1084  if (list_name == PAIR_LIST_UNKNOWN) {
1085  RWDEBUG("Invalid list qualifier, skipping");
1086  curl_free(name);
1087 
1088  continue;
1089  }
1090 
1091  da = fr_dict_attr_by_name(NULL, attribute);
1092  if (!da) {
1093  RWDEBUG("Attribute \"%s\" unknown, skipping", attribute);
1094 
1095  curl_free(name);
1096 
1097  continue;
1098  }
1099 
1100  vps = radius_list(reference, list_name);
1101  rad_assert(vps);
1102 
1103  RINDENT();
1104  RDEBUG3("Type : %s", fr_int2str(dict_attr_types, da->type, "<INVALID>"));
1105 
1106  ctx = radius_list_ctx(reference, list_name);
1107 
1108  q = strchr(p, '&');
1109  len = (!q) ? (rawlen - (p - raw)) : (unsigned)(q - p);
1110 
1111  value = curl_easy_unescape(candle, p, len, &curl_len);
1112 
1113  /*
1114  * If we found a delimiter we want to skip over it,
1115  * if we didn't we do *NOT* want to skip over the end
1116  * of the buffer...
1117  */
1118  p += (!q) ? len : (len + 1);
1119 
1120  RDEBUG3("Length : %i", curl_len);
1121  RDEBUG3("Value : \"%s\"", value);
1122  REXDENT();
1123 
1124  RDEBUG2("Performing xlat expansion of response value");
1125 
1126  if (radius_axlat(&expanded, request, value, NULL, NULL) < 0) {
1127  goto skip;
1128  }
1129 
1130  vp = fr_pair_afrom_da(ctx, da);
1131  if (!vp) {
1132  REDEBUG("Failed creating valuepair");
1133  talloc_free(expanded);
1134 
1135  goto error;
1136  }
1137 
1138  ret = fr_pair_value_from_str(vp, expanded, -1);
1139  TALLOC_FREE(expanded);
1140  if (ret < 0) {
1141  RWDEBUG("Incompatible value assignment, skipping");
1142  talloc_free(vp);
1143  goto skip;
1144  }
1145 
1146  fr_pair_add(vps, vp);
1147 
1148  count++;
1149 
1150  skip:
1151  curl_free(name);
1152  curl_free(value);
1153 
1154  continue;
1155 
1156  error:
1157  curl_free(name);
1158  curl_free(value);
1159 
1160  return count;
1161  }
1162 
1163  if (!count) {
1164  REDEBUG("Malformed POST data \"%s\"", raw);
1165  }
1166 
1167  return count;
1168 
1169 }
1170 
1171 #ifdef HAVE_JSON
1172 /** Converts JSON "value" key into VALUE_PAIR.
1173  *
1174  * If leaf is not in fact a leaf node, but contains JSON data, the data will
1175  * written to the attribute in JSON string format.
1176  *
1177  * @param[in] instance configuration data.
1178  * @param[in] section configuration data.
1179  * @param[in] ctx to allocate new VALUE_PAIRs in.
1180  * @param[in] request Current request.
1181  * @param[in] da Attribute to create.
1182  * @param[in] flags containing the operator other flags controlling value
1183  * expansion.
1184  * @param[in] leaf object containing the VALUE_PAIR value.
1185  * @return
1186  * - #VALUE_PAIR just created.
1187  * - NULL on error.
1188  */
1190  TALLOC_CTX *ctx, REQUEST *request, fr_dict_attr_t const *da,
1191  json_flags_t *flags, json_object *leaf)
1192 {
1193  char const *value, *to_parse;
1194  char *expanded = NULL;
1195  int ret;
1196 
1197  VALUE_PAIR *vp;
1198 
1199  if (fr_json_object_is_type(leaf, json_type_null)) {
1200  RDEBUG3("Got null value for attribute \"%s\", skipping...", da->name);
1201 
1202  return NULL;
1203  }
1204 
1205  /*
1206  * Should encode any nested JSON structures into JSON strings.
1207  *
1208  * "I knew you liked JSON so I put JSON in your JSON!"
1209  */
1210  value = json_object_get_string(leaf);
1211  if (!value) {
1212  RWDEBUG("Failed getting string value for attribute \"%s\", skipping...", da->name);
1213 
1214  return NULL;
1215  }
1216 
1217  RINDENT();
1218  RDEBUG3("Type : %s", fr_int2str(dict_attr_types, da->type, "<INVALID>"));
1219  RDEBUG3("Length : %zu", strlen(value));
1220  RDEBUG3("Value : \"%s\"", value);
1221  REXDENT();
1222 
1223  if (flags->do_xlat) {
1224  if (radius_axlat(&expanded, request, value, NULL, NULL) < 0) {
1225  return NULL;
1226  }
1227 
1228  to_parse = expanded;
1229  } else {
1230  to_parse = value;
1231  }
1232 
1233  vp = fr_pair_afrom_da(ctx, da);
1234  if (!vp) {
1235  RWDEBUG("Failed creating valuepair for attribute \"%s\", skipping...", da->name);
1236  talloc_free(expanded);
1237 
1238  return NULL;
1239  }
1240 
1241  vp->op = flags->op;
1242 
1243  ret = fr_pair_value_from_str(vp, to_parse, -1);
1244  talloc_free(expanded);
1245  if (ret < 0) {
1246  RWDEBUG("Incompatible value assignment for attribute \"%s\", skipping...", da->name);
1247  talloc_free(vp);
1248 
1249  return NULL;
1250  }
1251 
1252  return vp;
1253 }
1254 
1255 /** Processes JSON response and converts it into multiple VALUE_PAIRs
1256  *
1257  * Processes JSON attribute declarations in the format below. Will recurse when
1258  * processing nested attributes. When processing nested attributes flags and
1259  * operators from previous attributes are not inherited.
1260  *
1261  * JSON response format is:
1262 @verbatim
1263 {
1264  "<attribute0>":{
1265  "do_xlat":<bool>,
1266  "is_json":<bool>,
1267  "op":"<operator>",
1268  "value":[<value0>,<value1>,<valueN>]
1269  },
1270  "<attribute1>":{
1271  "value":{
1272  "<nested-attribute0>":{
1273  "op":"<operator>",
1274  "value":<value0>
1275  }
1276  }
1277  },
1278  "<attribute2>":"<value0>",
1279  "<attributeN>":[<value0>,<value1>,<valueN>]
1280 }
1281 @endverbatim
1282  *
1283  * JSON valuepair flags:
1284  * - do_xlat (optional) Controls xlat expansion of values. Defaults to true.
1285  * - is_json (optional) If true, any nested JSON data will be copied to the
1286  * VALUE_PAIR in string form. Defaults to true.
1287  * - op (optional) Controls how the attribute is inserted into
1288  * the target list. Defaults to ':=' (T_OP_SET).
1289  *
1290  * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
1291  * second and subsequent values in multivalued attributes. This does not work
1292  * between multiple attribute declarations.
1293  *
1294  * @see fr_tokens_table
1295  *
1296  * @param[in] instance configuration data.
1297  * @param[in] section configuration data.
1298  * @param[in] request Current request.
1299  * @param[in] object containing root node, or parent node.
1300  * @param[in] level Current nesting level.
1301  * @param[in] max counter, decremented after each VALUE_PAIR is created,
1302  * when 0 no more attributes will be processed.
1303  * @return
1304  * - Number of attributes created.
1305  * - < 0 on error.
1306  */
1307 static int json_pair_make(rlm_rest_t const *instance, rlm_rest_section_t *section,
1308  REQUEST *request, json_object *object, UNUSED int level, int max)
1309 {
1310  int max_attrs = max;
1311 
1312  if (!fr_json_object_is_type(object, json_type_object)) {
1313 #ifdef HAVE_JSON_TYPE_TO_NAME
1314  REDEBUG("Can't process VP container, expected JSON object"
1315  "got \"%s\", skipping...",
1316  json_type_to_name(json_object_get_type(object)));
1317 #else
1318  REDEBUG("Can't process VP container, expected JSON object"
1319  ", skipping...");
1320 #endif
1321  return -1;
1322  }
1323 
1324  /*
1325  * Process VP container
1326  */
1327  json_object_object_foreach(object, name, value) {
1328  int i = 0, elements;
1329  struct json_object *element, *tmp;
1330  TALLOC_CTX *ctx;
1331 
1332  json_flags_t flags = {
1333  .op = T_OP_SET,
1334  .do_xlat = 1,
1335  .is_json = 0
1336  };
1337 
1338  vp_tmpl_t dst;
1339  REQUEST *current = request;
1340  VALUE_PAIR **vps, *vp = NULL;
1341 
1342  memset(&dst, 0, sizeof(dst));
1343 
1344  /*
1345  * Resolve attribute name to a dictionary entry and pairlist.
1346  */
1347  RDEBUG2("Parsing attribute \"%s\"", name);
1348 
1349  if (tmpl_from_attr_str(&dst, name, REQUEST_CURRENT, PAIR_LIST_REPLY, false, false) <= 0) {
1350  RWDEBUG("Failed parsing attribute: %s, skipping...", fr_strerror());
1351  continue;
1352  }
1353 
1354  if (radius_request(&current, dst.tmpl_request) < 0) {
1355  RWDEBUG("Attribute name refers to outer request but not in a tunnel, skipping...");
1356  continue;
1357  }
1358 
1359  vps = radius_list(current, dst.tmpl_list);
1360  if (!vps) {
1361  RWDEBUG("List not valid in this context, skipping...");
1362  continue;
1363  }
1364  ctx = radius_list_ctx(current, dst.tmpl_list);
1365 
1366  /*
1367  * Alternative JSON structure which allows operator,
1368  * and other flags to be specified.
1369  *
1370  * "<name>":{
1371  * "do_xlat":<bool>,
1372  * "is_json":<bool>,
1373  * "op":"<op>",
1374  * "value":<value>
1375  * }
1376  *
1377  * Where value is a:
1378  * - [] Multivalued array
1379  * - {} Nested Valuepair
1380  * - * Integer or string value
1381  */
1382  if (fr_json_object_is_type(value, json_type_object)) {
1383  /*
1384  * Process operator if present.
1385  */
1386  if (json_object_object_get_ex(value, "op", &tmp)) {
1387  flags.op = fr_str2int(fr_tokens_table, json_object_get_string(tmp), 0);
1388  if (!flags.op) {
1389  RWDEBUG("Invalid operator value \"%s\", skipping...",
1390  json_object_get_string(tmp));
1391  continue;
1392  }
1393  }
1394 
1395  /*
1396  * Process optional do_xlat bool.
1397  */
1398  if (json_object_object_get_ex(value, "do_xlat", &tmp)) {
1399  flags.do_xlat = json_object_get_boolean(tmp);
1400  }
1401 
1402  /*
1403  * Process optional is_json bool.
1404  */
1405  if (json_object_object_get_ex(value, "is_json", &tmp)) {
1406  flags.is_json = json_object_get_boolean(tmp);
1407  }
1408 
1409  /*
1410  * Value key must be present if were using the expanded syntax.
1411  */
1412  if (!json_object_object_get_ex(value, "value", &tmp)) {
1413  RWDEBUG("Value key missing, skipping...");
1414  continue;
1415  }
1416  }
1417 
1418  /*
1419  * Setup fr_pair_make / recursion loop.
1420  */
1421  if (!flags.is_json && fr_json_object_is_type(value, json_type_array)) {
1422  elements = json_object_array_length(value);
1423  if (!elements) {
1424  RWDEBUG("Zero length value array, skipping...");
1425  continue;
1426  }
1427  element = json_object_array_get_idx(value, 0);
1428  } else {
1429  elements = 1;
1430  element = value;
1431  }
1432 
1433  /*
1434  * A JSON 'value' key, may have multiple elements, iterate
1435  * over each of them, creating a new VALUE_PAIR.
1436  */
1437  do {
1438  if (max_attrs-- <= 0) {
1439  RWDEBUG("At maximum attribute limit");
1440  return max;
1441  }
1442 
1443  /*
1444  * Automagically switch the op for multivalued attributes.
1445  */
1446  if (((flags.op == T_OP_SET) || (flags.op == T_OP_EQ)) && (i >= 1)) {
1447  flags.op = T_OP_ADD;
1448  }
1449 
1450  if (fr_json_object_is_type(element, json_type_object) && !flags.is_json) {
1451  /* TODO: Insert nested VP into VP structure...*/
1452  RWDEBUG("Found nested VP, these are not yet supported, skipping...");
1453 
1454  continue;
1455 
1456  /*
1457  vp = json_pair_make(instance, section,
1458  request, value,
1459  level + 1, max_attrs);*/
1460  } else {
1461  vp = json_pair_make_leaf(instance, section, ctx, request,
1462  dst.tmpl_da, &flags, element);
1463  if (!vp) continue;
1464  }
1465  rdebug_pair(2, request, vp, NULL);
1466  radius_pairmove(current, vps, vp, false);
1467  /*
1468  * If we call json_object_array_get_idx on something that's not an array
1469  * the behaviour appears to be to occasionally segfault.
1470  */
1471  } while ((++i < elements) && (element = json_object_array_get_idx(value, i)));
1472  }
1473 
1474  return max - max_attrs;
1475 }
1476 
1477 /** Converts JSON response into VALUE_PAIRs and adds them to the request.
1478  *
1479  * Converts the raw JSON string into a json-c object tree and passes it to
1480  * json_pair_make. After the tree has been parsed json_object_put is called
1481  * which decrements the reference count of the root node by one, and frees
1482  * the entire tree.
1483  *
1484  * @see rest_encode_json
1485  * @see json_pair_make
1486  *
1487  * @param[in] instance configuration data.
1488  * @param[in] section configuration data.
1489  * @param[in,out] request Current request.
1490  * @param[in] handle REST handle.
1491  * @param[in] raw buffer containing JSON data.
1492  * @param[in] rawlen Length of data in raw buffer.
1493  * @return
1494  * - The number of #VALUE_PAIR processed.
1495  * - -1 on unrecoverable error.
1496  */
1497 static int rest_decode_json(rlm_rest_t const *instance, rlm_rest_section_t *section,
1498  REQUEST *request, UNUSED void *handle, char *raw, UNUSED size_t rawlen)
1499 {
1500  char const *p = raw;
1501 
1502  struct json_object *json;
1503 
1504  int ret;
1505 
1506  /*
1507  * Empty response?
1508  */
1509  while (isspace(*p)) p++;
1510  if (*p == '\0') return 0;
1511 
1512  json = json_tokener_parse(p);
1513  if (!json) {
1514  REDEBUG("Malformed JSON data \"%s\"", raw);
1515  return -1;
1516  }
1517 
1518  ret = json_pair_make(instance, section, request, json, 0, REST_BODY_MAX_ATTRS);
1519 
1520  /*
1521  * Decrement reference count for root object, should free entire JSON tree.
1522  */
1523  json_object_put(json);
1524 
1525  return ret;
1526 }
1527 #endif
1528 
1529 /** Processes incoming HTTP header data from libcurl.
1530  *
1531  * Processes the status line, and Content-Type headers from the incoming HTTP
1532  * response.
1533  *
1534  * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1535  * by libcurl.
1536  *
1537  * @param[in] in Char buffer where inbound header data is written.
1538  * @param[in] size Multiply by nmemb to get the length of ptr.
1539  * @param[in] nmemb Multiply by size to get the length of ptr.
1540  * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1541  * @return
1542  * - Length of data processed.
1543  * - 0 on error.
1544  */
1545 static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *userdata)
1546 {
1547  rlm_rest_response_t *ctx = userdata;
1548  REQUEST *request = ctx->request; /* Used by RDEBUG */
1549 
1550  char const *p = in, *q;
1551 
1552  size_t const t = (size * nmemb);
1553  size_t s = t;
1554  size_t len;
1555 
1556  http_body_type_t type;
1557 
1558  /*
1559  * Curl seems to throw these (\r\n) in before the next set of headers when
1560  * looks like it's just a body separator and safe to ignore after we
1561  * receive a 100 Continue.
1562  */
1563  if (t == 2 && ((p[0] == '\r') && (p[1] == '\n'))) return t;
1564 
1565  switch (ctx->state) {
1566  case WRITE_STATE_INIT:
1567  RDEBUG2("Processing response header");
1568 
1569  /*
1570  * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1571  *
1572  * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
1573  */
1574  if (s < 14) {
1575  REDEBUG("Malformed HTTP header: Status line too short");
1576  goto malformed;
1577  }
1578  /*
1579  * Check start of header matches...
1580  */
1581  if (strncasecmp("HTTP/", p, 5) != 0) {
1582  REDEBUG("Malformed HTTP header: Missing HTTP version");
1583  goto malformed;
1584  }
1585  p += 5;
1586  s -= 5;
1587 
1588  /*
1589  * Skip the version field, next space should mark start of reason_code.
1590  */
1591  q = memchr(p, ' ', s);
1592  if (!q) {
1593  RDEBUG("Malformed HTTP header: Missing reason code");
1594  goto malformed;
1595  }
1596 
1597  s -= (q - p);
1598  p = q;
1599 
1600  /*
1601  * Process reason_code.
1602  *
1603  * " 100" (4) + "\r\n" (2) = 6
1604  */
1605  if (s < 6) {
1606  REDEBUG("Malformed HTTP header: Reason code too short");
1607  goto malformed;
1608  }
1609  p++;
1610  s--;
1611 
1612  /* Char after reason code must be a space, or \r */
1613  if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
1614 
1615  ctx->code = atoi(p);
1616 
1617  /*
1618  * Process reason_phrase (if present).
1619  */
1620  RINDENT();
1621  if (p[3] == ' ') {
1622  p += 4;
1623  s -= 4;
1624 
1625  q = memchr(p, '\r', s);
1626  if (!q) goto malformed;
1627 
1628  len = (q - p);
1629 
1630  RDEBUG2("Status : %i (%.*s)", ctx->code, (int) len, p);
1631  } else {
1632  RDEBUG2("Status : %i", ctx->code);
1633  }
1634  REXDENT();
1635 
1637 
1638  break;
1639 
1641  if ((s >= 14) &&
1642  (strncasecmp("Content-Type: ", p, 14) == 0)) {
1643  p += 14;
1644  s -= 14;
1645 
1646  /*
1647  * Check to see if there's a parameter separator.
1648  */
1649  q = memchr(p, ';', s);
1650 
1651  /*
1652  * If there's not, find the end of this header.
1653  */
1654  if (!q) q = memchr(p, '\r', s);
1655 
1656  len = !q ? s : (size_t) (q - p);
1657  type = fr_substr2int(http_content_type_table, p, HTTP_BODY_UNKNOWN, len);
1658 
1659  RINDENT();
1660  RDEBUG2("Type : %s (%.*s)", fr_int2str(http_body_type_table, type, "<INVALID>"),
1661  (int) len, p);
1662  REXDENT();
1663 
1664  /*
1665  * Assume the force_to value has already been validated.
1666  */
1667  if (ctx->force_to != HTTP_BODY_UNKNOWN) {
1668  if (ctx->force_to != ctx->type) {
1669  RDEBUG3("Forcing body type to \"%s\"",
1670  fr_int2str(http_body_type_table, ctx->force_to, "<INVALID>"));
1671  ctx->type = ctx->force_to;
1672  }
1673  /*
1674  * Figure out if the type is supported by one of the decoders.
1675  */
1676  } else {
1677  ctx->type = http_body_type_supported[type];
1678  switch (ctx->type) {
1679  case HTTP_BODY_UNKNOWN:
1680  RWDEBUG("Couldn't determine type, using the request's type \"%s\".",
1681  fr_int2str(http_body_type_table, type, "<INVALID>"));
1682  break;
1683 
1684  case HTTP_BODY_UNSUPPORTED:
1685  REDEBUG("Type \"%s\" is currently unsupported",
1686  fr_int2str(http_body_type_table, type, "<INVALID>"));
1687  break;
1688 
1689  case HTTP_BODY_UNAVAILABLE:
1690  REDEBUG("Type \"%s\" is unavailable, please rebuild this module with the required "
1691  "library", fr_int2str(http_body_type_table, type, "<INVALID>"));
1692  break;
1693 
1694  case HTTP_BODY_INVALID:
1695  REDEBUG("Type \"%s\" is not a valid web API data markup format",
1696  fr_int2str(http_body_type_table, type, "<INVALID>"));
1697  break;
1698 
1699  /* supported type */
1700  default:
1701  break;
1702  }
1703  }
1704  }
1705  break;
1706 
1707  default:
1708  break;
1709  }
1710 
1711  /*
1712  * If we got a 100 Continue, we need to send additional payload data.
1713  * reset the state to WRITE_STATE_INIT, so that when were called again
1714  * we overwrite previous header data with that from the proper header.
1715  */
1716  if (ctx->code == 100) {
1717  RDEBUG2("Continuing...");
1718  ctx->state = WRITE_STATE_INIT;
1719  }
1720 
1721  return t;
1722 
1723 malformed:
1724  {
1725  char escaped[1024];
1726 
1727  fr_snprint(escaped, sizeof(escaped), (char *) in, t, '\0');
1728 
1729  REDEBUG("Received %zu bytes of response data: %s", t, escaped);
1730  ctx->code = -1;
1731  }
1732 
1733  return (t - s);
1734 }
1735 
1736 /** Processes incoming HTTP body data from libcurl.
1737  *
1738  * Writes incoming body data to an intermediary buffer for later parsing by
1739  * one of the decode functions.
1740  *
1741  * @param[in] ptr Char buffer where inbound header data is written
1742  * @param[in] size Multiply by nmemb to get the length of ptr.
1743  * @param[in] nmemb Multiply by size to get the length of ptr.
1744  * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1745  * @return
1746  * - Length of data processed.
1747  * - 0 on error.
1748  */
1749 static size_t rest_response_body(void *ptr, size_t size, size_t nmemb, void *userdata)
1750 {
1751  rlm_rest_response_t *ctx = userdata;
1752  REQUEST *request = ctx->request; /* Used by RDEBUG */
1753 
1754  char const *p = ptr, *q;
1755  char *tmp;
1756 
1757  size_t const t = (size * nmemb);
1758 
1759  if (t == 0) return 0;
1760 
1761  /*
1762  * Any post processing of headers should go here...
1763  */
1764  if (ctx->state == WRITE_STATE_PARSE_HEADERS) {
1766  }
1767 
1768  switch (ctx->type) {
1769  case HTTP_BODY_UNSUPPORTED:
1770  case HTTP_BODY_UNAVAILABLE:
1771  case HTTP_BODY_INVALID:
1772  while ((q = memchr(p, '\n', t - (p - (char *)ptr)))) {
1773  REDEBUG("%.*s", (int) (q - p), p);
1774  p = q + 1;
1775  }
1776 
1777  if (*p != '\0') {
1778  REDEBUG("%.*s", (int)(t - (p - (char *)ptr)), p);
1779  }
1780 
1781  return t;
1782 
1783  case HTTP_BODY_NONE:
1784  while ((q = memchr(p, '\n', t - (p - (char *)ptr)))) {
1785  RDEBUG3("%.*s", (int) (q - p), p);
1786  p = q + 1;
1787  }
1788 
1789  if (*p != '\0') {
1790  RDEBUG3("%.*s", (int)(t - (p - (char *)ptr)), p);
1791  }
1792 
1793  return t;
1794 
1795  default:
1796  if (t > (ctx->alloc - ctx->used)) {
1797  ctx->alloc += ((t + 1) > REST_BODY_INIT) ? t + 1 : REST_BODY_INIT;
1798 
1799  tmp = ctx->buffer;
1800 
1801  ctx->buffer = rad_malloc(ctx->alloc);
1802 
1803  /* If data has been written previously */
1804  if (tmp) {
1805  strlcpy(ctx->buffer, tmp, (ctx->used + 1));
1806  free(tmp);
1807  }
1808  }
1809  strlcpy(ctx->buffer + ctx->used, p, t + 1);
1810  ctx->used += t;
1811 
1812  break;
1813  }
1814 
1815  return t;
1816 }
1817 
1818 /** Print out the response text as error lines
1819  *
1820  * @param request The Current request.
1821  * @param handle rlm_rest_handle_t used to execute the previous request.
1822  */
1824 {
1825  char const *p, *q;
1826  size_t len;
1827 
1828  len = rest_get_handle_data(&p, handle);
1829  if (len == 0) {
1830  RERROR("Server returned no data");
1831  return;
1832  }
1833 
1834  RERROR("Server returned:");
1835  while ((q = strchr(p, '\n'))) {
1836  RERROR("%.*s", (int) (q - p), p);
1837  p = q + 1;
1838  }
1839  if (*p != '\0') RERROR("%s", p);
1840 }
1841 
1842 /** (Re-)Initialises the data in a rlm_rest_response_t.
1843  *
1844  * This resets the values of the a rlm_rest_response_t to their defaults.
1845  * Must be called between encoding sessions.
1846  *
1847  * @see rest_response_body
1848  * @see rest_response_header
1849  *
1850  * @param[in] request Current request.
1851  * @param[in] ctx data to initialise.
1852  * @param[in] type Default http_body_type to use when decoding raw data, may be
1853  * overwritten by rest_response_header.
1854  */
1856 {
1857  ctx->request = request;
1858  ctx->type = type;
1859  ctx->state = WRITE_STATE_INIT;
1860  ctx->alloc = 0;
1861  ctx->used = 0;
1862  ctx->buffer = NULL;
1863 }
1864 
1865 /** Extracts pointer to buffer containing response data
1866  *
1867  * @param[out] out Where to write the pointer to the buffer.
1868  * @param[in] handle used for the last request.
1869  * @return > 0 if data is available.
1870  */
1871 size_t rest_get_handle_data(char const **out, rlm_rest_handle_t *handle)
1872 {
1873  rlm_rest_curl_context_t *ctx = handle->ctx;
1874 
1875  rad_assert(ctx->response.buffer || (!ctx->response.buffer && !ctx->response.used));
1876 
1877  *out = ctx->response.buffer;
1878  return ctx->response.used;
1879 }
1880 
1881 /** Configures body specific curlopts.
1882  *
1883  * Configures libcurl handle to use either chunked mode, where the request
1884  * data will be sent using multiple HTTP requests, or contiguous mode where
1885  * the request data will be sent in a single HTTP request.
1886  *
1887  * @param[in] instance configuration data.
1888  * @param[in] section configuration data.
1889  * @param[in] request Current request.
1890  * @param[in] handle rlm_rest_handle_t to configure.
1891  * @param[in] func to pass to libcurl for chunked.
1892  * transfers (NULL if not using chunked mode).
1893  * @return
1894  * - 0 on success.
1895  * - -1 on failure.
1896  */
1897 static int rest_request_config_body(UNUSED rlm_rest_t const *instance, rlm_rest_section_t *section,
1898  REQUEST *request, rlm_rest_handle_t *handle, rest_read_t func)
1899 {
1900  rlm_rest_curl_context_t *ctx = handle->ctx;
1901  CURL *candle = handle->handle;
1902 
1903  CURLcode ret = CURLE_OK;
1904  char const *option = "unknown";
1905 
1906  ssize_t len;
1907 
1908  /*
1909  * We were provided with no read function, assume this means
1910  * no body should be sent.
1911  */
1912  if (!func) {
1913  SET_OPTION(CURLOPT_POSTFIELDSIZE, 0);
1914  return 0;
1915  }
1916 
1917  /*
1918  * Chunked transfer encoding means the body will be sent in
1919  * multiple parts.
1920  */
1921  if (section->chunk > 0) {
1922  SET_OPTION(CURLOPT_READDATA, &ctx->request);
1923  SET_OPTION(CURLOPT_READFUNCTION, func);
1924 
1925  return 0;
1926  }
1927 
1928  /*
1929  * If were not doing chunked encoding then we read the entire
1930  * body into a buffer, and send it in one go.
1931  */
1932  len = rest_request_encode_wrapper(&ctx->body, func, REST_BODY_MAX_LEN, &ctx->request);
1933  if (len <= 0) {
1934  REDEBUG("Failed creating HTTP body content");
1935  return -1;
1936  }
1937  RDEBUG2("Content-Length will be %zu bytes", len);
1938 
1939  rad_assert((len == 0) || (talloc_array_length(ctx->body) >= (size_t)len));
1940  SET_OPTION(CURLOPT_POSTFIELDS, ctx->body);
1941  SET_OPTION(CURLOPT_POSTFIELDSIZE, len);
1942 
1943  return 0;
1944 
1945 error:
1946  REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret);
1947 
1948  return -1;
1949 }
1950 
1951 /** Configures request curlopts.
1952  *
1953  * Configures libcurl handle setting various curlopts for things like local
1954  * client time, Content-Type, and other FreeRADIUS custom headers.
1955  *
1956  * Current FreeRADIUS custom headers are:
1957  * - X-FreeRADIUS-Section The module section being processed.
1958  * - X-FreeRADIUS-Server The current virtual server the REQUEST is
1959  * passing through.
1960  *
1961  * Sets up callbacks for all response processing (buffers and body data).
1962  *
1963  * @param[in] instance configuration data.
1964  * @param[in] section configuration data.
1965  * @param[in] handle to configure.
1966  * @param[in] request Current request.
1967  * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1968  * @param[in] type Content-Type for request encoding, also sets the default for decoding.
1969  * @param[in] username to use for HTTP authentication, may be NULL in which case configured defaults will be used.
1970  * @param[in] password to use for HTTP authentication, may be NULL in which case configured defaults will be used.
1971  * @param[in] uri buffer containing the expanded URI to send the request to.
1972  * @return
1973  * - 0 on success (all opts configured).
1974  * - -1 on failure.
1975  */
1976 int rest_request_config(rlm_rest_t const *instance, rlm_rest_section_t *section,
1977  REQUEST *request, void *handle, http_method_t method,
1978  http_body_type_t type,
1979  char const *uri, char const *username, char const *password)
1980 {
1981  rlm_rest_handle_t *randle = handle;
1982  rlm_rest_curl_context_t *ctx = randle->ctx;
1983  CURL *candle = randle->handle;
1984  struct timeval timeout;
1985 
1986  http_auth_type_t auth = section->auth;
1987 
1988  CURLcode ret = CURLE_OK;
1989  char const *option = "unknown";
1990  char const *content_type;
1991 
1992  VALUE_PAIR *header;
1993  vp_cursor_t headers;
1994 
1995  char buffer[512];
1996 
1997  rad_assert(candle);
1998  rad_assert((!username && !password) || (username && password));
1999 
2000  buffer[(sizeof(buffer) - 1)] = '\0';
2001 
2002  /*
2003  * Setup any header options and generic headers.
2004  */
2005  SET_OPTION(CURLOPT_URL, uri);
2006  if (section->proxy) SET_OPTION(CURLOPT_PROXY, section->proxy);
2007  SET_OPTION(CURLOPT_NOSIGNAL, 1);
2008  SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING);
2009 
2010  /*
2011  * HTTP/1.1 doesn't require a content type, so only set it
2012  * if we were provided with one explicitly.
2013  */
2014  if (type != HTTP_BODY_NONE) {
2015  content_type = fr_int2str(http_content_type_table, type, section->body_str);
2016  snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type);
2017  ctx->headers = curl_slist_append(ctx->headers, buffer);
2018  if (!ctx->headers) goto error_header;
2019  }
2020 
2021  timeout = fr_connection_pool_timeout(instance->pool);
2022  SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, FR_TIMEVAL_TO_MS(&timeout));
2023  SET_OPTION(CURLOPT_TIMEOUT_MS, section->timeout);
2024 
2025 #ifdef CURLOPT_PROTOCOLS
2026  SET_OPTION(CURLOPT_PROTOCOLS, (CURLPROTO_HTTP | CURLPROTO_HTTPS));
2027 #endif
2028 
2029  /*
2030  * FreeRADIUS custom headers
2031  */
2032  RDEBUG3("Adding custom headers:");
2033  RINDENT();
2034  snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Section: %s", section->name);
2035  RDEBUG3("%s", buffer);
2036  ctx->headers = curl_slist_append(ctx->headers, buffer);
2037  if (!ctx->headers) goto error_header;
2038 
2039  snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Server: %s", request->server);
2040  RDEBUG3("%s", buffer);
2041  ctx->headers = curl_slist_append(ctx->headers, buffer);
2042  if (!ctx->headers) goto error_header;
2043 
2044  fr_cursor_init(&headers, &request->config);
2045  while (fr_cursor_next_by_num(&headers, 0, PW_REST_HTTP_HEADER, TAG_ANY)) {
2046  header = fr_cursor_remove(&headers);
2047  if (!strchr(header->vp_strvalue, ':')) {
2048  RWDEBUG("Invalid HTTP header \"%s\" must be in format '<attribute>: <value>'. Skipping...",
2049  header->vp_strvalue);
2050  talloc_free(header);
2051  continue;
2052  }
2053  RDEBUG3("%s", header->vp_strvalue);
2054  ctx->headers = curl_slist_append(ctx->headers, header->vp_strvalue);
2055  talloc_free(header);
2056  }
2057  REXDENT();
2058 
2059  /*
2060  * Configure HTTP verb (GET, POST, PUT, PATCH, DELETE, other...)
2061  */
2062  switch (method) {
2063  case HTTP_METHOD_GET:
2064  SET_OPTION(CURLOPT_HTTPGET, 1L);
2065  break;
2066 
2067  case HTTP_METHOD_POST:
2068  SET_OPTION(CURLOPT_POST, 1L);
2069  break;
2070 
2071  case HTTP_METHOD_PUT:
2072  /*
2073  * Do not set CURLOPT_PUT, this will cause libcurl
2074  * to ignore CURLOPT_POSTFIELDs and attempt to read
2075  * whatever was set with CURLOPT_READDATA, which by
2076  * default is stdin.
2077  *
2078  * This is many cases will cause the server to block,
2079  * indefinitely.
2080  */
2081  SET_OPTION(CURLOPT_CUSTOMREQUEST, "PUT");
2082  break;
2083 
2084  case HTTP_METHOD_PATCH:
2085  SET_OPTION(CURLOPT_CUSTOMREQUEST, "PATCH");
2086  break;
2087 
2088  case HTTP_METHOD_DELETE:
2089  SET_OPTION(CURLOPT_CUSTOMREQUEST, "DELETE");
2090  break;
2091 
2092  case HTTP_METHOD_CUSTOM:
2093  SET_OPTION(CURLOPT_CUSTOMREQUEST, section->method_str);
2094  break;
2095 
2096  default:
2097  rad_assert(0);
2098  break;
2099  };
2100 
2101  /*
2102  * Set user based authentication parameters
2103  */
2104  if (auth) {
2105  if ((auth >= HTTP_AUTH_BASIC) &&
2106  (auth <= HTTP_AUTH_ANY_SAFE)) {
2107  SET_OPTION(CURLOPT_HTTPAUTH, http_curl_auth[auth]);
2108 
2109  if (username) {
2110  SET_OPTION(CURLOPT_USERNAME, username);
2111  } else if (section->username) {
2112  if (radius_xlat(buffer, sizeof(buffer), request, section->username, NULL, NULL) < 0) {
2113  option = STRINGIFY(CURLOPT_USERNAME);
2114  goto error;
2115  }
2116  SET_OPTION(CURLOPT_USERNAME, buffer);
2117  }
2118 
2119  if (password) {
2120  SET_OPTION(CURLOPT_PASSWORD, password);
2121  } else if (section->password) {
2122  if (radius_xlat(buffer, sizeof(buffer), request, section->password, NULL, NULL) < 0) {
2123  option = STRINGIFY(CURLOPT_PASSWORD);
2124  goto error;
2125  }
2126  SET_OPTION(CURLOPT_PASSWORD, buffer);
2127  }
2128 #ifdef CURLOPT_TLSAUTH_USERNAME
2129  } else if (auth == HTTP_AUTH_TLS_SRP) {
2130  SET_OPTION(CURLOPT_TLSAUTH_TYPE, http_curl_auth[auth]);
2131 
2132  if (username) {
2133  SET_OPTION(CURLOPT_TLSAUTH_USERNAME, username);
2134  } else if (section->username) {
2135  if (radius_xlat(buffer, sizeof(buffer), request, section->username, NULL, NULL) < 0) {
2136  option = STRINGIFY(CURLOPT_TLSAUTH_USERNAME);
2137  goto error;
2138  }
2139  SET_OPTION(CURLOPT_TLSAUTH_USERNAME, buffer);
2140  }
2141 
2142  if (password) {
2143  SET_OPTION(CURLOPT_TLSAUTH_PASSWORD, password);
2144  } else if (section->password) {
2145  if (radius_xlat(buffer, sizeof(buffer), request, section->password, NULL, NULL) < 0) {
2146  option = STRINGIFY(CURLOPT_TLSAUTH_PASSWORD);
2147  goto error;
2148  }
2149  SET_OPTION(CURLOPT_TLSAUTH_PASSWORD, buffer);
2150  }
2151 #endif
2152  }
2153  }
2154 
2155  /*
2156  * Set SSL/TLS authentication parameters
2157  */
2158  if (section->tls_certificate_file) {
2159  SET_OPTION(CURLOPT_SSLCERT, section->tls_certificate_file);
2160  }
2161 
2162  if (section->tls_private_key_file) {
2163  SET_OPTION(CURLOPT_SSLKEY, section->tls_private_key_file);
2164  }
2165 
2166  if (section->tls_private_key_password) {
2167  SET_OPTION(CURLOPT_KEYPASSWD, section->tls_private_key_password);
2168  }
2169 
2170  if (section->tls_ca_file) {
2171  SET_OPTION(CURLOPT_ISSUERCERT, section->tls_ca_file);
2172  }
2173 
2174  if (section->tls_ca_path) {
2175  SET_OPTION(CURLOPT_CAPATH, section->tls_ca_path);
2176  }
2177 
2178  if (section->tls_random_file) {
2179  SET_OPTION(CURLOPT_RANDOM_FILE, section->tls_random_file);
2180  }
2181 
2182  SET_OPTION(CURLOPT_SSL_VERIFYPEER, (section->tls_check_cert == true) ? 1 : 0);
2183  SET_OPTION(CURLOPT_SSL_VERIFYHOST, (section->tls_check_cert_cn == true) ? 2 : 0);
2184 
2185  /*
2186  * Tell CURL how to get HTTP body content, and how to process incoming data.
2187  */
2188  rest_response_init(request, &ctx->response, type);
2189 
2190  SET_OPTION(CURLOPT_HEADERFUNCTION, rest_response_header);
2191  SET_OPTION(CURLOPT_HEADERDATA, &ctx->response);
2192  SET_OPTION(CURLOPT_WRITEFUNCTION, rest_response_body);
2193  SET_OPTION(CURLOPT_WRITEDATA, &ctx->response);
2194 
2195  /*
2196  * Force parsing the body text as a particular encoding.
2197  */
2198  ctx->response.force_to = section->force_to;
2199 
2200  switch (method) {
2201  case HTTP_METHOD_GET:
2202  case HTTP_METHOD_DELETE:
2203  RDEBUG3("Using a HTTP method which does not require a body. Forcing request body type to \"none\"");
2204  goto finish;
2205 
2206  case HTTP_METHOD_POST:
2207  case HTTP_METHOD_PUT:
2208  case HTTP_METHOD_PATCH:
2209  case HTTP_METHOD_CUSTOM:
2210  if (section->chunk > 0) {
2211  ctx->request.chunk = section->chunk;
2212 
2213  ctx->headers = curl_slist_append(ctx->headers, "Expect:");
2214  if (!ctx->headers) goto error_header;
2215 
2216  ctx->headers = curl_slist_append(ctx->headers, "Transfer-Encoding: chunked");
2217  if (!ctx->headers) goto error_header;
2218  }
2219 
2220  RDEBUG3("Request body content-type will be \"%s\"",
2221  fr_int2str(http_content_type_table, type, section->body_str));
2222  break;
2223 
2224  default:
2225  rad_assert(0);
2226  };
2227 
2228  /*
2229  * Setup encoder specific options
2230  */
2231  switch (type) {
2232  case HTTP_BODY_NONE:
2233  if (rest_request_config_body(instance, section, request, handle,
2234  NULL) < 0) {
2235  return -1;
2236  }
2237 
2238  break;
2239 
2240  case HTTP_BODY_CUSTOM_XLAT:
2241  {
2243  char *expanded = NULL;
2244 
2245  if (radius_axlat(&expanded, request, section->data, NULL, NULL) < 0) return -1;
2246 
2247  data = talloc_zero(request, rest_custom_data_t);
2248  data->p = expanded;
2249  data->start = expanded;
2250  data->len = strlen(expanded); // Fix me when we do binary xlat
2251 
2252  /* Use the encoder specific pointer to store the data we need to encode */
2253  ctx->request.encoder = data;
2254  if (rest_request_config_body(instance, section, request, handle,
2255  rest_encode_custom) < 0) {
2256  TALLOC_FREE(ctx->request.encoder);
2257  return -1;
2258  }
2259 
2260  break;
2261  }
2262 
2264  {
2266 
2267  data = talloc_zero(request, rest_custom_data_t);
2268  data->p = section->data;
2269  data->start = section->data;
2270  data->len = strlen(section->data);
2271 
2272  /* Use the encoder specific pointer to store the data we need to encode */
2273  ctx->request.encoder = data;
2274  if (rest_request_config_body(instance, section, request, handle,
2275  rest_encode_custom) < 0) {
2276  TALLOC_FREE(ctx->request.encoder);
2277  return -1;
2278  }
2279  }
2280  break;
2281 
2282 #ifdef HAVE_JSON
2283  case HTTP_BODY_JSON:
2284  rest_request_init(request, &ctx->request, true);
2285 
2286  if (rest_request_config_body(instance, section, request, handle,
2287  rest_encode_json) < 0) {
2288  return -1;
2289  }
2290 
2291  break;
2292 #endif
2293 
2294  case HTTP_BODY_POST:
2295  rest_request_init(request, &ctx->request, false);
2296 
2297  if (rest_request_config_body(instance, section, request, handle,
2298  rest_encode_post) < 0) {
2299  return -1;
2300  }
2301 
2302  break;
2303 
2304  default:
2305  rad_assert(0);
2306  }
2307 
2308 
2309 finish:
2310  SET_OPTION(CURLOPT_HTTPHEADER, ctx->headers);
2311 
2312  return 0;
2313 
2314 error:
2315  REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret);
2316  return -1;
2317 
2318 error_header:
2319  REDEBUG("Failed creating header");
2320  REXDENT();
2321  return -1;
2322 }
2323 
2324 /** Sends a REST (HTTP) request.
2325  *
2326  * Send the actual REST request to the server. The response will be handled by
2327  * the numerous callbacks configured in rest_request_config.
2328  *
2329  * @param[in] instance configuration data.
2330  * @param[in] section configuration data.
2331  * @param[in] request Current request.
2332  * @param[in] handle to use.
2333  * @return
2334  * - 0 on success.
2335  * - -1 on failure.
2336  */
2338  REQUEST *request, void *handle)
2339 {
2340  rlm_rest_handle_t *randle = handle;
2341  CURL *candle = randle->handle;
2342  CURLcode ret;
2343 
2344  ret = curl_easy_perform(candle);
2345  if (ret != CURLE_OK) {
2346  REDEBUG("Request failed: %i - %s", ret, curl_easy_strerror(ret));
2347 
2348  return -1;
2349  }
2350 
2351  return 0;
2352 }
2353 
2354 /** Sends the response to the correct decode function.
2355  *
2356  * Uses the Content-Type information written in rest_response_header to
2357  * determine the correct decode function to use. The decode function will
2358  * then convert the raw received data into VALUE_PAIRs.
2359  *
2360  * @param[in] instance configuration data.
2361  * @param[in] section configuration data.
2362  * @param[in] request Current request.
2363  * @param[in] handle to use.
2364  * @return
2365  * - 0 on success.
2366  * - -1 on failure.
2367  */
2368 int rest_response_decode(rlm_rest_t const *instance, rlm_rest_section_t *section, REQUEST *request, void *handle)
2369 {
2370  rlm_rest_handle_t *randle = handle;
2371  rlm_rest_curl_context_t *ctx = randle->ctx;
2372 
2373  int ret = -1; /* -Wsometimes-uninitialized */
2374 
2375  if (!ctx->response.buffer) {
2376  RDEBUG2("Skipping attribute processing, no valid body data received");
2377  return 0;
2378  }
2379 
2380  switch (ctx->response.type) {
2381  case HTTP_BODY_NONE:
2382  return 0;
2383 
2384  case HTTP_BODY_PLAIN:
2385  ret = rest_decode_plain(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2386  break;
2387 
2388  case HTTP_BODY_POST:
2389  ret = rest_decode_post(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2390  break;
2391 
2392 #ifdef HAVE_JSON
2393  case HTTP_BODY_JSON:
2394  ret = rest_decode_json(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2395  break;
2396 #endif
2397 
2398  case HTTP_BODY_UNSUPPORTED:
2399  case HTTP_BODY_UNAVAILABLE:
2400  case HTTP_BODY_INVALID:
2401  return -1;
2402 
2403  default:
2404  rad_assert(0);
2405  }
2406 
2407  return ret;
2408 }
2409 
2410 /** Cleans up after a REST request.
2411  *
2412  * Resets all options associated with a CURL handle, and frees any headers
2413  * associated with it.
2414  *
2415  * Calls rest_read_ctx_free and rest_response_free to free any memory used by
2416  * context data.
2417  *
2418  * @param[in] instance configuration data.
2419  * @param[in] section configuration data.
2420  * @param[in] handle to cleanup.
2421  */
2422 void rest_request_cleanup(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section, void *handle)
2423 {
2424  rlm_rest_handle_t *randle = handle;
2425  rlm_rest_curl_context_t *ctx = randle->ctx;
2426  CURL *candle = randle->handle;
2427 
2428  /*
2429  * Clear any previously configured options
2430  */
2431  curl_easy_reset(candle);
2432 
2433  /*
2434  * Free header list
2435  */
2436  if (ctx->headers != NULL) {
2437  curl_slist_free_all(ctx->headers);
2438  ctx->headers = NULL;
2439  }
2440 
2441  /*
2442  * Free body data (only used if chunking is disabled)
2443  */
2444  if (ctx->body != NULL) TALLOC_FREE(ctx->body);
2445 
2446  /*
2447  * Free response data
2448  */
2449  if (ctx->response.buffer) {
2450  free(ctx->response.buffer);
2451  ctx->response.buffer = NULL;
2452  }
2453 
2454  TALLOC_FREE(ctx->request.encoder);
2455  TALLOC_FREE(ctx->response.decoder);
2456 }
2457 
2458 /** URL encodes a string.
2459  *
2460  * Encode special chars as per RFC 3986 section 4.
2461  *
2462  * @param[in] request Current request.
2463  * @param[out] out Where to write escaped string.
2464  * @param[in] outlen Size of out buffer.
2465  * @param[in] raw string to be urlencoded.
2466  * @param[in] arg pointer, gives context for escaping.
2467  * @return length of data written to out (excluding NULL).
2468  */
2469 size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
2470 {
2471  char *escaped;
2472 
2473  escaped = curl_escape(raw, strlen(raw));
2474  strlcpy(out, escaped, outlen);
2475  curl_free(escaped);
2476 
2477  return strlen(out);
2478 }
2479 
2480 /** Builds URI; performs XLAT expansions and encoding.
2481  *
2482  * Splits the URI into "http://example.org" and "/%{xlat}/query/?bar=foo"
2483  * Both components are expanded, but values expanded for the second component
2484  * are also url encoded.
2485  *
2486  * @param[out] out Where to write the pointer to the new buffer containing the escaped URI.
2487  * @param[in] instance configuration data.
2488  * @param[in] uri configuration data.
2489  * @param[in] request Current request
2490  * @return
2491  * - Length of data written to buffer (excluding NULL).
2492  * - < 0 if an error occurred.
2493  */
2494 ssize_t rest_uri_build(char **out, UNUSED rlm_rest_t *instance, REQUEST *request, char const *uri)
2495 {
2496  char const *p;
2497  char *path_exp = NULL;
2498 
2499  char *scheme;
2500  char const *path;
2501 
2502  ssize_t len;
2503 
2504  p = uri;
2505 
2506  /*
2507  * All URLs must contain at least <scheme>://<server>/
2508  */
2509  p = strchr(p, ':');
2510  if (!p || (*++p != '/') || (*++p != '/')) {
2511  malformed:
2512  REDEBUG("Error URI is malformed, can't find start of path");
2513  return -1;
2514  }
2515  p = strchr(p + 1, '/');
2516  if (!p) {
2517  goto malformed;
2518  }
2519 
2520  len = (p - uri);
2521 
2522  /*
2523  * Allocate a temporary buffer to hold the first part of the URI
2524  */
2525  scheme = talloc_array(request, char, len + 1);
2526  strlcpy(scheme, uri, len + 1);
2527 
2528  path = (uri + len);
2529 
2530  len = radius_axlat(out, request, scheme, NULL, NULL);
2531  talloc_free(scheme);
2532  if (len < 0) {
2533  TALLOC_FREE(*out);
2534 
2535  return 0;
2536  }
2537 
2538  len = radius_axlat(&path_exp, request, path, rest_uri_escape, NULL);
2539  if (len < 0) {
2540  TALLOC_FREE(*out);
2541 
2542  return 0;
2543  }
2544 
2545  MEM(*out = talloc_strdup_append(*out, path_exp));
2546  talloc_free(path_exp);
2547 
2548  return talloc_array_length(*out) - 1; /* array_length includes \0 */
2549 }
2550 
2551 /** Unescapes the host portion of a URI string
2552  *
2553  * This is required because the xlat functions which operate on the input string
2554  * cannot distinguish between host and path components.
2555  *
2556  * @param[out] out Where to write the pointer to the new buffer containing the escaped URI.
2557  * @param[in] mod_inst configuration data.
2558  * @param[in] request Current request
2559  * @param[in] handle to use.
2560  * @param[in] uri configuration data.
2561  * @return
2562  * - Length of data written to buffer (excluding NULL).
2563  * - < 0 if an error occurred.
2564  */
2565 ssize_t rest_uri_host_unescape(char **out, UNUSED rlm_rest_t const *mod_inst, REQUEST *request,
2566  void *handle, char const *uri)
2567 {
2568  rlm_rest_handle_t *randle = handle;
2569  CURL *candle = randle->handle;
2570 
2571  char const *p, *q;
2572 
2573  char *scheme;
2574 
2575  ssize_t len;
2576 
2577  p = uri;
2578 
2579  /*
2580  * All URLs must contain at least <scheme>://<server>/
2581  */
2582  p = strchr(p, ':');
2583  if (!p || (*++p != '/') || (*++p != '/')) {
2584  malformed:
2585  REDEBUG("Error URI is malformed, can't find start of path");
2586  return -1;
2587  }
2588  p = strchr(p + 1, '/');
2589  if (!p) {
2590  goto malformed;
2591  }
2592 
2593  len = (p - uri);
2594 
2595  /*
2596  * Unescape any special sequences in the first part of the URI
2597  */
2598  scheme = curl_easy_unescape(candle, uri, len, NULL);
2599  if (!scheme) {
2600  REDEBUG("Error unescaping host");
2601  return -1;
2602  }
2603 
2604  /*
2605  * URIs can't contain spaces, so anything after the space must
2606  * be something else.
2607  */
2608  q = strchr(p, ' ');
2609  *out = q ? talloc_typed_asprintf(request, "%s%.*s", scheme, (int)(q - p), p) :
2610  talloc_typed_asprintf(request, "%s%s", scheme, p);
2611 
2612  MEM(*out);
2613  curl_free(scheme);
2614 
2615  return talloc_array_length(*out) - 1; /* array_length includes \0 */
2616 }
char const * name
Section name.
Definition: rest.h:106
ssize_t ssize_t ssize_t radius_axlat(char **out, REQUEST *request, char const *fmt, xlat_escape_t escape, void *escape_ctx) CC_HINT(nonnull(1
size_t used
Space used in buffer.
Definition: rest.h:211
size_t len
Length of data.
Definition: rest.c:219
void radius_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from, bool do_xlat) CC_HINT(nonnull)
Definition: evaluate.c:774
http_body_type_t
Definition: rest.h:54
VALUE_PAIR * config
VALUE_PAIR (s) used to set per request parameters for modules and the server core at runtime...
Definition: radiusd.h:227
struct rest_custom_data rest_custom_data_t
uint32_t chunk
Max chunk-size (mainly for testing the encoders)
Definition: rest.h:140
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:265
char const * tls_ca_path
Definition: rest.h:133
#define RERROR(fmt,...)
Definition: log.h:207
bfd_auth_t auth
Definition: proto_bfd.c:209
void * decoder
Decoder specific data.
Definition: rest.h:217
#define json_object_object_foreach(obj, key, val)
Definition: json_missing.h:92
void rdebug_pair(log_lvl_t level, REQUEST *, VALUE_PAIR *, char const *)
Print a single valuepair to stderr or error log.
Definition: pair.c:739
#define DIAG_OPTIONAL
Definition: build.h:112
Dictionary attribute.
Definition: dict.h:77
const FR_NAME_NUMBER http_body_type_table[]
Conversion table for type config values.
Definition: rest.c:153
VALUE_PAIR * fr_cursor_next_by_num(vp_cursor_t *cursor, unsigned int vendor, unsigned int attr, int8_t tag)
Iterate over a collection of VALUE_PAIRs of a given type in the pairlist.
Definition: cursor.c:200
void * rad_malloc(size_t size)
Definition: util.c:411
#define MEM(x)
Definition: radiusd.h:396
http_body_type_t force_to
Override the Content-Type header in the response to force decoding as a particular type...
Definition: rest.h:118
#define CURLAUTH_BASIC
Definition: rest.c:73
TALLOC_CTX * radius_list_ctx(REQUEST *request, pair_lists_t list_name)
Return the correct TALLOC_CTX to alloc VALUE_PAIR in, for a list.
Definition: tmpl.c:331
VALUE_PAIR ** radius_list(REQUEST *request, pair_lists_t list)
Resolve attribute pair_lists_t value to an attribute list.
Definition: tmpl.c:195
#define DIAG_OFF(_x)
Definition: build.h:102
fr_connection_pool_t * pool
Pointer to the connection pool.
Definition: rest.h:154
#define INFO(fmt,...)
Definition: log.h:143
static char const * name
char const * start
Start of the buffer.
Definition: rest.c:217
const FR_NAME_NUMBER http_content_type_table[]
Conversion table for "Content-Type" header values.
Definition: rest.c:198
#define UNUSED
Definition: libradius.h:134
static int json_pair_make(rlm_rest_t const *instance, rlm_rest_section_t *section, REQUEST *request, json_object *object, UNUSED int level, int max)
Processes JSON response and converts it into multiple VALUE_PAIRs.
Definition: rest.c:1307
char const * password
Password used for HTTP-Auth.
Definition: rest.h:127
static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userdata)
Encodes VALUE_PAIR linked list in POST format.
Definition: rest.c:519
void size_t fr_pair_value_snprint(char *out, size_t outlen, VALUE_PAIR const *vp, char quote)
Print the value of an attribute to a string.
Definition: pair.c:2107
int rest_init(rlm_rest_t *instance)
Initialises libcurl.
Definition: rest.c:255
VALUE_PAIR * vps
Result of decoding the packet into VALUE_PAIRs.
Definition: libradius.h:162
static int rest_decode_json(rlm_rest_t const *instance, rlm_rest_section_t *section, REQUEST *request, UNUSED void *handle, char *raw, UNUSED size_t rawlen)
Converts JSON response into VALUE_PAIRs and adds them to the request.
Definition: rest.c:1497
Unknown request.
Definition: tmpl.h:109
const FR_NAME_NUMBER fr_tokens_table[]
Definition: token.c:30
char const * p
how much text we've sent so far.
Definition: rest.c:218
VALUE_PAIR * fr_cursor_init(vp_cursor_t *cursor, VALUE_PAIR *const *node)
Setup a cursor to iterate over attribute pairs.
Definition: cursor.c:60
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:686
rlm_rest_curl_context_t * ctx
Context.
Definition: rest.h:239
const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES]
Table of encoder/decoder support.
Definition: rest.c:46
static float timeout
Definition: radclient.c:43
#define inst
static int rest_request_config_body(UNUSED rlm_rest_t const *instance, rlm_rest_section_t *section, REQUEST *request, rlm_rest_handle_t *handle, rest_read_t func)
Configures body specific curlopts.
Definition: rest.c:1897
Definition: token.h:46
#define REST_BODY_MAX_ATTRS
Definition: rest.h:42
REQUEST * request
Current request.
Definition: rest.h:206
vp_cursor_t cursor
Cursor pointing to the start of the list to encode.
Definition: rest.h:193
Abstraction to allow iterating over different configurations of VALUE_PAIRs.
Definition: pair.h:144
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:1545
size_t fr_snprint(char *out, size_t outlen, char const *in, ssize_t inlen, char quote)
Escape any non printable or non-UTF8 characters in the input string.
Definition: print.c:179
static int rest_decode_plain(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section, REQUEST *request, UNUSED void *handle, char *raw, size_t rawlen)
Converts plain response into a single VALUE_PAIR.
Definition: rest.c:974
static void rest_response_init(REQUEST *request, rlm_rest_response_t *ctx, http_body_type_t type)
(Re-)Initialises the data in a rlm_rest_response_t.
Definition: rest.c:1855
size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
URL encodes a string.
Definition: rest.c:2469
int rest_request_config(rlm_rest_t const *instance, rlm_rest_section_t *section, REQUEST *request, void *handle, http_method_t method, http_body_type_t type, char const *uri, char const *username, char const *password)
Configures request curlopts.
Definition: rest.c:1976
#define is_truncated(_ret, _max)
Definition: libradius.h:204
static size_t rest_response_body(void *ptr, size_t size, size_t nmemb, void *userdata)
Processes incoming HTTP body data from libcurl.
Definition: rest.c:1749
int rest_response_decode(rlm_rest_t const *instance, rlm_rest_section_t *section, REQUEST *request, void *handle)
Sends the response to the correct decode function.
Definition: rest.c:2368
size_t(* rest_read_t)(void *ptr, size_t size, size_t nmemb, void *userdata)
Definition: rest.h:246
#define SET_OPTION(_x, _y)
Definition: rest.c:98
http_auth_type_t auth
HTTP auth type.
Definition: rest.h:124
#define rad_assert(expr)
Definition: rad_assert.h:38
static int _mod_conn_free(rlm_rest_handle_t *randle)
Frees a libcurl handle, and any additional memory used by context data.
Definition: rest.c:308
char const * tls_private_key_file
Definition: rest.h:130
struct curl_slist * headers
Any HTTP headers which will be sent with the request.
Definition: rest.h:224
int fr_str2int(FR_NAME_NUMBER const *table, char const *name, int def)
Definition: token.c:451
const FR_NAME_NUMBER http_auth_table[]
Definition: rest.c:169
bool tls_check_cert
Definition: rest.h:135
char const * proxy
Send request via this proxy.
Definition: rest.h:109
char const * data
Custom body data (optional).
Definition: rest.h:121
char const * connect_proxy
Send request via this proxy.
Definition: rest.h:152
#define DEBUG(fmt,...)
Definition: log.h:175
const FR_NAME_NUMBER http_method_table[]
Conversion table for method config values.
Definition: rest.c:133
void * handle
Real Handle.
Definition: rest.h:238
int json_object_object_get_ex(struct json_object *jso, const char *key, struct json_object **value)
Definition: json_missing.c:40
#define REST_BODY_MAX_LEN
Definition: rest.h:40
void rest_response_error(REQUEST *request, rlm_rest_handle_t *handle)
Print out the response text as error lines.
Definition: rest.c:1823
http_auth_type_t
Definition: rest.h:71
ssize_t rest_uri_host_unescape(char **out, UNUSED rlm_rest_t const *mod_inst, REQUEST *request, void *handle, char const *uri)
Unescapes the host portion of a URI string.
Definition: rest.c:2565
static ssize_t rest_request_encode_wrapper(char **out, rest_read_t func, size_t limit, void *userdata)
Emulates successive libcurl calls to an encoding function.
Definition: rest.c:907
ssize_t tmpl_from_attr_str(vp_tmpl_t *vpt, char const *name, request_refs_t request_def, pair_lists_t list_def, bool allow_unknown, bool allow_undefined)
Parse a string into a TMPL_TYPE_ATTR_* or TMPL_TYPE_LIST type vp_tmpl_t.
Definition: tmpl.c:877
Must always come last, should not be in method table.
Definition: rest.h:51
void fr_pair_add(VALUE_PAIR **head, VALUE_PAIR *vp)
Add a VP to the end of the list.
Definition: pair.c:659
const FR_NAME_NUMBER dict_attr_types[]
Map data types to names representing those types.
Definition: dict.c:85
#define CURLAUTH_DIGEST
Definition: rest.c:76
size_t chunk
Chunk size.
Definition: rest.h:195
#define DEBUG2(fmt,...)
Definition: log.h:176
#define STRINGIFY(x)
Definition: build.h:34
static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userdata)
Encodes VALUE_PAIR linked list in JSON format.
Definition: rest.c:698
static void rest_request_init(REQUEST *request, rlm_rest_request_t *ctx, bool sort)
(Re-)Initialises the data in a rlm_rest_request_t.
Definition: rest.c:945
Definition: token.h:43
int radius_request(REQUEST **request, request_refs_t name)
Resolve a request_refs_t to a REQUEST.
Definition: tmpl.c:451
char const * tls_random_file
Definition: rest.h:134
int mod_conn_alive(void *instance, void *handle)
Verifies that the last TCP socket associated with a handle is still active.
Definition: rest.c:426
static VALUE_PAIR * json_pair_make_leaf(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section, TALLOC_CTX *ctx, REQUEST *request, fr_dict_attr_t const *da, json_flags_t *flags, json_object *leaf)
Converts JSON "value" key into VALUE_PAIR.
Definition: rest.c:1189
#define CURLAUTH_NTLM
Definition: rest.c:85
#define CURLAUTH_NTLM_WB
Definition: rest.c:88
rlm_rest_response_t response
Response context data.
Definition: rest.h:231
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
struct json_flags json_flags_t
Flags to control the conversion of JSON values to VALUE_PAIRs.
VALUE_PAIR * fr_cursor_current(vp_cursor_t *cursor)
Return the VALUE_PAIR the cursor current points to.
Definition: cursor.c:304
int8_t fr_pair_cmp_by_da_tag(void const *a, void const *b)
Definition: pair.c:815
char * talloc_typed_asprintf(void const *t, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition: missing.c:611
FR_TOKEN op
The operator that determines how the new VP.
Definition: rest.c:236
char * body
Pointer to the buffer which contains body data/ Only used when not performing chunked encoding...
Definition: rest.h:227
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:272
The current request.
Definition: tmpl.h:113
REQUEST * request
Current request.
Definition: rest.h:190
int rest_request_perform(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section, REQUEST *request, void *handle)
Sends a REST (HTTP) request.
Definition: rest.c:2337
Definition: token.h:45
FR_TOKEN op
Operator to use when moving or inserting valuepair into a list.
Definition: pair.h:118
Flags to control the conversion of JSON values to VALUE_PAIRs.
Definition: rest.c:232
ssize_t radius_xlat(char *out, size_t outlen, REQUEST *request, char const *fmt, xlat_escape_t escape, void *escape_ctx) CC_HINT(nonnull(1
int fr_pair_value_from_str(VALUE_PAIR *vp, char const *value, size_t len)
Convert string value to native attribute value.
Definition: pair.c:1840
char * buffer
Raw incoming HTTP data.
Definition: rest.h:209
long timeout
Timeout in ms.
Definition: rest.h:139
char const * fr_strerror(void)
Get the last library error.
Definition: log.c:212
size_t alloc
Space allocated for buffer.
Definition: rest.h:210
const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES]
Definition: rest.c:106
struct timeval fr_connection_pool_timeout(fr_connection_pool_t *pool)
Connection pool get timeout.
Definition: connection.c:1091
#define RDEBUG2(fmt,...)
Definition: log.h:244
#define REST_BODY_INIT
Definition: rest.h:41
int code
HTTP Status Code.
Definition: rest.h:213
char name[1]
Attribute name.
Definition: dict.h:89
Function prototypes and datatypes for the REST (HTTP) transport.
uint8_t data[]
Definition: eap_pwd.h:625
#define TAG_ANY
Definition: pair.h:191
void fr_pair_list_sort(VALUE_PAIR **vps, fr_cmp_t cmp)
Sort a linked list of VALUE_PAIRs using merge sort.
Definition: pair.c:1036
http_body_type_t force_to
Force decoding the body type as a particular encoding.
Definition: rest.h:215
int do_xlat
If true value will be expanded with xlat.
Definition: rest.c:233
void * mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout)
Creates a new connection handle for use by the FR connection API.
Definition: rest.c:334
VALUE_PAIR * fr_cursor_next(vp_cursor_t *cursor)
Advanced the cursor to the next VALUE_PAIR.
Definition: cursor.c:263
static int rest_decode_post(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section, REQUEST *request, void *handle, char *raw, size_t rawlen)
Converts POST response into VALUE_PAIRs and adds them to the request.
Definition: rest.c:1018
size_t radius_list_name(pair_lists_t *out, char const *name, pair_lists_t default_list)
Resolve attribute name to a pair_lists_t value.
Definition: tmpl.c:120
#define pair_make_reply(_a, _b, _c)
Definition: radiusd.h:546
void rest_cleanup(void)
Cleans up after libcurl.
Definition: rest.c:297
#define CURLAUTH_GSSNEGOTIATE
Definition: rest.c:82
char const * method_str
The string version of the HTTP method.
Definition: rest.h:111
int strncasecmp(char *s1, char *s2, int n)
Definition: missing.c:43
Unknown list.
Definition: tmpl.h:81
http_method_t
Definition: rest.h:44
char const * username
Username used for HTTP-Auth.
Definition: rest.h:126
RADIUS_PACKET * packet
Incoming request.
Definition: radiusd.h:221
Attributes to send in the response.
Definition: tmpl.h:84
char const * tls_certificate_file
Definition: rest.h:129
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:460
#define WARN(fmt,...)
Definition: log.h:144
enum pair_lists pair_lists_t
char const * body_str
The string version of the encoding/content type.
Definition: rest.h:114
#define REDEBUG(fmt,...)
Definition: log.h:254
bool tls_check_cert_cn
Definition: rest.h:136
size_t rest_get_handle_data(char const **out, rlm_rest_handle_t *handle)
Extracts pointer to buffer containing response data.
Definition: rest.c:1871
void rest_request_cleanup(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section, void *handle)
Cleans up after a REST request.
Definition: rest.c:2422
int is_json
If true value will be inserted as raw JSON.
Definition: rest.c:234
VALUE_PAIR * fr_cursor_remove(vp_cursor_t *cursor)
Remove the current pair.
Definition: cursor.c:433
char const * tls_ca_file
Definition: rest.h:132
enum fr_token FR_TOKEN
VALUE_PAIR * fr_cursor_next_peek(vp_cursor_t *cursor)
Return the next VALUE_PAIR without advancing the cursor.
Definition: cursor.c:294
size_t radius_request_name(request_refs_t *out, char const *name, request_refs_t unknown)
Resolve attribute name to a request_refs_t value.
Definition: tmpl.c:413
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:38
char const * xlat_name
Instance name.
Definition: rest.h:147
VALUE_PAIR * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute.
Definition: pair.c:58
#define CURLAUTH_DIGEST_IE
Definition: rest.c:79
char const * fr_int2str(FR_NAME_NUMBER const *table, int number, char const *def)
Definition: token.c:506
fr_dict_attr_t const * da
Dictionary attribute defines the attribute.
Definition: pair.h:113
void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const *src, size_t len)
Copy data into an "string" data type.
Definition: pair.c:2043
rlm_rest_request_t request
Request context data.
Definition: rest.h:230
#define CURLOPT_TLSAUTH_SRP
Definition: rest.c:70
#define FR_TIMEVAL_TO_MS(_x)
Definition: conffile.h:235
char const * tls_private_key_password
Definition: rest.h:131
size_t fr_json_from_pair(char *out, size_t outlen, VALUE_PAIR const *vp)
Prints attribute as string, escaped suitably for use as JSON string.
Definition: json.c:150
#define RWDEBUG(fmt,...)
Definition: log.h:251
PW_TYPE type
Value type.
Definition: dict.h:80
rlm_rest_t * instance
This instance of rlm_rest.
Definition: rest.h:189
#define RCSID(id)
Definition: build.h:135
int fr_substr2int(FR_NAME_NUMBER const *table, char const *name, int def, int len)
Definition: token.c:471
#define fr_json_object_is_type(_obj, _type)
Definition: json_missing.h:57
char const * connect_uri
URI we attempt to connect to, to pre-establish TCP connections.
Definition: rest.h:149
write_state_t state
Decoder state.
Definition: rest.h:207
#define RDEBUG(fmt,...)
Definition: log.h:243
A source or sink of value data.
Definition: tmpl.h:187
enum requests request_refs_t
#define ERROR(fmt,...)
Definition: log.h:145
read_state_t state
Encoder state.
Definition: rest.h:191
ssize_t rest_uri_build(char **out, UNUSED rlm_rest_t *instance, REQUEST *request, char const *uri)
Builds URI; performs XLAT expansions and encoding.
Definition: rest.c:2494
http_body_type_t type
HTTP Content Type.
Definition: rest.h:214
char const * server
Definition: radiusd.h:289
fr_dict_attr_t const * fr_dict_attr_by_name(fr_dict_t *dict, char const *attr)
Locate a fr_dict_attr_t by its name.
Definition: dict.c:3493
#define RDEBUG3(fmt,...)
Definition: log.h:245
void * encoder
Encoder specific data.
Definition: rest.h:197