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