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();
290 /** Creates a new connection handle for use by the FR connection API.
292 * Matches the fr_connection_create_t function prototype, is passed to
293 * fr_connection_pool_init, and called when a new connection is required by the
294 * connection pool API.
296 * Creates an instances of rlm_rest_handle_t, and rlm_rest_curl_context_t
297 * which hold the context data required for generating requests and parsing
298 * responses. Calling mod_conn_delete will free this memory.
300 * If instance->connect_uri is not NULL libcurl will attempt to open a
301 * TCP socket to the server specified in the URI. This is done so that when the
302 * socket is first used, there will already be a cached TCP connection to the
303 * REST server associated with the curl handle.
305 * @see mod_conn_delete
306 * @see fr_connection_pool_init
307 * @see fr_connection_create_t
310 * @param[in] instance configuration data.
311 * @return connection handle or NULL if the connection failed or couldn't
314 void *mod_conn_create(void *instance)
316 rlm_rest_t *inst = instance;
318 rlm_rest_handle_t *randle = NULL;
319 rlm_rest_curl_context_t *ctx = NULL;
321 CURL *candle = curl_easy_init();
323 CURLcode ret = CURLE_OK;
324 char const *option = "unknown";
327 ERROR("rlm_rest (%s): Failed to create CURL handle", inst->xlat_name);
331 if (inst->connect_uri) {
333 * re-establish TCP connection to webserver. This would usually be
334 * done on the first request, but we do it here to minimise
337 SET_OPTION(CURLOPT_CONNECT_ONLY, 1);
338 SET_OPTION(CURLOPT_URL, inst->connect_uri);
340 DEBUG("rlm_rest (%s): Connecting to \"%s\"", inst->xlat_name, inst->connect_uri);
342 ret = curl_easy_perform(candle);
343 if (ret != CURLE_OK) {
344 ERROR("rlm_rest (%s): Connection failed: %i - %s", inst->xlat_name, ret, curl_easy_strerror(ret));
346 goto connection_error;
349 DEBUG2("rlm_rest (%s): Skipping pre-connect, connect_uri not specified", inst->xlat_name);
353 * Allocate memory for the connection handle abstraction.
355 randle = talloc_zero(inst, rlm_rest_handle_t);
356 ctx = talloc_zero(randle, rlm_rest_curl_context_t);
358 ctx->headers = NULL; /* CURL needs this to be NULL */
359 ctx->request.instance = inst;
362 randle->handle = candle;
365 * Clear any previously configured options for the first request.
367 curl_easy_reset(candle);
372 * Cleanup for error conditions.
375 ERROR("rlm_rest (%s): Failed setting curl option %s: %s (%i)", inst->xlat_name, option,
376 curl_easy_strerror(ret), ret);
379 * So we don't leak CURL handles.
382 curl_easy_cleanup(candle);
383 if (randle) talloc_free(randle);
388 /** Verifies that the last TCP socket associated with a handle is still active.
390 * Quieries libcurl to try and determine if the TCP socket associated with a
391 * connection handle is still viable.
393 * @param[in] instance configuration data.
394 * @param[in] handle to check.
395 * @returns false if the last socket is dead, or if the socket state couldn't be
396 * determined, else true.
398 int mod_conn_alive(void *instance, void *handle)
400 rlm_rest_t *inst = instance;
401 rlm_rest_handle_t *randle = handle;
402 CURL *candle = randle->handle;
407 ret = curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
408 if (ret != CURLE_OK) {
409 ERROR("rlm_rest (%s): Couldn't determine socket state: %i - %s", inst->xlat_name, ret,
410 curl_easy_strerror(ret));
415 if (last_socket == -1) {
422 /** Frees a libcurl handle, and any additional memory used by context data.
424 * @param[in] instance configuration data.
425 * @param[in] handle rlm_rest_handle_t to close and free.
426 * @return returns true.
428 int mod_conn_delete(UNUSED void *instance, void *handle)
430 rlm_rest_handle_t *randle = handle;
431 CURL *candle = randle->handle;
433 curl_easy_cleanup(candle);
440 /** Copies a pre-expanded xlat string to the output buffer
442 * @param[out] out Char buffer to write encoded data to.
443 * @param[in] size Multiply by nmemb to get the length of ptr.
444 * @param[in] nmemb Multiply by size to get the length of ptr.
445 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
446 * @return length of data (including NULL) written to ptr, or 0 if no more
449 static size_t rest_encode_custom(void *out, size_t size, size_t nmemb, void *userdata)
451 rlm_rest_request_t *ctx = userdata;
452 rest_custom_data_t *data = ctx->encoder;
454 size_t freespace = (size * nmemb) - 1;
457 len = strlcpy(out, data->p, freespace);
458 if (is_truncated(len, freespace)) {
459 data->p += (freespace - 1);
460 return freespace - 1;
467 /** Encodes VALUE_PAIR linked list in POST format
469 * This is a stream function matching the rest_read_t prototype. Multiple
470 * successive calls will return additional encoded VALUE_PAIRs.
471 * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
472 * will be written to the ptr buffer.
474 * POST request format is:
475 * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
477 * All attributes and values are url encoded. There is currently no support for
478 * nested attributes, or attribute qualifiers.
480 * Nested attributes may be added in the future using
481 * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
482 * to denotate nesting.
484 * Requires libcurl for url encoding.
486 * @see rest_decode_post
488 * @param[out] out Char buffer to write encoded data to.
489 * @param[in] size Multiply by nmemb to get the length of ptr.
490 * @param[in] nmemb Multiply by size to get the length of ptr.
491 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
492 * @return length of data (including NULL) written to ptr, or 0 if no more
495 static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userdata)
497 rlm_rest_request_t *ctx = userdata;
498 REQUEST *request = ctx->request; /* Used by RDEBUG */
501 char *p = out; /* Position in buffer */
502 char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
503 char *escaped; /* Pointer to current URL escaped data */
506 size_t freespace = (size * nmemb) - 1;
508 /* Allow manual chunking */
509 if ((ctx->chunk) && (ctx->chunk <= freespace)) {
510 freespace = (ctx->chunk - 1);
513 if (ctx->state == READ_STATE_END) return 0;
515 /* Post data requires no headers */
516 if (ctx->state == READ_STATE_INIT) ctx->state = READ_STATE_ATTR_BEGIN;
518 while (freespace > 0) {
519 vp = fr_cursor_current(&ctx->cursor);
521 ctx->state = READ_STATE_END;
526 RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
528 if (ctx->state == READ_STATE_ATTR_BEGIN) {
529 escaped = curl_escape(vp->da->name, strlen(vp->da->name));
531 REDEBUG("Failed escaping string \"%s\"", vp->da->name);
535 len = strlen(escaped);
536 if (freespace < (1 + len)) {
541 len = sprintf(p, "%s=", escaped);
547 * We wrote the attribute header, record progress.
550 ctx->state = READ_STATE_ATTR_CONT;
554 * Write out single attribute string.
556 len = vp_prints_value(p, freespace, vp, 0);
557 if (is_truncated(len, freespace)) goto no_space;
559 RDEBUG3("\tLength : %zd", len);
561 escaped = curl_escape(p, len);
563 REDEBUG("Failed escaping string \"%s\"", vp->da->name);
566 len = strlen(escaped);
568 if (freespace < len) {
573 len = strlcpy(p, escaped, len + 1);
577 RDEBUG3("\tValue : %s", p);
584 * there are more attributes, insert a separator
586 if (fr_cursor_next(&ctx->cursor)) {
587 if (freespace < 1) goto no_space;
593 * We wrote one full attribute value pair, record progress.
597 ctx->state = READ_STATE_ATTR_BEGIN;
602 len = p - (char *)out;
604 RDEBUG3("POST Data: %s", (char *)out);
605 RDEBUG3("Returning %zd bytes of POST data", len);
610 * Cleanup for error conditions
615 len = encoded - (char *)out;
617 RDEBUG3("POST Data: %s", (char *)out);
620 * The buffer wasn't big enough to encode a single attribute chunk.
623 REDEBUG("Failed encoding attribute");
625 RDEBUG3("Returning %zd bytes of POST data (buffer full or chunk exceeded)", len);
632 /** Encodes VALUE_PAIR linked list in JSON format
634 * This is a stream function matching the rest_read_t prototype. Multiple
635 * successive calls will return additional encoded VALUE_PAIRs.
637 * Only complete attribute headers
638 * @verbatim "<name>":{"type":"<type>","value":[' @endverbatim
639 * and complete attribute values will be written to ptr.
641 * If an attribute occurs multiple times in the request the attribute values
642 * will be concatenated into a single value array.
644 * JSON request format is:
649 "value":[<value0>,<value1>,<valueN>]
662 * @param[out] out Char buffer to write encoded data to.
663 * @param[in] size Multiply by nmemb to get the length of ptr.
664 * @param[in] nmemb Multiply by size to get the length of ptr.
665 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls.
666 * @return length of data (including NULL) written to ptr, or 0 if no more
669 static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userdata)
671 rlm_rest_request_t *ctx = userdata;
672 REQUEST *request = ctx->request; /* Used by RDEBUG */
673 VALUE_PAIR *vp, *next;
675 char *p = out; /* Position in buffer */
676 char *encoded = p; /* Position in buffer of last fully encoded attribute or value */
681 size_t freespace = (size * nmemb) - 1;
683 rad_assert(freespace > 0);
685 /* Allow manual chunking */
686 if ((ctx->chunk) && (ctx->chunk <= freespace)) {
687 freespace = (ctx->chunk - 1);
690 if (ctx->state == READ_STATE_END) return 0;
692 if (ctx->state == READ_STATE_INIT) {
693 ctx->state = READ_STATE_ATTR_BEGIN;
695 if (freespace < 1) goto no_space;
701 vp = fr_cursor_current(&ctx->cursor);
704 * We've encoded all the VPs
707 ctx->state = READ_STATE_END;
709 if (freespace < 1) goto no_space;
717 * New attribute, write name, type, and beginning of value array.
719 RDEBUG2("Encoding attribute \"%s\"", vp->da->name);
720 if (ctx->state == READ_STATE_ATTR_BEGIN) {
721 type = fr_int2str(dict_attr_types, vp->da->type, "<INVALID>");
723 len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type);
724 if (len >= freespace) goto no_space;
728 RDEBUG3("\tType : %s", type);
731 * We wrote the attribute header, record progress
734 ctx->state = READ_STATE_ATTR_CONT;
738 len = vp_prints_value_json(p, freespace, vp);
739 if (is_truncated(len, freespace)) goto no_space;
742 * Show actual value length minus quotes
744 RDEBUG3("\tLength : %zu", (size_t) (*p == '"') ? (len - 2) : len);
745 RDEBUG3("\tValue : %s", p);
751 * Multivalued attribute, we sorted all the attributes earlier, so multiple
752 * instances should occur in a contiguous block.
754 if ((next = fr_cursor_next(&ctx->cursor)) && (vp->da == next->da)) {
755 if (freespace < 1) goto no_space;
760 * We wrote one attribute value, record progress.
769 if (freespace < 2) goto no_space;
775 if (freespace < 1) goto no_space;
781 * We wrote one full attribute value pair, record progress.
784 ctx->state = READ_STATE_ATTR_BEGIN;
789 len = p - (char *)out;
791 RDEBUG3("JSON Data: %s", (char *)out);
792 RDEBUG3("Returning %zd bytes of JSON data", len);
797 * Were out of buffer space
802 len = encoded - (char *)out;
804 RDEBUG3("JSON Data: %s", (char *)out);
807 * The buffer wasn't big enough to encode a single attribute chunk.
810 REDEBUG("AVP exceeds buffer length or chunk");
812 RDEBUG2("Returning %zd bytes of JSON data (buffer full or chunk exceeded)", len);
819 /** Emulates successive libcurl calls to an encoding function
821 * This function is used when the request will be sent to the HTTP server as one
822 * contiguous entity. A buffer of REST_BODY_INIT bytes is allocated and passed
823 * to the stream encoding function.
825 * If the stream function does not return 0, a new buffer is allocated which is
826 * the size of the previous buffer + REST_BODY_INIT bytes, the data from the
827 * previous buffer is copied, and freed, and another call is made to the stream
828 * function, passing a pointer into the new buffer at the end of the previously
831 * This process continues until the stream function signals (by returning 0)
832 * that it has no more data to write.
834 * @param[out] buffer where the pointer to the alloced buffer should
836 * @param[in] func Stream function.
837 * @param[in] limit Maximum buffer size to alloc.
838 * @param[in] userdata rlm_rest_request_t to keep encoding state between calls to
840 * @return the length of the data written to the buffer (excluding NULL) or -1
843 static ssize_t rest_request_encode_wrapper(char **buffer, rest_read_t func, size_t limit, void *userdata)
845 char *previous = NULL;
846 char *current = NULL;
848 size_t alloc = REST_BODY_INIT; /* Size of buffer to alloc */
849 size_t used = 0; /* Size of data written */
852 while (alloc <= limit) {
853 current = rad_malloc(alloc);
856 strlcpy(current, previous, used + 1);
860 len = func(current + used, alloc - used, 1, userdata);
876 /** (Re-)Initialises the data in a rlm_rest_request_t.
878 * Resets the values of a rlm_rest_request_t to their defaults.
880 * @param[in] request Current request.
881 * @param[in] ctx to initialise.
882 * @param[in] sort If true VALUE_PAIRs will be sorted within the VALUE_PAIR
885 static void rest_request_init(REQUEST *request, rlm_rest_request_t *ctx, bool sort)
888 * Setup stream read data
890 ctx->request = request;
891 ctx->state = READ_STATE_INIT;
894 * Sorts pairs in place, oh well...
897 pairsort(&request->packet->vps, attrtagcmp);
899 fr_cursor_init(&ctx->cursor, &request->packet->vps);
902 /** Converts POST response into VALUE_PAIRs and adds them to the request
904 * Accepts VALUE_PAIRS in the same format as rest_encode_post, but with the
905 * addition of optional attribute list qualifiers as part of the attribute name
908 * If no qualifiers are specified, will default to the request list.
910 * POST response format is:
911 * @verbatim [outer.][<list>:]<attribute0>=<value0>&[outer.][<list>:]<attribute1>=<value1>&[outer.][<list>:]<attributeN>=<valueN> @endverbatim
913 * @see rest_encode_post
915 * @param[in] instance configuration data.
916 * @param[in] section configuration data.
917 * @param[in] handle rlm_rest_handle_t to use.
918 * @param[in] request Current request.
919 * @param[in] raw buffer containing POST data.
920 * @param[in] rawlen Length of data in raw buffer.
921 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
923 static int rest_decode_post(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
924 REQUEST *request, void *handle, char *raw, UNUSED size_t rawlen)
926 rlm_rest_handle_t *randle = handle;
927 CURL *candle = randle->handle;
929 char const *p = raw, *q;
931 char const *attribute;
935 char *expanded = NULL;
940 pair_lists_t list_name;
941 request_refs_t request_name;
942 REQUEST *reference = request;
947 int curl_len; /* Length from last curl_easy_unescape call */
955 while (isspace(*p)) p++;
956 if (*p == '\0') return 0;
958 while (((q = strchr(p, '=')) != NULL) && (count < REST_BODY_MAX_ATTRS)) {
961 name = curl_easy_unescape(candle, p, (q - p), &curl_len);
964 RDEBUG2("Parsing attribute \"%s\"", name);
967 * The attribute pointer is updated to point to the portion of
968 * the string after the list qualifier.
971 request_name = radius_request_name(&attribute, REQUEST_CURRENT);
972 if (request_name == REQUEST_UNKNOWN) {
973 RWDEBUG("Invalid request qualifier, skipping");
980 if (radius_request(&reference, request_name) < 0) {
981 RWDEBUG("Attribute name refers to outer request but not in a tunnel, skipping");
988 list_name = radius_list_name(&attribute, PAIR_LIST_REPLY);
989 if (list_name == PAIR_LIST_UNKNOWN) {
990 RWDEBUG("Invalid list qualifier, skipping");
997 da = dict_attrbyname(attribute);
999 RWDEBUG("Attribute \"%s\" unknown, skipping", attribute);
1006 vps = radius_list(reference, list_name);
1009 RDEBUG3("\tType : %s", fr_int2str(dict_attr_types, da->type, "<INVALID>"));
1011 ctx = radius_list_ctx(reference, list_name);
1014 len = (!q) ? (rawlen - (p - raw)) : (unsigned)(q - p);
1016 value = curl_easy_unescape(candle, p, len, &curl_len);
1019 * If we found a delimiter we want to skip over it,
1020 * if we didn't we do *NOT* want to skip over the end
1023 p += (!q) ? len : (len + 1);
1025 RDEBUG3("\tLength : %i", curl_len);
1026 RDEBUG3("\tValue : \"%s\"", value);
1028 RDEBUG2("Performing xlat expansion of response value");
1030 if (radius_axlat(&expanded, request, value, NULL, NULL) < 0) {
1034 vp = pairalloc(ctx, da);
1036 REDEBUG("Failed creating valuepair");
1037 talloc_free(expanded);
1042 ret = pairparsevalue(vp, expanded, 0);
1043 TALLOC_FREE(expanded);
1045 RWDEBUG("Incompatible value assignment, skipping");
1068 REDEBUG("Malformed POST data \"%s\"", raw);
1076 /** Converts JSON "value" key into VALUE_PAIR.
1078 * If leaf is not in fact a leaf node, but contains JSON data, the data will
1079 * written to the attribute in JSON string format.
1081 * @param[in] instance configuration data.
1082 * @param[in] section configuration data.
1083 * @param[in] request Current request.
1084 * @param[in] da Attribute to create.
1085 * @param[in] flags containing the operator other flags controlling value
1087 * @param[in] leaf object containing the VALUE_PAIR value.
1088 * @return The VALUE_PAIR just created, or NULL on error.
1090 static VALUE_PAIR *json_pairmake_leaf(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
1091 REQUEST *request, DICT_ATTR const *da,
1092 json_flags_t *flags, json_object *leaf)
1094 char const *value, *to_parse;
1095 char *expanded = NULL;
1101 * Should encode any nested JSON structures into JSON strings.
1103 * "I knew you liked JSON so I put JSON in your JSON!"
1105 value = json_object_get_string(leaf);
1107 RDEBUG3("\tType : %s", fr_int2str(dict_attr_types, da->type, "<INVALID>"));
1108 RDEBUG3("\tLength : %zu", strlen(value));
1109 RDEBUG3("\tValue : \"%s\"", value);
1111 if (flags->do_xlat) {
1112 if (radius_axlat(&expanded, request, value, NULL, NULL) < 0) {
1116 to_parse = expanded;
1121 vp = paircreate(request, da->attr, da->vendor);
1123 RWDEBUG("Failed creating valuepair, skipping...");
1124 talloc_free(expanded);
1131 ret = pairparsevalue(vp, to_parse, 0);
1132 talloc_free(expanded);
1134 RWDEBUG("Incompatible value assignment, skipping...");
1143 /** Processes JSON response and converts it into multiple VALUE_PAIRs
1145 * Processes JSON attribute declarations in the format below. Will recurse when
1146 * processing nested attributes. When processing nested attributes flags and
1147 * operators from previous attributes are not inherited.
1149 * JSON response format is:
1156 "value":[<value0>,<value1>,<valueN>]
1160 "<nested-attribute0>":{
1166 "<attribute2>":"<value0>",
1167 "<attributeN>":"[<value0>,<value1>,<valueN>]"
1171 * JSON valuepair flags (bools):
1172 * - do_xlat (optional) Controls xlat expansion of values. Defaults to true.
1173 * - is_json (optional) If true, any nested JSON data will be copied to the
1174 * VALUE_PAIR in string form. Defaults to true.
1175 * - op (optional) Controls how the attribute is inserted into
1176 * the target list. Defaults to ':=' (T_OP_SET).
1178 * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
1179 * second and subsequent values in multivalued attributes. This does not work
1180 * between multiple attribute declarations.
1184 * @param[in] instance configuration data.
1185 * @param[in] section configuration data.
1186 * @param[in] request Current request.
1187 * @param[in] object containing root node, or parent node.
1188 * @param[in] level Current nesting level.
1189 * @param[in] max counter, decremented after each VALUE_PAIR is created,
1190 * when 0 no more attributes will be processed.
1191 * @return number of attributes created or < 0 on error.
1193 static int json_pairmake(rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
1194 REQUEST *request, json_object *object, UNUSED int level, int max)
1196 struct lh_entry *entry;
1197 int max_attrs = max;
1199 if (!json_object_is_type(object, json_type_object)) {
1200 REDEBUG("Can't process VP container, expected JSON object"
1201 #ifdef HAVE_JSON_TYPE_TO_NAME
1202 "got \"%s\", skipping...",
1203 json_type_to_name(json_object_get_type(object)));
1211 * Process VP container
1213 for (entry = json_object_get_object(object)->head;
1215 entry = entry->next) {
1216 int i = 0, elements;
1217 struct json_object *value, *element, *tmp;
1219 char const *name = (char const *)entry->k;
1221 json_flags_t flags = {
1227 value_pair_tmpl_t dst;
1228 REQUEST *current = request;
1229 VALUE_PAIR **vps, *vp = NULL;
1231 memset(&dst, 0, sizeof(dst));
1233 /* Fix the compiler warnings regarding const... */
1234 memcpy(&value, &entry->v, sizeof(value));
1237 * Resolve attribute name to a dictionary entry and pairlist.
1239 RDEBUG2("Parsing attribute \"%s\"", name);
1241 if (radius_parse_attr(&dst, name, REQUEST_CURRENT, PAIR_LIST_REPLY) < 0) {
1242 RWDEBUG("Failed parsing attribute: %s, skipping...", fr_strerror());
1246 if (radius_request(¤t, dst.vpt_request) < 0) {
1247 RWDEBUG("Attribute name refers to outer request but not in a tunnel, skipping...");
1251 vps = radius_list(current, dst.vpt_list);
1253 RWDEBUG("List not valid in this context, skipping...");
1258 * Alternative JSON structure which allows operator,
1259 * and other flags to be specified.
1269 * - [] Multivalued array
1270 * - {} Nested Valuepair
1271 * - * Integer or string value
1273 if (json_object_is_type(value, json_type_object)) {
1275 * Process operator if present.
1277 tmp = json_object_object_get(value, "op");
1279 flags.op = fr_str2int(fr_tokens, json_object_get_string(tmp), 0);
1281 RWDEBUG("Invalid operator value \"%s\", skipping...",
1282 json_object_get_string(tmp));
1288 * Process optional do_xlat bool.
1290 tmp = json_object_object_get(value, "do_xlat");
1292 flags.do_xlat = json_object_get_boolean(tmp);
1296 * Process optional is_json bool.
1298 tmp = json_object_object_get(value, "is_json");
1300 flags.is_json = json_object_get_boolean(tmp);
1304 * Value key must be present if were using the expanded syntax.
1306 value = json_object_object_get(value, "value");
1308 RWDEBUG("Value key missing, skipping...");
1314 * Setup pairmake / recursion loop.
1316 if (!flags.is_json && json_object_is_type(value, json_type_array)) {
1317 elements = json_object_array_length(value);
1319 RWDEBUG("Zero length value array, skipping...");
1322 element = json_object_array_get_idx(value, 0);
1329 * A JSON 'value' key, may have multiple elements, iterate
1330 * over each of them, creating a new VALUE_PAIR.
1333 if (max_attrs-- <= 0) {
1334 RWDEBUG("At maximum attribute limit");
1339 * Automagically switch the op for multivalued attributes.
1341 if (((flags.op == T_OP_SET) || (flags.op == T_OP_EQ)) && (i >= 1)) {
1342 flags.op = T_OP_ADD;
1345 if (json_object_is_type(element, json_type_object) && !flags.is_json) {
1346 /* TODO: Insert nested VP into VP structure...*/
1347 RWDEBUG("Found nested VP, these are not yet supported, skipping...");
1352 vp = json_pairmake(instance, section,
1354 level + 1, max_attrs);*/
1356 vp = json_pairmake_leaf(instance, section, request, dst.vpt_da, &flags, element);
1360 radius_pairmove(current, vps, vp, false);
1362 * If we call json_object_array_get_idx on something that's not an array
1363 * the behaviour appears to be to occasionally segfault.
1365 } while ((++i < elements) && (element = json_object_array_get_idx(value, i)));
1368 return max - max_attrs;
1371 /** Converts JSON response into VALUE_PAIRs and adds them to the request.
1373 * Converts the raw JSON string into a json-c object tree and passes it to
1374 * json_pairmake. After the tree has been parsed json_object_put is called
1375 * which decrements the reference count of the root node by one, and frees
1378 * @see rest_encode_json
1379 * @see json_pairmake
1381 * @param[in] instance configuration data.
1382 * @param[in] section configuration data.
1383 * @param[in,out] request Current request.
1384 * @param[in] handle REST handle.
1385 * @param[in] raw buffer containing JSON data.
1386 * @param[in] rawlen Length of data in raw buffer.
1387 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
1389 static int rest_decode_json(rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
1390 REQUEST *request, UNUSED void *handle, char *raw, UNUSED size_t rawlen)
1392 char const *p = raw;
1394 struct json_object *json;
1401 while (isspace(*p)) p++;
1402 if (*p == '\0') return 0;
1404 json = json_tokener_parse(p);
1406 REDEBUG("Malformed JSON data \"%s\"", raw);
1410 ret = json_pairmake(instance, section, request, json, 0, REST_BODY_MAX_ATTRS);
1413 * Decrement reference count for root object, should free entire JSON tree.
1415 json_object_put(json);
1421 /** Processes incoming HTTP header data from libcurl.
1423 * Processes the status line, and Content-Type headers from the incoming HTTP
1426 * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1429 * @param[in] in Char buffer where inbound header data is written.
1430 * @param[in] size Multiply by nmemb to get the length of ptr.
1431 * @param[in] nmemb Multiply by size to get the length of ptr.
1432 * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1433 * @return Length of data processed, or 0 on error.
1435 static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *userdata)
1437 rlm_rest_response_t *ctx = userdata;
1438 REQUEST *request = ctx->request; /* Used by RDEBUG */
1440 char const *p = in, *q;
1442 size_t const t = (size * nmemb);
1446 http_body_type_t type;
1449 * Curl seems to throw these (\r\n) in before the next set of headers when
1450 * looks like it's just a body separator and safe to ignore after we
1451 * receive a 100 Continue.
1453 if (t == 2 && ((p[0] == '\r') && (p[1] == '\n'))) return t;
1455 switch (ctx->state) {
1456 case WRITE_STATE_INIT:
1457 RDEBUG2("Processing response header");
1460 * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1462 * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
1465 REDEBUG("Malformed HTTP header: Status line too short");
1469 * Check start of header matches...
1471 if (strncasecmp("HTTP/", p, 5) != 0) {
1472 REDEBUG("Malformed HTTP header: Missing HTTP version");
1479 * Skip the version field, next space should mark start of reason_code.
1481 q = memchr(p, ' ', s);
1483 RDEBUG("Malformed HTTP header: Missing reason code");
1491 * Process reason_code.
1493 * " 100" (4) + "\r\n" (2) = 6
1496 REDEBUG("Malformed HTTP header: Reason code too short");
1502 /* Char after reason code must be a space, or \r */
1503 if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
1505 ctx->code = atoi(p);
1508 * Process reason_phrase (if present).
1514 q = memchr(p, '\r', s);
1515 if (!q) goto malformed;
1519 RDEBUG2("\tStatus : %i (%.*s)", ctx->code, (int) len, p);
1521 RDEBUG2("\tStatus : %i", ctx->code);
1524 ctx->state = WRITE_STATE_PARSE_HEADERS;
1528 case WRITE_STATE_PARSE_HEADERS:
1530 (strncasecmp("Content-Type: ", p, 14) == 0)) {
1535 * Check to see if there's a parameter separator.
1537 q = memchr(p, ';', s);
1540 * If there's not, find the end of this header.
1542 if (!q) q = memchr(p, '\r', s);
1544 len = !q ? s : (size_t) (q - p);
1545 type = fr_substr2int(http_content_type_table, p, HTTP_BODY_UNKNOWN, len);
1548 RDEBUG2("\tType : %s (%.*s)", fr_int2str(http_body_type_table, type, "<INVALID>"),
1552 * Figure out if the type is supported by one of the decoders.
1554 if (ctx->force_to != HTTP_BODY_UNKNOWN) {
1555 if (ctx->force_to != ctx->type) {
1556 RDEBUG3("Forcing body type to \"%s\"",
1557 fr_int2str(http_body_type_table, ctx->force_to, "<INVALID>"));
1558 ctx->type = ctx->force_to;
1561 * Assume the force_to value has already been validated.
1563 } else switch (http_body_type_supported[ctx->type]) {
1564 case HTTP_BODY_UNKNOWN:
1565 RWDEBUG("Couldn't determine type, using the request's type \"%s\".",
1566 fr_int2str(http_body_type_table, ctx->type, "<INVALID>"));
1569 case HTTP_BODY_UNSUPPORTED:
1570 REDEBUG("Type \"%s\" is currently unsupported",
1571 fr_int2str(http_body_type_table, ctx->type, "<INVALID>"));
1572 ctx->type = HTTP_BODY_UNSUPPORTED;
1575 case HTTP_BODY_UNAVAILABLE:
1576 REDEBUG("Type \"%s\" is unavailable, please rebuild this module with the required "
1577 "library", fr_int2str(http_body_type_table, ctx->type, "<INVALID>"));
1578 ctx->type = HTTP_BODY_UNAVAILABLE;
1581 case HTTP_BODY_INVALID:
1582 REDEBUG("Type \"%s\" is not a valid web API data markup format",
1583 fr_int2str(http_body_type_table, ctx->type, "<INVALID>"));
1584 ctx->type = HTTP_BODY_INVALID;
1587 /* supported type */
1600 * If we got a 100 Continue, we need to send additional payload data.
1601 * reset the state to WRITE_STATE_INIT, so that when were called again
1602 * we overwrite previous header data with that from the proper header.
1604 if (ctx->code == 100) {
1605 RDEBUG2("Continuing...");
1606 ctx->state = WRITE_STATE_INIT;
1615 fr_print_string((char *) in, t, escaped, sizeof(escaped));
1617 REDEBUG("Received %zu bytes of response data: %s", t, escaped);
1624 /** Processes incoming HTTP body data from libcurl.
1626 * Writes incoming body data to an intermediary buffer for later parsing by
1627 * one of the decode functions.
1629 * @param[in] ptr Char buffer where inbound header data is written
1630 * @param[in] size Multiply by nmemb to get the length of ptr.
1631 * @param[in] nmemb Multiply by size to get the length of ptr.
1632 * @param[in] userdata rlm_rest_response_t to keep parsing state between calls.
1633 * @return length of data processed, or 0 on error.
1635 static size_t rest_response_body(void *ptr, size_t size, size_t nmemb, void *userdata)
1637 rlm_rest_response_t *ctx = userdata;
1638 REQUEST *request = ctx->request; /* Used by RDEBUG */
1640 char const *p = ptr, *q;
1643 size_t const t = (size * nmemb);
1645 if (t == 0) return 0;
1648 * Any post processing of headers should go here...
1650 if (ctx->state == WRITE_STATE_PARSE_HEADERS) {
1651 ctx->state = WRITE_STATE_PARSE_CONTENT;
1654 switch (ctx->type) {
1655 case HTTP_BODY_UNSUPPORTED:
1656 case HTTP_BODY_UNAVAILABLE:
1657 case HTTP_BODY_INVALID:
1658 while ((q = memchr(p, '\n', t - (p - (char *)ptr)))) {
1659 REDEBUG("%.*s", (int) (q - p), p);
1664 REDEBUG("%.*s", (int)(t - (p - (char *)ptr)), p);
1669 case HTTP_BODY_NONE:
1670 while ((q = memchr(p, '\n', t - (p - (char *)ptr)))) {
1671 RDEBUG3("%.*s", (int) (q - p), p);
1676 RDEBUG3("%.*s", (int)(t - (p - (char *)ptr)), p);
1682 if (t > (ctx->alloc - ctx->used)) {
1683 ctx->alloc += ((t + 1) > REST_BODY_INIT) ? t + 1 : REST_BODY_INIT;
1687 ctx->buffer = rad_malloc(ctx->alloc);
1689 /* If data has been written previously */
1691 strlcpy(ctx->buffer, tmp, (ctx->used + 1));
1695 strlcpy(ctx->buffer + ctx->used, p, t + 1);
1704 /** (Re-)Initialises the data in a rlm_rest_response_t.
1706 * This resets the values of the a rlm_rest_response_t to their defaults.
1707 * Must be called between encoding sessions.
1709 * @see rest_response_body
1710 * @see rest_response_header
1712 * @param[in] request Current request.
1713 * @param[in] ctx data to initialise.
1714 * @param[in] type Default http_body_type to use when decoding raw data, may be
1715 * overwritten by rest_response_header.
1717 static void rest_response_init(REQUEST *request, rlm_rest_response_t *ctx, http_body_type_t type)
1719 ctx->request = request;
1721 ctx->state = WRITE_STATE_INIT;
1727 /** Extracts pointer to buffer containing response data
1729 * @param[out] out Where to write the pointer to the buffer.
1730 * @param[in] handle used for the last request.
1731 * @return > 0 if data is available.
1733 size_t rest_get_handle_data(char const **out, rlm_rest_handle_t *handle)
1735 rlm_rest_curl_context_t *ctx = handle->ctx;
1737 rad_assert(ctx->response.buffer || (!ctx->response.buffer && !ctx->response.used));
1739 *out = ctx->response.buffer;
1740 return ctx->response.used;
1743 /** Configures body specific curlopts.
1745 * Configures libcurl handle to use either chunked mode, where the request
1746 * data will be sent using multiple HTTP requests, or contiguous mode where
1747 * the request data will be sent in a single HTTP request.
1749 * @param[in] instance configuration data.
1750 * @param[in] section configuration data.
1751 * @param[in] request Current request.
1752 * @param[in] handle rlm_rest_handle_t to configure.
1753 * @param[in] func to pass to libcurl for chunked.
1754 * transfers (NULL if not using chunked mode).
1755 * @return 0 on success -1 on error.
1757 static int rest_request_config_body(UNUSED rlm_rest_t *instance, rlm_rest_section_t *section,
1758 REQUEST *request, rlm_rest_handle_t *handle, rest_read_t func)
1760 rlm_rest_curl_context_t *ctx = handle->ctx;
1761 CURL *candle = handle->handle;
1763 CURLcode ret = CURLE_OK;
1764 char const *option = "unknown";
1769 * We were provided with no read function, assume this means
1770 * no body should be sent.
1773 SET_OPTION(CURLOPT_POSTFIELDSIZE, 0);
1778 * Chunked transfer encoding means the body will be sent in
1781 if (section->chunk > 0) {
1782 SET_OPTION(CURLOPT_READDATA, &ctx->request);
1783 SET_OPTION(CURLOPT_READFUNCTION, func);
1789 * If were not doing chunked encoding then we read the entire
1790 * body into a buffer, and send it in one go.
1792 len = rest_request_encode_wrapper(&ctx->body, func, REST_BODY_MAX_LEN, &ctx->request);
1794 REDEBUG("Failed creating HTTP body content");
1798 SET_OPTION(CURLOPT_POSTFIELDS, ctx->body);
1799 SET_OPTION(CURLOPT_POSTFIELDSIZE, len);
1804 REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret);
1809 /** Configures request curlopts.
1811 * Configures libcurl handle setting various curlopts for things like local
1812 * client time, Content-Type, and other FreeRADIUS custom headers.
1814 * Current FreeRADIUS custom headers are:
1815 * - X-FreeRADIUS-Section The module section being processed.
1816 * - X-FreeRADIUS-Server The current virtual server the REQUEST is
1819 * Sets up callbacks for all response processing (buffers and body data).
1821 * @param[in] instance configuration data.
1822 * @param[in] section configuration data.
1823 * @param[in] handle to configure.
1824 * @param[in] request Current request.
1825 * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1826 * @param[in] type Content-Type for request encoding, also sets the default for decoding.
1827 * @param[in] username to use for HTTP authentication, may be NULL in which case configured defaults will be used.
1828 * @param[in] password to use for HTTP authentication, may be NULL in which case configured defaults will be used.
1829 * @param[in] uri buffer containing the expanded URI to send the request to.
1830 * @return 0 on success (all opts configured) -1 on error.
1832 int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
1833 REQUEST *request, void *handle, http_method_t method,
1834 http_body_type_t type,
1835 char const *uri, char const *username, char const *password)
1837 rlm_rest_handle_t *randle = handle;
1838 rlm_rest_curl_context_t *ctx = randle->ctx;
1839 CURL *candle = randle->handle;
1841 http_auth_type_t auth = section->auth;
1843 CURLcode ret = CURLE_OK;
1844 char const *option = "unknown";
1845 char const *content_type;
1849 vp_cursor_t headers;
1854 rad_assert((!username && !password) || (username && password));
1856 buffer[(sizeof(buffer) - 1)] = '\0';
1859 * Setup any header options and generic headers.
1861 SET_OPTION(CURLOPT_URL, uri);
1862 SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING);
1864 content_type = fr_int2str(http_content_type_table, type, section->body_str);
1865 snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type);
1866 ctx->headers = curl_slist_append(ctx->headers, buffer);
1867 if (!ctx->headers) goto error_header;
1869 if (section->timeout) {
1870 SET_OPTION(CURLOPT_TIMEOUT, section->timeout);
1873 SET_OPTION(CURLOPT_PROTOCOLS, (CURLPROTO_HTTP | CURLPROTO_HTTPS));
1876 * FreeRADIUS custom headers
1878 RDEBUG3("Adding custom headers:");
1879 snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Section: %s", section->name);
1880 RDEBUG3("\t%s", buffer);
1881 ctx->headers = curl_slist_append(ctx->headers, buffer);
1882 if (!ctx->headers) goto error_header;
1884 snprintf(buffer, sizeof(buffer), "X-FreeRADIUS-Server: %s", request->server);
1885 RDEBUG3("\t%s", buffer);
1886 ctx->headers = curl_slist_append(ctx->headers, buffer);
1887 if (!ctx->headers) goto error_header;
1889 fr_cursor_init(&headers, &request->config_items);
1890 while (fr_cursor_next_by_num(&headers, PW_REST_HTTP_HEADER, 0, TAG_ANY)) {
1891 header = fr_cursor_remove(&headers);
1892 if (!strchr(header->vp_strvalue, ':')) {
1893 RWDEBUG("Invalid HTTP header \"%s\" must be in format '<attribute>: <value>'. Skipping...",
1894 header->vp_strvalue);
1895 talloc_free(header);
1898 RDEBUG3("\t%s", header->vp_strvalue);
1899 ctx->headers = curl_slist_append(ctx->headers, header->vp_strvalue);
1900 talloc_free(header);
1904 * Configure HTTP verb (GET, POST, PUT, DELETE, other...)
1907 case HTTP_METHOD_GET :
1908 SET_OPTION(CURLOPT_HTTPGET, val);
1911 case HTTP_METHOD_POST :
1912 SET_OPTION(CURLOPT_POST, val);
1915 case HTTP_METHOD_PUT :
1916 SET_OPTION(CURLOPT_PUT, val);
1919 case HTTP_METHOD_DELETE :
1920 SET_OPTION(CURLOPT_HTTPGET, val);
1921 SET_OPTION(CURLOPT_CUSTOMREQUEST, "DELETE");
1924 case HTTP_METHOD_CUSTOM :
1925 SET_OPTION(CURLOPT_HTTPGET, val);
1926 SET_OPTION(CURLOPT_CUSTOMREQUEST, section->method_str);
1935 * Set user based authentication parameters
1938 if ((auth >= HTTP_AUTH_BASIC) &&
1939 (auth <= HTTP_AUTH_ANY_SAFE)) {
1940 SET_OPTION(CURLOPT_HTTPAUTH, http_curl_auth[auth]);
1943 SET_OPTION(CURLOPT_USERNAME, username);
1944 } else if (section->username) {
1945 if (radius_xlat(buffer, sizeof(buffer), request, section->username, NULL, NULL) < 0) {
1946 option = STRINGIFY(CURLOPT_USERNAME);
1949 SET_OPTION(CURLOPT_USERNAME, buffer);
1953 SET_OPTION(CURLOPT_PASSWORD, password);
1954 } else if (section->password) {
1955 if (radius_xlat(buffer, sizeof(buffer), request, section->password, NULL, NULL) < 0) {
1956 option = STRINGIFY(CURLOPT_PASSWORD);
1959 SET_OPTION(CURLOPT_PASSWORD, buffer);
1961 #ifdef CURLOPT_TLSAUTH_USERNAME
1962 } else if (type == HTTP_AUTH_TLS_SRP) {
1963 SET_OPTION(CURLOPT_TLSAUTH_TYPE, http_curl_auth[auth]);
1966 SET_OPTION(CURLOPT_TLSAUTH_USERNAME, username);
1967 } else if (section->username) {
1968 if (radius_xlat(buffer, sizeof(buffer), request, section->username, NULL, NULL) < 0) {
1969 option = STRINGIFY(CURLOPT_TLSAUTH_USERNAME);
1972 SET_OPTION(CURLOPT_TLSAUTH_USERNAME, buffer);
1976 SET_OPTION(CURLOPT_TLSAUTH_PASSWORD, password);
1977 } else if (section->password) {
1978 if (radius_xlat(buffer, sizeof(buffer), request, section->password, NULL, NULL) < 0) {
1979 option = STRINGIFY(CURLOPT_TLSAUTH_PASSWORD);
1982 SET_OPTION(CURLOPT_TLSAUTH_PASSWORD, buffer);
1989 * Set SSL/TLS authentication parameters
1991 if (section->tls_certificate_file) {
1992 SET_OPTION(CURLOPT_SSLCERT, section->tls_certificate_file);
1995 if (section->tls_private_key_file) {
1996 SET_OPTION(CURLOPT_SSLKEY, section->tls_private_key_file);
1999 if (section->tls_private_key_password) {
2000 SET_OPTION(CURLOPT_KEYPASSWD, section->tls_private_key_password);
2003 if (section->tls_ca_file) {
2004 SET_OPTION(CURLOPT_ISSUERCERT, section->tls_ca_file);
2007 if (section->tls_ca_path) {
2008 SET_OPTION(CURLOPT_CAPATH, section->tls_ca_path);
2011 if (section->tls_random_file) {
2012 SET_OPTION(CURLOPT_RANDOM_FILE, section->tls_random_file);
2015 if (section->tls_check_cert) {
2016 SET_OPTION(CURLOPT_SSL_VERIFYHOST, (section->tls_check_cert_cn == true) ? 2 : 0);
2018 SET_OPTION(CURLOPT_SSL_VERIFYPEER, 0);
2022 * Tell CURL how to get HTTP body content, and how to process incoming data.
2024 rest_response_init(request, &ctx->response, type);
2026 SET_OPTION(CURLOPT_HEADERFUNCTION, rest_response_header);
2027 SET_OPTION(CURLOPT_HEADERDATA, &ctx->response);
2028 SET_OPTION(CURLOPT_WRITEFUNCTION, rest_response_body);
2029 SET_OPTION(CURLOPT_WRITEDATA, &ctx->response);
2032 * Force parsing the body text as a particular encoding.
2034 ctx->response.force_to = section->force_to;
2037 case HTTP_METHOD_GET :
2038 case HTTP_METHOD_DELETE :
2039 RDEBUG3("Using a HTTP method which does not require a body. Forcing request body type to \"none\"");
2042 case HTTP_METHOD_POST :
2043 case HTTP_METHOD_PUT :
2044 case HTTP_METHOD_CUSTOM :
2045 if (section->chunk > 0) {
2046 ctx->request.chunk = section->chunk;
2048 ctx->headers = curl_slist_append(ctx->headers, "Expect:");
2049 if (!ctx->headers) goto error_header;
2051 ctx->headers = curl_slist_append(ctx->headers, "Transfer-Encoding: chunked");
2052 if (!ctx->headers) goto error_header;
2055 RDEBUG3("Request body content-type will be \"%s\"",
2056 fr_int2str(http_content_type_table, type, section->body_str));
2064 * Setup encoder specific options
2067 case HTTP_BODY_NONE:
2068 if (rest_request_config_body(instance, section, request, handle,
2075 case HTTP_BODY_CUSTOM_XLAT:
2077 rest_custom_data_t *data;
2078 char *expanded = NULL;
2080 if (radius_axlat(&expanded, request, section->data, NULL, NULL) < 0) {
2084 data = talloc_zero(request, rest_custom_data_t);
2087 /* Use the encoder specific pointer to store the data we need to encode */
2088 ctx->request.encoder = data;
2089 if (rest_request_config_body(instance, section, request, handle,
2090 rest_encode_custom) < 0) {
2091 TALLOC_FREE(ctx->request.encoder);
2098 case HTTP_BODY_CUSTOM_LITERAL:
2100 rest_custom_data_t *data;
2102 data = talloc_zero(request, rest_custom_data_t);
2103 data->p = section->data;
2105 /* Use the encoder specific pointer to store the data we need to encode */
2106 ctx->request.encoder = data;
2107 if (rest_request_config_body(instance, section, request, handle,
2108 rest_encode_custom) < 0) {
2109 TALLOC_FREE(ctx->request.encoder);
2116 case HTTP_BODY_JSON:
2117 rest_request_init(request, &ctx->request, true);
2119 if (rest_request_config_body(instance, section, request, handle,
2120 rest_encode_json) < 0) {
2127 case HTTP_BODY_POST:
2128 rest_request_init(request, &ctx->request, false);
2130 if (rest_request_config_body(instance, section, request, handle,
2131 rest_encode_post) < 0) {
2143 SET_OPTION(CURLOPT_HTTPHEADER, ctx->headers);
2148 REDEBUG("Failed setting curl option %s: %s (%i)", option, curl_easy_strerror(ret), ret);
2152 REDEBUG("Failed creating header");
2156 /** Sends a REST (HTTP) request.
2158 * Send the actual REST request to the server. The response will be handled by
2159 * the numerous callbacks configured in rest_request_config.
2161 * @param[in] instance configuration data.
2162 * @param[in] section configuration data.
2163 * @param[in] request Current request.
2164 * @param[in] handle to use.
2165 * @return 0 on success or -1 on error.
2167 int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
2168 REQUEST *request, void *handle)
2170 rlm_rest_handle_t *randle = handle;
2171 CURL *candle = randle->handle;
2174 ret = curl_easy_perform(candle);
2175 if (ret != CURLE_OK) {
2176 REDEBUG("Request failed: %i - %s", ret, curl_easy_strerror(ret));
2184 /** Sends the response to the correct decode function.
2186 * Uses the Content-Type information written in rest_response_header to
2187 * determine the correct decode function to use. The decode function will
2188 * then convert the raw received data into VALUE_PAIRs.
2190 * @param[in] instance configuration data.
2191 * @param[in] section configuration data.
2192 * @param[in] request Current request.
2193 * @param[in] handle to use.
2194 * @return 0 on success or -1 on error.
2196 int rest_response_decode(rlm_rest_t *instance, UNUSED rlm_rest_section_t *section,
2197 REQUEST *request, void *handle)
2199 rlm_rest_handle_t *randle = handle;
2200 rlm_rest_curl_context_t *ctx = randle->ctx;
2202 int ret = -1; /* -Wsometimes-uninitialized */
2204 if (!ctx->response.buffer) {
2205 RDEBUG2("Skipping attribute processing, no valid body data received");
2209 RDEBUG3("Processing response body");
2211 switch (ctx->response.type) {
2212 case HTTP_BODY_NONE:
2215 case HTTP_BODY_POST:
2216 ret = rest_decode_post(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2220 case HTTP_BODY_JSON:
2221 ret = rest_decode_json(instance, section, request, handle, ctx->response.buffer, ctx->response.used);
2225 case HTTP_BODY_UNSUPPORTED:
2226 case HTTP_BODY_UNAVAILABLE:
2227 case HTTP_BODY_INVALID:
2237 /** Cleans up after a REST request.
2239 * Resets all options associated with a CURL handle, and frees any headers
2240 * associated with it.
2242 * Calls rest_read_ctx_free and rest_response_free to free any memory used by
2245 * @param[in] instance configuration data.
2246 * @param[in] section configuration data.
2247 * @param[in] handle to cleanup.
2249 void rest_request_cleanup(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t *section, void *handle)
2251 rlm_rest_handle_t *randle = handle;
2252 rlm_rest_curl_context_t *ctx = randle->ctx;
2253 CURL *candle = randle->handle;
2256 * Clear any previously configured options
2258 curl_easy_reset(candle);
2263 if (ctx->headers != NULL) {
2264 curl_slist_free_all(ctx->headers);
2265 ctx->headers = NULL;
2269 * Free body data (only used if chunking is disabled)
2271 if (ctx->body != NULL) {
2277 * Free response data
2279 if (ctx->response.buffer) {
2280 free(ctx->response.buffer);
2281 ctx->response.buffer = NULL;
2284 TALLOC_FREE(ctx->request.encoder);
2285 TALLOC_FREE(ctx->response.decoder);
2288 /** URL encodes a string.
2290 * Encode special chars as per RFC 3986 section 4.
2292 * @param[in] request Current request.
2293 * @param[out] out Where to write escaped string.
2294 * @param[in] outlen Size of out buffer.
2295 * @param[in] raw string to be urlencoded.
2296 * @param[in] arg pointer, gives context for escaping.
2297 * @return length of data written to out (excluding NULL).
2299 size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
2303 escaped = curl_escape(raw, strlen(raw));
2304 strlcpy(out, escaped, outlen);
2310 /** Builds URI; performs XLAT expansions and encoding.
2312 * Splits the URI into "http://example.org" and "/%{xlat}/query/?bar=foo"
2313 * Both components are expanded, but values expanded for the second component
2314 * are also url encoded.
2316 * @param[out] out Where to write the pointer to the new buffer containing the escaped URI.
2317 * @param[in] instance configuration data.
2318 * @param[in] uri configuration data.
2319 * @param[in] request Current request
2320 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2323 ssize_t rest_uri_build(char **out, UNUSED rlm_rest_t *instance, REQUEST *request, char const *uri)
2326 char *path_exp = NULL;
2336 * All URLs must contain at least <scheme>://<server>/
2339 if (!p || (*++p != '/') || (*++p != '/')) {
2341 REDEBUG("Error URI is malformed, can't find start of path");
2344 p = strchr(p + 1, '/');
2352 * Allocate a temporary buffer to hold the first part of the URI
2354 scheme = talloc_array(request, char, len + 1);
2355 strlcpy(scheme, uri, len + 1);
2359 len = radius_axlat(out, request, scheme, NULL, NULL);
2360 talloc_free(scheme);
2367 len = radius_axlat(&path_exp, request, path, rest_uri_escape, NULL);
2374 MEM(*out = talloc_strdup_append(*out, path_exp));
2375 talloc_free(path_exp);
2377 return talloc_array_length(*out) - 1; /* array_length includes \0 */
2380 /** Unescapes the host portion of a URI string
2382 * This is required because the xlat functions which operate on the input string
2383 * cannot distinguish between host and path components.
2385 * @param[out] out Where to write the pointer to the new buffer containing the escaped URI.
2386 * @param[in] instance configuration data.
2387 * @param[in] request Current request
2388 * @param[in] handle to use.
2389 * @param[in] uri configuration data.
2390 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2393 ssize_t rest_uri_host_unescape(char **out, UNUSED rlm_rest_t *instance, REQUEST *request,
2394 void *handle, char const *uri)
2396 rlm_rest_handle_t *randle = handle;
2397 CURL *candle = randle->handle;
2408 * All URLs must contain at least <scheme>://<server>/
2411 if (!p || (*++p != '/') || (*++p != '/')) {
2413 REDEBUG("Error URI is malformed, can't find start of path");
2416 p = strchr(p + 1, '/');
2424 * Unescape any special sequences in the first part of the URI
2426 scheme = curl_easy_unescape(candle, uri, len, NULL);
2428 REDEBUG("Error unescaping host");
2433 * URIs can't contain spaces, so anything after the space must
2434 * be something else.
2437 *out = q ? talloc_typed_asprintf(request, "%s%.*s", scheme, (int)(q - p), p) :
2438 talloc_typed_asprintf(request, "%s%s", scheme, p);
2443 return talloc_array_length(*out) - 1; /* array_length includes \0 */