[comp.sources.unix] v14i085: 3B2 Ethernet Connection and File Transfer Utility

rsalz@uunet.uu.net (Rich Salz) (05/10/88)

Submitted-by: Dave Settle <mcvax!oscar.smb.co.uk!dave@uunet.uu.net>
Posting-number: Volume 14, Issue 85
Archive-name: 3bconnect

[  I don't have a 3B, so you're on your own.  --r$  ]

This set is called 'connect', and is a utility for AT&T 3B2
3BNET (ethernet) networks, providing a remote login facility, and a
file transfer capability.

	Dave.
-----------------------------------CUT HERE-----------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	Makefile
#	connect.1
#	rcp.1
#	server.c
#	connect.c
#	rcp.c
#	eth.c
#	ipaddr.c
#	talk.c
#	sub.c
#	file.c
#	terminate.c
#	mynode.c
#	ftp.c
#	ni.h
#	ftp.h
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'README'" '(4159 characters)'
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
AT&T 3B2 Ethernet Connection and File Transfer Utility

This package allows you to login to other 3B2 computers connected via 3Bnet, 
in a similar fashion to 'cu', and also to transfer files across the ethernet.

CONNECT

'Connect' provides a simple connection to a shell on the remote machine, linked
to your terminal via pipes. Initially, YOUR terminal will be doing the echoing
(in contrast to 'cu'), with lines shipped across the ethernet only when you
hit RETURN.

You can force the connection into 'raw' mode (each character shipped as typed,
remote end doing the echoing) by sending a QUIT signal. This allows you to
use screen editors on the remote machine.
A further QUIT signal gets you back into 'cooked' mode - this is indicated by
'[cooked]' appearing on your terminal. Unfortunately, this means that you
can't send QUIT signals to processes which you start on the remote host.
Sorry about this. Interrupts get processed OK, though.

You close the connection by typing ^D at your terminal (in cooked mode) -
if you want to send ^D to the remote end, you MUST be in 'raw' mode.

The big problem is that your remote shell has stdin, stdout, and stderr 
connected to PIPES, rather than a real tty, which can cause certain commands
(e.g. stty) to fail.

PC-INTERFACE.
If you have PC-INTERFACE installed, then you can connect to one of the 
pseudo-tty devices provided by the driver, by using the '-l' option of 'connect'
This gets you a remote shell connected to a 'real' tty device, and 
automatically puts you in 'raw' mode.

You close this type of connection by sending a QUIT signal.


RCP
'rcp' provides a simple, but effective, method of transferring files across
the network. The syntax resembles 'uucp'.



WHAT YOU NEED TO DO TO INSTALL IT.

a) Find out the ethernet addresses of all machines on the network.
	Use 'nitable', or the program 'mynode' provided.
	
b) Set up a map file '/usr/lib/ethernet.addr' which maps the names of
your hosts to the rightmost 2 digits of their ethernet address. 
These should be unique - if not, change the value of LSB in 'ni.h' 
to use any pair of digits which are unique for your network.


E.g. 
oscar	80.00.10.30.18.f2        
olive   80.00.10.30.0b.45

/usr/lib/ethernet.addr:

		oscar	f2
		olive	45


c) Set up an inittab entry to run the server.

e.g. (assuming server is '/usr/lib/server')

	et:2:respawn:/usr/lib/server
	
It's important that this line is properly defined! 
DO NOT DEFINE ANY INPUT OR OUTPUT FILES FOR THIS PROCESS.


d) Make sure that you have enough 'port' structures allocated in the NI driver.
   You can do this through the 'packagemgmt/3bnet/nidriver" option in 'sysadm'
	I have 20 ports, 1 buffer per port, 1514 buffer size - this allows
	me at least 5 concurrent 'connects' (I've not tried more)
   You have to reboot for the reconfig to become effective.
   
e) Find out your value of NPROC, from the file /etc/master.d/KERNEL
   Define this value in 'ni.h'
	

f) To connect, say 'connect <host>'

e.g.
	connect olive
	


BUGS

TTY:
You're not connected to a tty at the remote end, so certain commands aren't
going to work. Screen editors will work, but only if you use them when you're
in 'raw' mode.

EOT:
There's no way to send EOT to a remote process. Typing ^D on your terminal will
close the connection. However, when you do a close, all your remote processes
get sent a hangup signal, which usually amounts to the same thing.

ETHERNET ADDRESS:
The ethernet addresses which are used by this package are NOT guaranteed to
be unique, and may interfere with other ethernet applications. (3Bnet and
PC-Interface seem to be OK, though).

If you get conflicts with other packages, you might have to change the values
in server[] and client[] in 'ni.h' ([1] and [2])

They are currently set to 0x02 0x02 - this is totally arbitrary,
so feel free to change it as you wish.
The rest of the bytes in server[] and client[] will be OK.

REMOTE SHELL TERMINATION:
If the remote shell dies, for any reason, you won't be informed until you 
attempt to send it some input.

ADDING FEATURES.
If you put any fancy bits in, please let me know!

Good luck!

Dave Settle, dave@smb.co.uk
SHAR_EOF
if test 4159 -ne "`wc -c < 'README'`"
then
	echo shar: "error transmitting 'README'" '(should have been 4159 characters)'
fi
fi
echo shar: "extracting 'Makefile'" '(1042 characters)'
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#CFLAGS = -g
#LIBS = -lg
#
# If you wish a SECURE version (no super-user server processes),
# define SECURE. This will prevent root from using 'connect' or 'rcp'.
#
# CFLAGS = -O -DSECURE

SERVER = server.o eth.o ipaddr.o talk.o sub.o file.o ftp.o
CONNECT = connect.o eth.o terminate.o ipaddr.o
MYNODE = mynode.o ipaddr.o
RCP = rcp.o sub.o ipaddr.o eth.o ftp.o

DOCS = README Makefile connect.1 rcp.1

SOURCES = server.c connect.c rcp.c eth.c ipaddr.c talk.c \
	sub.c file.c terminate.c mynode.c ftp.c ni.h ftp.h

all: server connect mynode rcp

lint: 
	lint ${SERVER:.o=.c}
	lint ${CONNECT:.o=.c}
	lint ${RCP:.o=.c}

${SERVER} ${CONNECT} ${RCP} : ni.h

rcp.o ftp.o file.o : ftp.h

server: ${SERVER}
	cc -o server ${SERVER} ${LIBS}
	
connect: ${CONNECT}
	cc -o connect ${CONNECT} ${LIBS}
	
rcp: ${RCP}
	cc -o rcp ${RCP} ${LIBS}

mynode: ${MYNODE}
	cc -o mynode ${MYNODE} ${LIBS}

shar:
	shar -cv ${DOCS} ${SOURCES} > connect.shar

install:
	if [ -f /usr/lib/server ] ; then \
		mv /usr/lib/server /usr/lib/server.old ; fi
	cp server /usr/lib
SHAR_EOF
if test 1042 -ne "`wc -c < 'Makefile'`"
then
	echo shar: "error transmitting 'Makefile'" '(should have been 1042 characters)'
fi
fi
echo shar: "extracting 'connect.1'" '(3547 characters)'
if test -f 'connect.1'
then
	echo shar: "will not over-write existing file 'connect.1'"
else
cat << \SHAR_EOF > 'connect.1'
.TH CONNECT 1 local
.SH NAME
connect
.SH SYNOPSIS
.B "connect [-l] remote_host"
.PP
.SH DESCRIPTION
.I connect
allows you to connect to a remote host connected via 3BNET, and to start a
remote shell on that machine. The shell started is the one specified by 
your SHELL variable, or "/bin/sh" if this is not set.
.PP
Initially, all the echoing is done by the local machine, and input is 
transmitted line by line (cooked mode). If you wish the remote machine to
do the echoing (e.g. you're running a screen editor there), you must put
your terminal in raw mode, by sending a QUIT signal.
.PP
In raw mode, each character is send as soon as it is typed, and no echoing
is performed by the local machine. A further QUIT signal will revert to
cooked mode again (i.e. QUIT can be used to toggle between raw and cooked modes)
.PP
The connection can be closed by typing EOT in cooked mode.
.PP
.SH "LOGIN OPTION"
.PP
If the \fB-l\fR option is specified, and if PC-INTERFACE is installed on the
remote host, then you will be connected to one of the pseudo-ttys supported
by driver.
.PP
This option gives you a remote connection which looks more like a direct
tty link, and automatically puts you in 'raw' mode. 
.PP
On the other hand, you have to go through all the bother of logging on,
which takes more time.
.PP
Using the \fB-i\fR option \fBonly\fR, the connection is closed by a QUIT
signal.
.SH SECURITY
The remote server will only accept a request to start a shell if your login
name is known on the remote machine, and your remote uid matches your local uid.
.PP
The uid used to start the remote process is your \fBreal\fR uid, not your
effective uid, so that even if you're running su(1), your remote process
will not be root.
.PP
The only way you can login to a remote machine as root, is to login to your
local machine as root (on the console), and \fIconnect\fR from there.
.PP
When the remote process it started, it will have your uid, the gid assosciated
with the \fBremote\fR uid, and will be located in your remote home directory.
.SH "HOST MAPPING"
The mapping between host names and ethernet addresses is defined in the
file '/usr/lib/ethernet.addr'.
.PP
The format of the file is:
.PP
		<hostname> <ID>
.PP
e.g.
.PP
		olive	45
		oscar	f2
.PP
As shipped, the package expects each host to have a physical ethernet
address which is unique \fBin the last (lsb) byte\fR.
.PP
The addresses which are used are:
.PP
		XX.YY.YY.00.00.HH	Server
		XX.YY.YY.PID.PID.HH	Client/Remote Process.
.PP
The values of XX and YY are totally arbitrary, and you can change them if
you wish. The value of HH must be different for each host, and is taken from
the LSB of the host physical ethernet address. If this does not produce
a unique value for each server, then you can choose to take it from any other
byte of the physical address, by redefining LSB (ni.h) to pick any other byte.
.SH EXAMPLES
.PP
connect -l olive
.PP
Connect to a PC-INTERFACE pseudo-tty on host 'olive'.
.PP
connect oscar
.PP
Connect to a remote shell on host 'oscar'
.PP
.SH FILES
/dev/ni				Ethernet interface.
.PP
/dev/ptty??			PC-INTERFACE pseudo-ttys
.PP
/usr/lib/ethernet.add		List of hosts.
.PP
.SH SEE ALSO
.SH BUGS
You can't send QUIT signals to remote processes, 'cos QUIT does various things
to the connection.
.PP
The values of XX and YY may conflict with other applications.
.PP
There's no guarantee that you can get a unique host ID using only one byte
from the physical node address.
.PP
\fIconnect <host>\fR gets you a shell which isn't connected to a real tty
device.
SHAR_EOF
if test 3547 -ne "`wc -c < 'connect.1'`"
then
	echo shar: "error transmitting 'connect.1'" '(should have been 3547 characters)'
fi
fi
echo shar: "extracting 'rcp.1'" '(2176 characters)'
if test -f 'rcp.1'
then
	echo shar: "will not over-write existing file 'rcp.1'"
else
cat << \SHAR_EOF > 'rcp.1'
.TH RCP 1 local
.SH SYNOPSIS
.B "rcp [-d] source [source ...] dest"
.SH DESCRIPTION
\fIrcp\fR allows files to be transferred to or from remote machines,
using the ethernet connection between Olivetti 3B2's supporting 3BNET.
It does \fBNOT\fR use the \fInisend\fB network interface, which has 
proved to be very unreliable, but uses the packet transport interface
directly.
.PP
Any of the file names specified may be prefixed with a \fBsystem name\fR,
meaning that the file should be transferred to or from the remote machine
specified. The system name is separated from the file name by either
a '!' or a ':' character.
.PP
Only one remote system may be specified for each \fIrcp\fR command, 
and all the source files must live on the same system. It is not possible
to move files from one place to another on a remote machine - they
must transfer across the ethernet connection. This is a restriction 
imposed by the protocol.
.SH FLAGS
.PP
.TP
.B -d
Turn debugging output on during the file transfer procedure. Useful only
if you know what the ftp protocol is supposed to be doing.
.PP
.SH EXAMPLES
.PP
rcp oscar!/usr/dave/file.c /usr/tmp
.PP
Copies the file \fB/usr/dave/file.c\fR from the system \fBoscar\fR to the
destination \fB/usr/tmp\fR on the local system.
.PP
rcp /usr/dave/src/rcp.c myprog olive:/usr/dave/public
.PP
Copies the local files \fB/usr/dave/src/rcp.c\fR and
\fBmyprog\fR, to the destination \fB/usr/dave/public\fR on the
system \fBolive\fR.
.SH "SEE ALSO"
uucp(1), cp(1), nisend(1)
.SH DIAGNOSTICS
Normal information about files which can't be read or written; this
information may come from either host.
.PP
In the event of a protocol failure, the remote server may terminate
\fIrcp\fR. There should be a diagnostic message - consult your guru.
.SH BUGS
You can't talk to more than one remote host at a time: things like
.PP
rcp a:file1 b:file2 c:dest
.PP
are \fBNOT\fR allowed.
.PP
You can't move things around on the remote host - you can only copy
from one host to another.
.PP
The file transfer protocol is a home-grown variety, and is not going
to be any sort of standard. It would be nice if it used TCP/IP, or
some other well-defined protocol.
SHAR_EOF
if test 2176 -ne "`wc -c < 'rcp.1'`"
then
	echo shar: "error transmitting 'rcp.1'" '(should have been 2176 characters)'
fi
fi
echo shar: "extracting 'server.c'" '(6188 characters)'
if test -f 'server.c'
then
	echo shar: "will not over-write existing file 'server.c'"
else
cat << \SHAR_EOF > 'server.c'
/*
 * Copyright (C) 1988 Dave Settle. All rights reserved.
 * Permission is granted to use, copy and modify this software, providing
 * that it is not sold for profit, and that this copyright notice is retained
 * in any copies of the source.
 */
/*
 * server: ethernet server. Accepts requests, and spawns processes to
 * serve remote clients.
 */
 
#include <sys/ni.h>
#include <stdio.h>
#include <sys/signal.h>
#include <sys/errno.h>
extern int errno;
#include <pwd.h>

struct passwd *getpwnam();
char *strrchr();

#define MAIN
#include "ni.h"

int input[2], output[2];		/* pipes for shell */
char cmd[128], program[32];
int reader, sh;				/* pid of child reader process */

#define LOG  "/usr/lib/server.log"

#define GIGABYTE	2*1024*1024	/* number of blocks in 1 GByte */

/*
 * endproc: called when the shell has died (via SIGPIPE) by the writer process
 */
endproc(){
	struct request req;
	skill(reader, SIGTERM);
	sprintf(req.r_data, "your shell died");
	send(&req, strlen(req.r_data), TERMINATE, client);
	exit(0);
}
/*
 * Called when we get a TERMINATE packet from the client.
 */
sigterm(){	
	skill(reader, SIGTERM);
	skill(sh, SIGHUP);
	sleep(1);
	skill(sh, SIGKILL);
	exit(0);
}

main()
{
	int in;
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTERM, sigterm);
	ulimit(2, (long) GIGABYTE);
/*
 * This process runs from "init" - it doesn't have ANY file descriptors open
 * Check this, and if necessary, create some dummy fd's.
 */
 	if(in = open("/dev/null", 0) == 0) {
 		open("/dev/null", 1);
 		open("/dev/null", 1);
 	}
 	else close(in);
	freopen(LOG, "a+", stderr);
	if(configure(server, 2, 0) == -1) exit(1);
 	accept();
 	return(0);
}
accept(){
	struct request request;
 	recv(&request);
/*
 * Fork off child to deal with the request. We die, and are respawned by
 * init.
 */
 	if(fork() == 0) exit(serve(&request));
/*
 * parent - exit.
 */
 	return(0);
}
/*
 * serve: set up a shell for the remote process, and pass data.
 */
#define RD 0
#define WT 1
serve(r)
register struct request *r;
{
	int pid = getpid(), writer, n, uid, ok;
	int oldnet;
	char user[32];
	struct passwd *pw;
	char home[32], path[128], shell[16], mail[64], term[32], logname[64];
/*
 * Get the user info from the request header, and set our parameters
 */	
	n = sscanf(r->r_data, "%d %s %s", &uid, user, term);
	memcpy(client, r->r_port.srcaddr, ETHERSIZE);
	pw = getpwnam(user);
	if(pw == NULL) {
		n = sprintf(r->r_data, "User '%s' not known here", user);
		send(r, n, TERMINATE, client);
		exit(0);
	}
	if(pw->pw_uid != uid) {
		n = sprintf(r->r_data, "User '%s' and uid '%d' don't match",
			user, uid);
		send(r, n, TERMINATE, client);
		exit(0);
	}
#ifdef SECURE
	if(uid == 0) {
		n = sprintf(r->r_data, "Secure server: root access not allowed");
		send(r, n, TERMINATE, client);
		exit(0);
	}
#endif
	setpgrp();			/* Set up a process group */
	setgid(pw->pw_gid);
	setuid(pw->pw_uid);
	sprintf(mail, "MAIL=/usr/mail/%s", user);
	sprintf(path, "PATH=:/usr/ucb:/bin:/usr/bin");
	sprintf(logname, "LOGNAME=%s", user);
	sprintf(shell, "SHELL=%s", pw->pw_shell);
	sprintf(home, "HOME=%s", pw->pw_dir);
	putenv(home); putenv(path); putenv(shell); 
	putenv(mail); putenv(logname);
	putenv(term);
	chdir(pw->pw_dir);
if(debug) {
	fprintf(stderr, "Server request from %s\n", getenv("LOGNAME"));
}
	server[PIDMSB] = (pid >> 8) & 0xff;
	server[PIDLSB] = pid & 0xff;
	oldnet = ethernet;			/* save old port for a bit */
	if(configure(server, 3, getpid()) == -1) {	/* We are this address */
		ethernet = oldnet;		/* Use old port */
		sprintf(r->r_data, "Can't allocate port");
		send(r, strlen(r->r_data), TERMINATE, client);
		exit(1);
	}
	close(oldnet);				/* OK - use the new port */
/*
 * Inform the client of our new address
 */
	memcpy(r->r_data, mynode, ETHERSIZE);
	send(r, ETHERSIZE, ACCEPT, client);
/*
 * Get the command which the client wishes us to execute
 */
 	recv(r);
 	memcpy(cmd, r->r_data, r->r_size);
/*
 * Special cases: 
 *	"login" - user wants a real login - use the PC-Interface pttys
 *	"fileserver" - user wants to transfer files.
 */
 	if(!strcmp(cmd, "login")) return(dologin(r));
 	if(!strcmp(cmd, "fileserver")) return(fileserver(r));
/*
 * Now start off the child process, and collect it's output
 */
 	pipe(input);
 	pipe(output);
 	if(sh = fork()) {
 		close(input[WT]);
 		close(output[RD]);
 		writer = getpid();
 		if(reader = fork()) {
	 		ok = 1;
	 		signal(SIGPIPE, endproc);
	 		while(ok) {
	 			recv(r);
if(debug) {
	 			fprintf(stderr, "server: got [%s] from %s\n",
	 				r->r_data, ipaddr(r->r_port.srcaddr));
}
				ok = passon(r, output[WT]);
	 		}
			sigterm();
	 	}
	 	else {
	 		signal(SIGTERM, SIG_DFL);
 			while((n = read(input[RD], r->r_data, sizeof r->r_data)) != -1) {
 				send(r, n, DATA, client);
if(debug) {
 				fprintf(stderr, "server: %s sent to %s\n",
 					r->r_data, ipaddr(client));
}
 			}
 			sprintf(r->r_data, "hangup\n");
 			send(r, strlen(r->r_data), TERMINATE, client);
 			skill(writer, SIGKILL);
	 		exit(0);
	 	}
 	}
 	else doexec(r);		/* exec the child shell */
}	
/*
 * doexec: perform exec of requested procedure
 */
doexec(r)
struct request *r;
{
	char *argv[6];
	split(cmd, argv);
 	close(0); close(1); close(2);
/*
 * Duplicate READ side of output as stdin
 */
 	if(dup(output[RD]) != 0) perror("dup(input) != 0");
/*
 * Duplicate WRITE side of input as  stdout & stderr
 */
 	if(dup(input[WT]) != 1) perror("dup(output) != 1");
 	if(dup(input[WT]) != 2) perror("dup(output) != 2");
/*
 * close all the parent's pipes - they're duplicated
 */
	close(input[0]); close(input[1]);
	close(output[0]); close(output[1]);
	close(ethernet);
	signal(SIGINT, SIG_DFL);
	signal(SIGQUIT, SIG_DFL);
	execvp(program, argv);
	sprintf(r->r_data, "Cannot exec %s\n", cmd);
	send(r, strlen(r->r_data), DATA, client);
	exit(1);
}
split(p, argv)
register char *p;
char **argv;
{	
	int n = 1;
	char *f;
	static char name[16];
	argv[0] = p;
	while(*p) {
		if(*p == ' ') {
			*p++ = '\0';
			while(*p == ' ') p++;
			argv[n++] = p;
		}
		else p++;
	}
	strcpy(program, argv[0]);
	if(f = strrchr(argv[0], '/')) f++;
	else f = argv[0];
	sprintf(name, "-%s", f);
	argv[0] = name;
	argv[n++] = NULL;
}

SHAR_EOF
if test 6188 -ne "`wc -c < 'server.c'`"
then
	echo shar: "error transmitting 'server.c'" '(should have been 6188 characters)'
fi
fi
echo shar: "extracting 'connect.c'" '(6204 characters)'
if test -f 'connect.c'
then
	echo shar: "will not over-write existing file 'connect.c'"
else
cat << \SHAR_EOF > 'connect.c'
/*
 * Copyright (C) 1988 Dave Settle. All rights reserved.
 * Permission is granted to use, copy and modify this software, providing
 * that it is not sold for profit, and that this copyright notice is retained
 * in any copies of the source.
 */
/*
 * connect.c: connect tty to remote host.
 */

#include <sys/types.h> 
#include <sys/signal.h>
#include <sys/errno.h>
#include <sys/utsname.h>
#include <termio.h>
#include <pwd.h>

struct passwd *getpwuid(), *getpwnam();
char *getlogin();
int (*signal())();

extern int errno;
#include <stdio.h>

#define MAIN
#include "ni.h"

char remoteshell[] = {0,0,0,0,0,0};

struct request request;

int reader, writer;			/* Processes */

int login = 0;				/* Are we doing a login? */

#define COOKED 0
#define RAW 1
struct termio raw, cooked;		/* To allow screen mode changes */
int screenmode = COOKED, toggle(), trap();

wakeup(){
	signal(SIGALRM, wakeup);
}
/*
 * Got a signal - die. Writer is child process here.
 */
die(sig){
	if(sig) printf("Connection closed.\r\n");
	if((getpid() == writer) && reader) kill(reader, SIGTERM);
	else if(writer) terminate(writer);
	send(&request, 0, TERMINATE, server);
	if(cooked.c_oflag) ioctl(fileno(stdout), TCSETA, &cooked);
	exit(0);
}

sendsig(sig)
{
	struct request request;
	signal(sig, sendsig);
	if(sig == SIGINT) send(&request, 0, RMTSIGINT, server);
	if(sig == SIGQUIT) send(&request, 0, RMTSIGQUIT, server);
}
	

main(argc, argv)
char **argv;

{
	struct utsname uts;
	register char *sys = argv[argc - 1];
	int i;
	if(argc < 2) {
		printf("usage: %s hostname\n", argv[0]);
		exit(1);
	}
	uname(&uts);
	for(i=1;i<argc;i++) if(*argv[i] == '-') switch(argv[i][1]) {
	case 'l':
		login = 1;
		break;
	case 'd':
		debug = 1;
		break;
	}
	signal(SIGALRM, wakeup);
	signal(SIGTERM, SIG_IGN);
	if(configure(client, 3, getpid()) == -1) exit(1);
	if((i = hostaddr(argv[argc - 1])) == -1) {
		printf("Host '%s' not known\n", argv[argc - 1]);
		exit(1);
	}
	else server[NODE] = i;
	connect(server, sys);
	return(0);
}
/*
 * connect(addr): set up connection to ethernet address 'addr' (system 'sys')
 */
connect(addr, sys)
char *addr, *sys;
{
	register struct request *r = &request;
	char *shell, *user;
	int n;
	struct passwd *pw;
/*
 * send off our login name and uid to remote host.
 * also send timezone.
 */
 	if((user = getlogin()) == NULL) 
 		pw = getpwuid(getuid());
 	else
 		pw = getpwnam(user);
 	if(pw == NULL) {
 		printf("Can't determine your user name!\n");
 		exit(1);
 	}
 	if(*pw->pw_name == 0) {
 		printf("Your user name is NULL! You seem to be %s\n",
 			user ? user : "unknown");
 		exit(1);
 	}
	printf("Trying to connect to %s ... ", ipaddr(addr));
 	fflush(stdout);
	sprintf(r->r_data, "%d %s TERM=%s", 
		pw->pw_uid, pw->pw_name, getenv("TERM"));
	n = strlen(r->r_data);
	send(r, n, REQUEST, addr);
	recv(r);
 	if(r->r_type == TERMINATE) {
 		printf("rejected!\n%s: %s\n", sys, r->r_data);
 		exit(0);
 	}
	memcpy(server, r->r_port.srcaddr, ETHERSIZE);
/*
 * trap signals from here on, so that we can terminate the remote side.
 */
	signal(SIGQUIT, toggle);
	signal(SIGINT, sendsig);
	signal(SIGHUP, die);
	printf("OK\nStarting remote %s ... ", login ? "login" : "shell");
	fflush(stdout);
	if(login) n = sprintf(r->r_data, "login");
 	else {
 		shell = getenv("SHELL");
	 	if(shell == 0) shell = "/bin/sh";
	 	n = sprintf(r->r_data, "%s -i", shell);
	}
 	send(r, n, REQUEST, server);
 	recv(r);
 	if(r->r_type == TERMINATE) {
 		printf("rejected!\n%s: %s\n", sys, r->r_data);
 		exit(0);
 	}
 	printf("OK\nConnection complete ... server is %s\n", ipaddr(server));
/*
 * Set up the termio structures needed for mode swapping
 */
 	ioctl(fileno(stdin), TCGETA, &cooked);
 	ioctl(fileno(stdin), TCGETA, &raw);
 	raw.c_oflag &= ~OPOST;
 	raw.c_cc[VMIN] = 1;
 	raw.c_cc[VTIME] = 0;
 	raw.c_iflag = 0;
 	raw.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL);
 	screenmode = COOKED;
/*
 * fork for reader and writer processes.
 */
	reader = getpid();
 	if(writer = fork()) {
/*
 * If we are starting a remote login via a ptty, then the remote host will
 * echo, so set to raw mode. SIGQUIT will close the connection.
 */
 		if(login) {
 			screenmode = RAW;
			ioctl(fileno(stdin), TCSETA, &raw);
			signal(SIGQUIT, die);
			signal(SIGINT, trap);
		}
		else {
/*
 * Send the remote shell an initial newline, so that the user sees a prompt.
 */
			r->r_data[0] = '\n';
			send(r, 1, DATA, server);
		}
 		while(n = read(fileno(stdin), r->r_data, sizeof r->r_data)) {
 			if(n == -1) {
 				if(errno == EINTR) continue;
 				else break;
 			}
if(debug) {
			printf("client: sent [");
			write(fileno(stdout), r->r_data, n);
			printf("] to %s\n", ipaddr(server));
}
 			send(r, n, DATA, server);
 		}
 		send(r, 0, TERMINATE, server);
 		terminate(writer);
 		ioctl(fileno(stdout), TCSETA, &cooked);
 		exit(0);
 	}
	else {
 		signal(SIGINT, SIG_IGN);
 		signal(SIGQUIT, SIG_IGN);
 		while(1) {
			recv(r);
if(debug) {
 			printf("client: got [");
			write(fileno(stdout), r->r_data, r->r_size);
 			printf("] from %s\n", ipaddr(r->r_port.srcaddr));
}
	 		if(r->r_type == TERMINATE) {
 				printf("Lost remote connection [");
 				fflush(stdout);
 				write(fileno(stdout), r->r_data, r->r_size);
 				printf("]\n");
				kill(reader, SIGTERM);
				ioctl(fileno(stdout), TCSETA, &cooked);
 				exit(0);
	 		}
 			write(fileno(stdout), r->r_data, r->r_size);
 		}
 	}
}
/*
 * toggle: change screen mode from RAW <-> COOKED
 */
toggle(sig){
	static int (*handler)();
	signal(sig, toggle);
	switch(screenmode) {
	case COOKED:
		handler = signal(SIGINT, trap);
		ioctl(fileno(stdin), TCSETA, &raw);
		putchar(07);		/* beep */
		fflush(stdout);
		screenmode = RAW;
		break;
	case RAW:
		if(handler) signal(SIGINT, handler);
		ioctl(fileno(stdin), TCSETA, &cooked);
		printf("[cooked]");
		fflush(stdout);
		screenmode = COOKED;
		break;
	}
}
/*
 * trap interrupts in raw mode, substitute 'del' char
 * We can't just ignore interrupts, otherwise we can't switch out of raw mode.
 */
trap(sig){
	struct request r;
	signal(sig, trap);
	switch(sig) {
	default:
	case SIGINT:
		r.r_data[0] = cooked.c_cc[VINTR];
		break;
	case SIGQUIT:
		r.r_data[0] = cooked.c_cc[VQUIT];
		break;
	}
	send(&r, 1, DATA, server);
}

SHAR_EOF
if test 6204 -ne "`wc -c < 'connect.c'`"
then
	echo shar: "error transmitting 'connect.c'" '(should have been 6204 characters)'
fi
fi
echo shar: "extracting 'rcp.c'" '(5486 characters)'
if test -f 'rcp.c'
then
	echo shar: "will not over-write existing file 'rcp.c'"
else
cat << \SHAR_EOF > 'rcp.c'
/*
 * rcp.c: NOT the Berkeley version.
 *
 * rcp [system!]source ... [system!]dest
 * e.g. rcp a!file1 b!file2 c!/usr/tmp
 *
 * AUTHOR: Dave Settle, THORN EMI SMB Business Software, June 86
 *
 * Electronic address:
 *		dave%smb@ukc
 *		dave@smb.co.uk
 *
 */
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <pwd.h>

#define MAIN
#include "ni.h"
#include "ftp.h"

extern int debug;

char *getenv(), *getlogin();
char *sysname(), *filename(), *lastpart();
struct passwd *getpwuid(), *getpwnam();
struct request request;
time_t time(), interval();
long kbytes();

die(sig){
	struct request req;
	if(sig) printf("\r\n%s: server terminated\n", 
		(sig == SIGINT) ? "Interrupt" : "Quit");
	send(&req, 0, TERMINATE, server);
	exit(1);
}

extern char *panicstr;
panic(sig){
	printf("server has terminated me: %s\n", 
		panicstr ? panicstr : "no reason");
	exit(0);
}

main(argc, argv)
int argc;
char **argv;
{
/*
 * check if the destination is local or remote.
 */
	int i, n, start = 1;
	struct utsname uts;
	char *user;
	struct passwd *pw;
	register struct request *r = &request;
	user = getenv("LOGNAME");
	if(user == NULL) printf("who are you?\n");
/*
 * find out node name
 */
 	uname(&uts);
 	strncpy(localnode, uts.nodename, sizeof uts.nodename);
#ifdef DEBUG
	printf("localnode: %s\n", localnode);
#endif
	hostname = NULL;
	for(i=1;i<argc;i++) {
		if(*argv[i] == '-') {
			switch(argv[i][1]) {
			case 'd':
				debug = 1;
				start = i + 1;
				break;
			}
			continue;
		}
		if(remote(argv[i])) {
			if(hostname && strcmp(hostname, sysname(argv[i]))) {
				printf("Cannot talk to %s AND %s!\n",
					hostname, sysname(argv[i]));
				exit(1);
			}
			else hostname = sysname(argv[i]);
		}
	}
/*
 * If this isn't really a remote copy, subsitute 'cp'
 */
	if(hostname == 0) {
		argv[0] = "cp";
		argv[argc] = 0;
		execv("/bin/cp", argv);
		exit(1);
	}
	if((i = hostaddr(hostname)) == -1) {
		printf("Host '%s' not known\n", hostname);
		exit(1);
	}
	else server[NODE] = i;
	if(configure(client, WINDOW, getpid()) == -1) {
		perror("/dev/ni");
		exit(1);
	}
/*
 * send off our login name and uid to remote host.
 * also send term type.
 */
 	if((user = getlogin()) == NULL) 
 		pw = getpwuid(getuid());
 	else
 		pw = getpwnam(user);
 	if(pw == NULL) {
 		printf("Can't determine your user name!\n");
 		exit(1);
 	}
 	if(*pw->pw_name == 0) {
 		printf("Your user name is NULL! You seem to be %s\n",
 			user ? user : "unknown");
 		exit(1);
 	}
	printf("Trying to connect to %s ... ", ipaddr(server));
 	fflush(stdout);
	sprintf(r->r_data, "%d %s TERM=%s", 
		pw->pw_uid, pw->pw_name, getenv("TERM"));
	n = strlen(r->r_data);
	send(r, n, REQUEST, server);
	recv(r);
 	if(r->r_type != ACCEPT) {
 		printf("rejected!\n%s: %s\n", hostname, r->r_data);
 		exit(0);
 	}
	memcpy(server, r->r_port.srcaddr, ETHERSIZE);
	printf("OK\nStarting remote file server ... "); fflush(stdout);
/*
 * trap signals from here on, so that we can terminate the remote side.
 */
	signal(SIGQUIT, die);
	signal(SIGINT, die);
	signal(SIGHUP, die);
	signal(SIGTERM, panic);
	n = sprintf(r->r_data, "fileserver");
	send(r, n, REQUEST, server);
	recv(r);
	if(r->r_type != ACCEPT) {
		printf("rejected!\n%s: %s\n", hostname, r->r_data);
		exit(1);
	}
	printf("OK\n");
/*
 * Now send (or receive) the files.
 */
	if(remote(argv[argc - 1])) 
		for(i=start;i<(argc - 1);i++) 
			sendit(filename(argv[i]), filename(argv[argc - 1]));
	else
		for(i=start;i<(argc - 1);i++) 
			getit(filename(argv[i]), filename(argv[argc - 1]));
	die(0);	
	/*NOTREACHED*/
}
/*
 * sendit: send local filename to remote server.
 */
sendit(local, remote)
char *local, *remote;
{
	register int n, f;
	register struct request *r = &request;
	time_t start;
	struct stat statb;
	long kb;
	if((f = open(local, 0)) == -1) {
		perror(local);
		return(-1);
	}
	n = sprintf(r->r_data, "%d %s %s", fmode(local), local, remote);
	printf("%s: ", local); fflush(stdout);
	send(r, n, PUTFILE, server);
	recv(r);
	stat(local, &statb);
	start = time((long *) 0);
	kb = kbytes(statb.st_size);
	if(r->r_type == ACCEPT) {
		printf("[sending] "); fflush(stdout);
		if(ftpout(f, server)) 
			printf("OK (%d kb/sec)\n", kb / interval(start));
	}
	else printf("[%s] %s\n", hostname, r->r_data);
	close(f);
	return(0);
}
/*
 * getit: persuade remote host to send a file.
 */
getit(remote, local)
char *local, *remote;
{
	register struct request *r = &request;
	register int n, f;
	int mode;
	char fname[128];
	time_t start;
	struct stat statb;
	long kb;
	int newfile;
	strcpy(fname, local);
	if(stat(local, &statb) != -1) {
		if((statb.st_mode & S_IFMT) == S_IFDIR)
			sprintf(fname, "%s/%s", local, lastpart(remote));
		newfile = 0;
	}
	else newfile = 1;
	if((f = creat(fname, 0666)) == -1) {
		perror(fname);
		return(-1);
	}
	n = sprintf(r->r_data, "%s", remote);
	printf("%s: ", fname); fflush(stdout);
	send(r, n, SENDFILE, server);
	recv(r);
	if(r->r_type == ACCEPT) {
		sscanf(r->r_data, "%d", &mode);
		if(newfile) chmod(fname, mode);
		start = time((long *) 0);
		printf("[receiving] "); fflush(stdout);
		if(ftpin(f, server, &kb))
			printf("OK (%d kb/sec)\n", kbytes(kb) / interval(start));
		else unlink(fname);
		close(f);
		return(0);
	}
	else {
		printf("rejected [%s: %s]\n", hostname, r->r_data);
		close(f);
		return(-1);
	}
}
long kbytes(size)
long size;
{
	size >>= 10;
	return(size ? size : 1);
}
long interval(start)
{
	long i = time((long *) 0) - start;
	return(i ? i : 1);
}
SHAR_EOF
if test 5486 -ne "`wc -c < 'rcp.c'`"
then
	echo shar: "error transmitting 'rcp.c'" '(should have been 5486 characters)'
fi
fi
echo shar: "extracting 'eth.c'" '(3689 characters)'
if test -f 'eth.c'
then
	echo shar: "will not over-write existing file 'eth.c'"
else
cat << \SHAR_EOF > 'eth.c'
/*
 * Copyright (C) 1988 Dave Settle. All rights reserved.
 * Permission is granted to use, copy and modify this software, providing
 * that it is not sold for profit, and that this copyright notice is retained
 * in any copies of the source.
 */
/*
 * eth.c: various useful routines to talk to the ethernet.
 */
#include <sys/types.h> 
#include <sys/errno.h>
#include <stdio.h>

#include "ni.h"

#define MINBUF	64 + 64		/* min 128 bytes data */

char *panicstr;			/* cause (if any) of a SIGTERM */

/*
 * configure the 'ethernet' fd so that we appear as node 'thisnode'
 * Allocate 'nbufs' to cope with expected data.
 */
configure(thisnode, nbufs, pid)
char *thisnode;
{
	if((ethernet = open("/dev/ni", 2)) == -1) {
		perror("/dev/ni");
		return(-1);
	}
	if(ioctl(ethernet, NIGETA, &port)) {
		perror("NIGETA");
		return(-1);
	}
	thisnode[NODE] = port.srcaddr[LSB];
	thisnode[PIDMSB] = (pid & 0xff00) >> 8;
	thisnode[PIDLSB] = pid & 0xff;
#ifdef DEBUG
	printf("Running on node %s\n", ipaddr(port.srcaddr));
#endif
 	port.type = ETHERTYPE;
 	if(nbufs) port.rcvq_sz = nbufs;
	port.rcvb_sz = (sizeof (struct request) + 3) & ~3;
 	memcpy(port.srcaddr, thisnode, ETHERSIZE);
 	port.protocol = PROTOCOL;
 	if(ioctl(ethernet, NISETA, &port)) {
 		perror("NISETA");
 		return(-1);
 	}
#ifdef DEBUG
 	printf("Port configured as node %s\n", ipaddr(thisnode));
#endif
 	memcpy(mynode, thisnode, ETHERSIZE);
 	return(0);
}
/*
 * recv: receive a request from the ethernet
 * If it's a request to terminate, propagate a SIGTERM signal.
 * This means that users of this routine can decide on what action to take,
 * and also be sure that no TERMINATE packets will not be detected.
 */
recv(r)
register struct request *r;
{
	if(read(ethernet, r, sizeof (struct request)) == -1) {
		r->r_type = UNDEFINED;
		r->r_size = 0;
		return(-1);
	}
	if(r->r_type == TERMINATE) {
		panicstr = r->r_data;
		skill(getpid(), SIGTERM);
	}
	
	return(0);
}
/*
 * send request to specified node
 */
send(r, size, type, node)
struct request *r;
char *node;
{
	int total, min = sizeof (EI_PORT) + 52;
	memcpy(r->r_port.srcaddr, mynode, ETHERSIZE);
	memcpy(r->r_port.destaddr, node, ETHERSIZE);
	memcpy(r->r_port.ptype, myprotocol, 2);
	r->r_type = type;
	r->r_size = size;
	total = sizeof (struct request) - ((sizeof r->r_data) - size);
	total = total > min ? total : min;
	if(write(ethernet, r, total) == -1) {
		perror("write error");
		printf("Packet: src %s ", ipaddr(r->r_port.srcaddr));
		printf("dest %s\n", ipaddr(r->r_port.destaddr));
		return(-1);
	}
	return(0);
}
/*
 * perror
 */
extern char *sys_errlist[];
extern int errno;
extern int sys_nerr;

char *ni_error[] = {
	"Bad network address",
	"Bad port configuration specification",
	"Port not configured",
	"Open failure - out of memory?",
	"HLP circuit failure",
	"HLP fault",
	"Port not available",
	"Network not available",
	"Driver fault",
	"Hardware failure",
	"Network fault",
	"Bad packet",
	"Device has been reset"
};

perror(msg)
char *msg;
{
	char *e;
	if((errno > 200) && (errno < 213)) e = ni_error[errno - 200];
	else e = sys_errlist[errno];
	fprintf(stderr, "%s: %s [%d]\n", msg, e, errno);
}
/*
 * used only with 'sdb'
 */
dump(s)
char *s;
{
	printf("%s\n", ipaddr(s));
}
hostaddr(host)
char *host;
{
	FILE *map;
	char name[32];
	int id;
	if((map = fopen(MAP, "r")) == NULL) return(0);
	while(fscanf(map, "%s%x", name, &id) != EOF) 
		if(!strcmp(name, host)) {
			fclose(map);
			return(id);
		}
	fclose(map);
	return(-1);
}
/*
 * skill: 'safe' kill. There's a bug somewhere when we kill proc 0 and log
 * everyone out. This is a wrokaround.
 */
skill(proc, sig){
	if(proc < 1) {
		errno = EINVAL;
		return(-1);
	}
	return(kill(proc, sig));
}

SHAR_EOF
if test 3689 -ne "`wc -c < 'eth.c'`"
then
	echo shar: "error transmitting 'eth.c'" '(should have been 3689 characters)'
fi
fi
echo shar: "extracting 'ipaddr.c'" '(466 characters)'
if test -f 'ipaddr.c'
then
	echo shar: "will not over-write existing file 'ipaddr.c'"
else
cat << \SHAR_EOF > 'ipaddr.c'
/*
 * Copyright (C) 1988 Dave Settle. All rights reserved.
 * Permission is granted to use, copy and modify this software, providing
 * that it is not sold for profit, and that this copyright notice is retained
 * in any copies of the source.
 */
/*
 * ipaddr: print address as xx.xx.xx.xx.xx.xx
 */
char *ipaddr(a)
char *a;
{
	register int i;
	static char s[26];
	for(i=0;i<5;i++) sprintf(s + (i * 3), "%02x.", a[i]);
	sprintf(s + 15, "%02x", a[5]);
	return(s);
}

SHAR_EOF
if test 466 -ne "`wc -c < 'ipaddr.c'`"
then
	echo shar: "error transmitting 'ipaddr.c'" '(should have been 466 characters)'
fi
fi
echo shar: "extracting 'talk.c'" '(1303 characters)'
if test -f 'talk.c'
then
	echo shar: "will not over-write existing file 'talk.c'"
else
cat << \SHAR_EOF > 'talk.c'
/*
 * talk.c: routines to talk to the remote shell, run on the remote system
 */
#include "ni.h"


passon(r, output)
register struct request *r;	/* stuff to send		*/
int output;			/* file descriptor to write on	*/
{
	int ok = 1;
	switch(r->r_type) {
	case TERMINATE:
		ok = 0;
		break;
	case RMTSIGINT:
		kill(0, SIGINT);
		break;
	case RMTSIGQUIT:
		kill(0, SIGQUIT);
		break;
	case DATA:
		write(output, r->r_data, r->r_size);
		break;
	default:
		sprintf(r->r_data, "Bad packet type %d\n", r->r_type);
			send(r, strlen(r->r_data), DATA, client);
	}
	return(ok);
}
#define PTTY "/dev/ptc"
#define MAXPTTYS 8
/*
 * special case routine - use the PC-Interface pseudo-ttys to get a real
 * remote login!
 */
dologin(r)
register struct request *r;
{
	int dev;			/* file descriptor	*/
	int i, n, reader;
	char name[sizeof PTTY + 4];
	for(i=0;i<MAXPTTYS;i++) {
		sprintf(name, "%s%02d", PTTY, i);
		if((dev = open(name, 2)) != -1) break;
	}
	if(dev == -1) {
		n = sprintf(r->r_data, "No PC-Interface ports available");
		send(r, n, TERMINATE, client);
		exit(1);
	}
	if(reader = fork()) {
		while(1) {
			recv(r);
			if(passon(r, dev) == 0) {
				skill(reader, SIGTERM);
				exit(0);
			}
		}
	}
	else {
		while(1) {
			n = read(dev, r->r_data, sizeof r->r_data);
			send(r, n, DATA, client);
		}
	}
}

		
SHAR_EOF
if test 1303 -ne "`wc -c < 'talk.c'`"
then
	echo shar: "error transmitting 'talk.c'" '(should have been 1303 characters)'
fi
fi
echo shar: "extracting 'sub.c'" '(1703 characters)'
if test -f 'sub.c'
then
	echo shar: "will not over-write existing file 'sub.c'"
else
cat << \SHAR_EOF > 'sub.c'
/*
 * sub.c: general subroutines for "rcp"
 * remote(s): check if "s" refers to a local(0) or remote(1) file.
 * sysname(s): return systemname assosciated with s. "localnode" returned
 * 		if there is no specific "sys!" prefix.
 * filename(s): return filname part - strip first system prefix, if there is one
 * 
 * fmode(s): return value of file mode.
 *
 * lastpart(f): return last entry of path 'f'
 */

#define rindex strrchr
#define index strchr

char *filename(), *sysname(), *lastpart(), *malloc(), 
	*strrchr(), *strchr();
extern char *sys_errlist[];
#include "errno.h"
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "ni.h"
long time();
char *asctime();
struct tm *localtime();

remote(s)
char *s;
{
	if(filename(s) == s) return(0);
	else return(1);
}
/*
 * sysname(f): get system name part of file name.
 * if no system present, return localnode
 */
char *sysname(f)
char *f;
{
	register char *p, *sys;
	register int length;
	if((p = filename(f)) == f) return(localnode);
	else {
		sys = malloc(length = p - f);
		strncpy(sys, f, length - 1);
		sys[length - 1] = 0;
		return(sys);
	}
}
/*
 * filename(f): return file name part of f
 * Strips the first (and only the first) system, if there is one.
 * [8] Also allow ':' as system flag.
 */
char *filename(f)
register char *f;
{
	register char *s;
	if((s = index(f, '!')) || (s = index(f, ':'))) return(++s);
	else return(f);
}
bang(c)
char c;
{
	if((c == '!') || (c == ':')) return(1);
	else return(0);
}

fmode(f)
char *f;
{
	struct stat s;
	if(stat(f, &s) == -1) return(0664);
	return(s.st_mode);
}

char *lastpart(f)
char *f;
{
	char *l;
	if(l = rindex(f, '/')) return(++l);
	else return(f);
}
	
SHAR_EOF
if test 1703 -ne "`wc -c < 'sub.c'`"
then
	echo shar: "error transmitting 'sub.c'" '(should have been 1703 characters)'
fi
fi
echo shar: "extracting 'file.c'" '(2159 characters)'
if test -f 'file.c'
then
	echo shar: "will not over-write existing file 'file.c'"
else
cat << \SHAR_EOF > 'file.c'
/*
 * file.c: server end of the "rcp" function.
 * Send or get files. Relies on the transport interface to detect errors,
 * only checks sequence numbers to identify lost packets.
 * If a packet is lost, then the transfer is abandoned and restarted - this
 * is not expected to happen often.
 */
#include <sys/types.h>
#include <sys/stat.h>

#include "ni.h"

#include "ftp.h"

extern char *sys_errlist[];
extern int errno;

char *lastpart();

fileserver(r)
register struct request *r;
{
	register int f, n;
	int mode, newfile;
	long kb;
	struct stat statb;
	char fname[128], local[128], remote[128];
/*
 * re-configure the ethernet port to handle sufficient buffers
 */
	close(ethernet);
	configure(server, WINDOW, getpid());
	send(r, 0, ACCEPT, client);
	signal(SIGTERM, SIG_DFL);		/* TERMINATE packet kills */
	while(1) {
		recv(r);			/* get request */
		switch(r->r_type) {
		case TERMINATE:
			exit(1);
			break;
		case SENDFILE:
			strncpy(fname, r->r_data, r->r_size);
			fname[r->r_size] = 0;
			if((f = open(fname, 0)) == -1) {
				n = sprintf(r->r_data, "%s: %s", fname,
					sys_errlist[errno]);
				send(r, n, REJECT, client);
			}
			else {
				stat(fname, &statb);
				n = sprintf(r->r_data, "%d", statb.st_mode & 0777);
				send(r, n, ACCEPT, client);
				ftpout(f, client);
				close(f);
			}
			break;
		case PUTFILE:
			n = sscanf(r->r_data,  "%d %s %s", 
					&mode,  remote, local);
			if(n != 3) {
				n = sprintf(r->r_data, "Illegal request parameters");
				send(r, n, REJECT, client);
				break;
			}
			strcpy(fname, local);
			if(stat(local, &statb) != -1) {
				if((statb.st_mode & S_IFMT) == S_IFDIR)
					sprintf(fname, "%s/%s", local, 
						lastpart(remote));
				newfile = 0;
			}
			else newfile = 1;
			if((f = creat(fname, 0666)) == -1) {
				n = sprintf(r->r_data, "%s: %s", fname,
					sys_errlist[errno]);
				send(r, n, REJECT, client);
			}
			else {
				if(newfile) chmod(fname, mode);
				send(r, 0, ACCEPT, client);
				if(!ftpin(f, client, &kb)) unlink(fname);
				close(f);
			}
			break;
		default:
			n = sprintf(r->r_data, "Illegal request function %d", 
					r->r_type);
			send(r, n, REJECT, client);
		}
	}
}
SHAR_EOF
if test 2159 -ne "`wc -c < 'file.c'`"
then
	echo shar: "error transmitting 'file.c'" '(should have been 2159 characters)'
fi
fi
echo shar: "extracting 'terminate.c'" '(512 characters)'
if test -f 'terminate.c'
then
	echo shar: "will not over-write existing file 'terminate.c'"
else
cat << \SHAR_EOF > 'terminate.c'
#include <sys/signal.h>

#define GRACETIME 1
/*
 * terminate: log out the child shell. Sends SIGHUP, SIGTERM, SIGKILL
 * until the child exits.
 */
terminate(child)
{
	int status;
	if(child < 1) return(0);
	kill(child, SIGHUP);
	alarm(GRACETIME);
	if(wait(&status) == -1) {
		kill(child, SIGTERM);
		alarm(GRACETIME);
		if(wait(&status) == -1) {
			kill(child, SIGKILL);
			alarm(GRACETIME);
			if(wait(&status) == -1) {
				printf("Cannot kill child pid %d\n", child);
				status = 0;
			}
		}
	}
	alarm(0);
}

SHAR_EOF
if test 512 -ne "`wc -c < 'terminate.c'`"
then
	echo shar: "error transmitting 'terminate.c'" '(should have been 512 characters)'
fi
fi
echo shar: "extracting 'mynode.c'" '(627 characters)'
if test -f 'mynode.c'
then
	echo shar: "will not over-write existing file 'mynode.c'"
else
cat << \SHAR_EOF > 'mynode.c'
/*
 * Copyright (C) 1988 Dave Settle. All rights reserved.
 * Permission is granted to use, copy and modify this software, providing
 * that it is not sold for profit, and that this copyright notice is retained
 * in any copies of the source.
 */
/*
 * mynode.c: print physical ethernet address of the local node.
 */
 
#include <sys/ni.h>

main(){
	NI_PORT port;
	int ethernet;
	if((ethernet = open("/dev/ni", 2)) == -1) {
		perror("/dev/ni");
		exit(1);
	}
	if(ioctl(ethernet, NIGETA, &port)) {
		perror("NIGETA");
		exit(1);
	}
	printf("Physical ethernet address: %s\n", ipaddr(port.srcaddr));
	close(ethernet);
	exit(0);
}
SHAR_EOF
if test 627 -ne "`wc -c < 'mynode.c'`"
then
	echo shar: "error transmitting 'mynode.c'" '(should have been 627 characters)'
fi
fi
echo shar: "extracting 'ftp.c'" '(7077 characters)'
if test -f 'ftp.c'
then
	echo shar: "will not over-write existing file 'ftp.c'"
else
cat << \SHAR_EOF > 'ftp.c'
/*
 * ftp.c: the 'file transfer' protocols.
 * Sliding-window protocol with selective retransmit.
 */
#include "ni.h"

#include "ftp.h"

extern char *sys_errlist[];
int debug;

#define NULL 0
#define slotno(num) ((num) % WINDOW)

struct request buffer[WINDOW];
struct request *packet[WINDOW];
int retry = 0;
sequence_t lower, upper, num;

wakeup(){
	signal(SIGALRM, wakeup);
} 
/*
 * ftpout: send the file to the indicated host.
 */
ftpout(f, addr)
char *addr;
{
	struct request ackreq, *ack = &ackreq;
	register struct request *r;
	register int n;
	int i, eof = 0;
	lower = upper = num = 0;
	retry = 0;
	for(i=0;i<WINDOW;i++) packet[i] = NULL;
	wakeup();
	while(1) {
/*
 * If the window is not sent, send another packet.
 * If last packet was null, then all data has been sent.
 */
		while(!fullwindow(lower, upper) && !eof) {
 			num = upper++;
 			r = &buffer[slotno(num)];
 			packet[slotno(num)] = r;
			r->r_sequence = num;
			n = read(f, r->r_data, sizeof r->r_data);
/*
 * read error causes premature EOF
 */
			if(n == -1) n = 0;
			if(debug) 
				printf("Sending packet %d\n", r->r_sequence);
/*
 * Reset the counter for ack timeouts, then send out the block
 */
			retry = 0;
			send(r, n, DATA, addr);
			if(n == 0) eof = 1;
		}
/* 
 * We have to wait for an ACK from the other end ...
 * If it's an ACK, then the sequence field determines which packet is OK
 * If it's a NACK, then the data is a list of packets which 
 * must have got lost somewhere. so we re-transmit them.
 */
		if(debug) printf("Window full - waiting for ack\n");
 		alarm(TRANSIT);
		recv(ack);
		alarm(0);
		switch(ack->r_type) {
/*
 * ACK - mark the packet as sent (delete pointer).
 * Move lower bound of window up as far as possible.
 */
		case ACCEPT:
			if(debug)
				printf("Got ack on packet %d\n", ack->r_sequence);
			release(ack->r_sequence);
			break;
		case REJECT:
			if(debug)
				printf("Got reject (ack packet %d)\n", ack->r_sequence);
			release(ack->r_sequence);
			for(i=0;i<ack->r_size;i++) {
				if(debug) 
					printf("\tpacket %d\n", ack->r_data[i]);
				r = packet[slotno(ack->r_data[i])];
				if(inwindow(ack->r_data[i], lower, upper))
					send(r, r->r_size, DATA, addr);
			}
			break;
/*
 * Timeout waiting for ACK - retransmit all the outstanding packets.
 */
		case UNDEFINED:
			printf("Timeout\n");
			if(retry++ > MAXRETRY) {
				printf("Too many timeouts - ftp abort\n");
				n = sprintf(ack->r_data, "Too many timeouts");
				send(ack, n, TERMINATE, addr);
				exit(0);
			}
			for(i=0;i<WINDOW;i++) if(packet[i])
				send(packet[i], packet[i]->r_size, DATA, addr);
			break;
 		default:
			printf("Illegal ack type %d\n", ack->r_type);
		} /* end switch */
/*
 * If we've transmitted all the data, and got acks back for all the packets
 * then the transfer is complete.
 */
 		if(eof && debug) 
 			printf("EOF - waiting to clear %d - %d\n", lower, upper);
		if(eof && (upper == lower)) return(1);
	} /* end data loop */
}
/*
 * ftpin: recv data, plonk in file.
 * Packet with data size 0 ends file transmission.
 */
ftpin(f, addr, kb)
char *addr;
long *kb;			/* data size received */
{
	register struct request *r;
	long count = 0;
	int i, eof = 0;
	struct request ackreq, *ack = &ackreq;
	register int n;
	sequence_t slot, datablock, last, diskblock = 0;
	lower = num = 0;
	upper = WINDOW - 1;
	datablock = 0;
	for(i=0;i<WINDOW;i++) packet[i] = NULL;
	while(1) {
/*
 * Zero received buffer space, so we know what has got here
 */
		retry = 0;
		if(debug) 
			printf("Waiting for %d - %d, using buffer %d\n", 
				lower, lower + WINDOW, slotno(datablock));
		r = &buffer[slotno(datablock++)];	/* get a buffer */
		recv(r);
		switch(r->r_type) {
/*
 * Normal data - fix in the correct place
 * If we missed packets in the sequence, ask for them again.
 */
		case DATA:
			upper = lower + WINDOW - 1;
			if(debug) printf("Received packet %d, size %d\n", 
				r->r_sequence, r->r_size);
/*
 * incoming packet not within our current window. The ack for a previous
 * packet must have got lost - send it again!
 * If the packet doesn't seem to fit anywhere, complain!
 */
			if(!inwindow(r->r_sequence, lower, upper)) {
				if(inwindow(r->r_sequence, lower - WINDOW, lower)) {
					ack->r_sequence = r->r_sequence;
					send(ack, 0, ACCEPT, addr);
				}
				else {
					if(debug) printf("packet %d out of bounds %d - %d\n",
					r->r_sequence, lower, upper);
					n = sprintf(ack->r_data, "protocol violation: packet %d arrived in window %d - %d\n",
						r->r_sequence, lower, upper);
					send(ack, n, TERMINATE, addr);
					exit(0);
				}
				datablock--;
				continue;		/* out of window */
			}
			slot = slotno(r->r_sequence);
			if(packet[slot]) {
				if(debug) printf("Duplicate - ignored\n");
				datablock--;
				continue;	/* duplicate */
			}
			packet[slot] = r;
			count += r->r_size;
/*
 * Check for eof, and send ack for this packet.
 */
			if(r->r_size == 0) {
				eof = 1;
				last = r->r_sequence;
			}
			ack->r_sequence = r->r_sequence;
			send(ack, 0, ACCEPT, addr);
/*
 * Write as many packets as possible.
 */
 			while(r = packet[slotno(lower)]) {
 				if(debug) 
					printf("Packet %d written to disk\n", r->r_sequence);
				if(r->r_sequence != diskblock++) {
					printf("Bug detected! data block %d written as disk block %d\n",
						r->r_sequence, datablock - 1);
					n = sprintf(ack->r_data, "data block sequence error");
					send(ack, n, TERMINATE, addr);
					exit(1);
				}
 				if(write(f, r->r_data, r->r_size) != r_size) {
 					perror("write error");
 					n = sprintf(ack->r_data, "write error: %s",
 						sys_errlist[errno]);
 					send(ack, n, TERMINATE, addr);
 					exit(1);
 				}
 				packet[slotno(lower)] = NULL;
 				lower++;
 			}
			break;
		default:
			printf("Illegal ftp packet %d\n", r->r_type);
		}	/* end switch */
		if(eof && (lower == (last + 1))) {
			*kb = count;
			return(1);
		}
	} /* end data loop */
}
/*
 * Check if 'num' falls between lbound and ubound
 */
inwindow(number, lbound, ubound)
sequence_t number, lbound, ubound;
{
	while(1) {
		if(number == lbound) return(1);
		if(lbound == ubound) return(0);
		else lbound++;
	}
}
/*
 * retransmit: we were looking for packet 'last', but got packet 'this'
 * Ask for retransmit of intervening packets - this also acks packet 'this'
 */
retransmit(last, this, ack, addr)
sequence_t last, this;
register struct request *ack;
char *addr;
{
	int j, n = 0;
	for(j=0;j<WINDOW;j++) if(packet[j] == NULL)
		ack->r_data[n++] = (sequence_t) lower + j;
	ack->r_sequence = this;
	send(ack, n, REJECT, addr);
}
/*
 * release: mark packet 'n' as being succesfully received.
 * Move lower bound of window up, if possible.
 */
release(n)
sequence_t n;
{
	packet[slotno(n)] = NULL;
	while(packet[slotno(lower)] == NULL) {
		lower++;
		if(lower == upper) break;
	}
	if(debug) printf("Packet %d released - lbound now %d\n", n, lower);
}
/*
 * fullwindow: if distance between lower and upper is >= WINDOW
 */
fullwindow(lower, upper)
register sequence_t lower, upper;
{
	register int n = 0;
	while(lower++ != upper) n++;
	if(n >= WINDOW) return(1);
	return(0);
}
SHAR_EOF
if test 7077 -ne "`wc -c < 'ftp.c'`"
then
	echo shar: "error transmitting 'ftp.c'" '(should have been 7077 characters)'
fi
fi
echo shar: "extracting 'ni.h'" '(1591 characters)'
if test -f 'ni.h'
then
	echo shar: "will not over-write existing file 'ni.h'"
else
cat << \SHAR_EOF > 'ni.h'
#include <sys/ni.h>
#include <sys/signal.h>

#ifdef MAIN
#define EXTERN
#else
#define EXTERN extern
#endif

#define MAP "/usr/lib/ethernet.addr"

EXTERN NI_PORT port;
EXTERN int debug;
EXTERN char *hostname, localnode[16];

#define ETHERSIZE 6

typedef char address_t[];
typedef unsigned char sequence_t;

#define PORT 4			/* plonk port id here */
#define NODE 5			/* plonk lsb node address */
#define LSB 5			/* lsb of node address */
#define PIDMSB 3
#define PIDLSB 4

EXTERN int ethernet;



#ifdef MAIN
char server[] = {0x0f, 0x02, 0x02, 0, 0, NODE};
#else
extern char server[];
#endif


#ifdef MAIN
char client[] = {0x0f, 0x02, 0x02, PIDMSB, PIDLSB, NODE};
#else
extern char client[];
#endif

#define PROTOCOL 0x5656


#ifdef MAIN
char myprotocol[] = {0x56, 0x56};
#else
extern char myprotocol[];
#endif


#ifdef MAIN
char mynode[] = {0,0,0,0,0,0};
#else
extern char mynode[];
#endif

char *ipaddr(), *getenv();

struct request {
	EI_PORT r_port;			/* Ethernet header */
	int r_size;			/* size of data field */
	char r_type;			/* type of request */
	unsigned char r_sequence;	/* Sequence number */
	char r_data[1024];		/* data field */
};

#define DATA 0			/* just data of some sort */
#define UNDEFINED 1		/* crappy packet */
#define REQUEST 2		/* Is this necessary? */
#define TERMINATE 3		/* STOP! Exit ASAP */
#define RMTSIGINT 4		/* signal from here to there */
#define RMTSIGQUIT 5		/* another signal */
#define ACCEPT 6		/* I accept that file */
#define REJECT 7		/* I have rejected that file */
#define PUTFILE 8		/* file to server */
#define SENDFILE 9		/* file from server */
SHAR_EOF
if test 1591 -ne "`wc -c < 'ni.h'`"
then
	echo shar: "error transmitting 'ni.h'" '(should have been 1591 characters)'
fi
fi
echo shar: "extracting 'ftp.h'" '(192 characters)'
if test -f 'ftp.h'
then
	echo shar: "will not over-write existing file 'ftp.h'"
else
cat << \SHAR_EOF > 'ftp.h'
/*
 * Some constants for the ftp
 */
#define WINDOW	4		/* Window size */
#define TRANSIT 2		/* Estimated transit time (sec) for window */
#define MAXRETRY 5		/* No of timeouts per window */


SHAR_EOF
if test 192 -ne "`wc -c < 'ftp.h'`"
then
	echo shar: "error transmitting 'ftp.h'" '(should have been 192 characters)'
fi
fi
exit 0
#	End of shell archive

--

Dave Settle, 
	SMB Business Software, Thorn EMI Datasolve, High St, Mansfield, UK

[Until 1 May] UUCP:	dave@smb.co.uk
			...!mcvax!ukc!nott-cs!smb!dave	

	
[After 1 May] UUCP:	(to be announced ...)


	<--- This way to point of view --->

-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.