The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
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: b981216ad36b46f1be5c2ec1a6ee14941e9654d0 $")
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 <freeradius-devel/json/base.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 void const *builder;
193 build_fn_t build;
194
195 if (!type) {
196 fprintf(stderr, "item without type field\n");
197 return -1;
198 }
199
200 builder = fr_table_value_by_str(item_builders, type, NULL);
201 build = (build_fn_t)(uintptr_t)builder;
202 if (!build) {
203 fprintf(stderr, "unknown item type %s\n", type);
204 return -1;
205 }
206 return build(parent, item);
207}
208
209/*
210 * Build a top-level CONF_SECTION from a JSON object that represents
211 * the root section. Returns the allocated CONF_SECTION on success,
212 * NULL on failure.
213 */
214static CONF_SECTION *build_root_section(TALLOC_CTX *ctx, struct json_object *root)
215{
216 CONF_SECTION *cs;
217 struct json_object *children;
218 char const *name1 = json_get_str(root, "name1");
219 char const *name2 = json_get_str(root, "name2");
220 char const *filename;
221 int lineno;
222
223 json_get_location(root, &filename, &lineno);
224
225 if (!name1) name1 = "main";
226
227 cs = cf_section_alloc(ctx, NULL, name1, name2);
228 if (!cs) return NULL;
229
230 if (filename) cf_filename_set(cs, filename);
231 if (lineno) cf_lineno_set(cs, lineno);
232
233 if (json_object_object_get_ex(root, "children", &children) && children) {
234 size_t n = json_object_array_length(children);
235 for (size_t i = 0; i < n; i++) {
236 struct json_object *child = json_object_array_get_idx(children, i);
237 if (build_item(cs, child) < 0) {
238 talloc_free(cs);
239 return NULL;
240 }
241 }
242 }
243
244 return cs;
245}
246
247static NEVER_RETURNS void usage(int rcode)
248{
249 FILE *fp = (rcode == 0) ? stdout : stderr;
250
251 fprintf(fp, "Usage: radjson2conf [options]\n"
252 " -i <file> Read JSON from <file> (default stdin).\n"
253 " -o <file> Write conf to <file> (default stdout).\n"
254 " -r Strip the root section wrapper, emit children at file scope.\n"
255 " Use this to produce a radiusd.conf-style top-level file.\n"
256 " -h This help.\n");
257 exit(rcode);
258}
259
260int main(int argc, char *argv[])
261{
262 int c;
263 char const *input_file = NULL;
264 char const *output_file = NULL;
265 bool strip_root = false;
266 FILE *out;
267 TALLOC_CTX *autofree;
268 struct json_object *root_json = NULL;
269 CONF_SECTION *root_cs;
270 int rcode = EXIT_SUCCESS;
271
273
274 /*
275 * We're rebuilding a CF tree out of JSON, fragment by fragment,
276 * to emit it back to disk. Resolving `${var}` references would
277 * either fail (the variable lives in a sibling fragment, not in
278 * the one we're parsing) or silently bake values in - both
279 * wrong for the round-trip. Keep variables verbatim.
280 */
282
283 while ((c = getopt(argc, argv, "hi:o:r")) != -1) {
284 switch (c) {
285 case 'h':
286 usage(EXIT_SUCCESS);
287
288 case 'i':
289 input_file = optarg;
290 break;
291
292 case 'o':
293 output_file = optarg;
294 break;
295
296 case 'r':
297 strip_root = true;
298 break;
299
300 default:
301 usage(EXIT_FAILURE);
302 }
303 }
304
305 if (input_file) {
306 root_json = json_object_from_file(input_file);
307 if (!root_json) {
308 fprintf(stderr, "Failed to parse %s: %s\n", input_file, json_util_get_last_err());
309 return EXIT_FAILURE;
310 }
311 } else {
312 /* Slurp stdin and parse */
313 char buf[65536];
314 ssize_t n;
315 struct json_tokener *tok = json_tokener_new();
316
317 while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
318 root_json = json_tokener_parse_ex(tok, buf, (int)n); /* n <= sizeof(buf) */
319 if (json_tokener_get_error(tok) == json_tokener_continue) continue;
320 if (json_tokener_get_error(tok) != json_tokener_success) {
321 fprintf(stderr, "JSON parse error: %s\n",
322 json_tokener_error_desc(json_tokener_get_error(tok)));
323 json_tokener_free(tok);
324 return EXIT_FAILURE;
325 }
326 break;
327 }
328 json_tokener_free(tok);
329
330 if (!root_json) {
331 fprintf(stderr, "No JSON read from stdin\n");
332 return EXIT_FAILURE;
333 }
334 }
335
336 root_cs = build_root_section(autofree, root_json);
337 if (!root_cs) {
338 fprintf(stderr, "Failed to build conf tree\n");
339 json_object_put(root_json);
340 return EXIT_FAILURE;
341 }
342
343 if (output_file) {
344 out = fopen(output_file, "w");
345 if (!out) {
346 fprintf(stderr, "Failed opening %s: %s\n", output_file, fr_syserror(errno));
347 rcode = EXIT_FAILURE;
348 goto finish;
349 }
350 } else {
351 out = stdout;
352 }
353
354 /*
355 * Strip-root: write each child of the synthetic root at file
356 * scope, no outer `{ ... }`. cf_section_write_children handles
357 * the same section/pair/comment dispatch (and blank-run
358 * collapsing) that cf_section_write does internally; we just
359 * skip the wrapper.
360 */
361 if (strip_root) {
362 if (cf_section_write_children(out, root_cs, 0) < 0) {
363 fprintf(stderr, "cf_section_write_children failed\n");
364 rcode = EXIT_FAILURE;
365 }
366 } else {
367 if (cf_section_write(out, root_cs, 0) < 0) {
368 fprintf(stderr, "cf_section_write failed\n");
369 rcode = EXIT_FAILURE;
370 }
371 }
372 if (out != stdout) fclose(out);
373
374finish:
375 talloc_free(root_cs);
376 json_object_put(root_json);
377 return rcode;
378}
int n
Definition acutest.h:642
#define RCSID(id)
Definition build.h:603
#define NEVER_RETURNS
Should be placed before the function return type.
Definition build.h:413
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:245
#define NUM_ELEMENTS(_t)
Definition build.h:437
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:4334
int cf_section_write(FILE *fp, CONF_SECTION *cs, int depth)
Definition cf_file.c:4279
A # ... comment line preserved verbatim from the input.
Definition cf_priv.h:185
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:1487
CONF_COMMENT * cf_comment_alloc(CONF_SECTION *parent, char const *text)
Allocate a new comment item attached to parent.
Definition cf_util.c:821
void cf_expand_variables_set(bool expand)
Opt out of ${var} expansion when reading config (default: enabled).
Definition cf_util.c:811
#define cf_lineno_set(_ci, _lineno)
Definition cf_util.h:183
#define cf_section_alloc(_ctx, _parent, _name1, _name2)
Definition cf_util.h:198
#define cf_filename_set(_ci, _filename)
Definition cf_util.h:180
TALLOC_CTX * autofree
Definition common.c:29
static NEVER_RETURNS void usage(void)
Definition dhcpclient.c:113
Test enumeration values.
Definition dict_test.h:92
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
@ T_OP_EQ
Definition token.h:81
enum fr_token fr_token_t
static fr_slen_t parent
Definition pair.h:864
#define RADIUSD_VERSION_BUILD(_x)
Create a version string for a utility in the suite of FreeRADIUS utilities.
Definition version.h:58
static fr_sbuff_err_t char ** out
Definition value.h:1061