/* TCP control and status routines
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Mods by G1EMM
 * Mods by WG7J
 * Mods by WA3DSP
 * Mods by PA0GRI
 * Copyright 1992 Gerard J van der Grinten, PA0GRI
 */

#include <stdio.h>
#ifdef MSDOS
#include <dos.h>
#endif
#include "global.h"
#include "config.h"
#include "timer.h"
#include "mbuf.h"
#include "netuser.h"
#include "internet.h"
#include "tcp.h"
#include "commands.h"
#include "socket.h"
#include <ctype.h>

#ifdef  TCPACCESS
static int doaccess __ARGS((int argc,char *argv[],void *p));
void addtaccess __ARGS((int32 target,unsigned int bits,int16 low,int16 high,int16 permit));
int tcp_check __ARGS((struct rtaccess *accptr,int32 src,int16 port));
#endif
static int doirtt __ARGS((int argc,char *argv[],void *p));
static int domss __ARGS((int argc,char *argv[],void *p));
static int dortt __ARGS((int argc,char *argv[],void *p));
static int dotcpkick __ARGS((int argc,char *argv[],void *p));
static int dotcpreset __ARGS((int argc,char *argv[],void *p));
static int dotcpretries __ARGS((int argc,char *argv[],void *p));
static int dotcpstat __ARGS((int argc,char *argv[],void *p));
static int dotcpconnect __ARGS((int argc,char *argv[],void *p));
static int dotcptimer __ARGS((int argc,char *argv[],void *p));
static int dotcptr __ARGS((int argc,char *argv[],void *p));
static int dowindow __ARGS((int argc,char *argv[],void *p));
static int dosyndata __ARGS((int argc,char *argv[],void *p));
static int dotcpview __ARGS((int argc,char *argv[],void *p));
static int dotcpblimit __ARGS((int argc,char *argv[],void *p));
void rxtx_data_compute __ARGS((struct tcb *tcb,int32 *sent,int32 *recvd));
static int tstat __ARGS((void));
static int tstat2 __ARGS((void));
#ifdef TCPACCESS
struct rtaccess *TCPaccess = NULLACCESS; /* access list */
#endif

/* TCP subcommand table */
static struct cmds DFAR Tcpcmds[] = {
#ifdef  TCPACCESS
	"access",	doaccess,	0,	0, NULLCHAR,
#endif
	"blimit",       dotcpblimit,    0, 0,   NULLCHAR,
	"connections",	dotcpconnect,	0, 0,	NULLCHAR,
	"irtt",         doirtt,         0, 0,   NULLCHAR,
	"kick",         dotcpkick,      0, 2,   "tcp kick <tcb>",
	"mss",          domss,          0, 0,   NULLCHAR,
	"reset",        dotcpreset,     0, 2,   "tcp reset <tcb>",
	"retries",      dotcpretries,   0, 0,   NULLCHAR,
	"rtt",          dortt,          0, 3,   "tcp rtt <tcb> <val>",
	"status",       dotcpstat,      0, 0,   NULLCHAR,
	"syndata",      dosyndata,      0, 0,   NULLCHAR,
	"timertype",    dotcptimer,     0, 0,   NULLCHAR,
	"trace",        dotcptr,        0, 0,   NULLCHAR,
	"view",         dotcpview,      0, 0,   NULLCHAR,
	"window",       dowindow,       0, 0,   NULLCHAR,
	NULLCHAR,
};
int
dotcp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return subcmd(Tcpcmds,argc,argv,p);
}
static int
dotcptr(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Tcp_trace,"TCP state tracing",argc,argv);
}

/* Eliminate a TCP connection */
static int
dotcpreset(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct tcb *tcb;
	
#ifndef TNOS_68K
	tcb = MK_FP(htoi(argv[1]),8);
#else
	tcb = htoi(argv[1]);
#endif
	if(!tcpval(tcb)){
		tprintf(Notval);
		return 1;
	}
	reset_tcp(tcb);
	return 0;
}

/* Set initial round trip time for new connections */
static int
doirtt(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct tcp_rtt *tp;

	setlong(&Tcp_irtt,"TCP default irtt",argc,argv);
	if(argc < 2){
		for(tp = &Tcp_rtt[0];tp < &Tcp_rtt[RTTCACHE];tp++){
			if(tp->addr != 0){
				if(tprintf("%s: srtt %lu mdev %lu\n",
				 inet_ntoa(tp->addr),
				 tp->srtt,tp->mdev) == EOF)
					break;
			}
		}
	}
	return 0;
}

/* Set smoothed round trip time for specified TCB */
static int
dortt(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct tcb *tcb;
#ifndef TNOS_68K
	tcb = MK_FP(htoi(argv[1]),8);
#else
	tcb = htoi(argv[1]);
#endif
	if(!tcpval(tcb)){
		tprintf(Notval);
		return 1;
	}
	tcb->srtt = atol(argv[2]);
	return 0;
}

/* Force a retransmission */
static int
dotcpkick(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct tcb *tcb;
#ifndef TNOS_68K
	tcb = MK_FP(htoi(argv[1]),8);
#else
	tcb = htoi(argv[1]);
#endif
	if(kick_tcp(tcb) == -1){
		tprintf(Notval);
		return 1;
	}
	return 0;
}

/* Set default maximum segment size */
static int
domss(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setshort(&Tcp_mss,"TCP MSS",argc,argv);
}

/* Set default window size */
static int
dowindow(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setshort(&Tcp_window,"TCP window",argc,argv);    
}

static int
dosyndata(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Tcp_syndata,"TCP syn+data piggybacking",argc,argv);
}

extern int Tcp_retries;

/* Set maximum number of backoffs before resetting the connection */
static int
dotcpretries(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setint(&Tcp_retries,"max. retries",argc,argv);
}

extern int Tcp_blimit;

/* Set backoff limit on the connection; from N1BEE */
static int
dotcpblimit(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setint(&Tcp_blimit,"backoff limit",argc,argv);
}

/* Display status of TCBs */
static int
dotcpconnect(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct tcb *tcb;

	if(argc < 2){
		tstat2();
	} else {

#ifndef TNOS_68K
	tcb = MK_FP(htoi(argv[1]),8);
#else
	tcb = htoi(argv[1]);
#endif
	if(!tcpval(tcb))
	    tprintf(Notval);
	else
	    st_tcp(tcb);
	}
	return 0;
}

/* Display status of TCBs with MIB info*/
static int
dotcpstat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct tcb *tcb;

	if(argc < 2){
		tstat();
	} else {

#ifndef TNOS_68K
	tcb = MK_FP(htoi(argv[1]),8);
#else
	tcb = htoi(argv[1]);
#endif
	if(!tcpval(tcb))
	    tprintf(Notval);
	else
	    st_tcp(tcb);
	}
	return 0;
}

/* Dump TCP stats and summary of all TCBs
/*     &TCB Rcv-Q Snd-Q  Local socket           Remote socket          State
 *     1234     0     0  xxx.xxx.xxx.xxx:xxxxx  xxx.xxx.xxx.xxx:xxxxx  Established
 * Dump display for TNOS_68K and UNIX
 *     &TCB     Rcv-Q Snd-Q  Local socket           Remote socket          State
 *     123456       0     0  xxx.xxx.xxx.xxx:xxxxx  xxx.xxx.xxx.xxx:xxxxx  Established
 */
static int
tstat2()
{
register struct tcb *tcb;

#if !defined(TNOS_68K) && !defined(UNIX)
    tprintf("&TCB Rcv-Q Snd-Q  Local socket           Remote socket          State\n");
#else
    tprintf("&TCB     Rcv-Q Snd-Q  Local socket           Remote socket          State\n");
#endif
	for(tcb=Tcbs;tcb != NULLTCB;tcb = tcb->next){
#if !defined(TNOS_68K) && !defined(UNIX)
        tprintf("%4.4x%6u%6u  ",
#else
        tprintf("%-8.8lx%6u%6u  ",
#endif
		FP_SEG(tcb),tcb->rcvcnt,tcb->sndcnt);
		tprintf("%-23s",pinet(&tcb->conn.local));
		tprintf("%-23s",pinet(&tcb->conn.remote));
		tprintf("%-s",Tcpstates[tcb->state]);
		if(tcb->state == TCP_LISTEN && tcb->flags.clone)
			tprintf(" (S)");
		if(tprintf("\n") == EOF)
			return 0;
	}
	return 0;
}


static int
tstat()
{
register int i;
int j;

	for(j=i=1;i<=NUMTCPMIB;i++){
		if(Tcp_mib[i].name == NULLCHAR)
			continue;
		tprintf("(%2u)%-20s%10lu",i,Tcp_mib[i].name,
		 Tcp_mib[i].value.integer);
		if(j++ % 2)
			tprintf("     ");
		else
			tprintf("\n");
	}
	if((j % 2) == 0)
		tprintf("\n");

	return (tstat2());
}


/* Dump a TCP control block in detail */
void
st_tcp(tcb)
struct tcb *tcb;
{
	int32 sent,recvd;

	if(tcb == NULLTCB)
		return;

	rxtx_data_compute(tcb,&sent,&recvd);

	tprintf("Local: %s",pinet(&tcb->conn.local));
	tprintf(" Remote: %s",pinet(&tcb->conn.remote));
	tprintf(" State: %s\n",Tcpstates[tcb->state]);
	tprintf("      Init seq    Unack     Next Resent CWind Thrsh  Wind  MSS Queue      Total\n");
	tprintf("Send:");
	tprintf("%9lx",tcb->iss);
	tprintf("%9lx",tcb->snd.una);
	tprintf("%9lx",tcb->snd.nxt);
	tprintf("%7lu",tcb->resent);
	tprintf("%6u",tcb->cwind);
	tprintf("%6u",tcb->ssthresh);
	tprintf("%6u",tcb->snd.wnd);
	tprintf("%5u",tcb->mss);
	tprintf("%6u",tcb->sndcnt);
	tprintf("%11lu\n",sent);

	tprintf("Recv:");
	tprintf("%9lx",tcb->irs);
	tprintf("         ");
	tprintf("%9lx",tcb->rcv.nxt);
	tprintf("%7lu",tcb->rerecv);
	tprintf("      ");
	tprintf("      ");
	tprintf("%6u",tcb->rcv.wnd);
	tprintf("     ");
	tprintf("%6u",tcb->rcvcnt);
	tprintf("%11lu\n",recvd);

	if(tcb->reseq != (struct reseq *)NULL){
		register struct reseq *rp;

		tprintf("Reassembly queue:\n");
		for(rp = tcb->reseq;rp != (struct reseq *)NULL; rp = rp->next){
			if(tprintf("  seq x%lx %u bytes\n",
			 rp->seg.seq,rp->length) == EOF)
				return;
		}
	}
	if(tcb->backoff > 0)
		tprintf("Backoff %u ",tcb->backoff);
	if(tcb->flags.retran)
		tprintf("Retrying ");
	switch(tcb->timer.state){
	case TIMER_STOP:
		tprintf("Timer stopped ");
		break;
	case TIMER_RUN:
		tprintf("Timer running (%ld/%ld ms) ",
		 (long)read_timer(&tcb->timer),
		 (long)dur_timer(&tcb->timer));
		break;
	case TIMER_EXPIRE:
		tprintf("Timer expired ");
	}
	tprintf("SRTT %ld ms Mean dev %ld ms\n",tcb->srtt,tcb->mdev);
}

void
rxtx_data_compute(tcb,sent,recvd)
struct tcb *tcb;
int32 *sent;
int32 *recvd;
{

	/* Compute total data sent and received; take out SYN and FIN */
	*sent = tcb->snd.una - tcb->iss;        /* Acknowledged data only */
	*recvd = tcb->rcv.nxt - tcb->irs;
	switch(tcb->state){
	case TCP_LISTEN:
	case TCP_SYN_SENT:      /* Nothing received or acked yet */
		*sent = *recvd = 0;     
		break;
	case TCP_SYN_RECEIVED:
		(*recvd)--;     /* Got SYN, no data acked yet */
		*sent = 0;
		break;
	case TCP_ESTABLISHED:   /* Got and sent SYN */
	case TCP_FINWAIT1:      /* FIN not acked yet */
		(*sent)--;
		(*recvd)--;
		break;
	case TCP_FINWAIT2:      /* Our SYN and FIN both acked */
		*sent -= 2;
		(*recvd)--;
		break;
	case TCP_CLOSE_WAIT:    /* Got SYN and FIN, our FIN not yet acked */
	case TCP_CLOSING:
	case TCP_LAST_ACK:
		(*sent)--;
		*recvd -= 2;
		break;
	case TCP_TIME_WAIT:     /* Sent and received SYN/FIN, all acked */
		*sent -= 2;
		*recvd -= 2;
		break;
	}
}

/* TCP View Command - D. Crompton 1/92 */
/* Modified for sorted display and     */
/* two views - tcp view b|t - 3/92     */

static int
dotcpview(argc,argv,p)
int argc;
char *argv[];
void *p;

{
	register struct tcb *tcb;
	int32 sent,recvd;
	int i,j,k=0,vtype;
	char temp[40];
	char *buf;

    if(argc == 1)
        vtype = 1;
    else {
        switch (argv[1][0]) {
            case 'b':  vtype=1;
			   break;
            case 't':  vtype=0;
			   break;
            default:   tprintf("Use: tcp view <bytes|timers>\n");
			   return 0;
        }
    }
	
	for(tcb=Tcbs,i=0;tcb != NULLTCB;tcb = tcb->next){
		if(tcb->state == TCP_LISTEN)
			continue;
		i++;
	}
	
	if (i) {
	 buf=mallocw((unsigned)(i*80));

	 if (vtype) {
	  tprintf("                                               Send  Send       Receive Receive\n");
	  tprintf("Remote Socket:Port:Local Port/State   &TCB     Bytes Retries      Bytes Retries\n");
	 } else {
	  tprintf("Remote Socket:Port:Local Port/State   &TCB Boff State      Timer          SRTT\n");
	 }                                                                     
	 for(tcb=Tcbs,j=0;tcb != NULLTCB;tcb = tcb->next){
		if(tcb->state == TCP_LISTEN)
			continue;
		 
		strcpy(temp,pinet(&tcb->conn.remote));
		strcat(temp,strstr(pinet(&tcb->conn.local),":"));
		strcat(temp,"/");
		strcat(temp,Tcpstates[tcb->state]);
		temp[37]=0;
		k=sprintf(&buf[j],"%-37s",temp);
		sprintf(temp,"%8lx",ptol(tcb));
		temp[4]=0;
		k+=sprintf(&buf[j+k]," %4s",temp);
		if (vtype) {
		  rxtx_data_compute(tcb,&sent,&recvd);
		  k+=sprintf(&buf[j+k],"%10lu ",sent);
		  k+=sprintf(&buf[j+k],"%7lu ",tcb->resent);
		  k+=sprintf(&buf[j+k],"%10lu ",recvd);
		  sprintf(&buf[j+k],"%7lu",tcb->rerecv);
		} else {
		  k+=sprintf(&buf[j+k]," %4u",tcb->backoff);
		  if(tcb->flags.retran)
		      k+=sprintf(&buf[j+k]," Retry ");
		  else
		      k+=sprintf(&buf[j+k],"  Try  ");
		  switch(tcb->timer.state) {
		       case TIMER_STOP:
			    k+=sprintf(&buf[j+k],"      Stopped");
			    break;
		       case TIMER_RUN:
			    k+=sprintf(&buf[j+k]," Run (");
			    if ((long)dur_timer(&tcb->timer)<10000) {
			      k+=sprintf(&buf[j+k],"%ld/%ld)ms",
			       (long)read_timer(&tcb->timer),
			       (long)dur_timer(&tcb->timer));
			    } else {
			    if (((long)read_timer(&tcb->timer)/1000)>9999) {
			      k+=sprintf(&buf[j+k],">9999/9999)s");
			    } else {
			      k+=sprintf(&buf[j+k],"%ld/%ld)s",
			       (long)read_timer(&tcb->timer)/1000,
			       (long)dur_timer(&tcb->timer)/1000);
			     }
			    }
			    break;
		       case TIMER_EXPIRE:
			    k+=sprintf(&buf[j+k],"      Expired");
		  }
		  for (;k<73;k++)
		       buf[j+k]=' ';
		  if ((tcb->srtt)<10000) {
		      sprintf(&buf[j+73],"%4ldms",tcb->srtt);
		  } else {
		  if ((tcb->srtt/1000)>9999) {
		      sprintf(&buf[j+73],">9999s");
		  } else {
		      sprintf(&buf[j+73],"%4lds",tcb->srtt/1000);
		   }
		  }
		 }
		 j+=80;
		}

		qsort(buf,(size_t)i,80,strcmp);

		for (j=0,k=0;j<i;j++,k+=80) {
		tprintf("%s",&buf[k]);
		if(tprintf("\n") == EOF)
			return 0;
		}
	    free(buf);
	  }
	return 0;
}

/* tcp timers type - linear v exponential */
static int
dotcptimer(argc,argv,p)
int argc ;
char *argv[] ;
void *p ;
{
	extern int tcptimertype;

	if (argc < 2) {
		tprintf("Tcp timer type is %s\n", tcptimertype ? "linear" : "exponential" ) ;
		return 0 ;
	}
	
	switch (argv[1][0]) {
		case 'l':
		case 'L':
			tcptimertype = 1 ;
			break ;
		case 'e':
		case 'E':
			tcptimertype = 0 ;
			break ;
		default:
			tprintf("use: tcp timertype [linear|exponential]\n") ;
			return -1 ;
	}

	return 0 ;
}
#ifdef  TCPACCESS
static int
doaccess(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int32 target;
	unsigned bits;
	char *bitp;
	int16 lport,hport,state;
	char *cp; /* for printing the table */
	struct rtaccess *tpacc;
	struct rtaccess *head;
	struct rtaccess *prev;

	if(argc == 1){ /* print out the table */
		tprintf("IP Address      Mask  Low Port High Port State\n");
		for(tpacc = TCPaccess;tpacc != NULLACCESS;tpacc = tpacc->nxtbits){
			if(tpacc->target != 0)
				cp = inet_ntoa(tpacc->target);
			else
				cp = "all";
			tprintf("%-16s",cp);
			tprintf("%4u ",tpacc->bits);
			tprintf("%9u",tpacc->lowport);
			tprintf("%10u ",tpacc->highport);
			if(tpacc->status)
				cp = "deny";
			else
				cp = "permit";
			tprintf("%-6s\n",cp);
		}
		return 0;
	}

	if(strcmp(argv[1],"permit") == 0){
		state = 0;
	} else {
		if((strcmp(argv[1],"deny") == 0)
		|| (strcmp(argv[1],"delete") == 0)){
			state = -1;
		} else {
			tprintf(" Format: tcp access <permit|deny|delete> <dest addr>[/<bits>] [lowport [highport]]\n");
			return 1;
		}
	}
	if(strcmp(argv[2],"all") == 0){
		target = 0;
		bits = 0;
	} else {
		/* If IP address is followed by an optional slash and
		 * a length field, (e.g., 128.96/16) get it;
		 * otherwise assume a full 32-bit address
		 */
		if((bitp = strchr(argv[2],'/')) != NULLCHAR){
			/* Terminate address token for resolve() call */
			*bitp++ = '\0';
			bits = atoi(bitp);
		} else
			bits = 32;

		if((target = resolve(argv[2])) == 0){
			tprintf(Badhost,argv[2]);
			return 1;
		}
	}

	if(argc > 3){
		if(strcmp(argv[3],"all") == 0){
			lport = 1;
			hport = 65534;
		} else {
			lport = atoi(argv[3]);
			hport = lport;
		}
	} else {
		lport = 0;
		hport = 0;
	}
	if(argc > 4)
		hport = atoi(argv[4]);

	if(strcmp(argv[1],"delete") == 0){
		prev = NULLACCESS;
		head = tpacc = TCPaccess;
		while(tpacc != NULLACCESS){
			head = tpacc;
			tpacc = tpacc->nxtbits;
			if((head->target == target) &&
			   (head->bits == bits)     &&
			   (head->lowport == lport) &&
			   (head->highport == hport)) { /*match*/


				/*now delete. watch for special cases*/
				if(head == TCPaccess) /* first in chain */
					TCPaccess = head->nxtbits;
				else
					/*
					  sanity check: we cant get here with
					  prev == NULLACCESS !!
					 */
					prev->nxtbits = tpacc;
				free(head);
				return 0;
			}
			prev = head;
		}
		tprintf("Not found.\n");
		return 1;
	}
	/* add the access */
	addtaccess(target,bits,lport,hport,state);
	return 0;
}
/* add an entry to the access control list */
/* not a lot of error checking 8-) */
void
addtaccess(target,bits,low,high,permit)
int32 target;           /* Target IP address prefix */
unsigned int bits;      /* Size of target address prefix in bits (0-32) */
int16 low;
int16 high;
int16 permit;
{
	struct rtaccess *tpacc; /*temporary*/
	struct rtaccess *holder; /*for the new record*/

	holder = (struct rtaccess *)callocw(1,sizeof(struct rtaccess));
	holder->nxtiface = NULLACCESS;
	holder->nxtbits = NULLACCESS;
	holder->target = target;
	holder->bits = bits;
	holder->lowport = low;
	holder->highport = high;
	holder->status = permit;
	if((tpacc = TCPaccess) == NULLACCESS){
		TCPaccess = holder;
	} else {
		while(tpacc->nxtbits != NULLACCESS)
			tpacc = tpacc->nxtbits;
		tpacc->nxtbits = holder;
	}
}
/* check to see if port is "authorized".  Returns 0 if matching permit record
   is found or no access records exists, -1 if not found or deny record found */
int
tcp_check(accptr,src,port)
struct rtaccess *accptr;
int32 src;
int16 port;
{
	unsigned long mask;

	if(accptr == NULLACCESS)
		return 0;		/* no access control */
	for(;accptr != NULLACCESS;accptr = accptr->nxtbits) {
		mask = ~0L << (32 - accptr->bits);
		if(( accptr->target == (mask & src)) &&
		  ((( port >= accptr->lowport ) && (port <= accptr->highport))
		   || (!accptr->lowport))){
			return (accptr->status);
		}
	}
	return -1; /* fall through to here if not found */
}
#endif

