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 const CONF_PARSER section_config[] = {
43 { "reference", PW_TYPE_STRING_PTR,
44 offsetof(rlm_sql_config_section_t, reference), NULL, ".query"},
46 {"logfile", PW_TYPE_STRING_PTR,
47 offsetof(rlm_sql_config_section_t, logfile), NULL, NULL},
48 {NULL, -1, 0, NULL, NULL}
51 static const CONF_PARSER module_config[] = {
52 {"driver",PW_TYPE_STRING_PTR,
53 offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"},
54 {"server",PW_TYPE_STRING_PTR,
55 offsetof(SQL_CONFIG,sql_server), NULL, "localhost"},
56 {"port",PW_TYPE_STRING_PTR,
57 offsetof(SQL_CONFIG,sql_port), NULL, ""},
58 {"login", PW_TYPE_STRING_PTR,
59 offsetof(SQL_CONFIG,sql_login), NULL, ""},
60 {"password", PW_TYPE_STRING_PTR,
61 offsetof(SQL_CONFIG,sql_password), NULL, ""},
62 {"radius_db", PW_TYPE_STRING_PTR,
63 offsetof(SQL_CONFIG,sql_db), NULL, "radius"},
64 {"filename", PW_TYPE_FILENAME, /* for sqlite */
65 offsetof(SQL_CONFIG,sql_file), NULL, NULL},
66 {"read_groups", PW_TYPE_BOOLEAN,
67 offsetof(SQL_CONFIG,read_groups), NULL, "yes"},
68 {"readclients", PW_TYPE_BOOLEAN,
69 offsetof(SQL_CONFIG,do_clients), NULL, "no"},
70 {"deletestalesessions", PW_TYPE_BOOLEAN,
71 offsetof(SQL_CONFIG,deletestalesessions), NULL, "yes"},
72 {"sql_user_name", PW_TYPE_STRING_PTR,
73 offsetof(SQL_CONFIG,query_user), NULL, ""},
74 {"logfile", PW_TYPE_STRING_PTR,
75 offsetof(SQL_CONFIG,logfile), NULL, NULL},
76 {"default_user_profile", PW_TYPE_STRING_PTR,
77 offsetof(SQL_CONFIG,default_profile), NULL, ""},
78 {"nas_query", PW_TYPE_STRING_PTR,
79 offsetof(SQL_CONFIG,nas_query), NULL, "SELECT id,nasname,shortname,type,secret FROM nas"},
80 {"authorize_check_query", PW_TYPE_STRING_PTR,
81 offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
82 {"authorize_reply_query", PW_TYPE_STRING_PTR,
83 offsetof(SQL_CONFIG,authorize_reply_query), NULL, NULL},
84 {"authorize_group_check_query", PW_TYPE_STRING_PTR,
85 offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
86 {"authorize_group_reply_query", PW_TYPE_STRING_PTR,
87 offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""},
88 {"group_membership_query", PW_TYPE_STRING_PTR,
89 offsetof(SQL_CONFIG,groupmemb_query), NULL, NULL},
90 #ifdef WITH_SESSION_MGMT
91 {"simul_count_query", PW_TYPE_STRING_PTR,
92 offsetof(SQL_CONFIG,simul_count_query), NULL, ""},
93 {"simul_verify_query", PW_TYPE_STRING_PTR,
94 offsetof(SQL_CONFIG,simul_verify_query), NULL, ""},
96 {"safe-characters", PW_TYPE_STRING_PTR,
97 offsetof(SQL_CONFIG,allowed_chars), NULL,
98 "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
101 * This only works for a few drivers.
103 {"query_timeout", PW_TYPE_INTEGER,
104 offsetof(SQL_CONFIG,query_timeout), NULL, NULL},
106 {NULL, -1, 0, NULL, NULL}
110 * Fall-Through checking function from rlm_files.c
112 static int fallthrough(VALUE_PAIR *vp)
115 tmp = pairfind(vp, PW_FALL_THROUGH, 0);
117 return tmp ? tmp->vp_integer : 0;
125 static int generate_sql_clients(SQL_INST *inst);
126 static size_t sql_escape_func(REQUEST *, char *out, size_t outlen, const char *in, void *arg);
131 * For selects the first value of the first column will be returned,
132 * for inserts, updates and deletes the number of rows afftected will be
135 static size_t sql_xlat(void *instance, REQUEST *request,
136 const char *fmt, char *out, size_t freespace)
140 SQL_INST *inst = instance;
141 char querystr[MAX_QUERY_LEN];
142 char sqlusername[MAX_STRING_LEN];
148 * Add SQL-User-Name attribute just in case it is needed
149 * We could search the string fmt for SQL-User-Name to see if this is
152 sql_set_user(inst, request, sqlusername, NULL);
154 * Do an xlat on the provided string (nice recursive operation).
156 if (!radius_xlat(querystr, sizeof(querystr), fmt, request, sql_escape_func, inst)) {
157 radlog(L_ERR, "rlm_sql (%s): xlat failed.",
158 inst->config->xlat_name);
162 sqlsocket = sql_get_socket(inst);
163 if (sqlsocket == NULL)
166 rlm_sql_query_log(inst, request, NULL, querystr);
169 * If the query starts with any of the following prefixes,
170 * then return the number of rows affected
172 if ((strncasecmp(querystr, "insert", 6) == 0) ||
173 (strncasecmp(querystr, "update", 6) == 0) ||
174 (strncasecmp(querystr, "delete", 6) == 0)) {
176 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
178 if (rlm_sql_query(&sqlsocket,inst,querystr)) {
179 sql_release_socket(inst,sqlsocket);
184 numaffected = (inst->module->sql_affected_rows)(sqlsocket,
186 if (numaffected < 1) {
187 RDEBUG("rlm_sql (%s): SQL query affected no rows",
188 inst->config->xlat_name);
192 * Don't chop the returned number if freespace is
193 * too small. This hack is necessary because
194 * some implementations of snprintf return the
195 * size of the written data, and others return
196 * the size of the data they *would* have written
197 * if the output buffer was large enough.
199 snprintf(buffer, sizeof(buffer), "%d", numaffected);
200 ret = strlen(buffer);
201 if (ret >= freespace){
202 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space",
203 inst->config->xlat_name);
204 (inst->module->sql_finish_query)(sqlsocket,
206 sql_release_socket(inst,sqlsocket);
210 memcpy(out, buffer, ret + 1); /* we did bounds checking above */
212 (inst->module->sql_finish_query)(sqlsocket, inst->config);
213 sql_release_socket(inst,sqlsocket);
215 } /* else it's a SELECT statement */
217 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
218 sql_release_socket(inst,sqlsocket);
222 ret = rlm_sql_fetch_row(&sqlsocket, inst);
224 RDEBUG("SQL query did not succeed");
225 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
226 sql_release_socket(inst,sqlsocket);
230 row = sqlsocket->row;
232 RDEBUG("SQL query did not return any results");
233 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
234 sql_release_socket(inst,sqlsocket);
239 RDEBUG("Null value in first column");
240 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
241 sql_release_socket(inst,sqlsocket);
244 ret = strlen(row[0]);
245 if (ret >= freespace){
246 RDEBUG("Insufficient string space");
247 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
248 sql_release_socket(inst,sqlsocket);
252 strlcpy(out,row[0],freespace);
254 RDEBUG("sql_xlat finished");
256 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
257 sql_release_socket(inst,sqlsocket);
261 static int generate_sql_clients(SQL_INST *inst)
265 char querystr[MAX_QUERY_LEN];
267 char *prefix_ptr = NULL;
271 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
272 inst->config->xlat_name);
274 /* NAS query isn't xlat'ed */
275 strlcpy(querystr, inst->config->nas_query, sizeof(querystr));
276 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
277 inst->config->xlat_name, querystr);
279 sqlsocket = sql_get_socket(inst);
280 if (sqlsocket == NULL)
282 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
286 while(rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
288 row = sqlsocket->row;
292 * The return data for each row MUST be in the following order:
294 * 0. Row ID (currently unused)
295 * 1. Name (or IP address)
299 * 5. Virtual Server (optional)
302 radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
306 radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
310 radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
314 radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
318 DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name,
319 row[1],row[2],row[4]);
321 c = rad_malloc(sizeof(*c));
322 memset(c, 0, sizeof(*c));
324 #ifdef WITH_DYNAMIC_CLIENTS
332 prefix_ptr = strchr(row[1], '/');
334 c->prefix = atoi(prefix_ptr + 1);
335 if ((c->prefix < 0) || (c->prefix > 128)) {
336 radlog(L_ERR, "rlm_sql (%s): Invalid Prefix value '%s' for IP.",
337 inst->config->xlat_name, prefix_ptr + 1);
341 /* Replace '/' with '\0' */
346 * Always get the numeric representation of IP
348 if (ip_hton(row[1], AF_UNSPEC, &c->ipaddr) < 0) {
349 radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s: %s",
350 inst->config->xlat_name,
351 row[1], fr_strerror());
356 ip_ntoh(&c->ipaddr, buffer, sizeof(buffer));
357 c->longname = strdup(buffer);
360 if (c->prefix < 0) switch (c->ipaddr.af) {
372 * Other values (secret, shortname, nastype, virtual_server)
374 c->secret = strdup(row[4]);
375 c->shortname = strdup(row[2]);
377 c->nastype = strdup(row[3]);
379 numf = (inst->module->sql_num_fields)(sqlsocket, inst->config);
380 if ((numf > 5) && (row[5] != NULL) && *row[5]) c->server = strdup(row[5]);
382 DEBUG("rlm_sql (%s): Adding client %s (%s, server=%s) to clients list",
383 inst->config->xlat_name,
384 c->longname,c->shortname, c->server ? c->server : "<none>");
385 if (!client_add(NULL, c)) {
386 sql_release_socket(inst, sqlsocket);
387 DEBUG("rlm_sql (%s): Failed to add client %s (%s) to clients list. Maybe there's a duplicate?",
388 inst->config->xlat_name,
389 c->longname,c->shortname);
394 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
395 sql_release_socket(inst, sqlsocket);
402 * Translate the SQL queries.
404 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, const char *in, void *arg)
406 SQL_INST *inst = arg;
411 * Non-printable characters get replaced with their
412 * mime-encoded equivalents.
415 strchr(inst->config->allowed_chars, *in) == NULL) {
417 * Only 3 or less bytes available.
423 snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
432 * Only one byte left.
452 * Set the SQL user name.
454 * We don't call the escape function here. The resulting string
455 * will be escaped later in the queries xlat so we don't need to
456 * escape it twice. (it will make things wrong if we have an
457 * escape candidate character in the username)
459 int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username)
462 char tmpuser[MAX_STRING_LEN];
465 sqlusername[0]= '\0';
467 /* Remove any user attr we added previously */
468 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0, -1);
470 if (username != NULL) {
471 strlcpy(tmpuser, username, sizeof(tmpuser));
472 } else if (strlen(inst->config->query_user)) {
473 radius_xlat(tmpuser, sizeof(tmpuser), inst->config->query_user, request, NULL, NULL);
478 strlcpy(sqlusername, tmpuser, MAX_STRING_LEN);
479 RDEBUG2("sql_set_user escaped user --> '%s'", sqlusername);
480 vp = radius_pairmake(request, &request->packet->vps,
481 "SQL-User-Name", NULL, 0);
483 radlog(L_ERR, "%s", fr_strerror());
487 strlcpy(vp->vp_strvalue, tmpuser, sizeof(vp->vp_strvalue));
488 vp->length = strlen(vp->vp_strvalue);
495 static void sql_grouplist_free (SQL_GROUPLIST **group_list)
501 *group_list = (*group_list)->next;
507 static int sql_get_grouplist (SQL_INST *inst, SQLSOCK *sqlsocket, REQUEST *request, SQL_GROUPLIST **group_list)
509 char querystr[MAX_QUERY_LEN];
512 SQL_GROUPLIST *group_list_tmp;
514 /* NOTE: sql_set_user should have been run before calling this function */
516 group_list_tmp = *group_list = NULL;
518 if (!inst->config->groupmemb_query ||
519 (inst->config->groupmemb_query[0] == 0))
522 if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, request, sql_escape_func, inst)) {
523 radlog_request(L_ERR, 0, request, "xlat \"%s\" failed.",
524 inst->config->groupmemb_query);
528 if (rlm_sql_select_query(&sqlsocket, inst, querystr) < 0) {
531 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
532 row = sqlsocket->row;
536 RDEBUG("row[0] returned NULL");
537 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
538 sql_grouplist_free(group_list);
541 if (*group_list == NULL) {
542 *group_list = rad_malloc(sizeof(SQL_GROUPLIST));
543 group_list_tmp = *group_list;
545 rad_assert(group_list_tmp != NULL);
546 group_list_tmp->next = rad_malloc(sizeof(SQL_GROUPLIST));
547 group_list_tmp = group_list_tmp->next;
549 group_list_tmp->next = NULL;
550 strlcpy(group_list_tmp->groupname, row[0], MAX_STRING_LEN);
553 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
560 * sql groupcmp function. That way we can do group comparisons (in the users file for example)
561 * with the group memberships reciding in sql
562 * The group membership query should only return one element which is the username. The returned
563 * username will then be checked with the passed check string.
566 static int sql_groupcmp(void *instance, REQUEST *request, VALUE_PAIR *request_vp, VALUE_PAIR *check,
567 VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
570 SQL_INST *inst = instance;
571 char sqlusername[MAX_STRING_LEN];
572 SQL_GROUPLIST *group_list, *group_list_tmp;
574 check_pairs = check_pairs;
575 reply_pairs = reply_pairs;
576 request_vp = request_vp;
578 RDEBUG("sql_groupcmp");
579 if (!check || !check->vp_strvalue || !check->length){
580 RDEBUG("sql_groupcmp: Illegal group name");
584 RDEBUG("sql_groupcmp: NULL request");
588 * Set, escape, and check the user attr here
590 if (sql_set_user(inst, request, sqlusername, NULL) < 0)
594 * Get a socket for this lookup
596 sqlsocket = sql_get_socket(inst);
597 if (sqlsocket == NULL) {
598 /* Remove the username we (maybe) added above */
599 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0, -1);
604 * Get the list of groups this user is a member of
606 if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
607 radlog_request(L_ERR, 0, request,
608 "Error getting group membership");
609 /* Remove the username we (maybe) added above */
610 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0, -1);
611 sql_release_socket(inst, sqlsocket);
615 for (group_list_tmp = group_list; group_list_tmp != NULL; group_list_tmp = group_list_tmp->next) {
616 if (strcmp(group_list_tmp->groupname, check->vp_strvalue) == 0){
617 RDEBUG("sql_groupcmp finished: User is a member of group %s",
619 /* Free the grouplist */
620 sql_grouplist_free(&group_list);
621 /* Remove the username we (maybe) added above */
622 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0, -1);
623 sql_release_socket(inst, sqlsocket);
628 /* Free the grouplist */
629 sql_grouplist_free(&group_list);
630 /* Remove the username we (maybe) added above */
631 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0, -1);
632 sql_release_socket(inst,sqlsocket);
634 RDEBUG("sql_groupcmp finished: User is NOT a member of group %s",
642 static int rlm_sql_process_groups(SQL_INST *inst, REQUEST *request, SQLSOCK *sqlsocket, int *dofallthrough)
644 VALUE_PAIR *check_tmp = NULL;
645 VALUE_PAIR *reply_tmp = NULL;
646 SQL_GROUPLIST *group_list, *group_list_tmp;
647 VALUE_PAIR *sql_group = NULL;
648 char querystr[MAX_QUERY_LEN];
653 * Get the list of groups this user is a member of
655 if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
656 radlog_request(L_ERR, 0, request, "Error retrieving group list");
660 for (group_list_tmp = group_list; group_list_tmp != NULL && *dofallthrough != 0; group_list_tmp = group_list_tmp->next) {
662 * Add the Sql-Group attribute to the request list so we know
663 * which group we're retrieving attributes for
665 sql_group = pairmake("Sql-Group", group_list_tmp->groupname, T_OP_EQ);
667 radlog_request(L_ERR, 0, request,
668 "Error creating Sql-Group attribute");
669 sql_grouplist_free(&group_list);
672 pairadd(&request->packet->vps, sql_group);
673 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func, inst)) {
674 radlog_request(L_ERR, 0, request,
675 "Error generating query; rejecting user");
676 /* Remove the grouup we added above */
677 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, -1);
678 sql_grouplist_free(&group_list);
681 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
683 radlog_request(L_ERR, 0, request, "Error retrieving check pairs for group %s",
684 group_list_tmp->groupname);
685 /* Remove the grouup we added above */
686 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, -1);
687 pairfree(&check_tmp);
688 sql_grouplist_free(&group_list);
690 } else if (rows > 0) {
692 * Only do this if *some* check pairs were returned
694 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
696 RDEBUG2("User found in group %s",
697 group_list_tmp->groupname);
699 * Now get the reply pairs since the paircompare matched
701 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func, inst)) {
702 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
703 /* Remove the grouup we added above */
704 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, -1);
705 pairfree(&check_tmp);
706 sql_grouplist_free(&group_list);
709 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
710 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
711 group_list_tmp->groupname);
712 /* Remove the grouup we added above */
713 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, -1);
714 pairfree(&check_tmp);
715 pairfree(&reply_tmp);
716 sql_grouplist_free(&group_list);
719 *dofallthrough = fallthrough(reply_tmp);
720 pairxlatmove(request, &request->reply->vps, &reply_tmp);
721 pairxlatmove(request, &request->config_items, &check_tmp);
725 * rows == 0. This is like having the username on a line
726 * in the user's file with no check vp's. As such, we treat
727 * it as found and add the reply attributes, so that we
728 * match expected behavior
731 RDEBUG2("User found in group %s",
732 group_list_tmp->groupname);
734 * Now get the reply pairs since the paircompare matched
736 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func, inst)) {
737 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
738 /* Remove the grouup we added above */
739 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, -1);
740 pairfree(&check_tmp);
741 sql_grouplist_free(&group_list);
744 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
745 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
746 group_list_tmp->groupname);
747 /* Remove the grouup we added above */
748 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, -1);
749 pairfree(&check_tmp);
750 pairfree(&reply_tmp);
751 sql_grouplist_free(&group_list);
754 *dofallthrough = fallthrough(reply_tmp);
755 pairxlatmove(request, &request->reply->vps, &reply_tmp);
756 pairxlatmove(request, &request->config_items, &check_tmp);
760 * Delete the Sql-Group we added above
761 * And clear out the pairlists
763 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, -1);
764 pairfree(&check_tmp);
765 pairfree(&reply_tmp);
768 sql_grouplist_free(&group_list);
773 static int rlm_sql_detach(void *instance)
775 SQL_INST *inst = instance;
777 paircompare_unregister(PW_SQL_GROUP, sql_groupcmp);
782 if (inst->pool) sql_poolfree(inst);
784 if (inst->config->xlat_name) {
785 xlat_unregister(inst->config->xlat_name, sql_xlat, instance);
786 free(inst->config->xlat_name);
790 * Free up dynamically allocated string pointers.
792 for (i = 0; module_config[i].name != NULL; i++) {
794 if (module_config[i].type != PW_TYPE_STRING_PTR) {
799 * Treat 'config' as an opaque array of bytes,
800 * and take the offset into it. There's a
801 * (char*) pointer at that offset, and we want
804 p = (char **) (((char *)inst->config) + module_config[i].offset);
805 if (!*p) { /* nothing allocated */
818 * FIXME: Call the modules 'destroy' function?
820 lt_dlclose(inst->handle); /* ignore any errors */
828 static int parse_sub_section(CONF_SECTION *parent,
829 UNUSED SQL_INST *instance,
830 rlm_sql_config_section_t *config,
831 rlm_components_t comp)
835 const char *name = section_type_value[comp].section;
837 cs = cf_section_sub_find(parent, name);
839 radlog(L_INFO, "Couldn't find configuration for %s. Will return NOOP for calls from this section.", name);
844 if (cf_section_parse(cs, config, section_config) < 0) {
845 radlog(L_ERR, "Failed parsing configuration for section %s",
856 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance)
859 const char *xlat_name;
861 inst = rad_malloc(sizeof(SQL_INST));
862 memset(inst, 0, sizeof(SQL_INST));
865 * Export these methods, too. This avoids RTDL_GLOBAL.
867 inst->sql_set_user = sql_set_user;
868 inst->sql_get_socket = sql_get_socket;
869 inst->sql_release_socket = sql_release_socket;
870 inst->sql_escape_func = sql_escape_func;
871 inst->sql_query = rlm_sql_query;
872 inst->sql_select_query = rlm_sql_select_query;
873 inst->sql_fetch_row = rlm_sql_fetch_row;
875 inst->config = rad_malloc(sizeof(SQL_CONFIG));
876 memset(inst->config, 0, sizeof(SQL_CONFIG));
880 * If the configuration parameters can't be parsed, then fail.
882 if ((cf_section_parse(conf, inst->config, module_config) < 0) ||
883 (parse_sub_section(conf, inst,
884 &inst->config->accounting,
885 RLM_COMPONENT_ACCT) < 0) ||
886 (parse_sub_section(conf, inst,
887 &inst->config->postauth,
888 RLM_COMPONENT_POST_AUTH) < 0)) {
889 radlog(L_ERR, "Failed parsing configuration");
893 xlat_name = cf_section_name2(conf);
894 if (xlat_name == NULL) {
895 xlat_name = cf_section_name1(conf);
902 * Allocate room for <instance>-SQL-Group
904 group_name = rad_malloc((strlen(xlat_name) + 1 + 11) * sizeof(char));
905 sprintf(group_name,"%s-SQL-Group", xlat_name);
906 DEBUG("rlm_sql Creating new attribute %s",group_name);
908 memset(&flags, 0, sizeof(flags));
909 dict_addattr(group_name, 0, PW_TYPE_STRING, -1, flags);
910 dattr = dict_attrbyname(group_name);
912 radlog(L_ERR, "rlm_sql: Failed to create attribute %s",
920 if (inst->config->groupmemb_query &&
921 inst->config->groupmemb_query[0]) {
922 DEBUG("rlm_sql: Registering sql_groupcmp for %s",
924 paircompare_register(dattr->attr, PW_USER_NAME,
931 rad_assert(xlat_name);
934 * Register the SQL xlat function
936 inst->config->xlat_name = strdup(xlat_name);
937 xlat_register(xlat_name, sql_xlat, inst);
940 * Sanity check for crazy people.
942 if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
943 radlog(L_ERR, "\"%s\" is NOT an SQL driver!",
944 inst->config->sql_driver);
949 * Load the appropriate driver for our database
951 inst->handle = lt_dlopenext(inst->config->sql_driver);
952 if (inst->handle == NULL) {
953 radlog(L_ERR, "Could not link driver %s: %s",
954 inst->config->sql_driver,
956 radlog(L_ERR, "Make sure it (and all its dependent libraries!)"
957 "are in the search path of your system's ld.");
962 inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle,
963 inst->config->sql_driver);
965 radlog(L_ERR, "Could not link symbol %s: %s",
966 inst->config->sql_driver,
972 radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
973 inst->config->xlat_name, inst->config->sql_driver,
977 * Initialise the connection pool for this instance
979 radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
980 inst->config->xlat_name, inst->config->sql_login,
981 inst->config->sql_server, inst->config->sql_port,
982 inst->config->sql_db);
984 if (sql_init_socketpool(inst) < 0)
987 if (inst->config->groupmemb_query &&
988 inst->config->groupmemb_query[0]) {
989 paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
992 if (inst->config->do_clients) {
993 if (generate_sql_clients(inst) == -1){
994 radlog(L_ERR, "Failed to load clients from SQL.");
1002 return RLM_MODULE_OK;
1005 rlm_sql_detach(inst);
1011 static int rlm_sql_authorize(void *instance, REQUEST * request)
1013 int ret = RLM_MODULE_NOTFOUND;
1015 SQL_INST *inst = instance;
1018 VALUE_PAIR *check_tmp = NULL;
1019 VALUE_PAIR *reply_tmp = NULL;
1020 VALUE_PAIR *user_profile = NULL;
1022 int dofallthrough = 1;
1025 char querystr[MAX_QUERY_LEN];
1026 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;
1044 * After this point use goto error or goto release to cleanup sockets
1045 * temporary pairlists and temporary attributes.
1047 sqlsocket = sql_get_socket(inst);
1048 if (sqlsocket == NULL)
1052 * Query the check table to find any conditions associated with
1053 * this user/realm/whatever...
1055 if (inst->config->authorize_check_query &&
1056 *inst->config->authorize_check_query) {
1057 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func, inst)) {
1058 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1063 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
1065 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1071 * Only do this if *some* check pairs were returned
1074 (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0)) {
1075 RDEBUG2("User found in radcheck table");
1077 pairxlatmove(request, &request->config_items, &check_tmp);
1079 ret = RLM_MODULE_OK;
1083 * We only process reply table items if check conditions
1090 if (inst->config->authorize_reply_query &&
1091 *inst->config->authorize_reply_query) {
1093 * Now get the reply pairs since the paircompare matched
1095 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func, inst)) {
1096 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1101 rows = sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr);
1103 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1109 if (!inst->config->read_groups)
1110 dofallthrough = fallthrough(reply_tmp);
1112 pairxlatmove(request, &request->reply->vps, &reply_tmp);
1114 ret = RLM_MODULE_OK;
1121 * Clear out the pairlists
1123 pairfree(&check_tmp);
1124 pairfree(&reply_tmp);
1127 * dofallthrough is set to 1 by default so that if the user information
1128 * is not found, we will still process groups. If the user information,
1129 * however, *is* found, Fall-Through must be set in order to process
1130 * the groups as well.
1132 if (dofallthrough) {
1133 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1135 radlog_request(L_ERR, 0, request, "Error processing groups; rejecting user");
1141 ret = RLM_MODULE_OK;
1145 * Repeat the above process with the default profile or User-Profile
1147 if (dofallthrough) {
1149 * Check for a default_profile or for a User-Profile.
1151 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0);
1153 char *profile = user_profile ?
1154 user_profile->vp_strvalue :
1155 inst->config->default_profile;
1157 if (!profile || !*profile)
1160 RDEBUG("Checking profile %s", profile);
1162 if (sql_set_user(inst, request, profileusername, profile) < 0) {
1163 radlog_request(L_ERR, 0, request, "Error setting profile; rejecting user");
1168 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1170 radlog_request(L_ERR, 0, request, "Error processing profile groups; rejecting user");
1176 ret = RLM_MODULE_OK;
1182 ret = RLM_MODULE_FAIL;
1185 sql_release_socket(inst, sqlsocket);
1187 /* Remove the username we (maybe) added above */
1188 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0, -1);
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 rlm_sql_redundant(SQL_INST *inst, REQUEST *request,
1207 rlm_sql_config_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];
1222 char sqlusername[MAX_STRING_LEN];
1226 if (!section || !section->reference) {
1227 RDEBUG("No configuration provided for this section");
1229 return RLM_MODULE_NOOP;
1232 if (section->reference[0] != '.')
1235 if (!radius_xlat(p, (sizeof(path) - (p - path)) - 1,
1236 section->reference, request, NULL, NULL))
1237 return RLM_MODULE_FAIL;
1239 item = cf_reference_item(NULL, section->cs, path);
1241 return RLM_MODULE_FAIL;
1243 if (cf_item_is_section(item)){
1244 radlog(L_ERR, "Sections are not supported as references");
1246 return RLM_MODULE_FAIL;
1249 pair = cf_itemtopair(item);
1250 attr = cf_pair_attr(pair);
1252 RDEBUG2("Using query template '%s'", attr);
1254 sqlsocket = sql_get_socket(inst);
1255 if (sqlsocket == NULL)
1256 return RLM_MODULE_FAIL;
1258 sql_set_user(inst, request, sqlusername, NULL);
1261 value = cf_pair_value(pair);
1263 RDEBUG("Ignoring null query");
1264 ret = RLM_MODULE_NOOP;
1269 radius_xlat(querystr, sizeof(querystr), value, request,
1270 sql_escape_func, inst);
1272 RDEBUG("Ignoring null query");
1273 ret = RLM_MODULE_NOOP;
1278 rlm_sql_query_log(inst, request, section, querystr);
1281 * If rlm_sql_query cannot use the socket it'll try and
1282 * reconnect. Reconnecting will automatically release
1283 * the current socket, and try to select a new one.
1285 * If we get SQL_DOWN it means all connections in the pool
1286 * were exhausted, and we couldn't create a new connection,
1287 * so we do not need to call sql_release_socket.
1289 sql_ret = rlm_sql_query(&sqlsocket, inst, querystr);
1290 if (sql_ret == SQL_DOWN)
1291 return RLM_MODULE_FAIL;
1293 rad_assert(sqlsocket);
1296 * Assume all other errors are incidental, and just meant our
1297 * operation failed and its not a client or SQL syntax error.
1300 numaffected = (inst->module->sql_affected_rows)
1301 (sqlsocket, inst->config);
1302 if (numaffected > 0)
1305 RDEBUG("No records updated");
1308 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1311 * We assume all entries with the same name form a redundant
1314 pair = cf_pair_find_next(section->cs, pair, attr);
1317 RDEBUG("No additional queries configured");
1319 ret = RLM_MODULE_NOOP;
1324 RDEBUG("Trying next query...");
1327 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1331 /* Remove the username we (maybe) added above */
1332 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0, -1);
1334 sql_release_socket(inst, sqlsocket);
1339 #ifdef WITH_ACCOUNTING
1342 * Accounting: Insert or update session data in our sql table
1344 static int rlm_sql_accounting(void *instance, REQUEST * request) {
1345 SQL_INST *inst = instance;
1347 return rlm_sql_redundant(inst, request, &inst->config->accounting);
1352 #ifdef WITH_SESSION_MGMT
1354 * See if a user is already logged in. Sets request->simul_count to the
1355 * current session count for this user.
1357 * Check twice. If on the first pass the user exceeds his
1358 * max. number of logins, do a second pass and validate all
1359 * logins by querying the terminal server (using eg. SNMP).
1362 static int rlm_sql_checksimul(void *instance, REQUEST * request) {
1364 SQL_INST *inst = instance;
1366 char querystr[MAX_QUERY_LEN];
1367 char sqlusername[MAX_STRING_LEN];
1370 char *call_num = NULL;
1373 uint32_t nas_addr = 0;
1376 /* If simul_count_query is not defined, we don't do any checking */
1377 if (!inst->config->simul_count_query ||
1378 (inst->config->simul_count_query[0] == 0)) {
1379 return RLM_MODULE_NOOP;
1382 if((request->username == NULL) || (request->username->length == 0)) {
1383 radlog_request(L_ERR, 0, request,
1384 "Zero Length username not permitted\n");
1385 return RLM_MODULE_INVALID;
1389 if(sql_set_user(inst, request, sqlusername, NULL) < 0)
1390 return RLM_MODULE_FAIL;
1392 radius_xlat(querystr, sizeof(querystr), inst->config->simul_count_query, request, sql_escape_func, inst);
1394 /* initialize the sql socket */
1395 sqlsocket = sql_get_socket(inst);
1396 if(sqlsocket == NULL)
1397 return RLM_MODULE_FAIL;
1399 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1400 sql_release_socket(inst, sqlsocket);
1401 return RLM_MODULE_FAIL;
1404 ret = rlm_sql_fetch_row(&sqlsocket, inst);
1406 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1407 sql_release_socket(inst, sqlsocket);
1408 return RLM_MODULE_FAIL;
1411 row = sqlsocket->row;
1413 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1414 sql_release_socket(inst, sqlsocket);
1415 return RLM_MODULE_FAIL;
1418 request->simul_count = atoi(row[0]);
1419 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1421 if(request->simul_count < request->simul_max) {
1422 sql_release_socket(inst, sqlsocket);
1423 return RLM_MODULE_OK;
1427 * Looks like too many sessions, so let's start verifying
1428 * them, unless told to rely on count query only.
1430 if (!inst->config->simul_verify_query ||
1431 (inst->config->simul_verify_query[0] == '\0')) {
1432 sql_release_socket(inst, sqlsocket);
1433 return RLM_MODULE_OK;
1436 radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func, inst);
1437 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1438 sql_release_socket(inst, sqlsocket);
1439 return RLM_MODULE_FAIL;
1443 * Setup some stuff, like for MPP detection.
1445 request->simul_count = 0;
1447 if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0)) != NULL)
1448 ipno = vp->vp_ipaddr;
1449 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0)) != NULL)
1450 call_num = vp->vp_strvalue;
1453 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
1454 row = sqlsocket->row;
1458 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1459 sql_release_socket(inst, sqlsocket);
1460 RDEBUG("Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
1461 return RLM_MODULE_FAIL;
1464 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1465 sql_release_socket(inst, sqlsocket);
1466 RDEBUG("Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
1467 return RLM_MODULE_FAIL;
1470 nas_addr = inet_addr(row[3]);
1472 nas_port = atoi(row[4]);
1474 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1478 * Stale record - zap it.
1480 if (inst->config->deletestalesessions == TRUE) {
1481 uint32_t framed_addr = 0;
1486 framed_addr = inet_addr(row[5]);
1488 if (strcmp(row[7], "PPP") == 0)
1490 else if (strcmp(row[7], "SLIP") == 0)
1494 sess_time = atoi(row[8]);
1495 session_zap(request, nas_addr, nas_port,
1496 row[2], row[1], framed_addr,
1500 else if (check == 1) {
1502 * User is still logged in.
1504 ++request->simul_count;
1507 * Does it look like a MPP attempt?
1509 if (row[5] && ipno && inet_addr(row[5]) == ipno)
1510 request->simul_mpp = 2;
1511 else if (row[6] && call_num &&
1512 !strncmp(row[6],call_num,16))
1513 request->simul_mpp = 2;
1517 * Failed to check the terminal server for
1518 * duplicate logins: return an error.
1520 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1521 sql_release_socket(inst, sqlsocket);
1522 radlog_request(L_ERR, 0, request, "Failed to check the terminal server for user '%s'.", row[2]);
1523 return RLM_MODULE_FAIL;
1527 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1528 sql_release_socket(inst, sqlsocket);
1531 * The Auth module apparently looks at request->simul_count,
1532 * not the return value of this module when deciding to deny
1533 * a call for too many sessions.
1535 return RLM_MODULE_OK;
1540 * Postauth: Write a record of the authentication attempt
1542 static int rlm_sql_postauth(void *instance, REQUEST * request) {
1543 SQL_INST *inst = instance;
1545 return rlm_sql_redundant(inst, request, &inst->config->postauth);
1549 * Execute postauth_query after authentication
1553 /* globally exported name */
1554 module_t rlm_sql = {
1557 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1558 rlm_sql_instantiate, /* instantiation */
1559 rlm_sql_detach, /* detach */
1561 NULL, /* authentication */
1562 rlm_sql_authorize, /* authorization */
1563 NULL, /* preaccounting */
1564 #ifdef WITH_ACCOUNTING
1565 rlm_sql_accounting, /* accounting */
1569 #ifdef WITH_SESSION_MGMT
1570 rlm_sql_checksimul, /* checksimul */
1574 NULL, /* pre-proxy */
1575 NULL, /* post-proxy */
1576 rlm_sql_postauth /* post-auth */