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 int 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));
166 * See if a VALUE_PAIR list contains Fall-Through = Yes
168 static int fallthrough(VALUE_PAIR *vp)
172 tmp = pairfind(vp, PW_FALL_THROUGH);
174 return tmp ? tmp->lvalue : 0;
177 static CONF_PARSER module_config[] = {
178 { "attrsfile", PW_TYPE_STRING_PTR,
179 offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
180 { NULL, -1, 0, NULL, NULL }
183 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
186 PAIR_LIST *attrs = NULL;
190 rcode = pairlist_read(filename, &attrs, 1);
196 * Walk through the 'attrs' file list.
202 entry->check = entry->reply;
205 for (vp = entry->check; vp != NULL; vp = vp->next) {
208 * If it's NOT a vendor attribute,
209 * and it's NOT a wire protocol
210 * and we ignore Fall-Through,
211 * then bitch about it, giving a good warning message.
213 if (!(vp->attribute & ~0xffff) &&
214 (vp->attribute > 0xff) &&
215 (vp->attribute > 1000)) {
216 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
217 "\tfound in filter list for realm \"%s\".\n",
218 filename, entry->lineno, vp->name,
231 * (Re-)read the "attrs" file into memory.
233 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
235 struct attr_filter_instance *inst;
238 inst = rad_malloc(sizeof *inst);
242 memset(inst, 0, sizeof(*inst));
244 if (cf_section_parse(conf, inst, module_config) < 0) {
249 rcode = getattrsfile(inst->attrsfile, &inst->attrs);
251 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
252 free(inst->attrsfile);
256 radlog(L_ERR|L_CONS, " rlm_attr_filter: Authorize method will be"\
262 static int attr_filter_authorize(void *instance, REQUEST *request)
264 struct attr_filter_instance *inst = instance;
265 VALUE_PAIR *request_pairs;
266 VALUE_PAIR **reply_items;
267 VALUE_PAIR *reply_item;
268 VALUE_PAIR *reply_tmp = NULL;
269 VALUE_PAIR *check_item;
274 VALUE_PAIR *realmpair;
279 * It's not a proxy reply, so return NOOP
282 if( request->proxy == NULL ) {
283 return( RLM_MODULE_NOOP );
286 request_pairs = request->packet->vps;
287 reply_items = &request->reply->vps;
290 * Get the realm. Can't use request->config_items as
291 * that gets freed by rad_authenticate.... use the one
292 * set in the original request vps
294 realmpair = pairfind(request_pairs, PW_REALM);
296 /* Can't find a realm, so no filtering of attributes
297 * or should we use a DEFAULT entry?
298 * For now, just return NOTFOUND. (maybe NOOP?)
300 return RLM_MODULE_NOTFOUND;
303 realmname = (char *) realmpair->strvalue;
304 realm = realm_find(realmname, FALSE);
307 * Find the attr_filter profile entry for the realm.
309 for(pl = inst->attrs; pl; pl = pl->next) {
312 * If the current entry is NOT a default,
313 * AND the realm does NOT match the current entry,
314 * then skip to the next entry.
316 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
317 (strcmp(realmname, pl->name) != 0) ) {
321 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
325 check_item = pl->check;
327 while( check_item != NULL ) {
330 * If it is a SET operator, add the attribute to
331 * the reply list without checking reply_items.
335 if( check_item->operator == T_OP_SET ) {
336 if (mypairappend(check_item, &reply_tmp) < 0) {
337 return RLM_MODULE_FAIL;
340 check_item = check_item->next;
345 * Iterate through the reply items, comparing each reply item
346 * to every rule, then moving it to the reply_tmp list
347 * only if it matches all rules for that attribute.
348 * IE, Idle-Timeout is moved only if it matches all rules that
349 * describe an Idle-Timeout.
352 for(reply_item = *reply_items;
354 reply_item = reply_item->next ) {
356 /* reset the pass,fail vars for each reply item */
359 /* reset the check_item pointer to beginning of the list */
360 check_item = pl->check;
362 while( check_item != NULL ) {
364 if(reply_item->attribute == check_item->attribute) {
366 compare = simplepaircmp(request, reply_item,
368 check_pair(check_item, reply_item, compare,
372 check_item = check_item->next;
376 /* only move attribute if it passed all rules */
377 if (fail == 0 && pass > 0) {
378 if (mypairappend( reply_item, &reply_tmp) < 0) {
379 return RLM_MODULE_FAIL;
385 /* If we shouldn't fall through, break */
386 if(!fallthrough(pl->check))
390 pairfree(&request->reply->vps);
391 request->reply->vps = reply_tmp;
394 * See if we succeeded. If we didn't find the realm,
395 * then exit from the module.
398 return RLM_MODULE_OK;
401 * Remove server internal parameters.
403 pairdelete(reply_items, PW_FALL_THROUGH);
405 return RLM_MODULE_UPDATED;
408 static int attr_filter_accounting(void *instance, REQUEST *request)
410 struct attr_filter_instance *inst = instance;
411 VALUE_PAIR *request_pairs;
412 VALUE_PAIR *send_item;
413 VALUE_PAIR *send_tmp = NULL;
414 VALUE_PAIR *check_item;
419 VALUE_PAIR *realmpair;
423 * Accounting is similar to pre-proxy.
424 * Here we are concerned with what we are going to forward to
425 * the remote server as opposed to concerns with what we will send
426 * to the NAS based on a proxy reply to an auth request.
429 if (request->packet->code != PW_ACCOUNTING_REQUEST) {
430 return (RLM_MODULE_NOOP);
433 request_pairs = request->packet->vps;
435 /* Get the realm from the original request vps. */
436 realmpair = pairfind(request_pairs, PW_REALM);
439 /* If there is no realm...NOOP */
440 return (RLM_MODULE_NOOP);
443 realmname = (char *) realmpair->strvalue;
444 realm = realm_find (realmname, FALSE);
447 * Find the attr_filter profile entry for the realm
449 for (pl = inst->attrs; pl; pl = pl->next) {
452 * If the current entry is NOT a default,
453 * AND the realm does not match the current entry,
454 * then skip to the next entry.
456 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
457 (strcasecmp(realmname, pl->name) != 0) ) {
461 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
465 check_item = pl->check;
467 while (check_item != NULL) {
470 * If it is a SET operator, add the attribute to
471 * the send list w/out checking.
474 if (check_item->operator == T_OP_SET) {
475 if (mypairappend(check_item, &send_tmp) < 0) {
476 return RLM_MODULE_FAIL;
479 check_item = check_item->next;
482 * Iterate through the request_pairs (items sent from NAS).
483 * Compare each pair to every rule for this realm/DEFAULT.
484 * Move an item to send_tmp if it matches all rules for
485 * attribute in question.
487 for (send_item = request_pairs; send_item != NULL;
488 send_item = send_item->next ) {
490 /* reset the pass/fail vars for each packet->vp. */
493 /* reset the check_item pointer to beginning of the list */
494 check_item = pl->check;
496 while (check_item != NULL) {
497 if (send_item->attribute == check_item->attribute) {
499 compare = simplepaircmp(request, send_item,
501 check_pair(check_item, send_item, compare,
505 check_item = check_item->next;
507 /* only send if attribute passed all rules */
508 if (fail == 0 && pass > 0) {
509 if (mypairappend (send_item, &send_tmp) < 0) {
510 return RLM_MODULE_FAIL;
514 if (!fallthrough(pl->check))
517 pairfree (&request->packet->vps);
518 request->packet->vps = send_tmp;
521 * See if we succeeded. If we didn't find the realm,
522 * then exit from the module.
525 return RLM_MODULE_OK;
528 * Remove server internal paramters.
530 pairdelete(&send_tmp, PW_FALL_THROUGH);
532 return RLM_MODULE_UPDATED;
535 static int attr_filter_preproxy (void *instance, REQUEST *request)
537 struct attr_filter_instance *inst = instance;
538 VALUE_PAIR *request_pairs;
539 VALUE_PAIR *send_item;
540 VALUE_PAIR *send_tmp = NULL;
541 VALUE_PAIR *check_item;
546 VALUE_PAIR *realmpair;
552 * concerned with what we are going to forward to
553 * to the remote server as opposed to we will do with
554 * with the remote servers' repsonse pairs. Consequently,
555 * we deal with modifications to the request->packet->vps;
557 request_pairs = request->proxy->vps;
558 if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
559 return (RLM_MODULE_NOOP);
561 realmpair = pairfind(request_pairs, PW_REALM);
563 return (RLM_MODULE_NOOP);
566 realmname = (char *)realmpair->strvalue;
567 realm = realm_find(realmname, FALSE);
569 for (pl = inst->attrs; pl; pl = pl->next) {
570 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
571 (strcasecmp(realmname, pl->name) != 0) ) {
575 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
579 check_item = pl->check;
581 while (check_item != NULL) {
584 * Append all SET operator attributes with no check.
586 if (check_item->operator == T_OP_SET) {
587 if (mypairappend(check_item, &send_tmp) < 0) {
588 return RLM_MODULE_FAIL;
591 check_item = check_item->next;
594 * Iterate through the request_pairs (items sent from NAS).
595 * Compare each pair to every rule for this realm/DEFAULT.
596 * Move an item to send_tmp if it matches all rules for
597 * attribute in question.
599 for (send_item = request_pairs;
601 send_item = send_item->next ) {
603 /* reset the pass/fail vars for each packet->vp. */
606 /* reset the check_item to the beginning */
607 check_item = pl->check;
610 * compare each packet->vp to the entire list of
611 * check_items for this realm.
613 while (check_item != NULL) {
614 if (send_item->attribute == check_item->attribute) {
616 compare = simplepaircmp(request, send_item,
618 check_pair(check_item, send_item, compare,
623 check_item = check_item->next;
626 /* only send if attribute passed all rules */
627 if (fail == 0 && pass > 0) {
628 if (mypairappend (send_item, &send_tmp) < 0) {
629 return RLM_MODULE_FAIL;
633 if (!fallthrough(pl->check))
636 pairfree (&request->proxy->vps);
637 request->proxy->vps = send_tmp;
640 return RLM_MODULE_OK;
641 pairdelete(&send_tmp, PW_FALL_THROUGH);
642 return RLM_MODULE_UPDATED;
645 static int attr_filter_postproxy(void *instance, REQUEST *request)
647 struct attr_filter_instance *inst = instance;
648 VALUE_PAIR *request_pairs;
649 VALUE_PAIR **reply_items;
650 VALUE_PAIR *reply_item;
651 VALUE_PAIR *reply_tmp = NULL;
652 VALUE_PAIR *check_item;
657 VALUE_PAIR *realmpair;
661 * It's not a proxy reply, so return NOOP
664 if( request->proxy == NULL ) {
665 return( RLM_MODULE_NOOP );
668 request_pairs = request->packet->vps;
669 reply_items = &request->proxy_reply->vps;
672 * Get the realm. Can't use request->config_items as
673 * that gets freed by rad_authenticate.... use the one
674 * set in the original request vps
676 realmpair = pairfind(request_pairs, PW_REALM);
678 /* Can't find a realm, so no filtering of attributes
679 * or should we use a DEFAULT entry?
680 * For now, just return NOTFOUND. (maybe NOOP?)
682 return RLM_MODULE_NOTFOUND;
685 realmname = (char *) realmpair->strvalue;
687 realm = realm_find(realmname, FALSE);
690 * Find the attr_filter profile entry for the realm.
692 for(pl = inst->attrs; pl; pl = pl->next) {
695 * If the current entry is NOT a default,
696 * AND the realm does NOT match the current entry,
697 * then skip to the next entry.
699 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
700 (strcmp(realmname, pl->name) != 0) ) {
704 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
708 check_item = pl->check;
710 while( check_item != NULL ) {
713 * If it is a SET operator, add the attribute to
714 * the reply list without checking reply_items.
717 if( check_item->operator == T_OP_SET ) {
718 if (mypairappend(check_item, &reply_tmp) < 0) {
719 return RLM_MODULE_FAIL;
722 check_item = check_item->next;
727 * Iterate through the reply items,
728 * comparing each reply item to every rule,
729 * then moving it to the reply_tmp list only if it matches all
730 * rules for that attribute.
731 * IE, Idle-Timeout is moved only if it matches
732 * all rules that describe an Idle-Timeout.
735 for( reply_item = *reply_items; reply_item != NULL;
736 reply_item = reply_item->next ) {
738 /* reset the pass,fail vars for each reply item */
741 /* reset the check_item pointer to beginning of the list */
742 check_item = pl->check;
744 while( check_item != NULL ) {
746 if(reply_item->attribute == check_item->attribute) {
748 compare = simplepaircmp(request, reply_item,
750 check_pair(check_item, reply_item, compare,
754 check_item = check_item->next;
758 /* only move attribute if it passed all rules */
759 if (fail == 0 && pass > 0) {
760 if (mypairappend( reply_item, &reply_tmp) < 0) {
761 return RLM_MODULE_FAIL;
767 /* If we shouldn't fall through, break */
768 if(!fallthrough(pl->check))
772 pairfree(&request->proxy_reply->vps);
773 request->proxy_reply->vps = reply_tmp;
776 * See if we succeeded. If we didn't find the realm,
777 * then exit from the module.
780 return RLM_MODULE_OK;
783 * Remove server internal parameters.
785 pairdelete(reply_items, PW_FALL_THROUGH);
787 return RLM_MODULE_UPDATED;
793 static int attr_filter_detach(void *instance)
795 struct attr_filter_instance *inst = instance;
796 pairlist_free(&inst->attrs);
797 free(inst->attrsfile);
803 /* globally exported name */
804 module_t rlm_attr_filter = {
806 0, /* type: reserved */
807 NULL, /* initialization */
808 attr_filter_instantiate, /* instantiation */
810 NULL, /* authentication */
811 attr_filter_authorize, /* authorization */
812 NULL, /* preaccounting */
813 attr_filter_accounting, /* accounting */
814 NULL, /* checksimul */
815 attr_filter_preproxy, /* pre-proxy */
816 attr_filter_postproxy, /* post-proxy */
819 attr_filter_detach, /* detach */