/*
 * main.c
 * (w) 1995 Andreas Godzina
 */

#include <fcntl.h>
#include <errno.h>

#include "ipxtunnel.h"
#include "ipx.h"
#include "misc.h"
#include "hash.h"
#include "version.h"

/* Globals */
struct config	*config;
int		sock802_2, sock802_3, sockipx, socksnap, sockudp;
struct packet	p = {UNCOMPRESSED, 0};
struct packet	pcomp = {COMPRESSED, 0};
char		*buf = p.buf;	/* Global buffer with last frame(s) */
int		frameLen;
struct location	*locs;			/* Array of remote/local receivers */
int		numLocs;
struct sockaddr from;
struct sockaddr_in *udpfrom = (struct sockaddr_in *) &from;
unsigned long	waitDelay = 0;
int		debugLevel = 0;
int		bridgeMode = FALSE;

unsigned long	ipxpackets;
unsigned long	udppackets;
unsigned long	ipxbytes;
unsigned long	udpbytes;
unsigned long	seq;
time_t		startTime;

unsigned long	byteTable[ETH_FRAME_LEN] = {0};

void	Init(void);
void	RecvData(void);
void	RecvEther(int sd);
void	RecvUDP(int sd);
void	WriteEther(struct addressinfo *addr, int fromWhere);
void	WriteUDP(struct addressinfo *addr);
int	CheckInterface(char *name);
void	IfSetFlag(char *ifname, short flag);
void	IfClearFlag(char *ifname, short flag);
void	SigHandler(int sig);
void	Usage(char *s);

int
main(int argc, char **argv)
{
	extern int	optind;
	extern char	*optarg;
	int		ch;
	char		*configFile = CONFIG_FILE;

	printf("%s\n", VERSION);
	while((ch = getopt(argc, argv, "w:d:f:m")) != -1) {
		switch(ch) {
		case 'w':	waitDelay = atol(optarg); break;
		case 'd':	debugLevel = atoi(optarg); break;
		case 'f':	configFile = optarg; break;
		default:	Usage(argv[0]); break;
		}
	}
	if(argc > optind)
		Usage(argv[0]);
	if(getuid()) {
		fprintf(stderr, "must be superuser\n");
		exit(1);
	}

	config = ReadConfigFile(configFile);
	if(!config->hosts) {
		fprintf(stderr, "no hosts - Bridge-mode\n");
		bridgeMode = TRUE;
	}
	Init();
	signal(SIGINT, SigHandler);
	startTime = time(NULL);
	RecvData();

	return 0;
}

/*******************************************************************************
Create sockets, bind udp-socket, create locations-array, set promisc mode
*******************************************************************************/
void
Init(void)
{
	struct sockaddr_in	saddr;
	int			i, curLoc;
	struct location		*loc;
	char			*name;

	if(sizeof(struct sockaddr) != sizeof(struct sockaddr_in)) {
		fprintf(stderr, "Ouch!\n");
		exit(1);
	}
	/* TODO: SIOCGIFCONF */
	if(!config->interfaces) {
		printf("Using default eth0\n");
		config->interfaces = Malloc(2 * sizeof(char **));
		config->interfaces[0] = strdup("eth0");
		config->interfaces[1] = NULL;
	}

	numLocs = 0;
	if(config->hosts)
		for(i = 0; config->hosts[i]; i++)
			numLocs++;
	for(i = 0; config->interfaces[i]; i++)
		numLocs++;

	locs = (struct location *) Malloc(sizeof(struct location) * numLocs);

	sock802_2 = socket(AF_INET, SOCK_PACKET, htons(ETH_P_802_2));
	sock802_3 = socket(AF_INET, SOCK_PACKET, htons(ETH_P_802_3));
	sockipx = socket(AF_INET, SOCK_PACKET, htons(ETH_P_IPX));
	socksnap = socket(AF_INET, SOCK_PACKET, htons(ETH_P_SNAP));
	sockudp = socket(AF_INET, SOCK_DGRAM, 0);
	if((sock802_2 | sock802_3 | sockipx | socksnap | sockudp) < 0) {
		perror("socket()");
		exit(1);
	}

#if 0
	if(fcntl(sock802_2, F_SETFL, FNDELAY) < 0)
		perror("fcntl()");
	if(fcntl(sock802_3, F_SETFL, FNDELAY) < 0)
		perror("fcntl()");
	if(fcntl(sockipx, F_SETFL, FNDELAY) < 0)
		perror("fcntl()");
	if(fcntl(socksnap, F_SETFL, FNDELAY) < 0)
		perror("fcntl()");
#endif

	curLoc = 0;
	for(i = 0; config->hosts && config->hosts[i]; i++) {
		name = config->hosts[i]->host;
		loc = &locs[curLoc];
		loc->where = REMOTE;
		loc->sd = sockudp;

		memset(&loc->remoteAddr, '\0', sizeof(loc->remoteAddr));
		loc->remoteAddr.sin_family = AF_INET;
		if((loc->remoteAddr.sin_addr.s_addr = inet_addr(name)) == -1) {
			struct hostent *hent;

			if(!(hent = gethostbyname(name))) {
				perror("gethostbyname()");
				exit(1);
			}
			memcpy((void *) &loc->remoteAddr.sin_addr,
				hent->h_addr_list[0],
				sizeof(loc->remoteAddr.sin_addr));
		}
		loc->remoteAddr.sin_port = htons((unsigned short)
			config->hosts[i]->port);
		curLoc++;
	}

	for(i = 0; config->interfaces[i]; i++) {
		IfSetFlag(config->interfaces[i], IFF_PROMISC);
		loc = &locs[curLoc];
		loc->where = LOCAL;
		loc->sd = sock802_2;	/* Any SOCK_PACKET should do */
		loc->localAddr.sa_family = AF_INET;
		strcpy(loc->localAddr.sa_data, config->interfaces[i]);
		curLoc++;
	}
	printf("Found %d destinations\n", curLoc);


	if(!bridgeMode) {
		memset((void *) &saddr, '\0', sizeof(saddr));	
		saddr.sin_family = AF_INET;
		saddr.sin_addr.s_addr = htonl(INADDR_ANY);
		saddr.sin_port = htons(config->port);
		if(bind(sockudp,(struct sockaddr *) &saddr, sizeof(saddr)) < 0){
			perror("bind()");
			exit(1);
		}
	}
}

/*******************************************************************************
select() on all sockets
*******************************************************************************/
void
RecvData(void)
{
	fd_set			sds;
	int			max, max2, no;

restart:	/* :-) */
	if(waitDelay)
		usleep(waitDelay);

	memset(&from, '\0', sizeof(from));
	do {
		FD_ZERO(&sds);
		FD_SET(sock802_2, &sds);
		FD_SET(sock802_3, &sds);
		FD_SET(sockipx, &sds);
		FD_SET(socksnap, &sds);
		FD_SET(sockudp, &sds);
		max = MAX(sock802_2, sock802_3);
		max2 = MAX(sockipx, socksnap);
		max = MAX(max, max2);
		max = MAX(max, sockudp);
	} while((no = select(max + 1, &sds, (fd_set *) 0, (fd_set *) 0,
		(struct timeval *) 0)) < 0 && errno == EINTR);

	/* errno != EINTR */
	if(no <= 0) {
		perror("select()");
		goto restart;
	}
	if(debugLevel && no > 1) {
		printf("(%d)", no);
		fflush(stdout);
	}
	if(FD_ISSET(sockudp, &sds)) {
		DPRINT("UDP ");
		RecvUDP(sockudp);
	}
	if(FD_ISSET(sock802_2, &sds)) {
		DPRINT("802.2 ");
		RecvEther(sock802_2);
	}
	if(FD_ISSET(sock802_3, &sds)) {
		DPRINT("802.3 ");
		RecvEther(sock802_3);
	}
	if(FD_ISSET(sockipx, &sds)) {
		DPRINT("IPX ");
		RecvEther(sockipx);
	}
	if(FD_ISSET(socksnap, &sds)) {
		DPRINT("SNAP ");
		RecvEther(socksnap);
	}

	goto restart;
}

/*******************************************************************************
Get frames with IPX-Packets from ethernet, forward them.
*******************************************************************************/
void
RecvEther(int sd)
{
	int		fromLen;
	unsigned char	*srcAddr;
	unsigned char	*dstAddr;
	struct addressinfo	*addr;

	fromLen = sizeof(from);

	buf = p.buf;
	frameLen = recvfrom(sd, buf, ETH_FRAME_LEN, 0, &from, &fromLen);
	if(frameLen <= 0) {
		if(frameLen < 0)
			perror("recvfrom(eth)");
		else
			printf("EOF");
		return;
	}
	p.len = frameLen;
	p.flag = UNCOMPRESSED;
	if(!CheckInterface(from.sa_data)) {
		if(debugLevel)
			printf("(%s ignored)", from.sa_data);
		return;
	}
	dstAddr = (unsigned char *) buf;
	srcAddr = (unsigned char *) buf + ETH_ALEN;
	if(!GetAddr(srcAddr)) {
		int i;

		for(i = 0; i < numLocs; i++) {
			if(locs[i].where == LOCAL)
				if(!strcmp(locs[i].localAddr.sa_data,
						from.sa_data)) {
					InsertAddr(srcAddr, i);
					break;
				}
		}
	}
	if(debugLevel == 1) {	
		putchar('l');
		fflush(stdout);
	}
	else if(debugLevel > 1) {
		printf("%s: %d bytes ", from.sa_data,
			p.len);
		PrintHex(srcAddr, 6);
		printf(" -> "); PrintHex(dstAddr, 6);
		putchar('\n');
	}
	else if(debugLevel > 2) {
		PrintIPX((ipx_packet *) (buf + ETH_HLEN));
		if(debugLevel > 3) {
			PrintHex(buf + ETH_HLEN + sizeof(ipx_packet),
				frameLen -
				(ETH_HLEN + sizeof(ipx_packet)));
			putchar('\n');
		}
	}
	ipxpackets++;
	ipxbytes += frameLen;
	byteTable[frameLen]++;

	if((addr = GetAddr(dstAddr))) {
		if(locs[addr->locIndex].where == REMOTE)
			WriteUDP(addr);
		else
			WriteEther(addr, LOCAL);
	} else {
		WriteUDP(NULL);
		WriteEther(NULL, LOCAL);
	}
}

void
WriteEther(struct addressinfo *addr, int fromWhere)
{
	int		i;
	struct location	*loc;

	if(debugLevel > 1)
		printf("WriteEther (%s addr) frameLen %d ",
			addr ? "known" : "unknown", frameLen);
	if(debugLevel > 2)
		PrintIPX((ipx_packet *) (buf + ETH_HLEN));
	if(debugLevel > 3) {
		PrintHex(buf + ETH_HLEN + sizeof(ipx_packet),
			frameLen -
			(ETH_HLEN + sizeof(ipx_packet)));
		putchar('\n');
	}

	if(addr) {
		loc = &locs[addr->locIndex];
		if(fromWhere == LOCAL)
			if(!strcmp(loc->localAddr.sa_data,
			from.sa_data))
				return;
		if(sendto(loc->sd, buf, frameLen, 0, &loc->localAddr,
				sizeof(struct sockaddr)) < 0)
			perror("sendto(eth)");
		if(debugLevel > 1) {
			putchar('w');
			fflush(stdout);
		}
		return;
	}

	/* dstAddr unknown */
	for(i = 0; i < numLocs; i++) {
		loc = &locs[i];
		if(loc->where == LOCAL) {
			if(fromWhere == LOCAL && !strcmp(loc->localAddr.sa_data,
					from.sa_data))
				continue;
			if(sendto(loc->sd, buf, frameLen, 0,
					(struct sockaddr *) &loc->localAddr,
					sizeof(struct sockaddr)) < 0)
				perror("sendto(eth)");
			if(debugLevel > 1) {
				putchar('w');
				fflush(stdout);
			}
		}
	}
}

/*******************************************************************************
Send struct packet.
*******************************************************************************/
void
WriteUDP(struct addressinfo *addr)
{
	int		i, len;
	void		*sendBuf;
	int		bytes = sizeof(p) - sizeof(p.buf);
	struct location *loc;


	sendBuf = &p;
	p.flag = UNCOMPRESSED;
	p.len = frameLen;
	if(debugLevel > 1)
		printf("UDP %d:\n", frameLen);
	
	bytes += len;

	if(addr) {
		loc = &locs[addr->locIndex];
		if(loc->where == REMOTE) {
			if(sendto(loc->sd, sendBuf, bytes, 0, (struct
					sockaddr *) &loc->remoteAddr,
					sizeof(struct sockaddr_in)) < 0)
			    perror("sendto(udp)");
		}
	} else {
		for(i = 0; i < numLocs; i++) {
			if(locs[i].where == REMOTE) {
				if(sendto(locs[i].sd, sendBuf, bytes, 0, (struct
						sockaddr *) &locs[i].remoteAddr,
						sizeof(struct sockaddr_in)) < 0)
				    perror("sendto(udp)");
			}
		}
	}
}

void
RecvUDP(int sd)
{
	int		bytesReadable;
	int		fromLen;
	unsigned char	*srcAddr, *dstAddr;

restart:
	fromLen = sizeof(from);
	if((frameLen = recvfrom(sockudp, &p, sizeof(p), 0,
			&from, &fromLen)) <= 0) {
		if(frameLen == 0) {
			fprintf(stderr, "UDP: EOF?\n");
			return;
		}
		if(errno == ECONNREFUSED)
			fprintf(stderr, "last sendto(udp) failed\n");
		else
			perror("recvfrom(udp)");
		return;
	}
	udppackets++;
	udpbytes += frameLen;
	frameLen = p.len;
	buf = p.buf;

	if(debugLevel == 1) {	
		putchar('r');
		fflush(stdout);
	}
	else if(debugLevel > 1) {
		printf("Recv udp %s, port %d, len %d\n",
			inet_ntoa(udpfrom->sin_addr),
			(int) ntohs(udpfrom->sin_port), frameLen);
	}

	dstAddr = (unsigned char *) buf;
	srcAddr = (unsigned char *) buf + ETH_ALEN;
	if(!GetAddr(srcAddr)) {
		int i;

		for(i = 0; i < numLocs; i++) {
			if(locs[i].where == REMOTE)
				if(!memcmp(&locs[i].remoteAddr, &from,
						sizeof(from)
						- sizeof(udpfrom->sin_zero))){
					InsertAddr(srcAddr, i);
					break;
				}
		}
	}

	/* Send only to Ethernet */
	WriteEther(GetAddr(dstAddr), REMOTE);

#if 1
	bytesReadable = 0;
	if(ioctl(sockudp, FIONREAD, &bytesReadable) < 0)
		perror("ioctl(FIONREAD)");
	if(bytesReadable) {
		if(debugLevel)
			putchar('R');
		goto restart;
	}
#endif
}

/*******************************************************************************
Check if given interface is on list
*******************************************************************************/
int
CheckInterface(char *name)
{
	int i;

	for(i = 0; config->interfaces[i]; i++)
		if(!strcmp(name, config->interfaces[i]))
			return TRUE;
	return FALSE;
}

/*******************************************************************************
Set interface-flag
*******************************************************************************/
void
IfSetFlag(char *ifname, short flag)
{
	struct ifreq ifr;

	strcpy(ifr.ifr_name, ifname);
	if(ioctl(sockudp, SIOCGIFFLAGS, &ifr) < 0) {
		perror("SIOCGIFFLAGS");
		return;
	}
	ifr.ifr_flags |= flag;
	if(ioctl(sockudp, SIOCSIFFLAGS, &ifr) < 0)
		perror("SIOCSIFFLAGS");
}

/*******************************************************************************
Clear interface-flag
*******************************************************************************/
void
IfClearFlag(char *ifname, short flag)
{
	struct ifreq ifr;

	strcpy(ifr.ifr_name, ifname);
	if(ioctl(sockudp, SIOCGIFFLAGS, &ifr) < 0) {
		perror("SIOCGIFFLAGS");
		return;
	}
	ifr.ifr_flags &= ~flag;
	if(ioctl(sockudp, SIOCSIFFLAGS, &ifr) < 0)
		perror("SIOCSIFFLAGS");
}

/*******************************************************************************
Clear promisc mode, print statistics, terminate
*******************************************************************************/
void
SigHandler(int sig)
{
	int		i;
	time_t		secs;
	unsigned long	bytesTotal;

	secs = time(NULL) - startTime + 1;
	bytesTotal = ipxbytes + udpbytes;

	for(i = 0; config->interfaces[i]; i++)
		IfClearFlag(config->interfaces[i], IFF_PROMISC);
	printf("\nFrames from local net: %lu Packets from others: %lu\n"
		"Ether-bytes: %lu UDP-bytes: %lu (total %lu)\n"
		"Time: %lu secs Bytes/sec: %lu\n",
		ipxpackets, udppackets, ipxbytes, udpbytes, bytesTotal, secs,
		bytesTotal / secs );
	for(i = 0; i < ETH_FRAME_LEN; i++)
		if(byteTable[i])
			printf("%d: %lu ", i, byteTable[i]);
	putchar('\n');
	exit(0);
}

void
Usage(char *s)
{
	fprintf(stderr, "Usage: %s [options]\n", s);
	fprintf(stderr, "\t-d <debuglevel>\n");
	fprintf(stderr, "\t-w <timeout in usecs>\n");
	fprintf(stderr, "\t-f <config-file>\n");
	exit(1);
}
