diff options
Diffstat (limited to '')
-rw-r--r-- | src/modules/rlm_json/json.c | 829 |
1 files changed, 829 insertions, 0 deletions
diff --git a/src/modules/rlm_json/json.c b/src/modules/rlm_json/json.c new file mode 100644 index 0000000..83bcba5 --- /dev/null +++ b/src/modules/rlm_json/json.c @@ -0,0 +1,829 @@ +/* + * This program is 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 (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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$ + * @file json.c + * @brief Common functions for working with json-c + * + * @author Matthew Newton + * @author Arran Cudbard-Bell + * + * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org) + * @copyright 2015,2021 Network RADIUS SARL (legal@networkradius.com) + * @copyright 2015 The FreeRADIUS Server Project + */ + +#include <freeradius-devel/rad_assert.h> +#include "json.h" + +#ifndef HAVE_JSON +# error "rlm_json should not be built unless json-c is available" +#endif + + +const FR_NAME_NUMBER fr_json_format_table[] = { + { "array", JSON_MODE_ARRAY }, + { "array_of_names", JSON_MODE_ARRAY_OF_NAMES }, + { "array_of_values", JSON_MODE_ARRAY_OF_VALUES }, + { "object", JSON_MODE_OBJECT }, + { "object_simple", JSON_MODE_OBJECT_SIMPLE }, + + { NULL, -1 } +}; + +static inline CC_HINT(always_inline) +void json_object_put_assert(json_object *obj) +{ + int ret; + + ret = json_object_put(obj); + if (ret == 1) return; + + rad_assert(0); +} + + +/** Given a VALUE_PAIR, create the correct JSON object based on the data type + * + * @param[in] ctx to allocate temporary buffers in + * @param[in] vp VALUE_PAIR to convert. + * @param[in] always_string create all values as strings + * @param[in] enum_as_int output enum attribute values as integers not strings + * @return Newly allocated JSON object, or NULL on error + */ +json_object *json_object_from_attr_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, bool always_string, bool enum_as_int) +{ + char buf[2048]; + ssize_t len; + + /* + * We're converting to PRESENTATION format + * so any attributes with enumeration values + * should be converted to string types, unless + * enum_as_int is set. + */ + +#define RETURN_ENUM_OR_STRING(_type) \ + if (always_string) { \ + len = snprintf(buf, sizeof(buf), "%d", vp->vp_ ## _type); \ + return json_object_new_string_len(buf, len); \ + } \ + return json_object_new_int(vp->vp_ ## _type) + + /* + * Handle enumeration values first + */ + if (vp->da->flags.has_value) { + if (enum_as_int) { + switch (vp->da->type) { + default: + break; + + case PW_TYPE_BYTE: + RETURN_ENUM_OR_STRING(byte); + + case PW_TYPE_SHORT: + RETURN_ENUM_OR_STRING(short); + + case PW_TYPE_INTEGER: + RETURN_ENUM_OR_STRING(integer); + } + } else { + always_string = true; + } + } + + + /* + * If always_string is set then we print everything to a string and + * return a JSON string object. + */ + if (always_string) { + char *p; + char *quoted_string; + json_object *obj = NULL; + + do_string: + p = vp_aprints_value(ctx, vp, '\0'); + if (!p) return NULL; + + quoted_string = fr_json_from_string(ctx, p, false); + if (!quoted_string) { + talloc_free(p); + return NULL; + } + + obj = json_object_new_string(quoted_string); + talloc_free(p); + + return obj; + } + + /* + * Otherwise use the correct JSON object function depending on the + * attribute value type. + */ + switch (vp->da->type) { + default: + goto do_string; + + case PW_TYPE_BOOLEAN: + return json_object_new_boolean(vp->vp_byte); + + case PW_TYPE_BYTE: + return json_object_new_int(vp->vp_byte); + + case PW_TYPE_SHORT: + return json_object_new_int(vp->vp_short); + +#ifdef HAVE_JSON_OBJECT_GET_INT64 + case PW_TYPE_INTEGER: + return json_object_new_int64((int64_t)vp->vp_integer64); /* uint32_t (max) > int32_t (max) */ + + case PW_TYPE_INTEGER64: + if (vp->vp_integer64 > INT64_MAX) goto do_string; + return json_object_new_int64(vp->vp_integer64); +#else + case PW_TYPE_INTEGER: + if (vp->vp_integer > INT32_MAX) goto do_string; + return json_object_new_int(vp->vp_integer); +#endif + + case PW_TYPE_SIGNED: + return json_object_new_int(vp->vp_signed); + } +} + +/** Escapes string for use as a JSON string + * + * @param ctx Talloc context to allocate this string + * @param s Input string + * @param include_quotes Include the surrounding quotes of JSON strings + * @return New allocated character string, or NULL if something failed. + */ +char *fr_json_from_string(TALLOC_CTX *ctx, char const *s, bool include_quotes) +{ + char const *p; + char *out = NULL; + struct json_object *json; + int len; + + json = json_object_new_string(s); + if (!json) return NULL; + + if ((p = json_object_to_json_string(json))) { + if (include_quotes) { + out = talloc_typed_strdup(ctx, p); + } else { + len = strlen(p); + out = talloc_bstrndup(ctx, p + 1, len - 2); /* to_json_string adds quotes (") */ + } + } + + /* + * Free the JSON structure, it's not needed any more + */ + json_object_put_assert(json); + + return out; +} + + +/** Convert VALUE_PAIR into a JSON object + * + * If inst.enum_as_int is set, and the given VP is an enum + * value, the integer value is returned as a json_object rather + * than the text representation. + * + * If inst.always_string is set then a numeric value pair + * will be returned as a JSON string object. + * + * @param[in] ctx Talloc context. + * @param[out] out returned json object. + * @param[in] vp to get the value of. + * @param[in] inst format definition, or NULL. + * @return + * - 1 if 'out' is the integer enum value, 0 otherwise + * - -1 on error. + */ +static int json_afrom_value_pair(TALLOC_CTX *ctx, json_object **out, + VALUE_PAIR *vp, rlm_json_t const *inst) +{ + struct json_object *obj; + int is_enum = 0; + + fr_assert(vp); + fr_assert(inst); + + MEM(obj = json_object_from_attr_value(ctx, vp, inst->always_string, inst->enum_as_int)); + + *out = obj; + return is_enum; +} + + +/** Add prefix to attribute name + * + * If the format "attr.prefix" string is set then prepend this + * to the given attribute name, otherwise return name unchanged. + * + * @param[out] buf where to write the new name, if set + * @param[in] buf_len length of buf + * @param[in] name original attribute name + * @param[in] inst json format structure + * @return pointer to name, or buf if the prefix was added + */ +static inline char const *attr_name_with_prefix(char *buf, size_t buf_len, const char *name, rlm_json_t const *inst) +{ + int len; + + if (!inst->attr_prefix) return name; + + len = snprintf(buf, buf_len, "%s:%s", inst->attr_prefix, name); + + if (len == (int)strlen(buf)) { + return buf; + } + + return name; +} + + +/** Verify that the options in rlm_json_t are valid + * + * Warnings are optional, will fatal error if the format is corrupt. + * + * @param[in] inst the format structure to check + * @param[in] verbose print out warnings if set + * @return true if format is good, otherwise false + */ +bool fr_json_format_verify(rlm_json_t const *inst, bool verbose) +{ + bool ret = true; + + fr_assert(inst); + + switch (inst->output_mode) { + case JSON_MODE_OBJECT: + case JSON_MODE_OBJECT_SIMPLE: + case JSON_MODE_ARRAY: + /* all options are valid */ + return true; + case JSON_MODE_ARRAY_OF_VALUES: + if (inst->attr_prefix) { + if (verbose) WARN("attribute name prefix not valid in output_mode 'array_of_values' and will be ignored"); + ret = false; + } + if (inst->value_as_array) { + if (verbose) WARN("'value_as_array' not valid in output_mode 'array_of_values' and will be ignored"); + ret = false; + } + return ret; + case JSON_MODE_ARRAY_OF_NAMES: + if (inst->value_as_array) { + if (verbose) WARN("'value_as_array' not valid in output_mode 'array_of_names' and will be ignored"); + ret = false; + } + if (inst->enum_as_int) { + if (verbose) WARN("'enum_as_int' not valid in output_mode 'array_of_names' and will be ignored"); + ret = false; + } + if (inst->always_string) { + if (verbose) WARN("'always_string' not valid in output_mode 'array_of_names' and will be ignored"); + ret = false; + } + return ret; + default: + ERROR("JSON format output mode is invalid"); + } + + /* If we get here, something has gone wrong */ + fr_assert(0); + + return false; +} + + +/** Returns a JSON object representation of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "object" format, JSON_MODE_OBJECT. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] inst Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static json_object *json_object_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + rlm_json_t const *inst) +{ + VALUE_PAIR *vp; + struct json_object *obj; + char buf[DICT_ATTR_MAX_NAME_LEN + 32]; + + /* Check format and type */ + fr_assert(inst); + fr_assert(inst->output_mode == JSON_MODE_OBJECT); + + MEM(obj = json_object_new_object()); + + for (vp = vps; + vp; + vp = vp->next) { + char const *attr_name; + struct json_object *vp_object, *values, *value, *type_name; + + /* + * Get attribute name and value. + */ + attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, inst); + + if (json_afrom_value_pair(ctx, &value, vp, inst) < 0) { + fr_strerror_printf("Failed to convert attribute value to JSON object"); + error: + json_object_put_assert(obj); + + return NULL; + } + + /* + * Look in the table to see if we already have + * a key for the attribute we're working on. + */ + if (!json_object_object_get_ex(obj, attr_name, &vp_object)) { + /* + * Wasn't there, so create a new object for this attribute. + */ + MEM(vp_object = json_object_new_object()); + json_object_object_add(obj, attr_name, vp_object); + + /* + * Add "type" to newly created keys. + */ + MEM(type_name = json_object_new_string(fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"))); + json_object_object_add_ex(vp_object, "type", type_name, JSON_C_OBJECT_KEY_IS_CONSTANT); + + /* + * Create a "value" array to hold any attribute values for this attribute... + */ + if (inst->value_as_array) { + MEM(values = json_object_new_array()); + json_object_object_add_ex(vp_object, "value", values, JSON_C_OBJECT_KEY_IS_CONSTANT); + } else { + /* + * ...unless this is the first time we've seen the attribute and + * value_as_array is false, in which case just add the value directly + * and move on to the next attribute. + */ + json_object_object_add_ex(vp_object, "value", value, JSON_C_OBJECT_KEY_IS_CONSTANT); + continue; + } + } else { + /* + * Find the 'values' array to add the current value to. + */ + if (!json_object_object_get_ex(vp_object, "value", &values)) { + fr_strerror_printf("Inconsistent JSON tree"); + goto error; + } + + /* + * If value_as_array is no set then "values" may not be an array, so it will + * need converting to an array to add this extra attribute. + */ + if (!inst->value_as_array) { + json_type type; + struct json_object *convert_value = values; + + /* Check "values" type */ + type = json_object_get_type(values); + + /* It wasn't an array, so turn it into one with the old value as the first entry */ + if (type != json_type_array) { + MEM(values = json_object_new_array()); + json_object_array_add(values, json_object_get(convert_value)); + json_object_object_del(vp_object, "value"); + json_object_object_add_ex(vp_object, "value", values, + JSON_C_OBJECT_KEY_IS_CONSTANT); + } + } + } + + /* + * Append to the JSON array. + */ + json_object_array_add(values, value); + } + + return obj; +} + + +/** Returns a JSON object representation of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "simple object" format, JSON_MODE_OBJECT_SIMPLE. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] inst Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static json_object *json_smplobj_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + rlm_json_t const *inst) +{ + VALUE_PAIR *vp; + struct json_object *obj; + char buf[DICT_ATTR_MAX_NAME_LEN + 32]; + json_type type; + + /* Check format and type */ + fr_assert(inst); + fr_assert(inst->output_mode == JSON_MODE_OBJECT_SIMPLE); + + MEM(obj = json_object_new_object()); + + for (vp = vps; + vp; + vp = vp->next) { + char const *attr_name; + struct json_object *vp_object, *value; + struct json_object *values = NULL; + bool add_single = false; + + /* + * Get attribute name and value. + */ + attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, inst); + + if (json_afrom_value_pair(ctx, &value, vp, inst) < 0) { + fr_strerror_printf("Failed to convert attribute value to JSON object"); + + json_object_put_assert(obj); + return NULL; + } + + /* + * See if we already have a key in the table we're working on, + * if not then create a new one. + */ + if (!json_object_object_get_ex(obj, attr_name, &vp_object)) { + if (inst->value_as_array) { + /* + * We have been asked to ensure /all/ values are lists, + * even if there's only one attribute. + */ + MEM(values = json_object_new_array()); + json_object_object_add(obj, attr_name, values); + } else { + /* + * Deal with it later on. + */ + add_single = true; + } + /* + * If we do have the key already, get its value array. + */ + } else { + type = json_object_get_type(vp_object); + + if (type == json_type_array) { + values = vp_object; + } else { + /* + * We've seen one of these before, but didn't add + * it as an array the first time. Sort that out. + */ + MEM(values = json_object_new_array()); + json_object_array_add(values, json_object_get(vp_object)); + + /* + * Existing key will have refcount decremented + * and will be freed if thise drops to zero. + */ + json_object_object_add(obj, attr_name, values); + } + } + + if (add_single) { + /* + * Only ever used the first time adding a new + * attribute when "value_as_array" is not set. + */ + json_object_object_add(obj, attr_name, value); + } else { + /* + * Otherwise we're always appending to a JSON array. + */ + json_object_array_add(values, value); + } + } + + return obj; +} + + +/** Returns a JSON array representation of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "array" format, JSON_MODE_ARRAY. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] inst Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static struct json_object *json_array_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + rlm_json_t const *inst) +{ + VALUE_PAIR *vp; + struct json_object *obj; + struct json_object *seen_attributes = NULL; + char buf[DICT_ATTR_MAX_NAME_LEN + 32]; + + /* Check format and type */ + fr_assert(inst); + fr_assert(inst->output_mode == JSON_MODE_ARRAY); + + MEM(obj = json_object_new_array()); + + /* + * If attribute values should be in a list format, then keep track + * of the attributes we've previously seen in a JSON object. + */ + if (inst->value_as_array) { + seen_attributes = json_object_new_object(); + } + + for (vp = vps; + vp; + vp = vp->next) { + char const *attr_name; + struct json_object *name, *value, *type_name; + struct json_object *values = NULL; + struct json_object *attrobj = NULL; + bool already_seen = false; + + /* + * Get attribute name and value. + */ + attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, inst); + + if (json_afrom_value_pair(ctx, &value, vp, inst) < 0) { + fr_strerror_printf("Failed to convert attribute value to JSON object"); + json_object_put_assert(obj); + return NULL; + } + + if (inst->value_as_array) { + /* + * Try and find this attribute in the "seen_attributes" object. If it is + * there then get the "values" array to add this attribute value to. + */ + already_seen = json_object_object_get_ex(seen_attributes, attr_name, &values); + } + + /* + * If we're adding all attributes to the toplevel array, or we're adding values + * to an array of an existing attribute but haven't seen it before, then we need + * to create a new JSON object for this attribute. + */ + if (!inst->value_as_array || !already_seen) { + /* + * Create object and add it to top-level array + */ + MEM(attrobj = json_object_new_object()); + json_object_array_add(obj, attrobj); + + /* + * Add the attribute name in the "name" key and the type in the "type" key + */ + MEM(name = json_object_new_string(attr_name)); + json_object_object_add_ex(attrobj, "name", name, JSON_C_OBJECT_KEY_IS_CONSTANT); + + MEM(type_name = json_object_new_string(fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"))); + json_object_object_add_ex(attrobj, "type", type_name, JSON_C_OBJECT_KEY_IS_CONSTANT); + } + + if (inst->value_as_array) { + /* + * We're adding values to an array for the first copy of this attribute + * that we saw. First time around we need to create an array. + */ + if (!already_seen) { + MEM(values = json_object_new_array()); + /* + * Add "value":[] key to the attribute object + */ + json_object_object_add_ex(attrobj, "value", values, JSON_C_OBJECT_KEY_IS_CONSTANT); + + /* + * Also add to "seen_attributes" to check later + */ + json_object_object_add(seen_attributes, attr_name, json_object_get(values)); + } + + /* + * Always add the value to the respective "values" array. + */ + json_object_array_add(values, value); + } else { + /* + * This is simpler; just add a "value": key to the attribute object. + */ + json_object_object_add_ex(attrobj, "value", value, JSON_C_OBJECT_KEY_IS_CONSTANT); + } + + } + + /* + * No longer need the "seen_attributes" object, it was just used for tracking. + */ + if (inst->value_as_array) { + json_object_put_assert(seen_attributes); + } + + return obj; +} + + +/** Returns a JSON array of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "array_of_values" format, + * JSON_MODE_ARRAY_OF_VALUES, listing just the attribute values. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] inst Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static struct json_object *json_value_array_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + rlm_json_t const *inst) +{ + VALUE_PAIR *vp; + struct json_object *obj; + + /* Check format and type */ + fr_assert(inst); + fr_assert(inst->output_mode == JSON_MODE_ARRAY_OF_VALUES); + + MEM(obj = json_object_new_array()); + + /* + * This array format is very simple - just add all the + * attribute values to the array in order. + */ + for (vp = vps; + vp; + vp = vp->next) { + struct json_object *value; + + if (json_afrom_value_pair(ctx, &value, vp, inst) < 0) { + fr_strerror_printf("Failed to convert attribute value to JSON object"); + json_object_put_assert(obj); + return NULL; + } + + json_object_array_add(obj, value); + } + + return obj; +} + + +/** Returns a JSON array of a list of value pairs + * + * The result is a struct json_object, which should be free'd with + * json_object_put() by the caller. Intended to only be called by + * fr_json_afrom_pair_list(). + * + * This function generates the "array_of_names" format, + * JSON_MODE_ARRAY_OF_NAMES, listing just the attribute names. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] inst Formatting control, must be set. + * @return JSON object with the generated representation. + */ +static struct json_object *json_attr_array_afrom_pair_list(UNUSED TALLOC_CTX *ctx, VALUE_PAIR *vps, + rlm_json_t const *inst) +{ + VALUE_PAIR *vp; + struct json_object *obj; + char buf[DICT_ATTR_MAX_NAME_LEN + 32]; + + /* Check format and type */ + fr_assert(inst); + fr_assert(inst->output_mode == JSON_MODE_ARRAY_OF_NAMES); + + MEM(obj = json_object_new_array()); + + /* + * Add all the attribute names to the array in order. + */ + for (vp = vps; + vp; + vp = vp->next) { + char const *attr_name; + struct json_object *value; + + attr_name = attr_name_with_prefix(buf, sizeof(buf), vp->da->name, inst); + value = json_object_new_string(attr_name); + + json_object_array_add(obj, value); + } + + return obj; +} + + +/** Returns a JSON string of a list of value pairs + * + * The result is a talloc-ed string, freeing the string is + * the responsibility of the caller. + * + * The 'inst' format struct contains settings to configure the output + * JSON document format. + * @see fr_json_format_s + * + * @param[in] ctx Talloc context. + * @param[in] vps a list of value pairs. + * @param[in] inst Formatting control, can be NULL to use default format. + * @return JSON string representation of the value pairs + */ +char *fr_json_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, + rlm_json_t const *inst) +{ + struct json_object *obj = NULL; + const char *p; + char *out; + + rad_assert(inst); + + switch (inst->output_mode) { + case JSON_MODE_OBJECT: + MEM(obj = json_object_afrom_pair_list(ctx, vps, inst)); + break; + case JSON_MODE_OBJECT_SIMPLE: + MEM(obj = json_smplobj_afrom_pair_list(ctx, vps, inst)); + break; + case JSON_MODE_ARRAY: + MEM(obj = json_array_afrom_pair_list(ctx, vps, inst)); + break; + case JSON_MODE_ARRAY_OF_VALUES: + MEM(obj = json_value_array_afrom_pair_list(ctx, vps, inst)); + break; + case JSON_MODE_ARRAY_OF_NAMES: + MEM(obj = json_attr_array_afrom_pair_list(ctx, vps, inst)); + break; + default: + /* This should never happen */ + rad_assert(0); + } + + /* + * p is a buff inside obj, and will be freed + * when it is freed. + */ + MEM(p = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN)); + MEM(out = talloc_typed_strdup(ctx, p)); + + /* + * Free the JSON structure, it's not needed any more + */ + json_object_put_assert(obj); + + return out; +} |