char radrelay_rcsid[] =
"$Id: radrelay.c,v 1.22.2.3.2.4 2007/03/16 13:22:03 aland Exp $";
#include "autoconf.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <netdb.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include "radiusd.h"
#include "conf.h"
#include "radpaths.h"
#include "missing.h"
#include "conffile.h"
#include "request_list.h"
const char *progname;
int debug_flag = 0;
const char *radlog_dir = NULL;
radlog_dest_t radlog_dest = RADLOG_FILES;
char *radius_dir = NULL;
const char *radacct_dir = NULL;
const char *radlib_dir = NULL;
uint32_t myip = INADDR_ANY;
int log_stripped_names;
struct main_config_t mainconfig;
#define STATE_EMPTY 0
#define STATE_BUSY1 1
#define STATE_BUSY2 2
#define STATE_FULL 3
#define STATE_RUN 0
#define STATE_BACKLOG 1
#define STATE_WAIT 2
#define STATE_SHUTDOWN 3
#define STATE_CLOSE 4
#define NR_SLOTS 64
#define DEFAULT_SLEEP 50
#define DEFAULT_SLEEP_EVERY 1
struct relay_request {
int state;
time_t retrans;
unsigned int retrans_num;
time_t timestamp;
uint32_t client_ip;
RADIUS_PACKET *req;
};
struct relay_misc {
int sockfd;
uint32_t dst_addr;
short dst_port;
uint32_t src_addr;
char detail[1024];
char *secret;
char f_secret[256];
int sleep_time;
int sleep_every;
int records_print;
};
struct relay_stats {
time_t startup;
uint32_t records_read;
uint32_t packets_sent;
uint32_t last_print_records;
};
char *c_secret = NULL;
char *c_shortname = NULL;
struct relay_request slots[NR_SLOTS];
char id_map[256];
int request_head = 0;
int got_sigterm = 0;
int debug = 0;
int get_radius_id(void);
void sigterm_handler(int sig);
void ms_sleep(int msec);
int isdateline(char *d);
int read_one(FILE *fp, struct relay_request *req);
int do_recv(struct relay_misc *r_args);
int do_send(struct relay_request *r, char *secret);
int detail_move(char *from, char *to);
void loop(struct relay_misc *r_args);
int find_shortname(char *shortname, char **host, char **secret);
void usage(void);
int get_radius_id()
{
unsigned int id = 0;
for(id = 0; id < 256; id++){
if (id_map[id] == 0)
break;
}
if (id == 256 || id_map[id] != 0){
fprintf(stdout, "get_radius_id(): No IDs available. Something is very wrong\n");
return -1;
}
id_map[id] = 1;
fprintf(stdout, "get_radius_id(): Assign RADIUS ID = %d\n",id);
return id;
}
void sigterm_handler(int sig)
{
signal(sig, sigterm_handler);
got_sigterm = 1;
}
inline void ms_sleep(int msec)
{
struct timeval tv;
tv.tv_sec = (msec / 1000);
tv.tv_usec = (msec % 1000) * 1000;
select(0, NULL, NULL, NULL, &tv);
}
inline int isdateline(char *d)
{
int y;
return sscanf(d, "%*s %*s %*d %*d:%*d:%*d %d", &y);
}
int read_one(FILE *fp, struct relay_request *r_req)
{
VALUE_PAIR *vp;
char *s;
char buf[2048];
char key[32], val[32];
int skip;
long fpos;
int x;
unsigned int i = 0;
if (r_req->state == STATE_FULL)
return 0;
if (r_req->state == STATE_EMPTY) {
r_req->state = STATE_BUSY1;
}
fpos = ftell(fp);
fseek(fp, 0L, SEEK_SET);
do {
x = rad_lockfd_nonblock(fileno(fp), 0);
if (x == -1)
ms_sleep(100);
} while (x == -1 && i++ < 20);
if (x == -1)
return 0;
redo:
s = NULL;
fseek(fp, fpos, SEEK_SET);
fpos = ftell(fp);
while ((s = fgets(buf, sizeof(buf), fp)) != NULL) {
if (!strlen(buf)) {
fprintf(stdout, "read_one: ZERO BYTE\n");
fseek(fp, fpos + 1, SEEK_SET);
break;
} else if (buf[strlen(buf) - 1] != '\n') {
fprintf(stdout, "read_one: BROKEN ATTRIBUTE\n");
fseek(fp, fpos + strlen(buf), SEEK_SET);
break;
}
if (r_req->state == STATE_BUSY1) {
if (isdateline(buf)) {
r_req->state = STATE_BUSY2;
}
} else if (r_req->state == STATE_BUSY2) {
if (buf[0] != ' ' && buf[0] != '\t') {
r_req->state = STATE_FULL;
break;
}
skip = 0;
if (sscanf(buf, "%31s = %31s", key, val) == 2) {
if (!strcasecmp(key, "Timestamp")) {
r_req->timestamp = atoi(val);
skip++;
} else
if (!strcasecmp(key, "Client-IP-Address")) {
r_req->client_ip = ip_getaddr(val);
skip++;
} else
if (!strcasecmp(key, "Request-Authenticator"))
skip++;
}
if (!skip) {
vp = NULL;
if (userparse(buf, &vp) > 0 &&
(vp != NULL) &&
(vp->attribute < 256 ||
vp->attribute > 65535) &&
vp->attribute != PW_VENDOR_SPECIFIC) {
pairadd(&(r_req->req->vps), vp);
} else {
pairfree(&vp);
}
}
}
fpos = ftell(fp);
}
clearerr(fp);
if (r_req->state == STATE_FULL) {
if (pairfind(r_req->req->vps, PW_ACCT_STATUS_TYPE) == NULL){
fprintf(stdout, "read_one: No Acct-Status-Type attribute present. Rejecting record.\n");
r_req->state = STATE_BUSY1;
if (r_req->req->vps != NULL) {
pairfree(&r_req->req->vps);
r_req->req->vps = NULL;
}
if (r_req->req->data != NULL) {
free (r_req->req->data);
r_req->req->data = NULL;
}
r_req->retrans = 0;
r_req->retrans_num = 0;
r_req->timestamp = 0;
r_req->client_ip = 0;
goto redo;
}
if (r_req->timestamp == 0)
r_req->timestamp = time(NULL);
if ((vp = pairfind(r_req->req->vps, PW_ACCT_DELAY_TIME)) != NULL) {
r_req->timestamp -= vp->lvalue;
vp->lvalue = 0;
}
r_req->req->id = get_radius_id();
}
if (s == NULL) {
if (r_req->state == STATE_BUSY1) {
r_req->state = STATE_EMPTY;
}
if (r_req->state == STATE_EMPTY || r_req->state == STATE_FULL)
return EOF;
}
fpos = ftell(fp);
fseek(fp, 0L, SEEK_SET);
rad_unlockfd(fileno(fp), 0);
fseek(fp, fpos, SEEK_SET);
return 0;
}
int do_recv(struct relay_misc *r_args)
{
RADIUS_PACKET *rep;
struct relay_request *r;
int i;
rep = rad_recv(r_args->sockfd);
if (rep == NULL) {
librad_perror("radrelay:");
return -1;
}
if (rep->code != PW_ACCOUNTING_RESPONSE) {
rad_free(&rep);
return -1;
}
for (i = 0; i < NR_SLOTS; i++) {
r = slots + i;
if (r->state == STATE_FULL && r->req->id == rep->id) {
if (rad_verify(rep, r->req, r_args->secret) != 0) {
librad_perror("rad_verify");
rad_free(&rep);
return -1;
}
if (rad_decode(rep, r->req, r_args->secret) != 0) {
librad_perror("rad_decode");
rad_free(&rep);
return -1;
}
id_map[r->req->id] = 0;
fprintf(stdout, "do_recv: Free RADIUS ID = %d\n",r->req->id);
if (r->req->vps != NULL) {
pairfree(&r->req->vps);
r->req->vps = NULL;
}
if (r->req->data != NULL) {
free (r->req->data);
r->req->data = NULL;
}
r->state = STATE_EMPTY;
r->retrans = 0;
r->retrans_num = 0;
r->timestamp = 0;
r->client_ip = 0;
break;
}
}
rad_free(&rep);
return 0;
}
int do_send(struct relay_request *r, char *secret)
{
VALUE_PAIR *vp;
time_t now;
if (r->client_ip == r->req->dst_ipaddr) {
fprintf(stdout, "do_send: Client-IP == Dest-IP. Droping packet.\n");
fprintf(stdout, "do_send: Free RADIUS ID = %d\n",r->req->id);
id_map[r->req->id] = 0;
if (r->req->vps != NULL) {
pairfree(&r->req->vps);
r->req->vps = NULL;
}
if (r->req->data != NULL) {
free (r->req->data);
r->req->data = NULL;
}
r->state = STATE_EMPTY;
r->retrans = 0;
r->retrans_num = 0;
r->timestamp = 0;
r->client_ip = 0;
return 0;
}
now = time(NULL);
if (r->retrans > now)
return 0;
if (r->retrans > 0){
id_map[r->req->id] = 0;
r->req->id = get_radius_id();
if (r->req->data != NULL){
free(r->req->data);
r->req->data = NULL;
}
r->retrans_num++;
}
if (r->retrans_num > 20)
r->retrans = now + 70;
else
r->retrans = now + 3 + (3 * r->retrans_num);
if ((vp = pairfind(r->req->vps, PW_ACCT_DELAY_TIME)) == NULL) {
vp = paircreate(PW_ACCT_DELAY_TIME, PW_TYPE_INTEGER);
pairadd(&(r->req->vps), vp);
}
vp->lvalue = (now - r->timestamp);
rad_send(r->req, NULL, secret);
return 1;
}
int detail_move(char *from, char *to)
{
struct stat st;
int n;
int oldmask;
if (stat(from, &st) < 0)
return -1;
if (rename(from, to) < 0)
return -1;
oldmask = umask(0);
if ((n = open(from, O_CREAT|O_RDWR, st.st_mode)) >= 0)
close(n);
umask(oldmask);
return 0;
}
void loop(struct relay_misc *r_args)
{
FILE *fp = NULL;
struct relay_request *r;
struct timeval tv;
struct relay_stats stats;
fd_set readfds;
char work[1030];
time_t now, uptime, last_rename = 0;
int i, n;
int state = STATE_RUN;
int id;
long fpos;
strNcpy(work, r_args->detail, sizeof(work) - 6);
strcat(work, ".work");
id = ((int)getpid() & 0xff);
memset(&stats,0,sizeof(struct relay_stats));
stats.startup = time(NULL);
for (i = 0; i < NR_SLOTS; i++) {
if ((slots[i].req = rad_alloc(1)) == NULL) {
librad_perror("radrelay");
exit(1);
}
slots[i].state = STATE_EMPTY;
slots[i].retrans = 0;
slots[i].retrans_num = 0;
slots[i].timestamp = 0;
slots[i].client_ip = 0;
slots[i].req->sockfd = r_args->sockfd;
slots[i].req->dst_ipaddr = r_args->dst_addr;
slots[i].req->dst_port = r_args->dst_port;
slots[i].req->src_ipaddr = r_args->src_addr;
slots[i].req->code = PW_ACCOUNTING_REQUEST;
slots[i].req->vps = NULL;
slots[i].req->data = NULL;
}
while(1) {
if (got_sigterm) state = STATE_SHUTDOWN;
if (state == STATE_RUN && fp == NULL) {
if ((fp = fopen(work, "r+")) != NULL)
state = STATE_BACKLOG;
else
fp = fopen(r_args->detail, "r+");
if (fp == NULL) {
fprintf(stderr, "%s: Unable to open detail file - %s\n", progname, r_args->detail);
perror("fopen");
return;
}
}
r = &slots[request_head];
if (fp && (state == STATE_RUN || state == STATE_BACKLOG) &&
r->state != STATE_FULL) {
if (read_one(fp, r) == EOF) do {
if (state == STATE_BACKLOG) {
state = STATE_CLOSE;
break;
}
now = time(NULL);
if (ftell(fp) == 0 || now < last_rename + 10) {
fpos = ftell(fp);
fseek(fp, 0L, SEEK_SET);
rad_unlockfd(fileno(fp), 0);
fseek(fp, fpos, SEEK_SET);
break;
}
last_rename = now;
if (detail_move(r_args->detail, work) == 0) {
if (debug_flag > 0)
fprintf(stderr, "Moving %s to %s\n",
r_args->detail, work);
ms_sleep(1000);
state = STATE_BACKLOG;
}
fpos = ftell(fp);
fseek(fp, 0L, SEEK_SET);
rad_unlockfd(fileno(fp), 0);
fseek(fp, fpos, SEEK_SET);
} while(0);
if (r_args->records_print && state == STATE_RUN){
stats.records_read++;
if (stats.last_print_records - stats.records_read >= r_args->records_print){
now = time(NULL);
uptime = (stats.startup == now) ? 1 : now - stats.startup;
fprintf(stderr, "%s: Running and Processing Records.\n",progname);
fprintf(stderr, "Seconds since startup: %ld\n",uptime);
fprintf(stderr, "Records Read: %d\n",stats.records_read);
fprintf(stderr, "Packets Sent: %d\n",stats.packets_sent);
fprintf(stderr, "Record Rate since startup: %.2f\n",
(double)stats.records_read / uptime);
fprintf(stderr, "Packet Rate since startup: %.2f\n",
(double)stats.packets_sent / uptime);
stats.last_print_records = stats.records_read;
}
}
if (r->state == STATE_FULL)
request_head = (request_head + 1) % NR_SLOTS;
}
tv.tv_sec = 0;
tv.tv_usec = 25000;
FD_ZERO(&readfds);
FD_SET(r_args->sockfd, &readfds);
n = 0;
while (select(r_args->sockfd + 1, &readfds, NULL, NULL, &tv) > 0) {
do_recv(r_args);
if (n++ >= NR_SLOTS) break;
}
if (state == STATE_WAIT || state == STATE_CLOSE || state == STATE_SHUTDOWN) {
for (i = 0; i < NR_SLOTS; i++)
if (slots[i].state != STATE_EMPTY)
break;
if (i == NR_SLOTS) {
if (state == STATE_CLOSE) {
if (fp) fclose(fp);
fp = NULL;
if (debug_flag > 0)
fprintf(stderr, "Unlink file %s\n", work);
unlink(work);
}
else if (state == STATE_SHUTDOWN) {
for (i = 0; i < NR_SLOTS; i++) {
rad_free(&slots[i].req);
}
exit(0);
}
state = STATE_RUN;
}
}
n=0;
for (i = 0; i < NR_SLOTS; i++) {
if (slots[i].state == STATE_FULL) {
n += do_send(&slots[i], r_args->secret);
if ((n % r_args->sleep_every) == 0)
ms_sleep(r_args->sleep_time);
if (n > NR_SLOTS / 2)
break;
}
}
if (r_args->records_print)
stats.packets_sent += n;
}
}
int find_shortname(char *shortname, char **host, char **secret)
{
CONF_SECTION *maincs, *cs;
char buffer[256];
memset(&mainconfig, 0, sizeof(mainconfig));
snprintf(buffer, sizeof(buffer), "%.200s/radiusd.conf", radius_dir);
if ((maincs = conf_read(NULL, 0, buffer, NULL)) == NULL) {
return -1;
}
cs = cf_section_sub_find(maincs, "client");
if (cs) {
c_shortname = cf_section_value_find(cs, "shortname");
c_secret = cf_section_value_find(cs, "secret");
while (cs && strcmp(shortname, c_shortname)) {
cs = cf_subsection_find_next(cs, cs, "client");
if (cs) {
c_shortname = cf_section_value_find(cs, "shortname");
c_secret = cf_section_value_find(cs, "secret");
}
};
};
if (cs) {
*host = cf_section_name2(cs);
*secret = c_secret;
if (host && secret)
return 0;
}
return -1;
}
void usage(void)
{
fprintf(stderr, "Usage: radrelay [-a accounting_dir] [-d radius_dir] [-i local_ip] [-s secret]\n");
fprintf(stderr, "[-e sleep_every packets] [-t sleep_time (ms)] [-S secret_file] [-fx]\n");
fprintf(stderr, "[-R records_print] <[-n shortname] [-r remote-server[:port]]> detailfile\n");
fprintf(stderr, " -a accounting_dir Base accounting directory.\n");
fprintf(stderr, " -d radius_dir Base radius (raddb) directory.\n");
fprintf(stderr, " -f Stay in the foreground (don't fork).\n");
fprintf(stderr, " -h This help.\n");
fprintf(stderr, " -i local_ip Use local_ip as source address.\n");
fprintf(stderr, " -n shortname Use the [shortname] entry from clients.conf for\n");
fprintf(stderr, " ip-adress and secret.\n");
fprintf(stderr, " -t sleep_time Sleep so much time (in ms) between sending packets. Default: %dms.\n",
DEFAULT_SLEEP);
fprintf(stderr, " -e sleep_every Sleep after sending so many packets. Default: %d\n",
DEFAULT_SLEEP_EVERY);
fprintf(stderr, " -R records_print If in foreground mode, print statistics after so many records read.\n");
fprintf(stderr, " -r remote-server The destination address/hostname.\n");
fprintf(stderr, " -s secret Server secret.\n");
fprintf(stderr, " -S secret_file Read server secret from file.\n");
fprintf(stderr, " -x Debug mode (-xx gives more debugging).\n");
exit(1);
}
int main(int argc, char **argv)
{
struct servent *svp;
char *server_name;
char *shortname;
char *p;
int c;
int i;
int dontfork = 0;
struct relay_misc r_args;
FILE *sfile_fp;
progname = argv[0];
r_args.sockfd = -1;
r_args.dst_addr = 0;
r_args.dst_port = 0;
r_args.src_addr = 0;
memset((char *) r_args.detail, 0, 1024);
memset((char *) r_args.f_secret, 0, 256);
r_args.secret = NULL;
r_args.sleep_time = DEFAULT_SLEEP;
r_args.sleep_every = DEFAULT_SLEEP_EVERY;
shortname = NULL;
server_name = NULL;
radius_dir = strdup(RADIUS_DIR);
librad_debug = 0;
while ((c = open("/dev/null", O_RDWR)) < 3 && c >= 0);
if (c >= 3) close(c);
while ((c = getopt(argc, argv, "a:d:fhi:t:e:n:r:R:s:S:x")) != EOF) switch(c) {
case 'a':
if (strlen(optarg) > 1021) {
fprintf(stderr, "%s: acct_dir to long\n", progname);
exit(1);
}
strncpy(r_args.detail, optarg, 1021);
break;
case 'd':
if (radius_dir)
free(radius_dir);
radius_dir = strdup(optarg);
break;
case 'f':
dontfork = 1;
break;
case 'n':
shortname = optarg;
break;
case 't':
r_args.sleep_time = atoi(optarg);
break;
case 'e':
r_args.sleep_every = atoi(optarg);
break;
case 'R':
if (!dontfork){
fprintf(stderr, "%s: Not in foreground mode. Can't print statistics.\n",progname);
usage();
}
r_args.records_print = atoi(optarg);
break;
case 'r':
server_name = optarg;
break;
case 's':
r_args.secret = optarg;
break;
case 'x':
if (debug == 1)
librad_debug = 1;
debug = 1;
dontfork = 1;
break;
case 'S':
sfile_fp = fopen(optarg, "r");
if (sfile_fp == NULL) {
fprintf(stderr, "Error opening %s: %s\n",
optarg, strerror(errno));
exit(1);
}
if (fgets(r_args.f_secret, 256, sfile_fp) == NULL) {
fprintf(stderr, "Error reading from %s: %s\n",
optarg, strerror(errno));
fclose(sfile_fp);
exit(1);
}
fclose(sfile_fp);
for (c = 0; c < strlen(r_args.f_secret); c++)
if (r_args.f_secret[c] == ' ' ||
r_args.f_secret[c] == '\n')
r_args.f_secret[c] = '\0';
if (strlen(r_args.f_secret) < 2) {
fprintf(stderr, "Secret in %s is to short\n",
optarg);
exit(1);
}
r_args.secret = r_args.f_secret;
break;
case 'i':
if ((r_args.src_addr = ip_getaddr(optarg)) == 0) {
fprintf(stderr, "%s: unknown host %s\n",
progname, optarg);
exit(1);
}
break;
case 'h':
default:
usage();
break;
}
if (argc == optind) {
usage();
}
argc -= (optind - 1);
argv += (optind - 1);
if (shortname && server_name)
usage();
if (!shortname && !server_name)
usage();
if (r_args.secret != NULL && shortname != NULL)
usage();
if (shortname != NULL) {
if (find_shortname(shortname, &server_name, &r_args.secret) == -1) {
fprintf(stderr, "Couldn't find %s in configuration files.\n", shortname);
exit(1);
}
}
if ((p = strrchr(server_name, ':')) != NULL) {
*p = 0;
p++;
r_args.dst_port = ntohs(atoi(p));
}
if (r_args.dst_port == 0) {
svp = getservbyname ("radacct", "udp");
r_args.dst_port = svp ? ntohs(svp->s_port) : PW_ACCT_UDP_PORT;
} else {
r_args.dst_port = ntohs(r_args.dst_port);
}
r_args.dst_addr = ip_getaddr(server_name);
if (r_args.dst_addr == 0) {
fprintf(stderr, "%s: unknown host\n",
server_name);
exit(1);
}
if (r_args.secret == NULL || r_args.secret[0] == 0) {
fprintf(stderr, "No secret available for server %s\n",
server_name);
exit(1);
}
if (r_args.detail[0] == '\0') {
if (strlen(RADIR) > 1021) {
fprintf(stderr, "acct_dir to long\n");
exit(1);
}
strncpy(r_args.detail, RADIR, 1021);
}
if (chdir(r_args.detail) == -1) {
perror("chdir");
exit(1);
}
if (strlen(argv[1]) + strlen(r_args.detail) > 1023) {
fprintf(stderr, "Detail file path to long");
exit(1);
} else {
if (r_args.detail[strlen(r_args.detail) - 1] != '/')
r_args.detail[strlen(r_args.detail)] = '/';
strncat (r_args.detail, argv[1], 1023 - strlen(r_args.detail));
}
if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
librad_perror("radrelay");
exit(1);
}
if ((r_args.sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
fprintf(stderr, "Error opening socket: %s", strerror(errno));
exit(1);
}
signal(SIGTERM, sigterm_handler);
if (!dontfork) {
if (fork() != 0)
exit(0);
close(0);
close(1);
close(2);
(void)open("/dev/null", O_RDWR);
dup(0);
dup(0);
signal(SIGHUP, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
#ifdef HAVE_SETSID
setsid();
#endif
}
for(i=0;i<256;i++)
id_map[i] = 0;
loop(&r_args);
return 0;
}