The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
radjson2conf.c
Go to the documentation of this file.
1/*
2 * radjson2conf.c Render a FreeRADIUS v4 config JSON tree back to a .conf file.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 *
18 * Copyright (C) 2026 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
19 */
20
21RCSID("$Id: 64b1eae4ca9278e86f0391cef83271d5d6e494c3 $")
22
23#include <freeradius-devel/server/base.h>
24#include <freeradius-devel/server/cf_file.h>
25#include <freeradius-devel/server/cf_util.h>
26#include <freeradius-devel/util/atexit.h>
27#include <freeradius-devel/util/debug.h>
28#include <freeradius-devel/util/syserror.h>
29
30#include <json-c/json.h>
31
32#ifdef HAVE_GETOPT_H
33# include <getopt.h>
34#endif
35
36char const *radiusd_version = RADIUSD_VERSION_BUILD("radjson2conf");
37
38/*
39 * `fr_token_from_quote_enum_str()` (src/lib/util/token.c) inverts
40 * `fr_token_to_enum_str` for the five quote-typed tokens - i.e.
41 * the exact strings radconf2json emits for `lhs_quote` / `rhs_quote`.
42 *
43 * `fr_tokens_table` already maps operator strings ("=", ":=", "+=", ...)
44 * to their `fr_token_t` value, which is what the CF parser itself
45 * uses; reuse it for the `op` field.
46 */
47#define quote_token(_s) fr_token_from_quote_enum_str((_s), T_BARE_WORD)
48
49static inline fr_token_t op_token(char const *s)
50{
51 if (!s) return T_OP_EQ;
53}
54
55static char const *json_get_str(struct json_object *o, char const *key)
56{
57 struct json_object *v;
58 if (!json_object_object_get_ex(o, key, &v)) return NULL;
59 if (!v || json_object_is_type(v, json_type_null)) return NULL;
60 return json_object_get_string(v);
61}
62
63static int json_get_int(struct json_object *o, char const *key, int dflt)
64{
65 struct json_object *v;
66 if (!json_object_object_get_ex(o, key, &v)) return dflt;
67 if (!v || json_object_is_type(v, json_type_null)) return dflt;
68 return json_object_get_int(v);
69}
70
71/*
72 * Pull `location.filename` / `location.lineno` out of a nested object.
73 * The location object is itself optional - emit nothing if absent.
74 */
75static void json_get_location(struct json_object *item, char const **filename, int *lineno)
76{
77 struct json_object *loc;
78
79 if (!json_object_object_get_ex(item, "location", &loc)) {
80 error:
81 *filename = NULL;
82 *lineno = 0;
83 return;
84 }
85 if (!loc || json_object_is_type(loc, json_type_null)) goto error;
86
87 *filename = json_get_str(loc, "filename");
88 *lineno = json_get_int(loc, "lineno", 0);
89}
90
91static int build_item(CONF_SECTION *parent, struct json_object *item);
92
93static int build_pair(CONF_SECTION *parent, struct json_object *item)
94{
95 CONF_PAIR *cp;
96 char const *attr = json_get_str(item, "attr");
97 char const *value = json_get_str(item, "value");
98 char const *op_s = json_get_str(item, "op");
99 char const *lhs_q_s = json_get_str(item, "lhs_quote");
100 char const *rhs_q_s = json_get_str(item, "rhs_quote");
101 char const *filename;
102 int lineno;
103
104 json_get_location(item, &filename, &lineno);
105
106 if (!attr) {
107 fprintf(stderr, "pair without attr (line %d)\n", lineno);
108 return -1;
109 }
110
111 cp = cf_pair_alloc(parent, attr, value, op_token(op_s), quote_token(lhs_q_s), quote_token(rhs_q_s));
112 if (!cp) return -1;
113
114 /*
115 * cf_section_write skips pairs whose filename is NULL or
116 * starts with '<' (the marker for synthetic items). Default
117 * to "converted" when the JSON has no location so converter-
118 * added pairs survive the round-trip.
119 */
120 cf_filename_set(cp, (filename && filename[0] != '<') ? filename : "converted");
121 cf_lineno_set(cp, lineno > 0 ? lineno : 1);
122
123 return 0;
124}
125
126static int build_section_into(CONF_SECTION *parent, struct json_object *item)
127{
128 CONF_SECTION *cs;
129 struct json_object *children;
130 char const *name1 = json_get_str(item, "name1");
131 char const *name2 = json_get_str(item, "name2");
132 char const *filename;
133 int lineno;
134
135 json_get_location(item, &filename, &lineno);
136
137 if (!name1) {
138 fprintf(stderr, "section without name1 (line %d)\n", lineno);
139 return -1;
140 }
141
142 cs = cf_section_alloc(parent, parent, name1, name2);
143 if (!cs) return -1;
144
145 if (filename) cf_filename_set(cs, filename);
146 if (lineno) cf_lineno_set(cs, lineno);
147
148 if (json_object_object_get_ex(item, "children", &children) && children) {
149 size_t n = json_object_array_length(children);
150 for (size_t i = 0; i < n; i++) {
151 struct json_object *child = json_object_array_get_idx(children, i);
152 if (build_item(cs, child) < 0) return -1;
153 }
154 }
155
156 return 0;
157}
158
159static int build_comment(CONF_SECTION *parent, struct json_object *item)
160{
161 CONF_COMMENT *c;
162 char const *text = json_get_str(item, "text");
163 char const *filename;
164 int lineno;
165
166 json_get_location(item, &filename, &lineno);
167
168 c = cf_comment_alloc(parent, text);
169 if (!c) return -1;
170
171 cf_filename_set(c, (filename && filename[0] != '<') ? filename : "converted");
172 cf_lineno_set(c, lineno > 0 ? lineno : 1);
173 return 0;
174}
175
176/*
177 * JSON `type` -> builder dispatch. Keep alphabetically sorted so
178 * the fr_table_value_by_str binary search lookup works.
179 */
180typedef int (*build_fn_t)(CONF_SECTION *parent, struct json_object *item);
181
182static int build_item(CONF_SECTION *parent, struct json_object *item)
183{
184 static fr_table_ptr_sorted_t const item_builders[] = {
185 { L("comment"), (void *) build_comment },
186 { L("pair"), (void *) build_pair },
187 { L("section"), (void *) build_section_into },
188 };
189 static size_t item_builders_len = NUM_ELEMENTS(item_builders);
190
191 char const *type = json_get_str(item, "type");
192 build_fn_t build;
193
194 if (!type) {
195 fprintf(stderr, "item without type field\n");
196 return -1;
197 }
198
199 build = (build_fn_t)(uintptr_t)fr_table_value_by_str(item_builders, type, NULL);
200 if (!build) {
201 fprintf(stderr, "unknown item type %s\n", type);
202 return -1;
203 }
204 return build(parent, item);
205}
206
207/*
208 * Build a top-level CONF_SECTION from a JSON object that represents
209 * the root section. Returns the allocated CONF_SECTION on success,
210 * NULL on failure.
211 */
212static CONF_SECTION *build_root_section(TALLOC_CTX *ctx, struct json_object *root)
213{
214 CONF_SECTION *cs;
215 struct json_object *children;
216 char const *name1 = json_get_str(root, "name1");
217 char const *name2 = json_get_str(root, "name2");
218 char const *filename;
219 int lineno;
220
221 json_get_location(root, &filename, &lineno);
222
223 if (!name1) name1 = "main";
224
225 cs = cf_section_alloc(ctx, NULL, name1, name2);
226 if (!cs) return NULL;
227
228 if (filename) cf_filename_set(cs, filename);
229 if (lineno) cf_lineno_set(cs, lineno);
230
231 if (json_object_object_get_ex(root, "children", &children) && children) {
232 size_t n = json_object_array_length(children);
233 for (size_t i = 0; i < n; i++) {
234 struct json_object *child = json_object_array_get_idx(children, i);
235 if (build_item(cs, child) < 0) {
236 talloc_free(cs);
237 return NULL;
238 }
239 }
240 }
241
242 return cs;
243}
244
245static NEVER_RETURNS void usage(int rcode)
246{
247 FILE *fp = (rcode == 0) ? stdout : stderr;
248
249 fprintf(fp, "Usage: radjson2conf [options]\n"
250 " -i <file> Read JSON from <file> (default stdin).\n"
251 " -o <file> Write conf to <file> (default stdout).\n"
252 " -r Strip the root section wrapper, emit children at file scope.\n"
253 " Use this to produce a radiusd.conf-style top-level file.\n"
254 " -h This help.\n");
255 exit(rcode);
256}
257
258int main(int argc, char *argv[])
259{
260 int c;
261 char const *input_file = NULL;
262 char const *output_file = NULL;
263 bool strip_root = false;
264 FILE *out;
265 TALLOC_CTX *autofree;
266 struct json_object *root_json = NULL;
267 CONF_SECTION *root_cs;
268 int rcode = EXIT_SUCCESS;
269
271
272 /*
273 * We're rebuilding a CF tree out of JSON, fragment by fragment,
274 * to emit it back to disk. Resolving `${var}` references would
275 * either fail (the variable lives in a sibling fragment, not in
276 * the one we're parsing) or silently bake values in - both
277 * wrong for the round-trip. Keep variables verbatim.
278 */
280
281 while ((c = getopt(argc, argv, "hi:o:r")) != -1) {
282 switch (c) {
283 case 'h':
284 usage(EXIT_SUCCESS);
285
286 case 'i':
287 input_file = optarg;
288 break;
289
290 case 'o':
291 output_file = optarg;
292 break;
293
294 case 'r':
295 strip_root = true;
296 break;
297
298 default:
299 usage(EXIT_FAILURE);
300 }
301 }
302
303 if (input_file) {
304 root_json = json_object_from_file(input_file);
305 if (!root_json) {
306 fprintf(stderr, "Failed to parse %s: %s\n", input_file, json_util_get_last_err());
307 return EXIT_FAILURE;
308 }
309 } else {
310 /* Slurp stdin and parse */
311 char buf[65536];
312 ssize_t n;
313 struct json_tokener *tok = json_tokener_new();
314
315 while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
316 root_json = json_tokener_parse_ex(tok, buf, n);
317 if (json_tokener_get_error(tok) == json_tokener_continue) continue;
318 if (json_tokener_get_error(tok) != json_tokener_success) {
319 fprintf(stderr, "JSON parse error: %s\n",
320 json_tokener_error_desc(json_tokener_get_error(tok)));
321 json_tokener_free(tok);
322 return EXIT_FAILURE;
323 }
324 break;
325 }
326 json_tokener_free(tok);
327
328 if (!root_json) {
329 fprintf(stderr, "No JSON read from stdin\n");
330 return EXIT_FAILURE;
331 }
332 }
333
334 root_cs = build_root_section(autofree, root_json);
335 if (!root_cs) {
336 fprintf(stderr, "Failed to build conf tree\n");
337 json_object_put(root_json);
338 return EXIT_FAILURE;
339 }
340
341 if (output_file) {
342 out = fopen(output_file, "w");
343 if (!out) {
344 fprintf(stderr, "Failed opening %s: %s\n", output_file, fr_syserror(errno));
345 rcode = EXIT_FAILURE;
346 goto finish;
347 }
348 } else {
349 out = stdout;
350 }
351
352 /*
353 * Strip-root: write each child of the synthetic root at file
354 * scope, no outer `{ ... }`. cf_section_write_children handles
355 * the same section/pair/comment dispatch (and blank-run
356 * collapsing) that cf_section_write does internally; we just
357 * skip the wrapper.
358 */
359 if (strip_root) {
360 if (cf_section_write_children(out, root_cs, 0) < 0) {
361 fprintf(stderr, "cf_section_write_children failed\n");
362 rcode = EXIT_FAILURE;
363 }
364 } else {
365 if (cf_section_write(out, root_cs, 0) < 0) {
366 fprintf(stderr, "cf_section_write failed\n");
367 rcode = EXIT_FAILURE;
368 }
369 }
370 if (out != stdout) fclose(out);
371
372finish:
373 talloc_free(root_cs);
374 json_object_put(root_json);
375 return rcode;
376}
int n
Definition acutest.h:577
#define RCSID(id)
Definition build.h:512
#define NEVER_RETURNS
Should be placed before the function return type.
Definition build.h:334
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:228
#define NUM_ELEMENTS(_t)
Definition build.h:358
int cf_section_write_children(FILE *fp, CONF_SECTION *cs, int depth)
Emit the children of a section at depth without an enclosing { ... }.
Definition cf_file.c:3903
int cf_section_write(FILE *fp, CONF_SECTION *cs, int depth)
Definition cf_file.c:3848
A # ... comment line preserved verbatim from the input.
Definition cf_priv.h:145
Configuration AVP similar to a fr_pair_t.
Definition cf_priv.h:77
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
CONF_PAIR * cf_pair_alloc(CONF_SECTION *parent, char const *attr, char const *value, fr_token_t op, fr_token_t lhs_quote, fr_token_t rhs_quote)
Allocate a CONF_PAIR.
Definition cf_util.c:1388
CONF_COMMENT * cf_comment_alloc(CONF_SECTION *parent, char const *text)
Allocate a new comment item attached to parent.
Definition cf_util.c:842
void cf_expand_variables_set(bool expand)
Opt out of ${var} expansion when reading config (default: enabled).
Definition cf_util.c:832
#define cf_lineno_set(_ci, _lineno)
Definition cf_util.h:186
#define cf_section_alloc(_ctx, _parent, _name1, _name2)
Definition cf_util.h:201
#define cf_filename_set(_ci, _filename)
Definition cf_util.h:183
static NEVER_RETURNS void usage(void)
Definition dhcpclient.c:113
Test enumeration values.
Definition dict_test.h:92
static TALLOC_CTX * autofree
Definition fuzzer.c:44
talloc_free(hp)
static void * item(fr_lst_t const *lst, fr_lst_index_t idx)
Definition lst.c:121
long int ssize_t
static void json_get_location(struct json_object *item, char const **filename, int *lineno)
int main(int argc, char *argv[])
int(* build_fn_t)(CONF_SECTION *parent, struct json_object *item)
static CONF_SECTION * build_root_section(TALLOC_CTX *ctx, struct json_object *root)
#define quote_token(_s)
static char const * json_get_str(struct json_object *o, char const *key)
static int json_get_int(struct json_object *o, char const *key, int dflt)
char const * radiusd_version
static int build_section_into(CONF_SECTION *parent, struct json_object *item)
static int build_pair(CONF_SECTION *parent, struct json_object *item)
static int build_comment(CONF_SECTION *parent, struct json_object *item)
static int build_item(CONF_SECTION *parent, struct json_object *item)
static fr_token_t op_token(char const *s)
fr_aka_sim_id_type_t type
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define fr_table_value_by_str(_table, _name, _def)
Convert a string to a value using a sorted or ordered table.
Definition table.h:685
An element in a lexicographically sorted array of name to ptr mappings.
Definition table.h:65
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition talloc.h:55
fr_table_num_ordered_t const fr_tokens_table[]
Definition token.c:33
enum fr_token fr_token_t
@ T_OP_EQ
Definition token.h:81
static fr_slen_t parent
Definition pair.h:858
#define RADIUSD_VERSION_BUILD(_x)
Create a version string for a utility in the suite of FreeRADIUS utilities.
Definition version.h:58
static size_t char ** out
Definition value.h:1030