58962f8701050e4f0b544cbfed11cd66b03b0737
[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
48         /* autz */
49         char *attrsfile;
50         PAIR_LIST *attrs;
51
52 };
53
54 /*
55  *      Copy the specified attribute to the specified list
56  */
57 static void mypairappend(VALUE_PAIR *item, VALUE_PAIR **to)
58 {
59   VALUE_PAIR *tmp;
60
61   tmp = paircreate(item->attribute, item->type);
62   if( tmp == NULL ) {
63     radlog(L_ERR|L_CONS, "no memory");
64     exit(1);
65   }
66   switch (tmp->type) {
67       case PW_TYPE_INTEGER:
68       case PW_TYPE_IPADDR:
69       case PW_TYPE_DATE:
70         tmp->lvalue = item->lvalue;
71         break;
72       default:
73         memcpy((char *)tmp->strvalue, (char *)item->strvalue, item->length);
74         tmp->length = item->length;
75         break;
76   }
77   pairadd(to, tmp);
78 }
79
80 /*
81  *     See if a VALUE_PAIR list contains Fall-Through = Yes
82  */
83 static int fallthrough(VALUE_PAIR *vp)
84 {
85         VALUE_PAIR *tmp;
86
87         tmp = pairfind(vp, PW_FALL_THROUGH);
88
89         return tmp ? tmp->lvalue : 0;
90 }
91
92 static CONF_PARSER module_config[] = {
93         { "attrsfile",     PW_TYPE_STRING_PTR,
94           offsetof(struct attr_filter_instance,attrsfile), NULL, "${raddbdir}/attrs" },
95         { NULL, -1, 0, NULL, NULL }
96 };
97
98 static int getattrsfile(const char *filename, PAIR_LIST **pair_list)
99 {
100         int rcode;
101         PAIR_LIST *attrs = NULL;
102         PAIR_LIST *entry;
103         VALUE_PAIR *vp;
104
105         rcode = pairlist_read(filename, &attrs, 1);
106         if (rcode < 0) {
107             return -1;
108         }
109         
110         /*
111          *      Walk through the 'attrs' file list.
112          */
113         
114         entry = attrs;
115         while (entry) {
116                 
117             entry->check = entry->reply;
118             entry->reply = NULL;
119         
120             for (vp = entry->check; vp != NULL; vp = vp->next) {
121
122                 /*
123                  *      If it's NOT a vendor attribute,
124                  *      and it's NOT a wire protocol
125                  *      and we ignore Fall-Through,
126                  *      then bitch about it, giving a
127                  *      good warning message.
128                  */
129                 if (!(vp->attribute & ~0xffff) &&
130                     (vp->attribute > 0xff) &&
131                     (vp->attribute > 1000)) {
132                     log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
133                               "\tfound in filter list for realm \"%s\".\n",
134                               filename, entry->lineno, vp->name,
135                               entry->name);
136                 }
137             }
138             
139             entry = entry->next;
140         }
141
142         *pair_list = attrs;
143         return 0;
144 }
145
146 /*
147  *      (Re-)read the "attrs" file into memory.
148  */
149 static int attr_filter_instantiate(CONF_SECTION *conf, void **instance)
150 {
151         struct attr_filter_instance *inst;
152         int rcode;
153
154         inst = rad_malloc(sizeof *inst);
155
156         if (cf_section_parse(conf, inst, module_config) < 0) {
157                 free(inst);
158                 return -1;
159         }
160
161         rcode = getattrsfile(inst->attrsfile, &inst->attrs);
162         if (rcode != 0) {
163                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->attrsfile);
164                 free(inst->attrsfile);
165                 free(inst);
166                 return -1;
167         }
168
169         *instance = inst;
170         return 0;
171 }
172
173 /*
174  *      Find the named realm in the database.  Create the
175  *      set of attribute-value pairs to check and reply with
176  *      for this realm from the database.
177  */
178 static int attr_filter_authorize(void *instance, REQUEST *request)
179 {
180         struct attr_filter_instance *inst = instance;
181         VALUE_PAIR      *request_pairs;
182         VALUE_PAIR      **reply_items;
183         VALUE_PAIR      *reply_item;
184         VALUE_PAIR      *reply_tmp = NULL;
185         VALUE_PAIR      *check_item;
186         PAIR_LIST       *pl;
187         int             found = 0;
188         int             compare;
189         int             pass, fail;
190 #ifdef HAVE_REGEX_H
191         regex_t         reg;
192 #endif
193         VALUE_PAIR      *realmpair;
194         REALM           *realm;
195         char            *realmname;
196
197         /*
198          *      It's not a proxy reply, so return NOOP
199          */
200
201         if( request->proxy == NULL ) {
202                 return( RLM_MODULE_NOOP );
203         }
204
205         request_pairs = request->packet->vps;
206         reply_items = &request->reply->vps;
207
208         /*
209          *      Get the realm.  Can't use request->config_items as
210          *      that gets freed by rad_authenticate....  use the one
211          *      set in the original request vps
212          */
213         realmpair = pairfind(request_pairs, PW_REALM);
214         if(!realmpair) {
215                 /*    Can't find a realm, so no filtering of attributes 
216                  *    or should we use a DEFAULT entry?
217                  *    For now, just return NOTFOUND. (maybe NOOP?)
218                  */ 
219                 return RLM_MODULE_NOTFOUND;
220         }
221
222         realmname = (char *) realmpair->strvalue;
223         realm = realm_find(realmname, FALSE);
224
225         /*
226          *      Find the attr_filter profile entry for the realm.
227          */
228         for(pl = inst->attrs; pl; pl = pl->next) {
229
230             /*
231              *  If the current entry is NOT a default,
232              *  AND the realm does NOT match the current entry,
233              *  then skip to the next entry.
234              */
235             if ( (strcmp(pl->name, "DEFAULT") != 0)
236                  && (strcmp(realmname, pl->name) != 0) )  {
237                         continue;
238                 }
239
240                 DEBUG2("  attr_filter: Matched entry %s at line %d", pl->name, pl->lineno);
241
242                 found = 1;
243                 
244                 check_item = pl->check;
245
246                 while( check_item != NULL ) {
247
248                     /*
249                      *      If it is a SET operator, add the attribute to
250                      *      the reply list without checking reply_items.
251                      *
252                      */
253
254                     if( check_item->operator == T_OP_SET ) {
255                         mypairappend(check_item, &reply_tmp);
256                     }
257                     check_item = check_item->next;
258
259                 }  /* while( check_item != NULL ) */
260
261                 /* 
262                  * Iterate through the reply items, comparing each reply item to every rule,
263                  * then moving it to the reply_tmp list only if it matches all rules for that
264                  * attribute.  IE, Idle-Timeout is moved only if it matches all rules that
265                  * describe an Idle-Timeout.  
266                  */
267
268                 for( reply_item = *reply_items; 
269                      reply_item != NULL; 
270                      reply_item = reply_item->next ) {
271
272                   /* reset the pass,fail vars for each reply item */
273                   pass = fail = 0;
274
275                   /* reset the check_item pointer to the beginning of the list */
276                   check_item = pl->check;
277
278                   while( check_item != NULL ) {
279                       
280                       if(reply_item->attribute == check_item->attribute) {
281
282                         compare = simplepaircmp(request, reply_item, check_item);
283
284                         switch(check_item->operator) {
285
286                             case T_OP_SET:            /* nothing to do for set */
287                                 break;
288                             case T_OP_EQ:
289                             default:
290                                 radlog(L_ERR, "Invalid operator for item %s: "
291                                        "reverting to '=='", check_item->name);
292                                 
293                             case T_OP_CMP_TRUE:       /* compare always == 0 */
294                             case T_OP_CMP_FALSE:      /* compare always == 1 */
295                             case T_OP_CMP_EQ:
296                                 if (compare == 0) {
297                                     pass++;
298                                 } else {
299                                     fail++;
300                                 }
301                                 break;
302
303                             case T_OP_NE:
304                                 if (compare != 0) {
305                                     pass++;
306                                 } else {
307                                     fail++;
308                                 }
309                                 break;
310
311                             case T_OP_LT:
312                                 if (compare < 0) {
313                                     pass++;
314                                 } else {
315                                     fail++;
316                                 }
317                                 break;
318
319                             case T_OP_GT:
320                                 if (compare > 0) {
321                                     pass++;
322                                 } else {
323                                     fail++;
324                                 }
325                                 break;
326                                 
327                             case T_OP_LE:
328                                 if (compare <= 0) {
329                                     pass++;
330                                 } else {
331                                     fail++;
332                                 }
333                                 break;
334
335                             case T_OP_GE:
336                                 if (compare >= 0) {
337                                     pass++;
338                                 } else {
339                                     fail++;
340                                 }
341                                 break;
342 #ifdef HAVE_REGEX_H
343                             case T_OP_REG_EQ:
344                                 regcomp(&reg, (char *)check_item->strvalue, 0);
345                                 compare = regexec(&reg, (char *)reply_item->strvalue,
346                                                   0, NULL, 0);
347                                 regfree(&reg);
348                                 if (compare == 0) {
349                                     pass++;
350                                 } else {
351                                     fail++;
352                                 }
353                                 break;
354
355                             case T_OP_REG_NE:
356                                 regcomp(&reg, (char *)check_item->strvalue, 0);
357                                 compare = regexec(&reg, (char *)reply_item->strvalue,
358                                                   0, NULL, 0);
359                                 regfree(&reg);
360                                 if (compare != 0) {
361                                     pass++;
362                                 } else {
363                                     fail++;
364                                 }
365                                 break;
366 #endif
367                         }  /* switch( check_item->operator ) */
368
369                       }  /* if reply == check */
370
371                       check_item = check_item->next;
372
373                     }  /* while( check ) */
374
375                     /* only move attribute if it passed all rules */
376                     if (fail == 0 && pass > 0) {
377                       mypairappend( reply_item, &reply_tmp);
378                     }
379
380                 }  /* for( reply ) */
381                 
382                 /* If we shouldn't fall through, break */
383                 if(!fallthrough(pl->check))
384                     break;
385         }
386
387         pairfree(&request->reply->vps);
388         request->reply->vps = reply_tmp;
389         
390         /*
391          *      See if we succeeded.  If we didn't find the realm,
392          *      then exit from the module.
393          */
394         if (!found)
395                 return RLM_MODULE_OK;
396
397         /*
398          *      Remove server internal parameters.
399          */
400         pairdelete(reply_items, PW_FALL_THROUGH);
401
402         return RLM_MODULE_UPDATED;
403 }
404
405 /*
406  *      Clean up.
407  */
408 static int attr_filter_detach(void *instance)
409 {
410         struct attr_filter_instance *inst = instance;
411         pairlist_free(&inst->attrs);
412         free(inst->attrsfile);
413         free(inst);
414         return 0;
415 }
416
417
418 /* globally exported name */
419 module_t rlm_attr_filter = {
420         "attr_filter",
421         0,                              /* type: reserved */
422         NULL,                           /* initialization */
423         attr_filter_instantiate,        /* instantiation */
424         {
425                 NULL,                   /* authentication */
426                 attr_filter_authorize,  /* authorization */
427                 NULL,                   /* preaccounting */
428                 NULL,                   /* accounting */
429                 NULL                    /* checksimul */
430         },
431         attr_filter_detach,             /* detach */
432         NULL                            /* destroy */
433 };
434