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 size_t sql_xlat(void *instance, REQUEST *request,
138 const char *fmt, char *out, size_t freespace)
142 SQL_INST *inst = instance;
143 char querystr[MAX_QUERY_LEN];
144 char sqlusername[MAX_STRING_LEN];
150 * Add SQL-User-Name attribute just in case it is needed
151 * We could search the string fmt for SQL-User-Name to see if this is
154 sql_set_user(inst, request, sqlusername, NULL);
156 * Do an xlat on the provided string (nice recursive operation).
158 if (!radius_xlat(querystr, sizeof(querystr), fmt, request, sql_escape_func, NULL)) {
159 radlog(L_ERR, "rlm_sql (%s): xlat failed.",
160 inst->config->xlat_name);
164 sqlsocket = sql_get_socket(inst);
165 if (sqlsocket == NULL)
168 rlm_sql_query_log(inst, request, NULL, querystr);
171 * If the query starts with any of the following prefixes,
172 * then return the number of rows affected
174 if ((strncasecmp(querystr, "insert", 6) == 0) ||
175 (strncasecmp(querystr, "update", 6) == 0) ||
176 (strncasecmp(querystr, "delete", 6) == 0)) {
178 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
180 if (rlm_sql_query(&sqlsocket,inst,querystr)) {
181 sql_release_socket(inst,sqlsocket);
186 numaffected = (inst->module->sql_affected_rows)(sqlsocket,
188 if (numaffected < 1) {
189 RDEBUG("rlm_sql (%s): SQL query affected no rows",
190 inst->config->xlat_name);
194 * Don't chop the returned number if freespace is
195 * too small. This hack is necessary because
196 * some implementations of snprintf return the
197 * size of the written data, and others return
198 * the size of the data they *would* have written
199 * if the output buffer was large enough.
201 snprintf(buffer, sizeof(buffer), "%d", numaffected);
202 ret = strlen(buffer);
203 if (ret >= freespace){
204 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space",
205 inst->config->xlat_name);
206 (inst->module->sql_finish_query)(sqlsocket,
208 sql_release_socket(inst,sqlsocket);
212 memcpy(out, buffer, ret + 1); /* we did bounds checking above */
214 (inst->module->sql_finish_query)(sqlsocket, inst->config);
215 sql_release_socket(inst,sqlsocket);
217 } /* else it's a SELECT statement */
219 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
220 sql_release_socket(inst,sqlsocket);
224 ret = rlm_sql_fetch_row(&sqlsocket, inst);
226 RDEBUG("SQL query did not succeed");
227 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
228 sql_release_socket(inst,sqlsocket);
232 row = sqlsocket->row;
234 RDEBUG("SQL query did not return any results");
235 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
236 sql_release_socket(inst,sqlsocket);
241 RDEBUG("Null value in first column");
242 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
243 sql_release_socket(inst,sqlsocket);
246 ret = strlen(row[0]);
247 if (ret >= freespace){
248 RDEBUG("Insufficient string space");
249 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
250 sql_release_socket(inst,sqlsocket);
254 strlcpy(out,row[0],freespace);
256 RDEBUG("sql_xlat finished");
258 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
259 sql_release_socket(inst,sqlsocket);
263 static int generate_sql_clients(SQL_INST *inst)
267 char querystr[MAX_QUERY_LEN];
269 char *prefix_ptr = NULL;
273 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
274 inst->config->xlat_name);
276 /* NAS query isn't xlat'ed */
277 strlcpy(querystr, inst->config->nas_query, sizeof(querystr));
278 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
279 inst->config->xlat_name, querystr);
281 sqlsocket = sql_get_socket(inst);
282 if (sqlsocket == NULL)
284 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
288 while(rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
290 row = sqlsocket->row;
294 * The return data for each row MUST be in the following order:
296 * 0. Row ID (currently unused)
297 * 1. Name (or IP address)
301 * 5. Virtual Server (optional)
304 radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
308 radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
312 radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
316 radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
320 DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name,
321 row[1],row[2],row[4]);
323 c = rad_malloc(sizeof(*c));
324 memset(c, 0, sizeof(*c));
326 #ifdef WITH_DYNAMIC_CLIENTS
334 prefix_ptr = strchr(row[1], '/');
336 c->prefix = atoi(prefix_ptr + 1);
337 if ((c->prefix < 0) || (c->prefix > 128)) {
338 radlog(L_ERR, "rlm_sql (%s): Invalid Prefix value '%s' for IP.",
339 inst->config->xlat_name, prefix_ptr + 1);
343 /* Replace '/' with '\0' */
348 * Always get the numeric representation of IP
350 if (ip_hton(row[1], AF_UNSPEC, &c->ipaddr) < 0) {
351 radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s: %s",
352 inst->config->xlat_name,
353 row[1], fr_strerror());
358 ip_ntoh(&c->ipaddr, buffer, sizeof(buffer));
359 c->longname = strdup(buffer);
362 if (c->prefix < 0) switch (c->ipaddr.af) {
374 * Other values (secret, shortname, nastype, virtual_server)
376 c->secret = strdup(row[4]);
377 c->shortname = strdup(row[2]);
379 c->nastype = strdup(row[3]);
381 numf = (inst->module->sql_num_fields)(sqlsocket, inst->config);
382 if ((numf > 5) && (row[5] != NULL) && *row[5]) c->server = strdup(row[5]);
384 DEBUG("rlm_sql (%s): Adding client %s (%s, server=%s) to clients list",
385 inst->config->xlat_name,
386 c->longname,c->shortname, c->server ? c->server : "<none>");
387 if (!client_add(NULL, c)) {
388 sql_release_socket(inst, sqlsocket);
389 DEBUG("rlm_sql (%s): Failed to add client %s (%s) to clients list. Maybe there's a duplicate?",
390 inst->config->xlat_name,
391 c->longname,c->shortname);
396 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
397 sql_release_socket(inst, sqlsocket);
404 * Translate the SQL queries.
406 static size_t sql_escape_func(char *out, size_t outlen, const char *in)
412 * Non-printable characters get replaced with their
413 * mime-encoded equivalents.
416 strchr(allowed_chars, *in) == NULL) {
418 * Only 3 or less bytes available.
424 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
433 * Only one byte left.
453 * Set the SQL user name.
455 * We don't call the escape function here. The resulting string
456 * will be escaped later in the queries xlat so we don't need to
457 * escape it twice. (it will make things wrong if we have an
458 * escape candidate character in the username)
460 int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username)
463 char tmpuser[MAX_STRING_LEN];
466 sqlusername[0]= '\0';
468 /* Remove any user attr we added previously */
469 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
471 if (username != NULL) {
472 strlcpy(tmpuser, username, sizeof(tmpuser));
473 } else if (strlen(inst->config->query_user)) {
474 radius_xlat(tmpuser, sizeof(tmpuser), inst->config->query_user, request, NULL, NULL);
479 strlcpy(sqlusername, tmpuser, MAX_STRING_LEN);
480 RDEBUG2("sql_set_user escaped user --> '%s'", sqlusername);
481 vp = radius_pairmake(request, &request->packet->vps,
482 "SQL-User-Name", NULL, 0);
484 radlog(L_ERR, "%s", fr_strerror());
488 strlcpy(vp->vp_strvalue, tmpuser, sizeof(vp->vp_strvalue));
489 vp->length = strlen(vp->vp_strvalue);
496 static void sql_grouplist_free (SQL_GROUPLIST **group_list)
502 *group_list = (*group_list)->next;
508 static int sql_get_grouplist (SQL_INST *inst, SQLSOCK *sqlsocket, REQUEST *request, SQL_GROUPLIST **group_list)
510 char querystr[MAX_QUERY_LEN];
513 SQL_GROUPLIST *group_list_tmp;
515 /* NOTE: sql_set_user should have been run before calling this function */
517 group_list_tmp = *group_list = NULL;
519 if (!inst->config->groupmemb_query ||
520 (inst->config->groupmemb_query[0] == 0))
523 if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, request, sql_escape_func, NULL)) {
524 radlog_request(L_ERR, 0, request, "xlat \"%s\" failed.",
525 inst->config->groupmemb_query);
529 if (rlm_sql_select_query(&sqlsocket, inst, querystr) < 0) {
532 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
533 row = sqlsocket->row;
537 RDEBUG("row[0] returned NULL");
538 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
539 sql_grouplist_free(group_list);
542 if (*group_list == NULL) {
543 *group_list = rad_malloc(sizeof(SQL_GROUPLIST));
544 group_list_tmp = *group_list;
546 rad_assert(group_list_tmp != NULL);
547 group_list_tmp->next = rad_malloc(sizeof(SQL_GROUPLIST));
548 group_list_tmp = group_list_tmp->next;
550 group_list_tmp->next = NULL;
551 strlcpy(group_list_tmp->groupname, row[0], MAX_STRING_LEN);
554 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
561 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
562 * with the group memberships reciding in sql
563 * The group membership query should only return one element which is the username. The returned
564 * username will then be checked with the passed check string.
567 static int sql_groupcmp(void *instance, REQUEST *request, VALUE_PAIR *request_vp, VALUE_PAIR *check,
568 VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
571 SQL_INST *inst = instance;
572 char sqlusername[MAX_STRING_LEN];
573 SQL_GROUPLIST *group_list, *group_list_tmp;
575 check_pairs = check_pairs;
576 reply_pairs = reply_pairs;
577 request_vp = request_vp;
579 RDEBUG("sql_groupcmp");
580 if (!check || !check->vp_strvalue || !check->length){
581 RDEBUG("sql_groupcmp: Illegal group name");
585 RDEBUG("sql_groupcmp: NULL request");
589 * Set, escape, and check the user attr here
591 if (sql_set_user(inst, request, sqlusername, NULL) < 0)
595 * Get a socket for this lookup
597 sqlsocket = sql_get_socket(inst);
598 if (sqlsocket == NULL) {
599 /* Remove the username we (maybe) added above */
600 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
605 * Get the list of groups this user is a member of
607 if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
608 radlog_request(L_ERR, 0, request,
609 "Error getting group membership");
610 /* Remove the username we (maybe) added above */
611 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
612 sql_release_socket(inst, sqlsocket);
616 for (group_list_tmp = group_list; group_list_tmp != NULL; group_list_tmp = group_list_tmp->next) {
617 if (strcmp(group_list_tmp->groupname, check->vp_strvalue) == 0){
618 RDEBUG("sql_groupcmp finished: User is a member of group %s",
620 /* Free the grouplist */
621 sql_grouplist_free(&group_list);
622 /* Remove the username we (maybe) added above */
623 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
624 sql_release_socket(inst, sqlsocket);
629 /* Free the grouplist */
630 sql_grouplist_free(&group_list);
631 /* Remove the username we (maybe) added above */
632 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
633 sql_release_socket(inst,sqlsocket);
635 RDEBUG("sql_groupcmp finished: User is NOT a member of group %s",
643 static int rlm_sql_process_groups(SQL_INST *inst, REQUEST *request, SQLSOCK *sqlsocket, int *dofallthrough)
645 VALUE_PAIR *check_tmp = NULL;
646 VALUE_PAIR *reply_tmp = NULL;
647 SQL_GROUPLIST *group_list, *group_list_tmp;
648 VALUE_PAIR *sql_group = NULL;
649 char querystr[MAX_QUERY_LEN];
654 * Get the list of groups this user is a member of
656 if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
657 radlog_request(L_ERR, 0, request, "Error retrieving group list");
661 for (group_list_tmp = group_list; group_list_tmp != NULL && *dofallthrough != 0; group_list_tmp = group_list_tmp->next) {
663 * Add the Sql-Group attribute to the request list so we know
664 * which group we're retrieving attributes for
666 sql_group = pairmake("Sql-Group", group_list_tmp->groupname, T_OP_EQ);
668 radlog_request(L_ERR, 0, request,
669 "Error creating Sql-Group attribute");
670 sql_grouplist_free(&group_list);
673 pairadd(&request->packet->vps, sql_group);
674 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func, NULL)) {
675 radlog_request(L_ERR, 0, request,
676 "Error generating query; rejecting user");
677 /* Remove the grouup we added above */
678 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
679 sql_grouplist_free(&group_list);
682 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
684 radlog_request(L_ERR, 0, request, "Error retrieving check pairs for group %s",
685 group_list_tmp->groupname);
686 /* Remove the grouup we added above */
687 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
688 pairfree(&check_tmp);
689 sql_grouplist_free(&group_list);
691 } else if (rows > 0) {
693 * Only do this if *some* check pairs were returned
695 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
697 RDEBUG2("User found in group %s",
698 group_list_tmp->groupname);
700 * Now get the reply pairs since the paircompare matched
702 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func, NULL)) {
703 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
704 /* Remove the grouup we added above */
705 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
706 pairfree(&check_tmp);
707 sql_grouplist_free(&group_list);
710 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
711 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
712 group_list_tmp->groupname);
713 /* Remove the grouup we added above */
714 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
715 pairfree(&check_tmp);
716 pairfree(&reply_tmp);
717 sql_grouplist_free(&group_list);
720 *dofallthrough = fallthrough(reply_tmp);
721 pairxlatmove(request, &request->reply->vps, &reply_tmp);
722 pairxlatmove(request, &request->config_items, &check_tmp);
726 * rows == 0. This is like having the username on a line
727 * in the user's file with no check vp's. As such, we treat
728 * it as found and add the reply attributes, so that we
729 * match expected behavior
732 RDEBUG2("User found in group %s",
733 group_list_tmp->groupname);
735 * Now get the reply pairs since the paircompare matched
737 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func, NULL)) {
738 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
739 /* Remove the grouup we added above */
740 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
741 pairfree(&check_tmp);
742 sql_grouplist_free(&group_list);
745 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
746 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
747 group_list_tmp->groupname);
748 /* Remove the grouup we added above */
749 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
750 pairfree(&check_tmp);
751 pairfree(&reply_tmp);
752 sql_grouplist_free(&group_list);
755 *dofallthrough = fallthrough(reply_tmp);
756 pairxlatmove(request, &request->reply->vps, &reply_tmp);
757 pairxlatmove(request, &request->config_items, &check_tmp);
761 * Delete the Sql-Group we added above
762 * And clear out the pairlists
764 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
765 pairfree(&check_tmp);
766 pairfree(&reply_tmp);
769 sql_grouplist_free(&group_list);
774 static int rlm_sql_detach(void *instance)
776 SQL_INST *inst = instance;
778 paircompare_unregister(PW_SQL_GROUP, sql_groupcmp);
783 if (inst->pool) sql_poolfree(inst);
785 if (inst->config->xlat_name) {
786 xlat_unregister(inst->config->xlat_name, sql_xlat, instance);
787 free(inst->config->xlat_name);
791 * Free up dynamically allocated string pointers.
793 for (i = 0; module_config[i].name != NULL; i++) {
795 if (module_config[i].type != PW_TYPE_STRING_PTR) {
800 * Treat 'config' as an opaque array of bytes,
801 * and take the offset into it. There's a
802 * (char*) pointer at that offset, and we want
805 p = (char **) (((char *)inst->config) + module_config[i].offset);
806 if (!*p) { /* nothing allocated */
813 * Catch multiple instances of the module.
815 if (allowed_chars == inst->config->allowed_chars) {
816 allowed_chars = NULL;
825 * FIXME: Call the modules 'destroy' function?
827 lt_dlclose(inst->handle); /* ignore any errors */
835 static int parse_sub_section(CONF_SECTION *parent,
836 UNUSED SQL_INST *instance,
837 rlm_sql_config_section_t *config,
838 rlm_components_t comp)
842 const char *name = section_type_value[comp].section;
844 cs = cf_section_sub_find(parent, name);
846 radlog(L_INFO, "Couldn't find configuration for %s. Will return NOOP for calls from this section.", name);
851 if (cf_section_parse(cs, config, section_config) < 0) {
852 radlog(L_ERR, "Failed parsing configuration for section %s",
863 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance)
866 const char *xlat_name;
868 inst = rad_malloc(sizeof(SQL_INST));
869 memset(inst, 0, sizeof(SQL_INST));
872 * Export these methods, too. This avoids RTDL_GLOBAL.
874 inst->sql_set_user = sql_set_user;
875 inst->sql_get_socket = sql_get_socket;
876 inst->sql_release_socket = sql_release_socket;
877 inst->sql_escape_func = sql_escape_func;
878 inst->sql_query = rlm_sql_query;
879 inst->sql_select_query = rlm_sql_select_query;
880 inst->sql_fetch_row = rlm_sql_fetch_row;
882 inst->config = rad_malloc(sizeof(SQL_CONFIG));
883 memset(inst->config, 0, sizeof(SQL_CONFIG));
887 * If the configuration parameters can't be parsed, then fail.
889 if ((cf_section_parse(conf, inst->config, module_config) < 0) ||
890 (parse_sub_section(conf, inst,
891 &inst->config->accounting,
892 RLM_COMPONENT_ACCT) < 0) ||
893 (parse_sub_section(conf, inst,
894 &inst->config->postauth,
895 RLM_COMPONENT_POST_AUTH) < 0)) {
896 radlog(L_ERR, "Failed parsing configuration");
900 xlat_name = cf_section_name2(conf);
901 if (xlat_name == NULL) {
902 xlat_name = cf_section_name1(conf);
909 * Allocate room for <instance>-SQL-Group
911 group_name = rad_malloc((strlen(xlat_name) + 1 + 11) * sizeof(char));
912 sprintf(group_name,"%s-SQL-Group", xlat_name);
913 DEBUG("rlm_sql Creating new attribute %s",group_name);
915 memset(&flags, 0, sizeof(flags));
916 dict_addattr(group_name, 0, PW_TYPE_STRING, -1, flags);
917 dattr = dict_attrbyname(group_name);
919 radlog(L_ERR, "rlm_sql: Failed to create attribute %s",
927 if (inst->config->groupmemb_query &&
928 inst->config->groupmemb_query[0]) {
929 DEBUG("rlm_sql: Registering sql_groupcmp for %s",
931 paircompare_register(dattr->attr, PW_USER_NAME,
938 rad_assert(xlat_name);
941 * Register the SQL xlat function
943 inst->config->xlat_name = strdup(xlat_name);
944 xlat_register(xlat_name, sql_xlat, inst);
947 * Sanity check for crazy people.
949 if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
950 radlog(L_ERR, "\"%s\" is NOT an SQL driver!",
951 inst->config->sql_driver);
956 * Load the appropriate driver for our database
958 inst->handle = lt_dlopenext(inst->config->sql_driver);
959 if (inst->handle == NULL) {
960 radlog(L_ERR, "Could not link driver %s: %s",
961 inst->config->sql_driver,
963 radlog(L_ERR, "Make sure it (and all its dependent libraries!)"
964 "are in the search path of your system's ld.");
969 inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle,
970 inst->config->sql_driver);
972 radlog(L_ERR, "Could not link symbol %s: %s",
973 inst->config->sql_driver,
979 radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
980 inst->config->xlat_name, inst->config->sql_driver,
984 * Initialise the connection pool for this instance
986 radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
987 inst->config->xlat_name, inst->config->sql_login,
988 inst->config->sql_server, inst->config->sql_port,
989 inst->config->sql_db);
991 if (sql_init_socketpool(inst) < 0)
994 if (inst->config->groupmemb_query &&
995 inst->config->groupmemb_query[0]) {
996 paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
999 if (inst->config->do_clients) {
1000 if (generate_sql_clients(inst) == -1){
1001 radlog(L_ERR, "Failed to load clients from SQL.");
1006 allowed_chars = inst->config->allowed_chars;
1010 return RLM_MODULE_OK;
1013 rlm_sql_detach(inst);
1019 static int rlm_sql_authorize(void *instance, REQUEST * request)
1021 VALUE_PAIR *check_tmp = NULL;
1022 VALUE_PAIR *reply_tmp = NULL;
1023 VALUE_PAIR *user_profile = NULL;
1025 int dofallthrough = 1;
1028 SQL_INST *inst = instance;
1029 char querystr[MAX_QUERY_LEN];
1030 char sqlusername[MAX_STRING_LEN];
1032 * the profile username is used as the sqlusername during
1033 * profile checking so that we don't overwrite the orignal
1034 * sqlusername string
1036 char profileusername[MAX_STRING_LEN];
1039 * Set, escape, and check the user attr here
1041 if (sql_set_user(inst, request, sqlusername, NULL) < 0)
1042 return RLM_MODULE_FAIL;
1048 sqlsocket = sql_get_socket(inst);
1049 if (sqlsocket == NULL) {
1050 /* Remove the username we (maybe) added above */
1051 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1052 return RLM_MODULE_FAIL;
1057 * After this point, ALL 'return's MUST release the SQL socket!
1061 * Alright, start by getting the specific entry for the user
1063 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func, NULL)) {
1064 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1065 sql_release_socket(inst, sqlsocket);
1066 /* Remove the username we (maybe) added above */
1067 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1068 return RLM_MODULE_FAIL;
1070 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
1072 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1073 sql_release_socket(inst, sqlsocket);
1074 /* Remove the username we (maybe) added above */
1075 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1076 pairfree(&check_tmp);
1077 return RLM_MODULE_FAIL;
1078 } else if (rows > 0) {
1080 * Only do this if *some* check pairs were returned
1082 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
1084 RDEBUG2("User found in radcheck table");
1086 if (inst->config->authorize_reply_query &&
1087 *inst->config->authorize_reply_query) {
1090 * Now get the reply pairs since the paircompare matched
1092 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func, NULL)) {
1093 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1094 sql_release_socket(inst, sqlsocket);
1095 /* Remove the username we (maybe) added above */
1096 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1097 pairfree(&check_tmp);
1098 return RLM_MODULE_FAIL;
1100 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
1101 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1102 sql_release_socket(inst, sqlsocket);
1103 /* Remove the username we (maybe) added above */
1104 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1105 pairfree(&check_tmp);
1106 pairfree(&reply_tmp);
1108 return RLM_MODULE_FAIL;
1111 if (!inst->config->read_groups)
1112 dofallthrough = fallthrough(reply_tmp);
1113 pairxlatmove(request, &request->reply->vps, &reply_tmp);
1115 pairxlatmove(request, &request->config_items, &check_tmp);
1120 * Clear out the pairlists
1122 pairfree(&check_tmp);
1123 pairfree(&reply_tmp);
1126 * dofallthrough is set to 1 by default so that if the user information
1127 * is not found, we will still process groups. If the user information,
1128 * however, *is* found, Fall-Through must be set in order to process
1129 * the groups as well
1131 if (dofallthrough) {
1132 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1134 radlog_request(L_ERR, 0, request, "Error processing groups; rejecting user");
1135 sql_release_socket(inst, sqlsocket);
1136 /* Remove the username we (maybe) added above */
1137 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1138 return RLM_MODULE_FAIL;
1139 } else if (rows > 0) {
1145 * repeat the above process with the default profile or User-Profile
1147 if (dofallthrough) {
1148 int profile_found = 0;
1150 * Check for a default_profile or for a User-Profile.
1152 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0);
1153 if (inst->config->default_profile[0] != 0 || user_profile != NULL){
1154 char *profile = inst->config->default_profile;
1156 if (user_profile != NULL)
1157 profile = user_profile->vp_strvalue;
1158 if (profile && strlen(profile)){
1159 RDEBUG("Checking profile %s", profile);
1160 if (sql_set_user(inst, request, profileusername, profile) < 0) {
1161 radlog_request(L_ERR, 0, request, "Error setting profile; rejecting user");
1162 sql_release_socket(inst, sqlsocket);
1163 /* Remove the username we (maybe) added above */
1164 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1165 return RLM_MODULE_FAIL;
1172 if (profile_found) {
1173 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1175 radlog_request(L_ERR, 0, request, "Error processing profile groups; rejecting user");
1176 sql_release_socket(inst, sqlsocket);
1177 /* Remove the username we (maybe) added above */
1178 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1179 return RLM_MODULE_FAIL;
1180 } else if (rows > 0) {
1186 /* Remove the username we (maybe) added above */
1187 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1188 sql_release_socket(inst, sqlsocket);
1191 RDEBUG("User %s not found", sqlusername);
1192 return RLM_MODULE_NOTFOUND;
1194 return RLM_MODULE_OK;
1199 * Generic function for failing between a bunch of queries.
1201 * Uses the same principle as rlm_linelog, expanding the 'reference' config
1202 * item using xlat to figure out what query it should execute.
1204 * If the reference matches multiple config items, and a query fails or
1205 * doesn't update any rows, the next matching config item is used.
1208 static int rlm_sql_redundant(SQL_INST *inst, REQUEST *request,
1209 rlm_sql_config_section_t *section)
1211 int ret = RLM_MODULE_OK;
1213 SQLSOCK *sqlsocket = NULL;
1215 int numaffected = 0;
1219 const char *attr = NULL;
1222 char path[MAX_STRING_LEN];
1223 char querystr[MAX_QUERY_LEN];
1224 char sqlusername[MAX_STRING_LEN];
1228 if (!section || !section->reference) {
1229 RDEBUG("No configuration provided for this section");
1231 return RLM_MODULE_NOOP;
1234 sql_set_user(inst, request, sqlusername, NULL);
1236 if (section->reference[0] != '.')
1239 if (radius_xlat(p, (sizeof(path) - (p - path)) - 1,
1240 section->reference, request, NULL, NULL) < 0)
1241 return RLM_MODULE_FAIL;
1243 item = cf_reference_item(NULL, section->cs, path);
1245 return RLM_MODULE_FAIL;
1247 if (cf_item_is_section(item)){
1248 radlog(L_ERR, "Sections are not supported as references");
1250 return RLM_MODULE_FAIL;
1253 pair = cf_itemtopair(item);
1254 attr = cf_pair_attr(pair);
1256 RDEBUG2("Using query template '%s'", attr);
1258 sqlsocket = sql_get_socket(inst);
1259 if (sqlsocket == NULL)
1260 return RLM_MODULE_FAIL;
1263 value = cf_pair_value(pair);
1267 radius_xlat(querystr, sizeof(querystr), value, request,
1268 sql_escape_func, NULL);
1272 rlm_sql_query_log(inst, request, section, querystr);
1274 sql_ret = rlm_sql_query(&sqlsocket, inst, querystr);
1275 if (sql_ret == SQL_DOWN)
1276 return RLM_MODULE_FAIL;
1278 rad_assert(sqlsocket);
1281 * Assume all other errors are incidental, and just meant our
1282 * operation failed and its not a client or SQL syntax error.
1285 numaffected = (inst->module->sql_affected_rows)
1286 (sqlsocket, inst->config);
1287 if (numaffected > 0)
1290 RDEBUG("No records updated");
1293 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1296 * We assume all entries with the same name form a redundant
1299 pair = cf_pair_find_next(section->cs, pair, attr);
1302 RDEBUG("No additional queries configured");
1304 ret = RLM_MODULE_NOOP;
1309 RDEBUG("Trying next query...");
1312 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1316 sql_release_socket(inst, sqlsocket);
1322 RDEBUG("Ignoring null query");
1324 sql_release_socket(inst, sqlsocket);
1326 return RLM_MODULE_NOOP;
1329 #ifdef WITH_ACCOUNTING
1332 * Accounting: Insert or update session data in our sql table
1334 static int rlm_sql_accounting(void *instance, REQUEST * request) {
1335 SQL_INST *inst = instance;
1337 return rlm_sql_redundant(inst, request, &inst->config->accounting);
1342 #ifdef WITH_SESSION_MGMT
1344 * See if a user is already logged in. Sets request->simul_count to the
1345 * current session count for this user.
1347 * Check twice. If on the first pass the user exceeds his
1348 * max. number of logins, do a second pass and validate all
1349 * logins by querying the terminal server (using eg. SNMP).
1352 static int rlm_sql_checksimul(void *instance, REQUEST * request) {
1354 SQL_INST *inst = instance;
1356 char querystr[MAX_QUERY_LEN];
1357 char sqlusername[MAX_STRING_LEN];
1360 char *call_num = NULL;
1363 uint32_t nas_addr = 0;
1366 /* If simul_count_query is not defined, we don't do any checking */
1367 if (!inst->config->simul_count_query ||
1368 (inst->config->simul_count_query[0] == 0)) {
1369 return RLM_MODULE_NOOP;
1372 if((request->username == NULL) || (request->username->length == 0)) {
1373 radlog_request(L_ERR, 0, request,
1374 "Zero Length username not permitted\n");
1375 return RLM_MODULE_INVALID;
1379 if(sql_set_user(inst, request, sqlusername, NULL) < 0)
1380 return RLM_MODULE_FAIL;
1382 radius_xlat(querystr, sizeof(querystr), inst->config->simul_count_query, request, sql_escape_func, NULL);
1384 /* initialize the sql socket */
1385 sqlsocket = sql_get_socket(inst);
1386 if(sqlsocket == NULL)
1387 return RLM_MODULE_FAIL;
1389 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1390 sql_release_socket(inst, sqlsocket);
1391 return RLM_MODULE_FAIL;
1394 ret = rlm_sql_fetch_row(&sqlsocket, inst);
1396 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1397 sql_release_socket(inst, sqlsocket);
1398 return RLM_MODULE_FAIL;
1401 row = sqlsocket->row;
1403 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1404 sql_release_socket(inst, sqlsocket);
1405 return RLM_MODULE_FAIL;
1408 request->simul_count = atoi(row[0]);
1409 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1411 if(request->simul_count < request->simul_max) {
1412 sql_release_socket(inst, sqlsocket);
1413 return RLM_MODULE_OK;
1417 * Looks like too many sessions, so let's start verifying
1418 * them, unless told to rely on count query only.
1420 if (!inst->config->simul_verify_query ||
1421 (inst->config->simul_verify_query[0] == '\0')) {
1422 sql_release_socket(inst, sqlsocket);
1423 return RLM_MODULE_OK;
1426 radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func, NULL);
1427 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1428 sql_release_socket(inst, sqlsocket);
1429 return RLM_MODULE_FAIL;
1433 * Setup some stuff, like for MPP detection.
1435 request->simul_count = 0;
1437 if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0)) != NULL)
1438 ipno = vp->vp_ipaddr;
1439 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0)) != NULL)
1440 call_num = vp->vp_strvalue;
1443 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
1444 row = sqlsocket->row;
1448 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1449 sql_release_socket(inst, sqlsocket);
1450 RDEBUG("Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
1451 return RLM_MODULE_FAIL;
1454 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1455 sql_release_socket(inst, sqlsocket);
1456 RDEBUG("Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
1457 return RLM_MODULE_FAIL;
1460 nas_addr = inet_addr(row[3]);
1462 nas_port = atoi(row[4]);
1464 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1468 * Stale record - zap it.
1470 if (inst->config->deletestalesessions == TRUE) {
1471 uint32_t framed_addr = 0;
1476 framed_addr = inet_addr(row[5]);
1478 if (strcmp(row[7], "PPP") == 0)
1480 else if (strcmp(row[7], "SLIP") == 0)
1484 sess_time = atoi(row[8]);
1485 session_zap(request, nas_addr, nas_port,
1486 row[2], row[1], framed_addr,
1490 else if (check == 1) {
1492 * User is still logged in.
1494 ++request->simul_count;
1497 * Does it look like a MPP attempt?
1499 if (row[5] && ipno && inet_addr(row[5]) == ipno)
1500 request->simul_mpp = 2;
1501 else if (row[6] && call_num &&
1502 !strncmp(row[6],call_num,16))
1503 request->simul_mpp = 2;
1507 * Failed to check the terminal server for
1508 * duplicate logins: return an error.
1510 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1511 sql_release_socket(inst, sqlsocket);
1512 radlog_request(L_ERR, 0, request, "Failed to check the terminal server for user '%s'.", row[2]);
1513 return RLM_MODULE_FAIL;
1517 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1518 sql_release_socket(inst, sqlsocket);
1521 * The Auth module apparently looks at request->simul_count,
1522 * not the return value of this module when deciding to deny
1523 * a call for too many sessions.
1525 return RLM_MODULE_OK;
1530 * Postauth: Write a record of the authentication attempt
1532 static int rlm_sql_postauth(void *instance, REQUEST * request) {
1533 SQL_INST *inst = instance;
1535 return rlm_sql_redundant(inst, request, &inst->config->postauth);
1539 * Execute postauth_query after authentication
1543 /* globally exported name */
1544 module_t rlm_sql = {
1547 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1548 rlm_sql_instantiate, /* instantiation */
1549 rlm_sql_detach, /* detach */
1551 NULL, /* authentication */
1552 rlm_sql_authorize, /* authorization */
1553 NULL, /* preaccounting */
1554 #ifdef WITH_ACCOUNTING
1555 rlm_sql_accounting, /* accounting */
1559 #ifdef WITH_SESSION_MGMT
1560 rlm_sql_checksimul, /* checksimul */
1564 NULL, /* pre-proxy */
1565 NULL, /* post-proxy */
1566 rlm_sql_postauth /* post-auth */