Merge tag 'release_3_0_12' into branch moonshot-fr-3.0.12-upgrade.
[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,
293                              rlm_sqlippool_t *data, REQUEST *request,
294                              char *param, int param_len)
295 {
296         char query[MAX_QUERY_LEN];
297         char *expanded = NULL;
298
299         int ret;
300
301         /*
302          *      If we don't have a command, do nothing.
303          */
304         if (!fmt || !*fmt) return 0;
305
306         /*
307          *      @todo this needs to die (should just be done in xlat expansion)
308          */
309         sqlippool_expand(query, sizeof(query), fmt, data, param, param_len);
310
311         if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, data->sql_inst) < 0) return -1;
312
313         ret = data->sql_inst->sql_query(data->sql_inst, request, handle, expanded);
314         if (ret < 0){
315                 talloc_free(expanded);
316                 return -1;
317         }
318         talloc_free(expanded);
319
320         if (*handle) (data->sql_inst->module->sql_finish_query)(*handle, data->sql_inst->config);
321
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 #define DO_PART(_x) sqlippool_command(inst->_x, &handle, inst, request, NULL, 0)
331
332 /*
333  * Query the database expecting a single result row
334  */
335 static int CC_HINT(nonnull (1, 3, 4, 5)) sqlippool_query1(char *out, int outlen, char const *fmt,
336                                                           rlm_sql_handle_t *handle, rlm_sqlippool_t *data,
337                                                           REQUEST *request, char *param, int param_len)
338 {
339         char query[MAX_QUERY_LEN];
340         char *expanded = NULL;
341
342         int rlen, retval;
343
344         /*
345          *      @todo this needs to die (should just be done in xlat expansion)
346          */
347         sqlippool_expand(query, sizeof(query), fmt, data, param, param_len);
348
349         *out = '\0';
350
351         /*
352          *      Do an xlat on the provided string
353          */
354         if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, data->sql_inst) < 0) {
355                 return 0;
356         }
357         retval = data->sql_inst->sql_select_query(data->sql_inst, request, &handle, expanded);
358         talloc_free(expanded);
359
360         if (retval != 0){
361                 REDEBUG("database query error on '%s'", query);
362                 return 0;
363         }
364
365         if (data->sql_inst->sql_fetch_row(data->sql_inst, request, &handle) < 0) {
366                 REDEBUG("Failed fetching query result");
367                 goto finish;
368         }
369
370         if (!handle->row) {
371                 REDEBUG("SQL query did not return any results");
372                 goto finish;
373         }
374
375         if (!handle->row[0]) {
376                 REDEBUG("The first column of the result was NULL");
377                 goto finish;
378         }
379
380         rlen = strlen(handle->row[0]);
381         if (rlen >= outlen) {
382                 RDEBUG("insufficient string space");
383                 goto finish;
384         }
385
386         strcpy(out, handle->row[0]);
387         retval = rlen;
388 finish:
389         (data->sql_inst->module->sql_finish_select_query)(handle, data->sql_inst->config);
390
391         return retval;
392 }
393
394 /*
395  *      Do any per-module initialization that is separate to each
396  *      configured instance of the module.  e.g. set up connections
397  *      to external databases, read configuration files, set up
398  *      dictionary entries, etc.
399  *
400  *      If configuration information is given in the config section
401  *      that must be referenced in later calls, store a handle to it
402  *      in *instance otherwise put a null pointer there.
403  */
404 static int mod_instantiate(CONF_SECTION *conf, void *instance)
405 {
406         module_instance_t *sql_inst;
407         rlm_sqlippool_t *inst = instance;
408         char const *pool_name = NULL;
409
410         pool_name = cf_section_name2(conf);
411         if (pool_name != NULL) {
412                 inst->pool_name = talloc_typed_strdup(inst, pool_name);
413         } else {
414                 inst->pool_name = talloc_typed_strdup(inst, "ippool");
415         }
416         sql_inst = module_instantiate(cf_section_find("modules"),
417                                         inst->sql_instance_name);
418         if (!sql_inst) {
419                 cf_log_err_cs(conf, "failed to find sql instance named %s",
420                            inst->sql_instance_name);
421                 return -1;
422         }
423
424         if (!inst->ipv6) {
425                 inst->framed_ip_address = PW_FRAMED_IP_ADDRESS;
426         } else {
427                 inst->framed_ip_address = PW_FRAMED_IPV6_PREFIX;
428         }
429
430         if (strcmp(sql_inst->entry->name, "rlm_sql") != 0) {
431                 cf_log_err_cs(conf, "Module \"%s\""
432                        " is not an instance of the rlm_sql module",
433                        inst->sql_instance_name);
434                 return -1;
435         }
436
437         inst->sql_inst = (rlm_sql_t *) sql_inst->insthandle;
438         return 0;
439 }
440
441
442 /*
443  *      If we have something to log, then we log it.
444  *      Otherwise we return the retcode as soon as possible
445  */
446 static int do_logging(REQUEST *request, char const *str, int rcode)
447 {
448         char *expanded = NULL;
449
450         if (!str || !*str) return rcode;
451
452         if (radius_axlat(&expanded, request, str, NULL, NULL) < 0) {
453                 return rcode;
454         }
455
456         pair_make_config("Module-Success-Message", expanded, T_OP_SET);
457
458         talloc_free(expanded);
459
460         return rcode;
461 }
462
463
464 /*
465  *      Allocate an IP number from the pool.
466  */
467 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
468 {
469         rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance;
470         char allocation[MAX_STRING_LEN];
471         int allocation_len;
472         VALUE_PAIR *vp;
473         rlm_sql_handle_t *handle;
474         time_t now;
475
476         /*
477          *      If there is a Framed-IP-Address attribute in the reply do nothing
478          */
479         if (fr_pair_find_by_num(request->reply->vps, inst->framed_ip_address, 0, TAG_ANY) != NULL) {
480                 RDEBUG("Framed-IP-Address already exists");
481
482                 return do_logging(request, inst->log_exists, RLM_MODULE_NOOP);
483         }
484
485         if (fr_pair_find_by_num(request->config, PW_POOL_NAME, 0, TAG_ANY) == NULL) {
486                 RDEBUG("No Pool-Name defined");
487
488                 return do_logging(request, inst->log_nopool, RLM_MODULE_NOOP);
489         }
490
491         handle = fr_connection_get(inst->sql_inst->pool);
492         if (!handle) {
493                 REDEBUG("Failed reserving SQL connection");
494                 return RLM_MODULE_FAIL;
495         }
496
497         if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) {
498                 return RLM_MODULE_FAIL;
499         }
500
501         /*
502          *      Limit the number of clears we do.  There are minor
503          *      race conditions for the check, but so what.  The
504          *      actual work is protected by a transaction.  The idea
505          *      here is that if we're allocating 100 IPs a second,
506          *      we're only do 1 CLEAR per second.
507          */
508         now = time(NULL);
509         if (inst->last_clear < now) {
510                 inst->last_clear = now;
511
512                 DO_PART(allocate_begin);
513                 DO_PART(allocate_clear);
514                 DO_PART(allocate_commit);
515         }
516
517         DO_PART(allocate_begin);
518
519         allocation_len = sqlippool_query1(allocation, sizeof(allocation),
520                                           inst->allocate_find, handle,
521                                           inst, request, (char *) NULL, 0);
522
523         /*
524          *      Nothing found...
525          */
526         if (allocation_len == 0) {
527                 DO_PART(allocate_commit);
528
529                 /*
530                  *Should we perform pool-check ?
531                  */
532                 if (inst->pool_check && *inst->pool_check) {
533
534                         /*
535                          *Ok, so the allocate-find query found nothing ...
536                          *Let's check if the pool exists at all
537                          */
538                         allocation_len = sqlippool_query1(allocation, sizeof(allocation),
539                                                           inst->pool_check, handle, inst, request,
540                                                           (char *) NULL, 0);
541
542                         fr_connection_release(inst->sql_inst->pool, handle);
543
544                         if (allocation_len) {
545
546                                 /*
547                                  *      Pool exists after all... So,
548                                  *      the failure to allocate the IP
549                                  *      address was most likely due to
550                                  *      the depletion of the pool. In
551                                  *      that case, we should return
552                                  *      NOTFOUND
553                                  */
554                                 RDEBUG("pool appears to be full");
555                                 return do_logging(request, inst->log_failed, RLM_MODULE_NOTFOUND);
556
557                         }
558
559                         /*
560                          *      Pool doesn't exist in the table. It
561                          *      may be handled by some other instance of
562                          *      sqlippool, so we should just ignore this
563                          *      allocation failure and return NOOP
564                          */
565                         RDEBUG("IP address could not be allocated as no pool exists with that name");
566                         return RLM_MODULE_NOOP;
567
568                 }
569
570                 fr_connection_release(inst->sql_inst->pool, handle);
571
572                 RDEBUG("IP address could not be allocated");
573                 return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
574         }
575
576         /*
577          *      See if we can create the VP from the returned data.  If not,
578          *      error out.  If so, add it to the list.
579          */
580         vp = fr_pair_afrom_num(request->reply, inst->framed_ip_address, 0);
581         if (fr_pair_value_from_str(vp, allocation, allocation_len) < 0) {
582                 DO_PART(allocate_commit);
583
584                 RDEBUG("Invalid IP number [%s] returned from instbase query.", allocation);
585                 fr_connection_release(inst->sql_inst->pool, handle);
586                 return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
587         }
588
589         RDEBUG("Allocated IP %s", allocation);
590         fr_pair_add(&request->reply->vps, vp);
591
592         /*
593          *      UPDATE
594          */
595         sqlippool_command(inst->allocate_update, &handle, inst, request,
596                           allocation, allocation_len);
597
598         DO_PART(allocate_commit);
599
600         fr_connection_release(inst->sql_inst->pool, handle);
601
602         return do_logging(request, inst->log_success, RLM_MODULE_OK);
603 }
604
605 static int mod_accounting_start(rlm_sql_handle_t **handle,
606                                 rlm_sqlippool_t *inst, REQUEST *request)
607 {
608         DO(start_begin);
609         DO(start_update);
610         DO(start_commit);
611
612         return RLM_MODULE_OK;
613 }
614
615 static int mod_accounting_alive(rlm_sql_handle_t **handle,
616                                 rlm_sqlippool_t *inst, REQUEST *request)
617 {
618         DO(alive_begin);
619         DO(alive_update);
620         DO(alive_commit);
621         return RLM_MODULE_OK;
622 }
623
624 static int mod_accounting_stop(rlm_sql_handle_t **handle,
625                                rlm_sqlippool_t *inst, REQUEST *request)
626 {
627         DO(stop_begin);
628         DO(stop_clear);
629         DO(stop_commit);
630
631         return do_logging(request, inst->log_clear, RLM_MODULE_OK);
632 }
633
634 static int mod_accounting_on(rlm_sql_handle_t **handle,
635                              rlm_sqlippool_t *inst, REQUEST *request)
636 {
637         DO(on_begin);
638         DO(on_clear);
639         DO(on_commit);
640
641         return RLM_MODULE_OK;
642 }
643
644 static int mod_accounting_off(rlm_sql_handle_t **handle,
645                               rlm_sqlippool_t *inst, REQUEST *request)
646 {
647         DO(off_begin);
648         DO(off_clear);
649         DO(off_commit);
650
651         return RLM_MODULE_OK;
652 }
653
654 /*
655  *      Check for an Accounting-Stop
656  *      If we find one and we have allocated an IP to this nas/port
657  *      combination, then deallocate it.
658  */
659 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
660 {
661         int                     rcode = RLM_MODULE_NOOP;
662         VALUE_PAIR              *vp;
663
664         int                     acct_status_type;
665
666         rlm_sqlippool_t         *inst = (rlm_sqlippool_t *) instance;
667         rlm_sql_handle_t        *handle;
668
669         vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY);
670         if (!vp) {
671                 RDEBUG("Could not find account status type in packet");
672                 return RLM_MODULE_NOOP;
673         }
674         acct_status_type = vp->vp_integer;
675
676         switch (acct_status_type) {
677         case PW_STATUS_START:
678         case PW_STATUS_ALIVE:
679         case PW_STATUS_STOP:
680         case PW_STATUS_ACCOUNTING_ON:
681         case PW_STATUS_ACCOUNTING_OFF:
682                 break;          /* continue through to the next section */
683
684         default:
685                 /* We don't care about any other accounting packet */
686                 return RLM_MODULE_NOOP;
687         }
688
689         handle = fr_connection_get(inst->sql_inst->pool);
690         if (!handle) {
691                 RDEBUG("Failed reserving SQL connection");
692                 return RLM_MODULE_FAIL;
693         }
694
695         if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) return RLM_MODULE_FAIL;
696
697         switch (acct_status_type) {
698         case PW_STATUS_START:
699                 rcode = mod_accounting_start(&handle, inst, request);
700                 break;
701
702         case PW_STATUS_ALIVE:
703                 rcode = mod_accounting_alive(&handle, inst, request);
704                 break;
705
706         case PW_STATUS_STOP:
707                 rcode = mod_accounting_stop(&handle, inst, request);
708                 break;
709
710         case PW_STATUS_ACCOUNTING_ON:
711                 rcode = mod_accounting_on(&handle, inst, request);
712                 break;
713
714         case PW_STATUS_ACCOUNTING_OFF:
715                 rcode = mod_accounting_off(&handle, inst, request);
716                 break;
717         }
718
719         fr_connection_release(inst->sql_inst->pool, handle);
720
721         return rcode;
722 }
723
724 /*
725  *      The module name should be the only globally exported symbol.
726  *      That is, everything else should be 'static'.
727  *
728  *      If the module needs to temporarily modify it's instantiation
729  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
730  *      The server will then take care of ensuring that the module
731  *      is single-threaded.
732  */
733 extern module_t rlm_sqlippool;
734 module_t rlm_sqlippool = {
735         .magic          = RLM_MODULE_INIT,
736         .name           = "sqlippool",
737         .type           = RLM_TYPE_THREAD_SAFE,
738         .inst_size      = sizeof(rlm_sqlippool_t),
739         .config         = module_config,
740         .instantiate    = mod_instantiate,
741         .methods = {
742                 [MOD_ACCOUNTING]        = mod_accounting,
743                 [MOD_POST_AUTH]         = mod_post_auth
744         },
745 };