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