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