summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_json/rlm_json.c
blob: 9cf99d832ebd7e87d49398c29ee26bb857a9c092 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
 *   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 rlm_json.c
 * @brief Parses JSON responses
 *
 * @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
 */
RCSID("$Id$")

#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>

#include "json.h"
#include <ctype.h>

#ifndef HAVE_JSON
#  error "rlm_json should not be built unless json-c is available"
#endif


static CONF_PARSER const json_format_attr_config[] = {
	{ "prefix", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_json_t, attr_prefix), NULL },

	CONF_PARSER_TERMINATOR
};

static CONF_PARSER const json_format_value_config[] = {
	{ "single_value_as_array", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_json_t, value_as_array), "no" },
	{ "enum_as_integer", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_json_t, enum_as_int), "no" },
	{ "always_string", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_json_t, always_string), "no" },

	CONF_PARSER_TERMINATOR
};

static CONF_PARSER const fr_json_format_config[] = {
	{ "output_mode", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_json_t, output_mode_str), "object" },
	{ "attribute", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) json_format_attr_config },
	{ "value", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) json_format_value_config },

	CONF_PARSER_TERMINATOR
};

static CONF_PARSER const module_config[] = {
	{ "encode", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) fr_json_format_config },

	CONF_PARSER_TERMINATOR
};


/** Convert given attributes to a JSON document
 *
 * Usage is `%{json_encode:attr tmpl list}`
 *
 * @ingroup xlat_functions
 *
 * @param instance module instance
 * @param request the current request
 * @param fmt input to the xlat
 * @param out where to write the output
 * @param outlen space available for the output
 * @return length of output generated
 */
static ssize_t json_encode_xlat(UNUSED void * instance, REQUEST *request, char const *fmt,
				      char *out, size_t outlen)
{
	rlm_json_t const	*inst = instance;
	ssize_t			slen;
	vp_tmpl_t		*vpt = NULL;
	VALUE_PAIR		*json_vps = NULL, *vps;
	bool			negate;
	char const		*p = fmt;
	char			*json_str = NULL;
	char			*buf;


	/*
	 * 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.
	 */

	p = fmt;

	while (isspace((uint8_t) *p)) p++;
	if (*p == '\0') return -1;

	while (*p) {
		while (isspace((uint8_t) *p)) p++;

		if (*p == '\0') break;

		negate = false;

		/* Check if we should be removing attributes */
		if (*p == '!') {
			p++;
			negate = true;
		}

		if (*p == '\0') {
			/* May happen e.g. with '!' on its own at the end */
			REMARKER(fmt, (p - fmt), "Missing attribute name");
		error:
			fr_pair_list_free(&json_vps);
			talloc_free(vpt);
			return -1;
		}


		/* Decode next attr template */
		slen = tmpl_afrom_attr_substr(request, &vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);

		if (slen <= 0) {
			REMARKER(fmt, (p - fmt) -slen, fr_strerror());
			goto error;
		}

		/*
		 * Get attributes from the template.
		 * Missing attribute isn't an error (so -1, not 0).
		 */
		if (tmpl_copy_vps(request, &vps, request, vpt) < -1) {
			REDEBUG("Error copying attributes");
			goto error;
		}

		if (negate) {
			/* Remove all template attributes from JSON list */
			for (VALUE_PAIR *vp = vps;
			     vp;
			     vp = vp->next) {
				fr_pair_delete_by_da(&json_vps, vp->da);
			}

			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;

		if (*p != '\0' && !isspace((uint8_t)*p)) {
			REMARKER(fmt, (p - fmt), "Missing whitespace");
			goto error;
		}
	}

	/*
	 * 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(buf = talloc_zero_array(request, char, 8192));

	json_str = fr_json_afrom_pair_list(request, json_vps, inst);
	if (!json_str) {
		REDEBUG("Failed to generate JSON string");
		goto error;
	}

	slen = snprintf(out, outlen, "%s", json_str);

	fr_pair_list_free(&json_vps);

	return slen;
}


static int mod_bootstrap(CONF_SECTION *conf, void *instance)
{
	rlm_json_t		*inst = talloc_get_type_abort(instance, rlm_json_t);
	char 			*name;

	inst->name = cf_section_name2(conf);
	if (!inst->name) inst->name = cf_section_name1(conf);

	name = talloc_asprintf(inst, "%s_encode", inst->name);
	xlat_register(name, json_encode_xlat, NULL, inst);
	talloc_free(name);

	/*
	 *	Check the output format type and warn on unused
	 *	format options
	 */
	inst->output_mode = fr_str2int(fr_json_format_table, inst->output_mode_str, JSON_MODE_UNSET);
	if (inst->output_mode == JSON_MODE_UNSET) {
		cf_log_err_cs(conf, "output_mode value \"%s\" is invalid", inst->output_mode_str);
		return -1;
	}
	fr_json_format_verify(inst, true);

	return 0;
}

/*
 *	The module name should be the only globally exported symbol.
 *	That is, everything else should be 'static'.
 *
 *	If the module needs to temporarily modify it's instantiation
 *	data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
 *	The server will then take care of ensuring that the module
 *	is single-threaded.
 */
extern module_t rlm_json;
module_t rlm_json = {
	.magic		= RLM_MODULE_INIT,
	.name		= "json",
	.type		= RLM_TYPE_THREAD_SAFE,
	.inst_size	= sizeof(rlm_json_t),
	.config		= module_config,
	.bootstrap	= mod_bootstrap,
};