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