/* NOS User Session control
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Mods by PA0GRI
 */
#include <stdio.h>
#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "proc.h"
#include "ftpcli.h"
#include "icmp.h"
#include "telnet.h"
#include "tty.h"
#include "session.h"
#include "hardware.h"
#include "socket.h"
#include "cmdparse.h"
#include "rlogin.h"
#include "commands.h"
#include "main.h"
#include "dirutil.h"

struct session *Sessions;
struct session *Command;
struct session *Current;
struct session *Lastcurr;
extern unsigned char SCREENlength;
int Row;
int Morewait;
extern int Numrows;

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

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"
};

/* 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(cp)
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(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct session *sp;
	struct sockaddr fsocket;
	int i,k,s;
	int r,t;
	char *cp;
	char *param[3];

	sp = (struct session *)p;
#ifdef __GNUC__
	k = 0;			/* spurious warning */
#endif

	if(argc > 1){
		if((sp = sessptr(argv[1])) == NULLSESSION){
			tprintf("Session %s not active\n",argv[1]);
			return 1;
		}
		if(argc == 2){
			go(0,NULL,sp);
		}
		param[0] = argv[2];
		param[1] = argv[3];
		param[2] = NULL;
		if(argc > 2){
			switch (*argv[2]) {
			case 'f':	/* flowmode */
				setbool(&sp->flowmode,"Set flowmode on/off",argc-2,param);
				break;
			default:
				tprintf("usage:session # [flow [on/off]]\n");
			}
		}
		return 0;
	}
	tprintf(" #  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));

		tprintf("\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(tprintf("\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(argc,argv,p)
int argc;
char *argv[];
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);
	psignal(sp,0);
	return 0;
}
int
doclose(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct session *sp;

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

	if(sp == NULLSESSION){
		tprintf(Badsess);
		return -1;
	}
	shutdown(sp->s,1);
	return 0;
}
int
doreset(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct session *sp;

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

	if(sp == NULLSESSION){
		tprintf(Badsess);
		return -1;
	}
	/* Unwedge anyone waiting for a domain resolution, etc */
	alert(sp->proc,EABORT);
	shutdown(sp->s,2);
	if(sp->type == FTP)
		shutdown(sp->cb.ftp->data,2);
	return 0;
}
int
dokick(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct session *sp;

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

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

struct session *
newsession(name,type,split)
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(i!=Nsessions-1) {
            i++;
            sp++;
        }
    } else {
        for(i=0,sp=Sessions;i < Nsessions;sp++,i++)
            if(sp->type == FREE)
                break;
    }
    if(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);
	seteol(Curproc->input,Eol);
	sockmode(Curproc->input,SOCK_BINARY);
	Curproc->output = sp->output = socket(AF_LOCAL,SOCK_STREAM,0);
	seteol(Curproc->output,Eol);
	sockmode(Curproc->output,SOCK_ASCII);

	/* on by default */
	sp->ttystate.crnl = sp->ttystate.edit = sp->ttystate.echo = 1;
	sp->flowmode = 0;	/* Off by default */
/*	sp->row = MOREROWS;	*/
#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;
	return sp;
}
void
freesession(sp)
struct session *sp;
{
	if(sp == NULLSESSION)
		return;
	pwait(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){
		fclose(sp->record);
		sp->record = NULLFILE;
	}
	free(sp->rfile);
	sp->rfile = NULLCHAR;
	if(sp->upload != NULLFILE){
		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;

	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
	if(Lastcurr == sp)
		Lastcurr = NULLSESSION;
}
#ifdef ALLCMD
/* Control session recording */
int
dorecord(argc,argv,p)
int argc;
char *argv[];
void *p;
{
struct session *sp;
char *mode;
char fname[128];

	sp = (struct session *)p;
	if(sp == NULLSESSION){
		tprintf("No current session\n");
		return 1;
	}
	if(argc > 1){
		if(sp->rfile != NULLCHAR){
			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;

			strcpy(fname,make_fname(Command->curdirs->dir,argv[1]));
			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
		tprintf("Recording off\n");
	return 0;
}
/* Control file transmission */
int
doupload(argc,argv,p)
int argc;
char *argv[];
void *p;
{
register struct session *sp;
char fname[128];

	sp = (struct session *)p;
	if(sp == NULLSESSION){
		tprintf("No current session\n");
		return 1;
	}
	if(argc < 2){
		if(sp->ufile != NULLCHAR)
			tprintf("Uploading %s\n",sp->ufile);
		else
			tprintf("Uploading off\n");
		return 0;
	}
	if(strcmp(argv[1],"stop") == 0 && sp->upload != NULLFILE){
		/* Abort upload */
		fclose(sp->upload);
		sp->upload = NULLFILE;
		free(sp->ufile);
		sp->ufile = NULLCHAR;
		killproc(sp->proc2);
		sp->proc2 = NULLPROC;
		return 0;
	}
	/* Open upload file */
	strcpy(fname,make_fname(Command->curdirs->dir,argv[1]));
	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 */
void
upload(unused,sp1,p)
int unused;
void *sp1;
void *p;
{
	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);
	setflush(sp->s,oldf);
	fclose(sp->upload);
	sp->upload = NULLFILE;
	free(sp->ufile);
	sp->ufile = NULLCHAR;
	sp->proc2 = NULLPROC;
}
#endif /*ALLCMD*/
