/* * 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 * Copyright 2000 Alan DeKok */ RCSID("$Id$") #include #include #include #if HAVE_GETOPT_H # include #endif #include #include #include #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 #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] []\n"); fprintf(stdout, " 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 Set user dictionary directory (defaults to " RADDBDIR ").\n"); fprintf(stdout, " -D Set main dictionary directory (defaults to " DICTDIR ").\n"); fprintf(stdout, " -f Read packets from file, not stdin.\n"); fprintf(stdout, " -h Print usage help information.\n"); fprintf(stdout, " -p Send 'num' packets in parallel.\n"); fprintf(stdout, " -q Do not print anything out.\n"); fprintf(stdout, " -r If timeout, retry sending the packet 'retries' times.\n"); fprintf(stdout, " -s Print out summary information of auth results.\n"); fprintf(stdout, " -S read secret from file, not command line.\n"); fprintf(stdout, " -t 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]; } /* * , 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; }