2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @file rlm_attr_filter.c
20 * @brief Filter the contents of a list, allowing only certain attributes.
22 * @copyright (C) 2001,2006 The FreeRADIUS server project
23 * @copyright (C) 2001 Chris Parker <cparker@starnetusa.net>
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/rad_assert.h>
37 * Define a structure with the module configuration, so it can
38 * be used as the instance handle.
40 typedef struct rlm_attr_filter {
47 static const CONF_PARSER module_config[] = {
48 { "attrsfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, rlm_attr_filter_t, filename), NULL },
49 { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_REQUIRED, rlm_attr_filter_t, filename), NULL },
50 { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_attr_filter_t, key), "%{Realm}" },
51 { "relaxed", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_attr_filter_t, relaxed), "no" },
52 CONF_PARSER_TERMINATOR
55 static void check_pair(REQUEST *request, VALUE_PAIR *check_item, VALUE_PAIR *reply_item, int *pass, int *fail)
59 if (check_item->op == T_OP_SET) return;
61 compare = fr_pair_cmp(check_item, reply_item);
63 REDEBUG("Comparison failed: %s", fr_strerror());
72 if (RDEBUG_ENABLED3) {
73 char rule[1024], pair[1024];
75 vp_prints(rule, sizeof(rule), check_item);
76 vp_prints(pair, sizeof(pair), reply_item);
77 RDEBUG3("%s %s %s", pair, compare == 1 ? "allowed by" : "disallowed by", rule);
83 static int attr_filter_getfile(TALLOC_CTX *ctx, char const *filename, PAIR_LIST **pair_list)
87 PAIR_LIST *attrs = NULL;
91 rcode = pairlist_read(ctx, filename, &attrs, 1);
97 * Walk through the 'attrs' file list.
102 entry->check = entry->reply;
105 for (vp = fr_cursor_init(&cursor, &entry->check);
107 vp = fr_cursor_next(&cursor)) {
109 * If it's NOT a vendor attribute,
110 * and it's NOT a wire protocol
111 * and we ignore Fall-Through,
112 * then bitch about it, giving a good warning message.
114 if ((vp->da->vendor == 0) &&
115 (vp->da->attr > 1000)) {
116 WARN("[%s]:%d Check item \"%s\"\n\tfound in filter list for realm \"%s\".\n",
117 filename, entry->lineno, vp->da->name, entry->name);
130 * (Re-)read the "attrs" file into memory.
132 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
134 rlm_attr_filter_t *inst = instance;
137 rcode = attr_filter_getfile(inst, inst->filename, &inst->attrs);
139 ERROR("Errors reading %s", inst->filename);
149 * Common attr_filter checks
151 static rlm_rcode_t CC_HINT(nonnull(1,2)) attr_filter_common(void *instance, REQUEST *request, RADIUS_PACKET *packet)
153 rlm_attr_filter_t *inst = instance;
155 vp_cursor_t input, check, out;
156 VALUE_PAIR *input_item, *check_item, *output;
160 char const *keyname = NULL;
163 if (!packet) return RLM_MODULE_NOOP;
166 VALUE_PAIR *namepair;
168 namepair = fr_pair_find_by_num(request->packet->vps, PW_REALM, 0, TAG_ANY);
170 return (RLM_MODULE_NOOP);
172 keyname = namepair->vp_strvalue;
176 len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL);
178 return RLM_MODULE_FAIL;
181 return RLM_MODULE_NOOP;
187 * Head of the output list
190 fr_cursor_init(&out, &output);
193 * Find the attr_filter profile entry for the entry.
195 for (pl = inst->attrs; pl; pl = pl->next) {
196 int fall_through = 0;
197 int relax_filter = inst->relaxed;
200 * If the current entry is NOT a default,
201 * AND the realm does NOT match the current entry,
202 * then skip to the next entry.
204 if ((strcmp(pl->name, "DEFAULT") != 0) &&
205 (strcmp(keyname, pl->name) != 0)) {
209 RDEBUG2("Matched entry %s at line %d", pl->name, pl->lineno);
212 for (check_item = fr_cursor_init(&check, &pl->check);
214 check_item = fr_cursor_next(&check)) {
215 if (!check_item->da->vendor &&
216 (check_item->da->attr == PW_FALL_THROUGH) &&
217 (check_item->vp_integer == 1)) {
221 else if (!check_item->da->vendor && check_item->da->attr == PW_RELAX_FILTER) {
222 relax_filter = check_item->vp_integer;
227 * If it is a SET operator, add the attribute to
228 * the output list without checking it.
230 if (check_item->op == T_OP_SET ) {
231 vp = fr_pair_copy(packet, check_item);
235 radius_xlat_do(request, vp);
236 fr_cursor_insert(&out, vp);
241 * Iterate through the input items, comparing
242 * each item to every rule, then moving it to the
243 * output list only if it matches all rules
244 * for that attribute. IE, Idle-Timeout is moved
245 * only if it matches all rules that describe an
248 for (input_item = fr_cursor_init(&input, &packet->vps);
250 input_item = fr_cursor_next(&input)) {
251 pass = fail = 0; /* reset the pass,fail vars for each reply item */
254 * Reset the check_item pointer to beginning of the list
256 for (check_item = fr_cursor_first(&check);
258 check_item = fr_cursor_next(&check)) {
260 * Vendor-Specific is special, and matches any VSA if the
261 * comparison is always true.
263 if ((check_item->da->attr == PW_VENDOR_SPECIFIC) && (input_item->da->vendor != 0) &&
264 (check_item->op == T_OP_CMP_TRUE)) {
269 if (input_item->da == check_item->da) {
270 check_pair(request, check_item, input_item, &pass, &fail);
274 RDEBUG3("Attribute \"%s\" allowed by %i rules, disallowed by %i rules",
275 input_item->da->name, pass, fail);
277 * Only move attribute if it passed all rules, or if the config says we
278 * should copy unmatched attributes ('relaxed' mode).
280 if (fail == 0 && (pass > 0 || relax_filter)) {
282 RDEBUG3("Attribute \"%s\" allowed by relaxed mode", input_item->da->name);
284 vp = fr_pair_copy(packet, input_item);
288 fr_cursor_insert(&out, vp);
292 /* If we shouldn't fall through, break */
299 * No entry matched. We didn't do anything.
303 return RLM_MODULE_NOOP;
307 * Replace the existing request list with our filtered one
309 fr_pair_list_free(&packet->vps);
310 packet->vps = output;
312 if (request->packet->code == PW_CODE_ACCESS_REQUEST) {
313 request->username = fr_pair_find_by_num(request->packet->vps, PW_STRIPPED_USER_NAME, 0, TAG_ANY);
314 if (!request->username) {
315 request->username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
317 request->password = fr_pair_find_by_num(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
320 return RLM_MODULE_UPDATED;
323 fr_pair_list_free(&output);
324 return RLM_MODULE_FAIL;
327 #define RLM_AF_FUNC(_x, _y) static rlm_rcode_t CC_HINT(nonnull) mod_##_x(void *instance, REQUEST *request) \
329 return attr_filter_common(instance, request, request->_y); \
332 RLM_AF_FUNC(authorize, packet)
333 RLM_AF_FUNC(post_auth, reply)
335 RLM_AF_FUNC(preacct, packet)
336 RLM_AF_FUNC(accounting, reply)
339 RLM_AF_FUNC(pre_proxy, proxy)
340 RLM_AF_FUNC(post_proxy, proxy_reply)
344 RLM_AF_FUNC(recv_coa, packet)
345 RLM_AF_FUNC(send_coa, reply)
348 /* globally exported name */
349 extern module_t rlm_attr_filter;
350 module_t rlm_attr_filter = {
351 .magic = RLM_MODULE_INIT,
352 .name = "attr_filter",
353 .type = RLM_TYPE_HUP_SAFE,
354 .inst_size = sizeof(rlm_attr_filter_t),
355 .config = module_config,
356 .instantiate = mod_instantiate,
358 [MOD_AUTHORIZE] = mod_authorize,
359 [MOD_PREACCT] = mod_preacct,
360 [MOD_ACCOUNTING] = mod_accounting,
362 [MOD_PRE_PROXY] = mod_pre_proxy,
363 [MOD_POST_PROXY] = mod_post_proxy,
365 [MOD_POST_AUTH] = mod_post_auth,
367 [MOD_RECV_COA] = mod_recv_coa,
368 [MOD_SEND_COA] = mod_send_coa