diff options
Diffstat (limited to 'src/modules/rlm_sqlippool/rlm_sqlippool.c')
-rw-r--r-- | src/modules/rlm_sqlippool/rlm_sqlippool.c | 900 |
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 + }, +}; |