2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief Functions and datatypes for the REST (HTTP) transport.
23 * @copyright 2012-2013 Arran Cudbard-Bell <a.cudbard-bell@freeradius.org>
26 #include <freeradius-devel/ident.h>
34 #include <freeradius-devel/radiusd.h>
35 #include <freeradius-devel/libradius.h>
36 #include <freeradius-devel/connection.h>
40 /** Table of encoder/decoder support.
42 * Indexes in this table match the http_body_type_t enum, and should be
43 * updated if additional enum values are added.
45 * @see http_body_type_t
47 const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES] = {
48 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNKOWN
49 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNSUPPORTED
50 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_INVALID
51 HTTP_BODY_POST, // HTTP_BODY_POST
53 HTTP_BODY_JSON, // HTTP_BODY_JSON
55 HTTP_BODY_UNAVAILABLE,
57 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_XML
58 HTTP_BODY_UNSUPPORTED, // HTTP_BODY_YAML
59 HTTP_BODY_INVALID, // HTTP_BODY_HTML
60 HTTP_BODY_INVALID // HTTP_BODY_PLAIN
64 * Lib CURL doesn't define symbols for unsupported auth methods
66 #ifndef CURLOPT_TLSAUTH_SRP
67 #define CURLOPT_TLSAUTH_SRP 0
69 #ifndef CURLAUTH_BASIC
70 #define CURLAUTH_BASIC 0
72 #ifndef CURLAUTH_DIGEST
73 #define CURLAUTH_DIGEST 0
75 #ifndef CURLAUTH_DIGEST_IE
76 #define CURLAUTH_DIGEST_IE 0
78 #ifndef CURLAUTH_GSSNEGOTIATE
79 #define CURLAUTH_GSSNEGOTIATE 0
82 #define CURLAUTH_NTLM 0
84 #ifndef CURLAUTH_NTLM_WB
85 #define CURLAUTH_NTLM_WB 0
88 const http_body_type_t http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = {
89 0, // HTTP_AUTH_UNKNOWN
91 CURLOPT_TLSAUTH_SRP, // HTTP_AUTH_TLS_SRP
92 CURLAUTH_BASIC, // HTTP_AUTH_BASIC
93 CURLAUTH_DIGEST, // HTTP_AUTH_DIGEST
94 CURLAUTH_DIGEST_IE, // HTTP_AUTH_DIGEST_IE
95 CURLAUTH_GSSNEGOTIATE, // HTTP_AUTH_GSSNEGOTIATE
96 CURLAUTH_NTLM, // HTTP_AUTH_NTLM
97 CURLAUTH_NTLM_WB, // HTTP_AUTH_NTLM_WB
98 CURLAUTH_ANY, // HTTP_AUTH_ANY
99 CURLAUTH_ANYSAFE // HTTP_AUTH_ANY_SAFE
103 /** Conversion table for method config values.
105 * HTTP verb strings for http_method_t enum values. Used by libcurl in the
106 * status line of the outgoing HTTP header, by rest_write_header for decoding
107 * incoming HTTP responses, and by the configuration parser.
113 const FR_NAME_NUMBER http_method_table[] = {
114 { "GET", HTTP_METHOD_GET },
115 { "POST", HTTP_METHOD_POST },
116 { "PUT", HTTP_METHOD_PUT },
117 { "DELETE", HTTP_METHOD_DELETE },
122 /** Conversion table for type config values.
124 * Textual names for http_body_type_t enum values, used by the
125 * configuration parser.
127 * @see http_body_Type_t
131 const FR_NAME_NUMBER http_body_type_table[] = {
132 { "unknown", HTTP_BODY_UNKNOWN },
133 { "unsupported", HTTP_BODY_UNSUPPORTED },
134 { "unavailable", HTTP_BODY_UNAVAILABLE },
135 { "invalid", HTTP_BODY_INVALID },
136 { "post", HTTP_BODY_POST },
137 { "json", HTTP_BODY_JSON },
138 { "xml", HTTP_BODY_XML },
139 { "yaml", HTTP_BODY_YAML },
140 { "html", HTTP_BODY_HTML },
141 { "plain", HTTP_BODY_PLAIN },
146 const FR_NAME_NUMBER http_auth_table[] = {
147 { "none", HTTP_AUTH_NONE },
148 { "srp", HTTP_AUTH_TLS_SRP },
149 { "basic", HTTP_AUTH_BASIC },
150 { "digest", HTTP_AUTH_DIGEST },
151 { "digest-ie", HTTP_AUTH_DIGEST_IE },
152 { "gss-negotiate", HTTP_AUTH_GSSNEGOTIATE },
153 { "ntlm", HTTP_AUTH_NTLM },
154 { "ntlm-winbind", HTTP_AUTH_NTLM_WB },
155 { "any", HTTP_AUTH_ANY },
156 { "safe", HTTP_AUTH_ANY_SAFE },
161 /** Conversion table for "Content-Type" header values.
163 * Used by rest_write_header for parsing incoming headers.
165 * Values we expect to see in the 'Content-Type:' header of the incoming
168 * Some data types (like YAML) do no have standard MIME types defined,
169 * so multiple types, are listed here.
171 * @see http_body_Type_t
175 const FR_NAME_NUMBER http_content_type_table[] = {
176 { "application/x-www-form-urlencoded", HTTP_BODY_POST },
177 { "application/json", HTTP_BODY_JSON },
178 { "text/html", HTTP_BODY_HTML },
179 { "text/plain", HTTP_BODY_PLAIN },
180 { "text/xml", HTTP_BODY_XML },
181 { "text/yaml", HTTP_BODY_YAML },
182 { "text/x-yaml", HTTP_BODY_YAML },
183 { "application/yaml", HTTP_BODY_YAML },
184 { "application/x-yaml", HTTP_BODY_YAML },
189 /** Flags to control the conversion of JSON values to VALUE_PAIRs.
191 * These fields are set when parsing the expanded format for value pairs in
192 * JSON, and control how json_pairmake_leaf and json_pairmake convert the JSON
193 * value, and move the new VALUE_PAIR into an attribute list.
196 * @see json_pairmake_leaf
198 typedef struct json_flags {
199 boolean do_xlat; //!< If TRUE value will be expanded with xlat.
200 boolean is_json; //!< If TRUE value will be inserted as raw JSON
201 // (multiple values not supported).
202 FR_TOKEN op; //!< The operator that determines how the new VP
203 // is processed. @see fr_tokens
207 /** Initialises libcurl.
209 * Allocates global variables and memory required for libcurl to fundtion.
210 * MUST only be called once per module instance.
212 * rest_cleanup must not be called if rest_init fails.
216 * @param[in] instance configuration data.
217 * @return TRUE if init succeeded FALSE if it failed.
219 int rest_init(rlm_rest_t *instance)
223 ret = curl_global_init(CURL_GLOBAL_ALL);
224 if (ret != CURLE_OK) {
226 "rlm_rest (%s): CURL init returned error: %i - %s",
228 ret, curl_easy_strerror(ret));
230 curl_global_cleanup();
234 radlog(L_DBG, "rlm_rest (%s): CURL library version: %s",
241 /** Cleans up after libcurl.
243 * Wrapper around curl_global_cleanup, frees any memory allocated by rest_init.
244 * Must only be called once per call of rest_init.
248 void rest_cleanup(void)
250 curl_global_cleanup();
253 /** Creates a new connection handle for use by the FR connection API.
255 * Matches the fr_connection_create_t function prototype, is passed to
256 * fr_connection_pool_init, and called when a new connection is required by the
257 * connection pool API.
259 * Creates an instances of rlm_rest_handle_t, and rlm_rest_curl_context_t
260 * which hold the context data required for generating requests and parsing
261 * responses. Calling rest_socket_delete will free this memory.
263 * If instance->connect_uri is not NULL libcurl will attempt to open a
264 * TCP socket to the server specified in the URI. This is done so that when the
265 * socket is first used, there will already be a cached TCP connection to the
266 * REST server associated with the curl handle.
268 * @see rest_socket_delete
269 * @see fr_connection_pool_init
270 * @see fr_connection_create_t
273 * @param[in] instance configuration data.
274 * @return connection handle or NULL if the connection failed or couldn't
277 void *rest_socket_create(void *instance)
279 rlm_rest_t *inst = instance;
281 rlm_rest_handle_t *randle;
282 rlm_rest_curl_context_t *ctx;
284 CURL *candle = curl_easy_init();
288 radlog(L_ERR, "rlm_rest (%s): Failed to create CURL handle",
293 if (!*inst->connect_uri) {
294 radlog(L_ERR, "rlm_rest (%s): Skipping pre-connect,"
295 " connect_uri not specified", inst->xlat_name);
300 * Pre-establish TCP connection to webserver. This would usually be
301 * done on the first request, but we do it here to minimise
304 ret = curl_easy_setopt(candle, CURLOPT_CONNECT_ONLY, 1);
305 if (ret != CURLE_OK) goto error;
307 ret = curl_easy_setopt(candle, CURLOPT_URL,
309 if (ret != CURLE_OK) goto error;
311 radlog(L_DBG, "rlm_rest (%s): Connecting to \"%s\"",
315 ret = curl_easy_perform(candle);
316 if (ret != CURLE_OK) {
317 radlog(L_ERR, "rlm_rest (%s): Connection failed: %i - %s",
319 ret, curl_easy_strerror(ret));
321 goto connection_error;
325 * Malloc memory for the connection handle abstraction.
327 randle = malloc(sizeof(*randle));
328 memset(randle, 0, sizeof(*randle));
330 ctx = malloc(sizeof(*ctx));
331 memset(ctx, 0, sizeof(*ctx));
333 ctx->headers = NULL; /* CURL needs this to be NULL */
334 ctx->read.instance = inst;
337 randle->handle = candle;
340 * Clear any previously configured options for the first request.
342 curl_easy_reset(candle);
347 * Cleanup for error conditions.
351 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
353 ret, curl_easy_strerror(ret));
356 * So we don't leak CURL handles.
360 curl_easy_cleanup(candle);
365 /** Verifies that the last TCP socket associated with a handle is still active.
367 * Quieries libcurl to try and determine if the TCP socket associated with a
368 * connection handle is still viable.
370 * @param[in] instance configuration data.
371 * @param[in] handle to check.
372 * @returns FALSE if the last socket is dead, or if the socket state couldn't be
373 * determined, else TRUE.
375 int rest_socket_alive(void *instance, void *handle)
377 rlm_rest_t *inst = instance;
378 rlm_rest_handle_t *randle = handle;
379 CURL *candle = randle->handle;
384 curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
385 if (ret != CURLE_OK) {
387 "rlm_rest (%s): Couldn't determine socket"
388 " state: %i - %s", inst->xlat_name, ret,
389 curl_easy_strerror(ret));
394 if (last_socket == -1) {
401 /** Frees a libcurl handle, and any additional memory used by context data.
403 * @param[in] instance configuration data.
404 * @param[in] handle rlm_rest_handle_t to close and free.
405 * @return returns TRUE.
407 int rest_socket_delete(UNUSED void *instance, void *handle)
409 rlm_rest_handle_t *randle = handle;
410 CURL *candle = randle->handle;
412 curl_easy_cleanup(candle);
420 /** Encodes VALUE_PAIR linked list in POST format
422 * This is a stream function matching the rest_read_t prototype. Multiple
423 * successive calls will return additional encoded VALUE_PAIRs.
424 * Only complete attribute headers @verbatim '<name>=' @endverbatim and values
425 * will be written to the ptr buffer.
427 * POST request format is:
428 * @verbatim <attribute0>=<value0>&<attribute1>=<value1>&<attributeN>=<valueN>@endverbatim
430 * All attributes and values are url encoded. There is currently no support for
431 * nested attributes, or attribute qualifiers.
433 * Nested attributes may be added in the future using
434 * @verbatim <attribute-outer>:<attribute-inner>@endverbatim
435 * to denotate nesting.
437 * Requires libcurl for url encoding.
439 * @see rest_decode_post
441 * @param[out] ptr Char buffer to write encoded data to.
442 * @param[in] size Multiply by nmemb to get the length of ptr.
443 * @param[in] nmemb Multiply by size to get the length of ptr.
444 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls.
445 * @return length of data (including NULL) written to ptr, or 0 if no more
448 static size_t rest_encode_post(void *ptr, size_t size, size_t nmemb,
451 rlm_rest_read_t *ctx = userdata;
452 REQUEST *request = ctx->request; /* Used by RDEBUG */
453 VALUE_PAIR **current = ctx->next;
455 char *p = ptr; /* Position in buffer */
456 char *f = ptr; /* Position in buffer of last fully encoded attribute or value */
457 char *escaped; /* Pointer to current URL escaped data */
460 ssize_t s = (size * nmemb) - 1;
462 /* Allow manual chunking */
463 if ((ctx->chunk) && (ctx->chunk <= s)) {
464 s = (ctx->chunk - 1);
467 if (ctx->state == READ_STATE_END) return FALSE;
469 /* Post data requires no headers */
470 if (ctx->state == READ_STATE_INIT) {
471 ctx->state = READ_STATE_ATTR_BEGIN;
476 ctx->state = READ_STATE_END;
481 RDEBUG2("Encoding attribute \"%s\"", current[0]->da->name);
483 if (ctx->state == READ_STATE_ATTR_BEGIN) {
484 escaped = curl_escape(current[0]->da->name,
485 strlen(current[0]->da->name));
486 len = strlen(escaped);
493 len = sprintf(p, "%s=", escaped);
501 * We wrote the attribute header, record progress.
504 ctx->state = READ_STATE_ATTR_CONT;
508 * Write out single attribute string.
510 len = vp_prints_value(p , s, current[0], 0);
511 escaped = curl_escape(p, len);
512 len = strlen(escaped);
519 len = strlcpy(p, escaped, len + 1);
523 RDEBUG("\tLength : %i", len);
524 RDEBUG("\tValue : %s", p);
530 if (!--s) goto no_space;
535 * We wrote one full attribute value pair, record progress.
539 ctx->state = READ_STATE_ATTR_BEGIN;
546 len = p - (char*)ptr;
548 RDEBUG2("POST Data: %s", (char*) ptr);
549 RDEBUG2("Returning %i bytes of POST data", len);
554 * Cleanup for error conditions
560 len = f - (char*)ptr;
562 RDEBUG2("POST Data: %s", (char*) ptr);
565 * The buffer wasn't big enough to encode a single attribute chunk.
568 radlog(L_ERR, "rlm_rest (%s): AVP exceeds buffer length"
569 " or chunk", ctx->instance->xlat_name);
571 RDEBUG2("Returning %i bytes of POST data"
572 " (buffer full or chunk exceeded)", len);
578 /** Encodes VALUE_PAIR linked list in JSON format
580 * This is a stream function matching the rest_read_t prototype. Multiple
581 * successive calls will return additional encoded VALUE_PAIRs.
583 * Only complete attribute headers
584 * @verbatim "<name>":{"type":"<type>","value":[' @endverbatim
585 * and complete attribute values will be written to ptr.
587 * If an attribute occurs multiple times in the request the attribute values
588 * will be concatenated into a single value array.
590 * JSON request format is:
595 "value":[<value0>,<value1>,<valueN>]
608 * @param[out] ptr Char buffer to write encoded data to.
609 * @param[in] size Multiply by nmemb to get the length of ptr.
610 * @param[in] nmemb Multiply by size to get the length of ptr.
611 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls.
612 * @return length of data (including NULL) written to ptr, or 0 if no more
615 static size_t rest_encode_json(void *ptr, size_t size, size_t nmemb,
618 rlm_rest_read_t *ctx = userdata;
619 REQUEST *request = ctx->request; /* Used by RDEBUG */
620 VALUE_PAIR **current = ctx->next;
622 char *p = ptr; /* Position in buffer */
623 char *f = ptr; /* Position in buffer of last fully encoded attribute or value */
628 ssize_t s = (size * nmemb) - 1;
632 /* Allow manual chunking */
633 if ((ctx->chunk) && (ctx->chunk <= s)) {
634 s = (ctx->chunk - 1);
637 if (ctx->state == READ_STATE_END) return FALSE;
639 if (ctx->state == READ_STATE_INIT) {
640 ctx->state = READ_STATE_ATTR_BEGIN;
642 if (!--s) goto no_space;
648 ctx->state = READ_STATE_END;
650 if (!--s) goto no_space;
657 * New attribute, write name, type, and beginning of
660 RDEBUG2("Encoding attribute \"%s\"", current[0]->da->name);
661 if (ctx->state == READ_STATE_ATTR_BEGIN) {
662 type = fr_int2str(dict_attr_types, current[0]->da->type,
666 len += strlen(current[0]->da->name);
668 if (s < (23 + len)) goto no_space;
670 len = sprintf(p, "\"%s\":{\"type\":\"%s\",\"value\":[" ,
671 current[0]->da->name, type);
675 RDEBUG2("\tType : %s", type);
678 * We wrote the attribute header, record progress
681 ctx->state = READ_STATE_ATTR_CONT;
685 * Put all attribute values in an array for easier remote
686 * parsing whether they're multivalued or not.
689 len = vp_prints_value_json(p , s, current[0]);
690 assert((s - len) >= 0);
692 if (len < 0) goto no_space;
695 * Show actual value length minus quotes
697 RDEBUG2("\tLength : %i", (*p == '"') ? (len - 2) : len);
698 RDEBUG2("\tValue : %s", p);
704 * Multivalued attribute
707 (current[0]->da == current[1]->da)) {
712 * We wrote one attribute value, record
722 if (!(s -= 2)) goto no_space;
727 if (!--s) goto no_space;
732 * We wrote one full attribute value pair, record progress.
736 ctx->state = READ_STATE_ATTR_BEGIN;
743 len = p - (char*)ptr;
745 RDEBUG2("JSON Data: %s", (char*) ptr);
746 RDEBUG2("Returning %i bytes of JSON data", len);
751 * Were out of buffer space
757 len = f - (char*)ptr;
759 RDEBUG2("JSON Data: %s", (char*) ptr);
762 * The buffer wasn't big enough to encode a single attribute chunk.
765 radlog(L_ERR, "rlm_rest (%s): AVP exceeds buffer length"
766 " or chunk", ctx->instance->xlat_name);
768 RDEBUG2("Returning %i bytes of JSON data"
769 " (buffer full or chunk exceeded)", len);
775 /** Emulates successive libcurl calls to an encoding function
777 * This function is used when the request will be sent to the HTTP server as one
778 * contiguous entity. A buffer of REST_BODY_INCR bytes is allocated and passed
779 * to the stream encoding function.
781 * If the stream function does not return 0, a new buffer is allocated which is
782 * the size of the previous buffer + REST_BODY_INCR bytes, the data from the
783 * previous buffer is copied, and freed, and another call is made to the stream
784 * function, passing a pointer into the new buffer at the end of the previously
787 * This process continues until the stream function signals (by returning 0)
788 * that it has no more data to write.
790 * @param[out] buffer where the pointer to the malloced buffer should
792 * @param[in] func Stream function.
793 * @param[in] limit Maximum buffer size to alloc.
794 * @param[in] userdata rlm_rest_read_t to keep encoding state between calls to
796 * @return the length of the data written to the buffer (excluding NULL) or -1
799 static ssize_t rest_read_wrapper(char **buffer, rest_read_t func,
800 size_t limit, void *userdata)
802 char *previous = NULL;
805 size_t alloc = REST_BODY_INCR; /* Size of buffer to malloc */
806 size_t used = 0; /* Size of data written */
809 while (alloc < limit) {
810 current = rad_malloc(alloc);
813 strlcpy(current, previous, used + 1);
817 len = func(current + used, REST_BODY_INCR, 1, userdata);
824 alloc += REST_BODY_INCR;
833 /** (Re-)Initialises the data in a rlm_rest_read_t.
835 * Resets the values of a rlm_rest_read_t to their defaults.
837 * Must be called between encoding sessions.
839 * As part of initialisation all VALUE_PAIR pointers in the REQUEST packet are
840 * written to an array.
842 * If sort is TRUE, this array of VALUE_PAIR pointers will be sorted by vendor
843 * and then by attribute. This is for stream encoders which may concatenate
844 * multiple attribute values together into an array.
846 * After the encoding session has completed this array must be freed by calling
847 * rest_read_ctx_free .
849 * @see rest_read_ctx_free
851 * @param[in] request Current request.
852 * @param[in] ctx to initialise.
853 * @param[in] sort If TRUE VALUE_PAIRs will be sorted within the VALUE_PAIR
856 static void rest_read_ctx_init(REQUEST *request,
857 rlm_rest_read_t *ctx,
860 unsigned short count = 0, i;
863 VALUE_PAIR **current, *tmp;
866 * Setup stream read data
868 ctx->request = request;
869 ctx->state = READ_STATE_INIT;
872 * Create sorted array of VP pointers
874 tmp = request->packet->vps;
875 while (tmp != NULL) {
880 ctx->first = current = rad_malloc((sizeof(tmp) * (count + 1)));
881 ctx->next = ctx->first;
883 tmp = request->packet->vps;
884 while (tmp != NULL) {
889 current = ctx->first;
891 if (!sort || (count < 2)) return;
893 /* TODO: Quicksort would be faster... */
895 for(i = 1; i < count; i++) {
897 if (current[i-1]->da > current[i]->da) {
899 current[i] = current[i-1];
907 /** Frees the VALUE_PAIR array created by rest_read_ctx_init.
909 * Must be called between encoding sessions else module will leak VALUE_PAIR
912 * @see rest_read_ctx_init
914 * @param[in] ctx to free.
916 static void rest_read_ctx_free(rlm_rest_read_t *ctx)
918 if (ctx->first != NULL) {
923 /** Verify that value wasn't truncated when it was converted to a VALUE_PAIR
925 * Certain values may be truncated when they're converted into VALUE_PAIRs
926 * for example 64bit integers converted to 32bit integers. Warn the user
929 * @param[in] request Current request.
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];
988 pair_lists_t list_name;
989 request_refs_t request_name;
990 REQUEST *reference = request;
994 int curl_len; /* Length from last curl_easy_unescape call */
1003 while (isspace(*p)) p++;
1004 if (*p == '\0') 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 request_name = radius_request_name(&attribute, REQUEST_CURRENT);
1017 if (request_name == REQUEST_UNKNOWN) {
1018 RDEBUG("WARNING: Invalid request qualifier, skipping");
1025 if (!radius_request(&reference, request_name)) {
1026 RDEBUG("WARNING: Attribute name refers to outer request"
1027 " but not in a tunnel, skipping");
1034 list_name = radius_list_name(&attribute, PAIR_LIST_REPLY);
1035 if (list_name == PAIR_LIST_UNKNOWN) {
1036 RDEBUG("WARNING: Invalid list qualifier, skipping");
1043 da = dict_attrbyname(attribute);
1045 RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
1053 vps = radius_list(reference, list_name);
1057 RDEBUG2("\tType : %s", fr_int2str(dict_attr_types, da->type,
1061 len = (q == NULL) ? (rawlen - (p - raw)) : (unsigned)(q - p);
1063 value = curl_easy_unescape(candle, p, len, &curl_len);
1066 * If we found a delimiter we want to skip over it,
1067 * if we didn't we do *NOT* want to skip over the end
1070 p += (q == NULL) ? len : (len + 1);
1072 RDEBUG2("\tLength : %i", curl_len);
1073 RDEBUG2("\tValue : \"%s\"", value);
1075 vp = paircreate(da->attr, da->vendor, da->type);
1077 radlog(L_ERR, "rlm_rest (%s): Failed creating"
1078 " value-pair", instance->xlat_name);
1086 * Check to see if we've already processed an
1087 * attribute of the same type if we have, change the op
1088 * from T_OP_ADD to T_OP_SET.
1090 current = processed;
1091 while (*current++) {
1092 if (current[0] == da) {
1098 if (vp->op != T_OP_ADD) {
1103 tmp = pairparsevalue(vp, value);
1105 RDEBUG("Incompatible value assignment, skipping");
1111 rest_check_truncation(request, value, vp);
1113 vp->flags.do_xlat = 1;
1115 RDEBUG("Performing xlat expansion of response value", value);
1116 pairxlatmove(request, vps, &vp);
1118 if (++count == REST_BODY_MAX_ATTRS) {
1119 radlog(L_ERR, "rlm_rest (%s): At maximum"
1120 " attribute limit", instance->xlat_name);
1140 radlog(L_ERR, "rlm_rest (%s): Malformed POST data \"%s\"",
1141 instance->xlat_name, raw);
1149 /** Converts JSON "value" key into VALUE_PAIR.
1151 * If leaf is not in fact a leaf node, but contains JSON data, the data will
1152 * written to the attribute in JSON string format.
1154 * @param[in] instance configuration data.
1155 * @param[in] section configuration data.
1156 * @param[in] request Current request.
1157 * @param[in] da Attribute to create.
1158 * @param[in] flags containing the operator other flags controlling value
1160 * @param[in] leaf object containing the VALUE_PAIR value.
1161 * @return The VALUE_PAIR just created, or NULL on error.
1163 static VALUE_PAIR *json_pairmake_leaf(rlm_rest_t *instance,
1164 UNUSED rlm_rest_section_t *section,
1165 REQUEST *request, const DICT_ATTR *da,
1166 json_flags_t *flags, json_object *leaf)
1169 VALUE_PAIR *vp, *tmp;
1172 * Should encode any nested JSON structures into JSON strings.
1174 * "I knew you liked JSON so I put JSON in your JSON!"
1176 value = json_object_get_string(leaf);
1178 RDEBUG2("\tType : %s", fr_int2str(dict_attr_types, da->type,
1180 RDEBUG2("\tLength : %i", strlen(value));
1181 RDEBUG2("\tValue : \"%s\"", value);
1183 vp = paircreate(da->attr, da->vendor, da->type);
1185 radlog(L_ERR, "rlm_rest (%s): Failed creating value-pair",
1186 instance->xlat_name);
1192 tmp = pairparsevalue(vp, value);
1194 RDEBUG("Incompatible value assignment, skipping");
1200 rest_check_truncation(request, value, vp);
1202 if (flags->do_xlat) vp->flags.do_xlat = 1;
1207 /** Processes JSON response and converts it into multiple VALUE_PAIRs
1209 * Processes JSON attribute declarations in the format below. Will recurse when
1210 * processing nested attributes. When processing nested attributes flags and
1211 * operators from previous attributes are not inherited.
1213 * JSON response format is:
1220 "value":[<value0>,<value1>,<valueN>]
1224 "<nested-attribute0>":{
1230 "<attribute2>":"<value0>",
1231 "<attributeN>":"[<value0>,<value1>,<valueN>]"
1235 * JSON valuepair flags (bools):
1236 * - do_xlat (optional) Controls xlat expansion of values. Defaults to TRUE.
1237 * - is_json (optional) If TRUE, any nested JSON data will be copied to the
1238 * VALUE_PAIR in string form. Defaults to TRUE.
1239 * - op (optional) Controls how the attribute is inserted into
1240 * the target list. Defaults to ':=' (T_OP_SET).
1242 * If "op" is ':=' or '=', it will be automagically changed to '+=' for the
1243 * second and subsequent values in multivalued attributes. This does not work
1244 * between multiple attribute declarations.
1248 * @param[in] instance configuration data.
1249 * @param[in] section configuration data.
1250 * @param[in] request Current request.
1251 * @param[in] object containing root node, or parent node.
1252 * @param[in] level Current nesting level.
1253 * @param[in] max_attrs counter, decremented after each VALUE_PAIR is created,
1254 * when 0 no more attributes will be processed.
1255 * @return VALUE_PAIR or NULL on error.
1257 static VALUE_PAIR *json_pairmake(rlm_rest_t *instance,
1258 UNUSED rlm_rest_section_t *section,
1259 REQUEST *request, json_object *object,
1260 int level, int *max_attrs)
1265 const char *name, *attribute;
1267 struct json_object *value, *idx, *tmp;
1268 struct lh_entry *entry;
1271 const DICT_ATTR *da;
1274 request_refs_t request_name;
1275 pair_lists_t list_name;
1276 REQUEST *reference = request;
1281 if (!json_object_is_type(object, json_type_object)) {
1282 RDEBUG("Can't process VP container, expected JSON object,"
1283 " got \"%s\", skipping",
1284 json_object_get_type(object));
1289 * Process VP container
1291 entry = json_object_get_object(object)->head;
1293 flags.op = T_OP_SET;
1297 name = (char*)entry->k;
1299 /* Fix the compiler warnings regarding const... */
1300 memcpy(&value, &entry->v, sizeof(value));
1302 entry = entry->next;
1305 * For people handcrafting JSON responses
1308 while ((p = q = strchr(p, '|'))) {
1314 reference = request;
1317 * Resolve attribute name to a dictionary entry and
1320 RDEBUG2("Decoding attribute \"%s\"", name);
1322 request_name = radius_request_name(&attribute, REQUEST_CURRENT);
1323 if (request_name == REQUEST_UNKNOWN) {
1324 RDEBUG("WARNING: Request qualifier, skipping");
1329 if (!radius_request(&reference, request_name)) {
1330 RDEBUG("WARNING: Attribute name refers to outer request"
1331 " but not in a tunnel, skipping");
1336 list_name = radius_list_name(&attribute, PAIR_LIST_REPLY);
1337 if (list_name == PAIR_LIST_UNKNOWN) {
1338 RDEBUG("WARNING: Invalid list qualifier, skipping");
1343 da = dict_attrbyname(attribute);
1345 RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
1351 vps = radius_list(reference, list_name);
1356 * Alternate JSON structure that allows operator,
1357 * and other flags to be specified.
1367 * - [] Multivalued array
1368 * - {} Nested Valuepair
1369 * - * Integer or string value
1371 if (json_object_is_type(value, json_type_object)) {
1373 * Process operator if present.
1375 tmp = json_object_object_get(value, "op");
1377 flags.op = fr_str2int(fr_tokens,
1378 json_object_get_string(tmp), 0);
1381 RDEBUG("Invalid operator value \"%s\","
1388 * Process optional do_xlat bool.
1390 tmp = json_object_object_get(value, "do_xlat");
1392 flags.do_xlat = json_object_get_boolean(tmp);
1396 * Process optional is_json bool.
1398 tmp = json_object_object_get(value, "is_json");
1400 flags.is_json = json_object_get_boolean(tmp);
1404 * Value key must be present if were using
1405 * the expanded syntax.
1407 value = json_object_object_get(value, "value");
1409 RDEBUG("Value key missing, skipping", value);
1415 * Setup pairmake / recursion loop.
1417 if (!flags.is_json &&
1418 json_object_is_type(value, json_type_array)) {
1419 len = json_object_array_length(value);
1421 RDEBUG("Zero length value array, skipping", value);
1424 idx = json_object_array_get_idx(value, 0);
1432 if (!(*max_attrs)--) {
1433 radlog(L_ERR, "rlm_rest (%s): At maximum"
1434 " attribute limit", instance->xlat_name);
1439 * Automagically switch the op for multivalued
1442 if (((flags.op == T_OP_SET) ||
1443 (flags.op == T_OP_EQ)) && (len > 1)) {
1444 flags.op = T_OP_ADD;
1447 if (!flags.is_json &&
1448 json_object_is_type(value, json_type_object)) {
1449 /* TODO: Insert nested VP into VP structure...*/
1450 RDEBUG("Found nested VP", value);
1451 vp = json_pairmake(instance, section,
1453 level + 1, max_attrs);
1455 vp = json_pairmake_leaf(instance, section,
1456 request, da, &flags,
1460 if (vp->flags.do_xlat) {
1461 RDEBUG("Performing xlat"
1462 " expansion of response"
1466 pairxlatmove(request, vps, &vp);
1469 } while ((++i < len) && (idx = json_object_array_get_idx(value, i)));
1475 /** Converts JSON response into VALUE_PAIRs and adds them to the request.
1477 * Converts the raw JSON string into a json-c object tree and passes it to
1478 * json_pairmake. After the tree has been parsed json_object_put is called
1479 * which decrements the reference count of the root node by one, and frees
1482 * @see rest_encode_json
1483 * @see json_pairmake
1485 * @param[in] instance configuration data.
1486 * @param[in] section configuration data.
1487 * @param[in,out] request Current request.
1488 * @param[in] handle REST handle.
1489 * @param[in] raw buffer containing JSON data.
1490 * @param[in] rawlen Length of data in raw buffer.
1491 * @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
1493 static int rest_decode_json(rlm_rest_t *instance,
1494 UNUSED rlm_rest_section_t *section,
1495 REQUEST *request, UNUSED void *handle,
1496 char *raw, UNUSED size_t rawlen)
1498 const char *p = raw;
1500 struct json_object *json;
1502 int max = REST_BODY_MAX_ATTRS;
1507 while (isspace(*p)) p++;
1508 if (*p == '\0') return FALSE;
1510 json = json_tokener_parse(p);
1512 radlog(L_ERR, "rlm_rest (%s): Malformed JSON data \"%s\"",
1513 instance->xlat_name, raw);
1517 json_pairmake(instance, section, request, json, 0, &max);
1520 * Decrement reference count for root object, should free entire
1523 json_object_put(json);
1525 return (REST_BODY_MAX_ATTRS - max);
1529 /** Processes incoming HTTP header data from libcurl.
1531 * Processes the status line, and Content-Type headers from the incoming HTTP
1534 * Matches prototype for CURLOPT_HEADERFUNCTION, and will be called directly
1537 * @param[in] ptr Char buffer where inbound header data is written.
1538 * @param[in] size Multiply by nmemb to get the length of ptr.
1539 * @param[in] nmemb Multiply by size to get the length of ptr.
1540 * @param[in] userdata rlm_rest_write_t to keep parsing state between calls.
1541 * @return Length of data processed, or 0 on error.
1543 static size_t rest_write_header(void *ptr, size_t size, size_t nmemb,
1546 rlm_rest_write_t *ctx = userdata;
1547 REQUEST *request = ctx->request; /* Used by RDEBUG */
1549 const char *p = ptr, *q;
1552 const size_t t = (size * nmemb);
1556 http_body_type_t type;
1557 http_body_type_t supp;
1561 case WRITE_STATE_INIT:
1562 RDEBUG("Processing header");
1565 * HTTP/<version> <reason_code>[ <reason_phrase>]\r\n
1567 * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14
1569 if (s < 14) goto malformed;
1572 * Check start of header matches...
1574 if (strncasecmp("HTTP/", p, 5) != 0) goto malformed;
1580 * Skip the version field, next space should mark start
1583 q = memchr(p, ' ', s);
1584 if (q == NULL) goto malformed;
1590 * Process reason_code.
1592 * " 100" (4) + "\r\n" (2) = 6
1594 if (s < 6) goto malformed;
1598 /* Char after reason code must be a space, or \r */
1599 if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed;
1601 ctx->code = atoi(p);
1604 * Process reason_phrase (if present).
1610 q = memchr(p, '\r', s);
1611 if (q == NULL) goto malformed;
1615 tmp = rad_malloc(len + 1);
1616 strlcpy(tmp, p, len + 1);
1618 RDEBUG("\tStatus : %i (%s)", ctx->code, tmp);
1622 RDEBUG("\tStatus : %i", ctx->code);
1625 ctx->state = WRITE_STATE_PARSE_HEADERS;
1629 case WRITE_STATE_PARSE_HEADERS:
1631 (strncasecmp("Content-Type: ", p, 14) == 0)) {
1636 * Check to see if there's a parameter
1639 q = memchr(p, ';', s);
1642 * If there's not, find the end of this
1645 if (q == NULL) q = memchr(p, '\r', s);
1647 len = (q == NULL) ? s : (unsigned)(q - p);
1649 type = fr_substr2int(http_content_type_table,
1650 p, HTTP_BODY_UNKNOWN,
1653 supp = http_body_type_supported[type];
1655 tmp = rad_malloc(len + 1);
1656 strlcpy(tmp, p, len + 1);
1658 RDEBUG("\tType : %s (%s)",
1659 fr_int2str(http_body_type_table, type,
1660 "¿Unknown?"), tmp);
1664 if (type == HTTP_BODY_UNKNOWN) {
1665 RDEBUG("Couldn't determine type, using"
1666 " request type \"%s\".",
1667 fr_int2str(http_body_type_table,
1671 } else if (supp == HTTP_BODY_UNSUPPORTED) {
1672 RDEBUG("Type \"%s\" is currently"
1674 fr_int2str(http_body_type_table,
1675 type, "¿Unknown?"));
1676 ctx->type = HTTP_BODY_UNSUPPORTED;
1677 } else if (supp == HTTP_BODY_UNAVAILABLE) {
1678 RDEBUG("Type \"%s\" is currently"
1679 " unavailable, please rebuild"
1680 " this module with the required"
1682 fr_int2str(http_body_type_table,
1683 type, "¿Unknown?"));
1684 ctx->type = HTTP_BODY_UNSUPPORTED;
1686 } else if (supp == HTTP_BODY_INVALID) {
1687 RDEBUG("Type \"%s\" is not a valid web"
1688 " API data markup format",
1689 fr_int2str(http_body_type_table,
1690 type, "¿Unknown?"));
1692 ctx->type = HTTP_BODY_INVALID;
1694 } else if (type != ctx->type) {
1707 RDEBUG("Incoming header was malformed");
1713 /** Processes incoming HTTP body data from libcurl.
1715 * Writes incoming body data to an intermediary buffer for later parsing by
1716 * one of the decode functions.
1718 * @param[in] ptr Char buffer where inbound header data is written
1719 * @param[in] size Multiply by nmemb to get the length of ptr.
1720 * @param[in] nmemb Multiply by size to get the length of ptr.
1721 * @param[in] userdata rlm_rest_write_t to keep parsing state between calls.
1722 * @return length of data processed, or 0 on error.
1724 static size_t rest_write_body(void *ptr, size_t size, size_t nmemb,
1727 rlm_rest_write_t *ctx = userdata;
1728 REQUEST *request = ctx->request; /* Used by RDEBUG */
1730 const char *p = ptr;
1733 const size_t t = (size * nmemb);
1736 * Any post processing of headers should go here...
1738 if (ctx->state == WRITE_STATE_PARSE_HEADERS) {
1739 ctx->state = WRITE_STATE_PARSE_CONTENT;
1744 case HTTP_BODY_UNSUPPORTED:
1747 case HTTP_BODY_INVALID:
1748 tmp = rad_malloc(t + 1);
1749 strlcpy(tmp, p, t + 1);
1758 if (t > (ctx->alloc - ctx->used)) {
1759 ctx->alloc += ((t + 1) > REST_BODY_INCR) ?
1760 t + 1 : REST_BODY_INCR;
1764 ctx->buffer = rad_malloc(ctx->alloc);
1766 /* If data has been written previously */
1768 strlcpy(ctx->buffer, tmp,
1773 strlcpy(ctx->buffer + ctx->used, p, t + 1);
1782 /** (Re-)Initialises the data in a rlm_rest_write_t.
1784 * This resets the values of the a rlm_rest_write_t to their defaults.
1785 * Must be called between encoding sessions.
1787 * @see rest_write_body
1788 * @see rest_write_header
1790 * @param[in] request Current request.
1791 * @param[in] ctx data to initialise.
1792 * @param[in] type Default http_body_type to use when decoding raw data, may be
1793 * overwritten by rest_write_header.
1795 static void rest_write_ctx_init(REQUEST *request, rlm_rest_write_t *ctx,
1796 http_body_type_t type)
1798 ctx->request = request;
1800 ctx->state = WRITE_STATE_INIT;
1806 /** Frees the intermediary buffer created by rest_write.
1808 * @param[in] ctx data to be freed.
1810 static void rest_write_free(rlm_rest_write_t *ctx)
1812 if (ctx->buffer != NULL) {
1817 /** Configures body specific curlopts.
1819 * Configures libcurl handle to use either chunked mode, where the request
1820 * data will be sent using multiple HTTP requests, or contiguous mode where
1821 * the request data will be sent in a single HTTP request.
1823 * @param[in] instance configuration data.
1824 * @param[in] section configuration data.
1825 * @param[in] handle rlm_rest_handle_t to configure.
1826 * @param[in] func to pass to libcurl for chunked.
1827 * transfers (NULL if not using chunked mode).
1828 * @return TRUE on success FALSE on error.
1830 static int rest_request_config_body(rlm_rest_t *instance,
1831 rlm_rest_section_t *section,
1832 rlm_rest_handle_t *handle,
1835 rlm_rest_curl_context_t *ctx = handle->ctx;
1836 CURL *candle = handle->handle;
1841 if (section->chunk > 0) {
1842 ret = curl_easy_setopt(candle, CURLOPT_READDATA,
1844 if (ret != CURLE_OK) goto error;
1846 ret = curl_easy_setopt(candle, CURLOPT_READFUNCTION,
1848 if (ret != CURLE_OK) goto error;
1850 len = rest_read_wrapper(&ctx->body, func,
1851 REST_BODY_MAX_LEN , &ctx->read);
1853 radlog(L_ERR, "rlm_rest (%s): Failed creating HTTP"
1854 " body content", instance->xlat_name);
1858 ret = curl_easy_setopt(candle, CURLOPT_POSTFIELDS,
1860 if (ret != CURLE_OK) goto error;
1862 ret = curl_easy_setopt(candle, CURLOPT_POSTFIELDSIZE,
1864 if (ret != CURLE_OK) goto error;
1870 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
1871 instance->xlat_name, ret, curl_easy_strerror(ret));
1876 /** Configures request curlopts.
1878 * Configures libcurl handle setting various curlopts for things like local
1879 * client time, Content-Type, and other FreeRADIUS custom headers.
1881 * Current FreeRADIUS custom headers are:
1882 * - X-FreeRADIUS-Section The module section being processed.
1883 * - X-FreeRADIUS-Server The current virtual server the REQUEST is
1886 * Sets up callbacks for all response processing (buffers and body data).
1888 * @param[in] instance configuration data.
1889 * @param[in] section configuration data.
1890 * @param[in] handle to configure.
1891 * @param[in] request Current request.
1892 * @param[in] method to use (HTTP verbs PUT, POST, DELETE etc...).
1893 * @param[in] type Content-Type for request encoding, also sets the default
1895 * @param[in] uri buffer containing the expanded URI to send the request to.
1896 * @return TRUE on success (all opts configured) FALSE on error.
1898 int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section,
1899 REQUEST *request, void *handle, http_method_t method,
1900 http_body_type_t type, char *uri)
1902 rlm_rest_handle_t *randle = handle;
1903 rlm_rest_curl_context_t *ctx = randle->ctx;
1904 CURL *candle = randle->handle;
1906 http_auth_type_t auth = section->auth;
1913 buffer[(sizeof(buffer) - 1)] = '\0';
1916 * Setup any header options and generic headers.
1918 ret = curl_easy_setopt(candle, CURLOPT_URL, uri);
1919 if (ret != CURLE_OK) goto error;
1921 ret = curl_easy_setopt(candle, CURLOPT_USERAGENT, "FreeRADIUS");
1922 if (ret != CURLE_OK) goto error;
1924 snprintf(buffer, (sizeof(buffer) - 1), "Content-Type: %s",
1925 fr_int2str(http_content_type_table, type, "¿Unknown?"));
1926 ctx->headers = curl_slist_append(ctx->headers, buffer);
1927 if (!ctx->headers) goto error_header;
1929 if (section->timeout) {
1930 ret = curl_easy_setopt(candle, CURLOPT_TIMEOUT,
1932 if (ret != CURLE_OK) goto error;
1935 ret = curl_easy_setopt(candle, CURLOPT_PROTOCOLS,
1936 (CURLPROTO_HTTP | CURLPROTO_HTTPS));
1937 if (ret != CURLE_OK) goto error;
1940 * FreeRADIUS custom headers
1942 snprintf(buffer, (sizeof(buffer) - 1), "X-FreeRADIUS-Section: %s",
1944 ctx->headers = curl_slist_append(ctx->headers, buffer);
1945 if (!ctx->headers) goto error_header;
1947 snprintf(buffer, (sizeof(buffer) - 1), "X-FreeRADIUS-Server: %s",
1949 ctx->headers = curl_slist_append(ctx->headers, buffer);
1950 if (!ctx->headers) goto error_header;
1953 * Configure HTTP verb (GET, POST, PUT, DELETE, other...)
1957 case HTTP_METHOD_GET :
1958 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1960 if (ret != CURLE_OK) goto error;
1964 case HTTP_METHOD_POST :
1965 ret = curl_easy_setopt(candle, CURLOPT_POST,
1967 if (ret != CURLE_OK) goto error;
1971 case HTTP_METHOD_PUT :
1972 ret = curl_easy_setopt(candle, CURLOPT_PUT,
1974 if (ret != CURLE_OK) goto error;
1978 case HTTP_METHOD_DELETE :
1979 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1981 if (ret != CURLE_OK) goto error;
1983 ret = curl_easy_setopt(candle,
1984 CURLOPT_CUSTOMREQUEST, "DELETE");
1985 if (ret != CURLE_OK) goto error;
1989 case HTTP_METHOD_CUSTOM :
1990 ret = curl_easy_setopt(candle, CURLOPT_HTTPGET,
1992 if (ret != CURLE_OK) goto error;
1994 ret = curl_easy_setopt(candle,
1995 CURLOPT_CUSTOMREQUEST,
1997 if (ret != CURLE_OK) goto error;
2005 * Set user based authentication parameters
2008 if ((auth >= HTTP_AUTH_BASIC) &&
2009 (auth <= HTTP_AUTH_ANY_SAFE)) {
2010 ret = curl_easy_setopt(candle, CURLOPT_HTTPAUTH,
2011 http_curl_auth[auth]);
2012 if (ret != CURLE_OK) goto error;
2014 if (section->username) {
2015 radius_xlat(buffer, sizeof(buffer),
2016 section->username, request, NULL, NULL);
2018 ret = curl_easy_setopt(candle, CURLOPT_USERNAME,
2020 if (ret != CURLE_OK) goto error;
2022 if (section->password) {
2023 radius_xlat(buffer, sizeof(buffer),
2024 section->password, request, NULL, NULL);
2026 ret = curl_easy_setopt(candle, CURLOPT_PASSWORD,
2028 if (ret != CURLE_OK) goto error;
2031 #ifdef CURLOPT_TLSAUTH_USERNAME
2032 } else if (type == HTTP_AUTH_TLS_SRP) {
2033 ret = curl_easy_setopt(candle, CURLOPT_TLSAUTH_TYPE,
2034 http_curl_auth[auth]);
2036 if (section->username) {
2037 radius_xlat(buffer, sizeof(buffer),
2038 section->username, request, NULL, NULL);
2040 ret = curl_easy_setopt(candle,
2041 CURLOPT_TLSAUTH_USERNAME,
2043 if (ret != CURLE_OK) goto error;
2045 if (section->password) {
2046 radius_xlat(buffer, sizeof(buffer),
2047 section->password, request, NULL, NULL);
2049 ret = curl_easy_setopt(candle,
2050 CURLOPT_TLSAUTH_PASSWORD,
2052 if (ret != CURLE_OK) goto error;
2059 * Set SSL/TLS authentication parameters
2061 if (section->tls_certfile) {
2062 ret = curl_easy_setopt(candle,
2064 section->tls_certfile);
2065 if (ret != CURLE_OK) goto error;
2068 if (section->tls_keyfile) {
2069 ret = curl_easy_setopt(candle,
2071 section->tls_keyfile);
2072 if (ret != CURLE_OK) goto error;
2075 if (section->tls_keypassword) {
2076 ret = curl_easy_setopt(candle,
2078 section->tls_keypassword);
2079 if (ret != CURLE_OK) goto error;
2082 if (section->tls_cacertfile) {
2083 ret = curl_easy_setopt(candle,
2085 section->tls_cacertfile);
2086 if (ret != CURLE_OK) goto error;
2089 if (section->tls_cacertdir) {
2090 ret = curl_easy_setopt(candle,
2092 section->tls_cacertdir);
2093 if (ret != CURLE_OK) goto error;
2096 if (section->tls_randfile) {
2097 ret = curl_easy_setopt(candle,
2098 CURLOPT_RANDOM_FILE,
2099 section->tls_randfile);
2100 if (ret != CURLE_OK) goto error;
2103 if (section->tls_verify_cert) {
2104 ret = curl_easy_setopt(candle,
2105 CURLOPT_SSL_VERIFYHOST,
2106 (section->tls_verify_cert_cn == TRUE) ?
2108 if (ret != CURLE_OK) goto error;
2110 ret = curl_easy_setopt(candle,
2111 CURLOPT_SSL_VERIFYPEER,
2113 if (ret != CURLE_OK) goto error;
2117 * Tell CURL how to get HTTP body content, and how to process
2120 rest_write_ctx_init(request, &ctx->write, type);
2122 ret = curl_easy_setopt(candle, CURLOPT_HEADERFUNCTION,
2124 if (ret != CURLE_OK) goto error;
2126 ret = curl_easy_setopt(candle, CURLOPT_HEADERDATA,
2128 if (ret != CURLE_OK) goto error;
2130 ret = curl_easy_setopt(candle, CURLOPT_WRITEFUNCTION,
2132 if (ret != CURLE_OK) goto error;
2134 ret = curl_easy_setopt(candle, CURLOPT_WRITEDATA,
2136 if (ret != CURLE_OK) goto error;
2140 case HTTP_METHOD_GET :
2141 case HTTP_METHOD_DELETE :
2145 case HTTP_METHOD_POST :
2146 case HTTP_METHOD_PUT :
2147 case HTTP_METHOD_CUSTOM :
2148 if (section->chunk > 0) {
2149 ctx->read.chunk = section->chunk;
2151 ctx->headers = curl_slist_append(ctx->headers,
2153 if (!ctx->headers) goto error_header;
2155 ctx->headers = curl_slist_append(ctx->headers,
2156 "Transfer-Encoding: chunked");
2157 if (!ctx->headers) goto error_header;
2163 case HTTP_BODY_JSON:
2164 rest_read_ctx_init(request,
2167 ret = rest_request_config_body(instance,
2171 if (!ret) return -1;
2176 case HTTP_BODY_POST:
2177 rest_read_ctx_init(request,
2180 ret = rest_request_config_body(instance,
2184 if (!ret) return -1;
2192 ret = curl_easy_setopt(candle, CURLOPT_HTTPHEADER,
2194 if (ret != CURLE_OK) goto error;
2205 radlog(L_ERR, "rlm_rest (%s): Failed setting curl option: %i - %s",
2206 instance->xlat_name, ret, curl_easy_strerror(ret));
2210 radlog(L_ERR, "rlm_rest (%s): Failed creating header",
2211 instance->xlat_name);
2215 /** Sends a REST (HTTP) request.
2217 * Send the actual REST request to the server. The response will be handled by
2218 * the numerous callbacks configured in rest_request_config.
2220 * @param[in] instance configuration data.
2221 * @param[in] section configuration data.
2222 * @param[in] handle to use.
2223 * @return TRUE on success or FALSE on error.
2225 int rest_request_perform(rlm_rest_t *instance,
2226 UNUSED rlm_rest_section_t *section, void *handle)
2228 rlm_rest_handle_t *randle = handle;
2229 CURL *candle = randle->handle;
2232 ret = curl_easy_perform(candle);
2233 if (ret != CURLE_OK) {
2234 radlog(L_ERR, "rlm_rest (%s): Request failed: %i - %s",
2235 instance->xlat_name, ret, curl_easy_strerror(ret));
2242 /** Sends the response to the correct decode function.
2244 * Uses the Content-Type information written in rest_write_header to
2245 * determine the correct decode function to use. The decode function will
2246 * then convert the raw received data into VALUE_PAIRs.
2248 * @param[in] instance configuration data.
2249 * @param[in] section configuration data.
2250 * @param[in] request Current request.
2251 * @param[in] handle to use.
2252 * @return TRUE on success or FALSE on error.
2254 int rest_request_decode(rlm_rest_t *instance,
2255 UNUSED rlm_rest_section_t *section,
2256 REQUEST *request, void *handle)
2258 rlm_rest_handle_t *randle = handle;
2259 rlm_rest_curl_context_t *ctx = randle->ctx;
2263 if (ctx->write.buffer == NULL) {
2264 RDEBUG("Skipping attribute processing, no body data received");
2268 RDEBUG("Processing body", ret);
2270 switch (ctx->write.type)
2272 case HTTP_BODY_POST:
2273 ret = rest_decode_post(instance, section, request,
2274 handle, ctx->write.buffer,
2278 case HTTP_BODY_JSON:
2279 ret = rest_decode_json(instance, section, request,
2280 handle, ctx->write.buffer,
2284 case HTTP_BODY_UNSUPPORTED:
2285 case HTTP_BODY_UNAVAILABLE:
2286 case HTTP_BODY_INVALID:
2296 /** Cleans up after a REST request.
2298 * Resets all options associated with a CURL handle, and frees any headers
2299 * associated with it.
2301 * Calls rest_read_ctx_free and rest_write_free to free any memory used by
2304 * @param[in] instance configuration data.
2305 * @param[in] section configuration data.
2306 * @param[in] handle to cleanup.
2307 * @return TRUE on success or FALSE on error.
2309 void rest_request_cleanup(UNUSED rlm_rest_t *instance,
2310 UNUSED rlm_rest_section_t *section, void *handle)
2312 rlm_rest_handle_t *randle = handle;
2313 rlm_rest_curl_context_t *ctx = randle->ctx;
2314 CURL *candle = randle->handle;
2317 * Clear any previously configured options
2319 curl_easy_reset(candle);
2324 if (ctx->headers != NULL) {
2325 curl_slist_free_all(ctx->headers);
2326 ctx->headers = NULL;
2330 * Free body data (only used if chunking is disabled)
2332 if (ctx->body != NULL) free(ctx->body);
2335 * Free other context info
2337 rest_read_ctx_free(&ctx->read);
2338 rest_write_free(&ctx->write);
2341 /** URL encodes a string.
2343 * Encode special chars as per RFC 3986 section 4.
2345 * @param[in] request Current request.
2346 * @param[out] out Where to write escaped string.
2347 * @param[in] outlen Size of out buffer.
2348 * @param[in] raw string to be urlencoded.
2349 * @param[in] arg pointer, gives context for escaping.
2350 * @return length of data written to out (excluding NULL).
2352 static size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen,
2353 const char *raw, UNUSED void *arg)
2357 escaped = curl_escape(raw, strlen(raw));
2358 strlcpy(out, escaped, outlen);
2364 /** Builds URI; performs XLAT expansions and encoding.
2366 * Splits the URI into "http://example.org" and "/%{xlat}/query/?bar=foo"
2367 * Both components are expanded, but values expanded for the second component
2368 * are also url encoded.
2370 * @param[in] instance configuration data.
2371 * @param[in] section configuration data.
2372 * @param[in] request Current request
2373 * @param[out] buffer to write expanded URI to.
2374 * @param[in] bufsize Size of buffer.
2375 * @return length of data written to buffer (excluding NULL) or < 0 if an error
2378 ssize_t rest_uri_build(rlm_rest_t *instance, rlm_rest_section_t *section,
2379 REQUEST *request, char *buffer, size_t bufsize)
2386 unsigned short count = 0;
2393 * All URLs must contain at least <scheme>://<server>/
2395 while ((q = strchr(p, '/'))) {
2404 radlog(L_ERR, "rlm_rest (%s): Error URI is malformed,"
2405 " can't find start of path", instance->xlat_name);
2411 scheme = rad_malloc(len + 1);
2412 strlcpy(scheme, section->uri, len + 1);
2417 out += radius_xlat(out, bufsize, scheme, request, NULL, NULL);
2421 out += radius_xlat(out, (bufsize - (buffer - out)), path, request,
2422 rest_uri_escape, NULL);
2424 return (buffer - out);