Use hash tables for "users" file entries, and add "order" to PAIR_LIST
[freeradius.git] / src / modules / rlm_files / rlm_files.c
1 /*
2  * rlm_files.c  authorization: Find a user in the "users" file.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2002  The FreeRADIUS server project
21  * Copyright 2000  Jeff Carneal <jeff@apex.net>
22  */
23
24 static const char rcsid[] = "$Id$";
25
26 #include        <freeradius-devel/autoconf.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 #include        <freeradius-devel/radiusd.h>
38 #include        <freeradius-devel/modules.h>
39
40 struct file_instance {
41         char *compat_mode;
42
43         /* autz */
44         char *usersfile;
45         lrad_hash_table_t *users;
46
47         /* preacct */
48         char *acctusersfile;
49         lrad_hash_table_t *acctusers;
50
51         /* pre-proxy */
52         char *preproxy_usersfile;
53         lrad_hash_table_t *preproxy_users;
54
55         /* authenticate */
56         char *auth_usersfile;
57         lrad_hash_table_t *auth_users;
58
59         /* post-proxy */
60         char *postproxy_usersfile;
61         lrad_hash_table_t *postproxy_users;
62
63         /* post-authenticate */
64         char *postauth_usersfile;
65         lrad_hash_table_t *postauth_users;
66 };
67
68
69 /*
70  *     See if a VALUE_PAIR list contains Fall-Through = Yes
71  */
72 static int fallthrough(VALUE_PAIR *vp)
73 {
74         VALUE_PAIR *tmp;
75         tmp = pairfind(vp, PW_FALL_THROUGH);
76
77         return tmp ? tmp->lvalue : 0;
78 }
79
80 static const CONF_PARSER module_config[] = {
81         { "usersfile",     PW_TYPE_FILENAME,
82           offsetof(struct file_instance,usersfile), NULL, NULL },
83         { "acctusersfile", PW_TYPE_FILENAME,
84           offsetof(struct file_instance,acctusersfile), NULL, NULL },
85         { "preproxy_usersfile", PW_TYPE_FILENAME,
86           offsetof(struct file_instance,preproxy_usersfile), NULL, NULL },
87         { "auth_usersfile", PW_TYPE_FILENAME,
88           offsetof(struct file_instance,auth_usersfile), NULL, NULL },
89         { "postproxy_usersfile", PW_TYPE_FILENAME,
90           offsetof(struct file_instance,postproxy_usersfile), NULL, NULL },
91         { "postauth_usersfile", PW_TYPE_FILENAME,
92           offsetof(struct file_instance,postauth_usersfile), NULL, NULL },
93         { "compat",        PW_TYPE_STRING_PTR,
94           offsetof(struct file_instance,compat_mode), NULL, "cistron" },
95         { NULL, -1, 0, NULL, NULL }
96 };
97
98
99 static void my_pairlist_free(void *data)
100 {
101         PAIR_LIST *pl = data;
102
103         pairlist_free(&pl);
104 }
105
106
107 static int getusersfile(const char *filename, lrad_hash_table_t **pht,
108                         char *compat_mode_str)
109 {
110         int rcode;
111         PAIR_LIST *users = NULL;
112         PAIR_LIST *entry, *next;
113         lrad_hash_table_t *ht, *tailht;
114         int order = 0;
115
116         if (!filename) {
117                 *pht = NULL;
118                 return 0;
119         }
120
121         rcode = pairlist_read(filename, &users, 1);
122         if (rcode < 0) {
123                 return -1;
124         }
125
126         /*
127          *      Walk through the 'users' file list, if we're debugging,
128          *      or if we're in compat_mode.
129          */
130         if ((debug_flag) ||
131             (strcmp(compat_mode_str, "cistron") == 0)) {
132                 VALUE_PAIR *vp;
133                 int compat_mode = FALSE;
134
135                 if (strcmp(compat_mode_str, "cistron") == 0) {
136                         compat_mode = TRUE;
137                 }
138
139                 entry = users;
140                 while (entry) {
141                         if (compat_mode) {
142                                 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
143                                                 filename, entry->lineno,
144                                                 entry->name);
145                         }
146
147                         /*
148                          *      Look for improper use of '=' in the
149                          *      check items.  They should be using
150                          *      '==' for on-the-wire RADIUS attributes,
151                          *      and probably ':=' for server
152                          *      configuration items.
153                          */
154                         for (vp = entry->check; vp != NULL; vp = vp->next) {
155                                 /*
156                                  *      Ignore attributes which are set
157                                  *      properly.
158                                  */
159                                 if (vp->operator != T_OP_EQ) {
160                                         continue;
161                                 }
162
163                                 /*
164                                  *      If it's a vendor attribute,
165                                  *      or it's a wire protocol,
166                                  *      ensure it has '=='.
167                                  */
168                                 if (((vp->attribute & ~0xffff) != 0) ||
169                                                 (vp->attribute < 0x100)) {
170                                         if (!compat_mode) {
171                                                 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
172                                                                 filename, entry->lineno,
173                                                                 vp->name, vp->name,
174                                                                 entry->name);
175                                         } else {
176                                                 DEBUG("\tChanging '%s =' to '%s =='",
177                                                                 vp->name, vp->name);
178                                         }
179                                         vp->operator = T_OP_CMP_EQ;
180                                         continue;
181                                 }
182
183                                 /*
184                                  *      Cistron Compatibility mode.
185                                  *
186                                  *      Re-write selected attributes
187                                  *      to be '+=', instead of '='.
188                                  *
189                                  *      All others get set to '=='
190                                  */
191                                 if (compat_mode) {
192                                         /*
193                                          *      Non-wire attributes become +=
194                                          *
195                                          *      On the write attributes
196                                          *      become ==
197                                          */
198                                         if ((vp->attribute >= 0x100) &&
199                                                         (vp->attribute <= 0xffff) &&
200                                                         (vp->attribute != PW_HINT) &&
201                                                         (vp->attribute != PW_HUNTGROUP_NAME)) {
202                                                 DEBUG("\tChanging '%s =' to '%s +='",
203                                                                 vp->name, vp->name);
204                                                 vp->operator = T_OP_ADD;
205                                         } else {
206                                                 DEBUG("\tChanging '%s =' to '%s =='",
207                                                                 vp->name, vp->name);
208                                                 vp->operator = T_OP_CMP_EQ;
209                                         }
210                                 }
211
212                         } /* end of loop over check items */
213
214
215                         /*
216                          *      Look for server configuration items
217                          *      in the reply list.
218                          *
219                          *      It's a common enough mistake, that it's
220                          *      worth doing.
221                          */
222                         for (vp = entry->reply; vp != NULL; vp = vp->next) {
223                                 /*
224                                  *      If it's NOT a vendor attribute,
225                                  *      and it's NOT a wire protocol
226                                  *      and we ignore Fall-Through,
227                                  *      then bitch about it, giving a
228                                  *      good warning message.
229                                  */
230                                 if (!(vp->attribute & ~0xffff) &&
231                                         (vp->attribute > 0xff) &&
232                                         (vp->attribute > 1000)) {
233                                         log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
234                                                         "\tfound in reply item list for user \"%s\".\n"
235                                                         "\tThis attribute MUST go on the first line"
236                                                         " with the other check items",
237                                                         filename, entry->lineno, vp->name,
238                                                         entry->name);
239                                 }
240                         }
241
242                         entry = entry->next;
243                 }
244
245         }
246
247         ht = lrad_hash_table_create(my_pairlist_free);
248         if (!ht) {
249                 pairlist_free(&users);
250                 return -1;
251         }
252
253         tailht = lrad_hash_table_create(NULL);
254         if (!tailht) {
255                 lrad_hash_table_free(ht);
256                 pairlist_free(&users);
257                 return -1;
258         }
259
260         /*
261          *      Now that we've read it in, put the entries into a hash
262          *      for faster access.
263          */
264         for (entry = users; entry != NULL; entry = next) {
265                 uint32_t hash;
266                 PAIR_LIST *tail;
267
268                 next = entry->next;
269                 entry->next = NULL;
270                 entry->order = order++;
271
272                 hash = lrad_hash_string(entry->name);
273
274                 /*
275                  *      Insert it into the hash table, and remember
276                  *      the tail of the linked list.
277                  */
278                 tail = lrad_hash_table_finddata(tailht, hash);
279                 if (!tail) {
280                         /*
281                          *      Insert it into the head & tail.
282                          */
283                         if (!lrad_hash_table_insert(ht, hash, entry) ||
284                             !lrad_hash_table_insert(tailht, hash, entry)) {
285                                 pairlist_free(&next);
286                                 lrad_hash_table_free(ht);
287                                 lrad_hash_table_free(tailht);
288                                 return -1;
289                         }
290                 } else {
291                         tail->next = entry;
292                         if (!lrad_hash_table_replace(tailht, hash, entry)) {
293                                 pairlist_free(&next);
294                                 lrad_hash_table_free(ht);
295                                 lrad_hash_table_free(tailht);
296                                 return -1;
297                         }
298                 }
299         }
300
301         lrad_hash_table_free(tailht);
302         *pht = ht;
303
304         return 0;
305 }
306
307 /*
308  *      Clean up.
309  */
310 static int file_detach(void *instance)
311 {
312         struct file_instance *inst = instance;
313         lrad_hash_table_free(inst->users);
314         lrad_hash_table_free(inst->acctusers);
315         lrad_hash_table_free(inst->preproxy_users);
316         lrad_hash_table_free(inst->auth_users);
317         lrad_hash_table_free(inst->postproxy_users);
318         lrad_hash_table_free(inst->postauth_users);
319         free(inst->usersfile);
320         free(inst->acctusersfile);
321         free(inst->preproxy_usersfile);
322         free(inst->auth_usersfile);
323         free(inst->postproxy_usersfile);
324         free(inst->postauth_usersfile);
325         free(inst->compat_mode);
326         free(inst);
327         return 0;
328 }
329
330
331
332 /*
333  *      (Re-)read the "users" file into memory.
334  */
335 static int file_instantiate(CONF_SECTION *conf, void **instance)
336 {
337         struct file_instance *inst;
338         int rcode;
339
340         inst = rad_malloc(sizeof *inst);
341         if (!inst) {
342                 return -1;
343         }
344         memset(inst, 0, sizeof(*inst));
345
346         if (cf_section_parse(conf, inst, module_config) < 0) {
347                 free(inst);
348                 return -1;
349         }
350
351         rcode = getusersfile(inst->usersfile, &inst->users, inst->compat_mode);
352         if (rcode != 0) {
353                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
354                 file_detach(inst);
355                 return -1;
356         }
357
358         rcode = getusersfile(inst->acctusersfile, &inst->acctusers, inst->compat_mode);
359         if (rcode != 0) {
360                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
361                 file_detach(inst);
362                 return -1;
363         }
364
365         /*
366          *  Get the pre-proxy stuff
367          */
368         rcode = getusersfile(inst->preproxy_usersfile, &inst->preproxy_users, inst->compat_mode);
369         if (rcode != 0) {
370                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->preproxy_usersfile);
371                 file_detach(inst);
372                 return -1;
373         }
374
375         rcode = getusersfile(inst->auth_usersfile, &inst->auth_users, inst->compat_mode);
376         if (rcode != 0) {
377                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->auth_usersfile);
378                 file_detach(inst);
379                 return -1;
380         }
381
382         rcode = getusersfile(inst->postproxy_usersfile, &inst->postproxy_users, inst->compat_mode);
383         if (rcode != 0) {
384                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->postproxy_usersfile);
385                 file_detach(inst);
386                 return -1;
387         }
388
389         rcode = getusersfile(inst->postauth_usersfile, &inst->postauth_users, inst->compat_mode);
390         if (rcode != 0) {
391                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->postauth_usersfile);
392                 file_detach(inst);
393                 return -1;
394         }
395
396         *instance = inst;
397         return 0;
398 }
399
400 /*
401  *      Common code called by everything below.
402  */
403 static int file_common(struct file_instance *inst, REQUEST *request,
404                        const char *filename, lrad_hash_table_t *ht,
405                        VALUE_PAIR *request_pairs, VALUE_PAIR **reply_pairs)
406 {
407         VALUE_PAIR      *namepair;
408         const char      *name;
409         VALUE_PAIR      **config_pairs;
410         VALUE_PAIR      *check_tmp;
411         VALUE_PAIR      *reply_tmp;
412         const PAIR_LIST *user_pl, *default_pl;
413         int             found = 0;
414
415         inst = inst;            /* -Wunused fix later? */
416
417         namepair = request->username;
418         name = namepair ? (char *) namepair->vp_strvalue : "NONE";
419         config_pairs = &request->config_items;
420
421         if (!ht) return RLM_MODULE_NOOP;
422
423         user_pl = lrad_hash_table_finddata(ht, lrad_hash_string(name));
424         default_pl = lrad_hash_table_finddata(ht, lrad_hash_string("DEFAULT"));
425
426         /*
427          *      Find the entry for the user.
428          */
429         while (user_pl || default_pl) {
430                 const PAIR_LIST *pl;
431
432                 if (!default_pl) {
433                         pl = user_pl;
434                         user_pl = user_pl->next;
435
436                 } else if (!user_pl) {
437                         pl = default_pl;
438                         default_pl = default_pl->next;
439
440                 } else {
441                         if (user_pl->order < default_pl->order) {
442                                 pl = user_pl;
443                                 user_pl = user_pl->next;
444                         } else {
445                                 pl = default_pl;
446                                 default_pl = default_pl->next;
447                         }
448                 }
449
450                 if (paircompare(request, request_pairs, pl->check, reply_pairs) == 0) {
451                         DEBUG2("    %s: Matched entry %s at line %d",
452                                filename, pl->name, pl->lineno);
453                         found = 1;
454                         check_tmp = paircopy(pl->check);
455                         reply_tmp = paircopy(pl->reply);
456                         pairxlatmove(request, reply_pairs, &reply_tmp);
457                         pairmove(config_pairs, &check_tmp);
458                         pairfree(&reply_tmp);
459                         pairfree(&check_tmp); /* should be NULL */
460
461                         /*
462                          *      Fallthrough?
463                          */
464                         if (!fallthrough(pl->reply))
465                                 break;
466                 }
467         }
468
469         /*
470          *      Remove server internal parameters.
471          */
472         pairdelete(reply_pairs, PW_FALL_THROUGH);
473
474         /*
475          *      See if we succeeded.
476          */
477         if (!found)
478                 return RLM_MODULE_NOOP; /* on to the next module */
479
480         return RLM_MODULE_OK;
481
482 }
483
484
485 /*
486  *      Find the named user in the database.  Create the
487  *      set of attribute-value pairs to check and reply with
488  *      for this user from the database. The main code only
489  *      needs to check the password, the rest is done here.
490  */
491 static int file_authorize(void *instance, REQUEST *request)
492 {
493         struct file_instance *inst = instance;
494
495         return file_common(inst, request, "users", inst->users,
496                            request->packet->vps, &request->reply->vps);
497 }
498
499
500 /*
501  *      Pre-Accounting - read the acct_users file for check_items and
502  *      config_items. Reply items are Not Recommended(TM) in acct_users,
503  *      except for Fallthrough, which should work
504  */
505 static int file_preacct(void *instance, REQUEST *request)
506 {
507         struct file_instance *inst = instance;
508
509         return file_common(inst, request, "acct_users", inst->acctusers,
510                            request->packet->vps, &request->reply->vps);
511 }
512
513 static int file_preproxy(void *instance, REQUEST *request)
514 {
515         struct file_instance *inst = instance;
516
517         return file_common(inst, request, "preproxy_users",
518                            inst->preproxy_users,
519                            request->packet->vps, &request->proxy->vps);
520 }
521
522 static int file_postproxy(void *instance, REQUEST *request)
523 {
524         struct file_instance *inst = instance;
525
526         return file_common(inst, request, "postproxy_users",
527                            inst->postproxy_users,
528                            request->proxy_reply->vps, &request->reply->vps);
529 }
530
531 static int file_authenticate(void *instance, REQUEST *request)
532 {
533         struct file_instance *inst = instance;
534
535         return file_common(inst, request, "auth_users",
536                            inst->auth_users,
537                            request->packet->vps, &request->reply->vps);
538 }
539
540 static int file_postauth(void *instance, REQUEST *request)
541 {
542         struct file_instance *inst = instance;
543
544         return file_common(inst, request, "postauth_users",
545                            inst->postauth_users,
546                            request->packet->vps, &request->reply->vps);
547 }
548
549
550 /* globally exported name */
551 module_t rlm_files = {
552         RLM_MODULE_INIT,
553         "files",
554         0,                              /* type: reserved */
555         file_instantiate,               /* instantiation */
556         file_detach,                    /* detach */
557         {
558                 file_authenticate,      /* authentication */
559                 file_authorize,         /* authorization */
560                 file_preacct,           /* preaccounting */
561                 NULL,                   /* accounting */
562                 NULL,                   /* checksimul */
563                 file_preproxy,          /* pre-proxy */
564                 file_postproxy,         /* post-proxy */
565                 file_postauth           /* post-auth */
566         },
567 };
568