/* * 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_realm.c * @brief Parses NAIs and assigns requests to realms. * * @copyright 2000-2013 The FreeRADIUS server project */ RCSID("$Id$") #include #include #include "trustrouter.h" #define REALM_FORMAT_PREFIX 0 #define REALM_FORMAT_SUFFIX 1 typedef struct rlm_realm_t { int format; char const *format_string; char const *delim; bool ignore_default; bool ignore_null; #ifdef HAVE_TRUST_ROUTER_TR_DH_H char const *default_community; char const *rp_realm; char const *trust_router; uint32_t tr_port; bool rekey_enabled; uint32_t realm_lifetime; #endif } rlm_realm_t; static CONF_PARSER module_config[] = { { "format", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t, format_string), "suffix" }, { "delimiter", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t, delim), "@" }, { "ignore_default", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_realm_t, ignore_default), "no" }, { "ignore_null", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_realm_t, ignore_null), "no" }, #ifdef HAVE_TRUST_ROUTER_TR_DH_H { "default_community", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t, default_community), "none" }, { "rp_realm", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t, rp_realm), "none" }, { "trust_router", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t, trust_router), "none" }, { "tr_port", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_realm_t, tr_port), "0" }, { "rekey_enabled", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_realm_t, rekey_enabled), "no" }, { "realm_lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_realm_t, realm_lifetime), "0" }, #endif CONF_PARSER_TERMINATOR }; /* * Internal function to cut down on duplicated code. * * Returns -1 on failure, 0 on no failure. returnrealm * is NULL on don't proxy, realm otherwise. */ static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm) { char *namebuf; char *username; char const *realmname = NULL; char *ptr; VALUE_PAIR *vp; REALM *realm; struct rlm_realm_t *inst = instance; /* initiate returnrealm */ *returnrealm = NULL; /* * If the request has a proxy entry, then it's a proxy * reply, and we're walking through the module list again. * * In that case, don't bother trying to proxy the request * again. * * Also, if there's no User-Name attribute, we can't * proxy it, either. */ if ((!request->username) #ifdef WITH_PROXY || (request->proxy != NULL) #endif ) { RDEBUG2("Proxy reply, or no User-Name. Ignoring"); return RLM_MODULE_NOOP; } /* * Check for 'Realm' attribute. If it exists, then we've proxied * it already ( via another rlm_realm instance ) and should return. */ if (fr_pair_find_by_num(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL ) { RDEBUG2("Request already has destination realm set. Ignoring"); return RLM_MODULE_NOOP; } /* * We will be modifing this later, so we want our own copy * of it. */ namebuf = talloc_typed_strdup(request, request->username->vp_strvalue); username = namebuf; switch (inst->format) { case REALM_FORMAT_SUFFIX: RDEBUG2("Checking for suffix after \"%c\"", inst->delim[0]); ptr = strrchr(username, inst->delim[0]); if (ptr) { *ptr = '\0'; realmname = ptr + 1; } break; case REALM_FORMAT_PREFIX: RDEBUG2("Checking for prefix before \"%c\"", inst->delim[0]); ptr = strchr(username, inst->delim[0]); if (ptr) { *ptr = '\0'; ptr++; realmname = username; username = ptr; } break; default: realmname = NULL; break; } /* * Print out excruciatingly descriptive debugging messages * for the people who find it too difficult to think about * what's going on. */ if (realmname) { RDEBUG2("Looking up realm \"%s\" for User-Name = \"%s\"", realmname, request->username->vp_strvalue); } else { if (inst->ignore_null ) { RDEBUG2("No '%c' in User-Name = \"%s\", skipping NULL due to config.", inst->delim[0], request->username->vp_strvalue); talloc_free(namebuf); return RLM_MODULE_NOOP; } RDEBUG2("No '%c' in User-Name = \"%s\", looking up realm NULL", inst->delim[0], request->username->vp_strvalue); } /* * Allow DEFAULT realms unless told not to. */ realm = realm_find(realmname); #ifdef HAVE_TRUST_ROUTER_TR_DH_H /* * Try querying for the dynamic realm. */ if (!realm) { if (inst->trust_router) { realm = tr_query_realm(request, realmname, inst->default_community, inst->rp_realm, inst->trust_router, inst->tr_port); } else { RDEBUG2("No trust router configured, skipping dynamic realm lookup"); } } #endif if (!realm) { RDEBUG2("No such realm \"%s\"", (!realmname) ? "NULL" : realmname); talloc_free(namebuf); return RLM_MODULE_NOOP; } if (inst->ignore_default && (strcmp(realm->name, "DEFAULT")) == 0) { RDEBUG2("Found DEFAULT, but skipping due to config"); talloc_free(namebuf); return RLM_MODULE_NOOP; } RDEBUG2("Found realm \"%s\"", realm->name); /* * If we've been told to strip the realm off, then do so. */ if (realm->strip_realm) { /* * Create the Stripped-User-Name attribute, if it * doesn't exist. * */ if (request->username->da->attr != PW_STRIPPED_USER_NAME) { vp = radius_pair_create(request->packet, &request->packet->vps, PW_STRIPPED_USER_NAME, 0); RDEBUG2("Adding Stripped-User-Name = \"%s\"", username); } else { vp = request->username; RDEBUG2("Setting Stripped-User-Name = \"%s\"", username); } fr_pair_value_strcpy(vp, username); request->username = vp; } /* * Add the realm name to the request. * If the realm is a regex, the use the realm as entered * by the user. Otherwise, use the configured realm name, * as realm name comparison is case insensitive. We want * to use the configured name, rather than what the user * entered. */ if (realm->name[0] != '~') realmname = realm->name; /* * A NULL realmname is allowed. */ if (realmname) { pair_make_request("Realm", realmname, T_OP_EQ); RDEBUG2("Adding Realm = \"%s\"", realmname); } talloc_free(namebuf); username = NULL; /* * Figure out what to do with the request. */ switch (request->packet->code) { default: RDEBUG2("Unknown packet code %d\n", request->packet->code); return RLM_MODULE_NOOP; /* * Perhaps accounting proxying was turned off. */ case PW_CODE_ACCOUNTING_REQUEST: if (!realm->acct_pool) { RDEBUG2("Accounting realm is LOCAL"); return RLM_MODULE_OK; } break; /* * Perhaps authentication proxying was turned off. */ case PW_CODE_ACCESS_REQUEST: if (!realm->auth_pool) { RDEBUG2("Authentication realm is LOCAL"); return RLM_MODULE_OK; } break; } #ifdef WITH_PROXY RDEBUG2("Proxying request from user %s to realm %s", request->username->vp_strvalue, realm->name); /* * Skip additional checks if it's not an accounting * request. */ if (request->packet->code != PW_CODE_ACCOUNTING_REQUEST) { *returnrealm = realm; return RLM_MODULE_UPDATED; } /* * FIXME: Each server should have a unique server key, * and put it in the accounting packet. Every server * should know about the keys, and NOT proxy requests to * a server with key X if the packet already contains key * X. */ /* * If this request has arrived from another freeradius server * that has already proxied the request, we don't need to do * it again. */ vp = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_PROXIED_TO, 0, TAG_ANY); if (vp && (request->packet->src_ipaddr.af == AF_INET)) { int i; fr_ipaddr_t my_ipaddr; my_ipaddr.af = AF_INET; my_ipaddr.prefix = 32; my_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; /* * Loop over the home accounting servers for this * realm. If one of them has the same IP as the * FreeRADIUS-Proxied-To attribute, then the * packet has already been sent there. Don't * send it there again. */ for (i = 0; i < realm->acct_pool->num_home_servers; i++) { if (realm->acct_pool->servers[i]->ipaddr.af == AF_UNSPEC) continue; if (fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &my_ipaddr) == 0) { RDEBUG2("Suppressing proxy due to FreeRADIUS-Proxied-To"); return RLM_MODULE_OK; } } /* * See detail_recv() in src/main/listen.c for the * additional checks. */ #ifdef WITH_DETAIL } else if ((request->listener->type == RAD_LISTEN_DETAIL) && !fr_inaddr_any(&request->packet->src_ipaddr)) { int i; /* * Loop over the home accounting servers for this * realm. If one of them has the same IP as the * FreeRADIUS-Proxied-To attribute, then the * packet has already been sent there. Don't * send it there again. */ for (i = 0; i < realm->acct_pool->num_home_servers; i++) { if (realm->acct_pool->servers[i]->ipaddr.af == AF_UNSPEC) continue; if ((fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &request->packet->src_ipaddr) == 0) && (realm->acct_pool->servers[i]->port == request->packet->src_port)) { RDEBUG2("Suppressing proxy because packet was already sent to a server in that realm"); return RLM_MODULE_OK; } } #endif /* WITH_DETAIL */ } #endif /* WITH_PROXY */ /* * We got this far, which means we have a realm, set returnrealm */ *returnrealm = realm; return RLM_MODULE_UPDATED; } /* * Perform the realm module instantiation. Configuration info is * stored in *instance for later use. */ static int mod_instantiate(CONF_SECTION *conf, void *instance) { struct rlm_realm_t *inst = instance; if (strcasecmp(inst->format_string, "suffix") == 0) { inst->format = REALM_FORMAT_SUFFIX; } else if (strcasecmp(inst->format_string, "prefix") == 0) { inst->format = REALM_FORMAT_PREFIX; } else { cf_log_err_cs(conf, "Invalid value \"%s\" for format", inst->format_string); return -1; } if (cf_new_escape && (strcmp(inst->delim, "\\\\") == 0)) { /* it's OK */ } else if (strlen(inst->delim) != 1) { cf_log_err_cs(conf, "Invalid value \"%s\" for delimiter", inst->delim); return -1; } #ifdef HAVE_TRUST_ROUTER_TR_DH_H /* initialize the trust router integration code */ if (strcmp(inst->trust_router, "none") != 0) { if (!tr_init(inst->rekey_enabled, inst->realm_lifetime)) return -1; } else { rad_const_free(inst->trust_router); inst->trust_router = NULL; } #endif return 0; } /* * Examine a request for a username with an realm, and if it * corresponds to something in the realms file, set that realm as * Proxy-To. * * This should very nearly duplicate the old proxy_send() code */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_rcode_t rcode; REALM *realm; /* * Check if we've got to proxy the request. * If not, return without adding a Proxy-To-Realm * attribute. */ rcode = check_for_realm(instance, request, &realm); if (rcode != RLM_MODULE_UPDATED) return rcode; if (!realm) return RLM_MODULE_NOOP; /* * Maybe add a Proxy-To-Realm attribute to the request. */ RDEBUG2("Preparing to proxy authentication request to realm \"%s\"\n", realm->name); pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ); return RLM_MODULE_UPDATED; /* try the next module */ } /* * This does the exact same thing as the mod_authorize, it's just called * differently. */ static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request) { int rcode; REALM *realm; if (!request->username) { return RLM_MODULE_NOOP; } /* * Check if we've got to proxy the request. * If not, return without adding a Proxy-To-Realm * attribute. */ rcode = check_for_realm(instance, request, &realm); if (rcode != RLM_MODULE_UPDATED) return rcode; if (!realm) return RLM_MODULE_NOOP; /* * Maybe add a Proxy-To-Realm attribute to the request. */ RDEBUG2("Preparing to proxy accounting request to realm \"%s\"\n", realm->name); pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ); return RLM_MODULE_UPDATED; /* try the next module */ } #ifdef WITH_COA /* * CoA realms via Operator-Name. Because the realm isn't in a * User-Name, concepts like "prefix" and "suffix' don't matter. */ static rlm_rcode_t mod_realm_recv_coa(UNUSED void *instance, REQUEST *request) { VALUE_PAIR *vp; REALM *realm; if (fr_pair_find_by_num(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL) { RDEBUG2("Request already has destination realm set. Ignoring"); return RLM_MODULE_NOOP; } vp = fr_pair_find_by_num(request->packet->vps, PW_OPERATOR_NAME, 0, TAG_ANY); if (!vp) return RLM_MODULE_NOOP; /* * Catch the case of broken dictionaries. */ if (vp->da->type != PW_TYPE_STRING) return RLM_MODULE_NOOP; /* * The string is too short. */ if (vp->vp_length == 1) return RLM_MODULE_NOOP; /* * '1' means "the rest of the string is a realm" */ if (vp->vp_strvalue[0] != '1') return RLM_MODULE_NOOP; realm = realm_find(vp->vp_strvalue + 1); if (!realm) return RLM_MODULE_NOTFOUND; if (!realm->coa_pool) { RDEBUG2("CoA realm is LOCAL"); return RLM_MODULE_OK; } /* * Maybe add a Proxy-To-Realm attribute to the request. */ RDEBUG2("Preparing to proxy authentication request to realm \"%s\"\n", realm->name); pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ); return RLM_MODULE_UPDATED; /* try the next module */ } #endif /* globally exported name */ extern module_t rlm_realm; module_t rlm_realm = { .magic = RLM_MODULE_INIT, .name = "realm", .type = RLM_TYPE_HUP_SAFE, .inst_size = sizeof(struct rlm_realm_t), .config = module_config, .instantiate = mod_instantiate, .methods = { [MOD_AUTHORIZE] = mod_authorize, [MOD_PREACCT] = mod_preacct, #ifdef WITH_COA [MOD_RECV_COA] = mod_realm_recv_coa #endif }, };