Sort attributes, and print times as offsets
[freeradius.git] / src / main / radsniff.c
1 /*
2  *  radsniff.c  Display the RADIUS traffic on the network.
3  *
4  *  Version:    $Id$
5  *
6  *  This program is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU General Public License
8  *  as published by the Free Software Foundation; either version 2
9  *  of the License, or (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  *  Copyright 2006  The FreeRADIUS server project
21  *  Copyright 2006  Nicolas Baradakis <nicolas.baradakis@cegetel.net>
22  */
23
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #define _LIBRADIUS 1
28 #include <freeradius-devel/libradius.h>
29
30 #include <pcap.h>
31
32 #include <freeradius-devel/radpaths.h>
33 #include <freeradius-devel/conf.h>
34 #include <freeradius-devel/radsniff.h>
35
36 static const char *radius_secret = "testing123";
37 static VALUE_PAIR *filter_vps = NULL;
38 #undef DEBUG
39 #define DEBUG if (fr_debug_flag) printf
40
41 static int minimal = 0;
42 static int do_sort = 0;
43 struct timeval start_pcap = {0, 0};
44 static rbtree_t *filter_tree = NULL;
45 typedef int (*rbcmp)(const void *, const void *);
46
47 static int filter_packet(RADIUS_PACKET *packet)
48 {
49         VALUE_PAIR *check_item;
50         VALUE_PAIR *vp;
51         unsigned int pass, fail;
52         int compare;
53
54         pass = fail = 0;
55         for (vp = packet->vps; vp != NULL; vp = vp->next) {
56                 for (check_item = filter_vps;
57                      check_item != NULL;
58                      check_item = check_item->next)
59                         if ((check_item->attribute == vp->attribute)
60                          && (check_item->operator != T_OP_SET)) {
61                                 compare = paircmp(check_item, vp);
62                                 if (compare == 1)
63                                         pass++;
64                                 else
65                                         fail++;
66                         }
67         }
68
69
70         if (fail == 0 && pass != 0) {
71                 /*
72                  *      Cache authentication requests, as the replies
73                  *      may not match the RADIUS filter.
74                  */
75                 if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
76                     (packet->code == PW_ACCOUNTING_REQUEST)) {
77                         rbtree_deletebydata(filter_tree, packet);
78                         
79                         if (!rbtree_insert(filter_tree, packet)) {
80                         oom:
81                                 fprintf(stderr, "radsniff: Out of memory\n");
82                                 exit(1);
83                         }
84                 }
85                 return 0;       /* matched */
86         }
87
88         /*
89          *      Don't create erroneous matches.
90          */
91         if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
92             (packet->code == PW_ACCOUNTING_REQUEST)) {
93                 rbtree_deletebydata(filter_tree, packet);
94                 return 1;
95         }
96         
97         /*
98          *      Else see if a previous Access-Request
99          *      matched.  If so, also print out the
100          *      matching accept, reject, or challenge.
101          */
102         if ((packet->code == PW_AUTHENTICATION_ACK) ||
103             (packet->code == PW_AUTHENTICATION_REJECT) ||
104             (packet->code == PW_ACCESS_CHALLENGE) ||
105             (packet->code == PW_ACCOUNTING_RESPONSE)) {
106                 RADIUS_PACKET *reply;
107
108                 /*
109                  *      This swaps the various fields.
110                  */
111                 reply = rad_alloc_reply(packet);
112                 if (!reply) goto oom;
113                 
114                 compare = 1;
115                 if (rbtree_finddata(filter_tree, reply)) {
116                         compare = 0;
117                 }
118                 
119                 rad_free(&reply);
120                 return compare;
121         }
122         
123         return 1;
124 }
125
126 /*
127  *      Bubble goodness
128  */
129 static void sort(RADIUS_PACKET *packet)
130 {
131         int i, j, size;
132         VALUE_PAIR *vp, *tmp;
133         VALUE_PAIR *array[1024]; /* way more than necessary */
134
135         size = 0;
136         for (vp = packet->vps; vp != NULL; vp = vp->next) {
137                 array[size++] = vp;
138         }
139
140         if (size == 0) return;
141
142         for (i = 0; i < size - 1; i++)  {
143                 for (j = 0; j < size - 1 - i; j++) {
144                         if (array[j + 1]->attribute < array[j]->attribute)  {
145                                 tmp = array[j];         
146                                 array[j] = array[j + 1];
147                                 array[j + 1] = tmp;
148                         }
149                 }
150         }
151
152         /*
153          *      And put them back again.
154          */
155         vp = packet->vps = array[0];
156         for (i = 1; i < size; i++) {
157                 vp->next = array[i];
158                 vp = array[i];
159         }
160         vp->next = NULL;
161 }
162
163 #define USEC 1000000
164 static void tv_sub(struct timeval *end, struct timeval *start,
165                    struct timeval *elapsed)
166 {
167         elapsed->tv_sec = end->tv_sec - start->tv_sec;
168         if (elapsed->tv_sec > 0) {
169                 elapsed->tv_sec--;
170                 elapsed->tv_usec = USEC;
171         } else {
172                 elapsed->tv_usec = 0;
173         }
174         elapsed->tv_usec += end->tv_usec;
175         elapsed->tv_usec -= start->tv_usec;
176         
177         if (elapsed->tv_usec >= USEC) {
178                 elapsed->tv_usec -= USEC;
179                 elapsed->tv_sec++;
180         }
181 }
182
183 static void got_packet(uint8_t *args, const struct pcap_pkthdr *header, const uint8_t *data)
184 {
185         /* Just a counter of how many packets we've had */
186         static int count = 1;
187         /* Define pointers for packet's attributes */
188         const struct ethernet_header *ethernet;  /* The ethernet header */
189         const struct ip_header *ip;              /* The IP header */
190         const struct udp_header *udp;            /* The UDP header */
191         const uint8_t *payload;                     /* Packet payload */
192         /* And define the size of the structures we're using */
193         int size_ethernet = sizeof(struct ethernet_header);
194         int size_ip = sizeof(struct ip_header);
195         int size_udp = sizeof(struct udp_header);
196         /* For FreeRADIUS */
197         RADIUS_PACKET *packet;
198         struct timeval elapsed;
199
200         args = args;            /* -Wunused */
201
202         /* Define our packet's attributes */
203         ethernet = (const struct ethernet_header*)(data);
204         ip = (const struct ip_header*)(data + size_ethernet);
205         udp = (const struct udp_header*)(data + size_ethernet + size_ip);
206         payload = (const uint8_t *)(data + size_ethernet + size_ip + size_udp);
207
208         packet = malloc(sizeof(*packet));
209         if (!packet) {
210                 fprintf(stderr, "Out of memory\n");
211                 return;
212         }
213
214         memset(packet, 0, sizeof(*packet));
215         packet->src_ipaddr.af = AF_INET;
216         packet->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr;
217         packet->src_port = ntohs(udp->udp_sport);
218         packet->dst_ipaddr.af = AF_INET;
219         packet->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr;
220         packet->dst_port = ntohs(udp->udp_dport);
221
222         packet->data = payload;
223         packet->data_len = header->len - size_ethernet - size_ip - size_udp;
224
225         if (!rad_packet_ok(packet, 0)) {
226                 fr_perror("Packet");
227                 
228                 fprintf(stderr, "\tFrom:    %s:%d\n", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
229                 fprintf(stderr, "\tTo:      %s:%d\n", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
230                 fprintf(stderr, "\tType:    %s\n", fr_packet_codes[packet->code]);
231
232                 free(packet);
233                 return;
234         }
235
236         /*
237          *      Decode the data without bothering to check the signatures.
238          */
239         if (rad_decode(packet, NULL, radius_secret) != 0) {
240                 free(packet);
241                 fr_perror("decode");
242                 return;
243         }
244
245         if (filter_vps && filter_packet(packet)) {
246                 free(packet);
247                 DEBUG("Packet number %d doesn't match\n", count++);
248                 return;
249         }
250         printf("%s Id %d\t", fr_packet_codes[packet->code], packet->id);
251
252         /* Print the RADIUS packet */
253         printf("%s:%d -> ", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
254         printf("%s:%d", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
255         if (fr_debug_flag) printf("\t(%d packets)", count++);
256
257         if (!start_pcap.tv_sec) {
258                 start_pcap = header->ts;
259         }
260
261         tv_sub(&header->ts, &start_pcap, &elapsed);
262
263         printf("\t+%u.%03u", (unsigned int) elapsed.tv_sec,
264                (unsigned int) elapsed.tv_usec / 1000);
265         if (!minimal) printf("\n");
266         if (!minimal && packet->vps) {
267                 if (do_sort) sort(packet);
268
269                 vp_printlist(stdout, packet->vps);
270                 pairfree(&packet->vps);
271         }
272         printf("\n");
273         fflush(stdout);
274
275         /*
276          *      If we're doing filtering, Access-Requests are cached
277          *      in the filter tree.
278          */
279         if (!filter_vps ||
280             ((packet->code != PW_AUTHENTICATION_REQUEST) &&
281              (packet->code != PW_ACCOUNTING_REQUEST))) {
282                 free(packet);
283         }
284 }
285
286 static void NEVER_RETURNS usage(int status)
287 {
288         FILE *output = status ? stderr : stdout;
289         fprintf(output, "usage: radsniff [options]\n");
290         fprintf(output, "options:\n");
291         fprintf(output, "\t-c count\tNumber of packets to capture.\n");
292         fprintf(output, "\t-d directory\tDirectory where the dictionaries are found\n");
293         fprintf(output, "\t-f filter\tPCAP filter. (default is udp port 1812 or 1813 or 1814)\n");
294         fprintf(output, "\t-h\t\tPrint this help message.\n");
295         fprintf(output, "\t-i interface\tInterface to capture.\n");
296         fprintf(output, "\t-I filename\tRead packets from filename.\n");
297         fprintf(output, "\t-m\t\tPrint packet headers only, not contents.\n");
298         fprintf(output, "\t-p port\tList for packets on port.\n");
299         fprintf(output, "\t-r filter\tRADIUS attribute filter.\n");
300         fprintf(output, "\t-s secret\tRADIUS secret.\n");
301         fprintf(output, "\t-S\t\tSort attributes in the packet.  Used to compare server results.\n");
302         fprintf(output, "\t-x\t\tPrint out debugging information.\n");
303         exit(status);
304 }
305
306 int main(int argc, char *argv[])
307 {
308         char *dev;                      /* sniffing device */
309         char errbuf[PCAP_ERRBUF_SIZE];  /* error buffer */
310         pcap_t *descr;                  /* sniff handler */
311         struct bpf_program fp;          /* hold compiled program */
312         bpf_u_int32 maskp;              /* subnet mask */
313         bpf_u_int32 netp;               /* ip */
314         char buffer[1024];
315         char *pcap_filter = NULL;
316         char *radius_filter = NULL;
317         char *filename = NULL;
318         int packet_count = -1;          /* how many packets to sniff */
319         int opt;
320         FR_TOKEN parsecode;
321         const char *radius_dir = RADIUS_DIR;
322         int port = 1812;
323
324         /* Default device */
325         dev = pcap_lookupdev(errbuf);
326
327         /* Get options */
328         while ((opt = getopt(argc, argv, "c:d:f:hi:I:mp:r:s:SxX")) != EOF) {
329                 switch (opt)
330                 {
331                 case 'c':
332                         packet_count = atoi(optarg);
333                         if (packet_count <= 0) {
334                                 fprintf(stderr, "radsniff: Invalid number of packets \"%s\"\n", optarg);
335                                 exit(1);
336                         }
337                         break;
338                 case 'd':
339                         radius_dir = optarg;
340                         break;
341                 case 'f':
342                         pcap_filter = optarg;
343                         break;
344                 case 'h':
345                         usage(0);
346                         break;
347                 case 'i':
348                         dev = optarg;
349                         break;
350                 case 'I':
351                         filename = optarg;
352                         break;
353                 case 'm':
354                         minimal = 1;
355                         break;
356                 case 'p':
357                         port = atoi(optarg);
358                         break;
359                 case 'r':
360                         radius_filter = optarg;
361                         break;
362                 case 's':
363                         radius_secret = optarg;
364                         break;
365                 case 'S':
366                         do_sort = 1;
367                         break;
368                 case 'x':
369                 case 'X':       /* for backwards compatibility */
370                         fr_debug_flag++;
371                         break;
372                 default:
373                         usage(1);
374                 }
375         }
376
377         if (!pcap_filter) {
378                 pcap_filter = buffer;
379                 snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d",
380                          port, port + 1, port + 2);
381         }
382
383         if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
384                 fr_perror("radsniff");
385                 return 1;
386         }
387
388         if (radius_filter) {
389                 parsecode = userparse(radius_filter, &filter_vps);
390                 if (parsecode == T_OP_INVALID) {
391                         fprintf(stderr, "radsniff: Invalid RADIUS filter \"%s\": %s\n", radius_filter, fr_strerror());
392                         exit(1);
393                 }
394                 if (!filter_vps) {
395                         fprintf(stderr, "radsniff: Empty RADIUS filter \"%s\"\n", radius_filter);
396                         exit(1);
397                 }
398
399                 filter_tree = rbtree_create((rbcmp) fr_packet_cmp,
400                                             free, 0);
401                 if (!filter_tree) {
402                         fprintf(stderr, "radsniff: Failed creating filter tree\n");
403                         exit(1);
404                 }
405         }
406
407         /* Set our device */
408         pcap_lookupnet(dev, &netp, &maskp, errbuf);
409
410         /* Print device to the user */
411         if (fr_debug_flag) {
412                 if (dev) printf("Device: [%s]\n", dev);
413                 if (packet_count > 0) {
414                         printf("Num of packets: [%d]\n",
415                                packet_count);
416                 }
417                 printf("PCAP filter: [%s]\n", pcap_filter);
418                 if (filter_vps) {
419                         printf("RADIUS filter:\n");
420                         vp_printlist(stdout, filter_vps);
421                 }
422                 printf("RADIUS secret: [%s]\n", radius_secret);
423         }
424
425         /* Open the device so we can spy */
426         if (filename) {
427                 descr = pcap_open_offline(filename, errbuf);
428         } else {
429                 descr = pcap_open_live(dev, SNAPLEN, 1, 0, errbuf);
430         }
431         if (descr == NULL)
432         {
433                 printf("radsniff: pcap_open_live failed (%s)\n", errbuf);
434                 exit(1);
435         }
436
437         /* Apply the rules */
438         if( pcap_compile(descr, &fp, pcap_filter, 0, netp) == -1)
439         {
440                 printf("radsniff: pcap_compile failed\n");
441                 exit(1);
442         }
443         if (pcap_setfilter(descr, &fp) == -1)
444         {
445                 printf("radsniff: pcap_setfilter failed\n");
446                 exit(1);
447         }
448
449         /* Now we can set our callback function */
450         pcap_loop(descr, packet_count, got_packet, NULL);
451         pcap_close(descr);
452
453         if (filter_tree) rbtree_free(filter_tree);
454
455         DEBUG("Done sniffing\n");
456         return 0;
457 }