summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_eap/radeapclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_eap/radeapclient.c')
-rw-r--r--src/modules/rlm_eap/radeapclient.c2315
1 files changed, 2315 insertions, 0 deletions
diff --git a/src/modules/rlm_eap/radeapclient.c b/src/modules/rlm_eap/radeapclient.c
new file mode 100644
index 0000000..ae24f06
--- /dev/null
+++ b/src/modules/rlm_eap/radeapclient.c
@@ -0,0 +1,2315 @@
+/*
+ * radeapclient.c EAP specific radius packet debug tool.
+ *
+ * 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,2006 The FreeRADIUS server project
+ * Copyright 2000 Miquel van Smoorenburg <miquels@cistron.nl>
+ * Copyright 2000 Alan DeKok <aland@ox.org>
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/libradius.h>
+
+#include <ctype.h>
+#include <assert.h>
+
+#if HAVE_GETOPT_H
+# include <getopt.h>
+#endif
+
+#include <freeradius-devel/conf.h>
+#include <freeradius-devel/radpaths.h>
+#include <freeradius-devel/md5.h>
+
+#include "eap_types.h"
+#include "eap_sim.h"
+#include "comp128.h"
+
+extern int sha1_data_problems;
+
+#undef DEBUG
+#undef DEBUG2
+#undef ERROR
+
+#define DEBUG if (fr_debug_lvl && fr_log_fp) fr_printf_log
+#define DEBUG2 if ((fr_debug_lvl >= 2) && fr_log_fp) fr_printf_log
+#define ERROR if (fr_debug_lvl && fr_log_fp) fr_printf_log
+
+#define USEC 1000000
+
+static uint32_t parallel = 1;
+static unsigned int retries = 3;
+static float timeout = 5;
+static struct timeval tv_timeout;
+static char const *secret = NULL;
+static int do_output = 1;
+static int do_summary = 0;
+static int totalapp = 0;
+static int totaldeny = 0;
+static char filesecret[256];
+static char const *radius_dir = NULL;
+static char const *progname = "radeapclient";
+/* fr_randctx randctx; */
+
+main_config_t main_config;
+char const *radiusd_version = "";
+
+#ifdef WITH_TLS
+#include <freeradius-devel/tls.h>
+#endif
+
+log_lvl_t rad_debug_lvl = 0;
+
+//TODO: move structures to a header file.
+
+typedef struct rc_input_vps_list rc_input_vps_list_t;
+typedef struct rc_input_vps rc_input_vps_t;
+typedef struct rc_transaction rc_transaction_t;
+
+/** Structure which contains EAP context, necessary to perform the full EAP transaction.
+ */
+typedef struct rc_eap_sim_context {
+ struct eapsim_keys keys;
+} rc_eap_sim_context_t;
+
+typedef struct rc_eap_md5_context {
+ int tried;
+} rc_eap_md5_context_t;
+
+typedef struct rc_eap_context {
+ int eap_type; //!< contains the EAP-Type
+ char password[256]; //!< copy of User-Password (or CHAP-Password).
+ VALUE_PAIR *ki;
+ union {
+ rc_eap_sim_context_t sim;
+ rc_eap_md5_context_t md5;
+ } eap;
+} rc_eap_context_t;
+
+
+/** Structure which holds a list of available input vps.
+ */
+struct rc_input_vps_list {
+ rc_input_vps_t *head;
+ rc_input_vps_t *tail;
+ uint32_t size;
+};
+
+/** Structure which holds an input vps entry (read from file or stdin),
+ * and linkage to previous / next entries.
+ */
+struct rc_input_vps {
+ uint32_t num; //!< The number (within the file) of the input we're reading.
+
+ VALUE_PAIR *vps_in; //!< the list of attribute/value pairs.
+
+ rc_input_vps_list_t *list; //!< the list to which this entry belongs (NULL for an unchained entry).
+
+ rc_input_vps_t *prev;
+ rc_input_vps_t *next;
+};
+
+
+/** Structure which holds a transaction: sent packet, reply received...
+ */
+struct rc_transaction {
+ uint32_t id; //!< id of transaction (0 for the first one).
+
+ uint32_t num_packet; //!< number of packets sent for this transaction.
+
+ RADIUS_PACKET *packet;
+ RADIUS_PACKET *reply;
+
+ rc_input_vps_t *input_vps;
+
+ rc_eap_context_t *eap_context;
+
+ uint32_t tries;
+
+ fr_event_t *event; //!< armed event (if any).
+
+ char password[256];
+ char const *name; //!< Test name (as specified in the request).
+};
+
+typedef struct eap_sim_server_state {
+ enum eapsim_serverstates state;
+ struct eapsim_keys keys;
+ int sim_id;
+} eap_sim_state_t;
+
+
+static TALLOC_CTX *autofree;
+static uint32_t num_trans = 0; //!< number of transactions initialized.
+static uint32_t num_started = 0; //!< number of transactions started.
+static uint32_t num_ongoing = 0; //!< number of ongoing transactions.
+static uint32_t num_finished = 0; //!< number of finished transactions.
+
+static rc_input_vps_list_t rc_vps_list_in; //!< list of available input vps entries.
+static fr_packet_list_t *pl = NULL; //!< list of outgoing packets.
+static unsigned int num_sockets = 0; //!< number of allocated sockets.
+static fr_event_list_t *ev_list = NULL; //!< list of armed events.
+
+static int force_af = AF_UNSPEC;
+static int ipproto = IPPROTO_UDP;
+static fr_ipaddr_t server_ipaddr;
+static bool server_addr_init = false;
+static uint16_t server_port = 0;
+static int packet_code = PW_CODE_UNDEFINED;
+
+static int rc_map_eap_methods(RADIUS_PACKET *req);
+static void rc_unmap_eap_methods(RADIUS_PACKET *rep);
+static int rc_map_eapsim_types(RADIUS_PACKET *r);
+static int rc_unmap_eapsim_types(RADIUS_PACKET *r);
+
+static void rc_get_port(PW_CODE type, uint16_t *port);
+static void rc_evprep_packet_timeout(rc_transaction_t *trans);
+static void rc_deallocate_id(rc_transaction_t *trans);
+
+/*
+ * For cbtls_cache_*()
+ */
+rlm_rcode_t process_post_auth(UNUSED int postauth_type, UNUSED REQUEST *request)
+{
+ return RLM_MODULE_FAIL;
+}
+
+
+fr_event_list_t *radius_event_list_corral(UNUSED event_corral_t hint)
+{
+ return NULL;
+}
+
+static void NEVER_RETURNS usage(void)
+{
+ fprintf(stdout, "Usage: radeapclient [options] server[:port] <command> [<secret>]\n");
+
+ fprintf(stdout, " <command> One of auth, acct, status, coa, disconnect or auto.\n");
+ fprintf(stdout, " -4 Use IPv4 address of server\n");
+ fprintf(stdout, " -6 Use IPv6 address of server.\n");
+ fprintf(stdout, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n");
+ fprintf(stdout, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n");
+ fprintf(stdout, " -f <file> Read packets from file, not stdin.\n");
+ fprintf(stdout, " -h Print usage help information.\n");
+ fprintf(stdout, " -p <num> Send 'num' packets in parallel.\n");
+ fprintf(stdout, " -q Do not print anything out.\n");
+ fprintf(stdout, " -r <retries> If timeout, retry sending the packet 'retries' times.\n");
+ fprintf(stdout, " -s Print out summary information of auth results.\n");
+ fprintf(stdout, " -S <file> read secret from file, not command line.\n");
+ fprintf(stdout, " -t <timeout> Wait 'timeout' seconds before retrying (may be a floating point number).\n");
+ fprintf(stdout, " -v Show program version information.\n");
+ fprintf(stdout, " -x Debugging mode.\n");
+
+ exit(1);
+}
+
+static const FR_NAME_NUMBER rc_request_types[] = {
+ { "auth", PW_CODE_ACCESS_REQUEST },
+ { "challenge", PW_CODE_ACCESS_CHALLENGE },
+ { "acct", PW_CODE_ACCOUNTING_REQUEST },
+ { "status", PW_CODE_STATUS_SERVER },
+ { "disconnect", PW_CODE_DISCONNECT_REQUEST },
+ { "coa", PW_CODE_COA_REQUEST },
+ { "auto", PW_CODE_UNDEFINED },
+
+ { NULL, 0}
+};
+
+int rad_virtual_server(REQUEST UNUSED *request)
+{
+ /*We're not the server so we cannot do this*/
+ abort();
+}
+
+/** Convert a float to struct timeval.
+ */
+static void rc_float_to_timeval(struct timeval *tv, float f_val)
+{
+ tv->tv_sec = (time_t)f_val;
+ uint64_t usec = (uint64_t)(f_val * USEC) - (tv->tv_sec * USEC);
+ tv->tv_usec = usec;
+}
+
+/** Add an allocated rc_input_vps_t entry to the tail of the list.
+ */
+ static void rc_add_vps_entry(rc_input_vps_list_t *list, rc_input_vps_t *entry)
+{
+ if (!list || !entry) return;
+
+ if (!list->head) {
+ assert(list->tail == NULL);
+ list->head = entry;
+ entry->prev = NULL;
+ } else {
+ assert(list->tail != NULL);
+ assert(list->tail->next == NULL);
+ list->tail->next = entry;
+ entry->prev = list->tail;
+ }
+ list->tail = entry;
+ entry->next = NULL;
+ entry->list = list;
+ list->size ++;
+}
+
+/** Remove a selected rc_input_vps_t entry from its current list.
+ */
+static rc_input_vps_t *rc_yank_vps_entry(rc_input_vps_t *entry)
+{
+ if (!entry) return NULL;
+
+ if (!entry->list) return entry; /* not in a list, nothing to do. Just return the entry. */
+
+ rc_input_vps_t *prev, *next;
+
+ prev = entry->prev;
+ next = entry->next;
+
+ rc_input_vps_list_t *list = entry->list;
+
+ assert(list->head != NULL); /* entry belongs to a list, so the list can't be empty. */
+ assert(list->tail != NULL); /* same. */
+
+ if (prev) {
+ assert(list->head != entry); /* if entry has a prev, then entry can't be head. */
+ prev->next = next;
+ }
+ else {
+ assert(list->head == entry); /* if entry has no prev, then entry must be head. */
+ list->head = next;
+ }
+
+ if (next) {
+ assert(list->tail != entry); /* if entry has a next, then entry can't be tail. */
+ next->prev = prev;
+ }
+ else {
+ assert(list->tail == entry); /* if entry has no next, then entry must be tail. */
+ list->tail = prev;
+ }
+
+ entry->list = NULL;
+ entry->prev = NULL;
+ entry->next = NULL;
+ list->size --;
+ return entry;
+}
+
+/** Load input entries (list of vps) from a file or stdin, and add them to the list.
+ * They will be used to initiate transactions.
+ */
+static int rc_load_input(TALLOC_CTX *ctx, char const *filename, rc_input_vps_list_t *list, uint32_t max_entries)
+{
+ FILE *file_in = NULL;
+ bool file_done = false;
+ rc_input_vps_t *request;
+ char const *input;
+ uint32_t input_num = 0;
+
+ /* Determine where to read the VP's from. */
+ if (filename && strcmp(filename, "-") != 0) {
+ DEBUG2("Opening input file: %s\n", filename);
+ file_in = fopen(filename, "r");
+ if (!file_in) {
+ ERROR("Error opening %s: %s\n", filename, strerror(errno));
+ return 0;
+ }
+ input = filename;
+ } else {
+ DEBUG2("Reading input vps from stdin\n");
+ file_in = stdin;
+ input = "stdin";
+ }
+
+ /* Loop over the file (or stdin). */
+ do {
+ input_num ++;
+ MEM(request = talloc_zero(ctx, rc_input_vps_t));
+
+ if (fr_pair_list_afrom_file(request, &request->vps_in, file_in, &file_done) < 0) {
+ ERROR("Error parsing entry %u from input: %s\n", input_num, input);
+ talloc_free(request);
+ break;
+ }
+ if (NULL == request->vps_in) {
+ /* Last line might be empty, in this case fr_pair_list_afrom_file will return a NULL vps pointer. Silently ignore this. */
+ talloc_free(request);
+ break;
+ }
+
+ /* Add that to the list */
+ rc_add_vps_entry(list, request);
+
+ request->num = list->size;
+
+ if (max_entries && list->size >= max_entries) {
+ /* Only load what we need. */
+ break;
+ }
+ } while (!file_done);
+
+ if (file_in != stdin) fclose(file_in);
+
+ /* And we're done. */
+ DEBUG("Read %d element(s) from input: %s\n", list->size, input);
+ return 1;
+}
+
+/** Perform packet initialization for a transaction.
+ */
+static int rc_init_packet(rc_transaction_t *trans)
+{
+ if (!trans || !trans->packet) return 0;
+
+ RADIUS_PACKET *packet = trans->packet;
+ vp_cursor_t cursor;
+ VALUE_PAIR *vp;
+
+ /*
+ * Process special attributes
+ */
+ for (vp = fr_cursor_init(&cursor, &packet->vps);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ /*
+ * Double quoted strings get marked up as xlat expansions,
+ * but we don't support that in request.
+ */
+ if (vp->type == VT_XLAT) {
+ vp->type = VT_DATA;
+ vp->vp_strvalue = vp->value.xlat;
+ vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1;
+ }
+
+ 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:
+ packet->code = vp->vp_integer;
+ break;
+
+ case PW_PACKET_DST_PORT:
+ packet->dst_port = (vp->vp_integer & 0xffff);
+ break;
+
+ case PW_PACKET_DST_IP_ADDRESS:
+ packet->dst_ipaddr.af = AF_INET;
+ packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ packet->dst_ipaddr.prefix = 32;
+ break;
+
+ case PW_PACKET_DST_IPV6_ADDRESS:
+ packet->dst_ipaddr.af = AF_INET6;
+ packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
+ packet->dst_ipaddr.prefix = 128;
+ break;
+
+ case PW_PACKET_SRC_PORT:
+ if ((vp->vp_integer < 1024) ||
+ (vp->vp_integer > 65535)) {
+ DEBUG("Invalid value '%u' for Packet-Src-Port\n", vp->vp_integer);
+ } else {
+ packet->src_port = (vp->vp_integer & 0xffff);
+ }
+ break;
+
+ case PW_PACKET_SRC_IP_ADDRESS:
+ packet->src_ipaddr.af = AF_INET;
+ packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ packet->src_ipaddr.prefix = 32;
+ break;
+
+ case PW_PACKET_SRC_IPV6_ADDRESS:
+ packet->src_ipaddr.af = AF_INET6;
+ packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
+ packet->src_ipaddr.prefix = 128;
+ 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);
+ if (!da) {
+ ERROR("Attribute 'Digest-Attributes' not found by value\n");
+ exit(1);
+ }
+ 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;
+
+ /*
+ * Keep a copy of the the password attribute.
+ */
+ case PW_CLEARTEXT_PASSWORD:
+ case PW_USER_PASSWORD:
+ case PW_CHAP_PASSWORD:
+ case PW_MS_CHAP_PASSWORD:
+ strlcpy(trans->password, vp->vp_strvalue, sizeof(trans->password));
+ break;
+
+ case PW_RADCLIENT_TEST_NAME:
+ trans->name = vp->vp_strvalue;
+ break;
+ }
+ } /* loop over the VP's we read in */
+
+ if (packet->dst_port == 0) packet->dst_port = server_port;
+
+ if (packet->dst_ipaddr.af == AF_UNSPEC) {
+ if (!server_addr_init) {
+ DEBUG("No server was given, and input entry %u did not contain Packet-Dst-IP-Address, ignored.\n", trans->input_vps->num);
+ return 0;
+ }
+ packet->dst_ipaddr = server_ipaddr;
+ }
+
+ /* Use the default set on the command line. */
+ if (packet->code == PW_CODE_UNDEFINED) {
+ if (packet_code == PW_CODE_UNDEFINED) {
+ DEBUG("No packet type was given, and input entry %u did not contain Packet-Type, ignored.\n", trans->input_vps->num);
+ return 0;
+ }
+ packet->code = packet_code;
+ }
+
+ /* Automatically set the dst port (if one wasn't already set). */
+ if (packet->dst_port == 0) {
+ rc_get_port(packet->code, &packet->dst_port);
+ if (packet->dst_port == 0) {
+ DEBUG("Can't determine destination port for input entry %u, ignored.\n", trans->input_vps->num);
+ return 0;
+ }
+ }
+
+ packet->sockfd = -1;
+
+ /* Done. */
+ return 1;
+}
+
+/** Map EAP methods and build EAP-Message (if EAP is involved).
+ * Also allocate the EAP context.
+ */
+static void rc_build_eap_context(rc_transaction_t *trans)
+{
+ if (!trans || !trans->packet) return;
+
+ RADIUS_PACKET *packet = trans->packet;
+
+ /* Build EAP-Message (if EAP is involved. Otherwise, do nothing). */
+ int eap_type = rc_map_eap_methods(packet);
+
+ if (eap_type) {
+ if (!trans->eap_context) {
+ MEM(trans->eap_context = talloc_zero(trans, rc_eap_context_t));
+ }
+ trans->eap_context->eap_type = eap_type;
+
+ /*
+ * Keep a copy of the the User-Password or CHAP-Password.
+ * Note: this is not useful for EAP-SIM, but we cannot know what kind
+ * of challenge the server will issue.
+ */
+ VALUE_PAIR *vp;
+ vp = fr_pair_find_by_num(packet->vps, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
+ if (!vp) vp = fr_pair_find_by_num(packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
+ if (!vp) vp = fr_pair_find_by_num(packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY);
+ if (vp) {
+ strlcpy(trans->eap_context->password, vp->vp_strvalue, sizeof(trans->eap_context->password));
+ }
+
+ vp = fr_pair_find_by_num(packet->vps, PW_EAP_SIM_KI, 0, TAG_ANY);
+ if (vp) trans->eap_context->ki = fr_pair_copy(autofree, vp);
+ }
+}
+
+
+/** Grab an element from the input list. Initialize a new transaction context, using this element.
+ */
+static rc_transaction_t *rc_init_transaction(TALLOC_CTX *ctx)
+{
+ if (!rc_vps_list_in.head || rc_vps_list_in.size == 0) {
+ /* Empty list, can't create a new transaction. */
+ return NULL;
+ }
+
+ rc_input_vps_t *vps_entry = rc_vps_list_in.head;
+
+ rc_yank_vps_entry(vps_entry); /* This cannot fail (we checked the list beforehand.) */
+
+ /* We grabbed an vps entry, now we can initialize a new transaction. */
+ rc_transaction_t *trans;
+ MEM(trans = talloc_zero(ctx, rc_transaction_t));
+
+ trans->input_vps = vps_entry;
+ trans->id = num_trans ++;
+
+ talloc_steal(trans, vps_entry); /* It's ours now. */
+
+ RADIUS_PACKET *packet;
+ MEM(packet = rad_alloc(trans, 1));
+ trans->packet = packet;
+
+ /* Fill in the packet value pairs. */
+ packet->vps = fr_pair_list_copy(packet, vps_entry->vps_in);
+
+ /* Initialize the transaction packet. */
+ if (!rc_init_packet(trans)) {
+ /* Failed... */
+ talloc_free(trans);
+ return NULL;
+ }
+
+ /* Update transactions counters. */
+ num_started ++;
+ num_ongoing ++;
+
+ return trans;
+}
+
+/** Terminate a transaction.
+ */
+static void rc_finish_transaction(rc_transaction_t *trans)
+{
+ if (!trans) return;
+
+ if (trans->event) fr_event_delete(ev_list, &trans->event);
+ rc_deallocate_id(trans);
+ talloc_free(trans);
+
+ /* Update transactions counters. */
+ num_ongoing --;
+ num_finished ++;
+
+ DEBUG4("pl: %d, ev: %d, in: %d\n", fr_packet_list_num_outgoing(pl), fr_event_list_num_elements(ev_list), rc_vps_list_in.size);
+}
+
+
+static uint16_t getport(char const *name)
+{
+ struct servent *svp;
+
+ svp = getservbyname(name, "udp");
+ if (!svp) return 0;
+
+ return ntohs(svp->s_port);
+}
+
+
+static void rc_cleanresp(RADIUS_PACKET *resp)
+{
+ VALUE_PAIR *vpnext, *vp, **last;
+
+ /*
+ * maybe should just copy things we care about, or keep
+ * a copy of the original input and start from there again?
+ */
+ fr_pair_delete_by_num(&resp->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
+ fr_pair_delete_by_num(&resp->vps, PW_EAP_TYPE_BASE+PW_EAP_IDENTITY, 0, TAG_ANY);
+
+ last = &resp->vps;
+ for (vp = *last; vp != NULL; vp = vpnext)
+ {
+ vpnext = vp->next;
+
+ if ((vp->da->attr > PW_EAP_TYPE_BASE &&
+ vp->da->attr <= PW_EAP_TYPE_BASE+256) ||
+ (vp->da->attr > PW_EAP_SIM_BASE &&
+ vp->da->attr <= PW_EAP_SIM_BASE+256))
+ {
+ *last = vpnext;
+ talloc_free(vp);
+ } else {
+ last = &vp->next;
+ }
+ }
+}
+
+
+static void generate_triplets(RADIUS_PACKET *packet, VALUE_PAIR *ki, uint8_t const *ch)
+{
+ int i, idx;
+ eap_sim_state_t ess;
+
+ for (idx = 0; idx < 3; idx++) {
+ VALUE_PAIR *vp;
+ char *p;
+ char buffer[33]; /* 32 hexits (16 bytes) + 1 */
+
+ for (i = 0; i < EAPSIM_RAND_SIZE; i++) {
+ ess.keys.rand[idx][i] = ch[(idx * EAPSIM_RAND_SIZE) + i];
+ }
+
+ /*
+ * <sigh>, we always do version 1.
+ */
+ switch (EAP_SIM_VERSION) {
+ case 1:
+ comp128v1(ess.keys.sres[idx], ess.keys.Kc[idx], ki->vp_octets, ess.keys.rand[idx]);
+ break;
+
+ case 2:
+ comp128v23(ess.keys.sres[idx], ess.keys.Kc[idx], ki->vp_octets, ess.keys.rand[idx],
+ true);
+ break;
+
+ case 3:
+ comp128v23(ess.keys.sres[idx], ess.keys.Kc[idx], ki->vp_octets, ess.keys.rand[idx],
+ false);
+ break;
+
+ case 4:
+ DEBUG("Comp128-4 algorithm is not supported as details have not yet been published. "
+ "If you have details of this algorithm please contact the FreeRADIUS "
+ "maintainers\n");
+ break;
+
+ default:
+ DEBUG("Unknown/unsupported algorithm Comp128-4\n");
+ }
+
+
+ DEBUG2("Generated following triplets for round %i:\n", idx);
+
+ p = buffer;
+ for (i = 0; i < EAPSIM_RAND_SIZE; i++) {
+ p += sprintf(p, "%02x", ess.keys.rand[idx][i]);
+ }
+ DEBUG2("RAND%d : 0x%s\n", idx, buffer);
+ vp = fr_pair_afrom_num(packet, PW_EAP_SIM_RAND1 + idx, 0);
+ fr_pair_value_memcpy(vp, ess.keys.rand[idx], EAPSIM_RAND_SIZE);
+ fr_pair_add(&packet->vps, vp);
+
+ p = buffer;
+ for (i = 0; i < EAPSIM_SRES_SIZE; i++) {
+ p += sprintf(p, "%02x", ess.keys.sres[idx][i]);
+ }
+ DEBUG2("SRES%d : 0x%s\n", idx, buffer);
+ vp = fr_pair_afrom_num(packet, PW_EAP_SIM_SRES1 + idx, 0);
+ fr_pair_value_memcpy(vp, ess.keys.sres[idx], EAPSIM_SRES_SIZE);
+ fr_pair_add(&packet->vps, vp);
+
+ p = buffer;
+ for (i = 0; i < EAPSIM_KC_SIZE; i++) {
+ p += sprintf(p, "%02x", ess.keys.Kc[idx][i]);
+ }
+ DEBUG2("Kc%d : 0x%s\n", idx, buffer);
+ vp = fr_pair_afrom_num(packet, PW_EAP_SIM_KC1 + idx, 0);
+ fr_pair_value_memcpy(vp, ess.keys.Kc[idx], EAPSIM_KC_SIZE);
+ fr_pair_add(&packet->vps, vp);
+ }
+}
+
+
+/*
+ * we got an EAP-Request/Sim/Start message in a legal state.
+ *
+ * pick a supported version, put it into the reply, and insert a nonce.
+ */
+static int rc_process_eap_start(rc_eap_context_t *eap_context,
+ RADIUS_PACKET *req, RADIUS_PACKET *rep)
+{
+ VALUE_PAIR *vp, *newvp;
+ VALUE_PAIR *anyidreq_vp, *fullauthidreq_vp, *permanentidreq_vp;
+ uint16_t const *versions;
+ uint16_t selectedversion;
+ unsigned int i,versioncount;
+ VALUE_PAIR *ki;
+
+ /* form new response clear of any EAP stuff */
+ rc_cleanresp(rep);
+
+ if ((vp = fr_pair_find_by_num(req->vps, PW_EAP_SIM_VERSION_LIST, 0, TAG_ANY)) == NULL) {
+ ERROR("illegal start message has no VERSION_LIST\n");
+ return 0;
+ }
+
+ versions = (uint16_t const *) vp->vp_strvalue;
+
+ /* verify that the attribute length is big enough for a length field */
+ if (vp->vp_length < 4)
+ {
+ ERROR("start message has illegal VERSION_LIST. Too short: %u\n", (unsigned int) vp->vp_length);
+ return 0;
+ }
+
+ versioncount = ntohs(versions[0])/2;
+ /* verify that the attribute length is big enough for the given number
+ * of versions present.
+ */
+ if ((unsigned)vp->vp_length <= (versioncount*2 + 2))
+ {
+ ERROR("start message is too short. Claimed %d versions does not fit in %u bytes\n", versioncount, (unsigned int) vp->vp_length);
+ return 0;
+ }
+
+ /*
+ * record the versionlist for the MK calculation.
+ */
+ eap_context->eap.sim.keys.versionlistlen = versioncount*2;
+ memcpy(eap_context->eap.sim.keys.versionlist, (unsigned char const *)(versions+1),
+ eap_context->eap.sim.keys.versionlistlen);
+
+ /* walk the version list, and pick the one we support, which
+ * at present, is 1, EAP_SIM_VERSION.
+ */
+ selectedversion=0;
+ for (i=0; i < versioncount; i++)
+ {
+ if (ntohs(versions[i+1]) == EAP_SIM_VERSION)
+ {
+ selectedversion=EAP_SIM_VERSION;
+ break;
+ }
+ }
+ if (selectedversion == 0)
+ {
+ ERROR("eap-sim start message. No compatible version found. We need %d\n", EAP_SIM_VERSION);
+ for (i=0; i < versioncount; i++)
+ {
+ ERROR("\tfound version %d\n",
+ ntohs(versions[i+1]));
+ }
+ }
+
+ /*
+ * now make sure that we have only FULLAUTH_ID_REQ.
+ * I think that it actually might not matter - we can answer in
+ * anyway we like, but it is illegal to have more than one
+ * present.
+ */
+ anyidreq_vp = fr_pair_find_by_num(req->vps, PW_EAP_SIM_ANY_ID_REQ, 0, TAG_ANY);
+ fullauthidreq_vp = fr_pair_find_by_num(req->vps, PW_EAP_SIM_FULLAUTH_ID_REQ, 0, TAG_ANY);
+ permanentidreq_vp = fr_pair_find_by_num(req->vps, PW_EAP_SIM_PERMANENT_ID_REQ, 0, TAG_ANY);
+
+ if (!fullauthidreq_vp ||
+ anyidreq_vp != NULL ||
+ permanentidreq_vp != NULL) {
+ ERROR("start message has %sanyidreq, %sfullauthid and %spermanentid. Illegal combination.\n",
+ (anyidreq_vp != NULL ? "a ": "no "),
+ (fullauthidreq_vp != NULL ? "a ": "no "),
+ (permanentidreq_vp != NULL ? "a ": "no "));
+ return 0;
+ }
+
+ /* okay, we have just any_id_req there, so fill in response */
+
+ /* mark the subtype as being EAP-SIM/Response/Start */
+ newvp = fr_pair_afrom_num(rep, PW_EAP_SIM_SUBTYPE, 0);
+ newvp->vp_integer = EAPSIM_START;
+ fr_pair_replace(&(rep->vps), newvp);
+
+ /* insert selected version into response. */
+ {
+ uint16_t no_versions;
+
+ no_versions = htons(selectedversion);
+
+ newvp = fr_pair_afrom_num(rep, PW_EAP_SIM_SELECTED_VERSION, 0);
+ fr_pair_value_memcpy(newvp, (uint8_t *) &no_versions, 2);
+ fr_pair_replace(&(rep->vps), newvp);
+
+ /* record the selected version */
+ memcpy(eap_context->eap.sim.keys.versionselect, &no_versions, 2);
+ }
+
+ vp = newvp = NULL;
+
+ {
+ uint32_t nonce[4];
+ uint8_t *p;
+ /*
+ * insert a nonce_mt that we make up.
+ */
+ nonce[0]=fr_rand();
+ nonce[1]=fr_rand();
+ nonce[2]=fr_rand();
+ nonce[3]=fr_rand();
+
+ newvp = fr_pair_afrom_num(rep, PW_EAP_SIM_NONCE_MT, 0);
+
+ p = talloc_zero_array(newvp, uint8_t, 18); /* 18 = 16 bytes of nonce + padding */
+ memcpy(&p[2], nonce, 16);
+ fr_pair_value_memsteal(newvp, p);
+
+ fr_pair_replace(&(rep->vps), newvp);
+
+ /* also keep a copy of the nonce! */
+ memcpy(eap_context->eap.sim.keys.nonce_mt, nonce, 16);
+ }
+
+ {
+ uint16_t idlen;
+ uint8_t *p;
+ uint16_t no_idlen;
+
+ /*
+ * insert the identity here.
+ */
+ vp = fr_pair_find_by_num(rep->vps, PW_USER_NAME, 0, TAG_ANY);
+ if (!vp)
+ {
+ ERROR("eap-sim: We need to have a User-Name attribute!\n");
+ return 0;
+ }
+ newvp = fr_pair_afrom_num(rep, PW_EAP_SIM_IDENTITY, 0);
+
+ idlen = strlen(vp->vp_strvalue);
+ p = talloc_zero_array(newvp, uint8_t, idlen + 2);
+ no_idlen = htons(idlen);
+ memcpy(p, &no_idlen, 2);
+ memcpy(p + 2, vp->vp_strvalue, idlen);
+ fr_pair_value_memsteal(newvp, p);
+
+ fr_pair_replace(&(rep->vps), newvp);
+
+ /* record it */
+ memcpy(eap_context->eap.sim.keys.identity, vp->vp_strvalue, idlen);
+ eap_context->eap.sim.keys.identitylen = idlen;
+ }
+
+ ki = fr_pair_find_by_num(req->vps, PW_EAP_SIM_KI, 0, TAG_ANY);
+ if (ki && !fr_pair_find_by_num(req->vps, PW_EAP_SIM_RAND1, 0, TAG_ANY)) {
+ generate_triplets(req, ki, NULL);
+ }
+
+ return 1;
+}
+
+
+/*
+ * we got an EAP-Request/Sim/Challenge message in a legal state.
+ *
+ * use the RAND challenge to produce the SRES result, and then
+ * use that to generate a new MAC.
+ *
+ * for the moment, we ignore the RANDs, then just plug in the SRES
+ * values.
+ *
+ */
+static int rc_process_eap_challenge(rc_eap_context_t *eap_context,
+ RADIUS_PACKET *req, RADIUS_PACKET *rep)
+{
+ VALUE_PAIR *newvp;
+ VALUE_PAIR *mac, *randvp;
+ VALUE_PAIR *sres1,*sres2,*sres3;
+ VALUE_PAIR *Kc1, *Kc2, *Kc3;
+ uint8_t calcmac[20];
+
+ /* look for the AT_MAC and the challenge data */
+ mac = fr_pair_find_by_num(req->vps, PW_EAP_SIM_MAC, 0, TAG_ANY);
+ randvp= fr_pair_find_by_num(req->vps, PW_EAP_SIM_RAND, 0, TAG_ANY);
+ if (!mac || !randvp) {
+ ERROR("challenge message needs to contain RAND and MAC\n");
+ return 0;
+ }
+
+ /*
+ * compare RAND with randX, to verify this is the right response
+ * to this challenge.
+ */
+ {
+ VALUE_PAIR *randcfgvp[3];
+ uint8_t const *randcfg[3];
+
+ randcfg[0] = &randvp->vp_octets[2];
+ randcfg[1] = &randvp->vp_octets[2+EAPSIM_RAND_SIZE];
+ randcfg[2] = &randvp->vp_octets[2+EAPSIM_RAND_SIZE*2];
+
+ randcfgvp[0] = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_RAND1, 0, TAG_ANY);
+ randcfgvp[1] = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_RAND2, 0, TAG_ANY);
+ randcfgvp[2] = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_RAND3, 0, TAG_ANY);
+
+ if (!randcfgvp[0] ||
+ !randcfgvp[1] ||
+ !randcfgvp[2]) {
+ int i;
+ VALUE_PAIR *ki;
+
+ /*
+ * Generate a new RAND value, and derive Kc and SRES from
+ * Ki, but only if we don't already have the random
+ * numbers.
+ */
+ ki = eap_context->ki;
+ if (!ki) {
+ ERROR("Need EAP-SIM-Rand1, EAP-SIM-Rand2, and EAP-SIM-Rand3\n");
+ return 0;
+ }
+
+ for (i = 0; i < 3; i++) {
+ fr_pair_delete_by_num(&req->vps, PW_EAP_SIM_RAND1 + i, 0, TAG_ANY);
+ fr_pair_delete_by_num(&req->vps, PW_EAP_SIM_SRES1 + i, 0, TAG_ANY);
+ fr_pair_delete_by_num(&req->vps, PW_EAP_SIM_KC1 + i, 0, TAG_ANY);
+ }
+
+ generate_triplets(rep, ki, randvp->vp_octets + 2);
+
+ randcfgvp[0] = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_RAND1, 0, TAG_ANY);
+ randcfgvp[1] = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_RAND2, 0, TAG_ANY);
+ randcfgvp[2] = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_RAND3, 0, TAG_ANY);
+
+ if (!randcfgvp[0] ||
+ !randcfgvp[1] ||
+ !randcfgvp[2]) {
+ ERROR("Failed to create triplets\n");
+ return 0;
+ }
+ }
+
+ if (memcmp(randcfg[0], randcfgvp[0]->vp_octets, EAPSIM_RAND_SIZE)!=0 ||
+ memcmp(randcfg[1], randcfgvp[1]->vp_octets, EAPSIM_RAND_SIZE)!=0 ||
+ memcmp(randcfg[2], randcfgvp[2]->vp_octets, EAPSIM_RAND_SIZE)!=0) {
+ int rnum, i;
+
+ ERROR("one of rand 1,2,3 didn't match\n");
+ for (rnum = 0; rnum < 3; rnum++) {
+ ERROR("rand %d\trecv\tconfig\n", rnum);
+ for (i = 0; i < EAPSIM_RAND_SIZE; i++) {
+ fprintf(fr_log_fp, "\t%02x\t%02x\n",
+ randcfg[rnum][i], randcfgvp[rnum]->vp_octets[i]);
+ }
+ }
+ return 0;
+ }
+ }
+
+ /*
+ * now dig up the sres values from the response packet,
+ * which were put there when we read things in.
+ *
+ * Really, they should be calculated from the RAND!
+ *
+ */
+ sres1 = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_SRES1, 0, TAG_ANY);
+ sres2 = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_SRES2, 0, TAG_ANY);
+ sres3 = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_SRES3, 0, TAG_ANY);
+
+ if (!sres1 ||
+ !sres2 ||
+ !sres3) {
+ ERROR("needs to have sres1, 2 and 3 set.\n");
+ return 0;
+ }
+ memcpy(eap_context->eap.sim.keys.sres[0], sres1->vp_strvalue, sizeof(eap_context->eap.sim.keys.sres[0]));
+ memcpy(eap_context->eap.sim.keys.sres[1], sres2->vp_strvalue, sizeof(eap_context->eap.sim.keys.sres[1]));
+ memcpy(eap_context->eap.sim.keys.sres[2], sres3->vp_strvalue, sizeof(eap_context->eap.sim.keys.sres[2]));
+
+ Kc1 = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_KC1, 0, TAG_ANY);
+ Kc2 = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_KC2, 0, TAG_ANY);
+ Kc3 = fr_pair_find_by_num(rep->vps, PW_EAP_SIM_KC3, 0, TAG_ANY);
+
+ if (!Kc1 ||
+ !Kc2 ||
+ !Kc3) {
+ ERROR("needs to have Kc1, 2 and 3 set.\n");
+ return 0;
+ }
+ memcpy(eap_context->eap.sim.keys.Kc[0], Kc1->vp_strvalue, sizeof(eap_context->eap.sim.keys.Kc[0]));
+ memcpy(eap_context->eap.sim.keys.Kc[1], Kc2->vp_strvalue, sizeof(eap_context->eap.sim.keys.Kc[1]));
+ memcpy(eap_context->eap.sim.keys.Kc[2], Kc3->vp_strvalue, sizeof(eap_context->eap.sim.keys.Kc[2]));
+
+ /* all set, calculate keys */
+ eapsim_calculate_keys(&eap_context->eap.sim.keys);
+
+ if (rad_debug_lvl > 2) {
+ eapsim_dump_mk(&eap_context->eap.sim.keys);
+ }
+
+ /* verify the MAC, now that we have all the keys. */
+ if (eapsim_checkmac(NULL, req->vps, eap_context->eap.sim.keys.K_aut,
+ eap_context->eap.sim.keys.nonce_mt, sizeof(eap_context->eap.sim.keys.nonce_mt),
+ calcmac)) {
+ DEBUG2("MAC check succeed\n");
+ } else {
+ int i, j;
+ j=0;
+ DEBUG("calculated MAC (\n");
+ for (i = 0; i < 20; i++) {
+ if (j==4) {
+ printf("_");
+ j=0;
+ }
+ j++;
+
+ DEBUG("%02x\n", calcmac[i]);
+ }
+ DEBUG("did not match\n");
+ return 0;
+ }
+
+ /* form new response clear of any EAP stuff */
+ rc_cleanresp(rep);
+
+ /* mark the subtype as being EAP-SIM/Response/Start */
+ newvp = fr_pair_afrom_num(rep, PW_EAP_SIM_SUBTYPE, 0);
+ newvp->vp_integer = EAPSIM_CHALLENGE;
+ fr_pair_replace(&(rep->vps), newvp);
+
+ {
+ uint8_t *p;
+ /*
+ * fill the SIM_MAC with a field that will in fact get appended
+ * to the packet before the MAC is calculated
+ */
+ newvp = fr_pair_afrom_num(rep, PW_EAP_SIM_MAC, 0);
+
+ p = talloc_zero_array(newvp, uint8_t, EAPSIM_SRES_SIZE*3);
+ memcpy(p+EAPSIM_SRES_SIZE * 0, sres1->vp_strvalue, EAPSIM_SRES_SIZE);
+ memcpy(p+EAPSIM_SRES_SIZE * 1, sres2->vp_strvalue, EAPSIM_SRES_SIZE);
+ memcpy(p+EAPSIM_SRES_SIZE * 2, sres3->vp_strvalue, EAPSIM_SRES_SIZE);
+ fr_pair_value_memsteal(newvp, p);
+
+ fr_pair_replace(&(rep->vps), newvp);
+ }
+
+ newvp = fr_pair_afrom_num(rep, PW_EAP_SIM_KEY, 0);
+ fr_pair_value_memcpy(newvp, eap_context->eap.sim.keys.K_aut, EAPSIM_AUTH_SIZE);
+
+ fr_pair_replace(&(rep->vps), newvp);
+
+ return 1;
+}
+
+/*
+ * this code runs the EAP-SIM client state machine.
+ * the *request* is from the server.
+ * the *reponse* is to the server.
+ *
+ */
+static int rc_respond_eap_sim(rc_eap_context_t *eap_context,
+ RADIUS_PACKET *req, RADIUS_PACKET *resp)
+{
+ enum eapsim_clientstates state, newstate;
+ enum eapsim_subtype subtype, newsubtype;
+ VALUE_PAIR *vp, *statevp, *radstate, *eapid;
+ char statenamebuf[32], subtypenamebuf[32];
+
+ if ((radstate = fr_pair_list_copy_by_num(NULL, req->vps, PW_STATE, 0, TAG_ANY)) == NULL)
+ {
+ return 0;
+ }
+
+ if ((eapid = fr_pair_list_copy_by_num(NULL, req->vps, PW_EAP_ID, 0, TAG_ANY)) == NULL)
+ {
+ return 0;
+ }
+
+ /* first, dig up the state from the request packet, setting
+ * ourselves to be in EAP-SIM-Start state if there is none.
+ */
+
+ if ((statevp = fr_pair_find_by_num(resp->vps, PW_EAP_SIM_STATE, 0, TAG_ANY)) == NULL)
+ {
+ /* must be initial request */
+ statevp = fr_pair_afrom_num(resp, PW_EAP_SIM_STATE, 0);
+ statevp->vp_integer = EAPSIM_CLIENT_INIT;
+ fr_pair_replace(&(resp->vps), statevp);
+ }
+ state = statevp->vp_integer;
+
+ /*
+ * map the attributes, and authenticate them.
+ */
+ rc_unmap_eapsim_types(req);
+
+ if ((vp = fr_pair_find_by_num(req->vps, PW_EAP_SIM_SUBTYPE, 0, TAG_ANY)) == NULL)
+ {
+ return 0;
+ }
+ subtype = vp->vp_integer;
+
+ DEBUG2("IN state %s subtype %s\n",
+ sim_state2name(state, statenamebuf, sizeof(statenamebuf)),
+ sim_subtype2name(subtype, subtypenamebuf, sizeof(subtypenamebuf)));
+
+ /*
+ * look for the appropriate state, and process incoming message
+ */
+ switch (state) {
+ case EAPSIM_CLIENT_INIT:
+ switch (subtype) {
+ case EAPSIM_START:
+ newstate = rc_process_eap_start(eap_context, req, resp);
+ break;
+
+ case EAPSIM_CHALLENGE:
+ case EAPSIM_NOTIFICATION:
+ case EAPSIM_REAUTH:
+ default:
+ ERROR("sim in state %s message %s is illegal. Reply dropped.\n",
+ sim_state2name(state, statenamebuf, sizeof(statenamebuf)),
+ sim_subtype2name(subtype, subtypenamebuf, sizeof(subtypenamebuf)));
+ /* invalid state, drop message */
+ return 0;
+ }
+ break;
+
+ case EAPSIM_CLIENT_START:
+ switch (subtype) {
+ case EAPSIM_START:
+ /* NOT SURE ABOUT THIS ONE, retransmit, I guess */
+ newstate = rc_process_eap_start(eap_context, req, resp);
+ break;
+
+ case EAPSIM_CHALLENGE:
+ newstate = rc_process_eap_challenge(eap_context, req, resp);
+ break;
+
+ default:
+ ERROR("sim in state %s message %s is illegal. Reply dropped.\n",
+ sim_state2name(state, statenamebuf, sizeof(statenamebuf)),
+ sim_subtype2name(subtype, subtypenamebuf, sizeof(subtypenamebuf)));
+ /* invalid state, drop message */
+ return 0;
+ }
+ break;
+
+
+ default:
+ ERROR("sim in illegal state %s\n",
+ sim_state2name(state, statenamebuf, sizeof(statenamebuf)));
+ return 0;
+ }
+
+ /* copy the eap state object in */
+ fr_pair_replace(&(resp->vps), eapid);
+
+ /* update stete info, and send new packet */
+ rc_map_eapsim_types(resp);
+
+ /* copy the radius state object in */
+ fr_pair_replace(&(resp->vps), radstate);
+
+ vp = fr_pair_find_by_num(req->vps, PW_EAP_SIM_SUBTYPE, 0, TAG_ANY);
+ newsubtype = vp->vp_integer;
+
+
+ DEBUG2("MOVE from state %s subtype %s\n",
+ sim_state2name(state, statenamebuf, sizeof(statenamebuf)),
+ sim_subtype2name(subtype, subtypenamebuf, sizeof(subtypenamebuf)));
+
+ DEBUG2(" to state %s subtype %s\n",
+ sim_state2name(newstate, statenamebuf, sizeof(statenamebuf)),
+ sim_subtype2name(newsubtype, subtypenamebuf, sizeof(subtypenamebuf)));
+
+ statevp->vp_integer = newstate;
+ return 1;
+}
+
+static int rc_respond_eap_md5(rc_eap_context_t *eap_context,
+ RADIUS_PACKET *req, RADIUS_PACKET *rep)
+{
+ VALUE_PAIR *vp, *id, *state;
+ size_t valuesize;
+ uint8_t identifier;
+ uint8_t const *value;
+ FR_MD5_CTX context;
+ uint8_t response[16];
+
+ rc_cleanresp(rep);
+
+ if ((state = fr_pair_list_copy_by_num(NULL, req->vps, PW_STATE, 0, TAG_ANY)) == NULL)
+ {
+ ERROR("no state attribute found\n");
+ return 0;
+ }
+
+ if ((id = fr_pair_list_copy_by_num(NULL, req->vps, PW_EAP_ID, 0, TAG_ANY)) == NULL)
+ {
+ ERROR("no EAP-ID attribute found\n");
+ return 0;
+ }
+ identifier = id->vp_integer;
+
+ if ((vp = fr_pair_find_by_num(req->vps, PW_EAP_TYPE_BASE+PW_EAP_MD5, 0, TAG_ANY)) == NULL)
+ {
+ ERROR("no EAP-MD5 attribute found\n");
+ return 0;
+ }
+
+ /* got the details of the MD5 challenge */
+ valuesize = vp->vp_octets[0];
+ value = &vp->vp_octets[1];
+
+ /* sanitize items */
+ if (valuesize > vp->vp_length)
+ {
+ ERROR("md5 valuesize if too big (%u > %u)\n",
+ (unsigned int) valuesize, (unsigned int) vp->vp_length);
+ return 0;
+ }
+
+ /* now do the CHAP operation ourself, rather than build the
+ * buffer. We could also call rad_chap_encode, but it wants
+ * a CHAP-Challenge, which we don't want to bother with.
+ */
+ fr_md5_init(&context);
+ fr_md5_update(&context, &identifier, 1);
+ fr_md5_update(&context, (uint8_t *) eap_context->password, strlen(eap_context->password));
+ fr_md5_update(&context, value, valuesize);
+ fr_md5_final(response, &context);
+
+ {
+ uint8_t *p;
+ uint8_t lg_response;
+
+ vp = fr_pair_afrom_num(rep, PW_EAP_TYPE_BASE+PW_EAP_MD5, 0);
+ vp->vp_length = 17;
+
+ p = talloc_zero_array(vp, uint8_t, 17);
+ lg_response = 16;
+ memcpy(p, &lg_response, 1);
+ memcpy(p + 1, response, 16);
+ fr_pair_value_memsteal(vp, p);
+ }
+ fr_pair_replace(&(rep->vps), vp);
+
+ fr_pair_replace(&(rep->vps), id);
+
+ /* copy the state object in */
+ fr_pair_replace(&(rep->vps), state);
+
+ return 1;
+}
+
+
+/** Allocate a new socket, and add it to the packet list.
+ */
+static void rc_add_socket(fr_ipaddr_t *src_ipaddr, uint16_t src_port, fr_ipaddr_t *dst_ipaddr, uint16_t dst_port)
+{
+ int mysockfd;
+
+ /* Trace what we're doing. */
+ char src_addr[15+1] = "";
+ char dst_addr[15+1] = "";
+ inet_ntop(AF_INET, &(src_ipaddr->ipaddr.ip4addr.s_addr), src_addr, sizeof(src_addr));
+ inet_ntop(AF_INET, &(dst_ipaddr->ipaddr.ip4addr.s_addr), dst_addr, sizeof(dst_addr));
+
+ INFO("Adding new socket: src: %s:%d, dst: %s:%d", src_addr, src_port, dst_addr, dst_port);
+
+ mysockfd = fr_socket(src_ipaddr, src_port);
+ if (mysockfd < 0) {
+ ERROR("Failed to create new socket: %s\n", fr_strerror());
+ exit(1);
+ }
+
+ if (!fr_packet_list_socket_add(pl, mysockfd, ipproto,
+#ifdef WITH_RADIUSV11
+ false,
+#endif
+ dst_ipaddr, dst_port, NULL)) {
+ ERROR("Failed to add new socket: %s\n", fr_strerror());
+ exit(1);
+ }
+
+ num_sockets ++;
+ DEBUG("Added new socket: %d (num sockets: %d)\n", mysockfd, num_sockets);
+}
+
+/** Send one packet for a transaction.
+ */
+static int rc_send_one_packet(rc_transaction_t *trans, RADIUS_PACKET **packet_p)
+{
+ if (!trans || !packet_p || !*packet_p) return -1;
+
+ assert(pl != NULL);
+
+ RADIUS_PACKET *packet = *packet_p;
+
+ if (packet->id == -1) {
+ /* Haven't sent the packet yet. Initialize it. */
+ bool rcode;
+ int i;
+
+ rc_build_eap_context(trans); /* In case of EAP, build EAP-Message and initialize EAP context. */
+
+ assert(trans->reply == NULL);
+
+ trans->tries = 0;
+ packet->src_ipaddr.af = server_ipaddr.af;
+ int nb_sock_add = 0;
+ while (1) {
+ /* Allocate a RADIUS packet ID from a suitable socket of the packet list. */
+ rcode = fr_packet_list_id_alloc(pl, ipproto, packet_p, NULL);
+
+ if (rcode) { /* Got an ID. */
+ break;
+ }
+ if (nb_sock_add >= 1) {
+ ERROR("Added %d new socket(s), but still could not get an ID (currently: %d outgoing requests).\n",
+ nb_sock_add, fr_packet_list_num_outgoing(pl));
+ exit(1);
+ }
+
+ /* Could not find a free packet ID. Allocate a new socket, then try again. */
+ rc_add_socket(&packet->src_ipaddr, packet->src_port, &packet->dst_ipaddr, packet->dst_port);
+
+ nb_sock_add ++;
+ }
+
+ assert(packet->id != -1);
+ assert(packet->data == NULL);
+
+ for (i = 0; i < 4; i++) {
+ ((uint32_t *) packet->vector)[i] = fr_rand();
+ }
+ }
+
+ /*
+ * Send the packet.
+ */
+ DEBUG2("Transaction: %u, sending packet: %u (id: %u)...\n", trans->id, trans->num_packet, packet->id);
+
+ gettimeofday(&packet->timestamp, NULL); /* set outgoing packet timestamp. */
+
+ if (rad_send(packet, NULL, secret) < 0) {
+ ERROR("Failed to send packet (sockfd: %d, id: %d): %s\n",
+ packet->sockfd, packet->id, fr_strerror());
+ }
+
+ trans->num_packet ++;
+ trans->tries ++;
+
+ if (fr_debug_lvl > 0) fr_packet_header_print(fr_log_fp, packet, false);
+ if (fr_debug_lvl > 0) vp_printlist(fr_log_fp, packet->vps);
+
+ return 1;
+}
+
+/** Send current packet of a transaction. Arm timeout event.
+ */
+static int rc_send_transaction_packet(rc_transaction_t *trans, RADIUS_PACKET **packet_p)
+// note: we need a 'RADIUS_PACKET **' for fr_packet_list_id_alloc.
+{
+ if (!trans || !packet_p || !*packet_p) return -1;
+
+ int ret = rc_send_one_packet(trans, packet_p);
+ if (ret == 1) {
+ /* Send successful: arm the timeout callback. */
+ rc_evprep_packet_timeout(trans);
+ }
+ return ret;
+}
+
+/** Deallocate RADIUS packet ID.
+ */
+static void rc_deallocate_id(rc_transaction_t *trans)
+{
+ if (!trans || !trans->packet ||
+ (trans->packet->id < 0)) {
+ return;
+ }
+
+ RADIUS_PACKET *packet = trans->packet;
+
+ DEBUG2("Deallocating (sockfd: %d, id: %d)\n", packet->sockfd, packet->id);
+
+ /*
+ * One more unused RADIUS ID.
+ */
+ fr_packet_list_id_free(pl, packet, true);
+ /* note: "true" means automatically yank, so we must *not* yank ourselves before calling (otherwise, it does nothing)
+ * so, *don't*: fr_packet_list_yank(pl, request->packet); */
+
+ /* free more stuff to ensure next allocate won't be stuck on a "full" socket. */
+ packet->id = -1;
+ packet->sockfd = -1;
+ packet->src_ipaddr.af = AF_UNSPEC;
+ packet->src_port = 0;
+
+ /*
+ * If we've already sent a packet, free up the old one,
+ * and ensure that the next packet has a unique
+ * authentication vector.
+ */
+ if (packet->data) {
+ talloc_free(packet->data);
+ packet->data = NULL;
+ }
+
+ if (trans->reply) rad_free(&trans->reply);
+}
+
+/** Receive one packet, maybe.
+ */
+static int rc_recv_one_packet(struct timeval *tv_wait_time)
+{
+ fd_set set;
+ struct timeval tv;
+ rc_transaction_t *trans;
+ RADIUS_PACKET *reply, **packet_p;
+ volatile int max_fd;
+ bool ongoing_trans = false;
+ char buffer[128];
+
+ /* Wait for reply, timing out as necessary */
+ FD_ZERO(&set);
+
+ max_fd = fr_packet_list_fd_set(pl, &set);
+ if (max_fd < 0) {
+ /* no sockets to listen on! */
+ return 0;
+ }
+
+ if (NULL == tv_wait_time) {
+ timerclear(&tv);
+ } else {
+ tv.tv_sec = tv_wait_time->tv_sec;
+ tv.tv_usec = tv_wait_time->tv_usec;
+ }
+
+ if (select(max_fd, &set, NULL, NULL, &tv) <= 0) {
+ /* No packet was received. */
+ return 0;
+ }
+
+ /*
+ * Receive the reply.
+ */
+ reply = fr_packet_list_recv(pl, &set);
+ if (!reply) {
+ ERROR("Received bad packet: %s\n", fr_strerror());
+ return -1; /* bad packet */
+ }
+
+ /*
+ * Look for the packet which matches the reply.
+ */
+ reply->src_ipaddr = server_ipaddr;
+ reply->src_port = server_port;
+
+ /*
+ * Note: this only works if all packets have the same destination (IP, port).
+ * We should handle a list of destinations. But we don't. radclient doesn't do it either).
+ */
+
+ packet_p = fr_packet_list_find_byreply(pl, reply);
+
+ if (!packet_p) {
+ /* got reply to packet we didn't send.
+ * (or maybe we sent it, got no response, freed the ID. Then server responds to first request.)
+ */
+ DEBUG("No outstanding request was found for reply from %s, port %d (sockfd: %d, id: %d)\n",
+ inet_ntop(reply->src_ipaddr.af, &reply->src_ipaddr.ipaddr, buffer, sizeof(buffer)),
+ reply->src_port, reply->sockfd, reply->id);
+ rad_free(&reply);
+ return -1;
+ }
+
+ trans = fr_packet2myptr(rc_transaction_t, packet, packet_p);
+
+ if (trans->event) fr_event_delete(ev_list, &trans->event);
+
+ /*
+ * Fails the signature validation: not a valid reply.
+ */
+ if (rad_verify(reply, trans->packet, secret) < 0) {
+ /* shared secret is incorrect.
+ * (or maybe this is a response to another packet we sent, for which we got no response,
+ * freed the ID, then reused it. Then server responds to first packet.)
+ */
+ DEBUG("Conflicting response authenticator for reply from %s (sockfd: %d, id: %d)\n",
+ inet_ntop(reply->src_ipaddr.af, &reply->src_ipaddr.ipaddr, buffer, sizeof(buffer)),
+ reply->sockfd, reply->id);
+
+ goto packet_done;
+ }
+
+ /* Set reply destination = packet source. */
+ reply->dst_ipaddr = trans->packet->src_ipaddr;
+ reply->dst_port = trans->packet->src_port;
+
+ trans->reply = reply;
+ reply = NULL;
+
+ if (rad_decode(trans->reply, trans->packet, secret) != 0) {
+ /* This can fail if packet contains too many attributes. */
+ DEBUG("Failed decoding reply\n");
+ goto packet_done;
+ }
+
+ gettimeofday(&trans->reply->timestamp, NULL); /* set received packet timestamp. */
+
+ if (trans->eap_context) {
+ /* Call unmap before packet print (so we can see the decoded EAP stuff). */
+ rc_unmap_eap_methods(trans->reply);
+ }
+
+ DEBUG2("Transaction: %u, received packet (id: %u).\n", trans->id, trans->reply->id);
+
+ if (fr_debug_lvl > 0) fr_packet_header_print(fr_log_fp, trans->reply, true);
+ if (fr_debug_lvl > 0) vp_printlist(fr_log_fp, trans->reply->vps);
+
+ if (!trans->eap_context) {
+ goto packet_done;
+ }
+
+ /* now look for the code type. */
+ VALUE_PAIR *vp, *vpnext;
+ for (vp = trans->reply->vps; vp != NULL; vp = vpnext) {
+ vpnext = vp->next;
+
+ switch (vp->da->attr) {
+ default:
+ break;
+
+ case PW_EAP_TYPE_BASE + PW_EAP_MD5:
+ if (rc_respond_eap_md5(trans->eap_context, trans->reply, trans->packet) && trans->eap_context->eap.md5.tried < 3)
+ {
+ /* answer the challenge from server. */
+ trans->eap_context->eap.md5.tried ++;
+ rc_deallocate_id(trans);
+ rc_send_transaction_packet(trans, &trans->packet);
+ ongoing_trans = true; // don't free the transaction yet.
+ }
+ goto packet_done;
+
+ case PW_EAP_TYPE_BASE + PW_EAP_SIM:
+ if (rc_respond_eap_sim(trans->eap_context, trans->reply, trans->packet)) {
+ /* answer the challenge from server. */
+ rc_deallocate_id(trans);
+ rc_send_transaction_packet(trans, &trans->packet);
+ ongoing_trans = true; // don't free the transaction yet.
+ }
+ goto packet_done;
+ }
+ }
+
+ /* EAP transaction ends here (no more requests from EAP server). */
+
+ /*
+ * success: if we have EAP-Code = Success, and reply is an Access-Accept.
+ */
+ if (trans->reply->code != PW_CODE_ACCESS_ACCEPT) {
+ DEBUG("EAP transaction finished, but reply is not an Access-Accept");
+ goto packet_done;
+ }
+ vp = fr_pair_find_by_num(trans->reply->vps, PW_EAP_CODE, 0, TAG_ANY);
+ if ( (!vp) || (vp->vp_integer != 3) ) {
+ DEBUG("EAP transaction finished, but reply does not contain EAP-Code = Success");
+ goto packet_done;
+ }
+
+ goto packet_done;
+
+packet_done:
+
+ /* Basic statistics (salvaged from old code). TODO: something better. */
+ if (trans->reply) {
+ if (trans->reply->code == PW_CODE_ACCESS_ACCEPT) {
+ totalapp ++;
+ } else if (trans->reply->code == PW_CODE_ACCESS_REJECT) {
+ totaldeny ++;
+ }
+ }
+
+ rad_free(&trans->reply);
+ rad_free(&reply); /* may be NULL */
+
+ if (!ongoing_trans) {
+ rc_deallocate_id(trans);
+ rc_finish_transaction(trans);
+ }
+
+ return 1;
+}
+
+/** Event callback: packet timeout.
+ */
+static void rc_evcb_packet_timeout(void *ctx)
+{
+ rc_transaction_t *trans = ctx;
+ if (!trans || !trans->packet) return;
+
+ DEBUG("Timeout for transaction: %d, tries (so far): %d (max: %d)", trans->id, trans->tries, retries);
+
+ if (trans->event) fr_event_delete(ev_list, &trans->event);
+
+ if (trans->tries < retries) {
+ /* Try again. */
+ rc_send_transaction_packet(trans, &trans->packet);
+ } else {
+ DEBUG("No response for transaction: %d, giving up", trans->id);
+ rc_finish_transaction(trans);
+ }
+}
+
+/** Prepare event: packet timeout.
+ */
+static void rc_evprep_packet_timeout(rc_transaction_t *trans)
+{
+ struct timeval tv_event;
+ gettimeofday(&tv_event, NULL);
+ timeradd(&tv_event, &tv_timeout, &tv_event);
+
+ if (!fr_event_insert(ev_list, rc_evcb_packet_timeout, (void *)trans, &tv_event, &trans->event)) {
+ ERROR("Failed to insert event\n");
+ exit(1);
+ }
+}
+
+/** Trigger all armed events for which time is reached.
+ */
+static int rc_loop_events(void)
+{
+ struct timeval when;
+ uint32_t nb_processed = 0;
+
+ if (!fr_event_list_num_elements(ev_list)) return 0;
+
+ while (1) {
+ gettimeofday(&when, NULL);
+ if (!fr_event_run(ev_list, &when)) {
+ /* no more. */
+ break;
+ }
+ nb_processed ++;
+ }
+ return nb_processed;
+}
+
+/** Receive loop.
+ * Handle incoming packets, until nothing more is received.
+ */
+static int dhb_loop_recv(void)
+{
+ uint32_t nb_received = 0;
+ while (rc_recv_one_packet(NULL) > 0) {
+ nb_received ++;
+ }
+ return nb_received;
+}
+
+/** Loop starting new transactions, until a limit is reached
+ * (max parallelism, or no more input available.)
+ */
+static int rc_loop_start_transactions(void)
+{
+ int nb_started = 0;
+
+ while (1) {
+ if (num_ongoing >= parallel) break;
+
+ /* Try to initialize a new transaction. */
+ rc_transaction_t *trans = rc_init_transaction(autofree);
+ if (!trans) break;
+
+ nb_started ++;
+ rc_send_transaction_packet(trans, &trans->packet);
+ }
+ return nb_started;
+}
+
+/** Main loop: Handle events. Receive and process responses. Start new transactions.
+ * Until we're done.
+ */
+static void rc_main_loop(void)
+{
+ while (1) {
+ /* Handle events. */
+ rc_loop_events();
+
+ /* Receive and process response until no more are received (don't wait). */
+ dhb_loop_recv();
+
+ /* Start new transactions and send the associated packet. */
+ rc_loop_start_transactions();
+
+ /* Check if we're done. */
+ if ( (rc_vps_list_in.size == 0)
+ && (fr_packet_list_num_outgoing(pl) == 0) ) {
+ break;
+ }
+ }
+ INFO("Main loop: done.");
+}
+
+
+void set_radius_dir(TALLOC_CTX *ctx, char const *path)
+{
+ if (radius_dir) {
+ char *p;
+
+ memcpy(&p, &radius_dir, sizeof(p));
+ talloc_free(p);
+ radius_dir = NULL;
+ }
+ if (path) radius_dir = talloc_strdup(ctx, path);
+}
+
+
+/** Set a port from the request type if we don't already have one.
+ */
+static void rc_get_port(PW_CODE type, uint16_t *port)
+{
+ switch (type) {
+ default:
+ case PW_CODE_ACCESS_REQUEST:
+ case PW_CODE_ACCESS_CHALLENGE:
+ case PW_CODE_STATUS_SERVER:
+ if (*port == 0) *port = getport("radius");
+ if (*port == 0) *port = PW_AUTH_UDP_PORT;
+ return;
+
+ case PW_CODE_ACCOUNTING_REQUEST:
+ if (*port == 0) *port = getport("radacct");
+ if (*port == 0) *port = PW_ACCT_UDP_PORT;
+ return;
+
+ case PW_CODE_DISCONNECT_REQUEST:
+ if (*port == 0) *port = PW_POD_UDP_PORT;
+ return;
+
+ case PW_CODE_COA_REQUEST:
+ if (*port == 0) *port = PW_COA_UDP_PORT;
+ return;
+
+ case PW_CODE_UNDEFINED:
+ if (*port == 0) *port = 0;
+ return;
+ }
+}
+
+/** Resolve a port to a request type.
+ */
+static PW_CODE rc_get_code(uint16_t port)
+{
+ /*
+ * getport returns 0 if the service doesn't exist
+ * so we need to return early, to avoid incorrect
+ * codes.
+ */
+ if (port == 0) return PW_CODE_UNDEFINED;
+
+ if ((port == getport("radius")) || (port == PW_AUTH_UDP_PORT) || (port == PW_AUTH_UDP_PORT_ALT)) {
+ return PW_CODE_ACCESS_REQUEST;
+ }
+ if ((port == getport("radacct")) || (port == PW_ACCT_UDP_PORT) || (port == PW_ACCT_UDP_PORT_ALT)) {
+ return PW_CODE_ACCOUNTING_REQUEST;
+ }
+ if (port == PW_COA_UDP_PORT) return PW_CODE_COA_REQUEST;
+ if (port == PW_POD_UDP_PORT) return PW_CODE_DISCONNECT_REQUEST;
+
+ return PW_CODE_UNDEFINED;
+}
+
+/** Resolve server hostname.
+ */
+static void rc_resolve_hostname(char *server_arg)
+{
+ if (force_af == AF_UNSPEC) force_af = AF_INET;
+ server_ipaddr.af = force_af;
+ if (strcmp(server_arg, "-") != 0) {
+ char *p;
+ char const *hostname = server_arg;
+ char const *portname = server_arg;
+ char buffer[256];
+
+ if (*server_arg == '[') { /* IPv6 URL encoded */
+ p = strchr(server_arg, ']');
+ if ((size_t) (p - server_arg) >= sizeof(buffer)) {
+ usage();
+ }
+
+ memcpy(buffer, server_arg + 1, p - server_arg - 1);
+ buffer[p - server_arg - 1] = '\0';
+
+ hostname = buffer;
+ portname = p + 1;
+
+ }
+ p = strchr(portname, ':');
+ if (p && (strchr(p + 1, ':') == NULL)) {
+ *p = '\0';
+ portname = p + 1;
+ } else {
+ portname = NULL;
+ }
+
+ if (ip_hton(&server_ipaddr, force_af, hostname, false) < 0) {
+ ERROR("%s: Failed to find IP address for host %s: %s\n", progname, hostname, strerror(errno));
+ exit(1);
+ }
+ server_addr_init = true;
+
+ /* Strip port from hostname if needed. */
+ if (portname) server_port = atoi(portname);
+
+ /*
+ * Work backwards from the port to determine the packet type
+ */
+ if (packet_code == PW_CODE_UNDEFINED) packet_code = rc_get_code(server_port);
+ }
+ rc_get_port(packet_code, &server_port);
+}
+
+int main(int argc, char **argv)
+{
+ char *p;
+ int c;
+ char *filename = NULL;
+ FILE *fp;
+
+ static fr_log_t radclient_log = {
+ .colourise = true,
+ .fd = STDOUT_FILENO,
+ .dst = L_DST_STDOUT,
+ .file = NULL,
+ .debug_file = NULL,
+ };
+
+ radlog_init(&radclient_log, false);
+
+ /*
+ * We probably don't want to free the talloc autofree context
+ * directly, so we'll allocate a new context beneath it, and
+ * free that before any leak reports.
+ */
+ autofree = talloc_init("main");
+
+ fr_debug_lvl = 0;
+ fr_log_fp = stdout;
+
+ set_radius_dir(autofree, RADIUS_DIR);
+
+ while ((c = getopt(argc, argv, "46c:d:D:f:hp:qst:r:S:xXv")) != EOF)
+ {
+ switch (c) {
+ case '4':
+ force_af = AF_INET;
+ break;
+ case '6':
+ force_af = AF_INET6;
+ break;
+ case 'd':
+ set_radius_dir(autofree, optarg);
+ break;
+ case 'D':
+ main_config.dictionary_dir = talloc_typed_strdup(NULL, optarg);
+ break;
+ case 'f':
+ filename = optarg;
+ break;
+ case 'p':
+ parallel = atoi(optarg);
+ if (parallel == 0) parallel = 1;
+ if (parallel > 65536) parallel = 65536;
+ break;
+ case 'q':
+ do_output = 0;
+ break;
+ case 'x':
+ rad_debug_lvl++;
+ fr_debug_lvl++;
+ break;
+
+ case 'X':
+#if 0
+ sha1_data_problems = 1; /* for debugging only */
+#endif
+ break;
+
+ case 'r':
+ if (!isdigit((uint8_t) *optarg))
+ usage();
+ retries = atoi(optarg);
+ break;
+ case 's':
+ do_summary = 1;
+ break;
+ case 't':
+ if (!isdigit((uint8_t) *optarg))
+ usage();
+ timeout = atof(optarg);
+ break;
+ case 'v':
+ printf("$Id$"
+#ifndef ENABLE_REPRODUCIBLE_BUILDS
+ ", built on " __DATE__ " at " __TIME__
+#endif
+ "\n"
+ );
+ exit(0);
+
+ case 'S':
+ fp = fopen(optarg, "r");
+ if (!fp) {
+ ERROR("Error opening %s: %s\n",
+ optarg, fr_syserror(errno));
+ exit(1);
+ }
+ if (fgets(filesecret, sizeof(filesecret), fp) == NULL) {
+ ERROR("Error reading %s: %s\n",
+ optarg, fr_syserror(errno));
+ exit(1);
+ }
+ fclose(fp);
+
+ /* truncate newline */
+ p = filesecret + strlen(filesecret) - 1;
+ while ((p >= filesecret) &&
+ (*p < ' ')) {
+ *p = '\0';
+ --p;
+ }
+
+ if (strlen(filesecret) < 2) {
+ ERROR("Secret in %s is too short\n", optarg);
+ exit(1);
+ }
+ secret = filesecret;
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= (optind - 1);
+ argv += (optind - 1);
+
+ if ((argc < 3) ||
+ ((!secret) && (argc < 4))) {
+ usage();
+ }
+
+ /* Prepare the timeout. */
+ rc_float_to_timeval(&tv_timeout, timeout);
+
+ if (!main_config.dictionary_dir) {
+ main_config.dictionary_dir = DICTDIR;
+ }
+
+ /*
+ * Read the distribution dictionaries first, then
+ * the ones in raddb.
+ */
+ DEBUG2("including dictionary file %s/%s", main_config.dictionary_dir, RADIUS_DICTIONARY);
+ if (dict_init(main_config.dictionary_dir, RADIUS_DICTIONARY) != 0) {
+ ERROR("Errors reading dictionary: %s\n", fr_strerror());
+ exit(1);
+ }
+
+ /*
+ * It's OK if this one doesn't exist.
+ */
+ int rcode = dict_read(radius_dir, RADIUS_DICTIONARY);
+ if (rcode == -1) {
+ ERROR("Errors reading %s/%s: %s\n", radius_dir, RADIUS_DICTIONARY, fr_strerror());
+ exit(1);
+ }
+
+ /*
+ * We print this after reading it. That way if
+ * it doesn't exist, it's OK, and we don't print
+ * anything.
+ */
+ if (rcode == 0) {
+ DEBUG2("Including dictionary file %s/%s", radius_dir, RADIUS_DICTIONARY);
+ }
+
+ /*
+ * Get the request type
+ */
+ if (!isdigit((uint8_t) argv[2][0])) {
+ packet_code = fr_str2int(rc_request_types, argv[2], -2);
+ if (packet_code == -2) {
+ ERROR("Unrecognised request type \"%s\"\n", argv[2]);
+ usage();
+ }
+ } else {
+ packet_code = atoi(argv[2]);
+ }
+
+ /*
+ * Resolve hostname.
+ */
+ rc_resolve_hostname(argv[1]);
+
+ /*
+ * Add the secret.
+ */
+ if (argv[3]) secret = argv[3];
+
+ /*
+ * Read input data vp(s) from the file (or stdin).
+ */
+ INFO("Loading input data...");
+ if (!rc_load_input(autofree, filename, &rc_vps_list_in, 0)
+ || rc_vps_list_in.size == 0) {
+ ERROR("No valid input. Nothing to send.\n");
+ exit(EXIT_FAILURE);
+ }
+ INFO("Loaded: %d input element(s).", rc_vps_list_in.size);
+
+ /* Initialize the packets list. */
+ MEM(pl = fr_packet_list_create(1));
+
+ /* Initialize the events list. */
+ ev_list = fr_event_list_create(autofree, NULL);
+ if (!ev_list) {
+ ERROR("Failed to create event list\n");
+ exit(1);
+ }
+
+ /*
+ * Start main loop.
+ */
+ rc_main_loop();
+
+ if (do_summary) {
+ INFO("\n\t Total approved auths: %d", totalapp);
+ INFO("\t Total denied auths: %d", totaldeny);
+ }
+
+ talloc_free(autofree);
+
+ return 0;
+}
+
+/** Given a radius request with some attributes in the EAP range, build
+ * them all into a single EAP-Message body.
+ *
+ * If there are multiple eligibles EAP-Type, the first one is picked.
+ * Function returns 0 if no EAP is involved, or the EAP-Type otherwise.
+ */
+static int rc_map_eap_methods(RADIUS_PACKET *req)
+{
+ VALUE_PAIR *vp, *vpnext;
+ int id, eapcode;
+ int eap_method = 0;
+
+ eap_packet_t *pt_ep = talloc_zero(req, eap_packet_t);
+
+ vp = fr_pair_find_by_num(req->vps, PW_EAP_ID, 0, TAG_ANY);
+ if (!vp) {
+ id = ((int)getpid() & 0xff);
+ } else {
+ id = vp->vp_integer;
+ }
+
+ vp = fr_pair_find_by_num(req->vps, PW_EAP_CODE, 0, TAG_ANY);
+ if (!vp) {
+ eapcode = PW_EAP_REQUEST;
+ } else {
+ eapcode = vp->vp_integer;
+ }
+
+ for (vp = req->vps; vp != NULL; vp = vpnext) {
+ /* save it in case it changes! */
+ vpnext = vp->next;
+
+ if (vp->da->attr >= PW_EAP_TYPE_BASE &&
+ vp->da->attr < PW_EAP_TYPE_BASE+256) {
+ break;
+ }
+ }
+
+ if (!vp) {
+ return 0;
+ }
+
+ eap_method = vp->da->attr - PW_EAP_TYPE_BASE;
+
+ switch (eap_method) {
+ case PW_EAP_IDENTITY:
+ case PW_EAP_NOTIFICATION:
+ case PW_EAP_NAK:
+ case PW_EAP_MD5:
+ case PW_EAP_OTP:
+ case PW_EAP_GTC:
+ case PW_EAP_TLS:
+ case PW_EAP_LEAP:
+ case PW_EAP_TTLS:
+ case PW_EAP_PEAP:
+ default:
+ /*
+ * no known special handling, it is just encoded as an
+ * EAP-message with the given type.
+ */
+
+ /* nuke any existing EAP-Messages */
+ fr_pair_delete_by_num(&req->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
+
+ pt_ep->code = eapcode;
+ pt_ep->id = id;
+ pt_ep->type.num = eap_method;
+ pt_ep->type.length = vp->vp_length;
+
+ pt_ep->type.data = talloc_memdup(vp, vp->vp_octets, vp->vp_length);
+ talloc_set_type(pt_ep->type.data, uint8_t);
+
+ eap_basic_compose(req, pt_ep);
+ }
+
+ return eap_method;
+}
+
+/*
+ * given a radius request with an EAP-Message body, decode it specific
+ * attributes.
+ */
+static void rc_unmap_eap_methods(RADIUS_PACKET *rep)
+{
+ VALUE_PAIR *eap1;
+ eap_packet_raw_t *e;
+ int len;
+ int type;
+
+ if (!rep) return;
+
+ /* find eap message */
+ e = eap_vp2packet(NULL, rep->vps);
+ if (!e) {
+ ERROR("failed decoding EAP: %s\n", fr_strerror());
+ return;
+ }
+ /* create EAP-ID and EAP-CODE attributes to start */
+ eap1 = fr_pair_afrom_num(rep, PW_EAP_ID, 0);
+ eap1->vp_integer = e->id;
+ fr_pair_add(&(rep->vps), eap1);
+
+ eap1 = fr_pair_afrom_num(rep, PW_EAP_CODE, 0);
+ eap1->vp_integer = e->code;
+ fr_pair_add(&(rep->vps), eap1);
+
+ switch (e->code) {
+ default:
+ case PW_EAP_SUCCESS:
+ case PW_EAP_FAILURE:
+ /* no data */
+ break;
+
+ case PW_EAP_REQUEST:
+ case PW_EAP_RESPONSE:
+ /* there is a type field, which we use to create
+ * a new attribute */
+
+ /* the length was decode already into the attribute
+ * length, and was checked already. Network byte
+ * order, just pull it out using math.
+ */
+ len = e->length[0]*256 + e->length[1];
+
+ /* verify the length is big enough to hold type */
+ if (len < 5)
+ {
+ talloc_free(e);
+ return;
+ }
+
+ type = e->data[0];
+
+ type += PW_EAP_TYPE_BASE;
+ len -= 5;
+
+ if (len > MAX_STRING_LEN) {
+ len = MAX_STRING_LEN;
+ }
+
+ eap1 = fr_pair_afrom_num(rep, type, 0);
+ fr_pair_value_memcpy(eap1, e->data + 1, len);
+
+ fr_pair_add(&(rep->vps), eap1);
+ break;
+ }
+
+ talloc_free(e);
+ return;
+}
+
+static int rc_map_eapsim_types(RADIUS_PACKET *r)
+{
+ int ret;
+
+ eap_packet_t *pt_ep = talloc_zero(r, eap_packet_t);
+
+ ret = map_eapsim_basictypes(r, pt_ep);
+
+ if (ret != 1) {
+ return ret;
+ }
+
+ eap_basic_compose(r, pt_ep);
+
+ return 1;
+}
+
+static int rc_unmap_eapsim_types(RADIUS_PACKET *r)
+{
+ VALUE_PAIR *esvp;
+ uint8_t *eap_data;
+ int rcode_unmap;
+
+ esvp = fr_pair_find_by_num(r->vps, PW_EAP_TYPE_BASE+PW_EAP_SIM, 0, TAG_ANY);
+ if (!esvp) {
+ ERROR("eap: EAP-Sim attribute not found\n");
+ return 0;
+ }
+
+ eap_data = talloc_memdup(esvp, esvp->vp_octets, esvp->vp_length);
+ talloc_set_type(eap_data, uint8_t);
+
+ rcode_unmap = unmap_eapsim_basictypes(r, eap_data, esvp->vp_length);
+
+ talloc_free(eap_data);
+ return rcode_unmap;
+}
+