X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Frlm_sql%2Frlm_sql.c;h=9bc0d35ec4b031b30dcbf4a4129ffc0c1b2c0ec0;hb=8dcf3e2a2b53342b0d6a65d7078b6c4629548321;hp=98afac254b332aa4f2c9f5bd0cd6eea835f6b9a4;hpb=bf888ac9d9620175e345105b073677112f132735;p=freeradius.git diff --git a/src/modules/rlm_sql/rlm_sql.c b/src/modules/rlm_sql/rlm_sql.c index 98afac2..9bc0d35 100644 --- a/src/modules/rlm_sql/rlm_sql.c +++ b/src/modules/rlm_sql/rlm_sql.c @@ -16,41 +16,27 @@ * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * - * Copyright 2000 The FreeRADIUS server project + * Copyright 2000,2006 The FreeRADIUS server project * Copyright 2000 Mike Machado * Copyright 2000 Alan DeKok */ -static const char rcsid[] = - "$Id$"; +#include +RCSID("$Id$") -#include "autoconf.h" +#include +#include +#include -#include #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "radiusd.h" -#include "modules.h" -#include "conffile.h" #include "rlm_sql.h" -#include "rad_assert.h" static char *allowed_chars = NULL; -static CONF_PARSER module_config[] = { +static const CONF_PARSER module_config[] = { {"driver",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"}, {"server",PW_TYPE_STRING_PTR, @@ -63,8 +49,10 @@ static CONF_PARSER module_config[] = { offsetof(SQL_CONFIG,sql_password), NULL, ""}, {"radius_db", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_db), NULL, "radius"}, - {"nas_table", PW_TYPE_STRING_PTR, - offsetof(SQL_CONFIG,sql_nas_table), NULL, "nas"}, + {"filename", PW_TYPE_FILENAME, /* for sqlite */ + offsetof(SQL_CONFIG,sql_file), NULL, NULL}, + {"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, @@ -72,23 +60,28 @@ static CONF_PARSER module_config[] = { {"readclients", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,do_clients), NULL, "no"}, {"deletestalesessions", PW_TYPE_BOOLEAN, - offsetof(SQL_CONFIG,deletestalesessions), NULL, "no"}, + offsetof(SQL_CONFIG,deletestalesessions), NULL, "yes"}, {"num_sql_socks", PW_TYPE_INTEGER, offsetof(SQL_CONFIG,num_sql_socks), NULL, "5"}, + {"lifetime", PW_TYPE_INTEGER, + offsetof(SQL_CONFIG,lifetime), NULL, "0"}, + {"max_queries", PW_TYPE_INTEGER, + offsetof(SQL_CONFIG,max_queries), NULL, "0"}, {"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, ""}, - {"query_on_not_found", PW_TYPE_BOOLEAN, - offsetof(SQL_CONFIG,query_on_not_found), NULL, "no"}, + {"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, ""}, + 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, ""}, +#ifdef WITH_ACCOUNTING {"accounting_onoff_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_onoff_query), NULL, ""}, {"accounting_update_query", PW_TYPE_STRING_PTR, @@ -103,61 +96,71 @@ static CONF_PARSER module_config[] = { offsetof(SQL_CONFIG,accounting_stop_query), NULL, ""}, {"accounting_stop_query_alt", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_stop_query_alt), NULL, ""}, +#endif {"group_membership_query", PW_TYPE_STRING_PTR, - offsetof(SQL_CONFIG,groupmemb_query), NULL, ""}, + offsetof(SQL_CONFIG,groupmemb_query), NULL, NULL}, {"connect_failure_retry_delay", PW_TYPE_INTEGER, offsetof(SQL_CONFIG,connect_failure_retry_delay), NULL, "60"}, +#ifdef WITH_SESSION_MGMT {"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, ""}, +#endif {"postauth_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,postauth_query), NULL, ""}, {"safe-characters", PW_TYPE_STRING_PTR, - offsetof(SQL_CONFIG,allowed_chars), NULL, + offsetof(SQL_CONFIG,allowed_chars), NULL, "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"}, + /* + * This only works for a few drivers. + */ + {"query_timeout", PW_TYPE_INTEGER, + offsetof(SQL_CONFIG,query_timeout), NULL, NULL}, + {NULL, -1, 0, NULL, NULL} }; -/*********************************************************************** - * start of main routines - ***********************************************************************/ -static int rlm_sql_init(void) { +/* + * Fall-Through checking function from rlm_files.c + */ +static int fallthrough(VALUE_PAIR *vp) +{ + VALUE_PAIR *tmp; + tmp = pairfind(vp, PW_FALL_THROUGH, 0); - /* - * FIXME: - * We should put the sqlsocket array here once - * the module code is reworked to not unload - * modules on HUP. This way we can have - * persistant connections. -jcarneal - */ - return 0; + return tmp ? tmp->vp_integer : 0; } + + /* * Yucky prototype. */ -static int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username); static int generate_sql_clients(SQL_INST *inst); -static int sql_escape_func(char *out, int outlen, const char *in); +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. + * + * For other statements (insert, update, delete, etc.), the + * number of affected rows will be returned. */ static int sql_xlat(void *instance, REQUEST *request, char *fmt, char *out, size_t freespace, - RADIUS_ESCAPE_STRING func) + UNUSED RADIUS_ESCAPE_STRING func) { SQLSOCK *sqlsocket; SQL_ROW row; SQL_INST *inst = instance; char querystr[MAX_QUERY_LEN]; char sqlusername[MAX_STRING_LEN]; - int ret = 0; + size_t ret = 0; + + RDEBUG("sql_xlat"); - DEBUG("rlm_sql (%s): - sql_xlat", inst->config->xlat_name); /* * 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 @@ -177,10 +180,63 @@ static int sql_xlat(void *instance, REQUEST *request, sqlsocket = sql_get_socket(inst); if (sqlsocket == NULL) return 0; + + /* + * If the query starts with any of the following prefixes, + * then return the number of rows affected + */ + if ((strncasecmp(querystr, "insert", 6) == 0) || + (strncasecmp(querystr, "update", 6) == 0) || + (strncasecmp(querystr, "delete", 6) == 0)) { + int numaffected; + char buffer[21]; /* 64bit max is 20 decimal chars + null byte */ + + if (rlm_sql_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; + } + + numaffected = (inst->module->sql_affected_rows)(sqlsocket, + inst->config); + if (numaffected < 1) { + RDEBUG("rlm_sql (%s): SQL query affected no rows", + inst->config->xlat_name); + } + + /* + * Don't chop the returned number if freespace is + * too small. This hack is necessary because + * some implementations of snprintf return the + * size of the written data, and others return + * the size of the data they *would* have written + * if the output buffer was large enough. + */ + snprintf(buffer, sizeof(buffer), "%d", numaffected); + ret = strlen(buffer); + if (ret >= freespace){ + RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", + inst->config->xlat_name); + (inst->module->sql_finish_query)(sqlsocket, + inst->config); + sql_release_socket(inst,sqlsocket); + return 0; + } + + memcpy(out, buffer, ret + 1); /* we did bounds checking above */ + + (inst->module->sql_finish_query)(sqlsocket, inst->config); + sql_release_socket(inst,sqlsocket); + return ret; + } /* else it's a SELECT statement */ + if (rlm_sql_select_query(sqlsocket,inst,querystr)){ radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s", inst->config->xlat_name,querystr, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + (inst->module->sql_error)(sqlsocket, inst->config)); sql_release_socket(inst,sqlsocket); return 0; } @@ -188,8 +244,7 @@ static int sql_xlat(void *instance, REQUEST *request, ret = rlm_sql_fetch_row(sqlsocket, inst); if (ret) { - DEBUG("rlm_sql (%s): SQL query did not succeed", - inst->config->xlat_name); + RDEBUG("SQL query did not succeed"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst,sqlsocket); return 0; @@ -197,33 +252,29 @@ static int sql_xlat(void *instance, REQUEST *request, row = sqlsocket->row; if (row == NULL) { - DEBUG("rlm_sql (%s): SQL query did not return any results", - inst->config->xlat_name); + 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){ - DEBUG("rlm_sql (%s): row[0] returned NULL", - inst->config->xlat_name); + 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){ - DEBUG("rlm_sql (%s): sql_xlat:: Insufficient string space", - inst->config->xlat_name); + if (ret >= freespace){ + RDEBUG("Insufficient string space"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst,sqlsocket); return 0; } - strncpy(out,row[0],ret); + strlcpy(out,row[0],freespace); - DEBUG("rlm_sql (%s): - sql_xlat finished", - inst->config->xlat_name); + RDEBUG("sql_xlat finished"); (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst,sqlsocket); @@ -236,25 +287,25 @@ static int generate_sql_clients(SQL_INST *inst) SQL_ROW row; char querystr[MAX_QUERY_LEN]; RADCLIENT *c; - char *netmask; + char *prefix_ptr = NULL; unsigned int i = 0; - - DEBUG("rlm_sql (%s): - generate_sql_clients",inst->config->xlat_name); + int numf = 0; - if (inst->config->sql_nas_table == NULL){ - radlog(L_ERR, "rlm_sql (%s): sql_nas_table is NULL.",inst->config->xlat_name); - return -1; - } - snprintf(querystr,MAX_QUERY_LEN - 1,"SELECT * FROM %s",inst->config->sql_nas_table); + 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); - DEBUG("rlm_sql (%s): Query: %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, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + inst->config->xlat_name,querystr, + (inst->module->sql_error)(sqlsocket, inst->config)); sql_release_socket(inst,sqlsocket); return -1; } @@ -264,14 +315,16 @@ static int generate_sql_clients(SQL_INST *inst) row = sqlsocket->row; if (row == NULL) break; -/* - * Format: - * Row1 Row2 Row3 Row4 Row5 Row6 Row7 Row8 - * - * id nasname shortname type ports secret community description - * - */ - + /* + * 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; @@ -280,101 +333,90 @@ static int generate_sql_clients(SQL_INST *inst) radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]); continue; } - if (strlen(row[1]) >= sizeof(c->longname)){ - radlog(L_ERR, "rlm_sql (%s): nasname of length %d is greater than the allowed maximum of %d", - inst->config->xlat_name,strlen(row[1]),sizeof(c->longname) - 1); - 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 (strlen(row[2]) >= sizeof(c->shortname)){ - radlog(L_ERR, "rlm_sql (%s): shortname of length %d is greater than the allowed maximum of %d", - inst->config->xlat_name,strlen(row[2]),sizeof(c->shortname) - 1); - continue; - } - if (row[3] && strlen(row[3]) >= sizeof(c->nastype)){ - radlog(L_ERR, "rlm_sql (%s): nastype of length %d is greater than the allowed maximum of %d", - inst->config->xlat_name,strlen(row[3]),sizeof(c->nastype) - 1); - continue; - } - if (!row[5]){ + if (!row[4]){ radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]); continue; } - if (strlen(row[5]) >= sizeof(c->secret)){ - radlog(L_ERR, "rlm_sql (%s): secret of length %d is greater than the allowed maximum of %d", - inst->config->xlat_name,strlen(row[5]),sizeof(c->secret) - 1); - continue; - } DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name, - row[1],row[2],row[5]); + row[1],row[2],row[4]); - c = rad_malloc(sizeof(RADCLIENT)); - memset(c, 0, sizeof(RADCLIENT)); + c = rad_malloc(sizeof(*c)); + memset(c, 0, sizeof(*c)); + +#ifdef WITH_DYNAMIC_CLIENTS + c->dynamic = 1; +#endif - c->netmask = ~0; - netmask = strchr(row[1], '/'); - /* - * Look for netmasks. + * Look for prefixes */ - c->netmask = ~0; - if (netmask) { - int mask_length; - - mask_length = atoi(netmask + 1); - if ((mask_length < 0) || (mask_length > 32)) { - radlog(L_ERR, "rlm_sql (%s): Invalid value '%s' for IP network mask for nasname %s.", - inst->config->xlat_name, netmask + 1,row[1]); + 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; } - - if (mask_length == 0) { - c->netmask = 0; - } else { - c->netmask = ~0 << (32 - mask_length); - } - - *netmask = '\0'; - c->netmask = htonl(c->netmask); + /* Replace '/' with '\0' */ + *prefix_ptr = '\0'; } - c->ipaddr = ip_getaddr(row[1]); - if (c->ipaddr == INADDR_NONE) { - radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s", - inst->config->xlat_name, row[1]); + /* + * 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); } - /* - * Update the client name again... - */ - if (netmask) { - *netmask = '/'; - c->ipaddr &= c->netmask; - strcpy(c->longname, row[1]); - } else { - ip_hostname(c->longname, sizeof(c->longname), - c->ipaddr); + if (c->prefix < 0) switch (c->ipaddr.af) { + case AF_INET: + c->prefix = 32; + break; + case AF_INET6: + c->prefix = 128; + break; + default: + break; } - strcpy((char *)c->secret, row[5]); - strcpy(c->shortname, row[2]); + /* + * Other values (secret, shortname, nastype, virtual_server) + */ + c->secret = strdup(row[4]); + c->shortname = strdup(row[2]); if(row[3] != NULL) - strcpy(c->nastype, row[3]); - - DEBUG("rlm_sql (%s): Adding client %s (%s) to clients list",inst->config->xlat_name, - c->longname,c->shortname); + c->nastype = strdup(row[3]); - c->next = mainconfig.clients; - mainconfig.clients = c; + numf = (inst->module->sql_num_fields)(sqlsocket, inst->config); + if ((numf > 5) && (row[5] != NULL) && *row[5]) 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 : ""); + if (!client_add(NULL, c)) { + sql_release_socket(inst, sqlsocket); + 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); @@ -386,9 +428,9 @@ static int generate_sql_clients(SQL_INST *inst) /* * Translate the SQL queries. */ -static int sql_escape_func(char *out, int outlen, const char *in) +static size_t sql_escape_func(char *out, size_t outlen, const char *in) { - int len = 0; + size_t len = 0; while (in[0]) { /* @@ -440,7 +482,7 @@ static int sql_escape_func(char *out, int outlen, const char *in) * escape it twice. (it will make things wrong if we have an * escape candidate character in the username) */ -static int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username) +int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username) { VALUE_PAIR *vp=NULL; char tmpuser[MAX_STRING_LEN]; @@ -449,32 +491,101 @@ static int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, con sqlusername[0]= '\0'; /* Remove any user attr we added previously */ - pairdelete(&request->packet->vps, PW_SQL_USER_NAME); + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); if (username != NULL) { - strNcpy(tmpuser, username, MAX_STRING_LEN); + 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; } - if (*tmpuser) { - strNcpy(sqlusername, tmpuser, MAX_STRING_LEN); - DEBUG2("rlm_sql (%s): sql_set_user escaped user --> '%s'", - inst->config->xlat_name, sqlusername); - vp = pairmake("SQL-User-Name", sqlusername, 0); - if (vp == NULL) { - radlog(L_ERR, "%s", librad_errstr); - return -1; - } + 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; + } - pairadd(&request->packet->vps, vp); + 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 { + rad_assert(group_list_tmp != NULL); + 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); } - return -1; + + (inst->module->sql_finish_select_query)(sqlsocket, inst->config); + + return num_groups; } + /* * sql groupcmp function. That way we can do group comparisons (in the users file for example) * with the group memberships reciding in sql @@ -482,104 +593,224 @@ static int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, con * username will then be checked with the passed check string. */ -static int sql_groupcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, +static int sql_groupcmp(void *instance, REQUEST *request, VALUE_PAIR *request_vp, VALUE_PAIR *check, VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs) { SQLSOCK *sqlsocket; - SQL_ROW row; SQL_INST *inst = instance; - char querystr[MAX_QUERY_LEN]; char sqlusername[MAX_STRING_LEN]; + SQL_GROUPLIST *group_list, *group_list_tmp; check_pairs = check_pairs; reply_pairs = reply_pairs; + request_vp = request_vp; - DEBUG("rlm_sql (%s): - sql_groupcmp", inst->config->xlat_name); - if (!check || !check->strvalue || !check->length){ - DEBUG("rlm_sql (%s): sql_groupcmp: Illegal group name", - inst->config->xlat_name); + RDEBUG("sql_groupcmp"); + if (!check || !check->vp_strvalue || !check->length){ + RDEBUG("sql_groupcmp: Illegal group name"); return 1; } - if (req == NULL){ - DEBUG("rlm_sql (%s): sql_groupcmp: NULL request", - inst->config->xlat_name); + if (!request){ + RDEBUG("sql_groupcmp: NULL request"); return 1; } - if (inst->config->groupmemb_query[0] == 0) - return 1; /* * Set, escape, and check the user attr here */ - if (sql_set_user(inst, req, sqlusername, NULL) < 0) + if (sql_set_user(inst, request, sqlusername, NULL) < 0) return 1; - if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, req, sql_escape_func)){ - radlog(L_ERR, "rlm_sql (%s): xlat failed.", - inst->config->xlat_name); + + /* + * Get a socket for this lookup + */ + sqlsocket = sql_get_socket(inst); + if (sqlsocket == NULL) { /* Remove the username we (maybe) added above */ - pairdelete(&req->packet->vps, PW_SQL_USER_NAME); + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); return 1; } - /* Remove the username we (maybe) added above */ - pairdelete(&req->packet->vps, PW_SQL_USER_NAME); - sqlsocket = sql_get_socket(inst); - if (sqlsocket == NULL) - return 1; - if ((inst->module->sql_select_query)(sqlsocket,inst->config,querystr) <0){ - radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s", - inst->config->xlat_name,querystr, - (char *)(inst->module->sql_error)(sqlsocket,inst->config)); - sql_release_socket(inst,sqlsocket); + /* + * Get the list of groups this user is a member of + */ + if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) { + radlog_request(L_ERR, 0, request, + "Error getting group membership"); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); + sql_release_socket(inst, sqlsocket); return 1; } - while (rlm_sql_fetch_row(sqlsocket, inst) == 0) { - row = sqlsocket->row; - if (row == NULL) - break; - if (row[0] == NULL){ - DEBUG("rlm_sql (%s): row[0] returned NULL", - inst->config->xlat_name); - (inst->module->sql_finish_select_query)(sqlsocket, inst->config); + + for (group_list_tmp = group_list; group_list_tmp != NULL; group_list_tmp = group_list_tmp->next) { + if (strcmp(group_list_tmp->groupname, check->vp_strvalue) == 0){ + RDEBUG("sql_groupcmp finished: User is a member of group %s", + check->vp_strvalue); + /* Free the grouplist */ + sql_grouplist_free(&group_list); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); sql_release_socket(inst, sqlsocket); - return 1; - } - if (strcmp(row[0],check->strvalue) == 0){ - DEBUG("rlm_sql (%s): - sql_groupcmp finished: User belongs in group %s", - inst->config->xlat_name, - (char *)check->strvalue); - (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst, sqlsocket); return 0; } } - (inst->module->sql_finish_select_query)(sqlsocket, inst->config); + /* Free the grouplist */ + sql_grouplist_free(&group_list); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); sql_release_socket(inst,sqlsocket); - DEBUG("rlm_sql (%s): - sql_groupcmp finished: User does not belong in group %s", - inst->config->xlat_name, (char *)check->strvalue); + RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", + check->vp_strvalue); return 1; } -static int rlm_sql_detach(void *instance) + +static int rlm_sql_process_groups(SQL_INST *inst, REQUEST *request, SQLSOCK *sqlsocket, int *dofallthrough) { - SQL_INST *inst = instance; + VALUE_PAIR *check_tmp = NULL; + VALUE_PAIR *reply_tmp = NULL; + SQL_GROUPLIST *group_list, *group_list_tmp; + VALUE_PAIR *sql_group = NULL; + char querystr[MAX_QUERY_LEN]; + int found = 0; + int rows; - if (inst->sqlpool) { - sql_poolfree(inst); + /* + * Get the list of groups this user is a member of + */ + if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) { + radlog_request(L_ERR, 0, request, "Error retrieving group list"); + return -1; } - if (inst->config->xlat_name) { - xlat_unregister(inst->config->xlat_name,sql_xlat); - free(inst->config->xlat_name); + for (group_list_tmp = group_list; group_list_tmp != NULL && *dofallthrough != 0; group_list_tmp = group_list_tmp->next) { + /* + * Add the Sql-Group attribute to the request list so we know + * which group we're retrieving attributes for + */ + sql_group = pairmake("Sql-Group", group_list_tmp->groupname, T_OP_EQ); + if (!sql_group) { + radlog_request(L_ERR, 0, request, + "Error creating Sql-Group attribute"); + return -1; + } + pairadd(&request->packet->vps, sql_group); + if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func)) { + radlog_request(L_ERR, 0, request, + "Error generating query; rejecting user"); + /* Remove the grouup we added above */ + pairdelete(&request->packet->vps, PW_SQL_GROUP, 0); + return -1; + } + rows = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr); + if (rows < 0) { + radlog_request(L_ERR, 0, request, "Error retrieving check pairs for group %s", + group_list_tmp->groupname); + /* Remove the grouup we added above */ + pairdelete(&request->packet->vps, PW_SQL_GROUP, 0); + pairfree(&check_tmp); + return -1; + } else if (rows > 0) { + /* + * Only do this if *some* check pairs were returned + */ + if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) { + found = 1; + RDEBUG2("User found in group %s", + group_list_tmp->groupname); + /* + * Now get the reply pairs since the paircompare matched + */ + if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) { + radlog_request(L_ERR, 0, request, "Error generating query; rejecting user"); + /* Remove the grouup we added above */ + pairdelete(&request->packet->vps, PW_SQL_GROUP, 0); + pairfree(&check_tmp); + return -1; + } + if (sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr) < 0) { + radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s", + group_list_tmp->groupname); + /* Remove the grouup we added above */ + pairdelete(&request->packet->vps, PW_SQL_GROUP, 0); + pairfree(&check_tmp); + pairfree(&reply_tmp); + return -1; + } + *dofallthrough = fallthrough(reply_tmp); + pairxlatmove(request, &request->reply->vps, &reply_tmp); + pairxlatmove(request, &request->config_items, &check_tmp); + } + } else { + /* + * rows == 0. This is like having the username on a line + * in the user's file with no check vp's. As such, we treat + * it as found and add the reply attributes, so that we + * match expected behavior + */ + found = 1; + RDEBUG2("User found in group %s", + group_list_tmp->groupname); + /* + * Now get the reply pairs since the paircompare matched + */ + if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) { + radlog_request(L_ERR, 0, request, "Error generating query; rejecting user"); + /* Remove the grouup we added above */ + pairdelete(&request->packet->vps, PW_SQL_GROUP, 0); + pairfree(&check_tmp); + return -1; + } + if (sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr) < 0) { + radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s", + group_list_tmp->groupname); + /* Remove the grouup we added above */ + pairdelete(&request->packet->vps, PW_SQL_GROUP, 0); + pairfree(&check_tmp); + pairfree(&reply_tmp); + return -1; + } + *dofallthrough = fallthrough(reply_tmp); + pairxlatmove(request, &request->reply->vps, &reply_tmp); + pairxlatmove(request, &request->config_items, &check_tmp); + } + + /* + * Delete the Sql-Group we added above + * And clear out the pairlists + */ + pairdelete(&request->packet->vps, PW_SQL_GROUP, 0); + pairfree(&check_tmp); + pairfree(&reply_tmp); } + sql_grouplist_free(&group_list); + return found; +} + + +static int rlm_sql_detach(void *instance) +{ + SQL_INST *inst = instance; + paircompare_unregister(PW_SQL_GROUP, sql_groupcmp); if (inst->config) { int i; + if (inst->sqlpool) { + sql_poolfree(inst); + } + + if (inst->config->xlat_name) { + xlat_unregister(inst->config->xlat_name,(RAD_XLAT_FUNC)sql_xlat); + free(inst->config->xlat_name); + } + /* * Free up dynamically allocated string pointers. */ @@ -602,7 +833,12 @@ static int rlm_sql_detach(void *instance) free(*p); *p = NULL; } - allowed_chars = NULL; + /* + * Catch multiple instances of the module. + */ + if (allowed_chars == inst->config->allowed_chars) { + allowed_chars = NULL; + } free(inst->config); inst->config = NULL; } @@ -622,7 +858,7 @@ static int rlm_sql_detach(void *instance) static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) { SQL_INST *inst; - char *xlat_name; + const char *xlat_name; inst = rad_malloc(sizeof(SQL_INST)); memset(inst, 0, sizeof(SQL_INST)); @@ -631,6 +867,17 @@ static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) memset(inst->config, 0, sizeof(SQL_CONFIG)); /* + * Export these methods, too. This avoids RTDL_GLOBAL. + */ + inst->sql_set_user = sql_set_user; + inst->sql_get_socket = sql_get_socket; + inst->sql_release_socket = sql_release_socket; + inst->sql_escape_func = sql_escape_func; + inst->sql_query = rlm_sql_query; + inst->sql_select_query = rlm_sql_select_query; + inst->sql_fetch_row = rlm_sql_fetch_row; + + /* * If the configuration parameters can't be parsed, then * fail. */ @@ -640,15 +887,45 @@ static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) } xlat_name = cf_section_name2(conf); - if (xlat_name == NULL) + if (xlat_name == NULL) { xlat_name = cf_section_name1(conf); + } else { + char *group_name; + DICT_ATTR *dattr; + ATTR_FLAGS flags; + + /* + * Allocate room for -SQL-Group + */ + group_name = rad_malloc((strlen(xlat_name) + 1 + 11) * sizeof(char)); + sprintf(group_name,"%s-SQL-Group",xlat_name); + DEBUG("rlm_sql Creating new attribute %s",group_name); + + memset(&flags, 0, sizeof(flags)); + dict_addattr(group_name, 0, PW_TYPE_STRING, -1, flags); + dattr = dict_attrbyname(group_name); + if (dattr == NULL){ + radlog(L_ERR, "rlm_ldap: Failed to create attribute %s",group_name); + free(group_name); + free(inst); /* FIXME: detach */ + return -1; + } + + if (inst->config->groupmemb_query && + inst->config->groupmemb_query[0]) { + DEBUG("rlm_sql: Registering sql_groupcmp for %s",group_name); + paircompare_register(dattr->attr, PW_USER_NAME, sql_groupcmp, inst); + } + + free(group_name); + } if (xlat_name){ inst->config->xlat_name = strdup(xlat_name); - xlat_register(xlat_name, sql_xlat, inst); + xlat_register(xlat_name, (RAD_XLAT_FUNC)sql_xlat, inst); } if (inst->config->num_sql_socks > MAX_SQL_SOCKS) { - radlog(L_ERR | L_CONS, "rlm_sql (%s): sql_instantiate: number of sqlsockets cannot exceed MAX_SQL_SOCKS, %d", + radlog(L_ERR, "rlm_sql (%s): sql_instantiate: number of sqlsockets cannot exceed MAX_SQL_SOCKS, %d", inst->config->xlat_name, MAX_SQL_SOCKS); rlm_sql_detach(inst); return -1; @@ -658,27 +935,26 @@ static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) * Sanity check for crazy people. */ if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) { - radlog(L_ERR, "rlm_sql (%s): \"%s\" is NOT an SQL driver!", - inst->config->xlat_name, inst->config->sql_driver); + radlog(L_ERR, "\"%s\" is NOT an SQL driver!", + inst->config->sql_driver); rlm_sql_detach(inst); return -1; } inst->handle = lt_dlopenext(inst->config->sql_driver); if (inst->handle == NULL) { - radlog(L_ERR, "rlm_sql (%s): Could not link driver %s: %s", - inst->config->xlat_name, inst->config->sql_driver, + radlog(L_ERR, "Could not link driver %s: %s", + inst->config->sql_driver, lt_dlerror()); - radlog(L_ERR, "rlm_sql (%s): Make sure it (and all its dependent libraries!) are in the search path of your system's ld.", - inst->config->xlat_name); + radlog(L_ERR, "Make sure it (and all its dependent libraries!) are in the search path of your system's ld."); rlm_sql_detach(inst); return -1; } inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle, inst->config->sql_driver); if (!inst->module) { - radlog(L_ERR, "rlm_sql (%s): Could not link symbol %s: %s", - inst->config->xlat_name, inst->config->sql_driver, + radlog(L_ERR, "Could not link symbol %s: %s", + inst->config->sql_driver, lt_dlerror()); rlm_sql_detach(inst); return -1; @@ -696,11 +972,15 @@ static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) rlm_sql_detach(inst); return -1; } - paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst); + + if (inst->config->groupmemb_query && + inst->config->groupmemb_query[0]) { + paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst); + } if (inst->config->do_clients){ if (generate_sql_clients(inst) == -1){ - radlog(L_ERR, "rlm_sql (%s): generate_sql_clients() returned error",inst->config->xlat_name); + radlog(L_ERR, "Failed to load clients from SQL."); rlm_sql_detach(inst); return -1; } @@ -712,11 +992,6 @@ static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) return RLM_MODULE_OK; } -static int rlm_sql_destroy(void) -{ - return 0; -} - static int rlm_sql_authorize(void *instance, REQUEST * request) { @@ -724,155 +999,179 @@ static int rlm_sql_authorize(void *instance, REQUEST * request) VALUE_PAIR *reply_tmp = NULL; VALUE_PAIR *user_profile = NULL; int found = 0; + int dofallthrough = 1; + int rows; SQLSOCK *sqlsocket; SQL_INST *inst = instance; char querystr[MAX_QUERY_LEN]; char sqlusername[MAX_STRING_LEN]; - /* - * They MUST have a user name to do SQL authorization. + * the profile username is used as the sqlusername during + * profile checking so that we don't overwrite the orignal + * sqlusername string */ - if ((request->username == NULL) || - (request->username->length == 0)) { - radlog(L_ERR, "rlm_sql (%s): zero length username not permitted\n", inst->config->xlat_name); - return RLM_MODULE_INVALID; - } + char profileusername[MAX_STRING_LEN]; /* - * Set, escape, and check the user attr here. + * Set, escape, and check the user attr here */ if (sql_set_user(inst, request, sqlusername, NULL) < 0) return RLM_MODULE_FAIL; - radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func); + + /* + * reserve a socket + */ sqlsocket = sql_get_socket(inst); if (sqlsocket == NULL) { /* Remove the username we (maybe) added above */ - pairdelete(&request->packet->vps, PW_SQL_USER_NAME); + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); return RLM_MODULE_FAIL; } + /* - * After this point, ALL 'return's MUST release the SQL socket! + * After this point, ALL 'return's MUST release the SQL socket! */ - found = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_USERDATA); /* - * Find the entry for the user. + * Alright, start by getting the specific entry for the user */ - if (found > 0) { - radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func); - sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA); - radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func); - sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_USERDATA); - radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func); - sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA); - } else if (found < 0) { - radlog(L_ERR, "rlm_sql (%s): SQL query error; rejecting user", - inst->config->xlat_name); + if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func)) { + radlog_request(L_ERR, 0, request, "Error generating query; rejecting user"); + sql_release_socket(inst, sqlsocket); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); + return RLM_MODULE_FAIL; + } + rows = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr); + if (rows < 0) { + radlog_request(L_ERR, 0, request, "SQL query error; rejecting user"); sql_release_socket(inst, sqlsocket); /* Remove the username we (maybe) added above */ - pairdelete(&request->packet->vps, PW_SQL_USER_NAME); + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); pairfree(&check_tmp); return RLM_MODULE_FAIL; + } else if (rows > 0) { + /* + * Only do this if *some* check pairs were returned + */ + if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) { + found = 1; + RDEBUG2("User found in radcheck table"); - } else { - radlog(L_DBG, "rlm_sql (%s): User %s not found in radcheck", - inst->config->xlat_name, sqlusername); + if (inst->config->authorize_reply_query && + *inst->config->authorize_reply_query) { - /* - * We didn't find the user in radcheck, so we try looking - * for radgroupcheck entry - */ - radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func); - found = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA); - radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func); - sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA); - } - if (!found) - radlog(L_DBG, "rlm_sql (%s): User %s not found in radgroupcheck", - inst->config->xlat_name, sqlusername); - if (found || (!found && inst->config->query_on_not_found)){ - int def_found = 0; + /* + * Now get the reply pairs since the paircompare matched + */ + if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func)) { + radlog_request(L_ERR, 0, request, "Error generating query; rejecting user"); + sql_release_socket(inst, sqlsocket); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); + pairfree(&check_tmp); + return RLM_MODULE_FAIL; + } + if (sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr) < 0) { + radlog_request(L_ERR, 0, request, "SQL query error; rejecting user"); + sql_release_socket(inst, sqlsocket); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); + pairfree(&check_tmp); + pairfree(&reply_tmp); + return RLM_MODULE_FAIL; + } + if (!inst->config->read_groups) + dofallthrough = fallthrough(reply_tmp); + pairxlatmove(request, &request->reply->vps, &reply_tmp); + } + pairxlatmove(request, &request->config_items, &check_tmp); + } + } + + /* + * Clear out the pairlists + */ + pairfree(&check_tmp); + pairfree(&reply_tmp); + + /* + * dofallthrough is set to 1 by default so that if the user information + * is not found, we will still process groups. If the user information, + * however, *is* found, Fall-Through must be set in order to process + * the groups as well + */ + if (dofallthrough) { + rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough); + if (rows < 0) { + radlog_request(L_ERR, 0, request, "Error processing groups; rejecting user"); + sql_release_socket(inst, sqlsocket); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); + return RLM_MODULE_FAIL; + } else if (rows > 0) { + found = 1; + } + } + + /* + * repeat the above process with the default profile or User-Profile + */ + if (dofallthrough) { + int profile_found = 0; /* * Check for a default_profile or for a User-Profile. */ - user_profile = pairfind(request->config_items, PW_USER_PROFILE); + user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0); if (inst->config->default_profile[0] != 0 || user_profile != NULL){ char *profile = inst->config->default_profile; if (user_profile != NULL) - profile = user_profile->strvalue; + profile = user_profile->vp_strvalue; if (profile && strlen(profile)){ - radlog(L_DBG, "rlm_sql (%s): Checking profile %s", - inst->config->xlat_name, profile); - if (sql_set_user(inst, request, sqlusername, profile) < 0) { + RDEBUG("Checking profile %s", profile); + if (sql_set_user(inst, request, profileusername, profile) < 0) { + radlog_request(L_ERR, 0, request, "Error setting profile; rejecting user"); sql_release_socket(inst, sqlsocket); - pairfree(&reply_tmp); - pairfree(&check_tmp); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); return RLM_MODULE_FAIL; + } else { + profile_found = 1; } - radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, - request, sql_escape_func); - def_found = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA); - if (def_found) - found = 1; - radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, - request, sql_escape_func); - sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA); + } + } + + if (profile_found) { + rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough); + if (rows < 0) { + radlog_request(L_ERR, 0, request, "Error processing profile groups; rejecting user"); + sql_release_socket(inst, sqlsocket); + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); + return RLM_MODULE_FAIL; + } else if (rows > 0) { + found = 1; } } } - /* - * We don't need the SQL socket anymore. - */ + /* Remove the username we (maybe) added above */ + pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0); sql_release_socket(inst, sqlsocket); if (!found) { - radlog(L_DBG, "rlm_sql (%s): User not found", - inst->config->xlat_name); - /* Remove the username we (maybe) added above */ - pairdelete(&request->packet->vps, PW_SQL_USER_NAME); - pairfree(&reply_tmp); - pairfree(&check_tmp); - return RLM_MODULE_NOTFOUND; - } - - /* - * Uncomment these lines for debugging - * Recompile, and run 'radiusd -X' - */ - - /* - DEBUG2("rlm_sql: check items"); - vp_listdebug(check_tmp); - DEBUG2("rlm_sql: reply items"); - vp_listdebug(reply_tmp); - */ - - if (paircmp(request, request->packet->vps, check_tmp, &reply_tmp) != 0) { - radlog(L_INFO, "rlm_sql (%s): No matching entry in the database for request from user [%s]", - inst->config->xlat_name, sqlusername); - /* Remove the username we (maybe) added above */ - pairdelete(&request->packet->vps, PW_SQL_USER_NAME); - pairfree(&reply_tmp); - pairfree(&check_tmp); + RDEBUG("User %s not found", sqlusername); return RLM_MODULE_NOTFOUND; + } else { + return RLM_MODULE_OK; } - - pairxlatmove(request, &request->reply->vps, &reply_tmp); - pairxlatmove(request, &request->config_items, &check_tmp); - pairfree(&reply_tmp); - pairfree(&check_tmp); - - /* Remove the username we (maybe) added above */ - pairdelete(&request->packet->vps, PW_SQL_USER_NAME); - - return RLM_MODULE_OK; } +#ifdef WITH_ACCOUNTING /* * Accounting: save the account data to our sql table */ @@ -897,12 +1196,11 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { /* * Find the Acct Status Type */ - if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL) { - acctstatustype = pair->lvalue; + if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0)) != NULL) { + acctstatustype = pair->vp_integer; } else { radius_xlat(logstr, sizeof(logstr), "packet has no accounting status type. [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL); - radlog(L_ERR, "rlm_sql (%s) in sql_accounting: %s", - inst->config->xlat_name, logstr); + radlog_request(L_ERR, 0, request, "%s", logstr); return RLM_MODULE_INVALID; } @@ -913,7 +1211,7 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { */ case PW_STATUS_ACCOUNTING_ON: case PW_STATUS_ACCOUNTING_OFF: - radlog(L_INFO, "rlm_sql (%s): received Acct On/Off packet", inst->config->xlat_name); + RDEBUG("Received Acct On/Off packet"); radius_xlat(querystr, sizeof(querystr), inst->config->accounting_onoff_query, request, sql_escape_func); query_log(request, inst, querystr); @@ -922,9 +1220,8 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { return(RLM_MODULE_FAIL); if (*querystr) { /* non-empty query */ if (rlm_sql_query(sqlsocket, inst, querystr)) { - radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting for Acct On/Off packet - %s", - inst->config->xlat_name, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + radlog_request(L_ERR, 0, request, "Couldn't update SQL accounting for Acct On/Off packet - %s", + (inst->module->sql_error)(sqlsocket, inst->config)); ret = RLM_MODULE_FAIL; } (inst->module->sql_finish_query)(sqlsocket, inst->config); @@ -950,9 +1247,8 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { return(RLM_MODULE_FAIL); if (*querystr) { /* non-empty query */ if (rlm_sql_query(sqlsocket, inst, querystr)) { - radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting ALIVE record - %s", - inst->config->xlat_name, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + radlog_request(L_ERR, 0, request, "Couldn't update SQL accounting ALIVE record - %s", + (inst->module->sql_error)(sqlsocket, inst->config)); ret = RLM_MODULE_FAIL; } else { @@ -969,9 +1265,8 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { query_log(request, inst, querystr); if (*querystr) { /* non-empty query */ if (rlm_sql_query(sqlsocket, inst, querystr)) { - radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting ALIVE record - %s", - inst->config->xlat_name, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + radlog_request(L_ERR, 0, request, "Couldn't insert SQL accounting ALIVE record - %s", + (inst->module->sql_error)(sqlsocket, inst->config)); ret = RLM_MODULE_FAIL; } (inst->module->sql_finish_query)(sqlsocket, inst->config); @@ -1000,9 +1295,8 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { return(RLM_MODULE_FAIL); if (*querystr) { /* non-empty query */ if (rlm_sql_query(sqlsocket, inst, querystr)) { - radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting START record - %s", - inst->config->xlat_name, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + radlog_request(L_ERR, 0, request, "Couldn't insert SQL accounting START record - %s", + (inst->module->sql_error)(sqlsocket, inst->config)); /* * We failed the insert above. It's probably because @@ -1014,9 +1308,8 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { if (*querystr) { /* non-empty query */ if (rlm_sql_query(sqlsocket, inst, querystr)) { - radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting START record - %s", - inst->config->xlat_name, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + radlog_request(L_ERR, 0, request, "Couldn't update SQL accounting START record - %s", + (inst->module->sql_error)(sqlsocket, inst->config)); ret = RLM_MODULE_FAIL; } (inst->module->sql_finish_query)(sqlsocket, inst->config); @@ -1044,9 +1337,8 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { return(RLM_MODULE_FAIL); if (*querystr) { /* non-empty query */ if (rlm_sql_query(sqlsocket, inst, querystr)) { - radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting STOP record - %s", - inst->config->xlat_name, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + radlog_request(L_ERR, 0, request, "Couldn't update SQL accounting STOP record - %s", + (inst->module->sql_error)(sqlsocket, inst->config)); ret = RLM_MODULE_FAIL; } else { @@ -1065,14 +1357,14 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { * This is to fix CISCO's aaa from filling our * table with bogus crap */ - if ((pair = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME)) != NULL) - acctsessiontime = pair->lvalue; + if ((pair = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME, 0)) != NULL) + acctsessiontime = pair->vp_integer; if (acctsessiontime <= 0) { radius_xlat(logstr, sizeof(logstr), "stop packet with zero session length. [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL); - radlog(L_ERR, "rlm_sql (%s) in sql_accounting: %s", inst->config->xlat_name, logstr); + radlog_request(L_DBG, 0, request, "%s", logstr); sql_release_socket(inst, sqlsocket); - ret = RLM_MODULE_NOOP; + return RLM_MODULE_NOOP; } #endif @@ -1081,9 +1373,9 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { if (*querystr) { /* non-empty query */ if (rlm_sql_query(sqlsocket, inst, querystr)) { - radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting STOP record - %s", - inst->config->xlat_name, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + radlog_request(L_ERR, 0, request, "Couldn't insert SQL accounting STOP record - %s", + + (inst->module->sql_error)(sqlsocket, inst->config)); ret = RLM_MODULE_FAIL; } (inst->module->sql_finish_query)(sqlsocket, inst->config); @@ -1098,7 +1390,8 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { * Anything else is ignored. */ default: - radlog(L_INFO, "rlm_sql (%s): Unsupported Acct-Status-Type = %d", inst->config->xlat_name, acctstatustype); + RDEBUG("Unsupported Acct-Status-Type = %d", + acctstatustype); return RLM_MODULE_NOOP; break; @@ -1108,8 +1401,10 @@ static int rlm_sql_accounting(void *instance, REQUEST * request) { return ret; } +#endif +#ifdef WITH_SESSION_MGMT /* * See if a user is already logged in. Sets request->simul_count to the * current session count for this user. @@ -1134,12 +1429,13 @@ static int rlm_sql_checksimul(void *instance, REQUEST * request) { int nas_port = 0; /* If simul_count_query is not defined, we don't do any checking */ - if (inst->config->simul_count_query[0] == 0) { + if (!inst->config->simul_count_query || + (inst->config->simul_count_query[0] == 0)) { return RLM_MODULE_NOOP; } if((request->username == NULL) || (request->username->length == 0)) { - radlog(L_ERR, "rlm_sql (%s): Zero Length username not permitted\n", inst->config->xlat_name); + radlog_request(L_ERR, 0, request, "Zero Length username not permitted\n"); return RLM_MODULE_INVALID; } @@ -1183,17 +1479,19 @@ static int rlm_sql_checksimul(void *instance, REQUEST * request) { return RLM_MODULE_OK; } - /* Looks like too many sessions, so lets start verifying them */ - - if (inst->config->simul_verify_query[0] == 0) { - /* No verify query defined, so skip verify step and rely on count query only */ + /* + * Looks like too many sessions, so let's start verifying + * them, unless told to rely on count query only. + */ + if (!inst->config->simul_verify_query || + (inst->config->simul_verify_query[0] == '\0')) { sql_release_socket(inst, sqlsocket); return RLM_MODULE_OK; } radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func); if(rlm_sql_select_query(sqlsocket, inst, querystr)) { - radlog(L_ERR, "rlm_sql (%s): sql_checksimul: Database query error", inst->config->xlat_name); + radlog_request(L_ERR, 0, request, "Database query error"); sql_release_socket(inst, sqlsocket); return RLM_MODULE_FAIL; } @@ -1203,10 +1501,10 @@ static int rlm_sql_checksimul(void *instance, REQUEST * request) { */ request->simul_count = 0; - if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS)) != NULL) - ipno = vp->lvalue; - if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL) - call_num = vp->strvalue; + if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0)) != NULL) + ipno = vp->vp_ipaddr; + if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0)) != NULL) + call_num = vp->vp_strvalue; while (rlm_sql_fetch_row(sqlsocket, inst) == 0) { @@ -1216,13 +1514,13 @@ static int rlm_sql_checksimul(void *instance, REQUEST * request) { if (!row[2]){ (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst, sqlsocket); - DEBUG("rlm_sql (%s): Cannot zap stale entry. No username present in entry.", inst->config->xlat_name); + RDEBUG("Cannot zap stale entry. No username present in entry.", inst->config->xlat_name); return RLM_MODULE_FAIL; } if (!row[1]){ (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst, sqlsocket); - DEBUG("rlm_sql (%s): Cannot zap stale entry. No session id in entry.", inst->config->xlat_name); + RDEBUG("Cannot zap stale entry. No session id in entry.", inst->config->xlat_name); return RLM_MODULE_FAIL; } if (row[3]) @@ -1232,19 +1530,34 @@ static int rlm_sql_checksimul(void *instance, REQUEST * request) { check = rad_check_ts(nas_addr, nas_port, row[2], row[1]); - /* - * Failed to check the terminal server for - * duplicate logins: Return an error. - */ - if (check < 0) { - (inst->module->sql_finish_select_query)(sqlsocket, inst->config); - sql_release_socket(inst, sqlsocket); - DEBUG("rlm_sql (%s) rad_check_ts() failed.", - inst->config->xlat_name); - return RLM_MODULE_FAIL; + if (check == 0) { + /* + * Stale record - zap it. + */ + if (inst->config->deletestalesessions == TRUE) { + uint32_t framed_addr = 0; + char proto = 0; + int sess_time = 0; + + if (row[5]) + framed_addr = inet_addr(row[5]); + if (row[7]){ + if (strcmp(row[7], "PPP") == 0) + proto = 'P'; + else if (strcmp(row[7], "SLIP") == 0) + proto = 'S'; + } + if (row[8]) + sess_time = atoi(row[8]); + session_zap(request, nas_addr, nas_port, + row[2], row[1], framed_addr, + proto, sess_time); + } } - - if(check == 1) { + else if (check == 1) { + /* + * User is still logged in. + */ ++request->simul_count; /* @@ -1257,32 +1570,28 @@ static int rlm_sql_checksimul(void *instance, REQUEST * request) { request->simul_mpp = 2; } else { - /* - * Stale record - zap it. - */ - uint32_t framed_addr = 0; - char proto = 'P'; - - if (row[5]) - framed_addr = inet_addr(row[5]); - if (row[7]) - if (strcmp(row[7],"SLIP") == 0) - proto = 'S'; - - session_zap(request, - nas_addr,nas_port,row[2],row[1], - framed_addr, proto); + /* + * Failed to check the terminal server for + * duplicate logins: return an error. + */ + (inst->module->sql_finish_select_query)(sqlsocket, inst->config); + sql_release_socket(inst, sqlsocket); + radlog_request(L_ERR, 0, request, "Failed to check the terminal server for user '%s'.", row[2]); + return RLM_MODULE_FAIL; } } (inst->module->sql_finish_select_query)(sqlsocket, inst->config); sql_release_socket(inst, sqlsocket); - /* The Auth module apparently looks at request->simul_count, not the return value - of this module when deciding to deny a call for too many sessions */ + /* + * The Auth module apparently looks at request->simul_count, + * not the return value of this module when deciding to deny + * a call for too many sessions. + */ return RLM_MODULE_OK; - } +#endif /* * Execute postauth_query after authentication @@ -1293,13 +1602,12 @@ static int rlm_sql_postauth(void *instance, REQUEST *request) { char querystr[MAX_QUERY_LEN]; char sqlusername[MAX_STRING_LEN]; - DEBUG("rlm_sql (%s): Processing sql_postauth", inst->config->xlat_name); - if(sql_set_user(inst, request, sqlusername, NULL) < 0) return RLM_MODULE_FAIL; /* If postauth_query is not defined, we stop here */ - if (inst->config->postauth_query[0] == '\0') + if (!inst->config->postauth_query || + (inst->config->postauth_query[0] == '\0')) return RLM_MODULE_NOOP; /* Expand variables in the query */ @@ -1319,7 +1627,7 @@ static int rlm_sql_postauth(void *instance, REQUEST *request) { if (rlm_sql_query(sqlsocket, inst, querystr)) { radlog(L_ERR, "rlm_sql (%s) in sql_postauth: Database query error - %s", inst->config->xlat_name, - (char *)(inst->module->sql_error)(sqlsocket, inst->config)); + (inst->module->sql_error)(sqlsocket, inst->config)); sql_release_socket(inst, sqlsocket); return RLM_MODULE_FAIL; } @@ -1331,20 +1639,27 @@ static int rlm_sql_postauth(void *instance, REQUEST *request) { /* globally exported name */ module_t rlm_sql = { + RLM_MODULE_INIT, "SQL", RLM_TYPE_THREAD_SAFE, /* type: reserved */ - rlm_sql_init, /* initialization */ rlm_sql_instantiate, /* instantiation */ + rlm_sql_detach, /* detach */ { NULL, /* authentication */ rlm_sql_authorize, /* authorization */ NULL, /* preaccounting */ +#ifdef WITH_ACCOUNTING rlm_sql_accounting, /* accounting */ +#else + NULL, +#endif +#ifdef WITH_SESSION_MGMT rlm_sql_checksimul, /* checksimul */ +#else + NULL, +#endif NULL, /* pre-proxy */ NULL, /* post-proxy */ rlm_sql_postauth /* post-auth */ }, - rlm_sql_detach, /* detach */ - rlm_sql_destroy, /* destroy */ };