Include files used to build the server are now <freeradius-devel/*.h>
[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        <freeradius-devel/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        <freeradius-devel/radiusd.h>
38 #include        <freeradius-devel/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_FILENAME,
69           offsetof(struct file_instance,usersfile), NULL, "${raddbdir}/users" },
70         { "acctusersfile", PW_TYPE_FILENAME,
71           offsetof(struct file_instance,acctusersfile), NULL, "${raddbdir}/acct_users" },
72         { "preproxy_usersfile", PW_TYPE_FILENAME,
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
290         request_pairs = request->packet->vps;
291         check_pairs = &request->config_items;
292         reply_pairs = &request->reply->vps;
293
294         /*
295          *      Grab the canonical user name.
296          */
297         namepair = request->username;
298         name = namepair ? (char *) namepair->vp_strvalue : "NONE";
299
300         /*
301          *      Find the entry for the user.
302          */
303         for(pl = inst->users; pl; pl = pl->next) {
304                 /*
305                  *      If the current entry is NOT a default,
306                  *      AND the name does NOT match the current entry,
307                  *      then skip to the next entry.
308                  */
309                 if ((strcmp(pl->name, "DEFAULT") != 0) &&
310                     (strcmp(name, pl->name) != 0))  {
311                         continue;
312                 }
313
314                 /*
315                  *      If the current request matches against the
316                  *      check pairs, then add the reply pairs from the
317                  *      entry to the current list of reply pairs.
318                  */
319                 if ((paircompare(request, request_pairs, pl->check, reply_pairs) == 0)) {
320                         DEBUG2("    users: Matched entry %s at line %d", pl->name, pl->lineno);
321                         found = 1;
322                         check_tmp = paircopy(pl->check);
323                         reply_tmp = paircopy(pl->reply);
324                         pairxlatmove(request, reply_pairs, &reply_tmp);
325                         pairmove(check_pairs, &check_tmp);
326                         pairfree(&reply_tmp);
327                         pairfree(&check_tmp); /* should be NULL */
328
329                         /*
330                          *      Fallthrough?
331                          */
332                         if (!fallthrough(pl->reply))
333                                 break;
334                 }
335         }
336
337         /*
338          *      See if we succeeded.  If we didn't find the user,
339          *      then exit from the module.
340          */
341         if (!found)
342                 return RLM_MODULE_NOTFOUND;
343
344         /*
345          *      Remove server internal parameters.
346          */
347         pairdelete(reply_pairs, PW_FALL_THROUGH);
348
349         return RLM_MODULE_OK;
350 }
351
352 /*
353  *      Pre-Accounting - read the acct_users file for check_items and
354  *      config_items. Reply items are Not Recommended(TM) in acct_users,
355  *      except for Fallthrough, which should work
356  *
357  *      This function is mostly a copy of file_authorize
358  */
359 static int file_preacct(void *instance, REQUEST *request)
360 {
361         VALUE_PAIR      *namepair;
362         const char      *name;
363         VALUE_PAIR      *request_pairs;
364         VALUE_PAIR      **config_pairs;
365         VALUE_PAIR      **reply_pairs;
366         VALUE_PAIR      *check_tmp;
367         VALUE_PAIR      *reply_tmp;
368         PAIR_LIST       *pl;
369         int             found = 0;
370         struct file_instance *inst = instance;
371
372         namepair = request->username;
373         name = namepair ? (char *) namepair->vp_strvalue : "NONE";
374         request_pairs = request->packet->vps;
375         config_pairs = &request->config_items;
376         reply_pairs = &request->reply->vps;
377
378         /*
379          *      Find the entry for the user.
380          */
381         for (pl = inst->acctusers; pl; pl = pl->next) {
382
383                 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
384                         continue;
385
386                 if (paircompare(request, request_pairs, pl->check, reply_pairs) == 0) {
387                         DEBUG2("    acct_users: Matched entry %s at line %d",
388                                pl->name, pl->lineno);
389                         found = 1;
390                         check_tmp = paircopy(pl->check);
391                         reply_tmp = paircopy(pl->reply);
392                         pairxlatmove(request, reply_pairs, &reply_tmp);
393                         pairmove(config_pairs, &check_tmp);
394                         pairfree(&reply_tmp);
395                         pairfree(&check_tmp); /* should be NULL */
396                         /*
397                          *      Fallthrough?
398                          */
399                         if (!fallthrough(pl->reply))
400                                 break;
401                 }
402         }
403
404         /*
405          *      See if we succeeded.
406          */
407         if (!found)
408                 return RLM_MODULE_NOOP; /* on to the next module */
409
410         return RLM_MODULE_OK;
411 }
412
413 /*
414  *      Pre-proxy - read the preproxy_users file for check_items and
415  *      config_items.
416  *
417  *      This function is mostly a copy of file_authorize
418  */
419 static int file_preproxy(void *instance, REQUEST *request)
420 {
421         VALUE_PAIR      *namepair;
422         const char      *name;
423         VALUE_PAIR      *request_pairs;
424         VALUE_PAIR      **config_pairs;
425         VALUE_PAIR      **reply_pairs;
426         VALUE_PAIR      *check_tmp;
427         VALUE_PAIR      *reply_tmp;
428         PAIR_LIST       *pl;
429         int             found = 0;
430         struct file_instance *inst = instance;
431
432         namepair = request->username;
433         name = namepair ? (char *) namepair->vp_strvalue : "NONE";
434         request_pairs = request->packet->vps;
435         config_pairs = &request->config_items;
436         reply_pairs = &request->proxy->vps;
437
438         /*
439          *      Find the entry for the user.
440          */
441         for (pl = inst->preproxy_users; pl; pl = pl->next) {
442
443                 /*
444                  *  No match: skip it.
445                  */
446                 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
447                         continue;
448
449                 if (paircompare(request, request_pairs, pl->check, reply_pairs) == 0) {
450                         VALUE_PAIR *vp;
451
452                         DEBUG2("    preproxy_users: Matched entry %s at line %d",
453                                pl->name, pl->lineno);
454                         found = 1;
455                         check_tmp = paircopy(pl->check);
456                         reply_tmp = paircopy(pl->reply);
457
458                         for (vp = reply_tmp; vp != NULL; vp = vp->next) {
459                                 /*
460                                  *      We've got to xlat the string
461                                  *      before moving it over.
462                                  */
463                                 if (vp->flags.do_xlat) {
464                                         int rcode;
465                                         char buffer[sizeof(vp->vp_strvalue)];
466
467                                         vp->flags.do_xlat = 0;
468                                         rcode = radius_xlat(buffer, sizeof(buffer),
469                                                             vp->vp_strvalue,
470                                                             request, NULL);
471
472                                         /*
473                                          *      Parse the string into
474                                          *      a new value.
475                                          */
476                                         pairparsevalue(vp, buffer);
477                                 }
478                         } /* loop over the things to add to the reply */
479
480                         pairxlatmove(request, reply_pairs, &reply_tmp);
481                         pairmove(config_pairs, &check_tmp);
482                         pairfree(&reply_tmp);
483                         pairfree(&check_tmp); /* should be NULL */
484                         /*
485                          *      Fallthrough?
486                          */
487                         if (!fallthrough(pl->reply))
488                                 break;
489                 }
490         }
491
492         /*
493          *      See if we succeeded.
494          */
495         if (!found)
496                 return RLM_MODULE_NOOP; /* on to the next module */
497
498         return RLM_MODULE_OK;
499 }
500
501 /*
502  *      Clean up.
503  */
504 static int file_detach(void *instance)
505 {
506         struct file_instance *inst = instance;
507         pairlist_free(&inst->users);
508         pairlist_free(&inst->acctusers);
509         pairlist_free(&inst->preproxy_users);
510         free(inst->usersfile);
511         free(inst->acctusersfile);
512         free(inst->preproxy_usersfile);
513         free(inst->compat_mode);
514         free(inst);
515         return 0;
516 }
517
518
519 /* globally exported name */
520 module_t rlm_files = {
521         RLM_MODULE_INIT,
522         "files",
523         0,                              /* type: reserved */
524         file_instantiate,               /* instantiation */
525         file_detach,                    /* detach */
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 };
537