/* "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 "config.h"
#include "mbuf.h"
#include "timer.h"
#include "proc.h"
#include "iface.h"
#ifdef UNIX
#include "unixasy.h"
#else
#include "i8250.h"
#endif
#include "asy.h"
#include "socket.h"
#include "usock.h"
#include "telnet.h"
#include "mailbox.h"
#include "tipmail.h"
#include "devparam.h"
#include "commands.h"

#ifdef TIPMAIL
#ifdef ALLSERV

struct tipcb *Tiplist;

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

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

struct suspended {
	struct iface *ifp;
	unsigned timeout;
	char modem;
} Tipsuspended[ASY_MAX];

/* Input process */
static void
tip_in(dev,n1,n2)
int dev;
void *n1,*n2;
{
	struct tipcb *tip;
	struct mbuf *bp;
	char *buf[2], line[MBXLINE];
	int c, ret, pos = 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(line,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("\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("\r\n",2);
#else
					bp = qdata("\r\l",2);
#endif
					break;
				default:
					bp = pushdown(NULLBUF,1);
					*bp->data = c;
					break;
				}
				asy_send(dev,bp);
				tip->iface->lastsent = secclock();
				if(ret)
					continue;
			}
			line[pos++] = 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,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;
	tip0(2,buf,NULL);
}
/* Start mailbox on serial line */
int
tipstart(argc,argv,p)
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 */
#ifndef UNIX
	struct fifo *fp; 
#endif
	char *buf[2];
	int dev, c, i, off, cnt, cmd, s[2], type = TIP;

	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;
	}
	psignal(Curproc,0);	/* Don't keep the parser waiting */
	chname(Curproc,"Mbox 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) ? atoi(argv[3]) : Tiptimeout;    
	if (tip->default_timeout)
		tprintf("%d Second",tip->default_timeout);
	else
		tprintf("No");
	tprintf(" 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(;;) {
		ifp->ioctl(ifp,PARAM_UP,TRUE,0L);
		/* Wait for DCD to be asserted if modem */
		if (tip->modem)		{
			mspause (1000);
			while (! carrier_detect(tip->asy_dev))
				pwait(NULL);
			mspause(1000L);
		}

		if(socketpair(AF_LOCAL,SOCK_STREAM,0,s) == -1){
			tprintf("Could not create socket pair, errno %d\n",errno);
			tip0(2,buf,p);
			return 1;
		}
		seteol(s[0],"\n");
		seteol(s[1],"\n");
		tip->echo = WONT;
		tip->s = s[0];
		if (tip->modem)
			log(tip->s,"Phone Mailbox Login");
		newproc("mbx_incom",2048,mbx_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);

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

				  if((cmd = ++i < cnt ?  PULLCHAR(&dp) :
					 recvchar(tip->s)) == -1)
					 break;
				  if(cmd > 250 && cmd < 255) {
					  if((c = ++i < cnt ? PULLCHAR(&dp) :
						  recvchar(tip->s)) == -1)
						  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;
					  }
					  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)) {
					dp = pushdown(dp,1);
					*dp->data = IAC;
					}
				  break;
#ifndef TNOS_68K
			case '\n':
#else
			case '\l':
#endif
				  dup_p(&op,bp,off,i-off);
#ifndef TNOS_68K
				  append(&op,qdata("\r\n",2));
#else
				  append(&op,qdata("\r\l",2));
#endif
				  asy_send(dev,op);
				  ifp->lastsent = secclock();
				  off = i + 1;
				  break;
			 }

			(void)pullup(&bp,(unsigned char *)0,off);
			asy_send(dev,bp);
		     } else {
			asy_send(dev,bp);
		     }
		     ifp->lastsent = secclock();

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

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

		if (!tip->keep && tip->modem)	{
			/* Tell line to go down */
			ifp->ioctl(ifp,PARAM_DOWN,TRUE,0L);
			mspause (5000);
		}
	}
}
int
tip0(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	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(t)
void *t;
{
	struct tipcb *tip;
#ifndef TNOS_68K
	static char *msg1 = "Line idle - One minute to disconnect...\007\r\n";
	static char *msg2 = "Disconnecting...\007\r\n";
#else
	static char *msg1 = "Line idle - One minute to disconnect...\007\r\l";
	static char *msg2 = "Disconnecting...\007\r\l";
#endif
	tip = (struct tipcb *) t;

	if (tip->modem && ! carrier_detect(tip->asy_dev)) {
		  close_s(tip->s);
		  return;
	}
	
	if (tip->default_timeout && --tip->timeout <= 0) {
	      if (tip->firstwarn) {
		  tip->iface->lastsent = secclock();
		  asy_send(tip->iface->dev,qdata(msg1,(int16)strlen(msg1)));
		  tip->timeout=60;
		  start_timer(&tip->timer);
		  tip->firstwarn=0;
	      } else {
		  asy_send(tip->iface->dev,qdata(msg2,(int16)strlen(msg2)));
		  close_s(tip->s);
	      }
	} else {
	      start_timer(&tip->timer);
	}
}




void
suspendTipMail(m)
struct mbx *m;
{
char *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;
	tip0 (2, args, NULL);
}
#endif /*ALLSERV*/
#endif /* TIPMAIL */

static int Stelnet = -1;

/* Start up Telnet server */
int
telnet1(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return (installserver (argc, argv, &Stelnet, "Telnet listener", IPPORT_TELNET,
		"mbox", mbx_incom,2048, (void *)TELNET_LINK));
}
/* Stop telnet server */
int
telnet0(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return (deleteserver (&Stelnet));
}


