Formatted code, loosely. s/ {8}/\t/g
[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  *   This program is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU General Public License as published by
9  *   the Free Software Foundation; either version 2 of the License, or
10  *   (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with this program; if not, write to the Free Software
19  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * Copyright 2000  The FreeRADIUS server project
22  * Copyright 2000  Jeff Carneal <jeff@apex.net>
23  */
24
25 static const char rcsid[] = "$Id$";
26
27 #include        "autoconf.h"
28 #include        "libradius.h"
29
30 #include        <sys/stat.h>
31
32 #include        <stdlib.h>
33 #include        <string.h>
34 #include        <netdb.h>
35 #include        <ctype.h>
36 #include        <fcntl.h>
37 #include        <limits.h>
38
39 #if HAVE_MALLOC_H
40 #       include <malloc.h>
41 #endif
42
43 #include        "radiusd.h"
44 #include        "modules.h"
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
58 /*
59  *     See if a VALUE_PAIR list contains Fall-Through = Yes
60  */
61 static int fallthrough(VALUE_PAIR *vp)
62 {
63         VALUE_PAIR *tmp;
64
65         tmp = pairfind(vp, PW_FALL_THROUGH);
66
67         return tmp ? tmp->lvalue : 0;
68 }
69
70 /*
71  *      A temporary holding area for config values to be extracted
72  *      into, before they are copied into the instance data
73  */
74 static struct file_instance config;
75
76 static CONF_PARSER module_config[] = {
77         { "usersfile",  PW_TYPE_STRING_PTR, &config.usersfile, "${raddbdir}/users" },
78         { "acctusersfile",      PW_TYPE_STRING_PTR, &config.acctusersfile, "${raddbdir}/acct_users" },
79         { "compat",     PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
80         { NULL, -1, NULL, NULL }
81 };
82
83 static int getusersfile(const char *filename, PAIR_LIST **pair_list)
84 {
85         int rcode;
86         PAIR_LIST *users = NULL;
87
88         rcode = pairlist_read(filename, &users, 1);
89         if (rcode < 0) {
90                 return -1;
91         }
92
93         /*
94          *      Walk through the 'users' file list, if we're debugging,
95          *      or if we're in compat_mode.
96          */
97         if ((debug_flag) ||
98                         (strcmp(config.compat_mode, "cistron") == 0)) {
99                 PAIR_LIST *entry;
100                 VALUE_PAIR *vp;
101                 int compat_mode = FALSE;
102
103                 if (strcmp(config.compat_mode, "cistron") == 0) {
104                         compat_mode = TRUE;
105                 }
106         
107                 entry = users;
108                 while (entry) {
109                         if (compat_mode) {
110                                 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
111                                                 filename, entry->lineno,
112                                                 entry->name);
113                         }
114
115                         /*
116                          *      Look for improper use of '=' in the
117                          *      check items.  They should be using
118                          *      '==' for on-the-wire RADIUS attributes,
119                          *      and probably ':=' for server
120                          *      configuration items.
121                          */
122                         for (vp = entry->check; vp != NULL; vp = vp->next) {
123                                 /*
124                                  *      Ignore attributes which are set
125                                  *      properly.
126                                  */
127                                 if (vp->operator != T_OP_EQ) {
128                                         continue;
129                                 }
130
131                                 /*
132                                  *      If it's a vendor attribute,
133                                  *      or it's a wire protocol, 
134                                  *      ensure it has '=='.
135                                  */
136                                 if (((vp->attribute & ~0xffff) != 0) ||
137                                                 (vp->attribute < 0x100)) {
138                                         if (!compat_mode) {
139                                                 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
140                                                                 filename, entry->lineno,
141                                                                 vp->name, vp->name,
142                                                                 entry->name);
143                                         } else {
144                                                 DEBUG("\tChanging '%s =' to '%s =='",
145                                                                 vp->name, vp->name);
146                                         }
147                                         vp->operator = T_OP_CMP_EQ;
148                                         continue;
149                                 }
150                                 
151                                 /*
152                                  *      Cistron Compatibility mode.
153                                  *
154                                  *      Re-write selected attributes
155                                  *      to be '+=', instead of '='.
156                                  *
157                                  *      All others get set to '=='
158                                  */
159                                 if (compat_mode) {
160                                         /*
161                                          *      Non-wire attributes become +=
162                                          *
163                                          *      On the write attributes
164                                          *      become ==
165                                          */
166                                         if ((vp->attribute >= 0x100) &&
167                                                         (vp->attribute <= 0xffff) &&
168                                                         (vp->attribute != PW_HINT) &&
169                                                         (vp->attribute != PW_HUNTGROUP_NAME)) {
170                                                 DEBUG("\tChanging '%s =' to '%s +='",
171                                                                 vp->name, vp->name);
172                                                 vp->operator = T_OP_ADD;
173                                         } else {
174                                                 DEBUG("\tChanging '%s =' to '%s =='",
175                                                                 vp->name, vp->name);
176                                                 vp->operator = T_OP_CMP_EQ;
177                                         }
178                                 }
179                                 
180                         } /* end of loop over check items */
181                 
182                 
183                         /*
184                          *      Look for server configuration items
185                          *      in the reply list.
186                          *
187                          *      It's a common enough mistake, that it's
188                          *      worth doing.
189                          */
190                         for (vp = entry->reply; vp != NULL; vp = vp->next) {
191                                 /*
192                                  *      If it's NOT a vendor attribute,
193                                  *      and it's NOT a wire protocol
194                                  *      and we ignore Fall-Through,
195                                  *      then bitch about it, giving a
196                                  *      good warning message.
197                                  */
198                                 if (!(vp->attribute & ~0xffff) &&
199                                         (vp->attribute > 0xff) &&
200                                         (vp->attribute > 1000)) {
201                                         log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
202                                                         "\tfound in reply item list for user \"%s\".\n"
203                                                         "\tThis attribute MUST go on the first line"
204                                                         " with the other check items", 
205                                                         filename, entry->lineno, vp->name,
206                                                         entry->name);
207                                 }
208                         }
209                 
210                         entry = entry->next;
211                 }
212         
213         }
214
215         *pair_list = users;
216         return 0;
217 }
218
219 /*
220  *      (Re-)read the "users" file into memory.
221  */
222 static int file_instantiate(CONF_SECTION *conf, void **instance)
223 {
224         struct file_instance *inst;
225         int rcode;
226
227         inst = malloc(sizeof *inst);
228         if (!inst) {
229                 radlog(L_ERR|L_CONS, "Out of memory\n");
230                 return -1;
231         }
232
233         if (cf_section_parse(conf, module_config) < 0) {
234                 free(inst);
235                 return -1;
236         }
237
238         inst->usersfile = config.usersfile;
239         inst->acctusersfile = config.acctusersfile;
240         inst->compat_mode = config.compat_mode;
241         config.usersfile = NULL;
242         config.acctusersfile = NULL;
243
244         rcode = getusersfile(inst->usersfile, &inst->users);
245         if (rcode != 0) {
246                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
247                 free(inst->usersfile);
248                 free(inst->acctusersfile);
249                 free(inst);
250                 return -1;
251         }
252
253         rcode = getusersfile(inst->acctusersfile, &inst->acctusers);
254         if (rcode != 0) {
255                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
256                 pairlist_free(&inst->users);
257                 free(inst->usersfile);
258                 free(inst->acctusersfile);
259                 free(inst);
260                 return -1;
261         }
262
263         config.compat_mode = NULL;
264
265         *instance = inst;
266         return 0;
267 }
268
269 /*
270  *      Find the named user in the database.  Create the
271  *      set of attribute-value pairs to check and reply with
272  *      for this user from the database. The main code only
273  *      needs to check the password, the rest is done here.
274  */
275 static int file_authorize(void *instance, REQUEST *request)
276 {
277         VALUE_PAIR      *namepair;
278         VALUE_PAIR      *request_pairs;
279         VALUE_PAIR      *check_tmp;
280         VALUE_PAIR      *reply_tmp;
281         PAIR_LIST       *pl;
282         int             found = 0;
283         const char      *name;
284         struct file_instance *inst = instance;
285         VALUE_PAIR **check_pairs, **reply_pairs;
286         VALUE_PAIR *check_save;
287
288         request_pairs = request->packet->vps;
289         check_pairs = &request->config_items;
290         reply_pairs = &request->reply->vps;
291
292         /*
293          *      Grab the canonical user name.
294          */
295         namepair = request->username;
296         name = namepair ? (char *) namepair->strvalue : "NONE";
297
298         /*
299          *      Find the entry for the user.
300          */
301         for(pl = inst->users; pl; pl = pl->next) {
302                 /*
303                  *      If the current entry is NOT a default,
304                  *      AND the name does NOT match the current entry,
305                  *      then skip to the next entry.
306                  */
307                 if ((strcmp(pl->name, "DEFAULT") != 0) &&
308                     (strcmp(name, pl->name) != 0))  {
309                         continue;
310                 }
311
312                 /*
313                  *      If the current request matches against the
314                  *      check pairs, then add the reply pairs from the
315                  *      entry to the current list of reply pairs.
316                  */
317                 if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
318
319                         if((mainconfig.do_usercollide) && (strcmp(pl->name, "DEFAULT"))) {
320
321                                 /* 
322                                  * We have to make sure the password
323                                  * matches as well
324                                  */
325         
326                                 /* Save the orginal config items */
327                                 check_save = paircopy(request->config_items);
328         
329                                 /* Copy this users check pairs to the request */
330                                 check_tmp = paircopy(pl->check);
331                                 pairmove(check_pairs, &check_tmp);
332                                 pairfree(&check_tmp);
333         
334                                 DEBUG2("  users: Checking %s at %d", pl->name, pl->lineno);
335                                 /* Check the req to see if we matched */
336                                 if (rad_check_password(request)==0) {
337                                         DEBUG2("  users: Matched %s at %d", pl->name, pl->lineno);
338
339                                         found = 1;
340
341                                         /* Free our saved config items */
342                                         pairfree(&check_save);
343
344                                         /* 
345                                          * Already copied check items, so 
346                                          * just copy reply here
347                                          */
348                                         reply_tmp = paircopy(pl->reply);
349                                         pairmove(reply_pairs, &reply_tmp);
350                                         pairfree(&reply_tmp);
351
352                                 /* We didn't match here */
353                                 } else {
354                                         /* Restore check items */
355                                         pairfree(&request->config_items);
356                                         request->config_items = paircopy(check_save);
357                                         check_pairs = &request->config_items;
358                                         continue;
359                                 }
360         
361                         /* No usercollide */
362                         } else {
363                 
364                                 DEBUG2("  users: Matched %s at %d", pl->name, pl->lineno);
365                                 found = 1;
366                                 check_tmp = paircopy(pl->check);
367                                 reply_tmp = paircopy(pl->reply);
368                                 pairmove(reply_pairs, &reply_tmp);
369                                 pairmove(check_pairs, &check_tmp);
370                                 pairfree(&reply_tmp);
371                                 pairfree(&check_tmp); /* should be NULL */
372                         }
373                         /*
374                          *      Fallthrough?
375                          */
376                         if (!fallthrough(pl->reply))
377                                 break;
378                 }
379         }
380         
381         /*
382          *      See if we succeeded.  If we didn't find the user,
383          *      then exit from the module.
384          */
385         if (!found)
386                 return RLM_MODULE_NOTFOUND;
387
388         /*
389          *      Remove server internal parameters.
390          */
391         pairdelete(reply_pairs, PW_FALL_THROUGH);
392
393         return RLM_MODULE_OK;
394 }
395
396 /*
397  *      Pre-Accounting - read the acct_users file for check_items and
398  *      config_items. Reply items are Not Recommended(TM) in acct_users,
399  *      except for Fallthrough, which should work
400  *
401  *      This function is mostly a copy of file_authorize
402  */
403 static int file_preacct(void *instance, REQUEST *request)
404 {
405         VALUE_PAIR      *namepair;
406         const char      *name;
407         VALUE_PAIR      *request_pairs;
408         VALUE_PAIR      **config_pairs;
409         VALUE_PAIR      *reply_pairs = NULL;
410         VALUE_PAIR      *check_tmp;
411         VALUE_PAIR      *reply_tmp;
412         PAIR_LIST       *pl;
413         int             found = 0;
414         struct file_instance *inst = instance;
415
416         namepair = request->username;
417         name = namepair ? (char *) namepair->strvalue : "NONE";
418         request_pairs = request->packet->vps;
419         config_pairs = &request->config_items;
420         
421         /*
422          *      Find the entry for the user.
423          */
424         for (pl = inst->acctusers; pl; pl = pl->next) {
425
426                 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
427                         continue;
428
429                 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
430                         DEBUG2("  acct_users: Matched %s at %d",
431                                pl->name, pl->lineno);
432                         found = 1;
433                         check_tmp = paircopy(pl->check);
434                         reply_tmp = paircopy(pl->reply);
435                         pairmove(&reply_pairs, &reply_tmp);
436                         pairmove(config_pairs, &check_tmp);
437                         pairfree(&reply_tmp);
438                         pairfree(&check_tmp); /* should be NULL */
439                         /*
440                          *      Fallthrough?
441                          */
442                         if (!fallthrough(pl->reply))
443                                 break;
444                 }
445         }
446
447         /*
448          *      See if we succeeded.
449          */
450         if (!found)
451                 return RLM_MODULE_NOOP; /* on to the next module */
452
453         /*
454          *      FIXME: log a warning if there are any reply items other than
455          *      Fallthrough
456          */
457         pairfree(&reply_pairs); /* Don't need these */
458
459         return RLM_MODULE_OK;
460 }
461
462 /*
463  *      Clean up.
464  */
465 static int file_detach(void *instance)
466 {
467         struct file_instance *inst = instance;
468         pairlist_free(&inst->users);
469         pairlist_free(&inst->acctusers);
470         free(inst->usersfile);
471         free(inst->acctusersfile);
472         free(inst->compat_mode);
473         free(inst);
474         return 0;
475 }
476
477
478 /* globally exported name */
479 module_t rlm_files = {
480         "files",
481         0,                              /* type: reserved */
482         NULL,                           /* initialization */
483         file_instantiate,               /* instantiation */
484         file_authorize,                 /* authorization */
485         NULL,                           /* authentication */
486         file_preacct,                   /* preaccounting */
487         NULL,                           /* accounting */
488         NULL,                           /* checksimul */
489         file_detach,                    /* detach */
490         NULL                            /* destroy */
491 };
492