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