Die bubble die (radsniff)
[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 RCSID("$Id$")
25
26 #define _LIBRADIUS 1
27 #include <freeradius-devel/libradius.h>
28
29 #include <pcap.h>
30
31 #include <freeradius-devel/radpaths.h>
32 #include <freeradius-devel/conf.h>
33 #include <freeradius-devel/radsniff.h>
34
35 static char const *radius_secret = "testing123";
36 static VALUE_PAIR *filter_vps = NULL;
37
38 static int do_sort = 0;
39 static int to_stdout = 0;
40 static FILE *log_dst;
41
42 #ifndef PCAP_NETMASK_UNKNOWN
43 #  define PCAP_NETMASK_UNKNOWN 0
44 #endif
45
46 #undef DEBUG1
47 #define DEBUG1 if (fr_debug_flag > 2) fprintf
48 #undef DEBUG
49 #define DEBUG if (fr_debug_flag > 1) fprintf
50 #undef INFO
51 #define INFO if (fr_debug_flag > 0) fprintf
52
53 struct timeval start_pcap = {0, 0};
54 static rbtree_t *filter_tree = NULL;
55 static rbtree_t *request_tree = NULL;
56 static pcap_dumper_t *out = NULL;
57 static RADIUS_PACKET *nullpacket = NULL;
58
59 typedef int (*rbcmp)(void const *, void const *);
60
61 static char const *radsniff_version = "radsniff version " RADIUSD_VERSION_STRING
62 #ifdef RADIUSD_VERSION_COMMIT
63 " (git #" RADIUSD_VERSION_COMMIT ")"
64 #endif
65 ", built on " __DATE__ " at " __TIME__;
66
67 static int filter_packet(RADIUS_PACKET *packet)
68 {
69         vp_cursor_t cursor, check_cursor;
70         VALUE_PAIR *check_item;
71         VALUE_PAIR *vp;
72         unsigned int pass, fail;
73         int compare;
74
75         pass = fail = 0;
76         for (vp = paircursor(&cursor, &packet->vps);
77              vp;
78              vp = pairnext(&cursor)) {
79                 for (check_item = paircursor(&check_cursor, &filter_vps);
80                      check_item;
81                      check_item = pairnext(&check_cursor))
82                         if ((check_item->da == vp->da)
83                          && (check_item->op != T_OP_SET)) {
84                                 compare = paircmp(check_item, vp);
85                                 if (compare == 1)
86                                         pass++;
87                                 else
88                                         fail++;
89                         }
90         }
91
92         if (fail == 0 && pass != 0) {
93                 /*
94                  *      Cache authentication requests, as the replies
95                  *      may not match the RADIUS filter.
96                  */
97                 if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
98                     (packet->code == PW_ACCOUNTING_REQUEST)) {
99                         rbtree_deletebydata(filter_tree, packet);
100                         
101                         if (!rbtree_insert(filter_tree, packet)) {
102                         oom:
103                                 fprintf(stderr, "radsniff: Out of memory\n");
104                                 exit(1);
105                         }
106                 }
107                 return 0;       /* matched */
108         }
109
110         /*
111          *      Don't create erroneous matches.
112          */
113         if ((packet->code == PW_AUTHENTICATION_REQUEST) ||
114             (packet->code == PW_ACCOUNTING_REQUEST)) {
115                 rbtree_deletebydata(filter_tree, packet);
116                 return 1;
117         }
118         
119         /*
120          *      Else see if a previous Access-Request
121          *      matched.  If so, also print out the
122          *      matching accept, reject, or challenge.
123          */
124         if ((packet->code == PW_AUTHENTICATION_ACK) ||
125             (packet->code == PW_AUTHENTICATION_REJECT) ||
126             (packet->code == PW_ACCESS_CHALLENGE) ||
127             (packet->code == PW_ACCOUNTING_RESPONSE)) {
128                 RADIUS_PACKET *reply;
129
130                 /*
131                  *      This swaps the various fields.
132                  */
133                 reply = rad_alloc_reply(NULL, packet);
134                 if (!reply) goto oom;
135                 
136                 compare = 1;
137                 if (rbtree_finddata(filter_tree, reply)) {
138                         compare = 0;
139                 }
140                 
141                 rad_free(&reply);
142                 return compare;
143         }
144         
145         return 1;
146 }
147
148 #define USEC 1000000
149 static void tv_sub(struct timeval const *end, struct timeval const *start,
150                    struct timeval *elapsed)
151 {
152         elapsed->tv_sec = end->tv_sec - start->tv_sec;
153         if (elapsed->tv_sec > 0) {
154                 elapsed->tv_sec--;
155                 elapsed->tv_usec = USEC;
156         } else {
157                 elapsed->tv_usec = 0;
158         }
159         elapsed->tv_usec += end->tv_usec;
160         elapsed->tv_usec -= start->tv_usec;
161         
162         if (elapsed->tv_usec >= USEC) {
163                 elapsed->tv_usec -= USEC;
164                 elapsed->tv_sec++;
165         }
166 }
167
168 static void got_packet(UNUSED uint8_t *args, struct pcap_pkthdr const*header, uint8_t const *data)
169 {
170
171         static int count = 1;                   /* Packets seen */
172         
173         /*
174          *  Define pointers for packet's attributes
175          */
176         const struct ip_header *ip;             /* The IP header */
177         const struct udp_header *udp;           /* The UDP header */
178         const uint8_t *payload;                 /* Packet payload */
179         
180         /*
181          *  And define the size of the structures we're using
182          */
183         int size_ethernet = sizeof(struct ethernet_header);
184         int size_ip = sizeof(struct ip_header);
185         int size_udp = sizeof(struct udp_header);
186         
187         /*
188          *  For FreeRADIUS
189          */
190         RADIUS_PACKET *packet, *original;
191         struct timeval elapsed;
192
193         /*
194          * Define our packet's attributes
195          */
196
197         if ((data[0] == 2) && (data[1] == 0) &&
198             (data[2] == 0) && (data[3] == 0)) {
199                 ip = (struct ip_header const *) (data + 4);
200
201         } else {
202                 ip = (struct ip_header const *)(data + size_ethernet);
203         }
204         
205         udp = (struct udp_header const *)(((uint8_t const *) ip) + size_ip);
206         payload = (uint8_t const *)(((uint8_t const *) udp) + size_udp);
207
208         packet = rad_alloc(NULL, 0);
209         if (!packet) {
210                 fprintf(stderr, "Out of memory\n");
211                 return;
212         }
213
214         packet->src_ipaddr.af = AF_INET;
215         packet->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr;
216         packet->src_port = ntohs(udp->udp_sport);
217         packet->dst_ipaddr.af = AF_INET;
218         packet->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr;
219         packet->dst_port = ntohs(udp->udp_dport);
220
221         memcpy(&packet->data, &payload, sizeof(packet->data));
222         packet->data_len = header->len - (payload - data);
223
224         if (!rad_packet_ok(packet, 0)) {
225                 DEBUG(log_dst, "Packet: %s\n", fr_strerror());
226                 
227                 DEBUG(log_dst, "  From     %s:%d\n", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
228                 DEBUG(log_dst, "  To:      %s:%d\n", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
229                 DEBUG(log_dst, "  Type:    %s\n", fr_packet_codes[packet->code]);
230
231                 rad_free(&packet);
232                 return;
233         }
234         
235         switch (packet->code) {
236         case PW_COA_REQUEST:
237                 /* we need a 16 x 0 byte vector for decrypting encrypted VSAs */
238                 original = nullpacket;
239                 break;
240         case PW_AUTHENTICATION_ACK:
241                 /* look for a matching request and use it for decoding */
242                 original = rbtree_finddata(request_tree, packet);
243                 break;
244         case PW_AUTHENTICATION_REQUEST:
245                 /* save the request for later matching */
246                 original = rad_alloc_reply(NULL, packet);
247                 if (original) { /* just ignore allocation failures */
248                         rbtree_deletebydata(request_tree, original);
249                         rbtree_insert(request_tree, original);
250                 }
251                 /* fallthrough */
252         default:
253                 /* don't attempt to decode any encrypted attributes */
254                 original = NULL;
255         }
256
257         /*
258          *  Decode the data without bothering to check the signatures.
259          */
260         if (rad_decode(packet, original, radius_secret) != 0) {
261                 rad_free(&packet);
262                 fr_perror("decode");
263                 return;
264         }
265
266         /*
267          *  We've seen a successfull reply to this, so delete it now
268          */
269         if (original)
270                 rbtree_deletebydata(request_tree, original);
271
272         if (filter_vps && filter_packet(packet)) {
273                 rad_free(&packet);
274                 DEBUG(log_dst, "Packet number %d doesn't match\n", count++);
275                 return;
276         }
277
278         if (out) {
279                 pcap_dump((void *) out, header, data);
280                 goto check_filter;
281         }
282
283         INFO(log_dst, "%s Id %d\t", fr_packet_codes[packet->code], packet->id);
284
285         /*
286          *  Print the RADIUS packet
287          */
288         INFO(log_dst, "%s:%d -> ", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport));
289         INFO(log_dst, "%s:%d", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport));
290         
291         DEBUG1(log_dst, "\t(%d packets)", count++);
292         
293         if (!start_pcap.tv_sec) {
294                 start_pcap = header->ts;
295         }
296
297         tv_sub(&header->ts, &start_pcap, &elapsed);
298
299         INFO(log_dst, "\t+%u.%03u", (unsigned int) elapsed.tv_sec,
300                (unsigned int) elapsed.tv_usec / 1000);
301         
302         if (fr_debug_flag > 1) {
303                 DEBUG(log_dst, "\n");
304                 if (packet->vps) {
305                         if (do_sort) {
306                                 pairsort(&packet->vps, true);
307                         }
308                         vp_printlist(log_dst, packet->vps);
309                         pairfree(&packet->vps);
310                 }
311         }
312         
313         INFO(log_dst, "\n");
314         
315         if (!to_stdout && (fr_debug_flag > 4)) {
316                 rad_print_hex(packet);
317         }
318         
319         fflush(log_dst);
320
321  check_filter:
322         /*
323          *  If we're doing filtering, Access-Requests are cached in the
324          *  filter tree.
325          */
326         if (!filter_vps ||
327             ((packet->code != PW_AUTHENTICATION_REQUEST) &&
328              (packet->code != PW_ACCOUNTING_REQUEST))) {
329                 rad_free(&packet);
330         }
331 }
332
333 static void NEVER_RETURNS usage(int status)
334 {
335         FILE *output = status ? stderr : stdout;
336         fprintf(output, "Usage: radsniff [options]\n");
337         fprintf(output, "options:\n");
338         fprintf(output, "  -c <count>      Number of packets to capture.\n");
339         fprintf(output, "  -d <directory>  Set dictionary directory.\n");
340         fprintf(output, "  -F              Filter PCAP file from stdin to stdout.\n");
341         fprintf(output, "  -f <filter>     PCAP filter (default is 'udp port <port> or <port + 1> or 3799')\n");
342         fprintf(output, "  -h              This help message.\n");
343         fprintf(output, "  -i <interface>  Capture packets from interface (defaults to any if supported).\n");
344         fprintf(output, "  -I <file>       Read packets from file (overrides input of -F).\n");
345         fprintf(output, "  -p <port>       Filter packets by port (default is 1812).\n");
346         fprintf(output, "  -q              Print less debugging information.\n");
347         fprintf(output, "  -r <filter>     RADIUS attribute filter.\n");
348         fprintf(output, "  -s <secret>     RADIUS secret.\n");
349         fprintf(output, "  -S              Sort attributes in the packet (useful for diffing responses).\n");
350         fprintf(output, "  -v              Show program version information.\n");
351         fprintf(output, "  -w <file>       Write output packets to file (overrides output of -F).\n");
352         fprintf(output, "  -x              Print more debugging information (defaults to -xx).\n");
353         exit(status);
354 }
355
356 int main(int argc, char *argv[])
357 {
358         char const *from_dev = NULL;                    /* Capture from device */
359         char const *from_file = NULL;                   /* Read from pcap file */
360         int from_stdin = 0;                             /* Read from stdin */
361         
362         pcap_t *in = NULL;                              /* PCAP input handle */
363         
364         int limit = -1;                                 /* How many packets to sniff */
365         
366         char errbuf[PCAP_ERRBUF_SIZE];                  /* Error buffer */
367
368         char *to_file = NULL;                           /* PCAP output file */
369         
370         char *pcap_filter = NULL;                       /* PCAP filter string */
371         char *radius_filter = NULL;
372         int port = 1812;
373         
374         struct bpf_program fp;                          /* Holds compiled filter */
375         bpf_u_int32 ip_mask = PCAP_NETMASK_UNKNOWN;     /* Device Subnet mask */
376         bpf_u_int32 ip_addr = 0;                        /* Device IP */
377         
378         char buffer[1024];
379
380         int opt;
381         FR_TOKEN parsecode;
382         char const *radius_dir = RADIUS_DIR;
383         
384         fr_debug_flag = 2;
385         log_dst = stdout;
386
387         talloc_set_log_stderr();
388         
389         /*
390          *  Get options
391          */
392         while ((opt = getopt(argc, argv, "c:d:Ff:hi:I:p:qr:s:Svw:xX")) != EOF) {
393                 switch (opt) {
394                 case 'c':
395                         limit = atoi(optarg);
396                         if (limit <= 0) {
397                                 fprintf(stderr, "radsniff: Invalid number of packets \"%s\"\n", optarg);
398                                 exit(1);
399                         }
400                         break;
401                 case 'd':
402                         radius_dir = optarg;
403                         break;
404                 case 'F':
405                         from_stdin = 1;
406                         to_stdout = 1;
407                         break;
408                 case 'f':
409                         pcap_filter = optarg;
410                         break;
411                 case 'h':
412                         usage(0);
413                         break;
414                 case 'i':
415                         from_dev = optarg;
416                         break;
417                 case 'I':
418                         from_file = optarg;
419                         break;
420                 case 'p':
421                         port = atoi(optarg);
422                         break;
423                 case 'q':
424                         if (fr_debug_flag > 0) {
425                                 fr_debug_flag--;
426                         }
427                         break;
428                 case 'r':
429                         radius_filter = optarg;
430                         break;
431                 case 's':
432                         radius_secret = optarg;
433                         break;
434                 case 'S':
435                         do_sort = 1;
436                         break;
437                 case 'v':
438                         INFO(log_dst, "%s %s\n", radsniff_version, pcap_lib_version());
439                         exit(0);
440                         break;
441                 case 'w':
442                         to_file = optarg;
443                         break;
444                 case 'x':
445                 case 'X':
446                         fr_debug_flag++;
447                         break;
448                 default:
449                         usage(64);
450                 }
451         }
452         
453         /* What's the point in specifying -F ?! */
454         if (from_stdin && from_file && to_file) {
455                 usage(64);
456         }
457         
458         /* Can't read from both... */
459         if (from_file && from_dev) {
460                 usage(64);
461         }
462         
463         /* Reading from file overrides stdin */
464         if (from_stdin && (from_file || from_dev)) {
465                 from_stdin = 0;
466         }
467         
468         /* Writing to file overrides stdout */
469         if (to_file && to_stdout) {
470                 to_stdout = 0;
471         }
472         
473         /*
474          *  If were writing pcap data stdout we *really* don't want to send
475          *  logging there as well.
476          */
477         log_dst = to_stdout ? stderr : stdout;
478
479 #if !defined(HAVE_PCAP_FOPEN_OFFLINE) || !defined(HAVE_PCAP_DUMP_FOPEN)
480         if (from_stdin || to_stdout) {
481                 fprintf(stderr, "radsniff: PCAP streams not supported.\n");
482                 exit(64);
483         }
484 #endif
485
486         if (!pcap_filter) {
487                 pcap_filter = buffer;
488                 snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d",
489                          port, port + 1, 3799);
490         }
491         
492         /*
493          *  There are times when we don't need the dictionaries.
494          */
495         if (!to_stdout) {
496                 if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
497                         fr_perror("radsniff");
498                         exit(64);
499                 }
500         }
501
502         if (radius_filter) {
503                 parsecode = userparse(NULL, radius_filter, &filter_vps);
504                 if (parsecode == T_OP_INVALID) {
505                         fprintf(stderr, "radsniff: Invalid RADIUS filter \"%s\" (%s)\n", radius_filter, fr_strerror());
506                         exit(64);
507                 }
508                 
509                 if (!filter_vps) {
510                         fprintf(stderr, "radsniff: Empty RADIUS filter \"%s\"\n", radius_filter);
511                         exit(64);
512                 }
513
514                 filter_tree = rbtree_create((rbcmp) fr_packet_cmp, free, 0);
515                 if (!filter_tree) {
516                         fprintf(stderr, "radsniff: Failed creating filter tree\n");
517                         exit(1);
518                 }
519         }
520
521         /*
522          *  Setup the request tree
523          */
524         request_tree = rbtree_create((rbcmp) fr_packet_cmp, free, 0);
525         if (!request_tree) {
526                 fprintf(stderr, "radsniff: Failed creating request tree\n");
527                 exit(1);
528         }
529
530         /*
531          *  Allocate a null packet for decrypting attributes in CoA requests
532          */
533         nullpacket = rad_alloc(NULL, 0);
534         if (!nullpacket) {
535                 fprintf(stderr, "radsniff: Out of memory\n");
536                 exit(1);
537         }
538
539         /*
540          *  Get the default capture device
541          */
542         if (!from_stdin && !from_file && !from_dev) {
543                 from_dev = pcap_lookupdev(errbuf);
544                 if (!from_dev) {
545                         fprintf(stderr, "radsniff: Failed discovering default interface (%s)\n", errbuf);
546                         exit(1);
547                 }
548
549                 INFO(log_dst, "Capturing from interface \"%s\"\n", from_dev);
550         }
551         
552         /*
553          *  Print captures values which will be used
554          */
555         if (fr_debug_flag > 2) {
556                                 DEBUG1(log_dst, "Sniffing with options:\n");
557                 if (from_dev)   DEBUG1(log_dst, "  Device                   : [%s]\n", from_dev);
558                 if (limit > 0)  DEBUG1(log_dst, "  Capture limit (packets)  : [%d]\n", limit);
559                                 DEBUG1(log_dst, "  PCAP filter              : [%s]\n", pcap_filter);
560                                 DEBUG1(log_dst, "  RADIUS secret            : [%s]\n", radius_secret);
561                 if (filter_vps){DEBUG1(log_dst, "  RADIUS filter            :\n");
562                         vp_printlist(log_dst, filter_vps);
563                 }
564         }
565
566         /*
567          *  Figure out whether were doing a reading from a file, doing a live
568          *  capture or reading from stdin.
569          */
570         if (from_file) {
571                 in = pcap_open_offline(from_file, errbuf);
572 #ifdef HAVE_PCAP_FOPEN_OFFLINE
573         } else if (from_stdin) {
574                 in = pcap_fopen_offline(stdin, errbuf);
575 #endif
576         } else if (from_dev) {
577                 pcap_lookupnet(from_dev, &ip_addr, &ip_mask, errbuf);
578                 in = pcap_open_live(from_dev, 65536, 1, 1, errbuf);
579         } else {
580                 fprintf(stderr, "radsniff: No capture devices available\n");
581         }
582         
583         if (!in) {
584                 fprintf(stderr, "radsniff: Failed opening input (%s)\n", errbuf);
585                 exit(1);
586         }
587
588         if (to_file) {
589                 out = pcap_dump_open(in, to_file);
590                 if (!out) {
591                         fprintf(stderr, "radsniff: Failed opening output file (%s)\n", pcap_geterr(in));
592                         exit(1);
593                 }
594 #ifdef HAVE_PCAP_DUMP_FOPEN
595         } else if (to_stdout) {
596                 out = pcap_dump_fopen(in, stdout);
597                 if (!out) {
598                         fprintf(stderr, "radsniff: Failed opening stdout (%s)\n", pcap_geterr(in));
599                         exit(1);
600                 }
601 #endif
602         }
603
604         /*
605          *  Apply the rules
606          */
607         if (pcap_compile(in, &fp, pcap_filter, 0, ip_mask) < 0) {
608                 fprintf(stderr, "radsniff: Failed compiling PCAP filter (%s)\n", pcap_geterr(in));
609                 exit(1);
610         }
611         
612         if (pcap_setfilter(in, &fp) < 0) {
613                 fprintf(stderr, "radsniff: Failed applying PCAP filter (%s)\n", pcap_geterr(in));
614                 exit(1);
615         }
616
617         /*
618          *  Enter the main capture loop...
619          */
620         pcap_loop(in, limit, got_packet, NULL);
621         
622         /*
623          *  ...were done capturing.
624          */
625         pcap_close(in);
626         if (out) {
627                 pcap_dump_close(out);
628         }
629         
630         if (filter_tree) {
631                 rbtree_free(filter_tree);
632         }
633         
634         INFO(log_dst, "Done sniffing\n");
635         
636         return 0;
637 }