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