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