source: bootcd/isolinux/syslinux-6.03/gpxe/src/util/hijack.c @ 26ffad7

Last change on this file since 26ffad7 was e16e8f2, checked in by Edwin Eefting <edwin@datux.nl>, 3 years ago

bootstuff

  • Property mode set to 100644
File size: 13.7 KB
Line 
1#include <stdio.h>
2#include <stdlib.h>
3#include <unistd.h>
4#include <string.h>
5#include <stdarg.h>
6#include <errno.h>
7#include <fcntl.h>
8#include <libgen.h>
9#include <signal.h>
10#include <net/if.h>
11#include <net/ethernet.h>
12#include <sys/select.h>
13#include <sys/socket.h>
14#include <sys/stat.h>
15#include <sys/un.h>
16#include <syslog.h>
17#include <getopt.h>
18#include <pcap.h>
19
20#define SNAPLEN 1600
21
22/*
23 * FIXME: is there a way to detect the version of the libpcap library?
24 * Version 0.9 has pcap_inject; version 0.8 doesn't, but both report
25 * their version number as 2.4.
26 */
27#define HAVE_PCAP_INJECT 0
28
29struct hijack {
30        pcap_t *pcap;
31        int fd;
32        int datalink;
33        int filtered;
34        unsigned long rx_count;
35        unsigned long tx_count;
36};
37
38struct hijack_listener {
39        struct sockaddr_un sun;
40        int fd;
41};
42
43struct hijack_options {
44        char interface[IF_NAMESIZE];
45        int daemonise;
46};
47
48static int daemonised = 0;
49
50static int signalled = 0;
51
52static void flag_signalled ( int signal __attribute__ (( unused )) ) {
53        signalled = 1;
54}
55
56#if ! HAVE_PCAP_INJECT
57/**
58 * Substitute for pcap_inject(), if this version of libpcap doesn't
59 * have it.  Will almost certainly only work under Linux.
60 *
61 */
62int pcap_inject ( pcap_t *pcap, const void *data, size_t len ) {
63        int fd;
64        char *errbuf = pcap_geterr ( pcap );
65
66        fd = pcap_get_selectable_fd ( pcap );
67        if ( fd < 0 ) {
68                snprintf ( errbuf, PCAP_ERRBUF_SIZE,
69                           "could not get file descriptor" );
70                return -1;
71        }
72        if ( write ( fd, data, len ) != len ) {
73                snprintf ( errbuf, PCAP_ERRBUF_SIZE,
74                           "could not write data: %s", strerror ( errno ) );
75                return -1;
76        }
77        return len;
78}
79#endif /* ! HAVE_PCAP_INJECT */
80
81/**
82 * Log error message
83 *
84 */
85static __attribute__ (( format ( printf, 2, 3 ) )) void
86logmsg ( int level, const char *format, ... ) {
87        va_list ap;
88
89        va_start ( ap, format );
90        if ( daemonised ) {
91                vsyslog ( ( LOG_DAEMON | level ), format, ap );
92        } else {
93                vfprintf ( stderr, format, ap );
94        }
95        va_end ( ap );
96}
97
98/**
99 * Open pcap device
100 *
101 */
102static int hijack_open ( const char *interface, struct hijack *hijack ) {
103        char errbuf[PCAP_ERRBUF_SIZE];
104
105        /* Open interface via pcap */
106        errbuf[0] = '\0';
107        hijack->pcap = pcap_open_live ( interface, SNAPLEN, 1, 0, errbuf );
108        if ( ! hijack->pcap ) {
109                logmsg ( LOG_ERR, "Failed to open %s: %s\n",
110                         interface, errbuf );
111                goto err;
112        }
113        if ( errbuf[0] )
114                logmsg ( LOG_WARNING, "Warning: %s\n", errbuf );
115
116        /* Set capture interface to non-blocking mode */
117        if ( pcap_setnonblock ( hijack->pcap, 1, errbuf ) < 0 ) {
118                logmsg ( LOG_ERR, "Could not make %s non-blocking: %s\n",
119                         interface, errbuf );
120                goto err;
121        }
122
123        /* Get file descriptor for select() */
124        hijack->fd = pcap_get_selectable_fd ( hijack->pcap );
125        if ( hijack->fd < 0 ) {
126                logmsg ( LOG_ERR, "Cannot get selectable file descriptor "
127                         "for %s\n", interface );
128                goto err;
129        }
130
131        /* Get link layer type */
132        hijack->datalink = pcap_datalink ( hijack->pcap );
133
134        return 0;
135
136 err:
137        if ( hijack->pcap )
138                pcap_close ( hijack->pcap );
139        return -1;
140}
141
142/**
143 * Close pcap device
144 *
145 */
146static void hijack_close ( struct hijack *hijack ) {
147        pcap_close ( hijack->pcap );
148}
149
150/**
151 * Install filter for hijacked connection
152 *
153 */
154static int hijack_install_filter ( struct hijack *hijack,
155                                   char *filter ) {
156        struct bpf_program program;
157
158        /* Compile filter */
159        if ( pcap_compile ( hijack->pcap, &program, filter, 1, 0 ) < 0 ) {
160                logmsg ( LOG_ERR, "could not compile filter \"%s\": %s\n",
161                         filter, pcap_geterr ( hijack->pcap ) );
162                goto err_nofree;
163        }
164
165        /* Install filter */
166        if ( pcap_setfilter ( hijack->pcap, &program ) < 0 ) {
167                logmsg ( LOG_ERR, "could not install filter \"%s\": %s\n",
168                         filter, pcap_geterr ( hijack->pcap ) );
169                goto err;
170        }
171       
172        logmsg ( LOG_INFO, "using filter \"%s\"\n", filter );
173
174        pcap_freecode ( &program );
175        return 0;
176
177 err:   
178        pcap_freecode ( &program );
179 err_nofree:
180        return -1;
181}
182
183/**
184 * Set up filter for hijacked ethernet connection
185 *
186 */
187static int hijack_filter_ethernet ( struct hijack *hijack, const char *buf,
188                                    size_t len ) {
189        char filter[55]; /* see format string */
190        struct ether_header *ether_header = ( struct ether_header * ) buf;
191        unsigned char *hwaddr = ether_header->ether_shost;
192
193        if ( len < sizeof ( *ether_header ) )
194                return -1;
195
196        snprintf ( filter, sizeof ( filter ), "broadcast or multicast or "
197                   "ether host %02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0],
198                   hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5] );
199
200        return hijack_install_filter ( hijack, filter );
201}
202
203/**
204 * Set up filter for hijacked connection
205 *
206 */
207static int hijack_filter ( struct hijack *hijack, const char *buf,
208                           size_t len ) {
209        switch ( hijack->datalink ) {
210        case DLT_EN10MB:
211                return hijack_filter_ethernet ( hijack, buf, len );
212        default:
213                logmsg ( LOG_ERR, "unsupported protocol %s: cannot filter\n",
214                         ( pcap_datalink_val_to_name ( hijack->datalink ) ?
215                           pcap_datalink_val_to_name ( hijack->datalink ) :
216                           "UNKNOWN" ) );
217                /* Return success so we don't get called again */
218                return 0;
219        }
220}
221
222/**
223 * Forward data from hijacker
224 *
225 */
226static ssize_t forward_from_hijacker ( struct hijack *hijack, int fd ) {
227        char buf[SNAPLEN];
228        ssize_t len;
229
230        /* Read packet from hijacker */
231        len = read ( fd, buf, sizeof ( buf ) );
232        if ( len < 0 ) {
233                logmsg ( LOG_ERR, "read from hijacker failed: %s\n",
234                         strerror ( errno ) );
235                return -1;
236        }
237        if ( len == 0 )
238                return 0;
239
240        /* Set up filter if not already in place */
241        if ( ! hijack->filtered ) {
242                if ( hijack_filter ( hijack, buf, len ) == 0 )
243                        hijack->filtered = 1;
244        }
245
246        /* Transmit packet to network */
247        if ( pcap_inject ( hijack->pcap, buf, len ) != len ) {
248                logmsg ( LOG_ERR, "write to hijacked port failed: %s\n",
249                         pcap_geterr ( hijack->pcap ) );
250                return -1;
251        }
252
253        hijack->tx_count++;
254        return len;
255};
256
257/**
258 * Forward data to hijacker
259 *
260 */
261static ssize_t forward_to_hijacker ( int fd, struct hijack *hijack ) {
262        struct pcap_pkthdr *pkt_header;
263        const unsigned char *pkt_data;
264        ssize_t len;
265
266        /* Receive packet from network */
267        if ( pcap_next_ex ( hijack->pcap, &pkt_header, &pkt_data ) < 0 ) {
268                logmsg ( LOG_ERR, "read from hijacked port failed: %s\n",
269                         pcap_geterr ( hijack->pcap ) );
270                return -1;
271        }
272        if ( pkt_header->caplen != pkt_header->len ) {
273                logmsg ( LOG_ERR, "read partial packet (%d of %d bytes)\n",
274                         pkt_header->caplen, pkt_header->len );
275                return -1;
276        }
277        if ( pkt_header->caplen == 0 )
278                return 0;
279        len = pkt_header->caplen;
280
281        /* Write packet to hijacker */
282        if ( write ( fd, pkt_data, len ) != len ) {
283                logmsg ( LOG_ERR, "write to hijacker failed: %s\n",
284                         strerror ( errno ) );
285                return -1;
286        }
287
288        hijack->rx_count++;
289        return len;
290};
291
292
293/**
294 * Run hijacker
295 *
296 */
297static int run_hijacker ( const char *interface, int fd ) {
298        struct hijack hijack;
299        fd_set fdset;
300        int max_fd;
301        ssize_t len;
302
303        logmsg ( LOG_INFO, "new connection for %s\n", interface );
304
305        /* Open connection to network */
306        memset ( &hijack, 0, sizeof ( hijack ) );
307        if ( hijack_open ( interface, &hijack ) < 0 )
308                goto err;
309       
310        /* Do the forwarding */
311        max_fd = ( ( fd > hijack.fd ) ? fd : hijack.fd );
312        while ( 1 ) {
313                /* Wait for available data */
314                FD_ZERO ( &fdset );
315                FD_SET ( fd, &fdset );
316                FD_SET ( hijack.fd, &fdset );
317                if ( select ( ( max_fd + 1 ), &fdset, NULL, NULL, 0 ) < 0 ) {
318                        logmsg ( LOG_ERR, "select failed: %s\n",
319                                 strerror ( errno ) );
320                        goto err;
321                }
322                if ( FD_ISSET ( fd, &fdset ) ) {
323                        len = forward_from_hijacker ( &hijack, fd );
324                        if ( len < 0 )
325                                goto err;
326                        if ( len == 0 )
327                                break;
328                }
329                if ( FD_ISSET ( hijack.fd, &fdset ) ) {
330                        len = forward_to_hijacker ( fd, &hijack );
331                        if ( len < 0 )
332                                goto err;
333                        if ( len == 0 )
334                                break;
335                }
336        }
337
338        hijack_close ( &hijack );
339        logmsg ( LOG_INFO, "closed connection for %s\n", interface );
340        logmsg ( LOG_INFO, "received %ld packets, sent %ld packets\n",
341                 hijack.rx_count, hijack.tx_count );
342
343        return 0;
344
345 err:
346        if ( hijack.pcap )
347                hijack_close ( &hijack );
348        return -1;
349}
350
351/**
352 * Open listener socket
353 *
354 */
355static int open_listener ( const char *interface,
356                           struct hijack_listener *listener ) {
357       
358        /* Create socket */
359        listener->fd = socket ( PF_UNIX, SOCK_SEQPACKET, 0 );
360        if ( listener->fd < 0 ) {
361                logmsg ( LOG_ERR, "Could not create socket: %s\n",
362                         strerror ( errno ) );
363                goto err;
364        }
365
366        /* Bind to local filename */
367        listener->sun.sun_family = AF_UNIX,
368        snprintf ( listener->sun.sun_path, sizeof ( listener->sun.sun_path ),
369                   "/var/run/hijack-%s", interface );
370        if ( bind ( listener->fd, ( struct sockaddr * ) &listener->sun,
371                    sizeof ( listener->sun ) ) < 0 ) {
372                logmsg ( LOG_ERR, "Could not bind socket to %s: %s\n",
373                         listener->sun.sun_path, strerror ( errno ) );
374                goto err;
375        }
376
377        /* Set as a listening socket */
378        if ( listen ( listener->fd, 0 ) < 0 ) {
379                logmsg ( LOG_ERR, "Could not listen to %s: %s\n",
380                         listener->sun.sun_path, strerror ( errno ) );
381                goto err;
382        }
383
384        return 0;
385       
386 err:
387        if ( listener->fd >= 0 )
388                close ( listener->fd );
389        return -1;
390}
391
392/**
393 * Listen on listener socket
394 *
395 */
396static int listen_for_hijackers ( struct hijack_listener *listener,
397                                  const char *interface ) {
398        int fd;
399        pid_t child;
400        int rc;
401
402        logmsg ( LOG_INFO, "Listening on %s\n", listener->sun.sun_path );
403
404        while ( ! signalled ) {
405                /* Accept new connection, interruptibly */
406                siginterrupt ( SIGINT, 1 );
407                siginterrupt ( SIGHUP, 1 );
408                fd = accept ( listener->fd, NULL, 0 );
409                siginterrupt ( SIGINT, 0 );
410                siginterrupt ( SIGHUP, 0 );
411                if ( fd < 0 ) {
412                        if ( errno == EINTR ) {
413                                continue;
414                        } else {
415                                logmsg ( LOG_ERR, "accept failed: %s\n",
416                                         strerror ( errno ) );
417                                goto err;
418                        }
419                }
420
421                /* Fork child process */
422                child = fork();
423                if ( child < 0 ) {
424                        logmsg ( LOG_ERR, "fork failed: %s\n",
425                                 strerror ( errno ) );
426                        goto err;
427                }
428                if ( child == 0 ) {
429                        /* I am the child; run the hijacker */
430                        rc = run_hijacker ( interface, fd );
431                        close ( fd );
432                        exit ( rc );
433                }
434               
435                close ( fd );
436        }
437
438        logmsg ( LOG_INFO, "Stopped listening on %s\n",
439                 listener->sun.sun_path );
440        return 0;
441
442 err:
443        if ( fd >= 0 )
444                close ( fd );
445        return -1;
446}
447
448/**
449 * Close listener socket
450 *
451 */
452static void close_listener ( struct hijack_listener *listener ) {
453        close ( listener->fd );
454        unlink ( listener->sun.sun_path );
455}
456
457/**
458 * Print usage
459 *
460 */
461static void usage ( char **argv ) {
462        logmsg ( LOG_ERR,
463                 "Usage: %s [options]\n"
464                 "\n"
465                 "Options:\n"
466                 "  -h|--help               Print this help message\n"
467                 "  -i|--interface intf     Use specified network interface\n"
468                 "  -n|--nodaemon           Run in foreground\n",
469                 argv[0] );
470}
471
472/**
473 * Parse command-line options
474 *
475 */
476static int parse_options ( int argc, char **argv,
477                           struct hijack_options *options ) {
478        static struct option long_options[] = {
479                { "interface", 1, NULL, 'i' },
480                { "nodaemon", 0, NULL, 'n' },
481                { "help", 0, NULL, 'h' },
482                { },
483        };
484        int c;
485
486        /* Set default options */
487        memset ( options, 0, sizeof ( *options ) );
488        strncpy ( options->interface, "eth0", sizeof ( options->interface ) );
489        options->daemonise = 1;
490
491        /* Parse command-line options */
492        while ( 1 ) {
493                int option_index = 0;
494               
495                c = getopt_long ( argc, argv, "i:hn", long_options,
496                                  &option_index );
497                if ( c < 0 )
498                        break;
499
500                switch ( c ) {
501                case 'i':
502                        strncpy ( options->interface, optarg,
503                                  sizeof ( options->interface ) );
504                        break;
505                case 'n':
506                        options->daemonise = 0;
507                        break;
508                case 'h':
509                        usage( argv );
510                        return -1;
511                case '?':
512                        /* Unrecognised option */
513                        return -1;
514                default:
515                        logmsg ( LOG_ERR, "Unrecognised option '-%c'\n", c );
516                        return -1;
517                }
518        }
519
520        /* Check there's nothing left over on the command line */
521        if ( optind != argc ) {
522                usage ( argv );
523                return -1;
524        }
525
526        return 0;
527}
528
529/**
530 * Daemonise
531 *
532 */
533static int daemonise ( const char *interface ) {
534        char pidfile[16 + IF_NAMESIZE + 4]; /* "/var/run/hijack-<intf>.pid" */
535        char pid[16];
536        int pidlen;
537        int fd = -1;
538
539        /* Daemonise */
540        if ( daemon ( 0, 0 ) < 0 ) {
541                logmsg ( LOG_ERR, "Could not daemonise: %s\n",
542                         strerror ( errno ) );
543                goto err;
544        }
545        daemonised = 1; /* Direct messages to syslog now */
546
547        /* Open pid file */
548        snprintf ( pidfile, sizeof ( pidfile ), "/var/run/hijack-%s.pid",
549                   interface );
550        fd = open ( pidfile, ( O_WRONLY | O_CREAT | O_TRUNC ),
551                    ( S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ) );
552        if ( fd < 0 ) {
553                logmsg ( LOG_ERR, "Could not open %s for writing: %s\n",
554                         pidfile, strerror ( errno ) );
555                goto err;
556        }
557
558        /* Write pid to file */
559        pidlen = snprintf ( pid, sizeof ( pid ), "%d\n", getpid() );
560        if ( write ( fd, pid, pidlen ) != pidlen ) {
561                logmsg ( LOG_ERR, "Could not write %s: %s\n",
562                         pidfile, strerror ( errno ) );
563                goto err;
564        }
565
566        close ( fd );
567        return 0;
568
569 err:
570        if ( fd >= 0 )
571                close ( fd );
572        return -1;
573}
574
575int main ( int argc, char **argv ) {
576        struct hijack_options options;
577        struct hijack_listener listener;
578        struct sigaction sa;
579
580        /* Parse command-line options */
581        if ( parse_options ( argc, argv, &options ) < 0 )
582                exit ( 1 );
583
584        /* Set up syslog connection */
585        openlog ( basename ( argv[0] ), LOG_PID, LOG_DAEMON );
586
587        /* Set up listening socket */
588        if ( open_listener ( options.interface, &listener ) < 0 )
589                exit ( 1 );
590
591        /* Daemonise on demand */
592        if ( options.daemonise ) {
593                if ( daemonise ( options.interface ) < 0 )
594                        exit ( 1 );
595        }
596
597        /* Avoid creating zombies */
598        memset ( &sa, 0, sizeof ( sa ) );
599        sa.sa_handler = SIG_IGN;
600        sa.sa_flags = SA_RESTART | SA_NOCLDWAIT;
601        if ( sigaction ( SIGCHLD, &sa, NULL ) < 0 ) {
602                logmsg ( LOG_ERR, "Could not set SIGCHLD handler: %s",
603                         strerror ( errno ) );
604                exit ( 1 );
605        }
606
607        /* Set 'signalled' flag on SIGINT or SIGHUP */
608        sa.sa_handler = flag_signalled;
609        sa.sa_flags = SA_RESTART | SA_RESETHAND;
610        if ( sigaction ( SIGINT, &sa, NULL ) < 0 ) {
611                logmsg ( LOG_ERR, "Could not set SIGINT handler: %s",
612                         strerror ( errno ) );
613                exit ( 1 );
614        }
615        if ( sigaction ( SIGHUP, &sa, NULL ) < 0 ) {
616                logmsg ( LOG_ERR, "Could not set SIGHUP handler: %s",
617                         strerror ( errno ) );
618                exit ( 1 );
619        }
620
621        /* Listen for hijackers */
622        if ( listen_for_hijackers ( &listener, options.interface ) < 0 )
623                exit ( 1 );
624
625        close_listener ( &listener );
626       
627        return 0;
628}
Note: See TracBrowser for help on using the repository browser.