#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#ifndef LOG_INFO
#define LOG_INFO (0)
#endif
#endif
typedef struct rlm_linelog_t {
CONF_SECTION *cs;
char *filename;
int permissions;
char *line;
char *reference;
} rlm_linelog_t;
static const CONF_PARSER module_config[] = {
{ "filename", PW_TYPE_STRING_PTR,
offsetof(rlm_linelog_t,filename), NULL, NULL},
{ "permissions", PW_TYPE_INTEGER,
offsetof(rlm_linelog_t,permissions), NULL, "0600"},
{ "format", PW_TYPE_STRING_PTR,
offsetof(rlm_linelog_t,line), NULL, NULL},
{ "reference", PW_TYPE_STRING_PTR,
offsetof(rlm_linelog_t,reference), NULL, NULL},
{ NULL, -1, 0, NULL, NULL }
};
static int linelog_detach(void *instance)
{
rlm_linelog_t *inst = instance;
free(inst);
return 0;
}
static int linelog_instantiate(CONF_SECTION *conf, void **instance)
{
rlm_linelog_t *inst;
inst = rad_malloc(sizeof(*inst));
memset(inst, 0, sizeof(*inst));
if (cf_section_parse(conf, inst, module_config) < 0) {
linelog_detach(inst);
return -1;
}
if (!inst->filename) {
radlog(L_ERR, "rlm_linelog: Must specify an output filename");
linelog_detach(inst);
return -1;
}
#ifndef HAVE_SYSLOG_H
if (strcmp(inst->filename, "syslog") == 0) {
radlog(L_ERR, "rlm_linelog: Syslog output is not supported");
linelog_detach(inst);
return -1;
}
#endif
if (!inst->line) {
radlog(L_ERR, "rlm_linelog: Must specify a log format");
linelog_detach(inst);
return -1;
}
inst->cs = conf;
*instance = inst;
return 0;
}
static size_t linelog_escape_func(char *out, size_t outlen, const char *in)
{
int len = 0;
if (outlen == 0) return 0;
if (outlen == 1) {
*out = '\0';
return 0;
}
while (in[0]) {
if (in[0] >= ' ') {
if (in[0] == '\\') {
if (outlen <= 2) break;
outlen--;
*out++ = '\\';
len++;
}
outlen--;
if (outlen == 1) break;
*out++ = *in++;
len++;
continue;
}
switch (in[0]) {
case '\n':
if (outlen <= 2) break;
*out++ = '\\';
*out++ = 'n';
in++;
len += 2;
break;
case '\r':
if (outlen <= 2) break;
*out++ = '\\';
*out++ = 'r';
in++;
len += 2;
break;
default:
if (outlen <= 4) break;
snprintf(out, outlen, "\\%03o", *in);
in++;
out += 4;
outlen -= 4;
len += 4;
break;
}
}
*out = '\0';
return len;
}
static int do_linelog(void *instance, REQUEST *request)
{
int fd = -1;
char buffer[4096];
char *p;
char line[1024];
rlm_linelog_t *inst = (rlm_linelog_t*) instance;
const char *value = inst->line;
if (inst->reference) {
CONF_ITEM *ci;
CONF_PAIR *cp;
radius_xlat(line + 1, sizeof(line) - 2, inst->reference,
request, linelog_escape_func);
line[0] = '.';
if (line[1] == '.') goto do_log;
ci = cf_reference_item(NULL, inst->cs, line);
if (!ci) {
RDEBUG2("No such entry \"%s\"", line);
return RLM_MODULE_NOOP;
}
if (!cf_item_is_pair(ci)) {
RDEBUG2("Entry \"%s\" is not a variable assignment ", line);
goto do_log;
}
cp = cf_itemtopair(ci);
value = cf_pair_value(cp);
if (!value) {
RDEBUG2("Entry \"%s\" has no value", line);
goto do_log;
}
if (!*value) return RLM_MODULE_OK;
}
do_log:
if (strcmp(inst->filename, "syslog") != 0) {
radius_xlat(buffer, sizeof(buffer), inst->filename, request,
NULL);
p = strrchr(buffer,'/');
if (p) {
*p = '\0';
if (rad_mkdir(buffer, 0700) < 0) {
radlog_request(L_ERR, 0, request, "rlm_linelog: Failed to create directory %s: %s", buffer, strerror(errno));
return RLM_MODULE_FAIL;
}
*p = '/';
}
fd = open(buffer, O_WRONLY | O_APPEND | O_CREAT, inst->permissions);
if (fd == -1) {
radlog(L_ERR, "rlm_linelog: Failed to open %s: %s",
buffer, strerror(errno));
return RLM_MODULE_FAIL;
}
}
radius_xlat(line, sizeof(line) - 1, value, request,
linelog_escape_func);
if (fd >= 0) {
strcat(line, "\n");
write(fd, line, strlen(line));
close(fd);
#ifdef HAVE_SYSLOG_H
} else {
syslog(LOG_INFO, "%s", line);
#endif
}
return RLM_MODULE_OK;
}
module_t rlm_linelog = {
RLM_MODULE_INIT,
"linelog",
RLM_TYPE_CHECK_CONFIG_SAFE,
linelog_instantiate,
linelog_detach,
{
do_linelog,
do_linelog,
do_linelog,
do_linelog,
NULL,
do_linelog,
do_linelog,
do_linelog
#ifdef WITH_COA
, do_linelog,
do_linelog
#endif
},
};