/* * Authenticate a previously sent challenge. */ static int mod_process(UNUSED void *arg, eap_handler_t *handler) { MD5_PACKET *packet; MD5_PACKET *reply; VALUE_PAIR *password; REQUEST *request = handler->request; /* * Get the Cleartext-Password for this user. */ rad_assert(handler->request != NULL); rad_assert(handler->stage == PROCESS); password = fr_pair_find_by_num(handler->request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); if (!password) { REDEBUG2("Cleartext-Password is required for EAP-MD5 authentication"); return 0; } /* * Extract the EAP-MD5 packet. */ if (!(packet = eapmd5_extract(handler->eap_ds))) return 0; /* * Create a reply, and initialize it. */ reply = talloc(packet, MD5_PACKET); if (!reply) { talloc_free(packet); return 0; } reply->id = handler->eap_ds->request->id; reply->length = 0; /* * Verify the received packet against the previous packet * (i.e. challenge) which we sent out. */ if (eapmd5_verify(packet, password, handler->opaque)) { reply->code = PW_MD5_SUCCESS; } else { reply->code = PW_MD5_FAILURE; } /* * Compose the EAP-MD5 packet out of the data structure, * and free it. */ eapmd5_compose(handler->eap_ds, reply); talloc_free(packet); return 1; }
/* * send an initial eap-leap request * ie access challenge to the user/peer. * Frame eap reply packet. * len = header + type + leap_methoddata * leap_methoddata = value_size + value */ static int CC_HINT(nonnull) mod_session_init(UNUSED void *instance, eap_handler_t *handler) { REQUEST *request = handler->request; leap_session_t *session; leap_packet_t *reply; RDEBUG2("Stage 2"); /* * LEAP requires a User-Name attribute */ if (!handler->request->username) { REDEBUG("User-Name is required for EAP-LEAP authentication"); return 0; } reply = eapleap_initiate(request, handler->eap_ds, handler->request->username); if (!reply) { return 0; } eapleap_compose(request, handler->eap_ds, reply); handler->opaque = session = talloc(handler, leap_session_t); if (!handler->opaque) { talloc_free(reply); return 0; } /* * Remember which stage we're in, and which challenge * we sent to the AP. The later stages will take care * of filling in the peer response. */ handler->free_opaque = NULL; session->stage = 4; /* the next stage we're in */ memcpy(session->peer_challenge, reply->challenge, reply->count); REDEBUG2("Successfully initiated"); /* * The next stage to process the packet. */ handler->stage = PROCESS; talloc_free(reply); return 1; }
static int ub_common_wait(rlm_unbound_t const *inst, REQUEST *request, char const *name, struct ub_result **ub, int async_id) { useconds_t iv, waited; iv = inst->timeout > 64 ? 64000 : inst->timeout * 1000; ub_process(inst->ub); for (waited = 0; (void const *)*ub == (void const *)inst; waited += iv, iv *= 2) { if (waited + iv > (useconds_t)inst->timeout * 1000) { usleep(inst->timeout * 1000 - waited); ub_process(inst->ub); break; } usleep(iv); /* Check if already handled by event loop */ if ((void const *)*ub != (void const *)inst) { break; } /* In case we are running single threaded */ ub_process(inst->ub); } if ((void const *)*ub == (void const *)inst) { int res; REDEBUG2("%s - DNS took too long", name); res = ub_cancel(inst->ub, async_id); if (res) { REDEBUG("%s - ub_cancel: %s", name, ub_strerror(res)); } return -1; } return 0; }
/** Authenticate a previously sent challenge * */ static int mod_authenticate(UNUSED void *arg, eap_handler_t *handler) { REQUEST *request = handler->request; eap_sim_state_t *ess = handler->opaque; VALUE_PAIR *vp, *vps; enum eapsim_subtype subtype; int success; /* * VPS is the data from the client */ vps = handler->request->packet->vps; success = unmap_eapsim_basictypes(handler->request->packet, handler->eap_ds->response->type.data, handler->eap_ds->response->type.length); if (!success) return 0; /* * See what kind of message we have gotten */ vp = pairfind(vps, PW_EAP_SIM_SUBTYPE, 0, TAG_ANY); if (!vp) { REDEBUG2("No subtype attribute was created, message dropped"); return 0; } subtype = vp->vp_integer; /* * Client error supersedes anything else. */ if (subtype == EAPSIM_CLIENT_ERROR) { return 0; } switch (ess->state) { case EAPSIM_SERVER_START: switch (subtype) { /* * Pretty much anything else here is illegal, so we will retransmit the request. */ default: eap_sim_stateenter(handler, ess, EAPSIM_SERVER_START); return 1; /* * A response to our EAP-Sim/Request/Start! */ case EAPSIM_START: return process_eap_sim_start(handler, vps); } break; case EAPSIM_SERVER_CHALLENGE: switch(subtype) { /* * Pretty much anything else here is illegal, so we will retransmit the request. */ default: eap_sim_stateenter(handler, ess, EAPSIM_SERVER_CHALLENGE); return 1; /* * A response to our EAP-Sim/Request/Challenge! */ case EAPSIM_CHALLENGE: return process_eap_sim_challenge(handler, vps); } break; default: rad_assert(0 == 1); } return 0; }
/* * Check password. * * Returns: 0 OK * -1 Password fail * -2 Rejected (Auth-Type = Reject, send Port-Message back) * 1 End check & return, don't reply * * NOTE: NOT the same as the RLM_ values ! */ static int CC_HINT(nonnull) rad_check_password(REQUEST *request) { vp_cursor_t cursor; VALUE_PAIR *auth_type_pair; int auth_type = -1; int result; int auth_type_count = 0; /* * Look for matching check items. We skip the whole lot * if the authentication type is PW_AUTHTYPE_ACCEPT or * PW_AUTHTYPE_REJECT. */ fr_cursor_init(&cursor, &request->config); while ((auth_type_pair = fr_cursor_next_by_num(&cursor, PW_AUTH_TYPE, 0, TAG_ANY))) { auth_type = auth_type_pair->vp_integer; auth_type_count++; RDEBUG2("Found Auth-Type = %s", dict_valnamebyattr(PW_AUTH_TYPE, 0, auth_type)); if (auth_type == PW_AUTHTYPE_REJECT) { RDEBUG2("Auth-Type = Reject, rejecting user"); return -2; } } /* * Warn if more than one Auth-Type was found, because only the last * one found will actually be used. */ if ((auth_type_count > 1) && (debug_flag)) { RERROR("Warning: Found %d auth-types on request for user '%s'", auth_type_count, request->username->vp_strvalue); } /* * This means we have a proxy reply or an accept and it wasn't * rejected in the above loop. So that means it is accepted and we * do no further authentication. */ if ((auth_type == PW_AUTHTYPE_ACCEPT) #ifdef WITH_PROXY || (request->proxy) #endif ) { RDEBUG2("Auth-Type = Accept, accepting the user"); return 0; } /* * Check that Auth-Type has been set, and reject if not. * * Do quick checks to see if Cleartext-Password or Crypt-Password have * been set, and complain if so. */ if (auth_type < 0) { if (pairfind(request->config, PW_CRYPT_PASSWORD, 0, TAG_ANY) != NULL) { RWDEBUG2("Please update your configuration, and remove 'Auth-Type = Crypt'"); RWDEBUG2("Use the PAP module instead"); } else if (pairfind(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY) != NULL) { RWDEBUG2("Please update your configuration, and remove 'Auth-Type = Local'"); RWDEBUG2("Use the PAP or CHAP modules instead"); } /* * The admin hasn't told us how to * authenticate the user, so we reject them! * * This is fail-safe. */ REDEBUG2("No Auth-Type found: rejecting the user via Post-Auth-Type = Reject"); return -2; } /* * See if there is a module that handles * this Auth-Type, and turn the RLM_ return * status into the values as defined at * the top of this function. */ result = process_authenticate(auth_type, request); switch (result) { /* * An authentication module FAIL * return code, or any return code that * is not expected from authentication, * is the same as an explicit REJECT! */ case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_REJECT: case RLM_MODULE_UPDATED: case RLM_MODULE_USERLOCK: default: result = -1; break; case RLM_MODULE_OK: result = 0; break; case RLM_MODULE_HANDLED: result = 1; break; } return result; }
/** Copy packet to multiple servers * * Create a duplicate of the packet and send it to a list of realms * defined by the presence of the Replicate-To-Realm VP in the control * list of the current request. * * This is pretty hacky and is 100% fire and forget. If you're looking * to forward authentication requests to multiple realms and process * the responses, this function will not allow you to do that. * * @param[in] instance of this module. * @param[in] request The current request. * @param[in] list of attributes to copy to the duplicate packet. * @param[in] code to write into the code field of the duplicate packet. * @return RCODE fail on error, invalid if list does not exist, noop if no replications succeeded, else ok. */ static rlm_rcode_t replicate_packet(UNUSED void *instance, REQUEST *request, pair_lists_t list, PW_CODE code) { int rcode = RLM_MODULE_NOOP; bool pass1 = true; vp_cursor_t cursor; VALUE_PAIR *vp; RADIUS_PACKET *packet = NULL; rcode = rlm_replicate_alloc(&packet, request, list, code); if (rcode != RLM_MODULE_OK) { return rcode; } /* * Send as many packets as necessary to different destinations. */ fr_cursor_init(&cursor, &request->config_items); while ((vp = fr_cursor_next_by_num(&cursor, PW_REPLICATE_TO_REALM, 0, TAG_ANY))) { home_server_t *home; REALM *realm; home_pool_t *pool; realm = realm_find2(vp->vp_strvalue); if (!realm) { REDEBUG2("Cannot Replicate to unknown realm \"%s\"", vp->vp_strvalue); continue; } /* * We shouldn't really do this on every loop. */ switch (request->packet->code) { default: REDEBUG2("Cannot replicate unknown packet code %d", request->packet->code); rcode = RLM_MODULE_FAIL; goto done; case PW_CODE_ACCESS_REQUEST: pool = realm->auth_pool; break; #ifdef WITH_ACCOUNTING case PW_CODE_ACCOUNTING_REQUEST: pool = realm->acct_pool; break; #endif #ifdef WITH_COA case PW_CODE_COA_REQUEST: case PW_CODE_DISCONNECT_REQUEST: pool = realm->acct_pool; break; #endif } if (!pool) { RWDEBUG2("Cancelling replication to Realm %s, as the realm is local", realm->name); continue; } home = home_server_ldb(realm->name, pool, request); if (!home) { REDEBUG2("Failed to find live home server for realm %s", realm->name); continue; } /* * For replication to multiple servers we re-use the packet * we built here. */ if (pass1) { packet->id = fr_rand() & 0xff; packet->sockfd = fr_socket(&home->src_ipaddr, 0); if (packet->sockfd < 0) { REDEBUG("Failed opening socket: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } pass1 = false; } else { size_t i; for (i = 0; i < sizeof(packet->vector); i++) { packet->vector[i] = fr_rand() & 0xff; } packet->id++; TALLOC_FREE(packet->data); packet->data_len = 0; } /* * (Re)-Write these. */ packet->dst_ipaddr = home->ipaddr; packet->dst_port = home->port; memset(&packet->src_ipaddr, 0, sizeof(packet->src_ipaddr)); packet->src_port = 0; /* * Encode, sign and then send the packet. */ RDEBUG("Replicating list '%s' to Realm '%s'", fr_int2str(pair_lists, list, "<INVALID>"), realm->name); if (rad_send(packet, NULL, home->secret) < 0) { REDEBUG("Failed replicating packet: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } /* * We've sent it to at least one destination. */ rcode = RLM_MODULE_OK; } done: talloc_free(packet); return rcode; }
/* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_sqlcounter_t *inst = instance; int rcode = RLM_MODULE_NOOP; uint64_t counter, res; DICT_ATTR const *da; VALUE_PAIR *key_vp, *limit; VALUE_PAIR *reply_item; char msg[128]; char query[MAX_QUERY_LEN], subst[MAX_QUERY_LEN]; char *expanded = NULL; size_t len; /* * Before doing anything else, see if we have to reset * the counters. */ if (inst->reset_time && (inst->reset_time <= request->timestamp)) { /* * Re-set the next time and prev_time for this counters range */ inst->last_reset = inst->reset_time; find_next_reset(inst,request->timestamp); } /* * Look for the key. User-Name is special. It means * The REAL username, after stripping. */ if ((inst->key_attr->vendor == 0) && (inst->key_attr->attr == PW_USER_NAME)) { key_vp = request->username; } else { key_vp = pairfind(request->packet->vps, inst->key_attr->attr, inst->key_attr->vendor, TAG_ANY); } if (!key_vp) { RWDEBUG2("Couldn't find key attribute 'request:%s'", inst->key_attr->name); return rcode; } /* * Look for the check item */ if ((da = dict_attrbyname(inst->limit_name)) == NULL) { return rcode; } limit = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY); if (limit == NULL) { RWDEBUG2("Couldn't find control attribute 'control:%s'", inst->limit_name); return rcode; } /* First, expand %k, %b and %e in query */ if (sqlcounter_expand(subst, sizeof(subst), inst->query, inst) <= 0) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } /* Then combine that with the name of the module were using to do the query */ len = snprintf(query, sizeof(query), "%%{%s:%s}", inst->sqlmod_inst, subst); if (len >= (sizeof(query) - 1)) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } /* Finally, xlat resulting SQL query */ if (radius_axlat(&expanded, request, query, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } talloc_free(expanded); if (sscanf(expanded, "%" PRIu64, &counter) != 1) { RDEBUG2("No integer found in result string \"%s\". May be first session, setting counter to 0", expanded); counter = 0; } /* * Check if check item > counter */ if (limit->vp_integer64 <= counter) { /* User is denied access, send back a reply message */ snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", inst->reset); pairmake_reply("Reply-Message", msg, T_OP_EQ); REDEBUG2("Maximum %s usage time reached", inst->reset); REDEBUG2("Rejecting user, control:%s value (%" PRIu64 ") is less than counter value (%" PRIu64 ")", inst->limit_name, limit->vp_integer64, counter); return RLM_MODULE_REJECT; } res = limit->vp_integer64 - counter; RDEBUG2("Allowing user, control:%s value (%" PRIu64 ") is greater than counter value (%" PRIu64 ")", inst->limit_name, limit->vp_integer64, counter); /* * We are assuming that simultaneous-use=1. But * even if that does not happen then our user * could login at max for 2*max-usage-time Is * that acceptable? */ /* * If we are near a reset then add the next * limit, so that the user will not need to login * again. Do this only for Session-Timeout. */ if (((inst->reply_attr->vendor == 0) && (inst->reply_attr->attr == PW_SESSION_TIMEOUT)) && inst->reset_time && ((int) res >= (inst->reset_time - request->timestamp))) { res = (inst->reset_time - request->timestamp); res += limit->vp_integer; } /* * Limit the reply attribute to the minimum of the existing value, or this new one. */ reply_item = pairfind(request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor, TAG_ANY); if (reply_item) { if (reply_item->vp_integer64 <= res) { RDEBUG2("Leaving existing reply:%s value of %" PRIu64, inst->reply_attr->name, reply_item->vp_integer64); return RLM_MODULE_OK; } } else { reply_item = radius_paircreate(request->reply, &request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor); } reply_item->vp_integer64 = res; RDEBUG2("Setting reply:%s value to %" PRIu64, inst->reply_name, reply_item->vp_integer64); return RLM_MODULE_OK; }
/* * Verify that the diameter packet is valid. */ static int diameter_verify(REQUEST *request, uint8_t const *data, unsigned int data_len) { uint32_t attr; uint32_t length; unsigned int hdr_len; unsigned int remaining = data_len; while (remaining > 0) { hdr_len = 12; if (remaining < hdr_len) { RDEBUG2("Diameter attribute is too small (%u) to contain a Diameter header", remaining); return 0; } memcpy(&attr, data, sizeof(attr)); attr = ntohl(attr); memcpy(&length, data + 4, sizeof(length)); length = ntohl(length); if ((data[4] & 0x80) != 0) { if (remaining < 16) { RDEBUG2("Diameter attribute is too small to contain a Diameter header with Vendor-Id"); return 0; } hdr_len = 16; } /* * Get the length. If it's too big, die. */ length &= 0x00ffffff; /* * Too short or too long is bad. */ if (length <= (hdr_len - 4)) { RDEBUG2("Tunneled attribute %u is too short (%u < %u) to contain anything useful.", attr, length, hdr_len); return 0; } if (length > remaining) { RDEBUG2("Tunneled attribute %u is longer than room remaining in the packet (%u > %u).", attr, length, remaining); return 0; } /* * Check for broken implementations, which don't * pad the AVP to a 4-octet boundary. */ if (remaining == length) break; /* * The length does NOT include the padding, so * we've got to account for it here by rounding up * to the nearest 4-byte boundary. */ length += 0x03; length &= ~0x03; /* * If the rest of the diameter packet is larger than * this attribute, continue. * * Otherwise, if the attribute over-flows the end * of the packet, die. */ if (remaining < length) { REDEBUG2("Diameter attribute overflows packet!"); return 0; } /* * remaining > length, continue. */ remaining -= length; data += length; } /* * We got this far. It looks OK. */ return 1; }
/* * Convert diameter attributes to our VALUE_PAIR's */ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, uint8_t const *data, size_t data_len) { uint32_t attr; uint32_t vendor; uint32_t length; size_t offset; size_t size; size_t data_left = data_len; VALUE_PAIR *first = NULL; VALUE_PAIR *vp; RADIUS_PACKET *packet = fake->packet; /* FIXME: api issues */ vp_cursor_t out; fr_cursor_init(&out, &first); while (data_left > 0) { rad_assert(data_left <= data_len); memcpy(&attr, data, sizeof(attr)); data += 4; attr = ntohl(attr); vendor = 0; memcpy(&length, data, sizeof(length)); data += 4; length = ntohl(length); /* * A "vendor" flag, with a vendor ID of zero, * is equivalent to no vendor. This is stupid. */ offset = 8; if ((length & ((uint32_t)1 << 31)) != 0) { memcpy(&vendor, data, sizeof(vendor)); vendor = ntohl(vendor); data += 4; /* skip the vendor field, it's zero */ offset += 4; /* offset to value field */ if (attr > 65535) goto next_attr; if (vendor > FR_MAX_VENDOR) goto next_attr; } /* * FIXME: Handle the M bit. For now, we assume that * some other module takes care of any attribute * with the M bit set. */ /* * Get the length. */ length &= 0x00ffffff; /* * Get the size of the value portion of the * attribute. */ size = length - offset; /* * Vendor attributes can be larger than 255. * Normal attributes cannot be. */ if ((attr > 255) && (vendor == 0)) { RWDEBUG2("Skipping Diameter attribute %u", attr); goto next_attr; } /* * EAP-Message AVPs can be larger than 253 octets. * * For now, we rely on the main decoder in * src/lib/radius to decode data into VPs. This * means putting the data into a RADIUS attribute * format. It also means that we can't handle * "extended" attributes in the Diameter space. Oh well... */ if ((size > 253) && !((vendor == 0) && (attr == PW_EAP_MESSAGE))) { RWDEBUG2("diameter2vp skipping long attribute %u", attr); goto next_attr; } /* * RADIUS VSAs are handled as Diameter attributes * with Vendor-Id == 0, and the VSA data packed * into the "String" field as per normal. * * EXCEPT for the MS-CHAP attributes. */ if ((vendor == 0) && (attr == PW_VENDOR_SPECIFIC)) { ssize_t decoded; uint8_t buffer[256]; buffer[0] = PW_VENDOR_SPECIFIC; buffer[1] = size + 2; memcpy(buffer + 2, data, size); vp = NULL; decoded = rad_attr2vp(packet, NULL, NULL, NULL, buffer, size + 2, &vp); if (decoded < 0) { REDEBUG2("diameter2vp failed decoding attr: %s", fr_strerror()); goto do_octets; } if ((size_t) decoded != size + 2) { REDEBUG2("diameter2vp failed to entirely decode VSA"); fr_pair_list_free(&vp); goto do_octets; } fr_cursor_merge(&out, vp); goto next_attr; } /* * Create it. If this fails, it's because we're OOM. */ do_octets: vp = fr_pair_afrom_num(packet, attr, vendor); if (!vp) { RDEBUG2("Failure in creating VP"); fr_pair_list_free(&first); return NULL; } /* * If it's a type from our dictionary, then * we need to put the data in a relevant place. * * @todo: Export the lib/radius.c decoder, and use it here! */ switch (vp->da->type) { case PW_TYPE_INTEGER: case PW_TYPE_DATE: if (size != vp->vp_length) { DICT_ATTR const *da; /* * Bad format. Create a "raw" * attribute. */ raw: if (vp) fr_pair_list_free(&vp); da = dict_unknown_afrom_fields(packet, attr, vendor); if (!da) return NULL; vp = fr_pair_afrom_da(packet, da); if (!vp) return NULL; fr_pair_value_memcpy(vp, data, size); break; } memcpy(&vp->vp_integer, data, vp->vp_length); /* * Stored in host byte order: change it. */ vp->vp_integer = ntohl(vp->vp_integer); break; case PW_TYPE_INTEGER64: if (size != vp->vp_length) goto raw; memcpy(&vp->vp_integer64, data, vp->vp_length); /* * Stored in host byte order: change it. */ vp->vp_integer64 = ntohll(vp->vp_integer64); break; case PW_TYPE_IPV4_ADDR: if (size != vp->vp_length) { RDEBUG2("Invalid length attribute %d", attr); fr_pair_list_free(&first); fr_pair_list_free(&vp); return NULL; } memcpy(&vp->vp_ipaddr, data, vp->vp_length); /* * Stored in network byte order: don't change it. */ break; case PW_TYPE_BYTE: if (size != vp->vp_length) goto raw; vp->vp_byte = data[0]; break; case PW_TYPE_SHORT: if (size != vp->vp_length) goto raw; vp->vp_short = (data[0] * 256) + data[1]; break; case PW_TYPE_SIGNED: if (size != vp->vp_length) goto raw; memcpy(&vp->vp_signed, data, vp->vp_length); vp->vp_signed = ntohl(vp->vp_signed); break; case PW_TYPE_IPV6_ADDR: if (size != vp->vp_length) goto raw; memcpy(&vp->vp_ipv6addr, data, vp->vp_length); break; case PW_TYPE_IPV6_PREFIX: if (size != vp->vp_length) goto raw; memcpy(vp->vp_ipv6prefix, data, vp->vp_length); break; case PW_TYPE_STRING: fr_pair_value_bstrncpy(vp, data, size); vp->vp_length = strlen(vp->vp_strvalue); /* embedded zeros are NOT allowed */ break; /* * Copy it over verbatim. */ case PW_TYPE_OCTETS: default: fr_pair_value_memcpy(vp, data, size); break; } /* * Ensure that the client is using the * correct challenge. This weirdness is * to protect against against replay * attacks, where anyone observing the * CHAP exchange could pose as that user, * by simply choosing to use the same * challenge. * * By using a challenge based on * information from the current session, * we can guarantee that the client is * not *choosing* a challenge. * * We're a little forgiving in that we * have loose checks on the length, and * we do NOT check the Id (first octet of * the response to the challenge) * * But if the client gets the challenge correct, * we're not too worried about the Id. */ if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) || ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) { uint8_t challenge[16]; if ((vp->vp_length < 8) || (vp->vp_length > 16)) { RDEBUG("Tunneled challenge has invalid length"); fr_pair_list_free(&first); fr_pair_list_free(&vp); return NULL; } eapttls_gen_challenge(ssl, challenge, sizeof(challenge)); if (memcmp(challenge, vp->vp_octets, vp->vp_length) != 0) { RDEBUG("Tunneled challenge is incorrect"); fr_pair_list_free(&first); fr_pair_list_free(&vp); return NULL; } } /* * Update the list. */ fr_cursor_insert(&out, vp); next_attr: /* * Catch non-aligned attributes. */ if (data_left == length) break; /* * The length does NOT include the padding, so * we've got to account for it here by rounding up * to the nearest 4-byte boundary. */ length += 0x03; length &= ~0x03; rad_assert(data_left >= length); data_left -= length; data += length - offset; /* already updated */ } /* * We got this far. It looks OK. */ return first; }
/** Authenticate a previously sent challenge * */ static rlm_rcode_t mod_process(UNUSED void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); fr_sim_decode_ctx_t ctx = { .keys = &eap_sim_session->keys, }; VALUE_PAIR *subtype_vp, *from_peer, *vp; fr_cursor_t cursor; eap_sim_subtype_t subtype; int ret; /* * VPS is the data from the client */ from_peer = eap_session->request->packet->vps; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_tail(&cursor); ret = fr_sim_decode(eap_session->request, &cursor, dict_eap_sim, eap_session->this_round->response->type.data, eap_session->this_round->response->type.length, &ctx); /* * RFC 4186 says we *MUST* notify, not just * send an EAP-Failure in this case where * we cannot decode an EAP-AKA packet. */ if (ret < 0) { RPEDEBUG2("Failed decoding EAP-SIM attributes"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } vp = fr_cursor_current(&cursor); if (vp && RDEBUG_ENABLED2) { RDEBUG2("Decoded EAP-SIM attributes"); log_request_pair_list(L_DBG_LVL_2, request, vp, NULL); } subtype_vp = fr_pair_find_by_da(from_peer, attr_eap_sim_subtype, TAG_ANY); if (!subtype_vp) { REDEBUG("Missing EAP-SIM-Subtype"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } subtype = subtype_vp->vp_uint16; switch (eap_sim_session->state) { /* * Response to our advertised versions and request for an ID * This is very similar to Identity negotiation in EAP-AKA['] */ case EAP_SIM_SERVER_START: switch (subtype) { case EAP_SIM_START: if (process_eap_sim_start(eap_session, from_peer) == 0) return RLM_MODULE_HANDLED; eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure * * This can happen in the case of a conservative * peer, where it refuses to provide the permanent * identity. */ case EAP_SIM_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(from_peer, attr_eap_sim_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-SIM Peer rejected SIM-Start (%s) with client-error message but " "has not supplied a client error code", fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>")); } else { REDEBUG("Client rejected SIM-Start (%s) with error: %s (%i)", fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>"), fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_SIM_NOTIFICATION: notification: { char buff[20]; vp = fr_pair_afrom_da(from_peer, attr_eap_sim_notification); if (!vp) { REDEBUG2("Received SIM-Notification with no notification code"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Case 2 where we're allowed to send an EAP-Failure */ if (!(vp->vp_uint16 & 0x8000)) { REDEBUG2("SIM-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * ...if it's not a failure, then re-enter the * current state. */ REDEBUG2("Got SIM-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_sim_state_enter(eap_session, eap_sim_session->state); return RLM_MODULE_HANDLED; default: unexpected_subtype: /* * RFC 4186 says we *MUST* notify, not just * send an EAP-Failure in this case. */ REDEBUG("Unexpected subtype %pV", &subtype_vp->data); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /* * Process the response to our previous challenge. */ case EAP_SIM_SERVER_CHALLENGE: switch (subtype) { /* * A response to our EAP-Sim/Request/Challenge! */ case EAP_SIM_CHALLENGE: switch (process_eap_sim_challenge(eap_session, from_peer)) { case 1: return RLM_MODULE_HANDLED; case 0: return RLM_MODULE_OK; case -1: eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } case EAP_SIM_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(from_peer, attr_eap_sim_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-SIM Peer rejected SIM-Challenge with client-error message but " "has not supplied a client error code"); } else { REDEBUG("Client rejected SIM-Challenge with error: %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_SIM_NOTIFICATION: goto notification; default: goto unexpected_subtype; } /* * Peer acked our failure */ case EAP_SIM_SERVER_FAILURE_NOTIFICATION: switch (subtype) { case EAP_SIM_NOTIFICATION: RDEBUG2("SIM-Notification ACKed, sending EAP-Failure"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; default: goto unexpected_subtype; } /* * Something bad happened... */ default: rad_assert(0); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /* * Initiate the EAP-SIM session by starting the state machine * and initiating the state. */ static rlm_rcode_t mod_session_init(void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session; rlm_eap_sim_t *inst = instance; fr_sim_id_type_t type; fr_sim_method_hint_t method; MEM(eap_sim_session = talloc_zero(eap_session, eap_sim_session_t)); eap_session->opaque = eap_sim_session; /* * Set default configuration, we may allow these * to be toggled by attributes later. */ eap_sim_session->send_result_ind = inst->protected_success; eap_sim_session->id_req = SIM_ANY_ID_REQ; /* Set the default */ /* * This value doesn't have be strong, but it is * good if it is different now and then. */ eap_sim_session->sim_id = (fr_rand() & 0xff); /* * Save the keying material, because it could change on a subsequent retrieval. */ RDEBUG2("New EAP-SIM session"); /* * Process the identity that we received in the * EAP-Identity-Response and use it to determine * the initial request we send to the Supplicant. */ if (fr_sim_id_type(&type, &method, eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { RPWDEBUG2("Failed parsing identity, continuing anyway"); } switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're attempting EAP-SIM", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_SIM: case SIM_METHOD_HINT_UNKNOWN: break; } eap_session->process = mod_process; /* * Figure out what type of identity we have * and use it to determine the initial * request we send. */ switch (type) { /* * These types need to be transformed into something * usable before we can do anything. */ case SIM_ID_TYPE_UNKNOWN: case SIM_ID_TYPE_PSEUDONYM: case SIM_ID_TYPE_FASTAUTH: /* * Permanent ID means we can just send the challenge */ case SIM_ID_TYPE_PERMANENT: eap_sim_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1; MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, eap_session->identity, eap_sim_session->keys.identity_len)); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START); return RLM_MODULE_HANDLED; } return RLM_MODULE_HANDLED; }
/** Process an EAP-Sim/Response/Start * * Verify that client chose a version, and provided a NONCE_MT, * and if so, then change states to challenge, and send the new * challenge, else, resend the Request/Start. */ static int process_eap_sim_start(eap_session_t *eap_session, VALUE_PAIR *vps) { REQUEST *request = eap_session->request; VALUE_PAIR *nonce_vp, *selected_version_vp; eap_sim_session_t *eap_sim_session; uint16_t eap_sim_version; VALUE_PAIR *id; fr_sim_id_type_t type = SIM_ID_TYPE_UNKNOWN; fr_sim_method_hint_t method = SIM_METHOD_HINT_UNKNOWN; eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); /* * For fullauth We require both the NONCE_MT * and SELECTED_VERSION from the peer, else * the packet is invalid. */ nonce_vp = fr_pair_find_by_da(vps, attr_eap_sim_nonce_mt, TAG_ANY); selected_version_vp = fr_pair_find_by_da(vps, attr_eap_sim_selected_version, TAG_ANY); if (!nonce_vp || !selected_version_vp) { REDEBUG2("Client did not select a version and send a NONCE"); return -1; } eap_sim_version = selected_version_vp->vp_uint16; if (eap_sim_version != EAP_SIM_VERSION) { REDEBUG2("EAP-SIM-Version %i is unknown", eap_sim_version); return -1; } /* * Record it for later keying */ eap_sim_version = htons(eap_sim_version); memcpy(eap_sim_session->keys.gsm.version_select, &eap_sim_version, sizeof(eap_sim_session->keys.gsm.version_select)); /* * Double check the nonce size. */ if (nonce_vp->vp_length != 16) { REDEBUG("EAP-SIM nonce_mt must be 16 bytes, not %zu bytes", nonce_vp->vp_length); return -1; } memcpy(eap_sim_session->keys.gsm.nonce_mt, nonce_vp->vp_octets, 16); /* * See if we got an AT_IDENTITY */ id = fr_pair_find_by_da(vps, attr_eap_sim_identity, TAG_ANY); if (id) { if (fr_sim_id_type(&type, &method, eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { RPWDEBUG2("Failed parsing identity"); } /* * Update cryptographic identity */ talloc_const_free(eap_sim_session->keys.identity); eap_sim_session->keys.identity_len = id->vp_length; MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, id->vp_strvalue, id->vp_length)); } /* * @TODO Run a virtual server to see if we can use the * identity we just acquired, or whether we need to * negotiate the next permissive ID. */ /* * Negotiate the next permissive form * if identity, or fail. */ switch (eap_sim_session->id_req) { case SIM_ANY_ID_REQ: eap_sim_session->id_req = SIM_FULLAUTH_ID_REQ; eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START); break; case SIM_FULLAUTH_ID_REQ: eap_sim_session->id_req = SIM_PERMANENT_ID_REQ; eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START); break; case SIM_NO_ID_REQ: case SIM_PERMANENT_ID_REQ: eap_sim_state_enter(eap_session, EAP_SIM_SERVER_CHALLENGE); // REDEBUG2("Failed to negotiate a usable identity"); // eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); break; } return 0; }
/* * Check NTLM authentication direct to winbind via * Samba's libwbclient library * * Returns: * 0 success * -1 auth failure * -648 password expired */ int do_auth_wbclient(rlm_mschap_t *inst, REQUEST *request, uint8_t const *challenge, uint8_t const *response, uint8_t nthashhash[NT_DIGEST_LENGTH]) { int rcode = -1; struct wbcContext *wb_ctx; struct wbcAuthUserParams authparams; wbcErr err; int len; struct wbcAuthUserInfo *info = NULL; struct wbcAuthErrorInfo *error = NULL; char user_name_buf[500]; char domain_name_buf[500]; uint8_t resp[NT_LENGTH]; /* * Clear the auth parameters - this is important, as * there are options that will cause wbcAuthenticateUserEx * to bomb out if not zero. */ memset(&authparams, 0, sizeof(authparams)); /* * wb_username must be set for this function to be called */ rad_assert(inst->wb_username); /* * Get the username and domain from the configuration */ len = tmpl_expand(&authparams.account_name, user_name_buf, sizeof(user_name_buf), request, inst->wb_username, NULL, NULL); if (len < 0) { REDEBUG2("Unable to expand winbind_username"); goto done; } if (inst->wb_domain) { len = tmpl_expand(&authparams.domain_name, domain_name_buf, sizeof(domain_name_buf), request, inst->wb_domain, NULL, NULL); if (len < 0) { REDEBUG2("Unable to expand winbind_domain"); goto done; } } else { RWDEBUG2("No domain specified; authentication may fail because of this"); } /* * Build the wbcAuthUserParams structure with what we know */ authparams.level = WBC_AUTH_USER_LEVEL_RESPONSE; authparams.password.response.nt_length = NT_LENGTH; memcpy(resp, response, NT_LENGTH); authparams.password.response.nt_data = resp; memcpy(authparams.password.response.challenge, challenge, sizeof(authparams.password.response.challenge)); /* * Send auth request across to winbind */ wb_ctx = fr_connection_get(inst->wb_pool); if (wb_ctx == NULL) { RERROR("Unable to get winbind connection from pool"); goto done; } RDEBUG2("sending authentication request user='******' domain='%s'", authparams.account_name, authparams.domain_name); err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error); fr_connection_release(inst->wb_pool, wb_ctx); /* * Try and give some useful feedback on what happened. There are only * a few errors that can actually be returned from wbcCtxAuthenticateUserEx. */ switch (err) { case WBC_ERR_SUCCESS: rcode = 0; RDEBUG2("Authenticated successfully"); /* Grab the nthashhash from the result */ memcpy(nthashhash, info->user_session_key, NT_DIGEST_LENGTH); break; case WBC_ERR_WINBIND_NOT_AVAILABLE: RERROR("Unable to contact winbind!"); RDEBUG2("Check that winbind is running and that FreeRADIUS has"); RDEBUG2("permission to connect to the winbind privileged socket."); break; case WBC_ERR_DOMAIN_NOT_FOUND: REDEBUG2("Domain not found"); break; case WBC_ERR_AUTH_ERROR: if (!error) { REDEBUG2("Authentication failed"); break; } /* * The password needs to be changed, so set rcode appropriately. */ if (error->nt_status & NT_STATUS_PASSWORD_EXPIRED || error->nt_status & NT_STATUS_PASSWORD_MUST_CHANGE) { rcode = -648; } /* * Return the NT_STATUS human readable error string, if there is one. */ if (error->display_string) { REDEBUG2("%s [0x%X]", error->display_string, error->nt_status); } else { REDEBUG2("Authentication failed [0x%X]", error->nt_status); } break; default: /* * Only errors left are * WBC_ERR_INVALID_PARAM * WBC_ERR_NO_MEMORY * neither of which are particularly likely. */ if (error && error->display_string) { REDEBUG2("libwbclient error: wbcErr %d (%s)", err, error->display_string); } else { REDEBUG2("libwbclient error: wbcErr %d", err); } break; } done: if (info) wbcFreeMemory(info); if (error) wbcFreeMemory(error); return rcode; }
/** Process the Peer's response and advantage the state machine * */ static rlm_rcode_t mod_process(UNUSED void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); fr_sim_decode_ctx_t ctx = { .keys = &eap_aka_session->keys, }; VALUE_PAIR *vp, *vps, *subtype_vp; fr_cursor_t cursor; eap_aka_subtype_t subtype; int ret; /* * RFC 4187 says we ignore the contents of the * next packet after we send our success notification * and always send a success. */ if (eap_aka_session->state == EAP_AKA_SERVER_SUCCESS_NOTIFICATION) { eap_aka_state_enter(eap_session, EAP_AKA_SERVER_SUCCESS); return RLM_MODULE_HANDLED; } /* vps is the data from the client */ vps = request->packet->vps; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_tail(&cursor); ret = fr_sim_decode(eap_session->request, &cursor, dict_eap_aka, eap_session->this_round->response->type.data, eap_session->this_round->response->type.length, &ctx); /* * RFC 4187 says we *MUST* notify, not just * send an EAP-Failure in this case where * we cannot decode an EAP-AKA packet. */ if (ret < 0) { RPEDEBUG2("Failed decoding EAP-AKA attributes"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } vp = fr_cursor_current(&cursor); if (vp && RDEBUG_ENABLED2) { RDEBUG2("EAP-AKA decoded attributes"); log_request_pair_list(L_DBG_LVL_2, request, vp, NULL); } subtype_vp = fr_pair_find_by_da(vps, attr_eap_aka_subtype, TAG_ANY); if (!subtype_vp) { REDEBUG("Missing EAP-AKA-Subtype"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } subtype = subtype_vp->vp_uint16; switch (eap_aka_session->state) { /* * Here we expected the peer to send * us identities for validation. */ case EAP_AKA_SERVER_IDENTITY: switch (subtype) { case EAP_AKA_IDENTITY: if (process_eap_aka_identity(eap_session, vps) == 0) return RLM_MODULE_HANDLED; eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure * * This can happen in the case of a conservative * peer, where it refuses to provide the permanent * identity. */ case EAP_AKA_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(vps, attr_eap_aka_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-AKA Peer rejected AKA-Identity (%s) with client-error message but " "has not supplied a client error code", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>")); } else { REDEBUG("Client rejected AKA-Identity (%s) with error: %s (%i)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>"), fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_AKA_NOTIFICATION: notification: { char buff[20]; vp = fr_pair_afrom_da(vps, attr_eap_aka_notification); if (!vp) { REDEBUG2("Received AKA-Notification with no notification code"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Case 3 where we're allowed to send an EAP-Failure */ if (!(vp->vp_uint16 & 0x8000)) { REDEBUG2("AKA-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * ...if it's not a failure, then re-enter the * current state. */ REDEBUG2("Got AKA-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_aka_state_enter(eap_session, eap_aka_session->state); return RLM_MODULE_HANDLED; } default: unexpected_subtype: /* * RFC 4187 says we *MUST* notify, not just * send an EAP-Failure in this case. */ REDEBUG("Unexpected subtype %pV", &subtype_vp->data); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Process the response to our previous challenge. */ case EAP_AKA_SERVER_CHALLENGE: switch (subtype) { case EAP_AKA_CHALLENGE: switch (process_eap_aka_challenge(eap_session, vps)) { case 1: return RLM_MODULE_HANDLED; case 0: return RLM_MODULE_OK; case -1: eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } case EAP_AKA_SYNCHRONIZATION_FAILURE: REDEBUG("EAP-AKA Peer synchronization failure"); /* We can't handle these yet */ eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure */ case EAP_AKA_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(vps, attr_eap_aka_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-AKA Peer rejected AKA-Challenge with client-error message but " "has not supplied a client error code"); } else { REDEBUG("Client rejected AKA-Challenge with error: %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * Case 2 where we're allowed to send an EAP-Failure */ case EAP_AKA_AUTHENTICATION_REJECT: REDEBUG("EAP-AKA Peer Rejected AUTN"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; case EAP_AKA_NOTIFICATION: goto notification; default: goto unexpected_subtype; } /* * Peer acked our failure */ case EAP_AKA_SERVER_FAILURE_NOTIFICATION: switch (subtype) { case EAP_AKA_NOTIFICATION: RDEBUG2("AKA-Notification ACKed, sending EAP-Failure"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; default: goto unexpected_subtype; } /* * Something bad happened... */ default: rad_assert(0); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /** Initiate the EAP-SIM session by starting the state machine * */ static rlm_rcode_t mod_session_init(void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session; rlm_eap_aka_t *inst = instance; fr_sim_id_type_t type; fr_sim_method_hint_t method; MEM(eap_aka_session = talloc_zero(eap_session, eap_aka_session_t)); eap_session->opaque = eap_aka_session; /* * Set default configuration, we may allow these * to be toggled by attributes later. */ eap_aka_session->request_identity = inst->request_identity; eap_aka_session->send_result_ind = inst->protected_success; eap_aka_session->id_req = SIM_NO_ID_REQ; /* Set the default */ /* * This value doesn't have be strong, but it is * good if it is different now and then. */ eap_aka_session->aka_id = (fr_rand() & 0xff); /* * Process the identity that we received in the * EAP-Identity-Response and use it to determine * the initial request we send to the Supplicant. */ if (fr_sim_id_type(&type, &method, eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { RPWDEBUG2("Failed parsing identity, continuing anyway"); } /* * Unless AKA-Prime is explicitly disabled, * use it... It has stronger keying, and * binds authentication to the network. */ switch (eap_session->type) { case FR_EAP_AKA_PRIME: default: RDEBUG2("New EAP-AKA' session"); eap_aka_session->type = FR_EAP_AKA_PRIME; eap_aka_session->kdf = FR_EAP_AKA_KDF_VALUE_EAP_AKA_PRIME_WITH_CK_PRIME_IK_PRIME; eap_aka_session->checkcode_md = eap_aka_session->mac_md = EVP_sha256(); eap_aka_session->keys.network = (uint8_t *)talloc_bstrndup(eap_aka_session, inst->network_name, talloc_array_length(inst->network_name) - 1); eap_aka_session->keys.network_len = talloc_array_length(eap_aka_session->keys.network) - 1; switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're " "attempting EAP-AKA'", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_AKA_PRIME: case SIM_METHOD_HINT_UNKNOWN: break; } break; case FR_EAP_AKA: RDEBUG2("New EAP-AKA session"); eap_aka_session->type = FR_EAP_AKA; eap_aka_session->kdf = FR_EAP_AKA_KDF_VALUE_EAP_AKA; /* Not actually sent */ eap_aka_session->checkcode_md = eap_aka_session->mac_md = EVP_sha1(); eap_aka_session->send_at_bidding = true; switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're " "attempting EAP-AKA", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_AKA: case SIM_METHOD_HINT_UNKNOWN: break; } break; } eap_session->process = mod_process; /* * Admin wants us to always request an identity * initially. The RFC says this is also the * better way to operate, as the supplicant * can 'decorate' the identity in the identity * response. */ if (inst->request_identity) { request_id: /* * We always start by requesting * any ID initially as we can * always negotiate down. */ eap_aka_session->id_req = SIM_ANY_ID_REQ; eap_aka_state_enter(eap_session, EAP_AKA_SERVER_IDENTITY); return RLM_MODULE_HANDLED; } /* * Figure out what type of identity we have * and use it to determine the initial * request we send. */ switch (type) { /* * If there's no valid tag on the identity * then it's probably been decorated by the * supplicant. * * Request the unmolested identity */ case SIM_ID_TYPE_UNKNOWN: RWDEBUG("Identity format unknown, sending Identity request"); goto request_id; /* * These types need to be transformed into something * usable before we can do anything. */ case SIM_ID_TYPE_PSEUDONYM: case SIM_ID_TYPE_FASTAUTH: /* * Permanent ID means we can just send the challenge */ case SIM_ID_TYPE_PERMANENT: eap_aka_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1; MEM(eap_aka_session->keys.identity = talloc_memdup(eap_aka_session, eap_session->identity, eap_aka_session->keys.identity_len)); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_CHALLENGE); return RLM_MODULE_HANDLED; } return RLM_MODULE_HANDLED; }
/** Send the challenge itself * * Challenges will come from one of three places eventually: * * 1 from attributes like FR_EAP_AKA_RANDx * (these might be retrieved from a database) * * 2 from internally implemented SIM authenticators * (a simple one based upon XOR will be provided) * * 3 from some kind of SS7 interface. * * For now, they only come from attributes. * It might be that the best way to do 2/3 will be with a different * module to generate/calculate things. */ static int eap_aka_send_challenge(eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); VALUE_PAIR **to_peer, *vp; RADIUS_PACKET *packet; fr_sim_vector_src_t src = SIM_VECTOR_SRC_AUTO; rad_assert(request); rad_assert(request->reply); /* * to_peer is the data to the client */ packet = eap_session->request->reply; to_peer = &packet->vps; RDEBUG2("Acquiring UMTS vector(s)"); /* * Toggle the AMF high bit to indicate we're doing AKA' */ if (eap_aka_session->type == FR_EAP_AKA_PRIME) { uint8_t amf_buff[2] = { 0x80, 0x00 }; /* Set the AMF separation bit high */ MEM(pair_update_control(&vp, attr_sim_amf) >= 0); fr_pair_value_memcpy(vp, amf_buff, sizeof(amf_buff)); } /* * Get vectors from attribute or generate * them using COMP128-* or Milenage. */ if (fr_sim_vector_umts_from_attrs(eap_session, request->control, &eap_aka_session->keys, &src) != 0) { REDEBUG("Failed retrieving UMTS vectors"); return RLM_MODULE_FAIL; } /* * Don't leave the AMF hanging around */ if (eap_aka_session->type == FR_EAP_AKA_PRIME) pair_delete_control(attr_sim_amf); /* * All set, calculate keys! */ switch (eap_aka_session->kdf) { case FR_EAP_AKA_KDF_VALUE_EAP_AKA_PRIME_WITH_CK_PRIME_IK_PRIME: fr_sim_crypto_kdf_1_umts(&eap_aka_session->keys); break; default: fr_sim_crypto_kdf_0_umts(&eap_aka_session->keys); break; } if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_aka_session->keys); RDEBUG2("Sending AKA-Challenge"); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; /* * Set the subtype to challenge */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_subtype)); vp->vp_uint16 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_CHALLENGE; fr_pair_replace(to_peer, vp); /* * Indicate we'd like to use protected success messages */ if (eap_aka_session->send_result_ind) { MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_result_ind)); vp->vp_bool = true; fr_pair_replace(to_peer, vp); } /* * We support EAP-AKA' and the peer should use that * if it's able to... */ if (eap_aka_session->send_at_bidding) { MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_bidding)); vp->vp_uint16 = FR_EAP_AKA_BIDDING_VALUE_PREFER_AKA_PRIME; fr_pair_replace(to_peer, vp); } /* * Send the network name and KDF to the peer */ if (eap_aka_session->type == FR_EAP_AKA_PRIME) { if (!eap_aka_session->keys.network_len) { REDEBUG2("No network name available, can't set EAP-AKA-KDF-Input"); failure: fr_pair_list_free(&packet->vps); return -1; } MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_kdf_input)); fr_pair_value_bstrncpy(vp, eap_aka_session->keys.network, eap_aka_session->keys.network_len); fr_pair_replace(to_peer, vp); MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_kdf)); vp->vp_uint16 = eap_aka_session->kdf; fr_pair_replace(to_peer, vp); } /* * Okay, we got the challenge! Put it into an attribute. */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_rand)); fr_pair_value_memcpy(vp, eap_aka_session->keys.umts.vector.rand, SIM_VECTOR_UMTS_RAND_SIZE); fr_pair_replace(to_peer, vp); /* * Send the AUTN value to the client, so it can authenticate * whoever has knowledge of the Ki. */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_autn)); fr_pair_value_memcpy(vp, eap_aka_session->keys.umts.vector.autn, SIM_VECTOR_UMTS_AUTN_SIZE); fr_pair_replace(to_peer, vp); /* * need to include an AT_MAC attribute so that it will get * calculated. */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_mac)); fr_pair_replace(to_peer, vp); /* * If we have checkcode data, send that to the peer * for validation. */ if (eap_aka_session->checkcode_state) { ssize_t slen; slen = fr_sim_crypto_finalise_checkcode(eap_aka_session->checkcode, &eap_aka_session->checkcode_state); if (slen < 0) { RPEDEBUG("Failed calculating checkcode"); goto failure; } eap_aka_session->checkcode_len = slen; MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_checkcode)); fr_pair_value_memcpy(vp, eap_aka_session->checkcode, slen); /* * If we don't have checkcode data, then we exchanged * no identity packets, so checkcode is zero. */ } else { MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_checkcode)); eap_aka_session->checkcode_len = 0; } fr_pair_replace(to_peer, vp); /* * We've sent the challenge so the peer should now be able * to accept encrypted attributes. */ eap_aka_session->allow_encrypted = true; /* * Encode the packet */ if (eap_aka_compose(eap_session) < 0) goto failure; return 0; }
/** Copy packet to multiple servers * * Create a duplicate of the packet and send it to a list of realms * defined by the presence of the Replicate-To-Realm VP in the control * list of the current request. * * This is pretty hacky and is 100% fire and forget. If you're looking * to forward authentication requests to multiple realms and process * the responses, this function will not allow you to do that. * * @param[in] instance of this module. * @param[in] request The current request. * @param[in] list of attributes to copy to the duplicate packet. * @param[in] code to write into the code field of the duplicate packet. * @return RCODE fail on error, invalid if list does not exist, noop if no replications succeeded, else ok. */ static int replicate_packet(UNUSED void *instance, REQUEST *request, pair_lists_t list, unsigned int code) { int rcode = RLM_MODULE_NOOP; VALUE_PAIR *vp, **vps; vp_cursor_t cursor; home_server_t *home; REALM *realm; home_pool_t *pool; RADIUS_PACKET *packet = NULL; /* * Send as many packets as necessary to different * destinations. */ fr_cursor_init(&cursor, &request->config); while ((vp = fr_cursor_next_by_num(&cursor, PW_REPLICATE_TO_REALM, 0, TAG_ANY))) { realm = realm_find2(vp->vp_strvalue); if (!realm) { REDEBUG2("Cannot Replicate to unknown realm \"%s\"", vp->vp_strvalue); continue; } /* * We shouldn't really do this on every loop. */ switch (request->packet->code) { default: REDEBUG2("Cannot replicate unknown packet code %d", request->packet->code); cleanup(packet); return RLM_MODULE_FAIL; case PW_CODE_ACCESS_REQUEST: pool = realm->auth_pool; break; #ifdef WITH_ACCOUNTING case PW_CODE_ACCOUNTING_REQUEST: pool = realm->acct_pool; break; #endif #ifdef WITH_COA case PW_CODE_COA_REQUEST: case PW_CODE_DISCONNECT_REQUEST: pool = realm->coa_pool; break; #endif } if (!pool) { RWDEBUG2("Cancelling replication to Realm %s, as the realm is local", realm->name); continue; } home = home_server_ldb(realm->name, pool, request); if (!home) { REDEBUG2("Failed to find live home server for realm %s", realm->name); continue; } /* * For replication to multiple servers we re-use the packet * we built here. */ if (!packet) { packet = rad_alloc(request, true); if (!packet) { return RLM_MODULE_FAIL; } packet->code = code; packet->id = fr_rand() & 0xff; packet->sockfd = fr_socket(&home->src_ipaddr, 0); if (packet->sockfd < 0) { REDEBUG("Failed opening socket: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } vps = radius_list(request, list); if (!vps) { RWDEBUG("List '%s' doesn't exist for this packet", fr_int2str(pair_lists, list, "<INVALID>")); rcode = RLM_MODULE_INVALID; goto done; } /* * Don't assume the list actually contains any * attributes. */ if (*vps) { packet->vps = fr_pair_list_copy(packet, *vps); if (!packet->vps) { rcode = RLM_MODULE_FAIL; goto done; } } /* * For CHAP, create the CHAP-Challenge if * it doesn't exist. */ if ((code == PW_CODE_ACCESS_REQUEST) && (fr_pair_find_by_num(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) != NULL) && (fr_pair_find_by_num(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL)) { uint8_t *p; vp = radius_pair_create(packet, &packet->vps, PW_CHAP_CHALLENGE, 0); vp->length = AUTH_VECTOR_LEN; vp->vp_octets = p = talloc_array(vp, uint8_t, vp->length); memcpy(p, request->packet->vector, AUTH_VECTOR_LEN); } } else { size_t i; for (i = 0; i < sizeof(packet->vector); i++) { packet->vector[i] = fr_rand() & 0xff; } packet->id++; TALLOC_FREE(packet->data); packet->data_len = 0; } /* * (Re)-Write these. */ packet->dst_ipaddr = home->ipaddr; packet->dst_port = home->port; memset(&packet->src_ipaddr, 0, sizeof(packet->src_ipaddr)); packet->src_port = 0; /* * Encode, sign and then send the packet. */ RDEBUG("Replicating list '%s' to Realm '%s'", fr_int2str(pair_lists, list, "<INVALID>"), realm->name); if (rad_send(packet, NULL, home->secret) < 0) { REDEBUG("Failed replicating packet: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } /* * We've sent it to at least one destination. */ rcode = RLM_MODULE_OK; } done: cleanup(packet); return rcode; }