The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 15b404eec48c4d3aec4f3e95435b9525a8d1fd0e $
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  */
26 RCSID("$Id: 15b404eec48c4d3aec4f3e95435b9525a8d1fd0e $")
27 
28 typedef 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/cf_util.h>
33 #include <freeradius-devel/server/command.h>
34 #include <freeradius-devel/server/dependency.h>
35 #include <freeradius-devel/server/dl_module.h>
36 #include <freeradius-devel/server/log.h>
37 #include <freeradius-devel/server/map.h>
38 #include <freeradius-devel/server/tmpl.h>
39 #ifdef WITH_TLS
40 # include <freeradius-devel/tls/base.h>
41 #endif
42 #include <freeradius-devel/unlang/base.h>
43 #include <freeradius-devel/unlang/xlat.h>
44 #include <freeradius-devel/unlang/xlat_func.h>
45 #include <freeradius-devel/util/atexit.h>
46 #include <freeradius-devel/util/base64.h>
47 #include <freeradius-devel/util/calc.h>
48 #include <freeradius-devel/util/conf.h>
49 #include <freeradius-devel/util/dns.h>
50 #include <freeradius-devel/util/file.h>
51 #include <freeradius-devel/util/log.h>
52 #include <freeradius-devel/util/pair_legacy.h>
53 #include <freeradius-devel/util/sha1.h>
54 #include <freeradius-devel/util/syserror.h>
55 
56 #include <ctype.h>
57 
58 #ifdef __clangd__
59 # undef HAVE_SANITIZER_LSAN_INTERFACE_H
60 #endif
61 #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
62 # include <sanitizer/asan_interface.h>
63 #endif
64 
65 #ifdef HAVE_GETOPT_H
66 # include <getopt.h>
67 #endif
68 
69 #include <assert.h>
70 #include <fcntl.h>
71 #include <libgen.h>
72 #include <limits.h>
73 #include <sys/file.h>
74 #include <sys/stat.h>
75 #include <sys/wait.h>
76 
77 #ifndef HAVE_SANITIZER_LSAN_INTERFACE_H
78 # define ASAN_POISON_MEMORY_REGION(_start, _end)
79 # define ASAN_UNPOISON_MEMORY_REGION(_start, _end)
80 #endif
81 
82 #define EXIT_WITH_FAILURE \
83 do { \
84  ret = EXIT_FAILURE; \
85  goto cleanup; \
86 } while (0)
87 
88 #define COMMAND_OUTPUT_MAX 8192
89 
90 #define RETURN_OK(_len) \
91  do { \
92  result->rcode = RESULT_OK; \
93  result->file = __FILE__; \
94  result->line = __LINE__; \
95  return (_len); \
96  } while (0)
97 
98 #define RETURN_OK_WITH_ERROR() \
99  do { \
100  result->rcode = RESULT_OK; \
101  result->file = __FILE__; \
102  result->line = __LINE__; \
103  result->error_to_data = true; \
104  return 0; \
105  } while (0)
106 
107 #define RETURN_NOOP(_len) \
108  do { \
109  result->rcode = RESULT_NOOP; \
110  result->file = __FILE__; \
111  result->line = __LINE__; \
112  return (_len); \
113  } while (0)
114 
115 #define RETURN_SKIP_FILE() \
116  do { \
117  result->rcode = RESULT_SKIP_FILE; \
118  result->file = __FILE__; \
119  result->line = __LINE__; \
120  return 0; \
121  } while (0)
122 
123 #define RETURN_PARSE_ERROR(_offset) \
124  do { \
125  result->rcode = RESULT_PARSE_ERROR; \
126  result->offset = _offset; \
127  result->file = __FILE__; \
128  result->line = __LINE__; \
129  return 0; \
130  } while (0)
131 
132 #define RETURN_COMMAND_ERROR() \
133  do { \
134  result->rcode = RESULT_COMMAND_ERROR; \
135  result->file = __FILE__; \
136  result->line = __LINE__; \
137  return 0; \
138  } while (0)
139 
140 #define RETURN_MISMATCH(_len) \
141  do { \
142  result->rcode = RESULT_MISMATCH; \
143  result->file = __FILE__; \
144  result->line = __LINE__; \
145  return (_len); \
146  } while (0)
147 
148 #define RETURN_EXIT(_ret) \
149  do { \
150  result->rcode = RESULT_EXIT; \
151  result->ret = _ret; \
152  result->file = __FILE__; \
153  result->line = __LINE__; \
154  return 0; \
155  } while (0)
156 
157 /** Default buffer size for a command_file_ctx_t
158  *
159  */
160 #define DEFAULT_BUFFER_SIZE 1024
161 
162 typedef enum {
163  RESULT_OK = 0, //!< Not an error - Result as expected.
164  RESULT_NOOP, //!< Not an error - Did nothing...
165  RESULT_SKIP_FILE, //!< Not an error - Skip the rest of this file, or until we
166  ///< reach an "eof" command.
167  RESULT_PARSE_ERROR, //!< Fatal error - Command syntax error.
168  RESULT_COMMAND_ERROR, //!< Fatal error - Command operation error.
169  RESULT_MISMATCH, //!< Fatal error - Result didn't match what we expected.
170  RESULT_EXIT, //!< Stop processing files and exit.
172 
174  { L("command-error"), RESULT_COMMAND_ERROR },
175  { L("exit"), RESULT_EXIT },
176  { L("ok"), RESULT_OK },
177  { L("parse-error"), RESULT_PARSE_ERROR },
178  { L("result-mismatch"), RESULT_MISMATCH },
179  { L("skip-file"), RESULT_SKIP_FILE },
180 };
182 
183 typedef struct {
184  TALLOC_CTX *tmp_ctx; //!< Temporary context to hold buffers
185  ///< in this
186  union {
187  size_t offset; //!< Where we failed parsing the command.
188  int ret; //!< What code we should exit with.
189  };
190  char const *file;
191  int line;
195 
196 /** Configuration parameters passed to command functions
197  *
198  */
199 typedef struct {
200  fr_dict_t *dict; //!< Dictionary to "reset" to.
201  fr_dict_gctx_t const *dict_gctx; //!< Dictionary gctx to "reset" to.
202  char const *raddb_dir;
203  char const *dict_dir;
204  char const *fuzzer_dir; //!< Where to write fuzzer files.
205  CONF_SECTION *features; //!< Enabled features.
207 
208 typedef struct {
209  TALLOC_CTX *tmp_ctx; //!< Talloc context for test points.
210 
211  char *path; //!< Current path we're operating in.
212  char const *filename; //!< Current file we're operating on.
213  uint32_t lineno; //!< Current line number.
214 
215  uint32_t test_count; //!< How many tests we've executed in this file.
216  ssize_t last_ret; //!< Last return value.
217 
218  uint8_t *buffer; //!< Temporary resizable buffer we use for
219  ///< holding non-string data.
220  uint8_t *buffer_start; //!< Where the non-poisoned region of the buffer starts.
221  uint8_t *buffer_end; //!< Where the non-poisoned region of the buffer ends.
222 
223  tmpl_rules_t tmpl_rules; //!< To pass to parsing functions.
224  fr_dict_t *test_internal_dict; //!< Internal dictionary of test_gctx.
225  fr_dict_gctx_t const *test_gctx; //!< Dictionary context for test dictionaries.
226 
227  int fuzzer_dir; //!< File descriptor pointing to a a directory to
228  ///< write fuzzer output.
231 
232 
233 typedef struct {
234  fr_dlist_t entry; //!< Entry in the dlist.
235  uint32_t start; //!< Start of line range.
236  uint32_t end; //!< End of line range.
238 
239 /** Command to execute
240  *
241  * @param[out] result Of executing the command.
242  * @param[in] cc Information about the file being processed.
243  * @param[in,out] data Output of this command, or the previous command.
244  * @param[in] data_used Length of data in the data buffer.
245  * @param[in] in Command text to process.
246  * @param[in] inlen Length of the remainder of the command to process.
247  */
249  size_t data_used, char *in, size_t inlen);
250 
251 typedef struct {
253  char const *usage;
254  char const *description;
256 
258  UNUSED xlat_ctx_t const *xctx, UNUSED request_t *request,
259  UNUSED fr_value_box_list_t *in)
260 {
261  return XLAT_ACTION_DONE;
262 }
263 
264 static char proto_name_prev[128];
265 static dl_t *dl;
266 static dl_loader_t *dl_loader = NULL;
267 
268 static fr_event_list_t *el = NULL;
269 
270 size_t process_line(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen);
271 static int process_file(bool *exit_now, TALLOC_CTX *ctx,
272  command_config_t const *config, const char *root_dir, char const *filename, fr_dlist_head_t *lines);
273 
274 #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
275 # define BUFF_POISON_START 1024
276 # define BUFF_POISON_END 1024
277 
278 /** Unpoison the start and end regions of the buffer
279  *
280  */
281 static int _free_buffer(uint8_t *buff)
282 {
283  size_t size = talloc_array_length(buff) - (BUFF_POISON_START + BUFF_POISON_END);
284 
287 
288  return 0;
289 }
290 #else
291 # define BUFF_POISON_START 0
292 # define BUFF_POISON_END 0
293 #endif
294 
295 /** Allocate a special buffer with poisoned memory regions at the start and end
296  *
297  */
298 static int poisoned_buffer_allocate(TALLOC_CTX *ctx, uint8_t **buff, size_t size)
299 {
300  uint8_t *our_buff = *buff;
301 
302  if (our_buff) {
303  /*
304  * If it's already the correct length
305  * don't bother re-allocing the buffer,
306  * just memset it to zero.
307  */
308  if ((size + BUFF_POISON_START + BUFF_POISON_END) == talloc_array_length(our_buff)) {
309  memset(our_buff + BUFF_POISON_START, 0, size);
310  return 0;
311  }
312 
313  talloc_free(our_buff); /* Destructor de-poisons */
314  *buff = NULL;
315  }
316 
317  our_buff = talloc_array(ctx, uint8_t, size + BUFF_POISON_START + BUFF_POISON_END);
318  if (!our_buff) return -1;
319 
320 #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
321  talloc_set_destructor(our_buff, _free_buffer);
322 
323  /*
324  * Poison regions before and after the buffer
325  */
328 #endif
329 
330  *buff = our_buff;
331 
332  return 0;
333 }
334 #define POISONED_BUFFER_START(_p) ((_p) + BUFF_POISON_START)
335 #define POISONED_BUFFER_END(_p) ((_p) + BUFF_POISON_START + (talloc_array_length(_p) - (BUFF_POISON_START + BUFF_POISON_END)))
336 
337 static void mismatch_print(command_file_ctx_t *cc, char const *command,
338  char *expected, size_t expected_len, char *got, size_t got_len,
339  bool print_diff)
340 {
341  char *g, *e;
342  char *spaces;
343 
344  ERROR("%s failed %s/%s:%d", command, cc->path, cc->filename, cc->lineno);
345  ERROR(" got : %.*s", (int) got_len, got);
346  ERROR(" expected : %.*s", (int) expected_len, expected);
347 
348  if (print_diff) {
349  g = got;
350  e = expected;
351 
352  while (*g && *e && (*g == *e)) {
353  g++;
354  e++;
355  }
356 
357  spaces = talloc_zero_array(NULL, char, (e - expected) + 1);
358  memset(spaces, ' ', talloc_array_length(spaces) - 1);
359  if (((e - expected) < 80) && (expected_len < 80)) {
360  ERROR(" %s^ differs here", spaces);
361  } else {
362  ERROR(" %s^ differs here (%zu) ... %.*s ...", spaces, e - expected,
363  16, e);
364  }
366  }
367 }
368 
369 /** Print hex string to buffer
370  *
371  */
372 static inline CC_HINT(nonnull) size_t hex_print(char *out, size_t outlen, uint8_t const *in, size_t inlen)
373 {
374  char *p = out;
375  char *end = p + outlen;
376  size_t i;
377 
378  if (inlen == 0) {
379  *p = '\0';
380  return 0;
381  }
382 
383  for (i = 0; i < inlen; i++) {
384  size_t len;
385 
386  len = snprintf(p, end - p, "%02x ", in[i]);
387  if (is_truncated(len, end - p)) return 0;
388 
389  p += len;
390  }
391 
392  *(--p) = '\0';
393 
394  return p - out;
395 }
396 
397 /** Concatenate error stack
398  */
399 static inline size_t strerror_concat(char *out, size_t outlen)
400 {
401  char *end = out + outlen;
402  char *p = out;
403  char const *err;
404 
405  while ((p < end) && (err = fr_strerror_pop())) {
406  if (*fr_strerror_peek()) {
407  p += snprintf(p, end - p, "%s: ", err);
408  } else {
409  p += strlcpy(p, err, end - p);
410  }
411  }
412 
413  return p - out;
414 }
415 
416 static inline CC_HINT(nonnull) int dump_fuzzer_data(int fd_dir, char const *text, uint8_t const *data, size_t data_len)
417 {
418  fr_sha1_ctx ctx;
419  uint8_t digest[SHA1_DIGEST_LENGTH];
420  char digest_str[(SHA1_DIGEST_LENGTH * 2) + 1];
421  int file_fd;
422 
423  fr_sha1_init(&ctx);
424  fr_sha1_update(&ctx, (uint8_t const *)text, strlen(text));
425  fr_sha1_final(digest, &ctx);
426 
427  /*
428  * We need to use the url alphabet as the standard
429  * one contains forwarded slashes which openat
430  * doesn't like.
431  */
432  fr_base64_encode_nstd(&FR_SBUFF_OUT(digest_str, sizeof(digest_str)), &FR_DBUFF_TMP(digest, sizeof(digest)),
434 
435  file_fd = openat(fd_dir, digest_str, O_RDWR | O_CREAT | O_TRUNC,
436  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
437  if (file_fd < 0) {
438  fr_strerror_printf("Failed opening or creating corpus seed file \"%s\": %s",
439  digest_str, fr_syserror(errno));
440  return -1;
441  }
442 
443  if (flock(file_fd, LOCK_EX) < 0) {
444  fr_strerror_printf("Failed locking corpus seed file \"%s\": %s",
445  digest_str, fr_syserror(errno));
446  return -1;
447  }
448 
449  while (data_len) {
450  ssize_t ret;
451 
452  ret = write(file_fd, data, data_len);
453  if (ret < 0) {
454  fr_strerror_printf("Failed writing to corpus seed file \"%s\": %s",
455  digest_str, fr_syserror(errno));
456  (void)flock(file_fd, LOCK_UN);
457  unlinkat(fd_dir, digest_str, 0);
458  return -1;
459  }
460  data_len -= ret;
461  data += ret;
462  }
463  (void)flock(file_fd, LOCK_UN);
464 
465  return 0;
466 }
467 
468 /*
469  * End of hacks for xlat
470  *
471  **********************************************************************/
472 
473 static ssize_t encode_tlv(char *buffer, uint8_t *output, size_t outlen);
474 
475 static char const hextab[] = "0123456789abcdef";
476 
477 static ssize_t encode_data_string(char *buffer, uint8_t *output, size_t outlen)
478 {
479  ssize_t slen = 0;
480  char *p;
481 
482  p = buffer + 1;
483 
484  while (*p && (outlen > 0)) {
485  if (*p == '"') {
486  return slen;
487  }
488 
489  if (*p != '\\') {
490  *(output++) = *(p++);
491  outlen--;
492  slen++;
493  continue;
494  }
495 
496  switch (p[1]) {
497  default:
498  *(output++) = p[1];
499  break;
500 
501  case 'n':
502  *(output++) = '\n';
503  break;
504 
505  case 'r':
506  *(output++) = '\r';
507  break;
508 
509  case 't':
510  *(output++) = '\t';
511  break;
512  }
513 
514  outlen--;
515  slen++;
516  }
517 
518  ERROR("String is not terminated");
519  return 0;
520 }
521 
522 static ssize_t encode_data_tlv(char *buffer, char **endptr, uint8_t *output, size_t outlen)
523 {
524  int depth = 0;
525  ssize_t slen;
526  char *p;
527 
528  for (p = buffer; *p != '\0'; p++) {
529  if (*p == '{') depth++;
530  if (*p == '}') {
531  depth--;
532  if (depth == 0) break;
533  }
534  }
535 
536  if (*p != '}') {
537  ERROR("No trailing '}' in string starting with \"%s\"", buffer);
538  return 0;
539  }
540 
541  *endptr = p + 1;
542  *p = '\0';
543 
544  p = buffer + 1;
546 
547  slen = encode_tlv(p, output, outlen);
548  if (slen <= 0) return 0;
549 
550  return slen;
551 }
552 
553 static ssize_t hex_to_bin(uint8_t *out, size_t outlen, char *in, size_t inlen)
554 {
555  char *p = in;
556  char *end = in + inlen;
557  uint8_t *out_p = out, *out_end = out_p + outlen;
558 
559  while (p < end) {
560  char *c1, *c2;
561 
562  if (out_p >= out_end) {
563  fr_strerror_const("Would overflow output buffer");
564  return -(p - in);
565  }
566 
568 
569  if (!*p) break;
570 
571  c1 = memchr(hextab, tolower((uint8_t) *p++), sizeof(hextab));
572  if (!c1) {
573  bad_input:
574  fr_strerror_printf("Invalid hex data starting at \"%s\"", p);
575  return -(p - in);
576  }
577 
578  c2 = memchr(hextab, tolower((uint8_t)*p++), sizeof(hextab));
579  if (!c2) goto bad_input;
580 
581  *out_p++ = ((c1 - hextab) << 4) + (c2 - hextab);
582  }
583 
584  return out_p - out;
585 }
586 
587 
588 static ssize_t encode_data(char *p, uint8_t *output, size_t outlen)
589 {
590  ssize_t slen;
591 
592  if (!isspace((uint8_t) *p)) {
593  ERROR("Invalid character following attribute definition");
594  return 0;
595  }
596 
598 
599  if (*p == '{') {
600  size_t sublen;
601  char *q;
602 
603  slen = 0;
604 
605  do {
607  if (!*p) {
608  if (slen == 0) {
609  ERROR("No data");
610  return 0;
611  }
612 
613  break;
614  }
615 
616  sublen = encode_data_tlv(p, &q, output, outlen);
617  if (sublen <= 0) return 0;
618 
619  slen += sublen;
620  output += sublen;
621  outlen -= sublen;
622  p = q;
623  } while (*q);
624 
625  return slen;
626  }
627 
628  if (*p == '"') {
629  slen = encode_data_string(p, output, outlen);
630  return slen;
631  }
632 
633  slen = hex_to_bin(output, outlen, p, strlen(p));
634  if (slen <= 0) {
635  fr_strerror_const_push("Empty hex string");
636  return slen;
637  }
638 
639  return slen;
640 }
641 
642 static int decode_attr(char *buffer, char **endptr)
643 {
644  long attr;
645 
646  attr = strtol(buffer, endptr, 10);
647  if (*endptr == buffer) {
648  ERROR("No valid number found in string starting with \"%s\"", buffer);
649  return 0;
650  }
651 
652  if (!**endptr) {
653  ERROR("Nothing follows attribute number");
654  return 0;
655  }
656 
657  if ((attr <= 0) || (attr > 256)) {
658  ERROR("Attribute number is out of valid range");
659  return 0;
660  }
661 
662  return (int) attr;
663 }
664 
665 static int decode_vendor(char *buffer, char **endptr)
666 {
667  long vendor;
668 
669  if (*buffer != '.') {
670  ERROR("Invalid separator before vendor id");
671  return 0;
672  }
673 
674  vendor = strtol(buffer + 1, endptr, 10);
675  if (*endptr == (buffer + 1)) {
676  ERROR("No valid vendor number found");
677  return 0;
678  }
679 
680  if (!**endptr) {
681  ERROR("Nothing follows vendor number");
682  return 0;
683  }
684 
685  if ((vendor <= 0) || (vendor > (1 << 24))) {
686  ERROR("Vendor number is out of valid range");
687  return 0;
688  }
689 
690  if (**endptr != '.') {
691  ERROR("Invalid data following vendor number");
692  return 0;
693  }
694  (*endptr)++;
695 
696  return (int) vendor;
697 }
698 
699 static ssize_t encode_tlv(char *buffer, uint8_t *output, size_t outlen)
700 {
701  int attr;
702  ssize_t slen;
703  char *p;
704 
705  attr = decode_attr(buffer, &p);
706  if (attr == 0) return 0;
707 
708  output[0] = attr;
709  output[1] = 2;
710 
711  if (*p == '.') {
712  p++;
713  slen = encode_tlv(p, output + 2, outlen - 2);
714 
715  } else {
716  slen = encode_data(p, output + 2, outlen - 2);
717  }
718 
719  if (slen <= 0) return slen;
720  if (slen > (255 - 2)) {
721  ERROR("TLV data is too long");
722  return 0;
723  }
724 
725  output[1] += slen;
726 
727  return slen + 2;
728 }
729 
730 static ssize_t encode_vsa(char *buffer, uint8_t *output, size_t outlen)
731 {
732  int vendor;
733  ssize_t slen;
734  char *p;
735 
736  vendor = decode_vendor(buffer, &p);
737  if (vendor == 0) return 0;
738 
739  output[0] = 0;
740  output[1] = (vendor >> 16) & 0xff;
741  output[2] = (vendor >> 8) & 0xff;
742  output[3] = vendor & 0xff;
743 
744  slen = encode_tlv(p, output + 4, outlen - 4);
745  if (slen <= 0) return slen;
746  if (slen > (255 - 6)) {
747  ERROR("VSA data is too long");
748  return 0;
749  }
750 
751  return slen + 4;
752 }
753 
754 static ssize_t encode_evs(char *buffer, uint8_t *output, size_t outlen)
755 {
756  int vendor;
757  int attr;
758  ssize_t slen;
759  char *p;
760 
761  vendor = decode_vendor(buffer, &p);
762  if (vendor == 0) return 0;
763 
764  attr = decode_attr(p, &p);
765  if (attr == 0) return 0;
766 
767  output[0] = 0;
768  output[1] = (vendor >> 16) & 0xff;
769  output[2] = (vendor >> 8) & 0xff;
770  output[3] = vendor & 0xff;
771  output[4] = attr;
772 
773  slen = encode_data(p, output + 5, outlen - 5);
774  if (slen <= 0) return slen;
775 
776  return slen + 5;
777 }
778 
779 static ssize_t encode_extended(char *buffer, uint8_t *output, size_t outlen)
780 {
781  int attr;
782  ssize_t slen;
783  char *p;
784 
785  attr = decode_attr(buffer, &p);
786  if (attr == 0) return 0;
787 
788  output[0] = attr;
789 
790  if (attr == 26) {
791  slen = encode_evs(p, output + 1, outlen - 1);
792  } else {
793  slen = encode_data(p, output + 1, outlen - 1);
794  }
795  if (slen <= 0) return slen;
796  if (slen > (255 - 3)) {
797  ERROR("Extended Attr data is too long");
798  return 0;
799  }
800 
801  return slen + 1;
802 }
803 
804 static ssize_t encode_long_extended(char *buffer, uint8_t *output, size_t outlen)
805 {
806  int attr;
807  ssize_t slen, total;
808  char *p;
809 
810  attr = decode_attr(buffer, &p);
811  if (attr == 0) return 0;
812 
813  /* output[0] is the extended attribute */
814  output[1] = 4;
815  output[2] = attr;
816  output[3] = 0;
817 
818  if (attr == 26) {
819  slen = encode_evs(p, output + 4, outlen - 4);
820  if (slen <= 0) return slen;
821 
822  output[1] += 5;
823  slen -= 5;
824  } else {
825  slen = encode_data(p, output + 4, outlen - 4);
826  }
827  if (slen <= 0) return slen;
828 
829  total = 0;
830  while (1) {
831  int sublen = 255 - output[1];
832 
833  if (slen <= sublen) {
834  output[1] += slen;
835  total += output[1];
836  break;
837  }
838 
839  slen -= sublen;
840 
841  memmove(output + 255 + 4, output + 255, slen);
842  memcpy(output + 255, output, 4);
843 
844  output[1] = 255;
845  output[3] |= 0x80;
846 
847  output += 255;
848  output[1] = 4;
849  total += 255;
850  }
851 
852  return total;
853 }
854 
855 static ssize_t encode_rfc(char *buffer, uint8_t *output, size_t outlen)
856 {
857  int attr;
858  ssize_t slen, sublen;
859  char *p;
860 
861  attr = decode_attr(buffer, &p);
862  if (attr == 0) return 0;
863 
864  slen = 2;
865  output[0] = attr;
866  output[1] = 2;
867 
868  if (attr == 26) {
869  sublen = encode_vsa(p, output + 2, outlen - 2);
870 
871  } else if ((attr < 241) || (attr > 246)) {
872  sublen = encode_data(p, output + 2, outlen - 2);
873 
874  } else {
875  if (*p != '.') {
876  ERROR("Invalid data following attribute number");
877  return 0;
878  }
879 
880  if (attr < 245) {
881  sublen = encode_extended(p + 1, output + 2, outlen - 2);
882  } else {
883  /*
884  * Not like the others!
885  */
886  return encode_long_extended(p + 1, output, outlen);
887  }
888  }
889  if (sublen <= 0) return sublen;
890  if (sublen > (255 -2)) {
891  ERROR("RFC Data is too long");
892  return 0;
893  }
894 
895  output[1] += sublen;
896  return slen + sublen;
897 }
898 
899 
900 static void unload_proto_library(void)
901 {
902  TALLOC_FREE(dl);
903 }
904 
905 static ssize_t load_proto_library(char const *proto_name)
906 {
907  char dl_name[128];
908 
909  if (strcmp(proto_name_prev, proto_name) != 0) {
910  /*
911  * Ensure the old proto library is unloaded
912  */
914 
915  snprintf(dl_name, sizeof(dl_name), "libfreeradius-%s", proto_name);
916  if (dl) TALLOC_FREE(dl);
917 
918  dl = dl_by_name(dl_loader, dl_name, NULL, false);
919  if (!dl) {
920  fr_perror("Failed to link to library \"%s\"", dl_name);
922  return 0;
923  }
924 
925  strlcpy(proto_name_prev, proto_name, sizeof(proto_name_prev));
926  }
927 
928  return strlen(proto_name);
929 }
930 
931 static ssize_t load_test_point_by_command(void **symbol, char *command, char const *dflt_symbol)
932 {
933  char buffer[256];
934  char const *p, *q;
935  void *dl_symbol;
936 
937  if (!dl) {
938  fr_strerror_printf("No protocol library loaded. Specify library with \"load <proto name>\"");
939  return 0;
940  }
941 
942  p = command;
943 
944  /*
945  * Use the dflt_symbol name as the test point
946  */
947  if ((*p == '.') && (q = strchr(p, ' ')) && (q != (p + 1)) && ((size_t)(q - p) < sizeof(buffer))) {
948  p++;
949  strlcpy(buffer, p, (q - p) + 1);
950  p = q + 1;
951  } else {
952  snprintf(buffer, sizeof(buffer), "%s_%s", proto_name_prev, dflt_symbol);
953  }
954 
955  dl_symbol = dlsym(dl->handle, buffer);
956  if (!dl_symbol) {
957  fr_strerror_printf("Test point (symbol \"%s\") not exported by library", buffer);
959  return 0;
960  }
961  *symbol = dl_symbol;
962 
963  return p - command;
964 }
965 
966 /** Common dictionary load function
967  *
968  * Callers call fr_dict_global_ctx_set to set the context
969  * the dictionaries will be loaded into.
970  */
971 static int dictionary_load_common(command_result_t *result, command_file_ctx_t *cc, char *in, char const *default_subdir)
972 {
973  char *name, *tmp = NULL;
974  char const *dir;
975  char *q;
976  int ret;
977  fr_dict_t *dict;
978 
979  if (in[0] == '\0') {
980  fr_strerror_const("Missing dictionary name");
982  }
983 
984  /*
985  * Decrease ref count if we're loading in a new dictionary
986  */
987  if (cc->tmpl_rules.attr.dict_def) {
989  }
990 
991  q = strchr(in, ' ');
992  if (q) {
993  name = tmp = talloc_bstrndup(NULL, in, q - in);
994  q++;
995  dir = q;
996  } else {
997  name = in;
998  dir = default_subdir;
999  }
1000 
1001  ret = fr_dict_protocol_afrom_file(&dict, name, dir, __FILE__);
1002  talloc_free(tmp);
1003  if (ret < 0) RETURN_COMMAND_ERROR();
1004 
1005  cc->tmpl_rules.attr.dict_def = dict;
1006 
1007  /*
1008  * Dump the dictionary if we're in super debug mode
1009  */
1011 
1012  RETURN_OK(0);
1013 }
1014 
1015 static size_t parse_typed_value(command_result_t *result, fr_value_box_t *box, char const **out, char const *in, size_t inlen)
1016 {
1017  fr_type_t type;
1018  size_t match_len;
1019  ssize_t slen;
1020  char const *p;
1021  fr_sbuff_t sbuff;
1022 
1023  /*
1024  * Parse data types
1025  */
1027  if (fr_type_is_null(type)) {
1028  RETURN_PARSE_ERROR(0);
1029  }
1030  fr_assert(match_len < inlen);
1031 
1032  p = in + match_len;
1033  fr_skip_whitespace(p);
1034  *out = p;
1035 
1036  /*
1037  * As a hack, allow most things to be inside
1038  * double-quoted strings. This is really only for dates,
1039  * which are space-delimited.
1040  */
1041  if (*p == '"'){
1042  p++;
1043  sbuff = FR_SBUFF_IN(p, strlen(p));
1044  slen = fr_value_box_from_substr(box, box, FR_TYPE_STRING, NULL,
1045  &sbuff,
1047  false);
1048  if (slen < 0) {
1050  }
1051 
1052  p += fr_sbuff_used(&sbuff);
1053  if (*p != '"') {
1054  RETURN_PARSE_ERROR(0);
1055  }
1056  p++;
1057 
1058  if (type != FR_TYPE_STRING) {
1059  if (fr_value_box_cast_in_place(box, box, type, NULL) < 0) {
1060  RETURN_PARSE_ERROR(0);
1061  }
1062  }
1063 
1064  } else {
1065  sbuff = FR_SBUFF_IN(p, strlen(p));
1066 
1067  slen = fr_value_box_from_substr(box, box, type, NULL,
1068  &sbuff,
1070  false);
1071  if (slen < 0) {
1073  }
1074  p += fr_sbuff_used(&sbuff);
1075  }
1076  fr_skip_whitespace(p);
1077 
1078  RETURN_OK(p - in);
1079 }
1080 
1081 static fr_cmd_t *command_head = NULL;
1082 
1083 static int command_func(UNUSED FILE *fp, UNUSED FILE *fp_err, UNUSED void *ctx, UNUSED fr_cmd_info_t const *info)
1084 {
1085  return 0;
1086 }
1087 
1088 static int command_walk(UNUSED void *ctx, fr_cmd_walk_info_t *info)
1089 {
1090  int i;
1091 
1092  for (i = 0; i < info->num_parents; i++) {
1093  printf("%s ", info->parents[i]);
1094  }
1095 
1096  printf(":%s ", info->name);
1097  if (info->syntax) printf("%s", info->syntax);
1098  printf("%s", "");
1099 
1100  return 1;
1101 }
1102 
1103 static void command_print(void)
1104 {
1105  void *walk_ctx = NULL;
1106 
1107  printf("Command hierarchy --------");
1108  fr_command_debug(stdout, command_head);
1109 
1110  printf("Command list --------");
1111  while (fr_command_walk(command_head, &walk_ctx, NULL, command_walk) == 1) {
1112  // do nothing
1113  }
1114 }
1115 
1116 #define CLEAR_TEST_POINT(_cc) \
1117 do { \
1118  talloc_free_children((_cc)->tmp_ctx); \
1119  tp = NULL; \
1120 } while (0)
1121 
1122 /** Placeholder function for comments
1123  *
1124  */
1126  UNUSED char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
1127 {
1128  return 0;
1129 }
1130 
1131 /** Execute another test file
1132  *
1133  */
1135  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
1136 {
1137  char *q;
1138  bool exit_now = false;
1139  int ret;
1140 
1141  q = strrchr(cc->path, '/');
1142  if (q) {
1143  *q = '\0';
1144  ret = process_file(&exit_now, cc->tmp_ctx, cc->config, cc->path, in, NULL);
1145  if (exit_now || (ret != 0)) RETURN_EXIT(ret);
1146  *q = '/';
1147  RETURN_OK(0);
1148  }
1149 
1150  ret = process_file(&exit_now, cc->tmp_ctx, cc->config, NULL, in, NULL);
1151  if (exit_now || (ret != 0)) RETURN_EXIT(ret);
1152 
1153  RETURN_OK(0);
1154 }
1155 
1156 /** Determine if unresolved attributes are allowed
1157  *
1158  */
1160  UNUSED char *data, UNUSED size_t data_used, char *in, size_t inlen)
1161 {
1162  fr_sbuff_t our_in = FR_SBUFF_IN(in, inlen);
1163  bool res;
1164 
1165  if (fr_sbuff_out_bool(&res, &our_in) == 0) {
1166  fr_strerror_printf("Invalid boolean value, must be \"yes\" or \"no\"");
1168  }
1169  cc->tmpl_rules.attr.allow_unresolved = res;
1170 
1171  RETURN_OK(0);
1172 }
1173 
1174 static const fr_token_t token2op[UINT8_MAX + 1] = {
1175  [ '+' ] = T_ADD,
1176  [ '-' ] = T_SUB,
1177  [ '*' ] = T_MUL,
1178  [ '/' ] = T_DIV,
1179  [ '^' ] = T_XOR,
1180  [ '.' ] = T_ADD,
1181  [ '&' ] = T_AND,
1182  [ '|' ] = T_OR,
1183  [ '%' ] = T_MOD,
1184 };
1185 
1186 /** Perform calculations
1187  *
1188  */
1190  char *data, UNUSED size_t data_used, char *in, size_t inlen)
1191 {
1192  fr_value_box_t *a, *b, *out;
1193  size_t match_len;
1194  fr_type_t type;
1195  fr_token_t op;
1196  char const *p, *value, *end;
1197  size_t slen;
1198  bool assignment;
1199 
1200  a = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1201  b = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1202 
1203  p = in;
1204  end = in + inlen;
1205 
1206  match_len = parse_typed_value(result, a, &value, p, end - p);
1207  if (match_len == 0) return 0; /* errors have already been updated */
1208 
1209  p += match_len;
1210  fr_skip_whitespace(p);
1211 
1212  op = fr_table_value_by_longest_prefix(&match_len, fr_tokens_table, p, end - p, T_INVALID);
1213  if (op != T_INVALID) {
1214  p += match_len;
1215  assignment = fr_assignment_op[op];
1216 
1217  } else {
1218  op = token2op[(uint8_t) p[0]];
1219  if (op == T_INVALID) {
1220  fr_strerror_printf("Unknown operator '%c'", p[0]);
1221  RETURN_PARSE_ERROR(0);
1222  }
1223  p++;
1224 
1225  assignment = false;
1226  }
1227  fr_skip_whitespace(p);
1228 
1229  match_len = parse_typed_value(result, b, &value, p, end - p);
1230  if (match_len == 0) return 0;
1231 
1232  p += match_len;
1233  fr_skip_whitespace(p);
1234 
1235  if (assignment) {
1236  if (fr_value_calc_assignment_op(cc->tmp_ctx, a, op, b) < 0) {
1238  }
1239  out = a;
1240 
1241  } else {
1242  out = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1243 
1244  /*
1245  * If there's no output data type, then the code tries to
1246  * figure one out automatically.
1247  */
1248  if (!*p) {
1249  type = FR_TYPE_NULL;
1250  } else {
1251  if (strncmp(p, "->", 2) != 0) RETURN_PARSE_ERROR(0);
1252  p += 2;
1253  fr_skip_whitespace(p);
1254 
1255  type = fr_table_value_by_longest_prefix(&match_len, fr_type_table, p, end - p, FR_TYPE_MAX);
1256  if (type == FR_TYPE_MAX) RETURN_PARSE_ERROR(0);
1257  fr_value_box_init(out, type, NULL, false);
1258  }
1259 
1260  if (fr_value_calc_binary_op(cc->tmp_ctx, out, type, a, op, b) < 0) {
1262  }
1263  }
1264 
1266  if (slen <= 0) RETURN_OK_WITH_ERROR();
1267 
1268  RETURN_OK(slen);
1269 }
1270 
1271 /** Perform calculations on multi-valued ops
1272  *
1273  */
1275  char *data, UNUSED size_t data_used, char *in, size_t inlen)
1276 {
1277  fr_value_box_t *group, *a, *out;
1278  size_t match_len;
1279  fr_type_t type;
1280  fr_token_t op;
1281  char const *p, *value, *end;
1282  size_t slen;
1283 
1284  group = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1285  fr_value_box_init(group, FR_TYPE_GROUP, NULL, false);
1286 
1287  p = in;
1288  end = in + inlen;
1289 
1290  /*
1291  * Multi-valued operations
1292  */
1293  op = token2op[(uint8_t) p[0]];
1294  if (op == T_INVALID) {
1295  fr_strerror_printf("Unknown operator '%c'", p[0]);
1296  RETURN_PARSE_ERROR(0);
1297  }
1298  p++;
1299 
1300  while (p < end) {
1301  fr_skip_whitespace(p);
1302 
1303  a = talloc_zero(group, fr_value_box_t);
1304 
1305  match_len = parse_typed_value(result, a, &value, p, end - p);
1306  if (match_len == 0) return 0; /* errors have already been updated */
1307 
1308  fr_value_box_list_insert_tail(&group->vb_group, a);
1309 
1310  p += match_len;
1311 
1312  if (strncmp(p, "->", 2) == 0) break;
1313  }
1314 
1315  out = talloc_zero(cc->tmp_ctx, fr_value_box_t);
1316 
1317  if (strncmp(p, "->", 2) != 0) RETURN_PARSE_ERROR(0);
1318  p += 2;
1319  fr_skip_whitespace(p);
1320 
1321  type = fr_table_value_by_longest_prefix(&match_len, fr_type_table, p, end - p, FR_TYPE_MAX);
1322  if (type == FR_TYPE_MAX) RETURN_PARSE_ERROR(0);
1323 
1324 
1325  if (fr_value_calc_nary_op(cc->tmp_ctx, out, type, op, group) < 0) {
1327  }
1328 
1330  if (slen <= 0) RETURN_OK_WITH_ERROR();
1331 
1332  RETURN_OK(slen);
1333 }
1334 
1335 /** Change the working directory
1336  *
1337  */
1339  char *data, UNUSED size_t data_used, char *in, size_t inlen)
1340 {
1341  TALLOC_FREE(cc->path); /* Free old directories */
1342 
1343  cc->path = fr_realpath(cc->tmp_ctx, in, inlen);
1344  if (!cc->path) RETURN_COMMAND_ERROR();
1345 
1347 
1348  RETURN_OK(talloc_array_length(cc->path) - 1);
1349 }
1350 
1351 /*
1352  * Clear the data buffer
1353  */
1355  char *data, size_t UNUSED data_used, UNUSED char *in, UNUSED size_t inlen)
1356 {
1357  memset(data, 0, COMMAND_OUTPUT_MAX);
1358  RETURN_NOOP(0);
1359 }
1360 
1361 /*
1362  * Add a command by talloc'ing a table for it.
1363  */
1365  char *data, size_t UNUSED data_used, char *in, UNUSED size_t inlen)
1366 {
1367  char *p, *name;
1368  char *parent = NULL;
1369  fr_cmd_table_t *table;
1370 
1371  table = talloc_zero(cc->tmp_ctx, fr_cmd_table_t);
1372 
1373  p = strchr(in, ':');
1374  if (!p) {
1375  fr_strerror_const("no ':name' specified");
1376  RETURN_PARSE_ERROR(0);
1377  }
1378 
1379  *p = '\0';
1380  p++;
1381 
1382  parent = talloc_strdup(cc->tmp_ctx, in);
1383 
1384  /*
1385  * Set the name and try to find the syntax.
1386  */
1387  name = p;
1388  fr_skip_whitespace(p);
1389 
1390  if (isspace((uint8_t) *p)) {
1391  *p = '\0';
1392  p++;
1393  }
1394 
1395  fr_skip_whitespace(p);
1396 
1397  if (*p) {
1398  table->syntax = talloc_strdup(table, p);
1399  }
1400  table->parent = parent;
1401  table->name = name;
1402  table->help = NULL;
1403  table->func = command_func;
1404  table->tab_expand = NULL;
1405  table->read_only = true;
1406 
1407  if (fr_command_add(table, &command_head, NULL, NULL, table) < 0) {
1408  fr_strerror_const_push("ERROR: Failed adding command");
1410  }
1411 
1412  if (fr_debug_lvl) command_print();
1413 
1415 }
1416 
1417 /*
1418  * Do tab completion on a command
1419  */
1421  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
1422 {
1423  int i;
1424  int num_expansions;
1425  char const *expansions[CMD_MAX_ARGV];
1426  char *p = data, *end = p + COMMAND_OUTPUT_MAX, **argv;
1427  fr_cmd_info_t info;
1428  size_t len;
1429 
1430  info.argc = 0;
1431  info.max_argc = CMD_MAX_ARGV;
1432  info.argv = talloc_zero_array(cc->tmp_ctx, char const *, CMD_MAX_ARGV);
1433  info.box = talloc_zero_array(cc->tmp_ctx, fr_value_box_t *, CMD_MAX_ARGV);
1434 
1435  memcpy(&argv, &info.argv, sizeof(argv)); /* const issues */
1436  info.argc = fr_dict_str_to_argv(in, argv, CMD_MAX_ARGV);
1437  if (info.argc <= 0) {
1438  fr_strerror_const("Failed splitting input");
1439  RETURN_PARSE_ERROR(-(info.argc));
1440  }
1441 
1442  num_expansions = fr_command_tab_expand(cc->tmp_ctx, command_head, &info, CMD_MAX_ARGV, expansions);
1443 
1444  len = snprintf(p, end - p, "%d - ", num_expansions);
1445  if (is_truncated(len, end - p)) {
1446  oob:
1447  fr_strerror_const("Out of output buffer space for radmin command");
1449  }
1450  p += len;
1451 
1452  for (i = 0; i < num_expansions; i++) {
1453  len = snprintf(p, end - p, "'%s', ", expansions[i]);
1454  if (is_truncated(len, end - p)) goto oob;
1455  p += len;
1456  }
1457 
1458  /*
1459  * Remove the trailing ", "
1460  */
1461  if (num_expansions > 0) {
1462  p -= 2;
1463  *p = '\0';
1464  }
1465 
1466  return p - data;
1467 }
1468 
1469 /** Parse and reprint a condition
1470  *
1471  */
1473  char *data, UNUSED size_t data_used, char *in, size_t inlen)
1474 {
1475  ssize_t slen;
1476  CONF_SECTION *cs;
1477  size_t len;
1478  xlat_exp_head_t *head = NULL;
1479 
1480  cs = cf_section_alloc(NULL, NULL, "if", "condition");
1481  if (!cs) {
1482  fr_strerror_const("Out of memory");
1484  }
1485  cf_filename_set(cs, cc->filename);
1486  cf_lineno_set(cs, cc->lineno);
1487 
1489 
1490  slen = xlat_tokenize_condition(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, inlen), NULL, &cc->tmpl_rules);
1491  if (slen <= 0) {
1492  fr_strerror_printf_push_head("ERROR offset %d", (int) -slen);
1493  talloc_free(cs);
1495  }
1497 
1498  talloc_free(head);
1499  talloc_free(cs);
1500 
1501  RETURN_OK(len);
1502 }
1503 
1505  char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
1506 {
1507  size_t len;
1508 
1509  len = snprintf(data, COMMAND_OUTPUT_MAX, "%u", cc->test_count);
1510  if (is_truncated(len, COMMAND_OUTPUT_MAX)) {
1511  fr_strerror_const("Command count would overflow data buffer (shouldn't happen)");
1513  }
1514 
1515  RETURN_OK(len);
1516 }
1517 
1519  char *data, size_t data_used, char *in, size_t inlen)
1520 {
1521  fr_test_point_pair_decode_t *tp = NULL;
1522  void *decode_ctx = NULL;
1523  char *p;
1524  uint8_t *to_dec;
1525  uint8_t *to_dec_end;
1526  ssize_t slen;
1527 
1528  fr_dict_attr_t const *da;
1529  fr_pair_t *head;
1530 
1531  da = fr_dict_attr_by_name(NULL, fr_dict_root(fr_dict_internal()), "request");
1532  fr_assert(da != NULL);
1533  head = fr_pair_afrom_da(cc->tmp_ctx, da);
1534  if (!head) {
1535  fr_strerror_const_push("Failed allocating memory");
1537  }
1538 
1539  p = in;
1540 
1541  slen = load_test_point_by_command((void **)&tp, in, "tp_decode_pair");
1542  if (!tp) {
1543  fr_strerror_const_push("Failed locating decoder testpoint");
1545  }
1546 
1547  p += slen;
1548  fr_skip_whitespace(p);
1549 
1550  if (tp->test_ctx && (tp->test_ctx(&decode_ctx, cc->tmp_ctx) < 0)) {
1551  fr_strerror_const_push("Failed initialising decoder testpoint");
1553  }
1554 
1555  /*
1556  * Hack because we consume more of the command string
1557  * so we need to check this again.
1558  */
1559  if (*p == '-') {
1560  p = data;
1561  inlen = data_used;
1562  }
1563 
1564  /*
1565  * Decode hex from input text
1566  */
1568  if (slen <= 0) {
1569  CLEAR_TEST_POINT(cc);
1570  RETURN_PARSE_ERROR(-(slen));
1571  }
1572 
1573  to_dec = (uint8_t *)data;
1574  to_dec_end = to_dec + slen;
1575 
1576  ASAN_POISON_MEMORY_REGION(to_dec_end, COMMAND_OUTPUT_MAX - slen);
1577 
1578  /*
1579  * Run the input data through the test
1580  * point to produce fr_pair_ts.
1581  */
1582  while (to_dec < to_dec_end) {
1583  slen = tp->func(head, &head->vp_group, fr_dict_root(cc->tmpl_rules.attr.dict_def ? cc->tmpl_rules.attr.dict_def : cc->config->dict),
1584  (uint8_t *)to_dec, (to_dec_end - to_dec), decode_ctx);
1585  cc->last_ret = slen;
1586  if (slen <= 0) {
1587  ASAN_UNPOISON_MEMORY_REGION(to_dec_end, COMMAND_OUTPUT_MAX - slen);
1588  CLEAR_TEST_POINT(cc);
1590  }
1591  if ((size_t)slen > (size_t)(to_dec_end - to_dec)) {
1592  fr_perror("%s: Internal sanity check failed at %d", __FUNCTION__, __LINE__);
1593  ASAN_UNPOISON_MEMORY_REGION(to_dec_end, COMMAND_OUTPUT_MAX - slen);
1594  CLEAR_TEST_POINT(cc);
1596  }
1597  to_dec += slen;
1598  }
1599 
1600  /*
1601  * Clear any spurious errors
1602  */
1604  ASAN_UNPOISON_MEMORY_REGION(to_dec_end, COMMAND_OUTPUT_MAX - slen);
1605 
1606  /*
1607  * Output may be an error, and we ignore
1608  * it if so.
1609  */
1610  slen = fr_pair_list_print(&FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX), NULL, &head->vp_group);
1611  if (slen <= 0) {
1613  }
1614 
1615  CLEAR_TEST_POINT(cc);
1616  RETURN_OK(slen);
1617 }
1618 
1620  char *data, size_t data_used, char *in, size_t inlen)
1621 {
1622  fr_test_point_proto_decode_t *tp = NULL;
1623  void *decode_ctx = NULL;
1624  char *p;
1625  uint8_t *to_dec;
1626  uint8_t *to_dec_end;
1627  ssize_t slen;
1628 
1629  fr_dict_attr_t const *da;
1630  fr_pair_t *head;
1631 
1632  da = fr_dict_attr_by_name(NULL, fr_dict_root(fr_dict_internal()), "request");
1633  fr_assert(da != NULL);
1634  head = fr_pair_afrom_da(cc->tmp_ctx, da);
1635  if (!head) {
1636  fr_strerror_const_push("Failed allocating memory");
1638  }
1639 
1640  p = in;
1641 
1642  slen = load_test_point_by_command((void **)&tp, in, "tp_decode_proto");
1643  if (!tp) {
1644  fr_strerror_const_push("Failed locating decoder testpoint");
1646  }
1647 
1648  p += slen;
1649  fr_skip_whitespace(p);
1650 
1651  if (tp->test_ctx && (tp->test_ctx(&decode_ctx, cc->tmp_ctx) < 0)) {
1652  fr_strerror_const_push("Failed initialising decoder testpoint");
1654  }
1655 
1656  /*
1657  * Hack because we consume more of the command string
1658  * so we need to check this again.
1659  */
1660  if (*p == '-') {
1661  p = data;
1662  inlen = data_used;
1663  }
1664 
1665  /*
1666  * Decode hex from input text
1667  */
1669  if (slen <= 0) {
1670  CLEAR_TEST_POINT(cc);
1671  RETURN_PARSE_ERROR(-(slen));
1672  }
1673 
1674  to_dec = (uint8_t *)data;
1675  to_dec_end = to_dec + slen;
1676 
1677  ASAN_POISON_MEMORY_REGION(to_dec_end, COMMAND_OUTPUT_MAX - slen);
1678 
1679  slen = tp->func(head, &head->vp_group,
1680  (uint8_t *)to_dec, (to_dec_end - to_dec), decode_ctx);
1681  cc->last_ret = slen;
1682  if (slen <= 0) {
1683  ASAN_UNPOISON_MEMORY_REGION(to_dec_end, COMMAND_OUTPUT_MAX - slen);
1684  CLEAR_TEST_POINT(cc);
1686  }
1687 
1688  /*
1689  * Clear any spurious errors
1690  */
1692  ASAN_UNPOISON_MEMORY_REGION(to_dec_end, COMMAND_OUTPUT_MAX - slen);
1693 
1694  /*
1695  * Output may be an error, and we ignore
1696  * it if so.
1697  */
1698 
1699  /*
1700  * Print the pairs.
1701  */
1702  slen = fr_pair_list_print(&FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX), NULL, &head->vp_group);
1703  if (slen <= 0) {
1704  fr_assert(0);
1706  }
1707 
1708  CLEAR_TEST_POINT(cc);
1709  RETURN_OK(slen);
1710 }
1711 
1712 /** Parse a dictionary attribute, writing "ok" to the data buffer is everything was ok
1713  *
1714  */
1716  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
1717 {
1719 
1721 }
1722 
1723 /** Print the currently loaded dictionary
1724  *
1725  */
1727  UNUSED char *data, size_t data_used, UNUSED char *in, UNUSED size_t inlen)
1728 {
1730 
1731  /*
1732  * Don't modify the contents of the data buffer
1733  */
1734  RETURN_OK(data_used);
1735 }
1736 
1737 static CC_HINT(nonnull)
1739  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
1740 {
1741  size_t need;
1742  ssize_t ret;
1743  char *p, *next;
1744  uint8_t *enc_p;
1745 
1746  p = in;
1747  next = strchr(p, ',');
1748  if (next) *next = 0;
1749 
1750  enc_p = cc->buffer_start;
1751 
1752  while (true) {
1753  fr_value_box_t *box = talloc_zero(NULL, fr_value_box_t);
1754 
1755  fr_skip_whitespace(p);
1756 
1757  if (fr_value_box_from_str(box, box, FR_TYPE_STRING, NULL,
1758  p, strlen(p),
1760  false) < 0) {
1761  talloc_free(box);
1763  }
1764 
1765  ret = fr_dns_label_from_value_box(&need,
1766  cc->buffer_start, cc->buffer_end - cc->buffer_start, enc_p, true, box, NULL);
1767  talloc_free(box);
1768 
1769  if (ret < 0) RETURN_OK_WITH_ERROR();
1770 
1771  if (ret == 0) RETURN_OK(snprintf(data, COMMAND_OUTPUT_MAX, "need=%zd", need));
1772 
1773  enc_p += ret;
1774 
1775  /*
1776  * Go to the next input string
1777  */
1778  if (!next) break;
1779 
1780  p = next + 1;
1781  next = strchr(p, ',');
1782  if (next) *next = 0;
1783  }
1784 
1785  if ((cc->fuzzer_dir >= 0) &&
1786  (dump_fuzzer_data(cc->fuzzer_dir, in, cc->buffer_start, enc_p - cc->buffer_start) < 0)) {
1788  }
1789 
1790  RETURN_OK(hex_print(data, COMMAND_OUTPUT_MAX, cc->buffer_start, enc_p - cc->buffer_start));
1791 }
1792 
1794  char *data, UNUSED size_t data_used, char *in, size_t inlen)
1795 {
1796  ssize_t slen, total, i, outlen;
1797  char *out, *end;
1798  fr_value_box_t *box = talloc_zero(NULL, fr_value_box_t);
1799 
1800  /*
1801  * Decode hex from input text
1802  */
1803  total = hex_to_bin(cc->buffer_start, cc->buffer_end - cc->buffer_start, in, inlen);
1804  if (total <= 0) RETURN_PARSE_ERROR(-total);
1805 
1806  out = data;
1807  end = data + COMMAND_OUTPUT_MAX;
1808 
1809  for (i = 0; i < total; i += slen) {
1810  slen = fr_dns_label_to_value_box(box, box, cc->buffer_start, total, cc->buffer_start + i, false, NULL);
1811  if (slen <= 0) {
1812  error:
1813  talloc_free(box);
1815  }
1816 
1817  /*
1818  * Separate names by commas
1819  */
1820  if (i > 0) *(out++) = ',';
1821 
1822  /*
1823  * We don't print it with quotes.
1824  */
1825  outlen = fr_value_box_print(&FR_SBUFF_OUT(out, end - out), box, NULL);
1826  if (outlen <= 0) goto error;
1827  out += outlen;
1828 
1829  fr_value_box_clear(box);
1830  }
1831 
1832  talloc_free(box);
1833  RETURN_OK(out - data);
1834 }
1835 
1837  char *data, UNUSED size_t data_used, char *in, size_t inlen)
1838 {
1839  fr_test_point_pair_encode_t *tp = NULL;
1840 
1841  fr_dcursor_t cursor;
1842  void *encode_ctx = NULL;
1843  ssize_t slen;
1844  char *p = in;
1845 
1846  uint8_t *enc_p, *enc_end;
1848  fr_pair_t *vp;
1849  bool truncate = false;
1850 
1851  size_t iterations = 0;
1852  fr_dict_t const *dict;
1853  fr_pair_parse_t root, relative;
1854 
1856 
1857  slen = load_test_point_by_command((void **)&tp, p, "tp_encode_pair");
1858  if (!tp) {
1859  fr_strerror_const_push("Failed locating encode testpoint");
1860  CLEAR_TEST_POINT(cc);
1862  }
1863 
1864  p += ((size_t)slen);
1865  fr_skip_whitespace(p);
1866 
1867  /*
1868  * The truncate torture test.
1869  *
1870  * Increase the buffer one byte at a time until all items in the cursor
1871  * have been encoded.
1872  *
1873  * The poisoned region at the end of the buffer will detect overruns
1874  * if we're running with asan.
1875  *
1876  */
1877  if (strncmp(p, "truncate", sizeof("truncate") - 1) == 0) {
1878  truncate = true;
1879  p += sizeof("truncate") - 1;
1880  fr_skip_whitespace(p);
1881  }
1882 
1883  if (tp->test_ctx && (tp->test_ctx(&encode_ctx, cc->tmp_ctx) < 0)) {
1884  fr_strerror_const_push("Failed initialising encoder testpoint");
1885  CLEAR_TEST_POINT(cc);
1887  }
1888 
1890 
1891  root = (fr_pair_parse_t) {
1892  .ctx = cc->tmp_ctx,
1893  .da = fr_dict_root(dict),
1894  .list = &head,
1895  };
1896  relative = (fr_pair_parse_t) { };
1897 
1898  slen = fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(p, inlen - (p - in)));
1899  if (slen <= 0) {
1900  CLEAR_TEST_POINT(cc);
1902  }
1903 
1905 
1906  /*
1907  * Outer loop implements truncate test
1908  */
1909  do {
1910  enc_p = cc->buffer_start;
1911  enc_end = truncate ? cc->buffer_start + iterations++ : cc->buffer_end;
1912 
1913  if (truncate) {
1914 #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
1915  /*
1916  * Poison the region between the subset of the buffer
1917  * we're using and the end of the buffer.
1918  */
1919  ASAN_POISON_MEMORY_REGION(enc_end, (cc->buffer_end) - enc_end);
1920 
1921  DEBUG("%s[%d]: Iteration %zu - Safe region %p-%p (%zu bytes), "
1922  "poisoned region %p-%p (%zu bytes)", cc->filename, cc->lineno, iterations - 1,
1923  enc_p, enc_end, enc_end - enc_p, enc_end, cc->buffer_end, cc->buffer_end - enc_end);
1924 #else
1925  DEBUG("%s[%d]: Iteration %zu - Allowed region %p-%p (%zu bytes)",
1926  cc->filename, cc->lineno, iterations - 1, enc_p, enc_end, enc_end - enc_p);
1927 #endif
1928  }
1929 
1930  for (vp = fr_pair_dcursor_iter_init(&cursor, &head,
1933  vp;
1934  vp = fr_dcursor_current(&cursor)) {
1935  slen = tp->func(&FR_DBUFF_TMP(enc_p, enc_end), &cursor, encode_ctx);
1936  cc->last_ret = slen;
1937 
1938  if (truncate) DEBUG("%s[%d]: Iteration %zu - Result %zd%s%s",
1939  cc->filename, cc->lineno, iterations - 1, slen,
1940  *fr_strerror_peek() != '\0' ? " - " : "",
1941  *fr_strerror_peek() != '\0' ? fr_strerror_peek() : "");
1942  if (slen < 0) break;
1943 
1944  /*
1945  * Encoder indicated it encoded too much data
1946  */
1947  if (slen > (enc_end - enc_p)) {
1948  fr_strerror_printf("Expected returned encoded length <= %zu bytes, got %zu bytes",
1949  (enc_end - enc_p), (size_t)slen);
1950 #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
1951  if (truncate) ASAN_UNPOISON_MEMORY_REGION(enc_end, (cc->buffer_end) - enc_end);
1952 #endif
1954  CLEAR_TEST_POINT(cc);
1956  }
1957 
1958  enc_p += slen;
1959 
1960  if (slen == 0) break;
1961 
1962  }
1963 
1964 #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H
1965  /*
1966  * un-poison the region between the subset of the buffer
1967  * we're using and the end of the buffer.
1968  */
1969  if (truncate) ASAN_UNPOISON_MEMORY_REGION(enc_end, (cc->buffer_end) - enc_end);
1970 #endif
1971  /*
1972  * We consumed all the VPs, so presumably encoded the
1973  * complete pair list.
1974  */
1975  if (!vp) break;
1976  } while (truncate && (enc_end < cc->buffer_end));
1977 
1978  /*
1979  * Last iteration result in an error
1980  */
1981  if (slen < 0) {
1983  CLEAR_TEST_POINT(cc);
1985  }
1986 
1987  /*
1988  * Clear any spurious errors
1989  */
1991 
1993 
1994  CLEAR_TEST_POINT(cc);
1995 
1996  if ((cc->fuzzer_dir >= 0) &&
1997  (dump_fuzzer_data(cc->fuzzer_dir, p, cc->buffer_start, enc_p - cc->buffer_start) < 0)) {
1999  }
2000 
2002 }
2003 
2004 /** Encode a RADIUS attribute writing the result to the data buffer as space separated hexits
2005  *
2006  */
2008  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2009 {
2010  size_t len;
2011 
2012  len = encode_rfc(in, cc->buffer_start, cc->buffer_end - cc->buffer_start);
2013  if (len <= 0) RETURN_PARSE_ERROR(0);
2014 
2015  if (len >= (size_t)(cc->buffer_end - cc->buffer_start)) {
2016  fr_strerror_const("Encoder output would overflow output buffer");
2018  }
2019 
2021 }
2022 
2023 /** Parse a list of pairs
2024  *
2025  */
2027  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2028 {
2029  ssize_t slen;
2031  bool done = false;
2032  char *filename;
2033  FILE *fp;
2034 
2035  filename = talloc_asprintf(cc->tmp_ctx, "%s/%s", cc->path, in);
2036 
2037  fp = fopen(filename, "r");
2038  talloc_free(filename);
2039 
2040  if (!fp) {
2041  fr_strerror_printf("Failed opening %s - %s", in, fr_syserror(errno));
2043  }
2044 
2046  slen = fr_pair_list_afrom_file(cc->tmp_ctx, cc->tmpl_rules.attr.dict_def, &head, fp, &done);
2047  fclose(fp);
2048  if (slen < 0) {
2050  }
2051 
2052  /*
2053  * Print the pairs.
2054  */
2056  if (slen <= 0) {
2057  fr_assert(0);
2059  }
2060 
2061  if (!done) {
2062  strlcpy(data + slen, "!DONE", COMMAND_OUTPUT_MAX - slen);
2063  slen += 5;
2064  }
2065 
2067 
2068  RETURN_OK(slen);
2069 }
2070 
2071 
2073  char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
2074 {
2076 }
2077 
2079  char *data, UNUSED size_t data_used, char *in, size_t inlen)
2080 {
2081  fr_test_point_proto_encode_t *tp = NULL;
2082 
2083  void *encode_ctx = NULL;
2084  ssize_t slen;
2085  char *p = in;
2086 
2088  fr_dict_t const *dict;
2089  fr_pair_parse_t root, relative;
2090 
2092 
2093  slen = load_test_point_by_command((void **)&tp, p, "tp_encode_proto");
2094  if (!tp) {
2095  fr_strerror_const_push("Failed locating encode testpoint");
2096  CLEAR_TEST_POINT(cc);
2098  }
2099 
2100  p += ((size_t)slen);
2101  fr_skip_whitespace(p);
2102  if (tp->test_ctx && (tp->test_ctx(&encode_ctx, cc->tmp_ctx) < 0)) {
2103  fr_strerror_const_push("Failed initialising encoder testpoint");
2104  CLEAR_TEST_POINT(cc);
2106  }
2107 
2109 
2110  root = (fr_pair_parse_t) {
2111  .ctx = cc->tmp_ctx,
2112  .da = fr_dict_root(dict),
2113  .list = &head,
2114  };
2115  relative = (fr_pair_parse_t) { };
2116 
2117  slen = fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(p, inlen - (p - in)));
2118  if (slen <= 0) {
2119  CLEAR_TEST_POINT(cc);
2121  }
2122 
2123  slen = tp->func(cc->tmp_ctx, &head, cc->buffer_start, cc->buffer_end - cc->buffer_start, encode_ctx);
2125  cc->last_ret = slen;
2126  if (slen < 0) {
2127  CLEAR_TEST_POINT(cc);
2129  }
2130  /*
2131  * Clear any spurious errors
2132  */
2134 
2135  CLEAR_TEST_POINT(cc);
2136 
2137  if ((cc->fuzzer_dir >= 0) &&
2138  (dump_fuzzer_data(cc->fuzzer_dir, p, cc->buffer_start, slen) < 0)) {
2140  }
2141 
2143 }
2144 
2145 /** Command eof
2146  *
2147  * Mark the end of a test file if we're reading from stdin.
2148  *
2149  * Doesn't actually do anything, is just a placeholder for the command processing loop.
2150  */
2152  UNUSED char *data, UNUSED size_t data_used, UNUSED char *in, UNUSED size_t inlen)
2153 {
2154  return 0;
2155 }
2156 
2157 /** Enable fuzzer output
2158  *
2159  * Any commands that produce potentially useful corpus seed data will write that out data
2160  * to files in the specified directory, using the md5 of the text input at as the file name.
2161  *
2162  */
2164  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2165 {
2166  int fd;
2167  struct stat sdir;
2168  char *fuzzer_dir;
2169  bool retry_dir = true;
2170 
2171  /*
2172  * Close any open fuzzer output dirs
2173  */
2174  if (cc->fuzzer_dir >= 0) {
2175  close(cc->fuzzer_dir);
2176  cc->fuzzer_dir = -1;
2177  }
2178 
2179  if (in[0] == '\0') {
2180  fr_strerror_const("Missing directory name");
2181  RETURN_PARSE_ERROR(0);
2182  }
2183 
2184  fuzzer_dir = talloc_asprintf(cc->tmp_ctx, "%s/%s",
2185  cc->config->fuzzer_dir ? cc->config->fuzzer_dir : cc->path, in);
2186 
2187 again:
2188  fd = open(fuzzer_dir, O_RDONLY);
2189  if (fd < 0) {
2190  if (mkdir(fuzzer_dir, 0777) == 0) {
2191  fd = open(fuzzer_dir, O_RDONLY);
2192  if (fd >= 0) goto stat;
2193  /*
2194  * Prevent race if multiple unit_test_attribute instances
2195  * attempt to create the same output dir.
2196  */
2197  } else if ((errno == EEXIST) && retry_dir) {
2198  retry_dir = false; /* Only allow this once */
2199  goto again;
2200  }
2201 
2202  fr_strerror_printf("fuzzer-out \"%s\" doesn't exist: %s", fuzzer_dir, fr_syserror(errno));
2203  RETURN_PARSE_ERROR(0);
2204  }
2205 
2206 stat:
2207  if (fstat(fd, &sdir) < 0) {
2208  close(fd);
2209  fr_strerror_printf("failed statting fuzzer-out \"%s\": %s", fuzzer_dir, fr_syserror(errno));
2210  RETURN_PARSE_ERROR(0);
2211  }
2212 
2213  if (!(sdir.st_mode & S_IFDIR)) {
2214  close(fd);
2215  fr_strerror_printf("fuzzer-out \"%s\" is not a directory", fuzzer_dir);
2216  RETURN_PARSE_ERROR(0);
2217  }
2218  cc->fuzzer_dir = fd;
2219  talloc_free(fuzzer_dir);
2220 
2221  return 0;
2222 }
2223 
2224 /** Exit gracefully with the specified code
2225  *
2226  */
2228  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2229 {
2230  if (!*in) RETURN_EXIT(0);
2231 
2232  RETURN_EXIT(atoi(in));
2233 }
2234 
2236  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2237 {
2238  char *name, *tmp = NULL;
2239  char const *dir;
2240  char *q;
2241  int ret;
2242 
2244 
2245  if (in[0] == '\0') {
2246  fr_strerror_const("Missing dictionary name");
2247  RETURN_PARSE_ERROR(0);
2248  }
2249 
2250  q = strchr(in, ' ');
2251  if (q) {
2252  name = tmp = talloc_bstrndup(NULL, in, q - in);
2253  q++;
2254  dir = q;
2255  } else {
2256  name = in;
2257  dir = cc->path;
2258  }
2259 
2260  ret = fr_dict_read(UNCONST(fr_dict_t *, cc->tmpl_rules.attr.dict_def), dir, name);
2261  talloc_free(tmp);
2262  if (ret < 0) RETURN_COMMAND_ERROR();
2263 
2264  RETURN_OK(0);
2265 }
2266 
2267 
2268 /** Compare the data buffer to an expected value
2269  *
2270  */
2272  char *data, size_t data_used, char *in, size_t inlen)
2273 {
2274  if (strcmp(in, data) != 0) {
2275  mismatch_print(cc, "match", in, inlen, data, data_used, true);
2276  RETURN_MISMATCH(data_used);
2277  }
2278 
2279  /*
2280  * We didn't actually write anything, but this
2281  * keeps the contents of the data buffer around
2282  * for the next command to operate on.
2283  */
2284  RETURN_OK(data_used);
2285 }
2286 
2287 /** Compare the data buffer against an expected expression
2288  *
2289  */
2291  char *data, size_t data_used, char *in, size_t inlen)
2292 {
2293  ssize_t slen;
2294  regex_t *regex;
2295  int ret;
2296 
2297  slen = regex_compile(cc->tmp_ctx, &regex, in, inlen, NULL, false, true);
2298  if (slen <= 0) RETURN_COMMAND_ERROR();
2299 
2300  ret = regex_exec(regex, data, data_used, NULL);
2301  talloc_free(regex);
2302 
2303  switch (ret) {
2304  case -1:
2305  default:
2307 
2308  case 0:
2309  mismatch_print(cc, "match-regex", in, inlen, data, data_used, false);
2310  RETURN_MISMATCH(data_used);
2311 
2312  case 1:
2313  RETURN_OK(data_used);
2314  }
2315 }
2316 
2317 /** Artificially limit the maximum packet size.
2318  *
2319  */
2321  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2322 {
2323  unsigned long size;
2324  char *end;
2325 
2327 
2328  if (*in != '\0') {
2329  size = strtoul(in, &end, 10);
2330  if ((size == ULONG_MAX) || *end || (size >= 65536)) {
2331  fr_strerror_const_push("Invalid integer");
2333  }
2334  } else {
2335  size = DEFAULT_BUFFER_SIZE;
2336  }
2337 
2338  if (poisoned_buffer_allocate(cc, &cc->buffer, size) < 0) RETURN_EXIT(1);
2341 
2342  RETURN_OK(snprintf(data, COMMAND_OUTPUT_MAX, "%ld", size));
2343 }
2344 
2345 /** Set or clear migration flags.
2346  *
2347  */
2349  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2350 {
2351  char *p;
2352  bool *out;
2353 
2355  p = in;
2356 
2357  if (strncmp(p, "xlat_new_functions", sizeof("xlat_new_functions") - 1) == 0) {
2358  p += sizeof("xlat_new_functions") - 1;
2360 
2361  } else {
2362  fr_strerror_const("Unknown migration flag");
2363  RETURN_PARSE_ERROR(0);
2364  }
2365 
2366  fr_skip_whitespace(p);
2367  if (*p != '=') {
2368  fr_strerror_const("Missing '=' after flag");
2369  RETURN_PARSE_ERROR(0);
2370  }
2371  p++;
2372 
2373  fr_skip_whitespace(p);
2374  if ((strcmp(p, "yes") == 0) || (strcmp(p, "true") == 0) || (strcmp(p, "1") == 0)) {
2375  *out = true;
2376 
2377  } else if ((strcmp(p, "no") == 0) || (strcmp(p, "false") == 0) || (strcmp(p, "0") == 0)) {
2378  *out = false;
2379 
2380  } else {
2381  fr_strerror_const("Invalid value for flag");
2382  RETURN_PARSE_ERROR(0);
2383  }
2384 
2385  RETURN_OK(0);
2386 }
2387 
2388 /** Skip the test file if we're missing a particular feature
2389  *
2390  */
2392  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2393 {
2394  CONF_PAIR *cp;
2395 
2396  if (in[0] == '\0') {
2397  fr_strerror_printf("Prerequisite syntax is \"need-feature <feature>\". "
2398  "Use -f to print features");
2399  RETURN_PARSE_ERROR(0);
2400  }
2401 
2402  cp = cf_pair_find(cc->config->features, in);
2403  if (!cp || (strcmp(cf_pair_value(cp), "yes") != 0)) {
2404  DEBUG("Skipping, missing feature \"%s\"", in);
2405  RETURN_SKIP_FILE();
2406  }
2407 
2408  RETURN_NOOP(0);
2409 }
2410 
2411 /** Negate the result of a match command or any command which returns "OK"
2412  *
2413  */
2415  char *data, size_t data_used, char *in, size_t inlen)
2416 {
2417  data_used = process_line(result, cc, data, data_used, in, inlen);
2418  switch (result->rcode) {
2419  /*
2420  * OK becomes a command error
2421  */
2422  case RESULT_OK:
2423  ERROR("%s[%d]: %.*s: returned 'ok', where we expected 'result-mismatch'",
2424  cc->filename, cc->lineno, (int) inlen, in);
2425  RETURN_MISMATCH(data_used);
2426 
2427  /*
2428  * Mismatch becomes OK
2429  */
2430  case RESULT_MISMATCH:
2431  RETURN_OK(data_used);
2432 
2433  /*
2434  * The rest are unchanged...
2435  */
2436  default:
2437  break;
2438  }
2439 
2440  return data_used;
2441 }
2442 
2443 /** Parse an print an attribute pair or pair list.
2444  *
2445  */
2447  char *data, UNUSED size_t data_used, char *in, size_t inlen)
2448 {
2450  ssize_t slen;
2452  fr_pair_parse_t root, relative;
2453 
2455 
2456  root = (fr_pair_parse_t) {
2457  .ctx = cc->tmp_ctx,
2458  .da = fr_dict_root(dict),
2459  .list = &head,
2460  };
2461  relative = (fr_pair_parse_t) { };
2462 
2463  slen = fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(in, inlen));
2464  if (slen <= 0) {
2465 // fr_strerror_printf_push_head("ERROR offset %d", (int) -slen);
2468  }
2469 
2470  /*
2471  * Output may be an error, and we ignore
2472  * it if so.
2473  */
2474 
2476  if (slen <= 0) {
2479  }
2480 
2482  RETURN_OK(slen);
2483 }
2484 
2485 /** Dynamically load a protocol library
2486  *
2487  */
2489  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2490 {
2491  ssize_t slen;
2492 
2493  if (*in == '\0') {
2494  fr_strerror_printf("Load syntax is \"proto <lib_name>\"");
2495  RETURN_PARSE_ERROR(0);
2496  }
2497 
2499  slen = load_proto_library(in);
2500  if (slen <= 0) RETURN_PARSE_ERROR(-(slen));
2501 
2502  RETURN_OK(0);
2503 }
2504 
2506  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2507 {
2509  return dictionary_load_common(result, cc, in, NULL);
2510 }
2511 
2512 /** Touch a file to indicate a test completed
2513  *
2514  */
2516  UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2517 {
2518  if (fr_unlink(in) < 0) RETURN_COMMAND_ERROR();
2519  if (fr_touch(NULL, in, 0644, true, 0755) <= 0) RETURN_COMMAND_ERROR();
2520 
2521  RETURN_OK(0);
2522 }
2523 
2524 /** Callback for a tmpl rule parser
2525  *
2526  */
2527 typedef ssize_t(*command_tmpl_rule_func)(TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value);
2528 
2530 {
2531  bool res;
2532  ssize_t slen;
2533 
2534  slen = fr_sbuff_out_bool(&res, value);
2535  rules->attr.allow_foreign = res;
2536  return slen;
2537 }
2538 
2540 {
2541  bool res;
2542  ssize_t slen;
2543 
2544  slen = fr_sbuff_out_bool(&res, value);
2545  rules->attr.allow_unknown = res;
2546  return slen;
2547 }
2548 
2550 {
2551  bool res;
2552  ssize_t slen;
2553 
2554  slen = fr_sbuff_out_bool(&res, value);
2555  rules->attr.allow_unresolved = res;
2556  return slen;
2557 }
2558 
2560 {
2562  fr_slen_t slen;
2563 
2565  &rules->attr.namespace,
2566  rules->attr.dict_def ? fr_dict_root(rules->attr.dict_def) :
2568  value, NULL);
2570  return slen;
2571 }
2572 
2574 {
2575  ssize_t slen;
2576 
2577  slen = tmpl_attr_list_from_substr(&rules->attr.list_def, value);
2578 
2579  if (slen == 0) {
2580  fr_strerror_printf("Invalid list specifier \"%pV\"",
2582  }
2583 
2584  return slen;
2585 }
2586 
2588 {
2589  fr_slen_t slen;
2590 
2591  slen = tmpl_request_ref_list_afrom_substr(ctx, NULL,
2592  &rules->attr.request_def,
2593  value);
2594  if (slen < 0) {
2595  fr_strerror_printf("Invalid request specifier \"%pV\"",
2597  }
2598 
2599  return slen;
2600 }
2601 
2603  UNUSED char *data, UNUSED size_t data_used, char *in, size_t inlen)
2604 {
2605  fr_sbuff_t sbuff = FR_SBUFF_IN(in, inlen);
2606  ssize_t slen;
2608  void *res;
2609 
2610  static fr_table_ptr_sorted_t tmpl_rule_func_table[] = {
2611  { L("allow_foreign"), (void *)command_tmpl_rule_allow_foreign },
2612  { L("allow_unknown"), (void *)command_tmpl_rule_allow_unknown },
2613  { L("allow_unresolved"), (void *)command_tmpl_rule_allow_unresolved },
2614  { L("attr_parent"), (void *)command_tmpl_rule_attr_parent },
2615  { L("list_def"), (void *)command_tmpl_rule_list_def },
2616  { L("request_def"), (void *)command_tmpl_rule_request_def }
2617  };
2618  static size_t tmpl_rule_func_table_len = NUM_ELEMENTS(tmpl_rule_func_table);
2619 
2620  while (fr_sbuff_extend(&sbuff)) {
2621  fr_sbuff_adv_past_whitespace(&sbuff, SIZE_MAX, NULL);
2622 
2623  fr_sbuff_out_by_longest_prefix(&slen, &res, tmpl_rule_func_table, &sbuff, NULL);
2624  if (res == NULL) {
2625  fr_strerror_printf("Specified rule \"%pV\" is invalid",
2628  }
2629  func = (command_tmpl_rule_func)res; /* -Wpedantic */
2630 
2631  fr_sbuff_adv_past_whitespace(&sbuff, SIZE_MAX, NULL);
2632 
2633  if (!fr_sbuff_next_if_char(&sbuff, '=')) {
2634  fr_strerror_printf("Expected '=' after rule identifier, got \"%pV\"",
2637  }
2638 
2639  fr_sbuff_adv_past_whitespace(&sbuff, SIZE_MAX, NULL);
2640 
2641  if (func(cc->tmp_ctx, &cc->tmpl_rules, &sbuff) <= 0) RETURN_COMMAND_ERROR();
2642  }
2643 
2644  return fr_sbuff_used(&sbuff);
2645 }
2646 
2648  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2649 {
2650  fr_value_box_t *box = talloc_zero(NULL, fr_value_box_t);
2651  fr_value_box_t *box2;
2652  char const *value;
2653  size_t match_len;
2654  ssize_t slen;
2655  fr_type_t type;
2656 
2657  match_len = parse_typed_value(result, box, &value, in, strlen(in));
2658  if (match_len == 0) {
2659  talloc_free(box);
2660  return 0; /* errors have already been updated */
2661  }
2662 
2663  type = box->type;
2664 
2665  /*
2666  * Don't print dates with enclosing quotation marks.
2667  */
2668  if (type != FR_TYPE_DATE) {
2671  } else {
2673  }
2674  if (slen <= 0) {
2675  talloc_free(box);
2677  }
2678 
2679  /*
2680  * Behind the scenes, parse the data
2681  * string. We should get the same value
2682  * box as last time.
2683  */
2684  box2 = talloc_zero(NULL, fr_value_box_t);
2685  if (fr_value_box_from_str(box2, box2, type, NULL,
2686  data, slen,
2687  &fr_value_unescape_double, false) < 0) {
2688  talloc_free(box2);
2689  talloc_free(box);
2691  }
2692 
2693  /*
2694  * They MUST be identical
2695  */
2696  if (fr_value_box_cmp(box, box2) != 0) {
2697  fr_strerror_const("ERROR value box reparsing failed. Results not identical");
2698  fr_strerror_printf_push("out: %pV (as string %.*s)", box2, (int) slen, data);
2699  fr_strerror_printf_push("in: %pV (from string %s)", box, value);
2700  talloc_free(box2);
2701  talloc_free(box);
2703  }
2704 
2705  /*
2706  * Store <type><value str...>
2707  */
2708  if (cc->fuzzer_dir >= 0) {
2709  char fuzzer_buffer[1024];
2710  char *fuzzer_p = fuzzer_buffer, *fuzzer_end = fuzzer_p + sizeof(fuzzer_buffer);
2711 
2712  *fuzzer_p++ = (uint8_t)type; /* Fuzzer uses first byte for type */
2713 
2714  strlcpy(fuzzer_p, data, slen > fuzzer_end - fuzzer_p ? fuzzer_end - fuzzer_p : slen);
2715 
2716  if (dump_fuzzer_data(cc->fuzzer_dir, fuzzer_buffer,
2717  (uint8_t *)fuzzer_buffer, strlen(fuzzer_buffer)) < 0) {
2719  }
2720  }
2721 
2722  talloc_free(box2);
2723  talloc_free(box);
2724  RETURN_OK(slen);
2725 }
2726 
2728  char *data, size_t data_used, char *in, size_t inlen)
2729 {
2730  int fd;
2731  char *path;
2732  bool locked = false;
2733 
2734  path = talloc_bstrndup(cc->tmp_ctx, in, inlen);
2735 
2736  fd = open(path, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
2737  if (fd < 0) {
2738  fr_strerror_printf("Failed opening \"%s\": %s", path, fr_syserror(errno));
2739  error:
2740  talloc_free(path);
2741  if (fd >= 0) {
2742  if (locked) (void)flock(fd, LOCK_UN);
2743  close(fd);
2744  }
2746  }
2747 
2748  if (flock(fd, LOCK_EX) < 0) {
2749  fr_strerror_printf("Failed locking \"%s\": %s", path, fr_syserror(errno));
2750  goto error;
2751  }
2752  locked = true;
2753 
2754  while (data_used) {
2755  ssize_t ret;
2756  ret = write(fd, data, data_used);
2757  if (ret < 0) {
2758  fr_strerror_printf("Failed writing to \"%s\": %s", path, fr_syserror(errno));
2759  goto error;
2760  }
2761  data_used -= ret;
2762  data += ret;
2763  }
2764  (void)flock(fd, LOCK_UN);
2765  talloc_free(path);
2766  close(fd);
2767 
2768  RETURN_OK(data_used);
2769 }
2770 
2771 /** Parse an reprint and xlat expansion
2772  *
2773  */
2775  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2776 {
2777  ssize_t dec_len;
2778  xlat_exp_head_t *head = NULL;
2779  size_t input_len = strlen(in), escaped_len;
2780  fr_sbuff_parse_rules_t p_rules = { .escapes = &fr_value_unescape_double };
2781 
2782  dec_len = xlat_tokenize(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len), &p_rules,
2783  &(tmpl_rules_t) {
2784  .attr = {
2785  .dict_def = cc->tmpl_rules.attr.dict_def ?
2786  cc->tmpl_rules.attr.dict_def : cc->config->dict,
2787  .list_def = request_attr_request,
2788  .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved
2789  },
2790  .xlat = cc->tmpl_rules.xlat,
2791  },
2792  0);
2793  if (dec_len <= 0) {
2794  fr_strerror_printf_push_head("ERROR offset %d", (int) -dec_len);
2795 
2796  return_error:
2798  }
2799 
2800  if (((size_t) dec_len != input_len)) {
2801  fr_strerror_printf_push_head("offset %d 'Too much text'", (int) dec_len);
2802  goto return_error;
2803  }
2804 
2806  RETURN_OK(escaped_len);
2807 }
2808 
2809 /** Parse and reprint an xlat expression expansion
2810  *
2811  */
2813  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2814 {
2815  ssize_t dec_len;
2816  xlat_exp_head_t *head = NULL;
2817  size_t input_len = strlen(in), escaped_len;
2818 // fr_sbuff_parse_rules_t p_rules = { .escapes = &fr_value_unescape_double };
2819 
2820  dec_len = xlat_tokenize_expression(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len), NULL,
2821  &(tmpl_rules_t) {
2822  .attr = {
2823  .dict_def = cc->tmpl_rules.attr.dict_def ?
2824  cc->tmpl_rules.attr.dict_def : cc->config->dict,
2825  .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved,
2826  .list_def = request_attr_request,
2827  }
2828  });
2829  if (dec_len <= 0) {
2830  fr_strerror_printf_push_head("ERROR offset %d", (int) -dec_len);
2831 
2832  return_error:
2834  }
2835 
2836  if (((size_t) dec_len != input_len)) {
2837  fr_strerror_printf_push_head("Passed in %zu characters, but only parsed %zd characters", input_len, dec_len);
2838  goto return_error;
2839  }
2840 
2842  RETURN_OK(escaped_len);
2843 }
2844 
2845 /** Parse, purify, and reprint an xlat expression expansion
2846  *
2847  */
2849  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2850 {
2851  ssize_t dec_len;
2852  xlat_exp_head_t *head = NULL;
2853  size_t input_len = strlen(in), escaped_len;
2854  tmpl_rules_t t_rules = (tmpl_rules_t) {
2855  .attr = {
2856  .dict_def = cc->tmpl_rules.attr.dict_def ?
2857  cc->tmpl_rules.attr.dict_def : cc->config->dict,
2858  .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved,
2859  .list_def = request_attr_request,
2860  },
2861  .xlat = cc->tmpl_rules.xlat,
2862  .at_runtime = true,
2863  };
2864 
2865  if (!el) {
2866  fr_strerror_const("Flag '-p' not used. xlat_purify is disabled");
2867  goto return_error;
2868  }
2869  t_rules.xlat.runtime_el = el;
2870 
2871  dec_len = xlat_tokenize_expression(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len), NULL, &t_rules);
2872  if (dec_len <= 0) {
2873  fr_strerror_printf_push_head("ERROR offset %d", (int) -dec_len);
2874 
2875  return_error:
2877  }
2878 
2879  if (((size_t) dec_len != input_len)) {
2880  fr_strerror_printf_push_head("Passed in %zu characters, but only parsed %zd characters", input_len, dec_len);
2881  goto return_error;
2882  }
2883 
2884  if (fr_debug_lvl > 2) {
2885  DEBUG("Before purify --------------------------------------------------");
2887  }
2888 
2889  (void) xlat_purify(head, NULL);
2890 
2891  if (fr_debug_lvl > 2) {
2892  DEBUG("After purify --------------------------------------------------");
2894  }
2895 
2897  RETURN_OK(escaped_len);
2898 }
2899 
2900 
2901 /** Parse an reprint and xlat argv expansion
2902  *
2903  */
2905  char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
2906 {
2907  int i, argc;
2908  char *p;
2909  ssize_t slen;
2910  xlat_exp_head_t *head = NULL;
2911  xlat_exp_head_t **argv;
2912  size_t len;
2913  size_t input_len = strlen(in);
2914  char buff[1024];
2915 
2916  slen = xlat_tokenize_argv(cc->tmp_ctx, &head, &FR_SBUFF_IN(in, input_len),
2917  NULL, NULL,
2918  &(tmpl_rules_t) {
2919  .attr = {
2920  .dict_def = cc->tmpl_rules.attr.dict_def ?
2921  cc->tmpl_rules.attr.dict_def : cc->config->dict,
2922  .list_def = request_attr_request,
2923  .allow_unresolved = cc->tmpl_rules.attr.allow_unresolved
2924  },
2925  }, false, false);
2926  if (slen <= 0) {
2927  fr_strerror_printf_push_head("ERROR offset %d", (int) -slen);
2929  }
2930 
2931  argc = xlat_flatten_compiled_argv(cc->tmp_ctx, &argv, head);
2932  if (argc <= 0) {
2933  fr_strerror_printf_push("ERROR in argument %d", (int) -argc);
2935  }
2936 
2937  for (i = 0, p = data; i < argc; i++) {
2938  (void) xlat_print(&FR_SBUFF_OUT(buff, sizeof(buff)), argv[i], NULL);
2939 
2940  len = snprintf(p, data + COMMAND_OUTPUT_MAX - p, "[%d]{ %s }, ", i, buff);
2941  p += len;
2942  }
2943 
2944  p -= 2;
2945  *p = '\0';
2946 
2947  RETURN_OK(p - data);
2948 }
2949 
2951  { L("#"), &(command_entry_t){
2952  .func = command_comment,
2953  .usage = "#<string>",
2954  .description = "A comment - not processed"
2955  }},
2956  { L("$INCLUDE "), &(command_entry_t){
2957  .func = command_include,
2958  .usage = "$INCLUDE <relative_path>",
2959  .description = "Execute a test file"
2960  }},
2961  { L("allow-unresolved "), &(command_entry_t){
2962  .func = command_allow_unresolved,
2963  .usage = "allow-unresolved yes|no",
2964  .description = "Allow or disallow unresolved attributes in xlats and references"
2965  }},
2966  { L("calc "), &(command_entry_t){
2967  .func = command_calc,
2968  .usage = "calc <type1> <value1> <operator> <type2> <value2> -> <output-type>",
2969  .description = "Perform calculations on value boxes",
2970  }},
2971  { L("calc_nary "), &(command_entry_t){
2972  .func = command_calc_nary,
2973  .usage = "calc_nary op <type1> <value1> <type2> <value2> ... -> <output-type>",
2974  .description = "Perform calculations on value boxes",
2975  }},
2976  { L("cd "), &(command_entry_t){
2977  .func = command_cd,
2978  .usage = "cd <path>",
2979  .description = "Change the directory for loading dictionaries and $INCLUDEs, writing the full path into the data buffer on success"
2980  }},
2981  { L("clear"), &(command_entry_t){
2982  .func = command_clear,
2983  .usage = "clear",
2984  .description = "Explicitly zero out the contents of the data buffer"
2985  }},
2986  { L("command add "), &(command_entry_t){
2987  .func = command_radmin_add,
2988  .usage = "command add <string>",
2989  .description = "Add a command to a radmin command tree"
2990  }},
2991  { L("command tab "), &(command_entry_t){
2992  .func = command_radmin_tab,
2993  .usage = "command tab <string>",
2994  .description = "Test a tab completion against a radmin command tree"
2995  }},
2996  { L("condition "), &(command_entry_t){
2998  .usage = "condition <string>",
2999  .description = "Parse and reprint a condition, writing the normalised condition to the data buffer on success"
3000  }},
3001  { L("count"), &(command_entry_t){
3002  .func = command_count,
3003  .usage = "count",
3004  .description = "Write the number of executed tests to the data buffer. A test is any command that should return 'ok'"
3005  }},
3006  { L("decode-dns-label "), &(command_entry_t){
3007  .func = command_decode_dns_label,
3008  .usage = "decode-dns-label (-|<hex_string>)",
3009  .description = "Decode one or more DNS labels, writing the decoded strings to the data buffer.",
3010  }},
3011  { L("decode-pair"), &(command_entry_t){
3012  .func = command_decode_pair,
3013  .usage = "decode-pair[.<testpoint_symbol>] (-|<hex_string>)",
3014  .description = "Produce an attribute value pair from a binary value using a specified protocol decoder. Protocol must be loaded with \"load <protocol>\" first",
3015  }},
3016  { L("decode-proto"), &(command_entry_t){
3017  .func = command_decode_proto,
3018  .usage = "decode-proto[.<testpoint_symbol>] (-|<hex string>)",
3019  .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",
3020  }},
3021  { L("dictionary "), &(command_entry_t){
3023  .usage = "dictionary <string>",
3024  .description = "Parse dictionary attribute definition, writing \"ok\" to the data buffer if successful",
3025  }},
3026  { L("dictionary-dump"), &(command_entry_t){
3027  .func = command_dictionary_dump,
3028  .usage = "dictionary-dump",
3029  .description = "Print the contents of the currently active dictionary to stdout",
3030  }},
3031  { L("encode-dns-label "), &(command_entry_t){
3032  .func = command_encode_dns_label,
3033  .usage = "encode-dns-label (-|string[,string])",
3034  .description = "Encode one or more DNS labels, writing a hex string to the data buffer.",
3035  }},
3036  { L("encode-pair"), &(command_entry_t){
3037  .func = command_encode_pair,
3038  .usage = "encode-pair[.<testpoint_symbol>] [truncate] (-|<attribute> = <value>[,<attribute = <value>])",
3039  .description = "Encode one or more attribute value pairs, writing a hex string to the data buffer. Protocol must be loaded with \"load <protocol>\" first",
3040  }},
3041  { L("encode-proto"), &(command_entry_t){
3042  .func = command_encode_proto,
3043  .usage = "encode-proto[.<testpoint_symbol>] (-|<attribute> = <value>[,<attribute = <value>])",
3044  .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"
3045  }},
3046  { L("eof"), &(command_entry_t){
3047  .func = command_eof,
3048  .usage = "eof",
3049  .description = "Mark the end of a 'virtual' file. Used to prevent 'need-feature' skipping all the content of a command stream or file",
3050  }},
3051  { L("exit"), &(command_entry_t){
3052  .func = command_exit,
3053  .usage = "exit[ <num>]",
3054  .description = "Exit with the specified error number. If no <num> is provided, process will exit with 0"
3055  }},
3056  { L("fuzzer-out"), &(command_entry_t){
3057  .func = command_fuzzer_out,
3058  .usage = "fuzzer-out <dir>",
3059  .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",
3060  }},
3061  { L("load-dictionary "),&(command_entry_t){
3062  .func = command_load_dictionary,
3063  .usage = "load-dictionary <name> [<dir>]",
3064  .description = "Load an additional dictionary from the same directory as the input file. Optionally you can specify a full path via <dir>",
3065  }},
3066  { L("match"), &(command_entry_t){
3067  .func = command_match,
3068  .usage = "match <string>",
3069  .description = "Compare the contents of the data buffer with an expected value"
3070  }},
3071  { L("match-regex "), &(command_entry_t){
3072  .func = command_match_regex,
3073  .usage = "match-regex <regex>",
3074  .description = "Compare the contents of the data buffer with a regular expression"
3075  }},
3076  { L("max-buffer-size"), &(command_entry_t){
3077  .func = command_max_buffer_size,
3078  .usage = "max-buffer-size[ <integer>]",
3079  .description = "Limit the maximum temporary buffer space available for any command which uses it"
3080  }},
3081  { L("migrate "), &(command_entry_t){
3082  .func = command_migrate,
3083  .usage = "migrate <flag>=<value>",
3084  .description = "Set migration flag"
3085  }},
3086  { L("need-feature "), &(command_entry_t){
3087  .func = command_need_feature,
3088  .usage = "need-feature <feature>",
3089  .description = "Skip the contents of the current file, or up to the next \"eof\" command if a particular feature is not available"
3090  }},
3091  { L("no "), &(command_entry_t){
3092  .func = command_no,
3093  .usage = "no ...",
3094  .description = "Negate the result of a command returning 'ok'"
3095  }},
3096  { L("pair "), &(command_entry_t){
3097  .func = command_pair,
3098  .usage = "pair ... data ...",
3099  .description = "Parse a list of pairs",
3100  }},
3101  { L("proto "), &(command_entry_t){
3102  .func = command_proto,
3103  .usage = "proto <protocol>",
3104  .description = "Switch the active protocol to the one specified, unloading the previous protocol",
3105  }},
3106  { L("proto-dictionary "),&(command_entry_t){
3107  .func = command_proto_dictionary,
3108  .usage = "proto-dictionary <proto_name> [<proto_dir>]",
3109  .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.",
3110  }},
3111  { L("raw "), &(command_entry_t){
3112  .func = command_encode_raw,
3113  .usage = "raw <string>",
3114  .description = "Create nested attributes from OID strings and values"
3115  }},
3116  { L("read_file "), &(command_entry_t){
3117  .func = command_read_file,
3118  .usage = "read_file <filename>",
3119  .description = "Read a list of pairs from a file",
3120  }},
3121  { L("returned"), &(command_entry_t){
3122  .func = command_returned,
3123  .usage = "returned",
3124  .description = "Print the returned value to the data buffer"
3125  }},
3126 
3127  { L("tmpl-rules "), &(command_entry_t){
3128  .func = command_tmpl_rules,
3129  .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]",
3130  .description = "Alter the tmpl parsing rules for subsequent tmpl parsing commands in the same command context"
3131  }},
3132  { L("touch "), &(command_entry_t){
3133  .func = command_touch,
3134  .usage = "touch <file>",
3135  .description = "Touch a file, updating its created timestamp. Useful for marking the completion of a series of tests"
3136  }},
3137  { L("value "), &(command_entry_t){
3139  .usage = "value <type> <string>",
3140  .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"
3141  }},
3142  { L("write "), &(command_entry_t){
3143  .func = command_write,
3144  .usage = "write <file>",
3145  .description = "Write the contents of the data buffer (as a raw binary string) to the specified file"
3146  }},
3147  { L("xlat "), &(command_entry_t){
3148  .func = command_xlat_normalise,
3149  .usage = "xlat <string>",
3150  .description = "Parse then print an xlat expansion, writing the normalised xlat expansion to the data buffer"
3151  }},
3152 
3153  { L("xlat_argv "), &(command_entry_t){
3154  .func = command_xlat_argv,
3155  .usage = "xlat_argv <string>",
3156  .description = "Parse then print an xlat expansion argv, writing the normalised xlat expansion arguments to the data buffer"
3157  }},
3158 
3159  { L("xlat_expr "), &(command_entry_t){
3160  .func = command_xlat_expr,
3161  .usage = "xlat_expr <string>",
3162  .description = "Parse then print an xlat expression, writing the normalised xlat expansion to the data buffer"
3163  }},
3164 
3165  { L("xlat_purify "), &(command_entry_t){
3166  .func = command_xlat_purify,
3167  .usage = "xlat_purify <string>",
3168  .description = "Parse, purify, then print an xlat expression, writing the normalised xlat expansion to the data buffer"
3169  }},
3170 
3171 };
3173 
3174 size_t process_line(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used,
3175  char *in, UNUSED size_t inlen)
3176 {
3177 
3178  command_entry_t *command;
3179  size_t match_len;
3180  char *p;
3181 
3182  p = in;
3183  fr_skip_whitespace(p);
3184  if (*p == '\0') RETURN_NOOP(data_used);
3185 
3186  DEBUG2("%s[%d]: %s", cc->filename, cc->lineno, p);
3187 
3188  /*
3189  * Look up the command by longest prefix
3190  */
3191  command = fr_table_value_by_longest_prefix(&match_len, commands, p, -1, NULL);
3192  if (!command) {
3193  fr_strerror_printf("Unknown command: %s", p);
3195  }
3196 
3197  /*
3198  * Skip processing the command
3199  */
3200  if (command->func == command_comment) RETURN_NOOP(data_used);
3201 
3202  p += match_len; /* Jump to after the command */
3203  fr_skip_whitespace(p); /* Skip any whitespace */
3204 
3205  /*
3206  * Feed the data buffer in as the command
3207  */
3208  if ((p[0] == '-') && ((p[1] == ' ') || (p[1] == '\0'))) {
3209  data_used = command->func(result, cc, data, data_used, data, data_used);
3210  }
3211  else {
3212  data_used = command->func(result, cc, data, data_used, p, strlen(p));
3213  }
3214 
3215  /*
3216  * Dump the contents of the error stack
3217  * to the data buffer.
3218  *
3219  * This is then what's checked in
3220  * subsequent match commands.
3221  */
3222  if (result->error_to_data) data_used = strerror_concat(data, COMMAND_OUTPUT_MAX);
3223 
3224  fr_assert((size_t)data_used < COMMAND_OUTPUT_MAX);
3225  data[data_used] = '\0'; /* Ensure the data buffer is \0 terminated */
3226 
3227  if (data_used) {
3228  DEBUG2("%s[%d]: --> %s (%zu bytes in buffer)", cc->filename, cc->lineno,
3229  fr_table_str_by_value(command_rcode_table, result->rcode, "<INVALID>"), data_used);
3230  } else {
3231  DEBUG2("%s[%d]: --> %s", cc->filename, cc->lineno,
3232  fr_table_str_by_value(command_rcode_table, result->rcode, "<INVALID>"));
3233  }
3234 
3235  talloc_free_children(cc->tmp_ctx);
3236 
3237  return data_used;
3238 }
3239 
3241 {
3242  if (fr_dict_free(&cc->test_internal_dict, __FILE__) < 0) {
3243  fr_perror("unit_test_attribute");
3244  return -1;
3245  }
3246  if (fr_dict_global_ctx_free(cc->test_gctx) < 0) {
3247  fr_perror("unit_test_attribute");
3248  return -1;
3249  }
3250  if (cc->fuzzer_dir >= 0) {
3251  close(cc->fuzzer_dir);
3252  cc->fuzzer_dir = -1;
3253  }
3254  return 0;
3255 }
3256 
3257 static command_file_ctx_t *command_ctx_alloc(TALLOC_CTX *ctx,
3258  command_config_t const *config, char const *path, char const *filename)
3259 {
3260  command_file_ctx_t *cc;
3261 
3262  cc = talloc_zero(ctx, command_file_ctx_t);
3263  talloc_set_destructor(cc, _command_ctx_free);
3264 
3265  cc->tmp_ctx = talloc_named_const(ctx, 0, "tmp_ctx");
3266  cc->path = talloc_strdup(cc, path);
3267  cc->filename = filename;
3268  cc->config = config;
3269 
3270  /*
3271  * Allocate a special buffer with poisoned regions
3272  * at either end.
3273  */
3275  talloc_free(cc);
3276  return NULL;
3277  }
3280 
3281  /*
3282  * Initialise a special temporary dictionary context
3283  *
3284  * Any protocol dictionaries loaded by "test-dictionary"
3285  * go in this context, and don't affect the main
3286  * dictionary context.
3287  */
3288  cc->test_gctx = fr_dict_global_ctx_init(cc, false, cc->config->dict_dir);
3289  if (!cc->test_gctx) {
3290  fr_perror("Failed allocating test dict_gctx");
3291  return NULL;
3292  }
3293 
3296  fr_perror("Failed loading test dict_gctx internal dictionary");
3297  return NULL;
3298  }
3299 
3300  fr_dict_global_ctx_dir_set(cc->path); /* Load new dictionaries relative to the test file */
3302 
3303  cc->fuzzer_dir = -1;
3304 
3306 
3307  return cc;
3308 }
3309 
3310 static void command_ctx_reset(command_file_ctx_t *cc, TALLOC_CTX *ctx)
3311 {
3312  talloc_free(cc->tmp_ctx);
3313  cc->tmp_ctx = talloc_named_const(ctx, 0, "tmp_ctx");
3314  cc->test_count = 0;
3315 
3316  if (fr_dict_free(&cc->test_internal_dict, __FILE__) < 0) {
3317  fr_perror("unit_test_attribute");
3318  }
3319 
3320  if (fr_dict_global_ctx_free(cc->test_gctx) < 0) fr_perror("unit_test_attribute");
3321 
3322  cc->test_gctx = fr_dict_global_ctx_init(cc, false, cc->config->dict_dir);
3324  fr_perror("Failed loading test dict_gctx internal dictionary");
3325  }
3326 
3327  if (cc->fuzzer_dir >= 0) {
3328  close(cc->fuzzer_dir);
3329  cc->fuzzer_dir = -1;
3330  }
3331 }
3332 
3333 static int process_file(bool *exit_now, TALLOC_CTX *ctx, command_config_t const *config,
3334  const char *root_dir, char const *filename, fr_dlist_head_t *lines)
3335 {
3336  int ret = 0;
3337  FILE *fp; /* File we're reading from */
3338  char buffer[8192]; /* Command buffer */
3339  char data[COMMAND_OUTPUT_MAX + 1]; /* Data written by previous command */
3340  ssize_t data_used = 0; /* How much data the last command wrote */
3341  static char path[PATH_MAX] = "";
3342  command_line_range_t *lr = NULL;
3343 
3344  command_file_ctx_t *cc;
3345 
3346  cc = command_ctx_alloc(ctx, config, root_dir, filename);
3347 
3348  /*
3349  * Open the file, or stdin
3350  */
3351  if (strcmp(filename, "-") == 0) {
3352  fp = stdin;
3353  filename = "<stdin>";
3354  } else {
3355  if (root_dir && *root_dir) {
3356  snprintf(path, sizeof(path), "%s/%s", root_dir, filename);
3357  } else {
3358  strlcpy(path, filename, sizeof(path));
3359  }
3360 
3361  fp = fopen(path, "r");
3362  if (!fp) {
3363  ERROR("Error opening test file \"%s\": %s", path, fr_syserror(errno));
3364  ret = -1;
3365  goto finish;
3366  }
3367 
3368  filename = path;
3369  }
3370 
3371  if (lines && !fr_dlist_empty(lines)) lr = fr_dlist_head(lines);
3372 
3373  /*
3374  * Loop over lines in the file or stdin
3375  */
3376  while (fgets(buffer, sizeof(buffer), fp) != NULL) {
3377  command_result_t result = { .rcode = RESULT_OK }; /* Reset to OK */
3378  char *p = strchr(buffer, '\n');
3379 
3381  cc->lineno++; /* The first line of the file becomes line 1 */
3382 
3383  if (lr) {
3384  if (cc->lineno > lr->end) {
3385  lr = fr_dlist_next(lines, lr);
3386  if (!lr) goto finish;
3387  }
3388 
3389  if (cc->lineno < lr->start) continue;
3390  }
3391 
3392  if (!p) {
3393  if (!feof(fp)) {
3394  ERROR("Line %d too long in %s/%s", cc->lineno, cc->path, cc->filename);
3395  ret = -1;
3396  goto finish;
3397  }
3398  } else {
3399  *p = '\0';
3400  }
3401 
3402  data_used = process_line(&result, cc, data, data_used, buffer, strlen(buffer));
3403  switch (result.rcode) {
3404  /*
3405  * Command completed successfully
3406  */
3407  case RESULT_OK:
3408  cc->test_count++;
3409  continue;
3410 
3411  /*
3412  * Did nothing (not a test)
3413  */
3414  case RESULT_NOOP:
3415  continue;
3416 
3417  /*
3418  * If this is a file, then break out of the loop
3419  * and cleanup, otherwise we need to find the
3420  * EOF marker in the input stream.
3421  */
3422  case RESULT_SKIP_FILE:
3423  if (fp != stdin) goto finish;
3424 
3425  /*
3426  * Skip over the input stream until we
3427  * find an eof command, or the stream
3428  * is closed.
3429  */
3430  while (fgets(buffer, sizeof(buffer), fp) != NULL) {
3431  command_entry_t *command;
3432  size_t match_len;
3433 
3434  command = fr_table_value_by_longest_prefix(&match_len, commands, buffer, -1, NULL);
3435  if (!command) {
3436  ERROR("%s[%d]: Unknown command: %s", cc->path, cc->lineno, p);
3437  ret = -1;
3438  goto finish;
3439  }
3440 
3441  if (command->func == command_eof) {
3442  command_ctx_reset(cc, ctx);
3443  break;
3444  }
3445  }
3446  break;
3447 
3448  /*
3449  * Fatal error parsing a command
3450  */
3451  case RESULT_PARSE_ERROR:
3452  case RESULT_COMMAND_ERROR:
3453  fr_perror("%s[%d]", filename, cc->lineno);
3454  ret = -1;
3455  goto finish;
3456 
3457  /*
3458  * Result didn't match what we expected
3459  */
3460  case RESULT_MISMATCH:
3461  {
3462  ret = EXIT_FAILURE;
3463  goto finish;
3464  }
3465 
3466  case RESULT_EXIT:
3467  ret = result.ret;
3468  *exit_now = true;
3469  goto finish;
3470 
3471  default:
3472  /*
3473  * If this happens, fix the damn command.
3474  */
3475  fr_assert_msg(false, "Command exited with invalid return code (%i)", result.rcode);
3476  ret = -1;
3477  goto finish;
3478  }
3479  }
3480 
3481 finish:
3482  if (fp && (fp != stdin)) fclose(fp);
3483 
3484  /*
3485  * Free any residual resources we loaded.
3486  */
3487  if (cc && (fr_dict_const_free(&cc->tmpl_rules.attr.dict_def, __FILE__) < 0)) {
3488  fr_perror("unit_test_attribute");
3489  ret = -1;
3490  }
3491 
3492  fr_dict_global_ctx_set(config->dict_gctx); /* Switch back to the main dict ctx */
3494  talloc_free(cc);
3495 
3496  return ret;
3497 }
3498 
3499 static void usage(char const *name)
3500 {
3501  INFO("usage: %s [options] (-|<filename>[:<lines>] [ <filename>[:<lines>]])", name);
3502  INFO("options:");
3503  INFO(" -d <raddb> Set user dictionary path (defaults to " RADDBDIR ").");
3504  INFO(" -D <dictdir> Set main dictionary path (defaults to " DICTDIR ").");
3505  INFO(" -x Debugging mode.");
3506  INFO(" -f Print features.");
3507  INFO(" -c Print commands.");
3508  INFO(" -h Print help text.");
3509  INFO(" -M Show talloc memory report.");
3510  INFO(" -p Allow xlat_purify");
3511  INFO(" -r <receipt_file> Create the <receipt_file> as a 'success' exit.");
3512  INFO("Where <filename> is a file containing one or more commands and '-' indicates commands should be read from stdin.");
3513  INFO("Ranges of <lines> may be specified in the format <start>[-[<end>]][,]");
3514 }
3515 
3516 static void features_print(CONF_SECTION *features)
3517 {
3518  CONF_PAIR *cp;
3519 
3520  INFO("features:");
3521  for (cp = cf_pair_find(features, CF_IDENT_ANY);
3522  cp;
3523  cp = cf_pair_find_next(features, cp, CF_IDENT_ANY)) {
3524  INFO(" %s %s", cf_pair_attr(cp), cf_pair_value(cp));
3525  }
3526 }
3527 
3528 static void commands_print(void)
3529 {
3530  size_t i;
3531 
3532  INFO("commands:");
3533  for (i = 0; i < commands_len; i++) {
3534  INFO(" %s:", ((command_entry_t const *)commands[i].value)->usage);
3535  INFO(" %s.", ((command_entry_t const *)commands[i].value)->description);
3536  INFO("%s", "");
3537  }
3538 }
3539 
3540 static int line_ranges_parse(TALLOC_CTX *ctx, fr_dlist_head_t *out, fr_sbuff_t *in)
3541 {
3542  static bool tokens[UINT8_MAX + 1] = { [','] = true , ['-'] = true };
3543  uint32_t max = 0;
3546 
3547  while (fr_sbuff_extend(in)) {
3548  fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
3549 
3550  MEM(lr = talloc_zero(ctx, command_line_range_t));
3552 
3553  fr_sbuff_out(&err, &lr->start, in);
3554  if (err != FR_SBUFF_PARSE_OK) {
3555  ERROR("Invalid line start number");
3556  error:
3558  return -1;
3559  }
3560  if (max > lr->start) {
3561  ERROR("Out of order line numbers (%u > %u) not allowed", max, lr->start);
3562  goto error;
3563  } else {
3564  max = lr->start;
3565  }
3566  lr->end = lr->start; /* Default to a single line */
3567  fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
3568 
3569  again:
3570  if (!fr_sbuff_extend(in)) break;
3571  if (!fr_sbuff_is_in_charset(in, tokens)) {
3572  ERROR("Unexpected text \"%pV\"",
3574  goto error;
3575  }
3576 
3577  fr_sbuff_switch(in, '\0') {
3578  /*
3579  * More ranges...
3580  */
3581  case ',':
3582  fr_sbuff_next(in);
3583  fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
3584  continue;
3585 
3586  /*
3587  * <start>-<end>
3588  */
3589  case '-':
3590  {
3591  fr_sbuff_next(in);
3592  fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
3593 
3594  /*
3595  * A bare '-' with no number means
3596  * run all remaining lines.
3597  */
3598  if (fr_sbuff_extend(in) == 0) {
3599  lr->end = UINT32_MAX;
3600  return 0;
3601  }
3602 
3603  fr_sbuff_out(&err, &lr->end, in);
3604  if (err != FR_SBUFF_PARSE_OK) {
3605  ERROR("Invalid line end number");
3606  goto error;
3607  }
3608  if (lr->end < lr->start) {
3609  ERROR("Line end must be >= line start (%u < %u)", lr->end, lr->start);
3610  goto error;
3611  }
3612  if (max > lr->end) {
3613  ERROR("Out of order line numbers (%u > %u) not allowed", max, lr->end);
3614  goto error;
3615  } else {
3616  max = lr->end;
3617  }
3618  fr_sbuff_adv_past_whitespace(in, SIZE_MAX, NULL);
3619  }
3620  goto again;
3621  }
3622  }
3623 
3624  return 0;
3625 }
3626 
3627 /**
3628  *
3629  * @hidecallgraph
3630  */
3631 int main(int argc, char *argv[])
3632 {
3633  int c;
3634  char const *receipt_file = NULL;
3635  CONF_SECTION *cs;
3636  int ret = EXIT_SUCCESS;
3637  TALLOC_CTX *autofree;
3638  TALLOC_CTX *thread_ctx;
3639  bool exit_now = false;
3640 
3642  .raddb_dir = RADDBDIR,
3643  .dict_dir = DICTDIR
3644  };
3645 
3646  char const *name;
3647  bool do_features = false;
3648  bool do_commands = false;
3649  bool do_usage = false;
3650  bool allow_purify = false;
3651 
3652  /*
3653  * Must be called first, so the handler is called last
3654  */
3656 
3658  thread_ctx = talloc_new(autofree);
3659 
3660 #ifndef NDEBUG
3661  if (fr_fault_setup(autofree, getenv("PANIC_ACTION"), argv[0]) < 0) {
3662  fr_perror("unit_test_attribute");
3663  goto cleanup;
3664  }
3665 #else
3667 #endif
3668 
3669  /*
3670  * Sync wallclock and cpu time so that we can find
3671  * uses of fr_time_[to|from]_* where
3672  * fr_unix_time_[to|from]_* should be used.
3673  *
3674  * If the wallclock/cpu offset is 0, then both sets
3675  * of macros produce the same result.
3676  */
3677  fr_time_start();
3678 
3679  /*
3680  * Allocate a root config section so we can write
3681  * out features and versions.
3682  */
3683  MEM(cs = cf_section_alloc(autofree, NULL, "unit_test_attribute", NULL));
3684  MEM(config.features = cf_section_alloc(cs, cs, "feature", NULL));
3685  dependency_features_init(config.features); /* Add build time features to the config section */
3686 
3687  name = argv[0];
3688 
3690  default_log.fd = STDOUT_FILENO;
3691  default_log.print_level = false;
3692 
3693  while ((c = getopt(argc, argv, "cd:D:F:fxMhpr:")) != -1) switch (c) {
3694  case 'c':
3695  do_commands = true;
3696  break;
3697 
3698  case 'd':
3699  config.raddb_dir = optarg;
3700  break;
3701 
3702  case 'D':
3703  config.dict_dir = optarg;
3704  break;
3705 
3706  case 'F':
3707  config.fuzzer_dir = optarg;
3708  break;
3709 
3710  case 'f':
3711  do_features = true;
3712  break;
3713 
3714  case 'x':
3715  fr_debug_lvl++;
3716  if (fr_debug_lvl > 2) default_log.print_level = true;
3717  break;
3718 
3719  case 'M':
3720  talloc_enable_leak_report();
3721  break;
3722 
3723  case 'r':
3724  receipt_file = optarg;
3725  break;
3726 
3727  case 'p':
3728  allow_purify = true;
3729  break;
3730 
3731  case 'h':
3732  default:
3733  do_usage = true; /* Just set a flag, so we can process extra -x args */
3734  break;
3735  }
3736  argc -= (optind - 1);
3737  argv += (optind - 1);
3738 
3739  if (do_usage) usage(name);
3740  if (do_features) features_print(config.features);
3741  if (do_commands) commands_print();
3742  if (do_usage || do_features || do_commands) {
3743  ret = EXIT_SUCCESS;
3744  goto cleanup;
3745  }
3746 
3747  if (receipt_file && (fr_unlink(receipt_file) < 0)) {
3748  fr_perror("unit_test_attribute");
3750  }
3751 
3752  /*
3753  * Mismatch between the binary and the libraries it depends on
3754  */
3756  fr_perror("unit_test_attribute");
3758  }
3759 
3760 #ifdef WITH_TLS
3761  /*
3762  * OpenSSL can only be initialised once during the lifetime
3763  * of a process. Initialise it here so that we don't attempt
3764  * to unload and load it multiple times.
3765  */
3766  if (fr_openssl_init() < 0) {
3767  fr_perror("unit_test_attribute");
3769  }
3770 #endif
3771 
3772  modules_init(NULL);
3773 
3774  dl_loader = dl_loader_init(autofree, NULL, false, false);
3775  if (!dl_loader) {
3776  fr_perror("unit_test_attribute");
3778  }
3779 
3780  config.dict_gctx = fr_dict_global_ctx_init(NULL, true, config.dict_dir);
3781  if (!config.dict_gctx) {
3782  fr_perror("unit_test_attribute");
3784  }
3785 
3787  fr_perror("unit_test_attribute");
3789  }
3790 
3791  /*
3792  * Load the custom dictionary
3793  */
3794  if (fr_dict_read(config.dict, config.raddb_dir, FR_DICTIONARY_FILE) == -1) {
3795  fr_perror("unit_test_attribute");
3797  }
3798 
3799  /*
3800  * Always needed so we can load the list attributes
3801  * otherwise the tmpl_tokenize code fails.
3802  */
3803  if (request_global_init() < 0) {
3804  fr_perror("unit_test_attribute");
3806  }
3807 
3808  /*
3809  * Initialise the interpreter, registering operations.
3810  * Needed because some keywords also register xlats.
3811  */
3812  if (unlang_global_init() < 0) {
3813  fr_perror("unit_test_attribute");
3815  }
3816 
3817  /*
3818  * Create a dummy event list
3819  */
3820  if (allow_purify) {
3821  el = fr_event_list_alloc(autofree, NULL, NULL);
3822  fr_assert(el != NULL);
3823 
3824  /*
3825  * Simulate thread specific instantiation
3826  */
3828  if (xlat_thread_instantiate(thread_ctx, el) < 0) EXIT_WITH_FAILURE;
3829  }
3830 
3831  unlang_thread_instantiate(thread_ctx);
3832 
3833  if (!xlat_func_register(NULL, "test", xlat_test, FR_TYPE_NULL)) {
3834  ERROR("Failed registering xlat");
3836  }
3837 
3838  /*
3839  * Disable hostname lookups, so we don't produce spurious DNS
3840  * queries, and there's no chance of spurious failures if
3841  * it takes a long time to get a response.
3842  */
3844 
3845  /*
3846  * Read tests from stdin
3847  */
3848  if (argc < 2) {
3849  ret = process_file(&exit_now, autofree, &config, name, "-", NULL);
3850 
3851  /*
3852  * ...or process each file in turn.
3853  */
3854  } else {
3855  int i;
3856 
3857  for (i = 1; i < argc; i++) {
3858  char *dir = NULL, *file;
3859  fr_sbuff_t in = FR_SBUFF_IN(argv[i], strlen(argv[i]));
3860  fr_sbuff_term_t dir_sep = FR_SBUFF_TERMS(
3861  L("/"),
3862  L(":")
3863  );
3864  fr_sbuff_marker_t file_start, file_end, dir_end;
3865  fr_dlist_head_t lines;
3866 
3867  fr_sbuff_marker(&file_start, &in);
3868  fr_sbuff_marker(&file_end, &in);
3869  fr_sbuff_marker(&dir_end, &in);
3870  fr_sbuff_set(&file_end, fr_sbuff_end(&in));
3871 
3872  fr_dlist_init(&lines, command_line_range_t, entry);
3873 
3874  while (fr_sbuff_extend(&in)) {
3875  fr_sbuff_adv_until(&in, SIZE_MAX, &dir_sep, '\0');
3876 
3877  fr_sbuff_switch(&in, '\0') {
3878  case '/':
3879  fr_sbuff_set(&dir_end, &in);
3880  fr_sbuff_advance(&in, 1);
3881  fr_sbuff_set(&file_start, &in);
3882  break;
3883 
3884  case ':':
3885  fr_sbuff_set(&file_end, &in);
3886  fr_sbuff_advance(&in, 1);
3887  if (line_ranges_parse(autofree, &lines, &in) < 0) EXIT_WITH_FAILURE;
3888  break;
3889 
3890  default:
3891  fr_sbuff_set(&file_end, &in);
3892  break;
3893  }
3894  }
3895 
3897  fr_sbuff_current(&file_start), fr_sbuff_diff(&file_end, &file_start));
3898  if (fr_sbuff_used(&dir_end)) dir = talloc_bstrndup(autofree,
3899  fr_sbuff_start(&in),
3900  fr_sbuff_used(&dir_end));
3901 
3902  ret = process_file(&exit_now, autofree, &config, dir, file, &lines);
3903  talloc_free(dir);
3904  talloc_free(file);
3905  fr_dlist_talloc_free(&lines);
3906 
3907  if ((ret != 0) || exit_now) break;
3908  }
3909  }
3910 
3911  /*
3912  * Try really hard to free any allocated
3913  * memory, so we get clean talloc reports.
3914  */
3915 cleanup:
3916  /*
3917  * Ensure all thread local memory is cleaned up
3918  * at the appropriate time. This emulates what's
3919  * done with worker/network threads in the
3920  * scheduler.
3921  */
3923 
3924 #ifdef WITH_TLS
3925  fr_openssl_free();
3926 #endif
3927 
3928  /*
3929  * dl_loader check needed as talloc_free
3930  * returns -1 on failure.
3931  */
3932  if (dl_loader && (talloc_free(dl_loader) < 0)) {
3933  fr_perror("unit_test_attribute - dl_loader - "); /* Print free order issues */
3935  }
3936  if (fr_dict_free(&config.dict, __FILE__) < 0) {
3937  fr_perror("unit_test_attribute");
3939  }
3940 
3942 
3944 
3945  if (receipt_file && (ret == EXIT_SUCCESS) && (fr_touch(NULL, receipt_file, 0644, true, 0755) <= 0)) {
3946  fr_perror("unit_test_attribute");
3948  }
3949 
3950  /*
3951  * Explicitly free the autofree context
3952  * to make errors less confusing.
3953  */
3954  if (talloc_free(autofree) < 0) {
3955  fr_perror("unit_test_attribute");
3957  }
3958 
3959  /*
3960  * Ensure our atexit handlers run before any other
3961  * atexit handlers registered by third party libraries.
3962  */
3964 
3965  return ret;
3966 }
#define true
Definition: abinary.c:57
static int const char char buffer[256]
Definition: acutest.h:574
int const char * file
Definition: acutest.h:702
int fr_atexit_global_setup(void)
Setup the atexit handler, should be called at the start of a program's execution.
Definition: atexit.c:160
int fr_atexit_global_trigger_all(void)
Cause all global free triggers to fire.
Definition: atexit.c:286
#define fr_atexit_thread_trigger_all(...)
Definition: atexit.h:233
ssize_t fr_base64_encode_nstd(fr_sbuff_t *out, fr_dbuff_t *in, bool add_padding, char const alphabet[static UINT8_MAX])
Base 64 encode binary data.
Definition: base64.c:326
char const fr_base64_url_alphabet_encode[UINT8_MAX]
Definition: base64.c:173
static fr_dict_t * dict
Definition: fuzzer.c:46
#define CMD_MAX_ARGV
Definition: radmin.c:150
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition: build.h:165
#define RCSID(id)
Definition: build.h:444
#define L(_str)
Helper for initialising arrays of string literals.
Definition: build.h:207
#define UNUSED
Definition: build.h:313
#define NUM_ELEMENTS(_t)
Definition: build.h:335
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:2228
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:2387
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:1893
Configuration AVP similar to a fr_pair_t.
Definition: cf_priv.h:70
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:89
char const * cf_pair_attr(CONF_PAIR const *pair)
Return the attr of a CONF_PAIR.
Definition: cf_util.c:1495
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:1356
char const * cf_pair_value(CONF_PAIR const *pair)
Return the value of a CONF_PAIR.
Definition: cf_util.c:1511
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:1370
#define cf_lineno_set(_ci, _lineno)
Definition: cf_util.h:128
#define cf_section_alloc(_ctx, _parent, _name1, _name2)
Definition: cf_util.h:137
#define cf_filename_set(_ci, _filename)
Definition: cf_util.h:125
#define CF_IDENT_ANY
Definition: cf_util.h:78
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:1019
void fr_command_debug(FILE *fp, fr_cmd_t *head)
Definition: command.c:1602
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:725
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:1290
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
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition: dbuff.h:509
static void * fr_dcursor_current(fr_dcursor_t *cursor)
Return the item the cursor current points to.
Definition: dcursor.h:336
void fr_disable_null_tracking_on_free(TALLOC_CTX *ctx)
Disable the null tracking context when a talloc chunk is freed.
Definition: debug.c:1180
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:1215
#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:208
void dependency_features_init(CONF_SECTION *cs)
Initialise core feature flags.
Definition: dependency.c:177
static char const * spaces
Definition: dependency.c:364
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
#define DEBUG(fmt,...)
Definition: dhcpclient.c:39
int fr_dict_internal_afrom_file(fr_dict_t **out, char const *internal_name, char const *dependent)
(Re-)Initialize the special internal dictionary
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:4077
static fr_slen_t err
Definition: dict.h:645
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:2860
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:1921
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
int fr_dict_str_to_argv(char *str, char **argv, int max_argc)
Definition: dict_tokenize.c:80
void fr_dict_debug(fr_dict_t const *dict)
Definition: dict_print.c:258
void fr_dict_global_ctx_set(fr_dict_gctx_t const *gctx)
Set a new, active, global dictionary context.
Definition: dict_util.c:4047
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition: dict_util.c:1997
int fr_dict_free(fr_dict_t **dict, char const *dependent)
Decrement the reference count on a previously loaded dictionary.
Definition: dict_util.c:3581
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:3565
fr_dict_t const * fr_dict_internal(void)
Definition: dict_util.c:4204
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:4063
int fr_dict_parse_str(fr_dict_t *dict, char *buf, fr_dict_attr_t const *parent)
int fr_dict_read(fr_dict_t *dict, char const *dict_dir, char const *filename)
Read supplementary attribute definitions into an existing dictionary.
fr_dict_attr_err_t
Errors returned by attribute lookup functions.
Definition: dict.h:272
@ FR_DICT_ATTR_OK
No error.
Definition: dict.h:273
static fr_slen_t in
Definition: dict.h:645
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:3984
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:885
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:62
Module handle.
Definition: dl.h:58
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition: dlist.h:260
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:555
static void fr_dlist_talloc_free(fr_dlist_head_t *head)
Free all items in a doubly linked list (with talloc)
Definition: dlist.h:908
static bool fr_dlist_empty(fr_dlist_head_t const *list_head)
Check whether a list has any items.
Definition: dlist.h:501
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:486
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:378
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:639
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:1225
int fr_unlink(char const *filename)
Remove a regular file from the filesystem.
Definition: file.c:348
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:304
char * fr_realpath(TALLOC_CTX *ctx, char const *path, ssize_t len)
Convenience wrapper around realpath.
Definition: file.c:265
bool fr_hostname_lookups
hostname -> IP lookups?
Definition: inet.c:52
bool fr_reverse_lookups
IP -> hostname lookups?
Definition: inet.c:51
void unlang_global_free(void)
Definition: base.c:134
int unlang_global_init(void)
Definition: base.c:74
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:2892
talloc_free(reap)
Stores all information relating to an event list.
Definition: event.c:411
int fr_debug_lvl
Definition: log.c:42
fr_log_t default_log
Definition: log.c:290
@ L_DST_STDOUT
Log to stdout.
Definition: log.h:78
fr_type_t
Definition: merged_model.c:80
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_MAX
Number of defined data types.
Definition: merged_model.c:130
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
Definition: merged_model.c:81
@ FR_TYPE_DATE
Unix time stamp, always has value >2^31.
Definition: merged_model.c:111
@ FR_TYPE_GROUP
A grouping of other attributes.
Definition: merged_model.c:124
unsigned int uint32_t
Definition: merged_model.c:33
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
ssize_t fr_slen_t
Definition: merged_model.c:35
unsigned long int size_t
Definition: merged_model.c:25
#define UINT8_MAX
Definition: merged_model.c:32
fr_sbuff_parse_error_t
Definition: merged_model.c:45
@ FR_SBUFF_PARSE_OK
No error.
Definition: merged_model.c:46
static uint8_t depth(fr_minmax_heap_index_t i)
Definition: minmax_heap.c:83
#define fr_skip_whitespace(_p)
Skip whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition: misc.h:59
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition: pair.c:278
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.
Definition: pair_legacy.c:150
int fr_pair_list_afrom_file(TALLOC_CTX *ctx, fr_dict_t const *dict, fr_pair_list_t *out, FILE *fp, bool *pfiledone)
Read valuepairs from the fp up to End-Of-File.
Definition: pair_legacy.c:572
struct fr_pair_parse_s fr_pair_parse_t
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:188
void * fr_proto_next_encodable(fr_dlist_head_t *list, 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
static TALLOC_CTX * autofree
Definition: radclient-ng.c:104
static bool done
Definition: radclient.c:80
#define DEBUG2(fmt,...)
Definition: radclient.h:43
#define INFO(fmt,...)
Definition: radict.c:54
static bool cleanup
Definition: radsniff.c:60
fr_dict_attr_t const * request_attr_request
Definition: request.c:41
void request_global_free(void)
Definition: request.c:713
int request_global_init(void)
Definition: request.c:698
static char const * name
fr_slen_t fr_sbuff_out_bool(bool *out, fr_sbuff_t *in)
See if the string contains a truth value.
Definition: sbuff.c:1074
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:1811
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:2047
#define fr_sbuff_start(_sbuff_or_marker)
#define fr_sbuff_out_by_longest_prefix(_match_len, _out, _table, _sbuff, _def)
#define fr_sbuff_set(_dst, _src)
#define fr_sbuff_diff(_a, _b)
#define FR_SBUFF_IN(_start, _len_or_end)
#define fr_sbuff_adv_past_whitespace(_sbuff, _len, _tt)
#define fr_sbuff_current(_sbuff_or_marker)
#define FR_SBUFF_TERMS(...)
Initialise a terminal structure with a list of sorted strings.
Definition: sbuff.h:167
#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.
Definition: merged_model.c:161
tmpl_xlat_rules_t xlat
Rules/data for parsing xlats.
Definition: tmpl.h:345
bool new_functions
new function syntax
Definition: tmpl.h:335
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:344
struct tmpl_rules_s tmpl_rules_t
Definition: tmpl.h:236
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:333
Optional arguments passed to vp_tmpl functions.
Definition: tmpl.h:341
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:41
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:1163
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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:97
int fd
File descriptor to write messages to.
Definition: log.h:112
bool print_level
sometimes we don't want log levels printed
Definition: log.h:106
fr_dict_attr_t const * list_def
Default list to use with unqualified attribute reference.
Definition: tmpl.h:307
uint8_t allow_unresolved
Allow attributes that look valid but were not found in the dictionaries.
Definition: tmpl.h:319
uint8_t allow_foreign
Allow arguments not found in dict_def.
Definition: tmpl.h:327
fr_dict_t const * dict_def
Default dictionary to use with unqualified attribute references.
Definition: tmpl.h:285
uint8_t allow_unknown
Allow unknown attributes i.e.
Definition: tmpl.h:316
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_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition: table.h:253
An element in a lexicographically sorted array of name to num mappings.
Definition: table.h:45
An element in a lexicographically sorted array of name to ptr mappings.
Definition: table.h:61
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition: talloc.c:452
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition: talloc.h:51
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition: test_point.h:84
fr_tp_proto_decode_t func
Decoder for proto layer.
Definition: test_point.h:67
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition: test_point.h:92
fr_dcursor_iter_t next_encodable
Iterator to use to select attributes to encode.
Definition: test_point.h:94
fr_tp_proto_encode_t func
Encoder for proto layer.
Definition: test_point.h:75
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition: test_point.h:74
fr_pair_decode_t func
Decoder for pairs.
Definition: test_point.h:85
fr_pair_encode_t func
Encoder for pairs.
Definition: test_point.h:93
fr_test_point_ctx_alloc_t test_ctx
Allocate a test ctx for the encoder.
Definition: test_point.h:66
Entry point for pair decoders.
Definition: test_point.h:83
Entry point for pair encoders.
Definition: test_point.h:91
Entry point for protocol decoders.
Definition: test_point.h:65
Entry point for protocol encoders.
Definition: test_point.h:73
int fr_time_start(void)
Initialize the local time.
Definition: time.c:150
const bool fr_assignment_op[T_TOKEN_LAST]
Definition: token.c:168
fr_table_num_ordered_t const fr_tokens_table[]
Definition: token.c:33
enum fr_token fr_token_t
@ T_AND
Definition: token.h:55
@ T_INVALID
Definition: token.h:39
@ T_SUB
Definition: token.h:52
@ T_XOR
Definition: token.h:58
@ T_DIV
Definition: token.h:54
@ T_MOD
Definition: token.h:60
@ T_ADD
Definition: token.h:51
@ T_DOUBLE_QUOTED_STRING
Definition: token.h:121
@ T_MUL
Definition: token.h:53
@ T_OR
Definition: token.h:56
close(uq->fd)
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)
int fuzzer_dir
File descriptor pointing to a a directory to write fuzzer output.
#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.
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_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 void commands_print(void)
#define POISONED_BUFFER_END(_p)
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_rules(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, size_t inlen)
static size_t command_fuzzer_out(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
Enable fuzzer output.
static ssize_t encode_rfc(char *buffer, uint8_t *output, size_t outlen)
static dl_t * dl
static void command_ctx_reset(command_file_ctx_t *cc, TALLOC_CTX *ctx)
static 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 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.
static void usage(char const *name)
ssize_t last_ret
Last return value.
#define ASAN_POISON_MEMORY_REGION(_start, _end)
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 int decode_attr(char *buffer, char **endptr)
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)
fr_dlist_t entry
Entry in the dlist.
uint8_t * buffer_end
Where the non-poisoned region of the buffer ends.
static fr_table_ptr_sorted_t commands[]
#define RETURN_NOOP(_len)
TALLOC_CTX * tmp_ctx
Talloc context for test points.
static size_t command_radmin_tab(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, UNUSED size_t inlen)
static size_t command_calc_nary(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Perform calculations on multi-valued ops.
static size_t command_dictionary_dump(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, size_t data_used, UNUSED char *in, UNUSED size_t inlen)
Print the currently loaded dictionary.
fr_dict_gctx_t const * test_gctx
Dictionary context for test dictionaries.
static 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.
static fr_cmd_t * command_head
static size_t command_write(command_result_t *result, command_file_ctx_t *cc, char *data, size_t data_used, char *in, size_t inlen)
char * path
Current path we're operating in.
static int poisoned_buffer_allocate(TALLOC_CTX *ctx, uint8_t **buff, size_t size)
Allocate a special buffer with poisoned memory regions at the start and end.
#define DEFAULT_BUFFER_SIZE
Default buffer size for a command_file_ctx_t.
static size_t 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 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)
static command_file_ctx_t * command_ctx_alloc(TALLOC_CTX *ctx, command_config_t const *config, char const *path, char const *filename)
#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 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.
static const fr_token_t token2op[UINT8_MAX+1]
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 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 RETURN_EXIT(_ret)
static fr_event_list_t * el
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.
char const * fuzzer_dir
Where to write fuzzer files.
#define EXIT_WITH_FAILURE
static size_t command_pair(command_result_t *result, command_file_ctx_t *cc, char *data, UNUSED size_t data_used, char *in, size_t inlen)
Parse an print an attribute pair or pair list.
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 ssize_t encode_data(char *p, uint8_t *output, size_t outlen)
#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.
#define ASAN_UNPOISON_MEMORY_REGION(_start, _end)
#define RETURN_SKIP_FILE()
command_func_t func
static ssize_t command_tmpl_rule_attr_parent(UNUSED TALLOC_CTX *ctx, tmpl_rules_t *rules, fr_sbuff_t *value)
static int dictionary_load_common(command_result_t *result, command_file_ctx_t *cc, char *in, char const *default_subdir)
Common dictionary load function.
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 parse_typed_value(command_result_t *result, fr_value_box_t *box, char const **out, char const *in, size_t inlen)
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)
command_rcode_t
@ 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_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.
char const * description
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.
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 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: compile.c:4965
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_argv(TALLOC_CTX *ctx, xlat_exp_head_t **head, fr_sbuff_t *in, xlat_t const *xlat, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules, bool comma, bool allow_attr)
Tokenize an xlat expansion into a series of XLAT_TYPE_CHILD arguments.
static fr_slen_t head
Definition: xlat.h:408
int xlat_instantiate(void)
Call instantiation functions for all registered, "permanent" xlats.
Definition: xlat_inst.c:508
int xlat_flatten_compiled_argv(TALLOC_CTX *ctx, xlat_exp_head_t ***argv, xlat_exp_head_t *head)
Turn xlat_tokenize_argv() into an argv[] array, and nuke the input list.
Definition: xlat_eval.c:1538
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:439
int xlat_purify(xlat_exp_head_t *head, unlang_interpret_t *intp)
Purify an xlat.
Definition: xlat_purify.c:194
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, fr_value_box_safe_for_t literals_safe_for)
Tokenize an xlat expansion.
xlat_action_t
Definition: xlat.h:35
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition: xlat.h:41
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:3023
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:3017
return fr_table_value_by_longest_prefix(NULL, schemes, fr_sbuff_start(&sbuff), fr_sbuff_used(&sbuff), def)
#define FR_DICTIONARY_FILE
Definition: conf.h:7
#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:568
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
Definition: pair_inline.c:113
#define PAIR_LIST_VERIFY(_x)
Definition: pair.h:193
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:211
static fr_slen_t parent
Definition: pair.h:844
void fr_perror(char const *fmt,...)
Print the current error to stderr with a prefix.
Definition: strerror.c:733
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition: strerror.c:577
char const * fr_strerror_peek(void)
Get the last library error.
Definition: strerror.c:627
char const * fr_strerror_pop(void)
Pop the last library error.
Definition: strerror.c:681
#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
#define fr_type_is_null(_x)
Definition: types.h:326
int fr_check_lib_magic(uint64_t magic)
Check if the application linking to the library has the correct magic number.
Definition: version.c:40
#define RADIUSD_MAGIC_NUMBER
Definition: version.h:81
fr_sbuff_escape_rules_t fr_value_escape_double
Definition: value.c:350
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, bool tainted)
Convert string value to a fr_value_box_t type.
Definition: value.c:4751
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, bool tainted)
Definition: value.c:5264
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:5301
int8_t fr_value_box_cmp(fr_value_box_t const *a, fr_value_box_t const *b)
Compare two values.
Definition: value.c:640
fr_sbuff_parse_rules_t const value_parse_rules_bareword_unquoted
Default formatting rules.
Definition: value.c:480
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:3521
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:5489
fr_sbuff_parse_rules_t const value_parse_rules_double_quoted
Definition: value.c:547
void fr_value_box_clear(fr_value_box_t *data)
Clear/free any existing value and metadata.
Definition: value.c:3672
fr_sbuff_unescape_rules_t fr_value_unescape_double
Definition: value.c:266
static fr_slen_t data
Definition: value.h:1259
#define fr_box_strvalue_len(_val, _len)
Definition: value.h:279
static size_t char fr_sbuff_t size_t inlen
Definition: value.h:984
int nonnull(2, 5))
#define fr_value_box_init(_vb, _type, _enumv, _tainted)
Initialise a fr_value_box_t.
Definition: value.h:574
static size_t char ** out
Definition: value.h:984
An xlat calling ctx.
Definition: xlat_ctx.h:42
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:195