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 *radacct_dir = NULL;
41 char const *radlog_dir = NULL;
42 bool log_stripped_names = false;
44 static bool memory_report = false;
45 static bool filedone = false;
47 char const *radiusd_version = "FreeRADIUS Version " RADIUSD_VERSION_STRING
48 #ifdef RADIUSD_VERSION_COMMIT
49 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
51 ", for host " HOSTINFO ", built on " __DATE__ " at " __TIME__;
56 static void usage(int);
58 void listen_free(UNUSED rad_listen_t **head)
64 static rad_listen_t *listen_alloc(void *ctx)
68 this = talloc_zero(ctx, rad_listen_t);
69 if (!this) return NULL;
71 this->type = RAD_LISTEN_AUTH;
79 * We probably don't care about this. We can always add
82 this->data = talloc_zero(this, listen_socket_t);
91 static RADCLIENT *client_alloc(void *ctx)
95 client = talloc_zero(ctx, RADCLIENT);
96 if (!client) return NULL;
101 static REQUEST *request_setup(FILE *fp)
109 * Create and initialize the new request.
111 request = request_alloc(NULL);
112 gettimeofday(&now, NULL);
113 request->timestamp = now.tv_sec;
115 request->packet = rad_alloc(request, false);
116 if (!request->packet) {
118 talloc_free(request);
121 request->packet->timestamp = now;
123 request->reply = rad_alloc(request, false);
124 if (!request->reply) {
126 talloc_free(request);
130 request->listener = listen_alloc(request);
131 request->client = client_alloc(request);
135 request->master_state = REQUEST_ACTIVE;
136 request->child_state = REQUEST_RUNNING;
137 request->handle = NULL;
138 request->server = talloc_typed_strdup(request, "default");
140 request->root = &main_config;
143 * Read packet from fp
145 if (fr_pair_list_afrom_file(request->packet, &request->packet->vps, fp, &filedone) < 0) {
146 fr_perror("unittest");
147 talloc_free(request);
152 * Set the defaults for IPs, etc.
154 request->packet->code = PW_CODE_ACCESS_REQUEST;
156 request->packet->src_ipaddr.af = AF_INET;
157 request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
158 request->packet->src_port = 18120;
160 request->packet->dst_ipaddr.af = AF_INET;
161 request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
162 request->packet->dst_port = 1812;
165 * Copied from radclient
167 * Fix up Digest-Attributes issues
169 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
171 vp = fr_cursor_next(&cursor)) {
173 * Double quoted strings get marked up as xlat expansions,
174 * but we don't support that here.
176 if (vp->type == VT_XLAT) {
177 vp->vp_strvalue = vp->value.xlat;
178 vp->value.xlat = NULL;
182 if (!vp->da->vendor) switch (vp->da->attr) {
187 * Allow it to set the packet type in
188 * the attributes read from the file.
191 request->packet->code = vp->vp_integer;
194 case PW_PACKET_DST_PORT:
195 request->packet->dst_port = (vp->vp_integer & 0xffff);
198 case PW_PACKET_DST_IP_ADDRESS:
199 request->packet->dst_ipaddr.af = AF_INET;
200 request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
201 request->packet->dst_ipaddr.prefix = 32;
204 case PW_PACKET_DST_IPV6_ADDRESS:
205 request->packet->dst_ipaddr.af = AF_INET6;
206 request->packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
207 request->packet->dst_ipaddr.prefix = 128;
210 case PW_PACKET_SRC_PORT:
211 request->packet->src_port = (vp->vp_integer & 0xffff);
214 case PW_PACKET_SRC_IP_ADDRESS:
215 request->packet->src_ipaddr.af = AF_INET;
216 request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
217 request->packet->src_ipaddr.prefix = 32;
220 case PW_PACKET_SRC_IPV6_ADDRESS:
221 request->packet->src_ipaddr.af = AF_INET6;
222 request->packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
223 request->packet->src_ipaddr.prefix = 128;
226 case PW_CHAP_PASSWORD: {
227 int i, already_hex = 0;
230 * If it's 17 octets, it *might* be already encoded.
231 * Or, it might just be a 17-character password (maybe UTF-8)
232 * Check it for non-printable characters. The odds of ALL
233 * of the characters being 32..255 is (1-7/8)^17, or (1/8)^17,
234 * or 1/(2^51), which is pretty much zero.
236 if (vp->vp_length == 17) {
237 for (i = 0; i < 17; i++) {
238 if (vp->vp_octets[i] < 32) {
246 * Allow the user to specify ASCII or hex CHAP-Password
252 len = len2 = vp->vp_length;
253 if (len2 < 17) len2 = 17;
255 p = talloc_zero_array(vp, uint8_t, len2);
257 memcpy(p, vp->vp_strvalue, len);
259 rad_chap_encode(request->packet,
261 fr_rand() & 0xff, vp);
268 case PW_DIGEST_REALM:
269 case PW_DIGEST_NONCE:
270 case PW_DIGEST_METHOD:
273 case PW_DIGEST_ALGORITHM:
274 case PW_DIGEST_BODY_DIGEST:
275 case PW_DIGEST_CNONCE:
276 case PW_DIGEST_NONCE_COUNT:
277 case PW_DIGEST_USER_NAME:
283 p = talloc_array(vp, uint8_t, vp->vp_length + 2);
285 memcpy(p + 2, vp->vp_octets, vp->vp_length);
286 p[0] = vp->da->attr - PW_DIGEST_REALM + 1;
288 p[1] = vp->vp_length;
290 da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0);
291 rad_assert(da != NULL);
295 * Re-do fr_pair_value_memsteal ourselves,
296 * because we play games with
297 * vp->da, and fr_pair_value_memsteal goes
298 * to GREAT lengths to sanitize
299 * and fix and change and
300 * double-check the various
303 memcpy(&q, &vp->vp_octets, sizeof(q));
306 vp->vp_octets = talloc_steal(vp, p);
314 } /* loop over the VP's we read in */
317 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
319 vp = fr_cursor_next(&cursor)) {
321 * Take this opportunity to verify all the VALUE_PAIRs are still valid.
323 if (!talloc_get_type(vp, VALUE_PAIR)) {
324 ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
326 fr_log_talloc_report(vp);
330 vp_print(fr_log_fp, vp);
336 * Build the reply template from the request.
338 request->reply->sockfd = request->packet->sockfd;
339 request->reply->dst_ipaddr = request->packet->src_ipaddr;
340 request->reply->src_ipaddr = request->packet->dst_ipaddr;
341 request->reply->dst_port = request->packet->src_port;
342 request->reply->src_port = request->packet->dst_port;
343 request->reply->id = request->packet->id;
344 request->reply->code = 0; /* UNKNOWN code */
345 memcpy(request->reply->vector, request->packet->vector,
346 sizeof(request->reply->vector));
347 request->reply->vps = NULL;
348 request->reply->data = NULL;
349 request->reply->data_len = 0;
354 request->log.lvl = rad_debug_lvl;
355 request->log.func = vradlog_request;
357 request->username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
358 request->password = fr_pair_find_by_num(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
364 static void print_packet(FILE *fp, RADIUS_PACKET *packet)
374 fprintf(fp, "%s\n", fr_packet_codes[packet->code]);
376 for (vp = fr_cursor_init(&cursor, &packet->vps);
378 vp = fr_cursor_next(&cursor)) {
380 * Take this opportunity to verify all the VALUE_PAIRs are still valid.
382 if (!talloc_get_type(vp, VALUE_PAIR)) {
383 ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
385 fr_log_talloc_report(vp);
395 #include <freeradius-devel/modpriv.h>
398 * %{poke:sql.foo=bar}
400 static ssize_t xlat_poke(UNUSED void *instance, REQUEST *request,
401 char const *fmt, char *out, size_t outlen)
406 module_instance_t *mi;
408 CONF_SECTION *modules;
410 CONF_PARSER const *variables;
413 rad_assert(outlen > 1);
414 rad_assert(request != NULL);
415 rad_assert(fmt != NULL);
416 rad_assert(out != NULL);
420 modules = cf_section_sub_find(request->root->config, "modules");
421 if (!modules) return 0;
423 buffer = talloc_strdup(request, fmt);
424 if (!buffer) return 0;
426 p = strchr(buffer, '.');
431 mi = module_find(modules, buffer);
433 RDEBUG("Failed finding module '%s'", buffer);
441 RDEBUG("Failed finding '=' in string '%s'", fmt);
447 if (strchr(p, '.') != NULL) {
448 RDEBUG("Can't do sub-sections right now");
452 cp = cf_pair_find(mi->cs, p);
454 RDEBUG("No such item '%s'", p);
459 * Copy the old value to the output buffer, that way
460 * tests can restore it later, if they need to.
462 len = strlcpy(out, cf_pair_value(cp), outlen);
464 if (cf_pair_replace(mi->cs, cp, q) < 0) {
465 RDEBUG("Failed replacing pair");
469 base = mi->insthandle;
470 variables = mi->entry->module->config;
473 * Handle the known configuration parameters.
475 for (i = 0; variables[i].name != NULL; i++) {
478 if (variables[i].type == PW_TYPE_SUBSECTION) continue;
479 /* else it's a CONF_PAIR */
482 * Not the pair we want. Skip it.
484 if (strcmp(variables[i].name, p) != 0) continue;
486 if (variables[i].data) {
487 data = variables[i].data; /* prefer this. */
489 data = ((char *)base) + variables[i].offset;
491 DEBUG2("Internal sanity check 2 failed in cf_section_parse");
496 * Parse the pair we found, or a default value.
498 ret = cf_item_parse(mi->cs, variables[i].name, variables[i].type, data, variables[i].dflt);
500 DEBUG2("Failed inserting new value into module instance data");
503 break; /* we found it, don't do any more */
513 * Read a file compose of xlat's and expected results
515 static bool do_xlats(char const *filename, FILE *fp)
526 * Create and initialize the new request.
528 request = request_alloc(NULL);
529 gettimeofday(&now, NULL);
530 request->timestamp = now.tv_sec;
532 request->log.lvl = rad_debug_lvl;
533 request->log.func = vradlog_request;
537 while (fgets(input, sizeof(input), fp) != NULL) {
541 * Ignore blank lines and comments
544 while (isspace((int) *p)) p++;
546 if (*p < ' ') continue;
547 if (*p == '#') continue;
552 fprintf(stderr, "Line %d too long in %s\n",
554 TALLOC_FREE(request);
564 if (strncmp(input, "xlat ", 5) == 0) {
566 char const *error = NULL;
567 char *fmt = talloc_typed_strdup(NULL, input + 5);
570 slen = xlat_tokenize(fmt, fmt, &head, &error);
573 snprintf(output, sizeof(output), "ERROR offset %d '%s'", (int) -slen, error);
577 if (input[slen + 5] != '\0') {
579 snprintf(output, sizeof(output), "ERROR offset %d 'Too much text' ::%s::", (int) slen, input + slen + 5);
583 len = radius_xlat_struct(output, sizeof(output), request, head, NULL, NULL);
585 snprintf(output, sizeof(output), "ERROR expanding xlat: %s", fr_strerror());
589 TALLOC_FREE(fmt); /* also frees 'head' */
596 if (strncmp(input, "data ", 5) == 0) {
597 if (strcmp(input + 5, output) != 0) {
598 fprintf(stderr, "Mismatch at line %d of %s\n\tgot : %s\n\texpected : %s\n",
599 lineno, filename, output, input + 5);
600 TALLOC_FREE(request);
606 fprintf(stderr, "Unknown keyword in %s[%d]\n", filename, lineno);
607 TALLOC_FREE(request);
611 TALLOC_FREE(request);
619 int main(int argc, char *argv[])
621 int rcode = EXIT_SUCCESS;
623 const char *input_file = NULL;
624 const char *output_file = NULL;
625 const char *filter_file = NULL;
627 REQUEST *request = NULL;
629 VALUE_PAIR *filter_vps = NULL;
630 bool xlat_only = false;
632 fr_state_t *state = NULL;
634 fr_talloc_fault_setup();
637 * If the server was built with debugging enabled always install
638 * the basic fatal signal handlers.
641 if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
642 fr_perror("unittest");
647 p = strrchr(argv[0], FR_DIR_SEP);
649 main_config.name = argv[0];
651 main_config.name = p + 1;
655 set_radius_dir(NULL, RADIUS_DIR);
658 * Ensure that the configuration is initialized.
660 memset(&main_config, 0, sizeof(main_config));
661 main_config.myip.af = AF_UNSPEC;
662 main_config.port = 0;
663 main_config.name = "radiusd";
666 * The tests should have only IPs, not host names.
668 fr_hostname_lookups = false;
671 * We always log to stdout.
674 default_log.dst = L_DST_STDOUT;
675 default_log.fd = STDOUT_FILENO;
677 /* Process the options. */
678 while ((argval = getopt(argc, argv, "d:D:f:hi:mMn:o:O:xX")) != EOF) {
682 set_radius_dir(NULL, optarg);
686 main_config.dictionary_dir = talloc_typed_strdup(NULL, optarg);
690 filter_file = optarg;
702 main_config.debug_memory = true;
706 memory_report = true;
707 main_config.debug_memory = true;
711 main_config.name = optarg;
715 output_file = optarg;
719 if (strcmp(optarg, "xlat_only") == 0) {
724 fprintf(stderr, "Unknown option '%s'\n", optarg);
729 main_config.log_auth = true;
730 main_config.log_auth_badpass = true;
731 main_config.log_auth_goodpass = true;
744 if (rad_debug_lvl) version_print();
745 fr_debug_lvl = rad_debug_lvl;
748 * Mismatch between the binary and the libraries it depends on
750 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
751 fr_perror("radiusd");
756 * Initialising OpenSSL once, here, is safer than having individual modules do it.
758 #ifdef HAVE_OPENSSL_CRYPTO_H
762 if (xlat_register("poke", xlat_poke, NULL, NULL) < 0) {
763 rcode = EXIT_FAILURE;
767 /* Read the configuration files, BEFORE doing anything else. */
768 if (main_config_init() < 0) {
769 rcode = EXIT_FAILURE;
776 if (modules_init(main_config.config) < 0) {
777 rcode = EXIT_FAILURE;
781 state =fr_state_init(NULL);
784 * Set the panic action (if required)
787 char const *panic_action = NULL;
789 panic_action = getenv("PANIC_ACTION");
790 if (!panic_action) panic_action = main_config.panic_action;
792 if (panic_action && (fr_fault_setup(panic_action, argv[0]) < 0)) {
793 fr_perror("radiusd");
798 setlinebuf(stdout); /* unbuffered output */
800 if (!input_file || (strcmp(input_file, "-") == 0)) {
803 fp = fopen(input_file, "r");
805 fprintf(stderr, "Failed reading %s: %s\n",
806 input_file, fr_syserror(errno));
812 * For simplicity, read xlat's.
815 if (!do_xlats(input_file, fp)) rcode = EXIT_FAILURE;
816 if (input_file) fclose(fp);
821 * Grab the VPs from stdin, or from the file.
823 request = request_setup(fp);
825 fprintf(stderr, "Failed reading input: %s\n", fr_strerror());
826 rcode = EXIT_FAILURE;
831 * No filter file, OR there's no more input, OR we're
832 * reading from a file, and it's different from the
835 if (!filter_file || filedone ||
836 ((input_file != NULL) && (strcmp(filter_file, input_file) != 0))) {
845 * There is a filter file. If necessary, open it. If we
846 * already are reading it via "input_file", then we don't
847 * need to re-open it.
851 fp = fopen(filter_file, "r");
853 fprintf(stderr, "Failed reading %s: %s\n", filter_file, strerror(errno));
854 rcode = EXIT_FAILURE;
860 if (fr_pair_list_afrom_file(request, &filter_vps, fp, &filedone) < 0) {
861 fprintf(stderr, "Failed reading attributes from %s: %s\n",
862 filter_file, fr_strerror());
863 rcode = EXIT_FAILURE;
868 * FIXME: loop over input packets.
873 rad_virtual_server(request);
875 if (!output_file || (strcmp(output_file, "-") == 0)) {
878 fp = fopen(output_file, "w");
880 fprintf(stderr, "Failed writing %s: %s\n",
881 output_file, fr_syserror(errno));
886 print_packet(fp, request->reply);
888 if (output_file) fclose(fp);
891 * Update the list with the response type.
893 vp = radius_pair_create(request->reply, &request->reply->vps,
894 PW_RESPONSE_PACKET_TYPE, 0);
895 vp->vp_integer = request->reply->code;
898 VALUE_PAIR const *failed[2];
900 if (filter_vps && !fr_pair_validate(failed, filter_vps, request->reply->vps)) {
901 fr_pair_validate_debug(request, failed);
902 fr_perror("Output file %s does not match attributes in filter %s (%s)",
903 output_file ? output_file : input_file, filter_file, fr_strerror());
904 rcode = EXIT_FAILURE;
909 INFO("Exiting normally");
912 talloc_free(request);
915 * Detach any modules.
919 xlat_unregister("poke", xlat_poke, NULL);
921 xlat_free(); /* modules may have xlat's */
923 fr_state_delete(state);
926 * Free the configuration items.
931 INFO("Allocated memory at time of report:");
932 fr_log_talloc_report(NULL);
940 * Display the syntax for starting this program.
942 static void NEVER_RETURNS usage(int status)
944 FILE *output = status?stderr:stdout;
946 fprintf(output, "Usage: %s [options]\n", main_config.name);
947 fprintf(output, "Options:\n");
948 fprintf(output, " -d raddb_dir Configuration files are in \"raddb_dir/*\".\n");
949 fprintf(output, " -D dict_dir Dictionary files are in \"dict_dir/*\".\n");
950 fprintf(output, " -f file Filter reply against attributes in 'file'.\n");
951 fprintf(output, " -h Print this help message.\n");
952 fprintf(output, " -i file File containing request attributes.\n");
953 fprintf(output, " -m On SIGINT or SIGQUIT exit cleanly instead of immediately.\n");
954 fprintf(output, " -n name Read raddb/name.conf instead of raddb/radiusd.conf.\n");
955 fprintf(output, " -X Turn on full debugging.\n");
956 fprintf(output, " -x Turn on additional debugging. (-xx gives more debugging).\n");