//
//
// tratt.c - bridge 2 physical network segments over a TCP/IP tunnel
//
// (c)2001 OlleB <olle@toolcrypt.org>
//
// 	original concept by Ag3nt0nd and YRG
//
//
// TODO:
//
//	bandwidth throttling/limiting
//
//	transport independant design, udp, GRE/AH transports
//
//	support for other media types? ieee802.11? TokenRing (ugh)?
//
//	runtime tuning of control values (throttling etc.)
//
//
/*

Copyright (c) 2001, OlleB <olle@toolcrypt.org>
All rights reserved.

Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

- Neither the name of the author nor the names of any contributors may be used
  to endorse or promote products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>

#include "pcap.h"

#define VERSION				"0.04-tc"

#define PCAP_SNAP_LEN			1514

// from bpf.h
#define DLT_EN10MB      1       /* Ethernet (10Mb) */
#define DLT_EN100MB     100     /* Ethernet (100Mb) */

#ifdef WPCAP
// libwpcap definitions
extern int pcap_sendpacket(pcap_t *p, u_char *buf, int size);
#endif

#ifdef LIBNET
// libnet definitions
void *libnet_open_link_interface(char *, char *);
int libnet_write_link_layer(void *, const u_char *, u_char *, int);
#endif

#ifndef __GETOPT_H__
// getopt definitions
extern int getopt(int, char * const *, const char *);
extern char *optarg;
#endif

// the tratt header
typedef struct tratthdr_s
	{
		char		tratt[6];	// "tratt\0"
		bpf_u_int32 	len;		// length of packet

	} tratthdr_t;

// ethernet and arp headers ripped from pcapsend.c by Andy Lutomirski
struct ethernet_hdr
{
#ifndef ETHER_ADDR_LEN
#define ETHER_ADDR_LEN 6
#endif
    u_char  ether_dhost[ETHER_ADDR_LEN];    /* destination ethernet address */
    u_char  ether_shost[ETHER_ADDR_LEN];    /* source ethernet address */
    u_short ether_type;                     /* packet type ID */
#define ETH_IP  0x0800
#define ETH_ARP 0x0806
};

struct arp_hdr
{
    u_short ar_hrd;                         /* format of hardware address */
#define ARPHRD_ETHER     1                  /* ethernet hardware format */
    u_short ar_pro;                         /* format of protocol address */
    u_char  ar_hln;                         /* length of hardware address */
    u_char  ar_pln;                         /* length of protocol addres */
    u_short ar_op;                          /* operation type */
#define ARPOP_REQUEST    1                  /* req to resolve address */
#define ARPOP_REPLY      2                  /* resp to previous request */
#define ARPOP_REVREQUEST 3                  /* req protocol address given hardware */
#define ARPOP_REVREPLY   4                  /* resp giving protocol address */
#define ARPOP_INVREQUEST 8                  /* req to identify peer */
#define ARPOP_INVREPLY   9                  /* resp identifying peer */

    /*
     *  These should implementation defined but I've hardcoded eth/IP.
     */
    u_char ar_sha[6];                         /* sender hardware address */
    u_char ar_spa[4];                         /* sender protocol address */
    u_char ar_tha[6];                         /* target hardware address */
    u_char ar_tpa[4];                         /* target protocol address */
};



//
// die - printf vararg errors and quit
//
static void die(char *format, ...)
{
  	va_list       args;

  	va_start(args, format);
  	fprintf(stderr, "\ntratt %s: ", VERSION);
  	vfprintf(stderr, format, args);
  	fprintf(stderr, "\n");
  	va_end(args);
	fflush(stderr);
	exit(1);
}


//
// pcap_init - get handle to pcap device
//
pcap_t *pcap_init(char *device, char *filter)
{
        pcap_t                 *pd;
        struct bpf_program      bp;
    	char                    errbuf[PCAP_ERRBUF_SIZE];

        pd = pcap_open_live(device, PCAP_SNAP_LEN, 1, 1, errbuf);
        if (pd == NULL)
        	die("pcap_open_live: %s", errbuf);

        fprintf(stderr,"listening on %s\n", device);

        if (pcap_compile(pd, &bp, filter, IFF_PROMISC, 0xffffffff) < 0)
        	die("pcap_compile: %s", errbuf);

        if (pcap_setfilter(pd, &bp) < 0)
        	die("pcap_setfilter: %s", errbuf);

        return(pd);
}


//
// tcp_accept - get an incoming socket connection
//
int tcp_accept(u_int16_t port)
{
  	int			 s,e;
  	struct sockaddr_in	 sin;

	fprintf(stderr,"waiting for connection on port %i\n", port);

  	sin.sin_family = AF_INET;
  	sin.sin_addr.s_addr = INADDR_ANY;
  	sin.sin_port = htons(port);

  	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  	if (s == -1)
    		die("tcp_accept: unable to create socket\n");

  	e = bind(s, (struct sockaddr *) &sin, sizeof(sin));
  	if (e == -1)
      		die("tcp_accept: unable to bind() socket\n");

  	e = listen(s, 0);
  	if (e == -1)
      		die("tcp_accept: unable to listen() on socket\n");

  	e = accept(s, (struct sockaddr *) &sin, &e);
  	if (e == -1)
      		die("tcp_accept: unable to accept() connection\n");

	close(s);

  	return(e);
}


//
// tcp_connect - get an outgoing socket connection
//
int tcp_connect(char *addr, u_int16_t port)
{
  	int			 s,e;
	struct hostent		*h;
  	struct sockaddr_in	 sin;

  	h = gethostbyname(addr);
  	if (!h)
    		die("tcp_connect: unable to resolve host\n");

  	sin.sin_family = h->h_addrtype;
  	sin.sin_addr.s_addr = *((u_int32_t *) h->h_addr);
  	sin.sin_port = htons(port);
	
  	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  	if (s == -1)
    		die("tcp_connect: unable to create socket\n");

  	e = connect(s, (struct sockaddr *) &sin, sizeof(sin));
  	if (e == -1)
      		die("tcp_connect: unable to connect()\n");

  	return(s);
}


//
// packet_insert_rawsock - insert a packet using raw sockets
//
void packet_insert_rawsock(u_char *device, u_char *packet, unsigned int len)
{
	die("packet_insert_rawsock: not implemented yet!\n");
}


//
// do_layer2_magic - rewrite ethernet addresses in packet
//
void do_layer2_magic(u_char *packet, char *mymac)
{
	struct ethernet_hdr	*ether;
	struct arp_hdr		*arp;

	ether = (struct ethernet_hdr *)packet;
	arp = (struct arp_hdr *)packet+sizeof(struct ethernet_hdr);

	// use my ethernet address
	bcopy(mymac, ether->ether_shost, 6);
}


//
// get_my_mac - lookup hardware address of device
//
char *get_my_mac(char *device)
{
	int		 s;
	struct ifreq	 ifrq;
	char		 *tmp;
#ifdef WPCAP
        char		*t;
        int		 i,ii;
        char		 cygdev[10]="\0";
    	char		 errbuf[PCAP_ERRBUF_SIZE];
	char		 p;
#endif

  	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  	if (s == -1)
    		die("get_my_mac: unable to create socket\n");

	memset(&ifrq, 0, sizeof(ifrq));

#ifdef WPCAP
        t=pcap_lookupdev(errbuf);
	if (t==NULL)
		die("get_my_mac: pcap_lookupdev failed: %s\n", errbuf);

	// strip unicode nulls if necessary
	if (t[1]=='\0')
	{
        	ii=0;
        	for (i=0;;i++)
        	{
			if (p == '\0' && t[ii] == '\0')
				break;
                	p = t[i] = t[ii];
                	ii += 2;
        	}
	}

	// translate pcap device to cygwin device
	ii=i=0;
	do {	
                if ((i==0)||(t[i-1]==0))
                {
			//fprintf(stderr, "device%i: %s\n",ii,t+i);
			if (strncmp(t+i, device, strlen(t+i))==0)
			{
				snprintf(cygdev, 9, "eth%i", ii);
				cygdev[9]='\0';
			}
			i++;
			ii++;
                }
                else 
                i++;

        } while (!(t[i]==0 && t[i-1]==0));

	if (strlen(cygdev)<1)
		die("get_my_mac: could not find cygwin devicename\n");

	//fprintf(stderr, "cygdev = %s\n",cygdev);

  	strncpy(ifrq.ifr_name, cygdev, IFNAMSIZ-1);
#else
  	strncpy(ifrq.ifr_name, device, IFNAMSIZ-1);
#endif

  	if (ioctl(s, SIOCGIFHWADDR, &ifrq) < 0 )
		die("couldn't find hardware address for %s\n", device);
	close(s);

	tmp = malloc(6);
	if (tmp==NULL)
		die("get_my_mac: could not malloc 6 bytes for hwaddr");

	bcopy(ifrq.ifr_hwaddr.sa_data, tmp, 6);

	return tmp;
}


//
// tratt - the part that actually does stuff
//
void tratt(char *dev, char *filter, int s)
{
	int	 		 arg,e,p,i;
	char			*device=dev;
	pcap_t			*pd;
	u_char 			*packet		= NULL;
	struct pcap_pkthdr	 pcaphdr; 
	tratthdr_t		 tratthdr	= {"tratt\0", 0};
    	char                     errbuf[PCAP_ERRBUF_SIZE];
	char			*mymac;
	struct ethernet_hdr	*ether;
#ifdef WPCAP
	u_char			 tmpchar = 'A';
	int			 ii;
#endif
#ifdef LIBNET
	void			*l;
#endif

	// get default device
        if (device == NULL)
        {
                device = pcap_lookupdev(errbuf);

                if (device == NULL)
                	die("pcap_lookupdev: %s", errbuf);
#ifdef WPCAP
		// strip unicode nulls if necessary
		if (device[1]=='\0')
		{
        		ii=0;
        		for (i=0;;i++)
        		{
				if (tmpchar == '\0' && device[ii] == '\0')
					break;
                		tmpchar = device[i] = device[ii];
                		ii += 2;
        		}
		}
#endif
        }

	// open pcap device
	if ((pd = pcap_init(device, filter)) == NULL)
		die("pcap_init: failed");

	// check for ethernet
	if (!(pcap_datalink(pd)==DLT_EN10MB || pcap_datalink(pd)==DLT_EN100MB))
		die("link layer type %i not supported!\n", pcap_datalink(pd));

	// discover mac address
	mymac = get_my_mac(device);

	fprintf(stderr, "hwaddr = %.2X:%.2X:%.2X:%.2X:%.2X:%.2X\n",mymac[0]&0xff,mymac[1]&0xff,mymac[2]&0xff,mymac[3]&0xff,mymac[4]&0xff,mymac[5]&0xff);


#ifdef LIBNET
	// open libnet link
	l = libnet_open_link_interface(device, errbuf);
	if (!l)
		die("libnet_open_link_interface: %s\n", errbuf);
#endif
		
	i=0;
	// enter main program loop
	while(1)
	{
		// print hashmarks
		i++;
		if (i==100)
		{
			i=0;
			fprintf(stderr,".");
		}

		// process next packet from pcap
		packet = (char *)pcap_next(pd, &pcaphdr);
		if (packet)
		{
			// check if snaplen was adequate
			if (pcaphdr.len != pcaphdr.caplen)
				fprintf(stderr, "packet truncated by pcap! increase PCAP_SNAP_LEN (%i)\n\n", PCAP_SNAP_LEN);

			// check for & exclude injected packets
			ether = (struct ethernet_hdr *)packet;
			if (memcmp(ether->ether_shost, mymac, 6)!=0)
			{
	
				// make socket blocking
				arg = 0;
				if (ioctl(s, FIONBIO, &arg))
					die("could not make socket blocking!\n");
		
				// send tratt header to remote tratt
				tratthdr.len = pcaphdr.len;
				p=0;
				while (p<sizeof(tratthdr_t))
				{
					e = send(s, (char *)(&tratthdr)+p, sizeof(tratthdr_t)-p, 0);
					if (e>0)
						p += e;
					else
						die("could not send header, connection closed?\n");
					//if (e!=sizeof(tratthdr_t)) fprintf(stderr, "sent %i of %i header bytes.\n\n", e, p);
				}
				//fprintf(stderr, "send header sent %i bytes.\n\n", e);
		
				// send packet to remote tratt
				p=0;
				while (p<tratthdr.len)
				{
					e = send(s, packet+p, tratthdr.len-p, 0);
					if (e>0)
						p += e;
					else
						die("could not send packet, connection closed?\n");
					//if (e!=tratthdr.len) fprintf(stderr, "sent %i of %i packet bytes.\n\n", e, p);
				}
				//fprintf(stderr, "send packet sent %i bytes.\n\n", p);
			} /* exclude injected packets */

		} /* process next packet from pcap */


		// make socket non-blocking
		arg = 1;
		if (ioctl(s, FIONBIO, &arg))
			die("could not make socket non-blocking!\n");

		// process traffic from remote tratt
		e = recv(s, (char *)&tratthdr, sizeof(tratthdr_t), 0);
		if (e > 0)
		{
			// make socket blocking
			arg = 0;
			if (ioctl(s, FIONBIO, &arg))
				die("could not make socket blocking!\n");
	
			// make sure we have the full header 
			if (e != sizeof(tratthdr_t))
			{
				//fprintf(stderr, "rcvd %i header bytes.\n\n", e);

				p=e;
				while(p<sizeof(tratthdr_t))
				{
					e = recv(s, (char *)(&tratthdr)+p, sizeof(tratthdr_t)-p, 0);
					if (e>0)
						p += e;
					else
						die("could not recv header, connection closed?\n");
					//fprintf(stderr, "retry rcvd %i header bytes.\n\n", e);
				}
			}

			// sanity check header
			if (strncmp(tratthdr.tratt, "tratt\0", 6)!=0)
				die("invalid tratt header recieved!\n");

			//fprintf(stderr, "tratt header recieved! %i bytes in packet.\n\n", tratthdr.len);

			// malloc for packet
			packet = NULL;
			packet = malloc(tratthdr.len+1);
			if (packet == NULL)
  				die("malloc for packet size %i failed!", tratthdr.len);

			// recv packet
			p=0;
			while (p<tratthdr.len)
			{
				e = recv(s, packet+p, tratthdr.len-p, 0);
				if (e>0)
					p += e;
				else
					die("could not recv packet, connection closed?\n");
					//if (e!=tratthdr.len) fprintf(stderr, "rcvd %i of %i packet bytes.\n\n", e, p);
			}

			// insert packet
			do_layer2_magic(packet, mymac);
#ifdef WPCAP
			e = pcap_sendpacket(pd, packet, tratthdr.len);
			if (e<0)
				die("pcap_sendpacket: couldn't insert packet: %s\n", pcap_geterr(pd));
#else
#ifdef LIBNET
			e = libnet_write_link_layer(l, device, packet, tratthdr.len);
			if (e<0)
				die("libnet_write_link_layer: couldn't insert packet!\n");
#else
			packet_insert_rawsock(device, packet, tratthdr.len);
#endif /* LIBNET */
#endif /* WPCAP */
			
			// free the mallocs
			free(packet);

		} /* process traffic from remote tratt */

	} /* main program loop */
	
	pcap_close(pd);
}


//
// print_device_list - print a pretty list of network interfaces
//
void print_device_list(void)
{
        char	*t;
        int	 i;
        int	 DescPos=0;
        char	*Desc;
    	char	 errbuf[PCAP_ERRBUF_SIZE];
#ifdef WPCAP
	int	 ii;
	char	 p;
#endif

	fprintf(stderr,"Available pcap devices:\n\n");

        t=pcap_lookupdev(errbuf);
	if (t==NULL)
		die("print_device_list: pcap_lookupdev failed: %s\n", errbuf);

#ifdef WPCAP
	// strip unicode nulls if necessary
	if (t[1]=='\0')
	{
        	ii=0;
        	for (i=0;;i++)
        	{
			if (p == '\0' && t[ii] == '\0')
				break;
                	p = t[i] = t[ii];
                	ii += 2;
        	}
	}
#endif

	// this part is ripped from windump
	i=0;
        while(*(t+DescPos)!=0 || *(t+DescPos-1)!=0)
                DescPos++;

        DescPos<<=1;
        Desc=(char*)t+DescPos+2;

        while (!(t[i]==0 && t[i-1]==0))
        {
                if (t[i]==0)
                {
                        fprintf(stderr, "%c",' ');
                        fprintf(stderr, "%c",'(');
                        while(*Desc!=0){
                                fprintf(stderr, "%c",*Desc);
                                Desc++;
                        }
                        Desc++;
                        fprintf(stderr, "%c",')');
                        fprintf(stderr, "%c",'\n');
                }
                else fprintf(stderr, "%c",t[i]);
                i++;
        }
        fprintf(stderr, "%c",'\n');
}


//
// usage - print usage information and quit
//
void usage(void)
{
		fprintf(stderr,"Usage: tratt [-D] <-l | -t host> [-p port] [-d net_device] [-f bpf_filter]\n\n"); 
		exit(1);
}


//
// main - program entrypoint
//
int main(int argc, char **argv)
{
	int	 arg,s;
	int	 listen		= 0;
	short	 port		= 80;
	char	*device		= NULL;
	char	*target		= NULL;
	char	*filter		= "ip or arp";

	fprintf(stderr,"\ntratt %s      (c)2001 <olle@nxs.se>\n\n", VERSION); 

        // parse the commandline options
        while((arg = getopt(argc,argv,"lDd:t:p:f:")) != EOF) {
                switch (arg) {

		// set listen switch
		case 'l':
		listen = 1;
		break;

		// set target IP
		case 't':
		target=optarg;
		break;

		// set target port
		case 'p':
		if ((atoi(optarg)>65536) || (atoi(optarg) < 0))
			die("%i is not a valid port value!\n", port);	
		else
			port = atoi(optarg);
		break;

		// set pcap device
		case 'd':
		device=optarg;
		break;
		
		// set pcap filter
		case 'f':
		filter=optarg;
		break;

		case 'D':
		print_device_list();
		exit(1);
		break;
		
		// usage
		default:
		usage();
		}
	}

	// die with usage if no remote tratt specified
	if ((!listen) && (target == NULL))
		usage();

	// connect to remote tratt
	if (listen)
		s = tcp_accept(port);
	else
		s = tcp_connect(target, port);

	// sanity check socket
	if (s < 0)
		die("socket value insane! (%i)\n", s);

	// start tratting
	tratt(device, filter, s);

	exit(0);
}
