/** Remove item from parent and fixup trees * * @param[in] parent to remove child from. * @param[in] child to remove. * @return * - The item removed. * - NULL if the item wasn't set. */ CONF_ITEM *cf_remove(CONF_ITEM *parent, CONF_ITEM *child) { CONF_ITEM *found; bool in_ident1, in_ident2; if (!parent || !parent->child) return NULL; if (parent != child->parent) return NULL; for (found = fr_cursor_head(&parent->cursor); found && (child != found); found = fr_cursor_next(&parent->cursor)); if (!found) return NULL; /* * Fixup the linked list */ found = fr_cursor_remove(&parent->cursor); if (!fr_cond_assert(found == child)) return NULL; in_ident1 = (rbtree_finddata(parent->ident1, child) == child); if (in_ident1 && (!rbtree_deletebydata(parent->ident1, child))) { rad_assert(0); return NULL; } in_ident2 = (rbtree_finddata(parent->ident2, child) == child); if (in_ident2 && (!rbtree_deletebydata(parent->ident2, child))) { rad_assert(0); return NULL; } /* * Look for twins */ for (found = fr_cursor_head(&parent->cursor); found && (in_ident1 || in_ident2); found = fr_cursor_next(&parent->cursor)) { if (in_ident1 && (_cf_ident1_cmp(found, child) == 0)) { rbtree_insert(parent->ident1, child); in_ident1 = false; } if (in_ident2 && (_cf_ident2_cmp(found, child) == 0)) { rbtree_insert(parent->ident2, child); in_ident2 = false; } } return child; }
/** Add a child * * @param[in] parent to add child to. * @param[in] child to add. */ void _cf_item_add(CONF_ITEM *parent, CONF_ITEM *child) { fr_cursor_t to_merge; CONF_ITEM *ci; rad_assert(parent != child); if (!parent || !child) return; /* * New child, add child trees. */ if (!parent->ident1) parent->ident1 = rbtree_create(parent, _cf_ident1_cmp, NULL, RBTREE_FLAG_NONE); if (!parent->ident2) parent->ident2 = rbtree_create(parent, _cf_ident2_cmp, NULL, RBTREE_FLAG_NONE); fr_cursor_init(&to_merge, &child); for (ci = fr_cursor_head(&to_merge); ci; ci = fr_cursor_next(&to_merge)) { rbtree_insert(parent->ident1, ci); rbtree_insert(parent->ident2, ci); /* NULL ident2 is still a value */ fr_cursor_append(&parent->cursor, ci); /* Append to the list of children */ } }
/** Handle authorization requests using Couchbase document data * * Attempt to fetch the document assocaited with the requested user by * using the deterministic key defined in the configuration. When a valid * document is found it will be parsed and the containing value pairs will be * injected into the request. * * @param instance The module instance. * @param thread specific data. * @param request The authorization request. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->user_key, NULL, NULL); if (slen < 0) return RLM_MODULE_FAIL; if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); return RLM_MODULE_FAIL; } /* get handle */ handle = fr_pool_connection_get(inst->pool, request); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || !cookie->jobj) { /* log error */ RERROR("failed to fetch document or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj)); { TALLOC_CTX *pool = talloc_pool(request, 1024); /* We need to do lots of allocs */ fr_cursor_t maps, vlms; vp_map_t *map_head = NULL, *map; vp_list_mod_t *vlm_head = NULL, *vlm; fr_cursor_init(&maps, &map_head); /* * Convert JSON data into maps */ if ((mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_CONTROL) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REPLY) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REQUEST) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_STATE) < 0)) { invalid: talloc_free(pool); rcode = RLM_MODULE_INVALID; goto finish; } fr_cursor_init(&vlms, &vlm_head); /* * Convert all the maps into list modifications, * which are guaranteed to succeed. */ for (map = fr_cursor_head(&maps); map; map = fr_cursor_next(&maps)) { if (map_to_list_mod(pool, &vlm, request, map, NULL, NULL) < 0) goto invalid; fr_cursor_insert(&vlms, vlm); } if (!vlm_head) { RDEBUG2("Nothing to update"); talloc_free(pool); rcode = RLM_MODULE_NOOP; goto finish; } /* * Apply the list of modifications */ for (vlm = fr_cursor_head(&vlms); vlm; vlm = fr_cursor_next(&vlms)) { int ret; ret = map_list_mod_apply(request, vlm); /* SHOULD NOT FAIL */ if (!fr_cond_assert(ret == 0)) { talloc_free(pool); rcode = RLM_MODULE_FAIL; goto finish; } } talloc_free(pool); } finish: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) fr_pool_connection_release(inst->pool, request, handle); /* return */ return rcode; }
/** Break apart a TLV attribute into individual attributes * * @param[in] ctx to allocate new attributes in. * @param[in] cursor to addd new attributes to. * @param[in] parent the current attribute TLV attribute we're processing. * @param[in] data to parse. Points to the data field of the attribute. * @param[in] attr_len length of the TLV attribute. * @param[in] data_len remaining data in the packet. * @param[in] decoder_ctx IVs, keys etc... * @return * - Length on success. * - < 0 on malformed attribute. */ static ssize_t sim_decode_tlv(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t const attr_len, size_t data_len, void *decoder_ctx) { uint8_t const *p = data, *end = p + attr_len; uint8_t *decr = NULL; ssize_t decr_len; fr_dict_attr_t const *child; VALUE_PAIR *head = NULL; fr_cursor_t tlv_cursor; ssize_t rcode; if (data_len < 2) { fr_strerror_printf("%s: Insufficient data", __FUNCTION__); return -1; /* minimum attr size */ } /* * We have an AES-128-CBC encrypted attribute * * IV is from AT_IV, key is from k_encr. * * unfortunately the ordering of these two attributes * aren't specified, so we may have to hunt for the IV. */ if (parent->flags.encrypt) { FR_PROTO_TRACE("found encrypted attribute '%s'", parent->name); decr_len = sim_value_decrypt(ctx, &decr, p + 2, attr_len - 2, data_len - 2, decoder_ctx); /* Skip reserved */ if (decr_len < 0) return -1; p = decr; end = p + decr_len; } else { p += 2; /* Skip the reserved bytes */ } FR_PROTO_HEX_DUMP(p, end - p, "tlvs"); /* * Record where we were in the list when packet_ctx function was called */ fr_cursor_init(&tlv_cursor, &head); while ((size_t)(end - p) >= sizeof(uint32_t)) { uint8_t sim_at = p[0]; size_t sim_at_len = ((size_t)p[1]) << 2; if ((p + sim_at_len) > end) { fr_strerror_printf("%s: Malformed nested attribute %d: Length field (%zu bytes) value " "longer than remaining data in parent (%zu bytes)", __FUNCTION__, sim_at, sim_at_len, end - p); error: talloc_free(decr); fr_pair_list_free(&head); return -1; } if (sim_at_len == 0) { fr_strerror_printf("%s: Malformed nested attribute %d: Length field 0", __FUNCTION__, sim_at); goto error; } /* * Padding attributes are cleartext inside of * encrypted TLVs to pad out the value to the * correct length for the block cipher * (16 in the case of AES-128-CBC). */ if (sim_at == FR_SIM_PADDING) { uint8_t zero = 0; uint8_t i; if (!parent->flags.encrypt) { fr_strerror_printf("%s: Found padding attribute outside of an encrypted TLV", __FUNCTION__); goto error; } if (!fr_cond_assert(data_len % 4)) goto error; if (sim_at_len > 12) { fr_strerror_printf("%s: Expected padding attribute length <= 12 bytes, got %zu bytes", __FUNCTION__, sim_at_len); goto error; } /* * RFC says we MUST verify that FR_SIM_PADDING * data is zeroed out. */ for (i = 2; i < sim_at_len; i++) zero |= p[i]; if (zero) { fr_strerror_printf("%s: Padding attribute value not zeroed 0x%pH", __FUNCTION__, fr_box_octets(p + 2, sim_at_len - 2)); goto error; } p += sim_at_len; continue; } child = fr_dict_attr_child_by_num(parent, p[0]); if (!child) { fr_dict_attr_t const *unknown_child; FR_PROTO_TRACE("Failed to find child %u of TLV %s", p[0], parent->name); /* * Encountered none skippable attribute * * RFC says we need to die on these if we don't * understand them. non-skippables are < 128. */ if (sim_at <= SIM_SKIPPABLE_MAX) { fr_strerror_printf("%s: Unknown (non-skippable) attribute %i", __FUNCTION__, sim_at); goto error; } /* * Build an unknown attr */ unknown_child = fr_dict_unknown_afrom_fields(ctx, parent, fr_dict_vendor_num_by_da(parent), p[0]); if (!unknown_child) goto error; child = unknown_child; } FR_PROTO_TRACE("decode context changed %s -> %s", parent->name, child->name); rcode = sim_decode_pair_value(ctx, &tlv_cursor, child, p + 2, sim_at_len - 2, (end - p) - 2, decoder_ctx); if (rcode < 0) goto error; p += sim_at_len; } fr_cursor_head(&tlv_cursor); fr_cursor_tail(cursor); fr_cursor_merge(cursor, &tlv_cursor); /* Wind to the end of the new pairs */ talloc_free(decr); return attr_len; }
/** Logging callback to write log messages to a destination * * This allows the logging destination to be customised on a per request basis. * * @note Function does not write log output immediately * * @param[in] type What type of message this is (error, warn, info, debug). * @param[in] lvl At what logging level this message should be output. * @param[in] request The current request. * @param[in] file src file the log message was generated in. * @param[in] line number the log message was generated on. * @param[in] fmt sprintf style fmt string. * @param[in] ap Arguments for the fmt string. * @param[in] uctx Context data for the log function. */ static void logtee_it(fr_log_type_t type, fr_log_lvl_t lvl, REQUEST *request, UNUSED char const *file, UNUSED int line, char const *fmt, va_list ap, void *uctx) { rlm_logtee_thread_t *t = talloc_get_type_abort(uctx, rlm_logtee_thread_t); rlm_logtee_t const *inst = t->inst; char *msg, *exp; fr_cursor_t cursor; VALUE_PAIR *vp; log_dst_t *dst; rad_assert(t->msg->vp_length == 0); /* Should have been cleared before returning */ /* * None of this should involve mallocs unless msg > 1k */ msg = talloc_typed_vasprintf(t->msg, fmt, ap); fr_value_box_strdup_buffer_shallow(NULL, &t->msg->data, attr_log_message, msg, true); t->type->vp_uint32 = (uint32_t) type; t->lvl->vp_uint32 = (uint32_t) lvl; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_prepend(&cursor, t->msg); fr_cursor_prepend(&cursor, t->type); fr_cursor_prepend(&cursor, t->lvl); fr_cursor_head(&cursor); /* * Now expand our fmt string to encapsulate the * message and any metadata * * Fixme: Would be better to call tmpl_expand * into a variable length ring buffer. */ dst = request->log.dst; request->log.dst = NULL; if (tmpl_aexpand(t, &exp, request, inst->log_fmt, NULL, NULL) < 0) goto finish; request->log.dst = dst; fr_fring_overwrite(t->fring, exp); /* Insert it into the buffer */ if (!t->pending) { t->pending = true; logtee_fd_active(t); /* Listen for when the fd is writable */ } finish: /* * Don't free, we re-use the VALUE_PAIRs for the next message */ vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->lvl)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->type)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->msg)) fr_cursor_append(&cursor, vp); fr_value_box_clear(&t->msg->data); /* Clear message data */ }
/* * 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; }
static unlang_action_t unlang_foreach(REQUEST *request, rlm_rcode_t *presult, int *priority) { VALUE_PAIR *vp; unlang_stack_t *stack = request->stack; unlang_stack_frame_t *frame = &stack->frame[stack->depth]; unlang_t *instruction = frame->instruction; unlang_frame_state_foreach_t *foreach = NULL; unlang_group_t *g; g = unlang_generic_to_group(instruction); if (!frame->repeat) { int i, foreach_depth = -1; VALUE_PAIR *vps; if (stack->depth >= UNLANG_STACK_MAX) { ERROR("Internal sanity check failed: module stack is too deep"); fr_exit(EXIT_FAILURE); } /* * Figure out how deep we are in nesting by looking at request_data * stored previously. * * FIXME: figure this out by walking up the modcall stack instead. */ for (i = 0; i < 8; i++) { if (!request_data_reference(request, (void *)xlat_fmt_get_vp, i)) { foreach_depth = i; break; } } if (foreach_depth < 0) { REDEBUG("foreach Nesting too deep!"); *presult = RLM_MODULE_FAIL; *priority = 0; return UNLANG_ACTION_CALCULATE_RESULT; } /* * Copy the VPs from the original request, this ensures deterministic * behaviour if someone decides to add or remove VPs in the set we're * iterating over. */ if (tmpl_copy_vps(stack, &vps, request, g->vpt) < 0) { /* nothing to loop over */ *presult = RLM_MODULE_NOOP; *priority = instruction->actions[RLM_MODULE_NOOP]; return UNLANG_ACTION_CALCULATE_RESULT; } MEM(frame->state = foreach = talloc_zero(stack, unlang_frame_state_foreach_t)); rad_assert(vps != NULL); foreach->depth = foreach_depth; foreach->vps = vps; fr_cursor_talloc_init(&foreach->cursor, &foreach->vps, VALUE_PAIR); #ifndef NDEBUG foreach->indent = request->log.unlang_indent; #endif vp = fr_cursor_head(&foreach->cursor); } else { foreach = talloc_get_type_abort(frame->state, unlang_frame_state_foreach_t); vp = fr_cursor_next(&foreach->cursor); /* * We've been asked to unwind to the * enclosing "foreach". We're here, so * we can stop unwinding. */ if (frame->unwind == UNLANG_TYPE_BREAK) { frame->unwind = UNLANG_TYPE_NULL; vp = NULL; } /* * Unwind all the way. */ if (frame->unwind == UNLANG_TYPE_RETURN) { vp = NULL; } if (!vp) { /* * Free the copied vps and the request data * If we don't remove the request data, something could call * the xlat outside of a foreach loop and trigger a segv. */ fr_pair_list_free(&foreach->vps); request_data_get(request, (void *)xlat_fmt_get_vp, foreach->depth); *presult = frame->result; if (*presult != RLM_MODULE_UNKNOWN) *priority = instruction->actions[*presult]; #ifndef NDEBUG rad_assert(foreach->indent == request->log.unlang_indent); #endif return UNLANG_ACTION_CALCULATE_RESULT; } } #ifndef NDEBUG RDEBUG2(""); RDEBUG2("# looping with: Foreach-Variable-%d = %pV", foreach->depth, &vp->data); #endif rad_assert(vp); /* * Add the vp to the request, so that * xlat.c, xlat_foreach() can find it. */ foreach->variable = vp; request_data_add(request, (void *)xlat_fmt_get_vp, foreach->depth, &foreach->variable, false, false, false); /* * Push the child, and yield for a later return. */ unlang_push(stack, g->children, frame->result, UNLANG_NEXT_SIBLING, UNLANG_SUB_FRAME); frame->repeat = true; return UNLANG_ACTION_PUSHED_CHILD; }