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 free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 * Copyright (C) 2001 The FreeRADIUS server project
23 * Copyright (C) 2001 Chris Parker <cparker@starnetusa.net>
27 #include "libradius.h"
45 static const char rcsid[] = "$Id$";
47 struct attr_filter_instance {
56 * Copy the specified attribute to the specified list
58 static void mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
62 tmp = paircreate(item->attribute, item->type);
64 radlog(L_ERR|L_CONS, "no memory");
71 tmp->lvalue = item->lvalue;
74 memcpy((char *)tmp->strvalue, (char *)item->strvalue, item->length);
75 tmp->length = item->length;
82 * See if a VALUE_PAIR list contains Fall-Through = Yes
84 static int fallthrough(VALUE_PAIR *vp)
88 tmp = pairfind(vp, PW_FALL_THROUGH);
90 return tmp ? tmp->lvalue : 0;
93 static CONF_PARSER module_config[] = {
94 { "attrsfile", PW_TYPE_STRING_PTR,
95 offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
96 { NULL, -1, 0, NULL, NULL }
99 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
102 PAIR_LIST *attrs = NULL;
106 rcode = pairlist_read(filename, &attrs, 1);
112 * Walk through the 'attrs' file list.
118 entry->check = entry->reply;
121 for (vp = entry->check; vp != NULL; vp = vp->next) {
124 * If it's NOT a vendor attribute,
125 * and it's NOT a wire protocol
126 * and we ignore Fall-Through,
127 * then bitch about it, giving a
128 * good warning message.
130 if (!(vp->attribute & ~0xffff) &&
131 (vp->attribute > 0xff) &&
132 (vp->attribute > 1000)) {
133 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
134 "\tfound in filter list for realm \"%s\".\n",
135 filename, entry->lineno, vp->name,
148 * (Re-)read the "attrs" file into memory.
150 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
152 struct attr_filter_instance *inst;
155 inst = rad_malloc(sizeof *inst);
159 memset(inst, 0, sizeof(*inst));
161 if (cf_section_parse(conf, inst, module_config) < 0) {
166 rcode = getattrsfile(inst->attrsfile, &inst->attrs);
168 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
169 free(inst->attrsfile);
179 * Find the named realm in the database. Create the
180 * set of attribute-value pairs to check and reply with
181 * for this realm from the database.
183 static int attr_filter_authorize(void *instance, REQUEST *request)
185 struct attr_filter_instance *inst = instance;
186 VALUE_PAIR *request_pairs;
187 VALUE_PAIR **reply_items;
188 VALUE_PAIR *reply_item;
189 VALUE_PAIR *reply_tmp = NULL;
190 VALUE_PAIR *check_item;
198 VALUE_PAIR *realmpair;
203 * It's not a proxy reply, so return NOOP
206 if( request->proxy == NULL ) {
207 return( RLM_MODULE_NOOP );
210 request_pairs = request->packet->vps;
211 reply_items = &request->reply->vps;
214 * Get the realm. Can't use request->config_items as
215 * that gets freed by rad_authenticate.... use the one
216 * set in the original request vps
218 realmpair = pairfind(request_pairs, PW_REALM);
220 /* Can't find a realm, so no filtering of attributes
221 * or should we use a DEFAULT entry?
222 * For now, just return NOTFOUND. (maybe NOOP?)
224 return RLM_MODULE_NOTFOUND;
227 realmname = (char *) realmpair->strvalue;
228 realm = realm_find(realmname, FALSE);
231 * Find the attr_filter profile entry for the realm.
233 for(pl = inst->attrs; pl; pl = pl->next) {
236 * If the current entry is NOT a default,
237 * AND the realm does NOT match the current entry,
238 * then skip to the next entry.
240 if ( (strcmp(pl->name, "DEFAULT") != 0)
241 && (strcmp(realmname, pl->name) != 0) ) {
245 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name, pl->lineno);
249 check_item = pl->check;
251 while( check_item != NULL ) {
254 * If it is a SET operator, add the attribute to
255 * the reply list without checking reply_items.
259 if( check_item->operator == T_OP_SET ) {
260 mypairappend(check_item, &reply_tmp);
262 check_item = check_item->next;
264 } /* while( check_item != NULL ) */
267 * Iterate through the reply items, comparing each reply item to every rule,
268 * then moving it to the reply_tmp list only if it matches all rules for that
269 * attribute. IE, Idle-Timeout is moved only if it matches all rules that
270 * describe an Idle-Timeout.
273 for( reply_item = *reply_items;
275 reply_item = reply_item->next ) {
277 /* reset the pass,fail vars for each reply item */
280 /* reset the check_item pointer to the beginning of the list */
281 check_item = pl->check;
283 while( check_item != NULL ) {
285 if(reply_item->attribute == check_item->attribute) {
287 compare = simplepaircmp(request, reply_item, check_item);
289 switch(check_item->operator) {
291 case T_OP_SET: /* nothing to do for set */
295 radlog(L_ERR, "Invalid operator for item %s: "
296 "reverting to '=='", check_item->name);
298 case T_OP_CMP_TRUE: /* compare always == 0 */
299 case T_OP_CMP_FALSE: /* compare always == 1 */
349 regcomp(®, (char *)check_item->strvalue, 0);
350 compare = regexec(®, (char *)reply_item->strvalue,
361 regcomp(®, (char *)check_item->strvalue, 0);
362 compare = regexec(®, (char *)reply_item->strvalue,
372 } /* switch( check_item->operator ) */
374 } /* if reply == check */
376 check_item = check_item->next;
378 } /* while( check ) */
380 /* only move attribute if it passed all rules */
381 if (fail == 0 && pass > 0) {
382 mypairappend( reply_item, &reply_tmp);
387 /* If we shouldn't fall through, break */
388 if(!fallthrough(pl->check))
392 pairfree(&request->reply->vps);
393 request->reply->vps = reply_tmp;
396 * See if we succeeded. If we didn't find the realm,
397 * then exit from the module.
400 return RLM_MODULE_OK;
403 * Remove server internal parameters.
405 pairdelete(reply_items, PW_FALL_THROUGH);
407 return RLM_MODULE_UPDATED;
413 static int attr_filter_detach(void *instance)
415 struct attr_filter_instance *inst = instance;
416 pairlist_free(&inst->attrs);
417 free(inst->attrsfile);
423 /* globally exported name */
424 module_t rlm_attr_filter = {
426 0, /* type: reserved */
427 NULL, /* initialization */
428 attr_filter_instantiate, /* instantiation */
430 NULL, /* authentication */
431 attr_filter_authorize, /* authorization */
432 NULL, /* preaccounting */
433 NULL, /* accounting */
434 NULL, /* checksimul */
435 NULL, /* pre-proxy */
436 NULL, /* post-proxy */
439 attr_filter_detach, /* detach */