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