The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 08b15f75098ddcd4de97f865174a80d643ad41e6 $")
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 ssize_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 ((isspace((uint8_t) *p)) && (p < end)) 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 ((isspace((uint8_t) *p)) && (p < end)) 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
264 fr_pair_append(pair_ctx->list, vp);
265
266 return p - start;
267}
268
269/** Set a new DA context based on the input string
270 *
271 * @param[in,out] pair_ctx the parsing context
272 * @param[in] in String to parse
273 * @param[in] inlen length of string to parse
274 * @return
275 * - <= 0 on error (offset as negative integer)
276 * - > 0 on success (number of bytes parsed).
277 *
278 * pair_ctx->da is set to the new parsing context.
279 *
280 * @todo - allow for child contexts, so that we can parse TLVs into vp->vp_children.
281 * This change requires nested cursors, but not necessarily nested contexts.
282 * We probably want to have a `fr_dlist_t` of pair_ctx, and we always
283 * operate on the last one. When we change contexts to an attributes parent,
284 * we also change pair_ctx to a parent context. This is like the da stack,
285 * but with child cursors, too.
286 *
287 * We also want to emulate the previous behavior of group attributes based
288 * on parent, and increasing child_num. i.e. if we're parsing a series of
289 * "attr-foo = bar", then we watch the parent context, and create a new
290 * parent VP if this child has a SMALLER attribute number than the previous
291 * child. This allows the previous configurations to "just work".
292 */
293static ssize_t fr_pair_ctx_set(fr_pair_ctx_t *pair_ctx, char const *in, size_t inlen)
294{
295 char const *end = in + inlen;
296 char const *p = in;
297 ssize_t slen;
298 fr_dict_attr_t const *da;
299 fr_dict_attr_t const *parent = pair_ctx->parent;
300
301 /*
302 * Parse the attribute name.
303 */
304 while (p < end) {
305 slen = fr_dict_attr_by_name_substr(NULL, &da, parent, &FR_SBUFF_IN(p, end), NULL);
306 if (slen <= 0) return slen - (p - in);
307
308 if (da->parent != parent) {
309 fr_strerror_printf("Unexpected attribute %s is not a child of %s",
310 da->name, parent->name);
311 return -(p - in);
312 }
313
314 if ((p + slen) > end) {
315 fr_strerror_const("Attribute name overflows the input buffer");
316 return -(p - in);
317 }
318
319 /*
320 * Check for ending conditions.
321 */
322 p += slen;
323 parent = da;
324
325 if ((p >= end) || (*p == ',')) {
326 break;
327 }
328
329 /*
330 * We now MUST have FOO.BAR
331 */
332 if (*p != '.') {
333 fr_strerror_const("Unexpected text after attribute");
334 return -(p - in);
335 }
336 p++;
337 }
338
339 pair_ctx->parent = parent;
340 return p - in;
341}
342
343
344/** Parse a pair context from a string.
345 *
346 * @param pair_ctx the parsing context
347 * @param in String to parse
348 * @param inlen length of string to parse
349 * @return
350 * - <= 0 on error (offset as negative integer)
351 * - > 0 on success (number of bytes parsed).
352 *
353 * This function will parse fr_pair_ts, or context changes, up to
354 * end of string, or a trailing ','. The caller is responsible for
355 * parsing the comma.
356 *
357 * It accepts the following syntax:
358 *
359 * - Attribute = value
360 * * reset to a new top-level context according to the parent of Attribute
361 * * parse the attribute and the value
362 *
363 * - .Attribute = value
364 * * parse the attribute and the value in the CURRENT top-level context
365 *
366 * - .Attribute
367 * * reset to a new context according to Attribute, relative to the current context
368 *
369 * - ..Attribute
370 * * reset to a new context according to Attribute, relative to the current context
371 * * more '.' will walk back up the context tree.
372 *
373 * - Attribute
374 * * reset to a new top-level context according to Attribute
375 *
376 */
377ssize_t fr_pair_ctx_afrom_str(fr_pair_ctx_t *pair_ctx, char const *in, size_t inlen)
378{
379 char const *end = in + inlen;
380 char const *p = in;
381 ssize_t slen;
382 fr_dict_attr_t const *da;
383
384 while (isspace((uint8_t) *p) && (p < end)) p++;
385 if (p >= end) return end - in;
386
387 /*
388 * There may be one or more leading '.'
389 */
390 if (*p == '.') {
391
392 /*
393 * .ATTRIBUTE = VALUE
394 */
395 if (p[1] != '.') {
396 p++;
397 return fr_pair_afrom_str(pair_ctx, in, p, end - p);
398 }
399
400 /*
401 * '.' is our parent.
402 */
403 da = pair_ctx->parent;
404
405 /*
406 * Loop until we find the end of the '.', resetting parent each time.
407 */
408 while (p < end) {
409 if (*p == '.') {
410 pair_ctx->parent = da;
411 da = da->parent;
412 p++;
413 continue;
414 }
415
416 /*
417 * Comma is the end of a reference.
418 */
419 if (*p == ',') {
420 return p - in;
421 }
422
423 /*
424 * .FOO, must be a reference.
425 */
426 break;
427 }
428
429 if (p == end) return p - in;
430
431 } else {
432 /*
433 * No leading '.', the reference MUST be from the root.
434 */
435 pair_ctx->parent = fr_dict_root(pair_ctx->parent->dict);
436
437 /*
438 * We allow a leaf OR a reference here.
439 */
440 slen = fr_dict_attr_by_name_substr(NULL, &da, pair_ctx->parent, &FR_SBUFF_IN(p, end), NULL);
441 if (slen <= 0) return slen - (p - in);
442
443 /*
444 * Structural types do not have values. So a
445 * bare "Foo-Group" string MUST be changing the
446 * reference to that da.
447 */
448 switch (da->type) {
449 case FR_TYPE_GROUP:
450 case FR_TYPE_TLV:
451 case FR_TYPE_STRUCT:
452 pair_ctx->parent = da;
453 p += slen;
454
455 if ((p >= end) || (*p == ',')) return p - in;
456
457 if (*p != '.') {
458 fr_strerror_const("Unexpected text");
459 return - (p - in);
460 }
461
462 p++;
463 break;
464
465 default:
466 /*
467 * Non-structural types MUST have values.
468 * So change the parent to its parent,
469 * and parse the leaf pair.
470 */
471 pair_ctx->parent = da->parent;
472 return fr_pair_afrom_str(pair_ctx, in, p, end - in);
473 }
474 }
475
476 /*
477 * Set the new context based on the attribute
478 */
479 slen = fr_pair_ctx_set(pair_ctx, p, end - p);
480 if (slen <= 0) return slen - (p - in);
481
482 p += slen;
483 return p - in;
484}
485
486/** Reset a pair_ctx to the dictionary root.
487 *
488 * @param pair_ctx the parsing context
489 * @param dict the dictionary to reset to the root
490 *
491 * This function is used in order to reset contexts when parsing
492 * strings that change attribute lists. i.e. &request.foo, &reply.bar
493 *
494 * This function is simple for now, but will get complex once we
495 * start using vp->vp_children
496 */
497void fr_pair_ctx_reset(fr_pair_ctx_t *pair_ctx, fr_dict_t const *dict)
498{
499 pair_ctx->parent = fr_dict_root(dict);
500}
#define RCSID(id)
Definition build.h:485
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:2673
static fr_slen_t in
Definition dict.h:887
Test enumeration values.
Definition dict_test.h:92
talloc_free(reap)
@ 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
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:2599
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1348
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:289
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 ssize_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.
ssize_t fr_skip_string(char const *start, char const *end)
Skip a quoted string.
Definition skip.c:37
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:104
@ T_OP_EQ
Definition token.h:83
@ T_OP_NE
Definition token.h:97
@ T_OP_CMP_FALSE
Definition token.h:105
@ T_OP_REG_EQ
Definition token.h:102
@ T_OP_CMP_EQ
Definition token.h:106
@ T_OP_LE
Definition token.h:100
@ T_OP_GE
Definition token.h:98
@ T_OP_GT
Definition token.h:99
@ T_OP_LT
Definition token.h:101
@ T_OP_REG_NE
Definition token.h:103
TALLOC_CTX * ctx
to allocate VPs in
Definition pair.h:878
#define PAIR_ALLOCED(_x)
Definition pair.h:209
fr_pair_list_t * list
of VPs to add
Definition pair.h:880
fr_dict_attr_t const * parent
current attribute to allocate VPs in
Definition pair.h:879
static fr_slen_t parent
Definition pair.h:854
#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 * fr_value_unescape_by_char[UINT8_MAX+1]
Definition value.c:349
static size_t char fr_sbuff_t size_t inlen
Definition value.h:1023