Initial revision

This commit is contained in:
hpa 1999-09-26 06:32:41 +00:00
commit 09f975fa4c
18 changed files with 4696 additions and 0 deletions

31
MCONFIG.in Normal file
View file

@ -0,0 +1,31 @@
# Prefixes
prefix = @prefix@
exec_prefix = @exec_prefix@
# Directory for user binaries
BINDIR = @bindir@
# Man page tree
MANDIR = @mandir@
# System binaries
SBINDIR = @sbindir@
# Install into chroot area
INSTALLROOT =
# Install program
INSTALL = @INSTALL@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_DATA = @INSTALL_DATA@
# Compiler and compiler flags
CC = @CC@
CFLAGS = @CFLAGS@
# Link flags
LDFLAGS = @LDFLAGS@
# Libraries
LIBS = @LIBS@

22
MRULES Normal file
View file

@ -0,0 +1,22 @@
# Standard compilation rules (don't use make builtins)
.SUFFIXES: .c .cc .o .s .S .i
.c.o:
$(CC) $(CFLAGS) -c $<
.c.s:
$(CC) $(CFLAGS) -S -o $@ $<
.c.i:
$(CC) $(CFLAGS) -E -o $@ $<
.cc.o:
$(CXX) $(CXXFLAGS) -c $<
.cc.s:
$(CXX) $(CXXFLAGS) -S -o $@ $<
.cc.i:
$(CXX) $(CXXFLAGS) -E -o $@ $<

25
Makefile Normal file
View file

@ -0,0 +1,25 @@
# You can do "make SUB=blah" to make only a few, or edit here, or both
# You can also run make directly in the subdirs you want.
SUB = tftp tftpd
%.build:
$(MAKE) -C $(patsubst %.build, %, $@)
%.install:
$(MAKE) -C $(patsubst %.install, %, $@) install
%.clean:
$(MAKE) -C $(patsubst %.clean, %, $@) clean
%.spotless:
$(MAKE) -C $(patsubst %.spotless, %, $@) spotless
all: $(patsubst %, %.build, $(SUB))
install: $(patsubst %, %.install, $(SUB))
clean: $(patsubst %, %.clean, $(SUB))
spotless: $(patsubst %, %.spotless, $(SUB))
rm -f MCONFIG config.* *~

24
README Normal file
View file

@ -0,0 +1,24 @@
This is tftp-hpa-0.12; this version was put out by H. Peter Anvin
<hpa@zytor.com>.
This is a conglomerate of a number of versions of the BSD TFTP code,
ported to Linux, although it should work on mostly any POSIX-compliant
OS with sockets.
The core software was taken from OpenBSD (CVS source as of
1999-09-21). I believe this was the most secure source base available
at the time I obtained this code, and it included support for the -s
and -c options.
The un-BSD-ized Makefiles and a lot of the configure macros were taken
from netkit-tftp-0.10 by David Holland; I also followed this example
and modernized the code style throughout.
Patches by Markus Gutschke and Gero Kuhlmann were the basis for the
option negotiation as well as the "blksize" and "tsize" option
support, although I made a fair amount of mostly stylistic changes to
their code.
Adding the -r option (disable a specific option), the "timeout"
option, and converting to using autoconf for setup was my own code, as
are any bugs introduced in this merge.

67
aclocal.m4 vendored Normal file
View file

@ -0,0 +1,67 @@
dnl --------------------------------------------------------------------------
dnl PA_ADD_CFLAGS()
dnl
dnl Attempt to add the given option to CFLAGS, if it doesn't break compilation
dnl --------------------------------------------------------------------------
AC_DEFUN(PA_ADD_CFLAGS,
[AC_MSG_CHECKING([if $CC accepts $1])
pa_add_cflags__old_cflags="$CFLAGS"
CFLAGS="$CFLAGS $1"
AC_TRY_COMPILE([#include <stdio.h>],
[printf("Hello, World!\n");],
AC_MSG_RESULT([yes]),
AC_MSG_RESULT([no])
CFLAGS="$pa_add_cflags__old_cflags")])
dnl --------------------------------------------------------------------------
dnl PA_BSD_SIGNAL()
dnl
dnl Test for BSD signal semantics. Set shell variable BSD_SIGNAL=1 if OK.
dnl May modify CFLAGS and/or LIBS.
dnl --------------------------------------------------------------------------
AC_DEFUN(PA_CHECK_BSD_SIGNAL,
[AC_TRY_RUN([
#include <unistd.h>
#include <signal.h>
int count=0;
handle() { count++; }
int main() {
int pid=getpid();
signal(SIGINT, handle);
kill(pid,SIGINT);
kill(pid,SIGINT);
kill(pid,SIGINT);
if (count!=3) return 1;
return 0;
}
], BSD_SIGNAL=1)])
AC_DEFUN(PA_BSD_SIGNAL,
[AC_MSG_CHECKING([for BSD signal semantics])
PA_CHECK_BSD_SIGNAL()
if test -z "$BSD_SIGNAL"; then
AC_MSG_RESULT([no])
AC_MSG_CHECKING([if -D__USE_BSD_SIGNAL helps])
pa_bsd_signal__old_cflags="$CFLAGS"
CFLAGS="$CFLAGS -D__USE_BSD_SIGNAL"
PA_CHECK_BSD_SIGNAL()
if test -z "$BSD_SIGNAL"; then
AC_MSG_RESULT([no])
CFLAGS="$pa_bsd_signal__old_cflags"
AC_MSG_CHECKING([if -lbsd helps])
pa_bsd_signal__old_libs="$LIBS"
LIBS="$LIBS -lbsd"
PA_CHECK_BSD_SIGNAL()
if test -z "$BSD_SIGNAL"; then
AC_MSG_RESULT([no])
LIBS="$pa_bsd_signal__old_libs"
else
AC_MSG_RESULT([yes])
fi
else
AC_MSG_RESULT([yes])
fi
else
AC_MSG_RESULT([yes])
fi
])

1547
configure vendored Executable file

File diff suppressed because it is too large Load diff

28
configure.in Normal file
View file

@ -0,0 +1,28 @@
dnl
dnl autoconf input file to generate MCONFIG
dnl
AC_INIT(MCONFIG.in)
AC_PROG_CC
AC_C_CONST
AC_C_INLINE
PA_ADD_CFLAGS(-Wall)
PA_ADD_CFLAGS(-W)
PA_ADD_CFLAGS(-Wpointer-arith)
PA_ADD_CFLAGS(-Wbad-function-cast)
PA_ADD_CFLAGS(-Wcast-equal)
PA_ADD_CFLAGS(-Wstrict-prototypes)
PA_ADD_CFLAGS(-Wmissing-prototypes)
PA_ADD_CFLAGS(-Wmissing-declarations)
PA_ADD_CFLAGS(-Wnested-externs)
PA_ADD_CFLAGS(-Winline)
PA_ADD_CFLAGS(-Wcast-align)
PA_ADD_CFLAGS(-pipe)
PA_BSD_SIGNAL()
AC_PROG_INSTALL
AC_OUTPUT(MCONFIG)

238
install-sh Executable file
View file

@ -0,0 +1,238 @@
#! /bin/sh
#
# install - install a program, script, or datafile
# This comes from X11R5.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# `make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.
#
# set DOITPROG to echo to test this script
# Don't use :- since 4.3BSD and earlier shells don't like it.
doit="${DOITPROG-}"
# put in absolute paths if you don't have them in your path; or use env. vars.
mvprog="${MVPROG-mv}"
cpprog="${CPPROG-cp}"
chmodprog="${CHMODPROG-chmod}"
chownprog="${CHOWNPROG-chown}"
chgrpprog="${CHGRPPROG-chgrp}"
stripprog="${STRIPPROG-strip}"
rmprog="${RMPROG-rm}"
mkdirprog="${MKDIRPROG-mkdir}"
tranformbasename=""
transform_arg=""
instcmd="$mvprog"
chmodcmd="$chmodprog 0755"
chowncmd=""
chgrpcmd=""
stripcmd=""
rmcmd="$rmprog -f"
mvcmd="$mvprog"
src=""
dst=""
dir_arg=""
while [ x"$1" != x ]; do
case $1 in
-c) instcmd="$cpprog"
shift
continue;;
-d) dir_arg=true
shift
continue;;
-m) chmodcmd="$chmodprog $2"
shift
shift
continue;;
-o) chowncmd="$chownprog $2"
shift
shift
continue;;
-g) chgrpcmd="$chgrpprog $2"
shift
shift
continue;;
-s) stripcmd="$stripprog"
shift
continue;;
-t=*) transformarg=`echo $1 | sed 's/-t=//'`
shift
continue;;
-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
shift
continue;;
*) if [ x"$src" = x ]
then
src=$1
else
# this colon is to work around a 386BSD /bin/sh bug
:
dst=$1
fi
shift
continue;;
esac
done
if [ x"$src" = x ]
then
echo "install: no input file specified"
exit 1
else
true
fi
if [ x"$dir_arg" != x ]; then
dst=$src
src=""
if [ -d $dst ]; then
instcmd=:
else
instcmd=mkdir
fi
else
# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
# might cause directories to be created, which would be especially bad
# if $src (and thus $dsttmp) contains '*'.
if [ -f $src -o -d $src ]
then
true
else
echo "install: $src does not exist"
exit 1
fi
if [ x"$dst" = x ]
then
echo "install: no destination specified"
exit 1
else
true
fi
# If destination is a directory, append the input filename; if your system
# does not like double slashes in filenames, you may need to add some logic
if [ -d $dst ]
then
dst="$dst"/`basename $src`
else
true
fi
fi
## this sed command emulates the dirname command
dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
# Make sure that the destination directory exists.
# this part is taken from Noah Friedman's mkinstalldirs script
# Skip lots of stat calls in the usual case.
if [ ! -d "$dstdir" ]; then
defaultIFS='
'
IFS="${IFS-${defaultIFS}}"
oIFS="${IFS}"
# Some sh's can't handle IFS=/ for some reason.
IFS='%'
set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
IFS="${oIFS}"
pathcomp=''
while [ $# -ne 0 ] ; do
pathcomp="${pathcomp}${1}"
shift
if [ ! -d "${pathcomp}" ] ;
then
$mkdirprog "${pathcomp}"
else
true
fi
pathcomp="${pathcomp}/"
done
fi
if [ x"$dir_arg" != x ]
then
$doit $instcmd $dst &&
if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
else
# If we're going to rename the final executable, determine the name now.
if [ x"$transformarg" = x ]
then
dstfile=`basename $dst`
else
dstfile=`basename $dst $transformbasename |
sed $transformarg`$transformbasename
fi
# don't allow the sed command to completely eliminate the filename
if [ x"$dstfile" = x ]
then
dstfile=`basename $dst`
else
true
fi
# Make a temp file name in the proper directory.
dsttmp=$dstdir/#inst.$$#
# Move or copy the file name to the temp name
$doit $instcmd $src $dsttmp &&
trap "rm -f ${dsttmp}" 0 &&
# and set any options; do chmod last to preserve setuid bits
# If any of these fail, we abort the whole thing. If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $instcmd $src $dsttmp" command.
if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
# Now rename the file to the real destination.
$doit $rmcmd -f $dstdir/$dstfile &&
$doit $mvcmd $dsttmp $dstdir/$dstfile
fi &&
exit 0

21
tftp/Makefile Normal file
View file

@ -0,0 +1,21 @@
all: tftp
include ../MCONFIG
include ../MRULES
OBJS = tftp.o main.o tftpsubs.o
tftp: $(OBJS)
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
$(OBJS): tftpsubs.h
install: tftp
$(INSTALL_PROGRAM) -s tftp $(INSTALLROOT)$(BINDIR)
$(INSTALL_DATA) tftp.1 $(INSTALLROOT)$(MANDIR)/man1
clean:
rm -f *.o tftp
spotless: clean
rm -f *~

42
tftp/extern.h Normal file
View file

@ -0,0 +1,42 @@
/* $Id$ */
/* $OpenBSD: extern.h,v 1.2 1996/06/26 05:40:33 deraadt Exp $ */
/* $NetBSD: extern.h,v 1.2 1994/12/08 09:51:24 jtc Exp $ */
/*
* Copyright (c) 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)extern.h 8.1 (Berkeley) 6/6/93
*/
void recvfile (int, char *, char *);
void sendfile (int, char *, char *);

704
tftp/main.c Normal file
View file

@ -0,0 +1,704 @@
/* $OpenBSD: main.c,v 1.4 1997/01/17 07:13:30 millert Exp $ */
/* $NetBSD: main.c,v 1.6 1995/05/21 16:54:10 mycroft Exp $ */
/*
* Copyright (c) 1983, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static const char *copyright =
"@(#) Copyright (c) 1983, 1993\n\
The Regents of the University of California. All rights reserved.\n";
/* static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/6/93"; */
/* static char rcsid[] = "$OpenBSD: main.c,v 1.4 1997/01/17 07:13:30 millert Exp $"; */
static const char *rcsid = "tftp-hpa $Id$";
#endif /* not lint */
/* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
/*
* TFTP User Program -- Command Interface.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "extern.h"
#define TIMEOUT 5 /* secs between rexmt's */
#define LBUFLEN 200 /* size of input buffer */
struct sockaddr_in peeraddr;
int f;
short port;
int trace;
int verbose;
int connected;
char mode[32];
char line[LBUFLEN];
int margc;
char *margv[20];
char *prompt = "tftp";
jmp_buf toplevel;
void intr(int);
struct servent *sp;
void get (int, char **);
void help (int, char **);
void modecmd (int, char **);
void put (int, char **);
void quit (int, char **);
void setascii (int, char **);
void setbinary (int, char **);
void setpeer (int, char **);
void setrexmt (int, char **);
void settimeout (int, char **);
void settrace (int, char **);
void setverbose (int, char **);
void status (int, char **);
static void command (void);
static void getusage (char *);
static void makeargv (void);
static void putusage (char *);
static void settftpmode (char *);
#define HELPINDENT (sizeof("connect"))
struct cmd {
char *name;
char *help;
void (*handler) (int, char **);
};
char vhelp[] = "toggle verbose mode";
char thelp[] = "toggle packet tracing";
char chelp[] = "connect to remote tftp";
char qhelp[] = "exit tftp";
char hhelp[] = "print help information";
char shelp[] = "send file";
char rhelp[] = "receive file";
char mhelp[] = "set file transfer mode";
char sthelp[] = "show current status";
char xhelp[] = "set per-packet retransmission timeout";
char ihelp[] = "set total retransmission timeout";
char ashelp[] = "set mode to netascii";
char bnhelp[] = "set mode to octet";
struct cmd cmdtab[] = {
{ "connect", chelp, setpeer },
{ "mode", mhelp, modecmd },
{ "put", shelp, put },
{ "get", rhelp, get },
{ "quit", qhelp, quit },
{ "verbose", vhelp, setverbose },
{ "trace", thelp, settrace },
{ "status", sthelp, status },
{ "binary", bnhelp, setbinary },
{ "ascii", ashelp, setascii },
{ "rexmt", xhelp, setrexmt },
{ "timeout", ihelp, settimeout },
{ "?", hhelp, help },
{ 0 }
};
struct cmd *getcmd(char *);
char *tail(char *);
int
main(int argc, char *argv[])
{
struct sockaddr_in s_in;
sp = getservbyname("tftp", "udp");
if (sp == 0) {
fprintf(stderr, "tftp: udp/tftp: unknown service\n");
exit(1);
}
f = socket(AF_INET, SOCK_DGRAM, 0);
if (f < 0) {
perror("tftp: socket");
exit(3);
}
bzero((char *)&s_in, sizeof (s_in));
s_in.sin_family = AF_INET;
if (bind(f, (struct sockaddr *)&s_in, sizeof (s_in)) < 0) {
perror("tftp: bind");
exit(1);
}
strcpy(mode, "netascii");
signal(SIGINT, intr);
if (argc > 1) {
if (setjmp(toplevel) != 0)
exit(0);
setpeer(argc, argv);
}
if (setjmp(toplevel) != 0)
(void)putchar('\n');
command();
return 0; /* Never reached */
}
char hostname[MAXHOSTNAMELEN];
void
setpeer(int argc, char *argv[])
{
struct hostent *host;
if (argc < 2) {
strcpy(line, "Connect ");
printf("(to) ");
fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
makeargv();
argc = margc;
argv = margv;
}
if ((argc < 2) || (argc > 3)) {
printf("usage: %s host-name [port]\n", argv[0]);
return;
}
if (inet_aton(argv[1], &peeraddr.sin_addr) != 0) {
peeraddr.sin_family = AF_INET;
(void) strncpy(hostname, argv[1], sizeof hostname);
hostname[sizeof(hostname)-1] = '\0';
} else {
host = gethostbyname(argv[1]);
if (host == 0) {
connected = 0;
printf("%s: unknown host\n", argv[1]);
return;
}
peeraddr.sin_family = host->h_addrtype;
bcopy(host->h_addr, &peeraddr.sin_addr, host->h_length);
(void) strcpy(hostname, host->h_name);
}
port = sp->s_port;
if (argc == 3) {
port = atoi(argv[2]);
if (port < 0) {
printf("%s: bad port number\n", argv[2]);
connected = 0;
return;
}
port = htons(port);
}
connected = 1;
}
struct modes {
char *m_name;
char *m_mode;
} modes[] = {
{ "ascii", "netascii" },
{ "netascii", "netascii" },
{ "binary", "octet" },
{ "image", "octet" },
{ "octet", "octet" },
/* { "mail", "mail" }, */
{ 0, 0 }
};
void
modecmd(int argc, char *argv[])
{
struct modes *p;
char *sep;
if (argc < 2) {
printf("Using %s mode to transfer files.\n", mode);
return;
}
if (argc == 2) {
for (p = modes; p->m_name; p++)
if (strcmp(argv[1], p->m_name) == 0)
break;
if (p->m_name) {
settftpmode(p->m_mode);
return;
}
printf("%s: unknown mode\n", argv[1]);
/* drop through and print usage message */
}
printf("usage: %s [", argv[0]);
sep = " ";
for (p = modes; p->m_name; p++) {
printf("%s%s", sep, p->m_name);
if (*sep == ' ')
sep = " | ";
}
printf(" ]\n");
return;
}
void
setbinary(int argc, char *argv[])
{
settftpmode("octet");
}
void
setascii(int argc, char *argv[])
{
settftpmode("netascii");
}
static void
settftpmode(char *newmode)
{
strcpy(mode, newmode);
if (verbose)
printf("mode set to %s\n", mode);
}
/*
* Send file(s).
*/
void
put(int argc, char *argv[])
{
int fd;
int n;
char *cp, *targ;
if (argc < 2) {
strcpy(line, "send ");
printf("(file) ");
fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
makeargv();
argc = margc;
argv = margv;
}
if (argc < 2) {
putusage(argv[0]);
return;
}
targ = argv[argc - 1];
if (strchr(argv[argc - 1], ':')) {
char *cp;
struct hostent *hp;
for (n = 1; n < argc - 1; n++)
if (strchr(argv[n], ':')) {
putusage(argv[0]);
return;
}
cp = argv[argc - 1];
targ = strchr(cp, ':');
*targ++ = 0;
hp = gethostbyname(cp);
if (hp == NULL) {
fprintf(stderr, "tftp: %s: ", cp);
herror((char *)NULL);
return;
}
bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr, hp->h_length);
peeraddr.sin_family = hp->h_addrtype;
connected = 1;
strcpy(hostname, hp->h_name);
}
if (!connected) {
printf("No target machine specified.\n");
return;
}
if (argc < 4) {
cp = argc == 2 ? tail(targ) : argv[1];
fd = open(cp, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "tftp: "); perror(cp);
return;
}
if (verbose)
printf("putting %s to %s:%s [%s]\n",
cp, hostname, targ, mode);
peeraddr.sin_port = port;
sendfile(fd, targ, mode);
return;
}
/* this assumes the target is a directory */
/* on a remote unix system. hmmmm. */
cp = strchr(targ, '\0');
*cp++ = '/';
for (n = 1; n < argc - 1; n++) {
strcpy(cp, tail(argv[n]));
fd = open(argv[n], O_RDONLY);
if (fd < 0) {
fprintf(stderr, "tftp: "); perror(argv[n]);
continue;
}
if (verbose)
printf("putting %s to %s:%s [%s]\n",
argv[n], hostname, targ, mode);
peeraddr.sin_port = port;
sendfile(fd, targ, mode);
}
}
static void
putusage(char *s)
{
printf("usage: %s file ... host:target, or\n", s);
printf(" %s file ... target (when already connected)\n", s);
}
/*
* Receive file(s).
*/
void
get(int argc, char *argv[])
{
int fd;
int n;
char *cp;
char *src;
if (argc < 2) {
strcpy(line, "get ");
printf("(files) ");
fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
makeargv();
argc = margc;
argv = margv;
}
if (argc < 2) {
getusage(argv[0]);
return;
}
if (!connected) {
for (n = 1; n < argc ; n++)
if (strchr(argv[n], ':') == 0) {
getusage(argv[0]);
return;
}
}
for (n = 1; n < argc ; n++) {
src = strchr(argv[n], ':');
if (src == NULL)
src = argv[n];
else {
struct hostent *hp;
*src++ = 0;
hp = gethostbyname(argv[n]);
if (hp == NULL) {
fprintf(stderr, "tftp: %s: ", argv[n]);
herror((char *)NULL);
continue;
}
bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr,
hp->h_length);
peeraddr.sin_family = hp->h_addrtype;
connected = 1;
strcpy(hostname, hp->h_name);
}
if (argc < 4) {
cp = argc == 3 ? argv[2] : tail(src);
fd = creat(cp, 0644);
if (fd < 0) {
fprintf(stderr, "tftp: "); perror(cp);
return;
}
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode);
peeraddr.sin_port = port;
recvfile(fd, src, mode);
break;
}
cp = tail(src); /* new .. jdg */
fd = creat(cp, 0644);
if (fd < 0) {
fprintf(stderr, "tftp: "); perror(cp);
continue;
}
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode);
peeraddr.sin_port = port;
recvfile(fd, src, mode);
}
}
static void
getusage(char *s)
{
printf("usage: %s host:file host:file ... file, or\n", s);
printf(" %s file file ... file if connected\n", s);
}
int rexmtval = TIMEOUT;
void
setrexmt(int argc, char *argv[])
{
int t;
if (argc < 2) {
strcpy(line, "Rexmt-timeout ");
printf("(value) ");
fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
makeargv();
argc = margc;
argv = margv;
}
if (argc != 2) {
printf("usage: %s value\n", argv[0]);
return;
}
t = atoi(argv[1]);
if (t < 0)
printf("%s: bad value\n", argv[1]);
else
rexmtval = t;
}
int maxtimeout = 5 * TIMEOUT;
void
settimeout(int argc, char *argv[])
{
int t;
if (argc < 2) {
strcpy(line, "Maximum-timeout ");
printf("(value) ");
fgets(&line[strlen(line)], LBUFLEN-strlen(line), stdin);
makeargv();
argc = margc;
argv = margv;
}
if (argc != 2) {
printf("usage: %s value\n", argv[0]);
return;
}
t = atoi(argv[1]);
if (t < 0)
printf("%s: bad value\n", argv[1]);
else
maxtimeout = t;
}
void
status(int argc, char *argv[])
{
if (connected)
printf("Connected to %s.\n", hostname);
else
printf("Not connected.\n");
printf("Mode: %s Verbose: %s Tracing: %s\n", mode,
verbose ? "on" : "off", trace ? "on" : "off");
printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
rexmtval, maxtimeout);
}
void
intr(int sig)
{
signal(SIGALRM, SIG_IGN);
alarm(0);
longjmp(toplevel, -1);
}
char *
tail(char *filename)
{
char *s;
while (*filename) {
s = strrchr(filename, '/');
if (s == NULL)
break;
if (s[1])
return (s + 1);
*s = '\0';
}
return (filename);
}
/*
* Command parser.
*/
static void
command(void)
{
struct cmd *c;
for (;;) {
printf("%s> ", prompt);
if (fgets(line, LBUFLEN, stdin) == 0) {
if (feof(stdin)) {
exit(0);
} else {
continue;
}
}
if ((line[0] == 0) || (line[0] == '\n'))
continue;
makeargv();
if (margc == 0)
continue;
c = getcmd(margv[0]);
if (c == (struct cmd *)-1) {
printf("?Ambiguous command\n");
continue;
}
if (c == 0) {
printf("?Invalid command\n");
continue;
}
(*c->handler)(margc, margv);
}
}
struct cmd *
getcmd(char *name)
{
char *p, *q;
struct cmd *c, *found;
int nmatches, longest;
longest = 0;
nmatches = 0;
found = 0;
for (c = cmdtab; (p = c->name) != NULL; c++) {
for (q = name; *q == *p++; q++)
if (*q == 0) /* exact match? */
return (c);
if (!*q) { /* the name was a prefix */
if (q - name > longest) {
longest = q - name;
nmatches = 1;
found = c;
} else if (q - name == longest)
nmatches++;
}
}
if (nmatches > 1)
return ((struct cmd *)-1);
return (found);
}
/*
* Slice a string up into argc/argv.
*/
static void
makeargv(void)
{
char *cp;
char **argp = margv;
margc = 0;
for (cp = line; *cp;) {
while (isspace(*cp))
cp++;
if (*cp == '\0')
break;
*argp++ = cp;
margc += 1;
while (*cp != '\0' && !isspace(*cp))
cp++;
if (*cp == '\0')
break;
*cp++ = '\0';
}
*argp++ = 0;
}
void
quit(int argc, char *argv[])
{
exit(0);
}
/*
* Help command.
*/
void
help(int argc, char *argv[])
{
struct cmd *c;
if (argc == 1) {
printf("Commands may be abbreviated. Commands are:\n\n");
for (c = cmdtab; c->name; c++)
printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help);
return;
}
while (--argc > 0) {
char *arg;
arg = *++argv;
c = getcmd(arg);
if (c == (struct cmd *)-1)
printf("?Ambiguous help command %s\n", arg);
else if (c == (struct cmd *)0)
printf("?Invalid help command %s\n", arg);
else
printf("%s\n", c->help);
}
}
void
settrace(int argc, char *argv[])
{
trace = !trace;
printf("Packet tracing %s.\n", trace ? "on" : "off");
}
void
setverbose(int argc, char *argv[])
{
verbose = !verbose;
printf("Verbose mode %s.\n", verbose ? "on" : "off");
}

177
tftp/tftp.1 Normal file
View file

@ -0,0 +1,177 @@
.\" $OpenBSD: tftp.1,v 1.4 1999/06/05 01:21:43 aaron Exp $
.\" $NetBSD: tftp.1,v 1.5 1995/08/18 14:45:44 pk Exp $
.\"
.\" Copyright (c) 1990, 1993, 1994
.\" The Regents of the University of California. All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\" 3. All advertising materials mentioning features or use of this software
.\" must display the following acknowledgement:
.\" This product includes software developed by the University of
.\" California, Berkeley and its contributors.
.\" 4. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" @(#)tftp.1 8.2 (Berkeley) 4/18/94
.\"
.Dd April 18, 1994
.Dt TFTP 1
.Os
.Sh NAME
.Nm tftp
.Nd trivial file transfer program
.Sh SYNOPSIS
.Nm tftp
.Op Ar host
.Sh DESCRIPTION
.Nm tftp
is the user interface to the Internet
.Tn TFTP
(Trivial File Transfer Protocol),
which allows users to transfer files to and from a remote machine.
The remote
.Ar host
may be specified on the command line, in which case
.Nm tftp
uses
.Ar host
as the default host for future transfers (see the
.Ic connect
command below).
.Sh COMMANDS
Once
.Nm tftp
is running, it issues the prompt
.Ql tftp>
and recognizes the following commands:
.Pp
.Bl -tag -width verbose -compact
.It Ic \&? Ar command-name Op Ar ...
Print help information.
.Pp
.It Ic ascii
Shorthand for
.Ic mode ascii .
.Pp
.It Ic binary
Shorthand for
.Ic mode binary .
.Pp
.It Ic connect Ar host Op Ar port
Set the
.Ar host
(and optionally
.Ar port )
for transfers.
Note that the
.Tn TFTP
protocol, unlike the
.Tn FTP
protocol,
does not maintain connections between transfers; thus, the
.Ic connect
command does not actually create a connection,
but merely remembers what host is to be used for transfers.
You do not have to use the
.Ic connect
command; the remote host can be specified as part of the
.Ic get
or
.Ic put
commands.
.Pp
.It Ic get Ar filename
.It Ic get Ar remotename localname
.It Ic get Ar file Op Ar ...
Get a file or set of files from the specified
.Ar sources .
.Ar source
can be in one of two forms:
a filename on the remote host, if the host has already been specified,
or a string of the form
.Ar hosts:filename
to specify both a host and filename at the same time.
If the latter form is used,
the last hostname specified becomes the default for future transfers.
.Pp
.It Ic mode Ar transfer-mode
Set the mode for transfers;
.Ar transfer-mode
may be one of
.Ic ascii
or
.Ic binary .
The default is
.Ic ascii .
.Pp
.It Ic put Ar file
.It Ic put Ar localfile remotefile
.It Ic put Ar file1 file2 ... fileN remote-directory
Put a file or set of files to the specified
remote file or directory.
The destination
can be in one of two forms:
a filename on the remote host, if the host has already been specified,
or a string of the form
.Ar hosts:filename
to specify both a host and filename at the same time.
If the latter form is used,
the hostname specified becomes the default for future transfers.
If the remote-directory form is used, the remote host is
assumed to be a
.Tn UNIX
machine.
.Pp
.It Ic quit
Exit
.Nm tftp .
An end-of-file also exits.
.Pp
.It Ic rexmt Ar retransmission-timeout
Set the per-packet retransmission timeout, in seconds.
.Pp
.It Ic status
Show current status.
.Pp
.It Ic timeout Ar total-transmission-timeout
Set the total transmission timeout, in seconds.
.Pp
.It Ic trace
Toggle packet tracing.
.Pp
.It Ic verbose
Toggle verbose mode.
.El
.Sh BUGS
Because there is no user login or validation within
the
.Tn TFTP
protocol, the remote site will probably have some
sort of file access restrictions in place. The
exact methods are specific to each site and therefore
difficult to document here.
.Sh HISTORY
The
.Nm
command appeared in
.Bx 4.3 .

453
tftp/tftp.c Normal file
View file

@ -0,0 +1,453 @@
/* $Id$ */
/* $OpenBSD: tftp.c,v 1.4 1997/08/06 06:43:45 deraadt Exp $ */
/* $NetBSD: tftp.c,v 1.5 1995/04/29 05:55:25 cgd Exp $ */
/*
* Copyright (c) 1983, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
/* static char sccsid[] = "@(#)tftp.c 8.1 (Berkeley) 6/6/93"; */
/* static char rcsid[] = "$OpenBSD: tftp.c,v 1.4 1997/08/06 06:43:45 deraadt Exp $"; */
static const char *rcsid = "tftp-hpa $Id$";
#endif /* not lint */
/* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
/*
* TFTP User Program -- Protocol Machines
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/tftp.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "extern.h"
#include "tftpsubs.h"
extern int errno;
extern struct sockaddr_in peeraddr; /* filled in by main */
extern int f; /* the opened socket */
extern int trace;
extern int verbose;
extern int rexmtval;
extern int maxtimeout;
#ifndef EOPTNEG
#define EOPTNEG 8
#endif
#ifndef OACK
#define OACK 6
#endif
#define PKTSIZE SEGSIZE+4
char ackbuf[PKTSIZE];
int timeout;
jmp_buf toplevel;
jmp_buf timeoutbuf;
static void nak(int);
static int makerequest(int, const char *, struct tftphdr *, const char *);
static void printstats(const char *, unsigned long);
static void startclock(void);
static void stopclock(void);
static void timer(int);
static void tpacket(const char *, struct tftphdr *, int);
/*
* Send the requested file.
*/
void
sendfile(int fd, char *name, char *mode)
{
struct tftphdr *ap; /* data and ack packets */
struct tftphdr *dp;
int n;
volatile int block, size, convert;
volatile unsigned long amount;
struct sockaddr_in from;
int fromlen;
FILE *file;
startclock(); /* start stat's clock */
dp = r_init(); /* reset fillbuf/read-ahead code */
ap = (struct tftphdr *)ackbuf;
file = fdopen(fd, "r");
convert = !strcmp(mode, "netascii");
block = 0;
amount = 0;
signal(SIGALRM, timer);
do {
if (block == 0)
size = makerequest(WRQ, name, dp, mode) - 4;
else {
/* size = read(fd, dp->th_data, SEGSIZE); */
size = readit(file, &dp, convert);
if (size < 0) {
nak(errno + 100);
break;
}
dp->th_opcode = htons((u_short)DATA);
dp->th_block = htons((u_short)block);
}
timeout = 0;
(void) setjmp(timeoutbuf);
send_data:
if (trace)
tpacket("sent", dp, size + 4);
n = sendto(f, dp, size + 4, 0,
(struct sockaddr *)&peeraddr, sizeof(peeraddr));
if (n != size + 4) {
perror("tftp: sendto");
goto abort;
}
read_ahead(file, convert);
for ( ; ; ) {
alarm(rexmtval);
do {
fromlen = sizeof(from);
n = recvfrom(f, ackbuf, sizeof(ackbuf), 0,
(struct sockaddr *)&from, &fromlen);
} while (n <= 0);
alarm(0);
if (n < 0) {
perror("tftp: recvfrom");
goto abort;
}
peeraddr.sin_port = from.sin_port; /* added */
if (trace)
tpacket("received", ap, n);
/* should verify packet came from server */
ap->th_opcode = ntohs(ap->th_opcode);
ap->th_block = ntohs(ap->th_block);
if (ap->th_opcode == ERROR) {
printf("Error code %d: %s\n", ap->th_code,
ap->th_msg);
goto abort;
}
if (ap->th_opcode == ACK) {
int j;
if (ap->th_block == block) {
break;
}
/* On an error, try to synchronize
* both sides.
*/
j = synchnet(f);
if (j && trace) {
printf("discarded %d packets\n",
j);
}
if (ap->th_block == (block-1)) {
goto send_data;
}
}
}
if (block > 0)
amount += size;
block++;
} while (size == SEGSIZE || block == 1);
abort:
fclose(file);
stopclock();
if (amount > 0)
printstats("Sent", amount);
}
/*
* Receive a file.
*/
void
recvfile(int fd, char *name, char *mode)
{
struct tftphdr *ap;
struct tftphdr *dp;
int n;
volatile int block, size, firsttrip;
volatile unsigned long amount;
struct sockaddr_in from;
int fromlen;
FILE *file;
volatile int convert; /* true if converting crlf -> lf */
startclock();
dp = w_init();
ap = (struct tftphdr *)ackbuf;
file = fdopen(fd, "w");
convert = !strcmp(mode, "netascii");
block = 1;
firsttrip = 1;
amount = 0;
signal(SIGALRM, timer);
do {
if (firsttrip) {
size = makerequest(RRQ, name, ap, mode);
firsttrip = 0;
} else {
ap->th_opcode = htons((u_short)ACK);
ap->th_block = htons((u_short)(block));
size = 4;
block++;
}
timeout = 0;
(void) setjmp(timeoutbuf);
send_ack:
if (trace)
tpacket("sent", ap, size);
if (sendto(f, ackbuf, size, 0, (struct sockaddr *)&peeraddr,
sizeof(peeraddr)) != size) {
alarm(0);
perror("tftp: sendto");
goto abort;
}
write_behind(file, convert);
for ( ; ; ) {
alarm(rexmtval);
do {
fromlen = sizeof(from);
n = recvfrom(f, dp, PKTSIZE, 0,
(struct sockaddr *)&from, &fromlen);
} while (n <= 0);
alarm(0);
if (n < 0) {
perror("tftp: recvfrom");
goto abort;
}
peeraddr.sin_port = from.sin_port; /* added */
if (trace)
tpacket("received", dp, n);
/* should verify client address */
dp->th_opcode = ntohs(dp->th_opcode);
dp->th_block = ntohs(dp->th_block);
if (dp->th_opcode == ERROR) {
printf("Error code %d: %s\n", dp->th_code,
dp->th_msg);
goto abort;
}
if (dp->th_opcode == DATA) {
int j;
if (dp->th_block == block) {
break; /* have next packet */
}
/* On an error, try to synchronize
* both sides.
*/
j = synchnet(f);
if (j && trace) {
printf("discarded %d packets\n", j);
}
if (dp->th_block == (block-1)) {
goto send_ack; /* resend ack */
}
}
}
/* size = write(fd, dp->th_data, n - 4); */
size = writeit(file, &dp, n - 4, convert);
if (size < 0) {
nak(errno + 100);
break;
}
amount += size;
} while (size == SEGSIZE);
abort: /* ok to ack, since user */
ap->th_opcode = htons((u_short)ACK); /* has seen err msg */
ap->th_block = htons((u_short)block);
(void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peeraddr,
sizeof(peeraddr));
write_behind(file, convert); /* flush last buffer */
fclose(file);
stopclock();
if (amount > 0)
printstats("Received", amount);
}
static int
makerequest(int request, const char *name,
struct tftphdr *tp, const char *mode)
{
char *cp;
tp->th_opcode = htons((u_short)request);
cp = tp->th_stuff;
strcpy(cp, name);
cp += strlen(name);
*cp++ = '\0';
strcpy(cp, mode);
cp += strlen(mode);
*cp++ = '\0';
return (cp - (char *)tp);
}
struct errmsg {
int e_code;
char *e_msg;
} errmsgs[] = {
{ EUNDEF, "Undefined error code" },
{ ENOTFOUND, "File not found" },
{ EACCESS, "Access violation" },
{ ENOSPACE, "Disk full or allocation exceeded" },
{ EBADOP, "Illegal TFTP operation" },
{ EBADID, "Unknown transfer ID" },
{ EEXISTS, "File already exists" },
{ ENOUSER, "No such user" },
{ EOPTNEG, "Failure to negotiate RFC2347 options" },
{ -1, 0 }
};
/*
* Send a nak packet (error message).
* Error code passed in is one of the
* standard TFTP codes, or a UNIX errno
* offset by 100.
*/
static void
nak(int error)
{
struct errmsg *pe;
struct tftphdr *tp;
int length;
tp = (struct tftphdr *)ackbuf;
tp->th_opcode = htons((u_short)ERROR);
tp->th_code = htons((u_short)error);
for (pe = errmsgs; pe->e_code >= 0; pe++)
if (pe->e_code == error)
break;
if (pe->e_code < 0) {
pe->e_msg = strerror(error - 100);
tp->th_code = EUNDEF;
}
strcpy(tp->th_msg, pe->e_msg);
length = strlen(pe->e_msg) + 4;
if (trace)
tpacket("sent", tp, length);
if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr,
sizeof(peeraddr)) != length)
perror("nak");
}
static void
tpacket(const char *s, struct tftphdr *tp, int n)
{
static char *opcodes[] =
{ "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
char *cp, *file;
u_short op = ntohs(tp->th_opcode);
if (op < RRQ || op > ERROR)
printf("%s opcode=%x ", s, op);
else
printf("%s %s ", s, opcodes[op]);
switch (op) {
case RRQ:
case WRQ:
n -= 2;
file = cp = tp->th_stuff;
cp = strchr(cp, '\0');
printf("<file=%s, mode=%s>\n", file, cp + 1);
break;
case DATA:
printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
break;
case ACK:
printf("<block=%d>\n", ntohs(tp->th_block));
break;
case ERROR:
printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
break;
}
}
struct timeval tstart;
struct timeval tstop;
static void
startclock(void)
{
(void)gettimeofday(&tstart, NULL);
}
static void
stopclock(void)
{
(void)gettimeofday(&tstop, NULL);
}
static void
printstats(const char *direction, unsigned long amount)
{
double delta;
/* compute delta in 1/10's second units */
delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) -
((tstart.tv_sec*10.)+(tstart.tv_usec/100000));
delta = delta/10.; /* back to seconds */
printf("%s %lu bytes in %.1f seconds", direction, amount, delta);
if (verbose)
printf(" [%.0f bits/sec]", (amount*8.)/delta);
putchar('\n');
}
static void
timer(int sig)
{
int save_errno = errno;
timeout += rexmtval;
if (timeout >= maxtimeout) {
printf("Transfer timed out.\n");
errno = save_errno;
longjmp(toplevel, -1);
}
errno = save_errno;
longjmp(timeoutbuf, 1);
}

271
tftp/tftpsubs.c Normal file
View file

@ -0,0 +1,271 @@
/* $OpenBSD: tftpsubs.c,v 1.2 1996/06/26 05:40:36 deraadt Exp $ */
/* $NetBSD: tftpsubs.c,v 1.3 1994/12/08 09:51:31 jtc Exp $ */
/*
* Copyright (c) 1983, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
#if 0
static char sccsid[] = "@(#)tftpsubs.c 8.1 (Berkeley) 6/6/93";
#endif
static char rcsid[] = "$OpenBSD: tftpsubs.c,v 1.2 1996/06/26 05:40:36 deraadt Exp $";
#endif /* not lint */
/* Simple minded read-ahead/write-behind subroutines for tftp user and
server. Written originally with multiple buffers in mind, but current
implementation has two buffer logic wired in.
Todo: add some sort of final error check so when the write-buffer
is finally flushed, the caller can detect if the disk filled up
(or had an i/o error) and return a nak to the other side.
Jim Guyton 10/85
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/tftp.h>
#include <stdio.h>
#include <unistd.h>
#include "tftpsubs.h"
#define PKTSIZE MAX_SEGSIZE+4 /* should be moved to tftp.h */
int segsize = SEGSIZE; /* Default segsize */
struct bf {
int counter; /* size of data in buffer, or flag */
char buf[PKTSIZE]; /* room for data packet */
} bfs[2];
/* Values for bf.counter */
#define BF_ALLOC -3 /* alloc'd but not yet filled */
#define BF_FREE -2 /* free */
/* [-1 .. segsize] = size of data in the data buffer */
static int nextone; /* index of next buffer to use */
static int current; /* index of buffer in use */
/* control flags for crlf conversions */
int newline = 0; /* fillbuf: in middle of newline expansion */
int prevchar = -1; /* putbuf: previous char (cr check) */
static struct tftphdr *rw_init(int);
struct tftphdr *w_init() { return rw_init(0); } /* write-behind */
struct tftphdr *r_init() { return rw_init(1); } /* read-ahead */
/* init for either read-ahead or write-behind */
/* x == zero for write-behind, one for read-head */
static struct tftphdr *
rw_init(int x)
{
newline = 0; /* init crlf flag */
prevchar = -1;
bfs[0].counter = BF_ALLOC; /* pass out the first buffer */
current = 0;
bfs[1].counter = BF_FREE;
nextone = x; /* ahead or behind? */
return (struct tftphdr *)bfs[0].buf;
}
/* Have emptied current buffer by sending to net and getting ack.
Free it and return next buffer filled with data.
*/
int
readit(FILE *file, struct tftphdr **dpp, int convert)
{
struct bf *b;
bfs[current].counter = BF_FREE; /* free old one */
current = !current; /* "incr" current */
b = &bfs[current]; /* look at new buffer */
if (b->counter == BF_FREE) /* if it's empty */
read_ahead(file, convert); /* fill it */
/* assert(b->counter != BF_FREE);*//* check */
*dpp = (struct tftphdr *)b->buf; /* set caller's ptr */
return b->counter;
}
/*
* fill the input buffer, doing ascii conversions if requested
* conversions are lf -> cr,lf and cr -> cr, nul
*/
void
read_ahead(FILE *file, int convert)
{
int i;
char *p;
int c;
struct bf *b;
struct tftphdr *dp;
b = &bfs[nextone]; /* look at "next" buffer */
if (b->counter != BF_FREE) /* nop if not free */
return;
nextone = !nextone; /* "incr" next buffer ptr */
dp = (struct tftphdr *)b->buf;
if (convert == 0) {
b->counter = read(fileno(file), dp->th_data, segsize);
return;
}
p = dp->th_data;
for (i = 0 ; i < segsize; i++) {
if (newline) {
if (prevchar == '\n')
c = '\n'; /* lf to cr,lf */
else c = '\0'; /* cr to cr,nul */
newline = 0;
}
else {
c = getc(file);
if (c == EOF) break;
if (c == '\n' || c == '\r') {
prevchar = c;
c = '\r';
newline = 1;
}
}
*p++ = c;
}
b->counter = (int)(p - dp->th_data);
}
/* Update count associated with the buffer, get new buffer
from the queue. Calls write_behind only if next buffer not
available.
*/
int
writeit(FILE *file, struct tftphdr **dpp, int ct, int convert)
{
bfs[current].counter = ct; /* set size of data to write */
current = !current; /* switch to other buffer */
if (bfs[current].counter != BF_FREE) /* if not free */
(void)write_behind(file, convert); /* flush it */
bfs[current].counter = BF_ALLOC; /* mark as alloc'd */
*dpp = (struct tftphdr *)bfs[current].buf;
return ct; /* this is a lie of course */
}
/*
* Output a buffer to a file, converting from netascii if requested.
* CR,NUL -> CR and CR,LF => LF.
* Note spec is undefined if we get CR as last byte of file or a
* CR followed by anything else. In this case we leave it alone.
*/
int
write_behind(FILE *file, int convert)
{
char *buf;
int count;
int ct;
char *p;
int c; /* current character */
struct bf *b;
struct tftphdr *dp;
b = &bfs[nextone];
if (b->counter < -1) /* anything to flush? */
return 0; /* just nop if nothing to do */
count = b->counter; /* remember byte count */
b->counter = BF_FREE; /* reset flag */
dp = (struct tftphdr *)b->buf;
nextone = !nextone; /* incr for next time */
buf = dp->th_data;
if (count <= 0) return -1; /* nak logic? */
if (convert == 0)
return write(fileno(file), buf, count);
p = buf;
ct = count;
while (ct--) { /* loop over the buffer */
c = *p++; /* pick up a character */
if (prevchar == '\r') { /* if prev char was cr */
if (c == '\n') /* if have cr,lf then just */
fseek(file, -1, 1); /* smash lf on top of the cr */
else
if (c == '\0') /* if have cr,nul then */
goto skipit; /* just skip over the putc */
/* else just fall through and allow it */
}
putc(c, file);
skipit:
prevchar = c;
}
return count;
}
/* When an error has occurred, it is possible that the two sides
* are out of synch. Ie: that what I think is the other side's
* response to packet N is really their response to packet N-1.
*
* So, to try to prevent that, we flush all the input queued up
* for us on the network connection on our host.
*
* We return the number of packets we flushed (mostly for reporting
* when trace is active).
*/
int
synchnet(int f) /* socket to flush */
{
int i, j = 0;
char rbuf[PKTSIZE];
struct sockaddr_in from;
int fromlen;
while (1) {
(void) ioctl(f, FIONREAD, &i);
if (i) {
j++;
fromlen = sizeof from;
(void) recvfrom(f, rbuf, sizeof (rbuf), 0,
(struct sockaddr *)&from, &fromlen);
} else {
return(j);
}
}
}

57
tftp/tftpsubs.h Normal file
View file

@ -0,0 +1,57 @@
/* $Id$ */
/* $OpenBSD: tftpsubs.h,v 1.2 1996/06/26 05:40:37 deraadt Exp $ */
/* $NetBSD: tftpsubs.h,v 1.2 1994/12/08 09:51:32 jtc Exp $ */
/*
* Copyright (c) 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)tftpsubs.h 8.1 (Berkeley) 6/6/93
*/
/*
* Prototypes for read-ahead/write-behind subroutines for tftp user and
* server.
*/
struct tftphdr *r_init(void);
void read_ahead(FILE *, int);
int readit(FILE *, struct tftphdr **, int);
int synchnet(int);
struct tftphdr *w_init(void);
int write_behind(FILE *, int);
int writeit(FILE *, struct tftphdr **, int, int);
extern int segsize;
#define MAX_SEGSIZE 65464

27
tftpd/Makefile Normal file
View file

@ -0,0 +1,27 @@
all: tftpd
include ../MCONFIG
include ../MRULES
OBJS = tftpd.o tftpsubs.o
tftpd: $(OBJS)
$(CC) $(LDFLAGS) $^ $(LIBS) -o $@
tftpsubs.c:
ln -sf ../tftp/tftpsubs.c .
tftpsubs.h:
ln -sf ../tftp/tftpsubs.h .
$(OBJS): tftpsubs.h
install: tftpd
$(INSTALL_PROGRAM) tftpd $(INSTALLROOT)$(SBINDIR)/in.tftpd
$(INSTALL_DATA) tftpd.8 $(INSTALLROOT)$(MANDIR)/man8/in.tftpd.8
ln -sf in.tftpd.8 $(INSTALLROOT)$(MANDIR)/man8/tftpd.8
clean:
rm -f *.o tftpd tftpsubs.c tftpsubs.h
spotless: clean
rm -f *~

141
tftpd/tftpd.8 Normal file
View file

@ -0,0 +1,141 @@
.\" $OpenBSD: tftpd.8,v 1.7 1999/07/09 13:35:51 aaron Exp $
.\"
.\" Copyright (c) 1983, 1991 The Regents of the University of California.
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\" 3. All advertising materials mentioning features or use of this software
.\" must display the following acknowledgement:
.\" This product includes software developed by the University of
.\" California, Berkeley and its contributors.
.\" 4. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" from: @(#)tftpd.8 6.7 (Berkeley) 5/13/91
.\" $OpenBSD: tftpd.8,v 1.7 1999/07/09 13:35:51 aaron Exp $
.\"
.Dd June 11, 1997
.Dt TFTPD 8
.Os
.Sh NAME
.Nm tftpd
.Nd
.Tn DARPA
Trivial File Transfer Protocol server
.Sh SYNOPSIS
.Nm tftpd
.Op Fl cs
.Op Fl r Ar option...
.Op Ar directory
.Sh DESCRIPTION
.Nm
is a server which supports the
.Tn DARPA
Trivial File Transfer
Protocol.
The
.Tn TFTP
server operates
at the port indicated in the
.Ql tftp
service description;
see
.Xr services 5 .
The server is normally started by
.Xr inetd 8 .
.Pp
The use of
.Xr tftp 1
does not require an account or password on the remote system.
Due to the lack of authentication information,
.Nm
will allow only publicly readable files to be
accessed.
Files may be written only if they already exist and are publicly writable.
Note that this extends the concept of
.Dq public
to include
all users on all hosts that can be reached through the network;
this may not be appropriate on all systems, and its implications
should be considered before enabling tftp service.
The server should have the user ID with the lowest possible privilege.
.Pp
Access to files may be restricted by invoking
.Nm
with a list of directories by including pathnames
as server program arguments in
.Pa /etc/inetd.conf .
In this case access is restricted to files whose
names are prefixed by the one of the given directories.
.Pp
If the
.Fl c
flag is used,
.Nm
will allow new files to be created; otherwise uploaded files must already
exist. Files are created with default permissions allowing anyone to read
or write to them.
.Pp
When using the
.Fl s
flag with a directory name,
.Nm
will
.Xr chroot 2
on startup; therefore the remote host is not expected to pass the directory
as part of the file name to transfer. This option is intended primarily for
compatibility with SunOS boot ROMs which do not include a directory name.
.Pp
This version of
.Nm
supports RFC 2347 option negotiation; the current version supports the
.Pa blksize
(RFC 2348),
.Pa blksize ,
(RFC 2349), and
.Pa timeout
(RFC 2349) options. The
.Fl r
flag can be used to disable options individually; this may allow
working around client bugs.
.Sh SEE ALSO
.Xr tftp 1 ,
.Xr inetd 8
.Sh HISTORY
The
.Nm
command appeared in
.Bx 4.2 .
.Pp
The
.Fl s
flag appeared in NetBSD 0.9a.
.Pp
The
.Fl c
flag was added in OpenBSD 2.1 .
.Pp
The
.Fl r
flag and RFC 2347 options were added by H. Peter Anvin based on
patches by Markus Gutschke and Gero Kulhman.

821
tftpd/tftpd.c Normal file
View file

@ -0,0 +1,821 @@
/* $OpenBSD: tftpd.c,v 1.13 1999/06/23 17:01:36 deraadt Exp $ */
/*
* Copyright (c) 1983 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static const char *copyright =
"@(#) Copyright (c) 1983 Regents of the University of California.\n\
All rights reserved.\n";
#endif /* not lint */
#ifndef lint
/*static char sccsid[] = "from: @(#)tftpd.c 5.13 (Berkeley) 2/26/91";*/
/*static char rcsid[] = "$OpenBSD: tftpd.c,v 1.13 1999/06/23 17:01:36 deraadt Exp $: tftpd.c,v 1.6 1997/02/16 23:49:21 deraadt Exp $";*/
static const char *cvsid = "tftp-hpa $Id$";
#endif /* not lint */
/*
* Trivial file transfer protocol server.
*
* This version includes many modifications by Jim Guyton <guyton@rand-unix>
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/tftp.h>
#include <netdb.h>
#include <setjmp.h>
#include <syslog.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <pwd.h>
#include <unistd.h>
#include <limits.h>
#include "tftpsubs.h"
#define TIMEOUT 5 /* Default timeout (seconds) */
#define TRIES 4 /* Number of attempts to send each packet */
#define TIMEOUT_LIMIT (TRIES*(TRIES+1)/2)
#ifndef OACK
#define OACK 6
#endif
#ifndef EOPTNEG
#define EOPTNEG 8
#endif
extern int errno;
extern char *__progname;
struct sockaddr_in s_in = { AF_INET };
int peer;
int timeout = TIMEOUT;
int rexmtval = TIMEOUT;
int maxtimeout = TIMEOUT_LIMIT*TIMEOUT;
#define PKTSIZE MAX_SEGSIZE+4
char buf[PKTSIZE];
char ackbuf[PKTSIZE];
struct sockaddr_in from;
int fromlen;
off_t tsize;
int tsize_ok;
int ndirs;
char **dirs;
int secure = 0;
int cancreate = 0;
struct formats;
int tftp(struct tftphdr *, int);
void nak(int);
void timer(int);
void justquit(int);
void do_opt(char *, char *, char **);
int set_blksize(char *, char **);
int set_tsize(char *, char **);
int set_timeout(char *, char **);
struct options {
char *o_opt;
int (*o_fnc)(char *, char **);
} options[] = {
{ "blksize", set_blksize },
{ "tsize", set_tsize },
{ "timeout", set_timeout },
{ NULL, NULL }
};
static void
usage(void)
{
syslog(LOG_ERR, "Usage: %s [-cs] [-r option...] [directory ...]",
__progname);
exit(1);
}
int
main(int argc, char **argv)
{
struct tftphdr *tp;
struct passwd *pw;
struct options *opt;
int n = 0;
int on = 1;
int fd = 0;
int pid;
int i, j;
int c;
openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
while ((c = getopt(argc, argv, "csr:")) != -1)
switch (c) {
case 'c':
cancreate = 1;
break;
case 's':
secure = 1;
break;
case 'r':
for ( opt = options ; opt->o_opt ; opt++ ) {
if ( !strcasecmp(optarg, opt->o_opt) ) {
opt->o_opt = ""; /* Don't support this option */
break;
}
}
if ( !opt->o_opt ) {
syslog(LOG_ERR, "Unknown option: %s", optarg);
exit(1);
}
break;
default:
usage();
break;
}
for (; optind != argc; optind++) {
if (dirs)
dirs = realloc(dirs, (ndirs+2) * sizeof (char *));
else
dirs = calloc(ndirs+2, sizeof(char *));
if (dirs == NULL) {
syslog(LOG_ERR, "malloc: %m");
exit(1);
}
dirs[n++] = argv[optind];
dirs[n] = NULL;
ndirs++;
}
if (secure) {
if (ndirs == 0) {
syslog(LOG_ERR, "no -s directory");
exit(1);
}
if (ndirs > 1) {
syslog(LOG_ERR, "too many -s directories");
exit(1);
}
if (chdir(dirs[0])) {
syslog(LOG_ERR, "%s: %m", dirs[0]);
exit(1);
}
}
pw = getpwnam("nobody");
if (!pw) {
syslog(LOG_ERR, "no nobody: %m");
exit(1);
}
if (secure && chroot(".")) {
syslog(LOG_ERR, "chroot: %m");
exit(1);
}
(void) setegid(pw->pw_gid);
(void) setgid(pw->pw_gid);
(void) seteuid(pw->pw_uid);
(void) setuid(pw->pw_uid);
if (ioctl(fd, FIONBIO, &on) < 0) {
syslog(LOG_ERR, "ioctl(FIONBIO): %m");
exit(1);
}
fromlen = sizeof (from);
n = recvfrom(fd, buf, sizeof (buf), 0,
(struct sockaddr *)&from, &fromlen);
if (n < 0) {
syslog(LOG_ERR, "recvfrom: %m");
exit(1);
}
/*
* Now that we have read the message out of the UDP
* socket, we fork and exit. Thus, inetd will go back
* to listening to the tftp port, and the next request
* to come in will start up a new instance of tftpd.
*
* We do this so that inetd can run tftpd in "wait" mode.
* The problem with tftpd running in "nowait" mode is that
* inetd may get one or more successful "selects" on the
* tftp port before we do our receive, so more than one
* instance of tftpd may be started up. Worse, if tftpd
* break before doing the above "recvfrom", inetd would
* spawn endless instances, clogging the system.
*/
for (i = 1; i < 20; i++) {
pid = fork();
if (pid < 0) {
sleep(i);
/*
* flush out to most recently sent request.
*
* This may drop some request, but those
* will be resent by the clients when
* they timeout. The positive effect of
* this flush is to (try to) prevent more
* than one tftpd being started up to service
* a single request from a single client.
*/
j = sizeof from;
i = recvfrom(fd, buf, sizeof (buf), 0,
(struct sockaddr *)&from, &j);
if (i > 0) {
n = i;
fromlen = j;
}
} else
break;
}
if (pid < 0) {
syslog(LOG_ERR, "fork: %m");
exit(1);
} else if (pid != 0)
exit(0);
from.sin_family = AF_INET;
alarm(0);
close(fd);
close(1);
peer = socket(AF_INET, SOCK_DGRAM, 0);
if (peer < 0) {
syslog(LOG_ERR, "socket: %m");
exit(1);
}
if (bind(peer, (struct sockaddr *)&s_in, sizeof (s_in)) < 0) {
syslog(LOG_ERR, "bind: %m");
exit(1);
}
if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) {
syslog(LOG_ERR, "connect: %m");
exit(1);
}
tp = (struct tftphdr *)buf;
tp->th_opcode = ntohs(tp->th_opcode);
if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
tftp(tp, n);
exit(0);
}
int validate_access(char *, int, struct formats *);
void sendfile(struct formats *, struct tftphdr *, int);
void recvfile(struct formats *, struct tftphdr *, int);
struct formats {
char *f_mode;
int (*f_validate)(char *, int, struct formats *);
void (*f_send)(struct formats *, struct tftphdr *, int);
void (*f_recv)(struct formats *, struct tftphdr *, int);
int f_convert;
} formats[] = {
{ "netascii", validate_access, sendfile, recvfile, 1 },
{ "octet", validate_access, sendfile, recvfile, 0 },
{ NULL }
};
/*
* Handle initial connection protocol.
*/
int
tftp(struct tftphdr *tp, int size)
{
char *cp;
int argn, ecode;
struct formats *pf = NULL;
char *filename, *mode = NULL;
char *val = NULL, *opt = NULL;
char *ap = ackbuf + 2;
((struct tftphdr *)ackbuf)->th_opcode = ntohs(OACK);
filename = cp = tp->th_stuff;
argn = 0;
while ( cp < buf + size && *cp ) {
do {
cp++;
} while (cp < buf + size && *cp);
if ( *cp ) {
nak(EBADOP); /* Corrupt packet - no final NULL */
exit(0);
}
argn++;
if (argn == 1) {
mode = ++cp;
} else if (argn == 2) {
for (cp = mode; *cp; cp++)
*cp = tolower(*cp);
for (pf = formats; pf->f_mode; pf++) {
if (!strcmp(pf->f_mode, mode))
break;
}
if (!pf->f_mode) {
nak(EBADOP);
exit(0);
}
ecode = (*pf->f_validate)(filename, tp->th_opcode, pf);
if (ecode) {
nak(ecode);
exit(0);
}
opt = ++cp;
} else if ( argn & 1 ) {
val = ++cp;
} else {
do_opt(opt, val, &ap);
opt = ++cp;
}
}
if (!pf) {
nak(EBADOP);
exit(0);
}
if ( ap != (ackbuf+2) ) {
if ( tp->th_opcode == WRQ )
(*pf->f_recv)(pf, ackbuf, ap-ackbuf);
else
(*pf->f_send)(pf, ackbuf, ap-ackbuf);
} else {
if (tp->th_opcode == WRQ)
(*pf->f_recv)(pf, NULL, 0);
else
(*pf->f_send)(pf, NULL, 0);
}
exit(1);
}
/*
* Set a non-standard block size (c.f. RFC2348)
*/
int
set_blksize(char *val, char **ret)
{
static char b_ret[6];
int sz = atoi(val);
if (sz < 8)
return(0);
else if (sz > MAX_SEGSIZE)
sz = MAX_SEGSIZE;
segsize = sz;
sprintf(*ret = b_ret, "%d", sz);
return(1);
}
/*
* Return a file size (c.f. RFC2349)
* For netascii mode, we don't know the size ahead of time;
* so reject the option.
*/
int
set_tsize(char *val, char **ret)
{
static char b_ret[sizeof(off_t)*CHAR_BIT/3+2];
off_t sz = atol(val);
if ( !tsize_ok )
return 0;
if (sz == 0)
sz = tsize;
sprintf(*ret = b_ret, "%lu", sz);
return(1);
}
/*
* Set the timeout (c.f. RFC2349)
*/
int
set_timeout(char *val, char **ret)
{
static char b_ret[4];
unsigned long to = atol(val);
if ( to < 1 || to > 255 )
return 0;
timeout = to;
rexmtval = to;
maxtimeout = TIMEOUT_LIMIT*to;
sprintf(*ret = b_ret, "%lu", to);
return(1);
}
/*
* Parse RFC2347 style options
*/
void
do_opt(char *opt, char *val, char **ap)
{
struct options *po;
char *ret;
if ( !*opt )
return;
for (po = options; po->o_opt; po++)
if (!strcasecmp(po->o_opt, opt)) {
if (po->o_fnc(val, &ret)) {
if (*ap + strlen(opt) + strlen(ret) + 2 >=
ackbuf + sizeof(ackbuf)) {
nak(ENOSPACE); /* EOPTNEG? */
exit(0);
}
*ap = strrchr(strcpy(strrchr(strcpy(*ap, opt),'\0') + 1,
ret),'\0') + 1;
} else {
nak(EOPTNEG);
exit(0);
}
break;
}
return;
}
FILE *file;
/*
* Validate file access. Since we
* have no uid or gid, for now require
* file to exist and be publicly
* readable/writable.
* If we were invoked with arguments
* from inetd then the file must also be
* in one of the given directory prefixes.
* Note also, full path name must be
* given as we have no login directory.
*/
int
validate_access(char *filename, int mode, struct formats *pf)
{
struct stat stbuf;
int fd, wmode;
char *cp, **dirp;
tsize_ok = 0;
if (!secure) {
if (*filename != '/')
return (EACCESS);
/*
* prevent tricksters from getting around the directory
* restrictions
*/
for (cp = filename + 1; *cp; cp++)
if(*cp == '.' && strncmp(cp-1, "/../", 4) == 0)
return(EACCESS);
for (dirp = dirs; *dirp; dirp++)
if (strncmp(filename, *dirp, strlen(*dirp)) == 0)
break;
if (*dirp==0 && dirp!=dirs)
return (EACCESS);
}
/*
* We use different a different permissions scheme if `cancreate' is
* set.
*/
wmode = O_TRUNC;
if (stat(filename, &stbuf) < 0) {
if (!cancreate)
return (errno == ENOENT ? ENOTFOUND : EACCESS);
else {
if ((errno == ENOENT) && (mode != RRQ))
wmode |= O_CREAT;
else
return(EACCESS);
}
} else {
if (mode == RRQ) {
if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
return (EACCESS);
tsize = stbuf.st_size;
tsize_ok = !pf->f_convert;
} else {
if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
return (EACCESS);
tsize = 0;
tsize_ok = 1;
}
}
fd = open(filename, mode == RRQ ? O_RDONLY : (O_WRONLY|wmode), 0666);
if (fd < 0)
return (errno + 100);
/*
* If the file was created, set default permissions.
*/
if ((wmode & O_CREAT) && fchmod(fd, 0666) < 0) {
int serrno = errno;
close(fd);
unlink(filename);
return (serrno + 100);
}
file = fdopen(fd, (mode == RRQ)? "r":"w");
if (file == NULL)
return (errno + 100);
return (0);
}
int timeout;
jmp_buf timeoutbuf;
void
timer(int sig)
{
timeout += rexmtval;
if (timeout >= maxtimeout)
exit(0);
longjmp(timeoutbuf, 1);
}
/*
* Send the requested file.
*/
void
sendfile(struct formats *pf, struct tftphdr *oap, int oacklen)
{
struct tftphdr *dp;
struct tftphdr *ap; /* ack packet */
int block = 1, size, n;
signal(SIGALRM, timer);
ap = (struct tftphdr *)ackbuf;
if (oap) {
timeout = 0;
(void)setjmp(timeoutbuf);
oack:
if (send(peer, oap, oacklen, 0) != oacklen) {
syslog(LOG_ERR, "tftpd: oack: %m\n");
goto abort;
}
for ( ; ; ) {
alarm(rexmtval);
n = recv(peer, ackbuf, sizeof(ackbuf), 0);
alarm(0);
if (n < 0) {
syslog(LOG_ERR, "tftpd: read: %m\n");
goto abort;
}
ap->th_opcode = ntohs((u_short)ap->th_opcode);
ap->th_block = ntohs((u_short)ap->th_block);
if (ap->th_opcode == ERROR) {
syslog(LOG_ERR, "tftp: client does not accept "
"options\n");
goto abort;
}
if (ap->th_opcode == ACK) {
if (ap->th_block == 0)
break;
/* Resynchronize with the other side */
(void)synchnet(peer);
goto oack;
}
}
}
dp = r_init();
do {
size = readit(file, &dp, pf->f_convert);
if (size < 0) {
nak(errno + 100);
goto abort;
}
dp->th_opcode = htons((u_short)DATA);
dp->th_block = htons((u_short)block);
timeout = 0;
(void) setjmp(timeoutbuf);
send_data:
if (send(peer, dp, size + 4, 0) != size + 4) {
syslog(LOG_ERR, "tftpd: write: %m");
goto abort;
}
read_ahead(file, pf->f_convert);
for ( ; ; ) {
alarm(rexmtval); /* read the ack */
n = recv(peer, ackbuf, sizeof (ackbuf), 0);
alarm(0);
if (n < 0) {
syslog(LOG_ERR, "tftpd: read(ack): %m");
goto abort;
}
ap->th_opcode = ntohs((u_short)ap->th_opcode);
ap->th_block = ntohs((u_short)ap->th_block);
if (ap->th_opcode == ERROR)
goto abort;
if (ap->th_opcode == ACK) {
if (ap->th_block == block) {
break;
}
/* Re-synchronize with the other side */
(void) synchnet(peer);
if (ap->th_block == (block -1)) {
goto send_data;
}
}
}
block++;
} while (size == segsize);
abort:
(void) fclose(file);
}
void
justquit(int sig)
{
exit(0);
}
/*
* Receive a file.
*/
void
recvfile(struct formats *pf, struct tftphdr *oap, int oacklen)
{
struct tftphdr *dp;
struct tftphdr *ap; /* ack buffer */
int block = 0, acksize, n, size;
signal(SIGALRM, timer);
dp = w_init();
do {
timeout = 0;
if (!block++ && oap) {
ap = (struct tftphdr *)ackbuf;
acksize = oacklen;
} else {
ap = (struct tftphdr *)ackbuf;
ap->th_opcode = htons((u_short)ACK);
ap->th_block = htons((u_short)block);
acksize = 4;
}
(void) setjmp(timeoutbuf);
send_ack:
if (send(peer, ackbuf, acksize, 0) != acksize) {
syslog(LOG_ERR, "tftpd: write(ack): %m");
goto abort;
}
write_behind(file, pf->f_convert);
for ( ; ; ) {
alarm(rexmtval);
n = recv(peer, dp, PKTSIZE, 0);
alarm(0);
if (n < 0) { /* really? */
syslog(LOG_ERR, "tftpd: read: %m");
goto abort;
}
dp->th_opcode = ntohs((u_short)dp->th_opcode);
dp->th_block = ntohs((u_short)dp->th_block);
if (dp->th_opcode == ERROR)
goto abort;
if (dp->th_opcode == DATA) {
if (dp->th_block == block) {
break; /* normal */
}
/* Re-synchronize with the other side */
(void) synchnet(peer);
if (dp->th_block == (block-1))
goto send_ack; /* rexmit */
}
}
/* size = write(file, dp->th_data, n - 4); */
size = writeit(file, &dp, n - 4, pf->f_convert);
if (size != (n-4)) { /* ahem */
if (size < 0) nak(errno + 100);
else nak(ENOSPACE);
goto abort;
}
} while (size == segsize);
write_behind(file, pf->f_convert);
(void) fclose(file); /* close data file */
ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */
ap->th_block = htons((u_short)(block));
(void) send(peer, ackbuf, 4, 0);
signal(SIGALRM, justquit); /* just quit on timeout */
alarm(rexmtval);
n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
alarm(0);
if (n >= 4 && /* if read some data */
dp->th_opcode == DATA && /* and got a data block */
block == dp->th_block) { /* then my last ack was lost */
(void) send(peer, ackbuf, 4, 0); /* resend final ack */
}
abort:
return;
}
struct errmsg {
int e_code;
char *e_msg;
} errmsgs[] = {
{ EUNDEF, "Undefined error code" },
{ ENOTFOUND, "File not found" },
{ EACCESS, "Access violation" },
{ ENOSPACE, "Disk full or allocation exceeded" },
{ EBADOP, "Illegal TFTP operation" },
{ EBADID, "Unknown transfer ID" },
{ EEXISTS, "File already exists" },
{ ENOUSER, "No such user" },
{ EOPTNEG, "Failure to negotiate RFC2347 options" },
{ -1, 0 }
};
/*
* Send a nak packet (error message).
* Error code passed in is one of the
* standard TFTP codes, or a UNIX errno
* offset by 100.
*/
void
nak(int error)
{
struct tftphdr *tp;
int length;
struct errmsg *pe;
tp = (struct tftphdr *)buf;
tp->th_opcode = htons((u_short)ERROR);
tp->th_code = htons((u_short)error);
for (pe = errmsgs; pe->e_code >= 0; pe++)
if (pe->e_code == error)
break;
if (pe->e_code < 0) {
pe->e_msg = strerror(error - 100);
tp->th_code = EUNDEF; /* set 'undef' errorcode */
}
strcpy(tp->th_msg, pe->e_msg);
length = strlen(pe->e_msg);
tp->th_msg[length] = '\0';
length += 5;
if (send(peer, buf, length, 0) != length)
syslog(LOG_ERR, "nak: %m");
}