summaryrefslogtreecommitdiffstats
path: root/src/main/unittest.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/main/unittest.c973
1 files changed, 973 insertions, 0 deletions
diff --git a/src/main/unittest.c b/src/main/unittest.c
new file mode 100644
index 0000000..50935c9
--- /dev/null
+++ b/src/main/unittest.c
@@ -0,0 +1,973 @@
+/*
+ * unittest.c Unit test wrapper for the RADIUS daemon.
+ *
+ * Version: $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2000-2013 The FreeRADIUS server project
+ * Copyright 2013 Alan DeKok <aland@ox.org>
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/state.h>
+#include <freeradius-devel/rad_assert.h>
+#include <freeradius-devel/event.h>
+
+#ifdef HAVE_GETOPT_H
+# include <getopt.h>
+#endif
+
+#include <ctype.h>
+
+/*
+ * Global variables.
+ */
+char const *radacct_dir = NULL;
+char const *radlog_dir = NULL;
+bool log_stripped_names = false;
+
+static bool memory_report = false;
+static bool filedone = false;
+
+char const *radiusd_version = "FreeRADIUS Version " RADIUSD_VERSION_STRING
+#ifdef RADIUSD_VERSION_COMMIT
+" (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
+#endif
+", for host " HOSTINFO
+#ifndef ENABLE_REPRODUCIBLE_BUILDS
+", built on " __DATE__ " at " __TIME__
+#endif
+;
+
+fr_event_list_t *el = NULL;
+
+/*
+ * Static functions.
+ */
+static void usage(int);
+
+void listen_free(UNUSED rad_listen_t **head)
+{
+ /* do nothing */
+}
+
+void request_inject(UNUSED REQUEST *request)
+{
+ /* do nothing */
+}
+
+static rad_listen_t *listen_alloc(void *ctx)
+{
+ rad_listen_t *this;
+
+ this = talloc_zero(ctx, rad_listen_t);
+ if (!this) return NULL;
+
+ this->type = RAD_LISTEN_AUTH;
+ this->recv = NULL;
+ this->send = NULL;
+ this->print = NULL;
+ this->encode = NULL;
+ this->decode = NULL;
+
+ /*
+ * We probably don't care about this. We can always add
+ * fields later.
+ */
+ this->data = talloc_zero(this, listen_socket_t);
+ if (!this->data) {
+ talloc_free(this);
+ return NULL;
+ }
+
+ return this;
+}
+
+static RADCLIENT *client_alloc(void *ctx)
+{
+ RADCLIENT *client;
+
+ client = talloc_zero(ctx, RADCLIENT);
+ if (!client) return NULL;
+
+ return client;
+}
+
+static REQUEST *request_setup(FILE *fp)
+{
+ VALUE_PAIR *vp;
+ REQUEST *request;
+ vp_cursor_t cursor;
+ struct timeval now;
+
+ /*
+ * Create and initialize the new request.
+ */
+ request = request_alloc(NULL);
+ gettimeofday(&now, NULL);
+ request->timestamp = now.tv_sec;
+
+ request->packet = rad_alloc(request, false);
+ if (!request->packet) {
+ ERROR("No memory");
+ talloc_free(request);
+ return NULL;
+ }
+ request->packet->timestamp = now;
+
+ request->reply = rad_alloc(request, false);
+ if (!request->reply) {
+ ERROR("No memory");
+ talloc_free(request);
+ return NULL;
+ }
+
+ request->listener = listen_alloc(request);
+ request->client = client_alloc(request);
+
+ request->number = 0;
+
+ request->master_state = REQUEST_ACTIVE;
+ request->child_state = REQUEST_RUNNING;
+ request->handle = NULL;
+ request->server = talloc_typed_strdup(request, "default");
+
+ request->root = &main_config;
+
+ /*
+ * Read packet from fp
+ */
+ if (fr_pair_list_afrom_file(request->packet, &request->packet->vps, fp, &filedone) < 0) {
+ fr_perror("unittest");
+ talloc_free(request);
+ return NULL;
+ }
+
+ /*
+ * Set the defaults for IPs, etc.
+ */
+ request->packet->code = PW_CODE_ACCESS_REQUEST;
+
+ request->packet->src_ipaddr.af = AF_INET;
+ request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
+ request->packet->src_port = 18120;
+
+ request->packet->dst_ipaddr.af = AF_INET;
+ request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
+ request->packet->dst_port = 1812;
+
+ /*
+ * Copied from radclient
+ *
+ * Fix up Digest-Attributes issues
+ */
+ for (vp = fr_cursor_init(&cursor, &request->packet->vps);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ /*
+ * Double quoted strings get marked up as xlat expansions,
+ * but we don't support that here.
+ */
+ if (vp->type == VT_XLAT) {
+ vp->vp_strvalue = vp->value.xlat;
+ vp->value.xlat = NULL;
+ vp->type = VT_DATA;
+ }
+
+ if (!vp->da->vendor) switch (vp->da->attr) {
+ default:
+ break;
+
+ /*
+ * Allow it to set the packet type in
+ * the attributes read from the file.
+ */
+ case PW_PACKET_TYPE:
+ request->packet->code = vp->vp_integer;
+ break;
+
+ case PW_PACKET_DST_PORT:
+ request->packet->dst_port = (vp->vp_integer & 0xffff);
+ break;
+
+ case PW_PACKET_DST_IP_ADDRESS:
+ request->packet->dst_ipaddr.af = AF_INET;
+ request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ request->packet->dst_ipaddr.prefix = 32;
+ break;
+
+ case PW_PACKET_DST_IPV6_ADDRESS:
+ request->packet->dst_ipaddr.af = AF_INET6;
+ request->packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
+ request->packet->dst_ipaddr.prefix = 128;
+ break;
+
+ case PW_PACKET_SRC_PORT:
+ request->packet->src_port = (vp->vp_integer & 0xffff);
+ break;
+
+ case PW_PACKET_SRC_IP_ADDRESS:
+ request->packet->src_ipaddr.af = AF_INET;
+ request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ request->packet->src_ipaddr.prefix = 32;
+ break;
+
+ case PW_PACKET_SRC_IPV6_ADDRESS:
+ request->packet->src_ipaddr.af = AF_INET6;
+ request->packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
+ request->packet->src_ipaddr.prefix = 128;
+ break;
+
+ case PW_CHAP_PASSWORD: {
+ int i, already_hex = 0;
+
+ /*
+ * If it's 17 octets, it *might* be already encoded.
+ * Or, it might just be a 17-character password (maybe UTF-8)
+ * Check it for non-printable characters. The odds of ALL
+ * of the characters being 32..255 is (1-7/8)^17, or (1/8)^17,
+ * or 1/(2^51), which is pretty much zero.
+ */
+ if (vp->vp_length == 17) {
+ for (i = 0; i < 17; i++) {
+ if (vp->vp_octets[i] < 32) {
+ already_hex = 1;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Allow the user to specify ASCII or hex CHAP-Password
+ */
+ if (!already_hex) {
+ uint8_t *p;
+ size_t len, len2;
+
+ len = len2 = vp->vp_length;
+ if (len2 < 17) len2 = 17;
+
+ p = talloc_zero_array(vp, uint8_t, len2);
+
+ memcpy(p, vp->vp_strvalue, len);
+
+ rad_chap_encode(request->packet,
+ p,
+ fr_rand() & 0xff, vp);
+ vp->vp_octets = p;
+ vp->vp_length = 17;
+ }
+ }
+ break;
+
+ case PW_DIGEST_REALM:
+ case PW_DIGEST_NONCE:
+ case PW_DIGEST_METHOD:
+ case PW_DIGEST_URI:
+ case PW_DIGEST_QOP:
+ case PW_DIGEST_ALGORITHM:
+ case PW_DIGEST_BODY_DIGEST:
+ case PW_DIGEST_CNONCE:
+ case PW_DIGEST_NONCE_COUNT:
+ case PW_DIGEST_USER_NAME:
+ /* overlapping! */
+ {
+ DICT_ATTR const *da;
+ uint8_t *p, *q;
+
+ p = talloc_array(vp, uint8_t, vp->vp_length + 2);
+
+ memcpy(p + 2, vp->vp_octets, vp->vp_length);
+ p[0] = vp->da->attr - PW_DIGEST_REALM + 1;
+ vp->vp_length += 2;
+ p[1] = vp->vp_length;
+
+ da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0);
+ rad_assert(da != NULL);
+ vp->da = da;
+
+ /*
+ * Re-do fr_pair_value_memsteal ourselves,
+ * because we play games with
+ * vp->da, and fr_pair_value_memsteal goes
+ * to GREAT lengths to sanitize
+ * and fix and change and
+ * double-check the various
+ * fields.
+ */
+ memcpy(&q, &vp->vp_octets, sizeof(q));
+ talloc_free(q);
+
+ vp->vp_octets = talloc_steal(vp, p);
+ vp->type = VT_DATA;
+
+ VERIFY_VP(vp);
+ }
+
+ break;
+ }
+ } /* loop over the VP's we read in */
+
+ if (rad_debug_lvl) {
+ for (vp = fr_cursor_init(&cursor, &request->packet->vps);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ /*
+ * Take this opportunity to verify all the VALUE_PAIRs are still valid.
+ */
+ if (!talloc_get_type(vp, VALUE_PAIR)) {
+ ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
+
+ fr_log_talloc_report(vp);
+ rad_assert(0);
+ }
+
+ vp_print(fr_log_fp, vp);
+ }
+ fflush(fr_log_fp);
+ }
+
+ /*
+ * Build the reply template from the request.
+ */
+ request->reply->sockfd = request->packet->sockfd;
+ request->reply->dst_ipaddr = request->packet->src_ipaddr;
+ request->reply->src_ipaddr = request->packet->dst_ipaddr;
+ request->reply->dst_port = request->packet->src_port;
+ request->reply->src_port = request->packet->dst_port;
+ request->reply->id = request->packet->id;
+ request->reply->code = 0; /* UNKNOWN code */
+ memcpy(request->reply->vector, request->packet->vector,
+ sizeof(request->reply->vector));
+ request->reply->vps = NULL;
+ request->reply->data = NULL;
+ request->reply->data_len = 0;
+
+ /*
+ * Debugging
+ */
+ request->log.lvl = rad_debug_lvl;
+ request->log.func = vradlog_request;
+
+ request->username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
+ request->password = fr_pair_find_by_num(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
+
+ return request;
+}
+
+
+static void print_packet(FILE *fp, RADIUS_PACKET *packet)
+{
+ VALUE_PAIR *vp;
+ vp_cursor_t cursor;
+
+ if (!packet) {
+ fprintf(fp, "\n");
+ return;
+ }
+
+ fprintf(fp, "%s\n", fr_packet_codes[packet->code]);
+
+ for (vp = fr_cursor_init(&cursor, &packet->vps);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ /*
+ * Take this opportunity to verify all the VALUE_PAIRs are still valid.
+ */
+ if (!talloc_get_type(vp, VALUE_PAIR)) {
+ ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
+
+ fr_log_talloc_report(vp);
+ rad_assert(0);
+ }
+
+ vp_print(fp, vp);
+ }
+ fflush(fp);
+}
+
+
+#include <freeradius-devel/modpriv.h>
+
+/*
+ * %{poke:sql.foo=bar}
+ */
+static ssize_t xlat_poke(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ int i;
+ void *data, *base;
+ char *p, *q;
+ module_instance_t *mi;
+ char *buffer;
+ CONF_SECTION *modules;
+ CONF_PAIR *cp;
+ CONF_PARSER const *variables;
+ size_t len;
+
+ rad_assert(outlen > 1);
+ rad_assert(request != NULL);
+ rad_assert(fmt != NULL);
+ rad_assert(out != NULL);
+
+ *out = '\0';
+
+ modules = cf_section_sub_find(request->root->config, "modules");
+ if (!modules) return 0;
+
+ buffer = talloc_strdup(request, fmt);
+ if (!buffer) return 0;
+
+ p = strchr(buffer, '.');
+ if (!p) return 0;
+
+ *(p++) = '\0';
+
+ mi = module_find(modules, buffer);
+ if (!mi) {
+ RDEBUG("Failed finding module '%s'", buffer);
+ fail:
+ talloc_free(buffer);
+ return 0;
+ }
+
+ q = strchr(p, '=');
+ if (!q) {
+ RDEBUG("Failed finding '=' in string '%s'", fmt);
+ goto fail;
+ }
+
+ *(q++) = '\0';
+
+ if (strchr(p, '.') != NULL) {
+ RDEBUG("Can't do sub-sections right now");
+ goto fail;
+ }
+
+ cp = cf_pair_find(mi->cs, p);
+ if (!cp) {
+ RDEBUG("No such item '%s'", p);
+ goto fail;
+ }
+
+ /*
+ * Copy the old value to the output buffer, that way
+ * tests can restore it later, if they need to.
+ */
+ len = strlcpy(out, cf_pair_value(cp), outlen);
+
+ if (cf_pair_replace(mi->cs, cp, q) < 0) {
+ RDEBUG("Failed replacing pair");
+ goto fail;
+ }
+
+ base = mi->insthandle;
+ variables = mi->entry->module->config;
+
+ /*
+ * Handle the known configuration parameters.
+ */
+ for (i = 0; variables[i].name != NULL; i++) {
+ int ret;
+
+ if (variables[i].type == PW_TYPE_SUBSECTION) continue;
+ /* else it's a CONF_PAIR */
+
+ /*
+ * Not the pair we want. Skip it.
+ */
+ if (strcmp(variables[i].name, p) != 0) continue;
+
+ if (variables[i].data) {
+ data = variables[i].data; /* prefer this. */
+ } else if (base) {
+ data = ((char *)base) + variables[i].offset;
+ } else {
+ DEBUG2("Internal sanity check 2 failed in cf_section_parse");
+ goto fail;
+ }
+
+ /*
+ * Parse the pair we found, or a default value.
+ */
+ ret = cf_item_parse(mi->cs, variables[i].name, variables[i].type, data, variables[i].dflt);
+ if (ret < 0) {
+ DEBUG2("Failed inserting new value into module instance data");
+ goto fail;
+ }
+ break; /* we found it, don't do any more */
+ }
+
+ talloc_free(buffer);
+
+ return len;
+}
+
+
+/*
+ * Read a file compose of xlat's and expected results
+ */
+static bool do_xlats(char const *filename, FILE *fp)
+{
+ int lineno = 0;
+ ssize_t len;
+ char *p;
+ char input[8192];
+ char output[8192];
+ REQUEST *request;
+ struct timeval now;
+
+ /*
+ * Create and initialize the new request.
+ */
+ request = request_alloc(NULL);
+ gettimeofday(&now, NULL);
+ request->timestamp = now.tv_sec;
+
+ request->log.lvl = rad_debug_lvl;
+ request->log.func = vradlog_request;
+
+ output[0] = '\0';
+
+ while (fgets(input, sizeof(input), fp) != NULL) {
+ lineno++;
+
+ /*
+ * Ignore blank lines and comments
+ */
+ p = input;
+ while (isspace((int) *p)) p++;
+
+ if (*p < ' ') continue;
+ if (*p == '#') continue;
+
+ p = strchr(p, '\n');
+ if (!p) {
+ if (!feof(fp)) {
+ fprintf(stderr, "Line %d too long in %s\n",
+ lineno, filename);
+ TALLOC_FREE(request);
+ return false;
+ }
+ } else {
+ *p = '\0';
+ }
+
+ /*
+ * Look for "xlat"
+ */
+ if (strncmp(input, "xlat ", 5) == 0) {
+ ssize_t slen;
+ char const *error = NULL;
+ char *fmt = talloc_typed_strdup(NULL, input + 5);
+ xlat_exp_t *head;
+
+ slen = xlat_tokenize(fmt, fmt, &head, &error);
+ if (slen <= 0) {
+ talloc_free(fmt);
+ snprintf(output, sizeof(output), "ERROR offset %d '%s'", (int) -slen, error);
+ continue;
+ }
+
+ if (input[slen + 5] != '\0') {
+ talloc_free(fmt);
+ snprintf(output, sizeof(output), "ERROR offset %d 'Too much text' ::%s::", (int) slen, input + slen + 5);
+ continue;
+ }
+
+ len = radius_xlat_struct(output, sizeof(output), request, head, NULL, NULL);
+ if (len < 0) {
+ snprintf(output, sizeof(output), "ERROR expanding xlat: %s", fr_strerror());
+ continue;
+ }
+
+ TALLOC_FREE(fmt); /* also frees 'head' */
+ continue;
+ }
+
+ /*
+ * Look for "data".
+ */
+ if (strncmp(input, "data ", 5) == 0) {
+ if (strcmp(input + 5, output) != 0) {
+ fprintf(stderr, "Mismatch at line %d of %s\n\tgot : %s\n\texpected : %s\n",
+ lineno, filename, output, input + 5);
+ TALLOC_FREE(request);
+ return false;
+ }
+ continue;
+ }
+
+ fprintf(stderr, "Unknown keyword in %s[%d]\n", filename, lineno);
+ TALLOC_FREE(request);
+ return false;
+ }
+
+ TALLOC_FREE(request);
+ return true;
+}
+
+/*
+ * Dummy event_list_corral
+ */
+fr_event_list_t *radius_event_list_corral(UNUSED event_corral_t hint) {
+ if (!el) {
+ el = fr_event_list_create(NULL, NULL);
+ }
+
+ return el;
+}
+
+/*
+ * The main guy.
+ */
+int main(int argc, char *argv[])
+{
+ int rcode = EXIT_SUCCESS;
+ int argval;
+ const char *input_file = NULL;
+ const char *output_file = NULL;
+ const char *filter_file = NULL;
+ FILE *fp;
+ REQUEST *request = NULL;
+ VALUE_PAIR *vp;
+ VALUE_PAIR *filter_vps = NULL;
+ bool xlat_only = false;
+ fr_state_t *state = NULL;
+
+ fr_talloc_fault_setup();
+
+ /*
+ * If the server was built with debugging enabled always install
+ * the basic fatal signal handlers.
+ */
+#ifndef NDEBUG
+ if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
+ fr_perror("unittest");
+ exit(EXIT_FAILURE);
+ }
+#endif
+
+ rad_debug_lvl = 0;
+ set_radius_dir(NULL, RADIUS_DIR);
+
+ /*
+ * Ensure that the configuration is initialized.
+ */
+ memset(&main_config, 0, sizeof(main_config));
+ main_config.myip.af = AF_UNSPEC;
+ main_config.port = 0;
+ main_config.name = "radiusd";
+
+ /*
+ * The tests should have only IPs, not host names.
+ */
+ fr_hostname_lookups = false;
+
+ /*
+ * We always log to stdout.
+ */
+ fr_log_fp = stdout;
+ default_log.dst = L_DST_STDOUT;
+ default_log.fd = STDOUT_FILENO;
+
+ /* Process the options. */
+ while ((argval = getopt(argc, argv, "d:D:f:hi:mMn:o:O:xX")) != EOF) {
+
+ switch (argval) {
+ case 'd':
+ set_radius_dir(NULL, optarg);
+ break;
+
+ case 'D':
+ main_config.dictionary_dir = talloc_typed_strdup(NULL, optarg);
+ break;
+
+ case 'f':
+ filter_file = optarg;
+ break;
+
+ case 'h':
+ usage(0);
+ break;
+
+ case 'i':
+ input_file = optarg;
+ break;
+
+ case 'm':
+ main_config.debug_memory = true;
+ break;
+
+ case 'M':
+ memory_report = true;
+ main_config.debug_memory = true;
+ break;
+
+ case 'n':
+ main_config.name = optarg;
+ break;
+
+ case 'o':
+ output_file = optarg;
+ break;
+
+ case 'O':
+ if (strcmp(optarg, "xlat_only") == 0) {
+ xlat_only = true;
+ break;
+ }
+
+ fprintf(stderr, "Unknown option '%s'\n", optarg);
+ exit(EXIT_FAILURE);
+
+ case 'X':
+ rad_debug_lvl += 2;
+ main_config.log_auth = true;
+ main_config.log_auth_badpass = true;
+ main_config.log_auth_goodpass = true;
+ break;
+
+ case 'x':
+ rad_debug_lvl++;
+ break;
+
+ default:
+ usage(1);
+ break;
+ }
+ }
+
+ if (rad_debug_lvl) version_print();
+ fr_debug_lvl = rad_debug_lvl;
+
+ /*
+ * Mismatch between the binary and the libraries it depends on
+ */
+ if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
+ fr_perror("radiusd");
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Initialising OpenSSL once, here, is safer than having individual modules do it.
+ */
+#ifdef HAVE_OPENSSL_CRYPTO_H
+ tls_global_init(false, false);
+#endif
+
+ if (xlat_register("poke", xlat_poke, NULL, NULL) < 0) {
+ rcode = EXIT_FAILURE;
+ goto finish;
+ }
+
+ /* Read the configuration files, BEFORE doing anything else. */
+ if (main_config_init() < 0) {
+ rcode = EXIT_FAILURE;
+ goto finish;
+ }
+
+ /*
+ * Load the modules
+ */
+ if (modules_init(main_config.config) < 0) {
+ rcode = EXIT_FAILURE;
+ goto finish;
+ }
+
+ state =fr_state_init(NULL);
+
+ /*
+ * Set the panic action (if required)
+ */
+ {
+ char const *panic_action = NULL;
+
+ panic_action = getenv("PANIC_ACTION");
+ if (!panic_action) panic_action = main_config.panic_action;
+
+ if (panic_action && (fr_fault_setup(panic_action, argv[0]) < 0)) {
+ fr_perror("radiusd");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ setlinebuf(stdout); /* unbuffered output */
+
+ if (!input_file || (strcmp(input_file, "-") == 0)) {
+ fp = stdin;
+ } else {
+ fp = fopen(input_file, "r");
+ if (!fp) {
+ fprintf(stderr, "Failed reading %s: %s\n",
+ input_file, fr_syserror(errno));
+ goto finish;
+ }
+ }
+
+ /*
+ * For simplicity, read xlat's.
+ */
+ if (xlat_only) {
+ if (!do_xlats(input_file, fp)) rcode = EXIT_FAILURE;
+ if (input_file) fclose(fp);
+ goto finish;
+ }
+
+ /*
+ * Grab the VPs from stdin, or from the file.
+ */
+ request = request_setup(fp);
+ if (!request) {
+ fprintf(stderr, "Failed reading input: %s\n", fr_strerror());
+ rcode = EXIT_FAILURE;
+ goto finish;
+ }
+
+ /*
+ * No filter file, OR there's no more input, OR we're
+ * reading from a file, and it's different from the
+ * filter file.
+ */
+ if (!filter_file || filedone ||
+ ((input_file != NULL) && (strcmp(filter_file, input_file) != 0))) {
+ if (output_file) {
+ fclose(fp);
+ fp = NULL;
+ }
+ filedone = false;
+ }
+
+ /*
+ * There is a filter file. If necessary, open it. If we
+ * already are reading it via "input_file", then we don't
+ * need to re-open it.
+ */
+ if (filter_file) {
+ if (!fp) {
+ fp = fopen(filter_file, "r");
+ if (!fp) {
+ fprintf(stderr, "Failed reading %s: %s\n", filter_file, strerror(errno));
+ rcode = EXIT_FAILURE;
+ goto finish;
+ }
+ }
+
+
+ if (fr_pair_list_afrom_file(request, &filter_vps, fp, &filedone) < 0) {
+ fprintf(stderr, "Failed reading attributes from %s: %s\n",
+ filter_file, fr_strerror());
+ rcode = EXIT_FAILURE;
+ goto finish;
+ }
+
+ /*
+ * FIXME: loop over input packets.
+ */
+ fclose(fp);
+ }
+
+ rad_virtual_server(request);
+
+ if (!output_file || (strcmp(output_file, "-") == 0)) {
+ fp = stdout;
+ } else {
+ fp = fopen(output_file, "w");
+ if (!fp) {
+ fprintf(stderr, "Failed writing %s: %s\n",
+ output_file, fr_syserror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ print_packet(fp, request->reply);
+
+ if (output_file) fclose(fp);
+
+ /*
+ * Update the list with the response type.
+ */
+ vp = radius_pair_create(request->reply, &request->reply->vps,
+ PW_RESPONSE_PACKET_TYPE, 0);
+ vp->vp_integer = request->reply->code;
+
+ {
+ VALUE_PAIR const *failed[2];
+
+ if (filter_vps && !fr_pair_validate(failed, filter_vps, request->reply->vps)) {
+ fr_pair_validate_debug(request, failed);
+ fr_perror("Output file %s does not match attributes in filter %s (%s)",
+ output_file ? output_file : input_file, filter_file, fr_strerror());
+ rcode = EXIT_FAILURE;
+ goto finish;
+ }
+ }
+
+ INFO("Exiting normally");
+
+finish:
+ talloc_free(request);
+
+ /*
+ * Detach any modules.
+ */
+ modules_free();
+
+ xlat_unregister("poke", xlat_poke, NULL);
+
+ xlat_free(); /* modules may have xlat's */
+
+ fr_state_delete(state);
+
+ /*
+ * Free the configuration items.
+ */
+ main_config_free();
+
+ if (el) talloc_free(el);
+
+ if (memory_report) {
+ INFO("Allocated memory at time of report:");
+ fr_log_talloc_report(NULL);
+ }
+
+ return rcode;
+}
+
+
+/*
+ * Display the syntax for starting this program.
+ */
+static void NEVER_RETURNS usage(int status)
+{
+ FILE *output = status?stderr:stdout;
+
+ fprintf(output, "Usage: %s [options]\n", main_config.name);
+ fprintf(output, "Options:\n");
+ fprintf(output, " -d raddb_dir Configuration files are in \"raddb_dir/*\".\n");
+ fprintf(output, " -D dict_dir Dictionary files are in \"dict_dir/*\".\n");
+ fprintf(output, " -f file Filter reply against attributes in 'file'.\n");
+ fprintf(output, " -h Print this help message.\n");
+ fprintf(output, " -i file File containing request attributes.\n");
+ fprintf(output, " -m On SIGINT or SIGQUIT exit cleanly instead of immediately.\n");
+ fprintf(output, " -n name Read raddb/name.conf instead of raddb/radiusd.conf.\n");
+ fprintf(output, " -X Turn on full debugging.\n");
+ fprintf(output, " -x Turn on additional debugging. (-xx gives more debugging).\n");
+ exit(status);
+}