/* * Decode ONE value into a VP */ static ssize_t decode_value_internal(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *da, uint8_t const *data, size_t data_len) { VALUE_PAIR *vp; uint8_t const *p = data; FR_PROTO_TRACE("%s called to parse %zu bytes", __FUNCTION__, data_len); FR_PROTO_HEX_DUMP(data, data_len, NULL); vp = fr_pair_afrom_da(ctx, da); if (!vp) return -1; /* * Unknown attributes always get converted to * octet types, so there's no way there could * be multiple attributes, so its safe to * steal the unknown attribute into the context * of the pair. */ if (da->flags.is_unknown) talloc_steal(vp, da); if (vp->da->type == FR_TYPE_STRING) { uint8_t const *q, *end; q = end = data + data_len; /* * Not allowed to be an array, copy the whole value */ if (!vp->da->flags.array) { fr_pair_value_bstrncpy(vp, (char const *)p, end - p); p = end; goto finish; } for (;;) { q = memchr(p, '\0', q - p); /* Malformed but recoverable */ if (!q) q = end; fr_pair_value_bstrncpy(vp, (char const *)p, q - p); p = q + 1; vp->vp_tainted = true; /* Need another VP for the next round */ if (p < end) { fr_cursor_append(cursor, vp); vp = fr_pair_afrom_da(ctx, da); if (!vp) return -1; continue; } break; } goto finish; } switch (vp->da->type) { /* * Doesn't include scope, whereas the generic format can */ case FR_TYPE_IPV6_ADDR: memcpy(&vp->vp_ipv6addr, p, sizeof(vp->vp_ipv6addr)); vp->vp_ip.af = AF_INET6; vp->vp_ip.scope_id = 0; vp->vp_ip.prefix = 128; vp->vp_tainted = true; p += sizeof(vp->vp_ipv6addr); break; case FR_TYPE_IPV6_PREFIX: memcpy(&vp->vp_ipv6addr, p + 1, sizeof(vp->vp_ipv6addr)); vp->vp_ip.af = AF_INET6; vp->vp_ip.scope_id = 0; vp->vp_ip.prefix = p[0]; vp->vp_tainted = true; p += sizeof(vp->vp_ipv6addr) + 1; break; default: { ssize_t ret; ret = fr_value_box_from_network(vp, &vp->data, vp->da->type, da, p, data_len, true); if (ret < 0) { FR_PROTO_TRACE("decoding as unknown type"); if (fr_pair_to_unknown(vp) < 0) return -1; fr_pair_value_memcpy(vp, p, data_len); ret = data_len; } p += (size_t) ret; } } finish: FR_PROTO_TRACE("decoding value complete, adding new pair and returning %zu byte(s)", p - data); fr_cursor_append(cursor, vp); return p - data; }
static FR_CODE eap_fast_eap_payload(REQUEST *request, eap_session_t *eap_session, tls_session_t *tls_session, VALUE_PAIR *tlv_eap_payload) { FR_CODE code = FR_CODE_ACCESS_REJECT; rlm_rcode_t rcode; VALUE_PAIR *vp; eap_fast_tunnel_t *t; REQUEST *fake; RDEBUG2("Processing received EAP Payload"); /* * Allocate a fake REQUEST structure. */ fake = request_alloc_fake(request, NULL); rad_assert(!fake->packet->vps); t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); /* * Add the tunneled attributes to the fake request. */ fake->packet->vps = fr_pair_afrom_da(fake->packet, attr_eap_message); fr_pair_value_memcpy(fake->packet->vps, tlv_eap_payload->vp_octets, tlv_eap_payload->vp_length, false); RDEBUG2("Got tunneled request"); log_request_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); /* * Tell the request that it's a fake one. */ MEM(fr_pair_add_by_da(fake->packet, &vp, &fake->packet->vps, attr_freeradius_proxied_to) >= 0); fr_pair_value_from_str(vp, "127.0.0.1", sizeof("127.0.0.1"), '\0', false); /* * Update other items in the REQUEST data structure. */ fake->username = fr_pair_find_by_da(fake->packet->vps, attr_user_name, TAG_ANY); fake->password = fr_pair_find_by_da(fake->packet->vps, attr_user_password, TAG_ANY); /* * No User-Name, try to create one from stored data. */ if (!fake->username) { /* * No User-Name in the stored data, look for * an EAP-Identity, and pull it out of there. */ if (!t->username) { vp = fr_pair_find_by_da(fake->packet->vps, attr_eap_message, TAG_ANY); if (vp && (vp->vp_length >= EAP_HEADER_LEN + 2) && (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) && (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) && (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { /* * Create & remember a User-Name */ MEM(t->username = fr_pair_afrom_da(t, attr_user_name)); t->username->vp_tainted = true; fr_pair_value_bstrncpy(t->username, vp->vp_octets + 5, vp->vp_length - 5); RDEBUG2("Got tunneled identity of %pV", &t->username->data); } else { /* * Don't reject the request outright, * as it's permitted to do EAP without * user-name. */ RWDEBUG2("No EAP-Identity found to start EAP conversation"); } } /* else there WAS a t->username */ if (t->username) { vp = fr_pair_copy(fake->packet, t->username); fr_pair_add(&fake->packet->vps, vp); fake->username = vp; } } /* else the request ALREADY had a User-Name */ if (t->stage == EAP_FAST_AUTHENTICATION) { /* FIXME do this only for MSCHAPv2 */ VALUE_PAIR *tvp; tvp = fr_pair_afrom_da(fake, attr_eap_type); tvp->vp_uint32 = t->default_provisioning_method; fr_pair_add(&fake->control, tvp); /* * RFC 5422 section 3.2.3 - Authenticating Using EAP-FAST-MSCHAPv2 */ if (t->mode == EAP_FAST_PROVISIONING_ANON) { tvp = fr_pair_afrom_da(fake, attr_ms_chap_challenge); fr_pair_value_memcpy(tvp, t->keyblock->server_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, false); fr_pair_add(&fake->control, tvp); RHEXDUMP(L_DBG_LVL_MAX, t->keyblock->server_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, "MSCHAPv2 auth_challenge"); tvp = fr_pair_afrom_da(fake, attr_ms_chap_peer_challenge); fr_pair_value_memcpy(tvp, t->keyblock->client_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, false); fr_pair_add(&fake->control, tvp); RHEXDUMP(L_DBG_LVL_MAX, t->keyblock->client_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, "MSCHAPv2 peer_challenge"); } } /* * Call authentication recursively, which will * do PAP, CHAP, MS-CHAP, etc. */ eap_virtual_server(request, fake, eap_session, t->virtual_server); /* * Decide what to do with the reply. */ switch (fake->reply->code) { case 0: /* No reply code, must be proxied... */ #ifdef WITH_PROXY vp = fr_pair_find_by_da(fake->control, attr_proxy_to_realm, TAG_ANY); if (vp) { int ret; eap_tunnel_data_t *tunnel; RDEBUG2("Tunneled authentication will be proxied to %pV", &vp->data); /* * Tell the original request that it's going to be proxied. */ fr_pair_list_copy_by_da(request, &request->control, fake->control, attr_proxy_to_realm); /* * Seed the proxy packet with the tunneled request. */ rad_assert(!request->proxy); /* * FIXME: Actually proxy stuff */ request->proxy = request_alloc_fake(request, NULL); request->proxy->packet = talloc_steal(request->proxy, fake->packet); memset(&request->proxy->packet->src_ipaddr, 0, sizeof(request->proxy->packet->src_ipaddr)); memset(&request->proxy->packet->src_ipaddr, 0, sizeof(request->proxy->packet->src_ipaddr)); request->proxy->packet->src_port = 0; request->proxy->packet->dst_port = 0; fake->packet = NULL; fr_radius_packet_free(&fake->reply); fake->reply = NULL; /* * Set up the callbacks for the tunnel */ tunnel = talloc_zero(request, eap_tunnel_data_t); tunnel->tls_session = tls_session; /* * Associate the callback with the request. */ ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, false, false, false); fr_cond_assert(ret == 0); /* * rlm_eap.c has taken care of associating the eap_session * with the fake request. * * So we associate the fake request with this request. */ ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK, fake, true, false, false); fr_cond_assert(ret == 0); fake = NULL; /* * Didn't authenticate the packet, but we're proxying it. */ code = FR_CODE_STATUS_CLIENT; } else #endif /* WITH_PROXY */ { REDEBUG("No tunneled reply was found, and the request was not proxied: rejecting the user"); code = FR_CODE_ACCESS_REJECT; } break; default: /* * Returns RLM_MODULE_FOO, and we want to return FR_FOO */ rcode = process_reply(eap_session, tls_session, request, fake->reply); switch (rcode) { case RLM_MODULE_REJECT: code = FR_CODE_ACCESS_REJECT; break; case RLM_MODULE_HANDLED: code = FR_CODE_ACCESS_CHALLENGE; break; case RLM_MODULE_OK: code = FR_CODE_ACCESS_ACCEPT; break; default: code = FR_CODE_ACCESS_REJECT; break; } break; } talloc_free(fake); return code; }
/** Create any kind of VP from the attribute contents * * @param[in] ctx to allocate new attributes in. * @param[in] cursor to addd new attributes to. * @param[in] parent the current attribute we're processing. * @param[in] data to parse. Points to the data field of the attribute. * @param[in] attr_len length of the attribute being parsed. * @param[in] data_len length of the remaining data in the packet. * @param[in] decoder_ctx IVs, keys etc... * @return * - Length on success. * - -1 on failure. */ static ssize_t sim_decode_pair_value(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t const attr_len, size_t const data_len, void *decoder_ctx) { VALUE_PAIR *vp; uint8_t const *p = data; size_t prefix = 0; fr_sim_decode_ctx_t *packet_ctx = decoder_ctx; if (!fr_cond_assert(attr_len <= data_len)) return -1; if (!fr_cond_assert(parent)) return -1; FR_PROTO_TRACE("Parent %s len %zu", parent->name, attr_len); FR_PROTO_HEX_DUMP(data, attr_len, __FUNCTION__ ); FR_PROTO_TRACE("Type \"%s\" (%u)", fr_int2str(fr_value_box_type_table, parent->type, "?Unknown?"), parent->type); /* * Special cases, attributes that either have odd formats, or need * have information we need to decode the packet. */ switch (parent->attr) { /* * We need to record packet_ctx so we can decrypt AT_ENCR attributes. * * If we don't find it before, then that's fine, we'll try and * find it in the rest of the packet after the encrypted * attribute. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_IV | Length = 5 | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | Initialization Vector | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_SIM_IV: if (sim_iv_extract(&packet_ctx->iv[0], data, attr_len) < 0) return -1; packet_ctx->have_iv = true; break; /* Now create the attribute */ /* * AT_RES - Special case (RES length is in bits) * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_RES | Length | RES Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| * | | * | RES | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_EAP_AKA_RES: { uint16_t res_len; if (attr_len < 2) goto raw; /* Need at least two bytes for the length field */ res_len = (p[0] << 8) | p[1]; if (res_len % 8) { fr_strerror_printf("%s: RES Length (%hu) is not a multiple of 8", __FUNCTION__, res_len); return -1; } res_len /= 8; if (res_len > (attr_len - 2)) { fr_strerror_printf("%s: RES Length field value (%u bits) > attribute value length (%zu bits)", __FUNCTION__, res_len * 8, (attr_len - 2) * 8); return -1; } if ((res_len < 4) || (res_len > 16)) { fr_strerror_printf("%s: RES Length field value must be between 32-128 bits, got %u bits", __FUNCTION__, (res_len * 8)); return -1; } vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; fr_pair_value_memcpy(vp, p + 2, res_len, true); } goto done; /* * AT_CHECKCODE - Special case (Variable length with no length field) * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_CHECKCODE | Length | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | Checkcode (0 or 20 bytes) | * | | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_EAP_AKA_CHECKCODE: if (attr_len < 2) goto raw; /* Need at least two bytes for reserved field */ vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; fr_pair_value_memcpy(vp, p + 2, attr_len - 2, true); goto done; default: break; } switch (parent->type) { case FR_TYPE_STRING: if (attr_len < 2) goto raw; /* Need at least two bytes for the length field */ if (parent->flags.length && (attr_len != parent->flags.length)) { wrong_len: fr_strerror_printf("%s: Attribute \"%s\" needs a value of exactly %zu bytes, " "but value was %zu bytes", __FUNCTION__, parent->name, (size_t)parent->flags.length, attr_len); goto raw; } break; case FR_TYPE_OCTETS: /* * Get the number of bytes we expect before the value */ prefix = fr_sim_octets_prefix_len(parent); if (attr_len < prefix) goto raw; if (parent->flags.length && (attr_len != (parent->flags.length + prefix))) goto wrong_len; break; case FR_TYPE_BOOL: case FR_TYPE_UINT8: case FR_TYPE_UINT16: case FR_TYPE_UINT32: case FR_TYPE_UINT64: if (attr_len != fr_sim_attr_sizes[parent->type][0]) goto raw; break; case FR_TYPE_TLV: if (attr_len < 2) goto raw; /* * We presume that the TLVs all fit into one * attribute, OR they've already been grouped * into a contiguous memory buffer. */ return sim_decode_tlv(ctx, cursor, parent, p, attr_len, data_len, decoder_ctx); default: raw: /* * We can't create unknowns for non-skippable attributes * as we're prohibited from continuing by the SIM RFCs. */ if (parent->attr <= SIM_SKIPPABLE_MAX) { fr_strerror_printf_push("%s: Failed parsing non-skippable attribute '%s'", __FUNCTION__, parent->name); return -1; } #ifdef __clang_analyzer__ if (!parent->parent) return -1; /* stupid static analyzers */ #endif rad_assert(parent->parent); /* * Re-write the attribute to be "raw". It is * therefore of type "octets", and will be * handled below. */ parent = fr_dict_unknown_afrom_fields(ctx, parent->parent, fr_dict_vendor_num_by_da(parent), parent->attr); if (!parent) { fr_strerror_printf_push("%s[%d]: Internal sanity check failed", __FUNCTION__, __LINE__); return -1; } } vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; /* * For unknown attributes copy the entire value, not skipping * any reserved bytes. */ if (parent->flags.is_unknown || parent->flags.is_raw) { fr_pair_value_memcpy(vp, p, attr_len, true); vp->vp_length = attr_len; goto done; } switch (parent->type) { /* * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<STRING> | Length | Actual <STRING> Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * . String . * . . * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_TYPE_STRING: { uint16_t actual_len = (p[0] << 8) | p[1]; if (actual_len > (attr_len - 2)) { fr_strerror_printf("%s: Actual length field value (%hu) > attribute value length (%zu)", __FUNCTION__, actual_len, attr_len - 2); return -1; } fr_pair_value_bstrncpy(vp, p + 2, actual_len); } break; case FR_TYPE_OCTETS: /* * Variable length octets buffer */ if (!parent->flags.length) { uint16_t actual_len = (p[0] << 8) | p[1]; if (actual_len > (attr_len - prefix)) { fr_strerror_printf("%s: Actual length field value (%hu) > attribute value length (%zu)", __FUNCTION__, actual_len, attr_len - 2); return -1; } fr_pair_value_memcpy(vp, p + prefix, actual_len, true); /* * Fixed length octets buffer */ } else { fr_pair_value_memcpy(vp, p + prefix, attr_len - prefix, true); } break; /* * Not proper bool. We Use packet_ctx to represent * flag attributes like AT_FULLAUTH_ID_REQ * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<BOOL> | Length = 1 | Reserved | * +---------------+---------------+-------------------------------+ */ case FR_TYPE_BOOL: vp->vp_bool = true; break; /* * Numbers are network byte order. * * In the base RFCs only short (16bit) unsigned integers are used. * We add support for more, just for completeness. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<SHORT> | Length = 1 | Short 1 | Short 2 | * +---------------+---------------+-------------------------------+ */ case FR_TYPE_UINT8: vp->vp_uint8 = p[0]; break; case FR_TYPE_UINT16: memcpy(&vp->vp_uint16, p, sizeof(vp->vp_uint16)); vp->vp_uint16 = ntohs(vp->vp_uint32); break; case FR_TYPE_UINT32: memcpy(&vp->vp_uint32, p, sizeof(vp->vp_uint32)); vp->vp_uint32 = ntohl(vp->vp_uint32); break; case FR_TYPE_UINT64: memcpy(&vp->vp_uint64, p, sizeof(vp->vp_uint64)); vp->vp_uint64 = ntohll(vp->vp_uint64); break; default: fr_pair_list_free(&vp); fr_strerror_printf_push("%s[%d]: Internal sanity check failed", __FUNCTION__, __LINE__); return -1; } done: vp->type = VT_DATA; fr_cursor_append(cursor, vp); return attr_len; }
/* * Process the "diameter" contents of the tunneled data. */ PW_CODE eapttls_process(eap_handler_t *handler, tls_session_t *tls_session) { PW_CODE code = PW_CODE_ACCESS_REJECT; rlm_rcode_t rcode; REQUEST *fake; VALUE_PAIR *vp; ttls_tunnel_t *t; uint8_t const *data; size_t data_len; REQUEST *request = handler->request; chbind_packet_t *chbind; /* * Just look at the buffer directly, without doing * record_minus. */ data_len = tls_session->clean_out.used; tls_session->clean_out.used = 0; data = tls_session->clean_out.data; t = (ttls_tunnel_t *) tls_session->opaque; /* * If there's no data, maybe this is an ACK to an * MS-CHAP2-Success. */ if (data_len == 0) { if (t->authenticated) { RDEBUG("Got ACK, and the user was already authenticated"); return PW_CODE_ACCESS_ACCEPT; } /* else no session, no data, die. */ /* * FIXME: Call SSL_get_error() to see what went * wrong. */ RDEBUG2("SSL_read Error"); return PW_CODE_ACCESS_REJECT; } #ifndef NDEBUG if ((rad_debug_lvl > 2) && fr_log_fp) { size_t i; for (i = 0; i < data_len; i++) { if ((i & 0x0f) == 0) fprintf(fr_log_fp, " TTLS tunnel data in %04x: ", (int) i); fprintf(fr_log_fp, "%02x ", data[i]); if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); } if ((data_len & 0x0f) != 0) fprintf(fr_log_fp, "\n"); } #endif if (!diameter_verify(request, data, data_len)) { return PW_CODE_ACCESS_REJECT; } /* * Allocate a fake REQUEST structure. */ fake = request_alloc_fake(request); rad_assert(!fake->packet->vps); /* * Add the tunneled attributes to the fake request. */ fake->packet->vps = diameter2vp(request, fake, tls_session->ssl, data, data_len); if (!fake->packet->vps) { talloc_free(fake); return PW_CODE_ACCESS_REJECT; } /* * Tell the request that it's a fake one. */ pair_make_request("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ); RDEBUG("Got tunneled request"); rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); /* * Update other items in the REQUEST data structure. */ fake->username = fr_pair_find_by_num(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); fake->password = fr_pair_find_by_num(fake->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); /* * No User-Name, try to create one from stored data. */ if (!fake->username) { /* * No User-Name in the stored data, look for * an EAP-Identity, and pull it out of there. */ if (!t->username) { vp = fr_pair_find_by_num(fake->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY); if (vp && (vp->vp_length >= EAP_HEADER_LEN + 2) && (vp->vp_strvalue[0] == PW_EAP_RESPONSE) && (vp->vp_strvalue[EAP_HEADER_LEN] == PW_EAP_IDENTITY) && (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { /* * Create & remember a User-Name */ t->username = fr_pair_make(t, NULL, "User-Name", NULL, T_OP_EQ); rad_assert(t->username != NULL); fr_pair_value_bstrncpy(t->username, vp->vp_octets + 5, vp->vp_length - 5); RDEBUG("Got tunneled identity of %s", t->username->vp_strvalue); /* * If there's a default EAP type, * set it here. */ if (t->default_method != 0) { RDEBUG("Setting default EAP type for tunneled EAP session"); vp = fr_pair_afrom_num(fake, PW_EAP_TYPE, 0); rad_assert(vp != NULL); vp->vp_integer = t->default_method; fr_pair_add(&fake->config, vp); } } else { /* * Don't reject the request outright, * as it's permitted to do EAP without * user-name. */ RWDEBUG2("No EAP-Identity found to start EAP conversation"); } } /* else there WAS a t->username */ if (t->username) { vp = fr_pair_list_copy(fake->packet, t->username); fr_pair_add(&fake->packet->vps, vp); fake->username = fr_pair_find_by_num(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); } } /* else the request ALREADY had a User-Name */ /* * Add the State attribute, too, if it exists. */ if (t->state) { vp = fr_pair_list_copy(fake->packet, t->state); if (vp) fr_pair_add(&fake->packet->vps, vp); } /* * If this is set, we copy SOME of the request attributes * from outside of the tunnel to inside of the tunnel. * * We copy ONLY those attributes which do NOT already * exist in the tunneled request. */ if (t->copy_request_to_tunnel) { VALUE_PAIR *copy; vp_cursor_t cursor; for (vp = fr_cursor_init(&cursor, &request->packet->vps); vp; vp = fr_cursor_next(&cursor)) { /* * The attribute is a server-side thingy, * don't copy it. */ if ((vp->da->attr > 255) && (vp->da->vendor == 0)) { continue; } /* * The outside attribute is already in the * tunnel, don't copy it. * * This works for BOTH attributes which * are originally in the tunneled request, * AND attributes which are copied there * from below. */ if (fr_pair_find_by_da(fake->packet->vps, vp->da, TAG_ANY)) { continue; } /* * Some attributes are handled specially. */ switch (vp->da->attr) { /* * NEVER copy Message-Authenticator, * EAP-Message, or State. They're * only for outside of the tunnel. */ case PW_USER_NAME: case PW_USER_PASSWORD: case PW_CHAP_PASSWORD: case PW_CHAP_CHALLENGE: case PW_PROXY_STATE: case PW_MESSAGE_AUTHENTICATOR: case PW_EAP_MESSAGE: case PW_STATE: continue; /* * By default, copy it over. */ default: break; } /* * Don't copy from the head, we've already * checked it. */ copy = fr_pair_list_copy_by_num(fake->packet, vp, vp->da->attr, vp->da->vendor, TAG_ANY); fr_pair_add(&fake->packet->vps, copy); } } if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { fake->server = vp->vp_strvalue; } else if (t->virtual_server) { fake->server = t->virtual_server; } /* else fake->server == request->server */ if ((rad_debug_lvl > 0) && fr_log_fp) { RDEBUG("Sending tunneled request"); } /* * Process channel binding. */ chbind = eap_chbind_vp2packet(fake, fake->packet->vps); if (chbind) { PW_CODE chbind_code; CHBIND_REQ *req = talloc_zero(fake, CHBIND_REQ); RDEBUG("received chbind request"); req->request = chbind; if (fake->username) { req->username = fake->username; } else { req->username = NULL; } chbind_code = chbind_process(request, req); /* encapsulate response here */ if (req->response) { RDEBUG("sending chbind response"); fr_pair_add(&fake->reply->vps, eap_chbind_packet2vp(fake, req->response)); } else { RDEBUG("no chbind response"); } /* clean up chbind req */ talloc_free(req); if (chbind_code != PW_CODE_ACCESS_ACCEPT) { return chbind_code; } } /* * Call authentication recursively, which will * do PAP, CHAP, MS-CHAP, etc. */ rad_virtual_server(fake); /* * Decide what to do with the reply. */ switch (fake->reply->code) { case 0: /* No reply code, must be proxied... */ #ifdef WITH_PROXY vp = fr_pair_find_by_num(fake->config, PW_PROXY_TO_REALM, 0, TAG_ANY); if (vp) { eap_tunnel_data_t *tunnel; RDEBUG("Tunneled authentication will be proxied to %s", vp->vp_strvalue); /* * Tell the original request that it's going * to be proxied. */ fr_pair_list_move_by_num(request, &request->config, &fake->config, PW_PROXY_TO_REALM, 0, TAG_ANY); /* * Seed the proxy packet with the * tunneled request. */ rad_assert(!request->proxy); request->proxy = talloc_steal(request, fake->packet); memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); request->proxy->src_port = 0; request->proxy->dst_port = 0; fake->packet = NULL; rad_free(&fake->reply); fake->reply = NULL; /* * Set up the callbacks for the tunnel */ tunnel = talloc_zero(request, eap_tunnel_data_t); tunnel->tls_session = tls_session; tunnel->callback = eapttls_postproxy; /* * Associate the callback with the request. */ code = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, false); rad_assert(code == 0); /* * rlm_eap.c has taken care of associating * the handler with the fake request. * * So we associate the fake request with * this request. */ code = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK, fake, true); rad_assert(code == 0); fake = NULL; /* * Didn't authenticate the packet, but * we're proxying it. */ code = PW_CODE_STATUS_CLIENT; } else #endif /* WITH_PROXY */ { RDEBUG("No tunneled reply was found for request %d , and the request was not proxied: rejecting the user.", request->number); code = PW_CODE_ACCESS_REJECT; } break; default: /* * Returns RLM_MODULE_FOO, and we want to return PW_FOO */ rcode = process_reply(handler, tls_session, request, fake->reply); switch (rcode) { case RLM_MODULE_REJECT: code = PW_CODE_ACCESS_REJECT; break; case RLM_MODULE_HANDLED: code = PW_CODE_ACCESS_CHALLENGE; break; case RLM_MODULE_OK: code = PW_CODE_ACCESS_ACCEPT; break; default: code = PW_CODE_ACCESS_REJECT; break; } break; } talloc_free(fake); return code; }
static int mod_process(void *arg, eap_session_t *eap_session) { pwd_session_t *session; pwd_hdr *hdr; pwd_id_packet_t *packet; eap_packet_t *response; REQUEST *request, *fake; VALUE_PAIR *pw, *vp; eap_round_t *eap_round; size_t in_len; int ret = 0; eap_pwd_t *inst = (eap_pwd_t *)arg; uint16_t offset; uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN]; uint8_t peer_confirm[SHA256_DIGEST_LENGTH]; BIGNUM *x = NULL, *y = NULL; if (((eap_round = eap_session->this_round) == NULL) || !inst) return 0; session = (pwd_session_t *)eap_session->opaque; request = eap_session->request; response = eap_session->this_round->response; hdr = (pwd_hdr *)response->type.data; /* * The header must be at least one byte. */ if (!hdr || (response->type.length < sizeof(pwd_hdr))) { RDEBUG("Packet with insufficient data"); return 0; } in = hdr->data; in_len = response->type.length - sizeof(pwd_hdr); /* * see if we're fragmenting, if so continue until we're done */ if (session->out_pos) { if (in_len) RDEBUG2("pwd got something more than an ACK for a fragment"); return send_pwd_request(session, eap_round); } /* * the first fragment will have a total length, make a * buffer to hold all the fragments */ if (EAP_PWD_GET_LENGTH_BIT(hdr)) { if (session->in) { RDEBUG2("pwd already alloced buffer for fragments"); return 0; } if (in_len < 2) { RDEBUG("Invalid packet: length bit set, but no length field"); return 0; } session->in_len = ntohs(in[0] * 256 | in[1]); if ((session->in = talloc_zero_array(session, uint8_t, session->in_len)) == NULL) { RDEBUG2("pwd cannot allocate %zd buffer to hold fragments", session->in_len); return 0; } memset(session->in, 0, session->in_len); session->in_pos = 0; in += sizeof(uint16_t); in_len -= sizeof(uint16_t); } /* * all fragments, including the 1st will have the M(ore) bit set, * buffer those fragments! */ if (EAP_PWD_GET_MORE_BIT(hdr)) { rad_assert(session->in != NULL); if ((session->in_pos + in_len) > session->in_len) { RDEBUG2("Fragment overflows packet."); return 0; } memcpy(session->in + session->in_pos, in, in_len); session->in_pos += in_len; /* * send back an ACK for this fragment */ exch = EAP_PWD_GET_EXCHANGE(hdr); eap_round->request->code = PW_EAP_REQUEST; eap_round->request->type.num = PW_EAP_PWD; eap_round->request->type.length = sizeof(pwd_hdr); if ((eap_round->request->type.data = talloc_array(eap_round->request, uint8_t, sizeof(pwd_hdr))) == NULL) { return 0; } hdr = (pwd_hdr *)eap_round->request->type.data; EAP_PWD_SET_EXCHANGE(hdr, exch); return 1; } if (session->in) { /* * the last fragment... */ if ((session->in_pos + in_len) > session->in_len) { RDEBUG2("pwd will not overflow a fragment buffer. Nope, not prudent"); return 0; } memcpy(session->in + session->in_pos, in, in_len); in = session->in; in_len = session->in_len; } switch (session->state) { case PWD_STATE_ID_REQ: if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_ID) { RDEBUG2("pwd exchange is incorrect: not ID"); return 0; } packet = (pwd_id_packet_t *) in; if (in_len < sizeof(packet)) { RDEBUG("Packet is too small (%zd < %zd).", in_len, sizeof(packet)); return 0; } if ((packet->prf != EAP_PWD_DEF_PRF) || (packet->random_function != EAP_PWD_DEF_RAND_FUN) || (packet->prep != EAP_PWD_PREP_NONE) || (CRYPTO_memcmp(packet->token, &session->token, 4)) || (packet->group_num != ntohs(session->group_num))) { RDEBUG2("pwd id response is invalid"); return 0; } /* * we've agreed on the ciphersuite, record it... */ ptr = (uint8_t *)&session->ciphersuite; memcpy(ptr, (char *)&packet->group_num, sizeof(uint16_t)); ptr += sizeof(uint16_t); *ptr = EAP_PWD_DEF_RAND_FUN; ptr += sizeof(uint8_t); *ptr = EAP_PWD_DEF_PRF; session->peer_id_len = in_len - sizeof(pwd_id_packet_t); if (session->peer_id_len >= sizeof(session->peer_id)) { RDEBUG2("pwd id response is malformed"); return 0; } memcpy(session->peer_id, packet->identity, session->peer_id_len); session->peer_id[session->peer_id_len] = '\0'; /* * make fake request to get the password for the usable ID */ if ((fake = request_alloc_fake(eap_session->request)) == NULL) { RDEBUG("pwd unable to create fake request!"); return 0; } fake->username = fr_pair_afrom_num(fake, 0, PW_USER_NAME); if (!fake->username) { RDEBUG("Failed creating pair for peer id"); talloc_free(fake); return 0; } fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); fr_pair_add(&fake->packet->vps, fake->username); if ((vp = fr_pair_find_by_num(request->config, 0, PW_VIRTUAL_SERVER, TAG_ANY)) != NULL) { fake->server = vp->vp_strvalue; } else if (inst->virtual_server) { fake->server = inst->virtual_server; } /* else fake->server == request->server */ RDEBUG("Sending tunneled request"); rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); if (fake->server) { RDEBUG("server %s {", fake->server); } else { RDEBUG("server {"); } /* * Call authorization recursively, which will * get the password. */ RINDENT(); process_authorize(0, fake); REXDENT(); /* * Note that we don't do *anything* with the reply * attributes. */ if (fake->server) { RDEBUG("} # server %s", fake->server); } else { RDEBUG("}"); } RDEBUG("Got tunneled reply code %d", fake->reply->code); rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); if ((pw = fr_pair_find_by_num(fake->config, 0, PW_CLEARTEXT_PASSWORD, TAG_ANY)) == NULL) { DEBUG2("failed to find password for %s to do pwd authentication", session->peer_id); talloc_free(fake); return 0; } if (compute_password_element(session, session->group_num, pw->data.strvalue, strlen(pw->data.strvalue), inst->server_id, strlen(inst->server_id), session->peer_id, strlen(session->peer_id), &session->token)) { DEBUG2("failed to obtain password element"); talloc_free(fake); return 0; } TALLOC_FREE(fake); /* * compute our scalar and element */ if (compute_scalar_element(session, inst->bnctx)) { DEBUG2("failed to compute server's scalar and element"); return 0; } if (((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { DEBUG2("server point allocation failed"); return 0; } /* * element is a point, get both coordinates: x and y */ if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, inst->bnctx)) { DEBUG2("server point assignment failed"); BN_clear_free(x); BN_clear_free(y); return 0; } /* * construct request */ session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime)); if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) { return 0; } memset(session->out, 0, session->out_len); ptr = session->out; offset = BN_num_bytes(session->prime) - BN_num_bytes(x); BN_bn2bin(x, ptr + offset); ptr += BN_num_bytes(session->prime); offset = BN_num_bytes(session->prime) - BN_num_bytes(y); BN_bn2bin(y, ptr + offset); ptr += BN_num_bytes(session->prime); offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); BN_bn2bin(session->my_scalar, ptr + offset); session->state = PWD_STATE_COMMIT; ret = send_pwd_request(session, eap_round); break; case PWD_STATE_COMMIT: if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) { RDEBUG2("pwd exchange is incorrect: not commit!"); return 0; } /* * process the peer's commit and generate the shared key, k */ if (process_peer_commit(session, in, in_len, inst->bnctx)) { RDEBUG2("failed to process peer's commit"); return 0; } /* * compute our confirm blob */ if (compute_server_confirm(session, session->my_confirm, inst->bnctx)) { ERROR("rlm_eap_pwd: failed to compute confirm!"); return 0; } /* * construct a response...which is just our confirm blob */ session->out_len = SHA256_DIGEST_LENGTH; if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) return 0; memset(session->out, 0, session->out_len); memcpy(session->out, session->my_confirm, SHA256_DIGEST_LENGTH); session->state = PWD_STATE_CONFIRM; ret = send_pwd_request(session, eap_round); break; case PWD_STATE_CONFIRM: if (in_len < SHA256_DIGEST_LENGTH) { RDEBUG("Peer confirm is too short (%zd < %d)", in_len, SHA256_DIGEST_LENGTH); return 0; } if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_CONFIRM) { RDEBUG2("pwd exchange is incorrect: not commit!"); return 0; } if (compute_peer_confirm(session, peer_confirm, inst->bnctx)) { RDEBUG2("pwd exchange cannot compute peer's confirm"); return 0; } if (CRYPTO_memcmp(peer_confirm, in, SHA256_DIGEST_LENGTH)) { RDEBUG2("pwd exchange fails: peer confirm is incorrect!"); return 0; } if (compute_keys(session, peer_confirm, msk, emsk)) { RDEBUG2("pwd exchange cannot generate (E)MSK!"); return 0; } eap_round->request->code = PW_EAP_SUCCESS; /* * return the MSK (in halves) */ eap_add_reply(eap_session->request, "MS-MPPE-Recv-Key", msk, MPPE_KEY_LEN); eap_add_reply(eap_session->request, "MS-MPPE-Send-Key", msk + MPPE_KEY_LEN, MPPE_KEY_LEN); ret = 1; break; default: RDEBUG2("unknown PWD state"); return 0; } /* * we processed the buffered fragments, get rid of them */ if (session->in) { talloc_free(session->in); session->in = NULL; } return ret; }
/* * 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; }
/* * Initialize a radclient data structure and add it to * the global linked list. */ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files) { FILE *packets, *filters = NULL; vp_cursor_t cursor; VALUE_PAIR *vp; rc_request_t *request; bool packets_done = false; uint64_t num = 0; assert(files->packets != NULL); /* * Determine where to read the VP's from. */ if (strcmp(files->packets, "-") != 0) { packets = fopen(files->packets, "r"); if (!packets) { ERROR("Error opening %s: %s", files->packets, strerror(errno)); return 0; } /* * Read in the pairs representing the expected response. */ if (files->filters) { filters = fopen(files->filters, "r"); if (!filters) { ERROR("Error opening %s: %s", files->filters, strerror(errno)); fclose(packets); return 0; } } } else { packets = stdin; } /* * Loop until the file is done. */ do { /* * Allocate it. */ request = talloc_zero(ctx, rc_request_t); if (!request) { ERROR("Out of memory"); goto error; } request->packet = fr_radius_alloc(request, true); if (!request->packet) { ERROR("Out of memory"); goto error; } #ifdef WITH_TCP request->packet->src_ipaddr = client_ipaddr; request->packet->src_port = client_port; request->packet->dst_ipaddr = server_ipaddr; request->packet->dst_port = server_port; request->packet->proto = ipproto; #endif request->files = files; request->packet->id = -1; /* allocate when sending */ request->num = num++; /* * Read the request VP's. */ if (fr_pair_list_afrom_file(request->packet, &request->packet->vps, packets, &packets_done) < 0) { char const *input; if ((files->packets[0] == '-') && (files->packets[1] == '\0')) { input = "stdin"; } else { input = files->packets; } REDEBUG("Error parsing \"%s\"", input); goto error; } /* * Skip empty entries */ if (!request->packet->vps) { talloc_free(request); continue; } /* * Read in filter VP's. */ if (filters) { bool filters_done; if (fr_pair_list_afrom_file(request, &request->filter, filters, &filters_done) < 0) { REDEBUG("Error parsing \"%s\"", files->filters); goto error; } if (filters_done && !packets_done) { REDEBUG("Differing number of packets/filters in %s:%s " "(too many requests))", files->packets, files->filters); goto error; } if (!filters_done && packets_done) { REDEBUG("Differing number of packets/filters in %s:%s " "(too many filters))", files->packets, files->filters); goto error; } /* * xlat expansions aren't supported here */ for (vp = fr_cursor_init(&cursor, &request->filter); vp; vp = fr_cursor_next(&cursor)) { if (vp->type == VT_XLAT) { vp->type = VT_DATA; vp->vp_strvalue = vp->xlat; vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1; } if (vp->da->vendor == 0 ) switch (vp->da->attr) { case PW_RESPONSE_PACKET_TYPE: case PW_PACKET_TYPE: fr_cursor_remove(&cursor); /* so we don't break the filter */ request->filter_code = vp->vp_integer; talloc_free(vp); default: break; } } /* * This allows efficient list comparisons later */ fr_pair_list_sort(&request->filter, fr_pair_cmp_by_da_tag); } /* * Process special attributes */ for (vp = fr_cursor_init(&cursor, &request->packet->vps); vp; vp = fr_cursor_next(&cursor)) { /* * Double quoted strings get marked up as xlat expansions, * but we don't support that in request. */ if (vp->type == VT_XLAT) { vp->type = VT_DATA; vp->vp_strvalue = vp->xlat; vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1; } if (!vp->da->vendor) switch (vp->da->attr) { default: break; /* * Allow it to set the packet type in * the attributes read from the file. */ case PW_PACKET_TYPE: request->packet->code = vp->vp_integer; break; case PW_RESPONSE_PACKET_TYPE: request->filter_code = vp->vp_integer; break; case PW_PACKET_DST_PORT: request->packet->dst_port = (vp->vp_integer & 0xffff); break; case PW_PACKET_DST_IP_ADDRESS: request->packet->dst_ipaddr.af = AF_INET; request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; request->packet->dst_ipaddr.prefix = 32; break; case PW_PACKET_DST_IPV6_ADDRESS: request->packet->dst_ipaddr.af = AF_INET6; request->packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; request->packet->dst_ipaddr.prefix = 128; break; case PW_PACKET_SRC_PORT: if ((vp->vp_integer < 1024) || (vp->vp_integer > 65535)) { ERROR("Invalid value '%u' for Packet-Src-Port", vp->vp_integer); goto error; } request->packet->src_port = (vp->vp_integer & 0xffff); break; case PW_PACKET_SRC_IP_ADDRESS: request->packet->src_ipaddr.af = AF_INET; request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; request->packet->src_ipaddr.prefix = 32; break; case PW_PACKET_SRC_IPV6_ADDRESS: request->packet->src_ipaddr.af = AF_INET6; request->packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; request->packet->src_ipaddr.prefix = 128; break; case PW_DIGEST_REALM: case PW_DIGEST_NONCE: case PW_DIGEST_METHOD: case PW_DIGEST_URI: case PW_DIGEST_QOP: case PW_DIGEST_ALGORITHM: case PW_DIGEST_BODY_DIGEST: case PW_DIGEST_CNONCE: case PW_DIGEST_NONCE_COUNT: case PW_DIGEST_USER_NAME: /* overlapping! */ { fr_dict_attr_t const *da; uint8_t *p, *q; p = talloc_array(vp, uint8_t, vp->vp_length + 2); memcpy(p + 2, vp->vp_octets, vp->vp_length); p[0] = vp->da->attr - PW_DIGEST_REALM + 1; vp->vp_length += 2; p[1] = vp->vp_length; da = fr_dict_attr_by_num(NULL, 0, PW_DIGEST_ATTRIBUTES); if (!da) { ERROR("Out of memory"); goto error; } vp->da = da; /* * Re-do fr_pair_value_memsteal ourselves, * because we play games with * vp->da, and fr_pair_value_memsteal goes * to GREAT lengths to sanitize * and fix and change and * double-check the various * fields. */ memcpy(&q, &vp->vp_octets, sizeof(q)); talloc_free(q); vp->vp_octets = talloc_steal(vp, p); vp->type = VT_DATA; VERIFY_VP(vp); } break; /* * Cache this for later. */ case PW_CLEARTEXT_PASSWORD: request->password = vp; break; /* * Keep a copy of the the password attribute. */ case PW_CHAP_PASSWORD: /* * If it's already hex, do nothing. */ if ((vp->vp_length == 17) && (already_hex(vp))) break; /* * CHAP-Password is octets, so it may not be zero terminated. */ request->password = fr_pair_make(request->packet, &request->packet->vps, "Cleartext-Password", "", T_OP_EQ); fr_pair_value_bstrncpy(request->password, vp->vp_strvalue, vp->vp_length); break; case PW_USER_PASSWORD: case PW_MS_CHAP_PASSWORD: request->password = fr_pair_make(request->packet, &request->packet->vps, "Cleartext-Password", vp->vp_strvalue, T_OP_EQ); break; case PW_RADCLIENT_TEST_NAME: request->name = vp->vp_strvalue; break; } } /* loop over the VP's we read in */ /* * Use the default set on the command line */ if (request->packet->code == PW_CODE_UNDEFINED) request->packet->code = packet_code; /* * Default to the filename */ if (!request->name) request->name = request->files->packets; /* * Automatically set the response code from the request code * (if one wasn't already set). */ if (request->filter_code == PW_CODE_UNDEFINED) { switch (request->packet->code) { case PW_CODE_ACCESS_REQUEST: request->filter_code = PW_CODE_ACCESS_ACCEPT; break; case PW_CODE_ACCOUNTING_REQUEST: request->filter_code = PW_CODE_ACCOUNTING_RESPONSE; break; case PW_CODE_COA_REQUEST: request->filter_code = PW_CODE_COA_ACK; break; case PW_CODE_DISCONNECT_REQUEST: request->filter_code = PW_CODE_DISCONNECT_ACK; break; case PW_CODE_STATUS_SERVER: switch (radclient_get_code(request->packet->dst_port)) { case PW_CODE_ACCESS_REQUEST: request->filter_code = PW_CODE_ACCESS_ACCEPT; break; case PW_CODE_ACCOUNTING_REQUEST: request->filter_code = PW_CODE_ACCOUNTING_RESPONSE; break; default: request->filter_code = PW_CODE_UNDEFINED; break; } break; case PW_CODE_UNDEFINED: REDEBUG("Both Packet-Type and Response-Packet-Type undefined, specify at least one, " "or a well known RADIUS port"); goto error; default: REDEBUG("Can't determine expected Response-Packet-Type for Packet-Type %i", request->packet->code); goto error; } /* * Automatically set the request code from the response code * (if one wasn't already set). */ } else if (request->packet->code == PW_CODE_UNDEFINED) { switch (request->filter_code) { case PW_CODE_ACCESS_ACCEPT: case PW_CODE_ACCESS_REJECT: request->packet->code = PW_CODE_ACCESS_REQUEST; break; case PW_CODE_ACCOUNTING_RESPONSE: request->packet->code = PW_CODE_ACCOUNTING_REQUEST; break; case PW_CODE_DISCONNECT_ACK: case PW_CODE_DISCONNECT_NAK: request->packet->code = PW_CODE_DISCONNECT_REQUEST; break; case PW_CODE_COA_ACK: case PW_CODE_COA_NAK: request->packet->code = PW_CODE_COA_REQUEST; break; default: REDEBUG("Can't determine expected Packet-Type for Response-Packet-Type %i", request->filter_code); goto error; } } /* * Automatically set the dst port (if one wasn't already set). */ if (request->packet->dst_port == 0) { radclient_get_port(request->packet->code, &request->packet->dst_port); if (request->packet->dst_port == 0) { REDEBUG("Can't determine destination port"); goto error; } } /* * Add it to the tail of the list. */ if (!request_head) { assert(rc_request_tail == NULL); request_head = request; request->prev = NULL; } else { assert(rc_request_tail->next == NULL); rc_request_tail->next = request; request->prev = rc_request_tail; } rc_request_tail = request; request->next = NULL; /* * Set the destructor so it removes itself from the * request list when freed. We don't set this until * the packet is actually in the list, else we trigger * the asserts in the free callback. */ talloc_set_destructor(request, _rc_request_free); } while (!packets_done); /* loop until the file is done. */ if (packets != stdin) fclose(packets); if (filters) fclose(filters); /* * And we're done. */ return 1; error: talloc_free(request); if (packets != stdin) fclose(packets); if (filters) fclose(filters); return 0; }
/** 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; }
/** Convert group membership information into attributes * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_groupobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; LDAPMessage *result = NULL; LDAPMessage *entry; char const *base_dn; char base_dn_buff[LDAP_MAX_DN_STR_LEN]; char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; char const *attrs[] = { inst->groupobj_name_attr, NULL }; VALUE_PAIR *vp; char *dn; rad_assert(inst->groupobj_base_dn); if (!inst->groupobj_membership_filter) { RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set"); return RLM_MODULE_OK; } if (rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)) < 0) { return RLM_MODULE_INVALID; } if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } status = rlm_ldap_search(&result, inst, request, pconn, base_dn, inst->groupobj_scope, filter, attrs, NULL, NULL); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG2("No cacheable group memberships found in group objects"); goto finish; default: rcode = RLM_MODULE_FAIL; goto finish; } entry = ldap_first_entry((*pconn)->handle, result); if (!entry) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); goto finish; } RDEBUG("Adding cacheable group object memberships"); do { if (inst->cacheable_group_dn) { dn = ldap_get_dn((*pconn)->handle, entry); if (!dn) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno)); goto finish; } rlm_ldap_normalise_dn(dn, dn); MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD)); fr_pair_value_strcpy(vp, dn); RINDENT(); RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, dn); REXDENT(); ldap_memfree(dn); } if (inst->cacheable_group_name) { struct berval **values; values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr); if (!values) continue; MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD)); fr_pair_value_bstrncpy(vp, values[0]->bv_val, values[0]->bv_len); RINDENT(); RDEBUG("&control:%s += \"%.*s\"", inst->cache_da->name, (int)values[0]->bv_len, values[0]->bv_val); REXDENT(); ldap_value_free_len(values); } } while ((entry = ldap_next_entry((*pconn)->handle, entry))); finish: if (result) ldap_msgfree(result); return rcode; }
/** Convert group membership information into attributes * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search. * @param[in] attr membership attribute to look for in the entry. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, LDAPMessage *entry, char const *attr) { rlm_rcode_t rcode = RLM_MODULE_OK; struct berval **values; char *group_name[LDAP_MAX_CACHEABLE + 1]; char **name_p = group_name; char *group_dn[LDAP_MAX_CACHEABLE + 1]; char **dn_p; char *name; VALUE_PAIR *vp, **list, *groups = NULL; TALLOC_CTX *list_ctx, *value_ctx; vp_cursor_t list_cursor, groups_cursor; int is_dn, i, count; rad_assert(entry); rad_assert(attr); /* * Parse the membership information we got in the initial user query. */ values = ldap_get_values_len((*pconn)->handle, entry, attr); if (!values) { RDEBUG2("No cacheable group memberships found in user object"); return RLM_MODULE_OK; } count = ldap_count_values_len(values); list = radius_list(request, PAIR_LIST_CONTROL); list_ctx = radius_list_ctx(request, PAIR_LIST_CONTROL); /* * Simplifies freeing temporary values */ value_ctx = talloc_new(request); /* * Temporary list to hold new group VPs, will be merged * once all group info has been gathered/resolved * successfully. */ fr_cursor_init(&groups_cursor, &groups); for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) { is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len); if (inst->cacheable_group_dn) { /* * The easy case, we're caching DNs and we got a DN. */ if (is_dn) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&groups_cursor, vp); /* * We were told to cache DNs but we got a name, we now need to resolve * this to a DN. Store all the group names in an array so we can do one query. */ } else { *name_p++ = rlm_ldap_berval_to_string(value_ctx, values[i]); } } if (inst->cacheable_group_name) { /* * The easy case, we're caching names and we got a name. */ if (!is_dn) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&groups_cursor, vp); /* * We were told to cache names but we got a DN, we now need to resolve * this to a name. * Only Active Directory supports filtering on DN, so we have to search * for each individual group. */ } else { char *dn; dn = rlm_ldap_berval_to_string(value_ctx, values[i]); rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name); talloc_free(dn); if (rcode != RLM_MODULE_OK) { ldap_value_free_len(values); talloc_free(value_ctx); fr_pair_list_free(&groups); return rcode; } MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, name, talloc_array_length(name) - 1); fr_cursor_insert(&groups_cursor, vp); talloc_free(name); } } } *name_p = NULL; rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn)); ldap_value_free_len(values); talloc_free(value_ctx); if (rcode != RLM_MODULE_OK) return rcode; fr_cursor_init(&list_cursor, list); RDEBUG("Adding cacheable user object memberships"); RINDENT(); if (RDEBUG_ENABLED) { for (vp = fr_cursor_first(&groups_cursor); vp; vp = fr_cursor_next(&groups_cursor)) { RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue); } } fr_cursor_merge(&list_cursor, groups); for (dn_p = group_dn; *dn_p; dn_p++) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_strcpy(vp, *dn_p); fr_cursor_insert(&list_cursor, vp); RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue); ldap_memfree(*dn_p); } REXDENT(); return rcode; }