4 * Main SQL module file. Most ICRADIUS code is located in sql.c
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 * Copyright 2012 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>
28 #include <freeradius-devel/ident.h>
33 #include <freeradius-devel/radiusd.h>
34 #include <freeradius-devel/modules.h>
35 #include <freeradius-devel/token.h>
36 #include <freeradius-devel/rad_assert.h>
42 static char *allowed_chars = NULL;
44 static const CONF_PARSER section_config[] = {
45 { "reference", PW_TYPE_STRING_PTR,
46 offsetof(rlm_sql_config_section_t, reference), NULL, ".query"},
48 {"logfile", PW_TYPE_STRING_PTR,
49 offsetof(rlm_sql_config_section_t, logfile), NULL, NULL},
50 {NULL, -1, 0, NULL, NULL}
53 static const CONF_PARSER module_config[] = {
54 {"driver",PW_TYPE_STRING_PTR,
55 offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"},
56 {"server",PW_TYPE_STRING_PTR,
57 offsetof(SQL_CONFIG,sql_server), NULL, "localhost"},
58 {"port",PW_TYPE_STRING_PTR,
59 offsetof(SQL_CONFIG,sql_port), NULL, ""},
60 {"login", PW_TYPE_STRING_PTR,
61 offsetof(SQL_CONFIG,sql_login), NULL, ""},
62 {"password", PW_TYPE_STRING_PTR,
63 offsetof(SQL_CONFIG,sql_password), NULL, ""},
64 {"radius_db", PW_TYPE_STRING_PTR,
65 offsetof(SQL_CONFIG,sql_db), NULL, "radius"},
66 {"filename", PW_TYPE_FILENAME, /* for sqlite */
67 offsetof(SQL_CONFIG,sql_file), NULL, NULL},
68 {"read_groups", PW_TYPE_BOOLEAN,
69 offsetof(SQL_CONFIG,read_groups), NULL, "yes"},
70 {"readclients", PW_TYPE_BOOLEAN,
71 offsetof(SQL_CONFIG,do_clients), NULL, "no"},
72 {"deletestalesessions", PW_TYPE_BOOLEAN,
73 offsetof(SQL_CONFIG,deletestalesessions), NULL, "yes"},
74 {"sql_user_name", PW_TYPE_STRING_PTR,
75 offsetof(SQL_CONFIG,query_user), NULL, ""},
76 {"logfile", PW_TYPE_STRING_PTR,
77 offsetof(SQL_CONFIG,logfile), NULL, NULL},
78 {"default_user_profile", PW_TYPE_STRING_PTR,
79 offsetof(SQL_CONFIG,default_profile), NULL, ""},
80 {"nas_query", PW_TYPE_STRING_PTR,
81 offsetof(SQL_CONFIG,nas_query), NULL, "SELECT id,nasname,shortname,type,secret FROM nas"},
82 {"authorize_check_query", PW_TYPE_STRING_PTR,
83 offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
84 {"authorize_reply_query", PW_TYPE_STRING_PTR,
85 offsetof(SQL_CONFIG,authorize_reply_query), NULL, NULL},
86 {"authorize_group_check_query", PW_TYPE_STRING_PTR,
87 offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
88 {"authorize_group_reply_query", PW_TYPE_STRING_PTR,
89 offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""},
90 {"group_membership_query", PW_TYPE_STRING_PTR,
91 offsetof(SQL_CONFIG,groupmemb_query), NULL, NULL},
92 #ifdef WITH_SESSION_MGMT
93 {"simul_count_query", PW_TYPE_STRING_PTR,
94 offsetof(SQL_CONFIG,simul_count_query), NULL, ""},
95 {"simul_verify_query", PW_TYPE_STRING_PTR,
96 offsetof(SQL_CONFIG,simul_verify_query), NULL, ""},
98 {"safe-characters", PW_TYPE_STRING_PTR,
99 offsetof(SQL_CONFIG,allowed_chars), NULL,
100 "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
103 * This only works for a few drivers.
105 {"query_timeout", PW_TYPE_INTEGER,
106 offsetof(SQL_CONFIG,query_timeout), NULL, NULL},
108 {NULL, -1, 0, NULL, NULL}
112 * Fall-Through checking function from rlm_files.c
114 static int fallthrough(VALUE_PAIR *vp)
117 tmp = pairfind(vp, PW_FALL_THROUGH, 0);
119 return tmp ? tmp->vp_integer : 0;
127 static int generate_sql_clients(SQL_INST *inst);
128 static size_t sql_escape_func(char *out, size_t outlen, const char *in);
133 * For selects the first value of the first column will be returned,
134 * for inserts, updates and deletes the number of rows afftected will be
137 static int sql_xlat(void *instance, REQUEST *request,
138 char *fmt, char *out, size_t freespace,
139 UNUSED RADIUS_ESCAPE_STRING func)
143 SQL_INST *inst = instance;
144 char querystr[MAX_QUERY_LEN];
145 char sqlusername[MAX_STRING_LEN];
151 * Add SQL-User-Name attribute just in case it is needed
152 * We could search the string fmt for SQL-User-Name to see if this is
155 sql_set_user(inst, request, sqlusername, NULL);
157 * Do an xlat on the provided string (nice recursive operation).
159 if (!radius_xlat(querystr, sizeof(querystr), fmt, request, sql_escape_func)) {
160 radlog(L_ERR, "rlm_sql (%s): xlat failed.",
161 inst->config->xlat_name);
165 sqlsocket = sql_get_socket(inst);
166 if (sqlsocket == NULL)
169 rlm_sql_query_log(inst, request, NULL, querystr);
172 * If the query starts with any of the following prefixes,
173 * then return the number of rows affected
175 if ((strncasecmp(querystr, "insert", 6) == 0) ||
176 (strncasecmp(querystr, "update", 6) == 0) ||
177 (strncasecmp(querystr, "delete", 6) == 0)) {
179 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
181 if (rlm_sql_query(&sqlsocket,inst,querystr)) {
182 sql_release_socket(inst,sqlsocket);
187 numaffected = (inst->module->sql_affected_rows)(sqlsocket,
189 if (numaffected < 1) {
190 RDEBUG("rlm_sql (%s): SQL query affected no rows",
191 inst->config->xlat_name);
195 * Don't chop the returned number if freespace is
196 * too small. This hack is necessary because
197 * some implementations of snprintf return the
198 * size of the written data, and others return
199 * the size of the data they *would* have written
200 * if the output buffer was large enough.
202 snprintf(buffer, sizeof(buffer), "%d", numaffected);
203 ret = strlen(buffer);
204 if (ret >= freespace){
205 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space",
206 inst->config->xlat_name);
207 (inst->module->sql_finish_query)(sqlsocket,
209 sql_release_socket(inst,sqlsocket);
213 memcpy(out, buffer, ret + 1); /* we did bounds checking above */
215 (inst->module->sql_finish_query)(sqlsocket, inst->config);
216 sql_release_socket(inst,sqlsocket);
218 } /* else it's a SELECT statement */
220 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
221 sql_release_socket(inst,sqlsocket);
225 ret = rlm_sql_fetch_row(&sqlsocket, inst);
227 RDEBUG("SQL query did not succeed");
228 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
229 sql_release_socket(inst,sqlsocket);
233 row = sqlsocket->row;
235 RDEBUG("SQL query did not return any results");
236 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
237 sql_release_socket(inst,sqlsocket);
242 RDEBUG("Null value in first column");
243 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
244 sql_release_socket(inst,sqlsocket);
247 ret = strlen(row[0]);
248 if (ret >= freespace){
249 RDEBUG("Insufficient string space");
250 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
251 sql_release_socket(inst,sqlsocket);
255 strlcpy(out,row[0],freespace);
257 RDEBUG("sql_xlat finished");
259 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
260 sql_release_socket(inst,sqlsocket);
264 static int generate_sql_clients(SQL_INST *inst)
268 char querystr[MAX_QUERY_LEN];
270 char *prefix_ptr = NULL;
274 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
275 inst->config->xlat_name);
277 /* NAS query isn't xlat'ed */
278 strlcpy(querystr, inst->config->nas_query, sizeof(querystr));
279 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
280 inst->config->xlat_name, querystr);
282 sqlsocket = sql_get_socket(inst);
283 if (sqlsocket == NULL)
285 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
289 while(rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
291 row = sqlsocket->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 radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
309 radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
313 radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
317 radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
321 DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name,
322 row[1],row[2],row[4]);
324 c = rad_malloc(sizeof(*c));
325 memset(c, 0, sizeof(*c));
327 #ifdef WITH_DYNAMIC_CLIENTS
335 prefix_ptr = strchr(row[1], '/');
337 c->prefix = atoi(prefix_ptr + 1);
338 if ((c->prefix < 0) || (c->prefix > 128)) {
339 radlog(L_ERR, "rlm_sql (%s): Invalid Prefix value '%s' for IP.",
340 inst->config->xlat_name, prefix_ptr + 1);
344 /* Replace '/' with '\0' */
349 * Always get the numeric representation of IP
351 if (ip_hton(row[1], AF_UNSPEC, &c->ipaddr) < 0) {
352 radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s: %s",
353 inst->config->xlat_name,
354 row[1], fr_strerror());
359 ip_ntoh(&c->ipaddr, buffer, sizeof(buffer));
360 c->longname = strdup(buffer);
363 if (c->prefix < 0) switch (c->ipaddr.af) {
375 * Other values (secret, shortname, nastype, virtual_server)
377 c->secret = strdup(row[4]);
378 c->shortname = strdup(row[2]);
380 c->nastype = strdup(row[3]);
382 numf = (inst->module->sql_num_fields)(sqlsocket, inst->config);
383 if ((numf > 5) && (row[5] != NULL) && *row[5]) c->server = strdup(row[5]);
385 DEBUG("rlm_sql (%s): Adding client %s (%s, server=%s) to clients list",
386 inst->config->xlat_name,
387 c->longname,c->shortname, c->server ? c->server : "<none>");
388 if (!client_add(NULL, c)) {
389 sql_release_socket(inst, sqlsocket);
390 DEBUG("rlm_sql (%s): Failed to add client %s (%s) to clients list. Maybe there's a duplicate?",
391 inst->config->xlat_name,
392 c->longname,c->shortname);
397 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
398 sql_release_socket(inst, sqlsocket);
405 * Translate the SQL queries.
407 static size_t sql_escape_func(char *out, size_t outlen, const char *in)
413 * Non-printable characters get replaced with their
414 * mime-encoded equivalents.
417 strchr(allowed_chars, *in) == NULL) {
419 * Only 3 or less bytes available.
425 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
434 * Only one byte left.
454 * Set the SQL user name.
456 * We don't call the escape function here. The resulting string
457 * will be escaped later in the queries xlat so we don't need to
458 * escape it twice. (it will make things wrong if we have an
459 * escape candidate character in the username)
461 int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username)
464 char tmpuser[MAX_STRING_LEN];
467 sqlusername[0]= '\0';
469 /* Remove any user attr we added previously */
470 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
472 if (username != NULL) {
473 strlcpy(tmpuser, username, sizeof(tmpuser));
474 } else if (strlen(inst->config->query_user)) {
475 radius_xlat(tmpuser, sizeof(tmpuser), inst->config->query_user, request, NULL);
480 strlcpy(sqlusername, tmpuser, MAX_STRING_LEN);
481 RDEBUG2("sql_set_user escaped user --> '%s'", sqlusername);
482 vp = radius_pairmake(request, &request->packet->vps,
483 "SQL-User-Name", NULL, 0);
485 radlog(L_ERR, "%s", fr_strerror());
489 strlcpy(vp->vp_strvalue, tmpuser, sizeof(vp->vp_strvalue));
490 vp->length = strlen(vp->vp_strvalue);
497 static void sql_grouplist_free (SQL_GROUPLIST **group_list)
503 *group_list = (*group_list)->next;
509 static int sql_get_grouplist (SQL_INST *inst, SQLSOCK *sqlsocket, REQUEST *request, SQL_GROUPLIST **group_list)
511 char querystr[MAX_QUERY_LEN];
514 SQL_GROUPLIST *group_list_tmp;
516 /* NOTE: sql_set_user should have been run before calling this function */
518 group_list_tmp = *group_list = NULL;
520 if (!inst->config->groupmemb_query ||
521 (inst->config->groupmemb_query[0] == 0))
524 if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, request, sql_escape_func)) {
525 radlog_request(L_ERR, 0, request, "xlat \"%s\" failed.",
526 inst->config->groupmemb_query);
530 if (rlm_sql_select_query(&sqlsocket, inst, querystr) < 0) {
533 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
534 row = sqlsocket->row;
538 RDEBUG("row[0] returned NULL");
539 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
540 sql_grouplist_free(group_list);
543 if (*group_list == NULL) {
544 *group_list = rad_malloc(sizeof(SQL_GROUPLIST));
545 group_list_tmp = *group_list;
547 rad_assert(group_list_tmp != NULL);
548 group_list_tmp->next = rad_malloc(sizeof(SQL_GROUPLIST));
549 group_list_tmp = group_list_tmp->next;
551 group_list_tmp->next = NULL;
552 strlcpy(group_list_tmp->groupname, row[0], MAX_STRING_LEN);
555 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
562 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
563 * with the group memberships reciding in sql
564 * The group membership query should only return one element which is the username. The returned
565 * username will then be checked with the passed check string.
568 static int sql_groupcmp(void *instance, REQUEST *request, VALUE_PAIR *request_vp, VALUE_PAIR *check,
569 VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
572 SQL_INST *inst = instance;
573 char sqlusername[MAX_STRING_LEN];
574 SQL_GROUPLIST *group_list, *group_list_tmp;
576 check_pairs = check_pairs;
577 reply_pairs = reply_pairs;
578 request_vp = request_vp;
580 RDEBUG("sql_groupcmp");
581 if (!check || !check->vp_strvalue || !check->length){
582 RDEBUG("sql_groupcmp: Illegal group name");
586 RDEBUG("sql_groupcmp: NULL request");
590 * Set, escape, and check the user attr here
592 if (sql_set_user(inst, request, sqlusername, NULL) < 0)
596 * Get a socket for this lookup
598 sqlsocket = sql_get_socket(inst);
599 if (sqlsocket == NULL) {
600 /* Remove the username we (maybe) added above */
601 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
606 * Get the list of groups this user is a member of
608 if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
609 radlog_request(L_ERR, 0, request,
610 "Error getting group membership");
611 /* Remove the username we (maybe) added above */
612 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
613 sql_release_socket(inst, sqlsocket);
617 for (group_list_tmp = group_list; group_list_tmp != NULL; group_list_tmp = group_list_tmp->next) {
618 if (strcmp(group_list_tmp->groupname, check->vp_strvalue) == 0){
619 RDEBUG("sql_groupcmp finished: User is a member of group %s",
621 /* Free the grouplist */
622 sql_grouplist_free(&group_list);
623 /* Remove the username we (maybe) added above */
624 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
625 sql_release_socket(inst, sqlsocket);
630 /* Free the grouplist */
631 sql_grouplist_free(&group_list);
632 /* Remove the username we (maybe) added above */
633 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
634 sql_release_socket(inst,sqlsocket);
636 RDEBUG("sql_groupcmp finished: User is NOT a member of group %s",
644 static int rlm_sql_process_groups(SQL_INST *inst, REQUEST *request, SQLSOCK *sqlsocket, int *dofallthrough)
646 VALUE_PAIR *check_tmp = NULL;
647 VALUE_PAIR *reply_tmp = NULL;
648 SQL_GROUPLIST *group_list, *group_list_tmp;
649 VALUE_PAIR *sql_group = NULL;
650 char querystr[MAX_QUERY_LEN];
655 * Get the list of groups this user is a member of
657 if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
658 radlog_request(L_ERR, 0, request, "Error retrieving group list");
662 for (group_list_tmp = group_list; group_list_tmp != NULL && *dofallthrough != 0; group_list_tmp = group_list_tmp->next) {
664 * Add the Sql-Group attribute to the request list so we know
665 * which group we're retrieving attributes for
667 sql_group = pairmake("Sql-Group", group_list_tmp->groupname, T_OP_EQ);
669 radlog_request(L_ERR, 0, request,
670 "Error creating Sql-Group attribute");
671 sql_grouplist_free(&group_list);
674 pairadd(&request->packet->vps, sql_group);
675 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func)) {
676 radlog_request(L_ERR, 0, request,
677 "Error generating query; rejecting user");
678 /* Remove the grouup we added above */
679 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
680 sql_grouplist_free(&group_list);
683 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
685 radlog_request(L_ERR, 0, request, "Error retrieving check pairs for group %s",
686 group_list_tmp->groupname);
687 /* Remove the grouup we added above */
688 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
689 pairfree(&check_tmp);
690 sql_grouplist_free(&group_list);
692 } else if (rows > 0) {
694 * Only do this if *some* check pairs were returned
696 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
698 RDEBUG2("User found in group %s",
699 group_list_tmp->groupname);
701 * Now get the reply pairs since the paircompare matched
703 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) {
704 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
705 /* Remove the grouup we added above */
706 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
707 pairfree(&check_tmp);
708 sql_grouplist_free(&group_list);
711 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
712 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
713 group_list_tmp->groupname);
714 /* Remove the grouup we added above */
715 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
716 pairfree(&check_tmp);
717 pairfree(&reply_tmp);
718 sql_grouplist_free(&group_list);
721 *dofallthrough = fallthrough(reply_tmp);
722 pairxlatmove(request, &request->reply->vps, &reply_tmp);
723 pairxlatmove(request, &request->config_items, &check_tmp);
727 * rows == 0. This is like having the username on a line
728 * in the user's file with no check vp's. As such, we treat
729 * it as found and add the reply attributes, so that we
730 * match expected behavior
733 RDEBUG2("User found in group %s",
734 group_list_tmp->groupname);
736 * Now get the reply pairs since the paircompare matched
738 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) {
739 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
740 /* Remove the grouup we added above */
741 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
742 pairfree(&check_tmp);
743 sql_grouplist_free(&group_list);
746 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
747 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
748 group_list_tmp->groupname);
749 /* Remove the grouup we added above */
750 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
751 pairfree(&check_tmp);
752 pairfree(&reply_tmp);
753 sql_grouplist_free(&group_list);
756 *dofallthrough = fallthrough(reply_tmp);
757 pairxlatmove(request, &request->reply->vps, &reply_tmp);
758 pairxlatmove(request, &request->config_items, &check_tmp);
762 * Delete the Sql-Group we added above
763 * And clear out the pairlists
765 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
766 pairfree(&check_tmp);
767 pairfree(&reply_tmp);
770 sql_grouplist_free(&group_list);
775 static int rlm_sql_detach(void *instance)
777 SQL_INST *inst = instance;
779 paircompare_unregister(PW_SQL_GROUP, sql_groupcmp);
784 if (inst->pool) sql_poolfree(inst);
786 if (inst->config->xlat_name) {
787 xlat_unregister(inst->config->xlat_name,(RAD_XLAT_FUNC)sql_xlat, instance);
788 free(inst->config->xlat_name);
792 * Free up dynamically allocated string pointers.
794 for (i = 0; module_config[i].name != NULL; i++) {
796 if (module_config[i].type != PW_TYPE_STRING_PTR) {
801 * Treat 'config' as an opaque array of bytes,
802 * and take the offset into it. There's a
803 * (char*) pointer at that offset, and we want
806 p = (char **) (((char *)inst->config) + module_config[i].offset);
807 if (!*p) { /* nothing allocated */
814 * Catch multiple instances of the module.
816 if (allowed_chars == inst->config->allowed_chars) {
817 allowed_chars = NULL;
826 * FIXME: Call the modules 'destroy' function?
828 lt_dlclose(inst->handle); /* ignore any errors */
836 static int parse_sub_section(CONF_SECTION *parent,
837 UNUSED SQL_INST *instance,
838 rlm_sql_config_section_t *config,
839 rlm_components_t comp)
843 const char *name = section_type_value[comp].section;
845 cs = cf_section_sub_find(parent, name);
846 if (!cs) return 1; /* no section == OK */
848 if (cf_section_parse(cs, config, section_config) < 0) {
849 radlog(L_ERR, "Failed parsing configuration for section %s",
860 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance)
863 const char *xlat_name;
865 inst = rad_malloc(sizeof(SQL_INST));
866 memset(inst, 0, sizeof(SQL_INST));
869 * Export these methods, too. This avoids RTDL_GLOBAL.
871 inst->sql_set_user = sql_set_user;
872 inst->sql_get_socket = sql_get_socket;
873 inst->sql_release_socket = sql_release_socket;
874 inst->sql_escape_func = sql_escape_func;
875 inst->sql_query = rlm_sql_query;
876 inst->sql_select_query = rlm_sql_select_query;
877 inst->sql_fetch_row = rlm_sql_fetch_row;
879 inst->config = rad_malloc(sizeof(SQL_CONFIG));
880 memset(inst->config, 0, sizeof(SQL_CONFIG));
884 * If the configuration parameters can't be parsed, then fail.
886 if ((cf_section_parse(conf, inst->config, module_config) < 0) ||
887 (parse_sub_section(conf, inst,
888 &inst->config->accounting,
889 RLM_COMPONENT_ACCT) < 0) ||
890 (parse_sub_section(conf, inst,
891 &inst->config->postauth,
892 RLM_COMPONENT_POST_AUTH) < 0)) {
893 radlog(L_ERR, "Failed parsing configuration");
897 xlat_name = cf_section_name2(conf);
898 if (xlat_name == NULL) {
899 xlat_name = cf_section_name1(conf);
906 * Allocate room for <instance>-SQL-Group
908 group_name = rad_malloc((strlen(xlat_name) + 1 + 11) * sizeof(char));
909 sprintf(group_name,"%s-SQL-Group", xlat_name);
910 DEBUG("rlm_sql Creating new attribute %s",group_name);
912 memset(&flags, 0, sizeof(flags));
913 dict_addattr(group_name, 0, PW_TYPE_STRING, -1, flags);
914 dattr = dict_attrbyname(group_name);
916 radlog(L_ERR, "rlm_sql: Failed to create attribute %s",
924 if (inst->config->groupmemb_query &&
925 inst->config->groupmemb_query[0]) {
926 DEBUG("rlm_sql: Registering sql_groupcmp for %s",
928 paircompare_register(dattr->attr, PW_USER_NAME,
935 rad_assert(xlat_name);
938 * Register the SQL xlat function
940 inst->config->xlat_name = strdup(xlat_name);
941 xlat_register(xlat_name, (RAD_XLAT_FUNC)sql_xlat, inst);
944 * Sanity check for crazy people.
946 if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
947 radlog(L_ERR, "\"%s\" is NOT an SQL driver!",
948 inst->config->sql_driver);
953 * Load the appropriate driver for our database
955 inst->handle = lt_dlopenext(inst->config->sql_driver);
956 if (inst->handle == NULL) {
957 radlog(L_ERR, "Could not link driver %s: %s",
958 inst->config->sql_driver,
960 radlog(L_ERR, "Make sure it (and all its dependent libraries!)"
961 "are in the search path of your system's ld.");
966 inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle,
967 inst->config->sql_driver);
969 radlog(L_ERR, "Could not link symbol %s: %s",
970 inst->config->sql_driver,
976 radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
977 inst->config->xlat_name, inst->config->sql_driver,
981 * Initialise the connection pool for this instance
983 radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
984 inst->config->xlat_name, inst->config->sql_login,
985 inst->config->sql_server, inst->config->sql_port,
986 inst->config->sql_db);
988 if (sql_init_socketpool(inst) < 0)
991 if (inst->config->groupmemb_query &&
992 inst->config->groupmemb_query[0]) {
993 paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
996 if (inst->config->do_clients) {
997 if (generate_sql_clients(inst) == -1){
998 radlog(L_ERR, "Failed to load clients from SQL.");
1003 allowed_chars = inst->config->allowed_chars;
1007 return RLM_MODULE_OK;
1010 rlm_sql_detach(inst);
1016 static int rlm_sql_authorize(void *instance, REQUEST * request)
1018 VALUE_PAIR *check_tmp = NULL;
1019 VALUE_PAIR *reply_tmp = NULL;
1020 VALUE_PAIR *user_profile = NULL;
1022 int dofallthrough = 1;
1025 SQL_INST *inst = instance;
1026 char querystr[MAX_QUERY_LEN];
1027 char sqlusername[MAX_STRING_LEN];
1029 * the profile username is used as the sqlusername during
1030 * profile checking so that we don't overwrite the orignal
1031 * sqlusername string
1033 char profileusername[MAX_STRING_LEN];
1036 * Set, escape, and check the user attr here
1038 if (sql_set_user(inst, request, sqlusername, NULL) < 0)
1039 return RLM_MODULE_FAIL;
1045 sqlsocket = sql_get_socket(inst);
1046 if (sqlsocket == NULL) {
1047 /* Remove the username we (maybe) added above */
1048 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1049 return RLM_MODULE_FAIL;
1054 * After this point, ALL 'return's MUST release the SQL socket!
1058 * Alright, start by getting the specific entry for the user
1060 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func)) {
1061 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1062 sql_release_socket(inst, sqlsocket);
1063 /* Remove the username we (maybe) added above */
1064 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1065 return RLM_MODULE_FAIL;
1067 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
1069 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1070 sql_release_socket(inst, sqlsocket);
1071 /* Remove the username we (maybe) added above */
1072 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1073 pairfree(&check_tmp);
1074 return RLM_MODULE_FAIL;
1075 } else if (rows > 0) {
1077 * Only do this if *some* check pairs were returned
1079 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
1081 RDEBUG2("User found in radcheck table");
1083 if (inst->config->authorize_reply_query &&
1084 *inst->config->authorize_reply_query) {
1087 * Now get the reply pairs since the paircompare matched
1089 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func)) {
1090 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1091 sql_release_socket(inst, sqlsocket);
1092 /* Remove the username we (maybe) added above */
1093 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1094 pairfree(&check_tmp);
1095 return RLM_MODULE_FAIL;
1097 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
1098 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1099 sql_release_socket(inst, sqlsocket);
1100 /* Remove the username we (maybe) added above */
1101 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1102 pairfree(&check_tmp);
1103 pairfree(&reply_tmp);
1105 return RLM_MODULE_FAIL;
1108 if (!inst->config->read_groups)
1109 dofallthrough = fallthrough(reply_tmp);
1110 pairxlatmove(request, &request->reply->vps, &reply_tmp);
1112 pairxlatmove(request, &request->config_items, &check_tmp);
1117 * Clear out the pairlists
1119 pairfree(&check_tmp);
1120 pairfree(&reply_tmp);
1123 * dofallthrough is set to 1 by default so that if the user information
1124 * is not found, we will still process groups. If the user information,
1125 * however, *is* found, Fall-Through must be set in order to process
1126 * the groups as well
1128 if (dofallthrough) {
1129 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1131 radlog_request(L_ERR, 0, request, "Error processing groups; rejecting user");
1132 sql_release_socket(inst, sqlsocket);
1133 /* Remove the username we (maybe) added above */
1134 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1135 return RLM_MODULE_FAIL;
1136 } else if (rows > 0) {
1142 * repeat the above process with the default profile or User-Profile
1144 if (dofallthrough) {
1145 int profile_found = 0;
1147 * Check for a default_profile or for a User-Profile.
1149 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0);
1150 if (inst->config->default_profile[0] != 0 || user_profile != NULL){
1151 char *profile = inst->config->default_profile;
1153 if (user_profile != NULL)
1154 profile = user_profile->vp_strvalue;
1155 if (profile && strlen(profile)){
1156 RDEBUG("Checking profile %s", profile);
1157 if (sql_set_user(inst, request, profileusername, profile) < 0) {
1158 radlog_request(L_ERR, 0, request, "Error setting profile; rejecting user");
1159 sql_release_socket(inst, sqlsocket);
1160 /* Remove the username we (maybe) added above */
1161 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1162 return RLM_MODULE_FAIL;
1169 if (profile_found) {
1170 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1172 radlog_request(L_ERR, 0, request, "Error processing profile groups; rejecting user");
1173 sql_release_socket(inst, sqlsocket);
1174 /* Remove the username we (maybe) added above */
1175 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1176 return RLM_MODULE_FAIL;
1177 } else if (rows > 0) {
1183 /* Remove the username we (maybe) added above */
1184 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1185 sql_release_socket(inst, sqlsocket);
1188 RDEBUG("User %s not found", sqlusername);
1189 return RLM_MODULE_NOTFOUND;
1191 return RLM_MODULE_OK;
1196 * Generic function for failing between a bunch of queries.
1198 * Uses the same principle as rlm_linelog, expanding the 'reference' config
1199 * item using xlat to figure out what query it should execute.
1201 * If the reference matches multiple config items, and a query fails or
1202 * doesn't update any rows, the next matching config item is used.
1205 static int rlm_sql_redundant(SQL_INST *inst, REQUEST *request,
1206 rlm_sql_config_section_t *section)
1208 int ret = RLM_MODULE_OK;
1210 SQLSOCK *sqlsocket = NULL;
1212 int numaffected = 0;
1216 const char *attr = NULL;
1219 char path[MAX_STRING_LEN];
1220 char querystr[MAX_QUERY_LEN];
1221 char sqlusername[MAX_STRING_LEN];
1225 sql_set_user(inst, request, sqlusername, NULL);
1227 if (section->reference[0] != '.')
1230 if (radius_xlat(p, (sizeof(path) - (p - path)) - 1,
1231 section->reference, request, NULL) < 0)
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;
1254 value = cf_pair_value(pair);
1258 radius_xlat(querystr, sizeof(querystr), value, request,
1263 rlm_sql_query_log(inst, request, section, querystr);
1265 sql_ret = rlm_sql_query(&sqlsocket, inst, querystr);
1266 if (sql_ret == SQL_DOWN)
1267 return RLM_MODULE_FAIL;
1269 rad_assert(sqlsocket);
1272 * Assume all other errors are incidental, and just meant our
1273 * operation failed and its not a client or SQL syntax error.
1276 numaffected = (inst->module->sql_affected_rows)
1277 (sqlsocket, inst->config);
1278 if (numaffected > 0)
1281 RDEBUG("No records updated");
1284 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1287 * We assume all entries with the same name form a redundant
1290 pair = cf_pair_find_next(section->cs, pair, attr);
1293 RDEBUG("No additional queries configured");
1295 ret = RLM_MODULE_NOOP;
1300 RDEBUG("Trying next query...");
1303 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1307 sql_release_socket(inst, sqlsocket);
1313 radlog_request(L_DBG, 0, request, "Ignoring null query");
1315 sql_release_socket(inst, sqlsocket);
1317 return RLM_MODULE_NOOP;
1320 #ifdef WITH_ACCOUNTING
1323 * Accounting: Insert or update session data in our sql table
1325 static int rlm_sql_accounting(void *instance, REQUEST * request) {
1326 SQL_INST *inst = instance;
1328 return rlm_sql_redundant(inst, request, &inst->config->accounting);
1333 #ifdef WITH_SESSION_MGMT
1335 * See if a user is already logged in. Sets request->simul_count to the
1336 * current session count for this user.
1338 * Check twice. If on the first pass the user exceeds his
1339 * max. number of logins, do a second pass and validate all
1340 * logins by querying the terminal server (using eg. SNMP).
1343 static int rlm_sql_checksimul(void *instance, REQUEST * request) {
1345 SQL_INST *inst = instance;
1347 char querystr[MAX_QUERY_LEN];
1348 char sqlusername[MAX_STRING_LEN];
1351 char *call_num = NULL;
1354 uint32_t nas_addr = 0;
1357 /* If simul_count_query is not defined, we don't do any checking */
1358 if (!inst->config->simul_count_query ||
1359 (inst->config->simul_count_query[0] == 0)) {
1360 return RLM_MODULE_NOOP;
1363 if((request->username == NULL) || (request->username->length == 0)) {
1364 radlog_request(L_ERR, 0, request,
1365 "Zero Length username not permitted\n");
1366 return RLM_MODULE_INVALID;
1370 if(sql_set_user(inst, request, sqlusername, NULL) < 0)
1371 return RLM_MODULE_FAIL;
1373 radius_xlat(querystr, sizeof(querystr), inst->config->simul_count_query, request, sql_escape_func);
1375 /* initialize the sql socket */
1376 sqlsocket = sql_get_socket(inst);
1377 if(sqlsocket == NULL)
1378 return RLM_MODULE_FAIL;
1380 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1381 sql_release_socket(inst, sqlsocket);
1382 return RLM_MODULE_FAIL;
1385 ret = rlm_sql_fetch_row(&sqlsocket, inst);
1387 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1388 sql_release_socket(inst, sqlsocket);
1389 return RLM_MODULE_FAIL;
1392 row = sqlsocket->row;
1394 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1395 sql_release_socket(inst, sqlsocket);
1396 return RLM_MODULE_FAIL;
1399 request->simul_count = atoi(row[0]);
1400 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1402 if(request->simul_count < request->simul_max) {
1403 sql_release_socket(inst, sqlsocket);
1404 return RLM_MODULE_OK;
1408 * Looks like too many sessions, so let's start verifying
1409 * them, unless told to rely on count query only.
1411 if (!inst->config->simul_verify_query ||
1412 (inst->config->simul_verify_query[0] == '\0')) {
1413 sql_release_socket(inst, sqlsocket);
1414 return RLM_MODULE_OK;
1417 radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func);
1418 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1419 sql_release_socket(inst, sqlsocket);
1420 return RLM_MODULE_FAIL;
1424 * Setup some stuff, like for MPP detection.
1426 request->simul_count = 0;
1428 if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0)) != NULL)
1429 ipno = vp->vp_ipaddr;
1430 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0)) != NULL)
1431 call_num = vp->vp_strvalue;
1434 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
1435 row = sqlsocket->row;
1439 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1440 sql_release_socket(inst, sqlsocket);
1441 RDEBUG("Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
1442 return RLM_MODULE_FAIL;
1445 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1446 sql_release_socket(inst, sqlsocket);
1447 RDEBUG("Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
1448 return RLM_MODULE_FAIL;
1451 nas_addr = inet_addr(row[3]);
1453 nas_port = atoi(row[4]);
1455 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1459 * Stale record - zap it.
1461 if (inst->config->deletestalesessions == TRUE) {
1462 uint32_t framed_addr = 0;
1467 framed_addr = inet_addr(row[5]);
1469 if (strcmp(row[7], "PPP") == 0)
1471 else if (strcmp(row[7], "SLIP") == 0)
1475 sess_time = atoi(row[8]);
1476 session_zap(request, nas_addr, nas_port,
1477 row[2], row[1], framed_addr,
1481 else if (check == 1) {
1483 * User is still logged in.
1485 ++request->simul_count;
1488 * Does it look like a MPP attempt?
1490 if (row[5] && ipno && inet_addr(row[5]) == ipno)
1491 request->simul_mpp = 2;
1492 else if (row[6] && call_num &&
1493 !strncmp(row[6],call_num,16))
1494 request->simul_mpp = 2;
1498 * Failed to check the terminal server for
1499 * duplicate logins: return an error.
1501 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1502 sql_release_socket(inst, sqlsocket);
1503 radlog_request(L_ERR, 0, request, "Failed to check the terminal server for user '%s'.", row[2]);
1504 return RLM_MODULE_FAIL;
1508 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1509 sql_release_socket(inst, sqlsocket);
1512 * The Auth module apparently looks at request->simul_count,
1513 * not the return value of this module when deciding to deny
1514 * a call for too many sessions.
1516 return RLM_MODULE_OK;
1521 * Postauth: Write a record of the authentication attempt
1523 static int rlm_sql_postauth(void *instance, REQUEST * request) {
1524 SQL_INST *inst = instance;
1526 return rlm_sql_redundant(inst, request, &inst->config->postauth);
1530 * Execute postauth_query after authentication
1534 /* globally exported name */
1535 module_t rlm_sql = {
1538 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1539 rlm_sql_instantiate, /* instantiation */
1540 rlm_sql_detach, /* detach */
1542 NULL, /* authentication */
1543 rlm_sql_authorize, /* authorization */
1544 NULL, /* preaccounting */
1545 #ifdef WITH_ACCOUNTING
1546 rlm_sql_accounting, /* accounting */
1550 #ifdef WITH_SESSION_MGMT
1551 rlm_sql_checksimul, /* checksimul */
1555 NULL, /* pre-proxy */
1556 NULL, /* post-proxy */
1557 rlm_sql_postauth /* post-auth */