/*
 * tcp-term.c v1.0  17 Feb 2000   James W. Abendschan <jwa@jammed.com>
 * http://www.jammed.com/~jwa/hacks/tcp-term.c
 *
 * binds a TCP port to a serial device -- use it to make a modem you
 * can telnet to.  IE, assuming a 1200 baud modem attached to /dev/modem
 * on the machine 'foonix':
 * 
 *  foonix% tcp-term -p 4001 -d /dev/modem -s 1200
 *
 * .. then, on another machine..
 *
 *  barnix% telnet foonix 4001
 *
 * .. and ATDT away!  You can also use it with a "clear" socket connection
 * (ie, netcat); it will attempt to do the right thing if a telnet client
 * is in use, but will still operate if the client end is not telnet.  this
 * lets you, say, use netcat to connect pppd to tcp-term (which was why
 * I originally wrote this program) as well as being able to simply telnet
 * to tcp-term on demand and have it behave as expected.  (To see what
 * happens if the IAC stuff is not done properly, call tcp-term with
 * a bufsize of 1 or 2 (-b 1), telnet to it, and watch everything get screwy.)
 *
 *
 * todo: 
 *   use termios instead of stty for baudrate
 *   ACLs
 *   syslog instead of stdout
 *
 * This software is made availble under the terms of the GNU General
 * Public License: http://www.gnu.org/copyleft/gpl.html
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>

// telnet protocol junk.

#define IAC             255
#define WILL            251
#define WONT            252
#define DO              253
#define DONT            254
#define SB              250
#define SE              240

#define IS              0
#define SEND            1

#define ECHO            1
#define TERM            24
#define SPEED           32
#define NAWS            31
#define TERMINAL_SPEED  32
#define SUPPRESS_GA     3
#define LINEMODE        34
#define REMOTE_FLOW     33
#define TRANSMIT_BINARY 0

// define #ANIMATION if you want a nifty twirly cursor on the host machine
// this is so silly..
#define ANIMATION

#ifdef ANIMATION
 char anim[] = "!/-\\";
 char *animp = anim;
 #define ANIMATE printf("%c\r", *animp++); fflush(stdout); if (!*animp) animp = anim;
#else
 #define ANIMATE ;
#endif

// prototypes

int  main(int argc, char **argv);
void copybytes(void);
int  tcp_listen (u_short port);
void telnet_init(void);
int  peername(int fd, char *ip, size_t size);
void process_IAC (char *iacbuf, size_t iacbufsize, int *outsize);
void usage(char *myname);
void fdprintf(int fd, char *fmt, ...);

// globals

int net_fd, device_fd;
size_t b2sock, b2dev;
size_t bufsize = 1024;
long baud = 115200;
int port = 4000;
char device[128];
char myname[128];

int IACs = 0;	// IAC state.
int IACc = 0;	// IAC byte counter

int main(int argc, char **argv) {
	char host[128];
	char buf[128];
	int c;

	strncpy(device, "/dev/modem", sizeof(device)-1); // default . . .

	// parse args

	while (1) {
		c = getopt(argc, argv, "p:s:d:b:");
		if (c == -1)
			break;	// end-of-args

		switch(c) {
			case 'p':
				port = atoi(optarg);
				break;
			case 'd':
				strncpy(device, optarg, sizeof(device)-1);
				break;
			case 'b':
				bufsize = strtol(optarg, NULL, 10);
				break;	
			case 's':
				baud = strtol(optarg, NULL, 10);
				break;
			default:
				usage(*argv+0);
				break;
		}	
	}


	// sanity checks

	device_fd = open(device, O_RDWR);
	if (device_fd < 0) {
		printf("Can't open %s (%s)\n", device, strerror(errno));
		exit(-1);
	}
	close (device_fd);

	if (bufsize < 16) {
		printf("Warning: small bufsize %d; automagic telnet IAC detector may fail.\n", bufsize);
	}

	snprintf(buf, sizeof(buf)-1, "stty %d < %s", baud, device);
	if (system(buf) != 0) {
		printf("%s failed!", buf);
		exit(-1);
	}
	if (argc > 1) 
		printf("Will use port=%d baud=%ld device=%s bufsize=%d\n", port, baud, device, bufsize);

	while (1) {
		printf("Listening on port %d ... ", port); fflush(stdout);
		net_fd = tcp_listen(port);
		peername(net_fd, host, sizeof(host)-1);
		printf("connection from %s\n", host);
		device_fd = open(device, O_RDWR);
		if (device_fd < 0) {
			// shouldn't happen, but if it does..
			fdprintf(net_fd, "open of %s failed: %s\r\n", device, strerror(errno));
			printf(buf); // stdout
			exit(-1);
		}
		fdprintf(net_fd, "Hello, %s.  You are now connected to %s at %d baud.\r\n", host, device, baud);
		telnet_init();
		b2sock = b2dev = 0;
		copybytes();
		shutdown(net_fd, 2);
		close(net_fd);
		close(device_fd);	// closing the device drops DTR; YMMV
		printf("Connection to %s closed; sent %d bytes to socket, %d bytes to device\n", host, b2sock, b2dev);
	}
}



// copy bytes between net_fd<->device_fd

void copybytes(void) {
	int bread;
	int bsent;
	fd_set fds;
	int eon = 0;		// end of network.. what a concept
	struct timeval tval;
	int max_fd;
	int num;
	int sz;
	int handle_IAC = 1;
	char *buf;
	
	buf = malloc(bufsize);
	if (!buf) {
		printf("error during malloc(%d): %s\n", bufsize, strerror(errno));
		exit(-1);
	}

	FD_ZERO(&fds);

	max_fd = net_fd;
	if (device_fd > max_fd) 
		max_fd = device_fd;
	max_fd++;

	while (!eon) {
		tval.tv_usec = 0;
		tval.tv_sec = 300;		// make globally hackable

		FD_ZERO(&fds);
		FD_SET(net_fd, &fds);
		FD_SET(device_fd, &fds);
		
		//FD_SET(net_fd, &w_fds);
		//FD_SET(device_fd, &w_fds);

		num = select(max_fd, &fds, NULL, NULL, &tval);
		if (!num) {
			printf("timeout\n");
			eon = 1;
		}

		ANIMATE;	// it's krad!

		if (FD_ISSET(net_fd, &fds)) {	// socket has data
			bread = read(net_fd, buf, bufsize-1);
			if (bread <= 0)
				eon = 1;
			else {
				if (handle_IAC) {
					// make the assumption that the
					// first time we are here is the
					// only time we need to be here..
					process_IAC(buf, bread, &sz);
					bread = sz;
					// .. unless we're *still* in IAC
					// state.
					//printf("came out w/ IACs=%d sz=%d\n", IACs, sz);
					if (IACs == 0) {
						//printf("Done w/ the IAC shit\n");
						handle_IAC = 0;
					}
				}

				bsent = write(device_fd, buf, bread);
				if (bsent != bread)  {
					printf("short write to device_fd?\n");
					eon = 1;
				}
				b2dev += bsent;
			}
		} else if (FD_ISSET(device_fd, &fds)) {
			bread = read(device_fd, buf, bufsize-1);
			if (bread <= 0)
				eon = 1;
			else {
				bsent = write(net_fd, buf, bread);
				if (bsent != bread) {
					printf("short write to net_fd?\n");
					eon = 1;
				}
				b2sock += bsent;
			}
		}
	}
	// that's all
	//free (buf);
}




void usage(char *myname) {
	printf("usage: %s [-p port#] [-d device] [-s device baudrate] [-b read bufsize]\n", myname);
	printf("With no args, will use port %d, device %s, %ld baud, bufsize %d\n", port, device, baud, bufsize);
	exit(-1);
}



// printf to an fd

void fdprintf(int fd, char *fmt, ...) {
	va_list ap;
	char buf[2048];

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf)-1, fmt, ap);
	va_end(ap);

	write(fd, buf, strlen(buf));
}





// listen on a TCP port; fork to handle connection
// stolen from the socket faq

int tcp_listen (u_short port) {
	struct sockaddr_in address;
	int listening_socket;
	int connected_socket = -1;
	int reuse_addr = 1;
	int backlog = 15;
	char *ptr;

	errno = 0;

LISTEN:

	listening_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (listening_socket < 0) {
		perror("socket");
		exit(-1);
	}

	setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, 
		sizeof(reuse_addr));

	memset((char *) &address, 0, sizeof(address));
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	address.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(listening_socket, (struct sockaddr *) &address, sizeof(address)) < 0) {
		perror("bind");
		close(listening_socket);
		exit(-1);
	}

	// env LISTENQ .. from Stevens Unix Network Programming, section 4.5
	if ((ptr = getenv("LISTENQ")) != NULL)
		backlog = atoi(ptr);

	if (listen(listening_socket, backlog) < 0) {
		perror("listen");
		close(listening_socket);
		exit(-1);
	}
	    

	connected_socket = accept(listening_socket, NULL, NULL);
	if (connected_socket < 0) {
		perror("listen");
		// Either a real error occured, or blocking was 
		// interrupted for some reason.  Only abort 
		// execution if a real error occured.
		if (errno != EINTR) {
			// usually the connection came up halfway
			// but then it died.  ie, SYN w/ no SYN-ACK.
			// (nmap -sS -p xxxx hostname can induce this)
			// just restart the listener
			printf("Connection came up half-way?  Restarting..\n");
			close(listening_socket);
			goto LISTEN;
		}
		exit(-1);
	}
	setsid();
	close (listening_socket);
	return connected_socket;
}



// get the peer's IP.  stolen from the fwtk.

int peername(int fd, char *ip, size_t size) {
	struct sockaddr_in a;
	socklen_t y;

	y = sizeof(a);
	if (getpeername(fd, (struct sockaddr *)&a, &y) < 0) {
		//logger("getpeername failed: %m");
		return(1);
	}
	strncpy(ip, inet_ntoa(a.sin_addr), size);
	return (0);
}





// spew out a bunch of stuff to what might be a telnet client
// we'll know if it's a telnet client because it will respond to our
// IAC's with IAC [something] within moments after connecting.
// we try to intelligently handle these IACs in copybytes()

void telnet_init(void) {
        const static unsigned char init[] = {
                                 //IAC, DO, TERMINAL_SPEED,
                                 IAC, WILL, SUPPRESS_GA,
                                 IAC, DO, REMOTE_FLOW,
                                 IAC, DONT, LINEMODE,
                                 IAC, DONT, ECHO,
                                 IAC, WILL, ECHO,
                                 IAC, SB, REMOTE_FLOW, IS, IAC, SE,
                                 IAC, DO, TRANSMIT_BINARY
                                 };

	write(net_fd, init, sizeof(init));
}



// nothing fancy here.  Just walk buf for telnet Interpret-As-Command bytes
// and throw them away, copy everything else back into iacbuf.  Yes, iacbuf
// copies over itself.. but it's fairly safe, coz it's simple.
// this hasn't been exercised fully-- namely, I've never seen iacbuf
// come back with any data, since the first pass thru the select()
// contains only IAC stuff.

void process_IAC (char *iacbuf, size_t iacbufsize, int *outsize) {
	char *p;
	unsigned char c;
	int sc = 0;

	p = iacbuf;
	*(outsize) = 0;

	while (sc != iacbufsize) {
		sc++;
		c = *p++;
		if (IACs) {	
			IACc++;
			if (IACc == 3)
				IACs = 0; // 3 bytes & we're out of IAC state
                        
			if ((c == SE) && (IACs == 2))
				// IAC Subnegotiation End; kick out of IAC state
				IACs = 0;

			if ((c == SB) && (IACs == 1))
				// IAC Subnegotation Begin; go into IAC state
				IACs = 2;       
       
		} else if (c == IAC) {
			IACc = 0;
			IACs = 1;
		} else {
			*p = c;		
			(void) *(outsize)++;
		}
	}
}


