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