6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2001 The FreeRADIUS server project
21 * Copyright 2002 Kostas Kalevras <kkalev@noc.ntua.gr>
23 * March 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
25 * April 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
26 * - Add support for the Pool-Name attribute
27 * May 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
28 * - Check the return value of a gdbm_fetch() we didn't check
29 * - Change the nas entry in the ippool_key structure from uint32 to string[64]
30 * That should allow us to also use the NAS-Identifier attribute
31 * Sep 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
32 * - Move from authorize to post-auth
33 * - Use mutex locks when accessing the gdbm files
34 * - Fail if we don't find nas port information
35 * Oct 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
36 * - Do a memset(0) on the key.nas before doing searches. Nusty bug
41 #include "libradius.h"
54 #include <netinet/in.h>
56 #ifdef NEEDS_GDBM_SYNC
57 # define GDBM_SYNCOPT GDBM_SYNC
59 # define GDBM_SYNCOPT 0
63 #define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
65 #define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT)
68 #define MAX_NAS_NAME_SIZE 64
70 static const char rcsid[] = "$Id$";
73 * Define a structure for our module configuration.
75 * These variables do not need to be in a structure, but it's
76 * a lot cleaner to do so, and a pointer to the structure can
77 * be used as the instance handle.
79 typedef struct rlm_ippool_t {
90 pthread_mutex_t session_mutex;
91 pthread_mutex_t ip_mutex;
94 typedef struct ippool_info {
100 typedef struct ippool_key {
101 char nas[MAX_NAS_NAME_SIZE];
106 * A mapping of configuration file names to internal variables.
108 * Note that the string is dynamically allocated, so it MUST
109 * be freed. When the configuration file parse re-reads the string,
110 * it free's the old one, and strdup's the new one, placing the pointer
111 * to the strdup'd string into 'config.string'. This gets around
114 static CONF_PARSER module_config[] = {
115 { "session-db", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,session_db), NULL, NULL },
116 { "ip-index", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,ip_index), NULL, NULL },
117 { "range-start", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_start), NULL, "0" },
118 { "range-stop", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_stop), NULL, "0" },
119 { "netmask", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,netmask), NULL, "0" },
120 { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,cache_size), NULL, "1000" },
121 { "override", PW_TYPE_BOOLEAN, offsetof(rlm_ippool_t,override), NULL, "no" },
122 { NULL, -1, 0, NULL, NULL }
127 * Do any per-module initialization that is separate to each
128 * configured instance of the module. e.g. set up connections
129 * to external databases, read configuration files, set up
130 * dictionary entries, etc.
132 * If configuration information is given in the config section
133 * that must be referenced in later calls, store a handle to it
134 * in *instance otherwise put a null pointer there.
136 static int ippool_instantiate(CONF_SECTION *conf, void **instance)
146 const char *cli = "0";
147 char *pool_name = NULL;
150 * Set up a storage area for instance data
152 data = rad_malloc(sizeof(*data));
156 memset(data, 0, sizeof(*data));
159 * If the configuration parameters can't be parsed, then
162 if (cf_section_parse(conf, data, module_config) < 0) {
166 cache_size = data->cache_size;
168 if (data->session_db == NULL) {
169 radlog(L_ERR, "rlm_ippool: 'session-db' must be set.");
173 if (data->ip_index == NULL) {
174 radlog(L_ERR, "rlm_ippool: 'ip-index' must be set.");
178 data->range_start = htonl(data->range_start);
179 data->range_stop = htonl(data->range_stop);
180 data->netmask = htonl(data->netmask);
181 if (data->range_start == 0 || data->range_stop == 0 || \
182 data->range_start >= data->range_stop ) {
183 radlog(L_ERR, "rlm_ippool: Invalid configuration data given.");
188 data->gdbm = gdbm_open(data->session_db, sizeof(int),
189 GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
190 if (data->gdbm == NULL) {
191 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
192 data->session_db, strerror(errno));
195 data->ip = gdbm_open(data->ip_index, sizeof(int),
196 GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
197 if (data->ip == NULL) {
198 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
199 data->ip_index, strerror(errno));
202 if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
203 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
204 if (gdbm_setopt(data->ip, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
205 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
207 key_datum = gdbm_firstkey(data->gdbm);
208 if (key_datum.dptr == NULL){
210 * If the database does not exist initialize it.
211 * We set the nas/port pairs to not existent values and
217 const char *nas_init = "NOT_EXIST";
219 DEBUG("rlm_ippool: Initializing database");
220 for(i=data->range_start,j=~0;i<=data->range_stop;i++,j--){
223 * Net and Broadcast addresses are excluded
225 or_result = i | data->netmask;
226 if (~data->netmask != 0 &&
227 (or_result == data->netmask ||
228 (~or_result == 0))) {
229 DEBUG("rlm_ippool: IP %s excluded",
230 ip_ntoa(str, ntohl(i)));
234 strcpy(key.nas, nas_init);
236 key_datum.dptr = (char *) &key;
237 key_datum.dsize = sizeof(ippool_key);
239 entry.ipaddr = ntohl(i);
241 strcpy(entry.cli,cli);
243 data_datum.dptr = (char *) &entry;
244 data_datum.dsize = sizeof(ippool_info);
246 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
248 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
249 data->session_db, gdbm_strerror(gdbm_errno));
251 gdbm_close(data->gdbm);
252 gdbm_close(data->ip);
258 free(key_datum.dptr);
260 /* Add the ip pool name */
262 pool_name = cf_section_name2(conf);
263 if (pool_name != NULL)
264 data->name = strdup(pool_name);
265 pthread_mutex_init(&data->session_mutex, NULL);
266 pthread_mutex_init(&data->ip_mutex, NULL);
275 * Check for an Accounting-Stop
276 * If we find one and we have allocated an IP to this nas/port combination, deallocate it.
278 static int ippool_accounting(void *instance, REQUEST *request)
280 rlm_ippool_t *data = (rlm_ippool_t *)instance;
283 int acctstatustype = 0;
284 unsigned int port = ~0;
286 char nas[MAX_NAS_NAME_SIZE];
294 if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL)
295 acctstatustype = vp->lvalue;
297 DEBUG("rlm_ippool: Could not find account status type in packet.");
298 return RLM_MODULE_NOOP;
300 switch(acctstatustype){
302 if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
305 DEBUG("rlm_ippool: Could not find port number in packet.");
306 return RLM_MODULE_NOOP;
308 if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
309 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
311 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
312 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
314 DEBUG("rlm_ippool: Could not find nas information in packet.");
315 return RLM_MODULE_NOOP;
320 /* We don't care about any other accounting packet */
322 return RLM_MODULE_NOOP;
325 memset(key.nas,0,MAX_NAS_NAME_SIZE);
326 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
328 DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%u",key.nas,key.port);
329 key_datum.dptr = (char *) &key;
330 key_datum.dsize = sizeof(ippool_key);
332 pthread_mutex_lock(&data->session_mutex);
333 data_datum = gdbm_fetch(data->gdbm, key_datum);
334 pthread_mutex_unlock(&data->session_mutex);
335 if (data_datum.dptr != NULL){
338 * If the entry was found set active to zero
340 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
341 free(data_datum.dptr);
342 DEBUG("rlm_ippool: Deallocated entry for ip/port: %s/%u",ip_ntoa(str,entry.ipaddr),port);
345 data_datum.dptr = (char *) &entry;
346 data_datum.dsize = sizeof(ippool_info);
348 pthread_mutex_lock(&data->session_mutex);
349 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
350 pthread_mutex_unlock(&data->session_mutex);
352 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
353 data->session_db, gdbm_strerror(gdbm_errno));
354 return RLM_MODULE_FAIL;
358 * Decrease allocated count from the ip index
360 key_datum.dptr = (char *) &entry.ipaddr;
361 key_datum.dsize = sizeof(uint32_t);
362 pthread_mutex_lock(&data->ip_mutex);
363 data_datum = gdbm_fetch(data->ip, key_datum);
364 pthread_mutex_unlock(&data->ip_mutex);
365 if (data_datum.dptr != NULL){
366 memcpy(&num, data_datum.dptr, sizeof(int));
367 free(data_datum.dptr);
370 DEBUG("rlm_ippool: num: %d",num);
371 data_datum.dptr = (char *) #
372 data_datum.dsize = sizeof(int);
373 pthread_mutex_lock(&data->ip_mutex);
374 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
375 pthread_mutex_unlock(&data->ip_mutex);
377 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
378 data->ip_index, gdbm_strerror(gdbm_errno));
379 return RLM_MODULE_FAIL;
385 DEBUG("rlm_ippool: Entry not found");
387 return RLM_MODULE_OK;
390 static int ippool_postauth(void *instance, REQUEST *request)
392 rlm_ippool_t *data = (rlm_ippool_t *) instance;
393 unsigned int port = 0;
397 char nas[MAX_NAS_NAME_SIZE];
408 /* quiet the compiler */
412 /* Check if Pool-Name attribute exists. If it exists check our name and
413 * run only if they match
415 if ((vp = pairfind(request->config_items, PW_POOL_NAME)) != NULL){
416 if (data->name == NULL || strcmp(data->name,vp->strvalue))
417 return RLM_MODULE_NOOP;
419 DEBUG("rlm_ippool: Could not find Pool-Name attribute.");
420 return RLM_MODULE_NOOP;
424 * Get the nas ip address
427 if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
428 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
430 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
431 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
433 DEBUG("rlm_ippool: Could not find nas information.");
434 return RLM_MODULE_NOOP;
441 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
448 if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
451 DEBUG("rlm_ippool: Could not find port information.");
452 return RLM_MODULE_NOOP;
455 memset(key.nas,0,MAX_NAS_NAME_SIZE);
456 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
458 DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%u",key.nas,key.port);
459 key_datum.dptr = (char *) &key;
460 key_datum.dsize = sizeof(ippool_key);
462 pthread_mutex_lock(&data->session_mutex);
463 data_datum = gdbm_fetch(data->gdbm, key_datum);
464 pthread_mutex_unlock(&data->session_mutex);
465 if (data_datum.dptr != NULL){
467 * If there is a corresponding entry in the database with active=1 it is stale.
470 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
471 free(data_datum.dptr);
473 DEBUG("rlm_ippool: Found a stale entry for ip/port: %s/%u",ip_ntoa(str,entry.ipaddr),port);
476 data_datum.dptr = (char *) &entry;
477 data_datum.dsize = sizeof(ippool_info);
479 pthread_mutex_lock(&data->session_mutex);
480 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
481 pthread_mutex_unlock(&data->session_mutex);
483 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
484 data->session_db, gdbm_strerror(gdbm_errno));
485 return RLM_MODULE_FAIL;
487 /* Decrease allocated count from the ip index */
489 key_datum.dptr = (char *) &entry.ipaddr;
490 key_datum.dsize = sizeof(uint32_t);
491 pthread_mutex_lock(&data->ip_mutex);
492 data_datum = gdbm_fetch(data->ip, key_datum);
493 pthread_mutex_unlock(&data->ip_mutex);
494 if (data_datum.dptr != NULL){
495 memcpy(&num, data_datum.dptr, sizeof(int));
496 free(data_datum.dptr);
499 DEBUG("rlm_ippool: num: %d",num);
500 data_datum.dptr = (char *) #
501 data_datum.dsize = sizeof(int);
502 pthread_mutex_lock(&data->ip_mutex);
503 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
504 pthread_mutex_unlock(&data->ip_mutex);
506 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
507 data->ip_index, gdbm_strerror(gdbm_errno));
508 return RLM_MODULE_FAIL;
515 * If there is a Framed-IP-Address attribute in the reply, check for override
517 if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS) != NULL) {
520 /* Override supplied Framed-IP-Address */
521 pairdelete(&request->reply->vps, PW_FRAMED_IP_ADDRESS);
524 return RLM_MODULE_NOOP;
529 * Walk through the database searching for an active=0 entry.
532 pthread_mutex_lock(&data->session_mutex);
533 key_datum = gdbm_firstkey(data->gdbm);
534 while(key_datum.dptr){
535 data_datum = gdbm_fetch(data->gdbm, key_datum);
536 if (data_datum.dptr){
537 memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
538 free(data_datum.dptr);
540 * If we find an entry for the same caller-id and nas with active=1
541 * then we use that for multilink (MPPP) to work properly.
543 if (cli != NULL && strcmp(entry.cli,cli) == 0 && entry.active){
544 memcpy(&key,key_datum.dptr,sizeof(ippool_key));
545 if (!strcmp(key.nas,nas))
548 if (entry.active == 0){
551 tmp.dptr = (char *) &entry.ipaddr;
552 tmp.dsize = sizeof(uint32_t);
553 pthread_mutex_lock(&data->ip_mutex);
554 data_datum = gdbm_fetch(data->ip, tmp);
555 pthread_mutex_unlock(&data->ip_mutex);
558 * If we find an entry in the ip index and the number is zero (meaning
559 * that we haven't allocated the same ip address to another nas/port pair)
560 * or if we don't find an entry then delete the session entry so
561 * that we can change the key (nas/port)
562 * Else we don't delete the session entry since we haven't yet deallocated the
563 * corresponding ip address and we continue our search.
566 if (data_datum.dptr){
567 memcpy(&num,data_datum.dptr, sizeof(int));
568 free(data_datum.dptr);
580 nextkey = gdbm_nextkey(data->gdbm, key_datum);
581 free(key_datum.dptr);
584 pthread_mutex_unlock(&data->session_mutex);
586 * If we have found a free entry set active to 1 then add a Framed-IP-Address attribute to
591 data_datum.dptr = (char *) &entry;
592 data_datum.dsize = sizeof(ippool_info);
596 * Delete the entry so that we can change the key
598 pthread_mutex_lock(&data->session_mutex);
599 gdbm_delete(data->gdbm, key_datum);
600 pthread_mutex_unlock(&data->session_mutex);
602 free(key_datum.dptr);
603 memset(key.nas,0,MAX_NAS_NAME_SIZE);
604 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE - 1);
606 key_datum.dptr = (char *) &key;
607 key_datum.dsize = sizeof(ippool_key);
609 DEBUG2("rlm_ippool: Allocating ip to nas/port: %s/%u",key.nas,key.port);
610 pthread_mutex_lock(&data->session_mutex);
611 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
612 pthread_mutex_unlock(&data->session_mutex);
614 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
615 data->session_db, gdbm_strerror(gdbm_errno));
616 return RLM_MODULE_FAIL;
619 /* Increase the ip index count */
620 key_datum.dptr = (char *) &entry.ipaddr;
621 key_datum.dsize = sizeof(uint32_t);
622 pthread_mutex_lock(&data->ip_mutex);
623 data_datum = gdbm_fetch(data->ip, key_datum);
624 pthread_mutex_unlock(&data->ip_mutex);
625 if (data_datum.dptr){
626 memcpy(&num,data_datum.dptr,sizeof(int));
627 free(data_datum.dptr);
630 DEBUG("rlm_ippool: num: %d",num);
631 data_datum.dptr = (char *) #
632 data_datum.dsize = sizeof(int);
633 pthread_mutex_lock(&data->ip_mutex);
634 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
635 pthread_mutex_unlock(&data->ip_mutex);
637 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
638 data->ip_index, gdbm_strerror(gdbm_errno));
639 return RLM_MODULE_FAIL;
643 DEBUG("rlm_ippool: Allocated ip %s to client on nas %s,port %u",ip_ntoa(str,entry.ipaddr),
645 if ((vp = paircreate(PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR)) == NULL) {
646 radlog(L_ERR|L_CONS, "no memory");
647 return RLM_MODULE_NOOP;
649 vp->lvalue = entry.ipaddr;
650 ip_ntoa(vp->strvalue, vp->lvalue);
651 pairadd(&request->reply->vps, vp);
654 * If there is no Framed-Netmask attribute in the
657 if (pairfind(request->reply->vps, PW_FRAMED_IP_NETMASK) == NULL) {
658 if ((vp = paircreate(PW_FRAMED_IP_NETMASK, PW_TYPE_IPADDR)) == NULL)
659 radlog(L_ERR|L_CONS, "no memory");
661 vp->lvalue = ntohl(data->netmask);
662 ip_ntoa(vp->strvalue, vp->lvalue);
663 pairadd(&request->reply->vps, vp);
669 DEBUG("rlm_ippool: No available ip addresses in pool.");
670 return RLM_MODULE_NOOP;
673 return RLM_MODULE_OK;
676 static int ippool_detach(void *instance)
678 rlm_ippool_t *data = (rlm_ippool_t *) instance;
680 gdbm_close(data->gdbm);
681 gdbm_close(data->ip);
682 free(data->session_db);
683 free(data->ip_index);
684 pthread_mutex_destroy(&data->session_mutex);
685 pthread_mutex_destroy(&data->ip_mutex);
692 * The module name should be the only globally exported symbol.
693 * That is, everything else should be 'static'.
695 * If the module needs to temporarily modify it's instantiation
696 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
697 * The server will then take care of ensuring that the module
698 * is single-threaded.
700 module_t rlm_ippool = {
702 RLM_TYPE_THREAD_SAFE, /* type */
703 NULL, /* initialization */
704 ippool_instantiate, /* instantiation */
706 NULL, /* authentication */
707 NULL, /* authorization */
708 NULL, /* preaccounting */
709 ippool_accounting, /* accounting */
710 NULL, /* checksimul */
711 NULL, /* pre-proxy */
712 NULL, /* post-proxy */
713 ippool_postauth /* post-auth */
715 ippool_detach, /* detach */