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

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

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

#	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:
#	arp.c
#	ax25.c
#	ftpcli.c
#	smtpserv.c
# This archive created: Fri Mar 17 18:01:19 1989
cat << \SHAR_EOF > arp.c
/* Address Resolution Protocol (ARP) functions. Sits between IP and
 * Level 2, mapping IP to Level 2 addresses for all outgoing datagrams.
 */
#include "machdep.h"
#include "mbuf.h"
#include "timer.h"
#include "iface.h"
#include "ether.h"
#include "ax25.h"
#include "arp.h"
#include "cmdparse.h"

extern int32 ip_addr;		/* Our IP address */

int ec_output();
int pether(),gether();

int setcall(),psax25();

/* Table of ARP hardware types */
struct arp_type arp_type[] = {
	0,
	0,
	0,
	NULLCHAR,
	NULLFP,
	NULLFP,

	/* 10 megabit Ethernet */
	6,			/* Ethernet address length */
	0x800,			/* Ethernet type field for IP */
	0x806,			/* Ethernet type field for ARP */
	ether_bdcst,		/* Ethernet broadcast address */
	pether,
	gether,
	
	/* 3 megabit Ethernet */
	0,
	0,
	0,
	NULLCHAR,
	NULLFP,
	NULLFP,

	/* AX.25 */
	7,			/* AX.25 address length */
	0xCC,			/* AX.25 pid field for IP */
	0xCD,			/* AX.25 pid field for ARP */
	(char *)&ax25_bdcst,	/* AX.25 broadcast address */
	psax25,
	setcall,
};
#define	NTYPES	4

/* Hash table headers */
struct arp_tab *arp_tab[ARPSIZE];

struct arp_stat arp_stat;

/* Resolve an IP address to a hardware address; if not found,
 * initiate query and return NULLCHAR.  If an address is returned, the
 * interface driver may send the packet; if NULLCHAR is returned,
 * res_arp() will have saved the packet on its pending queue,
 * so no further action (like freeing the packet) is necessary.
 */
char *
res_arp(interface,hardware,target,bp)
struct interface *interface;	/* Pointer to interface block */
int16 hardware;		/* Hardware type */
int32 target;		/* Target IP address */
struct mbuf *bp;	/* IP datagram to be queued if unresolved */
{
	struct arp_tab *arp_lookup(),*arp_add();
	void arp_output();
	register struct arp_tab *arp;

	if((arp = arp_lookup(hardware,target)) != NULLARP && arp->state == ARP_VALID)
		return arp->hw_addr;
	/* Create an entry and put the datagram on the
	 * queue pending an answer
	 */
	arp = arp_add(target,hardware,NULLCHAR,0);
	enqueue(&arp->pending,bp);
	arp_output(interface,hardware,target);
	return NULLCHAR;
}
/* Handle incoming ARP packets. This is almost a direct implementation of
 * the algorithm on page 5 of RFC 826, except for:
 * 1. Outgoing datagrams to unresolved addresses are kept on a queue
 *    pending a reply to our ARP request.
 * 2. The names of the fields in the ARP packet were made more mnemonic.
 */
void
arp_input(interface,bp)
struct interface *interface;
struct mbuf *bp;
{
	struct arp arp;
	struct arp_tab *arp_lookup(),*ap;
	struct arp_type *at;
	struct mbuf *htonarp();
	
	arp_stat.recv++;
	if(ntoharp(&arp,bp) == -1)	/* Convert into host format */
		return;
	if(arp.hardware >= NTYPES){
		/* Unknown hardware type, ignore */
		arp_stat.badtype++;
		return;
	}
	at = &arp_type[arp.hardware];
	if(arp.protocol != at->iptype){
		/* Unsupported protocol type, ignore */
		arp_stat.badtype++;
		return;
	}
	if(arp.hwalen > MAXHWALEN || arp.pralen != sizeof(int32)){
		/* Incorrect protocol addr length (different hw addr lengths
		 * are OK since AX.25 addresses can be of variable length)
		 */
	arp_stat.badlen++;
		return;
	}
	/* If this guy is already in the table, update its entry
	 * unless it's a manual entry (noted by the lack of a timer)
	 */
	ap = NULLARP;	/* ap plays the role of merge_flag in the spec */
	if((ap = arp_lookup(arp.hardware,arp.sprotaddr)) != NULLARP
	 && ap->timer.start != 0){
		ap = arp_add(arp.sprotaddr,arp.hardware,arp.shwaddr,arp.hwalen & 0xff);
	}
	/* See if we're the address they're looking for */
	if(arp.tprotaddr == ip_addr){
		if(ap == NULLARP)	/* Only if not already in the table */
			arp_add(arp.sprotaddr,arp.hardware,arp.shwaddr,arp.hwalen & 0xff);

		if(arp.opcode == ARP_REQUEST){
			/* Swap sender's and target's (us) hardware and protocol
			 * fields, and send the packet back as a reply
			 */
			bcopy(arp.shwaddr,arp.thwaddr,arp.hwalen);
			/* Mark the end of the sender's AX.25 address
			 * in case he didn't
			 */
			if(arp.hardware == ARP_AX25)
				arp.thwaddr[arp.hwalen-1] |= E;

			bcopy(interface->hwaddr,arp.shwaddr,at->hwalen);
			arp.tprotaddr = arp.sprotaddr;
			arp.sprotaddr = ip_addr;
			arp.opcode = ARP_REPLY;
			bp = htonarp(&arp);
			(*interface->output)(interface,arp.thwaddr,
				interface->hwaddr,at->arptype,bp);
			arp_stat.inreq++;
		} else {
			arp_stat.replies++;
		}
	}
}
/* Add an IP-addr / hardware-addr pair to the ARP table */
static
struct arp_tab *
arp_add(ip_addr,hardware,hw_addr,hw_alen)
int32 ip_addr;	/* IP address, host order */
int16 hardware;	/* Hardware type */
char *hw_addr;	/* Hardware address, if known; NULLCHAR otherwise */
int16 hw_alen;	/* Length of hardware address */
{
	char *calloc(),*malloc();
	struct arp_tab *arp_lookup();
	void arp_drop();
	struct mbuf *bp,*dequeue();
	register struct arp_tab *ap;
	register struct arp_type *at;
	unsigned hashval,arp_hash();

	at = &arp_type[hardware];
	if((ap = arp_lookup(hardware,ip_addr)) == NULLARP){
		/* New entry */
		if((ap = (struct arp_tab *)calloc(1,sizeof(struct arp_tab))) == NULLARP)
			return NULLARP;
		ap->timer.func = arp_drop;
		ap->timer.arg = (int *)ap;
		ap->hardware = hardware;
		ap->ip_addr = ip_addr;

		/* Put on head of hash chain */
		hashval = arp_hash(hardware,ip_addr);
		ap->prev = NULLARP;
		ap->next = arp_tab[hashval];
		arp_tab[hashval] = ap;
		if(ap->next != NULLARP){
			ap->next->prev = ap;
		}
	}
	if(hw_addr == NULLCHAR){
		/* Await response */
		ap->state = ARP_PENDING;
		ap->timer.start = PENDTIME;
	} else {
		/* Response has come in, update entry and run through queue */
		ap->state = ARP_VALID;
		ap->timer.start = ARPLIFE;
		if(ap->hw_addr != NULLCHAR)
			free(ap->hw_addr);
		if((ap->hw_addr = malloc(hw_alen)) == NULLCHAR){
			free((char *)ap);
			return NULLARP;
		}
		bcopy(hw_addr,ap->hw_addr,hw_alen);
		/* This kludge marks the end of an AX.25 address to allow
		 * for optional digipeaters (insert Joan Rivers salute here)
		 */
		if(hardware == ARP_AX25)
			ap->hw_addr[hw_alen-1] |= E;
		while((bp = dequeue(&ap->pending)) != NULLBUF)
			ip_route(bp,0);
	}
	start_timer(&ap->timer);
	return ap;
}

/* Remove an entry from the ARP table */
static
void
arp_drop(ap)
register struct arp_tab *ap;
{
	unsigned arp_hash();

	if(ap == NULLARP)
		return;
	stop_timer(&ap->timer);	/* Shouldn't be necessary */
	if(ap->next != NULLARP)
		ap->next->prev = ap->prev;
	if(ap->prev != NULLARP)
		ap->prev->next = ap->next;
	else
		arp_tab[arp_hash(ap->hardware,ap->ip_addr)] = ap->next;
	if(ap->hw_addr != NULLCHAR)
		free(ap->hw_addr);
	free_q(&ap->pending);
	free((char *)ap);
}

/* Look up the given IP address in the ARP table */
static
struct arp_tab *
arp_lookup(hardware,ip_addr)
int16 hardware;
int32 ip_addr;
{
	unsigned arp_hash();
	register struct arp_tab *ap;

	for(ap = arp_tab[arp_hash(hardware,ip_addr)]; ap != NULLARP; ap = ap->next){
		if(ap->ip_addr == ip_addr && ap->hardware == hardware)
			break;
	}
	return ap;
}
/* Send an ARP request to resolve IP address target_ip */
static
void
arp_output(interface,hardware,target)
struct interface *interface;
int16 hardware;
int32 target;
{
	struct arp arp;
	struct mbuf *bp,*htonarp();
	struct arp_type *at;

	at = &arp_type[hardware];
	if(interface->output == NULLFP)
		return;
	
	arp.hardware = hardware;
	arp.protocol = at->iptype;
	arp.hwalen = at->hwalen;
	arp.pralen = sizeof(int32);
	arp.opcode = ARP_REQUEST;
	bcopy(interface->hwaddr,arp.shwaddr,at->hwalen);
	arp.sprotaddr = ip_addr;
	bzero(arp.thwaddr,at->hwalen);
	arp.tprotaddr = target;
	bp = htonarp(&arp);
	(*interface->output)(interface,at->bdcst,
		interface->hwaddr,at->arptype,bp);
	arp_stat.outreq++;
}

/* Hash a {hardware type, IP address} pair */
static
unsigned
arp_hash(hardware,ip_addr)
int16 hardware;
int32 ip_addr;
{
	register unsigned hashval;

	hashval = hardware;
	hashval ^= hiword(ip_addr);
	hashval ^= loword(ip_addr);
	hashval %= ARPSIZE;
	return hashval;
}		
/* Copy a host format arp structure into mbuf for transmission */
#ifdef	AMIGA
/*
 *  We play some dirty tricks here.  Since the AMIGA is a 68000 based
 *  machine, it doesn't take kindly to doing word and long word stores 
 *  on odd address boundaries.  We'll use bcopy() instead.  We can do
 *  this simply because the 68000 is a big-endian machine, and we don't
 *  need to convert to network byte order.  This is ugly.
 */
#endif
static
struct mbuf *
htonarp(arp)
register struct arp *arp;
{
	struct mbuf *bp;
	register char *buf;

	if(arp == (struct arp *)NULL)
		return NULLBUF;
	if((bp = alloc_mbuf(sizeof(struct arp))) == NULLBUF)
		return NULLBUF;

	buf = bp->data;

	*(int16 *)buf = htons(arp->hardware);
	buf += sizeof(int16);

	*(int16 *)buf = htons(arp->protocol);
	buf += sizeof(int16);

	*buf++ = arp->hwalen;

	*buf++ = arp->pralen;

	*(int16 *)buf = htons(arp->opcode);
	buf += sizeof(int16);

	bcopy(arp->shwaddr,buf,arp->hwalen);
	buf += arp->hwalen;

#ifndef	AMIGA
	*(int32 *)buf = htonl(arp->sprotaddr);
#else
	/* we've been alright up to now, but arp->hwalen may have been
	   odd, so we don't know if buf is word aligned any more! */
	bcopy(&arp->sprotaddr, buf, sizeof(int32));
#endif
	buf += sizeof(int32);

	bcopy(arp->thwaddr,buf,arp->hwalen);
	buf += arp->hwalen;

#ifndef	AMIGA
	*(int32 *)buf = htonl(arp->tprotaddr);
#else
	bcopy(&arp->tprotaddr, buf, sizeof(int32));
#endif
	buf += sizeof(int32);

	bp->cnt = buf - bp->data;
	return bp;
}
/* Convert an incoming ARP packet into a host-format structure */
static
int
ntoharp(arp,bp)
register struct arp *arp;
struct mbuf *bp;
{
	if(arp == (struct arp *)NULL || bp == NULLBUF)
		return -1;

	pullup(&bp,(char *)&arp->hardware,sizeof(int16));
	arp->hardware = ntohs(arp->hardware);

	pullup(&bp,(char *)&arp->protocol,sizeof(int16));
	arp->protocol = ntohs(arp->protocol);

	pullup(&bp,(char *)&arp->hwalen,sizeof(char));

	pullup(&bp,(char *)&arp->pralen,sizeof(char));

	pullup(&bp,(char *)&arp->opcode,sizeof(int16));
	arp->opcode = ntohs(arp->opcode);

	pullup(&bp,arp->shwaddr,arp->hwalen);

	pullup(&bp,(char *)&arp->sprotaddr,sizeof(int32));
	arp->sprotaddr = ntohl(arp->sprotaddr);

	pullup(&bp,arp->thwaddr,arp->hwalen);

	pullup(&bp,(char *)&arp->tprotaddr,sizeof(int32));
	arp->tprotaddr = ntohl(arp->tprotaddr);

	free_p(bp);
	return 0;
}
#ifdef	TRACE
char *arptypes[] = {
	NULLCHAR,
	"Ethernet",
	"Exp Ethernet",
	"AX.25",
	"Pronet",
	"Chaos"
};
int doarpadd(),doarpdrop();
struct cmds arpcmds[] = {
	"add", doarpadd, 4,
	"usage: arp add <ip addr> ether|ax25 <callsign|ether addr>",
	"arp add failed",

	"drop", doarpdrop, 3,
	"usage: arp drop <ip addr> ether|ax25",
	"not in table",

	NULLCHAR, NULLFP, 0,
	"arp subcommands: add, drop",
	NULLCHAR, 
};
int
doarp(argc,argv)
int argc;
char *argv[];
{
	if(argc < 2){
		dumparp();
		return 0;
	}
	return subcmd(arpcmds,argc,argv);
}
static
doarpadd(argc,argv)
int argc;
char *argv[];
{
	int hardware,hwalen,i;
	int32 addr,aton();
	char *malloc(),*hwaddr;
	int naddr;
	struct arp_tab *ap;
	struct arp_type *at;
	struct ax25_addr *axp;

	addr = aton(argv[1]);
	/* This is a kludge. It really ought to be table driven */
	switch(tolower(argv[2][0])){
	case 'e':	/* "ether" */
		hardware = ARP_ETHER;
		naddr = 1;
		break;		
	case 'a':	/* "ax25" */
		hardware = ARP_AX25;
		naddr = argc - 3;
		break;
	default:
		printf("unknown hardware type \"%s\"\r\n",argv[2]);
		return -1;
	}
	/* If an entry already exists, clear it */
	if((ap = arp_lookup(hardware,addr)) != NULLARP)
		arp_drop(ap);

	at = &arp_type[hardware];

	/* Allocate buffer for hardware address and fill with remaining args */
	hwalen = at->hwalen * naddr;
	if((hwaddr = malloc(hwalen)) == NULLCHAR){
		printf("No space\r\n");
		return 0;
	}
	(*at->scan)(hwaddr,argv[3]);	/* Destination address */

	/* Special hackery to handle a series of AX.25 digipeaters */
	if(hardware == ARP_AX25){
		axp = (struct ax25_addr *)hwaddr;
		for(i=1;i<naddr;i++){
			/* Set E bit only on last AX.25 call */
			axp->ssid &= ~E;
			/* axp++; */
			axp = (struct ax25_addr *) ((char *)axp + AXALEN);
			(*at->scan)((char *)axp,argv[3+i]);
		}
	}
	ap = arp_add(addr,hardware,hwaddr,hwalen);	/* Put in table */
	free(hwaddr);					/* Clean up */
	stop_timer(&ap->timer);			/* Make entry permanent */
	ap->timer.count = ap->timer.start = 0;
}
/* Remove an ARP entry */
static
doarpdrop(argc,argv)
int argc;
char *argv[];
{
	int hardware;
	int32 addr,aton();
	struct arp_tab *ap;

	addr = aton(argv[1]);
	/* This is a kludge. It really ought to be table driven */
	switch(tolower(argv[2][0])){
	case 'e':	/* "ether" */
		hardware = ARP_ETHER;
		break;		
	case 'a':	/* "ax25" */
		hardware = ARP_AX25;
		break;
	default:
		hardware = 0;
		break;
	}
	if((ap = arp_lookup(hardware,addr)) == NULLARP)
		return -1;
	arp_drop(ap);
	return 0;	
}
/* Dump ARP table */
static
dumparp()
{
	register int i;
	extern struct arp_stat arp_stat;
	register struct arp_tab *ap;
	char e[128];
	char *inet_ntoa();

	printf("received %u badtype %u reqst in %u replies %u reqst out %u\r\n",
	 arp_stat.recv,arp_stat.badtype,arp_stat.inreq,
	 arp_stat.replies,arp_stat.outreq);

	printf("IP addr         Type     Time Q Addr\r\n");
	for(i=0;i<ARPSIZE;i++){
		for(ap = arp_tab[i];ap != (struct arp_tab *)NULL;ap = ap->next){
			printf("%-16s",inet_ntoa(ap->ip_addr));
			printf("%-9s",arptypes[ap->hardware]);
			printf("%-5ld",ap->timer.count*(long)MSPTICK/1000);
			if(ap->state == ARP_PENDING)
				printf("%-2u",len_q(ap->pending));
			else
				printf("  ");
			if(ap->state == ARP_VALID){
				if(arp_type[ap->hardware].format != NULLFP){
					(*arp_type[ap->hardware].format)(e,ap->hw_addr);
				} else {
					e[0] = '\0';
				}
				printf("%s",e);
			} else {
				printf("[unknown]");
			}
			printf("\r\n");
		}
	}
	return 0;
}
/* Dump ARP packets (incomplete) */
static char *hwtypes[] = {
	"",
	"10 Mb Ethernet",
	"3 Mb Ethernet",
	"AX.25",
	NULLCHAR,
};
#define	NHWTYPES 4
arp_dump(bp)
struct mbuf *bp;
{
	struct arp arp;
	struct mbuf *tbp;
	char *inet_ntoa();

	if(bp == NULLBUF)
		return;
	/* Make temporary copy  */
	dup_p(&tbp,bp,0,len_mbuf(bp));
	ntoharp(&arp,tbp);

	if(arp.hardware < NHWTYPES)
		printf("ARP: hwtype %s",hwtypes[arp.hardware]);
	else
		printf("ARP: hwtype %u",arp.hardware);
	printf(" prot 0x%x hwlen %u prlen %u",
		arp.protocol,arp.hwalen,arp.pralen);
	switch(arp.opcode){
	case ARP_REQUEST:
		printf(" op REQUEST");
		break;
	case ARP_REPLY:
		printf(" op REPLY");
		break;
	default:
		printf(" op %u",arp.opcode);
		break;
	}
	printf(" target %s\r\n",inet_ntoa(arp.tprotaddr));
}
#endif
SHAR_EOF
cat << \SHAR_EOF > ax25.c
/* Vestigial AX.25 link layer, understands only UI frames */

#include <stdio.h>
#include "machdep.h"
#include "mbuf.h"
#include "iface.h"
#include "timer.h"
#include "arp.h"
#include "slip.h"
#include "ax25.h"
#include <ctype.h>

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

/* AX.25 broadcast address: "QST-0" in shifted ascii */
struct ax25_addr ax25_bdcst = {
	'Q'<<1, 'S'<<1, 'T'<<1, ' '<<1, ' '<<1, ' '<<1,
	('0'<<1) | E,
};
struct ax25_addr mycall;
int digipeat;		/* Controls digipeating */

/* Send IP datagrams in AX.25 UI frames using ARP */
int
ax_send(bp,interface,gateway,precedence,delay,throughput,reliability)
struct mbuf *bp;
struct interface *interface;
int32 gateway;
char precedence;
char delay;
char throughput;
char reliability;
{
	char *hw_addr,*res_arp();

	hw_addr = res_arp(interface,ARP_AX25,gateway,bp);
	if(hw_addr != NULLCHAR)
		(*interface->output)(interface,(struct ax25_addr *)hw_addr,
			interface->hwaddr,PID_IP,bp);
}
/* Send a raw packet to the KISS TNC using AX.25 link header.
 * Note that the calling order here must match ec_output
 * since ARP also uses it.
 */
kiss_output(interface,dest,src,pid,bp)
struct interface *interface;
struct ax25_addr *dest;	/* Destination AX.25 address (7 bytes, shifted) */
struct ax25_addr *src;	/* Source AX.25 address (7 bytes, shifted) */
char pid;		/* Protocol ID */
struct mbuf *bp;	/* Data field (follows PID) */
{
	register struct mbuf *hbp;
	struct mbuf *ax_encode();
	struct slip *sp;

	if((bp = ax_encode(dest,src,pid,bp)) == NULLBUF)
		return;
#ifdef	TRACE
	if(trace & TRACE_AX25){
		printf("%s sent:\r\n",interface->name);
		if((trace & TRACE_HDR) > 1)
			ax25_dump(bp);
		if(trace & TRACE_DUMP)
			hexdump(bp);
		if(trace & TRACE_ASCII)
			asciidump(bp);
		fflush(stdout);
	}
#endif
	/* Put type field for KISS TNC on front */
	if((hbp = alloc_mbuf(1)) == NULLBUF){
		free_p(bp);
		return;
	}
	*hbp->data = 0;
	hbp->cnt = 1;
	hbp->next = bp;
	slipq(interface->dev,hbp);
}

/* Put a AX.25 header on the front of a packet */
struct mbuf *
ax_encode(dest,src,pid,bp)
struct ax25_addr *dest;	/* Destination AX.25 address (7 bytes, shifted) */
struct ax25_addr *src;	/* Source AX.25 address (7 bytes, shifted) */
char pid;		/* Protocol ID */
struct mbuf *bp;	/* Data field (follows PID) */
{
	register struct ax25_addr *ap;
	struct mbuf *abp;
	char *cp;
	int ndigi;
	int16 hdr_len;

	/* Determine length of dest addr */
	for(ndigi = 0,ap = dest; (ap->ssid & E) == 0;
			ap = (struct ax25_addr *) ((char *)ap + AXALEN))
		ndigi++;

	/* Compute header length:
	 * 2 AX.25 address fields for source and dest +
	 * "ndigi" AX.25 address field(s) for digipeaters +
	 * 2 bytes for control and PID fields
	 */

	hdr_len = (2 + ndigi)*AXALEN + 2;

	/* Create AX.25 link level header */
	if((abp = alloc_mbuf(hdr_len)) == NULLBUF){
		free_p(bp);
		return NULLBUF;
	}
	abp->cnt = hdr_len;

	/* Now fill it in */

	ap = (struct ax25_addr *)(abp->data);

	bcopy((char *)dest,(char *)ap,AXALEN);
	ap->ssid &= ~E;
	ap = (struct ax25_addr *) ((char *)ap + AXALEN);
	dest = (struct ax25_addr *) ((char *)dest + AXALEN);

	bcopy((char *)src,(char *)ap,AXALEN);
	ap->ssid &= ~E;
	cp = (char *)ap;	/* get pointer to last address (source) */
	ap = (struct ax25_addr *) ((char *)ap + AXALEN);

	while(ndigi-- != 0) {
		bcopy((char *)dest,(char *)ap,AXALEN);
		dest = (struct ax25_addr *) ((char *)dest + AXALEN);
		cp = (char *)ap;
		ap = (struct ax25_addr *) ((char *)ap + AXALEN);
	}
	((struct ax25_addr *)cp)->ssid |= E;	/* Mark end of address field */	

	cp = (char *)ap;	/* Point to first byte past address field */
	*cp++ = UI;
	*cp++ = pid;

	abp->next = bp;		/* Link in data field */
	return abp;
}
/* Process incoming KISS TNC frame */
kiss_recv(interface,bp)
struct interface *interface;
struct mbuf *bp;
{
	pullup(&bp,NULLCHAR,1);	/* remove KISS TNC type field */
	if(bp != NULLBUF)
		ax_recv(interface,bp);
}
/* Process incoming AX.25 packets.
 * After optional tracing, the address field is examined. If it is
 * directed to us as a digipeater, repeat it.  If it is addressed to
 * us or to QST-0, kick it upstairs depending on the protocol ID.
 */
int
ax_recv(interface,bp)
struct interface *interface;
struct mbuf *bp;
{
	void arp_input(),ip_route();
	struct ax25_addr *ap,*ap1;
	char pid,multicast,ours,*control,*cbyte();
	int addrsize;
	struct mbuf *hbp;

#ifdef	TRACE
	if(trace & TRACE_AX25){
		printf("%s recv:\r\n",interface->name);
		if((trace & TRACE_HDR) > 1)
			ax25_dump(bp);
		if(trace & TRACE_DUMP)
			hexdump(bp);
		if(trace & TRACE_ASCII)
			asciidump(bp);
		fflush(stdout);
	}
#endif
	control = cbyte(bp);		/* control -> control byte */

	ap = (struct ax25_addr *)bp->data;	/* -> address field */
	addrsize = control - (char *)ap;	/* # bytes in address field */
	/* Check for either a missing control byte or a residual length
	 * address field
	 */
	if(control == NULL || addrsize % AXALEN != 0){
		free_p(bp);
		return;
	}
	addrsize /= AXALEN; /* # addresses in address field */
	/* Check for invalid address field (too short or odd length) */	
	if(addrsize < 2) {
		free_p(bp);
		return;
	}
	/* Rescan, looking for our call in the repeater fields, if any.
	 * Repeat appropriate packets.
	 */
	/* for(ap1 = &ap[2]; ap1 < &ap[addrsize]; ap1++){ */

	for(ap1 = (struct ax25_addr *) ((char *)ap + 2*AXALEN);
	    ap1 < (struct ax25_addr *) ((char *)ap + addrsize*AXALEN);
	    ap1 = (struct ax25_addr *) ((char *)ap1 + AXALEN)) {

		if((ap1->ssid & REPEATED) == 0){
			/* Check if packet is directed to us as a digipeater */
			if(digipeat && addreq(ap1,&mycall)){
				/* Yes, kick it back out */
				ap1->ssid |= REPEATED;
				/* Put type field for KISS TNC on front */
				if((hbp = alloc_mbuf(1)) == NULLBUF){
					free_p(bp);
					return;
				}
				*hbp->data = 0;
				hbp->cnt = 1;
				hbp->next = bp;
				slipq(interface->dev,hbp);
			} else {
				/* Addressed to some other digipeater */
				free_p(bp);
			}
			return;
		}
	}
	/* Packet has passed all repeaters, now look at destination */
	ours = 0;
	if(addreq(&ap[0],&ax25_bdcst)){
		multicast = 1;	/* Broadcast packet */
	} else if(addreq(&ap[0],&mycall)){
		multicast = 0;	/* Packet directed at us */
		ours = 1;
#if	0
	/* we really do want to see all of the packets, later on */
	} else {
		/* Not for us */
		free_p(bp);
		return;
#endif
	}
	/* Now remove the header and the control field. Note: This will
	 * have to be changed if the connected mode of AX.25 (ugh!) is ever
	 * implemented
	 */
	pullup(&bp,NULLCHAR,1 + addrsize * AXALEN);

	/* Examine the protocol ID field and switch to the right protocol */
	if(pullup(&bp,&pid,1) != 1)
		return;	/* No PID, probably not an I-frame */
	switch(pid & 0xff){
	case PID_ARP:
		arp_input(interface,bp);
		break;
	case PID_IP:
		if (!(ours || multicast)) {
			free_p(bp);
			break;
		}
		ip_route(bp,multicast);
		break;
#ifdef	NETROM
	case PID_NETROM:
		netrom_input(interface, bp);
		break;
#endif
	default:
		free_p(bp);
		break;
	}
}
/* Display or change our AX.25 address */
domycall(argc,argv)
int argc;
char *argv[];
{
	char buf[15];

	if(argc < 2){
		pax25(buf,&mycall);
		printf("%s\r\n",buf);
		return 0;
	}
	if(setcall(&mycall,argv[1]) == -1)
		return -1;
	mycall.ssid |= E;
}
/*
 * setcall - convert callsign plus substation ID of the form
 * "KA9Q-0" to AX.25 (shifted) address format
 *   Address extension bit is left clear
 *   Return -1 on error, 0 if OK
 */
int
setcall(out,call)
struct ax25_addr *out;
char *call;
{
	int csize;
	unsigned ssid;
	register int i;
	register char *cp,*dp;
	char c,*index();

	if(out == (struct ax25_addr *)NULL || call == NULLCHAR || *call == '\0'){
		return -1;
	}
	/* Find dash, if any, separating callsign from ssid
	 * Then compute length of callsign field and make sure
	 * it isn't excessive
	 */
	dp = index(call,'-');
	if(dp == NULLCHAR)
		csize = strlen(call);
	else
		csize = dp - call;
	if(csize > 6)
		return -1;
	/* Now find and convert ssid, if any */
	if(dp != NULLCHAR){
		dp++;	/* skip dash */
		ssid = atoi(dp);
		if(ssid > 15)
			return -1;
	} else
		ssid = 0;
	/* Copy upper-case callsign, left shifted one bit */
	cp = out->call;
	for(i=0;i<csize;i++){
		c = *call++;
		if(islower(c))
			c = toupper(c);
		*cp++ = c << 1;
	}
	/* Pad with shifted spaces if necessary */
	for(;i<6;i++)
		*cp++ = ' ' << 1;
	
	/* Insert substation ID field and set reserved bits */
	out->ssid = 0x60 | (ssid << 1);
	return 0;
}
static
addreq(a,b)
register struct ax25_addr *a,*b;
{
	if(bcmp(a->call,b->call,ALEN) != 0)
		return 0;
	if((a->ssid & SSID) != (b->ssid & SSID))
		return 0;
	return 1;
}
/* Convert encoded AX.25 address to printable string */
pax25(e,addr)
char *e;
struct ax25_addr *addr;
{
	register int i;
	char c,*cp;

	cp = addr->call;
	for(i=6;i != 0;i--){
		c = (*cp++ >> 1) & 0x7f;
		if(c == ' ')
			break;
		*e++ = c;
	}
	sprintf(e,"-%d",(addr->ssid >> 1) & 0xf);	/* ssid */
}
/* Print a string of AX.25 addresses in the form
 * "KA9Q-0 [via N4HY-0,N2DSY-2]"
 */
psax25(e,addr)
register char *e;
register struct ax25_addr *addr;
{
	int i;

	for(i=0;;i++){
		pax25(e,addr);
		if(addr->ssid & E)
			break;
		if(i == 0)
			strcat(e," via ");
		else
			strcat(e,",");
		e += strlen(e);
		/* addr++; */
		addr = (struct ax25_addr *) ((char *)addr + AXALEN);
	}
}
/* Return a pointer to the control byte in the given frame */
char *
cbyte(fp)
register struct mbuf *fp;
{
	register char *cp;
	register unsigned cnt;

	if(fp == NULLBUF || fp->data == NULLCHAR)
		return NULLCHAR;

	cnt = fp->cnt;
	cp = fp->data;
	while(cnt != 0 && (*cp & E) == 0){
		cnt--;
		cp++;
	}
	/* If the address field never ended, cnt = 0; if it ended
	 * on the last byte of a frame, cnt = 1. In either case,
	 * there is no control field
	 */
	if(cnt <= 1)
		return NULLCHAR;
	else
		return cp + 1;
}
dokiss(argc,argv)
int argc;
char *argv[];
{
	struct interface *ifp;
	struct mbuf *hbp;
	int i;
	char *cp;

	if(argc < 2){
		printf("Interface name missing\r\n");
		return 1;
	}
	for(ifp=ifaces;ifp != NULLIF;ifp = ifp->next){
		if(strcmp(argv[1],ifp->name) == 0)
			break;
	}
	if(ifp == NULL){
		printf("Interface \"%s\" unknown\r\n",argv[1]);
		return 1;
	}
	if(ifp->output != kiss_output){
		printf("Interface \"%s\" not kiss\r\n",argv[1]);
		return 1;
	}
	if(argc < 3){
		printf("Data field missing\r\n");
		return 1;
	}
	/* Number of bytes in message == number of args - 2, since
	 * first two args are "kiss" and the interface name
	 */
	if((hbp = alloc_mbuf(argc - 2)) == NULLBUF){
		free_p(hbp);
		return;
	}
	hbp->cnt = argc - 2;
	hbp->next = NULL;
	for(i=2,cp = hbp->data;i < argc;i++,cp++){
		*cp = htoi(argv[i]);
	}
	slipq(ifp->dev,hbp);
}
#ifdef	TRACE

/* Dump an AX.25 packet header */
ax25_dump(abp)
struct mbuf *abp;
{
	struct mbuf *bp;
	struct ax25_addr src,dest,addr;
	char tmp[20],*cp,control,cmdrsp,pid;
	int16 len,type,ftype();

	dup_p(&bp,abp,0,len_mbuf(abp));

	/* Read and print the destination and source addresses */
	if(pullup(&bp,(char *)&dest,AXALEN) != AXALEN)
		goto quit;
	if(pullup(&bp,(char *)&src,AXALEN) != AXALEN)
		goto quit;

	pax25(tmp,&src);
	printf("AX25: %s",tmp);
	pax25(tmp,&dest);
	printf("->%s",tmp);

	if((src.ssid & E) == 0){
		/* Find the last address entry in the digi string */
		printf(" v");
		while(pullup(&bp,(char *)&addr,AXALEN) == AXALEN){
			pax25(tmp,&addr);
#ifdef	AMIGA
			/* this is just personal preference; reminds me of 
			   the WA8DED TNC-1 code */
			printf(" %s%s",tmp,(addr.ssid & REPEATED) ? "*":"");
#else
			printf(" %s%s",tmp,(addr.ssid & REPEATED) ? "H":"");
#endif
			if(addr.ssid & E)
				break;	/* Found it */
		}
	}
	if(pullup(&bp,&control,1) != 1)
		goto quit;

	putchar(' ');
	type = ftype(control);
	switch(type){
	case I:
		printf("I");
		break;
	case SABM:
		printf("SABM");
		break;
	case DISC:
		printf("DISC");
		break;
	case DM:
		printf("DM");
		break;
	case UA:
		printf("UA");
		break;
	case RR:
		printf("RR");
		break;
	case RNR:
		printf("RNR");
		break;
	case REJ:
		printf("REJ");
		break;
	case FRMR:
		printf("FRMR");
		break;
	case UI:
		printf("UI");
		break;
	default:
		printf("[invalid]");
	}

	if((dest.ssid & C) != (src.ssid & C)){
		if(dest.ssid & C)
			cmdrsp = COMMAND;
		else
			cmdrsp = RESPONSE;
	} else 
		cmdrsp = UNKNOWN;
	/* Dump poll/final bit */
	if(control & PF){
		switch(cmdrsp){
		case COMMAND:
			printf("(P)");
			break;
		case RESPONSE:
			printf("(F)");
			break;
		default:
			printf("(P/F)");
			break;
		}
	}
	/* Dump sequence numbers */
	if((type & 0x3) != U)	/* I or S frame? */
		printf(" NR=%d",(control>>5)&7);
	if((type & 0x1) == I)	/* I frame? */
		printf(" NS=%d",(control>>1)&7);

	if(pullup(&bp,&pid,1) != 1)
		goto quit;
	printf(" pid 0x%x\r\n",pid & 0xff);
	if((trace & TRACE_HDR) > 2){
		switch(pid & 0xff){
		case PID_ARP:
			arp_dump(bp);
			break;
		case PID_IP:
			ip_dump(bp);
			break;
#ifdef	NETROM
		case PID_NETROM:
			netrom_dump(bp);
			break;
#endif
		}
	}
	free_p(bp);
	fflush(stdout);
	return;
quit:	/* I hate go-to's, but sometimes there's no real choice */
	free_p(bp);
	printf("\r\n");
	fflush(stdout);
}
/* Figure out the frame type from the control field
 * This is done by masking out any sequence numbers and the
 * poll/final bit after determining the general class (I/S/U) of the frame
 */
static
int16
ftype(control)
register char control;
{
	if((control & 1) == 0)	/* An I-frame is an I-frame... */
		return I;
	if(control & 2)			/* U-frames use all except P/F bit for type */
		return(control & ~PF);
	else					/* S-frames use low order 4 bits for type */
		return(control & 0xf);
}
#endif
SHAR_EOF
cat << \SHAR_EOF > ftpcli.c
/* FTP client (interactive user) code */
#include <stdio.h>
#include "machdep.h"
#include "mbuf.h"
#include "netuser.h"
#include "icmp.h"
#include "timer.h"
#include "tcp.h"
#include "ftp.h"
#include "session.h"
#include "cmdparse.h"
#include "dos.h"

extern struct session *current;
extern char nospace[];
#define MAXFILENAME	32
char multiarg[NARG][MAXFILENAME];
int multiargc = 0, curmulti = 0;
struct FileInfoBlock info;
int dodfind = 1;
int docd(),dodir(),doget(),dols(),doput(),dotype(),doabort(), domput();

struct cmds ftpabort[] = {
	"abort",     doabort, 0, NULLCHAR, NULLCHAR,
	NULLCHAR,    NULLFP,  0, "Only valid command is \"abort\"", NULLCHAR,
};

struct cmds ftpcmds[] = {
	"cd",	docd,   2,    "cd <directory>", "Could not change directory",
	"dir",	dodir,  0,    NULLCHAR,         "Could not complete dir",
	"list",	dodir,  0,    NULLCHAR,         "Could not complete dir",
	"get",	doget,  0,    NULLCHAR,         "Could not complete get",
	"ls",	dols,   0,    NULLCHAR,         "Could not complete ls",
	"mput", domput,	0,    NULLCHAR, 	"Could not complete mput",
	"nlst",	dols,   0,    NULLCHAR,         "Could not complete ls",
	"put",	doput,  0,    NULLCHAR,         "Could not complete put",
	"type",	dotype, 0,    NULLCHAR,         "Could not complete type",
	NULLCHAR,	NULLFP, 0, NULLCHAR, NULLCHAR,
};

/* Handle top-level FTP command */
doftp(argc,argv)
int argc;
char *argv[];
{
	int32 aton();
	int ftpparse();
	char *inet_ntoa();
	void r_ctl(),s_ctl();
	struct session *s,*newsession();
	struct ftp *ftp,*ftp_create();
	struct tcb *tcb;
	struct socket lsocket,fsocket;

	lsocket.address = ip_addr;
	lsocket.port = lport++;
	fsocket.address = aton(argv[1]);
	if(fsocket.address == ip_addr){
		printf("FTPing to yourself not supported\r\n");
		return 1;
	}
	if(argc < 3)
		fsocket.port = FTP_PORT;
	else
		fsocket.port = atoi(argv[2]);

	/* Allocate a session control block */
	if((s = newsession()) == NULLSESSION){
		printf("Too many sessions\r\n");
		return 1;
	}
	current = s;
	s->type = FTP;
	s->parse = ftpparse;

	/* Allocate an FTP control block */
	if((ftp = ftp_create(0)) == NULLFTP){
		s->type = FREE;
		printf(nospace);
		return 1;
	}
	ftp->state = COMMAND_STATE;
	s->cb.ftp = ftp;	/* Downward link */
	ftp->session = s;	/* Upward link */

	/* Now open the control connection */
	tcb = open_tcp(&lsocket,&fsocket,TCP_ACTIVE,
		0,r_ctl,NULLVFP,s_ctl,0,(int *)ftp);
	if(tcb == NULLTCB || tcb->state == CLOSED){
		/* This is actually a bit dirty here. About the only time the
		 * state will ever be closed here is if we tried to connect to
		 * ourselves and got RST'ed.  If this is true then the close
		 * upcall will already have freed the TCB and the FTP block,
		 * so we're looking at the TCB after it's been freed.
		 */
		s->type = FREE;
		return 0;
	}
	ftp->control = tcb;
	go();
	return 0;
}
/* Parse user FTP commands */
int
ftpparse(line,len)
char *line;
int16 len;
{
	struct mbuf *bp;

	if(current->cb.ftp->state != COMMAND_STATE){
		/* The only command allowed in data transfer state is ABORT */
		if(cmdparse(ftpabort,line) == -1){
			printf("Transfer in progress; only ABORT is acceptable\r\n");
		}
		fflush(stdout);
		return;
	}

	/* Save it now because cmdparse modifies the original */
	bp = qdata(line,len);

	if(cmdparse(ftpcmds,line) == -1){
		/* Send it direct */
		if(bp != NULLBUF)
			send_tcp(current->cb.ftp->control,bp);
		else
			printf(nospace);
	} else {
		free_p(bp);
	}
	fflush(stdout);
}
/* Translate 'cd' to 'cwd' for convenience */
static
int
docd(argc,argv)
int argc;
char *argv[];
{
	register struct ftp *ftp;

	ftp = current->cb.ftp;
	return sndmsg(ftp,"CWD %s\r\n",argv[1]);
}


/* Handle "type" command from user */
static
int
dotype(argc,argv)
int argc;
char *argv[];
{
	register struct ftp *ftp;

	ftp = current->cb.ftp;
	if(argc < 2){
		switch(ftp->type){
		case IMAGE_TYPE:
			printf("Image\r\n");
			break;
		case ASCII_TYPE:
			printf("Ascii\r\n");
			break;
		}
		return 0;
	}
	switch(*argv[1]){
	case 'i':
	case 'b':
		ftp->type = IMAGE_TYPE;
		break;
	case 'a':
		ftp->type = ASCII_TYPE;
		break;
	default:
		printf("Invalid type %s\r\n",argv[1]);
		return 1;
	}
	/* Send a TYPE message */
	return sndmsg(ftp,"TYPE %s\r\n",(ftp->type == ASCII_TYPE) ? "A" : "I");
}
/* Start receive transfer. Syntax: get <remote name> [<local name>] */
static
doget(argc,argv)
int argc;
char *argv[];
{
	void r_ftpd(),s_ftp();
	char *index(),*remotename,*localname;
	register struct ftp *ftp;

	ftp = current->cb.ftp;
	if(ftp == NULLFTP){
		printf("Not an FTP session!\r\n");
		return 1;
	}
	if(argc < 2){
		printf("File?\r\n");
		return 1;
	}
	remotename = argv[1];
	if(argc < 3)
		localname = remotename;
	else
		localname = argv[2];

	if(ftp->fp != NULLFILE)
		fclose(ftp->fp);

	if((ftp->fp = fopen(localname,"w")) == NULLFILE){
		printf("Cannot write %s\r\n",localname);
		return 1;
	}
	ftp->state = RECEIVING_STATE;
	ftpsetup(ftp,r_ftpd,NULLVFP,s_ftp);

	/* Generate the command to start the transfer */
	return sndmsg(ftp,"RETR %s\r\n",remotename);
}
/* List remote directory. Syntax: dir <remote directory/file> [<local name>] */
static
dodir(argc,argv)
int argc;
char *argv[];
{
	void r_ftpd(),s_ftp();
	char *index(),*localname;
	register struct ftp *ftp;

	ftp = current->cb.ftp;
	if(ftp == NULLFTP){
		printf("Not an FTP session!\r\n");
		return 1;
	}
	if(argc < 3)
#ifdef	CPM
		localname = "con:";
#endif
#ifdef	MSDOS
		localname = "con";
#endif
#ifdef	UNIX
		localname = "/dev/tty";
#endif
#ifdef  XENIX
                localname = "/dev/tty";
#endif
#ifdef  AMIGA
		localname = "*";
#endif  AMIGA
	else
		localname = argv[2];

	if(ftp->fp != NULLFILE)
		fclose(ftp->fp);

	if((ftp->fp = fopen(localname,"w")) == NULLFILE){
		printf("Cannot write %s\r\n",localname);
		return 1;
	}
	ftp->state = RECEIVING_STATE;
	ftpsetup(ftp,r_ftpd,NULLVFP,s_ftp);
	/* Generate the command to start the transfer
	 * It's done this way to avoid confusing the 4.2 FTP server
	 * if there's no argument
	 */
	if(argc > 1)
		return sndmsg(ftp,"LIST %s\r\n",argv[1]);
	else
		return sndmsg(ftp,"LIST\r\n","");
}
/* Abbreviated (name only) list of remote directory.
 * Syntax: ls <remote directory/file> [<local name>]
 */
static
dols(argc,argv)
int argc;
char *argv[];
{
	void r_ftpd(),s_ftp();
	char *index(),*localname;
	register struct ftp *ftp;

	ftp = current->cb.ftp;
	if(ftp == NULLFTP){
		printf("Not an FTP session!\r\n");
		return 1;
	}
	if(argc < 3)
#ifdef	CPM
		localname = "con:";
#endif
#ifdef	MSDOS
		localname = "con";
#endif
#ifdef	UNIX
		localname = "/dev/tty";
#endif
#ifdef	XENIX
		localname = "/dev/tty";
#endif
#ifdef  AMIGA
		localname = "*";
#endif  AMIGA
	else
		localname = argv[2];

	if(ftp->fp != NULLFILE)
		fclose(ftp->fp);

	if((ftp->fp = fopen(localname,"w")) == NULLFILE){
		printf("Cannot write %s\r\n",localname);
		return 1;
	}
	ftp->state = RECEIVING_STATE;
	ftpsetup(ftp,r_ftpd,NULLVFP,s_ftp);
	/* Generate the command to start the transfer */
	if(argc > 1)
		return sndmsg(ftp,"NLST %s\r\n",argv[1]);
	else
		return sndmsg(ftp,"NLST\r\n","");
}
doone(localname, remotename)
char *localname, *remotename;
{
	void t_ftpd(),s_ftp();
	struct ftp *ftp;

	if((ftp = current->cb.ftp) == NULLFTP){
		printf("Not an FTP session!\r\n");
		return 1;
	}
	if(ftp->fp != NULLFILE)
		fclose(ftp->fp);

	if((ftp->fp = fopen(localname,"r")) == NULLFILE){
		printf("Cannot read %s\r\n",localname);
		return 1;
	}
	ftp->state = SENDING_STATE;
	ftpsetup(ftp,NULLVFP,t_ftpd,s_ftp);

	/* Generate the command to start the transfer */
	return sndmsg(ftp,"STOR %s\r\n",remotename);
}
/* Start transmit. Syntax: put <local name> [<remote name>] */
static
doput(argc,argv)
int argc;
char *argv[];
{

	char *remotename,*localname;

	if(argc < 2){
		printf("File?\r\n");
		return 1;
	}
	localname = argv[1];
	if(argc < 3)
		remotename = localname;
	else
		remotename = argv[2];
	doone(localname, remotename);
}

donextmulti()
{

  int error;
	struct ftp *ftp;

	if((ftp = current->cb.ftp) == NULLFTP){
		printf("Not an FTP session!\r\n");
		return 1;
	}
again:
  if (multiargc > 0)
    {
      if (dodfind)
	{
	  error = dfind(&info, multiarg[curmulti], 0);
	  if (error < 0)
            printf("No files matching %s\n", multiarg[curmulti]);
	  dodfind = 0;
	}
       else
        error = dnext(&info);
      if (error == 0)
        doone(info.fib_FileName, info.fib_FileName);
       else
        {
          curmulti++;
          multiargc--;
          dodfind = 1;
	  goto again;
        }
    }
  if (multiargc == 0)
    {
      printf("Multi put is done\n");
      ftp->state = COMMAND_STATE;
    }
}
/* Start transmit. Syntax: put <local name> [<remote name>] */
static
domput(argc,argv)
int argc;
char *argv[];
{
	int i;
	char *remotename,*localname;
	struct ftp *ftp;

	if((ftp = current->cb.ftp) == NULLFTP){
		printf("Not an FTP session!\r\n");
		return 1;
	}
	if(argc < 2){
		printf("File list?\r\n");
		return 1;
	}
	for(i = 0; i < argc - 1; i++)
		strncpy(multiarg[i], argv[i+1], MAXFILENAME);
	multiargc = argc - 1;
	for(i = 0; i < multiargc; i++)
		printf("copy %s out\n", multiarg[i]);
	curmulti = 0;
	dodfind = 1;
	donextmulti();
}
/* Abort a GET or PUT operation in progress. Note: this will leave
 * the partial file on the local or remote system
 */
doabort(argc,argv)
int argc;
char *argv[];
{
	register struct ftp *ftp;

	ftp = current->cb.ftp;

	/* Close the local file */
	fclose(ftp->fp);
	ftp->fp = NULLFILE;

	switch(ftp->state){
	case SENDING_STATE:
		/* Send a premature EOF.
		 * Unfortunately we can't just reset the connection
		 * since the remote side might end up waiting forever
		 * for us to send something.
		 */
		close_tcp(ftp->data);
		multiargc = 0;
		printf("Put aborted\r\n");
		break;
	case RECEIVING_STATE:
		/* Just exterminate the data channel TCB; this will
		 * generate a RST on the next data packet which will
		 * abort the sender
		 */
		del_tcp(ftp->data);
		ftp->data = NULLTCB;
		printf("Get aborted\r\n");
		break;
	}
	ftp->state = COMMAND_STATE;
	fflush(stdout);
}
/* create data port, and send PORT message */
static
ftpsetup(ftp,recv,send,state)
struct ftp *ftp;
void (*send)();
void (*recv)();
void (*state)();
{
	struct socket lsocket,fsocket;
	struct mbuf *bp;

	lsocket.address = ip_addr;
	lsocket.port = lport++;
	fsocket.address = ftp->control->conn.remote.address;
	fsocket.port = FTPD_PORT;

	/* Compose and send PORT a,a,a,a,p,p message */

	if((bp = alloc_mbuf(35)) == NULLBUF){	/* 5 more than worst case */
		printf(nospace);
		return;
	}
	/* I know, this looks gross, but it works! */
	sprintf(bp->data,"PORT %u,%u,%u,%u,%u,%u\r\n",
		hibyte(hiword(lsocket.address)),
		lobyte(hiword(lsocket.address)),
		hibyte(loword(lsocket.address)),
		lobyte(loword(lsocket.address)),
		hibyte(lsocket.port),
		lobyte(lsocket.port));
	bp->cnt = strlen(bp->data);
	send_tcp(ftp->control,bp);

	/* Post a listen on the data connection */
	ftp->data = open_tcp(&lsocket,&fsocket,TCP_PASSIVE,0,
		recv,send,state,0,(int *)ftp);

}
/* FTP control channel receiver upcall routine */
void
r_ctl(tcb,cnt)
register struct tcb *tcb;
int16 cnt;
{
	struct mbuf *bp;
	struct ftp *ftp;

	if((ftp = (struct ftp *)tcb->user) == NULLFTP){
		/* Unknown connection; kill it */
		close_tcp(tcb);
		return;
	}
	/* Hold output if we're not the current session */
	if(mode != CONV_MODE || current == NULLSESSION || current->cb.ftp != ftp)
		return;

	if(recv_tcp(tcb,&bp,cnt) > 0){
		while(bp != NULLBUF){
#ifndef	AMIGA
			fwrite(bp->data,1,(unsigned)bp->cnt,stdout);
#else
			register unsigned len = bp->cnt;
			register char *dp = bp->data;

			while (len--)
				putchar(*dp++);
#endif
			bp = free_mbuf(bp);
		}
		fflush(stdout);
	}
}

/* Control channel state change upcall routine */
static
void
s_ctl(tcb,old,new)
register struct tcb *tcb;
char old,new;
{
	void ftp_delete();
	struct ftp *ftp;
	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.
	 */
	ftp = (struct ftp *)tcb->user;

	if(current != NULLSESSION && current->cb.ftp == ftp)
		notify = 1;

	switch(new){
	case CLOSE_WAIT:
		if(notify)
			printf("%s\r\n",tcpstates[new]);
		close_tcp(tcb);
		break;
	case CLOSED:	/* heh heh */
		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(ftp != NULLFTP)
			ftp_delete(ftp);
		break;
	default:
		if(notify)
			printf("%s\r\n",tcpstates[new]);
		break;
	}
	if(notify)
		fflush(stdout);
}
/* FTP client data channel connection state change upcall handler */
static
void
s_ftp(tcb,old,new)
struct tcb *tcb;
char old,new;
{
	struct ftp *ftp;

	if((ftp = (struct ftp *)tcb->user) == NULLFTP){
		/* Unknown connection, kill it */
		close_tcp(tcb);
		return;
	}
	switch(new){
	case FINWAIT2:
	case TIME_WAIT:
		if(ftp != NULLFTP && ftp->state == SENDING_STATE){
			/* We've received an ack of our FIN, so
			 * return to command mode
			 */
			if (multiargc <= 0)
				{
					printf("Multiput done\r\n");
					ftp->state = COMMAND_STATE;
				}
			if(current != NULLSESSION && current->cb.ftp == ftp){
				printf("Put complete, %lu bytes sent\r\n",
					tcb->snd.una - tcb->iss - 2);
				fflush(stdout);
			}
		}
		break;		
	case CLOSE_WAIT:
		close_tcp(tcb);
		if(ftp != NULLFTP && ftp->state == RECEIVING_STATE){
			/* End of file received on incoming file */
#ifdef	CPM
			if(ftp->type == ASCII_TYPE)
				putc(CTLZ,ftp->fp);
#endif
			fclose(ftp->fp);
			ftp->fp = NULLFILE;
			ftp->state = COMMAND_STATE;
			if(current != NULLSESSION && current->cb.ftp == ftp){
				printf("Get complete, %lu bytes received\r\n",
					tcb->rcv.nxt - tcb->irs - 2);
				fflush(stdout);
			}
		}
		break;
	case CLOSED:
		if(ftp != NULLFTP)
			ftp->data = NULLTCB;
		del_tcp(tcb);
		if (multiargc > 0)
		  donextmulti();
		break;
	}

}
/* Send a message on the control channel */
/*VARARGS*/
static
int
sndmsg(ftp,fmt,arg)
struct ftp *ftp;
char *fmt;
char *arg;
{
	struct mbuf *bp;
	int16 len;

	len = strlen(fmt) + strlen(arg) + 10;	/* fudge factor */
	if((bp = alloc_mbuf(len)) == NULLBUF){
		printf(nospace);
		return 1;
	}
	sprintf(bp->data,fmt,arg);
	bp->cnt = strlen(bp->data);
	send_tcp(ftp->control,bp);
	return 0;
}
SHAR_EOF
cat << \SHAR_EOF > smtpserv.c
/* SMTP Server state machine - see RFC 821
 * Very simple implementation; no forwarding allowed
 * (who wants to re-create "sendmail" ??)
 */
#include <stdio.h>
#include "machdep.h"
#include "mbuf.h"
#include "netuser.h"
#include "timer.h"
#include "tcp.h"
#include "smtp.h"
#ifdef LATTICE
#define tmpfile(x) tmpnam("testingxxxxxxxxxx")
#endif

/* Command table */
static char *commands[] = {
	"helo",
#define	HELO_CMD	0
	"noop",
#define	NOOP_CMD	1
	"mail from:",
#define	MAIL_CMD	2
	"quit",
#define	QUIT_CMD	3
	"rcpt to:",
#define	RCPT_CMD	4
	"help",
#define	HELP_CMD	5
	"data",
#define	DATA_CMD	6
	"rset",
#define	RSET_CMD	7
	NULLCHAR
};

/* Reply messages */
static char help[] = "214-Commands:\r\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET\r\n214 End\r\n";
static char banner[] = "220 %s SMTP Ready\r\n";
static char closing[] = "221 Closing\r\n";
static char ok[] = "250 Ok\r\n";
static char reset[] = "250 Reset state\r\n";
static char sent[] = "250 Sent\r\n";
static char ourname[] = "250 %s, Share and Enjoy!\r\n";
static char enter[] = "354 Enter mail, end with .\r\n";
static char ioerr[] = "452 Temp file write error\r\n";
static char mboxerr[] = "452 Mailbox %s write error\r\n";
static char badcmd[] = "500 Command unrecognized\r\n";
static char syntax[] = "501 Syntax error\r\n";
static char needrcpt[] = "503 Need RCPT (recipient)\r\n";
static char badname[] = "550 Can't open mailbox for %s\r\n";
static struct tcb *smtp_tcb;
/* Start up SMTP receiver service */
smtp_start(argc,argv)
int argc;
char *argv[];
{
	struct socket lsocket;
	void r_mail(),s_mail();

	lsocket.address = ip_addr;
	if(argc < 2)
		lsocket.port = SMTP_PORT;
	else
		lsocket.port = atoi(argv[1]);

	smtp_tcb = open_tcp(&lsocket,NULLSOCK,
		TCP_PASSIVE,0,r_mail,NULLVFP,s_mail,0,(int *)NULL);
}

/* Shutdown SMTP service (existing connections are allowed to finish) */
smtp_stop()
{
	if(smtp_tcb != NULLTCB)
		close_tcp(smtp_tcb);
}
/* SMTP connection state change upcall handler */
static void
s_mail(tcb,old,new)
struct tcb *tcb;
char old,new;
{
	struct mail *mp,*mail_create();

	switch(new){
#ifdef	QUICKSTART
	case SYN_RECEIVED:
#else
	case ESTABLISHED:
#endif
		if((mp = mail_create(tcb)) == NULLMAIL){
			close_tcp(tcb);
			break;
		}
		tprintf(mp->tcb,banner,hostname);
		log(tcb,"open SMTP");
		break;		
	case CLOSE_WAIT:
		mp = (struct mail *)tcb->user;
		mail_delete(mp);				
		close_tcp(tcb);
		break;
	case CLOSED:
		log(tcb,"close SMTP");
		del_tcp(tcb);
		/* Check if server is being shut down */
		if(tcb == smtp_tcb)
			smtp_tcb = NULLTCB;
		break;
	}
}

/* SMTP receiver upcall handler */
static void
r_mail(tcb,cnt)
struct tcb *tcb;
int16 cnt;
{
	register struct mail *mp;
	char *index(),*inet_ntoa(),c;
	struct mbuf *bp;
	void docommand(),deliver(),doline();

	if((mp = (struct mail *)tcb->user) == NULLMAIL){
		/* Unknown session */
		close_tcp(tcb);
		return;
	}
	recv_tcp(tcb,&bp,cnt);
	/* Assemble an input line in the session buffer.
	 * Return if incomplete
	 */
	while(pullup(&bp,&c,1) == 1){
		switch(c){
		case '\r':	/* Strip cr's */
			continue;
		case '\n':	/* Complete line; process it */
			mp->buf[mp->cnt] = '\0';
			doline(mp);
			break;
		default:	/* Assemble line */
			mp->buf[mp->cnt++] = c;
			break;
		}
	}
}
/* Process a line read on an SMTP connection (any state) */
static
void
doline(mp)
register struct mail *mp;
{
	switch(mp->state){
	case COMMAND_STATE:
		docommand(mp);
		break;
	case DATA_STATE:
		tcp_output(mp->tcb);	/* Send ACK; disk I/O is slow */
		if(mp->buf[0] == '.' && strlen(mp->buf) == 1){
			fprintf(mp->data,"\n");	/* Leave a blank line between msgs */

			mp->state = COMMAND_STATE;
			deliver(mp);	/* Also sends appropriate response */
			break;
		}
		/* Append to data file */
		if(fprintf(mp->data,"%s\n",mp->buf) < 0){
			mp->state = COMMAND_STATE;
			tprintf(mp->tcb,ioerr);
		}
		break;
	}
	mp->cnt = 0;
}
/* Create control block, initialize */
static struct mail *
mail_create(tcb)
register struct tcb *tcb;
{
	register struct mail *mp;
	char *calloc();

	if((mp = (struct mail *)calloc(1,sizeof (struct mail))) == NULLMAIL){
		return NULLMAIL;
	}
	mp->tcb = tcb;		/* Downward pointer */
	tcb->user = (int *)mp;	/* Upward pointer */
	return mp;
}

/* Free resources, delete control block */
static
mail_delete(mp)
register struct mail *mp;
{
	register struct addr *ap,*ap1;

	if(mp->system != NULLCHAR)
		free(mp->system);
	for(ap = mp->to;ap != NULLADDR;ap = ap1){
		if(ap->val != NULLCHAR)
			free(ap->val);
		ap1 = ap->next;
		free((char *)ap);
	}
	if(mp->data != NULLFILE)
		fclose(mp->data);
	free((char *)mp);
}

/* Parse and execute mail commands */
static
void
docommand(mp)
register struct mail *mp;
{
	char mailbox[50];
	char *cmd,*arg,*cp,*cp1,**cmdp;
	char *index(),*malloc(),*getname();
	struct tcb *tcb;
	struct addr *ap;
#ifdef LATTICE
	FILE *fp;
#else
	FILE *tmpfile(),*fp;
#endif
#ifdef	DATE
	long t;
	char *ctime();
#endif

	cmd = mp->buf;
	if(mp->cnt < 4){
		/* Can't be a legal SMTP command */
		tprintf(mp->tcb,badcmd);
		return;
	}	
	cmd = mp->buf;

	/* Translate entire buffer to lower case */
	for(cp = cmd;*cp != '\0';cp++)
		*cp = tolower(*cp);

	/* Find command in table; if not present, return syntax error */
	for(cmdp = commands;*cmdp != NULLCHAR;cmdp++)
		if(strncmp(*cmdp,cmd,strlen(*cmdp)) == 0)
			break;
	if(*cmdp == NULLCHAR){
		tprintf(mp->tcb,badcmd);
		return;
	}
	arg = &cmd[strlen(*cmdp)];
	/* Skip spaces after command */
	while(*arg == ' ')
		*arg++;
	/* Execute specific command */
	switch(cmdp-commands){
	case HELO_CMD:
		if((mp->system = malloc((unsigned)strlen(arg)+1)) == NULLCHAR){
			/* If the system is out of memory, just close */
			close_tcp(mp->tcb);
			mail_delete(mp);
			break;			
		} else {
			strcpy(mp->system,arg);
			tprintf(mp->tcb,ourname,hostname);
		}
		break;
	case NOOP_CMD:
	case MAIL_CMD:	/* Rather useless */
		tprintf(mp->tcb,ok);
		break;
	case QUIT_CMD:
		tprintf(mp->tcb,closing);
		close_tcp(mp->tcb);
		mail_delete(mp);
		break;
	case RCPT_CMD:	/* Specify recipient */
		if((cp = getname(arg)) == NULLCHAR){
			tprintf(mp->tcb,syntax);
			break;
		}
		/* Strip the @host part; all names must be local */
		if((cp1 = index(cp,'@')) != NULLCHAR)
			*cp1 = '\0';

		/* Check to see if we can open the mailbox */
		sprintf(mailbox,MAILSPOOL,cp);
#ifdef	AMIGA
		/* probably could use AMIGA's Lock()/Unlock(), but that
		   seems like overkill */
		if((fp = fopen(mailbox,"r")) == NULL){
#else
		if((fp = fopen(mailbox,"a+")) == NULL){
#endif
			tprintf(mp->tcb,badname,cp);
			break;
		}
		fclose(fp);
		/* Allocate an entry on the recipient list. This
		 * assembles the list backwards, but what the heck.
		 */
		if((ap = (struct addr *)malloc(sizeof(struct addr))) == NULLADDR){
			close_tcp(mp->tcb);
			mail_delete(mp);
			break;
		}
		if((ap->val = malloc((unsigned)strlen(mailbox)+1)) == NULLCHAR){
			free((char *)ap);
			close_tcp(mp->tcb);
			mail_delete(mp);
			break;
		}
		strcpy(ap->val,mailbox);
		ap->next = mp->to;
		mp->to = ap;
		tprintf(mp->tcb,ok);
		break;
	case HELP_CMD:
		tprintf(mp->tcb,help);
		break;
	case DATA_CMD:
		if(mp->to == NULLADDR){
			tprintf(mp->tcb,needrcpt);
			break;
		}
		tcp_output(mp->tcb);	/* Send ACK; disk I/O is slow */
		if((mp->data = tmpfile()) == NULLFILE){
			tprintf(mp->tcb,ioerr);
			break;
		}
#ifdef	DATE
		/* Add timestamp; ctime adds newline */
		time(&t);
		if(mp->system != NULLCHAR)
			fprintf(mp->data,
				"Received: from %s by %s\n\twith SMTP ; %s",
				mp->system,
				hostname,
				ctime(&t));
		else
			fprintf(mp->data,"Received: %s\n",ctime(&t));
#endif
		if(ferror(mp->data)){
			tprintf(mp->tcb,ioerr);
		} else {
			mp->state = DATA_STATE;
			tprintf(mp->tcb,enter);
		}
		break;
	case RSET_CMD:
		tcb = mp->tcb;
		mail_delete(mp);
		if((mp = mail_create(tcb)) == NULLMAIL){
			close_tcp(tcb);
		} else {
			mp->state = COMMAND_STATE;
			tprintf(mp->tcb,reset);
		}
		break;
	}
}
/* Given a string of the form <user@host>, extract the part inside the
 * brackets and return a pointer to it.
 */
static
char *
getname(cp)
register char *cp;
{
	char *cp1;
	char *index(),*strncpy();

	if((cp = index(cp,'<')) == NULLCHAR){
		return NULLCHAR;
	}
	cp++;	/* cp -> first char of name */
	if((cp1 = index(cp,'>')) == NULLCHAR){
		return NULLCHAR;
	}
	*cp1 = '\0';
	return cp;
}
/* Deliver mail to the appropriate mail boxes and delete temp file */
static
void
deliver(mp)
register struct mail *mp;
{
	char *index();
	int c;
	register struct addr *ap;
	register FILE *fp;

	for(ap = mp->to;ap != NULLADDR;ap = ap->next){
		fseek(mp->data,0L,0);	/* rewind */
		fp = fopen(ap->val,"a+");
		while((c = getc(mp->data)) != EOF){
			if(putc(c,fp) == EOF)
				break;
		}
		if(ferror(fp)){
			fclose(fp);
			tprintf(mp->tcb,mboxerr,ap->val);
			fclose(mp->data);
			mp->data = NULLFILE;
			return;
		}
		fclose(fp);
	}
	tprintf(mp->tcb,sent);
	fclose(mp->data);
	mp->data = NULLFILE;
}
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.