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 {
89 pthread_mutex_t session_mutex;
90 pthread_mutex_t ip_mutex;
93 typedef struct ippool_info {
99 typedef struct ippool_key {
100 char nas[MAX_NAS_NAME_SIZE];
105 * A mapping of configuration file names to internal variables.
107 * Note that the string is dynamically allocated, so it MUST
108 * be freed. When the configuration file parse re-reads the string,
109 * it free's the old one, and strdup's the new one, placing the pointer
110 * to the strdup'd string into 'config.string'. This gets around
113 static CONF_PARSER module_config[] = {
114 { "session-db", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,session_db), NULL, NULL },
115 { "ip-index", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,ip_index), NULL, NULL },
116 { "range-start", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_start), NULL, "0" },
117 { "range-stop", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_stop), NULL, "0" },
118 { "netmask", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,netmask), NULL, "0" },
119 { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,cache_size), NULL, "1000" },
120 { NULL, -1, 0, NULL, NULL }
125 * Do any per-module initialization that is separate to each
126 * configured instance of the module. e.g. set up connections
127 * to external databases, read configuration files, set up
128 * dictionary entries, etc.
130 * If configuration information is given in the config section
131 * that must be referenced in later calls, store a handle to it
132 * in *instance otherwise put a null pointer there.
134 static int ippool_instantiate(CONF_SECTION *conf, void **instance)
143 const char *cli = "0";
144 char *pool_name = NULL;
147 * Set up a storage area for instance data
149 data = rad_malloc(sizeof(*data));
152 * If the configuration parameters can't be parsed, then
155 if (cf_section_parse(conf, data, module_config) < 0) {
159 cache_size = data->cache_size;
161 if (data->session_db == NULL) {
162 radlog(L_ERR, "rlm_ippool: 'session-db' must be set.");
166 if (data->ip_index == NULL) {
167 radlog(L_ERR, "rlm_ippool: 'ip-index' must be set.");
171 data->range_start = htonl(data->range_start);
172 data->range_stop = htonl(data->range_stop);
173 data->netmask = htonl(data->netmask);
174 if (data->range_start == 0 || data->range_stop == 0 || \
175 data->range_start >= data->range_stop ) {
176 radlog(L_ERR, "rlm_ippool: Invalid configuration data given.");
181 data->gdbm = gdbm_open(data->session_db, sizeof(int),
182 GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
183 if (data->gdbm == NULL) {
184 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
185 data->session_db, strerror(errno));
188 data->ip = gdbm_open(data->ip_index, sizeof(int),
189 GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
190 if (data->ip == NULL) {
191 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
192 data->ip_index, strerror(errno));
195 if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
196 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
197 if (gdbm_setopt(data->ip, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
198 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
200 key_datum = gdbm_firstkey(data->gdbm);
201 if (key_datum.dptr == NULL){
203 * If the database does not exist initialize it.
204 * We set the nas/port pairs to not existent values and
210 const char *nas_init = "NOT_EXIST";
212 DEBUG("rlm_ippool: Initializing database");
213 for(i=data->range_start,j=-1;i<=data->range_stop;i++,j--){
216 * Net and Broadcast addresses are excluded
218 or_result = i | data->netmask;
219 if (~data->netmask != 0 &&
220 (or_result == data->netmask ||
221 (~or_result == 0))) {
222 DEBUG("rlm_ippool: IP %s excluded",
223 ip_ntoa(str, ntohl(i)));
227 strcpy(key.nas, nas_init);
229 key_datum.dptr = (ippool_key *) &key;
230 key_datum.dsize = sizeof(ippool_key);
232 entry.ipaddr = ntohl(i);
234 strcpy(entry.cli,cli);
236 data_datum.dptr = (ippool_info *) &entry;
237 data_datum.dsize = sizeof(ippool_info);
239 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
241 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
242 data->session_db, gdbm_strerror(gdbm_errno));
244 gdbm_close(data->gdbm);
245 gdbm_close(data->ip);
251 free(key_datum.dptr);
253 /* Add the ip pool name */
255 pool_name = cf_section_name2(conf);
256 if (pool_name != NULL)
257 data->name = strdup(pool_name);
258 pthread_mutex_init(&data->session_mutex, NULL);
259 pthread_mutex_init(&data->ip_mutex, NULL);
268 * Check for an Accounting-Stop
269 * If we find one and we have allocated an IP to this nas/port combination, deallocate it.
271 static int ippool_accounting(void *instance, REQUEST *request)
273 rlm_ippool_t *data = (rlm_ippool_t *)instance;
276 int acctstatustype = 0;
279 char nas[MAX_NAS_NAME_SIZE];
287 if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL)
288 acctstatustype = vp->lvalue;
290 DEBUG("rlm_ippool: Could not find account status type in packet.");
291 return RLM_MODULE_NOOP;
293 switch(acctstatustype){
295 if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
298 DEBUG("rlm_ippool: Could not find port number in packet.");
299 return RLM_MODULE_NOOP;
301 if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
302 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
304 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
305 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
307 DEBUG("rlm_ippool: Could not find nas information in packet.");
308 return RLM_MODULE_NOOP;
313 /* We don't care about any other accounting packet */
315 return RLM_MODULE_NOOP;
318 memset(key.nas,0,MAX_NAS_NAME_SIZE);
319 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
321 DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%d",key.nas,key.port);
322 key_datum.dptr = (ippool_key *) &key;
323 key_datum.dsize = sizeof(ippool_key);
325 pthread_mutex_lock(&data->session_mutex);
326 data_datum = gdbm_fetch(data->gdbm, key_datum);
327 pthread_mutex_unlock(&data->session_mutex);
328 if (data_datum.dptr != NULL){
331 * If the entry was found set active to zero
333 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
334 free(data_datum.dptr);
335 DEBUG("rlm_ippool: Deallocated entry for ip/port: %s/%d",ip_ntoa(str,entry.ipaddr),port);
338 data_datum.dptr = (ippool_info *) &entry;
339 data_datum.dsize = sizeof(ippool_info);
341 pthread_mutex_lock(&data->session_mutex);
342 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
343 pthread_mutex_unlock(&data->session_mutex);
345 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
346 data->session_db, gdbm_strerror(gdbm_errno));
347 return RLM_MODULE_FAIL;
351 * Decrease allocated count from the ip index
353 key_datum.dptr = (uint32_t *) &entry.ipaddr;
354 key_datum.dsize = sizeof(uint32_t);
355 pthread_mutex_lock(&data->ip_mutex);
356 data_datum = gdbm_fetch(data->ip, key_datum);
357 pthread_mutex_unlock(&data->ip_mutex);
358 if (data_datum.dptr != NULL){
359 memcpy(&num, data_datum.dptr, sizeof(int));
360 free(data_datum.dptr);
363 DEBUG("rlm_ippool: num: %d",num);
364 data_datum.dptr = (int *) #
365 data_datum.dsize = sizeof(int);
366 pthread_mutex_lock(&data->ip_mutex);
367 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
368 pthread_mutex_unlock(&data->ip_mutex);
370 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
371 data->ip_index, gdbm_strerror(gdbm_errno));
372 return RLM_MODULE_FAIL;
378 DEBUG("rlm_ippool: Entry not found");
380 return RLM_MODULE_OK;
383 static int ippool_postauth(void *instance, REQUEST *request)
385 rlm_ippool_t *data = (rlm_ippool_t *) instance;
390 char nas[MAX_NAS_NAME_SIZE];
401 /* quiet the compiler */
405 /* Check if Pool-Name attribute exists. If it exists check our name and
406 * run only if they match
408 if ((vp = pairfind(request->config_items, PW_POOL_NAME)) != NULL){
409 if (data->name == NULL || strcmp(data->name,vp->strvalue))
410 return RLM_MODULE_NOOP;
412 DEBUG("rlm_ippool: Could not find Pool-Name attribute.");
413 return RLM_MODULE_NOOP;
417 * Get the nas ip address
420 if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
421 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
423 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
424 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
426 DEBUG("rlm_ippool: Could not find nas information.");
427 return RLM_MODULE_NOOP;
434 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
441 if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
444 DEBUG("rlm_ippool: Could not find port information.");
445 return RLM_MODULE_NOOP;
448 memset(key.nas,0,MAX_NAS_NAME_SIZE);
449 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
451 DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%d",key.nas,key.port);
452 key_datum.dptr = (ippool_key *) &key;
453 key_datum.dsize = sizeof(ippool_key);
455 pthread_mutex_lock(&data->session_mutex);
456 data_datum = gdbm_fetch(data->gdbm, key_datum);
457 pthread_mutex_unlock(&data->session_mutex);
458 if (data_datum.dptr != NULL){
460 * If there is a corresponding entry in the database with active=1 it is stale.
463 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
464 free(data_datum.dptr);
466 DEBUG("rlm_ippool: Found a stale entry for ip/port: %s/%d",ip_ntoa(str,entry.ipaddr),port);
469 data_datum.dptr = (ippool_info *) &entry;
470 data_datum.dsize = sizeof(ippool_info);
472 pthread_mutex_lock(&data->session_mutex);
473 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
474 pthread_mutex_unlock(&data->session_mutex);
476 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
477 data->session_db, gdbm_strerror(gdbm_errno));
478 return RLM_MODULE_FAIL;
480 /* Decrease allocated count from the ip index */
482 key_datum.dptr = (uint32_t *) &entry.ipaddr;
483 key_datum.dsize = sizeof(uint32_t);
484 pthread_mutex_lock(&data->ip_mutex);
485 data_datum = gdbm_fetch(data->ip, key_datum);
486 pthread_mutex_unlock(&data->ip_mutex);
487 if (data_datum.dptr != NULL){
488 memcpy(&num, data_datum.dptr, sizeof(int));
489 free(data_datum.dptr);
492 DEBUG("rlm_ippool: num: %d",num);
493 data_datum.dptr = (int *) #
494 data_datum.dsize = sizeof(int);
495 pthread_mutex_lock(&data->ip_mutex);
496 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
497 pthread_mutex_unlock(&data->ip_mutex);
499 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
500 data->ip_index, gdbm_strerror(gdbm_errno));
501 return RLM_MODULE_FAIL;
508 * If there is a Framed-IP-Address attribute in the reply do nothing
510 if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS) != NULL)
511 return RLM_MODULE_NOOP;
514 * Walk through the database searching for an active=0 entry.
517 pthread_mutex_lock(&data->session_mutex);
518 key_datum = gdbm_firstkey(data->gdbm);
519 while(key_datum.dptr){
520 data_datum = gdbm_fetch(data->gdbm, key_datum);
521 if (data_datum.dptr){
522 memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
523 free(data_datum.dptr);
525 * If we find an entry for the same caller-id and nas with active=1
526 * then we use that for multilink (MPPP) to work properly.
528 if (cli != NULL && strcmp(entry.cli,cli) == 0 && entry.active){
529 memcpy(&key,key_datum.dptr,sizeof(ippool_key));
530 if (!strcmp(key.nas,nas))
533 if (entry.active == 0){
536 tmp.dptr = (uint32_t *) &entry.ipaddr;
537 tmp.dsize = sizeof(uint32_t);
538 pthread_mutex_lock(&data->ip_mutex);
539 data_datum = gdbm_fetch(data->ip, tmp);
540 pthread_mutex_unlock(&data->ip_mutex);
543 * If we find an entry in the ip index and the number is zero (meaning
544 * that we haven't allocated the same ip address to another nas/port pair)
545 * or if we don't find an entry then delete the session entry so
546 * that we can change the key (nas/port)
547 * Else we don't delete the session entry since we haven't yet deallocated the
548 * corresponding ip address and we continue our search.
551 if (data_datum.dptr){
552 memcpy(&num,data_datum.dptr, sizeof(int));
553 free(data_datum.dptr);
565 nextkey = gdbm_nextkey(data->gdbm, key_datum);
566 free(key_datum.dptr);
569 pthread_mutex_unlock(&data->session_mutex);
571 * If we have found a free entry set active to 1 then add a Framed-IP-Address attribute to
576 data_datum.dptr = (ippool_info *) &entry;
577 data_datum.dsize = sizeof(ippool_info);
581 * Delete the entry so that we can change the key
583 pthread_mutex_lock(&data->session_mutex);
584 gdbm_delete(data->gdbm, key_datum);
585 pthread_mutex_unlock(&data->session_mutex);
587 free(key_datum.dptr);
588 memset(key.nas,0,MAX_NAS_NAME_SIZE);
589 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE - 1);
591 key_datum.dptr = (ippool_key *) &key;
592 key_datum.dsize = sizeof(ippool_key);
594 DEBUG2("rlm_ippool: Allocating ip to nas/port: %s/%d",key.nas,key.port);
595 pthread_mutex_lock(&data->session_mutex);
596 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
597 pthread_mutex_unlock(&data->session_mutex);
599 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
600 data->session_db, gdbm_strerror(gdbm_errno));
601 return RLM_MODULE_FAIL;
604 /* Increase the ip index count */
605 key_datum.dptr = (uint32_t *) &entry.ipaddr;
606 key_datum.dsize = sizeof(uint32_t);
607 pthread_mutex_lock(&data->ip_mutex);
608 data_datum = gdbm_fetch(data->ip, key_datum);
609 pthread_mutex_unlock(&data->ip_mutex);
610 if (data_datum.dptr){
611 memcpy(&num,data_datum.dptr,sizeof(int));
612 free(data_datum.dptr);
615 DEBUG("rlm_ippool: num: %d",num);
616 data_datum.dptr = (int *) #
617 data_datum.dsize = sizeof(int);
618 pthread_mutex_lock(&data->ip_mutex);
619 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
620 pthread_mutex_unlock(&data->ip_mutex);
622 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
623 data->ip_index, gdbm_strerror(gdbm_errno));
624 return RLM_MODULE_FAIL;
628 DEBUG("rlm_ippool: Allocated ip %s to client on nas %s,port %d",ip_ntoa(str,entry.ipaddr),
630 if ((vp = paircreate(PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR)) == NULL) {
631 radlog(L_ERR|L_CONS, "no memory");
632 return RLM_MODULE_NOOP;
634 vp->lvalue = entry.ipaddr;
635 ip_ntoa(vp->strvalue, vp->lvalue);
636 pairadd(&request->reply->vps, vp);
639 * If there is no Framed-Netmask attribute in the
642 if (pairfind(request->reply->vps, PW_FRAMED_IP_NETMASK) == NULL) {
643 if ((vp = paircreate(PW_FRAMED_IP_NETMASK, PW_TYPE_IPADDR)) == NULL)
644 radlog(L_ERR|L_CONS, "no memory");
646 vp->lvalue = ntohl(data->netmask);
647 ip_ntoa(vp->strvalue, vp->lvalue);
648 pairadd(&request->reply->vps, vp);
654 DEBUG("rlm_ippool: No available ip addresses in pool.");
655 return RLM_MODULE_NOOP;
658 return RLM_MODULE_OK;
661 static int ippool_detach(void *instance)
663 rlm_ippool_t *data = (rlm_ippool_t *) instance;
665 gdbm_close(data->gdbm);
666 gdbm_close(data->ip);
667 free(data->session_db);
668 free(data->ip_index);
669 pthread_mutex_destroy(&data->session_mutex);
670 pthread_mutex_destroy(&data->ip_mutex);
677 * The module name should be the only globally exported symbol.
678 * That is, everything else should be 'static'.
680 * If the module needs to temporarily modify it's instantiation
681 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
682 * The server will then take care of ensuring that the module
683 * is single-threaded.
685 module_t rlm_ippool = {
687 RLM_TYPE_THREAD_SAFE, /* type */
688 NULL, /* initialization */
689 ippool_instantiate, /* instantiation */
691 NULL, /* authentication */
692 NULL, /* authorization */
693 NULL, /* preaccounting */
694 ippool_accounting, /* accounting */
695 NULL, /* checksimul */
696 NULL, /* pre-proxy */
697 NULL, /* post-proxy */
698 ippool_postauth /* post-auth */
700 ippool_detach, /* detach */