/* "Dumb terminal" mailbox interface
 * Copyright 1991 Phil Karn, KA9Q
 *
 *	May '91 Bill Simpson
 *		move to separate file for compilation & linking
 *	Sep '91 Bill Simpson
 *		minor changes for DTR & RLSD
 *      Jan '93 Doug Crompton
 *              Mods to code to make it work with both terminal
 *              and Modem. Timers and CD check revamped. Now Always
 *              detects CD loss and timeouts work properly.
 *              Setting Tiptimeout to 0 disables idle and CD loss check
 *
 *              Command Syntax now : 'start tip <interface> <modem|terminal>'
 */
#include "global.h"
#include "commands.h"
#include "mbuf.h"
#include "timer.h"
#include "proc.h"
#include "iface.h"
#ifdef UNIX
#include "unixasy.h"
#else
#include "n8250.h"
#endif
#include "asy.h"
#include "usock.h"
#include "telnet.h"
#include "mailbox.h"
#include "devparam.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: tipmail.c,v 1.24 1997/08/19 01:19:22 root Exp $";
#endif

#if defined(TIPMAIL) || defined(ALLSERV)

static struct tipcb *Tiplist;
struct suspended *Tipsuspended;

static void tip_in (int dev, void *n1, void *n2);
static void tipidle (void *t);
void suspendTipMail (struct mbx * m);

unsigned Tiptimeout = 180;	/* Default tip inactivity timeout (seconds) */


/* Input process */
static void
tip_in (int dev, void *n1, void *n2 OPTIONAL)
{
struct tipcb *tip;
struct mbuf *bp;
const char *buf[2];
char line[MBXLINE];
int c, ret, pos = 0;

	line[0] = 0;
	tip = (struct tipcb *) n1;
	while ((c = get_asy (dev)) != -1) {
		tip->firstwarn = 1;
		tip->timeout = tip->default_timeout;
		Asy[dev].iface->lastrecv = secclock ();

		if (!tip->raw) {
			c &= 0x7f;
			ret = 0;
			if (tip->echo == WONT) {
				switch (c) {
					case 18:	/* CTRL-R */
						bp = pushdown (qdata ((unsigned char *) line, (int16) pos), 4);
#ifndef TNOS_68K
						memcpy (bp->data, "^R\r\n", 4);
#else
						memcpy (bp->data, "^R\r\l", 4);
#endif
						ret = 1;
						break;
					case 0x7f:	/* DEL */
					case '\b':
						bp = NULLBUF;
						if (pos) {
							--pos;
							bp = qdata ((const unsigned char *) "\b \b", 3);
						}
						ret = 1;
						break;
#ifndef TNOS_68K
					case '\r':
#else
					case '\l':
#endif
						c = '\n';	/* CR => NL */
					case '\n':
#ifndef TNOS_68K
						bp = qdata ((const unsigned char *) "\r\n", 2);
#else
						bp = qdata ("\r\l", 2);
#endif
						break;
					default:
						bp = pushdown (NULLBUF, 1);
						*bp->data = uchar (c);
						break;
				}
				(void) asy_send (dev, bp);
				tip->iface->lastsent = secclock ();
				if (ret)
					continue;
			}
			line[pos++] = (char) c;
			if (pos == MBXLINE - 1 || tip->echo == WILL || c == '\n') {
				line[pos] = '\0';
				pos = 0;
				usputs (tip->s, line);
				usflush (tip->s);
			}
		} else {
			usputc (tip->s, uchar (c));
			usflush (tip->s);
		}
	}
	/* get_asy() failed, terminate */
	close_s (tip->s);
	tip->in = tip->proc;
	tip->proc = Curproc;
	buf[1] = Asy[dev].iface->name;
	(void) tip0 (2, buf, NULL);
}



/* Start mailbox on serial line */
int
tipstart (int argc, char *argv[], void *p)
{
register struct iface *ifp;
register struct asy *ap;
register struct tipcb *tip;
struct mbuf *bp, *dp, *op;	/* op is output, dp is duplicate */
char const *buf[2];
int dev, c, i, off, cnt, cmd, s[2], type = TIP;
#ifndef UNIX
struct fifo *fp;
#endif

	if ((ifp = if_lookup (argv[1])) == NULLIF) {
		tprintf (Badinterface, argv[1]);
		return 1;
	}
	for (dev = 0, ap = Asy; dev < ASY_MAX; dev++, ap++)
		if (ap->iface == ifp)
			break;
	if (dev == ASY_MAX) {
		tprintf ("Interface %s not asy port\n", argv[1]);
		return 1;
	}
	if (ifp->raw == bitbucket) {
		tprintf ("Tip session already active on %s\n", argv[1]);
		return 1;
	}
	ksignal (Curproc, 0);	/* Don't keep the parser waiting */
	chname (Curproc, "PBBS tip");
	tip = (struct tipcb *) callocw (1, sizeof (struct tipcb));

	if (argc > 2 && *argv[2] == 'm')
		tip->modem = 1;
	tprintf ("Tip started on %s -  with%s CD check - ", argv[1], (tip->modem) ? "" : "out");
	tip->default_timeout = (argc > 3) ? (unsigned) atoi (argv[3]) : Tiptimeout;
	if (tip->default_timeout)
		tprintf ("%d Second", tip->default_timeout);
	else
		tputs ("No");
	tputs (" Timeout\n");


	/* Save output handler and temporarily redirect output to null */
	tip->asy_dev = dev;
	tip->rawsave = ifp->raw;
	ifp->raw = bitbucket;
	tip->iface = ifp;
	tip->proc = Curproc;
	tip->timer.func = tipidle;
	tip->timer.arg = (void *) tip;
	tip->raw = 0;
	tip->next = Tiplist;
	Tiplist = tip;
	buf[1] = ifp->name;

	/* Suspend packet input drivers */
	suspend (ifp->rxproc);

	for (;;) {
		(void) ifp->ioctl (ifp, PARAM_UP, TRUE, 0L);
		/* Wait for DCD to be asserted if modem */
		if (tip->modem) {
			kpause (1000);
#ifndef MSDOS
			while (!carrier_detect (tip->asy_dev))
				kwait (NULL);
			kpause (1000L);
#endif
		}
		if (socketpair (AF_LOCAL, SOCK_STREAM, 0, s) == -1) {
			tprintf ("Could not create socket pair, errno %d\n", errno);
			(void) tip0 (2, buf, p);
			return 1;
		}
		(void) seteol (s[0], "\n");
		(void) seteol (s[1], "\n");
		tip->echo = WONT;
		tip->s = s[0];
		if (tip->modem)
			log (tip->s, "Phone Mailbox Login");
		(void) newproc ("pbbs_incom", 2048, pbbs_incom, s[1], (void *) type, (void *) tip, 0);

		/* check for line idle timeout and CD failure */
		tip->firstwarn = 1;
		tip->timeout = tip->default_timeout;
		set_timer (&tip->timer, 1000L);
		start_timer (&tip->timer);

		(void) setflush (tip->s, -1);
		(void) sockmode (tip->s, SOCK_ASCII);

		/* Now fork into two paths, one rx, one tx */
#ifndef UNIX
		/* first clear (ignore) junk in asyinc input
		   which is always receiving - modem can garbage
		   on disconnect
		*/
		fp = &ap->fifo;
		fp->wp = fp->rp = fp->buf;
		fp->cnt = 0;
#endif

		tip->in = newproc ("PBBS tip in", 256, tip_in, dev, (void *) tip, NULL, 0);
		while ((cnt = recv_mbuf (tip->s, &bp, 0, NULL, 0)) != -1) {
			if (!tip->raw) {
				(void) dup_p (&dp, bp, off = 0, (int16) cnt);	/* dup the whole pkt to pull */
				for (i = 0; i < cnt; i++)
					switch (PULLCHAR (&dp)) {	/*lint !e506 */
						case IAC:	/* ignore most telnet options */
							(void) dup_p (&op, bp, (int16) off, (int16) (i - off));
							(void) asy_send (dev, op);
							ifp->lastsent = secclock ();

							if ((cmd = ++i < cnt ? PULLCHAR (&dp) : recvchar (tip->s)) == EOF)	/*lint !e506 */
								break;
							if (cmd > 250 && cmd < 255) {
								if ((c = ++i < cnt ? PULLCHAR (&dp) : recvchar (tip->s)) == EOF)	/*lint !e506 */
									break;
								switch (cmd) {
									case WILL:
										if (c == TN_ECHO) {
											tip->echo = cmd;
											cmd = DO;
										} else
											cmd = DONT;
										break;
									case WONT:
										if (c == TN_ECHO)
											tip->echo = cmd;
										cmd = DONT;
										break;
									case DO:
									case DONT:
										cmd = WONT;
										break;
									default:
										break;
								}
								usprintf (tip->s, "%c%c%c", IAC, cmd, c);
								usflush (tip->s);
							}
							off = i + 1;
							break;
						case '\r':
							if (++i < cnt &&	/* Skip NL but not IAC */
							    (PULLCHAR (&dp) == IAC)) {		/*lint !e506 */
								dp = pushdown (dp, 1);
								*dp->data = IAC;
							}
							break;
#ifndef TNOS_68K
						case '\n':
#else
						case '\l':
#endif
							(void) dup_p (&op, bp, (int16) off, (int16)(i - off));
#ifndef TNOS_68K
							append (&op, qdata ((const unsigned char *) "\r\n", 2));
#else
							append (&op, qdata ("\r\l", 2));
#endif
							(void) asy_send (dev, op);
							ifp->lastsent = secclock ();
							off = i + 1;
							break;
						default:
							break;
					}

				(void) pullup (&bp, (unsigned char *) 0, (int16) off);
				(void) asy_send (dev, bp);
			} else
				(void) asy_send (dev, bp);

			ifp->lastsent = secclock ();

			kwait (NULL);
		}
		stop_timer (&tip->timer);
		kpause (2000L);
		close_s (tip->s);
		killproc (tip->in);
		tip->in = NULLPROC;

		kwait (itop (s[1]));	/* let mailbox terminate, if necessary */

		if (!tip->keep && tip->modem) {
			/* Tell line to go down */
			(void) ifp->ioctl (ifp, PARAM_DOWN, TRUE, 0L);
			kpause (5000);
		}
	}
}



int
tip0 (int argc OPTIONAL, char const *argv[], void *p OPTIONAL)
{
register struct iface *ifp;
struct tipcb *tip, *prev = NULLTIP;
struct proc *proc;

	if ((ifp = if_lookup (argv[1])) == NULLIF) {
		tprintf (Badinterface, argv[1]);
		return 1;
	}
	for (tip = Tiplist; tip != NULLTIP; prev = tip, tip = tip->next)
		if (tip->iface == ifp) {
			if (prev != NULLTIP)
				prev->next = tip->next;
			else
				Tiplist = tip->next;
			proc = tip->proc;
			close_s (tip->s);
			ifp->raw = tip->rawsave;
			resume (ifp->rxproc);
			stop_timer (&tip->timer);
			killproc (tip->in);
			free ((char *) tip);
			killproc (proc);
			return 0;
		}
	return 0;
}



static void
tipidle (void *t)
{
struct tipcb *tip;
#ifndef TNOS_68K
static const char *msg1 = "Line idle - One minute to disconnect...\007\r\n";
static const char *msg2 = "Disconnecting...\007\r\n";
#else
static const char *msg1 = "Line idle - One minute to disconnect...\007\r\l";
static const char *msg2 = "Disconnecting...\007\r\l";
#endif

	tip = (struct tipcb *) t;

	if (tip->modem
#ifndef MSDOS
	   && !carrier_detect (tip->asy_dev)
#endif
	   ) {
		close_s (tip->s);
		return;
	}
	if (tip->default_timeout && (int)(--tip->timeout) <= 0) {
		if (tip->firstwarn) {
			tip->iface->lastsent = secclock ();
			(void) asy_send (tip->iface->dev, qdata ((const unsigned char *) msg1, (int16) strlen (msg1)));
			tip->timeout = 60;
			start_timer (&tip->timer);
			tip->firstwarn = 0;
		} else {
			(void) asy_send (tip->iface->dev, qdata ((const unsigned char *) msg2, (int16) strlen (msg2)));
			close_s (tip->s);
		}
	} else
		start_timer (&tip->timer);
}



void
suspendTipMail (struct mbx *m)
{
char const *args[2];

	if (m->tip->modem) {	/* if modem connect, we can resume */
		/* save the interface in the suspended array, for asytimer() */
		Tipsuspended[m->tip->asy_dev].ifp = m->tip->iface;
		Tipsuspended[m->tip->asy_dev].modem = m->tip->modem;
		Tipsuspended[m->tip->asy_dev].timeout = m->tip->default_timeout;
		m->tip->keep = 1;
		tputs ("Tipmail will resume after you disconnect\n");
	} else
		tputs ("Tipmail terminated on this interface\n");
	usflush (Curproc->output);
	/* now stop the tipmail */
	args[1] = m->tip->iface->name;
	(void) tip0 (2, args, NULL);
}
#endif /* ALLSERV || TIPMAIL */



static int Stelnet = -1;

/* Start up Telnet server */
int
telnet1 (int argc, char *argv[], void *p OPTIONAL)
{
	return (installserver (argc, argv, &Stelnet, "Telnet listener", IPPORT_TELNET,
		INADDR_ANY, "pbbs", pbbs_incom, 2048, (void *) TELNET_LINK));
}

/* Stop telnet server */
int
telnet0 (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
	return (deleteserver (&Stelnet));
}


static int SFBBtelnet = -1;

/* Start up FBB Telnet server */
int
fbbtelnet1 (int argc, char *argv[], void *p OPTIONAL)
{
	return (installserver (argc, argv, &SFBBtelnet, "FBB/Telnet listener", IPPORT_FTELNET,
		INADDR_ANY, "pbbs", pbbs_incom, 2048, (void *) (TELNET_LINK | JUMPSTARTED)));
}

/* Stop FBB telnet server */
int
fbbtelnet0 (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
	return (deleteserver (&SFBBtelnet));
}

