/* NOS User Session control
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Mods by PA0GRI
 */
#include "global.h"
#include "commands.h"
#include "mbuf.h"
#include "proc.h"
#include "ftpcli.h"
#include "icmp.h"
#ifndef UNIX
#include "main.h"
#endif

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

struct session *Sessions;
struct session *Command;
struct session *Current;
struct session *Lastcurr;
#ifndef UNIX
extern unsigned char SCREENlength;
#endif
#ifdef UNIX
extern int Numrows;
#endif

extern int INStatline, BLOCKStatline;
#if defined(UNIX)
extern void statLineToggle (int clearmarquee);
#endif
static void upload (int unused,void *sp1,void *p);

char Notval[] = "Not a valid control block\n";
static char Badsess[] = "Invalid session\n";
char TooManySessions[] = "Too many sessions\n";

const char *Sestypes[] =
{
	"",			/* The command session */
	"Telnet",
	"FTP",
	"AX25",
	"Finger",
	"Ping",
	"NET/ROM",
	"Command",
	"More",
	"Hopcheck",
	"Tip",
	"PPP PAP",
	"Dial",
	"Query",
	"Cache",
	"Rlogin",
#ifdef UNIX
	"Trace",
#else
	"",			/* The trace session */
#endif
	"View",
	"Script",
	"Repeat",
	"Resolve",
	"DNSquery",
	"Look",
	"AXUI"
};



/* Convert a character string containing a decimal session index number
 * into a pointer. If the arg is NULLCHAR, use the current default session.
 * If the index is out of range or unused, return NULLSESSION.
 */
struct session *
sessptr (char *cp)
{
register struct session *sp;
unsigned int i;

	if (cp == NULLCHAR)
		sp = Lastcurr;
	else {
		i = (unsigned) atoi (cp);
		if (i >= Nsessions)
			sp = NULLSESSION;
		else
			sp = &Sessions[i];
	}
	if (sp == NULLSESSION || sp->type == FREE)
		sp = NULLSESSION;

	return sp;
}



/* Select and display sessions */
int
dosession (int argc, char *argv[], void *p)
{
struct session *sp;
struct sockaddr fsocket;
int i, k = 0, s;
int r, t;
char const *cp;
char *param[3];

	sp = (struct session *) p;

	if (argc > 1) {
		if ((sp = sessptr (argv[1])) == NULLSESSION) {
			tprintf ("Session %s not active\n", argv[1]);
			return 1;
		}
		if (argc == 2)
			(void) go (0, NULL, sp);

		param[0] = argv[2];
		param[1] = argv[3];
		param[2] = NULL;
		if (argc > 2) {
			switch (*argv[2]) {
				case 'f':	/* flowmode */
					(void) setbool (&sp->flowmode, "Set flowmode on/off", argc - 2, param);
					break;
				default:
					tputs ("usage:session # [flow [on/off]]\n");
			}
		}
		return 0;
	}
	tputs (" #  S#  Type     Rcv-Q Snd-Q State        Remote socket\n");
	for (sp = Sessions; sp < &Sessions[Nsessions]; sp++) {
		if (sp->type == FREE || sp->type == COMMAND || sp->type == TRACESESSION)
			continue;

		/* Rcv-Q includes output pending at the screen driver */
		r = socklen (sp->output, 1);
		t = 0;
		cp = NULLCHAR;
		if ((s = sp->s) != -1) {
			i = SOCKSIZE;
			s = sp->s;
			k = getpeername (s, (char *) &fsocket, &i);
			r += socklen (s, 0);
			t += socklen (s, 1);
			cp = sockstate (s);
		}
		tprintf ("%c", (Lastcurr == sp) ? '*' : ' ');
		tprintf ("%-3u", (unsigned) (sp - Sessions));
		tprintf ("%-4d%-8s%6d%6d %-13s", s, Sestypes[sp->type], r, t,
			 (cp != NULLCHAR) ? cp : "Limbo!");
		if (sp->name != NULLCHAR)
			tprintf ("%s ", sp->name);
		if (sp->s != -1 && k == 0)
			tprintf ("(%s)", psocket (&fsocket));

		tputc ('\n');
		if (sp->type == FTP && (s = sp->cb.ftp->data) != -1) {
			/* Display data channel, if any */
			i = SOCKSIZE;
			k = getpeername (s, (char *) &fsocket, &i);
			r = socklen (s, 0);
			t = socklen (s, 1);
			cp = sockstate (s);
			tprintf ("    %-4d%-8s%6d%6d %-13s%s", s, Sestypes[sp->type],
				 r, t, (cp != NULLCHAR) ? cp : "Limbo!",
				 (sp->name != NULLCHAR) ? sp->name : "");
			if (k == 0)
				tprintf (" (%s)", psocket (&fsocket));
			if (tputc ('\n') == EOF)
				break;
		}
		if (sp->rfile != NULLCHAR)
			tprintf ("    Record: %s\n", sp->rfile);
		if (sp->ufile != NULLCHAR)
			tprintf ("    Upload: %s\n", sp->ufile);
	}
	return 0;
}



/* Resume current session, and wait for it */
int
go (int argc OPTIONAL, char *argv[] OPTIONAL, void *p)
{
struct session *sp;

	sp = (struct session *) p;
	if (sp == NULLSESSION || sp->type == FREE || sp->type == COMMAND || sp->type == TRACESESSION)
		return 0;
	Current = sp;
	swapscreen (Command, sp);
	ksignal (sp, 0);
	return 0;
}



int
doclose (int argc, char *argv[], void *p)
{
struct session *sp;

	sp = (struct session *) p;
	if (argc > 1)
		sp = sessptr (argv[1]);

	if (sp == NULLSESSION) {
		tputs (Badsess);
		return -1;
	}
	(void) shutdown (sp->s, 1);
	return 0;
}



int
doreset (int argc, char *argv[], void *p)
{
struct session *sp;

	sp = (struct session *) p;
	if (argc > 1)
		sp = sessptr (argv[1]);

	if (sp == NULLSESSION) {
		tputs (Badsess);
		return -1;
	}
	/* Unwedge anyone waiting for a domain resolution, etc */
	alert (sp->proc, EABORT);
	(void) shutdown (sp->s, 2);
	if (sp->type == FTP)
		(void) shutdown (sp->cb.ftp->data, 2);
	return 0;
}



int
dokick (int argc, char *argv[], void *p)
{
struct session *sp;

	sp = (struct session *) p;
	if (argc > 1)
		sp = sessptr (argv[1]);

	if (sp == NULLSESSION) {
		tputs (Badsess);
		return -1;
	}
	(void) sockkick (sp->s);
	if (sp->type == FTP)
		(void) sockkick (sp->cb.ftp->data);
	return 0;
}


#ifdef UNIX
extern int STATLINE;
#endif



struct session *
newsession (const char *name, int type, int split)
{
register struct session *sp;
int i;

	/* Reserve the highest session for Trace, so that
	 * the F-key session switching works correctly - WG7J
	 */
	if (type == TRACESESSION) {	/* This can only get called once !! */
		i = 0;
		sp = Sessions;
		while ((unsigned) i != Nsessions - 1) {
			i++;
			sp++;
		}
	} else {
		for (i = 0, sp = Sessions; (unsigned) i < Nsessions; sp++, i++)
			if (sp->type == FREE)
				break;
	}
	if ((unsigned) i == Nsessions)
		return NULLSESSION;

	sp->type = type;
	sp->s = -1;
	if (name != NULLCHAR)
		sp->name = strdup (name);
	else
		sp->name = NULLCHAR;
	sp->proc = Curproc;
#ifdef UNIX
	/* update Curproc's session pointer as well! */
	/* (in theory this could leave a dangling session...?) */
	Curproc->session = sp;
#endif
	/* Create standard input and output sockets. Output is
	 * translated to local end-of-line by default
	 */
	Curproc->input = sp->input = socket (AF_LOCAL, SOCK_STREAM, 0);
	(void) seteol (Curproc->input, Eol);
	(void) sockmode (Curproc->input, SOCK_BINARY);
	Curproc->output = sp->output = socket (AF_LOCAL, SOCK_STREAM, 0);
	(void) seteol (Curproc->output, Eol);
	(void) sockmode (Curproc->output, SOCK_ASCII);

	/* on by default */
	sp->ttystate.crnl = sp->ttystate.edit = sp->ttystate.echo = 1;
	sp->flowmode = 0;	/* Off by default */
#ifdef UNIX
	sp->row = Numrows - 1 - (split * 2);
#else
	sp->row = SCREENlength - 1 - (split * 2);
#endif
	sp->morewait = 0;
	sp->split = split;
	newscreen (sp);
	swapscreen (Current, sp);
	Current = sp;
#ifdef UNIX
	sp->screen->statline = uchar (STATLINE);
	if (STATLINE) {
		statLineToggle (0);
		statLineToggle (0);
	}
#endif
	return sp;
}



void
freesession (struct session *sp)
{
	if (sp == NULLSESSION)
		return;
	kwait (NULL);		/* Wait for any pending output to go */
	rflush ();

	if (sp->proc1 != NULLPROC)
		killproc (sp->proc1);
	sp->proc1 = NULLPROC;
	if (sp->proc2 != NULLPROC)
		killproc (sp->proc2);
	sp->proc2 = NULLPROC;

	free_p (sp->ttystate.line);
	sp->ttystate.line = NULLBUF;
	if (sp->s != -1)
		close_s (sp->s);
	if (sp->record != NULLFILE) {
		(void) fclose (sp->record);
		sp->record = NULLFILE;
	}
	free (sp->rfile);
	sp->rfile = NULLCHAR;
	if (sp->upload != NULLFILE) {
		(void) fclose (sp->upload);
		sp->upload = NULLFILE;
	}
	free (sp->ufile);
	sp->ufile = NULLCHAR;
	free (sp->name);
	sp->name = NULLCHAR;
	sp->type = FREE;

	close_s (sp->input);
	sp->input = -1;
	sp->proc->input = -1;
	close_s (sp->output);
	sp->output = -1;
	sp->proc->output = -1;

	sp->screen->statline = 0;

	while (INStatline)
		kwait (NULL);
	BLOCKStatline += 1;

	freescreen (sp);
	if (Current == sp) {
		Current = Command;
		swapscreen (NULLSESSION, Command);
#ifndef UNIX
		alert (Display, 1);
#endif
	}
#ifdef UNIX
	/* reparent process to command session */
	sp->proc->session = Command;
#endif
	BLOCKStatline -= 1;
	if (Lastcurr == sp)
		Lastcurr = NULLSESSION;
}



#ifdef ALLCMD
/* Control session recording */
int
dorecord (int argc, char *argv[], void *p)
{
struct session *sp;
char const *mode;
char fname[128];

	sp = (struct session *) p;
	if (sp == NULLSESSION) {
		tputs ("No current session\n");
		return 1;
	}
	if (argc > 1) {
		if (sp->rfile != NULLCHAR) {
			(void) fclose (sp->record);
			free (sp->rfile);
			sp->record = NULLFILE;
			sp->rfile = NULLCHAR;
		}
		/* Open new record file, unless file name is "off", which means
		 * disable recording
		 */
		if (strcmp (argv[1], "off") != 0) {
			if (sockmode (sp->output, -1) == SOCK_ASCII)
				mode = APPEND_TEXT;
			else
				mode = APPEND_BINARY;

			strncpy (fname, make_fname (Command->curdirs->dir, argv[1]), 128);
			if ((sp->record = fopen (fname, mode)) == NULLFILE)
				tprintf ("Can't open %s: %s\n", fname, SYS_ERRLIST(errno));
			else
				sp->rfile = strdup (fname);
		}
	}
	if (sp->rfile != NULLCHAR)
		tprintf ("Recording into %s\n", sp->rfile);
	else
		tputs ("Recording off\n");
	return 0;
}



/* Control file transmission */
int
doupload (int argc, char *argv[], void *p)
{
register struct session *sp;
char fname[128];

	sp = (struct session *) p;
	if (sp == NULLSESSION) {
		tputs ("No current session\n");
		return 1;
	}
	if (argc < 2) {
		if (sp->ufile != NULLCHAR)
			tprintf ("Uploading %s\n", sp->ufile);
		else
			tputs ("Uploading off\n");
		return 0;
	}
	if (strcmp (argv[1], "stop") == 0 && sp->upload != NULLFILE) {
		/* Abort upload */
		(void) fclose (sp->upload);
		sp->upload = NULLFILE;
		free (sp->ufile);
		sp->ufile = NULLCHAR;
		killproc (sp->proc2);
		sp->proc2 = NULLPROC;
		return 0;
	}
	/* Open upload file */
	strncpy (fname, make_fname (Command->curdirs->dir, argv[1]), 128);
	if ((sp->upload = fopen (fname, READ_TEXT)) == NULLFILE) {
		tprintf ("Can't read %s: %s\n", fname, SYS_ERRLIST(errno));
		return 1;
	}
	sp->ufile = strdup (fname);
	/* All set, invoke the upload process */
	sp->proc2 = newproc ("upload", 1024, upload, 0, sp, NULL, 0);
	return 0;
}



/* File uploading task */
static void
upload (int unused OPTIONAL, void *sp1, void *p OPTIONAL)
{
struct session *sp;
int oldf;
char *buf;

	sp = (struct session *) sp1;

	/* Disable newline buffering for the duration */
	oldf = setflush (sp->s, -1);

	buf = mallocw (BUFSIZ);
	while (fgets (buf, BUFSIZ, sp->upload) != NULLCHAR)
		if (usputs (sp->s, buf) == EOF)
			break;

	free (buf);
	usflush (sp->s);
	(void) setflush (sp->s, oldf);
	(void) fclose (sp->upload);
	sp->upload = NULLFILE;
	free (sp->ufile);
	sp->ufile = NULLCHAR;
	sp->proc2 = NULLPROC;
}
#endif /*ALLCMD*/



/* Flush the current session's standard output. Called on every clock tick */
void
sesflush(void)
{
	if (Current != NULL)
		usflush (Current->output);
}

 
