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