radsniff: decoding encrypted attributes
[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 static rbtree_t *request_tree = NULL;
46 static pcap_dumper_t *pcap_dumper = NULL;
47 static RADIUS_PACKET *nullpacket = NULL;
48
49 typedef int (*rbcmp)(const void *, const void *);
50
51 static int filter_packet(RADIUS_PACKET *packet)
52 {
53         VALUE_PAIR *check_item;
54         VALUE_PAIR *vp;
55         unsigned int pass, fail;
56         int compare;
57
58         pass = fail = 0;
59         for (vp = packet->vps; vp != NULL; vp = vp->next) {
60                 for (check_item = filter_vps;
61                      check_item != NULL;
62                      check_item = check_item->next)
63                         if ((check_item->attribute == vp->attribute)
64                          && (check_item->operator != T_OP_SET)) {
65                                 compare = paircmp(check_item, vp);
66                                 if (compare == 1)
67                                         pass++;
68                                 else
69                                         fail++;
70                         }
71         }
72
73
74         if (fail == 0 && pass != 0) {
75                 /*
76                  *      Cache authentication requests, as the replies
77                  *      may not match the RADIUS filter.
78                  */
79                 if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
80                     (packet->code == PW_ACCOUNTING_REQUEST)) {
81                         rbtree_deletebydata(filter_tree, packet);
82                         
83                         if (!rbtree_insert(filter_tree, packet)) {
84                         oom:
85                                 fprintf(stderr, "radsniff: Out of memory\n");
86                                 exit(1);
87                         }
88                 }
89                 return 0;       /* matched */
90         }
91
92         /*
93          *      Don't create erroneous matches.
94          */
95         if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
96             (packet->code == PW_ACCOUNTING_REQUEST)) {
97                 rbtree_deletebydata(filter_tree, packet);
98                 return 1;
99         }
100         
101         /*
102          *      Else see if a previous Access-Request
103          *      matched.  If so, also print out the
104          *      matching accept, reject, or challenge.
105          */
106         if ((packet->code == PW_AUTHENTICATION_ACK) ||
107             (packet->code == PW_AUTHENTICATION_REJECT) ||
108             (packet->code == PW_ACCESS_CHALLENGE) ||
109             (packet->code == PW_ACCOUNTING_RESPONSE)) {
110                 RADIUS_PACKET *reply;
111
112                 /*
113                  *      This swaps the various fields.
114                  */
115                 reply = rad_alloc_reply(packet);
116                 if (!reply) goto oom;
117                 
118                 compare = 1;
119                 if (rbtree_finddata(filter_tree, reply)) {
120                         compare = 0;
121                 }
122                 
123                 rad_free(&reply);
124                 return compare;
125         }
126         
127         return 1;
128 }
129
130 /*
131  *      Bubble goodness
132  */
133 static void sort(RADIUS_PACKET *packet)
134 {
135         int i, j, size;
136         VALUE_PAIR *vp, *tmp;
137         VALUE_PAIR *array[1024]; /* way more than necessary */
138
139         size = 0;
140         for (vp = packet->vps; vp != NULL; vp = vp->next) {
141                 array[size++] = vp;
142         }
143
144         if (size == 0) return;
145
146         for (i = 0; i < size - 1; i++)  {
147                 for (j = 0; j < size - 1 - i; j++) {
148                         if (array[j + 1]->attribute < array[j]->attribute)  {
149                                 tmp = array[j];         
150                                 array[j] = array[j + 1];
151                                 array[j + 1] = tmp;
152                         }
153                 }
154         }
155
156         /*
157          *      And put them back again.
158          */
159         vp = packet->vps = array[0];
160         for (i = 1; i < size; i++) {
161                 vp->next = array[i];
162                 vp = array[i];
163         }
164         vp->next = NULL;
165 }
166
167 #define USEC 1000000
168 static void tv_sub(const struct timeval *end, const struct timeval *start,
169                    struct timeval *elapsed)
170 {
171         elapsed->tv_sec = end->tv_sec - start->tv_sec;
172         if (elapsed->tv_sec > 0) {
173                 elapsed->tv_sec--;
174                 elapsed->tv_usec = USEC;
175         } else {
176                 elapsed->tv_usec = 0;
177         }
178         elapsed->tv_usec += end->tv_usec;
179         elapsed->tv_usec -= start->tv_usec;
180         
181         if (elapsed->tv_usec >= USEC) {
182                 elapsed->tv_usec -= USEC;
183                 elapsed->tv_sec++;
184         }
185 }
186
187 static void got_packet(uint8_t *args, const struct pcap_pkthdr *header, const uint8_t *data)
188 {
189         /* Just a counter of how many packets we've had */
190         static int count = 1;
191         /* Define pointers for packet's attributes */
192         const struct ethernet_header *ethernet;  /* The ethernet header */
193         const struct ip_header *ip;              /* The IP header */
194         const struct udp_header *udp;            /* The UDP header */
195         const uint8_t *payload;                     /* Packet payload */
196         /* And define the size of the structures we're using */
197         int size_ethernet = sizeof(struct ethernet_header);
198         int size_ip = sizeof(struct ip_header);
199         int size_udp = sizeof(struct udp_header);
200         /* For FreeRADIUS */
201         RADIUS_PACKET *packet, *original;
202         struct timeval elapsed;
203
204         args = args;            /* -Wunused */
205
206         /* Define our packet's attributes */
207
208         if ((data[0] == 2) && (data[1] == 0) &&
209             (data[2] == 0) && (data[3] == 0)) {
210                 ip = (const struct ip_header*) (data + 4);
211
212         } else {
213                 ethernet = (const struct ethernet_header*)(data);
214                 ip = (const struct ip_header*)(data + size_ethernet);
215         }
216         udp = (const struct udp_header*)(((const uint8_t *) ip) + size_ip);
217         payload = (const uint8_t *)(((const uint8_t *) udp) + size_udp);
218
219         packet = malloc(sizeof(*packet));
220         if (!packet) {
221                 fprintf(stderr, "Out of memory\n");
222                 return;
223         }
224
225         memset(packet, 0, sizeof(*packet));
226         packet->src_ipaddr.af = AF_INET;
227         packet->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr;
228         packet->src_port = ntohs(udp->udp_sport);
229         packet->dst_ipaddr.af = AF_INET;
230         packet->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr;
231         packet->dst_port = ntohs(udp->udp_dport);
232
233         packet->data = payload;
234         packet->data_len = header->len - (payload - data);
235
236         if (!rad_packet_ok(packet, 0)) {
237                 printf("Packet: %s\n", fr_strerror());
238                 
239                 printf("\tFrom:    %s:%d\n", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
240                 printf("\tTo:      %s:%d\n", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
241                 printf("\tType:    %s\n", fr_packet_codes[packet->code]);
242
243                 free(packet);
244                 return;
245         }
246         
247         switch (packet->code) {
248         case PW_COA_REQUEST:
249                 /* we need a 16 x 0 byte vector for decrypting encrypted VSAs */
250                 original = nullpacket;
251                 break;
252         case PW_AUTHENTICATION_ACK:
253                 /* look for a matching request and use it for decoding */
254                 original = rbtree_finddata(request_tree, packet);
255                 break;
256         case PW_AUTHENTICATION_REQUEST:
257                 /* save the request for later matching */
258                 original = rad_alloc_reply(packet);
259                 if (original) { /* just ignore allocation failures */
260                         rbtree_deletebydata(request_tree, original);
261                         rbtree_insert(request_tree, original);
262                 }
263                 /* fallthrough */
264         default:
265                 /* don't attempt to decode any encrypted attributes */
266                 original = NULL;
267         }
268
269         /*
270          *      Decode the data without bothering to check the signatures.
271          */
272         if (rad_decode(packet, original, radius_secret) != 0) {
273                 free(packet);
274                 fr_perror("decode");
275                 return;
276         }
277
278         /* we've seen a successfull reply to this, so delete it now */
279         if (original)
280                 rbtree_deletebydata(request_tree, original);
281
282         if (filter_vps && filter_packet(packet)) {
283                 free(packet);
284                 DEBUG("Packet number %d doesn't match\n", count++);
285                 return;
286         }
287
288         if (pcap_dumper) {
289                 pcap_dump((void *) pcap_dumper, header, data);
290                 goto check_filter;
291         }
292
293         printf("%s Id %d\t", fr_packet_codes[packet->code], packet->id);
294
295         /* Print the RADIUS packet */
296         printf("%s:%d -> ", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
297         printf("%s:%d", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
298         if (fr_debug_flag) printf("\t(%d packets)", count++);
299
300         if (!start_pcap.tv_sec) {
301                 start_pcap = header->ts;
302         }
303
304         tv_sub(&header->ts, &start_pcap, &elapsed);
305
306         printf("\t+%u.%03u", (unsigned int) elapsed.tv_sec,
307                (unsigned int) elapsed.tv_usec / 1000);
308         if (!minimal) printf("\n");
309         if (!minimal && packet->vps) {
310                 if (do_sort) sort(packet);
311
312                 vp_printlist(stdout, packet->vps);
313                 pairfree(&packet->vps);
314         }
315         printf("\n");
316         if (fr_debug_flag > 2) rad_print_hex(packet);
317         fflush(stdout);
318
319  check_filter:
320         /*
321          *      If we're doing filtering, Access-Requests are cached
322          *      in the filter tree.
323          */
324         if (!filter_vps ||
325             ((packet->code != PW_AUTHENTICATION_REQUEST) &&
326              (packet->code != PW_ACCOUNTING_REQUEST))) {
327                 free(packet);
328         }
329 }
330
331 static void NEVER_RETURNS usage(int status)
332 {
333         FILE *output = status ? stderr : stdout;
334         fprintf(output, "usage: radsniff [options]\n");
335         fprintf(output, "options:\n");
336         fprintf(output, "\t-c count\tNumber of packets to capture.\n");
337         fprintf(output, "\t-d directory\tDirectory where the dictionaries are found\n");
338         fprintf(output, "\t-F\t\tFilter PCAP file from stdin to stdout.\n");
339         fprintf(output, "\t\t\tOutput file will contain RADIUS packets.\n");
340         fprintf(output, "\t-f filter\tPCAP filter. (default is udp port 1812 or 1813)\n");
341         fprintf(output, "\t-h\t\tPrint this help message.\n");
342         fprintf(output, "\t-i interface\tInterface to capture.\n");
343         fprintf(output, "\t-I filename\tRead packets from filename.\n");
344         fprintf(output, "\t-m\t\tPrint packet headers only, not contents.\n");
345         fprintf(output, "\t-p port\t\tListen for packets on port.\n");
346         fprintf(output, "\t-r filter\tRADIUS attribute filter.\n");
347         fprintf(output, "\t-s secret\tRADIUS secret.\n");
348         fprintf(output, "\t-S\t\tSort attributes in the packet.\n");
349         fprintf(output, "\t\t\tUsed to compare server results.\n");
350         fprintf(output, "\t-w file\tWrite output packets to file.\n");
351         fprintf(output, "\t-x\t\tPrint out debugging information.\n");
352         exit(status);
353 }
354
355 int main(int argc, char *argv[])
356 {
357         char *dev;                      /* sniffing device */
358         char errbuf[PCAP_ERRBUF_SIZE];  /* error buffer */
359         pcap_t *descr;                  /* sniff handler */
360         struct bpf_program fp;          /* hold compiled program */
361         bpf_u_int32 maskp;              /* subnet mask */
362         bpf_u_int32 netp;               /* ip */
363         char buffer[1024];
364         char *pcap_filter = NULL;
365         char *radius_filter = NULL;
366         char *filename = NULL;
367         int printable_output = 1;
368         char *dump_file = NULL;
369         int packet_count = -1;          /* how many packets to sniff */
370         int opt;
371         FR_TOKEN parsecode;
372         const char *radius_dir = RADIUS_DIR;
373         int port = 1812;
374         int filter_stdin = 0;
375
376         /* Default device */
377         dev = pcap_lookupdev(errbuf);
378
379         /* Get options */
380         while ((opt = getopt(argc, argv, "c:d:Ff:hi:I:mp:r:s:Sw:xX")) != EOF) {
381                 switch (opt)
382                 {
383                 case 'c':
384                         packet_count = atoi(optarg);
385                         if (packet_count <= 0) {
386                                 fprintf(stderr, "radsniff: Invalid number of packets \"%s\"\n", optarg);
387                                 exit(1);
388                         }
389                         break;
390                 case 'd':
391                         radius_dir = optarg;
392                         break;
393                 case 'F':
394                         filter_stdin = 1;
395                         printable_output = 0;
396                         break;
397                 case 'f':
398                         pcap_filter = optarg;
399                         break;
400                 case 'h':
401                         usage(0);
402                         break;
403                 case 'i':
404                         dev = optarg;
405                         break;
406                 case 'I':
407                         filename = optarg;
408                         break;
409                 case 'm':
410                         minimal = 1;
411                         break;
412                 case 'p':
413                         port = atoi(optarg);
414                         break;
415                 case 'r':
416                         radius_filter = optarg;
417                         break;
418                 case 's':
419                         radius_secret = optarg;
420                         break;
421                 case 'S':
422                         do_sort = 1;
423                         break;
424                 case 'w':
425                         dump_file = optarg;
426                         printable_output = 0;
427                         break;
428                 case 'x':
429                 case 'X':       /* for backwards compatibility */
430                         fr_debug_flag++;
431                         break;
432                 default:
433                         usage(1);
434                 }
435         }
436
437         /*
438          *      Cross-check command-line arguments.
439          */
440         if (filter_stdin && (filename || dump_file)) usage(1);
441
442 #ifndef HAVE_PCAP_FOPEN_OFFLINE
443         if (filter_stdin) {
444                 fr_perror("-F is unsupported on this platform.");
445                 return 1;
446         }
447 #endif
448
449         if (!pcap_filter) {
450                 pcap_filter = buffer;
451                 snprintf(buffer, sizeof(buffer), "udp port %d or %d",
452                          port, port + 1);
453         }
454         
455         /*
456          *      There are times when we don't need the dictionaries.
457          */
458         if (printable_output) {
459                 if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
460                         fr_perror("radsniff");
461                         return 1;
462                 }
463         }
464
465         if (radius_filter) {
466                 parsecode = userparse(radius_filter, &filter_vps);
467                 if (parsecode == T_OP_INVALID) {
468                         fprintf(stderr, "radsniff: Invalid RADIUS filter \"%s\": %s\n", radius_filter, fr_strerror());
469                         exit(1);
470                 }
471                 if (!filter_vps) {
472                         fprintf(stderr, "radsniff: Empty RADIUS filter \"%s\"\n", radius_filter);
473                         exit(1);
474                 }
475
476                 filter_tree = rbtree_create((rbcmp) fr_packet_cmp,
477                                             free, 0);
478                 if (!filter_tree) {
479                         fprintf(stderr, "radsniff: Failed creating filter tree\n");
480                         exit(1);
481                 }
482         }
483
484         /* setup the request tree */
485         request_tree = rbtree_create((rbcmp) fr_packet_cmp, free, 0);
486         if (!request_tree) {
487                 fprintf(stderr, "radsniff: Failed creating request tree\n");
488                 exit(1);
489         }
490
491         /* allocate a null packet for decrypting attributes in CoA requests */
492         nullpacket = rad_alloc(0);
493         if (!nullpacket) {
494                 fprintf(stderr, "radsniff: Out of memory\n");
495                 exit(1);
496         }
497
498         /* Set our device */
499         pcap_lookupnet(dev, &netp, &maskp, errbuf);
500
501         /* Print device to the user */
502         if (fr_debug_flag) {
503                 if (dev) printf("Device: [%s]\n", dev);
504                 if (packet_count > 0) {
505                         printf("Num of packets: [%d]\n",
506                                packet_count);
507                 }
508                 printf("PCAP filter: [%s]\n", pcap_filter);
509                 if (filter_vps) {
510                         printf("RADIUS filter:\n");
511                         vp_printlist(stdout, filter_vps);
512                 }
513                 printf("RADIUS secret: [%s]\n", radius_secret);
514         }
515
516         /* Open the device so we can spy */
517         if (filename) {
518                 descr = pcap_open_offline(filename, errbuf);
519
520 #ifdef HAVE_PCAP_FOPEN_OFFLINE
521         } else if (filter_stdin) {
522                 descr = pcap_fopen_offline(stdin, errbuf);
523 #endif
524
525         } else if (!dev) {
526                 fprintf(stderr, "radsniff: No filename or device was specified.\n");
527                 exit(1);
528
529         } else {
530                 descr = pcap_open_live(dev, 65536, 1, 0, errbuf);
531         }
532         if (descr == NULL)
533         {
534                 fprintf(stderr, "radsniff: pcap_open_live failed (%s)\n", errbuf);
535                 exit(1);
536         }
537
538         if (dump_file) {
539                 pcap_dumper = pcap_dump_open(descr, dump_file);
540                 if (!pcap_dumper) {
541                         fprintf(stderr, "radsniff: Failed opening output file (%s)\n", pcap_geterr(descr));
542                         exit(1);
543                 }
544
545 #ifdef HAVE_PCAP_DUMP_FOPEN
546         } else if (filter_stdin) {
547                 pcap_dumper = pcap_dump_fopen(descr, stdout);
548                 if (!pcap_dumper) {
549                         fprintf(stderr, "radsniff: Failed opening stdout: %s\n", pcap_geterr(descr));
550                         exit(1);
551                 }
552
553 #endif
554         }
555
556         /* Apply the rules */
557         if( pcap_compile(descr, &fp, pcap_filter, 0, netp) == -1)
558         {
559                 fprintf(stderr, "radsniff: pcap_compile failed\n");
560                 exit(1);
561         }
562         if (pcap_setfilter(descr, &fp) == -1)
563         {
564                 fprintf(stderr, "radsniff: pcap_setfilter failed\n");
565                 exit(1);
566         }
567
568         /* Now we can set our callback function */
569         pcap_loop(descr, packet_count, got_packet, NULL);
570         pcap_close(descr);
571
572         if (filter_tree) rbtree_free(filter_tree);
573
574         DEBUG("Done sniffing\n");
575         return 0;
576 }