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/rad_assert.h>
37 char const *progname = NULL;
38 char const *radius_dir = NULL;
39 char const *radacct_dir = NULL;
40 char const *radlog_dir = NULL;
41 char const *radlib_dir = NULL;
42 log_debug_t debug_flag = 0;
43 bool memory_report = false;
44 bool check_config = false;
45 bool log_stripped_names = false;
47 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)
110 * Create and initialize the new request.
112 request = request_alloc(NULL);
114 request->packet = rad_alloc(request, false);
115 if (!request->packet) {
117 talloc_free(request);
121 request->reply = rad_alloc(request, false);
122 if (!request->reply) {
124 talloc_free(request);
128 request->listener = listen_alloc(request);
129 request->client = client_alloc(request);
133 request->master_state = REQUEST_ACTIVE;
134 request->child_state = REQUEST_RUNNING;
135 request->handle = NULL;
136 request->server = talloc_typed_strdup(request, "default");
138 request->root = &main_config;
141 * Read packet from fp
143 if (readvp2(&request->packet->vps, request->packet, fp, &filedone) < 0) {
144 fr_perror("unittest");
145 talloc_free(request);
150 * Set the defaults for IPs, etc.
152 request->packet->code = PW_CODE_ACCESS_REQUEST;
154 request->packet->src_ipaddr.af = AF_INET;
155 request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
156 request->packet->src_port = 18120;
158 request->packet->dst_ipaddr.af = AF_INET;
159 request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
160 request->packet->dst_port = 1812;
163 * Copied from radclient
165 * Fix up Digest-Attributes issues
167 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
169 vp = fr_cursor_next(&cursor)) {
171 * Double quoted strings get marked up as xlat expansions,
172 * but we don't support that here.
174 if (vp->type == VT_XLAT) {
175 vp->vp_strvalue = vp->value.xlat;
176 vp->value.xlat = NULL;
180 if (!vp->da->vendor) switch (vp->da->attr) {
185 * Allow it to set the packet type in
186 * the attributes read from the file.
189 request->packet->code = vp->vp_integer;
192 case PW_PACKET_DST_PORT:
193 request->packet->dst_port = (vp->vp_integer & 0xffff);
196 case PW_PACKET_DST_IP_ADDRESS:
197 request->packet->dst_ipaddr.af = AF_INET;
198 request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
201 case PW_PACKET_DST_IPV6_ADDRESS:
202 request->packet->dst_ipaddr.af = AF_INET6;
203 request->packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
206 case PW_PACKET_SRC_PORT:
207 request->packet->src_port = (vp->vp_integer & 0xffff);
210 case PW_PACKET_SRC_IP_ADDRESS:
211 request->packet->src_ipaddr.af = AF_INET;
212 request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
215 case PW_PACKET_SRC_IPV6_ADDRESS:
216 request->packet->src_ipaddr.af = AF_INET6;
217 request->packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
220 case PW_CHAP_PASSWORD: {
221 int i, already_hex = 0;
224 * If it's 17 octets, it *might* be already encoded.
225 * Or, it might just be a 17-character password (maybe UTF-8)
226 * Check it for non-printable characters. The odds of ALL
227 * of the characters being 32..255 is (1-7/8)^17, or (1/8)^17,
228 * or 1/(2^51), which is pretty much zero.
230 if (vp->length == 17) {
231 for (i = 0; i < 17; i++) {
232 if (vp->vp_octets[i] < 32) {
240 * Allow the user to specify ASCII or hex CHAP-Password
246 len = len2 = vp->length;
247 if (len2 < 17) len2 = 17;
249 p = talloc_zero_array(vp, uint8_t, len2);
251 memcpy(p, vp->vp_strvalue, len);
253 rad_chap_encode(request->packet,
255 fr_rand() & 0xff, vp);
262 case PW_DIGEST_REALM:
263 case PW_DIGEST_NONCE:
264 case PW_DIGEST_METHOD:
267 case PW_DIGEST_ALGORITHM:
268 case PW_DIGEST_BODY_DIGEST:
269 case PW_DIGEST_CNONCE:
270 case PW_DIGEST_NONCE_COUNT:
271 case PW_DIGEST_USER_NAME:
277 p = talloc_array(vp, uint8_t, vp->length + 2);
279 memcpy(p + 2, vp->vp_octets, vp->length);
280 p[0] = vp->da->attr - PW_DIGEST_REALM + 1;
284 da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0);
285 rad_assert(da != NULL);
289 * Re-do pairmemsteal ourselves,
290 * because we play games with
291 * vp->da, and pairmemsteal goes
292 * to GREAT lengths to sanitize
293 * and fix and change and
294 * double-check the various
297 memcpy(&q, &vp->vp_octets, sizeof(q));
300 vp->vp_octets = talloc_steal(vp, p);
308 } /* loop over the VP's we read in */
311 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
313 vp = fr_cursor_next(&cursor)) {
315 * Take this opportunity to verify all the VALUE_PAIRs are still valid.
317 if (!talloc_get_type(vp, VALUE_PAIR)) {
318 ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
320 fr_log_talloc_report(vp);
324 vp_print(fr_log_fp, vp);
330 * Build the reply template from the request.
332 request->reply->sockfd = request->packet->sockfd;
333 request->reply->dst_ipaddr = request->packet->src_ipaddr;
334 request->reply->src_ipaddr = request->packet->dst_ipaddr;
335 request->reply->dst_port = request->packet->src_port;
336 request->reply->src_port = request->packet->dst_port;
337 request->reply->id = request->packet->id;
338 request->reply->code = 0; /* UNKNOWN code */
339 memcpy(request->reply->vector, request->packet->vector,
340 sizeof(request->reply->vector));
341 request->reply->vps = NULL;
342 request->reply->data = NULL;
343 request->reply->data_len = 0;
348 request->log.lvl = debug_flag;
349 request->log.func = vradlog_request;
351 request->username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
352 request->password = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
358 static void print_packet(FILE *fp, RADIUS_PACKET *packet)
368 fprintf(fp, "%s\n", fr_packet_codes[packet->code]);
370 for (vp = fr_cursor_init(&cursor, &packet->vps);
372 vp = fr_cursor_next(&cursor)) {
374 * Take this opportunity to verify all the VALUE_PAIRs are still valid.
376 if (!talloc_get_type(vp, VALUE_PAIR)) {
377 ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
379 fr_log_talloc_report(vp);
391 int main(int argc, char *argv[])
393 int rcode = EXIT_SUCCESS;
395 const char *input_file = NULL;
396 const char *output_file = NULL;
397 const char *filter_file = NULL;
399 REQUEST *request = NULL;
401 VALUE_PAIR *filter_vps = NULL;
404 * If the server was built with debugging enabled always install
405 * the basic fatal signal handlers.
408 if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
409 fr_perror("unittest");
414 if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL)
420 set_radius_dir(NULL, RADIUS_DIR);
423 * Ensure that the configuration is initialized.
425 memset(&main_config, 0, sizeof(main_config));
426 main_config.myip.af = AF_UNSPEC;
427 main_config.port = 0;
428 main_config.name = "radiusd";
431 * The tests should have only IPs, not host names.
433 fr_hostname_lookups = false;
436 * We always log to stdout.
439 default_log.dst = L_DST_STDOUT;
440 default_log.fd = STDOUT_FILENO;
442 /* Process the options. */
443 while ((argval = getopt(argc, argv, "d:D:f:hi:mMn:o:xX")) != EOF) {
447 set_radius_dir(NULL, optarg);
451 main_config.dictionary_dir = talloc_typed_strdup(NULL, optarg);
455 filter_file = optarg;
467 main_config.debug_memory = true;
471 memory_report = true;
472 main_config.debug_memory = true;
476 main_config.name = optarg;
480 output_file = optarg;
485 main_config.log_auth = true;
486 main_config.log_auth_badpass = true;
487 main_config.log_auth_goodpass = true;
503 fr_debug_flag = debug_flag;
506 * Mismatch between the binary and the libraries it depends on
508 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
509 fr_perror("radiusd");
513 /* Read the configuration files, BEFORE doing anything else. */
514 if (main_config_init() < 0) {
515 rcode = EXIT_FAILURE;
522 if (modules_init(main_config.config) < 0) {
523 rcode = EXIT_FAILURE;
527 /* Set the panic action (if required) */
528 if (main_config.panic_action &&
530 !getenv("PANIC_ACTION") &&
532 (fr_fault_setup(main_config.panic_action, argv[0]) < 0)) {
533 rcode = EXIT_FAILURE;
537 setlinebuf(stdout); /* unbuffered output */
539 if (!input_file || (strcmp(input_file, "-") == 0)) {
542 fp = fopen(input_file, "r");
544 fprintf(stderr, "Failed reading %s: %s\n",
545 input_file, fr_syserror(errno));
551 * Grab the VPs from stdin, or from the file.
553 request = request_setup(fp);
555 fprintf(stderr, "Failed reading input: %s\n", fr_strerror());
556 rcode = EXIT_FAILURE;
561 * No filter file, OR there's no more input, OR we're
562 * reading from a file, and it's different from the
565 if (!filter_file || filedone ||
566 ((input_file != NULL) && (strcmp(filter_file, input_file) != 0))) {
575 * There is a filter file. If necessary, open it. If we
576 * already are reading it via "input_file", then we don't
577 * need to re-open it.
581 fp = fopen(filter_file, "r");
583 fprintf(stderr, "Failed reading %s: %s\n", filter_file, strerror(errno));
584 rcode = EXIT_FAILURE;
590 if (readvp2(&filter_vps, request, fp, &filedone) < 0) {
591 fprintf(stderr, "Failed reading attributes from %s: %s\n",
592 filter_file, fr_strerror());
593 rcode = EXIT_FAILURE;
598 * FIXME: loop over input packets.
603 rad_virtual_server(request);
605 if (!output_file || (strcmp(output_file, "-") == 0)) {
608 fp = fopen(output_file, "w");
610 fprintf(stderr, "Failed writing %s: %s\n",
611 output_file, fr_syserror(errno));
616 print_packet(fp, request->reply);
618 if (output_file) fclose(fp);
621 * Update the list with the response type.
623 vp = radius_paircreate(request->reply, &request->reply->vps,
624 PW_RESPONSE_PACKET_TYPE, 0);
625 vp->vp_integer = request->reply->code;
628 VALUE_PAIR const *failed[2];
630 if (filter_vps && !pairvalidate(failed, filter_vps, request->reply->vps)) {
631 pairvalidate_debug(request, failed);
632 fr_perror("Output file %s does not match attributes in filter %s",
633 output_file ? output_file : input_file, filter_file);
634 rcode = EXIT_FAILURE;
639 INFO("Exiting normally");
642 talloc_free(request);
645 * Detach any modules.
649 xlat_free(); /* modules may have xlat's */
652 * Free the configuration items.
657 INFO("Allocated memory at time of report:");
658 fr_log_talloc_report(NULL);
666 * Display the syntax for starting this program.
668 static void NEVER_RETURNS usage(int status)
670 FILE *output = status?stderr:stdout;
672 fprintf(output, "Usage: %s [options]\n", progname);
673 fprintf(output, "Options:\n");
674 fprintf(output, " -d raddb_dir Configuration files are in \"raddb_dir/*\".\n");
675 fprintf(output, " -D dict_dir Dictionary files are in \"dict_dir/*\".\n");
676 fprintf(output, " -f file Filter reply against attributes in 'file'.\n");
677 fprintf(output, " -h Print this help message.\n");
678 fprintf(output, " -m On SIGINT or SIGQUIT exit cleanly instead of immediately.\n");
679 fprintf(output, " -n name Read raddb/name.conf instead of raddb/radiusd.conf.\n");
680 fprintf(output, " -X Turn on full debugging.\n");
681 fprintf(output, " -x Turn on additional debugging. (-xx gives more debugging).\n");