#include <freeradius-devel/ident.h>
RCSID("$Id$")
#define _LIBRADIUS 1
#include <freeradius-devel/libradius.h>
#include <pcap.h>
#include <freeradius-devel/radpaths.h>
#include <freeradius-devel/conf.h>
#include <freeradius-devel/radsniff.h>
static const char *radius_secret = "testing123";
static VALUE_PAIR *filter_vps = NULL;
#undef DEBUG
#define DEBUG if (fr_debug_flag) printf
static int minimal = 0;
static int do_sort = 0;
struct timeval start_pcap = {0, 0};
static rbtree_t *filter_tree = NULL;
static pcap_dumper_t *pcap_dumper = NULL;
typedef int (*rbcmp)(const void *, const void *);
static int filter_packet(RADIUS_PACKET *packet)
{
VALUE_PAIR *check_item;
VALUE_PAIR *vp;
unsigned int pass, fail;
int compare;
pass = fail = 0;
for (vp = packet->vps; vp != NULL; vp = vp->next) {
for (check_item = filter_vps;
check_item != NULL;
check_item = check_item->next)
if ((check_item->attribute == vp->attribute)
&& (check_item->operator != T_OP_SET)) {
compare = paircmp(check_item, vp);
if (compare == 1)
pass++;
else
fail++;
}
}
if (fail == 0 && pass != 0) {
if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
(packet->code == PW_ACCOUNTING_REQUEST)) {
rbtree_deletebydata(filter_tree, packet);
if (!rbtree_insert(filter_tree, packet)) {
oom:
fprintf(stderr, "radsniff: Out of memory\n");
exit(1);
}
}
return 0;
}
if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
(packet->code == PW_ACCOUNTING_REQUEST)) {
rbtree_deletebydata(filter_tree, packet);
return 1;
}
if ((packet->code == PW_AUTHENTICATION_ACK) ||
(packet->code == PW_AUTHENTICATION_REJECT) ||
(packet->code == PW_ACCESS_CHALLENGE) ||
(packet->code == PW_ACCOUNTING_RESPONSE)) {
RADIUS_PACKET *reply;
reply = rad_alloc_reply(packet);
if (!reply) goto oom;
compare = 1;
if (rbtree_finddata(filter_tree, reply)) {
compare = 0;
}
rad_free(&reply);
return compare;
}
return 1;
}
static void sort(RADIUS_PACKET *packet)
{
int i, j, size;
VALUE_PAIR *vp, *tmp;
VALUE_PAIR *array[1024];
size = 0;
for (vp = packet->vps; vp != NULL; vp = vp->next) {
array[size++] = vp;
}
if (size == 0) return;
for (i = 0; i < size - 1; i++) {
for (j = 0; j < size - 1 - i; j++) {
if (array[j + 1]->attribute < array[j]->attribute) {
tmp = array[j];
array[j] = array[j + 1];
array[j + 1] = tmp;
}
}
}
vp = packet->vps = array[0];
for (i = 1; i < size; i++) {
vp->next = array[i];
vp = array[i];
}
vp->next = NULL;
}
#define USEC 1000000
static void tv_sub(const struct timeval *end, const struct timeval *start,
struct timeval *elapsed)
{
elapsed->tv_sec = end->tv_sec - start->tv_sec;
if (elapsed->tv_sec > 0) {
elapsed->tv_sec--;
elapsed->tv_usec = USEC;
} else {
elapsed->tv_usec = 0;
}
elapsed->tv_usec += end->tv_usec;
elapsed->tv_usec -= start->tv_usec;
if (elapsed->tv_usec >= USEC) {
elapsed->tv_usec -= USEC;
elapsed->tv_sec++;
}
}
static void got_packet(uint8_t *args, const struct pcap_pkthdr *header, const uint8_t *data)
{
static int count = 1;
const struct ethernet_header *ethernet;
const struct ip_header *ip;
const struct udp_header *udp;
const uint8_t *payload;
int size_ethernet = sizeof(struct ethernet_header);
int size_ip = sizeof(struct ip_header);
int size_udp = sizeof(struct udp_header);
RADIUS_PACKET *packet;
struct timeval elapsed;
args = args;
if ((data[0] == 2) && (data[1] == 0) &&
(data[2] == 0) && (data[3] == 0)) {
ip = (const struct ip_header*) (data + 4);
} else {
ethernet = (const struct ethernet_header*)(data);
ip = (const struct ip_header*)(data + size_ethernet);
}
udp = (const struct udp_header*)(((const uint8_t *) ip) + size_ip);
payload = (const uint8_t *)(((const uint8_t *) udp) + size_udp);
packet = malloc(sizeof(*packet));
if (!packet) {
fprintf(stderr, "Out of memory\n");
return;
}
memset(packet, 0, sizeof(*packet));
packet->src_ipaddr.af = AF_INET;
packet->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr;
packet->src_port = ntohs(udp->udp_sport);
packet->dst_ipaddr.af = AF_INET;
packet->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr;
packet->dst_port = ntohs(udp->udp_dport);
packet->data = payload;
packet->data_len = header->len - (payload - data);
if (!rad_packet_ok(packet, 0)) {
printf("Packet: %s\n", fr_strerror());
printf("\tFrom: %s:%d\n", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
printf("\tTo: %s:%d\n", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
printf("\tType: %s\n", fr_packet_codes[packet->code]);
free(packet);
return;
}
if (rad_decode(packet, NULL, radius_secret) != 0) {
free(packet);
fr_perror("decode");
return;
}
if (filter_vps && filter_packet(packet)) {
free(packet);
DEBUG("Packet number %d doesn't match\n", count++);
return;
}
if (pcap_dumper) {
pcap_dump((void *) pcap_dumper, header, data);
goto check_filter;
}
printf("%s Id %d\t", fr_packet_codes[packet->code], packet->id);
printf("%s:%d -> ", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
printf("%s:%d", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
if (fr_debug_flag) printf("\t(%d packets)", count++);
if (!start_pcap.tv_sec) {
start_pcap = header->ts;
}
tv_sub(&header->ts, &start_pcap, &elapsed);
printf("\t+%u.%03u", (unsigned int) elapsed.tv_sec,
(unsigned int) elapsed.tv_usec / 1000);
if (!minimal) printf("\n");
if (!minimal && packet->vps) {
if (do_sort) sort(packet);
vp_printlist(stdout, packet->vps);
pairfree(&packet->vps);
}
printf("\n");
fflush(stdout);
check_filter:
if (!filter_vps ||
((packet->code != PW_AUTHENTICATION_REQUEST) &&
(packet->code != PW_ACCOUNTING_REQUEST))) {
free(packet);
}
}
static void NEVER_RETURNS usage(int status)
{
FILE *output = status ? stderr : stdout;
fprintf(output, "usage: radsniff [options]\n");
fprintf(output, "options:\n");
fprintf(output, "\t-c count\tNumber of packets to capture.\n");
fprintf(output, "\t-d directory\tDirectory where the dictionaries are found\n");
fprintf(output, "\t-F\t\tFilter PCAP file from stdin to stdout.\n");
fprintf(output, "\t\t\tOutput file will contain RADIUS packets.\n");
fprintf(output, "\t-f filter\tPCAP filter. (default is udp port 1812 or 1813)\n");
fprintf(output, "\t-h\t\tPrint this help message.\n");
fprintf(output, "\t-i interface\tInterface to capture.\n");
fprintf(output, "\t-I filename\tRead packets from filename.\n");
fprintf(output, "\t-m\t\tPrint packet headers only, not contents.\n");
fprintf(output, "\t-p port\t\tListen for packets on port.\n");
fprintf(output, "\t-r filter\tRADIUS attribute filter.\n");
fprintf(output, "\t-s secret\tRADIUS secret.\n");
fprintf(output, "\t-S\t\tSort attributes in the packet.\n");
fprintf(output, "\t\t\tUsed to compare server results.\n");
fprintf(output, "\t-x\t\tPrint out debugging information.\n");
exit(status);
}
int main(int argc, char *argv[])
{
char *dev;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *descr;
struct bpf_program fp;
bpf_u_int32 maskp;
bpf_u_int32 netp;
char buffer[1024];
char *pcap_filter = NULL;
char *radius_filter = NULL;
char *filename = NULL;
char *dump_file = NULL;
int packet_count = -1;
int opt;
FR_TOKEN parsecode;
const char *radius_dir = RADIUS_DIR;
int port = 1812;
int filter_stdin = 0;
dev = pcap_lookupdev(errbuf);
while ((opt = getopt(argc, argv, "c:d:Ff:hi:I:mp:r:s:Sw:xX")) != EOF) {
switch (opt)
{
case 'c':
packet_count = atoi(optarg);
if (packet_count <= 0) {
fprintf(stderr, "radsniff: Invalid number of packets \"%s\"\n", optarg);
exit(1);
}
break;
case 'd':
radius_dir = optarg;
break;
case 'F':
filter_stdin = 1;
break;
case 'f':
pcap_filter = optarg;
break;
case 'h':
usage(0);
break;
case 'i':
dev = optarg;
break;
case 'I':
filename = optarg;
break;
case 'm':
minimal = 1;
break;
case 'p':
port = atoi(optarg);
break;
case 'r':
radius_filter = optarg;
break;
case 's':
radius_secret = optarg;
break;
case 'S':
do_sort = 1;
break;
case 'w':
dump_file = optarg;
break;
case 'x':
case 'X':
fr_debug_flag++;
break;
default:
usage(1);
}
}
if (filter_stdin && (filename || dump_file)) usage(1);
if (!pcap_filter) {
pcap_filter = buffer;
snprintf(buffer, sizeof(buffer), "udp port %d or %d",
port, port + 1);
}
if (fr_debug_flag || radius_filter) {
if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
fr_perror("radsniff");
return 1;
}
}
if (radius_filter) {
parsecode = userparse(radius_filter, &filter_vps);
if (parsecode == T_OP_INVALID) {
fprintf(stderr, "radsniff: Invalid RADIUS filter \"%s\": %s\n", radius_filter, fr_strerror());
exit(1);
}
if (!filter_vps) {
fprintf(stderr, "radsniff: Empty RADIUS filter \"%s\"\n", radius_filter);
exit(1);
}
filter_tree = rbtree_create((rbcmp) fr_packet_cmp,
free, 0);
if (!filter_tree) {
fprintf(stderr, "radsniff: Failed creating filter tree\n");
exit(1);
}
}
pcap_lookupnet(dev, &netp, &maskp, errbuf);
if (fr_debug_flag) {
if (dev) printf("Device: [%s]\n", dev);
if (packet_count > 0) {
printf("Num of packets: [%d]\n",
packet_count);
}
printf("PCAP filter: [%s]\n", pcap_filter);
if (filter_vps) {
printf("RADIUS filter:\n");
vp_printlist(stdout, filter_vps);
}
printf("RADIUS secret: [%s]\n", radius_secret);
}
if (filename) {
descr = pcap_open_offline(filename, errbuf);
} else if (filter_stdin) {
descr = pcap_fopen_offline(stdin, errbuf);
} else if (!dev) {
fprintf(stderr, "radsniff: No filename or device was specified.\n");
exit(1);
} else {
descr = pcap_open_live(dev, 65536, 1, 0, errbuf);
}
if (descr == NULL)
{
fprintf(stderr, "radsniff: pcap_open_live failed (%s)\n", errbuf);
exit(1);
}
if (dump_file) {
pcap_dumper = pcap_dump_open(descr, dump_file);
if (!pcap_dumper) {
fprintf(stderr, "radsniff: Failed opening output file (%s)\n", pcap_geterr(descr));
exit(1);
}
} else if (filter_stdin) {
pcap_dumper = pcap_dump_fopen(descr, stdout);
if (!pcap_dumper) {
fprintf(stderr, "radsniff: Failed opening stdout: %s\n", pcap_geterr(descr));
exit(1);
}
}
if( pcap_compile(descr, &fp, pcap_filter, 0, netp) == -1)
{
fprintf(stderr, "radsniff: pcap_compile failed\n");
exit(1);
}
if (pcap_setfilter(descr, &fp) == -1)
{
fprintf(stderr, "radsniff: pcap_setfilter failed\n");
exit(1);
}
pcap_loop(descr, packet_count, got_packet, NULL);
pcap_close(descr);
if (filter_tree) rbtree_free(filter_tree);
DEBUG("Done sniffing\n");
return 0;
}