/*
 * TNET		A server program for MINIX which implements the TCP/IP
 *		suite of networking protocols.  It is based on the
 *		TCP/IP code written by Phil Karn et al, as found in
 *		his NET package for Packet Radio communications.
 *
 *		This module handles telnet option processing.
 *
 * Author:	Michael Temari, <temari@temari.ae.ge.com>  01/13/93
 *
 */
#include <sys/types.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#include "telnetd.h"
#include "telnet.h"
#include <stdio.h>
#include <sys/ioctl.h>


#define	IN_DATA	0
#define	IN_CR	1
#define	IN_IAC	2
#define	IN_IAC2	3
#define IN_SB	4

_PROTOTYPE(static void dowill, (int c));
_PROTOTYPE(static void dowont, (int c));
_PROTOTYPE(static void dodo, (int c));
_PROTOTYPE(static void dodont, (int c));
_PROTOTYPE(static void respond, (int ack, int option));
_PROTOTYPE(static void respond_really, (int ack, int option));

#define	LASTTELOPT	TELOPT_SGA

static int r_winch = 0;

static int TelROpts[LASTTELOPT+1];
static int TelLOpts[LASTTELOPT+1];

static int telfdout;

void tel_init()
{
int i;

   for(i = 0; i <= LASTTELOPT; i++) {
	TelROpts[i] = 0;
	TelLOpts[i] = 0;
   }
}

void telopt(fdout, what, option)
int fdout;
int what;
int option;
{
char buf[3];
int len;

   buf[0] = IAC;
   buf[1] = what;
   buf[2] = option;
   len = 0;

   switch(what) {
	case DO:
		if(option <= LASTTELOPT) {
			TelROpts[option] = 1;
			len = 3;
		} else if(option == TELOPT_WINCH && !r_winch) { r_winch = 1; len = 3; } 
		break;
	case DONT:
		if(option <= LASTTELOPT) {
			TelROpts[option] = 1;
			len = 3;
		}
		break;
	case WILL:
		if(option <= LASTTELOPT) {
			TelLOpts[option] = 1;
			len = 3;
		}
		break;
	case WONT:
		if(option <= LASTTELOPT) {
			TelLOpts[option] = 1;
			len = 3;
		}
		break;
   }
   if(len > 0)
	(void) write(fdout, buf, len);
}

int set_winsize(int fd, unsigned int cols, unsigned int rows)
{
	struct winsize w;
	memset(&w, 0, sizeof(w));
	w.ws_col = cols;
	w.ws_row = rows;
	ioctl(fd, TIOCSWINSZ, (char *) &w);
}

int tel_in(fdout, telout, buffer, len)
int fdout;
int telout;
char *buffer;
int len;
{
static int InState = IN_DATA;
static int ThisOpt = 0;
char *p;
char *p2;
int size;
int c;

   telfdout = telout;
   p = p2 = buffer;
   size = 0;

   while(len > 0) {
   	c = (unsigned char)*p++; len--;
	switch(InState) {
   		case IN_CR:
   			InState = IN_DATA;
   			if(c == 0 || c == '\n')
   				break;
   			/* fall through */
   		case IN_DATA:
   			if(c == IAC) {
   				InState = IN_IAC;
   				break;
   			}
   			*p2++ = c; size++;
   			if(c == '\r') InState = IN_CR;
   			break;
   		case IN_IAC:
   			switch(c) {
   				case IAC:
	   				*p2++ = c; size++;
   					InState = IN_DATA;
   					break;
   				case WILL:
   				case WONT:
   				case DO:
   				case DONT:
   					InState = IN_IAC2;
   					ThisOpt = c;
   					break;
   				case SB:
   				 	InState = IN_SB; 
   					break;
   				case EOR:
   				case SE:
   				case NOP:
   				case BREAK:
   				case IP:
   				case AO:
   				case AYT:
   				case EC:
   				case EL:
   				case GA:
   					break;
   				default:
   					break;
   			}
   			break;
   		case IN_IAC2:
   			if(size > 0) {
   				write(fdout, buffer, size);
   				p2 = buffer;
   				size = 0;
   			}
   			InState = IN_DATA;
   			switch(ThisOpt) {
   				case WILL:	dowill(c);	break;
   				case WONT:	dowont(c);	break;
   				case DO:	dodo(c);	break;
   				case DONT:	dodont(c);	break;
   			}
   			break;
   		case IN_SB:
 		{
			static int winchpos = -1;
   			/* Subnegotiation. */
   			if(winchpos >= 0) {
				static unsigned int winchbuf[5], iacs = 0;
   				winchbuf[winchpos] = c;
   				/* IAC is escaped - unescape it. */
   				if(c == IAC) iacs++; else { iacs = 0; winchpos++; }
   				if(iacs == 2) { winchpos++; iacs = 0; }
   				if(winchpos >= 4) {
   					/* End of WINCH data. */
   					set_winsize(fdout,
   					(winchbuf[0] << 8) | winchbuf[1],
   					(winchbuf[2] << 8) | winchbuf[3]);
   					winchpos = -1;
   				}
   			} else {
				static int lastiac = 0;
	   			switch(c) {
   					case TELOPT_WINCH:
   						/* Start listening. */
   						winchpos = 0;
   						break;
   					case SE:
   						if(lastiac) InState = IN_DATA;
   						break;
   					default:
   						break;
   				}
   				if(c == IAC) lastiac = 1;
   				else lastiac = 0;


   			}
   			break;
   		}
   	}
   }

   if(size > 0)
   	write(fdout, buffer, size);
}

int tel_out(fdout, buf, size)
int fdout;
char *buf;
int size;
{
char *p;
int got_iac, len;

   p = buf;
   while(size > 0) {
	buf = p;
	got_iac = 0;
	if((p = (char *)memchr(buf, IAC, size)) != (char *)NULL) {
		got_iac = 1;
		p++;
	} else
		p = buf + size;
	len = p - buf;
	if(len > 0)
		(void) write(fdout, buf, len);
	if(got_iac)
		(void) write(fdout, p - 1, 1);
	size = size - len;
   }
}

static void dowill(c)
int c;
{
int ack;

   switch(c) {
	case TELOPT_BINARY:
	case TELOPT_ECHO:
	case TELOPT_SGA:
		if(TelROpts[c] == 1)
			return;
		TelROpts[c] = 1;
		ack = DO;
		break;
	case TELOPT_WINCH:
		if(r_winch) return;
		r_winch = 1;
		ack = DO;
 		respond_really(ack, c); 
		return;
	default:
		ack = DONT;
   }

   respond(ack, c);
}

static void dowont(c)
int c;
{
   if(c <= LASTTELOPT) {
	if(TelROpts[c] == 0)
		return;
	TelROpts[c] = 0;
   }
   respond(DONT, c);
}

static void dodo(c)
int c;
{
int ack;

   switch(c) {
	default:
		ack = WONT;
   }
   respond(ack, c);
}

static void dodont(c)
int c;
{
   if(c <= LASTTELOPT) {
	if(TelLOpts[c] == 0)
		return;
	TelLOpts[c] = 0;
   }
   respond(WONT, c);
}

static void respond(ack, option)
int ack, option;
{
unsigned char c[3];

   c[0] = IAC;
   c[1] = ack;
   c[2] = option;
/*   write(telfdout, c, 3); */
}

static void respond_really(ack, option)
int ack, option;
{
unsigned char c[3];

   c[0] = IAC;
   c[1] = ack;
   c[2] = option;
   write(telfdout, c, 3); 
}
