| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <stdarg.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <libgen.h> | |
| #include <signal.h> | |
| #include <net/if.h> | |
| #include <net/ethernet.h> | |
| #include <sys/select.h> | |
| #include <sys/socket.h> | |
| #include <sys/stat.h> | |
| #include <sys/un.h> | |
| #include <syslog.h> | |
| #include <getopt.h> | |
| #include <pcap.h> | |
| #define SNAPLEN 1600 | |
| /* | |
| * FIXME: is there a way to detect the version of the libpcap library? | |
| * Version 0.9 has pcap_inject; version 0.8 doesn't, but both report | |
| * their version number as 2.4. | |
| */ | |
| #define HAVE_PCAP_INJECT 0 | |
| struct hijack { | |
| pcap_t *pcap; | |
| int fd; | |
| int datalink; | |
| int filtered; | |
| unsigned long rx_count; | |
| unsigned long tx_count; | |
| }; | |
| struct hijack_listener { | |
| struct sockaddr_un sun; | |
| int fd; | |
| }; | |
| struct hijack_options { | |
| char interface[IF_NAMESIZE]; | |
| int daemonise; | |
| }; | |
| static int daemonised = 0; | |
| static int signalled = 0; | |
| static void flag_signalled ( int signal __attribute__ (( unused )) ) { | |
| signalled = 1; | |
| } | |
| #if ! HAVE_PCAP_INJECT | |
| /** | |
| * Substitute for pcap_inject(), if this version of libpcap doesn't | |
| * have it. Will almost certainly only work under Linux. | |
| * | |
| */ | |
| int pcap_inject ( pcap_t *pcap, const void *data, size_t len ) { | |
| int fd; | |
| char *errbuf = pcap_geterr ( pcap ); | |
| fd = pcap_get_selectable_fd ( pcap ); | |
| if ( fd < 0 ) { | |
| snprintf ( errbuf, PCAP_ERRBUF_SIZE, | |
| "could not get file descriptor" ); | |
| return -1; | |
| } | |
| if ( write ( fd, data, len ) != len ) { | |
| snprintf ( errbuf, PCAP_ERRBUF_SIZE, | |
| "could not write data: %s", strerror ( errno ) ); | |
| return -1; | |
| } | |
| return len; | |
| } | |
| #endif /* ! HAVE_PCAP_INJECT */ | |
| /** | |
| * Log error message | |
| * | |
| */ | |
| static __attribute__ (( format ( printf, 2, 3 ) )) void | |
| logmsg ( int level, const char *format, ... ) { | |
| va_list ap; | |
| va_start ( ap, format ); | |
| if ( daemonised ) { | |
| vsyslog ( ( LOG_DAEMON | level ), format, ap ); | |
| } else { | |
| vfprintf ( stderr, format, ap ); | |
| } | |
| va_end ( ap ); | |
| } | |
| /** | |
| * Open pcap device | |
| * | |
| */ | |
| static int hijack_open ( const char *interface, struct hijack *hijack ) { | |
| char errbuf[PCAP_ERRBUF_SIZE]; | |
| /* Open interface via pcap */ | |
| errbuf[0] = '\0'; | |
| hijack->pcap = pcap_open_live ( interface, SNAPLEN, 1, 0, errbuf ); | |
| if ( ! hijack->pcap ) { | |
| logmsg ( LOG_ERR, "Failed to open %s: %s\n", | |
| interface, errbuf ); | |
| goto err; | |
| } | |
| if ( errbuf[0] ) | |
| logmsg ( LOG_WARNING, "Warning: %s\n", errbuf ); | |
| /* Set capture interface to non-blocking mode */ | |
| if ( pcap_setnonblock ( hijack->pcap, 1, errbuf ) < 0 ) { | |
| logmsg ( LOG_ERR, "Could not make %s non-blocking: %s\n", | |
| interface, errbuf ); | |
| goto err; | |
| } | |
| /* Get file descriptor for select() */ | |
| hijack->fd = pcap_get_selectable_fd ( hijack->pcap ); | |
| if ( hijack->fd < 0 ) { | |
| logmsg ( LOG_ERR, "Cannot get selectable file descriptor " | |
| "for %s\n", interface ); | |
| goto err; | |
| } | |
| /* Get link layer type */ | |
| hijack->datalink = pcap_datalink ( hijack->pcap ); | |
| return 0; | |
| err: | |
| if ( hijack->pcap ) | |
| pcap_close ( hijack->pcap ); | |
| return -1; | |
| } | |
| /** | |
| * Close pcap device | |
| * | |
| */ | |
| static void hijack_close ( struct hijack *hijack ) { | |
| pcap_close ( hijack->pcap ); | |
| } | |
| /** | |
| * Install filter for hijacked connection | |
| * | |
| */ | |
| static int hijack_install_filter ( struct hijack *hijack, | |
| char *filter ) { | |
| struct bpf_program program; | |
| /* Compile filter */ | |
| if ( pcap_compile ( hijack->pcap, &program, filter, 1, 0 ) < 0 ) { | |
| logmsg ( LOG_ERR, "could not compile filter \"%s\": %s\n", | |
| filter, pcap_geterr ( hijack->pcap ) ); | |
| goto err_nofree; | |
| } | |
| /* Install filter */ | |
| if ( pcap_setfilter ( hijack->pcap, &program ) < 0 ) { | |
| logmsg ( LOG_ERR, "could not install filter \"%s\": %s\n", | |
| filter, pcap_geterr ( hijack->pcap ) ); | |
| goto err; | |
| } | |
| logmsg ( LOG_INFO, "using filter \"%s\"\n", filter ); | |
| pcap_freecode ( &program ); | |
| return 0; | |
| err: | |
| pcap_freecode ( &program ); | |
| err_nofree: | |
| return -1; | |
| } | |
| /** | |
| * Set up filter for hijacked ethernet connection | |
| * | |
| */ | |
| static int hijack_filter_ethernet ( struct hijack *hijack, const char *buf, | |
| size_t len ) { | |
| char filter[55]; /* see format string */ | |
| struct ether_header *ether_header = ( struct ether_header * ) buf; | |
| unsigned char *hwaddr = ether_header->ether_shost; | |
| if ( len < sizeof ( *ether_header ) ) | |
| return -1; | |
| snprintf ( filter, sizeof ( filter ), "broadcast or multicast or " | |
| "ether host %02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0], | |
| hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5] ); | |
| return hijack_install_filter ( hijack, filter ); | |
| } | |
| /** | |
| * Set up filter for hijacked connection | |
| * | |
| */ | |
| static int hijack_filter ( struct hijack *hijack, const char *buf, | |
| size_t len ) { | |
| switch ( hijack->datalink ) { | |
| case DLT_EN10MB: | |
| return hijack_filter_ethernet ( hijack, buf, len ); | |
| default: | |
| logmsg ( LOG_ERR, "unsupported protocol %s: cannot filter\n", | |
| ( pcap_datalink_val_to_name ( hijack->datalink ) ? | |
| pcap_datalink_val_to_name ( hijack->datalink ) : | |
| "UNKNOWN" ) ); | |
| /* Return success so we don't get called again */ | |
| return 0; | |
| } | |
| } | |
| /** | |
| * Forward data from hijacker | |
| * | |
| */ | |
| static ssize_t forward_from_hijacker ( struct hijack *hijack, int fd ) { | |
| char buf[SNAPLEN]; | |
| ssize_t len; | |
| /* Read packet from hijacker */ | |
| len = read ( fd, buf, sizeof ( buf ) ); | |
| if ( len < 0 ) { | |
| logmsg ( LOG_ERR, "read from hijacker failed: %s\n", | |
| strerror ( errno ) ); | |
| return -1; | |
| } | |
| if ( len == 0 ) | |
| return 0; | |
| /* Set up filter if not already in place */ | |
| if ( ! hijack->filtered ) { | |
| if ( hijack_filter ( hijack, buf, len ) == 0 ) | |
| hijack->filtered = 1; | |
| } | |
| /* Transmit packet to network */ | |
| if ( pcap_inject ( hijack->pcap, buf, len ) != len ) { | |
| logmsg ( LOG_ERR, "write to hijacked port failed: %s\n", | |
| pcap_geterr ( hijack->pcap ) ); | |
| return -1; | |
| } | |
| hijack->tx_count++; | |
| return len; | |
| }; | |
| /** | |
| * Forward data to hijacker | |
| * | |
| */ | |
| static ssize_t forward_to_hijacker ( int fd, struct hijack *hijack ) { | |
| struct pcap_pkthdr *pkt_header; | |
| const unsigned char *pkt_data; | |
| ssize_t len; | |
| /* Receive packet from network */ | |
| if ( pcap_next_ex ( hijack->pcap, &pkt_header, &pkt_data ) < 0 ) { | |
| logmsg ( LOG_ERR, "read from hijacked port failed: %s\n", | |
| pcap_geterr ( hijack->pcap ) ); | |
| return -1; | |
| } | |
| if ( pkt_header->caplen != pkt_header->len ) { | |
| logmsg ( LOG_ERR, "read partial packet (%d of %d bytes)\n", | |
| pkt_header->caplen, pkt_header->len ); | |
| return -1; | |
| } | |
| if ( pkt_header->caplen == 0 ) | |
| return 0; | |
| len = pkt_header->caplen; | |
| /* Write packet to hijacker */ | |
| if ( write ( fd, pkt_data, len ) != len ) { | |
| logmsg ( LOG_ERR, "write to hijacker failed: %s\n", | |
| strerror ( errno ) ); | |
| return -1; | |
| } | |
| hijack->rx_count++; | |
| return len; | |
| }; | |
| /** | |
| * Run hijacker | |
| * | |
| */ | |
| static int run_hijacker ( const char *interface, int fd ) { | |
| struct hijack hijack; | |
| fd_set fdset; | |
| int max_fd; | |
| ssize_t len; | |
| logmsg ( LOG_INFO, "new connection for %s\n", interface ); | |
| /* Open connection to network */ | |
| memset ( &hijack, 0, sizeof ( hijack ) ); | |
| if ( hijack_open ( interface, &hijack ) < 0 ) | |
| goto err; | |
| /* Do the forwarding */ | |
| max_fd = ( ( fd > hijack.fd ) ? fd : hijack.fd ); | |
| while ( 1 ) { | |
| /* Wait for available data */ | |
| FD_ZERO ( &fdset ); | |
| FD_SET ( fd, &fdset ); | |
| FD_SET ( hijack.fd, &fdset ); | |
| if ( select ( ( max_fd + 1 ), &fdset, NULL, NULL, 0 ) < 0 ) { | |
| logmsg ( LOG_ERR, "select failed: %s\n", | |
| strerror ( errno ) ); | |
| goto err; | |
| } | |
| if ( FD_ISSET ( fd, &fdset ) ) { | |
| len = forward_from_hijacker ( &hijack, fd ); | |
| if ( len < 0 ) | |
| goto err; | |
| if ( len == 0 ) | |
| break; | |
| } | |
| if ( FD_ISSET ( hijack.fd, &fdset ) ) { | |
| len = forward_to_hijacker ( fd, &hijack ); | |
| if ( len < 0 ) | |
| goto err; | |
| if ( len == 0 ) | |
| break; | |
| } | |
| } | |
| hijack_close ( &hijack ); | |
| logmsg ( LOG_INFO, "closed connection for %s\n", interface ); | |
| logmsg ( LOG_INFO, "received %ld packets, sent %ld packets\n", | |
| hijack.rx_count, hijack.tx_count ); | |
| return 0; | |
| err: | |
| if ( hijack.pcap ) | |
| hijack_close ( &hijack ); | |
| return -1; | |
| } | |
| /** | |
| * Open listener socket | |
| * | |
| */ | |
| static int open_listener ( const char *interface, | |
| struct hijack_listener *listener ) { | |
| /* Create socket */ | |
| listener->fd = socket ( PF_UNIX, SOCK_SEQPACKET, 0 ); | |
| if ( listener->fd < 0 ) { | |
| logmsg ( LOG_ERR, "Could not create socket: %s\n", | |
| strerror ( errno ) ); | |
| goto err; | |
| } | |
| /* Bind to local filename */ | |
| listener->sun.sun_family = AF_UNIX, | |
| snprintf ( listener->sun.sun_path, sizeof ( listener->sun.sun_path ), | |
| "/var/run/hijack-%s", interface ); | |
| if ( bind ( listener->fd, ( struct sockaddr * ) &listener->sun, | |
| sizeof ( listener->sun ) ) < 0 ) { | |
| logmsg ( LOG_ERR, "Could not bind socket to %s: %s\n", | |
| listener->sun.sun_path, strerror ( errno ) ); | |
| goto err; | |
| } | |
| /* Set as a listening socket */ | |
| if ( listen ( listener->fd, 0 ) < 0 ) { | |
| logmsg ( LOG_ERR, "Could not listen to %s: %s\n", | |
| listener->sun.sun_path, strerror ( errno ) ); | |
| goto err; | |
| } | |
| return 0; | |
| err: | |
| if ( listener->fd >= 0 ) | |
| close ( listener->fd ); | |
| return -1; | |
| } | |
| /** | |
| * Listen on listener socket | |
| * | |
| */ | |
| static int listen_for_hijackers ( struct hijack_listener *listener, | |
| const char *interface ) { | |
| int fd; | |
| pid_t child; | |
| int rc; | |
| logmsg ( LOG_INFO, "Listening on %s\n", listener->sun.sun_path ); | |
| while ( ! signalled ) { | |
| /* Accept new connection, interruptibly */ | |
| siginterrupt ( SIGINT, 1 ); | |
| siginterrupt ( SIGHUP, 1 ); | |
| fd = accept ( listener->fd, NULL, 0 ); | |
| siginterrupt ( SIGINT, 0 ); | |
| siginterrupt ( SIGHUP, 0 ); | |
| if ( fd < 0 ) { | |
| if ( errno == EINTR ) { | |
| continue; | |
| } else { | |
| logmsg ( LOG_ERR, "accept failed: %s\n", | |
| strerror ( errno ) ); | |
| goto err; | |
| } | |
| } | |
| /* Fork child process */ | |
| child = fork(); | |
| if ( child < 0 ) { | |
| logmsg ( LOG_ERR, "fork failed: %s\n", | |
| strerror ( errno ) ); | |
| goto err; | |
| } | |
| if ( child == 0 ) { | |
| /* I am the child; run the hijacker */ | |
| rc = run_hijacker ( interface, fd ); | |
| close ( fd ); | |
| exit ( rc ); | |
| } | |
| close ( fd ); | |
| } | |
| logmsg ( LOG_INFO, "Stopped listening on %s\n", | |
| listener->sun.sun_path ); | |
| return 0; | |
| err: | |
| if ( fd >= 0 ) | |
| close ( fd ); | |
| return -1; | |
| } | |
| /** | |
| * Close listener socket | |
| * | |
| */ | |
| static void close_listener ( struct hijack_listener *listener ) { | |
| close ( listener->fd ); | |
| unlink ( listener->sun.sun_path ); | |
| } | |
| /** | |
| * Print usage | |
| * | |
| */ | |
| static void usage ( char **argv ) { | |
| logmsg ( LOG_ERR, | |
| "Usage: %s [options]\n" | |
| "\n" | |
| "Options:\n" | |
| " -h|--help Print this help message\n" | |
| " -i|--interface intf Use specified network interface\n" | |
| " -n|--nodaemon Run in foreground\n", | |
| argv[0] ); | |
| } | |
| /** | |
| * Parse command-line options | |
| * | |
| */ | |
| static int parse_options ( int argc, char **argv, | |
| struct hijack_options *options ) { | |
| static struct option long_options[] = { | |
| { "interface", 1, NULL, 'i' }, | |
| { "nodaemon", 0, NULL, 'n' }, | |
| { "help", 0, NULL, 'h' }, | |
| { }, | |
| }; | |
| int c; | |
| /* Set default options */ | |
| memset ( options, 0, sizeof ( *options ) ); | |
| strncpy ( options->interface, "eth0", sizeof ( options->interface ) ); | |
| options->daemonise = 1; | |
| /* Parse command-line options */ | |
| while ( 1 ) { | |
| int option_index = 0; | |
| c = getopt_long ( argc, argv, "i:hn", long_options, | |
| &option_index ); | |
| if ( c < 0 ) | |
| break; | |
| switch ( c ) { | |
| case 'i': | |
| strncpy ( options->interface, optarg, | |
| sizeof ( options->interface ) ); | |
| break; | |
| case 'n': | |
| options->daemonise = 0; | |
| break; | |
| case 'h': | |
| usage( argv ); | |
| return -1; | |
| case '?': | |
| /* Unrecognised option */ | |
| return -1; | |
| default: | |
| logmsg ( LOG_ERR, "Unrecognised option '-%c'\n", c ); | |
| return -1; | |
| } | |
| } | |
| /* Check there's nothing left over on the command line */ | |
| if ( optind != argc ) { | |
| usage ( argv ); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Daemonise | |
| * | |
| */ | |
| static int daemonise ( const char *interface ) { | |
| char pidfile[16 + IF_NAMESIZE + 4]; /* "/var/run/hijack-<intf>.pid" */ | |
| char pid[16]; | |
| int pidlen; | |
| int fd = -1; | |
| /* Daemonise */ | |
| if ( daemon ( 0, 0 ) < 0 ) { | |
| logmsg ( LOG_ERR, "Could not daemonise: %s\n", | |
| strerror ( errno ) ); | |
| goto err; | |
| } | |
| daemonised = 1; /* Direct messages to syslog now */ | |
| /* Open pid file */ | |
| snprintf ( pidfile, sizeof ( pidfile ), "/var/run/hijack-%s.pid", | |
| interface ); | |
| fd = open ( pidfile, ( O_WRONLY | O_CREAT | O_TRUNC ), | |
| ( S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ) ); | |
| if ( fd < 0 ) { | |
| logmsg ( LOG_ERR, "Could not open %s for writing: %s\n", | |
| pidfile, strerror ( errno ) ); | |
| goto err; | |
| } | |
| /* Write pid to file */ | |
| pidlen = snprintf ( pid, sizeof ( pid ), "%d\n", getpid() ); | |
| if ( write ( fd, pid, pidlen ) != pidlen ) { | |
| logmsg ( LOG_ERR, "Could not write %s: %s\n", | |
| pidfile, strerror ( errno ) ); | |
| goto err; | |
| } | |
| close ( fd ); | |
| return 0; | |
| err: | |
| if ( fd >= 0 ) | |
| close ( fd ); | |
| return -1; | |
| } | |
| int main ( int argc, char **argv ) { | |
| struct hijack_options options; | |
| struct hijack_listener listener; | |
| struct sigaction sa; | |
| /* Parse command-line options */ | |
| if ( parse_options ( argc, argv, &options ) < 0 ) | |
| exit ( 1 ); | |
| /* Set up syslog connection */ | |
| openlog ( basename ( argv[0] ), LOG_PID, LOG_DAEMON ); | |
| /* Set up listening socket */ | |
| if ( open_listener ( options.interface, &listener ) < 0 ) | |
| exit ( 1 ); | |
| /* Daemonise on demand */ | |
| if ( options.daemonise ) { | |
| if ( daemonise ( options.interface ) < 0 ) | |
| exit ( 1 ); | |
| } | |
| /* Avoid creating zombies */ | |
| memset ( &sa, 0, sizeof ( sa ) ); | |
| sa.sa_handler = SIG_IGN; | |
| sa.sa_flags = SA_RESTART | SA_NOCLDWAIT; | |
| if ( sigaction ( SIGCHLD, &sa, NULL ) < 0 ) { | |
| logmsg ( LOG_ERR, "Could not set SIGCHLD handler: %s", | |
| strerror ( errno ) ); | |
| exit ( 1 ); | |
| } | |
| /* Set 'signalled' flag on SIGINT or SIGHUP */ | |
| sa.sa_handler = flag_signalled; | |
| sa.sa_flags = SA_RESTART | SA_RESETHAND; | |
| if ( sigaction ( SIGINT, &sa, NULL ) < 0 ) { | |
| logmsg ( LOG_ERR, "Could not set SIGINT handler: %s", | |
| strerror ( errno ) ); | |
| exit ( 1 ); | |
| } | |
| if ( sigaction ( SIGHUP, &sa, NULL ) < 0 ) { | |
| logmsg ( LOG_ERR, "Could not set SIGHUP handler: %s", | |
| strerror ( errno ) ); | |
| exit ( 1 ); | |
| } | |
| /* Listen for hijackers */ | |
| if ( listen_for_hijackers ( &listener, options.interface ) < 0 ) | |
| exit ( 1 ); | |
| close_listener ( &listener ); | |
| return 0; | |
| } |