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: 0e3679d60ed6fc96cb7022987572dba6792fc5e6 $
19 * @file rlm_sqlippool.c
20 * @brief Allocates an IP address / prefix 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: 0e3679d60ed6fc96cb7022987572dba6792fc5e6 $")
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; //!< Name of the attribute in the check VPS for which the value will be used as key
48 bool ipv6; //!< Whether or not we do IPv6 pools.
49 bool allow_duplicates; //!< assign even if it already exists
50 char const *attribute_name; //!< name of the IP address attribute
51 char const *req_attribute_name; //!< name of the requested IP address attribute
52
53 DICT_ATTR const *framed_ip_address; //!< the attribute for IP address allocation
54 DICT_ATTR const *req_framed_ip_address; //!< the attribute for requested IP address
55 DICT_ATTR const *pool_attribute; //!< the attribute corresponding to the pool_name
56
57 time_t last_clear; //!< So we only do it once a second.
58 char const *allocate_begin; //!< SQL query to begin.
59 char const *allocate_clear; //!< SQL query to clear an IP.
60 uint32_t allocate_clear_timeout; //!< Number of second between two allocate_clear SQL query
61 char const *allocate_existing; //!< SQL query to find existing IP leased to the device.
62 char const *allocate_requested; //!< SQL query to find requested IP.
63 char const *allocate_find; //!< SQL query to find an unused IP.
64 char const *allocate_update; //!< SQL query to mark an IP as used.
65 char const *allocate_commit; //!< SQL query to commit.
66
67 char const *pool_check; //!< Query to check for the existence of the pool.
68
69 /* Start sequence */
70 char const *start_begin; //!< SQL query to begin.
71 char const *start_update; //!< SQL query to update an IP entry.
72 char const *start_commit; //!< SQL query to commit.
73
74 /* Alive sequence */
75 char const *alive_begin; //!< SQL query to begin.
76 char const *alive_update; //!< SQL query to update an IP entry.
77 char const *alive_commit; //!< SQL query to commit.
78
79 /* Stop sequence */
80 char const *stop_begin; //!< SQL query to begin.
81 char const *stop_clear; //!< SQL query to clear an IP.
82 char const *stop_commit; //!< SQL query to commit.
83
84 /* On sequence */
85 char const *on_begin; //!< SQL query to begin.
86 char const *on_clear; //!< SQL query to clear an entire NAS.
87 char const *on_commit; //!< SQL query to commit.
88
89 /* Off sequence */
90 char const *off_begin; //!< SQL query to begin.
91 char const *off_clear; //!< SQL query to clear an entire NAS.
92 char const *off_commit; //!< SQL query to commit.
93
94 /* Logging Section */
95 char const *log_exists; //!< There was an ip address already assigned.
96 char const *log_success; //!< We successfully allocated ip address from pool.
97 char const *log_clear; //!< We successfully deallocated ip address from pool.
98 char const *log_failed; //!< Failed to allocate ip from the pool.
99 char const *log_nopool; //!< There was no Framed-IP-Address but also no Pool-Name.
100
101 /* Reserved to handle 255.255.255.254 Requests */
102 char const *defaultpool; //!< Default Pool-Name if there is none in the check items.
103
104 } rlm_sqlippool_t;
105
106 static CONF_PARSER message_config[] = {
107 { "exists", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_exists), NULL },
108 { "success", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_success), NULL },
109 { "clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_clear), NULL },
110 { "failed", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_failed), NULL },
111 { "nopool", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_nopool), NULL },
112 CONF_PARSER_TERMINATOR
113 };
114
115 /*
116 * A mapping of configuration file names to internal variables.
117 *
118 * Note that the string is dynamically allocated, so it MUST
119 * be freed. When the configuration file parse re-reads the string,
120 * it free's the old one, and strdup's the new one, placing the pointer
121 * to the strdup'd string into 'config.string'. This gets around
122 * buffer over-flows.
123 */
124 static CONF_PARSER module_config[] = {
125 { "sql-instance-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, sql_instance_name), NULL },
126 { "sql_module_instance", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_sqlippool_t, sql_instance_name), "sql" },
127
128 { "lease-duration", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, rlm_sqlippool_t, lease_duration), NULL },
129 { "lease_duration", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sqlippool_t, lease_duration), "86400" },
130
131 { "pool-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, pool_name), NULL },
132 { "pool_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, pool_name), "Pool-Name" },
133
134 { "default-pool", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, defaultpool), NULL },
135 { "default_pool", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, defaultpool), "main_pool" },
136
137
138 { "ipv6", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sqlippool_t, ipv6), NULL},
139 { "allow_duplicates", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sqlippool_t, allow_duplicates), NULL},
140 { "attribute_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, attribute_name), NULL},
141 { "req_attribute_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, req_attribute_name), NULL},
142
143 { "allocate-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_begin), NULL },
144 { "allocate_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_begin), "START TRANSACTION" },
145
146 { "allocate-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_clear), NULL },
147 { "allocate_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, allocate_clear), "" },
148
149 { "allocate_clear_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sqlippool_t, allocate_clear_timeout), "1" },
150
151 { "allocate-existing", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_existing), NULL },
152 { "allocate_existing", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_existing), "" },
153
154 { "allocate-requested", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_requested), NULL },
155 { "allocate_requested", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_requested), "" },
156
157 { "allocate-find", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_find), NULL },
158 { "allocate_find", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_REQUIRED, rlm_sqlippool_t, allocate_find), "" },
159
160 { "allocate-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_update), NULL },
161 { "allocate_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, allocate_update), "" },
162
163 { "allocate-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_commit), NULL },
164 { "allocate_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_commit), "COMMIT" },
165
166
167 { "pool-check", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, pool_check), NULL },
168 { "pool_check", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, pool_check), "" },
169
170
171 { "start-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_begin), NULL },
172 { "start_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, start_begin), "" },
173
174 { "start-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_update), NULL },
175 { "start_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, start_update), "" },
176
177 { "start-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_commit), NULL },
178 { "start_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, start_commit), "" },
179
180
181 { "alive-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_begin), NULL },
182 { "alive_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, alive_begin), "" },
183
184 { "alive-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_update), NULL },
185 { "alive_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, alive_update), "" },
186
187 { "alive-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_commit), NULL },
188 { "alive_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, alive_commit), "" },
189
190
191 { "stop-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_begin), NULL },
192 { "stop_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, stop_begin), "" },
193
194 { "stop-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_clear), NULL },
195 { "stop_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, stop_clear), "" },
196
197 { "stop-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_commit), NULL },
198 { "stop_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, stop_commit), "" },
199
200
201 { "on-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_begin), NULL },
202 { "on_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, on_begin), "" },
203
204 { "on-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_clear), NULL },
205 { "on_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, on_clear), "" },
206
207 { "on-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_commit), NULL },
208 { "on_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, on_commit), "" },
209
210
211 { "off-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_begin), NULL },
212 { "off_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, off_begin), "" },
213
214 { "off-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_clear), NULL },
215 { "off_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, off_clear), "" },
216
217 { "off-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_commit), NULL },
218 { "off_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, off_commit), "" },
219
220 { "messages", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) message_config },
221 CONF_PARSER_TERMINATOR
222 };
223
224 /*
225 * Replace %<whatever> in a string.
226 *
227 * %P pool_name
228 * %I param
229 * %J lease_duration
230 *
231 */
sqlippool_expand(char * out,int outlen,char const * fmt,rlm_sqlippool_t * data,char * param,int param_len)232 static int sqlippool_expand(char * out, int outlen, char const * fmt,
233 rlm_sqlippool_t *data, char * param, int param_len)
234 {
235 char *q;
236 char const *p;
237 char tmp[40]; /* For temporary storing of integers */
238
239 q = out;
240 for (p = fmt; *p ; p++) {
241 int freespace;
242 int c;
243
244 /* Calculate freespace in output */
245 freespace = outlen - (q - out);
246 if (freespace <= 1)
247 break;
248
249 c = *p;
250 if (c != '%') {
251 *q++ = *p;
252 continue;
253 }
254
255 if (*++p == '\0') {
256 break;
257 }
258
259 if (c == '%') {
260 switch (*p) {
261 case 'P': /* pool name */
262 strlcpy(q, data->pool_name, freespace);
263 q += strlen(q);
264 break;
265 case 'I': /* IP address */
266 if (param && param_len > 0) {
267 if (param_len > freespace) {
268 strlcpy(q, param, freespace);
269 q += strlen(q);
270 }
271 else {
272 memcpy(q, param, param_len);
273 q += param_len;
274 }
275 }
276 break;
277 case 'J': /* lease duration */
278 sprintf(tmp, "%d", data->lease_duration);
279 strlcpy(q, tmp, freespace);
280 q += strlen(q);
281 break;
282
283 default:
284 *q++ = '%';
285 *q++ = *p;
286 break;
287 }
288 }
289 }
290 *q = '\0';
291
292 #if 0
293 DEBUG2("sqlippool_expand: \"%s\"", out);
294 #endif
295
296 return strlen(out);
297 }
298
299 /** Perform a single sqlippool query
300 *
301 * Mostly wrapper around sql_query which does some special sqlippool sequence substitutions and expands
302 * the format string.
303 *
304 * @param fmt sql query to expand.
305 * @param handle sql connection handle.
306 * @param data Instance of rlm_sqlippool.
307 * @param request Current request.
308 * @param param ip address string.
309 * @param param_len ip address string len.
310 * @return 0 on success or < 0 on error.
311 */
sqlippool_command(char const * fmt,rlm_sql_handle_t ** handle,rlm_sqlippool_t * data,REQUEST * request,char * param,int param_len)312 static int sqlippool_command(char const *fmt, rlm_sql_handle_t **handle,
313 rlm_sqlippool_t *data, REQUEST *request,
314 char *param, int param_len)
315 {
316 char query[MAX_QUERY_LEN];
317 char *expanded = NULL;
318
319 int ret;
320 int affected;
321
322 /*
323 * If we don't have a command, do nothing.
324 */
325 if (!fmt || !*fmt) return 0;
326
327 /*
328 * No handle? That's an error.
329 */
330 if (!handle || !*handle) return -1;
331
332 /*
333 * @todo this needs to die (should just be done in xlat expansion)
334 */
335 sqlippool_expand(query, sizeof(query), fmt, data, param, param_len);
336
337 if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, *handle) < 0) return -1;
338
339 ret = data->sql_inst->sql_query(data->sql_inst, request, handle, expanded);
340 if (ret < 0){
341 talloc_free(expanded);
342 return -1;
343 }
344 talloc_free(expanded);
345
346 /*
347 * No handle, we can't continue.
348 */
349 if (!*handle) return -1;
350
351 affected = (data->sql_inst->module->sql_affected_rows)(*handle, data->sql_inst->config);
352
353 if (*handle) (data->sql_inst->module->sql_finish_query)(*handle, data->sql_inst->config);
354
355 return affected;
356 }
357
358 /*
359 * Don't repeat yourself
360 */
361 #undef DO
362 #define DO(_x) if (sqlippool_command(inst->_x, handle, inst, request, NULL, 0) < 0) return RLM_MODULE_FAIL
363 #define DO_AFFECTED(_x, _affected) _affected = sqlippool_command(inst->_x, handle, inst, request, NULL, 0); if (_affected < 0) return RLM_MODULE_FAIL
364 #define DO_PART(_x) if (sqlippool_command(inst->_x, &handle, inst, request, NULL, 0) < 0) goto error
365
366 /*
367 * Query the database expecting a single result row
368 */
sqlippool_query1(char * out,int outlen,char const * fmt,rlm_sql_handle_t ** handle,rlm_sqlippool_t * data,REQUEST * request,char * param,int param_len)369 static int CC_HINT(nonnull (1, 3, 4, 5)) sqlippool_query1(char *out, int outlen, char const *fmt,
370 rlm_sql_handle_t **handle, rlm_sqlippool_t *data,
371 REQUEST *request, char *param, int param_len)
372 {
373 char query[MAX_QUERY_LEN];
374 char *expanded = NULL;
375
376 int rlen, retval;
377
378 /*
379 * @todo this needs to die (should just be done in xlat expansion)
380 */
381 sqlippool_expand(query, sizeof(query), fmt, data, param, param_len);
382
383 *out = '\0';
384
385 /*
386 * Do an xlat on the provided string
387 *
388 * Note that on an escaping error the handle is still valid!
389 */
390 if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, *handle) < 0) {
391 return 0;
392 }
393
394 retval = data->sql_inst->sql_select_query(data->sql_inst, request, handle, expanded);
395 talloc_free(expanded);
396
397 if ((retval != 0) || !*handle) {
398 REDEBUG("database query error on '%s'", query);
399 return 0;
400 }
401
402 if (data->sql_inst->sql_fetch_row(data->sql_inst, request, handle) < 0) {
403 REDEBUG("Failed fetching query result");
404 goto finish;
405 }
406
407 if (!(*handle)->row) {
408 REDEBUG("SQL query did not return any results");
409 goto finish;
410 }
411
412 if (!(*handle)->row[0]) {
413 REDEBUG("The first column of the result was NULL");
414 goto finish;
415 }
416
417 rlen = strlen((*handle)->row[0]);
418 if (rlen >= outlen) {
419 RDEBUG("insufficient string space");
420 goto finish;
421 }
422
423 strcpy(out, (*handle)->row[0]);
424 retval = rlen;
425 finish:
426 (data->sql_inst->module->sql_finish_select_query)(*handle, data->sql_inst->config);
427
428 return retval;
429 }
430
431 /*
432 * Do any per-module initialization that is separate to each
433 * configured instance of the module. e.g. set up connections
434 * to external databases, read configuration files, set up
435 * dictionary entries, etc.
436 *
437 * If configuration information is given in the config section
438 * that must be referenced in later calls, store a handle to it
439 * in *instance otherwise put a null pointer there.
440 */
mod_instantiate(CONF_SECTION * conf,void * instance)441 static int mod_instantiate(CONF_SECTION *conf, void *instance)
442 {
443 module_instance_t *sql_inst;
444 rlm_sqlippool_t *inst = instance;
445
446 sql_inst = module_instantiate(cf_section_find("modules"),
447 inst->sql_instance_name);
448 if (!sql_inst) {
449 cf_log_err_cs(conf, "failed to find sql instance named %s",
450 inst->sql_instance_name);
451 return -1;
452 }
453
454 if (inst->pool_name) {
455 DICT_ATTR const *da;
456
457 da = dict_attrbyname(inst->pool_name);
458 if (!da) {
459 cf_log_err_cs(conf, "Unknown attribute 'pool_name = %s'", inst->pool_name);
460 return -1;
461 }
462
463 if (da->type != PW_TYPE_STRING) {
464 cf_log_err_cs(conf, "Cannot use non-string attributes for 'pool_name = %s'", inst->pool_name);
465 return -1;
466 }
467
468 inst->pool_attribute = da;
469 }
470
471 if (inst->attribute_name) {
472 DICT_ATTR const *da;
473
474 da = dict_attrbyname(inst->attribute_name);
475 if (!da) {
476 fail:
477 cf_log_err_cs(conf, "Unknown attribute 'attribute_name = %s'", inst->attribute_name);
478 return -1;
479 }
480
481 switch (da->type) {
482 default:
483 cf_log_err_cs(conf, "Cannot use non-IP attributes for 'attribute_name = %s'", inst->attribute_name);
484 return -1;
485
486 case PW_TYPE_IPV4_ADDR:
487 case PW_TYPE_IPV6_ADDR:
488 case PW_TYPE_IPV4_PREFIX:
489 case PW_TYPE_IPV6_PREFIX:
490 break;
491
492 }
493
494 inst->framed_ip_address = da;
495 } else {
496 if (!inst->ipv6) {
497 inst->attribute_name = "Framed-IP-Address";
498 inst->framed_ip_address = dict_attrbyvalue(PW_FRAMED_IP_ADDRESS, 0);
499 } else {
500 inst->attribute_name = "Framed-IPv6-Prefix";
501 inst->framed_ip_address = dict_attrbyvalue(PW_FRAMED_IPV6_PREFIX, 0);
502 }
503
504 if (!inst->framed_ip_address) goto fail;
505 }
506
507 if (inst->req_attribute_name) {
508 DICT_ATTR const *da;
509
510 da = dict_attrbyname(inst->req_attribute_name);
511 if (!da) {
512 cf_log_err_cs(conf, "Unknown attribute 'req_attribute_name = %s'", inst->req_attribute_name);
513 return -1;
514 }
515
516 switch (da->type) {
517 default:
518 cf_log_err_cs(conf, "Cannot use non-IP attributes for 'req_attribute_name = %s'", inst->req_attribute_name);
519 return -1;
520
521 case PW_TYPE_IPV4_ADDR:
522 case PW_TYPE_IPV6_ADDR:
523 case PW_TYPE_IPV4_PREFIX:
524 case PW_TYPE_IPV6_PREFIX:
525 break;
526
527 }
528
529 inst->req_framed_ip_address = da;
530 }
531
532 if (strcmp(sql_inst->entry->name, "rlm_sql") != 0) {
533 cf_log_err_cs(conf, "Module \"%s\""
534 " is not an instance of the rlm_sql module",
535 inst->sql_instance_name);
536 return -1;
537 }
538
539 if (inst->allocate_clear) {
540 FR_INTEGER_BOUND_CHECK("allocate_clear_timeout", inst->allocate_clear_timeout, >, 1);
541 FR_INTEGER_BOUND_CHECK("allocate_clear_timeout", inst->allocate_clear_timeout, <=, 2*86400);
542 }
543
544 inst->sql_inst = (rlm_sql_t *) sql_inst->insthandle;
545 return 0;
546 }
547
548
549 /*
550 * If we have something to log, then we log it.
551 * Otherwise we return the retcode as soon as possible
552 */
do_logging(REQUEST * request,char const * str,int rcode)553 static int do_logging(REQUEST *request, char const *str, int rcode)
554 {
555 char *expanded = NULL;
556
557 if (!str || !*str) return rcode;
558
559 if (radius_axlat(&expanded, request, str, NULL, NULL) < 0) {
560 return rcode;
561 }
562
563 pair_make_config("Module-Success-Message", expanded, T_OP_SET);
564
565 talloc_free(expanded);
566
567 return rcode;
568 }
569
570
571 /*
572 * Allocate an IP number from the pool.
573 */
CC_HINT(nonnull)574 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
575 {
576 rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance;
577 char allocation[MAX_STRING_LEN];
578 int allocation_len;
579 VALUE_PAIR *vp;
580 rlm_sql_handle_t *handle;
581 time_t now;
582 uint32_t diff_time;
583
584 /*
585 * If there is already an attribute in the reply do nothing
586 */
587 if (!inst->allow_duplicates && (fr_pair_find_by_num(request->reply->vps, inst->framed_ip_address->attr, inst->framed_ip_address->vendor, TAG_ANY) != NULL)) {
588 RDEBUG("%s already exists", inst->attribute_name);
589
590 return do_logging(request, inst->log_exists, RLM_MODULE_NOOP);
591 }
592
593 if (fr_pair_find_by_num(request->config, inst->pool_attribute->attr, inst->pool_attribute->vendor, TAG_ANY) == NULL) {
594 RDEBUG("No %s defined", inst->pool_name);
595
596 return do_logging(request, inst->log_nopool, RLM_MODULE_NOOP);
597 }
598
599 handle = fr_connection_get(inst->sql_inst->pool);
600 if (!handle) {
601 REDEBUG("Failed reserving SQL connection");
602 return RLM_MODULE_FAIL;
603 }
604
605 if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) {
606 return RLM_MODULE_FAIL;
607 }
608
609 /*
610 * Limit the number of clears we do. There are minor
611 * race conditions for the check, but so what. The
612 * actual work is protected by a transaction. The idea
613 * here is that if we're allocating 100 IPs a second,
614 * we're only do 1 CLEAR per allocate_clear_timeout.
615 *
616 * This will avoid having several queries to deadlock and blocking all
617 * the sqlippool module.
618 */
619 now = time(NULL);
620 diff_time = difftime(now, inst->last_clear);
621 if (inst->allocate_clear && *inst->allocate_clear && (diff_time >= inst->allocate_clear_timeout)) {
622 inst->last_clear = now;
623
624 DO_PART(allocate_begin);
625 DO_PART(allocate_clear);
626 DO_PART(allocate_commit);
627 }
628
629 DO_PART(allocate_begin);
630
631 /*
632 * If we have a query to find an existing IP run that first
633 */
634 if (inst->allocate_existing && *inst->allocate_existing) {
635 allocation_len = sqlippool_query1(allocation, sizeof(allocation),
636 inst->allocate_existing, &handle,
637 inst, request, (char *) NULL, 0);
638 if (!handle) return RLM_MODULE_FAIL;
639 } else {
640 allocation_len = 0;
641 }
642
643 /*
644 * If we have a requested IP address and a query to find whether
645 * it is available then run that next
646 */
647 if (allocation_len == 0 && inst->allocate_requested && *inst->allocate_requested &&
648 fr_pair_find_by_num(request->packet->vps,
649 inst->req_framed_ip_address->attr,
650 inst->req_framed_ip_address->vendor,
651 TAG_ANY) != NULL) {
652 allocation_len = sqlippool_query1(allocation, sizeof(allocation),
653 inst->allocate_requested, &handle,
654 inst, request, (char *) NULL, 0);
655 if (!handle) return RLM_MODULE_FAIL;
656 }
657
658 /*
659 * If no IP found, look for a free one
660 */
661 if (allocation_len == 0) {
662 allocation_len = sqlippool_query1(allocation, sizeof(allocation),
663 inst->allocate_find, &handle,
664 inst, request, (char *) NULL, 0);
665 if (!handle) return RLM_MODULE_FAIL;
666 }
667
668 /*
669 * Nothing found...
670 */
671 if (allocation_len == 0) {
672 DO_PART(allocate_commit);
673
674 /*
675 *Should we perform pool-check ?
676 */
677 if (inst->pool_check && *inst->pool_check) {
678
679 /*
680 *Ok, so the allocate-find query found nothing ...
681 *Let's check if the pool exists at all
682 */
683 allocation_len = sqlippool_query1(allocation, sizeof(allocation),
684 inst->pool_check, &handle, inst, request,
685 (char *) NULL, 0);
686 if (!handle) return RLM_MODULE_FAIL;
687
688 fr_connection_release(inst->sql_inst->pool, handle);
689
690 if (allocation_len) {
691
692 /*
693 * Pool exists after all... So,
694 * the failure to allocate the IP
695 * address was most likely due to
696 * the depletion of the pool. In
697 * that case, we should return
698 * NOTFOUND
699 */
700 RDEBUG("pool appears to be full");
701 return do_logging(request, inst->log_failed, RLM_MODULE_NOTFOUND);
702
703 }
704
705 /*
706 * Pool doesn't exist in the table. It
707 * may be handled by some other instance of
708 * sqlippool, so we should just ignore this
709 * allocation failure and return NOOP
710 */
711 RDEBUG("IP address could not be allocated as no pool exists with that name");
712 return RLM_MODULE_NOOP;
713
714 }
715
716 fr_connection_release(inst->sql_inst->pool, handle);
717
718 RDEBUG("IP address could not be allocated");
719 return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
720 }
721
722 /*
723 * See if we can create the VP from the returned data. If not,
724 * error out. If so, add it to the list.
725 */
726 vp = fr_pair_afrom_num(request->reply, inst->framed_ip_address->attr, inst->framed_ip_address->vendor);
727 if (fr_pair_value_from_str(vp, allocation, allocation_len) < 0) {
728 DO_PART(allocate_commit);
729
730 RDEBUG("Invalid IP number [%s] returned from instbase query.", allocation);
731 fr_connection_release(inst->sql_inst->pool, handle);
732 return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
733 }
734
735 RDEBUG("Allocated IP %s", allocation);
736 fr_pair_add(&request->reply->vps, vp);
737
738 /*
739 * UPDATE
740 */
741 if (sqlippool_command(inst->allocate_update, &handle, inst, request,
742 allocation, allocation_len) < 0) {
743 error:
744 if (handle) fr_connection_release(inst->sql_inst->pool, handle);
745 return RLM_MODULE_FAIL;
746 }
747
748 DO_PART(allocate_commit);
749
750 if (handle) fr_connection_release(inst->sql_inst->pool, handle);
751
752 return do_logging(request, inst->log_success, RLM_MODULE_OK);
753 }
754
mod_accounting_start(rlm_sql_handle_t ** handle,rlm_sqlippool_t * inst,REQUEST * request)755 static int mod_accounting_start(rlm_sql_handle_t **handle,
756 rlm_sqlippool_t *inst, REQUEST *request)
757 {
758 DO(start_begin);
759 DO(start_update);
760 DO(start_commit);
761
762 return RLM_MODULE_OK;
763 }
764
mod_accounting_alive(rlm_sql_handle_t ** handle,rlm_sqlippool_t * inst,REQUEST * request)765 static int mod_accounting_alive(rlm_sql_handle_t **handle,
766 rlm_sqlippool_t *inst, REQUEST *request)
767 {
768 int affected;
769
770 DO(alive_begin);
771 DO_AFFECTED(alive_update, affected);
772 DO(alive_commit);
773
774 return (affected == 0 ? RLM_MODULE_NOTFOUND : RLM_MODULE_OK);
775 }
776
mod_accounting_stop(rlm_sql_handle_t ** handle,rlm_sqlippool_t * inst,REQUEST * request)777 static int mod_accounting_stop(rlm_sql_handle_t **handle,
778 rlm_sqlippool_t *inst, REQUEST *request)
779 {
780 DO(stop_begin);
781 DO(stop_clear);
782 DO(stop_commit);
783
784 return do_logging(request, inst->log_clear, RLM_MODULE_OK);
785 }
786
mod_accounting_on(rlm_sql_handle_t ** handle,rlm_sqlippool_t * inst,REQUEST * request)787 static int mod_accounting_on(rlm_sql_handle_t **handle,
788 rlm_sqlippool_t *inst, REQUEST *request)
789 {
790 DO(on_begin);
791 DO(on_clear);
792 DO(on_commit);
793
794 return RLM_MODULE_OK;
795 }
796
mod_accounting_off(rlm_sql_handle_t ** handle,rlm_sqlippool_t * inst,REQUEST * request)797 static int mod_accounting_off(rlm_sql_handle_t **handle,
798 rlm_sqlippool_t *inst, REQUEST *request)
799 {
800 DO(off_begin);
801 DO(off_clear);
802 DO(off_commit);
803
804 return RLM_MODULE_OK;
805 }
806
807 /*
808 * Check for an Accounting-Stop
809 * If we find one and we have allocated an IP to this nas/port
810 * combination, then deallocate it.
811 */
CC_HINT(nonnull)812 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
813 {
814 int rcode = RLM_MODULE_NOOP;
815 VALUE_PAIR *vp;
816
817 int acct_status_type;
818
819 rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance;
820 rlm_sql_handle_t *handle;
821
822 vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY);
823 if (!vp) {
824 RDEBUG("Could not find account status type in packet");
825 return RLM_MODULE_NOOP;
826 }
827 acct_status_type = vp->vp_integer;
828
829 switch (acct_status_type) {
830 case PW_STATUS_START:
831 case PW_STATUS_ALIVE:
832 case PW_STATUS_STOP:
833 case PW_STATUS_ACCOUNTING_ON:
834 case PW_STATUS_ACCOUNTING_OFF:
835 break; /* continue through to the next section */
836
837 default:
838 /* We don't care about any other accounting packet */
839 return RLM_MODULE_NOOP;
840 }
841
842 handle = fr_connection_get(inst->sql_inst->pool);
843 if (!handle) {
844 RDEBUG("Failed reserving SQL connection");
845 return RLM_MODULE_FAIL;
846 }
847
848 if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) return RLM_MODULE_FAIL;
849
850 switch (acct_status_type) {
851 case PW_STATUS_START:
852 rcode = mod_accounting_start(&handle, inst, request);
853 break;
854
855 case PW_STATUS_ALIVE:
856 rcode = mod_accounting_alive(&handle, inst, request);
857 break;
858
859 case PW_STATUS_STOP:
860 rcode = mod_accounting_stop(&handle, inst, request);
861 break;
862
863 case PW_STATUS_ACCOUNTING_ON:
864 rcode = mod_accounting_on(&handle, inst, request);
865 break;
866
867 case PW_STATUS_ACCOUNTING_OFF:
868 rcode = mod_accounting_off(&handle, inst, request);
869 break;
870 }
871
872 if (handle) fr_connection_release(inst->sql_inst->pool, handle);
873
874 return rcode;
875 }
876
877 /*
878 * The module name should be the only globally exported symbol.
879 * That is, everything else should be 'static'.
880 *
881 * If the module needs to temporarily modify it's instantiation
882 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
883 * The server will then take care of ensuring that the module
884 * is single-threaded.
885 */
886 extern module_t rlm_sqlippool;
887 module_t rlm_sqlippool = {
888 .magic = RLM_MODULE_INIT,
889 .name = "sqlippool",
890 .type = RLM_TYPE_THREAD_SAFE,
891 .inst_size = sizeof(rlm_sqlippool_t),
892 .config = module_config,
893 .instantiate = mod_instantiate,
894 .methods = {
895 [MOD_ACCOUNTING] = mod_accounting,
896 [MOD_POST_AUTH] = mod_post_auth
897 },
898 };
899