/** Check group membership attributes to see if a user is a member. * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in] check vp containing the group value (name or dn). * * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_check_cached(ldap_instance_t const *inst, REQUEST *request, VALUE_PAIR *check) { VALUE_PAIR *vp; int ret; vp_cursor_t cursor; fr_cursor_init(&cursor, &request->config_items); /* * We return RLM_MODULE_INVALID here as an indication * the caller should try a dynamic group lookup instead. */ vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY); if (!vp) return RLM_MODULE_INVALID; fr_cursor_first(&cursor); while ((vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY))) { ret = paircmp_op(T_OP_CMP_EQ, vp, check); if (ret == 1) { RDEBUG2("User found. Matched cached membership"); return RLM_MODULE_OK; } if (ret < -1) { return RLM_MODULE_FAIL; } } RDEBUG2("Cached membership not found"); return RLM_MODULE_NOTFOUND; }
/** Wind cursor to the last pair in the list * * @addtogroup module_safe * * @param cursor to operate on. * @return #VALUE_PAIR at the end of the list. */ VALUE_PAIR *fr_cursor_last(vp_cursor_t *cursor) { if (!cursor->first || !*cursor->first) return NULL; /* Need to start at the start */ if (!cursor->current) fr_cursor_first(cursor); /* Wind to the end */ while (cursor->next) fr_cursor_next(cursor); return cursor->current; }
/* * get the vps and put them in perl hash * If one VP have multiple values it is added as array_ref * Example for this is Cisco-AVPair that holds multiple values. * Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'} */ static void perl_store_vps(TALLOC_CTX *ctx, VALUE_PAIR *vps, HV *rad_hv) { VALUE_PAIR *head, *sublist; AV *av; char const *name; char namebuf[256]; char buffer[1024]; int len; hv_undef(rad_hv); /* * Copy the valuepair list so we can remove attributes * we've already processed. This is a horrible hack to * get around various other stupidity. */ head = paircopy(ctx, vps); while (head) { vp_cursor_t cursor; /* * Tagged attributes are added to the hash with name * <attribute>:<tag>, others just use the normal attribute * name as the key. */ if (head->da->flags.has_tag && (head->tag != 0)) { snprintf(namebuf, sizeof(namebuf), "%s:%d", head->da->name, head->tag); name = namebuf; } else { name = head->da->name; } /* * Create a new list with all the attributes like this one * which are in the same tag group. */ sublist = NULL; pairfilter(ctx, &sublist, &head, head->da->attr, head->da->vendor, head->tag); fr_cursor_init(&cursor, &sublist); /* * Attribute has multiple values */ if (fr_cursor_next(&cursor)) { VALUE_PAIR *vp; av = newAV(); for (vp = fr_cursor_first(&cursor); vp; vp = fr_cursor_next(&cursor)) { len = vp_prints_value(buffer, sizeof(buffer), vp, 0); av_push(av, newSVpv(buffer, len)); } (void)hv_store(rad_hv, name, strlen(name), newRV_noinc((SV *)av), 0); /* * Attribute has a single value, so its value just gets * added to the hash. */ } else { len = vp_prints_value(buffer, sizeof(buffer), sublist, 0); (void)hv_store(rad_hv, name, strlen(name), newSVpv(buffer, len), 0); } pairfree(&sublist); } rad_assert(!head); }
/** Uses paircmp to verify all VALUE_PAIRs in list match the filter defined by check * * @note will sort both filter and list in place. * * @param failed pointer to an array to write the pointers of the filter/list attributes that didn't match. * May be NULL. * @param filter attributes to check list against. * @param list attributes, probably a request or reply */ bool pairvalidate_relaxed(VALUE_PAIR const *failed[2], VALUE_PAIR *filter, VALUE_PAIR *list) { vp_cursor_t filter_cursor; vp_cursor_t list_cursor; VALUE_PAIR *check, *last_check = NULL, *match = NULL; if (!filter && !list) { return true; } /* * This allows us to verify the sets of validate and reply are equal * i.e. we have a validate rule which matches every reply attribute. * * @todo this should be removed one we have sets and lists */ pairsort(&filter, attrtagcmp); pairsort(&list, attrtagcmp); fr_cursor_init(&list_cursor, &list); for (check = fr_cursor_init(&filter_cursor, &filter); check; check = fr_cursor_next(&filter_cursor)) { /* * Were processing check attributes of a new type. */ if (!ATTRIBUTE_EQ(last_check, check)) { /* * Record the start of the matching attributes in the pair list * For every other operator we require the match to be present */ match = fr_cursor_next_by_da(&list_cursor, check->da, check->tag); if (!match) { if (check->op == T_OP_CMP_FALSE) continue; goto mismatch; } fr_cursor_init(&list_cursor, &match); last_check = check; } /* * Now iterate over all attributes of the same type. */ for (match = fr_cursor_first(&list_cursor); ATTRIBUTE_EQ(match, check); match = fr_cursor_next(&list_cursor)) { /* * This attribute passed the filter */ if (!paircmp(check, match)) goto mismatch; } } return true; mismatch: if (failed) { failed[0] = check; failed[1] = match; } return false; }
/* * 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; }
/* * Handles multiple EAP-Message attrs * ie concatenates all to get the complete EAP packet. * * NOTE: Sometimes Framed-MTU might contain the length of EAP-Message, * refer fragmentation in rfc2869. */ eap_packet_raw_t *eap_vp2packet(TALLOC_CTX *ctx, VALUE_PAIR *vps) { VALUE_PAIR *first, *i; eap_packet_raw_t *eap_packet; unsigned char *ptr; uint16_t len; int total_len; vp_cursor_t cursor; /* * Get only EAP-Message attribute list */ first = fr_pair_find_by_num(vps, 0, PW_EAP_MESSAGE, TAG_ANY); if (!first) { fr_strerror_printf("EAP-Message not found"); return NULL; } /* * Sanity check the length before doing anything. */ if (first->vp_length < 4) { fr_strerror_printf("EAP packet is too short"); return NULL; } /* * Get the Actual length from the EAP packet * First EAP-Message contains the EAP packet header */ memcpy(&len, first->vp_strvalue + 2, sizeof(len)); len = ntohs(len); /* * Take out even more weird things. */ if (len < 4) { fr_strerror_printf("EAP packet has invalid length (less than 4 bytes)"); return NULL; } /* * Sanity check the length, BEFORE allocating memory. */ total_len = 0; fr_cursor_init(&cursor, &first); while ((i = fr_cursor_next_by_num(&cursor, 0, PW_EAP_MESSAGE, TAG_ANY))) { total_len += i->vp_length; if (total_len > len) { fr_strerror_printf("Malformed EAP packet. Length in packet header %i, " "does not match actual length %i", len, total_len); return NULL; } } /* * If the length is SMALLER, die, too. */ if (total_len < len) { fr_strerror_printf("Malformed EAP packet. Length in packet header does not " "match actual length"); return NULL; } /* * Now that we know the lengths are OK, allocate memory. */ eap_packet = (eap_packet_raw_t *) talloc_zero_array(ctx, uint8_t, len); if (!eap_packet) { return NULL; } /* * Copy the data from EAP-Message's over to our EAP packet. */ ptr = (unsigned char *)eap_packet; /* RADIUS ensures order of attrs, so just concatenate all */ fr_cursor_first(&cursor); while ((i = fr_cursor_next_by_num(&cursor, 0, PW_EAP_MESSAGE, TAG_ANY))) { memcpy(ptr, i->vp_strvalue, i->vp_length); ptr += i->vp_length; } return eap_packet; }
/** 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; }
/* * given a radius request with many attributes in the EAP-SIM range, build * them all into a single EAP-SIM body. * */ int map_eapsim_basictypes(RADIUS_PACKET *r, eap_packet_t *ep) { VALUE_PAIR *vp; int encoded_size; uint8_t *encodedmsg, *attr; unsigned int id, eapcode; uint8_t *macspace; uint8_t const *append; int appendlen; unsigned char subtype; vp_cursor_t cursor; macspace = NULL; append = NULL; appendlen = 0; /* * encodedmsg is now an EAP-SIM message. * it might be too big for putting into an EAP-Type-SIM * */ subtype = (vp = fr_pair_find_by_num(r->vps, PW_EAP_SIM_SUBTYPE, 0, TAG_ANY)) ? vp->vp_integer : EAPSIM_START; id = (vp = fr_pair_find_by_num(r->vps, PW_EAP_ID, 0, TAG_ANY)) ? vp->vp_integer : ((int)getpid() & 0xff); eapcode = (vp = fr_pair_find_by_num(r->vps, PW_EAP_CODE, 0, TAG_ANY)) ? vp->vp_integer : PW_EAP_REQUEST; /* * take a walk through the attribute list to see how much space * that we need to encode all of this. */ encoded_size = 0; for (vp = fr_cursor_init(&cursor, &r->vps); vp; vp = fr_cursor_next(&cursor)) { int roundedlen; int vplen; if ((vp->da->attr < PW_EAP_SIM_BASE) || (vp->da->attr >= (PW_EAP_SIM_BASE + 256))) { continue; } vplen = vp->vp_length; /* * the AT_MAC attribute is a bit different, when we get to this * attribute, we pull the contents out, save it for later * processing, set the size to 16 bytes (plus 2 bytes padding). * * At this point, we only care about the size. */ if(vp->da->attr == PW_EAP_SIM_MAC) { vplen = 18; } /* round up to next multiple of 4, after taking in * account the type and length bytes */ roundedlen = (vplen + 2 + 3) & ~3; encoded_size += roundedlen; } if (ep->code != PW_EAP_SUCCESS) { ep->code = eapcode; } ep->id = (id & 0xff); ep->type.num = PW_EAP_SIM; /* * if no attributes were found, do very little. * */ if (encoded_size == 0) { encodedmsg = talloc_array(ep, uint8_t, 3); /* FIX: could be NULL */ encodedmsg[0] = subtype; encodedmsg[1] = 0; encodedmsg[2] = 0; ep->type.length = 3; ep->type.data = encodedmsg; return 0; } /* * figured out the length, so allocate some space for the results. * * Note that we do not bother going through an "EAP" stage, which * is a bit strange compared to the unmap, which expects to see * an EAP-SIM virtual attributes. * * EAP is 1-code, 1-identifier, 2-length, 1-type = 5 overhead. * * SIM code adds a subtype, and 2 bytes of reserved = 3. * */ encoded_size += 3; encodedmsg = talloc_array(ep, uint8_t, encoded_size); if (!encodedmsg) { return 0; } memset(encodedmsg, 0, encoded_size); /* * now walk the attributes again, sticking them in. * * we go three bytes into the encoded message, because there are two * bytes of reserved, and we will fill the "subtype" in later. * */ attr = encodedmsg+3; for (vp = fr_cursor_first(&cursor); vp; vp = fr_cursor_next(&cursor)) { int roundedlen; if(vp->da->attr < PW_EAP_SIM_BASE || vp->da->attr >= PW_EAP_SIM_BASE + 256) { continue; } /* * the AT_MAC attribute is a bit different, when we get to this * attribute, we pull the contents out, save it for later * processing, set the size to 16 bytes (plus 2 bytes padding). * * At this point, we put in zeros, and remember where the * sixteen bytes go. */ if(vp->da->attr == PW_EAP_SIM_MAC) { roundedlen = 20; memset(&attr[2], 0, 18); macspace = &attr[4]; append = vp->vp_octets; appendlen = vp->vp_length; } else { roundedlen = (vp->vp_length + 2 + 3) & ~3; memset(attr, 0, roundedlen); memcpy(&attr[2], vp->vp_strvalue, vp->vp_length); } attr[0] = vp->da->attr - PW_EAP_SIM_BASE; attr[1] = roundedlen >> 2; attr += roundedlen; } encodedmsg[0] = subtype; ep->type.length = encoded_size; ep->type.data = encodedmsg; /* * if macspace was set and we have a key, * then we should calculate the HMAC-SHA1 of the resulting EAP-SIM * packet, appended with the value of append. */ vp = fr_pair_find_by_num(r->vps, PW_EAP_SIM_KEY, 0, TAG_ANY); if(macspace != NULL && vp != NULL) { unsigned char *buffer; eap_packet_raw_t *hdr; uint16_t hmaclen, total_length = 0; unsigned char sha1digest[20]; total_length = EAP_HEADER_LEN + 1 + encoded_size; hmaclen = total_length + appendlen; buffer = talloc_array(r, uint8_t, hmaclen); hdr = (eap_packet_raw_t *) buffer; if (!hdr) { talloc_free(encodedmsg); return 0; } hdr->code = eapcode & 0xFF; hdr->id = (id & 0xFF); total_length = htons(total_length); memcpy(hdr->length, &total_length, sizeof(total_length)); hdr->data[0] = PW_EAP_SIM; /* copy the data */ memcpy(&hdr->data[1], encodedmsg, encoded_size); /* copy the nonce */ memcpy(&hdr->data[encoded_size+1], append, appendlen); /* HMAC it! */ fr_hmac_sha1(sha1digest, buffer, hmaclen, vp->vp_octets, vp->vp_length); /* done with the buffer, free it */ talloc_free(buffer); /* now copy the digest to where it belongs in the AT_MAC */ /* note that it is truncated to 128-bits */ memcpy(macspace, sha1digest, 16); } /* if we had an AT_MAC and no key, then fail */ if ((macspace != NULL) && !vp) { if (encodedmsg != NULL) { talloc_free(encodedmsg); } return 0; } return 1; }