static int eap_req2vp(EAP_HANDLER *handler) { int encoded, total, size; const uint8_t *ptr; VALUE_PAIR *head = NULL; VALUE_PAIR **tail = &head; VALUE_PAIR *vp; ptr = wpabuf_head(handler->server_ctx.eap_if->eapReqData); encoded = total = wpabuf_len(handler->server_ctx.eap_if->eapReqData); do { size = total; if (size > 253) size = 253; vp = paircreate(handler->request->reply, PW_EAP_MESSAGE, 0); if (!vp) { pairfree(&head); return -1; } pairmemcpy(vp, ptr, size); *tail = vp; tail = &(vp->next); ptr += size; total -= size; } while (total > 0); pairdelete(&handler->request->reply->vps, PW_EAP_MESSAGE, TAG_ANY); pairadd(&handler->request->reply->vps, head); return encoded; }
static int eap_sim_sendstart(eap_handler_t *handler) { VALUE_PAIR **vps, *newvp; uint16_t words[3]; eap_sim_state_t *ess; RADIUS_PACKET *packet; uint8_t *p; rad_assert(handler->request != NULL); rad_assert(handler->request->reply); ess = (eap_sim_state_t *)handler->opaque; /* these are the outgoing attributes */ packet = handler->request->reply; vps = &packet->vps; rad_assert(vps != NULL); /* * Add appropriate TLVs for the EAP things we wish to send. */ /* the version list. We support only version 1. */ words[0] = htons(sizeof(words[1])); words[1] = htons(EAP_SIM_VERSION); words[2] = 0; newvp = paircreate(packet, PW_EAP_SIM_VERSION_LIST, 0); pairmemcpy(newvp, (uint8_t const *) words, sizeof(words)); pairadd(vps, newvp); /* set the EAP_ID - new value */ newvp = paircreate(packet, PW_EAP_ID, 0); newvp->vp_integer = ess->sim_id++; pairreplace(vps, newvp); /* record it in the ess */ ess->keys.versionlistlen = 2; memcpy(ess->keys.versionlist, words + 1, ess->keys.versionlistlen); /* the ANY_ID attribute. We do not support re-auth or pseudonym */ newvp = paircreate(packet, PW_EAP_SIM_FULLAUTH_ID_REQ, 0); newvp->length = 2; newvp->vp_octets = p = talloc_array(newvp, uint8_t, 2); p[0] = 0; p[0] = 1; pairadd(vps, newvp); /* the SUBTYPE, set to start. */ newvp = paircreate(packet, PW_EAP_SIM_SUBTYPE, 0); newvp->vp_integer = EAPSIM_START; pairreplace(vps, newvp); return 1; }
/** Allocate a request packet * * This is done once per request with the same packet being sent to multiple realms. */ static rlm_rcode_t rlm_replicate_alloc(RADIUS_PACKET **out, REQUEST *request, pair_lists_t list, PW_CODE code) { rlm_rcode_t rcode = RLM_MODULE_OK; RADIUS_PACKET *packet = NULL; VALUE_PAIR *vp, **vps; *out = NULL; packet = rad_alloc(request, 1); if (!packet) { return RLM_MODULE_FAIL; } packet->code = code; /* * Figure out which list in the request were replicating */ 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 error; } /* * Don't assume the list actually contains any attributes. */ if (*vps) { packet->vps = paircopy(packet, *vps); if (!packet->vps) { rcode = RLM_MODULE_FAIL; goto error; } } /* * For CHAP, create the CHAP-Challenge if it doesn't exist. */ if ((code == PW_CODE_ACCESS_REQUEST) && (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) != NULL) && (pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL)) { vp = radius_paircreate(packet, &packet->vps, PW_CHAP_CHALLENGE, 0); pairmemcpy(vp, request->packet->vector, AUTH_VECTOR_LEN); } *out = packet; return rcode; error: talloc_free(packet); return rcode; }
/** Copy a single valuepair * * Allocate a new valuepair and copy the da from the old vp. * * @param[in] ctx for talloc * @param[in] vp to copy. * @return a copy of the input VP or NULL on error. */ VALUE_PAIR *paircopyvp(TALLOC_CTX *ctx, VALUE_PAIR const *vp) { VALUE_PAIR *n; if (!vp) return NULL; VERIFY_VP(vp); n = pairalloc(ctx, vp->da); if (!n) return NULL; memcpy(n, vp, sizeof(*n)); /* * If the DA is unknown, steal "n" to "ctx". This does * nothing for "n", but will also copy the unknown "da". */ if (n->da->flags.is_unknown) { pairsteal(ctx, n); } n->next = NULL; /* * If it's an xlat, copy the raw string and return early, * so we don't pre-expand or otherwise mangle the VALUE_PAIR. */ if (vp->type == VT_XLAT) { n->xlat = talloc_typed_strdup(n, n->xlat); return n; } switch (vp->da->type) { case PW_TYPE_TLV: case PW_TYPE_OCTETS: n->vp_octets = NULL; /* else pairmemcpy will free vp's value */ pairmemcpy(n, vp->vp_octets, n->vp_length); break; case PW_TYPE_STRING: n->vp_strvalue = NULL; /* else pairstrnpy will free vp's value */ pairstrncpy(n, vp->vp_strvalue, n->vp_length); break; default: break; } return n; }
/* * Add raw hex data to the reply. */ void eap_add_reply(REQUEST *request, char const *name, uint8_t const *value, int len) { VALUE_PAIR *vp; vp = pairmake_reply(name, NULL, T_OP_EQ); if (!vp) { REDEBUG("Did not create attribute %s: %s\n", name, fr_strerror()); return; } pairmemcpy(vp, value, len); }
/* * Access-Requests can have the CHAP-Challenge implicitly taken * from the request authenticator. If the NAS has done that, * then we need to copy the data to a real CHAP-Challenge * attribute when proxying. Otherwise when we proxy the request, * the new authenticator is different, and the CHAP calculations * will fail. */ static rlm_rcode_t CC_HINT(nonnull) mod_pre_proxy(UNUSED void *instance, REQUEST *request) { VALUE_PAIR *vp; /* * For Access-Requests, which have CHAP-Password, * and no CHAP-Challenge, copy it over from the request. */ if (request->packet->code != PW_CODE_AUTHENTICATION_REQUEST) return RLM_MODULE_NOOP; if (!pairfind(request->proxy->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) return RLM_MODULE_NOOP; vp = radius_paircreate(request, &request->proxy->vps, PW_CHAP_CHALLENGE, 0); if (!vp) return RLM_MODULE_FAIL; pairmemcpy(vp, request->packet->vector, sizeof(request->packet->vector)); return RLM_MODULE_OK; }
VALUE_PAIR *eap_packet2vp(RADIUS_PACKET *packet, eap_packet_raw_t const *eap) { int total, size; uint8_t const *ptr; VALUE_PAIR *head = NULL; VALUE_PAIR *vp; vp_cursor_t out; total = eap->length[0] * 256 + eap->length[1]; if (total == 0) { DEBUG("Asked to encode empty EAP-Message!"); return NULL; } ptr = (uint8_t const *) eap; fr_cursor_init(&out, &head); do { size = total; if (size > 253) size = 253; vp = paircreate(packet, PW_EAP_MESSAGE, 0); if (!vp) { pairfree(&head); return NULL; } pairmemcpy(vp, ptr, size); fr_cursor_insert(&out, vp); ptr += size; total -= size; } while (total > 0); return head; }
/* * * Verify that a Perl SV is a string and save it in FreeRadius * Value Pair Format * */ static int pairadd_sv(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, char *key, SV *sv, FR_TOKEN op, const char *hashname, const char *list_name) { char *val; VALUE_PAIR *vp; if (SvOK(sv)) { STRLEN len; val = SvPV(sv, len); vp = pairmake(ctx, vps, key, NULL, op); if (!vp) { fail: REDEBUG("Failed to create pair %s:%s %s %s", list_name, key, fr_int2str(fr_tokens, op, "<INVALID>"), val); return -1; } switch (vp->da->type) { case PW_TYPE_STRING: pairstrncpy(vp, val, len); break; case PW_TYPE_OCTETS: pairmemcpy(vp, (uint8_t const *)val, len); break; default: if (pairparsevalue(vp, val, len) < 0) goto fail; } RDEBUG("&%s:%s %s $%s{'%s'} -> '%s'", list_name, key, fr_int2str(fr_tokens, op, "<INVALID>"), hashname, key, val); return 0; } return -1; }
/* * Copy data from src to dst, where the attributes are of * different type. */ static bool do_cast_copy(VALUE_PAIR *dst, VALUE_PAIR const *src) { rad_assert(dst->da->type != src->da->type); if (dst->da->type == PW_TYPE_STRING) { dst->vp_strvalue = vp_aprint(dst, src); dst->length = strlen(dst->vp_strvalue); return true; } if (dst->da->type == PW_TYPE_OCTETS) { if (src->da->type == PW_TYPE_STRING) { pairmemcpy(dst, src->vp_octets, src->length); /* Copy embedded NULLs */ } else { pairmemcpy(dst, (uint8_t const *) &src->data, src->length); } return true; } if (src->da->type == PW_TYPE_STRING) { return pairparsevalue(dst, src->vp_strvalue); } if ((src->da->type == PW_TYPE_INTEGER64) && (dst->da->type == PW_TYPE_ETHERNET)) { uint8_t array[8]; uint64_t i; i = htonll(src->vp_integer64); memcpy(array, &i, 8); /* * For OUIs in the DB. */ if ((array[0] != 0) || (array[1] != 0)) return false; memcpy(&dst->vp_ether, &array[2], 6); dst->length = 6; return true; } /* * The attribute we've found has to have a size which is * compatible with the type of the destination cast. */ if ((src->length < dict_attr_sizes[dst->da->type][0]) || (src->length > dict_attr_sizes[dst->da->type][1])) { EVAL_DEBUG("Casted attribute is wrong size (%u)", (unsigned int) src->length); return false; } if (src->da->type == PW_TYPE_OCTETS) { switch (dst->da->type) { case PW_TYPE_INTEGER64: dst->vp_integer = ntohll(*(uint64_t const *) src->vp_octets); break; case PW_TYPE_INTEGER: case PW_TYPE_DATE: case PW_TYPE_SIGNED: dst->vp_integer = ntohl(*(uint32_t const *) src->vp_octets); break; case PW_TYPE_SHORT: dst->vp_integer = ntohs(*(uint16_t const *) src->vp_octets); break; case PW_TYPE_BYTE: dst->vp_integer = src->vp_octets[0]; break; default: memcpy(&dst->data, src->vp_octets, src->length); break; } dst->length = src->length; return true; } /* * Convert host order to network byte order. */ if ((dst->da->type == PW_TYPE_IPADDR) && ((src->da->type == PW_TYPE_INTEGER) || (src->da->type == PW_TYPE_DATE) || (src->da->type == PW_TYPE_SIGNED))) { dst->vp_ipaddr = htonl(src->vp_integer); } else if ((src->da->type == PW_TYPE_IPADDR) && ((dst->da->type == PW_TYPE_INTEGER) || (dst->da->type == PW_TYPE_DATE) || (dst->da->type == PW_TYPE_SIGNED))) { dst->vp_integer = htonl(src->vp_ipaddr); } else { /* they're of the same byte order */ memcpy(&dst->data, &src->data, src->length); } dst->length = src->length; return true; }
/* * Generate a challenge to be presented to the user. */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_otp_t *inst = (rlm_otp_t *) instance; char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */ int auth_type_found; /* Early exit if Auth-Type != inst->name */ { VALUE_PAIR *vp; auth_type_found = 0; vp = pairfind(request->config_items, PW_AUTHTYPE, 0, TAG_ANY); if (vp) { auth_type_found = 1; if (strcmp(vp->vp_strvalue, inst->name)) { return RLM_MODULE_NOOP; } } } /* The State attribute will be present if this is a response. */ if (pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY) != NULL) { DEBUG("rlm_otp: autz: Found response to Access-Challenge"); return RLM_MODULE_OK; } /* User-Name attribute required. */ if (!request->username) { RWDEBUG("Attribute \"User-Name\" " "required for authentication"); return RLM_MODULE_INVALID; } if (otp_pwe_present(request) == 0) { RWDEBUG("Attribute " "\"User-Password\" or equivalent required " "for authentication"); return RLM_MODULE_INVALID; } /* * We used to check for special "challenge" and "resync" passcodes * here, but these are complicated to explain and application is * limited. More importantly, since we've removed all actual OTP * code (now we ask otpd), it's awkward for us to support them. * Should the need arise to reinstate these options, the most * likely choice is to duplicate some otpd code here. */ if (inst->allow_sync && !inst->allow_async) { /* This is the token sync response. */ if (!auth_type_found) { pairmake_config("Auth-Type", inst->name, T_OP_EQ); } return RLM_MODULE_OK; } /* * Generate a random challenge. */ otp_async_challenge(challenge, inst->challenge_len); /* * Create the State attribute, which will be returned to * us along with the response. * * We will need this to verify the response. * * It must be hmac protected to prevent insertion of arbitrary * State by an inside attacker. * * If we won't actually use the State (server config doesn't * allow async), we just use a trivial State. * * We always create at least a trivial State, so mod_authorize() * can quickly pass on to mod_authenticate(). */ { int32_t now = htonl(time(NULL)); //!< Low-order 32 bits on LP64. char gen_state[OTP_MAX_RADSTATE_LEN]; size_t len; VALUE_PAIR *vp; len = otp_gen_state(gen_state, challenge, inst->challenge_len, 0, now, inst->hmac_key); vp = paircreate(request->reply, PW_STATE, 0); if (!vp) { return RLM_MODULE_FAIL; } pairmemcpy(vp, (uint8_t const *) gen_state, len); pairadd(&request->reply->vps, vp); } /* * Add the challenge to the reply. */ { VALUE_PAIR *vp; char *expanded = NULL; ssize_t len; /* * First add the internal OTP challenge attribute to * the reply list. */ vp = paircreate(request->reply, PW_OTP_CHALLENGE, 0); if (!vp) { return RLM_MODULE_FAIL; } pairstrcpy(vp, challenge); vp->op = T_OP_SET; pairadd(&request->reply->vps, vp); /* * Then add the message to the user to they known * what the challenge value is. */ len = radius_axlat(&expanded, request, inst->chal_prompt, NULL, NULL); if (len < 0) { return RLM_MODULE_FAIL; } vp = paircreate(request->reply, PW_REPLY_MESSAGE, 0); if (!vp) { talloc_free(expanded); return RLM_MODULE_FAIL; } (void) talloc_steal(vp, expanded); vp->vp_strvalue = expanded; vp->length = len; vp->op = T_OP_SET; vp->type = VT_DATA; pairadd(&request->reply->vps, vp); } /* * Mark the packet as an Access-Challenge packet. * The server will take care of sending it to the user. */ request->reply->code = PW_CODE_ACCESS_CHALLENGE; DEBUG("rlm_otp: Sending Access-Challenge"); if (!auth_type_found) { pairmake_config("Auth-Type", inst->name, T_OP_EQ); } return RLM_MODULE_HANDLED; }
/* * filterBinary: * * This routine will call routines to parse entries from an ASCII format * to a binary format recognized by the Ascend boxes. * * pair: Pointer to value_pair to place return. * * valstr: The string to parse * * return: -1 for error or 0. */ int ascend_parse_filter(VALUE_PAIR *pair) { int token, type; int rcode; int argc; char *argv[32]; ascend_filter_t filter; char *p; rcode = -1; /* * Rather than printing specific error messages, we create * a general one here, which won't be used if the function * returns OK. */ fr_strerror_printf("Text is not in proper format"); /* * Tokenize the input string in the VP. * * Once the filter is *completelty* parsed, then we will * over-write it with the final binary filter. */ p = talloc_strdup(pair, pair->vp_strvalue); argc = str2argv(p, argv, 32); if (argc < 3) { talloc_free(p); return -1; } /* * Decide which filter type it is: ip, ipx, or generic */ type = fr_str2int(filterType, argv[0], -1); memset(&filter, 0, sizeof(filter)); /* * Validate the filter type. */ switch (type) { case RAD_FILTER_GENERIC: case RAD_FILTER_IP: case RAD_FILTER_IPX: filter.type = type; break; default: fr_strerror_printf("Unknown Ascend filter type \"%s\"", argv[0]); talloc_free(p); return -1; break; } /* * Parse direction */ token = fr_str2int(filterKeywords, argv[1], -1); switch (token) { case FILTER_IN: filter.direction = 1; break; case FILTER_OUT: filter.direction = 0; break; default: fr_strerror_printf("Unknown Ascend filter direction \"%s\"", argv[1]); talloc_free(p); return -1; break; } /* * Parse action */ token = fr_str2int(filterKeywords, argv[2], -1); switch (token) { case FILTER_FORWARD: filter.forward = 1; break; case FILTER_DROP: filter.forward = 0; break; default: fr_strerror_printf("Unknown Ascend filter action \"%s\"", argv[2]); talloc_free(p); return -1; break; } switch (type) { case RAD_FILTER_GENERIC: rcode = ascend_parse_generic(argc - 3, &argv[3], &filter.u.generic); break; case RAD_FILTER_IP: rcode = ascend_parse_ip(argc - 3, &argv[3], &filter.u.ip); break; case RAD_FILTER_IPX: rcode = ascend_parse_ipx(argc - 3, &argv[3], &filter.u.ipx); break; } /* * Touch the VP only if everything was OK. */ if (rcode == 0) { pair->length = sizeof(filter); memcpy(pair->vp_filter, &filter, sizeof(filter)); } talloc_free(p); return rcode; #if 0 /* * if 'more' is set then this new entry must exist, be a * FILTER_GENERIC_TYPE, direction and disposition must match for * the previous 'more' to be valid. If any should fail then TURN OFF * previous 'more' */ if( prevRadPair ) { filt = ( RadFilter * )prevRadPair->vp_strvalue; if(( tok != FILTER_GENERIC_TYPE ) || (rc == -1 ) || ( prevRadPair->attribute != pair->attribute ) || ( filt->indirection != radFil.indirection ) || ( filt->forward != radFil.forward ) ) { gen = &filt->u.generic; gen->more = false; fr_strerror_printf("filterBinary: 'more' for previous entry doesn't match: %s.\n", valstr); } } prevRadPair = NULL; if( rc != -1 && tok == FILTER_GENERIC_TYPE ) { if( radFil.u.generic.more ) { prevRadPair = pair; } } if( rc != -1 ) { pairmemcpy(pair, &radFil, pair->length ); } return(rc); #endif }
/* * 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 process_eap_challenge(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 = pairfind(req->vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC, 0, TAG_ANY); randvp= pairfind(req->vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_RAND, 0, TAG_ANY); if(!mac || !randvp) { fprintf(stderr, "radeapclient: 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] = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_RAND1, 0, TAG_ANY); randcfgvp[1] = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_RAND2, 0, TAG_ANY); randcfgvp[2] = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_RAND3, 0, TAG_ANY); if(!randcfgvp[0] || !randcfgvp[1] || !randcfgvp[2]) { fprintf(stderr, "radeapclient: needs to have rand1, 2 and 3 set.\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,j; fprintf(stderr, "radeapclient: one of rand 1,2,3 didn't match\n"); for(rnum = 0; rnum < 3; rnum++) { fprintf(stderr, "received rand %d: ", rnum); j=0; for (i = 0; i < EAPSIM_RAND_SIZE; i++) { if(j==4) { printf("_"); j=0; } j++; fprintf(stderr, "%02x", randcfg[rnum][i]); } fprintf(stderr, "\nconfigured rand %d: ", rnum); j=0; for (i = 0; i < EAPSIM_RAND_SIZE; i++) { if(j==4) { printf("_"); j=0; } j++; fprintf(stderr, "%02x", randcfgvp[rnum]->vp_octets[i]); } fprintf(stderr, "\n"); } 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 = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_SRES1, 0, TAG_ANY); sres2 = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_SRES2, 0, TAG_ANY); sres3 = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_SRES3, 0, TAG_ANY); if(!sres1 || !sres2 || !sres3) { fprintf(stderr, "radeapclient: needs to have sres1, 2 and 3 set.\n"); return 0; } memcpy(eapsim_mk.sres[0], sres1->vp_strvalue, sizeof(eapsim_mk.sres[0])); memcpy(eapsim_mk.sres[1], sres2->vp_strvalue, sizeof(eapsim_mk.sres[1])); memcpy(eapsim_mk.sres[2], sres3->vp_strvalue, sizeof(eapsim_mk.sres[2])); Kc1 = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_KC1, 0, TAG_ANY); Kc2 = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_KC2, 0, TAG_ANY); Kc3 = pairfind(rep->vps, ATTRIBUTE_EAP_SIM_KC3, 0, TAG_ANY); if(!Kc1 || !Kc2 || !Kc3) { fprintf(stderr, "radeapclient: needs to have Kc1, 2 and 3 set.\n"); return 0; } memcpy(eapsim_mk.Kc[0], Kc1->vp_strvalue, sizeof(eapsim_mk.Kc[0])); memcpy(eapsim_mk.Kc[1], Kc2->vp_strvalue, sizeof(eapsim_mk.Kc[1])); memcpy(eapsim_mk.Kc[2], Kc3->vp_strvalue, sizeof(eapsim_mk.Kc[2])); /* all set, calculate keys */ eapsim_calculate_keys(&eapsim_mk); if(debug_flag) { eapsim_dump_mk(&eapsim_mk); } /* verify the MAC, now that we have all the keys. */ if(eapsim_checkmac(NULL, req->vps, eapsim_mk.K_aut, eapsim_mk.nonce_mt, sizeof(eapsim_mk.nonce_mt), calcmac)) { printf("MAC check succeed\n"); } else { int i, j; j=0; printf("calculated MAC ("); for (i = 0; i < 20; i++) { if(j==4) { printf("_"); j=0; } j++; printf("%02x", calcmac[i]); } printf(" did not match\n"); return 0; } /* form new response clear of any EAP stuff */ cleanresp(rep); /* mark the subtype as being EAP-SIM/Response/Start */ newvp = paircreate(rep, ATTRIBUTE_EAP_SIM_SUBTYPE, 0); newvp->vp_integer = eapsim_challenge; pairreplace(&(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 = paircreate(rep, ATTRIBUTE_EAP_SIM_BASE+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); pairmemsteal(newvp, p); pairreplace(&(rep->vps), newvp); } newvp = paircreate(rep, ATTRIBUTE_EAP_SIM_KEY, 0); pairmemcpy(newvp, eapsim_mk.K_aut, EAPSIM_AUTH_SIZE); pairreplace(&(rep->vps), newvp); return 1; }
/* * Copy data from src to dst, where the attributes are of * different type. */ static int do_cast_copy(VALUE_PAIR *dst, VALUE_PAIR const *src) { rad_assert(dst->da->type != src->da->type); if (dst->da->type == PW_TYPE_STRING) { dst->vp_strvalue = vp_aprint_value(dst, src, false); dst->length = strlen(dst->vp_strvalue); return 0; } if (dst->da->type == PW_TYPE_OCTETS) { if (src->da->type == PW_TYPE_STRING) { pairmemcpy(dst, src->vp_octets, src->length); /* Copy embedded NULLs */ } else { pairmemcpy(dst, (uint8_t const *) &src->data, src->length); } return 0; } if (src->da->type == PW_TYPE_STRING) { return pairparsevalue(dst, src->vp_strvalue, 0); } if ((src->da->type == PW_TYPE_INTEGER64) && (dst->da->type == PW_TYPE_ETHERNET)) { uint8_t array[8]; uint64_t i; i = htonll(src->vp_integer64); memcpy(array, &i, 8); /* * For OUIs in the DB. */ if ((array[0] != 0) || (array[1] != 0)) return -1; memcpy(&dst->vp_ether, &array[2], 6); dst->length = 6; return 0; } /* * For integers, we allow the casting of a SMALL type to * a larger type, but not vice-versa. */ if (dst->da->type == PW_TYPE_INTEGER64) { switch (src->da->type) { case PW_TYPE_BYTE: dst->vp_integer64 = src->vp_byte; break; case PW_TYPE_SHORT: dst->vp_integer64 = src->vp_short; break; case PW_TYPE_INTEGER: dst->vp_integer64 = src->vp_integer; break; case PW_TYPE_OCTETS: goto do_octets; default: EVAL_DEBUG("Invalid cast to integer64"); return -1; } return 0; } /* * We can compare LONG integers to SHORTER ones, so long * as the long one is on the LHS. */ if (dst->da->type == PW_TYPE_INTEGER) { switch (src->da->type) { case PW_TYPE_BYTE: dst->vp_integer = src->vp_byte; break; case PW_TYPE_SHORT: dst->vp_integer = src->vp_short; break; case PW_TYPE_OCTETS: goto do_octets; default: EVAL_DEBUG("Invalid cast to integer"); return -1; } return 0; } if (dst->da->type == PW_TYPE_SHORT) { switch (src->da->type) { case PW_TYPE_BYTE: dst->vp_short = src->vp_byte; break; case PW_TYPE_OCTETS: goto do_octets; default: EVAL_DEBUG("Invalid cast to short"); return -1; } return 0; } /* * The attribute we've found has to have a size which is * compatible with the type of the destination cast. */ if ((src->length < dict_attr_sizes[dst->da->type][0]) || (src->length > dict_attr_sizes[dst->da->type][1])) { EVAL_DEBUG("Casted attribute is wrong size (%u)", (unsigned int) src->length); return -1; } if (src->da->type == PW_TYPE_OCTETS) { do_octets: switch (dst->da->type) { case PW_TYPE_INTEGER64: dst->vp_integer = ntohll(*(uint64_t const *) src->vp_octets); break; case PW_TYPE_INTEGER: case PW_TYPE_DATE: case PW_TYPE_SIGNED: dst->vp_integer = ntohl(*(uint32_t const *) src->vp_octets); break; case PW_TYPE_SHORT: dst->vp_integer = ntohs(*(uint16_t const *) src->vp_octets); break; case PW_TYPE_BYTE: dst->vp_integer = src->vp_octets[0]; break; default: memcpy(&dst->data, src->vp_octets, src->length); break; } dst->length = src->length; return 0; } /* * Convert host order to network byte order. */ if ((dst->da->type == PW_TYPE_IPV4_ADDR) && ((src->da->type == PW_TYPE_INTEGER) || (src->da->type == PW_TYPE_DATE) || (src->da->type == PW_TYPE_SIGNED))) { dst->vp_ipaddr = htonl(src->vp_integer); } else if ((src->da->type == PW_TYPE_IPV4_ADDR) && ((dst->da->type == PW_TYPE_INTEGER) || (dst->da->type == PW_TYPE_DATE) || (dst->da->type == PW_TYPE_SIGNED))) { dst->vp_integer = htonl(src->vp_ipaddr); } else { /* they're of the same byte order */ memcpy(&dst->data, &src->data, src->length); } dst->length = src->length; return 0; }
/* * Add a handler to the set of active sessions. * * Since we're adding it to the list, we guess that this means * the packet needs a State attribute. So add one. */ int eaplist_add(rlm_eap_t *inst, eap_handler_t *handler) { int status = 0; VALUE_PAIR *state; REQUEST *request = handler->request; rad_assert(handler != NULL); rad_assert(request != NULL); /* * Generate State, since we've been asked to add it to * the list. */ state = pairmake_reply("State", NULL, T_OP_EQ); if (!state) return 0; /* * The time at which this request was made was the time * at which it was received by the RADIUS server. */ handler->timestamp = request->timestamp; handler->status = 1; handler->src_ipaddr = request->packet->src_ipaddr; handler->eap_id = handler->eap_ds->request->id; /* * Playing with a data structure shared among threads * means that we need a lock, to avoid conflict. */ PTHREAD_MUTEX_LOCK(&(inst->session_mutex)); /* * If we have a DoS attack, discard new sessions. */ if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) { status = -1; eaplist_expire(inst, request, handler->timestamp); goto done; } /* * Create a unique content for the State variable. * It will be modified slightly per round trip, but less so * than in 1.x. */ if (handler->trips == 0) { int i; for (i = 0; i < 4; i++) { uint32_t lvalue; lvalue = eap_rand(&inst->rand_pool); memcpy(handler->state + i * 4, &lvalue, sizeof(lvalue)); } } /* * Add some more data to distinguish the sessions. */ handler->state[4] = handler->trips ^ handler->state[0]; handler->state[5] = handler->eap_id ^ handler->state[1]; handler->state[6] = handler->type ^ handler->state[2]; pairmemcpy(state, handler->state, sizeof(handler->state)); /* * Big-time failure. */ status = rbtree_insert(inst->session_tree, handler); /* * Catch Access-Challenge without response. */ if (inst->handler_tree) { check_handler_t *check = rad_malloc(sizeof(*check)); check->inst = inst; check->handler = handler; check->trips = handler->trips; request_data_add(request, inst, 0, check, check_handler); } if (status) { eap_handler_t *prev; prev = inst->session_tail; if (prev) { prev->next = handler; handler->prev = prev; handler->next = NULL; inst->session_tail = handler; } else { inst->session_head = inst->session_tail = handler; handler->next = handler->prev = NULL; } } /* * Now that we've finished mucking with the list, * unlock it. */ done: /* * We don't need this any more. */ if (status > 0) handler->request = NULL; PTHREAD_MUTEX_UNLOCK(&(inst->session_mutex)); if (status <= 0) { pairfree(&state); if (status < 0) { static time_t last_logged = 0; if (last_logged < handler->timestamp) { last_logged = handler->timestamp; ERROR("rlm_eap (%s): Too many open sessions. Try increasing \"max_sessions\" " "in the EAP module configuration", inst->xlat_name); } } else { ERROR("rlm_eap (%s): Failed to store handler", inst->xlat_name); } return 0; } RDEBUG("New EAP session, adding 'State' attribute to reply 0x%02x%02x%02x%02x%02x%02x%02x%02x", state->vp_octets[0], state->vp_octets[1], state->vp_octets[2], state->vp_octets[3], state->vp_octets[4], state->vp_octets[5], state->vp_octets[6], state->vp_octets[7]); return 1; }
static int eap_example_server_step(EAP_HANDLER *handler) { int res, process = 0; REQUEST *request = handler->request; res = eap_server_sm_step(handler->server_ctx.eap); if (handler->server_ctx.eap_if->eapReq) { DEBUG("==> Request"); process = 1; handler->server_ctx.eap_if->eapReq = 0; } if (handler->server_ctx.eap_if->eapSuccess) { DEBUG("==> Success"); process = 1; res = 0; if (handler->server_ctx.eap_if->eapKeyAvailable) { int length = handler->server_ctx.eap_if->eapKeyDataLen; VALUE_PAIR *vp; if (length > 64) { length = 32; } else { length /= 2; /* * FIXME: Length is zero? */ } vp = pairmake_reply("MS-MPPE-Recv-Key", NULL, T_OP_EQ); if (vp) { pairmemcpy(vp, handler->server_ctx.eap_if->eapKeyData, length); } vp = pairmake_reply("MS-MPPE-Send-Key", NULL, T_OP_EQ); if (vp) { pairmemcpy(vp, handler->server_ctx.eap_if->eapKeyData + length, length); } } } if (handler->server_ctx.eap_if->eapFail) { DEBUG("==> Fail"); process = 1; } if (process) { if (wpabuf_head(handler->server_ctx.eap_if->eapReqData)) { if (!eap_req2vp(handler)) return -1; } else { return -1; } } return res; }
/* * given a radius request with an EAP-Message body, decode it specific * attributes. */ static void 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); /* nothing to do! */ if(!e) return; /* create EAP-ID and EAP-CODE attributes to start */ eap1 = paircreate(rep, ATTRIBUTE_EAP_ID, 0); eap1->vp_integer = e->id; pairadd(&(rep->vps), eap1); eap1 = paircreate(rep, ATTRIBUTE_EAP_CODE, 0); eap1->vp_integer = e->code; pairadd(&(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 += ATTRIBUTE_EAP_BASE; len -= 5; if (len > MAX_STRING_LEN) { len = MAX_STRING_LEN; } eap1 = paircreate(rep, type, 0); pairmemcpy(eap1, e->data + 1, len); pairadd(&(rep->vps), eap1); break; } talloc_free(e); return; }
/* * 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 process_eap_start(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; /* form new response clear of any EAP stuff */ cleanresp(rep); if((vp = pairfind(req->vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_VERSION_LIST, 0, TAG_ANY)) == NULL) { fprintf(stderr, "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->length < 4) { fprintf(stderr, "start message has illegal VERSION_LIST. Too short: %u\n", (unsigned int) 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->length <= (versioncount*2 + 2)) { fprintf(stderr, "start message is too short. Claimed %d versions does not fit in %u bytes\n", versioncount, (unsigned int) vp->length); return 0; } /* * record the versionlist for the MK calculation. */ eapsim_mk.versionlistlen = versioncount*2; memcpy(eapsim_mk.versionlist, (unsigned char const *)(versions+1), eapsim_mk.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) { fprintf(stderr, "eap-sim start message. No compatible version found. We need %d\n", EAP_SIM_VERSION); for(i=0; i < versioncount; i++) { fprintf(stderr, "\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 = pairfind(req->vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_ANY_ID_REQ, 0, TAG_ANY); fullauthidreq_vp = pairfind(req->vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_FULLAUTH_ID_REQ, 0, TAG_ANY); permanentidreq_vp = pairfind(req->vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_PERMANENT_ID_REQ, 0, TAG_ANY); if(!fullauthidreq_vp || anyidreq_vp != NULL || permanentidreq_vp != NULL) { fprintf(stderr, "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 = paircreate(rep, ATTRIBUTE_EAP_SIM_SUBTYPE, 0); newvp->vp_integer = eapsim_start; pairreplace(&(rep->vps), newvp); /* insert selected version into response. */ { uint16_t no_versions; no_versions = htons(selectedversion); newvp = paircreate(rep, ATTRIBUTE_EAP_SIM_BASE + PW_EAP_SIM_SELECTED_VERSION, 0); pairmemcpy(newvp, (uint8_t *) &no_versions, 2); pairreplace(&(rep->vps), newvp); /* record the selected version */ memcpy(eapsim_mk.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 = paircreate(rep, ATTRIBUTE_EAP_SIM_BASE+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); pairmemsteal(newvp, p); pairreplace(&(rep->vps), newvp); /* also keep a copy of the nonce! */ memcpy(eapsim_mk.nonce_mt, nonce, 16); } { uint16_t idlen; uint8_t *p; uint16_t no_idlen; /* * insert the identity here. */ vp = pairfind(rep->vps, PW_USER_NAME, 0, TAG_ANY); if(!vp) { fprintf(stderr, "eap-sim: We need to have a User-Name attribute!\n"); return 0; } newvp = paircreate(rep, ATTRIBUTE_EAP_SIM_BASE+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); pairmemsteal(newvp, p); pairreplace(&(rep->vps), newvp); /* record it */ memcpy(eapsim_mk.identity, vp->vp_strvalue, idlen); eapsim_mk.identitylen = idlen; } return 1; }
/** Decrypt a Yubikey OTP AES block * * @param inst Module configuration. * @param passcode string to decrypt. * @return one of the RLM_RCODE_* constants. */ rlm_rcode_t rlm_yubikey_decrypt(rlm_yubikey_t *inst, REQUEST *request, char const *passcode) { uint32_t counter; yubikey_token_st token; DICT_ATTR const *da; char private_id[(YUBIKEY_UID_SIZE * 2) + 1]; VALUE_PAIR *key, *vp; da = dict_attrbyname("Yubikey-Key"); if (!da) { REDEBUG("Dictionary missing entry for 'Yubikey-Key'"); return RLM_MODULE_FAIL; } key = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY); if (!key) { REDEBUG("Yubikey-Key attribute not found in control list, can't decrypt OTP data"); return RLM_MODULE_INVALID; } if (key->length != YUBIKEY_KEY_SIZE) { REDEBUG("Yubikey-Key length incorrect, expected %u got %zu", YUBIKEY_KEY_SIZE, key->length); return RLM_MODULE_INVALID; } yubikey_parse((uint8_t const *) passcode + inst->id_len, key->vp_octets, &token); /* * Apparently this just uses byte offsets... */ if (!yubikey_crc_ok_p((uint8_t *) &token)) { REDEBUG("Decrypting OTP token data failed, rejecting"); return RLM_MODULE_REJECT; } RDEBUG("Token data decrypted successfully"); if (request->log.lvl && request->log.func) { (void) fr_bin2hex((char *) &private_id, (uint8_t*) &token.uid, YUBIKEY_UID_SIZE); RDEBUG2("Private ID : 0x%s", private_id); RDEBUG2("Session counter : %u", yubikey_counter(token.ctr)); RDEBUG2("# used in session : %u", token.use); RDEBUG2("Token timestamp : %u", (token.tstph << 16) | token.tstpl); RDEBUG2("Random data : %u", token.rnd); RDEBUG2("CRC data : 0x%x", token.crc); } /* * Private ID used for validation purposes */ vp = pairmake(request, &request->packet->vps, "Yubikey-Private-ID", NULL, T_OP_SET); if (!vp) { REDEBUG("Failed creating Yubikey-Private-ID"); return RLM_MODULE_FAIL; } pairmemcpy(vp, token.uid, YUBIKEY_UID_SIZE); /* * Token timestamp */ vp = pairmake(request, &request->packet->vps, "Yubikey-Timestamp", NULL, T_OP_SET); if (!vp) { REDEBUG("Failed creating Yubikey-Timestamp"); return RLM_MODULE_FAIL; } vp->vp_integer = (token.tstph << 16) | token.tstpl; vp->length = 4; /* * Token random */ vp = pairmake(request, &request->packet->vps, "Yubikey-Random", NULL, T_OP_SET); if (!vp) { REDEBUG("Failed creating Yubikey-Random"); return RLM_MODULE_FAIL; } vp->vp_integer = token.rnd; vp->length = 4; /* * Combine the two counter fields together so we can do * replay attack checks. */ counter = (yubikey_counter(token.ctr) << 16) | token.use; vp = pairmake(request, &request->packet->vps, "Yubikey-Counter", NULL, T_OP_SET); if (!vp) { REDEBUG("Failed creating Yubikey-Counter"); return RLM_MODULE_FAIL; } vp->vp_integer = counter; vp->length = 4; /* * Now we check for replay attacks */ vp = pairfind(request->config_items, vp->da->attr, vp->da->vendor, TAG_ANY); if (!vp) { RWDEBUG("Yubikey-Counter not found in control list, skipping replay attack checks"); return RLM_MODULE_OK; } if (counter <= vp->vp_integer) { REDEBUG("Replay attack detected! Counter value %u, is lt or eq to last known counter value %u", counter, vp->vp_integer); return RLM_MODULE_REJECT; } return RLM_MODULE_OK; }
/** Send the challenge itself * * Challenges will come from one of three places eventually: * * 1 from attributes like PW_EAP_SIM_RANDx * (these might be retrived 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_sim_sendchallenge(eap_handler_t *handler) { REQUEST *request = handler->request; eap_sim_state_t *ess; VALUE_PAIR **invps, **outvps, *newvp; RADIUS_PACKET *packet; uint8_t *p; ess = (eap_sim_state_t *)handler->opaque; rad_assert(handler->request != NULL); rad_assert(handler->request->reply); /* * Invps is the data from the client but this is for non-protocol data here. * We should already have consumed any client originated data. */ invps = &handler->request->packet->vps; /* * Outvps is the data to the client */ packet = handler->request->reply; outvps = &packet->vps; if (RDEBUG_ENABLED2) { RDEBUG2("EAP-SIM decoded packet:"); debug_pair_list(*invps); } /* * Okay, we got the challenges! Put them into an attribute. */ newvp = paircreate(packet, PW_EAP_SIM_RAND, 0); newvp->length = 2 + (EAPSIM_RAND_SIZE * 3); newvp->vp_octets = p = talloc_array(newvp, uint8_t, newvp->length); memset(p, 0, 2); /* clear reserved bytes */ p += 2; memcpy(p, ess->keys.rand[0], EAPSIM_RAND_SIZE); p += EAPSIM_RAND_SIZE; memcpy(p, ess->keys.rand[1], EAPSIM_RAND_SIZE); p += EAPSIM_RAND_SIZE; memcpy(p, ess->keys.rand[2], EAPSIM_RAND_SIZE); pairadd(outvps, newvp); /* * Set the EAP_ID - new value */ newvp = paircreate(packet, PW_EAP_ID, 0); newvp->vp_integer = ess->sim_id++; pairreplace(outvps, newvp); /* * Make a copy of the identity */ ess->keys.identitylen = strlen(handler->identity); memcpy(ess->keys.identity, handler->identity, ess->keys.identitylen); /* * Use the SIM identity, if available */ newvp = pairfind(*invps, PW_EAP_SIM_IDENTITY, 0, TAG_ANY); if (newvp && newvp->length > 2) { uint16_t len; memcpy(&len, newvp->vp_octets, sizeof(uint16_t)); len = ntohs(len); if (len <= newvp->length - 2 && len <= MAX_STRING_LEN) { ess->keys.identitylen = len; memcpy(ess->keys.identity, newvp->vp_octets + 2, ess->keys.identitylen); } } /* * All set, calculate keys! */ eapsim_calculate_keys(&ess->keys); #ifdef EAP_SIM_DEBUG_PRF eapsim_dump_mk(&ess->keys); #endif /* * Need to include an AT_MAC attribute so that it will get * calculated. The NONCE_MT and the MAC are both 16 bytes, so * We store the NONCE_MT in the MAC for the encoder, which * will pull it out before it does the operation. */ newvp = paircreate(packet, PW_EAP_SIM_MAC, 0); pairmemcpy(newvp, ess->keys.nonce_mt, 16); pairreplace(outvps, newvp); newvp = paircreate(packet, PW_EAP_SIM_KEY, 0); pairmemcpy(newvp, ess->keys.K_aut, 16); pairreplace(outvps, newvp); /* the SUBTYPE, set to challenge. */ newvp = paircreate(packet, PW_EAP_SIM_SUBTYPE, 0); newvp->vp_integer = EAPSIM_CHALLENGE; pairreplace(outvps, newvp); return 1; }
/** * @brief Parses the MS-SOH type/value (note: NOT type/length/value) data and * update the sohvp list * * See section 2.2.4 of MS-SOH. Because there's no "length" field we CANNOT just skip * unknown types; we need to know their length ahead of time. Therefore, we abort * if we find an unknown type. Note that sohvp may still have been modified in the * failure case. * * @param request Current request * @param p binary blob * @param data_len length of blob * @return 1 on success, 0 on failure */ static int eapsoh_mstlv(REQUEST *request, uint8_t const *p, unsigned int data_len) { VALUE_PAIR *vp; uint8_t c; int t; char *q; while (data_len > 0) { c = *p++; data_len--; switch (c) { case 1: /* MS-Machine-Inventory-Packet * MS-SOH section 2.2.4.1 */ if (data_len < 18) { RDEBUG("insufficient data for MS-Machine-Inventory-Packet"); return 0; } data_len -= 18; vp = pairmake_packet("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ); if (!vp) return 0; vp = pairmake_packet("SoH-MS-Machine-OS-version", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_32(p); p+=4; vp = pairmake_packet("SoH-MS-Machine-OS-release", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_32(p); p+=4; vp = pairmake_packet("SoH-MS-Machine-OS-build", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_32(p); p+=4; vp = pairmake_packet("SoH-MS-Machine-SP-version", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_16(p); p+=2; vp = pairmake_packet("SoH-MS-Machine-SP-release", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_16(p); p+=2; vp = pairmake_packet("SoH-MS-Machine-Processor", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = soh_pull_be_16(p); p+=2; break; case 2: /* MS-Quarantine-State - FIXME: currently unhandled * MS-SOH 2.2.4.1 * * 1 byte reserved * 1 byte flags * 8 bytes NT Time field (100-nanosec since 1 Jan 1601) * 2 byte urilen * N bytes uri */ p += 10; t = soh_pull_be_16(p); /* t == uri len */ p += 2; p += t; data_len -= 12 + t; break; case 3: /* MS-Packet-Info * MS-SOH 2.2.4.3 */ RDEBUG3("SoH MS-Packet-Info %s vers=%i", *p & 0x10 ? "request" : "response", *p & 0xf); p++; data_len--; break; case 4: /* MS-SystemGenerated-Ids - FIXME: currently unhandled * MS-SOH 2.2.4.4 * * 2 byte length * N bytes (3 bytes IANA enterprise# + 1 byte component id#) */ t = soh_pull_be_16(p); p += 2; p += t; data_len -= 2 + t; break; case 5: /* MS-MachineName * MS-SOH 2.2.4.5 * * 1 byte namelen * N bytes name */ t = soh_pull_be_16(p); p += 2; vp = pairmake_packet("SoH-MS-Machine-Name", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_strvalue = q = talloc_array(vp, char, t); vp->type = VT_DATA; memcpy(q, p, t); q[t] = 0; p += t; data_len -= 2 + t; break; case 6: /* MS-CorrelationId * MS-SOH 2.2.4.6 * * 24 bytes opaque binary which we might, in future, have * to echo back to the client in a final SoHR */ vp = pairmake_packet("SoH-MS-Correlation-Id", NULL, T_OP_EQ); if (!vp) return 0; pairmemcpy(vp, p, 24); p += 24; data_len -= 24; break; case 7: /* MS-Installed-Shvs - FIXME: currently unhandled * MS-SOH 2.2.4.7 * * 2 bytes length * N bytes (3 bytes IANA enterprise# + 1 byte component id#) */ t = soh_pull_be_16(p); p += 2; p += t; data_len -= 2 + t; break; case 8: /* MS-Machine-Inventory-Ex * MS-SOH 2.2.4.8 * * 4 bytes reserved * 1 byte product type (client=1 domain_controller=2 server=3) */ p += 4; vp = pairmake_packet("SoH-MS-Machine-Role", NULL, T_OP_EQ); if (!vp) return 0; vp->vp_integer = *p; p++; data_len -= 5; break; default: RDEBUG("SoH Unknown MS TV %i stopping", c); return 0; } } return 1; }
/* * Preprocess a request. */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { int r; rlm_preprocess_t *inst = instance; /* * Mangle the username, to get rid of stupid implementation * bugs. */ rad_mangle(inst, request); if (inst->with_ascend_hack) { /* * If we're using Ascend systems, hack the NAS-Port-Id * in place, to go from Ascend's weird values to something * approaching rationality. */ ascend_nasport_hack(pairfind(request->packet->vps, PW_NAS_PORT, 0, TAG_ANY), inst->ascend_channels_per_line); } if (inst->with_cisco_vsa_hack) { /* * We need to run this hack because the h323-conf-id * attribute should be used. */ cisco_vsa_hack(request); } if (inst->with_alvarion_vsa_hack) { /* * We need to run this hack because the Alvarion * people are crazy. */ alvarion_vsa_hack(request->packet->vps); } if (inst->with_cablelabs_vsa_hack) { /* * We need to run this hack because the Cablelabs * people are crazy. */ cablelabs_vsa_hack(&request->packet->vps); } /* * Note that we add the Request-Src-IP-Address to the request * structure BEFORE checking huntgroup access. This allows * the Request-Src-IP-Address to be used for huntgroup * comparisons. */ if (add_nas_attr(request) < 0) { return RLM_MODULE_FAIL; } hints_setup(inst->hints, request); /* * If there is a PW_CHAP_PASSWORD attribute but there * is PW_CHAP_CHALLENGE we need to add it so that other * modules can use it as a normal attribute. */ if (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) && pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL) { VALUE_PAIR *vp; vp = radius_paircreate(request->packet, &request->packet->vps, PW_CHAP_CHALLENGE, 0); pairmemcpy(vp, request->packet->vector, AUTH_VECTOR_LEN); } if ((r = huntgroup_access(request, inst->huntgroups)) != RLM_MODULE_OK) { char buf[1024]; RIDEBUG("No huntgroup access: [%s] (%s)", request->username ? request->username->vp_strvalue : "<NO User-Name>", auth_name(buf, sizeof(buf), request, 1)); return r; } return RLM_MODULE_OK; /* Meaning: try next authorization module */ }
/* * Authenticate a previously sent challenge. */ static int mschapv2_authenticate(void *arg, eap_handler_t *handler) { int rcode, ccode; uint8_t *p; mschapv2_opaque_t *data; EAP_DS *eap_ds = handler->eap_ds; VALUE_PAIR *challenge, *response, *name; rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg; REQUEST *request = handler->request; rad_assert(request != NULL); rad_assert(handler->stage == AUTHENTICATE); data = (mschapv2_opaque_t *) handler->opaque; /* * Sanity check the response. */ if (eap_ds->response->length <= 5) { REDEBUG("corrupted data"); return 0; } ccode = eap_ds->response->type.data[0]; switch (data->code) { case PW_EAP_MSCHAPV2_FAILURE: if (ccode == PW_EAP_MSCHAPV2_RESPONSE) { RDEBUG2("authentication re-try from client after we sent a failure"); break; } /* * if we sent error 648 (password expired) to the client * we might get an MSCHAP-CPW packet here; turn it into a * regular MS-CHAP2-CPW packet and pass it to rlm_mschap * (or proxy it, I guess) */ if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) { VALUE_PAIR *cpw; int mschap_id = eap_ds->response->type.data[1]; int copied=0,seq=1; RDEBUG2("password change packet received"); challenge = pairmake_packet("MS-CHAP-Challenge", NULL, T_OP_EQ); if (!challenge) { return 0; } pairmemcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN); cpw = pairmake_packet("MS-CHAP2-CPW", NULL, T_OP_EQ); cpw->length = 68; cpw->vp_octets = p = talloc_array(cpw, uint8_t, cpw->length); p[0] = 7; p[1] = mschap_id; memcpy(p + 2, eap_ds->response->type.data + 520, 66); /* * break the encoded password into VPs (3 of them) */ while (copied < 516) { VALUE_PAIR *nt_enc; int to_copy = 516 - copied; if (to_copy > 243) to_copy = 243; nt_enc = pairmake_packet("MS-CHAP-NT-Enc-PW", NULL, T_OP_ADD); nt_enc->length = 4 + to_copy; nt_enc->vp_octets = p = talloc_array(nt_enc, uint8_t, nt_enc->length); p[0] = 6; p[1] = mschap_id; p[2] = 0; p[3] = seq++; memcpy(p + 4, eap_ds->response->type.data + 4 + copied, to_copy); copied += to_copy; } RDEBUG2("built change password packet"); debug_pair_list(request->packet->vps); /* * jump to "authentication" */ goto packet_ready; } /* * we sent a failure and are expecting a failure back */ if (ccode != PW_EAP_MSCHAPV2_FAILURE) { REDEBUG("Sent FAILURE expecting FAILURE but got %d", ccode); return 0; } failure: request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; eap_ds->request->code = PW_EAP_FAILURE; return 1; case PW_EAP_MSCHAPV2_SUCCESS: /* * we sent a success to the client; some clients send a * success back as-per the RFC, some send an ACK. Permit * both, I guess... */ switch (ccode) { case PW_EAP_MSCHAPV2_SUCCESS: eap_ds->request->code = PW_EAP_SUCCESS; pairfilter(request->reply, &request->reply->vps, &data->mppe_keys, 0, 0, TAG_ANY); /* fall through... */ case PW_EAP_MSCHAPV2_ACK: #ifdef WITH_PROXY /* * It's a success. Don't proxy it. */ request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; #endif pairfilter(request->reply, &request->reply->vps, &data->reply, 0, 0, TAG_ANY); return 1; } REDEBUG("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode); return 0; case PW_EAP_MSCHAPV2_CHALLENGE: if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure; /* * we sent a challenge, expecting a response */ if (ccode != PW_EAP_MSCHAPV2_RESPONSE) { REDEBUG("Sent CHALLENGE expecting RESPONSE but got %d", ccode); return 0; } /* authentication happens below */ break; default: /* should never happen */ REDEBUG("unknown state %d", data->code); return 0; } /* * Ensure that we have at least enough data * to do the following checks. * * EAP header (4), EAP type, MS-CHAP opcode, * MS-CHAP ident, MS-CHAP data length (2), * MS-CHAP value length. */ if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) { REDEBUG("Response is too short"); return 0; } /* * The 'value_size' is the size of the response, * which is supposed to be the response (48 * bytes) plus 1 byte of flags at the end. */ if (eap_ds->response->type.data[4] != 49) { REDEBUG("Response is of incorrect length %d", eap_ds->response->type.data[4]); return 0; } /* * The MS-Length field is 5 + value_size + length * of name, which is put after the response. */ if (((eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3]) < (5 + 49)) { REDEBUG("Response contains contradictory length %d %d", (eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3], 5 + 49); return 0; } /* * We now know that the user has sent us a response * to the challenge. Let's try to authenticate it. * * We do this by taking the challenge from 'data', * the response from the EAP packet, and creating VALUE_PAIR's * to pass to the 'mschap' module. This is a little wonky, * but it works. */ challenge = pairmake_packet("MS-CHAP-Challenge", NULL, T_OP_EQ); if (!challenge) { return 0; } pairmemcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN); response = pairmake_packet("MS-CHAP2-Response", NULL, T_OP_EQ); if (!response) { return 0; } response->length = MSCHAPV2_RESPONSE_LEN; response->vp_octets = p = talloc_array(response, uint8_t, response->length); p[0] = eap_ds->response->type.data[1]; p[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN]; memcpy(p + 2, &eap_ds->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2); name = pairmake_packet("MS-CHAP-User-Name", NULL, T_OP_EQ); if (!name) { return 0; } /* * MS-Length - MS-Value - 5. */ name->length = (((eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3]) - eap_ds->response->type.data[4] - 5); name->vp_octets = p = talloc_array(name, uint8_t, name->length + 1); memcpy(p, &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN], name->length); p[name->length] = '\0'; packet_ready: #ifdef WITH_PROXY /* * If this options is set, then we do NOT authenticate the * user here. Instead, now that we've added the MS-CHAP * attributes to the request, we STOP, and let the outer * tunnel code handle it. * * This means that the outer tunnel code will DELETE the * EAP attributes, and proxy the MS-CHAP attributes to a * home server. */ if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) { char *username = NULL; eap_tunnel_data_t *tunnel; RDEBUG2("cancelling authentication and letting it be proxied"); /* * Set up the callbacks for the tunnel */ tunnel = talloc_zero(request, eap_tunnel_data_t); tunnel->tls_session = arg; tunnel->callback = mschap_postproxy; /* * Associate the callback with the request. */ rcode = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, NULL); rad_assert(rcode == 0); /* * The State attribute is NOT supposed to * go into the proxied packet, it will confuse * other RADIUS servers, and they will discard * the request. * * The PEAP module will take care of adding * the State attribute back, before passing * the handler & request back into the tunnel. */ pairdelete(&request->packet->vps, PW_STATE, 0, TAG_ANY); /* * Fix the User-Name when proxying, to strip off * the NT Domain, if we're told to, and a User-Name * exists, and there's a \\, meaning an NT-Domain * in the user name, THEN discard the user name. */ if (inst->with_ntdomain_hack && ((challenge = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) && ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) { /* * Wipe out the NT domain. * * FIXME: Put it into MS-CHAP-Domain? */ username++; /* skip the \\ */ pairstrcpy(challenge, username); } /* * Remember that in the post-proxy stage, we've got * to do the work below, AFTER the call to MS-CHAP * authentication... */ return 1; } #endif /* * This is a wild & crazy hack. */ rcode = process_authenticate(PW_AUTHTYPE_MS_CHAP, request); /* * Delete MPPE keys & encryption policy. We don't * want these here. */ fix_mppe_keys(handler, data); /* * Take the response from the mschap module, and * return success or failure, depending on the result. */ response = NULL; if (rcode == RLM_MODULE_OK) { pairfilter(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY); data->code = PW_EAP_MSCHAPV2_SUCCESS; } else if (inst->send_error) { pairfilter(data, &response, &request->reply->vps, PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY); if (response) { int n,err,retry; char buf[34]; RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue); /* * Pxarse the new challenge out of the * MS-CHAP-Error, so that if the client * issues a re-try, we will know which * challenge value that they used. */ n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]); if (n == 3) { DEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf); fr_hex2bin(buf, data->challenge, 16); } else { DEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n); } } data->code = PW_EAP_MSCHAPV2_FAILURE; } else { eap_ds->request->code = PW_EAP_FAILURE; return 1; } /* * No response, die. */ if (!response) { REDEBUG("No MS-CHAP-Success or MS-CHAP-Error was found."); return 0; } /* * Compose the response (whatever it is), * and return it to the over-lying EAP module. */ eapmschapv2_compose(handler, response); pairfree(&response); return 1; }