#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <freeradius-devel/rad_assert.h>
#include <fcntl.h>
#include <sys/stat.h>
static int sql_log_instantiate(CONF_SECTION *conf, void **instance);
static int sql_log_detach(void *instance);
static int sql_log_accounting(void *instance, REQUEST *request);
static int sql_log_postauth(void *instance, REQUEST *request);
#define MAX_QUERY_LEN 4096
typedef struct rlm_sql_log_t {
char *path;
char *postauth_query;
char *sql_user_name;
int utf8;
char *allowed_chars;
CONF_SECTION *conf_section;
} rlm_sql_log_t;
static const CONF_PARSER module_config[] = {
{"path", PW_TYPE_STRING_PTR,
offsetof(rlm_sql_log_t,path), NULL, "${radacctdir}/sql-relay"},
{"Post-Auth", PW_TYPE_STRING_PTR,
offsetof(rlm_sql_log_t,postauth_query), NULL, ""},
{"sql_user_name", PW_TYPE_STRING_PTR,
offsetof(rlm_sql_log_t,sql_user_name), NULL, ""},
{"utf8", PW_TYPE_BOOLEAN,
offsetof(rlm_sql_log_t,utf8), NULL, "no"},
{"safe-characters", PW_TYPE_STRING_PTR,
offsetof(rlm_sql_log_t,allowed_chars), NULL,
"@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
{ NULL, -1, 0, NULL, NULL }
};
static char *allowed_chars = NULL;
static int sql_log_instantiate(CONF_SECTION *conf, void **instance)
{
rlm_sql_log_t *inst;
inst = calloc(1, sizeof(rlm_sql_log_t));
if (inst == NULL) {
radlog(L_ERR, "rlm_sql_log: Not enough memory");
return -1;
}
if (cf_section_parse(conf, inst, module_config) < 0) {
radlog(L_ERR, "rlm_sql_log: Unable to parse parameters");
sql_log_detach(inst);
return -1;
}
inst->conf_section = conf;
allowed_chars = inst->allowed_chars;
*instance = inst;
return 0;
}
static int sql_log_detach(void *instance)
{
int i;
char **p;
rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
for (i = 0; module_config[i].name != NULL; i++) {
if (module_config[i].type != PW_TYPE_STRING_PTR) {
continue;
}
p = (char **) (((char *)inst) + module_config[i].offset);
if (!*p) {
continue;
}
free(*p);
*p = NULL;
}
allowed_chars = NULL;
free(inst);
return 0;
}
static size_t sql_escape_func(char *out, size_t outlen, const char *in)
{
int len = 0;
while (in[0]) {
if ((in[0] < 32) ||
strchr(allowed_chars, *in) == NULL) {
if (outlen <= 3) {
break;
}
snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
in++;
out += 3;
outlen -= 3;
len += 3;
continue;
}
if (outlen <= 1) {
break;
}
*out = *in;
out++;
in++;
outlen--;
len++;
}
*out = '\0';
return len;
}
static size_t sql_utf8_escape_func(char *out, size_t outlen, const char *in)
{
int len = 0;
int utf8 = 0;
while (in[0]) {
utf8 = fr_utf8_char((uint8_t *)in);
if (utf8) {
if (outlen <= utf8) {
break;
}
while (utf8-- > 0) {
*out = *in;
out++;
in++;
outlen--;
len++;
}
continue;
}
if ((in[0] < 32) ||
strchr(allowed_chars, *in) == NULL) {
if (outlen <= 3) {
break;
}
snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
in++;
out += 3;
outlen -= 3;
len += 3;
continue;
}
if (outlen <= 1) {
break;
}
*out = *in;
out++;
in++;
outlen--;
len++;
}
*out = '\0';
return len;
}
static int sql_set_user(rlm_sql_log_t *inst, REQUEST *request, char *sqlusername, const char *username)
{
VALUE_PAIR *vp=NULL;
char tmpuser[MAX_STRING_LEN];
tmpuser[0] = '\0';
sqlusername[0] = '\0';
rad_assert(request != NULL);
rad_assert(request->packet != NULL);
pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
if (username != NULL) {
strlcpy(tmpuser, username, MAX_STRING_LEN);
} else if (inst->sql_user_name[0] != '\0') {
radius_xlat(tmpuser, sizeof(tmpuser), inst->sql_user_name,
request, NULL);
} else {
return 0;
}
if (tmpuser[0] != '\0') {
strlcpy(sqlusername, tmpuser, sizeof(tmpuser));
RDEBUG2("sql_set_user escaped user --> '%s'", sqlusername);
vp = pairmake("SQL-User-Name", sqlusername, 0);
if (vp == NULL) {
radlog(L_ERR, "%s", fr_strerror());
return -1;
}
pairadd(&request->packet->vps, vp);
return 0;
}
return -1;
}
static int sql_xlat_query(rlm_sql_log_t *inst, REQUEST *request, const char *query, char *xlat_query, size_t len)
{
char sqlusername[MAX_STRING_LEN];
if (query[0] == '\0')
return RLM_MODULE_NOOP;
if (sql_set_user(inst, request, sqlusername, NULL) <0) {
radlog_request(L_ERR, 0, request,
"Couldn't add SQL-User-Name attribute");
return RLM_MODULE_FAIL;
}
xlat_query[0] = '\0';
radius_xlat(xlat_query, len, query, request,
inst->utf8 ? sql_utf8_escape_func : sql_escape_func);
if (xlat_query[0] == '\0') {
radlog_request(L_ERR, 0, request, "Couldn't xlat the query %s",
query);
return RLM_MODULE_FAIL;
}
return RLM_MODULE_OK;
}
static int setlock(int fd)
{
#ifdef F_WRLCK
struct flock fl;
memset(&fl, 0, sizeof(fl));
fl.l_start = 0;
fl.l_len = 0;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
return fcntl(fd, F_SETLKW, &fl);
#else
return -1;
#endif
}
static int sql_log_write(rlm_sql_log_t *inst, REQUEST *request, const char *line)
{
int fd;
FILE *fp;
int locked = 0;
struct stat st;
char path[MAX_STRING_LEN];
path[0] = '\0';
radius_xlat(path, sizeof(path), inst->path, request, NULL);
if (path[0] == '\0') {
return RLM_MODULE_FAIL;
}
while (!locked) {
if ((fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) {
radlog_request(L_ERR, 0, request, "Couldn't open file %s: %s",
path, strerror(errno));
return RLM_MODULE_FAIL;
}
if (setlock(fd) != 0) {
radlog_request(L_ERR, 0, request, "Couldn't lock file %s: %s",
path, strerror(errno));
close(fd);
return RLM_MODULE_FAIL;
}
if (fstat(fd, &st) != 0) {
radlog_request(L_ERR, 0, request, "Couldn't stat file %s: %s",
path, strerror(errno));
close(fd);
return RLM_MODULE_FAIL;
}
if (st.st_nlink == 0) {
RDEBUG("File %s removed by another program, retrying",
path);
close(fd);
continue;
}
locked = 1;
}
if ((fp = fdopen(fd, "a")) == NULL) {
radlog_request(L_ERR, 0, request, "Couldn't associate a stream with file %s: %s",
path, strerror(errno));
close(fd);
return RLM_MODULE_FAIL;
}
fputs(line, fp);
putc('\n', fp);
fclose(fp);
return RLM_MODULE_OK;
}
static int sql_log_accounting(void *instance, REQUEST *request)
{
int ret;
char querystr[MAX_QUERY_LEN];
const char *cfquery;
rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
VALUE_PAIR *pair;
DICT_VALUE *dval;
CONF_PAIR *cp;
rad_assert(request != NULL);
rad_assert(request->packet != NULL);
RDEBUG("Processing sql_log_accounting");
if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) == NULL) {
radlog_request(L_ERR, 0, request, "Packet has no account status type");
return RLM_MODULE_INVALID;
}
if ((dval = dict_valbyattr(PW_ACCT_STATUS_TYPE, pair->vp_integer)) == NULL) {
radlog_request(L_ERR, 0, request, "Unsupported Acct-Status-Type = %d",
pair->vp_integer);
return RLM_MODULE_NOOP;
}
if ((cp = cf_pair_find(inst->conf_section, dval->name)) == NULL) {
RDEBUG("Couldn't find an entry %s in the config section",
dval->name);
return RLM_MODULE_NOOP;
}
cfquery = cf_pair_value(cp);
ret = sql_xlat_query(inst, request, cfquery, querystr, sizeof(querystr));
if (ret != RLM_MODULE_OK)
return ret;
return sql_log_write(inst, request, querystr);
}
static int sql_log_postauth(void *instance, REQUEST *request)
{
int ret;
char querystr[MAX_QUERY_LEN];
rlm_sql_log_t *inst = (rlm_sql_log_t *)instance;
rad_assert(request != NULL);
RDEBUG("Processing sql_log_postauth");
ret = sql_xlat_query(inst, request, inst->postauth_query,
querystr, sizeof(querystr));
if (ret != RLM_MODULE_OK)
return ret;
return sql_log_write(inst, request, querystr);
}
module_t rlm_sql_log = {
RLM_MODULE_INIT,
"sql_log",
RLM_TYPE_THREAD_UNSAFE | RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
sql_log_instantiate,
sql_log_detach,
{
NULL,
NULL,
NULL,
sql_log_accounting,
NULL,
NULL,
NULL,
sql_log_postauth
},
};