1 /*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 /**
18 * $Id: 9e77cd7ff193100a7e6e3038700beea9955560c0 $
19 * @file rlm_files.c
20 * @brief Process simple 'users' policy files.
21 *
22 * @copyright 2000,2006 The FreeRADIUS server project
23 * @copyright 2000 Jeff Carneal <jeff@apex.net>
24 */
25 RCSID("$Id: 9e77cd7ff193100a7e6e3038700beea9955560c0 $")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29
30 #include <ctype.h>
31 #include <fcntl.h>
32
33 typedef struct rlm_files_t {
34 char const *compat_mode;
35
36 char const *key;
37
38 char const *filename;
39 rbtree_t *common;
40
41 /* autz */
42 char const *usersfile;
43 rbtree_t *users;
44
45
46 /* authenticate */
47 char const *auth_usersfile;
48 rbtree_t *auth_users;
49
50 /* preacct */
51 char const *acctusersfile;
52 rbtree_t *acctusers;
53
54 #ifdef WITH_PROXY
55 /* pre-proxy */
56 char const *preproxy_usersfile;
57 rbtree_t *preproxy_users;
58
59 /* post-proxy */
60 char const *postproxy_usersfile;
61 rbtree_t *postproxy_users;
62 #endif
63
64 /* post-authenticate */
65 char const *postauth_usersfile;
66 rbtree_t *postauth_users;
67 } rlm_files_t;
68
69
70 /*
71 * See if a VALUE_PAIR list contains Fall-Through = Yes
72 */
fall_through(VALUE_PAIR * vp)73 static int fall_through(VALUE_PAIR *vp)
74 {
75 VALUE_PAIR *tmp;
76 tmp = fr_pair_find_by_num(vp, PW_FALL_THROUGH, 0, TAG_ANY);
77
78 return tmp ? tmp->vp_integer : 0;
79 }
80
81 static const CONF_PARSER module_config[] = {
82 { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, filename), NULL },
83 { "usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, usersfile), NULL },
84 { "acctusersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, acctusersfile), NULL },
85 #ifdef WITH_PROXY
86 { "preproxy_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, preproxy_usersfile), NULL },
87 { "postproxy_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, postproxy_usersfile), NULL },
88 #endif
89 { "auth_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, auth_usersfile), NULL },
90 { "postauth_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, postauth_usersfile), NULL },
91 { "compat", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_files_t, compat_mode), NULL },
92 { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_files_t, key), NULL },
93 CONF_PARSER_TERMINATOR
94 };
95
96
pairlist_cmp(void const * a,void const * b)97 static int pairlist_cmp(void const *a, void const *b)
98 {
99 return strcmp(((PAIR_LIST const *)a)->name,
100 ((PAIR_LIST const *)b)->name);
101 }
102
getusersfile(TALLOC_CTX * ctx,char const * filename,rbtree_t ** ptree,char const * compat_mode_str)103 static int getusersfile(TALLOC_CTX *ctx, char const *filename, rbtree_t **ptree, char const *compat_mode_str)
104 {
105 int rcode;
106 PAIR_LIST *users = NULL;
107 PAIR_LIST *entry, *next;
108 PAIR_LIST *user_list, *default_list, **default_tail;
109 rbtree_t *tree;
110
111 if (!filename) {
112 *ptree = NULL;
113 return 0;
114 }
115
116 rcode = pairlist_read(ctx, filename, &users, 1);
117 if (rcode < 0) {
118 return -1;
119 }
120
121 /*
122 * Walk through the 'users' file list, if we're debugging,
123 * or if we're in compat_mode.
124 */
125 if ((rad_debug_lvl) ||
126 (compat_mode_str && (strcmp(compat_mode_str, "cistron") == 0))) {
127 VALUE_PAIR *vp;
128 bool compat_mode = false;
129
130 if (compat_mode_str && (strcmp(compat_mode_str, "cistron") == 0)) {
131 compat_mode = true;
132 }
133
134 entry = users;
135 while (entry) {
136 vp_cursor_t cursor;
137 if (compat_mode) {
138 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
139 filename, entry->lineno,
140 entry->name);
141 }
142
143 /*
144 * Look for improper use of '=' in the
145 * check items. They should be using
146 * '==' for on-the-wire RADIUS attributes,
147 * and probably ':=' for server
148 * configuration items.
149 */
150 for (vp = fr_cursor_init(&cursor, &entry->check); vp; vp = fr_cursor_next(&cursor)) {
151 /*
152 * Ignore attributes which are set
153 * properly.
154 */
155 if (vp->op != T_OP_EQ) {
156 continue;
157 }
158
159 /*
160 * If it's a vendor attribute,
161 * or it's a wire protocol,
162 * ensure it has '=='.
163 */
164 if ((vp->da->vendor != 0) ||
165 (vp->da->attr < 0x100)) {
166 if (!compat_mode) {
167 WARN("[%s]:%d Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
168 filename, entry->lineno,
169 vp->da->name, vp->da->name,
170 entry->name);
171 } else {
172 DEBUG("\tChanging '%s =' to '%s =='",
173 vp->da->name, vp->da->name);
174 }
175 vp->op = T_OP_CMP_EQ;
176 continue;
177 }
178
179 /*
180 * Cistron Compatibility mode.
181 *
182 * Re-write selected attributes
183 * to be '+=', instead of '='.
184 *
185 * All others get set to '=='
186 */
187 if (compat_mode) {
188 /*
189 * Non-wire attributes become +=
190 *
191 * On the write attributes
192 * become ==
193 */
194 if ((vp->da->attr >= 0x100) &&
195 (vp->da->attr <= 0xffff) &&
196 (vp->da->attr != PW_HINT) &&
197 (vp->da->attr != PW_HUNTGROUP_NAME)) {
198 DEBUG("\tChanging '%s =' to '%s +='", vp->da->name, vp->da->name);
199
200 vp->op = T_OP_ADD;
201 } else {
202 DEBUG("\tChanging '%s =' to '%s =='", vp->da->name, vp->da->name);
203
204 vp->op = T_OP_CMP_EQ;
205 }
206 }
207 } /* end of loop over check items */
208
209 /*
210 * Look for server configuration items
211 * in the reply list.
212 *
213 * It's a common enough mistake, that it's
214 * worth doing.
215 */
216 for (vp = fr_cursor_init(&cursor, &entry->reply); vp; vp = fr_cursor_next(&cursor)) {
217 /*
218 * If it's NOT a vendor attribute,
219 * and it's NOT a wire protocol
220 * and we ignore Fall-Through,
221 * then bitch about it, giving a
222 * good warning message.
223 */
224 if ((vp->da->vendor == 0) &&
225 (vp->da->attr > 1000)) {
226 WARN("[%s]:%d Check item \"%s\"\n"
227 "\tfound in reply item list for user \"%s\".\n"
228 "\tThis attribute MUST go on the first line"
229 " with the other check items", filename, entry->lineno, vp->da->name,
230 entry->name);
231 }
232 }
233
234 entry = entry->next;
235 }
236 }
237
238 tree = rbtree_create(ctx, pairlist_cmp, NULL, RBTREE_FLAG_NONE);
239 if (!tree) {
240 pairlist_free(&users);
241 return -1;
242 }
243
244 default_list = NULL;
245 default_tail = &default_list;
246
247 /*
248 * We've read the entries in linearly, but putting them
249 * into an indexed data structure would be much faster.
250 * Let's go fix that now.
251 */
252 for (entry = users; entry != NULL; entry = next) {
253 /*
254 * Remove this entry from the input list.
255 */
256 next = entry->next;
257 entry->next = NULL;
258 (void) talloc_steal(tree, entry);
259
260 /*
261 * DEFAULT entries get their own list.
262 */
263 if (strcmp(entry->name, "DEFAULT") == 0) {
264 if (!default_list) {
265 default_list = entry;
266
267 /*
268 * Insert the first DEFAULT into the tree.
269 */
270 if (!rbtree_insert(tree, entry)) {
271 error:
272 pairlist_free(&entry);
273 pairlist_free(&next);
274 rbtree_free(tree);
275 return -1;
276 }
277
278 } else {
279 /*
280 * Tack this entry onto the tail
281 * of the DEFAULT list.
282 */
283 *default_tail = entry;
284 }
285
286 default_tail = &entry->next;
287 continue;
288 }
289
290 /*
291 * Not DEFAULT, must be a normal user.
292 */
293 user_list = rbtree_finddata(tree, entry);
294 if (!user_list) {
295 /*
296 * Insert the first one.
297 */
298 if (!rbtree_insert(tree, entry)) goto error;
299 } else {
300 /*
301 * Find the tail of this list, and add it
302 * there.
303 */
304 while (user_list->next) user_list = user_list->next;
305
306 user_list->next = entry;
307 }
308 }
309
310 *ptree = tree;
311
312 return 0;
313 }
314
315
316
317 /*
318 * (Re-)read the "users" file into memory.
319 */
mod_instantiate(UNUSED CONF_SECTION * conf,void * instance)320 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
321 {
322 rlm_files_t *inst = instance;
323
324 #undef READFILE
325 #define READFILE(_x, _y) do { if (getusersfile(inst, inst->_x, &inst->_y, inst->compat_mode) != 0) { ERROR("Failed reading %s", inst->_x); return -1;} } while (0)
326
327 READFILE(filename, common);
328 READFILE(usersfile, users);
329 READFILE(acctusersfile, acctusers);
330
331 #ifdef WITH_PROXY
332 READFILE(preproxy_usersfile, preproxy_users);
333 READFILE(postproxy_usersfile, postproxy_users);
334 #endif
335
336 READFILE(auth_usersfile, auth_users);
337 READFILE(postauth_usersfile, postauth_users);
338
339 return 0;
340 }
341
342 /*
343 * Common code called by everything below.
344 */
file_common(rlm_files_t * inst,REQUEST * request,char const * filename,rbtree_t * tree,RADIUS_PACKET * request_packet,RADIUS_PACKET * reply_packet)345 static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const *filename, rbtree_t *tree,
346 RADIUS_PACKET *request_packet, RADIUS_PACKET *reply_packet)
347 {
348 char const *name;
349 VALUE_PAIR *check_tmp;
350 VALUE_PAIR *reply_tmp;
351 PAIR_LIST const *user_pl, *default_pl;
352 bool found = false;
353 PAIR_LIST my_pl;
354 char buffer[256];
355
356 if (!inst->key) {
357 VALUE_PAIR *namepair;
358
359 namepair = request->username;
360 name = namepair ? namepair->vp_strvalue : "NONE";
361 } else {
362 int len;
363
364 len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL);
365 if (len < 0) {
366 return RLM_MODULE_FAIL;
367 }
368
369 name = len ? buffer : "NONE";
370 }
371
372 if (!tree) return RLM_MODULE_NOOP;
373
374 my_pl.name = name;
375 user_pl = rbtree_finddata(tree, &my_pl);
376 my_pl.name = "DEFAULT";
377 default_pl = rbtree_finddata(tree, &my_pl);
378
379 /*
380 * Find the entry for the user.
381 */
382 while (user_pl || default_pl) {
383 vp_cursor_t cursor;
384 VALUE_PAIR *vp;
385 PAIR_LIST const *pl;
386
387 /*
388 * Figure out which entry to match on.
389 */
390 if (!default_pl && user_pl) {
391 pl = user_pl;
392 user_pl = user_pl->next;
393
394 } else if (!user_pl && default_pl) {
395 pl = default_pl;
396 default_pl = default_pl->next;
397
398 } else if (user_pl->order < default_pl->order) {
399 pl = user_pl;
400 user_pl = user_pl->next;
401
402 } else {
403 pl = default_pl;
404 default_pl = default_pl->next;
405 }
406
407 check_tmp = fr_pair_list_copy(request, pl->check);
408 for (vp = fr_cursor_init(&cursor, &check_tmp);
409 vp;
410 vp = fr_cursor_next(&cursor)) {
411 if (radius_xlat_do(request, vp) < 0) {
412 RWARN("Failed parsing expanded value for check item, skipping entry: %s", fr_strerror());
413 fr_pair_list_free(&check_tmp);
414 continue;
415 }
416 }
417
418 if (paircompare(request, request_packet->vps, check_tmp, &reply_packet->vps) == 0) {
419 RDEBUG2("%s: Matched entry %s at line %d", filename, pl->name, pl->lineno);
420 found = true;
421
422 /* ctx may be reply or proxy */
423 reply_tmp = fr_pair_list_copy(reply_packet, pl->reply);
424 radius_pairmove(request, &reply_packet->vps, reply_tmp, true);
425 fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD);
426 fr_pair_list_free(&check_tmp);
427
428 /*
429 * Fallthrough?
430 */
431 if (!fall_through(pl->reply))
432 break;
433 }
434 }
435
436 /*
437 * Remove server internal parameters.
438 */
439 fr_pair_delete_by_num(&reply_packet->vps, PW_FALL_THROUGH, 0, TAG_ANY);
440
441 /*
442 * See if we succeeded.
443 */
444 if (!found)
445 return RLM_MODULE_NOOP; /* on to the next module */
446
447 return RLM_MODULE_OK;
448
449 }
450
451
452 /*
453 * Find the named user in the database. Create the
454 * set of attribute-value pairs to check and reply with
455 * for this user from the database. The main code only
456 * needs to check the password, the rest is done here.
457 */
CC_HINT(nonnull)458 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
459 {
460 rlm_files_t *inst = instance;
461
462 return file_common(inst, request, "users",
463 inst->users ? inst->users : inst->common,
464 request->packet, request->reply);
465 }
466
467
468 /*
469 * Pre-Accounting - read the acct_users file for check_items and
470 * config. Reply items are Not Recommended(TM) in acct_users,
471 * except for Fallthrough, which should work
472 */
CC_HINT(nonnull)473 static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request)
474 {
475 rlm_files_t *inst = instance;
476
477 return file_common(inst, request, "acct_users",
478 inst->acctusers ? inst->acctusers : inst->common,
479 request->packet, request->reply);
480 }
481
482 #ifdef WITH_PROXY
CC_HINT(nonnull)483 static rlm_rcode_t CC_HINT(nonnull) mod_pre_proxy(void *instance, REQUEST *request)
484 {
485 rlm_files_t *inst = instance;
486
487 return file_common(inst, request, "preproxy_users",
488 inst->preproxy_users ? inst->preproxy_users : inst->common,
489 request->packet, request->proxy);
490 }
491
CC_HINT(nonnull)492 static rlm_rcode_t CC_HINT(nonnull) mod_post_proxy(void *instance, REQUEST *request)
493 {
494 rlm_files_t *inst = instance;
495
496 return file_common(inst, request, "postproxy_users",
497 inst->postproxy_users ? inst->postproxy_users : inst->common,
498 request->proxy_reply, request->reply);
499 }
500 #endif
501
CC_HINT(nonnull)502 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
503 {
504 rlm_files_t *inst = instance;
505
506 return file_common(inst, request, "auth_users",
507 inst->auth_users ? inst->auth_users : inst->common,
508 request->packet, request->reply);
509 }
510
CC_HINT(nonnull)511 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
512 {
513 rlm_files_t *inst = instance;
514
515 return file_common(inst, request, "postauth_users",
516 inst->postauth_users ? inst->postauth_users : inst->common,
517 request->packet, request->reply);
518 }
519
520
521 /* globally exported name */
522 extern module_t rlm_files;
523 module_t rlm_files = {
524 .magic = RLM_MODULE_INIT,
525 .name = "files",
526 .type = RLM_TYPE_HUP_SAFE,
527 .inst_size = sizeof(rlm_files_t),
528 .config = module_config,
529 .instantiate = mod_instantiate,
530 .methods = {
531 [MOD_AUTHENTICATE] = mod_authenticate,
532 [MOD_AUTHORIZE] = mod_authorize,
533 [MOD_PREACCT] = mod_preacct,
534
535 #ifdef WITH_PROXY
536 [MOD_PRE_PROXY] = mod_pre_proxy,
537 [MOD_POST_PROXY] = mod_post_proxy,
538 #endif
539 [MOD_POST_AUTH] = mod_post_auth
540 },
541 };
542
543