Use macro for terminating CONF_PARSER arrays
[freeradius.git] / src / modules / rlm_sqlippool / rlm_sqlippool.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_sqlippool.c
20  * @brief Allocates an IPv4 address from pools stored in SQL.
21  *
22  * @copyright 2002  Globe.Net Communications Limited
23  * @copyright 2006  The FreeRADIUS server project
24  * @copyright 2006  Suntel Communications
25  */
26 RCSID("$Id$")
27
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 #include <ctype.h>
32
33 #include <rlm_sql.h>
34
35 #define MAX_QUERY_LEN 4096
36
37 /*
38  *      Define a structure for our module configuration.
39  */
40 typedef struct rlm_sqlippool_t {
41         char const      *sql_instance_name;
42
43         uint32_t        lease_duration;
44
45         rlm_sql_t       *sql_inst;
46
47         char const      *pool_name;
48         bool            ipv6;                   //!< Whether or not we do IPv6 pools.
49         int             framed_ip_address;      //!< the attribute number for Framed-IP(v6)-Address
50
51         time_t          last_clear;             //!< So we only do it once a second.
52         char const      *allocate_begin;        //!< SQL query to begin.
53         char const      *allocate_clear;        //!< SQL query to clear an IP.
54         char const      *allocate_find;         //!< SQL query to find an unused IP.
55         char const      *allocate_update;       //!< SQL query to mark an IP as used.
56         char const      *allocate_commit;       //!< SQL query to commit.
57
58         char const      *pool_check;            //!< Query to check for the existence of the pool.
59
60                                                 /* Start sequence */
61         char const      *start_begin;           //!< SQL query to begin.
62         char const      *start_update;          //!< SQL query to update an IP entry.
63         char const      *start_commit;          //!< SQL query to commit.
64
65                                                 /* Alive sequence */
66         char const      *alive_begin;           //!< SQL query to begin.
67         char const      *alive_update;          //!< SQL query to update an IP entry.
68         char const      *alive_commit;          //!< SQL query to commit.
69
70                                                 /* Stop sequence */
71         char const      *stop_begin;            //!< SQL query to begin.
72         char const      *stop_clear;            //!< SQL query to clear an IP.
73         char const      *stop_commit;           //!< SQL query to commit.
74
75                                                 /* On sequence */
76         char const      *on_begin;              //!< SQL query to begin.
77         char const      *on_clear;              //!< SQL query to clear an entire NAS.
78         char const      *on_commit;             //!< SQL query to commit.
79
80                                                 /* Off sequence */
81         char const      *off_begin;             //!< SQL query to begin.
82         char const      *off_clear;             //!< SQL query to clear an entire NAS.
83         char const      *off_commit;            //!< SQL query to commit.
84
85                                                 /* Logging Section */
86         char const      *log_exists;            //!< There was an ip address already assigned.
87         char const      *log_success;           //!< We successfully allocated ip address from pool.
88         char const      *log_clear;             //!< We successfully deallocated ip address from pool.
89         char const      *log_failed;            //!< Failed to allocate ip from the pool.
90         char const      *log_nopool;            //!< There was no Framed-IP-Address but also no Pool-Name.
91
92                                                 /* Reserved to handle 255.255.255.254 Requests */
93         char const      *defaultpool;           //!< Default Pool-Name if there is none in the check items.
94
95 } rlm_sqlippool_t;
96
97 static CONF_PARSER message_config[] = {
98         { "exists", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_exists), NULL },
99         { "success", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_success), NULL },
100         { "clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_clear), NULL },
101         { "failed", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_failed), NULL },
102         { "nopool", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_nopool), NULL },
103         CONF_PARSER_TERMINATOR
104 };
105
106 /*
107  *      A mapping of configuration file names to internal variables.
108  *
109  *      Note that the string is dynamically allocated, so it MUST
110  *      be freed.  When the configuration file parse re-reads the string,
111  *      it free's the old one, and strdup's the new one, placing the pointer
112  *      to the strdup'd string into 'config.string'.  This gets around
113  *      buffer over-flows.
114  */
115 static CONF_PARSER module_config[] = {
116         { "sql-instance-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, sql_instance_name), NULL },
117         { "sql_module_instance", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_sqlippool_t, sql_instance_name), "sql" },
118
119         { "lease-duration", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, rlm_sqlippool_t, lease_duration), NULL },
120         { "lease_duration", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sqlippool_t, lease_duration), "86400" },
121
122         { "pool-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, pool_name), NULL },
123         { "pool_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, pool_name), "" },
124
125         { "default-pool", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, defaultpool), NULL },
126         { "default_pool", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, defaultpool), "main_pool" },
127
128
129         { "ipv6", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sqlippool_t, ipv6), NULL},
130
131         { "allocate-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_begin), NULL },
132         { "allocate_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_begin), "START TRANSACTION" },
133
134         { "allocate-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_clear), NULL },
135         { "allocate_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, allocate_clear), ""  },
136
137         { "allocate-find", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_find), NULL },
138         { "allocate_find", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_REQUIRED, rlm_sqlippool_t, allocate_find), ""  },
139
140         { "allocate-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_update), NULL },
141         { "allocate_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, allocate_update), ""  },
142
143         { "allocate-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_commit), NULL },
144         { "allocate_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_commit), "COMMIT" },
145
146
147         { "pool-check", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, pool_check), NULL },
148         { "pool_check", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, pool_check), ""  },
149
150
151         { "start-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_begin), NULL },
152         { "start_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, start_begin), "START TRANSACTION" },
153
154         { "start-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_update), NULL },
155         { "start_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, start_update), ""  },
156
157         { "start-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_commit), NULL },
158         { "start_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, start_commit), "COMMIT" },
159
160
161         { "alive-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_begin), NULL },
162         { "alive_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, alive_begin), "START TRANSACTION" },
163
164         { "alive-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_update), NULL },
165         { "alive_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, alive_update), ""  },
166
167         { "alive-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_commit), NULL },
168         { "alive_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, alive_commit), "COMMIT" },
169
170
171         { "stop-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_begin), NULL },
172         { "stop_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, stop_begin), "START TRANSACTION" },
173
174         { "stop-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_clear), NULL },
175         { "stop_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, stop_clear), ""  },
176
177         { "stop-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_commit), NULL },
178         { "stop_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, stop_commit), "COMMIT" },
179
180
181         { "on-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_begin), NULL },
182         { "on_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, on_begin), "START TRANSACTION" },
183
184         { "on-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_clear), NULL },
185         { "on_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, on_clear), ""  },
186
187         { "on-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_commit), NULL },
188         { "on_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, on_commit), "COMMIT" },
189
190
191         { "off-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_begin), NULL },
192         { "off_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, off_begin), "START TRANSACTION" },
193
194         { "off-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_clear), NULL },
195         { "off_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, off_clear), ""  },
196
197         { "off-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_commit), NULL },
198         { "off_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, off_commit), "COMMIT" },
199
200         { "messages", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) message_config },
201         CONF_PARSER_TERMINATOR
202 };
203
204 /*
205  *      Replace %<whatever> in a string.
206  *
207  *      %P      pool_name
208  *      %I      param
209  *      %J      lease_duration
210  *
211  */
212 static int sqlippool_expand(char * out, int outlen, char const * fmt,
213                             rlm_sqlippool_t *data, char * param, int param_len)
214 {
215         char *q;
216         char const *p;
217         char tmp[40]; /* For temporary storing of integers */
218
219         q = out;
220         for (p = fmt; *p ; p++) {
221                 int freespace;
222                 int c;
223
224                 /* Calculate freespace in output */
225                 freespace = outlen - (q - out);
226                 if (freespace <= 1)
227                         break;
228
229                 c = *p;
230                 if (c != '%') {
231                         *q++ = *p;
232                         continue;
233                 }
234
235                 if (*++p == '\0') {
236                         break;
237                 }
238
239                 if (c == '%') {
240                         switch (*p) {
241                         case 'P': /* pool name */
242                                 strlcpy(q, data->pool_name, freespace);
243                                 q += strlen(q);
244                                 break;
245                         case 'I': /* IP address */
246                                 if (param && param_len > 0) {
247                                         if (param_len > freespace) {
248                                                 strlcpy(q, param, freespace);
249                                                 q += strlen(q);
250                                         }
251                                         else {
252                                                 memcpy(q, param, param_len);
253                                                 q += param_len;
254                                         }
255                                 }
256                                 break;
257                         case 'J': /* lease duration */
258                                 sprintf(tmp, "%d", data->lease_duration);
259                                 strlcpy(q, tmp, freespace);
260                                 q += strlen(q);
261                                 break;
262
263                         default:
264                                 *q++ = '%';
265                                 *q++ = *p;
266                                 break;
267                         }
268                 }
269         }
270         *q = '\0';
271
272 #if 0
273         DEBUG2("sqlippool_expand: \"%s\"", out);
274 #endif
275
276         return strlen(out);
277 }
278
279 /** Perform a single sqlippool query
280  *
281  * Mostly wrapper around sql_query which does some special sqlippool sequence substitutions and expands
282  * the format string.
283  *
284  * @param fmt sql query to expand.
285  * @param handle sql connection handle.
286  * @param data Instance of rlm_sqlippool.
287  * @param request Current request.
288  * @param param ip address string.
289  * @param param_len ip address string len.
290  * @return 0 on success or < 0 on error.
291  */
292 static int sqlippool_command(char const * fmt, rlm_sql_handle_t * handle, rlm_sqlippool_t *data, REQUEST *request,
293                              char *param, int param_len)
294 {
295         char query[MAX_QUERY_LEN];
296         char *expanded = NULL;
297
298         int ret;
299
300         /*
301          *      If we don't have a command, do nothing.
302          */
303         if (!fmt || !*fmt) return 0;
304
305         /*
306          *      @todo this needs to die (should just be done in xlat expansion)
307          */
308         sqlippool_expand(query, sizeof(query), fmt, data, param, param_len);
309
310         if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, data->sql_inst) < 0) {
311                 return -1;
312         }
313
314         ret = data->sql_inst->sql_query(data->sql_inst, request, &handle, expanded);
315         if (ret < 0){
316                 talloc_free(expanded);
317                 return -1;
318         }
319         talloc_free(expanded);
320
321         (data->sql_inst->module->sql_finish_query)(handle, data->sql_inst->config);
322         return 0;
323 }
324
325 /*
326  *      Don't repeat yourself
327  */
328 #undef DO
329 #define DO(_x) sqlippool_command(inst->_x, handle, inst, request, NULL, 0)
330
331 /*
332  * Query the database expecting a single result row
333  */
334 static int CC_HINT(nonnull (1, 3, 4, 5)) sqlippool_query1(char *out, int outlen, char const *fmt,
335                                                           rlm_sql_handle_t *handle, rlm_sqlippool_t *data,
336                                                           REQUEST *request, char *param, int param_len)
337 {
338         char query[MAX_QUERY_LEN];
339         char *expanded = NULL;
340
341         int rlen, retval;
342
343         /*
344          *      @todo this needs to die (should just be done in xlat expansion)
345          */
346         sqlippool_expand(query, sizeof(query), fmt, data, param, param_len);
347
348         *out = '\0';
349
350         /*
351          *      Do an xlat on the provided string
352          */
353         if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, data->sql_inst) < 0) {
354                 return 0;
355         }
356         retval = data->sql_inst->sql_select_query(data->sql_inst, request, &handle, expanded);
357         talloc_free(expanded);
358
359         if (retval != 0){
360                 REDEBUG("database query error on '%s'", query);
361                 return 0;
362         }
363
364         if (data->sql_inst->sql_fetch_row(data->sql_inst, request, &handle) < 0) {
365                 REDEBUG("Failed fetching query result");
366                 goto finish;
367         }
368
369         if (!handle->row) {
370                 REDEBUG("SQL query did not return any results");
371                 goto finish;
372         }
373
374         if (!handle->row[0]) {
375                 REDEBUG("The first column of the result was NULL");
376                 goto finish;
377         }
378
379         rlen = strlen(handle->row[0]);
380         if (rlen >= outlen) {
381                 RDEBUG("insufficient string space");
382                 goto finish;
383         }
384
385         strcpy(out, handle->row[0]);
386         retval = rlen;
387 finish:
388         (data->sql_inst->module->sql_finish_select_query)(handle, data->sql_inst->config);
389
390         return retval;
391 }
392
393 /*
394  *      Do any per-module initialization that is separate to each
395  *      configured instance of the module.  e.g. set up connections
396  *      to external databases, read configuration files, set up
397  *      dictionary entries, etc.
398  *
399  *      If configuration information is given in the config section
400  *      that must be referenced in later calls, store a handle to it
401  *      in *instance otherwise put a null pointer there.
402  */
403 static int mod_instantiate(CONF_SECTION *conf, void *instance)
404 {
405         module_instance_t *sql_inst;
406         rlm_sqlippool_t *inst = instance;
407         char const *pool_name = NULL;
408
409         pool_name = cf_section_name2(conf);
410         if (pool_name != NULL) {
411                 inst->pool_name = talloc_typed_strdup(inst, pool_name);
412         } else {
413                 inst->pool_name = talloc_typed_strdup(inst, "ippool");
414         }
415         sql_inst = module_instantiate(cf_section_find("modules"),
416                                         inst->sql_instance_name);
417         if (!sql_inst) {
418                 cf_log_err_cs(conf, "failed to find sql instance named %s",
419                            inst->sql_instance_name);
420                 return -1;
421         }
422
423         if (!inst->ipv6) {
424                 inst->framed_ip_address = PW_FRAMED_IP_ADDRESS;
425         } else {
426                 inst->framed_ip_address = PW_FRAMED_IPV6_PREFIX;
427         }
428
429         if (strcmp(sql_inst->entry->name, "rlm_sql") != 0) {
430                 cf_log_err_cs(conf, "Module \"%s\""
431                        " is not an instance of the rlm_sql module",
432                        inst->sql_instance_name);
433                 return -1;
434         }
435
436         inst->sql_inst = (rlm_sql_t *) sql_inst->insthandle;
437         return 0;
438 }
439
440
441 /*
442  *      If we have something to log, then we log it.
443  *      Otherwise we return the retcode as soon as possible
444  */
445 static int do_logging(REQUEST *request, char const *str, int rcode)
446 {
447         char *expanded = NULL;
448
449         if (!str || !*str) return rcode;
450
451         if (radius_axlat(&expanded, request, str, NULL, NULL) < 0) {
452                 return rcode;
453         }
454
455         pair_make_config("Module-Success-Message", expanded, T_OP_SET);
456
457         talloc_free(expanded);
458
459         return rcode;
460 }
461
462
463 /*
464  *      Allocate an IP number from the pool.
465  */
466 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
467 {
468         rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance;
469         char allocation[MAX_STRING_LEN];
470         int allocation_len;
471         VALUE_PAIR *vp;
472         rlm_sql_handle_t *handle;
473         time_t now;
474
475         /*
476          *      If there is a Framed-IP-Address attribute in the reply do nothing
477          */
478         if (fr_pair_find_by_num(request->reply->vps, inst->framed_ip_address, 0, TAG_ANY) != NULL) {
479                 RDEBUG("Framed-IP-Address already exists");
480
481                 return do_logging(request, inst->log_exists, RLM_MODULE_NOOP);
482         }
483
484         if (fr_pair_find_by_num(request->config, PW_POOL_NAME, 0, TAG_ANY) == NULL) {
485                 RDEBUG("No Pool-Name defined");
486
487                 return do_logging(request, inst->log_nopool, RLM_MODULE_NOOP);
488         }
489
490         handle = fr_connection_get(inst->sql_inst->pool);
491         if (!handle) {
492                 REDEBUG("cannot get sql connection");
493                 return RLM_MODULE_FAIL;
494         }
495
496         if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) {
497                 return RLM_MODULE_FAIL;
498         }
499
500         /*
501          *      Limit the number of clears we do.  There are minor
502          *      race conditions for the check, but so what.  The
503          *      actual work is protected by a transaction.  The idea
504          *      here is that if we're allocating 100 IPs a second,
505          *      we're only do 1 CLEAR per second.
506          */
507         now = time(NULL);
508         if (inst->last_clear < now) {
509                 inst->last_clear = now;
510
511                 DO(allocate_begin);
512                 DO(allocate_clear);
513                 DO(allocate_commit);
514         }
515
516         DO(allocate_begin);
517
518         allocation_len = sqlippool_query1(allocation, sizeof(allocation),
519                                           inst->allocate_find, handle,
520                                           inst, request, (char *) NULL, 0);
521
522         /*
523          *      Nothing found...
524          */
525         if (allocation_len == 0) {
526                 DO(allocate_commit);
527
528                 /*
529                  *Should we perform pool-check ?
530                  */
531                 if (inst->pool_check && *inst->pool_check) {
532
533                         /*
534                          *Ok, so the allocate-find query found nothing ...
535                          *Let's check if the pool exists at all
536                          */
537                         allocation_len = sqlippool_query1(allocation, sizeof(allocation),
538                                                           inst->pool_check, handle, inst, request,
539                                                           (char *) NULL, 0);
540
541                         fr_connection_release(inst->sql_inst->pool, handle);
542
543                         if (allocation_len) {
544
545                                 /*
546                                  *      Pool exists after all... So,
547                                  *      the failure to allocate the IP
548                                  *      address was most likely due to
549                                  *      the depletion of the pool. In
550                                  *      that case, we should return
551                                  *      NOTFOUND
552                                  */
553                                 RDEBUG("pool appears to be full");
554                                 return do_logging(request, inst->log_failed, RLM_MODULE_NOTFOUND);
555
556                         }
557
558                         /*
559                          *      Pool doesn't exist in the table. It
560                          *      may be handled by some other instance of
561                          *      sqlippool, so we should just ignore this
562                          *      allocation failure and return NOOP
563                          */
564                         RDEBUG("IP address could not be allocated as no pool exists with that name");
565                         return RLM_MODULE_NOOP;
566
567                 }
568
569                 fr_connection_release(inst->sql_inst->pool, handle);
570
571                 RDEBUG("IP address could not be allocated");
572                 return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
573         }
574
575         /*
576          *      See if we can create the VP from the returned data.  If not,
577          *      error out.  If so, add it to the list.
578          */
579         vp = fr_pair_afrom_num(request->reply, inst->framed_ip_address, 0);
580         if (fr_pair_value_from_str(vp, allocation, allocation_len) < 0) {
581                 DO(allocate_commit);
582
583                 RDEBUG("Invalid IP number [%s] returned from instbase query.", allocation);
584                 fr_connection_release(inst->sql_inst->pool, handle);
585                 return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
586         }
587
588         RDEBUG("Allocated IP %s", allocation);
589         fr_pair_add(&request->reply->vps, vp);
590
591         /*
592          *      UPDATE
593          */
594         sqlippool_command(inst->allocate_update, handle, inst, request,
595                           allocation, allocation_len);
596
597         DO(allocate_commit);
598
599         fr_connection_release(inst->sql_inst->pool, handle);
600
601         return do_logging(request, inst->log_success, RLM_MODULE_OK);
602 }
603
604 static int mod_accounting_start(rlm_sql_handle_t *handle,
605                                       rlm_sqlippool_t *inst, REQUEST *request)
606 {
607         DO(start_begin);
608         DO(start_update);
609         DO(start_commit);
610
611         return RLM_MODULE_OK;
612 }
613
614 static int mod_accounting_alive(rlm_sql_handle_t *handle,
615                                       rlm_sqlippool_t *inst, REQUEST *request)
616 {
617         DO(alive_begin);
618         DO(alive_update);
619         DO(alive_commit);
620         return RLM_MODULE_OK;
621 }
622
623 static int mod_accounting_stop(rlm_sql_handle_t *handle,
624                                       rlm_sqlippool_t *inst, REQUEST *request)
625 {
626         DO(stop_begin);
627         DO(stop_clear);
628         DO(stop_commit);
629
630         return do_logging(request, inst->log_clear, RLM_MODULE_OK);
631 }
632
633 static int mod_accounting_on(rlm_sql_handle_t *handle,
634                                       rlm_sqlippool_t *inst, REQUEST *request)
635 {
636         DO(on_begin);
637         DO(on_clear);
638         DO(on_commit);
639
640         return RLM_MODULE_OK;
641 }
642
643 static int mod_accounting_off(rlm_sql_handle_t *handle,
644                                       rlm_sqlippool_t *inst, REQUEST *request)
645 {
646         DO(off_begin);
647         DO(off_clear);
648         DO(off_commit);
649
650         return RLM_MODULE_OK;
651 }
652
653 /*
654  *      Check for an Accounting-Stop
655  *      If we find one and we have allocated an IP to this nas/port
656  *      combination, then deallocate it.
657  */
658 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
659 {
660         int rcode = RLM_MODULE_NOOP;
661         VALUE_PAIR *vp;
662         int acct_status_type;
663         rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance;
664         rlm_sql_handle_t *handle;
665
666         vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY);
667         if (!vp) {
668                 RDEBUG("Could not find account status type in packet");
669                 return RLM_MODULE_NOOP;
670         }
671         acct_status_type = vp->vp_integer;
672
673         switch (acct_status_type) {
674         case PW_STATUS_START:
675         case PW_STATUS_ALIVE:
676         case PW_STATUS_STOP:
677         case PW_STATUS_ACCOUNTING_ON:
678         case PW_STATUS_ACCOUNTING_OFF:
679                 break;          /* continue through to the next section */
680
681         default:
682                 /* We don't care about any other accounting packet */
683                 return RLM_MODULE_NOOP;
684         }
685
686         handle = fr_connection_get(inst->sql_inst->pool);
687         if (!handle) {
688                 RDEBUG("Cannot allocate sql connection");
689                 return RLM_MODULE_FAIL;
690         }
691
692         if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) {
693                 return RLM_MODULE_FAIL;
694         }
695
696         switch (acct_status_type) {
697         case PW_STATUS_START:
698                 rcode = mod_accounting_start(handle, inst, request);
699                 break;
700
701         case PW_STATUS_ALIVE:
702                 rcode = mod_accounting_alive(handle, inst, request);
703                 break;
704
705         case PW_STATUS_STOP:
706                 rcode = mod_accounting_stop(handle, inst, request);
707                 break;
708
709         case PW_STATUS_ACCOUNTING_ON:
710                 rcode = mod_accounting_on(handle, inst, request);
711                 break;
712
713         case PW_STATUS_ACCOUNTING_OFF:
714                 rcode = mod_accounting_off(handle, inst, request);
715                 break;
716         }
717
718         fr_connection_release(inst->sql_inst->pool, handle);
719
720         return rcode;
721 }
722
723 /*
724  *      The module name should be the only globally exported symbol.
725  *      That is, everything else should be 'static'.
726  *
727  *      If the module needs to temporarily modify it's instantiation
728  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
729  *      The server will then take care of ensuring that the module
730  *      is single-threaded.
731  */
732 extern module_t rlm_sqlippool;
733 module_t rlm_sqlippool = {
734         .magic          = RLM_MODULE_INIT,
735         .name           = "sqlippool",
736         .type           = RLM_TYPE_THREAD_SAFE,
737         .inst_size      = sizeof(rlm_sqlippool_t),
738         .config         = module_config,
739         .instantiate    = mod_instantiate,
740         .methods = {
741                 [MOD_ACCOUNTING]        = mod_accounting,
742                 [MOD_POST_AUTH]         = mod_post_auth
743         },
744 };