/src/freeradius-server/src/lib/server/pairmove.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program 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 |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: d945598cf34745f34e3849eb7601dc69802d83d9 $ |
19 | | * |
20 | | * @file src/lib/server/pairmove.c |
21 | | * @brief Old style mapping code |
22 | | * |
23 | | * @copyright 2007 The FreeRADIUS server project |
24 | | * @copyright 2007 Alan DeKok (aland@deployingradius.com) |
25 | | */ |
26 | | RCSID("$Id: d945598cf34745f34e3849eb7601dc69802d83d9 $") |
27 | | |
28 | | #include <freeradius-devel/server/paircmp.h> |
29 | | #include <freeradius-devel/server/pairmove.h> |
30 | | #include <freeradius-devel/server/tmpl_dcursor.h> |
31 | | |
32 | | #include <freeradius-devel/util/debug.h> |
33 | | #include <freeradius-devel/util/calc.h> |
34 | | #include <freeradius-devel/util/pair_legacy.h> |
35 | | |
36 | | #include <freeradius-devel/protocol/radius/rfc2865.h> |
37 | | #include <freeradius-devel/protocol/freeradius/freeradius.internal.h> |
38 | | |
39 | | /* |
40 | | * @fixme - integrate this with the code calling it, so that we |
41 | | * only fr_pair_list_copy() those attributes that we're really going to |
42 | | * use. |
43 | | */ |
44 | | void radius_pairmove(request_t *request, fr_pair_list_t *to, fr_pair_list_t *from) |
45 | 0 | { |
46 | 0 | int i, j, count, to_count, tailto; |
47 | 0 | fr_pair_t *from_vp, *next_from, *to_vp, *next_to = NULL; |
48 | 0 | fr_pair_list_t append, prepend; |
49 | 0 | bool *edited = NULL; |
50 | 0 | bool *deleted = NULL; |
51 | | |
52 | | /* |
53 | | * Set up arrays for editing, to remove some of the |
54 | | * O(N^2) dependencies. These record which elements in |
55 | | * the "to" list have been either edited or marked for |
56 | | * deletion. |
57 | | * |
58 | | * It also means that the operators apply ONLY to the |
59 | | * attributes in the original list. |
60 | | * |
61 | | * Also, the previous implementation did NOT implement |
62 | | * "-=" correctly. If two of the same attributes existed |
63 | | * in the "to" list, and you tried to subtract something |
64 | | * matching the *second* value, then the fr_pair_delete_by_da() |
65 | | * function was called, and the *all* attributes of that |
66 | | * number were deleted. With this implementation, only |
67 | | * the matching attributes are deleted. |
68 | | */ |
69 | |
|
70 | 0 | fr_pair_list_init(&append); |
71 | 0 | fr_pair_list_init(&prepend); |
72 | |
|
73 | 0 | to_count = fr_pair_list_num_elements(to); |
74 | 0 | tailto = to_count; |
75 | 0 | edited = talloc_zero_array(request, bool, to_count); |
76 | 0 | deleted = talloc_zero_array(request, bool, to_count); |
77 | |
|
78 | 0 | count = to_count + fr_pair_list_num_elements(from); |
79 | |
|
80 | 0 | RDEBUG4("::: FROM %ld TO %d MAX %d", fr_pair_list_num_elements(from), to_count, count); |
81 | | |
82 | | /* |
83 | | * Now that we have the lists initialized, start working |
84 | | * over them. |
85 | | */ |
86 | 0 | for (i = 0, from_vp = fr_pair_list_head(from); from_vp; i++, from_vp = next_from) { |
87 | 0 | int found; |
88 | | /* Find the next from pair before any manipulation happens */ |
89 | 0 | next_from = fr_pair_list_next(from, from_vp); |
90 | |
|
91 | 0 | RDEBUG4("::: Examining %s", from_vp->da->name); |
92 | | |
93 | | /* |
94 | | * Attribute should be appended, OR the "to" list |
95 | | * is empty, and we're supposed to replace or |
96 | | * "add if not existing". |
97 | | */ |
98 | 0 | if (from_vp->op == T_OP_ADD_EQ) goto do_append; |
99 | | |
100 | | /* |
101 | | * The attribute needs to be prepended to the "to" |
102 | | * list - store it in the prepend list |
103 | | */ |
104 | | |
105 | 0 | if (from_vp->op == T_OP_PREPEND) { |
106 | 0 | RDEBUG4("::: PREPENDING %s FROM %d", from_vp->da->name, i); |
107 | 0 | fr_pair_remove(from, from_vp); |
108 | 0 | fr_pair_prepend(&prepend, from_vp); |
109 | 0 | from_vp->op = T_OP_EQ; |
110 | 0 | continue; |
111 | 0 | } |
112 | 0 | found = false; |
113 | 0 | j = 0; |
114 | 0 | for (to_vp = fr_pair_list_head(to); to_vp; to_vp = next_to, j++) { |
115 | 0 | next_to = fr_pair_list_next(to, to_vp); |
116 | 0 | if (edited[j] || deleted[j] || !from_vp) continue; |
117 | | |
118 | | /* |
119 | | * Attributes aren't the same, skip them. |
120 | | */ |
121 | 0 | if (from_vp->da != to_vp->da) { |
122 | 0 | continue; |
123 | 0 | } |
124 | | |
125 | | /* |
126 | | * We don't use a "switch" statement here |
127 | | * because we want to break out of the |
128 | | * "for" loop over 'j' in most cases. |
129 | | */ |
130 | | |
131 | | /* |
132 | | * Over-write the FIRST instance of the |
133 | | * matching attribute name. We free the |
134 | | * one in the "to" list, and move over |
135 | | * the one in the "from" list. |
136 | | */ |
137 | 0 | if (from_vp->op == T_OP_SET) { |
138 | 0 | RDEBUG4("::: OVERWRITING %s FROM %d TO %d", |
139 | 0 | to_vp->da->name, i, j); |
140 | 0 | fr_pair_remove(from, from_vp); |
141 | 0 | fr_pair_replace(to, to_vp, from_vp); |
142 | 0 | from_vp = NULL; |
143 | 0 | edited[j] = true; |
144 | 0 | break; |
145 | 0 | } |
146 | | |
147 | | /* |
148 | | * Add the attribute only if it does not |
149 | | * exist... but it exists, so we stop |
150 | | * looking. |
151 | | */ |
152 | 0 | if (from_vp->op == T_OP_EQ) { |
153 | 0 | found = true; |
154 | 0 | break; |
155 | 0 | } |
156 | | |
157 | | /* |
158 | | * Delete every attribute, independent |
159 | | * of its value. |
160 | | */ |
161 | 0 | if (from_vp->op == T_OP_CMP_FALSE) { |
162 | 0 | goto delete; |
163 | 0 | } |
164 | | |
165 | | /* |
166 | | * Delete all matching attributes from |
167 | | * "to" |
168 | | */ |
169 | 0 | if ((from_vp->op == T_OP_SUB_EQ) || |
170 | 0 | (from_vp->op == T_OP_CMP_EQ) || |
171 | 0 | (from_vp->op == T_OP_LE) || |
172 | 0 | (from_vp->op == T_OP_GE)) { |
173 | 0 | int rcode; |
174 | 0 | int old_op = from_vp->op; |
175 | | /* |
176 | | * Check for equality. |
177 | | */ |
178 | 0 | from_vp->op = T_OP_CMP_EQ; |
179 | | |
180 | | /* |
181 | | * If equal, delete the one in |
182 | | * the "to" list. |
183 | | */ |
184 | 0 | rcode = paircmp_pairs(NULL, from_vp, to_vp); |
185 | | |
186 | | /* |
187 | | * We may want to do more |
188 | | * subtractions, so we re-set the |
189 | | * operator back to it's original |
190 | | * value. |
191 | | */ |
192 | 0 | from_vp->op = old_op; |
193 | |
|
194 | 0 | switch (old_op) { |
195 | 0 | case T_OP_CMP_EQ: |
196 | 0 | if (rcode != 0) goto delete; |
197 | 0 | break; |
198 | | |
199 | 0 | case T_OP_SUB_EQ: |
200 | 0 | if (rcode == 0) { |
201 | 0 | delete: |
202 | 0 | RDEBUG4("::: DELETING %s FROM %d TO %d", |
203 | 0 | from_vp->da->name, i, j); |
204 | | /* |
205 | | * Mark that this will be deleted |
206 | | */ |
207 | 0 | deleted[j] = true; |
208 | 0 | } |
209 | 0 | break; |
210 | | |
211 | | /* |
212 | | * Enforce <=. If it's |
213 | | * >, replace it. |
214 | | */ |
215 | 0 | case T_OP_LE: |
216 | 0 | if (rcode > 0) { |
217 | 0 | RDEBUG4("::: REPLACING %s FROM %d TO %d", |
218 | 0 | from_vp->da->name, i, j); |
219 | 0 | goto replace; |
220 | 0 | } |
221 | 0 | break; |
222 | | |
223 | 0 | case T_OP_GE: |
224 | 0 | if (rcode < 0) { |
225 | 0 | RDEBUG4("::: REPLACING %s FROM %d TO %d", |
226 | 0 | from_vp->da->name, i, j); |
227 | 0 | replace: |
228 | 0 | fr_pair_remove(from, from_vp); |
229 | 0 | fr_pair_replace(to, to_vp, from_vp); |
230 | 0 | from_vp = NULL; |
231 | 0 | edited[j] = true; |
232 | 0 | } |
233 | 0 | break; |
234 | 0 | } |
235 | | |
236 | 0 | continue; |
237 | 0 | } |
238 | | |
239 | 0 | fr_assert(0 == 1); /* panic! */ |
240 | 0 | } |
241 | | |
242 | | /* |
243 | | * We were asked to add it if it didn't exist, |
244 | | * and it doesn't exist. Move it over to the |
245 | | * tail of the "to" list, UNLESS it was already |
246 | | * moved by another operator. |
247 | | */ |
248 | 0 | if (!found && from_vp) { |
249 | 0 | if ((from_vp->op == T_OP_EQ) || |
250 | 0 | (from_vp->op == T_OP_LE) || |
251 | 0 | (from_vp->op == T_OP_GE) || |
252 | 0 | (from_vp->op == T_OP_SET)) { |
253 | 0 | do_append: |
254 | 0 | RDEBUG4("::: APPENDING %s FROM %d TO %d", |
255 | 0 | from_vp->da->name, i, tailto++); |
256 | 0 | fr_pair_remove(from, from_vp); |
257 | 0 | fr_pair_append(&append, from_vp); |
258 | 0 | from_vp->op = T_OP_EQ; |
259 | 0 | } |
260 | 0 | } |
261 | 0 | } |
262 | | |
263 | | /* |
264 | | * Delete remaining attributes in the "from" list. |
265 | | */ |
266 | 0 | fr_pair_list_free(from); |
267 | |
|
268 | 0 | RDEBUG4("::: TO in %d out %d", to_count, tailto); |
269 | | |
270 | | /* |
271 | | * Delete any "to" items marked for deletion |
272 | | */ |
273 | |
|
274 | 0 | i = 0; |
275 | 0 | for (to_vp = fr_pair_list_head(to); to_vp; to_vp = next_to, i++) { |
276 | 0 | next_to = fr_pair_list_next(to, to_vp); |
277 | |
|
278 | 0 | if (deleted[i]) { |
279 | 0 | fr_pair_remove(to, to_vp); |
280 | 0 | continue; |
281 | 0 | } |
282 | | |
283 | 0 | RDEBUG4("::: to[%d] = %s", i, to_vp->da->name); |
284 | | |
285 | | /* |
286 | | * Mash the operator to a simple '='. The |
287 | | * operators in the "to" list aren't used for |
288 | | * anything. BUT they're used in the "detail" |
289 | | * file and debug output, where we don't want to |
290 | | * see the operators. |
291 | | */ |
292 | 0 | to_vp->op = T_OP_EQ; |
293 | 0 | } |
294 | | |
295 | | /* |
296 | | * Now prepend any items in the "prepend" list to |
297 | | * the head of the "to" list. |
298 | | */ |
299 | 0 | fr_pair_list_prepend(to, &prepend); |
300 | | |
301 | | /* |
302 | | * And finally add in the attributes we're appending to |
303 | | * the tail of the "to" list. |
304 | | */ |
305 | 0 | fr_pair_list_append(to, &append); |
306 | |
|
307 | 0 | fr_assert(request->packet != NULL); |
308 | |
|
309 | 0 | talloc_free(edited); |
310 | 0 | talloc_free(deleted); |
311 | 0 | } |
312 | | |
313 | | static int radius_legacy_map_to_vp(request_t *request, fr_pair_t *parent, map_t const *map) |
314 | 0 | { |
315 | 0 | fr_pair_t *vp; |
316 | 0 | fr_dict_attr_t const *da; |
317 | 0 | fr_value_box_t *box, *to_free = NULL; |
318 | |
|
319 | 0 | RDEBUG(" %s %s %s", map->lhs->name, fr_tokens[map->op], map->rhs->name); |
320 | |
|
321 | 0 | da = tmpl_attr_tail_da(map->lhs); |
322 | 0 | fr_assert(fr_type_is_leaf(da->type)); |
323 | |
|
324 | 0 | if (tmpl_is_data(map->rhs)) { |
325 | 0 | box = tmpl_value(map->rhs); |
326 | |
|
327 | 0 | } else if (tmpl_is_attr(map->rhs)) { |
328 | 0 | fr_pair_t *rhs; |
329 | |
|
330 | 0 | if (tmpl_find_vp(&rhs, request, map->rhs) < 0) return -1; |
331 | | |
332 | 0 | if (rhs->vp_type != da->type) { |
333 | 0 | fr_strerror_const("Incompatible data types"); |
334 | 0 | return -1; |
335 | 0 | } |
336 | | |
337 | 0 | box = &rhs->data; |
338 | |
|
339 | 0 | } else if (tmpl_is_xlat(map->rhs)) { |
340 | 0 | if (tmpl_aexpand(parent, &to_free, request, map->rhs, NULL, NULL) < 0) return -1; |
341 | | |
342 | 0 | box = to_free; |
343 | |
|
344 | 0 | } else { |
345 | 0 | fr_strerror_const("Unknown RHS"); |
346 | 0 | return -1; |
347 | 0 | } |
348 | | |
349 | 0 | if (fr_pair_append_by_da(parent, &vp, &parent->vp_group, da) < 0) return -1; |
350 | | |
351 | 0 | if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) { |
352 | 0 | TALLOC_FREE(to_free); |
353 | 0 | return -1; |
354 | 0 | } |
355 | | |
356 | 0 | TALLOC_FREE(to_free); |
357 | 0 | return 0; |
358 | 0 | } |
359 | | |
360 | | |
361 | | static int CC_HINT(nonnull) radius_legacy_map_apply_structural(request_t *request, map_t const *map, fr_pair_t *vp) |
362 | 0 | { |
363 | 0 | fr_value_box_t *box, *to_free = NULL; |
364 | | |
365 | | /* |
366 | | * No RHS map, but we have children. Create them, and add them to the list. |
367 | | */ |
368 | 0 | if (!map->rhs) { |
369 | 0 | map_t *child; |
370 | | |
371 | | /* |
372 | | * Convert the child maps to VPs. We know that |
373 | | * we just created the pair, so there's no reason |
374 | | * to apply operators to the children. |
375 | | */ |
376 | 0 | for (child = map_list_next(&map->child, NULL); |
377 | 0 | child != NULL; |
378 | 0 | child = map_list_next(&map->child, child)) { |
379 | 0 | fr_assert(child->op == T_OP_EQ); |
380 | 0 | if (radius_legacy_map_to_vp(request, vp, child) < 0) return -1; |
381 | 0 | } |
382 | | |
383 | 0 | return 0; |
384 | 0 | } |
385 | | |
386 | | /* |
387 | | * Copy an existing attribute. |
388 | | */ |
389 | 0 | if (tmpl_is_attr(map->rhs)) { |
390 | 0 | fr_pair_t *rhs; |
391 | |
|
392 | 0 | if (tmpl_find_vp(&rhs, request, map->rhs) < 0) return -1; |
393 | | |
394 | 0 | if (rhs->vp_type != vp->vp_type) { |
395 | 0 | fr_strerror_const("Incompatible data types"); |
396 | 0 | return -1; |
397 | 0 | } |
398 | | |
399 | 0 | if (rhs == vp) { |
400 | 0 | fr_strerror_const("Invalid self-reference"); |
401 | 0 | return -1; |
402 | 0 | } |
403 | | |
404 | 0 | return fr_pair_list_copy(vp, &vp->vp_group, &rhs->vp_group); |
405 | 0 | } |
406 | | |
407 | | /* |
408 | | * RHS is a string or an xlat expansion. |
409 | | */ |
410 | 0 | if (tmpl_is_data(map->rhs)) { |
411 | 0 | box = tmpl_value(map->rhs); |
412 | |
|
413 | 0 | } else if (tmpl_is_xlat(map->rhs)) { |
414 | 0 | if (tmpl_aexpand(request, &to_free, request, map->rhs, NULL, NULL) < 0) return -1; |
415 | | |
416 | 0 | box = to_free; |
417 | |
|
418 | 0 | } else { |
419 | 0 | fr_strerror_const("Unknown RHS"); |
420 | 0 | return -1; |
421 | 0 | } |
422 | | |
423 | 0 | if (box->type != FR_TYPE_STRING) { |
424 | 0 | fr_strerror_const("Cannot parse child list"); |
425 | 0 | TALLOC_FREE(to_free); |
426 | 0 | return -1; |
427 | 0 | } |
428 | | |
429 | | /* |
430 | | * If there's no value, just leave the list alone. |
431 | | * |
432 | | * Otherwise parse the children in the context of the parent. |
433 | | */ |
434 | 0 | if (box->vb_strvalue[0]) { |
435 | 0 | fr_pair_parse_t root, relative; |
436 | | |
437 | | /* |
438 | | * Parse the string as a list of pairs. |
439 | | */ |
440 | 0 | root = (fr_pair_parse_t) { |
441 | 0 | .ctx = vp, |
442 | 0 | .da = vp->da, |
443 | 0 | .list = &vp->vp_group, |
444 | 0 | .dict = vp->da->dict, |
445 | 0 | .internal = fr_dict_internal(), |
446 | 0 | .allow_compare = false, |
447 | 0 | .tainted = box->tainted, |
448 | 0 | }; |
449 | 0 | relative = (fr_pair_parse_t) { }; |
450 | |
|
451 | 0 | if (fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(box->vb_strvalue, box->vb_length)) < 0) { |
452 | 0 | RPEDEBUG("Failed parsing string '%pV' as attribute list", box); |
453 | 0 | TALLOC_FREE(to_free); |
454 | 0 | return -1; |
455 | 0 | } |
456 | 0 | } |
457 | | |
458 | 0 | TALLOC_FREE(to_free); |
459 | 0 | return 0; |
460 | 0 | } |
461 | | |
462 | | typedef struct { |
463 | | fr_edit_list_t *el; |
464 | | fr_pair_t *vp; /* the one we created */ |
465 | | } legacy_pair_build_t; |
466 | | |
467 | | /** Build the relevant pairs at each level. |
468 | | * |
469 | | * See edit_list_pair_build() for similar code. |
470 | | */ |
471 | | static fr_pair_t *legacy_pair_build(fr_pair_t *parent, fr_dcursor_t *cursor, fr_dict_attr_t const *da, void *uctx) |
472 | 0 | { |
473 | 0 | fr_pair_t *vp; |
474 | 0 | legacy_pair_build_t *lp = uctx; |
475 | |
|
476 | 0 | vp = fr_pair_afrom_da(parent, da); |
477 | 0 | if (!vp) return NULL; |
478 | | |
479 | 0 | if (fr_edit_list_insert_pair_tail(lp->el, &parent->vp_group, vp) < 0) { |
480 | 0 | talloc_free(vp); |
481 | 0 | return NULL; |
482 | 0 | } |
483 | | |
484 | | /* |
485 | | * Tell the cursor that we appended a pair. This |
486 | | * function only gets called when we've ran off of the |
487 | | * end of the list, and can't find the thing we're |
488 | | * looking for. So it's safe at set the current one |
489 | | * here. |
490 | | * |
491 | | * @todo - mainly only because we don't allow creating |
492 | | * foo[4] when there's <3 matching entries. i.e. the |
493 | | * "arrays" here are really lists, so we can't create |
494 | | * "holes" in the list. |
495 | | */ |
496 | 0 | fr_dcursor_set_current(cursor, vp); |
497 | |
|
498 | 0 | lp->vp = vp; |
499 | |
|
500 | 0 | return vp; |
501 | 0 | } |
502 | | |
503 | | |
504 | | /** Move a map using the operators from the old pairmove functionality. |
505 | | * |
506 | | */ |
507 | | int radius_legacy_map_apply(request_t *request, map_t const *map, fr_edit_list_t *el) |
508 | 0 | { |
509 | 0 | int16_t num; |
510 | 0 | int err, rcode; |
511 | 0 | bool added = false; |
512 | 0 | fr_pair_t *vp = NULL, *next, *parent; |
513 | 0 | fr_dict_attr_t const *da; |
514 | 0 | fr_pair_list_t *list; |
515 | 0 | TALLOC_CTX *ctx; |
516 | 0 | fr_value_box_t *to_free = NULL; |
517 | 0 | fr_value_box_t const *box; |
518 | 0 | tmpl_dcursor_ctx_t cc; |
519 | 0 | fr_dcursor_t cursor; |
520 | | |
521 | | /* |
522 | | * Find out where this attribute exists, or should exist. |
523 | | */ |
524 | 0 | tmpl_pair_list_and_ctx(ctx, list, request, tmpl_request(map->lhs), tmpl_list(map->lhs)); |
525 | 0 | if (!ctx) return -1; /* no request or list head exists */ |
526 | | |
527 | 0 | da = tmpl_attr_tail_da(map->lhs); |
528 | | |
529 | | /* |
530 | | * These operations are the same for both leaf and structural types. |
531 | | */ |
532 | 0 | switch (map->op) { |
533 | 0 | case T_OP_EQ: |
534 | 0 | if (tmpl_find_vp(&vp, request, map->lhs) < -1) return -1; |
535 | 0 | if (vp) return 0; |
536 | 0 | goto add; |
537 | | |
538 | 0 | case T_OP_SET: |
539 | | /* |
540 | | * Set a value. Note that we might do |
541 | | * |
542 | | * &foo[1] := 1 |
543 | | * |
544 | | * In which case we don't want to delete the attribute, we just want to replace its |
545 | | * value. |
546 | | * |
547 | | * @todo - we can't do &foo[*].bar[*].baz = 1, as that's an implicit cursor, and we don't |
548 | | * do that. |
549 | | */ |
550 | 0 | num = tmpl_attr_tail_num(map->lhs); |
551 | 0 | if (num == NUM_COUNT) { |
552 | 0 | fr_strerror_const("Invalid count in attribute reference"); |
553 | 0 | return -1; |
554 | 0 | } |
555 | | |
556 | 0 | vp = tmpl_dcursor_init(&err, ctx, &cc, &cursor, request, map->lhs); |
557 | | |
558 | | /* |
559 | | * We're editing a specific number. It must exist, otherwise the edit does nothing. |
560 | | */ |
561 | 0 | if ((num >= 0) || (num == NUM_LAST)) { |
562 | 0 | if (!vp) return 0; |
563 | | |
564 | 0 | if (fr_type_is_leaf(vp->vp_type)) { |
565 | 0 | if (fr_edit_list_save_pair_value(el, vp) < 0) return -1; |
566 | 0 | } else { |
567 | 0 | fr_assert(fr_type_is_structural(vp->vp_type)); |
568 | |
|
569 | 0 | if (fr_edit_list_free_pair_children(el, vp) < 0) return -1; |
570 | 0 | } |
571 | 0 | break; |
572 | 0 | } |
573 | | |
574 | | /* |
575 | | * We don't delete the main lists, we just modify their contents. |
576 | | */ |
577 | 0 | if (request_attr_is_list(da)) { |
578 | 0 | fr_assert(vp != NULL); |
579 | |
|
580 | 0 | if (fr_edit_list_free_pair_children(el, vp) < 0) return -1; |
581 | 0 | break; |
582 | 0 | } |
583 | | |
584 | 0 | if (!vp) goto add; |
585 | | |
586 | | /* |
587 | | * Delete the first attribute we found. |
588 | | */ |
589 | 0 | parent = fr_pair_parent(vp); |
590 | 0 | fr_assert(parent != NULL); |
591 | |
|
592 | 0 | if (fr_edit_list_pair_delete(el, &parent->vp_group, vp) < 0) return -1; |
593 | 0 | tmpl_dcursor_clear(&cc); |
594 | | |
595 | | /* |
596 | | * Delete all existing attributes. Note that we re-initialize the cursor every time, |
597 | | * because creating "foo := baz" means deleting ALL existing "foo". But we can't use |
598 | | * the tmpl as a cursor, because the tmpl containst NUM_UNSPEC, and the cursor needs |
599 | | * NUM_ALL. So we have to delete all existing attributes, and then add a new one. |
600 | | */ |
601 | 0 | while (true) { |
602 | 0 | vp = tmpl_dcursor_init(&err, ctx, &cc, &cursor, request, map->lhs); |
603 | 0 | if (!vp) break; |
604 | | |
605 | 0 | parent = fr_pair_parent(vp); |
606 | 0 | fr_assert(parent != NULL); |
607 | |
|
608 | 0 | if (fr_edit_list_pair_delete(el, &parent->vp_group, vp) < 0) return -1; |
609 | 0 | tmpl_dcursor_clear(&cc); |
610 | 0 | } |
611 | 0 | FALL_THROUGH; |
612 | |
|
613 | 0 | case T_OP_ADD_EQ: |
614 | 0 | add: |
615 | 0 | { |
616 | 0 | legacy_pair_build_t lp = (legacy_pair_build_t) { |
617 | 0 | .el = el, |
618 | 0 | .vp = NULL, |
619 | 0 | }; |
620 | |
|
621 | 0 | fr_strerror_clear(); |
622 | 0 | vp = tmpl_dcursor_build_init(&err, ctx, &cc, &cursor, request, map->lhs, legacy_pair_build, &lp); |
623 | 0 | tmpl_dcursor_clear(&cc); |
624 | 0 | if (!vp) { |
625 | 0 | RWDEBUG("Failed creating attribute %s", map->lhs->name); |
626 | 0 | return -1; |
627 | 0 | } |
628 | | |
629 | | /* |
630 | | * If we're adding and one already exists, create a new one in the same context. |
631 | | */ |
632 | 0 | if ((map->op == T_OP_ADD_EQ) && !lp.vp) { |
633 | 0 | parent = fr_pair_parent(vp); |
634 | 0 | fr_assert(parent != NULL); |
635 | |
|
636 | 0 | MEM(vp = fr_pair_afrom_da(parent, da)); |
637 | 0 | if (fr_edit_list_insert_pair_tail(el, &parent->vp_group, vp) < 0) return -1; |
638 | 0 | } |
639 | | |
640 | 0 | added = true; |
641 | 0 | } |
642 | 0 | break; |
643 | | |
644 | 0 | case T_OP_LE: /* replace if not <= */ |
645 | 0 | case T_OP_GE: /* replace if not >= */ |
646 | 0 | if (fr_type_is_structural(da->type)) goto invalid_operator; |
647 | | |
648 | 0 | if (tmpl_find_vp(&vp, request, map->lhs) < -1) return -1; |
649 | 0 | if (!vp) goto add; |
650 | 0 | break; |
651 | | |
652 | 0 | case T_OP_SUB_EQ: /* delete if match, otherwise ignore */ |
653 | 0 | if (fr_type_is_structural(da->type)) { |
654 | 0 | invalid_operator: |
655 | 0 | fr_strerror_printf("Invalid operator '%s' for structural type", fr_type_to_str(da->type)); |
656 | 0 | return -1; |
657 | 0 | } |
658 | | |
659 | 0 | if (tmpl_find_vp(&vp, request, map->lhs) < -1) return -1; |
660 | 0 | if (!vp) return 0; |
661 | 0 | break; |
662 | | |
663 | 0 | default: |
664 | 0 | fr_strerror_printf("Invalid operator '%s'", fr_tokens[map->op]); |
665 | 0 | return -1; |
666 | 0 | } |
667 | | |
668 | 0 | fr_assert(vp); |
669 | | |
670 | | /* |
671 | | * We don't support operations on structural types. Just creation, and assign values. |
672 | | * |
673 | | * The code above has ensured that the structural type has been either saved or cleared via the |
674 | | * edit list, so the next function doesn't need to do that. |
675 | | */ |
676 | 0 | if (fr_type_is_structural(tmpl_attr_tail_da(map->lhs)->type)) { |
677 | 0 | fr_assert(added); |
678 | 0 | return radius_legacy_map_apply_structural(request, map, vp); |
679 | 0 | } |
680 | | |
681 | | /* |
682 | | * We have now found the RHS. Expand it. |
683 | | * |
684 | | * Note that |
685 | | * |
686 | | * &foo := %tolower(&foo) |
687 | | * |
688 | | * works, as we save the value above in the T_OP_SET handler. So we don't delete it. |
689 | | */ |
690 | 0 | if (tmpl_is_data(map->rhs)) { |
691 | 0 | box = tmpl_value(map->rhs); |
692 | |
|
693 | 0 | } else if (tmpl_is_attr(map->rhs)) { |
694 | 0 | fr_pair_t *rhs; |
695 | |
|
696 | 0 | if (tmpl_find_vp(&rhs, request, map->rhs) < 0) return -1; |
697 | | |
698 | 0 | if (rhs->vp_type != da->type) { |
699 | 0 | fr_strerror_const("Incompatible data types"); |
700 | 0 | return -1; |
701 | 0 | } |
702 | | |
703 | 0 | if (rhs == vp) { |
704 | 0 | fr_strerror_const("Invalid self-reference"); |
705 | 0 | return -1; |
706 | 0 | } |
707 | | |
708 | 0 | box = &rhs->data; |
709 | |
|
710 | 0 | } else if (tmpl_is_xlat(map->rhs)) { |
711 | 0 | if (tmpl_aexpand(ctx, &to_free, request, map->rhs, NULL, NULL) < 0) return -1; |
712 | | |
713 | 0 | box = to_free; |
714 | |
|
715 | 0 | } else { |
716 | 0 | fr_strerror_const("Unknown RHS"); |
717 | 0 | return -1; |
718 | 0 | } |
719 | | |
720 | | /* |
721 | | * We added a VP which hadn't previously existed. Therefore just set the value and return. |
722 | | */ |
723 | 0 | if (added) { |
724 | 0 | if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) { |
725 | 0 | fail: |
726 | 0 | TALLOC_FREE(to_free); |
727 | 0 | return -1; |
728 | 0 | } |
729 | | |
730 | 0 | if (vp->da->flags.unsafe) fr_value_box_mark_unsafe(&vp->data); |
731 | 0 | TALLOC_FREE(to_free); |
732 | 0 | return 0; |
733 | 0 | } |
734 | | |
735 | 0 | while (vp) { |
736 | 0 | next = fr_pair_find_by_da_nested(list, vp, da); /* could be deleted in the loop*/ |
737 | |
|
738 | 0 | switch (map->op) { |
739 | 0 | case T_OP_LE: /* replace if not <= */ |
740 | 0 | case T_OP_GE: /* replace if not >= */ |
741 | 0 | rcode = fr_value_box_cmp_op(map->op, &vp->data, box); |
742 | 0 | if (rcode < 0) goto fail; |
743 | | |
744 | 0 | if (rcode != 0) break; |
745 | | |
746 | 0 | if (fr_value_box_cast(vp, &vp->data, vp->vp_type, vp->da, box) < 0) goto fail; |
747 | 0 | break; |
748 | | |
749 | 0 | case T_OP_SUB_EQ: /* delete if match */ |
750 | 0 | rcode = fr_value_box_cmp_op(T_OP_CMP_EQ, &vp->data, box); |
751 | 0 | if (rcode < 0) goto fail; |
752 | | |
753 | 0 | if (rcode == 1) { |
754 | 0 | fr_pair_list_t *parent_list = fr_pair_parent_list(vp); |
755 | |
|
756 | 0 | if (fr_edit_list_pair_delete(el, parent_list, vp) < 0) goto fail; |
757 | 0 | } |
758 | 0 | break; |
759 | | |
760 | 0 | default: |
761 | 0 | fr_assert(0); /* should have been caught above */ |
762 | 0 | return -1; |
763 | 0 | } |
764 | | |
765 | 0 | vp = next; |
766 | 0 | } |
767 | | |
768 | 0 | TALLOC_FREE(to_free); |
769 | 0 | return 0; |
770 | 0 | } |
771 | | |
772 | | int radius_legacy_map_list_apply(request_t *request, map_list_t const *list, fr_edit_list_t *el) |
773 | 0 | { |
774 | 0 | map_t const *map; |
775 | |
|
776 | 0 | for (map = map_list_head(list); |
777 | 0 | map != NULL; |
778 | 0 | map = map_list_next(list, map)) { |
779 | 0 | RDEBUG2("%s %s %s", map->lhs->name, fr_tokens[map->op], |
780 | 0 | map->rhs ? map->rhs->name : "{ ... }"); |
781 | |
|
782 | 0 | if (radius_legacy_map_apply(request, map, el) < 0) { |
783 | 0 | RPEDEBUG("Failed applying result"); |
784 | 0 | return -1; |
785 | 0 | } |
786 | 0 | } |
787 | | |
788 | 0 | return 0; |
789 | 0 | } |
790 | | |
791 | | int radius_legacy_map_cmp(request_t *request, map_t const *map) |
792 | 0 | { |
793 | 0 | int rcode; |
794 | 0 | fr_pair_t *vp; |
795 | 0 | fr_value_box_t const *box; |
796 | 0 | fr_value_box_t *to_free = NULL; |
797 | 0 | fr_value_box_t dst, str; |
798 | 0 | fr_dcursor_t cursor; |
799 | 0 | tmpl_dcursor_ctx_t cc; |
800 | |
|
801 | 0 | fr_assert(tmpl_is_attr(map->lhs)); |
802 | 0 | fr_assert(fr_comparison_op[map->op]); |
803 | |
|
804 | 0 | vp = tmpl_dcursor_init(NULL, request, &cc, &cursor, request, map->lhs); |
805 | |
|
806 | 0 | if (!vp) { |
807 | 0 | tmpl_dcursor_clear(&cc); |
808 | 0 | if (map->op == T_OP_CMP_FALSE) return true; |
809 | 0 | return 0; |
810 | 0 | } |
811 | | |
812 | 0 | if (map->op == T_OP_CMP_TRUE){ |
813 | 0 | tmpl_dcursor_clear(&cc); |
814 | 0 | return false; |
815 | 0 | } |
816 | | |
817 | 0 | if (fr_type_is_structural(vp->vp_type)) { |
818 | 0 | fr_strerror_const("Invalid comparison for structural type"); |
819 | 0 | error: |
820 | 0 | tmpl_dcursor_clear(&cc); |
821 | 0 | return -1; |
822 | 0 | } |
823 | | |
824 | 0 | if (tmpl_is_data(map->rhs)) { |
825 | 0 | box = tmpl_value(map->rhs); |
826 | |
|
827 | 0 | } else if (tmpl_is_attr(map->rhs)) { |
828 | 0 | fr_pair_t *rhs; |
829 | |
|
830 | 0 | if (tmpl_find_vp(&rhs, request, map->rhs) < 0) goto error; |
831 | | |
832 | 0 | box = &rhs->data; |
833 | |
|
834 | 0 | } else if (tmpl_contains_xlat(map->rhs)) { |
835 | 0 | if (tmpl_aexpand(request, &to_free, request, map->rhs, NULL, NULL) < 0) goto error; |
836 | | |
837 | 0 | box = to_free; |
838 | |
|
839 | 0 | } else if (tmpl_is_regex(map->rhs)) { |
840 | | /* |
841 | | * @todo - why box it and parse it again, when we can just run the regex? |
842 | | */ |
843 | 0 | fr_value_box_strdup_shallow(&str, NULL, map->rhs->name, false); |
844 | 0 | box = &str; |
845 | |
|
846 | 0 | } else { |
847 | 0 | fr_strerror_const("Unknown RHS"); |
848 | 0 | goto error; |
849 | 0 | } |
850 | | |
851 | | /* |
852 | | * Check all possible vps matching the lhs |
853 | | * Allows for comparisons such as &foo[*] == "bar" - i.e. true if any instance of &foo has the value "bar" |
854 | | */ |
855 | 0 | rcode = 0; |
856 | 0 | while (vp) { |
857 | | /* |
858 | | * Let the calculation code do upcasting as necessary. |
859 | | */ |
860 | 0 | rcode = fr_value_calc_binary_op(request, &dst, FR_TYPE_BOOL, &vp->data, map->op, box); |
861 | 0 | if ((rcode >= 0) && dst.vb_bool) break; // Found a "true" result, no need to check any further |
862 | 0 | vp = fr_dcursor_next(&cursor); |
863 | 0 | } |
864 | 0 | TALLOC_FREE(to_free); |
865 | 0 | tmpl_dcursor_clear(&cc); |
866 | |
|
867 | 0 | if (rcode < 0) return rcode; |
868 | | |
869 | 0 | return dst.vb_bool; |
870 | 0 | } |