The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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  */
23 RCSID("$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 
34 static 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  */
144 static 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) {
159  talloc_free(vp);
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  */
177 static 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  */
291 static 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  */
375 ssize_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  */
496 {
497  pair_ctx->parent = fr_dict_root(dict);
498 }
static fr_dict_t * dict
Definition: fuzzer.c:46
#define RCSID(id)
Definition: build.h:444
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:1997
static fr_slen_t in
Definition: dict.h:645
Test enumeration values.
Definition: dict_test.h:92
talloc_free(reap)
@ FR_TYPE_TLV
Contains nested attributes.
Definition: merged_model.c:118
@ FR_TYPE_STRUCT
like TLV, but without T or L, and fixed-width children
Definition: merged_model.c:119
@ FR_TYPE_GROUP
A grouping of other attributes.
Definition: merged_model.c:124
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
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:278
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:1340
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:2586
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)
Definition: pair_tokenize.c:34
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.
Definition: merged_model.c:163
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:504
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:866
fr_pair_list_t * list
of VPs to add
Definition: pair.h:868
fr_dict_attr_t const * parent
current attribute to allocate VPs in
Definition: pair.h:867
static fr_slen_t parent
Definition: pair.h:844
#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:984