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, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Implements SQL 'users' file, and SQL accounting.
21 * @copyright 2012 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
22 * @copyright 2000,2006 The FreeRADIUS server project
23 * @copyright 2000 Mike Machado <mike@innercite.com>
24 * @copyright 2000 Alan DeKok <aland@ox.org>
30 #include <freeradius-devel/radiusd.h>
31 #include <freeradius-devel/modules.h>
32 #include <freeradius-devel/token.h>
33 #include <freeradius-devel/rad_assert.h>
39 static const CONF_PARSER acct_section_config[] = {
40 { "reference", FR_CONF_OFFSET(PW_TYPE_STRING, sql_acct_section_t, reference), ".query" },
41 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING, sql_acct_section_t, logfile), NULL },
43 {NULL, -1, 0, NULL, NULL}
46 static const CONF_PARSER module_config[] = {
47 { "driver", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_driver_name), "rlm_sql_null" },
48 { "server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_server), "localhost" },
49 { "port", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_port), "" },
50 { "login", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_login), "" },
51 { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_sql_config_t, sql_password), "" },
52 { "radius_db", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_db), "radius" },
53 { "read_groups", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_groups), "yes" },
54 { "read_profiles", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_profiles), "yes" },
55 { "readclients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, do_clients), NULL },
56 { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, do_clients), "no" },
57 { "deletestalesessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, deletestalesessions), NULL },
58 { "delete_stale_sessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, deletestalesessions), "yes" },
59 { "sql_user_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, query_user), "" },
60 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, logfile), NULL },
61 { "default_user_profile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, default_profile), "" },
62 { "nas_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, client_query), NULL },
63 { "client_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, client_query), "SELECT id,nasname,shortname,type,secret FROM nas" },
64 { "authorize_check_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, authorize_check_query), "" },
65 { "open_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, open_query), NULL },
66 { "authorize_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, authorize_reply_query), NULL },
67 { "authorize_group_check_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, authorize_group_check_query), "" },
68 { "authorize_group_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, authorize_group_reply_query), "" },
69 { "group_membership_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, groupmemb_query), NULL },
70 #ifdef WITH_SESSION_MGMT
71 { "simul_count_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, simul_count_query), "" },
72 { "simul_verify_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, simul_verify_query), "" },
74 { "safe-characters", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, allowed_chars), NULL },
75 { "safe_characters", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, allowed_chars), "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" },
78 * This only works for a few drivers.
80 { "query_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_config_t, query_timeout), NULL },
82 {NULL, -1, 0, NULL, NULL}
86 * Fall-Through checking function from rlm_files.c
88 static sql_fall_through_t fall_through(VALUE_PAIR *vp)
91 tmp = pairfind(vp, PW_FALL_THROUGH, 0, TAG_ANY);
93 return tmp ? tmp->vp_integer : FALL_THROUGH_DEFAULT;
99 static int generate_sql_clients(rlm_sql_t *inst);
100 static size_t sql_escape_func(REQUEST *, char *out, size_t outlen, char const *in, void *arg);
105 * For selects the first value of the first column will be returned,
106 * for inserts, updates and deletes the number of rows affected will be
109 static ssize_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace)
111 rlm_sql_handle_t *handle = NULL;
113 rlm_sql_t *inst = instance;
118 * Add SQL-User-Name attribute just in case it is needed
119 * We could search the string fmt for SQL-User-Name to see if this is
122 sql_set_user(inst, request, NULL);
124 handle = sql_get_socket(inst);
129 rlm_sql_query_log(inst, request, NULL, query);
132 * If the query starts with any of the following prefixes,
133 * then return the number of rows affected
135 if ((strncasecmp(query, "insert", 6) == 0) ||
136 (strncasecmp(query, "update", 6) == 0) ||
137 (strncasecmp(query, "delete", 6) == 0)) {
139 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
141 if (rlm_sql_query(&handle, inst, query) != RLM_SQL_OK) {
142 char const *error = (inst->module->sql_error)(handle, inst->config);
143 REDEBUG("SQL query failed: %s", error);
149 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
150 if (numaffected < 1) {
151 RDEBUG("SQL query affected no rows");
157 * Don't chop the returned number if freespace is
158 * too small. This hack is necessary because
159 * some implementations of snprintf return the
160 * size of the written data, and others return
161 * the size of the data they *would* have written
162 * if the output buffer was large enough.
164 snprintf(buffer, sizeof(buffer), "%d", numaffected);
166 len = strlen(buffer);
167 if (len >= freespace){
168 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", inst->config->xlat_name);
170 (inst->module->sql_finish_query)(handle, inst->config);
176 memcpy(out, buffer, len + 1); /* we did bounds checking above */
179 (inst->module->sql_finish_query)(handle, inst->config);
182 } /* else it's a SELECT statement */
184 if (rlm_sql_select_query(&handle, inst, query) != RLM_SQL_OK){
185 char const *error = (inst->module->sql_error)(handle, inst->config);
186 REDEBUG("SQL query failed: %s", error);
192 ret = rlm_sql_fetch_row(&handle, inst);
194 REDEBUG("SQL query failed");
195 (inst->module->sql_finish_select_query)(handle, inst->config);
203 RDEBUG("SQL query returned no results");
204 (inst->module->sql_finish_select_query)(handle, inst->config);
211 RDEBUG("NULL value in first column of result");
212 (inst->module->sql_finish_select_query)(handle, inst->config);
218 len = strlen(row[0]);
219 if (len >= freespace){
220 RDEBUG("Insufficient string space");
221 (inst->module->sql_finish_select_query)(handle, inst->config);
227 strlcpy(out, row[0], freespace);
230 (inst->module->sql_finish_select_query)(handle, inst->config);
233 sql_release_socket(inst, handle);
238 static int generate_sql_clients(rlm_sql_t *inst)
240 rlm_sql_handle_t *handle;
245 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
246 inst->config->xlat_name);
248 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
249 inst->config->xlat_name, inst->config->client_query);
251 handle = sql_get_socket(inst);
256 if (rlm_sql_select_query(&handle, inst, inst->config->client_query) != RLM_SQL_OK){
260 while ((rlm_sql_fetch_row(&handle, inst) == 0) && (row = handle->row)) {
265 * The return data for each row MUST be in the following order:
267 * 0. Row ID (currently unused)
268 * 1. Name (or IP address)
272 * 5. Virtual Server (optional)
275 ERROR("rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
279 ERROR("rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
283 ERROR("rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
287 ERROR("rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
291 if (((inst->module->sql_num_fields)(handle, inst->config) > 5) && (row[5] != NULL) && *row[5]) {
295 DEBUG("rlm_sql (%s): Adding client %s (%s) to %s clients list",
296 inst->config->xlat_name,
297 row[1], row[2], server ? server : "global");
299 /* FIXME: We should really pass a proper ctx */
300 c = client_from_query(NULL,
301 row[1], /* identifier */
303 row[2], /* shortname */
306 false); /* require message authenticator */
311 if (!client_add(NULL, c)) {
312 WARN("Failed to add client, possible duplicate?");
318 DEBUG("rlm_sql (%s): Client \"%s\" (%s) added", c->longname, c->shortname,
319 inst->config->xlat_name);
322 (inst->module->sql_finish_select_query)(handle, inst->config);
323 sql_release_socket(inst, handle);
330 * Translate the SQL queries.
332 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen,
333 char const *in, void *arg)
335 rlm_sql_t *inst = arg;
340 * Non-printable characters get replaced with their
341 * mime-encoded equivalents.
344 strchr(inst->config->allowed_chars, *in) == NULL) {
346 * Only 3 or less bytes available.
352 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
361 * Only one byte left.
381 * Set the SQL user name.
383 * We don't call the escape function here. The resulting string
384 * will be escaped later in the queries xlat so we don't need to
385 * escape it twice. (it will make things wrong if we have an
386 * escape candidate character in the username)
388 int sql_set_user(rlm_sql_t *inst, REQUEST *request, char const *username)
390 char *expanded = NULL;
391 VALUE_PAIR *vp = NULL;
395 rad_assert(request->packet != NULL);
397 if (username != NULL) {
399 } else if (inst->config->query_user[0] != '\0') {
400 sqluser = inst->config->query_user;
405 len = radius_axlat(&expanded, request, sqluser, NULL, NULL);
410 vp = pairalloc(request->packet, inst->sql_user);
412 talloc_free(expanded);
416 pairstrsteal(vp, expanded);
417 RDEBUG2("SQL-User-Name set to '%s'", vp->vp_strvalue);
419 radius_pairmove(request, &request->packet->vps, vp, false); /* needs to be pair move else op is not respected */
425 * Do a set/unset user, so it's a bit clearer what's going on.
427 #define sql_unset_user(_i, _r) pairdelete(&_r->packet->vps, _i->sql_user->attr, _i->sql_user->vendor, TAG_ANY)
429 static int sql_get_grouplist(rlm_sql_t *inst, rlm_sql_handle_t **handle, REQUEST *request,
430 rlm_sql_grouplist_t **phead)
432 char *expanded = NULL;
435 rlm_sql_grouplist_t *entry;
438 /* NOTE: sql_set_user should have been run before calling this function */
440 entry = *phead = NULL;
442 if (!inst->config->groupmemb_query || (inst->config->groupmemb_query[0] == 0)) {
446 if (radius_axlat(&expanded, request, inst->config->groupmemb_query, sql_escape_func, inst) < 0) {
450 ret = rlm_sql_select_query(handle, inst, expanded);
451 talloc_free(expanded);
452 if (ret != RLM_SQL_OK) {
456 while (rlm_sql_fetch_row(handle, inst) == 0) {
457 row = (*handle)->row;
462 RDEBUG("row[0] returned NULL");
463 (inst->module->sql_finish_select_query)(*handle, inst->config);
469 *phead = talloc_zero(*handle, rlm_sql_grouplist_t);
472 entry->next = talloc_zero(*phead, rlm_sql_grouplist_t);
476 entry->name = talloc_typed_strdup(entry, row[0]);
481 (inst->module->sql_finish_select_query)(*handle, inst->config);
488 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
489 * with the group memberships reciding in sql
490 * The group membership query should only return one element which is the username. The returned
491 * username will then be checked with the passed check string.
494 static int CC_HINT(nonnull (1 ,2, 4)) sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp,
495 VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs,
496 UNUSED VALUE_PAIR **reply_pairs)
498 rlm_sql_handle_t *handle;
499 rlm_sql_t *inst = instance;
500 rlm_sql_grouplist_t *head, *entry;
502 RDEBUG("sql_groupcmp");
504 if (check->length == 0){
505 RDEBUG("sql_groupcmp: Illegal group name");
510 * Set, escape, and check the user attr here
512 if (sql_set_user(inst, request, NULL) < 0)
516 * Get a socket for this lookup
518 handle = sql_get_socket(inst);
524 * Get the list of groups this user is a member of
526 if (sql_get_grouplist(inst, &handle, request, &head) < 0) {
527 REDEBUG("Error getting group membership");
528 sql_release_socket(inst, handle);
532 for (entry = head; entry != NULL; entry = entry->next) {
533 if (strcmp(entry->name, check->vp_strvalue) == 0){
534 RDEBUG("sql_groupcmp finished: User is a member of group %s",
537 sql_release_socket(inst, handle);
542 /* Free the grouplist */
544 sql_release_socket(inst, handle);
546 RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", check->vp_strvalue);
551 static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
552 sql_fall_through_t *do_fall_through)
554 rlm_rcode_t rcode = RLM_MODULE_NOOP;
555 VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL;
556 rlm_sql_grouplist_t *head = NULL, *entry = NULL;
558 char *expanded = NULL;
561 rad_assert(request->packet != NULL);
564 * Get the list of groups this user is a member of
566 rows = sql_get_grouplist(inst, handle, request, &head);
568 REDEBUG("Error retrieving group list");
570 return RLM_MODULE_FAIL;
573 RDEBUG2("User not found in any groups");
574 rcode = RLM_MODULE_NOTFOUND;
578 RDEBUG2("User found in the group table");
580 for (entry = head; entry != NULL && (*do_fall_through == FALL_THROUGH_YES); entry = entry->next) {
582 * Add the Sql-Group attribute to the request list so we know
583 * which group we're retrieving attributes for
585 sql_group = pairmake_packet("Sql-Group", entry->name, T_OP_EQ);
587 REDEBUG("Error creating Sql-Group attribute");
588 rcode = RLM_MODULE_FAIL;
592 if (inst->config->authorize_group_check_query && (inst->config->authorize_group_check_query != '\0')) {
594 * Expand the group query
596 if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query,
597 sql_escape_func, inst) < 0) {
598 REDEBUG("Error generating query");
599 rcode = RLM_MODULE_FAIL;
603 rows = sql_getvpdata(inst, handle, request, &check_tmp, expanded);
604 TALLOC_FREE(expanded);
606 REDEBUG("Error retrieving check pairs for group %s", entry->name);
607 rcode = RLM_MODULE_FAIL;
612 * If we got check rows we need to process them before we decide to process the reply rows
615 (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) {
616 pairfree(&check_tmp);
617 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
622 RDEBUG2("Group \"%s\" check items matched", entry->name);
623 rcode = RLM_MODULE_OK;
625 radius_pairmove(request, &request->config_items, check_tmp, true);
629 if (inst->config->authorize_group_reply_query && (inst->config->authorize_group_reply_query != '\0')) {
631 * Now get the reply pairs since the paircompare matched
633 if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query,
634 sql_escape_func, inst) < 0) {
635 REDEBUG("Error generating query");
636 rcode = RLM_MODULE_FAIL;
640 rows = sql_getvpdata(inst, handle, request->reply, &reply_tmp, expanded);
641 TALLOC_FREE(expanded);
643 REDEBUG("Error retrieving reply pairs for group %s", entry->name);
644 rcode = RLM_MODULE_FAIL;
648 *do_fall_through = fall_through(reply_tmp);
650 RDEBUG2("Group \"%s\" reply items processed", entry->name);
651 rcode = RLM_MODULE_OK;
653 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
657 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
663 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
669 static int mod_detach(void *instance)
671 rlm_sql_t *inst = instance;
674 if (inst->pool) sql_poolfree(inst);
680 * FIXME: Call the modules 'destroy' function?
682 dlclose(inst->handle); /* ignore any errors */
689 static int parse_sub_section(CONF_SECTION *parent,
691 sql_acct_section_t **config,
692 rlm_components_t comp)
696 char const *name = section_type_value[comp].section;
698 cs = cf_section_sub_find(parent, name);
700 INFO("rlm_sql (%s): Couldn't find configuration for "
701 "%s, will return NOOP for calls from this section",
702 inst->config->xlat_name, name);
707 *config = talloc_zero(parent, sql_acct_section_t);
708 if (cf_section_parse(cs, *config, acct_section_config) < 0) {
709 ERROR("rlm_sql (%s): Couldn't find configuration for "
710 "%s, will return NOOP for calls from this section",
711 inst->config->xlat_name, name);
720 static int mod_instantiate(CONF_SECTION *conf, void *instance)
722 rlm_sql_t *inst = instance;
727 inst->config = &inst->myconfig;
730 inst->config->xlat_name = cf_section_name2(conf);
731 if (!inst->config->xlat_name) {
732 inst->config->xlat_name = cf_section_name1(conf);
739 * Allocate room for <instance>-SQL-Group
741 group_name = talloc_typed_asprintf(inst, "%s-SQL-Group", inst->config->xlat_name);
742 DEBUG("rlm_sql (%s): Creating new attribute %s",
743 inst->config->xlat_name, group_name);
745 memset(&flags, 0, sizeof(flags));
746 if (dict_addattr(group_name, -1, 0, PW_TYPE_STRING, flags) < 0) {
747 ERROR("rlm_sql (%s): Failed to create "
748 "attribute %s: %s", inst->config->xlat_name, group_name,
753 da = dict_attrbyname(group_name);
755 ERROR("rlm_sql (%s): Failed to create "
756 "attribute %s", inst->config->xlat_name, group_name);
760 if (inst->config->groupmemb_query &&
761 inst->config->groupmemb_query[0]) {
762 DEBUG("rlm_sql (%s): Registering sql_groupcmp for %s",
763 inst->config->xlat_name, group_name);
764 paircompare_register(da, dict_attrbyvalue(PW_USER_NAME, 0),
765 false, sql_groupcmp, inst);
769 rad_assert(inst->config->xlat_name);
772 * If the configuration parameters can't be parsed, then fail.
774 if ((parse_sub_section(conf, inst, &inst->config->accounting, RLM_COMPONENT_ACCT) < 0) ||
775 (parse_sub_section(conf, inst, &inst->config->postauth, RLM_COMPONENT_POST_AUTH) < 0)) {
776 cf_log_err_cs(conf, "Invalid configuration");
781 * Cache the SQL-User-Name DICT_ATTR, so we can be slightly
782 * more efficient about creating SQL-User-Name attributes.
784 inst->sql_user = dict_attrbyname("SQL-User-Name");
785 if (!inst->sql_user) {
790 * Export these methods, too. This avoids RTDL_GLOBAL.
792 inst->sql_set_user = sql_set_user;
793 inst->sql_get_socket = sql_get_socket;
794 inst->sql_release_socket = sql_release_socket;
795 inst->sql_escape_func = sql_escape_func;
796 inst->sql_query = rlm_sql_query;
797 inst->sql_select_query = rlm_sql_select_query;
798 inst->sql_fetch_row = rlm_sql_fetch_row;
801 * Register the SQL xlat function
803 xlat_register(inst->config->xlat_name, sql_xlat, sql_escape_func, inst);
806 * Sanity check for crazy people.
808 if (strncmp(inst->config->sql_driver_name, "rlm_sql_", 8) != 0) {
809 ERROR("rlm_sql (%s): \"%s\" is NOT an SQL driver!",
810 inst->config->xlat_name, inst->config->sql_driver_name);
815 * Load the appropriate driver for our database
817 inst->handle = lt_dlopenext(inst->config->sql_driver_name);
819 ERROR("Could not link driver %s: %s",
820 inst->config->sql_driver_name,
822 ERROR("Make sure it (and all its dependent libraries!)"
823 "are in the search path of your system's ld");
827 inst->module = (rlm_sql_module_t *) dlsym(inst->handle,
828 inst->config->sql_driver_name);
830 ERROR("Could not link symbol %s: %s",
831 inst->config->sql_driver_name,
836 if (inst->module->mod_instantiate) {
840 name = strrchr(inst->config->sql_driver_name, '_');
842 name = inst->config->sql_driver_name;
847 cs = cf_section_sub_find(conf, name);
849 cs = cf_section_alloc(conf, name, NULL);
856 * It's up to the driver to register a destructor
858 if (inst->module->mod_instantiate(cs, inst->config) < 0) {
863 inst->lf = fr_logfile_init(inst);
865 cf_log_err_cs(conf, "Failed creating log file context");
869 INFO("rlm_sql (%s): Driver %s (module %s) loaded and linked",
870 inst->config->xlat_name, inst->config->sql_driver_name,
874 * Initialise the connection pool for this instance
876 INFO("rlm_sql (%s): Attempting to connect to database \"%s\"",
877 inst->config->xlat_name, inst->config->sql_db);
879 if (sql_socket_pool_init(inst) < 0) return -1;
881 if (inst->config->groupmemb_query &&
882 inst->config->groupmemb_query[0]) {
883 paircompare_register(dict_attrbyvalue(PW_SQL_GROUP, 0),
884 dict_attrbyvalue(PW_USER_NAME, 0), false, sql_groupcmp, inst);
887 if (inst->config->do_clients) {
888 if (generate_sql_clients(inst) == -1){
889 ERROR("Failed to load clients from SQL");
894 return RLM_MODULE_OK;
898 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
900 rlm_rcode_t rcode = RLM_MODULE_NOOP;
902 rlm_sql_t *inst = instance;
903 rlm_sql_handle_t *handle;
905 VALUE_PAIR *check_tmp = NULL;
906 VALUE_PAIR *reply_tmp = NULL;
907 VALUE_PAIR *user_profile = NULL;
909 bool user_found = false;
911 sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT;
915 char *expanded = NULL;
917 rad_assert(request->packet != NULL);
918 rad_assert(request->reply != NULL);
921 * Set, escape, and check the user attr here
923 if (sql_set_user(inst, request, NULL) < 0) {
924 return RLM_MODULE_FAIL;
930 * After this point use goto error or goto release to cleanup socket temporary pairlists and
931 * temporary attributes.
933 handle = sql_get_socket(inst);
935 rcode = RLM_MODULE_FAIL;
940 * Query the check table to find any conditions associated with this user/realm/whatever...
942 if (inst->config->authorize_check_query && (inst->config->authorize_check_query[0] != '\0')) {
943 if (radius_axlat(&expanded, request, inst->config->authorize_check_query,
944 sql_escape_func, inst) < 0) {
945 REDEBUG("Error generating query");
946 rcode = RLM_MODULE_FAIL;
950 rows = sql_getvpdata(inst, &handle, request, &check_tmp, expanded);
951 TALLOC_FREE(expanded);
953 REDEBUG("SQL query error");
954 rcode = RLM_MODULE_FAIL;
958 if (rows == 0) goto skipreply; /* Don't need to free VPs we don't have */
961 * Only do this if *some* check pairs were returned
963 RDEBUG2("User found in radcheck table");
965 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) {
966 pairfree(&check_tmp);
971 RDEBUG2("Check items matched");
972 radius_pairmove(request, &request->config_items, check_tmp, true);
973 rcode = RLM_MODULE_OK;
977 if (inst->config->authorize_reply_query && (inst->config->authorize_reply_query[0] != '\0')) {
979 * Now get the reply pairs since the paircompare matched
981 if (radius_axlat(&expanded, request, inst->config->authorize_reply_query,
982 sql_escape_func, inst) < 0) {
983 REDEBUG("Error generating query");
984 rcode = RLM_MODULE_FAIL;
988 rows = sql_getvpdata(inst, &handle, request->reply, &reply_tmp, expanded);
989 TALLOC_FREE(expanded);
991 REDEBUG("SQL query error");
992 rcode = RLM_MODULE_FAIL;
996 if (rows == 0) goto skipreply;
998 do_fall_through = fall_through(reply_tmp);
1000 RDEBUG2("User found in radreply table");
1002 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
1003 rcode = RLM_MODULE_OK;
1008 if ((do_fall_through == FALL_THROUGH_YES) ||
1009 (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1012 RDEBUG3("... falling-through to group processing");
1013 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1016 * Nothing bad happened, continue...
1018 case RLM_MODULE_UPDATED:
1019 rcode = RLM_MODULE_UPDATED;
1022 if (rcode != RLM_MODULE_UPDATED) {
1023 rcode = RLM_MODULE_OK;
1026 case RLM_MODULE_NOOP:
1030 case RLM_MODULE_NOTFOUND:
1040 * Repeat the above process with the default profile or User-Profile
1042 if ((do_fall_through == FALL_THROUGH_YES) ||
1043 (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1047 * Check for a default_profile or for a User-Profile.
1049 RDEBUG3("... falling-through to profile processing");
1050 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY);
1052 char const *profile = user_profile ?
1053 user_profile->vp_strvalue :
1054 inst->config->default_profile;
1056 if (!profile || !*profile) {
1060 RDEBUG2("Checking profile %s", profile);
1062 if (sql_set_user(inst, request, profile) < 0) {
1063 REDEBUG("Error setting profile");
1064 rcode = RLM_MODULE_FAIL;
1068 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1071 * Nothing bad happened, continue...
1073 case RLM_MODULE_UPDATED:
1074 rcode = RLM_MODULE_UPDATED;
1077 if (rcode != RLM_MODULE_UPDATED) {
1078 rcode = RLM_MODULE_OK;
1081 case RLM_MODULE_NOOP:
1085 case RLM_MODULE_NOTFOUND:
1095 * At this point the key (user) hasn't be found in the check table, the reply table
1096 * or the group mapping table, and there was no matching profile.
1100 rcode = RLM_MODULE_NOTFOUND;
1103 sql_release_socket(inst, handle);
1104 sql_unset_user(inst, request);
1109 pairfree(&check_tmp);
1110 pairfree(&reply_tmp);
1111 sql_unset_user(inst, request);
1113 sql_release_socket(inst, handle);
1119 * Generic function for failing between a bunch of queries.
1121 * Uses the same principle as rlm_linelog, expanding the 'reference' config
1122 * item using xlat to figure out what query it should execute.
1124 * If the reference matches multiple config items, and a query fails or
1125 * doesn't update any rows, the next matching config item is used.
1128 static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section)
1130 rlm_rcode_t rcode = RLM_MODULE_OK;
1132 rlm_sql_handle_t *handle = NULL;
1134 int numaffected = 0;
1138 char const *attr = NULL;
1141 char path[MAX_STRING_LEN];
1143 char *expanded = NULL;
1145 rad_assert(section);
1147 if (section->reference[0] != '.') {
1151 if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) {
1152 rcode = RLM_MODULE_FAIL;
1157 item = cf_reference_item(NULL, section->cs, path);
1159 rcode = RLM_MODULE_FAIL;
1164 if (cf_item_is_section(item)){
1165 REDEBUG("Sections are not supported as references");
1166 rcode = RLM_MODULE_FAIL;
1171 pair = cf_itemtopair(item);
1172 attr = cf_pair_attr(pair);
1174 RDEBUG2("Using query template '%s'", attr);
1176 handle = sql_get_socket(inst);
1178 rcode = RLM_MODULE_FAIL;
1183 sql_set_user(inst, request, NULL);
1186 value = cf_pair_value(pair);
1188 RDEBUG("Ignoring null query");
1189 rcode = RLM_MODULE_NOOP;
1194 if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) {
1195 rcode = RLM_MODULE_FAIL;
1201 RDEBUG("Ignoring null query");
1202 rcode = RLM_MODULE_NOOP;
1203 talloc_free(expanded);
1208 rlm_sql_query_log(inst, request, section, expanded);
1211 * If rlm_sql_query cannot use the socket it'll try and
1212 * reconnect. Reconnecting will automatically release
1213 * the current socket, and try to select a new one.
1215 * If we get RLM_SQL_RECONNECT it means all connections in the pool
1216 * were exhausted, and we couldn't create a new connection,
1217 * so we do not need to call sql_release_socket.
1219 sql_ret = rlm_sql_query(&handle, inst, expanded);
1220 TALLOC_FREE(expanded);
1221 if (sql_ret == RLM_SQL_RECONNECT) {
1222 rcode = RLM_MODULE_FAIL;
1228 * Assume all other errors are incidental, and just meant our
1229 * operation failed and its not a client or SQL syntax error.
1231 * @fixme We should actually be able to distinguish between key
1232 * constraint violations (which we expect) and other errors.
1234 if (sql_ret == RLM_SQL_OK) {
1235 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
1236 if (numaffected > 0) {
1237 break; /* A query succeeded, were done! */
1240 RDEBUG("No records updated");
1243 (inst->module->sql_finish_query)(handle, inst->config);
1246 * We assume all entries with the same name form a redundant
1249 pair = cf_pair_find_next(section->cs, pair, attr);
1252 RDEBUG("No additional queries configured");
1253 rcode = RLM_MODULE_NOOP;
1258 RDEBUG("Trying next query...");
1261 (inst->module->sql_finish_query)(handle, inst->config);
1264 talloc_free(expanded);
1265 sql_release_socket(inst, handle);
1266 sql_unset_user(inst, request);
1271 #ifdef WITH_ACCOUNTING
1274 * Accounting: Insert or update session data in our sql table
1276 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST * request) {
1277 rlm_sql_t *inst = instance;
1279 if (inst->config->accounting) {
1280 return acct_redundant(inst, request, inst->config->accounting);
1283 return RLM_MODULE_NOOP;
1288 #ifdef WITH_SESSION_MGMT
1290 * See if a user is already logged in. Sets request->simul_count to the
1291 * current session count for this user.
1293 * Check twice. If on the first pass the user exceeds his
1294 * max. number of logins, do a second pass and validate all
1295 * logins by querying the terminal server (using eg. SNMP).
1298 static rlm_rcode_t CC_HINT(nonnull) mod_checksimul(void *instance, REQUEST * request) {
1299 rlm_rcode_t rcode = RLM_MODULE_OK;
1300 rlm_sql_handle_t *handle = NULL;
1301 rlm_sql_t *inst = instance;
1305 char const *call_num = NULL;
1308 uint32_t nas_addr = 0;
1309 uint32_t nas_port = 0;
1311 char *expanded = NULL;
1313 /* If simul_count_query is not defined, we don't do any checking */
1314 if (!inst->config->simul_count_query || (inst->config->simul_count_query[0] == '\0')) {
1315 return RLM_MODULE_NOOP;
1318 if((!request->username) || (request->username->length == '\0')) {
1319 REDEBUG("Zero Length username not permitted");
1321 return RLM_MODULE_INVALID;
1325 if(sql_set_user(inst, request, NULL) < 0) {
1326 return RLM_MODULE_FAIL;
1329 if (radius_axlat(&expanded, request, inst->config->simul_count_query, sql_escape_func, inst) < 0) {
1330 sql_unset_user(inst, request);
1331 return RLM_MODULE_FAIL;
1334 /* initialize the sql socket */
1335 handle = sql_get_socket(inst);
1337 talloc_free(expanded);
1338 sql_unset_user(inst, request);
1339 return RLM_MODULE_FAIL;
1342 if (rlm_sql_select_query(&handle, inst, expanded) != RLM_SQL_OK) {
1343 rcode = RLM_MODULE_FAIL;
1347 ret = rlm_sql_fetch_row(&handle, inst);
1349 rcode = RLM_MODULE_FAIL;
1355 rcode = RLM_MODULE_FAIL;
1359 request->simul_count = atoi(row[0]);
1361 (inst->module->sql_finish_select_query)(handle, inst->config);
1362 TALLOC_FREE(expanded);
1364 if (request->simul_count < request->simul_max) {
1365 rcode = RLM_MODULE_OK;
1370 * Looks like too many sessions, so let's start verifying
1371 * them, unless told to rely on count query only.
1373 if (!inst->config->simul_verify_query || (inst->config->simul_verify_query[0] == '\0')) {
1374 rcode = RLM_MODULE_OK;
1379 if (radius_axlat(&expanded, request, inst->config->simul_verify_query, sql_escape_func, inst) < 0) {
1380 rcode = RLM_MODULE_FAIL;
1385 if (rlm_sql_select_query(&handle, inst, expanded) != RLM_SQL_OK) {
1390 * Setup some stuff, like for MPP detection.
1392 request->simul_count = 0;
1394 if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
1395 ipno = vp->vp_ipaddr;
1398 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
1399 call_num = vp->vp_strvalue;
1402 while (rlm_sql_fetch_row(&handle, inst) == 0) {
1409 RDEBUG("Cannot zap stale entry. No username present in entry");
1410 rcode = RLM_MODULE_FAIL;
1416 RDEBUG("Cannot zap stale entry. No session id in entry");
1417 rcode = RLM_MODULE_FAIL;
1423 nas_addr = inet_addr(row[3]);
1427 nas_port = atoi(row[4]);
1430 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1433 * Stale record - zap it.
1435 if (inst->config->deletestalesessions == true) {
1436 uint32_t framed_addr = 0;
1441 framed_addr = inet_addr(row[5]);
1443 if (strcmp(row[7], "PPP") == 0)
1445 else if (strcmp(row[7], "SLIP") == 0)
1449 sess_time = atoi(row[8]);
1450 session_zap(request, nas_addr, nas_port,
1451 row[2], row[1], framed_addr,
1455 else if (check == 1) {
1457 * User is still logged in.
1459 ++request->simul_count;
1462 * Does it look like a MPP attempt?
1464 if (row[5] && ipno && inet_addr(row[5]) == ipno) {
1465 request->simul_mpp = 2;
1466 } else if (row[6] && call_num && !strncmp(row[6],call_num,16)) {
1467 request->simul_mpp = 2;
1471 * Failed to check the terminal server for
1472 * duplicate logins: return an error.
1474 REDEBUG("Failed to check the terminal server for user '%s'.", row[2]);
1476 rcode = RLM_MODULE_FAIL;
1483 (inst->module->sql_finish_select_query)(handle, inst->config);
1484 sql_release_socket(inst, handle);
1485 talloc_free(expanded);
1486 sql_unset_user(inst, request);
1489 * The Auth module apparently looks at request->simul_count,
1490 * not the return value of this module when deciding to deny
1491 * a call for too many sessions.
1498 * Postauth: Write a record of the authentication attempt
1500 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST * request) {
1501 rlm_sql_t *inst = instance;
1503 if (inst->config->postauth) {
1504 return acct_redundant(inst, request, inst->config->postauth);
1507 return RLM_MODULE_NOOP;
1511 * Execute postauth_query after authentication
1515 /* globally exported name */
1516 module_t rlm_sql = {
1519 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1522 mod_instantiate, /* instantiation */
1523 mod_detach, /* detach */
1525 NULL, /* authentication */
1526 mod_authorize, /* authorization */
1527 NULL, /* preaccounting */
1528 #ifdef WITH_ACCOUNTING
1529 mod_accounting, /* accounting */
1533 #ifdef WITH_SESSION_MGMT
1534 mod_checksimul, /* checksimul */
1538 NULL, /* pre-proxy */
1539 NULL, /* post-proxy */
1540 mod_post_auth /* post-auth */