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