#include <stdio.h>
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/tasks.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <proto/exec.h>
#include <devices/console.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#include <dos.h>
#include "machdep.h"
#include "mbuf.h"
#include "timer.h"
#include "internet.h"
#include "icmp.h"
#include "netuser.h"
#include "tcp.h"
#include "telnet.h"
#include "session.h"
#include "inetdev.h"
#include "inetlib.h"
#define DEBUG
struct Process *mytask;
APTR	oldwindowptr;
struct IntuitionBase *IntuitionBase;
char banner[80] = "telnet window";
static struct NewWindow nw = {
	0, 0, 640, 200,		/* left, top, (max) width, (max) height */
	0, 1,			/* detail pen, block pen */
	0,			/* IDCMP flags */
	SMART_REFRESH | WINDOWDRAG | WINDOWDEPTH | WINDOWSIZING |
	    SIZEBBOTTOM | ACTIVATE | NOCAREREFRESH,	/* window flags */
	NULL, NULL,		/* gadget, checkmark */
	(UBYTE *)&banner[0],	/* title of window */
	NULL, NULL,		/* screen, bitmap */
	200, 50, -1, -1,	/* sizing limits */
	WBENCHSCREEN,		/* on the workbench */
};
struct Window *win;
struct MsgPort *keyboard, *consinp, *consoutp, *tcpinp, *tcpoutp;
struct IOStdReq consin, consout;
char InputCharacter;
int deviceopened = 0;
struct IOINETReq tnreq, tninreq, tnoutreq;
char recv[512], snd[512];
struct telnet *tn;
#ifdef LATTICE
extern struct { short error; char *msg; } os_errlist[];
extern int _OSERR, os_nerr;
#endif
static
clean(why)
	char *why;
{
	int i;
	InputCharacter = ' ';
        while (InputCharacter != '<')
	  if (kbread() > 0)
	    amigaputchar(InputCharacter);

	if (win)
		CloseWindow(win);
	if (consinp)
		DeletePort(consinp);
	if (consoutp)
		DeletePort(consoutp);
	if (tcpinp)
		DeletePort(tcpinp);
	if (tcpoutp)
		DeletePort(tcpoutp);
	if (deviceopened)
		CloseDevice(&tnreq);
	mytask->pr_WindowPtr = oldwindowptr;
	if (why) {
           myoserr(why);
	}
	exit(0);
}
printlist(l)
struct List *l;
{
  printf("head %x tail %x tailpred %x\n", l->lh_Head, l->lh_Tail, 
		l->lh_TailPred);
}
myoserr(why)
char *why;
{
  int i;
		fprintf(stderr, "%s: ", why); 
#ifdef LATTICE
		fprintf(stderr, "%d: ", _OSERR);

		for(i = 0; os_errlist[i].error < os_nerr; i++)
		  if (os_errlist[i].error == _OSERR)
			fprintf(stderr, os_errlist[i].msg);
#endif
		fprintf(stderr, "\r\n");
}
/* Called at startup time to set up console I/O, memory heap */
ioinit()
{
	struct Screen *scr;

	mytask = (struct Process *) FindTask((char *) NULL);
	oldwindowptr = mytask->pr_WindowPtr;
	mytask->pr_WindowPtr = (APTR) -1;	/* disable DOS requestors */

	if ((IntuitionBase = (struct IntuitionBase *)
	   OpenLibrary("intuition.library", 33L)) == NULL)
		clean("No intuition: Version 1.2 of Amiga Systems Software required");
	/*
	 *  Try to determine the size of the workbench screen
	 */
	scr = malloc(sizeof(struct Screen));
	if (scr==NULL)
		clean("Can't alloc screen");

	if (GetScreenData(scr, (ULONG) sizeof(struct Screen),
			  WBENCHSCREEN, NULL) == TRUE) {
		nw.Width = scr->Width;
		nw.Height = scr->Height-20;
		nw.TopEdge = 19;
	} else
		fprintf(stderr, "Can't GetScreenData()\n");

	free((char *)scr);
	if ((win = OpenWindow(&nw)) == NULL)
		clean("Can't open window");
	if ((consinp = CreatePort("telnet:console in", 0L)) == NULL)
		clean("Can't create console port");
	if ((tcpinp = CreatePort("telnet:tcp in", 0L)) == NULL)
		clean("Can't create telnet tcp input port");
	if ((tcpoutp = CreatePort("telnet:tcp out", 0L)) == NULL)
		clean("Can't create telnet tcp output port");

	consin.io_Data = (APTR) win;
	consin.io_Length = sizeof(struct Window);

	_OSERR = OpenDevice("console.device", 0L, &consin, 0L);
	if (_OSERR != 0L){
		printf("opendevice returned %d\n", _OSERR);
		myoserr("could not get console");
		clean("Can't open console device");
	}
	consout = consin;

	consin.io_Message.mn_ReplyPort = consinp;
	consin.io_Length = 1;
	consin.io_Data = (APTR) &InputCharacter;
	consin.io_Command = CMD_READ;
	SendIO(&consin);
	consout.io_Message.mn_ReplyPort = consoutp;
	consout.io_Command = CMD_WRITE;


}
/* Read characters from the keyboard, translating them to "real" ASCII
 * If none are ready, return the -1 from kbraw()
 */
kbread()
{
	char c;

	if (CheckIO(&consin)) {
		WaitIO(&consin);
		c = InputCharacter;
		consin.io_Length = 1;
		consin.io_Data = (APTR) &InputCharacter;
		consin.io_Command = CMD_READ;
		SendIO(&consin);		/* start next read up */
		return (c & 0xff);
	}

	return -1;		/* nuthin here */
}
extern char nospace[];
int refuse_echo = 0;
int unix_line_mode = 0;    /* if true turn <cr> to <nl> when in line mode */

#ifdef	DEBUG
char *t_options[] = {
	"Transmit Binary",
	"Echo",
	"",
	"Suppress Go Ahead",
	"",
	"Status",
	"Timing Mark"
};
#endif
/* Telnet receiver upcall routine */
void
rcv_char()
{
/*printf("rcv_char: %d\n", tninreq.io_Actual);*/
	tel_input(tn,tninreq.io_Data, tninreq.io_Actual);

	fflush(stdout);
}
brk()
{
  clean("ok i iwll quit\n");
}
/* TCP connection states */
char *tcpstates[] = {
	"Closed",
	"Listen",
	"SYN sent",
	"SYN received",
	"Established",
	"FIN wait 1",
	"FIN wait 2",
	"Close wait",
	"Closing",
	"Last ACK",
	"Time wait"
};
/* TCP segment header flags */
char *tcpflags[] = {
	"FIN",	/* 0x01 */
	"SYN",	/* 0x02 */
	"RST",	/* 0x04 */
	"PSH",	/* 0x08 */
	"ACK",	/* 0x10 */
	"URG"	/* 0x20 */
};

/* TCP closing reasons */
char *reasons[] = {
	"Normal",
	"Reset",
	"Timeout",
	"ICMP"
};
char old = LISTEN;
int done = 0;
char *hostname="";
int hostport=0;
char *bannerfmt = "telnet %10s %4d %10s";
void
showstate(old, new)
	char old, new;
{


/*	extern char *tcpstates[];
	extern char *reasons[];
	extern char *unreach[];
	extern char *exceed[];
*/
	/* Can't add a check for unknown connection here, it would loop
	 * on a close upcall! We're just careful later on.
	 */

	sprintf(banner, bannerfmt, hostname, hostport, tcpstates[new]);
	SetWindowTitles(win, banner, -1);
	switch(new){
	case CLOSE_WAIT:
		done = 1;
		break;
	case CLOSED:	/* court adjourned */
/*			printf("%s (%s",tcpstates[new],reasons[tcb->reason]);
			if(tcb->reason == NETWORK){
				switch(tcb->type){
				case DEST_UNREACH:
					printf(": %s unreachable",unreach[tcb->code]);
					break;
				case TIME_EXCEED:
					printf(": %s time exceeded",exceed[tcb->code]);
					break;
				}
			}
			printf(")\r\n");
*/
		done = 1;
		break;
	default:
		break;
	}
	fflush(stdout);

}

/* Execute user telnet command */
main(argc,argv)
int argc;
char *argv[];
{
	extern int _OSERR;
	struct InternetBase *InternetBase;
	int send_tel();
        int unix_send_tel();
	struct session *s;

/*	struct tcb *tcb = NULL;*/
	struct socket lsocket,fsocket;
	ioinit();
	hostname = argv[1];
	old = LISTEN;
	showstate(old, LISTEN);
	tnreq.io_fsocket.address = aton(argv[1]);
	if(argc < 3)
		tnreq.io_fsocket.port = TELNET_PORT;
	else
		tnreq.io_fsocket.port = atoi(argv[2]);
	tnreq.io_Device = NULL;
	tnreq.io_Unit = NULL;
	tnreq.io_Flags = 0;
	tnreq.io_Error = 0;
	InternetBase = (struct InternetBase *) OpenDevice("internet.device",
				(long) INET_UNIT_TCP, &tnreq, 0L);
	if (InternetBase != 0L){
	  printf("it did not open %d\n",InternetBase);
	  clean("i quit");
	}
/*	tcb = (struct tcb *) tnreq.io_Unit->iu_ccb;*/

	hostport = tnreq.io_lsocket.port;
	deviceopened = 1;
	tninreq = tnreq; /* possible lettuce bug  ?*/
	tnoutreq = tnreq;
	tninreq.io_Length = 512;
	tnoutreq.io_Length = 1;
	tninreq.io_Data = recv;
	tnoutreq.io_Data = &InputCharacter;
	tninreq.io_Command = CMD_READ;
	tnoutreq.io_Command = CMD_WRITE;
	tninreq.io_Message.mn_ReplyPort = tcpinp;
	tnoutreq.io_Message.mn_ReplyPort = tcpoutp;
	/* Create and initialize a Telnet protocol descriptor */
	if((tn = (struct telnet *)calloc(1,sizeof(struct telnet))) == NULLTN){
		myoserr("calloc faiuled\n");
		goto done;
	}
	tn->session = s;	/* Upward pointer */
	tn->state = TS_DATA;
        SendIO(&tninreq);
	onbreak(&brk);
	InputCharacter = ' ';
	while (! done)
	  {
	    if ((snd[0] = kbread()) >= 0){
	      
	      unix_send_tel(snd, (short) 1);}

	    if (CheckIO(&tninreq))
	      {
		chkabort();
		WaitIO(&tninreq);
	        rcv_char();
	        if (tninreq.io_State != old)
	          {
		    showstate(old, tninreq.io_State);
		    old = tninreq.io_State;
	          }
    	        SendIO(&tninreq);
	      }
	  }
done:
	clean("All done");	      

#ifdef NOTDEF
	/* Allocate a session descriptor */
	if((s = newsession()) == NULLSESSION){
		printf("Too many sessions\r\n");
		return 1;
	}
	s->type = TELNET;
	if ((refuse_echo == 0) && (unix_line_mode != 0)) {
		s->parse = unix_send_tel;
	} else {
		s->parse = send_tel;
	}
	current = s;

	/* Create and initialize a Telnet protocol descriptor */
	if((tn = (struct telnet *)calloc(1,sizeof(struct telnet))) == NULLTN){
		printf(nospace);
		s->type = FREE;
		return 1;
	}
	tn->session = s;	/* Upward pointer */
	tn->state = TS_DATA;
	s->cb.telnet = tn;	/* Downward pointer */

	tcb = open_tcp(&lsocket,&fsocket,TCP_ACTIVE,0,
	 rcv_char,NULLVFP,t_state,0,(int *)tn);
	if(tcb == NULLTCB || tcb->state == CLOSED){
		/* This is actually a bit dirty here. About the only time the
		 * state will be closed here is if we tried to connect to
		 * ourselves and got RST'ed.  If this is true then the close
		 * upcall will already have freed the TCB and telnet block,
		 * so we're looking at the TCB after it's back on the heap.
		 */
		return 0;
	}
	tn->tcb = tcb;	/* Downward pointer */
	go();
	return 0;
#endif
}

/* Process typed characters */
int
unix_send_tel(buf,n)
char *buf;
int16 n;
{
	int i;
/*printf("unix_send_tel: buf[0] %d\n", buf[0]);*/
	for (i=0; (i<n) && (buf[i] != '\r'); i++)
		;
	if (buf[i] == '\r') {
		buf[i] = '\n';
		n = i+1;
	}
	send_tel(buf,n);
}
int
send_tel(buf,n)
char *buf;
int16 n;
{
	int i;

	tnoutreq.io_Data = buf;
	tnoutreq.io_Length = n;

	SendIO(&tnoutreq);
/*	printf("now waitio insend_tel: ");*/
	i = WaitIO(&tnoutreq); 
/*printf("send_tel: WaitIo is %d\n", i);*/
	if (tnoutreq.io_State != old)
	  {
	    showstate(old, tnoutreq.io_State);
	    old = tnoutreq.io_State;
	  }

}

/* Process incoming TELNET characters */
int
tel_input(tn,bp, len)
register struct telnet *tn;
char *bp;
int len;
{
	char c;
	int ci;
	void doopt(),dontopt(),willopt(),wontopt(),answer();
#ifdef	FAST	/* DON'T USE -- Aztec memchr() routine is broken */
	char *memchr();

	/* Optimization for very common special case -- no command chars */
	if(tn->state == TS_DATA){
		while(bp != NULLBUF && memchr(bp->data,IAC,bp->cnt) 
			== NULLCHAR){
			fflush(stdout);
			write(1,bp->data,bp->cnt);
			bp = free_mbuf(bp);
		}
	}
#endif
	while(len--){
		c = *bp++;
		ci = c & 0xff;
		switch(tn->state){
		case TS_DATA:
			if(ci == IAC){
				tn->state = TS_IAC;
			} else {
				if(!tn->remote[TN_TRANSMIT_BINARY])
					c &= 0x7f;
				putchar(c);
			}
			break;
		case TS_IAC:
			switch(ci){
			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:
				putchar(c);
				tn->state = TS_DATA;
				break;
			default:
				tn->state = TS_DATA;
				break;
			}
			break;
		case TS_WILL:
			willopt(tn,ci);
			tn->state = TS_DATA;
			break;
		case TS_WONT:
			wontopt(tn,ci);
			tn->state = TS_DATA;
			break;
		case TS_DO:
			doopt(tn,ci);
			tn->state = TS_DATA;
			break;
		case TS_DONT:
			dontopt(tn,ci);
			tn->state = TS_DATA;
			break;
		}
	}
}

#ifdef NOTDEF
/* State change upcall routine */
void
t_state(tcb,old,new)
register struct tcb *tcb;
char old,new;
{
	struct telnet *tn;
	char notify = 0;
	extern char *tcpstates[];
	extern char *reasons[];
	extern char *unreach[];
	extern char *exceed[];

	/* Can't add a check for unknown connection here, it would loop
	 * on a close upcall! We're just careful later on.
	 */
	tn = (struct telnet *)tcb->user;

	if(current != NULLSESSION && current->type == TELNET && current->cb.telnet == tn)
		notify = 1;

	switch(new){
	case CLOSE_WAIT:
		if(notify)
			printf("%s\r\n",tcpstates[new]);
		close_tcp(tcb);
		break;
	case CLOSED:	/* court adjourned */
		if(notify){
			printf("%s (%s",tcpstates[new],reasons[tcb->reason]);
			if(tcb->reason == NETWORK){
				switch(tcb->type){
				case DEST_UNREACH:
					printf(": %s unreachable",unreach[tcb->code]);
					break;
				case TIME_EXCEED:
					printf(": %s time exceeded",exceed[tcb->code]);
					break;
				}
			}
			printf(")\r\n");
			cmdmode();
		}
		del_tcp(tcb);
		if(tn != NULLTN)
			free_telnet(tn);
		break;
	default:
		if(notify)
			printf("%s\r\n",tcpstates[new]);
		break;
	}
	fflush(stdout);
}
#endif
/* Delete telnet structure */
static
free_telnet(tn)
struct telnet *tn;
{

	if(tn != NULLTN)
		free((char *)tn);
}

/* The guts of the actual Telnet protocol: negotiating options */
static
void
willopt(tn,opt)
struct telnet *tn;
int opt;
{
	int ack;
	void answer();

#ifdef	DEBUG
	printf("recv: will ");
	if(opt <= NOPTIONS)
		printf("%s\r\n",t_options[opt]);
	else
		printf("%u\r\n",opt);
#endif
	
	switch(opt){
	case TN_TRANSMIT_BINARY:
	case TN_ECHO:
	case TN_SUPPRESS_GA:
		if(tn->remote[opt] == 1)
			return;		/* Already set, ignore to prevent loop */
		if(opt == TN_ECHO){
			if(refuse_echo){
				/* User doesn't want to accept */
				ack = DONT;
				break;
			} else
				raw();		/* Put tty into raw mode */
		}
		tn->remote[opt] = 1;
		ack = DO;			
		break;
	default:
		ack = DONT;	/* We don't know what he's offering; refuse */
	}
	answer(tn,ack,opt);
}
static
void
wontopt(tn,opt)
struct telnet *tn;
int opt;
{
	void answer();

#ifdef	DEBUG
	printf("recv: wont ");
	if(opt <= NOPTIONS)
		printf("%s\r\n",t_options[opt]);
	else
		printf("%u\r\n",opt);
#endif
	if(opt <= NOPTIONS){
		if(tn->remote[opt] == 0)
			return;		/* Already clear, ignore to prevent loop */
		tn->remote[opt] = 0;
		if(opt == TN_ECHO)
			cooked();	/* Put tty into cooked mode */
	}
	answer(tn,DONT,opt);	/* Must always accept */
}
static
void
doopt(tn,opt)
struct telnet *tn;
int opt;
{
	void answer();
	int ack;

#ifdef	DEBUG
	printf("recv: do ");
	if(opt <= NOPTIONS)
		printf("%s\r\n",t_options[opt]);
	else
		printf("%u\r\n",opt);
#endif
	switch(opt){
#ifdef	FUTURE	/* Use when local options are implemented */
		if(tn->local[opt] == 1)
			return;		/* Already set, ignore to prevent loop */
		tn->local[opt] = 1;
		ack = WILL;
		break;
#endif
	default:
		ack = WONT;	/* Don't know what it is */
	}
	answer(tn,ack,opt);
}
static
void
dontopt(tn,opt)
struct telnet *tn;
int opt;
{
	void answer();

#ifdef	DEBUG
	printf("recv: dont ");
	if(opt <= NOPTIONS)
		printf("%s\r\n",t_options[opt]);
	else
		printf("%u\r\n",opt);
#endif
	if(opt <= NOPTIONS){
		if(tn->local[opt] == 0){
			/* Already clear, ignore to prevent loop */
			return;
		}
		tn->local[opt] = 0;
	}
	answer(tn,WONT,opt);
}
static
void
answer(tn,r1,r2)
struct telnet *tn;
int r1,r2;
{
	struct mbuf *bp,*qdata();
	char s[3];

#ifdef	DEBUG
	switch(r1){
	case WILL:
		printf("sent: will ");
		break;
	case WONT:
		printf("sent: wont ");
		break;
	case DO:
		printf("sent: do ");
		break;
	case DONT:
		printf("sent: dont ");
		break;
	}
	if(r2 <= 6)
		printf("%s\r\n",t_options[r2]);
	else
		printf("%u\r\n",r2);
#endif

	s[0] = IAC;
	s[1] = r1;
	s[2] = r2;
	tnoutreq.io_Data = s;
	tnoutreq.io_Length = 3;
	DoIO(&tnoutreq);
/*
	bp = qdata(s,(int16)3);
	send_tcp(tn->tcb,bp);
*/
}

#define	BUFMAXCNT	150
static char conbuf[BUFMAXCNT];
static int concnt = 0;

int
amigaputchar(c)
	char c;
{
	conbuf[concnt++] = c;
	if ((c == '\n') || (concnt == BUFMAXCNT))
		amigaflush();
	return c;
}

amigaflush()
{
	if (concnt == 0)
		return;
	consout.io_Data = (APTR) conbuf;
	consout.io_Length = concnt;
	consout.io_Command = CMD_WRITE;
	DoIO(&consout);
	concnt = 0;
}
	
/*
 *  Begin terrible, horrible hack.  All output should be printed upon (into?)
 *  the window we opened before.  Here goes nothing...
 */

printf(a, b, c, d, e, f, g, h, i, j, k)
	char *a;
	int b, c, d, e, f, g, h, i, j, k;
{
	if (concnt)
		amigaflush();

	sprintf(conbuf, a, b, c, d, e, f, g, h, i, j, k);
	consout.io_Data = (APTR) conbuf;
	consout.io_Length = strlen(conbuf);
	consout.io_Command = CMD_WRITE;
	DoIO(&consout);		/* no use in doing this async */
}
