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 {NULL, -1, 0, NULL, NULL}
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 {NULL, -1, 0, NULL, NULL}
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 {NULL, -1, 0, NULL, NULL}
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 },
79 {NULL, -1, 0, NULL, NULL}
82 static const CONF_PARSER module_config[] = {
83 { "driver", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_driver_name), "rlm_sql_null" },
84 { "server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_server), "localhost" },
85 { "port", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_port), "" },
86 { "login", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_login), "" },
87 { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_sql_config_t, sql_password), "" },
88 { "radius_db", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_db), "radius" },
89 { "read_groups", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_groups), "yes" },
90 { "read_profiles", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_profiles), "yes" },
91 { "readclients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, do_clients), NULL },
92 { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, do_clients), "no" },
93 { "deletestalesessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, delete_stale_sessions), NULL },
94 { "delete_stale_sessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, delete_stale_sessions), "yes" },
95 { "sql_user_name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, query_user), "" },
96 { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, logfile), NULL },
97 { "default_user_profile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, default_profile), "" },
98 { "nas_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, client_query), NULL },
99 { "client_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, client_query), "SELECT id,nasname,shortname,type,secret FROM nas" },
100 { "open_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, connect_query), NULL },
102 { "authorize_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_check_query), NULL },
103 { "authorize_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_reply_query), NULL },
105 { "authorize_group_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_check_query), NULL },
106 { "authorize_group_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_reply_query), NULL },
107 { "group_membership_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, groupmemb_query), NULL },
108 #ifdef WITH_SESSION_MGMT
109 { "simul_count_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_count_query), NULL },
110 { "simul_verify_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_verify_query), NULL },
112 { "safe-characters", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, allowed_chars), NULL },
113 { "safe_characters", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, allowed_chars), "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" },
116 * This only works for a few drivers.
118 { "query_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_config_t, query_timeout), NULL },
120 { "accounting", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) acct_config },
122 { "post-auth", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) postauth_config },
124 {NULL, -1, 0, NULL, NULL}
128 * Fall-Through checking function from rlm_files.c
130 static sql_fall_through_t fall_through(VALUE_PAIR *vp)
133 tmp = pairfind(vp, PW_FALL_THROUGH, 0, TAG_ANY);
135 return tmp ? tmp->vp_integer : FALL_THROUGH_DEFAULT;
141 static int generate_sql_clients(rlm_sql_t *inst);
142 static size_t sql_escape_func(REQUEST *, char *out, size_t outlen, char const *in, void *arg);
147 * For selects the first value of the first column will be returned,
148 * for inserts, updates and deletes the number of rows affected will be
151 static ssize_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace)
153 rlm_sql_handle_t *handle = NULL;
155 rlm_sql_t *inst = instance;
161 * Add SQL-User-Name attribute just in case it is needed
162 * We could search the string fmt for SQL-User-Name to see if this is
165 sql_set_user(inst, request, NULL);
167 handle = fr_connection_get(inst->pool); /* connection pool should produce error */
168 if (!handle) return 0;
170 rlm_sql_query_log(inst, request, NULL, query);
173 * If the query starts with any of the following prefixes,
174 * then return the number of rows affected
176 if ((strncasecmp(query, "insert", 6) == 0) ||
177 (strncasecmp(query, "update", 6) == 0) ||
178 (strncasecmp(query, "delete", 6) == 0)) {
180 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
182 rcode = rlm_sql_query(inst, request, &handle, query);
183 if (rcode != RLM_SQL_OK) {
185 RERROR("SQL query failed: %s", fr_int2str(sql_rcode_table, rcode, "<INVALID>"));
191 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
192 if (numaffected < 1) {
193 RDEBUG("SQL query affected no rows");
199 * Don't chop the returned number if freespace is
200 * too small. This hack is necessary because
201 * some implementations of snprintf return the
202 * size of the written data, and others return
203 * the size of the data they *would* have written
204 * if the output buffer was large enough.
206 snprintf(buffer, sizeof(buffer), "%d", numaffected);
208 len = strlen(buffer);
209 if (len >= freespace){
210 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", inst->name);
212 (inst->module->sql_finish_query)(handle, inst->config);
218 memcpy(out, buffer, len + 1); /* we did bounds checking above */
221 (inst->module->sql_finish_query)(handle, inst->config);
224 } /* else it's a SELECT statement */
226 rcode = rlm_sql_select_query(inst, request, &handle, query);
227 if (rcode != RLM_SQL_OK) goto query_error;
229 rcode = rlm_sql_fetch_row(inst, request, &handle);
231 (inst->module->sql_finish_select_query)(handle, inst->config);
237 RDEBUG("SQL query returned no results");
238 (inst->module->sql_finish_select_query)(handle, inst->config);
245 RDEBUG("NULL value in first column of result");
246 (inst->module->sql_finish_select_query)(handle, inst->config);
252 len = strlen(row[0]);
253 if (len >= freespace){
254 RDEBUG("Insufficient string space");
255 (inst->module->sql_finish_select_query)(handle, inst->config);
261 strlcpy(out, row[0], freespace);
264 (inst->module->sql_finish_select_query)(handle, inst->config);
267 fr_connection_release(inst->pool, handle);
272 static int generate_sql_clients(rlm_sql_t *inst)
274 rlm_sql_handle_t *handle;
279 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
282 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
283 inst->name, inst->config->client_query);
285 handle = fr_connection_get(inst->pool);
286 if (!handle) return -1;
288 if (rlm_sql_select_query(inst, NULL, &handle, inst->config->client_query) != RLM_SQL_OK) return -1;
290 while ((rlm_sql_fetch_row(inst, NULL, &handle) == 0) && (row = handle->row)) {
295 * The return data for each row MUST be in the following order:
297 * 0. Row ID (currently unused)
298 * 1. Name (or IP address)
302 * 5. Virtual Server (optional)
305 ERROR("rlm_sql (%s): No row id found on pass %d",inst->name,i);
309 ERROR("rlm_sql (%s): No nasname found for row %s",inst->name,row[0]);
313 ERROR("rlm_sql (%s): No short name found for row %s",inst->name,row[0]);
317 ERROR("rlm_sql (%s): No secret found for row %s",inst->name,row[0]);
321 if (((inst->module->sql_num_fields)(handle, inst->config) > 5) && (row[5] != NULL) && *row[5]) {
325 DEBUG("rlm_sql (%s): Adding client %s (%s) to %s clients list",
327 row[1], row[2], server ? server : "global");
329 /* FIXME: We should really pass a proper ctx */
330 c = client_afrom_query(NULL,
331 row[1], /* identifier */
333 row[2], /* shortname */
336 false); /* require message authenticator */
341 if (!client_add(NULL, c)) {
342 WARN("Failed to add client, possible duplicate?");
348 DEBUG("rlm_sql (%s): Client \"%s\" (%s) added", c->longname, c->shortname,
352 (inst->module->sql_finish_select_query)(handle, inst->config);
353 fr_connection_release(inst->pool, handle);
360 * Translate the SQL queries.
362 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen,
363 char const *in, void *arg)
365 rlm_sql_t *inst = arg;
372 * Allow all multi-byte UTF8 characters.
374 utf8_len = fr_utf8_char((uint8_t const *) in);
376 if (outlen <= utf8_len) break;
378 memcpy(out, in, utf8_len);
388 * Because we register our own escape function
389 * we're now responsible for escaping all special
390 * chars in an xlat expansion or attribute value.
394 if (outlen <= 2) break;
405 if (outlen <= 2) break;
416 if (outlen <= 2) break;
428 * Non-printable characters get replaced with their
429 * mime-encoded equivalents.
432 strchr(inst->config->allowed_chars, *in) == NULL) {
434 * Only 3 or less bytes available.
440 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
449 * Only one byte left.
469 * Set the SQL user name.
471 * We don't call the escape function here. The resulting string
472 * will be escaped later in the queries xlat so we don't need to
473 * escape it twice. (it will make things wrong if we have an
474 * escape candidate character in the username)
476 int sql_set_user(rlm_sql_t *inst, REQUEST *request, char const *username)
478 char *expanded = NULL;
479 VALUE_PAIR *vp = NULL;
483 rad_assert(request->packet != NULL);
485 if (username != NULL) {
487 } else if (inst->config->query_user[0] != '\0') {
488 sqluser = inst->config->query_user;
493 len = radius_axlat(&expanded, request, sqluser, NULL, NULL);
498 vp = pairalloc(request->packet, inst->sql_user);
500 talloc_free(expanded);
504 pairstrsteal(vp, expanded);
505 RDEBUG2("SQL-User-Name set to '%s'", vp->vp_strvalue);
507 radius_pairmove(request, &request->packet->vps, vp, false); /* needs to be pair move else op is not respected */
513 * Do a set/unset user, so it's a bit clearer what's going on.
515 #define sql_unset_user(_i, _r) pairdelete(&_r->packet->vps, _i->sql_user->attr, _i->sql_user->vendor, TAG_ANY)
517 static int sql_get_grouplist(rlm_sql_t *inst, rlm_sql_handle_t **handle, REQUEST *request,
518 rlm_sql_grouplist_t **phead)
520 char *expanded = NULL;
523 rlm_sql_grouplist_t *entry;
526 /* NOTE: sql_set_user should have been run before calling this function */
528 entry = *phead = NULL;
530 if (!inst->config->groupmemb_query) return 0;
532 if (radius_axlat(&expanded, request, inst->config->groupmemb_query, sql_escape_func, inst) < 0) return -1;
534 ret = rlm_sql_select_query(inst, request, handle, expanded);
535 talloc_free(expanded);
536 if (ret != RLM_SQL_OK) return -1;
538 while (rlm_sql_fetch_row(inst, request, handle) == 0) {
539 row = (*handle)->row;
544 RDEBUG("row[0] returned NULL");
545 (inst->module->sql_finish_select_query)(*handle, inst->config);
551 *phead = talloc_zero(*handle, rlm_sql_grouplist_t);
554 entry->next = talloc_zero(*phead, rlm_sql_grouplist_t);
558 entry->name = talloc_typed_strdup(entry, row[0]);
563 (inst->module->sql_finish_select_query)(*handle, inst->config);
570 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
571 * with the group memberships reciding in sql
572 * The group membership query should only return one element which is the username. The returned
573 * username will then be checked with the passed check string.
576 static int CC_HINT(nonnull (1, 2, 4)) sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp,
577 VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs,
578 UNUSED VALUE_PAIR **reply_pairs)
580 rlm_sql_handle_t *handle;
581 rlm_sql_t *inst = instance;
582 rlm_sql_grouplist_t *head, *entry;
584 RDEBUG("sql_groupcmp");
586 if (check->vp_length == 0){
587 RDEBUG("sql_groupcmp: Illegal group name");
592 * Set, escape, and check the user attr here
594 if (sql_set_user(inst, request, NULL) < 0)
598 * Get a socket for this lookup
600 handle = fr_connection_get(inst->pool);
606 * Get the list of groups this user is a member of
608 if (sql_get_grouplist(inst, &handle, request, &head) < 0) {
609 REDEBUG("Error getting group membership");
610 fr_connection_release(inst->pool, handle);
614 for (entry = head; entry != NULL; entry = entry->next) {
615 if (strcmp(entry->name, check->vp_strvalue) == 0){
616 RDEBUG("sql_groupcmp finished: User is a member of group %s",
619 fr_connection_release(inst->pool, handle);
624 /* Free the grouplist */
626 fr_connection_release(inst->pool, handle);
628 RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", check->vp_strvalue);
633 static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
634 sql_fall_through_t *do_fall_through)
636 rlm_rcode_t rcode = RLM_MODULE_NOOP;
637 VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL;
638 rlm_sql_grouplist_t *head = NULL, *entry = NULL;
640 char *expanded = NULL;
643 rad_assert(request->packet != NULL);
646 * Get the list of groups this user is a member of
648 rows = sql_get_grouplist(inst, handle, request, &head);
650 REDEBUG("Error retrieving group list");
652 return RLM_MODULE_FAIL;
655 RDEBUG2("User not found in any groups");
656 rcode = RLM_MODULE_NOTFOUND;
657 *do_fall_through = FALL_THROUGH_DEFAULT;
663 RDEBUG2("User found in the group table");
666 * Add the Sql-Group attribute to the request list so we know
667 * which group we're retrieving attributes for
669 sql_group = pairmake_packet("Sql-Group", NULL, T_OP_EQ);
671 REDEBUG("Error creating Sql-Group attribute");
672 rcode = RLM_MODULE_FAIL;
679 rad_assert(entry != NULL);
680 pairstrcpy(sql_group, entry->name);
682 if (inst->config->authorize_group_check_query) {
687 * Expand the group query
689 if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query,
690 sql_escape_func, inst) < 0) {
691 REDEBUG("Error generating query");
692 rcode = RLM_MODULE_FAIL;
696 rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded);
697 TALLOC_FREE(expanded);
699 REDEBUG("Error retrieving check pairs for group %s", entry->name);
700 rcode = RLM_MODULE_FAIL;
705 * If we got check rows we need to process them before we decide to
706 * process the reply rows
709 (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) {
710 pairfree(&check_tmp);
713 goto next; /* != continue */
716 RDEBUG2("Group \"%s\": Conditional check items matched", entry->name);
717 rcode = RLM_MODULE_OK;
719 RDEBUG2("Group \"%s\": Merging assignment check items", entry->name);
721 for (vp = fr_cursor_init(&cursor, &check_tmp);
723 vp = fr_cursor_next(&cursor)) {
724 if (!fr_assignment_op[vp->op]) continue;
726 rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
729 radius_pairmove(request, &request->config_items, check_tmp, true);
733 if (inst->config->authorize_group_reply_query) {
735 * Now get the reply pairs since the paircompare matched
737 if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query,
738 sql_escape_func, inst) < 0) {
739 REDEBUG("Error generating query");
740 rcode = RLM_MODULE_FAIL;
744 rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded);
745 TALLOC_FREE(expanded);
747 REDEBUG("Error retrieving reply pairs for group %s", entry->name);
748 rcode = RLM_MODULE_FAIL;
751 *do_fall_through = fall_through(reply_tmp);
753 RDEBUG2("Group \"%s\": Merging reply items", entry->name);
754 rcode = RLM_MODULE_OK;
756 rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL);
758 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
761 * If there's no reply query configured, then we assume
762 * FALL_THROUGH_NO, which is the same as the users file if you
763 * had no reply attributes.
766 *do_fall_through = FALL_THROUGH_DEFAULT;
770 } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES));
774 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
780 static int mod_detach(void *instance)
782 rlm_sql_t *inst = instance;
784 if (inst->pool) fr_connection_pool_delete(inst->pool);
787 * We need to explicitly free all children, so if the driver
788 * parented any memory off the instance, their destructors
789 * run before we unload the bytecode for them.
791 * If we don't do this, we get a SEGV deep inside the talloc code
792 * when it tries to call a destructor that no longer exists.
794 talloc_free_children(inst);
797 * Decrements the reference count. The driver object won't be unloaded
798 * until all instances of rlm_sql that use it have been destroyed.
800 if (inst->handle) dlclose(inst->handle);
805 static int mod_instantiate(CONF_SECTION *conf, void *instance)
807 rlm_sql_t *inst = instance;
812 inst->config = &inst->myconfig;
815 inst->name = cf_section_name2(conf);
817 inst->name = cf_section_name1(conf);
824 * Allocate room for <instance>-SQL-Group
826 group_name = talloc_typed_asprintf(inst, "%s-SQL-Group", inst->name);
827 DEBUG("rlm_sql (%s): Creating new attribute %s",
828 inst->name, group_name);
830 memset(&flags, 0, sizeof(flags));
831 if (dict_addattr(group_name, -1, 0, PW_TYPE_STRING, flags) < 0) {
832 ERROR("rlm_sql (%s): Failed to create "
833 "attribute %s: %s", inst->name, group_name,
838 da = dict_attrbyname(group_name);
840 ERROR("rlm_sql (%s): Failed to create "
841 "attribute %s", inst->name, group_name);
845 if (inst->config->groupmemb_query) {
846 DEBUG("rlm_sql (%s): Registering sql_groupcmp for %s",
847 inst->name, group_name);
848 paircompare_register(da, dict_attrbyvalue(PW_USER_NAME, 0),
849 false, sql_groupcmp, inst);
853 rad_assert(inst->name);
856 * Complain if the strings exist, but are empty.
858 #define CHECK_STRING(_x) if (inst->config->_x && !inst->config->_x[0]) \
860 WARN("rlm_sql (%s): " STRINGIFY(_x) " is empty. Please delete it from the configuration", inst->name);\
861 inst->config->_x = NULL;\
864 CHECK_STRING(groupmemb_query);
865 CHECK_STRING(authorize_check_query);
866 CHECK_STRING(authorize_reply_query);
867 CHECK_STRING(authorize_group_check_query);
868 CHECK_STRING(authorize_group_reply_query);
869 CHECK_STRING(simul_count_query);
870 CHECK_STRING(simul_verify_query);
871 CHECK_STRING(connect_query);
872 CHECK_STRING(client_query);
873 if (strncmp(inst->config->sql_driver_name, "rlm_sql_", 8) != 0) {
874 ERROR("rlm_sql (%s): \"%s\" is NOT an SQL driver!", inst->name, inst->config->sql_driver_name);
879 * We need authorize_group_check_query or authorize_group_reply_query
880 * if group_membership_query is set.
882 * Or we need group_membership_query if authorize_group_check_query or
883 * authorize_group_reply_query is set.
885 if (!inst->config->groupmemb_query) {
886 if (inst->config->authorize_group_check_query) {
887 WARN("rlm_sql (%s): Ignoring authorize_group_reply_query as group_membership_query "
888 "is not configured", inst->name);
891 if (inst->config->authorize_group_reply_query) {
892 WARN("rlm_sql (%s): Ignoring authorize_group_check_query as group_membership_query "
893 "is not configured", inst->name);
896 if (!inst->config->authorize_group_check_query) {
897 ERROR("rlm_sql (%s): authorize_group_check_query must be configured as group_membership_query "
898 "is configured", inst->name);
902 if (!inst->config->authorize_group_reply_query) {
903 ERROR("rlm_sql (%s): authorize_group_reply_query must be configured as group_membership_query "
904 "is configured", inst->name);
910 * This will always exist, as cf_section_parse_init()
911 * will create it if it doesn't exist. However, the
912 * "reference" config item won't exist in an auto-created
913 * configuration. So if that doesn't exist, we ignore
914 * the whole subsection.
916 inst->config->accounting.cs = cf_section_sub_find(conf, "accounting");
917 inst->config->accounting.reference_cp = (cf_pair_find(inst->config->accounting.cs, "reference") != NULL);
919 inst->config->postauth.cs = cf_section_sub_find(conf, "post-auth");
920 inst->config->postauth.reference_cp = (cf_pair_find(inst->config->postauth.cs, "reference") != NULL);
923 * Cache the SQL-User-Name DICT_ATTR, so we can be slightly
924 * more efficient about creating SQL-User-Name attributes.
926 inst->sql_user = dict_attrbyname("SQL-User-Name");
927 if (!inst->sql_user) {
932 * Export these methods, too. This avoids RTDL_GLOBAL.
934 inst->sql_set_user = sql_set_user;
935 inst->sql_escape_func = sql_escape_func;
936 inst->sql_query = rlm_sql_query;
937 inst->sql_select_query = rlm_sql_select_query;
938 inst->sql_fetch_row = rlm_sql_fetch_row;
941 * Register the SQL xlat function
943 xlat_register(inst->name, sql_xlat, sql_escape_func, inst);
946 * Load the appropriate driver for our database
948 inst->handle = lt_dlopenext(inst->config->sql_driver_name);
950 ERROR("Could not link driver %s: %s", inst->config->sql_driver_name, dlerror());
951 ERROR("Make sure it (and all its dependent libraries!) are in the search path of your system's ld");
955 inst->module = (rlm_sql_module_t *) dlsym(inst->handle,
956 inst->config->sql_driver_name);
958 ERROR("Could not link symbol %s: %s", inst->config->sql_driver_name, dlerror());
962 if (inst->module->mod_instantiate) {
966 name = strrchr(inst->config->sql_driver_name, '_');
968 name = inst->config->sql_driver_name;
973 cs = cf_section_sub_find(conf, name);
975 cs = cf_section_alloc(conf, name, NULL);
982 * It's up to the driver to register a destructor
984 if (inst->module->mod_instantiate(cs, inst->config) < 0) {
989 inst->ef = exfile_init(inst, 64, 30);
991 cf_log_err_cs(conf, "Failed creating log file context");
995 INFO("rlm_sql (%s): Driver %s (module %s) loaded and linked", inst->name,
996 inst->config->sql_driver_name, inst->module->name);
999 * Initialise the connection pool for this instance
1001 INFO("rlm_sql (%s): Attempting to connect to database \"%s\"", inst->name, inst->config->sql_db);
1003 inst->pool = fr_connection_pool_module_init(inst->cs, inst, mod_conn_create, NULL, NULL);
1004 if (!inst->pool) return -1;
1006 if (inst->config->groupmemb_query) {
1007 paircompare_register(dict_attrbyvalue(PW_SQL_GROUP, 0),
1008 dict_attrbyvalue(PW_USER_NAME, 0), false, sql_groupcmp, inst);
1011 if (inst->config->do_clients) {
1012 if (generate_sql_clients(inst) == -1){
1013 ERROR("Failed to load clients from SQL");
1018 return RLM_MODULE_OK;
1022 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
1024 rlm_rcode_t rcode = RLM_MODULE_NOOP;
1026 rlm_sql_t *inst = instance;
1027 rlm_sql_handle_t *handle;
1029 VALUE_PAIR *check_tmp = NULL;
1030 VALUE_PAIR *reply_tmp = NULL;
1031 VALUE_PAIR *user_profile = NULL;
1033 bool user_found = false;
1035 sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT;
1039 char *expanded = NULL;
1041 rad_assert(request->packet != NULL);
1042 rad_assert(request->reply != NULL);
1044 if (!inst->config->authorize_check_query && !inst->config->authorize_reply_query &&
1045 !inst->config->read_groups && !inst->config->read_profiles) {
1046 RWDEBUG("No authorization checks configured, returning noop");
1048 return RLM_MODULE_NOOP;
1052 * Set, escape, and check the user attr here
1054 if (sql_set_user(inst, request, NULL) < 0) {
1055 return RLM_MODULE_FAIL;
1061 * After this point use goto error or goto release to cleanup socket temporary pairlists and
1062 * temporary attributes.
1064 handle = fr_connection_get(inst->pool);
1066 rcode = RLM_MODULE_FAIL;
1071 * Query the check table to find any conditions associated with this user/realm/whatever...
1073 if (inst->config->authorize_check_query) {
1077 if (radius_axlat(&expanded, request, inst->config->authorize_check_query,
1078 sql_escape_func, inst) < 0) {
1079 REDEBUG("Error generating query");
1080 rcode = RLM_MODULE_FAIL;
1084 rows = sql_getvpdata(request, inst, request, &handle, &check_tmp, expanded);
1085 TALLOC_FREE(expanded);
1087 REDEBUG("Error getting check attributes");
1088 rcode = RLM_MODULE_FAIL;
1092 if (rows == 0) goto skipreply; /* Don't need to free VPs we don't have */
1095 * Only do this if *some* check pairs were returned
1097 RDEBUG2("User found in radcheck table");
1099 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) {
1100 pairfree(&check_tmp);
1105 RDEBUG2("Conditional check items matched, merging assignment check items");
1107 for (vp = fr_cursor_init(&cursor, &check_tmp);
1109 vp = fr_cursor_next(&cursor)) {
1110 if (!fr_assignment_op[vp->op]) continue;
1112 rdebug_pair(2, request, vp, NULL);
1115 radius_pairmove(request, &request->config_items, check_tmp, true);
1117 rcode = RLM_MODULE_OK;
1121 if (inst->config->authorize_reply_query) {
1123 * Now get the reply pairs since the paircompare matched
1125 if (radius_axlat(&expanded, request, inst->config->authorize_reply_query,
1126 sql_escape_func, inst) < 0) {
1127 REDEBUG("Error generating query");
1128 rcode = RLM_MODULE_FAIL;
1132 rows = sql_getvpdata(request->reply, inst, request, &handle, &reply_tmp, expanded);
1133 TALLOC_FREE(expanded);
1135 REDEBUG("SQL query error getting reply attributes");
1136 rcode = RLM_MODULE_FAIL;
1140 if (rows == 0) goto skipreply;
1142 do_fall_through = fall_through(reply_tmp);
1144 RDEBUG2("User found in radreply table, merging reply items");
1147 rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL);
1149 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
1151 rcode = RLM_MODULE_OK;
1156 * Neither group checks or profiles will work without
1157 * a group membership query.
1159 if (!inst->config->groupmemb_query) goto release;
1162 if ((do_fall_through == FALL_THROUGH_YES) ||
1163 (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1166 RDEBUG3("... falling-through to group processing");
1167 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1170 * Nothing bad happened, continue...
1172 case RLM_MODULE_UPDATED:
1173 rcode = RLM_MODULE_UPDATED;
1176 if (rcode != RLM_MODULE_UPDATED) {
1177 rcode = RLM_MODULE_OK;
1180 case RLM_MODULE_NOOP:
1184 case RLM_MODULE_NOTFOUND:
1194 * Repeat the above process with the default profile or User-Profile
1196 if ((do_fall_through == FALL_THROUGH_YES) ||
1197 (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1201 * Check for a default_profile or for a User-Profile.
1203 RDEBUG3("... falling-through to profile processing");
1204 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY);
1206 char const *profile = user_profile ?
1207 user_profile->vp_strvalue :
1208 inst->config->default_profile;
1210 if (!profile || !*profile) {
1214 RDEBUG2("Checking profile %s", profile);
1216 if (sql_set_user(inst, request, profile) < 0) {
1217 REDEBUG("Error setting profile");
1218 rcode = RLM_MODULE_FAIL;
1222 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1225 * Nothing bad happened, continue...
1227 case RLM_MODULE_UPDATED:
1228 rcode = RLM_MODULE_UPDATED;
1231 if (rcode != RLM_MODULE_UPDATED) {
1232 rcode = RLM_MODULE_OK;
1235 case RLM_MODULE_NOOP:
1239 case RLM_MODULE_NOTFOUND:
1249 * At this point the key (user) hasn't be found in the check table, the reply table
1250 * or the group mapping table, and there was no matching profile.
1254 rcode = RLM_MODULE_NOTFOUND;
1257 fr_connection_release(inst->pool, handle);
1258 sql_unset_user(inst, request);
1263 pairfree(&check_tmp);
1264 pairfree(&reply_tmp);
1265 sql_unset_user(inst, request);
1267 fr_connection_release(inst->pool, handle);
1273 * Generic function for failing between a bunch of queries.
1275 * Uses the same principle as rlm_linelog, expanding the 'reference' config
1276 * item using xlat to figure out what query it should execute.
1278 * If the reference matches multiple config items, and a query fails or
1279 * doesn't update any rows, the next matching config item is used.
1282 static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section)
1284 rlm_rcode_t rcode = RLM_MODULE_OK;
1286 rlm_sql_handle_t *handle = NULL;
1288 int numaffected = 0;
1292 char const *attr = NULL;
1295 char path[MAX_STRING_LEN];
1297 char *expanded = NULL;
1299 rad_assert(section);
1301 if (section->reference[0] != '.') {
1305 if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) {
1306 rcode = RLM_MODULE_FAIL;
1311 item = cf_reference_item(NULL, section->cs, path);
1313 rcode = RLM_MODULE_FAIL;
1318 if (cf_item_is_section(item)){
1319 REDEBUG("Sections are not supported as references");
1320 rcode = RLM_MODULE_FAIL;
1325 pair = cf_item_to_pair(item);
1326 attr = cf_pair_attr(pair);
1328 RDEBUG2("Using query template '%s'", attr);
1330 handle = fr_connection_get(inst->pool);
1332 rcode = RLM_MODULE_FAIL;
1337 sql_set_user(inst, request, NULL);
1340 value = cf_pair_value(pair);
1342 RDEBUG("Ignoring null query");
1343 rcode = RLM_MODULE_NOOP;
1348 if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) {
1349 rcode = RLM_MODULE_FAIL;
1355 RDEBUG("Ignoring null query");
1356 rcode = RLM_MODULE_NOOP;
1357 talloc_free(expanded);
1362 rlm_sql_query_log(inst, request, section, expanded);
1364 sql_ret = rlm_sql_query(inst, request, &handle, expanded);
1365 TALLOC_FREE(expanded);
1366 RDEBUG("SQL query returned: %s", fr_int2str(sql_rcode_table, sql_ret, "<INVALID>"));
1370 * Query was a success! Now we just need to check if it did anything.
1376 * A general, unrecoverable server fault.
1380 * If we get RLM_SQL_RECONNECT it means all connections in the pool
1381 * were exhausted, and we couldn't create a new connection,
1382 * so we do not need to call fr_connection_release.
1384 case RLM_SQL_RECONNECT:
1388 * Query was invalid, this is a terminal error, but we still need
1389 * to do cleanup, as the connection handle is still valid.
1391 case RLM_SQL_QUERY_INVALID:
1392 rcode = RLM_MODULE_INVALID;
1396 * Driver found an error (like a unique key constraint violation)
1397 * that hinted it might be a good idea to try an alternative query.
1399 case RLM_SQL_ALT_QUERY:
1405 * We need to have updated something for the query to have been
1406 * counted as successful.
1408 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
1409 (inst->module->sql_finish_query)(handle, inst->config);
1410 RDEBUG("%i record(s) updated", numaffected);
1412 if (numaffected > 0) break; /* A query succeeded, were done! */
1415 * We assume all entries with the same name form a redundant
1418 pair = cf_pair_find_next(section->cs, pair, attr);
1421 RDEBUG("No additional queries configured");
1422 rcode = RLM_MODULE_NOOP;
1427 RDEBUG("Trying next query...");
1432 talloc_free(expanded);
1433 fr_connection_release(inst->pool, handle);
1434 sql_unset_user(inst, request);
1439 #ifdef WITH_ACCOUNTING
1442 * Accounting: Insert or update session data in our sql table
1444 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST * request) {
1445 rlm_sql_t *inst = instance;
1447 if (inst->config->accounting.reference_cp) {
1448 return acct_redundant(inst, request, &inst->config->accounting);
1451 return RLM_MODULE_NOOP;
1456 #ifdef WITH_SESSION_MGMT
1458 * See if a user is already logged in. Sets request->simul_count to the
1459 * current session count for this user.
1461 * Check twice. If on the first pass the user exceeds his
1462 * max. number of logins, do a second pass and validate all
1463 * logins by querying the terminal server (using eg. SNMP).
1466 static rlm_rcode_t CC_HINT(nonnull) mod_checksimul(void *instance, REQUEST * request) {
1467 rlm_rcode_t rcode = RLM_MODULE_OK;
1468 rlm_sql_handle_t *handle = NULL;
1469 rlm_sql_t *inst = instance;
1473 char const *call_num = NULL;
1476 uint32_t nas_addr = 0;
1477 uint32_t nas_port = 0;
1479 char *expanded = NULL;
1481 /* If simul_count_query is not defined, we don't do any checking */
1482 if (!inst->config->simul_count_query) {
1483 return RLM_MODULE_NOOP;
1486 if ((!request->username) || (request->username->vp_length == '\0')) {
1487 REDEBUG("Zero Length username not permitted");
1489 return RLM_MODULE_INVALID;
1492 if (sql_set_user(inst, request, NULL) < 0) {
1493 return RLM_MODULE_FAIL;
1496 if (radius_axlat(&expanded, request, inst->config->simul_count_query, sql_escape_func, inst) < 0) {
1497 sql_unset_user(inst, request);
1498 return RLM_MODULE_FAIL;
1501 /* initialize the sql socket */
1502 handle = fr_connection_get(inst->pool);
1504 talloc_free(expanded);
1505 sql_unset_user(inst, request);
1506 return RLM_MODULE_FAIL;
1509 if (rlm_sql_select_query(inst, request, &handle, expanded) != RLM_SQL_OK) {
1510 rcode = RLM_MODULE_FAIL;
1514 ret = rlm_sql_fetch_row(inst, request, &handle);
1516 rcode = RLM_MODULE_FAIL;
1522 rcode = RLM_MODULE_FAIL;
1526 request->simul_count = atoi(row[0]);
1528 (inst->module->sql_finish_select_query)(handle, inst->config);
1529 TALLOC_FREE(expanded);
1531 if (request->simul_count < request->simul_max) {
1532 rcode = RLM_MODULE_OK;
1537 * Looks like too many sessions, so let's start verifying
1538 * them, unless told to rely on count query only.
1540 if (!inst->config->simul_verify_query) {
1541 rcode = RLM_MODULE_OK;
1546 if (radius_axlat(&expanded, request, inst->config->simul_verify_query, sql_escape_func, inst) < 0) {
1547 rcode = RLM_MODULE_FAIL;
1552 if (rlm_sql_select_query(inst, request, &handle, expanded) != RLM_SQL_OK) goto release;
1555 * Setup some stuff, like for MPP detection.
1557 request->simul_count = 0;
1559 if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
1560 ipno = vp->vp_ipaddr;
1563 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
1564 call_num = vp->vp_strvalue;
1567 while (rlm_sql_fetch_row(inst, request, &handle) == 0) {
1574 RDEBUG("Cannot zap stale entry. No username present in entry");
1575 rcode = RLM_MODULE_FAIL;
1581 RDEBUG("Cannot zap stale entry. No session id in entry");
1582 rcode = RLM_MODULE_FAIL;
1588 nas_addr = inet_addr(row[3]);
1592 nas_port = atoi(row[4]);
1595 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1598 * Stale record - zap it.
1600 if (inst->config->delete_stale_sessions == true) {
1601 uint32_t framed_addr = 0;
1606 framed_addr = inet_addr(row[5]);
1608 if (strcmp(row[7], "PPP") == 0)
1610 else if (strcmp(row[7], "SLIP") == 0)
1614 sess_time = atoi(row[8]);
1615 session_zap(request, nas_addr, nas_port,
1616 row[2], row[1], framed_addr,
1620 else if (check == 1) {
1622 * User is still logged in.
1624 ++request->simul_count;
1627 * Does it look like a MPP attempt?
1629 if (row[5] && ipno && inet_addr(row[5]) == ipno) {
1630 request->simul_mpp = 2;
1631 } else if (row[6] && call_num && !strncmp(row[6],call_num,16)) {
1632 request->simul_mpp = 2;
1636 * Failed to check the terminal server for
1637 * duplicate logins: return an error.
1639 REDEBUG("Failed to check the terminal server for user '%s'.", row[2]);
1641 rcode = RLM_MODULE_FAIL;
1647 (inst->module->sql_finish_select_query)(handle, inst->config);
1649 fr_connection_release(inst->pool, handle);
1650 talloc_free(expanded);
1651 sql_unset_user(inst, request);
1654 * The Auth module apparently looks at request->simul_count,
1655 * not the return value of this module when deciding to deny
1656 * a call for too many sessions.
1663 * Postauth: Write a record of the authentication attempt
1665 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST * request) {
1666 rlm_sql_t *inst = instance;
1668 if (inst->config->postauth.reference_cp) {
1669 return acct_redundant(inst, request, &inst->config->postauth);
1672 return RLM_MODULE_NOOP;
1676 * Execute postauth_query after authentication
1680 /* globally exported name */
1681 extern module_t rlm_sql;
1682 module_t rlm_sql = {
1685 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1688 mod_instantiate, /* instantiation */
1689 mod_detach, /* detach */
1691 NULL, /* authentication */
1692 mod_authorize, /* authorization */
1693 NULL, /* preaccounting */
1694 #ifdef WITH_ACCOUNTING
1695 mod_accounting, /* accounting */
1699 #ifdef WITH_SESSION_MGMT
1700 mod_checksimul, /* checksimul */
1704 NULL, /* pre-proxy */
1705 NULL, /* post-proxy */
1706 mod_post_auth /* post-auth */