#include <stdio.h>
#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "timer.h"
#include "icmp.h"
#include "netuser.h"
#include "tcp.h"
#include "telnet.h"
#include "session.h"

#define TUMAXSCAN 3	/* max number of telunix clients active */
#define TURQSIZ	512	/* max data we will request from pty at once */

struct tcb *tnix_tcb = NULLTCB;
struct tcb *tnixtcb[TUMAXSCAN];	/* savebuf for tcb ptrs for scan routines */
extern int errno;
#ifdef	SYS5
extern unsigned long selmask;
#endif

/* Start telnet-unix server */
tnix1(argc,argv)
char *argv[];
{
	struct socket lsocket;
	extern int32 ip_addr;
	void tnix_state();

	/* Incoming Telnet */
	lsocket.address = ip_addr;

	if(argc < 2)
		lsocket.port = TELNET_PORT;
	else
		lsocket.port = atoi(argv[1]);

	tnix_tcb = open_tcp(&lsocket,NULLSOCK,TCP_SERVER,0,
			NULLVFP,NULLVFP,tnix_state,0,(char *)NULL);

	if(tnix_tcb == NULLTCB)
		fprintf(stderr,"start telunix fails rsn %d.\n",net_error);
	else
		log(tnix_tcb,"STARTED Telunix - (%d %x)",
			lsocket.port,tnix_tcb);
}

/* Shut down Telnet server */
tnix0()
{
	if(tnix_tcb != NULLTCB) {
		log(tnix_tcb,"STOPPED Telunix - (%x)",tnix_tcb);
		close_tcp(tnix_tcb);
	} else
		fprintf(stderr,"stop telunix fails -- no server active.\n");
}

/* Handle incoming Telnet-Unix connect requests */
static void
tnix_state(tcb,old,new)
struct tcb *tcb;
char old,new;
{
	register struct telnet *tn;
	int tnix_addscan();
	extern void tnix_rmvscan();
	extern int send_tcp();
	extern struct mbuf *free_p();
	extern int del_tcp();
	extern int close_tcp();
#ifdef	SYS5
	extern void free();
#endif

	tn = (struct telnet *)tcb->user;

	switch(new){
	case ESTABLISHED:
		/* Create and initialize a Telnet protocol descriptor */
		if((tn = (struct telnet *)calloc(1,sizeof(struct telnet))) == NULLTN){
			log(tcb,"reject Telunix - no space");
			sndmsg(tcb,"Rejected; no space on remote\n");
			close_tcp(tcb);
			return;
		}
		tn->session = NULLSESSION;
		tn->state = TS_DATA;
		tcb->user = (char *)tn;	/* Upward pointer */
		tn->tcb = tcb;		/* Downward pointer */
		tn->inbuf = NULLBUF;
		tn->outbuf = NULLBUF;
		if(tnix_addscan(tcb) < 0 ||
		   (tn->fd = OpenPty()) < 3) {	/* barf if <= stderr */
			tnix_rmvscan(tcb);
			log(tcb,"reject Telunix - no Unix ports");
			sndmsg(tcb,
			    "Rejected; no ports available on remote\n");
			close_tcp(tcb);
			return;
		}
#ifdef	SYS5
		selmask |= (1 << tn->fd);
#endif
		log(tcb,"open Telunix - (%d %x %d %d)",tn->fd,tcb,old,new);
		break;

	case FINWAIT1:
	case FINWAIT2:
	case CLOSING:
	case LAST_ACK:
	case TIME_WAIT:
		if(tn != NULLTN &&
		   tn->fd > 2) {
			log(tcb,"close Telunix - (%d %x %d %d)",
				tn->fd,tcb,old,new);
			close(tn->fd);
#ifdef	SYS5
			selmask &=  ~(1 << tn->fd);
#endif
			tn->fd = 0;
		}
		tnix_rmvscan(tcb);
		break;

	case CLOSE_WAIT:
		/* flush that last buffer */
		if(tn != NULLTN &&
		   tn->outbuf != NULLBUF &&
		   tn->outbuf->cnt != 0) {
			send_tcp(tcb,tn->outbuf);
			tn->outbuf = NULLBUF;
		}
		close_tcp(tcb);
		break;
	
	case CLOSED:
		if(tn != NULLTN) {
			if(tn->fd > 2) {
				log(tcb,"close Telunix - (%d %x %d %d)",
					tn->fd,tcb,old,new);
				close(tn->fd);
#ifdef	SYS5
				selmask &=  ~(1 << tn->fd);
#endif
				tn->fd = 0;
			}
			if(tn->inbuf != NULLBUF)
				free_p(tn->inbuf);
			if(tn->outbuf != NULLBUF)
				free_p(tn->outbuf);
			free((char *)tn);
		}
		tnix_rmvscan(tcb);
		del_tcp(tcb);
		if(tcb == tnix_tcb)
			tnix_tcb = NULLTCB;
		break;
	}
}

/* Telunix io interface.  Called periodically to process any waiting io.  */
void
tnix_try(tcb)
register struct tcb *tcb;
{
	extern void tnix_rmvscan();
	extern int recv_tcp();
	extern int send_tcp();
	extern void tnix_input();

	register struct telnet *tn;
	register int i;

	if((tn = (struct telnet *)tcb->user) == NULLTN || tn->fd < 3) {
		/* Unknown connection - remove it from queue */
		log(tcb,"error Telnet - tnix_try (%d)", tn);
		tnix_rmvscan(tcb);
		return;
	}
	/*
	 * First, check if there is any pending io for the pty:
	 */
	if(tn->inbuf != NULLBUF) {
		tnix_input(tn);
	}
	if(tn->inbuf == NULLBUF) {
		if(tcb->rcvcnt > 0 &&
		   recv_tcp(tcb,&(tn->inbuf),0) > 0)
			tnix_input(tn);
	}
	/*
	 * Next, check if there is any io for tcp:
	 */
	do {
		if(tn->outbuf == NULLBUF &&
		   (tn->outbuf = alloc_mbuf(TURQSIZ)) == NULLBUF)
			return;		/* can't do much without a buffer */
	
		if(tn->outbuf->cnt < TURQSIZ) {
			if((i = read(tn->fd, tn->outbuf->data + tn->outbuf->cnt,
				(int)(TURQSIZ - tn->outbuf->cnt))) == -1) {
				log(tcb,"error Telunix - read (%d %d %d)",
					errno, tn->fd,
					TURQSIZ - tn->outbuf->cnt);
				close_tcp(tcb);
				return;
			}
			if((tn->outbuf->cnt += i) < TURQSIZ)
				i = 0;	/* didn't fill buffer so don't retry */
		} else {
			i = -1;		/* any nonzero value will do */
		}
		if(tn->outbuf->cnt == 0 ||	/* nothing to send */
		   tcb->sndcnt > tcb->window)	/* too congested to send */
			return;
		if(send_tcp(tcb,tn->outbuf) < 0) {
			log(tcb,"error Telunix - send_tcp (%d %d %d)",
				net_error, tn->fd, tn->outbuf->cnt);
			close_tcp(tcb);
			tn->outbuf = NULLBUF;
			return;
		}
		tn->outbuf = NULLBUF;
	} while(i);
}

/* Process incoming TELNET characters */
void
tnix_input(tn)
register struct telnet *tn;
{
	void doopt(),dontopt(),willopt(),wontopt(),answer();
	char *memchr();
	register int i;
	register struct mbuf *bp;
	char c;

	bp = tn->inbuf;

	/* Optimization for very common special case -- no special chars */
	if(tn->state == TS_DATA){
		while(bp != NULLBUF &&
		      memchr(bp->data,IAC,(int)bp->cnt) == NULLCHAR) {
			if((i = write(tn->fd, bp->data, (int)bp->cnt)) == bp->cnt) {
				tn->inbuf = bp = free_mbuf(bp);
			} else if(i == -1) {
				log(tn->tcb,"error Telunix - write (%d %d %d)",
					errno, tn->fd, bp->cnt);
				close_tcp(tn->tcb);
				return;
			} else {
				bp->cnt -= i;
				bp->data += i;
				return;
			}
		}
		if(bp == NULLBUF)
			return;
	}
	while(pullup(&(tn->inbuf),&c,1) == 1){
		bp = tn->inbuf;
		switch(tn->state){
		case TS_DATA:
			if(uchar(c) == IAC){
				tn->state = TS_IAC;
			} else {
				if(!tn->remote[TN_TRANSMIT_BINARY])
					c &= 0x7f;
				if(write(tn->fd, &c, 1) != 1) {
					/* we drop a character here */
					return;
				}
			}
			break;
		case TS_IAC:
			switch(uchar(c)){
			case WILL:
				tn->state = TS_WILL;
				break;
			case WONT:
				tn->state = TS_WONT;
				break;
			case DO:
				tn->state = TS_DO;
				break;
			case DONT:
				tn->state = TS_DONT;
				break;
			case IAC:
				if(write(tn->fd, &c, 1) != 1) {
					/* we drop a character here */
					return;
				}
				tn->state = TS_DATA;
				break;
			default:
				tn->state = TS_DATA;
				break;
			}
			break;
		case TS_WILL:
			willopt(tn,c);
			tn->state = TS_DATA;
			break;
		case TS_WONT:
			wontopt(tn,c);
			tn->state = TS_DATA;
			break;
		case TS_DO:
			doopt(tn,c);
			tn->state = TS_DATA;
			break;
		case TS_DONT:
			dontopt(tn,c);
			tn->state = TS_DATA;
			break;
		}
	}
}

/* called periodically from main loop */
void
tnix_scan()
{
	void tnix_try();
	register int i;

	for(i = 0; i < TUMAXSCAN; i += 1)
		if(tnixtcb[i] != NULLTCB)
			tnix_try(tnixtcb[i]);
}

int
tnix_addscan(tcb)
struct tcb *tcb;
{
	register int i;
	for(i = 0; i < TUMAXSCAN; i += 1)
		if(tnixtcb[i] == NULLTCB) {
			tnixtcb[i] = tcb;
			return i;
		}
	return -1;
}

void
tnix_rmvscan(tcb)
struct tcb *tcb;
{
	register int i;

	for(i = 0; i < TUMAXSCAN; i += 1)
		if(tnixtcb[i] == tcb)
			tnixtcb[i] = NULLTCB;
}
