Coverage Report

Created: 2026-08-14 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/token.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
/** Tokenisation code and constants
18
 *
19
 * This is mostly for the attribute filter and user files.
20
 *
21
 * @file src/lib/util/token.c
22
 *
23
 * @copyright 2001,2006 The FreeRADIUS server project
24
 */
25
RCSID("$Id: 9fb1b39f02230880e8eee89f9f800e0e0407a391 $")
26
27
#include <stdio.h>
28
29
#include <freeradius-devel/util/skip.h>
30
#include <freeradius-devel/util/token.h>
31
32
33
fr_table_num_ordered_t const fr_tokens_table[] = {
34
  { L("=~"),  T_OP_REG_EQ }, /* order is important! */
35
  { L("!~"),  T_OP_REG_NE },
36
  { L("{"), T_LCBRACE },
37
  { L("}"), T_RCBRACE },
38
  { L("("), T_LBRACE  },
39
  { L(")"), T_RBRACE  },
40
  { L(","), T_COMMA   },
41
  { L("++"),  T_OP_INCRM  },
42
  { L("+="),  T_OP_ADD_EQ },
43
  { L("-="),  T_OP_SUB_EQ },
44
  { L("*="),  T_OP_MUL_EQ },
45
  { L("/="),  T_OP_DIV_EQ },
46
  { L(":="),  T_OP_SET  },
47
  { L("=*"),  T_OP_CMP_TRUE },
48
  { L("!*"),  T_OP_CMP_FALSE  },
49
  { L("=="),  T_OP_CMP_EQ },
50
  { L("==="), T_OP_CMP_EQ_TYPE },
51
  { L("^="),  T_OP_PREPEND  },
52
  { L("|="),  T_OP_OR_EQ  },
53
  { L("&="),  T_OP_AND_EQ },
54
  { L("="), T_OP_EQ   },
55
  { L("!="),  T_OP_NE   },
56
  { L("!=="), T_OP_CMP_NE_TYPE },
57
  { L(">>="), T_OP_RSHIFT_EQ  },
58
  { L(">="),  T_OP_GE   },
59
  { L(">"), T_OP_GT   },
60
  { L("<<="), T_OP_LSHIFT_EQ  },
61
  { L("<="),  T_OP_LE   },
62
  { L("<"), T_OP_LT   },
63
  { L("#"), T_HASH    },
64
  { L(";"), T_SEMICOLON }
65
};
66
size_t fr_tokens_table_len = NUM_ELEMENTS(fr_tokens_table);
67
68
fr_table_num_sorted_t const fr_token_quotes_table[] = {
69
  { L(""),  T_BARE_WORD   },
70
  { L("'"), T_SINGLE_QUOTED_STRING  },
71
  { L("/"), T_SOLIDUS_QUOTED_STRING },
72
  { L("\""),  T_DOUBLE_QUOTED_STRING  },
73
  { L("`"), T_BACK_QUOTED_STRING  }
74
};
75
size_t fr_token_quotes_table_len = NUM_ELEMENTS(fr_token_quotes_table);
76
77
/** Map each `fr_token_t` to the C identifier of its enum constant.
78
 *
79
 * Complement of `fr_tokens[]` (the operator-character / human-display
80
 * form like ":=" / "<BARE-WORD>") - this one returns the source-identical
81
 * `T_OP_SET` / `T_BARE_WORD` form so tooling (radconf2json, etc.) can
82
 * round-trip enum names through a JSON intermediate.
83
 */
84
static fr_table_num_indexed_t const fr_token_to_enum_str_table[] = {
85
  FR_TABLE_INDEXED_ENTRY(T_BARE_WORD),
86
  FR_TABLE_INDEXED_ENTRY(T_DOUBLE_QUOTED_STRING),
87
  FR_TABLE_INDEXED_ENTRY(T_SINGLE_QUOTED_STRING),
88
  FR_TABLE_INDEXED_ENTRY(T_BACK_QUOTED_STRING),
89
  FR_TABLE_INDEXED_ENTRY(T_SOLIDUS_QUOTED_STRING),
90
  FR_TABLE_INDEXED_ENTRY(T_OP_EQ),
91
  FR_TABLE_INDEXED_ENTRY(T_OP_NE),
92
  FR_TABLE_INDEXED_ENTRY(T_OP_GE),
93
  FR_TABLE_INDEXED_ENTRY(T_OP_GT),
94
  FR_TABLE_INDEXED_ENTRY(T_OP_LE),
95
  FR_TABLE_INDEXED_ENTRY(T_OP_LT),
96
  FR_TABLE_INDEXED_ENTRY(T_OP_CMP_EQ),
97
  FR_TABLE_INDEXED_ENTRY(T_OP_CMP_EQ_TYPE),
98
  FR_TABLE_INDEXED_ENTRY(T_OP_CMP_NE_TYPE),
99
  FR_TABLE_INDEXED_ENTRY(T_OP_ADD_EQ),
100
  FR_TABLE_INDEXED_ENTRY(T_OP_SUB_EQ),
101
  FR_TABLE_INDEXED_ENTRY(T_OP_MUL_EQ),
102
  FR_TABLE_INDEXED_ENTRY(T_OP_DIV_EQ),
103
  FR_TABLE_INDEXED_ENTRY(T_OP_AND_EQ),
104
  FR_TABLE_INDEXED_ENTRY(T_OP_OR_EQ),
105
  FR_TABLE_INDEXED_ENTRY(T_OP_RSHIFT_EQ),
106
  FR_TABLE_INDEXED_ENTRY(T_OP_LSHIFT_EQ),
107
  FR_TABLE_INDEXED_ENTRY(T_OP_SET),
108
  FR_TABLE_INDEXED_ENTRY(T_OP_PREPEND),
109
  FR_TABLE_INDEXED_ENTRY(T_OP_REG_EQ),
110
  FR_TABLE_INDEXED_ENTRY(T_OP_REG_NE),
111
  FR_TABLE_INDEXED_ENTRY(T_OP_CMP_TRUE),
112
  FR_TABLE_INDEXED_ENTRY(T_OP_CMP_FALSE),
113
  FR_TABLE_INDEXED_ENTRY(T_OP_INCRM),
114
};
115
static size_t fr_token_to_enum_str_table_len = NUM_ELEMENTS(fr_token_to_enum_str_table);
116
117
char const *fr_token_to_enum_str(fr_token_t t)
118
0
{
119
0
  return fr_table_str_by_value(fr_token_to_enum_str_table, t, "T_INVALID");
120
0
}
121
122
/** Quote-token names that radconf2json's JSON output uses.
123
 *
124
 * Only the five quote-typed tokens, kept sorted by name so the
125
 * `fr_table_value_by_str` binary search can find them.  Used by
126
 * radjson2conf when rebuilding a CF tree from JSON.
127
 */
128
static fr_table_num_sorted_t const fr_token_from_quote_enum_str_table[] = {
129
  { L("T_BACK_QUOTED_STRING"),  T_BACK_QUOTED_STRING  },
130
  { L("T_BARE_WORD"),   T_BARE_WORD   },
131
  { L("T_DOUBLE_QUOTED_STRING"),  T_DOUBLE_QUOTED_STRING  },
132
  { L("T_SINGLE_QUOTED_STRING"),  T_SINGLE_QUOTED_STRING  },
133
  { L("T_SOLIDUS_QUOTED_STRING"), T_SOLIDUS_QUOTED_STRING },
134
};
135
static size_t fr_token_from_quote_enum_str_table_len = NUM_ELEMENTS(fr_token_from_quote_enum_str_table);
136
137
fr_token_t fr_token_from_quote_enum_str(char const *s, fr_token_t dflt)
138
0
{
139
0
  if (!s) return dflt;
140
0
  return fr_table_value_by_str(fr_token_from_quote_enum_str_table, s, dflt);
141
0
}
142
143
/*
144
 *  String versions for all of the tokens.
145
 */
146
char const *fr_tokens[T_TOKEN_LAST] = {
147
  [T_INVALID] = "?",
148
  [T_EOL] = "EOL",
149
150
  [T_LCBRACE] = "{",
151
  [T_RCBRACE] = "}",
152
  [T_LBRACE] = "(",
153
  [T_RBRACE] = ")",
154
  [T_COMMA] = ",",
155
  [T_SEMICOLON] = ";",
156
157
  [T_ADD]      = "+",
158
  [T_SUB]      = "-",
159
  [T_MUL]      = "*",
160
  [T_DIV]      = "/",
161
  [T_AND]      = "&",
162
  [T_OR]       = "|",
163
  [T_NOT]      = "!",
164
  [T_XOR]      = "^",
165
  [T_COMPLEMENT]  = "~",
166
  [T_MOD]  = "%",
167
168
  [T_RSHIFT]   = ">>",
169
  [T_LSHIFT]   = "<<",
170
171
  [T_LAND]     = "&&",
172
  [T_LOR]      = "||",
173
174
  [T_OP_INCRM] = "++",
175
176
  [T_OP_ADD_EQ] = "+=",
177
  [T_OP_SUB_EQ] = "-=",
178
  [T_OP_SET]    = ":=",
179
  [T_OP_EQ]     = "=",
180
  [T_OP_OR_EQ]  = "|=",
181
  [T_OP_AND_EQ]  = "&=",
182
183
  [T_OP_RSHIFT_EQ]   = ">>=",
184
  [T_OP_LSHIFT_EQ]   = "<<=",
185
186
  [T_OP_NE]     = "!=",
187
  [T_OP_GE]     = ">=",
188
  [T_OP_GT]     = ">",
189
  [T_OP_LE]     = "<=",
190
  [T_OP_LT]     = "<",
191
  [T_OP_REG_EQ] = "=~",
192
  [T_OP_REG_NE] = "!~",
193
194
  [T_OP_CMP_TRUE] = "=*",
195
  [T_OP_CMP_FALSE] = "!*",
196
197
  [T_OP_CMP_EQ] = "==",
198
199
  [T_OP_CMP_EQ_TYPE] = "===",
200
  [T_OP_CMP_NE_TYPE] = "!==",
201
202
  [T_OP_PREPEND] = "^=",
203
204
  [T_HASH]                  = "#",
205
  [T_BARE_WORD]             = "<BARE-WORD>",
206
  [T_DOUBLE_QUOTED_STRING]  = "<\"STRING\">",
207
  [T_SINGLE_QUOTED_STRING]  = "<'STRING'>",
208
  [T_BACK_QUOTED_STRING]    = "<`STRING`>",
209
  [T_SOLIDUS_QUOTED_STRING] = "</STRING/>",
210
};
211
212
213
/*
214
 *  This is fine.  Don't complain.
215
 */
216
#ifdef __clang__
217
#pragma clang diagnostic ignored "-Wgnu-designator"
218
#endif
219
220
/** Convert tokens back to a quoting character
221
 *
222
 * Non-string types convert to '?' to screw ups can be identified easily
223
 */
224
const char fr_token_quote[T_TOKEN_LAST] = {
225
  [ 0 ... T_HASH ] = '?', /* GCC extension for range initialization, also allowed by clang */
226
227
  [T_BARE_WORD] = '\0',
228
  [T_DOUBLE_QUOTED_STRING] = '"',
229
  [T_SINGLE_QUOTED_STRING] = '\'',
230
  [T_BACK_QUOTED_STRING] = '`',
231
  [T_SOLIDUS_QUOTED_STRING] = '/',
232
};
233
234
#define T(_x) [T_OP_ ## _x] = true
235
236
const bool fr_assignment_op[T_TOKEN_LAST] = {
237
  T(INCRM),   /* only used by LDAP :( */
238
239
  T(ADD_EQ),
240
  T(SUB_EQ),
241
  T(MUL_EQ),
242
  T(DIV_EQ),
243
  T(AND_EQ),
244
  T(OR_EQ),
245
  T(RSHIFT_EQ),
246
  T(LSHIFT_EQ),
247
248
  T(SET),
249
  T(EQ),
250
  T(PREPEND),
251
};
252
253
const bool fr_list_assignment_op[T_TOKEN_LAST] = {
254
  T(ADD_EQ),    /* append */
255
  T(SUB_EQ),    /* remove */
256
  T(AND_EQ),    /* intersection */
257
  T(OR_EQ),   /* union */
258
  T(LE),      /* merge RHS */
259
  T(GE),      /* merge LHS */
260
261
  T(SET),
262
  T(EQ),
263
  T(PREPEND),   /* prepend */
264
};
265
266
const bool fr_comparison_op[T_TOKEN_LAST] = {
267
  T(NE),
268
  T(GE),
269
  T(GT),
270
  T(LE),
271
  T(LT),
272
  T(REG_EQ),
273
  T(REG_NE),
274
  T(CMP_TRUE),
275
  T(CMP_FALSE),
276
  T(CMP_EQ),
277
  T(CMP_EQ_TYPE),
278
  T(CMP_NE_TYPE),
279
};
280
281
#undef T
282
#define T(_x) [T_ ## _x] = true
283
284
const bool fr_binary_op[T_TOKEN_LAST] = {
285
  T(ADD),
286
  T(SUB),
287
  T(MUL),
288
  T(DIV),
289
  T(AND),
290
  T(OR),
291
  T(MOD),
292
  T(RSHIFT),
293
  T(LSHIFT),
294
};
295
296
297
#undef T
298
#define T(_x) [T_## _x] = true
299
const bool fr_str_tok[T_TOKEN_LAST] = {
300
  T(BARE_WORD),
301
  T(DOUBLE_QUOTED_STRING),
302
  T(SINGLE_QUOTED_STRING),
303
  T(BACK_QUOTED_STRING),
304
};
305
306
/*
307
 *  This works only as long as special tokens
308
 *  are max. 2 characters, but it's fast.
309
 */
310
#define TOKEN_MATCH(bptr, tptr) \
311
41.4k
  ( (tptr)[0] == (bptr)[0] && \
312
41.4k
   ((tptr)[1] == (bptr)[1] || (tptr)[1] == 0))
313
314
/*
315
 *  Read a word from a buffer and advance pointer.
316
 *  This function knows about escapes and quotes.
317
 *
318
 *  At end-of-line, buf[0] is set to '\0'.
319
 *  Returns 0 or special token value.
320
 */
321
static fr_token_t getthing(char const **ptr, char *buf, int buflen, bool tok,
322
       fr_table_num_ordered_t const *tokenlist, size_t tokenlist_len, bool unescape)
323
1.13k
{
324
1.13k
  char      *s;
325
1.13k
  char const    *p;
326
1.13k
  char      quote;
327
1.13k
  bool      triple = false;
328
1.13k
  unsigned int    x;
329
1.13k
  size_t      i;
330
1.13k
  fr_token_t    token;
331
332
1.13k
  buf[0] = '\0';
333
334
  /* Skip whitespace */
335
1.13k
  p = *ptr;
336
337
1.13k
  fr_skip_whitespace(p);
338
339
1.13k
  if (!*p) {
340
0
    *ptr = p;
341
0
    return T_EOL;
342
0
  }
343
344
  /*
345
   *  Might be a 1 or 2 character token.
346
   */
347
1.13k
  if (tok) {
348
25.3k
    for (i = 0; i < tokenlist_len; i++) {
349
25.0k
      if (TOKEN_MATCH(p, tokenlist[i].name.str)) {
350
856
        strcpy(buf, tokenlist[i].name.str);
351
856
        p += tokenlist[i].name.len;
352
353
        /*
354
         *  Try to shut up Coverity, which claims fr_token_t can be between 0..63, not
355
         *  0..48???
356
         */
357
856
        if ((tokenlist[i].value < 0) || (tokenlist[i].value >= T_TOKEN_LAST)) return T_INVALID;
358
359
856
        token = tokenlist[i].value;
360
856
        goto done;
361
856
      }
362
25.0k
    }
363
1.11k
  }
364
365
  /* Read word. */
366
283
  quote = '\0';
367
283
  switch (*p) {
368
278
  default:
369
278
    token = T_BARE_WORD;
370
278
    break;
371
372
4
  case '\'':
373
4
    token = T_SINGLE_QUOTED_STRING;
374
4
    break;
375
376
1
  case '"':
377
1
    token = T_DOUBLE_QUOTED_STRING;
378
1
    break;
379
380
0
  case '`':
381
0
    token = T_BACK_QUOTED_STRING;
382
0
    break;
383
283
  }
384
385
283
  if (token != T_BARE_WORD) {
386
5
    quote = *p;
387
388
    /*
389
     *  Triple-quoted strings are copied over verbatim, without escapes.
390
     */
391
5
    if ((buflen >= 3) && (p[1] == quote) && (p[2] == quote)) {
392
0
      p += 3;
393
0
      triple = true;
394
0
    }
395
396
5
    p++;
397
5
  }
398
283
  s = buf;
399
400
14.4k
  while (*p && buflen-- > 1) {
401
    /*
402
     *  We're looking for strings.  Stop on spaces, or
403
     *  (if given a token list), on a token, or on a
404
     *  comma.
405
     */
406
14.3k
    if (!quote) {
407
10.2k
      if (isspace((uint8_t) *p)) break;
408
409
410
9.97k
      if (tok) {
411
16.8k
        for (i = 0; i < tokenlist_len; i++) {
412
16.3k
          if (TOKEN_MATCH(p, tokenlist[i].name.str)) {
413
5
            *s++ = 0;
414
5
            goto done;
415
5
          }
416
16.3k
        }
417
532
      }
418
9.96k
      if (*p == ',') break;
419
420
      /*
421
       *  Copy the character over.
422
       */
423
9.96k
      *s++ = *p++;
424
9.96k
      continue;
425
9.96k
    } /* else there was a quotation character */
426
427
    /*
428
     *  Un-escaped quote character.  We're done.
429
     */
430
4.15k
    if (*p == quote) {
431
0
      if (!triple) {
432
0
        p++;
433
0
        *s++ = 0;
434
0
        goto done;
435
0
      }
436
437
0
      if ((buflen >= 3) && (p[1] == quote) && (p[2] == quote)) {
438
0
        p += 3;
439
0
        *s++ = 0;
440
0
        goto done;
441
0
      }
442
443
0
      *s++ = *p++;
444
0
      continue;
445
0
    }
446
447
    /*
448
     *  Everything but backslash gets copied over.
449
     */
450
4.15k
    if (*p != '\\') {
451
4.15k
      *s++ = *p++;
452
4.15k
      continue;
453
4.15k
    }
454
455
    /*
456
     *  There's nothing after the backslash, it's an error.
457
     */
458
0
    if (!p[1]) {
459
0
      fr_strerror_const("Unterminated string");
460
0
      return T_INVALID;
461
0
    }
462
463
0
    if (unescape) {
464
0
      p++;
465
466
0
      switch (*p) {
467
0
        case 'r':
468
0
          *s++ = '\r';
469
0
          break;
470
0
        case 'n':
471
0
          *s++ = '\n';
472
0
          break;
473
0
        case 't':
474
0
          *s++ = '\t';
475
0
          break;
476
477
0
        default:
478
0
          if (*p >= '0' && *p <= '9' &&
479
0
              sscanf(p, "%3o", &x) == 1) {
480
0
            *s++ = x;
481
0
            p += 2;
482
0
          } else
483
0
            *s++ = *p;
484
0
          break;
485
0
      }
486
0
      p++;
487
488
0
    } else {
489
      /*
490
       *  Convert backslash-quote to quote, but
491
       *  leave everything else alone.
492
       */
493
0
      if (p[1] == quote) { /* convert '\'' --> ' */
494
0
        p++;
495
0
      } else {
496
0
        if (buflen < 2) {
497
0
          fr_strerror_const("Truncated input");
498
0
          return T_INVALID;
499
0
        }
500
501
0
        *(s++) = *(p++);
502
0
      }
503
0
      *(s++) = *(p++);
504
0
    }
505
0
  }
506
507
278
  *s++ = 0;
508
509
278
  if (quote) {
510
5
    fr_strerror_const("Unterminated string");
511
5
    return T_INVALID;
512
5
  }
513
514
1.13k
done:
515
  /* Skip whitespace again. */
516
1.13k
  fr_skip_whitespace(p);
517
518
1.13k
  *ptr = p;
519
520
1.13k
  return token;
521
278
}
522
523
/*
524
 *  Read a "word" - this means we don't honor
525
 *  tokens as delimiters.
526
 */
527
int getword(char const **ptr, char *buf, int buflen, bool unescape)
528
22
{
529
22
  return getthing(ptr, buf, buflen, false, fr_tokens_table, fr_tokens_table_len, unescape) == T_EOL ? 0 : 1;
530
22
}
531
532
533
/*
534
 *  Read the next word, use tokens as delimiters.
535
 */
536
fr_token_t gettoken(char const **ptr, char *buf, int buflen, bool unescape)
537
1.11k
{
538
1.11k
  return getthing(ptr, buf, buflen, true, fr_tokens_table, fr_tokens_table_len, unescape);
539
1.11k
}
540
541
/*
542
 *  Expect an operator.
543
 */
544
fr_token_t getop(char const **ptr)
545
0
{
546
0
  char op[3];
547
0
  fr_token_t token;
548
549
0
  token = getthing(ptr, op, sizeof(op), true, fr_tokens_table, fr_tokens_table_len, false);
550
0
  if (!fr_assignment_op[token] && !fr_comparison_op[token]) {
551
0
    fr_strerror_const("Expected operator");
552
0
    return T_INVALID;
553
0
  }
554
0
  return token;
555
0
}
556
557
/*
558
 *  Expect a string.
559
 */
560
fr_token_t getstring(char const **ptr, char *buf, int buflen, bool unescape)
561
0
{
562
0
  char const *p;
563
564
0
  if (!ptr || !*ptr || !buf) return T_INVALID;
565
566
0
  p = *ptr;
567
568
0
  fr_skip_whitespace(p);
569
570
0
  *ptr = p;
571
572
0
  if ((*p == '"') || (*p == '\'') || (*p == '`')) {
573
0
    return gettoken(ptr, buf, buflen, unescape);
574
0
  }
575
576
0
  return getthing(ptr, buf, buflen, false, fr_tokens_table, fr_tokens_table_len, unescape);
577
0
}
578
579
char const *fr_token_name(int token)
580
0
{
581
0
  return fr_table_str_by_value(fr_tokens_table, token, "<INVALID>");
582
0
}