summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_files/rlm_files.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_files/rlm_files.c')
-rw-r--r--src/modules/rlm_files/rlm_files.c550
1 files changed, 550 insertions, 0 deletions
diff --git a/src/modules/rlm_files/rlm_files.c b/src/modules/rlm_files/rlm_files.c
new file mode 100644
index 0000000..08679e6
--- /dev/null
+++ b/src/modules/rlm_files/rlm_files.c
@@ -0,0 +1,550 @@
+/*
+ * This program is is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file rlm_files.c
+ * @brief Process simple 'users' policy files.
+ *
+ * @copyright 2000,2006 The FreeRADIUS server project
+ * @copyright 2000 Jeff Carneal <jeff@apex.net>
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+
+typedef struct rlm_files_t {
+ char const *compat_mode;
+
+ char const *key;
+
+ char const *filename;
+ rbtree_t *common;
+
+ /* autz */
+ char const *usersfile;
+ rbtree_t *users;
+
+
+ /* authenticate */
+ char const *auth_usersfile;
+ rbtree_t *auth_users;
+
+ /* preacct */
+ char const *acctusersfile;
+ rbtree_t *acctusers;
+
+#ifdef WITH_PROXY
+ /* pre-proxy */
+ char const *preproxy_usersfile;
+ rbtree_t *preproxy_users;
+
+ /* post-proxy */
+ char const *postproxy_usersfile;
+ rbtree_t *postproxy_users;
+#endif
+
+ /* post-authenticate */
+ char const *postauth_usersfile;
+ rbtree_t *postauth_users;
+} rlm_files_t;
+
+
+/*
+ * See if a VALUE_PAIR list contains Fall-Through = Yes
+ */
+static int fall_through(VALUE_PAIR *vp)
+{
+ VALUE_PAIR *tmp;
+ tmp = fr_pair_find_by_num(vp, PW_FALL_THROUGH, 0, TAG_ANY);
+
+ return tmp ? tmp->vp_integer : 0;
+}
+
+static const CONF_PARSER module_config[] = {
+ { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, filename), NULL },
+ { "usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, usersfile), NULL },
+ { "acctusersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, acctusersfile), NULL },
+#ifdef WITH_PROXY
+ { "preproxy_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, preproxy_usersfile), NULL },
+ { "postproxy_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, postproxy_usersfile), NULL },
+#endif
+ { "auth_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, auth_usersfile), NULL },
+ { "postauth_usersfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_files_t, postauth_usersfile), NULL },
+ { "compat", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_files_t, compat_mode), NULL },
+ { "key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_files_t, key), NULL },
+ CONF_PARSER_TERMINATOR
+};
+
+
+static int pairlist_cmp(void const *a, void const *b)
+{
+ return strcmp(((PAIR_LIST const *)a)->name,
+ ((PAIR_LIST const *)b)->name);
+}
+
+static int getusersfile(TALLOC_CTX *ctx, char const *filename, rbtree_t **ptree, char const *compat_mode_str)
+{
+ int rcode;
+ PAIR_LIST *users = NULL;
+ PAIR_LIST *entry, *next;
+ PAIR_LIST *user_list, *default_list, **default_tail;
+ rbtree_t *tree;
+
+ if (!filename) {
+ *ptree = NULL;
+ return 0;
+ }
+
+ rcode = pairlist_read(ctx, filename, &users, 1);
+ if (rcode < 0) {
+ return -1;
+ }
+
+ /*
+ * Walk through the 'users' file list, if we're debugging,
+ * or if we're in compat_mode.
+ */
+ if ((rad_debug_lvl) ||
+ (compat_mode_str && (strcmp(compat_mode_str, "cistron") == 0))) {
+ VALUE_PAIR *vp;
+ bool compat_mode = false;
+
+ if (compat_mode_str && (strcmp(compat_mode_str, "cistron") == 0)) {
+ compat_mode = true;
+ }
+
+ entry = users;
+ while (entry) {
+ vp_cursor_t cursor;
+ if (compat_mode) {
+ DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
+ filename, entry->lineno,
+ entry->name);
+ }
+
+ /*
+ * Look for improper use of '=' in the
+ * check items. They should be using
+ * '==' for on-the-wire RADIUS attributes,
+ * and probably ':=' for server
+ * configuration items.
+ */
+ for (vp = fr_cursor_init(&cursor, &entry->check); vp; vp = fr_cursor_next(&cursor)) {
+ /*
+ * Ignore attributes which are set
+ * properly.
+ */
+ if (vp->op != T_OP_EQ) {
+ continue;
+ }
+
+ /*
+ * If it's a vendor attribute,
+ * or it's a wire protocol,
+ * ensure it has '=='.
+ */
+ if ((vp->da->vendor != 0) ||
+ (vp->da->attr < 0x100)) {
+ if (!compat_mode) {
+ WARN("[%s]:%d Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
+ filename, entry->lineno,
+ vp->da->name, vp->da->name,
+ entry->name);
+ } else {
+ DEBUG("\tChanging '%s =' to '%s =='",
+ vp->da->name, vp->da->name);
+ }
+ vp->op = T_OP_CMP_EQ;
+ continue;
+ }
+
+ /*
+ * Cistron Compatibility mode.
+ *
+ * Re-write selected attributes
+ * to be '+=', instead of '='.
+ *
+ * All others get set to '=='
+ */
+ if (compat_mode) {
+ /*
+ * Non-wire attributes become +=
+ *
+ * On the write attributes
+ * become ==
+ */
+ if ((vp->da->attr >= 0x100) &&
+ (vp->da->attr <= 0xffff) &&
+ (vp->da->attr != PW_HINT) &&
+ (vp->da->attr != PW_HUNTGROUP_NAME)) {
+ DEBUG("\tChanging '%s =' to '%s +='", vp->da->name, vp->da->name);
+
+ vp->op = T_OP_ADD;
+ } else {
+ DEBUG("\tChanging '%s =' to '%s =='", vp->da->name, vp->da->name);
+
+ vp->op = T_OP_CMP_EQ;
+ }
+ }
+ } /* end of loop over check items */
+
+ /*
+ * Look for server configuration items
+ * in the reply list.
+ *
+ * It's a common enough mistake, that it's
+ * worth doing.
+ */
+ for (vp = fr_cursor_init(&cursor, &entry->reply); vp; vp = fr_cursor_next(&cursor)) {
+ /*
+ * If it's NOT a vendor attribute,
+ * and it's NOT a wire protocol
+ * and we ignore Fall-Through,
+ * then bitch about it, giving a
+ * good warning message.
+ */
+ if ((vp->da->vendor == 0) &&
+ (vp->da->attr > 1000)) {
+ WARN("[%s]:%d Check item \"%s\"\n"
+ "\tfound in reply item list for user \"%s\".\n"
+ "\tThis attribute MUST go on the first line"
+ " with the other check items", filename, entry->lineno, vp->da->name,
+ entry->name);
+ }
+ }
+
+ entry = entry->next;
+ }
+ }
+
+ tree = rbtree_create(ctx, pairlist_cmp, NULL, RBTREE_FLAG_NONE);
+ if (!tree) {
+ pairlist_free(&users);
+ return -1;
+ }
+
+ default_list = NULL;
+ default_tail = &default_list;
+
+ /*
+ * We've read the entries in linearly, but putting them
+ * into an indexed data structure would be much faster.
+ * Let's go fix that now.
+ */
+ for (entry = users; entry != NULL; entry = next) {
+ /*
+ * Remove this entry from the input list.
+ */
+ next = entry->next;
+ entry->next = NULL;
+ (void) talloc_steal(tree, entry);
+
+ /*
+ * DEFAULT entries get their own list.
+ */
+ if (strcmp(entry->name, "DEFAULT") == 0) {
+ if (!default_list) {
+ default_list = entry;
+
+ /*
+ * Insert the first DEFAULT into the tree.
+ */
+ if (!rbtree_insert(tree, entry)) {
+ error:
+ pairlist_free(&entry);
+ pairlist_free(&next);
+ rbtree_free(tree);
+ return -1;
+ }
+
+ } else {
+ /*
+ * Tack this entry onto the tail
+ * of the DEFAULT list.
+ */
+ *default_tail = entry;
+ }
+
+ default_tail = &entry->next;
+ continue;
+ }
+
+ /*
+ * Not DEFAULT, must be a normal user.
+ */
+ user_list = rbtree_finddata(tree, entry);
+ if (!user_list) {
+ /*
+ * Insert the first one.
+ */
+ if (!rbtree_insert(tree, entry)) goto error;
+ } else {
+ /*
+ * Find the tail of this list, and add it
+ * there.
+ */
+ while (user_list->next) user_list = user_list->next;
+
+ user_list->next = entry;
+ }
+ }
+
+ *ptree = tree;
+
+ return 0;
+}
+
+
+
+/*
+ * (Re-)read the "users" file into memory.
+ */
+static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
+{
+ rlm_files_t *inst = instance;
+
+#undef READFILE
+#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)
+
+ READFILE(filename, common);
+ READFILE(usersfile, users);
+ READFILE(acctusersfile, acctusers);
+
+#ifdef WITH_PROXY
+ READFILE(preproxy_usersfile, preproxy_users);
+ READFILE(postproxy_usersfile, postproxy_users);
+#endif
+
+ READFILE(auth_usersfile, auth_users);
+ READFILE(postauth_usersfile, postauth_users);
+
+ return 0;
+}
+
+/*
+ * Common code called by everything below.
+ */
+static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const *filename, rbtree_t *tree,
+ RADIUS_PACKET *request_packet, RADIUS_PACKET *reply_packet)
+{
+ char const *name;
+ VALUE_PAIR *check_tmp = NULL;
+ VALUE_PAIR *reply_tmp = NULL;
+ PAIR_LIST const *user_pl, *default_pl;
+ bool found = false;
+ PAIR_LIST my_pl;
+ char buffer[256];
+
+ /*
+ * Certain post-proxy fail situations can cause there not to be
+ * a valid request_packet to lookup check pairs in.
+ * Test here in case there are other situations where this happens.
+ */
+ if (!request_packet) return RLM_MODULE_NOOP;
+
+ if (!inst->key) {
+ VALUE_PAIR *namepair;
+
+ namepair = request->username;
+ name = namepair ? namepair->vp_strvalue : "NONE";
+ } else {
+ int len;
+
+ len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL);
+ if (len < 0) {
+ return RLM_MODULE_FAIL;
+ }
+
+ name = len ? buffer : "NONE";
+ }
+
+ if (!tree) return RLM_MODULE_NOOP;
+
+ my_pl.name = name;
+ user_pl = rbtree_finddata(tree, &my_pl);
+ my_pl.name = "DEFAULT";
+ default_pl = rbtree_finddata(tree, &my_pl);
+
+ /*
+ * Find the entry for the user.
+ */
+ while (user_pl || default_pl) {
+ vp_cursor_t cursor;
+ VALUE_PAIR *vp;
+ PAIR_LIST const *pl;
+
+ /*
+ * Figure out which entry to match on.
+ */
+ if (!default_pl && user_pl) {
+ pl = user_pl;
+ user_pl = user_pl->next;
+
+ } else if (!user_pl && default_pl) {
+ pl = default_pl;
+ default_pl = default_pl->next;
+
+ } else if (user_pl->order < default_pl->order) {
+ pl = user_pl;
+ user_pl = user_pl->next;
+
+ } else {
+ pl = default_pl;
+ default_pl = default_pl->next;
+ }
+
+ if (pl->check) {
+ check_tmp = fr_pair_list_copy(request, pl->check);
+ for (vp = fr_cursor_init(&cursor, &check_tmp);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ if (radius_xlat_do(request, vp) < 0) {
+ RWARN("Failed parsing expanded value for check item, skipping entry: %s", fr_strerror());
+ fr_pair_list_free(&check_tmp);
+ continue;
+ }
+ }
+ }
+
+ if (paircompare(request, request_packet->vps, check_tmp, &reply_packet->vps) == 0) {
+ RDEBUG2("%s: Matched entry %s at line %d", filename, pl->name, pl->lineno);
+ found = true;
+
+ /* ctx may be reply or proxy */
+ reply_tmp = fr_pair_list_copy(reply_packet, pl->reply);
+ if (reply_tmp) radius_pairmove(request, &reply_packet->vps, reply_tmp, true);
+
+ fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD);
+ fr_pair_list_free(&check_tmp);
+
+ /*
+ * Fallthrough?
+ */
+ if (!fall_through(pl->reply)) break;
+ }
+ }
+
+ /*
+ * Remove server internal parameters.
+ */
+ fr_pair_delete_by_num(&reply_packet->vps, PW_FALL_THROUGH, 0, TAG_ANY);
+
+ /*
+ * See if we succeeded.
+ */
+ if (!found) return RLM_MODULE_NOOP; /* on to the next module */
+
+ return RLM_MODULE_OK;
+
+}
+
+
+/*
+ * Find the named user in the database. Create the
+ * set of attribute-value pairs to check and reply with
+ * for this user from the database. The main code only
+ * needs to check the password, the rest is done here.
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
+{
+ rlm_files_t *inst = instance;
+
+ return file_common(inst, request, "users",
+ inst->users ? inst->users : inst->common,
+ request->packet, request->reply);
+}
+
+
+/*
+ * Pre-Accounting - read the acct_users file for check_items and
+ * config. Reply items are Not Recommended(TM) in acct_users,
+ * except for Fallthrough, which should work
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request)
+{
+ rlm_files_t *inst = instance;
+
+ return file_common(inst, request, "acct_users",
+ inst->acctusers ? inst->acctusers : inst->common,
+ request->packet, request->reply);
+}
+
+#ifdef WITH_PROXY
+static rlm_rcode_t CC_HINT(nonnull) mod_pre_proxy(void *instance, REQUEST *request)
+{
+ rlm_files_t *inst = instance;
+
+ return file_common(inst, request, "preproxy_users",
+ inst->preproxy_users ? inst->preproxy_users : inst->common,
+ request->packet, request->proxy);
+}
+
+static rlm_rcode_t CC_HINT(nonnull) mod_post_proxy(void *instance, REQUEST *request)
+{
+ rlm_files_t *inst = instance;
+
+ return file_common(inst, request, "postproxy_users",
+ inst->postproxy_users ? inst->postproxy_users : inst->common,
+ request->proxy_reply, request->reply);
+}
+#endif
+
+static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
+{
+ rlm_files_t *inst = instance;
+
+ return file_common(inst, request, "auth_users",
+ inst->auth_users ? inst->auth_users : inst->common,
+ request->packet, request->reply);
+}
+
+static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
+{
+ rlm_files_t *inst = instance;
+
+ return file_common(inst, request, "postauth_users",
+ inst->postauth_users ? inst->postauth_users : inst->common,
+ request->packet, request->reply);
+}
+
+
+/* globally exported name */
+extern module_t rlm_files;
+module_t rlm_files = {
+ .magic = RLM_MODULE_INIT,
+ .name = "files",
+ .type = RLM_TYPE_HUP_SAFE,
+ .inst_size = sizeof(rlm_files_t),
+ .config = module_config,
+ .instantiate = mod_instantiate,
+ .methods = {
+ [MOD_AUTHENTICATE] = mod_authenticate,
+ [MOD_AUTHORIZE] = mod_authorize,
+ [MOD_PREACCT] = mod_preacct,
+
+#ifdef WITH_PROXY
+ [MOD_PRE_PROXY] = mod_pre_proxy,
+ [MOD_POST_PROXY] = mod_post_proxy,
+#endif
+ [MOD_POST_AUTH] = mod_post_auth
+ },
+};
+