/src/freeradius-server/src/lib/server/users_file.c
Line | Count | Source |
1 | | /* |
2 | | * files.c Read config files into memory. |
3 | | * |
4 | | * Version: $Id: 8464bd60ea30cd61f2f103fe9fb0c1904817fde6 $ |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation; either version 2 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
19 | | * |
20 | | * @copyright 2000,2006 The FreeRADIUS server project |
21 | | * @copyright 2000 Miquel van Smoorenburg (miquels@cistron.nl) |
22 | | * @copyright 2000 Alan DeKok (aland@freeradius.org) |
23 | | */ |
24 | | |
25 | | RCSID("$Id: 8464bd60ea30cd61f2f103fe9fb0c1904817fde6 $") |
26 | | |
27 | | #include <freeradius-devel/server/log.h> |
28 | | #include <freeradius-devel/util/debug.h> |
29 | | #include <freeradius-devel/server/users_file.h> |
30 | | |
31 | | #include <freeradius-devel/util/misc.h> |
32 | | #include <freeradius-devel/util/pair_legacy.h> |
33 | | #include <freeradius-devel/util/syserror.h> |
34 | | |
35 | | #include <sys/stat.h> |
36 | | |
37 | | #include <fcntl.h> |
38 | | |
39 | | static int pairlist_read_internal(TALLOC_CTX *ctx, fr_dict_t const *dict, char const *file, PAIR_LIST_LIST *list, |
40 | | bool complain, bool v3_compat, int *order); |
41 | | |
42 | | static inline void line_error_marker(char const *src_file, int src_line, |
43 | | char const *user_file, int user_line, |
44 | | fr_sbuff_t *sbuff, char const *error) |
45 | 0 | { |
46 | 0 | char *end; |
47 | 0 | fr_sbuff_marker_t start; |
48 | |
|
49 | 0 | fr_sbuff_marker(&start, sbuff); |
50 | 0 | end = fr_sbuff_adv_to_chr(sbuff, SIZE_MAX, '\n'); |
51 | 0 | if (!end) end = fr_sbuff_end(sbuff); |
52 | 0 | fr_sbuff_set(sbuff, &start); |
53 | 0 | fr_sbuff_marker_release(&start); |
54 | |
|
55 | 0 | fr_log_marker(LOG_DST, L_ERR, src_file, src_line, |
56 | 0 | fr_sbuff_start(sbuff), end - fr_sbuff_start(sbuff), |
57 | 0 | fr_sbuff_used(sbuff), error, "%s[%d]: ", user_file, user_line); |
58 | 0 | } |
59 | | /** Print out a line oriented error marker at the current position of the sbuff |
60 | | * |
61 | | * @param[in] _sbuff to print error for. |
62 | | * @param[in] _error message. |
63 | | */ |
64 | 0 | #define ERROR_MARKER(_sbuff, _error) line_error_marker(__FILE__, __LINE__, file, lineno, _sbuff, _error) |
65 | | |
66 | | static inline void line_error_marker_adj(char const *src_file, int src_line, |
67 | | char const *user_file, int user_line, |
68 | | fr_sbuff_t *sbuff, ssize_t marker_idx, char const *error) |
69 | 0 | { |
70 | 0 | char *end; |
71 | 0 | fr_sbuff_marker_t start; |
72 | |
|
73 | 0 | fr_sbuff_marker(&start, sbuff); |
74 | 0 | end = fr_sbuff_adv_to_chr(sbuff, SIZE_MAX, '\n'); |
75 | 0 | if (!end) end = fr_sbuff_end(sbuff); |
76 | 0 | fr_sbuff_set(sbuff, &start); |
77 | 0 | fr_sbuff_marker_release(&start); |
78 | |
|
79 | 0 | fr_log_marker(LOG_DST, L_ERR, src_file, src_line, |
80 | 0 | fr_sbuff_current(sbuff), end - fr_sbuff_current(sbuff), |
81 | 0 | marker_idx, error, "%s[%d]: ", user_file, user_line); |
82 | 0 | } |
83 | | /** Print out a line oriented error marker relative to the current position of the sbuff |
84 | | * |
85 | | * @param[in] _sbuff to print error for. |
86 | | * @param[in] _idx Where the error occurred. |
87 | | * @param[in] _error message. |
88 | | */ |
89 | 0 | #define ERROR_MARKER_ADJ(_sbuff, _idx, _error) line_error_marker_adj(__FILE__, __LINE__, file, lineno, _sbuff, _idx, _error) |
90 | | |
91 | | static fr_table_num_sorted_t const check_cmp_op_table[] = { |
92 | | { L("!*"), T_OP_CMP_FALSE }, |
93 | | { L("!="), T_OP_NE }, |
94 | | { L("!~"), T_OP_REG_NE }, |
95 | | { L("+="), T_OP_ADD_EQ }, |
96 | | { L(":="), T_OP_SET }, |
97 | | { L("<"), T_OP_LT }, |
98 | | { L("<="), T_OP_LE }, |
99 | | { L("="), T_OP_EQ }, |
100 | | { L("=*"), T_OP_CMP_TRUE }, |
101 | | { L("=="), T_OP_CMP_EQ }, |
102 | | { L("=~"), T_OP_REG_EQ }, |
103 | | { L(">"), T_OP_GT }, |
104 | | { L(">="), T_OP_GE } |
105 | | }; |
106 | | static size_t check_cmp_op_table_len = NUM_ELEMENTS(check_cmp_op_table); |
107 | | |
108 | | static const fr_sbuff_term_t name_terms = FR_SBUFF_TERMS( |
109 | | L("\t"), |
110 | | L("\n"), |
111 | | L(" "), |
112 | | L("#"), |
113 | | ); |
114 | | |
115 | | static fr_sbuff_parse_rules_t const rhs_term = { |
116 | | .escapes = &(fr_sbuff_unescape_rules_t){ |
117 | | .chr = '\\', |
118 | | /* |
119 | | * Allow barewords to contain whitespace |
120 | | * if they're escaped. |
121 | | */ |
122 | | .subs = { |
123 | | ['\t'] = '\t', |
124 | | ['\n'] = '\n', |
125 | | [' '] = ' ' |
126 | | }, |
127 | | .do_hex = true, |
128 | | .do_oct = false |
129 | | }, |
130 | | .terminals = &FR_SBUFF_TERMS( |
131 | | L(""), |
132 | | L("\t"), |
133 | | L("\n"), |
134 | | L("#"), |
135 | | L(","), |
136 | | ) |
137 | | }; |
138 | | |
139 | | /* |
140 | | * Caller saw a $INCLUDE at the start of a line. |
141 | | */ |
142 | | static int users_include(TALLOC_CTX *ctx, fr_dict_t const *dict, fr_sbuff_t *sbuff, PAIR_LIST_LIST *list, |
143 | | char const *file, int lineno, bool v3_compat, int *order) |
144 | 0 | { |
145 | 0 | size_t len; |
146 | 0 | char *newfile, c; |
147 | 0 | char const *p; |
148 | 0 | fr_sbuff_marker_t name; |
149 | |
|
150 | 0 | fr_sbuff_advance(sbuff, 8); |
151 | | |
152 | | /* |
153 | | * Skip spaces after the $INCLUDE. |
154 | | */ |
155 | 0 | if (fr_sbuff_adv_past_blank(sbuff, SIZE_MAX, NULL) == 0) { |
156 | 0 | ERROR_MARKER(sbuff, "Unexpected text after $INCLUDE"); |
157 | 0 | return -1; |
158 | 0 | } |
159 | | |
160 | | /* |
161 | | * Remember when the name started, and skip over the name |
162 | | * until spaces, comments, or LF |
163 | | */ |
164 | 0 | fr_sbuff_marker(&name, sbuff); |
165 | 0 | len = fr_sbuff_adv_until(sbuff, SIZE_MAX, &name_terms, 0); |
166 | 0 | if (len == 0) { |
167 | 0 | ERROR_MARKER(sbuff, "No filename after $INCLUDE"); |
168 | 0 | fr_sbuff_marker_release(&name); |
169 | 0 | return -1; |
170 | 0 | } |
171 | | |
172 | | /* |
173 | | * If the input file is a relative path, try to copy the |
174 | | * leading directory from it. If there's no leading |
175 | | * directory, just use the $INCLUDE name as-is. |
176 | | * |
177 | | * If there is a leading directory in the input name, |
178 | | * paste that as the directory to the $INCLUDE name. |
179 | | * |
180 | | * Otherwise the $INCLUDE name is an absolute path, use |
181 | | * it as -is. |
182 | | */ |
183 | 0 | c = fr_sbuff_char(&name, '\0'); |
184 | 0 | if (c != '/') { |
185 | 0 | p = strrchr(file, '/'); |
186 | |
|
187 | 0 | if (!p) goto copy_name; |
188 | | |
189 | 0 | newfile = talloc_asprintf(NULL, "%.*s/%.*s", |
190 | 0 | (int) (p - file), file, |
191 | 0 | (int) len, fr_sbuff_current(&name)); |
192 | 0 | } else { |
193 | 0 | copy_name: |
194 | 0 | newfile = talloc_asprintf(NULL, "%.*s", (int) len, fr_sbuff_current(&name)); |
195 | 0 | } |
196 | 0 | fr_sbuff_marker_release(&name); |
197 | | |
198 | | /* |
199 | | * Skip spaces and comments after the name. |
200 | | */ |
201 | 0 | fr_sbuff_adv_past_blank(sbuff, SIZE_MAX, NULL); |
202 | 0 | if (fr_sbuff_next_if_char(sbuff, '#')) { |
203 | 0 | (void) fr_sbuff_adv_to_chr(sbuff, SIZE_MAX, '\n'); |
204 | 0 | } |
205 | | |
206 | | /* |
207 | | * There's no LF, but if we skip non-spaces and |
208 | | * non-comments to find the LF, then there must be extra |
209 | | * text after the filename. That's an error. |
210 | | * |
211 | | * Unless the line has EOF after the filename. in which |
212 | | * case this error will get hit, too. |
213 | | */ |
214 | 0 | if (!fr_sbuff_is_char(sbuff, '\n') && |
215 | 0 | (fr_sbuff_adv_to_chr(sbuff, SIZE_MAX, '\n') != NULL)) { |
216 | 0 | ERROR_MARKER(sbuff, "Unexpected text after filename"); |
217 | 0 | talloc_free(newfile); |
218 | 0 | return -1; |
219 | 0 | } |
220 | | |
221 | | /* |
222 | | * Read the $INCLUDEd file recursively. |
223 | | */ |
224 | 0 | if (pairlist_read_internal(ctx, dict, newfile, list, false, v3_compat, order) != 0) { |
225 | 0 | ERROR("%s[%d]: Could not read included file %s: %s", |
226 | 0 | file, lineno, newfile, fr_syserror(errno)); |
227 | 0 | talloc_free(newfile); |
228 | 0 | return -1; |
229 | 0 | } |
230 | 0 | talloc_free(newfile); |
231 | |
|
232 | 0 | return 0; |
233 | 0 | } |
234 | | |
235 | | int pairlist_read(TALLOC_CTX *ctx, fr_dict_t const *dict, char const *file, PAIR_LIST_LIST *list, bool v3_compat) |
236 | 0 | { |
237 | 0 | int order = 0; |
238 | |
|
239 | 0 | return pairlist_read_internal(ctx, dict, file, list, true, v3_compat, &order); |
240 | 0 | } |
241 | | |
242 | | /* |
243 | | * Read the users file. Return a PAIR_LIST. |
244 | | */ |
245 | | static int pairlist_read_internal(TALLOC_CTX *ctx, fr_dict_t const *dict, char const *file, PAIR_LIST_LIST *list, bool complain, bool v3_compat, int *order) |
246 | 0 | { |
247 | 0 | char *q; |
248 | 0 | int lineno = 1; |
249 | 0 | map_t *new_map, *relative_map; |
250 | 0 | FILE *fp; |
251 | 0 | fr_sbuff_t sbuff; |
252 | 0 | fr_sbuff_uctx_file_t fctx; |
253 | 0 | tmpl_rules_t lhs_rules, rhs_rules; |
254 | 0 | char *filename = talloc_strdup(ctx, file); |
255 | 0 | char buffer[8192]; |
256 | |
|
257 | 0 | DEBUG2("Reading file %s", file); |
258 | | |
259 | | /* |
260 | | * Open the file. The error message should be a little |
261 | | * more useful... |
262 | | */ |
263 | 0 | if ((fp = fopen(file, "r")) == NULL) { |
264 | 0 | if (complain) ERROR("Couldn't open %s for reading: %s", file, fr_syserror(errno)); |
265 | 0 | return -1; |
266 | 0 | } |
267 | | |
268 | 0 | fr_sbuff_init_file(&sbuff, &fctx, buffer, sizeof(buffer), fp, SIZE_MAX); |
269 | |
|
270 | 0 | lhs_rules = (tmpl_rules_t) { |
271 | 0 | .attr = { |
272 | 0 | .dict_def = dict, |
273 | 0 | .list_def = request_attr_request, |
274 | 0 | .list_presence = TMPL_ATTR_LIST_ALLOW, |
275 | 0 | }, |
276 | 0 | .literals_safe_for = FR_VALUE_BOX_SAFE_FOR_ANY, |
277 | |
|
278 | 0 | }; |
279 | 0 | rhs_rules = (tmpl_rules_t) { |
280 | 0 | .attr = { |
281 | 0 | .dict_def = dict, |
282 | 0 | .list_def = request_attr_request, |
283 | 0 | .list_presence = TMPL_ATTR_LIST_ALLOW, |
284 | 0 | .bare_word_enum = v3_compat, |
285 | 0 | }, |
286 | 0 | .literals_safe_for = FR_VALUE_BOX_SAFE_FOR_ANY, |
287 | 0 | }; |
288 | |
|
289 | 0 | while (true) { |
290 | 0 | size_t len; |
291 | 0 | ssize_t slen; |
292 | 0 | bool comma; |
293 | 0 | bool leading_spaces; |
294 | 0 | PAIR_LIST *t; |
295 | | |
296 | | /* |
297 | | * If the line is empty or has only comments, |
298 | | * then we don't care about leading spaces. |
299 | | */ |
300 | 0 | leading_spaces = (fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL) > 0); |
301 | 0 | if (fr_sbuff_next_if_char(&sbuff, '#')) { |
302 | 0 | (void) fr_sbuff_adv_to_chr(&sbuff, SIZE_MAX, '\n'); |
303 | 0 | } |
304 | 0 | if (fr_sbuff_next_if_char(&sbuff, '\n')) { |
305 | 0 | lineno++; |
306 | 0 | continue; |
307 | 0 | } |
308 | | |
309 | | /* |
310 | | * We're trying to read a key value. It MUST have |
311 | | * been at the start of the line. So whatever |
312 | | * this is, it's wrong. |
313 | | */ |
314 | 0 | if (leading_spaces) { |
315 | 0 | ERROR_MARKER(&sbuff, "Entry does not begin with a key value"); |
316 | 0 | fail: |
317 | 0 | fclose(fp); |
318 | 0 | return -1; |
319 | 0 | } |
320 | | |
321 | | /* |
322 | | * $INCLUDE filename. This will do some sanity checks, and then add the new entries to |
323 | | * the tail of the current list. |
324 | | */ |
325 | 0 | if (fr_sbuff_is_str(&sbuff, "$INCLUDE", 8)) { |
326 | 0 | if (users_include(ctx, dict, &sbuff, list, file, lineno, v3_compat, order) < 0) goto fail; |
327 | | |
328 | 0 | if (fr_sbuff_next_if_char(&sbuff, '\n')) { |
329 | 0 | lineno++; |
330 | 0 | continue; |
331 | 0 | } |
332 | | |
333 | | /* |
334 | | * The next character is not LF, but the |
335 | | * function skipped to LF. So, by |
336 | | * process of elimination, we must be at |
337 | | * EOF. |
338 | | */ |
339 | 0 | break; |
340 | 0 | } /* else it wasn't $INCLUDE */ |
341 | | |
342 | | /* |
343 | | * We MUST be either at a valid entry, OR at EOF. |
344 | | */ |
345 | 0 | MEM(t = talloc_zero(ctx, PAIR_LIST)); |
346 | 0 | map_list_init(&t->check); |
347 | 0 | map_list_init(&t->reply); |
348 | 0 | t->filename = filename; |
349 | 0 | t->lineno = lineno; |
350 | 0 | t->order = (*order)++; |
351 | | |
352 | | /* |
353 | | * Copy the name from the entry. |
354 | | */ |
355 | 0 | if (fr_sbuff_out_abstrncpy_until(t, &q, &len, &sbuff, SIZE_MAX, &name_terms, NULL) < 0) { |
356 | 0 | ERROR_MARKER(&sbuff, "Failed reading entry name"); |
357 | 0 | talloc_free(t); |
358 | 0 | goto fail; |
359 | 0 | } |
360 | 0 | if (len == 0) { |
361 | 0 | talloc_free(t); |
362 | 0 | break; |
363 | 0 | } |
364 | 0 | t->name = q; |
365 | |
|
366 | 0 | lhs_rules.attr.list_def = request_attr_control; |
367 | 0 | comma = false; |
368 | |
|
369 | 0 | check_item: |
370 | | /* |
371 | | * Skip spaces before the item, and allow the |
372 | | * check list to end on comment or LF. |
373 | | * |
374 | | * Note that we _don't_ call map_afrom_substr() to |
375 | | * skip spaces, as it will skip LF, too! |
376 | | */ |
377 | 0 | (void) fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL); |
378 | 0 | if (fr_sbuff_is_char(&sbuff, '#')) goto check_item_comment; |
379 | 0 | if (fr_sbuff_is_char(&sbuff, '\n')) goto check_item_end; |
380 | | |
381 | | /* |
382 | | * Try to parse the check item. |
383 | | */ |
384 | 0 | slen = map_afrom_substr(t, &new_map, NULL, &sbuff, check_cmp_op_table, check_cmp_op_table_len, |
385 | 0 | &lhs_rules, &rhs_rules, &rhs_term); |
386 | 0 | if (!new_map) { |
387 | 0 | ERROR_MARKER_ADJ(&sbuff, slen, fr_strerror()); |
388 | 0 | fail_entry: |
389 | 0 | talloc_free(t); |
390 | 0 | goto fail; |
391 | 0 | } |
392 | | |
393 | 0 | fr_assert(new_map->lhs != NULL); |
394 | 0 | fr_assert(new_map->rhs != NULL); |
395 | | |
396 | | /* |
397 | | * The default rule says that the check |
398 | | * items look at the control list, but |
399 | | * protocol attributes should be compared in the request. |
400 | | */ |
401 | 0 | if (!tmpl_attr_tail_da(new_map->lhs)->flags.internal) { |
402 | 0 | tmpl_attr_set_list(new_map->lhs, request_attr_request); |
403 | 0 | } |
404 | |
|
405 | 0 | if (tmpl_contains_regex(new_map->rhs)) { |
406 | 0 | if (tmpl_is_regex_uncompiled(new_map->rhs) && |
407 | 0 | (tmpl_regex_compile(new_map->rhs, false) < 0)) { |
408 | 0 | ERROR("%s[%d]: Failed compiling regular expression /%s/ - %s", |
409 | 0 | file, lineno, new_map->rhs->name, fr_strerror()); |
410 | 0 | goto fail_entry; |
411 | 0 | } |
412 | | |
413 | 0 | goto do_insert; |
414 | 0 | } |
415 | | |
416 | 0 | do_insert: |
417 | 0 | fr_assert(!new_map->parent); |
418 | 0 | map_list_insert_tail(&t->check, new_map); |
419 | | |
420 | | /* |
421 | | * There can be spaces before any comma. |
422 | | */ |
423 | 0 | (void) fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL); |
424 | | |
425 | | /* |
426 | | * Allow a comma after this item. But remember |
427 | | * if we had a comma. |
428 | | */ |
429 | 0 | if (fr_sbuff_next_if_char(&sbuff, ',')) { |
430 | 0 | comma = true; |
431 | 0 | goto check_item; |
432 | 0 | } |
433 | 0 | comma = false; |
434 | |
|
435 | 0 | check_item_comment: |
436 | | /* |
437 | | * There wasn't a comma after the item, so the |
438 | | * next thing MUST be a comment, LF, EOF. |
439 | | * |
440 | | * If there IS stuff before the LF, then it's |
441 | | * unknown text. |
442 | | */ |
443 | 0 | if (fr_sbuff_next_if_char(&sbuff, '#')) { |
444 | 0 | (void) fr_sbuff_adv_to_chr(&sbuff, SIZE_MAX, '\n'); |
445 | 0 | } |
446 | 0 | check_item_end: |
447 | 0 | if (fr_sbuff_next_if_char(&sbuff, '\n')) { |
448 | | /* |
449 | | * The check item list ended with a comma. |
450 | | * That's bad. |
451 | | */ |
452 | 0 | if (comma) { |
453 | 0 | ERROR_MARKER(&sbuff, "Invalid comma ending the check item list"); |
454 | 0 | goto fail_entry; |
455 | 0 | } |
456 | | |
457 | 0 | lineno++; |
458 | 0 | goto setup_reply; |
459 | 0 | } |
460 | | |
461 | | /* |
462 | | * We didn't see SPACE LF or SPACE COMMENT LF. |
463 | | * There's something else going on. |
464 | | */ |
465 | 0 | if (fr_sbuff_adv_to_chr(&sbuff, SIZE_MAX, '\n') != NULL) { |
466 | 0 | ERROR_MARKER(&sbuff, "Unexpected text after check item"); |
467 | 0 | goto fail_entry; |
468 | 0 | } |
469 | | |
470 | | /* |
471 | | * The next character is not LF, but we |
472 | | * skipped to LF above. So, by process |
473 | | * of elimination, we must be at EOF. |
474 | | */ |
475 | 0 | if (!fr_sbuff_is_char(&sbuff, '\n')) { |
476 | 0 | add_entry: |
477 | 0 | fr_dlist_insert_tail(&list->head, t); |
478 | 0 | break; |
479 | 0 | } |
480 | | |
481 | 0 | setup_reply: |
482 | | /* |
483 | | * Setup the reply items. |
484 | | */ |
485 | 0 | lhs_rules.attr.list_def = request_attr_reply; |
486 | |
|
487 | 0 | comma = false; |
488 | |
|
489 | 0 | rhs_rules.attr.list_def = request_attr_request; |
490 | |
|
491 | 0 | relative_map = NULL; |
492 | |
|
493 | 0 | reply_item: |
494 | | /* |
495 | | * Reply items start with spaces. If there's no |
496 | | * spaces, then the current entry is done. Add |
497 | | * it to the list, and go back to reading the |
498 | | * user name or $INCLUDE. |
499 | | */ |
500 | 0 | if (fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL) == 0) { |
501 | 0 | if (comma) { |
502 | 0 | ERROR("%s[%d]: Unexpected trailing comma in previous line", file, lineno); |
503 | 0 | goto fail_entry; |
504 | 0 | } |
505 | | |
506 | | /* |
507 | | * The line doesn't begin with spaces. |
508 | | * The list of reply items MUST be |
509 | | * finished. Go look for an entry name. |
510 | | * |
511 | | * Note that we don't allow comments in |
512 | | * the middle of the reply item list. Oh |
513 | | * well. |
514 | | */ |
515 | 0 | fr_dlist_insert_tail(&list->head, t); |
516 | 0 | continue; |
517 | |
|
518 | 0 | } else if (lineno == (t->lineno + 1)) { |
519 | 0 | fr_assert(comma == false); |
520 | |
|
521 | 0 | } else if (!comma) { |
522 | 0 | ERROR("%s[%d]: Missing comma in previous line", file, lineno); |
523 | 0 | goto fail_entry; |
524 | 0 | } |
525 | | |
526 | | /* |
527 | | * SPACES COMMENT or SPACES LF means "end of |
528 | | * reply item list" |
529 | | */ |
530 | 0 | if (fr_sbuff_is_char(&sbuff, '#')) { |
531 | 0 | (void) fr_sbuff_adv_to_chr(&sbuff, SIZE_MAX, '\n'); |
532 | 0 | } |
533 | 0 | if (fr_sbuff_next_if_char(&sbuff, '\n')) { |
534 | 0 | lineno++; |
535 | 0 | goto add_entry; |
536 | 0 | } |
537 | | |
538 | 0 | next_reply_item: |
539 | | /* |
540 | | * Unlike check items, we don't skip spaces or |
541 | | * comments here. All of the code paths which |
542 | | * lead to here have already checked for those |
543 | | * cases. |
544 | | */ |
545 | 0 | slen = map_afrom_substr(t, &new_map, &relative_map, &sbuff, map_assignment_op_table, map_assignment_op_table_len, |
546 | 0 | &lhs_rules, &rhs_rules, &rhs_term); |
547 | 0 | if (!new_map) { |
548 | 0 | ERROR_MARKER_ADJ(&sbuff, slen, fr_strerror()); |
549 | 0 | goto fail_entry; |
550 | 0 | } |
551 | | |
552 | 0 | fr_assert(new_map->lhs != NULL); |
553 | |
|
554 | 0 | if (!tmpl_is_attr(new_map->lhs)) { |
555 | 0 | ERROR("%s[%d]: Unknown attribute '%s'", |
556 | 0 | file, lineno, new_map->lhs->name); |
557 | 0 | goto fail_entry; |
558 | 0 | } |
559 | | |
560 | | /* |
561 | | * We no longer really care what the RHS is, or what the list is on the LHS. The caller |
562 | | * takes care of checking that for us. |
563 | | */ |
564 | | |
565 | 0 | if (!new_map->parent) map_list_insert_tail(&t->reply, new_map); |
566 | |
|
567 | 0 | (void) fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL); |
568 | | |
569 | | /* |
570 | | * Commas separate entries on the same line. And |
571 | | * we allow spaces after commas, too. |
572 | | */ |
573 | 0 | if (fr_sbuff_next_if_char(&sbuff, ',')) { |
574 | 0 | comma = true; |
575 | 0 | (void) fr_sbuff_adv_past_blank(&sbuff, SIZE_MAX, NULL); |
576 | 0 | } else { |
577 | 0 | comma = false; |
578 | 0 | } |
579 | | |
580 | | /* |
581 | | * Comments or LF will end this particular line. |
582 | | * |
583 | | * Reading the next line will cause a complaint |
584 | | * if this line ended with a comma. |
585 | | */ |
586 | 0 | if (fr_sbuff_next_if_char(&sbuff, '#')) { |
587 | 0 | (void) fr_sbuff_adv_to_chr(&sbuff, SIZE_MAX, '\n'); |
588 | 0 | } |
589 | |
|
590 | 0 | if (fr_sbuff_next_if_char(&sbuff, '\n')) { |
591 | 0 | lineno++; |
592 | 0 | goto reply_item; |
593 | 0 | } |
594 | | |
595 | | /* |
596 | | * Not comment or LF, the content MUST be another |
597 | | * pair. |
598 | | */ |
599 | 0 | if (comma) goto next_reply_item; |
600 | | |
601 | 0 | ERROR_MARKER(&sbuff, "Unexpected text after reply"); |
602 | 0 | goto fail_entry; |
603 | 0 | } |
604 | | |
605 | | /* |
606 | | * Else we were looking for an entry. We didn't get one |
607 | | * because we were at EOF, so that's OK. |
608 | | */ |
609 | | |
610 | 0 | fclose(fp); |
611 | |
|
612 | 0 | return 0; |
613 | 0 | } |