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