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 <curl/curl.h>
33 #include <json/json.h>
35 #include <freeradius-devel/radiusd.h>
36 #include <freeradius-devel/libradius.h>
37 #include <freeradius-devel/connection.h>
41 /** Table of encoder/decoder support.
43 * Indexes in this table match the http_body_type_t enum, and should be
44 * updated if additional enum values are added.
46 * @see http_body_type_t
48 const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES] = {
49 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNKOWN
50 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNSUPPORTED
51 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_INVALID
52 HTTP_BODY_POST, // HTTP_BODY_POST
53 HTTP_BODY_JSON, // HTTP_BODY_JSON
54 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_XML
55 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_YAML
56 HTTP_BODY_INVALID, // HTTP_BODY_HTML
57 HTTP_BODY_INVALID // HTTP_BODY_PLAIN
61 * Lib CURL doesn't define symbols for unsupported auth methods
63 #ifndef CURLOPT_TLSAUTH_SRP
64 #define CURLOPT_TLSAUTH_SRP 0
66 #ifndef CURLAUTH_BASIC
67 #define CURLAUTH_BASIC 0
69 #ifndef CURLAUTH_DIGEST
70 #define CURLAUTH_DIGEST 0
72 #ifndef CURLAUTH_DIGEST_IE
73 #define CURLAUTH_DIGEST_IE 0
75 #ifndef CURLAUTH_GSSNEGOTIATE
76 #define CURLAUTH_GSSNEGOTIATE 0
79 #define CURLAUTH_NTLM 0
81 #ifndef CURLAUTH_NTLM_WB
82 #define CURLAUTH_NTLM_WB 0
85 const http_body_type_t http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
86 0, // HTTP_AUTH_UNKNOWN
88 CURLOPT_TLSAUTH_SRP, // HTTP_AUTH_TLS_SRP
89 CURLAUTH_BASIC, // HTTP_AUTH_BASIC
90 CURLAUTH_DIGEST, // HTTP_AUTH_DIGEST
91 CURLAUTH_DIGEST_IE, // HTTP_AUTH_DIGEST_IE
92 CURLAUTH_GSSNEGOTIATE, // HTTP_AUTH_GSSNEGOTIATE
93 CURLAUTH_NTLM, // HTTP_AUTH_NTLM
94 CURLAUTH_NTLM_WB, // HTTP_AUTH_NTLM_WB
95 CURLAUTH_ANY, // HTTP_AUTH_ANY
96 CURLAUTH_ANYSAFE // HTTP_AUTH_ANY_SAFE
100 /** Conversion table for method config values.
102 * HTTP verb strings for http_method_t enum values. Used by libcurl in the
103 * status line of the outgoing HTTP header, by rest_write_header for decoding
104 * incoming HTTP responses, and by the configuration parser.
110 const FR_NAME_NUMBER http_method_table[] = {
111 { "GET", HTTP_METHOD_GET },
112 { "POST", HTTP_METHOD_POST },
113 { "PUT", HTTP_METHOD_PUT },
114 { "DELETE", HTTP_METHOD_DELETE },
119 /** Conversion table for type config values.
121 * Textual names for http_body_type_t enum values, used by the
122 * configuration parser.
124 * @see http_body_Type_t
128 const FR_NAME_NUMBER http_body_type_table[] = {
129 { "unknown", HTTP_BODY_UNKNOWN },
130 { "unsupported", HTTP_BODY_UNSUPPORTED },
131 { "invalid", HTTP_BODY_INVALID },
132 { "post", HTTP_BODY_POST },
133 { "json", HTTP_BODY_JSON },
134 { "xml", HTTP_BODY_XML },
135 { "yaml", HTTP_BODY_YAML },
136 { "html", HTTP_BODY_HTML },
137 { "plain", HTTP_BODY_PLAIN },
142 const FR_NAME_NUMBER http_auth_table[] = {
143 { "none", HTTP_AUTH_NONE },
144 { "srp", HTTP_AUTH_TLS_SRP },
145 { "basic", HTTP_AUTH_BASIC },
146 { "digest", HTTP_AUTH_DIGEST },
147 { "digest-ie", HTTP_AUTH_DIGEST_IE },
148 { "gss-negotiate", HTTP_AUTH_GSSNEGOTIATE },
149 { "ntlm", HTTP_AUTH_NTLM },
150 { "ntlm-winbind", HTTP_AUTH_NTLM_WB },
151 { "any", HTTP_AUTH_ANY },
152 { "safe", HTTP_AUTH_ANY_SAFE },
157 /** Conversion table for "Content-Type" header values.
159 * Used by rest_write_header for parsing incoming headers.
161 * Values we expect to see in the 'Content-Type:' header of the incoming
164 * Some data types (like YAML) do no have standard MIME types defined,
165 * so multiple types, are listed here.
167 * @see http_body_Type_t
171 const FR_NAME_NUMBER http_content_type_table[] = {
172 { "application/x-www-form-urlencoded", HTTP_BODY_POST },
173 { "application/json", HTTP_BODY_JSON },
174 { "text/html", HTTP_BODY_HTML },
175 { "text/plain", HTTP_BODY_PLAIN },
176 { "text/xml", HTTP_BODY_XML },
177 { "text/yaml", HTTP_BODY_YAML },
178 { "text/x-yaml", HTTP_BODY_YAML },
179 { "application/yaml", HTTP_BODY_YAML },
180 { "application/x-yaml", HTTP_BODY_YAML },
184 /** Flags to control the conversion of JSON values to VALUE_PAIRs.
186 * These fields are set when parsing the expanded format for value pairs in
187 * JSON, and control how json_pairmake_leaf and json_pairmake convert the JSON
188 * value, and move the new VALUE_PAIR into an attribute list.
191 * @see json_pairmake_leaf
193 typedef struct json_flags {
194 boolean do_xlat; //!< If TRUE value will be expanded with xlat.
195 boolean is_json; //!< If TRUE value will be inserted as raw JSON
196 // (multiple values not supported).
197 FR_TOKEN operator; //!< The operator that determines how the new VP
198 // is processed. @see fr_tokens
201 /** Initialises libcurl.
203 * Allocates global variables and memory required for libcurl to fundtion.
204 * MUST only be called once per module instance.
206 * rest_cleanup must not be called if rest_init fails.
210 * @param[in] instance configuration data.
211 * @return TRUE if init succeeded FALSE if it failed.
213 int rest_init(rlm_rest_t *instance)
217 ret = curl_global_init(CURL_GLOBAL_ALL);
218 if (ret != CURLE_OK) {
220 "rlm_rest (%s): CURL init returned error: %i - %s",
222 ret, curl_easy_strerror(ret));
224 curl_global_cleanup();
228 radlog(L_DBG, "rlm_rest (%s): CURL library version: %s",
235 /** Cleans up after libcurl.
237 * Wrapper around curl_global_cleanup, frees any memory allocated by rest_init.
238 * Must only be called once per call of rest_init.
242 void rest_cleanup(void)
244 curl_global_cleanup();
247 /** Creates a new connection handle for use by the FR connection API.
249 * Matches the fr_connection_create_t function prototype, is passed to
250 * fr_connection_pool_init, and called when a new connection is required by the
251 * connection pool API.
253 * Creates an instances of rlm_rest_handle_t, and rlm_rest_curl_context_t
254 * which hold the context data required for generating requests and parsing
255 * responses. Calling rest_socket_delete will free this memory.
257 * If instance->connect_uri is not NULL libcurl will attempt to open a
258 * TCP socket to the server specified in the URI. This is done so that when the
259 * socket is first used, there will already be a cached TCP connection to the
260 * REST server associated with the curl handle.
262 * @see rest_socket_delete
263 * @see fr_connection_pool_init
264 * @see fr_connection_create_t
267 * @param[in] instance configuration data.
268 * @return connection handle or NULL if the connection failed or couldn't
271 void *rest_socket_create(void *instance)
273 rlm_rest_t *inst = instance;
275 rlm_rest_handle_t *randle;
276 rlm_rest_curl_context_t *ctx;
278 CURL *candle = curl_easy_init();
282 radlog(L_ERR, "rlm_rest (%s): Failed to create CURL handle",
287 if (!*inst->connect_uri) {
288 radlog(L_ERR, "rlm_rest (%s): Skipping pre-connect,"
289 " connect_uri not specified", inst->xlat_name);
294 * Pre-establish TCP connection to webserver. This would usually be
295 * done on the first request, but we do it here to minimise
298 ret = curl_easy_setopt(candle, CURLOPT_CONNECT_ONLY, 1);
299 if (ret != CURLE_OK) goto error;
301 ret = curl_easy_setopt(candle, CURLOPT_URL,
303 if (ret != CURLE_OK) goto error;
305 radlog(L_DBG, "rlm_rest (%s): Connecting to \"%s\"",
309 ret = curl_easy_perform(candle);
310 if (ret != CURLE_OK) {
311 radlog(L_ERR, "rlm_rest (%s): Connection failed: %i - %s",
313 ret, curl_easy_strerror(ret));
315 goto connection_error;
319 * Malloc memory for the connection handle abstraction.
321 randle = malloc(sizeof(*randle));
322 memset(randle, 0, sizeof(*randle));
324 ctx = malloc(sizeof(*ctx));
325 memset(ctx, 0, sizeof(*ctx));
327 ctx->headers = NULL; /* CURL needs this to be NULL */
328 ctx->read.instance = inst;
331 randle->handle = candle;
334 * Clear any previously configured options for the first request.
336 curl_easy_reset(candle);
341 * Cleanup for error conditions.
345 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
347 ret, curl_easy_strerror(ret));
350 * So we don't leak CURL handles.
354 curl_easy_cleanup(candle);
359 /** Verifies that the last TCP socket associated with a handle is still active.
361 * Quieries libcurl to try and determine if the TCP socket associated with a
362 * connection handle is still viable.
364 * @param[in] instance configuration data.
365 * @param[in] handle to check.
366 * @returns FALSE if the last socket is dead, or if the socket state couldn't be
367 * determined, else TRUE.
369 int rest_socket_alive(void *instance, void *handle)
371 rlm_rest_t *inst = instance;
372 rlm_rest_handle_t *randle = handle;
373 CURL *candle = randle->handle;
378 curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
379 if (ret != CURLE_OK) {
381 "rlm_rest (%s): Couldn't determine socket"
382 " state: %i - %s", inst->xlat_name, ret,
383 curl_easy_strerror(ret));
388 if (last_socket == -1) {
395 /** Frees a libcurl handle, and any additional memory used by context data.
397 * @param[in] instance configuration data.
398 * @param[in] handle rlm_rest_handle_t to close and free.
399 * @return returns TRUE.
401 int rest_socket_delete(UNUSED void *instance, void *handle)
403 rlm_rest_handle_t *randle = handle;
404 CURL *candle = randle->handle;
406 curl_easy_cleanup(candle);
414 /** Encodes VALUE_PAIR linked list in POST format
416 * This is a stream function matching the rest_read_t prototype. Multiple
417 * successive calls will return additional encoded VALUE_PAIRs.
418 * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
419 * will be written to the ptr buffer.
421 * POST request format is:
422 * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
424 * All attributes and values are url encoded. There is currently no support for
425 * nested attributes, or attribute qualifiers.
427 * Nested attributes may be added in the future using
428 * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
429 * to denotate nesting.
431 * Requires libcurl for url encoding.
433 * @see rest_decode_post
435 * @param[out] ptr Char buffer to write encoded data to.
436 * @param[in] size Multiply by nmemb to get the length of ptr.
437 * @param[in] nmemb Multiply by size to get the length of ptr.
438 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls.
439 * @return length of data (including NULL) written to ptr, or 0 if no more
442 static size_t rest_encode_post(void *ptr, size_t size, size_t nmemb,
445 rlm_rest_read_t *ctx = userdata;
446 REQUEST *request = ctx->request; /* Used by RDEBUG */
447 VALUE_PAIR **current = ctx->next;
449 char *p = ptr; /* Position in buffer */
450 char *f = ptr; /* Position in buffer of last fully encoded attribute or value */
451 char *escaped; /* Pointer to current URL escaped data */
454 ssize_t s = (size * nmemb) - 1;
456 /* Allow manual chunking */
457 if ((ctx->chunk) && (ctx->chunk <= s)) {
458 s = (ctx->chunk - 1);
461 if (ctx->state == READ_STATE_END) return FALSE;
463 /* Post data requires no headers */
464 if (ctx->state == READ_STATE_INIT) {
465 ctx->state = READ_STATE_ATTR_BEGIN;
470 ctx->state = READ_STATE_END;
475 RDEBUG2("Encoding attribute \"%s\"", current[0]->name);
477 if (ctx->state == READ_STATE_ATTR_BEGIN) {
478 escaped = curl_escape(current[0]->name,
479 strlen(current[0]->name));
480 len = strlen(escaped);
487 len = sprintf(p, "%s=", escaped);
495 * We wrote the attribute header, record progress.
498 ctx->state = READ_STATE_ATTR_CONT;
502 * Write out single attribute string.
504 len = vp_prints_value(p , s, current[0], 0);
505 escaped = curl_escape(p, len);
506 len = strlen(escaped);
513 len = strlcpy(p, escaped, len + 1);
517 RDEBUG("\tLength : %i", len);
518 RDEBUG("\tValue : %s", p);
524 if (!--s) goto no_space;
529 * We wrote one full attribute value pair, record progress.
533 ctx->state = READ_STATE_ATTR_BEGIN;
540 len = p - (char*)ptr;
542 RDEBUG2("POST Data: %s", (char*) ptr);
543 RDEBUG2("Returning %i bytes of POST data", len);
548 * Cleanup for error conditions
554 len = f - (char*)ptr;
556 RDEBUG2("POST Data: %s", (char*) ptr);
559 * The buffer wasn't big enough to encode a single attribute chunk.
562 radlog(L_ERR, "rlm_rest (%s): AVP exceeds buffer length"
563 " or chunk", ctx->instance->xlat_name);
565 RDEBUG2("Returning %i bytes of POST data"
566 " (buffer full or chunk exceeded)", len);
572 /** Encodes VALUE_PAIR linked list in JSON format
574 * This is a stream function matching the rest_read_t prototype. Multiple
575 * successive calls will return additional encoded VALUE_PAIRs.
577 * Only complete attribute headers
578 * @verbatim "<name>":{"type":"<type>","value":['</pre> @endverbatim
579 * and complete attribute values will be written to ptr.
581 * If an attribute occurs multiple times in the request the attribute values
582 * will be concatenated into a single value array.
584 * JSON request format is:
589 "value":[<value0>,<value1>,<valueN>]
602 * @param[out] ptr Char buffer to write encoded data to.
603 * @param[in] size Multiply by nmemb to get the length of ptr.
604 * @param[in] nmemb Multiply by size to get the length of ptr.
605 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls.
606 * @return length of data (including NULL) written to ptr, or 0 if no more
609 static size_t rest_encode_json(void *ptr, size_t size, size_t nmemb,
612 rlm_rest_read_t *ctx = userdata;
613 REQUEST *request = ctx->request; /* Used by RDEBUG */
614 VALUE_PAIR **current = ctx->next;
616 char *p = ptr; /* Position in buffer */
617 char *f = ptr; /* Position in buffer of last fully encoded attribute or value */
622 ssize_t s = (size * nmemb) - 1;
626 /* Allow manual chunking */
627 if ((ctx->chunk) && (ctx->chunk <= s)) {
628 s = (ctx->chunk - 1);
631 if (ctx->state == READ_STATE_END) return FALSE;
633 if (ctx->state == READ_STATE_INIT) {
634 ctx->state = READ_STATE_ATTR_BEGIN;
636 if (!--s) goto no_space;
642 ctx->state = READ_STATE_END;
644 if (!--s) goto no_space;
651 * New attribute, write name, type, and beginning of
654 RDEBUG2("Encoding attribute \"%s\"", current[0]->name);
655 if (ctx->state == READ_STATE_ATTR_BEGIN) {
656 type = fr_int2str(dict_attr_types, current[0]->type,
660 len += strlen(current[0]->name);
662 if (s < (23 + len)) goto no_space;
664 len = sprintf(p, "\"%s\":{\"type\":\"%s\",\"value\":[" ,
665 current[0]->name, type);
669 RDEBUG2("\tType : %s", type);
672 * We wrote the attribute header, record progress
675 ctx->state = READ_STATE_ATTR_CONT;
679 * Put all attribute values in an array for easier remote
680 * parsing whether they're multivalued or not.
683 len = vp_prints_value_json(p , s, current[0]);
684 assert((s - len) >= 0);
686 if (len < 0) goto no_space;
689 * Show actual value length minus quotes
691 RDEBUG2("\tLength : %i", (*p == '"') ? (len - 2) : len);
692 RDEBUG2("\tValue : %s", p);
698 * Multivalued attribute
701 ((current[0]->attribute == current[1]->attribute) &&
702 (current[0]->vendor == current[1]->vendor))) {
707 * We wrote one attribute value, record
717 if (!(s -= 2)) goto no_space;
722 if (!--s) goto no_space;
727 * We wrote one full attribute value pair, record progress.
731 ctx->state = READ_STATE_ATTR_BEGIN;
738 len = p - (char*)ptr;
740 RDEBUG2("JSON Data: %s", (char*) ptr);
741 RDEBUG2("Returning %i bytes of JSON data", len);
746 * Were out of buffer space
752 len = f - (char*)ptr;
754 RDEBUG2("JSON Data: %s", (char*) ptr);
757 * The buffer wasn't big enough to encode a single attribute chunk.
760 radlog(L_ERR, "rlm_rest (%s): AVP exceeds buffer length"
761 " or chunk", ctx->instance->xlat_name);
763 RDEBUG2("Returning %i bytes of JSON data"
764 " (buffer full or chunk exceeded)", len);
770 /** Emulates successive libcurl calls to an encoding function
772 * This function is used when the request will be sent to the HTTP server as one
773 * contiguous entity. A buffer of REST_BODY_INCR bytes is allocated and passed
774 * to the stream encoding function.
776 * If the stream function does not return 0, a new buffer is allocated which is
777 * the size of the previous buffer + REST_BODY_INCR bytes, the data from the
778 * previous buffer is copied, and freed, and another call is made to the stream
779 * function, passing a pointer into the new buffer at the end of the previously
782 * This process continues until the stream function signals (by returning 0)
783 * that it has no more data to write.
785 * @param[out] buffer where the pointer to the malloced buffer should
787 * @param[in] func Stream function.
788 * @param[in] limit Maximum buffer size to alloc.
789 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls to
791 * @return the length of the data written to the buffer (excluding NULL) or -1
794 static ssize_t rest_read_wrapper(char **buffer, rest_read_t func,
795 size_t limit, void *userdata)
797 char *previous = NULL;
800 size_t alloc = REST_BODY_INCR; /* Size of buffer to malloc */
801 size_t used = 0; /* Size of data written */
804 while (alloc < limit) {
805 current = rad_malloc(alloc);
808 strlcpy(current, previous, used + 1);
812 len = func(current + used, REST_BODY_INCR, 1, userdata);
819 alloc += REST_BODY_INCR;
828 /** (Re-)Initialises the data in a rlm_rest_read_t.
830 * Resets the values of a rlm_rest_read_t to their defaults.
832 * Must be called between encoding sessions.
834 * As part of initialisation all VALUE_PAIR pointers in the REQUEST packet are
835 * written to an array.
837 * If sort is TRUE, this array of VALUE_PAIR pointers will be sorted by vendor
838 * and then by attribute. This is for stream encoders which may concatenate
839 * multiple attribute values together into an array.
841 * After the encoding session has completed this array must be freed by calling
842 * rest_read_ctx_free .
844 * @see rest_read_ctx_free
846 * @param[in] request Current request.
847 * @param[in] read to initialise.
848 * @param[in] sort If TRUE VALUE_PAIRs will be sorted within the VALUE_PAIR
851 static void rest_read_ctx_init(REQUEST *request,
852 rlm_rest_read_t *ctx,
855 unsigned short count = 0, i;
858 VALUE_PAIR **current, *tmp;
861 * Setup stream read data
863 ctx->request = request;
864 ctx->state = READ_STATE_INIT;
867 * Create sorted array of VP pointers
869 tmp = request->packet->vps;
870 while (tmp != NULL) {
875 ctx->first = current = rad_malloc((sizeof(tmp) * (count + 1)));
876 ctx->next = ctx->first;
878 tmp = request->packet->vps;
879 while (tmp != NULL) {
884 current = ctx->first;
886 if (!sort || (count < 2)) return;
888 /* TODO: Quicksort would be faster... */
890 for(i = 1; i < count; i++) {
891 assert(current[i-1]->attribute &&
892 current[i]->attribute);
895 if ((current[i-1]->vendor > current[i]->vendor) ||
896 ((current[i-1]->vendor == current[i]->vendor) &&
897 (current[i-1]->attribute > current[i]->attribute)
900 current[i] = current[i-1];
908 /** Frees the VALUE_PAIR array created by rest_read_ctx_init.
910 * Must be called between encoding sessions else module will leak VALUE_PAIR
913 * @see rest_read_ctx_init
915 * @param[in] read to free.
917 static void rest_read_ctx_free(rlm_rest_read_t *ctx)
919 if (ctx->first != NULL) {
924 /** Verify that value wasn't truncated when it was converted to a VALUE_PAIR
926 * Certain values may be truncated when they're converted into VALUE_PAIRs
927 * for example 64bit integers converted to 32bit integers. Warn the user
930 * @param[in] raw string from decoder.
931 * @param[in] vp containing parsed value.
933 static void rest_check_truncation(REQUEST *request, const char *raw,
938 vp_prints_value(cooked, sizeof(cooked), vp, 0);
939 if (strcmp(raw, cooked) != 0) {
940 RDEBUG("WARNING: Value-Pair does not match POST value, "
941 "truncation may have occurred");
942 RDEBUG("\tValue (pair) : \"%s\"", cooked);
943 RDEBUG("\tValue (post) : \"%s\"", raw);
947 /** Converts POST response into VALUE_PAIRs and adds them to the request
949 * Accepts VALUE_PAIRS in the same format as rest_encode_post, but with the
950 * addition of optional attribute list qualifiers as part of the attribute name
953 * If no qualifiers are specified, will default to the request list.
955 * POST response format is:
956 * @verbatim [outer.][<list>:]<attribute0>=<value0>&[outer.][<list>:]<attribute1>=<value1>&[outer.][<list>:]<attributeN>=<valueN> @endverbatim
958 * @see rest_encode_post
960 * @param[in] instance configuration data.
961 * @param[in] section configuration data.
962 * @param[in] handle rlm_rest_handle_t to use.
963 * @param[in] request Current request.
964 * @param[in] raw buffer containing POST data.
965 * @param[in] rawlen Length of data in raw buffer.
966 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
968 static int rest_decode_post(rlm_rest_t *instance,
969 UNUSED rlm_rest_section_t *section,
970 REQUEST *request, void *handle, char *raw,
971 UNUSED size_t rawlen)
973 rlm_rest_handle_t *randle = handle;
974 CURL *candle = randle->handle;
976 const char *p = raw, *q;
978 const char *attribute;
985 const DICT_ATTR **current, *processed[REST_BODY_MAX_ATTRS + 1];
989 REQUEST *reference = request;
993 int curl_len; /* Length from last curl_easy_unescape call */
1002 while (isspace(*p)) p++;
1004 if (p == NULL) return FALSE;
1006 while (((q = strchr(p, '=')) != NULL) &&
1007 (count < REST_BODY_MAX_ATTRS)) {
1009 reference = request;
1011 name = curl_easy_unescape(candle, p, (q - p), &curl_len);
1014 RDEBUG("Decoding attribute \"%s\"", name);
1016 if (!radius_ref_request(&reference, &attribute)) {
1017 RDEBUG("WARNING: Attribute name refers to outer request"
1018 " but not in a tunnel, skipping");
1025 list = radius_list_name(&attribute, PAIR_LIST_REPLY);
1026 if (list == PAIR_LIST_UNKNOWN) {
1027 RDEBUG("WARNING: Invalid list qualifier, skipping");
1034 da = dict_attrbyname(attribute);
1036 RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
1044 vps = radius_list(reference, list);
1048 RDEBUG2("\tType : %s", fr_int2str(dict_attr_types, da->type,
1052 len = (q == NULL) ? (rawlen - (p - raw)) : (unsigned)(q - p);
1054 value = curl_easy_unescape(candle, p, len, &curl_len);
1057 * If we found a delimiter we want to skip over it,
1058 * if we didn't we do *NOT* want to skip over the end
1061 p += (q == NULL) ? len : (len + 1);
1063 RDEBUG2("\tLength : %i", curl_len);
1064 RDEBUG2("\tValue : \"%s\"", value);
1066 vp = paircreate(da->attr, da->vendor, da->type);
1068 radlog(L_ERR, "rlm_rest (%s): Failed creating"
1069 " value-pair", instance->xlat_name);
1074 vp->operator = T_OP_SET;
1077 * Check to see if we've already processed an
1078 * attribute of the same type if we have, change the op
1079 * from T_OP_ADD to T_OP_SET.
1081 current = processed;
1082 while (*current++) {
1083 if ((current[0]->attr == da->attr) &&
1084 (current[0]->vendor == da->vendor)) {
1085 vp->operator = T_OP_ADD;
1090 if (vp->operator != T_OP_ADD) {
1095 tmp = pairparsevalue(vp, value);
1097 RDEBUG("Incompatible value assignment, skipping");
1103 rest_check_truncation(request, value, vp);
1105 vp->flags.do_xlat = 1;
1107 RDEBUG("Performing xlat expansion of response value", value);
1108 pairxlatmove(request, vps, &vp);
1110 if (++count == REST_BODY_MAX_ATTRS) {
1111 radlog(L_ERR, "rlm_rest (%s): At maximum"
1112 " attribute limit", instance->xlat_name);
1132 radlog(L_ERR, "rlm_rest (%s): Malformed POST data \"%s\"",
1133 instance->xlat_name, raw);
1140 /** Converts JSON "value" key into VALUE_PAIR.
1142 * If leaf is not in fact a leaf node, but contains JSON data, the data will
1143 * written to the attribute in JSON string format.
1145 * @param[in] instance configuration data.
1146 * @param[in] section configuration data.
1147 * @param[in] request Current request.
1148 * @param[in] attribute name without qualifiers.
1149 * @param[in] flags containing the operator other flags controlling value
1151 * @param[in] leaf object containing the VALUE_PAIR value.
1152 * @return The VALUE_PAIR just created, or NULL on error.
1154 static VALUE_PAIR *json_pairmake_leaf(rlm_rest_t *instance,
1155 UNUSED rlm_rest_section_t *section,
1156 REQUEST *request, const DICT_ATTR *da,
1157 json_flags_t *flags, json_object *leaf)
1160 VALUE_PAIR *vp, *tmp;
1163 * Should encode any nested JSON structures into JSON strings.
1165 * "I knew you liked JSON so I put JSON in your JSON!"
1167 value = json_object_get_string(leaf);
1169 RDEBUG2("\tType : %s", fr_int2str(dict_attr_types, da->type,
1171 RDEBUG2("\tLength : %i", strlen(value));
1172 RDEBUG2("\tValue : \"%s\"", value);
1174 vp = paircreate(da->attr, da->vendor, da->type);
1176 radlog(L_ERR, "rlm_rest (%s): Failed creating value-pair",
1177 instance->xlat_name);
1181 vp->operator = flags->operator;
1183 tmp = pairparsevalue(vp, value);
1185 RDEBUG("Incompatible value assignment, skipping");
1191 rest_check_truncation(request, value, vp);
1193 if (flags->do_xlat) vp->flags.do_xlat = 1;
1198 /** Processes JSON response and converts it into multiple VALUE_PAIRs
1200 * Processes JSON attribute declarations in the format below. Will recurse when
1201 * processing nested attributes. When processing nested attributes flags and
1202 * operators from previous attributes are not inherited.
1204 * JSON response format is:
1211 "value":[<value0>,<value1>,<valueN>]
1215 "<nested-attribute0>":{
1221 "<attribute2>":"<value0>",
1222 "<attributeN>":"[<value0>,<value1>,<valueN>]"
1226 * JSON valuepair flags (bools):
1227 * - do_xlat (optional) Controls xlat expansion of values. Defaults to TRUE.
1228 * - is_json (optional) If TRUE, any nested JSON data will be copied to the
1229 * VALUE_PAIR in string form. Defaults to TRUE.
1230 * - op (optional) Controls how the attribute is inserted into
1231 * the target list. Defaults to ':=' (T_OP_SET).
1233 * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
1234 * second and subsequent values in multivalued attributes. This does not work
1235 * between multiple attribute declarations.
1239 * @param[in] instance configuration data.
1240 * @param[in] section configuration data.
1241 * @param[in] request Current request.
1242 * @param[in] object containing root node, or parent node.
1243 * @param[in] level Current nesting level.
1244 * @param[in] max_attrs counter, decremented after each VALUE_PAIR is created,
1245 * when 0 no more attributes will be processed.
1246 * @return VALUE_PAIR or NULL on error.
1248 static VALUE_PAIR *json_pairmake(rlm_rest_t *instance,
1249 UNUSED rlm_rest_section_t *section,
1250 REQUEST *request, json_object *object,
1251 int level, int *max_attrs)
1256 const char *name, *attribute;
1258 struct json_object *value, *idx, *tmp;
1259 struct lh_entry *entry;
1262 const DICT_ATTR *da;
1266 REQUEST *reference = request;
1271 if (!json_object_is_type(object, json_type_object)) {
1272 RDEBUG("Can't process VP container, expected JSON object,"
1273 " got \"%s\", skipping",
1274 json_object_get_type(object));
1279 * Process VP container
1281 entry = json_object_get_object(object)->head;
1283 flags.operator = T_OP_SET;
1287 name = (char*)entry->k;
1289 /* Fix the compiler warnings regarding const... */
1290 memcpy(&value, &entry->v, sizeof(value));
1292 entry = entry->next;
1295 * For people handcrafting JSON responses
1298 while ((p = q = strchr(p, '|'))) {
1304 reference = request;
1307 * Resolve attribute name to a dictionary entry and
1310 RDEBUG2("Decoding attribute \"%s\"", name);
1312 if (!radius_ref_request(&reference, &attribute)) {
1313 RDEBUG("WARNING: Attribute name refers to outer request"
1314 " but not in a tunnel, skipping");
1319 list = radius_list_name(&attribute, PAIR_LIST_REPLY);
1320 if (list == PAIR_LIST_UNKNOWN) {
1321 RDEBUG("WARNING: Invalid list qualifier, skipping");
1326 da = dict_attrbyname(attribute);
1328 RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
1334 vps = radius_list(reference, list);
1339 * Alternate JSON structure that allows operator,
1340 * and other flags to be specified.
1350 * - [] Multivalued array
1351 * - {} Nested Valuepair
1352 * - * Integer or string value
1354 if (json_object_is_type(value, json_type_object)) {
1356 * Process operator if present.
1358 tmp = json_object_object_get(value, "op");
1360 flags.operator = fr_str2int(fr_tokens,
1361 json_object_get_string(tmp), 0);
1363 if (!flags.operator) {
1364 RDEBUG("Invalid operator value \"%s\","
1371 * Process optional do_xlat bool.
1373 tmp = json_object_object_get(value, "do_xlat");
1375 flags.do_xlat = json_object_get_boolean(tmp);
1379 * Process optional is_json bool.
1381 tmp = json_object_object_get(value, "is_json");
1383 flags.is_json = json_object_get_boolean(tmp);
1387 * Value key must be present if were using
1388 * the expanded syntax.
1390 value = json_object_object_get(value, "value");
1392 RDEBUG("Value key missing, skipping", value);
1398 * Setup pairmake / recursion loop.
1400 if (!flags.is_json &&
1401 json_object_is_type(value, json_type_array)) {
1402 len = json_object_array_length(value);
1404 RDEBUG("Zero length value array, skipping", value);
1407 idx = json_object_array_get_idx(value, 0);
1415 if (!(*max_attrs)--) {
1416 radlog(L_ERR, "rlm_rest (%s): At maximum"
1417 " attribute limit", instance->xlat_name);
1422 * Automagically switch the op for multivalued
1425 if (((flags.operator == T_OP_SET) ||
1426 (flags.operator == T_OP_EQ)) && (len > 1)) {
1427 flags.operator = T_OP_ADD;
1430 if (!flags.is_json &&
1431 json_object_is_type(value, json_type_object)) {
1432 /* TODO: Insert nested VP into VP structure...*/
1433 RDEBUG("Found nested VP", value);
1434 vp = json_pairmake(instance, section,
1436 level + 1, max_attrs);
1438 vp = json_pairmake_leaf(instance, section,
1439 request, da, &flags,
1443 if (vp->flags.do_xlat) {
1444 RDEBUG("Performing xlat"
1445 " expansion of response"
1449 pairxlatmove(request, vps, &vp);
1452 } while ((++i < len) && (idx = json_object_array_get_idx(value, i)));
1458 /** Converts JSON response into VALUE_PAIRs and adds them to the request.
1460 * Converts the raw JSON string into a json-c object tree and passes it to
1461 * json_pairmake. After the tree has been parsed json_object_put is called
1462 * which decrements the reference, count to the root node by one, and frees
1465 * @see rest_encode_json
1466 * @see json_pairmake
1468 * @param[in] instance configuration data.
1469 * @param[in] section configuration data.
1470 * @param[in] g to use.
1471 * @param[in] request Current request.
1472 * @param[in] raw buffer containing JSON data.
1473 * @param[in] rawlen Length of data in raw buffer.
1474 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
1476 static int rest_decode_json(rlm_rest_t *instance,
1477 UNUSED rlm_rest_section_t *section,
1478 UNUSED REQUEST *request, UNUSED void *handle,
1479 char *raw, UNUSED size_t rawlen)
1481 const char *p = raw;
1483 struct json_object *json;
1485 int max = REST_BODY_MAX_ATTRS;
1490 while (isspace(*p)) p++;
1491 if (p == NULL) return FALSE;
1493 json = json_tokener_parse(p);
1495 radlog(L_ERR, "rlm_rest (%s): Malformed JSON data \"%s\"",
1496 instance->xlat_name, raw);
1500 json_pairmake(instance, section, request, json, 0, &max);
1503 * Decrement reference count for root object, should free entire
1506 json_object_put(json);
1508 return (REST_BODY_MAX_ATTRS - max);
1511 /** Processes incoming HTTP header data from libcurl.
1513 * Processes the status line, and Content-Type headers from the incoming HTTP
1516 * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1519 * @param[in] ptr Char buffer where inbound header data is written.
1520 * @param[in] size Multiply by nmemb to get the length of ptr.
1521 * @param[in] nmemb Multiply by size to get the length of ptr.
1522 * @param[in] userdata rlm_rest_write_t to keep parsing state between calls.
1523 * @return Length of data processed, or 0 on error.
1525 static size_t rest_write_header(void *ptr, size_t size, size_t nmemb,
1528 rlm_rest_write_t *ctx = userdata;
1529 REQUEST *request = ctx->request; /* Used by RDEBUG */
1531 const char *p = ptr, *q;
1534 const size_t t = (size * nmemb);
1538 http_body_type_t type;
1539 http_body_type_t supp;
1543 case WRITE_STATE_INIT:
1544 RDEBUG("Processing header");
1547 * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1549 * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
1551 if (s < 14) goto malformed;
1554 * Check start of header matches...
1556 if (strncasecmp("HTTP/", p, 5) != 0) goto malformed;
1562 * Skip the version field, next space should mark start
1565 q = memchr(p, ' ', s);
1566 if (q == NULL) goto malformed;
1572 * Process reason_code.
1574 * " 100" (4) + "\r\n" (2) = 6
1576 if (s < 6) goto malformed;
1580 /* Char after reason code must be a space, or \r */
1581 if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
1583 ctx->code = atoi(p);
1586 * Process reason_phrase (if present).
1592 q = memchr(p, '\r', s);
1593 if (q == NULL) goto malformed;
1597 tmp = rad_malloc(len + 1);
1598 strlcpy(tmp, p, len + 1);
1600 RDEBUG("\tStatus : %i (%s)", ctx->code, tmp);
1604 RDEBUG("\tStatus : %i", ctx->code);
1607 ctx->state = WRITE_STATE_PARSE_HEADERS;
1611 case WRITE_STATE_PARSE_HEADERS:
1613 (strncasecmp("Content-Type: ", p, 14) == 0)) {
1618 * Check to see if there's a parameter
1621 q = memchr(p, ';', s);
1624 * If there's not, find the end of this
1627 if (q == NULL) q = memchr(p, '\r', s);
1629 len = (q == NULL) ? s : (unsigned)(q - p);
1631 type = fr_substr2int(http_content_type_table,
1632 p, HTTP_BODY_UNKNOWN,
1635 supp = http_body_type_supported[type];
1637 tmp = rad_malloc(len + 1);
1638 strlcpy(tmp, p, len + 1);
1640 RDEBUG("\tType : %s (%s)",
1641 fr_int2str(http_body_type_table, type,
1642 "¿Unknown?"), tmp);
1646 if (type == HTTP_BODY_UNKNOWN) {
1647 RDEBUG("Couldn't determine type, using"
1648 " request type \"%s\".",
1649 fr_int2str(http_body_type_table,
1653 } else if (supp == HTTP_BODY_UNSUPPORTED) {
1654 RDEBUG("Type \"%s\" is currently"
1656 fr_int2str(http_body_type_table,
1657 type, "¿Unknown?"));
1658 ctx->type = HTTP_BODY_UNSUPPORTED;
1660 } else if (supp == HTTP_BODY_INVALID) {
1661 RDEBUG("Type \"%s\" is not a valid web"
1662 " API data markup format",
1663 fr_int2str(http_body_type_table,
1664 type, "¿Unknown?"));
1666 ctx->type = HTTP_BODY_INVALID;
1668 } else if (type != ctx->type) {
1681 RDEBUG("Incoming header was malformed");
1687 /** Processes incoming HTTP body data from libcurl.
1689 * Writes incoming body data to an intermediary buffer for later parsing by
1690 * one of the decode functions.
1692 * @param[in] ptr Char buffer where inbound header data is written
1693 * @param[in] size Multiply by nmemb to get the length of ptr.
1694 * @param[in] nmemb Multiply by size to get the length of ptr.
1695 * @param[in] userdata rlm_rest_write_t to keep parsing state between calls.
1696 * @return length of data processed, or 0 on error.
1698 static size_t rest_write_body(void *ptr, size_t size, size_t nmemb,
1701 rlm_rest_write_t *ctx = userdata;
1702 REQUEST *request = ctx->request; /* Used by RDEBUG */
1704 const char *p = ptr;
1707 const size_t t = (size * nmemb);
1710 * Any post processing of headers should go here...
1712 if (ctx->state == WRITE_STATE_PARSE_HEADERS) {
1713 ctx->state = WRITE_STATE_PARSE_CONTENT;
1718 case HTTP_BODY_UNSUPPORTED:
1721 case HTTP_BODY_INVALID:
1722 tmp = rad_malloc(t + 1);
1723 strlcpy(tmp, p, t + 1);
1732 if (t > (ctx->alloc - ctx->used)) {
1733 ctx->alloc += ((t + 1) > REST_BODY_INCR) ?
1734 t + 1 : REST_BODY_INCR;
1738 ctx->buffer = rad_malloc(ctx->alloc);
1740 /* If data has been written previously */
1742 strlcpy(ctx->buffer, tmp,
1747 strlcpy(ctx->buffer + ctx->used, p, t + 1);
1756 /** (Re-)Initialises the data in a rlm_rest_write_t.
1758 * This resets the values of the a rlm_rest_write_t to their defaults.
1759 * Must be called between encoding sessions.
1761 * @see rest_write_body
1762 * @see rest_write_header
1764 * @param[in] request Current request.
1765 * @param[in] data to initialise.
1766 * @param[in] type Default http_body_type to use when decoding raw data, may be
1767 * overwritten by rest_write_header.
1769 static void rest_write_ctx_init(REQUEST *request, rlm_rest_write_t *ctx,
1770 http_body_type_t type)
1772 ctx->request = request;
1774 ctx->state = WRITE_STATE_INIT;
1780 /** Frees the intermediary buffer created by rest_write.
1782 * @param[in] data to be freed.
1784 static void rest_write_free(rlm_rest_write_t *ctx)
1786 if (ctx->buffer != NULL) {
1791 /** Configures body specific curlopts.
1793 * Configures libcurl handle to use either chunked mode, where the request
1794 * data will be sent using multiple HTTP requests, or contiguous mode where
1795 * the request data will be sent in a single HTTP request.
1797 * @param[in] instance configuration data.
1798 * @param[in] section configuration data.
1799 * @param[in] handle rlm_rest_handle_t to configure.
1800 * @param[in] func to pass to libcurl for chunked.
1801 * transfers (NULL if not using chunked mode).
1802 * @return TRUE on success FALSE on error.
1804 static int rest_request_config_body(rlm_rest_t *instance,
1805 rlm_rest_section_t *section,
1806 rlm_rest_handle_t *handle,
1809 rlm_rest_curl_context_t *ctx = handle->ctx;
1810 CURL *candle = handle->handle;
1815 if (section->chunk > 0) {
1816 ret = curl_easy_setopt(candle, CURLOPT_READDATA,
1818 if (ret != CURLE_OK) goto error;
1820 ret = curl_easy_setopt(candle, CURLOPT_READFUNCTION,
1822 if (ret != CURLE_OK) goto error;
1824 len = rest_read_wrapper(&ctx->body, func,
1825 REST_BODY_MAX_LEN , &ctx->read);
1827 radlog(L_ERR, "rlm_rest (%s): Failed creating HTTP"
1828 " body content", instance->xlat_name);
1832 ret = curl_easy_setopt(candle, CURLOPT_POSTFIELDS,
1834 if (ret != CURLE_OK) goto error;
1836 ret = curl_easy_setopt(candle, CURLOPT_POSTFIELDSIZE,
1838 if (ret != CURLE_OK) goto error;
1844 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
1845 instance->xlat_name, ret, curl_easy_strerror(ret));
1850 /** Configures request curlopts.
1852 * Configures libcurl handle setting various curlopts for things like local
1853 * client time, Content-Type, and other FreeRADIUS custom headers.
1855 * Current FreeRADIUS custom headers are:
1856 * - X-FreeRADIUS-Section The module section being processed.
1857 * - X-FreeRADIUS-Server The current virtual server the REQUEST is
1860 * Sets up callbacks for all response processing (buffers and body data).
1862 * @param[in] instance configuration data.
1863 * @param[in] section configuration data.
1864 * @param[in] handle to configure.
1865 * @param[in] request Current request.
1866 * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1867 * @param[in] type Content-Type for request encoding, also sets the default
1869 * @param[in] uri buffer containing the expanded URI to send the request to.
1870 * @return TRUE on success (all opts configured) FALSE on error.
1872 int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
1873 REQUEST *request, void *handle, http_method_t method,
1874 http_body_type_t type, char *uri)
1876 rlm_rest_handle_t *randle = handle;
1877 rlm_rest_curl_context_t *ctx = randle->ctx;
1878 CURL *candle = randle->handle;
1880 http_auth_type_t auth = section->auth;
1887 buffer[(sizeof(buffer) - 1)] = '\0';
1890 * Setup any header options and generic headers.
1892 ret = curl_easy_setopt(candle, CURLOPT_URL, uri);
1893 if (ret != CURLE_OK) goto error;
1895 ret = curl_easy_setopt(candle, CURLOPT_USERAGENT, "FreeRADIUS");
1896 if (ret != CURLE_OK) goto error;
1898 snprintf(buffer, (sizeof(buffer) - 1), "Content-Type: %s",
1899 fr_int2str(http_content_type_table, type, "¿Unknown?"));
1900 ctx->headers = curl_slist_append(ctx->headers, buffer);
1901 if (!ctx->headers) goto error_header;
1903 if (section->timeout) {
1904 ret = curl_easy_setopt(candle, CURLOPT_TIMEOUT,
1906 if (ret != CURLE_OK) goto error;
1909 ret = curl_easy_setopt(candle, CURLOPT_PROTOCOLS,
1910 (CURLPROTO_HTTP | CURLPROTO_HTTPS));
1911 if (ret != CURLE_OK) goto error;
1914 * FreeRADIUS custom headers
1916 snprintf(buffer, (sizeof(buffer) - 1), "X-FreeRADIUS-Section: %s",
1918 ctx->headers = curl_slist_append(ctx->headers, buffer);
1919 if (!ctx->headers) goto error_header;
1921 snprintf(buffer, (sizeof(buffer) - 1), "X-FreeRADIUS-Server: %s",
1923 ctx->headers = curl_slist_append(ctx->headers, buffer);
1924 if (!ctx->headers) goto error_header;
1927 * Configure HTTP verb (GET, POST, PUT, DELETE, other...)
1931 case HTTP_METHOD_GET :
1932 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1934 if (ret != CURLE_OK) goto error;
1938 case HTTP_METHOD_POST :
1939 ret = curl_easy_setopt(candle, CURLOPT_POST,
1941 if (ret != CURLE_OK) goto error;
1945 case HTTP_METHOD_PUT :
1946 ret = curl_easy_setopt(candle, CURLOPT_PUT,
1948 if (ret != CURLE_OK) goto error;
1952 case HTTP_METHOD_DELETE :
1953 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1955 if (ret != CURLE_OK) goto error;
1957 ret = curl_easy_setopt(candle,
1958 CURLOPT_CUSTOMREQUEST, "DELETE");
1959 if (ret != CURLE_OK) goto error;
1963 case HTTP_METHOD_CUSTOM :
1964 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1966 if (ret != CURLE_OK) goto error;
1968 ret = curl_easy_setopt(candle,
1969 CURLOPT_CUSTOMREQUEST,
1971 if (ret != CURLE_OK) goto error;
1979 * Set user based authentication parameters
1982 if ((auth >= HTTP_AUTH_BASIC) &&
1983 (auth <= HTTP_AUTH_ANY_SAFE)) {
1984 ret = curl_easy_setopt(candle, CURLOPT_HTTPAUTH,
1985 http_curl_auth[auth]);
1986 if (ret != CURLE_OK) goto error;
1988 if (section->username) {
1989 radius_xlat(buffer, sizeof(buffer),
1990 section->username, request, NULL);
1992 ret = curl_easy_setopt(candle, CURLOPT_USERNAME,
1994 if (ret != CURLE_OK) goto error;
1996 if (section->password) {
1997 radius_xlat(buffer, sizeof(buffer),
1998 section->password, request, NULL);
2000 ret = curl_easy_setopt(candle, CURLOPT_PASSWORD,
2002 if (ret != CURLE_OK) goto error;
2005 #ifdef CURLOPT_TLSAUTH_USERNAME
2006 } else if (type == HTTP_AUTH_TLS_SRP) {
2007 ret = curl_easy_setopt(candle, CURLOPT_TLSAUTH_TYPE,
2008 http_curl_auth[auth]);
2010 if (section->username) {
2011 radius_xlat(buffer, sizeof(buffer),
2012 section->username, request, NULL);
2014 ret = curl_easy_setopt(candle,
2015 CURLOPT_TLSAUTH_USERNAME,
2017 if (ret != CURLE_OK) goto error;
2019 if (section->password) {
2020 radius_xlat(buffer, sizeof(buffer),
2021 section->password, request, NULL);
2023 ret = curl_easy_setopt(candle,
2024 CURLOPT_TLSAUTH_PASSWORD,
2026 if (ret != CURLE_OK) goto error;
2033 * Set SSL authentication parameters
2035 if (section->certificate_file) {
2036 ret = curl_easy_setopt(candle,
2038 section->certificate_file);
2039 if (ret != CURLE_OK) goto error;
2042 if (section->file_type == FALSE) {
2043 ret = curl_easy_setopt(candle,
2046 if (ret != CURLE_OK) goto error;
2049 if (section->private_key_file) {
2050 ret = curl_easy_setopt(candle,
2052 section->private_key_file);
2053 if (ret != CURLE_OK) goto error;
2056 if (section->private_key_password) {
2057 ret = curl_easy_setopt(candle,
2059 section->private_key_password);
2060 if (ret != CURLE_OK) goto error;
2063 if (section->ca_file) {
2064 ret = curl_easy_setopt(candle,
2067 if (ret != CURLE_OK) goto error;
2070 if (section->ca_path) {
2071 ret = curl_easy_setopt(candle,
2074 if (ret != CURLE_OK) goto error;
2077 if (section->random_file) {
2078 ret = curl_easy_setopt(candle,
2079 CURLOPT_RANDOM_FILE,
2080 section->random_file);
2081 if (ret != CURLE_OK) goto error;
2084 ret = curl_easy_setopt(candle,
2085 CURLOPT_SSL_VERIFYHOST,
2086 (section->check_cert_cn == TRUE) ?
2088 if (ret != CURLE_OK) goto error;
2091 * Tell CURL how to get HTTP body content, and how to process
2094 rest_write_ctx_init(request, &ctx->write, type);
2096 ret = curl_easy_setopt(candle, CURLOPT_HEADERFUNCTION,
2098 if (ret != CURLE_OK) goto error;
2100 ret = curl_easy_setopt(candle, CURLOPT_HEADERDATA,
2102 if (ret != CURLE_OK) goto error;
2104 ret = curl_easy_setopt(candle, CURLOPT_WRITEFUNCTION,
2106 if (ret != CURLE_OK) goto error;
2108 ret = curl_easy_setopt(candle, CURLOPT_WRITEDATA,
2110 if (ret != CURLE_OK) goto error;
2114 case HTTP_METHOD_GET :
2115 case HTTP_METHOD_DELETE :
2119 case HTTP_METHOD_POST :
2120 case HTTP_METHOD_PUT :
2121 case HTTP_METHOD_CUSTOM :
2122 if (section->chunk > 0) {
2123 ctx->read.chunk = section->chunk;
2125 ctx->headers = curl_slist_append(ctx->headers,
2127 if (!ctx->headers) goto error_header;
2129 ctx->headers = curl_slist_append(ctx->headers,
2130 "Transfer-Encoding: chunked");
2131 if (!ctx->headers) goto error_header;
2136 case HTTP_BODY_JSON:
2137 rest_read_ctx_init(request,
2140 ret = rest_request_config_body(instance,
2144 if (!ret) return -1;
2148 case HTTP_BODY_POST:
2149 rest_read_ctx_init(request,
2152 ret = rest_request_config_body(instance,
2156 if (!ret) return -1;
2164 ret = curl_easy_setopt(candle, CURLOPT_HTTPHEADER,
2166 if (ret != CURLE_OK) goto error;
2177 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
2178 instance->xlat_name, ret, curl_easy_strerror(ret));
2182 radlog(L_ERR, "rlm_rest (%s): Failed creating header",
2183 instance->xlat_name);
2187 /** Sends a REST (HTTP) request.
2189 * Send the actual REST request to the server. The response will be handled by
2190 * the numerous callbacks configured in rest_request_config.
2192 * @param[in] instance configuration data.
2193 * @param[in] section configuration data.
2194 * @param[in] handle to use.
2195 * @return TRUE on success or FALSE on error.
2197 int rest_request_perform(rlm_rest_t *instance,
2198 UNUSED rlm_rest_section_t *section, void *handle)
2200 rlm_rest_handle_t *randle = handle;
2201 CURL *candle = randle->handle;
2204 ret = curl_easy_perform(candle);
2205 if (ret != CURLE_OK) {
2206 radlog(L_ERR, "rlm_rest (%s): Request failed: %i - %s",
2207 instance->xlat_name, ret, curl_easy_strerror(ret));
2214 /** Sends the response to the correct decode function.
2216 * Uses the Content-Type information written in rest_write_header to
2217 * determine the correct decode function to use. The decode function will
2218 * then convert the raw received data into VALUE_PAIRs.
2220 * @param[in] instance configuration data.
2221 * @param[in] section configuration data.
2222 * @param[in] request Current request.
2223 * @param[in] handle to use.
2224 * @return TRUE on success or FALSE on error.
2226 int rest_request_decode(rlm_rest_t *instance,
2227 UNUSED rlm_rest_section_t *section,
2228 REQUEST *request, void *handle)
2230 rlm_rest_handle_t *randle = handle;
2231 rlm_rest_curl_context_t *ctx = randle->ctx;
2235 if (ctx->write.buffer == NULL) {
2236 RDEBUG("Skipping attribute processing, no body data received");
2240 RDEBUG("Processing body", ret);
2242 switch (ctx->write.type)
2244 case HTTP_BODY_POST:
2245 ret = rest_decode_post(instance, section, request,
2246 handle, ctx->write.buffer,
2250 case HTTP_BODY_JSON:
2251 ret = rest_decode_json(instance, section, request,
2252 handle, ctx->write.buffer,
2256 case HTTP_BODY_UNSUPPORTED:
2257 case HTTP_BODY_INVALID:
2267 /** Cleans up after a REST request.
2269 * Resets all options associated with a CURL handle, and frees any headers
2270 * associated with it.
2272 * Calls rest_read_ctx_free and rest_write_free to free any memory used by
2275 * @param[in] instance configuration data.
2276 * @param[in] section configuration data.
2277 * @param[in] handle to cleanup.
2278 * @return TRUE on success or FALSE on error.
2280 void rest_request_cleanup(UNUSED rlm_rest_t *instance,
2281 UNUSED rlm_rest_section_t *section, void *handle)
2283 rlm_rest_handle_t *randle = handle;
2284 rlm_rest_curl_context_t *ctx = randle->ctx;
2285 CURL *candle = randle->handle;
2288 * Clear any previously configured options
2290 curl_easy_reset(candle);
2295 if (ctx->headers != NULL) {
2296 curl_slist_free_all(ctx->headers);
2297 ctx->headers = NULL;
2301 * Free body data (only used if chunking is disabled)
2303 if (ctx->body != NULL) free(ctx->body);
2306 * Free other context info
2308 rest_read_ctx_free(&ctx->read);
2309 rest_write_free(&ctx->write);
2312 /** URL encodes a string.
2314 * Encode special chars as per RFC 3986 section 4.
2316 * @param[out] out Where to write escaped string.
2317 * @param[in] outlen Size of out buffer.
2318 * @param[in] raw string to be urlencoded.
2319 * @return length of data written to out (excluding NULL).
2321 static size_t rest_uri_escape(char *out, size_t outlen, const char *raw)
2325 escaped = curl_escape(raw, strlen(raw));
2326 strlcpy(out, escaped, outlen);
2332 /** Builds URI; performs XLAT expansions and encoding.
2334 * Splits the URI into "http://example.org" and "/%{xlat}/query/?bar=foo"
2335 * Both components are expanded, but values expanded for the second component
2336 * are also url encoded.
2338 * @param[in] instance configuration data.
2339 * @param[in] section configuration data.
2340 * @param[in] request Current request
2341 * @param[out] buffer to write expanded URI to.
2342 * @param[in] bufsize Size of buffer.
2343 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2346 ssize_t rest_uri_build(rlm_rest_t *instance, rlm_rest_section_t *section,
2347 REQUEST *request, char *buffer, size_t bufsize)
2354 unsigned short count = 0;
2360 while ((q = strchr(p, '/')) && (count++ < 3)) p = (q + 1);
2363 radlog(L_ERR, "rlm_rest (%s): Error URI is malformed,"
2364 " can't find start of path", instance->xlat_name);
2370 scheme = rad_malloc(len + 1);
2371 strlcpy(scheme, section->uri, len + 1);
2376 out += radius_xlat(out, bufsize, scheme, request, NULL);
2380 out += radius_xlat(out, (bufsize - (buffer - out)), path, request,
2383 return (buffer - out);