Massively cleaned up #include's, so they're in a consistent
[freeradius.git] / src / modules / rlm_sqlhpwippool / rlm_sqlhpwippool.c
1 /*
2  * rlm_sqlhpwippool.c
3  * Chooses an IPv4 address from pools defined in ASN Netvim
4  *
5  * Version:     $Id$
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19  * Place, Suite 330, Boston, MA 02111-1307 USA
20  *
21  * Copyright (c) 2005-2006 Pawel Foremski <pjf@asn.pl>,
22  *               2000-2006 The FreeRADIUS server project
23  *
24  * Current bugs/limits:
25  * - probably works only with newer versions of MySQL (subqueries)
26  * - requires FreeRADIUS' SQL user to have proper permissions on proper tables
27  *   from Netvim database
28  * - of course uses dirty hacks to get access to the database
29  * - queries and table names are not configurable
30  * - IPv4 only (I don't even care about IPv6 by now)
31  * - pool names (fetched from database) are not "escaped"
32  * - you have to set encoding of radius.acctuniqueid to same as
33  *   netvim.ips.rsv_by
34  */
35
36 #include "config.h"
37 #include <freeradius-devel/radiusd.h>
38 #include <freeradius-devel/modules.h>
39 #include <freeradius-devel/modpriv.h>
40
41 #include <ctype.h>
42
43 #include "rlm_sql.h"
44
45 #define VENDOR_ASN 23782
46 #define ASN_IP_POOL_NAME 1
47 #define PW_ASN_IP_POOL_NAME (ASN_IP_POOL_NAME | (VENDOR_ASN << 16))
48
49 #define RLM_NETVIM_LOG_FMT "rlm_sqlhpwippool(%s, line %u): %s"
50 #define RLM_NETVIM_MAX_ROWS 1000000
51 #define RLM_NETVIM_TMP_PREFIX "auth-tmp-"
52
53 static const char rcsid[] = "$Id$";
54
55 typedef struct rlm_sqlhpwippool_t {
56         const char *myname;         /* name of this instance */
57         SQL_INST *sqlinst;          /* SQL_INST for requested instance */
58         rlm_sql_module_t *db;       /* here the fun takes place ;-) */
59 #ifdef HAVE_PTHREAD_D
60         pthread_mutex_t mutex;      /* used "with" syncafter */
61 #endif
62         int sincesync;              /* req. done so far since last free IP sync. */
63
64         /* from config */
65         char *sqlinst_name;         /* rlm_sql instance to use */
66         char *db_name;              /* netvim database */
67         int nofreefail;             /* fail if no free IP addresses found */
68         int freeafter;              /* how many seconds an IP should not be used after
69                                        freeing */
70         int syncafter;              /* how often to sync with radacct */
71 } rlm_sqlhpwippool_t;
72
73 /* char *name, int type,
74  * size_t offset, void *data, char *dflt */
75 static CONF_PARSER module_config[] = {
76         { "sqlinst_name",       PW_TYPE_STRING_PTR,
77           offsetof(rlm_sqlhpwippool_t, sqlinst_name),       NULL, "sql" },
78         { "db_name",            PW_TYPE_STRING_PTR,
79           offsetof(rlm_sqlhpwippool_t, db_name),            NULL, "netvim" },
80         { "nofreefail",         PW_TYPE_BOOLEAN,
81           offsetof(rlm_sqlhpwippool_t, nofreefail),         NULL, "yes" },
82         { "freeafter",          PW_TYPE_INTEGER,
83           offsetof(rlm_sqlhpwippool_t, freeafter),          NULL, "300" },
84         { "syncafter",          PW_TYPE_INTEGER,
85           offsetof(rlm_sqlhpwippool_t, syncafter),          NULL, "25" },
86         { NULL, -1, 0, NULL, NULL } /* end */
87 };
88
89 /* wrapper around radlog which adds prefix with module and instance name */
90 static int nvp_log(unsigned int line, rlm_sqlhpwippool_t *data, int lvl,
91                    const char *fmt, ...)
92 {
93         va_list ap;
94         int r;
95         char pfmt[4096];
96
97         /* prefix log message with RLM_NETVIM_LOG_FMT */
98         snprintf(pfmt, sizeof(pfmt), RLM_NETVIM_LOG_FMT,
99                  data->myname, line, fmt);
100
101         va_start(ap, fmt);
102         r = vradlog(lvl, pfmt, ap);
103         va_end(ap);
104
105         return r;
106 }
107 /* handy SQL query tool */
108 static int nvp_vquery(unsigned int line, rlm_sqlhpwippool_t *data,
109                       SQLSOCK *sqlsock, const char *fmt, va_list ap)
110 {
111         char query[MAX_QUERY_LEN];
112
113         vsnprintf(query, MAX_QUERY_LEN, fmt, ap);
114
115         if (rlm_sql_query(sqlsock, data->sqlinst, query)) {
116                 nvp_log(__LINE__, data, L_ERR, "nvp_vquery(): query from line %u: %s",
117                         line, (char *)(data->db->sql_error)(sqlsock, data->sqlinst->config));
118                 return 0;
119         }
120
121         return 1;
122 }
123
124 /* wrapper around nvp_vquery */
125 static int nvp_query(unsigned int line, rlm_sqlhpwippool_t *data,
126                     SQLSOCK *sqlsock, const char *fmt, ...)
127 {
128         int r;
129         va_list ap;
130
131         va_start(ap, fmt);
132         r = nvp_vquery(line, data, sqlsock, fmt, ap);
133         va_end(ap);
134
135         return r;
136 }
137
138 /* handy wrapper around data->db->sql_finish_query() */
139 static int nvp_finish(rlm_sqlhpwippool_t *data, SQLSOCK *sqlsock)
140 {
141         return (data->db->sql_finish_query)(sqlsock, data->sqlinst->config);
142 }
143
144 /* executes query and fetches first row
145  * -1 on no results
146  *  0 on db error
147  *  1 on success */
148 static int nvp_select(unsigned int line, rlm_sqlhpwippool_t *data,
149                       SQLSOCK *sqlsock, const char *fmt, ...)
150 {
151         va_list ap;
152
153         va_start(ap, fmt);
154         if (!nvp_vquery(line, data, sqlsock, fmt, ap)) {
155                 va_end(ap);
156                 return 0;
157         }
158         va_end(ap);
159
160         if ((data->db->sql_store_result)(sqlsock, data->sqlinst->config)) {
161                 nvp_log(__LINE__, data, L_ERR,
162                         "nvp_select(): error while saving results of query from line %u",
163                         line);
164                 return 0;
165         }
166
167         if ((data->db->sql_num_rows)(sqlsock, data->sqlinst->config) < 1) {
168                 nvp_log(__LINE__, data, L_DBG,
169                         "nvp_select(): no results in query from line %u", line);
170                 return -1;
171         }
172
173         if ((data->db->sql_fetch_row)(sqlsock, data->sqlinst->config)) {
174                 nvp_log(__LINE__, data, L_ERR, "nvp_select(): couldn't fetch row "
175                                                "from results of query from line %u",
176                         line);
177                 return 0;
178         }
179
180         return 1;
181 }
182
183 static int nvp_select_finish(rlm_sqlhpwippool_t *data, SQLSOCK *sqlsock)
184 {
185         return ((data->db->sql_free_result)(sqlsock, data->sqlinst->config) ||
186                 nvp_finish(data, sqlsock));
187 }
188
189 /* frees IPs of closed sessions (eg. by external modifications to db) */
190 static int nvp_freeclosed(rlm_sqlhpwippool_t *data, SQLSOCK *sqlsock)
191 {
192         if (!nvp_query(__LINE__, data, sqlsock,
193             "UPDATE `%s`.`ips`, `radacct` "
194                 "SET "
195                         "`ips`.`rsv_until` = `radacct`.`acctstoptime` + INTERVAL %u SECOND "
196                 "WHERE "
197                         "`radacct`.`acctstoptime` IS NOT NULL AND "   /* session is closed */
198                         "("                                    /* address is being used */
199                                 "`ips`.`pid` IS NOT NULL AND "
200                                 "(`rsv_until` = 0 OR `rsv_until` > NOW())"
201                         ") AND "
202                         "`radacct`.`acctuniqueid` = `ips`.`rsv_by`",
203             data->db_name, data->freeafter)) {
204                 return 0;
205         }
206
207         nvp_finish(data, sqlsock);
208         return 1;
209 }
210
211 /* updates number of free IP addresses in pools */
212 static int nvp_syncfree(rlm_sqlhpwippool_t *data, SQLSOCK *sqlsock)
213 {
214         if (!nvp_query(__LINE__, data, sqlsock,
215             "UPDATE `%s`.`ip_pools` "
216                 "SET `ip_pools`.`free` = "
217                         "(SELECT COUNT(*) "
218                                 "FROM `%1$s`.`ips` "
219                                 "WHERE "
220                                         "`ips`.`ip` BETWEEN "
221                                                 "`ip_pools`.`ip_start` AND `ip_pools`.`ip_stop` AND "
222                                         "("
223                                                 "`ips`.`pid` IS NULL OR "
224                                                 "(`ips`.`rsv_until` > 0 AND `ips`.`rsv_until` < NOW())"
225                                         "))",
226             data->db_name)) {
227                 return 0;
228         }
229
230         nvp_finish(data, sqlsock);
231         return 1;
232 }
233
234 /* cleanup IP pools and sync them with radacct */
235 static int nvp_cleanup(rlm_sqlhpwippool_t *data)
236 {
237         SQLSOCK *sqlsock;
238
239         /* initialize the SQL socket */
240         sqlsock = sql_get_socket(data->sqlinst);
241         if (!sqlsock) {
242                 nvp_log(__LINE__, data, L_ERR, "nvp_cleanup(): error while "
243                                                "requesting new SQL connection");
244                 return 0;
245         }
246
247         /* free IPs of closed sessions */
248         if (!nvp_freeclosed(data, sqlsock)) {
249                 sql_release_socket(data->sqlinst, sqlsock);
250                 return 0;
251         }
252
253         /* add sessions opened in the meantime */
254         if (!nvp_query(__LINE__, data, sqlsock,
255             "UPDATE `%s`.`ips`, `radacct` "
256                 "SET "
257                         "`ips`.`pid` = 0, "
258                         "`ips`.`rsv_by` = `radacct`.`acctuniqueid`, "
259                         "`ips`.`rsv_since` = `radacct`.`acctstarttime`, "
260                         "`ips`.`rsv_until` = 0 "
261                 "WHERE "
262                         "`radacct`.`acctstoptime` IS NULL AND "     /* session is opened */
263                         "`ips`.`ip` = INET_ATON(`radacct`.`framedipaddress`) AND "
264                         "("
265                                 "`ips`.`pid` IS NULL OR "
266 /*                              "(`ips`.`rsv_until` > 0 AND `ips.`rsv_until` < NOW()) " */
267                                 "`ips`.`rsv_until` != 0"   /* no acct pkt received yet */
268                         ")",
269             data->db_name)) {
270                 sql_release_socket(data->sqlinst, sqlsock);
271                 return 0;
272         }
273         else {
274                 nvp_finish(data, sqlsock);
275         }
276
277         /* count number of free IP addresses in IP pools */
278         if (!nvp_syncfree(data, sqlsock)) {
279                 sql_release_socket(data->sqlinst, sqlsock);
280                 return 0;
281         }
282
283         sql_release_socket(data->sqlinst, sqlsock);
284         return 1;
285 }
286
287 static int sqlhpwippool_detach(void *instance)
288 {
289         rlm_sqlhpwippool_t *data = (rlm_sqlhpwippool_t *) instance;
290
291         /* (*data) is zeroed on instantiation */
292         if (data->sqlinst_name) free(data->sqlinst_name);
293         if (data->db_name)      free(data->db_name);
294         free(data);
295
296         return 0;
297 }
298
299 /* standard foobar code */
300 static int sqlhpwippool_instantiate(CONF_SECTION *conf, void **instance)
301 {
302         rlm_sqlhpwippool_t *data;
303         module_instance_t *modinst;
304
305         /* set up a storage area for instance data */
306         data = rad_malloc(sizeof(*data));
307         if (!data) return -1;
308         memset(data, 0, sizeof(*data)); /* so _detach will know what to free */
309
310         /* fail if the configuration parameters can't be parsed */
311         if (cf_section_parse(conf, data, module_config) < 0) {
312                 sqlhpwippool_detach(*instance);
313                 return -1;
314         }
315
316         /* save my name */
317         data->myname = cf_section_name2(conf);
318         if (!data->myname) {
319                 data->myname = "(no name)";
320         }
321
322         data->sincesync = 0;
323
324         modinst = find_module_instance(cf_section_find("modules"), (data->sqlinst_name) );
325         if (!modinst) {
326                 nvp_log(__LINE__, data, L_ERR,
327                         "sqlhpwippool_instantiate(): cannot find module instance "
328                         "named \"%s\"",
329                         data->sqlinst_name);
330                 return -1;
331         }
332
333         /* check if the given instance is really a rlm_sql instance */
334         if (strcmp(modinst->entry->name, "rlm_sql") != 0) {
335                 nvp_log(__LINE__, data, L_ERR,
336                         "sqlhpwippool_instantiate(): given instance (%s) is not "
337                         "an instance of the rlm_sql module",
338                         data->sqlinst_name);
339                 return -1;
340         }
341
342         /* save pointers to useful "objects" */
343         data->sqlinst = (SQL_INST *) modinst->insthandle;
344         data->db = (rlm_sql_module_t *) data->sqlinst->module;
345
346         /* everything went ok, cleanup pool */
347         *instance = data;
348
349         return ((nvp_cleanup(data)) ? 0 : -1);
350 }
351
352 /* assign new IP address, if required */
353 static int sqlhpwippool_postauth(void *instance, REQUEST *request)
354 {
355         VALUE_PAIR *vp;
356         unsigned char *pname;       /* name of requested IP pool */
357         uint32_t nasip;             /* NAS IP in host byte order */
358         struct in_addr ip = {0};    /* reserved IP for client (net. byte order) */
359         SQLSOCK *sqlsock;
360         unsigned long s_gid,        /* _s_elected in sql result set */
361                       s_prio,       /* as above */
362                       s_pid,        /* as above */
363                       gid,          /* real integer value */
364                       pid,          /* as above */
365                       weights_sum, used_sum, ip_start, ip_stop, connid;
366         long prio;
367
368         rlm_sqlhpwippool_t *data = (rlm_sqlhpwippool_t *) instance;
369
370         /* if IP is already there, then nothing to do */
371         vp = pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS);
372         if (vp) {
373                 nvp_log(__LINE__, data, L_DBG,
374                         "sqlhpwippool_postauth(): IP address "
375                         "already in the reply packet - exiting");
376                 return RLM_MODULE_NOOP;
377         }
378
379         /* if no pool name, we don't need to do anything */
380         vp = pairfind(request->reply->vps, PW_ASN_IP_POOL_NAME);
381         if (vp) {
382                 pname = vp->vp_strvalue;
383                 nvp_log(__LINE__, data, L_DBG,
384                         "sqlhpwippool_postauth(): pool name = '%s'",
385                         pname);
386         }
387         else {
388                 nvp_log(__LINE__, data, L_DBG,
389                         "sqlhpwippool_postauth(): no IP pool name - exiting");
390                 return RLM_MODULE_NOOP;
391         }
392
393         /* if no NAS IP address, assign 0 */
394         vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
395         if (vp) {
396                 nasip = ntohl(vp->lvalue);
397         }
398         else {
399                 nasip = 0;
400                 nvp_log(__LINE__, data, L_DBG,
401                         "sqlhpwippool_postauth(): no NAS IP address in "
402                         "the request packet - using \"0.0.0.0/0\" (any)");
403         }
404
405         /* get our database connection */
406         sqlsock = sql_get_socket(data->sqlinst);
407         if (!sqlsock) {
408                 nvp_log(__LINE__, data, L_ERR,
409                         "sqlhpwippool_postauth(): error while requesting an SQL socket");
410                 return RLM_MODULE_FAIL;
411         }
412
413         /* get connection id as temporary unique integer */
414         if (nvp_select(__LINE__, data, sqlsock, "SELECT CONNECTION_ID()") < 1) {
415                 nvp_log(__LINE__, data, L_ERR, "sqlhpwippool_postauth(): WTF ;-)!");
416                 nvp_select_finish(data, sqlsock);
417                 sql_release_socket(data->sqlinst, sqlsock);
418                 return RLM_MODULE_FAIL;
419         }
420
421         connid = strtoul(sqlsock->row[0], (char **) NULL, 10);
422         nvp_select_finish(data, sqlsock);
423
424         /* synchronize with radacct db, if needed */
425         if (++data->sincesync >= data->syncafter
426 #ifdef HAVE_PTHREAD_D
427             && (pthread_mutex_trylock(&data->mutex)) == 0
428 #endif
429            ) {
430                 int r;
431
432                 data->sincesync = 0;
433
434                 nvp_log(__LINE__, data, L_DBG,
435                         "sqlhpwippool_postauth(): syncing with radacct table");
436
437                 r = (nvp_freeclosed(data, sqlsock) && nvp_syncfree(data, sqlsock));
438
439 #ifdef HAVE_PTHREAD_D
440                 pthread_mutex_unlock(&data->mutex);
441 #endif
442
443                 if (!r) {
444                         nvp_log(__LINE__, data, L_ERR,
445                                 "sqlhpwippool_postauth(): synchronization failed");
446                         sql_release_socket(data->sqlinst, sqlsock);
447                         return RLM_MODULE_FAIL;
448                 }
449         }
450
451         for (s_gid = 0; s_gid < RLM_NETVIM_MAX_ROWS && !(ip.s_addr); s_gid++) {
452                 nvp_log(__LINE__, data, L_DBG,
453                         "sqlhpwippool_postauth(): selecting gid on position %lu",
454                         s_gid);
455
456                 /* find the most specific group which NAS belongs to */
457                 switch (nvp_select(__LINE__, data, sqlsock,
458                        "SELECT `host_groups`.`gid` "
459                         "FROM "
460                                 "`%s`.`host_groups`, "
461                                 "`%1$s`.`gid_ip`, "
462                                 "`%1$s`.`ids` "
463                         "WHERE "
464                                 "`host_groups`.`gid` = `ids`.`id` AND "
465                                 "`ids`.`enabled` = 1 AND "
466                                 "`host_groups`.`gid` = `gid_ip`.`gid` AND "
467                                 "%lu BETWEEN `gid_ip`.`ip_start` AND `gid_ip`.`ip_stop` "
468                         "ORDER BY (`gid_ip`.`ip_stop` - `gid_ip`.`ip_start`) ASC "
469                         "LIMIT %lu, 1",
470                        data->db_name, nasip, s_gid)) {
471                         case -1:
472                                 nvp_log(__LINE__, data, L_ERR,
473                                         "sqlhpwippool_postauth(): couldn't find "
474                                         "any more matching host groups");
475                                 goto end_gid;                  /* exit the main loop */
476                         case 0:
477                                 sql_release_socket(data->sqlinst, sqlsock);
478                                 return RLM_MODULE_FAIL;
479                 }
480
481                 /* store the group ID and free memory occupied by results */
482                 gid = strtoul(sqlsock->row[0], (char **) NULL, 10);
483                 nvp_select_finish(data, sqlsock);
484
485                 for (s_prio = 0; s_prio < RLM_NETVIM_MAX_ROWS && !(ip.s_addr); s_prio++) {
486                         nvp_log(__LINE__, data, L_DBG,
487                                 "sqlhpwippool_postauth(): selecting prio on position %lu",
488                                 s_prio);
489
490                         /* prepare to search for best fit pool */
491                         switch (nvp_select(__LINE__, data, sqlsock,
492                                 "SELECT "
493                                         "`ip_pools`.`prio`, "
494                                         "SUM(`ip_pools`.`weight`) AS `weights_sum`, "
495                                         "(SUM(`ip_pools`.`total`) - "
496                                                 "SUM(`ip_pools`.`free`)) AS `used_sum` "
497                                         "FROM "
498                                                 "`%s`.`ip_pools`, "
499                                                 "`%1$s`.`ids`, "
500                                                 "`%1$s`.`pool_names` "
501                                         "WHERE "
502                                                 "`ip_pools`.`gid` = %lu AND "
503                                                 "`ids`.`id` = `ip_pools`.`pid` AND "
504                                                 "`ids`.`enabled` = 1 AND "
505                                                 "`pool_names`.`pnid` = `ip_pools`.`pnid` AND "
506                                                 "`pool_names`.`name` = '%s' AND "
507                                                 "`ip_pools`.`free` > 0 "
508                                         "GROUP BY `prio` "
509                                         "ORDER BY `prio` ASC "
510                                         "LIMIT %lu, 1",
511                                 data->db_name, gid, pname, s_prio)) {
512                                 case -1:
513                                         nvp_log(__LINE__, data, L_DBG,
514                                                 "sqlhpwippool_postauth(): couldn't find "
515                                                 "any more matching pools for gid = %u",
516                                                 gid);
517                                         goto end_prio;               /* select next gid */
518                                 case 0:
519                                         sql_release_socket(data->sqlinst, sqlsock);
520                                         return RLM_MODULE_FAIL;
521                         }
522
523                         /* store the prio and weights sum */
524                         prio = strtol(sqlsock->row[0], (char **) NULL, 10);
525                         weights_sum = strtoul(sqlsock->row[1], (char **) NULL, 10);
526                         used_sum = strtoul(sqlsock->row[2], (char **) NULL, 10);
527
528                         /* free memory */
529                         nvp_select_finish(data, sqlsock);
530
531                         for (s_pid = 0; s_pid < RLM_NETVIM_MAX_ROWS && !(ip.s_addr); s_pid++) {
532                                 nvp_log(__LINE__, data, L_DBG,
533                                         "sqlhpwippool_postauth(): selecting PID on position %lu",
534                                         s_pid);
535
536                                 /* search for best fit pool */
537                                 switch (nvp_select(__LINE__, data, sqlsock,
538                                         "SELECT "
539                                                 "`ip_pools`.`pid`, "
540                                                 "`ip_pools`.`ip_start`, "
541                                                 "`ip_pools`.`ip_stop` "
542                                                 "FROM "
543                                                         "`%s`.`ip_pools`, "
544                                                         "`%1$s`.`ids`, "
545                                                         "`%1$s`.`pool_names` "
546                                                 "WHERE "
547                                                         "`ip_pools`.`gid` = %lu AND "
548                                                         "`ids`.`id` = `ip_pools`.`pid` AND "
549                                                         "`ids`.`enabled` = 1 AND "
550                                                         "`pool_names`.`pnid` = `ip_pools`.`pnid` AND "
551                                                         "`pool_names`.`name` = '%s' AND "
552                                                         "`ip_pools`.`free` > 0 AND "
553                                                         "`prio` = %ld "
554                                                 "ORDER BY (`weight`/%lu.0000 - (`total` - `free`)/%lu) DESC "
555                                                 "LIMIT %lu, 1",
556                                         data->db_name, gid, pname, prio,
557                                         weights_sum, used_sum, s_pid)) {
558                                         case -1:
559                                                 nvp_log(__LINE__, data, L_DBG,
560                                                         "sqlhpwippool_postauth(): couldn't find any more "
561                                                         "matching pools of prio = %ld for gid = %lu",
562                                                         prio, gid);
563                                                 goto end_pid;              /* select next prio */
564                                         case 0:
565                                                 sql_release_socket(data->sqlinst, sqlsock);
566                                                 return RLM_MODULE_FAIL;
567                                 }
568
569                                 /* store the data and free memory occupied by results */
570                                 pid = strtoul(sqlsock->row[0], (char **) NULL, 10);
571                                 ip_start = strtoul(sqlsock->row[1], (char **) NULL, 10);
572                                 ip_stop = strtoul(sqlsock->row[2], (char **) NULL, 10);
573                                 nvp_select_finish(data, sqlsock);
574
575                                 /* reserve an IP address */
576                                 if (!nvp_query(__LINE__, data, sqlsock,
577                                     "UPDATE `%s`.`ips` "
578                                         "SET "
579                                                 "`pid` = %lu, "
580                                                 "`rsv_since` = NOW(), "
581                                                 "`rsv_by` = '" RLM_NETVIM_TMP_PREFIX "%lu', "
582                                                 "`rsv_until` = NOW() + INTERVAL %d SECOND "
583                                         "WHERE "
584                                                 "`ip` BETWEEN %lu AND %lu AND "
585                                                 "("
586                                                         "`pid` IS NULL OR "
587                                                         "(`rsv_until` > 0 AND `rsv_until` < NOW())"
588                                                 ") "
589                                         "ORDER BY RAND() "
590                                         "LIMIT 1",
591                                     data->db_name, pid, connid, data->freeafter, ip_start, ip_stop)) {
592                                         sql_release_socket(data->sqlinst, sqlsock);
593                                         return RLM_MODULE_FAIL;
594                                 }
595                                 else {
596                                         nvp_finish(data, sqlsock);
597                                 }
598
599                                 /* select assigned IP address */
600                                 switch (nvp_select(__LINE__, data, sqlsock,
601                                         "SELECT `ip` "
602                                                 "FROM `%s`.`ips` "
603                                                 "WHERE `rsv_by` = '" RLM_NETVIM_TMP_PREFIX "%lu' "
604                                                 "ORDER BY `rsv_since` DESC "
605                                                 "LIMIT 1",
606                                         data->db_name, connid)) {
607                                         case -1:
608                                                 nvp_log(__LINE__, data, L_ERR,
609                                                         "sqlhpwippool_postauth(): couldn't reserve an IP address "
610                                                         "from pool of pid = %lu (prio = %ld, gid = %lu)",
611                                                         pid, prio, gid);
612                                                 continue;                            /* select next pid */
613                                         case 0:
614                                                 sql_release_socket(data->sqlinst, sqlsock);
615                                                 return RLM_MODULE_FAIL;
616                                 }
617
618                                 /* update free IPs count */
619                                 if (!nvp_query(__LINE__, data, sqlsock,
620                                     "UPDATE `%s`.`ip_pools` "
621                                         "SET "
622                                                 "`free` = `free` - 1 "
623                                         "WHERE "
624                                                 "`pid` = %lu "
625                                 "LIMIT 1",
626                                     data->db_name, pid)) {
627                                         sql_release_socket(data->sqlinst, sqlsock);
628                                         return RLM_MODULE_FAIL;
629                                 }
630                                 else {
631                                         nvp_finish(data, sqlsock);
632                                 }
633
634                                 /* get assigned IP and free memory */
635                                 ip.s_addr = htonl(strtoul(sqlsock->row[0], (char **) NULL, 10));
636                                 nvp_select_finish(data, sqlsock);
637                         } /* pid */
638 end_pid: continue;           /* stupid */
639                 } /* prio */
640 end_prio: continue;          /* stupid */
641         } /* gid */
642 end_gid:
643
644         /* release SQL socket */
645         sql_release_socket(data->sqlinst, sqlsock);
646
647         /* no free IP address found */
648         if (!ip.s_addr) {
649                 nvp_log(__LINE__, data, L_INFO,
650                         "sqlhpwippool_postauth(): no free IP address found!");
651
652                 if (data->nofreefail) {
653                         nvp_log(__LINE__, data, L_DBG, "sqlhpwippool_postauth(): rejecting user");
654                         return RLM_MODULE_REJECT;
655                 }
656                 else {
657                         nvp_log(__LINE__, data, L_DBG, "sqlhpwippool_postauth(): exiting");
658                         return RLM_MODULE_NOOP;
659                 }
660         }
661
662         /* add IP address to reply packet */
663         vp = paircreate(PW_FRAMED_IP_ADDRESS, PW_TYPE_IPADDR);
664         if (!vp) {
665                 nvp_log(__LINE__, data, L_ERR,
666                         "sqlhpwippool_postauth(): couldn't save chosen IP - no memory - "
667                         "exiting with error");
668
669                 /* don't free chosen IP, because we have no memory (now adress will need to
670                  * wait for timeout, what's not so bad, BTW) */
671                 return RLM_MODULE_FAIL;
672         }
673
674         vp->lvalue = ip.s_addr;
675         pairadd(&request->reply->vps, vp);
676
677         nvp_log(__LINE__, data, L_DBG, "sqlhpwippool_postauth(): returning %s",
678                 inet_ntoa(ip));
679         return RLM_MODULE_OK;
680 }
681
682 static int sqlhpwippool_accounting(void *instance, REQUEST *request)
683 {
684         VALUE_PAIR *vp;
685         SQLSOCK *sqlsock;
686         struct in_addr nasip;      /* NAS IP */
687         unsigned char *sessid;     /* unique session id */
688         char nasipstr[16];         /* NAS IP in string format */
689         uint32_t framedip = 0;     /* client's IP, host byte order */
690         uint32_t acct_type;
691
692         rlm_sqlhpwippool_t *data = (rlm_sqlhpwippool_t *) instance;
693
694         /* if no unique session ID, don't even try */
695         vp = pairfind(request->packet->vps, PW_ACCT_UNIQUE_SESSION_ID);
696         if (vp) {
697                 sessid = vp->vp_strvalue;
698         }
699         else {
700                 nvp_log(__LINE__, data, L_ERR,
701                         "sqlhpwippool_accounting(): unique session ID not found");
702                 return RLM_MODULE_FAIL;
703         }
704
705         vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE);
706         if (vp) {
707                 acct_type = vp->lvalue;
708         }
709         else {
710                 nvp_log(__LINE__, data, L_ERR, "sqlhpwippool_accounting(): "
711                                                "couldn't find type of accounting packet");
712                 return RLM_MODULE_FAIL;
713         }
714
715         if (!(acct_type == PW_STATUS_START ||
716               acct_type == PW_STATUS_ALIVE ||
717               acct_type == PW_STATUS_STOP  ||
718               acct_type == PW_STATUS_ACCOUNTING_OFF ||
719               acct_type == PW_STATUS_ACCOUNTING_ON)) {
720                 return RLM_MODULE_NOOP;
721         }
722
723         /* connect to database */
724         sqlsock = sql_get_socket(data->sqlinst);
725         if (!sqlsock) {
726                 nvp_log(__LINE__, data, L_ERR,
727                         "sqlhpwippool_accounting(): couldn't connect to database");
728                 return RLM_MODULE_FAIL;
729         }
730
731
732         switch (acct_type) {
733                 case PW_STATUS_START:
734                 case PW_STATUS_ALIVE:
735                         vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS);
736                         if (!vp) {
737                                 nvp_log(__LINE__, data, L_ERR, "sqlhpwippool_accounting(): no framed IP");
738                                 sql_release_socket(data->sqlinst, sqlsock);
739                                 return RLM_MODULE_FAIL;
740                         }
741
742                         framedip = ntohl(vp->lvalue);
743
744                         if (!nvp_query(__LINE__, data, sqlsock,
745                             "UPDATE `%s`.`ips` "
746                                 "SET "
747                                         "`rsv_until` = 0, "
748                                         "`rsv_by` = '%s' "
749                                 "WHERE `ip` = %lu",
750                             data->db_name, sessid, framedip)) {
751                                 sql_release_socket(data->sqlinst, sqlsock);
752                                 return RLM_MODULE_FAIL;
753                         }
754                         nvp_finish(data, sqlsock);
755                         break;
756
757                 case PW_STATUS_STOP:
758                         if (!nvp_query(__LINE__, data, sqlsock,
759                             "UPDATE `%s`.`ips`, `%1$s`.`ip_pools` "
760                                 "SET "
761                                         "`ips`.`rsv_until` = NOW() + INTERVAL %u SECOND, "
762                                         "`ip_pools`.`free` = `ip_pools`.`free` + 1 "
763                                 "WHERE "
764                                         "`ips`.`rsv_by` = '%s' AND "
765                                         "`ips`.`ip` BETWEEN `ip_pools`.`ip_start` AND `ip_pools`.`ip_stop`",
766                             data->db_name, data->freeafter, sessid)) {
767                                 sql_release_socket(data->sqlinst, sqlsock);
768                                 return RLM_MODULE_FAIL;
769                         }
770                         nvp_finish(data, sqlsock);
771                 break;
772
773                 case PW_STATUS_ACCOUNTING_OFF:
774                 case PW_STATUS_ACCOUNTING_ON:
775                         vp = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
776                         if (!vp) {
777                                 nvp_log(__LINE__, data, L_ERR, "sqlhpwippool_accounting(): no NAS IP");
778                                 sql_release_socket(data->sqlinst, sqlsock);
779                                 return RLM_MODULE_FAIL;
780                         }
781
782                         nasip.s_addr = vp->lvalue;
783                         strncpy(nasipstr, inet_ntoa(nasip), sizeof(nasipstr) - 1);
784                         nasipstr[sizeof(nasipstr)] = 0;
785
786                         if (!nvp_query(__LINE__, data, sqlsock,
787                             "UPDATE `%s`.`ips`, `radacct` "
788                                 "SET `ips`.`rsv_until` = NOW() + INTERVAL %u SECOND "
789                                 "WHERE "
790                                         "`radacct`.`nasipaddress` = '%s' AND "
791                                         "`ips`.`rsv_by` = `radacct`.`acctuniqueid`",
792                             data->db_name, data->freeafter, nasipstr)) {
793                                 sql_release_socket(data->sqlinst, sqlsock);
794                                 return RLM_MODULE_FAIL;
795                         }
796                         nvp_finish(data, sqlsock);
797
798                         break;
799         }
800
801         sql_release_socket(data->sqlinst, sqlsock);
802         return RLM_MODULE_OK;
803 }
804
805 module_t rlm_sqlhpwippool = {
806         RLM_MODULE_INIT,
807         "sqlhpwippool",                 /* name */
808         RLM_TYPE_THREAD_SAFE,           /* type */
809         sqlhpwippool_instantiate,       /* instantiation */
810         sqlhpwippool_detach,            /* detach */
811         {
812                 NULL,                   /* authentication */
813                 NULL,                   /* authorization */
814                 NULL,                   /* preaccounting */
815                 sqlhpwippool_accounting,/* accounting */
816                 NULL,                   /* checksimul */
817                 NULL,                   /* pre-proxy */
818                 NULL,                   /* post-proxy */
819                 sqlhpwippool_postauth   /* post-auth */
820         },
821 };