Move buffer to enclosing block, so we don't point to a buffer
[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->key);
334         free(inst->usersfile);
335         free(inst->acctusersfile);
336         free(inst->preproxy_usersfile);
337         free(inst->auth_usersfile);
338         free(inst->postproxy_usersfile);
339         free(inst->postauth_usersfile);
340         free(inst->compat_mode);
341         free(inst);
342         return 0;
343 }
344
345
346
347 /*
348  *      (Re-)read the "users" file into memory.
349  */
350 static int file_instantiate(CONF_SECTION *conf, void **instance)
351 {
352         struct file_instance *inst;
353         int rcode;
354
355         inst = rad_malloc(sizeof *inst);
356         if (!inst) {
357                 return -1;
358         }
359         memset(inst, 0, sizeof(*inst));
360
361         if (cf_section_parse(conf, inst, module_config) < 0) {
362                 free(inst);
363                 return -1;
364         }
365
366         rcode = getusersfile(inst->usersfile, &inst->users, inst->compat_mode);
367         if (rcode != 0) {
368           radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
369                 file_detach(inst);
370                 return -1;
371         }
372
373         rcode = getusersfile(inst->acctusersfile, &inst->acctusers, inst->compat_mode);
374         if (rcode != 0) {
375                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
376                 file_detach(inst);
377                 return -1;
378         }
379
380         /*
381          *  Get the pre-proxy stuff
382          */
383         rcode = getusersfile(inst->preproxy_usersfile, &inst->preproxy_users, inst->compat_mode);
384         if (rcode != 0) {
385                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->preproxy_usersfile);
386                 file_detach(inst);
387                 return -1;
388         }
389
390         rcode = getusersfile(inst->auth_usersfile, &inst->auth_users, inst->compat_mode);
391         if (rcode != 0) {
392                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->auth_usersfile);
393                 file_detach(inst);
394                 return -1;
395         }
396
397         rcode = getusersfile(inst->postproxy_usersfile, &inst->postproxy_users, inst->compat_mode);
398         if (rcode != 0) {
399                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->postproxy_usersfile);
400                 file_detach(inst);
401                 return -1;
402         }
403
404         rcode = getusersfile(inst->postauth_usersfile, &inst->postauth_users, inst->compat_mode);
405         if (rcode != 0) {
406                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->postauth_usersfile);
407                 file_detach(inst);
408                 return -1;
409         }
410
411         *instance = inst;
412         return 0;
413 }
414
415 /*
416  *      Common code called by everything below.
417  */
418 static int file_common(struct file_instance *inst, REQUEST *request,
419                        const char *filename, lrad_hash_table_t *ht,
420                        VALUE_PAIR *request_pairs, VALUE_PAIR **reply_pairs)
421 {
422         const char      *name, *match;
423         VALUE_PAIR      **config_pairs;
424         VALUE_PAIR      *check_tmp;
425         VALUE_PAIR      *reply_tmp;
426         const PAIR_LIST *user_pl, *default_pl;
427         int             found = 0;
428         PAIR_LIST       my_pl;
429         char            buffer[256];
430
431         if (!inst->key) {
432                 VALUE_PAIR      *namepair;
433
434                 namepair = request->username;
435                 name = namepair ? (char *) namepair->vp_strvalue : "NONE";
436         } else {
437                 int len;
438
439                 len = radius_xlat(buffer, sizeof(buffer), inst->key,
440                                   request, NULL);
441                 if (len) name = buffer;
442                 else name = "NONE";
443         }
444
445         config_pairs = &request->config_items;
446
447         if (!ht) return RLM_MODULE_NOOP;
448
449         my_pl.name = name;
450         user_pl = lrad_hash_table_finddata(ht, &my_pl);
451         my_pl.name = "DEFAULT";
452         default_pl = lrad_hash_table_finddata(ht, &my_pl);
453
454         /*
455          *      Find the entry for the user.
456          */
457         while (user_pl || default_pl) {
458                 const PAIR_LIST *pl;
459
460                 if (!default_pl && user_pl) {
461                         pl = user_pl;
462                         match = name;
463                         user_pl = user_pl->next;
464
465                 } else if (!user_pl && default_pl) {
466                         pl = default_pl;
467                         match = "DEFAULT";
468                         default_pl = default_pl->next;
469
470                 } else if (user_pl->order < default_pl->order) {
471                         pl = user_pl;
472                         match = name;
473                         user_pl = user_pl->next;
474
475                 } else {
476                         pl = default_pl;
477                         match = "DEFAULT";
478                         default_pl = default_pl->next;
479                 }
480
481                 if (paircompare(request, request_pairs, pl->check, reply_pairs) == 0) {
482                         DEBUG2("    %s: Matched entry %s at line %d",
483                                filename, match, pl->lineno);
484                         found = 1;
485                         check_tmp = paircopy(pl->check);
486                         reply_tmp = paircopy(pl->reply);
487                         pairxlatmove(request, reply_pairs, &reply_tmp);
488                         pairmove(config_pairs, &check_tmp);
489                         pairfree(&reply_tmp);
490                         pairfree(&check_tmp);
491
492                         /*
493                          *      Fallthrough?
494                          */
495                         if (!fallthrough(pl->reply))
496                                 break;
497                 }
498         }
499
500         /*
501          *      Remove server internal parameters.
502          */
503         pairdelete(reply_pairs, PW_FALL_THROUGH);
504
505         /*
506          *      See if we succeeded.
507          */
508         if (!found)
509                 return RLM_MODULE_NOOP; /* on to the next module */
510
511         return RLM_MODULE_OK;
512
513 }
514
515
516 /*
517  *      Find the named user in the database.  Create the
518  *      set of attribute-value pairs to check and reply with
519  *      for this user from the database. The main code only
520  *      needs to check the password, the rest is done here.
521  */
522 static int file_authorize(void *instance, REQUEST *request)
523 {
524         struct file_instance *inst = instance;
525
526         return file_common(inst, request, "users", inst->users,
527                            request->packet->vps, &request->reply->vps);
528 }
529
530
531 /*
532  *      Pre-Accounting - read the acct_users file for check_items and
533  *      config_items. Reply items are Not Recommended(TM) in acct_users,
534  *      except for Fallthrough, which should work
535  */
536 static int file_preacct(void *instance, REQUEST *request)
537 {
538         struct file_instance *inst = instance;
539
540         return file_common(inst, request, "acct_users", inst->acctusers,
541                            request->packet->vps, &request->reply->vps);
542 }
543
544 static int file_preproxy(void *instance, REQUEST *request)
545 {
546         struct file_instance *inst = instance;
547
548         return file_common(inst, request, "preproxy_users",
549                            inst->preproxy_users,
550                            request->packet->vps, &request->proxy->vps);
551 }
552
553 static int file_postproxy(void *instance, REQUEST *request)
554 {
555         struct file_instance *inst = instance;
556
557         return file_common(inst, request, "postproxy_users",
558                            inst->postproxy_users,
559                            request->proxy_reply->vps, &request->reply->vps);
560 }
561
562 static int file_authenticate(void *instance, REQUEST *request)
563 {
564         struct file_instance *inst = instance;
565
566         return file_common(inst, request, "auth_users",
567                            inst->auth_users,
568                            request->packet->vps, &request->reply->vps);
569 }
570
571 static int file_postauth(void *instance, REQUEST *request)
572 {
573         struct file_instance *inst = instance;
574
575         return file_common(inst, request, "postauth_users",
576                            inst->postauth_users,
577                            request->packet->vps, &request->reply->vps);
578 }
579
580
581 /* globally exported name */
582 module_t rlm_files = {
583         RLM_MODULE_INIT,
584         "files",
585         0,                              /* type: reserved */
586         file_instantiate,               /* instantiation */
587         file_detach,                    /* detach */
588         {
589                 file_authenticate,      /* authentication */
590                 file_authorize,         /* authorization */
591                 file_preacct,           /* preaccounting */
592                 NULL,                   /* accounting */
593                 NULL,                   /* checksimul */
594                 file_preproxy,          /* pre-proxy */
595                 file_postproxy,         /* post-proxy */
596                 file_postauth           /* post-auth */
597         },
598 };
599