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