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>
40 * So we can do pass2 xlat checks on the queries.
42 static const CONF_PARSER query_config[] = {
44 { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_MULTI, rlm_sql_config_t, accounting.query), NULL },
46 {NULL, -1, 0, NULL, NULL}
50 * For now hard-code the subsections. This isn't perfect, but it
51 * helps the average case.
53 static const CONF_PARSER type_config[] = {
55 { "accounting-on", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
56 { "accounting-off", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
57 { "start", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
58 { "interim-update", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
59 { "stop", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
61 {NULL, -1, 0, NULL, NULL}
66 static const CONF_PARSER acct_config[] = {
67 { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, accounting.reference), ".query" },
68 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, accounting.logfile), NULL },
70 { "type", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) type_config },
72 {NULL, -1, 0, NULL, NULL}
75 static const CONF_PARSER postauth_config[] = {
76 { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, postauth.reference), ".query" },
77 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, postauth.logfile), NULL },
79 { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_MULTI, rlm_sql_config_t, postauth.query), NULL },
81 {NULL, -1, 0, NULL, NULL}
84 static const CONF_PARSER module_config[] = {
85 { "driver", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_driver_name), "rlm_sql_null" },
86 { "server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_server), "localhost" },
87 { "port", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_port), "" },
88 { "login", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_login), "" },
89 { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_sql_config_t, sql_password), "" },
90 { "radius_db", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_db), "radius" },
91 { "read_groups", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_groups), "yes" },
92 { "read_profiles", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_profiles), "yes" },
93 { "readclients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, do_clients), NULL },
94 { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, do_clients), "no" },
95 { "deletestalesessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, deletestalesessions), NULL },
96 { "delete_stale_sessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, deletestalesessions), "yes" },
97 { "sql_user_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, query_user), "" },
98 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, logfile), NULL },
99 { "default_user_profile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, default_profile), "" },
100 { "nas_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, client_query), NULL },
101 { "client_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, client_query), "SELECT id,nasname,shortname,type,secret FROM nas" },
102 { "open_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, open_query), NULL },
104 { "authorize_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_check_query), NULL },
105 { "authorize_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_reply_query), NULL },
107 { "authorize_group_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_check_query), "" },
108 { "authorize_group_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_reply_query), "" },
109 { "group_membership_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, groupmemb_query), NULL },
110 #ifdef WITH_SESSION_MGMT
111 { "simul_count_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_count_query), "" },
112 { "simul_verify_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_verify_query), "" },
114 { "safe-characters", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, allowed_chars), NULL },
115 { "safe_characters", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, allowed_chars), "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" },
118 * This only works for a few drivers.
120 { "query_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_config_t, query_timeout), NULL },
122 { "accounting", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) acct_config },
124 { "post-auth", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) postauth_config },
126 {NULL, -1, 0, NULL, NULL}
130 * Fall-Through checking function from rlm_files.c
132 static sql_fall_through_t fall_through(VALUE_PAIR *vp)
135 tmp = pairfind(vp, PW_FALL_THROUGH, 0, TAG_ANY);
137 return tmp ? tmp->vp_integer : FALL_THROUGH_DEFAULT;
143 static int generate_sql_clients(rlm_sql_t *inst);
144 static size_t sql_escape_func(REQUEST *, char *out, size_t outlen, char const *in, void *arg);
149 * For selects the first value of the first column will be returned,
150 * for inserts, updates and deletes the number of rows affected will be
153 static ssize_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace)
155 rlm_sql_handle_t *handle = NULL;
157 rlm_sql_t *inst = instance;
162 * Add SQL-User-Name attribute just in case it is needed
163 * We could search the string fmt for SQL-User-Name to see if this is
166 sql_set_user(inst, request, NULL);
168 handle = fr_connection_get(inst->pool);
173 rlm_sql_query_log(inst, request, NULL, query);
176 * If the query starts with any of the following prefixes,
177 * then return the number of rows affected
179 if ((strncasecmp(query, "insert", 6) == 0) ||
180 (strncasecmp(query, "update", 6) == 0) ||
181 (strncasecmp(query, "delete", 6) == 0)) {
183 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
185 if (rlm_sql_query(&handle, inst, query) != RLM_SQL_OK) {
186 char const *error = (inst->module->sql_error)(handle, inst->config);
187 REDEBUG("SQL query failed: %s", error);
193 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
194 if (numaffected < 1) {
195 RDEBUG("SQL query affected no rows");
201 * Don't chop the returned number if freespace is
202 * too small. This hack is necessary because
203 * some implementations of snprintf return the
204 * size of the written data, and others return
205 * the size of the data they *would* have written
206 * if the output buffer was large enough.
208 snprintf(buffer, sizeof(buffer), "%d", numaffected);
210 len = strlen(buffer);
211 if (len >= freespace){
212 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", inst->config->xlat_name);
214 (inst->module->sql_finish_query)(handle, inst->config);
220 memcpy(out, buffer, len + 1); /* we did bounds checking above */
223 (inst->module->sql_finish_query)(handle, inst->config);
226 } /* else it's a SELECT statement */
228 if (rlm_sql_select_query(&handle, inst, query) != RLM_SQL_OK) {
229 ret = -1; /* error handled by rlm_sql_select_query */
234 ret = rlm_sql_fetch_row(&handle, inst);
236 REDEBUG("SQL query failed");
237 (inst->module->sql_finish_select_query)(handle, inst->config);
245 RDEBUG("SQL query returned no results");
246 (inst->module->sql_finish_select_query)(handle, inst->config);
253 RDEBUG("NULL value in first column of result");
254 (inst->module->sql_finish_select_query)(handle, inst->config);
260 len = strlen(row[0]);
261 if (len >= freespace){
262 RDEBUG("Insufficient string space");
263 (inst->module->sql_finish_select_query)(handle, inst->config);
269 strlcpy(out, row[0], freespace);
272 (inst->module->sql_finish_select_query)(handle, inst->config);
275 fr_connection_release(inst->pool, handle);
280 static int generate_sql_clients(rlm_sql_t *inst)
282 rlm_sql_handle_t *handle;
287 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
288 inst->config->xlat_name);
290 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
291 inst->config->xlat_name, inst->config->client_query);
293 handle = fr_connection_get(inst->pool);
298 if (rlm_sql_select_query(&handle, inst, inst->config->client_query) != RLM_SQL_OK) return -1;
300 while ((rlm_sql_fetch_row(&handle, inst) == 0) && (row = handle->row)) {
305 * The return data for each row MUST be in the following order:
307 * 0. Row ID (currently unused)
308 * 1. Name (or IP address)
312 * 5. Virtual Server (optional)
315 ERROR("rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
319 ERROR("rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
323 ERROR("rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
327 ERROR("rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
331 if (((inst->module->sql_num_fields)(handle, inst->config) > 5) && (row[5] != NULL) && *row[5]) {
335 DEBUG("rlm_sql (%s): Adding client %s (%s) to %s clients list",
336 inst->config->xlat_name,
337 row[1], row[2], server ? server : "global");
339 /* FIXME: We should really pass a proper ctx */
340 c = client_afrom_query(NULL,
341 row[1], /* identifier */
343 row[2], /* shortname */
346 false); /* require message authenticator */
351 if (!client_add(NULL, c)) {
352 WARN("Failed to add client, possible duplicate?");
358 DEBUG("rlm_sql (%s): Client \"%s\" (%s) added", c->longname, c->shortname,
359 inst->config->xlat_name);
362 (inst->module->sql_finish_select_query)(handle, inst->config);
363 fr_connection_release(inst->pool, handle);
370 * Translate the SQL queries.
372 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen,
373 char const *in, void *arg)
375 rlm_sql_t *inst = arg;
382 * Allow all multi-byte UTF8 characters.
384 utf8_len = fr_utf8_char((uint8_t const *) in);
386 if (outlen <= utf8_len) break;
388 memcpy(out, in, utf8_len);
398 * Because we register our own escape function
399 * we're now responsible for escaping all special
400 * chars in an xlat expansion or attribute value.
404 if (outlen <= 2) break;
415 if (outlen <= 2) break;
426 if (outlen <= 2) break;
438 * Non-printable characters get replaced with their
439 * mime-encoded equivalents.
442 strchr(inst->config->allowed_chars, *in) == NULL) {
444 * Only 3 or less bytes available.
450 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
459 * Only one byte left.
479 * Set the SQL user name.
481 * We don't call the escape function here. The resulting string
482 * will be escaped later in the queries xlat so we don't need to
483 * escape it twice. (it will make things wrong if we have an
484 * escape candidate character in the username)
486 int sql_set_user(rlm_sql_t *inst, REQUEST *request, char const *username)
488 char *expanded = NULL;
489 VALUE_PAIR *vp = NULL;
493 rad_assert(request->packet != NULL);
495 if (username != NULL) {
497 } else if (inst->config->query_user[0] != '\0') {
498 sqluser = inst->config->query_user;
503 len = radius_axlat(&expanded, request, sqluser, NULL, NULL);
508 vp = pairalloc(request->packet, inst->sql_user);
510 talloc_free(expanded);
514 pairstrsteal(vp, expanded);
515 RDEBUG2("SQL-User-Name set to '%s'", vp->vp_strvalue);
517 radius_pairmove(request, &request->packet->vps, vp, false); /* needs to be pair move else op is not respected */
523 * Do a set/unset user, so it's a bit clearer what's going on.
525 #define sql_unset_user(_i, _r) pairdelete(&_r->packet->vps, _i->sql_user->attr, _i->sql_user->vendor, TAG_ANY)
527 static int sql_get_grouplist(rlm_sql_t *inst, rlm_sql_handle_t **handle, REQUEST *request,
528 rlm_sql_grouplist_t **phead)
530 char *expanded = NULL;
533 rlm_sql_grouplist_t *entry;
536 /* NOTE: sql_set_user should have been run before calling this function */
538 entry = *phead = NULL;
540 if (!inst->config->groupmemb_query || (inst->config->groupmemb_query[0] == 0)) {
544 if (radius_axlat(&expanded, request, inst->config->groupmemb_query, sql_escape_func, inst) < 0) {
548 ret = rlm_sql_select_query(handle, inst, expanded);
549 talloc_free(expanded);
550 if (ret != RLM_SQL_OK) return -1;
552 while (rlm_sql_fetch_row(handle, inst) == 0) {
553 row = (*handle)->row;
558 RDEBUG("row[0] returned NULL");
559 (inst->module->sql_finish_select_query)(*handle, inst->config);
565 *phead = talloc_zero(*handle, rlm_sql_grouplist_t);
568 entry->next = talloc_zero(*phead, rlm_sql_grouplist_t);
572 entry->name = talloc_typed_strdup(entry, row[0]);
577 (inst->module->sql_finish_select_query)(*handle, inst->config);
584 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
585 * with the group memberships reciding in sql
586 * The group membership query should only return one element which is the username. The returned
587 * username will then be checked with the passed check string.
590 static int CC_HINT(nonnull (1, 2, 4)) sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp,
591 VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs,
592 UNUSED VALUE_PAIR **reply_pairs)
594 rlm_sql_handle_t *handle;
595 rlm_sql_t *inst = instance;
596 rlm_sql_grouplist_t *head, *entry;
598 RDEBUG("sql_groupcmp");
600 if (check->length == 0){
601 RDEBUG("sql_groupcmp: Illegal group name");
606 * Set, escape, and check the user attr here
608 if (sql_set_user(inst, request, NULL) < 0)
612 * Get a socket for this lookup
614 handle = fr_connection_get(inst->pool);
620 * Get the list of groups this user is a member of
622 if (sql_get_grouplist(inst, &handle, request, &head) < 0) {
623 REDEBUG("Error getting group membership");
624 fr_connection_release(inst->pool, handle);
628 for (entry = head; entry != NULL; entry = entry->next) {
629 if (strcmp(entry->name, check->vp_strvalue) == 0){
630 RDEBUG("sql_groupcmp finished: User is a member of group %s",
633 fr_connection_release(inst->pool, handle);
638 /* Free the grouplist */
640 fr_connection_release(inst->pool, handle);
642 RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", check->vp_strvalue);
647 static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
648 sql_fall_through_t *do_fall_through)
650 rlm_rcode_t rcode = RLM_MODULE_NOOP;
651 VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL;
652 rlm_sql_grouplist_t *head = NULL, *entry = NULL;
654 char *expanded = NULL;
657 rad_assert(request->packet != NULL);
660 * Get the list of groups this user is a member of
662 rows = sql_get_grouplist(inst, handle, request, &head);
664 REDEBUG("Error retrieving group list");
666 return RLM_MODULE_FAIL;
669 RDEBUG2("User not found in any groups");
670 rcode = RLM_MODULE_NOTFOUND;
675 RDEBUG2("User found in the group table");
680 * Add the Sql-Group attribute to the request list so we know
681 * which group we're retrieving attributes for
683 sql_group = pairmake_packet("Sql-Group", entry->name, T_OP_EQ);
685 REDEBUG("Error creating Sql-Group attribute");
686 rcode = RLM_MODULE_FAIL;
690 if (inst->config->authorize_group_check_query && *inst->config->authorize_group_check_query) {
695 * Expand the group query
697 if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query,
698 sql_escape_func, inst) < 0) {
699 REDEBUG("Error generating query");
700 rcode = RLM_MODULE_FAIL;
704 rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded);
705 TALLOC_FREE(expanded);
707 REDEBUG("Error retrieving check pairs for group %s", entry->name);
708 rcode = RLM_MODULE_FAIL;
713 * If we got check rows we need to process them before we decide to process the reply rows
716 (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) {
717 pairfree(&check_tmp);
718 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
723 RDEBUG2("Group \"%s\": Conditional check items matched", entry->name);
724 rcode = RLM_MODULE_OK;
726 RDEBUG2("Group \"%s\": Merging assignment check items", entry->name);
728 for (vp = fr_cursor_init(&cursor, &check_tmp);
730 vp = fr_cursor_next(&cursor)) {
731 if (!fr_assignment_op[vp->op]) continue;
733 rdebug_pair(2, request, vp);
736 radius_pairmove(request, &request->config_items, check_tmp, true);
740 if (inst->config->authorize_group_reply_query && *inst->config->authorize_group_reply_query) {
742 * Now get the reply pairs since the paircompare matched
744 if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query,
745 sql_escape_func, inst) < 0) {
746 REDEBUG("Error generating query");
747 rcode = RLM_MODULE_FAIL;
751 rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded);
752 TALLOC_FREE(expanded);
754 REDEBUG("Error retrieving reply pairs for group %s", entry->name);
755 rcode = RLM_MODULE_FAIL;
758 *do_fall_through = fall_through(reply_tmp);
760 RDEBUG2("Group \"%s\": Merging reply items", entry->name);
761 rcode = RLM_MODULE_OK;
763 rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp);
765 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
769 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
771 } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES));
775 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
781 static int mod_detach(void *instance)
783 rlm_sql_t *inst = instance;
785 if (inst->pool) fr_connection_pool_delete(inst->pool);
788 * We need to explicitly free all children, so if the driver
789 * parented any memory off the instance, their destructors
790 * run before we unload the bytecode for them.
792 * If we don't do this, we get a SEGV deep inside the talloc code
793 * when it tries to call a destructor that no longer exists.
795 talloc_free_children(inst);
798 * Decrements the reference count. The driver object won't be unloaded
799 * until all instances of rlm_sql that use it have been destroyed.
801 if (inst->handle) dlclose(inst->handle);
806 static int mod_instantiate(CONF_SECTION *conf, void *instance)
808 rlm_sql_t *inst = instance;
813 inst->config = &inst->myconfig;
816 inst->config->xlat_name = cf_section_name2(conf);
817 if (!inst->config->xlat_name) {
818 inst->config->xlat_name = cf_section_name1(conf);
825 * Allocate room for <instance>-SQL-Group
827 group_name = talloc_typed_asprintf(inst, "%s-SQL-Group", inst->config->xlat_name);
828 DEBUG("rlm_sql (%s): Creating new attribute %s",
829 inst->config->xlat_name, group_name);
831 memset(&flags, 0, sizeof(flags));
832 if (dict_addattr(group_name, -1, 0, PW_TYPE_STRING, flags) < 0) {
833 ERROR("rlm_sql (%s): Failed to create "
834 "attribute %s: %s", inst->config->xlat_name, group_name,
839 da = dict_attrbyname(group_name);
841 ERROR("rlm_sql (%s): Failed to create "
842 "attribute %s", inst->config->xlat_name, group_name);
846 if (inst->config->groupmemb_query &&
847 inst->config->groupmemb_query[0]) {
848 DEBUG("rlm_sql (%s): Registering sql_groupcmp for %s",
849 inst->config->xlat_name, group_name);
850 paircompare_register(da, dict_attrbyvalue(PW_USER_NAME, 0),
851 false, sql_groupcmp, inst);
855 rad_assert(inst->config->xlat_name);
858 * Complain if the strings exist, but are empty.
860 #define CHECK_STRING(_x) if (inst->config->_x && !inst->config->_x) WARN("rlm_sql (%s): " STRINGIFY(_x) " is empty. Please delete it from the configuration", inst->config->xlat_name)
862 CHECK_STRING(authorize_check_query);
863 CHECK_STRING(authorize_reply_query);
864 CHECK_STRING(authorize_group_check_query);
865 CHECK_STRING(authorize_group_reply_query);
868 * This will always exist, as cf_section_parse_init()
869 * will create it if it doesn't exist. However, the
870 * "reference" config item won't exist in an auto-created
871 * configuration. So if that doesn't exist, we ignore
872 * the whole subsection.
874 inst->config->accounting.cs = cf_section_sub_find(conf, "accounting");
875 inst->config->accounting.reference_cp = (cf_pair_find(inst->config->accounting.cs, "reference") != NULL);
877 inst->config->postauth.cs = cf_section_sub_find(conf, "post-auth");
878 inst->config->postauth.reference_cp = (cf_pair_find(inst->config->postauth.cs, "reference") != NULL);
881 * Cache the SQL-User-Name DICT_ATTR, so we can be slightly
882 * more efficient about creating SQL-User-Name attributes.
884 inst->sql_user = dict_attrbyname("SQL-User-Name");
885 if (!inst->sql_user) {
890 * Export these methods, too. This avoids RTDL_GLOBAL.
892 inst->sql_set_user = sql_set_user;
893 inst->sql_escape_func = sql_escape_func;
894 inst->sql_query = rlm_sql_query;
895 inst->sql_select_query = rlm_sql_select_query;
896 inst->sql_fetch_row = rlm_sql_fetch_row;
899 * Register the SQL xlat function
901 xlat_register(inst->config->xlat_name, sql_xlat, sql_escape_func, inst);
904 * Sanity check for crazy people.
906 if (strncmp(inst->config->sql_driver_name, "rlm_sql_", 8) != 0) {
907 ERROR("rlm_sql (%s): \"%s\" is NOT an SQL driver!", inst->config->xlat_name, inst->config->sql_driver_name);
912 * Load the appropriate driver for our database
914 inst->handle = lt_dlopenext(inst->config->sql_driver_name);
916 ERROR("Could not link driver %s: %s", inst->config->sql_driver_name, dlerror());
917 ERROR("Make sure it (and all its dependent libraries!) are in the search path of your system's ld");
921 inst->module = (rlm_sql_module_t *) dlsym(inst->handle,
922 inst->config->sql_driver_name);
924 ERROR("Could not link symbol %s: %s", inst->config->sql_driver_name, dlerror());
928 if (inst->module->mod_instantiate) {
932 name = strrchr(inst->config->sql_driver_name, '_');
934 name = inst->config->sql_driver_name;
939 cs = cf_section_sub_find(conf, name);
941 cs = cf_section_alloc(conf, name, NULL);
948 * It's up to the driver to register a destructor
950 if (inst->module->mod_instantiate(cs, inst->config) < 0) {
955 inst->lf = fr_logfile_init(inst);
957 cf_log_err_cs(conf, "Failed creating log file context");
961 INFO("rlm_sql (%s): Driver %s (module %s) loaded and linked", inst->config->xlat_name,
962 inst->config->sql_driver_name, inst->module->name);
965 * Initialise the connection pool for this instance
967 INFO("rlm_sql (%s): Attempting to connect to database \"%s\"", inst->config->xlat_name, inst->config->sql_db);
969 inst->pool = fr_connection_pool_module_init(inst->cs, inst, mod_conn_create, NULL, NULL);
970 if (!inst->pool) return -1;
972 if (inst->config->groupmemb_query &&
973 inst->config->groupmemb_query[0]) {
974 paircompare_register(dict_attrbyvalue(PW_SQL_GROUP, 0),
975 dict_attrbyvalue(PW_USER_NAME, 0), false, sql_groupcmp, inst);
978 if (inst->config->do_clients) {
979 if (generate_sql_clients(inst) == -1){
980 ERROR("Failed to load clients from SQL");
985 return RLM_MODULE_OK;
989 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
991 rlm_rcode_t rcode = RLM_MODULE_NOOP;
993 rlm_sql_t *inst = instance;
994 rlm_sql_handle_t *handle;
996 VALUE_PAIR *check_tmp = NULL;
997 VALUE_PAIR *reply_tmp = NULL;
998 VALUE_PAIR *user_profile = NULL;
1000 bool user_found = false;
1002 sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT;
1006 char *expanded = NULL;
1008 rad_assert(request->packet != NULL);
1009 rad_assert(request->reply != NULL);
1011 if (!inst->config->authorize_check_query && !inst->config->authorize_reply_query &&
1012 !inst->config->read_groups && !inst->config->read_profiles) {
1013 RWDEBUG("No authorization checks configured, returning noop");
1015 return RLM_MODULE_NOOP;
1019 * Set, escape, and check the user attr here
1021 if (sql_set_user(inst, request, NULL) < 0) {
1022 return RLM_MODULE_FAIL;
1028 * After this point use goto error or goto release to cleanup socket temporary pairlists and
1029 * temporary attributes.
1031 handle = fr_connection_get(inst->pool);
1033 rcode = RLM_MODULE_FAIL;
1038 * Query the check table to find any conditions associated with this user/realm/whatever...
1040 if (inst->config->authorize_check_query && *inst->config->authorize_check_query) {
1044 if (radius_axlat(&expanded, request, inst->config->authorize_check_query,
1045 sql_escape_func, inst) < 0) {
1046 REDEBUG("Error generating query");
1047 rcode = RLM_MODULE_FAIL;
1051 rows = sql_getvpdata(request, inst, request, &handle, &check_tmp, expanded);
1052 TALLOC_FREE(expanded);
1054 REDEBUG("SQL query error getting check attributes");
1055 rcode = RLM_MODULE_FAIL;
1059 if (rows == 0) goto skipreply; /* Don't need to free VPs we don't have */
1062 * Only do this if *some* check pairs were returned
1064 RDEBUG2("User found in radcheck table");
1066 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) {
1067 pairfree(&check_tmp);
1072 RDEBUG2("Conditional check items matched, merging assignment check items");
1074 for (vp = fr_cursor_init(&cursor, &check_tmp);
1076 vp = fr_cursor_next(&cursor)) {
1077 if (!fr_assignment_op[vp->op]) continue;
1079 rdebug_pair(2, request, vp);
1082 radius_pairmove(request, &request->config_items, check_tmp, true);
1084 rcode = RLM_MODULE_OK;
1088 if (inst->config->authorize_reply_query && *inst->config->authorize_reply_query) {
1090 * Now get the reply pairs since the paircompare matched
1092 if (radius_axlat(&expanded, request, inst->config->authorize_reply_query,
1093 sql_escape_func, inst) < 0) {
1094 REDEBUG("Error generating query");
1095 rcode = RLM_MODULE_FAIL;
1099 rows = sql_getvpdata(request->reply, inst, request, &handle, &reply_tmp, expanded);
1100 TALLOC_FREE(expanded);
1102 REDEBUG("SQL query error getting reply attributes");
1103 rcode = RLM_MODULE_FAIL;
1107 if (rows == 0) goto skipreply;
1109 do_fall_through = fall_through(reply_tmp);
1111 RDEBUG2("User found in radreply table, merging reply items");
1114 rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp);
1116 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
1118 rcode = RLM_MODULE_OK;
1123 if ((do_fall_through == FALL_THROUGH_YES) ||
1124 (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1127 RDEBUG3("... falling-through to group processing");
1128 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1131 * Nothing bad happened, continue...
1133 case RLM_MODULE_UPDATED:
1134 rcode = RLM_MODULE_UPDATED;
1137 if (rcode != RLM_MODULE_UPDATED) {
1138 rcode = RLM_MODULE_OK;
1141 case RLM_MODULE_NOOP:
1145 case RLM_MODULE_NOTFOUND:
1155 * Repeat the above process with the default profile or User-Profile
1157 if ((do_fall_through == FALL_THROUGH_YES) ||
1158 (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1162 * Check for a default_profile or for a User-Profile.
1164 RDEBUG3("... falling-through to profile processing");
1165 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY);
1167 char const *profile = user_profile ?
1168 user_profile->vp_strvalue :
1169 inst->config->default_profile;
1171 if (!profile || !*profile) {
1175 RDEBUG2("Checking profile %s", profile);
1177 if (sql_set_user(inst, request, profile) < 0) {
1178 REDEBUG("Error setting profile");
1179 rcode = RLM_MODULE_FAIL;
1183 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1186 * Nothing bad happened, continue...
1188 case RLM_MODULE_UPDATED:
1189 rcode = RLM_MODULE_UPDATED;
1192 if (rcode != RLM_MODULE_UPDATED) {
1193 rcode = RLM_MODULE_OK;
1196 case RLM_MODULE_NOOP:
1200 case RLM_MODULE_NOTFOUND:
1210 * At this point the key (user) hasn't be found in the check table, the reply table
1211 * or the group mapping table, and there was no matching profile.
1215 rcode = RLM_MODULE_NOTFOUND;
1218 fr_connection_release(inst->pool, handle);
1219 sql_unset_user(inst, request);
1224 pairfree(&check_tmp);
1225 pairfree(&reply_tmp);
1226 sql_unset_user(inst, request);
1228 fr_connection_release(inst->pool, handle);
1234 * Generic function for failing between a bunch of queries.
1236 * Uses the same principle as rlm_linelog, expanding the 'reference' config
1237 * item using xlat to figure out what query it should execute.
1239 * If the reference matches multiple config items, and a query fails or
1240 * doesn't update any rows, the next matching config item is used.
1243 static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section)
1245 rlm_rcode_t rcode = RLM_MODULE_OK;
1247 rlm_sql_handle_t *handle = NULL;
1249 int numaffected = 0;
1253 char const *attr = NULL;
1256 char path[MAX_STRING_LEN];
1258 char *expanded = NULL;
1260 rad_assert(section);
1262 if (section->reference[0] != '.') {
1266 if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) {
1267 rcode = RLM_MODULE_FAIL;
1272 item = cf_reference_item(NULL, section->cs, path);
1274 rcode = RLM_MODULE_FAIL;
1279 if (cf_item_is_section(item)){
1280 REDEBUG("Sections are not supported as references");
1281 rcode = RLM_MODULE_FAIL;
1286 pair = cf_itemtopair(item);
1287 attr = cf_pair_attr(pair);
1289 RDEBUG2("Using query template '%s'", attr);
1291 handle = fr_connection_get(inst->pool);
1293 rcode = RLM_MODULE_FAIL;
1298 sql_set_user(inst, request, NULL);
1301 value = cf_pair_value(pair);
1303 RDEBUG("Ignoring null query");
1304 rcode = RLM_MODULE_NOOP;
1309 if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) {
1310 rcode = RLM_MODULE_FAIL;
1316 RDEBUG("Ignoring null query");
1317 rcode = RLM_MODULE_NOOP;
1318 talloc_free(expanded);
1323 rlm_sql_query_log(inst, request, section, expanded);
1326 * If rlm_sql_query cannot use the socket it'll try and
1327 * reconnect. Reconnecting will automatically release
1328 * the current socket, and try to select a new one.
1330 * If we get RLM_SQL_RECONNECT it means all connections in the pool
1331 * were exhausted, and we couldn't create a new connection,
1332 * so we do not need to call fr_connection_release.
1334 sql_ret = rlm_sql_query(&handle, inst, expanded);
1335 TALLOC_FREE(expanded);
1336 if (sql_ret == RLM_SQL_RECONNECT) {
1337 rcode = RLM_MODULE_FAIL;
1343 * Assume all other errors are incidental, and just meant our
1344 * operation failed and its not a client or SQL syntax error.
1346 * @fixme We should actually be able to distinguish between key
1347 * constraint violations (which we expect) and other errors.
1349 if (sql_ret == RLM_SQL_OK) {
1350 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
1351 if (numaffected > 0) {
1352 break; /* A query succeeded, were done! */
1355 RDEBUG("No records updated");
1358 (inst->module->sql_finish_query)(handle, inst->config);
1361 * We assume all entries with the same name form a redundant
1364 pair = cf_pair_find_next(section->cs, pair, attr);
1367 RDEBUG("No additional queries configured");
1368 rcode = RLM_MODULE_NOOP;
1373 RDEBUG("Trying next query...");
1376 (inst->module->sql_finish_query)(handle, inst->config);
1379 talloc_free(expanded);
1380 fr_connection_release(inst->pool, handle);
1381 sql_unset_user(inst, request);
1386 #ifdef WITH_ACCOUNTING
1389 * Accounting: Insert or update session data in our sql table
1391 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST * request) {
1392 rlm_sql_t *inst = instance;
1394 if (inst->config->accounting.reference_cp) {
1395 return acct_redundant(inst, request, &inst->config->accounting);
1398 return RLM_MODULE_NOOP;
1403 #ifdef WITH_SESSION_MGMT
1405 * See if a user is already logged in. Sets request->simul_count to the
1406 * current session count for this user.
1408 * Check twice. If on the first pass the user exceeds his
1409 * max. number of logins, do a second pass and validate all
1410 * logins by querying the terminal server (using eg. SNMP).
1413 static rlm_rcode_t CC_HINT(nonnull) mod_checksimul(void *instance, REQUEST * request) {
1414 rlm_rcode_t rcode = RLM_MODULE_OK;
1415 rlm_sql_handle_t *handle = NULL;
1416 rlm_sql_t *inst = instance;
1420 char const *call_num = NULL;
1423 uint32_t nas_addr = 0;
1424 uint32_t nas_port = 0;
1426 char *expanded = NULL;
1428 /* If simul_count_query is not defined, we don't do any checking */
1429 if (!inst->config->simul_count_query || (inst->config->simul_count_query[0] == '\0')) {
1430 return RLM_MODULE_NOOP;
1433 if((!request->username) || (request->username->length == '\0')) {
1434 REDEBUG("Zero Length username not permitted");
1436 return RLM_MODULE_INVALID;
1440 if(sql_set_user(inst, request, NULL) < 0) {
1441 return RLM_MODULE_FAIL;
1444 if (radius_axlat(&expanded, request, inst->config->simul_count_query, sql_escape_func, inst) < 0) {
1445 sql_unset_user(inst, request);
1446 return RLM_MODULE_FAIL;
1449 /* initialize the sql socket */
1450 handle = fr_connection_get(inst->pool);
1452 talloc_free(expanded);
1453 sql_unset_user(inst, request);
1454 return RLM_MODULE_FAIL;
1457 if (rlm_sql_select_query(&handle, inst, expanded) != RLM_SQL_OK) {
1458 rcode = RLM_MODULE_FAIL;
1462 ret = rlm_sql_fetch_row(&handle, inst);
1464 rcode = RLM_MODULE_FAIL;
1470 rcode = RLM_MODULE_FAIL;
1474 request->simul_count = atoi(row[0]);
1476 (inst->module->sql_finish_select_query)(handle, inst->config);
1477 TALLOC_FREE(expanded);
1479 if (request->simul_count < request->simul_max) {
1480 rcode = RLM_MODULE_OK;
1485 * Looks like too many sessions, so let's start verifying
1486 * them, unless told to rely on count query only.
1488 if (!inst->config->simul_verify_query || (inst->config->simul_verify_query[0] == '\0')) {
1489 rcode = RLM_MODULE_OK;
1494 if (radius_axlat(&expanded, request, inst->config->simul_verify_query, sql_escape_func, inst) < 0) {
1495 rcode = RLM_MODULE_FAIL;
1500 if (rlm_sql_select_query(&handle, inst, expanded) != RLM_SQL_OK) goto finish;
1503 * Setup some stuff, like for MPP detection.
1505 request->simul_count = 0;
1507 if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
1508 ipno = vp->vp_ipaddr;
1511 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
1512 call_num = vp->vp_strvalue;
1515 while (rlm_sql_fetch_row(&handle, inst) == 0) {
1522 RDEBUG("Cannot zap stale entry. No username present in entry");
1523 rcode = RLM_MODULE_FAIL;
1529 RDEBUG("Cannot zap stale entry. No session id in entry");
1530 rcode = RLM_MODULE_FAIL;
1536 nas_addr = inet_addr(row[3]);
1540 nas_port = atoi(row[4]);
1543 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1546 * Stale record - zap it.
1548 if (inst->config->deletestalesessions == true) {
1549 uint32_t framed_addr = 0;
1554 framed_addr = inet_addr(row[5]);
1556 if (strcmp(row[7], "PPP") == 0)
1558 else if (strcmp(row[7], "SLIP") == 0)
1562 sess_time = atoi(row[8]);
1563 session_zap(request, nas_addr, nas_port,
1564 row[2], row[1], framed_addr,
1568 else if (check == 1) {
1570 * User is still logged in.
1572 ++request->simul_count;
1575 * Does it look like a MPP attempt?
1577 if (row[5] && ipno && inet_addr(row[5]) == ipno) {
1578 request->simul_mpp = 2;
1579 } else if (row[6] && call_num && !strncmp(row[6],call_num,16)) {
1580 request->simul_mpp = 2;
1584 * Failed to check the terminal server for
1585 * duplicate logins: return an error.
1587 REDEBUG("Failed to check the terminal server for user '%s'.", row[2]);
1589 rcode = RLM_MODULE_FAIL;
1596 (inst->module->sql_finish_select_query)(handle, inst->config);
1597 fr_connection_release(inst->pool, handle);
1598 talloc_free(expanded);
1599 sql_unset_user(inst, request);
1602 * The Auth module apparently looks at request->simul_count,
1603 * not the return value of this module when deciding to deny
1604 * a call for too many sessions.
1611 * Postauth: Write a record of the authentication attempt
1613 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST * request) {
1614 rlm_sql_t *inst = instance;
1616 if (inst->config->postauth.reference_cp) {
1617 return acct_redundant(inst, request, &inst->config->postauth);
1620 return RLM_MODULE_NOOP;
1624 * Execute postauth_query after authentication
1628 /* globally exported name */
1629 module_t rlm_sql = {
1632 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1635 mod_instantiate, /* instantiation */
1636 mod_detach, /* detach */
1638 NULL, /* authentication */
1639 mod_authorize, /* authorization */
1640 NULL, /* preaccounting */
1641 #ifdef WITH_ACCOUNTING
1642 mod_accounting, /* accounting */
1646 #ifdef WITH_SESSION_MGMT
1647 mod_checksimul, /* checksimul */
1651 NULL, /* pre-proxy */
1652 NULL, /* post-proxy */
1653 mod_post_auth /* post-auth */