* @brief Parses JSON responses
*
* @author Arran Cudbard-Bell
+ * @author Matthew Newton
*
* @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
- * @copyright 2015 Network RADIUS SARL (legal@networkradius.com)
+ * @copyright 2015,2020 Network RADIUS SARL (legal@networkradius.com)
* @copyright 2015 The FreeRADIUS Server Project
*/
RCSID("$Id$")
# error "rlm_json should not be built unless json-c is available"
#endif
+/** rlm_json module instance
+ *
+ */
+typedef struct {
+ char const *name;
+ fr_json_format_t *format;
+} rlm_json_t;
+
+
+static CONF_PARSER const module_config[] = {
+ { FR_CONF_OFFSET("encode", FR_TYPE_SUBSECTION, rlm_json_t, format),
+ .subcs_size = sizeof(fr_json_format_t), .subcs_type = "fr_json_format_t",
+ .subcs = (void const *) fr_json_format_config },
+
+ CONF_PARSER_TERMINATOR
+};
+
+
+/** Boilerplate to copy the pointer to the main module config into the xlat instance data
+ *
+ */
+static int json_xlat_instantiate(void *xlat_inst, UNUSED xlat_exp_t const *exp, void *uctx)
+{
+ *((rlm_json_t **)xlat_inst) = talloc_get_type_abort(uctx, rlm_json_t);
+
+ return 0;
+}
+
+
/** Forms a linked list of jpath head node pointers (a list of jpaths)
*/
typedef struct rlm_json_jpath_cache rlm_json_jpath_cache_t;
return ret;
}
+/** Convert given attributes to a JSON document
+ *
+ * Usage is `%{json_encode:attr tmpl list}`
+ *
+ * @param ctx talloc context
+ * @param out where to write the output
+ * @param request the current request
+ * @param xlat_inst xlat instance data
+ * @param xlat_thread_inst unused
+ * @param in list of value boxes as input
+ * @return XLAT_ACTION_DONE or XLAT_ACTION_FAIL
+ */
+static xlat_action_t json_encode_xlat(TALLOC_CTX *ctx, fr_cursor_t *out, REQUEST *request,
+ void const *xlat_inst, UNUSED void *xlat_thread_inst,
+ fr_value_box_t **in)
+{
+ rlm_json_t const *inst = talloc_get_type_abort_const(*((void const * const *)xlat_inst),
+ rlm_json_t);
+ fr_json_format_t const *format = inst->format;
+
+ ssize_t slen;
+ vp_tmpl_t *vpt = NULL;
+ fr_cursor_t cursor;
+ fr_cursor_t filter;
+ VALUE_PAIR *json_vps = NULL;
+ VALUE_PAIR *vps = NULL;
+ bool negate;
+ char *json_str = NULL;
+ char const *start;
+ char const *p;
+ char const *end;
+ fr_value_box_t *vb;
+
+ if (!in) {
+ REDEBUG("Missing attribute(s)");
+ return XLAT_ACTION_FAIL;
+ }
+
+ if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) {
+ RPEDEBUG("Failed concatenating input");
+ return XLAT_ACTION_FAIL;
+ }
+
+ start = p = (*in)->vb_strvalue;
+ end = p + (*in)->vb_length;
+
+ fr_bskip_whitespace(p, end);
+
+ /*
+ * Iterate through the list of attribute templates in the xlat. For each
+ * one we either add it to the list of attributes for the JSON document
+ * or, if prefixed with '!', remove from the JSON list.
+ */
+ while (p < end) {
+ negate = false;
+
+ /* Check if we should be removing attributes */
+ if (*p == '!') {
+ negate = true;
+ p++;
+ }
+
+ /* Decode next attr template */
+ slen = tmpl_afrom_attr_substr(ctx, NULL, &vpt, p, -1,
+ &(vp_tmpl_rules_t){ .dict_def = request->dict });
+
+ if (slen <= 0) {
+ REMARKER(start, (p-start) + (-slen), "Invalid input");
+ error:
+ fr_pair_list_free(&json_vps);
+ talloc_free(vpt);
+ return XLAT_ACTION_FAIL;
+ }
+
+ /*
+ * Get attributes from the template.
+ * Missing attribute isn't an error (so -1, not 0).
+ */
+ if (tmpl_copy_vps(ctx, &vps, request, vpt) < -1) {
+ RPEDEBUG("Error copying attributes");
+ goto error;
+ }
+
+ if (negate) {
+ /* Remove all template attributes from JSON list */
+ for (VALUE_PAIR *vp = fr_cursor_init(&filter, &vps);
+ vp;
+ vp = fr_cursor_next(&filter)) {
+
+ VALUE_PAIR *vpm = fr_cursor_init(&cursor, &json_vps);
+ while (vpm) {
+ if (vp->da == vpm->da) {
+ talloc_free(fr_cursor_remove(&cursor));
+ vpm = fr_cursor_current(&cursor);
+ continue;
+ }
+ vpm = fr_cursor_next(&cursor);
+ }
+ }
+
+ fr_pair_list_free(&vps);
+ } else {
+ /* Add template VPs to JSON list */
+ fr_pair_add(&json_vps, vps);
+ }
+
+ TALLOC_FREE(vpt);
+
+ /* Jump forward to next attr */
+ p += slen;
+ fr_bskip_whitespace(p, end);
+ }
+
+ /*
+ * Given the list of attributes we now have in json_vps,
+ * convert them into a JSON document and append it to the
+ * return cursor.
+ */
+ MEM(vb = fr_value_box_alloc_null(ctx));
+
+ json_str = fr_json_afrom_pair_list(vb, json_vps, format);
+ if (!json_str) {
+ REDEBUG("Failed to generate JSON string");
+ goto error;
+ }
+
+ if (unlikely(fr_value_box_bstrsteal(vb, vb, NULL, json_str, false) < 0)) {
+ REDEBUG("Failed to allocate JSON string");
+ goto error;
+ }
+
+ fr_cursor_append(out, vb);
+ fr_pair_list_free(&json_vps);
+
+ return XLAT_ACTION_DONE;
+}
+
+
/** Pre-parse and validate literal jpath expressions for maps
*
* @param[in] cs #CONF_SECTION that defined the map instance.
return rcode;
}
-static int mod_bootstrap(void *instance, UNUSED CONF_SECTION *conf)
+static int mod_bootstrap(void *instance, CONF_SECTION *conf)
{
+ rlm_json_t *inst = talloc_get_type_abort(instance, rlm_json_t);
+ xlat_t const *xlat;
+ char *name;
+ fr_json_format_t *format = inst->format;
+
+ inst->name = cf_section_name2(conf);
+ if (!inst->name) inst->name = cf_section_name1(conf);
+
xlat_register(instance, "jsonquote", jsonquote_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
xlat_register(instance, "jpathvalidate", jpath_validate_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
+ name = talloc_asprintf(inst, "%s_encode", inst->name);
+ xlat = xlat_async_register(instance, name, json_encode_xlat);
+ xlat_async_instantiate_set(xlat, json_xlat_instantiate,
+ rlm_json_t *, NULL, inst);
+ talloc_free(name);
+
+ /*
+ * Check the output format type and warn on unused
+ * format options
+ */
+ format->output_mode = fr_table_value_by_str(fr_json_format_table, format->output_mode_str, JSON_MODE_UNSET);
+ if (format->output_mode == JSON_MODE_UNSET) {
+ cf_log_err(conf, "output_mode value \"%s\" is invalid", format->output_mode_str);
+ return -1;
+ }
+ fr_json_format_verify(format, true);
+
if (map_proc_register(instance, "json", mod_map_proc,
mod_map_proc_instantiate, sizeof(rlm_json_jpath_cache_t)) < 0) return -1;
return 0;
.name = "json",
.type = RLM_TYPE_THREAD_SAFE,
.onload = mod_load,
+ .config = module_config,
+ .inst_size = sizeof(rlm_json_t),
.bootstrap = mod_bootstrap,
};