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