/** Copy matching pairs * * Copy pairs of a matching attribute number, vendor number and tag from the * the input list to a new list, and returns the head of this list. * * @param[in] ctx for talloc * @param[in] from whence to copy VALUE_PAIRs. * @param[in] attr to match, if 0 input list will not be filtered by attr. * @param[in] vendor to match. * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs. * @return the head of the new VALUE_PAIR list or NULL on error. */ VALUE_PAIR *paircopy_by_num(TALLOC_CTX *ctx, VALUE_PAIR *from, unsigned int attr, unsigned int vendor, int8_t tag) { vp_cursor_t src, dst; VALUE_PAIR *out = NULL, *vp; fr_cursor_init(&dst, &out); for (vp = fr_cursor_init(&src, &from); vp; vp = fr_cursor_next(&src)) { VERIFY_VP(vp); if ((vp->da->attr != attr) || (vp->da->vendor != vendor)) { continue; } if (vp->da->flags.has_tag && !TAG_EQ(tag, vp->tag)) { continue; } vp = paircopyvp(ctx, vp); if (!vp) { pairfree(&out); return NULL; } fr_cursor_insert(&dst, vp); } return out; }
static int arp_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) { int i; uint8_t const *p = request->packet->data, *end = p + request->packet->data_len; fr_cursor_t cursor; fr_cursor_init(&cursor, &request->packet->vps); for (i = 0; header_names[i].name != NULL; i++) { ssize_t ret; size_t len; fr_dict_attr_t const *da; VALUE_PAIR *vp = NULL; len = header_names[i].len; if (!fr_cond_assert((size_t)(end - p) < len)) return -1; /* Should have been detected in socket_recv */ da = fr_dict_attr_by_name(dict_arp, header_names[i].name); if (!da) return 0; MEM(vp = fr_pair_afrom_da(request->packet, da)); ret = fr_value_box_from_network(vp, &vp->data, da->type, da, p, len, true); if (ret <= 0) { fr_pair_to_unknown(vp); fr_pair_value_memcpy(vp, p, len); } DEBUG2("&%pP", vp); fr_cursor_insert(&cursor, vp); } return 0; }
/** Merges two sets of VPs * * The list represented by cursor will hold the union of cursor and * add lists. * * @param cursor to insert VALUE_PAIRs with * @param add one or more VALUE_PAIRs. */ void fr_cursor_merge(vp_cursor_t *cursor, VALUE_PAIR *add) { vp_cursor_t from; VALUE_PAIR *vp; for (vp = fr_cursor_init(&from, &add); vp; vp = fr_cursor_next(&from)) { fr_cursor_insert(cursor, vp); } }
/** Merges multiple VALUE_PAIR into the cursor * * Add multiple VALUE_PAIR from add to cursor. * * @addtogroup module_safe * * @param cursor to insert VALUE_PAIRs with * @param add one or more VALUE_PAIRs (may be NULL, which results in noop). */ void fr_cursor_merge(vp_cursor_t *cursor, VALUE_PAIR *add) { vp_cursor_t from; VALUE_PAIR *vp; if (!add) return; if (!fr_assert(cursor->first)) return; /* cursor must have been initialised */ for (vp = fr_cursor_init(&from, &add); vp; vp = fr_cursor_next(&from)) { fr_cursor_insert(cursor, vp); } }
/** Copy a pairlist. * * Copy all pairs from 'from' regardless of tag, attribute or vendor. * * @param[in] ctx for new VALUE_PAIRs to be allocated in. * @param[in] from whence to copy VALUE_PAIRs. * @return the head of the new VALUE_PAIR list or NULL on error. */ VALUE_PAIR *paircopy(TALLOC_CTX *ctx, VALUE_PAIR *from) { vp_cursor_t src, dst; VALUE_PAIR *out = NULL, *vp; fr_cursor_init(&dst, &out); for (vp = fr_cursor_init(&src, &from); vp; vp = fr_cursor_next(&src)) { VERIFY_VP(vp); vp = paircopyvp(ctx, vp); if (!vp) { pairfree(&out); return NULL; } fr_cursor_insert(&dst, vp); /* paircopy sets next pointer to NULL */ } return out; }
static xlat_action_t xlat_delay_resume(TALLOC_CTX *ctx, fr_cursor_t *out, REQUEST *request, UNUSED void const *xlat_inst, UNUSED void *xlat_thread_inst, UNUSED fr_value_box_t **in, void *rctx) { struct timeval *yielded_at = talloc_get_type_abort(rctx, struct timeval); struct timeval delayed, now; fr_value_box_t *vb; gettimeofday(&now, NULL); fr_timeval_subtract(&delayed, &now, yielded_at); talloc_free(yielded_at); RDEBUG3("Request delayed by %pVs", fr_box_timeval(delayed)); MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_TIMEVAL, NULL, false)); vb->vb_timeval = delayed; fr_cursor_insert(out, vb); return XLAT_ACTION_DONE; }
VALUE_PAIR *eap_packet2vp(RADIUS_PACKET *packet, eap_packet_raw_t const *eap) { int total, size; uint8_t const *ptr; VALUE_PAIR *head = NULL; VALUE_PAIR *vp; vp_cursor_t out; total = eap->length[0] * 256 + eap->length[1]; if (total == 0) { DEBUG("Asked to encode empty EAP-Message!"); return NULL; } ptr = (uint8_t const *) eap; fr_cursor_init(&out, &head); do { size = total; if (size > 253) size = 253; vp = fr_pair_afrom_num(packet, 0, PW_EAP_MESSAGE); if (!vp) { fr_pair_list_free(&head); return NULL; } fr_pair_value_memcpy(vp, ptr, size); fr_cursor_insert(&out, vp); ptr += size; total -= size; } while (total > 0); return head; }
/** 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; }
/* * Common attr_filter checks */ static rlm_rcode_t CC_HINT(nonnull(1,2)) attr_filter_common(void *instance, REQUEST *request, RADIUS_PACKET *packet) { rlm_attr_filter_t *inst = instance; VALUE_PAIR *vp; vp_cursor_t input, check, out; VALUE_PAIR *input_item, *check_item, *output; PAIR_LIST *pl; int found = 0; int pass, fail = 0; char const *keyname = NULL; char buffer[256]; if (!packet) return RLM_MODULE_NOOP; if (!inst->key) { VALUE_PAIR *namepair; namepair = pairfind(request->packet->vps, PW_REALM, 0, TAG_ANY); if (!namepair) { return (RLM_MODULE_NOOP); } keyname = namepair->vp_strvalue; } else { int len; len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL); if (len < 0) { return RLM_MODULE_FAIL; } if (len == 0) { return RLM_MODULE_NOOP; } keyname = buffer; } /* * Head of the output list */ output = NULL; fr_cursor_init(&out, &output); /* * Find the attr_filter profile entry for the entry. */ for (pl = inst->attrs; pl; pl = pl->next) { int fall_through = 0; int relax_filter = inst->relaxed; /* * If the current entry is NOT a default, * AND the realm does NOT match the current entry, * then skip to the next entry. */ if ((strcmp(pl->name, "DEFAULT") != 0) && (strcmp(keyname, pl->name) != 0)) { continue; } RDEBUG2("Matched entry %s at line %d", pl->name, pl->lineno); found = 1; for (check_item = fr_cursor_init(&check, &pl->check); check_item; check_item = fr_cursor_next(&check)) { if (!check_item->da->vendor && (check_item->da->attr == PW_FALL_THROUGH) && (check_item->vp_integer == 1)) { fall_through = 1; continue; } else if (!check_item->da->vendor && check_item->da->attr == PW_RELAX_FILTER) { relax_filter = check_item->vp_integer; continue; } /* * If it is a SET operator, add the attribute to * the output list without checking it. */ if (check_item->op == T_OP_SET ) { vp = paircopyvp(packet, check_item); if (!vp) { goto error; } radius_xlat_do(request, vp); fr_cursor_insert(&out, vp); } } /* * Iterate through the input items, comparing * each item to every rule, then moving it to the * output list only if it matches all rules * for that attribute. IE, Idle-Timeout is moved * only if it matches all rules that describe an * Idle-Timeout. */ for (input_item = fr_cursor_init(&input, &packet->vps); input_item; input_item = fr_cursor_next(&input)) { pass = fail = 0; /* reset the pass,fail vars for each reply item */ /* * Reset the check_item pointer to beginning of the list */ for (check_item = fr_cursor_first(&check); check_item; check_item = fr_cursor_next(&check)) { /* * Vendor-Specific is special, and matches any VSA if the * comparison is always true. */ if ((check_item->da->attr == PW_VENDOR_SPECIFIC) && (input_item->da->vendor != 0) && (check_item->op == T_OP_CMP_TRUE)) { pass++; continue; } if (input_item->da == check_item->da) { check_pair(request, check_item, input_item, &pass, &fail); } } RDEBUG3("Attribute \"%s\" allowed by %i rules, disallowed by %i rules", input_item->da->name, pass, fail); /* * Only move attribute if it passed all rules, or if the config says we * should copy unmatched attributes ('relaxed' mode). */ if (fail == 0 && (pass > 0 || relax_filter)) { if (!pass) { RDEBUG3("Attribute \"%s\" allowed by relaxed mode", input_item->da->name); } vp = paircopyvp(packet, input_item); if (!vp) { goto error; } fr_cursor_insert(&out, vp); } } /* If we shouldn't fall through, break */ if (!fall_through) { break; } } /* * No entry matched. We didn't do anything. */ if (!found) { rad_assert(!output); return RLM_MODULE_NOOP; } /* * Replace the existing request list with our filtered one */ pairfree(&packet->vps); packet->vps = output; if (request->packet->code == PW_CODE_AUTHENTICATION_REQUEST) { request->username = pairfind(request->packet->vps, PW_STRIPPED_USER_NAME, 0, TAG_ANY); if (!request->username) { request->username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); } request->password = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); } return RLM_MODULE_UPDATED; error: pairfree(&output); return RLM_MODULE_FAIL; }
/** Callback for map_to_request * * Performs exactly the same job as map_to_vp, but pulls attribute values from LDAP entries * * @see map_to_vp */ int rlm_ldap_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { rlm_ldap_result_t *self = uctx; VALUE_PAIR *head = NULL, *vp; vp_cursor_t cursor; int i; fr_cursor_init(&cursor, &head); switch (map->lhs->type) { /* * This is a mapping in the form of: * <list>: += <ldap attr> * * Where <ldap attr> is: * <list>:<attr> <op> <value> * * It is to allow for legacy installations which stored * RADIUS control and reply attributes in separate LDAP * attributes. */ case TMPL_TYPE_LIST: for (i = 0; i < self->count; i++) { vp_map_t *attr = NULL; RDEBUG3("Parsing valuepair string \"%s\"", self->values[i]->bv_val); if (map_afrom_attr_str(ctx, &attr, self->values[i]->bv_val, map->lhs->tmpl_request, map->lhs->tmpl_list, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { RWDEBUG("Failed parsing \"%s\" as valuepair (%s), skipping...", fr_strerror(), self->values[i]->bv_val); continue; } if (attr->lhs->tmpl_request != map->lhs->tmpl_request) { RWDEBUG("valuepair \"%s\" has conflicting request qualifier (%s vs %s), skipping...", self->values[i]->bv_val, fr_int2str(request_refs, attr->lhs->tmpl_request, "<INVALID>"), fr_int2str(request_refs, map->lhs->tmpl_request, "<INVALID>")); next_pair: talloc_free(attr); continue; } if ((attr->lhs->tmpl_list != map->lhs->tmpl_list)) { RWDEBUG("valuepair \"%s\" has conflicting list qualifier (%s vs %s), skipping...", self->values[i]->bv_val, fr_int2str(pair_lists, attr->lhs->tmpl_list, "<INVALID>"), fr_int2str(pair_lists, map->lhs->tmpl_list, "<INVALID>")); goto next_pair; } if (map_to_vp(request, &vp, request, attr, NULL) < 0) { RWDEBUG("Failed creating attribute for valuepair \"%s\", skipping...", self->values[i]->bv_val); goto next_pair; } fr_cursor_merge(&cursor, vp); talloc_free(attr); /* * Only process the first value, unless the operator is += */ if (map->op != T_OP_ADD) break; } break; /* * Iterate over all the retrieved values, * don't try and be clever about changing operators * just use whatever was set in the attribute map. */ case TMPL_TYPE_ATTR: for (i = 0; i < self->count; i++) { if (!self->values[i]->bv_len) continue; vp = pairalloc(ctx, map->lhs->tmpl_da); rad_assert(vp); if (pairparsevalue(vp, self->values[i]->bv_val, self->values[i]->bv_len) < 0) { char *escaped; escaped = fr_aprints(vp, self->values[i]->bv_val, self->values[i]->bv_len, '"'); RWDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped, map->lhs->tmpl_da->name, fr_strerror()); talloc_free(vp); /* also frees escaped */ continue; } vp->op = map->op; fr_cursor_insert(&cursor, vp); /* * Only process the first value, unless the operator is += */ if (map->op != T_OP_ADD) break; } break; default: rad_assert(0); } *out = head; return 0; }
/* * 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; }
/** Convert value_pair_map_t to VALUE_PAIR(s) and add them to a REQUEST. * * Takes a single value_pair_map_t, resolves request and list identifiers * to pointers in the current request, then attempts to retrieve module * specific value(s) using callback, and adds the resulting values to the * correct request/list. * * @param request The current request. * @param map specifying destination attribute and location and src identifier. * @param func to retrieve module specific values and convert them to * VALUE_PAIRS. * @param ctx to be passed to func. * @param src name to be used in debugging if different from map value. * @return -1 if the operation failed, -2 in the source attribute wasn't valid, 0 on success. */ int radius_map2request(REQUEST *request, value_pair_map_t const *map, UNUSED char const *src, radius_tmpl_getvalue_t func, void *ctx) { int rcode, num; VALUE_PAIR **list, *vp, *head = NULL; REQUEST *context; TALLOC_CTX *parent; vp_cursor_t cursor; /* * Sanity check inputs. We can have a list or attribute * as a destination. */ if ((map->dst->type != VPT_TYPE_LIST) && (map->dst->type != VPT_TYPE_ATTR)) { REDEBUG("Invalid mapping destination"); return -2; } context = request; if (radius_request(&context, map->dst->request) < 0) { REDEBUG("Mapping \"%s\" -> \"%s\" invalid in this context", map->src->name, map->dst->name); return -2; } /* * If there's no CoA packet and we're updating it, * auto-allocate it. */ if (((map->dst->list == PAIR_LIST_COA) || (map->dst->list == PAIR_LIST_DM)) && !request->coa) { request_alloc_coa(context); if (map->dst->list == PAIR_LIST_COA) { context->coa->proxy->code = PW_CODE_COA_REQUEST; } else { context->coa->proxy->code = PW_CODE_DISCONNECT_REQUEST; } } list = radius_list(context, map->dst->list); if (!list) { REDEBUG("Mapping \"%s\" -> \"%s\" invalid in this context", map->src->name, map->dst->name); return -2; } parent = radius_list_ctx(context, map->dst->list); /* * The callback should either return -1 to signify operations error, -2 when it can't find the * attribute or list being referenced, or 0 to signify success. * It may return "sucess", but still have no VPs to work with. */ rcode = func(&head, request, map, ctx); if (rcode < 0) { rad_assert(!head); return rcode; } if (!head) return 0; /* * Reparent the VP */ for (vp = fr_cursor_init(&cursor, &head); vp; vp = fr_cursor_next(&cursor)) { VERIFY_VP(vp); if (debug_flag) debug_map(request, map, vp); (void) talloc_steal(parent, vp); } /* * List to list copies. */ if (map->dst->type == VPT_TYPE_LIST) { switch (map->op) { case T_OP_CMP_FALSE: rad_assert(head == NULL); pairfree(list); if (map->dst->list == PAIR_LIST_REQUEST) { context->username = NULL; context->password = NULL; } break; case T_OP_SET: if (map->src->type == VPT_TYPE_LIST) { pairfree(list); *list = head; } else { case T_OP_EQ: rad_assert(map->src->type == VPT_TYPE_EXEC); pairmove(parent, list, &head); pairfree(&head); } if (map->dst->list == PAIR_LIST_REQUEST) { context->username = pairfind(head, PW_USER_NAME, 0, TAG_ANY); context->password = pairfind(head, PW_USER_PASSWORD, 0, TAG_ANY); } break; case T_OP_ADD: pairadd(list, head); break; default: pairfree(&head); return -1; } return 0; } /* * We now should have only one destination attribute, and * only one source attribute. */ rad_assert(head->next == NULL); /* * Find the destination attribute. We leave with either * the cursor and vp pointing to the attribute, or vp is * NULL. */ num = map->dst->num; for (vp = fr_cursor_init(&cursor, list); vp != NULL; vp = fr_cursor_next(&cursor)) { VERIFY_VP(vp); if ((vp->da == map->dst->da) && (!vp->da->flags.has_tag || (map->dst->tag == TAG_ANY) || (vp->tag == map->dst->tag))) { if (num == 0) break; num--; } } /* * Figure out what to do with the source attribute. */ switch (map->op) { case T_OP_CMP_FALSE: /* remove matching attributes */ pairfree(&head); if (!vp) return 0; /* * Wildcard: delete all of the matching ones, * based on tag. */ if (!map->dst->num) { pairdelete(list, map->dst->da->attr, map->dst->da->vendor, map->dst->tag); vp = NULL; } else { /* * We've found the Nth one. Delete it, and only * it. */ vp = fr_cursor_remove(&cursor); } /* * Check that the User-Name and User-Password * caches point to the correct attribute. */ fixup: if (map->dst->list == PAIR_LIST_REQUEST) { context->username = pairfind(*list, PW_USER_NAME, 0, TAG_ANY); context->password = pairfind(*list, PW_USER_PASSWORD, 0, TAG_ANY); } pairfree(&vp); return 0; case T_OP_EQ: /* set only if not already set */ if (vp) { pairfree(&head); return 0; } fr_cursor_insert(&cursor, head); goto fixup; case T_OP_SET: /* over-write if existing, or else add */ if (vp) vp = fr_cursor_remove(&cursor); fr_cursor_insert(&cursor, head); goto fixup; case T_OP_ADD: /* append no matter what */ vp = NULL; pairadd(list, head); goto fixup; case T_OP_SUB: /* delete if it matches */ head->op = T_OP_CMP_EQ; rcode = radius_compare_vps(NULL, head, vp); pairfree(&head); if (rcode == 0) { vp = fr_cursor_remove(&cursor); goto fixup; } return 0; default: /* filtering operators */ /* * If the VP doesn't exist, the filters will add * it with the given value. */ if (!vp) { fr_cursor_insert(&cursor, head); goto fixup; } break; } /* * The LHS exists. We need to limit it's value based on * the operator, and the value of the RHS. */ head->op = map->op; rcode = radius_compare_vps(NULL, head, vp); head->op = T_OP_SET; switch (map->op) { case T_OP_CMP_EQ: if (rcode == 0) { leave: pairfree(&head); break; } replace: vp = fr_cursor_remove(&cursor); fr_cursor_insert(&cursor, head); goto fixup; case T_OP_LE: if (rcode <= 0) goto leave; goto replace; case T_OP_GE: if (rcode >= 0) goto leave; goto replace; default: pairfree(&head); return -1; } 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. * @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(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, LDAPMessage *entry, char const *attr) { rlm_rcode_t rcode = RLM_MODULE_OK; struct berval **values; size_t value_len = 0; TALLOC_CTX *value_pool; 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, **vps; TALLOC_CTX *ctx; vp_cursor_t 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); vps = radius_list(request, PAIR_LIST_CONTROL); ctx = radius_list_ctx(request, PAIR_LIST_CONTROL); fr_cursor_init(&cursor, vps); /* * Avoid allocing buffers for each value. * * The old code used ldap_get_values, which was likely doing * a very similar thing internally to produce \0 terminated * buffers from bervalues. */ for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) value_len += values[i]->bv_len + 1; value_pool = talloc_pool(request, value_len); 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 = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&cursor, vp); RDEBUG("Added %s with value \"%s\" to control list", inst->cache_da->name, vp->vp_strvalue); /* * 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_pool, values[i]); } } if (inst->cacheable_group_name) { /* * The easy case, we're caching names and we got a name. */ if (!is_dn) { MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, vp->vp_strvalue); /* * 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_pool, 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_pool); return rcode; } MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, name, talloc_array_length(name) - 1); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, name); 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_pool); if (rcode != RLM_MODULE_OK) return rcode; dn_p = group_dn; while (*dn_p) { MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrcpy(vp, *dn_p); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, *dn_p); ldap_memfree(*dn_p); dn_p++; } 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; }
static int rlm_ldap_map_getvalue(VALUE_PAIR **out, REQUEST *request, value_pair_map_t const *map, void *ctx) { rlm_ldap_result_t *self = ctx; VALUE_PAIR *head = NULL, *vp; vp_cursor_t cursor; int i; fr_cursor_init(&cursor, &head); switch (map->dst->type) { /* * This is a mapping in the form of: * <list>: += <ldap attr> * * Where <ldap attr> is: * <list>:<attr> <op> <value> * * It is to allow for legacy installations which stored * RADIUS control and reply attributes in separate LDAP * attributes. */ case VPT_TYPE_LIST: for (i = 0; i < self->count; i++) { value_pair_map_t *attr = NULL; RDEBUG3("Parsing valuepair string \"%s\"", self->values[i]); if (radius_strpair2map(&attr, request, self->values[i], map->dst->vpt_request, map->dst->vpt_list, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { RWDEBUG("Failed parsing \"%s\" as valuepair, skipping...", self->values[i]); continue; } if (attr->dst->vpt_request != map->dst->vpt_request) { RWDEBUG("valuepair \"%s\" has conflicting request qualifier (%s vs %s), skipping...", self->values[i], fr_int2str(request_refs, attr->dst->vpt_request, "<INVALID>"), fr_int2str(request_refs, map->dst->vpt_request, "<INVALID>")); next_pair: talloc_free(attr); continue; } if ((attr->dst->vpt_list != map->dst->vpt_list)) { RWDEBUG("valuepair \"%s\" has conflicting list qualifier (%s vs %s), skipping...", self->values[i], fr_int2str(pair_lists, attr->dst->vpt_list, "<INVALID>"), fr_int2str(pair_lists, map->dst->vpt_list, "<INVALID>")); goto next_pair; } if (radius_map2vp(&vp, request, attr, NULL) < 0) { RWDEBUG("Failed creating attribute for \"%s\", skipping...", self->values[i]); goto next_pair; } fr_cursor_insert(&cursor, vp); talloc_free(attr); } break; /* * Iterate over all the retrieved values, * don't try and be clever about changing operators * just use whatever was set in the attribute map. */ case VPT_TYPE_ATTR: for (i = 0; i < self->count; i++) { vp = pairalloc(request, map->dst->vpt_da); rad_assert(vp); if (!pairparsevalue(vp, self->values[i])) { RDEBUG("Failed parsing value for \"%s\"", map->dst->vpt_da->name); talloc_free(vp); continue; } vp->op = map->op; fr_cursor_insert(&cursor, vp); } break; default: rad_assert(0); } *out = head; return 0; }
/** Converts a serialized cache entry back into a structure * * @param c Cache entry to populate (should already be allocated) * @param in String representation of cache entry. * @param inlen Length of string. May be < 0 in which case strlen will be * used to calculate the length of the string. * @return 0 on success, -1 on error. */ int cache_deserialize(rlm_cache_entry_t *c, char *in, ssize_t inlen) { vp_cursor_t packet, control, reply; TALLOC_CTX *store = NULL; char *p, *q; store = talloc_pool(c, 1024); if (!store) return -1; if (inlen < 0) inlen = strlen(in); fr_cursor_init(&packet, &c->packet); fr_cursor_init(&control, &c->control); fr_cursor_init(&reply, &c->reply); p = in; while (((size_t)(p - in)) < (size_t)inlen) { value_pair_map_t *map = NULL; VALUE_PAIR *vp = NULL; ssize_t len; q = strchr(p, '\n'); if (!q) break; /* List should also be terminated with a \n */ *q = '\0'; if (map_afrom_attr_str(store, &map, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { fr_strerror_printf("Failed parsing pair: %s", p); goto error; } if (map->lhs->type != TMPL_TYPE_ATTR) { fr_strerror_printf("Pair left hand side \"%s\" parsed as %s, needed attribute. " "Check local dictionaries", map->lhs->name, fr_int2str(tmpl_names, map->lhs->type, "<INVALID>")); goto error; } if (map->rhs->type != TMPL_TYPE_LITERAL) { fr_strerror_printf("Pair right hand side \"%s\" parsed as %s, needed literal. " "Check serialized data quoting", map->rhs->name, fr_int2str(tmpl_names, map->rhs->type, "<INVALID>")); goto error; } /* * Convert literal to a type appropriate for * the VP. */ if (tmpl_cast_in_place(map->rhs, map->lhs->tmpl_da->type, map->lhs->tmpl_da) < 0) goto error; vp = pairalloc(c, map->lhs->tmpl_da); len = value_data_copy(vp, &vp->data, map->rhs->tmpl_data_type, &map->rhs->tmpl_data_value, map->rhs->tmpl_data_length); if (len < 0) goto error; /* * Pull out the special attributes, and set the * relevant cache entry fields. */ if (vp->da->vendor == 0) switch (vp->da->attr) { case PW_CACHE_CREATED: c->created = vp->vp_date; talloc_free(vp); goto next; case PW_CACHE_EXPIRES: c->expires = vp->vp_date; talloc_free(vp); goto next; default: break; } switch (map->lhs->tmpl_list) { case PAIR_LIST_REQUEST: fr_cursor_insert(&packet, vp); break; case PAIR_LIST_CONTROL: fr_cursor_insert(&control, vp); break; case PAIR_LIST_REPLY: fr_cursor_insert(&reply, vp); break; default: fr_strerror_printf("Invalid cache list for pair: %s", p); error: talloc_free(vp); talloc_free(map); return -1; } next: p = q + 1; talloc_free(map); } return 0; }
/** Create and insert a cache entry. * * @return RLM_MODULE_OK on success, RLM_MODULE_UPDATED if we merged the cache entry and RLM_MODULE_FAIL on failure. */ static rlm_rcode_t cache_insert(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle, char const *key, int ttl) { VALUE_PAIR *vp, *to_cache; vp_cursor_t src_list, cached_request, cached_reply, cached_control; value_pair_map_t const *map; bool merge = false; rlm_cache_entry_t *c; if ((inst->max_entries > 0) && inst->module->count && (inst->module->count(inst, request, handle) > inst->max_entries)) { RWDEBUG("Cache is full: %d entries", inst->max_entries); return RLM_MODULE_FAIL; } c = cache_alloc(inst, request); if (!c) return RLM_MODULE_FAIL; c->key = talloc_typed_strdup(c, key); c->created = c->expires = request->timestamp; c->expires += ttl; RDEBUG("Creating new cache entry"); fr_cursor_init(&cached_request, &c->packet); fr_cursor_init(&cached_reply, &c->reply); fr_cursor_init(&cached_control, &c->control); for (map = inst->maps; map != NULL; map = map->next) { rad_assert(map->lhs && map->rhs); if (map_to_vp(&to_cache, request, map, NULL) < 0) { RDEBUG("Skipping %s", map->rhs->name); continue; } /* * Reparent the VPs map_to_vp may return multiple. */ for (vp = fr_cursor_init(&src_list, &to_cache); vp; vp = fr_cursor_next(&src_list)) { VERIFY_VP(vp); /* * Prevent people from accidentally caching * cache control attributes. */ if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) { case PW_CACHE_TTL: case PW_CACHE_STATUS_ONLY: case PW_CACHE_READ_ONLY: case PW_CACHE_MERGE: case PW_CACHE_ENTRY_HITS: RDEBUG2("Skipping %s", vp->da->name); continue; default: break; } RINDENT(); if (RDEBUG_ENABLED2) map_debug_log(request, map, vp); REXDENT(); (void) talloc_steal(c, vp); vp->op = map->op; switch (map->lhs->tmpl_list) { case PAIR_LIST_REQUEST: fr_cursor_insert(&cached_request, vp); break; case PAIR_LIST_REPLY: fr_cursor_insert(&cached_reply, vp); break; case PAIR_LIST_CONTROL: fr_cursor_insert(&cached_control, vp); break; default: rad_assert(0); /* should have been caught by validation */ } } } /* * Check to see if we need to merge the entry into the request */ vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY); if (vp && (vp->vp_integer > 0)) merge = true; if (merge) cache_merge(inst, request, c); for (;;) { cache_status_t ret; ret = inst->module->insert(inst, request, handle, c); switch (ret) { case CACHE_RECONNECT: if (cache_reconnect(inst, request, handle) == 0) continue; return RLM_MODULE_FAIL; case CACHE_OK: RDEBUG("Commited entry, TTL %d seconds", ttl); cache_free(inst, &c); return merge ? RLM_MODULE_UPDATED : RLM_MODULE_OK; default: talloc_free(c); /* Failed insertion - use talloc_free not the driver free */ return RLM_MODULE_FAIL; } } }
/** Copy pairs matching a VPT in the current request * * @param ctx to allocate new VALUE_PAIRs under. * @param out where to write the copied vps. * @param request current request. * @param vpt the value pair template * @return -1 if VP could not be found, -2 if list could not be found, -3 if context could not be found. */ int tmpl_copy_vps(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, value_pair_tmpl_t const *vpt) { VALUE_PAIR **vps, *vp; REQUEST *current = request; vp_cursor_t from, to; rad_assert((vpt->type == TMPL_TYPE_ATTR) || (vpt->type == TMPL_TYPE_LIST)); if (out) *out = NULL; if (radius_request(¤t, vpt->tmpl_request) < 0) { return -3; } vps = radius_list(request, vpt->tmpl_list); if (!vps) { return -2; } switch (vpt->type) { /* * May not be found, but it *is* a known name. */ case TMPL_TYPE_ATTR: { int num; (void) fr_cursor_init(&to, out); (void) fr_cursor_init(&from, vps); vp = fr_cursor_next_by_da(&from, vpt->tmpl_da, vpt->tmpl_tag); if (!vp) return -1; switch (vpt->tmpl_num) { /* Copy all pairs of this type (and tag) */ case NUM_ALL: do { VERIFY_VP(vp); vp = paircopyvp(ctx, vp); if (!vp) { pairfree(out); return -4; } fr_cursor_insert(&to, vp); } while ((vp = fr_cursor_next_by_da(&from, vpt->tmpl_da, vpt->tmpl_tag))); break; /* Specific attribute number */ default: for (num = vpt->tmpl_num; num && vp; num--, vp = fr_cursor_next_by_da(&from, vpt->tmpl_da, vpt->tmpl_tag)) { VERIFY_VP(vp); } if (!vp) return -1; /* FALL-THROUGH */ /* Just copy the first pair */ case NUM_ANY: vp = paircopyvp(ctx, vp); if (!vp) { pairfree(out); return -4; } fr_cursor_insert(&to, vp); } } break; case TMPL_TYPE_LIST: vp = paircopy(ctx, *vps); if (!vp) return 0; fr_cursor_merge(&to, vp); break; default: rad_assert(0); } return 0; }