2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief Implements SQL 'users' file, and SQL accounting.
22 * @copyright 2012-2014 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23 * @copyright 2000,2006 The FreeRADIUS server project
24 * @copyright 2000 Mike Machado <mike@innercite.com>
25 * @copyright 2000 Alan DeKok <aland@ox.org>
31 #include <freeradius-devel/radiusd.h>
32 #include <freeradius-devel/modules.h>
33 #include <freeradius-devel/token.h>
34 #include <freeradius-devel/rad_assert.h>
35 #include <freeradius-devel/exfile.h>
42 * So we can do pass2 xlat checks on the queries.
44 static const CONF_PARSER query_config[] = {
45 { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_MULTI, rlm_sql_config_t, accounting.query), NULL },
47 CONF_PARSER_TERMINATOR
51 * For now hard-code the subsections. This isn't perfect, but it
52 * helps the average case.
54 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 CONF_PARSER_TERMINATOR
64 static const CONF_PARSER acct_config[] = {
65 { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, accounting.reference), ".query" },
66 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, accounting.logfile), NULL },
68 { "type", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) type_config },
70 CONF_PARSER_TERMINATOR
73 static const CONF_PARSER postauth_config[] = {
74 { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, postauth.reference), ".query" },
75 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, postauth.logfile), NULL },
77 { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_MULTI, rlm_sql_config_t, postauth.query), NULL },
78 CONF_PARSER_TERMINATOR
81 static const CONF_PARSER module_config[] = {
82 { "driver", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_driver_name), "rlm_sql_null" },
83 { "server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_server), "" }, /* Must be zero length so drivers can determine if it was set */
84 { "port", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_config_t, sql_port), "0" },
85 { "login", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_login), "" },
86 { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_sql_config_t, sql_password), "" },
87 { "radius_db", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_db), "radius" },
88 { "read_groups", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_groups), "yes" },
89 { "read_profiles", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_profiles), "yes" },
90 { "readclients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, do_clients), NULL },
91 { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, do_clients), "no" },
92 { "deletestalesessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, delete_stale_sessions), NULL },
93 { "delete_stale_sessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, delete_stale_sessions), "yes" },
94 { "sql_user_name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, query_user), "" },
95 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, logfile), NULL },
96 { "default_user_profile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, default_profile), "" },
97 { "nas_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, client_query), NULL },
98 { "client_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, client_query), "SELECT id,nasname,shortname,type,secret FROM nas" },
99 { "open_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, connect_query), NULL },
101 { "authorize_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_check_query), NULL },
102 { "authorize_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_reply_query), NULL },
104 { "authorize_group_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_check_query), NULL },
105 { "authorize_group_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_reply_query), NULL },
106 { "group_membership_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, groupmemb_query), NULL },
107 #ifdef WITH_SESSION_MGMT
108 { "simul_count_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_count_query), NULL },
109 { "simul_verify_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_verify_query), NULL },
111 { "safe-characters", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, allowed_chars), NULL },
112 { "safe_characters", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, allowed_chars), "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" },
115 * This only works for a few drivers.
117 { "query_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_config_t, query_timeout), NULL },
119 { "accounting", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) acct_config },
121 { "post-auth", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) postauth_config },
122 CONF_PARSER_TERMINATOR
126 * Fall-Through checking function from rlm_files.c
128 static sql_fall_through_t fall_through(VALUE_PAIR *vp)
131 tmp = fr_pair_find_by_num(vp, PW_FALL_THROUGH, 0, TAG_ANY);
133 return tmp ? tmp->vp_integer : FALL_THROUGH_DEFAULT;
139 static int generate_sql_clients(rlm_sql_t *inst);
140 static size_t sql_escape_func(REQUEST *, char *out, size_t outlen, char const *in, void *arg);
145 * For selects the first value of the first column will be returned,
146 * for inserts, updates and deletes the number of rows affected will be
149 static ssize_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace)
151 rlm_sql_handle_t *handle = NULL;
153 rlm_sql_t *inst = instance;
160 * Add SQL-User-Name attribute just in case it is needed
161 * We could search the string fmt for SQL-User-Name to see if this is
164 sql_set_user(inst, request, NULL);
166 handle = fr_connection_get(inst->pool); /* connection pool should produce error */
167 if (!handle) return 0;
169 rlm_sql_query_log(inst, request, NULL, query);
172 * Trim whitespace for the prefix check
174 for (p = query; is_whitespace(p); p++);
177 * If the query starts with any of the following prefixes,
178 * then return the number of rows affected
180 if ((strncasecmp(p, "insert", 6) == 0) ||
181 (strncasecmp(p, "update", 6) == 0) ||
182 (strncasecmp(p, "delete", 6) == 0)) {
184 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
186 rcode = rlm_sql_query(inst, request, &handle, query);
187 if (rcode != RLM_SQL_OK) {
189 RERROR("SQL query failed: %s", fr_int2str(sql_rcode_table, rcode, "<INVALID>"));
195 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
196 if (numaffected < 1) {
197 RDEBUG("SQL query affected no rows");
198 (inst->module->sql_finish_query)(handle, inst->config);
204 * Don't chop the returned number if freespace is
205 * too small. This hack is necessary because
206 * some implementations of snprintf return the
207 * size of the written data, and others return
208 * the size of the data they *would* have written
209 * if the output buffer was large enough.
211 snprintf(buffer, sizeof(buffer), "%d", numaffected);
213 len = strlen(buffer);
214 if (len >= freespace){
215 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", inst->name);
217 (inst->module->sql_finish_query)(handle, inst->config);
223 memcpy(out, buffer, len + 1); /* we did bounds checking above */
226 (inst->module->sql_finish_query)(handle, inst->config);
229 } /* else it's a SELECT statement */
231 rcode = rlm_sql_select_query(inst, request, &handle, query);
232 if (rcode != RLM_SQL_OK) goto query_error;
234 rcode = rlm_sql_fetch_row(inst, request, &handle);
236 (inst->module->sql_finish_select_query)(handle, inst->config);
242 RDEBUG("SQL query returned no results");
243 (inst->module->sql_finish_select_query)(handle, inst->config);
250 RDEBUG("NULL value in first column of result");
251 (inst->module->sql_finish_select_query)(handle, inst->config);
257 len = strlen(row[0]);
258 if (len >= freespace){
259 RDEBUG("Insufficient string space");
260 (inst->module->sql_finish_select_query)(handle, inst->config);
266 strlcpy(out, row[0], freespace);
269 (inst->module->sql_finish_select_query)(handle, inst->config);
272 fr_connection_release(inst->pool, handle);
277 static int generate_sql_clients(rlm_sql_t *inst)
279 rlm_sql_handle_t *handle;
284 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
287 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
288 inst->name, inst->config->client_query);
290 handle = fr_connection_get(inst->pool);
291 if (!handle) return -1;
293 if (rlm_sql_select_query(inst, NULL, &handle, inst->config->client_query) != RLM_SQL_OK) return -1;
295 while ((rlm_sql_fetch_row(inst, NULL, &handle) == RLM_SQL_OK) && (row = handle->row)) {
301 num_rows = (inst->module->sql_num_fields)(handle, inst->config);
303 WARN("SELECT returned too few rows. Please do not edit 'client_query'");
308 * The return data for each row MUST be in the following order:
310 * 0. Row ID (currently unused)
311 * 1. Name (or IP address)
315 * 5. Virtual Server (optional)
318 ERROR("rlm_sql (%s): No row id found on pass %d",inst->name,i);
322 ERROR("rlm_sql (%s): No nasname found for row %s",inst->name,row[0]);
326 ERROR("rlm_sql (%s): No short name found for row %s",inst->name,row[0]);
330 ERROR("rlm_sql (%s): No secret found for row %s",inst->name,row[0]);
334 if ((num_rows > 5) && (row[5] != NULL) && *row[5]) {
338 DEBUG("rlm_sql (%s): Adding client %s (%s) to %s clients list",
340 row[1], row[2], server ? server : "global");
342 /* FIXME: We should really pass a proper ctx */
343 c = client_afrom_query(NULL,
344 row[1], /* identifier */
346 row[2], /* shortname */
349 false); /* require message authenticator */
354 if (!client_add(NULL, c)) {
355 WARN("Failed to add client, possible duplicate?");
361 DEBUG("rlm_sql (%s): Client \"%s\" (%s) added", c->longname, c->shortname,
365 (inst->module->sql_finish_select_query)(handle, inst->config);
366 fr_connection_release(inst->pool, handle);
373 * Translate the SQL queries.
375 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen,
376 char const *in, void *arg)
378 rlm_sql_t *inst = arg;
385 * Allow all multi-byte UTF8 characters.
387 utf8_len = fr_utf8_char((uint8_t const *) in, -1);
389 if (outlen <= utf8_len) break;
391 memcpy(out, in, utf8_len);
401 * Because we register our own escape function
402 * we're now responsible for escaping all special
403 * chars in an xlat expansion or attribute value.
407 if (outlen <= 2) break;
418 if (outlen <= 2) break;
429 if (outlen <= 2) break;
441 * Non-printable characters get replaced with their
442 * mime-encoded equivalents.
445 strchr(inst->config->allowed_chars, *in) == NULL) {
447 * Only 3 or less bytes available.
453 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
462 * Only one byte left.
482 * Set the SQL user name.
484 * We don't call the escape function here. The resulting string
485 * will be escaped later in the queries xlat so we don't need to
486 * escape it twice. (it will make things wrong if we have an
487 * escape candidate character in the username)
489 int sql_set_user(rlm_sql_t *inst, REQUEST *request, char const *username)
491 char *expanded = NULL;
492 VALUE_PAIR *vp = NULL;
496 rad_assert(request->packet != NULL);
498 if (username != NULL) {
500 } else if (inst->config->query_user[0] != '\0') {
501 sqluser = inst->config->query_user;
506 len = radius_axlat(&expanded, request, sqluser, NULL, NULL);
511 vp = fr_pair_afrom_da(request->packet, inst->sql_user);
513 talloc_free(expanded);
517 fr_pair_value_strsteal(vp, expanded);
518 RDEBUG2("SQL-User-Name set to '%s'", vp->vp_strvalue);
522 * Delete any existing SQL-User-Name, and replace it with ours.
524 fr_pair_delete_by_num(&request->packet->vps, vp->da->attr, vp->da->vendor, TAG_ANY);
525 fr_pair_add(&request->packet->vps, vp);
531 * Do a set/unset user, so it's a bit clearer what's going on.
533 #define sql_unset_user(_i, _r) fr_pair_delete_by_num(&_r->packet->vps, _i->sql_user->attr, _i->sql_user->vendor, TAG_ANY)
535 static int sql_get_grouplist(rlm_sql_t *inst, rlm_sql_handle_t **handle, REQUEST *request,
536 rlm_sql_grouplist_t **phead)
538 char *expanded = NULL;
541 rlm_sql_grouplist_t *entry;
544 /* NOTE: sql_set_user should have been run before calling this function */
546 entry = *phead = NULL;
548 if (!inst->config->groupmemb_query) return 0;
550 if (radius_axlat(&expanded, request, inst->config->groupmemb_query, sql_escape_func, inst) < 0) return -1;
552 ret = rlm_sql_select_query(inst, request, handle, expanded);
553 talloc_free(expanded);
554 if (ret != RLM_SQL_OK) return -1;
556 while (rlm_sql_fetch_row(inst, request, handle) == RLM_SQL_OK) {
557 row = (*handle)->row;
562 RDEBUG("row[0] returned NULL");
563 (inst->module->sql_finish_select_query)(*handle, inst->config);
569 *phead = talloc_zero(*handle, rlm_sql_grouplist_t);
572 entry->next = talloc_zero(*phead, rlm_sql_grouplist_t);
576 entry->name = talloc_typed_strdup(entry, row[0]);
581 (inst->module->sql_finish_select_query)(*handle, inst->config);
588 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
589 * with the group memberships reciding in sql
590 * The group membership query should only return one element which is the username. The returned
591 * username will then be checked with the passed check string.
593 static int sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp,
594 VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs,
595 UNUSED VALUE_PAIR **reply_pairs) CC_HINT(nonnull (1, 2, 4));
597 static int sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp,
598 VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs,
599 UNUSED VALUE_PAIR **reply_pairs)
601 rlm_sql_handle_t *handle;
602 rlm_sql_t *inst = instance;
603 rlm_sql_grouplist_t *head, *entry;
606 * No group queries, don't do group comparisons.
608 if (!inst->config->groupmemb_query) {
609 RWARN("Cannot do group comparison when group_membership_query is not set");
613 RDEBUG("sql_groupcmp");
615 if (check->vp_length == 0){
616 RDEBUG("sql_groupcmp: Illegal group name");
621 * Set, escape, and check the user attr here
623 if (sql_set_user(inst, request, NULL) < 0)
627 * Get a socket for this lookup
629 handle = fr_connection_get(inst->pool);
635 * Get the list of groups this user is a member of
637 if (sql_get_grouplist(inst, &handle, request, &head) < 0) {
638 REDEBUG("Error getting group membership");
639 fr_connection_release(inst->pool, handle);
643 for (entry = head; entry != NULL; entry = entry->next) {
644 if (strcmp(entry->name, check->vp_strvalue) == 0){
645 RDEBUG("sql_groupcmp finished: User is a member of group %s",
648 fr_connection_release(inst->pool, handle);
653 /* Free the grouplist */
655 fr_connection_release(inst->pool, handle);
657 RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", check->vp_strvalue);
662 static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
663 sql_fall_through_t *do_fall_through)
665 rlm_rcode_t rcode = RLM_MODULE_NOOP;
666 VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL;
667 rlm_sql_grouplist_t *head = NULL, *entry = NULL;
669 char *expanded = NULL;
672 rad_assert(request->packet != NULL);
674 if (!inst->config->groupmemb_query) {
675 RWARN("Cannot do check groups when group_membership_query is not set");
678 *do_fall_through = FALL_THROUGH_DEFAULT;
681 * Didn't add group attributes or allocate
682 * memory, so don't do anything else.
684 return RLM_MODULE_NOTFOUND;
688 * Get the list of groups this user is a member of
690 rows = sql_get_grouplist(inst, handle, request, &head);
692 REDEBUG("Error retrieving group list");
694 return RLM_MODULE_FAIL;
697 RDEBUG2("User not found in any groups");
702 RDEBUG2("User found in the group table");
705 * Add the Sql-Group attribute to the request list so we know
706 * which group we're retrieving attributes for
708 sql_group = pair_make_request(inst->group_da->name, NULL, T_OP_EQ);
710 REDEBUG("Error creating %s attribute", inst->group_da->name);
711 rcode = RLM_MODULE_FAIL;
718 rad_assert(entry != NULL);
719 fr_pair_value_strcpy(sql_group, entry->name);
721 if (inst->config->authorize_group_check_query) {
726 * Expand the group query
728 if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query,
729 sql_escape_func, inst) < 0) {
730 REDEBUG("Error generating query");
731 rcode = RLM_MODULE_FAIL;
735 rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded);
736 TALLOC_FREE(expanded);
738 REDEBUG("Error retrieving check pairs for group %s", entry->name);
739 rcode = RLM_MODULE_FAIL;
744 * If we got check rows we need to process them before we decide to
745 * process the reply rows
748 (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) {
749 fr_pair_list_free(&check_tmp);
754 goto next; /* != continue */
757 RDEBUG2("Group \"%s\": Conditional check items matched", entry->name);
758 rcode = RLM_MODULE_OK;
760 RDEBUG2("Group \"%s\": Merging assignment check items", entry->name);
762 for (vp = fr_cursor_init(&cursor, &check_tmp);
764 vp = fr_cursor_next(&cursor)) {
765 if (!fr_assignment_op[vp->op]) continue;
767 rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
770 radius_pairmove(request, &request->config, check_tmp, true);
774 if (inst->config->authorize_group_reply_query) {
776 * Now get the reply pairs since the paircompare matched
778 if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query,
779 sql_escape_func, inst) < 0) {
780 REDEBUG("Error generating query");
781 rcode = RLM_MODULE_FAIL;
785 rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded);
786 TALLOC_FREE(expanded);
788 REDEBUG("Error retrieving reply pairs for group %s", entry->name);
789 rcode = RLM_MODULE_FAIL;
792 *do_fall_through = fall_through(reply_tmp);
794 RDEBUG2("Group \"%s\": Merging reply items", entry->name);
795 rcode = RLM_MODULE_OK;
797 rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL);
799 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
802 * If there's no reply query configured, then we assume
803 * FALL_THROUGH_NO, which is the same as the users file if you
804 * had no reply attributes.
807 *do_fall_through = FALL_THROUGH_DEFAULT;
811 } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES));
815 fr_pair_delete_by_num(&request->packet->vps, inst->group_da->attr, 0, TAG_ANY);
821 static int mod_detach(void *instance)
823 rlm_sql_t *inst = instance;
825 if (inst->pool) fr_connection_pool_free(inst->pool);
828 * We need to explicitly free all children, so if the driver
829 * parented any memory off the instance, their destructors
830 * run before we unload the bytecode for them.
832 * If we don't do this, we get a SEGV deep inside the talloc code
833 * when it tries to call a destructor that no longer exists.
835 talloc_free_children(inst);
838 * Decrements the reference count. The driver object won't be unloaded
839 * until all instances of rlm_sql that use it have been destroyed.
841 if (inst->handle) dlclose(inst->handle);
846 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
848 rlm_sql_t *inst = instance;
853 inst->config = &inst->myconfig;
856 inst->name = cf_section_name2(conf);
857 if (!inst->name) inst->name = cf_section_name1(conf);
860 * Load the appropriate driver for our database.
862 * We need this to check if the sql_fields callback is provided.
864 inst->handle = fr_dlopenext(inst->config->sql_driver_name);
866 ERROR("Could not link driver %s: %s", inst->config->sql_driver_name, fr_strerror());
867 ERROR("Make sure it (and all its dependent libraries!) are in the search path of your system's ld");
871 inst->module = (rlm_sql_module_t *) dlsym(inst->handle, inst->config->sql_driver_name);
873 ERROR("Could not link symbol %s: %s", inst->config->sql_driver_name, dlerror());
877 INFO("rlm_sql (%s): Driver %s (module %s) loaded and linked", inst->name,
878 inst->config->sql_driver_name, inst->module->name);
880 if (inst->config->groupmemb_query) {
881 if (cf_section_name2(conf)) {
884 snprintf(buffer, sizeof(buffer), "%s-SQL-Group", inst->name);
886 if (paircompare_register_byname(buffer, dict_attrbyvalue(PW_USER_NAME, 0),
887 false, sql_groupcmp, inst) < 0) {
888 ERROR("Error registering group comparison: %s", fr_strerror());
892 inst->group_da = dict_attrbyname(buffer);
895 * We're the default instance
898 if (paircompare_register_byname("SQL-Group", dict_attrbyvalue(PW_USER_NAME, 0),
899 false, sql_groupcmp, inst) < 0) {
900 ERROR("Error registering group comparison: %s", fr_strerror());
904 inst->group_da = dict_attrbyname("SQL-Group");
907 if (!inst->group_da) {
908 ERROR("Failed resolving group attribute");
914 * Register the SQL xlat function
916 xlat_register(inst->name, sql_xlat, sql_escape_func, inst);
922 static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
925 rlm_sql_t *inst = instance;
926 rlm_sql_handle_t *handle;
929 * Connections cannot be alloced from the inst or
930 * pool contexts due to threading issues.
932 handle = talloc_zero(ctx, rlm_sql_handle_t);
933 if (!handle) return NULL;
935 handle->log_ctx = talloc_pool(handle, 2048);
936 if (!handle->log_ctx) {
942 * Handle requires a pointer to the SQL inst so the
943 * destructor has access to the module configuration.
947 rcode = (inst->module->sql_socket_init)(handle, inst->config);
950 exec_trigger(NULL, inst->cs, "modules.sql.fail", true);
953 * Destroy any half opened connections.
959 if (inst->config->connect_query) {
960 if (rlm_sql_select_query(inst, NULL, &handle, inst->config->connect_query) != RLM_SQL_OK) goto fail;
961 (inst->module->sql_finish_select_query)(handle, inst->config);
968 static int mod_instantiate(CONF_SECTION *conf, void *instance)
970 rlm_sql_t *inst = instance;
973 * Complain if the strings exist, but are empty.
975 #define CHECK_STRING(_x) if (inst->config->_x && !inst->config->_x[0]) \
977 WARN("rlm_sql (%s): " STRINGIFY(_x) " is empty. Please delete it from the configuration", inst->name);\
978 inst->config->_x = NULL;\
981 CHECK_STRING(groupmemb_query);
982 CHECK_STRING(authorize_check_query);
983 CHECK_STRING(authorize_reply_query);
984 CHECK_STRING(authorize_group_check_query);
985 CHECK_STRING(authorize_group_reply_query);
986 CHECK_STRING(simul_count_query);
987 CHECK_STRING(simul_verify_query);
988 CHECK_STRING(connect_query);
989 CHECK_STRING(client_query);
990 if (strncmp(inst->config->sql_driver_name, "rlm_sql_", 8) != 0) {
991 ERROR("rlm_sql (%s): \"%s\" is NOT an SQL driver!", inst->name, inst->config->sql_driver_name);
996 * We need authorize_group_check_query or authorize_group_reply_query
997 * if group_membership_query is set.
999 * Or we need group_membership_query if authorize_group_check_query or
1000 * authorize_group_reply_query is set.
1002 if (!inst->config->groupmemb_query) {
1003 if (inst->config->authorize_group_check_query) {
1004 WARN("rlm_sql (%s): Ignoring authorize_group_reply_query as group_membership_query "
1005 "is not configured", inst->name);
1008 if (inst->config->authorize_group_reply_query) {
1009 WARN("rlm_sql (%s): Ignoring authorize_group_check_query as group_membership_query "
1010 "is not configured", inst->name);
1013 if (!inst->config->read_groups) {
1014 WARN("rlm_sql (%s): Ignoring read_groups as group_membership_query "
1015 "is not configured", inst->name);
1016 inst->config->read_groups = false;
1018 } /* allow the group check / reply queries to be NULL */
1021 * This will always exist, as cf_section_parse_init()
1022 * will create it if it doesn't exist. However, the
1023 * "reference" config item won't exist in an auto-created
1024 * configuration. So if that doesn't exist, we ignore
1025 * the whole subsection.
1027 inst->config->accounting.cs = cf_section_sub_find(conf, "accounting");
1028 inst->config->accounting.reference_cp = (cf_pair_find(inst->config->accounting.cs, "reference") != NULL);
1030 inst->config->postauth.cs = cf_section_sub_find(conf, "post-auth");
1031 inst->config->postauth.reference_cp = (cf_pair_find(inst->config->postauth.cs, "reference") != NULL);
1034 * Cache the SQL-User-Name DICT_ATTR, so we can be slightly
1035 * more efficient about creating SQL-User-Name attributes.
1037 inst->sql_user = dict_attrbyname("SQL-User-Name");
1038 if (!inst->sql_user) {
1043 * Export these methods, too. This avoids RTDL_GLOBAL.
1045 inst->sql_set_user = sql_set_user;
1046 inst->sql_escape_func = sql_escape_func;
1047 inst->sql_query = rlm_sql_query;
1048 inst->sql_select_query = rlm_sql_select_query;
1049 inst->sql_fetch_row = rlm_sql_fetch_row;
1051 if (inst->module->mod_instantiate) {
1055 name = strrchr(inst->config->sql_driver_name, '_');
1057 name = inst->config->sql_driver_name;
1062 cs = cf_section_sub_find(conf, name);
1064 cs = cf_section_alloc(conf, name, NULL);
1071 * It's up to the driver to register a destructor
1073 if (inst->module->mod_instantiate(cs, inst->config) < 0) {
1078 inst->ef = exfile_init(inst, 256, 30, true);
1080 cf_log_err_cs(conf, "Failed creating log file context");
1085 * Initialise the connection pool for this instance
1087 INFO("rlm_sql (%s): Attempting to connect to database \"%s\"", inst->name, inst->config->sql_db);
1089 inst->pool = fr_connection_pool_module_init(inst->cs, inst, mod_conn_create, NULL, NULL);
1090 if (!inst->pool) return -1;
1092 if (inst->config->do_clients) {
1093 if (generate_sql_clients(inst) == -1){
1094 ERROR("Failed to load clients from SQL");
1099 return RLM_MODULE_OK;
1102 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) CC_HINT(nonnull);
1103 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
1105 rlm_rcode_t rcode = RLM_MODULE_NOOP;
1107 rlm_sql_t *inst = instance;
1108 rlm_sql_handle_t *handle;
1110 VALUE_PAIR *check_tmp = NULL;
1111 VALUE_PAIR *reply_tmp = NULL;
1112 VALUE_PAIR *user_profile = NULL;
1114 bool user_found = false;
1116 sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT;
1120 char *expanded = NULL;
1122 rad_assert(request->packet != NULL);
1123 rad_assert(request->reply != NULL);
1125 if (!inst->config->authorize_check_query && !inst->config->authorize_reply_query &&
1126 !inst->config->read_groups && !inst->config->read_profiles) {
1127 RWDEBUG("No authorization checks configured, returning noop");
1129 return RLM_MODULE_NOOP;
1133 * Set, escape, and check the user attr here
1135 if (sql_set_user(inst, request, NULL) < 0) {
1136 return RLM_MODULE_FAIL;
1142 * After this point use goto error or goto release to cleanup socket temporary pairlists and
1143 * temporary attributes.
1145 handle = fr_connection_get(inst->pool);
1147 rcode = RLM_MODULE_FAIL;
1152 * Query the check table to find any conditions associated with this user/realm/whatever...
1154 if (inst->config->authorize_check_query) {
1158 if (radius_axlat(&expanded, request, inst->config->authorize_check_query,
1159 sql_escape_func, inst) < 0) {
1160 REDEBUG("Error generating query");
1161 rcode = RLM_MODULE_FAIL;
1165 rows = sql_getvpdata(request, inst, request, &handle, &check_tmp, expanded);
1166 TALLOC_FREE(expanded);
1168 REDEBUG("Error getting check attributes");
1169 rcode = RLM_MODULE_FAIL;
1173 if (rows == 0) goto skipreply; /* Don't need to free VPs we don't have */
1176 * Only do this if *some* check pairs were returned
1178 RDEBUG2("User found in radcheck table");
1180 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) {
1181 fr_pair_list_free(&check_tmp);
1186 RDEBUG2("Conditional check items matched, merging assignment check items");
1188 for (vp = fr_cursor_init(&cursor, &check_tmp);
1190 vp = fr_cursor_next(&cursor)) {
1191 if (!fr_assignment_op[vp->op]) continue;
1193 rdebug_pair(2, request, vp, NULL);
1196 radius_pairmove(request, &request->config, check_tmp, true);
1198 rcode = RLM_MODULE_OK;
1202 if (inst->config->authorize_reply_query) {
1204 * Now get the reply pairs since the paircompare matched
1206 if (radius_axlat(&expanded, request, inst->config->authorize_reply_query,
1207 sql_escape_func, inst) < 0) {
1208 REDEBUG("Error generating query");
1209 rcode = RLM_MODULE_FAIL;
1213 rows = sql_getvpdata(request->reply, inst, request, &handle, &reply_tmp, expanded);
1214 TALLOC_FREE(expanded);
1216 REDEBUG("SQL query error getting reply attributes");
1217 rcode = RLM_MODULE_FAIL;
1221 if (rows == 0) goto skipreply;
1223 do_fall_through = fall_through(reply_tmp);
1225 RDEBUG2("User found in radreply table, merging reply items");
1228 rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL);
1230 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
1232 rcode = RLM_MODULE_OK;
1237 * Neither group checks or profiles will work without
1238 * a group membership query.
1240 if (!inst->config->groupmemb_query) goto release;
1243 if ((do_fall_through == FALL_THROUGH_YES) ||
1244 (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1247 RDEBUG3("... falling-through to group processing");
1248 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1251 * Nothing bad happened, continue...
1253 case RLM_MODULE_UPDATED:
1254 rcode = RLM_MODULE_UPDATED;
1257 if (rcode != RLM_MODULE_UPDATED) {
1258 rcode = RLM_MODULE_OK;
1261 case RLM_MODULE_NOOP:
1265 case RLM_MODULE_NOTFOUND:
1275 * Repeat the above process with the default profile or User-Profile
1277 if ((do_fall_through == FALL_THROUGH_YES) ||
1278 (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1282 * Check for a default_profile or for a User-Profile.
1284 RDEBUG3("... falling-through to profile processing");
1285 user_profile = fr_pair_find_by_num(request->config, PW_USER_PROFILE, 0, TAG_ANY);
1287 char const *profile = user_profile ?
1288 user_profile->vp_strvalue :
1289 inst->config->default_profile;
1291 if (!profile || !*profile) {
1295 RDEBUG2("Checking profile %s", profile);
1297 if (sql_set_user(inst, request, profile) < 0) {
1298 REDEBUG("Error setting profile");
1299 rcode = RLM_MODULE_FAIL;
1303 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1306 * Nothing bad happened, continue...
1308 case RLM_MODULE_UPDATED:
1309 rcode = RLM_MODULE_UPDATED;
1312 if (rcode != RLM_MODULE_UPDATED) {
1313 rcode = RLM_MODULE_OK;
1316 case RLM_MODULE_NOOP:
1320 case RLM_MODULE_NOTFOUND:
1330 * At this point the key (user) hasn't be found in the check table, the reply table
1331 * or the group mapping table, and there was no matching profile.
1335 rcode = RLM_MODULE_NOTFOUND;
1338 fr_connection_release(inst->pool, handle);
1339 sql_unset_user(inst, request);
1344 fr_pair_list_free(&check_tmp);
1345 fr_pair_list_free(&reply_tmp);
1346 sql_unset_user(inst, request);
1348 fr_connection_release(inst->pool, handle);
1354 * Generic function for failing between a bunch of queries.
1356 * Uses the same principle as rlm_linelog, expanding the 'reference' config
1357 * item using xlat to figure out what query it should execute.
1359 * If the reference matches multiple config items, and a query fails or
1360 * doesn't update any rows, the next matching config item is used.
1363 static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section)
1365 rlm_rcode_t rcode = RLM_MODULE_OK;
1367 rlm_sql_handle_t *handle = NULL;
1369 int numaffected = 0;
1373 char const *attr = NULL;
1376 char path[MAX_STRING_LEN];
1378 char *expanded = NULL;
1380 rad_assert(section);
1382 if (section->reference[0] != '.') {
1386 if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) {
1387 rcode = RLM_MODULE_FAIL;
1393 * If we can't find a matching config item we do
1394 * nothing so return RLM_MODULE_NOOP.
1396 item = cf_reference_item(NULL, section->cs, path);
1398 RWDEBUG("No such configuration item %s", path);
1399 rcode = RLM_MODULE_NOOP;
1403 if (cf_item_is_section(item)){
1404 RWDEBUG("Sections are not supported as references");
1405 rcode = RLM_MODULE_NOOP;
1410 pair = cf_item_to_pair(item);
1411 attr = cf_pair_attr(pair);
1413 RDEBUG2("Using query template '%s'", attr);
1415 handle = fr_connection_get(inst->pool);
1417 rcode = RLM_MODULE_FAIL;
1422 sql_set_user(inst, request, NULL);
1425 value = cf_pair_value(pair);
1427 RDEBUG("Ignoring null query");
1428 rcode = RLM_MODULE_NOOP;
1433 if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) {
1434 rcode = RLM_MODULE_FAIL;
1440 RDEBUG("Ignoring null query");
1441 rcode = RLM_MODULE_NOOP;
1442 talloc_free(expanded);
1447 rlm_sql_query_log(inst, request, section, expanded);
1449 sql_ret = rlm_sql_query(inst, request, &handle, expanded);
1450 TALLOC_FREE(expanded);
1451 RDEBUG("SQL query returned: %s", fr_int2str(sql_rcode_table, sql_ret, "<INVALID>"));
1455 * Query was a success! Now we just need to check if it did anything.
1461 * A general, unrecoverable server fault.
1465 * If we get RLM_SQL_RECONNECT it means all connections in the pool
1466 * were exhausted, and we couldn't create a new connection,
1467 * so we do not need to call fr_connection_release.
1469 case RLM_SQL_RECONNECT:
1470 rcode = RLM_MODULE_FAIL;
1474 * Query was invalid, this is a terminal error, but we still need
1475 * to do cleanup, as the connection handle is still valid.
1477 case RLM_SQL_QUERY_INVALID:
1478 rcode = RLM_MODULE_INVALID;
1482 * Driver found an error (like a unique key constraint violation)
1483 * that hinted it might be a good idea to try an alternative query.
1485 case RLM_SQL_ALT_QUERY:
1491 * We need to have updated something for the query to have been
1492 * counted as successful.
1494 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
1495 (inst->module->sql_finish_query)(handle, inst->config);
1496 RDEBUG("%i record(s) updated", numaffected);
1498 if (numaffected > 0) break; /* A query succeeded, were done! */
1501 * We assume all entries with the same name form a redundant
1504 pair = cf_pair_find_next(section->cs, pair, attr);
1507 RDEBUG("No additional queries configured");
1508 rcode = RLM_MODULE_NOOP;
1513 RDEBUG("Trying next query...");
1518 talloc_free(expanded);
1519 fr_connection_release(inst->pool, handle);
1520 sql_unset_user(inst, request);
1525 #ifdef WITH_ACCOUNTING
1528 * Accounting: Insert or update session data in our sql table
1530 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) CC_HINT(nonnull);
1531 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request)
1533 rlm_sql_t *inst = instance;
1535 if (inst->config->accounting.reference_cp) {
1536 return acct_redundant(inst, request, &inst->config->accounting);
1539 return RLM_MODULE_NOOP;
1544 #ifdef WITH_SESSION_MGMT
1546 * See if a user is already logged in. Sets request->simul_count to the
1547 * current session count for this user.
1549 * Check twice. If on the first pass the user exceeds his
1550 * max. number of logins, do a second pass and validate all
1551 * logins by querying the terminal server (using eg. SNMP).
1553 static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) CC_HINT(nonnull);
1554 static rlm_rcode_t mod_checksimul(void *instance, REQUEST * request)
1556 rlm_rcode_t rcode = RLM_MODULE_OK;
1557 rlm_sql_handle_t *handle = NULL;
1558 rlm_sql_t *inst = instance;
1562 char const *call_num = NULL;
1565 uint32_t nas_addr = 0;
1566 uint32_t nas_port = 0;
1568 char *expanded = NULL;
1570 /* If simul_count_query is not defined, we don't do any checking */
1571 if (!inst->config->simul_count_query) {
1572 RWDEBUG("Simultaneous-Use checking requires 'simul_count_query' to be configured");
1573 return RLM_MODULE_NOOP;
1576 if ((!request->username) || (request->username->vp_length == 0)) {
1577 REDEBUG("Zero Length username not permitted");
1579 return RLM_MODULE_INVALID;
1582 if (sql_set_user(inst, request, NULL) < 0) {
1583 return RLM_MODULE_FAIL;
1586 if (radius_axlat(&expanded, request, inst->config->simul_count_query, sql_escape_func, inst) < 0) {
1587 sql_unset_user(inst, request);
1588 return RLM_MODULE_FAIL;
1591 /* initialize the sql socket */
1592 handle = fr_connection_get(inst->pool);
1594 talloc_free(expanded);
1595 sql_unset_user(inst, request);
1596 return RLM_MODULE_FAIL;
1599 if (rlm_sql_select_query(inst, request, &handle, expanded) != RLM_SQL_OK) {
1600 rcode = RLM_MODULE_FAIL;
1601 goto release; /* handle may no longer be valid */
1604 ret = rlm_sql_fetch_row(inst, request, &handle);
1606 rcode = RLM_MODULE_FAIL;
1612 rcode = RLM_MODULE_FAIL;
1616 request->simul_count = atoi(row[0]);
1618 (inst->module->sql_finish_select_query)(handle, inst->config);
1619 TALLOC_FREE(expanded);
1621 if (request->simul_count < request->simul_max) {
1622 rcode = RLM_MODULE_OK;
1627 * Looks like too many sessions, so let's start verifying
1628 * them, unless told to rely on count query only.
1630 if (!inst->config->simul_verify_query) {
1631 rcode = RLM_MODULE_OK;
1636 if (radius_axlat(&expanded, request, inst->config->simul_verify_query, sql_escape_func, inst) < 0) {
1637 rcode = RLM_MODULE_FAIL;
1642 if (rlm_sql_select_query(inst, request, &handle, expanded) != RLM_SQL_OK) goto release;
1645 * Setup some stuff, like for MPP detection.
1647 request->simul_count = 0;
1649 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
1650 ipno = vp->vp_ipaddr;
1653 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
1654 call_num = vp->vp_strvalue;
1657 while (rlm_sql_fetch_row(inst, request, &handle) == RLM_SQL_OK) {
1665 num_rows = (inst->module->sql_num_fields)(handle, inst->config);
1667 RDEBUG("Too few rows returned. Please do not edit 'simul_verify_query'");
1668 rcode = RLM_MODULE_FAIL;
1674 RDEBUG("Cannot zap stale entry. No username present in entry");
1675 rcode = RLM_MODULE_FAIL;
1681 RDEBUG("Cannot zap stale entry. No session id in entry");
1682 rcode = RLM_MODULE_FAIL;
1688 nas_addr = inet_addr(row[3]);
1692 nas_port = atoi(row[4]);
1695 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1698 * Stale record - zap it.
1700 if (inst->config->delete_stale_sessions == true) {
1701 uint32_t framed_addr = 0;
1706 framed_addr = inet_addr(row[5]);
1708 if (strcmp(row[7], "PPP") == 0)
1710 else if (strcmp(row[7], "SLIP") == 0)
1713 if ((num_rows > 8) && row[8])
1714 sess_time = atoi(row[8]);
1715 session_zap(request, nas_addr, nas_port,
1716 row[2], row[1], framed_addr,
1720 else if (check == 1) {
1722 * User is still logged in.
1724 ++request->simul_count;
1727 * Does it look like a MPP attempt?
1729 if (row[5] && ipno && inet_addr(row[5]) == ipno) {
1730 request->simul_mpp = 2;
1731 } else if (row[6] && call_num && !strncmp(row[6],call_num,16)) {
1732 request->simul_mpp = 2;
1736 * Failed to check the terminal server for
1737 * duplicate logins: return an error.
1739 REDEBUG("Failed to check the terminal server for user '%s'.", row[2]);
1741 rcode = RLM_MODULE_FAIL;
1747 (inst->module->sql_finish_select_query)(handle, inst->config);
1749 fr_connection_release(inst->pool, handle);
1750 talloc_free(expanded);
1751 sql_unset_user(inst, request);
1754 * The Auth module apparently looks at request->simul_count,
1755 * not the return value of this module when deciding to deny
1756 * a call for too many sessions.
1763 * Postauth: Write a record of the authentication attempt
1765 static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request) CC_HINT(nonnull);
1766 static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request)
1768 rlm_sql_t *inst = instance;
1770 if (inst->config->postauth.reference_cp) {
1771 return acct_redundant(inst, request, &inst->config->postauth);
1774 return RLM_MODULE_NOOP;
1778 * Execute postauth_query after authentication
1782 /* globally exported name */
1783 extern module_t rlm_sql;
1784 module_t rlm_sql = {
1785 .magic = RLM_MODULE_INIT,
1787 .type = RLM_TYPE_THREAD_SAFE,
1788 .inst_size = sizeof(rlm_sql_t),
1789 .config = module_config,
1790 .bootstrap = mod_bootstrap,
1791 .instantiate = mod_instantiate,
1792 .detach = mod_detach,
1794 [MOD_AUTHORIZE] = mod_authorize,
1795 #ifdef WITH_ACCOUNTING
1796 [MOD_ACCOUNTING] = mod_accounting,
1798 #ifdef WITH_SESSION_MGMT
1799 [MOD_SESSION] = mod_checksimul,
1801 [MOD_POST_AUTH] = mod_post_auth