2 * unittest.c Unit test wrapper for the RADIUS daemon.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2000-2013 The FreeRADIUS server project
21 * Copyright 2013 Alan DeKok <aland@ox.org>
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/state.h>
29 #include <freeradius-devel/rad_assert.h>
40 char const *progname = NULL;
41 char const *radacct_dir = NULL;
42 char const *radlog_dir = NULL;
43 char const *radlib_dir = NULL;
44 bool log_stripped_names = false;
46 static bool memory_report = false;
47 static bool filedone = false;
49 char const *radiusd_version = "FreeRADIUS Version " RADIUSD_VERSION_STRING
50 #ifdef RADIUSD_VERSION_COMMIT
51 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
53 ", for host " HOSTINFO ", built on " __DATE__ " at " __TIME__;
58 static void usage(int);
60 void listen_free(UNUSED rad_listen_t **head)
66 static rad_listen_t *listen_alloc(void *ctx)
70 this = talloc_zero(ctx, rad_listen_t);
71 if (!this) return NULL;
73 this->type = RAD_LISTEN_AUTH;
81 * We probably don't care about this. We can always add
84 this->data = talloc_zero(this, listen_socket_t);
93 static RADCLIENT *client_alloc(void *ctx)
97 client = talloc_zero(ctx, RADCLIENT);
98 if (!client) return NULL;
103 static REQUEST *request_setup(FILE *fp)
111 * Create and initialize the new request.
113 request = request_alloc(NULL);
114 gettimeofday(&now, NULL);
115 request->timestamp = now.tv_sec;
117 request->packet = rad_alloc(request, false);
118 if (!request->packet) {
120 talloc_free(request);
123 request->packet->timestamp = now;
125 request->reply = rad_alloc(request, false);
126 if (!request->reply) {
128 talloc_free(request);
132 request->listener = listen_alloc(request);
133 request->client = client_alloc(request);
137 request->master_state = REQUEST_ACTIVE;
138 request->child_state = REQUEST_RUNNING;
139 request->handle = NULL;
140 request->server = talloc_typed_strdup(request, "default");
142 request->root = &main_config;
145 * Read packet from fp
147 if (fr_pair_list_afrom_file(request->packet, &request->packet->vps, fp, &filedone) < 0) {
148 fr_perror("unittest");
149 talloc_free(request);
154 * Set the defaults for IPs, etc.
156 request->packet->code = PW_CODE_ACCESS_REQUEST;
158 request->packet->src_ipaddr.af = AF_INET;
159 request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
160 request->packet->src_port = 18120;
162 request->packet->dst_ipaddr.af = AF_INET;
163 request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
164 request->packet->dst_port = 1812;
167 * Copied from radclient
169 * Fix up Digest-Attributes issues
171 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
173 vp = fr_cursor_next(&cursor)) {
175 * Double quoted strings get marked up as xlat expansions,
176 * but we don't support that here.
178 if (vp->type == VT_XLAT) {
179 vp->vp_strvalue = vp->value.xlat;
180 vp->value.xlat = NULL;
184 if (!vp->da->vendor) switch (vp->da->attr) {
189 * Allow it to set the packet type in
190 * the attributes read from the file.
193 request->packet->code = vp->vp_integer;
196 case PW_PACKET_DST_PORT:
197 request->packet->dst_port = (vp->vp_integer & 0xffff);
200 case PW_PACKET_DST_IP_ADDRESS:
201 request->packet->dst_ipaddr.af = AF_INET;
202 request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
203 request->packet->dst_ipaddr.prefix = 32;
206 case PW_PACKET_DST_IPV6_ADDRESS:
207 request->packet->dst_ipaddr.af = AF_INET6;
208 request->packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
209 request->packet->dst_ipaddr.prefix = 128;
212 case PW_PACKET_SRC_PORT:
213 request->packet->src_port = (vp->vp_integer & 0xffff);
216 case PW_PACKET_SRC_IP_ADDRESS:
217 request->packet->src_ipaddr.af = AF_INET;
218 request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
219 request->packet->src_ipaddr.prefix = 32;
222 case PW_PACKET_SRC_IPV6_ADDRESS:
223 request->packet->src_ipaddr.af = AF_INET6;
224 request->packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
225 request->packet->src_ipaddr.prefix = 128;
228 case PW_CHAP_PASSWORD: {
229 int i, already_hex = 0;
232 * If it's 17 octets, it *might* be already encoded.
233 * Or, it might just be a 17-character password (maybe UTF-8)
234 * Check it for non-printable characters. The odds of ALL
235 * of the characters being 32..255 is (1-7/8)^17, or (1/8)^17,
236 * or 1/(2^51), which is pretty much zero.
238 if (vp->vp_length == 17) {
239 for (i = 0; i < 17; i++) {
240 if (vp->vp_octets[i] < 32) {
248 * Allow the user to specify ASCII or hex CHAP-Password
254 len = len2 = vp->vp_length;
255 if (len2 < 17) len2 = 17;
257 p = talloc_zero_array(vp, uint8_t, len2);
259 memcpy(p, vp->vp_strvalue, len);
261 rad_chap_encode(request->packet,
263 fr_rand() & 0xff, vp);
270 case PW_DIGEST_REALM:
271 case PW_DIGEST_NONCE:
272 case PW_DIGEST_METHOD:
275 case PW_DIGEST_ALGORITHM:
276 case PW_DIGEST_BODY_DIGEST:
277 case PW_DIGEST_CNONCE:
278 case PW_DIGEST_NONCE_COUNT:
279 case PW_DIGEST_USER_NAME:
285 p = talloc_array(vp, uint8_t, vp->vp_length + 2);
287 memcpy(p + 2, vp->vp_octets, vp->vp_length);
288 p[0] = vp->da->attr - PW_DIGEST_REALM + 1;
290 p[1] = vp->vp_length;
292 da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0);
293 rad_assert(da != NULL);
297 * Re-do fr_pair_value_memsteal ourselves,
298 * because we play games with
299 * vp->da, and fr_pair_value_memsteal goes
300 * to GREAT lengths to sanitize
301 * and fix and change and
302 * double-check the various
305 memcpy(&q, &vp->vp_octets, sizeof(q));
308 vp->vp_octets = talloc_steal(vp, p);
316 } /* loop over the VP's we read in */
319 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
321 vp = fr_cursor_next(&cursor)) {
323 * Take this opportunity to verify all the VALUE_PAIRs are still valid.
325 if (!talloc_get_type(vp, VALUE_PAIR)) {
326 ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
328 fr_log_talloc_report(vp);
332 vp_print(fr_log_fp, vp);
338 * Build the reply template from the request.
340 request->reply->sockfd = request->packet->sockfd;
341 request->reply->dst_ipaddr = request->packet->src_ipaddr;
342 request->reply->src_ipaddr = request->packet->dst_ipaddr;
343 request->reply->dst_port = request->packet->src_port;
344 request->reply->src_port = request->packet->dst_port;
345 request->reply->id = request->packet->id;
346 request->reply->code = 0; /* UNKNOWN code */
347 memcpy(request->reply->vector, request->packet->vector,
348 sizeof(request->reply->vector));
349 request->reply->vps = NULL;
350 request->reply->data = NULL;
351 request->reply->data_len = 0;
356 request->log.lvl = rad_debug_lvl;
357 request->log.func = vradlog_request;
359 request->username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
360 request->password = fr_pair_find_by_num(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
366 static void print_packet(FILE *fp, RADIUS_PACKET *packet)
376 fprintf(fp, "%s\n", fr_packet_codes[packet->code]);
378 for (vp = fr_cursor_init(&cursor, &packet->vps);
380 vp = fr_cursor_next(&cursor)) {
382 * Take this opportunity to verify all the VALUE_PAIRs are still valid.
384 if (!talloc_get_type(vp, VALUE_PAIR)) {
385 ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
387 fr_log_talloc_report(vp);
397 #include <freeradius-devel/modpriv.h>
400 * %{poke:sql.foo=bar}
402 static ssize_t xlat_poke(UNUSED void *instance, REQUEST *request,
403 char const *fmt, char *out, size_t outlen)
408 module_instance_t *mi;
410 CONF_SECTION *modules;
412 CONF_PARSER const *variables;
415 rad_assert(outlen > 1);
416 rad_assert(request != NULL);
417 rad_assert(fmt != NULL);
418 rad_assert(out != NULL);
422 modules = cf_section_sub_find(request->root->config, "modules");
423 if (!modules) return 0;
425 buffer = talloc_strdup(request, fmt);
426 if (!buffer) return 0;
428 p = strchr(buffer, '.');
433 mi = module_find(modules, buffer);
435 RDEBUG("Failed finding module '%s'", buffer);
443 RDEBUG("Failed finding '=' in string '%s'", fmt);
449 if (strchr(p, '.') != NULL) {
450 RDEBUG("Can't do sub-sections right now");
454 cp = cf_pair_find(mi->cs, p);
456 RDEBUG("No such item '%s'", p);
461 * Copy the old value to the output buffer, that way
462 * tests can restore it later, if they need to.
464 len = strlcpy(out, cf_pair_value(cp), outlen);
466 if (cf_pair_replace(mi->cs, cp, q) < 0) {
467 RDEBUG("Failed replacing pair");
471 base = mi->insthandle;
472 variables = mi->entry->module->config;
475 * Handle the known configuration parameters.
477 for (i = 0; variables[i].name != NULL; i++) {
480 if (variables[i].type == PW_TYPE_SUBSECTION) continue;
481 /* else it's a CONF_PAIR */
484 * Not the pair we want. Skip it.
486 if (strcmp(variables[i].name, p) != 0) continue;
488 if (variables[i].data) {
489 data = variables[i].data; /* prefer this. */
491 data = ((char *)base) + variables[i].offset;
493 DEBUG2("Internal sanity check 2 failed in cf_section_parse");
498 * Parse the pair we found, or a default value.
500 ret = cf_item_parse(mi->cs, variables[i].name, variables[i].type, data, variables[i].dflt);
502 DEBUG2("Failed inserting new value into module instance data");
505 break; /* we found it, don't do any more */
515 * Read a file compose of xlat's and expected results
517 static bool do_xlats(char const *filename, FILE *fp)
528 * Create and initialize the new request.
530 request = request_alloc(NULL);
531 gettimeofday(&now, NULL);
532 request->timestamp = now.tv_sec;
534 request->log.lvl = rad_debug_lvl;
535 request->log.func = vradlog_request;
539 while (fgets(input, sizeof(input), fp) != NULL) {
543 * Ignore blank lines and comments
546 while (isspace((int) *p)) p++;
548 if (*p < ' ') continue;
549 if (*p == '#') continue;
554 fprintf(stderr, "Line %d too long in %s\n",
556 TALLOC_FREE(request);
566 if (strncmp(input, "xlat ", 5) == 0) {
568 char const *error = NULL;
569 char *fmt = talloc_typed_strdup(NULL, input + 5);
572 slen = xlat_tokenize(fmt, fmt, &head, &error);
575 snprintf(output, sizeof(output), "ERROR offset %d '%s'", (int) -slen, error);
579 if (input[slen + 5] != '\0') {
581 snprintf(output, sizeof(output), "ERROR offset %d 'Too much text' ::%s::", (int) slen, input + slen + 5);
585 len = radius_xlat_struct(output, sizeof(output), request, head, NULL, NULL);
587 snprintf(output, sizeof(output), "ERROR expanding xlat: %s", fr_strerror());
591 TALLOC_FREE(fmt); /* also frees 'head' */
598 if (strncmp(input, "data ", 5) == 0) {
599 if (strcmp(input + 5, output) != 0) {
600 fprintf(stderr, "Mismatch at line %d of %s\n\tgot : %s\n\texpected : %s\n",
601 lineno, filename, output, input + 5);
602 TALLOC_FREE(request);
608 fprintf(stderr, "Unknown keyword in %s[%d]\n", filename, lineno);
609 TALLOC_FREE(request);
613 TALLOC_FREE(request);
621 int main(int argc, char *argv[])
623 int rcode = EXIT_SUCCESS;
625 const char *input_file = NULL;
626 const char *output_file = NULL;
627 const char *filter_file = NULL;
629 REQUEST *request = NULL;
631 VALUE_PAIR *filter_vps = NULL;
632 bool xlat_only = false;
633 fr_state_t *state = NULL;
635 fr_talloc_fault_setup();
638 * If the server was built with debugging enabled always install
639 * the basic fatal signal handlers.
642 if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
643 fr_perror("unittest");
648 if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL)
654 set_radius_dir(NULL, RADIUS_DIR);
657 * Ensure that the configuration is initialized.
659 memset(&main_config, 0, sizeof(main_config));
660 main_config.myip.af = AF_UNSPEC;
661 main_config.port = 0;
662 main_config.name = "radiusd";
665 * The tests should have only IPs, not host names.
667 fr_hostname_lookups = false;
670 * We always log to stdout.
673 default_log.dst = L_DST_STDOUT;
674 default_log.fd = STDOUT_FILENO;
676 /* Process the options. */
677 while ((argval = getopt(argc, argv, "d:D:f:hi:mMn:o:O:xX")) != EOF) {
681 set_radius_dir(NULL, optarg);
685 main_config.dictionary_dir = talloc_typed_strdup(NULL, optarg);
689 filter_file = optarg;
701 main_config.debug_memory = true;
705 memory_report = true;
706 main_config.debug_memory = true;
710 main_config.name = optarg;
714 output_file = optarg;
718 if (strcmp(optarg, "xlat_only") == 0) {
723 fprintf(stderr, "Unknown option '%s'\n", optarg);
728 main_config.log_auth = true;
729 main_config.log_auth_badpass = true;
730 main_config.log_auth_goodpass = true;
743 if (rad_debug_lvl) version_print();
744 fr_debug_lvl = rad_debug_lvl;
747 * Mismatch between the binary and the libraries it depends on
749 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
750 fr_perror("radiusd");
755 * Initialising OpenSSL once, here, is safer than having individual modules do it.
757 #ifdef HAVE_OPENSSL_CRYPTO_H
761 if (xlat_register("poke", xlat_poke, NULL, NULL) < 0) {
762 rcode = EXIT_FAILURE;
766 /* Read the configuration files, BEFORE doing anything else. */
767 if (main_config_init() < 0) {
768 rcode = EXIT_FAILURE;
775 if (modules_init(main_config.config) < 0) {
776 rcode = EXIT_FAILURE;
780 state =fr_state_init(NULL);
783 * Set the panic action (if required)
786 char const *panic_action = NULL;
788 panic_action = getenv("PANIC_ACTION");
789 if (!panic_action) panic_action = main_config.panic_action;
791 if (panic_action && (fr_fault_setup(panic_action, argv[0]) < 0)) {
792 fr_perror("radiusd");
797 setlinebuf(stdout); /* unbuffered output */
799 if (!input_file || (strcmp(input_file, "-") == 0)) {
802 fp = fopen(input_file, "r");
804 fprintf(stderr, "Failed reading %s: %s\n",
805 input_file, fr_syserror(errno));
811 * For simplicity, read xlat's.
814 if (!do_xlats(input_file, fp)) rcode = EXIT_FAILURE;
815 if (input_file) fclose(fp);
820 * Grab the VPs from stdin, or from the file.
822 request = request_setup(fp);
824 fprintf(stderr, "Failed reading input: %s\n", fr_strerror());
825 rcode = EXIT_FAILURE;
830 * No filter file, OR there's no more input, OR we're
831 * reading from a file, and it's different from the
834 if (!filter_file || filedone ||
835 ((input_file != NULL) && (strcmp(filter_file, input_file) != 0))) {
844 * There is a filter file. If necessary, open it. If we
845 * already are reading it via "input_file", then we don't
846 * need to re-open it.
850 fp = fopen(filter_file, "r");
852 fprintf(stderr, "Failed reading %s: %s\n", filter_file, strerror(errno));
853 rcode = EXIT_FAILURE;
859 if (fr_pair_list_afrom_file(request, &filter_vps, fp, &filedone) < 0) {
860 fprintf(stderr, "Failed reading attributes from %s: %s\n",
861 filter_file, fr_strerror());
862 rcode = EXIT_FAILURE;
867 * FIXME: loop over input packets.
872 rad_virtual_server(request);
874 if (!output_file || (strcmp(output_file, "-") == 0)) {
877 fp = fopen(output_file, "w");
879 fprintf(stderr, "Failed writing %s: %s\n",
880 output_file, fr_syserror(errno));
885 print_packet(fp, request->reply);
887 if (output_file) fclose(fp);
890 * Update the list with the response type.
892 vp = radius_pair_create(request->reply, &request->reply->vps,
893 PW_RESPONSE_PACKET_TYPE, 0);
894 vp->vp_integer = request->reply->code;
897 VALUE_PAIR const *failed[2];
899 if (filter_vps && !fr_pair_validate(failed, filter_vps, request->reply->vps)) {
900 fr_pair_validate_debug(request, failed);
901 fr_perror("Output file %s does not match attributes in filter %s (%s)",
902 output_file ? output_file : input_file, filter_file, fr_strerror());
903 rcode = EXIT_FAILURE;
908 INFO("Exiting normally");
911 talloc_free(request);
914 * Detach any modules.
918 xlat_unregister("poke", xlat_poke, NULL);
920 xlat_free(); /* modules may have xlat's */
922 fr_state_delete(state);
925 * Free the configuration items.
930 INFO("Allocated memory at time of report:");
931 fr_log_talloc_report(NULL);
939 * Display the syntax for starting this program.
941 static void NEVER_RETURNS usage(int status)
943 FILE *output = status?stderr:stdout;
945 fprintf(output, "Usage: %s [options]\n", progname);
946 fprintf(output, "Options:\n");
947 fprintf(output, " -d raddb_dir Configuration files are in \"raddb_dir/*\".\n");
948 fprintf(output, " -D dict_dir Dictionary files are in \"dict_dir/*\".\n");
949 fprintf(output, " -f file Filter reply against attributes in 'file'.\n");
950 fprintf(output, " -h Print this help message.\n");
951 fprintf(output, " -i file File containing request attributes.\n");
952 fprintf(output, " -m On SIGINT or SIGQUIT exit cleanly instead of immediately.\n");
953 fprintf(output, " -n name Read raddb/name.conf instead of raddb/radiusd.conf.\n");
954 fprintf(output, " -X Turn on full debugging.\n");
955 fprintf(output, " -x Turn on additional debugging. (-xx gives more debugging).\n");