/src/freeradius-server/src/lib/util/pair_print.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /* |
18 | | * Groups are printed from the referenced attribute. |
19 | | * |
20 | | * @todo - parent should _never_ be vp->da. |
21 | | */ |
22 | 0 | #define fr_pair_reset_parent(parent) do { \ |
23 | 0 | if (!parent) break; \ |
24 | 0 | fr_assert(parent != vp->da); \ |
25 | 0 | fr_assert(fr_type_is_structural(parent->type)); \ |
26 | 0 | if (parent->type == FR_TYPE_GROUP) { \ |
27 | 0 | parent = fr_dict_attr_ref(parent); \ |
28 | 0 | if (parent->flags.is_root) { \ |
29 | 0 | parent = NULL; \ |
30 | 0 | break; \ |
31 | 0 | } \ |
32 | 0 | } \ |
33 | 0 | if (parent->dict != vp->da->dict) parent = NULL; \ |
34 | 0 | } while (0) |
35 | | |
36 | | /** Pair serialisation API |
37 | | * |
38 | | * @file src/lib/util/pair_print.c |
39 | | * |
40 | | * @copyright 2020 The FreeRADIUS server project |
41 | | */ |
42 | | #include <freeradius-devel/util/pair.h> |
43 | | #include <freeradius-devel/util/talloc.h> |
44 | | #include <freeradius-devel/util/proto.h> |
45 | | #include <freeradius-devel/util/pair_legacy.h> |
46 | | |
47 | | /** Print the value of an attribute to a string |
48 | | * |
49 | | * @param[in] out Where to write the string. |
50 | | * @param[in] vp to print. |
51 | | * @param[in] quote Char to add before and after printed value, |
52 | | * if 0 no char will be added, if < 0 raw string |
53 | | * will be added. |
54 | | * @return |
55 | | * - >= 0 length of data written to out. |
56 | | * - <0 the number of bytes we would have needed to write |
57 | | * the complete string to out. |
58 | | */ |
59 | | ssize_t fr_pair_print_value_quoted(fr_sbuff_t *out, fr_pair_t const *vp, fr_token_t quote) |
60 | 619 | { |
61 | 619 | fr_sbuff_t our_out; |
62 | 619 | ssize_t slen; |
63 | | |
64 | 619 | PAIR_VERIFY(vp); |
65 | | |
66 | 619 | our_out = FR_SBUFF(out); |
67 | | |
68 | 619 | switch (vp->vp_type) { |
69 | | /* |
70 | | * For structural types descend down |
71 | | */ |
72 | 0 | case FR_TYPE_STRUCTURAL: |
73 | 0 | if (fr_pair_list_empty(&vp->vp_group)) { |
74 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, '{', ' ', '}'); |
75 | |
|
76 | 0 | } else { |
77 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, '{', ' '); |
78 | | |
79 | 0 | FR_SBUFF_RETURN(fr_pair_list_print, &our_out, vp->da, &vp->vp_group); |
80 | | |
81 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, ' ', '}'); |
82 | 0 | } |
83 | | |
84 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
85 | | |
86 | | /* |
87 | | * For simple types just print the box |
88 | | */ |
89 | 619 | default: |
90 | | /* |
91 | | * If it's raw / unknown and not octets, print the cast before the type. |
92 | | * |
93 | | * Otherwise on parsing, we don't know how to interpret the value. :( |
94 | | */ |
95 | 619 | if ((vp->da->flags.is_raw || vp->da->flags.is_unknown) && |
96 | 0 | (vp->vp_type != FR_TYPE_OCTETS)) { |
97 | 0 | FR_SBUFF_IN_SPRINTF_RETURN(&our_out, "(%s) ", fr_type_to_str(vp->vp_type)); |
98 | 0 | } |
99 | | |
100 | 619 | slen = fr_value_box_print_quoted(&our_out, &vp->data, quote); |
101 | 619 | if (slen <= 0) return slen; |
102 | 619 | } |
103 | | |
104 | 619 | FR_SBUFF_SET_RETURN(out, &our_out); |
105 | 619 | } |
106 | | |
107 | | /** Print either a quoted value, an enum, or a normal value. |
108 | | * |
109 | | */ |
110 | | static ssize_t fr_pair_print_value(fr_sbuff_t *out, fr_pair_t const *vp) |
111 | 0 | { |
112 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
113 | 0 | char const *name; |
114 | |
|
115 | 0 | if ((name = fr_value_box_enum_name(&vp->data)) != NULL) { |
116 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, ':', ':'); |
117 | 0 | FR_SBUFF_IN_STRCPY_RETURN(&our_out, name); |
118 | 0 | } else { |
119 | |
|
120 | 0 | FR_SBUFF_RETURN(fr_pair_print_value_quoted, &our_out, vp, T_DOUBLE_QUOTED_STRING); |
121 | 0 | } |
122 | | |
123 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
124 | 0 | } |
125 | | |
126 | | /** Print one attribute and value to a string |
127 | | * |
128 | | * Print a fr_pair_t in the format: |
129 | | @verbatim |
130 | | <attribute_name> <op> <value> |
131 | | @endverbatim |
132 | | * to a string. |
133 | | * |
134 | | * @param[in] out Where to write the string. |
135 | | * @param[in] parent If not NULL, only print OID components from |
136 | | * this parent to the VP. |
137 | | * @param[in] vp to print. |
138 | | * @return |
139 | | * - Length of data written to out. |
140 | | * - value >= outlen on truncation. |
141 | | */ |
142 | | ssize_t fr_pair_print(fr_sbuff_t *out, fr_dict_attr_t const *parent, fr_pair_t const *vp) |
143 | 0 | { |
144 | 0 | char const *token = NULL; |
145 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
146 | |
|
147 | 0 | PAIR_VERIFY(vp); |
148 | | |
149 | | /* |
150 | | * Omit the union if we can. But if the child is raw, then always print it. That way it's |
151 | | * clearer what's going on. |
152 | | */ |
153 | 0 | if (vp->vp_type == FR_TYPE_UNION) { |
154 | 0 | fr_pair_t *child = fr_pair_list_head(&vp->vp_group); |
155 | |
|
156 | 0 | if (!child->da->flags.is_unknown && |
157 | 0 | (fr_pair_list_num_elements(&vp->vp_group) == 1)) { |
158 | 0 | parent = vp->da; |
159 | 0 | vp = fr_pair_list_head(&vp->vp_group); |
160 | 0 | } |
161 | 0 | } |
162 | |
|
163 | 0 | if ((vp->op > T_INVALID) && (vp->op < T_TOKEN_LAST)) { |
164 | 0 | token = fr_tokens[vp->op]; |
165 | 0 | } else { |
166 | 0 | token = "<INVALID-TOKEN>"; |
167 | 0 | } |
168 | |
|
169 | 0 | fr_pair_reset_parent(parent); |
170 | |
|
171 | 0 | if (vp->vp_raw) FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "raw."); |
172 | 0 | FR_DICT_ATTR_OID_PRINT_RETURN(&our_out, parent, vp->da, false); |
173 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, ' '); |
174 | 0 | FR_SBUFF_IN_STRCPY_RETURN(&our_out, token); |
175 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, ' '); |
176 | | |
177 | 0 | FR_SBUFF_RETURN(fr_pair_print_value, &our_out, vp); |
178 | | |
179 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
180 | 0 | } |
181 | | |
182 | | /** Print one attribute and value to a string with escape rules |
183 | | * |
184 | | * Similar to fr_pair_print(), but secrets are omitted. This function duplicates parts of the functionality |
185 | | * of fr_pair_print(). fr_pair_print_value_quoted(), and fr_value_box_print_quoted(), but for the special |
186 | | * case of secure strings. |
187 | | * |
188 | | * Note that only secrets of type "string" and "octets" are omitted. Other "secret" data types are still |
189 | | * printed as-is. |
190 | | * |
191 | | * "octets" are still printed as "<<< secret >>>". Which won't parse correctly, but that's fine. Because |
192 | | * omitted data is not meant to be parsed into real data. |
193 | | * |
194 | | * @param[in] out Where to write the string. |
195 | | * @param[in] parent If not NULL, only print OID components from |
196 | | * this parent to the VP. |
197 | | * @param[in] vp to print. |
198 | | |
199 | | * @return |
200 | | * - < 0 on error |
201 | | * - Length of data written to out. |
202 | | * - value >= outlen on truncation. |
203 | | */ |
204 | | ssize_t fr_pair_print_secure(fr_sbuff_t *out, fr_dict_attr_t const *parent, fr_pair_t const *vp) |
205 | 0 | { |
206 | 0 | char const *token = NULL; |
207 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
208 | |
|
209 | 0 | PAIR_VERIFY(vp); |
210 | |
|
211 | 0 | if ((vp->op > T_INVALID) && (vp->op < T_TOKEN_LAST)) { |
212 | 0 | token = fr_tokens[vp->op]; |
213 | 0 | } else { |
214 | 0 | token = "<INVALID-TOKEN>"; |
215 | 0 | } |
216 | |
|
217 | 0 | fr_pair_reset_parent(parent); |
218 | |
|
219 | 0 | if (vp->vp_raw) FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "raw."); |
220 | 0 | FR_DICT_ATTR_OID_PRINT_RETURN(&our_out, parent, vp->da, false); |
221 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, ' '); |
222 | 0 | FR_SBUFF_IN_STRCPY_RETURN(&our_out, token); |
223 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, ' '); |
224 | | |
225 | 0 | if (fr_type_is_leaf(vp->vp_type)) { |
226 | 0 | if (!vp->data.secret) { |
227 | 0 | FR_SBUFF_RETURN(fr_pair_print_value, &our_out, vp); |
228 | |
|
229 | 0 | } else { |
230 | 0 | switch (vp->vp_type) { |
231 | 0 | case FR_TYPE_STRING: |
232 | 0 | case FR_TYPE_OCTETS: |
233 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "<<< secret >>>"); |
234 | 0 | break; |
235 | | |
236 | 0 | default: |
237 | 0 | fr_assert(0); /* see dict_tokenize.c, which enforces parsing of "secret" in dictionaries */ |
238 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "<<< secret >>>"); |
239 | 0 | break; |
240 | 0 | } |
241 | 0 | } |
242 | 0 | } else { |
243 | 0 | fr_pair_t *child; |
244 | 0 | fr_dcursor_t cursor; |
245 | |
|
246 | 0 | fr_assert(fr_type_is_structural(vp->vp_type)); |
247 | |
|
248 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, '{', ' '); |
249 | 0 | for (child = fr_pair_dcursor_init(&cursor, &vp->vp_group); |
250 | 0 | child != NULL; |
251 | 0 | child = fr_dcursor_next(&cursor)) { |
252 | 0 | FR_SBUFF_RETURN(fr_pair_print_secure, &our_out, vp->da, child); |
253 | 0 | if (fr_dcursor_next_peek(&cursor)) FR_SBUFF_IN_CHAR_RETURN(&our_out, ',', ' '); |
254 | 0 | } |
255 | 0 | FR_SBUFF_IN_CHAR_RETURN(&our_out, ' ', '}'); |
256 | 0 | } |
257 | | |
258 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
259 | 0 | } |
260 | | |
261 | | /** Print a pair list |
262 | | * |
263 | | * @param[in] out Where to write the string. |
264 | | * @param[in] parent parent da to start from |
265 | | * @param[in] list pair list |
266 | | * @return |
267 | | * - Length of data written to out. |
268 | | * - value >= outlen on truncation. |
269 | | */ |
270 | | ssize_t fr_pair_list_print(fr_sbuff_t *out, fr_dict_attr_t const *parent, fr_pair_list_t const *list) |
271 | 451 | { |
272 | 451 | fr_pair_t *vp; |
273 | 451 | fr_sbuff_t our_out = FR_SBUFF(out); |
274 | | |
275 | 451 | vp = fr_pair_list_head(list); |
276 | 451 | if (!vp) { |
277 | 451 | FR_SBUFF_IN_CHAR_RETURN(out, '\0'); |
278 | 451 | return fr_sbuff_used(out); |
279 | 451 | } |
280 | | |
281 | 0 | fr_pair_reset_parent(parent); |
282 | |
|
283 | 0 | while (true) { |
284 | 0 | FR_SBUFF_RETURN(fr_pair_print, &our_out, parent, vp); |
285 | 0 | vp = fr_pair_list_next(list, vp); |
286 | 0 | if (!vp) break; |
287 | | |
288 | 0 | FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, ", "); |
289 | 0 | } |
290 | | |
291 | 0 | FR_SBUFF_SET_RETURN(out, &our_out); |
292 | 0 | } |
293 | | |
294 | | static void fr_pair_list_log_sbuff(fr_log_t const *log, int lvl, fr_pair_t *parent, fr_pair_list_t const *list, char const *file, int line, fr_sbuff_t *sbuff) |
295 | 0 | { |
296 | 0 | fr_dict_attr_t const *parent_da = NULL; |
297 | |
|
298 | 0 | fr_pair_list_foreach(list, vp) { |
299 | 0 | PAIR_VERIFY_WITH_LIST(list, vp); |
300 | |
|
301 | 0 | fr_sbuff_set_to_start(sbuff); |
302 | |
|
303 | 0 | if (vp->vp_raw) (void) fr_sbuff_in_strcpy(sbuff, "raw."); |
304 | |
|
305 | 0 | if (parent && (parent->vp_type != FR_TYPE_GROUP)) parent_da = parent->da; |
306 | 0 | if (fr_dict_attr_oid_print(sbuff, parent_da, vp->da, false) <= 0) return; |
307 | | |
308 | | /* |
309 | | * Recursively print grouped attributes. |
310 | | */ |
311 | 0 | switch (vp->vp_type) { |
312 | 0 | case FR_TYPE_STRUCTURAL: |
313 | 0 | fr_log(log, L_DBG, file, line, "%*s%*s {", lvl * 2, "", |
314 | 0 | (int) fr_sbuff_used(sbuff), fr_sbuff_start(sbuff)); |
315 | 0 | _fr_pair_list_log(log, lvl + 1, vp, &vp->vp_group, file, line); |
316 | 0 | fr_log(log, L_DBG, file, line, "%*s}", lvl * 2, ""); |
317 | 0 | break; |
318 | | |
319 | 0 | default: |
320 | 0 | (void) fr_sbuff_in_strcpy(sbuff, " = "); |
321 | 0 | if (fr_pair_print_value(sbuff, vp) < 0) break; |
322 | | |
323 | 0 | fr_log(log, L_DBG, file, line, "%*s%*s", lvl * 2, "", |
324 | 0 | (int) fr_sbuff_used(sbuff), fr_sbuff_start(sbuff)); |
325 | 0 | } |
326 | 0 | } |
327 | 0 | } |
328 | | |
329 | | |
330 | | /** Print a list of attributes and enumv |
331 | | * |
332 | | * @param[in] log to output to. |
333 | | * @param[in] lvl depth in structural attribute. |
334 | | * @param[in] parent parent attribute |
335 | | * @param[in] list to print. |
336 | | * @param[in] file where the message originated |
337 | | * @param[in] line where the message originated |
338 | | */ |
339 | | void _fr_pair_list_log(fr_log_t const *log, int lvl, fr_pair_t *parent, fr_pair_list_t const *list, char const *file, int line) |
340 | 0 | { |
341 | 0 | fr_sbuff_t sbuff; |
342 | 0 | char buffer[1024]; |
343 | |
|
344 | 0 | buffer[0] = '\0'; |
345 | |
|
346 | 0 | fr_sbuff_init_out(&sbuff, buffer, sizeof(buffer)); |
347 | |
|
348 | 0 | fr_pair_list_log_sbuff(log, lvl, parent, list, file, line, &sbuff); |
349 | 0 | } |
350 | | |
351 | | static void fr_pair_list_debug_sbuff(FILE *fp, int lvl, fr_pair_t *parent, fr_pair_list_t const *list, fr_sbuff_t *sbuff) |
352 | 0 | { |
353 | 0 | fr_dict_attr_t const *parent_da = NULL; |
354 | |
|
355 | 0 | fr_pair_list_foreach(list, vp) { |
356 | 0 | PAIR_VERIFY_WITH_LIST(list, vp); |
357 | |
|
358 | 0 | fr_sbuff_set_to_start(sbuff); |
359 | |
|
360 | 0 | if (vp->vp_raw) (void) fr_sbuff_in_strcpy(sbuff, "raw."); |
361 | |
|
362 | 0 | if (parent && (parent->vp_type != FR_TYPE_GROUP)) parent_da = parent->da; |
363 | 0 | if (fr_dict_attr_oid_print(sbuff, parent_da, vp->da, false) <= 0) return; |
364 | | |
365 | | /* |
366 | | * Recursively print grouped attributes. |
367 | | */ |
368 | 0 | switch (vp->vp_type) { |
369 | 0 | case FR_TYPE_STRUCTURAL: |
370 | 0 | fprintf(fp, "%*s%*s {\n", lvl * 2, "", (int) fr_sbuff_used(sbuff), fr_sbuff_start(sbuff)); |
371 | 0 | _fr_pair_list_debug(fp, lvl + 1, vp, &vp->vp_group); |
372 | 0 | fprintf(fp, "%*s}\n", lvl * 2, ""); |
373 | 0 | break; |
374 | | |
375 | 0 | default: |
376 | 0 | (void) fr_sbuff_in_strcpy(sbuff, " = "); |
377 | 0 | if (fr_value_box_print_quoted(sbuff, &vp->data, T_DOUBLE_QUOTED_STRING)< 0) break; |
378 | | |
379 | 0 | fprintf(fp, "%*s%*s\n", lvl * 2, "", (int) fr_sbuff_used(sbuff), fr_sbuff_start(sbuff)); |
380 | 0 | } |
381 | 0 | } |
382 | 0 | } |
383 | | |
384 | | /** Print a list of attributes and enumv |
385 | | * |
386 | | * @param[in] fp to output to. |
387 | | * @param[in] lvl depth in structural attribute. |
388 | | * @param[in] parent parent attribute |
389 | | * @param[in] list to print. |
390 | | */ |
391 | | void _fr_pair_list_debug(FILE *fp, int lvl, fr_pair_t *parent, fr_pair_list_t const *list) |
392 | 0 | { |
393 | 0 | fr_sbuff_t sbuff; |
394 | 0 | char buffer[1024]; |
395 | |
|
396 | 0 | buffer[0] = '\0'; |
397 | |
|
398 | 0 | fr_sbuff_init_out(&sbuff, buffer, sizeof(buffer)); |
399 | |
|
400 | 0 | fr_pair_list_debug_sbuff(fp, lvl, parent, list, &sbuff); |
401 | 0 | } |
402 | | |
403 | | /** Dumps a list to the default logging destination - Useful for calling from debuggers |
404 | | * |
405 | | */ |
406 | | void fr_pair_list_debug(FILE *fp, fr_pair_list_t const *list) |
407 | 0 | { |
408 | 0 | _fr_pair_list_debug(fp, 0, NULL, list); |
409 | 0 | } |
410 | | |
411 | | |
412 | | /** Dumps a pair to the default logging destination - Useful for calling from debuggers |
413 | | * |
414 | | */ |
415 | | void fr_pair_debug(FILE *fp, fr_pair_t const *pair) |
416 | 0 | { |
417 | 0 | fr_sbuff_t sbuff; |
418 | 0 | char buffer[1024]; |
419 | |
|
420 | 0 | buffer[0] = '\0'; |
421 | |
|
422 | 0 | fr_sbuff_init_out(&sbuff, buffer, sizeof(buffer)); |
423 | |
|
424 | 0 | (void) fr_pair_print(&sbuff, NULL, pair); |
425 | |
|
426 | 0 | fprintf(fp, "%pV\n", fr_box_strvalue_len(fr_sbuff_start(&sbuff), fr_sbuff_used(&sbuff))); |
427 | 0 | } |
428 | | |
429 | | static const char spaces[] = " "; |
430 | | |
431 | | static void fprintf_pair_list(FILE *fp, fr_pair_list_t const *list, int depth) |
432 | 0 | { |
433 | 0 | fr_pair_list_foreach(list, vp) { |
434 | 0 | fprintf(fp, "%.*s", depth, spaces); |
435 | |
|
436 | 0 | if (fr_type_is_leaf(vp->vp_type)) { |
437 | 0 | fr_fprintf(fp, "%s %s %pV\n", vp->da->name, fr_tokens[vp->op], &vp->data); |
438 | 0 | continue; |
439 | 0 | } |
440 | | |
441 | 0 | fr_assert(fr_type_is_structural(vp->vp_type)); |
442 | |
|
443 | 0 | fprintf(fp, "%s = {\n", vp->da->name); |
444 | 0 | fprintf_pair_list(fp, &vp->vp_group, depth + 1); |
445 | 0 | fprintf(fp, "%.*s}\n", depth, spaces); |
446 | 0 | } |
447 | 0 | } |
448 | | |
449 | | void fr_fprintf_pair_list(FILE *fp, fr_pair_list_t const *list) |
450 | 0 | { |
451 | 0 | fprintf_pair_list(fp, list, 0); |
452 | 0 | } |
453 | | |
454 | | /* |
455 | | * print.c doesn't include pair.h, and doing so causes too many knock-on effects. |
456 | | */ |
457 | | void fr_fprintf_pair(FILE *fp, char const *msg, fr_pair_t const *vp) |
458 | 0 | { |
459 | 0 | if (msg) fputs(msg, fp); |
460 | |
|
461 | 0 | if (fr_type_is_leaf(vp->vp_type)) { |
462 | 0 | fr_fprintf(fp, "%s %s %pV\n", vp->da->name, fr_tokens[vp->op], &vp->data); |
463 | 0 | } else { |
464 | 0 | fr_assert(fr_type_is_structural(vp->vp_type)); |
465 | |
|
466 | 0 | fprintf(fp, "%s = {\n", vp->da->name); |
467 | 0 | fprintf_pair_list(fp, &vp->vp_group, 1); |
468 | 0 | fprintf(fp, "}\n"); |
469 | 0 | } |
470 | 0 | } |