Coverage Report

Created: 2025-12-14 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}