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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * Copyright (C) 2001 The FreeRADIUS server project
22 * Copyright (C) 2001 Chris Parker <cparker@starnetusa.net>
43 static const char rcsid[] = "$Id$";
46 * Define a structure with the module configuration, so it can
47 * be used as the instance handle.
49 struct attr_filter_instance {
54 static int check_pair(VALUE_PAIR *check_item, VALUE_PAIR *reply_item,
55 int comp, int *pa, int *fa) {
59 switch(check_item->operator) {
61 case T_OP_SET: /* nothing to do for set */
65 radlog(L_ERR, "Invalid operator for item %s: "
66 "reverting to '=='", check_item->name);
68 case T_OP_CMP_TRUE: /* comp always == 0 */
69 case T_OP_CMP_FALSE: /* comp always == 1 */
119 regcomp(®, (char *)check_item->vp_strvalue, REG_EXTENDED);
120 comp = regexec(®, (char *)reply_item->vp_strvalue,
131 regcomp(®, (char *)check_item->vp_strvalue, REG_EXTENDED);
132 comp = regexec(®, (char *)reply_item->vp_strvalue,
147 * Copy the specified attribute to the specified list
149 static int mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
152 tmp = paircreate(item->attribute, item->type);
154 radlog(L_ERR|L_CONS, "no memory");
161 memcpy(tmp, item, sizeof(*tmp));
168 * See if a VALUE_PAIR list contains Fall-Through = Yes
170 static int fallthrough(VALUE_PAIR *vp)
174 tmp = pairfind(vp, PW_FALL_THROUGH);
176 return tmp ? tmp->lvalue : 0;
179 static const CONF_PARSER module_config[] = {
180 { "attrsfile", PW_TYPE_FILENAME,
181 offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
182 { NULL, -1, 0, NULL, NULL }
185 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
188 PAIR_LIST *attrs = NULL;
192 rcode = pairlist_read(filename, &attrs, 1);
198 * Walk through the 'attrs' file list.
204 entry->check = entry->reply;
207 for (vp = entry->check; vp != NULL; vp = vp->next) {
210 * If it's NOT a vendor attribute,
211 * and it's NOT a wire protocol
212 * and we ignore Fall-Through,
213 * then bitch about it, giving a good warning message.
215 if (!(vp->attribute & ~0xffff) &&
216 (vp->attribute > 0xff) &&
217 (vp->attribute > 1000)) {
218 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
219 "\tfound in filter list for realm \"%s\".\n",
220 filename, entry->lineno, vp->name,
233 * (Re-)read the "attrs" file into memory.
235 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
237 struct attr_filter_instance *inst;
240 inst = rad_malloc(sizeof *inst);
244 memset(inst, 0, sizeof(*inst));
246 if (cf_section_parse(conf, inst, module_config) < 0) {
251 rcode = getattrsfile(inst->attrsfile, &inst->attrs);
253 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
254 free(inst->attrsfile);
262 static int attr_filter_accounting(void *instance, REQUEST *request)
264 struct attr_filter_instance *inst = instance;
265 VALUE_PAIR *request_pairs;
266 VALUE_PAIR *send_item;
267 VALUE_PAIR *send_tmp = NULL;
268 VALUE_PAIR *check_item;
273 VALUE_PAIR *realmpair;
277 * Accounting is similar to pre-proxy.
278 * Here we are concerned with what we are going to forward to
279 * the remote server as opposed to concerns with what we will send
280 * to the NAS based on a proxy reply to an auth request.
283 if (request->packet->code != PW_ACCOUNTING_REQUEST) {
284 return (RLM_MODULE_NOOP);
287 request_pairs = request->packet->vps;
289 /* Get the realm from the original request vps. */
290 realmpair = pairfind(request_pairs, PW_REALM);
293 /* If there is no realm...NOOP */
294 return (RLM_MODULE_NOOP);
297 realmname = (char *) realmpair->vp_strvalue;
298 realm = realm_find (realmname, FALSE);
301 * Find the attr_filter profile entry for the realm
303 for (pl = inst->attrs; pl; pl = pl->next) {
306 * If the current entry is NOT a default,
307 * AND the realm does not match the current entry,
308 * then skip to the next entry.
310 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
311 (strcasecmp(realmname, pl->name) != 0) ) {
315 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
319 check_item = pl->check;
321 while (check_item != NULL) {
324 * If it is a SET operator, add the attribute to
325 * the send list w/out checking.
328 if (check_item->operator == T_OP_SET) {
329 if (mypairappend(check_item, &send_tmp) < 0) {
330 return RLM_MODULE_FAIL;
333 check_item = check_item->next;
336 * Iterate through the request_pairs (items sent from NAS).
337 * Compare each pair to every rule for this realm/DEFAULT.
338 * Move an item to send_tmp if it matches all rules for
339 * attribute in question.
341 for (send_item = request_pairs; send_item != NULL;
342 send_item = send_item->next ) {
344 /* reset the pass/fail vars for each packet->vp. */
347 /* reset the check_item pointer to beginning of the list */
348 check_item = pl->check;
350 while (check_item != NULL) {
351 if (send_item->attribute == check_item->attribute) {
353 compare = simplepaircmp(request, send_item,
355 check_pair(check_item, send_item, compare,
359 check_item = check_item->next;
361 /* only send if attribute passed all rules */
362 if (fail == 0 && pass > 0) {
363 if (mypairappend (send_item, &send_tmp) < 0) {
364 return RLM_MODULE_FAIL;
368 if (!fallthrough(pl->check))
371 pairfree (&request->packet->vps);
372 request->packet->vps = send_tmp;
375 * See if we succeeded. If we didn't find the realm,
376 * then exit from the module.
379 return RLM_MODULE_OK;
382 * Remove server internal paramters.
384 pairdelete(&send_tmp, PW_FALL_THROUGH);
386 return RLM_MODULE_UPDATED;
389 static int attr_filter_preproxy (void *instance, REQUEST *request)
391 struct attr_filter_instance *inst = instance;
392 VALUE_PAIR *request_pairs;
393 VALUE_PAIR *send_item;
394 VALUE_PAIR *send_tmp = NULL;
395 VALUE_PAIR *check_item;
400 VALUE_PAIR *realmpair;
406 * concerned with what we are going to forward to
407 * to the remote server as opposed to we will do with
408 * with the remote servers' repsonse pairs. Consequently,
409 * we deal with modifications to the request->packet->vps;
411 request_pairs = request->proxy->vps;
412 if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
413 return (RLM_MODULE_NOOP);
415 realmpair = pairfind(request_pairs, PW_REALM);
417 return (RLM_MODULE_NOOP);
420 realmname = (char *)realmpair->vp_strvalue;
421 realm = realm_find(realmname, FALSE);
423 for (pl = inst->attrs; pl; pl = pl->next) {
424 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
425 (strcasecmp(realmname, pl->name) != 0) ) {
429 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
433 check_item = pl->check;
435 while (check_item != NULL) {
438 * Append all SET operator attributes with no check.
440 if (check_item->operator == T_OP_SET) {
441 if (mypairappend(check_item, &send_tmp) < 0) {
442 return RLM_MODULE_FAIL;
445 check_item = check_item->next;
448 * Iterate through the request_pairs (items sent from NAS).
449 * Compare each pair to every rule for this realm/DEFAULT.
450 * Move an item to send_tmp if it matches all rules for
451 * attribute in question.
453 for (send_item = request_pairs;
455 send_item = send_item->next ) {
457 /* reset the pass/fail vars for each packet->vp. */
460 /* reset the check_item to the beginning */
461 check_item = pl->check;
464 * compare each packet->vp to the entire list of
465 * check_items for this realm.
467 while (check_item != NULL) {
468 if (send_item->attribute == check_item->attribute) {
470 compare = simplepaircmp(request, send_item,
472 check_pair(check_item, send_item, compare,
477 check_item = check_item->next;
480 /* only send if attribute passed all rules */
481 if (fail == 0 && pass > 0) {
482 if (mypairappend (send_item, &send_tmp) < 0) {
483 return RLM_MODULE_FAIL;
487 if (!fallthrough(pl->check))
490 pairfree (&request->proxy->vps);
491 request->proxy->vps = send_tmp;
494 return RLM_MODULE_OK;
495 pairdelete(&send_tmp, PW_FALL_THROUGH);
496 return RLM_MODULE_UPDATED;
499 static int attr_filter_postproxy(void *instance, REQUEST *request)
501 struct attr_filter_instance *inst = instance;
502 VALUE_PAIR *request_pairs;
503 VALUE_PAIR **reply_items;
504 VALUE_PAIR *reply_item;
505 VALUE_PAIR *reply_tmp = NULL;
506 VALUE_PAIR *check_item;
511 VALUE_PAIR *realmpair;
515 * It's not a proxy reply, so return NOOP
518 if( request->proxy == NULL ) {
519 return( RLM_MODULE_NOOP );
522 request_pairs = request->packet->vps;
523 reply_items = &request->proxy_reply->vps;
526 * Get the realm. Can't use request->config_items as
527 * that gets freed by rad_authenticate.... use the one
528 * set in the original request vps
530 realmpair = pairfind(request_pairs, PW_REALM);
532 /* Can't find a realm, so no filtering of attributes
533 * or should we use a DEFAULT entry?
534 * For now, just return NOTFOUND. (maybe NOOP?)
536 return RLM_MODULE_NOTFOUND;
539 realmname = (char *) realmpair->vp_strvalue;
541 realm = realm_find(realmname, FALSE);
544 * Find the attr_filter profile entry for the realm.
546 for(pl = inst->attrs; pl; pl = pl->next) {
549 * If the current entry is NOT a default,
550 * AND the realm does NOT match the current entry,
551 * then skip to the next entry.
553 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
554 (strcmp(realmname, pl->name) != 0) ) {
558 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
562 check_item = pl->check;
564 while( check_item != NULL ) {
567 * If it is a SET operator, add the attribute to
568 * the reply list without checking reply_items.
571 if( check_item->operator == T_OP_SET ) {
572 if (mypairappend(check_item, &reply_tmp) < 0) {
573 return RLM_MODULE_FAIL;
576 check_item = check_item->next;
581 * Iterate through the reply items,
582 * comparing each reply item to every rule,
583 * then moving it to the reply_tmp list only if it matches all
584 * rules for that attribute.
585 * IE, Idle-Timeout is moved only if it matches
586 * all rules that describe an Idle-Timeout.
589 for( reply_item = *reply_items; reply_item != NULL;
590 reply_item = reply_item->next ) {
592 /* reset the pass,fail vars for each reply item */
595 /* reset the check_item pointer to beginning of the list */
596 check_item = pl->check;
598 while( check_item != NULL ) {
600 if(reply_item->attribute == check_item->attribute) {
602 compare = simplepaircmp(request, reply_item,
604 check_pair(check_item, reply_item, compare,
608 check_item = check_item->next;
612 /* only move attribute if it passed all rules */
613 if (fail == 0 && pass > 0) {
614 if (mypairappend( reply_item, &reply_tmp) < 0) {
615 return RLM_MODULE_FAIL;
621 /* If we shouldn't fall through, break */
622 if(!fallthrough(pl->check))
626 pairfree(&request->proxy_reply->vps);
627 request->proxy_reply->vps = reply_tmp;
630 * See if we succeeded. If we didn't find the realm,
631 * then exit from the module.
634 return RLM_MODULE_OK;
637 * Remove server internal parameters.
639 pairdelete(reply_items, PW_FALL_THROUGH);
641 return RLM_MODULE_UPDATED;
647 static int attr_filter_detach(void *instance)
649 struct attr_filter_instance *inst = instance;
650 pairlist_free(&inst->attrs);
651 free(inst->attrsfile);
657 /* globally exported name */
658 module_t rlm_attr_filter = {
661 0, /* type: reserved */
662 attr_filter_instantiate, /* instantiation */
663 attr_filter_detach, /* detach */
665 NULL, /* authentication */
666 NULL, /* authorization */
667 NULL, /* preaccounting */
668 attr_filter_accounting, /* accounting */
669 NULL, /* checksimul */
670 attr_filter_preproxy, /* pre-proxy */
671 attr_filter_postproxy, /* post-proxy */