Don't call inet_ntop() for IP addresses by default. It's not
[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                   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));
124                   }
125
126                     regcomp(&reg, (char *)check_item->vp_strvalue, REG_EXTENDED);
127                     comp = regexec(&reg, (char *)reply_item->vp_strvalue,
128                                       0, NULL, 0);
129                     regfree(&reg);
130                     if (comp == 0) {
131                         ++*(pa);
132                     } else {
133                         ++*(fa);
134                     }
135                     break;
136
137                 case T_OP_REG_NE:
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));
143                   }
144
145                     regcomp(&reg, (char *)check_item->vp_strvalue, REG_EXTENDED);
146                     comp = regexec(&reg, (char *)reply_item->vp_strvalue,
147                                       0, NULL, 0);
148                     regfree(&reg);
149                     if (comp != 0) {
150                         ++*(pa);
151                     } else {
152                         ++*(fa);
153                     }
154                     break;
155 #endif
156         }
157         return 0;
158 }
159
160 /*
161  *      Copy the specified attribute to the specified list
162  */
163 static int mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
164 {
165   VALUE_PAIR *tmp;
166   tmp = paircreate(item->attribute, item->type);
167   if (!tmp) {
168         radlog(L_ERR|L_CONS, "no memory");
169         return -1;
170   }
171
172   /*
173    *    Copy EVERYTHING.
174    */
175   memcpy(tmp, item, sizeof(*tmp));
176   tmp->next = NULL;
177   pairadd(to, tmp);
178   return 0;
179 }
180
181 /*
182  *     See if a VALUE_PAIR list contains Fall-Through = Yes
183  */
184 static int fallthrough(VALUE_PAIR *vp)
185 {
186         VALUE_PAIR *tmp;
187
188         tmp = pairfind(vp, PW_FALL_THROUGH);
189
190         return tmp ? tmp->lvalue : 0;
191 }
192
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 }
197 };
198
199 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
200 {
201         int rcode;
202         PAIR_LIST *attrs = NULL;
203         PAIR_LIST *entry;
204         VALUE_PAIR *vp;
205
206         rcode = pairlist_read(filename, &attrs, 1);
207         if (rcode < 0) {
208                 return -1;
209         }
210
211         /*
212          * Walk through the 'attrs' file list.
213          */
214
215         entry = attrs;
216         while (entry) {
217
218                 entry->check = entry->reply;
219                 entry->reply = NULL;
220
221                 for (vp = entry->check; vp != NULL; vp = vp->next) {
222
223                     /*
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.
228                      */
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,
235                                   entry->name);
236                     }
237                 }
238
239                 entry = entry->next;
240         }
241
242         *pair_list = attrs;
243         return 0;
244 }
245
246 /*
247  *      (Re-)read the "attrs" file into memory.
248  */
249 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
250 {
251         struct attr_filter_instance *inst;
252         int rcode;
253
254         inst = rad_malloc(sizeof *inst);
255         if (!inst) {
256                 return -1;
257         }
258         memset(inst, 0, sizeof(*inst));
259
260         if (cf_section_parse(conf, inst, module_config) < 0) {
261                 free(inst);
262                 return -1;
263         }
264
265         rcode = getattrsfile(inst->attrsfile, &inst->attrs);
266         if (rcode != 0) {
267                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
268                 free(inst->attrsfile);
269                 free(inst);
270                 return -1;
271         }
272         *instance = inst;
273         return 0;
274 }
275
276 static int attr_filter_accounting(void *instance, REQUEST *request)
277 {
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;
283         PAIR_LIST       *pl;
284         int             found = 0;
285         int             compare;
286         int             pass, fail;
287         VALUE_PAIR      *realmpair;
288         REALM           *realm;
289         char            *realmname;
290         /*
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.
295          */
296
297         if (request->packet->code != PW_ACCOUNTING_REQUEST) {
298                 return (RLM_MODULE_NOOP);
299         }
300
301         request_pairs = request->packet->vps;
302
303         /* Get the realm from the original request vps. */
304         realmpair = pairfind(request_pairs, PW_REALM);
305
306         if (!realmpair) {
307                 /* If there is no realm...NOOP */
308                 return (RLM_MODULE_NOOP);
309         }
310
311         realmname = (char *) realmpair->vp_strvalue;
312         realm = realm_find (realmname, FALSE);
313
314         /*
315          * Find the attr_filter profile entry for the realm
316          */
317         for (pl = inst->attrs; pl; pl = pl->next) {
318
319                 /*
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.
323                  */
324                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
325                      (strcasecmp(realmname, pl->name) != 0) ) {
326                     continue;
327                 }
328
329                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
330                                                                     pl->lineno);
331                 found = 1;
332
333                 check_item = pl->check;
334
335                 while (check_item != NULL) {
336
337                     /*
338                      * If it is a SET operator, add the attribute to
339                      * the send list w/out checking.
340                      */
341
342                     if (check_item->operator == T_OP_SET) {
343                         if (mypairappend(check_item, &send_tmp) < 0) {
344                                 return RLM_MODULE_FAIL;
345                         }
346                     }
347                     check_item = check_item->next;
348                 }
349                 /*
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.
354                  */
355                 for (send_item = request_pairs; send_item != NULL;
356                      send_item = send_item->next ) {
357
358                     /* reset the pass/fail vars for each packet->vp. */
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                         if (send_item->attribute == check_item->attribute) {
366
367                             compare = simplepaircmp(request, send_item,
368                                                     check_item);
369                             check_pair(check_item, send_item, compare,
370                                        &pass, &fail);
371                         }
372
373                         check_item = check_item->next;
374                     }
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;
379                         }
380                     }
381                 }
382                 if (!fallthrough(pl->check))
383                     break;
384         }
385         pairfree (&request->packet->vps);
386         request->packet->vps = send_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 paramters.
397          */
398         pairdelete(&send_tmp, PW_FALL_THROUGH);
399
400         return RLM_MODULE_UPDATED;
401 }
402
403 static int attr_filter_preproxy (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         /*
419          * Pre-proxy we are
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;
424          */
425         request_pairs = request->proxy->vps;
426         if (request->packet->code != PW_AUTHENTICATION_REQUEST) {
427                 return (RLM_MODULE_NOOP);
428         }
429         realmpair = pairfind(request_pairs, PW_REALM);
430         if (!realmpair) {
431                 return (RLM_MODULE_NOOP);
432         }
433
434         realmname = (char *)realmpair->vp_strvalue;
435         realm = realm_find(realmname, FALSE);
436
437         for (pl = inst->attrs; pl; pl = pl->next) {
438                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
439                      (strcasecmp(realmname, pl->name) != 0) ) {
440                     continue;
441                 }
442
443                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
444                                                                     pl->lineno);
445                 found = 1;
446
447                 check_item = pl->check;
448
449                 while (check_item != NULL) {
450
451                     /*
452                      * Append all SET operator attributes with no check.
453                      */
454                     if (check_item->operator == T_OP_SET) {
455                         if (mypairappend(check_item, &send_tmp) < 0) {
456                                 return RLM_MODULE_FAIL;
457                         }
458                     }
459                     check_item = check_item->next;
460                 }
461                 /*
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.
466                  */
467                 for (send_item = request_pairs;
468                      send_item != NULL;
469                     send_item = send_item->next ) {
470
471                     /* reset the pass/fail vars for each packet->vp. */
472                     pass = fail = 0;
473
474                     /* reset the check_item to the beginning */
475                     check_item = pl->check;
476
477                     /*
478                      * compare each packet->vp to the entire list of
479                      * check_items for this realm.
480                      */
481                     while (check_item != NULL) {
482                         if (send_item->attribute == check_item->attribute) {
483
484                             compare = simplepaircmp(request, send_item,
485                                                     check_item);
486                             check_pair(check_item, send_item, compare,
487                                        &pass, &fail);
488
489                         }
490
491                         check_item = check_item->next;
492                     }
493
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;
498                         }
499                     }
500                 }
501                 if (!fallthrough(pl->check))
502                     break;
503         }
504         pairfree (&request->proxy->vps);
505         request->proxy->vps = send_tmp;
506
507         if (!found)
508                 return RLM_MODULE_OK;
509         pairdelete(&send_tmp, PW_FALL_THROUGH);
510         return RLM_MODULE_UPDATED;
511 }
512
513 static int attr_filter_postproxy(void *instance, REQUEST *request)
514 {
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;
521         PAIR_LIST       *pl;
522         int             found = 0;
523         int             compare;
524         int             pass, fail = 0;
525         VALUE_PAIR      *realmpair;
526         REALM           *realm;
527         char            *realmname;
528         /*
529          *      It's not a proxy reply, so return NOOP
530          */
531
532         if( request->proxy == NULL ) {
533                 return( RLM_MODULE_NOOP );
534         }
535
536         request_pairs = request->packet->vps;
537         reply_items = &request->proxy_reply->vps;
538
539         /*
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
543          */
544         realmpair = pairfind(request_pairs, PW_REALM);
545         if(!realmpair) {
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?)
549                  */
550                 return RLM_MODULE_NOTFOUND;
551         }
552
553         realmname = (char *) realmpair->vp_strvalue;
554
555         realm = realm_find(realmname, FALSE);
556
557         /*
558          *      Find the attr_filter profile entry for the realm.
559          */
560         for(pl = inst->attrs; pl; pl = pl->next) {
561
562                 /*
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.
566                  */
567                 if ( (strcmp(pl->name, "DEFAULT") != 0) &&
568                      (strcmp(realmname, pl->name) != 0) )  {
569                     continue;
570                 }
571
572                 DEBUG2(" attr_filter: Matched entry %s at line %d", pl->name,
573                                                                     pl->lineno);
574                 found = 1;
575
576                 check_item = pl->check;
577
578                 while( check_item != NULL ) {
579
580                     /*
581                      *    If it is a SET operator, add the attribute to
582                      *    the reply list without checking reply_items.
583                      */
584
585                     if( check_item->operator == T_OP_SET ) {
586                         if (mypairappend(check_item, &reply_tmp) < 0) {
587                                 return RLM_MODULE_FAIL;
588                         }
589                     }
590                     check_item = check_item->next;
591
592                 }
593
594                 /*
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.
601                  */
602
603                 for( reply_item = *reply_items; reply_item != NULL;
604                      reply_item = reply_item->next ) {
605
606                     /* reset the pass,fail vars for each reply item */
607                     pass = fail = 0;
608
609                     /* reset the check_item pointer to beginning of the list */
610                     check_item = pl->check;
611
612                     while( check_item != NULL ) {
613
614                         if(reply_item->attribute == check_item->attribute) {
615
616                             compare = simplepaircmp(request, reply_item,
617                                                     check_item);
618                             check_pair(check_item, reply_item, compare,
619                                        &pass, &fail);
620                         }
621
622                         check_item = check_item->next;
623
624                     }
625
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;
630                         }
631                     }
632
633                 }
634
635                 /* If we shouldn't fall through, break */
636                 if(!fallthrough(pl->check))
637                     break;
638         }
639
640         pairfree(&request->proxy_reply->vps);
641         request->proxy_reply->vps = reply_tmp;
642
643         /*
644          * See if we succeeded.  If we didn't find the realm,
645          * then exit from the module.
646          */
647         if (!found)
648                 return RLM_MODULE_OK;
649
650         /*
651          *      Remove server internal parameters.
652          */
653         pairdelete(reply_items, PW_FALL_THROUGH);
654
655         return RLM_MODULE_UPDATED;
656 }
657
658 /*
659  *      Clean up.
660  */
661 static int attr_filter_detach(void *instance)
662 {
663         struct attr_filter_instance *inst = instance;
664         pairlist_free(&inst->attrs);
665         free(inst->attrsfile);
666         free(inst);
667         return 0;
668 }
669
670
671 /* globally exported name */
672 module_t rlm_attr_filter = {
673         RLM_MODULE_INIT,
674         "attr_filter",
675         0,                              /* type: reserved */
676         attr_filter_instantiate,        /* instantiation */
677         attr_filter_detach,             /* detach */
678         {
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 */
686                 NULL                    /* post-auth */
687         },
688 };
689