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 if ((reply_item->type == PW_TYPE_IPADDR) &&
120 (reply_item->vp_strvalue[0] == '\0')) {
121 inet_ntop(AF_INET, &(reply_item->lvalue),
122 reply_item->vp_strvalue,
123 sizeof(reply_item->vp_strvalue));
126 regcomp(®, (char *)check_item->vp_strvalue, REG_EXTENDED);
127 comp = regexec(®, (char *)reply_item->vp_strvalue,
138 if ((reply_item->type == PW_TYPE_IPADDR) &&
139 (reply_item->vp_strvalue[0] == '\0')) {
140 inet_ntop(AF_INET, &(reply_item->lvalue),
141 reply_item->vp_strvalue,
142 sizeof(reply_item->vp_strvalue));
145 regcomp(®, (char *)check_item->vp_strvalue, REG_EXTENDED);
146 comp = regexec(®, (char *)reply_item->vp_strvalue,
161 * Copy the specified attribute to the specified list
163 static int mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
166 tmp = paircreate(item->attribute, item->type);
168 radlog(L_ERR|L_CONS, "no memory");
175 memcpy(tmp, item, sizeof(*tmp));
182 * See if a VALUE_PAIR list contains Fall-Through = Yes
184 static int fallthrough(VALUE_PAIR *vp)
188 tmp = pairfind(vp, PW_FALL_THROUGH);
190 return tmp ? tmp->lvalue : 0;
193 static const CONF_PARSER module_config[] = {
194 { "attrsfile", PW_TYPE_FILENAME,
195 offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
196 { NULL, -1, 0, NULL, NULL }
199 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
202 PAIR_LIST *attrs = NULL;
206 rcode = pairlist_read(filename, &attrs, 1);
212 * Walk through the 'attrs' file list.
218 entry->check = entry->reply;
221 for (vp = entry->check; vp != NULL; vp = vp->next) {
224 * If it's NOT a vendor attribute,
225 * and it's NOT a wire protocol
226 * and we ignore Fall-Through,
227 * then bitch about it, giving a good warning message.
229 if (!(vp->attribute & ~0xffff) &&
230 (vp->attribute > 0xff) &&
231 (vp->attribute > 1000)) {
232 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
233 "\tfound in filter list for realm \"%s\".\n",
234 filename, entry->lineno, vp->name,
247 * (Re-)read the "attrs" file into memory.
249 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
251 struct attr_filter_instance *inst;
254 inst = rad_malloc(sizeof *inst);
258 memset(inst, 0, sizeof(*inst));
260 if (cf_section_parse(conf, inst, module_config) < 0) {
265 rcode = getattrsfile(inst->attrsfile, &inst->attrs);
267 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
268 free(inst->attrsfile);
276 static int attr_filter_accounting(void *instance, REQUEST *request)
278 struct attr_filter_instance *inst = instance;
279 VALUE_PAIR *request_pairs;
280 VALUE_PAIR *send_item;
281 VALUE_PAIR *send_tmp = NULL;
282 VALUE_PAIR *check_item;
287 VALUE_PAIR *realmpair;
291 * Accounting is similar to pre-proxy.
292 * Here we are concerned with what we are going to forward to
293 * the remote server as opposed to concerns with what we will send
294 * to the NAS based on a proxy reply to an auth request.
297 if (request->packet->code != PW_ACCOUNTING_REQUEST) {
298 return (RLM_MODULE_NOOP);
301 request_pairs = request->packet->vps;
303 /* Get the realm from the original request vps. */
304 realmpair = pairfind(request_pairs, PW_REALM);
307 /* If there is no realm...NOOP */
308 return (RLM_MODULE_NOOP);
311 realmname = (char *) realmpair->vp_strvalue;
312 realm = realm_find (realmname, FALSE);
315 * Find the attr_filter profile entry for the realm
317 for (pl = inst->attrs; pl; pl = pl->next) {
320 * If the current entry is NOT a default,
321 * AND the realm does not match the current entry,
322 * then skip to the next entry.
324 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
325 (strcasecmp(realmname, pl->name) != 0) ) {
329 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
333 check_item = pl->check;
335 while (check_item != NULL) {
338 * If it is a SET operator, add the attribute to
339 * the send list w/out checking.
342 if (check_item->operator == T_OP_SET) {
343 if (mypairappend(check_item, &send_tmp) < 0) {
344 return RLM_MODULE_FAIL;
347 check_item = check_item->next;
350 * Iterate through the request_pairs (items sent from NAS).
351 * Compare each pair to every rule for this realm/DEFAULT.
352 * Move an item to send_tmp if it matches all rules for
353 * attribute in question.
355 for (send_item = request_pairs; send_item != NULL;
356 send_item = send_item->next ) {
358 /* reset the pass/fail vars for each packet->vp. */
361 /* reset the check_item pointer to beginning of the list */
362 check_item = pl->check;
364 while (check_item != NULL) {
365 if (send_item->attribute == check_item->attribute) {
367 compare = simplepaircmp(request, send_item,
369 check_pair(check_item, send_item, compare,
373 check_item = check_item->next;
375 /* only send if attribute passed all rules */
376 if (fail == 0 && pass > 0) {
377 if (mypairappend (send_item, &send_tmp) < 0) {
378 return RLM_MODULE_FAIL;
382 if (!fallthrough(pl->check))
385 pairfree (&request->packet->vps);
386 request->packet->vps = send_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 paramters.
398 pairdelete(&send_tmp, PW_FALL_THROUGH);
400 return RLM_MODULE_UPDATED;
403 static int attr_filter_preproxy (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;
420 * concerned with what we are going to forward to
421 * to the remote server as opposed to we will do with
422 * with the remote servers' repsonse pairs. Consequently,
423 * we deal with modifications to the request->packet->vps;
425 request_pairs = request->proxy->vps;
426 if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
427 return (RLM_MODULE_NOOP);
429 realmpair = pairfind(request_pairs, PW_REALM);
431 return (RLM_MODULE_NOOP);
434 realmname = (char *)realmpair->vp_strvalue;
435 realm = realm_find(realmname, FALSE);
437 for (pl = inst->attrs; pl; pl = pl->next) {
438 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
439 (strcasecmp(realmname, pl->name) != 0) ) {
443 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
447 check_item = pl->check;
449 while (check_item != NULL) {
452 * Append all SET operator attributes with no check.
454 if (check_item->operator == T_OP_SET) {
455 if (mypairappend(check_item, &send_tmp) < 0) {
456 return RLM_MODULE_FAIL;
459 check_item = check_item->next;
462 * Iterate through the request_pairs (items sent from NAS).
463 * Compare each pair to every rule for this realm/DEFAULT.
464 * Move an item to send_tmp if it matches all rules for
465 * attribute in question.
467 for (send_item = request_pairs;
469 send_item = send_item->next ) {
471 /* reset the pass/fail vars for each packet->vp. */
474 /* reset the check_item to the beginning */
475 check_item = pl->check;
478 * compare each packet->vp to the entire list of
479 * check_items for this realm.
481 while (check_item != NULL) {
482 if (send_item->attribute == check_item->attribute) {
484 compare = simplepaircmp(request, send_item,
486 check_pair(check_item, send_item, compare,
491 check_item = check_item->next;
494 /* only send if attribute passed all rules */
495 if (fail == 0 && pass > 0) {
496 if (mypairappend (send_item, &send_tmp) < 0) {
497 return RLM_MODULE_FAIL;
501 if (!fallthrough(pl->check))
504 pairfree (&request->proxy->vps);
505 request->proxy->vps = send_tmp;
508 return RLM_MODULE_OK;
509 pairdelete(&send_tmp, PW_FALL_THROUGH);
510 return RLM_MODULE_UPDATED;
513 static int attr_filter_postproxy(void *instance, REQUEST *request)
515 struct attr_filter_instance *inst = instance;
516 VALUE_PAIR *request_pairs;
517 VALUE_PAIR **reply_items;
518 VALUE_PAIR *reply_item;
519 VALUE_PAIR *reply_tmp = NULL;
520 VALUE_PAIR *check_item;
525 VALUE_PAIR *realmpair;
529 * It's not a proxy reply, so return NOOP
532 if( request->proxy == NULL ) {
533 return( RLM_MODULE_NOOP );
536 request_pairs = request->packet->vps;
537 reply_items = &request->proxy_reply->vps;
540 * Get the realm. Can't use request->config_items as
541 * that gets freed by rad_authenticate.... use the one
542 * set in the original request vps
544 realmpair = pairfind(request_pairs, PW_REALM);
546 /* Can't find a realm, so no filtering of attributes
547 * or should we use a DEFAULT entry?
548 * For now, just return NOTFOUND. (maybe NOOP?)
550 return RLM_MODULE_NOTFOUND;
553 realmname = (char *) realmpair->vp_strvalue;
555 realm = realm_find(realmname, FALSE);
558 * Find the attr_filter profile entry for the realm.
560 for(pl = inst->attrs; pl; pl = pl->next) {
563 * If the current entry is NOT a default,
564 * AND the realm does NOT match the current entry,
565 * then skip to the next entry.
567 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
568 (strcmp(realmname, pl->name) != 0) ) {
572 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
576 check_item = pl->check;
578 while( check_item != NULL ) {
581 * If it is a SET operator, add the attribute to
582 * the reply list without checking reply_items.
585 if( check_item->operator == T_OP_SET ) {
586 if (mypairappend(check_item, &reply_tmp) < 0) {
587 return RLM_MODULE_FAIL;
590 check_item = check_item->next;
595 * Iterate through the reply items,
596 * comparing each reply item to every rule,
597 * then moving it to the reply_tmp list only if it matches all
598 * rules for that attribute.
599 * IE, Idle-Timeout is moved only if it matches
600 * all rules that describe an Idle-Timeout.
603 for( reply_item = *reply_items; reply_item != NULL;
604 reply_item = reply_item->next ) {
606 /* reset the pass,fail vars for each reply item */
609 /* reset the check_item pointer to beginning of the list */
610 check_item = pl->check;
612 while( check_item != NULL ) {
614 if(reply_item->attribute == check_item->attribute) {
616 compare = simplepaircmp(request, reply_item,
618 check_pair(check_item, reply_item, compare,
622 check_item = check_item->next;
626 /* only move attribute if it passed all rules */
627 if (fail == 0 && pass > 0) {
628 if (mypairappend( reply_item, &reply_tmp) < 0) {
629 return RLM_MODULE_FAIL;
635 /* If we shouldn't fall through, break */
636 if(!fallthrough(pl->check))
640 pairfree(&request->proxy_reply->vps);
641 request->proxy_reply->vps = reply_tmp;
644 * See if we succeeded. If we didn't find the realm,
645 * then exit from the module.
648 return RLM_MODULE_OK;
651 * Remove server internal parameters.
653 pairdelete(reply_items, PW_FALL_THROUGH);
655 return RLM_MODULE_UPDATED;
661 static int attr_filter_detach(void *instance)
663 struct attr_filter_instance *inst = instance;
664 pairlist_free(&inst->attrs);
665 free(inst->attrsfile);
671 /* globally exported name */
672 module_t rlm_attr_filter = {
675 0, /* type: reserved */
676 attr_filter_instantiate, /* instantiation */
677 attr_filter_detach, /* detach */
679 NULL, /* authentication */
680 NULL, /* authorization */
681 NULL, /* preaccounting */
682 attr_filter_accounting, /* accounting */
683 NULL, /* checksimul */
684 attr_filter_preproxy, /* pre-proxy */
685 attr_filter_postproxy, /* post-proxy */