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: 981bd93372c412207126323dfcc1727b51919575 $")
24
25#include <freeradius-devel/util/misc.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;
157
158 if (fr_pair_value_from_str(vp, value, value_len, uerules, false) < 0) {
160 return NULL;
161 }
162
163 return vp;
164}
165
166
167/** Allocate one fr_pair_t from a string, and add it to the pair_ctx cursor.
168 *
169 * @param[in,out] pair_ctx the parsing context
170 * @param[in] start Where to create relative error offsets in relation to.
171 * @param in String to parse
172 * @param inlen length of string to parse
173 * @return
174 * - <= 0 on error (offset as negative integer)
175 * - > 0 on success (number of bytes parsed).
176 */
177static ssize_t fr_pair_afrom_str(fr_pair_ctx_t *pair_ctx, char const *start, char const *in, size_t inlen)
178{
179 char const *end = in + inlen;
180 char const *p = in;
181 ssize_t slen;
182 fr_dict_attr_t const *da;
183 char quote;
184 char const *value;
185 size_t value_len;
186 fr_pair_t *vp;
187 fr_token_t op;
188
189 slen = fr_dict_attr_by_name_substr(NULL, &da, pair_ctx->parent, &FR_SBUFF_IN(p, end), NULL);
190 if (slen <= 0) return slen - (in - start);
191
192 if (da->parent != pair_ctx->parent) {
193 fr_strerror_printf("Unexpected attribute %s is not a child of %s",
194 da->name, pair_ctx->parent->name);
195 return -(in - start);
196 }
197
198 p = in + slen;
199 if (p >= end) {
200 fr_strerror_const("Attribute name overflows the input buffer");
201 return -(in - start);
202 }
203
204 while ((isspace((uint8_t) *p)) && (p < end)) p++;
205
206 if (p >= end) {
207 fr_strerror_const("No operator found in the input buffer");
208 return -(p - start);
209 }
210
211 /*
212 * For now, the only allowed operator is equals.
213 */
214 slen = op_to_token(&op, p, (end - p));
215 if (slen <= 0) {
216 fr_strerror_const("Syntax error: expected '='");
217 return slen - -(p - start);
218 }
219 p += slen;
220
221 while ((isspace((uint8_t) *p)) && (p < end)) p++;
222
223 if (p >= end) {
224 fr_strerror_const("No value found in the input buffer");
225 return -(p - start);
226 }
227
228 if (*p == '`') {
229 fr_strerror_const("Invalid string quotation");
230 return -(p - start);
231 }
232
233 if ((*p == '"') || (*p == '\'')) {
234 quote = *p;
235 value = p + 1;
236
237 slen = fr_skip_string(p, end);
238 if (slen <= 0) return slen - (p - start);
239 p += slen;
240 value_len = slen - 2; /* account for two "" */
241
242 } else {
243 quote = 0;
244 value = p;
245
246 /*
247 * Skip bare words, but end at comma or end-of-buffer.
248 */
249 while (!isspace((uint8_t) *p) && (*p != ',') && (p < end)) p++;
250
251 value_len = p - value;
252 }
253
254 if (p > end) {
255 fr_strerror_const("Value overflows the input buffer");
256 return -(p - start);
257 }
258
259 vp = fr_pair_afrom_fields(pair_ctx->ctx, da, op, value, value_len, fr_value_unescape_by_char[(uint8_t)quote]);
260 if (!vp) return -(in - start);
261
262 fr_pair_append(pair_ctx->list, vp);
263
264 return p - start;
265}
266
267/** Set a new DA context based on the input string
268 *
269 * @param[in,out] pair_ctx the parsing context
270 * @param[in] in String to parse
271 * @param[in] inlen length of string to parse
272 * @return
273 * - <= 0 on error (offset as negative integer)
274 * - > 0 on success (number of bytes parsed).
275 *
276 * pair_ctx->da is set to the new parsing context.
277 *
278 * @todo - allow for child contexts, so that we can parse TLVs into vp->vp_children.
279 * This change requires nested cursors, but not necessarily nested contexts.
280 * We probably want to have a `fr_dlist_t` of pair_ctx, and we always
281 * operate on the last one. When we change contexts to an attributes parent,
282 * we also change pair_ctx to a parent context. This is like the da stack,
283 * but with child cursors, too.
284 *
285 * We also want to emulate the previous behavior of group attributes based
286 * on parent, and increasing child_num. i.e. if we're parsing a series of
287 * "attr-foo = bar", then we watch the parent context, and create a new
288 * parent VP if this child has a SMALLER attribute number than the previous
289 * child. This allows the previous configurations to "just work".
290 */
291static ssize_t fr_pair_ctx_set(fr_pair_ctx_t *pair_ctx, char const *in, size_t inlen)
292{
293 char const *end = in + inlen;
294 char const *p = in;
295 ssize_t slen;
296 fr_dict_attr_t const *da;
297 fr_dict_attr_t const *parent = pair_ctx->parent;
298
299 /*
300 * Parse the attribute name.
301 */
302 while (p < end) {
303 slen = fr_dict_attr_by_name_substr(NULL, &da, parent, &FR_SBUFF_IN(p, end), NULL);
304 if (slen <= 0) return slen - (p - in);
305
306 if (da->parent != parent) {
307 fr_strerror_printf("Unexpected attribute %s is not a child of %s",
308 da->name, parent->name);
309 return -(p - in);
310 }
311
312 if ((p + slen) > end) {
313 fr_strerror_const("Attribute name overflows the input buffer");
314 return -(p - in);
315 }
316
317 /*
318 * Check for ending conditions.
319 */
320 p += slen;
321 parent = da;
322
323 if ((p >= end) || (*p == ',')) {
324 break;
325 }
326
327 /*
328 * We now MUST have FOO.BAR
329 */
330 if (*p != '.') {
331 fr_strerror_const("Unexpected text after attribute");
332 return -(p - in);
333 }
334 p++;
335 }
336
337 pair_ctx->parent = parent;
338 return p - in;
339}
340
341
342/** Parse a pair context from a string.
343 *
344 * @param pair_ctx the parsing context
345 * @param in String to parse
346 * @param inlen length of string to parse
347 * @return
348 * - <= 0 on error (offset as negative integer)
349 * - > 0 on success (number of bytes parsed).
350 *
351 * This function will parse fr_pair_ts, or context changes, up to
352 * end of string, or a trailing ','. The caller is responsible for
353 * parsing the comma.
354 *
355 * It accepts the following syntax:
356 *
357 * - Attribute = value
358 * * reset to a new top-level context according to the parent of Attribute
359 * * parse the attribute and the value
360 *
361 * - .Attribute = value
362 * * parse the attribute and the value in the CURRENT top-level context
363 *
364 * - .Attribute
365 * * reset to a new context according to Attribute, relative to the current context
366 *
367 * - ..Attribute
368 * * reset to a new context according to Attribute, relative to the current context
369 * * more '.' will walk back up the context tree.
370 *
371 * - Attribute
372 * * reset to a new top-level context according to Attribute
373 *
374 */
375ssize_t fr_pair_ctx_afrom_str(fr_pair_ctx_t *pair_ctx, char const *in, size_t inlen)
376{
377 char const *end = in + inlen;
378 char const *p = in;
379 ssize_t slen;
380 fr_dict_attr_t const *da;
381
382 while (isspace((uint8_t) *p) && (p < end)) p++;
383 if (p >= end) return end - in;
384
385 /*
386 * There may be one or more leading '.'
387 */
388 if (*p == '.') {
389
390 /*
391 * .ATTRIBUTE = VALUE
392 */
393 if (p[1] != '.') {
394 p++;
395 return fr_pair_afrom_str(pair_ctx, in, p, end - p);
396 }
397
398 /*
399 * '.' is our parent.
400 */
401 da = pair_ctx->parent;
402
403 /*
404 * Loop until we find the end of the '.', resetting parent each time.
405 */
406 while (p < end) {
407 if (*p == '.') {
408 pair_ctx->parent = da;
409 da = da->parent;
410 p++;
411 continue;
412 }
413
414 /*
415 * Comma is the end of a reference.
416 */
417 if (*p == ',') {
418 return p - in;
419 }
420
421 /*
422 * .FOO, must be a reference.
423 */
424 break;
425 }
426
427 if (p == end) return p - in;
428
429 } else {
430 /*
431 * No leading '.', the reference MUST be from the root.
432 */
433 pair_ctx->parent = fr_dict_root(pair_ctx->parent->dict);
434
435 /*
436 * We allow a leaf OR a reference here.
437 */
438 slen = fr_dict_attr_by_name_substr(NULL, &da, pair_ctx->parent, &FR_SBUFF_IN(p, end), NULL);
439 if (slen <= 0) return slen - (p - in);
440
441 /*
442 * Structural types do not have values. So a
443 * bare "Foo-Group" string MUST be changing the
444 * reference to that da.
445 */
446 switch (da->type) {
447 case FR_TYPE_GROUP:
448 case FR_TYPE_TLV:
449 case FR_TYPE_STRUCT:
450 pair_ctx->parent = da;
451 p += slen;
452
453 if ((p >= end) || (*p == ',')) return p - in;
454
455 if (*p != '.') {
456 fr_strerror_const("Unexpected text");
457 return - (p - in);
458 }
459
460 p++;
461 break;
462
463 default:
464 /*
465 * Non-structural types MUST have values.
466 * So change the parent to its parent,
467 * and parse the leaf pair.
468 */
469 pair_ctx->parent = da->parent;
470 return fr_pair_afrom_str(pair_ctx, in, p, end - in);
471 }
472 }
473
474 /*
475 * Set the new context based on the attribute
476 */
477 slen = fr_pair_ctx_set(pair_ctx, p, end - p);
478 if (slen <= 0) return slen - (p - in);
479
480 p += slen;
481 return p - in;
482}
483
484/** Reset a pair_ctx to the dictionary root.
485 *
486 * @param pair_ctx the parsing context
487 * @param dict the dictionary to reset to the root
488 *
489 * This function is used in order to reset contexts when parsing
490 * strings that change attribute lists. i.e. &request.foo, &reply.bar
491 *
492 * This function is simple for now, but will get complex once we
493 * start using vp->vp_children
494 */
495void fr_pair_ctx_reset(fr_pair_ctx_t *pair_ctx, fr_dict_t const *dict)
496{
497 pair_ctx->parent = fr_dict_root(dict);
498}
#define RCSID(id)
Definition build.h:483
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:2400
static fr_slen_t in
Definition dict.h:824
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_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1345
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:283
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, bool tainted)
Convert string value to native attribute value.
Definition pair.c:2589
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.
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
ssize_t fr_skip_string(char const *start, char const *end)
Skip a quoted string.
Definition token.c:525
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:873
fr_pair_list_t * list
of VPs to add
Definition pair.h:875
fr_dict_attr_t const * parent
current attribute to allocate VPs in
Definition pair.h:874
static fr_slen_t parent
Definition pair.h:851
#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:343
static size_t char fr_sbuff_t size_t inlen
Definition value.h:997