Add radpaths to radsniff.c
[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 <freeradius-devel/wrapper/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 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         int from_stdin = 0;                             /* 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 = 1;
416                         to_stdout = 1;
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 = 1;
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         /* What's the point in specifying -F ?! */
464         if (from_stdin && from_file && to_file) {
465                 usage(64);
466         }
467
468         /* Can't read from both... */
469         if (from_file && from_dev) {
470                 usage(64);
471         }
472
473         /* Reading from file overrides stdin */
474         if (from_stdin && (from_file || from_dev)) {
475                 from_stdin = 0;
476         }
477
478         /* Writing to file overrides stdout */
479         if (to_file && to_stdout) {
480                 to_stdout = 0;
481         }
482
483         /*
484          *  If were writing pcap data stdout we *really* don't want to send
485          *  logging there as well.
486          */
487         log_dst = to_stdout ? stderr : stdout;
488
489 #if !defined(HAVE_PCAP_FOPEN_OFFLINE) || !defined(HAVE_PCAP_DUMP_FOPEN)
490         if (from_stdin || to_stdout) {
491                 fprintf(stderr, "radsniff: PCAP streams not supported.\n");
492                 exit(64);
493         }
494 #endif
495
496         if (!pcap_filter) {
497                 pcap_filter = buffer;
498                 snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d",
499                          port, port + 1, 3799);
500         }
501
502         /*
503          *  There are times when we don't need the dictionaries.
504          */
505         if (!to_stdout) {
506                 if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
507                         fr_perror("radsniff");
508                         exit(64);
509                 }
510         }
511
512         if (radius_filter) {
513                 parsecode = userparse(NULL, radius_filter, &filter_vps);
514                 if (parsecode == T_OP_INVALID) {
515                         fprintf(stderr, "radsniff: Invalid RADIUS filter \"%s\" (%s)\n", radius_filter, fr_strerror());
516                         exit(64);
517                 }
518
519                 if (!filter_vps) {
520                         fprintf(stderr, "radsniff: Empty RADIUS filter \"%s\"\n", radius_filter);
521                         exit(64);
522                 }
523
524                 filter_tree = rbtree_create((rbcmp) fr_packet_cmp, _rb_rad_free, 0);
525                 if (!filter_tree) {
526                         fprintf(stderr, "radsniff: Failed creating filter tree\n");
527                         exit(1);
528                 }
529         }
530
531         /*
532          *  Setup the request tree
533          */
534         request_tree = rbtree_create((rbcmp) fr_packet_cmp, _rb_rad_free, 0);
535         if (!request_tree) {
536                 fprintf(stderr, "radsniff: Failed creating request tree\n");
537                 exit(1);
538         }
539
540         /*
541          *  Allocate a null packet for decrypting attributes in CoA requests
542          */
543         nullpacket = rad_alloc(NULL, 0);
544         if (!nullpacket) {
545                 fprintf(stderr, "radsniff: Out of memory\n");
546                 exit(1);
547         }
548
549         /*
550          *  Get the default capture device
551          */
552         if (!from_stdin && !from_file && !from_dev) {
553                 from_dev = pcap_lookupdev(errbuf);
554                 if (!from_dev) {
555                         fprintf(stderr, "radsniff: Failed discovering default interface (%s)\n", errbuf);
556                         exit(1);
557                 }
558
559                 INFO(log_dst, "Capturing from interface \"%s\"\n", from_dev);
560         }
561
562         /*
563          *  Print captures values which will be used
564          */
565         if (fr_debug_flag > 2) {
566                                 DEBUG1(log_dst, "Sniffing with options:\n");
567                 if (from_dev)   DEBUG1(log_dst, "  Device                   : [%s]\n", from_dev);
568                 if (limit > 0)  DEBUG1(log_dst, "  Capture limit (packets)  : [%d]\n", limit);
569                                 DEBUG1(log_dst, "  PCAP filter              : [%s]\n", pcap_filter);
570                                 DEBUG1(log_dst, "  RADIUS secret            : [%s]\n", radius_secret);
571                 if (filter_vps){DEBUG1(log_dst, "  RADIUS filter            :\n");
572                         vp_printlist(log_dst, filter_vps);
573                 }
574         }
575
576         /*
577          *  Figure out whether were doing a reading from a file, doing a live
578          *  capture or reading from stdin.
579          */
580         if (from_file) {
581                 in = pcap_open_offline(from_file, errbuf);
582 #ifdef HAVE_PCAP_FOPEN_OFFLINE
583         } else if (from_stdin) {
584                 in = pcap_fopen_offline(stdin, errbuf);
585 #endif
586         } else if (from_dev) {
587                 pcap_lookupnet(from_dev, &ip_addr, &ip_mask, errbuf);
588                 in = pcap_open_live(from_dev, 65536, 1, 1, errbuf);
589         } else {
590                 fprintf(stderr, "radsniff: No capture devices available\n");
591         }
592
593         if (!in) {
594                 fprintf(stderr, "radsniff: Failed opening input (%s)\n", errbuf);
595                 exit(1);
596         }
597
598         if (to_file) {
599                 out = pcap_dump_open(in, to_file);
600                 if (!out) {
601                         fprintf(stderr, "radsniff: Failed opening output file (%s)\n", pcap_geterr(in));
602                         exit(1);
603                 }
604 #ifdef HAVE_PCAP_DUMP_FOPEN
605         } else if (to_stdout) {
606                 out = pcap_dump_fopen(in, stdout);
607                 if (!out) {
608                         fprintf(stderr, "radsniff: Failed opening stdout (%s)\n", pcap_geterr(in));
609                         exit(1);
610                 }
611 #endif
612         }
613
614         /*
615          *  Apply the rules
616          */
617         if (pcap_compile(in, &fp, pcap_filter, 0, ip_mask) < 0) {
618                 fprintf(stderr, "radsniff: Failed compiling PCAP filter (%s)\n", pcap_geterr(in));
619                 exit(1);
620         }
621
622         if (pcap_setfilter(in, &fp) < 0) {
623                 fprintf(stderr, "radsniff: Failed applying PCAP filter (%s)\n", pcap_geterr(in));
624                 exit(1);
625         }
626
627         /*
628          *  Enter the main capture loop...
629          */
630         pcap_loop(in, limit, got_packet, NULL);
631
632         /*
633          *  ...were done capturing.
634          */
635         pcap_close(in);
636         if (out) {
637                 pcap_dump_close(out);
638         }
639
640         if (filter_tree) {
641                 rbtree_free(filter_tree);
642         }
643
644         INFO(log_dst, "Done sniffing\n");
645
646         return 0;
647 }