When copying an attribute, copy ALL of it, not just some.
[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) {
152           radlog(L_ERR|L_CONS, "no memory");
153           exit(1);
154   }
155
156   /*
157    *    Copy EVERYTHING.
158    */
159   memcpy(tmp, item, sizeof(*tmp));
160   tmp->next = NULL;
161   pairadd(to, tmp);
162 }
163
164 /*
165  *     See if a VALUE_PAIR list contains Fall-Through = Yes
166  */
167 static int fallthrough(VALUE_PAIR *vp)
168 {
169         VALUE_PAIR *tmp;
170
171         tmp = pairfind(vp, PW_FALL_THROUGH);
172
173         return tmp ? tmp->lvalue : 0;
174 }
175
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 }
180 };
181
182 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
183 {
184         int rcode;
185         PAIR_LIST *attrs = NULL;
186         PAIR_LIST *entry;
187         VALUE_PAIR *vp;
188
189         rcode = pairlist_read(filename, &attrs, 1);
190         if (rcode < 0) {
191                 return -1;
192         }
193
194         /*
195          * Walk through the 'attrs' file list.
196          */
197
198         entry = attrs;
199         while (entry) {
200
201                 entry->check = entry->reply;
202                 entry->reply = NULL;
203
204                 for (vp = entry->check; vp != NULL; vp = vp->next) {
205
206                     /*
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.
211                      */
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,
218                                   entry->name);
219                     }
220                 }
221
222                 entry = entry->next;
223         }
224
225         *pair_list = attrs;
226         return 0;
227 }
228
229 /*
230  *      (Re-)read the "attrs" file into memory.
231  */
232 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
233 {
234         struct attr_filter_instance *inst;
235         int rcode;
236
237         inst = rad_malloc(sizeof *inst);
238         if (!inst) {
239                 return -1;
240         }
241         memset(inst, 0, sizeof(*inst));
242
243         if (cf_section_parse(conf, inst, module_config) < 0) {
244                 free(inst);
245                 return -1;
246         }
247
248         rcode = getattrsfile(inst->attrsfile, &inst->attrs);
249         if (rcode != 0) {
250                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
251                 free(inst->attrsfile);
252                 free(inst);
253                 return -1;
254         }
255         radlog(L_ERR|L_CONS, " rlm_attr_filter: Authorize method will be"\
256                              " deprecated.");
257         *instance = inst;
258         return 0;
259 }
260
261 static int attr_filter_authorize(void *instance, REQUEST *request)
262 {
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;
269         PAIR_LIST       *pl;
270         int             found = 0;
271         int             compare;
272         int             pass, fail;
273         VALUE_PAIR      *realmpair;
274         REALM           *realm;
275         char            *realmname;
276
277         /*
278          *      It's not a proxy reply, so return NOOP
279          */
280
281         if( request->proxy == NULL ) {
282                 return( RLM_MODULE_NOOP );
283         }
284
285         request_pairs = request->packet->vps;
286         reply_items = &request->reply->vps;
287
288         /*
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
292          */
293         realmpair = pairfind(request_pairs, PW_REALM);
294         if(!realmpair) {
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?)
298                  */
299                 return RLM_MODULE_NOTFOUND;
300         }
301
302         realmname = (char *) realmpair->strvalue;
303         realm = realm_find(realmname, FALSE);
304
305         /*
306          *      Find the attr_filter profile entry for the realm.
307          */
308         for(pl = inst->attrs; pl; pl = pl->next) {
309
310                 /*
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.
314                  */
315                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
316                      (strcmp(realmname, pl->name) != 0) )  {
317                     continue;
318                 }
319
320                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
321                                                                     pl->lineno);
322                 found = 1;
323
324                 check_item = pl->check;
325
326                 while( check_item != NULL ) {
327
328                     /*
329                      *      If it is a SET operator, add the attribute to
330                      *      the reply list without checking reply_items.
331                      *
332                      */
333
334                     if( check_item->operator == T_OP_SET ) {
335                         mypairappend(check_item, &reply_tmp);
336                     }
337                     check_item = check_item->next;
338
339                 }
340
341                 /*
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.
347                  */
348
349                 for(reply_item = *reply_items;
350                     reply_item != NULL;
351                     reply_item = reply_item->next ) {
352
353                     /* reset the pass,fail vars for each reply item */
354                     pass = fail = 0;
355
356                     /* reset the check_item pointer to beginning of the list */
357                     check_item = pl->check;
358
359                     while( check_item != NULL ) {
360
361                         if(reply_item->attribute == check_item->attribute) {
362
363                             compare = simplepaircmp(request, reply_item,
364                                                     check_item);
365                             check_pair(check_item, reply_item, compare,
366                                        &pass, &fail);
367                         }
368
369                         check_item = check_item->next;
370
371                     }
372
373                     /* only move attribute if it passed all rules */
374                     if (fail == 0 && pass > 0) {
375                         mypairappend( reply_item, &reply_tmp);
376                     }
377
378                 }
379
380                 /* If we shouldn't fall through, break */
381                 if(!fallthrough(pl->check))
382                     break;
383         }
384
385         pairfree(&request->reply->vps);
386         request->reply->vps = reply_tmp;
387
388         /*
389          *      See if we succeeded.  If we didn't find the realm,
390          *      then exit from the module.
391          */
392         if (!found)
393                 return RLM_MODULE_OK;
394
395         /*
396          *      Remove server internal parameters.
397          */
398         pairdelete(reply_items, PW_FALL_THROUGH);
399
400         return RLM_MODULE_UPDATED;
401 }
402
403 static int attr_filter_accounting(void *instance, REQUEST *request)
404 {
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;
410         PAIR_LIST       *pl;
411         int             found = 0;
412         int             compare;
413         int             pass, fail;
414         VALUE_PAIR      *realmpair;
415         REALM           *realm;
416         char            *realmname;
417         /*
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.
422          */
423
424         if (request->packet->code != PW_ACCOUNTING_REQUEST) {
425                 return (RLM_MODULE_NOOP);
426         }
427
428         request_pairs = request->packet->vps;
429
430         /* Get the realm from the original request vps. */
431         realmpair = pairfind(request_pairs, PW_REALM);
432
433         if (!realmpair) {
434                 /* If there is no realm...NOOP */
435                 return (RLM_MODULE_NOOP);
436         }
437
438         realmname = (char *) realmpair->strvalue;
439         realm = realm_find (realmname, FALSE);
440
441         /*
442          * Find the attr_filter profile entry for the realm
443          */
444         for (pl = inst->attrs; pl; pl = pl->next) {
445
446                 /*
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.
450                  */
451                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
452                      (strcasecmp(realmname, pl->name) != 0) ) {
453                     continue;
454                 }
455
456                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
457                                                                     pl->lineno);
458                 found = 1;
459
460                 check_item = pl->check;
461
462                 while (check_item != NULL) {
463
464                     /*
465                      * If it is a SET operator, add the attribute to
466                      * the send list w/out checking.
467                      */
468
469                     if (check_item->operator == T_OP_SET) {
470                         mypairappend(check_item, &send_tmp);
471                     }
472                     check_item = check_item->next;
473                 }
474                 /*
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.
479                  */
480                 for (send_item = request_pairs; send_item != NULL;
481                      send_item = send_item->next ) {
482
483                     /* reset the pass/fail vars for each packet->vp. */
484                     pass = fail = 0;
485
486                     /* reset the check_item pointer to beginning of the list */
487                     check_item = pl->check;
488
489                     while (check_item != NULL) {
490                         if (send_item->attribute == check_item->attribute) {
491
492                             compare = simplepaircmp(request, send_item,
493                                                     check_item);
494                             check_pair(check_item, send_item, compare,
495                                        &pass, &fail);
496                         }
497
498                         check_item = check_item->next;
499                     }
500                     /* only send if attribute passed all rules */
501                     if (fail == 0 && pass > 0) {
502                         mypairappend (send_item, &send_tmp);
503                     }
504                 }
505                 if (!fallthrough(pl->check))
506                     break;
507         }
508         pairfree (&request->packet->vps);
509         request->packet->vps = send_tmp;
510
511         /*
512          * See if we succeeded. If we didn't find the realm,
513          * then exit from the module.
514          */
515         if (!found)
516                 return RLM_MODULE_OK;
517
518         /*
519          * Remove server internal paramters.
520          */
521         pairdelete(&send_tmp, PW_FALL_THROUGH);
522
523         return RLM_MODULE_UPDATED;
524 }
525
526 static int attr_filter_preproxy (void *instance, REQUEST *request)
527 {
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;
533         PAIR_LIST       *pl;
534         int             found = 0;
535         int             compare;
536         int             pass, fail;
537         VALUE_PAIR      *realmpair;
538         REALM           *realm;
539         char            *realmname;
540
541         /*
542          * Pre-proxy we are
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;
547          */
548         request_pairs = request->proxy->vps;
549         if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
550                 return (RLM_MODULE_NOOP);
551         }
552         realmpair = pairfind(request_pairs, PW_REALM);
553         if (!realmpair) {
554                 return (RLM_MODULE_NOOP);
555         }
556
557         realmname = (char *)realmpair->strvalue;
558         realm = realm_find(realmname, FALSE);
559
560         for (pl = inst->attrs; pl; pl = pl->next) {
561                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
562                      (strcasecmp(realmname, pl->name) != 0) ) {
563                     continue;
564                 }
565
566                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
567                                                                     pl->lineno);
568                 found = 1;
569
570                 check_item = pl->check;
571
572                 while (check_item != NULL) {
573
574                     /*
575                      * Append all SET operator attributes with no check.
576                      */
577                     if (check_item->operator == T_OP_SET) {
578                         mypairappend(check_item, &send_tmp);
579                     }
580                     check_item = check_item->next;
581                 }
582                 /*
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.
587                  */
588                 for (send_item = request_pairs;
589                      send_item != NULL;
590                     send_item = send_item->next ) {
591
592                     /* reset the pass/fail vars for each packet->vp. */
593                     pass = fail = 0;
594
595                     /* reset the check_item to the beginning */
596                     check_item = pl->check;
597
598                     /*
599                      * compare each packet->vp to the entire list of
600                      * check_items for this realm.
601                      */
602                     while (check_item != NULL) {
603                         if (send_item->attribute == check_item->attribute) {
604
605                             compare = simplepaircmp(request, send_item,
606                                                     check_item);
607                             check_pair(check_item, send_item, compare,
608                                        &pass, &fail);
609
610                         }
611
612                         check_item = check_item->next;
613                     }
614
615                     /* only send if attribute passed all rules */
616                     if (fail == 0 && pass > 0) {
617                         mypairappend (send_item, &send_tmp);
618                     }
619                 }
620                 if (!fallthrough(pl->check))
621                     break;
622         }
623         pairfree (&request->proxy->vps);
624         request->proxy->vps = send_tmp;
625
626         if (!found)
627                 return RLM_MODULE_OK;
628         pairdelete(&send_tmp, PW_FALL_THROUGH);
629         return RLM_MODULE_UPDATED;
630 }
631
632 static int attr_filter_postproxy(void *instance, REQUEST *request)
633 {
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;
640         PAIR_LIST       *pl;
641         int             found = 0;
642         int             compare;
643         int             pass, fail = 0;
644         VALUE_PAIR      *realmpair;
645         REALM           *realm;
646         char            *realmname;
647         /*
648          *      It's not a proxy reply, so return NOOP
649          */
650
651         if( request->proxy == NULL ) {
652                 return( RLM_MODULE_NOOP );
653         }
654
655         request_pairs = request->packet->vps;
656         reply_items = &request->proxy_reply->vps;
657
658         /*
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
662          */
663         realmpair = pairfind(request_pairs, PW_REALM);
664         if(!realmpair) {
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?)
668                  */
669                 return RLM_MODULE_NOTFOUND;
670         }
671
672         realmname = (char *) realmpair->strvalue;
673
674         realm = realm_find(realmname, FALSE);
675
676         /*
677          *      Find the attr_filter profile entry for the realm.
678          */
679         for(pl = inst->attrs; pl; pl = pl->next) {
680
681                 /*
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.
685                  */
686                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
687                      (strcmp(realmname, pl->name) != 0) )  {
688                     continue;
689                 }
690
691                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
692                                                                     pl->lineno);
693                 found = 1;
694
695                 check_item = pl->check;
696
697                 while( check_item != NULL ) {
698
699                     /*
700                      *    If it is a SET operator, add the attribute to
701                      *    the reply list without checking reply_items.
702                      */
703
704                     if( check_item->operator == T_OP_SET ) {
705                         mypairappend(check_item, &reply_tmp);
706                     }
707                     check_item = check_item->next;
708
709                 }
710
711                 /*
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.
718                  */
719
720                 for( reply_item = *reply_items; reply_item != NULL;
721                      reply_item = reply_item->next ) {
722
723                     /* reset the pass,fail vars for each reply item */
724                     pass = fail = 0;
725
726                     /* reset the check_item pointer to beginning of the list */
727                     check_item = pl->check;
728
729                     while( check_item != NULL ) {
730
731                         if(reply_item->attribute == check_item->attribute) {
732
733                             compare = simplepaircmp(request, reply_item,
734                                                     check_item);
735                             check_pair(check_item, reply_item, compare,
736                                        &pass, &fail);
737                         }
738
739                         check_item = check_item->next;
740
741                     }
742
743                     /* only move attribute if it passed all rules */
744                     if (fail == 0 && pass > 0) {
745                         mypairappend( reply_item, &reply_tmp);
746                     }
747
748                 }
749
750                 /* If we shouldn't fall through, break */
751                 if(!fallthrough(pl->check))
752                     break;
753         }
754
755         pairfree(&request->proxy_reply->vps);
756         request->proxy_reply->vps = reply_tmp;
757
758         /*
759          * See if we succeeded.  If we didn't find the realm,
760          * then exit from the module.
761          */
762         if (!found)
763                 return RLM_MODULE_OK;
764
765         /*
766          *      Remove server internal parameters.
767          */
768         pairdelete(reply_items, PW_FALL_THROUGH);
769
770         return RLM_MODULE_UPDATED;
771 }
772
773 /*
774  *      Clean up.
775  */
776 static int attr_filter_detach(void *instance)
777 {
778         struct attr_filter_instance *inst = instance;
779         pairlist_free(&inst->attrs);
780         free(inst->attrsfile);
781         free(inst);
782         return 0;
783 }
784
785
786 /* globally exported name */
787 module_t rlm_attr_filter = {
788         "attr_filter",
789         0,                              /* type: reserved */
790         NULL,                           /* initialization */
791         attr_filter_instantiate,        /* instantiation */
792         {
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 */
800                 NULL                    /* post-auth */
801         },
802         attr_filter_detach,             /* detach */
803         NULL                            /* destroy */
804 };
805