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