Use macro for terminating CONF_PARSER arrays
[freeradius.git] / src / modules / rlm_attr_filter / rlm_attr_filter.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_attr_filter.c
20  * @brief Filter the contents of a list, allowing only certain attributes.
21  *
22  * @copyright (C) 2001,2006 The FreeRADIUS server project
23  * @copyright (C) 2001 Chris Parker <cparker@starnetusa.net>
24  */
25 RCSID("$Id$")
26
27 #include        <freeradius-devel/radiusd.h>
28 #include        <freeradius-devel/modules.h>
29 #include        <freeradius-devel/rad_assert.h>
30
31 #include        <sys/stat.h>
32
33 #include        <ctype.h>
34 #include        <fcntl.h>
35
36 /*
37  *      Define a structure with the module configuration, so it can
38  *      be used as the instance handle.
39  */
40 typedef struct rlm_attr_filter {
41         char const      *filename;
42         char const      *key;
43         bool            relaxed;
44         PAIR_LIST       *attrs;
45 } rlm_attr_filter_t;
46
47 static const CONF_PARSER module_config[] = {
48         { "attrsfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, rlm_attr_filter_t, filename), NULL },
49         { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_REQUIRED, rlm_attr_filter_t, filename), NULL },
50         { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_attr_filter_t, key), "%{Realm}" },
51         { "relaxed", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_attr_filter_t, relaxed), "no" },
52         CONF_PARSER_TERMINATOR
53 };
54
55 static void check_pair(REQUEST *request, VALUE_PAIR *check_item, VALUE_PAIR *reply_item, int *pass, int *fail)
56 {
57         int compare;
58
59         if (check_item->op == T_OP_SET) return;
60
61         compare = fr_pair_cmp(check_item, reply_item);
62         if (compare < 0) {
63                 REDEBUG("Comparison failed: %s", fr_strerror());
64         }
65
66         if (compare == 1) {
67                 ++*(pass);
68         } else {
69                 ++*(fail);
70         }
71
72         if (RDEBUG_ENABLED3) {
73                 char rule[1024], pair[1024];
74
75                 vp_prints(rule, sizeof(rule), check_item);
76                 vp_prints(pair, sizeof(pair), reply_item);
77                 RDEBUG3("%s %s %s", pair, compare == 1 ? "allowed by" : "disallowed by", rule);
78         }
79
80         return;
81 }
82
83 static int attr_filter_getfile(TALLOC_CTX *ctx, char const *filename, PAIR_LIST **pair_list)
84 {
85         vp_cursor_t cursor;
86         int rcode;
87         PAIR_LIST *attrs = NULL;
88         PAIR_LIST *entry;
89         VALUE_PAIR *vp;
90
91         rcode = pairlist_read(ctx, filename, &attrs, 1);
92         if (rcode < 0) {
93                 return -1;
94         }
95
96         /*
97          * Walk through the 'attrs' file list.
98          */
99
100         entry = attrs;
101         while (entry) {
102                 entry->check = entry->reply;
103                 entry->reply = NULL;
104
105                 for (vp = fr_cursor_init(&cursor, &entry->check);
106                      vp;
107                      vp = fr_cursor_next(&cursor)) {
108                     /*
109                      * If it's NOT a vendor attribute,
110                      * and it's NOT a wire protocol
111                      * and we ignore Fall-Through,
112                      * then bitch about it, giving a good warning message.
113                      */
114                      if ((vp->da->vendor == 0) &&
115                          (vp->da->attr > 1000)) {
116                         WARN("[%s]:%d Check item \"%s\"\n\tfound in filter list for realm \"%s\".\n",
117                                filename, entry->lineno, vp->da->name, entry->name);
118                     }
119                 }
120
121                 entry = entry->next;
122         }
123
124         *pair_list = attrs;
125         return 0;
126 }
127
128
129 /*
130  *      (Re-)read the "attrs" file into memory.
131  */
132 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
133 {
134         rlm_attr_filter_t *inst = instance;
135         int rcode;
136
137         rcode = attr_filter_getfile(inst, inst->filename, &inst->attrs);
138         if (rcode != 0) {
139                 ERROR("Errors reading %s", inst->filename);
140
141                 return -1;
142         }
143
144         return 0;
145 }
146
147
148 /*
149  *      Common attr_filter checks
150  */
151 static rlm_rcode_t CC_HINT(nonnull(1,2)) attr_filter_common(void *instance, REQUEST *request, RADIUS_PACKET *packet)
152 {
153         rlm_attr_filter_t *inst = instance;
154         VALUE_PAIR      *vp;
155         vp_cursor_t     input, check, out;
156         VALUE_PAIR      *input_item, *check_item, *output;
157         PAIR_LIST       *pl;
158         int             found = 0;
159         int             pass, fail = 0;
160         char const      *keyname = NULL;
161         char            buffer[256];
162
163         if (!packet) return RLM_MODULE_NOOP;
164
165         if (!inst->key) {
166                 VALUE_PAIR      *namepair;
167
168                 namepair = fr_pair_find_by_num(request->packet->vps, PW_REALM, 0, TAG_ANY);
169                 if (!namepair) {
170                         return (RLM_MODULE_NOOP);
171                 }
172                 keyname = namepair->vp_strvalue;
173         } else {
174                 int len;
175
176                 len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL);
177                 if (len < 0) {
178                         return RLM_MODULE_FAIL;
179                 }
180                 if (len == 0) {
181                         return RLM_MODULE_NOOP;
182                 }
183                 keyname = buffer;
184         }
185
186         /*
187          *      Head of the output list
188          */
189         output = NULL;
190         fr_cursor_init(&out, &output);
191
192         /*
193          *      Find the attr_filter profile entry for the entry.
194          */
195         for (pl = inst->attrs; pl; pl = pl->next) {
196                 int fall_through = 0;
197                 int relax_filter = inst->relaxed;
198
199                 /*
200                  *  If the current entry is NOT a default,
201                  *  AND the realm does NOT match the current entry,
202                  *  then skip to the next entry.
203                  */
204                 if ((strcmp(pl->name, "DEFAULT") != 0) &&
205                     (strcmp(keyname, pl->name) != 0))  {
206                     continue;
207                 }
208
209                 RDEBUG2("Matched entry %s at line %d", pl->name, pl->lineno);
210                 found = 1;
211
212                 for (check_item = fr_cursor_init(&check, &pl->check);
213                      check_item;
214                      check_item = fr_cursor_next(&check)) {
215                         if (!check_item->da->vendor &&
216                             (check_item->da->attr == PW_FALL_THROUGH) &&
217                                 (check_item->vp_integer == 1)) {
218                                 fall_through = 1;
219                                 continue;
220                         }
221                         else if (!check_item->da->vendor && check_item->da->attr == PW_RELAX_FILTER) {
222                                 relax_filter = check_item->vp_integer;
223                                 continue;
224                         }
225
226                         /*
227                          *    If it is a SET operator, add the attribute to
228                          *    the output list without checking it.
229                          */
230                         if (check_item->op == T_OP_SET ) {
231                                 vp = fr_pair_copy(packet, check_item);
232                                 if (!vp) {
233                                         goto error;
234                                 }
235                                 radius_xlat_do(request, vp);
236                                 fr_cursor_insert(&out, vp);
237                         }
238                 }
239
240                 /*
241                  *      Iterate through the input items, comparing
242                  *      each item to every rule, then moving it to the
243                  *      output list only if it matches all rules
244                  *      for that attribute.  IE, Idle-Timeout is moved
245                  *      only if it matches all rules that describe an
246                  *      Idle-Timeout.
247                  */
248                 for (input_item = fr_cursor_init(&input, &packet->vps);
249                      input_item;
250                      input_item = fr_cursor_next(&input)) {
251                         pass = fail = 0; /* reset the pass,fail vars for each reply item */
252
253                         /*
254                          *  Reset the check_item pointer to beginning of the list
255                          */
256                         for (check_item = fr_cursor_first(&check);
257                              check_item;
258                              check_item = fr_cursor_next(&check)) {
259                                 /*
260                                  *  Vendor-Specific is special, and matches any VSA if the
261                                  *  comparison is always true.
262                                  */
263                                 if ((check_item->da->attr == PW_VENDOR_SPECIFIC) && (input_item->da->vendor != 0) &&
264                                     (check_item->op == T_OP_CMP_TRUE)) {
265                                         pass++;
266                                         continue;
267                                 }
268
269                                 if (input_item->da == check_item->da) {
270                                         check_pair(request, check_item, input_item, &pass, &fail);
271                                 }
272                         }
273
274                         RDEBUG3("Attribute \"%s\" allowed by %i rules, disallowed by %i rules",
275                                 input_item->da->name, pass, fail);
276                         /*
277                          *  Only move attribute if it passed all rules, or if the config says we
278                          *  should copy unmatched attributes ('relaxed' mode).
279                          */
280                         if (fail == 0 && (pass > 0 || relax_filter)) {
281                                 if (!pass) {
282                                         RDEBUG3("Attribute \"%s\" allowed by relaxed mode", input_item->da->name);
283                                 }
284                                 vp = fr_pair_copy(packet, input_item);
285                                 if (!vp) {
286                                         goto error;
287                                 }
288                                 fr_cursor_insert(&out, vp);
289                         }
290                 }
291
292                 /* If we shouldn't fall through, break */
293                 if (!fall_through) {
294                         break;
295                 }
296         }
297
298         /*
299          *      No entry matched.  We didn't do anything.
300          */
301         if (!found) {
302                 rad_assert(!output);
303                 return RLM_MODULE_NOOP;
304         }
305
306         /*
307          *      Replace the existing request list with our filtered one
308          */
309         fr_pair_list_free(&packet->vps);
310         packet->vps = output;
311
312         if (request->packet->code == PW_CODE_ACCESS_REQUEST) {
313                 request->username = fr_pair_find_by_num(request->packet->vps, PW_STRIPPED_USER_NAME, 0, TAG_ANY);
314                 if (!request->username) {
315                         request->username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
316                 }
317                 request->password = fr_pair_find_by_num(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
318         }
319
320         return RLM_MODULE_UPDATED;
321
322         error:
323         fr_pair_list_free(&output);
324         return RLM_MODULE_FAIL;
325 }
326
327 #define RLM_AF_FUNC(_x, _y) static rlm_rcode_t CC_HINT(nonnull) mod_##_x(void *instance, REQUEST *request) \
328                         { \
329                                 return attr_filter_common(instance, request, request->_y); \
330                         }
331
332 RLM_AF_FUNC(authorize, packet)
333 RLM_AF_FUNC(post_auth, reply)
334
335 RLM_AF_FUNC(preacct, packet)
336 RLM_AF_FUNC(accounting, reply)
337
338 #ifdef WITH_PROXY
339 RLM_AF_FUNC(pre_proxy, proxy)
340 RLM_AF_FUNC(post_proxy, proxy_reply)
341 #endif
342
343 #ifdef WITH_COA
344 RLM_AF_FUNC(recv_coa, packet)
345 RLM_AF_FUNC(send_coa, reply)
346 #endif
347
348 /* globally exported name */
349 extern module_t rlm_attr_filter;
350 module_t rlm_attr_filter = {
351         .magic          = RLM_MODULE_INIT,
352         .name           = "attr_filter",
353         .type           = RLM_TYPE_HUP_SAFE,
354         .inst_size      = sizeof(rlm_attr_filter_t),
355         .config         = module_config,
356         .instantiate    = mod_instantiate,
357         .methods = {
358                 [MOD_AUTHORIZE]         = mod_authorize,
359                 [MOD_PREACCT]           = mod_preacct,
360                 [MOD_ACCOUNTING]        = mod_accounting,
361 #ifdef WITH_PROXY
362                 [MOD_PRE_PROXY]         = mod_pre_proxy,
363                 [MOD_POST_PROXY]        = mod_post_proxy,
364 #endif
365                 [MOD_POST_AUTH]         = mod_post_auth,
366 #ifdef WITH_COA
367                 [MOD_RECV_COA]          = mod_recv_coa,
368                 [MOD_SEND_COA]          = mod_send_coa
369 #endif
370         },
371 };
372