The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
unit_test_attribute.c
Go to the documentation of this file.
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: 702b3f48a7164347fd2be0d2fcd0843123174413 $
19 *
20 * @file unit_test_attribute.c
21 * @brief Provides a test harness for various internal libraries and functions.
22 *
23 * @copyright 2019 Arran Cudbard-Bell ("a.cudbardb@freeradius.org")
24 * @copyright 2010 Alan DeKok ("aland@freeradius.org")
25 */
26RCSID("$Id: 702b3f48a7164347fd2be0d2fcd0843123174413 $")
27
28typedef struct request_s request_t;
29
30#include <freeradius-devel/io/test_point.h>
31#include <freeradius-devel/server/cf_parse.h>
32#include <freeradius-devel/server/command.h>
33#include <freeradius-devel/server/dependency.h>
34#include <freeradius-devel/server/dl_module.h>
35#include <freeradius-devel/server/log.h>
36#include <freeradius-devel/server/map.h>
37#ifdef WITH_TLS
38# include <freeradius-devel/tls/base.h>
39#endif
40#include <freeradius-devel/unlang/base.h>
41#include <freeradius-devel/unlang/xlat_func.h>
42#include <freeradius-devel/util/lsan.h>
43#include <freeradius-devel/util/atexit.h>
44#include <freeradius-devel/util/base64.h>
45#include <freeradius-devel/util/calc.h>
46#include <freeradius-devel/util/conf.h>
47#include <freeradius-devel/util/dns.h>
48#include <freeradius-devel/util/file.h>
49#include <freeradius-devel/util/skip.h>
50#include <freeradius-devel/util/pair_legacy.h>
51#include <freeradius-devel/util/rand.h>
52#include <freeradius-devel/util/sha1.h>
53#include <freeradius-devel/util/syserror.h>
54
55#include <freeradius-devel/util/dict_priv.h>
56
57#ifdef HAVE_GETOPT_H
58# include <getopt.h>
59#endif
60
61#include <assert.h>
62#include <fcntl.h>
63#include <libgen.h>
64#include <sys/file.h>
65#include <sys/stat.h>
66#include <sys/wait.h>
67
68#define EXIT_WITH_FAILURE \
69do { \
70 ret = EXIT_FAILURE; \
71 goto cleanup; \
72} while (0)
73
74#define COMMAND_OUTPUT_MAX 8192
75
76#define RETURN_OK(_len) \
77 do { \
78 result->rcode = RESULT_OK; \
79 result->file = __FILE__; \
80 result->line = __LINE__; \
81 return (_len); \
82 } while (0)
83
84#define RETURN_OK_WITH_ERROR() \
85 do { \
86 result->rcode = RESULT_OK; \
87 result->file = __FILE__; \
88 result->line = __LINE__; \
89 result->error_to_data = true; \
90 return 0; \
91 } while (0)
92
93#define RETURN_NOOP(_len) \
94 do { \
95 result->rcode = RESULT_NOOP; \
96 result->file = __FILE__; \
97 result->line = __LINE__; \
98 return (_len); \
99 } while (0)
100
101#define RETURN_SKIP_FILE() \
102 do { \
103 result->rcode = RESULT_SKIP_FILE; \
104 result->file = __FILE__; \
105 result->line = __LINE__; \
106 return 0; \
107 } while (0)
108
109#define RETURN_PARSE_ERROR(_offset) \
110 do { \
111 result->rcode = RESULT_PARSE_ERROR; \
112 result->offset = _offset; \
113 result->file = __FILE__; \
114 result->line = __LINE__; \
115 return 0; \
116 } while (0)
117
118#define RETURN_COMMAND_ERROR() \
119 do { \
120 result->rcode = RESULT_COMMAND_ERROR; \
121 result->file = __FILE__; \
122 result->line = __LINE__; \
123 return 0; \
124 } while (0)
125
126#define RETURN_MISMATCH(_len) \
127 do { \
128 result->rcode = RESULT_MISMATCH; \
129 result->file = __FILE__; \
130 result->line = __LINE__; \
131 return (_len); \
132 } while (0)
133
134#define RETURN_EXIT(_ret) \
135 do { \
136 result->rcode = RESULT_EXIT; \
137 result->ret = _ret; \
138 result->file = __FILE__; \
139 result->line = __LINE__; \
140 return 0; \
141 } while (0)
142
143#define SBUFF_RETURN_OK_WITH_ERROR(_function, ...) \
144 do { \
145 slen = _function(__VA_ARGS__); \
146 if (slen <= 0) RETURN_OK_WITH_ERROR(); \
147 } while (0)
148
149/** Default buffer size for a command_file_ctx_t
150 *
151 */
152#define DEFAULT_BUFFER_SIZE 1024
153
154typedef enum : uint8_t {
155 RESULT_OK = 0, //!< Not an error - Result as expected.
156 RESULT_NOOP, //!< Not an error - Did nothing...
157 RESULT_SKIP_FILE, //!< Not an error - Skip the rest of this file, or until we
158 ///< reach an "eof" command.
159 RESULT_PARSE_ERROR, //!< Fatal error - Command syntax error.
160 RESULT_COMMAND_ERROR, //!< Fatal error - Command operation error.
161 RESULT_MISMATCH, //!< Fatal error - Result didn't match what we expected.
162 RESULT_EXIT, //!< Stop processing files and exit.
164
166 { L("command-error"), RESULT_COMMAND_ERROR },
167 { L("exit"), RESULT_EXIT },
168 { L("ok"), RESULT_OK },
169 { L("parse-error"), RESULT_PARSE_ERROR },
170 { L("result-mismatch"), RESULT_MISMATCH },
171 { L("skip-file"), RESULT_SKIP_FILE },
172};
174
175typedef struct {
176 TALLOC_CTX *tmp_ctx; //!< Temporary context to hold buffers
177 ///< in this
178 union {
179 size_t offset; //!< Where we failed parsing the command.
180 int ret; //!< What code we should exit with.
181 };
182 char const *file;
183 int line;
187
188/** Configuration parameters passed to command functions
189 *
190 */
191typedef struct {
192 fr_dict_t *dict; //!< Dictionary to "reset" to.
193 fr_dict_gctx_t const *dict_gctx; //!< Dictionary gctx to "reset" to.
194 char const *confdir;
195 char const *dict_dir;
196 char const *fuzzer_base_dir; //!< Base directory of where to write fuzzer files, from '-F'
197 CONF_SECTION *features; //!< Enabled features.
199
200typedef struct {
201 TALLOC_CTX *tmp_ctx; //!< Talloc context for test points.
202
203 char *path; //!< Current path we're operating in.
204 char const *filename; //!< Current file we're operating on.
205 uint32_t lineno; //!< Current line number.
206
207 uint32_t test_count; //!< How many tests we've executed in this file.
208 ssize_t last_ret; //!< Last return value.
209
210 uint8_t *buffer; //!< Temporary resizable buffer we use for
211 ///< holding non-string data.
212 uint8_t *buffer_start; //!< Where the non-poisoned region of the buffer starts.
213 uint8_t *buffer_end; //!< Where the non-poisoned region of the buffer ends.
214
215 tmpl_rules_t tmpl_rules; //!< To pass to parsing functions.
216 fr_dict_t *test_internal_dict; //!< Internal dictionary of test_gctx.
217 fr_dict_gctx_t const *test_gctx; //!< Dictionary context for test dictionaries.
218
219 char *fuzzer_proto_dir; //!< Subdirectory of where to write fuzzer files,
220 //!< from 'fuzzer-out dir'.
221 int fuzzer_fd; //!< File descriptor pointing to a a directory to
222 ///< write fuzzer output.
225
226
227typedef struct {
228 fr_dlist_t entry; //!< Entry in the dlist.
229 uint32_t start; //!< Start of line range.
230 uint32_t end; //!< End of line range.
232
233/** Command to execute
234 *
235 * @param[out] result Of executing the command.
236 * @param[in] cc Information about the file being processed.
237 * @param[in,out] data Output of this command, or the previous command.
238 * @param[in] data_used Length of data in the data buffer.
239 * @param[in] in Command text to process.
240 * @param[in] inlen Length of the remainder of the command to process.
241 */
243 size_t data_used, char *in, size_t inlen);
244
245typedef struct {
247 char const *usage;
248 char const *description;
250
252 { .required = true, .single = true, .type = FR_TYPE_STRING },
254};
255
259
262 UNUSED fr_value_box_list_t *in)
263{
264 return XLAT_ACTION_DONE;
265}
266
267static char proto_name_prev[128] = {};
268static dl_t *dl = NULL;
269static dl_loader_t *dl_loader = NULL;
270
271static fr_event_list_t *el = NULL;
272
273static bool allow_purify = false;
274
275static char const *write_filename = NULL;
276static FILE *write_fp = NULL;
277
278static char const *receipt_file = NULL;
279static char const *receipt_dir = NULL;
280static char const *fail_file = "";
281static char const *fail_message = NULL;
282
283size_t process_line(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen);
284static int process_file(bool *exit_now, TALLOC_CTX *ctx,
285 command_config_t const *config, const char *root_dir, char const *filename, fr_dlist_head_t *lines);
286
287#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
288# define BUFF_POISON_START 1024
289# define BUFF_POISON_END 1024
290
291/** Unpoison the start and end regions of the buffer
292 *
293 */
294static int _free_buffer(uint8_t *buff)
295{
296 size_t size = talloc_array_length(buff) - (BUFF_POISON_START + BUFF_POISON_END);
297
300
301 return 0;
302}
303#else
304# define BUFF_POISON_START 0
305# define BUFF_POISON_END 0
306#endif
307
308/** Allocate a special buffer with poisoned memory regions at the start and end
309 *
310 */
311static int poisoned_buffer_allocate(TALLOC_CTX *ctx, uint8_t **buff, size_t size)
312{
313 uint8_t *our_buff = *buff;
314
315 if (our_buff) {
316 /*
317 * If it's already the correct length
318 * don't bother re-allocing the buffer,
319 * just memset it to zero.
320 */
321 if ((size + BUFF_POISON_START + BUFF_POISON_END) == talloc_array_length(our_buff)) {
322 memset(our_buff + BUFF_POISON_START, 0, size);
323 return 0;
324 }
325
326 talloc_free(our_buff); /* Destructor de-poisons */
327 *buff = NULL;
328 }
329
330 our_buff = talloc_array(ctx, uint8_t, size + BUFF_POISON_START + BUFF_POISON_END);
331 if (!our_buff) return -1;
332
333#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
334 talloc_set_destructor(our_buff, _free_buffer);
335
336 /*
337 * Poison regions before and after the buffer
338 */
341#endif
342
343 *buff = our_buff;
344
345 return 0;
346}
347#define POISONED_BUFFER_START(_p) ((_p) + BUFF_POISON_START)
348#define POISONED_BUFFER_END(_p) ((_p) + BUFF_POISON_START + (talloc_array_length(_p) - (BUFF_POISON_START + BUFF_POISON_END)))
349
350static void mismatch_print(command_file_ctx_t *cc, char const *command,
351 char *expected, size_t expected_len, char *got, size_t got_len,
352 bool print_diff)
353{
354 char *g, *e;
355
356 ERROR("%s failed %s/%s:%" PRIu32, command, cc->path, cc->filename, cc->lineno);
357
358 if (!print_diff) {
359 ERROR(" got : %.*s", (int) got_len, got);
360 ERROR(" expected : %.*s", (int) expected_len, expected);
361 } else {
362 g = got;
363 e = expected;
364
365 while (*g && *e && (*g == *e)) {
366 g++;
367 e++;
368 }
369
370 if (expected_len < 100) {
371 char const *spaces = " ";
372
373 ERROR(" EXPECTED : %.*s", (int) expected_len, expected);
374 ERROR(" GOT : %.*s", (int) got_len, got);
375 ERROR(" %.*s^ differs here (%td)", (int) (e - expected), spaces, e - expected);
376 } else if (fr_debug_lvl > 1) {
377 ERROR(" EXPECTED : %.*s", (int) expected_len, expected);
378 ERROR(" GOT : %.*s", (int) got_len, got);
379 ERROR("Differs at : %td", e - expected);
380
381 } else {
382 size_t glen, elen;
383
384 elen = strlen(e);
385 if (elen > 70) elen = 70;
386 glen = strlen(g);
387 if (glen > 70) glen = 70;
388
389 ERROR("(%td) ... %.*s ... ", e - expected, (int) elen, e);
390 ERROR("(%td) ... %.*s ... ", e - expected, (int) glen, g);
391 }
392 }
393}
394
395/** Print hex string to buffer
396 *
397 */
398static inline CC_HINT(nonnull) size_t hex_print(char *out, size_t outlen, uint8_t const *in, size_t inlen)
399{
400 char *p = out;
401 char *end = p + outlen;
402 size_t i;
403
404 if (inlen == 0) {
405 *p = '\0';
406 return 0;
407 }
408
409 for (i = 0; i < inlen; i++) {
410 size_t len;
411
412 len = snprintf(p, end - p, "%02x ", in[i]);
413 if (is_truncated(len, end - p)) return 0;
414
415 p += len;
416 }
417
418 *(--p) = '\0';
419
420 return p - out;
421}
422
423static inline CC_HINT(nonnull) int dump_fuzzer_data(int fd_dir, char const *text, uint8_t const *data, size_t data_len)
424{
425 fr_sha1_ctx ctx;
427 char digest_str[(SHA1_DIGEST_LENGTH * 2) + 1];
428 int file_fd;
429
430 fr_assert(data_len <= COMMAND_OUTPUT_MAX);
431
432 fr_sha1_init(&ctx);
433 fr_sha1_update(&ctx, (uint8_t const *)text, strlen(text));
434 fr_sha1_final(digest, &ctx);
435
436 /*
437 * We need to use the url alphabet as the standard
438 * one contains forwarded slashes which openat
439 * doesn't like.
440 */
441 fr_base64_encode_nstd(&FR_SBUFF_OUT(digest_str, sizeof(digest_str)), &FR_DBUFF_TMP(digest, sizeof(digest)),
443
444 file_fd = openat(fd_dir, digest_str, O_RDWR | O_CREAT | O_TRUNC,
445 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
446 if (file_fd < 0) {
447 fr_strerror_printf("Failed opening or creating corpus seed file \"%s\": %s",
448 digest_str, fr_syserror(errno));
449 return -1;
450 }
451
452 if (flock(file_fd, LOCK_EX) < 0) {
453 close(file_fd);
454 fr_strerror_printf("Failed locking corpus seed file \"%s\": %s",
455 digest_str, fr_syserror(errno));
456 return -1;
457 }
458
459 while (data_len) {
460 ssize_t ret;
461
462 ret = write(file_fd, data, data_len);
463 if (ret < 0) {
464 fr_strerror_printf("Failed writing to corpus seed file \"%s\": %s",
465 digest_str, fr_syserror(errno));
466 (void)flock(file_fd, LOCK_UN);
467 unlinkat(fd_dir, digest_str, 0);
468 close(file_fd);
469 return -1;
470 }
471 data_len -= ret;
472 data += ret;
473 }
474 (void)flock(file_fd, LOCK_UN);
475 close(file_fd);
476
477 return 0;
478}
479
480/*
481 * End of hacks for xlat
482 *
483 **********************************************************************/
484
485static ssize_t encode_tlv(char *buffer, uint8_t *output, size_t outlen);
486
487static char const hextab[] = "0123456789abcdef";
488
489static ssize_t encode_data_string(char *buffer, uint8_t *output, size_t outlen)
490{
491 ssize_t slen = 0;
492 char *p;
493
494 p = buffer + 1;
495
496 while (*p && (outlen > 0)) {
497 if (*p == '"') {
498 return slen;
499 }
500
501 if (*p != '\\') {
502 *(output++) = *(p++);
503 outlen--;
504 slen++;
505 continue;
506 }
507
508 switch (p[1]) {
509 default:
510 *(output++) = p[1];
511 break;
512
513 case 'n':
514 *(output++) = '\n';
515 break;
516
517 case 'r':
518 *(output++) = '\r';
519 break;
520
521 case 't':
522 *(output++) = '\t';
523 break;
524 }
525
526 p += 2;
527 outlen--;
528 slen++;
529 }
530
531 ERROR("String is not terminated");
532 return 0;
533}
534
535static ssize_t encode_data_tlv(char *buffer, char **endptr, uint8_t *output, size_t outlen)
536{
537 int depth = 0;
538 ssize_t slen;
539 char *p;
540
541 for (p = buffer; *p != '\0'; p++) {
542 if (*p == '{') depth++;
543 if (*p == '}') {
544 depth--;
545 if (depth == 0) break;
546 }
547 }
548
549 if (*p != '}') {
550 ERROR("No trailing '}' in string starting with \"%s\"", buffer);
551 return 0;
552 }
553
554 *endptr = p + 1;
555 *p = '\0';
556
557 p = buffer + 1;
559
560 slen = encode_tlv(p, output, outlen);
561 if (slen <= 0) return 0;
562
563 return slen;
564}
565
566static ssize_t hex_to_bin(uint8_t *out, size_t outlen, char *in, size_t inlen)
567{
568 char *p = in;
569 char *end = in + inlen;
570 uint8_t *out_p = out, *out_end = out_p + outlen;
571
572 while (p < end) {
573 char const *c1, *c2;
574
575 if (out_p >= out_end) {
576 fr_strerror_const("Would overflow output buffer");
577 return -(p - in);
578 }
579
581
582 if (!*p) break;
583
584 c1 = memchr(hextab, tolower((uint8_t) *p), sizeof(hextab) - 1);
585 if (!c1) {
586 bad_input:
587 fr_strerror_printf("Invalid hex data starting at \"%s\"", p);
588 return -(p - in);
589 }
590 p++;
591
592 c2 = memchr(hextab, tolower((uint8_t) *p), sizeof(hextab) - 1);
593 if (!c2) goto bad_input;
594 p++;
595
596 *out_p++ = ((c1 - hextab) << 4) + (c2 - hextab);
597 }
598
599 return out_p - out;
600}
601
602
603static ssize_t encode_data(char *p, uint8_t *output, size_t outlen)
604{
605 ssize_t slen;
606
607 if (!isspace((uint8_t) *p)) {
608 ERROR("Invalid character following attribute definition");
609 return 0;
610 }
611
613
614 if (*p == '{') {
615 size_t sublen;
616 char *q;
617
618 slen = 0;
619
620 do {
622 if (!*p) {
623 if (slen == 0) {
624 ERROR("No data");
625 return 0;
626 }
627
628 break;
629 }
630
631 sublen = encode_data_tlv(p, &q, output, outlen);
632 if (sublen <= 0) return 0;
633
634 slen += sublen;
635 output += sublen;
636 outlen -= sublen;
637 p = q;
638 } while (*q);
639
640 return slen;
641 }
642
643 if (*p == '"') {
644 slen = encode_data_string(p, output, outlen);
645 return slen;
646 }
647
648 slen = hex_to_bin(output, outlen, p, strlen(p));
649 if (slen <= 0) {
650 fr_strerror_const_push("Empty hex string");
651 return slen;
652 }
653
654 return slen;
655}
656
657static int decode_attr(char *buffer, char **endptr)
658{
659 long attr;
660
661 attr = strtol(buffer, endptr, 10);
662 if (*endptr == buffer) {
663 ERROR("No valid number found in string starting with \"%s\"", buffer);
664 return 0;
665 }
666
667 if (!**endptr) {
668 ERROR("Nothing follows attribute number");
669 return 0;
670 }
671
672 if ((attr <= 0) || (attr > 256)) {
673 ERROR("Attribute number is out of valid range");
674 return 0;
675 }
676
677 return (int) attr;
678}
679
680static int decode_vendor(char *buffer, char **endptr)
681{
682 long vendor;
683
684 if (*buffer != '.') {
685 ERROR("Invalid separator before vendor id");
686 return 0;
687 }
688
689 vendor = strtol(buffer + 1, endptr, 10);
690 if (*endptr == (buffer + 1)) {
691 ERROR("No valid vendor number found");
692 return 0;
693 }
694
695 if (!**endptr) {
696 ERROR("Nothing follows vendor number");
697 return 0;
698 }
699
700 if ((vendor <= 0) || (vendor > (1 << 24))) {
701 ERROR("Vendor number is out of valid range");
702 return 0;
703 }
704
705 if (**endptr != '.') {
706 ERROR("Invalid data following vendor number");
707 return 0;
708 }
709 (*endptr)++;
710
711 return (int) vendor;
712}
713
714static ssize_t encode_tlv(char *buffer, uint8_t *output, size_t outlen)
715{
716 int attr;
717 ssize_t slen;
718 char *p;
719
720 attr = decode_attr(buffer, &p);
721 if (attr == 0) return 0;
722
723 output[0] = attr;
724 output[1] = 2;
725
726 if (*p == '.') {
727 p++;
728 slen = encode_tlv(p, output + 2, outlen - 2);
729
730 } else {
731 slen = encode_data(p, output + 2, outlen - 2);
732 }
733
734 if (slen <= 0) return slen;
735 if (slen > (255 - 2)) {
736 ERROR("TLV data is too long");
737 return 0;
738 }
739
740 output[1] += slen;
741
742 return slen + 2;
743}
744
745static ssize_t encode_vsa(char *buffer, uint8_t *output, size_t outlen)
746{
747 int vendor;
748 ssize_t slen;
749 char *p;
750
751 vendor = decode_vendor(buffer, &p);
752 if (vendor == 0) return 0;
753
754 output[0] = 0;
755 output[1] = (vendor >> 16) & 0xff;
756 output[2] = (vendor >> 8) & 0xff;
757 output[3] = vendor & 0xff;
758
759 slen = encode_tlv(p, output + 4, outlen - 4);
760 if (slen <= 0) return slen;
761 if (slen > (255 - 6)) {
762 ERROR("VSA data is too long");
763 return 0;
764 }
765
766 return slen + 4;
767}
768
769static ssize_t encode_evs(char *buffer, uint8_t *output, size_t outlen)
770{
771 int vendor;
772 int attr;
773 ssize_t slen;
774 char *p;
775
776 vendor = decode_vendor(buffer, &p);
777 if (vendor == 0) return 0;
778
779 attr = decode_attr(p, &p);
780 if (attr == 0) return 0;
781
782 output[0] = 0;
783 output[1] = (vendor >> 16) & 0xff;
784 output[2] = (vendor >> 8) & 0xff;
785 output[3] = vendor & 0xff;
786 output[4] = attr;
787
788 slen = encode_data(p, output + 5, outlen - 5);
789 if (slen <= 0) return slen;
790
791 return slen + 5;
792}
793
794static ssize_t encode_extended(char *buffer, uint8_t *output, size_t outlen)
795{
796 int attr;
797 ssize_t slen;
798 char *p;
799
800 attr = decode_attr(buffer, &p);
801 if (attr == 0) return 0;
802
803 output[0] = attr;
804
805 if (attr == 26) {
806 slen = encode_evs(p, output + 1, outlen - 1);
807 } else {
808 slen = encode_data(p, output + 1, outlen - 1);
809 }
810 if (slen <= 0) return slen;
811 if (slen > (255 - 3)) {
812 ERROR("Extended Attr data is too long");
813 return 0;
814 }
815
816 return slen + 1;
817}
818
819static ssize_t encode_long_extended(char *buffer, uint8_t *output, size_t outlen)
820{
821 int attr;
822 ssize_t slen, total;
823 char *p;
824
825 attr = decode_attr(buffer, &p);
826 if (attr == 0) return 0;
827
828 /* output[0] is the extended attribute */
829 output[1] = 4;
830 output[2] = attr;
831 output[3] = 0;
832
833 if (attr == 26) {
834 slen = encode_evs(p, output + 4, outlen - 4);
835 if (slen <= 0) return slen;
836
837 output[1] += 5;
838 slen -= 5;
839 } else {
840 slen = encode_data(p, output + 4, outlen - 4);
841 }
842 if (slen <= 0) return slen;
843
844 total = 0;
845 while (1) {
846 int sublen = 255 - output[1];
847
848 if (slen <= sublen) {
849 output[1] += slen;
850 total += output[1];
851 break;
852 }
853
854 slen -= sublen;
855
856 memmove(output + 255 + 4, output + 255, slen);
857 memcpy(output + 255, output, 4);
858
859 output[1] = 255;
860 output[3] |= 0x80;
861
862 output += 255;
863 output[1] = 4;
864 total += 255;
865 }
866
867 return total;
868}
869
870static ssize_t encode_rfc(char *buffer, uint8_t *output, size_t outlen)
871{
872 int attr;
873 ssize_t slen, sublen;
874 char *p;
875
876 attr = decode_attr(buffer, &p);
877 if (attr == 0) return 0;
878
879 slen = 2;
880 output[0] = attr;
881 output[1] = 2;
882
883 if (attr == 26) {
884 sublen = encode_vsa(p, output + 2, outlen - 2);
885
886 } else if ((attr < 241) || (attr > 246)) {
887 sublen = encode_data(p, output + 2, outlen - 2);
888
889 } else {
890 if (*p != '.') {
891 ERROR("Invalid data following attribute number");
892 return 0;
893 }
894
895 if (attr < 245) {
896 sublen = encode_extended(p + 1, output + 2, outlen - 2);
897 } else {
898 /*
899 * Not like the others!
900 */
901 return encode_long_extended(p + 1, output, outlen);
902 }
903 }
904 if (sublen <= 0) return sublen;
905 if (sublen > (255 -2)) {
906 ERROR("RFC Data is too long");
907 return 0;
908 }
909
910 output[1] += sublen;
911 return slen + sublen;
912}
913
914
915static void unload_proto_library(void)
916{
917 proto_name_prev[0] = '\0';
918 TALLOC_FREE(dl);
919}
920
921static ssize_t load_proto_library(char const *proto_name)
922{
923 char dl_name[128];
924
925 if (strcmp(proto_name_prev, proto_name) != 0) {
926 /*
927 * Ensure the old proto library is unloaded
928 */
930
931 snprintf(dl_name, sizeof(dl_name), "libfreeradius-%s", proto_name);
932 if (dl) TALLOC_FREE(dl);
933
934 dl = dl_by_name(dl_loader, dl_name, NULL, false);
935 if (!dl) {
936 fr_perror("Failed to link to library \"%s\"", dl_name);
938 return 0;
939 }
940
941 strlcpy(proto_name_prev, proto_name, sizeof(proto_name_prev));
942 }
943
944 fr_assert(dl != NULL);
945 return strlen(proto_name);
946}
947
948static ssize_t load_test_point_by_command(void **symbol, char *command, char const *dflt_symbol)
949{
950 char buffer[256];
951 char const *p, *q;
952 void *dl_symbol;
953
954 if (!dl) {
955 fr_strerror_printf("No protocol library loaded. Specify library with \"load <proto name>\"");
956 return 0;
957 }
958
959 p = command;
960
961 /*
962 * Use the dflt_symbol name as the test point
963 */
964 if ((*p == '.') && (q = strchr(p, ' ')) && (q != (p + 1)) && ((size_t)(q - p) < sizeof(buffer))) {
965 p++;
966 strlcpy(buffer, p, (q - p) + 1);
967 p = q + 1;
968 } else {
969 snprintf(buffer, sizeof(buffer), "%s_%s", proto_name_prev, dflt_symbol);
970 }
971
972 dl_symbol = dlsym(dl->handle, buffer);
973 if (!dl_symbol) {
974 fr_strerror_printf("Test point (symbol \"%s\") not exported by library", buffer);
976 return 0;
977 }
978 *symbol = dl_symbol;
979
980 return p - command;
981}
982
984{
985 if (cc->tmpl_rules.attr.dict_def) {
987 }
988
989 return cc->config->dict;
990}
991
992/** Common dictionary load function
993 *
994 * Callers call fr_dict_global_ctx_set to set the context
995 * the dictionaries will be loaded into.
996 */
997static int dictionary_load_common(command_result_t *result, command_file_ctx_t *cc, char const *in, char const *default_subdir)
998{
999 char const *dir;
1000 char const *q;
1001 char const *name;
1002 char *tmp = NULL;
1003 int ret;
1004 fr_dict_t *dict;
1005
1006 if (in[0] == '\0') {
1007 fr_strerror_const("Missing dictionary name");
1009 }
1010
1011 /*
1012 * Decrease ref count if we're loading in a new dictionary
1013 */
1014 if (cc->tmpl_rules.attr.dict_def) {
1016 }
1017
1018 q = strchr(in, ' ');
1019 if (q) {
1020 name = tmp = talloc_bstrndup(NULL, in, q - in);
1021 q++;
1022 dir = q;
1023 } else {
1024 name = in;
1025 dir = default_subdir;
1026 }
1027
1028 ret = fr_dict_protocol_afrom_file(&dict, name, dir, __FILE__);
1029 talloc_free(tmp);
1030 if (ret < 0) RETURN_COMMAND_ERROR();
1031
1033 cc->tmpl_rules.attr.namespace = fr_dict_root(dict);
1034
1035 /*
1036 * Dump the dictionary if we're in super debug mode
1037 */
1039
1040
1041 RETURN_OK(0);
1042}
1043
1044static size_t parse_typed_value(command_result_t *result, command_file_ctx_t *cc, fr_value_box_t *box, char const **out, char const *in, size_t inlen)
1045{
1047 size_t match_len;
1048 fr_slen_t slen;
1049 char const *p;
1050 fr_sbuff_t sbuff;
1051 fr_dict_attr_t const *enumv = NULL;
1052
1053 /*
1054 * Parse data types
1055 */
1057 if (fr_type_is_null(type)) {
1059 }
1060 fr_assert(match_len <= inlen);
1061
1062 p = in + match_len;
1064 *out = p;
1065
1066 if (type == FR_TYPE_ATTR) {
1067 enumv = cc->tmpl_rules.attr.dict_def ?
1070 }
1071
1072 /*
1073 * As a hack, allow most things to be inside
1074 * double-quoted strings. This is really only for dates,
1075 * which are space-delimited.
1076 */
1077 if (*p == '"'){
1078 p++;
1079 sbuff = FR_SBUFF_IN(p, strlen(p));
1080 slen = fr_value_box_from_substr(box, box, FR_TYPE_STRING, enumv,
1081 &sbuff,
1083 if (slen < 0) {
1085 }
1086
1087 p += fr_sbuff_used(&sbuff);
1088 if (*p != '"') {
1090 }
1091 p++;
1092
1093 if (type != FR_TYPE_STRING) {
1094 if (fr_value_box_cast_in_place(box, box, type, NULL) < 0) {
1096 }
1097 }
1098
1099 } else {
1100 sbuff = FR_SBUFF_IN(p, strlen(p));
1101
1102 /*
1103 * We have no other way to pass the dict to the value-box parse function.
1104 */
1105 if (type == FR_TYPE_ATTR) {
1106 fr_dict_t const *dict = dictionary_current(cc);
1107
1108 if (!dict) {
1109 fr_strerror_const("proto-dictionary must be defined");
1111 }
1112
1113 enumv = fr_dict_root(dict);
1114 }
1115
1116 slen = fr_value_box_from_substr(box, box, type, enumv,
1117 &sbuff,
1119 if (slen < 0) {
1121 }
1122 p += fr_sbuff_used(&sbuff);
1123 }
1125
1126 RETURN_OK(p - in);
1127}
1128
1129static fr_cmd_t *command_head = NULL;
1130
1131static int command_func(UNUSED FILE *fp, UNUSED FILE *fp_err, UNUSED void *ctx, UNUSED fr_cmd_info_t const *info)
1132{
1133 return 0;
1134}
1135
1136static int command_walk(UNUSED void *ctx, fr_cmd_walk_info_t *info)
1137{
1138 int i;
1139
1140 for (i = 0; i < info->num_parents; i++) {
1141 printf("%s ", info->parents[i]);
1142 }
1143
1144 printf(":%s ", info->name);
1145 if (info->syntax) printf("%s", info->syntax);
1146 printf("%s", "");
1147
1148 return 1;
1149}
1150
1151static void command_print(void)
1152{
1153 void *walk_ctx = NULL;
1154
1155 printf("Command hierarchy --------");
1156 fr_cmd_debug(stdout, command_head);
1157
1158 printf("Command list --------");
1159 while (fr_command_walk(command_head, &walk_ctx, NULL, command_walk) == 1) {
1160 // do nothing
1161 }
1162}
1163
1164#define CLEAR_TEST_POINT(_cc) \
1165do { \
1166 talloc_free_children((_cc)->tmp_ctx); \
1167 tp = NULL; \
1168} while (0)
1169
1170/** Placeholder function for comments
1171 *
1172 */
1174 UNUSED char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
1175{
1176 return 0;
1177}
1178
1179/** Execute another test file
1180 *
1181 */
1183 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
1184{
1185 char *q;
1186 bool exit_now = false;
1187 int ret;
1188
1189 if (write_fp) {
1190 fprintf(stderr, "Can't do $INCLUDE with -w %s\n", write_filename);
1191 RETURN_EXIT(1);
1192 }
1193
1194 q = strrchr(cc->path, '/');
1195 if (q) {
1196 *q = '\0';
1197 ret = process_file(&exit_now, cc->tmp_ctx, cc->config, cc->path, in, NULL);
1198 if (exit_now || (ret != 0)) RETURN_EXIT(ret);
1199 *q = '/';
1200 RETURN_OK(0);
1201 }
1202
1203 ret = process_file(&exit_now, cc->tmp_ctx, cc->config, NULL, in, NULL);
1204 if (exit_now || (ret != 0)) RETURN_EXIT(ret);
1205
1206 RETURN_OK(0);
1207}
1208
1209/** Determine if unresolved attributes are allowed
1210 *
1211 */
1213 UNUSED char *data, UNUSED size_t data_used, char *in, size_t inlen)
1214{
1215 fr_sbuff_t our_in = FR_SBUFF_IN(in, inlen);
1216 bool res;
1217
1218 if (fr_sbuff_out_bool(&res, &our_in) < 0) {
1219 fr_strerror_printf("Invalid boolean value, must be \"yes\" or \"no\"");
1221 }
1223
1224 RETURN_OK(0);
1225}
1226
1227#define ATTR_COMMON \
1228 fr_sbuff_t our_in = FR_SBUFF_IN(in, inlen); \
1229 fr_dict_attr_err_t err; \
1230 fr_slen_t slen; \
1231 fr_dict_attr_t const *root; \
1232 fr_dict_attr_t const *da; \
1233 root = cc->tmpl_rules.attr.dict_def ? \
1234 fr_dict_root(cc->tmpl_rules.attr.dict_def) : \
1235 fr_dict_root(fr_dict_internal()); \
1236 slen = fr_dict_attr_by_oid_substr(&err, \
1237 &da, \
1238 root, \
1239 &our_in, NULL); \
1240 if (err != FR_DICT_ATTR_OK) FR_SBUFF_ERROR_RETURN(&our_in)
1241
1242
1243/** Print attribute information
1244 *
1245 */
1247 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1248{
1249 fr_hash_table_t *namespace;
1250 fr_hash_iter_t iter;
1251 fr_dict_attr_t const *ref;
1254
1255 namespace = dict_attr_namespace(da);
1256 fr_assert(namespace != NULL);
1257
1258 for (da = fr_hash_table_iter_init(namespace, &iter);
1259 da != NULL;
1260 da = fr_hash_table_iter_next(namespace, &iter)) {
1261 if (da->flags.is_alias) {
1262 ref = fr_dict_attr_ref(da);
1263 fr_assert(ref != NULL);
1264
1265 SBUFF_RETURN_OK_WITH_ERROR(fr_sbuff_in_sprintf, &out, "%s (ALIAS ref=", da->name);
1266
1267 slen = fr_dict_attr_oid_print(&out, fr_dict_root(da->dict), ref, false);
1268 if (slen <= 0) RETURN_OK_WITH_ERROR();
1269
1271 continue;
1272 }
1273
1274 SBUFF_RETURN_OK_WITH_ERROR(fr_sbuff_in_sprintf, &out, "%s (%s), ", da->name, fr_type_to_str(da->type));
1275 }
1276
1277 fr_sbuff_trim(&out, (bool[SBUFF_CHAR_CLASS]){ [' '] = true, [','] = true });
1278
1280}
1281
1282
1283/** Print attribute information
1284 *
1285 */
1287 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1288{
1290
1291 slen = fr_dict_attr_flags_print(&FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX), da->dict, da->type, &da->flags);
1292 if (slen <= 0) RETURN_OK_WITH_ERROR();
1293
1294 RETURN_OK(slen);
1295}
1296
1297/** Print attribute information
1298 *
1299 */
1301 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1302{
1304
1305 slen = fr_dict_attr_oid_print(&FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX), root, da, false);
1306 if (slen <= 0) RETURN_OK_WITH_ERROR();
1307
1308 RETURN_OK(slen);
1309}
1310
1311/** Print attribute information
1312 *
1313 */
1315 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1316{
1318
1320 if (slen <= 0) RETURN_OK_WITH_ERROR();
1321
1322 RETURN_OK(slen);
1323}
1324
1325/** Print attribute information
1326 *
1327 */
1329 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1330{
1332
1334
1335 RETURN_OK(slen);
1336}
1337
1339 [ '+' ] = T_ADD,
1340 [ '-' ] = T_SUB,
1341 [ '*' ] = T_MUL,
1342 [ '/' ] = T_DIV,
1343 [ '^' ] = T_XOR,
1344 [ '.' ] = T_ADD,
1345 [ '&' ] = T_AND,
1346 [ '|' ] = T_OR,
1347 [ '%' ] = T_MOD,
1348};
1349
1350/** Perform calculations
1351 *
1352 */
1354 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1355{
1356 fr_value_box_t *a, *b, *out;
1357 size_t match_len;
1359 fr_token_t op;
1360 char const *p, *value, *end;
1361 size_t slen;
1362 bool assignment;
1363
1364 a = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1365 b = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1366
1367 p = in;
1368 end = in + inlen;
1369
1370 match_len = parse_typed_value(result, cc, a, &value, p, end - p);
1371 if (match_len == 0) return 0; /* errors have already been updated */
1372
1373 p += match_len;
1375
1376 op = fr_table_value_by_longest_prefix(&match_len, fr_tokens_table, p, end - p, T_INVALID);
1377 if (op != T_INVALID) {
1378 p += match_len;
1379 assignment = fr_assignment_op[op];
1380
1381 } else {
1382 op = token2op[(uint8_t) p[0]];
1383 if (op == T_INVALID) {
1384 fr_strerror_printf("Unknown operator '%c'", p[0]);
1386 }
1387 p++;
1388
1389 assignment = false;
1390 }
1392
1393 match_len = parse_typed_value(result, cc, b, &value, p, end - p);
1394 if (match_len == 0) return 0;
1395
1396 p += match_len;
1398
1399 if (assignment) {
1400 if (fr_value_calc_assignment_op(cc->tmp_ctx, a, op, b) < 0) {
1402 }
1403 out = a;
1404
1405 } else {
1406 out = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1407
1408 /*
1409 * If there's no output data type, then the code tries to
1410 * figure one out automatically.
1411 */
1412 if (!*p) {
1414 } else {
1415 if (strncmp(p, "->", 2) != 0) RETURN_PARSE_ERROR(0);
1416 p += 2;
1418
1421 fr_value_box_init(out, type, NULL, false);
1422 }
1423
1424 if (fr_value_calc_binary_op(cc->tmp_ctx, out, type, a, op, b) < 0) {
1426 }
1427 }
1428
1430 if (slen <= 0) RETURN_OK_WITH_ERROR();
1431
1432 RETURN_OK(slen);
1433}
1434
1435/** Perform calculations on multi-valued ops
1436 *
1437 */
1439 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1440{
1441 fr_value_box_t *group, *a, *out;
1442 size_t match_len;
1444 fr_token_t op;
1445 char const *p, *value, *end;
1446 size_t slen;
1447
1448 group = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1449 fr_value_box_init(group, FR_TYPE_GROUP, NULL, false);
1450
1451 p = in;
1452 end = in + inlen;
1453
1454 /*
1455 * Multi-valued operations
1456 */
1457 op = token2op[(uint8_t) p[0]];
1458 if (op == T_INVALID) {
1459 fr_strerror_printf("Unknown operator '%c'", p[0]);
1461 }
1462 p++;
1463
1464 while (p < end) {
1466
1467 a = talloc_zero(group, fr_value_box_t);
1468
1469 match_len = parse_typed_value(result, cc, a, &value, p, end - p);
1470 if (match_len == 0) return 0; /* errors have already been updated */
1471
1472 fr_value_box_list_insert_tail(&group->vb_group, a);
1473
1474 p += match_len;
1475
1476 if (strncmp(p, "->", 2) == 0) break;
1477 }
1478
1479 out = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1481
1482 if (strncmp(p, "->", 2) != 0) RETURN_PARSE_ERROR(0);
1483 p += 2;
1485
1488
1489
1490 if (fr_value_calc_nary_op(cc->tmp_ctx, out, type, op, group) < 0) {
1492 }
1493
1495 if (slen <= 0) RETURN_OK_WITH_ERROR();
1496
1497 RETURN_OK(slen);
1498}
1499
1500/** Perform casting
1501 *
1502 */
1504 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1505{
1506 fr_value_box_t *a, *out;
1507 size_t match_len;
1509 char const *p, *value, *end;
1510 size_t slen;
1511 fr_dict_attr_t const *enumv = NULL;
1512
1513 a = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1514
1515 p = in;
1516 end = in + inlen;
1517
1518 match_len = parse_typed_value(result, cc, a, &value, p, end - p);
1519 if (match_len == 0) return 0; /* errors have already been updated */
1520
1521 p += match_len;
1523
1524 out = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1525
1526 if (strncmp(p, "->", 2) != 0) RETURN_PARSE_ERROR(0);
1527 p += 2;
1529
1532 fr_value_box_init(out, type, NULL, false);
1533
1534 if (type == FR_TYPE_ATTR) {
1535 enumv = cc->tmpl_rules.attr.dict_def ?
1538 }
1539
1540 if (fr_value_box_cast(out, out, type, enumv, a) < 0) {
1542 }
1543
1545 if (slen <= 0) RETURN_OK_WITH_ERROR();
1546
1547 RETURN_OK(slen);
1548}
1549
1550/** Change the working directory
1551 *
1552 */
1554 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1555{
1556 TALLOC_FREE(cc->path); /* Free old directories */
1557
1558 cc->path = fr_realpath(cc->tmp_ctx, in, inlen);
1559 if (!cc->path) RETURN_COMMAND_ERROR();
1560
1562
1564}
1565
1566/*
1567 * Clear the data buffer
1568 */
1570 char *data, size_t UNUSED data_used, UNUSED char *in, UNUSED size_t inlen)
1571{
1572 memset(data, 0, COMMAND_OUTPUT_MAX);
1573 RETURN_NOOP(0);
1574}
1575
1576/*
1577 * Add a command by talloc'ing a table for it.
1578 */
1580 char *data, size_t UNUSED data_used, char *in, UNUSED size_t inlen)
1581{
1582 char *p, *name;
1583 char *parent = NULL;
1584 fr_cmd_table_t *table;
1585 char buffer[8192];
1586
1587 table = talloc_zero(cc->tmp_ctx, fr_cmd_table_t);
1588
1589 strlcpy(buffer, in, sizeof(buffer));
1590
1591 p = strchr(buffer, ':');
1592 if (!p) {
1593 fr_strerror_const("no ':name' specified");
1595 }
1596
1597 *p = '\0';
1598 p++;
1599
1601
1602 /*
1603 * Set the name and try to find the syntax.
1604 */
1605 name = p;
1607
1608 if (isspace((uint8_t) *p)) {
1609 *p = '\0';
1610 p++;
1611 }
1612
1614
1615 if (*p) {
1616 table->syntax = talloc_strdup(table, p);
1617 }
1618 table->parent = parent;
1619 table->name = name;
1620 table->help = NULL;
1621 table->func = command_func;
1622 table->tab_expand = NULL;
1623 table->read_only = true;
1624
1625 if (fr_command_add(table, &command_head, NULL, NULL, table) < 0) {
1626 fr_strerror_const_push("Failed adding command");
1628 }
1629
1631
1633}
1634
1635/*
1636 * Do tab completion on a command
1637 */
1639 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
1640{
1641 int i;
1642 int num_expansions;
1643 char const *expansions[CMD_MAX_ARGV];
1644 char *p = data, *end = p + COMMAND_OUTPUT_MAX, **argv;
1645 fr_cmd_info_t info;
1646 size_t len;
1647
1648 info.argc = 0;
1649 info.max_argc = CMD_MAX_ARGV;
1650 info.argv = talloc_zero_array(cc->tmp_ctx, char const *, CMD_MAX_ARGV);
1651 info.box = talloc_zero_array(cc->tmp_ctx, fr_value_box_t *, CMD_MAX_ARGV);
1652
1653 memcpy(&argv, &info.argv, sizeof(argv)); /* const issues */
1654 info.argc = fr_dict_str_to_argv(in, argv, CMD_MAX_ARGV);
1655 if (info.argc <= 0) {
1656 fr_strerror_const("Failed splitting input");
1657 RETURN_PARSE_ERROR(-(info.argc));
1658 }
1659
1660 num_expansions = fr_command_tab_expand(cc->tmp_ctx, command_head, &info, CMD_MAX_ARGV, expansions);
1661
1662 len = snprintf(p, end - p, "%d - ", num_expansions);
1663 if (is_truncated(len, end - p)) {
1664 oob:
1665 fr_strerror_const("Out of output buffer space for radmin command");
1667 }
1668 p += len;
1669
1670 for (i = 0; i < num_expansions; i++) {
1671 len = snprintf(p, end - p, "'%s', ", expansions[i]);
1672 if (is_truncated(len, end - p)) goto oob;
1673 p += len;
1674 }
1675
1676 /*
1677 * Remove the trailing ", "
1678 */
1679 if (num_expansions > 0) {
1680 p -= 2;
1681 *p = '\0';
1682 }
1683
1684 return p - data;
1685}
1686
1687/** Parse and reprint a condition
1688 *
1689 */
1691 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1692{
1693 fr_slen_t slen;
1694 CONF_SECTION *cs;
1695 size_t len;
1696 xlat_exp_head_t *head = NULL;
1697
1698 cs = cf_section_alloc(NULL, NULL, "if", "condition");
1699 if (!cs) {
1700 fr_strerror_const("Out of memory");
1702 }
1703 cf_filename_set(cs, cc->filename);
1704 cf_lineno_set(cs, cc->lineno);
1705
1707
1708 slen = xlat_tokenize_condition(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, inlen), NULL, &cc->tmpl_rules);
1709 if (slen == 0) {
1710 fr_strerror_printf_push_head("failed to parse any input");
1711 talloc_free(cs);
1713 }
1714
1715 if (slen < 0) {
1716 fr_strerror_printf_push_head("offset %d", (int) -slen - 1);
1717 talloc_free(cs);
1719 }
1720
1721 if ((size_t) slen < inlen) {
1722 len = snprintf(data, COMMAND_OUTPUT_MAX, "ERROR passed in %zu, returned %zd", inlen, slen);
1723
1724 } else {
1726 }
1727
1729 talloc_free(cs);
1730
1731 RETURN_OK(len);
1732}
1733
1735 char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
1736{
1737 size_t len;
1738
1741 fr_strerror_const("Command count would overflow data buffer (shouldn't happen)");
1743 }
1744
1745 RETURN_OK(len);
1746}
1747
1748/** Decode hex data into a list of pairs
1749 *
1750 * Shared by "decode-pair" and "de-encode-pair". The decoded pairs are the children of *head_p,
1751 * and are allocated in cc->tmp_ctx. The caller has to clear that context once it is finished
1752 * with them.
1753 *
1754 * @param[out] result of the command, when this function fails.
1755 * @param[in] cc command file context.
1756 * @param[in,out] data data buffer, which holds the input hex, and is then used as scratch space.
1757 * @param[in] in text of the command, which starts with the test point.
1758 * @param[in] inlen length of "in".
1759 * @param[out] head_p pair which holds the decoded pairs.
1760 * @param[out] hex_p where the hex data started in "in". The caller needs it in order to look
1761 * for a second test point. May be NULL.
1762 * @return
1763 * - >0 the number of bytes of input which were decoded.
1764 * - 0 on failure, where "result" has been filled in, and the caller has to return 0.
1765 */
1767 char *data, char *in, size_t inlen, fr_pair_t **head_p, char **hex_p)
1768{
1769 fr_test_point_pair_decode_t *tp = NULL;
1770 void *decode_ctx = NULL;
1771 char *p;
1772 uint8_t *to_dec, *to_dec_start;
1773 uint8_t *to_dec_end;
1774 ssize_t slen;
1775#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
1776 size_t poison_size;
1777#endif
1778
1779 fr_dict_attr_t const *da;
1780 fr_pair_t *head;
1781
1782 da = fr_dict_attr_by_name(NULL, fr_dict_root(fr_dict_internal()), "request");
1783 fr_assert(da != NULL);
1784 head = fr_pair_afrom_da(cc->tmp_ctx, da);
1785 if (!head) {
1786 fr_strerror_const_push("Failed allocating memory");
1788 }
1789
1790 p = in;
1791
1792 slen = load_test_point_by_command((void **)&tp, in, "tp_decode_pair");
1793 if (!tp) {
1794 fr_strerror_const_push("Failed locating decoder testpoint");
1796 }
1797
1798 p += slen;
1800
1801 if (tp->test_ctx && (tp->test_ctx(&decode_ctx, cc->tmp_ctx, dictionary_current(cc), NULL) < 0)) {
1802 fr_strerror_const_push("Failed initialising decoder testpoint");
1804 }
1805
1806 /*
1807 * If we are asked to re-use the previous output, then
1808 * just copy the output to the input. That way when this
1809 * function writes to the output, the input data isn't
1810 * over-written.
1811 */
1812 if (*p == '-') {
1813 strcpy(p, data);
1814 inlen = strlen(data);
1815 }
1816
1817 /*
1818 * Decode hex from input text
1819 */
1820 slen = hex_to_bin((uint8_t *)data, COMMAND_OUTPUT_MAX, p, inlen);
1821 if (slen <= 0) {
1822 CLEAR_TEST_POINT(cc);
1823 RETURN_PARSE_ERROR(-(slen));
1824 }
1825
1826 to_dec = to_dec_start = (uint8_t *)data;
1827 to_dec_end = to_dec + slen;
1828
1829#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
1830 poison_size = COMMAND_OUTPUT_MAX - slen;
1831#endif
1832 ASAN_POISON_MEMORY_REGION(to_dec_end, poison_size);
1833
1834 /*
1835 * Run the input data through the test
1836 * point to produce fr_pair_ts.
1837 */
1838 while (to_dec < to_dec_end) {
1839 slen = tp->func(head, &head->vp_group, cc->tmpl_rules.attr.namespace,
1840 (uint8_t *)to_dec, (to_dec_end - to_dec), decode_ctx);
1841
1842 cc->last_ret = slen;
1843 if (slen <= 0) {
1844 ASAN_UNPOISON_MEMORY_REGION(to_dec_end, poison_size);
1845 CLEAR_TEST_POINT(cc);
1847 }
1848 if ((size_t)slen > (size_t)(to_dec_end - to_dec)) {
1849 fr_perror("%s: Internal sanity check failed at %d", __FUNCTION__, __LINE__);
1850 ASAN_UNPOISON_MEMORY_REGION(to_dec_end, poison_size);
1851 CLEAR_TEST_POINT(cc);
1853 }
1854 to_dec += slen;
1855 }
1856
1857 /*
1858 * Clear any spurious errors
1859 */
1861 ASAN_UNPOISON_MEMORY_REGION(to_dec_end, poison_size);
1862
1863 if ((cc->fuzzer_fd >= 0) &&
1864 (dump_fuzzer_data(cc->fuzzer_fd, in, to_dec_start, (to_dec - to_dec_start)) < 0)) {
1866 }
1867
1868 if (hex_p) *hex_p = p;
1869 *head_p = head;
1870
1871 return to_dec - to_dec_start;
1872}
1873
1875 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1876{
1877 fr_pair_t *head;
1878 ssize_t slen;
1879
1880 if (decode_pair(result, cc, data, in, inlen, &head, NULL) == 0) return 0;
1881
1882 /*
1883 * Output may be an error, and we ignore
1884 * it if so.
1885 */
1886 slen = fr_pair_list_print(&FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX), NULL, &head->vp_group);
1887 if (slen <= 0) {
1889 }
1890
1891 /*
1892 * As CLEAR_TEST_POINT(), but the test point is a local variable of decode_pair().
1893 */
1894 talloc_free_children(cc->tmp_ctx);
1895
1896 RETURN_OK(slen);
1897}
1898
1900 char *data, UNUSED size_t data_used, char *in, size_t inlen)
1901{
1903 void *decode_ctx = NULL;
1904 char *p;
1905 uint8_t *to_dec, *to_dec_start;
1906 uint8_t *to_dec_end;
1907 ssize_t slen;
1908#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
1909 size_t poison_size;
1910#endif
1911 fr_dict_attr_t const *da;
1912 fr_pair_t *head;
1913
1914 da = fr_dict_attr_by_name(NULL, fr_dict_root(fr_dict_internal()), "request");
1915 fr_assert(da != NULL);
1916 head = fr_pair_afrom_da(cc->tmp_ctx, da);
1917 if (!head) {
1918 fr_strerror_const_push("Failed allocating memory");
1920 }
1921
1922 p = in;
1923
1924 slen = load_test_point_by_command((void **)&tp, in, "tp_decode_proto");
1925 if (!tp) {
1926 fr_strerror_const_push("Failed locating decoder testpoint");
1928 }
1929
1930 p += slen;
1932
1933 if (tp->test_ctx && (tp->test_ctx(&decode_ctx, cc->tmp_ctx, dictionary_current(cc), NULL) < 0)) {
1934 fr_strerror_const_push("Failed initialising decoder testpoint");
1936 }
1937
1938 /*
1939 * If we are asked to re-use the previous output, then
1940 * just copy the output to the input. That way when this
1941 * function writes to the output, the input data isn't
1942 * over-written.
1943 */
1944 if (*p == '-') {
1945 strcpy(in, data);
1946 inlen = strlen(data);
1947 }
1948
1949 /*
1950 * Decode hex from input text
1951 */
1952 slen = hex_to_bin((uint8_t *)data, COMMAND_OUTPUT_MAX, p, inlen);
1953 if (slen <= 0) {
1954 CLEAR_TEST_POINT(cc);
1955 RETURN_PARSE_ERROR(-(slen));
1956 }
1957
1958 to_dec = to_dec_start = (uint8_t *)data;
1959 to_dec_end = to_dec + slen;
1960
1961#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
1962 poison_size = COMMAND_OUTPUT_MAX - slen;
1963#endif
1964 ASAN_POISON_MEMORY_REGION(to_dec_end, poison_size);
1965
1966 slen = tp->func(head, &head->vp_group,
1967 (uint8_t *)to_dec, (to_dec_end - to_dec), decode_ctx);
1968 cc->last_ret = slen;
1969 if (slen <= 0) {
1970 ASAN_UNPOISON_MEMORY_REGION(to_dec_end, poison_size);
1971 CLEAR_TEST_POINT(cc);
1973 }
1974
1975 /*
1976 * Clear any spurious errors
1977 */
1979 ASAN_UNPOISON_MEMORY_REGION(to_dec_end, poison_size);
1980
1981 if ((cc->fuzzer_fd >= 0) &&
1982 (dump_fuzzer_data(cc->fuzzer_fd, in, to_dec_start, slen) < 0)) {
1984 }
1985
1986 /*
1987 * Output may be an error, and we ignore
1988 * it if so.
1989 */
1990
1991 /*
1992 * Print the pairs.
1993 */
1994 slen = fr_pair_list_print(&FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX), NULL, &head->vp_group);
1995 if (slen <= 0) {
1996 fr_assert(0);
1998 }
1999
2000 CLEAR_TEST_POINT(cc);
2001 RETURN_OK(slen);
2002}
2003
2004/** Parse a dictionary attribute, writing "ok" to the data buffer is everything was ok
2005 *
2006 */
2008 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2009{
2011
2013}
2014
2015/** Read a dictionary from a file, and then free it.
2016 *
2017 */
2019 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2020{
2021 int ret;
2022 char *filename, *dir = NULL;
2023 char *p;
2024 fr_dict_t *dict;
2025
2027
2028 if (in[0] == '\0') {
2029 fr_strerror_const("Missing dictionary name");
2031 }
2032
2033 p = strchr(in, ' ');
2034 if (p) *p = '\0';
2035
2036 filename = in;
2037 if (*filename != '/') {
2038 dir = cc->path;
2039
2040 }
2041
2042 /*
2043 * Load the dictionary, and then immediately free it. This lets each run proceed independently.
2044 */
2045 ret = fr_dict_afrom_file(&dict, dir, filename);
2046 if (ret < 0) RETURN_OK_WITH_ERROR();
2047
2049
2051}
2052
2053/** Print the currently loaded dictionary
2054 *
2055 */
2057 UNUSED char *data, size_t data_used, UNUSED char *in, UNUSED size_t inlen)
2058{
2060
2061 /*
2062 * Don't modify the contents of the data buffer
2063 */
2064 RETURN_OK(data_used);
2065}
2066
2067static CC_HINT(nonnull)
2069 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2070{
2071 size_t need;
2072 ssize_t ret;
2073 char *p, *next;
2074 uint8_t *enc_p;
2075 char buffer[8192];
2076
2077 strlcpy(buffer, in, sizeof(buffer));
2078
2079 p = buffer;
2080 next = strchr(p, ',');
2081 if (next) *next = 0;
2082
2083 enc_p = cc->buffer_start;
2084
2085 while (true) {
2086 fr_value_box_t *box = talloc_zero(NULL, fr_value_box_t);
2087
2089
2090 if (fr_value_box_from_str(box, box, FR_TYPE_STRING, NULL,
2091 p, strlen(p),
2093 talloc_free(box);
2095 }
2096
2097 ret = fr_dns_label_from_value_box(&need,
2098 cc->buffer_start, cc->buffer_end - cc->buffer_start, enc_p, true, box, NULL);
2099 talloc_free(box);
2100
2101 if (ret < 0) RETURN_OK_WITH_ERROR();
2102
2103 if (ret == 0) RETURN_OK(snprintf(data, COMMAND_OUTPUT_MAX, "need=%zu", need));
2104
2105 enc_p += ret;
2106
2107 /*
2108 * Go to the next input string
2109 */
2110 if (!next) break;
2111
2112 p = next + 1;
2113 next = strchr(p, ',');
2114 if (next) *next = 0;
2115 }
2116
2117 if ((cc->fuzzer_fd >= 0) &&
2118 (dump_fuzzer_data(cc->fuzzer_fd, in, cc->buffer_start, enc_p - cc->buffer_start) < 0)) {
2120 }
2121
2122 RETURN_OK(hex_print(data, COMMAND_OUTPUT_MAX, cc->buffer_start, enc_p - cc->buffer_start));
2123}
2124
2126 char *data, UNUSED size_t data_used, char *in, size_t inlen)
2127{
2128 ssize_t slen, total, i, outlen;
2129 char *out, *end;
2130 fr_value_box_t *box = talloc_zero(NULL, fr_value_box_t);
2131
2132 /*
2133 * Decode hex from input text
2134 */
2135 total = hex_to_bin(cc->buffer_start, cc->buffer_end - cc->buffer_start, in, inlen);
2136 if (total <= 0) RETURN_PARSE_ERROR(-total);
2137
2138 out = data;
2139 end = data + COMMAND_OUTPUT_MAX;
2140
2141 for (i = 0; i < total; i += slen) {
2142 slen = fr_dns_label_to_value_box(box, box, cc->buffer_start, total, cc->buffer_start + i, false, NULL);
2143 if (slen <= 0) {
2144 error:
2145 talloc_free(box);
2147 }
2148
2149 /*
2150 * Separate names by commas
2151 */
2152 if (i > 0) *(out++) = ',';
2153
2154 /*
2155 * We don't print it with quotes.
2156 */
2157 outlen = fr_value_box_print(&FR_SBUFF_OUT(out, end - out), box, NULL);
2158 if (outlen <= 0) goto error;
2159 out += outlen;
2160
2161 fr_value_box_clear(box);
2162 }
2163
2164 talloc_free(box);
2165 RETURN_OK(out - data);
2166}
2167
2168/** Encode a list of pairs, writing the result to the data buffer as hex
2169 *
2170 * Shared by "encode-pair" and "de-encode-pair". The pair list and the test point context are
2171 * freed here, which means that the caller cannot use either one afterwards.
2172 *
2173 * @param[out] result of the command. It is always filled in, so the caller returns
2174 * whatever this function returns.
2175 * @param[in] cc command file context.
2176 * @param[out] data data buffer, where the hex output is written.
2177 * @param[in] tp encoder test point.
2178 * @param[in] encode_ctx context for the encoder test point.
2179 * @param[in] head pairs to encode.
2180 * @param[in] truncate run the truncate torture test.
2181 * @param[in] fuzzer_text name the corpus seed file after this text, or NULL to write no
2182 * seed file.
2183 * @return the number of characters written to the data buffer.
2184 */
2185static size_t encode_pair(command_result_t *result, command_file_ctx_t *cc, char *data,
2187 fr_pair_list_t *head, bool truncate, char const *fuzzer_text)
2188{
2189 fr_dcursor_t cursor;
2190 fr_pair_t *vp;
2191 uint8_t *enc_p, *enc_end;
2192 ssize_t slen = 0;
2193 size_t iterations = 0;
2194
2195 /*
2196 * Outer loop implements truncate test
2197 */
2198 do {
2199 enc_p = cc->buffer_start;
2200 enc_end = truncate ? cc->buffer_start + iterations++ : cc->buffer_end;
2201
2202 if (truncate) {
2203#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
2204 /*
2205 * Poison the region between the subset of the buffer
2206 * we're using and the end of the buffer.
2207 */
2208 ASAN_POISON_MEMORY_REGION(enc_end, (cc->buffer_end) - enc_end);
2209
2210 DEBUG("%s[%" PRIu32 "]: Iteration %zu - Safe region %p-%p (%td bytes), "
2211 "poisoned region %p-%p (%td bytes)", cc->filename, cc->lineno, iterations - 1,
2212 enc_p, enc_end, enc_end - enc_p, enc_end, cc->buffer_end, cc->buffer_end - enc_end);
2213#else
2214 DEBUG("%s[%" PRIu32 "]: Iteration %zu - Allowed region %p-%p (%td bytes)",
2215 cc->filename, cc->lineno, iterations - 1, enc_p, enc_end, enc_end - enc_p);
2216#endif
2217 }
2218
2219 for (vp = fr_pair_dcursor_iter_init(&cursor, head,
2221 dictionary_current(cc));
2222 vp;
2223 vp = fr_dcursor_current(&cursor)) {
2224 slen = tp->func(&FR_DBUFF_TMP(enc_p, enc_end), &cursor, encode_ctx);
2225 cc->last_ret = slen;
2226
2227 if (truncate) DEBUG("%s[%" PRIu32 "]: Iteration %zu - Result %zd%s%s",
2228 cc->filename, cc->lineno, iterations - 1, slen,
2229 *fr_strerror_peek() != '\0' ? " - " : "",
2230 *fr_strerror_peek() != '\0' ? fr_strerror_peek() : "");
2231 if (slen < 0) break;
2232
2233 /*
2234 * Encoder indicated it encoded too much data
2235 */
2236 if (slen > (enc_end - enc_p)) {
2237 fr_strerror_printf("Expected returned encoded length <= %td bytes, got %zu bytes",
2238 (enc_end - enc_p), (size_t)slen);
2239#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
2240 if (truncate) ASAN_UNPOISON_MEMORY_REGION(enc_end, (cc->buffer_end) - enc_end);
2241#endif
2243 CLEAR_TEST_POINT(cc);
2245 }
2246
2247 enc_p += slen;
2248
2249 if (slen == 0) break;
2250
2251 }
2252
2253#ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
2254 /*
2255 * un-poison the region between the subset of the buffer
2256 * we're using and the end of the buffer.
2257 */
2258 if (truncate) ASAN_UNPOISON_MEMORY_REGION(enc_end, (cc->buffer_end) - enc_end);
2259#endif
2260 /*
2261 * We consumed all the VPs, so presumably encoded the
2262 * complete pair list.
2263 */
2264 if (!vp) break;
2265 } while (truncate && (enc_end < cc->buffer_end));
2266
2267 /*
2268 * Last iteration result in an error
2269 */
2270 if (slen < 0) {
2272 CLEAR_TEST_POINT(cc);
2274 }
2275
2276 /*
2277 * Clear any spurious errors
2278 */
2280
2282
2283 CLEAR_TEST_POINT(cc);
2284
2285 if (fuzzer_text && (cc->fuzzer_fd >= 0) &&
2286 (dump_fuzzer_data(cc->fuzzer_fd, fuzzer_text, cc->buffer_start, enc_p - cc->buffer_start) < 0)) {
2288 }
2289
2291}
2292
2294 char *data, UNUSED size_t data_used, char *in, size_t inlen)
2295{
2296 fr_test_point_pair_encode_t *tp = NULL;
2297
2298 void *encode_ctx = NULL;
2299 fr_slen_t parse_len;
2300 ssize_t slen;
2301 char *p = in;
2302
2304 bool truncate = false;
2305
2306 fr_pair_parse_t root, relative;
2307
2309
2310 slen = load_test_point_by_command((void **)&tp, p, "tp_encode_pair");
2311 if (!tp) {
2312 fr_strerror_const_push("Failed locating encode testpoint");
2313 CLEAR_TEST_POINT(cc);
2315 }
2316
2317 p += ((size_t)slen);
2319
2320 /*
2321 * The truncate torture test.
2322 *
2323 * Increase the buffer one byte at a time until all items in the cursor
2324 * have been encoded.
2325 *
2326 * The poisoned region at the end of the buffer will detect overruns
2327 * if we're running with asan.
2328 *
2329 */
2330 if (strncmp(p, "truncate", sizeof("truncate") - 1) == 0) {
2331 truncate = true;
2332 p += sizeof("truncate") - 1;
2334 }
2335
2336 if (tp->test_ctx && (tp->test_ctx(&encode_ctx, cc->tmp_ctx, dictionary_current(cc), NULL) < 0)) {
2337 fr_strerror_const_push("Failed initialising encoder testpoint");
2338 CLEAR_TEST_POINT(cc);
2340 }
2341
2342 root = (fr_pair_parse_t) {
2343 .ctx = cc->tmp_ctx,
2344 .da = cc->tmpl_rules.attr.namespace,
2345 .list = &head,
2346 .dict = cc->tmpl_rules.attr.namespace->dict,
2347 .internal = fr_dict_internal(),
2348 .allow_exec = true
2349 };
2350 relative = (fr_pair_parse_t) { };
2351
2352 parse_len = fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(p, inlen - (p - in)));
2353 if (parse_len <= 0) {
2354 CLEAR_TEST_POINT(cc);
2356 }
2357
2359
2360 return encode_pair(result, cc, data, tp, encode_ctx, &head, truncate, p);
2361}
2362
2363/** Decode hex data into pairs, and then encode the pairs again
2364 *
2365 * The test point for the decoder can be given as "de-encode-pair.<symbol>". The encoder always
2366 * uses the default test point of the protocol, as there is only one place to name a test point.
2367 */
2369 char *data, UNUSED size_t data_used, char *in, size_t inlen)
2370{
2371 fr_test_point_pair_encode_t *tp = NULL;
2372 void *encode_ctx = NULL;
2373 fr_pair_t *head;
2374 char *p;
2375
2376 if (decode_pair(result, cc, data, in, inlen, &head, &p) == 0) return 0;
2377
2378 /*
2379 * "p" points to the hex data, and not to a test point name, so this always finds the
2380 * default encoder for the protocol.
2381 */
2382 (void) load_test_point_by_command((void **)&tp, p, "tp_encode_pair");
2383 if (!tp) {
2384 fr_strerror_const_push("Failed locating encode testpoint");
2385 CLEAR_TEST_POINT(cc);
2387 }
2388
2389 if (tp->test_ctx && (tp->test_ctx(&encode_ctx, cc->tmp_ctx, dictionary_current(cc), NULL) < 0)) {
2390 fr_strerror_const_push("Failed initialising encoder testpoint");
2391 CLEAR_TEST_POINT(cc);
2393 }
2394
2395 /*
2396 * The data which was decoded has already been written to the corpus seed directory, so we
2397 * do not write it again here.
2398 */
2399 return encode_pair(result, cc, data, tp, encode_ctx, &head->vp_group, false, NULL);
2400}
2401
2402/** Encode a RADIUS attribute writing the result to the data buffer as space separated hexits
2403 *
2404 */
2406 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2407{
2408 size_t len;
2409 char buffer[8192];
2410
2411 strlcpy(buffer, in, sizeof(buffer));
2412
2414 if (len <= 0) RETURN_PARSE_ERROR(0);
2415
2416 if (len >= (size_t)(cc->buffer_end - cc->buffer_start)) {
2417 fr_strerror_const("Encoder output would overflow output buffer");
2419 }
2420
2422}
2423
2424/** Parse a list of pairs
2425 *
2426 */
2428 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2429{
2430 ssize_t slen;
2432 bool done = false;
2433 char *filename;
2434 FILE *fp;
2435
2436 filename = talloc_asprintf(cc->tmp_ctx, "%s/%s", cc->path, in);
2437
2438 fp = fopen(filename, "r");
2439 talloc_free(filename);
2440
2441 if (!fp) {
2442 fr_strerror_printf("Failed opening %s - %s", in, fr_syserror(errno));
2444 }
2445
2447 slen = fr_pair_list_afrom_file(cc->tmp_ctx, cc->tmpl_rules.attr.dict_def, &head, fp, &done, true);
2448 fclose(fp);
2449 if (slen < 0) {
2451 }
2452
2453 /*
2454 * Print the pairs.
2455 */
2457 if (slen <= 0) {
2458 fr_assert(0);
2460 }
2461
2462 if (!done) {
2463 strlcpy(data + slen, "!DONE", COMMAND_OUTPUT_MAX - slen);
2464 slen += 5;
2465 }
2466
2468
2469 RETURN_OK(slen);
2470}
2471
2472
2474 char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
2475{
2477}
2478
2480 char *data, UNUSED size_t data_used, char *in, size_t inlen)
2481{
2483
2484 void *encode_ctx = NULL;
2485 fr_slen_t parse_len;
2486 ssize_t slen;
2487 char *p = in;
2488
2490 fr_pair_parse_t root, relative;
2491
2493
2494 slen = load_test_point_by_command((void **)&tp, p, "tp_encode_proto");
2495 if (!tp) {
2496 fr_strerror_const_push("Failed locating encode testpoint");
2497 CLEAR_TEST_POINT(cc);
2499 }
2500
2501 p += ((size_t)slen);
2503 if (tp->test_ctx && (tp->test_ctx(&encode_ctx, cc->tmp_ctx, dictionary_current(cc), NULL) < 0)) {
2504 fr_strerror_const_push("Failed initialising encoder testpoint");
2505 CLEAR_TEST_POINT(cc);
2507 }
2508
2509 root = (fr_pair_parse_t) {
2510 .ctx = cc->tmp_ctx,
2511 .da = cc->tmpl_rules.attr.namespace,
2512 .list = &head,
2513 .dict = cc->tmpl_rules.attr.namespace->dict,
2514 .internal = fr_dict_internal(),
2515 .allow_exec = true
2516 };
2517 relative = (fr_pair_parse_t) { };
2518
2519 parse_len = fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(p, inlen - (p - in)));
2520 if (parse_len <= 0) {
2521 CLEAR_TEST_POINT(cc);
2523 }
2524
2525 slen = tp->func(cc->tmp_ctx, &head, cc->buffer_start, cc->buffer_end - cc->buffer_start, encode_ctx);
2527 cc->last_ret = slen;
2528 if (slen < 0) {
2529 CLEAR_TEST_POINT(cc);
2531 }
2532 /*
2533 * Clear any spurious errors
2534 */
2536
2537 CLEAR_TEST_POINT(cc);
2538
2539 if ((cc->fuzzer_fd >= 0) &&
2540 (dump_fuzzer_data(cc->fuzzer_fd, p, cc->buffer_start, slen) < 0)) {
2542 }
2543
2545}
2546
2547/** Command eof
2548 *
2549 * Mark the end of a test file if we're reading from stdin.
2550 *
2551 * Doesn't actually do anything, is just a placeholder for the command processing loop.
2552 */
2554 UNUSED char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
2555{
2556 return 0;
2557}
2558
2559/** Helper function to open a fuzzer path, and update the name / FD.
2560 *
2561 */
2562static int fuzzer_open_fd(command_file_ctx_t *cc, char **out, char const *base, char const *dir)
2563{
2564 int fd;
2565 struct stat sdir;
2566 char *fuzzer_dir = NULL;
2567 bool retry_dir = true;
2568
2569 /*
2570 * Close any open fuzzer output dirs
2571 */
2572 if (cc->fuzzer_fd >= 0) {
2573 close(cc->fuzzer_fd);
2574 cc->fuzzer_fd = -1;
2575 }
2576
2577 fuzzer_dir = talloc_asprintf(cc, "%s/%s", base, dir);
2578
2579again:
2580 fd = open(fuzzer_dir, O_RDONLY);
2581 if (fd < 0) {
2582 if (mkdir(fuzzer_dir, 0777) == 0) {
2583 fd = open(fuzzer_dir, O_RDONLY);
2584 if (fd >= 0) goto stat;
2585 /*
2586 * Prevent race if multiple unit_test_attribute instances
2587 * attempt to create the same output dir.
2588 */
2589 } else if ((errno == EEXIST) && retry_dir) {
2590 retry_dir = false; /* Only allow this once */
2591 goto again;
2592 }
2593
2594 fr_strerror_printf("fuzzer-out \"%s\" doesn't exist: %s", fuzzer_dir, fr_syserror(errno));
2595 return -1;
2596 }
2597
2598stat:
2599 if (fstat(fd, &sdir) < 0) {
2600 close(fd);
2601 fr_strerror_printf("failed statting fuzzer-out \"%s\": %s", fuzzer_dir, fr_syserror(errno));
2602 return -1;
2603 }
2604
2605 if (!(sdir.st_mode & S_IFDIR)) {
2606 close(fd);
2607 fr_strerror_printf("fuzzer-out \"%s\" is not a directory", fuzzer_dir);
2608 return -1;
2609 }
2610
2611 cc->fuzzer_fd = fd;
2612
2613 talloc_free(*out);
2614 *out = fuzzer_dir;
2615
2616 return 0;
2617}
2618
2619/** Enable fuzzer output
2620 *
2621 * Any commands that produce potentially useful corpus seed data will write that out data
2622 * to files in the specified directory, using the md5 of the text input at as the file name.
2623 *
2624 * The output directory given here is appended to the path given by '-F path'.
2625 *
2626 * If there's no '-F path' command-line option set, then the path of the current file is used as the base
2627 * directory.
2628 */
2630 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2631{
2632 if (in[0] == '\0') {
2633 fr_strerror_const("Missing directory name");
2635 }
2636
2637 if (fuzzer_open_fd(cc, &cc->fuzzer_proto_dir,
2639 in) < 0) {
2641
2642 }
2643
2644 return 0;
2645}
2646
2647/** Exit gracefully with the specified code
2648 *
2649 */
2651 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2652{
2653 if (!*in) RETURN_EXIT(0);
2654
2655 RETURN_EXIT(atoi(in));
2656}
2657
2659 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2660{
2661 char *name, *tmp = NULL;
2662 char const *dir;
2663 char *q;
2664 int ret;
2665
2667
2668 if (in[0] == '\0') {
2669 fr_strerror_const("Missing dictionary name");
2671 }
2672
2673 q = strchr(in, ' ');
2674 if (q) {
2675 name = tmp = talloc_bstrndup(NULL, in, q - in);
2676 q++;
2677 dir = q;
2678 } else {
2679 name = in;
2680 dir = cc->path;
2681 }
2682
2683 /*
2684 * When we're reading multiple files at the same time, they might all have a 'load-dictionary foo'
2685 * command. In which case we don't complain.
2686 */
2688 RETURN_OK(0);
2689 }
2690
2692 talloc_free(tmp);
2693 if (ret < 0) RETURN_COMMAND_ERROR();
2694
2695 RETURN_OK(0);
2696}
2697
2698
2699/** Compare the data buffer to an expected value
2700 *
2701 */
2703 char *data, size_t data_used, char *in, size_t inlen)
2704{
2705 if (strcmp(in, data) != 0) {
2706 if (write_fp) {
2707 strcpy(in, data);
2708 RETURN_OK(data_used);
2709 }
2710
2711 mismatch_print(cc, "match", in, inlen, data, data_used, true);
2712 RETURN_MISMATCH(data_used);
2713 }
2714
2715 /*
2716 * We didn't actually write anything, but this
2717 * keeps the contents of the data buffer around
2718 * for the next command to operate on.
2719 */
2720 RETURN_OK(data_used);
2721}
2722
2723/** Compare the data buffer against an expected expression
2724 *
2725 */
2727 char *data, size_t data_used, char *in, size_t inlen)
2728{
2729 ssize_t slen;
2730 regex_t *regex;
2731 int ret;
2732
2733 slen = regex_compile(cc->tmp_ctx, &regex, in, inlen, NULL, false, true);
2734 if (slen <= 0) RETURN_COMMAND_ERROR();
2735
2736 ret = regex_exec(regex, data, data_used, NULL);
2737 talloc_free(regex);
2738
2739 switch (ret) {
2740 case -1:
2741 default:
2743
2744 case 0:
2745 mismatch_print(cc, "match-regex", in, inlen, data, data_used, false);
2746 RETURN_MISMATCH(data_used);
2747
2748 case 1:
2749 RETURN_OK(data_used);
2750 }
2751}
2752
2753/** Artificially limit the maximum packet size.
2754 *
2755 */
2757 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2758{
2759 unsigned long size;
2760 char *end;
2761
2763
2764 if (*in != '\0') {
2765 size = strtoul(in, &end, 10);
2766 if ((size == ULONG_MAX) || *end || (size >= 65536)) {
2767 fr_strerror_const_push("Invalid integer");
2769 }
2770 } else {
2771 size = DEFAULT_BUFFER_SIZE;
2772 }
2773
2774 if (poisoned_buffer_allocate(cc, &cc->buffer, size) < 0) RETURN_EXIT(1);
2777
2778 RETURN_OK(snprintf(data, COMMAND_OUTPUT_MAX, "%lu", size));
2779}
2780
2781/** Set or clear migration flags.
2782 *
2783 */
2785 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2786{
2787 char *p;
2788 bool *out;
2789
2791 p = in;
2792
2793 if (strncmp(p, "xlat_new_functions", sizeof("xlat_new_functions") - 1) == 0) {
2794 p += sizeof("xlat_new_functions") - 1;
2796
2797 } else {
2798 fr_strerror_const("Unknown migration flag");
2800 }
2801
2803 if (*p != '=') {
2804 fr_strerror_const("Missing '=' after flag");
2806 }
2807 p++;
2808
2810 if ((strcmp(p, "yes") == 0) || (strcmp(p, "true") == 0) || (strcmp(p, "1") == 0)) {
2811 *out = true;
2812
2813 } else if ((strcmp(p, "no") == 0) || (strcmp(p, "false") == 0) || (strcmp(p, "0") == 0)) {
2814 *out = false;
2815
2816 } else {
2817 fr_strerror_const("Invalid value for flag");
2819 }
2820
2821 RETURN_OK(0);
2822}
2823
2824/** Skip the test file if we're missing a particular feature
2825 *
2826 */
2828 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2829{
2830 CONF_PAIR *cp;
2831
2832 if (in[0] == '\0') {
2833 fr_strerror_printf("Prerequisite syntax is \"need-feature <feature>\". "
2834 "Use -f to print features");
2836 }
2837
2838 cp = cf_pair_find(cc->config->features, in);
2839 if (!cp || (strcmp(cf_pair_value(cp), "yes") != 0)) {
2840 DEBUG("Skipping, missing feature \"%s\"", in);
2842 }
2843
2844 RETURN_NOOP(0);
2845}
2846
2847/** Negate the result of a match command or any command which returns "OK"
2848 *
2849 */
2851 char *data, size_t data_used, char *in, size_t inlen)
2852{
2853 data_used = process_line(result, cc, data, data_used, in, inlen);
2854 switch (result->rcode) {
2855 /*
2856 * OK becomes a command error
2857 */
2858 case RESULT_OK:
2859 ERROR("%s[%" PRIu32 "]: %.*s: returned 'ok', where we expected 'result-mismatch'",
2860 cc->filename, cc->lineno, (int) inlen, in);
2861 RETURN_MISMATCH(data_used);
2862
2863 /*
2864 * Mismatch becomes OK
2865 */
2866 case RESULT_MISMATCH:
2867 RETURN_OK(data_used);
2868
2869 /*
2870 * The rest are unchanged...
2871 */
2872 default:
2873 break;
2874 }
2875
2876 return data_used;
2877}
2878
2879/** Parse an print an attribute pair or pair list.
2880 *
2881 */
2883 char *data, UNUSED size_t data_used, char *in, size_t inlen,
2884 bool allow_compare)
2885{
2887 fr_slen_t slen;
2888 ssize_t out_len;
2889 fr_dict_t const *dict = dictionary_current(cc);
2890 fr_pair_parse_t root, relative;
2891
2893
2894 root = (fr_pair_parse_t) {
2895 .ctx = cc->tmp_ctx,
2896 .da = fr_dict_root(dict),
2897 .list = &head,
2898 .dict = dict,
2899 .internal = fr_dict_internal(),
2900 .allow_compare = allow_compare,
2901 .allow_exec = true
2902 };
2903 relative = (fr_pair_parse_t) { };
2904
2905 slen = fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(in, inlen));
2906 if (slen <= 0) {
2907// fr_strerror_printf_push_head("offset %d", (int) -slen);
2910 }
2911
2912 /*
2913 * Output may be an error, and we ignore
2914 * it if so.
2915 */
2916
2918 if (out_len <= 0) {
2921 }
2922
2924 RETURN_OK(out_len);
2925}
2926
2928 char *data, size_t data_used, char *in, size_t inlen)
2929{
2930 return command_pair_common(result, cc, data, data_used, in, inlen, false);
2931}
2932
2934 char *data, size_t data_used, char *in, size_t inlen)
2935{
2936 return command_pair_common(result, cc, data, data_used, in, inlen, true);
2937}
2938
2939
2940/** Dynamically load a protocol library
2941 *
2942 */
2944 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2945{
2946 ssize_t slen;
2947
2948 if (*in == '\0') {
2949 fr_strerror_printf("Load syntax is \"proto <lib_name>\"");
2951 }
2952
2954 slen = load_proto_library(in);
2955 if (slen <= 0) RETURN_PARSE_ERROR(-(slen));
2956
2957 RETURN_OK(0);
2958}
2959
2961 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2962{
2964 return dictionary_load_common(result, cc, in, NULL);
2965}
2966
2968 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2969{
2970 fr_dict_t const *dict = dictionary_current(cc);
2972 fr_dict_attr_t const *new_root;
2973 char *p, buffer[FR_DICT_ATTR_MAX_NAME_LEN + 1];
2974 char *fuzzer_dir = NULL;
2975 char const *q;
2976
2977 if (is_whitespace(in) || (*in == '\0')) {
2978 new_root = fr_dict_root(dict);
2979 } else {
2980 new_root = fr_dict_attr_by_name(NULL, fr_dict_root(dict), in);
2981 if (!new_root) {
2982 fr_strerror_printf("dictionary attribute \"%s\" not found in %s", in, root_da->name);
2984 }
2985 }
2986
2987 cc->tmpl_rules.attr.namespace = new_root;
2988
2989 if (cc->fuzzer_fd < 0) RETURN_OK(0);
2990
2991 for (p = buffer, q = new_root->name; *q != '\0'; p++, q++) {
2992 if (isalnum((uint8_t) *q)) {
2993 *p = *q;
2994 } else {
2995 *p = '_';
2996 }
2997 }
2998 *p = '\0';
2999
3000 if (fuzzer_open_fd(cc, &fuzzer_dir, cc->fuzzer_proto_dir, buffer) < 0) {
3002 }
3003
3004 RETURN_OK(0);
3005}
3006
3007/** Seed the random number generator, so fr_rand() returns a fixed sequence
3008 *
3009 */
3011 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3012{
3013 uint64_t seed = 0;
3014 char *end;
3015
3017
3018 if (*in != '\0') {
3019 errno = 0;
3020 seed = strtoull(in, &end, 10);
3021 if ((*in == '-') || errno || *end) {
3022 fr_strerror_const_push("Invalid integer");
3024 }
3025 }
3026
3027 fr_rand_seed(seed);
3028
3029 RETURN_OK(snprintf(data, COMMAND_OUTPUT_MAX, "%" PRIu64, seed));
3030}
3031
3032/** Parse an reprint a tmpl expansion
3033 *
3034 */
3036 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3037{
3038 fr_slen_t slen;
3039 tmpl_t *vpt;
3040 size_t input_len = strlen(in), escaped_len;
3041
3042 slen = tmpl_afrom_substr(cc->tmp_ctx, &vpt, &FR_SBUFF_IN(in, input_len), T_BARE_WORD,
3044 &(tmpl_rules_t) {
3045 .attr = {
3046 .dict_def = dictionary_current(cc),
3047 .list_def = request_attr_request,
3048 .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved
3049 },
3050 .xlat = cc->tmpl_rules.xlat,
3051 });
3052 if (slen == 0) {
3053 fr_strerror_printf_push_head("failed to parse any input");
3055 }
3056
3057 if (slen < 0) {
3058 fr_strerror_printf_push_head("offset %d", (int) -slen - 1);
3059
3060 return_error:
3062 }
3063
3064 if (((size_t) slen != input_len)) {
3065 fr_strerror_printf_push_head("offset %d 'Too much text'", (int) slen);
3066 goto return_error;
3067 }
3068
3069 escaped_len = tmpl_print(&FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX), vpt, NULL);
3070 RETURN_OK(escaped_len);
3071}
3072
3073/** Touch a file to indicate a test completed
3074 *
3075 */
3077 UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3078{
3079 if (fr_unlink(in) < 0) RETURN_COMMAND_ERROR();
3080 if (fr_touch(NULL, in, 0644, true, 0755) <= 0) RETURN_COMMAND_ERROR();
3081
3082 RETURN_OK(0);
3083}
3084
3085/** Callback for a tmpl rule parser
3086 *
3087 */
3088typedef ssize_t(*command_tmpl_rule_func)(TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value);
3089
3091{
3092 bool res;
3093
3094 if (fr_sbuff_out_bool(&res, value) < 0) return -1;
3095
3096 rules->attr.allow_foreign = res;
3097 return 0;
3098}
3099
3101{
3102 bool res;
3103
3104 if (fr_sbuff_out_bool(&res, value) < 0) return -1;
3105
3106 rules->attr.allow_unknown = res;
3107 return 0;
3108}
3109
3111{
3112 bool res;
3113
3114 if (fr_sbuff_out_bool(&res, value) < 0) return -1;
3115
3116 rules->attr.allow_unresolved = res;
3117 return 0;
3118}
3119
3121{
3123 fr_slen_t slen;
3124
3125 slen = fr_dict_attr_by_oid_substr(&err,
3126 &rules->attr.namespace,
3127 rules->attr.dict_def ? fr_dict_root(rules->attr.dict_def) :
3129 value, NULL);
3131 return slen;
3132}
3133
3135{
3136 fr_slen_t slen;
3137
3139
3140 if (slen == 0) {
3141 fr_strerror_printf("Invalid list specifier \"%pV\"",
3143 }
3144
3145 return slen;
3146}
3147
3149{
3150 fr_slen_t slen;
3151
3152 slen = tmpl_request_ref_list_afrom_substr(ctx, NULL,
3153 &rules->attr.request_def,
3154 value);
3155 if (slen < 0) {
3156 fr_strerror_printf("Invalid request specifier \"%pV\"",
3158 }
3159
3160 return slen;
3161}
3162
3164 UNUSED char *data, UNUSED size_t data_used, char *in, size_t inlen)
3165{
3166 fr_sbuff_t sbuff = FR_SBUFF_IN(in, inlen);
3167 ssize_t slen;
3169 void *res;
3170
3171 static fr_table_ptr_sorted_t tmpl_rule_func_table[] = {
3172 { L("allow_foreign"), (void *)command_tmpl_rule_allow_foreign },
3173 { L("allow_unknown"), (void *)command_tmpl_rule_allow_unknown },
3174 { L("allow_unresolved"), (void *)command_tmpl_rule_allow_unresolved },
3175 { L("attr_parent"), (void *)command_tmpl_rule_attr_parent },
3176 { L("list_def"), (void *)command_tmpl_rule_list_def },
3177 { L("request_def"), (void *)command_tmpl_rule_request_def }
3178 };
3179 static size_t tmpl_rule_func_table_len = NUM_ELEMENTS(tmpl_rule_func_table);
3180
3181 while (fr_sbuff_extend(&sbuff)) {
3182 fr_sbuff_adv_past_whitespace(&sbuff, SIZE_MAX, NULL);
3183
3184 fr_sbuff_out_by_longest_prefix(&slen, &res, tmpl_rule_func_table, &sbuff, NULL);
3185 if (res == NULL) {
3186 fr_strerror_printf("Specified rule \"%pV\" is invalid",
3189 }
3190 func = (command_tmpl_rule_func)res; /* -Wpedantic */
3191
3192 fr_sbuff_adv_past_whitespace(&sbuff, SIZE_MAX, NULL);
3193
3194 if (!fr_sbuff_next_if_char(&sbuff, '=')) {
3195 fr_strerror_printf("Expected '=' after rule identifier, got \"%pV\"",
3198 }
3199
3200 fr_sbuff_adv_past_whitespace(&sbuff, SIZE_MAX, NULL);
3201
3202 if (func(cc->tmp_ctx, &cc->tmpl_rules, &sbuff) < 0) RETURN_COMMAND_ERROR();
3203 }
3204
3205 return fr_sbuff_used(&sbuff);
3206}
3207
3209 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3210{
3211 fr_value_box_t *box = talloc_zero(NULL, fr_value_box_t);
3212 fr_value_box_t *box2;
3213 char const *value;
3214 size_t match_len;
3215 ssize_t slen;
3217
3218 match_len = parse_typed_value(result, cc, box, &value, in, strlen(in));
3219 if (match_len == 0) {
3220 talloc_free(box);
3221 return 0; /* errors have already been updated */
3222 }
3223
3224 type = box->type;
3225
3226 /*
3227 * Don't print dates with enclosing quotation marks.
3228 */
3229 if (type != FR_TYPE_DATE) {
3232 } else {
3234 }
3235 if (slen <= 0) {
3236 talloc_free(box);
3238 }
3239
3240 /*
3241 * Behind the scenes, parse the data
3242 * string. We should get the same value
3243 * box as last time.
3244 */
3245 box2 = talloc_zero(NULL, fr_value_box_t);
3246 if (fr_value_box_from_str(box2, box2, type, box->enumv,
3247 data, slen,
3249 talloc_free(box2);
3250 talloc_free(box);
3252 }
3253
3254 /*
3255 * They MUST be identical
3256 */
3257 if (fr_value_box_cmp(box, box2) != 0) {
3258 fr_strerror_const("value box reparsing failed. Results not identical");
3259 fr_strerror_printf_push("out: %pV (as string %.*s)", box2, (int) slen, data);
3260 fr_strerror_printf_push("in: %pV (from string %s)", box, value);
3261 talloc_free(box2);
3262 talloc_free(box);
3264 }
3265
3266 /*
3267 * Store <type><value str...>
3268 */
3269 if (cc->fuzzer_fd >= 0) {
3270 char fuzzer_buffer[1024];
3271 char *fuzzer_p = fuzzer_buffer, *fuzzer_end = fuzzer_p + sizeof(fuzzer_buffer);
3272
3273 *fuzzer_p++ = (uint8_t)type; /* Fuzzer uses first byte for type */
3274
3275 strlcpy(fuzzer_p, data, slen > fuzzer_end - fuzzer_p ? fuzzer_end - fuzzer_p : slen);
3276
3277 if (dump_fuzzer_data(cc->fuzzer_fd, fuzzer_buffer,
3278 (uint8_t *)fuzzer_buffer, strlen(fuzzer_buffer)) < 0) {
3280 }
3281 }
3282
3283 talloc_free(box2);
3284 talloc_free(box);
3285 RETURN_OK(slen);
3286}
3287
3289 char *data, size_t data_used, char *in, size_t inlen)
3290{
3291 int fd;
3292 char *path;
3293 bool locked = false;
3294
3295 path = talloc_bstrndup(cc->tmp_ctx, in, inlen);
3296
3297 fd = open(path, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
3298 if (fd < 0) {
3299 fr_strerror_printf("Failed opening \"%s\": %s", path, fr_syserror(errno));
3300 error:
3301 talloc_free(path);
3302 if (fd >= 0) {
3303 if (locked) (void)flock(fd, LOCK_UN);
3304 close(fd);
3305 }
3307 }
3308
3309 if (flock(fd, LOCK_EX) < 0) {
3310 fr_strerror_printf("Failed locking \"%s\": %s", path, fr_syserror(errno));
3311 goto error;
3312 }
3313 locked = true;
3314
3315 while (data_used) {
3316 ssize_t ret;
3317 ret = write(fd, data, data_used);
3318 if (ret < 0) {
3319 fr_strerror_printf("Failed writing to \"%s\": %s", path, fr_syserror(errno));
3320 goto error;
3321 }
3322 data_used -= ret;
3323 data += ret;
3324 }
3325 (void)flock(fd, LOCK_UN);
3326 talloc_free(path);
3327 close(fd);
3328
3329 RETURN_OK(data_used);
3330}
3331
3332/** Parse an reprint and xlat expansion
3333 *
3334 */
3336 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3337{
3338 fr_slen_t slen;
3339 xlat_exp_head_t *head = NULL;
3340 size_t input_len = strlen(in), escaped_len;
3341 fr_sbuff_parse_rules_t p_rules = { .escapes = &fr_value_unescape_double };
3342
3343 if (allow_purify) {
3344 fr_strerror_printf_push_head("cannot run 'xlat' when running with command-line argument '-p'");
3346 }
3347
3348 slen = xlat_tokenize(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len), &p_rules,
3349 &(tmpl_rules_t) {
3350 .attr = {
3351 .dict_def = dictionary_current(cc),
3352 .list_def = request_attr_request,
3353 .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved
3354 },
3355 .xlat = cc->tmpl_rules.xlat,
3356 });
3357 if (slen == 0) {
3358 fr_strerror_printf_push_head("failed to parse any input");
3360 }
3361
3362 if (slen < 0) {
3363 fr_strerror_printf_push_head("offset %d", (int) -slen - 1);
3364
3365 return_error:
3367 }
3368
3369 if (((size_t) slen != input_len)) {
3370 fr_strerror_printf_push_head("offset %d 'Too much text'", (int) slen);
3371 goto return_error;
3372 }
3373
3375 RETURN_OK(escaped_len);
3376}
3377
3378/** Parse and reprint an xlat expression expansion
3379 *
3380 */
3382 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3383{
3384 fr_slen_t dec_len;
3385 xlat_exp_head_t *head = NULL;
3386 size_t input_len = strlen(in), escaped_len;
3387// fr_sbuff_parse_rules_t p_rules = { .escapes = &fr_value_unescape_double };
3388
3389 dec_len = xlat_tokenize_expression(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len), NULL,
3390 &(tmpl_rules_t) {
3391 .attr = {
3392 .dict_def = dictionary_current(cc),
3393 .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved,
3394 .list_def = request_attr_request,
3395 }
3396 });
3397 if (dec_len <= 0) {
3398 fr_strerror_printf_push_head("offset %d", (int) -dec_len);
3399
3400 return_error:
3402 }
3403
3404 if (((size_t) dec_len != input_len)) {
3405 fr_strerror_printf_push_head("Passed in %zu characters, but only parsed %zd characters", input_len, dec_len);
3406 goto return_error;
3407 }
3408
3410 RETURN_OK(escaped_len);
3411}
3412
3413/** Parse, purify, and reprint an xlat expression expansion
3414 *
3415 */
3417 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3418{
3419 fr_slen_t slen;
3420 xlat_exp_head_t *head = NULL;
3421 size_t input_len = strlen(in), escaped_len;
3422 tmpl_rules_t t_rules = (tmpl_rules_t) {
3423 .attr = {
3425 .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved,
3426 .list_def = request_attr_request,
3427 },
3428 .xlat = cc->tmpl_rules.xlat,
3429 .at_runtime = true,
3430 };
3431
3432 if (!el) {
3433 fr_strerror_const("Flag '-p' not used. xlat_purify is disabled");
3434 goto return_error;
3435 }
3436 t_rules.xlat.runtime_el = el;
3437
3438 slen = xlat_tokenize_expression(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len), NULL, &t_rules);
3439 if (slen == 0) {
3440 fr_strerror_printf_push_head("failed to parse any input");
3442 }
3443
3444 if (slen < 0) {
3445 fr_strerror_printf_push_head("offset %d", (int) -slen - 1);
3446 return_error:
3448 }
3449
3450 if (((size_t) slen != input_len)) {
3451 fr_strerror_printf_push_head("Passed in %zu characters, but only parsed %zd characters", input_len, slen);
3452 goto return_error;
3453 }
3454
3455 if (fr_debug_lvl > 2) {
3456 DEBUG("Before purify --------------------------------------------------");
3458 }
3459
3460 if (xlat_purify(head, NULL) < 0) {
3461 fr_strerror_printf_push_head("purifying node - %s", fr_strerror());
3462 goto return_error;
3463 }
3464
3465 if (fr_debug_lvl > 2) {
3466 DEBUG("After purify --------------------------------------------------");
3468 }
3469
3471 RETURN_OK(escaped_len);
3472}
3473
3474
3475/** Parse, purify, and reprint an xlat expression expansion
3476 *
3477 */
3479 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3480{
3481 fr_slen_t slen;
3482 xlat_exp_head_t *head = NULL;
3483 size_t input_len = strlen(in), escaped_len;
3484 tmpl_rules_t t_rules = (tmpl_rules_t) {
3485 .attr = {
3487 .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved,
3488 .list_def = request_attr_request,
3489 },
3490 .xlat = cc->tmpl_rules.xlat,
3491 .at_runtime = true,
3492 };
3493
3494 if (!el) {
3495 fr_strerror_const("Flag '-p' not used. xlat_purify is disabled");
3496 goto return_error;
3497 }
3498 t_rules.xlat.runtime_el = el;
3499
3500 slen = xlat_tokenize_condition(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len), NULL, &t_rules);
3501 if (slen == 0) {
3502 fr_strerror_printf_push_head("failed to parse any input");
3504 }
3505
3506 if (slen < 0) {
3507 fr_strerror_printf_push_head("offset %d", (int) -slen - 1);
3508 return_error:
3510 }
3511
3512 if (((size_t) slen != input_len)) {
3513 fr_strerror_printf_push_head("Passed in %zu characters, but only parsed %zd characters", input_len, slen);
3514 goto return_error;
3515 }
3516
3517 if (fr_debug_lvl > 2) {
3518 DEBUG("Before purify --------------------------------------------------");
3520 }
3521
3522 if (xlat_purify(head, NULL) < 0) {
3523 fr_strerror_printf_push_head("purifying node - %s", fr_strerror());
3524 goto return_error;
3525 }
3526
3527 if (fr_debug_lvl > 2) {
3528 DEBUG("After purify --------------------------------------------------");
3530 }
3531
3533 RETURN_OK(escaped_len);
3534}
3535
3536
3537/** Parse an reprint and xlat argv expansion
3538 *
3539 */
3541 char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
3542{
3543 ssize_t i, argc;
3544 char *p;
3545 fr_slen_t slen;
3546 xlat_exp_head_t *head = NULL;
3547 xlat_exp_head_t **argv;
3548 size_t len;
3549 size_t input_len = strlen(in);
3550 char buff[1024];
3551
3552 if (allow_purify) {
3553 fr_strerror_printf_push_head("cannot run 'xlat_argv' when running with command-line argument '-p'");
3555 }
3556
3557 slen = xlat_tokenize_argv(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len),
3558 NULL, NULL,
3559 &(tmpl_rules_t) {
3560 .attr = {
3561 .dict_def = dictionary_current(cc),
3562 .list_def = request_attr_request,
3563 .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved
3564 },
3565 }, true);
3566 if (slen <= 0) {
3567 fr_strerror_printf_push_head("offset %d", (int) -slen);
3569 }
3570
3571 argc = xlat_flatten_to_argv(cc->tmp_ctx, &argv, head);
3572 if (argc <= 0) {
3573 fr_strerror_printf_push("in argument %d", (int) -argc);
3575 }
3576
3577 for (i = 0, p = data; i < argc; i++) {
3578 (void) xlat_print(&FR_SBUFF_OUT(buff, sizeof(buff)), argv[i], NULL);
3579
3580 len = snprintf(p, data + COMMAND_OUTPUT_MAX - p, "[%zd]{ %s }, ", i, buff);
3581 p += len;
3582 }
3583
3584 p -= 2;
3585 *p = '\0';
3586
3587 RETURN_OK(p - data);
3588}
3589
3591 { L("#"), &(command_entry_t){
3592 .func = command_comment,
3593 .usage = "#<string>",
3594 .description = "A comment - not processed"
3595 }},
3596 { L("$INCLUDE "), &(command_entry_t){
3597 .func = command_include,
3598 .usage = "$INCLUDE <relative_path>",
3599 .description = "Execute a test file"
3600 }},
3601 { L("allow-unresolved "), &(command_entry_t){
3603 .usage = "allow-unresolved yes|no",
3604 .description = "Allow or disallow unresolved attributes in xlats and references"
3605 }},
3606 { L("attr.children"), &(command_entry_t){
3607 .func = command_attr_children,
3608 .usage = "attr.children",
3609 .description = "Return the children of the named attribute",
3610 }},
3611 { L("attr.flags"), &(command_entry_t){
3612 .func = command_attr_flags,
3613 .usage = "attr.flags",
3614 .description = "Return the flags of the named attribute",
3615 }},
3616 { L("attr.name"), &(command_entry_t){
3617 .func = command_attr_name,
3618 .usage = "attr.name",
3619 .description = "Return the number of the named attribute",
3620 }},
3621#if 0
3622 { L("attr.number"), &(command_entry_t){
3623 .func = command_attr_number,
3624 .usage = "attr.number",
3625 .description = "Return the number of the named attribute",
3626 }},
3627#endif
3628 { L("attr.oid"), &(command_entry_t){
3629 .func = command_attr_oid,
3630 .usage = "attr.oid",
3631 .description = "Return the OID of the named attribute",
3632 }},
3633#if 0
3634 { L("attr.ref"), &(command_entry_t){
3635 .func = command_attr_ref,
3636 .usage = "attr.ref",
3637 .description = "Return the reference (if any) of the named attribute",
3638 }},
3639#endif
3640 { L("attr.type"), &(command_entry_t){
3641 .func = command_attr_type,
3642 .usage = "attr.type",
3643 .description = "Return the data type of the named attribute",
3644 }},
3645 { L("calc "), &(command_entry_t){
3646 .func = command_calc,
3647 .usage = "calc <type1> <value1> <operator> <type2> <value2> -> <output-type>",
3648 .description = "Perform calculations on value boxes",
3649 }},
3650 { L("calc_nary "), &(command_entry_t){
3651 .func = command_calc_nary,
3652 .usage = "calc_nary op <type1> <value1> <type2> <value2> ... -> <output-type>",
3653 .description = "Perform calculations on value boxes",
3654 }},
3655 { L("cast "), &(command_entry_t){
3656 .func = command_cast,
3657 .usage = "cast (type) <value> -> <output-type>",
3658 .description = "Perform calculations on value boxes",
3659 }},
3660 { L("cd "), &(command_entry_t){
3661 .func = command_cd,
3662 .usage = "cd <path>",
3663 .description = "Change the directory for loading dictionaries and $INCLUDEs, writing the full path into the data buffer on success"
3664 }},
3665 { L("clear"), &(command_entry_t){
3666 .func = command_clear,
3667 .usage = "clear",
3668 .description = "Explicitly zero out the contents of the data buffer"
3669 }},
3670 { L("command add "), &(command_entry_t){
3671 .func = command_radmin_add,
3672 .usage = "command add <string>",
3673 .description = "Add a command to a radmin command tree"
3674 }},
3675 { L("command tab "), &(command_entry_t){
3676 .func = command_radmin_tab,
3677 .usage = "command tab <string>",
3678 .description = "Test a tab completion against a radmin command tree"
3679 }},
3680 { L("condition "), &(command_entry_t){
3682 .usage = "condition <string>",
3683 .description = "Parse and reprint a condition, writing the normalised condition to the data buffer on success"
3684 }},
3685 { L("count"), &(command_entry_t){
3686 .func = command_count,
3687 .usage = "count",
3688 .description = "Write the number of executed tests to the data buffer. A test is any command that should return 'ok'"
3689 }},
3690 { L("de-encode-pair"), &(command_entry_t){
3691 .func = command_de_encode_pair,
3692 .usage = "de-encode-pair[.<testpoint_symbol>] (-|<hex_string>)",
3693 .description = "Decode a binary value into attribute value pairs, encode the pairs again, and write the result to the data buffer as hex. Protocol must be loaded with \"load <protocol>\" first",
3694 }},
3695 { L("decode-dns-label "), &(command_entry_t){
3697 .usage = "decode-dns-label (-|<hex_string>)",
3698 .description = "Decode one or more DNS labels, writing the decoded strings to the data buffer.",
3699 }},
3700 { L("decode-pair"), &(command_entry_t){
3701 .func = command_decode_pair,
3702 .usage = "decode-pair[.<testpoint_symbol>] (-|<hex_string>)",
3703 .description = "Produce an attribute value pair from a binary value using a specified protocol decoder. Protocol must be loaded with \"load <protocol>\" first",
3704 }},
3705 { L("decode-proto"), &(command_entry_t){
3706 .func = command_decode_proto,
3707 .usage = "decode-proto[.<testpoint_symbol>] (-|<hex string>)",
3708 .description = "Decode a packet as attribute value pairs from a binary value using a specified protocol decoder. Protocol must be loaded with \"load <protocol>\" first",
3709 }},
3710 { L("dictionary "), &(command_entry_t){
3712 .usage = "dictionary <string>",
3713 .description = "Parse dictionary attribute definition, writing \"ok\" to the data buffer if successful",
3714 }},
3715 { L("dictionary-dump"), &(command_entry_t){
3717 .usage = "dictionary-dump",
3718 .description = "Print the contents of the currently active dictionary to stdout",
3719 }},
3720 { L("dictionary-read "), &(command_entry_t){
3722 .usage = "dictionary-read <filename>",
3723 .description = "Load the named dictionary file, writing \"ok\" to the data buffer if successful",
3724 }},
3725 { L("encode-dns-label "), &(command_entry_t){
3727 .usage = "encode-dns-label (-|string[,string])",
3728 .description = "Encode one or more DNS labels, writing a hex string to the data buffer.",
3729 }},
3730 { L("encode-pair"), &(command_entry_t){
3731 .func = command_encode_pair,
3732 .usage = "encode-pair[.<testpoint_symbol>] [truncate] (-|<attribute> = <value>[,<attribute = <value>])",
3733 .description = "Encode one or more attribute value pairs, writing a hex string to the data buffer. Protocol must be loaded with \"load <protocol>\" first",
3734 }},
3735 { L("encode-proto"), &(command_entry_t){
3736 .func = command_encode_proto,
3737 .usage = "encode-proto[.<testpoint_symbol>] (-|<attribute> = <value>[,<attribute = <value>])",
3738 .description = "Encode one or more attributes as a packet, writing a hex string to the data buffer. Protocol must be loaded with \"proto <protocol>\" first"
3739 }},
3740 { L("eof"), &(command_entry_t){
3741 .func = command_eof,
3742 .usage = "eof",
3743 .description = "Mark the end of a 'virtual' file. Used to prevent 'need-feature' skipping all the content of a command stream or file",
3744 }},
3745 { L("exit"), &(command_entry_t){
3746 .func = command_exit,
3747 .usage = "exit[ <num>]",
3748 .description = "Exit with the specified error number. If no <num> is provided, process will exit with 0"
3749 }},
3750 { L("fuzzer-out"), &(command_entry_t){
3751 .func = command_fuzzer_out,
3752 .usage = "fuzzer-out <dir>",
3753 .description = "Write encode-pair, encode-proto, and encode-dns-label output, and value input as separate files in the specified directory. Text input will be sha1 hashed and base64 encoded to create the filename",
3754 }},
3755 { L("load-dictionary "),&(command_entry_t){
3757 .usage = "load-dictionary <name> [<dir>]",
3758 .description = "Load an additional dictionary from the same directory as the input file. "
3759 "Optionally you can specify a full path via <dir>. ",
3760 }},
3761 { L("match"), &(command_entry_t){
3762 .func = command_match,
3763 .usage = "match <string>",
3764 .description = "Compare the contents of the data buffer with an expected value"
3765 }},
3766 { L("match-regex "), &(command_entry_t){
3767 .func = command_match_regex,
3768 .usage = "match-regex <regex>",
3769 .description = "Compare the contents of the data buffer with a regular expression"
3770 }},
3771 { L("max-buffer-size"), &(command_entry_t){
3773 .usage = "max-buffer-size[ <integer>]",
3774 .description = "Limit the maximum temporary buffer space available for any command which uses it"
3775 }},
3776 { L("migrate "), &(command_entry_t){
3777 .func = command_migrate,
3778 .usage = "migrate <flag>=<value>",
3779 .description = "Set migration flag"
3780 }},
3781 { L("need-feature "), &(command_entry_t){
3782 .func = command_need_feature,
3783 .usage = "need-feature <feature>",
3784 .description = "Skip the contents of the current file, or up to the next \"eof\" command if a particular feature is not available"
3785 }},
3786 { L("no "), &(command_entry_t){
3787 .func = command_no,
3788 .usage = "no ...",
3789 .description = "Negate the result of a command returning 'ok'"
3790 }},
3791 { L("pair "), &(command_entry_t){
3792 .func = command_pair,
3793 .usage = "pair ... data ...",
3794 .description = "Parse a list of pairs",
3795 }},
3796 { L("pair-compare "), &(command_entry_t){
3797 .func = command_pair_compare,
3798 .usage = "pair-compare ... data ...",
3799 .description = "Parse a list of pairs, allowing comparison operators",
3800 }},
3801 { L("proto "), &(command_entry_t){
3802 .func = command_proto,
3803 .usage = "proto <protocol>",
3804 .description = "Switch the active protocol to the one specified, unloading the previous protocol",
3805 }},
3806 { L("proto-dictionary "),&(command_entry_t){
3808 .usage = "proto-dictionary <proto_name> [<proto_dir>]",
3809 .description = "Switch the active dictionary. Root is set to the default dictionary path, or the one specified with -d. <proto_dir> is relative to the root.",
3810 }},
3811
3812
3813 { L("proto-dictionary-root "), &(command_entry_t){
3815 .usage = "proto-dictionary-root[ <root_attribute>]",
3816 .description = "Set the root attribute for the current protocol dictionary. "
3817 "If no attribute name is provided, the root will be reset to the root of the current dictionary",
3818 }},
3819 { L("rand-seed"), &(command_entry_t){
3820 .func = command_rand_seed,
3821 .usage = "rand-seed[ <integer>]",
3822 .description = "Seed the random number generator, so random values such as "
3823 "identifiers and salts are the same on every run. The seed defaults to 0"
3824 }},
3825 { L("raw "), &(command_entry_t){
3826 .func = command_encode_raw,
3827 .usage = "raw <string>",
3828 .description = "Create nested attributes from OID strings and values"
3829 }},
3830 { L("read_file "), &(command_entry_t){
3831 .func = command_read_file,
3832 .usage = "read_file <filename>",
3833 .description = "Read a list of pairs from a file",
3834 }},
3835 { L("returned"), &(command_entry_t){
3836 .func = command_returned,
3837 .usage = "returned",
3838 .description = "Print the returned value to the data buffer"
3839 }},
3840
3841 { L("tmpl "), &(command_entry_t){
3842 .func = command_tmpl,
3843 .usage = "parse <string>",
3844 .description = "Parse then print a tmpl expansion, writing the normalised tmpl expansion to the data buffer"
3845 }},
3846
3847 { L("tmpl-rules "), &(command_entry_t){
3848 .func = command_tmpl_rules,
3849 .usage = "tmpl-rule [allow_foreign=yes] [allow_unknown=yes|no] [allow_unresolved=yes|no] [attr_parent=<oid>] [list_def=request|reply|control|session-state] [request_def=current|outer|parent]",
3850 .description = "Alter the tmpl parsing rules for subsequent tmpl parsing commands in the same command context"
3851 }},
3852 { L("touch "), &(command_entry_t){
3853 .func = command_touch,
3854 .usage = "touch <file>",
3855 .description = "Touch a file, updating its created timestamp. Useful for marking the completion of a series of tests"
3856 }},
3857 { L("value "), &(command_entry_t){
3859 .usage = "value <type> <string>",
3860 .description = "Parse a value of a given type from its presentation form, print it, then parse it again (checking printed/parsed versions match), writing printed form to the data buffer"
3861 }},
3862 { L("write "), &(command_entry_t){
3863 .func = command_write,
3864 .usage = "write <file>",
3865 .description = "Write the contents of the data buffer (as a raw binary string) to the specified file"
3866 }},
3867 { L("xlat "), &(command_entry_t){
3868 .func = command_xlat_normalise,
3869 .usage = "xlat <string>",
3870 .description = "Parse then print an xlat expansion, writing the normalised xlat expansion to the data buffer"
3871 }},
3872
3873 { L("xlat_argv "), &(command_entry_t){
3874 .func = command_xlat_argv,
3875 .usage = "xlat_argv <string>",
3876 .description = "Parse then print an xlat expansion argv, writing the normalised xlat expansion arguments to the data buffer"
3877 }},
3878
3879 { L("xlat_expr "), &(command_entry_t){
3880 .func = command_xlat_expr,
3881 .usage = "xlat_expr <string>",
3882 .description = "Parse then print an xlat expression, writing the normalised xlat expansion to the data buffer"
3883 }},
3884
3885 { L("xlat_purify "), &(command_entry_t){
3886 .func = command_xlat_purify,
3887 .usage = "xlat_purify <string>",
3888 .description = "Parse, purify, then print an xlat expression, writing the normalised xlat expansion to the data buffer"
3889 }},
3890
3891 { L("xlat_purify_cond "), &(command_entry_t){
3893 .usage = "xlat_purify_cond <string>",
3894 .description = "Parse, purify, then print an xlat condition, writing the normalised xlat expansion to the data buffer"
3895 }},
3896
3897};
3899
3900size_t process_line(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used,
3901 char *in, UNUSED size_t inlen)
3902{
3903
3904 command_entry_t *command;
3905 size_t match_len;
3906 char *p;
3907 char const *error;
3908
3909 p = in;
3911
3912 /*
3913 * Skip empty lines and comments.
3914 */
3915 if (!*p || (*p == '#')) {
3916 /*
3917 * Dump the input to the output.
3918 */
3919 if (write_fp) {
3920 fputs(in, write_fp);
3921 fputs("\n", write_fp);
3922 }
3923
3924 RETURN_NOOP(data_used);
3925 }
3926
3927 DEBUG2("%s[%" PRIu32 "]: %s", cc->filename, cc->lineno, p);
3928
3929 /*
3930 * Look up the command by longest prefix
3931 */
3932 command = fr_table_value_by_longest_prefix(&match_len, commands, p, -1, NULL);
3933 if (!command) {
3934 fr_strerror_printf("Unknown command: %s", p);
3936 }
3937
3938 p += match_len; /* Jump to after the command */
3939 fr_skip_whitespace(p); /* Skip any whitespace */
3940
3941 /*
3942 * Feed the data buffer in as the command
3943 */
3944 if ((p[0] == '-') && ((p[1] == ' ') || (p[1] == '\0'))) {
3945 data_used = command->func(result, cc, data, data_used, data, data_used);
3946 }
3947 else {
3948 data_used = command->func(result, cc, data, data_used, p, strlen(p));
3949 }
3950
3951 /*
3952 * Dump the contents of the error stack
3953 * to the data buffer.
3954 *
3955 * This is then what's checked in
3956 * subsequent match commands.
3957 */
3958 if (result->error_to_data) {
3959 error = fr_strerror_concat("ERROR: ", ": ");
3960
3961 data_used = strlcpy(data, error, COMMAND_OUTPUT_MAX);
3962 if (data_used >= (COMMAND_OUTPUT_MAX - 1)) {
3963 data_used = COMMAND_OUTPUT_MAX - 1;
3964 data[data_used] = '\0';
3965 }
3966 }
3967
3968 if (data_used) {
3969 DEBUG2("%s[%" PRIu32 "]: --> %s (%zu bytes in buffer)", cc->filename, cc->lineno,
3970 fr_table_str_by_value(command_rcode_table, result->rcode, "<INVALID>"), data_used);
3971 } else {
3972 DEBUG2("%s[%" PRIu32 "]: --> %s", cc->filename, cc->lineno,
3973 fr_table_str_by_value(command_rcode_table, result->rcode, "<INVALID>"));
3974 }
3975
3976 /*
3977 * Dump the input to the output.
3978 */
3979 if (write_fp) {
3980 fputs(in, write_fp);
3981 fputs("\n", write_fp);
3982 }
3983
3984 talloc_free_children(cc->tmp_ctx);
3985
3986 return data_used;
3987}
3988
3990{
3991 if (fr_dict_free(&cc->test_internal_dict, __FILE__) < 0) {
3992 fr_perror("unit_test_attribute");
3993 return -1;
3994 }
3995 if (fr_dict_global_ctx_free(cc->test_gctx) < 0) {
3996 fr_perror("unit_test_attribute");
3997 return -1;
3998 }
3999 if (cc->fuzzer_fd >= 0) {
4000 close(cc->fuzzer_fd);
4001 cc->fuzzer_fd = -1;
4002 }
4003 return 0;
4004}
4005
4007 command_config_t const *config, char const *path, char const *filename)
4008{
4010
4011 cc = talloc_zero(ctx, command_file_ctx_t);
4012 talloc_set_destructor(cc, _command_ctx_free);
4013
4014 cc->tmp_ctx = talloc_named_const(ctx, 0, "tmp_ctx");
4015 cc->path = talloc_strdup(cc, path);
4016 cc->filename = filename;
4017 cc->config = config;
4018
4019 /*
4020 * Allocate a special buffer with poisoned regions
4021 * at either end.
4022 */
4024 talloc_free(cc);
4025 return NULL;
4026 }
4029
4030 /*
4031 * Initialise a special temporary dictionary context
4032 *
4033 * Any protocol dictionaries loaded by "test-dictionary"
4034 * go in this context, and don't affect the main
4035 * dictionary context.
4036 */
4037 cc->test_gctx = fr_dict_global_ctx_init(cc, false, cc->config->dict_dir);
4038 if (!cc->test_gctx) {
4039 fr_perror("Failed allocating test dict_gctx");
4040 return NULL;
4041 }
4042
4045 fr_perror("Failed loading test dict_gctx internal dictionary");
4046 return NULL;
4047 }
4048
4049 fr_dict_global_ctx_dir_set(cc->path); /* Load new dictionaries relative to the test file */
4051
4052 cc->fuzzer_fd = -1;
4053
4055 cc->tmpl_rules.attr.namespace = fr_dict_root(cc->config->dict);
4056 cc->tmpl_rules.attr.allow_unresolved = false; /* tests have to use real attributes */
4057
4058 return cc;
4059}
4060
4061static void command_ctx_reset(command_file_ctx_t *cc, TALLOC_CTX *ctx)
4062{
4063 talloc_free(cc->tmp_ctx);
4064 cc->tmp_ctx = talloc_named_const(ctx, 0, "tmp_ctx");
4065 cc->test_count = 0;
4066
4067 if (fr_dict_free(&cc->test_internal_dict, __FILE__) < 0) {
4068 fr_perror("unit_test_attribute");
4069 }
4070
4071 if (fr_dict_global_ctx_free(cc->test_gctx) < 0) fr_perror("unit_test_attribute");
4072
4073 cc->test_gctx = fr_dict_global_ctx_init(cc, false, cc->config->dict_dir);
4075 fr_perror("Failed loading test dict_gctx internal dictionary");
4076 }
4077
4078 if (cc->fuzzer_fd >= 0) {
4079 close(cc->fuzzer_fd);
4080 cc->fuzzer_fd = -1;
4081 }
4082}
4083
4084static int process_file(bool *exit_now, TALLOC_CTX *ctx, command_config_t const *config,
4085 const char *root_dir, char const *filename, fr_dlist_head_t *lines)
4086{
4087 int ret = 0;
4088 FILE *fp; /* File we're reading from */
4089 char buffer[8192]; /* Command buffer */
4090 char data[COMMAND_OUTPUT_MAX + 1]; /* Data written by previous command */
4091 ssize_t data_used = 0; /* How much data the last command wrote */
4092 static char path[PATH_MAX] = "";
4093 command_line_range_t *lr = NULL;
4094 bool opened_fp = false;
4095
4097
4098 cc = command_ctx_alloc(ctx, config, root_dir, filename);
4099
4100 /*
4101 * Open the file, or stdin
4102 */
4103 if (strcmp(filename, "-") == 0) {
4104 fp = stdin;
4105 filename = "<stdin>";
4106 fr_assert(!root_dir);
4107
4108 } else {
4109 if (root_dir && *root_dir) {
4110 snprintf(path, sizeof(path), "%s/%s", root_dir, filename);
4111 } else {
4112 strlcpy(path, filename, sizeof(path));
4113 }
4114
4115 fp = fopen(path, "r");
4116 if (!fp) {
4117 ERROR("Error opening test file \"%s\": %s", path, fr_syserror(errno));
4118 ret = -1;
4119 goto finish;
4120 }
4121
4122 filename = path;
4123 opened_fp = true;
4124 }
4125
4126 if (lines && !fr_dlist_empty(lines)) lr = fr_dlist_head(lines);
4127
4128 /*
4129 * Loop over lines in the file or stdin
4130 */
4131 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
4132 command_result_t result = { .rcode = RESULT_OK }; /* Reset to OK */
4133 char *p = strchr(buffer, '\n');
4134
4136 cc->lineno++; /* The first line of the file becomes line 1 */
4137
4138 if (lr) {
4139 if (cc->lineno > lr->end) {
4140 lr = fr_dlist_next(lines, lr);
4141 if (!lr) goto finish;
4142 }
4143
4144 if (cc->lineno < lr->start) continue;
4145 }
4146
4147 if (!p) {
4148 if (!feof(fp)) {
4149 ERROR("Line %" PRIu32 " too long in %s/%s", cc->lineno, cc->path, cc->filename);
4150 ret = -1;
4151 goto finish;
4152 }
4153 } else {
4154 *p = '\0';
4155 }
4156
4157 data_used = process_line(&result, cc, data, data_used, buffer, strlen(buffer));
4158 switch (result.rcode) {
4159 /*
4160 * Command completed successfully
4161 */
4162 case RESULT_OK:
4163 cc->test_count++;
4164 continue;
4165
4166 /*
4167 * Did nothing (not a test)
4168 */
4169 case RESULT_NOOP:
4170 continue;
4171
4172 /*
4173 * If this is a file, then break out of the loop
4174 * and cleanup, otherwise we need to find the
4175 * EOF marker in the input stream.
4176 */
4177 case RESULT_SKIP_FILE:
4178 if (fp != stdin) goto finish;
4179
4180 /*
4181 * Skip over the input stream until we
4182 * find an eof command, or the stream
4183 * is closed.
4184 */
4185 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
4186 command_entry_t *command;
4187 size_t match_len;
4188
4189 command = fr_table_value_by_longest_prefix(&match_len, commands, buffer, -1, NULL);
4190 if (!command) {
4191 ERROR("%s[%" PRIu32 "]: Unknown command: %s", cc->path, cc->lineno, p);
4192 ret = -1;
4193 goto finish;
4194 }
4195
4196 if (command->func == command_eof) {
4197 command_ctx_reset(cc, ctx);
4198 break;
4199 }
4200 }
4201 goto finish;
4202
4203 /*
4204 * Fatal error parsing a command
4205 */
4206 case RESULT_PARSE_ERROR:
4208 fr_perror("%s[%" PRIu32 "]", filename, cc->lineno);
4209 ret = -1;
4210 goto finish;
4211
4212 /*
4213 * Result didn't match what we expected
4214 */
4215 case RESULT_MISMATCH:
4216 {
4217 ret = EXIT_FAILURE;
4218 goto finish;
4219 }
4220
4221 case RESULT_EXIT:
4222 ret = result.ret;
4223 *exit_now = true;
4224 goto finish;
4225
4226 default:
4227 /*
4228 * If this happens, fix the damn command.
4229 */
4230 fr_assert_msg(false, "Command exited with invalid return code (%u)", result.rcode);
4231 ret = -1;
4232 goto finish;
4233 }
4234 }
4235
4236finish:
4237 /* The explicit check is to quiet clang_analyzer */
4238 if (opened_fp) fclose(fp);
4239
4240 /*
4241 * Free any residual resources we loaded.
4242 */
4243 if (cc && (fr_dict_const_free(&cc->tmpl_rules.attr.dict_def, __FILE__) < 0)) {
4244 fr_perror("unit_test_attribute");
4245 ret = -1;
4246 }
4247
4248 if ((ret == 0) && !cc->test_count) {
4249 ERROR("Empty input file is invalid");
4250 ret = -1;
4251 }
4252
4253 fr_dict_global_ctx_set(config->dict_gctx); /* Switch back to the main dict ctx */
4255 talloc_free(cc);
4256
4257 return ret;
4258}
4259
4260static void usage(char const *name)
4261{
4262 INFO("usage: %s [options] (-|<filename>[:<lines>] [ <filename>[:<lines>]])", name);
4263 INFO("options:");
4264 INFO(" -d <confdir> Set user dictionary path (defaults to " CONFDIR ").");
4265 INFO(" -D <dictdir> Set main dictionary path (defaults to " DICTDIR ").");
4266 INFO(" -x Debugging mode.");
4267 INFO(" -f Print features.");
4268 INFO(" -c Print commands.");
4269 INFO(" -h Print help text.");
4270 INFO(" -M Show talloc memory report.");
4271 INFO(" -p Allow xlat_purify");
4272 INFO(" -o <receipt_file> Create the <receipt_file> as a 'success' exit.");
4273 INFO(" -m <message> On failure, print <message> followed by the failed file.");
4274 INFO(" -w <output_file> Write 'corrected' output to <output_file>.");
4275 INFO("Where <filename> is a file containing one or more commands and '-' indicates commands should be read from stdin.");
4276 INFO("Ranges of <lines> may be specified in the format <start>[-[<end>]][,]");
4277}
4278
4279static void features_print(CONF_SECTION *features)
4280{
4281 CONF_PAIR *cp;
4282
4283 INFO("features:");
4284 for (cp = cf_pair_find(features, CF_IDENT_ANY);
4285 cp;
4286 cp = cf_pair_find_next(features, cp, CF_IDENT_ANY)) {
4287 INFO(" %s %s", cf_pair_attr(cp), cf_pair_value(cp));
4288 }
4289}
4290
4291static void commands_print(void)
4292{
4293 size_t i;
4294
4295 INFO("commands:");
4296 for (i = 0; i < commands_len; i++) {
4297 INFO(" %s:", ((command_entry_t const *)commands[i].value)->usage);
4298 INFO(" %s.", ((command_entry_t const *)commands[i].value)->description);
4299 INFO("%s", "");
4300 }
4301}
4302
4303static int line_ranges_parse(TALLOC_CTX *ctx, fr_dlist_head_t *out, fr_sbuff_t *in)
4304{
4305 static bool tokens[SBUFF_CHAR_CLASS] = { [','] = true , ['-'] = true };
4306 uint32_t max = 0;
4308
4309 while (fr_sbuff_extend(in)) {
4310 fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
4311
4312 MEM(lr = talloc_zero(ctx, command_line_range_t));
4314
4315 if (fr_sbuff_out(&lr->start, in) < 0) {
4316 ERROR("Invalid line start number");
4317 error:
4319 return -1;
4320 }
4321 if (max > lr->start) {
4322 ERROR("Out of order line numbers (%u > %u) not allowed", max, lr->start);
4323 goto error;
4324 } else {
4325 max = lr->start;
4326 }
4327 lr->end = lr->start; /* Default to a single line */
4328 fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
4329
4330 again:
4331 if (!fr_sbuff_extend(in)) break;
4332 if (!fr_sbuff_is_in_charset(in, tokens)) {
4333 ERROR("Unexpected text \"%pV\"",
4335 goto error;
4336 }
4337
4338 fr_sbuff_switch(in, '\0') {
4339 /*
4340 * More ranges...
4341 */
4342 case ',':
4343 fr_sbuff_next(in);
4344 fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
4345 continue;
4346
4347 /*
4348 * <start>-<end>
4349 */
4350 case '-':
4351 {
4352 fr_sbuff_next(in);
4353 fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
4354
4355 /*
4356 * A bare '-' with no number means
4357 * run all remaining lines.
4358 */
4359 if (fr_sbuff_extend(in) == 0) {
4360 lr->end = UINT32_MAX;
4361 return 0;
4362 }
4363
4364 if (fr_sbuff_out(&lr->end, in) < 0) {
4365 ERROR("Invalid line end number");
4366 goto error;
4367 }
4368 if (lr->end < lr->start) {
4369 ERROR("Line end must be >= line start (%u < %u)", lr->end, lr->start);
4370 goto error;
4371 }
4372 if (max > lr->end) {
4373 ERROR("Out of order line numbers (%u > %u) not allowed", max, lr->end);
4374 goto error;
4375 } else {
4376 max = lr->end;
4377 }
4378 fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
4379 }
4380 goto again;
4381 }
4382 }
4383
4384 return 0;
4385}
4386
4387static int process_path(bool *exit_now, TALLOC_CTX *ctx, command_config_t const *config, const char *path)
4388{
4389 char *p, *dir = NULL, *file;
4390 int ret = EXIT_SUCCESS;
4391 fr_sbuff_t in = FR_SBUFF_IN(path, strlen(path));
4393 L("/"),
4394 L(":")
4395 );
4396 fr_sbuff_marker_t file_start, file_end, dir_end;
4397 fr_dlist_head_t lines;
4398
4399 fr_sbuff_marker(&file_start, &in);
4400 fr_sbuff_marker(&file_end, &in);
4401 fr_sbuff_marker(&dir_end, &in);
4402 fr_sbuff_set(&file_end, fr_sbuff_end(&in));
4403
4404 fr_dlist_init(&lines, command_line_range_t, entry);
4405
4406 while (fr_sbuff_extend(&in)) {
4407 fr_sbuff_adv_until(&in, SIZE_MAX, &dir_sep, '\0');
4408
4409 fr_sbuff_switch(&in, '\0') {
4410 case '/':
4411 fr_sbuff_set(&dir_end, &in);
4412 fr_sbuff_advance(&in, 1);
4413 fr_sbuff_set(&file_start, &in);
4414 break;
4415
4416 case ':':
4417 fr_sbuff_set(&file_end, &in);
4418 fr_sbuff_advance(&in, 1);
4419 if (line_ranges_parse(ctx, &lines, &in) < 0) {
4420 return EXIT_FAILURE;
4421 }
4422 break;
4423
4424 default:
4425 fr_sbuff_set(&file_end, &in);
4426 break;
4427 }
4428 }
4429
4430 file = talloc_bstrndup(ctx,
4431 fr_sbuff_current(&file_start), fr_sbuff_diff(&file_end, &file_start));
4432 if (fr_sbuff_used(&dir_end)) dir = talloc_bstrndup(ctx,
4434 fr_sbuff_used(&dir_end));
4435
4436 /*
4437 * Do things so that GNU Make does less work.
4438 */
4439 if ((receipt_dir || receipt_file) &&
4440 (strncmp(path, "src/tests/unit/", 15) == 0)) {
4441 p = UNCONST(char *, strchr(path + 15, '/'));
4442 if (!p) {
4443 printf("UNIT-TEST %s\n", path + 15);
4444 } else {
4445 char *q = UNCONST(char *, strchr(p + 1, '/'));
4446
4447 *p = '\0';
4448
4449 if (!q) {
4450 printf("UNIT-TEST %s - %s\n", path + 15, p + 1);
4451 } else {
4452 *q = '\0';
4453
4454 printf("UNIT-TEST %s - %s\n", p + 1, q + 1);
4455 *q = '/';
4456 }
4457
4458 *p = '/';
4459 }
4460 }
4461
4462 /*
4463 * Rewrite this file if requested.
4464 */
4465 if (write_filename) {
4466 write_fp = fopen(write_filename, "w");
4467 if (!write_fp) {
4468 ERROR("Failed opening %s: %s", write_filename, strerror(errno));
4469 return EXIT_FAILURE;
4470 }
4471 }
4472
4473 ret = process_file(exit_now, ctx, config, dir, file, &lines);
4474
4475 if ((ret == EXIT_SUCCESS) && receipt_dir && dir) {
4476 char *touch_file, *subdir;
4477
4478 if (strncmp(dir, "src/", 4) == 0) {
4479 subdir = dir + 4;
4480 } else {
4481 subdir = dir;
4482 }
4483
4484 touch_file = talloc_asprintf(ctx, "build/%s/%s", subdir, file);
4485 fr_assert(touch_file);
4486
4487 p = strchr(touch_file, '/');
4488 fr_assert(p);
4489
4490 if (fr_mkdir(NULL, touch_file, (size_t) (p - touch_file), S_IRWXU, NULL, NULL) < 0) {
4491 fr_perror("unit_test_attribute - failed to make directory %.*s - ",
4492 (int) (p - touch_file), touch_file);
4493fail:
4494 if (write_fp) fclose(write_fp);
4495 return EXIT_FAILURE;
4496 }
4497
4498 if (fr_touch(NULL, touch_file, 0644, true, 0755) <= 0) {
4499 fr_perror("unit_test_attribute - failed to create receipt file %s - ",
4500 touch_file);
4501 goto fail;
4502 }
4503
4504 talloc_free(touch_file);
4505 }
4506
4507 talloc_free(dir);
4509 fr_dlist_talloc_free(&lines);
4510
4511 if (ret != EXIT_SUCCESS) {
4512 if (write_fp) {
4513 fclose(write_fp);
4514 write_fp = NULL;
4515 }
4516 fail_file = path;
4517 }
4518
4519 if (write_fp) {
4520 fclose(write_fp);
4521 if (rename(write_filename, path) < 0) {
4522 ERROR("Failed renaming %s: %s", write_filename, strerror(errno));
4523 return EXIT_FAILURE;
4524 }
4525 }
4526
4527 return ret;
4528}
4529
4530/** Print a string as a single shell word
4531 *
4532 * The failure message is usually a command for a shell, and a file name
4533 * containing spaces, quotes, or glob characters must reach the shell as one
4534 * word. Because a single quote cannot appear between single quotes, each
4535 * single quote in str becomes '\'', which closes the quoted section, adds an
4536 * escaped quote, and opens a new quoted section.
4537 */
4538static void shell_word_print(FILE *fp, char const *str)
4539{
4540 char const *p;
4541
4542 fputc('\'', fp);
4543 for (p = str; *p != '\0'; p++) {
4544 if (*p == '\'') {
4545 fputs("'\\''", fp);
4546 continue;
4547 }
4548 fputc(*p, fp);
4549 }
4550 fputc('\'', fp);
4551}
4552
4553/**
4554 *
4555 * @hidecallgraph
4556 */
4557int main(int argc, char *argv[])
4558{
4559 int c;
4560 CONF_SECTION *cs;
4561 int ret = EXIT_SUCCESS;
4562 TALLOC_CTX *autofree;
4563 TALLOC_CTX *thread_ctx;
4564 bool exit_now = false;
4565
4567 .confdir = CONFDIR,
4568 .dict_dir = DICTDIR
4569 };
4570
4571 char const *name;
4572 bool do_features = false;
4573 bool do_commands = false;
4574 bool do_usage = false;
4575 xlat_t *xlat;
4576 char *p;
4577 char const *error_str = NULL, *fail_str = NULL;
4578
4579 /*
4580 * Must be called first, so the handler is called last
4581 */
4583
4585 thread_ctx = talloc_new(autofree);
4586
4587 if (fr_fault_setup(autofree, getenv("PANIC_ACTION"), argv[0], PANIC_ACTION_SIGNALS) < 0) {
4588 fr_perror("unit_test_attribute");
4589 goto cleanup;
4590 }
4591#ifdef NDEBUG
4593#endif
4594
4595 /*
4596 * Sync wallclock and cpu time so that we can find
4597 * uses of fr_time_[to|from]_* where
4598 * fr_unix_time_[to|from]_* should be used.
4599 *
4600 * If the wallclock/cpu offset is 0, then both sets
4601 * of macros produce the same result.
4602 */
4603 fr_time_start();
4604
4605 /*
4606 * Allocate a root config section so we can write
4607 * out features and versions.
4608 */
4609 MEM(cs = cf_section_alloc(autofree, NULL, "unit_test_attribute", NULL));
4610 MEM(config.features = cf_section_alloc(cs, cs, "feature", NULL));
4611 dependency_features_init(config.features); /* Add build time features to the config section */
4612
4613 name = argv[0];
4614
4616 default_log.fd = STDOUT_FILENO;
4617 default_log.print_level = false;
4618
4619 while ((c = getopt(argc, argv, "cd:D:F:fxm:Mhpo:S:w:")) != -1) switch (c) {
4620 case 'c':
4621 do_commands = true;
4622 break;
4623
4624 case 'd':
4625 config.confdir = optarg;
4626 break;
4627
4628 case 'D':
4629 config.dict_dir = optarg;
4630 break;
4631
4632 case 'F':
4633 config.fuzzer_base_dir = optarg;
4634 break;
4635
4636 case 'f':
4637 do_features = true;
4638 break;
4639
4640 case 'x':
4641 fr_debug_lvl++;
4642 if (fr_debug_lvl > 2) default_log.print_level = true;
4643 break;
4644
4645 case 'M':
4646 talloc_enable_leak_report();
4647 break;
4648
4649 case 'o':
4650 p = strrchr(optarg, '/');
4651 if (!p || p[1]) {
4652 receipt_file = optarg;
4653
4654 if ((fr_unlink(receipt_file) < 0)) {
4655 fr_perror("unit_test_attribute");
4657 }
4658
4659 } else {
4660 receipt_dir = optarg;
4661 }
4662 break;
4663
4664 case 'p':
4665 allow_purify = true;
4666 break;
4667
4668 case 'm':
4669 fail_message = optarg;
4670 break;
4671
4672 case 'S':
4673 fprintf(stderr, "Invalid option to -S\n");
4675
4676 case 'w':
4677 write_filename = optarg;
4678 break;
4679
4680 case 'h':
4681 default:
4682 do_usage = true; /* Just set a flag, so we can process extra -x args */
4683 break;
4684 }
4685 argc -= (optind - 1);
4686 argv += (optind - 1);
4687
4688 if (do_usage) usage(name);
4689 if (do_features) features_print(config.features);
4690 if (do_commands) commands_print();
4691 if (do_usage || do_features || do_commands) {
4692 ret = EXIT_SUCCESS;
4693 goto cleanup;
4694 }
4695
4696 /*
4697 * Mismatch between the binary and the libraries it depends on
4698 */
4700 fr_perror("unit_test_attribute");
4702 }
4703
4704#ifdef WITH_TLS
4705 /*
4706 * OpenSSL can only be initialised once during the lifetime
4707 * of a process. Initialise it here so that we don't attempt
4708 * to unload and load it multiple times.
4709 */
4710 if (fr_openssl_init() < 0) {
4711 fr_perror("unit_test_attribute");
4713 }
4714#endif
4715
4716 modules_init(NULL);
4717
4718 dl_loader = dl_loader_init(autofree, NULL, false, false);
4719 if (!dl_loader) {
4720 fr_perror("unit_test_attribute");
4722 }
4723
4724 config.dict_gctx = fr_dict_global_ctx_init(NULL, true, config.dict_dir);
4725 if (!config.dict_gctx) {
4726 fr_perror("unit_test_attribute");
4728 }
4729
4731 fr_perror("unit_test_attribute");
4733 }
4734
4735 /*
4736 * Initialize the internal attributes needed by the tmpls.
4737 */
4738 if (tmpl_global_init() < 0) {
4739 fr_perror("unit_test_attribute");
4741 }
4742
4743 /*
4744 * Always needed so we can load the list attributes
4745 * otherwise the tmpl_tokenize code fails.
4746 */
4747 if (request_global_init() < 0) {
4748 fr_perror("unit_test_attribute");
4750 }
4751
4752 /*
4753 * Initialise the interpreter, registering operations.
4754 * Needed because some keywords also register xlats.
4755 */
4756 if (unlang_global_init() < 0) {
4757 fr_perror("unit_test_attribute");
4759 }
4760
4761 /*
4762 * Create a dummy event list
4763 */
4764 if (allow_purify) {
4765 el = fr_event_list_alloc(autofree, NULL, NULL);
4766 fr_assert(el != NULL);
4767
4768 /*
4769 * Simulate thread specific instantiation
4770 */
4772 if (xlat_thread_instantiate(thread_ctx, el) < 0) EXIT_WITH_FAILURE;
4773 }
4774
4775 unlang_thread_instantiate(thread_ctx);
4776
4777 xlat = xlat_func_register(NULL, "test", xlat_test, FR_TYPE_NULL);
4778 if (!xlat) {
4779 ERROR("Failed registering xlat");
4781 }
4783
4784 /*
4785 * And again WITHOUT arguments.
4786 */
4787 xlat = xlat_func_register(NULL, "test_no_args", xlat_test, FR_TYPE_NULL);
4788 if (!xlat) {
4789 ERROR("Failed registering xlat");
4791 }
4793
4794 /*
4795 * Disable hostname lookups, so we don't produce spurious DNS
4796 * queries, and there's no chance of spurious failures if
4797 * it takes a long time to get a response.
4798 */
4800
4801 /*
4802 * Read test commands from stdin
4803 */
4804 if ((argc < 2) && !receipt_dir) {
4805 if (write_filename) {
4806 ERROR("Can only use '-w' with input files");
4808 }
4809
4810 ret = process_file(&exit_now, autofree, &config, NULL, "-", NULL);
4811
4812 } else if ((argc == 2) && (strcmp(argv[1], "-") == 0)) {
4813 char buffer[1024];
4814
4815 /*
4816 * Read the list of filenames from stdin.
4817 */
4818 while (fgets(buffer, sizeof(buffer) - 1, stdin) != NULL) {
4819 buffer[sizeof(buffer) - 1] = '\0';
4820
4821 p = buffer;
4822 while (isspace((unsigned int) *p)) p++;
4823
4824 if (!*p || (*p == '#')) continue;
4825
4826 name = p;
4827
4828 /*
4829 * Smash CR/LF.
4830 *
4831 * Note that we don't care about truncated filenames. The code below
4832 * will complain that it can't open the file.
4833 */
4834 while (*p) {
4835 if (*p < ' ') {
4836 *p = '\0';
4837 break;
4838 }
4839
4840 p++;
4841 }
4842
4843 ret = process_path(&exit_now, autofree, &config, name);
4844 if ((ret != EXIT_SUCCESS) || exit_now) break;
4845 }
4846
4847 } else if (argc > 1) {
4848 int i;
4849
4850 if (receipt_file) for (i = 1; i < argc; i++) {
4851 if (strcmp(receipt_file, argv[i]) == 0) {
4852 ERROR("Receipt file cannot be one of the input files");
4854 }
4855 }
4856
4857 /*
4858 * Read test commands from a list of files in argv[].
4859 */
4860 for (i = 1; i < argc; i++) {
4861 ret = process_path(&exit_now, autofree, &config, argv[i]);
4862 if ((ret != EXIT_SUCCESS) || exit_now) break;
4863 }
4864 } /* nothing to do */
4865
4866 /*
4867 * Try really hard to free any allocated
4868 * memory, so we get clean talloc reports.
4869 */
4870cleanup:
4871#undef EXIT_WITH_FAILURE
4872#define EXIT_WITH_FAILURE \
4873do { \
4874 ret = EXIT_FAILURE; \
4875 error_str = fr_strerror(); \
4876 if (error_str) error_str = talloc_strdup(NULL, error_str); \
4877 goto fail; \
4878} while (0)
4879
4880 /*
4881 * Ensure all thread local memory is cleaned up
4882 * at the appropriate time. This emulates what's
4883 * done with worker/network threads in the
4884 * scheduler.
4885 */
4887
4888#ifdef WITH_TLS
4889 fr_openssl_free();
4890#endif
4891
4892 /*
4893 * dl_loader check needed as talloc_free
4894 * returns -1 on failure.
4895 */
4896 if (dl_loader && (talloc_free(dl_loader) < 0)) {
4897 fail_str = "cleaning up dynamically loaded libraries";
4899 }
4900
4901 if (fr_dict_free(&config.dict, __FILE__) < 0) {
4902 fail_str = "cleaning up dictionaries";
4904 }
4905
4906 if (receipt_file && (ret == EXIT_SUCCESS) && (fr_touch(NULL, receipt_file, 0644, true, 0755) <= 0)) {
4907 fail_str = "creating receipt file";
4909 }
4910
4911 /*
4912 * Explicitly free the autofree context
4913 * to make errors less confusing.
4914 */
4915 if (talloc_free(autofree) < 0) {
4916 fail_str = "cleaning up all memory";
4918 }
4919
4920 if (ret != EXIT_SUCCESS) {
4921 fail:
4922 if (!fail_str) fail_str = "in an input file";
4923 if (!error_str) error_str = "";
4924
4925 fprintf(stderr, "unit_test_attribute failed %s - %s\n", fail_str, error_str);
4926
4927 /*
4928 * Print any command needed to run the test from the command line.
4929 */
4930 if (fail_message) {
4931 fputs(fail_message, stdout);
4932 if (*fail_file) {
4933 fputc(' ', stdout);
4934 shell_word_print(stdout, fail_file);
4935 }
4936 fputc('\n', stdout);
4937 }
4938 }
4939
4940
4941 /*
4942 * Ensure our atexit handlers run before any other
4943 * atexit handlers registered by third party libraries.
4944 */
4946
4947 return ret;
4948}
static int const char char buffer[256]
Definition acutest.h:641
int const char * file
Definition acutest.h:855
strcpy(log_entry->msg, buffer)
int fr_atexit_global_setup(void)
Setup the atexit handler, should be called at the start of a program's execution.
Definition atexit.c:179
int fr_atexit_global_trigger_all(void)
Cause all global free triggers to fire.
Definition atexit.c:310
#define fr_atexit_thread_trigger_all(...)
Definition atexit.h:236
char const fr_base64_url_alphabet_encode[SBUFF_CHAR_CLASS]
Definition base64.c:173
ssize_t fr_base64_encode_nstd(fr_sbuff_t *out, fr_dbuff_t *in, bool add_padding, char const alphabet[static SBUFF_CHAR_CLASS])
Base 64 encode binary data.
Definition base64.c:326
#define CMD_MAX_ARGV
Definition radmin.c:151
static uint8_t request[4]
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:203
#define RCSID(id)
Definition build.h:603
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:245
#define UNUSED
Definition build.h:415
#define NUM_ELEMENTS(_t)
Definition build.h:437
int fr_value_calc_nary_op(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t type, fr_token_t op, fr_value_box_t const *group)
Calculate DST = OP { A, B, C, ... }.
Definition calc.c:2372
int fr_value_calc_assignment_op(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_token_t op, fr_value_box_t const *src)
Calculate DST OP SRC.
Definition calc.c:2527
int fr_value_calc_binary_op(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t hint, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b)
Calculate DST = A OP B.
Definition calc.c:2013
Configuration AVP similar to a fr_pair_t.
Definition cf_priv.h:77
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
CONF_PAIR * cf_pair_find_next(CONF_SECTION const *cs, CONF_PAIR const *prev, char const *attr)
Find a pair with a name matching attr, after specified pair.
Definition cf_util.c:1654
CONF_PAIR * cf_pair_find(CONF_SECTION const *cs, char const *attr)
Search for a CONF_PAIR with a specific name.
Definition cf_util.c:1640
char const * cf_pair_value(CONF_PAIR const *pair)
Return the value of a CONF_PAIR.
Definition cf_util.c:1758
char const * cf_pair_attr(CONF_PAIR const *pair)
Return the attr of a CONF_PAIR.
Definition cf_util.c:1742
#define cf_lineno_set(_ci, _lineno)
Definition cf_util.h:183
#define cf_section_alloc(_ctx, _parent, _name1, _name2)
Definition cf_util.h:198
#define cf_filename_set(_ci, _filename)
Definition cf_util.h:180
#define CF_IDENT_ANY
Definition cf_util.h:80
int fr_command_walk(fr_cmd_t *head, void **walk_ctx, void *ctx, fr_cmd_walk_t callback)
Walk over a command hierarchy.
Definition command.c:1027
void fr_cmd_debug(FILE *fp, fr_cmd_t *head)
Definition command.c:1610
int fr_command_add(TALLOC_CTX *talloc_ctx, fr_cmd_t **head, char const *name, void *ctx, fr_cmd_table_t const *table)
Add one command to the global command tree.
Definition command.c:733
int fr_command_tab_expand(TALLOC_CTX *ctx, fr_cmd_t *head, fr_cmd_info_t *info, int max_expansions, char const **expansions)
Get the commands && help at a particular level.
Definition command.c:1298
char const ** parents
Definition command.h:66
char const * help
help text
Definition command.h:55
int argc
current argument count
Definition command.h:39
fr_cmd_func_t func
function to process this command
Definition command.h:56
char const * syntax
Definition command.h:68
char const * syntax
e.g. "STRING"
Definition command.h:54
fr_value_box_t ** box
value_box version of commands.
Definition command.h:43
bool read_only
Definition command.h:58
char const * parent
e.g. "show module"
Definition command.h:52
fr_cmd_tab_t tab_expand
tab expand things in the syntax string
Definition command.h:57
int max_argc
maximum number of arguments
Definition command.h:40
char const * name
e.g. "stats"
Definition command.h:53
char const ** argv
text version of commands
Definition command.h:42
char const * name
Definition command.h:67
TALLOC_CTX * autofree
Definition common.c:29
fr_dict_t * dict
Definition common.c:31
fr_dict_attr_t const * root_da
Definition common.c:32
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:556
static void * fr_dcursor_current(fr_dcursor_t *cursor)
Return the item the cursor current points to.
Definition dcursor.h:337
void fr_disable_null_tracking_on_free(TALLOC_CTX *ctx)
Disable the null tracking context when a talloc chunk is freed.
Definition debug.c:1031
int fr_fault_setup(TALLOC_CTX *ctx, char const *cmd, char const *program, unsigned long fault_signals)
Registers signal handlers to execute panic_action on fatal signal.
Definition debug.c:1071
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition debug.h:248
#define PANIC_ACTION_SIGNALS
Definition debug.h:133
#define MEM(x)
Definition debug.h:38
void dependency_features_init(CONF_SECTION *cs)
Initialise core feature flags.
Definition dependency.c:182
#define ERROR(fmt,...)
Definition dhcpclient.c:40
#define DEBUG(fmt,...)
Definition dhcpclient.c:38
static NEVER_RETURNS void usage(void)
Definition dhcpclient.c:113
int fr_dict_global_ctx_dir_set(char const *dict_dir)
Allow the default dict dir to be changed after initialisation.
Definition dict_util.c:4936
fr_dict_attr_t const * fr_dict_attr_by_name(fr_dict_attr_err_t *err, fr_dict_attr_t const *parent, char const *attr))
Locate a fr_dict_attr_t by its name.
Definition dict_util.c:3600
void fr_dict_debug(FILE *fp, fr_dict_t const *dict)
Definition dict_print.c:278
fr_dict_attr_err_t
Errors returned by attribute lookup functions.
Definition dict.h:341
@ FR_DICT_ATTR_OK
No error.
Definition dict.h:342
fr_slen_t fr_dict_attr_by_oid_substr(fr_dict_attr_err_t *err, fr_dict_attr_t const **out, fr_dict_attr_t const *parent, fr_sbuff_t *in, fr_sbuff_term_t const *tt))
Resolve an attribute using an OID string.
Definition dict_util.c:2641
int fr_dict_str_to_argv(char *str, char **argv, int max_argc)
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2717
void fr_dict_global_ctx_set(fr_dict_gctx_t const *gctx)
Set a new, active, global dictionary context.
Definition dict_util.c:4906
int fr_dict_read(fr_dict_t *dict, char const *dict_dir, char const *filename))
Read supplementary attribute definitions into an existing dictionary.
int fr_dict_free(fr_dict_t **dict, char const *dependent)
Decrement the reference count on a previously loaded dictionary.
Definition dict_util.c:4466
int fr_dict_const_free(fr_dict_t const **dict, char const *dependent)
Decrement the reference count on a previously loaded dictionary.
Definition dict_util.c:4450
fr_dict_t const * fr_dict_internal(void)
Definition dict_util.c:5065
int fr_dict_protocol_afrom_file(fr_dict_t **out, char const *proto_name, char const *proto_dir, char const *dependent))
(Re)-initialize a protocol dictionary
bool fr_dict_filename_loaded(fr_dict_t const *dict, char const *dict_dir, char const *filename))
int fr_dict_global_ctx_free(fr_dict_gctx_t const *gctx)
Explicitly free all data associated with a global dictionary context.
Definition dict_util.c:4922
int fr_dict_parse_str(fr_dict_t *dict, char const *str, fr_dict_attr_t const *parent))
fr_dict_gctx_t * fr_dict_global_ctx_init(TALLOC_CTX *ctx, bool free_at_exit, char const *dict_dir))
Initialise the global protocol hashes.
Definition dict_util.c:4850
#define FR_DICT_ATTR_MAX_NAME_LEN
Maximum length of a attribute name.
Definition dict.h:525
static fr_dict_attr_t const * fr_dict_attr_ref(fr_dict_attr_t const *da)
Return the reference associated with a group type attribute.
Definition dict_ext.h:149
int fr_dict_afrom_file(fr_dict_t **out, char const *dir, char const *filename))
Load one dictionary file.
Test enumeration values.
Definition dict_test.h:92
int fr_dict_internal_afrom_file(fr_dict_t **out, char const *dict_subdir, char const *dependent)
(Re-)Initialize the special internal dictionary
dl_loader_t * dl_loader_init(TALLOC_CTX *ctx, void *uctx, bool uctx_free, bool defer_symbol_init)
Initialise structures needed by the dynamic linker.
Definition dl.c:907
dl_t * dl_by_name(dl_loader_t *dl_loader, char const *name, void *uctx, bool uctx_free)
Search for a dl's shared object in various locations.
Definition dl.c:470
A dynamic loader.
Definition dl.c:81
void * handle
Handle returned by dlopen.
Definition dl.h:61
Module handle.
Definition dl.h:57
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:242
static void * fr_dlist_head(fr_dlist_head_t const *list_head)
Return the HEAD item of a list or NULL if the list is empty.
Definition dlist.h:468
static void fr_dlist_talloc_free(fr_dlist_head_t *head)
Free all items in a doubly linked list (with talloc)
Definition dlist.h:892
static bool fr_dlist_empty(fr_dlist_head_t const *list_head)
Check whether a list has any items.
Definition dlist.h:483
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition dlist.h:360
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition dlist.h:537
Head of a doubly linked list.
Definition dlist.h:51
Entry in a doubly linked list.
Definition dlist.h:41
ssize_t fr_dns_label_from_value_box(size_t *need, uint8_t *buf, size_t buf_len, uint8_t *where, bool compression, fr_value_box_t const *value, fr_dns_labels_t *lb)
Encode a single value box of type string, serializing its contents to a dns label.
Definition dns.c:636
ssize_t fr_dns_label_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *dst, uint8_t const *src, size_t len, uint8_t const *label, bool tainted, fr_dns_labels_t *lb)
Decode a fr_value_box_t from one DNS label.
Definition dns.c:1224
void * fr_hash_table_iter_next(fr_hash_table_t *ht, fr_hash_iter_t *iter)
Iterate over entries in a hash table.
Definition hash.c:668
void * fr_hash_table_iter_init(fr_hash_table_t *ht, fr_hash_iter_t *iter)
Initialise an iterator.
Definition hash.c:723
Stores the state of the current iteration operation.
Definition hash.h:41
talloc_free(hp)
bool fr_hostname_lookups
hostname -> IP lookups?
Definition inet.c:52
bool fr_reverse_lookups
IP -> hostname lookups?
Definition inet.c:51
int unlang_global_init(void)
Definition base.c:158
fr_event_list_t * fr_event_list_alloc(TALLOC_CTX *ctx, fr_event_status_cb_t status, void *status_uctx)
Initialise a new event list.
Definition event.c:2551
Stores all information relating to an event list.
Definition event.c:375
ssize_t fr_mkdir(int *fd_out, char const *path, ssize_t len, mode_t mode, fr_mkdir_func_t func, void *uctx)
Create directories that are missing in the specified path.
Definition file.c:218
int fr_unlink(char const *filename)
Remove a regular file from the filesystem.
Definition file.c:366
ssize_t fr_touch(int *fd_out, char const *filename, mode_t mode, bool mkdir, mode_t dir_mode)
Create an empty file.
Definition file.c:322
char * fr_realpath(TALLOC_CTX *ctx, char const *path, ssize_t len)
Convenience wrapper around realpath.
Definition file.c:283
int fr_debug_lvl
Definition log.c:41
FILE * fr_log_fp
Definition log.c:40
fr_log_t default_log
Definition log.c:308
@ L_DST_STDOUT
Log to stdout.
Definition log.h:75
#define ASAN_POISON_MEMORY_REGION(_start, _size)
Definition lsan.h:62
#define ASAN_UNPOISON_MEMORY_REGION(_start, _size)
Definition lsan.h:63
#define SBUFF_CHAR_CLASS
fr_type_t
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_MAX
Number of defined data types.
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
@ FR_TYPE_DATE
Unix time stamp, always has value >2^31.
@ FR_TYPE_GROUP
A grouping of other attributes.
unsigned int uint32_t
ssize_t fr_dict_attr_flags_print(fr_sbuff_t *out, fr_dict_t const *dict, fr_type_t type, fr_dict_attr_flags_t const *flags)
long int ssize_t
ssize_t fr_dict_attr_oid_print(fr_sbuff_t *out, fr_dict_attr_t const *ancestor, fr_dict_attr_t const *da, bool numeric)
unsigned char uint8_t
ssize_t fr_slen_t
fr_slen_t tmpl_print(fr_sbuff_t *out, tmpl_t const *vpt, fr_sbuff_escape_rules_t const *e_rules)
unsigned long int size_t
static uint8_t depth(fr_minmax_heap_index_t i)
Definition minmax_heap.c:83
static bool is_whitespace(char const *value)
Check whether the string is all whitespace.
Definition misc.h:69
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:284
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:47
fr_slen_t fr_pair_list_afrom_substr(fr_pair_parse_t const *root, fr_pair_parse_t *relative, fr_sbuff_t *in)
Parse a fr_pair_list_t from a substring.
int fr_pair_list_afrom_file(TALLOC_CTX *ctx, fr_dict_t const *dict, fr_pair_list_t *out, FILE *fp, bool *pfiledone, bool allow_exec)
Read valuepairs from the fp up to End-Of-File.
TALLOC_CTX * ctx
Definition pair_legacy.h:43
#define is_truncated(_ret, _max)
Definition print.h:48
static const conf_parser_t config[]
Definition base.c:162
void * fr_proto_next_encodable(fr_dcursor_t *cursor, void *current, void *uctx)
Implements the default iterator to encode pairs belonging to a specific dictionary that are not inter...
Definition proto.c:100
static fr_internal_encode_ctx_t encode_ctx
#define fr_assert(_expr)
Definition rad_assert.h:37
#define DEBUG2(fmt,...)
static bool done
Definition radclient.c:80
#define INFO(fmt,...)
Definition radict.c:67
static const char * spaces
Definition radict.c:185
static bool cleanup
Definition radsniff.c:59
void fr_rand_seed(uint64_t seed)
Seed the random number generator with a fixed value.
Definition rand.c:82
fr_dict_attr_t const * request_attr_request
Definition request.c:43
int request_global_init(void)
Definition request.c:614
static char const * name
ssize_t fr_sbuff_in_strcpy(fr_sbuff_t *sbuff, char const *str)
Copy bytes into the sbuff up to the first \0.
Definition sbuff.c:1529
size_t fr_sbuff_trim(fr_sbuff_t *sbuff, bool const to_trim[static SBUFF_CHAR_CLASS])
Trim trailing characters from a string we're composing.
Definition sbuff.c:2287
size_t fr_sbuff_adv_until(fr_sbuff_t *sbuff, size_t len, fr_sbuff_term_t const *tt, char escape_chr)
Wind position until we hit a character in the terminal set.
Definition sbuff.c:2011
fr_sbuff_err_t fr_sbuff_out_bool(bool *out, fr_sbuff_t *in)
See if the string contains a truth value.
Definition sbuff.c:1209
ssize_t fr_sbuff_in_sprintf(fr_sbuff_t *sbuff, char const *fmt,...)
Print using a fmt string to an sbuff.
Definition sbuff.c:1680
bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c)
Return true if the current char matches, and if it does, advance.
Definition sbuff.c:2247
#define fr_sbuff_start(_sbuff_or_marker)
#define fr_sbuff_out_by_longest_prefix(_match_len, _out, _table, _sbuff, _def)
#define fr_sbuff_set(_dst, _src)
#define fr_sbuff_diff(_a, _b)
#define FR_SBUFF_IN(_start, _len_or_end)
#define fr_sbuff_adv_past_whitespace(_sbuff, _len, _tt)
#define fr_sbuff_current(_sbuff_or_marker)
#define FR_SBUFF_TERMS(...)
Initialise a terminal structure with a list of sorted strings.
Definition sbuff.h:191
#define fr_sbuff_extend(_sbuff_or_marker)
#define FR_SBUFF_ERROR_RETURN(_sbuff_or_marker)
#define fr_sbuff_end(_sbuff_or_marker)
#define fr_sbuff_advance(_sbuff_or_marker, _len)
#define fr_sbuff_out(_out, _in)
#define fr_sbuff_switch(_sbuff_or_marker, _eob)
#define fr_sbuff_remaining(_sbuff_or_marker)
#define FR_SBUFF_OUT(_start, _len_or_end)
#define fr_sbuff_used(_sbuff_or_marker)
Set of terminal elements.
ssize_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff_t *in, fr_token_t quote, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Convert an arbitrary string into a tmpl_t.
int tmpl_global_init(void)
Definition tmpl_eval.c:1384
tmpl_xlat_rules_t xlat
Rules/data for parsing xlats.
Definition tmpl.h:347
bool new_functions
new function syntax
Definition tmpl.h:337
static fr_slen_t vpt
Definition tmpl.h:1300
fr_slen_t tmpl_request_ref_list_afrom_substr(TALLOC_CTX *ctx, tmpl_attr_error_t *err, FR_DLIST_HEAD(tmpl_request_list) _CONST **out, fr_sbuff_t *in)
tmpl_attr_rules_t attr
Rules/data for parsing attribute references.
Definition tmpl.h:346
struct tmpl_rules_s tmpl_rules_t
Definition tmpl.h:240
fr_slen_t tmpl_attr_list_from_substr(fr_dict_attr_t const **da_p, fr_sbuff_t *in)
Parse one a single list reference.
fr_event_list_t * runtime_el
The eventlist to use for runtime instantiation of xlats.
Definition tmpl.h:335
Optional arguments passed to vp_tmpl functions.
Definition tmpl.h:343
void fr_sha1_init(fr_sha1_ctx *context)
Definition sha1.c:93
void fr_sha1_final(uint8_t digest[static SHA1_DIGEST_LENGTH], fr_sha1_ctx *context)
Definition sha1.c:141
void fr_sha1_update(fr_sha1_ctx *context, uint8_t const *in, size_t len)
Definition sha1.c:105
#define SHA1_DIGEST_LENGTH
Definition sha1.h:29
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:37
#define fr_skip_whitespace(_p)
Skip whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition skip.h:36
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition snprintf.c:689
void modules_init(char const *lib_dir)
Perform global initialisation for modules.
Definition module.c:1981
fr_aka_sim_id_type_t type
fr_pair_t * vp
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition strlcpy.c:34
fr_log_dst_t dst
Log destination.
Definition log.h:94
int fd
File descriptor to write messages to.
Definition log.h:109
bool print_level
sometimes we don't want log levels printed
Definition log.h:103
unsigned int allow_unknown
Allow unknown attributes i.e.
Definition tmpl.h:310
fr_dict_attr_t const * list_def
Default list to use with unqualified attribute reference.
Definition tmpl.h:302
fr_dict_t const * dict_def
Default dictionary to use with unqualified attribute references.
Definition tmpl.h:280
unsigned int allow_foreign
Allow arguments not found in dict_def.
Definition tmpl.h:321
unsigned int allow_unresolved
Allow attributes that look valid but were not found in the dictionaries.
Definition tmpl.h:313
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define fr_table_value_by_longest_prefix(_match_len, _table, _name, _name_len, _def)
Find the longest string match using a sorted or ordered table.
Definition table.h:764
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:804
An element in a lexicographically sorted array of name to num mappings.
Definition table.h:49
An element in a lexicographically sorted array of name to ptr mappings.
Definition table.h:65
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition talloc.c:693
#define talloc_asprintf
Definition talloc.h:151
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition talloc.h:55
#define talloc_strdup(_ctx, _str)
Definition talloc.h:149
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
Definition talloc.h:143
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition test_point.h:86
fr_tp_proto_decode_t func
Decoder for proto layer.
Definition test_point.h:69
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition test_point.h:94
fr_dcursor_iter_t next_encodable
Iterator to use to select attributes to encode.
Definition test_point.h:96
fr_tp_proto_encode_t func
Encoder for proto layer.
Definition test_point.h:77
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition test_point.h:76
fr_pair_decode_t func
Decoder for pairs.
Definition test_point.h:87
fr_pair_encode_t func
Encoder for pairs.
Definition test_point.h:95
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition test_point.h:68
Entry point for pair decoders.
Definition test_point.h:85
Entry point for pair encoders.
Definition test_point.h:93
Entry point for protocol decoders.
Definition test_point.h:67
Entry point for protocol encoders.
Definition test_point.h:75
int fr_time_start(void)
Initialize the local time.
Definition time.c:157
const bool fr_assignment_op[T_TOKEN_LAST]
Definition token.c:234
fr_table_num_ordered_t const fr_tokens_table[]
Definition token.c:33
@ T_AND
Definition token.h:53
@ T_INVALID
Definition token.h:37
@ T_SUB
Definition token.h:50
@ T_XOR
Definition token.h:56
@ T_DIV
Definition token.h:52
@ T_MOD
Definition token.h:58
@ T_BARE_WORD
Definition token.h:118
@ T_ADD
Definition token.h:49
@ T_DOUBLE_QUOTED_STRING
Definition token.h:119
@ T_MUL
Definition token.h:51
@ T_OR
Definition token.h:54
enum fr_token fr_token_t
static int command_func(UNUSED FILE *fp, UNUSED FILE *fp_err, UNUSED void *ctx, UNUSED fr_cmd_info_t const *info)
static size_t encode_pair(command_result_t *result, command_file_ctx_t *cc, char *data, fr_test_point_pair_encode_t *tp, void *encode_ctx, fr_pair_list_t *head, bool truncate, char const *fuzzer_text)
Encode a list of pairs, writing the result to the data buffer as hex.
#define RETURN_OK(_len)
#define POISONED_BUFFER_START(_p)
static size_t commands_len
static int dump_fuzzer_data(int fd_dir, char const *text, uint8_t const *data, size_t data_len)
static size_t command_touch(command_result_t *result, UNUSED command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Touch a file to indicate a test completed.
uint32_t start
Start of line range.
static ssize_t encode_data_string(char *buffer, uint8_t *output, size_t outlen)
static dl_loader_t * dl_loader
#define BUFF_POISON_START
static ssize_t encode_extended(char *buffer, uint8_t *output, size_t outlen)
int main(int argc, char *argv[])
static size_t command_decode_dns_label(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
static void unload_proto_library(void)
static size_t command_xlat_normalise(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Parse an reprint and xlat expansion.
static size_t parse_typed_value(command_result_t *result, command_file_ctx_t *cc, fr_value_box_t *box, char const **out, char const *in, size_t inlen)
uint32_t test_count
How many tests we've executed in this file.
static int decode_vendor(char *buffer, char **endptr)
static size_t command_xlat_purify_condition(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Parse, purify, and reprint an xlat expression expansion.
static size_t command_proto_dictionary(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
static char const * fail_file
static void commands_print(void)
#define POISONED_BUFFER_END(_p)
@ RESULT_MISMATCH
Fatal error - Result didn't match what we expected.
@ RESULT_COMMAND_ERROR
Fatal error - Command operation error.
@ RESULT_NOOP
Not an error - Did nothing...
@ RESULT_OK
Not an error - Result as expected.
@ RESULT_EXIT
Stop processing files and exit.
@ RESULT_SKIP_FILE
Not an error - Skip the rest of this file, or until we reach an "eof" command.
@ RESULT_PARSE_ERROR
Fatal error - Command syntax error.
static bool allow_purify
static char proto_name_prev[128]
TALLOC_CTX * tmp_ctx
Temporary context to hold buffers in this.
static size_t hex_print(char *out, size_t outlen, uint8_t const *in, size_t inlen)
Print hex string to buffer.
static size_t command_exit(command_result_t *result, UNUSED command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Exit gracefully with the specified code.
#define SBUFF_RETURN_OK_WITH_ERROR(_function,...)
static ssize_t hex_to_bin(uint8_t *out, size_t outlen, char *in, size_t inlen)
#define RETURN_OK_WITH_ERROR()
static size_t command_rand_seed(command_result_t *result, UNUSED command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Seed the random number generator, so fr_rand() returns a fixed sequence.
static size_t command_xlat_purify(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Parse, purify, and reprint an xlat expression expansion.
char const * filename
Current file we're operating on.
static void mismatch_print(command_file_ctx_t *cc, char const *command, char *expected, size_t expected_len, char *got, size_t got_len, bool print_diff)
static size_t command_tmpl(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Parse an reprint a tmpl expansion.
static size_t command_tmpl_rules(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, size_t inlen)
static size_t command_fuzzer_out(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Enable fuzzer output.
static ssize_t encode_rfc(char *buffer, uint8_t *output, size_t outlen)
static dl_t * dl
static void command_ctx_reset(command_file_ctx_t *cc, TALLOC_CTX *ctx)
static int dictionary_load_common(command_result_t *result, command_file_ctx_t *cc, char const *in, char const *default_subdir)
Common dictionary load function.
static size_t command_include(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Execute another test file.
static size_t command_proto_dictionary_root(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
static ssize_t command_tmpl_rule_request_def(TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value)
static ssize_t command_tmpl_rule_allow_unresolved(UNUSED TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value)
uint32_t lineno
Current line number.
ssize_t last_ret
Last return value.
static size_t command_pair_compare(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
static void features_print(CONF_SECTION *features)
static size_t command_returned(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
static xlat_arg_parser_t const xlat_test_args[]
static int decode_attr(char *buffer, char **endptr)
static size_t command_attr_name(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Print attribute information.
static fr_dict_t * dictionary_current(command_file_ctx_t *cc)
static size_t command_encode_pair(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
int fuzzer_fd
File descriptor pointing to a a directory to write fuzzer output.
fr_dlist_t entry
Entry in the dlist.
uint8_t * buffer_end
Where the non-poisoned region of the buffer ends.
static fr_table_ptr_sorted_t commands[]
#define RETURN_NOOP(_len)
TALLOC_CTX * tmp_ctx
Talloc context for test points.
static size_t command_radmin_tab(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
static size_t command_calc_nary(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Perform calculations on multi-valued ops.
static size_t command_dictionary_dump(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, size_t data_used, UNUSED char *in, UNUSED size_t inlen)
Print the currently loaded dictionary.
fr_dict_gctx_t const * test_gctx
Dictionary context for test dictionaries.
static void shell_word_print(FILE *fp, char const *str)
Print a string as a single shell word.
static size_t command_decode_proto(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
static size_t command_proto(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Dynamically load a protocol library.
char * fuzzer_proto_dir
Subdirectory of where to write fuzzer files, from 'fuzzer-out dir'.
static fr_cmd_t * command_head
static size_t command_write(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
char * path
Current path we're operating in.
static int poisoned_buffer_allocate(TALLOC_CTX *ctx, uint8_t **buff, size_t size)
Allocate a special buffer with poisoned memory regions at the start and end.
#define DEFAULT_BUFFER_SIZE
Default buffer size for a command_file_ctx_t.
static size_t command_attr_oid(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Print attribute information.
static char const * receipt_file
static FILE * write_fp
static size_t command_match_regex(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
Compare the data buffer against an expected expression.
static size_t command_encode_raw(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Encode a RADIUS attribute writing the result to the data buffer as space separated hexits.
#define RETURN_PARSE_ERROR(_offset)
static int fuzzer_open_fd(command_file_ctx_t *cc, char **out, char const *base, char const *dir)
Helper function to open a fuzzer path, and update the name / FD.
static ssize_t command_tmpl_rule_allow_unknown(UNUSED TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value)
command_config_t const * config
static int _command_ctx_free(command_file_ctx_t *cc)
#define COMMAND_OUTPUT_MAX
static size_t command_allow_unresolved(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, size_t inlen)
Determine if unresolved attributes are allowed.
static size_t command_read_file(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Parse a list of pairs.
static int process_file(bool *exit_now, TALLOC_CTX *ctx, command_config_t const *config, const char *root_dir, char const *filename, fr_dlist_head_t *lines)
static size_t command_cast(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Perform casting.
static ssize_t encode_tlv(char *buffer, uint8_t *output, size_t outlen)
#define BUFF_POISON_END
static int command_walk(UNUSED void *ctx, fr_cmd_walk_info_t *info)
static size_t command_condition_normalise(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Parse and reprint a condition.
static size_t command_count(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
static fr_table_num_sorted_t command_rcode_table[]
static size_t command_max_buffer_size(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Artificially limit the maximum packet size.
tmpl_rules_t tmpl_rules
To pass to parsing functions.
static size_t decode_pair(command_result_t *result, command_file_ctx_t *cc, char *data, char *in, size_t inlen, fr_pair_t **head_p, char **hex_p)
Decode hex data into a list of pairs.
size_t(* command_func_t)(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
Command to execute.
#define RETURN_MISMATCH(_len)
uint32_t end
End of line range.
static char const hextab[]
static command_file_ctx_t * command_ctx_alloc(TALLOC_CTX *ctx, command_config_t const *config, char const *path, char const *filename)
static size_t command_encode_proto(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
static size_t command_load_dictionary(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
static ssize_t load_test_point_by_command(void **symbol, char *command, char const *dflt_symbol)
static void command_print(void)
#define ATTR_COMMON
static size_t command_pair_common(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen, bool allow_compare)
Parse an print an attribute pair or pair list.
#define RETURN_EXIT(_ret)
static fr_event_list_t * el
static char const * receipt_dir
static size_t command_xlat_expr(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Parse and reprint an xlat expression expansion.
#define EXIT_WITH_FAILURE
CONF_SECTION * features
Enabled features.
static size_t command_calc(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Perform calculations.
static size_t command_migrate(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Set or clear migration flags.
static size_t command_dictionary_attribute_parse(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Parse a dictionary attribute, writing "ok" to the data buffer is everything was ok.
static size_t command_xlat_argv(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Parse an reprint and xlat argv expansion.
#define CLEAR_TEST_POINT(_cc)
static int process_path(bool *exit_now, TALLOC_CTX *ctx, command_config_t const *config, const char *path)
static ssize_t encode_data(char *p, uint8_t *output, size_t outlen)
static size_t command_dictionary_read(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Read a dictionary from a file, and then free it.
#define RETURN_COMMAND_ERROR()
static size_t command_eof(UNUSED command_result_t *result, UNUSED command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
Command eof.
static size_t command_match(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
Compare the data buffer to an expected value.
static size_t command_decode_pair(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
#define RETURN_SKIP_FILE()
static ssize_t command_tmpl_rule_attr_parent(UNUSED TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value)
command_rcode_t rcode
fr_dict_gctx_t const * dict_gctx
Dictionary gctx to "reset" to.
static ssize_t command_tmpl_rule_list_def(UNUSED TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value)
static ssize_t command_tmpl_rule_allow_foreign(UNUSED TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value)
char const * fuzzer_base_dir
Base directory of where to write fuzzer files, from '-F'.
static ssize_t encode_vsa(char *buffer, uint8_t *output, size_t outlen)
size_t process_line(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
static ssize_t load_proto_library(char const *proto_name)
static int line_ranges_parse(TALLOC_CTX *ctx, fr_dlist_head_t *out, fr_sbuff_t *in)
static size_t command_cd(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Change the working directory.
static size_t command_pair(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
static size_t command_comment(UNUSED command_result_t *result, UNUSED command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
Placeholder function for comments.
uint8_t * buffer_start
Where the non-poisoned region of the buffer starts.
static ssize_t encode_data_tlv(char *buffer, char **endptr, uint8_t *output, size_t outlen)
static size_t command_radmin_add(command_result_t *result, command_file_ctx_t *cc, char *data, size_t UNUSED data_used, char *in, UNUSED size_t inlen)
static size_t command_need_feature(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Skip the test file if we're missing a particular feature.
static char const * fail_message
static size_t command_attr_type(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Print attribute information.
static ssize_t encode_long_extended(char *buffer, uint8_t *output, size_t outlen)
ssize_t(* command_tmpl_rule_func)(TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value)
Callback for a tmpl rule parser.
static size_t command_attr_flags(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Print attribute information.
static size_t command_clear(command_result_t *result, UNUSED command_file_ctx_t *cc, char *data, size_t UNUSED data_used, UNUSED char *in, UNUSED size_t inlen)
static size_t command_attr_children(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Print attribute information.
fr_dict_t * test_internal_dict
Internal dictionary of test_gctx.
static size_t command_de_encode_pair(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Decode hex data into pairs, and then encode the pairs again.
static ssize_t encode_evs(char *buffer, uint8_t *output, size_t outlen)
static size_t command_value_box_normalise(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
static xlat_action_t xlat_test(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, UNUSED xlat_ctx_t const *xctx, UNUSED request_t *request, UNUSED fr_value_box_list_t *in)
fr_dict_t * dict
Dictionary to "reset" to.
static char const * write_filename
static xlat_arg_parser_t const xlat_test_no_args[]
uint8_t * buffer
Temporary resizable buffer we use for holding non-string data.
static size_t command_no(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
Negate the result of a match command or any command which returns "OK".
static size_t command_rcode_table_len
static const fr_token_t token2op[SBUFF_CHAR_CLASS]
static size_t command_encode_dns_label(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Configuration parameters passed to command functions.
int unlang_thread_instantiate(TALLOC_CTX *ctx)
Create thread-specific data structures for unlang.
Definition interpret.c:69
fr_slen_t xlat_tokenize_condition(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Definition xlat_expr.c:3255
fr_slen_t xlat_print(fr_sbuff_t *in, xlat_exp_head_t const *node, fr_sbuff_escape_rules_t const *e_rules)
Reconstitute an xlat expression from its constituent nodes.
void xlat_debug_head(xlat_exp_head_t const *head)
fr_slen_t xlat_tokenize(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules)
Tokenize an xlat expansion.
xlat_action_t
Definition xlat.h:37
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition xlat.h:43
ssize_t xlat_flatten_to_argv(TALLOC_CTX *ctx, xlat_exp_head_t ***argv, xlat_exp_head_t *head)
Turn am xlat list into an argv[] array, and nuke the input list.
Definition xlat_eval.c:1939
static fr_slen_t head
Definition xlat.h:410
int xlat_instantiate(void)
Call instantiation functions for all registered, "permanent" xlats.
Definition xlat_inst.c:510
int xlat_thread_instantiate(TALLOC_CTX *ctx, fr_event_list_t *el)
Create thread specific instance tree and create thread instances.
Definition xlat_inst.c:441
int xlat_purify(xlat_exp_head_t *head, unlang_interpret_t *intp)
Purify an xlat.
unsigned int required
Argument must be present, and non-empty.
Definition xlat.h:136
#define XLAT_ARG_PARSER_TERMINATOR
Definition xlat.h:160
fr_slen_t xlat_tokenize_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, xlat_arg_parser_t const *xlat_args, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, bool spaces))
Tokenize an xlat expansion into a series of XLAT_TYPE_CHILD arguments.
fr_slen_t xlat_tokenize_expression(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Definition xlat_expr.c:3227
Definition for a single argument consumed by an xlat function.
Definition xlat.h:135
#define FR_DICTIONARY_INTERNAL_DIR
Definition conf.h:7
#define fr_pair_dcursor_iter_init(_cursor, _list, _iter, _uctx)
Initialises a special dcursor with callbacks that will maintain the attr sublists correctly.
Definition pair.h:585
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
#define PAIR_LIST_VERIFY_WITH_CTX(_c, _x)
Definition pair.h:213
ssize_t fr_pair_list_print(fr_sbuff_t *out, fr_dict_attr_t const *parent, fr_pair_list_t const *list)
Print a pair list.
Definition pair_print.c:316
static fr_slen_t parent
Definition pair.h:864
char const * fr_strerror(void)
Get the last library error.
Definition strerror.c:558
void fr_perror(char const *fmt,...)
Print the current error to stderr with a prefix.
Definition strerror.c:737
char const * fr_strerror_peek(void)
Get the last library error.
Definition strerror.c:631
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition strerror.c:581
char const * fr_strerror_concat(char const *prefix, char const *line_sep)
Print the stack of string buffers to a thread local buffer.
Definition strerror.c:828
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_printf_push(_fmt,...)
Add a message to an existing stack of messages at the tail.
Definition strerror.h:84
#define fr_strerror_const_push(_msg)
Definition strerror.h:227
#define fr_strerror_printf_push_head(_fmt,...)
Add a message to an existing stack of messages at the head.
Definition strerror.h:104
#define fr_strerror_const(_msg)
Definition strerror.h:223
fr_table_num_ordered_t const fr_type_table[]
Map data types to names representing those types.
Definition types.c:31
@ FR_TYPE_ATTR
A contains an attribute reference.
Definition types.h:83
#define fr_type_is_null(_x)
Definition types.h:347
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition types.h:454
int fr_check_lib_magic(uint64_t magic)
Check if the application linking to the library has the correct magic number.
Definition version.c:40
#define RADIUSD_MAGIC_NUMBER
Definition version.h:81
fr_cmp_ret_t fr_value_box_cmp(fr_value_box_t const *a, fr_value_box_t const *b)
Compare two values.
Definition value.c:770
fr_slen_t fr_value_box_from_str(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, char const *in, size_t inlen, fr_sbuff_unescape_rules_t const *erules)
Definition value.c:6256
ssize_t fr_value_box_print(fr_sbuff_t *out, fr_value_box_t const *data, fr_sbuff_escape_rules_t const *e_rules)
Print one boxed value to a string.
Definition value.c:6293
fr_sbuff_escape_rules_t const fr_value_escape_double
Definition value.c:366
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition value.c:4071
fr_sbuff_parse_rules_t const value_parse_rules_bareword_unquoted
Default formatting rules.
Definition value.c:494
int fr_value_box_cast_in_place(TALLOC_CTX *ctx, fr_value_box_t *vb, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv)
Convert one type of fr_value_box_t to another in place.
Definition value.c:4326
fr_slen_t fr_value_box_from_substr(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_sbuff_t *in, fr_sbuff_parse_rules_t const *rules)
Convert string value to a fr_value_box_t type.
Definition value.c:5584
fr_sbuff_unescape_rules_t const fr_value_unescape_double
Definition value.c:282
ssize_t fr_value_box_print_quoted(fr_sbuff_t *out, fr_value_box_t const *data, fr_token_t quote)
Print one boxed value to a string with quotes (where needed)
Definition value.c:6533
fr_sbuff_parse_rules_t const value_parse_rules_double_quoted
Definition value.c:561
void fr_value_box_clear(fr_value_box_t *data)
Clear/free any existing value and metadata.
Definition value.c:4509
static fr_sbuff_err_t char size_t fr_sbuff_t * in
Definition value.h:1061
#define fr_value_box_mark_safe_for(_box, _safe_for)
Definition value.h:1124
static fr_sbuff_err_t char size_t fr_sbuff_t size_t max
Definition value.h:1061
static fr_slen_t data
Definition value.h:1366
#define fr_box_strvalue_len(_val, _len)
Definition value.h:340
static fr_sbuff_err_t char ** out
Definition value.h:1061
static fr_sbuff_err_t char size_t * len
Definition value.h:1061
int nonnull(2, 5))
#define fr_value_box_init(_vb, _type, _enumv, _tainted)
Initialise a fr_value_box_t.
Definition value.h:634
#define FR_VALUE_BOX_SAFE_FOR_ANY
Definition value.h:178
An xlat calling ctx.
Definition xlat_ctx.h:49
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
Definition xlat_func.c:374
xlat_t * xlat_func_register(TALLOC_CTX *ctx, char const *name, xlat_func_t func, fr_type_t return_type)
Register an xlat function.
Definition xlat_func.c:225