Added 'accounting' and 'pre-proxy' method calls.
[freeradius.git] / src / modules / rlm_attr_filter / rlm_attr_filter.c
1 /*
2  * rlm_attr_filter.c  - Filter A/V Pairs received back from proxy reqs
3  *                      before sending reply to the NAS/Server that sent
4  *                      it to us.
5  *
6  * Version:      $Id$
7  *
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.
11  * 
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.
16  *  
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.
20  *
21  * Copyright (C) 2001 The FreeRADIUS server project
22  * Copyright (C) 2001 Chris Parker <cparker@starnetusa.net>
23  */
24
25 #include        "autoconf.h"
26 #include        "libradius.h"
27
28 #include        <sys/stat.h>
29
30 #include        <stdlib.h>
31 #include        <string.h>
32 #include        <netdb.h>
33 #include        <ctype.h>
34 #include        <fcntl.h>
35 #include        <limits.h>
36
37 #ifdef HAVE_REGEX_H
38 #  include      <regex.h>
39 #endif
40
41 #include        "radiusd.h"
42 #include        "modules.h"
43
44 static const char rcsid[] = "$Id$";
45
46 struct attr_filter_instance {
47         /* autz */
48         char *attrsfile;
49         PAIR_LIST *attrs;
50 };
51
52 static int check_pair(VALUE_PAIR *check_item, VALUE_PAIR *reply_item,
53                       int comp, int *pa, int *fa) {
54 #ifdef HAVE_REGEX_H
55         regex_t         reg;
56 #endif
57         switch(check_item->operator) {
58
59                 case T_OP_SET:            /* nothing to do for set */
60                     break;
61                 case T_OP_EQ:
62                      default:
63                         radlog(L_ERR, "Invalid operator for item %s: "
64                         "reverting to '=='", check_item->name);
65                         
66                 case T_OP_CMP_TRUE:       /* comp always == 0 */
67                 case T_OP_CMP_FALSE:      /* comp always == 1 */
68                 case T_OP_CMP_EQ:
69                     if (comp == 0) {
70                         ++*(pa);
71                     } else {
72                         ++*(fa);
73                     }
74                     break;
75
76                 case T_OP_NE:
77                     if (comp != 0) {
78                         ++*(pa);
79                     } else {
80                         ++*(fa);
81                     }
82                     break;
83
84                 case T_OP_LT:
85                     if (comp < 0) {
86                         ++*(pa);
87                     } else {
88                         ++*(fa);
89                     }
90                     break;
91
92                 case T_OP_GT:
93                     if (comp > 0) {
94                         ++*(pa);
95                     } else {
96                         ++*(fa);
97                     }
98                     break;
99                                 
100                 case T_OP_LE:
101                     if (comp <= 0) {
102                         ++*(pa);
103                     } else {
104                         ++*(fa);
105                     }
106                     break;
107
108                 case T_OP_GE:
109                     if (comp >= 0) {
110                         ++*(pa);
111                     } else {
112                         ++*(fa);
113                     }
114                     break;
115 #ifdef HAVE_REGEX_H
116                 case T_OP_REG_EQ:
117                     regcomp(&reg, (char *)check_item->strvalue, 0);
118                     comp = regexec(&reg, (char *)reply_item->strvalue,
119                                       0, NULL, 0);
120                     regfree(&reg);
121                     if (comp == 0) {
122                         ++*(pa);
123                     } else {
124                         ++*(fa);
125                     }
126                     break;
127
128                 case T_OP_REG_NE:
129                     regcomp(&reg, (char *)check_item->strvalue, 0);
130                     comp = regexec(&reg, (char *)reply_item->strvalue,
131                                       0, NULL, 0);
132                     regfree(&reg);
133                     if (comp != 0) {
134                         ++*(pa);
135                     } else {
136                         ++*(fa);
137                     }
138                     break;
139 #endif
140         }
141         return 0;
142 }
143
144 /*
145  *      Copy the specified attribute to the specified list
146  */
147 static void mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
148 {
149   VALUE_PAIR *tmp;
150   tmp = paircreate(item->attribute, item->type);
151   if( tmp == NULL ) {
152     radlog(L_ERR|L_CONS, "no memory");
153     exit(1);
154   }
155   switch (tmp->type) {
156       case PW_TYPE_INTEGER:
157       case PW_TYPE_IPADDR:
158       case PW_TYPE_DATE:
159         tmp->lvalue = item->lvalue;
160         break;
161       default:
162         memcpy((char *)tmp->strvalue, (char *)item->strvalue, item->length);
163         tmp->length = item->length;
164         break;
165   }
166   pairadd(to, tmp);
167 }
168
169 /*
170  *     See if a VALUE_PAIR list contains Fall-Through = Yes
171  */
172 static int fallthrough(VALUE_PAIR *vp)
173 {
174         VALUE_PAIR *tmp;
175
176         tmp = pairfind(vp, PW_FALL_THROUGH);
177
178         return tmp ? tmp->lvalue : 0;
179 }
180
181 static CONF_PARSER module_config[] = {
182         { "attrsfile",     PW_TYPE_STRING_PTR,
183           offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
184         { NULL, -1, 0, NULL, NULL }
185 };
186
187 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
188 {
189         int rcode;
190         PAIR_LIST *attrs = NULL;
191         PAIR_LIST *entry;
192         VALUE_PAIR *vp;
193
194         rcode = pairlist_read(filename, &attrs, 1);
195         if (rcode < 0) {
196                 return -1;
197         }
198         
199         /*
200          * Walk through the 'attrs' file list.
201          */
202         
203         entry = attrs;
204         while (entry) {
205                 
206                 entry->check = entry->reply;
207                 entry->reply = NULL;
208         
209                 for (vp = entry->check; vp != NULL; vp = vp->next) {
210
211                     /*
212                      * If it's NOT a vendor attribute,
213                      * and it's NOT a wire protocol
214                      * and we ignore Fall-Through,
215                      * then bitch about it, giving a good warning message.
216                      */
217                     if (!(vp->attribute & ~0xffff) &&
218                          (vp->attribute > 0xff) &&
219                          (vp->attribute > 1000)) {
220                         log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
221                                   "\tfound in filter list for realm \"%s\".\n",
222                                   filename, entry->lineno, vp->name,
223                                   entry->name);
224                     }
225                 }
226             
227                 entry = entry->next;
228         }
229
230         *pair_list = attrs;
231         return 0;
232 }
233
234 /*
235  *      (Re-)read the "attrs" file into memory.
236  */
237 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
238 {
239         struct attr_filter_instance *inst;
240         int rcode;
241
242         inst = rad_malloc(sizeof *inst);
243         if (!inst) {
244                 return -1;
245         }
246         memset(inst, 0, sizeof(*inst));
247
248         if (cf_section_parse(conf, inst, module_config) < 0) {
249                 free(inst);
250                 return -1;
251         }
252
253         rcode = getattrsfile(inst->attrsfile, &inst->attrs);
254         if (rcode != 0) {
255                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
256                 free(inst->attrsfile);
257                 free(inst);
258                 return -1;
259         }
260         radlog(L_ERR|L_CONS, " rlm_attr_filter: Authorize method will be"\
261                              " deprecated.");
262         *instance = inst;
263         return 0;
264 }
265
266 static int attr_filter_authorize(void *instance, REQUEST *request)
267 {
268         struct attr_filter_instance *inst = instance;
269         VALUE_PAIR      *request_pairs;
270         VALUE_PAIR      **reply_items;
271         VALUE_PAIR      *reply_item;
272         VALUE_PAIR      *reply_tmp = NULL;
273         VALUE_PAIR      *check_item;
274         PAIR_LIST       *pl;
275         int             found = 0;
276         int             compare;
277         int             pass, fail;
278         VALUE_PAIR      *realmpair;
279         REALM           *realm;
280         char            *realmname;
281
282         /*
283          *      It's not a proxy reply, so return NOOP
284          */
285
286         if( request->proxy == NULL ) {
287                 return( RLM_MODULE_NOOP );
288         }
289
290         request_pairs = request->packet->vps;
291         reply_items = &request->reply->vps;
292
293         /*
294          *      Get the realm.  Can't use request->config_items as
295          *      that gets freed by rad_authenticate....  use the one
296          *      set in the original request vps
297          */
298         realmpair = pairfind(request_pairs, PW_REALM);
299         if(!realmpair) {
300                 /*    Can't find a realm, so no filtering of attributes 
301                  *    or should we use a DEFAULT entry?
302                  *    For now, just return NOTFOUND. (maybe NOOP?)
303                  */
304                 return RLM_MODULE_NOTFOUND;
305         }
306
307         realmname = (char *) realmpair->strvalue;
308         realm = realm_find(realmname, FALSE);
309
310         /*
311          *      Find the attr_filter profile entry for the realm.
312          */
313         for(pl = inst->attrs; pl; pl = pl->next) {
314
315                 /*
316                  *  If the current entry is NOT a default,
317                  *  AND the realm does NOT match the current entry,
318                  *  then skip to the next entry.
319                  */
320                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
321                      (strcmp(realmname, pl->name) != 0) )  {
322                     continue;
323                 }
324
325                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
326                                                                     pl->lineno);
327                 found = 1;
328
329                 check_item = pl->check;
330
331                 while( check_item != NULL ) {
332
333                     /*
334                      *      If it is a SET operator, add the attribute to
335                      *      the reply list without checking reply_items.
336                      *
337                      */
338
339                     if( check_item->operator == T_OP_SET ) {
340                         mypairappend(check_item, &reply_tmp);
341                     }
342                     check_item = check_item->next;
343
344                 }
345
346                 /* 
347                  * Iterate through the reply items, comparing each reply item
348                  * to every rule, then moving it to the reply_tmp list 
349                  * only if it matches all rules for that attribute.
350                  * IE, Idle-Timeout is moved only if it matches all rules that
351                  * describe an Idle-Timeout.  
352                  */
353
354                 for(reply_item = *reply_items;
355                     reply_item != NULL;
356                     reply_item = reply_item->next ) {
357
358                     /* reset the pass,fail vars for each reply item */
359                     pass = fail = 0;
360
361                     /* reset the check_item pointer to beginning of the list */
362                     check_item = pl->check;
363
364                     while( check_item != NULL ) {
365
366                         if(reply_item->attribute == check_item->attribute) {
367
368                             compare = simplepaircmp(request, reply_item,
369                                                     check_item);
370                             check_pair(check_item, reply_item, compare, 
371                                        &pass, &fail);
372                         }
373
374                         check_item = check_item->next;
375
376                     }
377
378                     /* only move attribute if it passed all rules */
379                     if (fail == 0 && pass > 0) {
380                         mypairappend( reply_item, &reply_tmp);
381                     }
382
383                 }
384
385                 /* If we shouldn't fall through, break */
386                 if(!fallthrough(pl->check))
387                     break;
388         }
389
390         pairfree(&request->reply->vps);
391         request->reply->vps = reply_tmp;
392
393         /*
394          *      See if we succeeded.  If we didn't find the realm,
395          *      then exit from the module.
396          */
397         if (!found)
398                 return RLM_MODULE_OK;
399
400         /*
401          *      Remove server internal parameters.
402          */
403         pairdelete(reply_items, PW_FALL_THROUGH);
404
405         return RLM_MODULE_UPDATED;
406 }
407
408 static int attr_filter_accounting(void *instance, REQUEST *request)
409 {
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;
415         PAIR_LIST       *pl;
416         int             found = 0;
417         int             compare;
418         int             pass, fail;
419         VALUE_PAIR      *realmpair;
420         REALM           *realm;
421         char            *realmname;
422         /*
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.
427          */
428
429         if (request->packet->code != PW_ACCOUNTING_REQUEST) {
430                 return (RLM_MODULE_NOOP);
431         }
432
433         request_pairs = request->packet->vps;
434
435         /* Get the realm from the original request vps. */
436         realmpair = pairfind(request_pairs, PW_REALM);
437
438         if (!realmpair) {
439                 /* If there is no realm...NOOP */
440                 return (RLM_MODULE_NOOP);
441         }
442
443         realmname = (char *) realmpair->strvalue;
444         realm = realm_find (realmname, FALSE);
445
446         /*
447          * Find the attr_filter profile entry for the realm
448          */
449         for (pl = inst->attrs; pl; pl = pl->next) {
450
451                 /*
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.
455                  */
456                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
457                      (strcasecmp(realmname, pl->name) != 0) ) {
458                     continue;
459                 }
460
461                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
462                                                                     pl->lineno);
463                 found = 1;
464
465                 check_item = pl->check;
466
467                 while (check_item != NULL) {
468
469                     /*
470                      * If it is a SET operator, add the attribute to
471                      * the send list w/out checking.
472                      */
473
474                     if (check_item->operator == T_OP_SET) {
475                         mypairappend(check_item, &send_tmp);
476                     }
477                     check_item = check_item->next;
478                 }
479                 /*
480                  * Iterate through the request_pairs (items sent from NAS).
481                  * Compare each pair to every rule for this realm/DEFAULT.
482                  * Move an item to send_tmp if it matches all rules for
483                  * attribute in question.
484                  */
485                 for (send_item = request_pairs; send_item != NULL;
486                      send_item = send_item->next ) {
487
488                     /* reset the pass/fail vars for each packet->vp. */
489                     pass = fail = 0;
490                 
491                     /* reset the check_item pointer to beginning of the list */
492                     check_item = pl->check;
493
494                     while (check_item != NULL) {
495                         if (send_item->attribute == check_item->attribute) {
496
497                             compare = simplepaircmp(request, send_item,
498                                                     check_item);
499                             check_pair(check_item, send_item, compare,
500                                        &pass, &fail);
501                         }
502
503                         check_item = check_item->next;
504                     }
505                     /* only send if attribute passed all rules */
506                     if (fail == 0 && pass > 0) {
507                         mypairappend (send_item, &send_tmp);
508                     }
509                 }
510                 if (!fallthrough(pl->check))
511                     break;
512         }
513         pairfree (&request->packet->vps);
514         request->packet->vps = send_tmp;
515         
516         /*
517          * See if we succeeded. If we didn't find the realm,
518          * then exit from the module.
519          */
520         if (!found)
521                 return RLM_MODULE_OK;
522
523         /*
524          * Remove server internal paramters.
525          */
526         pairdelete(&send_tmp, PW_FALL_THROUGH);
527
528         return RLM_MODULE_UPDATED;
529 }
530
531 static int attr_filter_preproxy (void *instance, REQUEST *request)
532 {
533         struct attr_filter_instance *inst = instance;
534         VALUE_PAIR      *request_pairs;
535         VALUE_PAIR      *send_item;
536         VALUE_PAIR      *send_tmp = NULL;
537         VALUE_PAIR      *check_item;
538         PAIR_LIST       *pl;
539         int             found = 0;
540         int             compare;
541         int             pass, fail;
542         VALUE_PAIR      *realmpair;
543         REALM           *realm;
544         char            *realmname;
545
546         /*
547          * Pre-proxy we are
548          * concerned with what we are going to forward to
549          * to the remote server as opposed to we will do with
550          * with the remote servers' repsonse pairs. Consequently,
551          * we deal with modifications to the request->packet->vps;
552          */
553         request_pairs = request->proxy->vps;
554         if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
555                 return (RLM_MODULE_NOOP);
556         }
557         realmpair = pairfind(request_pairs, PW_REALM);
558         if (!realmpair) {
559                 return (RLM_MODULE_NOOP);
560         }
561
562         realmname = (char *)realmpair->strvalue;
563         realm = realm_find(realmname, FALSE);
564
565         for (pl = inst->attrs; pl; pl = pl->next) {
566                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
567                      (strcasecmp(realmname, pl->name) != 0) ) {
568                     continue;
569                 }
570
571                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
572                                                                     pl->lineno);
573                 found = 1;
574
575                 check_item = pl->check;
576
577                 while (check_item != NULL) {
578
579                     /*
580                      * Append all SET operator attributes with no check.
581                      */
582                     if (check_item->operator == T_OP_SET) {
583                         mypairappend(check_item, &send_tmp);
584                     }
585                     check_item = check_item->next;
586                 }
587                 /*
588                  * Iterate through the request_pairs (items sent from NAS).
589                  * Compare each pair to every rule for this realm/DEFAULT.
590                  * Move an item to send_tmp if it matches all rules for
591                  * attribute in question.
592                  */
593                 for (send_item = request_pairs;
594                      send_item != NULL;
595                     send_item = send_item->next ) {
596
597                     /* reset the pass/fail vars for each packet->vp. */
598                     pass = fail = 0;
599
600                     /* reset the check_item to the beginning */
601                     check_item = pl->check;
602
603                     /*
604                      * compare each packet->vp to the entire list of
605                      * check_items for this realm.
606                      */
607                     while (check_item != NULL) {
608                         if (send_item->attribute == check_item->attribute) {
609
610                             compare = simplepaircmp(request, send_item,
611                                                     check_item);
612                             check_pair(check_item, send_item, compare,
613                                        &pass, &fail);
614
615                         }
616
617                         check_item = check_item->next;
618                     }
619
620                     /* only send if attribute passed all rules */
621                     if (fail == 0 && pass > 0) {
622                         mypairappend (send_item, &send_tmp);
623                     }
624                 }
625                 if (!fallthrough(pl->check))
626                     break;
627         }
628         pairfree (&request->proxy->vps);
629         request->proxy->vps = send_tmp;
630
631         if (!found)
632                 return RLM_MODULE_OK;
633         pairdelete(&send_tmp, PW_FALL_THROUGH);
634         return RLM_MODULE_UPDATED;
635 }
636
637 static int attr_filter_postproxy(void *instance, REQUEST *request)
638 {
639         struct attr_filter_instance *inst = instance;
640         VALUE_PAIR      *request_pairs;
641         VALUE_PAIR      **reply_items;
642         VALUE_PAIR      *reply_item;
643         VALUE_PAIR      *reply_tmp = NULL;
644         VALUE_PAIR      *check_item;
645         PAIR_LIST       *pl;
646         int             found = 0;
647         int             compare;
648         int             pass, fail = 0;
649         VALUE_PAIR      *realmpair;
650         REALM           *realm;
651         char            *realmname;
652         /*
653          *      It's not a proxy reply, so return NOOP
654          */
655
656         if( request->proxy == NULL ) {
657                 return( RLM_MODULE_NOOP );
658         }
659
660         request_pairs = request->packet->vps;
661         reply_items = &request->proxy_reply->vps;
662
663         /*
664          * Get the realm.  Can't use request->config_items as
665          * that gets freed by rad_authenticate....  use the one
666          * set in the original request vps
667          */
668         realmpair = pairfind(request_pairs, PW_REALM);
669         if(!realmpair) {
670                 /*    Can't find a realm, so no filtering of attributes
671                  *    or should we use a DEFAULT entry?
672                  *    For now, just return NOTFOUND. (maybe NOOP?)
673                  */
674                 return RLM_MODULE_NOTFOUND;
675         }
676
677         realmname = (char *) realmpair->strvalue;
678
679         realm = realm_find(realmname, FALSE);
680
681         /*
682          *      Find the attr_filter profile entry for the realm.
683          */
684         for(pl = inst->attrs; pl; pl = pl->next) {
685
686                 /*
687                  *  If the current entry is NOT a default,
688                  *  AND the realm does NOT match the current entry,
689                  *  then skip to the next entry.
690                  */
691                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
692                      (strcmp(realmname, pl->name) != 0) )  {
693                     continue;
694                 }
695
696                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
697                                                                     pl->lineno);
698                 found = 1;
699
700                 check_item = pl->check;
701
702                 while( check_item != NULL ) {
703
704                     /*
705                      *    If it is a SET operator, add the attribute to
706                      *    the reply list without checking reply_items.
707                      */
708
709                     if( check_item->operator == T_OP_SET ) {
710                         mypairappend(check_item, &reply_tmp);
711                     }
712                     check_item = check_item->next;
713
714                 }
715
716                 /*
717                  * Iterate through the reply items,
718                  * comparing each reply item to every rule,
719                  * then moving it to the reply_tmp list only if it matches all
720                  * rules for that attribute.
721                  * IE, Idle-Timeout is moved only if it matches
722                  * all rules that describe an Idle-Timeout.
723                  */
724
725                 for( reply_item = *reply_items; reply_item != NULL;
726                      reply_item = reply_item->next ) {
727
728                     /* reset the pass,fail vars for each reply item */
729                     pass = fail = 0;
730
731                     /* reset the check_item pointer to beginning of the list */
732                     check_item = pl->check;
733
734                     while( check_item != NULL ) {
735
736                         if(reply_item->attribute == check_item->attribute) {
737
738                             compare = simplepaircmp(request, reply_item,
739                                                     check_item);
740                             check_pair(check_item, reply_item, compare, 
741                                        &pass, &fail);
742                         }
743
744                         check_item = check_item->next;
745
746                     }
747
748                     /* only move attribute if it passed all rules */
749                     if (fail == 0 && pass > 0) {
750                         mypairappend( reply_item, &reply_tmp);
751                     }
752
753                 }
754
755                 /* If we shouldn't fall through, break */
756                 if(!fallthrough(pl->check))
757                     break;
758         }
759
760         pairfree(&request->proxy_reply->vps);
761         request->proxy_reply->vps = reply_tmp;
762
763         /*
764          * See if we succeeded.  If we didn't find the realm,
765          * then exit from the module.
766          */
767         if (!found)
768                 return RLM_MODULE_OK;
769
770         /*
771          *      Remove server internal parameters.
772          */
773         pairdelete(reply_items, PW_FALL_THROUGH);
774
775         return RLM_MODULE_UPDATED;
776 }
777                 
778 /*
779  *      Clean up.
780  */
781 static int attr_filter_detach(void *instance)
782 {
783         struct attr_filter_instance *inst = instance;
784         pairlist_free(&inst->attrs);
785         free(inst->attrsfile);
786         free(inst);
787         return 0;
788 }
789
790
791 /* globally exported name */
792 module_t rlm_attr_filter = {
793         "attr_filter",
794         0,                              /* type: reserved */
795         NULL,                           /* initialization */
796         attr_filter_instantiate,        /* instantiation */
797         {
798                 NULL,                   /* authentication */
799                 attr_filter_authorize,  /* authorization */
800                 NULL,                   /* preaccounting */
801                 attr_filter_accounting, /* accounting */
802                 NULL,                   /* checksimul */
803                 attr_filter_preproxy,   /* pre-proxy */
804                 attr_filter_postproxy,  /* post-proxy */
805                 NULL                    /* post-auth */
806         },
807         attr_filter_detach,             /* detach */
808         NULL                            /* destroy */
809 };
810