2 * rlm_attr_filter.c - Filter A/V Pairs received back from proxy reqs
3 * before sending reply to the NAS/Server that sent
8 * This program is is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License, version 2 if the
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * Copyright (C) 2001,2006 The FreeRADIUS server project
22 * Copyright (C) 2001 Chris Parker <cparker@starnetusa.net>
25 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
30 #include <freeradius-devel/rad_assert.h>
40 * Define a structure with the module configuration, so it can
41 * be used as the instance handle.
43 struct attr_filter_instance {
50 static const CONF_PARSER module_config[] = {
51 { "attrsfile", PW_TYPE_FILENAME,
52 offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
53 { "key", PW_TYPE_STRING_PTR,
54 offsetof(struct attr_filter_instance,key), NULL, "%{Realm}" },
55 { "relaxed", PW_TYPE_BOOLEAN,
56 offsetof(struct attr_filter_instance,relaxed), NULL, "no" },
57 { NULL, -1, 0, NULL, NULL }
60 static void check_pair(VALUE_PAIR *check_item, VALUE_PAIR *reply_item,
65 if (check_item->operator == T_OP_SET) return;
67 compare = paircmp(check_item, reply_item);
78 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
81 PAIR_LIST *attrs = NULL;
85 rcode = pairlist_read(filename, &attrs, 1);
91 * Walk through the 'attrs' file list.
97 entry->check = entry->reply;
100 for (vp = entry->check; vp != NULL; vp = vp->next) {
103 * If it's NOT a vendor attribute,
104 * and it's NOT a wire protocol
105 * and we ignore Fall-Through,
106 * then bitch about it, giving a good warning message.
108 if (!(vp->attribute & ~0xffff) &&
109 (vp->attribute > 0xff) &&
110 (vp->attribute > 1000)) {
111 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
112 "\tfound in filter list for realm \"%s\".\n",
113 filename, entry->lineno, vp->name,
129 static int attr_filter_detach(void *instance)
131 struct attr_filter_instance *inst = instance;
132 pairlist_free(&inst->attrs);
139 * (Re-)read the "attrs" file into memory.
141 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
143 struct attr_filter_instance *inst;
146 inst = rad_malloc(sizeof *inst);
150 memset(inst, 0, sizeof(*inst));
152 if (cf_section_parse(conf, inst, module_config) < 0) {
153 attr_filter_detach(inst);
157 rcode = getattrsfile(inst->attrsfile, &inst->attrs);
159 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
160 attr_filter_detach(inst);
169 * Common attr_filter checks
171 static int attr_filter_common(void *instance, REQUEST *request,
172 RADIUS_PACKET *packet)
174 struct attr_filter_instance *inst = instance;
177 VALUE_PAIR **output_tail;
178 VALUE_PAIR *check_item;
182 char *keyname = NULL;
186 if (!packet) return RLM_MODULE_NOOP;
188 input = &(packet->vps);
191 VALUE_PAIR *namepair;
193 namepair = pairfind(request->packet->vps, PW_REALM);
195 return (RLM_MODULE_NOOP);
197 keyname = namepair->vp_strvalue;
201 len = radius_xlat(buffer, sizeof(buffer), inst->key,
204 return RLM_MODULE_NOOP;
210 output_tail = &output;
213 * Find the attr_filter profile entry for the entry.
215 for (pl = inst->attrs; pl; pl = pl->next) {
216 int fall_through = 0;
217 int relax_filter = inst->relaxed;
220 * If the current entry is NOT a default,
221 * AND the realm does NOT match the current entry,
222 * then skip to the next entry.
224 if ((strcmp(pl->name, "DEFAULT") != 0) &&
225 (strcmp(keyname, pl->name) != 0)) {
229 DEBUG2("attr_filter: Matched entry %s at line %d", pl->name,
233 for (check_item = pl->check;
235 check_item = check_item->next) {
236 if ((check_item->attribute == PW_FALL_THROUGH) &&
237 (check_item->vp_integer == 1)) {
241 else if (check_item->attribute == PW_RELAX_FILTER) {
242 if ( check_item->vp_integer != inst->relaxed ) {
243 DEBUG3("attr_filter: Overriding relaxed config-item with check-item value %d",
244 check_item->vp_integer);
245 relax_filter = check_item->vp_integer;
251 * If it is a SET operator, add the attribute to
252 * the output list without checking it.
254 if (check_item->operator == T_OP_SET ) {
255 vp = paircopyvp(check_item);
258 return RLM_MODULE_FAIL;
261 output_tail = &(vp->next);
266 * Iterate through the input items, comparing
267 * each item to every rule, then moving it to the
268 * output list only if it matches all rules
269 * for that attribute. IE, Idle-Timeout is moved
270 * only if it matches all rules that describe an
273 for (vp = *input; vp != NULL; vp = vp->next ) {
274 /* reset the pass,fail vars for each reply item */
278 * reset the check_item pointer to
279 * beginning of the list
281 for (check_item = pl->check;
283 check_item = check_item->next) {
285 * Vendor-Specific is special, and
286 * matches any VSA if the comparison
289 if ((check_item->attribute == PW_VENDOR_SPECIFIC) &&
290 (VENDOR(vp->attribute) != 0) &&
291 (check_item->operator == T_OP_CMP_TRUE)) {
296 if (vp->attribute == check_item->attribute) {
297 check_pair(check_item, vp,
303 * Only move attribute if it passed all rules,
304 * or if the config says we should copy unmatched
305 * attributes ('relaxed' mode).
307 if (fail == 0 && (pass > 0 || relax_filter)) {
309 DEBUG3("attr_filter: Attribute (%s) allowed by relaxed mode", vp->name);
311 *output_tail = paircopyvp(vp);
314 return RLM_MODULE_FAIL;
316 output_tail = &((*output_tail)->next);
320 /* If we shouldn't fall through, break */
326 * No entry matched. We didn't do anything.
329 rad_assert(output == NULL);
330 return RLM_MODULE_NOOP;
336 if (request->packet->code == PW_AUTHENTICATION_REQUEST) {
337 request->username = pairfind(request->packet->vps,
338 PW_STRIPPED_USER_NAME);
339 if (!request->username)
340 request->username = pairfind(request->packet->vps,
342 request->password = pairfind(request->packet->vps,
346 return RLM_MODULE_UPDATED;
349 static int attr_filter_preacct(void *instance, REQUEST *request)
351 return attr_filter_common(instance, request, request->packet);
354 static int attr_filter_accounting(void *instance, REQUEST *request)
356 return attr_filter_common(instance, request, request->reply);
359 static int attr_filter_preproxy(void *instance, REQUEST *request)
361 return attr_filter_common(instance, request, request->proxy);
364 static int attr_filter_postproxy(void *instance, REQUEST *request)
366 return attr_filter_common(instance, request, request->proxy_reply);
369 static int attr_filter_postauth(void *instance, REQUEST *request)
371 return attr_filter_common(instance, request, request->reply);
374 static int attr_filter_authorize(void *instance, REQUEST *request)
376 return attr_filter_common(instance, request, request->packet);
380 /* globally exported name */
381 module_t rlm_attr_filter = {
384 RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE, /* type */
385 attr_filter_instantiate, /* instantiation */
386 attr_filter_detach, /* detach */
388 NULL, /* authentication */
389 attr_filter_authorize, /* authorization */
390 attr_filter_preacct, /* pre-acct */
391 attr_filter_accounting, /* accounting */
392 NULL, /* checksimul */
393 attr_filter_preproxy, /* pre-proxy */
394 attr_filter_postproxy, /* post-proxy */
395 attr_filter_postauth /* post-auth */