The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
pair_tokenize.c
Go to the documentation of this file.
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/** AVP parsing
18 *
19 * @file src/lib/util/pair_tokenize.c
20 *
21 * @copyright 2020 Network RADIUS SAS (legal@networkradius.com)
22 */
23RCSID("$Id: fdb8d48b4cd2f83891070799689ceec9f91b2c3e $")
24
25#include <freeradius-devel/util/skip.h>
26#include <freeradius-devel/util/pair.h>
27#include <freeradius-devel/util/proto.h>
28#include <freeradius-devel/util/regex.h>
29
30#include <freeradius-devel/protocol/radius/rfc2865.h>
31#include <freeradius-devel/protocol/freeradius/freeradius.internal.h>
32
33
34static ssize_t op_to_token(fr_token_t *token, char const *op, size_t oplen)
35{
36 char const *p = op;
37
38 if (!token || !op || !oplen) return 0;
39
40 switch (*p) {
41 default:
42 fr_strerror_const("Invalid text. Expected comparison operator");
43 return -(p - op);
44
45 case '!':
46 if (oplen < 2) goto invalid_operator;
47
48 if (p[1] == '=') {
49 *token = T_OP_NE;
50 p += 2;
51
52#ifdef HAVE_REGEX
53 } else if (p[1] == '~') {
54 *token = T_OP_REG_NE;
55 p += 2;
56#endif
57
58 } else if (p[1] == '*') {
59 *token = T_OP_CMP_FALSE;
60 p += 2;
61
62 } else {
63 invalid_operator:
64 fr_strerror_const("Invalid operator");
65 return -(p - op);
66 }
67 break;
68
69 case '=':
70 /*
71 * Bare '=' is allowed.
72 */
73 if (oplen == 1) {
74 *token = T_OP_EQ;
75 p++;
76 break;
77 }
78
79 if (p[1] == '=') {
80 *token = T_OP_CMP_EQ;
81 p += 2;
82
83#ifdef HAVE_REGEX
84 } else if (p[1] == '~') {
85 *token = T_OP_REG_EQ;
86 p += 2;
87#endif
88
89 } else if (p[1] == '*') {
90 *token = T_OP_CMP_TRUE;
91 p += 2;
92
93 } else {
94 /*
95 * Ignore whatever is after the '=' sign.
96 */
97 *token = T_OP_EQ;
98 p++;
99 }
100 break;
101
102 case '<':
103 if ((oplen > 1) && (p[1] == '=')) {
104 *token = T_OP_LE;
105 p += 2;
106
107 } else {
108 *token = T_OP_LT;
109 p++;
110 }
111 break;
112
113 case '>':
114 if ((oplen > 1) && (p[1] == '=')) {
115 *token = T_OP_GE;
116 p += 2;
117
118 } else {
119 *token = T_OP_GT;
120 p++;
121 }
122 break;
123 }
124
125 return p - op;
126}
127
128/** Allocate a fr_pair_t based on pre-parsed fields.
129 *
130 * @param ctx the talloc ctx
131 * @param da the da for the vp
132 * @param op the operator
133 * @param value the value to parse
134 * @param value_len length of the value string
135 * @param uerules used for unescaping.
136 * @return
137 * - fr_pair_t* on success
138 * - NULL on error
139 *
140 * It's just better for this function to take the broken-out /
141 * pre-parsed fields. That way the caller can do any necessary
142 * parsing.
143 */
144static fr_pair_t *fr_pair_afrom_fields(TALLOC_CTX *ctx, fr_dict_attr_t const *da,
145 fr_token_t op,
146 char const *value, size_t value_len,
147 fr_sbuff_unescape_rules_t const *uerules)
148{
149 fr_pair_t *vp;
150
151 if (!da || !value || (value_len == 0)) return NULL;
152
153 vp = fr_pair_afrom_da(ctx, da);
154 if (!vp) return NULL;
155
156 vp->op = op;
158
159 if (fr_pair_value_from_str(vp, value, value_len, uerules, false) < 0) {
161 return NULL;
162 }
163
164 return vp;
165}
166
167
168/** Allocate one fr_pair_t from a string, and add it to the pair_ctx cursor.
169 *
170 * @param[in,out] pair_ctx the parsing context
171 * @param[in] start Where to create relative error offsets in relation to.
172 * @param in String to parse
173 * @param inlen length of string to parse
174 * @return
175 * - <= 0 on error (offset as negative integer)
176 * - > 0 on success (number of bytes parsed).
177 */
178static ssize_t fr_pair_afrom_str(fr_pair_ctx_t *pair_ctx, char const *start, char const *in, size_t inlen)
179{
180 char const *end = in + inlen;
181 char const *p = in;
182 fr_slen_t slen;
183 fr_dict_attr_t const *da;
184 char quote;
185 char const *value;
186 size_t value_len;
187 fr_pair_t *vp;
188 fr_token_t op;
189
190 slen = fr_dict_attr_by_name_substr(NULL, &da, pair_ctx->parent, &FR_SBUFF_IN(p, end), NULL);
191 if (slen <= 0) return slen - (in - start);
192
193 if (da->parent != pair_ctx->parent) {
194 fr_strerror_printf("Unexpected attribute %s is not a child of %s",
195 da->name, pair_ctx->parent->name);
196 return -(in - start);
197 }
198
199 p = in + slen;
200 if (p >= end) {
201 fr_strerror_const("Attribute name overflows the input buffer");
202 return -(in - start);
203 }
204
205 while ((p < end) && isspace((uint8_t) *p)) p++;
206
207 if (p >= end) {
208 fr_strerror_const("No operator found in the input buffer");
209 return -(p - start);
210 }
211
212 /*
213 * For now, the only allowed operator is equals.
214 */
215 slen = op_to_token(&op, p, (end - p));
216 if (slen <= 0) {
217 fr_strerror_const("Syntax error: expected '='");
218 return slen - (p - start);
219 }
220 p += slen;
221
222 while ((p < end) && isspace((uint8_t) *p)) p++;
223
224 if (p >= end) {
225 fr_strerror_const("No value found in the input buffer");
226 return -(p - start);
227 }
228
229 if (*p == '`') {
230 fr_strerror_const("Invalid string quotation");
231 return -(p - start);
232 }
233
234 if ((*p == '"') || (*p == '\'')) {
235 quote = *p;
236 value = p + 1;
237
238 slen = fr_skip_string(p, end);
239 if (slen <= 0) return slen - (p - start);
240 p += slen;
241 value_len = slen - 2; /* account for two "" */
242
243 } else {
244 quote = 0;
245 value = p;
246
247 /*
248 * Skip bare words, but end at comma or end-of-buffer.
249 */
250 while (!isspace((uint8_t) *p) && (*p != ',') && (p < end)) p++;
251
252 value_len = p - value;
253 }
254
255 if (p > end) {
256 fr_strerror_const("Value overflows the input buffer");
257 return -(p - start);
258 }
259
260 vp = fr_pair_afrom_fields(pair_ctx->ctx, da, op, value, value_len, fr_value_unescape_by_char[(uint8_t)quote]);
261 if (!vp) return -(in - start);
262
263 FR_PAIR_APPEND(pair_ctx->list, vp);
264
265 return p - start;
266}
267
268/** Set a new DA context based on the input string
269 *
270 * @param[in,out] pair_ctx the parsing context
271 * @param[in] in String to parse
272 * @param[in] inlen length of string to parse
273 * @return
274 * - <= 0 on error (offset as negative integer)
275 * - > 0 on success (number of bytes parsed).
276 *
277 * pair_ctx->da is set to the new parsing context.
278 *
279 * @todo - allow for child contexts, so that we can parse TLVs into vp->vp_children.
280 * This change requires nested cursors, but not necessarily nested contexts.
281 * We probably want to have a `fr_dlist_t` of pair_ctx, and we always
282 * operate on the last one. When we change contexts to an attributes parent,
283 * we also change pair_ctx to a parent context. This is like the da stack,
284 * but with child cursors, too.
285 *
286 * We also want to emulate the previous behavior of group attributes based
287 * on parent, and increasing child_num. i.e. if we're parsing a series of
288 * "attr-foo = bar", then we watch the parent context, and create a new
289 * parent VP if this child has a SMALLER attribute number than the previous
290 * child. This allows the previous configurations to "just work".
291 */
292static fr_slen_t fr_pair_ctx_set(fr_pair_ctx_t *pair_ctx, char const *in, size_t inlen)
293{
294 char const *end = in + inlen;
295 char const *p = in;
296 fr_slen_t slen;
297 fr_dict_attr_t const *da;
298 fr_dict_attr_t const *parent = pair_ctx->parent;
299
300 /*
301 * Parse the attribute name.
302 */
303 while (p < end) {
304 slen = fr_dict_attr_by_name_substr(NULL, &da, parent, &FR_SBUFF_IN(p, end), NULL);
305 if (slen <= 0) return slen - (p - in);
306
307 if (da->parent != parent) {
308 fr_strerror_printf("Unexpected attribute %s is not a child of %s",
309 da->name, parent->name);
310 return -(p - in);
311 }
312
313 if ((p + slen) > end) {
314 fr_strerror_const("Attribute name overflows the input buffer");
315 return -(p - in);
316 }
317
318 /*
319 * Check for ending conditions.
320 */
321 p += slen;
322 parent = da;
323
324 if ((p >= end) || (*p == ',')) {
325 break;
326 }
327
328 /*
329 * We now MUST have FOO.BAR
330 */
331 if (*p != '.') {
332 fr_strerror_const("Unexpected text after attribute");
333 return -(p - in);
334 }
335 p++;
336 }
337
338 pair_ctx->parent = parent;
339 return p - in;
340}
341
342
343/** Parse a pair context from a string.
344 *
345 * @param pair_ctx the parsing context
346 * @param in String to parse
347 * @param inlen length of string to parse
348 * @return
349 * - <= 0 on error (offset as negative integer)
350 * - > 0 on success (number of bytes parsed).
351 *
352 * This function will parse fr_pair_ts, or context changes, up to
353 * end of string, or a trailing ','. The caller is responsible for
354 * parsing the comma.
355 *
356 * It accepts the following syntax:
357 *
358 * - Attribute = value
359 * * reset to a new top-level context according to the parent of Attribute
360 * * parse the attribute and the value
361 *
362 * - .Attribute = value
363 * * parse the attribute and the value in the CURRENT top-level context
364 *
365 * - .Attribute
366 * * reset to a new context according to Attribute, relative to the current context
367 *
368 * - ..Attribute
369 * * reset to a new context according to Attribute, relative to the current context
370 * * more '.' will walk back up the context tree.
371 *
372 * - Attribute
373 * * reset to a new top-level context according to Attribute
374 *
375 */
376ssize_t fr_pair_ctx_afrom_str(fr_pair_ctx_t *pair_ctx, char const *in, size_t inlen)
377{
378 char const *end = in + inlen;
379 char const *p = in;
380 fr_slen_t slen;
381 fr_dict_attr_t const *da;
382
383 while (isspace((uint8_t) *p) && (p < end)) p++;
384 if (p >= end) return end - in;
385
386 /*
387 * There may be one or more leading '.'
388 */
389 if (*p == '.') {
390
391 /*
392 * .ATTRIBUTE = VALUE
393 */
394 if (p[1] != '.') {
395 p++;
396 return fr_pair_afrom_str(pair_ctx, in, p, end - p);
397 }
398
399 /*
400 * '.' is our parent.
401 */
402 da = pair_ctx->parent;
403
404 /*
405 * Loop until we find the end of the '.', resetting parent each time.
406 */
407 while (p < end) {
408 if (*p == '.') {
409 pair_ctx->parent = da;
410 da = da->parent;
411 p++;
412 continue;
413 }
414
415 /*
416 * Comma is the end of a reference.
417 */
418 if (*p == ',') {
419 return p - in;
420 }
421
422 /*
423 * .FOO, must be a reference.
424 */
425 break;
426 }
427
428 if (p == end) return p - in;
429
430 } else {
431 /*
432 * No leading '.', the reference MUST be from the root.
433 */
434 pair_ctx->parent = fr_dict_root(pair_ctx->parent->dict);
435
436 /*
437 * We allow a leaf OR a reference here.
438 */
439 slen = fr_dict_attr_by_name_substr(NULL, &da, pair_ctx->parent, &FR_SBUFF_IN(p, end), NULL);
440 if (slen <= 0) return slen - (p - in);
441
442 /*
443 * Structural types do not have values. So a
444 * bare "Foo-Group" string MUST be changing the
445 * reference to that da.
446 */
447 switch (da->type) {
448 case FR_TYPE_GROUP:
449 case FR_TYPE_TLV:
450 case FR_TYPE_STRUCT:
451 pair_ctx->parent = da;
452 p += slen;
453
454 if ((p >= end) || (*p == ',')) return p - in;
455
456 if (*p != '.') {
457 fr_strerror_const("Unexpected text");
458 return - (p - in);
459 }
460
461 p++;
462 break;
463
464 default:
465 /*
466 * Non-structural types MUST have values.
467 * So change the parent to its parent,
468 * and parse the leaf pair.
469 */
470 pair_ctx->parent = da->parent;
471 return fr_pair_afrom_str(pair_ctx, in, p, end - in);
472 }
473 }
474
475 /*
476 * Set the new context based on the attribute
477 */
478 slen = fr_pair_ctx_set(pair_ctx, p, end - p);
479 if (slen <= 0) return slen - (p - in);
480
481 p += slen;
482 return p - in;
483}
484
485/** Reset a pair_ctx to the dictionary root.
486 *
487 * @param pair_ctx the parsing context
488 * @param dict the dictionary to reset to the root
489 *
490 * This function is used in order to reset contexts when parsing
491 * strings that change attribute lists. i.e. &request.foo, &reply.bar
492 *
493 * This function is simple for now, but will get complex once we
494 * start using vp->vp_children
495 */
496void fr_pair_ctx_reset(fr_pair_ctx_t *pair_ctx, fr_dict_t const *dict)
497{
498 pair_ctx->parent = fr_dict_root(dict);
499}
#define RCSID(id)
Definition build.h:560
fr_dict_t * dict
Definition common.c:31
fr_slen_t fr_dict_attr_by_name_substr(fr_dict_attr_err_t *err, fr_dict_attr_t const **out, fr_dict_attr_t const *parent, fr_sbuff_t *name, fr_sbuff_term_t const *tt))
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2720
static fr_slen_t in
Definition dict.h:904
Test enumeration values.
Definition dict_test.h:92
talloc_free(hp)
@ FR_TYPE_TLV
Contains nested attributes.
@ FR_TYPE_STRUCT
like TLV, but without T or L, and fixed-width children
@ FR_TYPE_GROUP
A grouping of other attributes.
long int ssize_t
unsigned char uint8_t
ssize_t fr_slen_t
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, UNUSED bool tainted)
Convert string value to native attribute value.
Definition pair.c:2549
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:291
static ssize_t fr_pair_afrom_str(fr_pair_ctx_t *pair_ctx, char const *start, char const *in, size_t inlen)
Allocate one fr_pair_t from a string, and add it to the pair_ctx cursor.
static ssize_t op_to_token(fr_token_t *token, char const *op, size_t oplen)
static fr_slen_t fr_pair_ctx_set(fr_pair_ctx_t *pair_ctx, char const *in, size_t inlen)
Set a new DA context based on the input string.
static fr_pair_t * fr_pair_afrom_fields(TALLOC_CTX *ctx, fr_dict_attr_t const *da, fr_token_t op, char const *value, size_t value_len, fr_sbuff_unescape_rules_t const *uerules)
Allocate a fr_pair_t based on pre-parsed fields.
void fr_pair_ctx_reset(fr_pair_ctx_t *pair_ctx, fr_dict_t const *dict)
Reset a pair_ctx to the dictionary root.
ssize_t fr_pair_ctx_afrom_str(fr_pair_ctx_t *pair_ctx, char const *in, size_t inlen)
Parse a pair context from a string.
#define FR_SBUFF_IN(_start, _len_or_end)
Set of parsing rules for *unescape_until functions.
fr_slen_t fr_skip_string(char const *start, char const *end)
Skip a quoted string.
Definition skip.c:36
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
enum fr_token fr_token_t
@ T_OP_CMP_TRUE
Definition token.h:102
@ T_OP_EQ
Definition token.h:81
@ T_OP_NE
Definition token.h:95
@ T_OP_CMP_FALSE
Definition token.h:103
@ T_OP_REG_EQ
Definition token.h:100
@ T_OP_CMP_EQ
Definition token.h:104
@ T_OP_LE
Definition token.h:98
@ T_OP_GE
Definition token.h:96
@ T_OP_GT
Definition token.h:97
@ T_OP_LT
Definition token.h:99
@ T_OP_REG_NE
Definition token.h:101
TALLOC_CTX * ctx
to allocate VPs in
Definition pair.h:884
#define PAIR_ALLOCED(_x)
Definition pair.h:213
fr_pair_list_t * list
of VPs to add
Definition pair.h:886
#define FR_PAIR_APPEND
Definition pair.h:214
fr_dict_attr_t const * parent
current attribute to allocate VPs in
Definition pair.h:885
static fr_slen_t parent
Definition pair.h:860
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const(_msg)
Definition strerror.h:223
fr_sbuff_unescape_rules_t const * fr_value_unescape_by_char[SBUFF_CHAR_CLASS]
Definition value.c:348