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