#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>
#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 {
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.
+ *
+ */
+static 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 void logmsg ( int level, const char *format, ... ) {
+static __attribute__ (( format ( printf, 2, 3 ) )) void
+logmsg ( int level, const char *format, ... ) {
va_list ap;
va_start ( ap, format );
goto err;
}
+ /* Get link layer type */
+ hijack->datalink = pcap_datalink ( hijack->pcap );
+
return 0;
err:
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
*
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;
- if ( write ( hijack->fd, buf, len ) != len ) {
+ /* 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",
- strerror ( errno ) );
+ pcap_geterr ( hijack->pcap ) );
return -1;
}
- logmsg ( LOG_INFO, "forwarded %zd bytes from hijacker\n", len );
-
+ hijack->tx_count++;
return len;
};
*
*/
static ssize_t forward_to_hijacker ( int fd, struct hijack *hijack ) {
- char buf[SNAPLEN];
+ struct pcap_pkthdr *pkt_header;
+ const unsigned char *pkt_data;
ssize_t len;
- len = read ( hijack->fd, buf, sizeof ( buf ) );
- if ( len < 0 ) {
+ /* 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",
- strerror ( errno ) );
+ pcap_geterr ( hijack->pcap ) );
return -1;
}
-
- if ( len == 0 )
+ 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;
- if ( write ( fd, buf, len ) != len ) {
+ /* Write packet to hijacker */
+ if ( write ( fd, pkt_data, len ) != len ) {
logmsg ( LOG_ERR, "write to hijacker failed: %s\n",
strerror ( errno ) );
return -1;
}
- logmsg ( LOG_INFO, "forwarded %zd bytes to hijacker\n", len );
-
+ hijack->rx_count++;
return len;
};
int max_fd;
ssize_t len;
- memset ( &hijack, 0, sizeof ( hijack ) );
-
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;
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;
logmsg ( LOG_INFO, "Listening on %s\n", listener->sun.sun_path );
- while ( 1 ) {
- /* Accept new connection */
+ 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 ) {
- logmsg ( LOG_ERR, "accept failed: %s\n",
- strerror ( errno ) );
- goto err;
+ if ( errno == EINTR ) {
+ continue;
+ } else {
+ logmsg ( LOG_ERR, "accept failed: %s\n",
+ strerror ( errno ) );
+ goto err;
+ }
}
/* Fork child process */
close ( fd );
}
+ logmsg ( LOG_INFO, "Stopped listening on %s\n",
+ listener->sun.sun_path );
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 sigchld;
+ struct sigaction sa;
/* Parse command-line options */
if ( parse_options ( argc, argv, &options ) < 0 )
}
/* Avoid creating zombies */
- memset ( &sigchld, 0, sizeof ( sigchld ) );
- sigchld.sa_handler = SIG_IGN;
- sigchld.sa_flags = SA_NOCLDWAIT;
- if ( sigaction ( SIGCHLD, &sigchld, NULL ) < 0 ) {
- logmsg ( LOG_ERR, "Could not set signal handler: %s",
+ 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 );
}