#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <ctype.h>
#include "rlm_sql.h"
#ifdef HAVE_PTHREAD_H
#endif
static int connect_single_socket(SQLSOCK *sqlsocket, SQL_INST *inst)
{
int rcode;
radlog(L_INFO, "rlm_sql (%s): Attempting to connect %s #%d",
inst->config->xlat_name, inst->module->name, sqlsocket->id);
rcode = (inst->module->sql_init_socket)(sqlsocket, inst->config);
if (rcode == 0) {
radlog(L_INFO, "rlm_sql (%s): Connected new DB handle, #%d",
inst->config->xlat_name, sqlsocket->id);
sqlsocket->state = sockconnected;
if (inst->config->lifetime) time(&sqlsocket->connected);
sqlsocket->queries = 0;
return(0);
}
radlog(L_CONS | L_ERR, "rlm_sql (%s): Failed to connect DB handle #%d", inst->config->xlat_name, sqlsocket->id);
inst->connect_after = time(NULL) + inst->config->connect_failure_retry_delay;
sqlsocket->state = sockunconnected;
return(-1);
}
int sql_init_socketpool(SQL_INST * inst)
{
int i, rcode;
int success = 0;
SQLSOCK *sqlsocket;
inst->connect_after = 0;
inst->sqlpool = NULL;
for (i = 0; i < inst->config->num_sql_socks; i++) {
radlog(L_DBG, "rlm_sql (%s): starting %d",
inst->config->xlat_name, i);
sqlsocket = rad_malloc(sizeof(*sqlsocket));
if (sqlsocket == NULL) {
return -1;
}
memset(sqlsocket, 0, sizeof(*sqlsocket));
sqlsocket->conn = NULL;
sqlsocket->id = i;
sqlsocket->state = sockunconnected;
#ifdef HAVE_PTHREAD_H
rcode = pthread_mutex_init(&sqlsocket->mutex,NULL);
if (rcode != 0) {
free(sqlsocket);
radlog(L_ERR, "rlm_sql: Failed to init lock: %s",
strerror(errno));
return 0;
}
#endif
if (time(NULL) > inst->connect_after) {
if (connect_single_socket(sqlsocket, inst) == 0) {
success = 1;
}
}
sqlsocket->next = inst->sqlpool;
inst->sqlpool = sqlsocket;
}
inst->last_used = NULL;
if (!success) {
radlog(L_DBG, "rlm_sql (%s): Failed to connect to any SQL server.",
inst->config->xlat_name);
}
return 1;
}
void sql_poolfree(SQL_INST * inst)
{
SQLSOCK *cur;
SQLSOCK *next;
for (cur = inst->sqlpool; cur; cur = next) {
next = cur->next;
sql_close_socket(inst, cur);
}
inst->sqlpool = NULL;
}
int sql_close_socket(SQL_INST *inst, SQLSOCK * sqlsocket)
{
radlog(L_INFO, "rlm_sql (%s): Closing sqlsocket %d",
inst->config->xlat_name, sqlsocket->id);
if (sqlsocket->state == sockconnected) {
(inst->module->sql_close)(sqlsocket, inst->config);
}
if (inst->module->sql_destroy_socket) {
(inst->module->sql_destroy_socket)(sqlsocket, inst->config);
}
#ifdef HAVE_PTHREAD_H
pthread_mutex_destroy(&sqlsocket->mutex);
#endif
free(sqlsocket);
return 1;
}
static time_t last_logged_failure = 0;
SQLSOCK * sql_get_socket(SQL_INST * inst)
{
SQLSOCK *cur, *start;
int tried_to_connect = 0;
int unconnected = 0;
time_t now = time(NULL);
start = inst->last_used;
if (!start) start = inst->sqlpool;
cur = start;
while (cur) {
#ifdef HAVE_PTHREAD_H
if (pthread_mutex_trylock(&cur->mutex) != 0) {
goto next;
}
#endif
if (inst->config->lifetime && (cur->state == sockconnected) &&
((cur->connected + inst->config->lifetime) < now)) {
DEBUG2("Closing socket %d as its lifetime has been exceeded", cur->id);
(inst->module->sql_close)(cur, inst->config);
cur->state = sockunconnected;
goto reconnect;
}
if (inst->config->max_queries && (cur->state == sockconnected) &&
(cur->queries >= inst->config->max_queries)) {
DEBUG2("Closing socket %d as its max_queries has been exceeded", cur->id);
(inst->module->sql_close)(cur, inst->config);
cur->state = sockunconnected;
goto reconnect;
}
if ((cur->state == sockunconnected) && (now > inst->connect_after)) {
reconnect:
radlog(L_INFO, "rlm_sql (%s): Trying to (re)connect unconnected handle %d..", inst->config->xlat_name, cur->id);
tried_to_connect++;
connect_single_socket(cur, inst);
}
if (cur->state == sockunconnected) {
DEBUG("rlm_sql (%s): Ignoring unconnected handle %d..", inst->config->xlat_name, cur->id);
unconnected++;
#ifdef HAVE_PTHREAD_H
pthread_mutex_unlock(&cur->mutex);
#endif
goto next;
}
DEBUG("rlm_sql (%s): Reserving sql socket id: %d", inst->config->xlat_name, cur->id);
if (unconnected != 0 || tried_to_connect != 0) {
DEBUG("rlm_sql (%s): got socket %d after skipping %d unconnected handles, tried to reconnect %d though", inst->config->xlat_name, cur->id, unconnected, tried_to_connect);
}
inst->last_used = cur->next;
cur->queries++;
return cur;
next:
cur = cur->next;
if (!cur) {
cur = inst->sqlpool;
}
if (cur == start) {
break;
}
}
if (now <= last_logged_failure) return NULL;
last_logged_failure = now;
radlog(L_INFO, "rlm_sql (%s): There are no DB handles to use! skipped %d, tried to connect %d", inst->config->xlat_name, unconnected, tried_to_connect);
return NULL;
}
int sql_release_socket(SQL_INST * inst, SQLSOCK * sqlsocket)
{
#ifdef HAVE_PTHREAD_H
pthread_mutex_unlock(&sqlsocket->mutex);
#endif
radlog(L_DBG, "rlm_sql (%s): Released sql socket id: %d",
inst->config->xlat_name, sqlsocket->id);
return 0;
}
int sql_userparse(VALUE_PAIR ** first_pair, SQL_ROW row)
{
VALUE_PAIR *pair;
const char *ptr, *value;
char buf[MAX_STRING_LEN];
char do_xlat = 0;
FR_TOKEN token, operator = T_EOL;
if (row[2] == NULL || row[2][0] == '\0') {
radlog(L_ERR, "rlm_sql: The 'Attribute' field is empty or NULL, skipping the entire row.");
return -1;
}
if (row[4] != NULL && row[4][0] != '\0') {
ptr = row[4];
operator = gettoken(&ptr, buf, sizeof(buf));
if ((operator < T_OP_ADD) ||
(operator > T_OP_CMP_EQ)) {
radlog(L_ERR, "rlm_sql: Invalid operator \"%s\" for attribute %s", row[4], row[2]);
return -1;
}
} else {
operator = T_OP_CMP_EQ;
radlog(L_ERR, "rlm_sql: The 'op' field for attribute '%s = %s' is NULL, or non-existent.", row[2], row[3]);
radlog(L_ERR, "rlm_sql: You MUST FIX THIS if you want the configuration to behave as you expect.");
}
value = row[3];
if (row[3] != NULL &&
((row[3][0] == '\'') || (row[3][0] == '`') || (row[3][0] == '"')) &&
(row[3][0] == row[3][strlen(row[3])-1])) {
token = gettoken(&value, buf, sizeof(buf));
switch (token) {
case T_SINGLE_QUOTED_STRING:
case T_DOUBLE_QUOTED_STRING:
value = buf;
break;
case T_BACK_QUOTED_STRING:
value = NULL;
do_xlat = 1;
break;
default:
value = row[3];
break;
}
}
pair = pairmake(row[2], value, operator);
if (pair == NULL) {
radlog(L_ERR, "rlm_sql: Failed to create the pair: %s", fr_strerror());
return -1;
}
if (do_xlat) {
pair->flags.do_xlat = 1;
strlcpy(pair->vp_strvalue, buf, sizeof(pair->vp_strvalue));
pair->length = 0;
}
pairadd(first_pair, pair);
return 0;
}
int rlm_sql_fetch_row(SQLSOCK *sqlsocket, SQL_INST *inst)
{
int ret;
if (sqlsocket->conn) {
ret = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
} else {
ret = SQL_DOWN;
}
if (ret == SQL_DOWN) {
if (sqlsocket->conn) {
(inst->module->sql_close)(sqlsocket, inst->config);
}
if (connect_single_socket(sqlsocket, inst) < 0) {
radlog(L_ERR, "rlm_sql (%s): reconnect failed, database down?", inst->config->xlat_name);
return -1;
}
ret = (inst->module->sql_fetch_row)(sqlsocket, inst->config);
if (ret) {
radlog(L_ERR, "rlm_sql (%s): failed after re-connect",
inst->config->xlat_name);
return -1;
}
}
return ret;
}
int rlm_sql_query(SQLSOCK *sqlsocket, SQL_INST *inst, char *query)
{
int ret;
if (!query || !*query) {
return -1;
}
if (sqlsocket->conn) {
ret = (inst->module->sql_query)(sqlsocket, inst->config, query);
} else {
ret = SQL_DOWN;
}
if (ret == SQL_DOWN) {
if (sqlsocket->state == sockconnected) {
(inst->module->sql_close)(sqlsocket, inst->config);
}
if (connect_single_socket(sqlsocket, inst) < 0) {
radlog(L_ERR, "rlm_sql (%s): reconnect failed, database down?", inst->config->xlat_name);
return -1;
}
ret = (inst->module->sql_query)(sqlsocket, inst->config, query);
if (ret) {
radlog(L_ERR, "rlm_sql (%s): failed after re-connect",
inst->config->xlat_name);
return -1;
}
}
return ret;
}
int rlm_sql_select_query(SQLSOCK *sqlsocket, SQL_INST *inst, char *query)
{
int ret;
if (!query || !*query) {
return -1;
}
if (sqlsocket->conn) {
ret = (inst->module->sql_select_query)(sqlsocket, inst->config,
query);
} else {
ret = SQL_DOWN;
}
if (ret == SQL_DOWN) {
if (sqlsocket->state == sockconnected) {
(inst->module->sql_close)(sqlsocket, inst->config);
}
if (connect_single_socket(sqlsocket, inst) < 0) {
radlog(L_ERR, "rlm_sql (%s): reconnect failed, database down?", inst->config->xlat_name);
return -1;
}
ret = (inst->module->sql_select_query)(sqlsocket, inst->config, query);
if (ret) {
radlog(L_ERR, "rlm_sql (%s): failed after re-connect",
inst->config->xlat_name);
return -1;
}
}
return ret;
}
int sql_getvpdata(SQL_INST * inst, SQLSOCK * sqlsocket, VALUE_PAIR **pair, char *query)
{
SQL_ROW row;
int rows = 0;
if (rlm_sql_select_query(sqlsocket, inst, query)) {
radlog(L_ERR, "rlm_sql_getvpdata: database query error");
return -1;
}
while (rlm_sql_fetch_row(sqlsocket, inst)==0) {
row = sqlsocket->row;
if (!row)
break;
if (sql_userparse(pair, row) != 0) {
radlog(L_ERR | L_CONS, "rlm_sql (%s): Error getting data from database", inst->config->xlat_name);
(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
return -1;
}
rows++;
}
(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
return rows;
}
void query_log(REQUEST *request, SQL_INST *inst, char *querystr)
{
FILE *sqlfile = NULL;
if (inst->config->sqltrace) {
char buffer[8192];
if (!radius_xlat(buffer, sizeof(buffer),
inst->config->tracefile, request, NULL)) {
radlog(L_ERR, "rlm_sql (%s): xlat failed.",
inst->config->xlat_name);
return;
}
if ((sqlfile = fopen(buffer, "a")) == (FILE *) NULL) {
radlog(L_ERR, "rlm_sql (%s): Couldn't open file %s",
inst->config->xlat_name,
buffer);
} else {
int fd = fileno(sqlfile);
rad_lockfd(fd, MAX_QUERY_LEN);
fputs(querystr, sqlfile);
fputs(";\n", sqlfile);
fclose(sqlfile);
}
}
}