#include <stdio.h>
#include <ctype.h>
#include "config.h"
#include "global.h"
#include "mbuf.h"
#include "timer.h"
#include "icmp.h"
#include "netuser.h"
#include "tcp.h"
#include "telnet.h"
#include "session.h"
#include "ftp.h"
#include "iface.h"
#include "ax25.h"
#include "lapb.h"
#include "finger.h"
#include "nr4.h"
#ifdef	UNIX
#include <string.h>
#endif

#define	CTLZ	26

#if !defined( _MEMORY_H) && !defined(_STRING_H)
char *memchr();
#endif


static free_telnet();
static void answer();
extern char nospace[];
extern char badhost[];
int refuse_echo = 0;
int unix_line_mode = 0;    /* if true turn <cr> to <nl> when in line mode */
int debug_options = 0;
extern FILE *trfp;            /* file pointer used for tracing */
void suboption();

char *t_options[] = {
	"BINARY", "ECHO", "RCP", "SGA",	"NAMS",	"STATUS", "TM",
	"RCTE", "NAOL", "NAOP", "NAOCRD", "NAOHTS", "NAOHTD", "NAOFFD", 
	"NAOVTS", "NAOVTD", "NAOLFD", "XASCII", "LOGOUT", "BM", "DET", 
	"SUPDUP", "SUPDUPOUOT", "SNDLOC", "TTYPE", "EOR", "TACACS", 
	"OUTMARK", "LOCNUM", "TN3270", "X3PAD", "NAWS", "TSPEED",
	"LFLOW", "LINEMODE", "XDISPLOC"
};

/* Execute user telnet command */
int
dotelnet(argc,argv)
int argc;
char *argv[];   /* argv[1] = host,  argv[2] = service */
{
	void t_state(),rcv_char(),tn_tx();
	char *inet_ntoa();
	int32 resolve();
	int send_tel();
        int unix_send_tel();
	struct session *s;
	struct telnet *tn;
	struct tcb *tcb;
	struct socket lsocket,fsocket;

	lsocket.address = ip_addr;
	lsocket.port = lport++;
	printf("Lookup ...");
	if((fsocket.address = resolve(argv[1])) == 0){
		printf(badhost,argv[1]);
		return 1;
	}
	
	if(argc < 3)
		fsocket.port = TELNET_PORT;
	else
		fsocket.port = service_code(argv[2]);
	if(fsocket.port<0){
	        printf("Unknown service  %s\n", argv[2]);
		return(1);
	      }
	printf(" done. Trying to connect to %s ...\n",
	       inet_ntoa( fsocket.address));
	/* Allocate a session descriptor */
	if((s = newsession()) == NULLSESSION){
		printf("Too many sessions\n");
		return 1;
	}
	if((s->name = malloc((unsigned)strlen(argv[1])+1)) != NULLCHAR)
		strcpy(s->name,argv[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,tn_tx,t_state,0,(char *)tn);

	tn->tcb = tcb;	/* Downward pointer */
	go_mode();
	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;
	if(current == NULLSESSION || current->cb.telnet == NULLTN
	 || current->cb.telnet->tcb == NULLTCB)
		return;
	/* If we're doing our own echoing and recording is enabled, record it */
	if(!current->cb.telnet->remote[TN_ECHO] && current->record != NULLFILE)
		fwrite(buf,1,(int)n,current->record);
	bp = qdata(buf,n);
	send_tcp(current->cb.telnet->tcb,bp);
}

/* set up correct tty modes */
int
tel_setterm(tn)
register struct telnet *tn;
{
	if(current->cb.telnet->remote[TN_ECHO]) {
		raw();	/* other end is echoing, we're raw */
		if (tn->lflow)
			flowon();
		else
			flowoff();
	} else {
		cooked();
		flowdefault();
	}
}

/* 
 * stdio has no way to flush.  So do our own.
 */

/* might as well make the buffer big enough for an Ethernet packet */
#define TBSIZE 2048
char termbuf[TBSIZE];
char *termbufp = termbuf;
char *termbufe = termbuf + TBSIZE;

int
tnputchar(c)
int c;
{
  if (termbufp >= termbufe) {
    write(1, termbuf, TBSIZE);
    termbufp = termbuf;
  }
  *termbufp++ = c;
}

/* send out buffered data */
int
tnflush()
{
  if (termbufp > termbuf)
    write(1, termbuf, termbufp - termbuf);
  termbufp = termbuf;
}

/* clear output -- should also do clearout to do Unix clear */
int
tnclear()
{
  termbufp = termbuf;
}

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


	/* 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 (! tn->outsup) {
				if((record = tn->session->record) != NULLFILE)
					fwrite(bp->data,1,(int)bp->cnt,record);
				while(bp->cnt-- != 0)
					tnputchar(*bp->data++);
			}
			bp = free_mbuf(bp);
		}
	}
	while(pullup(&bp,&c,1) == 1){
		switch(tn->state){
		case TS_DATA:
			if(uchar(c) == IAC){
				tn->state = TS_IAC;
			} else {
#ifdef undef
				if(!tn->remote[TN_TRANSMIT_BINARY])
					c &= 0x7f;
#endif
				if (! tn->outsup) {
					tnputchar(c);
					if((record = tn->session->record) 
					   != NULLFILE)
						putc(c,record);
				}
			}
			break;
		case TS_IAC:
process_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 TN_DM:
				/*
				 * if outsup > 1, we are still in urgent
				 * data, so DM is ignored
				 */
				if (tn->outsup == 1) {
					tn->outsup = 0;
					if (debug_options)
						fprintf(trfp, "[End of urgent data]\n");
				}
				tn->state = TS_DATA;
				break;
			case SB:
				SB_CLEAR();
				tn->state = TS_SB;
				break;
			case IAC:
				tnputchar(c);
				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;
	        case TS_SB:
			if (uchar(c) == IAC) {
				tn->state = TS_SE;
				break;
			} else {
				SB_ACCUM(c);
			}
			break;
		case TS_SE:
			if (uchar(c) != SE) {
			  if (uchar(c) != IAC) {
			    /*
			     * This is an error.  We only expect to get
			     * "IAC IAC" or "IAC SE".  Several things may
			     * have happend.  An IAC was not doubled, the
			     * IAC SE was left off, or another option got
			     * inserted into the suboption are all possibilities.
			     * If we assume that the IAC was not doubled,
			     * and really the IAC SE was left off, we could
			     * get into an infinate loop here.  So, instead,
			     * we terminate the suboption, and process the
			     * partial suboption if we can.
			     */
			    SB_ACCUM(IAC);
			    SB_ACCUM(c);
			    tn->subpointer -= 2;
			    SB_TERM();
			    suboption(tn);	/* handle sub-option */
			    tn->state = TS_IAC;
			    goto process_iac;
			  }
			  SB_ACCUM(c);
			  tn->state = TS_SB;
			} else {
			  SB_ACCUM(IAC);
			  SB_ACCUM(SE);
			  tn->subpointer -= 2;
			  SB_TERM();
			  suboption(tn);	/* handle sub-option */
			  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;
	FILE *record;
#ifdef	FLOW
	extern int ttyflow;
#endif
	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
#ifdef	FLOW
	 || !ttyflow	/* Or if blocked by keyboard input -- hyc */
#endif
	 || current->type != TELNET || current->cb.telnet != tn)
		return;

/*
 * Urgent data is done by combination of the TCP level and this code.
 * The TCP level sets URGCUR and tcb->up (urgent pointer), but we
 * clear it at this level.  We want to clear it when we've taken
 * data beyond the urgent pointer, and only here are we in a position
 * to control that.  The code is better here anyway, because other
 * applications may treat the urgent pointer differently.  Rlogin
 * for example, would have rather different code.  This implementation
 * is a layering violation, but lets us get very good results.
 */
	if (tcb->flags & URGCUR) {
		/* we are in urgent data */
		int32 start = tcb->rcv.nxt - tcb->rcvcnt;
		int32 count;

/*
 * we use rcv.up - 2  as the end of urgent data so that the DM (which
 * is 2 characters isn't included.  Actually one could argue that it should
 * be - 1 because of the modification to the definition of urgent 
 * introduced in the host requirements RFC and other official modifications,
 * but - 2 is safe.  Note that outsup == 2 indicates that we're still in
 * urgent data, so we should ignore any DM.
 */
		if (seq_gt (tcb->rcv.up - 2, start)) {
			/* 
			 * at least some data in this segment is urgent.
			 * clear output and set outsup to suppress output.
			 */
			tnclear();
			clearout();
			tn->outsup = 2;
			/* see how much data is urgent */
			count = (tcb->rcv.up - 2) - start;
			if (count > cnt)
				count = cnt;
			if(recv_tcp(tcb,&bp,count) > 0) { /* get from TCP */
				tel_input(tn,bp);  /* process through telnet */
				/* any data beyond urgent? */
				cnt -= count;
				if (cnt > 0) {
					/*
					 * say we're no longer in urgent
					 * data.  outsup 1 means to suppress
					 * output until next DM.
					 */
					tcb->flags &= ~URGCUR;
					tn->outsup = 1;
					if(recv_tcp(tcb,&bp,cnt) > 0)
						tel_input(tn,bp);
				}
			}
		} else {
			/* 
			 * no more urgent data.  Clear urgent flag and
			 * set so suppress output stops at next DM
			 */
			tcb->flags &= ~URGCUR;
			tn->outsup = 1;
			if(recv_tcp(tcb,&bp,cnt) > 0)
				tel_input(tn,bp);
	        }
	} else {
	/* normal (no urgent data) case */
	if(recv_tcp(tcb,&bp,cnt) > 0)
		tel_input(tn,bp);
        }

	tnflush();  /* flush tty output buffer */
	if((record = tn->session->record) != NULLFILE)
		fflush(record);
}
/* Handle transmit upcalls. Used only for file uploading */
void
tn_tx(tcb,cnt)
struct tcb *tcb;
int16 cnt;
{
	struct telnet *tn;
	struct session *s;
	struct mbuf *bp;
	int size;

	if((tn = (struct telnet *)tcb->user) == NULLTN
	 || (s = tn->session) == NULLSESSION
	 || s->upload == NULLFILE)
		return;
	if((bp = alloc_mbuf(cnt)) == NULLBUF)
		return;
	if((size = fread(bp->data,1,(int)cnt,s->upload)) > 0){
		bp->cnt = (int16)size;
		send_tcp(tcb,bp);
	} else {
		free_p(bp);
	}
	if(size != cnt){
		/* Error or end-of-file */
		fclose(s->upload);
		s->upload = NULLFILE;
		free(s->ufile);
		s->ufile = NULLCHAR;
	}
}

/* State change upcall routine */
/*ARGSUSED*/
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;
		cooked();	/* prettify things... -- hyc */
	}

	switch(new){
	case CLOSE_WAIT:
		if(notify)
			printf("%s\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(")\n");
			cmdmode();
		}
		del_tcp(tcb);
		if(tn != NULLTN)
			free_telnet(tn);
		break;
	default:
		if(notify)
			printf("%s\n",tcpstates[new]);
		break;
	}
	/* not sure which of these is needed */
	tnflush();  
	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 */
void
willopt(tn,opt)
struct telnet *tn;
char opt;
{
	int ack;
	void answer();

	if (debug_options) {
		fprintf(trfp, "[Recv: will ");
		if(uchar(opt) <= NOPTIONS)
			fprintf(trfp, "%s]\n",t_options[opt]);
		else
			fprintf(trfp, "%u]\n",opt);
	}
	
	switch(uchar(opt)){
	case TN_TRANSMIT_BINARY:
	case TN_ECHO:
	case TN_SUPPRESS_GA:
		if(tn->remote[uchar(opt)] == 1)
			return;		/* Already set, ignore to prevent loop */
		if(uchar(opt) == TN_ECHO){
			if(refuse_echo){
				/* User doesn't want to accept */
				ack = DONT;
				break;
			} else {
				/* must set before calling tel_setterm */
				tn->remote[uchar(opt)] = 1;
				tel_setterm(tn); /* raw mode etc. */
			}
		} else
			tn->remote[uchar(opt)] = 1;
		ack = DO;			
		break;
	default:
		ack = DONT;	/* We don't know what he's offering; refuse */
	}
	answer(tn,ack,opt);
}
void
wontopt(tn,opt)
struct telnet *tn;
char opt;
{
	void answer();

	if (debug_options) {
		fprintf(trfp, "[Recv: wont ");
		if(uchar(opt) <= NOPTIONS)
			fprintf(trfp, "%s]\n",t_options[opt]);
		else
			fprintf(trfp, "%u]\n",opt);
	}

	if(uchar(opt) <= NOPTIONS){
		if(tn->remote[uchar(opt)] == 0)
			return;		/* Already clear, ignore to prevent loop */
		tn->remote[uchar(opt)] = 0;
		if(uchar(opt) == TN_ECHO)
			tel_setterm(tn);  /* cooked mode, etc. */
	}
	answer(tn,DONT,opt);	/* Must always accept */
}
void
doopt(tn,opt)
struct telnet *tn;
char opt;
{
	void answer();
	int ack;

	if (debug_options) {
		fprintf(trfp, "[Recv: do ");
		if(uchar(opt) <= NOPTIONS)
			fprintf(trfp, "%s]\n",t_options[opt]);
		else
			fprintf(trfp, "%u]\n",opt);
	}

	switch(uchar(opt)){
	case TN_TTYPE:
		if(tn->local[uchar(opt)] == 1)
			return;		/* Already set, ignore to prevent loop */
		tn->local[uchar(opt)] = 1;
#if 0
		ack = WILL;
#else /* dje hack */
		ack = WONT;
#endif
		break;

	case TN_LFLOW:
		if(tn->local[uchar(opt)] == 1)
			return;		/* Already set, ignore to prevent loop */
		tn->local[uchar(opt)] = 1;
		tn->lflow = 1;  /* protocol says initialize to known state */
		tel_setterm(tn);  /* enable flow */
		ack = WILL;
		break;
	default:
		ack = WONT;	/* Don't know what it is */
	}
	answer(tn,ack,opt);
}
void
dontopt(tn,opt)
struct telnet *tn;
char opt;
{
	void answer();

	if (debug_options) {
		fprintf(trfp, "[Recv: dont ");
		if(uchar(opt) <= NOPTIONS)
			fprintf(trfp, "%s]\n",t_options[opt]);
		else
			fprintf(trfp, "%u]\n",opt);
	}

	if(uchar(opt) <= NOPTIONS){
		if(tn->local[uchar(opt)] == 0){
			/* Already clear, ignore to prevent loop */
			return;
		}
		tn->local[uchar(opt)] = 0;
	}
	answer(tn,WONT,opt);
}

void
suboption(tn)
register struct telnet *tn;
{
   if (debug_options) {
        int opt = SB_PEEK();
  	fprintf(trfp, "[Recv: subopt ");
		if(opt <= NOPTIONS)
			fprintf(trfp, "%s]\n",t_options[opt]);
		else
			fprintf(trfp, "%u]\n",opt);
    }

    switch (SB_GET()) {
    case TN_LFLOW:
	if (tn->local[TN_LFLOW] == 0)
	    return;
	if (SB_EOF())
	    return;
	switch(SB_GET()) {
	case 1:
	    tn->lflow = 1;
	    break;
	case 0:
	    tn->lflow = 0;
	    break;
	default:
	    return;
	}
	tel_setterm(tn);  /* get flow control right */
	break;
    case TN_TTYPE:
	if (tn->local[TN_TTYPE] == 0)
	    return;
	if (SB_EOF() || SB_GET() != 1) {
	    return;
	} else {
	    char *name;
	    unsigned char temp[50];
	    int len;
	    struct mbuf *bp;

	    name = getenv("TERM");
	    if(!name) name="tty";        /* dje hack 31-7-92 */
	    len = strlen(name) + 4 + 2;
	    if (len < sizeof(temp)) {
		sprintf((char *)temp, "%c%c%c%c%s%c%c", IAC, SB, TN_TTYPE,
				0, name, IAC, SE);
		bp = qdata((char *)temp,len);
		send_tcp(tn->tcb,bp);
		if (debug_options)
		  fprintf(trfp, "[Send: term %s]\n", name);
	    } else
	        return;
	}
	break;

    default:
	break;
    }
}

static
void
answer(tn,r1,r2)
struct telnet *tn;
int r1,r2;
{
	struct mbuf *bp;
	char s[3];

	if (debug_options) {
	  switch(r1){
	  case WILL:
	    fprintf(trfp, "[Sent: will ");
	    break;
	  case WONT:
	    fprintf(trfp, "[Sent: wont ");
	    break;
	  case DO:
	    fprintf(trfp, "[Sent: do ");
	    break;
	  case DONT:
	    fprintf(trfp, "[Sent: dont ");
	    break;
	  }
	  if(uchar(r2) <= NOPTIONS)
	    fprintf(trfp, "%s]\n",t_options[r2]);
	  else
	    fprintf(trfp, "%u]\n",r2);
	}

	s[0] = IAC;
	s[1] = r1;
	s[2] = r2;
	bp = qdata(s,(int16)3);
	send_tcp(tn->tcb,bp);
}

static struct svc
{
char *name;
int service_code;
} services[]={
  "rje",5,
  "echo",7,
  "discard",9,
  "users",11,
  "daytime",13,
  "netstat",15,
  "quote",17,
  "chargen",19,
  "ftp-data",20,
  "ftp",21,
  "telnet",23,
  "smtp",25,
  "time",37,
  "rlp",39,
  "nameserver",42,
  "nicname",43,
  "domain",53,
  "bootps",67,
  "bootpc",58,
  "tftp",69,
  "finger",79,
  "supdup",95,
  "hostname",101,
  "auth",113,
  "uucp-path",117,
  "nntp",119,
  "ntp",123,
  (char *)0,-1
  };

int service_code(str)
char *str;
{
char sp[20];
char *cp, *ip ;
int i;
struct svc *svc = services;
cp=sp; ip =str; 

if(!(i=strlen(str)) || i >19 )return(-1);
/* if str is numeric, convert it to an integer */
if( *str >='0' && *str <='9') return(atoi(str));  
while(*ip != '\0'){ *cp++ = tolower(*ip); ip++; }
*cp='\0';
while( svc->name ){
  if(!strcmp(svc->name,sp)) {
    printf("%s service is code %d\n",str,svc->service_code);
    return(svc->service_code);
  }
  svc++;
}
printf("Invalid service mnemonic %s\n",str);

return(-1);
}
