#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <freeradius-devel/rad_assert.h>
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
#define DIRLEN 8192
static const char *packet_codes[] = {
"",
"Access-Request",
"Access-Accept",
"Access-Reject",
"Accounting-Request",
"Accounting-Response",
"Accounting-Status",
"Password-Request",
"Password-Accept",
"Password-Reject",
"Accounting-Message",
"Access-Challenge"
};
struct detail_instance {
char *detailfile;
int detailperm;
int dirperm;
char *last_made_directory;
char *header;
int locking;
int log_srcdst;
fr_hash_table_t *ht;
};
static const CONF_PARSER module_config[] = {
{ "detailfile", PW_TYPE_STRING_PTR,
offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
{ "header", PW_TYPE_STRING_PTR,
offsetof(struct detail_instance,header), NULL, "%t" },
{ "detailperm", PW_TYPE_INTEGER,
offsetof(struct detail_instance,detailperm), NULL, "0600" },
{ "dirperm", PW_TYPE_INTEGER,
offsetof(struct detail_instance,dirperm), NULL, "0755" },
{ "locking", PW_TYPE_BOOLEAN,
offsetof(struct detail_instance,locking), NULL, "no" },
{ "log_packet_header", PW_TYPE_BOOLEAN,
offsetof(struct detail_instance,log_srcdst), NULL, "no" },
{ NULL, -1, 0, NULL, NULL }
};
static int detail_detach(void *instance)
{
struct detail_instance *inst = instance;
free((char*) inst->last_made_directory);
if (inst->ht) fr_hash_table_free(inst->ht);
free(inst);
return 0;
}
static uint32_t detail_hash(const void *data)
{
const DICT_ATTR *da = data;
return fr_hash(&(da->attr), sizeof(da->attr));
}
static int detail_cmp(const void *a, const void *b)
{
return ((const DICT_ATTR *)a)->attr - ((const DICT_ATTR *)b)->attr;
}
static int detail_instantiate(CONF_SECTION *conf, void **instance)
{
struct detail_instance *inst;
CONF_SECTION *cs;
inst = rad_malloc(sizeof(*inst));
if (!inst) {
return -1;
}
memset(inst, 0, sizeof(*inst));
if (cf_section_parse(conf, inst, module_config) < 0) {
detail_detach(inst);
return -1;
}
inst->last_made_directory = NULL;
cs = cf_section_sub_find(conf, "suppress");
if (cs) {
CONF_ITEM *ci;
inst->ht = fr_hash_table_create(detail_hash, detail_cmp,
NULL);
for (ci = cf_item_find_next(cs, NULL);
ci != NULL;
ci = cf_item_find_next(cs, ci)) {
const char *attr;
DICT_ATTR *da;
if (!cf_item_is_pair(ci)) continue;
attr = cf_pair_attr(cf_itemtopair(ci));
if (!attr) continue;
da = dict_attrbyname(attr);
if (!da) {
radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
continue;
}
if (!fr_hash_table_insert(inst->ht, da)) {
radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
detail_detach(inst);
return -1;
}
}
}
*instance = inst;
return 0;
}
static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
int compat)
{
int outfd;
FILE *outfp;
char timestamp[256];
char buffer[DIRLEN];
char *p;
struct stat st;
int locked;
int lock_count;
struct timeval tv;
VALUE_PAIR *pair;
struct detail_instance *inst = instance;
if (!packet) {
return RLM_MODULE_NOOP;
}
radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
RDEBUG2("%s expands to %s", inst->detailfile, buffer);
p = strrchr(buffer,'/');
if ((p) && (stat(buffer, &st) < 0)) {
*p = '\0';
if ((inst->last_made_directory == NULL) ||
(strcmp(inst->last_made_directory, buffer) != 0)) {
free((char *) inst->last_made_directory);
inst->last_made_directory = strdup(buffer);
}
if (rad_mkdir(inst->last_made_directory, inst->dirperm) < 0) {
radlog_request(L_ERR, 0, request, "rlm_detail: Failed to create directory %s: %s", inst->last_made_directory, strerror(errno));
return RLM_MODULE_FAIL;
}
*p = '/';
}
locked = 0;
lock_count = 0;
do {
if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
inst->detailperm)) < 0) {
radlog_request(L_ERR, 0, request, "rlm_detail: Couldn't open file %s: %s",
buffer, strerror(errno));
return RLM_MODULE_FAIL;
}
if (inst->locking) {
lseek(outfd, 0L, SEEK_SET);
if (rad_lockfd_nonblock(outfd, 0) < 0) {
close(outfd);
tv.tv_sec = 0;
tv.tv_usec = 25000;
select(0, NULL, NULL, NULL, &tv);
lock_count++;
continue;
}
if (fstat(outfd, &st) != 0) {
radlog_request(L_ERR, 0, request, "rlm_detail: Couldn't stat file %s: %s",
buffer, strerror(errno));
close(outfd);
return RLM_MODULE_FAIL;
}
if (st.st_nlink == 0) {
RDEBUG2("File %s removed by another program, retrying",
buffer);
close(outfd);
lock_count = 0;
continue;
}
RDEBUG2("Acquired filelock, tried %d time(s)",
lock_count + 1);
locked = 1;
}
} while (inst->locking && !locked && lock_count < 80);
if (inst->locking && !locked) {
close(outfd);
radlog_request(L_ERR, 0, request, "rlm_detail: Failed to acquire filelock for %s, giving up",
buffer);
return RLM_MODULE_FAIL;
}
if ((outfp = fdopen(outfd, "a")) == NULL) {
radlog_request(L_ERR, 0, request, "rlm_detail: Couldn't open file %s: %s",
buffer, strerror(errno));
if (inst->locking) {
lseek(outfd, 0L, SEEK_SET);
rad_unlockfd(outfd, 0);
RDEBUG2("Released filelock");
}
close(outfd);
return RLM_MODULE_FAIL;
}
fseek(outfp, 0L, SEEK_END);
radius_xlat(timestamp, sizeof(timestamp), inst->header, request, NULL);
fprintf(outfp, "%s\n", timestamp);
if (!compat) {
if ((packet->code > 0) &&
(packet->code <= PW_ACCESS_CHALLENGE)) {
fprintf(outfp, "\tPacket-Type = %s\n",
packet_codes[packet->code]);
} else {
fprintf(outfp, "\tPacket-Type = %d\n", packet->code);
}
}
if (inst->log_srcdst) {
VALUE_PAIR src_vp, dst_vp;
memset(&src_vp, 0, sizeof(src_vp));
memset(&dst_vp, 0, sizeof(dst_vp));
src_vp.operator = dst_vp.operator = T_OP_EQ;
switch (packet->src_ipaddr.af) {
case AF_INET:
src_vp.type = PW_TYPE_IPADDR;
src_vp.attribute = PW_PACKET_SRC_IP_ADDRESS;
src_vp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
dst_vp.type = PW_TYPE_IPADDR;
dst_vp.attribute = PW_PACKET_DST_IP_ADDRESS;
dst_vp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
break;
case AF_INET6:
src_vp.type = PW_TYPE_IPV6ADDR;
src_vp.attribute = PW_PACKET_SRC_IPV6_ADDRESS;
memcpy(src_vp.vp_strvalue,
&packet->src_ipaddr.ipaddr.ip6addr,
sizeof(packet->src_ipaddr.ipaddr.ip6addr));
dst_vp.type = PW_TYPE_IPV6ADDR;
dst_vp.attribute = PW_PACKET_DST_IPV6_ADDRESS;
memcpy(dst_vp.vp_strvalue,
&packet->dst_ipaddr.ipaddr.ip6addr,
sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
break;
default:
break;
}
fputs("\t", outfp);
vp_print(outfp, &src_vp);
fputs("\n", outfp);
fputs("\t", outfp);
vp_print(outfp, &dst_vp);
fputs("\n", outfp);
src_vp.attribute = PW_PACKET_SRC_PORT;
src_vp.type = PW_TYPE_INTEGER;
src_vp.vp_integer = packet->src_port;
dst_vp.attribute = PW_PACKET_DST_PORT;
dst_vp.type = PW_TYPE_INTEGER;
dst_vp.vp_integer = packet->dst_port;
fputs("\t", outfp);
vp_print(outfp, &src_vp);
fputs("\n", outfp);
fputs("\t", outfp);
vp_print(outfp, &dst_vp);
fputs("\n", outfp);
}
for (pair = packet->vps; pair != NULL; pair = pair->next) {
DICT_ATTR da;
da.attr = pair->attribute;
if (inst->ht &&
fr_hash_table_finddata(inst->ht, &da)) continue;
if (compat && (pair->attribute == PW_USER_PASSWORD)) continue;
fputs("\t", outfp);
vp_print(outfp, pair);
fputs("\n", outfp);
}
if (compat) {
if (request->proxy) {
char proxy_buffer[128];
inet_ntop(request->proxy->dst_ipaddr.af,
&request->proxy->dst_ipaddr.ipaddr,
proxy_buffer, sizeof(proxy_buffer));
fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
proxy_buffer);
RDEBUG("Freeradius-Proxied-To = %s",
proxy_buffer);
}
fprintf(outfp, "\tTimestamp = %ld\n",
(unsigned long) request->timestamp);
fputs("\tRequest-Authenticator = Verified\n", outfp);
}
fputs("\n", outfp);
if (inst->locking) {
fflush(outfp);
lseek(outfd, 0L, SEEK_SET);
rad_unlockfd(outfd, 0);
RDEBUG2("Released filelock");
}
fclose(outfp);
return RLM_MODULE_OK;
}
static int detail_accounting(void *instance, REQUEST *request)
{
if (request->listener->type == RAD_LISTEN_DETAIL) {
RDEBUG("Suppressing writes to detail file as the request was just read from a detail file.");
return RLM_MODULE_NOOP;
}
return do_detail(instance,request,request->packet, TRUE);
}
static int detail_authorize(void *instance, REQUEST *request)
{
return do_detail(instance,request,request->packet, FALSE);
}
static int detail_postauth(void *instance, REQUEST *request)
{
return do_detail(instance,request,request->reply, FALSE);
}
static int detail_pre_proxy(void *instance, REQUEST *request)
{
if (request->proxy &&
request->proxy->vps) {
return do_detail(instance,request,request->proxy, FALSE);
}
return RLM_MODULE_NOOP;
}
static int detail_post_proxy(void *instance, REQUEST *request)
{
if (request->proxy_reply &&
request->proxy_reply->vps) {
return do_detail(instance,request,request->proxy_reply, FALSE);
}
if (!request->proxy_reply) {
return detail_accounting(instance, request);
}
return RLM_MODULE_NOOP;
}
module_t rlm_detail = {
RLM_MODULE_INIT,
"detail",
RLM_TYPE_THREAD_UNSAFE | RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
detail_instantiate,
detail_detach,
{
NULL,
detail_authorize,
NULL,
detail_accounting,
NULL,
detail_pre_proxy,
detail_post_proxy,
detail_postauth
},
};