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>
39 /** Table of encoder/decoder support.
41 * Indexes in this table match the http_body_type_t enum, and should be
42 * updated if additional enum values are added.
44 * @see http_body_type_t
46 const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES] = {
47 HTTP_BODY_UNKNOWN, // HTTP_BODY_UNKOWN
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
56 HTTP_BODY_JSON, // HTTP_BODY_JSON
58 HTTP_BODY_UNAVAILABLE,
60 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_XML
61 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_YAML
62 HTTP_BODY_INVALID, // HTTP_BODY_HTML
63 HTTP_BODY_INVALID // HTTP_BODY_PLAIN
67 * Lib CURL doesn't define symbols for unsupported auth methods
69 #ifndef CURLOPT_TLSAUTH_SRP
70 # define CURLOPT_TLSAUTH_SRP 0
72 #ifndef CURLAUTH_BASIC
73 # define CURLAUTH_BASIC 0
75 #ifndef CURLAUTH_DIGEST
76 # define CURLAUTH_DIGEST 0
78 #ifndef CURLAUTH_DIGEST_IE
79 # define CURLAUTH_DIGEST_IE 0
81 #ifndef CURLAUTH_GSSNEGOTIATE
82 # define CURLAUTH_GSSNEGOTIATE 0
85 # define CURLAUTH_NTLM 0
87 #ifndef CURLAUTH_NTLM_WB
88 # define CURLAUTH_NTLM_WB 0
91 #define SET_OPTION(_x, _y)\
93 if ((ret = curl_easy_setopt(candle, _x, _y)) != CURLE_OK) {\
94 option = STRINGIFY(_x);\
99 const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
100 0, // HTTP_AUTH_UNKNOWN
102 CURLOPT_TLSAUTH_SRP, // HTTP_AUTH_TLS_SRP
103 CURLAUTH_BASIC, // HTTP_AUTH_BASIC
104 CURLAUTH_DIGEST, // HTTP_AUTH_DIGEST
105 CURLAUTH_DIGEST_IE, // HTTP_AUTH_DIGEST_IE
106 CURLAUTH_GSSNEGOTIATE, // HTTP_AUTH_GSSNEGOTIATE
107 CURLAUTH_NTLM, // HTTP_AUTH_NTLM
108 CURLAUTH_NTLM_WB, // HTTP_AUTH_NTLM_WB
109 CURLAUTH_ANY, // HTTP_AUTH_ANY
110 CURLAUTH_ANYSAFE // HTTP_AUTH_ANY_SAFE
114 /** Conversion table for method config values.
116 * HTTP verb strings for http_method_t enum values. Used by libcurl in the
117 * status line of the outgoing HTTP header, by rest_response_header for decoding
118 * incoming HTTP responses, and by the configuration parser.
120 * @note must be kept in sync with http_method_t enum.
126 const FR_NAME_NUMBER http_method_table[] = {
127 { "UNKNOWN", HTTP_METHOD_UNKNOWN },
128 { "GET", HTTP_METHOD_GET },
129 { "POST", HTTP_METHOD_POST },
130 { "PUT", HTTP_METHOD_PUT },
131 { "DELETE", HTTP_METHOD_DELETE },
136 /** Conversion table for type config values.
138 * Textual names for http_body_type_t enum values, used by the
139 * configuration parser.
141 * @see http_body_Type_t
145 const FR_NAME_NUMBER http_body_type_table[] = {
146 { "unknown", HTTP_BODY_UNKNOWN },
147 { "unsupported", HTTP_BODY_UNSUPPORTED },
148 { "unavailable", HTTP_BODY_UNAVAILABLE },
149 { "invalid", HTTP_BODY_INVALID },
150 { "none", HTTP_BODY_NONE },
151 { "post", HTTP_BODY_POST },
152 { "json", HTTP_BODY_JSON },
153 { "xml", HTTP_BODY_XML },
154 { "yaml", HTTP_BODY_YAML },
155 { "html", HTTP_BODY_HTML },
156 { "plain", HTTP_BODY_PLAIN },
161 const FR_NAME_NUMBER http_auth_table[] = {
162 { "none", HTTP_AUTH_NONE },
163 { "srp", HTTP_AUTH_TLS_SRP },
164 { "basic", HTTP_AUTH_BASIC },
165 { "digest", HTTP_AUTH_DIGEST },
166 { "digest-ie", HTTP_AUTH_DIGEST_IE },
167 { "gss-negotiate", HTTP_AUTH_GSSNEGOTIATE },
168 { "ntlm", HTTP_AUTH_NTLM },
169 { "ntlm-winbind", HTTP_AUTH_NTLM_WB },
170 { "any", HTTP_AUTH_ANY },
171 { "safe", HTTP_AUTH_ANY_SAFE },
176 /** Conversion table for "Content-Type" header values.
178 * Used by rest_response_header for parsing incoming headers.
180 * Values we expect to see in the 'Content-Type:' header of the incoming
183 * Some data types (like YAML) do no have standard MIME types defined,
184 * so multiple types, are listed here.
186 * @see http_body_Type_t
190 const FR_NAME_NUMBER http_content_type_table[] = {
191 { "application/x-www-form-urlencoded", HTTP_BODY_POST },
192 { "application/json", HTTP_BODY_JSON },
193 { "text/html", HTTP_BODY_HTML },
194 { "text/plain", HTTP_BODY_PLAIN },
195 { "text/xml", HTTP_BODY_XML },
196 { "text/yaml", HTTP_BODY_YAML },
197 { "text/x-yaml", HTTP_BODY_YAML },
198 { "application/yaml", HTTP_BODY_YAML },
199 { "application/x-yaml", HTTP_BODY_YAML },
205 * Encoder specific structures.
206 * @todo split encoders/decoders into submodules.
208 typedef struct rest_custom_data {
209 char const *p; //!< how much text we've sent so far.
210 } rest_custom_data_t;
213 /** Flags to control the conversion of JSON values to VALUE_PAIRs.
215 * These fields are set when parsing the expanded format for value pairs in
216 * JSON, and control how json_pairmake_leaf and json_pairmake convert the JSON
217 * value, and move the new VALUE_PAIR into an attribute list.
220 * @see json_pairmake_leaf
222 typedef struct json_flags {
223 int do_xlat; //!< If true value will be expanded with xlat.
224 int is_json; //!< If true value will be inserted as raw JSON
225 // (multiple values not supported).
226 FR_TOKEN op; //!< The operator that determines how the new VP
227 // is processed. @see fr_tokens
231 /** Initialises libcurl.
233 * Allocates global variables and memory required for libcurl to fundtion.
234 * MUST only be called once per module instance.
236 * rest_cleanup must not be called if rest_init fails.
240 * @param[in] instance configuration data.
241 * @return 0 if init succeeded -1 if it failed.
243 int rest_init(rlm_rest_t *instance)
245 static bool version_done;
248 /* developer sanity */
249 rad_assert((sizeof(http_body_type_supported) / sizeof(*http_body_type_supported)) == HTTP_BODY_NUM_ENTRIES);
251 ret = curl_global_init(CURL_GLOBAL_ALL);
252 if (ret != CURLE_OK) {
253 ERROR("rlm_rest (%s): CURL init returned error: %i - %s",
255 ret, curl_easy_strerror(ret));
257 curl_global_cleanup();
262 curl_version_info_data *curlversion;
266 curlversion = curl_version_info(CURLVERSION_NOW);
267 if (strcmp(LIBCURL_VERSION, curlversion->version) != 0) {
268 WARN("rlm_rest: libcurl version changed since the server was built");
269 WARN("rlm_rest: linked: %s built: %s", curlversion->version, LIBCURL_VERSION);
272 INFO("rlm_rest: libcurl version: %s", curl_version());
278 /** Cleans up after libcurl.
280 * Wrapper around curl_global_cleanup, frees any memory allocated by rest_init.
281 * Must only be called once per call of rest_init.
285 void rest_cleanup(void)
287 curl_global_cleanup();
291 /** Frees a libcurl handle, and any additional memory used by context data.
293 * @param[in] randle rlm_rest_handle_t to close and free.
294 * @return returns true.
296 static int _mod_conn_free(rlm_rest_handle_t *randle)
298 curl_easy_cleanup(randle->handle);
303 /** Creates a new connection handle for use by the FR connection API.
305 * Matches the fr_connection_create_t function prototype, is passed to
306 * fr_connection_pool_init, and called when a new connection is required by the
307 * connection pool API.
309 * Creates an instances of rlm_rest_handle_t, and rlm_rest_curl_context_t
310 * which hold the context data required for generating requests and parsing
313 * If instance->connect_uri is not NULL libcurl will attempt to open a
314 * TCP socket to the server specified in the URI. This is done so that when the
315 * socket is first used, there will already be a cached TCP connection to the
316 * REST server associated with the curl handle.
318 * @see fr_connection_pool_init
319 * @see fr_connection_create_t
322 * @param[in] instance configuration data.
323 * @return connection handle or NULL if the connection failed or couldn't
326 void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
328 rlm_rest_t *inst = instance;
330 rlm_rest_handle_t *randle = NULL;
331 rlm_rest_curl_context_t *curl_ctx = NULL;
333 CURL *candle = curl_easy_init();
335 CURLcode ret = CURLE_OK;
336 char const *option = "unknown";
339 ERROR("rlm_rest (%s): Failed to create CURL handle", inst->xlat_name);
343 if (inst->connect_uri) {
345 * re-establish TCP connection to webserver. This would usually be
346 * done on the first request, but we do it here to minimise
349 SET_OPTION(CURLOPT_CONNECT_ONLY, 1);
350 SET_OPTION(CURLOPT_URL, inst->connect_uri);
352 DEBUG("rlm_rest (%s): Connecting to \"%s\"", inst->xlat_name, inst->connect_uri);
354 ret = curl_easy_perform(candle);
355 if (ret != CURLE_OK) {
356 ERROR("rlm_rest (%s): Connection failed: %i - %s", inst->xlat_name, ret, curl_easy_strerror(ret));
358 goto connection_error;
361 DEBUG2("rlm_rest (%s): Skipping pre-connect, connect_uri not specified", inst->xlat_name);
365 * Allocate memory for the connection handle abstraction.
367 randle = talloc_zero(ctx, rlm_rest_handle_t);
368 curl_ctx = talloc_zero(randle, rlm_rest_curl_context_t);
370 curl_ctx->headers = NULL; /* CURL needs this to be NULL */
371 curl_ctx->request.instance = inst;
373 randle->ctx = curl_ctx;
374 randle->handle = candle;
375 talloc_set_destructor(randle, _mod_conn_free);
378 * Clear any previously configured options for the first request.
380 curl_easy_reset(candle);
385 * Cleanup for error conditions.
388 ERROR("rlm_rest (%s): Failed setting curl option %s: %s (%i)", inst->xlat_name, option,
389 curl_easy_strerror(ret), ret);
392 * So we don't leak CURL handles.
395 curl_easy_cleanup(candle);
396 if (randle) talloc_free(randle);
401 /** Verifies that the last TCP socket associated with a handle is still active.
403 * Quieries libcurl to try and determine if the TCP socket associated with a
404 * connection handle is still viable.
406 * @param[in] instance configuration data.
407 * @param[in] handle to check.
408 * @returns false if the last socket is dead, or if the socket state couldn't be
409 * determined, else true.
411 int mod_conn_alive(void *instance, void *handle)
413 rlm_rest_t *inst = instance;
414 rlm_rest_handle_t *randle = handle;
415 CURL *candle = randle->handle;
420 ret = curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
421 if (ret != CURLE_OK) {
422 ERROR("rlm_rest (%s): Couldn't determine socket state: %i - %s", inst->xlat_name, ret,
423 curl_easy_strerror(ret));
428 if (last_socket == -1) {
435 /** Copies a pre-expanded xlat string to the output buffer
437 * @param[out] out Char buffer to write encoded data to.
438 * @param[in] size Multiply by nmemb to get the length of ptr.
439 * @param[in] nmemb Multiply by size to get the length of ptr.
440 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
441 * @return length of data (including NULL) written to ptr, or 0 if no more
444 static size_t rest_encode_custom(void *out, size_t size, size_t nmemb, void *userdata)
446 rlm_rest_request_t *ctx = userdata;
447 rest_custom_data_t *data = ctx->encoder;
449 size_t freespace = (size * nmemb) - 1;
452 len = strlcpy(out, data->p, freespace);
453 if (is_truncated(len, freespace)) {
454 data->p += (freespace - 1);
455 return freespace - 1;
462 /** Encodes VALUE_PAIR linked list in POST format
464 * This is a stream function matching the rest_read_t prototype. Multiple
465 * successive calls will return additional encoded VALUE_PAIRs.
466 * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
467 * will be written to the ptr buffer.
469 * POST request format is:
470 * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
472 * All attributes and values are url encoded. There is currently no support for
473 * nested attributes, or attribute qualifiers.
475 * Nested attributes may be added in the future using
476 * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
477 * to denotate nesting.
479 * Requires libcurl for url encoding.
481 * @see rest_decode_post
483 * @param[out] out Char buffer to write encoded data to.
484 * @param[in] size Multiply by nmemb to get the length of ptr.
485 * @param[in] nmemb Multiply by size to get the length of ptr.
486 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
487 * @return length of data (including NULL) written to ptr, or 0 if no more
490 static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userdata)
492 rlm_rest_request_t *ctx = userdata;
493 REQUEST *request = ctx->request; /* Used by RDEBUG */
496 char *p = out; /* Position in buffer */
497 char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
498 char *escaped; /* Pointer to current URL escaped data */
501 size_t freespace = (size * nmemb) - 1;
503 /* Allow manual chunking */
504 if ((ctx->chunk) && (ctx->chunk <= freespace)) {
505 freespace = (ctx->chunk - 1);
508 if (ctx->state == READ_STATE_END) return 0;
510 /* Post data requires no headers */
511 if (ctx->state == READ_STATE_INIT) ctx->state = READ_STATE_ATTR_BEGIN;
513 while (freespace > 0) {
514 vp = fr_cursor_current(&ctx->cursor);
516 ctx->state = READ_STATE_END;
521 RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
523 if (ctx->state == READ_STATE_ATTR_BEGIN) {
524 escaped = curl_escape(vp->da->name, strlen(vp->da->name));
526 REDEBUG("Failed escaping string \"%s\"", vp->da->name);
530 len = strlen(escaped);
531 if (freespace < (1 + len)) {
536 len = sprintf(p, "%s=", escaped);
542 * We wrote the attribute header, record progress.
545 ctx->state = READ_STATE_ATTR_CONT;
549 * Write out single attribute string.
551 len = vp_prints_value(p, freespace, vp, 0);
552 if (is_truncated(len, freespace)) goto no_space;
554 RDEBUG3("\tLength : %zd", len);
556 escaped = curl_escape(p, len);
558 REDEBUG("Failed escaping string \"%s\"", vp->da->name);
561 len = strlen(escaped);
563 if (freespace < len) {
568 len = strlcpy(p, escaped, len + 1);
572 RDEBUG3("\tValue : %s", p);
579 * there are more attributes, insert a separator
581 if (fr_cursor_next(&ctx->cursor)) {
582 if (freespace < 1) goto no_space;
588 * We wrote one full attribute value pair, record progress.
592 ctx->state = READ_STATE_ATTR_BEGIN;
597 len = p - (char *)out;
599 RDEBUG3("POST Data: %s", (char *)out);
600 RDEBUG3("Returning %zd bytes of POST data", len);
605 * Cleanup for error conditions
610 len = encoded - (char *)out;
612 RDEBUG3("POST Data: %s", (char *)out);
615 * The buffer wasn't big enough to encode a single attribute chunk.
618 REDEBUG("Failed encoding attribute");
620 RDEBUG3("Returning %zd bytes of POST data (buffer full or chunk exceeded)", len);
627 /** Encodes VALUE_PAIR linked list in JSON format
629 * This is a stream function matching the rest_read_t prototype. Multiple
630 * successive calls will return additional encoded VALUE_PAIRs.
632 * Only complete attribute headers
633 * @verbatim "<name>":{"type":"<type>","value":[' @endverbatim
634 * and complete attribute values will be written to ptr.
636 * If an attribute occurs multiple times in the request the attribute values
637 * will be concatenated into a single value array.
639 * JSON request format is:
644 "value":[<value0>,<value1>,<valueN>]
657 * @param[out] out Char buffer to write encoded data to.
658 * @param[in] size Multiply by nmemb to get the length of ptr.
659 * @param[in] nmemb Multiply by size to get the length of ptr.
660 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
661 * @return length of data (including NULL) written to ptr, or 0 if no more
664 static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userdata)
666 rlm_rest_request_t *ctx = userdata;
667 REQUEST *request = ctx->request; /* Used by RDEBUG */
668 VALUE_PAIR *vp, *next;
670 char *p = out; /* Position in buffer */
671 char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
676 size_t freespace = (size * nmemb) - 1;
678 rad_assert(freespace > 0);
680 /* Allow manual chunking */
681 if ((ctx->chunk) && (ctx->chunk <= freespace)) {
682 freespace = (ctx->chunk - 1);
685 if (ctx->state == READ_STATE_END) return 0;
687 if (ctx->state == READ_STATE_INIT) {
688 ctx->state = READ_STATE_ATTR_BEGIN;
690 if (freespace < 1) goto no_space;
696 vp = fr_cursor_current(&ctx->cursor);
699 * We've encoded all the VPs
702 ctx->state = READ_STATE_END;
704 if (freespace < 1) goto no_space;
712 * New attribute, write name, type, and beginning of value array.
714 RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
715 if (ctx->state == READ_STATE_ATTR_BEGIN) {
716 type = fr_int2str(dict_attr_types, vp->da->type, "<INVALID>");
718 len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type);
719 if (len >= freespace) goto no_space;
723 RDEBUG3("\tType : %s", type);
726 * We wrote the attribute header, record progress
729 ctx->state = READ_STATE_ATTR_CONT;
733 len = vp_prints_value_json(p, freespace, vp);
734 if (is_truncated(len, freespace)) goto no_space;
737 * Show actual value length minus quotes
739 RDEBUG3("\tLength : %zu", (size_t) (*p == '"') ? (len - 2) : len);
740 RDEBUG3("\tValue : %s", p);
746 * Multivalued attribute, we sorted all the attributes earlier, so multiple
747 * instances should occur in a contiguous block.
749 if ((next = fr_cursor_next(&ctx->cursor)) && (vp->da == next->da)) {
750 if (freespace < 1) goto no_space;
755 * We wrote one attribute value, record progress.
764 if (freespace < 2) goto no_space;
770 if (freespace < 1) goto no_space;
776 * We wrote one full attribute value pair, record progress.
779 ctx->state = READ_STATE_ATTR_BEGIN;
784 len = p - (char *)out;
786 RDEBUG3("JSON Data: %s", (char *)out);
787 RDEBUG3("Returning %zd bytes of JSON data", len);
792 * Were out of buffer space
797 len = encoded - (char *)out;
799 RDEBUG3("JSON Data: %s", (char *)out);
802 * The buffer wasn't big enough to encode a single attribute chunk.
805 REDEBUG("AVP exceeds buffer length or chunk");
807 RDEBUG2("Returning %zd bytes of JSON data (buffer full or chunk exceeded)", len);
814 /** Emulates successive libcurl calls to an encoding function
816 * This function is used when the request will be sent to the HTTP server as one
817 * contiguous entity. A buffer of REST_BODY_INIT bytes is allocated and passed
818 * to the stream encoding function.
820 * If the stream function does not return 0, a new buffer is allocated which is
821 * the size of the previous buffer + REST_BODY_INIT bytes, the data from the
822 * previous buffer is copied, and freed, and another call is made to the stream
823 * function, passing a pointer into the new buffer at the end of the previously
826 * This process continues until the stream function signals (by returning 0)
827 * that it has no more data to write.
829 * @param[out] buffer where the pointer to the alloced buffer should
831 * @param[in] func Stream function.
832 * @param[in] limit Maximum buffer size to alloc.
833 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls to
835 * @return the length of the data written to the buffer (excluding NULL) or -1
838 static ssize_t rest_request_encode_wrapper(char **buffer, rest_read_t func, size_t limit, void *userdata)
840 char *previous = NULL;
841 char *current = NULL;
843 size_t alloc = REST_BODY_INIT; /* Size of buffer to alloc */
844 size_t used = 0; /* Size of data written */
847 while (alloc <= limit) {
848 current = rad_malloc(alloc);
851 strlcpy(current, previous, used + 1);
855 len = func(current + used, alloc - used, 1, userdata);
871 /** (Re-)Initialises the data in a rlm_rest_request_t.
873 * Resets the values of a rlm_rest_request_t to their defaults.
875 * @param[in] request Current request.
876 * @param[in] ctx to initialise.
877 * @param[in] sort If true VALUE_PAIRs will be sorted within the VALUE_PAIR
880 static void rest_request_init(REQUEST *request, rlm_rest_request_t *ctx, bool sort)
883 * Setup stream read data
885 ctx->request = request;
886 ctx->state = READ_STATE_INIT;
889 * Sorts pairs in place, oh well...
892 pairsort(&request->packet->vps, attrtagcmp);
894 fr_cursor_init(&ctx->cursor, &request->packet->vps);
897 /** Converts POST response into VALUE_PAIRs and adds them to the request
899 * Accepts VALUE_PAIRS in the same format as rest_encode_post, but with the
900 * addition of optional attribute list qualifiers as part of the attribute name
903 * If no qualifiers are specified, will default to the request list.
905 * POST response format is:
906 * @verbatim [outer.][<list>:]<attribute0>=<value0>&[outer.][<list>:]<attribute1>=<value1>&[outer.][<list>:]<attributeN>=<valueN> @endverbatim
908 * @see rest_encode_post
910 * @param[in] instance configuration data.
911 * @param[in] section configuration data.
912 * @param[in] handle rlm_rest_handle_t to use.
913 * @param[in] request Current request.
914 * @param[in] raw buffer containing POST data.
915 * @param[in] rawlen Length of data in raw buffer.
916 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
918 static int rest_decode_post(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
919 REQUEST *request, void *handle, char *raw, UNUSED size_t rawlen)
921 rlm_rest_handle_t *randle = handle;
922 CURL *candle = randle->handle;
924 char const *p = raw, *q;
926 char const *attribute;
930 char *expanded = NULL;
935 pair_lists_t list_name;
936 request_refs_t request_name;
937 REQUEST *reference = request;
942 int curl_len; /* Length from last curl_easy_unescape call */
950 while (isspace(*p)) p++;
951 if (*p == '\0') return 0;
953 while (((q = strchr(p, '=')) != NULL) && (count < REST_BODY_MAX_ATTRS)) {
956 name = curl_easy_unescape(candle, p, (q - p), &curl_len);
959 RDEBUG2("Parsing attribute \"%s\"", name);
962 * The attribute pointer is updated to point to the portion of
963 * the string after the list qualifier.
966 request_name = radius_request_name(&attribute, REQUEST_CURRENT);
967 if (request_name == REQUEST_UNKNOWN) {
968 RWDEBUG("Invalid request qualifier, skipping");
975 if (radius_request(&reference, request_name) < 0) {
976 RWDEBUG("Attribute name refers to outer request but not in a tunnel, skipping");
983 list_name = radius_list_name(&attribute, PAIR_LIST_REPLY);
984 if (list_name == PAIR_LIST_UNKNOWN) {
985 RWDEBUG("Invalid list qualifier, skipping");
992 da = dict_attrbyname(attribute);
994 RWDEBUG("Attribute \"%s\" unknown, skipping", attribute);
1001 vps = radius_list(reference, list_name);
1004 RDEBUG3("\tType : %s", fr_int2str(dict_attr_types, da->type, "<INVALID>"));
1006 ctx = radius_list_ctx(reference, list_name);
1009 len = (!q) ? (rawlen - (p - raw)) : (unsigned)(q - p);
1011 value = curl_easy_unescape(candle, p, len, &curl_len);
1014 * If we found a delimiter we want to skip over it,
1015 * if we didn't we do *NOT* want to skip over the end
1018 p += (!q) ? len : (len + 1);
1020 RDEBUG3("\tLength : %i", curl_len);
1021 RDEBUG3("\tValue : \"%s\"", value);
1023 RDEBUG2("Performing xlat expansion of response value");
1025 if (radius_axlat(&expanded, request, value, NULL, NULL) < 0) {
1029 vp = pairalloc(ctx, da);
1031 REDEBUG("Failed creating valuepair");
1032 talloc_free(expanded);
1037 ret = pairparsevalue(vp, expanded, 0);
1038 TALLOC_FREE(expanded);
1040 RWDEBUG("Incompatible value assignment, skipping");
1063 REDEBUG("Malformed POST data \"%s\"", raw);
1071 /** Converts JSON "value" key into VALUE_PAIR.
1073 * If leaf is not in fact a leaf node, but contains JSON data, the data will
1074 * written to the attribute in JSON string format.
1076 * @param[in] instance configuration data.
1077 * @param[in] section configuration data.
1078 * @param[in] ctx to allocate new VALUE_PAIRs in.
1079 * @param[in] request Current request.
1080 * @param[in] da Attribute to create.
1081 * @param[in] flags containing the operator other flags controlling value
1083 * @param[in] leaf object containing the VALUE_PAIR value.
1084 * @return The VALUE_PAIR just created, or NULL on error.
1086 static VALUE_PAIR *json_pairmake_leaf(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
1087 TALLOC_CTX *ctx, REQUEST *request, DICT_ATTR const *da,
1088 json_flags_t *flags, json_object *leaf)
1090 char const *value, *to_parse;
1091 char *expanded = NULL;
1097 * Should encode any nested JSON structures into JSON strings.
1099 * "I knew you liked JSON so I put JSON in your JSON!"
1101 value = json_object_get_string(leaf);
1103 RDEBUG3("\tType : %s", fr_int2str(dict_attr_types, da->type, "<INVALID>"));
1104 RDEBUG3("\tLength : %zu", strlen(value));
1105 RDEBUG3("\tValue : \"%s\"", value);
1107 if (flags->do_xlat) {
1108 if (radius_axlat(&expanded, request, value, NULL, NULL) < 0) {
1112 to_parse = expanded;
1117 vp = pairalloc(ctx, da);
1119 RWDEBUG("Failed creating valuepair, skipping...");
1120 talloc_free(expanded);
1127 ret = pairparsevalue(vp, to_parse, 0);
1128 talloc_free(expanded);
1130 RWDEBUG("Incompatible value assignment, skipping...");
1139 /** Processes JSON response and converts it into multiple VALUE_PAIRs
1141 * Processes JSON attribute declarations in the format below. Will recurse when
1142 * processing nested attributes. When processing nested attributes flags and
1143 * operators from previous attributes are not inherited.
1145 * JSON response format is:
1152 "value":[<value0>,<value1>,<valueN>]
1156 "<nested-attribute0>":{
1162 "<attribute2>":"<value0>",
1163 "<attributeN>":"[<value0>,<value1>,<valueN>]"
1167 * JSON valuepair flags (bools):
1168 * - do_xlat (optional) Controls xlat expansion of values. Defaults to true.
1169 * - is_json (optional) If true, any nested JSON data will be copied to the
1170 * VALUE_PAIR in string form. Defaults to true.
1171 * - op (optional) Controls how the attribute is inserted into
1172 * the target list. Defaults to ':=' (T_OP_SET).
1174 * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
1175 * second and subsequent values in multivalued attributes. This does not work
1176 * between multiple attribute declarations.
1180 * @param[in] instance configuration data.
1181 * @param[in] section configuration data.
1182 * @param[in] request Current request.
1183 * @param[in] object containing root node, or parent node.
1184 * @param[in] level Current nesting level.
1185 * @param[in] max counter, decremented after each VALUE_PAIR is created,
1186 * when 0 no more attributes will be processed.
1187 * @return number of attributes created or < 0 on error.
1189 static int json_pairmake(rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
1190 REQUEST *request, json_object *object, UNUSED int level, int max)
1192 struct lh_entry *entry;
1193 int max_attrs = max;
1195 if (!json_object_is_type(object, json_type_object)) {
1196 REDEBUG("Can't process VP container, expected JSON object"
1197 #ifdef HAVE_JSON_TYPE_TO_NAME
1198 "got \"%s\", skipping...",
1199 json_type_to_name(json_object_get_type(object)));
1207 * Process VP container
1209 for (entry = json_object_get_object(object)->head;
1211 entry = entry->next) {
1212 int i = 0, elements;
1213 struct json_object *value, *element, *tmp;
1216 char const *name = (char const *)entry->k;
1218 json_flags_t flags = {
1224 value_pair_tmpl_t dst;
1225 REQUEST *current = request;
1226 VALUE_PAIR **vps, *vp = NULL;
1228 memset(&dst, 0, sizeof(dst));
1230 /* Fix the compiler warnings regarding const... */
1231 memcpy(&value, &entry->v, sizeof(value));
1234 * Resolve attribute name to a dictionary entry and pairlist.
1236 RDEBUG2("Parsing attribute \"%s\"", name);
1238 if (radius_parse_attr(&dst, name, REQUEST_CURRENT, PAIR_LIST_REPLY) < 0) {
1239 RWDEBUG("Failed parsing attribute: %s, skipping...", fr_strerror());
1243 if (radius_request(¤t, dst.vpt_request) < 0) {
1244 RWDEBUG("Attribute name refers to outer request but not in a tunnel, skipping...");
1248 vps = radius_list(current, dst.vpt_list);
1250 RWDEBUG("List not valid in this context, skipping...");
1253 ctx = radius_list_ctx(current, dst.vpt_list);
1256 * Alternative JSON structure which allows operator,
1257 * and other flags to be specified.
1267 * - [] Multivalued array
1268 * - {} Nested Valuepair
1269 * - * Integer or string value
1271 if (json_object_is_type(value, json_type_object)) {
1273 * Process operator if present.
1275 tmp = json_object_object_get(value, "op");
1277 flags.op = fr_str2int(fr_tokens, json_object_get_string(tmp), 0);
1279 RWDEBUG("Invalid operator value \"%s\", skipping...",
1280 json_object_get_string(tmp));
1286 * Process optional do_xlat bool.
1288 tmp = json_object_object_get(value, "do_xlat");
1290 flags.do_xlat = json_object_get_boolean(tmp);
1294 * Process optional is_json bool.
1296 tmp = json_object_object_get(value, "is_json");
1298 flags.is_json = json_object_get_boolean(tmp);
1302 * Value key must be present if were using the expanded syntax.
1304 value = json_object_object_get(value, "value");
1306 RWDEBUG("Value key missing, skipping...");
1312 * Setup pairmake / recursion loop.
1314 if (!flags.is_json && json_object_is_type(value, json_type_array)) {
1315 elements = json_object_array_length(value);
1317 RWDEBUG("Zero length value array, skipping...");
1320 element = json_object_array_get_idx(value, 0);
1327 * A JSON 'value' key, may have multiple elements, iterate
1328 * over each of them, creating a new VALUE_PAIR.
1331 if (max_attrs-- <= 0) {
1332 RWDEBUG("At maximum attribute limit");
1337 * Automagically switch the op for multivalued attributes.
1339 if (((flags.op == T_OP_SET) || (flags.op == T_OP_EQ)) && (i >= 1)) {
1340 flags.op = T_OP_ADD;
1343 if (json_object_is_type(element, json_type_object) && !flags.is_json) {
1344 /* TODO: Insert nested VP into VP structure...*/
1345 RWDEBUG("Found nested VP, these are not yet supported, skipping...");
1350 vp = json_pairmake(instance, section,
1352 level + 1, max_attrs);*/
1354 vp = json_pairmake_leaf(instance, section, ctx, request,
1355 dst.vpt_da, &flags, element);
1359 radius_pairmove(current, vps, vp, false);
1361 * If we call json_object_array_get_idx on something that's not an array
1362 * the behaviour appears to be to occasionally segfault.
1364 } while ((++i < elements) && (element = json_object_array_get_idx(value, i)));
1367 return max - max_attrs;
1370 /** Converts JSON response into VALUE_PAIRs and adds them to the request.
1372 * Converts the raw JSON string into a json-c object tree and passes it to
1373 * json_pairmake. After the tree has been parsed json_object_put is called
1374 * which decrements the reference count of the root node by one, and frees
1377 * @see rest_encode_json
1378 * @see json_pairmake
1380 * @param[in] instance configuration data.
1381 * @param[in] section configuration data.
1382 * @param[in,out] request Current request.
1383 * @param[in] handle REST handle.
1384 * @param[in] raw buffer containing JSON data.
1385 * @param[in] rawlen Length of data in raw buffer.
1386 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
1388 static int rest_decode_json(rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
1389 REQUEST *request, UNUSED void *handle, char *raw, UNUSED size_t rawlen)
1391 char const *p = raw;
1393 struct json_object *json;
1400 while (isspace(*p)) p++;
1401 if (*p == '\0') return 0;
1403 json = json_tokener_parse(p);
1405 REDEBUG("Malformed JSON data \"%s\"", raw);
1409 ret = json_pairmake(instance, section, request, json, 0, REST_BODY_MAX_ATTRS);
1412 * Decrement reference count for root object, should free entire JSON tree.
1414 json_object_put(json);
1420 /** Processes incoming HTTP header data from libcurl.
1422 * Processes the status line, and Content-Type headers from the incoming HTTP
1425 * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1428 * @param[in] in Char buffer where inbound header data is written.
1429 * @param[in] size Multiply by nmemb to get the length of ptr.
1430 * @param[in] nmemb Multiply by size to get the length of ptr.
1431 * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1432 * @return Length of data processed, or 0 on error.
1434 static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *userdata)
1436 rlm_rest_response_t *ctx = userdata;
1437 REQUEST *request = ctx->request; /* Used by RDEBUG */
1439 char const *p = in, *q;
1441 size_t const t = (size * nmemb);
1445 http_body_type_t type;
1448 * Curl seems to throw these (\r\n) in before the next set of headers when
1449 * looks like it's just a body separator and safe to ignore after we
1450 * receive a 100 Continue.
1452 if (t == 2 && ((p[0] == '\r') && (p[1] == '\n'))) return t;
1454 switch (ctx->state) {
1455 case WRITE_STATE_INIT:
1456 RDEBUG2("Processing response header");
1459 * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1461 * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
1464 REDEBUG("Malformed HTTP header: Status line too short");
1468 * Check start of header matches...
1470 if (strncasecmp("HTTP/", p, 5) != 0) {
1471 REDEBUG("Malformed HTTP header: Missing HTTP version");
1478 * Skip the version field, next space should mark start of reason_code.
1480 q = memchr(p, ' ', s);
1482 RDEBUG("Malformed HTTP header: Missing reason code");
1490 * Process reason_code.
1492 * " 100" (4) + "\r\n" (2) = 6
1495 REDEBUG("Malformed HTTP header: Reason code too short");
1501 /* Char after reason code must be a space, or \r */
1502 if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
1504 ctx->code = atoi(p);
1507 * Process reason_phrase (if present).
1513 q = memchr(p, '\r', s);
1514 if (!q) goto malformed;
1518 RDEBUG2("\tStatus : %i (%.*s)", ctx->code, (int) len, p);
1520 RDEBUG2("\tStatus : %i", ctx->code);
1523 ctx->state = WRITE_STATE_PARSE_HEADERS;
1527 case WRITE_STATE_PARSE_HEADERS:
1529 (strncasecmp("Content-Type: ", p, 14) == 0)) {
1534 * Check to see if there's a parameter separator.
1536 q = memchr(p, ';', s);
1539 * If there's not, find the end of this header.
1541 if (!q) q = memchr(p, '\r', s);
1543 len = !q ? s : (size_t) (q - p);
1544 type = fr_substr2int(http_content_type_table, p, HTTP_BODY_UNKNOWN, len);
1547 RDEBUG2("\tType : %s (%.*s)", fr_int2str(http_body_type_table, type, "<INVALID>"),
1551 * Figure out if the type is supported by one of the decoders.
1553 if (ctx->force_to != HTTP_BODY_UNKNOWN) {
1554 if (ctx->force_to != ctx->type) {
1555 RDEBUG3("Forcing body type to \"%s\"",
1556 fr_int2str(http_body_type_table, ctx->force_to, "<INVALID>"));
1557 ctx->type = ctx->force_to;
1560 * Assume the force_to value has already been validated.
1562 } else switch (http_body_type_supported[ctx->type]) {
1563 case HTTP_BODY_UNKNOWN:
1564 RWDEBUG("Couldn't determine type, using the request's type \"%s\".",
1565 fr_int2str(http_body_type_table, ctx->type, "<INVALID>"));
1568 case HTTP_BODY_UNSUPPORTED:
1569 REDEBUG("Type \"%s\" is currently unsupported",
1570 fr_int2str(http_body_type_table, ctx->type, "<INVALID>"));
1571 ctx->type = HTTP_BODY_UNSUPPORTED;
1574 case HTTP_BODY_UNAVAILABLE:
1575 REDEBUG("Type \"%s\" is unavailable, please rebuild this module with the required "
1576 "library", fr_int2str(http_body_type_table, ctx->type, "<INVALID>"));
1577 ctx->type = HTTP_BODY_UNAVAILABLE;
1580 case HTTP_BODY_INVALID:
1581 REDEBUG("Type \"%s\" is not a valid web API data markup format",
1582 fr_int2str(http_body_type_table, ctx->type, "<INVALID>"));
1583 ctx->type = HTTP_BODY_INVALID;
1586 /* supported type */
1599 * If we got a 100 Continue, we need to send additional payload data.
1600 * reset the state to WRITE_STATE_INIT, so that when were called again
1601 * we overwrite previous header data with that from the proper header.
1603 if (ctx->code == 100) {
1604 RDEBUG2("Continuing...");
1605 ctx->state = WRITE_STATE_INIT;
1614 fr_print_string((char *) in, t, escaped, sizeof(escaped));
1616 REDEBUG("Received %zu bytes of response data: %s", t, escaped);
1623 /** Processes incoming HTTP body data from libcurl.
1625 * Writes incoming body data to an intermediary buffer for later parsing by
1626 * one of the decode functions.
1628 * @param[in] ptr Char buffer where inbound header data is written
1629 * @param[in] size Multiply by nmemb to get the length of ptr.
1630 * @param[in] nmemb Multiply by size to get the length of ptr.
1631 * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1632 * @return length of data processed, or 0 on error.
1634 static size_t rest_response_body(void *ptr, size_t size, size_t nmemb, void *userdata)
1636 rlm_rest_response_t *ctx = userdata;
1637 REQUEST *request = ctx->request; /* Used by RDEBUG */
1639 char const *p = ptr, *q;
1642 size_t const t = (size * nmemb);
1644 if (t == 0) return 0;
1647 * Any post processing of headers should go here...
1649 if (ctx->state == WRITE_STATE_PARSE_HEADERS) {
1650 ctx->state = WRITE_STATE_PARSE_CONTENT;
1653 switch (ctx->type) {
1654 case HTTP_BODY_UNSUPPORTED:
1655 case HTTP_BODY_UNAVAILABLE:
1656 case HTTP_BODY_INVALID:
1657 while ((q = memchr(p, '\n', t - (p - (char *)ptr)))) {
1658 REDEBUG("%.*s", (int) (q - p), p);
1663 REDEBUG("%.*s", (int)(t - (p - (char *)ptr)), p);
1668 case HTTP_BODY_NONE:
1669 while ((q = memchr(p, '\n', t - (p - (char *)ptr)))) {
1670 RDEBUG3("%.*s", (int) (q - p), p);
1675 RDEBUG3("%.*s", (int)(t - (p - (char *)ptr)), p);
1681 if (t > (ctx->alloc - ctx->used)) {
1682 ctx->alloc += ((t + 1) > REST_BODY_INIT) ? t + 1 : REST_BODY_INIT;
1686 ctx->buffer = rad_malloc(ctx->alloc);
1688 /* If data has been written previously */
1690 strlcpy(ctx->buffer, tmp, (ctx->used + 1));
1694 strlcpy(ctx->buffer + ctx->used, p, t + 1);
1703 /** (Re-)Initialises the data in a rlm_rest_response_t.
1705 * This resets the values of the a rlm_rest_response_t to their defaults.
1706 * Must be called between encoding sessions.
1708 * @see rest_response_body
1709 * @see rest_response_header
1711 * @param[in] request Current request.
1712 * @param[in] ctx data to initialise.
1713 * @param[in] type Default http_body_type to use when decoding raw data, may be
1714 * overwritten by rest_response_header.
1716 static void rest_response_init(REQUEST *request, rlm_rest_response_t *ctx, http_body_type_t type)
1718 ctx->request = request;
1720 ctx->state = WRITE_STATE_INIT;
1726 /** Extracts pointer to buffer containing response data
1728 * @param[out] out Where to write the pointer to the buffer.
1729 * @param[in] handle used for the last request.
1730 * @return > 0 if data is available.
1732 size_t rest_get_handle_data(char const **out, rlm_rest_handle_t *handle)
1734 rlm_rest_curl_context_t *ctx = handle->ctx;
1736 rad_assert(ctx->response.buffer || (!ctx->response.buffer && !ctx->response.used));
1738 *out = ctx->response.buffer;
1739 return ctx->response.used;
1742 /** Configures body specific curlopts.
1744 * Configures libcurl handle to use either chunked mode, where the request
1745 * data will be sent using multiple HTTP requests, or contiguous mode where
1746 * the request data will be sent in a single HTTP request.
1748 * @param[in] instance configuration data.
1749 * @param[in] section configuration data.
1750 * @param[in] request Current request.
1751 * @param[in] handle rlm_rest_handle_t to configure.
1752 * @param[in] func to pass to libcurl for chunked.
1753 * transfers (NULL if not using chunked mode).
1754 * @return 0 on success -1 on error.
1756 static int rest_request_config_body(UNUSED rlm_rest_t *instance, rlm_rest_section_t *section,
1757 REQUEST *request, rlm_rest_handle_t *handle, rest_read_t func)
1759 rlm_rest_curl_context_t *ctx = handle->ctx;
1760 CURL *candle = handle->handle;
1762 CURLcode ret = CURLE_OK;
1763 char const *option = "unknown";
1768 * We were provided with no read function, assume this means
1769 * no body should be sent.
1772 SET_OPTION(CURLOPT_POSTFIELDSIZE, 0);
1777 * Chunked transfer encoding means the body will be sent in
1780 if (section->chunk > 0) {
1781 SET_OPTION(CURLOPT_READDATA, &ctx->request);
1782 SET_OPTION(CURLOPT_READFUNCTION, func);
1788 * If were not doing chunked encoding then we read the entire
1789 * body into a buffer, and send it in one go.
1791 len = rest_request_encode_wrapper(&ctx->body, func, REST_BODY_MAX_LEN, &ctx->request);
1793 REDEBUG("Failed creating HTTP body content");
1797 SET_OPTION(CURLOPT_POSTFIELDS, ctx->body);
1798 SET_OPTION(CURLOPT_POSTFIELDSIZE, len);
1803 REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret);
1808 /** Configures request curlopts.
1810 * Configures libcurl handle setting various curlopts for things like local
1811 * client time, Content-Type, and other FreeRADIUS custom headers.
1813 * Current FreeRADIUS custom headers are:
1814 * - X-FreeRADIUS-Section The module section being processed.
1815 * - X-FreeRADIUS-Server The current virtual server the REQUEST is
1818 * Sets up callbacks for all response processing (buffers and body data).
1820 * @param[in] instance configuration data.
1821 * @param[in] section configuration data.
1822 * @param[in] handle to configure.
1823 * @param[in] request Current request.
1824 * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1825 * @param[in] type Content-Type for request encoding, also sets the default for decoding.
1826 * @param[in] username to use for HTTP authentication, may be NULL in which case configured defaults will be used.
1827 * @param[in] password to use for HTTP authentication, may be NULL in which case configured defaults will be used.
1828 * @param[in] uri buffer containing the expanded URI to send the request to.
1829 * @return 0 on success (all opts configured) -1 on error.
1831 int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
1832 REQUEST *request, void *handle, http_method_t method,
1833 http_body_type_t type,
1834 char const *uri, char const *username, char const *password)
1836 rlm_rest_handle_t *randle = handle;
1837 rlm_rest_curl_context_t *ctx = randle->ctx;
1838 CURL *candle = randle->handle;
1840 http_auth_type_t auth = section->auth;
1842 CURLcode ret = CURLE_OK;
1843 char const *option = "unknown";
1844 char const *content_type;
1848 vp_cursor_t headers;
1853 rad_assert((!username && !password) || (username && password));
1855 buffer[(sizeof(buffer) - 1)] = '\0';
1858 * Setup any header options and generic headers.
1860 SET_OPTION(CURLOPT_URL, uri);
1861 SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING);
1863 content_type = fr_int2str(http_content_type_table, type, section->body_str);
1864 snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type);
1865 ctx->headers = curl_slist_append(ctx->headers, buffer);
1866 if (!ctx->headers) goto error_header;
1868 if (section->timeout) {
1869 SET_OPTION(CURLOPT_TIMEOUT, section->timeout);
1872 SET_OPTION(CURLOPT_PROTOCOLS, (CURLPROTO_HTTP | CURLPROTO_HTTPS));
1875 * FreeRADIUS custom headers
1877 RDEBUG3("Adding custom headers:");
1878 snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Section: %s", section->name);
1879 RDEBUG3("\t%s", buffer);
1880 ctx->headers = curl_slist_append(ctx->headers, buffer);
1881 if (!ctx->headers) goto error_header;
1883 snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Server: %s", request->server);
1884 RDEBUG3("\t%s", buffer);
1885 ctx->headers = curl_slist_append(ctx->headers, buffer);
1886 if (!ctx->headers) goto error_header;
1888 fr_cursor_init(&headers, &request->config_items);
1889 while (fr_cursor_next_by_num(&headers, PW_REST_HTTP_HEADER, 0, TAG_ANY)) {
1890 header = fr_cursor_remove(&headers);
1891 if (!strchr(header->vp_strvalue, ':')) {
1892 RWDEBUG("Invalid HTTP header \"%s\" must be in format '<attribute>: <value>'. Skipping...",
1893 header->vp_strvalue);
1894 talloc_free(header);
1897 RDEBUG3("\t%s", header->vp_strvalue);
1898 ctx->headers = curl_slist_append(ctx->headers, header->vp_strvalue);
1899 talloc_free(header);
1903 * Configure HTTP verb (GET, POST, PUT, DELETE, other...)
1906 case HTTP_METHOD_GET :
1907 SET_OPTION(CURLOPT_HTTPGET, val);
1910 case HTTP_METHOD_POST :
1911 SET_OPTION(CURLOPT_POST, val);
1914 case HTTP_METHOD_PUT :
1915 SET_OPTION(CURLOPT_PUT, val);
1918 case HTTP_METHOD_DELETE :
1919 SET_OPTION(CURLOPT_HTTPGET, val);
1920 SET_OPTION(CURLOPT_CUSTOMREQUEST, "DELETE");
1923 case HTTP_METHOD_CUSTOM :
1924 SET_OPTION(CURLOPT_HTTPGET, val);
1925 SET_OPTION(CURLOPT_CUSTOMREQUEST, section->method_str);
1934 * Set user based authentication parameters
1937 if ((auth >= HTTP_AUTH_BASIC) &&
1938 (auth <= HTTP_AUTH_ANY_SAFE)) {
1939 SET_OPTION(CURLOPT_HTTPAUTH, http_curl_auth[auth]);
1942 SET_OPTION(CURLOPT_USERNAME, username);
1943 } else if (section->username) {
1944 if (radius_xlat(buffer, sizeof(buffer), request, section->username, NULL, NULL) < 0) {
1945 option = STRINGIFY(CURLOPT_USERNAME);
1948 SET_OPTION(CURLOPT_USERNAME, buffer);
1952 SET_OPTION(CURLOPT_PASSWORD, password);
1953 } else if (section->password) {
1954 if (radius_xlat(buffer, sizeof(buffer), request, section->password, NULL, NULL) < 0) {
1955 option = STRINGIFY(CURLOPT_PASSWORD);
1958 SET_OPTION(CURLOPT_PASSWORD, buffer);
1960 #ifdef CURLOPT_TLSAUTH_USERNAME
1961 } else if (type == HTTP_AUTH_TLS_SRP) {
1962 SET_OPTION(CURLOPT_TLSAUTH_TYPE, http_curl_auth[auth]);
1965 SET_OPTION(CURLOPT_TLSAUTH_USERNAME, username);
1966 } else if (section->username) {
1967 if (radius_xlat(buffer, sizeof(buffer), request, section->username, NULL, NULL) < 0) {
1968 option = STRINGIFY(CURLOPT_TLSAUTH_USERNAME);
1971 SET_OPTION(CURLOPT_TLSAUTH_USERNAME, buffer);
1975 SET_OPTION(CURLOPT_TLSAUTH_PASSWORD, password);
1976 } else if (section->password) {
1977 if (radius_xlat(buffer, sizeof(buffer), request, section->password, NULL, NULL) < 0) {
1978 option = STRINGIFY(CURLOPT_TLSAUTH_PASSWORD);
1981 SET_OPTION(CURLOPT_TLSAUTH_PASSWORD, buffer);
1988 * Set SSL/TLS authentication parameters
1990 if (section->tls_certificate_file) {
1991 SET_OPTION(CURLOPT_SSLCERT, section->tls_certificate_file);
1994 if (section->tls_private_key_file) {
1995 SET_OPTION(CURLOPT_SSLKEY, section->tls_private_key_file);
1998 if (section->tls_private_key_password) {
1999 SET_OPTION(CURLOPT_KEYPASSWD, section->tls_private_key_password);
2002 if (section->tls_ca_file) {
2003 SET_OPTION(CURLOPT_ISSUERCERT, section->tls_ca_file);
2006 if (section->tls_ca_path) {
2007 SET_OPTION(CURLOPT_CAPATH, section->tls_ca_path);
2010 if (section->tls_random_file) {
2011 SET_OPTION(CURLOPT_RANDOM_FILE, section->tls_random_file);
2014 if (section->tls_check_cert) {
2015 SET_OPTION(CURLOPT_SSL_VERIFYHOST, (section->tls_check_cert_cn == true) ? 2 : 0);
2017 SET_OPTION(CURLOPT_SSL_VERIFYPEER, 0);
2021 * Tell CURL how to get HTTP body content, and how to process incoming data.
2023 rest_response_init(request, &ctx->response, type);
2025 SET_OPTION(CURLOPT_HEADERFUNCTION, rest_response_header);
2026 SET_OPTION(CURLOPT_HEADERDATA, &ctx->response);
2027 SET_OPTION(CURLOPT_WRITEFUNCTION, rest_response_body);
2028 SET_OPTION(CURLOPT_WRITEDATA, &ctx->response);
2031 * Force parsing the body text as a particular encoding.
2033 ctx->response.force_to = section->force_to;
2036 case HTTP_METHOD_GET :
2037 case HTTP_METHOD_DELETE :
2038 RDEBUG3("Using a HTTP method which does not require a body. Forcing request body type to \"none\"");
2041 case HTTP_METHOD_POST :
2042 case HTTP_METHOD_PUT :
2043 case HTTP_METHOD_CUSTOM :
2044 if (section->chunk > 0) {
2045 ctx->request.chunk = section->chunk;
2047 ctx->headers = curl_slist_append(ctx->headers, "Expect:");
2048 if (!ctx->headers) goto error_header;
2050 ctx->headers = curl_slist_append(ctx->headers, "Transfer-Encoding: chunked");
2051 if (!ctx->headers) goto error_header;
2054 RDEBUG3("Request body content-type will be \"%s\"",
2055 fr_int2str(http_content_type_table, type, section->body_str));
2063 * Setup encoder specific options
2066 case HTTP_BODY_NONE:
2067 if (rest_request_config_body(instance, section, request, handle,
2074 case HTTP_BODY_CUSTOM_XLAT:
2076 rest_custom_data_t *data;
2077 char *expanded = NULL;
2079 if (radius_axlat(&expanded, request, section->data, NULL, NULL) < 0) {
2083 data = talloc_zero(request, rest_custom_data_t);
2086 /* Use the encoder specific pointer to store the data we need to encode */
2087 ctx->request.encoder = data;
2088 if (rest_request_config_body(instance, section, request, handle,
2089 rest_encode_custom) < 0) {
2090 TALLOC_FREE(ctx->request.encoder);
2097 case HTTP_BODY_CUSTOM_LITERAL:
2099 rest_custom_data_t *data;
2101 data = talloc_zero(request, rest_custom_data_t);
2102 data->p = section->data;
2104 /* Use the encoder specific pointer to store the data we need to encode */
2105 ctx->request.encoder = data;
2106 if (rest_request_config_body(instance, section, request, handle,
2107 rest_encode_custom) < 0) {
2108 TALLOC_FREE(ctx->request.encoder);
2115 case HTTP_BODY_JSON:
2116 rest_request_init(request, &ctx->request, true);
2118 if (rest_request_config_body(instance, section, request, handle,
2119 rest_encode_json) < 0) {
2126 case HTTP_BODY_POST:
2127 rest_request_init(request, &ctx->request, false);
2129 if (rest_request_config_body(instance, section, request, handle,
2130 rest_encode_post) < 0) {
2142 SET_OPTION(CURLOPT_HTTPHEADER, ctx->headers);
2147 REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret);
2151 REDEBUG("Failed creating header");
2155 /** Sends a REST (HTTP) request.
2157 * Send the actual REST request to the server. The response will be handled by
2158 * the numerous callbacks configured in rest_request_config.
2160 * @param[in] instance configuration data.
2161 * @param[in] section configuration data.
2162 * @param[in] request Current request.
2163 * @param[in] handle to use.
2164 * @return 0 on success or -1 on error.
2166 int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
2167 REQUEST *request, void *handle)
2169 rlm_rest_handle_t *randle = handle;
2170 CURL *candle = randle->handle;
2173 ret = curl_easy_perform(candle);
2174 if (ret != CURLE_OK) {
2175 REDEBUG("Request failed: %i - %s", ret, curl_easy_strerror(ret));
2183 /** Sends the response to the correct decode function.
2185 * Uses the Content-Type information written in rest_response_header to
2186 * determine the correct decode function to use. The decode function will
2187 * then convert the raw received data into VALUE_PAIRs.
2189 * @param[in] instance configuration data.
2190 * @param[in] section configuration data.
2191 * @param[in] request Current request.
2192 * @param[in] handle to use.
2193 * @return 0 on success or -1 on error.
2195 int rest_response_decode(rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
2196 REQUEST *request, void *handle)
2198 rlm_rest_handle_t *randle = handle;
2199 rlm_rest_curl_context_t *ctx = randle->ctx;
2201 int ret = -1; /* -Wsometimes-uninitialized */
2203 if (!ctx->response.buffer) {
2204 RDEBUG2("Skipping attribute processing, no valid body data received");
2208 RDEBUG3("Processing response body");
2210 switch (ctx->response.type) {
2211 case HTTP_BODY_NONE:
2214 case HTTP_BODY_POST:
2215 ret = rest_decode_post(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2219 case HTTP_BODY_JSON:
2220 ret = rest_decode_json(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2224 case HTTP_BODY_UNSUPPORTED:
2225 case HTTP_BODY_UNAVAILABLE:
2226 case HTTP_BODY_INVALID:
2236 /** Cleans up after a REST request.
2238 * Resets all options associated with a CURL handle, and frees any headers
2239 * associated with it.
2241 * Calls rest_read_ctx_free and rest_response_free to free any memory used by
2244 * @param[in] instance configuration data.
2245 * @param[in] section configuration data.
2246 * @param[in] handle to cleanup.
2248 void rest_request_cleanup(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section, void *handle)
2250 rlm_rest_handle_t *randle = handle;
2251 rlm_rest_curl_context_t *ctx = randle->ctx;
2252 CURL *candle = randle->handle;
2255 * Clear any previously configured options
2257 curl_easy_reset(candle);
2262 if (ctx->headers != NULL) {
2263 curl_slist_free_all(ctx->headers);
2264 ctx->headers = NULL;
2268 * Free body data (only used if chunking is disabled)
2270 if (ctx->body != NULL) {
2276 * Free response data
2278 if (ctx->response.buffer) {
2279 free(ctx->response.buffer);
2280 ctx->response.buffer = NULL;
2283 TALLOC_FREE(ctx->request.encoder);
2284 TALLOC_FREE(ctx->response.decoder);
2287 /** URL encodes a string.
2289 * Encode special chars as per RFC 3986 section 4.
2291 * @param[in] request Current request.
2292 * @param[out] out Where to write escaped string.
2293 * @param[in] outlen Size of out buffer.
2294 * @param[in] raw string to be urlencoded.
2295 * @param[in] arg pointer, gives context for escaping.
2296 * @return length of data written to out (excluding NULL).
2298 size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
2302 escaped = curl_escape(raw, strlen(raw));
2303 strlcpy(out, escaped, outlen);
2309 /** Builds URI; performs XLAT expansions and encoding.
2311 * Splits the URI into "http://example.org" and "/%{xlat}/query/?bar=foo"
2312 * Both components are expanded, but values expanded for the second component
2313 * are also url encoded.
2315 * @param[out] out Where to write the pointer to the new buffer containing the escaped URI.
2316 * @param[in] instance configuration data.
2317 * @param[in] uri configuration data.
2318 * @param[in] request Current request
2319 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2322 ssize_t rest_uri_build(char **out, UNUSED rlm_rest_t *instance, REQUEST *request, char const *uri)
2325 char *path_exp = NULL;
2335 * All URLs must contain at least <scheme>://<server>/
2338 if (!p || (*++p != '/') || (*++p != '/')) {
2340 REDEBUG("Error URI is malformed, can't find start of path");
2343 p = strchr(p + 1, '/');
2351 * Allocate a temporary buffer to hold the first part of the URI
2353 scheme = talloc_array(request, char, len + 1);
2354 strlcpy(scheme, uri, len + 1);
2358 len = radius_axlat(out, request, scheme, NULL, NULL);
2359 talloc_free(scheme);
2366 len = radius_axlat(&path_exp, request, path, rest_uri_escape, NULL);
2373 MEM(*out = talloc_strdup_append(*out, path_exp));
2374 talloc_free(path_exp);
2376 return talloc_array_length(*out) - 1; /* array_length includes \0 */
2379 /** Unescapes the host portion of a URI string
2381 * This is required because the xlat functions which operate on the input string
2382 * cannot distinguish between host and path components.
2384 * @param[out] out Where to write the pointer to the new buffer containing the escaped URI.
2385 * @param[in] instance configuration data.
2386 * @param[in] request Current request
2387 * @param[in] handle to use.
2388 * @param[in] uri configuration data.
2389 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2392 ssize_t rest_uri_host_unescape(char **out, UNUSED rlm_rest_t *instance, REQUEST *request,
2393 void *handle, char const *uri)
2395 rlm_rest_handle_t *randle = handle;
2396 CURL *candle = randle->handle;
2407 * All URLs must contain at least <scheme>://<server>/
2410 if (!p || (*++p != '/') || (*++p != '/')) {
2412 REDEBUG("Error URI is malformed, can't find start of path");
2415 p = strchr(p + 1, '/');
2423 * Unescape any special sequences in the first part of the URI
2425 scheme = curl_easy_unescape(candle, uri, len, NULL);
2427 REDEBUG("Error unescaping host");
2432 * URIs can't contain spaces, so anything after the space must
2433 * be something else.
2436 *out = q ? talloc_typed_asprintf(request, "%s%.*s", scheme, (int)(q - p), p) :
2437 talloc_typed_asprintf(request, "%s%s", scheme, p);
2442 return talloc_array_length(*out) - 1; /* array_length includes \0 */