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>
26 #include "libradius.h"
44 static const char rcsid[] = "$Id$";
46 struct attr_filter_instance {
52 static int check_pair(VALUE_PAIR *check_item, VALUE_PAIR *reply_item,
53 int comp, int *pa, int *fa) {
57 switch(check_item->operator) {
59 case T_OP_SET: /* nothing to do for set */
63 radlog(L_ERR, "Invalid operator for item %s: "
64 "reverting to '=='", check_item->name);
66 case T_OP_CMP_TRUE: /* comp always == 0 */
67 case T_OP_CMP_FALSE: /* comp always == 1 */
117 regcomp(®, (char *)check_item->strvalue, 0);
118 comp = regexec(®, (char *)reply_item->strvalue,
129 regcomp(®, (char *)check_item->strvalue, 0);
130 comp = regexec(®, (char *)reply_item->strvalue,
145 * Copy the specified attribute to the specified list
147 static void mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
150 tmp = paircreate(item->attribute, item->type);
152 radlog(L_ERR|L_CONS, "no memory");
159 memcpy(tmp, item, sizeof(*tmp));
165 * See if a VALUE_PAIR list contains Fall-Through = Yes
167 static int fallthrough(VALUE_PAIR *vp)
171 tmp = pairfind(vp, PW_FALL_THROUGH);
173 return tmp ? tmp->lvalue : 0;
176 static CONF_PARSER module_config[] = {
177 { "attrsfile", PW_TYPE_STRING_PTR,
178 offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
179 { NULL, -1, 0, NULL, NULL }
182 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
185 PAIR_LIST *attrs = NULL;
189 rcode = pairlist_read(filename, &attrs, 1);
195 * Walk through the 'attrs' file list.
201 entry->check = entry->reply;
204 for (vp = entry->check; vp != NULL; vp = vp->next) {
207 * If it's NOT a vendor attribute,
208 * and it's NOT a wire protocol
209 * and we ignore Fall-Through,
210 * then bitch about it, giving a good warning message.
212 if (!(vp->attribute & ~0xffff) &&
213 (vp->attribute > 0xff) &&
214 (vp->attribute > 1000)) {
215 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
216 "\tfound in filter list for realm \"%s\".\n",
217 filename, entry->lineno, vp->name,
230 * (Re-)read the "attrs" file into memory.
232 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
234 struct attr_filter_instance *inst;
237 inst = rad_malloc(sizeof *inst);
241 memset(inst, 0, sizeof(*inst));
243 if (cf_section_parse(conf, inst, module_config) < 0) {
248 rcode = getattrsfile(inst->attrsfile, &inst->attrs);
250 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
251 free(inst->attrsfile);
255 radlog(L_ERR|L_CONS, " rlm_attr_filter: Authorize method will be"\
261 static int attr_filter_authorize(void *instance, REQUEST *request)
263 struct attr_filter_instance *inst = instance;
264 VALUE_PAIR *request_pairs;
265 VALUE_PAIR **reply_items;
266 VALUE_PAIR *reply_item;
267 VALUE_PAIR *reply_tmp = NULL;
268 VALUE_PAIR *check_item;
273 VALUE_PAIR *realmpair;
278 * It's not a proxy reply, so return NOOP
281 if( request->proxy == NULL ) {
282 return( RLM_MODULE_NOOP );
285 request_pairs = request->packet->vps;
286 reply_items = &request->reply->vps;
289 * Get the realm. Can't use request->config_items as
290 * that gets freed by rad_authenticate.... use the one
291 * set in the original request vps
293 realmpair = pairfind(request_pairs, PW_REALM);
295 /* Can't find a realm, so no filtering of attributes
296 * or should we use a DEFAULT entry?
297 * For now, just return NOTFOUND. (maybe NOOP?)
299 return RLM_MODULE_NOTFOUND;
302 realmname = (char *) realmpair->strvalue;
303 realm = realm_find(realmname, FALSE);
306 * Find the attr_filter profile entry for the realm.
308 for(pl = inst->attrs; pl; pl = pl->next) {
311 * If the current entry is NOT a default,
312 * AND the realm does NOT match the current entry,
313 * then skip to the next entry.
315 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
316 (strcmp(realmname, pl->name) != 0) ) {
320 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
324 check_item = pl->check;
326 while( check_item != NULL ) {
329 * If it is a SET operator, add the attribute to
330 * the reply list without checking reply_items.
334 if( check_item->operator == T_OP_SET ) {
335 mypairappend(check_item, &reply_tmp);
337 check_item = check_item->next;
342 * Iterate through the reply items, comparing each reply item
343 * to every rule, then moving it to the reply_tmp list
344 * only if it matches all rules for that attribute.
345 * IE, Idle-Timeout is moved only if it matches all rules that
346 * describe an Idle-Timeout.
349 for(reply_item = *reply_items;
351 reply_item = reply_item->next ) {
353 /* reset the pass,fail vars for each reply item */
356 /* reset the check_item pointer to beginning of the list */
357 check_item = pl->check;
359 while( check_item != NULL ) {
361 if(reply_item->attribute == check_item->attribute) {
363 compare = simplepaircmp(request, reply_item,
365 check_pair(check_item, reply_item, compare,
369 check_item = check_item->next;
373 /* only move attribute if it passed all rules */
374 if (fail == 0 && pass > 0) {
375 mypairappend( reply_item, &reply_tmp);
380 /* If we shouldn't fall through, break */
381 if(!fallthrough(pl->check))
385 pairfree(&request->reply->vps);
386 request->reply->vps = reply_tmp;
389 * See if we succeeded. If we didn't find the realm,
390 * then exit from the module.
393 return RLM_MODULE_OK;
396 * Remove server internal parameters.
398 pairdelete(reply_items, PW_FALL_THROUGH);
400 return RLM_MODULE_UPDATED;
403 static int attr_filter_accounting(void *instance, REQUEST *request)
405 struct attr_filter_instance *inst = instance;
406 VALUE_PAIR *request_pairs;
407 VALUE_PAIR *send_item;
408 VALUE_PAIR *send_tmp = NULL;
409 VALUE_PAIR *check_item;
414 VALUE_PAIR *realmpair;
418 * Accounting is similar to pre-proxy.
419 * Here we are concerned with what we are going to forward to
420 * the remote server as opposed to concerns with what we will send
421 * to the NAS based on a proxy reply to an auth request.
424 if (request->packet->code != PW_ACCOUNTING_REQUEST) {
425 return (RLM_MODULE_NOOP);
428 request_pairs = request->packet->vps;
430 /* Get the realm from the original request vps. */
431 realmpair = pairfind(request_pairs, PW_REALM);
434 /* If there is no realm...NOOP */
435 return (RLM_MODULE_NOOP);
438 realmname = (char *) realmpair->strvalue;
439 realm = realm_find (realmname, FALSE);
442 * Find the attr_filter profile entry for the realm
444 for (pl = inst->attrs; pl; pl = pl->next) {
447 * If the current entry is NOT a default,
448 * AND the realm does not match the current entry,
449 * then skip to the next entry.
451 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
452 (strcasecmp(realmname, pl->name) != 0) ) {
456 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
460 check_item = pl->check;
462 while (check_item != NULL) {
465 * If it is a SET operator, add the attribute to
466 * the send list w/out checking.
469 if (check_item->operator == T_OP_SET) {
470 mypairappend(check_item, &send_tmp);
472 check_item = check_item->next;
475 * Iterate through the request_pairs (items sent from NAS).
476 * Compare each pair to every rule for this realm/DEFAULT.
477 * Move an item to send_tmp if it matches all rules for
478 * attribute in question.
480 for (send_item = request_pairs; send_item != NULL;
481 send_item = send_item->next ) {
483 /* reset the pass/fail vars for each packet->vp. */
486 /* reset the check_item pointer to beginning of the list */
487 check_item = pl->check;
489 while (check_item != NULL) {
490 if (send_item->attribute == check_item->attribute) {
492 compare = simplepaircmp(request, send_item,
494 check_pair(check_item, send_item, compare,
498 check_item = check_item->next;
500 /* only send if attribute passed all rules */
501 if (fail == 0 && pass > 0) {
502 mypairappend (send_item, &send_tmp);
505 if (!fallthrough(pl->check))
508 pairfree (&request->packet->vps);
509 request->packet->vps = send_tmp;
512 * See if we succeeded. If we didn't find the realm,
513 * then exit from the module.
516 return RLM_MODULE_OK;
519 * Remove server internal paramters.
521 pairdelete(&send_tmp, PW_FALL_THROUGH);
523 return RLM_MODULE_UPDATED;
526 static int attr_filter_preproxy (void *instance, REQUEST *request)
528 struct attr_filter_instance *inst = instance;
529 VALUE_PAIR *request_pairs;
530 VALUE_PAIR *send_item;
531 VALUE_PAIR *send_tmp = NULL;
532 VALUE_PAIR *check_item;
537 VALUE_PAIR *realmpair;
543 * concerned with what we are going to forward to
544 * to the remote server as opposed to we will do with
545 * with the remote servers' repsonse pairs. Consequently,
546 * we deal with modifications to the request->packet->vps;
548 request_pairs = request->proxy->vps;
549 if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
550 return (RLM_MODULE_NOOP);
552 realmpair = pairfind(request_pairs, PW_REALM);
554 return (RLM_MODULE_NOOP);
557 realmname = (char *)realmpair->strvalue;
558 realm = realm_find(realmname, FALSE);
560 for (pl = inst->attrs; pl; pl = pl->next) {
561 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
562 (strcasecmp(realmname, pl->name) != 0) ) {
566 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
570 check_item = pl->check;
572 while (check_item != NULL) {
575 * Append all SET operator attributes with no check.
577 if (check_item->operator == T_OP_SET) {
578 mypairappend(check_item, &send_tmp);
580 check_item = check_item->next;
583 * Iterate through the request_pairs (items sent from NAS).
584 * Compare each pair to every rule for this realm/DEFAULT.
585 * Move an item to send_tmp if it matches all rules for
586 * attribute in question.
588 for (send_item = request_pairs;
590 send_item = send_item->next ) {
592 /* reset the pass/fail vars for each packet->vp. */
595 /* reset the check_item to the beginning */
596 check_item = pl->check;
599 * compare each packet->vp to the entire list of
600 * check_items for this realm.
602 while (check_item != NULL) {
603 if (send_item->attribute == check_item->attribute) {
605 compare = simplepaircmp(request, send_item,
607 check_pair(check_item, send_item, compare,
612 check_item = check_item->next;
615 /* only send if attribute passed all rules */
616 if (fail == 0 && pass > 0) {
617 mypairappend (send_item, &send_tmp);
620 if (!fallthrough(pl->check))
623 pairfree (&request->proxy->vps);
624 request->proxy->vps = send_tmp;
627 return RLM_MODULE_OK;
628 pairdelete(&send_tmp, PW_FALL_THROUGH);
629 return RLM_MODULE_UPDATED;
632 static int attr_filter_postproxy(void *instance, REQUEST *request)
634 struct attr_filter_instance *inst = instance;
635 VALUE_PAIR *request_pairs;
636 VALUE_PAIR **reply_items;
637 VALUE_PAIR *reply_item;
638 VALUE_PAIR *reply_tmp = NULL;
639 VALUE_PAIR *check_item;
644 VALUE_PAIR *realmpair;
648 * It's not a proxy reply, so return NOOP
651 if( request->proxy == NULL ) {
652 return( RLM_MODULE_NOOP );
655 request_pairs = request->packet->vps;
656 reply_items = &request->proxy_reply->vps;
659 * Get the realm. Can't use request->config_items as
660 * that gets freed by rad_authenticate.... use the one
661 * set in the original request vps
663 realmpair = pairfind(request_pairs, PW_REALM);
665 /* Can't find a realm, so no filtering of attributes
666 * or should we use a DEFAULT entry?
667 * For now, just return NOTFOUND. (maybe NOOP?)
669 return RLM_MODULE_NOTFOUND;
672 realmname = (char *) realmpair->strvalue;
674 realm = realm_find(realmname, FALSE);
677 * Find the attr_filter profile entry for the realm.
679 for(pl = inst->attrs; pl; pl = pl->next) {
682 * If the current entry is NOT a default,
683 * AND the realm does NOT match the current entry,
684 * then skip to the next entry.
686 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
687 (strcmp(realmname, pl->name) != 0) ) {
691 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
695 check_item = pl->check;
697 while( check_item != NULL ) {
700 * If it is a SET operator, add the attribute to
701 * the reply list without checking reply_items.
704 if( check_item->operator == T_OP_SET ) {
705 mypairappend(check_item, &reply_tmp);
707 check_item = check_item->next;
712 * Iterate through the reply items,
713 * comparing each reply item to every rule,
714 * then moving it to the reply_tmp list only if it matches all
715 * rules for that attribute.
716 * IE, Idle-Timeout is moved only if it matches
717 * all rules that describe an Idle-Timeout.
720 for( reply_item = *reply_items; reply_item != NULL;
721 reply_item = reply_item->next ) {
723 /* reset the pass,fail vars for each reply item */
726 /* reset the check_item pointer to beginning of the list */
727 check_item = pl->check;
729 while( check_item != NULL ) {
731 if(reply_item->attribute == check_item->attribute) {
733 compare = simplepaircmp(request, reply_item,
735 check_pair(check_item, reply_item, compare,
739 check_item = check_item->next;
743 /* only move attribute if it passed all rules */
744 if (fail == 0 && pass > 0) {
745 mypairappend( reply_item, &reply_tmp);
750 /* If we shouldn't fall through, break */
751 if(!fallthrough(pl->check))
755 pairfree(&request->proxy_reply->vps);
756 request->proxy_reply->vps = reply_tmp;
759 * See if we succeeded. If we didn't find the realm,
760 * then exit from the module.
763 return RLM_MODULE_OK;
766 * Remove server internal parameters.
768 pairdelete(reply_items, PW_FALL_THROUGH);
770 return RLM_MODULE_UPDATED;
776 static int attr_filter_detach(void *instance)
778 struct attr_filter_instance *inst = instance;
779 pairlist_free(&inst->attrs);
780 free(inst->attrsfile);
786 /* globally exported name */
787 module_t rlm_attr_filter = {
789 0, /* type: reserved */
790 NULL, /* initialization */
791 attr_filter_instantiate, /* instantiation */
793 NULL, /* authentication */
794 attr_filter_authorize, /* authorization */
795 NULL, /* preaccounting */
796 attr_filter_accounting, /* accounting */
797 NULL, /* checksimul */
798 attr_filter_preproxy, /* pre-proxy */
799 attr_filter_postproxy, /* post-proxy */
802 attr_filter_detach, /* detach */