1 /** Functions and datatypes for the REST (HTTP) transport.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * Copyright 2012 Arran Cudbard-Bell <a.cudbard-bell@freeradius.org>
24 #include <freeradius-devel/ident.h>
32 #include <freeradius-devel/radiusd.h>
33 #include <freeradius-devel/libradius.h>
34 #include <freeradius-devel/connection.h>
38 /** Table of encoder/decoder support.
40 * Indexes in this table match the http_body_type_t enum, and should be
41 * updated if additional enum values are added.
43 * @see http_body_type_t
45 const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES] = {
46 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNKOWN
47 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNSUPPORTED
48 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_INVALID
49 HTTP_BODY_POST, // HTTP_BODY_POST
51 HTTP_BODY_JSON, // HTTP_BODY_JSON
53 HTTP_BODY_UNAVAILABLE,
55 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_XML
56 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_YAML
57 HTTP_BODY_INVALID, // HTTP_BODY_HTML
58 HTTP_BODY_INVALID // HTTP_BODY_PLAIN
62 * Lib CURL doesn't define symbols for unsupported auth methods
64 #ifndef CURLOPT_TLSAUTH_SRP
65 #define CURLOPT_TLSAUTH_SRP 0
67 #ifndef CURLAUTH_BASIC
68 #define CURLAUTH_BASIC 0
70 #ifndef CURLAUTH_DIGEST
71 #define CURLAUTH_DIGEST 0
73 #ifndef CURLAUTH_DIGEST_IE
74 #define CURLAUTH_DIGEST_IE 0
76 #ifndef CURLAUTH_GSSNEGOTIATE
77 #define CURLAUTH_GSSNEGOTIATE 0
80 #define CURLAUTH_NTLM 0
82 #ifndef CURLAUTH_NTLM_WB
83 #define CURLAUTH_NTLM_WB 0
86 const http_body_type_t http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
87 0, // HTTP_AUTH_UNKNOWN
89 CURLOPT_TLSAUTH_SRP, // HTTP_AUTH_TLS_SRP
90 CURLAUTH_BASIC, // HTTP_AUTH_BASIC
91 CURLAUTH_DIGEST, // HTTP_AUTH_DIGEST
92 CURLAUTH_DIGEST_IE, // HTTP_AUTH_DIGEST_IE
93 CURLAUTH_GSSNEGOTIATE, // HTTP_AUTH_GSSNEGOTIATE
94 CURLAUTH_NTLM, // HTTP_AUTH_NTLM
95 CURLAUTH_NTLM_WB, // HTTP_AUTH_NTLM_WB
96 CURLAUTH_ANY, // HTTP_AUTH_ANY
97 CURLAUTH_ANYSAFE // HTTP_AUTH_ANY_SAFE
101 /** Conversion table for method config values.
103 * HTTP verb strings for http_method_t enum values. Used by libcurl in the
104 * status line of the outgoing HTTP header, by rest_write_header for decoding
105 * incoming HTTP responses, and by the configuration parser.
111 const FR_NAME_NUMBER http_method_table[] = {
112 { "GET", HTTP_METHOD_GET },
113 { "POST", HTTP_METHOD_POST },
114 { "PUT", HTTP_METHOD_PUT },
115 { "DELETE", HTTP_METHOD_DELETE },
120 /** Conversion table for type config values.
122 * Textual names for http_body_type_t enum values, used by the
123 * configuration parser.
125 * @see http_body_Type_t
129 const FR_NAME_NUMBER http_body_type_table[] = {
130 { "unknown", HTTP_BODY_UNKNOWN },
131 { "unsupported", HTTP_BODY_UNSUPPORTED },
132 { "unavailable", HTTP_BODY_UNAVAILABLE },
133 { "invalid", HTTP_BODY_INVALID },
134 { "post", HTTP_BODY_POST },
135 { "json", HTTP_BODY_JSON },
136 { "xml", HTTP_BODY_XML },
137 { "yaml", HTTP_BODY_YAML },
138 { "html", HTTP_BODY_HTML },
139 { "plain", HTTP_BODY_PLAIN },
144 const FR_NAME_NUMBER http_auth_table[] = {
145 { "none", HTTP_AUTH_NONE },
146 { "srp", HTTP_AUTH_TLS_SRP },
147 { "basic", HTTP_AUTH_BASIC },
148 { "digest", HTTP_AUTH_DIGEST },
149 { "digest-ie", HTTP_AUTH_DIGEST_IE },
150 { "gss-negotiate", HTTP_AUTH_GSSNEGOTIATE },
151 { "ntlm", HTTP_AUTH_NTLM },
152 { "ntlm-winbind", HTTP_AUTH_NTLM_WB },
153 { "any", HTTP_AUTH_ANY },
154 { "safe", HTTP_AUTH_ANY_SAFE },
159 /** Conversion table for "Content-Type" header values.
161 * Used by rest_write_header for parsing incoming headers.
163 * Values we expect to see in the 'Content-Type:' header of the incoming
166 * Some data types (like YAML) do no have standard MIME types defined,
167 * so multiple types, are listed here.
169 * @see http_body_Type_t
173 const FR_NAME_NUMBER http_content_type_table[] = {
174 { "application/x-www-form-urlencoded", HTTP_BODY_POST },
175 { "application/json", HTTP_BODY_JSON },
176 { "text/html", HTTP_BODY_HTML },
177 { "text/plain", HTTP_BODY_PLAIN },
178 { "text/xml", HTTP_BODY_XML },
179 { "text/yaml", HTTP_BODY_YAML },
180 { "text/x-yaml", HTTP_BODY_YAML },
181 { "application/yaml", HTTP_BODY_YAML },
182 { "application/x-yaml", HTTP_BODY_YAML },
187 /** Flags to control the conversion of JSON values to VALUE_PAIRs.
189 * These fields are set when parsing the expanded format for value pairs in
190 * JSON, and control how json_pairmake_leaf and json_pairmake convert the JSON
191 * value, and move the new VALUE_PAIR into an attribute list.
194 * @see json_pairmake_leaf
196 typedef struct json_flags {
197 boolean do_xlat; //!< If TRUE value will be expanded with xlat.
198 boolean is_json; //!< If TRUE value will be inserted as raw JSON
199 // (multiple values not supported).
200 FR_TOKEN operator; //!< The operator that determines how the new VP
201 // is processed. @see fr_tokens
205 /** Initialises libcurl.
207 * Allocates global variables and memory required for libcurl to fundtion.
208 * MUST only be called once per module instance.
210 * rest_cleanup must not be called if rest_init fails.
214 * @param[in] instance configuration data.
215 * @return TRUE if init succeeded FALSE if it failed.
217 int rest_init(rlm_rest_t *instance)
221 ret = curl_global_init(CURL_GLOBAL_ALL);
222 if (ret != CURLE_OK) {
224 "rlm_rest (%s): CURL init returned error: %i - %s",
226 ret, curl_easy_strerror(ret));
228 curl_global_cleanup();
232 radlog(L_DBG, "rlm_rest (%s): CURL library version: %s",
239 /** Cleans up after libcurl.
241 * Wrapper around curl_global_cleanup, frees any memory allocated by rest_init.
242 * Must only be called once per call of rest_init.
246 void rest_cleanup(void)
248 curl_global_cleanup();
251 /** Creates a new connection handle for use by the FR connection API.
253 * Matches the fr_connection_create_t function prototype, is passed to
254 * fr_connection_pool_init, and called when a new connection is required by the
255 * connection pool API.
257 * Creates an instances of rlm_rest_handle_t, and rlm_rest_curl_context_t
258 * which hold the context data required for generating requests and parsing
259 * responses. Calling rest_socket_delete will free this memory.
261 * If instance->connect_uri is not NULL libcurl will attempt to open a
262 * TCP socket to the server specified in the URI. This is done so that when the
263 * socket is first used, there will already be a cached TCP connection to the
264 * REST server associated with the curl handle.
266 * @see rest_socket_delete
267 * @see fr_connection_pool_init
268 * @see fr_connection_create_t
271 * @param[in] instance configuration data.
272 * @return connection handle or NULL if the connection failed or couldn't
275 void *rest_socket_create(void *instance)
277 rlm_rest_t *inst = instance;
279 rlm_rest_handle_t *randle;
280 rlm_rest_curl_context_t *ctx;
282 CURL *candle = curl_easy_init();
286 radlog(L_ERR, "rlm_rest (%s): Failed to create CURL handle",
291 if (!*inst->connect_uri) {
292 radlog(L_ERR, "rlm_rest (%s): Skipping pre-connect,"
293 " connect_uri not specified", inst->xlat_name);
298 * Pre-establish TCP connection to webserver. This would usually be
299 * done on the first request, but we do it here to minimise
302 ret = curl_easy_setopt(candle, CURLOPT_CONNECT_ONLY, 1);
303 if (ret != CURLE_OK) goto error;
305 ret = curl_easy_setopt(candle, CURLOPT_URL,
307 if (ret != CURLE_OK) goto error;
309 radlog(L_DBG, "rlm_rest (%s): Connecting to \"%s\"",
313 ret = curl_easy_perform(candle);
314 if (ret != CURLE_OK) {
315 radlog(L_ERR, "rlm_rest (%s): Connection failed: %i - %s",
317 ret, curl_easy_strerror(ret));
319 goto connection_error;
323 * Malloc memory for the connection handle abstraction.
325 randle = malloc(sizeof(*randle));
326 memset(randle, 0, sizeof(*randle));
328 ctx = malloc(sizeof(*ctx));
329 memset(ctx, 0, sizeof(*ctx));
331 ctx->headers = NULL; /* CURL needs this to be NULL */
332 ctx->read.instance = inst;
335 randle->handle = candle;
338 * Clear any previously configured options for the first request.
340 curl_easy_reset(candle);
345 * Cleanup for error conditions.
349 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
351 ret, curl_easy_strerror(ret));
354 * So we don't leak CURL handles.
358 curl_easy_cleanup(candle);
363 /** Verifies that the last TCP socket associated with a handle is still active.
365 * Quieries libcurl to try and determine if the TCP socket associated with a
366 * connection handle is still viable.
368 * @param[in] instance configuration data.
369 * @param[in] handle to check.
370 * @returns FALSE if the last socket is dead, or if the socket state couldn't be
371 * determined, else TRUE.
373 int rest_socket_alive(void *instance, void *handle)
375 rlm_rest_t *inst = instance;
376 rlm_rest_handle_t *randle = handle;
377 CURL *candle = randle->handle;
382 curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
383 if (ret != CURLE_OK) {
385 "rlm_rest (%s): Couldn't determine socket"
386 " state: %i - %s", inst->xlat_name, ret,
387 curl_easy_strerror(ret));
392 if (last_socket == -1) {
399 /** Frees a libcurl handle, and any additional memory used by context data.
401 * @param[in] instance configuration data.
402 * @param[in] handle rlm_rest_handle_t to close and free.
403 * @return returns TRUE.
405 int rest_socket_delete(UNUSED void *instance, void *handle)
407 rlm_rest_handle_t *randle = handle;
408 CURL *candle = randle->handle;
410 curl_easy_cleanup(candle);
418 /** Encodes VALUE_PAIR linked list in POST format
420 * This is a stream function matching the rest_read_t prototype. Multiple
421 * successive calls will return additional encoded VALUE_PAIRs.
422 * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
423 * will be written to the ptr buffer.
425 * POST request format is:
426 * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
428 * All attributes and values are url encoded. There is currently no support for
429 * nested attributes, or attribute qualifiers.
431 * Nested attributes may be added in the future using
432 * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
433 * to denotate nesting.
435 * Requires libcurl for url encoding.
437 * @see rest_decode_post
439 * @param[out] ptr Char buffer to write encoded data to.
440 * @param[in] size Multiply by nmemb to get the length of ptr.
441 * @param[in] nmemb Multiply by size to get the length of ptr.
442 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls.
443 * @return length of data (including NULL) written to ptr, or 0 if no more
446 static size_t rest_encode_post(void *ptr, size_t size, size_t nmemb,
449 rlm_rest_read_t *ctx = userdata;
450 REQUEST *request = ctx->request; /* Used by RDEBUG */
451 VALUE_PAIR **current = ctx->next;
453 char *p = ptr; /* Position in buffer */
454 char *f = ptr; /* Position in buffer of last fully encoded attribute or value */
455 char *escaped; /* Pointer to current URL escaped data */
458 ssize_t s = (size * nmemb) - 1;
460 /* Allow manual chunking */
461 if ((ctx->chunk) && (ctx->chunk <= s)) {
462 s = (ctx->chunk - 1);
465 if (ctx->state == READ_STATE_END) return FALSE;
467 /* Post data requires no headers */
468 if (ctx->state == READ_STATE_INIT) {
469 ctx->state = READ_STATE_ATTR_BEGIN;
474 ctx->state = READ_STATE_END;
479 RDEBUG2("Encoding attribute \"%s\"", current[0]->name);
481 if (ctx->state == READ_STATE_ATTR_BEGIN) {
482 escaped = curl_escape(current[0]->name,
483 strlen(current[0]->name));
484 len = strlen(escaped);
491 len = sprintf(p, "%s=", escaped);
499 * We wrote the attribute header, record progress.
502 ctx->state = READ_STATE_ATTR_CONT;
506 * Write out single attribute string.
508 len = vp_prints_value(p , s, current[0], 0);
509 escaped = curl_escape(p, len);
510 len = strlen(escaped);
517 len = strlcpy(p, escaped, len + 1);
521 RDEBUG("\tLength : %i", len);
522 RDEBUG("\tValue : %s", p);
528 if (!--s) goto no_space;
533 * We wrote one full attribute value pair, record progress.
537 ctx->state = READ_STATE_ATTR_BEGIN;
544 len = p - (char*)ptr;
546 RDEBUG2("POST Data: %s", (char*) ptr);
547 RDEBUG2("Returning %i bytes of POST data", len);
552 * Cleanup for error conditions
558 len = f - (char*)ptr;
560 RDEBUG2("POST Data: %s", (char*) ptr);
563 * The buffer wasn't big enough to encode a single attribute chunk.
566 radlog(L_ERR, "rlm_rest (%s): AVP exceeds buffer length"
567 " or chunk", ctx->instance->xlat_name);
569 RDEBUG2("Returning %i bytes of POST data"
570 " (buffer full or chunk exceeded)", len);
576 /** Encodes VALUE_PAIR linked list in JSON format
578 * This is a stream function matching the rest_read_t prototype. Multiple
579 * successive calls will return additional encoded VALUE_PAIRs.
581 * Only complete attribute headers
582 * @verbatim "<name>":{"type":"<type>","value":[' @endverbatim
583 * and complete attribute values will be written to ptr.
585 * If an attribute occurs multiple times in the request the attribute values
586 * will be concatenated into a single value array.
588 * JSON request format is:
593 "value":[<value0>,<value1>,<valueN>]
606 * @param[out] ptr Char buffer to write encoded data to.
607 * @param[in] size Multiply by nmemb to get the length of ptr.
608 * @param[in] nmemb Multiply by size to get the length of ptr.
609 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls.
610 * @return length of data (including NULL) written to ptr, or 0 if no more
613 static size_t rest_encode_json(void *ptr, size_t size, size_t nmemb,
616 rlm_rest_read_t *ctx = userdata;
617 REQUEST *request = ctx->request; /* Used by RDEBUG */
618 VALUE_PAIR **current = ctx->next;
620 char *p = ptr; /* Position in buffer */
621 char *f = ptr; /* Position in buffer of last fully encoded attribute or value */
626 ssize_t s = (size * nmemb) - 1;
630 /* Allow manual chunking */
631 if ((ctx->chunk) && (ctx->chunk <= s)) {
632 s = (ctx->chunk - 1);
635 if (ctx->state == READ_STATE_END) return FALSE;
637 if (ctx->state == READ_STATE_INIT) {
638 ctx->state = READ_STATE_ATTR_BEGIN;
640 if (!--s) goto no_space;
646 ctx->state = READ_STATE_END;
648 if (!--s) goto no_space;
655 * New attribute, write name, type, and beginning of
658 RDEBUG2("Encoding attribute \"%s\"", current[0]->name);
659 if (ctx->state == READ_STATE_ATTR_BEGIN) {
660 type = fr_int2str(dict_attr_types, current[0]->type,
664 len += strlen(current[0]->name);
666 if (s < (23 + len)) goto no_space;
668 len = sprintf(p, "\"%s\":{\"type\":\"%s\",\"value\":[" ,
669 current[0]->name, type);
673 RDEBUG2("\tType : %s", type);
676 * We wrote the attribute header, record progress
679 ctx->state = READ_STATE_ATTR_CONT;
683 * Put all attribute values in an array for easier remote
684 * parsing whether they're multivalued or not.
687 len = vp_prints_value_json(p , s, current[0]);
688 assert((s - len) >= 0);
690 if (len < 0) goto no_space;
693 * Show actual value length minus quotes
695 RDEBUG2("\tLength : %i", (*p == '"') ? (len - 2) : len);
696 RDEBUG2("\tValue : %s", p);
702 * Multivalued attribute
705 ((current[0]->attribute == current[1]->attribute) &&
706 (current[0]->vendor == current[1]->vendor))) {
711 * We wrote one attribute value, record
721 if (!(s -= 2)) goto no_space;
726 if (!--s) goto no_space;
731 * We wrote one full attribute value pair, record progress.
735 ctx->state = READ_STATE_ATTR_BEGIN;
742 len = p - (char*)ptr;
744 RDEBUG2("JSON Data: %s", (char*) ptr);
745 RDEBUG2("Returning %i bytes of JSON data", len);
750 * Were out of buffer space
756 len = f - (char*)ptr;
758 RDEBUG2("JSON Data: %s", (char*) ptr);
761 * The buffer wasn't big enough to encode a single attribute chunk.
764 radlog(L_ERR, "rlm_rest (%s): AVP exceeds buffer length"
765 " or chunk", ctx->instance->xlat_name);
767 RDEBUG2("Returning %i bytes of JSON data"
768 " (buffer full or chunk exceeded)", len);
774 /** Emulates successive libcurl calls to an encoding function
776 * This function is used when the request will be sent to the HTTP server as one
777 * contiguous entity. A buffer of REST_BODY_INCR bytes is allocated and passed
778 * to the stream encoding function.
780 * If the stream function does not return 0, a new buffer is allocated which is
781 * the size of the previous buffer + REST_BODY_INCR bytes, the data from the
782 * previous buffer is copied, and freed, and another call is made to the stream
783 * function, passing a pointer into the new buffer at the end of the previously
786 * This process continues until the stream function signals (by returning 0)
787 * that it has no more data to write.
789 * @param[out] buffer where the pointer to the malloced buffer should
791 * @param[in] func Stream function.
792 * @param[in] limit Maximum buffer size to alloc.
793 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls to
795 * @return the length of the data written to the buffer (excluding NULL) or -1
798 static ssize_t rest_read_wrapper(char **buffer, rest_read_t func,
799 size_t limit, void *userdata)
801 char *previous = NULL;
804 size_t alloc = REST_BODY_INCR; /* Size of buffer to malloc */
805 size_t used = 0; /* Size of data written */
808 while (alloc < limit) {
809 current = rad_malloc(alloc);
812 strlcpy(current, previous, used + 1);
816 len = func(current + used, REST_BODY_INCR, 1, userdata);
823 alloc += REST_BODY_INCR;
832 /** (Re-)Initialises the data in a rlm_rest_read_t.
834 * Resets the values of a rlm_rest_read_t to their defaults.
836 * Must be called between encoding sessions.
838 * As part of initialisation all VALUE_PAIR pointers in the REQUEST packet are
839 * written to an array.
841 * If sort is TRUE, this array of VALUE_PAIR pointers will be sorted by vendor
842 * and then by attribute. This is for stream encoders which may concatenate
843 * multiple attribute values together into an array.
845 * After the encoding session has completed this array must be freed by calling
846 * rest_read_ctx_free .
848 * @see rest_read_ctx_free
850 * @param[in] request Current request.
851 * @param[in] ctx to initialise.
852 * @param[in] sort If TRUE VALUE_PAIRs will be sorted within the VALUE_PAIR
855 static void rest_read_ctx_init(REQUEST *request,
856 rlm_rest_read_t *ctx,
859 unsigned short count = 0, i;
862 VALUE_PAIR **current, *tmp;
865 * Setup stream read data
867 ctx->request = request;
868 ctx->state = READ_STATE_INIT;
871 * Create sorted array of VP pointers
873 tmp = request->packet->vps;
874 while (tmp != NULL) {
879 ctx->first = current = rad_malloc((sizeof(tmp) * (count + 1)));
880 ctx->next = ctx->first;
882 tmp = request->packet->vps;
883 while (tmp != NULL) {
888 current = ctx->first;
890 if (!sort || (count < 2)) return;
892 /* TODO: Quicksort would be faster... */
894 for(i = 1; i < count; i++) {
895 assert(current[i-1]->attribute &&
896 current[i]->attribute);
899 if ((current[i-1]->vendor > current[i]->vendor) ||
900 ((current[i-1]->vendor == current[i]->vendor) &&
901 (current[i-1]->attribute > current[i]->attribute)
904 current[i] = current[i-1];
912 /** Frees the VALUE_PAIR array created by rest_read_ctx_init.
914 * Must be called between encoding sessions else module will leak VALUE_PAIR
917 * @see rest_read_ctx_init
919 * @param[in] ctx to free.
921 static void rest_read_ctx_free(rlm_rest_read_t *ctx)
923 if (ctx->first != NULL) {
928 /** Verify that value wasn't truncated when it was converted to a VALUE_PAIR
930 * Certain values may be truncated when they're converted into VALUE_PAIRs
931 * for example 64bit integers converted to 32bit integers. Warn the user
934 * @param[in] request Current request.
935 * @param[in] raw string from decoder.
936 * @param[in] vp containing parsed value.
938 static void rest_check_truncation(REQUEST *request, const char *raw,
943 vp_prints_value(cooked, sizeof(cooked), vp, 0);
944 if (strcmp(raw, cooked) != 0) {
945 RDEBUG("WARNING: Value-Pair does not match POST value, "
946 "truncation may have occurred");
947 RDEBUG("\tValue (pair) : \"%s\"", cooked);
948 RDEBUG("\tValue (post) : \"%s\"", raw);
952 /** Converts POST response into VALUE_PAIRs and adds them to the request
954 * Accepts VALUE_PAIRS in the same format as rest_encode_post, but with the
955 * addition of optional attribute list qualifiers as part of the attribute name
958 * If no qualifiers are specified, will default to the request list.
960 * POST response format is:
961 * @verbatim [outer.][<list>:]<attribute0>=<value0>&[outer.][<list>:]<attribute1>=<value1>&[outer.][<list>:]<attributeN>=<valueN> @endverbatim
963 * @see rest_encode_post
965 * @param[in] instance configuration data.
966 * @param[in] section configuration data.
967 * @param[in] handle rlm_rest_handle_t to use.
968 * @param[in] request Current request.
969 * @param[in] raw buffer containing POST data.
970 * @param[in] rawlen Length of data in raw buffer.
971 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
973 static int rest_decode_post(rlm_rest_t *instance,
974 UNUSED rlm_rest_section_t *section,
975 REQUEST *request, void *handle, char *raw,
976 UNUSED size_t rawlen)
978 rlm_rest_handle_t *randle = handle;
979 CURL *candle = randle->handle;
981 const char *p = raw, *q;
983 const char *attribute;
990 const DICT_ATTR **current, *processed[REST_BODY_MAX_ATTRS + 1];
993 pair_lists_t list_name;
994 request_refs_t request_name;
995 REQUEST *reference = request;
999 int curl_len; /* Length from last curl_easy_unescape call */
1003 processed[0] = NULL;
1008 while (isspace(*p)) p++;
1009 if (*p == '\0') return FALSE;
1011 while (((q = strchr(p, '=')) != NULL) &&
1012 (count < REST_BODY_MAX_ATTRS)) {
1014 reference = request;
1016 name = curl_easy_unescape(candle, p, (q - p), &curl_len);
1019 RDEBUG("Decoding attribute \"%s\"", name);
1021 request_name = radius_request_name(&attribute, REQUEST_CURRENT);
1022 if (request_name == REQUEST_UNKNOWN) {
1023 RDEBUG("WARNING: Invalid request qualifier, skipping");
1030 if (!radius_request(&reference, request_name)) {
1031 RDEBUG("WARNING: Attribute name refers to outer request"
1032 " but not in a tunnel, skipping");
1039 list_name = radius_list_name(&attribute, PAIR_LIST_REPLY);
1040 if (list_name == PAIR_LIST_UNKNOWN) {
1041 RDEBUG("WARNING: Invalid list qualifier, skipping");
1048 da = dict_attrbyname(attribute);
1050 RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
1058 vps = radius_list(reference, list_name);
1062 RDEBUG2("\tType : %s", fr_int2str(dict_attr_types, da->type,
1066 len = (q == NULL) ? (rawlen - (p - raw)) : (unsigned)(q - p);
1068 value = curl_easy_unescape(candle, p, len, &curl_len);
1071 * If we found a delimiter we want to skip over it,
1072 * if we didn't we do *NOT* want to skip over the end
1075 p += (q == NULL) ? len : (len + 1);
1077 RDEBUG2("\tLength : %i", curl_len);
1078 RDEBUG2("\tValue : \"%s\"", value);
1080 vp = paircreate(da->attr, da->vendor, da->type);
1082 radlog(L_ERR, "rlm_rest (%s): Failed creating"
1083 " value-pair", instance->xlat_name);
1088 vp->operator = T_OP_SET;
1091 * Check to see if we've already processed an
1092 * attribute of the same type if we have, change the op
1093 * from T_OP_ADD to T_OP_SET.
1095 current = processed;
1096 while (*current++) {
1097 if ((current[0]->attr == da->attr) &&
1098 (current[0]->vendor == da->vendor)) {
1099 vp->operator = T_OP_ADD;
1104 if (vp->operator != T_OP_ADD) {
1109 tmp = pairparsevalue(vp, value);
1111 RDEBUG("Incompatible value assignment, skipping");
1117 rest_check_truncation(request, value, vp);
1119 vp->flags.do_xlat = 1;
1121 RDEBUG("Performing xlat expansion of response value", value);
1122 pairxlatmove(request, vps, &vp);
1124 if (++count == REST_BODY_MAX_ATTRS) {
1125 radlog(L_ERR, "rlm_rest (%s): At maximum"
1126 " attribute limit", instance->xlat_name);
1146 radlog(L_ERR, "rlm_rest (%s): Malformed POST data \"%s\"",
1147 instance->xlat_name, raw);
1155 /** Converts JSON "value" key into VALUE_PAIR.
1157 * If leaf is not in fact a leaf node, but contains JSON data, the data will
1158 * written to the attribute in JSON string format.
1160 * @param[in] instance configuration data.
1161 * @param[in] section configuration data.
1162 * @param[in] request Current request.
1163 * @param[in] attribute name without qualifiers.
1164 * @param[in] flags containing the operator other flags controlling value
1166 * @param[in] leaf object containing the VALUE_PAIR value.
1167 * @return The VALUE_PAIR just created, or NULL on error.
1169 static VALUE_PAIR *json_pairmake_leaf(rlm_rest_t *instance,
1170 UNUSED rlm_rest_section_t *section,
1171 REQUEST *request, const DICT_ATTR *da,
1172 json_flags_t *flags, json_object *leaf)
1175 VALUE_PAIR *vp, *tmp;
1178 * Should encode any nested JSON structures into JSON strings.
1180 * "I knew you liked JSON so I put JSON in your JSON!"
1182 value = json_object_get_string(leaf);
1184 RDEBUG2("\tType : %s", fr_int2str(dict_attr_types, da->type,
1186 RDEBUG2("\tLength : %i", strlen(value));
1187 RDEBUG2("\tValue : \"%s\"", value);
1189 vp = paircreate(da->attr, da->vendor, da->type);
1191 radlog(L_ERR, "rlm_rest (%s): Failed creating value-pair",
1192 instance->xlat_name);
1196 vp->operator = flags->operator;
1198 tmp = pairparsevalue(vp, value);
1200 RDEBUG("Incompatible value assignment, skipping");
1206 rest_check_truncation(request, value, vp);
1208 if (flags->do_xlat) vp->flags.do_xlat = 1;
1213 /** Processes JSON response and converts it into multiple VALUE_PAIRs
1215 * Processes JSON attribute declarations in the format below. Will recurse when
1216 * processing nested attributes. When processing nested attributes flags and
1217 * operators from previous attributes are not inherited.
1219 * JSON response format is:
1226 "value":[<value0>,<value1>,<valueN>]
1230 "<nested-attribute0>":{
1236 "<attribute2>":"<value0>",
1237 "<attributeN>":"[<value0>,<value1>,<valueN>]"
1241 * JSON valuepair flags (bools):
1242 * - do_xlat (optional) Controls xlat expansion of values. Defaults to TRUE.
1243 * - is_json (optional) If TRUE, any nested JSON data will be copied to the
1244 * VALUE_PAIR in string form. Defaults to TRUE.
1245 * - op (optional) Controls how the attribute is inserted into
1246 * the target list. Defaults to ':=' (T_OP_SET).
1248 * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
1249 * second and subsequent values in multivalued attributes. This does not work
1250 * between multiple attribute declarations.
1254 * @param[in] instance configuration data.
1255 * @param[in] section configuration data.
1256 * @param[in] request Current request.
1257 * @param[in] object containing root node, or parent node.
1258 * @param[in] level Current nesting level.
1259 * @param[in] max_attrs counter, decremented after each VALUE_PAIR is created,
1260 * when 0 no more attributes will be processed.
1261 * @return VALUE_PAIR or NULL on error.
1263 static VALUE_PAIR *json_pairmake(rlm_rest_t *instance,
1264 UNUSED rlm_rest_section_t *section,
1265 REQUEST *request, json_object *object,
1266 int level, int *max_attrs)
1271 const char *name, *attribute;
1273 struct json_object *value, *idx, *tmp;
1274 struct lh_entry *entry;
1277 const DICT_ATTR *da;
1280 request_refs_t request_name;
1281 pair_lists_t list_name;
1282 REQUEST *reference = request;
1287 if (!json_object_is_type(object, json_type_object)) {
1288 RDEBUG("Can't process VP container, expected JSON object,"
1289 " got \"%s\", skipping",
1290 json_object_get_type(object));
1295 * Process VP container
1297 entry = json_object_get_object(object)->head;
1299 flags.operator = T_OP_SET;
1303 name = (char*)entry->k;
1305 /* Fix the compiler warnings regarding const... */
1306 memcpy(&value, &entry->v, sizeof(value));
1308 entry = entry->next;
1311 * For people handcrafting JSON responses
1314 while ((p = q = strchr(p, '|'))) {
1320 reference = request;
1323 * Resolve attribute name to a dictionary entry and
1326 RDEBUG2("Decoding attribute \"%s\"", name);
1328 request_name = radius_request_name(&attribute, REQUEST_CURRENT);
1329 if (request_name == REQUEST_UNKNOWN) {
1330 RDEBUG("WARNING: Request qualifier, skipping");
1335 if (!radius_request(&reference, request_name)) {
1336 RDEBUG("WARNING: Attribute name refers to outer request"
1337 " but not in a tunnel, skipping");
1342 list_name = radius_list_name(&attribute, PAIR_LIST_REPLY);
1343 if (list_name == PAIR_LIST_UNKNOWN) {
1344 RDEBUG("WARNING: Invalid list qualifier, skipping");
1349 da = dict_attrbyname(attribute);
1351 RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
1357 vps = radius_list(reference, list_name);
1362 * Alternate JSON structure that allows operator,
1363 * and other flags to be specified.
1373 * - [] Multivalued array
1374 * - {} Nested Valuepair
1375 * - * Integer or string value
1377 if (json_object_is_type(value, json_type_object)) {
1379 * Process operator if present.
1381 tmp = json_object_object_get(value, "op");
1383 flags.operator = fr_str2int(fr_tokens,
1384 json_object_get_string(tmp), 0);
1386 if (!flags.operator) {
1387 RDEBUG("Invalid operator value \"%s\","
1394 * Process optional do_xlat bool.
1396 tmp = json_object_object_get(value, "do_xlat");
1398 flags.do_xlat = json_object_get_boolean(tmp);
1402 * Process optional is_json bool.
1404 tmp = json_object_object_get(value, "is_json");
1406 flags.is_json = json_object_get_boolean(tmp);
1410 * Value key must be present if were using
1411 * the expanded syntax.
1413 value = json_object_object_get(value, "value");
1415 RDEBUG("Value key missing, skipping", value);
1421 * Setup pairmake / recursion loop.
1423 if (!flags.is_json &&
1424 json_object_is_type(value, json_type_array)) {
1425 len = json_object_array_length(value);
1427 RDEBUG("Zero length value array, skipping", value);
1430 idx = json_object_array_get_idx(value, 0);
1438 if (!(*max_attrs)--) {
1439 radlog(L_ERR, "rlm_rest (%s): At maximum"
1440 " attribute limit", instance->xlat_name);
1445 * Automagically switch the op for multivalued
1448 if (((flags.operator == T_OP_SET) ||
1449 (flags.operator == T_OP_EQ)) && (len > 1)) {
1450 flags.operator = T_OP_ADD;
1453 if (!flags.is_json &&
1454 json_object_is_type(value, json_type_object)) {
1455 /* TODO: Insert nested VP into VP structure...*/
1456 RDEBUG("Found nested VP", value);
1457 vp = json_pairmake(instance, section,
1459 level + 1, max_attrs);
1461 vp = json_pairmake_leaf(instance, section,
1462 request, da, &flags,
1466 if (vp->flags.do_xlat) {
1467 RDEBUG("Performing xlat"
1468 " expansion of response"
1472 pairxlatmove(request, vps, &vp);
1475 } while ((++i < len) && (idx = json_object_array_get_idx(value, i)));
1481 /** Converts JSON response into VALUE_PAIRs and adds them to the request.
1483 * Converts the raw JSON string into a json-c object tree and passes it to
1484 * json_pairmake. After the tree has been parsed json_object_put is called
1485 * which decrements the reference count of the root node by one, and frees
1488 * @see rest_encode_json
1489 * @see json_pairmake
1491 * @param[in] instance configuration data.
1492 * @param[in] section configuration data.
1493 * @param[in] request Current request.
1494 * @param[in] raw buffer containing JSON data.
1495 * @param[in] rawlen Length of data in raw buffer.
1496 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
1498 static int rest_decode_json(rlm_rest_t *instance,
1499 UNUSED rlm_rest_section_t *section,
1500 UNUSED REQUEST *request, UNUSED void *handle,
1501 char *raw, UNUSED size_t rawlen)
1503 const char *p = raw;
1505 struct json_object *json;
1507 int max = REST_BODY_MAX_ATTRS;
1512 while (isspace(*p)) p++;
1513 if (*p == '\0') return FALSE;
1515 json = json_tokener_parse(p);
1517 radlog(L_ERR, "rlm_rest (%s): Malformed JSON data \"%s\"",
1518 instance->xlat_name, raw);
1522 json_pairmake(instance, section, request, json, 0, &max);
1525 * Decrement reference count for root object, should free entire
1528 json_object_put(json);
1530 return (REST_BODY_MAX_ATTRS - max);
1534 /** Processes incoming HTTP header data from libcurl.
1536 * Processes the status line, and Content-Type headers from the incoming HTTP
1539 * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1542 * @param[in] ptr Char buffer where inbound header data is written.
1543 * @param[in] size Multiply by nmemb to get the length of ptr.
1544 * @param[in] nmemb Multiply by size to get the length of ptr.
1545 * @param[in] userdata rlm_rest_write_t to keep parsing state between calls.
1546 * @return Length of data processed, or 0 on error.
1548 static size_t rest_write_header(void *ptr, size_t size, size_t nmemb,
1551 rlm_rest_write_t *ctx = userdata;
1552 REQUEST *request = ctx->request; /* Used by RDEBUG */
1554 const char *p = ptr, *q;
1557 const size_t t = (size * nmemb);
1561 http_body_type_t type;
1562 http_body_type_t supp;
1566 case WRITE_STATE_INIT:
1567 RDEBUG("Processing header");
1570 * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1572 * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
1574 if (s < 14) goto malformed;
1577 * Check start of header matches...
1579 if (strncasecmp("HTTP/", p, 5) != 0) goto malformed;
1585 * Skip the version field, next space should mark start
1588 q = memchr(p, ' ', s);
1589 if (q == NULL) goto malformed;
1595 * Process reason_code.
1597 * " 100" (4) + "\r\n" (2) = 6
1599 if (s < 6) goto malformed;
1603 /* Char after reason code must be a space, or \r */
1604 if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
1606 ctx->code = atoi(p);
1609 * Process reason_phrase (if present).
1615 q = memchr(p, '\r', s);
1616 if (q == NULL) goto malformed;
1620 tmp = rad_malloc(len + 1);
1621 strlcpy(tmp, p, len + 1);
1623 RDEBUG("\tStatus : %i (%s)", ctx->code, tmp);
1627 RDEBUG("\tStatus : %i", ctx->code);
1630 ctx->state = WRITE_STATE_PARSE_HEADERS;
1634 case WRITE_STATE_PARSE_HEADERS:
1636 (strncasecmp("Content-Type: ", p, 14) == 0)) {
1641 * Check to see if there's a parameter
1644 q = memchr(p, ';', s);
1647 * If there's not, find the end of this
1650 if (q == NULL) q = memchr(p, '\r', s);
1652 len = (q == NULL) ? s : (unsigned)(q - p);
1654 type = fr_substr2int(http_content_type_table,
1655 p, HTTP_BODY_UNKNOWN,
1658 supp = http_body_type_supported[type];
1660 tmp = rad_malloc(len + 1);
1661 strlcpy(tmp, p, len + 1);
1663 RDEBUG("\tType : %s (%s)",
1664 fr_int2str(http_body_type_table, type,
1665 "¿Unknown?"), tmp);
1669 if (type == HTTP_BODY_UNKNOWN) {
1670 RDEBUG("Couldn't determine type, using"
1671 " request type \"%s\".",
1672 fr_int2str(http_body_type_table,
1676 } else if (supp == HTTP_BODY_UNSUPPORTED) {
1677 RDEBUG("Type \"%s\" is currently"
1679 fr_int2str(http_body_type_table,
1680 type, "¿Unknown?"));
1681 ctx->type = HTTP_BODY_UNSUPPORTED;
1682 } else if (supp == HTTP_BODY_UNAVAILABLE) {
1683 RDEBUG("Type \"%s\" is currently"
1684 " unavailable, please rebuild"
1685 " this module with the required"
1687 fr_int2str(http_body_type_table,
1688 type, "¿Unknown?"));
1689 ctx->type = HTTP_BODY_UNSUPPORTED;
1691 } else if (supp == HTTP_BODY_INVALID) {
1692 RDEBUG("Type \"%s\" is not a valid web"
1693 " API data markup format",
1694 fr_int2str(http_body_type_table,
1695 type, "¿Unknown?"));
1697 ctx->type = HTTP_BODY_INVALID;
1699 } else if (type != ctx->type) {
1712 RDEBUG("Incoming header was malformed");
1718 /** Processes incoming HTTP body data from libcurl.
1720 * Writes incoming body data to an intermediary buffer for later parsing by
1721 * one of the decode functions.
1723 * @param[in] ptr Char buffer where inbound header data is written
1724 * @param[in] size Multiply by nmemb to get the length of ptr.
1725 * @param[in] nmemb Multiply by size to get the length of ptr.
1726 * @param[in] userdata rlm_rest_write_t to keep parsing state between calls.
1727 * @return length of data processed, or 0 on error.
1729 static size_t rest_write_body(void *ptr, size_t size, size_t nmemb,
1732 rlm_rest_write_t *ctx = userdata;
1733 REQUEST *request = ctx->request; /* Used by RDEBUG */
1735 const char *p = ptr;
1738 const size_t t = (size * nmemb);
1741 * Any post processing of headers should go here...
1743 if (ctx->state == WRITE_STATE_PARSE_HEADERS) {
1744 ctx->state = WRITE_STATE_PARSE_CONTENT;
1749 case HTTP_BODY_UNSUPPORTED:
1752 case HTTP_BODY_INVALID:
1753 tmp = rad_malloc(t + 1);
1754 strlcpy(tmp, p, t + 1);
1763 if (t > (ctx->alloc - ctx->used)) {
1764 ctx->alloc += ((t + 1) > REST_BODY_INCR) ?
1765 t + 1 : REST_BODY_INCR;
1769 ctx->buffer = rad_malloc(ctx->alloc);
1771 /* If data has been written previously */
1773 strlcpy(ctx->buffer, tmp,
1778 strlcpy(ctx->buffer + ctx->used, p, t + 1);
1787 /** (Re-)Initialises the data in a rlm_rest_write_t.
1789 * This resets the values of the a rlm_rest_write_t to their defaults.
1790 * Must be called between encoding sessions.
1792 * @see rest_write_body
1793 * @see rest_write_header
1795 * @param[in] request Current request.
1796 * @param[in] ctx data to initialise.
1797 * @param[in] type Default http_body_type to use when decoding raw data, may be
1798 * overwritten by rest_write_header.
1800 static void rest_write_ctx_init(REQUEST *request, rlm_rest_write_t *ctx,
1801 http_body_type_t type)
1803 ctx->request = request;
1805 ctx->state = WRITE_STATE_INIT;
1811 /** Frees the intermediary buffer created by rest_write.
1813 * @param[in] ctx data to be freed.
1815 static void rest_write_free(rlm_rest_write_t *ctx)
1817 if (ctx->buffer != NULL) {
1822 /** Configures body specific curlopts.
1824 * Configures libcurl handle to use either chunked mode, where the request
1825 * data will be sent using multiple HTTP requests, or contiguous mode where
1826 * the request data will be sent in a single HTTP request.
1828 * @param[in] instance configuration data.
1829 * @param[in] section configuration data.
1830 * @param[in] handle rlm_rest_handle_t to configure.
1831 * @param[in] func to pass to libcurl for chunked.
1832 * transfers (NULL if not using chunked mode).
1833 * @return TRUE on success FALSE on error.
1835 static int rest_request_config_body(rlm_rest_t *instance,
1836 rlm_rest_section_t *section,
1837 rlm_rest_handle_t *handle,
1840 rlm_rest_curl_context_t *ctx = handle->ctx;
1841 CURL *candle = handle->handle;
1846 if (section->chunk > 0) {
1847 ret = curl_easy_setopt(candle, CURLOPT_READDATA,
1849 if (ret != CURLE_OK) goto error;
1851 ret = curl_easy_setopt(candle, CURLOPT_READFUNCTION,
1853 if (ret != CURLE_OK) goto error;
1855 len = rest_read_wrapper(&ctx->body, func,
1856 REST_BODY_MAX_LEN , &ctx->read);
1858 radlog(L_ERR, "rlm_rest (%s): Failed creating HTTP"
1859 " body content", instance->xlat_name);
1863 ret = curl_easy_setopt(candle, CURLOPT_POSTFIELDS,
1865 if (ret != CURLE_OK) goto error;
1867 ret = curl_easy_setopt(candle, CURLOPT_POSTFIELDSIZE,
1869 if (ret != CURLE_OK) goto error;
1875 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
1876 instance->xlat_name, ret, curl_easy_strerror(ret));
1881 /** Configures request curlopts.
1883 * Configures libcurl handle setting various curlopts for things like local
1884 * client time, Content-Type, and other FreeRADIUS custom headers.
1886 * Current FreeRADIUS custom headers are:
1887 * - X-FreeRADIUS-Section The module section being processed.
1888 * - X-FreeRADIUS-Server The current virtual server the REQUEST is
1891 * Sets up callbacks for all response processing (buffers and body data).
1893 * @param[in] instance configuration data.
1894 * @param[in] section configuration data.
1895 * @param[in] handle to configure.
1896 * @param[in] request Current request.
1897 * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1898 * @param[in] type Content-Type for request encoding, also sets the default
1900 * @param[in] uri buffer containing the expanded URI to send the request to.
1901 * @return TRUE on success (all opts configured) FALSE on error.
1903 int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
1904 REQUEST *request, void *handle, http_method_t method,
1905 http_body_type_t type, char *uri)
1907 rlm_rest_handle_t *randle = handle;
1908 rlm_rest_curl_context_t *ctx = randle->ctx;
1909 CURL *candle = randle->handle;
1911 http_auth_type_t auth = section->auth;
1918 buffer[(sizeof(buffer) - 1)] = '\0';
1921 * Setup any header options and generic headers.
1923 ret = curl_easy_setopt(candle, CURLOPT_URL, uri);
1924 if (ret != CURLE_OK) goto error;
1926 ret = curl_easy_setopt(candle, CURLOPT_USERAGENT, "FreeRADIUS");
1927 if (ret != CURLE_OK) goto error;
1929 snprintf(buffer, (sizeof(buffer) - 1), "Content-Type: %s",
1930 fr_int2str(http_content_type_table, type, "¿Unknown?"));
1931 ctx->headers = curl_slist_append(ctx->headers, buffer);
1932 if (!ctx->headers) goto error_header;
1934 if (section->timeout) {
1935 ret = curl_easy_setopt(candle, CURLOPT_TIMEOUT,
1937 if (ret != CURLE_OK) goto error;
1940 ret = curl_easy_setopt(candle, CURLOPT_PROTOCOLS,
1941 (CURLPROTO_HTTP | CURLPROTO_HTTPS));
1942 if (ret != CURLE_OK) goto error;
1945 * FreeRADIUS custom headers
1947 snprintf(buffer, (sizeof(buffer) - 1), "X-FreeRADIUS-Section: %s",
1949 ctx->headers = curl_slist_append(ctx->headers, buffer);
1950 if (!ctx->headers) goto error_header;
1952 snprintf(buffer, (sizeof(buffer) - 1), "X-FreeRADIUS-Server: %s",
1954 ctx->headers = curl_slist_append(ctx->headers, buffer);
1955 if (!ctx->headers) goto error_header;
1958 * Configure HTTP verb (GET, POST, PUT, DELETE, other...)
1962 case HTTP_METHOD_GET :
1963 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1965 if (ret != CURLE_OK) goto error;
1969 case HTTP_METHOD_POST :
1970 ret = curl_easy_setopt(candle, CURLOPT_POST,
1972 if (ret != CURLE_OK) goto error;
1976 case HTTP_METHOD_PUT :
1977 ret = curl_easy_setopt(candle, CURLOPT_PUT,
1979 if (ret != CURLE_OK) goto error;
1983 case HTTP_METHOD_DELETE :
1984 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1986 if (ret != CURLE_OK) goto error;
1988 ret = curl_easy_setopt(candle,
1989 CURLOPT_CUSTOMREQUEST, "DELETE");
1990 if (ret != CURLE_OK) goto error;
1994 case HTTP_METHOD_CUSTOM :
1995 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1997 if (ret != CURLE_OK) goto error;
1999 ret = curl_easy_setopt(candle,
2000 CURLOPT_CUSTOMREQUEST,
2002 if (ret != CURLE_OK) goto error;
2010 * Set user based authentication parameters
2013 if ((auth >= HTTP_AUTH_BASIC) &&
2014 (auth <= HTTP_AUTH_ANY_SAFE)) {
2015 ret = curl_easy_setopt(candle, CURLOPT_HTTPAUTH,
2016 http_curl_auth[auth]);
2017 if (ret != CURLE_OK) goto error;
2019 if (section->username) {
2020 radius_xlat(buffer, sizeof(buffer),
2021 section->username, request, NULL, NULL);
2023 ret = curl_easy_setopt(candle, CURLOPT_USERNAME,
2025 if (ret != CURLE_OK) goto error;
2027 if (section->password) {
2028 radius_xlat(buffer, sizeof(buffer),
2029 section->password, request, NULL, NULL);
2031 ret = curl_easy_setopt(candle, CURLOPT_PASSWORD,
2033 if (ret != CURLE_OK) goto error;
2036 #ifdef CURLOPT_TLSAUTH_USERNAME
2037 } else if (type == HTTP_AUTH_TLS_SRP) {
2038 ret = curl_easy_setopt(candle, CURLOPT_TLSAUTH_TYPE,
2039 http_curl_auth[auth]);
2041 if (section->username) {
2042 radius_xlat(buffer, sizeof(buffer),
2043 section->username, request, NULL, NULL);
2045 ret = curl_easy_setopt(candle,
2046 CURLOPT_TLSAUTH_USERNAME,
2048 if (ret != CURLE_OK) goto error;
2050 if (section->password) {
2051 radius_xlat(buffer, sizeof(buffer),
2052 section->password, request, NULL, NULL);
2054 ret = curl_easy_setopt(candle,
2055 CURLOPT_TLSAUTH_PASSWORD,
2057 if (ret != CURLE_OK) goto error;
2064 * Set SSL/TLS authentication parameters
2066 if (section->tls_certfile) {
2067 ret = curl_easy_setopt(candle,
2069 section->tls_certfile);
2070 if (ret != CURLE_OK) goto error;
2073 if (section->tls_keyfile) {
2074 ret = curl_easy_setopt(candle,
2076 section->tls_keyfile);
2077 if (ret != CURLE_OK) goto error;
2080 if (section->tls_keypassword) {
2081 ret = curl_easy_setopt(candle,
2083 section->tls_keypassword);
2084 if (ret != CURLE_OK) goto error;
2087 if (section->tls_cacertfile) {
2088 ret = curl_easy_setopt(candle,
2090 section->tls_cacertfile);
2091 if (ret != CURLE_OK) goto error;
2094 if (section->tls_cacertdir) {
2095 ret = curl_easy_setopt(candle,
2097 section->tls_cacertdir);
2098 if (ret != CURLE_OK) goto error;
2101 if (section->tls_randfile) {
2102 ret = curl_easy_setopt(candle,
2103 CURLOPT_RANDOM_FILE,
2104 section->tls_randfile);
2105 if (ret != CURLE_OK) goto error;
2108 if (section->tls_verify_cert) {
2109 ret = curl_easy_setopt(candle,
2110 CURLOPT_SSL_VERIFYHOST,
2111 (section->tls_verify_cert_cn == TRUE) ?
2113 if (ret != CURLE_OK) goto error;
2115 ret = curl_easy_setopt(candle,
2116 CURLOPT_SSL_VERIFYPEER,
2118 if (ret != CURLE_OK) goto error;
2122 * Tell CURL how to get HTTP body content, and how to process
2125 rest_write_ctx_init(request, &ctx->write, type);
2127 ret = curl_easy_setopt(candle, CURLOPT_HEADERFUNCTION,
2129 if (ret != CURLE_OK) goto error;
2131 ret = curl_easy_setopt(candle, CURLOPT_HEADERDATA,
2133 if (ret != CURLE_OK) goto error;
2135 ret = curl_easy_setopt(candle, CURLOPT_WRITEFUNCTION,
2137 if (ret != CURLE_OK) goto error;
2139 ret = curl_easy_setopt(candle, CURLOPT_WRITEDATA,
2141 if (ret != CURLE_OK) goto error;
2145 case HTTP_METHOD_GET :
2146 case HTTP_METHOD_DELETE :
2150 case HTTP_METHOD_POST :
2151 case HTTP_METHOD_PUT :
2152 case HTTP_METHOD_CUSTOM :
2153 if (section->chunk > 0) {
2154 ctx->read.chunk = section->chunk;
2156 ctx->headers = curl_slist_append(ctx->headers,
2158 if (!ctx->headers) goto error_header;
2160 ctx->headers = curl_slist_append(ctx->headers,
2161 "Transfer-Encoding: chunked");
2162 if (!ctx->headers) goto error_header;
2168 case HTTP_BODY_JSON:
2169 rest_read_ctx_init(request,
2172 ret = rest_request_config_body(instance,
2176 if (!ret) return -1;
2181 case HTTP_BODY_POST:
2182 rest_read_ctx_init(request,
2185 ret = rest_request_config_body(instance,
2189 if (!ret) return -1;
2197 ret = curl_easy_setopt(candle, CURLOPT_HTTPHEADER,
2199 if (ret != CURLE_OK) goto error;
2210 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
2211 instance->xlat_name, ret, curl_easy_strerror(ret));
2215 radlog(L_ERR, "rlm_rest (%s): Failed creating header",
2216 instance->xlat_name);
2220 /** Sends a REST (HTTP) request.
2222 * Send the actual REST request to the server. The response will be handled by
2223 * the numerous callbacks configured in rest_request_config.
2225 * @param[in] instance configuration data.
2226 * @param[in] section configuration data.
2227 * @param[in] handle to use.
2228 * @return TRUE on success or FALSE on error.
2230 int rest_request_perform(rlm_rest_t *instance,
2231 UNUSED rlm_rest_section_t *section, void *handle)
2233 rlm_rest_handle_t *randle = handle;
2234 CURL *candle = randle->handle;
2237 ret = curl_easy_perform(candle);
2238 if (ret != CURLE_OK) {
2239 radlog(L_ERR, "rlm_rest (%s): Request failed: %i - %s",
2240 instance->xlat_name, ret, curl_easy_strerror(ret));
2247 /** Sends the response to the correct decode function.
2249 * Uses the Content-Type information written in rest_write_header to
2250 * determine the correct decode function to use. The decode function will
2251 * then convert the raw received data into VALUE_PAIRs.
2253 * @param[in] instance configuration data.
2254 * @param[in] section configuration data.
2255 * @param[in] request Current request.
2256 * @param[in] handle to use.
2257 * @return TRUE on success or FALSE on error.
2259 int rest_request_decode(rlm_rest_t *instance,
2260 UNUSED rlm_rest_section_t *section,
2261 REQUEST *request, void *handle)
2263 rlm_rest_handle_t *randle = handle;
2264 rlm_rest_curl_context_t *ctx = randle->ctx;
2268 if (ctx->write.buffer == NULL) {
2269 RDEBUG("Skipping attribute processing, no body data received");
2273 RDEBUG("Processing body", ret);
2275 switch (ctx->write.type)
2277 case HTTP_BODY_POST:
2278 ret = rest_decode_post(instance, section, request,
2279 handle, ctx->write.buffer,
2283 case HTTP_BODY_JSON:
2284 ret = rest_decode_json(instance, section, request,
2285 handle, ctx->write.buffer,
2289 case HTTP_BODY_UNSUPPORTED:
2290 case HTTP_BODY_UNAVAILABLE:
2291 case HTTP_BODY_INVALID:
2301 /** Cleans up after a REST request.
2303 * Resets all options associated with a CURL handle, and frees any headers
2304 * associated with it.
2306 * Calls rest_read_ctx_free and rest_write_free to free any memory used by
2309 * @param[in] instance configuration data.
2310 * @param[in] section configuration data.
2311 * @param[in] handle to cleanup.
2312 * @return TRUE on success or FALSE on error.
2314 void rest_request_cleanup(UNUSED rlm_rest_t *instance,
2315 UNUSED rlm_rest_section_t *section, void *handle)
2317 rlm_rest_handle_t *randle = handle;
2318 rlm_rest_curl_context_t *ctx = randle->ctx;
2319 CURL *candle = randle->handle;
2322 * Clear any previously configured options
2324 curl_easy_reset(candle);
2329 if (ctx->headers != NULL) {
2330 curl_slist_free_all(ctx->headers);
2331 ctx->headers = NULL;
2335 * Free body data (only used if chunking is disabled)
2337 if (ctx->body != NULL) free(ctx->body);
2340 * Free other context info
2342 rest_read_ctx_free(&ctx->read);
2343 rest_write_free(&ctx->write);
2346 /** URL encodes a string.
2348 * Encode special chars as per RFC 3986 section 4.
2350 * @param[in] request Current request.
2351 * @param[out] out Where to write escaped string.
2352 * @param[in] outlen Size of out buffer.
2353 * @param[in] raw string to be urlencoded.
2354 * @param[in] arg pointer, gives context for escaping.
2355 * @return length of data written to out (excluding NULL).
2357 static size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen,
2358 const char *raw, UNUSED void *arg)
2362 escaped = curl_escape(raw, strlen(raw));
2363 strlcpy(out, escaped, outlen);
2369 /** Builds URI; performs XLAT expansions and encoding.
2371 * Splits the URI into "http://example.org" and "/%{xlat}/query/?bar=foo"
2372 * Both components are expanded, but values expanded for the second component
2373 * are also url encoded.
2375 * @param[in] instance configuration data.
2376 * @param[in] section configuration data.
2377 * @param[in] request Current request
2378 * @param[out] buffer to write expanded URI to.
2379 * @param[in] bufsize Size of buffer.
2380 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2383 ssize_t rest_uri_build(rlm_rest_t *instance, rlm_rest_section_t *section,
2384 REQUEST *request, char *buffer, size_t bufsize)
2391 unsigned short count = 0;
2398 * All URLs must contain at least <scheme>://<server>/
2400 while ((q = strchr(p, '/'))) {
2409 radlog(L_ERR, "rlm_rest (%s): Error URI is malformed,"
2410 " can't find start of path", instance->xlat_name);
2416 scheme = rad_malloc(len + 1);
2417 strlcpy(scheme, section->uri, len + 1);
2422 out += radius_xlat(out, bufsize, scheme, request, NULL, NULL);
2426 out += radius_xlat(out, (bufsize - (buffer - out)), path, request,
2427 rest_uri_escape, NULL);
2429 return (buffer - out);