[comp.sources.amiga] v89i085: amigatcp - tcp/ip for the amiga, Part06/06

page@swan.ulowell.edu (Bob Page) (03/18/89)

Submitted-by: rminnich@super.org (Ronald G. Minnich)
Posting-number: Volume 89, Issue 85
Archive-name: comm/amigatcp.6

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	main.c
#	tcpin.c
#	newtelnetp.c
# This archive created: Fri Mar 17 18:00:34 1989
cat << \SHAR_EOF > main.c
/*
 *  Amiga version changes are 
 *
 *  Copyright (c)1987
 *  Louis A. Mamakos
 *
 *  For non-commercial use only.
 */

/* Main network program - provides both client and server functions */
#define	NSESSIONS	10	/* Maximum interactive client sessions */
#define HOSTNAMELEN 32		/* changed from 16 by Bdale 860812 */

#ifndef	STARTUP
#ifdef	AMIGA
#define	STARTUP "s:net-sequence"
#else
#define	STARTUP	"/autoexec.net"	/* File to read startup commands from */
#endif
#endif

#include <stdio.h>
#include "machdep.h"
#include "mbuf.h"
#include "netuser.h"
#include "timer.h"
#include "icmp.h"
#include "iface.h"
#include "ip.h"
#include "tcp.h"
#include "ftp.h"
#include "telnet.h"
#include "session.h"
#include "cmdparse.h"

#ifdef	AMIGA
#include "amiga.h"
#else
#include "pc.h"
#endif

extern struct interface *ifaces;
struct session sessions[NSESSIONS];
struct session *current;
extern char major_rev[], minor_rev[];

int mode;
FILE *logfp;

#ifdef	TRACE
#include "trace.h"
int32 trace;
#endif

char hostname[HOSTNAMELEN];	
int32 aton();
int16 lport = 1001;
#ifdef	AMIGA
char prompt[] = "\33[1;33mnet>\33[m ";	/* make it standout and stuff */
#else
char prompt[] = "net> ";
#endif
char nospace[] = "No space!!\r\n";	/* Generic malloc fail message */
static char notval[] = "Not a valid TCB\r\n";

#ifndef	MSDOS			/* PC uses F-10 key always */
static char escape = 0x1d;	/* default escape character is ^] */
#endif

/* Command lookup and branch table */
int go(),cmdmode(),dodigipeat(),
	doipaddr(),dotelnet(),doexit(),doclose(),dohostname(),
	doreset(),dotcpstat(),dotrace(),doescape(),dospeed(),dohelp(),
	dowindow(),doroute(),doecho(),
	dolog(),doipstat(),doicmpstat(),doetherstat(),memstat(),doarp(),
	dosession(),doftp(),domss(),dostart(),dostop(),doattach(),
	domycall(),dosmtptick(),doudpstat(),dottl(),dokiss(),dotcpkick(),
	doeol();

#ifdef HAPN
int	dohapnstat();
#endif HAPN
#ifdef AMIGADEVDRV
int	DriverInit(), DriverShutdown(), CheckTcp();
#endif
static struct cmds cmds[] = {
	/* The "go" command must be first */
	"",		go,		0, NULLCHAR,	NULLCHAR,
	"arp",		doarp,		0, NULLCHAR,	NULLCHAR,
	"attach",	doattach,	2,
		"attach <hardware> <hw specific options>", NULLCHAR,
	"close",	doclose,	0, NULLCHAR,	NULLCHAR,
#ifdef	AX25
	"digipeat",	dodigipeat,	0, NULLCHAR,	NULLCHAR,
#endif
#ifdef AMIGADEVDRV
	"checktcp",	CheckTcp,	0, NULLCHAR,	NULLCHAR,
	"driver",	DriverInit,	0, NULLCHAR,	NULLCHAR,
	"driveroff",	DriverShutdown,	0, NULLCHAR,	NULLCHAR,
#endif
	"echo",		doecho,		0, NULLCHAR,	NULLCHAR,   
	"eol",		doeol,		0, NULLCHAR,
		"eol options: unix, standard",
#ifndef	MSDOS
	"escape",	doescape,	0, NULLCHAR,	NULLCHAR,   
#endif
#ifdef	PC_EC
	"etherstat",	doetherstat,	0, NULLCHAR,	NULLCHAR,
#endif  PC_EC
	"exit",		doexit,		0, NULLCHAR,	NULLCHAR,
	"ftp",		doftp,		2, "ftp <address>",	NULLCHAR,
#ifdef HAPN
	"hapnstat",	dohapnstat,	0, NULLCHAR,	NULLCHAR,
#endif HAPN
	"help",		dohelp,		0, NULLCHAR,	NULLCHAR,
	"hostname",	dohostname,	0, NULLCHAR,	NULLCHAR,
	"log",		dolog,		0, NULLCHAR,	NULLCHAR,
	"icmpstat",	doicmpstat,	0, NULLCHAR,	NULLCHAR,
	"ipaddr",	doipaddr,	0, NULLCHAR,	NULLCHAR,
	"ipstat",	doipstat,	0, NULLCHAR,	NULLCHAR,
#ifdef	AX25
	"kiss",		dokiss,		0, NULLCHAR,	NULLCHAR,
#endif
	"smtp",		dosmtptick,	0, NULLCHAR,	NULLCHAR,
#ifndef	AMIGA
	"memstat",	memstat,	0, NULLCHAR,	NULLCHAR,
#endif
	"mss",		domss,		0, NULLCHAR,	NULLCHAR,
#ifdef	AX25
	"mycall",	domycall,	0, NULLCHAR,	NULLCHAR,
#endif
	"reset",	doreset,	0, NULLCHAR,	NULLCHAR,
	"route",	doroute,	0, NULLCHAR,	NULLCHAR,
	"session",	dosession,	0, NULLCHAR,	NULLCHAR,
	"speed",	dospeed,	0, NULLCHAR,	NULLCHAR,
#ifdef	SERVERS
	"start",	dostart,	2, "start <servername>",NULLCHAR,
	"stop",		dostop,		2, "stop <servername>",	NULLCHAR,
#endif
	"tcpstat",	dotcpstat,	0, NULLCHAR,	NULLCHAR,
	"tcpkick",	dotcpkick,	0, NULLCHAR,	NULLCHAR,
	"telnet",	dotelnet,	2, "telnet <address>",	NULLCHAR,
	"trace",	dotrace,	0, NULLCHAR,	NULLCHAR,
	"ttl",		dottl,		0, NULLCHAR,	NULLCHAR,
	"udpstat",	doudpstat,	0, NULLCHAR,	NULLCHAR,
	"window",	dowindow,	0, NULLCHAR,	NULLCHAR,
	"?",		dohelp,		0, NULLCHAR,	NULLCHAR,
	NULLCHAR,	NULLFP,		0,
		"Unknown command; type \"?\" for list",   NULLCHAR, 
};

/* "route" subcommands */
int doadd(),dodrop();
static struct cmds rtcmds[] = {
	"add", doadd, 3,
	"route add <dest addr>[/<bits>] <if name> [gateway] [metric]",
	"Add failed",

	"drop", dodrop, 2,
	"route drop <dest addr>[/<bits>]",
	"Not in table",

	NULLCHAR, NULLFP, 0,
	"route subcommands: add, drop",
	NULLCHAR, 
};

#ifdef	SERVERS
/* "start" and "stop" subcommands */
int ftp_start(),smtp_start(),discard_start(),echo_start(),telnet_start();
static struct cmds startcmds[] = {
	"discard",	discard_start,	0, NULLCHAR, NULLCHAR,
	"echo",		echo_start,	0, NULLCHAR, NULLCHAR,
	"ftp",		ftp_start,	0, NULLCHAR, NULLCHAR,
	"smtp",		smtp_start,	0, NULLCHAR, NULLCHAR,
	"telnet",	telnet_start,	0, NULLCHAR, NULLCHAR,
	NULLCHAR,	NULLFP,		0,
		"start options: discard, echo, ftp, smtp, telnet", NULLCHAR,
};
int ftp_stop(),smtp_stop(),echo_stop(),discard_stop(),telnet_stop();
static struct cmds stopcmds[] = {
	"discard",	discard_stop,	0, NULLCHAR, NULLCHAR,
	"echo",		echo_stop,	0, NULLCHAR, NULLCHAR,
	"ftp",		ftp_stop,	0, NULLCHAR, NULLCHAR,
	"smtp",		smtp_stop,	0, NULLCHAR, NULLCHAR,
	"telnet",	telnet_stop,	0, NULLCHAR, NULLCHAR,
	NULLCHAR,	NULLFP,		0,
		"stop options: discard, echo, ftp, smtp, telnet",  NULLCHAR,
};
#endif

main(argc,argv)
int argc;
char *argv[];
{
	static char inbuf[BUFSIZ];	/* keep it off the stack */
	int c;
	char *ttybuf,*fgets();
	int16 cnt;
	int ttydriv();
	int cmdparse();
	void check_time();
	FILE *fp;
	struct interface *ifp;
#ifdef	AMIGA
	char	*startup = STARTUP;
#endif

	ioinit();
	printf("KA9Q Internet Protocol Package, v%s.%s\n",major_rev,minor_rev);
#ifdef	AMIGA
	if (argc > 1)
		startup = argv[1];

	if((fp = fopen(startup,"r")) != NULLFILE){
		while(fgets(inbuf,BUFSIZ,fp) != NULLCHAR){
			printf(prompt); printf(inbuf);
			cmdparse(cmds, inbuf);
		}
		fclose(fp);
	} else
		printf("Can't read inital commands from %s\n", startup);
#else
	if((fp = fopen(STARTUP,"r")) != NULLFILE){
		while(fgets(inbuf,BUFSIZ,fp) != NULLCHAR){
			cmdparse(cmds,inbuf);
		}
		fclose(fp);
	}		
#endif
	cmdmode();
	smtpclinit();			/* arm SMTP background client timer */

	/* Main commutator loop */
	for(;;){
		/* Process any keyboard input */
		while((c = kbread()) != -1){
#ifdef	MSDOS
			/* c == -2 means the command escape key (F10) */
			if(c == -2){
				if(mode != CMD_MODE){
					printf("\r\n");
					cmdmode();
				}
				continue;
			}
#else
			if(c == escape && escape != 0 && mode != CMD_MODE) {
				printf("\r\n");
				cmdmode();
				continue;
			}
#endif
			if((cnt = ttydriv(c,&ttybuf)) == 0)
				continue;
			switch(mode){
			case CMD_MODE:
				(void)cmdparse(cmds,ttybuf);
				fflush(stdout);
				break;
			case CONV_MODE:
#ifndef	MSDOS
				if(ttybuf[0] == escape && escape != 0){
					printf("\r\n");
					cmdmode();
				} else
#endif MSDOS
					if(current->parse != NULLFP)
						(*current->parse)(ttybuf,cnt);

				break;
			}
			if(mode == CMD_MODE){
				printf(prompt);
				fflush(stdout);
			}
		}
		/* Service the interfaces */
		for(ifp = ifaces; ifp != NULLIF; ifp = ifp->next){
			if(ifp->recv != NULLFP)
				(*ifp->recv)(ifp);
		}

		/* Service the clock if it has ticked */
		check_time();
#ifdef AMIGADEVDRV
		/* check device driver open request */
		check_driver();
		CheckTcp();
#else
#ifdef	MSDOS
		/* Tell DoubleDos to let the other task run for awhile.
		 * If DoubleDos isn't active, this is a no-op
		 */
		giveup();
#else
		/* Wait until interrupt, then do it all over again */
		eihalt();
#endif
#endif AMIGADEVDRV
	}
}

/* Enter command mode */
int
cmdmode()
{
	if(mode != CMD_MODE){
		mode = CMD_MODE;
		cooked();
		printf(prompt);
		fflush(stdout);
	}
	return 0;
}
/* Select and display sessions */
static
dosession(argc,argv)
int argc;
char *argv[];
{
	unsigned i;
	struct session *s;
	extern char *tcpstates[];
	char *psocket();

	if(argc < 2){
		printf(" #  Type    Remote socket          TCB  State\r\n");
		for(s=sessions;s < & sessions[NSESSIONS];s++){
			switch(s->type){
			case TELNET:
#ifndef	AMIGA
				printf("%c%-3d%Telnet  %-23s%4x %-s\r\n",
					(current == s)? '*':' ',
					s - sessions,
					psocket(&s->cb.telnet->tcb->conn.remote),
					(int)s->cb.telnet->tcb,
					tcpstates[s->cb.telnet->tcb->state]);
#else
				printf("%c%-3d%Telnet  %-23s%6lx %-s\r\n",
					(current == s)? '*':' ',
					s - sessions,
					psocket(&s->cb.telnet->tcb->conn.remote),
					(unsigned long)s->cb.telnet->tcb,
					tcpstates[s->cb.telnet->tcb->state]);
#endif				break;
			case FTP:
#ifndef	AMIGA
				printf("%c%-3d%FTP     %-23s%4x %-s\r\n",
					(current == s)? '*':' ',
					s - sessions,
					psocket(&s->cb.ftp->control->conn.remote),
					(int)s->cb.ftp->control,
					tcpstates[s->cb.ftp->control->state]);
#else
				printf("%c%-3d%FTP     %-23s%6lx %-s\r\n",
					(current == s)? '*':' ',
					s - sessions,
					psocket(&s->cb.ftp->control->conn.remote),
					(unsigned long)s->cb.ftp->control,
					tcpstates[s->cb.ftp->control->state]);
#endif
				break;
			}
		}
		return 0;
	}
	i = atoi(argv[1]);
	if(i > NSESSIONS){
		printf("Invalid session: %d\r\n",i);
		return 1;
	}
	if(sessions[i].type == FREE){
		printf("Inactive session: %d\r\n",i);
		return 1;
	}
	current = &sessions[i];
	go();
	return 0;
}
/* Enter conversational mode with current session */
int
go()
{
	void rcv_char(),r_ctl();

	if(current == NULLSESSION || current->type == FREE)
		return 0;
	mode = CONV_MODE;
	switch(current->type){
	case TELNET:
		if(current->cb.telnet->remote[TN_ECHO])
			raw();	/* Re-establish raw mode if it was set */
		rcv_char(current->cb.telnet->tcb,0); /* Get any pending input */
		break;
	case FTP:
		r_ctl(current->cb.ftp->control,0);
		break;
	}
	return 0;
}
#ifdef	AX25
extern int digipeat;
dodigipeat(argc,argv)
int argc;
char *argv[];
{
	if(argc == 1) {
		printf("digipeat %s\r\n",digipeat ? "on" : "off");
	} else {
		if(strcmp(argv[1],"on") == 0)
			digipeat = 1;
		else
			digipeat = 0;
	}
}
#endif
static
doipaddr(argc,argv)
int argc;
char *argv[];
{
	char *inet_ntoa();

	if(argc < 2)
		printf("%s\r\n",inet_ntoa(ip_addr));
	else
		ip_addr = aton(argv[1]);
	return 0;
}
static
doexit(argc,argv)
int argc;
char *argv[];
{
	iostop();
	DriverShutdown();
	exit(0);
}
static
doclose(argc,argv)
int argc;
char *argv[];
{
	if(current == NULLSESSION){
		printf("No current session\r\n");
		return 0;
	}
	switch(current->type){
	case TELNET:
		close_tcp(current->cb.telnet->tcb);
		break;
	case FTP:
		close_tcp(current->cb.ftp->control);
		break;
	}
	return 0;
}
static
doreset(argc,argv)
int argc;
char *argv[];
{
	long htol();
	struct tcb *tcb;

	if(argc < 2){
		if(current == NULLSESSION){
			printf("No current session\r\n");
			return 1;
		}
		switch(current->type){
		case TELNET:
			tcb = current->cb.telnet->tcb;
			break;
		case FTP:
			tcb = current->cb.ftp->control;
			break;
		}
	} else
		tcb = (struct tcb *)htol(argv[1]);
	if(!tcpval(tcb)){
		printf(notval);
		return 1;
	}
	close_self(tcb,RESET);
	return 0;
}
static
int
dotcpstat(argc,argv)
int argc;
char *argv[];
{
	long htol();
	register struct tcb *tcb;

	if(argc < 2){
		tcpstat();
	} else {
		tcb = (struct tcb *)htol(argv[1]);
		if(tcpval(tcb))
			state_tcp(tcb);
		else
			printf(notval);
	}
	return 0;
}
static
int
dotcpkick(argc,argv)
int argc;
char *argv[];
{
	long htol();
	register struct tcb *tcb;
	void tcp_timeout();

	if(argc < 2){
		if(current == NULLSESSION){
			printf("No current session\r\n");
			return 1;
		}
		switch(current->type){
		case TELNET:
			tcb = current->cb.telnet->tcb;
			break;
		case FTP:
			tcb = current->cb.ftp->control;
			break;
		}
	} else
		tcb = (struct tcb *)htol(argv[1]);

	if(!tcpval(tcb)){
		printf(notval);
		return 1;
	}
	if(argc > 2){
		/* Optional value for SRTT */
		tcb->srtt = atoi(argv[2]);
	}
	/* Don't actually do anything unless something is pending */
	if(tcb->sndcnt != 0){
		tcb->retry = 0;
		tcp_timeout((int *)tcb);
	}
	return 0;
}
static
int
dotrace(argc,argv)
int argc;
char *argv[];
{
	long htol();
#ifdef TRACE
	if(argc < 2)
		printf("trace level 0x%08lx\r\n",trace);
	else
		trace = htol(argv[1]);
	return 0;
#else
	printf("Trace not enabled - recompile with TRACE defined\r\n");
#endif
}
static
dohostname(argc,argv)
int argc;
char *argv[];
{
	char *strncpy();

	if(argc < 2)
		printf("%s\r\n",hostname);
	else 
		strncpy(hostname,argv[1],HOSTNAMELEN);
	return 0;
}
static
int
dolog(argc,argv)
int argc;
char *argv[];
{
	char *strncpy();

#ifdef	AMIGA
	static char logname[45];  /* long enough for CON:10/10/300/300/title */
#else
	static char logname[15];
#endif
	if(argc < 2){
		if(logfp)
			printf("Logging to %s\r\n",logname);
		else
			printf("Logging off\r\n");
		return 0;
	}
	if(logfp){
		fclose(logfp);
		logfp = NULLFILE;
	}
	if(strcmp(argv[1],"stop") != 0){
#ifdef	AMIGA
		strncpy(logname,argv[1],45);
#else
		strncpy(logname,argv[1],15);
#endif
		logfp = fopen(logname,"a+");
	}
	return 0;
}
#ifndef	MSDOS
static
int
doescape(argc,argv)
int argc;
char *argv[];
{
	if(argc < 2)
		printf("0x%x\r\n",escape);
	else 
		escape = *argv[1];
	return 0;
}
#endif	MSDOS
static
int
dohelp(argc,argv)
int argc;
char *argv[];
{
	register struct cmds *cmdp;
	int i,j;

	printf("Main commands:\r\n");
	for(i=0,cmdp = cmds;cmdp->name != NULL;cmdp++,i++){
		printf("%s",cmdp->name);
		if((i % 4) == 3)
			printf("\r\n");
		else {
			for(j=strlen(cmdp->name);j < 16; j++)
				putchar(' ');
		}
	}
	if((i % 4) != 0)
		printf("\r\n");
	return 0;
}
static
int
domss(argc,argv)
int argc;
char *argv[];
{
	if(argc < 2)
		printf("%d\r\n",tcp_mss);
	else
		tcp_mss = atoi(argv[1]);
	return 0;
}
static
int
dowindow(argc,argv)
int argc;
char *argv[];
{
	if(argc < 2)
		printf("%d\r\n",tcp_window);
	else
		tcp_window = atoi(argv[1]);
	return 0;
}
static
int
dottl(argc,argv)
char *argv[];
{
	if(argc < 2)
		printf("%u\r\n",ip_ttl & 0xff);
	else
		ip_ttl = atoi(argv[1]);
	return 0;
}
struct session *
newsession()
{
	register int i;

	for(i=0;i<NSESSIONS;i++)
		if(sessions[i].type == FREE)
			return &sessions[i];
	return NULLSESSION;
}
freesession(s)
struct session *s;
{
	if(s != NULLSESSION)
		s->type = FREE;
}
/* Display and/or manipulate routing table */
int
doroute(argc,argv)
int argc;
char *argv[];
{
	if(argc < 2){
		dumproute();
		return 0;
	}
	return subcmd(rtcmds,argc,argv);
}
/* Add an entry to the routing table
 * E.g., "add 1.2.3.4 ax0 5.6.7.8 3"
 */
int
doadd(argc,argv)
int argc;
char *argv[];
{
	struct interface *ifp;
	int32 dest,gateway;
	unsigned bits;
	char *bitp,*index();
	int metric;

	if(strcmp(argv[1],"default") == 0){
		dest = 0;
		bits = 0;
	} else {
		dest = aton(argv[1]);

		/* 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 = index(argv[1],'/')) != NULLCHAR){
			bitp++;
			bits = atoi(bitp);
		} else
			bits = 32;
	}
	for(ifp=ifaces;ifp != NULLIF;ifp = ifp->next){
		if(strcmp(argv[2],ifp->name) == 0)
			break;
	}
	if(ifp == NULL){
		printf("Interface \"%s\" unknown\r\n",argv[2]);
		return 1;
	}
	if(argc > 3)
		gateway = aton(argv[3]);
	else
		gateway = 0;

	if(argc > 4)
		metric = atoi(argv[4]);
	else
		metric = 0;

	rt_add(dest,bits,gateway,metric,ifp);
	return 0;
}
/* Drop an entry from the routing table
 * E.g., "drop 128.96/16
 */
int
dodrop(argc,argv)
int argc;
char *argv[];
{
	char *bitp,*index();
	unsigned bits;

	/* If IP address is followed by an optional slash and length field,
	 * (e.g., 128.96/16) get it; otherwise assume a full 32-bit address
	 */
	if((bitp = index(argv[1],'/')) != NULLCHAR){
		bitp++;
		bits = atoi(bitp);
	} else
		bits = 32;

	return rt_drop(aton(argv[1]),bits);
}

#ifdef SERVERS
dostart(argc,argv)
int argc;
char *argv[];
{
	return subcmd(startcmds,argc,argv);
}
dostop(argc,argv)
int argc;
char *argv[];
{
	return subcmd(stopcmds,argc,argv);
}
#endif SERVERS

doecho(argc,argv)
int argc;
char *argv[];
{
	extern int refuse_echo;

	if(argc < 2){
		if(refuse_echo)
			printf("Refuse\r\n");
		else
			printf("Accept\r\n");
	} else {
		if(strcmp(argv[1],"refuse") == 0)
			refuse_echo = 1;
		else if(strcmp(argv[1],"accept") == 0)
			refuse_echo = 0;
		else
			return -1;
	}
	return 0;
}
/* set for unix end of line for remote echo mode telnet */
doeol(argc,argv)
int argc;
char *argv[];
{
	extern int unix_line_mode;

	if(argc < 2){
		if(unix_line_mode)
			printf("Unix\r\n");
		else
			printf("Standard\r\n");
	} else {
		if(strcmp(argv[1],"unix") == 0)
			unix_line_mode = 1;
		else if(strcmp(argv[1],"standard") == 0)
			unix_line_mode = 0;
		else {
			return -1;
		}
	}
	return 0;
}
/* List of supported hardware devices */
#ifdef	PC_EC
int ec_attach();
#endif

int asy_attach();

#ifdef	PC100
int pc_attach();
#endif

#ifdef HAPN
int hapn_attach();
#endif

#ifdef	NETROM
int netrom_attach();
#endif

struct cmds attab[] = {
#ifdef	NETROM
	/* NET/ROM virtual interface using companion asy interface in AX.25 mode */
	"netrom", netrom_attach, 4,
	"attach netrom <asy-intf> ax25 <label> [mtu [ upd-freq ]]",
	"Could not attach NET/ROM virtual interface",
#endif
#ifdef	PC_EC
	/* 3-Com Ethernet interface */
	"3c500", ec_attach, 7, 
	"attach 3c500 <address> <vector> arpa <label> <buffers> <mtu>",
	"Could not attach 3c500",
#endif
	/* Ordinary PC asynchronous adaptor */
	"asy", asy_attach, 8, 
	"attach asy <address> <vector> slip|ax25 <label> <buffers> <mtu> <speed>",
	"Could not attach asy",
#ifdef	PC100
	/* PACCOMM PC-100 8530 HDLC adaptor */
	"pc100", pc_attach, 8, 
	"attach pc100 <address> <vector> ax25 <label> <buffers> <mtu> <speed>",
	"Could not attach pc100",
#endif
#ifdef	HAPN
	/* Hamilton Area Packet Radio (HAPN) 8273 HDLC adaptor */
	"hapn", hapn_attach, 8,
	"attach hapn <address> <vector> ax25 <label> <rx bufsize> <mtu> csma|full",
	"Could not attach hapn",
#endif
	NULLCHAR, NULLFP, 0,
	"Unknown device",
	NULLCHAR,
};

/* Attach an interface
 * Syntax: attach <hw type> <I/O address> <vector> <mode> <label> <bufsize> [<speed>]
 */
doattach(argc,argv)
int argc;
char *argv[];
{
	return subcmd(attab,argc,argv);
}
int
dospeed(argc,argv)
int argc;
char *argv[];
{
	int i;

	if(argc < 3){
		for(i=0;i < nasy; i++){
			printf("%d: %d bps\r\n",i,asy[i].speed);
		}
		return 0;
	}
	i = atoi(argv[1]);
	if(i >= nasy){
		printf("Line %d out of range\r\n",i);
		return 0;
	}
	asy_speed(i,atoi(argv[2]));
	return i;
}

/* Log messages of the form
 * Tue Jan 31 00:00:00 1987 44.64.0.7:1003 open FTP
 */
/*VARARGS2*/
log(tcb,fmt,arg1,arg2,arg3,arg4)
struct tcb *tcb;
char *fmt;
int arg1,arg2,arg3,arg4;
{
	char *cp,*cp1,*ctime(),*index();
	long t;

	if(logfp == NULLFILE)
		return;
	time(&t);
	cp = ctime(&t);
	if((cp1 = index(cp,'\n')) != NULLCHAR)
		*cp1 = '\0';
	fprintf(logfp,"%s %s - ",cp,psocket(&tcb->conn.remote));
	fprintf(logfp,fmt,arg1,arg2,arg3,arg4);
	fprintf(logfp,"\n");
	fflush(logfp);
}
/* Define null definitions for unused interfaces to avoid pulling in code
 * from the library
 */
#ifndef	ETHER
int ec_output() {}
int pether() {}
int gether() {}
char *ether_bdcst = NULLCHAR;
#endif ETHER

#ifndef AX25
int setcall() {}
int psax25() {}
struct ax25_addr ax25_bdcst,mycall;
#endif AX25
SHAR_EOF
cat << \SHAR_EOF > tcpin.c
/* Process incoming TCP segments. Page number references are to ARPA RFC-793,
 * the TCP specification.
 */

#include "machdep.h"
#include "timer.h"
#include "mbuf.h"
#include "netuser.h"
#include "internet.h"
#include "tcp.h"
#include "icmp.h"
#include "ip.h"

struct tcp_stat tcp_stat;

/* This function is called from IP with the IP header in machine byte order,
 * along with a mbuf chain pointing to the TCP header.
 */
void
tcp_input(bp,protocol,source,dest,tos,length,rxbroadcast)
struct mbuf *bp;	/* Data field, if any */
char protocol;		/* Should always be TCP_PTCL */
int32 source;		/* Remote IP address */
int32 dest;		/* Our IP address */
char tos;		/* Type of Service */
int16 length;		/* Length of data field */
char rxbroadcast;	/* Incoming broadcast - discard if true */
{
	void reset(),update();
	void proc_syn(),send_syn(),add_reseq(),get_reseq();

	register struct tcb *tcb;	/* TCP Protocol control block */
	struct tcp_header seg;		/* Local copy of segment header */
	int16 mss;			/* Incoming MSS, if any */
	struct connection conn;		/* Local copy of addresses */
	struct pseudo_header ph;	/* Pseudo-header for checksumming */
	int16 optlen;			/* Length of TCP options */

	if(bp == NULLBUF)
		return;

	if(rxbroadcast){
		/* Any TCP packet arriving as a broadcast is
		 * to be completely IGNORED!!
		 */
		tcp_stat.bdcsts++;
		free_p(bp);
	}
	ph.source = source;
	ph.dest = dest;
	ph.protocol = protocol;
	ph.zero = 0;
	ph.length = length;
	if(cksum(&ph,bp,length) != 0){
		/* Checksum failed, ignore segment completely */
		tcp_stat.checksum++;
		free_p(bp);
		return;
	}
	/* Form local copy of TCP header in host byte order */
	if(pullup(&bp,(char *)&seg,sizeof(struct tcp_header))
		 != sizeof(struct tcp_header)){
		/* TCP header is too small */
		tcp_stat.runt++;
		free_p(bp);
		return;
	}
	length -= sizeof(struct tcp_header);
	seg.source = ntohs(seg.source);
	seg.dest = ntohs(seg.dest);
	seg.seq = ntohl(seg.seq);
	seg.ack = ntohl(seg.ack);
	seg.wnd = ntohs(seg.wnd);
	seg.up = ntohs(seg.up);	/* Unused as of yet */

	/* Examine options, if any (there can only be MSS). */
	optlen = hinibble(seg.offset) * sizeof(long) - sizeof(struct tcp_header);
	if(optlen != 0){
		struct mss *mssp;

		mssp = (struct mss *)bp->data;
		if(mssp->kind == MSS_KIND && mssp->length == MSS_LENGTH){
			mss = ntohs(mssp->mss);
		}
		pullup(&bp,NULLCHAR,optlen);
		length -= optlen;
	} else {
		mss = 0;
	}
	/* Fill in connection structure and find TCB */
	conn.local.address = dest;
	conn.local.port = seg.dest;
	conn.remote.address = source;
	conn.remote.port = seg.source;
	
	if((tcb = lookup_tcb(&conn)) == NULLTCB){
		struct tcb *ntcb;
		char *malloc();
		void link_tcb();

		/* Check that this segment carries a SYN, and that
		 * there's a LISTEN on this socket with
		 * unspecified source address and port
		 */
		conn.remote.address = 0;
		conn.remote.port = 0;
		if(!(seg.flags & SYN) || (tcb = lookup_tcb(&conn)) == NULLTCB){
			/* No unspecified LISTEN either, so reject */
			free_p(bp);
			reset(source,dest,tos,length,&seg);
			return;
		}
		/* We've found an unspecified remote socket, so clone the TCB
		 * and stuff the foreign socket into the clone.
		 */
		if((ntcb = (struct tcb *)malloc(sizeof (struct tcb))) == NULLTCB){
			free_p(bp);
			/* This may fail, but we should at least try */
			reset(source,dest,tos,length,&seg);
			return;
		}
		bcopy((char *)tcb,(char *)ntcb,sizeof(struct tcb));
		tcb = ntcb;
		tcb->conn.remote.address = source;
		tcb->conn.remote.port = seg.source;
		tcb->timer.arg = (int *)tcb;
		link_tcb(tcb);
	}
	/* Do unsynchronized-state processing (p. 65-68) */
	switch(tcb->state){
	case CLOSED:
		free_p(bp);
		reset(source,dest,tos,length,&seg);
		return;
	case LISTEN:
		if(seg.flags & RST){
			free_p(bp);
			return;
		}
		if(seg.flags & ACK){
			free_p(bp);
			reset(source,dest,tos,length,&seg);
			return;
		}
		if(seg.flags & SYN){

			/* (Security check is bypassed) */
			/* page 66 */
			tcp_stat.conin++;
			proc_syn(tcb,tos,&seg,mss);
			send_syn(tcb);
			setstate(tcb,SYN_RECEIVED);		
			if(length != 0 || seg.flags & FIN) {
				break;		/* Continue processing if there's more */
			}
			tcp_output(tcb);
		}
		free_p(bp);	/* Unlikely to get here directly */
		return;
	case SYN_SENT:
		if(seg.flags & ACK){
			if(!seq_within(seg.ack,tcb->iss+1,tcb->snd.nxt)){
				free_p(bp);
				reset(source,dest,tos,length,&seg);
				return;
			}
		}
		if(seg.flags & RST){	/* p 67 */
			if(seg.flags & ACK){
				/* The ack must be acceptable since we just checked it.
				 * This is how the remote side refuses connect requests.
				 */
				close_self(tcb,RESET);
			}
			free_p(bp);
			return;
		}
		/* (Security check skipped here) */
		/* Check incoming precedence; it must match if there's an ACK */
		if((seg.flags & ACK) && PREC(tos) != PREC(tcb->tos)){
			free_p(bp);
			reset(source,dest,tos,length,&seg);
			return;
		}
		if(seg.flags & SYN){
			proc_syn(tcb,tos,&seg,mss);
			if(seg.flags & ACK){
				/* Our SYN has been acked, otherwise the ACK
				 * wouldn't have been valid.
				 */
				update(tcb,&seg);
				setstate(tcb,ESTABLISHED);
			} else {
				setstate(tcb,SYN_RECEIVED);
			}
			if(length != 0 || (seg.flags & FIN)) {
				break;		/* Continue processing if there's more */
			}
			tcp_output(tcb);
		} else {
			free_p(bp);	/* Ignore if neither SYN or RST is set */
		}
		return;
	}
	/* We reach this point directly in any synchronized state. Note that
	 * if we fell through from LISTEN or SYN_SENT processing because of a
	 * data-bearing SYN, window trimming and sequence testing "cannot fail".
	 */

	/* Trim segment to fit receive window. */
	if(trim(tcb,&seg,&bp,&length) == -1){
		/* Segment is unacceptable */
		if(!(seg.flags & RST)){
			tcb->force = 1;
			tcp_output(tcb);
		}
		return;
	}
	/* If segment isn't the next one expected, and there's data
	 * or flags associated with it, put it on the resequencing
	 * queue and return. Don't send anything in reply.
	 *
	 * Processing the ACK in an out-of-sequence segment without
	 * flags or data should be safe, however.
	 */
	if(seg.seq != tcb->rcv.nxt
	 && (length != 0 || (seg.flags & (SYN|FIN)) )){
		add_reseq(tcb,tos,&seg,bp,length);
		return;
	}
	/* This loop first processes the current segment, and then
	 * repeats if it can process the resequencing queue.
	 */
	for(;;){
		/* We reach this point with an acceptable segment; all data and flags
		 * are in the window, and the starting sequence number equals rcv.nxt
		 * (p. 70)
		 */	
		if(seg.flags & RST){
			switch(tcb->state){
			case SYN_RECEIVED:
				setstate(tcb,LISTEN);
				free_p(bp);
				return;
			default:
				close_self(tcb,RESET);
				free_p(bp);
				return;
			}
		}
		/* (Security check skipped here) p. 71 */
		/* Check for precedence mismatch or erroneous extra SYN */
		if(PREC(tos) != PREC(tcb->tos) || (seg.flags & SYN)){
			free_p(bp);
			reset(source,dest,tos,length,&seg);
			return;
		}
		/* Check ack field p. 72 */
		if(!(seg.flags & ACK)){
			free_p(bp);	/* All segments after synchronization must have ACK */
			return;
		}
		/* Process ACK */
		switch(tcb->state){
		case SYN_RECEIVED:
			if(seq_within(seg.ack,tcb->snd.una+1,tcb->snd.nxt)){
				update(tcb,&seg);
				setstate(tcb,ESTABLISHED);
			} else {
				free_p(bp);
				reset(source,dest,tos,length,&seg);
				return;
			}
			break;
		case ESTABLISHED:
		case CLOSE_WAIT:
			update(tcb,&seg);
			break;
		case FINWAIT1:	/* p. 73 */
			update(tcb,&seg);
			if(tcb->sndcnt == 0){
				/* Our FIN is acknowledged */
				setstate(tcb,FINWAIT2);
			}
			break;
		case FINWAIT2:
			update(tcb,&seg);
			break;
		case CLOSING:
			update(tcb,&seg);
			if(tcb->sndcnt == 0){
				/* Our FIN is acknowledged */
				setstate(tcb,TIME_WAIT);
				tcb->timer.start = MSL2;
				start_timer(&tcb->timer);
			}
			break;
		case LAST_ACK:
			update(tcb,&seg);
			if(tcb->sndcnt == 0){
				/* Our FIN is acknowledged, close connection */
				close_self(tcb,NORMAL);
				return;
			}			
		case TIME_WAIT:
			tcb->force = 1;
			start_timer(&tcb->timer);
		}

		/* (URGent bit processing skipped here) */

		/* Process the segment text, if any, beginning at rcv.nxt (p. 74) */
		if(length != 0){
			switch(tcb->state){
			case SYN_RECEIVED:
			case ESTABLISHED:
			case FINWAIT1:
			case FINWAIT2:
				/* Place on receive queue */
				append(&tcb->rcvq,bp);
				tcb->rcvcnt += length;
				tcb->rcv.nxt += length;
				tcb->rcv.wnd -= length;
				tcb->force = 1;
				break;
			default:
				/* Ignore segment text */
				free_p(bp);
				break;
			}
		}
		/* If the user has set up a r_upcall function, AND
		 * the receive window is more than half full OR
		 * has the urg, push or fin flags set, notify him.
		 *
		 * All this is done before sending an acknowledgement,
		 * to give the user a chance to piggyback some reply data.
		 * It's also done before processing FIN so that the state
		 * change upcall will occur after the user has had a chance
		 * to read the last of the incoming data.
		 */
		if(tcb->r_upcall){
			if(tcb->rcvcnt >= tcb->rcv.wnd || (seg.flags & (FIN|URG|PSH))){
				(*tcb->r_upcall)(tcb,tcb->rcvcnt);
			}
		}
		/* process FIN bit (p 75) */
		if(seg.flags & FIN){
			tcb->force = 1;	/* Always respond with an ACK */

			switch(tcb->state){
			case SYN_RECEIVED:
			case ESTABLISHED:
				tcb->rcv.nxt++;
				setstate(tcb,CLOSE_WAIT);
				break;
			case FINWAIT1:
				tcb->rcv.nxt++;
				if(tcb->sndcnt == 0){
					/* Our FIN has been acked; bypass CLOSING state */
					setstate(tcb,TIME_WAIT);
					tcb->timer.start = MSL2;
					start_timer(&tcb->timer);
				} else {
					setstate(tcb,CLOSING);
				}
				break;
			case FINWAIT2:
				tcb->rcv.nxt++;
				setstate(tcb,TIME_WAIT);
				tcb->timer.start = MSL2;
				start_timer(&tcb->timer);
				break;
			case CLOSE_WAIT:
			case CLOSING:
			case LAST_ACK:
				break;		/* Ignore */
			case TIME_WAIT:	/* p 76 */
				start_timer(&tcb->timer);
				break;
			}
		}
		/* Scan the resequencing queue, looking for a segment we can handle,
		 * and freeing all those that are now obsolete.
		 */
		while(tcb->reseq != NULLRESEQ && seq_ge(tcb->rcv.nxt,tcb->reseq->seg.seq)){
			get_reseq(tcb,&tos,&seg,&bp,&length);
			if(trim(tcb,&seg,&bp,&length) == 0)
				goto gotone;
			/* Segment is an old one; trim has freed it */
		}
		break;
gotone:	;
	}
	tcp_output(tcb);	/* Send any necessary ack */
}

/* Process an incoming ICMP response */
tcp_icmp(source,dest,type,code,data)
int32 source;	/* Original IP datagram source (i.e. us) */
int32 dest;	/* Original IP datagram dest (i.e., them) */
char type,code;	/* ICMP error codes */
char *data;	/* First 8 bytes of TCP header */
{
	struct tcp_header *tcph;
	struct connection conn;
	register struct tcb *tcb;

	/* Extract the socket info from the returned TCP header fragment
	 * Note that since this is a datagram we sent, the source fields
	 * refer to the local side.
	 */
	tcph = (struct tcp_header *)data;
	conn.local.address = source;
	conn.local.port = ntohs(tcph->source);
	conn.remote.address = dest;
	conn.remote.port = ntohs(tcph->dest);
	if((tcb = lookup_tcb(&conn)) == NULLTCB)
		return;	/* Unknown connection, ignore */

	/* Verify that the sequence number in the returned segment corresponds
	 * to something currently unacknowledged. If not, it can safely
	 * be ignored.
	 */
	if(!seq_within(ntohl(tcph->seq),tcb->snd.una,tcb->snd.nxt))
		return;

	/* The strategy here is that Destination Unreachable and Time Exceeded
	 * messages that occur after a connection has been established are likely
	 * to be transient events, and shouldn't kill our connection (at least
	 * until after we've tried a few more times). On the other hand, if
	 * they occur on our very first attempt to send a datagram on a new
	 * connection, they're probably "for real". In any event, the info
	 * is saved.
	 */
	switch(type){
	case DEST_UNREACH:
	case TIME_EXCEED:
		tcb->type = type;
		tcb->code = code;
		if(tcb->state == SYN_SENT || tcb->state == SYN_RECEIVED){
			close_self(tcb,NETWORK);
		}
		break;
	case QUENCH:
		break;		/* I really ought to implement this */
	}
}

/* Send an acceptable reset (RST) response for this segment */
static void
reset(source,dest,tos,length,seg)
int32 source;	/* Remote IP address */
int32 dest;		/* Our IP address */
char tos;		/* Type of Service */
int16 length;	/* Length of data portion */
register struct tcp_header *seg;	/* Offending TCP header */
{
	struct mbuf *hbp;
	struct pseudo_header ph;
	register struct tcp_header *tcph;

	if(seg->flags & RST)
		return;	/* Never send an RST in response to an RST */

	tcp_stat.resets++;
	/* Compose the RST IP pseudo-header, swapping addresses */
	ph.source = dest;
	ph.dest = source;
	ph.protocol = TCP_PTCL;
	ph.zero = 0;
	ph.length = sizeof(struct tcp_header);

	/* Allocate mbuf for the RST TCP header and fill it in */
	if((hbp = alloc_mbuf(ph.length)) == NULLBUF)
		return;	/* Can't do nothin' */

	hbp->cnt = ph.length;
	tcph = (struct tcp_header *)hbp->data;

	tcph->source = htons(seg->dest);
	tcph->dest = htons(seg->source);
	tcph->flags = RST;

	if(seg->flags & ACK){
		/* This reset is being sent to clear a half-open connection.
		 * Set the sequence number of the RST to the incoming ACK
		 * so it will be acceptable.
		 */
		tcph->seq = htonl(seg->ack);
		tcph->ack = 0;
	} else {
		/* We're rejecting a connect request (SYN) from LISTEN state
		 * so we have to "acknowledge" their SYN.
		 */
		tcph->seq = 0;
		if(seg->flags & SYN)
			length++;
		if(seg->flags & FIN)
			length++;
		tcph->ack = htonl((int32)(seg->seq + length));
		tcph->flags |= ACK;
	}
	tcph->offset = (ph.length/sizeof(long)) << DSHIFT;
	tcph->wnd = 0;
	tcph->checksum = 0;
	tcph->up = 0;
	tcph->checksum = cksum(&ph,hbp,ph.length);
	/* Ship it out (note swap of addresses) */
	ip_send(dest,source,TCP_PTCL,tos,0,hbp,ph.length,0,0);
}

/* Process an incoming acknowledgement and window indication.
 * From page 72.
 */
static void
update(tcb,seg)
register struct tcb *tcb;
register struct tcp_header *seg;
{
	int16 acked;
	int32 rtt;

	acked = 0;
	if(seq_gt(seg->ack,tcb->snd.nxt)){
		tcb->force = 1;	/* Acks something not yet sent */
		return;
	}
	/* Round trip time estimation */
	if(seq_gt(tcb->rttseq,tcb->snd.una)){
		/* A sequence number is actively being timed */
		if(tcb->retry == 0 && seq_ge(seg->ack,tcb->rttseq)){
			/* This packet was sent only once and now
			 * it's been acked, so compute the new smoothed
			 * estimate. The RT time is just the current time
			 * on the retransmission timer.
			 */
			rtt = tcb->timer.start - tcb->timer.count;
			rtt *= MSPTICK;	/* convert to ticks */
			/* Fast attack/slow decay algorithm by Dave Mills
			 * (see RFC-889)
			 */
			if(rtt > tcb->srtt){
				/* RTT is increasing, use fast attack */
				tcb->srtt = (ALPHA1*tcb->srtt + rtt)/(ALPHA1+1);
			} else {
				/* RTT is decreasing, use slow decay */
				tcb->srtt = (ALPHA2*tcb->srtt + rtt)/(ALPHA2+1);
			}
			/* Set new retransmission timeout */
			tcb->timer.start = (BETA * tcb->srtt)/MSPTICK;
		}
	}
	/* If the remote window is closed, reset the retry counter even if
	 * this packet doesn't acknowledge anything new as long as the ACK
	 * is current.
	 * This allows closed-window probes to go on indefinitely.
	 */
	if(seg->wnd == 0 && seq_ge(seg->ack,tcb->snd.una))
		tcb->retry = 0;

	/* See if anything new is being acknowledged */
	if(seq_gt(seg->ack,tcb->snd.una)){
		acked = seg->ack - tcb->snd.una;
		/* If we're waiting for an ack of our SYN, process it */
		switch(tcb->state){
		case SYN_SENT:
		case SYN_RECEIVED:
			acked--;
			tcb->sndcnt--;
		}
		/* Remove acknowledged bytes from the send queue and update the
		 * unacknowledged pointer. If a FIN is being acked,
		 * pullup won't be able to remove it from the queue.
		 */
		pullup(&tcb->sndq,NULLCHAR,acked);

		/* This will include the FIN if there is one */
		tcb->sndcnt -= acked;
		tcb->snd.una = seg->ack;

		/* If retransmissions have been occurring, make sure the
		 * send pointer doesn't repeat ancient history
		 */
		if(seq_lt(tcb->snd.ptr,tcb->snd.una))
			tcb->snd.ptr = tcb->snd.una;

		/* Reset the retry counter */
		tcb->retry = 0;
		/* Stop retransmission timer, but restart it if there is still
		 * unacknowledged data
		 */	
		stop_timer(&tcb->timer);
		if(tcb->snd.una != tcb->snd.nxt){
			start_timer(&tcb->timer);
		}
	}
	/* Decide if we need to do a window update.
	 * This is always checked whenever a legal ACK is received,
	 * because it might be a spontaneous window reopening.
	 */
	if(seq_gt(seg->seq,tcb->snd.wl1) || ((seg->seq == tcb->snd.wl1) 
	 && seq_ge(seg->ack,tcb->snd.wl2))){
		/* If the window had been closed, crank back the
		 * send pointer so we'll immediately resume transmission.
		 * Otherwise we'd have to wait until the next probe.
		 */
		if(tcb->snd.wnd == 0 && seg->wnd != 0)
			tcb->snd.ptr = tcb->snd.una;
		tcb->snd.wnd = seg->wnd;
		tcb->snd.wl1 = seg->seq;
		tcb->snd.wl2 = seg->ack;
	}
	/* If outgoing data was acked, notify the user so he can send more
	 * unless we've already sent a FIN.
	 */
	if(acked != 0 && tcb->t_upcall){
		switch(tcb->state){
	 	case ESTABLISHED:
		case CLOSE_WAIT:
			(*tcb->t_upcall)(tcb,tcb->window - tcb->sndcnt);
		}
	}
}

/* Determine if the given sequence number is in our receiver window.
 * NB: must not be used when window is closed!
 */
static
int
in_window(tcb,seq)
struct tcb *tcb;
int32 seq;
{
	return seq_within(seq,tcb->rcv.nxt,(int32)(tcb->rcv.nxt+tcb->rcv.wnd-1));
}

/* Process an incoming SYN */
static void
proc_syn(tcb,tos,seg,mss)
register struct tcb *tcb;
char tos;
struct tcp_header *seg;
int16 mss;
{
	int16 mtu,ip_mtu();

	tcb->force = 1;	/* Always send a response */

	/* Note: It's not specified in RFC 793, but SND.WL1 and
	 * SND.WND are initialized here since it's possible for the
	 * window update routine in update() to fail depending on the
	 * IRS if they are left unitialized.
	 */
	/* Check incoming precedence and increase if higher */
	if(PREC(tos) > PREC(tcb->tos))
		tcb->tos = tos;
	tcb->rcv.nxt = seg->seq + 1;	/* p 68 */
	tcb->snd.wl1 = tcb->irs = seg->seq;
	tcb->snd.wnd = seg->wnd;
	if(mss != 0)
		tcb->mss = mss;
	/* Check the MTU of the interface we'll use to reach this guy
	 * and lower the MSS so that unnecessary fragmentation won't occur
	 */
	if((mtu = ip_mtu(tcb->conn.remote.address)) != 0){
		/* Allow space for the TCP and IP headers */
		mtu -= sizeof(struct tcp_header) + sizeof(struct ip_header);
		tcb->mss = min(mtu,tcb->mss);
	}
}

/* Generate an initial sequence number and put a SYN on the send queue */
void
send_syn(tcb)
register struct tcb *tcb;
{
	tcb->iss = iss();
	tcb->rttseq = tcb->snd.ptr = tcb->snd.wl2 = tcb->snd.nxt = tcb->snd.una = tcb->iss;
	tcb->sndcnt++;
	tcb->force = 1;
}

/* Add an entry to the resequencing queue in the proper place */
static void
add_reseq(tcb,tos,seg,bp,length)
struct tcb *tcb;
char tos;
struct tcp_header *seg;
struct mbuf *bp;
int16 length;
{
	register struct reseq *rp,*rp1;
	char *malloc();

	/* Allocate reassembly descriptor */
	if((rp = (struct reseq *)malloc(sizeof (struct reseq))) == NULLRESEQ){
		/* No space, toss on floor */
		free_p(bp);
		return;
	}
	bcopy((char *)seg,(char *)&rp->seg,sizeof(struct tcp_header));
	rp->tos = tos;
	rp->bp = bp;
	rp->length = length;

	/* Place on reassembly list sorting by starting seq number */
	rp1 = tcb->reseq;
	if(rp1 == NULLRESEQ || seq_lt(seg->seq,rp1->seg.seq)){
		/* Either the list is empty, or we're less than all other
		 * entries; insert at beginning.
		 */
		rp->next = rp1;
		tcb->reseq = rp;
	} else {
		/* Find the last entry less than us */
		for(;;){
			if(rp1->next == NULLRESEQ || seq_lt(seg->seq,rp1->next->seg.seq)){
				/* We belong just after this one */
				rp->next = rp1->next;
				rp1->next = rp;
				break;
			}
			rp1 = rp1->next;
		}
	}
}

/* Fetch the first entry off the resequencing queue */
static void
get_reseq(tcb,tos,seg,bp,length)
register struct tcb *tcb;
char *tos;
struct tcp_header *seg;
struct mbuf **bp;
int16 *length;
{
	register struct reseq *rp;

	if((rp = tcb->reseq) == NULLRESEQ)
		return;

	tcb->reseq = rp->next;

	*tos = rp->tos;
	bcopy((char *)&rp->seg,(char *)seg,sizeof(struct tcp_header));
	*bp = rp->bp;
	*length = rp->length;
	free((char *)rp);
}

/* Trim segment to fit window. Return 0 if OK, -1 if segment is
 * unacceptable.
 */
static int
trim(tcb,seg,bp,length)
register struct tcb *tcb;
register struct tcp_header *seg;
struct mbuf **bp;
int16 *length;
{
	struct mbuf *nbp;
	long dupcnt,excess;
	int16 len;		/* Segment length including flags */
	char accept;

	accept = 0;
	len = *length;
	if(seg->flags & SYN)
		len++;
	if(seg->flags & FIN)
		len++;

	/* Acceptability tests */
	if(tcb->rcv.wnd == 0){
		/* Only in-order, zero-length segments are acceptable when our window
		 * is closed.
		 */
		if(seg->seq == tcb->rcv.nxt && len == 0){
			return 0;	/* Acceptable, no trimming needed */
		}
	} else {
		/* Some part of the segment must be in the window */
		if(in_window(tcb,seg->seq)){
			accept++;	/* Beginning is */
		} else if(len != 0){
			if(in_window(tcb,(int32)(seg->seq+len-1)) || /* End is */
			 seq_within(tcb->rcv.nxt,seg->seq,(int32)(seg->seq+len-1))){ /* Straddles */
				accept++;
			}
		}
	}
	if(!accept){
		free_p(*bp);
		return -1;
	}
	dupcnt = tcb->rcv.nxt - seg->seq;
	if(dupcnt > 0){
		/* Trim off SYN if present */
		if(seg->flags & SYN){
			/* SYN is before first data byte */
			seg->flags &= ~SYN;
			seg->seq++;
			dupcnt--;
		}
		if(dupcnt > 0){
			pullup(bp,NULLCHAR,(int16)dupcnt);
			seg->seq += dupcnt;
			*length -= dupcnt;
		}
	}
	excess = seg->seq + *length - (tcb->rcv.nxt + tcb->rcv.wnd);
	if(excess > 0){
		/* Trim right edge */
		*length -= excess;
		nbp = copy_p(*bp,*length);
		free_p(*bp);
		*bp = nbp;
		seg->flags &= ~FIN;	/* FIN follows last data byte */
	}
	return 0;
}
SHAR_EOF
cat << \SHAR_EOF > newtelnetp.c
#include <stdio.h>
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/tasks.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <proto/exec.h>
#include <devices/console.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#include <dos.h>
#include "machdep.h"
#include "mbuf.h"
#include "timer.h"
#include "internet.h"
#include "icmp.h"
#include "netuser.h"
#include "tcp.h"
#include "telnet.h"
#include "session.h"
#include "inetdev.h"
#include "inetlib.h"
#define DEBUG
struct Process *mytask;
APTR	oldwindowptr;
struct IntuitionBase *IntuitionBase;
char banner[80] = "telnet window";
static struct NewWindow nw = {
	0, 0, 640, 200,		/* left, top, (max) width, (max) height */
	0, 1,			/* detail pen, block pen */
	0,			/* IDCMP flags */
	SMART_REFRESH | WINDOWDRAG | WINDOWDEPTH | WINDOWSIZING |
	    SIZEBBOTTOM | ACTIVATE | NOCAREREFRESH,	/* window flags */
	NULL, NULL,		/* gadget, checkmark */
	(UBYTE *)&banner[0],	/* title of window */
	NULL, NULL,		/* screen, bitmap */
	200, 50, -1, -1,	/* sizing limits */
	WBENCHSCREEN,		/* on the workbench */
};
struct Window *win;
struct MsgPort *keyboard, *consinp, *consoutp, *tcpinp, *tcpoutp;
struct IOStdReq consin, consout;
char InputCharacter;
int deviceopened = 0;
struct IOINETReq tnreq, tninreq, tnoutreq;
char recv[512], snd[512], recvbuf[512], tcpinbuf[512];
struct telnet *tn;
#ifdef LATTICE
extern struct { short error; char *msg; } os_errlist[];
extern int _OSERR, os_nerr;
#endif
static
/*
 * wait for something to happen
 */
eihalt()
{
	register struct IntuiMessage *msg;
	static ULONG mask = 0;

	if (mask == 0L)
		mask = 	1L << consinp->mp_SigBit |
			1L << tcpinp->mp_SigBit;

	(void) Wait(mask);
}
clean(why)
	char *why;
{
	int i;

	if (win)
		CloseWindow(win);
	if (consinp)
		DeletePort(consinp);
	if (consoutp)
		DeletePort(consoutp);
	if (tcpinp)
		DeletePort(tcpinp);
	if (tcpoutp)
		DeletePort(tcpoutp);
	if (deviceopened)
		CloseDevice(&tnreq);
	mytask->pr_WindowPtr = oldwindowptr;
	if (why) {
           myoserr(why);
	}
	exit(0);
}
printlist(l)
struct List *l;
{
  printf("head %x tail %x tailpred %x\n", l->lh_Head, l->lh_Tail, 
		l->lh_TailPred);
}
myoserr(why)
char *why;
{
  int i;
		fprintf(stderr, "%s: ", why); 
#ifdef LATTICE
		fprintf(stderr, "%d: ", _OSERR);

		for(i = 0; os_errlist[i].error < os_nerr; i++)
		  if (os_errlist[i].error == _OSERR)
			fprintf(stderr, os_errlist[i].msg);
#endif
		fprintf(stderr, "\r\n");
}
/* Called at startup time to set up console I/O, memory heap */
ioinit()
{
	struct Screen *scr;

	mytask = (struct Process *) FindTask((char *) NULL);
	oldwindowptr = mytask->pr_WindowPtr;
	mytask->pr_WindowPtr = (APTR) -1;	/* disable DOS requestors */

	if ((IntuitionBase = (struct IntuitionBase *)
	   OpenLibrary("intuition.library", 33L)) == NULL)
		clean("No intuition: Version 1.2 of Amiga Systems Software required");
	/*
	 *  Try to determine the size of the workbench screen
	 */
	scr = malloc(sizeof(struct Screen));
	if (scr==NULL)
		clean("Can't alloc screen");

	if (GetScreenData(scr, (ULONG) sizeof(struct Screen),
			  WBENCHSCREEN, NULL) == TRUE) {
		nw.Width = scr->Width;
		nw.Height = scr->Height-20;
		nw.TopEdge = 19;
	} else
		fprintf(stderr, "Can't GetScreenData()\n");

	free((char *)scr);
	if ((win = OpenWindow(&nw)) == NULL)
		clean("Can't open window");
	if ((consinp = CreatePort("telnet:console in", 0L)) == NULL)
		clean("Can't create console port");
	if ((tcpinp = CreatePort("telnet:tcp in", 0L)) == NULL)
		clean("Can't create telnet tcp input port");
	if ((tcpoutp = CreatePort("telnet:tcp out", 0L)) == NULL)
		clean("Can't create telnet tcp output port");

	consin.io_Data = (APTR) win;
	consin.io_Length = sizeof(struct Window);

	_OSERR = OpenDevice("console.device", 0L, &consin, 0L);
	if (_OSERR != 0L){
		printf("opendevice returned %d\n", _OSERR);
		myoserr("could not get console");
		clean("Can't open console device");
	}
	consout = consin;

	consin.io_Message.mn_ReplyPort = consinp;
	consin.io_Length = 512;
	consin.io_Data = (APTR) recvbuf;
	consin.io_Command = CMD_READ;
	SendIO(&consin);
	consout.io_Message.mn_ReplyPort = consoutp;
	consout.io_Command = CMD_WRITE;


}
/* Read characters from the keyboard, translating them to "real" ASCII
 * If none are ready, return the -1 from kbraw()
 */
kbread()
{
	char c;
	int amount;

	if (CheckIO(&consin)) {
		WaitIO(&consin);
		amount = consin.io_Actual;
		if (amount > 512)
		  amount = 511;
		strncpy(recv, recvbuf, amount);
		SendIO(&consin);
		return (amount);
	}

	return -1;		/* nuthin here */
}
extern char nospace[];
int refuse_echo = 0;
int unix_line_mode = 0;    /* if true turn <cr> to <nl> when in line mode */

#ifdef	DEBUG
char *t_options[] = {
	"Transmit Binary",
	"Echo",
	"",
	"Suppress Go Ahead",
	"",
	"Status",
	"Timing Mark"
};
#endif
/* Telnet receiver upcall routine */
void
rcv_char()
{
	tel_input(tn,tninreq.io_Data, tninreq.io_Actual);
	fflush(stdout);
}
brk()
{
  clean("ok i iwll quit\n");
}
/* TCP connection states */
char *tcpstates[] = {
	"Closed",
	"Listen",
	"SYN sent",
	"SYN received",
	"Established",
	"FIN wait 1",
	"FIN wait 2",
	"Close wait",
	"Closing",
	"Last ACK",
	"Time wait"
};
/* TCP segment header flags */
char *tcpflags[] = {
	"FIN",	/* 0x01 */
	"SYN",	/* 0x02 */
	"RST",	/* 0x04 */
	"PSH",	/* 0x08 */
	"ACK",	/* 0x10 */
	"URG"	/* 0x20 */
};

/* TCP closing reasons */
char *reasons[] = {
	"Normal",
	"Reset",
	"Timeout",
	"ICMP"
};
char old = LISTEN;
int done = 0;
char *hostname="";
int hostport=0;
char *bannerfmt = "telnet %10s %4d %10s";
void
showstate(old, new)
	char old, new;
{


	/* Can't add a check for unknown connection here, it would loop
	 * on a close upcall! We're just careful later on.
	 */

	sprintf(banner, bannerfmt, hostname, hostport, tcpstates[new]);
	SetWindowTitles(win, banner, -1);
	switch(new){
	case CLOSE_WAIT:
		done = 1;
		break;
	case CLOSED:	/* court adjourned */
		done = 1;
		break;
	default:
		break;
	}
	fflush(stdout);

}

/* Execute user telnet command */
main(argc,argv)
int argc;
char *argv[];
{
	extern int _OSERR;
	struct InternetBase *InternetBase;
	int send_tel();
        int unix_send_tel();
	struct session *s;
	int amount;
	struct socket lsocket,fsocket;
	ioinit();
	hostname = argv[1];
	old = LISTEN;
	showstate(old, LISTEN);
	tnreq.io_fsocket.address = aton(argv[1]);
	if(argc < 3)
		tnreq.io_fsocket.port = TELNET_PORT;
	else
		tnreq.io_fsocket.port = atoi(argv[2]);
	tnreq.io_Device = NULL;
	tnreq.io_Unit = NULL;
	tnreq.io_Flags = 0;
	tnreq.io_Error = 0;
	InternetBase = (struct InternetBase *) OpenDevice("internet.device",
				(long) INET_UNIT_TCP, &tnreq, 0L);
	if (InternetBase != 0L){
	  printf("it did not open %d\n",InternetBase);
	  clean("i quit");
	}
	hostport = tnreq.io_lsocket.port;
	deviceopened = 1;
	tninreq = tnreq; /* possible lettuce bug  ?*/
	tnoutreq = tnreq;
	tninreq.io_Length = 512;
	tnoutreq.io_Length = 1;
	tninreq.io_Data = tcpinbuf;
	tnoutreq.io_Data = snd;
	tnoutreq.io_Length = 0;
	tninreq.io_Command = CMD_READ;
	tnoutreq.io_Command = CMD_WRITE;
	tninreq.io_Message.mn_ReplyPort = tcpinp;
	tnoutreq.io_Message.mn_ReplyPort = tcpoutp;
	/* Create and initialize a Telnet protocol descriptor */
	if((tn = (struct telnet *)calloc(1,sizeof(struct telnet))) == NULLTN){
		myoserr("calloc faiuled\n");
		goto done;
	}
	tn->session = s;	/* Upward pointer */
	tn->state = TS_DATA;
        SendIO(&tninreq);
	onbreak(&brk);
	InputCharacter = ' ';
	while (! done)
	  {
	    eihalt();
	    if ((amount = kbread()) >= 0){
	      
	      unix_send_tel(recv, amount);}

	    if (CheckIO(&tninreq))
	      {
		chkabort();
		WaitIO(&tninreq);
	        rcv_char();
	        if (tninreq.io_State != old)
	          {
		    showstate(old, tninreq.io_State);
		    old = tninreq.io_State;
	          }
    	        SendIO(&tninreq);
	      }
	  }
done:
	clean("All done");	      

}

/* Process typed characters */
int
unix_send_tel(buf,n)
char *buf;
int16 n;
{
	int i;
	for (i=0; (i<n) && (buf[i] != '\r'); i++)
		;
	if (buf[i] == '\r') {
		buf[i] = '\n';
		n = i+1;
	}
	send_tel(buf,n);
}
int
send_tel(buf,n)
char *buf;
int16 n;
{
	int i;

	tnoutreq.io_Data = buf;
	tnoutreq.io_Length = n;

	SendIO(&tnoutreq);
	i = WaitIO(&tnoutreq); 
	if (tnoutreq.io_State != old)
	  {
	    showstate(old, tnoutreq.io_State);
	    old = tnoutreq.io_State;
	  }

}

/* Process incoming TELNET characters */
int
tel_input(tn,bp, len)
register struct telnet *tn;
char *bp;
int len;
{
	char c;
	int ci;
	void doopt(),dontopt(),willopt(),wontopt(),answer();
#ifdef	FAST	/* DON'T USE -- Aztec memchr() routine is broken */
	char *memchr();

	/* Optimization for very common special case -- no command chars */
	if(tn->state == TS_DATA){
		while(bp != NULLBUF && memchr(bp->data,IAC,bp->cnt) 
			== NULLCHAR){
			fflush(stdout);
			write(1,bp->data,bp->cnt);
			bp = free_mbuf(bp);
		}
	}
#endif
	while(len--){
		c = *bp++;
		ci = c & 0xff;
		switch(tn->state){
		case TS_DATA:
			if(ci == IAC){
				tn->state = TS_IAC;
			} else {
				if(!tn->remote[TN_TRANSMIT_BINARY])
					c &= 0x7f;
				putchar(c);
			}
			break;
		case TS_IAC:
			switch(ci){
			case WILL:
				tn->state = TS_WILL;
				break;
			case WONT:
				tn->state = TS_WONT;
				break;
			case DO:
				tn->state = TS_DO;
				break;
			case DONT:
				tn->state = TS_DONT;
				break;
			case IAC:
				putchar(c);
				tn->state = TS_DATA;
				break;
			default:
				tn->state = TS_DATA;
				break;
			}
			break;
		case TS_WILL:
			willopt(tn,ci);
			tn->state = TS_DATA;
			break;
		case TS_WONT:
			wontopt(tn,ci);
			tn->state = TS_DATA;
			break;
		case TS_DO:
			doopt(tn,ci);
			tn->state = TS_DATA;
			break;
		case TS_DONT:
			dontopt(tn,ci);
			tn->state = TS_DATA;
			break;
		}
	}
}

#ifdef NOTDEF
/* State change upcall routine */
void
t_state(tcb,old,new)
register struct tcb *tcb;
char old,new;
{
	struct telnet *tn;
	char notify = 0;
	extern char *tcpstates[];
	extern char *reasons[];
	extern char *unreach[];
	extern char *exceed[];

	/* Can't add a check for unknown connection here, it would loop
	 * on a close upcall! We're just careful later on.
	 */
	tn = (struct telnet *)tcb->user;

	if(current != NULLSESSION && current->type == TELNET && current->cb.telnet == tn)
		notify = 1;

	switch(new){
	case CLOSE_WAIT:
		if(notify)
			printf("%s\r\n",tcpstates[new]);
		close_tcp(tcb);
		break;
	case CLOSED:	/* court adjourned */
		if(notify){
			printf("%s (%s",tcpstates[new],reasons[tcb->reason]);
			if(tcb->reason == NETWORK){
				switch(tcb->type){
				case DEST_UNREACH:
					printf(": %s unreachable",unreach[tcb->code]);
					break;
				case TIME_EXCEED:
					printf(": %s time exceeded",exceed[tcb->code]);
					break;
				}
			}
			printf(")\r\n");
			cmdmode();
		}
		del_tcp(tcb);
		if(tn != NULLTN)
			free_telnet(tn);
		break;
	default:
		if(notify)
			printf("%s\r\n",tcpstates[new]);
		break;
	}
	fflush(stdout);
}
#endif
/* Delete telnet structure */
static
free_telnet(tn)
struct telnet *tn;
{

	if(tn != NULLTN)
		free((char *)tn);
}

/* The guts of the actual Telnet protocol: negotiating options */
static
void
willopt(tn,opt)
struct telnet *tn;
int opt;
{
	int ack;
	void answer();

#ifdef	DEBUG
	printf("recv: will ");
	if(opt <= NOPTIONS)
		printf("%s\r\n",t_options[opt]);
	else
		printf("%u\r\n",opt);
#endif
	
	switch(opt){
	case TN_TRANSMIT_BINARY:
	case TN_ECHO:
	case TN_SUPPRESS_GA:
		if(tn->remote[opt] == 1)
			return;		/* Already set, ignore to prevent loop */
		if(opt == TN_ECHO){
			if(refuse_echo){
				/* User doesn't want to accept */
				ack = DONT;
				break;
			} else
				raw();		/* Put tty into raw mode */
		}
		tn->remote[opt] = 1;
		ack = DO;			
		break;
	default:
		ack = DONT;	/* We don't know what he's offering; refuse */
	}
	answer(tn,ack,opt);
}
static
void
wontopt(tn,opt)
struct telnet *tn;
int opt;
{
	void answer();

#ifdef	DEBUG
	printf("recv: wont ");
	if(opt <= NOPTIONS)
		printf("%s\r\n",t_options[opt]);
	else
		printf("%u\r\n",opt);
#endif
	if(opt <= NOPTIONS){
		if(tn->remote[opt] == 0)
			return;		/* Already clear, ignore to prevent loop */
		tn->remote[opt] = 0;
		if(opt == TN_ECHO)
			cooked();	/* Put tty into cooked mode */
	}
	answer(tn,DONT,opt);	/* Must always accept */
}
static
void
doopt(tn,opt)
struct telnet *tn;
int opt;
{
	void answer();
	int ack;

#ifdef	DEBUG
	printf("recv: do ");
	if(opt <= NOPTIONS)
		printf("%s\r\n",t_options[opt]);
	else
		printf("%u\r\n",opt);
#endif
	switch(opt){
#ifdef	FUTURE	/* Use when local options are implemented */
		if(tn->local[opt] == 1)
			return;		/* Already set, ignore to prevent loop */
		tn->local[opt] = 1;
		ack = WILL;
		break;
#endif
	default:
		ack = WONT;	/* Don't know what it is */
	}
	answer(tn,ack,opt);
}
static
void
dontopt(tn,opt)
struct telnet *tn;
int opt;
{
	void answer();

#ifdef	DEBUG
	printf("recv: dont ");
	if(opt <= NOPTIONS)
		printf("%s\r\n",t_options[opt]);
	else
		printf("%u\r\n",opt);
#endif
	if(opt <= NOPTIONS){
		if(tn->local[opt] == 0){
			/* Already clear, ignore to prevent loop */
			return;
		}
		tn->local[opt] = 0;
	}
	answer(tn,WONT,opt);
}
static
void
answer(tn,r1,r2)
struct telnet *tn;
int r1,r2;
{
	struct mbuf *bp,*qdata();
	char s[3];

#ifdef	DEBUG
	switch(r1){
	case WILL:
		printf("sent: will ");
		break;
	case WONT:
		printf("sent: wont ");
		break;
	case DO:
		printf("sent: do ");
		break;
	case DONT:
		printf("sent: dont ");
		break;
	}
	if(r2 <= 6)
		printf("%s\r\n",t_options[r2]);
	else
		printf("%u\r\n",r2);
#endif

	s[0] = IAC;
	s[1] = r1;
	s[2] = r2;
	tnoutreq.io_Data = s;
	tnoutreq.io_Length = 3;
	DoIO(&tnoutreq);
/*
	bp = qdata(s,(int16)3);
	send_tcp(tn->tcb,bp);
*/
}

#define	BUFMAXCNT	150
static char conbuf[BUFMAXCNT];
static int concnt = 0;

int
amigaputchar(c)
	char c;
{
	conbuf[concnt++] = c;
	if ((c == '\n') || (concnt == BUFMAXCNT))
		amigaflush();
	return c;
}

amigaflush()
{
	if (concnt == 0)
		return;
	consout.io_Data = (APTR) conbuf;
	consout.io_Length = concnt;
	consout.io_Command = CMD_WRITE;
	DoIO(&consout);
	concnt = 0;
}
	
/*
 *  Begin terrible, horrible hack.  All output should be printed upon (into?)
 *  the window we opened before.  Here goes nothing...
 */

printf(a, b, c, d, e, f, g, h, i, j, k)
	char *a;
	int b, c, d, e, f, g, h, i, j, k;
{
	if (concnt)
		amigaflush();

	sprintf(conbuf, a, b, c, d, e, f, g, h, i, j, k);
	consout.io_Data = (APTR) conbuf;
	consout.io_Length = strlen(conbuf);
	consout.io_Command = CMD_WRITE;
	DoIO(&consout);		/* no use in doing this async */
}
SHAR_EOF
#	End of shell archive
exit 0
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.