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 ALL_ONES 4294967295
69 #define MAX_NAS_NAME_SIZE 64
71 static const char rcsid[] = "$Id$";
74 * Define a structure for our module configuration.
76 * These variables do not need to be in a structure, but it's
77 * a lot cleaner to do so, and a pointer to the structure can
78 * be used as the instance handle.
80 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 { NULL, -1, 0, NULL, NULL }
126 * Do any per-module initialization that is separate to each
127 * configured instance of the module. e.g. set up connections
128 * to external databases, read configuration files, set up
129 * dictionary entries, etc.
131 * If configuration information is given in the config section
132 * that must be referenced in later calls, store a handle to it
133 * in *instance otherwise put a null pointer there.
135 static int ippool_instantiate(CONF_SECTION *conf, void **instance)
144 const char *cli = "0";
145 char *pool_name = NULL;
148 * Set up a storage area for instance data
150 data = rad_malloc(sizeof(*data));
153 * If the configuration parameters can't be parsed, then
156 if (cf_section_parse(conf, data, module_config) < 0) {
160 cache_size = data->cache_size;
162 if (data->session_db == NULL) {
163 radlog(L_ERR, "rlm_ippool: 'session-db' must be set.");
167 if (data->ip_index == NULL) {
168 radlog(L_ERR, "rlm_ippool: 'ip-index' must be set.");
172 data->range_start = htonl(data->range_start);
173 data->range_stop = htonl(data->range_stop);
174 data->netmask = htonl(data->netmask);
175 if (data->range_start == 0 || data->range_stop == 0 || \
176 data->range_start >= data->range_stop ) {
177 radlog(L_ERR, "rlm_ippool: Invalid configuration data given.");
182 data->gdbm = gdbm_open(data->session_db, sizeof(int),
183 GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
184 if (data->gdbm == NULL) {
185 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
186 data->session_db, strerror(errno));
189 data->ip = gdbm_open(data->ip_index, sizeof(int),
190 GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
191 if (data->ip == NULL) {
192 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
193 data->ip_index, strerror(errno));
196 if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
197 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
198 if (gdbm_setopt(data->ip, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
199 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
201 key_datum = gdbm_firstkey(data->gdbm);
202 if (key_datum.dptr == NULL){
204 * If the database does not exist initialize it.
205 * We set the nas/port pairs to not existent values and
211 const char *nas_init = "NOT_EXIST";
213 DEBUG("rlm_ippool: Initializing database");
214 for(i=data->range_start,j=-1;i<=data->range_stop;i++,j--){
217 * Net and Broadcast addresses are excluded
219 or_result = i | data->netmask;
220 if (or_result == data->netmask || or_result == ALL_ONES){
221 DEBUG("rlm_ippool: IP %s exlcluded",ip_ntoa(str,ntohl(i)));
225 strcpy(key.nas, nas_init);
227 key_datum.dptr = (ippool_key *) &key;
228 key_datum.dsize = sizeof(ippool_key);
230 entry.ipaddr = ntohl(i);
232 strcpy(entry.cli,cli);
234 data_datum.dptr = (ippool_info *) &entry;
235 data_datum.dsize = sizeof(ippool_info);
237 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
239 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
240 data->session_db, gdbm_strerror(gdbm_errno));
242 gdbm_close(data->gdbm);
243 gdbm_close(data->ip);
249 free(key_datum.dptr);
251 /* Add the ip pool name */
253 pool_name = cf_section_name2(conf);
254 if (pool_name != NULL)
255 data->name = strdup(pool_name);
256 pthread_mutex_init(&data->session_mutex, NULL);
257 pthread_mutex_init(&data->ip_mutex, NULL);
266 * Check for an Accounting-Stop
267 * If we find one and we have allocated an IP to this nas/port combination, deallocate it.
269 static int ippool_accounting(void *instance, REQUEST *request)
271 rlm_ippool_t *data = (rlm_ippool_t *)instance;
274 int acctstatustype = 0;
277 char nas[MAX_NAS_NAME_SIZE];
285 if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL)
286 acctstatustype = vp->lvalue;
288 DEBUG("rlm_ippool: Could not find account status type in packet.");
289 return RLM_MODULE_NOOP;
291 switch(acctstatustype){
293 if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
296 DEBUG("rlm_ippool: Could not find port number in packet.");
297 return RLM_MODULE_NOOP;
299 if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
300 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
302 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
303 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
305 DEBUG("rlm_ippool: Could not find nas information in packet.");
306 return RLM_MODULE_NOOP;
311 /* We don't care about any other accounting packet */
313 return RLM_MODULE_NOOP;
316 memset(key.nas,0,MAX_NAS_NAME_SIZE);
317 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
319 DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%d",key.nas,key.port);
320 key_datum.dptr = (ippool_key *) &key;
321 key_datum.dsize = sizeof(ippool_key);
323 pthread_mutex_lock(&data->session_mutex);
324 data_datum = gdbm_fetch(data->gdbm, key_datum);
325 pthread_mutex_unlock(&data->session_mutex);
326 if (data_datum.dptr != NULL){
329 * If the entry was found set active to zero
331 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
332 free(data_datum.dptr);
333 DEBUG("rlm_ippool: Deallocated entry for ip/port: %s/%d",ip_ntoa(str,entry.ipaddr),port);
336 data_datum.dptr = (ippool_info *) &entry;
337 data_datum.dsize = sizeof(ippool_info);
339 pthread_mutex_lock(&data->session_mutex);
340 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
341 pthread_mutex_unlock(&data->session_mutex);
343 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
344 data->session_db, gdbm_strerror(gdbm_errno));
345 return RLM_MODULE_FAIL;
349 * Decrease allocated count from the ip index
351 key_datum.dptr = (uint32_t *) &entry.ipaddr;
352 key_datum.dsize = sizeof(uint32_t);
353 pthread_mutex_lock(&data->ip_mutex);
354 data_datum = gdbm_fetch(data->ip, key_datum);
355 pthread_mutex_unlock(&data->ip_mutex);
356 if (data_datum.dptr != NULL){
357 memcpy(&num, data_datum.dptr, sizeof(int));
358 free(data_datum.dptr);
361 DEBUG("rlm_ippool: num: %d",num);
362 data_datum.dptr = (int *) #
363 data_datum.dsize = sizeof(int);
364 pthread_mutex_lock(&data->ip_mutex);
365 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
366 pthread_mutex_unlock(&data->ip_mutex);
368 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
369 data->ip_index, gdbm_strerror(gdbm_errno));
370 return RLM_MODULE_FAIL;
376 DEBUG("rlm_ippool: Entry not found");
378 return RLM_MODULE_OK;
381 static int ippool_postauth(void *instance, REQUEST *request)
383 rlm_ippool_t *data = (rlm_ippool_t *) instance;
388 char nas[MAX_NAS_NAME_SIZE];
399 /* quiet the compiler */
403 /* Check if Pool-Name attribute exists. If it exists check our name and
404 * run only if they match
406 if ((vp = pairfind(request->config_items, PW_POOL_NAME)) != NULL){
407 if (data->name == NULL || strcmp(data->name,vp->strvalue))
408 return RLM_MODULE_NOOP;
410 DEBUG("rlm_ippool: Could not find Pool-Name attribute.");
411 return RLM_MODULE_NOOP;
415 * Get the nas ip address
418 if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
419 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
421 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
422 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
424 DEBUG("rlm_ippool: Could not find nas information.");
425 return RLM_MODULE_NOOP;
432 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
439 if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
442 DEBUG("rlm_ippool: Could not find port information.");
443 return RLM_MODULE_NOOP;
446 memset(key.nas,0,MAX_NAS_NAME_SIZE);
447 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
449 DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%d",key.nas,key.port);
450 key_datum.dptr = (ippool_key *) &key;
451 key_datum.dsize = sizeof(ippool_key);
453 pthread_mutex_lock(&data->session_mutex);
454 data_datum = gdbm_fetch(data->gdbm, key_datum);
455 pthread_mutex_unlock(&data->session_mutex);
456 if (data_datum.dptr != NULL){
458 * If there is a corresponding entry in the database with active=1 it is stale.
461 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
462 free(data_datum.dptr);
464 DEBUG("rlm_ippool: Found a stale entry for ip/port: %s/%d",ip_ntoa(str,entry.ipaddr),port);
467 data_datum.dptr = (ippool_info *) &entry;
468 data_datum.dsize = sizeof(ippool_info);
470 pthread_mutex_lock(&data->session_mutex);
471 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
472 pthread_mutex_unlock(&data->session_mutex);
474 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
475 data->session_db, gdbm_strerror(gdbm_errno));
476 return RLM_MODULE_FAIL;
478 /* Decrease allocated count from the ip index */
480 key_datum.dptr = (uint32_t *) &entry.ipaddr;
481 key_datum.dsize = sizeof(uint32_t);
482 pthread_mutex_lock(&data->ip_mutex);
483 data_datum = gdbm_fetch(data->ip, key_datum);
484 pthread_mutex_unlock(&data->ip_mutex);
485 if (data_datum.dptr != NULL){
486 memcpy(&num, data_datum.dptr, sizeof(int));
487 free(data_datum.dptr);
490 DEBUG("rlm_ippool: num: %d",num);
491 data_datum.dptr = (int *) #
492 data_datum.dsize = sizeof(int);
493 pthread_mutex_lock(&data->ip_mutex);
494 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
495 pthread_mutex_unlock(&data->ip_mutex);
497 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
498 data->ip_index, gdbm_strerror(gdbm_errno));
499 return RLM_MODULE_FAIL;
506 * If there is a Framed-IP-Address attribute in the reply do nothing
508 if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS) != NULL)
509 return RLM_MODULE_NOOP;
512 * Walk through the database searching for an active=0 entry.
515 pthread_mutex_lock(&data->session_mutex);
516 key_datum = gdbm_firstkey(data->gdbm);
517 while(key_datum.dptr){
518 data_datum = gdbm_fetch(data->gdbm, key_datum);
519 if (data_datum.dptr){
520 memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
521 free(data_datum.dptr);
523 * If we find an entry for the same caller-id and nas with active=1
524 * then we use that for multilink (MPPP) to work properly.
526 if (cli != NULL && strcmp(entry.cli,cli) == 0 && entry.active){
527 memcpy(&key,key_datum.dptr,sizeof(ippool_key));
528 if (!strcmp(key.nas,nas))
531 if (entry.active == 0){
534 tmp.dptr = (uint32_t *) &entry.ipaddr;
535 tmp.dsize = sizeof(uint32_t);
536 pthread_mutex_lock(&data->ip_mutex);
537 data_datum = gdbm_fetch(data->ip, tmp);
538 pthread_mutex_unlock(&data->ip_mutex);
541 * If we find an entry in the ip index and the number is zero (meaning
542 * that we haven't allocated the same ip address to another nas/port pair)
543 * or if we don't find an entry then delete the session entry so
544 * that we can change the key (nas/port)
545 * Else we don't delete the session entry since we haven't yet deallocated the
546 * corresponding ip address and we continue our search.
549 if (data_datum.dptr){
550 memcpy(&num,data_datum.dptr, sizeof(int));
551 free(data_datum.dptr);
563 nextkey = gdbm_nextkey(data->gdbm, key_datum);
564 free(key_datum.dptr);
567 pthread_mutex_unlock(&data->session_mutex);
569 * If we have found a free entry set active to 1 then add a Framed-IP-Address attribute to
574 data_datum.dptr = (ippool_info *) &entry;
575 data_datum.dsize = sizeof(ippool_info);
579 * Delete the entry so that we can change the key
581 pthread_mutex_lock(&data->session_mutex);
582 gdbm_delete(data->gdbm, key_datum);
583 pthread_mutex_unlock(&data->session_mutex);
585 free(key_datum.dptr);
586 memset(key.nas,0,MAX_NAS_NAME_SIZE);
587 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE - 1);
589 key_datum.dptr = (ippool_key *) &key;
590 key_datum.dsize = sizeof(ippool_key);
592 DEBUG2("rlm_ippool: Allocating ip to nas/port: %s/%d",key.nas,key.port);
593 pthread_mutex_lock(&data->session_mutex);
594 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
595 pthread_mutex_unlock(&data->session_mutex);
597 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
598 data->session_db, gdbm_strerror(gdbm_errno));
599 return RLM_MODULE_FAIL;
602 /* Increase the ip index count */
603 key_datum.dptr = (uint32_t *) &entry.ipaddr;
604 key_datum.dsize = sizeof(uint32_t);
605 pthread_mutex_lock(&data->ip_mutex);
606 data_datum = gdbm_fetch(data->ip, key_datum);
607 pthread_mutex_unlock(&data->ip_mutex);
608 if (data_datum.dptr){
609 memcpy(&num,data_datum.dptr,sizeof(int));
610 free(data_datum.dptr);
613 DEBUG("rlm_ippool: num: %d",num);
614 data_datum.dptr = (int *) #
615 data_datum.dsize = sizeof(int);
616 pthread_mutex_lock(&data->ip_mutex);
617 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
618 pthread_mutex_unlock(&data->ip_mutex);
620 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
621 data->ip_index, gdbm_strerror(gdbm_errno));
622 return RLM_MODULE_FAIL;
626 DEBUG("rlm_ippool: Allocated ip %s to client on nas %s,port %d",ip_ntoa(str,entry.ipaddr),
628 if ((vp = paircreate(PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR)) == NULL) {
629 radlog(L_ERR|L_CONS, "no memory");
630 return RLM_MODULE_NOOP;
632 vp->lvalue = entry.ipaddr;
633 ip_ntoa(vp->strvalue, vp->lvalue);
634 pairadd(&request->reply->vps, vp);
637 DEBUG("rlm_ippool: No available ip addresses in pool.");
638 return RLM_MODULE_NOOP;
641 return RLM_MODULE_OK;
644 static int ippool_detach(void *instance)
646 rlm_ippool_t *data = (rlm_ippool_t *) instance;
648 gdbm_close(data->gdbm);
649 gdbm_close(data->ip);
650 free(data->session_db);
651 free(data->ip_index);
652 pthread_mutex_destroy(&data->session_mutex);
653 pthread_mutex_destroy(&data->ip_mutex);
660 * The module name should be the only globally exported symbol.
661 * That is, everything else should be 'static'.
663 * If the module needs to temporarily modify it's instantiation
664 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
665 * The server will then take care of ensuring that the module
666 * is single-threaded.
668 module_t rlm_ippool = {
670 RLM_TYPE_THREAD_SAFE, /* type */
671 NULL, /* initialization */
672 ippool_instantiate, /* instantiation */
674 NULL, /* authentication */
675 NULL, /* authorization */
676 NULL, /* preaccounting */
677 ippool_accounting, /* accounting */
678 NULL, /* checksimul */
679 NULL, /* pre-proxy */
680 NULL, /* post-proxy */
681 ippool_postauth /* post-auth */
683 ippool_detach, /* detach */