#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <freeradius-devel/detail.h>
#include <freeradius-devel/rad_assert.h>
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_GLOB_H
#include <glob.h>
#endif
#include <fcntl.h>
#ifdef WITH_DETAIL
#define USEC (1000000)
static FR_NAME_NUMBER state_names[] = {
{ "unopened", STATE_UNOPENED },
{ "unlocked", STATE_UNLOCKED },
{ "header", STATE_HEADER },
{ "reading", STATE_READING },
{ "queued", STATE_QUEUED },
{ "running", STATE_RUNNING },
{ "no-reply", STATE_NO_REPLY },
{ "replied", STATE_REPLIED },
{ NULL, 0 }
};
int detail_send(rad_listen_t *listener, REQUEST *request)
{
int rtt;
struct timeval now;
listen_detail_t *data = listener->data;
rad_assert(request->listener == listener);
rad_assert(listener->send == detail_send);
if (request->reply->code == 0) {
data->delay_time = data->retry_interval * USEC;
data->signal = 1;
data->state = STATE_NO_REPLY;
RDEBUG("Detail - No response configured for request %d. Will retry in %d seconds",
request->number, data->retry_interval);
radius_signal_self(RADIUS_SIGNAL_SELF_DETAIL);
return 0;
}
gettimeofday(&now, NULL);
now.tv_sec -= 1;
if (timercmp(&data->last_packet, &now, <)) {
data->has_rtt = FALSE;
}
now.tv_sec += 1;
rtt = now.tv_sec - request->received.tv_sec;
rtt *= USEC;
rtt += now.tv_usec;
rtt -= request->received.tv_usec;
if (!data->has_rtt) {
data->has_rtt = TRUE;
data->srtt = rtt;
data->rttvar = rtt / 2;
} else {
data->rttvar -= data->rttvar >> 2;
data->rttvar += (data->srtt - rtt);
data->srtt -= data->srtt >> 3;
data->srtt += rtt >> 3;
}
data->delay_time = (data->srtt * (100 - data->load_factor)) / (data->load_factor);
if (data->delay_time > (USEC / 4)) data->delay_time= USEC / 4;
RDEBUG3("Received response for request %d. Will read the next packet in %d seconds",
request->number, data->delay_time / USEC);
data->last_packet = now;
data->signal = 1;
data->state = STATE_REPLIED;
radius_signal_self(RADIUS_SIGNAL_SELF_DETAIL);
return 0;
}
static int detail_open(rad_listen_t *this)
{
struct stat st;
listen_detail_t *data = this->data;
char *filename = data->filename;
rad_assert(data->state == STATE_UNOPENED);
data->delay_time = USEC;
this->fd = open(data->filename_work, O_RDWR);
if (this->fd < 0) {
DEBUG2("Polling for detail file %s", filename);
if (stat(filename, &st) < 0) {
#ifdef HAVE_GLOB_H
unsigned int i;
int found;
time_t chtime;
glob_t files;
memset(&files, 0, sizeof(files));
if (glob(filename, 0, NULL, &files) != 0) {
return 0;
}
chtime = 0;
found = -1;
for (i = 0; i < files.gl_pathc; i++) {
if (stat(files.gl_pathv[i], &st) < 0) continue;
if ((i == 0) ||
(st.st_ctime < chtime)) {
chtime = st.st_ctime;
found = i;
}
}
if (found < 0) {
globfree(&files);
return 0;
}
filename = strdup(files.gl_pathv[found]);
globfree(&files);
#else
return 0;
#endif
}
this->fd = open(filename, O_RDWR);
if (this->fd < 0) {
radlog(L_ERR, "Detail - Failed to open %s: %s",
filename, strerror(errno));
if (filename != data->filename) free(filename);
return 0;
}
DEBUG("Detail - Renaming %s -> %s", filename, data->filename_work);
if (rename(filename, data->filename_work) < 0) {
if (filename != data->filename) free(filename);
close(this->fd);
this->fd = -1;
return 0;
}
if (filename != data->filename) free(filename);
}
rad_assert(data->vps == NULL);
rad_assert(data->fp == NULL);
data->state = STATE_UNLOCKED;
data->client_ip.af = AF_UNSPEC;
data->timestamp = 0;
data->offset = 0;
data->packets = 0;
data->tries = 0;
return 1;
}
int detail_recv(rad_listen_t *listener,
RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
{
char key[256], op[8], value[1024];
VALUE_PAIR *vp, **tail;
RADIUS_PACKET *packet;
char buffer[2048];
listen_detail_t *data = listener->data;
if (data->signal) return 0;
switch (data->state) {
case STATE_UNOPENED:
open_file:
rad_assert(listener->fd < 0);
if (!detail_open(listener)) return 0;
rad_assert(data->state == STATE_UNLOCKED);
rad_assert(listener->fd >= 0);
case STATE_UNLOCKED:
if (rad_lockfd_nonblock(listener->fd, 0) < 0) {
close(listener->fd);
listener->fd = -1;
data->state = STATE_UNOPENED;
return 0;
}
data->fp = fdopen(listener->fd, "r");
if (!data->fp) {
radlog(L_ERR, "FATAL: Failed to re-open detail file %s: %s",
data->filename, strerror(errno));
exit(1);
}
data->state = STATE_HEADER;
data->delay_time = USEC;
data->vps = NULL;
case STATE_HEADER:
do_header:
data->tries = 0;
if (!data->fp) {
data->state = STATE_UNOPENED;
goto open_file;
}
{
struct stat buf;
fstat(listener->fd, &buf);
if (((off_t) ftell(data->fp)) == buf.st_size) {
goto cleanup;
}
}
if (feof(data->fp)) {
cleanup:
DEBUG("Detail - unlinking %s",
data->filename_work);
unlink(data->filename_work);
if (data->fp) fclose(data->fp);
data->fp = NULL;
listener->fd = -1;
data->state = STATE_UNOPENED;
rad_assert(data->vps == NULL);
return 0;
}
break;
case STATE_READING:
if (data->fp && !feof(data->fp)) break;
data->state = STATE_QUEUED;
case STATE_QUEUED:
goto alloc_packet;
case STATE_RUNNING:
if (time(NULL) < (data->running + data->retry_interval)) {
return 0;
}
DEBUG("No response to detail request. Retrying");
data->state = STATE_NO_REPLY;
case STATE_NO_REPLY:
data->state = STATE_QUEUED;
goto alloc_packet;
case STATE_REPLIED:
pairfree(&data->vps);
data->state = STATE_HEADER;
goto do_header;
}
tail = &data->vps;
while (*tail) tail = &(*tail)->next;
while (fgets(buffer, sizeof(buffer), data->fp)) {
data->offset = ftell(data->fp);
if (!strchr(buffer, '\n')) {
pairfree(&data->vps);
goto cleanup;
}
if ((data->state == STATE_READING) &&
(buffer[0] == '\n')) {
data->state = STATE_QUEUED;
break;
}
if (data->state == STATE_HEADER) {
int y;
if (sscanf(buffer, "%*s %*s %*d %*d:%*d:%*d %d", &y)) {
data->state = STATE_READING;
}
continue;
}
if (sscanf(buffer, "%255s %8s %1023s", key, op, value) != 3) {
DEBUG2("WARNING: Skipping badly formatted line %s",
buffer);
continue;
}
if (!strchr(op, '=')) continue;
if (!strcasecmp(key, "Request-Authenticator")) continue;
if (!strcasecmp(key, "Client-IP-Address")) {
data->client_ip.af = AF_INET;
ip_hton(value, AF_INET, &data->client_ip);
continue;
}
if (!strcasecmp(key, "Timestamp")) {
data->timestamp = atoi(value);
vp = paircreate(PW_PACKET_ORIGINAL_TIMESTAMP,
PW_TYPE_DATE);
if (vp) {
vp->vp_date = (uint32_t) data->timestamp;
*tail = vp;
tail = &(vp->next);
}
continue;
}
vp = NULL;
if ((userparse(buffer, &vp) > 0) &&
(vp != NULL)) {
*tail = vp;
tail = &(vp->next);
}
}
if (ferror(data->fp)) goto cleanup;
data->tries = 0;
data->packets++;
alloc_packet:
data->tries++;
if (data->state != STATE_QUEUED) {
radlog(L_ERR, "Truncated record: treating it as EOF for detail file %s", data->filename_work);
goto cleanup;
}
if (!data->vps) {
data->state = STATE_HEADER;
if (feof(data->fp)) goto cleanup;
return 0;
}
packet = rad_alloc(1);
if (!packet) {
radlog(L_ERR, "FATAL: Failed allocating memory for detail");
exit(1);
}
memset(packet, 0, sizeof(*packet));
packet->sockfd = -1;
packet->src_ipaddr.af = AF_INET;
packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE);
packet->code = PW_ACCOUNTING_REQUEST;
packet->timestamp = time(NULL);
if (data->client_ip.af != AF_UNSPEC) {
packet->src_ipaddr = data->client_ip;
}
vp = pairfind(packet->vps, PW_PACKET_SRC_IP_ADDRESS);
if (vp) {
packet->src_ipaddr.af = AF_INET;
packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
} else {
vp = pairfind(packet->vps, PW_PACKET_SRC_IPV6_ADDRESS);
if (vp) {
packet->src_ipaddr.af = AF_INET6;
memcpy(&packet->src_ipaddr.ipaddr.ip6addr,
&vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
}
}
vp = pairfind(packet->vps, PW_PACKET_DST_IP_ADDRESS);
if (vp) {
packet->dst_ipaddr.af = AF_INET;
packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
} else {
vp = pairfind(packet->vps, PW_PACKET_DST_IPV6_ADDRESS);
if (vp) {
packet->dst_ipaddr.af = AF_INET6;
memcpy(&packet->dst_ipaddr.ipaddr.ip6addr,
&vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
}
}
packet->id = fr_rand() & 0xffff;
packet->src_port = 1024 + (fr_rand() & 0x7fff);
packet->dst_port = 1024 + (fr_rand() & 0x7fff);
packet->dst_ipaddr.af = AF_INET;
packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl((INADDR_LOOPBACK & ~0xffffff) | (fr_rand() & 0xffffff));
packet->vps = paircopy(data->vps);
vp = pairfind(packet->vps, PW_ACCT_DELAY_TIME);
if (!vp) {
vp = paircreate(PW_ACCT_DELAY_TIME, PW_TYPE_INTEGER);
rad_assert(vp != NULL);
pairadd(&packet->vps, vp);
}
if (data->timestamp != 0) {
vp->vp_integer += time(NULL) - data->timestamp;
}
vp = pairfind(packet->vps, PW_PACKET_TRANSMIT_COUNTER);
if (!vp) {
vp = paircreate(PW_PACKET_TRANSMIT_COUNTER, PW_TYPE_INTEGER);
rad_assert(vp != NULL);
pairadd(&packet->vps, vp);
}
vp->vp_integer = data->tries;
*pfun = rad_accounting;
if (debug_flag) {
fr_printf_log("detail_recv: Read packet from %s\n", data->filename_work);
for (vp = packet->vps; vp; vp = vp->next) {
debug_pair(vp);
}
}
if (!received_request(listener, packet, prequest,
&data->detail_client)) {
rad_free(&packet);
data->state = STATE_NO_REPLY;
return 0;
}
data->state = STATE_RUNNING;
data->running = packet->timestamp;
return 1;
}
void detail_free(rad_listen_t *this)
{
listen_detail_t *data = this->data;
free(data->filename);
data->filename = NULL;
pairfree(&data->vps);
if (data->fp != NULL) {
fclose(data->fp);
data->fp = NULL;
}
}
int detail_print(const rad_listen_t *this, char *buffer, size_t bufsize)
{
if (!this->server) {
return snprintf(buffer, bufsize, "%s",
((listen_detail_t *)(this->data))->filename);
}
return snprintf(buffer, bufsize, "detail file %s as server %s",
((listen_detail_t *)(this->data))->filename,
this->server);
}
int detail_encode(rad_listen_t *this, UNUSED REQUEST *request)
{
listen_detail_t *data = this->data;
if (!data->signal) {
int delay = (data->poll_interval - 1) * USEC;
delay += (USEC * 3) / 4;
delay += fr_rand() % (USEC / 2);
DEBUG2("Detail listener %s state %s signalled %d waiting %d.%06d sec",
data->filename,
fr_int2str(state_names, data->state, "?"), data->signal,
(delay / USEC), delay % USEC);
return delay;
}
data->signal = 0;
DEBUG2("Detail listener %s state %s signalled %d waiting %d.%06d sec",
data->filename, fr_int2str(state_names, data->state, "?"),
data->signal,
data->delay_time / USEC,
data->delay_time % USEC);
return data->delay_time;
}
int detail_decode(rad_listen_t *this, UNUSED REQUEST *request)
{
listen_detail_t *data = this->data;
return data->signal;
}
static const CONF_PARSER detail_config[] = {
{ "filename", PW_TYPE_STRING_PTR,
offsetof(listen_detail_t, filename), NULL, NULL },
{ "load_factor", PW_TYPE_INTEGER,
offsetof(listen_detail_t, load_factor), NULL, Stringify(10)},
{ "poll_interval", PW_TYPE_INTEGER,
offsetof(listen_detail_t, poll_interval), NULL, Stringify(1)},
{ "retry_interval", PW_TYPE_INTEGER,
offsetof(listen_detail_t, retry_interval), NULL, Stringify(30)},
{ NULL, -1, 0, NULL, NULL }
};
extern int check_config;
int detail_parse(CONF_SECTION *cs, rad_listen_t *this)
{
int rcode;
listen_detail_t *data;
RADCLIENT *client;
char buffer[2048];
if (check_config) return 0;
if (!this->data) {
this->data = rad_malloc(sizeof(*data));
memset(this->data, 0, sizeof(*data));
}
data = this->data;
rcode = cf_section_parse(cs, data, detail_config);
if (rcode < 0) {
cf_log_err(cf_sectiontoitem(cs), "Failed parsing listen section");
return -1;
}
if (!data->filename) {
cf_log_err(cf_sectiontoitem(cs), "No detail file specified in listen section");
return -1;
}
if ((data->load_factor < 1) || (data->load_factor > 100)) {
cf_log_err(cf_sectiontoitem(cs), "Load factor must be between 1 and 100");
return -1;
}
if ((data->poll_interval < 1) || (data->poll_interval > 20)) {
cf_log_err(cf_sectiontoitem(cs), "poll_interval must be between 1 and 20");
return -1;
}
if ((strchr(data->filename, '*') != NULL) ||
(strchr(data->filename, '[') != NULL)) {
char *p;
#ifndef HAVE_GLOB_H
radlog(L_INFO, "WARNING: Detail file \"%s\" appears to use file globbing, but it is not supported on this system.", data->filename);
#endif
strlcpy(buffer, data->filename, sizeof(buffer));
p = strrchr(buffer, FR_DIR_SEP);
if (p) {
p[1] = '\0';
} else {
buffer[0] = '\0';
}
strlcat(buffer, "detail.work",
sizeof(buffer) - strlen(buffer));
} else {
snprintf(buffer, sizeof(buffer), "%s.work", data->filename);
}
free(data->filename_work);
data->filename_work = strdup(buffer);
data->vps = NULL;
data->fp = NULL;
data->state = STATE_UNOPENED;
data->delay_time = data->poll_interval * USEC;
data->signal = 1;
client = &data->detail_client;
memset(client, 0, sizeof(*client));
client->ipaddr.af = AF_INET;
client->ipaddr.ipaddr.ip4addr.s_addr = INADDR_NONE;
client->prefix = 0;
client->longname = client->shortname = data->filename;
client->secret = client->shortname;
client->nastype = strdup("none");
return 0;
}
#endif