summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_sqlippool/rlm_sqlippool.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_sqlippool/rlm_sqlippool.c')
-rw-r--r--src/modules/rlm_sqlippool/rlm_sqlippool.c900
1 files changed, 900 insertions, 0 deletions
diff --git a/src/modules/rlm_sqlippool/rlm_sqlippool.c b/src/modules/rlm_sqlippool/rlm_sqlippool.c
new file mode 100644
index 0000000..22e9381
--- /dev/null
+++ b/src/modules/rlm_sqlippool/rlm_sqlippool.c
@@ -0,0 +1,900 @@
+/*
+ * This program is is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file rlm_sqlippool.c
+ * @brief Allocates an IP address / prefix from pools stored in SQL.
+ *
+ * @copyright 2002 Globe.Net Communications Limited
+ * @copyright 2006 The FreeRADIUS server project
+ * @copyright 2006 Suntel Communications
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <ctype.h>
+
+#include <rlm_sql.h>
+
+#define MAX_QUERY_LEN 4096
+
+/*
+ * Define a structure for our module configuration.
+ */
+typedef struct rlm_sqlippool_t {
+ char const *sql_instance_name;
+
+ uint32_t lease_duration;
+
+ rlm_sql_t *sql_inst;
+
+ char const *pool_name; //!< Name of the attribute in the check VPS for which the value will be used as key
+ bool ipv6; //!< Whether or not we do IPv6 pools.
+ bool allow_duplicates; //!< assign even if it already exists
+ char const *attribute_name; //!< name of the IP address attribute
+ char const *req_attribute_name; //!< name of the requested IP address attribute
+
+ DICT_ATTR const *framed_ip_address; //!< the attribute for IP address allocation
+ DICT_ATTR const *req_framed_ip_address; //!< the attribute for requested IP address
+ DICT_ATTR const *pool_attribute; //!< the attribute corresponding to the pool_name
+
+ time_t last_clear; //!< So we only do it once a second.
+ char const *allocate_begin; //!< SQL query to begin.
+ char const *allocate_clear; //!< SQL query to clear an IP.
+ uint32_t allocate_clear_timeout; //!< Number of second between two allocate_clear SQL query
+ char const *allocate_existing; //!< SQL query to find existing IP leased to the device.
+ char const *allocate_requested; //!< SQL query to find requested IP.
+ char const *allocate_find; //!< SQL query to find an unused IP.
+ char const *allocate_update; //!< SQL query to mark an IP as used.
+ char const *allocate_commit; //!< SQL query to commit.
+
+ char const *pool_check; //!< Query to check for the existence of the pool.
+
+ /* Start sequence */
+ char const *start_begin; //!< SQL query to begin.
+ char const *start_update; //!< SQL query to update an IP entry.
+ char const *start_commit; //!< SQL query to commit.
+
+ /* Alive sequence */
+ char const *alive_begin; //!< SQL query to begin.
+ char const *alive_update; //!< SQL query to update an IP entry.
+ char const *alive_commit; //!< SQL query to commit.
+
+ /* Stop sequence */
+ char const *stop_begin; //!< SQL query to begin.
+ char const *stop_clear; //!< SQL query to clear an IP.
+ char const *stop_commit; //!< SQL query to commit.
+
+ /* On sequence */
+ char const *on_begin; //!< SQL query to begin.
+ char const *on_clear; //!< SQL query to clear an entire NAS.
+ char const *on_commit; //!< SQL query to commit.
+
+ /* Off sequence */
+ char const *off_begin; //!< SQL query to begin.
+ char const *off_clear; //!< SQL query to clear an entire NAS.
+ char const *off_commit; //!< SQL query to commit.
+
+ /* Logging Section */
+ char const *log_exists; //!< There was an ip address already assigned.
+ char const *log_success; //!< We successfully allocated ip address from pool.
+ char const *log_clear; //!< We successfully deallocated ip address from pool.
+ char const *log_failed; //!< Failed to allocate ip from the pool.
+ char const *log_nopool; //!< There was no Framed-IP-Address but also no Pool-Name.
+
+ /* Reserved to handle 255.255.255.254 Requests */
+ char const *defaultpool; //!< Default Pool-Name if there is none in the check items.
+
+} rlm_sqlippool_t;
+
+static CONF_PARSER message_config[] = {
+ { "exists", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_exists), NULL },
+ { "success", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_success), NULL },
+ { "clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_clear), NULL },
+ { "failed", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_failed), NULL },
+ { "nopool", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, log_nopool), NULL },
+ CONF_PARSER_TERMINATOR
+};
+
+/*
+ * A mapping of configuration file names to internal variables.
+ *
+ * Note that the string is dynamically allocated, so it MUST
+ * be freed. When the configuration file parse re-reads the string,
+ * it free's the old one, and strdup's the new one, placing the pointer
+ * to the strdup'd string into 'config.string'. This gets around
+ * buffer over-flows.
+ */
+static CONF_PARSER module_config[] = {
+ { "sql-instance-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, sql_instance_name), NULL },
+ { "sql_module_instance", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_sqlippool_t, sql_instance_name), "sql" },
+
+ { "lease-duration", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, rlm_sqlippool_t, lease_duration), NULL },
+ { "lease_duration", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sqlippool_t, lease_duration), "86400" },
+
+ { "pool-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, pool_name), NULL },
+ { "pool_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, pool_name), "Pool-Name" },
+
+ { "default-pool", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, defaultpool), NULL },
+ { "default_pool", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, defaultpool), "main_pool" },
+
+
+ { "ipv6", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sqlippool_t, ipv6), NULL},
+ { "allow_duplicates", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sqlippool_t, allow_duplicates), NULL},
+ { "attribute_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, attribute_name), NULL},
+ { "req_attribute_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, req_attribute_name), NULL},
+
+ { "allocate-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_begin), NULL },
+ { "allocate_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_begin), "START TRANSACTION" },
+
+ { "allocate-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_clear), NULL },
+ { "allocate_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, allocate_clear), "" },
+
+ { "allocate_clear_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sqlippool_t, allocate_clear_timeout), "1" },
+
+ { "allocate-existing", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_existing), NULL },
+ { "allocate_existing", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_existing), "" },
+
+ { "allocate-requested", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_requested), NULL },
+ { "allocate_requested", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_requested), "" },
+
+ { "allocate-find", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_find), NULL },
+ { "allocate_find", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_REQUIRED, rlm_sqlippool_t, allocate_find), "" },
+
+ { "allocate-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_update), NULL },
+ { "allocate_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, allocate_update), "" },
+
+ { "allocate-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, allocate_commit), NULL },
+ { "allocate_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, allocate_commit), "COMMIT" },
+
+
+ { "pool-check", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, pool_check), NULL },
+ { "pool_check", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, pool_check), "" },
+
+
+ { "start-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_begin), NULL },
+ { "start_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, start_begin), "" },
+
+ { "start-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_update), NULL },
+ { "start_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, start_update), "" },
+
+ { "start-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_commit), NULL },
+ { "start_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, start_commit), "" },
+
+
+ { "alive-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_begin), NULL },
+ { "alive_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, alive_begin), "" },
+
+ { "alive-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_update), NULL },
+ { "alive_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, alive_update), "" },
+
+ { "alive-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_commit), NULL },
+ { "alive_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, alive_commit), "" },
+
+
+ { "stop-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_begin), NULL },
+ { "stop_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, stop_begin), "" },
+
+ { "stop-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_clear), NULL },
+ { "stop_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, stop_clear), "" },
+
+ { "stop-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_commit), NULL },
+ { "stop_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, stop_commit), "" },
+
+
+ { "on-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_begin), NULL },
+ { "on_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, on_begin), "" },
+
+ { "on-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_clear), NULL },
+ { "on_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, on_clear), "" },
+
+ { "on-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_commit), NULL },
+ { "on_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, on_commit), "" },
+
+
+ { "off-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_begin), NULL },
+ { "off_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, off_begin), "" },
+
+ { "off-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_clear), NULL },
+ { "off_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, off_clear), "" },
+
+ { "off-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_commit), NULL },
+ { "off_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, off_commit), "" },
+
+ { "messages", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) message_config },
+ CONF_PARSER_TERMINATOR
+};
+
+/*
+ * Replace %<whatever> in a string.
+ *
+ * %P pool_name
+ * %I param
+ * %J lease_duration
+ *
+ */
+static int sqlippool_expand(char * out, int outlen, char const * fmt,
+ rlm_sqlippool_t *data, char * param, int param_len)
+{
+ char *q;
+ char const *p;
+ char tmp[40]; /* For temporary storing of integers */
+
+ q = out;
+ for (p = fmt; *p ; p++) {
+ int freespace;
+ int c;
+
+ /* Calculate freespace in output */
+ freespace = outlen - (q - out);
+ if (freespace <= 1)
+ break;
+
+ c = *p;
+ if (c != '%') {
+ *q++ = *p;
+ continue;
+ }
+
+ if (*++p == '\0') {
+ break;
+ }
+
+ if (c == '%') {
+ switch (*p) {
+ case 'P': /* pool name */
+ strlcpy(q, data->pool_name, freespace);
+ q += strlen(q);
+ break;
+ case 'I': /* IP address */
+ if (param && param_len > 0) {
+ if (param_len > freespace) {
+ strlcpy(q, param, freespace);
+ q += strlen(q);
+ }
+ else {
+ memcpy(q, param, param_len);
+ q += param_len;
+ }
+ }
+ break;
+ case 'J': /* lease duration */
+ sprintf(tmp, "%d", data->lease_duration);
+ strlcpy(q, tmp, freespace);
+ q += strlen(q);
+ break;
+
+ default:
+ *q++ = '%';
+ *q++ = *p;
+ break;
+ }
+ }
+ }
+ *q = '\0';
+
+#if 0
+ DEBUG2("sqlippool_expand: \"%s\"", out);
+#endif
+
+ return strlen(out);
+}
+
+/** Perform a single sqlippool query
+ *
+ * Mostly wrapper around sql_query which does some special sqlippool sequence substitutions and expands
+ * the format string.
+ *
+ * @param fmt sql query to expand.
+ * @param handle sql connection handle.
+ * @param data Instance of rlm_sqlippool.
+ * @param request Current request.
+ * @param param ip address string.
+ * @param param_len ip address string len.
+ * @return 0 on success or < 0 on error.
+ */
+static int sqlippool_command(char const *fmt, rlm_sql_handle_t **handle,
+ rlm_sqlippool_t *data, REQUEST *request,
+ char *param, int param_len)
+{
+ char query[MAX_QUERY_LEN];
+ char *expanded = NULL;
+
+ int ret;
+ int affected;
+
+ /*
+ * If we don't have a command, do nothing.
+ */
+ if (!fmt || !*fmt) return 0;
+
+ /*
+ * No handle? That's an error.
+ */
+ if (!handle || !*handle) return -1;
+
+ /*
+ * @todo this needs to die (should just be done in xlat expansion)
+ */
+ sqlippool_expand(query, sizeof(query), fmt, data, param, param_len);
+
+ if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, *handle) < 0) return -1;
+
+ ret = data->sql_inst->sql_query(data->sql_inst, request, handle, expanded);
+ if (ret < 0){
+ talloc_free(expanded);
+ return -1;
+ }
+ talloc_free(expanded);
+
+ /*
+ * No handle, we can't continue.
+ */
+ if (!*handle) return -1;
+
+ affected = (data->sql_inst->module->sql_affected_rows)(*handle, data->sql_inst->config);
+
+ if (*handle) (data->sql_inst->module->sql_finish_query)(*handle, data->sql_inst->config);
+
+ return affected;
+}
+
+/*
+ * Don't repeat yourself
+ */
+#undef DO
+#define DO(_x) if (sqlippool_command(inst->_x, handle, inst, request, NULL, 0) < 0) return RLM_MODULE_FAIL
+#define DO_AFFECTED(_x, _affected) _affected = sqlippool_command(inst->_x, handle, inst, request, NULL, 0); if (_affected < 0) return RLM_MODULE_FAIL
+#define DO_PART(_x) if (sqlippool_command(inst->_x, &handle, inst, request, NULL, 0) < 0) goto error
+
+/*
+ * Query the database expecting a single result row
+ */
+static int CC_HINT(nonnull (1, 3, 4, 5)) 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)
+{
+ char query[MAX_QUERY_LEN];
+ char *expanded = NULL;
+
+ int rlen, retval;
+
+ /*
+ * @todo this needs to die (should just be done in xlat expansion)
+ */
+ sqlippool_expand(query, sizeof(query), fmt, data, param, param_len);
+
+ *out = '\0';
+
+ /*
+ * Do an xlat on the provided string
+ *
+ * Note that on an escaping error the handle is still valid!
+ */
+ if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, *handle) < 0) {
+ return 0;
+ }
+
+ retval = data->sql_inst->sql_select_query(data->sql_inst, request, handle, expanded);
+ talloc_free(expanded);
+
+ if ((retval != 0) || !*handle) {
+ REDEBUG("database query error on '%s'", query);
+ return 0;
+ }
+
+ if (data->sql_inst->sql_fetch_row(data->sql_inst, request, handle) < 0) {
+ REDEBUG("Failed fetching query result");
+ goto finish;
+ }
+
+ if (!(*handle)->row) {
+ REDEBUG("SQL query did not return any results");
+ goto finish;
+ }
+
+ if (!(*handle)->row[0]) {
+ REDEBUG("The first column of the result was NULL");
+ goto finish;
+ }
+
+ rlen = strlen((*handle)->row[0]);
+ if (rlen >= outlen) {
+ RDEBUG("insufficient string space");
+ goto finish;
+ }
+
+ strcpy(out, (*handle)->row[0]);
+ retval = rlen;
+finish:
+ (data->sql_inst->module->sql_finish_select_query)(*handle, data->sql_inst->config);
+
+ return retval;
+}
+
+/*
+ * Do any per-module initialization that is separate to each
+ * configured instance of the module. e.g. set up connections
+ * to external databases, read configuration files, set up
+ * dictionary entries, etc.
+ *
+ * If configuration information is given in the config section
+ * that must be referenced in later calls, store a handle to it
+ * in *instance otherwise put a null pointer there.
+ */
+static int mod_instantiate(CONF_SECTION *conf, void *instance)
+{
+ module_instance_t *sql_inst;
+ rlm_sqlippool_t *inst = instance;
+
+ sql_inst = module_instantiate(cf_section_find("modules"),
+ inst->sql_instance_name);
+ if (!sql_inst) {
+ cf_log_err_cs(conf, "failed to find sql instance named %s",
+ inst->sql_instance_name);
+ return -1;
+ }
+
+ if (inst->pool_name) {
+ DICT_ATTR const *da;
+
+ da = dict_attrbyname(inst->pool_name);
+ if (!da) {
+ cf_log_err_cs(conf, "Unknown attribute 'pool_name = %s'", inst->pool_name);
+ return -1;
+ }
+
+ if (da->type != PW_TYPE_STRING) {
+ cf_log_err_cs(conf, "Cannot use non-string attributes for 'pool_name = %s'", inst->pool_name);
+ return -1;
+ }
+
+ inst->pool_attribute = da;
+ }
+
+ if (inst->attribute_name) {
+ DICT_ATTR const *da;
+
+ da = dict_attrbyname(inst->attribute_name);
+ if (!da) {
+ fail:
+ cf_log_err_cs(conf, "Unknown attribute 'attribute_name = %s'", inst->attribute_name);
+ return -1;
+ }
+
+ switch (da->type) {
+ default:
+ cf_log_err_cs(conf, "Cannot use non-IP attributes for 'attribute_name = %s'", inst->attribute_name);
+ return -1;
+
+ case PW_TYPE_IPV4_ADDR:
+ case PW_TYPE_IPV6_ADDR:
+ case PW_TYPE_IPV4_PREFIX:
+ case PW_TYPE_IPV6_PREFIX:
+ break;
+
+ }
+
+ inst->framed_ip_address = da;
+ } else {
+ if (!inst->ipv6) {
+ inst->attribute_name = "Framed-IP-Address";
+ inst->framed_ip_address = dict_attrbyvalue(PW_FRAMED_IP_ADDRESS, 0);
+ } else {
+ inst->attribute_name = "Framed-IPv6-Prefix";
+ inst->framed_ip_address = dict_attrbyvalue(PW_FRAMED_IPV6_PREFIX, 0);
+ }
+
+ if (!inst->framed_ip_address) goto fail;
+ }
+
+ if (inst->req_attribute_name) {
+ DICT_ATTR const *da;
+
+ da = dict_attrbyname(inst->req_attribute_name);
+ if (!da) {
+ cf_log_err_cs(conf, "Unknown attribute 'req_attribute_name = %s'", inst->req_attribute_name);
+ return -1;
+ }
+
+ switch (da->type) {
+ default:
+ cf_log_err_cs(conf, "Cannot use non-IP attributes for 'req_attribute_name = %s'", inst->req_attribute_name);
+ return -1;
+
+ case PW_TYPE_IPV4_ADDR:
+ case PW_TYPE_IPV6_ADDR:
+ case PW_TYPE_IPV4_PREFIX:
+ case PW_TYPE_IPV6_PREFIX:
+ break;
+
+ }
+
+ inst->req_framed_ip_address = da;
+ }
+
+ if (strcmp(sql_inst->entry->name, "rlm_sql") != 0) {
+ cf_log_err_cs(conf, "Module \"%s\""
+ " is not an instance of the rlm_sql module",
+ inst->sql_instance_name);
+ return -1;
+ }
+
+ if (inst->allocate_clear) {
+ FR_INTEGER_BOUND_CHECK("allocate_clear_timeout", inst->allocate_clear_timeout, >=, 1);
+ FR_INTEGER_BOUND_CHECK("allocate_clear_timeout", inst->allocate_clear_timeout, <=, 2*86400);
+ }
+
+ inst->sql_inst = (rlm_sql_t *) sql_inst->insthandle;
+ return 0;
+}
+
+
+/*
+ * If we have something to log, then we log it.
+ * Otherwise we return the retcode as soon as possible
+ */
+static int do_logging(REQUEST *request, char const *str, int rcode)
+{
+ char *expanded = NULL;
+
+ if (!str || !*str) return rcode;
+
+ if (radius_axlat(&expanded, request, str, NULL, NULL) < 0) {
+ return rcode;
+ }
+
+ pair_make_config("Module-Success-Message", expanded, T_OP_SET);
+
+ talloc_free(expanded);
+
+ return rcode;
+}
+
+
+/*
+ * Allocate an IP number from the pool.
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
+{
+ rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance;
+ char allocation[MAX_STRING_LEN];
+ int allocation_len;
+ VALUE_PAIR *vp = NULL;
+ rlm_sql_handle_t *handle;
+ time_t now;
+ uint32_t diff_time;
+
+ /*
+ * If there is already an attribute in the reply do nothing
+ */
+ 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)) {
+ RDEBUG("%s already exists", inst->attribute_name);
+
+ return do_logging(request, inst->log_exists, RLM_MODULE_NOOP);
+ }
+
+ if (fr_pair_find_by_num(request->config, inst->pool_attribute->attr, inst->pool_attribute->vendor, TAG_ANY) == NULL) {
+ RDEBUG("No %s defined", inst->pool_name);
+
+ return do_logging(request, inst->log_nopool, RLM_MODULE_NOOP);
+ }
+
+ handle = fr_connection_get(inst->sql_inst->pool);
+ if (!handle) {
+ REDEBUG("Failed reserving SQL connection");
+ return RLM_MODULE_FAIL;
+ }
+
+ if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) {
+ return RLM_MODULE_FAIL;
+ }
+
+ /*
+ * Limit the number of clears we do. There are minor
+ * race conditions for the check, but so what. The
+ * actual work is protected by a transaction. The idea
+ * here is that if we're allocating 100 IPs a second,
+ * we're only do 1 CLEAR per allocate_clear_timeout.
+ *
+ * This will avoid having several queries to deadlock and blocking all
+ * the sqlippool module.
+ */
+ now = time(NULL);
+ diff_time = difftime(now, inst->last_clear);
+ if (inst->allocate_clear && *inst->allocate_clear && (diff_time >= inst->allocate_clear_timeout)) {
+ inst->last_clear = now;
+
+ DO_PART(allocate_begin);
+ DO_PART(allocate_clear);
+ DO_PART(allocate_commit);
+ }
+
+ DO_PART(allocate_begin);
+
+ /*
+ * If we have a query to find an existing IP run that first
+ */
+ if (inst->allocate_existing && *inst->allocate_existing) {
+ allocation_len = sqlippool_query1(allocation, sizeof(allocation),
+ inst->allocate_existing, &handle,
+ inst, request, (char *) NULL, 0);
+ if (!handle) return RLM_MODULE_FAIL;
+ } else {
+ allocation_len = 0;
+ }
+
+ /*
+ * If we have a requested IP address and a query to find whether
+ * it is available then run that next
+ */
+ if (allocation_len == 0 && inst->allocate_requested && *inst->allocate_requested &&
+ fr_pair_find_by_num(request->packet->vps,
+ inst->req_framed_ip_address->attr,
+ inst->req_framed_ip_address->vendor,
+ TAG_ANY) != NULL) {
+ allocation_len = sqlippool_query1(allocation, sizeof(allocation),
+ inst->allocate_requested, &handle,
+ inst, request, (char *) NULL, 0);
+ if (!handle) return RLM_MODULE_FAIL;
+ }
+
+ /*
+ * If no IP found, look for a free one
+ */
+ if (allocation_len == 0) {
+ allocation_len = sqlippool_query1(allocation, sizeof(allocation),
+ inst->allocate_find, &handle,
+ inst, request, (char *) NULL, 0);
+ if (!handle) return RLM_MODULE_FAIL;
+ }
+
+ /*
+ * Nothing found...
+ */
+ if (allocation_len == 0) {
+ DO_PART(allocate_commit);
+
+ /*
+ *Should we perform pool-check ?
+ */
+ if (inst->pool_check && *inst->pool_check) {
+
+ /*
+ *Ok, so the allocate-find query found nothing ...
+ *Let's check if the pool exists at all
+ */
+ allocation_len = sqlippool_query1(allocation, sizeof(allocation),
+ inst->pool_check, &handle, inst, request,
+ (char *) NULL, 0);
+ if (!handle) return RLM_MODULE_FAIL;
+
+ fr_connection_release(inst->sql_inst->pool, handle);
+
+ if (allocation_len) {
+
+ /*
+ * Pool exists after all... So,
+ * the failure to allocate the IP
+ * address was most likely due to
+ * the depletion of the pool. In
+ * that case, we should return
+ * NOTFOUND
+ */
+ REDEBUG("pool appears to be full");
+ return do_logging(request, inst->log_failed, RLM_MODULE_NOTFOUND);
+
+ }
+
+ /*
+ * Pool doesn't exist in the table. It
+ * may be handled by some other instance of
+ * sqlippool, so we should just ignore this
+ * allocation failure and return NOOP
+ */
+ REDEBUG("IP address could not be allocated as no pool exists with that name");
+ return RLM_MODULE_NOOP;
+
+ }
+
+ fr_connection_release(inst->sql_inst->pool, handle);
+
+ REDEBUG("IP address could not be allocated");
+ return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
+ }
+
+ /*
+ * See if we can create the VP from the returned data. If not,
+ * error out. If so, add it to the list.
+ */
+ vp = fr_pair_afrom_num(request->reply, inst->framed_ip_address->attr, inst->framed_ip_address->vendor);
+ if (fr_pair_value_from_str(vp, allocation, allocation_len) < 0) {
+ DO_PART(allocate_commit);
+
+ talloc_free(vp);
+ REDEBUG("Invalid IP address [%s] returned from database query.", allocation);
+ fr_connection_release(inst->sql_inst->pool, handle);
+ return do_logging(request, inst->log_failed, RLM_MODULE_NOOP);
+ }
+
+ /*
+ * UPDATE
+ */
+ if (sqlippool_command(inst->allocate_update, &handle, inst, request,
+ allocation, allocation_len) < 0) {
+ error:
+ talloc_free(vp);
+ if (handle) fr_connection_release(inst->sql_inst->pool, handle);
+ return RLM_MODULE_FAIL;
+ }
+
+ DO_PART(allocate_commit);
+
+ RDEBUG("Allocated IP %s", allocation);
+ fr_pair_add(&request->reply->vps, vp);
+
+ if (handle) fr_connection_release(inst->sql_inst->pool, handle);
+
+ return do_logging(request, inst->log_success, RLM_MODULE_OK);
+}
+
+static int mod_accounting_start(rlm_sql_handle_t **handle,
+ rlm_sqlippool_t *inst, REQUEST *request)
+{
+ DO(start_begin);
+ DO(start_update);
+ DO(start_commit);
+
+ return RLM_MODULE_OK;
+}
+
+static int mod_accounting_alive(rlm_sql_handle_t **handle,
+ rlm_sqlippool_t *inst, REQUEST *request)
+{
+ int affected;
+
+ DO(alive_begin);
+ DO_AFFECTED(alive_update, affected);
+ DO(alive_commit);
+
+ return (affected == 0 ? RLM_MODULE_NOTFOUND : RLM_MODULE_OK);
+}
+
+static int mod_accounting_stop(rlm_sql_handle_t **handle,
+ rlm_sqlippool_t *inst, REQUEST *request)
+{
+ DO(stop_begin);
+ DO(stop_clear);
+ DO(stop_commit);
+
+ return do_logging(request, inst->log_clear, RLM_MODULE_OK);
+}
+
+static int mod_accounting_on(rlm_sql_handle_t **handle,
+ rlm_sqlippool_t *inst, REQUEST *request)
+{
+ DO(on_begin);
+ DO(on_clear);
+ DO(on_commit);
+
+ return RLM_MODULE_OK;
+}
+
+static int mod_accounting_off(rlm_sql_handle_t **handle,
+ rlm_sqlippool_t *inst, REQUEST *request)
+{
+ DO(off_begin);
+ DO(off_clear);
+ DO(off_commit);
+
+ return RLM_MODULE_OK;
+}
+
+/*
+ * Check for an Accounting-Stop
+ * If we find one and we have allocated an IP to this nas/port
+ * combination, then deallocate it.
+ */
+static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
+{
+ int rcode = RLM_MODULE_NOOP;
+ VALUE_PAIR *vp;
+
+ int acct_status_type;
+
+ rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance;
+ rlm_sql_handle_t *handle;
+
+ vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY);
+ if (!vp) {
+ RDEBUG("Could not find account status type in packet");
+ return RLM_MODULE_NOOP;
+ }
+ acct_status_type = vp->vp_integer;
+
+ switch (acct_status_type) {
+ case PW_STATUS_START:
+ case PW_STATUS_ALIVE:
+ case PW_STATUS_STOP:
+ case PW_STATUS_ACCOUNTING_ON:
+ case PW_STATUS_ACCOUNTING_OFF:
+ break; /* continue through to the next section */
+
+ default:
+ /* We don't care about any other accounting packet */
+ return RLM_MODULE_NOOP;
+ }
+
+ handle = fr_connection_get(inst->sql_inst->pool);
+ if (!handle) {
+ RDEBUG("Failed reserving SQL connection");
+ return RLM_MODULE_FAIL;
+ }
+
+ if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) return RLM_MODULE_FAIL;
+
+ switch (acct_status_type) {
+ case PW_STATUS_START:
+ rcode = mod_accounting_start(&handle, inst, request);
+ break;
+
+ case PW_STATUS_ALIVE:
+ rcode = mod_accounting_alive(&handle, inst, request);
+ break;
+
+ case PW_STATUS_STOP:
+ rcode = mod_accounting_stop(&handle, inst, request);
+ break;
+
+ case PW_STATUS_ACCOUNTING_ON:
+ rcode = mod_accounting_on(&handle, inst, request);
+ break;
+
+ case PW_STATUS_ACCOUNTING_OFF:
+ rcode = mod_accounting_off(&handle, inst, request);
+ break;
+ }
+
+ if (handle) fr_connection_release(inst->sql_inst->pool, handle);
+
+ return rcode;
+}
+
+/*
+ * The module name should be the only globally exported symbol.
+ * That is, everything else should be 'static'.
+ *
+ * If the module needs to temporarily modify it's instantiation
+ * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
+ * The server will then take care of ensuring that the module
+ * is single-threaded.
+ */
+extern module_t rlm_sqlippool;
+module_t rlm_sqlippool = {
+ .magic = RLM_MODULE_INIT,
+ .name = "sqlippool",
+ .type = RLM_TYPE_THREAD_SAFE,
+ .inst_size = sizeof(rlm_sqlippool_t),
+ .config = module_config,
+ .instantiate = mod_instantiate,
+ .methods = {
+ [MOD_ACCOUNTING] = mod_accounting,
+ [MOD_POST_AUTH] = mod_post_auth
+ },
+};