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.
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.
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
20 * @brief Functions and datatypes for the REST (HTTP) transport.
23 * @copyright 2012-2014 Arran Cudbard-Bell <a.cudbard-bell@freeradius.org>
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>
40 * This is a workaround to backward versions.
42 #if defined(HAVE_JSON) && !defined(JSON_C_MINOR_VERSION) /* The versions less then 10, don't declare the 'JSON_C_MINOR_VERSION'*/
43 int json_object_object_get_ex(struct json_object* jso, const char *key, struct json_object **value);
44 int json_object_object_get_ex(struct json_object* jso, const char *key, struct json_object **value) {
45 *value = json_object_object_get(jso, key);
47 return (*value != NULL);
51 /** Table of encoder/decoder support.
53 * Indexes in this table match the http_body_type_t enum, and should be
54 * updated if additional enum values are added.
56 * @see http_body_type_t
58 const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES] = {
59 HTTP_BODY_UNKNOWN, // HTTP_BODY_UNKNOWN
60 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNSUPPORTED
61 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNAVAILABLE
62 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_INVALID
63 HTTP_BODY_NONE, // HTTP_BODY_NONE
64 HTTP_BODY_CUSTOM_XLAT, // HTTP_BODY_CUSTOM_XLAT
65 HTTP_BODY_CUSTOM_LITERAL, // HTTP_BODY_CUSTOM_LITERAL
66 HTTP_BODY_POST, // HTTP_BODY_POST
68 HTTP_BODY_JSON, // HTTP_BODY_JSON
70 HTTP_BODY_UNAVAILABLE,
72 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_XML
73 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_YAML
74 HTTP_BODY_INVALID, // HTTP_BODY_HTML
75 HTTP_BODY_PLAIN // HTTP_BODY_PLAIN
79 * Lib CURL doesn't define symbols for unsupported auth methods
81 #ifndef CURLOPT_TLSAUTH_SRP
82 # define CURLOPT_TLSAUTH_SRP 0
84 #ifndef CURLAUTH_BASIC
85 # define CURLAUTH_BASIC 0
87 #ifndef CURLAUTH_DIGEST
88 # define CURLAUTH_DIGEST 0
90 #ifndef CURLAUTH_DIGEST_IE
91 # define CURLAUTH_DIGEST_IE 0
93 #ifndef CURLAUTH_GSSNEGOTIATE
94 # define CURLAUTH_GSSNEGOTIATE 0
97 # define CURLAUTH_NTLM 0
99 #ifndef CURLAUTH_NTLM_WB
100 # define CURLAUTH_NTLM_WB 0
106 * #define curl_easy_setopt(handle,opt,param) curl_easy_setopt(handle,opt,param)
109 DIAG_OFF(disabled-macro-expansion)
110 #define SET_OPTION(_x, _y)\
112 if ((ret = curl_easy_setopt(candle, _x, _y)) != CURLE_OK) {\
113 option = STRINGIFY(_x);\
118 const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
119 0, // HTTP_AUTH_UNKNOWN
121 CURLOPT_TLSAUTH_SRP, // HTTP_AUTH_TLS_SRP
122 CURLAUTH_BASIC, // HTTP_AUTH_BASIC
123 CURLAUTH_DIGEST, // HTTP_AUTH_DIGEST
124 CURLAUTH_DIGEST_IE, // HTTP_AUTH_DIGEST_IE
125 CURLAUTH_GSSNEGOTIATE, // HTTP_AUTH_GSSNEGOTIATE
126 CURLAUTH_NTLM, // HTTP_AUTH_NTLM
127 CURLAUTH_NTLM_WB, // HTTP_AUTH_NTLM_WB
128 CURLAUTH_ANY, // HTTP_AUTH_ANY
129 CURLAUTH_ANYSAFE // HTTP_AUTH_ANY_SAFE
133 /** Conversion table for method config values.
135 * HTTP verb strings for http_method_t enum values. Used by libcurl in the
136 * status line of the outgoing HTTP header, by rest_response_header for decoding
137 * incoming HTTP responses, and by the configuration parser.
139 * @note must be kept in sync with http_method_t enum.
145 const FR_NAME_NUMBER http_method_table[] = {
146 { "UNKNOWN", HTTP_METHOD_UNKNOWN },
147 { "GET", HTTP_METHOD_GET },
148 { "POST", HTTP_METHOD_POST },
149 { "PUT", HTTP_METHOD_PUT },
150 { "PATCH", HTTP_METHOD_PATCH },
151 { "DELETE", HTTP_METHOD_DELETE },
156 /** Conversion table for type config values.
158 * Textual names for http_body_type_t enum values, used by the
159 * configuration parser.
161 * @see http_body_Type_t
165 const FR_NAME_NUMBER http_body_type_table[] = {
166 { "unknown", HTTP_BODY_UNKNOWN },
167 { "unsupported", HTTP_BODY_UNSUPPORTED },
168 { "unavailable", HTTP_BODY_UNAVAILABLE },
169 { "invalid", HTTP_BODY_INVALID },
170 { "none", HTTP_BODY_NONE },
171 { "post", HTTP_BODY_POST },
172 { "json", HTTP_BODY_JSON },
173 { "xml", HTTP_BODY_XML },
174 { "yaml", HTTP_BODY_YAML },
175 { "html", HTTP_BODY_HTML },
176 { "plain", HTTP_BODY_PLAIN },
181 const FR_NAME_NUMBER http_auth_table[] = {
182 { "none", HTTP_AUTH_NONE },
183 { "srp", HTTP_AUTH_TLS_SRP },
184 { "basic", HTTP_AUTH_BASIC },
185 { "digest", HTTP_AUTH_DIGEST },
186 { "digest-ie", HTTP_AUTH_DIGEST_IE },
187 { "gss-negotiate", HTTP_AUTH_GSSNEGOTIATE },
188 { "ntlm", HTTP_AUTH_NTLM },
189 { "ntlm-winbind", HTTP_AUTH_NTLM_WB },
190 { "any", HTTP_AUTH_ANY },
191 { "safe", HTTP_AUTH_ANY_SAFE },
196 /** Conversion table for "Content-Type" header values.
198 * Used by rest_response_header for parsing incoming headers.
200 * Values we expect to see in the 'Content-Type:' header of the incoming
203 * Some data types (like YAML) do no have standard MIME types defined,
204 * so multiple types, are listed here.
206 * @see http_body_Type_t
210 const FR_NAME_NUMBER http_content_type_table[] = {
211 { "application/x-www-form-urlencoded", HTTP_BODY_POST },
212 { "application/json", HTTP_BODY_JSON },
213 { "text/html", HTTP_BODY_HTML },
214 { "text/plain", HTTP_BODY_PLAIN },
215 { "text/xml", HTTP_BODY_XML },
216 { "text/yaml", HTTP_BODY_YAML },
217 { "text/x-yaml", HTTP_BODY_YAML },
218 { "application/yaml", HTTP_BODY_YAML },
219 { "application/x-yaml", HTTP_BODY_YAML },
225 * Encoder specific structures.
226 * @todo split encoders/decoders into submodules.
228 typedef struct rest_custom_data {
229 char const *p; //!< how much text we've sent so far.
230 } rest_custom_data_t;
233 /** Flags to control the conversion of JSON values to VALUE_PAIRs.
235 * These fields are set when parsing the expanded format for value pairs in
236 * JSON, and control how json_pair_make_leaf and json_pair_make convert the JSON
237 * value, and move the new VALUE_PAIR into an attribute list.
239 * @see json_pair_make
240 * @see json_pair_make_leaf
242 typedef struct json_flags {
243 int do_xlat; //!< If true value will be expanded with xlat.
244 int is_json; //!< If true value will be inserted as raw JSON
245 // (multiple values not supported).
246 FR_TOKEN op; //!< The operator that determines how the new VP
247 // is processed. @see fr_tokens
251 /** Initialises libcurl.
253 * Allocates global variables and memory required for libcurl to function.
254 * MUST only be called once per module instance.
256 * rest_cleanup must not be called if rest_init fails.
260 * @param[in] instance configuration data.
261 * @return 0 if init succeeded -1 if it failed.
263 int rest_init(rlm_rest_t *instance)
265 static bool version_done;
268 /* developer sanity */
269 rad_assert((sizeof(http_body_type_supported) / sizeof(*http_body_type_supported)) == HTTP_BODY_NUM_ENTRIES);
271 ret = curl_global_init(CURL_GLOBAL_ALL);
272 if (ret != CURLE_OK) {
273 ERROR("rlm_rest (%s): CURL init returned error: %i - %s",
275 ret, curl_easy_strerror(ret));
277 curl_global_cleanup();
282 curl_version_info_data *curlversion;
286 curlversion = curl_version_info(CURLVERSION_NOW);
287 if (strcmp(LIBCURL_VERSION, curlversion->version) != 0) {
288 WARN("rlm_rest: libcurl version changed since the server was built");
289 WARN("rlm_rest: linked: %s built: %s", curlversion->version, LIBCURL_VERSION);
292 INFO("rlm_rest: libcurl version: %s", curl_version());
298 /** Cleans up after libcurl.
300 * Wrapper around curl_global_cleanup, frees any memory allocated by rest_init.
301 * Must only be called once per call of rest_init.
305 void rest_cleanup(void)
307 curl_global_cleanup();
311 /** Frees a libcurl handle, and any additional memory used by context data.
313 * @param[in] randle rlm_rest_handle_t to close and free.
314 * @return returns true.
316 static int _mod_conn_free(rlm_rest_handle_t *randle)
318 curl_easy_cleanup(randle->handle);
323 /** Creates a new connection handle for use by the FR connection API.
325 * Matches the fr_connection_create_t function prototype, is passed to
326 * fr_connection_pool_init, and called when a new connection is required by the
327 * connection pool API.
329 * Creates an instances of rlm_rest_handle_t, and rlm_rest_curl_context_t
330 * which hold the context data required for generating requests and parsing
333 * If instance->connect_uri is not NULL libcurl will attempt to open a
334 * TCP socket to the server specified in the URI. This is done so that when the
335 * socket is first used, there will already be a cached TCP connection to the
336 * REST server associated with the curl handle.
338 * @see fr_connection_pool_init
339 * @see fr_connection_create_t
342 void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
344 rlm_rest_t *inst = instance;
346 rlm_rest_handle_t *randle = NULL;
347 rlm_rest_curl_context_t *curl_ctx = NULL;
349 CURL *candle = curl_easy_init();
351 CURLcode ret = CURLE_OK;
352 char const *option = "unknown";
355 ERROR("rlm_rest (%s): Failed to create CURL handle", inst->xlat_name);
359 SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, inst->connect_timeout);
361 if (inst->connect_uri) {
363 * re-establish TCP connection to webserver. This would usually be
364 * done on the first request, but we do it here to minimise
367 SET_OPTION(CURLOPT_SSL_VERIFYPEER, 0);
368 SET_OPTION(CURLOPT_SSL_VERIFYHOST, 0);
369 SET_OPTION(CURLOPT_CONNECT_ONLY, 1);
370 SET_OPTION(CURLOPT_URL, inst->connect_uri);
371 SET_OPTION(CURLOPT_NOSIGNAL, 1);
373 DEBUG("rlm_rest (%s): Connecting to \"%s\"", inst->xlat_name, inst->connect_uri);
375 ret = curl_easy_perform(candle);
376 if (ret != CURLE_OK) {
377 ERROR("rlm_rest (%s): Connection failed: %i - %s", inst->xlat_name, ret, curl_easy_strerror(ret));
379 goto connection_error;
382 DEBUG2("rlm_rest (%s): Skipping pre-connect, connect_uri not specified", inst->xlat_name);
386 * Allocate memory for the connection handle abstraction.
388 randle = talloc_zero(ctx, rlm_rest_handle_t);
389 curl_ctx = talloc_zero(randle, rlm_rest_curl_context_t);
391 curl_ctx->headers = NULL; /* CURL needs this to be NULL */
392 curl_ctx->request.instance = inst;
394 randle->ctx = curl_ctx;
395 randle->handle = candle;
396 talloc_set_destructor(randle, _mod_conn_free);
399 * Clear any previously configured options for the first request.
401 curl_easy_reset(candle);
406 * Cleanup for error conditions.
409 ERROR("rlm_rest (%s): Failed setting curl option %s: %s (%i)", inst->xlat_name, option,
410 curl_easy_strerror(ret), ret);
413 * So we don't leak CURL handles.
416 curl_easy_cleanup(candle);
417 if (randle) talloc_free(randle);
422 /** Verifies that the last TCP socket associated with a handle is still active.
424 * Quieries libcurl to try and determine if the TCP socket associated with a
425 * connection handle is still viable.
427 * @param[in] instance configuration data.
428 * @param[in] handle to check.
429 * @returns false if the last socket is dead, or if the socket state couldn't be
430 * determined, else true.
432 int mod_conn_alive(void *instance, void *handle)
434 rlm_rest_t *inst = instance;
435 rlm_rest_handle_t *randle = handle;
436 CURL *candle = randle->handle;
441 ret = curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
442 if (ret != CURLE_OK) {
443 ERROR("rlm_rest (%s): Couldn't determine socket state: %i - %s", inst->xlat_name, ret,
444 curl_easy_strerror(ret));
449 if (last_socket == -1) {
456 /** Copies a pre-expanded xlat string to the output buffer
458 * @param[out] out Char buffer to write encoded data to.
459 * @param[in] size Multiply by nmemb to get the length of ptr.
460 * @param[in] nmemb Multiply by size to get the length of ptr.
461 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
462 * @return length of data (including NULL) written to ptr, or 0 if no more
465 static size_t rest_encode_custom(void *out, size_t size, size_t nmemb, void *userdata)
467 rlm_rest_request_t *ctx = userdata;
468 rest_custom_data_t *data = ctx->encoder;
470 size_t freespace = (size * nmemb) - 1;
473 len = strlcpy(out, data->p, freespace);
474 if (is_truncated(len, freespace)) {
475 data->p += (freespace - 1);
476 return freespace - 1;
483 /** Encodes VALUE_PAIR linked list in POST format
485 * This is a stream function matching the rest_read_t prototype. Multiple
486 * successive calls will return additional encoded VALUE_PAIRs.
487 * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
488 * will be written to the ptr buffer.
490 * POST request format is:
491 * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
493 * All attributes and values are url encoded. There is currently no support for
494 * nested attributes, or attribute qualifiers.
496 * Nested attributes may be added in the future using
497 * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
498 * to denotate nesting.
500 * Requires libcurl for url encoding.
502 * @see rest_decode_post
504 * @param[out] out Char buffer to write encoded data to.
505 * @param[in] size Multiply by nmemb to get the length of ptr.
506 * @param[in] nmemb Multiply by size to get the length of ptr.
507 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
508 * @return length of data (including NULL) written to ptr, or 0 if no more
511 static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userdata)
513 rlm_rest_request_t *ctx = userdata;
514 REQUEST *request = ctx->request; /* Used by RDEBUG */
517 char *p = out; /* Position in buffer */
518 char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
519 char *escaped; /* Pointer to current URL escaped data */
522 size_t freespace = (size * nmemb) - 1;
524 /* Allow manual chunking */
525 if ((ctx->chunk) && (ctx->chunk <= freespace)) {
526 freespace = (ctx->chunk - 1);
529 if (ctx->state == READ_STATE_END) return 0;
531 /* Post data requires no headers */
532 if (ctx->state == READ_STATE_INIT) ctx->state = READ_STATE_ATTR_BEGIN;
534 while (freespace > 0) {
535 vp = fr_cursor_current(&ctx->cursor);
537 ctx->state = READ_STATE_END;
542 RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
544 if (ctx->state == READ_STATE_ATTR_BEGIN) {
545 escaped = curl_escape(vp->da->name, strlen(vp->da->name));
547 REDEBUG("Failed escaping string \"%s\"", vp->da->name);
551 len = strlen(escaped);
552 if (freespace < (1 + len)) {
557 len = sprintf(p, "%s=", escaped);
563 * We wrote the attribute header, record progress.
566 ctx->state = READ_STATE_ATTR_CONT;
570 * Write out single attribute string.
572 len = vp_prints_value(p, freespace, vp, 0);
573 if (is_truncated(len, freespace)) goto no_space;
576 RDEBUG3("Length : %zd", len);
579 escaped = curl_escape(p, len);
581 REDEBUG("Failed escaping string \"%s\"", vp->da->name);
584 len = strlen(escaped);
586 if (freespace < len) {
591 len = strlcpy(p, escaped, len + 1);
596 RDEBUG3("Value : %s", p);
604 * there are more attributes, insert a separator
606 if (fr_cursor_next(&ctx->cursor)) {
607 if (freespace < 1) goto no_space;
613 * We wrote one full attribute value pair, record progress.
617 ctx->state = READ_STATE_ATTR_BEGIN;
622 len = p - (char *)out;
624 RDEBUG3("POST Data: %s", (char *)out);
625 RDEBUG3("Returning %zd bytes of POST data", len);
630 * Cleanup for error conditions
635 len = encoded - (char *)out;
637 RDEBUG3("POST Data: %s", (char *)out);
640 * The buffer wasn't big enough to encode a single attribute chunk.
643 REDEBUG("Failed encoding attribute");
645 RDEBUG3("Returning %zd bytes of POST data (buffer full or chunk exceeded)", len);
652 /** Encodes VALUE_PAIR linked list in JSON format
654 * This is a stream function matching the rest_read_t prototype. Multiple
655 * successive calls will return additional encoded VALUE_PAIRs.
657 * Only complete attribute headers
658 * @verbatim "<name>":{"type":"<type>","value":[' @endverbatim
659 * and complete attribute values will be written to ptr.
661 * If an attribute occurs multiple times in the request the attribute values
662 * will be concatenated into a single value array.
664 * JSON request format is:
669 "value":[<value0>,<value1>,<valueN>]
682 * @param[out] out Char buffer to write encoded data to.
683 * @param[in] size Multiply by nmemb to get the length of ptr.
684 * @param[in] nmemb Multiply by size to get the length of ptr.
685 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
686 * @return length of data (including NULL) written to ptr, or 0 if no more
689 static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userdata)
691 rlm_rest_request_t *ctx = userdata;
692 REQUEST *request = ctx->request; /* Used by RDEBUG */
693 VALUE_PAIR *vp, *next;
695 char *p = out; /* Position in buffer */
696 char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
701 size_t freespace = (size * nmemb) - 1; /* account for the \0 byte here */
703 rad_assert(freespace > 0);
705 /* Allow manual chunking */
706 if ((ctx->chunk) && (ctx->chunk <= freespace)) {
707 freespace = (ctx->chunk - 1);
710 if (ctx->state == READ_STATE_END) return 0;
712 if (ctx->state == READ_STATE_INIT) {
713 ctx->state = READ_STATE_ATTR_BEGIN;
715 if (freespace < 1) goto no_space;
721 vp = fr_cursor_current(&ctx->cursor);
724 * We've encoded all the VPs
726 * The check for READ_STATE_ATTR_BEGIN is needed as we might be in
727 * READ_STATE_ATTR_END, and need to close out the current attribute
730 if (!vp && (ctx->state == READ_STATE_ATTR_BEGIN)) {
731 if (freespace < 1) goto no_space;
735 ctx->state = READ_STATE_END;
740 if (ctx->state == READ_STATE_ATTR_BEGIN) {
742 * New attribute, write name, type, and beginning of value array.
744 RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
746 type = fr_int2str(dict_attr_types, vp->da->type, "<INVALID>");
748 len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type);
749 if (len >= freespace) goto no_space;
754 RDEBUG3("Type : %s", type);
757 * We wrote the attribute header, record progress
760 ctx->state = READ_STATE_ATTR_CONT;
763 if (ctx->state == READ_STATE_ATTR_CONT) {
767 rad_assert(vp); /* coverity */
770 * We need at least a single byte to write out the
771 * shortest attribute value.
773 if (freespace < 1) goto no_space;
776 * Code below expects length of the buffer, so we
777 * add +1 to freespace.
779 * If we know we need a comma after the value, we
780 * need to -1 to make sure we have enough room to
783 attr_space = fr_cursor_next_peek(&ctx->cursor) ? freespace - 1 : freespace;
784 len = vp_prints_value_json(p, attr_space + 1, vp);
785 if (is_truncated(len, attr_space + 1)) goto no_space;
788 * Show actual value length minus quotes
791 RDEBUG3("Length : %zu", (size_t) (*p == '"') ? (len - 2) : len);
792 RDEBUG3("Value : %s", p);
800 * Multivalued attribute, we sorted all the attributes earlier, so multiple
801 * instances should occur in a contiguous block.
803 if ((next = fr_cursor_next(&ctx->cursor)) && (vp->da == next->da)) {
804 rad_assert(freespace >= 1);
809 * We wrote one attribute value, record progress.
817 ctx->state = READ_STATE_ATTR_END;
820 if (ctx->state == READ_STATE_ATTR_END) {
821 next = fr_cursor_current(&ctx->cursor);
822 if (freespace < 2) goto no_space;
828 if (freespace < 1) goto no_space;
834 * We wrote one full attribute value pair, record progress.
837 ctx->state = READ_STATE_ATTR_BEGIN;
843 len = p - (char *)out;
845 RDEBUG3("JSON Data: %s", (char *)out);
846 RDEBUG3("Returning %zd bytes of JSON data", len);
851 * Were out of buffer space
856 len = encoded - (char *)out;
858 RDEBUG3("JSON Data: %s", (char *)out);
861 * The buffer wasn't big enough to encode a single attribute chunk.
864 REDEBUG("AVP exceeds buffer length or chunk");
866 RDEBUG2("Returning %zd bytes of JSON data (buffer full or chunk exceeded)", len);
873 /** Emulates successive libcurl calls to an encoding function
875 * This function is used when the request will be sent to the HTTP server as one
876 * contiguous entity. A buffer of REST_BODY_INIT bytes is allocated and passed
877 * to the stream encoding function.
879 * If the stream function does not return 0, a new buffer is allocated which is
880 * the size of the previous buffer + REST_BODY_INIT bytes, the data from the
881 * previous buffer is copied, and freed, and another call is made to the stream
882 * function, passing a pointer into the new buffer at the end of the previously
885 * This process continues until the stream function signals (by returning 0)
886 * that it has no more data to write.
888 * @param[out] buffer where the pointer to the alloced buffer should
890 * @param[in] func Stream function.
891 * @param[in] limit Maximum buffer size to alloc.
892 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls to
894 * @return the length of the data written to the buffer (excluding NULL) or -1
897 static ssize_t rest_request_encode_wrapper(char **buffer, rest_read_t func, size_t limit, void *userdata)
899 char *previous = NULL;
900 char *current = NULL;
902 size_t alloc = REST_BODY_INIT; /* Size of buffer to alloc */
903 size_t used = 0; /* Size of data written */
906 while (alloc <= limit) {
907 current = rad_malloc(alloc);
910 strlcpy(current, previous, used + 1);
914 len = func(current + used, alloc - used, 1, userdata);
930 /** (Re-)Initialises the data in a rlm_rest_request_t.
932 * Resets the values of a rlm_rest_request_t to their defaults.
934 * @param[in] request Current request.
935 * @param[in] ctx to initialise.
936 * @param[in] sort If true VALUE_PAIRs will be sorted within the VALUE_PAIR
939 static void rest_request_init(REQUEST *request, rlm_rest_request_t *ctx, bool sort)
942 * Setup stream read data
944 ctx->request = request;
945 ctx->state = READ_STATE_INIT;
948 * Sorts pairs in place, oh well...
951 fr_pair_list_sort(&request->packet->vps, fr_pair_cmp_by_da_tag);
953 fr_cursor_init(&ctx->cursor, &request->packet->vps);
956 /** Converts plain response into a single VALUE_PAIR
958 * @param[in] instance configuration data.
959 * @param[in] section configuration data.
960 * @param[in] handle rlm_rest_handle_t to use.
961 * @param[in] request Current request.
962 * @param[in] raw buffer containing POST data.
963 * @param[in] rawlen Length of data in raw buffer.
964 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
966 static int rest_decode_plain(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
967 REQUEST *request, UNUSED void *handle, char *raw, size_t rawlen)
974 if (*raw == '\0') return 0;
977 * Use rawlen to protect against overrun, and to cope with any binary data
979 vp = pair_make_reply("REST-HTTP-Body", NULL, T_OP_ADD);
980 fr_pair_value_bstrncpy(vp, raw, rawlen);
982 RDEBUG2("Adding reply:REST-HTTP-Body += \"%s\"", vp->vp_strvalue);
987 /** Converts POST response into VALUE_PAIRs and adds them to the request
989 * Accepts VALUE_PAIRS in the same format as rest_encode_post, but with the
990 * addition of optional attribute list qualifiers as part of the attribute name
993 * If no qualifiers are specified, will default to the request list.
995 * POST response format is:
996 * @verbatim [outer.][<list>:]<attribute0>=<value0>&[outer.][<list>:]<attribute1>=<value1>&[outer.][<list>:]<attributeN>=<valueN> @endverbatim
998 * @see rest_encode_post
1000 * @param[in] instance configuration data.
1001 * @param[in] section configuration data.
1002 * @param[in] handle rlm_rest_handle_t to use.
1003 * @param[in] request Current request.
1004 * @param[in] raw buffer containing POST data.
1005 * @param[in] rawlen Length of data in raw buffer.
1006 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
1008 static int rest_decode_post(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
1009 REQUEST *request, void *handle, char *raw, size_t rawlen)
1011 rlm_rest_handle_t *randle = handle;
1012 CURL *candle = randle->handle;
1014 char const *p = raw, *q;
1016 char const *attribute;
1020 char *expanded = NULL;
1022 DICT_ATTR const *da;
1025 pair_lists_t list_name;
1026 request_refs_t request_name;
1027 REQUEST *reference = request;
1032 int curl_len; /* Length from last curl_easy_unescape call */
1040 while (isspace(*p)) p++;
1041 if (*p == '\0') return 0;
1043 while (((q = strchr(p, '=')) != NULL) && (count < REST_BODY_MAX_ATTRS)) {
1044 reference = request;
1046 name = curl_easy_unescape(candle, p, (q - p), &curl_len);
1049 RDEBUG2("Parsing attribute \"%s\"", name);
1052 * The attribute pointer is updated to point to the portion of
1053 * the string after the list qualifier.
1056 attribute += radius_request_name(&request_name, attribute, REQUEST_CURRENT);
1057 if (request_name == REQUEST_UNKNOWN) {
1058 RWDEBUG("Invalid request qualifier, skipping");
1065 if (radius_request(&reference, request_name) < 0) {
1066 RWDEBUG("Attribute name refers to outer request but not in a tunnel, skipping");
1073 attribute += radius_list_name(&list_name, attribute, PAIR_LIST_REPLY);
1074 if (list_name == PAIR_LIST_UNKNOWN) {
1075 RWDEBUG("Invalid list qualifier, skipping");
1081 da = dict_attrbyname(attribute);
1083 RWDEBUG("Attribute \"%s\" unknown, skipping", attribute);
1090 vps = radius_list(reference, list_name);
1094 RDEBUG3("Type : %s", fr_int2str(dict_attr_types, da->type, "<INVALID>"));
1096 ctx = radius_list_ctx(reference, list_name);
1099 len = (!q) ? (rawlen - (p - raw)) : (unsigned)(q - p);
1101 value = curl_easy_unescape(candle, p, len, &curl_len);
1104 * If we found a delimiter we want to skip over it,
1105 * if we didn't we do *NOT* want to skip over the end
1108 p += (!q) ? len : (len + 1);
1110 RDEBUG3("Length : %i", curl_len);
1111 RDEBUG3("Value : \"%s\"", value);
1114 RDEBUG2("Performing xlat expansion of response value");
1116 if (radius_axlat(&expanded, request, value, NULL, NULL) < 0) {
1120 vp = fr_pair_afrom_da(ctx, da);
1122 REDEBUG("Failed creating valuepair");
1123 talloc_free(expanded);
1128 ret = fr_pair_value_from_str(vp, expanded, -1);
1129 TALLOC_FREE(expanded);
1131 RWDEBUG("Incompatible value assignment, skipping");
1136 fr_pair_add(vps, vp);
1154 REDEBUG("Malformed POST data \"%s\"", raw);
1162 /** Converts JSON "value" key into VALUE_PAIR.
1164 * If leaf is not in fact a leaf node, but contains JSON data, the data will
1165 * written to the attribute in JSON string format.
1167 * @param[in] instance configuration data.
1168 * @param[in] section configuration data.
1169 * @param[in] ctx to allocate new VALUE_PAIRs in.
1170 * @param[in] request Current request.
1171 * @param[in] da Attribute to create.
1172 * @param[in] flags containing the operator other flags controlling value
1174 * @param[in] leaf object containing the VALUE_PAIR value.
1175 * @return The VALUE_PAIR just created, or NULL on error.
1177 static VALUE_PAIR *json_pair_make_leaf(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
1178 TALLOC_CTX *ctx, REQUEST *request, DICT_ATTR const *da,
1179 json_flags_t *flags, json_object *leaf)
1181 char const *value, *to_parse;
1182 char *expanded = NULL;
1187 if (json_object_is_type(leaf, json_type_null)) {
1188 RDEBUG3("Got null value for attribute \"%s\", skipping...", da->name);
1194 * Should encode any nested JSON structures into JSON strings.
1196 * "I knew you liked JSON so I put JSON in your JSON!"
1198 value = json_object_get_string(leaf);
1200 RWDEBUG("Failed getting string value for attribute \"%s\", skipping...", da->name);
1206 RDEBUG3("Type : %s", fr_int2str(dict_attr_types, da->type, "<INVALID>"));
1207 RDEBUG3("Length : %zu", strlen(value));
1208 RDEBUG3("Value : \"%s\"", value);
1211 if (flags->do_xlat) {
1212 if (radius_axlat(&expanded, request, value, NULL, NULL) < 0) {
1216 to_parse = expanded;
1221 vp = fr_pair_afrom_da(ctx, da);
1223 RWDEBUG("Failed creating valuepair for attribute \"%s\", skipping...", da->name);
1224 talloc_free(expanded);
1231 ret = fr_pair_value_from_str(vp, to_parse, -1);
1232 talloc_free(expanded);
1234 RWDEBUG("Incompatible value assignment for attribute \"%s\", skipping...", da->name);
1243 /** Processes JSON response and converts it into multiple VALUE_PAIRs
1245 * Processes JSON attribute declarations in the format below. Will recurse when
1246 * processing nested attributes. When processing nested attributes flags and
1247 * operators from previous attributes are not inherited.
1249 * JSON response format is:
1256 "value":[<value0>,<value1>,<valueN>]
1260 "<nested-attribute0>":{
1266 "<attribute2>":"<value0>",
1267 "<attributeN>":[<value0>,<value1>,<valueN>]
1271 * JSON valuepair flags:
1272 * - do_xlat (optional) Controls xlat expansion of values. Defaults to true.
1273 * - is_json (optional) If true, any nested JSON data will be copied to the
1274 * VALUE_PAIR in string form. Defaults to true.
1275 * - op (optional) Controls how the attribute is inserted into
1276 * the target list. Defaults to ':=' (T_OP_SET).
1278 * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
1279 * second and subsequent values in multivalued attributes. This does not work
1280 * between multiple attribute declarations.
1284 * @param[in] instance configuration data.
1285 * @param[in] section configuration data.
1286 * @param[in] request Current request.
1287 * @param[in] object containing root node, or parent node.
1288 * @param[in] level Current nesting level.
1289 * @param[in] max counter, decremented after each VALUE_PAIR is created,
1290 * when 0 no more attributes will be processed.
1291 * @return number of attributes created or < 0 on error.
1293 static int json_pair_make(rlm_rest_t *instance, rlm_rest_section_t *section,
1294 REQUEST *request, json_object *object, UNUSED int level, int max)
1296 struct lh_entry *entry;
1297 int max_attrs = max;
1299 if (!json_object_is_type(object, json_type_object)) {
1300 #ifdef HAVE_JSON_TYPE_TO_NAME
1301 REDEBUG("Can't process VP container, expected JSON object"
1302 "got \"%s\", skipping...",
1303 json_type_to_name(json_object_get_type(object)));
1305 REDEBUG("Can't process VP container, expected JSON object"
1312 * Process VP container
1314 for (entry = json_object_get_object(object)->head;
1316 entry = entry->next) {
1317 int i = 0, elements;
1318 struct json_object *value, *element, *tmp;
1321 char const *name = (char const *)entry->k;
1323 json_flags_t flags = {
1330 REQUEST *current = request;
1331 VALUE_PAIR **vps, *vp = NULL;
1333 memset(&dst, 0, sizeof(dst));
1335 /* Fix the compiler warnings regarding const... */
1336 memcpy(&value, &entry->v, sizeof(value));
1339 * Resolve attribute name to a dictionary entry and pairlist.
1341 RDEBUG2("Parsing attribute \"%s\"", name);
1343 if (tmpl_from_attr_str(&dst, name, REQUEST_CURRENT, PAIR_LIST_REPLY, false, false) <= 0) {
1344 RWDEBUG("Failed parsing attribute: %s, skipping...", fr_strerror());
1348 if (radius_request(¤t, dst.tmpl_request) < 0) {
1349 RWDEBUG("Attribute name refers to outer request but not in a tunnel, skipping...");
1353 vps = radius_list(current, dst.tmpl_list);
1355 RWDEBUG("List not valid in this context, skipping...");
1358 ctx = radius_list_ctx(current, dst.tmpl_list);
1361 * Alternative JSON structure which allows operator,
1362 * and other flags to be specified.
1372 * - [] Multivalued array
1373 * - {} Nested Valuepair
1374 * - * Integer or string value
1376 if (json_object_is_type(value, json_type_object)) {
1378 * Process operator if present.
1380 if (json_object_object_get_ex(value, "op", &tmp)) {
1381 flags.op = fr_str2int(fr_tokens, json_object_get_string(tmp), 0);
1383 RWDEBUG("Invalid operator value \"%s\", skipping...",
1384 json_object_get_string(tmp));
1390 * Process optional do_xlat bool.
1392 if (json_object_object_get_ex(value, "do_xlat", &tmp)) {
1393 flags.do_xlat = json_object_get_boolean(tmp);
1397 * Process optional is_json bool.
1399 if (json_object_object_get_ex(value, "is_json", &tmp)) {
1400 flags.is_json = json_object_get_boolean(tmp);
1404 * Value key must be present if were using the expanded syntax.
1406 if (!json_object_object_get_ex(value, "value", &value)) {
1407 RWDEBUG("Value key missing, skipping...");
1413 * Setup fr_pair_make / recursion loop.
1415 if (!flags.is_json && json_object_is_type(value, json_type_array)) {
1416 elements = json_object_array_length(value);
1418 RWDEBUG("Zero length value array, skipping...");
1421 element = json_object_array_get_idx(value, 0);
1428 * A JSON 'value' key, may have multiple elements, iterate
1429 * over each of them, creating a new VALUE_PAIR.
1432 if (max_attrs-- <= 0) {
1433 RWDEBUG("At maximum attribute limit");
1438 * Automagically switch the op for multivalued attributes.
1440 if (((flags.op == T_OP_SET) || (flags.op == T_OP_EQ)) && (i >= 1)) {
1441 flags.op = T_OP_ADD;
1444 if (json_object_is_type(element, json_type_object) && !flags.is_json) {
1445 /* TODO: Insert nested VP into VP structure...*/
1446 RWDEBUG("Found nested VP, these are not yet supported, skipping...");
1451 vp = json_pair_make(instance, section,
1453 level + 1, max_attrs);*/
1455 vp = json_pair_make_leaf(instance, section, ctx, request,
1456 dst.tmpl_da, &flags, element);
1459 rdebug_pair(2, request, vp, NULL);
1460 radius_pairmove(current, vps, vp, false);
1462 * If we call json_object_array_get_idx on something that's not an array
1463 * the behaviour appears to be to occasionally segfault.
1465 } while ((++i < elements) && (element = json_object_array_get_idx(value, i)));
1468 return max - max_attrs;
1471 /** Converts JSON response into VALUE_PAIRs and adds them to the request.
1473 * Converts the raw JSON string into a json-c object tree and passes it to
1474 * json_pair_make. After the tree has been parsed json_object_put is called
1475 * which decrements the reference count of the root node by one, and frees
1478 * @see rest_encode_json
1479 * @see json_pair_make
1481 * @param[in] instance configuration data.
1482 * @param[in] section configuration data.
1483 * @param[in,out] request Current request.
1484 * @param[in] handle REST handle.
1485 * @param[in] raw buffer containing JSON data.
1486 * @param[in] rawlen Length of data in raw buffer.
1487 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
1489 static int rest_decode_json(rlm_rest_t *instance, rlm_rest_section_t *section,
1490 REQUEST *request, UNUSED void *handle, char *raw, UNUSED size_t rawlen)
1492 char const *p = raw;
1494 struct json_object *json;
1501 while (isspace(*p)) p++;
1502 if (*p == '\0') return 0;
1504 json = json_tokener_parse(p);
1506 REDEBUG("Malformed JSON data \"%s\"", raw);
1510 ret = json_pair_make(instance, section, request, json, 0, REST_BODY_MAX_ATTRS);
1513 * Decrement reference count for root object, should free entire JSON tree.
1515 json_object_put(json);
1521 /** Processes incoming HTTP header data from libcurl.
1523 * Processes the status line, and Content-Type headers from the incoming HTTP
1526 * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1529 * @param[in] in Char buffer where inbound header data is written.
1530 * @param[in] size Multiply by nmemb to get the length of ptr.
1531 * @param[in] nmemb Multiply by size to get the length of ptr.
1532 * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1533 * @return Length of data processed, or 0 on error.
1535 static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *userdata)
1537 rlm_rest_response_t *ctx = userdata;
1538 REQUEST *request = ctx->request; /* Used by RDEBUG */
1540 char const *p = in, *q;
1542 size_t const t = (size * nmemb);
1546 http_body_type_t type;
1549 * Curl seems to throw these (\r\n) in before the next set of headers when
1550 * looks like it's just a body separator and safe to ignore after we
1551 * receive a 100 Continue.
1553 if (t == 2 && ((p[0] == '\r') && (p[1] == '\n'))) return t;
1555 switch (ctx->state) {
1556 case WRITE_STATE_INIT:
1557 RDEBUG2("Processing response header");
1560 * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1562 * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
1565 REDEBUG("Malformed HTTP header: Status line too short");
1569 * Check start of header matches...
1571 if (strncasecmp("HTTP/", p, 5) != 0) {
1572 REDEBUG("Malformed HTTP header: Missing HTTP version");
1579 * Skip the version field, next space should mark start of reason_code.
1581 q = memchr(p, ' ', s);
1583 RDEBUG("Malformed HTTP header: Missing reason code");
1591 * Process reason_code.
1593 * " 100" (4) + "\r\n" (2) = 6
1596 REDEBUG("Malformed HTTP header: Reason code too short");
1602 /* Char after reason code must be a space, or \r */
1603 if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
1605 ctx->code = atoi(p);
1608 * Process reason_phrase (if present).
1615 q = memchr(p, '\r', s);
1616 if (!q) goto malformed;
1620 RDEBUG2("Status : %i (%.*s)", ctx->code, (int) len, p);
1622 RDEBUG2("Status : %i", ctx->code);
1626 ctx->state = WRITE_STATE_PARSE_HEADERS;
1630 case WRITE_STATE_PARSE_HEADERS:
1632 (strncasecmp("Content-Type: ", p, 14) == 0)) {
1637 * Check to see if there's a parameter separator.
1639 q = memchr(p, ';', s);
1642 * If there's not, find the end of this header.
1644 if (!q) q = memchr(p, '\r', s);
1646 len = !q ? s : (size_t) (q - p);
1647 type = fr_substr2int(http_content_type_table, p, HTTP_BODY_UNKNOWN, len);
1650 RDEBUG2("Type : %s (%.*s)", fr_int2str(http_body_type_table, type, "<INVALID>"),
1655 * Assume the force_to value has already been validated.
1657 if (ctx->force_to != HTTP_BODY_UNKNOWN) {
1658 if (ctx->force_to != ctx->type) {
1659 RDEBUG3("Forcing body type to \"%s\"",
1660 fr_int2str(http_body_type_table, ctx->force_to, "<INVALID>"));
1661 ctx->type = ctx->force_to;
1664 * Figure out if the type is supported by one of the decoders.
1667 ctx->type = http_body_type_supported[type];
1668 switch (ctx->type) {
1669 case HTTP_BODY_UNKNOWN:
1670 RWDEBUG("Couldn't determine type, using the request's type \"%s\".",
1671 fr_int2str(http_body_type_table, type, "<INVALID>"));
1674 case HTTP_BODY_UNSUPPORTED:
1675 REDEBUG("Type \"%s\" is currently unsupported",
1676 fr_int2str(http_body_type_table, type, "<INVALID>"));
1679 case HTTP_BODY_UNAVAILABLE:
1680 REDEBUG("Type \"%s\" is unavailable, please rebuild this module with the required "
1681 "library", fr_int2str(http_body_type_table, type, "<INVALID>"));
1684 case HTTP_BODY_INVALID:
1685 REDEBUG("Type \"%s\" is not a valid web API data markup format",
1686 fr_int2str(http_body_type_table, type, "<INVALID>"));
1689 /* supported type */
1702 * If we got a 100 Continue, we need to send additional payload data.
1703 * reset the state to WRITE_STATE_INIT, so that when were called again
1704 * we overwrite previous header data with that from the proper header.
1706 if (ctx->code == 100) {
1707 RDEBUG2("Continuing...");
1708 ctx->state = WRITE_STATE_INIT;
1717 fr_prints(escaped, sizeof(escaped), (char *) in, t, '\0');
1719 REDEBUG("Received %zu bytes of response data: %s", t, escaped);
1726 /** Processes incoming HTTP body data from libcurl.
1728 * Writes incoming body data to an intermediary buffer for later parsing by
1729 * one of the decode functions.
1731 * @param[in] ptr Char buffer where inbound header data is written
1732 * @param[in] size Multiply by nmemb to get the length of ptr.
1733 * @param[in] nmemb Multiply by size to get the length of ptr.
1734 * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1735 * @return length of data processed, or 0 on error.
1737 static size_t rest_response_body(void *ptr, size_t size, size_t nmemb, void *userdata)
1739 rlm_rest_response_t *ctx = userdata;
1740 REQUEST *request = ctx->request; /* Used by RDEBUG */
1742 char const *p = ptr, *q;
1745 size_t const t = (size * nmemb);
1747 if (t == 0) return 0;
1750 * Any post processing of headers should go here...
1752 if (ctx->state == WRITE_STATE_PARSE_HEADERS) {
1753 ctx->state = WRITE_STATE_PARSE_CONTENT;
1756 switch (ctx->type) {
1757 case HTTP_BODY_UNSUPPORTED:
1758 case HTTP_BODY_UNAVAILABLE:
1759 case HTTP_BODY_INVALID:
1760 while ((q = memchr(p, '\n', t - (p - (char *)ptr)))) {
1761 REDEBUG("%.*s", (int) (q - p), p);
1766 REDEBUG("%.*s", (int)(t - (p - (char *)ptr)), p);
1771 case HTTP_BODY_NONE:
1772 while ((q = memchr(p, '\n', t - (p - (char *)ptr)))) {
1773 RDEBUG3("%.*s", (int) (q - p), p);
1778 RDEBUG3("%.*s", (int)(t - (p - (char *)ptr)), p);
1784 if (t > (ctx->alloc - ctx->used)) {
1785 ctx->alloc += ((t + 1) > REST_BODY_INIT) ? t + 1 : REST_BODY_INIT;
1789 ctx->buffer = rad_malloc(ctx->alloc);
1791 /* If data has been written previously */
1793 strlcpy(ctx->buffer, tmp, (ctx->used + 1));
1797 strlcpy(ctx->buffer + ctx->used, p, t + 1);
1806 /** Print out the response text as error lines
1808 * @param request The Current request.
1809 * @param handle rlm_rest_handle_t used to execute the previous request.
1811 void rest_response_error(REQUEST *request, rlm_rest_handle_t *handle)
1816 len = rest_get_handle_data(&p, handle);
1818 RERROR("Server returned no data");
1822 RERROR("Server returned:");
1823 while ((q = strchr(p, '\n'))) {
1824 RERROR("%.*s", (int) (q - p), p);
1827 if (*p != '\0') RERROR("%s", p);
1830 /** (Re-)Initialises the data in a rlm_rest_response_t.
1832 * This resets the values of the a rlm_rest_response_t to their defaults.
1833 * Must be called between encoding sessions.
1835 * @see rest_response_body
1836 * @see rest_response_header
1838 * @param[in] request Current request.
1839 * @param[in] ctx data to initialise.
1840 * @param[in] type Default http_body_type to use when decoding raw data, may be
1841 * overwritten by rest_response_header.
1843 static void rest_response_init(REQUEST *request, rlm_rest_response_t *ctx, http_body_type_t type)
1845 ctx->request = request;
1847 ctx->state = WRITE_STATE_INIT;
1853 /** Extracts pointer to buffer containing response data
1855 * @param[out] out Where to write the pointer to the buffer.
1856 * @param[in] handle used for the last request.
1857 * @return > 0 if data is available.
1859 size_t rest_get_handle_data(char const **out, rlm_rest_handle_t *handle)
1861 rlm_rest_curl_context_t *ctx = handle->ctx;
1863 rad_assert(ctx->response.buffer || (!ctx->response.buffer && !ctx->response.used));
1865 *out = ctx->response.buffer;
1866 return ctx->response.used;
1869 /** Configures body specific curlopts.
1871 * Configures libcurl handle to use either chunked mode, where the request
1872 * data will be sent using multiple HTTP requests, or contiguous mode where
1873 * the request data will be sent in a single HTTP request.
1875 * @param[in] instance configuration data.
1876 * @param[in] section configuration data.
1877 * @param[in] request Current request.
1878 * @param[in] handle rlm_rest_handle_t to configure.
1879 * @param[in] func to pass to libcurl for chunked.
1880 * transfers (NULL if not using chunked mode).
1881 * @return 0 on success -1 on error.
1883 static int rest_request_config_body(UNUSED rlm_rest_t *instance, rlm_rest_section_t *section,
1884 REQUEST *request, rlm_rest_handle_t *handle, rest_read_t func)
1886 rlm_rest_curl_context_t *ctx = handle->ctx;
1887 CURL *candle = handle->handle;
1889 CURLcode ret = CURLE_OK;
1890 char const *option = "unknown";
1895 * We were provided with no read function, assume this means
1896 * no body should be sent.
1899 SET_OPTION(CURLOPT_POSTFIELDSIZE, 0);
1904 * Chunked transfer encoding means the body will be sent in
1907 if (section->chunk > 0) {
1908 SET_OPTION(CURLOPT_READDATA, &ctx->request);
1909 SET_OPTION(CURLOPT_READFUNCTION, func);
1915 * If were not doing chunked encoding then we read the entire
1916 * body into a buffer, and send it in one go.
1918 len = rest_request_encode_wrapper(&ctx->body, func, REST_BODY_MAX_LEN, &ctx->request);
1920 REDEBUG("Failed creating HTTP body content");
1924 SET_OPTION(CURLOPT_POSTFIELDS, ctx->body);
1925 SET_OPTION(CURLOPT_POSTFIELDSIZE, len);
1930 REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret);
1935 /** Configures request curlopts.
1937 * Configures libcurl handle setting various curlopts for things like local
1938 * client time, Content-Type, and other FreeRADIUS custom headers.
1940 * Current FreeRADIUS custom headers are:
1941 * - X-FreeRADIUS-Section The module section being processed.
1942 * - X-FreeRADIUS-Server The current virtual server the REQUEST is
1945 * Sets up callbacks for all response processing (buffers and body data).
1947 * @param[in] instance configuration data.
1948 * @param[in] section configuration data.
1949 * @param[in] handle to configure.
1950 * @param[in] request Current request.
1951 * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1952 * @param[in] type Content-Type for request encoding, also sets the default for decoding.
1953 * @param[in] username to use for HTTP authentication, may be NULL in which case configured defaults will be used.
1954 * @param[in] password to use for HTTP authentication, may be NULL in which case configured defaults will be used.
1955 * @param[in] uri buffer containing the expanded URI to send the request to.
1956 * @return 0 on success (all opts configured) -1 on error.
1958 int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
1959 REQUEST *request, void *handle, http_method_t method,
1960 http_body_type_t type,
1961 char const *uri, char const *username, char const *password)
1963 rlm_rest_handle_t *randle = handle;
1964 rlm_rest_curl_context_t *ctx = randle->ctx;
1965 CURL *candle = randle->handle;
1967 http_auth_type_t auth = section->auth;
1969 CURLcode ret = CURLE_OK;
1970 char const *option = "unknown";
1971 char const *content_type;
1974 vp_cursor_t headers;
1979 rad_assert((!username && !password) || (username && password));
1981 buffer[(sizeof(buffer) - 1)] = '\0';
1984 * Setup any header options and generic headers.
1986 SET_OPTION(CURLOPT_URL, uri);
1987 SET_OPTION(CURLOPT_NOSIGNAL, 1);
1988 SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING);
1990 content_type = fr_int2str(http_content_type_table, type, section->body_str);
1991 snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type);
1992 ctx->headers = curl_slist_append(ctx->headers, buffer);
1993 if (!ctx->headers) goto error_header;
1995 SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, instance->connect_timeout);
1996 SET_OPTION(CURLOPT_TIMEOUT_MS, section->timeout);
1998 #ifdef CURLOPT_PROTOCOLS
1999 SET_OPTION(CURLOPT_PROTOCOLS, (CURLPROTO_HTTP | CURLPROTO_HTTPS));
2003 * FreeRADIUS custom headers
2005 RDEBUG3("Adding custom headers:");
2007 snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Section: %s", section->name);
2008 RDEBUG3("%s", buffer);
2009 ctx->headers = curl_slist_append(ctx->headers, buffer);
2010 if (!ctx->headers) goto error_header;
2012 snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Server: %s", request->server);
2013 RDEBUG3("%s", buffer);
2014 ctx->headers = curl_slist_append(ctx->headers, buffer);
2015 if (!ctx->headers) goto error_header;
2017 fr_cursor_init(&headers, &request->config);
2018 while (fr_cursor_next_by_num(&headers, PW_REST_HTTP_HEADER, 0, TAG_ANY)) {
2019 header = fr_cursor_remove(&headers);
2020 if (!strchr(header->vp_strvalue, ':')) {
2021 RWDEBUG("Invalid HTTP header \"%s\" must be in format '<attribute>: <value>'. Skipping...",
2022 header->vp_strvalue);
2023 talloc_free(header);
2026 RDEBUG3("%s", header->vp_strvalue);
2027 ctx->headers = curl_slist_append(ctx->headers, header->vp_strvalue);
2028 talloc_free(header);
2033 * Configure HTTP verb (GET, POST, PUT, PATCH, DELETE, other...)
2036 case HTTP_METHOD_GET:
2037 SET_OPTION(CURLOPT_HTTPGET, 1L);
2040 case HTTP_METHOD_POST:
2041 SET_OPTION(CURLOPT_POST, 1L);
2044 case HTTP_METHOD_PUT:
2046 * Do not set CURLOPT_PUT, this will cause libcurl
2047 * to ignore CURLOPT_POSTFIELDs and attempt to read
2048 * whatever was set with CURLOPT_READDATA, which by
2051 * This is many cases will cause the server to block,
2054 SET_OPTION(CURLOPT_CUSTOMREQUEST, "PUT");
2057 case HTTP_METHOD_PATCH:
2058 SET_OPTION(CURLOPT_CUSTOMREQUEST, "PATCH");
2061 case HTTP_METHOD_DELETE:
2062 SET_OPTION(CURLOPT_CUSTOMREQUEST, "DELETE");
2065 case HTTP_METHOD_CUSTOM:
2066 SET_OPTION(CURLOPT_CUSTOMREQUEST, section->method_str);
2075 * Set user based authentication parameters
2078 if ((auth >= HTTP_AUTH_BASIC) &&
2079 (auth <= HTTP_AUTH_ANY_SAFE)) {
2080 SET_OPTION(CURLOPT_HTTPAUTH, http_curl_auth[auth]);
2083 SET_OPTION(CURLOPT_USERNAME, username);
2084 } else if (section->username) {
2085 if (radius_xlat(buffer, sizeof(buffer), request, section->username, NULL, NULL) < 0) {
2086 option = STRINGIFY(CURLOPT_USERNAME);
2089 SET_OPTION(CURLOPT_USERNAME, buffer);
2093 SET_OPTION(CURLOPT_PASSWORD, password);
2094 } else if (section->password) {
2095 if (radius_xlat(buffer, sizeof(buffer), request, section->password, NULL, NULL) < 0) {
2096 option = STRINGIFY(CURLOPT_PASSWORD);
2099 SET_OPTION(CURLOPT_PASSWORD, buffer);
2101 #ifdef CURLOPT_TLSAUTH_USERNAME
2102 } else if (auth == HTTP_AUTH_TLS_SRP) {
2103 SET_OPTION(CURLOPT_TLSAUTH_TYPE, http_curl_auth[auth]);
2106 SET_OPTION(CURLOPT_TLSAUTH_USERNAME, username);
2107 } else if (section->username) {
2108 if (radius_xlat(buffer, sizeof(buffer), request, section->username, NULL, NULL) < 0) {
2109 option = STRINGIFY(CURLOPT_TLSAUTH_USERNAME);
2112 SET_OPTION(CURLOPT_TLSAUTH_USERNAME, buffer);
2116 SET_OPTION(CURLOPT_TLSAUTH_PASSWORD, password);
2117 } else if (section->password) {
2118 if (radius_xlat(buffer, sizeof(buffer), request, section->password, NULL, NULL) < 0) {
2119 option = STRINGIFY(CURLOPT_TLSAUTH_PASSWORD);
2122 SET_OPTION(CURLOPT_TLSAUTH_PASSWORD, buffer);
2129 * Set SSL/TLS authentication parameters
2131 if (section->tls_certificate_file) {
2132 SET_OPTION(CURLOPT_SSLCERT, section->tls_certificate_file);
2135 if (section->tls_private_key_file) {
2136 SET_OPTION(CURLOPT_SSLKEY, section->tls_private_key_file);
2139 if (section->tls_private_key_password) {
2140 SET_OPTION(CURLOPT_KEYPASSWD, section->tls_private_key_password);
2143 if (section->tls_ca_file) {
2144 SET_OPTION(CURLOPT_ISSUERCERT, section->tls_ca_file);
2147 if (section->tls_ca_path) {
2148 SET_OPTION(CURLOPT_CAPATH, section->tls_ca_path);
2151 if (section->tls_random_file) {
2152 SET_OPTION(CURLOPT_RANDOM_FILE, section->tls_random_file);
2155 SET_OPTION(CURLOPT_SSL_VERIFYPEER, (section->tls_check_cert == true) ? 1 : 0);
2156 SET_OPTION(CURLOPT_SSL_VERIFYHOST, (section->tls_check_cert_cn == true) ? 2 : 0);
2159 * Tell CURL how to get HTTP body content, and how to process incoming data.
2161 rest_response_init(request, &ctx->response, type);
2163 SET_OPTION(CURLOPT_HEADERFUNCTION, rest_response_header);
2164 SET_OPTION(CURLOPT_HEADERDATA, &ctx->response);
2165 SET_OPTION(CURLOPT_WRITEFUNCTION, rest_response_body);
2166 SET_OPTION(CURLOPT_WRITEDATA, &ctx->response);
2169 * Force parsing the body text as a particular encoding.
2171 ctx->response.force_to = section->force_to;
2174 case HTTP_METHOD_GET:
2175 case HTTP_METHOD_DELETE:
2176 RDEBUG3("Using a HTTP method which does not require a body. Forcing request body type to \"none\"");
2179 case HTTP_METHOD_POST:
2180 case HTTP_METHOD_PUT:
2181 case HTTP_METHOD_PATCH:
2182 case HTTP_METHOD_CUSTOM:
2183 if (section->chunk > 0) {
2184 ctx->request.chunk = section->chunk;
2186 ctx->headers = curl_slist_append(ctx->headers, "Expect:");
2187 if (!ctx->headers) goto error_header;
2189 ctx->headers = curl_slist_append(ctx->headers, "Transfer-Encoding: chunked");
2190 if (!ctx->headers) goto error_header;
2193 RDEBUG3("Request body content-type will be \"%s\"",
2194 fr_int2str(http_content_type_table, type, section->body_str));
2202 * Setup encoder specific options
2205 case HTTP_BODY_NONE:
2206 if (rest_request_config_body(instance, section, request, handle,
2213 case HTTP_BODY_CUSTOM_XLAT:
2215 rest_custom_data_t *data;
2216 char *expanded = NULL;
2218 if (radius_axlat(&expanded, request, section->data, NULL, NULL) < 0) {
2222 data = talloc_zero(request, rest_custom_data_t);
2225 /* Use the encoder specific pointer to store the data we need to encode */
2226 ctx->request.encoder = data;
2227 if (rest_request_config_body(instance, section, request, handle,
2228 rest_encode_custom) < 0) {
2229 TALLOC_FREE(ctx->request.encoder);
2236 case HTTP_BODY_CUSTOM_LITERAL:
2238 rest_custom_data_t *data;
2240 data = talloc_zero(request, rest_custom_data_t);
2241 data->p = section->data;
2243 /* Use the encoder specific pointer to store the data we need to encode */
2244 ctx->request.encoder = data;
2245 if (rest_request_config_body(instance, section, request, handle,
2246 rest_encode_custom) < 0) {
2247 TALLOC_FREE(ctx->request.encoder);
2254 case HTTP_BODY_JSON:
2255 rest_request_init(request, &ctx->request, true);
2257 if (rest_request_config_body(instance, section, request, handle,
2258 rest_encode_json) < 0) {
2265 case HTTP_BODY_POST:
2266 rest_request_init(request, &ctx->request, false);
2268 if (rest_request_config_body(instance, section, request, handle,
2269 rest_encode_post) < 0) {
2281 SET_OPTION(CURLOPT_HTTPHEADER, ctx->headers);
2286 REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret);
2290 REDEBUG("Failed creating header");
2295 /** Sends a REST (HTTP) request.
2297 * Send the actual REST request to the server. The response will be handled by
2298 * the numerous callbacks configured in rest_request_config.
2300 * @param[in] instance configuration data.
2301 * @param[in] section configuration data.
2302 * @param[in] request Current request.
2303 * @param[in] handle to use.
2304 * @return 0 on success or -1 on error.
2306 int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
2307 REQUEST *request, void *handle)
2309 rlm_rest_handle_t *randle = handle;
2310 CURL *candle = randle->handle;
2313 ret = curl_easy_perform(candle);
2314 if (ret != CURLE_OK) {
2315 REDEBUG("Request failed: %i - %s", ret, curl_easy_strerror(ret));
2323 /** Sends the response to the correct decode function.
2325 * Uses the Content-Type information written in rest_response_header to
2326 * determine the correct decode function to use. The decode function will
2327 * then convert the raw received data into VALUE_PAIRs.
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 0 on success or -1 on error.
2335 int rest_response_decode(rlm_rest_t *instance, rlm_rest_section_t *section, REQUEST *request, void *handle)
2337 rlm_rest_handle_t *randle = handle;
2338 rlm_rest_curl_context_t *ctx = randle->ctx;
2340 int ret = -1; /* -Wsometimes-uninitialized */
2342 if (!ctx->response.buffer) {
2343 RDEBUG2("Skipping attribute processing, no valid body data received");
2347 switch (ctx->response.type) {
2348 case HTTP_BODY_NONE:
2351 case HTTP_BODY_PLAIN:
2352 ret = rest_decode_plain(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2355 case HTTP_BODY_POST:
2356 ret = rest_decode_post(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2360 case HTTP_BODY_JSON:
2361 ret = rest_decode_json(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2365 case HTTP_BODY_UNSUPPORTED:
2366 case HTTP_BODY_UNAVAILABLE:
2367 case HTTP_BODY_INVALID:
2377 /** Cleans up after a REST request.
2379 * Resets all options associated with a CURL handle, and frees any headers
2380 * associated with it.
2382 * Calls rest_read_ctx_free and rest_response_free to free any memory used by
2385 * @param[in] instance configuration data.
2386 * @param[in] section configuration data.
2387 * @param[in] handle to cleanup.
2389 void rest_request_cleanup(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section, void *handle)
2391 rlm_rest_handle_t *randle = handle;
2392 rlm_rest_curl_context_t *ctx = randle->ctx;
2393 CURL *candle = randle->handle;
2396 * Clear any previously configured options
2398 curl_easy_reset(candle);
2403 if (ctx->headers != NULL) {
2404 curl_slist_free_all(ctx->headers);
2405 ctx->headers = NULL;
2409 * Free body data (only used if chunking is disabled)
2411 if (ctx->body != NULL) {
2417 * Free response data
2419 if (ctx->response.buffer) {
2420 free(ctx->response.buffer);
2421 ctx->response.buffer = NULL;
2424 TALLOC_FREE(ctx->request.encoder);
2425 TALLOC_FREE(ctx->response.decoder);
2428 /** URL encodes a string.
2430 * Encode special chars as per RFC 3986 section 4.
2432 * @param[in] request Current request.
2433 * @param[out] out Where to write escaped string.
2434 * @param[in] outlen Size of out buffer.
2435 * @param[in] raw string to be urlencoded.
2436 * @param[in] arg pointer, gives context for escaping.
2437 * @return length of data written to out (excluding NULL).
2439 size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
2443 escaped = curl_escape(raw, strlen(raw));
2444 strlcpy(out, escaped, outlen);
2450 /** Builds URI; performs XLAT expansions and encoding.
2452 * Splits the URI into "http://example.org" and "/%{xlat}/query/?bar=foo"
2453 * Both components are expanded, but values expanded for the second component
2454 * are also url encoded.
2456 * @param[out] out Where to write the pointer to the new buffer containing the escaped URI.
2457 * @param[in] instance configuration data.
2458 * @param[in] uri configuration data.
2459 * @param[in] request Current request
2460 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2463 ssize_t rest_uri_build(char **out, UNUSED rlm_rest_t *instance, REQUEST *request, char const *uri)
2466 char *path_exp = NULL;
2476 * All URLs must contain at least <scheme>://<server>/
2479 if (!p || (*++p != '/') || (*++p != '/')) {
2481 REDEBUG("Error URI is malformed, can't find start of path");
2484 p = strchr(p + 1, '/');
2492 * Allocate a temporary buffer to hold the first part of the URI
2494 scheme = talloc_array(request, char, len + 1);
2495 strlcpy(scheme, uri, len + 1);
2499 len = radius_axlat(out, request, scheme, NULL, NULL);
2500 talloc_free(scheme);
2507 len = radius_axlat(&path_exp, request, path, rest_uri_escape, NULL);
2514 MEM(*out = talloc_strdup_append(*out, path_exp));
2515 talloc_free(path_exp);
2517 return talloc_array_length(*out) - 1; /* array_length includes \0 */
2520 /** Unescapes the host portion of a URI string
2522 * This is required because the xlat functions which operate on the input string
2523 * cannot distinguish between host and path components.
2525 * @param[out] out Where to write the pointer to the new buffer containing the escaped URI.
2526 * @param[in] instance configuration data.
2527 * @param[in] request Current request
2528 * @param[in] handle to use.
2529 * @param[in] uri configuration data.
2530 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2533 ssize_t rest_uri_host_unescape(char **out, UNUSED rlm_rest_t *instance, REQUEST *request,
2534 void *handle, char const *uri)
2536 rlm_rest_handle_t *randle = handle;
2537 CURL *candle = randle->handle;
2548 * All URLs must contain at least <scheme>://<server>/
2551 if (!p || (*++p != '/') || (*++p != '/')) {
2553 REDEBUG("Error URI is malformed, can't find start of path");
2556 p = strchr(p + 1, '/');
2564 * Unescape any special sequences in the first part of the URI
2566 scheme = curl_easy_unescape(candle, uri, len, NULL);
2568 REDEBUG("Error unescaping host");
2573 * URIs can't contain spaces, so anything after the space must
2574 * be something else.
2577 *out = q ? talloc_typed_asprintf(request, "%s%.*s", scheme, (int)(q - p), p) :
2578 talloc_typed_asprintf(request, "%s%s", scheme, p);
2583 return talloc_array_length(*out) - 1; /* array_length includes \0 */