Add version consistency checks between applications, libfreeradius-radius, libfreerad...
[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 bool do_sort = false;
39 static bool to_stdout = false;
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 #" STRINGIFY(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 successful 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
334 /** Wrapper function to allow rad_free to be called as an rbtree destructor callback
335  *
336  * @param packet to free.
337  */
338 static void _rb_rad_free(void *packet)
339 {
340         rad_free((RADIUS_PACKET **) &packet);
341 }
342
343 static void NEVER_RETURNS usage(int status)
344 {
345         FILE *output = status ? stderr : stdout;
346         fprintf(output, "Usage: radsniff [options]\n");
347         fprintf(output, "options:\n");
348         fprintf(output, "  -c <count>      Number of packets to capture.\n");
349         fprintf(output, "  -d <directory>  Set dictionary directory.\n");
350         fprintf(output, "  -F              Filter PCAP file from stdin to stdout.\n");
351         fprintf(output, "  -f <filter>     PCAP filter (default is 'udp port <port> or <port + 1> or 3799')\n");
352         fprintf(output, "  -h              This help message.\n");
353         fprintf(output, "  -i <interface>  Capture packets from interface (defaults to any if supported).\n");
354         fprintf(output, "  -I <file>       Read packets from file (overrides input of -F).\n");
355         fprintf(output, "  -p <port>       Filter packets by port (default is 1812).\n");
356         fprintf(output, "  -q              Print less debugging information.\n");
357         fprintf(output, "  -r <filter>     RADIUS attribute filter.\n");
358         fprintf(output, "  -s <secret>     RADIUS secret.\n");
359         fprintf(output, "  -S              Sort attributes in the packet (useful for diffing responses).\n");
360         fprintf(output, "  -v              Show program version information.\n");
361         fprintf(output, "  -w <file>       Write output packets to file (overrides output of -F).\n");
362         fprintf(output, "  -x              Print more debugging information (defaults to -xx).\n");
363         exit(status);
364 }
365
366 int main(int argc, char *argv[])
367 {
368         char const *from_dev = NULL;                    /* Capture from device */
369         char const *from_file = NULL;                   /* Read from pcap file */
370         bool from_stdin = false;                        /* Read from stdin */
371
372         pcap_t *in = NULL;                              /* PCAP input handle */
373
374         int limit = -1;                                 /* How many packets to sniff */
375
376         char errbuf[PCAP_ERRBUF_SIZE];                  /* Error buffer */
377
378         char *to_file = NULL;                           /* PCAP output file */
379
380         char *pcap_filter = NULL;                       /* PCAP filter string */
381         char *radius_filter = NULL;
382         int port = 1812;
383
384         struct bpf_program fp;                          /* Holds compiled filter */
385         bpf_u_int32 ip_mask = PCAP_NETMASK_UNKNOWN;     /* Device Subnet mask */
386         bpf_u_int32 ip_addr = 0;                        /* Device IP */
387
388         char buffer[1024];
389
390         int opt;
391         FR_TOKEN parsecode;
392         char const *radius_dir = RADIUS_DIR;
393
394         fr_debug_flag = 2;
395         log_dst = stdout;
396
397         talloc_set_log_stderr();
398
399         /*
400          *  Get options
401          */
402         while ((opt = getopt(argc, argv, "c:d:Ff:hi:I:p:qr:s:Svw:xX")) != EOF) {
403                 switch (opt) {
404                 case 'c':
405                         limit = atoi(optarg);
406                         if (limit <= 0) {
407                                 fprintf(stderr, "radsniff: Invalid number of packets \"%s\"\n", optarg);
408                                 exit(1);
409                         }
410                         break;
411                 case 'd':
412                         radius_dir = optarg;
413                         break;
414                 case 'F':
415                         from_stdin = true;
416                         to_stdout = true;
417                         break;
418                 case 'f':
419                         pcap_filter = optarg;
420                         break;
421                 case 'h':
422                         usage(0);
423                         break;
424                 case 'i':
425                         from_dev = optarg;
426                         break;
427                 case 'I':
428                         from_file = optarg;
429                         break;
430                 case 'p':
431                         port = atoi(optarg);
432                         break;
433                 case 'q':
434                         if (fr_debug_flag > 0) {
435                                 fr_debug_flag--;
436                         }
437                         break;
438                 case 'r':
439                         radius_filter = optarg;
440                         break;
441                 case 's':
442                         radius_secret = optarg;
443                         break;
444                 case 'S':
445                         do_sort = true;
446                         break;
447                 case 'v':
448                         INFO(log_dst, "%s %s\n", radsniff_version, pcap_lib_version());
449                         exit(0);
450                         break;
451                 case 'w':
452                         to_file = optarg;
453                         break;
454                 case 'x':
455                 case 'X':
456                         fr_debug_flag++;
457                         break;
458                 default:
459                         usage(64);
460                 }
461         }
462
463         /*
464          *      Mismatch between the binary and the libraries it depends on
465          */
466         if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
467                 fr_perror("radsniff");
468                 exit(1);
469         }
470
471         /* What's the point in specifying -F ?! */
472         if (from_stdin && from_file && to_file) {
473                 usage(64);
474         }
475
476         /* Can't read from both... */
477         if (from_file && from_dev) {
478                 usage(64);
479         }
480
481         /* Reading from file overrides stdin */
482         if (from_stdin && (from_file || from_dev)) {
483                 from_stdin = false;
484         }
485
486         /* Writing to file overrides stdout */
487         if (to_file && to_stdout) {
488                 to_stdout = false;
489         }
490
491         /*
492          *  If were writing pcap data stdout we *really* don't want to send
493          *  logging there as well.
494          */
495         log_dst = to_stdout ? stderr : stdout;
496
497 #if !defined(HAVE_PCAP_FOPEN_OFFLINE) || !defined(HAVE_PCAP_DUMP_FOPEN)
498         if (from_stdin || to_stdout) {
499                 fprintf(stderr, "radsniff: PCAP streams not supported.\n");
500                 exit(64);
501         }
502 #endif
503
504         if (!pcap_filter) {
505                 pcap_filter = buffer;
506                 snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d",
507                          port, port + 1, 3799);
508         }
509
510         /*
511          *  There are times when we don't need the dictionaries.
512          */
513         if (!to_stdout) {
514                 if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
515                         fr_perror("radsniff");
516                         exit(64);
517                 }
518         }
519
520         if (radius_filter) {
521                 parsecode = userparse(NULL, radius_filter, &filter_vps);
522                 if (parsecode == T_OP_INVALID) {
523                         fprintf(stderr, "radsniff: Invalid RADIUS filter \"%s\" (%s)\n", radius_filter, fr_strerror());
524                         exit(64);
525                 }
526
527                 if (!filter_vps) {
528                         fprintf(stderr, "radsniff: Empty RADIUS filter \"%s\"\n", radius_filter);
529                         exit(64);
530                 }
531
532                 filter_tree = rbtree_create((rbcmp) fr_packet_cmp, _rb_rad_free, 0);
533                 if (!filter_tree) {
534                         fprintf(stderr, "radsniff: Failed creating filter tree\n");
535                         exit(1);
536                 }
537         }
538
539         /*
540          *  Setup the request tree
541          */
542         request_tree = rbtree_create((rbcmp) fr_packet_cmp, _rb_rad_free, 0);
543         if (!request_tree) {
544                 fprintf(stderr, "radsniff: Failed creating request tree\n");
545                 exit(1);
546         }
547
548         /*
549          *  Allocate a null packet for decrypting attributes in CoA requests
550          */
551         nullpacket = rad_alloc(NULL, 0);
552         if (!nullpacket) {
553                 fprintf(stderr, "radsniff: Out of memory\n");
554                 exit(1);
555         }
556
557         /*
558          *  Get the default capture device
559          */
560         if (!from_stdin && !from_file && !from_dev) {
561                 from_dev = pcap_lookupdev(errbuf);
562                 if (!from_dev) {
563                         fprintf(stderr, "radsniff: Failed discovering default interface (%s)\n", errbuf);
564                         exit(1);
565                 }
566
567                 INFO(log_dst, "Capturing from interface \"%s\"\n", from_dev);
568         }
569
570         /*
571          *  Print captures values which will be used
572          */
573         if (fr_debug_flag > 2) {
574                                 DEBUG1(log_dst, "Sniffing with options:\n");
575                 if (from_dev)   DEBUG1(log_dst, "  Device                   : [%s]\n", from_dev);
576                 if (limit > 0)  DEBUG1(log_dst, "  Capture limit (packets)  : [%d]\n", limit);
577                                 DEBUG1(log_dst, "  PCAP filter              : [%s]\n", pcap_filter);
578                                 DEBUG1(log_dst, "  RADIUS secret            : [%s]\n", radius_secret);
579                 if (filter_vps){DEBUG1(log_dst, "  RADIUS filter            :\n");
580                         vp_printlist(log_dst, filter_vps);
581                 }
582         }
583
584         /*
585          *  Figure out whether were doing a reading from a file, doing a live
586          *  capture or reading from stdin.
587          */
588         if (from_file) {
589                 in = pcap_open_offline(from_file, errbuf);
590 #ifdef HAVE_PCAP_FOPEN_OFFLINE
591         } else if (from_stdin) {
592                 in = pcap_fopen_offline(stdin, errbuf);
593 #endif
594         } else if (from_dev) {
595                 pcap_lookupnet(from_dev, &ip_addr, &ip_mask, errbuf);
596                 in = pcap_open_live(from_dev, 65536, 1, 1, errbuf);
597         } else {
598                 fprintf(stderr, "radsniff: No capture devices available\n");
599         }
600
601         if (!in) {
602                 fprintf(stderr, "radsniff: Failed opening input (%s)\n", errbuf);
603                 exit(1);
604         }
605
606         if (to_file) {
607                 out = pcap_dump_open(in, to_file);
608                 if (!out) {
609                         fprintf(stderr, "radsniff: Failed opening output file (%s)\n", pcap_geterr(in));
610                         exit(1);
611                 }
612 #ifdef HAVE_PCAP_DUMP_FOPEN
613         } else if (to_stdout) {
614                 out = pcap_dump_fopen(in, stdout);
615                 if (!out) {
616                         fprintf(stderr, "radsniff: Failed opening stdout (%s)\n", pcap_geterr(in));
617                         exit(1);
618                 }
619 #endif
620         }
621
622         /*
623          *  Apply the rules
624          */
625         if (pcap_compile(in, &fp, pcap_filter, 0, ip_mask) < 0) {
626                 fprintf(stderr, "radsniff: Failed compiling PCAP filter (%s)\n", pcap_geterr(in));
627                 exit(1);
628         }
629
630         if (pcap_setfilter(in, &fp) < 0) {
631                 fprintf(stderr, "radsniff: Failed applying PCAP filter (%s)\n", pcap_geterr(in));
632                 exit(1);
633         }
634
635         /*
636          *  Enter the main capture loop...
637          */
638         pcap_loop(in, limit, got_packet, NULL);
639
640         /*
641          *  ...were done capturing.
642          */
643         pcap_close(in);
644         if (out) {
645                 pcap_dump_close(out);
646         }
647
648         if (filter_tree) {
649                 rbtree_free(filter_tree);
650         }
651
652         INFO(log_dst, "Done sniffing\n");
653
654         return 0;
655 }