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