#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; }