Use macro for terminating CONF_PARSER arrays
[freeradius.git] / src / modules / rlm_files / rlm_files.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_files.c
20  * @brief Process simple 'users' policy files.
21  *
22  * @copyright 2000,2006  The FreeRADIUS server project
23  * @copyright 2000  Jeff Carneal <jeff@apex.net>
24  */
25 RCSID("$Id$")
26
27 #include        <freeradius-devel/radiusd.h>
28 #include        <freeradius-devel/modules.h>
29
30 #include        <ctype.h>
31 #include        <fcntl.h>
32
33 typedef struct rlm_files_t {
34         char const *compat_mode;
35
36         char const *key;
37
38         char const *filename;
39         rbtree_t *common;
40
41         /* autz */
42         char const *usersfile;
43         rbtree_t *users;
44
45
46         /* authenticate */
47         char const *auth_usersfile;
48         rbtree_t *auth_users;
49
50         /* preacct */
51         char const *acctusersfile;
52         rbtree_t *acctusers;
53
54 #ifdef WITH_PROXY
55         /* pre-proxy */
56         char const *preproxy_usersfile;
57         rbtree_t *preproxy_users;
58
59         /* post-proxy */
60         char const *postproxy_usersfile;
61         rbtree_t *postproxy_users;
62 #endif
63
64         /* post-authenticate */
65         char const *postauth_usersfile;
66         rbtree_t *postauth_users;
67 } rlm_files_t;
68
69
70 /*
71  *     See if a VALUE_PAIR list contains Fall-Through = Yes
72  */
73 static int fall_through(VALUE_PAIR *vp)
74 {
75         VALUE_PAIR *tmp;
76         tmp = fr_pair_find_by_num(vp, PW_FALL_THROUGH, 0, TAG_ANY);
77
78         return tmp ? tmp->vp_integer : 0;
79 }
80
81 static const CONF_PARSER module_config[] = {
82         { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, filename), NULL },
83         { "usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, usersfile), NULL },
84         { "acctusersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, acctusersfile), NULL },
85 #ifdef WITH_PROXY
86         { "preproxy_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, preproxy_usersfile), NULL },
87         { "postproxy_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, postproxy_usersfile), NULL },
88 #endif
89         { "auth_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, auth_usersfile), NULL },
90         { "postauth_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, postauth_usersfile), NULL },
91         { "compat", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_files_t, compat_mode), NULL },
92         { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_files_t, key), NULL },
93         CONF_PARSER_TERMINATOR
94 };
95
96
97 static int pairlist_cmp(void const *a, void const *b)
98 {
99         return strcmp(((PAIR_LIST const *)a)->name,
100                       ((PAIR_LIST const *)b)->name);
101 }
102
103 static int getusersfile(TALLOC_CTX *ctx, char const *filename, rbtree_t **ptree, char const *compat_mode_str)
104 {
105         int rcode;
106         PAIR_LIST *users = NULL;
107         PAIR_LIST *entry, *next;
108         PAIR_LIST *user_list, *default_list, **default_tail;
109         rbtree_t *tree;
110
111         if (!filename) {
112                 *ptree = NULL;
113                 return 0;
114         }
115
116         rcode = pairlist_read(ctx, filename, &users, 1);
117         if (rcode < 0) {
118                 return -1;
119         }
120
121         /*
122          *      Walk through the 'users' file list, if we're debugging,
123          *      or if we're in compat_mode.
124          */
125         if ((rad_debug_lvl) ||
126             (compat_mode_str && (strcmp(compat_mode_str, "cistron") == 0))) {
127                 VALUE_PAIR *vp;
128                 bool compat_mode = false;
129
130                 if (compat_mode_str && (strcmp(compat_mode_str, "cistron") == 0)) {
131                         compat_mode = true;
132                 }
133
134                 entry = users;
135                 while (entry) {
136                         vp_cursor_t cursor;
137                         if (compat_mode) {
138                                 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
139                                                 filename, entry->lineno,
140                                                 entry->name);
141                         }
142
143                         /*
144                          *      Look for improper use of '=' in the
145                          *      check items.  They should be using
146                          *      '==' for on-the-wire RADIUS attributes,
147                          *      and probably ':=' for server
148                          *      configuration items.
149                          */
150                         for (vp = fr_cursor_init(&cursor, &entry->check); vp; vp = fr_cursor_next(&cursor)) {
151                                 /*
152                                  *      Ignore attributes which are set
153                                  *      properly.
154                                  */
155                                 if (vp->op != T_OP_EQ) {
156                                         continue;
157                                 }
158
159                                 /*
160                                  *      If it's a vendor attribute,
161                                  *      or it's a wire protocol,
162                                  *      ensure it has '=='.
163                                  */
164                                 if ((vp->da->vendor != 0) ||
165                                                 (vp->da->attr < 0x100)) {
166                                         if (!compat_mode) {
167                                                 WARN("[%s]:%d Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
168                                                                 filename, entry->lineno,
169                                                                 vp->da->name, vp->da->name,
170                                                                 entry->name);
171                                         } else {
172                                                 DEBUG("\tChanging '%s =' to '%s =='",
173                                                                 vp->da->name, vp->da->name);
174                                         }
175                                         vp->op = T_OP_CMP_EQ;
176                                         continue;
177                                 }
178
179                                 /*
180                                  *      Cistron Compatibility mode.
181                                  *
182                                  *      Re-write selected attributes
183                                  *      to be '+=', instead of '='.
184                                  *
185                                  *      All others get set to '=='
186                                  */
187                                 if (compat_mode) {
188                                         /*
189                                          *      Non-wire attributes become +=
190                                          *
191                                          *      On the write attributes
192                                          *      become ==
193                                          */
194                                         if ((vp->da->attr >= 0x100) &&
195                                             (vp->da->attr <= 0xffff) &&
196                                             (vp->da->attr != PW_HINT) &&
197                                             (vp->da->attr != PW_HUNTGROUP_NAME)) {
198                                                 DEBUG("\tChanging '%s =' to '%s +='", vp->da->name, vp->da->name);
199
200                                                 vp->op = T_OP_ADD;
201                                         } else {
202                                                 DEBUG("\tChanging '%s =' to '%s =='", vp->da->name, vp->da->name);
203
204                                                 vp->op = T_OP_CMP_EQ;
205                                         }
206                                 }
207                         } /* end of loop over check items */
208
209                         /*
210                          *      Look for server configuration items
211                          *      in the reply list.
212                          *
213                          *      It's a common enough mistake, that it's
214                          *      worth doing.
215                          */
216                         for (vp = fr_cursor_init(&cursor, &entry->reply); vp; vp = fr_cursor_next(&cursor)) {
217                                 /*
218                                  *      If it's NOT a vendor attribute,
219                                  *      and it's NOT a wire protocol
220                                  *      and we ignore Fall-Through,
221                                  *      then bitch about it, giving a
222                                  *      good warning message.
223                                  */
224                                  if ((vp->da->vendor == 0) &&
225                                         (vp->da->attr > 1000)) {
226                                         WARN("[%s]:%d Check item \"%s\"\n"
227                                                "\tfound in reply item list for user \"%s\".\n"
228                                                "\tThis attribute MUST go on the first line"
229                                                " with the other check items", filename, entry->lineno, vp->da->name,
230                                                entry->name);
231                                 }
232                         }
233
234                         entry = entry->next;
235                 }
236         }
237
238         tree = rbtree_create(ctx, pairlist_cmp, NULL, RBTREE_FLAG_NONE);
239         if (!tree) {
240                 pairlist_free(&users);
241                 return -1;
242         }
243
244         default_list = NULL;
245         default_tail = &default_list;
246
247         /*
248          *      We've read the entries in linearly, but putting them
249          *      into an indexed data structure would be much faster.
250          *      Let's go fix that now.
251          */
252         for (entry = users; entry != NULL; entry = next) {
253                 /*
254                  *      Remove this entry from the input list.
255                  */
256                 next = entry->next;
257                 entry->next = NULL;
258                 (void) talloc_steal(tree, entry);
259
260                 /*
261                  *      DEFAULT entries get their own list.
262                  */
263                 if (strcmp(entry->name, "DEFAULT") == 0) {
264                         if (!default_list) {
265                                 default_list = entry;
266
267                                 /*
268                                  *      Insert the first DEFAULT into the tree.
269                                  */
270                                 if (!rbtree_insert(tree, entry)) {
271                                 error:
272                                         pairlist_free(&entry);
273                                         pairlist_free(&next);
274                                         rbtree_free(tree);
275                                         return -1;
276                                 }
277
278                         } else {
279                                 /*
280                                  *      Tack this entry onto the tail
281                                  *      of the DEFAULT list.
282                                  */
283                                 *default_tail = entry;
284                         }
285
286                         default_tail = &entry->next;
287                         continue;
288                 }
289
290                 /*
291                  *      Not DEFAULT, must be a normal user.
292                  */
293                 user_list = rbtree_finddata(tree, entry);
294                 if (!user_list) {
295                         /*
296                          *      Insert the first one.
297                          */
298                         if (!rbtree_insert(tree, entry)) goto error;
299                 } else {
300                         /*
301                          *      Find the tail of this list, and add it
302                          *      there.
303                          */
304                         while (user_list->next) user_list = user_list->next;
305
306                         user_list->next = entry;
307                 }
308         }
309
310         *ptree = tree;
311
312         return 0;
313 }
314
315
316
317 /*
318  *      (Re-)read the "users" file into memory.
319  */
320 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
321 {
322         rlm_files_t *inst = instance;
323
324 #undef READFILE
325 #define READFILE(_x, _y) do { if (getusersfile(inst, inst->_x, &inst->_y, inst->compat_mode) != 0) { ERROR("Failed reading %s", inst->_x); return -1;} } while (0)
326
327         READFILE(filename, common);
328         READFILE(usersfile, users);
329         READFILE(acctusersfile, acctusers);
330
331 #ifdef WITH_PROXY
332         READFILE(preproxy_usersfile, preproxy_users);
333         READFILE(postproxy_usersfile, postproxy_users);
334 #endif
335
336         READFILE(auth_usersfile, auth_users);
337         READFILE(postauth_usersfile, postauth_users);
338
339         return 0;
340 }
341
342 /*
343  *      Common code called by everything below.
344  */
345 static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const *filename, rbtree_t *tree,
346                                RADIUS_PACKET *request_packet, RADIUS_PACKET *reply_packet)
347 {
348         char const      *name, *match;
349         VALUE_PAIR      *check_tmp;
350         VALUE_PAIR      *reply_tmp;
351         PAIR_LIST const *user_pl, *default_pl;
352         bool            found = false;
353         PAIR_LIST       my_pl;
354         char            buffer[256];
355
356         if (!inst->key) {
357                 VALUE_PAIR      *namepair;
358
359                 namepair = request->username;
360                 name = namepair ? namepair->vp_strvalue : "NONE";
361         } else {
362                 int len;
363
364                 len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL);
365                 if (len < 0) {
366                         return RLM_MODULE_FAIL;
367                 }
368
369                 name = len ? buffer : "NONE";
370         }
371
372         if (!tree) return RLM_MODULE_NOOP;
373
374         my_pl.name = name;
375         user_pl = rbtree_finddata(tree, &my_pl);
376         my_pl.name = "DEFAULT";
377         default_pl = rbtree_finddata(tree, &my_pl);
378
379         /*
380          *      Find the entry for the user.
381          */
382         while (user_pl || default_pl) {
383                 vp_cursor_t cursor;
384                 VALUE_PAIR *vp;
385                 PAIR_LIST const *pl;
386
387                 /*
388                  *      Figure out which entry to match on.
389                  */
390
391                 if (!default_pl && user_pl) {
392                         pl = user_pl;
393                         match = name;
394                         user_pl = user_pl->next;
395
396                 } else if (!user_pl && default_pl) {
397                         pl = default_pl;
398                         match = "DEFAULT";
399                         default_pl = default_pl->next;
400
401                 } else if (user_pl->lineno < default_pl->lineno) {
402                         pl = user_pl;
403                         match = name;
404                         user_pl = user_pl->next;
405
406                 } else {
407                         pl = default_pl;
408                         match = "DEFAULT";
409                         default_pl = default_pl->next;
410                 }
411
412                 check_tmp = fr_pair_list_copy(request, pl->check);
413                 for (vp = fr_cursor_init(&cursor, &check_tmp);
414                      vp;
415                      vp = fr_cursor_next(&cursor)) {
416                         if (radius_xlat_do(request, vp) < 0) {
417                                 RWARN("Failed parsing expanded value for check item, skipping entry: %s", fr_strerror());
418                                 fr_pair_list_free(&check_tmp);
419                                 continue;
420                         }
421                 }
422
423                 if (paircompare(request, request_packet->vps, check_tmp, &reply_packet->vps) == 0) {
424                         RDEBUG2("%s: Matched entry %s at line %d", filename, match, pl->lineno);
425                         found = true;
426
427                         /* ctx may be reply or proxy */
428                         reply_tmp = fr_pair_list_copy(reply_packet, pl->reply);
429                         radius_pairmove(request, &reply_packet->vps, reply_tmp, true);
430                         fr_pair_list_move(request, &request->config, &check_tmp);
431                         fr_pair_list_free(&check_tmp);
432
433                         /*
434                          *      Fallthrough?
435                          */
436                         if (!fall_through(pl->reply))
437                                 break;
438                 }
439         }
440
441         /*
442          *      Remove server internal parameters.
443          */
444         fr_pair_delete_by_num(&reply_packet->vps, PW_FALL_THROUGH, 0, TAG_ANY);
445
446         /*
447          *      See if we succeeded.
448          */
449         if (!found)
450                 return RLM_MODULE_NOOP; /* on to the next module */
451
452         return RLM_MODULE_OK;
453
454 }
455
456
457 /*
458  *      Find the named user in the database.  Create the
459  *      set of attribute-value pairs to check and reply with
460  *      for this user from the database. The main code only
461  *      needs to check the password, the rest is done here.
462  */
463 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
464 {
465         rlm_files_t *inst = instance;
466
467         return file_common(inst, request, "users",
468                            inst->users ? inst->users : inst->common,
469                            request->packet, request->reply);
470 }
471
472
473 /*
474  *      Pre-Accounting - read the acct_users file for check_items and
475  *      config. Reply items are Not Recommended(TM) in acct_users,
476  *      except for Fallthrough, which should work
477  */
478 static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request)
479 {
480         rlm_files_t *inst = instance;
481
482         return file_common(inst, request, "acct_users",
483                            inst->acctusers ? inst->acctusers : inst->common,
484                            request->packet, request->reply);
485 }
486
487 #ifdef WITH_PROXY
488 static rlm_rcode_t CC_HINT(nonnull) mod_pre_proxy(void *instance, REQUEST *request)
489 {
490         rlm_files_t *inst = instance;
491
492         return file_common(inst, request, "preproxy_users",
493                            inst->preproxy_users ? inst->preproxy_users : inst->common,
494                            request->packet, request->proxy);
495 }
496
497 static rlm_rcode_t CC_HINT(nonnull) mod_post_proxy(void *instance, REQUEST *request)
498 {
499         rlm_files_t *inst = instance;
500
501         return file_common(inst, request, "postproxy_users",
502                            inst->postproxy_users ? inst->postproxy_users : inst->common,
503                            request->proxy_reply, request->reply);
504 }
505 #endif
506
507 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
508 {
509         rlm_files_t *inst = instance;
510
511         return file_common(inst, request, "auth_users",
512                            inst->auth_users ? inst->auth_users : inst->common,
513                            request->packet, request->reply);
514 }
515
516 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
517 {
518         rlm_files_t *inst = instance;
519
520         return file_common(inst, request, "postauth_users",
521                            inst->postauth_users ? inst->postauth_users : inst->common,
522                            request->packet, request->reply);
523 }
524
525
526 /* globally exported name */
527 extern module_t rlm_files;
528 module_t rlm_files = {
529         .magic          = RLM_MODULE_INIT,
530         .name           = "files",
531         .type           = RLM_TYPE_HUP_SAFE,
532         .inst_size      = sizeof(rlm_files_t),
533         .config         = module_config,
534         .instantiate    = mod_instantiate,
535         .methods = {
536                 [MOD_AUTHENTICATE]      = mod_authenticate,
537                 [MOD_AUTHORIZE]         = mod_authorize,
538                 [MOD_PREACCT]           = mod_preacct,
539
540 #ifdef WITH_PROXY
541                 [MOD_PRE_PROXY]         = mod_pre_proxy,
542                 [MOD_POST_PROXY]        = mod_post_proxy,
543 #endif
544                 [MOD_POST_AUTH]         = mod_post_auth
545         },
546 };
547