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