When processing the acct_users file, actually *keep* the reply
[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 #include        "radiusd.h"
40 #include        "modules.h"
41
42 struct file_instance {
43         char *compat_mode;
44
45         /* autz */
46         char *usersfile;
47         PAIR_LIST *users;
48
49         /* preacct */
50         char *acctusersfile;
51         PAIR_LIST *acctusers;
52 };
53
54 /*
55  *     See if a VALUE_PAIR list contains Fall-Through = Yes
56  */
57 static int fallthrough(VALUE_PAIR *vp)
58 {
59         VALUE_PAIR *tmp;
60
61         tmp = pairfind(vp, PW_FALL_THROUGH);
62
63         return tmp ? tmp->lvalue : 0;
64 }
65
66 static CONF_PARSER module_config[] = {
67         { "usersfile",     PW_TYPE_STRING_PTR,
68           offsetof(struct file_instance,usersfile), NULL, "${raddbdir}/users" },
69         { "acctusersfile", PW_TYPE_STRING_PTR,
70           offsetof(struct file_instance,acctusersfile), NULL, "${raddbdir}/acct_users" },
71         { "compat",        PW_TYPE_STRING_PTR,
72           offsetof(struct file_instance,compat_mode), NULL, "cistron" },
73         { NULL, -1, 0, NULL, NULL }
74 };
75
76 static int getusersfile(const char *filename, PAIR_LIST **pair_list, char *compat_mode_str)
77 {
78         int rcode;
79         PAIR_LIST *users = NULL;
80
81         rcode = pairlist_read(filename, &users, 1);
82         if (rcode < 0) {
83                 return -1;
84         }
85
86         /*
87          *      Walk through the 'users' file list, if we're debugging,
88          *      or if we're in compat_mode.
89          */
90         if ((debug_flag) ||
91                         (strcmp(compat_mode_str, "cistron") == 0)) {
92                 PAIR_LIST *entry;
93                 VALUE_PAIR *vp;
94                 int compat_mode = FALSE;
95
96                 if (strcmp(compat_mode_str, "cistron") == 0) {
97                         compat_mode = TRUE;
98                 }
99         
100                 entry = users;
101                 while (entry) {
102                         if (compat_mode) {
103                                 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
104                                                 filename, entry->lineno,
105                                                 entry->name);
106                         }
107
108                         /*
109                          *      Look for improper use of '=' in the
110                          *      check items.  They should be using
111                          *      '==' for on-the-wire RADIUS attributes,
112                          *      and probably ':=' for server
113                          *      configuration items.
114                          */
115                         for (vp = entry->check; vp != NULL; vp = vp->next) {
116                                 /*
117                                  *      Ignore attributes which are set
118                                  *      properly.
119                                  */
120                                 if (vp->operator != T_OP_EQ) {
121                                         continue;
122                                 }
123
124                                 /*
125                                  *      If it's a vendor attribute,
126                                  *      or it's a wire protocol, 
127                                  *      ensure it has '=='.
128                                  */
129                                 if (((vp->attribute & ~0xffff) != 0) ||
130                                                 (vp->attribute < 0x100)) {
131                                         if (!compat_mode) {
132                                                 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
133                                                                 filename, entry->lineno,
134                                                                 vp->name, vp->name,
135                                                                 entry->name);
136                                         } else {
137                                                 DEBUG("\tChanging '%s =' to '%s =='",
138                                                                 vp->name, vp->name);
139                                         }
140                                         vp->operator = T_OP_CMP_EQ;
141                                         continue;
142                                 }
143                                 
144                                 /*
145                                  *      Cistron Compatibility mode.
146                                  *
147                                  *      Re-write selected attributes
148                                  *      to be '+=', instead of '='.
149                                  *
150                                  *      All others get set to '=='
151                                  */
152                                 if (compat_mode) {
153                                         /*
154                                          *      Non-wire attributes become +=
155                                          *
156                                          *      On the write attributes
157                                          *      become ==
158                                          */
159                                         if ((vp->attribute >= 0x100) &&
160                                                         (vp->attribute <= 0xffff) &&
161                                                         (vp->attribute != PW_HINT) &&
162                                                         (vp->attribute != PW_HUNTGROUP_NAME)) {
163                                                 DEBUG("\tChanging '%s =' to '%s +='",
164                                                                 vp->name, vp->name);
165                                                 vp->operator = T_OP_ADD;
166                                         } else {
167                                                 DEBUG("\tChanging '%s =' to '%s =='",
168                                                                 vp->name, vp->name);
169                                                 vp->operator = T_OP_CMP_EQ;
170                                         }
171                                 }
172                                 
173                         } /* end of loop over check items */
174                 
175                 
176                         /*
177                          *      Look for server configuration items
178                          *      in the reply list.
179                          *
180                          *      It's a common enough mistake, that it's
181                          *      worth doing.
182                          */
183                         for (vp = entry->reply; vp != NULL; vp = vp->next) {
184                                 /*
185                                  *      If it's NOT a vendor attribute,
186                                  *      and it's NOT a wire protocol
187                                  *      and we ignore Fall-Through,
188                                  *      then bitch about it, giving a
189                                  *      good warning message.
190                                  */
191                                 if (!(vp->attribute & ~0xffff) &&
192                                         (vp->attribute > 0xff) &&
193                                         (vp->attribute > 1000)) {
194                                         log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
195                                                         "\tfound in reply item list for user \"%s\".\n"
196                                                         "\tThis attribute MUST go on the first line"
197                                                         " with the other check items", 
198                                                         filename, entry->lineno, vp->name,
199                                                         entry->name);
200                                 }
201                         }
202                 
203                         entry = entry->next;
204                 }
205         
206         }
207
208         *pair_list = users;
209         return 0;
210 }
211
212 /*
213  *      (Re-)read the "users" file into memory.
214  */
215 static int file_instantiate(CONF_SECTION *conf, void **instance)
216 {
217         struct file_instance *inst;
218         int rcode;
219
220         inst = rad_malloc(sizeof *inst);
221
222         if (cf_section_parse(conf, inst, module_config) < 0) {
223                 free(inst);
224                 return -1;
225         }
226
227         rcode = getusersfile(inst->usersfile, &inst->users, inst->compat_mode);
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, inst->compat_mode);
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;
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         reply_pairs = &request->reply->vps;
402
403         /*
404          *      Find the entry for the user.
405          */
406         for (pl = inst->acctusers; pl; pl = pl->next) {
407
408                 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
409                         continue;
410
411                 if (paircmp(request_pairs, pl->check, reply_pairs) == 0) {
412                         DEBUG2("    acct_users: Matched %s at %d",
413                                pl->name, pl->lineno);
414                         found = 1;
415                         check_tmp = paircopy(pl->check);
416                         reply_tmp = paircopy(pl->reply);
417                         pairmove(reply_pairs, &reply_tmp);
418                         pairmove(config_pairs, &check_tmp);
419                         pairfree(&reply_tmp);
420                         pairfree(&check_tmp); /* should be NULL */
421                         /*
422                          *      Fallthrough?
423                          */
424                         if (!fallthrough(pl->reply))
425                                 break;
426                 }
427         }
428
429         /*
430          *      See if we succeeded.
431          */
432         if (!found)
433                 return RLM_MODULE_NOOP; /* on to the next module */
434
435         return RLM_MODULE_OK;
436 }
437
438 /*
439  *      Clean up.
440  */
441 static int file_detach(void *instance)
442 {
443         struct file_instance *inst = instance;
444         pairlist_free(&inst->users);
445         pairlist_free(&inst->acctusers);
446         free(inst->usersfile);
447         free(inst->acctusersfile);
448         free(inst->compat_mode);
449         free(inst);
450         return 0;
451 }
452
453
454 /* globally exported name */
455 module_t rlm_files = {
456         "files",
457         0,                              /* type: reserved */
458         NULL,                           /* initialization */
459         file_instantiate,               /* instantiation */
460         {
461                 NULL,                   /* authentication */
462                 file_authorize,         /* authorization */
463                 file_preacct,           /* preaccounting */
464                 NULL,                   /* accounting */
465                 NULL                    /* checksimul */
466         },
467         file_detach,                    /* detach */
468         NULL                            /* destroy */
469 };
470