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