-/** Functions and datatypes for the REST (HTTP) transport.
- *
- * @file rest.c
- *
- * Version: $Id$
- *
+/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/*
+ * $Id$
+ *
+ * @brief Functions and datatypes for the REST (HTTP) transport.
+ * @file rest.c
*
- * Copyright 2012 Arran Cudbard-Bell <a.cudbard-bell@freeradius.org>
+ * @copyright 2012-2013 Arran Cudbard-Bell <a.cudbard-bell@freeradius.org>
*/
#include <freeradius-devel/ident.h>
HTTP_BODY_UNSUPPORTED, // HTTP_BODY_UNSUPPORTED
HTTP_BODY_UNSUPPORTED, // HTTP_BODY_INVALID
HTTP_BODY_POST, // HTTP_BODY_POST
-#ifdef WITH_JSON
+#ifdef HAVE_JSON
HTTP_BODY_JSON, // HTTP_BODY_JSON
#else
HTTP_BODY_UNAVAILABLE,
{ NULL , -1 }
};
+#ifdef HAVE_JSON
/** Flags to control the conversion of JSON values to VALUE_PAIRs.
*
* These fields are set when parsing the expanded format for value pairs in
* @see json_pairmake
* @see json_pairmake_leaf
*/
-#ifdef WITH_JSON
typedef struct json_flags {
- boolean do_xlat; //!< If TRUE value will be expanded with xlat.
- boolean is_json; //!< If TRUE value will be inserted as raw JSON
+ int do_xlat; //!< If TRUE value will be expanded with xlat.
+ int is_json; //!< If TRUE value will be inserted as raw JSON
// (multiple values not supported).
- FR_TOKEN operator; //!< The operator that determines how the new VP
+ FR_TOKEN op; //!< The operator that determines how the new VP
// is processed. @see fr_tokens
} json_flags_t;
#endif
long last_socket;
CURLcode ret;
- curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
+ ret = curl_easy_getinfo(candle, CURLINFO_LASTSOCKET, &last_socket);
if (ret != CURLE_OK) {
radlog(L_ERR,
"rlm_rest (%s): Couldn't determine socket"
goto end_chunk;
}
- RDEBUG2("Encoding attribute \"%s\"", current[0]->name);
+ RDEBUG2("Encoding attribute \"%s\"", current[0]->da->name);
if (ctx->state == READ_STATE_ATTR_BEGIN) {
- escaped = curl_escape(current[0]->name,
- strlen(current[0]->name));
+ escaped = curl_escape(current[0]->da->name,
+ strlen(current[0]->da->name));
len = strlen(escaped);
if (s < (1 + len)) {
* successive calls will return additional encoded VALUE_PAIRs.
*
* Only complete attribute headers
- * @verbatim "<name>":{"type":"<type>","value":['</pre> @endverbatim
+ * @verbatim "<name>":{"type":"<type>","value":[' @endverbatim
* and complete attribute values will be written to ptr.
*
* If an attribute occurs multiple times in the request the attribute values
* New attribute, write name, type, and beginning of
* value array.
*/
- RDEBUG2("Encoding attribute \"%s\"", current[0]->name);
+ RDEBUG2("Encoding attribute \"%s\"", current[0]->da->name);
if (ctx->state == READ_STATE_ATTR_BEGIN) {
- type = fr_int2str(dict_attr_types, current[0]->type,
+ type = fr_int2str(dict_attr_types, current[0]->da->type,
"¿Unknown?");
len = strlen(type);
- len += strlen(current[0]->name);
+ len += strlen(current[0]->da->name);
if (s < (23 + len)) goto no_space;
len = sprintf(p, "\"%s\":{\"type\":\"%s\",\"value\":[" ,
- current[0]->name, type);
+ current[0]->da->name, type);
p += len;
s -= len;
/*
* Multivalued attribute
*/
- if (current[1] &&
- ((current[0]->attribute == current[1]->attribute) &&
- (current[0]->vendor == current[1]->vendor))) {
+ if (current[1] &&
+ (current[0]->da == current[1]->da)) {
*p++ = ',';
current++;
* @see rest_read_ctx_free
*
* @param[in] request Current request.
- * @param[in] read to initialise.
+ * @param[in] ctx to initialise.
* @param[in] sort If TRUE VALUE_PAIRs will be sorted within the VALUE_PAIR
* pointer array.
*/
/* TODO: Quicksort would be faster... */
do {
for(i = 1; i < count; i++) {
- assert(current[i-1]->attribute &&
- current[i]->attribute);
-
swap = 0;
- if ((current[i-1]->vendor > current[i]->vendor) ||
- ((current[i-1]->vendor == current[i]->vendor) &&
- (current[i-1]->attribute > current[i]->attribute)
- )) {
+ if (current[i-1]->da > current[i]->da) {
tmp = current[i];
current[i] = current[i-1];
current[i-1] = tmp;
*
* @see rest_read_ctx_init
*
- * @param[in] read to free.
+ * @param[in] ctx to free.
*/
static void rest_read_ctx_free(rlm_rest_read_t *ctx)
{
}
}
-/** Verify that value wasn't truncated when it was converted to a VALUE_PAIR
- *
- * Certain values may be truncated when they're converted into VALUE_PAIRs
- * for example 64bit integers converted to 32bit integers. Warn the user
- * when this happens.
- *
- * @param[in] raw string from decoder.
- * @param[in] vp containing parsed value.
- */
-static void rest_check_truncation(REQUEST *request, const char *raw,
- VALUE_PAIR *vp)
-{
- char cooked[1024];
-
- vp_prints_value(cooked, sizeof(cooked), vp, 0);
- if (strcmp(raw, cooked) != 0) {
- RDEBUG("WARNING: Value-Pair does not match POST value, "
- "truncation may have occurred");
- RDEBUG("\tValue (pair) : \"%s\"", cooked);
- RDEBUG("\tValue (post) : \"%s\"", raw);
- }
-}
-
/** Converts POST response into VALUE_PAIRs and adds them to the request
*
* Accepts VALUE_PAIRS in the same format as rest_encode_post, but with the
const char *attribute;
char *name = NULL;
char *value = NULL;
+
+ char buffer[1024];
const DICT_ATTR *da;
VALUE_PAIR *vp;
const DICT_ATTR **current, *processed[REST_BODY_MAX_ATTRS + 1];
- VALUE_PAIR *tmp;
- pair_lists_t list;
+ pair_lists_t list_name;
+ request_refs_t request_name;
REQUEST *reference = request;
VALUE_PAIR **vps;
* Empty response?
*/
while (isspace(*p)) p++;
-
- if (p == NULL) return FALSE;
+ if (*p == '\0') return FALSE;
while (((q = strchr(p, '=')) != NULL) &&
(count < REST_BODY_MAX_ATTRS)) {
p = (q + 1);
RDEBUG("Decoding attribute \"%s\"", name);
+
+ request_name = radius_request_name(&attribute, REQUEST_CURRENT);
+ if (request_name == REQUEST_UNKNOWN) {
+ RDEBUGW("Invalid request qualifier, skipping");
+
+ curl_free(name);
+
+ continue;
+ }
- if (!radius_ref_request(&reference, &attribute)) {
- RDEBUG("WARNING: Attribute name refers to outer request"
+ if (!radius_request(&reference, request_name)) {
+ RDEBUGW("Attribute name refers to outer request"
" but not in a tunnel, skipping");
curl_free(name);
continue;
}
- list = radius_list_name(&attribute, PAIR_LIST_REPLY);
- if (list == PAIR_LIST_UNKNOWN) {
- RDEBUG("WARNING: Invalid list qualifier, skipping");
+ list_name = radius_list_name(&attribute, PAIR_LIST_REPLY);
+ if (list_name == PAIR_LIST_UNKNOWN) {
+ RDEBUGW("Invalid list qualifier, skipping");
curl_free(name);
da = dict_attrbyname(attribute);
if (!da) {
- RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
+ RDEBUGW("Attribute \"%s\" unknown, skipping",
attribute);
curl_free(name);
continue;
}
- vps = radius_list(reference, list);
+ vps = radius_list(reference, list_name);
assert(vps);
RDEBUG2("\tLength : %i", curl_len);
RDEBUG2("\tValue : \"%s\"", value);
+
+ RDEBUG("Performing xlat expansion of response value");
+
+ if (!radius_xlat(buffer, sizeof(buffer),
+ value, request, NULL, NULL)) {
+ goto skip;
+ }
- vp = paircreate(da->attr, da->vendor, da->type);
+ vp = pairalloc(NULL, da);
if (!vp) {
radlog(L_ERR, "rlm_rest (%s): Failed creating"
- " value-pair", instance->xlat_name);
+ " valuepair", instance->xlat_name);
goto error;
}
- vp->operator = T_OP_SET;
+ vp->op = T_OP_SET;
/*
* Check to see if we've already processed an
*/
current = processed;
while (*current++) {
- if ((current[0]->attr == da->attr) &&
- (current[0]->vendor == da->vendor)) {
- vp->operator = T_OP_ADD;
+ if (current[0] == da) {
+ vp->op = T_OP_ADD;
break;
}
}
- if (vp->operator != T_OP_ADD) {
+ if (vp->op != T_OP_ADD) {
current[0] = da;
current[1] = NULL;
}
- tmp = pairparsevalue(vp, value);
- if (tmp == NULL) {
+ if (!pairparsevalue(vp, buffer)) {
RDEBUG("Incompatible value assignment, skipping");
pairbasicfree(vp);
goto skip;
}
- vp = tmp;
-
- rest_check_truncation(request, value, vp);
-
- vp->flags.do_xlat = 1;
-
- RDEBUG("Performing xlat expansion of response value", value);
- pairxlatmove(request, vps, &vp);
if (++count == REST_BODY_MAX_ATTRS) {
radlog(L_ERR, "rlm_rest (%s): At maximum"
}
+#ifdef HAVE_JSON
/** Converts JSON "value" key into VALUE_PAIR.
*
* If leaf is not in fact a leaf node, but contains JSON data, the data will
* @param[in] instance configuration data.
* @param[in] section configuration data.
* @param[in] request Current request.
- * @param[in] attribute name without qualifiers.
+ * @param[in] da Attribute to create.
* @param[in] flags containing the operator other flags controlling value
* expansion.
* @param[in] leaf object containing the VALUE_PAIR value.
* @return The VALUE_PAIR just created, or NULL on error.
*/
-#ifdef WITH_JSON
static VALUE_PAIR *json_pairmake_leaf(rlm_rest_t *instance,
UNUSED rlm_rest_section_t *section,
REQUEST *request, const DICT_ATTR *da,
json_flags_t *flags, json_object *leaf)
{
- const char *value;
- VALUE_PAIR *vp, *tmp;
+ const char *value, *to_parse;
+ char buffer[1024];
+
+ VALUE_PAIR *vp;
/*
* Should encode any nested JSON structures into JSON strings.
RDEBUG2("\tLength : %i", strlen(value));
RDEBUG2("\tValue : \"%s\"", value);
- vp = paircreate(da->attr, da->vendor, da->type);
+ if (flags->do_xlat) {
+ if (!radius_xlat(buffer, sizeof(buffer), value,
+ request, NULL, NULL)) {
+ return NULL;
+ }
+
+ to_parse = buffer;
+ } else {
+ to_parse = value;
+ }
+
+ vp = paircreate(da->attr, da->vendor);
if (!vp) {
- radlog(L_ERR, "rlm_rest (%s): Failed creating value-pair",
+ radlog(L_ERR, "rlm_rest (%s): Failed creating valuepair",
instance->xlat_name);
return NULL;
}
- vp->operator = flags->operator;
-
- tmp = pairparsevalue(vp, value);
- if (tmp == NULL) {
+ vp->op = flags->op;
+
+ if (!pairparsevalue(vp, to_parse)) {
RDEBUG("Incompatible value assignment, skipping");
pairbasicfree(vp);
return NULL;
}
- vp = tmp;
-
- rest_check_truncation(request, value, vp);
-
- if (flags->do_xlat) vp->flags.do_xlat = 1;
return vp;
}
* @param[in] object containing root node, or parent node.
* @param[in] level Current nesting level.
* @param[in] max_attrs counter, decremented after each VALUE_PAIR is created,
- * when 0 no more attributes will be processed.
+ * when 0 no more attributes will be processed.
* @return VALUE_PAIR or NULL on error.
*/
static VALUE_PAIR *json_pairmake(rlm_rest_t *instance,
json_flags_t flags;
const DICT_ATTR *da;
- VALUE_PAIR *vp;
+ VALUE_PAIR *vp = NULL;
- pair_lists_t list;
+ request_refs_t request_name;
+ pair_lists_t list_name;
REQUEST *reference = request;
VALUE_PAIR **vps;
*/
entry = json_object_get_object(object)->head;
while (entry) {
- flags.operator = T_OP_SET;
+ flags.op = T_OP_SET;
flags.do_xlat = 1;
flags.is_json = 0;
* pairlist.
*/
RDEBUG2("Decoding attribute \"%s\"", name);
+
+ request_name = radius_request_name(&attribute, REQUEST_CURRENT);
+ if (request_name == REQUEST_UNKNOWN) {
+ RDEBUGW("Request qualifier unknown, skipping");
- if (!radius_ref_request(&reference, &attribute)) {
- RDEBUG("WARNING: Attribute name refers to outer request"
+ continue;
+ }
+
+ if (!radius_request(&reference, request_name)) {
+ RDEBUGW("Attribute name refers to outer request"
" but not in a tunnel, skipping");
continue;
}
- list = radius_list_name(&attribute, PAIR_LIST_REPLY);
- if (list == PAIR_LIST_UNKNOWN) {
- RDEBUG("WARNING: Invalid list qualifier, skipping");
+ list_name = radius_list_name(&attribute, PAIR_LIST_REPLY);
+ if (list_name == PAIR_LIST_UNKNOWN) {
+ RDEBUGW("Invalid list qualifier, skipping");
continue;
}
da = dict_attrbyname(attribute);
if (!da) {
- RDEBUG("WARNING: Attribute \"%s\" unknown, skipping",
+ RDEBUGW("Attribute \"%s\" unknown, skipping",
attribute);
continue;
}
- vps = radius_list(reference, list);
+ vps = radius_list(reference, list_name);
assert(vps);
*/
tmp = json_object_object_get(value, "op");
if (tmp) {
- flags.operator = fr_str2int(fr_tokens,
- json_object_get_string(tmp), 0);
-
- if (!flags.operator) {
+ flags.op = fr_str2int(fr_tokens, json_object_get_string(tmp), 0);
+ if (!flags.op) {
RDEBUG("Invalid operator value \"%s\","
" skipping", tmp);
continue;
}
}
- /*
- * Setup pairmake / recursion loop.
- */
- if (!flags.is_json &&
- json_object_is_type(value, json_type_array)) {
- len = json_object_array_length(value);
- if (!len) {
- RDEBUG("Zero length value array, skipping", value);
- continue;
- }
- idx = json_object_array_get_idx(value, 0);
- } else {
- len = 1;
- idx = value;
- }
-
- i = 0;
- do {
- if (!(*max_attrs)--) {
- radlog(L_ERR, "rlm_rest (%s): At maximum"
- " attribute limit", instance->xlat_name);
- return NULL;
- }
+ /*
+ * Setup pairmake / recursion loop.
+ */
+ if (!flags.is_json &&
+ json_object_is_type(value, json_type_array)) {
+ len = json_object_array_length(value);
+ if (!len) {
+ RDEBUG("Zero length value array, skipping",
+ value);
+ continue;
+ }
+ idx = json_object_array_get_idx(value, 0);
+ } else {
+ len = 1;
+ idx = value;
+ }
- /*
- * Automagically switch the op for multivalued
- * attributes.
- */
- if (((flags.operator == T_OP_SET) ||
- (flags.operator == T_OP_EQ)) && (len > 1)) {
- flags.operator = T_OP_ADD;
- }
+ i = 0;
+ do {
+ if (!(*max_attrs)--) {
+ radlog(L_ERR, "rlm_rest (%s): At "
+ "maximum attribute limit",
+ instance->xlat_name);
+ return NULL;
+ }
- if (!flags.is_json &&
- json_object_is_type(value, json_type_object)) {
- /* TODO: Insert nested VP into VP structure...*/
- RDEBUG("Found nested VP", value);
- vp = json_pairmake(instance, section,
- request, value,
- level + 1, max_attrs);
- } else {
- vp = json_pairmake_leaf(instance, section,
- request, da, &flags,
- idx);
-
- if (vp != NULL) {
- if (vp->flags.do_xlat) {
- RDEBUG("Performing xlat"
- " expansion of response"
- " value", value);
- }
+ /*
+ * Automagically switch the op for multivalued
+ * attributes.
+ */
+ if (((flags.op == T_OP_SET) ||
+ (flags.op == T_OP_EQ)) && (len > 1)) {
+ flags.op = T_OP_ADD;
+ }
- pairxlatmove(request, vps, &vp);
+ if (!flags.is_json &&
+ json_object_is_type(value, json_type_object)) {
+ /* TODO: Insert nested VP into VP structure...*/
+ RDEBUG("Found nested VP", value);
+ vp = json_pairmake(instance, section,
+ request, value,
+ level + 1, max_attrs);
+ } else {
+ vp = json_pairmake_leaf(instance, section,
+ request, da, &flags,
+ idx);
}
- }
- } while ((++i < len) && (idx = json_object_array_get_idx(value, i)));
+ } while ((++i < len) &&
+ (idx = json_object_array_get_idx(value, i)));
}
return vp;
*
* Converts the raw JSON string into a json-c object tree and passes it to
* json_pairmake. After the tree has been parsed json_object_put is called
- * which decrements the reference, count to the root node by one, and frees
+ * which decrements the reference count of the root node by one, and frees
* the entire tree.
*
* @see rest_encode_json
*
* @param[in] instance configuration data.
* @param[in] section configuration data.
- * @param[in] g to use.
- * @param[in] request Current request.
+ * @param[in,out] request Current request.
+ * @param[in] handle REST handle.
* @param[in] raw buffer containing JSON data.
* @param[in] rawlen Length of data in raw buffer.
* @return the number of VALUE_PAIRs processed or -1 on unrecoverable error.
*/
static int rest_decode_json(rlm_rest_t *instance,
UNUSED rlm_rest_section_t *section,
- UNUSED REQUEST *request, UNUSED void *handle,
+ REQUEST *request, UNUSED void *handle,
char *raw, UNUSED size_t rawlen)
{
const char *p = raw;
* Empty response?
*/
while (isspace(*p)) p++;
- if (p == NULL) return FALSE;
+ if (*p == '\0') return FALSE;
json = json_tokener_parse(p);
if (!json) {
ctx->code = atoi(p);
/*
- * Process reason_phrase (if present).
+ * Process reason_phrase (if present).
*/
if (p[3] == ' ') {
p += 4;
* @see rest_write_header
*
* @param[in] request Current request.
- * @param[in] data to initialise.
+ * @param[in] ctx data to initialise.
* @param[in] type Default http_body_type to use when decoding raw data, may be
* overwritten by rest_write_header.
*/
/** Frees the intermediary buffer created by rest_write.
*
- * @param[in] data to be freed.
+ * @param[in] ctx data to be freed.
*/
static void rest_write_free(rlm_rest_write_t *ctx)
{
* @param[in] section configuration data.
* @param[in] handle rlm_rest_handle_t to configure.
* @param[in] func to pass to libcurl for chunked.
- * transfers (NULL if not using chunked mode).
+ * transfers (NULL if not using chunked mode).
* @return TRUE on success FALSE on error.
*/
static int rest_request_config_body(rlm_rest_t *instance,
CURLOPT_CUSTOMREQUEST,
section->method);
if (ret != CURLE_OK) goto error;
+ break;
default:
assert(0);
switch (type)
{
-#ifdef WITH_JSON
+#ifdef HAVE_JSON
case HTTP_BODY_JSON:
rest_read_ctx_init(request,
&ctx->read, 1);
return FALSE;
}
- RDEBUG("Processing body", ret);
+ RDEBUG("Processing body");
switch (ctx->write.type)
{
handle, ctx->write.buffer,
ctx->write.used);
break;
-#ifdef WITH_JSON
+#ifdef HAVE_JSON
case HTTP_BODY_JSON:
ret = rest_decode_json(instance, section, request,
handle, ctx->write.buffer,
*
* Encode special chars as per RFC 3986 section 4.
*
+ * @param[in] request Current request.
* @param[out] out Where to write escaped string.
* @param[in] outlen Size of out buffer.
* @param[in] raw string to be urlencoded.
+ * @param[in] arg pointer, gives context for escaping.
* @return length of data written to out (excluding NULL).
*/
static size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen,
p = section->uri;
- while ((q = strchr(p, '/')) && (count++ < 3)) p = (q + 1);
+ /*
+ * All URLs must contain at least <scheme>://<server>/
+ */
+ while ((q = strchr(p, '/'))) {
+ p = q + 1;
+
+ if (++count == 3) {
+ break;
+ }
+ }
if (count != 3) {
radlog(L_ERR, "rlm_rest (%s): Error URI is malformed,"