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