/* * 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 realm_config_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_OK; } /* * Check for 'Realm' attribute. If it exists, then we've proxied * it already ( via another rlm_realm instance ) and should return. */ if (pairfind(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL ) { RDEBUG2("Request already proxied. Ignoring."); return RLM_MODULE_OK; } /* * We will be modifing this later, so we want our own copy * of it. */ namebuf = talloc_strdup(request, request->username->vp_strvalue); username = namebuf; switch(inst->format) { case REALM_FORMAT_SUFFIX: /* DEBUG2(" rlm_realm: 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: /* DEBUG2(" rlm_realm: 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); 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->striprealm) { /* * Create the Stripped-User-Name attribute, if it * doesn't exist. * */ if (request->username->da->attr != PW_STRIPPED_USER_NAME) { vp = radius_paircreate(request, &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); } pairstrcpy(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; pairmake_packet("Realm", realmname, T_OP_EQ); RDEBUG2("Adding Realm = \"%s\"", realmname); talloc_free(namebuf); realmname = 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_OK; /* don't do anything */ /* * Perhaps accounting proxying was turned off. */ case PW_ACCOUNTING_REQUEST: if (!realm->acct_pool) { RDEBUG2("Accounting realm is LOCAL."); return RLM_MODULE_OK; } break; /* * Perhaps authentication proxying was turned off. */ case PW_AUTHENTICATION_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_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 = pairfind(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.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 (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 ((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; }
/** Add a client to a RADCLIENT_LIST * * @param clients list to add client to, may be NULL if global client list is being used. * @param client to add. * @return true on success, false on failure. */ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) { RADCLIENT *old; char buffer[INET6_ADDRSTRLEN + 3]; if (!client) return false; /* * Hack to fixup wildcard clients * * If the IP is all zeros, with a 32 or 128 bit netmask * assume the user meant to configure 0.0.0.0/0 instead * of 0.0.0.0/32 - which would require the src IP of * the client to be all zeros. */ if (fr_inaddr_any(&client->ipaddr) == 1) switch (client->ipaddr.af) { case AF_INET: if (client->ipaddr.prefix == 32) client->ipaddr.prefix = 0; break; case AF_INET6: if (client->ipaddr.prefix == 128) client->ipaddr.prefix = 0; break; default: rad_assert(0); } fr_ntop(buffer, sizeof(buffer), &client->ipaddr); DEBUG3("Adding client %s (%s) to prefix tree %i", buffer, client->longname, client->ipaddr.prefix); /* * If the client also defines a server, do that now. */ if (client->defines_coa_server) if (!realm_home_server_add(client->coa_server)) return false; /* * If "clients" is NULL, it means add to the global list, * unless we're trying to add it to a virtual server... */ if (!clients) { if (client->server != NULL) { CONF_SECTION *cs; cs = cf_section_sub_find_name2(main_config.config, "server", client->server); if (!cs) { ERROR("Failed to find virtual server %s", client->server); return false; } /* * If the client list already exists, use that. * Otherwise, create a new client list. */ clients = cf_data_find(cs, "clients"); if (!clients) { clients = client_list_init(cs); if (!clients) { ERROR("Out of memory"); return false; } if (cf_data_add(cs, "clients", clients, (void (*)(void *)) client_list_free) < 0) { ERROR("Failed to associate clients with virtual server %s", client->server); client_list_free(clients); return false; } } } else { /* * Initialize the global list, if not done already. */ if (!root_clients) { root_clients = client_list_init(NULL); if (!root_clients) return false; } clients = root_clients; } } /* * Create a tree for it. */ if (!clients->trees[client->ipaddr.prefix]) { clients->trees[client->ipaddr.prefix] = rbtree_create(clients, client_ipaddr_cmp, NULL, 0); if (!clients->trees[client->ipaddr.prefix]) { return false; } } #define namecmp(a) ((!old->a && !client->a) || (old->a && client->a && (strcmp(old->a, client->a) == 0))) /* * Cannot insert the same client twice. */ old = rbtree_finddata(clients->trees[client->ipaddr.prefix], client); if (old) { /* * If it's a complete duplicate, then free the new * one, and return "OK". */ if ((fr_ipaddr_cmp(&old->ipaddr, &client->ipaddr) == 0) && (old->ipaddr.prefix == client->ipaddr.prefix) && namecmp(longname) && namecmp(secret) && namecmp(shortname) && namecmp(nas_type) && namecmp(login) && namecmp(password) && namecmp(server) && #ifdef WITH_DYNAMIC_CLIENTS (old->lifetime == client->lifetime) && namecmp(client_server) && #endif #ifdef WITH_COA namecmp(coa_name) && (old->coa_server == client->coa_server) && (old->coa_pool == client->coa_pool) && #endif (old->message_authenticator == client->message_authenticator)) { WARN("Ignoring duplicate client %s", client->longname); client_free(client); return true; } ERROR("Failed to add duplicate client %s", client->shortname); return false; } #undef namecmp /* * Other error adding client: likely is fatal. */ if (!rbtree_insert(clients->trees[client->ipaddr.prefix], client)) { return false; } #ifdef WITH_STATS if (!tree_num) { tree_num = rbtree_create(clients, client_num_cmp, NULL, 0); } #ifdef WITH_DYNAMIC_CLIENTS /* * More catching of clients added by rlm_sql. * * The sql modules sets the dynamic flag BEFORE calling * us. The client_afrom_request() function sets it AFTER * calling us. */ if (client->dynamic && (client->lifetime == 0)) { RADCLIENT *network; /* * If there IS an enclosing network, * inherit the lifetime from it. */ network = client_find(clients, &client->ipaddr, client->proto); if (network) { client->lifetime = network->lifetime; } } #endif client->number = tree_num_max; tree_num_max++; if (tree_num) rbtree_insert(tree_num, client); #endif if (client->ipaddr.prefix < clients->min_prefix) { clients->min_prefix = client->ipaddr.prefix; } (void) talloc_steal(clients, client); /* reparent it */ return true; }
/* * 1 == ID was allocated & assigned * 0 == couldn't allocate ID. * * Note that this ALSO assigns a socket to use, and updates * packet->request->src_ipaddr && packet->request->src_port * * In multi-threaded systems, the calls to id_alloc && id_free * should be protected by a mutex. This does NOT have to be * the same mutex as the one protecting the insert/find/yank * calls! * * We assume that the packet has dst_ipaddr && dst_port * already initialized. We will use those to find an * outgoing socket. The request MAY also have src_ipaddr set. * * We also assume that the sender doesn't care which protocol * should be used. */ bool fr_packet_list_id_alloc(fr_packet_list_t *pl, int proto, RADIUS_PACKET **request_p, void **pctx) { int i, fd, start_i; int src_any = 0; fr_packet_socket_t *ps; RADIUS_PACKET *request = *request_p; if ((request->dst_ipaddr.af == AF_UNSPEC) || (request->dst_port == 0)) { fr_strerror_printf("No destination address/port specified"); return false; } #ifndef WITH_TCP if ((proto != 0) && (proto != IPPROTO_UDP)) { fr_strerror_printf("Invalid destination protocol"); return false; } #endif /* * Special case: unspec == "don't care" */ if (request->src_ipaddr.af == AF_UNSPEC) { memset(&request->src_ipaddr, 0, sizeof(request->src_ipaddr)); request->src_ipaddr.af = request->dst_ipaddr.af; } src_any = fr_inaddr_any(&request->src_ipaddr); if (src_any < 0) { fr_strerror_printf("Can't check src_ipaddr"); return false; } /* * MUST specify a destination address. */ if (fr_inaddr_any(&request->dst_ipaddr) != 0) { fr_strerror_printf("Must specify a dst_ipaddr"); return false; } fd = -1; start_i = fr_rand() & SOCKOFFSET_MASK; /* * Pick a random socket which has a free ID */ #define ID_i ((i + start_i) & SOCKOFFSET_MASK) for (i = 0; i < MAX_SOCKETS; i++) { if (pl->sockets[ID_i].sockfd == -1) continue; /* paranoia */ ps = &(pl->sockets[ID_i]); /* * This socket is marked as "don't use for new * packets". But we can still receive packets * that are outstanding. */ if (ps->dont_use) continue; /* * All IDs are allocated: ignore it. */ if (ps->num_outgoing == 256) continue; #ifdef WITH_TCP if (ps->proto != proto) continue; #endif /* * Address families don't match, skip it. */ if (ps->src_ipaddr.af != request->dst_ipaddr.af) continue; /* * MUST match dst port, if we have one. */ if ((ps->dst_port != 0) && (ps->dst_port != request->dst_port)) continue; /* * MUST match requested src port, if one has been given. */ if ((request->src_port != 0) && (ps->src_port != request->src_port)) continue; /* * We're sourcing from *, and they asked for a * specific source address: ignore it. */ if (ps->src_any && !src_any) continue; /* * We're sourcing from a specific IP, and they * asked for a source IP that isn't us: ignore * it. */ if (!ps->src_any && !src_any && (fr_ipaddr_cmp(&request->src_ipaddr, &ps->src_ipaddr) != 0)) continue; /* * UDP sockets are allowed to match * destination IPs exactly, OR a socket * with destination * is allowed to match * any requested destination. * * TCP sockets must match the destination * exactly. They *always* have dst_any=0, * so the first check always matches. */ if (!ps->dst_any && (fr_ipaddr_cmp(&request->dst_ipaddr, &ps->dst_ipaddr) != 0)) continue; /* * We have a socket with less than 256 outgoing * connections. There MUST be an ID free. */ fd = i; break; } /* * Ask the caller to allocate a new ID. */ if (fd < 0) { fr_strerror_printf("Failed finding socket, caller must allocate a new one"); return false; } /* * Set the ID, source IP, and source port. */ request->id = ps->ids[ps->ids_index]; request->sockfd = ps->sockfd; request->src_ipaddr = ps->src_ipaddr; request->src_port = ps->src_port; /* * If we managed didn't insert it, undo the work above. */ if (!fr_packet_list_insert(pl, request_p)) { request->id = -1; request->sockfd = -1; request->src_ipaddr.af = AF_UNSPEC; request->src_port = 0; return false; } /* * We did insert it. Update the counters. */ if (pctx) *pctx = ps->ctx; ps->num_outgoing++; pl->num_outgoing++; ps->ids_index++; /* * Refill the ID array with random IDs. */ if (ps->ids_index == 256) { for (i = 0; i < 256; i++) { ps->ids[fr_rand() & 0xff] = i; } ps->ids_index = 0; } return true; }
/* * 1 == ID was allocated & assigned * 0 == error allocating memory * -1 == all ID's are used, caller should open a new socket. * * Note that this ALSO assigns a socket to use, and updates * packet->request->src_ipaddr && packet->request->src_port * * In multi-threaded systems, the calls to id_alloc && id_free * should be protected by a mutex. This does NOT have to be * the same mutex as the one protecting the insert/find/yank * calls! */ int fr_packet_list_id_alloc(fr_packet_list_t *pl, RADIUS_PACKET *request) { int i, id, start; int src_any = 0; uint32_t free_mask; fr_packet_dst2id_t my_pd, *pd; fr_packet_socket_t *ps; bzero(&my_pd,sizeof(my_pd)); if (!pl || !pl->alloc_id || !request) { fr_strerror_printf("Invalid arguments"); return 0; } /* * Error out if no destination is specified. */ if ((request->dst_ipaddr.af == AF_UNSPEC) || (request->dst_port == 0)) { fr_strerror_printf("No destination address/port specified"); return 0; } /* * Special case: unspec == "don't care" */ if (request->src_ipaddr.af == AF_UNSPEC) { memset(&request->src_ipaddr, 0, sizeof(request->src_ipaddr)); request->src_ipaddr.af = request->dst_ipaddr.af; } src_any = fr_inaddr_any(&request->src_ipaddr); if (src_any < 0) { fr_strerror_printf("Error checking src IP address"); return 0; } /* * MUST specify a destination address. */ if (fr_inaddr_any(&request->dst_ipaddr) != 0) { fr_strerror_printf("Error checking dst IP address"); return 0; } my_pd.dst_ipaddr = request->dst_ipaddr; my_pd.dst_port = request->dst_port; pd = fr_hash_table_finddata(pl->dst2id_ht, &my_pd); if (!pd) { pd = malloc(sizeof(*pd) + 255 * sizeof(pd->id[0])); if (!pd) return 0; memset(pd, 0, sizeof(*pd) + 255 * sizeof(pd->id[0])); pd->dst_ipaddr = request->dst_ipaddr; pd->dst_port = request->dst_port; if (!fr_hash_table_insert(pl->dst2id_ht, pd)) { free(pd); fr_strerror_printf("Failed inserting into hash"); return 0; } } /* * FIXME: Go to an LRU system. This prevents ID re-use * for as long as possible. The main problem with that * approach is that it requires us to populate the * LRU/FIFO when we add a new socket, or a new destination, * which can be expensive. * * The LRU can be avoided if the caller takes care to free * Id's only when all responses have been received, OR after * a timeout. */ id = start = (int) fr_rand() & 0xff; while (pd->id[id] == pl->mask) { /* all sockets are using this ID */ id++; id &= 0xff; if (id == start) { fr_strerror_printf("All IDs are being used"); return 0; } } free_mask = ~((~pd->id[id]) & pl->mask); start = -1; for (i = 0; i < MAX_SOCKETS; i++) { if (pl->sockets[i].sockfd == -1) continue; /* paranoia */ ps = &(pl->sockets[i]); /* * Address families don't match, skip it. */ if (ps->ipaddr.af != request->dst_ipaddr.af) continue; /* * We're sourcing from *, and they asked for a * specific source address: ignore it. */ if (ps->inaddr_any && !src_any) continue; /* * We're sourcing from a specific IP, and they * asked for a source IP that isn't us: ignore * it. */ if (!ps->inaddr_any && !src_any && (fr_ipaddr_cmp(&request->src_ipaddr, &ps->ipaddr) != 0)) continue; if ((free_mask & (1 << i)) == 0) { start = i; break; } } if (start < 0) { fr_strerror_printf("Internal sanity check failed"); return 0; /* bad error */ } pd->id[id] |= (1 << start); ps = &pl->sockets[start]; pd->num_outgoing++; ps->num_outgoing++; pl->num_outgoing++; /* * Set the ID, source IP, and source port. */ request->id = id; request->sockfd = ps->sockfd; request->src_ipaddr = ps->ipaddr; request->src_port = ps->port; return 1; }
bool fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd, int proto, fr_ipaddr_t *dst_ipaddr, int dst_port, void *ctx) { int i, start; struct sockaddr_storage src; socklen_t sizeof_src; fr_packet_socket_t *ps; if (!pl || !dst_ipaddr || (dst_ipaddr->af == AF_UNSPEC)) { fr_strerror_printf("Invalid argument"); return false; } if (pl->num_sockets >= MAX_SOCKETS) { fr_strerror_printf("Too many open sockets"); return false; } #ifndef WITH_TCP if (proto != IPPROTO_UDP) { fr_strerror_printf("only UDP is supported"); return false; } #endif ps = NULL; i = start = SOCK2OFFSET(sockfd); do { if (pl->sockets[i].sockfd == -1) { ps = &pl->sockets[i]; break; } i = (i + 1) & SOCKOFFSET_MASK; } while (i != start); if (!ps) { fr_strerror_printf("All socket entries are full"); return false; } memset(ps, 0, sizeof(*ps)); ps->ctx = ctx; #ifdef WITH_TCP ps->proto = proto; #endif /* * Get address family, etc. first, so we know if we * need to do udpfromto. * * FIXME: udpfromto also does this, but it's not * a critical problem. */ sizeof_src = sizeof(src); memset(&src, 0, sizeof_src); if (getsockname(sockfd, (struct sockaddr *) &src, &sizeof_src) < 0) { fr_strerror_printf("%s", fr_syserror(errno)); return false; } if (!fr_sockaddr2ipaddr(&src, sizeof_src, &ps->src_ipaddr, &ps->src_port)) { fr_strerror_printf("Failed to get IP"); return false; } ps->dst_ipaddr = *dst_ipaddr; ps->dst_port = dst_port; ps->src_any = fr_inaddr_any(&ps->src_ipaddr); if (ps->src_any < 0) return false; ps->dst_any = fr_inaddr_any(&ps->dst_ipaddr); if (ps->dst_any < 0) return false; /* * As the last step before returning. */ ps->sockfd = sockfd; pl->num_sockets++; /* * Populate the ID array with a random list of IDs. */ ps->ids_index = 0; for (i = 0; i < 256; i++) { ps->ids[fr_rand() & 0xff] = i; } return true; }