/* * 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; }
/* * The pairmove() function in src/lib/valuepair.c does all sorts of * extra magic that we don't want here. * * FIXME: integrate this with the code calling it, so that we * only paircopy() those attributes that we're really going to * use. */ void radius_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from, bool do_xlat) { int i, j, count, from_count, to_count, tailto; vp_cursor_t cursor; VALUE_PAIR *vp, *next, **last; VALUE_PAIR **from_list, **to_list; bool *edited = NULL; REQUEST *fixup = NULL; if (!request) return; /* * Set up arrays for editing, to remove some of the * O(N^2) dependencies. This also makes it easier to * insert and remove attributes. * * It also means that the operators apply ONLY to the * attributes in the original list. With the previous * implementation of pairmove(), adding two attributes * via "+=" and then "=" would mean that the second one * wasn't added, because of the existence of the first * one in the "to" list. This implementation doesn't * have that bug. * * Also, the previous implementation did NOT implement * "-=" correctly. If two of the same attributes existed * in the "to" list, and you tried to subtract something * matching the *second* value, then the pairdelete() * function was called, and the *all* attributes of that * number were deleted. With this implementation, only * the matching attributes are deleted. */ count = 0; for (vp = fr_cursor_init(&cursor, &from); vp; vp = fr_cursor_next(&cursor)) count++; from_list = talloc_array(request, VALUE_PAIR *, count); for (vp = fr_cursor_init(&cursor, to); vp; vp = fr_cursor_next(&cursor)) count++; to_list = talloc_array(request, VALUE_PAIR *, count); /* * Move the lists to the arrays, and break the list * chains. */ from_count = 0; for (vp = from; vp != NULL; vp = next) { next = vp->next; from_list[from_count++] = vp; vp->next = NULL; } to_count = 0; for (vp = *to; vp != NULL; vp = next) { next = vp->next; to_list[to_count++] = vp; vp->next = NULL; } tailto = to_count; edited = talloc_zero_array(request, bool, to_count); RDEBUG4("::: FROM %d TO %d MAX %d", from_count, to_count, count); /* * Now that we have the lists initialized, start working * over them. */ for (i = 0; i < from_count; i++) { int found; RDEBUG4("::: Examining %s", from_list[i]->da->name); if (do_xlat) radius_xlat_do(request, from_list[i]); /* * Attribute should be appended, OR the "to" list * is empty, and we're supposed to replace or * "add if not existing". */ if (from_list[i]->op == T_OP_ADD) goto append; found = false; for (j = 0; j < to_count; j++) { if (edited[j] || !to_list[j] || !from_list[i]) continue; /* * Attributes aren't the same, skip them. */ if (from_list[i]->da != to_list[j]->da) { continue; } /* * We don't use a "switch" statement here * because we want to break out of the * "for" loop over 'j' in most cases. */ /* * Over-write the FIRST instance of the * matching attribute name. We free the * one in the "to" list, and move over * the one in the "from" list. */ if (from_list[i]->op == T_OP_SET) { RDEBUG4("::: OVERWRITING %s FROM %d TO %d", to_list[j]->da->name, i, j); pairfree(&to_list[j]); to_list[j] = from_list[i]; from_list[i] = NULL; edited[j] = true; break; } /* * Add the attribute only if it does not * exist... but it exists, so we stop * looking. */ if (from_list[i]->op == T_OP_EQ) { found = true; break; } /* * Delete every attribute, independent * of its value. */ if (from_list[i]->op == T_OP_CMP_FALSE) { goto delete; } /* * Delete all matching attributes from * "to" */ if ((from_list[i]->op == T_OP_SUB) || (from_list[i]->op == T_OP_CMP_EQ) || (from_list[i]->op == T_OP_LE) || (from_list[i]->op == T_OP_GE)) { int rcode; int old_op = from_list[i]->op; /* * Check for equality. */ from_list[i]->op = T_OP_CMP_EQ; /* * If equal, delete the one in * the "to" list. */ rcode = radius_compare_vps(NULL, from_list[i], to_list[j]); /* * We may want to do more * subtractions, so we re-set the * operator back to it's original * value. */ from_list[i]->op = old_op; switch (old_op) { case T_OP_CMP_EQ: if (rcode != 0) goto delete; break; case T_OP_SUB: if (rcode == 0) { delete: RDEBUG4("::: DELETING %s FROM %d TO %d", from_list[i]->da->name, i, j); pairfree(&to_list[j]); to_list[j] = NULL; } break; /* * Enforce <=. If it's * >, replace it. */ case T_OP_LE: if (rcode > 0) { RDEBUG4("::: REPLACING %s FROM %d TO %d", from_list[i]->da->name, i, j); pairfree(&to_list[j]); to_list[j] = from_list[i]; from_list[i] = NULL; edited[j] = true; } break; case T_OP_GE: if (rcode < 0) { RDEBUG4("::: REPLACING %s FROM %d TO %d", from_list[i]->da->name, i, j); pairfree(&to_list[j]); to_list[j] = from_list[i]; from_list[i] = NULL; edited[j] = true; } break; } continue; } rad_assert(0 == 1); /* panic! */ }
/** Move pairs, replacing/over-writing them, and doing xlat. * * Move attributes from one list to the other if not already present. */ void radius_xlat_move(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR **from) { VALUE_PAIR **tailto, *i, *j, *next; VALUE_PAIR *tailfrom = NULL; VALUE_PAIR *found; /* * Point "tailto" to the end of the "to" list. */ tailto = to; for (i = *to; i; i = i->next) { tailto = &i->next; } /* * Loop over the "from" list. */ for (i = *from; i; i = next) { next = i->next; /* * Don't move 'fallthrough' over. */ if (!i->da->vendor && i->da->attr == PW_FALL_THROUGH) { tailfrom = i; continue; } /* * We've got to xlat the string before moving * it over. */ radius_xlat_do(request, i); found = pairfind(*to, i->da->attr, i->da->vendor, TAG_ANY); switch (i->op) { /* * If a similar attribute is found, * delete it. */ case T_OP_SUB: /* -= */ if (found) { if (!i->vp_strvalue[0] || (strcmp(found->vp_strvalue, i->vp_strvalue) == 0)) { pairdelete(to, found->da->attr, found->da->vendor, found->tag); /* * 'tailto' may have been * deleted... */ tailto = to; for (j = *to; j; j = j->next) { tailto = &j->next; } } } tailfrom = i; continue; break; /* * Add it, if it's not already there. */ case T_OP_EQ: /* = */ if (found) { tailfrom = i; continue; /* with the loop */ } break; /* * If a similar attribute is found, * replace it with the new one. Otherwise, * add the new one to the list. */ case T_OP_SET: /* := */ if (found) { VALUE_PAIR *vp; vp = found->next; memcpy(found, i, sizeof(*found)); found->next = vp; tailfrom = i; continue; } break; /* * FIXME: Add support for <=, >=, <, > * * which will mean (for integers) * 'make the attribute the smaller, etc' */ /* * Add the new element to the list, even * if similar ones already exist. */ default: case T_OP_ADD: /* += */ break; } if (tailfrom) { tailfrom->next = next; } else { *from = next; } /* * If ALL of the 'to' attributes have been deleted, * then ensure that the 'tail' is updated to point * to the head. */ if (!*to) { tailto = to; } *tailto = i; if (i) { i->next = NULL; tailto = &i->next; } } /* loop over the 'from' list */ }
/* * Common code called by everything below. */ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const *filename, fr_hash_table_t *ht, VALUE_PAIR *request_pairs, VALUE_PAIR **reply_pairs) { char const *name, *match; VALUE_PAIR *check_tmp; VALUE_PAIR *reply_tmp; PAIR_LIST const *user_pl, *default_pl; int found = 0; PAIR_LIST my_pl; char buffer[256]; if (!inst->key) { VALUE_PAIR *namepair; namepair = request->username; name = namepair ? namepair->vp_strvalue : "NONE"; } else { int len; len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL); if (len < 0) { return RLM_MODULE_FAIL; } name = len ? buffer : "NONE"; } if (!ht) return RLM_MODULE_NOOP; my_pl.name = name; user_pl = fr_hash_table_finddata(ht, &my_pl); my_pl.name = "DEFAULT"; default_pl = fr_hash_table_finddata(ht, &my_pl); /* * Find the entry for the user. */ while (user_pl || default_pl) { vp_cursor_t cursor; VALUE_PAIR *vp; PAIR_LIST const *pl; if (!default_pl && user_pl) { pl = user_pl; match = name; user_pl = user_pl->next; } else if (!user_pl && default_pl) { pl = default_pl; match = "DEFAULT"; default_pl = default_pl->next; } else if (user_pl->order < default_pl->order) { pl = user_pl; match = name; user_pl = user_pl->next; } else { pl = default_pl; match = "DEFAULT"; default_pl = default_pl->next; } check_tmp = paircopy(request, pl->check); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (radius_xlat_do(request, vp) < 0) { RWARN("Failed parsing expanded value for check item, skipping entry: %s", fr_strerror()); pairfree(&check_tmp); continue; } } if (paircompare(request, request_pairs, pl->check, reply_pairs) == 0) { RDEBUG2("%s: Matched entry %s at line %d", filename, match, pl->lineno); found = 1; /* ctx may be reply or proxy */ reply_tmp = paircopy(request, pl->reply); radius_xlat_move(request, reply_pairs, &reply_tmp); pairmove(request, &request->config_items, &check_tmp); /* Cleanup any unmoved valuepairs */ pairfree(&reply_tmp); pairfree(&check_tmp); /* * Fallthrough? */ if (!fallthrough(pl->reply)) break; } } /* * Remove server internal parameters. */ pairdelete(reply_pairs, PW_FALL_THROUGH, 0, TAG_ANY); /* * See if we succeeded. */ if (!found) return RLM_MODULE_NOOP; /* on to the next module */ return RLM_MODULE_OK; }
/** Compare two pair lists except for the password information. * * For every element in "check" at least one matching copy must be present * in "reply". * * @param[in] request Current request. * @param[in] req_list request valuepairs. * @param[in] check Check/control valuepairs. * @param[in,out] rep_list Reply value pairs. * * @return 0 on match. */ int paircompare(REQUEST *request, VALUE_PAIR *req_list, VALUE_PAIR *check, VALUE_PAIR **rep_list) { vp_cursor_t cursor; VALUE_PAIR *check_item; VALUE_PAIR *auth_item; DICT_ATTR const *from; int result = 0; int compare; bool first_only; for (check_item = paircursor(&cursor, &check); check_item; check_item = pairnext(&cursor)) { /* * If the user is setting a configuration value, * then don't bother comparing it to any attributes * sent to us by the user. It ALWAYS matches. */ if ((check_item->op == T_OP_SET) || (check_item->op == T_OP_ADD)) { continue; } if (!check_item->da->vendor) switch (check_item->da->attr) { /* * Attributes we skip during comparison. * These are "server" check items. */ case PW_CRYPT_PASSWORD: case PW_AUTH_TYPE: case PW_AUTZ_TYPE: case PW_ACCT_TYPE: case PW_SESSION_TYPE: case PW_STRIP_USER_NAME: continue; break; /* * IF the password attribute exists, THEN * we can do comparisons against it. If not, * then the request did NOT contain a * User-Password attribute, so we CANNOT do * comparisons against it. * * This hack makes CHAP-Password work.. */ case PW_USER_PASSWORD: if (check_item->op == T_OP_CMP_EQ) { WDEBUG("Found User-Password == \"...\"."); WDEBUG("Are you sure you don't mean Cleartext-Password?"); WDEBUG("See \"man rlm_pap\" for more information"); } if (pairfind(req_list, PW_USER_PASSWORD, 0, TAG_ANY) == NULL) { continue; } break; } /* * See if this item is present in the request. */ first_only = otherattr(check_item->da, &from); auth_item = req_list; try_again: if (!first_only) { while (auth_item != NULL) { if ((auth_item->da == from) || (!from)) { break; } auth_item = auth_item->next; } } /* * Not found, it's not a match. */ if (auth_item == NULL) { /* * Didn't find it. If we were *trying* * to not find it, then we succeeded. */ if (check_item->op == T_OP_CMP_FALSE) { continue; } else { return -1; } } /* * Else we found it, but we were trying to not * find it, so we failed. */ if (check_item->op == T_OP_CMP_FALSE) { return -1; } /* * We've got to xlat the string before doing * the comparison. */ radius_xlat_do(request, check_item); /* * OK it is present now compare them. */ compare = radius_callback_compare(request, auth_item, check_item, check, rep_list); switch (check_item->op) { case T_OP_EQ: default: INFO("Invalid operator for item %s: " "reverting to '=='", check_item->da->name); /* FALL-THROUGH */ case T_OP_CMP_TRUE: case T_OP_CMP_FALSE: case T_OP_CMP_EQ: if (compare != 0) result = -1; break; case T_OP_NE: if (compare == 0) result = -1; break; case T_OP_LT: if (compare >= 0) result = -1; break; case T_OP_GT: if (compare <= 0) result = -1; break; case T_OP_LE: if (compare > 0) result = -1; break; case T_OP_GE: if (compare < 0) result = -1; break; #ifdef HAVE_REGEX_H case T_OP_REG_EQ: case T_OP_REG_NE: if (compare != 0) result = -1; break; #endif } /* switch over the operator of the check item */ /* * This attribute didn't match, but maybe there's * another of the same attribute, which DOES match. */ if ((result != 0) && (!first_only)) { auth_item = auth_item->next; result = 0; goto try_again; } } /* for every entry in the check item list */ return result; }