removed the check_items && reply_items from the authorize and
[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        <sys/types.h>
14 #include        <sys/socket.h>
15 #include        <sys/time.h>
16 #include        <sys/stat.h>
17 #include        <netinet/in.h>
18
19 #include        <stdio.h>
20 #include        <stdlib.h>
21 #include        <string.h>
22 #include        <errno.h>
23 #include        <netdb.h>
24 #include        <pwd.h>
25 #include        <grp.h>
26 #include        <time.h>
27 #include        <ctype.h>
28 #include        <fcntl.h>
29 #include        <unistd.h>
30 #include        <limits.h>
31
32 #if HAVE_MALLOC_H
33 #  include      <malloc.h>
34 #endif
35
36 #include        "radiusd.h"
37 #include        "modules.h"
38
39 #ifdef WITH_DBM
40 #  include      <dbm.h>
41 #endif
42 #ifdef WITH_NDBM
43 #  include      <ndbm.h>
44 #endif
45
46 struct file_instance {
47         char *compat_mode;
48
49         /* autz */
50         char *usersfile;
51         PAIR_LIST *users;
52
53         /* preacct */
54         char *acctusersfile;
55         PAIR_LIST *acctusers;
56
57         /*acct*/
58         int detailperm;
59 };
60
61 #if defined(WITH_DBM) || defined(WITH_NDBM)
62 /*
63  *      See if a potential DBM file is present.
64  */
65 static int checkdbm(char *users, char *ext)
66 {
67         char buffer[256];
68         struct stat st;
69
70         strcpy(buffer, users);
71         strcat(buffer, ext);
72
73         return stat(buffer, &st);
74 }
75
76 /*
77  *      Find the named user in the DBM user database.
78  *      Returns: -1 not found
79  *                0 found but doesn't match.
80  *                1 found and matches.
81  */
82 static int dbm_find(DBM *dbmfile, char *name, VALUE_PAIR *request_pairs,
83                 VALUE_PAIR **check_pairs, VALUE_PAIR **reply_pairs)
84 {
85         datum           named;
86         datum           contentd;
87         char            *ptr;
88         VALUE_PAIR      *check_tmp;
89         VALUE_PAIR      *reply_tmp;
90         int             ret = 0;
91
92         named.dptr = name;
93         named.dsize = strlen(name);
94 #ifdef WITH_DBM
95         contentd = fetch(named);
96 #endif
97 #ifdef WITH_NDBM
98         contentd = dbm_fetch(dbmfile, named);
99 #endif
100         if(contentd.dptr == NULL)
101                 return -1;
102
103         check_tmp = NULL;
104         reply_tmp = NULL;
105
106         /*
107          *      Parse the check values
108          */
109         ptr = contentd.dptr;
110         contentd.dptr[contentd.dsize] = '\0';
111
112         if (*ptr != '\n' && userparse(ptr, &check_tmp) != 0) {
113                 radlog(L_ERR|L_CONS, "Parse error (check) for user %s", name);
114                 pairfree(check_tmp);
115                 return -1;
116         }
117         while(*ptr != '\n' && *ptr != '\0') {
118                 ptr++;
119         }
120         if(*ptr != '\n') {
121                 radlog(L_ERR|L_CONS, "Parse error (no reply pairs) for user %s",
122                         name);
123                 pairfree(check_tmp);
124                 return -1;
125         }
126         ptr++;
127
128         /*
129          *      Parse the reply values
130          */
131         if (userparse(ptr, &reply_tmp) != 0) {
132                 radlog(L_ERR|L_CONS, "Parse error (reply) for user %s", name);
133                 pairfree(check_tmp);
134                 pairfree(reply_tmp);
135                 return -1;
136         }
137
138         /*
139          *      See if the check_pairs match.
140          */
141         if (paircmp(request_pairs, check_tmp, reply_pairs) == 0) {
142                 ret = 1;
143                 pairmove(reply_pairs, &reply_tmp);
144                 pairmove2(reply_pairs, &reply_tmp, PW_FALL_THROUGH);
145                 pairmove(check_pairs, &check_tmp);
146         }
147         pairfree(reply_tmp);
148         pairfree(check_tmp);
149
150         return ret;
151 }
152 #endif /* DBM */
153
154 /*
155  *     See if a VALUE_PAIR list contains Fall-Through = Yes
156  */
157 static int fallthrough(VALUE_PAIR *vp)
158 {
159         VALUE_PAIR *tmp;
160
161         tmp = pairfind(vp, PW_FALL_THROUGH);
162
163         return tmp ? tmp->lvalue : 0;
164 }
165
166 /*
167  *  USE_DYNAMIC LOGS is set to FALSE, as this code should be rewritten.
168  */
169 #define USER_DYNAMIC_LOGS 0
170
171 #if USE_DYNAMIC_LOGS
172 #define DL_FLAG_START     1
173 #define DL_FLAG_STOP      2
174 #define DL_FLAG_ACCT_ON   4
175 #define DL_FLAG_ACCT_OFF  8
176 #define DL_FLAG_ALIVE    16
177
178 typedef struct dyn_log {
179         char dir[256];
180         char fname[256];
181         char fmt[1024];
182         char mode[5];
183         int flags;
184 } DYN_LOG;
185 #define MAX_LOGS 20
186 static DYN_LOG logcfg[MAX_LOGS];
187 static int logcnt;
188
189 /*
190  * Initialize dynamic logging
191  */
192 static void file_getline(FILE *f,char * buff,int len)
193 {
194         char tmp[2048];
195         int i;
196
197         tmp[0] = '\0';
198         while (!feof(f)) {
199                 fgets(tmp,len,f);
200                 if (tmp[0] != '#') {
201                         break;
202                 }
203         }
204         i = 0;
205         while (tmp[i] != '\n') {
206                 *buff = tmp[i];
207                 buff++;
208                 i++;
209         }
210 }
211
212 static void file_dynamic_log_init(void)
213 {
214         FILE * f;
215         char fn[1024];
216
217         sprintf(fn,"%s/%s",radius_dir,"rlm_files_log.cfg");
218         logcnt = 0;
219         f = fopen(fn, "r");
220         if (f != NULL) {
221                 log_debug("Loading %s",fn);
222                 while (logcnt < MAX_LOGS) {
223                         file_getline(f,logcfg[logcnt].dir,sizeof(logcfg[logcnt].dir));
224                         file_getline(f,logcfg[logcnt].fname,sizeof(logcfg[logcnt].fname));
225                         file_getline(f,logcfg[logcnt].fmt,sizeof(logcfg[logcnt].fmt));
226                         file_getline(f,logcfg[logcnt].mode,sizeof(logcfg[logcnt].mode));
227                         file_getline(f,fn,sizeof(fn));
228                         logcfg[logcnt].flags = atoi(fn);
229                         if ((logcfg[logcnt].flags != 0) &&
230                             (strlen(logcfg[logcnt].mode) != 0)) {
231                                 logcnt++;
232                         } else {
233                                 break;
234                         }
235                 }
236                 log_debug("%d logs configured",logcnt);
237                 fclose(f);
238         } else {
239                 log_debug("Error loading %s: %s",fn, strerror(errno));
240         }
241
242
243 }
244 #endif
245
246
247 static int file_init(void)
248 {
249 #if USE_DYNAMIC_LOGS
250         file_dynamic_log_init();
251 #endif
252
253         return 0;
254 }
255
256 /*
257  *      A temporary holding area for config values to be extracted
258  *      into, before they are copied into the instance data
259  */
260 static struct file_instance config;
261
262 static CONF_PARSER module_config[] = {
263         { "usersfile",     PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
264         { "acctusersfile", PW_TYPE_STRING_PTR, &config.acctusersfile, RADIUS_ACCT_USERS },
265         { "detailperm",    PW_TYPE_INTEGER,    &config.detailperm, "0600" },
266         { "compat",        PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
267         { NULL, -1, NULL, NULL }
268 };
269
270 static int getusersfile(const char *filename, PAIR_LIST **pair_list)
271 {
272         int rcode;
273         PAIR_LIST *users = NULL;
274 #if defined(WITH_DBM) || defined(WITH_NDBM)
275         if (!use_dbm &&
276             (checkdbm(filename, ".dir") == 0 ||
277              checkdbm(filename, ".db") == 0)) {
278                 radlog(L_INFO|L_CONS, "DBM files found but no -b flag " "given - NOT using DBM");
279         }
280 #endif
281
282         if (!use_dbm) {
283                 rcode = pairlist_read(filename, &users, 1);
284                 if (rcode < 0) {
285                         return -1;
286                 }
287         }
288
289         /*
290          *      Walk through the 'users' file list, if we're debugging,
291          *      or if we're in compat_mode.
292          */
293         if ((debug_flag) ||
294             (strcmp(config.compat_mode, "cistron") == 0)) {
295                 PAIR_LIST *entry;
296                 VALUE_PAIR *vp;
297                 int compat_mode = FALSE;
298
299                 if (strcmp(config.compat_mode, "cistron") == 0) {
300                         compat_mode = TRUE;
301                 }
302         
303                 entry = users;
304                 while (entry) {
305                         if (compat_mode) {
306                                 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
307                                       filename, entry->lineno,
308                                       entry->name);
309                         }
310
311                         /*
312                          *      Look for improper use of '=' in the
313                          *      check items.  They should be using
314                          *      '==' for on-the-wire RADIUS attributes,
315                          *      and probably ':=' for server
316                          *      configuration items.
317                          */
318                         for (vp = entry->check; vp != NULL; vp = vp->next) {
319                                 /*
320                                  *      Ignore attributes which are set
321                                  *      properly.
322                                  */
323                                 if (vp->operator != T_OP_EQ) {
324                                         continue;
325                                 }
326
327                                 /*
328                                  *      If it's a vendor attribute,
329                                  *      or it's a wire protocol, 
330                                  *      ensure it has '=='.
331                                  */
332                                 if (((vp->attribute & ~0xffff) != 0) ||
333                                     (vp->attribute < 0x100)) {
334                                         if (!compat_mode) {
335                                                 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
336                                                       filename, entry->lineno,
337                                                       vp->name, vp->name,
338                                                       entry->name);
339                                         } else {
340                                                 DEBUG("\tChanging '%s =' to '%s =='",
341                                                       vp->name, vp->name);
342                                         }
343                                         vp->operator = T_OP_CMP_EQ;
344                                         continue;
345                                 }
346                                 
347                                 /*
348                                  *      Cistron Compatibility mode.
349                                  *
350                                  *      Re-write selected attributes
351                                  *      to be '+=', instead of '='.
352                                  *
353                                  *      All others get set to '=='
354                                  */
355                                 if (compat_mode) {
356                                         /*
357                                          *      Non-wire attributes become +=
358                                          *
359                                          *      On the write attributes
360                                          *      become ==
361                                          */
362                                         if ((vp->attribute >= 0x100) &&
363                                             (vp->attribute <= 0xffff) &&
364                                             (vp->attribute != PW_HINT) &&
365                                             (vp->attribute != PW_HUNTGROUP_NAME)) {
366                                                 DEBUG("\tChanging '%s =' to '%s +='",
367                                                       vp->name, vp->name);
368                                                 vp->operator = T_OP_ADD;
369                                         } else {
370                                                 DEBUG("\tChanging '%s =' to '%s =='",
371                                                       vp->name, vp->name);
372                                                 vp->operator = T_OP_CMP_EQ;
373                                         }
374                                 }
375                                 
376                         } /* end of loop over check items */
377                 
378                 
379                         /*
380                          *      Look for server configuration items
381                          *      in the reply list.
382                          *
383                          *      It's a common enough mistake, that it's
384                          *      worth doing.
385                          */
386                         for (vp = entry->reply; vp != NULL; vp = vp->next) {
387                                 /*
388                                  *      If it's NOT a vendor attribute,
389                                  *      and it's NOT a wire protocol
390                                  *      and we ignore Fall-Through,
391                                  *      then bitch about it, giving a
392                                  *      good warning message.
393                                  */
394                                 if (!(vp->attribute & ~0xffff) &&
395                                     (vp->attribute > 0xff) &&
396                                     (vp->attribute > 1000)) {
397                                         log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
398                                                   "\tfound in reply item list for user \"%s\".\n"
399                                                   "\tThis attribute MUST go on the first line"
400                                                   " with the other check items", 
401                                                   filename, entry->lineno, vp->name,
402                                                   entry->name);
403                                 }
404                         }
405                 
406                         entry = entry->next;
407                 }
408         
409         }
410
411         *pair_list = users;
412         return 0;
413 }
414
415 /*
416  *      (Re-)read the "users" file into memory.
417  */
418 static int file_instantiate(CONF_SECTION *conf, void **instance)
419 {
420         struct file_instance *inst;
421         int rcode;
422
423         inst = malloc(sizeof *inst);
424         if (!inst) {
425                 radlog(L_ERR|L_CONS, "Out of memory\n");
426                 return -1;
427         }
428
429         if (cf_section_parse(conf, module_config) < 0) {
430                 free(inst);
431                 return -1;
432         }
433
434         inst->detailperm = config.detailperm;
435         inst->usersfile = config.usersfile;
436         inst->acctusersfile = config.acctusersfile;
437         config.usersfile = NULL;
438         config.acctusersfile = NULL;
439
440         rcode = getusersfile(inst->usersfile, &inst->users);
441         if (rcode != 0) {
442                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
443                 free(inst->usersfile);
444                 free(inst->acctusersfile);
445                 free(inst);
446                 return -1;
447         }
448
449         rcode = getusersfile(inst->acctusersfile, &inst->acctusers);
450         if (rcode != 0) {
451                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
452                 pairlist_free(&inst->users);
453                 free(inst->usersfile);
454                 free(inst->acctusersfile);
455                 free(inst);
456                 return -1;
457         }
458
459         *instance = inst;
460         return 0;
461 }
462
463 /*
464  *      Find the named user in the database.  Create the
465  *      set of attribute-value pairs to check and reply with
466  *      for this user from the database. The main code only
467  *      needs to check the password, the rest is done here.
468  */
469 static int file_authorize(void *instance, REQUEST *request)
470 {
471         int             nas_port = 0;
472         VALUE_PAIR      *namepair;
473         VALUE_PAIR      *request_pairs;
474         VALUE_PAIR      *check_tmp;
475         VALUE_PAIR      *reply_tmp;
476         VALUE_PAIR      *tmp;
477         PAIR_LIST       *pl;
478         int             found = 0;
479 #if defined(WITH_DBM) || defined(WITH_NDBM)
480         int             i, r;
481         char            buffer[256];
482 #endif
483         const char      *name;
484         struct file_instance *inst = instance;
485 #ifdef WITH_USERCOLLIDE
486         VALUE_PAIR  *auth_type_pair;
487         VALUE_PAIR  *password_pair;
488         VALUE_PAIR  *auth_item;
489         int   auth_type = -1;
490         int   result = 1;
491 #endif
492         VALUE_PAIR **check_pairs, **reply_pairs;
493
494
495         request_pairs = request->packet->vps;
496         check_pairs = &request->config_items;
497         reply_pairs = &request->reply->vps;
498
499         /*
500          *      Grab the canonical user name.
501          */
502         namepair = request->username;
503         name = namepair ? (char *) namepair->strvalue : "NONE";
504
505         /*
506          *      Find the NAS port ID.
507          */
508         if ((tmp = pairfind(request_pairs, PW_NAS_PORT_ID)) != NULL)
509                 nas_port = tmp->lvalue;
510
511         /*
512          *      Find the entry for the user.
513          */
514 #if defined(WITH_DBM) || defined(WITH_NDBM)
515         /*
516          *      FIXME: move to rlm_dbm.c
517          */
518         if (use_dbm) {
519                 /*
520                  *      FIXME: No Prefix / Suffix support for DBM.
521                  */
522 #ifdef WITH_DBM
523                 if (dbminit(inst->usersfile) != 0)
524 #endif
525 #ifdef WITH_NDBM
526                 if ((dbmfile = dbm_open(inst->usersfile, O_RDONLY, 0)) == NULL)
527 #endif
528                 {
529                         radlog(L_ERR|L_CONS, "cannot open dbm file %s",
530                                 buffer);
531                         return RLM_MODULE_FAIL;
532                 }
533
534                 r = dbm_find(dbmfile, name, request_pairs, check_pairs,
535                              reply_pairs);
536                 if (r > 0) found = 1;
537                 if (r <= 0 || fallthrough(*reply_pairs)) {
538
539                         pairdelete(reply_pairs, PW_FALL_THROUGH);
540
541                         sprintf(buffer, "DEFAULT");
542                         i = 0;
543                         while ((r = dbm_find(dbmfile, buffer, request_pairs,
544                                check_pairs, reply_pairs)) >= 0 || i < 2) {
545                                 if (r > 0) {
546                                         found = 1;
547                                         if (!fallthrough(*reply_pairs))
548                                                 break;
549                                         pairdelete(reply_pairs,PW_FALL_THROUGH);
550                                 }
551                                 sprintf(buffer, "DEFAULT%d", i++);
552                         }
553                 }
554 #ifdef WITH_DBM
555                 dbmclose();
556 #endif
557 #ifdef WITH_NDBM
558                 dbm_close(dbmfile);
559 #endif
560         } else
561         /*
562          *      Note the fallthrough through the #endif.
563          */
564 #endif
565
566         for(pl = inst->users; pl; pl = pl->next) {
567 #ifdef WITH_USERCOLLIDE
568                 result = 1;
569 #endif
570                 /*
571                  *      If the current entry is NOT a default,
572                  *      AND the name does NOT match the current entry,
573                  *      then skip to the next entry.
574                  */
575                 if ((strcmp(pl->name, "DEFAULT") != 0) &&
576                     (strcmp(name, pl->name) != 0))  {
577                         continue;
578                 }
579
580                 /*
581                  *      If the current request matches against the
582                  *      check pairs, then add the reply pairs from the
583                  *      entry to the current list of reply pairs.
584                  */
585 #ifdef WITH_USERCOLLIDE
586                 if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
587                         /* 
588                          *      We don't compare pass on default users
589                          *      or they never match.  Oops.
590                          */
591                         if(strcmp(pl->name, "DEFAULT")) {
592                                 /* 
593                                  *      We check the pass as a config
594                                  *      item with user collisions Most
595                                  *      of this is stolen out of
596                                  *      rad_check_password()
597                                  */
598                                 if ((auth_type_pair = pairfind(pl->check, PW_AUTHTYPE)) != NULL) {
599                                         auth_type = auth_type_pair->lvalue;
600                                         DEBUG2("  file_auth (Usercollide):  auth_type %d", auth_type);
601                                 }
602         
603                                 /* Find pass in the REQ */
604                                 auth_item = request->password;
605                                 if (auth_item == NULL) {
606                                         DEBUG2("  file_auth (Usercollide): No password in the request");
607                                         return RLM_MODULE_OK;
608                                 }
609                 
610                                 /* Find the password from the users file. */
611                                 if ((password_pair = pairfind(pl->check, PW_CRYPT_PASSWORD)) != NULL)
612                                         auth_type = PW_AUTHTYPE_CRYPT;
613                                 else
614                                         password_pair = pairfind(pl->check, PW_PASSWORD);
615                                 
616                                 switch(auth_type) {
617                                 case PW_AUTHTYPE_CRYPT:
618                                         DEBUG2("  file_auth (Usercollide): Checking Crypt");
619                                         if (password_pair == NULL) {
620                                                 result = auth_item->strvalue ? 0 : 1;
621                                                 break;
622                                         }
623                                         if (strcmp(password_pair->strvalue,
624                                                    crypt(auth_item->strvalue,
625                                                          password_pair->strvalue)) != 0)
626                                                 result = 0;
627                                         break;
628                                 case PW_AUTHTYPE_LOCAL:
629                                         DEBUG2("  file_auth (Usercollide): Checking Local");
630                                         if (auth_item->attribute != PW_CHAP_PASSWORD) {
631                                                 if (password_pair == NULL ||
632                                                     strcmp(password_pair->strvalue,
633                                                            auth_item->strvalue)!=0)
634                                                         result = 0;
635                                                 break;
636                                         }
637                                 case PW_AUTHTYPE_ACCEPT:
638                                         break;  
639                                 default:
640                                         continue;
641                                 } /* switch(auth_type) */
642                         } /* if(!default) */
643
644                         if(result) { 
645 #endif
646                                 DEBUG2("  users: Matched %s at %d", pl->name, pl->lineno);
647                                 found = 1;
648                                 check_tmp = paircopy(pl->check);
649                                 reply_tmp = paircopy(pl->reply);
650                                 pairmove(reply_pairs, &reply_tmp);
651                                 pairmove(check_pairs, &check_tmp);
652                                 pairfree(reply_tmp);
653                                 pairfree(check_tmp); /* should be NULL */
654                                 /*
655                                  *      Fallthrough?
656                                  */
657                                 if (!fallthrough(pl->reply))
658                                         break;
659 #ifdef WITH_USERCOLLIDE
660                         }
661                 }
662 #endif
663         }
664         
665         /*
666          *      See if we succeeded.  If we didn't find the user,
667          *      then exit from the module.
668          */
669         if (!found)
670                 return RLM_MODULE_OK;
671
672         /*
673          *      Add the port number to the Framed-IP-Address if
674          *      vp->addport is set, or if the Add-Port-To-IP-Address
675          *      pair is present.
676          *
677          *      FIXME: this should not happen here, but
678          *      after module_authorize in the main code!
679          */
680         if ((tmp = pairfind(*reply_pairs, PW_FRAMED_IP_ADDRESS)) != NULL) {
681                 VALUE_PAIR *tmp2;
682
683                 tmp2 = pairfind(*reply_pairs, PW_ADD_PORT_TO_IP_ADDRESS);
684                 if (tmp->addport || (tmp2 && tmp2->lvalue)) {
685                         tmp->lvalue = htonl(ntohl(tmp->lvalue) + nas_port);
686                         tmp->addport = 0;
687                 }
688                 pairdelete(reply_pairs, PW_ADD_PORT_TO_IP_ADDRESS);
689         }
690
691         /*
692          *      Remove server internal parameters.
693          */
694         pairdelete(reply_pairs, PW_FALL_THROUGH);
695
696         return RLM_MODULE_OK;
697 }
698
699 /*
700  *      Authentication - unused.
701  */
702 static int file_authenticate(void *instance, REQUEST *request)
703 {
704         instance = instance;
705         request = request;
706         return RLM_MODULE_OK;
707 }
708
709 #if USE_DYNAMIC_LOGS
710 /*
711  * Write the dynamic log files
712  */
713 static void file_write_dynamic_log(REQUEST * request)
714 {
715         char fn[1024];
716         char buffer[4096];
717         int x,y;
718         VALUE_PAIR * pair;
719         FILE * f;
720
721         pair = pairfind(request->packet->vps,PW_ACCT_STATUS_TYPE);
722         for (x = 0; x < logcnt; x++) {
723                 if (((pair->lvalue == PW_STATUS_START) && (logcfg[x].flags & DL_FLAG_START)) ||
724                     ((pair->lvalue == PW_STATUS_STOP) && (logcfg[x].flags & DL_FLAG_STOP)) ||
725                     ((pair->lvalue == PW_STATUS_ACCOUNTING_ON) && (logcfg[x].flags & DL_FLAG_ACCT_ON)) ||
726                     ((pair->lvalue == PW_STATUS_ACCOUNTING_OFF) && (logcfg[x].flags & DL_FLAG_ACCT_OFF)) ||
727                     ((pair->lvalue == PW_STATUS_ALIVE) && (logcfg[x].flags & DL_FLAG_ALIVE))) {
728                         y = radius_xlat2(fn,sizeof(fn),logcfg[x].dir,request,request->packet->vps);
729                         (void) mkdir(fn, 0755);
730                         strcat(fn,"/");
731                         y++;
732                         /* FIXME must get the reply packet */
733                         radius_xlat2(&fn[y],sizeof(fn)-y,logcfg[x].fname,request,request->packet->vps);
734                         if (strcasecmp(logcfg[x].mode,"d") == 0) {
735                                 remove(fn);
736                         } else {
737                                 if (fn[y] == '|') {
738                                         f = popen(&fn[y+1],logcfg[x].mode);
739                                 } else {
740                                         /* FIXME: permissions? */
741                                         f = fopen(fn,logcfg[x].mode);
742                                 }
743                                 if (f) {
744                                         /* FIXME must get the reply packet */
745                                         radius_xlat2(buffer,sizeof(buffer),logcfg[x].fmt,request,request->packet->vps);
746                                         fprintf(f,"%s\n",buffer);
747                                         if (fn[y] == '|') {
748                                                 pclose(f);
749                                         } else {
750                                                 fclose(f);
751                                         }
752                                 } else {
753                                         if (fn[y] == '|') {
754                                                 log_debug("Error opening pipe %s",fn[y+1]);
755                                         } else {
756                                                 log_debug("Error opening log %s",fn);
757                                         }
758                                 }
759                         }
760                 }
761
762
763         }
764 }
765 #endif /* USE_DYNAMIC_LOGS */
766
767 /*
768  *      Pre-Accounting - read the acct_users file for check_items and
769  *      config_items. Reply items are Not Recommended(TM) in acct_users,
770  *      except for Fallthrough, which should work
771  *
772  *      This function is mostly a copy of file_authorize
773  */
774 static int file_preacct(void *instance, REQUEST *request)
775 {
776         VALUE_PAIR      *namepair;
777         const char      *name;
778         VALUE_PAIR      *request_pairs;
779         VALUE_PAIR      **config_pairs;
780         VALUE_PAIR      *reply_pairs = NULL;
781         VALUE_PAIR      *check_tmp;
782         VALUE_PAIR      *reply_tmp;
783         PAIR_LIST       *pl;
784         int             found = 0;
785 #if defined(WITH_DBM) || defined(WITH_NDBM)
786         int             i, r;
787         char            buffer[256];
788 #endif
789         struct file_instance *inst = instance;
790
791         namepair = request->username;
792         name = namepair ? (char *) namepair->strvalue : "NONE";
793         request_pairs = request->packet->vps;
794         config_pairs = &request->config_items;
795         
796         /*
797          *      Find the entry for the user.
798          */
799 #if defined(WITH_DBM) || defined(WITH_NDBM)
800         /*
801          *      FIXME: move to rlm_dbm.c
802          */
803         if (use_dbm) {
804                 /*
805                  *      FIXME: No Prefix / Suffix support for DBM.
806                  */
807 #ifdef WITH_DBM
808                 if (dbminit(inst->acctusersfile) != 0)
809 #endif
810 #ifdef WITH_NDBM
811                 if ((dbmfile = dbm_open(inst->acctusersfile, O_RDONLY, 0)) == NULL)
812 #endif
813                 {
814                         radlog(L_ERR|L_CONS, "cannot open dbm file %s",
815                                 buffer);
816                         return RLM_MODULE_FAIL;
817                 }
818
819                 r = dbm_find(dbmfile, name, request_pairs, config_pairs,
820                              &reply_pairs);
821                 if (r > 0) found = 1;
822                 if (r <= 0 || fallthrough(*reply_pairs)) {
823
824                   pairdelete(reply_pairs, PW_FALL_THROUGH);
825
826                         sprintf(buffer, "DEFAULT");
827                         i = 0;
828                         while ((r = dbm_find(dbmfile, buffer, request_pairs,
829                                config_pairs, &reply_pairs)) >= 0 || i < 2) {
830                                 if (r > 0) {
831                                         found = 1;
832                                         if (!fallthrough(*reply_pairs))
833                                                 break;
834                                         pairdelete(reply_pairs,PW_FALL_THROUGH);
835                                 }
836                                 sprintf(buffer, "DEFAULT%d", i++);
837                         }
838                 }
839 #ifdef WITH_DBM
840                 dbmclose();
841 #endif
842 #ifdef WITH_NDBM
843                 dbm_close(dbmfile);
844 #endif
845         } else
846         /*
847          *      Note the fallthrough through the #endif.
848          */
849 #endif
850
851         for(pl = inst->acctusers; pl; pl = pl->next) {
852
853                 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
854                         continue;
855
856                 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
857                         DEBUG2("  acct_users: Matched %s at %d",
858                                pl->name, pl->lineno);
859                         found = 1;
860                         check_tmp = paircopy(pl->check);
861                         reply_tmp = paircopy(pl->reply);
862                         pairmove(&reply_pairs, &reply_tmp);
863                         pairmove(config_pairs, &check_tmp);
864                         pairfree(reply_tmp);
865                         pairfree(check_tmp); /* should be NULL */
866                         /*
867                          *      Fallthrough?
868                          */
869                         if (!fallthrough(pl->reply))
870                                 break;
871                 }
872         }
873
874         /*
875          *      See if we succeeded.
876          */
877         if (!found)
878                 return RLM_MODULE_OK; /* on to the next module */
879
880         /*
881          *      FIXME: log a warning if there are any reply items other than
882          *      Fallthrough
883          */
884         pairfree(reply_pairs); /* Don't need these */
885
886         return RLM_MODULE_OK;
887 }
888
889 /*
890  *      Accounting - write the detail files.
891  */
892 static int file_accounting(void *instance, REQUEST *request)
893 {
894         int             outfd;
895         FILE            *outfp;
896         char            nasname[128];
897         char            buffer[512];
898         VALUE_PAIR      *pair;
899         uint32_t        nas;
900         NAS             *cl;
901         long            curtime;
902         int             ret = RLM_MODULE_OK;
903         struct stat     st;
904
905         struct file_instance *inst = instance;
906
907         /*
908          *      See if we have an accounting directory. If not,
909          *      return.
910          */
911         if (stat(radacct_dir, &st) < 0) {
912                 DEBUG("No accounting directory %s", radacct_dir);
913                 return RLM_MODULE_OK;
914         }
915         curtime = time(0);
916
917         /*
918          *      Find out the name of this terminal server. We try
919          *      to find the PW_NAS_IP_ADDRESS in the naslist file.
920          *      If that fails, we look for the originating address.
921          *      Only if that fails we resort to a name lookup.
922          */
923         cl = NULL;
924         nas = request->packet->src_ipaddr;
925         if ((pair = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
926                 nas = pair->lvalue;
927         if (request->proxy && request->proxy->src_ipaddr)
928                 nas = request->proxy->src_ipaddr;
929
930         if ((cl = nas_find(nas)) != NULL) {
931                 if (cl->shortname[0])
932                         strcpy(nasname, cl->shortname);
933                 else
934                         strcpy(nasname, cl->longname);
935         }
936
937         if (cl == NULL) {
938                 ip_hostname(nasname, sizeof(nasname), nas);
939         }
940
941         /*
942          *      Create a directory for this nas.
943          */
944         sprintf(buffer, "%s/%s", radacct_dir, nasname);
945         (void) mkdir(buffer, 0755);
946
947         /*
948          *      Write Detail file.
949          */
950         sprintf(buffer, "%s/%s/%s", radacct_dir, nasname, "detail");
951         if ((outfd = open(buffer, O_WRONLY|O_APPEND|O_CREAT,
952                           inst->detailperm)) < 0) {
953                 radlog(L_ERR, "Acct: Couldn't open file %s", buffer);
954                 ret = RLM_MODULE_FAIL;
955         } else if ((outfp = fdopen(outfd, "a")) == NULL) {
956                 radlog(L_ERR, "Acct: Couldn't open file %s: %s",
957                     buffer, strerror(errno));
958                 ret = RLM_MODULE_FAIL;
959                 close(outfd);
960         } else {
961
962                 /* Post a timestamp */
963                 fputs(ctime(&curtime), outfp);
964
965                 /* Write each attribute/value to the log file */
966                 pair = request->packet->vps;
967                 while (pair) {
968                         if (pair->attribute != PW_PASSWORD) {
969                                 fputs("\t", outfp);
970                                 fprint_attr_val(outfp, pair);
971                                 fputs("\n", outfp);
972                         }
973                         pair = pair->next;
974                 }
975
976                 /*
977                  *      Add non-protocol attibutes.
978                  */
979                 fprintf(outfp, "\tTimestamp = %ld\n", curtime);
980                 if (request->packet->verified)
981                         fputs("\tRequest-Authenticator = Verified\n", outfp);
982                 else
983                         fputs("\tRequest-Authenticator = None\n", outfp);
984                 fputs("\n", outfp);
985                 fclose(outfp);
986         }
987
988 #if USE_DYNAMIC_LOGS
989         file_write_dynamic_log(request);
990 #endif /* USE_DYNAMIC_LOGS */
991         return ret;
992 }
993
994
995 /*
996  *      Clean up.
997  */
998 static int file_detach(void *instance)
999 {
1000         struct file_instance *inst = instance;
1001         pairlist_free(&inst->users);
1002         pairlist_free(&inst->acctusers);
1003         free(inst->usersfile);
1004         free(inst->acctusersfile);
1005         free(inst);
1006         return 0;
1007 }
1008
1009
1010 /* globally exported name */
1011 module_t rlm_files = {
1012         "files",
1013         0,                              /* type: reserved */
1014         file_init,                      /* initialization */
1015         file_instantiate,               /* instantiation */
1016         file_authorize,                 /* authorization */
1017         file_authenticate,              /* authentication */
1018         file_preacct,                   /* preaccounting */
1019         file_accounting,                /* accounting */
1020         file_detach,                    /* detach */
1021         NULL                            /* destroy */
1022 };
1023