tftp/tftpd - much newer versions

Steven M. Schultz sms at wlv.imsd.contel.com
Fri May 17 03:48:41 AEST 1991


	In the course of experimenting last night i noticed that
	the version of tftp/tftpd in 2.10.1BSD and 2.11BSD was
	incredibly ancient and lacked the security features of
	later versions.

	This is a later version with the few remaining portability
	problems fixed.

	Remove /usr/src/etc/tftpd and /usr/src/ucb/tftp and unshar
	this version, recompile and install.

	Steven

#! /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:
#	/usr/src/etc/tftpd
#	/usr/src/ucb/tftp
# This archive created: Thu May 16 10:30:32 1991
export PATH; PATH=/bin:/usr/bin:$PATH
if test ! -d '/usr/src/etc/tftpd'
then
	mkdir '/usr/src/etc/tftpd'
fi
cd '/usr/src/etc/tftpd'
if test -f 'tftpsubs.c'
then
	echo shar: "will not over-write existing file 'tftpsubs.c'"
else
sed 's/^X//' << \SHAR_EOF > 'tftpsubs.c'
X/*
X * Copyright (c) 1983 Regents of the University of California.
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms are permitted
X * provided that: (1) source distributions retain this entire copyright
X * notice and comment, and (2) distributions including binaries display
X * the following acknowledgement:  ``This product includes software
X * developed by the University of California, Berkeley and its contributors''
X * in the documentation or other materials provided with the distribution
X * and in all advertising materials mentioning features or use of this
X * software. Neither the name of the University nor the names of its
X * contributors may be used to endorse or promote products derived
X * from this software without specific prior written permission.
X * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
X * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
X * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#)tftpsubs.c	5.5 (Berkeley) 6/1/90";
X#endif /* not lint */
X
X/* Simple minded read-ahead/write-behind subroutines for tftp user and
X   server.  Written originally with multiple buffers in mind, but current
X   implementation has two buffer logic wired in.
X
X   Todo:  add some sort of final error check so when the write-buffer
X   is finally flushed, the caller can detect if the disk filled up
X   (or had an i/o error) and return a nak to the other side.
X
X			Jim Guyton 10/85
X */
X
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <sys/ioctl.h>
X#include <netinet/in.h>
X#include <arpa/tftp.h>
X#include <stdio.h>
X
X#define PKTSIZE SEGSIZE+4       /* should be moved to tftp.h */
X
Xstruct bf {
X	int counter;            /* size of data in buffer, or flag */
X	char buf[PKTSIZE];      /* room for data packet */
X} bfs[2];
X
X				/* Values for bf.counter  */
X#define BF_ALLOC -3             /* alloc'd but not yet filled */
X#define BF_FREE  -2             /* free */
X/* [-1 .. SEGSIZE] = size of data in the data buffer */
X
Xstatic int nextone;     /* index of next buffer to use */
Xstatic int current;     /* index of buffer in use */
X
X			/* control flags for crlf conversions */
Xint newline = 0;        /* fillbuf: in middle of newline expansion */
Xint prevchar = -1;      /* putbuf: previous char (cr check) */
X
Xstruct tftphdr *rw_init();
X
Xstruct tftphdr *w_init() { return rw_init(0); }         /* write-behind */
Xstruct tftphdr *r_init() { return rw_init(1); }         /* read-ahead */
X
Xstruct tftphdr *
Xrw_init(x)              /* init for either read-ahead or write-behind */
Xint x;                  /* zero for write-behind, one for read-head */
X{
X	newline = 0;            /* init crlf flag */
X	prevchar = -1;
X	bfs[0].counter =  BF_ALLOC;     /* pass out the first buffer */
X	current = 0;
X	bfs[1].counter = BF_FREE;
X	nextone = x;                    /* ahead or behind? */
X	return (struct tftphdr *)bfs[0].buf;
X}
X
X
X/* Have emptied current buffer by sending to net and getting ack.
X   Free it and return next buffer filled with data.
X */
Xreadit(file, dpp, convert)
X	FILE *file;                     /* file opened for read */
X	struct tftphdr **dpp;
X	int convert;                    /* if true, convert to ascii */
X{
X	struct bf *b;
X
X	bfs[current].counter = BF_FREE; /* free old one */
X	current = !current;             /* "incr" current */
X
X	b = &bfs[current];              /* look at new buffer */
X	if (b->counter == BF_FREE)      /* if it's empty */
X		read_ahead(file, convert);      /* fill it */
X/*      assert(b->counter != BF_FREE);  /* check */
X	*dpp = (struct tftphdr *)b->buf;        /* set caller's ptr */
X	return b->counter;
X}
X
X/*
X * fill the input buffer, doing ascii conversions if requested
X * conversions are  lf -> cr,lf  and cr -> cr, nul
X */
Xread_ahead(file, convert)
X	FILE *file;                     /* file opened for read */
X	int convert;                    /* if true, convert to ascii */
X{
X	register int i;
X	register char *p;
X	register int c;
X	struct bf *b;
X	struct tftphdr *dp;
X
X	b = &bfs[nextone];              /* look at "next" buffer */
X	if (b->counter != BF_FREE)      /* nop if not free */
X		return;
X	nextone = !nextone;             /* "incr" next buffer ptr */
X
X	dp = (struct tftphdr *)b->buf;
X
X	if (convert == 0) {
X		b->counter = read(fileno(file), dp->th_data, SEGSIZE);
X		return;
X	}
X
X	p = dp->th_data;
X	for (i = 0 ; i < SEGSIZE; i++) {
X		if (newline) {
X			if (prevchar == '\n')
X				c = '\n';       /* lf to cr,lf */
X			else    c = '\0';       /* cr to cr,nul */
X			newline = 0;
X		}
X		else {
X			c = getc(file);
X			if (c == EOF) break;
X			if (c == '\n' || c == '\r') {
X				prevchar = c;
X				c = '\r';
X				newline = 1;
X			}
X		}
X	       *p++ = c;
X	}
X	b->counter = (int)(p - dp->th_data);
X}
X
X/* Update count associated with the buffer, get new buffer
X   from the queue.  Calls write_behind only if next buffer not
X   available.
X */
Xwriteit(file, dpp, ct, convert)
X	FILE *file;
X	struct tftphdr **dpp;
X	int convert;
X{
X	bfs[current].counter = ct;      /* set size of data to write */
X	current = !current;             /* switch to other buffer */
X	if (bfs[current].counter != BF_FREE)     /* if not free */
X		write_behind(file, convert);     /* flush it */
X	bfs[current].counter = BF_ALLOC;        /* mark as alloc'd */
X	*dpp =  (struct tftphdr *)bfs[current].buf;
X	return ct;                      /* this is a lie of course */
X}
X
X/*
X * Output a buffer to a file, converting from netascii if requested.
X * CR,NUL -> CR  and CR,LF => LF.
X * Note spec is undefined if we get CR as last byte of file or a
X * CR followed by anything else.  In this case we leave it alone.
X */
Xwrite_behind(file, convert)
X	FILE *file;
X	int convert;
X{
X	char *buf;
X	int count;
X	register int ct;
X	register char *p;
X	register int c;                 /* current character */
X	struct bf *b;
X	struct tftphdr *dp;
X
X	b = &bfs[nextone];
X	if (b->counter < -1)            /* anything to flush? */
X		return 0;               /* just nop if nothing to do */
X
X	count = b->counter;             /* remember byte count */
X	b->counter = BF_FREE;           /* reset flag */
X	dp = (struct tftphdr *)b->buf;
X	nextone = !nextone;             /* incr for next time */
X	buf = dp->th_data;
X
X	if (count <= 0) return -1;      /* nak logic? */
X
X	if (convert == 0)
X		return write(fileno(file), buf, count);
X
X	p = buf;
X	ct = count;
X	while (ct--) {                  /* loop over the buffer */
X	    c = *p++;                   /* pick up a character */
X	    if (prevchar == '\r') {     /* if prev char was cr */
X		if (c == '\n')          /* if have cr,lf then just */
X		   fseek(file, -1L, 1);  /* smash lf on top of the cr */
X		else
X		   if (c == '\0')       /* if have cr,nul then */
X			goto skipit;    /* just skip over the putc */
X		/* else just fall through and allow it */
X	    }
X	    putc(c, file);
Xskipit:
X	    prevchar = c;
X	}
X	return count;
X}
X
X
X/* When an error has occurred, it is possible that the two sides
X * are out of synch.  Ie: that what I think is the other side's
X * response to packet N is really their response to packet N-1.
X *
X * So, to try to prevent that, we flush all the input queued up
X * for us on the network connection on our host.
X *
X * We return the number of packets we flushed (mostly for reporting
X * when trace is active).
X */
X
Xint
Xsynchnet(f)
Xint	f;		/* socket to flush */
X{
X	int i, j = 0;
X	char rbuf[PKTSIZE];
X	struct sockaddr_in from;
X	int fromlen;
X
X	while (1) {
X		(void) ioctl(f, FIONREAD, &i);
X		if (i) {
X			j++;
X			fromlen = sizeof from;
X			(void) recvfrom(f, rbuf, sizeof (rbuf), 0,
X				(caddr_t)&from, &fromlen);
X		} else {
X			return(j);
X		}
X	}
X}
SHAR_EOF
fi
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
sed 's/^X//' << \SHAR_EOF > 'Makefile'
X#
X# Copyright (c) 1983 Regents of the University of California.
X# All rights reserved.  The Berkeley software License Agreement
X# specifies the terms and conditions for redistribution.
X#
X#	@(#)Makefile	1.2 (Berkeley) 2/7/86
X#
X
XALL=	tftpd.o tftpsubs.o
XDESTDIR=
XCFLAGS=	-O
XSEPFLAG= -i
XLDFLAGS=
X
Xtftpd:	${ALL}
X	${CC} ${LDFLAGS} ${SEPFLAG} -o tftpd ${ALL}
X
Xinstall:
X	install -s tftpd ${DESTDIR}/etc/tftpd
X
Xclean:
X	rm -f tftpd *.o *.s errs core a.out t.?
SHAR_EOF
fi
if test -f 'tftpd.c'
then
	echo shar: "will not over-write existing file 'tftpd.c'"
else
sed 's/^X//' << \SHAR_EOF > 'tftpd.c'
X/*
X * Copyright (c) 1983 Regents of the University of California.
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms are permitted
X * provided that: (1) source distributions retain this entire copyright
X * notice and comment, and (2) distributions including binaries display
X * the following acknowledgement:  ``This product includes software
X * developed by the University of California, Berkeley and its contributors''
X * in the documentation or other materials provided with the distribution
X * and in all advertising materials mentioning features or use of this
X * software. Neither the name of the University nor the names of its
X * contributors may be used to endorse or promote products derived
X * from this software without specific prior written permission.
X * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
X * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
X * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X */
X
X#ifndef lint
Xchar copyright[] =
X"@(#) Copyright (c) 1983 Regents of the University of California.\n\
X All rights reserved.\n";
X#endif /* not lint */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#)tftpd.c	5.12 (Berkeley) 6/1/90";
X#endif /* not lint */
X
X#ifdef	BSD2_10
X#define	timeoutbuf	to_buf
X#endif
X
X/*
X * Trivial file transfer protocol server.
X *
X * This version includes many modifications by Jim Guyton <guyton at rand-unix>
X */
X
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <sys/ioctl.h>
X#include <sys/wait.h>
X#include <sys/stat.h>
X#include <sys/signal.h>
X
X#include <netinet/in.h>
X
X#include <arpa/tftp.h>
X
X#include <netdb.h>
X#include <setjmp.h>
X#include <stdio.h>
X#include <errno.h>
X#include <ctype.h>
X#include <syslog.h>
X#include <string.h>
X
X#define	TIMEOUT		5
X
Xextern	int errno;
Xstruct	sockaddr_in sin = { AF_INET };
Xint	peer;
Xint	rexmtval = TIMEOUT;
Xint	maxtimeout = 5*TIMEOUT;
X
X#define	PKTSIZE	SEGSIZE+4
Xchar	buf[PKTSIZE];
Xchar	ackbuf[PKTSIZE];
Xstruct	sockaddr_in from;
Xint	fromlen;
X
X#define MAXARG	4
Xchar	*dirs[MAXARG+1];
X
Xmain(ac, av)
X	char **av;
X{
X	register struct tftphdr *tp;
X	register int n = 0;
X	int on = 1;
X
X	ac--; av++;
X	while (ac-- > 0 && n < MAXARG)
X		dirs[n++] = *av++;
X	openlog("tftpd", LOG_PID, LOG_DAEMON);
X	if (ioctl(0, FIONBIO, &on) < 0) {
X		syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
X		exit(1);
X	}
X	fromlen = sizeof (from);
X	n = recvfrom(0, buf, sizeof (buf), 0,
X	    (caddr_t)&from, &fromlen);
X	if (n < 0) {
X		syslog(LOG_ERR, "recvfrom: %m\n");
X		exit(1);
X	}
X	/*
X	 * Now that we have read the message out of the UDP
X	 * socket, we fork and exit.  Thus, inetd will go back
X	 * to listening to the tftp port, and the next request
X	 * to come in will start up a new instance of tftpd.
X	 *
X	 * We do this so that inetd can run tftpd in "wait" mode.
X	 * The problem with tftpd running in "nowait" mode is that
X	 * inetd may get one or more successful "selects" on the
X	 * tftp port before we do our receive, so more than one
X	 * instance of tftpd may be started up.  Worse, if tftpd
X	 * break before doing the above "recvfrom", inetd would
X	 * spawn endless instances, clogging the system.
X	 */
X	{
X		int pid;
X		int i, j;
X
X		for (i = 1; i < 20; i++) {
X		    pid = fork();
X		    if (pid < 0) {
X				sleep(i);
X				/*
X				 * flush out to most recently sent request.
X				 *
X				 * This may drop some request, but those
X				 * will be resent by the clients when
X				 * they timeout.  The positive effect of
X				 * this flush is to (try to) prevent more
X				 * than one tftpd being started up to service
X				 * a single request from a single client.
X				 */
X				j = sizeof from;
X				i = recvfrom(0, buf, sizeof (buf), 0,
X				    (caddr_t)&from, &j);
X				if (i > 0) {
X					n = i;
X					fromlen = j;
X				}
X		    } else {
X				break;
X		    }
X		}
X		if (pid < 0) {
X			syslog(LOG_ERR, "fork: %m\n");
X			exit(1);
X		} else if (pid != 0) {
X			exit(0);
X		}
X	}
X	from.sin_family = AF_INET;
X	alarm(0);
X	close(0);
X	close(1);
X	peer = socket(AF_INET, SOCK_DGRAM, 0);
X	if (peer < 0) {
X		syslog(LOG_ERR, "socket: %m\n");
X		exit(1);
X	}
X	if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
X		syslog(LOG_ERR, "bind: %m\n");
X		exit(1);
X	}
X	if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
X		syslog(LOG_ERR, "connect: %m\n");
X		exit(1);
X	}
X	tp = (struct tftphdr *)buf;
X	tp->th_opcode = ntohs(tp->th_opcode);
X	if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
X		tftp(tp, n);
X	exit(1);
X}
X
Xint	validate_access();
Xint	sendfile(), recvfile();
X
Xstruct formats {
X	char	*f_mode;
X	int	(*f_validate)();
X	int	(*f_send)();
X	int	(*f_recv)();
X	int	f_convert;
X} formats[] = {
X	{ "netascii",	validate_access,	sendfile,	recvfile, 1 },
X	{ "octet",	validate_access,	sendfile,	recvfile, 0 },
X#ifdef notdef
X	{ "mail",	validate_user,		sendmail,	recvmail, 1 },
X#endif
X	{ 0 }
X};
X
X/*
X * Handle initial connection protocol.
X */
Xtftp(tp, size)
X	struct tftphdr *tp;
X	int size;
X{
X	register char *cp;
X	int first = 1, ecode;
X	register struct formats *pf;
X	char *filename, *mode;
X
X	filename = cp = tp->th_stuff;
Xagain:
X	while (cp < buf + size) {
X		if (*cp == '\0')
X			break;
X		cp++;
X	}
X	if (*cp != '\0') {
X		nak(EBADOP);
X		exit(1);
X	}
X	if (first) {
X		mode = ++cp;
X		first = 0;
X		goto again;
X	}
X	for (cp = mode; *cp; cp++)
X		if (isupper(*cp))
X			*cp = tolower(*cp);
X	for (pf = formats; pf->f_mode; pf++)
X		if (strcmp(pf->f_mode, mode) == 0)
X			break;
X	if (pf->f_mode == 0) {
X		nak(EBADOP);
X		exit(1);
X	}
X	ecode = (*pf->f_validate)(filename, tp->th_opcode);
X	if (ecode) {
X		nak(ecode);
X		exit(1);
X	}
X	if (tp->th_opcode == WRQ)
X		(*pf->f_recv)(pf);
X	else
X		(*pf->f_send)(pf);
X	exit(0);
X}
X
X
XFILE *file;
X
X/*
X * Validate file access.  Since we
X * have no uid or gid, for now require
X * file to exist and be publicly
X * readable/writable.
X * If we were invoked with arguments
X * from inetd then the file must also be
X * in one of the given directory prefixes.
X * Note also, full path name must be
X * given as we have no login directory.
X */
Xvalidate_access(filename, mode)
X	char *filename;
X	int mode;
X{
X	struct stat stbuf;
X	int	fd;
X	char *cp, **dirp;
X
X	if (*filename != '/')
X		return (EACCESS);
X	/*
X	 * prevent tricksters from getting around the directory restrictions
X	 */
X	for (cp = filename + 1; *cp; cp++)
X		if(*cp == '.' && strncmp(cp-1, "/../", 4) == 0)
X			return(EACCESS);
X	for (dirp = dirs; *dirp; dirp++)
X		if (strncmp(filename, *dirp, strlen(*dirp)) == 0)
X			break;
X	if (*dirp==0 && dirp!=dirs)
X		return (EACCESS);
X	if (stat(filename, &stbuf) < 0)
X		return (errno == ENOENT ? ENOTFOUND : EACCESS);
X	if (mode == RRQ) {
X		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
X			return (EACCESS);
X	} else {
X		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
X			return (EACCESS);
X	}
X	fd = open(filename, mode == RRQ ? 0 : 1);
X	if (fd < 0)
X		return (errno + 100);
X	file = fdopen(fd, (mode == RRQ)? "r":"w");
X	if (file == NULL) {
X		return errno+100;
X	}
X	return (0);
X}
X
Xint	timeout;
Xjmp_buf	timeoutbuf;
X
Xtimer()
X{
X
X	timeout += rexmtval;
X	if (timeout >= maxtimeout)
X		exit(1);
X	longjmp(timeoutbuf, 1);
X}
X
X/*
X * Send the requested file.
X */
Xsendfile(pf)
X	struct formats *pf;
X{
X	struct tftphdr *dp, *r_init();
X	register struct tftphdr *ap;    /* ack packet */
X	register int block = 1, size, n;
X
X	signal(SIGALRM, timer);
X	dp = r_init();
X	ap = (struct tftphdr *)ackbuf;
X	do {
X		size = readit(file, &dp, pf->f_convert);
X		if (size < 0) {
X			nak(errno + 100);
X			goto abort;
X		}
X		dp->th_opcode = htons((u_short)DATA);
X		dp->th_block = htons((u_short)block);
X		timeout = 0;
X		(void) setjmp(timeoutbuf);
X
Xsend_data:
X		if (send(peer, dp, size + 4, 0) != size + 4) {
X			syslog(LOG_ERR, "tftpd: write: %m\n");
X			goto abort;
X		}
X		read_ahead(file, pf->f_convert);
X		for ( ; ; ) {
X			alarm(rexmtval);        /* read the ack */
X			n = recv(peer, ackbuf, sizeof (ackbuf), 0);
X			alarm(0);
X			if (n < 0) {
X				syslog(LOG_ERR, "tftpd: read: %m\n");
X				goto abort;
X			}
X			ap->th_opcode = ntohs((u_short)ap->th_opcode);
X			ap->th_block = ntohs((u_short)ap->th_block);
X
X			if (ap->th_opcode == ERROR)
X				goto abort;
X			
X			if (ap->th_opcode == ACK) {
X				if (ap->th_block == block) {
X					break;
X				}
X				/* Re-synchronize with the other side */
X				(void) synchnet(peer);
X				if (ap->th_block == (block -1)) {
X					goto send_data;
X				}
X			}
X
X		}
X		block++;
X	} while (size == SEGSIZE);
Xabort:
X	(void) fclose(file);
X}
X
Xjustquit()
X{
X	exit(0);
X}
X
X
X/*
X * Receive a file.
X */
Xrecvfile(pf)
X	struct formats *pf;
X{
X	struct tftphdr *dp, *w_init();
X	register struct tftphdr *ap;    /* ack buffer */
X	register int block = 0, n, size;
X
X	signal(SIGALRM, timer);
X	dp = w_init();
X	ap = (struct tftphdr *)ackbuf;
X	do {
X		timeout = 0;
X		ap->th_opcode = htons((u_short)ACK);
X		ap->th_block = htons((u_short)block);
X		block++;
X		(void) setjmp(timeoutbuf);
Xsend_ack:
X		if (send(peer, ackbuf, 4, 0) != 4) {
X			syslog(LOG_ERR, "tftpd: write: %m\n");
X			goto abort;
X		}
X		write_behind(file, pf->f_convert);
X		for ( ; ; ) {
X			alarm(rexmtval);
X			n = recv(peer, dp, PKTSIZE, 0);
X			alarm(0);
X			if (n < 0) {            /* really? */
X				syslog(LOG_ERR, "tftpd: read: %m\n");
X				goto abort;
X			}
X			dp->th_opcode = ntohs((u_short)dp->th_opcode);
X			dp->th_block = ntohs((u_short)dp->th_block);
X			if (dp->th_opcode == ERROR)
X				goto abort;
X			if (dp->th_opcode == DATA) {
X				if (dp->th_block == block) {
X					break;   /* normal */
X				}
X				/* Re-synchronize with the other side */
X				(void) synchnet(peer);
X				if (dp->th_block == (block-1))
X					goto send_ack;          /* rexmit */
X			}
X		}
X		/*  size = write(file, dp->th_data, n - 4); */
X		size = writeit(file, &dp, n - 4, pf->f_convert);
X		if (size != (n-4)) {                    /* ahem */
X			if (size < 0) nak(errno + 100);
X			else nak(ENOSPACE);
X			goto abort;
X		}
X	} while (size == SEGSIZE);
X	write_behind(file, pf->f_convert);
X	(void) fclose(file);            /* close data file */
X
X	ap->th_opcode = htons((u_short)ACK);    /* send the "final" ack */
X	ap->th_block = htons((u_short)(block));
X	(void) send(peer, ackbuf, 4, 0);
X
X	signal(SIGALRM, justquit);      /* just quit on timeout */
X	alarm(rexmtval);
X	n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
X	alarm(0);
X	if (n >= 4 &&                   /* if read some data */
X	    dp->th_opcode == DATA &&    /* and got a data block */
X	    block == dp->th_block) {	/* then my last ack was lost */
X		(void) send(peer, ackbuf, 4, 0);     /* resend final ack */
X	}
Xabort:
X	return;
X}
X
Xstruct errmsg {
X	int	e_code;
X	char	*e_msg;
X} errmsgs[] = {
X	{ EUNDEF,	"Undefined error code" },
X	{ ENOTFOUND,	"File not found" },
X	{ EACCESS,	"Access violation" },
X	{ ENOSPACE,	"Disk full or allocation exceeded" },
X	{ EBADOP,	"Illegal TFTP operation" },
X	{ EBADID,	"Unknown transfer ID" },
X	{ EEXISTS,	"File already exists" },
X	{ ENOUSER,	"No such user" },
X	{ -1,		0 }
X};
X
X/*
X * Send a nak packet (error message).
X * Error code passed in is one of the
X * standard TFTP codes, or a UNIX errno
X * offset by 100.
X */
Xnak(error)
X	int error;
X{
X	register struct tftphdr *tp;
X	int length;
X	register struct errmsg *pe;
X
X	tp = (struct tftphdr *)buf;
X	tp->th_opcode = htons((u_short)ERROR);
X	tp->th_code = htons((u_short)error);
X	for (pe = errmsgs; pe->e_code >= 0; pe++)
X		if (pe->e_code == error)
X			break;
X	if (pe->e_code < 0) {
X		pe->e_msg = strerror(error - 100);
X		tp->th_code = EUNDEF;   /* set 'undef' errorcode */
X	}
X	strcpy(tp->th_msg, pe->e_msg);
X	length = strlen(pe->e_msg);
X	tp->th_msg[length] = '\0';
X	length += 5;
X	if (send(peer, buf, length, 0) != length)
X		syslog(LOG_ERR, "nak: %m\n");
X}
SHAR_EOF
fi
if test -f 'tftpd.8'
then
	echo shar: "will not over-write existing file 'tftpd.8'"
else
sed 's/^X//' << \SHAR_EOF > 'tftpd.8'
X.\" Copyright (c) 1983 The Regents of the University of California.
X.\" All rights reserved.
X.\"
X.\" Redistribution and use in source and binary forms are permitted provided
X.\" that: (1) source distributions retain this entire copyright notice and
X.\" comment, and (2) distributions including binaries display the following
X.\" acknowledgement:  ``This product includes software developed by the
X.\" University of California, Berkeley and its contributors'' in the
X.\" documentation or other materials provided with the distribution and in
X.\" all advertising materials mentioning features or use of this software.
X.\" Neither the name of the University nor the names of its contributors may
X.\" be used to endorse or promote products derived from this software without
X.\" specific prior written permission.
X.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
X.\" WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
X.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X.\"
X.\"	@(#)tftpd.8	6.5 (Berkeley) 6/24/90
X.\"
X.TH TFTPD 8 "June 24, 1990"
X.UC 5
X.SH NAME
Xtftpd \- DARPA Trivial File Transfer Protocol server
X.SH SYNOPSIS
X.B tftpd
X[ directory ... ]
X.SH DESCRIPTION
X.I Tftpd
Xis a server which supports the DARPA Trivial File Transfer
XProtocol.
XThe TFTP server operates
Xat the port indicated in the ``tftp'' service description;
Xsee
X.IR services (5).
XThe server is normally started by
X.IR inetd (8).
X.PP
XThe use of
X.I tftp
Xdoes not require an account or password on the remote system.
XDue to the lack of authentication information, 
X.I tftpd
Xwill allow only publicly readable files to be
Xaccessed.
XFiles may be written only if they already exist and are publicly writable.
XNote that this extends the concept of ``public'' to include
Xall users on all hosts that can be reached through the network;
Xthis may not be appropriate on all systems, and its implications
Xshould be considered before enabling tftp service.
XThe server should have the user ID with the lowest possible privilege.
X.PP
XAccess to files may be restricted by invoking
X.I tftpd
Xwith a list of directories by including pathnames
Xas server program arguments in
X.IR /etc/inetd.conf .
XIn this case access is restricted to files whose
Xnames are prefixed by the one of the given directories.
X.SH "SEE ALSO"
Xtftp(1), inetd(8)
SHAR_EOF
fi
cd ..
if test ! -d '/usr/src/ucb/tftp'
then
	mkdir '/usr/src/ucb/tftp'
fi
cd '/usr/src/ucb/tftp'
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
sed 's/^X//' << \SHAR_EOF > 'Makefile'
X#
X# Copyright (c) 1983 Regents of the University of California.
X# All rights reserved.  The Berkeley software License Agreement
X# specifies the terms and conditions for redistribution.
X#
X#	@(#)Makefile	5.2 (Berkeley) 2/6/86
X#
XALL=	tftp
XDESTDIR=
XCFLAGS=-O
XSEPFLAG=-i
X
Xall: ${ALL}
X
Xtftp:	main.o tftp.o tftpsubs.o
X	${CC} main.o tftp.o tftpsubs.o ${SEPFLAG} -o tftp
X
Xclean:
X	rm -f ${ALL} *.o *.s errs core a.out t.?
X
Xinstall: 
X	install -s tftp ${DESTDIR}/usr/ucb
SHAR_EOF
fi
if test -f 'tftp.c'
then
	echo shar: "will not over-write existing file 'tftp.c'"
else
sed 's/^X//' << \SHAR_EOF > 'tftp.c'
X/*
X * Copyright (c) 1983 Regents of the University of California.
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms are permitted
X * provided that: (1) source distributions retain this entire copyright
X * notice and comment, and (2) distributions including binaries display
X * the following acknowledgement:  ``This product includes software
X * developed by the University of California, Berkeley and its contributors''
X * in the documentation or other materials provided with the distribution
X * and in all advertising materials mentioning features or use of this
X * software. Neither the name of the University nor the names of its
X * contributors may be used to endorse or promote products derived
X * from this software without specific prior written permission.
X * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
X * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
X * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#)tftp.c	5.9 (Berkeley) 6/1/90";
X#endif /* not lint */
X
X#ifdef	BSD2_10
X#define	timeoutbuf to_buf
X#endif
X
X/* Many bug fixes are from Jim Guyton <guyton at rand-unix> */
X
X/*
X * TFTP User Program -- Protocol Machines
X */
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <sys/time.h>
X
X#include <netinet/in.h>
X
X#include <arpa/tftp.h>
X
X#include <signal.h>
X#include <stdio.h>
X#include <errno.h>
X#include <setjmp.h>
X
Xextern	int errno;
X
Xextern  struct sockaddr_in sin;         /* filled in by main */
Xextern  int     f;                      /* the opened socket */
Xextern  int     trace;
Xextern  int     verbose;
Xextern  int     rexmtval;
Xextern  int     maxtimeout;
X
X#define PKTSIZE    SEGSIZE+4
Xchar    ackbuf[PKTSIZE];
Xint	timeout;
Xjmp_buf	toplevel;
Xjmp_buf	timeoutbuf;
X
Xtimer()
X{
X
X	timeout += rexmtval;
X	if (timeout >= maxtimeout) {
X		printf("Transfer timed out.\n");
X		longjmp(toplevel, -1);
X	}
X	longjmp(timeoutbuf, 1);
X}
X
X/*
X * Send the requested file.
X */
Xsendfile(fd, name, mode)
X	int fd;
X	char *name;
X	char *mode;
X{
X	register struct tftphdr *ap;       /* data and ack packets */
X	struct tftphdr *r_init(), *dp;
X	register int block = 0, size, n;
X	u_long amount = 0;
X	struct sockaddr_in from;
X	int fromlen;
X	int convert;            /* true if doing nl->crlf conversion */
X	FILE *file;
X
X	startclock();           /* start stat's clock */
X	dp = r_init();          /* reset fillbuf/read-ahead code */
X	ap = (struct tftphdr *)ackbuf;
X	file = fdopen(fd, "r");
X	convert = !strcmp(mode, "netascii");
X
X	signal(SIGALRM, timer);
X	do {
X		if (block == 0)
X			size = makerequest(WRQ, name, dp, mode) - 4;
X		else {
X		/*      size = read(fd, dp->th_data, SEGSIZE);   */
X			size = readit(file, &dp, convert);
X			if (size < 0) {
X				nak(errno + 100);
X				break;
X			}
X			dp->th_opcode = htons((u_short)DATA);
X			dp->th_block = htons((u_short)block);
X		}
X		timeout = 0;
X		(void) setjmp(timeoutbuf);
Xsend_data:
X		if (trace)
X			tpacket("sent", dp, size + 4);
X		n = sendto(f, dp, size + 4, 0, (caddr_t)&sin, sizeof (sin));
X		if (n != size + 4) {
X			perror("tftp: sendto");
X			goto abort;
X		}
X		read_ahead(file, convert);
X		for ( ; ; ) {
X			alarm(rexmtval);
X			do {
X				fromlen = sizeof (from);
X				n = recvfrom(f, ackbuf, sizeof (ackbuf), 0,
X				    (caddr_t)&from, &fromlen);
X			} while (n <= 0);
X			alarm(0);
X			if (n < 0) {
X				perror("tftp: recvfrom");
X				goto abort;
X			}
X			sin.sin_port = from.sin_port;   /* added */
X			if (trace)
X				tpacket("received", ap, n);
X			/* should verify packet came from server */
X			ap->th_opcode = ntohs(ap->th_opcode);
X			ap->th_block = ntohs(ap->th_block);
X			if (ap->th_opcode == ERROR) {
X				printf("Error code %d: %s\n", ap->th_code,
X					ap->th_msg);
X				goto abort;
X			}
X			if (ap->th_opcode == ACK) {
X				int j;
X
X				if (ap->th_block == block) {
X					break;
X				}
X				/* On an error, try to synchronize
X				 * both sides.
X				 */
X				j = synchnet(f);
X				if (j && trace) {
X					printf("discarded %d packets\n",
X							j);
X				}
X				if (ap->th_block == (block-1)) {
X					goto send_data;
X				}
X			}
X		}
X		if (block > 0)
X			amount += size;
X		block++;
X	} while (size == SEGSIZE || block == 1);
Xabort:
X	fclose(file);
X	stopclock();
X	if (amount > 0)
X		printstats("Sent", amount);
X}
X
X/*
X * Receive a file.
X */
Xrecvfile(fd, name, mode)
X	int fd;
X	char *name;
X	char *mode;
X{
X	register struct tftphdr *ap;
X	struct tftphdr *dp, *w_init();
X	register int block = 1, n, size;
X	u_long amount = 0;
X	struct sockaddr_in from;
X	int fromlen, firsttrip = 1;
X	FILE *file;
X	int convert;                    /* true if converting crlf -> lf */
X
X	startclock();
X	dp = w_init();
X	ap = (struct tftphdr *)ackbuf;
X	file = fdopen(fd, "w");
X	convert = !strcmp(mode, "netascii");
X
X	signal(SIGALRM, timer);
X	do {
X		if (firsttrip) {
X			size = makerequest(RRQ, name, ap, mode);
X			firsttrip = 0;
X		} else {
X			ap->th_opcode = htons((u_short)ACK);
X			ap->th_block = htons((u_short)(block));
X			size = 4;
X			block++;
X		}
X		timeout = 0;
X		(void) setjmp(timeoutbuf);
Xsend_ack:
X		if (trace)
X			tpacket("sent", ap, size);
X		if (sendto(f, ackbuf, size, 0, (caddr_t)&sin,
X		    sizeof (sin)) != size) {
X			alarm(0);
X			perror("tftp: sendto");
X			goto abort;
X		}
X		write_behind(file, convert);
X		for ( ; ; ) {
X			alarm(rexmtval);
X			do  {
X				fromlen = sizeof (from);
X				n = recvfrom(f, dp, PKTSIZE, 0,
X				    (caddr_t)&from, &fromlen);
X			} while (n <= 0);
X			alarm(0);
X			if (n < 0) {
X				perror("tftp: recvfrom");
X				goto abort;
X			}
X			sin.sin_port = from.sin_port;   /* added */
X			if (trace)
X				tpacket("received", dp, n);
X			/* should verify client address */
X			dp->th_opcode = ntohs(dp->th_opcode);
X			dp->th_block = ntohs(dp->th_block);
X			if (dp->th_opcode == ERROR) {
X				printf("Error code %d: %s\n", dp->th_code,
X					dp->th_msg);
X				goto abort;
X			}
X			if (dp->th_opcode == DATA) {
X				int j;
X
X				if (dp->th_block == block) {
X					break;          /* have next packet */
X				}
X				/* On an error, try to synchronize
X				 * both sides.
X				 */
X				j = synchnet(f);
X				if (j && trace) {
X					printf("discarded %d packets\n", j);
X				}
X				if (dp->th_block == (block-1)) {
X					goto send_ack;  /* resend ack */
X				}
X			}
X		}
X	/*      size = write(fd, dp->th_data, n - 4); */
X		size = writeit(file, &dp, n - 4, convert);
X		if (size < 0) {
X			nak(errno + 100);
X			break;
X		}
X		amount += size;
X	} while (size == SEGSIZE);
Xabort:                                          /* ok to ack, since user */
X	ap->th_opcode = htons((u_short)ACK);    /* has seen err msg */
X	ap->th_block = htons((u_short)block);
X	(void) sendto(f, ackbuf, 4, 0, &sin, sizeof (sin));
X	write_behind(file, convert);            /* flush last buffer */
X	fclose(file);
X	stopclock();
X	if (amount > 0)
X		printstats("Received", amount);
X}
X
Xmakerequest(request, name, tp, mode)
X	int request;
X	char *name, *mode;
X	struct tftphdr *tp;
X{
X	register char *cp;
X
X	tp->th_opcode = htons((u_short)request);
X	cp = tp->th_stuff;
X	strcpy(cp, name);
X	cp += strlen(name);
X	*cp++ = '\0';
X	strcpy(cp, mode);
X	cp += strlen(mode);
X	*cp++ = '\0';
X	return (cp - (char *)tp);
X}
X
Xstruct errmsg {
X	int	e_code;
X	char	*e_msg;
X} errmsgs[] = {
X	{ EUNDEF,	"Undefined error code" },
X	{ ENOTFOUND,	"File not found" },
X	{ EACCESS,	"Access violation" },
X	{ ENOSPACE,	"Disk full or allocation exceeded" },
X	{ EBADOP,	"Illegal TFTP operation" },
X	{ EBADID,	"Unknown transfer ID" },
X	{ EEXISTS,	"File already exists" },
X	{ ENOUSER,	"No such user" },
X	{ -1,		0 }
X};
X
X/*
X * Send a nak packet (error message).
X * Error code passed in is one of the
X * standard TFTP codes, or a UNIX errno
X * offset by 100.
X */
Xnak(error)
X	int error;
X{
X	register struct errmsg *pe;
X	register struct tftphdr *tp;
X	int length;
X	char *strerror();
X
X	tp = (struct tftphdr *)ackbuf;
X	tp->th_opcode = htons((u_short)ERROR);
X	tp->th_code = htons((u_short)error);
X	for (pe = errmsgs; pe->e_code >= 0; pe++)
X		if (pe->e_code == error)
X			break;
X	if (pe->e_code < 0) {
X		pe->e_msg = strerror(error - 100);
X		tp->th_code = EUNDEF;
X	}
X	strcpy(tp->th_msg, pe->e_msg);
X	length = strlen(pe->e_msg) + 4;
X	if (trace)
X		tpacket("sent", tp, length);
X	if (sendto(f, ackbuf, length, 0, &sin, sizeof (sin)) != length)
X		perror("nak");
X}
X
Xtpacket(s, tp, n)
X	char *s;
X	struct tftphdr *tp;
X	int n;
X{
X	static char *opcodes[] =
X	   { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" };
X	register char *cp, *file;
X	u_short op = ntohs(tp->th_opcode);
X	char *index();
X
X	if (op < RRQ || op > ERROR)
X		printf("%s opcode=%x ", s, op);
X	else
X		printf("%s %s ", s, opcodes[op]);
X	switch (op) {
X
X	case RRQ:
X	case WRQ:
X		n -= 2;
X		file = cp = tp->th_stuff;
X		cp = index(cp, '\0');
X		printf("<file=%s, mode=%s>\n", file, cp + 1);
X		break;
X
X	case DATA:
X		printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
X		break;
X
X	case ACK:
X		printf("<block=%d>\n", ntohs(tp->th_block));
X		break;
X
X	case ERROR:
X		printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
X		break;
X	}
X}
X
Xstruct timeval tstart;
Xstruct timeval tstop;
Xstruct timezone zone;
X
Xstartclock() {
X	gettimeofday(&tstart, &zone);
X}
X
Xstopclock() {
X	gettimeofday(&tstop, &zone);
X}
X
Xprintstats(direction, amount)
Xchar *direction;
Xu_long amount;
X{
X	double delta;
X			/* compute delta in 1/10's second units */
X	delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000L)) -
X		((tstart.tv_sec*10.)+(tstart.tv_usec/100000L));
X	delta = delta/10.;      /* back to seconds */
X	printf("%s %ld bytes in %.1f seconds", direction, amount, delta);
X	if (verbose)
X		printf(" [%.0f bits/sec]", (amount*8.)/delta);
X	putchar('\n');
X}
X
SHAR_EOF
fi
if test -f 'tftpsubs.c'
then
	echo shar: "will not over-write existing file 'tftpsubs.c'"
else
sed 's/^X//' << \SHAR_EOF > 'tftpsubs.c'
X/*
X * Copyright (c) 1983 Regents of the University of California.
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms are permitted
X * provided that: (1) source distributions retain this entire copyright
X * notice and comment, and (2) distributions including binaries display
X * the following acknowledgement:  ``This product includes software
X * developed by the University of California, Berkeley and its contributors''
X * in the documentation or other materials provided with the distribution
X * and in all advertising materials mentioning features or use of this
X * software. Neither the name of the University nor the names of its
X * contributors may be used to endorse or promote products derived
X * from this software without specific prior written permission.
X * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
X * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
X * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#)tftpsubs.c	5.5 (Berkeley) 6/1/90";
X#endif /* not lint */
X
X/* Simple minded read-ahead/write-behind subroutines for tftp user and
X   server.  Written originally with multiple buffers in mind, but current
X   implementation has two buffer logic wired in.
X
X   Todo:  add some sort of final error check so when the write-buffer
X   is finally flushed, the caller can detect if the disk filled up
X   (or had an i/o error) and return a nak to the other side.
X
X			Jim Guyton 10/85
X */
X
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <sys/ioctl.h>
X#include <netinet/in.h>
X#include <arpa/tftp.h>
X#include <stdio.h>
X
X#define PKTSIZE SEGSIZE+4       /* should be moved to tftp.h */
X
Xstruct bf {
X	int counter;            /* size of data in buffer, or flag */
X	char buf[PKTSIZE];      /* room for data packet */
X} bfs[2];
X
X				/* Values for bf.counter  */
X#define BF_ALLOC -3             /* alloc'd but not yet filled */
X#define BF_FREE  -2             /* free */
X/* [-1 .. SEGSIZE] = size of data in the data buffer */
X
Xstatic int nextone;     /* index of next buffer to use */
Xstatic int current;     /* index of buffer in use */
X
X			/* control flags for crlf conversions */
Xint newline = 0;        /* fillbuf: in middle of newline expansion */
Xint prevchar = -1;      /* putbuf: previous char (cr check) */
X
Xstruct tftphdr *rw_init();
X
Xstruct tftphdr *w_init() { return rw_init(0); }         /* write-behind */
Xstruct tftphdr *r_init() { return rw_init(1); }         /* read-ahead */
X
Xstruct tftphdr *
Xrw_init(x)              /* init for either read-ahead or write-behind */
Xint x;                  /* zero for write-behind, one for read-head */
X{
X	newline = 0;            /* init crlf flag */
X	prevchar = -1;
X	bfs[0].counter =  BF_ALLOC;     /* pass out the first buffer */
X	current = 0;
X	bfs[1].counter = BF_FREE;
X	nextone = x;                    /* ahead or behind? */
X	return (struct tftphdr *)bfs[0].buf;
X}
X
X
X/* Have emptied current buffer by sending to net and getting ack.
X   Free it and return next buffer filled with data.
X */
Xreadit(file, dpp, convert)
X	FILE *file;                     /* file opened for read */
X	struct tftphdr **dpp;
X	int convert;                    /* if true, convert to ascii */
X{
X	struct bf *b;
X
X	bfs[current].counter = BF_FREE; /* free old one */
X	current = !current;             /* "incr" current */
X
X	b = &bfs[current];              /* look at new buffer */
X	if (b->counter == BF_FREE)      /* if it's empty */
X		read_ahead(file, convert);      /* fill it */
X/*      assert(b->counter != BF_FREE);  /* check */
X	*dpp = (struct tftphdr *)b->buf;        /* set caller's ptr */
X	return b->counter;
X}
X
X/*
X * fill the input buffer, doing ascii conversions if requested
X * conversions are  lf -> cr,lf  and cr -> cr, nul
X */
Xread_ahead(file, convert)
X	FILE *file;                     /* file opened for read */
X	int convert;                    /* if true, convert to ascii */
X{
X	register int i;
X	register char *p;
X	register int c;
X	struct bf *b;
X	struct tftphdr *dp;
X
X	b = &bfs[nextone];              /* look at "next" buffer */
X	if (b->counter != BF_FREE)      /* nop if not free */
X		return;
X	nextone = !nextone;             /* "incr" next buffer ptr */
X
X	dp = (struct tftphdr *)b->buf;
X
X	if (convert == 0) {
X		b->counter = read(fileno(file), dp->th_data, SEGSIZE);
X		return;
X	}
X
X	p = dp->th_data;
X	for (i = 0 ; i < SEGSIZE; i++) {
X		if (newline) {
X			if (prevchar == '\n')
X				c = '\n';       /* lf to cr,lf */
X			else    c = '\0';       /* cr to cr,nul */
X			newline = 0;
X		}
X		else {
X			c = getc(file);
X			if (c == EOF) break;
X			if (c == '\n' || c == '\r') {
X				prevchar = c;
X				c = '\r';
X				newline = 1;
X			}
X		}
X	       *p++ = c;
X	}
X	b->counter = (int)(p - dp->th_data);
X}
X
X/* Update count associated with the buffer, get new buffer
X   from the queue.  Calls write_behind only if next buffer not
X   available.
X */
Xwriteit(file, dpp, ct, convert)
X	FILE *file;
X	struct tftphdr **dpp;
X	int convert;
X{
X	bfs[current].counter = ct;      /* set size of data to write */
X	current = !current;             /* switch to other buffer */
X	if (bfs[current].counter != BF_FREE)     /* if not free */
X		write_behind(file, convert);     /* flush it */
X	bfs[current].counter = BF_ALLOC;        /* mark as alloc'd */
X	*dpp =  (struct tftphdr *)bfs[current].buf;
X	return ct;                      /* this is a lie of course */
X}
X
X/*
X * Output a buffer to a file, converting from netascii if requested.
X * CR,NUL -> CR  and CR,LF => LF.
X * Note spec is undefined if we get CR as last byte of file or a
X * CR followed by anything else.  In this case we leave it alone.
X */
Xwrite_behind(file, convert)
X	FILE *file;
X	int convert;
X{
X	char *buf;
X	int count;
X	register int ct;
X	register char *p;
X	register int c;                 /* current character */
X	struct bf *b;
X	struct tftphdr *dp;
X
X	b = &bfs[nextone];
X	if (b->counter < -1)            /* anything to flush? */
X		return 0;               /* just nop if nothing to do */
X
X	count = b->counter;             /* remember byte count */
X	b->counter = BF_FREE;           /* reset flag */
X	dp = (struct tftphdr *)b->buf;
X	nextone = !nextone;             /* incr for next time */
X	buf = dp->th_data;
X
X	if (count <= 0) return -1;      /* nak logic? */
X
X	if (convert == 0)
X		return write(fileno(file), buf, count);
X
X	p = buf;
X	ct = count;
X	while (ct--) {                  /* loop over the buffer */
X	    c = *p++;                   /* pick up a character */
X	    if (prevchar == '\r') {     /* if prev char was cr */
X		if (c == '\n')          /* if have cr,lf then just */
X		   fseek(file, -1L, 1);  /* smash lf on top of the cr */
X		else
X		   if (c == '\0')       /* if have cr,nul then */
X			goto skipit;    /* just skip over the putc */
X		/* else just fall through and allow it */
X	    }
X	    putc(c, file);
Xskipit:
X	    prevchar = c;
X	}
X	return count;
X}
X
X
X/* When an error has occurred, it is possible that the two sides
X * are out of synch.  Ie: that what I think is the other side's
X * response to packet N is really their response to packet N-1.
X *
X * So, to try to prevent that, we flush all the input queued up
X * for us on the network connection on our host.
X *
X * We return the number of packets we flushed (mostly for reporting
X * when trace is active).
X */
X
Xint
Xsynchnet(f)
Xint	f;		/* socket to flush */
X{
X	int i, j = 0;
X	char rbuf[PKTSIZE];
X	struct sockaddr_in from;
X	int fromlen;
X
X	while (1) {
X		(void) ioctl(f, FIONREAD, &i);
X		if (i) {
X			j++;
X			fromlen = sizeof from;
X			(void) recvfrom(f, rbuf, sizeof (rbuf), 0,
X				(caddr_t)&from, &fromlen);
X		} else {
X			return(j);
X		}
X	}
X}
SHAR_EOF
fi
if test -f 'main.c'
then
	echo shar: "will not over-write existing file 'main.c'"
else
sed 's/^X//' << \SHAR_EOF > 'main.c'
X/*
X * Copyright (c) 1983 Regents of the University of California.
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms are permitted
X * provided that: (1) source distributions retain this entire copyright
X * notice and comment, and (2) distributions including binaries display
X * the following acknowledgement:  ``This product includes software
X * developed by the University of California, Berkeley and its contributors''
X * in the documentation or other materials provided with the distribution
X * and in all advertising materials mentioning features or use of this
X * software. Neither the name of the University nor the names of its
X * contributors may be used to endorse or promote products derived
X * from this software without specific prior written permission.
X * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
X * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
X * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X */
X
X#ifndef lint
Xchar copyright[] =
X"@(#) Copyright (c) 1983 Regents of the University of California.\n\
X All rights reserved.\n";
X#endif /* not lint */
X
X#ifndef lint
Xstatic char sccsid[] = "@(#)main.c	5.9 (Berkeley) 6/1/90";
X#endif /* not lint */
X
X/* Many bug fixes are from Jim Guyton <guyton at rand-unix> */
X
X/*
X * TFTP User Program -- Command Interface.
X */
X#include <sys/types.h>
X#include <sys/socket.h>
X#include <sys/file.h>
X
X#include <netinet/in.h>
X
X#include <signal.h>
X#include <stdio.h>
X#include <errno.h>
X#include <setjmp.h>
X#include <ctype.h>
X#include <netdb.h>
X
X#define	TIMEOUT		5		/* secs between rexmt's */
X
Xstruct	sockaddr_in sin;
Xint	f;
Xshort   port;
Xint	trace;
Xint	verbose;
Xint	connected;
Xchar	mode[32];
Xchar	line[200];
Xint	margc;
Xchar	*margv[20];
Xchar	*prompt = "tftp";
Xjmp_buf	toplevel;
Xint	intr();
Xstruct	servent *sp;
X
Xint	quit(), help(), setverbose(), settrace(), status();
Xint     get(), put(), setpeer(), modecmd(), setrexmt(), settimeout();
Xint     setbinary(), setascii();
X
X#define HELPINDENT (sizeof("connect"))
X
Xstruct cmd {
X	char	*name;
X	char	*help;
X	int	(*handler)();
X};
X
Xchar	vhelp[] = "toggle verbose mode";
Xchar	thelp[] = "toggle packet tracing";
Xchar	chelp[] = "connect to remote tftp";
Xchar	qhelp[] = "exit tftp";
Xchar	hhelp[] = "print help information";
Xchar	shelp[] = "send file";
Xchar	rhelp[] = "receive file";
Xchar	mhelp[] = "set file transfer mode";
Xchar	sthelp[] = "show current status";
Xchar	xhelp[] = "set per-packet retransmission timeout";
Xchar	ihelp[] = "set total retransmission timeout";
Xchar    ashelp[] = "set mode to netascii";
Xchar    bnhelp[] = "set mode to octet";
X
Xstruct cmd cmdtab[] = {
X	{ "connect",	chelp,		setpeer },
X	{ "mode",       mhelp,          modecmd },
X	{ "put",	shelp,		put },
X	{ "get",	rhelp,		get },
X	{ "quit",	qhelp,		quit },
X	{ "verbose",	vhelp,		setverbose },
X	{ "trace",	thelp,		settrace },
X	{ "status",	sthelp,		status },
X	{ "binary",     bnhelp,         setbinary },
X	{ "ascii",      ashelp,         setascii },
X	{ "rexmt",	xhelp,		setrexmt },
X	{ "timeout",	ihelp,		settimeout },
X	{ "?",		hhelp,		help },
X	0
X};
X
Xstruct	cmd *getcmd();
Xchar	*tail();
Xchar	*index();
Xchar	*rindex();
X
Xmain(argc, argv)
X	char *argv[];
X{
X	struct sockaddr_in sin;
X	int top;
X
X	sp = getservbyname("tftp", "udp");
X	if (sp == 0) {
X		fprintf(stderr, "tftp: udp/tftp: unknown service\n");
X		exit(1);
X	}
X	f = socket(AF_INET, SOCK_DGRAM, 0);
X	if (f < 0) {
X		perror("tftp: socket");
X		exit(3);
X	}
X	bzero((char *)&sin, sizeof (sin));
X	sin.sin_family = AF_INET;
X	if (bind(f, &sin, sizeof (sin)) < 0) {
X		perror("tftp: bind");
X		exit(1);
X	}
X	strcpy(mode, "netascii");
X	signal(SIGINT, intr);
X	if (argc > 1) {
X		if (setjmp(toplevel) != 0)
X			exit(0);
X		setpeer(argc, argv);
X	}
X	top = setjmp(toplevel) == 0;
X	for (;;)
X		command(top);
X}
X
Xchar    hostname[100];
X
Xsetpeer(argc, argv)
X	int argc;
X	char *argv[];
X{
X	struct hostent *host;
X
X	if (argc < 2) {
X		strcpy(line, "Connect ");
X		printf("(to) ");
X		gets(&line[strlen(line)]);
X		makeargv();
X		argc = margc;
X		argv = margv;
X	}
X	if (argc > 3) {
X		printf("usage: %s host-name [port]\n", argv[0]);
X		return;
X	}
X	host = gethostbyname(argv[1]);
X	if (host) {
X		sin.sin_family = host->h_addrtype;
X		bcopy(host->h_addr, &sin.sin_addr, host->h_length);
X		strcpy(hostname, host->h_name);
X	} else {
X		sin.sin_family = AF_INET;
X		sin.sin_addr.s_addr = inet_addr(argv[1]);
X		if (sin.sin_addr.s_addr == -1) {
X			connected = 0;
X			printf("%s: unknown host\n", argv[1]);
X			return;
X		}
X		strcpy(hostname, argv[1]);
X	}
X	port = sp->s_port;
X	if (argc == 3) {
X		port = atoi(argv[2]);
X		if (port < 0) {
X			printf("%s: bad port number\n", argv[2]);
X			connected = 0;
X			return;
X		}
X		port = htons(port);
X	}
X	connected = 1;
X}
X
Xstruct	modes {
X	char *m_name;
X	char *m_mode;
X} modes[] = {
X	{ "ascii",	"netascii" },
X	{ "netascii",   "netascii" },
X	{ "binary",     "octet" },
X	{ "image",      "octet" },
X	{ "octet",     "octet" },
X/*      { "mail",       "mail" },       */
X	{ 0,		0 }
X};
X
Xmodecmd(argc, argv)
X	char *argv[];
X{
X	register struct modes *p;
X	char *sep;
X
X	if (argc < 2) {
X		printf("Using %s mode to transfer files.\n", mode);
X		return;
X	}
X	if (argc == 2) {
X		for (p = modes; p->m_name; p++)
X			if (strcmp(argv[1], p->m_name) == 0)
X				break;
X		if (p->m_name) {
X			setmode(p->m_mode);
X			return;
X		}
X		printf("%s: unknown mode\n", argv[1]);
X		/* drop through and print usage message */
X	}
X
X	printf("usage: %s [", argv[0]);
X	sep = " ";
X	for (p = modes; p->m_name; p++) {
X		printf("%s%s", sep, p->m_name);
X		if (*sep == ' ')
X			sep = " | ";
X	}
X	printf(" ]\n");
X	return;
X}
X
Xsetbinary(argc, argv)
Xchar *argv[];
X{       setmode("octet");
X}
X
Xsetascii(argc, argv)
Xchar *argv[];
X{       setmode("netascii");
X}
X
Xsetmode(newmode)
Xchar *newmode;
X{
X	strcpy(mode, newmode);
X	if (verbose)
X		printf("mode set to %s\n", mode);
X}
X
X
X/*
X * Send file(s).
X */
Xput(argc, argv)
X	char *argv[];
X{
X	int fd;
X	register int n;
X	register char *cp, *targ;
X
X	if (argc < 2) {
X		strcpy(line, "send ");
X		printf("(file) ");
X		gets(&line[strlen(line)]);
X		makeargv();
X		argc = margc;
X		argv = margv;
X	}
X	if (argc < 2) {
X		putusage(argv[0]);
X		return;
X	}
X	targ = argv[argc - 1];
X	if (index(argv[argc - 1], ':')) {
X		char *cp;
X		struct hostent *hp;
X
X		for (n = 1; n < argc - 1; n++)
X			if (index(argv[n], ':')) {
X				putusage(argv[0]);
X				return;
X			}
X		cp = argv[argc - 1];
X		targ = index(cp, ':');
X		*targ++ = 0;
X		hp = gethostbyname(cp);
X		if (hp == NULL) {
X			fprintf(stderr, "tftp: %s: ", cp);
X			herror((char *)NULL);
X			return;
X		}
X		bcopy(hp->h_addr, (caddr_t)&sin.sin_addr, hp->h_length);
X		sin.sin_family = hp->h_addrtype;
X		connected = 1;
X		strcpy(hostname, hp->h_name);
X	}
X	if (!connected) {
X		printf("No target machine specified.\n");
X		return;
X	}
X	if (argc < 4) {
X		cp = argc == 2 ? tail(targ) : argv[1];
X		fd = open(cp, O_RDONLY);
X		if (fd < 0) {
X			fprintf(stderr, "tftp: "); perror(cp);
X			return;
X		}
X		if (verbose)
X			printf("putting %s to %s:%s [%s]\n",
X				cp, hostname, targ, mode);
X		sin.sin_port = port;
X		sendfile(fd, targ, mode);
X		return;
X	}
X				/* this assumes the target is a directory */
X				/* on a remote unix system.  hmmmm.  */
X	cp = index(targ, '\0'); 
X	*cp++ = '/';
X	for (n = 1; n < argc - 1; n++) {
X		strcpy(cp, tail(argv[n]));
X		fd = open(argv[n], O_RDONLY);
X		if (fd < 0) {
X			fprintf(stderr, "tftp: "); perror(argv[n]);
X			continue;
X		}
X		if (verbose)
X			printf("putting %s to %s:%s [%s]\n",
X				argv[n], hostname, targ, mode);
X		sin.sin_port = port;
X		sendfile(fd, targ, mode);
X	}
X}
X
Xputusage(s)
X	char *s;
X{
X	printf("usage: %s file ... host:target, or\n", s);
X	printf("       %s file ... target (when already connected)\n", s);
X}
X
X/*
X * Receive file(s).
X */
Xget(argc, argv)
X	char *argv[];
X{
X	int fd;
X	register int n;
X	register char *cp;
X	char *src;
X
X	if (argc < 2) {
X		strcpy(line, "get ");
X		printf("(files) ");
X		gets(&line[strlen(line)]);
X		makeargv();
X		argc = margc;
X		argv = margv;
X	}
X	if (argc < 2) {
X		getusage(argv[0]);
X		return;
X	}
X	if (!connected) {
X		for (n = 1; n < argc ; n++)
X			if (index(argv[n], ':') == 0) {
X				getusage(argv[0]);
X				return;
X			}
X	}
X	for (n = 1; n < argc ; n++) {
X		src = index(argv[n], ':');
X		if (src == NULL)
X			src = argv[n];
X		else {
X			struct hostent *hp;
X
X			*src++ = 0;
X			hp = gethostbyname(argv[n]);
X			if (hp == NULL) {
X				fprintf(stderr, "tftp: %s: ", argv[n]);
X				herror((char *)NULL);
X				continue;
X			}
X			bcopy(hp->h_addr, (caddr_t)&sin.sin_addr, hp->h_length);
X			sin.sin_family = hp->h_addrtype;
X			connected = 1;
X			strcpy(hostname, hp->h_name);
X		}
X		if (argc < 4) {
X			cp = argc == 3 ? argv[2] : tail(src);
X			fd = creat(cp, 0644);
X			if (fd < 0) {
X				fprintf(stderr, "tftp: "); perror(cp);
X				return;
X			}
X			if (verbose)
X				printf("getting from %s:%s to %s [%s]\n",
X					hostname, src, cp, mode);
X			sin.sin_port = port;
X			recvfile(fd, src, mode);
X			break;
X		}
X		cp = tail(src);         /* new .. jdg */
X		fd = creat(cp, 0644);
X		if (fd < 0) {
X			fprintf(stderr, "tftp: "); perror(cp);
X			continue;
X		}
X		if (verbose)
X			printf("getting from %s:%s to %s [%s]\n",
X				hostname, src, cp, mode);
X		sin.sin_port = port;
X		recvfile(fd, src, mode);
X	}
X}
X
Xgetusage(s)
Xchar * s;
X{
X	printf("usage: %s host:file host:file ... file, or\n", s);
X	printf("       %s file file ... file if connected\n", s);
X}
X
Xint	rexmtval = TIMEOUT;
X
Xsetrexmt(argc, argv)
X	char *argv[];
X{
X	int t;
X
X	if (argc < 2) {
X		strcpy(line, "Rexmt-timeout ");
X		printf("(value) ");
X		gets(&line[strlen(line)]);
X		makeargv();
X		argc = margc;
X		argv = margv;
X	}
X	if (argc != 2) {
X		printf("usage: %s value\n", argv[0]);
X		return;
X	}
X	t = atoi(argv[1]);
X	if (t < 0)
X		printf("%s: bad value\n", t);
X	else
X		rexmtval = t;
X}
X
Xint	maxtimeout = 5 * TIMEOUT;
X
Xsettimeout(argc, argv)
X	char *argv[];
X{
X	int t;
X
X	if (argc < 2) {
X		strcpy(line, "Maximum-timeout ");
X		printf("(value) ");
X		gets(&line[strlen(line)]);
X		makeargv();
X		argc = margc;
X		argv = margv;
X	}
X	if (argc != 2) {
X		printf("usage: %s value\n", argv[0]);
X		return;
X	}
X	t = atoi(argv[1]);
X	if (t < 0)
X		printf("%s: bad value\n", t);
X	else
X		maxtimeout = t;
X}
X
Xstatus(argc, argv)
X	char *argv[];
X{
X	if (connected)
X		printf("Connected to %s.\n", hostname);
X	else
X		printf("Not connected.\n");
X	printf("Mode: %s Verbose: %s Tracing: %s\n", mode,
X		verbose ? "on" : "off", trace ? "on" : "off");
X	printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
X		rexmtval, maxtimeout);
X}
X
Xintr()
X{
X	signal(SIGALRM, SIG_IGN);
X	alarm(0);
X	longjmp(toplevel, -1);
X}
X
Xchar *
Xtail(filename)
X	char *filename;
X{
X	register char *s;
X	
X	while (*filename) {
X		s = rindex(filename, '/');
X		if (s == NULL)
X			break;
X		if (s[1])
X			return (s + 1);
X		*s = '\0';
X	}
X	return (filename);
X}
X
X/*
X * Command parser.
X */
Xcommand(top)
X	int top;
X{
X	register struct cmd *c;
X
X	if (!top)
X		putchar('\n');
X	for (;;) {
X		printf("%s> ", prompt);
X		if (gets(line) == 0) {
X			if (feof(stdin)) {
X				quit();
X			} else {
X				continue;
X			}
X		}
X		if (line[0] == 0)
X			continue;
X		makeargv();
X		c = getcmd(margv[0]);
X		if (c == (struct cmd *)-1) {
X			printf("?Ambiguous command\n");
X			continue;
X		}
X		if (c == 0) {
X			printf("?Invalid command\n");
X			continue;
X		}
X		(*c->handler)(margc, margv);
X	}
X}
X
Xstruct cmd *
Xgetcmd(name)
X	register char *name;
X{
X	register char *p, *q;
X	register struct cmd *c, *found;
X	register int nmatches, longest;
X
X	longest = 0;
X	nmatches = 0;
X	found = 0;
X	for (c = cmdtab; p = c->name; c++) {
X		for (q = name; *q == *p++; q++)
X			if (*q == 0)		/* exact match? */
X				return (c);
X		if (!*q) {			/* the name was a prefix */
X			if (q - name > longest) {
X				longest = q - name;
X				nmatches = 1;
X				found = c;
X			} else if (q - name == longest)
X				nmatches++;
X		}
X	}
X	if (nmatches > 1)
X		return ((struct cmd *)-1);
X	return (found);
X}
X
X/*
X * Slice a string up into argc/argv.
X */
Xmakeargv()
X{
X	register char *cp;
X	register char **argp = margv;
X
X	margc = 0;
X	for (cp = line; *cp;) {
X		while (isspace(*cp))
X			cp++;
X		if (*cp == '\0')
X			break;
X		*argp++ = cp;
X		margc += 1;
X		while (*cp != '\0' && !isspace(*cp))
X			cp++;
X		if (*cp == '\0')
X			break;
X		*cp++ = '\0';
X	}
X	*argp++ = 0;
X}
X
X/*VARARGS*/
Xquit()
X{
X	exit(0);
X}
X
X/*
X * Help command.
X */
Xhelp(argc, argv)
X	int argc;
X	char *argv[];
X{
X	register struct cmd *c;
X
X	if (argc == 1) {
X		printf("Commands may be abbreviated.  Commands are:\n\n");
X		for (c = cmdtab; c->name; c++)
X			printf("%-*s\t%s\n", HELPINDENT, c->name, c->help);
X		return;
X	}
X	while (--argc > 0) {
X		register char *arg;
X		arg = *++argv;
X		c = getcmd(arg);
X		if (c == (struct cmd *)-1)
X			printf("?Ambiguous help command %s\n", arg);
X		else if (c == (struct cmd *)0)
X			printf("?Invalid help command %s\n", arg);
X		else
X			printf("%s\n", c->help);
X	}
X}
X
X/*VARARGS*/
Xsettrace()
X{
X	trace = !trace;
X	printf("Packet tracing %s.\n", trace ? "on" : "off");
X}
X
X/*VARARGS*/
Xsetverbose()
X{
X	verbose = !verbose;
X	printf("Verbose mode %s.\n", verbose ? "on" : "off");
X}
SHAR_EOF
fi
if test -f 'tftp.1'
then
	echo shar: "will not over-write existing file 'tftp.1'"
else
sed 's/^X//' << \SHAR_EOF > 'tftp.1'
X.\" Copyright (c) 1990 The Regents of the University of California.
X.\" All rights reserved.
X.\"
X.\" Redistribution and use in source and binary forms are permitted provided
X.\" that: (1) source distributions retain this entire copyright notice and
X.\" comment, and (2) distributions including binaries display the following
X.\" acknowledgement:  ``This product includes software developed by the
X.\" University of California, Berkeley and its contributors'' in the
X.\" documentation or other materials provided with the distribution and in
X.\" all advertising materials mentioning features or use of this software.
X.\" Neither the name of the University nor the names of its contributors may
X.\" be used to endorse or promote products derived from this software without
X.\" specific prior written permission.
X.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
X.\" WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
X.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X.\"
X.\"     @(#)tftp.1	5.4 (Berkeley) 6/24/90
X.\"
X.TH TFTP 1 "%Q"
X.UC 6
X.SH NAME
Xtftp \- trivial file transfer program
X.SH SYNOPSIS
X.B tftp
X[
Xhost
X]
X.SH DESCRIPTION
X.I Tftp
Xis the user interface to the Internet TFTP
X(Trivial File Transfer Protocol),
Xwhich allows users to transfer files to and from a remote machine.
XThe remote
X.I host
Xmay be specified on the command line, in which case
X.I tftp
Xuses
X.I host
Xas the default host for future transfers (see the
X.B connect
Xcommand below).
X.SH COMMANDS
XOnce
X.I tftp
Xis running, it issues the prompt
X.B tftp>
Xand recognizes the following commands:
X.TP
X\fBconnect\fP \fIhost-name\fP [ \fIport\fP ]
XSet the
X.I host
X(and optionally
X.IR port )
Xfor transfers.
XNote that the TFTP protocol, unlike the FTP protocol,
Xdoes not maintain connections betweeen transfers; thus, the
X.I connect
Xcommand does not actually create a connection,
Xbut merely remembers what host is to be used for transfers.
XYou do not have to use the 
X.I connect
Xcommand; the remote host can be specified as part of the
X.I get
Xor
X.I put
Xcommands.
X.TP
X\fBmode\fP \fItransfer-mode\fP
XSet the mode for transfers; 
X.I transfer-mode
Xmay be one of
X.IR ascii
Xor
X.IR binary .
XThe default is
X.IR ascii .
X.TP
X\fBput\fP \fIfile\fP
X.ns
X.TP
X\fBput\fP \fIlocalfile remotefile\fP
X.ns
X.TP
X\fBput\fP \fIfile1 file2 ... fileN remote-directory\fP
XPut a file or set of files to the specified
Xremote file or directory.
XThe destination
Xcan be in one of two forms:
Xa filename on the remote host, if the host has already been specified,
Xor a string of the form
X.I host:filename
Xto specify both a host and filename at the same time.
XIf the latter form is used,
Xthe hostname specified becomes the default for future transfers.
XIf the remote-directory form is used, the remote host is
Xassumed to be a
X.I UNIX
Xmachine.
X.TP
X\fBget\fP \fIfilename\fP
X.ns
X.TP
X\fBget\fP \fIremotename\fP \fIlocalname\fP
X.ns
X.TP
X\fBget\fP \fIfile1\fP \fIfile2\fP ... \fIfileN\fP
XGet a file or set of files from the specified
X.IR sources .
X.I Source
Xcan be in one of two forms:
Xa filename on the remote host, if the host has already been specified,
Xor a string of the form
X.I host:filename
Xto specify both a host and filename at the same time.
XIf the latter form is used,
Xthe last hostname specified becomes the default for future transfers.
X.TP
X.B quit
XExit
X.IR tftp .
XAn end of file also exits.
X.TP
X.B verbose
XToggle verbose mode.
X.TP
X.B trace
XToggle packet tracing.
X.TP
X.B status
XShow current status.
X.TP
X\fBrexmt\fP \fIretransmission-timeout\fP
XSet the per-packet retransmission timeout, in seconds.
X.TP
X\fBtimeout\fP \fItotal-transmission-timeout\fP
XSet the total transmission timeout, in seconds.
X.TP
X.B ascii
XShorthand for "mode ascii"
X.TP
X.B binary
XShorthand for "mode binary"
X.TP
X\fB?\fP \ [ \fIcommand-name\fP ... ]
XPrint help information.
X.SH BUGS
X.PP
XBecause there is no user-login or validation within
Xthe
X.I TFTP
Xprotocol, the remote site will probably have some
Xsort of file-access restrictions in place.  The
Xexact methods are specific to each site and therefore
Xdifficult to document here.
SHAR_EOF
fi
cd ..
exit 0
#	End of shell archive



More information about the Comp.bugs.2bsd mailing list