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