📄 rlm_sql.c
字号:
/* * rlm_sql.c SQL Module * Main SQL module file. Most ICRADIUS code is located in sql.c * * Version: $Id$ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * Copyright 2000,2006 The FreeRADIUS server project * Copyright 2000 Mike Machado <mike@innercite.com> * Copyright 2000 Alan DeKok <aland@ox.org> */#include <freeradius-devel/ident.h>RCSID("$Id$")#include <freeradius-devel/radiusd.h>#include <freeradius-devel/modules.h>#include <freeradius-devel/rad_assert.h>#include <ltdl.h>#include <sys/stat.h>#include "rlm_sql.h"static char *allowed_chars = NULL;static const CONF_PARSER module_config[] = { {"driver",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"}, {"server",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_server), NULL, "localhost"}, {"port",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_port), NULL, ""}, {"login", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_login), NULL, ""}, {"password", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_password), NULL, ""}, {"radius_db", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_db), NULL, "radius"}, {"read_groups", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,read_groups), NULL, "yes"}, {"sqltrace", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,sqltrace), NULL, "no"}, {"sqltracefile", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,tracefile), NULL, SQLTRACEFILE}, {"readclients", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,do_clients), NULL, "no"}, {"deletestalesessions", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,deletestalesessions), NULL, "yes"}, {"num_sql_socks", PW_TYPE_INTEGER, offsetof(SQL_CONFIG,num_sql_socks), NULL, "5"}, {"sql_user_name", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,query_user), NULL, ""}, {"default_user_profile", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,default_profile), NULL, ""}, {"nas_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,nas_query), NULL, "SELECT id,nasname,shortname,type,secret FROM nas"}, {"authorize_check_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_check_query), NULL, ""}, {"authorize_reply_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_reply_query), NULL, NULL}, {"authorize_group_check_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""}, {"authorize_group_reply_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""}, {"accounting_onoff_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_onoff_query), NULL, ""}, {"accounting_update_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_update_query), NULL, ""}, {"accounting_update_query_alt", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_update_query_alt), NULL, ""}, {"accounting_start_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_start_query), NULL, ""}, {"accounting_start_query_alt", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_start_query_alt), NULL, ""}, {"accounting_stop_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_stop_query), NULL, ""}, {"accounting_stop_query_alt", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_stop_query_alt), NULL, ""}, {"group_membership_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,groupmemb_query), NULL, NULL}, {"connect_failure_retry_delay", PW_TYPE_INTEGER, offsetof(SQL_CONFIG,connect_failure_retry_delay), NULL, "60"}, {"simul_count_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,simul_count_query), NULL, ""}, {"simul_verify_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,simul_verify_query), NULL, ""}, {"postauth_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,postauth_query), NULL, ""}, {"safe-characters", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,allowed_chars), NULL, "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"}, {NULL, -1, 0, NULL, NULL}};/* * Fall-Through checking function from rlm_files.c */static int fallthrough(VALUE_PAIR *vp){ VALUE_PAIR *tmp; tmp = pairfind(vp, PW_FALL_THROUGH); return tmp ? tmp->vp_integer : 0;}/* * Yucky prototype. */static int generate_sql_clients(SQL_INST *inst);static size_t sql_escape_func(char *out, size_t outlen, const char *in);/* * sql xlat function. Right now only SELECTs are supported. Only * the first element of the SELECT result will be used. */static int sql_xlat(void *instance, REQUEST *request, char *fmt, char *out, size_t freespace, UNUSED RADIUS_ESCAPE_STRING func){ SQLSOCK *sqlsocket; SQL_ROW row; SQL_INST *inst = instance; char querystr[MAX_QUERY_LEN]; char sqlusername[MAX_STRING_LEN]; size_t ret = 0; RDEBUG("sql_xlat"); /* * Add SQL-User-Name attribute just in case it is needed * We could search the string fmt for SQL-User-Name to see if this is * needed or not */ sql_set_user(inst, request, sqlusername, NULL); /* * Do an xlat on the provided string (nice recursive operation). */ if (!radius_xlat(querystr, sizeof(querystr), fmt, request, sql_escape_func)) { radlog(L_ERR, "rlm_sql (%s): xlat failed.", inst->config->xlat_name); return 0; } query_log(request, inst,querystr); sqlsocket = sql_get_socket(inst); if (sqlsocket == NULL) return 0; if (rlm_sql_select_query(sqlsocket,inst,querystr)){ radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s", inst->config->xlat_name,querystr, (inst->module->sql_error)(sqlsocket, inst->config)); sql_release_socket(inst,sqlsocket); return 0; } ret = rlm_sql_fetch_row(sqlsocket, inst); if (ret) { RDEBUG("SQL query did not succeed"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst,sqlsocket); return 0; } row = sqlsocket->row; if (row == NULL) { RDEBUG("SQL query did not return any results"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst,sqlsocket); return 0; } if (row[0] == NULL){ RDEBUG("row[0] returned NULL"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst,sqlsocket); return 0; } ret = strlen(row[0]); if (ret >= freespace){ RDEBUG("Insufficient string space"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst,sqlsocket); return 0; } strlcpy(out,row[0],freespace); RDEBUG("sql_xlat finished"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst,sqlsocket); return ret;}static int generate_sql_clients(SQL_INST *inst){ SQLSOCK *sqlsocket; SQL_ROW row; char querystr[MAX_QUERY_LEN]; RADCLIENT *c; char *prefix_ptr = NULL; unsigned int i = 0; int numf = 0; DEBUG("rlm_sql (%s): Processing generate_sql_clients", inst->config->xlat_name); /* NAS query isn't xlat'ed */ strlcpy(querystr, inst->config->nas_query, sizeof(querystr)); DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s", inst->config->xlat_name, querystr); sqlsocket = sql_get_socket(inst); if (sqlsocket == NULL) return -1; if (rlm_sql_select_query(sqlsocket,inst,querystr)){ radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s", inst->config->xlat_name,querystr, (inst->module->sql_error)(sqlsocket, inst->config)); sql_release_socket(inst,sqlsocket); return -1; } while(rlm_sql_fetch_row(sqlsocket, inst) == 0) { i++; row = sqlsocket->row; if (row == NULL) break; /* * The return data for each row MUST be in the following order: * * 0. Row ID (currently unused) * 1. Name (or IP address) * 2. Shortname * 3. Type * 4. Secret * 5. Virtual Server (optional) */ if (!row[0]){ radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i); continue; } if (!row[1]){ radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]); continue; } if (!row[2]){ radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]); continue; } if (!row[4]){ radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]); continue; } DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name, row[1],row[2],row[4]); c = rad_malloc(sizeof(*c)); memset(c, 0, sizeof(*c));#ifdef WITH_DYNAMIC_CLIENTS c->dynamic = 1;#endif /* * Look for prefixes */ c->prefix = -1; prefix_ptr = strchr(row[1], '/'); if (prefix_ptr) { c->prefix = atoi(prefix_ptr + 1); if ((c->prefix < 0) || (c->prefix > 128)) { radlog(L_ERR, "rlm_sql (%s): Invalid Prefix value '%s' for IP.", inst->config->xlat_name, prefix_ptr + 1); free(c); continue; } /* Replace '/' with '\0' */ *prefix_ptr = '\0'; } /* * Always get the numeric representation of IP */ if (ip_hton(row[1], AF_UNSPEC, &c->ipaddr) < 0) { radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s: %s", inst->config->xlat_name, row[1], fr_strerror()); free(c); continue; } else { char buffer[256]; ip_ntoh(&c->ipaddr, buffer, sizeof(buffer)); c->longname = strdup(buffer); } if (c->prefix < 0) switch (c->ipaddr.af) { case AF_INET: c->prefix = 32; break; case AF_INET6: c->prefix = 128; break; default: break; } /* * Other values (secret, shortname, nastype, virtual_server) */ c->secret = strdup(row[4]); c->shortname = strdup(row[2]); if(row[3] != NULL) c->nastype = strdup(row[3]); numf = (inst->module->sql_num_fields)(sqlsocket, inst->config); if ((numf > 5) && (row[5] != NULL)) c->server = strdup(row[5]); DEBUG("rlm_sql (%s): Adding client %s (%s, server=%s) to clients list", inst->config->xlat_name, c->longname,c->shortname, c->server ? c->server : "<none>"); if (!client_add(NULL, c)) { DEBUG("rlm_sql (%s): Failed to add client %s (%s) to clients list. Maybe there's a duplicate?", inst->config->xlat_name, c->longname,c->shortname); client_free(c); return -1; } } (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst, sqlsocket); return 0;}/* * Translate the SQL queries. */static size_t sql_escape_func(char *out, size_t outlen, const char *in){ size_t len = 0; while (in[0]) { /* * Non-printable characters get replaced with their * mime-encoded equivalents. */ if ((in[0] < 32) || strchr(allowed_chars, *in) == NULL) { /* * Only 3 or less bytes available. */ if (outlen <= 3) { break; } snprintf(out, outlen, "=%02X", (unsigned char) in[0]); in++; out += 3; outlen -= 3; len += 3; continue; } /* * Only one byte left. */ if (outlen <= 1) { break; } /* * Allowed character. */ *out = *in; out++; in++; outlen--; len++; } *out = '\0'; return len;}/* * Set the SQL user name. * * We don't call the escape function here. The resulting string * will be escaped later in the queries xlat so we don't need to * escape it twice. (it will make things wrong if we have an * escape candidate character in the username) */int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username){ VALUE_PAIR *vp=NULL; char tmpuser[MAX_STRING_LEN]; tmpuser[0] = '\0'; sqlusername[0]= '\0'; /* Remove any user attr we added previously */ pairdelete(&request->packet->vps, PW_SQL_USER_NAME); if (username != NULL) { strlcpy(tmpuser, username, sizeof(tmpuser)); } else if (strlen(inst->config->query_user)) { radius_xlat(tmpuser, sizeof(tmpuser), inst->config->query_user, request, NULL); } else { return 0; } strlcpy(sqlusername, tmpuser, MAX_STRING_LEN); RDEBUG2("sql_set_user escaped user --> '%s'", sqlusername); vp = radius_pairmake(request, &request->packet->vps, "SQL-User-Name", NULL, 0); if (!vp) { radlog(L_ERR, "%s", fr_strerror()); return -1; } strlcpy(vp->vp_strvalue, tmpuser, sizeof(vp->vp_strvalue)); vp->length = strlen(vp->vp_strvalue); return 0;}static void sql_grouplist_free (SQL_GROUPLIST **group_list){ SQL_GROUPLIST *last; while(*group_list) { last = *group_list; *group_list = (*group_list)->next; free(last); }}static int sql_get_grouplist (SQL_INST *inst, SQLSOCK *sqlsocket, REQUEST *request, SQL_GROUPLIST **group_list){ char querystr[MAX_QUERY_LEN]; int num_groups = 0; SQL_ROW row; SQL_GROUPLIST *group_list_tmp; /* NOTE: sql_set_user should have been run before calling this function */ group_list_tmp = *group_list = NULL; if (!inst->config->groupmemb_query || (inst->config->groupmemb_query[0] == 0)) return 0; if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, request, sql_escape_func)) { radlog_request(L_ERR, 0, request, "xlat \"%s\" failed.", inst->config->groupmemb_query); return -1; } if (rlm_sql_select_query(sqlsocket, inst, querystr) < 0) { radlog_request(L_ERR, 0, request, "database query error, %s: %s", querystr, (inst->module->sql_error)(sqlsocket,inst->config)); return -1; } while (rlm_sql_fetch_row(sqlsocket, inst) == 0) { row = sqlsocket->row; if (row == NULL) break; if (row[0] == NULL){ RDEBUG("row[0] returned NULL"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_grouplist_free(group_list); return -1; } if (*group_list == NULL) { *group_list = rad_malloc(sizeof(SQL_GROUPLIST)); group_list_tmp = *group_list; } else { group_list_tmp->next = rad_malloc(sizeof(SQL_GROUPLIST)); group_list_tmp = group_list_tmp->next; } group_list_tmp->next = NULL; strlcpy(group_list_tmp->groupname, row[0], MAX_STRING_LEN); } (inst->module->sql_finish_select_query)(sqlsocket, inst->config); return num_groups;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -