import from HEAD:
[freeradius.git] / src / modules / rlm_ippool / rlm_ippool.c
1 /*
2  * rlm_ippool.c
3  *
4  * Version:  $Id$
5  *
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.
10  *
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.
15  *
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
19  *
20  * Copyright 2001  The FreeRADIUS server project
21  * Copyright 2002  Kostas Kalevras <kkalev@noc.ntua.gr>
22  *
23  * March 2002, Kostas Kalevras <kkalev@noc.ntua.gr>
24  * - Initial release
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).
41  * Sep 2003, Kostas Kalevras <kkalev@noc.ntua.gr>
42  * - Fix postauth to not leak ip's
43  *   Add an extra attribute in each entry <char extra> signifying if we need to delete this
44  *   entry in the accounting phase. This is only true in case we are doing MPPP
45  *   Various other code changes. Code comments should explain things
46  *   Highly experimental at this phase.
47  * Mar 2004, Kostas Kalevras <kkalev@noc.ntua.gr>
48  * - Add a timestamp and a timeout attribute in ippool_info. When we assign an ip we set timestamp
49  *   to request->timestamp and timeout to %{Session-Timeout:-0}. When we search for a free entry
50  *   we check if timeout has expired. If it has then we free the entry. We also add a maximum
51  *   timeout configuration directive. If it is non zero then we also use that one to free entries.
52  */
53
54 #include "config.h"
55 #include "autoconf.h"
56
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <ctype.h>
61
62 #ifdef HAVE_SYS_TYPES_H
63 #include <sys/types.h>
64 #endif
65
66 #ifdef HAVE_STDINT_H
67 #include <stdint.h>
68 #endif
69
70 #ifdef HAVE_INTTYPES_H
71 #include <inttypes.h>
72 #endif
73
74 #ifdef HAVE_NETINET_IN_H
75 #include <netinet/in.h>
76 #endif
77
78 #include "radiusd.h"
79 #include "modules.h"
80 #include "conffile.h"
81
82 #include <gdbm.h>
83 #include <time.h>
84
85 #ifdef NEEDS_GDBM_SYNC
86 #       define GDBM_SYNCOPT GDBM_SYNC
87 #else
88 #       define GDBM_SYNCOPT 0
89 #endif
90
91 #ifdef GDBM_NOLOCK
92 #define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT | GDBM_NOLOCK)
93 #else
94 #define GDBM_IPPOOL_OPTS (GDBM_SYNCOPT)
95 #endif
96
97 #define MAX_NAS_NAME_SIZE 64
98
99 static const char rcsid[] = "$Id$";
100
101 /*
102  *      Define a structure for our module configuration.
103  *
104  *      These variables do not need to be in a structure, but it's
105  *      a lot cleaner to do so, and a pointer to the structure can
106  *      be used as the instance handle.
107  */
108 typedef struct rlm_ippool_t {
109         char *session_db;
110         char *ip_index;
111         char *name;
112         uint32_t range_start;
113         uint32_t range_stop;
114         uint32_t netmask;
115         time_t max_timeout;
116         int cache_size;
117         int override;
118         GDBM_FILE gdbm;
119         GDBM_FILE ip;
120 #ifdef HAVE_PTHREAD_H
121         pthread_mutex_t op_mutex;
122 #endif
123 } rlm_ippool_t;
124
125 #ifndef HAVE_PTHREAD_H
126 /*
127  *      This is easier than ifdef's throughout the code.
128  */
129 #define pthread_mutex_init(_x, _y)
130 #define pthread_mutex_destroy(_x)
131 #define pthread_mutex_lock(_x)
132 #define pthread_mutex_unlock(_x)
133 #endif
134
135 typedef struct ippool_info {
136         uint32_t        ipaddr;
137         char            active;
138         char            cli[32];
139         char            extra;
140         time_t          timestamp;
141         time_t          timeout;
142 } ippool_info;
143
144 typedef struct ippool_key {
145         char nas[MAX_NAS_NAME_SIZE];
146         unsigned int port;
147 } ippool_key;
148
149 /*
150  *      A mapping of configuration file names to internal variables.
151  *
152  *      Note that the string is dynamically allocated, so it MUST
153  *      be freed.  When the configuration file parse re-reads the string,
154  *      it free's the old one, and strdup's the new one, placing the pointer
155  *      to the strdup'd string into 'config.string'.  This gets around
156  *      buffer over-flows.
157  */
158 static CONF_PARSER module_config[] = {
159   { "session-db", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,session_db), NULL, NULL },
160   { "ip-index", PW_TYPE_STRING_PTR, offsetof(rlm_ippool_t,ip_index), NULL, NULL },
161   { "range-start", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_start), NULL, "0" },
162   { "range-stop", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,range_stop), NULL, "0" },
163   { "netmask", PW_TYPE_IPADDR, offsetof(rlm_ippool_t,netmask), NULL, "0" },
164   { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,cache_size), NULL, "1000" },
165   { "override", PW_TYPE_BOOLEAN, offsetof(rlm_ippool_t,override), NULL, "no" },
166   { "maximum-timeout", PW_TYPE_INTEGER, offsetof(rlm_ippool_t,max_timeout), NULL, "0" },
167   { NULL, -1, 0, NULL, NULL }
168 };
169
170
171 /*
172  *      Do any per-module initialization that is separate to each
173  *      configured instance of the module.  e.g. set up connections
174  *      to external databases, read configuration files, set up
175  *      dictionary entries, etc.
176  *
177  *      If configuration information is given in the config section
178  *      that must be referenced in later calls, store a handle to it
179  *      in *instance otherwise put a null pointer there.
180  */
181 static int ippool_instantiate(CONF_SECTION *conf, void **instance)
182 {
183         rlm_ippool_t *data;
184         int cache_size;
185         ippool_info entry;
186         ippool_key key;
187         datum key_datum;
188         datum data_datum;
189         int i;
190         unsigned j;
191         const char *cli = "0";
192         char *pool_name = NULL;
193
194         /*
195          *      Set up a storage area for instance data
196          */
197         data = rad_malloc(sizeof(*data));
198         if (!data) {
199                 return -1;
200         }
201         memset(data, 0, sizeof(*data));
202
203         /*
204          *      If the configuration parameters can't be parsed, then
205          *      fail.
206          */
207         if (cf_section_parse(conf, data, module_config) < 0) {
208                 free(data);
209                 return -1;
210         }
211         cache_size = data->cache_size;
212
213         if (data->session_db == NULL) {
214                 radlog(L_ERR, "rlm_ippool: 'session-db' must be set.");
215                 free(data);
216                 return -1;
217         }
218         if (data->ip_index == NULL) {
219                 radlog(L_ERR, "rlm_ippool: 'ip-index' must be set.");
220                 free(data);
221                 return -1;
222         }
223         data->range_start = htonl(data->range_start);
224         data->range_stop = htonl(data->range_stop);
225         data->netmask = htonl(data->netmask);
226         if (data->range_start == 0 || data->range_stop == 0 || \
227                          data->range_start >= data->range_stop ) {
228                 radlog(L_ERR, "rlm_ippool: Invalid configuration data given.");
229                 free(data);
230                 return -1;
231         }
232
233         data->gdbm = gdbm_open(data->session_db, sizeof(int),
234                         GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
235         if (data->gdbm == NULL) {
236                 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
237                                 data->session_db, strerror(errno));
238                 return -1;
239         }
240         data->ip = gdbm_open(data->ip_index, sizeof(int),
241                         GDBM_WRCREAT | GDBM_IPPOOL_OPTS, 0600, NULL);
242         if (data->ip == NULL) {
243                 radlog(L_ERR, "rlm_ippool: Failed to open file %s: %s",
244                                 data->ip_index, strerror(errno));
245                 return -1;
246         }
247         if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
248                 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
249         if (gdbm_setopt(data->ip, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
250                 radlog(L_ERR, "rlm_ippool: Failed to set cache size");
251
252         key_datum = gdbm_firstkey(data->gdbm);
253         if (key_datum.dptr == NULL){
254                         /*
255                          * If the database does not exist initialize it.
256                          * We set the nas/port pairs to not existent values and
257                          * active = 0
258                          */
259                 int rcode;
260                 uint32_t or_result;
261                 char str[32];
262                 const char *nas_init = "NOT_EXIST";
263
264                 DEBUG("rlm_ippool: Initializing database");
265                 for(i=data->range_start,j=~0;i<=data->range_stop;i++,j--){
266
267                         /*
268                          * Net and Broadcast addresses are excluded
269                          */
270                         or_result = i | data->netmask;
271                         if (~data->netmask != 0 &&
272                                 (or_result == data->netmask ||
273                             (~or_result == 0))) {
274                                 DEBUG("rlm_ippool: IP %s excluded",
275                                       ip_ntoa(str, ntohl(i)));
276                                 continue;
277                         }
278
279                         strcpy(key.nas, nas_init);
280                         key.port = j;
281                         key_datum.dptr = (char *) &key;
282                         key_datum.dsize = sizeof(ippool_key);
283
284                         entry.ipaddr = ntohl(i);
285                         entry.active = 0;
286                         entry.extra = 0;
287                         entry.timestamp = 0;
288                         entry.timeout = 0;
289                         strcpy(entry.cli,cli);
290
291                         data_datum.dptr = (char *) &entry;
292                         data_datum.dsize = sizeof(ippool_info);
293
294                         rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
295                         if (rcode < 0) {
296                                 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
297                                                 data->session_db, gdbm_strerror(gdbm_errno));
298                                 free(data);
299                                 gdbm_close(data->gdbm);
300                                 gdbm_close(data->ip);
301                                 return -1;
302                         }
303                 }
304         }
305         else
306                 free(key_datum.dptr);
307
308         /* Add the ip pool name */
309         data->name = NULL;
310         pool_name = cf_section_name2(conf);
311         if (pool_name != NULL)
312                 data->name = strdup(pool_name);
313
314         pthread_mutex_init(&data->op_mutex, NULL);
315         *instance = data;
316
317         return 0;
318 }
319
320
321 /*
322  *      Check for an Accounting-Stop
323  *      If we find one and we have allocated an IP to this nas/port combination, deallocate it.
324  */
325 static int ippool_accounting(void *instance, REQUEST *request)
326 {
327         rlm_ippool_t *data = (rlm_ippool_t *)instance;
328         datum key_datum;
329         datum data_datum;
330         datum save_datum;
331         int acctstatustype = 0;
332         unsigned int port = ~0;
333         int rcode;
334         char nas[MAX_NAS_NAME_SIZE];
335         ippool_info entry;
336         ippool_key key;
337         int num = 0;
338         VALUE_PAIR *vp;
339         char str[32];
340
341
342         if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL)
343                 acctstatustype = vp->lvalue;
344         else {
345                 DEBUG("rlm_ippool: Could not find account status type in packet. Return NOOP.");
346                 return RLM_MODULE_NOOP;
347         }
348         switch(acctstatustype){
349                 case PW_STATUS_STOP:
350                         if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
351                                 port = vp->lvalue;
352                         else {
353                                 DEBUG("rlm_ippool: Could not find port number in packet. Return NOOP.");
354                                 return RLM_MODULE_NOOP;
355                         }
356                         if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
357                                 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
358                         else {
359                                 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
360                                         strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
361                                 else {
362                                         DEBUG("rlm_ippool: Could not find nas information in packet. Return NOOP.");
363                                         return RLM_MODULE_NOOP;
364                                 }
365                         }
366                         break;
367                 default:
368                         /* We don't care about any other accounting packet */
369                         DEBUG("rlm_ippool: This is not an Accounting-Stop. Return NOOP.");
370
371                         return RLM_MODULE_NOOP;
372         }
373
374         memset(key.nas,0,MAX_NAS_NAME_SIZE);
375         strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
376         key.port = port;
377         DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%u",key.nas,key.port);
378         key_datum.dptr = (char *) &key;
379         key_datum.dsize = sizeof(ippool_key);
380
381         pthread_mutex_lock(&data->op_mutex);
382         data_datum = gdbm_fetch(data->gdbm, key_datum);
383         if (data_datum.dptr != NULL){
384
385                 /*
386                  * If the entry was found set active to zero
387                  */
388                 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
389                 free(data_datum.dptr);
390                 DEBUG("rlm_ippool: Deallocated entry for ip/port: %s/%u",ip_ntoa(str,entry.ipaddr),port);
391                 entry.active = 0;
392                 entry.timestamp = 0;
393                 entry.timeout = 0;
394
395                 /*
396                  * Save the reference to the entry
397                  */
398                 save_datum.dptr = key_datum.dptr;
399                 save_datum.dsize = key_datum.dsize;
400
401                 data_datum.dptr = (char *) &entry;
402                 data_datum.dsize = sizeof(ippool_info);
403
404                 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
405                 if (rcode < 0) {
406                         radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
407                                         data->session_db, gdbm_strerror(gdbm_errno));
408                         pthread_mutex_unlock(&data->op_mutex);
409                         return RLM_MODULE_FAIL;
410                 }
411
412                 /*
413                  * Decrease allocated count from the ip index
414                  */
415                 key_datum.dptr = (char *) &entry.ipaddr;
416                 key_datum.dsize = sizeof(uint32_t);
417                 data_datum = gdbm_fetch(data->ip, key_datum);
418                 if (data_datum.dptr != NULL){
419                         memcpy(&num, data_datum.dptr, sizeof(int));
420                         free(data_datum.dptr);
421                         if (num >0){
422                                 num--;
423                                 DEBUG("rlm_ippool: num: %d",num);
424                                 data_datum.dptr = (char *) &num;
425                                 data_datum.dsize = sizeof(int);
426                                 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
427                                 if (rcode < 0) {
428                                         radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
429                                                         data->ip_index, gdbm_strerror(gdbm_errno));
430                                         pthread_mutex_unlock(&data->op_mutex);
431                                         return RLM_MODULE_FAIL;
432                                 }
433                                 if (num >0 && entry.extra == 1){
434                                         /*
435                                          * We are doing MPPP and we still have nas/port entries referencing
436                                          * this ip. Delete this entry so that eventually we only keep one
437                                          * reference to this ip.
438                                          */
439                                         gdbm_delete(data->gdbm,save_datum);
440                                 }
441                         }
442                 }
443                 pthread_mutex_unlock(&data->op_mutex);
444         }
445         else{
446                 pthread_mutex_unlock(&data->op_mutex);
447                 DEBUG("rlm_ippool: Entry not found");
448         }
449
450         return RLM_MODULE_OK;
451 }
452
453 static int ippool_postauth(void *instance, REQUEST *request)
454 {
455         rlm_ippool_t *data = (rlm_ippool_t *) instance;
456         unsigned int port = 0;
457         int delete = 0;
458         int found = 0;
459         int mppp = 0;
460         int extra = 0;
461         int rcode;
462         int num = 0;
463         char nas[MAX_NAS_NAME_SIZE];
464         datum key_datum;
465         datum nextkey;
466         datum data_datum;
467         datum save_datum;
468         ippool_key key;
469         ippool_info entry;
470         VALUE_PAIR *vp;
471         char *cli = NULL;
472         char str[32];
473
474
475         /* quiet the compiler */
476         instance = instance;
477         request = request;
478
479         /* Check if Pool-Name attribute exists. If it exists check our name and
480          * run only if they match
481          */
482         if ((vp = pairfind(request->config_items, PW_POOL_NAME)) != NULL){
483                 if (data->name == NULL || strcmp(data->name,vp->strvalue))
484                         return RLM_MODULE_NOOP;
485         } else {
486                 DEBUG("rlm_ippool: Could not find Pool-Name attribute.");
487                 return RLM_MODULE_NOOP;
488         }
489
490         /*
491          * Get the nas ip address
492          * If not fail
493          */
494         if ((vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
495                 strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
496         else{
497                 if ((vp = pairfind(request->packet->vps, PW_NAS_IDENTIFIER)) != NULL)
498                         strncpy(nas, vp->strvalue, MAX_NAS_NAME_SIZE - 1);
499                 else{
500                         DEBUG("rlm_ippool: Could not find nas information. Return NOOP.");
501                         return RLM_MODULE_NOOP;
502                 }
503         }
504
505         /*
506          * Find the caller id
507          */
508         if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
509                 cli = vp->strvalue;
510
511         /*
512          * Find the port
513          * If not fail
514          */
515         if ((vp = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL)
516                 port = vp->lvalue;
517         else{
518                 DEBUG("rlm_ippool: Could not find nas port information. Return NOOP.");
519                 return RLM_MODULE_NOOP;
520         }
521
522         memset(key.nas,0,MAX_NAS_NAME_SIZE);
523         strncpy(key.nas,nas,MAX_NAS_NAME_SIZE -1 );
524         key.port = port;
525         DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%u",key.nas,key.port);
526         key_datum.dptr = (char *) &key;
527         key_datum.dsize = sizeof(ippool_key);
528
529         pthread_mutex_lock(&data->op_mutex);
530         data_datum = gdbm_fetch(data->gdbm, key_datum);
531         if (data_datum.dptr != NULL){
532                 /*
533                  * If there is a corresponding entry in the database with active=1 it is stale.
534                  * Set active to zero
535                  */
536                 found = 1;
537                 memcpy(&entry, data_datum.dptr, sizeof(ippool_info));
538                 free(data_datum.dptr);
539                 if (entry.active){
540                         DEBUG("rlm_ippool: Found a stale entry for ip/port: %s/%u",ip_ntoa(str,entry.ipaddr),port);
541                         entry.active = 0;
542                         entry.timestamp = 0;
543                         entry.timeout = 0;
544
545                         /*
546                          * Save the reference to the entry
547                          */
548                         save_datum.dptr = key_datum.dptr;
549                         save_datum.dsize = key_datum.dsize;
550
551                         data_datum.dptr = (char *) &entry;
552                         data_datum.dsize = sizeof(ippool_info);
553
554                         rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
555                         if (rcode < 0) {
556                                 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
557                                         data->session_db, gdbm_strerror(gdbm_errno));
558                                 pthread_mutex_unlock(&data->op_mutex);
559                                 return RLM_MODULE_FAIL;
560                         }
561                         /* Decrease allocated count from the ip index */
562
563                         key_datum.dptr = (char *) &entry.ipaddr;
564                         key_datum.dsize = sizeof(uint32_t);
565                         data_datum = gdbm_fetch(data->ip, key_datum);
566                         if (data_datum.dptr != NULL){
567                                 memcpy(&num, data_datum.dptr, sizeof(int));
568                                 free(data_datum.dptr);
569                                 if (num >0){
570                                         num--;
571                                         DEBUG("rlm_ippool: num: %d",num);
572                                         data_datum.dptr = (char *) &num;
573                                         data_datum.dsize = sizeof(int);
574                                         rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
575                                         if (rcode < 0) {
576                                                 radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
577                                                                 data->ip_index, gdbm_strerror(gdbm_errno));
578                                                 pthread_mutex_unlock(&data->op_mutex);
579                                                 return RLM_MODULE_FAIL;
580                                         }
581                                         if (num >0 && entry.extra == 1){
582                                                 /*
583                                                  * We are doing MPPP and we still have nas/port entries referencing
584                                                  * this ip. Delete this entry so that eventually we only keep one
585                                                  * reference to this ip.
586                                                  */
587                                                 gdbm_delete(data->gdbm,save_datum);
588                                         }
589                                 }
590                         }
591                 }
592         }
593
594         pthread_mutex_unlock(&data->op_mutex);
595
596         /*
597          * If there is a Framed-IP-Address attribute in the reply, check for override
598          */
599         if (pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS) != NULL) {
600                 DEBUG("rlm_ippool: Found Framed-IP-Address attribute in reply attribute list.");
601                 if (data->override)
602                 {
603                         /* Override supplied Framed-IP-Address */
604                         DEBUG("rlm_ippool: override is set to yes. Override the existing Framed-IP-Address attribute.");
605                         pairdelete(&request->reply->vps, PW_FRAMED_IP_ADDRESS);
606                 } else {
607                         /* Abort */
608                         DEBUG("rlm_ippool: override is set to no. Return NOOP.");
609                         return RLM_MODULE_NOOP;
610                 }
611         }
612
613         /*
614          * Walk through the database searching for an active=0 entry.
615          * We search twice. Once to see if we have an active entry with the same callerid
616          * so that MPPP can work ok and then once again to find a free entry.
617          */
618
619         pthread_mutex_lock(&data->op_mutex);
620
621         key_datum.dptr = NULL;
622         if (cli != NULL){
623                 key_datum = gdbm_firstkey(data->gdbm);
624                 while(key_datum.dptr){
625                         data_datum = gdbm_fetch(data->gdbm, key_datum);
626                         if (data_datum.dptr){
627                                 memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
628                                 free(data_datum.dptr);
629                                 /*
630                                 * If we find an entry for the same caller-id and nas with active=1
631                                 * then we use that for multilink (MPPP) to work properly.
632                                 */
633                                 if (strcmp(entry.cli,cli) == 0 && entry.active){
634                                         memcpy(&key,key_datum.dptr,sizeof(ippool_key));
635                                         if (!strcmp(key.nas,nas)){
636                                                 mppp = 1;
637                                                 break;
638                                         }
639                                 }
640                         }
641                         nextkey = gdbm_nextkey(data->gdbm, key_datum);
642                         free(key_datum.dptr);
643                         key_datum = nextkey;
644                 }
645         }
646
647         if (key_datum.dptr == NULL){
648                 key_datum = gdbm_firstkey(data->gdbm);
649                 while(key_datum.dptr){
650                         data_datum = gdbm_fetch(data->gdbm, key_datum);
651                         if (data_datum.dptr){
652                                 memcpy(&entry,data_datum.dptr, sizeof(ippool_info));
653                                 free(data_datum.dptr);
654
655                                 /*
656                                  * Find an entry with active == 0
657                                  * or an entry that has expired
658                                  */
659                                 if (entry.active == 0 || (entry.timestamp && ((entry.timeout && 
660                                 request->timestamp >= (entry.timestamp + entry.timeout)) ||
661                                 (data->max_timeout && request->timestamp >= (entry.timestamp + data->max_timeout))))){
662                                         datum tmp;
663
664                                         tmp.dptr = (char *) &entry.ipaddr;
665                                         tmp.dsize = sizeof(uint32_t);
666                                         data_datum = gdbm_fetch(data->ip, tmp);
667
668                                         /*
669                                          * If we find an entry in the ip index and the number is zero (meaning
670                                          * that we haven't allocated the same ip address to another nas/port pair)
671                                          * or if we don't find an entry then delete the session entry so
672                                          * that we can change the key (nas/port)
673                                          * Else we don't delete the session entry since we haven't yet deallocated the
674                                          * corresponding ip address and we continue our search.
675                                          */
676
677                                         if (data_datum.dptr){
678                                                 memcpy(&num,data_datum.dptr, sizeof(int));
679                                                 free(data_datum.dptr);
680                                                 if (num == 0){
681                                                         delete = 1;
682                                                         break;
683                                                 }
684                                         }
685                                         else{
686                                                 delete = 1;
687                                                 break;
688                                         }
689                                 }
690                         }
691                         nextkey = gdbm_nextkey(data->gdbm, key_datum);
692                         free(key_datum.dptr);
693                         key_datum = nextkey;
694                 }
695         }
696         /*
697          * If we have found a free entry set active to 1 then add a Framed-IP-Address attribute to
698          * the reply
699          * We keep the operation mutex locked until after we have set the corresponding entry active
700          */
701         if (key_datum.dptr){
702                 if (found && !mppp){
703                         /*
704                          * Found == 1 means we have the nas/port combination entry in our database
705                          * We exchange the ip address between the nas/port entry and the free entry
706                          * Afterwards we will save the free ip address to the nas/port entry.
707                          * That is:
708                          *  ---------------------------------------------
709                          *  - NAS/PORT Entry  |||| Free Entry  ||| Time
710                          *  -    IP1                 IP2(Free)    BEFORE
711                          *  -    IP2(Free)           IP1          AFTER
712                          *  ---------------------------------------------
713                          *
714                          * We only do this if we are NOT doing MPPP
715                          *
716                          */
717                         datum key_datum_tmp;
718                         datum data_datum_tmp;
719                         ippool_key key_tmp;
720
721                         memset(key_tmp.nas,0,MAX_NAS_NAME_SIZE);
722                         strncpy(key_tmp.nas,nas,MAX_NAS_NAME_SIZE -1 );
723                         key_tmp.port = port;
724                         DEBUG("rlm_ippool: Searching for an entry for nas/port: %s/%u",key_tmp.nas,key_tmp.port);
725                         key_datum_tmp.dptr = (char *) &key_tmp;
726                         key_datum_tmp.dsize = sizeof(ippool_key);
727
728                         data_datum_tmp = gdbm_fetch(data->gdbm, key_datum_tmp);
729                         if (data_datum_tmp.dptr != NULL){
730
731                                 rcode = gdbm_store(data->gdbm, key_datum, data_datum_tmp, GDBM_REPLACE);
732                                 if (rcode < 0) {
733                                         radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
734                                                 data->session_db, gdbm_strerror(gdbm_errno));
735                                                 pthread_mutex_unlock(&data->op_mutex);
736                                         return RLM_MODULE_FAIL;
737                                 }
738                                 free(data_datum_tmp.dptr);
739                         }
740                 }
741                 else{
742                         /*
743                          * We have not found the nas/port combination
744                          */
745                         if (delete){
746                                 /*
747                                  * Delete the entry so that we can change the key
748                                  * All is well. We delete one entry and we add one entry
749                                  */
750                                 gdbm_delete(data->gdbm, key_datum);
751                         }
752                         else{
753                                 /*
754                                  * We are doing MPPP. (mppp should be 1)
755                                  * We don't do anything.
756                                  * We will create an extra not needed entry in the database in this case
757                                  * but we don't really care since we always also use the ip_index database
758                                  * when we search for a free entry.
759                                  * We will also delete that entry on the accounting section so that we only
760                                  * have one nas/port entry referencing each ip
761                                  */
762                                 if (mppp)
763                                         extra = 1;
764                                 if (!mppp)
765                                         radlog(L_ERR, "rlm_ippool: mppp is not one. Please report this behaviour.");
766                         }
767                 }
768                 free(key_datum.dptr);
769                 entry.active = 1;
770                 entry.timestamp = request->timestamp;
771                 if ((vp = pairfind(request->reply->vps, PW_SESSION_TIMEOUT)) != NULL)   
772                         entry.timeout = (time_t) vp->lvalue;
773                 else
774                         entry.timeout = 0;
775                 if (extra)
776                         entry.extra = 1;
777                 data_datum.dptr = (char *) &entry;
778                 data_datum.dsize = sizeof(ippool_info);
779                 memset(key.nas,0,MAX_NAS_NAME_SIZE);
780                 strncpy(key.nas,nas,MAX_NAS_NAME_SIZE - 1);
781                 key.port = port;
782                 key_datum.dptr = (char *) &key;
783                 key_datum.dsize = sizeof(ippool_key);
784
785                 DEBUG2("rlm_ippool: Allocating ip to nas/port: %s/%u",key.nas,key.port);
786                 rcode = gdbm_store(data->gdbm, key_datum, data_datum, GDBM_REPLACE);
787                 if (rcode < 0) {
788                         radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
789                                 data->session_db, gdbm_strerror(gdbm_errno));
790                                 pthread_mutex_unlock(&data->op_mutex);
791                         return RLM_MODULE_FAIL;
792                 }
793
794                 /* Increase the ip index count */
795                 key_datum.dptr = (char *) &entry.ipaddr;
796                 key_datum.dsize = sizeof(uint32_t);
797                 data_datum = gdbm_fetch(data->ip, key_datum);
798                 if (data_datum.dptr){
799                         memcpy(&num,data_datum.dptr,sizeof(int));
800                         free(data_datum.dptr);
801                 } else
802                         num = 0;
803                 num++;
804                 DEBUG("rlm_ippool: num: %d",num);
805                 data_datum.dptr = (char *) &num;
806                 data_datum.dsize = sizeof(int);
807                 rcode = gdbm_store(data->ip, key_datum, data_datum, GDBM_REPLACE);
808                 if (rcode < 0) {
809                         radlog(L_ERR, "rlm_ippool: Failed storing data to %s: %s",
810                                 data->ip_index, gdbm_strerror(gdbm_errno));
811                         pthread_mutex_unlock(&data->op_mutex);
812                         return RLM_MODULE_FAIL;
813                 }
814                 pthread_mutex_unlock(&data->op_mutex);
815
816
817                 DEBUG("rlm_ippool: Allocated ip %s to client on nas %s,port %u",ip_ntoa(str,entry.ipaddr),
818                                 key.nas,port);
819                 if ((vp = paircreate(PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR)) == NULL) {
820                         radlog(L_ERR|L_CONS, "no memory");
821                         return RLM_MODULE_FAIL;
822                 }
823                 vp->lvalue = entry.ipaddr;
824                 ip_ntoa(vp->strvalue, vp->lvalue);
825                 pairadd(&request->reply->vps, vp);
826
827                 /*
828                  *      If there is no Framed-Netmask attribute in the
829                  *      reply, add one
830                  */
831                 if (pairfind(request->reply->vps, PW_FRAMED_IP_NETMASK) == NULL) {
832                         if ((vp = paircreate(PW_FRAMED_IP_NETMASK, PW_TYPE_IPADDR)) == NULL)
833                                 radlog(L_ERR|L_CONS, "no memory");
834                         else {
835                                 vp->lvalue = ntohl(data->netmask);
836                                 ip_ntoa(vp->strvalue, vp->lvalue);
837                                 pairadd(&request->reply->vps, vp);
838                         }
839                 }
840
841         }
842         else{
843                 pthread_mutex_unlock(&data->op_mutex);
844                 DEBUG("rlm_ippool: No available ip addresses in pool.");
845                 return RLM_MODULE_NOTFOUND;
846         }
847
848         return RLM_MODULE_OK;
849 }
850
851 static int ippool_detach(void *instance)
852 {
853         rlm_ippool_t *data = (rlm_ippool_t *) instance;
854
855         gdbm_close(data->gdbm);
856         gdbm_close(data->ip);
857         free(data->session_db);
858         free(data->ip_index);
859         pthread_mutex_destroy(&data->op_mutex);
860
861         free(instance);
862         return 0;
863 }
864
865 /*
866  *      The module name should be the only globally exported symbol.
867  *      That is, everything else should be 'static'.
868  *
869  *      If the module needs to temporarily modify it's instantiation
870  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
871  *      The server will then take care of ensuring that the module
872  *      is single-threaded.
873  */
874 module_t rlm_ippool = {
875         "IPPOOL",
876         RLM_TYPE_THREAD_SAFE,           /* type */
877         NULL,                           /* initialization */
878         ippool_instantiate,             /* instantiation */
879         {
880                 NULL,                   /* authentication */
881                 NULL,                   /* authorization */
882                 NULL,                   /* preaccounting */
883                 ippool_accounting,      /* accounting */
884                 NULL,                   /* checksimul */
885                 NULL,                   /* pre-proxy */
886                 NULL,                   /* post-proxy */
887                 ippool_postauth         /* post-auth */
888         },
889         ippool_detach,                  /* detach */
890         NULL,                           /* destroy */
891 };