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