#include <stdio.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"

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

/* Execute user telnet command */
int
dotelnet(argc,argv)
int argc;
char *argv[];
{
	void t_state(),rcv_char();
	char *inet_ntoa(),*calloc();
	int32 aton();
	int send_tel();
        int unix_send_tel();
	struct session *s,*newsession();
	struct telnet *tn;
	struct tcb *tcb;
	struct socket lsocket,fsocket;


	lsocket.address = ip_addr;
	lsocket.port = lport++;
	fsocket.address = aton(argv[1]);
	if(argc < 3)
		fsocket.port = TELNET_PORT;
	else
		fsocket.port = atoi(argv[2]);

	/* 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;
}

/* Process typed characters */
int
unix_send_tel(buf,n)
char *buf;
int16 n;
{
	int i;

	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;
{
	struct mbuf *bp,*qdata();
	if(current == NULLSESSION || current->cb.telnet == NULLTN
	 || current->cb.telnet->tcb == NULLTCB)
		return;
	bp = qdata(buf,n);
	send_tcp(current->cb.telnet->tcb,bp);
}

/* Process incoming TELNET characters */
int
tel_input(tn,bp)
register struct telnet *tn;
struct mbuf *bp;
{
	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(pullup(&bp,&c,1) == 1){
		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;
		}
	}
}

/* Telnet receiver upcall routine */
void
rcv_char(tcb,cnt)
register struct tcb *tcb;
int16 cnt;
{
	struct mbuf *bp;
	struct telnet *tn;

	if((tn = (struct telnet *)tcb->user) == NULLTN){
		/* Unknown connection; ignore it */
		return;
	}
	/* Hold output if we're not the current session */
	if(mode != CONV_MODE || current == NULLSESSION || current->cb.telnet != tn)
		return;

	if(recv_tcp(tcb,&bp,cnt) > 0)
		tel_input(tn,bp);

	fflush(stdout);
}

/* 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);
}
/* Delete telnet structure */
static
free_telnet(tn)
struct telnet *tn;
{
	if(tn->session != NULLSESSION)
		freesession(tn->session);

	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;
	bp = qdata(s,(int16)3);
	send_tcp(tn->tcb,bp);
}
