int8_t fr_dhcpv4_attr_cmp(void const *a, void const *b) { VALUE_PAIR const *my_a = a, *my_b = b; fr_dict_attr_t const *a_82, *b_82; VP_VERIFY(my_a); VP_VERIFY(my_b); /* * We can only use attribute numbers if we know they're * not nested attributes. * * @fixme We should be able to use my_a->da->parent->flags.is_root, * but the DHCP attributes are hacked into the server under a vendor * dictionary, so we can't. */ /* * DHCP-Message-Type is first, for simplicity. */ if (((my_a->da->parent->type != FR_TYPE_TLV) && (my_a->da == attr_dhcp_message_type)) && ((my_b->da->parent->type == FR_TYPE_TLV) || (my_b->da != attr_dhcp_message_type))) return -1; if (((my_a->da->parent->type == FR_TYPE_TLV) || (my_a->da != attr_dhcp_message_type)) && ((my_b->da->parent->type != FR_TYPE_TLV) && (my_b->da == attr_dhcp_message_type))) return +1; /* * Relay-Agent is last. * * Check if either of the options are descended from option 82. */ a_82 = fr_dict_parent_common(dhcp_option_82, my_a->da, true); b_82 = fr_dict_parent_common(dhcp_option_82, my_b->da, true); if (a_82 && !b_82) return +1; if (!a_82 && !b_82) return -1; return fr_pair_cmp_by_parent_num_tag(my_a, my_b); }
/** Iterate over attributes with a given ancestor * * Find the next attribute of a given type. If no fr_pair_cursor_next_by_* function * has been called on a cursor before, or the previous call returned * NULL, the search will start with the current attribute. Subsequent calls to * fr_pair_cursor_next_by_* functions will start the search from the previously * matched attribute. * * @param cursor to operate on. * @param ancestor attribute to match on. * @param tag to match. Either a tag number or TAG_ANY to match any tagged or * untagged attribute, TAG_NONE to match attributes without tags. * @return * - Next matching #VALUE_PAIR. * - NULL if no #VALUE_PAIR (s) match. */ VALUE_PAIR *fr_pair_cursor_next_by_ancestor(vp_cursor_t *cursor, fr_dict_attr_t const *ancestor, int8_t tag) { VALUE_PAIR *i; if (!cursor->first) return NULL; for (i = cursor->found ? cursor->found->next : cursor->current; i != NULL; i = i->next) { VP_VERIFY(i); if (fr_dict_parent_common(ancestor, i->da, true) && ATTR_TAG_MATCH(i, tag)) { break; } } return fr_pair_cursor_update(cursor, i); }
/** Write the result of a get or getnext operation back to net-snmp * * Returns three lines of output per attribute: * - OID string * - type * - value * * Index attributes (num 0) must be in order of depth (shallowest first). * * If no attributes were written, will write "NONE\n" to inform net-snmp * that no value was available at the specified OID. * * @param fd to write to. * @param root of the SNMP portion of the main dictionary. * @param type attribute. * @param head of list of attributes to convert and write. * @return * - >=0 on success (the number of varbind responses written). * - -1 on failure. */ static int radsnmp_get_response(int fd, fr_dict_attr_t const *root, fr_dict_attr_t const *type, VALUE_PAIR *head) { fr_cursor_t cursor; VALUE_PAIR *vp, *type_vp; fr_dict_attr_t const *parent = root; unsigned int written = 0; ssize_t slen; size_t len; char type_buff[32]; /* type */ size_t type_len = 0; char oid_buff[256]; char value_buff[128]; char *p = oid_buff, *end = p + sizeof(oid_buff); struct iovec io_vector[6]; char newline[] = "\n"; type_buff[0] = '\0'; /* * Print first part of OID string. */ slen = snprintf(oid_buff, sizeof(oid_buff), "%u.", parent->attr); if (is_truncated((size_t)slen, sizeof(oid_buff))) { oob: fr_strerror_printf("OID Buffer too small"); return -1; } p += slen; /* * @fixme, this is very dependent on ordering * * This code should be reworked when we have proper * attribute grouping to coalesce all related index * attributes under a single request OID. */ for (vp = fr_cursor_init(&cursor, &head); vp; vp = fr_cursor_next(&cursor)) { fr_dict_attr_t const *common; /* * We only care about TLV attributes beneath our root */ if (!fr_dict_parent_common(root, vp->da, true)) continue; /* * Sanity checks to ensure we're processing attributes * in the right order. */ common = fr_dict_parent_common(parent, vp->da, true); if (!common) { fr_strerror_printf("Out of order index attributes. \"%s\" is not a child of \"%s\"", vp->da->name, parent->name); return -1; } /* * Index attribute */ if (vp->da->attr == 0) { /* * Print OID from last index/root up to the parent of * the index attribute. */ slen = fr_dict_print_attr_oid(p, end - p, parent, vp->da->parent); if (slen < 0) return -1; if (vp->vp_type != FR_TYPE_UINT32) { fr_strerror_printf("Index attribute \"%s\" is not of type \"integer\"", vp->da->name); return -1; } if (slen >= (end - p)) goto oob; p += slen; /* * Add the value of the index attribute as the next * OID component. */ len = snprintf(p, end - p, ".%i.", vp->vp_uint32); if (is_truncated(len, end - p)) goto oob; p += len; /* * Set the parent to be the attribute representing * the entry. */ parent = fr_dict_attr_child_by_num(vp->da->parent, 1); continue; } /* * Actual TLV attribute */ slen = fr_dict_print_attr_oid(p, end - p, parent, vp->da); if (slen < 0) return -1; /* * Next attribute should be the type */ type_vp = fr_cursor_next(&cursor); if (!type_vp || (type_vp->da != type)) { fr_strerror_printf("No %s found in response, or occurred out of order", type->name); return -1; } type_len = fr_pair_value_snprint(type_buff, sizeof(type_buff), type_vp, '\0'); /* * Build up the vector * * This represents output for a single varbind attribute */ io_vector[0].iov_base = oid_buff; io_vector[0].iov_len = strlen(oid_buff); io_vector[1].iov_base = newline; io_vector[1].iov_len = 1; io_vector[2].iov_base = type_buff; io_vector[2].iov_len = type_len; io_vector[3].iov_base = newline; io_vector[3].iov_len = 1; switch (vp->vp_type) { case FR_TYPE_OCTETS: memcpy(&io_vector[4].iov_base, &vp->vp_strvalue, sizeof(io_vector[4].iov_base)); io_vector[4].iov_len = vp->vp_length; break; case FR_TYPE_STRING: memcpy(&io_vector[4].iov_base, &vp->vp_strvalue, sizeof(io_vector[4].iov_base)); io_vector[4].iov_len = vp->vp_length; break; default: /* * We call fr_value_box_snprint with a NULL da pointer * because we always need return integer values not * value aliases. */ len = fr_value_box_snprint(value_buff, sizeof(value_buff), &vp->data, '\0'); if (is_truncated(len, sizeof(value_buff))) { fr_strerror_printf("Insufficient fixed value buffer"); return -1; } io_vector[4].iov_base = value_buff; io_vector[4].iov_len = len; break; } io_vector[5].iov_base = newline; io_vector[5].iov_len = 1; DEBUG2("said: %s", (char *)io_vector[0].iov_base); DEBUG2("said: %s", (char *)io_vector[2].iov_base); DEBUG2("said: %s", (char *)io_vector[4].iov_base); if (writev(fd, io_vector, sizeof(io_vector) / sizeof(*io_vector)) < 0) { fr_strerror_printf("Failed writing varbind result: %s", fr_syserror(errno)); return -1; } /* * Reset in case we're encoding multiple values */ parent = root; p = oid_buff; type_buff[0] = '\0'; written++; } if (!written && (write(fd, "NONE\n", 5)) < 0) { fr_strerror_printf("Failed writing get response: %s", fr_syserror(errno)); return -1; } return written; }
/* * build a reply to be sent. */ static int eap_sim_compose(eap_session_t *eap_session, uint8_t const *hmac_extra, size_t hmac_extra_len) { eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); fr_cursor_t cursor; fr_cursor_t to_encode; VALUE_PAIR *head = NULL, *vp; REQUEST *request = eap_session->request; fr_sim_encode_ctx_t encoder_ctx = { .root = fr_dict_root(dict_eap_sim), .keys = &eap_sim_session->keys, .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, .iv_included = false, .hmac_md = EVP_sha1(), .eap_packet = eap_session->this_round->request, .hmac_extra = hmac_extra, .hmac_extra_len = hmac_extra_len }; ssize_t ret; /* we will set the ID on requests, since we have to HMAC it */ eap_session->this_round->set_request_id = true; fr_cursor_init(&cursor, &eap_session->request->reply->vps); fr_cursor_init(&to_encode, &head); while ((vp = fr_cursor_current(&cursor))) { if (!fr_dict_parent_common(fr_dict_root(dict_eap_sim), vp->da, true)) { fr_cursor_next(&cursor); continue; } vp = fr_cursor_remove(&cursor); /* * Silently discard encrypted attributes until * the peer should have k_encr. These can be * added by policy, and seem to cause * wpa_supplicant to fail if sent before the challenge. */ if (!eap_sim_session->allow_encrypted && fr_dict_parent_common(attr_eap_sim_encr_data, vp->da, true)) { RWDEBUG("Silently discarding &reply:%s: Encrypted attributes not allowed in this round", vp->da->name); talloc_free(vp); continue; } fr_cursor_append(&to_encode, vp); } RDEBUG2("Encoding EAP-SIM attributes"); log_request_pair_list(L_DBG_LVL_2, request, head, NULL); eap_session->this_round->request->type.num = FR_EAP_SIM; eap_session->this_round->request->id = eap_sim_session->sim_id++ & 0xff; eap_session->this_round->set_request_id = true; ret = fr_sim_encode(eap_session->request, head, &encoder_ctx); fr_cursor_head(&to_encode); fr_cursor_free_list(&to_encode); if (ret < 0) { RPEDEBUG("Failed encoding EAP-SIM data"); return -1; } return 0; } static int eap_sim_send_start(eap_session_t *eap_session) { REQUEST *request = eap_session->request; VALUE_PAIR **vps, *vp; uint16_t version; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); RADIUS_PACKET *packet; rad_assert(eap_session->request != NULL); rad_assert(eap_session->request->reply); RDEBUG2("Sending SIM-State"); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; eap_sim_session->allow_encrypted = false; /* In case this is after failed fast-resumption */ /* these are the outgoing attributes */ packet = eap_session->request->reply; vps = &packet->vps; rad_assert(vps != NULL); /* * Add appropriate TLVs for the EAP things we wish to send. */ vp = fr_pair_afrom_da(packet, attr_eap_sim_version_list); vp->vp_uint16 = EAP_SIM_VERSION; fr_pair_add(vps, vp); /* record it in the ess */ version = htons(EAP_SIM_VERSION); memcpy(eap_sim_session->keys.gsm.version_list, &version, sizeof(version)); eap_sim_session->keys.gsm.version_list_len = 2; /* * Select the right type of identity request attribute */ switch (eap_sim_session->id_req) { case SIM_ANY_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_sim_any_id_req); break; case SIM_PERMANENT_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_sim_permanent_id_req); break; case SIM_FULLAUTH_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_sim_fullauth_id_req); break; default: rad_assert(0); } vp->vp_bool = true; fr_pair_replace(vps, vp); /* the SUBTYPE, set to start. */ vp = fr_pair_afrom_da(packet, attr_eap_sim_subtype); vp->vp_uint16 = EAP_SIM_START; fr_pair_replace(vps, vp); /* * Encode the packet */ if (eap_sim_compose(eap_session, NULL, 0) < 0) { fr_pair_list_free(&packet->vps); return -1; } return 0; }
static int eap_aka_compose(eap_session_t *eap_session) { eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); fr_cursor_t cursor; fr_cursor_t to_encode; VALUE_PAIR *head = NULL, *vp; REQUEST *request = eap_session->request; ssize_t ret; fr_sim_encode_ctx_t encoder_ctx = { .root = fr_dict_root(dict_eap_aka), .keys = &eap_aka_session->keys, .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, .iv_included = false, .hmac_md = eap_aka_session->mac_md, .eap_packet = eap_session->this_round->request, .hmac_extra = NULL, .hmac_extra_len = 0 }; fr_cursor_init(&cursor, &eap_session->request->reply->vps); fr_cursor_init(&to_encode, &head); while ((vp = fr_cursor_current(&cursor))) { if (!fr_dict_parent_common(encoder_ctx.root, vp->da, true)) { fr_cursor_next(&cursor); continue; } vp = fr_cursor_remove(&cursor); /* * Silently discard encrypted attributes until * the peer should have k_encr. These can be * added by policy, and seem to cause * wpa_supplicant to fail if sent before the challenge. */ if (!eap_aka_session->allow_encrypted && fr_dict_parent_common(attr_eap_aka_encr_data, vp->da, true)) { RWDEBUG("Silently discarding &reply:%s: Encrypted attributes not allowed in this round", vp->da->name); talloc_free(vp); continue; } fr_cursor_append(&to_encode, vp); } RDEBUG2("Encoding EAP-AKA attributes"); log_request_pair_list(L_DBG_LVL_2, request, head, NULL); eap_session->this_round->request->type.num = eap_aka_session->type; eap_session->this_round->request->id = eap_aka_session->aka_id++ & 0xff; eap_session->this_round->set_request_id = true; ret = fr_sim_encode(eap_session->request, head, &encoder_ctx); fr_cursor_head(&to_encode); fr_cursor_free_list(&to_encode); if (ret < 0) { RPEDEBUG("Failed encoding EAP-AKA data"); return -1; } return 0; } /** Send an EAP-AKA identity request to the supplicant * * There are three types of user identities that can be implemented * - Permanent identities such as [email protected] * Permanent identities can be identified by the leading zero followed by * by 15 digits (the IMSI number). * - Ephemeral identities (pseudonyms). These are identities assigned for * identity privacy so the user can't be tracked. These can identities * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses' * document section 14, where a set of up to 16 encryption keys are used * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely * randomised and stored in a datastore. * - A fast resumption ID which resolves to data used for fast resumption. * * In order to perform full authentication the original IMSI is required for * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym, * or can't perform fast resumption, we need to request the full identity from * the supplicant. * * @param[in] eap_session to continue. * @return * - 0 on success. * - <0 on failure. */ static int eap_aka_send_identity_request(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 *vp; RADIUS_PACKET *packet; fr_cursor_t cursor; RDEBUG2("Sending AKA-Identity (%s)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>")); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; eap_aka_session->allow_encrypted = false; /* In case this is after failed fast-resumption */ packet = request->reply; fr_cursor_init(&cursor, &packet->vps); /* * Set the subtype to identity request */ vp = fr_pair_afrom_da(packet, attr_eap_aka_subtype); vp->vp_uint16 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_IDENTITY; fr_cursor_append(&cursor, vp); /* * Select the right type of identity request attribute */ switch (eap_aka_session->id_req) { case SIM_ANY_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_any_id_req); break; case SIM_PERMANENT_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_permanent_id_req); break; case SIM_FULLAUTH_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_fullauth_id_req); break; default: rad_assert(0); } vp->vp_bool = true; fr_cursor_append(&cursor, vp); /* * Encode the packet */ if (eap_aka_compose(eap_session) < 0) { failure: fr_pair_list_free(&packet->vps); return -1; } /* * Digest the packet contents, updating our checkcode. */ if (!eap_aka_session->checkcode_state && fr_sim_crypto_init_checkcode(eap_aka_session, &eap_aka_session->checkcode_state, eap_aka_session->checkcode_md) < 0) { RPEDEBUG("Failed initialising checkcode"); goto failure; } if (fr_sim_crypto_update_checkcode(eap_aka_session->checkcode_state, eap_session->this_round->request) < 0) { RPEDEBUG("Failed updating checkcode"); goto failure; } return 0; }