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