22dcace08f174975380fb35a5eeacd62ecad6e35
[freeradius.git] / src / modules / rlm_files / rlm_files.c
1 /*
2  * rlm_files.c  authorization: Find a user in the "users" file.
3  *              accounting:    Write the "detail" files.
4  *
5  * Version:     $Id$
6  *
7  */
8
9 static const char rcsid[] = "$Id$";
10
11 #include        "autoconf.h"
12
13 #include        "radiusd.h"
14
15 #include        <sys/stat.h>
16
17 #include        <stdlib.h>
18 #include        <string.h>
19 #include        <netdb.h>
20 #include        <ctype.h>
21 #include        <fcntl.h>
22 #include        <limits.h>
23
24 #if HAVE_MALLOC_H
25 #  include      <malloc.h>
26 #endif
27
28 #include        "modules.h"
29
30 #ifdef WITH_DBM
31 #  include      <dbm.h>
32 #endif
33 #ifdef WITH_NDBM
34 #  include      <ndbm.h>
35 #endif
36
37 struct file_instance {
38         char *compat_mode;
39
40         /* autz */
41         char *usersfile;
42         PAIR_LIST *users;
43
44         /* preacct */
45         char *acctusersfile;
46         PAIR_LIST *acctusers;
47 };
48
49 #if defined(WITH_DBM) || defined(WITH_NDBM)
50 /*
51  *      See if a potential DBM file is present.
52  */
53 static int checkdbm(char *users, char *ext)
54 {
55         char buffer[256];
56         struct stat st;
57
58         strcpy(buffer, users);
59         strcat(buffer, ext);
60
61         return stat(buffer, &st);
62 }
63
64 /*
65  *      Find the named user in the DBM user database.
66  *      Returns: -1 not found
67  *                0 found but doesn't match.
68  *                1 found and matches.
69  */
70 static int dbm_find(DBM *dbmfile, char *name, VALUE_PAIR *request_pairs,
71                 VALUE_PAIR **check_pairs, VALUE_PAIR **reply_pairs)
72 {
73         datum           named;
74         datum           contentd;
75         char            *ptr;
76         VALUE_PAIR      *check_tmp;
77         VALUE_PAIR      *reply_tmp;
78         int             ret = 0;
79
80         named.dptr = name;
81         named.dsize = strlen(name);
82 #ifdef WITH_DBM
83         contentd = fetch(named);
84 #endif
85 #ifdef WITH_NDBM
86         contentd = dbm_fetch(dbmfile, named);
87 #endif
88         if(contentd.dptr == NULL)
89                 return -1;
90
91         check_tmp = NULL;
92         reply_tmp = NULL;
93
94         /*
95          *      Parse the check values
96          */
97         ptr = contentd.dptr;
98         contentd.dptr[contentd.dsize] = '\0';
99
100         if (*ptr != '\n' && userparse(ptr, &check_tmp) != 0) {
101                 radlog(L_ERR|L_CONS, "Parse error (check) for user %s", name);
102                 pairfree(check_tmp);
103                 return -1;
104         }
105         while(*ptr != '\n' && *ptr != '\0') {
106                 ptr++;
107         }
108         if(*ptr != '\n') {
109                 radlog(L_ERR|L_CONS, "Parse error (no reply pairs) for user %s",
110                         name);
111                 pairfree(check_tmp);
112                 return -1;
113         }
114         ptr++;
115
116         /*
117          *      Parse the reply values
118          */
119         if (userparse(ptr, &reply_tmp) != 0) {
120                 radlog(L_ERR|L_CONS, "Parse error (reply) for user %s", name);
121                 pairfree(check_tmp);
122                 pairfree(reply_tmp);
123                 return -1;
124         }
125
126         /*
127          *      See if the check_pairs match.
128          */
129         if (paircmp(request_pairs, check_tmp, reply_pairs) == 0) {
130                 ret = 1;
131                 pairmove(reply_pairs, &reply_tmp);
132                 pairmove2(reply_pairs, &reply_tmp, PW_FALL_THROUGH);
133                 pairmove(check_pairs, &check_tmp);
134         }
135         pairfree(reply_tmp);
136         pairfree(check_tmp);
137
138         return ret;
139 }
140 #endif /* DBM */
141
142 /*
143  *     See if a VALUE_PAIR list contains Fall-Through = Yes
144  */
145 static int fallthrough(VALUE_PAIR *vp)
146 {
147         VALUE_PAIR *tmp;
148
149         tmp = pairfind(vp, PW_FALL_THROUGH);
150
151         return tmp ? tmp->lvalue : 0;
152 }
153
154
155
156 static int file_init(void)
157 {
158         return 0;
159 }
160
161 /*
162  *      A temporary holding area for config values to be extracted
163  *      into, before they are copied into the instance data
164  */
165 static struct file_instance config;
166
167 static CONF_PARSER module_config[] = {
168         { "usersfile",     PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
169         { "acctusersfile", PW_TYPE_STRING_PTR, &config.acctusersfile, RADIUS_ACCT_USERS },
170         { "compat",        PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
171         { NULL, -1, NULL, NULL }
172 };
173
174 static int getusersfile(const char *filename, PAIR_LIST **pair_list)
175 {
176         int rcode;
177         PAIR_LIST *users = NULL;
178 #if defined(WITH_DBM) || defined(WITH_NDBM)
179         if (!use_dbm &&
180             (checkdbm(filename, ".dir") == 0 ||
181              checkdbm(filename, ".db") == 0)) {
182                 radlog(L_INFO|L_CONS, "DBM files found but no -b flag " "given - NOT using DBM");
183         }
184 #endif
185
186         if (!use_dbm) {
187                 rcode = pairlist_read(filename, &users, 1);
188                 if (rcode < 0) {
189                         return -1;
190                 }
191         }
192
193         /*
194          *      Walk through the 'users' file list, if we're debugging,
195          *      or if we're in compat_mode.
196          */
197         if ((debug_flag) ||
198             (strcmp(config.compat_mode, "cistron") == 0)) {
199                 PAIR_LIST *entry;
200                 VALUE_PAIR *vp;
201                 int compat_mode = FALSE;
202
203                 if (strcmp(config.compat_mode, "cistron") == 0) {
204                         compat_mode = TRUE;
205                 }
206         
207                 entry = users;
208                 while (entry) {
209                         if (compat_mode) {
210                                 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
211                                       filename, entry->lineno,
212                                       entry->name);
213                         }
214
215                         /*
216                          *      Look for improper use of '=' in the
217                          *      check items.  They should be using
218                          *      '==' for on-the-wire RADIUS attributes,
219                          *      and probably ':=' for server
220                          *      configuration items.
221                          */
222                         for (vp = entry->check; vp != NULL; vp = vp->next) {
223                                 /*
224                                  *      Ignore attributes which are set
225                                  *      properly.
226                                  */
227                                 if (vp->operator != T_OP_EQ) {
228                                         continue;
229                                 }
230
231                                 /*
232                                  *      If it's a vendor attribute,
233                                  *      or it's a wire protocol, 
234                                  *      ensure it has '=='.
235                                  */
236                                 if (((vp->attribute & ~0xffff) != 0) ||
237                                     (vp->attribute < 0x100)) {
238                                         if (!compat_mode) {
239                                                 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
240                                                       filename, entry->lineno,
241                                                       vp->name, vp->name,
242                                                       entry->name);
243                                         } else {
244                                                 DEBUG("\tChanging '%s =' to '%s =='",
245                                                       vp->name, vp->name);
246                                         }
247                                         vp->operator = T_OP_CMP_EQ;
248                                         continue;
249                                 }
250                                 
251                                 /*
252                                  *      Cistron Compatibility mode.
253                                  *
254                                  *      Re-write selected attributes
255                                  *      to be '+=', instead of '='.
256                                  *
257                                  *      All others get set to '=='
258                                  */
259                                 if (compat_mode) {
260                                         /*
261                                          *      Non-wire attributes become +=
262                                          *
263                                          *      On the write attributes
264                                          *      become ==
265                                          */
266                                         if ((vp->attribute >= 0x100) &&
267                                             (vp->attribute <= 0xffff) &&
268                                             (vp->attribute != PW_HINT) &&
269                                             (vp->attribute != PW_HUNTGROUP_NAME)) {
270                                                 DEBUG("\tChanging '%s =' to '%s +='",
271                                                       vp->name, vp->name);
272                                                 vp->operator = T_OP_ADD;
273                                         } else {
274                                                 DEBUG("\tChanging '%s =' to '%s =='",
275                                                       vp->name, vp->name);
276                                                 vp->operator = T_OP_CMP_EQ;
277                                         }
278                                 }
279                                 
280                         } /* end of loop over check items */
281                 
282                 
283                         /*
284                          *      Look for server configuration items
285                          *      in the reply list.
286                          *
287                          *      It's a common enough mistake, that it's
288                          *      worth doing.
289                          */
290                         for (vp = entry->reply; vp != NULL; vp = vp->next) {
291                                 /*
292                                  *      If it's NOT a vendor attribute,
293                                  *      and it's NOT a wire protocol
294                                  *      and we ignore Fall-Through,
295                                  *      then bitch about it, giving a
296                                  *      good warning message.
297                                  */
298                                 if (!(vp->attribute & ~0xffff) &&
299                                     (vp->attribute > 0xff) &&
300                                     (vp->attribute > 1000)) {
301                                         log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
302                                                   "\tfound in reply item list for user \"%s\".\n"
303                                                   "\tThis attribute MUST go on the first line"
304                                                   " with the other check items", 
305                                                   filename, entry->lineno, vp->name,
306                                                   entry->name);
307                                 }
308                         }
309                 
310                         entry = entry->next;
311                 }
312         
313         }
314
315         *pair_list = users;
316         return 0;
317 }
318
319 /*
320  *      (Re-)read the "users" file into memory.
321  */
322 static int file_instantiate(CONF_SECTION *conf, void **instance)
323 {
324         struct file_instance *inst;
325         int rcode;
326
327         inst = malloc(sizeof *inst);
328         if (!inst) {
329                 radlog(L_ERR|L_CONS, "Out of memory\n");
330                 return -1;
331         }
332
333         if (cf_section_parse(conf, module_config) < 0) {
334                 free(inst);
335                 return -1;
336         }
337
338         inst->usersfile = config.usersfile;
339         inst->acctusersfile = config.acctusersfile;
340         config.usersfile = NULL;
341         config.acctusersfile = NULL;
342
343         rcode = getusersfile(inst->usersfile, &inst->users);
344         if (rcode != 0) {
345                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
346                 free(inst->usersfile);
347                 free(inst->acctusersfile);
348                 free(inst);
349                 return -1;
350         }
351
352         rcode = getusersfile(inst->acctusersfile, &inst->acctusers);
353         if (rcode != 0) {
354                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
355                 pairlist_free(&inst->users);
356                 free(inst->usersfile);
357                 free(inst->acctusersfile);
358                 free(inst);
359                 return -1;
360         }
361
362         *instance = inst;
363         return 0;
364 }
365
366 /*
367  *      Find the named user in the database.  Create the
368  *      set of attribute-value pairs to check and reply with
369  *      for this user from the database. The main code only
370  *      needs to check the password, the rest is done here.
371  */
372 static int file_authorize(void *instance, REQUEST *request)
373 {
374         VALUE_PAIR      *namepair;
375         VALUE_PAIR      *request_pairs;
376         VALUE_PAIR      *check_tmp;
377         VALUE_PAIR      *reply_tmp;
378         PAIR_LIST       *pl;
379         int             found = 0;
380 #if defined(WITH_DBM) || defined(WITH_NDBM)
381         int             i, r;
382         char            buffer[256];
383 #endif
384         const char      *name;
385         struct file_instance *inst = instance;
386         VALUE_PAIR **check_pairs, **reply_pairs;
387         VALUE_PAIR *check_save;
388
389
390         request_pairs = request->packet->vps;
391         check_pairs = &request->config_items;
392         reply_pairs = &request->reply->vps;
393
394         /*
395          *      Grab the canonical user name.
396          */
397         namepair = request->username;
398         name = namepair ? (char *) namepair->strvalue : "NONE";
399
400         /*
401          *      Find the entry for the user.
402          */
403 #if defined(WITH_DBM) || defined(WITH_NDBM)
404         /*
405          *      FIXME: move to rlm_dbm.c
406          */
407         if (use_dbm) {
408                 /*
409                  *      FIXME: No Prefix / Suffix support for DBM.
410                  */
411 #ifdef WITH_DBM
412                 if (dbminit(inst->usersfile) != 0)
413 #endif
414 #ifdef WITH_NDBM
415                 if ((dbmfile = dbm_open(inst->usersfile, O_RDONLY, 0)) == NULL)
416 #endif
417                 {
418                         radlog(L_ERR|L_CONS, "cannot open dbm file %s",
419                                 buffer);
420                         return RLM_MODULE_FAIL;
421                 }
422
423                 r = dbm_find(dbmfile, name, request_pairs, check_pairs,
424                              reply_pairs);
425                 if (r > 0) found = 1;
426                 if (r <= 0 || fallthrough(*reply_pairs)) {
427
428                         pairdelete(reply_pairs, PW_FALL_THROUGH);
429
430                         sprintf(buffer, "DEFAULT");
431                         i = 0;
432                         while ((r = dbm_find(dbmfile, buffer, request_pairs,
433                                check_pairs, reply_pairs)) >= 0 || i < 2) {
434                                 if (r > 0) {
435                                         found = 1;
436                                         if (!fallthrough(*reply_pairs))
437                                                 break;
438                                         pairdelete(reply_pairs,PW_FALL_THROUGH);
439                                 }
440                                 sprintf(buffer, "DEFAULT%d", i++);
441                         }
442                 }
443 #ifdef WITH_DBM
444                 dbmclose();
445 #endif
446 #ifdef WITH_NDBM
447                 dbm_close(dbmfile);
448 #endif
449         } else
450         /*
451          *      Note the fallthrough through the #endif.
452          */
453 #endif
454
455         for(pl = inst->users; pl; pl = pl->next) {
456                 /*
457                  *      If the current entry is NOT a default,
458                  *      AND the name does NOT match the current entry,
459                  *      then skip to the next entry.
460                  */
461                 if ((strcmp(pl->name, "DEFAULT") != 0) &&
462                     (strcmp(name, pl->name) != 0))  {
463                         continue;
464                 }
465
466                 /*
467                  *      If the current request matches against the
468                  *      check pairs, then add the reply pairs from the
469                  *      entry to the current list of reply pairs.
470                  */
471                 if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
472
473                         if((mainconfig.do_usercollide) && (strcmp(pl->name, "DEFAULT"))) {
474
475                                 /* 
476                                  * We have to make sure the password
477                                  * matches as well
478                                  */
479         
480                                 /* Save the orginal config items */
481                                 check_save = paircopy(request->config_items);
482         
483                                 /* Copy this users check pairs to the request */
484                                 check_tmp = paircopy(pl->check);
485                                 pairmove(check_pairs, &check_tmp);
486                                 pairfree(check_tmp);
487         
488                                 DEBUG2("  users: Checking %s at %d", pl->name, pl->lineno);
489                                 /* Check the req to see if we matched */
490                                 if(rad_check_password(request)==0) {
491                                         DEBUG2("  users: Matched %s at %d", pl->name, pl->lineno);
492
493                                         found = 1;
494
495                                         /* Free our saved config items */
496                                         pairfree(check_save);
497
498                                         /* 
499                                          * Already copied check items, so 
500                                          * just copy reply here
501                                          */
502                                         reply_tmp = paircopy(pl->reply);
503                                         pairmove(reply_pairs, &reply_tmp);
504                                         pairfree(reply_tmp);
505
506                                 /* We didn't match here */
507                                 } else {
508                                         /* Restore check items */
509                                         pairfree(request->config_items);
510                                         request->config_items = paircopy(check_save);
511                                         check_pairs = &request->config_items;
512                                         continue;
513                                 }
514         
515                         /* No usercollide */
516                         } else {
517                 
518                                 DEBUG2("  users: Matched %s at %d", pl->name, pl->lineno);
519                                 found = 1;
520                                 check_tmp = paircopy(pl->check);
521                                 reply_tmp = paircopy(pl->reply);
522                                 pairmove(reply_pairs, &reply_tmp);
523                                 pairmove(check_pairs, &check_tmp);
524                                 pairfree(reply_tmp);
525                                 pairfree(check_tmp); /* should be NULL */
526                         }
527                         /*
528                          *      Fallthrough?
529                          */
530                         if (!fallthrough(pl->reply))
531                                 break;
532                 }
533         }
534         
535         /*
536          *      See if we succeeded.  If we didn't find the user,
537          *      then exit from the module.
538          */
539         if (!found)
540                 return RLM_MODULE_NOTFOUND;
541
542         /*
543          *      Remove server internal parameters.
544          */
545         pairdelete(reply_pairs, PW_FALL_THROUGH);
546
547         return RLM_MODULE_OK;
548 }
549
550 /*
551  *      Authentication - unused.
552  */
553 static int file_authenticate(void *instance, REQUEST *request)
554 {
555         instance = instance;
556         request = request;
557         return RLM_MODULE_OK;
558 }
559
560 /*
561  *      Pre-Accounting - read the acct_users file for check_items and
562  *      config_items. Reply items are Not Recommended(TM) in acct_users,
563  *      except for Fallthrough, which should work
564  *
565  *      This function is mostly a copy of file_authorize
566  */
567 static int file_preacct(void *instance, REQUEST *request)
568 {
569         VALUE_PAIR      *namepair;
570         const char      *name;
571         VALUE_PAIR      *request_pairs;
572         VALUE_PAIR      **config_pairs;
573         VALUE_PAIR      *reply_pairs = NULL;
574         VALUE_PAIR      *check_tmp;
575         VALUE_PAIR      *reply_tmp;
576         PAIR_LIST       *pl;
577         int             found = 0;
578 #if defined(WITH_DBM) || defined(WITH_NDBM)
579         int             i, r;
580         char            buffer[256];
581 #endif
582         struct file_instance *inst = instance;
583
584         namepair = request->username;
585         name = namepair ? (char *) namepair->strvalue : "NONE";
586         request_pairs = request->packet->vps;
587         config_pairs = &request->config_items;
588         
589         /*
590          *      Find the entry for the user.
591          */
592 #if defined(WITH_DBM) || defined(WITH_NDBM)
593         /*
594          *      FIXME: move to rlm_dbm.c
595          */
596         if (use_dbm) {
597                 /*
598                  *      FIXME: No Prefix / Suffix support for DBM.
599                  */
600 #ifdef WITH_DBM
601                 if (dbminit(inst->acctusersfile) != 0)
602 #endif
603 #ifdef WITH_NDBM
604                 if ((dbmfile = dbm_open(inst->acctusersfile, O_RDONLY, 0)) == NULL)
605 #endif
606                 {
607                         radlog(L_ERR|L_CONS, "cannot open dbm file %s",
608                                 buffer);
609                         return RLM_MODULE_FAIL;
610                 }
611
612                 r = dbm_find(dbmfile, name, request_pairs, config_pairs,
613                              &reply_pairs);
614                 if (r > 0) found = 1;
615                 if (r <= 0 || fallthrough(*reply_pairs)) {
616
617                   pairdelete(reply_pairs, PW_FALL_THROUGH);
618
619                         sprintf(buffer, "DEFAULT");
620                         i = 0;
621                         while ((r = dbm_find(dbmfile, buffer, request_pairs,
622                                config_pairs, &reply_pairs)) >= 0 || i < 2) {
623                                 if (r > 0) {
624                                         found = 1;
625                                         if (!fallthrough(*reply_pairs))
626                                                 break;
627                                         pairdelete(reply_pairs,PW_FALL_THROUGH);
628                                 }
629                                 sprintf(buffer, "DEFAULT%d", i++);
630                         }
631                 }
632 #ifdef WITH_DBM
633                 dbmclose();
634 #endif
635 #ifdef WITH_NDBM
636                 dbm_close(dbmfile);
637 #endif
638         } else
639         /*
640          *      Note the fallthrough through the #endif.
641          */
642 #endif
643
644         for(pl = inst->acctusers; pl; pl = pl->next) {
645
646                 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
647                         continue;
648
649                 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
650                         DEBUG2("  acct_users: Matched %s at %d",
651                                pl->name, pl->lineno);
652                         found = 1;
653                         check_tmp = paircopy(pl->check);
654                         reply_tmp = paircopy(pl->reply);
655                         pairmove(&reply_pairs, &reply_tmp);
656                         pairmove(config_pairs, &check_tmp);
657                         pairfree(reply_tmp);
658                         pairfree(check_tmp); /* should be NULL */
659                         /*
660                          *      Fallthrough?
661                          */
662                         if (!fallthrough(pl->reply))
663                                 break;
664                 }
665         }
666
667         /*
668          *      See if we succeeded.
669          */
670         if (!found)
671                 return RLM_MODULE_NOOP; /* on to the next module */
672
673         /*
674          *      FIXME: log a warning if there are any reply items other than
675          *      Fallthrough
676          */
677         pairfree(reply_pairs); /* Don't need these */
678
679         return RLM_MODULE_OK;
680 }
681
682 /*
683  *      Clean up.
684  */
685 static int file_detach(void *instance)
686 {
687         struct file_instance *inst = instance;
688         pairlist_free(&inst->users);
689         pairlist_free(&inst->acctusers);
690         free(inst->usersfile);
691         free(inst->acctusersfile);
692         free(inst);
693         return 0;
694 }
695
696
697 /* globally exported name */
698 module_t rlm_files = {
699         "files",
700         0,                              /* type: reserved */
701         file_init,                      /* initialization */
702         file_instantiate,               /* instantiation */
703         file_authorize,                 /* authorization */
704         file_authenticate,              /* authentication */
705         file_preacct,                   /* preaccounting */
706         NULL,                           /* accounting */
707         NULL,                           /* checksimul */
708         file_detach,                    /* detach */
709         NULL                            /* destroy */
710 };
711