/* Internet Telnet client
 * Copyright 1991 Phil Karn, KA9Q
 */
/* Mods by PA0GRI */
/* Mods by KO4KS */
#ifdef MSDOS
#include <conio.h>
#endif
#include "global.h"
#include "commands.h"
#include "mbuf.h"
#include "socket.h"
#include "telnet.h"
#include "netuser.h"
#include "usock.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: telnet.c,v 1.24 2001/05/06 16:32:58 brian Exp $";
#endif

#if !defined(TNOS_68K) && !defined(UNIX)
void displayStatLine (int offset, int phase, int onlymarquee);
extern unsigned char SCREENwidth, SCREENlength;
#endif

#ifdef CONVERS
extern char CConsole[];
#endif

static void dontopt (struct telnet *tn,int opt);
static void tel_output (int unused,void *p1,void *p2);
static void answer (struct telnet *tn,int r1,int r2);
static int filemode (FILE * fp, int mode);
static void doopt (struct telnet *tn,int opt);
static void willopt (struct telnet *tn,int opt);
static void wontopt (struct telnet *tn,int opt);

#ifndef CTLZ
#define	CTLZ	26
#endif

static int Refuse_echo = 0;
static int Tn_cr_mode = 0;			/* if true turn <cr> to <cr-nul> */
static int usesplit = 0;		/* kludge for doconf */

static char *conversinit;


#ifdef	DEBUG
char *T_options[] =
{
	"Transmit Binary",
	"Echo",
	"",
	"Suppress Go Ahead",
	"",
	"Status",
	"Timing Mark"
};
#endif



#ifdef	MAILBOX
/* Execute user BBS command */
int
dobbs (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
struct session *sp;
struct sockaddr_in fsocket;

	/*Make sure this comes from console - WG7J*/
	if (Curproc->input != Command->input)
		return 0;

	/* Allocate a session descriptor */
	if ((sp = newsession ("Local PBBS access", TELNET, 0)) == NULLSESSION) {
		tputs (TooManySessions);
		return 1;
	}
	chname (Curproc, "Local PBBS access");
	fsocket.sin_family = AF_INET;
	fsocket.sin_port = IPPORT_TELNET;

	fsocket.sin_addr.s_addr = 0x7f000001L;	/* 127.0.0.1 the loopback interface */
	if ((sp->s = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
		tputs (Nosock);
		(void) keywait (NULLCHAR, 1);
		freesession (sp);
		return 1;
	}
	return tel_connect (sp, (char *) &fsocket, SOCKSIZE);
}
#endif /*MAILBOX*/




#ifdef RLOGINSERV
/* Execute remote login command */
int
dorlogin (int argc OPTIONAL, char *argv[], void *p)
{
	free (argv[0]);
	argv[0] = strdup ("telnet");
	if (argv[2])
		free (argv[2]);
	argv[2] = strdup ("513");
	if (!argv[1])
		argv[1] = strdup (Hostname);
	usesplit = 1;
	return dotelnet (3, argv, p);
}
#endif



#ifdef ALLSESSIONS
#ifdef CONVERS
/* Execute user conference command */
int
doconf (int argc OPTIONAL, char *argv[], void *p)
{
char buf[32], buf2[96];
char *cptr;

	if (CConsole[0])
		(void) strlwr (CConsole);	/* just in case */

	sprintf (buf, "/NA %s %s\n", (CConsole[0]) ? CConsole : "sysop", (argc > 2 && argv[2]) ? argv[2] : "-1");

	free (argv[0]);
	argv[0] = strdup ("telnet");

	if (argc > 1 && !argv[1])	{
		strncpy (buf2, Hostname, 96);
		cptr = strchr (buf2, '.');
		if (cptr)
			*cptr = 0;
		(void) strlwr (buf2);
		argv[1] = strdup (buf2);
	}

	if (argc > 2 && argv[2])
		free (argv[2]);
	argv[2] = strdup ("3600");

	conversinit = buf;		/*lint !e789 */
	usesplit = 1;
	return dotelnet (3, argv, p);
}
#endif /* CONVERS */


#ifdef ALLSERV
extern void clrchatline (struct session * sp);
#endif

/* Execute user telnet command */
int
dotelnet (int argc, char *argv[], void *p OPTIONAL)
{
struct session *sp;
struct sockaddr_in fsocket;
int split;

	split = usesplit;
	usesplit = 0;

	/*Make sure this comes from console - WG7J*/
	if (Curproc->input != Command->input)
		return 0;

#ifdef ALLSERV
	if ((strlen (argv[0]) > 1) && (argv[0][1] == 't'))	/* tty-link command */
		split = 1;
#endif

	/* Allocate a session descriptor */
	if ((sp = newsession (argv[1], TELNET, split)) == NULLSESSION) {
		tputs (TooManySessions);
		return 1;
	}
	fsocket.sin_family = AF_INET;
	if (argc < 3) {
		if (split)
			fsocket.sin_port = IPPORT_TTYLINK;
		else
			fsocket.sin_port = IPPORT_TELNET;
	} else
		fsocket.sin_port = (int16) atoip (argv[2]);

	tprintf ("Resolving %s... ", sp->name);
	if ((fsocket.sin_addr.s_addr = resolve (sp->name)) == 0) {
		tprintf (Badhost, sp->name);
		(void) keywait (NULLCHAR, 1);
		freesession (sp);
		return 1;
	}
	if ((sp->s = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
		tputs (Nosock);
		(void) keywait (NULLCHAR, 1);
		freesession (sp);
		return 1;
	}
	return tel_connect (sp, (char *) &fsocket, SOCKSIZE);
}
#endif



/* Generic interactive connect routine, used by Telnet, AX.25, NET/ROM */
int
tel_connect (struct session *sp, char *fsocket, int len)
{
unsigned int theindex;
struct telnet tn;

	theindex = (unsigned int) (sp - Sessions);
	memset ((char *) &tn, 0, sizeof (tn));
	tn.eolmode = (char) Tn_cr_mode;
	tn.session = sp;	/* Upward pointer */
	sp->cb.telnet = &tn;	/* Downward pointer */
	(void) sockmode (sp->s, SOCK_ASCII);	/* Default to ascii mode */

	/* place the initial cursor in the input area of split screens */
#ifdef ALLSERV
	if (sp->split) {
		clrchatline (sp);
		clrscr ();
	}
#endif

	tprintf ("Trying %s...\n", psocket ((struct sockaddr *) fsocket));
	if (connect (sp->s, fsocket, len) == -1) {
		tprintf ("%s session %u failed: %s errno %d\n",
		      Sestypes[sp->type], theindex, sockerr (sp->s), errno);

		(void) keywait (NULLCHAR, 1);
		freesession (sp);
		return 1;
	}
	tprintf ("%s session ", Sestypes[sp->type]);
	tprintf ("%u connected to %s\n", theindex, sp->name);
	tnrecv (&tn);
	return 0;
}



/* Telnet input routine, common to both telnet and ttylink */
void
tnrecv (struct telnet *tn)
{
int c, s, theindex;
struct session *sp;
char const *cp;
#if !defined(TNOS_68K) && !defined(UNIX)
struct text_info tr;
unsigned char attr;
#endif
#ifdef LZW
int bits, mode;
struct usock *up;
#endif

	sp = tn->session;
	s = sp->s;

	theindex = sp - Sessions;

	/* Fork off the transmit process */
	sp->proc1 = newproc ("tel_out", 1024, tel_output, 0, tn, NULL, 0);

	/* Process input on the connection */
	while ((c = recvchar (s)) != EOF) {
		if (c != IAC) {
			tputc (uchar(c));
			continue;
		}
		/* IAC received, get command sequence */
		c = recvchar (s);
		switch (c) {
			case WILL:
				c = recvchar (s);
				willopt (tn, c);
				break;
			case WONT:
				c = recvchar (s);
				wontopt (tn, c);
				break;
			case DO:
				c = recvchar (s);
				doopt (tn, c);
				break;
			case DONT:
				c = recvchar (s);
				dontopt (tn, c);
				break;
#ifdef LZW
			case COMPRESSED:
				mode = recvchar (s);
				bits = recvchar (s);
				mode = (mode == 'f') ? 1 : 0;
				if (bits < 9 || bits > 16)
					break;
				if ((up = itop (s)) == NULLUSOCK)
					break;
				if (up->zout == NULLLZW)
					lzwinit (s, bits, mode);
				else
					lzwfree (up);
				break;
#endif
			case CLEARSCREEN:
#if !defined(TNOS_68K) && !defined(UNIX)
				if (!sp->split) {
					window (1, 1, SCREENwidth, SCREENlength);
					clrscr ();	/* clear the screen */
					window (1, SCREENlength - 1, SCREENwidth, SCREENlength);	/* changed 1,1->1,24 */
					gettextinfo (&tr);
					attr = ((tr.attribute & 0x0f) << 4) + ((tr.attribute & 0x70) >> 4);
					textattr (attr);
					clrscr ();
					cputs ("_\b");
					window (1, 1 + sp->screen->statline, SCREENwidth, SCREENlength - 2);
					textattr (tr.attribute);
					sp->split = 1;
					if (sp->screen->statline)
						displayStatLine (0, 1, 0);
				}
#endif
				break;
			case CLEARSPLIT:
#if !defined(TNOS_68K) && !defined(UNIX) && defined(ALLSERV)
				clrchatline (sp);
#endif
				break;
			case IAC:	/* Escaped IAC */
				tputc (IAC);
				break;
			default:
				break;
		}
	}
	/* A close was received from the remote host.
	 * Notify the user, kill the output task and wait for a response
	 * from the user before freeing the session.
	 */
	(void) sockmode (sp->output, SOCK_ASCII);	/* Restore newline translation */
	cp = sockerr (s);
	tprintf ("%s session %u", Sestypes[sp->type], theindex);
	tprintf (" closed: %s\n", cp != NULLCHAR ? cp : "EOF");
	killproc (sp->proc1);
	sp->proc1 = NULLPROC;
	close_s (sp->s);
	sp->s = -1;
	(void) keywait (NULLCHAR, 1);
	freesession (sp);
}



/* User telnet output task, started by user telnet command */
static void
tel_output (int unused OPTIONAL, void *tn1, void *p OPTIONAL)
{
struct session *sp;
int c;
struct telnet *tn;

	tn = (struct telnet *) tn1;
	sp = tn->session;

	if (conversinit) {
		usprintf (sp->s, conversinit);
		conversinit = 0;
	}
	/* Send whatever's typed on the terminal */
	while ((c = recvchar (sp->input)) != EOF) {
		usputc (sp->s, uchar(c));
		if (!tn->remote[TN_ECHO] && sp->record != NULLFILE)
			(void) putc (c, sp->record);

		/* By default, output is transparent in remote echo mode.
		 * If eolmode is set, turn a cr into cr-null.
		 * This can only happen when in remote echo (raw) mode, since
		 * the tty driver normally maps \r to \n in cooked mode.
		 */
		if (c == '\r' && tn->eolmode)
			usputc (sp->s, '\0');

		if (tn->remote[TN_ECHO])
			usflush (sp->s);
	}
	/* Make sure our parent doesn't try to kill us after we exit */
	sp->proc1 = NULLPROC;
}



int
doechomode (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		if (Refuse_echo)
			tputs ("Refuse\n");
		else
			tputs ("Accept\n");
	} else {
		if (argv[1][0] == 'r')
			Refuse_echo = 1;
		else if (argv[1][0] == 'a')
			Refuse_echo = 0;
		else
			return -1;
	}
	return 0;
}



/* set for unix end of line for remote echo mode telnet */
int
doeol (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2) {
		if (Tn_cr_mode)
			tputs ("null\n");
		else
			tputs ("standard\n");
	} else {
		if (argv[1][0] == 'n')
			Tn_cr_mode = 1;
		else if (argv[1][0] == 's')
			Tn_cr_mode = 0;
		else {
			tprintf ("Usage: %s [standard|null]\n", argv[0]);
			return -1;
		}
	}
	return 0;
}



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

#ifdef	DEBUG
	tcmdprintf ("recv: will ");
	if (uchar (opt) <= NOPTIONS)
		tcmdprintf ("%s\n", T_options[opt]);
	else
		tcmdprintf ("%u\n", opt);
#endif

	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 {
					/* Put tty into raw mode */
					tn->session->ttystate.edit = 0;
					tn->session->ttystate.echo = 0;
					(void) sockmode (tn->session->s, SOCK_BINARY);
					(void) sockmode (tn->session->input, SOCK_BINARY);
					(void) sockmode (tn->session->output, SOCK_BINARY);
					if (tn->session->record != NULLFILE)
						(void) filemode (tn->session->record, SOCK_BINARY);

				}
			}
			tn->remote[uchar (opt)] = 1;
			ack = DO;
			break;
		default:
			ack = DONT;	/* We don't know what he's offering; refuse */
	}
	answer (tn, ack, opt);
}



static void
wontopt (struct telnet *tn, int opt)
{
#ifdef	DEBUG
	tcmdprintf ("recv: wont ");
	if (uchar (opt) <= NOPTIONS)
		tcmdprintf ("%s\n", T_options[uchar (opt)]);
	else
		tcmdprintf ("%u\n", uchar (opt));
#endif
	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) {
			/* Put tty into cooked mode */
			tn->session->ttystate.edit = 1;
			tn->session->ttystate.echo = 1;
			(void) sockmode (tn->session->s, SOCK_ASCII);
			(void) sockmode (tn->session->input, SOCK_ASCII);
			(void) sockmode (tn->session->output, SOCK_ASCII);
			if (tn->session->record != NULLFILE)
				(void) filemode (tn->session->record, SOCK_ASCII);
		}
	}
	answer (tn, DONT, opt);	/* Must always accept */
}



static void
doopt (struct telnet *tn, int opt)
{
int ack;

#ifdef	DEBUG
	tcmdprintf ("recv: do ");
	if (uchar (opt) <= NOPTIONS)
		tcmdprintf ("%s\n", T_options[uchar (opt)]);
	else
		tcmdprintf ("%u\n", uchar (opt));
#endif
	switch (uchar (opt)) {
		case TN_SUPPRESS_GA:
			if (tn->local[uchar (opt)] == 1)
				return;	/* Already set, ignore to prevent loop */
			tn->local[uchar (opt)] = 1;
			ack = WILL;
			break;
		default:
			ack = WONT;	/* Don't know what it is */
	}
	answer (tn, ack, opt);
}



static void
dontopt (struct telnet *tn, int opt)
{
#ifdef	DEBUG
	tcmdprintf ("recv: dont ");
	if (uchar (opt) <= NOPTIONS)
		tcmdprintf ("%s\n", T_options[uchar (opt)]);
	else
		tcmdprintf ("%u\n", uchar (opt));
#endif
	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);
}



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

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

	s[0] = (char) IAC;
	s[1] = (char) r1;
	s[2] = (char) r2;
	(void) send (tn->session->s, s, 3, 0);
}



static int
filemode (FILE *fp OPTIONAL, int mode OPTIONAL)
{
	return 0;
}

