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$";
45 struct attr_filter_instance {
51 static int check_pair(VALUE_PAIR *check_item, VALUE_PAIR *reply_item,
52 int comp, int *pa, int *fa) {
56 switch(check_item->operator) {
58 case T_OP_SET: /* nothing to do for set */
62 radlog(L_ERR, "Invalid operator for item %s: "
63 "reverting to '=='", check_item->name);
65 case T_OP_CMP_TRUE: /* comp always == 0 */
66 case T_OP_CMP_FALSE: /* comp always == 1 */
116 regcomp(®, (char *)check_item->strvalue, REG_EXTENDED);
117 comp = regexec(®, (char *)reply_item->strvalue,
128 regcomp(®, (char *)check_item->strvalue, REG_EXTENDED);
129 comp = regexec(®, (char *)reply_item->strvalue,
144 * Copy the specified attribute to the specified list
146 static int mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
149 tmp = paircreate(item->attribute, item->type);
151 radlog(L_ERR|L_CONS, "no memory");
158 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 const 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 if (mypairappend(check_item, &reply_tmp) < 0) {
336 return RLM_MODULE_FAIL;
339 check_item = check_item->next;
344 * Iterate through the reply items, comparing each reply item
345 * to every rule, then moving it to the reply_tmp list
346 * only if it matches all rules for that attribute.
347 * IE, Idle-Timeout is moved only if it matches all rules that
348 * describe an Idle-Timeout.
351 for(reply_item = *reply_items;
353 reply_item = reply_item->next ) {
355 /* reset the pass,fail vars for each reply item */
358 /* reset the check_item pointer to beginning of the list */
359 check_item = pl->check;
361 while( check_item != NULL ) {
363 if(reply_item->attribute == check_item->attribute) {
365 compare = simplepaircmp(request, reply_item,
367 check_pair(check_item, reply_item, compare,
371 check_item = check_item->next;
375 /* only move attribute if it passed all rules */
376 if (fail == 0 && pass > 0) {
377 if (mypairappend( reply_item, &reply_tmp) < 0) {
378 return RLM_MODULE_FAIL;
384 /* If we shouldn't fall through, break */
385 if(!fallthrough(pl->check))
389 pairfree(&request->reply->vps);
390 request->reply->vps = reply_tmp;
393 * See if we succeeded. If we didn't find the realm,
394 * then exit from the module.
397 return RLM_MODULE_OK;
400 * Remove server internal parameters.
402 pairdelete(reply_items, PW_FALL_THROUGH);
404 return RLM_MODULE_UPDATED;
407 static int attr_filter_accounting(void *instance, REQUEST *request)
409 struct attr_filter_instance *inst = instance;
410 VALUE_PAIR *request_pairs;
411 VALUE_PAIR *send_item;
412 VALUE_PAIR *send_tmp = NULL;
413 VALUE_PAIR *check_item;
418 VALUE_PAIR *realmpair;
422 * Accounting is similar to pre-proxy.
423 * Here we are concerned with what we are going to forward to
424 * the remote server as opposed to concerns with what we will send
425 * to the NAS based on a proxy reply to an auth request.
428 if (request->packet->code != PW_ACCOUNTING_REQUEST) {
429 return (RLM_MODULE_NOOP);
432 request_pairs = request->packet->vps;
434 /* Get the realm from the original request vps. */
435 realmpair = pairfind(request_pairs, PW_REALM);
438 /* If there is no realm...NOOP */
439 return (RLM_MODULE_NOOP);
442 realmname = (char *) realmpair->strvalue;
443 realm = realm_find (realmname, FALSE);
446 * Find the attr_filter profile entry for the realm
448 for (pl = inst->attrs; pl; pl = pl->next) {
451 * If the current entry is NOT a default,
452 * AND the realm does not match the current entry,
453 * then skip to the next entry.
455 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
456 (strcasecmp(realmname, pl->name) != 0) ) {
460 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
464 check_item = pl->check;
466 while (check_item != NULL) {
469 * If it is a SET operator, add the attribute to
470 * the send list w/out checking.
473 if (check_item->operator == T_OP_SET) {
474 if (mypairappend(check_item, &send_tmp) < 0) {
475 return RLM_MODULE_FAIL;
478 check_item = check_item->next;
481 * Iterate through the request_pairs (items sent from NAS).
482 * Compare each pair to every rule for this realm/DEFAULT.
483 * Move an item to send_tmp if it matches all rules for
484 * attribute in question.
486 for (send_item = request_pairs; send_item != NULL;
487 send_item = send_item->next ) {
489 /* reset the pass/fail vars for each packet->vp. */
492 /* reset the check_item pointer to beginning of the list */
493 check_item = pl->check;
495 while (check_item != NULL) {
496 if (send_item->attribute == check_item->attribute) {
498 compare = simplepaircmp(request, send_item,
500 check_pair(check_item, send_item, compare,
504 check_item = check_item->next;
506 /* only send if attribute passed all rules */
507 if (fail == 0 && pass > 0) {
508 if (mypairappend (send_item, &send_tmp) < 0) {
509 return RLM_MODULE_FAIL;
513 if (!fallthrough(pl->check))
516 pairfree (&request->packet->vps);
517 request->packet->vps = send_tmp;
520 * See if we succeeded. If we didn't find the realm,
521 * then exit from the module.
524 return RLM_MODULE_OK;
527 * Remove server internal paramters.
529 pairdelete(&send_tmp, PW_FALL_THROUGH);
531 return RLM_MODULE_UPDATED;
534 static int attr_filter_preproxy (void *instance, REQUEST *request)
536 struct attr_filter_instance *inst = instance;
537 VALUE_PAIR *request_pairs;
538 VALUE_PAIR *send_item;
539 VALUE_PAIR *send_tmp = NULL;
540 VALUE_PAIR *check_item;
545 VALUE_PAIR *realmpair;
551 * concerned with what we are going to forward to
552 * to the remote server as opposed to we will do with
553 * with the remote servers' repsonse pairs. Consequently,
554 * we deal with modifications to the request->packet->vps;
556 request_pairs = request->proxy->vps;
557 if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
558 return (RLM_MODULE_NOOP);
560 realmpair = pairfind(request_pairs, PW_REALM);
562 return (RLM_MODULE_NOOP);
565 realmname = (char *)realmpair->strvalue;
566 realm = realm_find(realmname, FALSE);
568 for (pl = inst->attrs; pl; pl = pl->next) {
569 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
570 (strcasecmp(realmname, pl->name) != 0) ) {
574 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
578 check_item = pl->check;
580 while (check_item != NULL) {
583 * Append all SET operator attributes with no check.
585 if (check_item->operator == T_OP_SET) {
586 if (mypairappend(check_item, &send_tmp) < 0) {
587 return RLM_MODULE_FAIL;
590 check_item = check_item->next;
593 * Iterate through the request_pairs (items sent from NAS).
594 * Compare each pair to every rule for this realm/DEFAULT.
595 * Move an item to send_tmp if it matches all rules for
596 * attribute in question.
598 for (send_item = request_pairs;
600 send_item = send_item->next ) {
602 /* reset the pass/fail vars for each packet->vp. */
605 /* reset the check_item to the beginning */
606 check_item = pl->check;
609 * compare each packet->vp to the entire list of
610 * check_items for this realm.
612 while (check_item != NULL) {
613 if (send_item->attribute == check_item->attribute) {
615 compare = simplepaircmp(request, send_item,
617 check_pair(check_item, send_item, compare,
622 check_item = check_item->next;
625 /* only send if attribute passed all rules */
626 if (fail == 0 && pass > 0) {
627 if (mypairappend (send_item, &send_tmp) < 0) {
628 return RLM_MODULE_FAIL;
632 if (!fallthrough(pl->check))
635 pairfree (&request->proxy->vps);
636 request->proxy->vps = send_tmp;
639 return RLM_MODULE_OK;
640 pairdelete(&send_tmp, PW_FALL_THROUGH);
641 return RLM_MODULE_UPDATED;
644 static int attr_filter_postproxy(void *instance, REQUEST *request)
646 struct attr_filter_instance *inst = instance;
647 VALUE_PAIR *request_pairs;
648 VALUE_PAIR **reply_items;
649 VALUE_PAIR *reply_item;
650 VALUE_PAIR *reply_tmp = NULL;
651 VALUE_PAIR *check_item;
656 VALUE_PAIR *realmpair;
660 * It's not a proxy reply, so return NOOP
663 if( request->proxy == NULL ) {
664 return( RLM_MODULE_NOOP );
667 request_pairs = request->packet->vps;
668 reply_items = &request->proxy_reply->vps;
671 * Get the realm. Can't use request->config_items as
672 * that gets freed by rad_authenticate.... use the one
673 * set in the original request vps
675 realmpair = pairfind(request_pairs, PW_REALM);
677 /* Can't find a realm, so no filtering of attributes
678 * or should we use a DEFAULT entry?
679 * For now, just return NOTFOUND. (maybe NOOP?)
681 return RLM_MODULE_NOTFOUND;
684 realmname = (char *) realmpair->strvalue;
686 realm = realm_find(realmname, FALSE);
689 * Find the attr_filter profile entry for the realm.
691 for(pl = inst->attrs; pl; pl = pl->next) {
694 * If the current entry is NOT a default,
695 * AND the realm does NOT match the current entry,
696 * then skip to the next entry.
698 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
699 (strcmp(realmname, pl->name) != 0) ) {
703 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
707 check_item = pl->check;
709 while( check_item != NULL ) {
712 * If it is a SET operator, add the attribute to
713 * the reply list without checking reply_items.
716 if( check_item->operator == T_OP_SET ) {
717 if (mypairappend(check_item, &reply_tmp) < 0) {
718 return RLM_MODULE_FAIL;
721 check_item = check_item->next;
726 * Iterate through the reply items,
727 * comparing each reply item to every rule,
728 * then moving it to the reply_tmp list only if it matches all
729 * rules for that attribute.
730 * IE, Idle-Timeout is moved only if it matches
731 * all rules that describe an Idle-Timeout.
734 for( reply_item = *reply_items; reply_item != NULL;
735 reply_item = reply_item->next ) {
737 /* reset the pass,fail vars for each reply item */
740 /* reset the check_item pointer to beginning of the list */
741 check_item = pl->check;
743 while( check_item != NULL ) {
745 if(reply_item->attribute == check_item->attribute) {
747 compare = simplepaircmp(request, reply_item,
749 check_pair(check_item, reply_item, compare,
753 check_item = check_item->next;
757 /* only move attribute if it passed all rules */
758 if (fail == 0 && pass > 0) {
759 if (mypairappend( reply_item, &reply_tmp) < 0) {
760 return RLM_MODULE_FAIL;
766 /* If we shouldn't fall through, break */
767 if(!fallthrough(pl->check))
771 pairfree(&request->proxy_reply->vps);
772 request->proxy_reply->vps = reply_tmp;
775 * See if we succeeded. If we didn't find the realm,
776 * then exit from the module.
779 return RLM_MODULE_OK;
782 * Remove server internal parameters.
784 pairdelete(reply_items, PW_FALL_THROUGH);
786 return RLM_MODULE_UPDATED;
792 static int attr_filter_detach(void *instance)
794 struct attr_filter_instance *inst = instance;
795 pairlist_free(&inst->attrs);
796 free(inst->attrsfile);
802 /* globally exported name */
803 module_t rlm_attr_filter = {
805 0, /* type: reserved */
806 NULL, /* initialization */
807 attr_filter_instantiate, /* instantiation */
809 NULL, /* authentication */
810 attr_filter_authorize, /* authorization */
811 NULL, /* preaccounting */
812 attr_filter_accounting, /* accounting */
813 NULL, /* checksimul */
814 attr_filter_preproxy, /* pre-proxy */
815 attr_filter_postproxy, /* post-proxy */
818 attr_filter_detach, /* detach */