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 {"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(char *out, size_t outlen, const char *in);
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 int sql_xlat(void *instance, REQUEST *request,
136 char *fmt, char *out, size_t freespace,
137 UNUSED RADIUS_ESCAPE_STRING func)
141 SQL_INST *inst = instance;
142 char querystr[MAX_QUERY_LEN];
143 char sqlusername[MAX_STRING_LEN];
149 * Add SQL-User-Name attribute just in case it is needed
150 * We could search the string fmt for SQL-User-Name to see if this is
153 sql_set_user(inst, request, sqlusername, NULL);
155 * Do an xlat on the provided string (nice recursive operation).
157 if (!radius_xlat(querystr, sizeof(querystr), fmt, request, sql_escape_func)) {
158 radlog(L_ERR, "rlm_sql (%s): xlat failed.",
159 inst->config->xlat_name);
163 sqlsocket = sql_get_socket(inst);
164 if (sqlsocket == NULL)
167 query_log(inst, request, NULL, querystr);
170 * If the query starts with any of the following prefixes,
171 * then return the number of rows affected
173 if ((strncasecmp(querystr, "insert", 6) == 0) ||
174 (strncasecmp(querystr, "update", 6) == 0) ||
175 (strncasecmp(querystr, "delete", 6) == 0)) {
177 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
179 if (rlm_sql_query(&sqlsocket,inst,querystr)) {
180 sql_release_socket(inst,sqlsocket);
185 numaffected = (inst->module->sql_affected_rows)(sqlsocket,
187 if (numaffected < 1) {
188 RDEBUG("rlm_sql (%s): SQL query affected no rows",
189 inst->config->xlat_name);
193 * Don't chop the returned number if freespace is
194 * too small. This hack is necessary because
195 * some implementations of snprintf return the
196 * size of the written data, and others return
197 * the size of the data they *would* have written
198 * if the output buffer was large enough.
200 snprintf(buffer, sizeof(buffer), "%d", numaffected);
201 ret = strlen(buffer);
202 if (ret >= freespace){
203 RDEBUG("rlm_sql (%s): Can't write result, insufficient string space",
204 inst->config->xlat_name);
205 (inst->module->sql_finish_query)(sqlsocket,
207 sql_release_socket(inst,sqlsocket);
211 memcpy(out, buffer, ret + 1); /* we did bounds checking above */
213 (inst->module->sql_finish_query)(sqlsocket, inst->config);
214 sql_release_socket(inst,sqlsocket);
216 } /* else it's a SELECT statement */
218 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
219 sql_release_socket(inst,sqlsocket);
223 ret = rlm_sql_fetch_row(&sqlsocket, inst);
225 RDEBUG("SQL query did not succeed");
226 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
227 sql_release_socket(inst,sqlsocket);
231 row = sqlsocket->row;
233 RDEBUG("SQL query did not return any results");
234 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
235 sql_release_socket(inst,sqlsocket);
240 RDEBUG("Null value in first column");
241 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
242 sql_release_socket(inst,sqlsocket);
245 ret = strlen(row[0]);
246 if (ret >= freespace){
247 RDEBUG("Insufficient string space");
248 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
249 sql_release_socket(inst,sqlsocket);
253 strlcpy(out,row[0],freespace);
255 RDEBUG("sql_xlat finished");
257 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
258 sql_release_socket(inst,sqlsocket);
262 static int generate_sql_clients(SQL_INST *inst)
266 char querystr[MAX_QUERY_LEN];
268 char *prefix_ptr = NULL;
272 DEBUG("rlm_sql (%s): Processing generate_sql_clients",
273 inst->config->xlat_name);
275 /* NAS query isn't xlat'ed */
276 strlcpy(querystr, inst->config->nas_query, sizeof(querystr));
277 DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
278 inst->config->xlat_name, querystr);
280 sqlsocket = sql_get_socket(inst);
281 if (sqlsocket == NULL)
283 if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
287 while(rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
289 row = sqlsocket->row;
293 * The return data for each row MUST be in the following order:
295 * 0. Row ID (currently unused)
296 * 1. Name (or IP address)
300 * 5. Virtual Server (optional)
303 radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
307 radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
311 radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
315 radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
319 DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name,
320 row[1],row[2],row[4]);
322 c = rad_malloc(sizeof(*c));
323 memset(c, 0, sizeof(*c));
325 #ifdef WITH_DYNAMIC_CLIENTS
333 prefix_ptr = strchr(row[1], '/');
335 c->prefix = atoi(prefix_ptr + 1);
336 if ((c->prefix < 0) || (c->prefix > 128)) {
337 radlog(L_ERR, "rlm_sql (%s): Invalid Prefix value '%s' for IP.",
338 inst->config->xlat_name, prefix_ptr + 1);
342 /* Replace '/' with '\0' */
347 * Always get the numeric representation of IP
349 if (ip_hton(row[1], AF_UNSPEC, &c->ipaddr) < 0) {
350 radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s: %s",
351 inst->config->xlat_name,
352 row[1], fr_strerror());
357 ip_ntoh(&c->ipaddr, buffer, sizeof(buffer));
358 c->longname = strdup(buffer);
361 if (c->prefix < 0) switch (c->ipaddr.af) {
373 * Other values (secret, shortname, nastype, virtual_server)
375 c->secret = strdup(row[4]);
376 c->shortname = strdup(row[2]);
378 c->nastype = strdup(row[3]);
380 numf = (inst->module->sql_num_fields)(sqlsocket, inst->config);
381 if ((numf > 5) && (row[5] != NULL) && *row[5]) c->server = strdup(row[5]);
383 DEBUG("rlm_sql (%s): Adding client %s (%s, server=%s) to clients list",
384 inst->config->xlat_name,
385 c->longname,c->shortname, c->server ? c->server : "<none>");
386 if (!client_add(NULL, c)) {
387 sql_release_socket(inst, sqlsocket);
388 DEBUG("rlm_sql (%s): Failed to add client %s (%s) to clients list. Maybe there's a duplicate?",
389 inst->config->xlat_name,
390 c->longname,c->shortname);
395 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
396 sql_release_socket(inst, sqlsocket);
403 * Translate the SQL queries.
405 static size_t sql_escape_func(char *out, size_t outlen, const char *in)
411 * Non-printable characters get replaced with their
412 * mime-encoded equivalents.
415 strchr(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);
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);
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)) {
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);
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);
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);
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);
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)) {
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);
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);
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)) {
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);
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);
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)) {
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);
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);
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);
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,(RAD_XLAT_FUNC)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 */
812 * Catch multiple instances of the module.
814 if (allowed_chars == inst->config->allowed_chars) {
815 allowed_chars = NULL;
824 * FIXME: Call the modules 'destroy' function?
826 lt_dlclose(inst->handle); /* ignore any errors */
834 static int parse_sub_section(CONF_SECTION *parent,
835 UNUSED SQL_INST *instance,
836 rlm_sql_config_section_t *config,
837 rlm_components_t comp)
841 const char *name = section_type_value[comp].section;
843 cs = cf_section_sub_find(parent, name);
845 /* TODO: Should really setup section with default values */
849 if (cf_section_parse(cs, config, section_config) < 0)
857 radlog(L_ERR, "Failed parsing configuration for section %s",
864 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance)
867 const char *xlat_name;
869 inst = rad_malloc(sizeof(SQL_INST));
870 memset(inst, 0, sizeof(SQL_INST));
873 * Export these methods, too. This avoids RTDL_GLOBAL.
875 inst->sql_set_user = sql_set_user;
876 inst->sql_get_socket = sql_get_socket;
877 inst->sql_release_socket = sql_release_socket;
878 inst->sql_escape_func = sql_escape_func;
879 inst->sql_query = rlm_sql_query;
880 inst->sql_select_query = rlm_sql_select_query;
881 inst->sql_fetch_row = rlm_sql_fetch_row;
883 inst->config = rad_malloc(sizeof(SQL_CONFIG));
884 memset(inst->config, 0, sizeof(SQL_CONFIG));
888 * If the configuration parameters can't be parsed, then fail.
891 (cf_section_parse(conf, inst->config, module_config) < 0) ||
892 (parse_sub_section(conf, inst,
893 &inst->config->accounting,
894 RLM_COMPONENT_ACCT) < 0) ||
895 (parse_sub_section(conf, inst,
896 &inst->config->postauth,
897 RLM_COMPONENT_POST_AUTH) < 0)
899 radlog(L_ERR, "Failed parsing configuration");
904 * Sanity check for crazy people.
906 if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
907 radlog(L_ERR, "\"%s\" is NOT an SQL driver!",
908 inst->config->sql_driver);
912 xlat_name = cf_section_name2(conf);
913 if (xlat_name == NULL) {
914 xlat_name = cf_section_name1(conf);
921 * Allocate room for <instance>-SQL-Group
923 group_name = rad_malloc((strlen(xlat_name) + 1 + 11) * sizeof(char));
924 sprintf(group_name,"%s-SQL-Group", xlat_name);
925 DEBUG("rlm_sql Creating new attribute %s",group_name);
927 memset(&flags, 0, sizeof(flags));
928 dict_addattr(group_name, 0, PW_TYPE_STRING, -1, flags);
929 dattr = dict_attrbyname(group_name);
931 radlog(L_ERR, "rlm_sql: Failed to create attribute %s",
939 if (inst->config->groupmemb_query &&
940 inst->config->groupmemb_query[0]) {
941 DEBUG("rlm_sql: Registering sql_groupcmp for %s",
943 paircompare_register(dattr->attr, PW_USER_NAME,
950 rad_assert(xlat_name);
953 * Register the SQL xlat function
955 inst->config->xlat_name = strdup(xlat_name);
956 xlat_register(xlat_name, (RAD_XLAT_FUNC)sql_xlat, inst);
959 * Load the appropriate driver for our database
961 inst->handle = lt_dlopenext(inst->config->sql_driver);
962 if (inst->handle == NULL) {
963 radlog(L_ERR, "Could not link driver %s: %s",
964 inst->config->sql_driver,
966 radlog(L_ERR, "Make sure it (and all its dependent libraries!)"
967 "are in the search path of your system's ld.");
972 inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle,
973 inst->config->sql_driver);
975 radlog(L_ERR, "Could not link symbol %s: %s",
976 inst->config->sql_driver,
982 radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
983 inst->config->xlat_name, inst->config->sql_driver,
987 * Initialise the connection pool for this instance
989 radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
990 inst->config->xlat_name, inst->config->sql_login,
991 inst->config->sql_server, inst->config->sql_port,
992 inst->config->sql_db);
994 if (sql_init_socketpool(inst) < 0)
997 if (inst->config->groupmemb_query &&
998 inst->config->groupmemb_query[0]) {
999 paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
1002 if (inst->config->do_clients) {
1003 if (generate_sql_clients(inst) == -1){
1004 radlog(L_ERR, "Failed to load clients from SQL.");
1009 allowed_chars = inst->config->allowed_chars;
1013 return RLM_MODULE_OK;
1016 rlm_sql_detach(inst);
1022 static int rlm_sql_authorize(void *instance, REQUEST * request)
1024 VALUE_PAIR *check_tmp = NULL;
1025 VALUE_PAIR *reply_tmp = NULL;
1026 VALUE_PAIR *user_profile = NULL;
1028 int dofallthrough = 1;
1031 SQL_INST *inst = instance;
1032 char querystr[MAX_QUERY_LEN];
1033 char sqlusername[MAX_STRING_LEN];
1035 * the profile username is used as the sqlusername during
1036 * profile checking so that we don't overwrite the orignal
1037 * sqlusername string
1039 char profileusername[MAX_STRING_LEN];
1042 * Set, escape, and check the user attr here
1044 if (sql_set_user(inst, request, sqlusername, NULL) < 0)
1045 return RLM_MODULE_FAIL;
1051 sqlsocket = sql_get_socket(inst);
1052 if (sqlsocket == NULL) {
1053 /* Remove the username we (maybe) added above */
1054 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1055 return RLM_MODULE_FAIL;
1060 * After this point, ALL 'return's MUST release the SQL socket!
1064 * Alright, start by getting the specific entry for the user
1066 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func)) {
1067 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1068 sql_release_socket(inst, sqlsocket);
1069 /* Remove the username we (maybe) added above */
1070 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1071 return RLM_MODULE_FAIL;
1073 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
1075 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1076 sql_release_socket(inst, sqlsocket);
1077 /* Remove the username we (maybe) added above */
1078 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1079 pairfree(&check_tmp);
1080 return RLM_MODULE_FAIL;
1081 } else if (rows > 0) {
1083 * Only do this if *some* check pairs were returned
1085 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
1087 RDEBUG2("User found in radcheck table");
1089 if (inst->config->authorize_reply_query &&
1090 *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)) {
1096 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1097 sql_release_socket(inst, sqlsocket);
1098 /* Remove the username we (maybe) added above */
1099 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1100 pairfree(&check_tmp);
1101 return RLM_MODULE_FAIL;
1103 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
1104 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1105 sql_release_socket(inst, sqlsocket);
1106 /* Remove the username we (maybe) added above */
1107 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1108 pairfree(&check_tmp);
1109 pairfree(&reply_tmp);
1111 return RLM_MODULE_FAIL;
1114 if (!inst->config->read_groups)
1115 dofallthrough = fallthrough(reply_tmp);
1116 pairxlatmove(request, &request->reply->vps, &reply_tmp);
1118 pairxlatmove(request, &request->config_items, &check_tmp);
1123 * Clear out the pairlists
1125 pairfree(&check_tmp);
1126 pairfree(&reply_tmp);
1129 * dofallthrough is set to 1 by default so that if the user information
1130 * is not found, we will still process groups. If the user information,
1131 * however, *is* found, Fall-Through must be set in order to process
1132 * the groups as well
1134 if (dofallthrough) {
1135 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1137 radlog_request(L_ERR, 0, request, "Error processing groups; rejecting user");
1138 sql_release_socket(inst, sqlsocket);
1139 /* Remove the username we (maybe) added above */
1140 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1141 return RLM_MODULE_FAIL;
1142 } else if (rows > 0) {
1148 * repeat the above process with the default profile or User-Profile
1150 if (dofallthrough) {
1151 int profile_found = 0;
1153 * Check for a default_profile or for a User-Profile.
1155 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0);
1156 if (inst->config->default_profile[0] != 0 || user_profile != NULL){
1157 char *profile = inst->config->default_profile;
1159 if (user_profile != NULL)
1160 profile = user_profile->vp_strvalue;
1161 if (profile && strlen(profile)){
1162 RDEBUG("Checking profile %s", profile);
1163 if (sql_set_user(inst, request, profileusername, profile) < 0) {
1164 radlog_request(L_ERR, 0, request, "Error setting profile; rejecting user");
1165 sql_release_socket(inst, sqlsocket);
1166 /* Remove the username we (maybe) added above */
1167 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1168 return RLM_MODULE_FAIL;
1175 if (profile_found) {
1176 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1178 radlog_request(L_ERR, 0, request, "Error processing profile groups; rejecting user");
1179 sql_release_socket(inst, sqlsocket);
1180 /* Remove the username we (maybe) added above */
1181 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1182 return RLM_MODULE_FAIL;
1183 } else if (rows > 0) {
1189 /* Remove the username we (maybe) added above */
1190 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1191 sql_release_socket(inst, sqlsocket);
1194 RDEBUG("User %s not found", sqlusername);
1195 return RLM_MODULE_NOTFOUND;
1197 return RLM_MODULE_OK;
1202 * Generic function for failing between a bunch of queries.
1204 * Uses the same principle as rlm_linelog, expanding the 'reference' config
1205 * item using xlat to figure out what query it should execute.
1207 * If the reference matches multiple config items, and a query fails or
1208 * doesn't update any rows, the next matching config item is used.
1211 static int rlm_sql_redundant(SQL_INST *inst, REQUEST *request,
1212 rlm_sql_config_section_t *section)
1214 int ret = RLM_MODULE_OK;
1216 SQLSOCK *sqlsocket = NULL;
1218 int numaffected = 0;
1222 const char *attr = NULL;
1225 char path[MAX_STRING_LEN];
1226 char querystr[MAX_QUERY_LEN];
1227 char sqlusername[MAX_STRING_LEN];
1231 sql_set_user(inst, request, sqlusername, NULL);
1233 if (section->reference[0] != '.')
1236 if (radius_xlat(p, (sizeof(path) - (p - path)) - 1,
1237 section->reference, request, NULL) < 0)
1238 return RLM_MODULE_FAIL;
1240 item = cf_reference_item(NULL, section->cs, path);
1242 return RLM_MODULE_FAIL;
1244 if (cf_item_is_section(item)){
1245 radlog(L_ERR, "Sections are not supported as references");
1247 return RLM_MODULE_FAIL;
1250 pair = cf_itemtopair(item);
1251 attr = cf_pair_attr(pair);
1253 RDEBUG2("Failing between pairs with name '%s'", attr);
1255 sqlsocket = sql_get_socket(inst);
1256 if (sqlsocket == NULL)
1257 return RLM_MODULE_FAIL;
1260 value = cf_pair_value(pair);
1264 radius_xlat(querystr, sizeof(querystr), value, request,
1269 query_log(inst, request, section, querystr);
1271 sql_ret = rlm_sql_query(&sqlsocket, inst, querystr);
1272 if (sql_ret == SQL_DOWN)
1273 return RLM_MODULE_FAIL;
1275 rad_assert(sqlsocket);
1278 * Assume all other errors are incidental, and just meant our
1279 * operation failed and its not a client or SQL syntax error.
1282 numaffected = (inst->module->sql_affected_rows)
1283 (sqlsocket, inst->config);
1284 if (numaffected > 0)
1287 RDEBUG("No records updated");
1290 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1293 * We assume all entries with the same name form a redundant
1296 pair = cf_pair_find_next(section->cs, pair, attr);
1299 RDEBUG("No additional queries configured");
1301 ret = RLM_MODULE_NOOP;
1306 RDEBUG("Trying next query...");
1309 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1313 sql_release_socket(inst, sqlsocket);
1319 radlog_request(L_DBG, 0, request, "Ignoring null query");
1321 sql_release_socket(inst, sqlsocket);
1323 return RLM_MODULE_NOOP;
1326 #ifdef WITH_ACCOUNTING
1329 * Accounting: Insert or update session data in our sql table
1331 static int rlm_sql_accounting(void *instance, REQUEST * request) {
1332 SQL_INST *inst = instance;
1334 return rlm_sql_redundant(inst, request, &inst->config->accounting);
1339 #ifdef WITH_SESSION_MGMT
1341 * See if a user is already logged in. Sets request->simul_count to the
1342 * current session count for this user.
1344 * Check twice. If on the first pass the user exceeds his
1345 * max. number of logins, do a second pass and validate all
1346 * logins by querying the terminal server (using eg. SNMP).
1349 static int rlm_sql_checksimul(void *instance, REQUEST * request) {
1351 SQL_INST *inst = instance;
1353 char querystr[MAX_QUERY_LEN];
1354 char sqlusername[MAX_STRING_LEN];
1357 char *call_num = NULL;
1360 uint32_t nas_addr = 0;
1363 /* If simul_count_query is not defined, we don't do any checking */
1364 if (!inst->config->simul_count_query ||
1365 (inst->config->simul_count_query[0] == 0)) {
1366 return RLM_MODULE_NOOP;
1369 if((request->username == NULL) || (request->username->length == 0)) {
1370 radlog_request(L_ERR, 0, request,
1371 "Zero Length username not permitted\n");
1372 return RLM_MODULE_INVALID;
1376 if(sql_set_user(inst, request, sqlusername, NULL) < 0)
1377 return RLM_MODULE_FAIL;
1379 radius_xlat(querystr, sizeof(querystr), inst->config->simul_count_query, request, sql_escape_func);
1381 /* initialize the sql socket */
1382 sqlsocket = sql_get_socket(inst);
1383 if(sqlsocket == NULL)
1384 return RLM_MODULE_FAIL;
1386 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1387 sql_release_socket(inst, sqlsocket);
1388 return RLM_MODULE_FAIL;
1391 ret = rlm_sql_fetch_row(&sqlsocket, inst);
1393 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1394 sql_release_socket(inst, sqlsocket);
1395 return RLM_MODULE_FAIL;
1398 row = sqlsocket->row;
1400 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1401 sql_release_socket(inst, sqlsocket);
1402 return RLM_MODULE_FAIL;
1405 request->simul_count = atoi(row[0]);
1406 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1408 if(request->simul_count < request->simul_max) {
1409 sql_release_socket(inst, sqlsocket);
1410 return RLM_MODULE_OK;
1414 * Looks like too many sessions, so let's start verifying
1415 * them, unless told to rely on count query only.
1417 if (!inst->config->simul_verify_query ||
1418 (inst->config->simul_verify_query[0] == '\0')) {
1419 sql_release_socket(inst, sqlsocket);
1420 return RLM_MODULE_OK;
1423 radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func);
1424 if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1425 sql_release_socket(inst, sqlsocket);
1426 return RLM_MODULE_FAIL;
1430 * Setup some stuff, like for MPP detection.
1432 request->simul_count = 0;
1434 if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0)) != NULL)
1435 ipno = vp->vp_ipaddr;
1436 if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0)) != NULL)
1437 call_num = vp->vp_strvalue;
1440 while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
1441 row = sqlsocket->row;
1445 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1446 sql_release_socket(inst, sqlsocket);
1447 RDEBUG("Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
1448 return RLM_MODULE_FAIL;
1451 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1452 sql_release_socket(inst, sqlsocket);
1453 RDEBUG("Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
1454 return RLM_MODULE_FAIL;
1457 nas_addr = inet_addr(row[3]);
1459 nas_port = atoi(row[4]);
1461 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1465 * Stale record - zap it.
1467 if (inst->config->deletestalesessions == TRUE) {
1468 uint32_t framed_addr = 0;
1473 framed_addr = inet_addr(row[5]);
1475 if (strcmp(row[7], "PPP") == 0)
1477 else if (strcmp(row[7], "SLIP") == 0)
1481 sess_time = atoi(row[8]);
1482 session_zap(request, nas_addr, nas_port,
1483 row[2], row[1], framed_addr,
1487 else if (check == 1) {
1489 * User is still logged in.
1491 ++request->simul_count;
1494 * Does it look like a MPP attempt?
1496 if (row[5] && ipno && inet_addr(row[5]) == ipno)
1497 request->simul_mpp = 2;
1498 else if (row[6] && call_num &&
1499 !strncmp(row[6],call_num,16))
1500 request->simul_mpp = 2;
1504 * Failed to check the terminal server for
1505 * duplicate logins: return an error.
1507 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1508 sql_release_socket(inst, sqlsocket);
1509 radlog_request(L_ERR, 0, request, "Failed to check the terminal server for user '%s'.", row[2]);
1510 return RLM_MODULE_FAIL;
1514 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1515 sql_release_socket(inst, sqlsocket);
1518 * The Auth module apparently looks at request->simul_count,
1519 * not the return value of this module when deciding to deny
1520 * a call for too many sessions.
1522 return RLM_MODULE_OK;
1527 * Postauth: Write a record of the authentication attempt
1529 static int rlm_sql_postauth(void *instance, REQUEST * request) {
1530 SQL_INST *inst = instance;
1532 return rlm_sql_redundant(inst, request, &inst->config->postauth);
1536 * Execute postauth_query after authentication
1540 /* globally exported name */
1541 module_t rlm_sql = {
1544 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1545 rlm_sql_instantiate, /* instantiation */
1546 rlm_sql_detach, /* detach */
1548 NULL, /* authentication */
1549 rlm_sql_authorize, /* authorization */
1550 NULL, /* preaccounting */
1551 #ifdef WITH_ACCOUNTING
1552 rlm_sql_accounting, /* accounting */
1556 #ifdef WITH_SESSION_MGMT
1557 rlm_sql_checksimul, /* checksimul */
1561 NULL, /* pre-proxy */
1562 NULL, /* post-proxy */
1563 rlm_sql_postauth /* post-auth */