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>
26 #include <freeradius-devel/ident.h>
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>
40 static const CONF_PARSER acct_section_config[] = {
41 {"reference", PW_TYPE_STRING_PTR,
42 offsetof(sql_acct_section_t, reference), NULL, ".query"},
44 {"logfile", PW_TYPE_STRING_PTR,
45 offsetof(sql_acct_section_t, logfile), NULL, NULL},
46 {NULL, -1, 0, NULL, NULL}
49 static const CONF_PARSER module_config[] = {
50 {"driver", PW_TYPE_STRING_PTR,
51 offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"},
52 {"server", PW_TYPE_STRING_PTR,
53 offsetof(SQL_CONFIG,sql_server), NULL, "localhost"},
54 {"port", PW_TYPE_STRING_PTR,
55 offsetof(SQL_CONFIG,sql_port), NULL, ""},
56 {"login", PW_TYPE_STRING_PTR,
57 offsetof(SQL_CONFIG,sql_login), NULL, ""},
58 {"password", PW_TYPE_STRING_PTR,
59 offsetof(SQL_CONFIG,sql_password), NULL, ""},
60 {"radius_db", PW_TYPE_STRING_PTR,
61 offsetof(SQL_CONFIG,sql_db), NULL, "radius"},
62 {"filename", PW_TYPE_FILENAME, /* for sqlite */
63 offsetof(SQL_CONFIG,sql_file), NULL, NULL},
64 {"read_groups", PW_TYPE_BOOLEAN,
65 offsetof(SQL_CONFIG,read_groups), NULL, "yes"},
66 {"readclients", PW_TYPE_BOOLEAN,
67 offsetof(SQL_CONFIG,do_clients), NULL, "no"},
68 {"deletestalesessions", PW_TYPE_BOOLEAN,
69 offsetof(SQL_CONFIG,deletestalesessions), NULL, "yes"},
70 {"sql_user_name", PW_TYPE_STRING_PTR,
71 offsetof(SQL_CONFIG,query_user), NULL, ""},
72 {"logfile", PW_TYPE_STRING_PTR,
73 offsetof(SQL_CONFIG,logfile), NULL, NULL},
74 {"default_user_profile", PW_TYPE_STRING_PTR,
75 offsetof(SQL_CONFIG,default_profile), NULL, ""},
76 {"nas_query", PW_TYPE_STRING_PTR,
77 offsetof(SQL_CONFIG,nas_query), NULL,
78 "SELECT id,nasname,shortname,type,secret FROM nas"},
79 {"authorize_check_query", PW_TYPE_STRING_PTR,
80 offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
81 {"authorize_reply_query", PW_TYPE_STRING_PTR,
82 offsetof(SQL_CONFIG,authorize_reply_query), NULL, NULL},
83 {"authorize_group_check_query", PW_TYPE_STRING_PTR,
84 offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
85 {"authorize_group_reply_query", PW_TYPE_STRING_PTR,
86 offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""},
87 {"group_membership_query", PW_TYPE_STRING_PTR,
88 offsetof(SQL_CONFIG,groupmemb_query), NULL, NULL},
89 #ifdef WITH_SESSION_MGMT
90 {"simul_count_query", PW_TYPE_STRING_PTR,
91 offsetof(SQL_CONFIG,simul_count_query), NULL, ""},
92 {"simul_verify_query", PW_TYPE_STRING_PTR,
93 offsetof(SQL_CONFIG,simul_verify_query), NULL, ""},
95 {"safe-characters", PW_TYPE_STRING_PTR,
96 offsetof(SQL_CONFIG,allowed_chars), NULL,
97 "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
100 * This only works for a few drivers.
102 {"query_timeout", PW_TYPE_INTEGER,
103 offsetof(SQL_CONFIG,query_timeout), NULL, NULL},
105 {NULL, -1, 0, NULL, NULL}
109 * Fall-Through checking function from rlm_files.c
111 static int fallthrough(VALUE_PAIR *vp)
114 tmp = pairfind(vp, PW_FALL_THROUGH, 0, TAG_ANY);
116 return tmp ? tmp->vp_integer : 0;
122 static int generate_sql_clients(SQL_INST *inst);
123 static size_t sql_escape_func(REQUEST *, char *out, size_t outlen, const char *in, void *arg);
128 * For selects the first value of the first column will be returned,
129 * for inserts, updates and deletes the number of rows afftected will be
132 static size_t sql_xlat(void *instance, REQUEST *request,
133 const char *fmt, char *out, size_t freespace)
137 SQL_INST *inst = instance;
138 char querystr[MAX_QUERY_LEN];
144 * Add SQL-User-Name attribute just in case it is needed
145 * We could search the string fmt for SQL-User-Name to see if this is
148 sql_set_user(inst, request, NULL);
150 * Do an xlat on the provided string (nice recursive operation).
152 if (!radius_xlat(querystr, sizeof(querystr), fmt, request, sql_escape_func, inst)) {
153 radlog(L_ERR, "rlm_sql (%s): xlat failed.",
154 inst->config->xlat_name);
158 sqlsocket = sql_get_socket(inst);
159 if (sqlsocket == NULL)
162 rlm_sql_query_log(inst, request, NULL, querystr);
165 * If the query starts with any of the following prefixes,
166 * then return the number of rows affected
168 if ((strncasecmp(querystr, "insert", 6) == 0) ||
169 (strncasecmp(querystr, "update", 6) == 0) ||
170 (strncasecmp(querystr, "delete", 6) == 0)) {
172 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
174 if (rlm_sql_query(&sqlsocket,inst,querystr)) {
175 sql_release_socket(inst,sqlsocket);
180 numaffected = (inst->module->sql_affected_rows)(sqlsocket,
182 if (numaffected < 1) {
183 RDEBUG("rlm_sql (%s): SQL query affected no rows",
184 inst->config->xlat_name);
188 * Don't chop the returned number if freespace is
189 * too small. This hack is necessary because
190 * some implementations of snprintf return the
191 * size of the written data, and others return
192 * the size of the data they *would* have written
193 * if the output buffer was large enough.
195 snprintf(buffer, sizeof(buffer), "%d", numaffected);
196 ret = strlen(buffer);
197 if (ret >= freespace){
198 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space",
199 inst->config->xlat_name);
200 (inst->module->sql_finish_query)(sqlsocket,
202 sql_release_socket(inst,sqlsocket);
206 memcpy(out, buffer, ret + 1); /* we did bounds checking above */
208 (inst->module->sql_finish_query)(sqlsocket, inst->config);
209 sql_release_socket(inst,sqlsocket);
211 } /* else it's a SELECT statement */
213 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
214 sql_release_socket(inst,sqlsocket);
218 ret = rlm_sql_fetch_row(&sqlsocket, inst);
220 RDEBUG("SQL query did not succeed");
221 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
222 sql_release_socket(inst,sqlsocket);
226 row = sqlsocket->row;
228 RDEBUG("SQL query did not return any results");
229 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
230 sql_release_socket(inst,sqlsocket);
235 RDEBUG("Null value in first column");
236 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
237 sql_release_socket(inst,sqlsocket);
240 ret = strlen(row[0]);
241 if (ret >= freespace){
242 RDEBUG("Insufficient string space");
243 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
244 sql_release_socket(inst,sqlsocket);
248 strlcpy(out,row[0],freespace);
250 RDEBUG("sql_xlat finished");
252 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
253 sql_release_socket(inst,sqlsocket);
257 static int generate_sql_clients(SQL_INST *inst)
261 char querystr[MAX_QUERY_LEN];
263 char *prefix_ptr = NULL;
267 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
268 inst->config->xlat_name);
270 /* NAS query isn't xlat'ed */
271 strlcpy(querystr, inst->config->nas_query, sizeof(querystr));
272 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
273 inst->config->xlat_name, querystr);
275 sqlsocket = sql_get_socket(inst);
276 if (sqlsocket == NULL)
278 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
282 while(rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
284 row = sqlsocket->row;
288 * The return data for each row MUST be in the following order:
290 * 0. Row ID (currently unused)
291 * 1. Name (or IP address)
295 * 5. Virtual Server (optional)
298 radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
302 radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
306 radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
310 radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
314 DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name,
315 row[1],row[2],row[4]);
317 c = rad_malloc(sizeof(*c));
318 memset(c, 0, sizeof(*c));
320 #ifdef WITH_DYNAMIC_CLIENTS
328 prefix_ptr = strchr(row[1], '/');
330 c->prefix = atoi(prefix_ptr + 1);
331 if ((c->prefix < 0) || (c->prefix > 128)) {
332 radlog(L_ERR, "rlm_sql (%s): Invalid Prefix value '%s' for IP.",
333 inst->config->xlat_name, prefix_ptr + 1);
337 /* Replace '/' with '\0' */
342 * Always get the numeric representation of IP
344 if (ip_hton(row[1], AF_UNSPEC, &c->ipaddr) < 0) {
345 radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s: %s",
346 inst->config->xlat_name,
347 row[1], fr_strerror());
352 ip_ntoh(&c->ipaddr, buffer, sizeof(buffer));
353 c->longname = strdup(buffer);
356 if (c->prefix < 0) switch (c->ipaddr.af) {
368 * Other values (secret, shortname, nastype, virtual_server)
370 c->secret = strdup(row[4]);
371 c->shortname = strdup(row[2]);
373 c->nastype = strdup(row[3]);
375 numf = (inst->module->sql_num_fields)(sqlsocket, inst->config);
376 if ((numf > 5) && (row[5] != NULL) && *row[5]) c->server = strdup(row[5]);
378 DEBUG("rlm_sql (%s): Adding client %s (%s, server=%s) to clients list",
379 inst->config->xlat_name,
380 c->longname,c->shortname, c->server ? c->server : "<none>");
381 if (!client_add(NULL, c)) {
382 sql_release_socket(inst, sqlsocket);
383 DEBUG("rlm_sql (%s): Failed to add client %s (%s) to clients list. Maybe there's a duplicate?",
384 inst->config->xlat_name,
385 c->longname,c->shortname);
390 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
391 sql_release_socket(inst, sqlsocket);
398 * Translate the SQL queries.
400 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen,
401 const char *in, void *arg)
403 SQL_INST *inst = arg;
408 * Non-printable characters get replaced with their
409 * mime-encoded equivalents.
412 strchr(inst->config->allowed_chars, *in) == NULL) {
414 * Only 3 or less bytes available.
420 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
429 * Only one byte left.
449 * Set the SQL user name.
451 * We don't call the escape function here. The resulting string
452 * will be escaped later in the queries xlat so we don't need to
453 * escape it twice. (it will make things wrong if we have an
454 * escape candidate character in the username)
456 int sql_set_user(SQL_INST *inst, REQUEST *request, const char *username)
459 VALUE_PAIR *vp = NULL;
463 if (username != NULL) {
465 } else if (*inst->config->query_user) {
466 sqluser = inst->config->query_user;
471 len = radius_xlat(buffer, sizeof(buffer), sqluser, request, NULL, NULL);
476 vp = pairalloc(inst->sql_user);
479 strlcpy(vp->vp_strvalue, buffer, sizeof(vp->vp_strvalue));
480 vp->length = strlen(vp->vp_strvalue);
482 RDEBUG2("SQL-User-Name updated");
488 static void sql_grouplist_free (SQL_GROUPLIST **group_list)
494 *group_list = (*group_list)->next;
500 static int sql_get_grouplist (SQL_INST *inst, SQLSOCK *sqlsocket, REQUEST *request, SQL_GROUPLIST **group_list)
502 char querystr[MAX_QUERY_LEN];
505 SQL_GROUPLIST *group_list_tmp;
507 /* NOTE: sql_set_user should have been run before calling this function */
509 group_list_tmp = *group_list = NULL;
511 if (!inst->config->groupmemb_query ||
512 (inst->config->groupmemb_query[0] == 0))
515 if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, request, sql_escape_func, inst)) {
516 radlog_request(L_ERR, 0, request, "xlat \"%s\" failed.",
517 inst->config->groupmemb_query);
521 if (rlm_sql_select_query(&sqlsocket, inst, querystr) < 0) {
524 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
525 row = sqlsocket->row;
529 RDEBUG("row[0] returned NULL");
530 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
531 sql_grouplist_free(group_list);
534 if (*group_list == NULL) {
535 *group_list = rad_malloc(sizeof(SQL_GROUPLIST));
536 group_list_tmp = *group_list;
538 rad_assert(group_list_tmp != NULL);
539 group_list_tmp->next = rad_malloc(sizeof(SQL_GROUPLIST));
540 group_list_tmp = group_list_tmp->next;
542 group_list_tmp->next = NULL;
543 strlcpy(group_list_tmp->groupname, row[0], MAX_STRING_LEN);
546 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
553 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
554 * with the group memberships reciding in sql
555 * The group membership query should only return one element which is the username. The returned
556 * username will then be checked with the passed check string.
559 static int sql_groupcmp(void *instance, REQUEST *request, VALUE_PAIR *request_vp, VALUE_PAIR *check,
560 VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
563 SQL_INST *inst = instance;
564 SQL_GROUPLIST *group_list, *group_list_tmp;
566 check_pairs = check_pairs;
567 reply_pairs = reply_pairs;
568 request_vp = request_vp;
570 RDEBUG("sql_groupcmp");
571 if (!check || !check->vp_strvalue || !check->length){
572 RDEBUG("sql_groupcmp: Illegal group name");
576 RDEBUG("sql_groupcmp: NULL request");
580 * Set, escape, and check the user attr here
582 if (sql_set_user(inst, request, NULL) < 0)
586 * Get a socket for this lookup
588 sqlsocket = sql_get_socket(inst);
589 if (sqlsocket == NULL) {
594 * Get the list of groups this user is a member of
596 if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
597 radlog_request(L_ERR, 0, request,
598 "Error getting group membership");
599 sql_release_socket(inst, sqlsocket);
603 for (group_list_tmp = group_list; group_list_tmp != NULL; group_list_tmp = group_list_tmp->next) {
604 if (strcmp(group_list_tmp->groupname, check->vp_strvalue) == 0){
605 RDEBUG("sql_groupcmp finished: User is a member of group %s",
607 /* Free the grouplist */
608 sql_grouplist_free(&group_list);
609 sql_release_socket(inst, sqlsocket);
614 /* Free the grouplist */
615 sql_grouplist_free(&group_list);
616 sql_release_socket(inst,sqlsocket);
618 RDEBUG("sql_groupcmp finished: User is NOT a member of group %s",
626 static int rlm_sql_process_groups(SQL_INST *inst, REQUEST *request, SQLSOCK *sqlsocket, int *dofallthrough)
628 VALUE_PAIR *check_tmp = NULL;
629 VALUE_PAIR *reply_tmp = NULL;
630 SQL_GROUPLIST *group_list, *group_list_tmp;
631 VALUE_PAIR *sql_group = NULL;
632 char querystr[MAX_QUERY_LEN];
637 * Get the list of groups this user is a member of
639 if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
640 radlog_request(L_ERR, 0, request, "Error retrieving group list");
644 for (group_list_tmp = group_list; group_list_tmp != NULL && *dofallthrough != 0; group_list_tmp = group_list_tmp->next) {
646 * Add the Sql-Group attribute to the request list so we know
647 * which group we're retrieving attributes for
649 sql_group = pairmake("Sql-Group", group_list_tmp->groupname, T_OP_EQ);
651 radlog_request(L_ERR, 0, request,
652 "Error creating Sql-Group attribute");
653 sql_grouplist_free(&group_list);
656 pairadd(&request->packet->vps, sql_group);
657 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func, inst)) {
658 radlog_request(L_ERR, 0, request,
659 "Error generating query; rejecting user");
660 /* Remove the grouup we added above */
661 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
662 sql_grouplist_free(&group_list);
665 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
667 radlog_request(L_ERR, 0, request, "Error retrieving check pairs for group %s",
668 group_list_tmp->groupname);
669 /* Remove the grouup we added above */
670 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
671 pairfree(&check_tmp);
672 sql_grouplist_free(&group_list);
674 } else if (rows > 0) {
676 * Only do this if *some* check pairs were returned
678 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
680 RDEBUG2("User found in group %s",
681 group_list_tmp->groupname);
683 * Now get the reply pairs since the paircompare matched
685 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func, inst)) {
686 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
687 /* Remove the grouup we added above */
688 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
689 pairfree(&check_tmp);
690 sql_grouplist_free(&group_list);
693 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
694 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
695 group_list_tmp->groupname);
696 /* Remove the grouup we added above */
697 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
698 pairfree(&check_tmp);
699 pairfree(&reply_tmp);
700 sql_grouplist_free(&group_list);
703 *dofallthrough = fallthrough(reply_tmp);
704 radius_xlat_move(request, &request->reply->vps, &reply_tmp);
705 radius_xlat_move(request, &request->config_items, &check_tmp);
709 * rows == 0. This is like having the username on a line
710 * in the user's file with no check vp's. As such, we treat
711 * it as found and add the reply attributes, so that we
712 * match expected behavior
715 RDEBUG2("User found in group %s",
716 group_list_tmp->groupname);
718 * Now get the reply pairs since the paircompare matched
720 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func, inst)) {
721 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
722 /* Remove the grouup we added above */
723 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
724 pairfree(&check_tmp);
725 sql_grouplist_free(&group_list);
728 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
729 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
730 group_list_tmp->groupname);
731 /* Remove the grouup we added above */
732 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
733 pairfree(&check_tmp);
734 pairfree(&reply_tmp);
735 sql_grouplist_free(&group_list);
738 *dofallthrough = fallthrough(reply_tmp);
739 radius_xlat_move(request, &request->reply->vps, &reply_tmp);
740 radius_xlat_move(request, &request->config_items, &check_tmp);
744 * Delete the Sql-Group we added above
745 * And clear out the pairlists
747 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
748 pairfree(&check_tmp);
749 pairfree(&reply_tmp);
752 sql_grouplist_free(&group_list);
757 static int rlm_sql_detach(void *instance)
759 SQL_INST *inst = instance;
761 paircompare_unregister(PW_SQL_GROUP, sql_groupcmp);
763 if (inst->config->postauth) free(inst->config->postauth);
764 if (inst->config->accounting) free(inst->config->accounting);
769 if (inst->pool) sql_poolfree(inst);
771 if (inst->config->xlat_name) {
772 xlat_unregister(inst->config->xlat_name, sql_xlat, instance);
773 rad_cfree(inst->config->xlat_name);
777 * Free up dynamically allocated string pointers.
779 for (i = 0; module_config[i].name != NULL; i++) {
781 if (module_config[i].type != PW_TYPE_STRING_PTR) {
786 * Treat 'config' as an opaque array of bytes,
787 * and take the offset into it. There's a
788 * (char*) pointer at that offset, and we want
791 p = (char **) (((char *)inst->config) + module_config[i].offset);
792 if (!*p) { /* nothing allocated */
805 * FIXME: Call the modules 'destroy' function?
807 lt_dlclose(inst->handle); /* ignore any errors */
815 static int parse_sub_section(CONF_SECTION *parent,
817 sql_acct_section_t **config,
818 rlm_components_t comp)
822 const char *name = section_type_value[comp].section;
824 cs = cf_section_sub_find(parent, name);
826 radlog(L_INFO, "rlm_sql (%s): Couldn't find configuration for "
827 "%s, will return NOOP for calls from this section",
828 inst->config->xlat_name, name);
833 *config = rad_calloc(sizeof(**config));
834 if (cf_section_parse(cs, *config, acct_section_config) < 0) {
835 radlog(L_ERR, "rlm_sql (%s): Couldn't find configuration for "
836 "%s, will return NOOP for calls from this section",
837 inst->config->xlat_name, name);
850 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance)
853 const char *xlat_name;
855 inst = rad_calloc(sizeof(SQL_INST));
858 * Cache the SQL-User-Name DICT_ATTR, so we can be slightly
859 * more efficient about creating SQL-User-Name attributes.
861 inst->sql_user = dict_attrbyname("SQL-User-Name");
862 if (!inst->sql_user) {
867 * Export these methods, too. This avoids RTDL_GLOBAL.
869 inst->sql_set_user = sql_set_user;
870 inst->sql_get_socket = sql_get_socket;
871 inst->sql_release_socket = sql_release_socket;
872 inst->sql_escape_func = sql_escape_func;
873 inst->sql_query = rlm_sql_query;
874 inst->sql_select_query = rlm_sql_select_query;
875 inst->sql_fetch_row = rlm_sql_fetch_row;
877 inst->config = rad_calloc(sizeof(SQL_CONFIG));
880 xlat_name = cf_section_name2(conf);
881 if (xlat_name == NULL) {
882 xlat_name = cf_section_name1(conf);
885 const DICT_ATTR *dattr;
889 * Allocate room for <instance>-SQL-Group
891 group_name = rad_malloc((strlen(xlat_name) + 1 + 11) * sizeof(char));
892 sprintf(group_name,"%s-SQL-Group", xlat_name);
893 DEBUG("rlm_sql (%s): Creating new attribute %s",
894 xlat_name, group_name);
896 memset(&flags, 0, sizeof(flags));
897 if (dict_addattr(group_name, -1, 0, PW_TYPE_STRING, flags) < 0) {
898 radlog(L_ERR, "rlm_sql (%s): Failed to create "
899 "attribute %s: %s", xlat_name, group_name,
905 dattr = dict_attrbyname(group_name);
907 radlog(L_ERR, "rlm_sql (%s): Failed to create "
908 "attribute %s", xlat_name, group_name);
915 if (inst->config->groupmemb_query &&
916 inst->config->groupmemb_query[0]) {
917 DEBUG("rlm_sql (%s): Registering sql_groupcmp for %s",
918 xlat_name, group_name);
919 paircompare_register(dattr->attr, PW_USER_NAME,
926 rad_assert(xlat_name);
930 * Register the SQL xlat function
932 inst->config->xlat_name = strdup(xlat_name);
933 xlat_register(xlat_name, sql_xlat, inst);
936 * If the configuration parameters can't be parsed, then fail.
938 if ((cf_section_parse(conf, inst->config, module_config) < 0) ||
939 (parse_sub_section(conf, inst,
940 &inst->config->accounting,
941 RLM_COMPONENT_ACCT) < 0) ||
942 (parse_sub_section(conf, inst,
943 &inst->config->postauth,
944 RLM_COMPONENT_POST_AUTH) < 0)) {
945 radlog(L_ERR, "rlm_sql (%s): Failed parsing configuration",
946 inst->config->xlat_name);
951 * Sanity check for crazy people.
953 if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
954 radlog(L_ERR, "rlm_sql (%s): \"%s\" is NOT an SQL driver!",
955 inst->config->xlat_name, inst->config->sql_driver);
960 * Load the appropriate driver for our database
962 inst->handle = lt_dlopenext(inst->config->sql_driver);
963 if (inst->handle == NULL) {
964 radlog(L_ERR, "Could not link driver %s: %s",
965 inst->config->sql_driver,
967 radlog(L_ERR, "Make sure it (and all its dependent libraries!)"
968 "are in the search path of your system's ld.");
973 inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle,
974 inst->config->sql_driver);
976 radlog(L_ERR, "Could not link symbol %s: %s",
977 inst->config->sql_driver,
983 radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
984 inst->config->xlat_name, inst->config->sql_driver,
988 * Initialise the connection pool for this instance
990 radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
991 inst->config->xlat_name, inst->config->sql_login,
992 inst->config->sql_server, inst->config->sql_port,
993 inst->config->sql_db);
995 if (sql_init_socketpool(inst) < 0)
998 if (inst->config->groupmemb_query &&
999 inst->config->groupmemb_query[0]) {
1000 paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
1003 if (inst->config->do_clients) {
1004 if (generate_sql_clients(inst) == -1){
1005 radlog(L_ERR, "Failed to load clients from SQL.");
1013 return RLM_MODULE_OK;
1016 rlm_sql_detach(inst);
1022 static rlm_rcode_t rlm_sql_authorize(void *instance, REQUEST * request)
1024 int ret = RLM_MODULE_NOTFOUND;
1026 SQL_INST *inst = instance;
1029 VALUE_PAIR *check_tmp = NULL;
1030 VALUE_PAIR *reply_tmp = NULL;
1031 VALUE_PAIR *user_profile = NULL;
1033 int dofallthrough = 1;
1036 char querystr[MAX_QUERY_LEN];
1039 * Set, escape, and check the user attr here
1041 if (sql_set_user(inst, request, NULL) < 0)
1042 return RLM_MODULE_FAIL;
1047 * After this point use goto error or goto release to cleanup sockets
1048 * temporary pairlists and temporary attributes.
1050 sqlsocket = sql_get_socket(inst);
1051 if (sqlsocket == NULL)
1055 * Query the check table to find any conditions associated with
1056 * this user/realm/whatever...
1058 if (inst->config->authorize_check_query &&
1059 *inst->config->authorize_check_query) {
1060 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func, inst)) {
1061 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1066 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
1068 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1074 * Only do this if *some* check pairs were returned
1077 (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0)) {
1078 RDEBUG2("User found in radcheck table");
1080 radius_xlat_move(request, &request->config_items, &check_tmp);
1082 ret = RLM_MODULE_OK;
1086 * We only process reply table items if check conditions
1093 if (inst->config->authorize_reply_query &&
1094 *inst->config->authorize_reply_query) {
1096 * Now get the reply pairs since the paircompare matched
1098 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func, inst)) {
1099 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1104 rows = sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr);
1106 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1112 if (!inst->config->read_groups)
1113 dofallthrough = fallthrough(reply_tmp);
1115 radius_xlat_move(request, &request->reply->vps, &reply_tmp);
1117 ret = RLM_MODULE_OK;
1124 * Clear out the pairlists
1126 pairfree(&check_tmp);
1127 pairfree(&reply_tmp);
1130 * dofallthrough is set to 1 by default so that if the user information
1131 * is not found, we will still process groups. If the user information,
1132 * however, *is* found, Fall-Through must be set in order to process
1133 * the groups as well.
1135 if (dofallthrough) {
1136 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1138 radlog_request(L_ERR, 0, request, "Error processing groups; rejecting user");
1144 ret = RLM_MODULE_OK;
1148 * Repeat the above process with the default profile or User-Profile
1150 if (dofallthrough) {
1152 * Check for a default_profile or for a User-Profile.
1154 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY);
1156 const char *profile = user_profile ?
1157 user_profile->vp_strvalue :
1158 inst->config->default_profile;
1160 if (!profile || !*profile)
1163 RDEBUG("Checking profile %s", profile);
1165 if (sql_set_user(inst, request, profile) < 0) {
1166 radlog_request(L_ERR, 0, request, "Error setting profile; rejecting user");
1171 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1173 radlog_request(L_ERR, 0, request, "Error processing profile groups; rejecting user");
1179 ret = RLM_MODULE_OK;
1185 ret = RLM_MODULE_FAIL;
1188 sql_release_socket(inst, sqlsocket);
1190 pairfree(&check_tmp);
1191 pairfree(&reply_tmp);
1197 * Generic function for failing between a bunch of queries.
1199 * Uses the same principle as rlm_linelog, expanding the 'reference' config
1200 * item using xlat to figure out what query it should execute.
1202 * If the reference matches multiple config items, and a query fails or
1203 * doesn't update any rows, the next matching config item is used.
1206 static int acct_redundant(SQL_INST *inst, REQUEST *request,
1207 sql_acct_section_t *section)
1209 int ret = RLM_MODULE_OK;
1211 SQLSOCK *sqlsocket = NULL;
1213 int numaffected = 0;
1217 const char *attr = NULL;
1220 char path[MAX_STRING_LEN];
1221 char querystr[MAX_QUERY_LEN];
1225 rad_assert(section);
1227 if (section->reference[0] != '.')
1230 if (!radius_xlat(p, (sizeof(path) - (p - path)) - 1,
1231 section->reference, request, NULL, NULL))
1232 return RLM_MODULE_FAIL;
1234 item = cf_reference_item(NULL, section->cs, path);
1236 return RLM_MODULE_FAIL;
1238 if (cf_item_is_section(item)){
1239 radlog(L_ERR, "Sections are not supported as references");
1241 return RLM_MODULE_FAIL;
1244 pair = cf_itemtopair(item);
1245 attr = cf_pair_attr(pair);
1247 RDEBUG2("Using query template '%s'", attr);
1249 sqlsocket = sql_get_socket(inst);
1250 if (sqlsocket == NULL)
1251 return RLM_MODULE_FAIL;
1253 sql_set_user(inst, request, NULL);
1256 value = cf_pair_value(pair);
1258 RDEBUG("Ignoring null query");
1259 ret = RLM_MODULE_NOOP;
1264 radius_xlat(querystr, sizeof(querystr), value, request,
1265 sql_escape_func, inst);
1267 RDEBUG("Ignoring null query");
1268 ret = RLM_MODULE_NOOP;
1273 rlm_sql_query_log(inst, request, section, querystr);
1276 * If rlm_sql_query cannot use the socket it'll try and
1277 * reconnect. Reconnecting will automatically release
1278 * the current socket, and try to select a new one.
1280 * If we get SQL_DOWN it means all connections in the pool
1281 * were exhausted, and we couldn't create a new connection,
1282 * so we do not need to call sql_release_socket.
1284 sql_ret = rlm_sql_query(&sqlsocket, inst, querystr);
1285 if (sql_ret == SQL_DOWN)
1286 return RLM_MODULE_FAIL;
1288 rad_assert(sqlsocket);
1291 * Assume all other errors are incidental, and just meant our
1292 * operation failed and its not a client or SQL syntax error.
1295 numaffected = (inst->module->sql_affected_rows)
1296 (sqlsocket, inst->config);
1297 if (numaffected > 0)
1300 RDEBUG("No records updated");
1303 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1306 * We assume all entries with the same name form a redundant
1309 pair = cf_pair_find_next(section->cs, pair, attr);
1312 RDEBUG("No additional queries configured");
1314 ret = RLM_MODULE_NOOP;
1319 RDEBUG("Trying next query...");
1322 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1325 sql_release_socket(inst, sqlsocket);
1330 #ifdef WITH_ACCOUNTING
1333 * Accounting: Insert or update session data in our sql table
1335 static rlm_rcode_t rlm_sql_accounting(void *instance, REQUEST * request) {
1336 SQL_INST *inst = instance;
1338 if (inst->config->accounting) {
1339 return acct_redundant(inst, request, inst->config->accounting);
1342 return RLM_MODULE_NOOP;
1347 #ifdef WITH_SESSION_MGMT
1349 * See if a user is already logged in. Sets request->simul_count to the
1350 * current session count for this user.
1352 * Check twice. If on the first pass the user exceeds his
1353 * max. number of logins, do a second pass and validate all
1354 * logins by querying the terminal server (using eg. SNMP).
1357 static rlm_rcode_t rlm_sql_checksimul(void *instance, REQUEST * request) {
1359 SQL_INST *inst = instance;
1361 char querystr[MAX_QUERY_LEN];
1364 char *call_num = NULL;
1367 uint32_t nas_addr = 0;
1370 /* If simul_count_query is not defined, we don't do any checking */
1371 if (!inst->config->simul_count_query ||
1372 (inst->config->simul_count_query[0] == 0)) {
1373 return RLM_MODULE_NOOP;
1376 if((request->username == NULL) || (request->username->length == 0)) {
1377 radlog_request(L_ERR, 0, request,
1378 "Zero Length username not permitted\n");
1379 return RLM_MODULE_INVALID;
1383 if(sql_set_user(inst, request, NULL) < 0)
1384 return RLM_MODULE_FAIL;
1386 radius_xlat(querystr, sizeof(querystr), inst->config->simul_count_query, request, sql_escape_func, inst);
1388 /* initialize the sql socket */
1389 sqlsocket = sql_get_socket(inst);
1390 if(sqlsocket == NULL)
1391 return RLM_MODULE_FAIL;
1393 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1394 sql_release_socket(inst, sqlsocket);
1395 return RLM_MODULE_FAIL;
1398 ret = rlm_sql_fetch_row(&sqlsocket, inst);
1400 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1401 sql_release_socket(inst, sqlsocket);
1402 return RLM_MODULE_FAIL;
1405 row = sqlsocket->row;
1407 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1408 sql_release_socket(inst, sqlsocket);
1409 return RLM_MODULE_FAIL;
1412 request->simul_count = atoi(row[0]);
1413 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1415 if(request->simul_count < request->simul_max) {
1416 sql_release_socket(inst, sqlsocket);
1417 return RLM_MODULE_OK;
1421 * Looks like too many sessions, so let's start verifying
1422 * them, unless told to rely on count query only.
1424 if (!inst->config->simul_verify_query ||
1425 (inst->config->simul_verify_query[0] == '\0')) {
1426 sql_release_socket(inst, sqlsocket);
1427 return RLM_MODULE_OK;
1430 radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func, inst);
1431 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1432 sql_release_socket(inst, sqlsocket);
1433 return RLM_MODULE_FAIL;
1437 * Setup some stuff, like for MPP detection.
1439 request->simul_count = 0;
1441 if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL)
1442 ipno = vp->vp_ipaddr;
1443 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL)
1444 call_num = vp->vp_strvalue;
1447 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
1448 row = sqlsocket->row;
1452 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1453 sql_release_socket(inst, sqlsocket);
1454 RDEBUG("Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
1455 return RLM_MODULE_FAIL;
1458 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1459 sql_release_socket(inst, sqlsocket);
1460 RDEBUG("Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
1461 return RLM_MODULE_FAIL;
1464 nas_addr = inet_addr(row[3]);
1466 nas_port = atoi(row[4]);
1468 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1472 * Stale record - zap it.
1474 if (inst->config->deletestalesessions == TRUE) {
1475 uint32_t framed_addr = 0;
1480 framed_addr = inet_addr(row[5]);
1482 if (strcmp(row[7], "PPP") == 0)
1484 else if (strcmp(row[7], "SLIP") == 0)
1488 sess_time = atoi(row[8]);
1489 session_zap(request, nas_addr, nas_port,
1490 row[2], row[1], framed_addr,
1494 else if (check == 1) {
1496 * User is still logged in.
1498 ++request->simul_count;
1501 * Does it look like a MPP attempt?
1503 if (row[5] && ipno && inet_addr(row[5]) == ipno)
1504 request->simul_mpp = 2;
1505 else if (row[6] && call_num &&
1506 !strncmp(row[6],call_num,16))
1507 request->simul_mpp = 2;
1511 * Failed to check the terminal server for
1512 * duplicate logins: return an error.
1514 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1515 sql_release_socket(inst, sqlsocket);
1516 radlog_request(L_ERR, 0, request, "Failed to check the terminal server for user '%s'.", row[2]);
1517 return RLM_MODULE_FAIL;
1521 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1522 sql_release_socket(inst, sqlsocket);
1525 * The Auth module apparently looks at request->simul_count,
1526 * not the return value of this module when deciding to deny
1527 * a call for too many sessions.
1529 return RLM_MODULE_OK;
1534 * Postauth: Write a record of the authentication attempt
1536 static rlm_rcode_t rlm_sql_postauth(void *instance, REQUEST * request) {
1537 SQL_INST *inst = instance;
1539 if (inst->config->postauth) {
1540 return acct_redundant(inst, request, inst->config->postauth);
1543 return RLM_MODULE_NOOP;
1547 * Execute postauth_query after authentication
1551 /* globally exported name */
1552 module_t rlm_sql = {
1555 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1556 rlm_sql_instantiate, /* instantiation */
1557 rlm_sql_detach, /* detach */
1559 NULL, /* authentication */
1560 rlm_sql_authorize, /* authorization */
1561 NULL, /* preaccounting */
1562 #ifdef WITH_ACCOUNTING
1563 rlm_sql_accounting, /* accounting */
1567 #ifdef WITH_SESSION_MGMT
1568 rlm_sql_checksimul, /* checksimul */
1572 NULL, /* pre-proxy */
1573 NULL, /* post-proxy */
1574 rlm_sql_postauth /* post-auth */