/* TERM server: access a local asy device from a tcp connection.
 * Copyright 1991 Phil Karn, KA9Q
 * Adapted from KA9Q's NOS 930622 by N5KNX 11/95.
 */
#include "global.h"
#ifdef TERMSERVER
#include "commands.h"
#include "netuser.h"
#include "smtp.h"
#include "iface.h"
#include "hardware.h"
#include "mailbox.h"
#include "asy.h"
#ifdef UNIX
#include "unixasy.h"
#else
#include "n8250.h"
#endif
#include "devparam.h"
#ifndef MSDOS
#include "telnet.h"
#endif

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: term.c,v 1.12 1997/05/24 13:11:12 root Exp $";
#endif

static void termserv (int s,void *unused,void *p);
static void termrx (int s,void *p1,void *p2);
static int prompting_read (const char *prompt, int s, char *buf, int buflen);

static const char termlistener[] = "TERM listener";
static const char termserver[] = "TERM server";
static int S_term = -1;

static char *Termpass = NULLCHAR;
struct portlist {
	char *portname;
	struct portlist *next;
};
#define NULLPL (struct portlist *)NULL
static struct portlist *Termports = NULLPL;


/* Start up TCP term server */
int
term1 (argc, argv, p)
int argc;
char *argv[];
void *p OPTIONAL;
{
int port = IPPORT_TERM;

	if (argc > 1)	{
		port = atoi (argv[1]);
		tprintf ("Starting Term Server on port %d\n", port);
	}
	return (installserver (argc, argv, &S_term, termlistener, port,
		INADDR_ANY, termserver, termserv, 512, NULL));
}


/* Stop term server */
int
term0 (argc, argv, p)
int argc OPTIONAL;
char *argv[] OPTIONAL;
void *p OPTIONAL;
{
	return (deleteserver (&S_term));
}


static void
termserv(s,unused,p)
int s;
void *unused OPTIONAL;
void *p OPTIONAL;
{
char buf[64];
struct iface *ifp = NULLIF;
struct route *rp;
struct sockaddr_in fsocket;
struct proc *rxproc = NULLPROC;
struct asy *ap = (struct asy *) 0;
struct mbuf *bp;
struct portlist *pl;
int (*rawsave) (struct iface *,struct mbuf *) = NULLFP((struct iface*,struct mbuf*));
int i, saved = 0;
    
	(void) sockmode (s, SOCK_BINARY);
	(void) sockowner (s, Curproc);
	close_s (Curproc->output);
	close_s (Curproc->input);
	Curproc->output = Curproc->input = s;
	(void) setflush (s, '\n');
	log (s, "open term");

	/* Prompt for and check remote password */
	if (Termpass) {
		if (prompting_read ("Password: ", s, buf, sizeof(buf)) < 0)
			goto quit;
		rip (buf);
		if (strcmp (buf, Termpass) != 0)	{
			tputs ("Login incorrect\n");
			goto quit;
		}
	}

	/* Prompt for desired interface. Verify that it exists, that
	 * we're not using it for our TCP connection, that it's an
	 * asynch port, and that there isn't already another tip, term
	 * or dialer session active on it.
	 */
	for ( ; ; )	{
		if (prompting_read ("Interface: ", s, buf, sizeof(buf)) < 0)
			goto quit;
		rip (buf);
		if ((ifp = if_lookup (buf)) == NULLIF)	{
			tprintf (Badinterface, buf);
			continue;
		}
		for (pl = Termports; pl; pl = pl->next)
			if (!strcmp (pl->portname, buf))
				break;
		if (!pl) {
			tprintf (Badinterface, buf);
			continue;
		}
		if (getpeername(s,(char *)&fsocket,&i) != -1
		    && !ismyaddr(fsocket.sin_addr.s_addr)
		    && (rp = rt_lookup(fsocket.sin_addr.s_addr)) != NULLROUTE
		    && rp->iface == ifp)	{
			tprintf ("You're using interface %s!\n", ifp->name);
			continue;
		}

		ap = &Asy[ifp->dev];
		if (ifp->dev >= ASY_MAX || ap->iface != ifp )	{
			tprintf ("Interface %s not asy port\n", buf);
			continue;
		}
		if (ifp->raw == bitbucket)	{
			tprintf("%s already in use\n", buf);
			continue;
		}

		/* Save output handler and temporarily redirect output to null */
		rawsave = ifp->raw;
		ifp->raw = bitbucket;
		saved = 1;

		/* Suspend the packet input driver. Note that the transmit driver
		 * is left running since we use it to send buffers to the line.
		 */
		suspend (ifp->rxproc);
#ifdef POLLEDKISS
		suspend (ap->poller);
#endif
		break;
	}

	if (prompting_read ("Wink DTR? ", s, buf, sizeof(buf)) < 0)
		goto quit;
	if (buf[0] == 'y' || buf[0] == 'Y')	{
		(void) asy_ioctl (ifp, PARAM_DTR, 1, 0);    /* drop DTR */
		(void) kpause (1000L);
		(void) asy_ioctl (ifp, PARAM_DTR, 1, 1);    /* raise DTR */
	}

	if (prompting_read ("Turn off local echo? ", s, buf, sizeof(buf)) < 0)
		goto quit;
	if (buf[0] == 'y' || buf[0] == 'Y')
		tprintf ("%c%c%c%c%c%c", IAC, WILL, TN_ECHO, IAC, WILL, TN_SUPPRESS_GA);

	/* Now fork into receive and transmit processes */
	rxproc = newproc ("term rx", 270, termrx, s, ifp, NULL, 0);

	/* We continue to handle the TCP->asy direction */
	while ((i = recvchar (s)) != EOF)	{
		while (i == IAC) {
			i = recvchar (s);	/* DO/DONT/WILL/WONT */
			if (i == IAC)
				break;		/* escaped IAC */
			i = recvchar (s);	/* opt */
			i = recvchar (s);
		}
		if (i == '\n')
			i = '\r';		/* NL => CR */
		bp = pushdown (NULLBUF, 1);
#if 1
		bp->data[0] = (unsigned char) i;
#else
		bp->data[0] = (i & 0x7f);
#endif
		(void) asy_send (ifp->dev,bp);
	        ifp->lastsent = secclock();
        	if (i == '\r')
			kwait(NULL);	/* give output buffers a chance to empty */
	}

quit:
	if (saved) {
		killproc (rxproc);
		if (ifp)	{
			ifp->raw = rawsave;
			resume (ifp->rxproc);
		}
#ifdef POLLEDKISS
		if (ap)
			resume (ap->poller);
#endif
	}
	log (s, "close term");
	Curproc->input = -1;  /* avoid closing s twice */
}


static void
termrx (s, p1, p2)
int s;
void *p1;
void *p2 OPTIONAL;
{
int c;
struct iface *ifp = (struct iface *)p1;
    
	while ((c = get_asy (ifp->dev)) != EOF)	{
#if 1
		(void) usputc (s, uchar(c));
#else
		usputc (s, c & 0x7f);
#endif
		usflush (s);
	}
}


static int
prompting_read (prompt, s, buf, buflen)
const char *prompt;
int s, buflen;
char *buf;
{
int nread;

	if (prompt) {
		(void) usputs (s, prompt);
		usflush (s);
	}
	nread = recvline (s, (unsigned char *) buf, (unsigned) buflen);
	return nread;
}


int
doterm (argc, argv, p)
int argc;
char *argv[];
void *p OPTIONAL;
{
int i;
struct portlist *pl, *pnext;

	if (argc == 1)
usage:
		tputs ("Usage: term ports [portlist] | term password pwstring\n");
	else if (!strcmp( argv[1], "ports")) {
		if (argc == 2) {
			tputs ("term ports: ");
			for (pl = Termports; pl; pl = pl->next)
				tprintf ("%s ", pl->portname);
			tputc ('\n');
		} else {
			for (pl = Termports; pl; pl = pnext) {
				free (pl->portname);
				pnext = pl->next;
				free (pl);
			}
			Termports = NULLPL;
			for (i = 2; i < argc; i++) {
				pl = mallocw (sizeof (struct portlist));
				pl->portname = strdup (argv[i]);
				pl->next = Termports;
				Termports = pl;
			}
		}
	} else if (!strncmp (argv[1], "pass", 4)) {
		if (argc != 3)
			goto usage;
		free (Termpass);
		Termpass = strdup (argv[2]);
	} else
		goto usage;
	return 0;
}

#endif /* TERMSERVER */
