Updated API for 2.2.
[freeradius.git] / src / modules / rlm_ippool / rlm_ippool.c
index 90f6321..6d811ea 100644 (file)
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
- * Copyright 2001  The FreeRADIUS server project
+ * Copyright 2001,2006  The FreeRADIUS server project
  * Copyright 2002  Kostas Kalevras <kkalev@noc.ntua.gr>
- * 
+ *
  * March 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
  * - Initial release
  * April 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
  * - Fail if we don't find nas port information
  * Oct 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
  * - Do a memset(0) on the key.nas before doing searches. Nusty bug
+ * Jul 2003, Kostas Kalevras <kkalev@noc.ntua.gr>
+ * - Make Multilink work this time
+ * - Instead of locking file operations, lock transactions. That means we only keep
+ *   one big transaction lock instead of per file locks (mutexes).
+ * Sep 2003, Kostas Kalevras <kkalev@noc.ntua.gr>
+ * - Fix postauth to not leak ip's
+ *   Add an extra attribute in each entry <char extra> signifying if we need to delete this
+ *   entry in the accounting phase. This is only true in case we are doing MPPP
+ *   Various other code changes. Code comments should explain things
+ *   Highly experimental at this phase.
+ * Mar 2004, Kostas Kalevras <kkalev@noc.ntua.gr>
+ * - Add a timestamp and a timeout attribute in ippool_info. When we assign an ip we set timestamp
+ *   to request->timestamp and timeout to %{Session-Timeout:-0}. When we search for a free entry
+ *   we check if timeout has expired. If it has then we free the entry. We also add a maximum
+ *   timeout configuration directive. If it is non zero then we also use that one to free entries.
+ * Jul 2004, Kostas Kalevras <kkalev@noc.ntua.gr>
+ * - If Pool-Name is set to DEFAULT then always run.
+ * Mar 2005, Kostas Kalevras <kkalev@noc.ntua.gr>
+ * - Make the key an MD5 of a configurable xlated string. This closes Bug #42
  */
 
-#include "config.h"
-#include "autoconf.h"
-#include "libradius.h"
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+
+#include "config.h"
 #include <ctype.h>
 
-#include "radiusd.h"
-#include "modules.h"
-#include "conffile.h"
+#include "../../include/md5.h"
 
 #include <gdbm.h>
-#include <time.h>
-#include <netinet/in.h>
 
 #ifdef NEEDS_GDBM_SYNC
 #      define GDBM_SYNCOPT GDBM_SYNC
 #define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT)
 #endif
 
-#define ALL_ONES 4294967295
 #define MAX_NAS_NAME_SIZE 64
 
-static const char rcsid[] = "$Id$";
-
 /*
  *     Define a structure for our module configuration.
  *
@@ -81,25 +93,41 @@ typedef struct rlm_ippool_t {
        char *session_db;
        char *ip_index;
        char *name;
+       char *key;
        uint32_t range_start;
        uint32_t range_stop;
        uint32_t netmask;
+       time_t max_timeout;
        int cache_size;
+       int override;
        GDBM_FILE gdbm;
        GDBM_FILE ip;
-       pthread_mutex_t session_mutex;
-       pthread_mutex_t ip_mutex;
+#ifdef HAVE_PTHREAD_H
+       pthread_mutex_t op_mutex;
+#endif
 } rlm_ippool_t;
 
+#ifndef HAVE_PTHREAD_H
+/*
+ *     This is easier than ifdef's throughout the code.
+ */
+#define pthread_mutex_init(_x, _y)
+#define pthread_mutex_destroy(_x)
+#define pthread_mutex_lock(_x)
+#define pthread_mutex_unlock(_x)
+#endif
+
 typedef struct ippool_info {
        uint32_t        ipaddr;
        char            active;
        char            cli[32];
+       char            extra;
+       time_t          timestamp;
+       time_t          timeout;
 } ippool_info;
 
 typedef struct ippool_key {
-       char nas[MAX_NAS_NAME_SIZE];
-       int port;
+       char key[16];
 } ippool_key;
 
 /*
@@ -111,17 +139,19 @@ typedef struct ippool_key {
  *     to the strdup'd string into 'config.string'.  This gets around
  *     buffer over-flows.
  */
-static CONF_PARSER module_config[] = {
+static const CONF_PARSER module_config[] = {
   { "session-db", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,session_db), NULL, NULL },
   { "ip-index", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,ip_index), NULL, NULL },
+  { "key", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,key), NULL, "%{NAS-IP-Address} %{NAS-Port}" },
   { "range-start", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_start), NULL, "0" },
   { "range-stop", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_stop), NULL, "0" },
   { "netmask", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,netmask), NULL, "0" },
   { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,cache_size), NULL, "1000" },
+  { "override", PW_TYPE_BOOLEAN, offsetof(rlm_ippool_t,override), NULL, "no" },
+  { "maximum-timeout", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,max_timeout), NULL, "0" },
   { NULL, -1, 0, NULL, NULL }
 };
 
-
 /*
  *     Do any per-module initialization that is separate to each
  *     configured instance of the module.  e.g. set up connections
@@ -140,14 +170,17 @@ static int ippool_instantiate(CONF_SECTION *conf, void **instance)
        ippool_key key;
        datum key_datum;
        datum data_datum;
-       int i,j;
        const char *cli = "0";
-       char *pool_name = NULL;
-       
+       const char *pool_name = NULL;
+
        /*
         *      Set up a storage area for instance data
         */
        data = rad_malloc(sizeof(*data));
+       if (!data) {
+               return -1;
+       }
+       memset(data, 0, sizeof(*data));
 
        /*
         *      If the configuration parameters can't be parsed, then
@@ -178,7 +211,7 @@ static int ippool_instantiate(CONF_SECTION *conf, void **instance)
                free(data);
                return -1;
        }
-       
+
        data->gdbm = gdbm_open(data->session_db, sizeof(int),
                        GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
        if (data->gdbm == NULL) {
@@ -206,41 +239,49 @@ static int ippool_instantiate(CONF_SECTION *conf, void **instance)
                         * active = 0
                         */
                int rcode;
+               uint32_t i, j;
                uint32_t or_result;
                char str[32];
-               const char *nas_init = "NOT_EXIST";
+               char init_str[17];
 
                DEBUG("rlm_ippool: Initializing database");
-               for(i=data->range_start,j=-1;i<=data->range_stop;i++,j--){
+               for(i=data->range_start,j=~0;i<=data->range_stop;i++,j--){
 
                        /*
                         * Net and Broadcast addresses are excluded
                         */
                        or_result = i | data->netmask;
-                       if (or_result == data->netmask || or_result == ALL_ONES){
-                               DEBUG("rlm_ippool: IP %s exlcluded",ip_ntoa(str,ntohl(i)));
+                       if (~data->netmask != 0 &&
+                               (or_result == data->netmask ||
+                           (~or_result == 0))) {
+                               DEBUG("rlm_ippool: IP %s excluded",
+                                     ip_ntoa(str, ntohl(i)));
                                continue;
                        }
-                       
-                       strcpy(key.nas, nas_init);
-                       key.port = j;
-                       key_datum.dptr = (ippool_key *) &key;
+
+                       sprintf(init_str,"%016d",j);
+                       DEBUG("rlm_ippool: Initialized bucket: %s",init_str);
+                       memcpy(key.key, init_str,16);
+                       key_datum.dptr = (char *) &key;
                        key_datum.dsize = sizeof(ippool_key);
 
                        entry.ipaddr = ntohl(i);
                        entry.active = 0;
+                       entry.extra = 0;
+                       entry.timestamp = 0;
+                       entry.timeout = 0;
                        strcpy(entry.cli,cli);
 
-                       data_datum.dptr = (ippool_info *) &entry;
+                       data_datum.dptr = (char *) &entry;
                        data_datum.dsize = sizeof(ippool_info);
 
                        rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
                        if (rcode < 0) {
                                radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
                                                data->session_db, gdbm_strerror(gdbm_errno));
-                               free(data);
                                gdbm_close(data->gdbm);
                                gdbm_close(data->ip);
+                               free(data);
                                return -1;
                        }
                }
@@ -253,76 +294,71 @@ static int ippool_instantiate(CONF_SECTION *conf, void **instance)
        pool_name = cf_section_name2(conf);
        if (pool_name != NULL)
                data->name = strdup(pool_name);
-       pthread_mutex_init(&data->session_mutex, NULL);
-       pthread_mutex_init(&data->ip_mutex, NULL);
 
+       pthread_mutex_init(&data->op_mutex, NULL);
        *instance = data;
-       
+
        return 0;
 }
 
 
 /*
  *     Check for an Accounting-Stop
- *     If we find one and we have allocated an IP to this nas/port combination, deallocate it. 
+ *     If we find one and we have allocated an IP to this nas/port combination, deallocate it.
  */
 static int ippool_accounting(void *instance, REQUEST *request)
 {
        rlm_ippool_t *data = (rlm_ippool_t *)instance;
        datum key_datum;
        datum data_datum;
+       datum save_datum;
        int acctstatustype = 0;
-       int port = -1;
        int rcode;
-       char nas[MAX_NAS_NAME_SIZE];
        ippool_info entry;
        ippool_key key;
        int num = 0;
        VALUE_PAIR *vp;
        char str[32];
+       uint8_t key_str[17];
+       char hex_str[35];
+       char xlat_str[MAX_STRING_LEN];
+       FR_MD5_CTX md5_context;
 
 
-       if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL)
-               acctstatustype = vp->lvalue;
+       if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0)) != NULL)
+               acctstatustype = vp->vp_integer;
        else {
-               DEBUG("rlm_ippool: Could not find account status type in packet.");
+               RDEBUG("Could not find account status type in packet. Return NOOP.");
                return RLM_MODULE_NOOP;
        }
        switch(acctstatustype){
                case PW_STATUS_STOP:
-                       if ((vp = pairfind(request->packet->vps, PW_NAS_PORT_ID)) != NULL)
-                               port = vp->lvalue;
-                       else {
-                               DEBUG("rlm_ippool: Could not find port number in packet.");
+                       if (!radius_xlat(xlat_str,MAX_STRING_LEN,data->key, request, NULL)){
+                               RDEBUG("xlat on the 'key' directive failed");
                                return RLM_MODULE_NOOP;
                        }
-                       if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
-                               strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
-                       else {
-                               if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
-                                       strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
-                               else {
-                                       DEBUG("rlm_ippool: Could not find nas information in packet.");
-                                       return RLM_MODULE_NOOP;
-                               }
-                       }
+                       fr_MD5Init(&md5_context);
+                       fr_MD5Update(&md5_context, xlat_str, strlen(xlat_str));
+                       fr_MD5Final(key_str, &md5_context);
+                       key_str[16] = '\0';
+                       fr_bin2hex(key_str,hex_str,16);
+                       hex_str[32] = '\0';
+                       RDEBUG("MD5 on 'key' directive maps to: %s",hex_str);
+                       memcpy(key.key,key_str,16);
                        break;
                default:
                        /* We don't care about any other accounting packet */
+                       RDEBUG("This is not an Accounting-Stop. Return NOOP.");
 
                        return RLM_MODULE_NOOP;
        }
 
-       memset(key.nas,0,MAX_NAS_NAME_SIZE);
-       strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
-       key.port = port;
-       DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%d",key.nas,key.port);
-       key_datum.dptr = (ippool_key *) &key;
+       RDEBUG("Searching for an entry for key: '%s'",xlat_str);
+       key_datum.dptr = (char *) &key;
        key_datum.dsize = sizeof(ippool_key);
 
-       pthread_mutex_lock(&data->session_mutex);
+       pthread_mutex_lock(&data->op_mutex);
        data_datum = gdbm_fetch(data->gdbm, key_datum);
-       pthread_mutex_unlock(&data->session_mutex);
        if (data_datum.dptr != NULL){
 
                /*
@@ -330,50 +366,65 @@ static int ippool_accounting(void *instance, REQUEST *request)
                 */
                memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
                free(data_datum.dptr);
-               DEBUG("rlm_ippool: Deallocated entry for ip/port: %s/%d",ip_ntoa(str,entry.ipaddr),port);
+               RDEBUG("Deallocated entry for ip: %s",ip_ntoa(str,entry.ipaddr));
                entry.active = 0;
+               entry.timestamp = 0;
+               entry.timeout = 0;
+
+               /*
+                * Save the reference to the entry
+                */
+               save_datum.dptr = key_datum.dptr;
+               save_datum.dsize = key_datum.dsize;
 
-               data_datum.dptr = (ippool_info *) &entry;
+               data_datum.dptr = (char *) &entry;
                data_datum.dsize = sizeof(ippool_info);
 
-               pthread_mutex_lock(&data->session_mutex);
                rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
-               pthread_mutex_unlock(&data->session_mutex);
                if (rcode < 0) {
                        radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
                                        data->session_db, gdbm_strerror(gdbm_errno));
+                       pthread_mutex_unlock(&data->op_mutex);
                        return RLM_MODULE_FAIL;
                }
 
                /*
                 * Decrease allocated count from the ip index
                 */
-               key_datum.dptr = (uint32_t *) &entry.ipaddr;
+               key_datum.dptr = (char *) &entry.ipaddr;
                key_datum.dsize = sizeof(uint32_t);
-               pthread_mutex_lock(&data->ip_mutex);
                data_datum = gdbm_fetch(data->ip, key_datum);
-               pthread_mutex_unlock(&data->ip_mutex);
                if (data_datum.dptr != NULL){
                        memcpy(&num, data_datum.dptr, sizeof(int));
                        free(data_datum.dptr);
                        if (num >0){
                                num--;
-                               DEBUG("rlm_ippool: num: %d",num);
-                               data_datum.dptr = (int *) &num;
+                               RDEBUG("num: %d",num);
+                               data_datum.dptr = (char *) &num;
                                data_datum.dsize = sizeof(int);
-                               pthread_mutex_lock(&data->ip_mutex);
                                rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
-                               pthread_mutex_unlock(&data->ip_mutex);
                                if (rcode < 0) {
                                        radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
                                                        data->ip_index, gdbm_strerror(gdbm_errno));
+                                       pthread_mutex_unlock(&data->op_mutex);
                                        return RLM_MODULE_FAIL;
                                }
+                               if (num >0 && entry.extra == 1){
+                                       /*
+                                        * We are doing MPPP and we still have nas/port entries referencing
+                                        * this ip. Delete this entry so that eventually we only keep one
+                                        * reference to this ip.
+                                        */
+                                       gdbm_delete(data->gdbm,save_datum);
+                               }
                        }
                }
+               pthread_mutex_unlock(&data->op_mutex);
+       }
+       else{
+               pthread_mutex_unlock(&data->op_mutex);
+               RDEBUG("Entry not found");
        }
-       else
-               DEBUG("rlm_ippool: Entry not found");
 
        return RLM_MODULE_OK;
 }
@@ -381,19 +432,25 @@ static int ippool_accounting(void *instance, REQUEST *request)
 static int ippool_postauth(void *instance, REQUEST *request)
 {
        rlm_ippool_t *data = (rlm_ippool_t *) instance;
-       int port = 0;
        int delete = 0;
+       int found = 0;
+       int mppp = 0;
+       int extra = 0;
        int rcode;
        int num = 0;
-       char nas[MAX_NAS_NAME_SIZE];
        datum key_datum;
        datum nextkey;
        datum data_datum;
+       datum save_datum;
        ippool_key key;
        ippool_info entry;
        VALUE_PAIR *vp;
        char *cli = NULL;
        char str[32];
+       uint8_t key_str[17];
+       char hex_str[35];
+       char xlat_str[MAX_STRING_LEN];
+       FR_MD5_CTX md5_context;
 
 
        /* quiet the compiler */
@@ -403,239 +460,340 @@ static int ippool_postauth(void *instance, REQUEST *request)
        /* Check if Pool-Name attribute exists. If it exists check our name and
         * run only if they match
         */
-       if ((vp = pairfind(request->config_items, PW_POOL_NAME)) != NULL){
-               if (data->name == NULL || strcmp(data->name,vp->strvalue))
+       if ((vp = pairfind(request->config_items, PW_POOL_NAME, 0)) != NULL){
+               if (data->name == NULL || (strcmp(data->name,vp->vp_strvalue) && strcmp(vp->vp_strvalue,"DEFAULT")))
                        return RLM_MODULE_NOOP;
        } else {
-               DEBUG("rlm_ippool: Could not find Pool-Name attribute.");
+               RDEBUG("Could not find Pool-Name attribute.");
                return RLM_MODULE_NOOP;
        }
 
-       /*
-        * Get the nas ip address
-        * If not fail
-        */
-       if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
-               strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
-       else{
-               if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
-                       strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
-               else{
-                       DEBUG("rlm_ippool: Could not find nas information.");
-                       return RLM_MODULE_NOOP;
-               }
-       }
 
        /*
         * Find the caller id
         */
-       if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
-               cli = vp->strvalue;
+       if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0)) != NULL)
+               cli = vp->vp_strvalue;
 
-       /*
-        * Find the port
-        * If not fail
-        */
-       if ((vp = pairfind(request->packet->vps, PW_NAS_PORT_ID)) != NULL)
-               port = vp->lvalue;
-       else{
-               DEBUG("rlm_ippool: Could not find port information.");
+
+       if (!radius_xlat(xlat_str,MAX_STRING_LEN,data->key, request, NULL)){
+               RDEBUG("xlat on the 'key' directive failed");
                return RLM_MODULE_NOOP;
        }
-
-       memset(key.nas,0,MAX_NAS_NAME_SIZE);
-       strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
-       key.port = port;        
-       DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%d",key.nas,key.port);
-       key_datum.dptr = (ippool_key *) &key;
+       fr_MD5Init(&md5_context);
+       fr_MD5Update(&md5_context, xlat_str, strlen(xlat_str));
+       fr_MD5Final(key_str, &md5_context);
+       key_str[16] = '\0';
+       fr_bin2hex(key_str,hex_str,16);
+       hex_str[32] = '\0';
+       RDEBUG("MD5 on 'key' directive maps to: %s",hex_str);
+       memcpy(key.key,key_str,16);
+
+       RDEBUG("Searching for an entry for key: '%s'",hex_str);
+       key_datum.dptr = (char *) &key;
        key_datum.dsize = sizeof(ippool_key);
 
-       pthread_mutex_lock(&data->session_mutex);
+       pthread_mutex_lock(&data->op_mutex);
        data_datum = gdbm_fetch(data->gdbm, key_datum);
-       pthread_mutex_unlock(&data->session_mutex);
        if (data_datum.dptr != NULL){
                /*
                 * If there is a corresponding entry in the database with active=1 it is stale.
                 * Set active to zero
                 */
+               found = 1;
                memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
                free(data_datum.dptr);
                if (entry.active){
-                       DEBUG("rlm_ippool: Found a stale entry for ip/port: %s/%d",ip_ntoa(str,entry.ipaddr),port);
+                       RDEBUG("Found a stale entry for ip: %s",ip_ntoa(str,entry.ipaddr));
                        entry.active = 0;
+                       entry.timestamp = 0;
+                       entry.timeout = 0;
+
+                       /*
+                        * Save the reference to the entry
+                        */
+                       save_datum.dptr = key_datum.dptr;
+                       save_datum.dsize = key_datum.dsize;
 
-                       data_datum.dptr = (ippool_info *) &entry;
+                       data_datum.dptr = (char *) &entry;
                        data_datum.dsize = sizeof(ippool_info);
 
-                       pthread_mutex_lock(&data->session_mutex);
                        rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
-                       pthread_mutex_unlock(&data->session_mutex);
                        if (rcode < 0) {
                                radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
                                        data->session_db, gdbm_strerror(gdbm_errno));
+                               pthread_mutex_unlock(&data->op_mutex);
                                return RLM_MODULE_FAIL;
                        }
                        /* Decrease allocated count from the ip index */
 
-                       key_datum.dptr = (uint32_t *) &entry.ipaddr;
+                       key_datum.dptr = (char *) &entry.ipaddr;
                        key_datum.dsize = sizeof(uint32_t);
-                       pthread_mutex_lock(&data->ip_mutex);
                        data_datum = gdbm_fetch(data->ip, key_datum);
-                       pthread_mutex_unlock(&data->ip_mutex);
                        if (data_datum.dptr != NULL){
                                memcpy(&num, data_datum.dptr, sizeof(int));
                                free(data_datum.dptr);
                                if (num >0){
                                        num--;
-                                       DEBUG("rlm_ippool: num: %d",num);
-                                       data_datum.dptr = (int *) &num;
+                                       RDEBUG("num: %d",num);
+                                       data_datum.dptr = (char *) &num;
                                        data_datum.dsize = sizeof(int);
-                                       pthread_mutex_lock(&data->ip_mutex);
                                        rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
-                                       pthread_mutex_unlock(&data->ip_mutex);
                                        if (rcode < 0) {
                                                radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
                                                                data->ip_index, gdbm_strerror(gdbm_errno));
+                                               pthread_mutex_unlock(&data->op_mutex);
                                                return RLM_MODULE_FAIL;
                                        }
+                                       if (num >0 && entry.extra == 1){
+                                               /*
+                                                * We are doing MPPP and we still have nas/port entries referencing
+                                                * this ip. Delete this entry so that eventually we only keep one
+                                                * reference to this ip.
+                                                */
+                                               gdbm_delete(data->gdbm,save_datum);
+                                       }
                                }
                        }
                }
        }
+
+       pthread_mutex_unlock(&data->op_mutex);
+
        /*
-        * If there is a Framed-IP-Address attribute in the reply do nothing
+        * If there is a Framed-IP-Address attribute in the reply, check for override
         */
-       if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS) != NULL)
-               return RLM_MODULE_NOOP;
+       if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS, 0) != NULL) {
+               RDEBUG("Found Framed-IP-Address attribute in reply attribute list.");
+               if (data->override)
+               {
+                       /* Override supplied Framed-IP-Address */
+                       RDEBUG("override is set to yes. Override the existing Framed-IP-Address attribute.");
+                       pairdelete(&request->reply->vps, PW_FRAMED_IP_ADDRESS, 0);
+               } else {
+                       /* Abort */
+                       RDEBUG("override is set to no. Return NOOP.");
+                       return RLM_MODULE_NOOP;
+               }
+       }
 
        /*
         * Walk through the database searching for an active=0 entry.
+        * We search twice. Once to see if we have an active entry with the same callerid
+        * so that MPPP can work ok and then once again to find a free entry.
         */
 
-       pthread_mutex_lock(&data->session_mutex);
-       key_datum = gdbm_firstkey(data->gdbm);
-       while(key_datum.dptr){
-               data_datum = gdbm_fetch(data->gdbm, key_datum);
-               if (data_datum.dptr){
-                       memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
-                       free(data_datum.dptr);  
-                       /*
-                       * If we find an entry for the same caller-id and nas with active=1
-                       * then we use that for multilink (MPPP) to work properly.
-                       */
-                       if (cli != NULL && strcmp(entry.cli,cli) == 0 && entry.active){
-                               memcpy(&key,key_datum.dptr,sizeof(ippool_key));
-                               if (!strcmp(key.nas,nas))
+       pthread_mutex_lock(&data->op_mutex);
+
+       key_datum.dptr = NULL;
+       if (cli != NULL){
+               key_datum = gdbm_firstkey(data->gdbm);
+               while(key_datum.dptr){
+                       data_datum = gdbm_fetch(data->gdbm, key_datum);
+                       if (data_datum.dptr){
+                               memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
+                               free(data_datum.dptr);
+                               /*
+                               * If we find an entry for the same caller-id with active=1
+                               * then we use that for multilink (MPPP) to work properly.
+                               */
+                               if (strcmp(entry.cli,cli) == 0 && entry.active){
+                                       mppp = 1;
                                        break;
+                               }
                        }
-                       if (entry.active == 0){
-                               datum tmp;              
+                       nextkey = gdbm_nextkey(data->gdbm, key_datum);
+                       free(key_datum.dptr);
+                       key_datum = nextkey;
+               }
+       }
 
-                               tmp.dptr = (uint32_t *) &entry.ipaddr;
-                               tmp.dsize = sizeof(uint32_t);
-                               pthread_mutex_lock(&data->ip_mutex);
-                               data_datum = gdbm_fetch(data->ip, tmp);
-                               pthread_mutex_unlock(&data->ip_mutex);
+       if (key_datum.dptr == NULL){
+               key_datum = gdbm_firstkey(data->gdbm);
+               while(key_datum.dptr){
+                       data_datum = gdbm_fetch(data->gdbm, key_datum);
+                       if (data_datum.dptr){
+                               memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
+                               free(data_datum.dptr);
 
                                /*
-                                * If we find an entry in the ip index and the number is zero (meaning
-                                * that we haven't allocated the same ip address to another nas/port pair)
-                                * or if we don't find an entry then delete the session entry so
-                                * that we can change the key (nas/port)
-                                * Else we don't delete the session entry since we haven't yet deallocated the
-                                * corresponding ip address and we continue our search.
+                                * Find an entry with active == 0
+                                * or an entry that has expired
                                 */
-
-                               if (data_datum.dptr){
-                                       memcpy(&num,data_datum.dptr, sizeof(int));
-                                       free(data_datum.dptr);
-                                       if (num == 0){
+                               if (entry.active == 0 || (entry.timestamp && ((entry.timeout &&
+                               request->timestamp >= (entry.timestamp + entry.timeout)) ||
+                               (data->max_timeout && request->timestamp >= (entry.timestamp + data->max_timeout))))){
+                                       datum tmp;
+
+                                       tmp.dptr = (char *) &entry.ipaddr;
+                                       tmp.dsize = sizeof(uint32_t);
+                                       data_datum = gdbm_fetch(data->ip, tmp);
+
+                                       /*
+                                        * If we find an entry in the ip index and the number is zero (meaning
+                                        * that we haven't allocated the same ip address to another nas/port pair)
+                                        * or if we don't find an entry then delete the session entry so
+                                        * that we can change the key
+                                        * Else we don't delete the session entry since we haven't yet deallocated the
+                                        * corresponding ip address and we continue our search.
+                                        */
+
+                                       if (data_datum.dptr){
+                                               memcpy(&num,data_datum.dptr, sizeof(int));
+                                               free(data_datum.dptr);
+                                               if (num == 0){
+                                                       delete = 1;
+                                                       break;
+                                               }
+                                       }
+                                       else{
                                                delete = 1;
                                                break;
                                        }
                                }
-                               else{
-                                       delete = 1;
-                                       break;
-                               }
                        }
+                       nextkey = gdbm_nextkey(data->gdbm, key_datum);
+                       free(key_datum.dptr);
+                       key_datum = nextkey;
                }
-               nextkey = gdbm_nextkey(data->gdbm, key_datum);
-               free(key_datum.dptr);
-               key_datum = nextkey;
        }
-       pthread_mutex_unlock(&data->session_mutex);
        /*
         * If we have found a free entry set active to 1 then add a Framed-IP-Address attribute to
         * the reply
+        * We keep the operation mutex locked until after we have set the corresponding entry active
         */
        if (key_datum.dptr){
-               entry.active = 1;
-               data_datum.dptr = (ippool_info *) &entry;
-               data_datum.dsize = sizeof(ippool_info);
+               if (found && !mppp){
+                       /*
+                        * Found == 1 means we have the nas/port combination entry in our database
+                        * We exchange the ip address between the nas/port entry and the free entry
+                        * Afterwards we will save the free ip address to the nas/port entry.
+                        * That is:
+                        *  ---------------------------------------------
+                        *  - NAS/PORT Entry  |||| Free Entry  ||| Time
+                        *  -    IP1                 IP2(Free)    BEFORE
+                        *  -    IP2(Free)           IP1          AFTER
+                        *  ---------------------------------------------
+                        *
+                        * We only do this if we are NOT doing MPPP
+                        *
+                        */
+                       datum key_datum_tmp;
+                       datum data_datum_tmp;
+                       ippool_key key_tmp;
+
+                       memcpy(key_tmp.key,key_str,16);
+                       key_datum_tmp.dptr = (char *) &key_tmp;
+                       key_datum_tmp.dsize = sizeof(ippool_key);
+
+                       data_datum_tmp = gdbm_fetch(data->gdbm, key_datum_tmp);
+                       if (data_datum_tmp.dptr != NULL){
 
-               if (delete){
+                               rcode = gdbm_store(data->gdbm, key_datum, data_datum_tmp, GDBM_REPLACE);
+                               free(data_datum_tmp.dptr);
+                               if (rcode < 0) {
+                                       radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
+                                               data->session_db, gdbm_strerror(gdbm_errno));
+                                               pthread_mutex_unlock(&data->op_mutex);
+                                       return RLM_MODULE_FAIL;
+                               }
+                       }
+               }
+               else{
                        /*
-                        * Delete the entry so that we can change the key
-                        */
-                       pthread_mutex_lock(&data->session_mutex);
-                       gdbm_delete(data->gdbm, key_datum);
-                       pthread_mutex_unlock(&data->session_mutex);
+                        * We have not found the nas/port combination
+                        */
+                       if (delete){
+                               /*
+                                * Delete the entry so that we can change the key
+                                * All is well. We delete one entry and we add one entry
+                                */
+                               gdbm_delete(data->gdbm, key_datum);
+                       }
+                       else{
+                               /*
+                                * We are doing MPPP. (mppp should be 1)
+                                * We don't do anything.
+                                * We will create an extra not needed entry in the database in this case
+                                * but we don't really care since we always also use the ip_index database
+                                * when we search for a free entry.
+                                * We will also delete that entry on the accounting section so that we only
+                                * have one nas/port entry referencing each ip
+                                */
+                               if (mppp)
+                                       extra = 1;
+                               if (!mppp)
+                                       radlog(L_ERR, "rlm_ippool: mppp is not one. Please report this behaviour.");
+                       }
                }
                free(key_datum.dptr);
-               memset(key.nas,0,MAX_NAS_NAME_SIZE);
-               strncpy(key.nas,nas,MAX_NAS_NAME_SIZE - 1);
-               key.port = port;
-               key_datum.dptr = (ippool_key *) &key;
+               entry.active = 1;
+               entry.timestamp = request->timestamp;
+               if ((vp = pairfind(request->reply->vps, PW_SESSION_TIMEOUT, 0)) != NULL)
+                       entry.timeout = (time_t) vp->vp_integer;
+               else
+                       entry.timeout = 0;
+               if (extra)
+                       entry.extra = 1;
+               data_datum.dptr = (char *) &entry;
+               data_datum.dsize = sizeof(ippool_info);
+               memcpy(key.key, key_str, 16);
+               key_datum.dptr = (char *) &key;
                key_datum.dsize = sizeof(ippool_key);
-               
-               DEBUG2("rlm_ippool: Allocating ip to nas/port: %s/%d",key.nas,key.port);
-               pthread_mutex_lock(&data->session_mutex);
+
+               DEBUG2("rlm_ippool: Allocating ip to key: '%s'",hex_str);
                rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
-               pthread_mutex_unlock(&data->session_mutex);
                if (rcode < 0) {
                        radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
                                data->session_db, gdbm_strerror(gdbm_errno));
+                               pthread_mutex_unlock(&data->op_mutex);
                        return RLM_MODULE_FAIL;
                }
 
                /* Increase the ip index count */
-               key_datum.dptr = (uint32_t *) &entry.ipaddr;
-               key_datum.dsize = sizeof(uint32_t);     
-               pthread_mutex_lock(&data->ip_mutex);
+               key_datum.dptr = (char *) &entry.ipaddr;
+               key_datum.dsize = sizeof(uint32_t);
                data_datum = gdbm_fetch(data->ip, key_datum);
-               pthread_mutex_unlock(&data->ip_mutex);
                if (data_datum.dptr){
                        memcpy(&num,data_datum.dptr,sizeof(int));
                        free(data_datum.dptr);
-               }
+               } else
+                       num = 0;
                num++;
-               DEBUG("rlm_ippool: num: %d",num);
-               data_datum.dptr = (int *) &num;
+               RDEBUG("num: %d",num);
+               data_datum.dptr = (char *) &num;
                data_datum.dsize = sizeof(int);
-               pthread_mutex_lock(&data->ip_mutex);
                rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
-               pthread_mutex_unlock(&data->ip_mutex);
                if (rcode < 0) {
                        radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
                                data->ip_index, gdbm_strerror(gdbm_errno));
+                       pthread_mutex_unlock(&data->op_mutex);
                        return RLM_MODULE_FAIL;
                }
-                       
+               pthread_mutex_unlock(&data->op_mutex);
 
-               DEBUG("rlm_ippool: Allocated ip %s to client on nas %s,port %d",ip_ntoa(str,entry.ipaddr),
-                               key.nas,port);
-               if ((vp = paircreate(PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR)) == NULL) {
-                       radlog(L_ERR|L_CONS, "no memory");
-                       return RLM_MODULE_NOOP;
+
+               RDEBUG("Allocated ip %s to client key: %s",ip_ntoa(str,entry.ipaddr),hex_str);
+               vp = radius_paircreate(request, &request->reply->vps,
+                                      PW_FRAMED_IP_ADDRESS, 0, PW_TYPE_IPADDR);
+               vp->vp_ipaddr = entry.ipaddr;
+
+               /*
+                *      If there is no Framed-Netmask attribute in the
+                *      reply, add one
+                */
+               if (pairfind(request->reply->vps, PW_FRAMED_IP_NETMASK, 0) == NULL) {
+                       vp = radius_paircreate(request, &request->reply->vps,
+                                              PW_FRAMED_IP_NETMASK, 0,
+                                              PW_TYPE_IPADDR);
+                       vp->vp_ipaddr = ntohl(data->netmask);
                }
-               vp->lvalue = entry.ipaddr;
-               ip_ntoa(vp->strvalue, vp->lvalue);
-               pairadd(&request->reply->vps, vp);
+
        }
        else{
-               DEBUG("rlm_ippool: No available ip addresses in pool.");
-               return RLM_MODULE_NOOP;
+               pthread_mutex_unlock(&data->op_mutex);
+               RDEBUG("No available ip addresses in pool.");
+               return RLM_MODULE_NOTFOUND;
        }
 
        return RLM_MODULE_OK;
@@ -647,10 +805,7 @@ static int ippool_detach(void *instance)
 
        gdbm_close(data->gdbm);
        gdbm_close(data->ip);
-       free(data->session_db);
-       free(data->ip_index);
-       pthread_mutex_destroy(&data->session_mutex);
-       pthread_mutex_destroy(&data->ip_mutex);
+       pthread_mutex_destroy(&data->op_mutex);
 
        free(instance);
        return 0;
@@ -666,10 +821,11 @@ static int ippool_detach(void *instance)
  *     is single-threaded.
  */
 module_t rlm_ippool = {
-       "IPPOOL",       
+       RLM_MODULE_INIT,
+       "ippool",
        RLM_TYPE_THREAD_SAFE,           /* type */
-       NULL,                           /* initialization */
        ippool_instantiate,             /* instantiation */
+       ippool_detach,                  /* detach */
        {
                NULL,                   /* authentication */
                NULL,                   /* authorization */
@@ -680,6 +836,4 @@ module_t rlm_ippool = {
                NULL,                   /* post-proxy */
                ippool_postauth         /* post-auth */
        },
-       ippool_detach,                  /* detach */
-       NULL,                           /* destroy */
 };