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
37 * Jul 2003, Kostas Kalevras <kkalev@noc.ntua.gr>
38 * - Make Multilink work this time
39 * - Instead of locking file operations, lock transactions. That means we only keep
40 * one big transaction lock instead of per file locks (mutexes).
45 #include "libradius.h"
58 #include <netinet/in.h>
60 #ifdef NEEDS_GDBM_SYNC
61 # define GDBM_SYNCOPT GDBM_SYNC
63 # define GDBM_SYNCOPT 0
67 #define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
69 #define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT)
72 #define MAX_NAS_NAME_SIZE 64
74 static const char rcsid[] = "$Id$";
77 * Define a structure for our module configuration.
79 * These variables do not need to be in a structure, but it's
80 * a lot cleaner to do so, and a pointer to the structure can
81 * be used as the instance handle.
83 typedef struct rlm_ippool_t {
94 pthread_mutex_t op_mutex;
97 typedef struct ippool_info {
103 typedef struct ippool_key {
104 char nas[MAX_NAS_NAME_SIZE];
109 * A mapping of configuration file names to internal variables.
111 * Note that the string is dynamically allocated, so it MUST
112 * be freed. When the configuration file parse re-reads the string,
113 * it free's the old one, and strdup's the new one, placing the pointer
114 * to the strdup'd string into 'config.string'. This gets around
117 static CONF_PARSER module_config[] = {
118 { "session-db", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,session_db), NULL, NULL },
119 { "ip-index", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,ip_index), NULL, NULL },
120 { "range-start", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_start), NULL, "0" },
121 { "range-stop", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_stop), NULL, "0" },
122 { "netmask", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,netmask), NULL, "0" },
123 { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,cache_size), NULL, "1000" },
124 { "override", PW_TYPE_BOOLEAN, offsetof(rlm_ippool_t,override), NULL, "no" },
125 { NULL, -1, 0, NULL, NULL }
130 * Do any per-module initialization that is separate to each
131 * configured instance of the module. e.g. set up connections
132 * to external databases, read configuration files, set up
133 * dictionary entries, etc.
135 * If configuration information is given in the config section
136 * that must be referenced in later calls, store a handle to it
137 * in *instance otherwise put a null pointer there.
139 static int ippool_instantiate(CONF_SECTION *conf, void **instance)
149 const char *cli = "0";
150 char *pool_name = NULL;
153 * Set up a storage area for instance data
155 data = rad_malloc(sizeof(*data));
159 memset(data, 0, sizeof(*data));
162 * If the configuration parameters can't be parsed, then
165 if (cf_section_parse(conf, data, module_config) < 0) {
169 cache_size = data->cache_size;
171 if (data->session_db == NULL) {
172 radlog(L_ERR, "rlm_ippool: 'session-db' must be set.");
176 if (data->ip_index == NULL) {
177 radlog(L_ERR, "rlm_ippool: 'ip-index' must be set.");
181 data->range_start = htonl(data->range_start);
182 data->range_stop = htonl(data->range_stop);
183 data->netmask = htonl(data->netmask);
184 if (data->range_start == 0 || data->range_stop == 0 || \
185 data->range_start >= data->range_stop ) {
186 radlog(L_ERR, "rlm_ippool: Invalid configuration data given.");
191 data->gdbm = gdbm_open(data->session_db, sizeof(int),
192 GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
193 if (data->gdbm == NULL) {
194 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
195 data->session_db, strerror(errno));
198 data->ip = gdbm_open(data->ip_index, sizeof(int),
199 GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
200 if (data->ip == NULL) {
201 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
202 data->ip_index, strerror(errno));
205 if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
206 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
207 if (gdbm_setopt(data->ip, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
208 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
210 key_datum = gdbm_firstkey(data->gdbm);
211 if (key_datum.dptr == NULL){
213 * If the database does not exist initialize it.
214 * We set the nas/port pairs to not existent values and
220 const char *nas_init = "NOT_EXIST";
222 DEBUG("rlm_ippool: Initializing database");
223 for(i=data->range_start,j=~0;i<=data->range_stop;i++,j--){
226 * Net and Broadcast addresses are excluded
228 or_result = i | data->netmask;
229 if (~data->netmask != 0 &&
230 (or_result == data->netmask ||
231 (~or_result == 0))) {
232 DEBUG("rlm_ippool: IP %s excluded",
233 ip_ntoa(str, ntohl(i)));
237 strcpy(key.nas, nas_init);
239 key_datum.dptr = (char *) &key;
240 key_datum.dsize = sizeof(ippool_key);
242 entry.ipaddr = ntohl(i);
244 strcpy(entry.cli,cli);
246 data_datum.dptr = (char *) &entry;
247 data_datum.dsize = sizeof(ippool_info);
249 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
251 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
252 data->session_db, gdbm_strerror(gdbm_errno));
254 gdbm_close(data->gdbm);
255 gdbm_close(data->ip);
261 free(key_datum.dptr);
263 /* Add the ip pool name */
265 pool_name = cf_section_name2(conf);
266 if (pool_name != NULL)
267 data->name = strdup(pool_name);
268 pthread_mutex_init(&data->op_mutex, NULL);
277 * Check for an Accounting-Stop
278 * If we find one and we have allocated an IP to this nas/port combination, deallocate it.
280 static int ippool_accounting(void *instance, REQUEST *request)
282 rlm_ippool_t *data = (rlm_ippool_t *)instance;
285 int acctstatustype = 0;
286 unsigned int port = ~0;
288 char nas[MAX_NAS_NAME_SIZE];
296 if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL)
297 acctstatustype = vp->lvalue;
299 DEBUG("rlm_ippool: Could not find account status type in packet. Return NOOP.");
300 return RLM_MODULE_NOOP;
302 switch(acctstatustype){
304 if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
307 DEBUG("rlm_ippool: Could not find port number in packet. Return NOOP.");
308 return RLM_MODULE_NOOP;
310 if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
311 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
313 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
314 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
316 DEBUG("rlm_ippool: Could not find nas information in packet. Return NOOP.");
317 return RLM_MODULE_NOOP;
322 /* We don't care about any other accounting packet */
323 DEBUG("rlm_ippool: This is not an Accounting-Stop. Return NOOP.");
325 return RLM_MODULE_NOOP;
328 memset(key.nas,0,MAX_NAS_NAME_SIZE);
329 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
331 DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%u",key.nas,key.port);
332 key_datum.dptr = (char *) &key;
333 key_datum.dsize = sizeof(ippool_key);
335 pthread_mutex_lock(&data->op_mutex);
336 data_datum = gdbm_fetch(data->gdbm, key_datum);
337 if (data_datum.dptr != NULL){
340 * If the entry was found set active to zero
342 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
343 free(data_datum.dptr);
344 DEBUG("rlm_ippool: Deallocated entry for ip/port: %s/%u",ip_ntoa(str,entry.ipaddr),port);
347 data_datum.dptr = (char *) &entry;
348 data_datum.dsize = sizeof(ippool_info);
350 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
352 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
353 data->session_db, gdbm_strerror(gdbm_errno));
354 pthread_mutex_unlock(&data->op_mutex);
355 return RLM_MODULE_FAIL;
359 * Decrease allocated count from the ip index
361 key_datum.dptr = (char *) &entry.ipaddr;
362 key_datum.dsize = sizeof(uint32_t);
363 data_datum = gdbm_fetch(data->ip, key_datum);
364 if (data_datum.dptr != NULL){
365 memcpy(&num, data_datum.dptr, sizeof(int));
366 free(data_datum.dptr);
369 DEBUG("rlm_ippool: num: %d",num);
370 data_datum.dptr = (char *) #
371 data_datum.dsize = sizeof(int);
372 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
374 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
375 data->ip_index, gdbm_strerror(gdbm_errno));
376 pthread_mutex_unlock(&data->op_mutex);
377 return RLM_MODULE_FAIL;
381 pthread_mutex_unlock(&data->op_mutex);
384 pthread_mutex_unlock(&data->op_mutex);
385 DEBUG("rlm_ippool: Entry not found");
388 return RLM_MODULE_OK;
391 static int ippool_postauth(void *instance, REQUEST *request)
393 rlm_ippool_t *data = (rlm_ippool_t *) instance;
394 unsigned int port = 0;
398 char nas[MAX_NAS_NAME_SIZE];
409 /* quiet the compiler */
413 /* Check if Pool-Name attribute exists. If it exists check our name and
414 * run only if they match
416 if ((vp = pairfind(request->config_items, PW_POOL_NAME)) != NULL){
417 if (data->name == NULL || strcmp(data->name,vp->strvalue))
418 return RLM_MODULE_NOOP;
420 DEBUG("rlm_ippool: Could not find Pool-Name attribute.");
421 return RLM_MODULE_NOOP;
425 * Get the nas ip address
428 if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
429 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
431 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
432 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
434 DEBUG("rlm_ippool: Could not find nas information. Return NOOP.");
435 return RLM_MODULE_NOOP;
442 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
449 if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
452 DEBUG("rlm_ippool: Could not find nas port information. Return NOOP.");
453 return RLM_MODULE_NOOP;
456 memset(key.nas,0,MAX_NAS_NAME_SIZE);
457 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
459 DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%u",key.nas,key.port);
460 key_datum.dptr = (char *) &key;
461 key_datum.dsize = sizeof(ippool_key);
463 pthread_mutex_lock(&data->op_mutex);
464 data_datum = gdbm_fetch(data->gdbm, key_datum);
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 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
481 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
482 data->session_db, gdbm_strerror(gdbm_errno));
483 pthread_mutex_unlock(&data->op_mutex);
484 return RLM_MODULE_FAIL;
486 /* Decrease allocated count from the ip index */
488 key_datum.dptr = (char *) &entry.ipaddr;
489 key_datum.dsize = sizeof(uint32_t);
490 data_datum = gdbm_fetch(data->ip, key_datum);
491 if (data_datum.dptr != NULL){
492 memcpy(&num, data_datum.dptr, sizeof(int));
493 free(data_datum.dptr);
496 DEBUG("rlm_ippool: num: %d",num);
497 data_datum.dptr = (char *) #
498 data_datum.dsize = sizeof(int);
499 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
501 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
502 data->ip_index, gdbm_strerror(gdbm_errno));
503 pthread_mutex_unlock(&data->op_mutex);
504 return RLM_MODULE_FAIL;
511 pthread_mutex_unlock(&data->op_mutex);
514 * If there is a Framed-IP-Address attribute in the reply, check for override
516 if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS) != NULL) {
519 /* Override supplied Framed-IP-Address */
520 pairdelete(&request->reply->vps, PW_FRAMED_IP_ADDRESS);
523 return RLM_MODULE_NOOP;
528 * Walk through the database searching for an active=0 entry.
529 * We search twice. Once to see if we have an active entry with the same callerid
530 * so that MPPP can work ok and then once again to find a free entry.
533 pthread_mutex_lock(&data->op_mutex);
535 key_datum.dptr = NULL;
537 key_datum = gdbm_firstkey(data->gdbm);
538 while(key_datum.dptr){
539 data_datum = gdbm_fetch(data->gdbm, key_datum);
540 if (data_datum.dptr){
541 memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
542 free(data_datum.dptr);
544 * If we find an entry for the same caller-id and nas with active=1
545 * then we use that for multilink (MPPP) to work properly.
547 if (strcmp(entry.cli,cli) == 0 && entry.active){
548 memcpy(&key,key_datum.dptr,sizeof(ippool_key));
549 if (!strcmp(key.nas,nas))
553 nextkey = gdbm_nextkey(data->gdbm, key_datum);
554 free(key_datum.dptr);
559 if (key_datum.dptr == NULL){
560 key_datum = gdbm_firstkey(data->gdbm);
561 while(key_datum.dptr){
562 data_datum = gdbm_fetch(data->gdbm, key_datum);
563 if (data_datum.dptr){
564 memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
565 free(data_datum.dptr);
568 * Find an entry with active == 0
570 if (entry.active == 0){
573 tmp.dptr = (char *) &entry.ipaddr;
574 tmp.dsize = sizeof(uint32_t);
575 data_datum = gdbm_fetch(data->ip, tmp);
578 * If we find an entry in the ip index and the number is zero (meaning
579 * that we haven't allocated the same ip address to another nas/port pair)
580 * or if we don't find an entry then delete the session entry so
581 * that we can change the key (nas/port)
582 * Else we don't delete the session entry since we haven't yet deallocated the
583 * corresponding ip address and we continue our search.
586 if (data_datum.dptr){
587 memcpy(&num,data_datum.dptr, sizeof(int));
588 free(data_datum.dptr);
600 nextkey = gdbm_nextkey(data->gdbm, key_datum);
601 free(key_datum.dptr);
606 * If we have found a free entry set active to 1 then add a Framed-IP-Address attribute to
608 * We keep the operation mutex locked until after we have set the corresponding entry active
612 data_datum.dptr = (char *) &entry;
613 data_datum.dsize = sizeof(ippool_info);
617 * Delete the entry so that we can change the key
619 gdbm_delete(data->gdbm, key_datum);
621 free(key_datum.dptr);
622 memset(key.nas,0,MAX_NAS_NAME_SIZE);
623 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE - 1);
625 key_datum.dptr = (char *) &key;
626 key_datum.dsize = sizeof(ippool_key);
628 DEBUG2("rlm_ippool: Allocating ip to nas/port: %s/%u",key.nas,key.port);
629 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
631 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
632 data->session_db, gdbm_strerror(gdbm_errno));
633 pthread_mutex_unlock(&data->op_mutex);
634 return RLM_MODULE_FAIL;
637 /* Increase the ip index count */
638 key_datum.dptr = (char *) &entry.ipaddr;
639 key_datum.dsize = sizeof(uint32_t);
640 data_datum = gdbm_fetch(data->ip, key_datum);
641 if (data_datum.dptr){
642 memcpy(&num,data_datum.dptr,sizeof(int));
643 free(data_datum.dptr);
646 DEBUG("rlm_ippool: num: %d",num);
647 data_datum.dptr = (char *) #
648 data_datum.dsize = sizeof(int);
649 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
651 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
652 data->ip_index, gdbm_strerror(gdbm_errno));
653 pthread_mutex_unlock(&data->op_mutex);
654 return RLM_MODULE_FAIL;
656 pthread_mutex_unlock(&data->op_mutex);
659 DEBUG("rlm_ippool: Allocated ip %s to client on nas %s,port %u",ip_ntoa(str,entry.ipaddr),
661 if ((vp = paircreate(PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR)) == NULL) {
662 radlog(L_ERR|L_CONS, "no memory");
663 return RLM_MODULE_NOOP;
665 vp->lvalue = entry.ipaddr;
666 ip_ntoa(vp->strvalue, vp->lvalue);
667 pairadd(&request->reply->vps, vp);
670 * If there is no Framed-Netmask attribute in the
673 if (pairfind(request->reply->vps, PW_FRAMED_IP_NETMASK) == NULL) {
674 if ((vp = paircreate(PW_FRAMED_IP_NETMASK, PW_TYPE_IPADDR)) == NULL)
675 radlog(L_ERR|L_CONS, "no memory");
677 vp->lvalue = ntohl(data->netmask);
678 ip_ntoa(vp->strvalue, vp->lvalue);
679 pairadd(&request->reply->vps, vp);
685 pthread_mutex_unlock(&data->op_mutex);
686 DEBUG("rlm_ippool: No available ip addresses in pool.");
687 return RLM_MODULE_NOOP;
690 return RLM_MODULE_OK;
693 static int ippool_detach(void *instance)
695 rlm_ippool_t *data = (rlm_ippool_t *) instance;
697 gdbm_close(data->gdbm);
698 gdbm_close(data->ip);
699 free(data->session_db);
700 free(data->ip_index);
701 pthread_mutex_destroy(&data->op_mutex);
708 * The module name should be the only globally exported symbol.
709 * That is, everything else should be 'static'.
711 * If the module needs to temporarily modify it's instantiation
712 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
713 * The server will then take care of ensuring that the module
714 * is single-threaded.
716 module_t rlm_ippool = {
718 RLM_TYPE_THREAD_SAFE, /* type */
719 NULL, /* initialization */
720 ippool_instantiate, /* instantiation */
722 NULL, /* authentication */
723 NULL, /* authorization */
724 NULL, /* preaccounting */
725 ippool_accounting, /* accounting */
726 NULL, /* checksimul */
727 NULL, /* pre-proxy */
728 NULL, /* post-proxy */
729 ippool_postauth /* post-auth */
731 ippool_detach, /* detach */