commit 7db43a3e8794a3f8fec9f91a40b212c7727dd757 Author: Alexander Zhirov Date: Wed May 10 01:18:22 2023 +0300 v5.2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a98dc8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +/MCONFIG +/aconfig.h +/aconfig.h.in +/autom4te.cache +/config.log +/config.status +/configure +/version.h +/tftp/tftp +/tftpd/tftpd +*.1 +*.8 +*.a +*.o +*~ +\#* diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..6b7b53c --- /dev/null +++ b/CHANGES @@ -0,0 +1,402 @@ +Changes in 5.2: + Fix breakage on newer Linux when a single interface has + multiple IP addresses. + + +Changes in 5.1: + Add -P option to write a PID file. Patch by Ferenc Wagner. + + Bounce the syslog socket in standalone mode, in case the + syslog daemon has been restarted. Patch by Ferenc Wagner. + + Build fixes. + + Fix handling of block number wraparound after a successful + options negotiation. + + Fix a buffer overflow in option parsing. + + +Changes in 5.0: + Try to on platforms with getaddrinfo() without AI_ADDRCONFIG or + AI_CANONNAME. + + Implement the "rollover" option, for clients which want block + number to rollover to anything other than zero. + + Correctly disable PMTU in standalone mode. Patch by Florian + Lohoff. + + +Changes in 0.49: + Add IPv6 support. Patch by Karsten Keil. + + Support systems with editline instead of readline. + + Support long options in the server. + + +Changes in 0.48: + Unbreak -l -s in the server, which was broken in 0.47. + + +Changes in 0.47: + Add -L option to the server to run standalone without + detaching from the shell. + + Parallel make fix. + + +Changes in 0.46: + Minor portability improvements. + + +Changes in 0.45: + Add -l (literal) option to the client, to override the special + treatment of the colon (:) character as a hostname separator. + + +Changes in 0.44: + Allow the client to specify a range of local port numbers, + just like the server can. + + Fix sending SIGHUP to update the regular expression table. + + +Changes in 0.43: + Fix double-free error on ^c in client. + + Try to deal with clients that send TFTP requests to broadcasts + (apparently some recent Sun boxes do this instead of using the + address told by DHCP. Bad Sun! Bad Sun!) + + Portability fixes. + + +Changes in 0.42: + Try to disable path MTU discovery for TFTP connections (it's + useless anyway.) + + Add a hack to allow the admin to specify a range of local port + numbers to use. + + Fix local IP number handling on systems which present + IP_RECVDSTADDR in recvmsg(). + + +Changes in 0.41: + Fix bug by which patterns of the form \U\1 weren't converted + correctly. + + +Changes in 0.40.1: + Solaris build fix. + + +Changes in 0.40: + Fix bug which would cause "r" remapping rules to be + incorrectly rejected. + + +Changes in 0.39: + Support Perl-style \U...\E and \L...\E, as well as allow + matching rules to be inverted (execute if rule *doesn't* + match.) + + Fix a timeout bug. + + Add an RPM spec file. + + +Changes in 0.38: + Portability fixes. + + +Changes in 0.37: + Fix a pathology where a client sending ACKs for the wrong + packet can prevent proper retransmission. + + +Changes in 0.36: + Portability fixes. + + +Changes in 0.35: + Add an option to control the maximum value of blksize + negotiated. + + Removed workaround for obsolete Cygwin problem. + + Don't use getopt() -- the -c option doesn't work correctly + since it depends on the ordering of arguments and options. It + is now possible to do: + + tftp -m binary hostname -c get filename + + This was previous possible by doing: + + tftp -m binary -c get hostname:filename + + ... but it seemed that was counterintuitive to people. + + Somewhat improved configure scripts. + + +Changes in 0.34: + Additional Solaris gcc compiler bug workarounds; these + actually make the code somewhat cleaner. + + +Changes in 0.33: + Even better error messages. + + Work around a suspect Solaris gcc bug. + + Configuration fix: readline needs termcap. + + Support running the tftp client from the command line. For + example: + + tftp -m binary -c get hostname:file + + +Changes in 0.32: + Better error messages; including the capability to send a + custom error message to the client when hitting an "a" rule in + a remapping table. + + +Changes in 0.31: + Put in a check to make sure xinetd (in particular) doesn't + pass us an IPv6 socket. + + Fix some problems related to timeout negotiation. + + Allow the user to set the default timeout speed. + + +Changes in 0.30: + (Hopefully) better timeout algorithm. + + Add a "utimeout" option; like "timeout" but in microseconds. + + Change the log level of client-side errors to LOG_WARNING. + + autoconf portability improvements. + + Minor bugfixes. + + +Changes in 0.29: + Posixly correctness. + + Now compiles and runs on Win32 systems using Cygwin + (http://www.cygwin.com/). + (). + + Fixed a bug which could cause a standalone server to exit with + a "recvfrom: Interrupted system call" log message if signals + arrive at a particularly inopportune moment. + + Fix a macro substitution bug (thanks to Richard Nyberg.) + + +Changes in 0.28: + Fix stupid one-liner bug which broke standalone mode (-l). + + +Changes in 0.27: + Make the Digital Unix 4.0F platform work again. Thanks to + Alan Sundell for helping out with this platform! + + Make the AIX 4.3 platform work again. Thanks to Josef Siemes + for helping out with this platform! + + Allow replacement patterns to include the IP address of the + requesting host (\i). + + Allow relying on Unix permissions rather than o+r magic if the + -p option is specified. As part of this, set all groups if + initgroups() is specified on the platform. + + Clean up race conditions inherited from the BSD source base. + + +Changes in 0.26: + Fix the configuration process so tftpd doesn't end up + depending on readline, which apparently could happen on some + platforms before. + + Make parallel builds (make -j) work correctly. + + Improve parsing of the "connect" command in the tftp client. + + Add a -V option to both tftp and tftpd to print the version + number on stdout and immediately exit. + + Add a -v option to tftp to start out in verbose mode. + + Rewrite the man pages using standard "man" troff macros. + + Enable the (limited) use of readline on systems which don't + have readline/history.h. + + Support compiling under MacOS X with fink (see + ). Thanks for Justin Hallett + and Eric Eslinger for their help in getting this working! + + +Changes in 0.25: + Fixed Sorcerer's Apprentice bug in both the client and the + server. These bugs were inherited from the original BSD code. + + +Changes in 0.24: + Fix bugs in both client and server dealing with block number + wraparound, usually manifesting themselves as failure to + handle files over 32 MB in size. + + Officially make the client a part of the tftp-hpa project. + + +Changes in 0.23: + Correct memory overwrite bug in the tftp client when compiled + with readline. + + +Changes in 0.22: + Even more portability improvements: FreeBSD and + Tru64/Digital Unix. + + Fix tsize option on systems on which off_t is "long long". + + Support large files on systems which need _LARGE_FILE_BITS or + similar. + + Some source cleanups; change to autoconf 2.52. + + Add support for readline command-line editing in tftp. + + +Changes in 0.21: + Support running in standalone mode, without inetd. + + Even more portability improvements. Now known to compile and + run on Linux, Solaris 5, 5.1, 6, 7 and 8, and AIX. Reports of + success or failure on other modern systems always appreciated. + + Clean and modernize some really ugly old code. + + Fix a potential illegal memory access when running in "totally + insecure mode" - no -s, no directories listed. + + +Changes in 0.20: + Portability improvements. Now known to compile and run on + Solaris 8. + + +Changes in 0.19: + Fork before performing tcpwrappers check. + + Don't rely on nonstandard bsd_signal() function, instead + require that the platform has sigaction(). This is 2001, + after all. This may resolve some potential portability + problems. + + Log a message if memory allocation fails, instead of dying + silently. + + Clean up the main dispatch loop. + + Use for exit codes, if it exists. + + Add support for debugging remapping rulefiles; if logging with + -vvv tftpd will log all rules actions. + + Correct the error code issued by an "abort" rule. + + +Changes in 0.18: + Support (almost) arbitrary filename remappings via regular + expression-based rulesets. + + Added -v option for more verbose logging. + + +Changes in 0.17: + + Add support for tcpwrapper checking (/etc/hosts.allow; + /etc/hosts.deny) in tftpd. + + Compile correctly on glibc 2.1.2. + + Add -u option to specify the user id to run as (default + "nobody".) + + Operate in "daemon mode" as long as we keep getting requests. + This should speed up handling large amounts of requests at + once, as can happen when a client starts up, and avoids inetd + misconfiguration problems. + + +Changes in 0.16: + + Correct massive lossage from 0.15: apparently 0.15 was based + on an out-of-date CVS repository, somehow. + + Fix for ACKs in TFTP PUT; patch by Roger Venning. + + +Changes in 0.15: + + If the operating system allows, try to obtain the local + address used for the request packet, and reply using the same + local IP address. Some embedded TFTP clients are (probably + incorrectly) picky about this. + + +Changes in 0.14: + + Hacks to signal handling to avoid "zombie servers." + + +Changes in 0.13: + + Added the non-standard option "blksize2". The "blksize" + option is limited in its usability, since TFTP is designed to + be implemented in a ROM, and ROM code might find it painful to + deal with packets that don't meet certain alignment + restrictions. + + The "blksize2" option tells the server that the block size + must be a power of 2 to be usable to the client. The server + SHALL respond with a block size that is a power of two, up to + a maximum of 32768, or reject the option. Furthermore, the + server SHALL grant a block size that is no smaller than 512 + bytes unless the client explicitly requested a smaller block + size. If the client request both options, the server MAY + accept one or the other, but not both. At some point I will + probably write up an IETF draft for this option. + + +General information on the tftp-hpa series: + +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, converting to using autoconf for setup, and any additions +listed in the Changes list above, has all been my own code, as are any +bugs introduced in the merge. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..d3d2b27 --- /dev/null +++ b/INSTALL @@ -0,0 +1,227 @@ +Basic Installation +================== + + These are generic installation instructions. See the file +INSTALL.tftp for specific install instructions for this package. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for variables by setting +them in the environment. You can do that on the command line like this: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Environment Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it cannot guess the host type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the host +platform (i.e., that on which the generated programs will eventually be +run) with `--host=TYPE'. In this case, you should also specify the +build platform with `--build=TYPE', because, in this case, it may not +be possible to guess the build platform (it sometimes involves +compiling and running simple test programs, and this can't be done if +the compiler is a cross compiler). + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Environment Variables +===================== + + Variables not defined in a site shell script can be set in the +environment passed to configure. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +will cause the specified gcc to be used as the C compiler (unless it is +overridden in the site shell script). + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/INSTALL.tftp b/INSTALL.tftp new file mode 100644 index 0000000..914094f --- /dev/null +++ b/INSTALL.tftp @@ -0,0 +1,36 @@ +Specific installation instructions +================================== + +In addition to what is described in the INSTALL file, the following +specifics apply to the tftp-hpa package: + +The tftp-hpa package supports the following options to ./configure: + + --without-tcpwrappers + Disables the use of the tcp wrapper library. This is + recommended, for performance reasons, on high-use sites. + Kernel-based firewalling is, in general, a better alternative. + + --without-remap + Disables the use of regular-expression-based filename + remapping (the -m option to tftpd). + + --without-readline + Disables the use of the readline command-line editing library + in the tftp client. + +The default prefix for the tftp-hpa package is /usr, with the tftp +client installing as /usr/bin/tftp and the tftpd server installing as +/usr/sbin/in.tftpd on most systems. This can be overridden by setting +--bindir and --sbindir. + +"make install" supports specifying an INSTALLROOT, which points to +what will be the root of the filesystem at runtime; this is typically +used when preparing packages for package-management systems. + +You almost certainly will need GNU make to build tftp-hpa. If you +don't already have it, you can find GNU make at: + + ftp://ftp.gnu.org/pub/make/ +or + ftp://mirrors.kernel.org/gnu/make/ diff --git a/MCONFIG.in b/MCONFIG.in new file mode 100644 index 0000000..2660f7e --- /dev/null +++ b/MCONFIG.in @@ -0,0 +1,71 @@ +## -*- makefile -*- ------------------------------------------------------ +## +## Copyright 2001-2007 H. Peter Anvin - All Rights Reserved +## +## This program is free software available under the same license +## as the "OpenBSD" operating system, distributed at +## http://www.openbsd.org/. +## +## ----------------------------------------------------------------------- + +## +## MCONFIG.in +## +## Basic Makefile definitions +## + +# Source and object root +SRCROOT = @SRCROOT@ +OBJROOT = @OBJROOT@ + +# Prefixes +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# Directory for user binaries +BINDIR = @bindir@ + +# Man page tree +MANDIR = @mandir@ + +# System binaries +SBINDIR = @sbindir@ + +# Data root directory +datarootdir = @datarootdir@ + +# Binary suffixes +O = @OBJEXT@ +X = @EXEEXT@ + +# Install into alternate root area, e.g. for package generation +INSTALLROOT = + +# Link +LN_S = @LN_S@ + +# Install program +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ + +# Compiler and compiler flags +CC = @CC@ +CFLAGS = @CFLAGS@ -I$(SRCROOT) + +# Link flags +LDFLAGS = @LDFLAGS@ + +# Libraries (client and server) +TFTP_LIBS = ../common/libcommon.a @TFTP_LIBS@ +TFTPD_LIBS = ../common/libcommon.a @TFTPD_LIBS@ + +# Additional library we need to build +LIBOBJS = @LIBOBJS@ + +# Additional tftpd objects we need to build +TFTPDOBJS = @TFTPDOBJS@ + +# ar and ranlib (for making libraries) +AR = ar cq +RANLIB = @RANLIB@ diff --git a/MRULES b/MRULES new file mode 100644 index 0000000..decd202 --- /dev/null +++ b/MRULES @@ -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 $@ $< + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9ff12d8 --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +# 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 = lib common tftp tftpd + +%.build: MCONFIG aconfig.h version.h + $(MAKE) -C $(patsubst %.build, %, $@) + +%.install: MCONFIG aconfig.h version.h + $(MAKE) -C $(patsubst %.install, %, $@) install + +%.clean: + $(MAKE) -C $(patsubst %.clean, %, $@) clean + +%.distclean: + $(MAKE) -C $(patsubst %.distclean, %, $@) distclean + +all: MCONFIG $(patsubst %, %.build, $(SUB)) + +tftp.build: lib.build common.build +tftpd.build: lib.build common.build + +install: MCONFIG $(patsubst %, %.install, $(SUB)) + +clean: localclean $(patsubst %, %.clean, $(SUB)) + +localclean: + rm -f version.h + +distclean: localdistclean $(patsubst %, %.distclean, $(SUB)) + +localdistclean: localclean + rm -f MCONFIG config.status config.log aconfig.h *~ \#* + rm -rf *.cache + find . -type f \( -name \*.orig -o -name \*.rej \) | xargs rm -f + +spotless: distclean + rm -f configure aconfig.h.in tftp.spec + +autoconf: configure aconfig.h.in + +config: MCONFIG aconfig.h + +release: + $(MAKE) autoconf + $(MAKE) tftp.spec + $(MAKE) distclean + +MCONFIG: configure MCONFIG.in aconfig.h.in + if test -x config.status; then \ + ./config.status --recheck && ./config.status ; \ + else \ + ./configure ; \ + fi + +aconfig.h: MCONFIG + : Generated by side effect + +# Adding "configure" to the dependencies serializes this with running +# autoconf, because there are apparently race conditions between +# autoconf and autoheader. +aconfig.h.in: configure.in configure aclocal.m4 + rm -f aconfig.h.in aconfig.h + autoheader + +configure: configure.in aclocal.m4 + rm -rf MCONFIG configure config.log aconfig.h *.cache + autoconf + +version.h: version + echo \#define VERSION \"tftp-hpa `cat version`\" > version.h + +tftp.spec: tftp.spec.in version + sed -e "s/@@VERSION@@/`cat version`/g" < $< > $@ || rm -f $@ + diff --git a/README b/README new file mode 100644 index 0000000..91afcb3 --- /dev/null +++ b/README @@ -0,0 +1,27 @@ +This is tftp-hpa, a conglomerate of a number of versions of the BSD +TFTP code, changed around to port to a whole collection of operating +systems. The goal is to work on any reasonably modern Unix with +sockets. + +The tftp-hpa series is maintained by H. Peter Anvin . + +The latest version of this collection can be found at: + + ftp://ftp.kernel.org/pub/software/network/tftp/ + +See the file CHANGES for a list of changes between versions. + + +Please see the INSTALL and INSTALL.tftp files for compilation and +installation instructions. + +===> IMPORTANT: IF YOU ARE UPGRADING FROM ANOTHER TFTP SERVER, OR FROM +===> A VERSION OF TFTP-HPA OLDER THAN 0.17 SEE THE FILE +===> "README.security" FOR IMPORTANT SECURITY MODEL CHANGES! + + +This software can be discussed on the SYSLINUX mailing list. To +subscribe, go to the list subscription page at: + + http://www.zytor.com/mailman/listinfo/syslinux + diff --git a/README.security b/README.security new file mode 100644 index 0000000..644babb --- /dev/null +++ b/README.security @@ -0,0 +1,56 @@ +Starting in version 0.27, tftp-hpa has the option of a "use Unix +permissions" mode. In this mode, tftpd can access any file accessible +by the tftpd effective user, specified via the -u option. This means +that files no longer need to be set to o+r or o+w. + +If file creation is enabled (via the -c option), the -p option also +changes the default umask from 0 (anyone can read or write) to +"unchanged" (inherited from the calling process.) The -U option can +be used to override the default umask; this is recommended. + +The sanest setup, from a security standpoint, for tftpd to run in is +probably the following: + +1. Create a separate "tftpd" user and group only used for tftpd; +2. Have all your boot files in a single directory tree (usually called + /tftpboot). +3. Specify "-p -u tftpd -s /tftpboot" on the tftpd command line; if + you want clients to be able to create files use + "-p -c -U 002 -u tftpd -s /tftpboot" (replace 002 with whatever + umask is appropriate for your setup.) + + ======================================= + +Starting in version 0.17, tftp-hpa operates in genuine "wait" mode, +which means that an in.tftpd process hangs around for some time after +the last service request has arrived. This speeds up servicing a +subsequent request, which apparently has been a problem in the past, +resulting in "request storms" as the client keeps retrying, resulting +in multiple connections on the server which the client has already +abandoned. + +This also means that spawning tftp via tcpd is useless (in fact, this +indirection seems to be part of the reason for these "request +storms.") Instead, tftp-hpa supports calling the tcpwrapper library +directly. Thus, if your /etc/inetd.conf looks like this (all on one +line): + +tftp dgram udp wait root /usr/sbin/tcpd +/usr/sbin/in.tftpd -s /tftpboot -r blksize + +... it's better to change to ... + +tftp dgram udp wait root /usr/sbin/in.tftpd +in.tftpd -s /tftpboot -r blksize + +You should make sure that you are using "wait" option in tftpd; you +also need to have tftpd spawned as root in order for chroot (-s) to +work. tftpd automatically drops privilege and changes user ID to +"nobody" by default; the appropriate user ID for tftpd can be +specified with the -u option (e.g. "-u tftpuser"). + +If you are running a busy boot server, I would suggest to instead use +kernel-based firewalling rules, and to compile tftpd without +tcpwrapper support, in order to provide significantly better +performance. To do so, specify the --without-tcpwrappers option to +configure when compiling; see the INSTALL.tftp file for more information. diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000..c07702c --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,280 @@ +dnl ----------------------------------------------------------------------- +dnl +dnl Copyright 1999-2008 H. Peter Anvin - All Rights Reserved +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation, Inc., 53 Temple Place Ste 330, +dnl Bostom MA 02111-1307, USA; either version 2 of the License, or +dnl (at your option) any later version; incorporated herein by reference. +dnl +dnl ----------------------------------------------------------------------- + +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_LINK([#include ], + [printf("Hello, World!\n");], + AC_MSG_RESULT([yes]), + AC_MSG_RESULT([no]) + CFLAGS="$pa_add_cflags__old_cflags")]) + +dnl -------------------------------------------------------------------------- +dnl PA_SIGSETJMP +dnl +dnl Do we have sigsetjmp/siglongjmp? (AC_CHECK_FUNCS doesn't seem to work +dnl for these particular functions.) +dnl -------------------------------------------------------------------------- +AC_DEFUN(PA_SIGSETJMP, +[AC_MSG_CHECKING([for sigsetjmp]) + AC_TRY_LINK([ +#ifdef HAVE_SETJMP_H +#include +#endif], + [sigjmp_buf buf; + sigsetjmp(buf,1); + siglongjmp(buf,2);], + AC_MSG_RESULT([yes]) + $1, + AC_MSG_RESULT([no]) + $2)]) + +dnl -------------------------------------------------------------------------- +dnl PA_MSGHDR_MSG_CONTROL +dnl +dnl Does struct msghdr have the msg_control field? +dnl -------------------------------------------------------------------------- +AH_TEMPLATE([HAVE_MSGHDR_MSG_CONTROL], +[Define if struct msghdr has the msg_control field.]) + +AC_DEFUN(PA_MSGHDR_MSG_CONTROL, + [AC_CHECK_MEMBER(struct msghdr.msg_control, + [AC_DEFINE(HAVE_MSGHDR_MSG_CONTROL)], + [], + [ +#include +#include +#include + ])]) + +dnl ------------------------------------------------------------------------ +dnl PA_STRUCT_IN_PKTINFO +dnl +dnl Look for definition of struct in_pktinfo, which at least has an +dnl ipi_addr member. Some versions of glibc lack struct in_pktinfo; +dnl if so we need to include the definition ourselves -- but we only +dnl want to do that if absolutely necessary! +dnl ------------------------------------------------------------------------ +AH_TEMPLATE([HAVE_STRUCT_IN_PKTINFO], +[Define if struct in_pktinfo is defined.]) + +AC_DEFUN(PA_STRUCT_IN_PKTINFO, + [AC_CHECK_MEMBER(struct in_pktinfo.ipi_addr, + [AC_DEFINE(HAVE_STRUCT_IN_PKTINFO)], + [], + [ +#include +#include +#include +#include +#include +#include + ])]) + + +dnl ------------------------------------------------------------------------ +dnl PA_STRUCT_SOCKADDR_IN6 +dnl +dnl Look for definition of struct sockaddr_in6, which at least has an +dnl sin6_addr member +dnl +AH_TEMPLATE([HAVE_STRUCT_SOCKADDR_IN6], +[Define if struct sockaddr_in6 is defined.]) + +AC_DEFUN(PA_STRUCT_SOCKADDR_IN6, + [AC_CHECK_MEMBER(struct sockaddr_in6.sin6_addr, + [ + AC_DEFINE(HAVE_STRUCT_SOCKADDR_IN6) + HAVE_INET6=true; + ], + [ + HAVE_INET6=false; + ], + [ +#include +#include +#include +#include + ])]) + +dnl ------------------------------------------------------------------------ +dnl PA_STRUCT_ADDRINFO +dnl +dnl Look for definition of struct addrinfo, which at least has an +dnl ai_addr member +dnl +AH_TEMPLATE([HAVE_STRUCT_ADDRINFO], +[Define if struct addrinfo is defined.]) + +AC_DEFUN(PA_STRUCT_ADDRINFO, + [AC_CHECK_MEMBER(struct addrinfo.ai_addr, + [AC_DEFINE(HAVE_STRUCT_ADDRINFO)], + [], + [ +#include +#include +#include + ])]) + +dnl ------------------------------------------------------------------------ +dnl PA_STRUCT_IN6_PKTINFO +dnl +dnl Look for definition of struct in6_pktinfo, which at least has an +dnl ipi6_addr member +dnl +AH_TEMPLATE([HAVE_STRUCT_IN6_PKTINFO], +[Define if struct in6_pktinfo is defined.]) + +AC_DEFUN(PA_STRUCT_IN6_PKTINFO, + [AC_CHECK_MEMBER(struct in6_pktinfo.ipi6_addr, + [AC_DEFINE(HAVE_STRUCT_IN6_PKTINFO)], + [], + [ +#include +#include +#include +#include + ])]) + +dnl -------------------------------------------------------------------------- +dnl PA_HAVE_TCPWRAPPERS +dnl +dnl Do we have the tcpwrappers -lwrap? This can't be done using AC_CHECK_LIBS +dnl due to the need to provide "allow_severity" and "deny_severity" variables +dnl -------------------------------------------------------------------------- +AH_TEMPLATE([HAVE_TCPWRAPPERS], +[Define if we have tcpwrappers (-lwrap) and .]) + +AC_DEFUN(PA_HAVE_TCPWRAPPERS, +[AC_CHECK_LIB([wrap], [main]) + AC_MSG_CHECKING([for tcpwrappers]) + AC_TRY_LINK( +[ +#include +int allow_severity = 0; +int deny_severity = 0; +], +[ + hosts_ctl("sample_daemon", STRING_UNKNOWN, STRING_UNKNOWN, STRING_UNKNOWN); +], +[ + AC_DEFINE(HAVE_TCPWRAPPERS) + AC_MSG_RESULT([yes]) +], +[ + AC_MSG_RESULT([no]) +])]) + +dnl ------------------------------------------------------------------------ +dnl PA_CHECK_INTTYPES_H_SANE +dnl +dnl At least some versions of AIX 4 have macros which are +dnl completely broken. Try to detect those. +dnl -------------------------------------------------------------------------- +AH_TEMPLATE([INTTYPES_H_IS_SANE], +[Define if the macros in are usable]) + +AC_DEFUN(PA_CHECK_INTTYPES_H_SANE, +[AC_CHECK_HEADERS(inttypes.h, + [ + AC_MSG_CHECKING([if inttypes.h is sane]) + AC_TRY_LINK( + [ +#include +#include + ], + [uintmax_t max = UINTMAX_C(0); + printf("%"PRIuMAX"\n", max);], + AC_MSG_RESULT([yes]) + AC_DEFINE(INTTYPES_H_IS_SANE), + AC_MSG_RESULT([no (AIX, eh?)])) + ]) +]) + +dnl ------------------------------------------------------------------------ +dnl PA_WITH_BOOL +dnl +dnl PA_WITH_BOOL(option, default, help, enable, disable) +dnl +dnl Provides a more convenient way to specify --with-option and +dnl --without-option, with a default. default should be either 0 or 1. +dnl ------------------------------------------------------------------------ +AC_DEFUN(PA_WITH_BOOL, +[AC_ARG_WITH([$1], [$3], +if test ["$withval"] != no; then +[$4] +else +[$5] +fi, +if test [$2] -ne 0; then +[$4] +else +[$5] +fi)]) + +dnl -------------------------------------------------------------------------- +dnl PA_HEADER_DEFINES +dnl +dnl PA_HEADER_DEFINES(header, type, value) +dnl -------------------------------------------------------------------------- +AC_DEFUN(PA_HEADER_DEFINES, +[AC_MSG_CHECKING([if $1 defines $3]) + AH_TEMPLATE([HAVE_$3_DEFINITION], [Define if $1 defines $3]) + AC_TRY_COMPILE([ +#include <$1> +], +[ +int main() +{ + $2 dummy = $3; + return 0; +} +], +[ + pa_header_define=`echo HAVE_$3_DEFINITION | tr '[a-z]' '[A-Z]'` + AC_DEFINE_UNQUOTED($pa_header_define) + AC_MSG_RESULT(yes) +], +[ + AC_MSG_RESULT(no) +])]) + +dnl -------------------------------------------------------------------------- +dnl PA_SEARCH_LIBS_AND_ADD +dnl +dnl PA_SEARCH_LIBS_AND_ADD(function, libraries [,function to add]) +dnl -------------------------------------------------------------------------- + +AC_DEFUN(PA_SEARCH_LIBS_AND_ADD, + [ + AH_TEMPLATE(AS_TR_CPP(HAVE_$1), [Define if $1 function was found]) + AC_SEARCH_LIBS($1, $2, + [ + AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_$1)) + pa_add_$1=false; + ], + [ + XTRA=true; + if test $# -eq 3; then + AC_LIBOBJ($3) + else + AC_LIBOBJ($1) + fi + pa_add_$1=true; + ])]) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..728a381 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,2 @@ +#!/bin/sh +make autoconf diff --git a/common/Makefile b/common/Makefile new file mode 100644 index 0000000..a825213 --- /dev/null +++ b/common/Makefile @@ -0,0 +1,25 @@ +SRCROOT = .. +VERSION = $(shell cat ../version) + +-include ../MCONFIG +include ../MRULES + +OBJS = tftpsubs.$(O) +LIB = libcommon.a + +all: $(LIB) + +$(LIB): $(OBJS) + -rm -f $(LIB) + $(AR) $(LIB) $(OBJS) + $(RANLIB) $(LIB) + +$(OBJS): tftpsubs.h + +install: + +clean: + rm -f *.o *.obj *.exe $(LIB) + +distclean: clean + rm -f *~ diff --git a/common/tftpsubs.c b/common/tftpsubs.c new file mode 100644 index 0000000..8c999f6 --- /dev/null +++ b/common/tftpsubs.c @@ -0,0 +1,406 @@ +/* + * 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. + */ + +#include "tftpsubs.h" + +/* 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 + +#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 pktcount = 0; + char rbuf[PKTSIZE]; + union sock_addr from; + socklen_t fromlen; + fd_set socketset; + struct timeval notime; + + while (1) { + notime.tv_sec = notime.tv_usec = 0; + + FD_ZERO(&socketset); + FD_SET(f, &socketset); + + if (select(f, &socketset, NULL, NULL, ¬ime) <= 0) + break; /* Nothing to read */ + + /* Otherwise drain the packet */ + pktcount++; + fromlen = sizeof(from); + (void)recvfrom(f, rbuf, sizeof(rbuf), 0, + &from.sa, &fromlen); + } + + return pktcount; /* Return packets drained */ +} + +int pick_port_bind(int sockfd, union sock_addr *myaddr, + unsigned int port_range_from, + unsigned int port_range_to) +{ + unsigned int port, firstport; + int port_range = 0; + + if (port_range_from != 0 && port_range_to != 0) { + port_range = 1; + } + + firstport = port_range + ? port_range_from + rand() % (port_range_to - port_range_from + 1) + : 0; + + port = firstport; + + do { + sa_set_port(myaddr, htons(port)); + if (bind(sockfd, &myaddr->sa, SOCKLEN(myaddr)) < 0) { + /* Some versions of Linux return EINVAL instead of EADDRINUSE */ + if (!(port_range && (errno == EINVAL || errno == EADDRINUSE))) + return -1; + + /* Normally, we shouldn't have to loop, but some situations involving + aborted transfers make it possible. */ + } else { + return 0; + } + + port++; + if (port > port_range_to) + port = port_range_from; + } while (port != firstport); + + return -1; +} + +int +set_sock_addr(char *host,union sock_addr *s, char **name) +{ + struct addrinfo *addrResult; + struct addrinfo hints; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = s->sa.sa_family; + hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + err = getaddrinfo(strip_address(host), NULL, &hints, &addrResult); + if (err) + return err; + if (addrResult == NULL) + return EAI_NONAME; + memcpy(s, addrResult->ai_addr, addrResult->ai_addrlen); + if (name) { + if (addrResult->ai_canonname) + *name = xstrdup(addrResult->ai_canonname); + else + *name = xstrdup(host); + } + freeaddrinfo(addrResult); + return 0; +} + +#ifdef HAVE_IPV6 +int is_numeric_ipv6(const char *p) +{ + /* A numeric IPv6 address consist at least of 2 ':' and + * it may have sequences of hex-digits and maybe contain + * a '.' from a IPv4 mapped address and maybe is enclosed in [] + * we do not check here, if it is a valid IPv6 address + * only if is something like a numeric IPv6 address or something else + */ + int colon = 0; + int dot = 0; + int bracket = 0; + char c; + + if (!p) + return 0; + + if (*p == '[') { + bracket = 1; + p++; + } + + while ((c = *p++) && c != ']') { + switch (c) { + case ':': + colon++; + break; + case '.': + dot++; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + break; + default: + return 0; /* Invalid character */ + } + } + + if (colon < 2 || colon > 7) + return 0; + + if (dot) { + /* An IPv4-mapped address in dot-quad form will have 3 dots */ + if (dot != 3) + return 0; + /* The IPv4-mapped address takes the space of one colon */ + if (colon > 6) + return 0; + } + + /* If bracketed, must be closed, and vice versa */ + if (bracket ^ (c == ']')) + return 0; + + /* Otherwise, assume we're okay */ + return 1; +} + +/* strip [] from numeric IPv6 addreses */ + +char *strip_address(char *addr) +{ + char *p; + + if (is_numeric_ipv6(addr) && (*addr == '[')) { + p = addr + strlen(addr); + p--; + if (*p == ']') { + *p = 0; + addr++; + } + } + return addr; +} +#endif diff --git a/common/tftpsubs.h b/common/tftpsubs.h new file mode 100644 index 0000000..b3a3bf3 --- /dev/null +++ b/common/tftpsubs.h @@ -0,0 +1,121 @@ +/* + * 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. + */ + +/* + * Prototypes for read-ahead/write-behind subroutines for tftp user and + * server. + */ +#ifndef TFTPSUBS_H +#define TFTPSUBS_H + +#include "config.h" + +union sock_addr { + struct sockaddr sa; + struct sockaddr_in si; +#ifdef HAVE_IPV6 + struct sockaddr_in6 s6; +#endif +}; + +#define SOCKLEN(sock) \ + (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \ + (sizeof(struct sockaddr_in)) : \ + (sizeof(union sock_addr))) + +#ifdef HAVE_IPV6 +#define SOCKPORT(sock) \ + (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \ + ((union sock_addr*)sock)->si.sin_port : \ + ((union sock_addr*)sock)->s6.sin6_port) +#else +#define SOCKPORT(sock) \ + (((union sock_addr*)sock)->si.sin_port) +#endif + +#ifdef HAVE_IPV6 +#define SOCKADDR_P(sock) \ + (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \ + (void *)&((union sock_addr*)sock)->si.sin_addr : \ + (void *)&((union sock_addr*)sock)->s6.sin6_addr) +#else +#define SOCKADDR_P(sock) \ + (void *)&((union sock_addr*)sock)->si.sin_addr +#endif + +#ifdef HAVE_IPV6 +int is_numeric_ipv6(const char *); +char *strip_address(char *); +#else +#define is_numeric_ipv6(a) 0 +#define strip_address(a) (a) +#endif + +static inline int sa_set_port(union sock_addr *s, u_short port) +{ + switch (s->sa.sa_family) { + case AF_INET: + s->si.sin_port = port; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + s->s6.sin6_port = port; + break; +#endif + default: + return -1; + } + return 0; +} + +int set_sock_addr(char *, union sock_addr *, char **); + +struct tftphdr; + +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 + +int pick_port_bind(int sockfd, union sock_addr *myaddr, + unsigned int from, unsigned int to); + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..0e35438 --- /dev/null +++ b/config.h @@ -0,0 +1,380 @@ +/* -*- c -*- ------------------------------------------------------------- * + * + * Copyright 2001-2006 H. Peter Anvin - All Rights Reserved + * + * This program is free software available under the same license + * as the "OpenBSD" operating system, distributed at + * http://www.openbsd.org/. + * + * ----------------------------------------------------------------------- */ + +/* + * config.h + * + * Sets up a common baseline environment, based on "autoconf" findings... + */ + +#ifndef CONFIG_H +#define CONFIG_H 1 + +/* Must be included before we include any system headers! */ +#include "aconfig.h" /* autogenerated configuration header */ + +/* Standard includes */ + +#include + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#ifdef STDC_HEADERS +#include +#include +#else +#ifdef HAVE_STDLIB_H +#include +#endif +#endif + +#ifdef HAVE_MEMORY_H +#ifndef STDC_HEADERS +#include +#endif +#endif + +#ifdef HAVE_STRING_H +#include +#endif + +#ifdef HAVE_STRINGS_H +#include +#endif + +#ifdef HAVE_INTTYPES_H +#ifdef INTTYPES_H_IS_SANE +#include +#endif +#else +#ifdef HAVE_STDINT_H +#include +#endif +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SETJMP_H +#include +#endif + +#ifdef TIME_WITH_SYS_TIME +#include +#include +#else +#if HAVE_SYS_TIME_H +#include +#else +#include +#endif +#endif + +#ifdef HAVE_GRP_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#include +#include + +#ifdef HAVE_SYS_SOCKET_H +#include +#else +#ifdef HAVE_WINSOCK2_H +#include +#else +#ifdef HAVE_WINSOCK_H +#include +#endif +#endif +#endif +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_GETOPT_LONG +#include +#else +#include "lib/getopt.h" +#endif + +/* Test for EAGAIN/EWOULDBLOCK */ +#ifdef EAGAIN +#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) +#define E_WOULD_BLOCK(x) ((x) == EAGAIN || (x) == EWOULDBLOCK) +#else +#define E_WOULD_BLOCK(x) ((x) == EAGAIN) +#endif +#else +#define E_WOULD_BLOCK(x) ((x) == EWOULDBLOCK) +#endif + +/* Some broken systems care about text versus binary, but + real Unix systems don't... */ +#ifndef HAVE_O_TEXT_DEFINITION +#define O_TEXT 0 +#endif +#ifndef HAVE_O_BINARY_DEFINITION +#define O_BINARY 0 +#endif + +/* If we don't have intmax_t, try creating it */ + +#ifndef HAVE_INTMAX_T +#ifdef HAVE_LONG_LONG +typedef long long intmax_t; +typedef unsigned long long uintmax_t; +#define PRIdMAX "lld" +#define PRIuMAX "llu" +#define PRIxMAX "llx" +#define INTMAX_C(x) (x##LL) +#define UINTMAX_C(x) (x##ULL) +#else +typedef long intmax_t; +typedef unsigned long uintmax_t; +#define PRIdMAX "ld" +#define PRIuMAX "lu" +#define PRIxMAX "lx" +#define INTMAX_C(x) (x##L) +#define UINTMAX_C(x) (x##UL) +#endif +#endif + +/* On some version of AIX, is buggy to the point of + unusability. We have to use macros here, not typedefs, to override. */ +#ifdef HAVE_INTTYPES_H +#ifndef INTTYPES_H_IS_SANE +#undef PRIdMAX +#undef PRIuMAX +#undef PRIxMAX +#undef INTMAX_C +#undef UINTMAX_C +#undef HAVE_STRTOUMAX + +#ifdef HAVE_LONG_LONG +#define intmax_t long long +#define uintmax_t unsigned long long +#define PRIdMAX "Ld" +#define PRIuMAX "Lu" +#define PRIxMAX "Lx" +#define INTMAX_C(x) (x##LL) +#define UINTMAX_C(x) (x##ULL) +#else +#define intmax_t long +#define uintmax_t unsigned long +#define PRIdMAX "ld" +#define PRIuMAX "lu" +#define PRIxMAX "lx" +#define INTMAX_C(x) (x##L) +#define UINTMAX_C(x) (x##UL) +#endif +#endif +#endif + +/* Even if intmax_t is defined, we may need this (Solaris 8 braindamage) */ +#ifndef HAVE_STRTOUMAX +#if defined(HAVE_LONG_LONG) && defined(HAVE_STRTOULL) +#define strtoumax(p,e,b) ((uintmax_t)strtoull(p,e,b)) +#else +#define strtoumax(p,e,b) ((uintmax_t)strtoul(p,e,b)) +#endif +#endif + +/* A lot of this is old BSD code. Some newer systems don't approve. */ + +/* The type used by htons(), ntohs() */ +#ifndef HAVE_U_SHORT +#ifdef HAVE_UINT16_T +typedef uint16_t u_short; +#else +typedef unsigned short u_short; +#endif +#endif + +/* The type used to htonl(), ntohl() */ +#ifndef HAVE_U_LONG +#ifdef HAVE_UINT32_T +typedef uint32_t u_long; +#else +typedef unsigned long u_long; +#endif +#endif + +/* socklen_t */ +#ifndef HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +/* sysexits.h */ + +#ifdef HAVE_SYSEXITS_H +#include +#else +#define EX_USAGE 64 /* command line usage error */ +#define EX_DATAERR 65 /* data format error */ +#define EX_NOINPUT 66 /* cannot open input */ +#define EX_NOUSER 67 /* addressee unknown */ +#define EX_NOHOST 68 /* host name unknown */ +#define EX_UNAVAILABLE 69 /* service unavailable */ +#define EX_SOFTWARE 70 /* internal software error */ +#define EX_OSERR 71 /* system error (e.g., can't fork) */ +#define EX_OSFILE 72 /* critical OS file missing */ +#define EX_CANTCREAT 73 /* can't create (user) output file */ +#define EX_IOERR 74 /* input/output error */ +#define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */ +#define EX_PROTOCOL 76 /* remote error in protocol */ +#define EX_NOPERM 77 /* permission denied */ +#define EX_CONFIG 78 /* configuration error */ +#endif + +/* If we don't have sigsetjmp() et all, setjmp() will have to do */ + +#ifndef HAVE_SIGSETJMP +#define sigsetjmp(x,y) setjmp(x) +#define siglongjmp(x,y) longjmp(x,y) +#define sigjmp_buf jmp_buf +#endif + +/* How do we annotate unused data items? */ + +#ifndef UNUSED +#ifdef __GNUC__ +#define UNUSED __attribute__((unused)) +#else +#define UNUSED +#endif +#endif + +/* netinet/in.h, and possible missing pieces */ + +#include + +#ifndef HAVE_IPPORT_TFTP_DEFINITION +#ifndef IPPORT_TFTP +#define IPPORT_TFTP 69 +#endif +#endif + +/* arpa/{inet,tftp}.h, and possible missing pieces */ + +#ifdef HAVE_ARPA_INET_H +#include +#endif +/* If we don't have arpa/tftp.h we have problems... */ +#include + +#ifndef OACK +#define OACK 6 +#endif +#ifndef EOPTNEG +#define EOPTNEG 8 +#endif + +/* Prototypes for libxtra functions */ + +void *xmalloc(size_t); +char *xstrdup(const char *); + +#ifndef HAVE_BSD_SIGNAL +void (*bsd_signal(int, void (*)(int))) (int); +#endif +#ifndef HAVE_DUP2 +int dup2(int, int); +#endif +#ifndef HAVE_DAEMON +int daemon(int, int); +#endif + +#ifndef HAVE_GETADDRINFO +#ifndef HAVE_STRUCT_ADDRINFO +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; +#endif +int getaddrinfo(const char *, const char *, const struct addrinfo *, + struct addrinfo **); +void freeaddrinfo(struct addrinfo *); +const char *gai_strerror(int); + + +#ifndef EAI_NONAME +#define EAI_NONAME -2 /* NAME or SERVICE is unknown. */ +#endif +#ifndef EAI_ADDRFAMILY +#define EAI_ADDRFAMILY -9 /* Address family for NAME not supported. */ +#endif +#ifndef EAI_MEMORY +#define EAI_MEMORY -10 /* Memory allocation failure. */ +#endif +#ifndef EAI_SYSTEM +#define EAI_SYSTEM -11 /* System error returned in `errno'. */ +#endif +#endif + +#ifndef AI_CANONNAME +#define AI_CANONNAME 0 +#endif + +#ifndef AI_ADDRCONFIG +#define AI_ADDRCONFIG 0 +#endif + +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +#ifndef HAVE_INET_NTOP +const char *inet_ntop(int, const void *, char *, socklen_t); +#endif + +/* tftp-hpa version and configuration strings */ + +#include "version.h" + +#ifdef WITH_READLINE +#define WITH_READLINE_STR ", with readline" +#else +#define WITH_READLINE_STR ", without readline" +#endif + +#ifdef WITH_REGEX +#define WITH_REGEX_STR ", with remap" +#else +#define WITH_REGEX_STR ", without remap" +#endif + +#ifdef HAVE_LIBWRAP +#define HAVE_LIBWRAP_STR ", with tcpwrappers" +#else +#define HAVE_LIBWRAP_STR ", without tcpwrappers" +#endif + +#define TFTP_CONFIG_STR VERSION WITH_READLINE_STR +#define TFTPD_CONFIG_STR VERSION WITH_REGEX_STR HAVE_LIBWRAP_STR + +#endif diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..7ab7c5a --- /dev/null +++ b/configure.in @@ -0,0 +1,303 @@ +dnl +dnl autoconf input file to generate MCONFIG +dnl + +AC_PREREQ(2.61) +AC_INIT(MCONFIG.in) +AC_PREFIX_DEFAULT(/usr) + +AC_USE_SYSTEM_EXTENSIONS +AC_ISC_POSIX +AC_PROG_CC + +AC_C_CONST +AC_C_INLINE + +PA_ADD_CFLAGS(-W) +PA_ADD_CFLAGS(-Wall) +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(-Wwrite-strings) +PA_ADD_CFLAGS(-Wundef) +PA_ADD_CFLAGS(-Wshadow) +PA_ADD_CFLAGS(-Wsign-compare) +PA_ADD_CFLAGS(-pipe) +PA_ADD_CFLAGS(-fno-strict-aliasing) + +AC_HEADER_STDC +AC_CHECK_HEADERS(inttypes.h) +AC_CHECK_HEADERS(stdint.h) +PA_CHECK_INTTYPES_H_SANE +AC_CHECK_HEADERS(fcntl.h) +AC_CHECK_HEADERS(grp.h) +AC_CHECK_HEADERS(libgen.h) +AC_CHECK_HEADERS(memory.h) +AC_CHECK_HEADERS(setjmp.h) +AC_CHECK_HEADERS(stddef.h) +AC_CHECK_HEADERS(stdlib.h) +AC_CHECK_HEADERS(string.h) +AC_CHECK_HEADERS(strings.h) +AC_CHECK_HEADERS(sysexits.h) +AC_CHECK_HEADERS(time.h) +AC_CHECK_HEADERS(unistd.h) +AC_CHECK_HEADERS(sys/file.h) +AC_CHECK_HEADERS(sys/filio.h) +AC_CHECK_HEADERS(sys/stat.h) +AC_CHECK_HEADERS(sys/time.h) +AC_CHECK_HEADERS(sys/types.h) +AC_CHECK_HEADERS(arpa/inet.h) +AC_CHECK_HEADERS(netdb.h) +AC_HEADER_TIME +dnl This is needed on some versions of FreeBSD... +AC_CHECK_HEADERS(machine/param.h) +AC_CHECK_HEADERS(sys/socket.h) +AC_CHECK_HEADERS(winsock2.h) +AC_CHECK_HEADERS(winsock.h) + +AC_SYS_LARGEFILE + +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_MODE_T +AC_TYPE_SIZE_T +AC_CHECK_TYPES(intmax_t) +AC_CHECK_TYPES(long long) +AC_CHECK_TYPES(uint16_t) +AC_CHECK_TYPES(uint32_t) +AC_CHECK_TYPES(u_short) +AC_CHECK_TYPES(u_long) + +dnl +dnl isn't among the list of standard headers that autoconf checks, +dnl but POSIX requires for socklen_t to be defined. +dnl +AC_CHECK_TYPES(socklen_t,,, +[ +#include +#if HAVE_SYS_TYPES_H +# include +#endif +#if HAVE_SYS_STAT_H +# include +#endif +#if STDC_HEADERS +# include +# include +#else +# if HAVE_STDLIB_H +# include +# endif +#endif +#if HAVE_STRING_H +# if !STDC_HEADERS && HAVE_MEMORY_H +# include +# endif +# include +#endif +#if HAVE_STRINGS_H +# include +#endif +#if HAVE_INTTYPES_H +# include +#else +# if HAVE_STDINT_H +# include +# endif +#endif +#if HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +]) + +AC_SEARCH_LIBS(socket, [socket ws2_32 wsock32], , [AC_MSG_ERROR(socket library not found)]) + +AC_CHECK_FUNCS(fcntl) +AC_CHECK_FUNCS(setsid) +AC_CHECK_FUNCS(recvmsg) +AC_CHECK_FUNCS(ftruncate) +AC_CHECK_FUNCS(setreuid) +AC_CHECK_FUNCS(setregid) +AC_CHECK_FUNCS(initgroups) +AC_CHECK_FUNCS(setgroups) + +dnl Solaris 8 has [u]intmax_t but not strtoumax(). How utterly braindamaged. +AC_CHECK_FUNCS(strtoumax) +AC_CHECK_FUNCS(strtoull) + +PA_MSGHDR_MSG_CONTROL +PA_STRUCT_IN_PKTINFO +PA_STRUCT_ADDRINFO + +PA_HEADER_DEFINES(fcntl.h, int, O_NONBLOCK) +PA_HEADER_DEFINES(fcntl.h, int, O_BINARY) +PA_HEADER_DEFINES(fcntl.h, int, O_TEXT) + +PA_HEADER_DEFINES(fcntl.h, int, F_SETLK) + +PA_HEADER_DEFINES(sys/file.h, int, LOCK_SH) +PA_HEADER_DEFINES(sys/file.h, int, LOCK_EX) + +AH_TEMPLATE([HAVE_SIGSETJMP], +[Define if we have sigsetjmp, siglongjmp and sigjmp_buf.]) +PA_SIGSETJMP([AC_DEFINE(HAVE_SIGSETJMP)]) + +dnl +dnl Get common paths +dnl +SRCROOT=`cd $srcdir && pwd` +OBJROOT=`pwd` + +XTRA=false +PA_SEARCH_LIBS_AND_ADD(xmalloc, iberty) +PA_SEARCH_LIBS_AND_ADD(xstrdup, iberty) +PA_SEARCH_LIBS_AND_ADD(bsd_signal, bsd, bsdsignal) +PA_SEARCH_LIBS_AND_ADD(getopt_long, getopt, getopt_long) +PA_SEARCH_LIBS_AND_ADD(getaddrinfo, [nsl resolv]) +if $pa_add_getaddrinfo +then + AC_SEARCH_LIBS(gethostbyname, [nsl resolv], + [AC_SEARCH_LIBS(herror, [nsl resolv], , + [AC_MSG_ERROR(herror not found)])], + [AC_MSG_ERROR(gethostbyname not found)]) +else + AC_SEARCH_LIBS(freeaddrinfo, [nsl resolv], , + [AC_MSG_ERROR(getaddrinfo but not freeaddrinfo found)]) + AC_SEARCH_LIBS(gai_strerror, [nsl resolv], , + [AC_MSG_ERROR(getaddrinfo but not gai_strerror found)]) +fi + +PA_SEARCH_LIBS_AND_ADD(inet_ntop, [nsl resolv]) +if $pa_add_inet_ntop +then + AC_SEARCH_LIBS(inet_ntoa, [nsl resolv], , + [AC_MSG_ERROR(inet_ntoa not found)]) +fi +AC_SEARCH_LIBS(inet_aton, [nsl resolv], ,[AC_MSG_ERROR(inet_aton not found)]) + +AC_CHECK_FUNCS(daemon, , [XTRA=true; AC_LIBOBJ(daemon)]) +AC_CHECK_FUNCS(dup2, , [XTRA=true; AC_LIBOBJ(dup2)]) +if $XTRA +then + XTRALIBS="$OBJROOT/lib/libxtra.a $XTRALIBS" +fi + +dnl +dnl These libraries apply to the server only +dnl + +common_libs="$LIBS" + +PA_HEADER_DEFINES(netinet/in.h, int, IPPORT_TFTP) + +PA_WITH_BOOL(tcpwrappers, 1, +[ --without-tcpwrappers disable tcpwrapper permissions checking], +[ + AC_SEARCH_LIBS(yp_get_default_domain, [nsl resolv]) + PA_HAVE_TCPWRAPPERS +],:) + + +AH_TEMPLATE([WITH_REGEX], +[Define if we are compiling with regex filename remapping.]) + +PA_WITH_BOOL(remap, 1, +[ --without-remap disable regex-based filename remapping], +[ + AC_CHECK_HEADER(regex.h, + [ + AC_SEARCH_LIBS(regcomp, [regex rx], + [ + AC_DEFINE(WITH_REGEX) + TFTPDOBJS="remap.${OBJEXT} $TFTPDOBJS" + ]) + ]) +],:) + +TFTPD_LIBS="$LIBS $XTRALIBS" +LIBS="$common_libs" + +dnl +dnl These libraries apply to the client only +dnl + +AH_TEMPLATE([WITH_READLINE], +[Define if we are compiling with readline/editline command-line editing.]) + +PA_WITH_BOOL(readline, 1, +[ --without-readline disable the use of readline command-line editing], +[ + AC_CHECK_HEADER(readline/readline.h, + [ + dnl readline may need libtermcap or somesuch... + AC_SEARCH_LIBS(tputs, [termcap terminfo]) + + AC_SEARCH_LIBS(readline, [readline history], + [AC_DEFINE(WITH_READLINE)]) + AC_CHECK_HEADERS(readline/history.h) + ], + [AC_CHECK_HEADER(editline/readline.h, + [ + dnl editline may need libtermcap or somesuch... + AC_SEARCH_LIBS(tputs, [termcap terminfo]) + + AC_SEARCH_LIBS(editline, [edit], + [AC_DEFINE(WITH_READLINE)]) + ])]) +],:) + +TFTP_LIBS="$LIBS $XTRALIBS" +LIBS="$common_libs" + +dnl +dnl Check for IPV6 and disable-ipv6 +dnl +PA_STRUCT_SOCKADDR_IN6 +AC_MSG_CHECKING([for IPv6 support]) +PA_WITH_BOOL(ipv6, 1, +[ --without-ipv6 disable the support for IPv6], +[ + if $HAVE_INET6 + then + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_IPV6, 1, [Define if IPv6 support is enabled.]) + PA_STRUCT_IN6_PKTINFO + else + AC_MSG_RESULT(no) + AC_MSG_WARN([*** we do not have required IPv6 structs - IPv6 will be disabled]) + fi +], +[AC_MSG_RESULT(disabled)]) + + +AC_SUBST(SRCROOT) +AC_SUBST(OBJROOT) + +AC_SUBST(TFTP_LIBS) +AC_SUBST(TFTPD_LIBS) +AC_SUBST(TFTPDOBJS) + +AC_PROG_LN_S +AC_PROG_RANLIB + +dnl +dnl Make sure the install program has an absolute path if it +dnl has a path at all. autoconf doesn't do this "in order +dnl to not pollute the cache." Sigh. +dnl Note: the $ needs to be double-quoted for reasons unknown. +dnl +AC_PROG_INSTALL +[if echo "$INSTALL" | grep '^[^/].*/' > /dev/null 2>&1; then + INSTALL='\${SRCROOT}'/"$INSTALL" +fi] + +AC_CONFIG_HEADERS(aconfig.h) +AC_OUTPUT(MCONFIG) diff --git a/install-sh b/install-sh new file mode 100755 index 0000000..398a88e --- /dev/null +++ b/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# 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. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# 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}" + +transformbasename="" +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 + : +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=$mkdirprog + 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 + : + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + : + 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 + : + 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 + : + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else : ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else : ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else : ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else : ; 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 + : + 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 :;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else :;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else :;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else :;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..a43ce19 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,29 @@ +# +# Extra functions which may not be available everywhere +# + +SRCROOT = .. + +-include ../MCONFIG +include ../MRULES + +ifeq ($(LIBOBJS),) +all: +else +all: libxtra.a +endif + +install: + +clean: + -rm -f *.a *.o *.obj *.exe + +distclean: clean + -rm -f *~ + +libxtra.a: $(LIBOBJS) + -rm -f libxtra.a + $(AR) libxtra.a $(LIBOBJS) + $(RANLIB) libxtra.a + + diff --git a/lib/bsdsignal.c b/lib/bsdsignal.c new file mode 100644 index 0000000..0aae136 --- /dev/null +++ b/lib/bsdsignal.c @@ -0,0 +1,27 @@ +/* + * bsdsignal.c + * + * Use sigaction() to simulate BSD signal() + */ + +#include "config.h" + +void (*bsd_signal(int signum, void (*handler) (int))) (int) { + struct sigaction action, oldaction; + + memset(&action, 0, sizeof action); + action.sa_handler = handler; + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, signum); + action.sa_flags = SA_RESTART; + + if (sigaction(signum, &action, &oldaction) == -1) { +#ifdef SIG_ERR + return SIG_ERR; +#else + return NULL; +#endif + } + + return oldaction.sa_handler; +} diff --git a/lib/daemon.c b/lib/daemon.c new file mode 100644 index 0000000..0eb39c9 --- /dev/null +++ b/lib/daemon.c @@ -0,0 +1,36 @@ +/* + * daemon.c - "daemonize" a process + */ + +#include "config.h" + +int daemon(int nochdir, int noclose) +{ + int nullfd; + pid_t f; + + if (!nochdir) { + if (chdir("/")) + return -1; + } + + if (!noclose) { + if ((nullfd = open("/dev/null", O_RDWR)) < 0 || + dup2(nullfd, 0) < 0 || + dup2(nullfd, 1) < 0 || dup2(nullfd, 2) < 0) + return -1; + close(nullfd); + } + + f = fork(); + if (f < 0) + return -1; + else if (f > 0) + _exit(0); + +#ifdef HAVE_SETSID + return setsid(); +#else + return 0; +#endif +} diff --git a/lib/dup2.c b/lib/dup2.c new file mode 100644 index 0000000..bba45c4 --- /dev/null +++ b/lib/dup2.c @@ -0,0 +1,23 @@ +/* + * dup2.c + * + * Ersatz dup2() for really ancient systems + */ + +#include "config.h" + +int dup2(int oldfd, int newfd) +{ + int rv, nfd; + + close(newfd); + + nfd = rv = dup(oldfd); + + if (rv >= 0 && rv != newfd) { + rv = dup2(oldfd, newfd); + close(nfd); + } + + return rv; +} diff --git a/lib/getaddrinfo.c b/lib/getaddrinfo.c new file mode 100644 index 0000000..ef7c9ae --- /dev/null +++ b/lib/getaddrinfo.c @@ -0,0 +1,121 @@ +/* + * getaddrinfo.c + * + * Simple version of getaddrinfo() + * + */ + +#include "config.h" + +extern int errno; +extern int h_errno; + +void freeaddrinfo(struct addrinfo *res) +{ + if (!res) + return; + if (res->ai_next) + freeaddrinfo(res->ai_next); + if (res->ai_addr) + free(res->ai_addr); + if (res->ai_canonname) + free(res->ai_canonname); + free(res); +} + +int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, + struct addrinfo **res) +{ + struct hostent *host; + struct sockaddr *sa; + int err, size = 0; + + if ((!node) || (!res)) { + errno = EINVAL; + return EAI_SYSTEM; + } + *res = NULL; + /* we do not support service in this version */ + if (service) { + errno = EINVAL; + return EAI_SYSTEM; + } + host = gethostbyname(node); + if (!host) + return EAI_NONAME; + if (hints) { + if (hints->ai_family != AF_UNSPEC) { + if (hints->ai_family != host->h_addrtype) + return EAI_ADDRFAMILY; + } + } + *res = malloc(sizeof(struct addrinfo)); + if (!*res) { + return EAI_MEMORY; + } + memset(*res, 0, sizeof(struct addrinfo)); + (*res)->ai_family = host->h_addrtype; + if (host->h_length) { + if (host->h_addrtype == AF_INET) + size = sizeof(struct sockaddr_in); +#ifdef HAVE_IPV6 + else if (host->h_addrtype == AF_INET6) + size = sizeof(struct sockaddr_in6); +#endif + else { + free(*res); + *res = NULL; + return EAI_ADDRFAMILY; + } + sa = malloc(size); + if (!sa) { + free(*res); + *res = NULL; + return EAI_MEMORY; + } + memset(sa, 0, size); + (*res)->ai_addr = sa; + (*res)->ai_addrlen = size; + sa->sa_family = host->h_addrtype; + if (host->h_addrtype == AF_INET) + memcpy(&((struct sockaddr_in *)sa)->sin_addr, host->h_addr, host->h_length); +#ifdef HAVE_IPV6 + else + memcpy(&((struct sockaddr_in6 *)sa)->sin6_addr, host->h_addr, host->h_length); +#endif + } + if (host->h_name) + (*res)->ai_canonname = strdup(host->h_name); + + /* we only handle the first address entry and do not build a list now */ + return 0; +} + + + +const char *gai_strerror(int errcode) +{ + const char *s = NULL; + + switch(errcode) { + case 0: + s = "no error"; + break; + case EAI_MEMORY: + s = "no memory"; + break; + case EAI_SYSTEM: + s = strerror(errno); + break; + case EAI_NONAME: + s = hstrerror(h_errno); + break; + case EAI_ADDRFAMILY: + s = "address does not match address family"; + break; + default: + s = "unknown error code"; + break; + } + return s; +} diff --git a/lib/getopt.h b/lib/getopt.h new file mode 100644 index 0000000..c1ad561 --- /dev/null +++ b/lib/getopt.h @@ -0,0 +1,23 @@ +#ifndef LIB_GETOPT_H +#define LIB_GETOPT_H + +extern char *optarg; +extern int optind, opterr, optopt; + +struct option { + const char *name; + int has_arg; + int *flag; + int val; +}; + +enum { + no_argument = 0, + required_argument = 1, + optional_argument = 2, +}; + +int getopt_long(int, char *const *, const char *, + const struct option *, int *); + +#endif /* LIB_GETOPT_H */ diff --git a/lib/getopt_long.c b/lib/getopt_long.c new file mode 100644 index 0000000..49d1274 --- /dev/null +++ b/lib/getopt_long.c @@ -0,0 +1,150 @@ +/* + * getopt_long.c + * + * getopt_long(), or at least a common subset thereof: + * + * - Option reordering is not supported + * - -W foo is not supported + * - First optstring character "-" not supported. + */ + +#include "config.h" + +char *optarg; +int optind, opterr, optopt; + +static struct getopt_private_state { + const char *optptr; + const char *last_optstring; + char *const *last_argv; +} pvt; + +static inline const char *option_matches(const char *arg_str, + const char *opt_name) +{ + while (*arg_str != '\0' && *arg_str != '=') { + if (*arg_str++ != *opt_name++) + return NULL; + } + + if (*opt_name) + return NULL; + + return arg_str; +} + +int getopt_long(int argc, char *const *argv, const char *optstring, + const struct option *longopts, int *longindex) +{ + const char *carg; + const char *osptr; + int opt; + + /* getopt() relies on a number of different global state + variables, which can make this really confusing if there is + more than one use of getopt() in the same program. This + attempts to detect that situation by detecting if the + "optstring" or "argv" argument have changed since last time + we were called; if so, reinitialize the query state. */ + + if (optstring != pvt.last_optstring || argv != pvt.last_argv || + optind < 1 || optind > argc) { + /* optind doesn't match the current query */ + pvt.last_optstring = optstring; + pvt.last_argv = argv; + optind = 1; + pvt.optptr = NULL; + } + + carg = argv[optind]; + + /* First, eliminate all non-option cases */ + + if (!carg || carg[0] != '-' || !carg[1]) + return -1; + + if (carg[1] == '-') { + const struct option *lo; + const char *opt_end = NULL; + + optind++; + + /* Either it's a long option, or it's -- */ + if (!carg[2]) { + /* It's -- */ + return -1; + } + + for (lo = longopts; lo->name; lo++) { + if ((opt_end = option_matches(carg+2, lo->name))) + break; + } + if (!opt_end) + return '?'; + + if (longindex) + *longindex = lo-longopts; + + if (*opt_end == '=') { + if (lo->has_arg) + optarg = (char *)opt_end+1; + else + return '?'; + } else if (lo->has_arg == 1) { + if (!(optarg = argv[optind])) + return '?'; + optind++; + } + + if (lo->flag) { + *lo->flag = lo->val; + return 0; + } else { + return lo->val; + } + } + + if ((uintptr_t) (pvt.optptr - carg) > (uintptr_t) strlen(carg)) { + /* Someone frobbed optind, change to new opt. */ + pvt.optptr = carg + 1; + } + + opt = *pvt.optptr++; + + if (opt != ':' && (osptr = strchr(optstring, opt))) { + if (osptr[1] == ':') { + if (*pvt.optptr) { + /* Argument-taking option with attached + argument */ + optarg = (char *)pvt.optptr; + optind++; + } else { + /* Argument-taking option with non-attached + argument */ + if (argv[optind + 1]) { + optarg = (char *)argv[optind+1]; + optind += 2; + } else { + /* Missing argument */ + optind++; + return (optstring[0] == ':') + ? ':' : '?'; + } + } + return opt; + } else { + /* Non-argument-taking option */ + /* pvt.optptr will remember the exact position to + resume at */ + if (!*pvt.optptr) + optind++; + return opt; + } + } else { + /* Unknown option */ + optopt = opt; + if (!*pvt.optptr) + optind++; + return '?'; + } +} diff --git a/lib/inet_ntop.c b/lib/inet_ntop.c new file mode 100644 index 0000000..fe8e560 --- /dev/null +++ b/lib/inet_ntop.c @@ -0,0 +1,52 @@ +/* + * inet_ntop.c + * + * Simple version of inet_ntop() + * + */ + +#include "config.h" + +extern int errno; + +const char *inet_ntop(int af, const void *src, + char *dst, socklen_t cnt) +{ + char *p; + + switch(af) { + case AF_INET: + p = inet_ntoa(*((struct in_addr *)src)); + if (p) { + if (cnt <= strlen(p)) { + errno = ENOSPC; + dst = NULL; + } else + strcpy(dst, p); + } else + dst = NULL; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + if (cnt < 40) { + errno = ENOSPC; + dst = NULL; + } else { + struct in6_addr *a = src; + int i; + + p = (char *)dst; + /* we do not compress :0: to :: */ + for (i = 0; i < 8; i++) + p += sprintf(p, "%x:", ntohs(a->s6_addr16[i])); + p--; + *p = 0; + } + break; +#endif + default: + errno = EAFNOSUPPORT; + dst = NULL; + } + return dst; +} diff --git a/lib/xmalloc.c b/lib/xmalloc.c new file mode 100644 index 0000000..30704f3 --- /dev/null +++ b/lib/xmalloc.c @@ -0,0 +1,20 @@ +/* + * xmalloc.c + * + * Simple error-checking version of malloc() + * + */ + +#include "config.h" + +void *xmalloc(size_t size) +{ + void *p = malloc(size); + + if (!p) { + fprintf(stderr, "Out of memory!\n"); + exit(128); + } + + return p; +} diff --git a/lib/xstrdup.c b/lib/xstrdup.c new file mode 100644 index 0000000..05e3054 --- /dev/null +++ b/lib/xstrdup.c @@ -0,0 +1,20 @@ +/* + * xstrdup.c + * + * Simple error-checking version of strdup() + * + */ + +#include "config.h" + +char *xstrdup(const char *s) +{ + char *p = strdup(s); + + if (!p) { + fprintf(stderr, "Out of memory!\n"); + exit(128); + } + + return p; +} diff --git a/tftp-xinetd b/tftp-xinetd new file mode 100644 index 0000000..982fe09 --- /dev/null +++ b/tftp-xinetd @@ -0,0 +1,18 @@ +# default: off +# description: The tftp server serves files using the trivial file transfer \ +# protocol. The tftp protocol is often used to boot diskless \ +# workstations, download configuration files to network-aware printers, \ +# and to start the installation process for some operating systems. +service tftp +{ + socket_type = dgram + protocol = udp + wait = yes + user = root + server = /usr/sbin/in.tftpd + server_args = -s /tftpboot + disable = yes + per_source = 11 + cps = 100 2 + flags = IPv4 +} diff --git a/tftp.spec b/tftp.spec new file mode 100644 index 0000000..2f8b367 --- /dev/null +++ b/tftp.spec @@ -0,0 +1,228 @@ +Summary: The client for the Trivial File Transfer Protocol (TFTP). +Name: tftp +Version: 5.2 +Release: 1 +License: BSD +Group: Applications/Internet +Source0: http://www.kernel.org/pub/software/network/tftp/tftp-hpa-%{version}.tar.gz +BuildRequires: tcp_wrappers-devel +BuildRoot: %{_tmppath}/%{name}-root + +%description +The Trivial File Transfer Protocol (TFTP) is normally used only for +booting diskless workstations. The tftp package provides the user +interface for TFTP, which allows users to transfer files to and from a +remote machine. This program and TFTP provide very little security, +and should not be enabled unless it is expressly needed. + +%package server +Group: System Environment/Daemons +Summary: The server for the Trivial File Transfer Protocol (TFTP). +Requires: xinetd + +%description server +The Trivial File Transfer Protocol (TFTP) is normally used only for +booting diskless workstations. The tftp-server package provides the +server for TFTP, which allows users to transfer files to and from a +remote machine. TFTP provides very little security, and should not be +enabled unless it is expressly needed. The TFTP server is run from +/etc/xinetd.d/tftp, and is disabled by default on Red Hat Linux systems. + +%prep +%setup -q -n tftp-hpa-%{version} + +%build + +%configure +make %{?_smp_mflags} + +%install +rm -rf ${RPM_BUILD_ROOT} +mkdir -p ${RPM_BUILD_ROOT}%{_bindir} +mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man{1,8} +mkdir -p ${RPM_BUILD_ROOT}%{_sbindir} + +make INSTALLROOT=${RPM_BUILD_ROOT} \ + SBINDIR=%{_sbindir} MANDIR=%{_mandir} \ + install +install -m755 -d ${RPM_BUILD_ROOT}%{_sysconfdir}/xinetd.d/ ${RPM_BUILD_ROOT}/tftpboot +install -m644 tftp-xinetd ${RPM_BUILD_ROOT}%{_sysconfdir}/xinetd.d/tftp + +%post server +/sbin/service xinetd reload > /dev/null 2>&1 || : + +%postun server +if [ $1 = 0 ]; then + /sbin/service xinetd reload > /dev/null 2>&1 || : +fi + +%clean +rm -rf ${RPM_BUILD_ROOT} + +%files +%defattr(-,root,root) +%{_bindir}/tftp +%{_mandir}/man1/* + +%files server +%defattr(-,root,root) +%config(noreplace) %{_sysconfdir}/xinetd.d/tftp +%dir /tftpboot +%{_sbindir}/in.tftpd +%{_mandir}/man8/* + +%changelog +* Tue Sep 14 2004 H. Peter Anvin +- removed completely broken "Malta" patch. +- integrated into build machinery so rpm -ta works. + +* Fri Feb 13 2004 Elliot Lee +- rebuilt + +* Wed Jun 04 2003 Elliot Lee +- rebuilt + +* Fri Apr 11 2003 Elliot Lee +- 0.33 +- Add /tftpboot directory (#88204) + +* Mon Feb 24 2003 Elliot Lee +- rebuilt + +* Sun Feb 23 2003 Tim Powers +- add BuildPreReq on tcp_wrappers + +* Wed Jan 22 2003 Tim Powers +- rebuilt + +* Mon Nov 11 2002 Elliot Lee 0.32-1 +- Update to 0.32 + +* Wed Oct 23 2002 Elliot Lee 0.30-1 +- Fix #55789 +- Update to 0.30 + +* Thu Jun 27 2002 Elliot Lee +- Try applying HJ's patch from #65476 + +* Fri Jun 21 2002 Tim Powers +- automated rebuild + +* Mon Jun 17 2002 Elliot Lee +- Update to 0.29 + +* Thu May 23 2002 Tim Powers +- automated rebuild + +* Wed Jan 09 2002 Tim Powers +- automated rebuild + +* Tue Dec 18 2001 Elliot Lee 0.17-15 +- Add patch4: netkit-tftp-0.17-defaultport.patch for bug #57562 +- Update to tftp-hpa-0.28 (bug #56131) +- Remove include/arpa/tftp.h to fix #57259 +- Add resource limits in tftp-xinetd (#56722) + +* Sun Jun 24 2001 Elliot Lee +- Bump release + rebuild. + +* Tue Jun 12 2001 Helge Deller (0.17-13) +- updated tftp-hpa source to tftp-hpa-0.17 +- tweaked specfile with different defines for tftp-netkit and tftp-hpa version +- use hpa's tftpd.8 man page instead of the netkits one + +* Mon May 07 2001 Helge Deller +- rebuilt in 7.1.x + +* Wed Apr 18 2001 Helge Deller +- fix tftp client's put problems (#29529) +- update to tftp-hpa-0.16 + +* Wed Apr 4 2001 Jakub Jelinek +- don't let configure to guess compiler, it can pick up egcs + +* Thu Feb 08 2001 Helge Deller +- changed "wait" in xinetd file to "yes" (hpa-tftpd forks and exits) (#26467) +- fixed hpa-tftpd to handle files greater than 32MB (#23725) +- added "-l" flag to hpa-tftpd for file-logging (#26467) +- added description for "-l" to the man-page + +* Thu Feb 08 2001 Helge Deller +- updated tftp client to 0.17 stable (#19640), +- drop dependency on xinetd for tftp client (#25051), + +* Wed Jan 17 2001 Jeff Johnson +- xinetd shouldn't wait on tftp (which forks) (#23923). + +* Sat Jan 6 2001 Jeff Johnson +- fix to permit tftp put's (#18128). +- startup as root with chroot to /tftpboot with early reversion to nobody + is preferable to starting as nobody w/o ability to chroot. +- %%post is needed by server, not client. Add %%postun for erasure as well. + +* Wed Aug 23 2000 Nalin Dahyabhai +- default to being disabled + +* Thu Aug 17 2000 Jeff Johnson +- correct group. + +* Tue Jul 25 2000 Nalin Dahyabhai +- change user from root to nobody + +* Sat Jul 22 2000 Jeff Johnson +- update to tftp-hpa-0.14 (#14003). +- add server_args (#14003). +- remove -D_BSD_SOURCE (#14003). + +* Fri Jul 21 2000 Nalin Dahyabhai +- cook up an xinetd config file for tftpd + +* Wed Jul 12 2000 Prospector +- automatic rebuild + +* Sun Jun 18 2000 Jeff Johnson +- FHS packaging. +- update to 0.17. + +* Fri May 5 2000 Matt Wilson +- use _BSD_SOURCE for hpa's tftpd so we get BSD signal semantics. + +* Fri Feb 11 2000 Bill Nottingham +- fix description + +* Wed Feb 9 2000 Jeff Johnson +- compress man pages (again). + +* Wed Feb 02 2000 Cristian Gafton +- man pages are compressed +- fix description and summary + +* Tue Jan 4 2000 Bill Nottingham +- split client and server + +* Tue Dec 21 1999 Jeff Johnson +- update to 0.16. + +* Sat Aug 28 1999 Jeff Johnson +- update to 0.15. + +* Wed Apr 7 1999 Jeff Johnson +- tftpd should truncate file when overwriting (#412) + +* Sun Mar 21 1999 Cristian Gafton +- auto rebuild in the new build environment (release 22) + +* Mon Mar 15 1999 Jeff Johnson +- compile for 6.0. + +* Fri Aug 7 1998 Jeff Johnson +- build root + +* Mon Apr 27 1998 Prospector System +- translations modified for de, fr, tr + +* Mon Sep 22 1997 Erik Troan +- added check for getpwnam() failure + +* Tue Jul 15 1997 Erik Troan +- initial build diff --git a/tftp.spec.in b/tftp.spec.in new file mode 100644 index 0000000..cd5e4cc --- /dev/null +++ b/tftp.spec.in @@ -0,0 +1,228 @@ +Summary: The client for the Trivial File Transfer Protocol (TFTP). +Name: tftp +Version: @@VERSION@@ +Release: 1 +License: BSD +Group: Applications/Internet +Source0: http://www.kernel.org/pub/software/network/tftp/tftp-hpa-%{version}.tar.gz +BuildRequires: tcp_wrappers-devel +BuildRoot: %{_tmppath}/%{name}-root + +%description +The Trivial File Transfer Protocol (TFTP) is normally used only for +booting diskless workstations. The tftp package provides the user +interface for TFTP, which allows users to transfer files to and from a +remote machine. This program and TFTP provide very little security, +and should not be enabled unless it is expressly needed. + +%package server +Group: System Environment/Daemons +Summary: The server for the Trivial File Transfer Protocol (TFTP). +Requires: xinetd + +%description server +The Trivial File Transfer Protocol (TFTP) is normally used only for +booting diskless workstations. The tftp-server package provides the +server for TFTP, which allows users to transfer files to and from a +remote machine. TFTP provides very little security, and should not be +enabled unless it is expressly needed. The TFTP server is run from +/etc/xinetd.d/tftp, and is disabled by default on Red Hat Linux systems. + +%prep +%setup -q -n tftp-hpa-%{version} + +%build + +%configure +make %{?_smp_mflags} + +%install +rm -rf ${RPM_BUILD_ROOT} +mkdir -p ${RPM_BUILD_ROOT}%{_bindir} +mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man{1,8} +mkdir -p ${RPM_BUILD_ROOT}%{_sbindir} + +make INSTALLROOT=${RPM_BUILD_ROOT} \ + SBINDIR=%{_sbindir} MANDIR=%{_mandir} \ + install +install -m755 -d ${RPM_BUILD_ROOT}%{_sysconfdir}/xinetd.d/ ${RPM_BUILD_ROOT}/tftpboot +install -m644 tftp-xinetd ${RPM_BUILD_ROOT}%{_sysconfdir}/xinetd.d/tftp + +%post server +/sbin/service xinetd reload > /dev/null 2>&1 || : + +%postun server +if [ $1 = 0 ]; then + /sbin/service xinetd reload > /dev/null 2>&1 || : +fi + +%clean +rm -rf ${RPM_BUILD_ROOT} + +%files +%defattr(-,root,root) +%{_bindir}/tftp +%{_mandir}/man1/* + +%files server +%defattr(-,root,root) +%config(noreplace) %{_sysconfdir}/xinetd.d/tftp +%dir /tftpboot +%{_sbindir}/in.tftpd +%{_mandir}/man8/* + +%changelog +* Tue Sep 14 2004 H. Peter Anvin +- removed completely broken "Malta" patch. +- integrated into build machinery so rpm -ta works. + +* Fri Feb 13 2004 Elliot Lee +- rebuilt + +* Wed Jun 04 2003 Elliot Lee +- rebuilt + +* Fri Apr 11 2003 Elliot Lee +- 0.33 +- Add /tftpboot directory (#88204) + +* Mon Feb 24 2003 Elliot Lee +- rebuilt + +* Sun Feb 23 2003 Tim Powers +- add BuildPreReq on tcp_wrappers + +* Wed Jan 22 2003 Tim Powers +- rebuilt + +* Mon Nov 11 2002 Elliot Lee 0.32-1 +- Update to 0.32 + +* Wed Oct 23 2002 Elliot Lee 0.30-1 +- Fix #55789 +- Update to 0.30 + +* Thu Jun 27 2002 Elliot Lee +- Try applying HJ's patch from #65476 + +* Fri Jun 21 2002 Tim Powers +- automated rebuild + +* Mon Jun 17 2002 Elliot Lee +- Update to 0.29 + +* Thu May 23 2002 Tim Powers +- automated rebuild + +* Wed Jan 09 2002 Tim Powers +- automated rebuild + +* Tue Dec 18 2001 Elliot Lee 0.17-15 +- Add patch4: netkit-tftp-0.17-defaultport.patch for bug #57562 +- Update to tftp-hpa-0.28 (bug #56131) +- Remove include/arpa/tftp.h to fix #57259 +- Add resource limits in tftp-xinetd (#56722) + +* Sun Jun 24 2001 Elliot Lee +- Bump release + rebuild. + +* Tue Jun 12 2001 Helge Deller (0.17-13) +- updated tftp-hpa source to tftp-hpa-0.17 +- tweaked specfile with different defines for tftp-netkit and tftp-hpa version +- use hpa's tftpd.8 man page instead of the netkits one + +* Mon May 07 2001 Helge Deller +- rebuilt in 7.1.x + +* Wed Apr 18 2001 Helge Deller +- fix tftp client's put problems (#29529) +- update to tftp-hpa-0.16 + +* Wed Apr 4 2001 Jakub Jelinek +- don't let configure to guess compiler, it can pick up egcs + +* Thu Feb 08 2001 Helge Deller +- changed "wait" in xinetd file to "yes" (hpa-tftpd forks and exits) (#26467) +- fixed hpa-tftpd to handle files greater than 32MB (#23725) +- added "-l" flag to hpa-tftpd for file-logging (#26467) +- added description for "-l" to the man-page + +* Thu Feb 08 2001 Helge Deller +- updated tftp client to 0.17 stable (#19640), +- drop dependency on xinetd for tftp client (#25051), + +* Wed Jan 17 2001 Jeff Johnson +- xinetd shouldn't wait on tftp (which forks) (#23923). + +* Sat Jan 6 2001 Jeff Johnson +- fix to permit tftp put's (#18128). +- startup as root with chroot to /tftpboot with early reversion to nobody + is preferable to starting as nobody w/o ability to chroot. +- %%post is needed by server, not client. Add %%postun for erasure as well. + +* Wed Aug 23 2000 Nalin Dahyabhai +- default to being disabled + +* Thu Aug 17 2000 Jeff Johnson +- correct group. + +* Tue Jul 25 2000 Nalin Dahyabhai +- change user from root to nobody + +* Sat Jul 22 2000 Jeff Johnson +- update to tftp-hpa-0.14 (#14003). +- add server_args (#14003). +- remove -D_BSD_SOURCE (#14003). + +* Fri Jul 21 2000 Nalin Dahyabhai +- cook up an xinetd config file for tftpd + +* Wed Jul 12 2000 Prospector +- automatic rebuild + +* Sun Jun 18 2000 Jeff Johnson +- FHS packaging. +- update to 0.17. + +* Fri May 5 2000 Matt Wilson +- use _BSD_SOURCE for hpa's tftpd so we get BSD signal semantics. + +* Fri Feb 11 2000 Bill Nottingham +- fix description + +* Wed Feb 9 2000 Jeff Johnson +- compress man pages (again). + +* Wed Feb 02 2000 Cristian Gafton +- man pages are compressed +- fix description and summary + +* Tue Jan 4 2000 Bill Nottingham +- split client and server + +* Tue Dec 21 1999 Jeff Johnson +- update to 0.16. + +* Sat Aug 28 1999 Jeff Johnson +- update to 0.15. + +* Wed Apr 7 1999 Jeff Johnson +- tftpd should truncate file when overwriting (#412) + +* Sun Mar 21 1999 Cristian Gafton +- auto rebuild in the new build environment (release 22) + +* Mon Mar 15 1999 Jeff Johnson +- compile for 6.0. + +* Fri Aug 7 1998 Jeff Johnson +- build root + +* Mon Apr 27 1998 Prospector System +- translations modified for de, fr, tr + +* Mon Sep 22 1997 Erik Troan +- added check for getpwnam() failure + +* Tue Jul 15 1997 Erik Troan +- initial build diff --git a/tftp/Makefile b/tftp/Makefile new file mode 100644 index 0000000..20f4c18 --- /dev/null +++ b/tftp/Makefile @@ -0,0 +1,28 @@ +SRCROOT = .. +VERSION = $(shell cat ../version) + +-include ../MCONFIG +include ../MRULES + +OBJS = tftp.$(O) main.$(O) + +all: tftp$(X) tftp.1 + +tftp$(X): $(OBJS) + $(CC) $(LDFLAGS) $^ $(TFTP_LIBS) -o $@ + +$(OBJS): ../common/tftpsubs.h + +tftp.1: tftp.1.in ../version + sed -e 's/@@VERSION@@/$(VERSION)/g' < $< > $@ + +install: all + mkdir -p $(INSTALLROOT)$(BINDIR) $(INSTALLROOT)$(MANDIR)/man1 + $(INSTALL_PROGRAM) tftp$(X) $(INSTALLROOT)$(BINDIR) + $(INSTALL_DATA) tftp.1 $(INSTALLROOT)$(MANDIR)/man1 + +clean: + rm -f *.o *.obj *.exe tftp tftp.1 + +distclean: clean + rm -f *~ diff --git a/tftp/extern.h b/tftp/extern.h new file mode 100644 index 0000000..78474fc --- /dev/null +++ b/tftp/extern.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef RECVFILE_H +#define RECVFILE_H + +void tftp_recvfile(int, const char *, const char *); +void tftp_sendfile(int, const char *, const char *); + +#endif diff --git a/tftp/main.c b/tftp/main.c new file mode 100644 index 0000000..1b8a881 --- /dev/null +++ b/tftp/main.c @@ -0,0 +1,945 @@ +/* + * 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. + */ + +#include "common/tftpsubs.h" + +/* Many bug fixes are from Jim Guyton */ + +/* + * TFTP User Program -- Command Interface. + */ +#include +#include +#ifdef WITH_READLINE +#include +#ifdef HAVE_READLINE_HISTORY_H +#include +#endif +#endif + +#include "extern.h" + +#define TIMEOUT 5 /* secs between rexmt's */ +#define LBUFLEN 200 /* size of input buffer */ + +struct modes { + const char *m_name; + const char *m_mode; + int m_openflags; +}; + +static const struct modes modes[] = { + {"netascii", "netascii", O_TEXT}, + {"ascii", "netascii", O_TEXT}, + {"octet", "octet", O_BINARY}, + {"binary", "octet", O_BINARY}, + {"image", "octet", O_BINARY}, + {0, 0, 0} +}; + +#define MODE_OCTET (&modes[2]) +#define MODE_NETASCII (&modes[0]) +#define MODE_DEFAULT MODE_NETASCII + +#ifdef HAVE_IPV6 +int ai_fam = AF_UNSPEC; +int ai_fam_sock = AF_UNSPEC; +#else +int ai_fam = AF_INET; +int ai_fam_sock = AF_INET; +#endif + +union sock_addr peeraddr; +int f = -1; +u_short port; +int trace; +int verbose; +int literal; +int connected; +const struct modes *mode; +#ifdef WITH_READLINE +char *line = NULL; +#else +char line[LBUFLEN]; +#endif +int margc; +char *margv[20]; +const char *prompt = "tftp> "; +sigjmp_buf toplevel; +void intr(int); +struct servent *sp; +int portrange = 0; +unsigned int portrange_from = 0; +unsigned int portrange_to = 0; + +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 **); +void setliteral(int, char **); + +static void command(void); + +static void getusage(char *); +static void makeargv(void); +static void putusage(char *); +static void settftpmode(const struct modes *); + +#define HELPINDENT (sizeof("connect")) + +struct cmd { + const char *name; + const char *help; + void (*handler) (int, char **); +}; + +struct cmd cmdtab[] = { + {"connect", + "connect to remote tftp", + setpeer}, + {"mode", + "set file transfer mode", + modecmd}, + {"put", + "send file", + put}, + {"get", + "receive file", + get}, + {"quit", + "exit tftp", + quit}, + {"verbose", + "toggle verbose mode", + setverbose}, + {"trace", + "toggle packet tracing", + settrace}, + {"literal", + "toggle literal mode, ignore ':' in file name", + setliteral}, + {"status", + "show current status", + status}, + {"binary", + "set mode to octet", + setbinary}, + {"ascii", + "set mode to netascii", + setascii}, + {"rexmt", + "set per-packet transmission timeout", + setrexmt}, + {"timeout", + "set total retransmission timeout", + settimeout}, + {"?", + "print help information", + help}, + {"help", + "print help information", + help}, + {0, 0, 0} +}; + +struct cmd *getcmd(char *); +char *tail(char *); + +char *xstrdup(const char *); + +const char *program; + +static inline void usage(int errcode) +{ + fprintf(stderr, +#ifdef HAVE_IPV6 + "Usage: %s [-4][-6][-v][-l][-m mode] [host [port]] [-c command]\n", +#else + "Usage: %s [-v][-l][-m mode] [host [port]] [-c command]\n", +#endif + program); + exit(errcode); +} + +int main(int argc, char *argv[]) +{ + union sock_addr sa; + int arg; + static int pargc, peerargc; + static int iscmd = 0; + char **pargv; + const char *optx; + char *peerargv[3]; + + program = argv[0]; + + mode = MODE_DEFAULT; + + peerargv[0] = argv[0]; + peerargc = 1; + + for (arg = 1; !iscmd && arg < argc; arg++) { + if (argv[arg][0] == '-') { + for (optx = &argv[arg][1]; *optx; optx++) { + switch (*optx) { + case '4': + ai_fam = AF_INET; + break; +#ifdef HAVE_IPV6 + case '6': + ai_fam = AF_INET6; + break; +#endif + case 'v': + verbose = 1; + break; + case 'V': + /* Print version and configuration to stdout and exit */ + printf("%s\n", TFTP_CONFIG_STR); + exit(0); + case 'l': + literal = 1; + break; + case 'm': + if (++arg >= argc) + usage(EX_USAGE); + { + const struct modes *p; + + for (p = modes; p->m_name; p++) { + if (!strcmp(argv[arg], p->m_name)) + break; + } + if (p->m_name) { + settftpmode(p); + } else { + fprintf(stderr, "%s: invalid mode: %s\n", + argv[0], argv[arg]); + exit(EX_USAGE); + } + } + break; + case 'c': + iscmd = 1; + break; + case 'R': + if (++arg >= argc) + usage(EX_USAGE); + if (sscanf + (argv[arg], "%u:%u", &portrange_from, + &portrange_to) != 2 + || portrange_from > portrange_to + || portrange_to > 65535) { + fprintf(stderr, "Bad port range: %s\n", argv[arg]); + exit(EX_USAGE); + } + portrange = 1; + break; + case 'h': + default: + usage(*optx == 'h' ? 0 : EX_USAGE); + } + } + } else { + if (peerargc >= 3) + usage(EX_USAGE); + + peerargv[peerargc++] = argv[arg]; + } + } + + ai_fam_sock = ai_fam; + + pargv = argv + arg; + pargc = argc - arg; + + sp = getservbyname("tftp", "udp"); + if (sp == 0) { + /* Use canned values */ + if (verbose) + fprintf(stderr, + "tftp: tftp/udp: unknown service, faking it...\n"); + sp = xmalloc(sizeof(struct servent)); + sp->s_name = (char *)"tftp"; + sp->s_aliases = NULL; + sp->s_port = htons(IPPORT_TFTP); + sp->s_proto = (char *)"udp"; + } + + bsd_signal(SIGINT, intr); + + if (peerargc) { + /* Set peer */ + if (sigsetjmp(toplevel, 1) != 0) + exit(EX_NOHOST); + setpeer(peerargc, peerargv); + } + + if (ai_fam_sock == AF_UNSPEC) + ai_fam_sock = AF_INET; + + f = socket(ai_fam_sock, SOCK_DGRAM, 0); + if (f < 0) { + perror("tftp: socket"); + exit(EX_OSERR); + } + bzero(&sa, sizeof(sa)); + sa.sa.sa_family = ai_fam_sock; + if (pick_port_bind(f, &sa, portrange_from, portrange_to)) { + perror("tftp: bind"); + exit(EX_OSERR); + } + + if (iscmd && pargc) { + /* -c specified; execute command and exit */ + struct cmd *c; + + if (sigsetjmp(toplevel, 1) != 0) + exit(EX_UNAVAILABLE); + + c = getcmd(pargv[0]); + if (c == (struct cmd *)-1 || c == (struct cmd *)0) { + fprintf(stderr, "%s: invalid command: %s\n", argv[0], + pargv[1]); + exit(EX_USAGE); + } + (*c->handler) (pargc, pargv); + exit(0); + } +#ifdef WITH_READLINE +#ifdef HAVE_READLINE_HISTORY_H + using_history(); +#endif +#endif + + if (sigsetjmp(toplevel, 1) != 0) + (void)putchar('\n'); + command(); + + return 0; /* Never reached */ +} + +char *hostname; + +/* Called when a command is incomplete; modifies + the global variable "line" */ +static void getmoreargs(const char *partial, const char *mprompt) +{ +#ifdef WITH_READLINE + char *eline; + int len, elen; + + len = strlen(partial); + eline = readline(mprompt); + if (!eline) + exit(0); /* EOF */ + + elen = strlen(eline); + + if (line) { + free(line); + line = NULL; + } + line = xmalloc(len + elen + 1); + strcpy(line, partial); + strcpy(line + len, eline); + free(eline); + +#ifdef HAVE_READLINE_HISTORY_H + add_history(line); +#endif +#else + int len = strlen(partial); + + strcpy(line, partial); + fputs(mprompt, stdout); + if (fgets(line + len, LBUFLEN - len, stdin) == 0) + if (feof(stdin)) + exit(0); /* EOF */ +#endif +} + +void setpeer(int argc, char *argv[]) +{ + int err; + + if (argc < 2) { + getmoreargs("connect ", "(to) "); + makeargv(); + argc = margc; + argv = margv; + } + if ((argc < 2) || (argc > 3)) { + printf("usage: %s host-name [port]\n", argv[0]); + return; + } + + peeraddr.sa.sa_family = ai_fam; + err = set_sock_addr(argv[1], &peeraddr, &hostname); + if (err) { + printf("Error: %s\n", gai_strerror(err)); + printf("%s: unknown host\n", argv[1]); + connected = 0; + return; + } + ai_fam = peeraddr.sa.sa_family; + if (f == -1) { /* socket not open */ + ai_fam_sock = ai_fam; + } else { /* socket was already open */ + if (ai_fam_sock != ai_fam) { /* need reopen socken for new family */ + union sock_addr sa; + + close(f); + ai_fam_sock = ai_fam; + f = socket(ai_fam_sock, SOCK_DGRAM, 0); + if (f < 0) { + perror("tftp: socket"); + exit(EX_OSERR); + } + bzero((char *)&sa, sizeof (sa)); + sa.sa.sa_family = ai_fam_sock; + if (pick_port_bind(f, &sa, portrange_from, portrange_to)) { + perror("tftp: bind"); + exit(EX_OSERR); + } + } + } + port = sp->s_port; + if (argc == 3) { + struct servent *usp; + usp = getservbyname(argv[2], "udp"); + if (usp) { + port = usp->s_port; + } else { + unsigned long myport; + char *ep; + myport = strtoul(argv[2], &ep, 10); + if (*ep || myport > 65535UL) { + printf("%s: bad port number\n", argv[2]); + connected = 0; + return; + } + port = htons((u_short) myport); + } + } + + if (verbose) { + char tmp[INET6_ADDRSTRLEN], *tp; + tp = (char *)inet_ntop(peeraddr.sa.sa_family, SOCKADDR_P(&peeraddr), + tmp, INET6_ADDRSTRLEN); + if (!tp) + tp = (char *)"???"; + printf("Connected to %s (%s), port %u\n", + hostname, tp, (unsigned int)ntohs(port)); + } + connected = 1; +} + +void modecmd(int argc, char *argv[]) +{ + const struct modes *p; + const char *sep; + + if (argc < 2) { + printf("Using %s mode to transfer files.\n", mode->m_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); + 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[]) +{ + (void)argc; + (void)argv; /* Quiet unused warning */ + settftpmode(MODE_OCTET); +} + +void setascii(int argc, char *argv[]) +{ + (void)argc; + (void)argv; /* Quiet unused warning */ + settftpmode(MODE_NETASCII); +} + +static void settftpmode(const struct modes *newmode) +{ + mode = newmode; + if (verbose) + printf("mode set to %s\n", mode->m_mode); +} + +/* + * Send file(s). + */ +void put(int argc, char *argv[]) +{ + int fd; + int n, err; + char *cp, *targ; + + if (argc < 2) { + getmoreargs("send ", "(file) "); + makeargv(); + argc = margc; + argv = margv; + } + if (argc < 2) { + putusage(argv[0]); + return; + } + targ = argv[argc - 1]; + if (!literal && strchr(argv[argc - 1], ':')) { + for (n = 1; n < argc - 1; n++) + if (strchr(argv[n], ':')) { + putusage(argv[0]); + return; + } + cp = argv[argc - 1]; + targ = strchr(cp, ':'); + *targ++ = 0; + peeraddr.sa.sa_family = ai_fam; + err = set_sock_addr(cp, &peeraddr,&hostname); + if (err) { + printf("Error: %s\n", gai_strerror(err)); + printf("%s: unknown host\n", argv[1]); + connected = 0; + return; + } + ai_fam = peeraddr.sa.sa_family; + connected = 1; + } + if (!connected) { + printf("No target machine specified.\n"); + return; + } + if (argc < 4) { + cp = argc == 2 ? tail(targ) : argv[1]; + fd = open(cp, O_RDONLY | mode->m_openflags); + if (fd < 0) { + fprintf(stderr, "tftp: "); + perror(cp); + return; + } + if (verbose) + printf("putting %s to %s:%s [%s]\n", + cp, hostname, targ, mode->m_mode); + sa_set_port(&peeraddr, port); + tftp_sendfile(fd, targ, mode->m_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 | mode->m_openflags); + 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->m_mode); + sa_set_port(&peeraddr, port); + tftp_sendfile(fd, targ, mode->m_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) { + getmoreargs("get ", "(files) "); + makeargv(); + argc = margc; + argv = margv; + } + if (argc < 2) { + getusage(argv[0]); + return; + } + if (!connected) { + for (n = 1; n < argc; n++) + if (literal || strchr(argv[n], ':') == 0) { + getusage(argv[0]); + return; + } + } + for (n = 1; n < argc; n++) { + src = strchr(argv[n], ':'); + if (literal || src == NULL) + src = argv[n]; + else { + int err; + + *src++ = 0; + peeraddr.sa.sa_family = ai_fam; + err = set_sock_addr(argv[n], &peeraddr, &hostname); + if (err) { + printf("Warning: %s\n", gai_strerror(err)); + printf("%s: unknown host\n", argv[1]); + continue; + } + ai_fam = peeraddr.sa.sa_family; + connected = 1; + } + if (argc < 4) { + cp = argc == 3 ? argv[2] : tail(src); + fd = open(cp, O_WRONLY | O_CREAT | O_TRUNC | mode->m_openflags, + 0666); + if (fd < 0) { + fprintf(stderr, "tftp: "); + perror(cp); + return; + } + if (verbose) + printf("getting from %s:%s to %s [%s]\n", + hostname, src, cp, mode->m_mode); + sa_set_port(&peeraddr, port); + tftp_recvfile(fd, src, mode->m_mode); + break; + } + cp = tail(src); /* new .. jdg */ + fd = open(cp, O_WRONLY | O_CREAT | O_TRUNC | mode->m_openflags, + 0666); + if (fd < 0) { + fprintf(stderr, "tftp: "); + perror(cp); + continue; + } + if (verbose) + printf("getting from %s:%s to %s [%s]\n", + hostname, src, cp, mode->m_mode); + sa_set_port(&peeraddr, port); + tftp_recvfile(fd, src, mode->m_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) { + getmoreargs("rexmt-timeout ", "(value) "); + 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) { + getmoreargs("maximum-timeout ", "(value) "); + 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 setliteral(int argc, char *argv[]) +{ + (void)argc; + (void)argv; /* Quiet unused warning */ + literal = !literal; + printf("Literal mode %s.\n", literal ? "on" : "off"); +} + +void status(int argc, char *argv[]) +{ + (void)argc; + (void)argv; /* Quiet unused warning */ + if (connected) + printf("Connected to %s.\n", hostname); + else + printf("Not connected.\n"); + printf("Mode: %s Verbose: %s Tracing: %s Literal: %s\n", mode->m_mode, + verbose ? "on" : "off", trace ? "on" : "off", + literal ? "on" : "off"); + printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n", + rexmtval, maxtimeout); +} + +void intr(int sig) +{ + (void)sig; /* Quiet unused warning */ + + bsd_signal(SIGALRM, SIG_IGN); + alarm(0); + siglongjmp(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 (;;) { +#ifdef WITH_READLINE + if (line) { + free(line); + line = NULL; + } + line = readline(prompt); + if (!line) + exit(0); /* EOF */ +#else + fputs(prompt, stdout); + if (fgets(line, LBUFLEN, stdin) == 0) { + if (feof(stdin)) { + exit(0); + } else { + continue; + } + } +#endif + if ((line[0] == 0) || (line[0] == '\n')) + continue; +#ifdef WITH_READLINE +#ifdef HAVE_READLINE_HISTORY_H + add_history(line); +#endif +#endif + 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) +{ + const char *p; + char *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[]) +{ + (void)argc; + (void)argv; /* Quiet unused warning */ + exit(0); +} + +/* + * Help command. + */ +void help(int argc, char *argv[]) +{ + struct cmd *c; + + printf("%s\n", VERSION); + + 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[]) +{ + (void)argc; + (void)argv; /* Quiet unused warning */ + + trace = !trace; + printf("Packet tracing %s.\n", trace ? "on" : "off"); +} + +void setverbose(int argc, char *argv[]) +{ + (void)argc; + (void)argv; /* Quiet unused warning */ + + verbose = !verbose; + printf("Verbose mode %s.\n", verbose ? "on" : "off"); +} diff --git a/tftp/tftp.1.in b/tftp/tftp.1.in new file mode 100644 index 0000000..b41f7b5 --- /dev/null +++ b/tftp/tftp.1.in @@ -0,0 +1,207 @@ +.\" -*- nroff -*- --------------------------------------------------------- * +.\" +.\" Copyright (c) 1990, 1993, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Copyright 2001 H. Peter Anvin - 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. 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. +.\" +.\"----------------------------------------------------------------------- */ +.TH TFTP 1 "23 July 2008" "tftp-hpa @@VERSION@@" "User's Manual" +.SH NAME +.B tftp +\- IPv4 Trivial File Transfer Protocol client +.SH SYNOPSIS +.B tftp +[ \fIoptions...\fP ] +[\fIhost\fP [\fIport\fP]] +[\fB\-c\fP \fIcommand\fP] +.br +.SH DESCRIPTION +.B tftp +is a client for the Trivial file Transfer Protocol, which can be +used to transfer files to and from remote machines, including some +very minimalistic, usually embedded, systems. The remote +.I host +may be specified on the command line, in which case +.B tftp +uses +.I host +as the default host for future transfers (see the +.B connect +command below.) +.SH OPTIONS +.TP +.B \-4 +Connect with IPv4 only, even if IPv6 support was compiled in. +.TP +.B \-6 +Connect with IPv6 only, if compiled in. +.TP +\fB\-c\fP \fIcommand\fP +Execute \fIcommand\fP as if it had been entered on the tftp prompt. +Must be specified last on the command line. +.TP +.B \-l +Default to literal mode. Used to avoid special processing of ':' in a +file name. +.TP +\fB\-m\fP \fImode\fP +Set the default transfer mode to \fImode\fP. This is usually used with \-c. +.TP +\fB\-R\fP \fIport:port\fP +Force the originating port number to be in the specified range of port +numbers. +.TP +.B \-v +Default to verbose mode. +.TP +.B \-V +Print the version number and configuration to standard output, then +exit gracefully. +.SH COMMANDS +Once +.B tftp +is running, it issues the prompt +\f(CWtftp>\fP +and recognizes the following commands: +.TP +\fB?\fP \fIcommand-name...\fP +.TP +\fBhelp\fP \fIcommand-name...\fP +Print help information +.TP +.B ascii +Shorthand for +.BR "mode ascii" . +.TP +.B binary +Shorthand for +.BR "mode binary" . +.TP +\fBconnect\fP \fIhost [port]\fP +Set the +.I host +(and optionally +.IR port ) +for transfers. Note that the TFTP protocol, unlike the FTP protocol, +does not maintain connections between transfers; thus, the +.B 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 +.B connect +command; the remote host can be specified as part of the +.B get +or +.B put +commands. +.TP +\fBget\fP \fIfile\fP +.sp -.6l +.TP +\fBget\fP \fIremotefile localfile\fP +.sp -.6l +.TP +\fBget\fP \fIfile1 file2 file3...\fP +Get a file or set of files from the specified sources. A remote +filename can be in one of two forms: a plain filename on the remote +host, if the host has already been specified, or a string of the form +.I "host: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. Enable +.B literal +mode to prevent special treatment of the ':' character (e.g. C:\\dir\\file). +.TP +.B literal +Toggle literal mode. When set, this mode prevents special treatment of ':' in filenames. +.TP +\fBmode\fP \fItransfer-mode\fP +Specify the mode for transfers; +.I transfer-mode +may be one of +.B ascii +(or +.BR netascii ) +or +.B binary +(or +.BR octet .) +The default is +.BR ascii . +.TP +\fBput\fP \fIfile\fP +.sp -.6l +.TP +\fBput\fP \fIlocalfile remotefile\fP +.sp -.6l +.TP +\fBput\fP \fIfile1 file2 file3... remote-directory\fP +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 +.I "host: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 UNIX system or another system using +.B / +as directory separator. Enable +.B literal +mode to prevent special treatment of the ':' character (e.g. C:\\dir\\file). +.TP +.B quit +Exit +.BR tftp . +End-of-file will also exit. +.TP +\fBrexmt\fP \fIretransmission-timeout\fP +Set the per-packet retransmission timeout, in seconds. +.TP +.B status +Show current status. +.TP +\fBtimeout\fP \fItotal-transmission-timeout\fP +Set the total transmission timeout, in seconds. +.TP +.B trace +Toggle packet tracing (a debugging feature.) +.TP +.B verbose +Toggle verbose mode. +.SH "NOTES" +The TFTP protocol provides no provisions for authentication or +security. Therefore, the remote server will probably implement some +kinds of access restriction or firewalling. These access restrictions +are likely to be site- and server-specific. +.SH "AUTHOR" +This version of +.B tftp +is maintained by H. Peter Anvin . It was derived from, +but has substantially diverged from, an OpenBSD source base, with +added patches by Markus Gutschke and Gero Kulhman. +.SH "SEE ALSO" +.BR tftpd (8). diff --git a/tftp/tftp.c b/tftp/tftp.c new file mode 100644 index 0000000..d15da22 --- /dev/null +++ b/tftp/tftp.c @@ -0,0 +1,425 @@ +/* + * 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. + */ + +#include "common/tftpsubs.h" + +/* + * TFTP User Program -- Protocol Machines + */ +#include "extern.h" + +extern union sock_addr peeraddr; /* filled in by main */ +extern int f; /* the opened socket */ +extern int trace; +extern int verbose; +extern int rexmtval; +extern int maxtimeout; + +#define PKTSIZE SEGSIZE+4 +char ackbuf[PKTSIZE]; +int timeout; +sigjmp_buf toplevel; +sigjmp_buf timeoutbuf; + +static void nak(int, const char *); +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 tftp_sendfile(int fd, const char *name, const char *mode) +{ + struct tftphdr *ap; /* data and ack packets */ + struct tftphdr *dp; + int n; + volatile int is_request; + volatile u_short block; + volatile int size, convert; + volatile off_t amount; + union sock_addr from; + socklen_t fromlen; + FILE *file; + u_short ap_opcode, ap_block; + + startclock(); /* start stat's clock */ + dp = r_init(); /* reset fillbuf/read-ahead code */ + ap = (struct tftphdr *)ackbuf; + convert = !strcmp(mode, "netascii"); + file = fdopen(fd, convert ? "rt" : "rb"); + block = 0; + is_request = 1; /* First packet is the actual WRQ */ + amount = 0; + + bsd_signal(SIGALRM, timer); + do { + if (is_request) { + 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, NULL); + break; + } + dp->th_opcode = htons((u_short) DATA); + dp->th_block = htons((u_short) block); + } + timeout = 0; + (void)sigsetjmp(timeoutbuf, 1); + + if (trace) + tpacket("sent", dp, size + 4); + n = sendto(f, dp, size + 4, 0, + &peeraddr.sa, SOCKLEN(&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, + &from.sa, &fromlen); + } while (n <= 0); + alarm(0); + if (n < 0) { + perror("tftp: recvfrom"); + goto abort; + } + sa_set_port(&peeraddr, SOCKPORT(&from)); /* added */ + if (trace) + tpacket("received", ap, n); + /* should verify packet came from server */ + ap_opcode = ntohs((u_short) ap->th_opcode); + ap_block = ntohs((u_short) ap->th_block); + if (ap_opcode == ERROR) { + printf("Error code %d: %s\n", ap_block, ap->th_msg); + goto abort; + } + if (ap_opcode == ACK) { + int j; + + if (ap_block == block) { + break; + } + /* On an error, try to synchronize + * both sides. + */ + j = synchnet(f); + if (j && trace) { + printf("discarded %d packets\n", j); + } + /* + * RFC1129/RFC1350: We MUST NOT re-send the DATA + * packet in response to an invalid ACK. Doing so + * would cause the Sorcerer's Apprentice bug. + */ + } + } + if (!is_request) + amount += size; + is_request = 0; + block++; + } while (size == SEGSIZE || block == 1); + abort: + fclose(file); + stopclock(); + if (amount > 0) + printstats("Sent", amount); +} + +/* + * Receive a file. + */ +void tftp_recvfile(int fd, const char *name, const char *mode) +{ + struct tftphdr *ap; + struct tftphdr *dp; + int n; + volatile u_short block; + volatile int size, firsttrip; + volatile unsigned long amount; + union sock_addr from; + socklen_t fromlen; + FILE *file; + volatile int convert; /* true if converting crlf -> lf */ + u_short dp_opcode, dp_block; + + startclock(); + dp = w_init(); + ap = (struct tftphdr *)ackbuf; + convert = !strcmp(mode, "netascii"); + file = fdopen(fd, convert ? "wt" : "wb"); + block = 1; + firsttrip = 1; + amount = 0; + + bsd_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)sigsetjmp(timeoutbuf, 1); + send_ack: + if (trace) + tpacket("sent", ap, size); + if (sendto(f, ackbuf, size, 0, &peeraddr.sa, + SOCKLEN(&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, + &from.sa, &fromlen); + } while (n <= 0); + alarm(0); + if (n < 0) { + perror("tftp: recvfrom"); + goto abort; + } + sa_set_port(&peeraddr, SOCKPORT(&from)); /* added */ + if (trace) + tpacket("received", dp, n); + /* should verify client address */ + dp_opcode = ntohs((u_short) dp->th_opcode); + dp_block = ntohs((u_short) dp->th_block); + if (dp_opcode == ERROR) { + printf("Error code %d: %s\n", dp_block, dp->th_msg); + goto abort; + } + if (dp_opcode == DATA) { + int j; + + if (dp_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_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, NULL); + 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, + SOCKLEN(&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 = (char *)&(tp->th_stuff); + strcpy(cp, name); + cp += strlen(name); + *cp++ = '\0'; + strcpy(cp, mode); + cp += strlen(mode); + *cp++ = '\0'; + return (cp - (char *)tp); +} + +static const char *const errmsgs[] = { + "Undefined error code", /* 0 - EUNDEF */ + "File not found", /* 1 - ENOTFOUND */ + "Access denied", /* 2 - EACCESS */ + "Disk full or allocation exceeded", /* 3 - ENOSPACE */ + "Illegal TFTP operation", /* 4 - EBADOP */ + "Unknown transfer ID", /* 5 - EBADID */ + "File already exists", /* 6 - EEXISTS */ + "No such user", /* 7 - ENOUSER */ + "Failure to negotiate RFC2347 options" /* 8 - EOPTNEG */ +}; + +#define ERR_CNT (sizeof(errmsgs)/sizeof(const char *)) + +/* + * 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, const char *msg) +{ + struct tftphdr *tp; + int length; + + tp = (struct tftphdr *)ackbuf; + tp->th_opcode = htons((u_short) ERROR); + tp->th_code = htons((u_short) error); + + if (error >= 100) { + /* This is a Unix errno+100 */ + if (!msg) + msg = strerror(error - 100); + error = EUNDEF; + } else { + if ((unsigned)error >= ERR_CNT) + error = EUNDEF; + + if (!msg) + msg = errmsgs[error]; + } + + tp->th_code = htons((u_short) error); + + length = strlen(msg) + 1; + memcpy(tp->th_msg, msg, length); + length += 4; /* Add space for header */ + + if (trace) + tpacket("sent", tp, length); + if (sendto(f, ackbuf, length, 0, &peeraddr.sa, + SOCKLEN(&peeraddr)) != length) + perror("nak"); +} + +static void tpacket(const char *s, struct tftphdr *tp, int n) +{ + static const char *opcodes[] = + { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" }; + char *cp, *file; + u_short op = ntohs((u_short) 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 = (char *)&(tp->th_stuff); + cp = strchr(cp, '\0'); + printf("\n", file, cp + 1); + break; + + case DATA: + printf("\n", ntohs(tp->th_block), n - 4); + break; + + case ACK: + printf("\n", ntohs(tp->th_block)); + break; + + case ERROR: + printf("\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; + + delta = (tstop.tv_sec + (tstop.tv_usec / 100000.0)) - + (tstart.tv_sec + (tstart.tv_usec / 100000.0)); + if (verbose) { + printf("%s %lu bytes in %.1f seconds", direction, amount, delta); + printf(" [%.0f bit/s]", (amount * 8.) / delta); + putchar('\n'); + } +} + +static void timer(int sig) +{ + int save_errno = errno; + + (void)sig; /* Shut up unused warning */ + + timeout += rexmtval; + if (timeout >= maxtimeout) { + printf("Transfer timed out.\n"); + errno = save_errno; + siglongjmp(toplevel, -1); + } + errno = save_errno; + siglongjmp(timeoutbuf, 1); +} diff --git a/tftpd/Makefile b/tftpd/Makefile new file mode 100644 index 0000000..a05335d --- /dev/null +++ b/tftpd/Makefile @@ -0,0 +1,29 @@ +SRCROOT = .. +VERSION = $(shell cat ../version) + +-include ../MCONFIG +include ../MRULES + +OBJS = tftpd.$(O) recvfrom.$(O) misc.$(O) $(TFTPDOBJS) + +all: tftpd$(X) tftpd.8 + +tftpd$(X): $(OBJS) + $(CC) $(LDFLAGS) $^ $(TFTPD_LIBS) -o $@ + +$(OBJS): ../common/tftpsubs.h + +tftpd.8: tftpd.8.in ../version + sed -e 's/@@VERSION@@/$(VERSION)/g' < $< > $@ + +install: all + mkdir -p $(INSTALLROOT)$(SBINDIR) $(INSTALLROOT)$(MANDIR)/man8 + $(INSTALL_PROGRAM) tftpd$(X) $(INSTALLROOT)$(SBINDIR)/in.tftpd + $(INSTALL_DATA) tftpd.8 $(INSTALLROOT)$(MANDIR)/man8/in.tftpd.8 + cd $(INSTALLROOT)$(MANDIR)/man8 && $(LN_S) -f in.tftpd.8 tftpd.8 + +clean: + rm -f *.o *.obj *.exe tftpd tftpsubs.c tftpsubs.h tftpd.8 + +distclean: clean + rm -f *~ diff --git a/tftpd/misc.c b/tftpd/misc.c new file mode 100644 index 0000000..07684dd --- /dev/null +++ b/tftpd/misc.c @@ -0,0 +1,68 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2001-2007 H. Peter Anvin - All Rights Reserved + * + * This program is free software available under the same license + * as the "OpenBSD" operating system, distributed at + * http://www.openbsd.org/. + * + * ----------------------------------------------------------------------- */ + +/* + * misc.c + * + * Minor help routines. + */ + +#include "config.h" /* Must be included first! */ +#include +#include "tftpd.h" + +/* + * Set the signal handler and flags. Basically a user-friendly + * wrapper around sigaction(). + */ +void set_signal(int signum, void (*handler) (int), int flags) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = flags; + + if (sigaction(signum, &sa, NULL)) { + syslog(LOG_ERR, "sigaction: %m"); + exit(EX_OSERR); + } +} + +/* + * malloc() that syslogs an error message and bails if it fails. + */ +void *tfmalloc(size_t size) +{ + void *p = malloc(size); + + if (!p) { + syslog(LOG_ERR, "malloc: %m"); + exit(EX_OSERR); + } + + return p; +} + +/* + * strdup() that does the equivalent + */ +char *tfstrdup(const char *str) +{ + char *p = strdup(str); + + if (!p) { + syslog(LOG_ERR, "strdup: %m"); + exit(EX_OSERR); + } + + return p; +} diff --git a/tftpd/recvfrom.c b/tftpd/recvfrom.c new file mode 100644 index 0000000..24abce3 --- /dev/null +++ b/tftpd/recvfrom.c @@ -0,0 +1,267 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2001-2006 H. Peter Anvin - All Rights Reserved + * + * This program is free software available under the same license + * as the "OpenBSD" operating system, distributed at + * http://www.openbsd.org/. + * + * ----------------------------------------------------------------------- */ + +/* + * recvfrom.c + * + * Emulate recvfrom() using recvmsg(), but try to capture the local address + * since some TFTP clients consider it an error to get the reply from another + * IP address than the request was sent to. + * + */ + +#include "config.h" /* Must be included first! */ +#include "common/tftpsubs.h" +#include "recvfrom.h" +#ifdef HAVE_MACHINE_PARAM_H +#include /* Needed on some versions of FreeBSD */ +#endif + +#if defined(HAVE_RECVMSG) && defined(HAVE_MSGHDR_MSG_CONTROL) + +#include + +#ifdef IP_PKTINFO +# ifndef HAVE_STRUCT_IN_PKTINFO +# ifdef __linux__ +/* Assume this version of glibc simply lacks the definition */ +struct in_pktinfo { + int ipi_ifindex; + struct in_addr ipi_spec_dst; + struct in_addr ipi_addr; +}; +# else +# undef IP_PKTINFO /* No definition, no way to get it */ +# endif +# endif +#endif + +#ifndef CMSG_LEN +# define CMSG_LEN(size) (sizeof(struct cmsghdr) + (size)) +#endif +#ifndef CMSG_SPACE +# define CMSG_SPACE(size) (sizeof(struct cmsghdr) + (size)) +#endif + +/* + * Check to see if this is a valid local address, meaning that we can + * legally bind to it. + */ +static int address_is_local(const union sock_addr *addr) +{ + union sock_addr sa1, sa2; + int sockfd = -1; + int e; + int rv = 0; + socklen_t addrlen; + + memcpy(&sa1, addr, sizeof sa1); + + /* Multicast or universal broadcast address? */ + if (sa1.sa.sa_family == AF_INET) { + if (ntohl(sa1.si.sin_addr.s_addr) >= (224UL << 24)) + return 0; + sa1.si.sin_port = 0; /* Any port */ + } +#ifdef HAVE_IPV6 + else if (sa1.sa.sa_family == AF_INET6) { + if (IN6_IS_ADDR_MULTICAST(&sa1.s6.sin6_addr)) + return 0; + sa1.s6.sin6_port = 0; /* Any port */ + } +#endif + else + return 0; + + sockfd = socket(sa1.sa.sa_family, SOCK_DGRAM, 0); + if (sockfd < 0) + goto err; + + if (bind(sockfd, &sa1.sa, SOCKLEN(&sa1))) + goto err; + + addrlen = SOCKLEN(addr); + if (getsockname(sockfd, (struct sockaddr *)&sa2, &addrlen)) + goto err; + + if (sa1.sa.sa_family != sa2.sa.sa_family) + goto err; + + if (sa2.sa.sa_family == AF_INET) + rv = sa1.si.sin_addr.s_addr == sa2.si.sin_addr.s_addr; +#ifdef HAVE_IPV6 + else if (sa2.sa.sa_family == AF_INET6) + rv = IN6_ARE_ADDR_EQUAL(&sa1.s6.sin6_addr, &sa2.s6.sin6_addr); +#endif + else + rv = 0; + +err: + e = errno; + + if (sockfd >= 0) + close(sockfd); + + errno = e; + return rv; +} + +int +myrecvfrom(int s, void *buf, int len, unsigned int flags, + struct sockaddr *from, socklen_t * fromlen, + union sock_addr *myaddr) +{ + struct msghdr msg; + struct iovec iov; + int n; + struct cmsghdr *cmptr; + union { + struct cmsghdr cm; +#ifdef IP_PKTINFO + char control[CMSG_SPACE(sizeof(struct in_addr)) + + CMSG_SPACE(sizeof(struct in_pktinfo))]; +#else + char control[CMSG_SPACE(sizeof(struct in_addr))]; +#endif +#ifdef HAVE_IPV6 +#ifdef HAVE_STRUCT_IN6_PKTINFO + char control6[CMSG_SPACE(sizeof(struct in6_addr)) + + CMSG_SPACE(sizeof(struct in6_pktinfo))]; +#else + char control6[CMSG_SPACE(sizeof(struct in6_addr))]; +#endif +#endif + } control_un; + int on = 1; +#ifdef IP_PKTINFO + struct in_pktinfo pktinfo; +#endif +#ifdef HAVE_STRUCT_IN6_PKTINFO + struct in6_pktinfo pktinfo6; +#endif + + /* Try to enable getting the return address */ +#ifdef IP_RECVDSTADDR + if (from->sa_family == AF_INET) + setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)); +#endif +#ifdef IP_PKTINFO + if (from->sa_family == AF_INET) + setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); +#endif +#ifdef HAVE_IPV6 +#ifdef IPV6_RECVPKTINFO + if (from->sa_family == AF_INET6) + setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); +#endif +#endif + bzero(&msg, sizeof msg); /* Clear possible system-dependent fields */ + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un); + msg.msg_flags = 0; + + msg.msg_name = from; + msg.msg_namelen = *fromlen; + iov.iov_base = buf; + iov.iov_len = len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + if ((n = recvmsg(s, &msg, flags)) < 0) + return n; /* Error */ + + *fromlen = msg.msg_namelen; + + if (myaddr) { + bzero(myaddr, sizeof(*myaddr)); + myaddr->sa.sa_family = from->sa_family; + + if (msg.msg_controllen < sizeof(struct cmsghdr) || + (msg.msg_flags & MSG_CTRUNC)) + return n; /* No information available */ + + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; + cmptr = CMSG_NXTHDR(&msg, cmptr)) { + + if (from->sa_family == AF_INET) { + myaddr->sa.sa_family = AF_INET; +#ifdef IP_RECVDSTADDR + if (cmptr->cmsg_level == IPPROTO_IP && + cmptr->cmsg_type == IP_RECVDSTADDR) { + memcpy(&myaddr->si.sin_addr, CMSG_DATA(cmptr), + sizeof(struct in_addr)); + } +#endif + +#ifdef IP_PKTINFO + if (cmptr->cmsg_level == IPPROTO_IP && + cmptr->cmsg_type == IP_PKTINFO) { + memcpy(&pktinfo, CMSG_DATA(cmptr), + sizeof(struct in_pktinfo)); + memcpy(&myaddr->si.sin_addr, &pktinfo.ipi_addr, + sizeof(struct in_addr)); + } +#endif + } +#ifdef HAVE_IPV6 + else if (from->sa_family == AF_INET6) { + myaddr->sa.sa_family = AF_INET6; +#ifdef IP6_RECVDSTADDR + if (cmptr->cmsg_level == IPPROTO_IPV6 && + cmptr->cmsg_type == IPV6_RECVDSTADDR ) + memcpy(&myaddr->s6.sin6_addr, CMSG_DATA(cmptr), + sizeof(struct in6_addr)); +#endif + +#ifdef HAVE_STRUCT_IN6_PKTINFO + if (cmptr->cmsg_level == IPPROTO_IPV6 && + (cmptr->cmsg_type == IPV6_RECVPKTINFO || + cmptr->cmsg_type == IPV6_PKTINFO)) { + memcpy(&pktinfo6, CMSG_DATA(cmptr), + sizeof(struct in6_pktinfo)); + memcpy(&myaddr->s6.sin6_addr, &pktinfo6.ipi6_addr, + sizeof(struct in6_addr)); + } +#endif + } +#endif + } + /* If the address is not a valid local address, + * then bind to any address... + */ + if (address_is_local(myaddr) != 1) { + if (myaddr->sa.sa_family == AF_INET) + ((struct sockaddr_in *)myaddr)->sin_addr.s_addr = INADDR_ANY; +#ifdef HAVE_IPV6 + else if (myaddr->sa.sa_family == AF_INET6) + memset(&myaddr->s6.sin6_addr, 0, sizeof(struct in6_addr)); +#endif + } + } + return n; +} + +#else /* pointless... */ + +int +myrecvfrom(int s, void *buf, int len, unsigned int flags, + struct sockaddr *from, socklen_t * fromlen, + union sock_addr *myaddr) +{ + /* There is no way we can get the local address, fudge it */ + + bzero(myaddr, sizeof(*myaddr)); + myaddr->sa.sa_family = from->sa_family; + sa_set_port(myaddr, htons(IPPORT_TFTP)); + + return recvfrom(s, buf, len, flags, from, fromlen); +} + +#endif diff --git a/tftpd/recvfrom.h b/tftpd/recvfrom.h new file mode 100644 index 0000000..e3c4055 --- /dev/null +++ b/tftpd/recvfrom.h @@ -0,0 +1,23 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2001-2006 H. Peter Anvin - All Rights Reserved + * + * This program is free software available under the same license + * as the "OpenBSD" operating system, distributed at + * http://www.openbsd.org/. + * + * ----------------------------------------------------------------------- */ + +/* + * recvfrom.h + * + * Header for recvfrom substitute + * + */ + +#include "config.h" + +int +myrecvfrom(int s, void *buf, int len, unsigned int flags, + struct sockaddr *from, socklen_t *fromlen, + union sock_addr *myaddr); diff --git a/tftpd/remap.c b/tftpd/remap.c new file mode 100644 index 0000000..1e7abe7 --- /dev/null +++ b/tftpd/remap.c @@ -0,0 +1,435 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2001-2007 H. Peter Anvin - All Rights Reserved + * + * This program is free software available under the same license + * as the "OpenBSD" operating system, distributed at + * http://www.openbsd.org/. + * + * ----------------------------------------------------------------------- */ + +/* + * remap.c + * + * Perform regular-expression based filename remapping. + */ + +#include "config.h" /* Must be included first! */ +#include +#include +#include + +#include "tftpd.h" +#include "remap.h" + +#define DEADMAN_MAX_STEPS 1024 /* Timeout after this many steps */ +#define MAXLINE 16384 /* Truncate a line at this many bytes */ + +#define RULE_REWRITE 0x01 /* This is a rewrite rule */ +#define RULE_GLOBAL 0x02 /* Global rule (repeat until no match) */ +#define RULE_EXIT 0x04 /* Exit after matching this rule */ +#define RULE_RESTART 0x08 /* Restart at the top after matching this rule */ +#define RULE_ABORT 0x10 /* Terminate processing with an error */ +#define RULE_INVERSE 0x20 /* Execute if regex *doesn't* match */ + +struct rule { + struct rule *next; + int nrule; + int rule_flags; + char rule_mode; + regex_t rx; + const char *pattern; +}; + +static int xform_null(int c) +{ + return c; +} + +static int xform_toupper(int c) +{ + return toupper(c); +} + +static int xform_tolower(int c) +{ + return tolower(c); +} + +/* Do \-substitution. Call with string == NULL to get length only. */ +static int genmatchstring(char *string, const char *pattern, + const char *input, const regmatch_t * pmatch, + match_pattern_callback macrosub) +{ + int (*xform) (int) = xform_null; + int len = 0; + int n, mlen, sublen; + int endbytes; + + /* Get section before match; note pmatch[0] is the whole match */ + endbytes = strlen(input) - pmatch[0].rm_eo; + len = pmatch[0].rm_so + endbytes; + if (string) { + memcpy(string, input, pmatch[0].rm_so); + string += pmatch[0].rm_so; + } + + /* Transform matched section */ + while (*pattern) { + mlen = 0; + + if (*pattern == '\\' && pattern[1] != '\0') { + char macro = pattern[1]; + switch (macro) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = pattern[1] - '0'; + + if (pmatch[n].rm_so != -1) { + mlen = pmatch[n].rm_eo - pmatch[n].rm_so; + len += mlen; + if (string) { + const char *p = input + pmatch[n].rm_so; + while (mlen--) + *string++ = xform(*p++); + } + } + break; + + case 'L': + xform = xform_tolower; + break; + + case 'U': + xform = xform_toupper; + break; + + case 'E': + xform = xform_null; + break; + + default: + if (macrosub && (sublen = macrosub(macro, string)) >= 0) { + while (sublen--) { + len++; + if (string) { + *string = xform(*string); + string++; + } + } + } else { + len++; + if (string) + *string++ = xform(pattern[1]); + } + } + pattern += 2; + } else { + len++; + if (string) + *string++ = xform(*pattern); + pattern++; + } + } + + /* Copy section after match */ + if (string) { + memcpy(string, input + pmatch[0].rm_eo, endbytes); + string[endbytes] = '\0'; + } + + return len; +} + +/* + * Extract a string terminated by non-escaped whitespace; ignoring + * leading whitespace. Consider an unescaped # to be a comment marker, + * functionally \n. + */ +static int readescstring(char *buf, char **str) +{ + char *p = *str; + int wasbs = 0, len = 0; + + while (*p && isspace(*p)) + p++; + + if (!*p) { + *buf = '\0'; + *str = p; + return 0; + } + + while (*p) { + if (!wasbs && (isspace(*p) || *p == '#')) { + *buf = '\0'; + *str = p; + return len; + } + /* Important: two backslashes leave us in the !wasbs state! */ + wasbs = !wasbs && (*p == '\\'); + *buf++ = *p++; + len++; + } + + *buf = '\0'; + *str = p; + return len; +} + +/* Parse a line into a set of instructions */ +static int parseline(char *line, struct rule *r, int lineno) +{ + char buffer[MAXLINE]; + char *p; + int rv; + int rxflags = REG_EXTENDED; + static int nrule; + + memset(r, 0, sizeof *r); + r->nrule = nrule; + + if (!readescstring(buffer, &line)) + return 0; /* No rule found */ + + for (p = buffer; *p; p++) { + switch (*p) { + case 'r': + r->rule_flags |= RULE_REWRITE; + break; + case 'g': + r->rule_flags |= RULE_GLOBAL; + break; + case 'e': + r->rule_flags |= RULE_EXIT; + break; + case 's': + r->rule_flags |= RULE_RESTART; + break; + case 'a': + r->rule_flags |= RULE_ABORT; + break; + case 'i': + rxflags |= REG_ICASE; + break; + case '~': + r->rule_flags |= RULE_INVERSE; + break; + case 'G': + case 'P': + r->rule_mode = *p; + break; + default: + syslog(LOG_ERR, + "Remap command \"%s\" on line %d contains invalid char \"%c\"", + buffer, lineno, *p); + return -1; /* Error */ + break; + } + } + + /* RULE_GLOBAL only applies when RULE_REWRITE specified */ + if (!(r->rule_flags & RULE_REWRITE)) + r->rule_flags &= ~RULE_GLOBAL; + + if ((r->rule_flags & (RULE_INVERSE | RULE_REWRITE)) == + (RULE_INVERSE | RULE_REWRITE)) { + syslog(LOG_ERR, "r rules cannot be inverted, line %d: %s\n", + lineno, line); + return -1; /* Error */ + } + + /* Read and compile the regex */ + if (!readescstring(buffer, &line)) { + syslog(LOG_ERR, "No regex on remap line %d: %s\n", lineno, line); + return -1; /* Error */ + } + + if ((rv = regcomp(&r->rx, buffer, rxflags)) != 0) { + char errbuf[BUFSIZ]; + regerror(rv, &r->rx, errbuf, BUFSIZ); + syslog(LOG_ERR, "Bad regex in remap line %d: %s\n", lineno, + errbuf); + return -1; /* Error */ + } + + /* Read the rewrite pattern, if any */ + if (readescstring(buffer, &line)) { + r->pattern = tfstrdup(buffer); + } else { + r->pattern = ""; + } + + nrule++; + return 1; /* Rule found */ +} + +/* Read a rule file */ +struct rule *parserulefile(FILE * f) +{ + char line[MAXLINE]; + struct rule *first_rule = NULL; + struct rule **last_rule = &first_rule; + struct rule *this_rule = tfmalloc(sizeof(struct rule)); + int rv; + int lineno = 0; + int err = 0; + + while (lineno++, fgets(line, MAXLINE, f)) { + rv = parseline(line, this_rule, lineno); + if (rv < 0) + err = 1; + if (rv > 0) { + *last_rule = this_rule; + last_rule = &this_rule->next; + this_rule = tfmalloc(sizeof(struct rule)); + } + } + + free(this_rule); /* Last one is always unused */ + + if (err) { + /* Bail on error, we have already logged an error message */ + exit(EX_CONFIG); + } + + return first_rule; +} + +/* Destroy a rule file data structure */ +void freerules(struct rule *r) +{ + struct rule *next; + + while (r) { + next = r->next; + + regfree(&r->rx); + + /* "" patterns aren't allocated by malloc() */ + if (r->pattern && *r->pattern) + free((void *)r->pattern); + + free(r); + + r = next; + } +} + +/* Execute a rule set on a string; returns a malloc'd new string. */ +char *rewrite_string(const char *input, const struct rule *rules, + char mode, match_pattern_callback macrosub, + const char **errmsg) +{ + char *current = tfstrdup(input); + char *newstr; + const struct rule *ruleptr = rules; + regmatch_t pmatch[10]; + int len; + int was_match = 0; + int deadman = DEADMAN_MAX_STEPS; + + /* Default error */ + *errmsg = "Remap table failure"; + + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: input: %s", current); + } + + for (ruleptr = rules; ruleptr; ruleptr = ruleptr->next) { + if (ruleptr->rule_mode && ruleptr->rule_mode != mode) + continue; /* Rule not applicable, try next */ + + if (!deadman--) { + syslog(LOG_WARNING, + "remap: Breaking loop, input = %s, last = %s", input, + current); + free(current); + return NULL; /* Did not terminate! */ + } + + do { + if (regexec(&ruleptr->rx, current, 10, pmatch, 0) == + (ruleptr->rule_flags & RULE_INVERSE ? REG_NOMATCH : 0)) { + /* Match on this rule */ + was_match = 1; + + if (ruleptr->rule_flags & RULE_INVERSE) { + /* No actual match, so clear out the pmatch array */ + int i; + for (i = 0; i < 10; i++) + pmatch[i].rm_so = pmatch[i].rm_eo = -1; + } + + if (ruleptr->rule_flags & RULE_ABORT) { + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: abort: %s", + ruleptr->nrule, current); + } + if (ruleptr->pattern[0]) { + /* Custom error message */ + len = + genmatchstring(NULL, ruleptr->pattern, current, + pmatch, macrosub); + newstr = tfmalloc(len + 1); + genmatchstring(newstr, ruleptr->pattern, current, + pmatch, macrosub); + *errmsg = newstr; + } else { + *errmsg = NULL; + } + free(current); + return (NULL); + } + + if (ruleptr->rule_flags & RULE_REWRITE) { + len = genmatchstring(NULL, ruleptr->pattern, current, + pmatch, macrosub); + newstr = tfmalloc(len + 1); + genmatchstring(newstr, ruleptr->pattern, current, + pmatch, macrosub); + free(current); + current = newstr; + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: rewrite: %s", + ruleptr->nrule, current); + } + } + } else { + break; /* No match, terminate unconditionally */ + } + /* If the rule is global, keep going until no match */ + } while (ruleptr->rule_flags & RULE_GLOBAL); + + if (was_match) { + was_match = 0; + + if (ruleptr->rule_flags & RULE_EXIT) { + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: exit", + ruleptr->nrule); + } + return current; /* Exit here, we're done */ + } else if (ruleptr->rule_flags & RULE_RESTART) { + ruleptr = rules; /* Start from the top */ + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: restart", + ruleptr->nrule); + } + } + } + } + + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: done"); + } + return current; +} diff --git a/tftpd/remap.h b/tftpd/remap.h new file mode 100644 index 0000000..69ca08d --- /dev/null +++ b/tftpd/remap.h @@ -0,0 +1,42 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2001-2007 H. Peter Anvin - All Rights Reserved + * + * This program is free software available under the same license + * as the "OpenBSD" operating system, distributed at + * http://www.openbsd.org/. + * + * ----------------------------------------------------------------------- */ + +/* + * remap.h + * + * Prototypes for regular-expression based filename remapping. + */ + +#ifndef TFTPD_REMAP_H +#define TFTPD_REMAP_H + +/* Opaque type */ +struct rule; + +#ifdef WITH_REGEX + +/* This is called when we encounter a substitution like \i. The + macro character is passed as the first argument; the output buffer, + if any, is passed as the second argument. The function should return + the number of characters output, or -1 on failure. */ +typedef int (*match_pattern_callback) (char, char *); + +/* Read a rule file */ +struct rule *parserulefile(FILE *); + +/* Destroy a rule file data structure */ +void freerules(struct rule *); + +/* Execute a rule set on a string; returns a malloc'd new string. */ +char *rewrite_string(const char *, const struct rule *, char, + match_pattern_callback, const char **); + +#endif /* WITH_REGEX */ +#endif /* TFTPD_REMAP_H */ diff --git a/tftpd/sample.rules b/tftpd/sample.rules new file mode 100644 index 0000000..55b56be --- /dev/null +++ b/tftpd/sample.rules @@ -0,0 +1,33 @@ +# +# Sample rule file for the -m (remapping option) +# +# This file has three fields: operation, regex, remapping +# +# The operation is a combination of the following letters: +# +# r - rewrite the matched string with the remapping pattern +# i - case-insensitive matching +# g - repeat until no match (used with "r") +# e - exit (with success) if we match this pattern, do not process +# subsequent rules +# s - start over from the first rule if we match this pattern +# a - abort (refuse the request) if we match this rule +# G - this rule applies to TFTP GET requests only +# P - this rule applies to TFTP PUT requests only +# +# The regex is a regular expression in the style of egrep(1). +# +# The remapping is a pattern, all characters are verbatim except \ +# \0 copies the full string that matched the regex +# \1..\9 copies the 9 first (..) expressions in the regex +# \\ is an escaped \ +# +# "#" begins a comment, unless \-escaped +# +ri ^[a-z]: # Remove "drive letters" +rg \\ / # Convert backslashes to slashes +rg \# @ # Convert hash marks to @ signs +rg /../ /..no../ # Convert /../ to /..no../ +e ^ok/ # These are always ok +r ^[^/] /tftpboot/\0 # Convert non-absolute files +a \.pvt$ # Reject requests for private files diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in new file mode 100644 index 0000000..78b4cfb --- /dev/null +++ b/tftpd/tftpd.8.in @@ -0,0 +1,423 @@ +.\" -*- nroff -*- --------------------------------------------------------- * +.\" +.\" Copyright (c) 1990, 1993, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Copyright 2001-2009 H. Peter Anvin - 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. 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. +.\" +.\"----------------------------------------------------------------------- */ +.TH TFTPD 8 "14 September 2009" "tftp-hpa @@VERSION@@" "System Manager's Manual" +.SH NAME +.B tftpd +\- Trivial File Transfer Protocol server +.SH SYNOPSIS +.B in.tftpd +.RI [ options... ] +.I directory... +.SH DESCRIPTION +.B tftpd +is a server for the Trivial File Transfer Protocol. The TFTP +protocol is extensively used to support remote booting of diskless +devices. The server is normally started by +.BR inetd , +but can also run standalone. +.PP +.SH OPTIONS +.TP +\fB\-\-ipv4\fP, \fB\-4\fP +Connect with IPv4 only, even if IPv6 support was compiled in. +.TP +\fB\-\-ipv6\fP, \fB\-6\fP +Connect with IPv6 only, if compiled in. +.TP +\fB\-l\fP, \fB\-\-listen\fP +Run the server in standalone (listen) mode, rather than run from +.BR inetd . +In listen mode, the +.B \-\-timeout +option is ignored, and the +.B \-\-address +option can be used to specify a specific local address or port to +listen to. +.TP +\fB\-\-foreground\fP, \fB\-L\fP +Similar to +.B \-\-listen +but do not detach from the foreground process. Implies +.BR \-\-listen . +.TP +\fB\-\-address\fP \fI[address][:port]\fP, \fB\-a\fP \fI[address][:port]\fP +Specify a specific +.I address +and +.I port +to listen to when called with the +.B \-\-listen +or +.B \-\-foreground +option. The default is to listen to the +.I tftp +port specified in +.I /etc/services +on all local addresses. + +.B Please note: +Numeric IPv6 adresses must be enclosed in square brackets +to avoid ambiguity with the optional port information. +.TP +\fB\-\-create\fP, \fB\-c\fP +Allow new files to be created. By default, +.B tftpd +will only allow upload of files that already exist. Files are created +with default permissions allowing anyone to read or write them, unless +the +.B \-\-permissive +or +.B \-\-umask +options are specified. +.TP +\fB\-\-secure\fP, \fB\-s\fP +Change root directory on startup. This means the remote host does not +need to pass along the directory as part of the transfer, and may add +security. When +.B \-\-secure +is specified, exactly one +.I directory +should be specified on the command line. The use of this option is +recommended for security as well as compatibility with some boot ROMs +which cannot be easily made to include a directory name in its request. +.TP +\fB\-\-user\fP \fIusername\fP, \fB\-u\fP \fIusername\fP +Specify the username which +.B tftpd +will run as; the default is "nobody". The user ID, group ID, and (if +possible on the platform) the supplementary group IDs will be set to +the ones specified in the system permission database for this +username. +.TP +\fB\-\-umask\fP \fIumask\fP, \fB\-U\fP \fIumask\fP +Sets the \fIumask\fP for newly created files to the specified value. +The default is zero (anyone can read or write) if the +.B \-\-permissive +option is not specified, or inherited from the invoking process if +.B \-\-permissive +is specified. +.TP +\fB\-\-permissive\fP, \fB\-p\fP +Perform no additional permissions checks above the normal +system-provided access controls for the user specified via the +.B \-\-user +option. +.TP +\fB\-\-pidfile\fP \fIpidfile\fP, \fB\-P\fP \fIpidfile\fP +When run in standalone mode, write the process ID of the listening +server into \fIpidfile\fP. On normal termination (SIGTERM or SIGINT) +the pid file is automatically removed. +.TP +\fB\-\-timeout\fP \fItimeout\fP, \fB\-t\fP \fItimeout\fP +When run from +.B inetd +this specifies how long, in seconds, to wait for a second connection +before terminating the server. +.B inetd +will then respawn the server when another request comes in. The +default is 900 (15 minutes.) +.TP +\fB\-\-retransmit\fP \fItimeout, \fP\fB\-T\fP \fItimeout\fP +Determine the default timeout, in microseconds, before the first +packet is retransmitted. This can be modified by the client if the +.B timeout +or +.B utimeout +option is negotiated. The default is 1000000 (1 second.) +.TP +\fB\-\-mapfile\fP \fIremap-file\fP, \fB\-m\fP \fIremap-file\fP +Specify the use of filename remapping. The +.I remap-file +is a file containing the remapping rules. See the section on filename +remapping below. This option may not be compiled in, see the output of +.B "in.tftpd \-V" +to verify whether or not it is available. +.TP +\fB\-\-verbose\fP, \fB\-v\fP +Increase the logging verbosity of +.BR tftpd . +This flag can be specified multiple times for even higher verbosity. +.TP +\fB\-\-verbosity\fP \fIvalue\fP +Set the verbosity value to \fIvalue\fP. +.TP +\fB\-\-refuse\fP \fItftp-option\fP, \fB\-r\fP \fItftp-option\fP +Indicate that a specific RFC 2347 TFTP option should never be +accepted. +.TP +\fB\-\-blocksize\fP \fImax-block-size\fP, \fB\-B\fP \fImax-block-size\fP +Specifies the maximum permitted block size. The permitted range for +this parameter is from 512 to 65464. Some embedded clients request +large block sizes and yet do not handle fragmented packets correctly; +for these clients, it is recommended to set this value to the smallest +MTU on your network minus 32 bytes (20 bytes for IP, 8 for UDP, and 4 +for TFTP; less if you use IP options on your network.) For example, +on a standard Ethernet (MTU 1500) a value of 1468 is reasonable. +.TP +\fB\-\-port-range\fP \fIport:port\fP, \fB\-R\fP \fIport:port\fP +Force the server port number (the Transaction ID) to be in the +specified range of port numbers. +.TP +\fB\-\-version\fP, \fB\-V\fP +Print the version number and configuration to standard output, then +exit gracefully. +.SH "RFC 2347 OPTION NEGOTIATION" +This version of +.B tftpd +supports RFC 2347 option negotation. Currently implemented options +are: +.TP +\fBblksize\fP (RFC 2348) +Set the transfer block size to anything less than or equal to the +specified option. This version of +.B tftpd +can support any block size up to the theoretical maximum of 65464 +bytes. +.TP +\fBblksize2\fP (nonstandard) +Set the transfer block size to anything less than or equal to the +specified option, but restrict the possible responses to powers of 2. +The maximum is 32768 bytes (the largest power of 2 less than or equal +to 65464.) +.TP +\fBtsize\fP (RFC 2349) +Report the size of the file that is about to be transferred. This +version of +.B tftpd +only supports the +.B tsize +option for binary (octet) mode transfers. +.TP +\fBtimeout\fP (RFC 2349) +Set the time before the server retransmits a packet, in seconds. +.TP +\fButimeout\fP (nonstandard) +Set the time before the server retransmits a packet, in microseconds. +.TP +\fBrollover\fP (nonstandard) +Set the block number to resume at after a block number rollover. The +default and recommended value is zero. +.PP +The +.B \-\-refuse +option can be used to disable specific options; this may be necessary +to work around bugs in specific TFTP client implementations. For +example, some TFTP clients have been found to request the +.B blksize +option, but crash with an error if they actually get the option +accepted by the server. +.SH "FILENAME REMAPPING" +The +.B \-\-mapfile +option specifies a file which contains filename remapping rules. Each +non-comment line (comments begin with hash marks, +.BR # ) +contains an +.IR operation , +specified below; a +.IR regex , +a regular expression in the style of +.BR egrep ; +and optionally a +.IR "replacement pattern" . +The operation indicated by +.I operation +is performed if the +.I regex +matches all or part of the filename. Rules are processed from the top +down, and by default, all rules are processed even if there is a +match. +.PP +The +.I operation +can be any combination of the following letters: +.TP +.B r +Replace the substring matched by +.I regex +by the +.IR "replacement pattern" . +The replacement pattern may contain escape sequences; see below. +.TP +.B g +Repeat this rule until it no longer matches. This is always used with +.BR r . +.TP +.B i +Match the +.I regex +case-insensitively. By default it is case sensitive. +.TP +.B e +If this rule matches, end rule processing after executing the rule. +.TP +.B s +If this rule matches, start rule processing over from the very first +rule after executing this rule. +.TP +.B a +If this rule matches, refuse the request and send an access denied +error to the client. +.TP +.B G +This rule applies to GET (RRQ) requests only. +.TP +.B P +This rule applies to PUT (WRQ) requests only. +.TP +.B ~ +Inverse the sense of this rule, i.e. execute the +.I operation +only if the +.I regex +.I doesn't +match. Cannot used together with +.BR r . +.PP +The following escape sequences are recognized as part of the +.IR "replacement pattern" : +.TP +\fB\\0\fP +The entire string matched by the +.IR regex . +.TP +\fB\\1\fP to \fB\\9\fP +The strings matched by each of the first nine parenthesized +subexpressions, \\( ... \\), of the +.I regex +pattern. +.TP +\fB\\i\fP +The IP address of the requesting host, in dotted-quad notation +(e.g. 192.0.2.169). +.TP +\fB\\x\fP +The IP address of the requesting host, in hexadecimal notation +(e.g. C00002A9). +.TP +\fB\\\\\fP +Literal backslash. +.TP +\fB\\\fP\fIwhitespace\fP +Literal whitespace. +.TP +\fB\\#\fP +Literal hash mark. +.TP +\fB\\U\fP +Turns all subsequent letters to upper case. +.TP +\fB\\L\fP +Turns all subsequent letters to lower case. +.TP +\fB\\E\fP +Cancels the effect of \fB\\U\fP or \fB\\L\fP. +.PP +If the mapping file is changed, you need to send +.B SIGHUP +to any outstanding +.B tftpd +process. +.SH "SECURITY" +The use of TFTP services does not require an account or password on +the server system. Due to the lack of authentication information, +.B tftpd +will allow only publicly readable files (o+r) to be accessed, unless the +.B \-\-permissive +option is specified. Files may be written only if they already exist +and are publicly writable, unless the +.B \-\-create +option is specified. Note that this extends the concept of ``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. +Typically, some kind of firewall or packet-filter solution should be +employed. If appropriately compiled (see the output of +.BR "in.tftpd \-\-version" ) +.B tftpd +will query the +.BR hosts_access (5) +database for access control information. This may be slow; sites +requiring maximum performance may want to compile without this option +and rely on firewalling or kernel-based packet filters instead. +.PP +The server should be set to run as the user with the lowest possible +privilege; please see the +.B \-\-user +flag. It is probably a good idea to set up a specific user account for +.BR tftpd , +rather than letting it run as "nobody", to guard against privilege +leaks between applications. +.PP +Access to files can, and should, be restricted by invoking +.B tftpd +with a list of directories by including pathnames as server program +arguments on the command line. In this case access is restricted to +files whole names are prefixed by one of the given directories. If +possible, it is recommended that the +.B \-\-secure +flag is used to set up a chroot() environment for the server to run in +once a connection has been set up. +.PP +Finally, the filename remapping +.RB ( \-\-mapfile +flag) support can be used to provide a limited amount of additional +access control. +.SH "CONFORMING TO" +RFC 1123, +.IR "Requirements for Internet Hosts \- Application and Support" . +.br +RFC 1350, +.IR "The TFTP Protocol (revision 2)" . +.br +RFC 2347, +.IR "TFTP Option Extension" . +.br +RFC 2348, +.IR "TFTP Blocksize Option" . +.br +RFC 2349, +.IR "TFTP Timeout Interval and Transfer Size Options" . +.SH "AUTHOR" +This version of +.B tftpd +is maintained by H. Peter Anvin . It was derived from, +but has substantially diverged from, an OpenBSD source base, with +added patches by Markus Gutschke and Gero Kulhman. +.SH "SEE ALSO" +.BR tftp (1), +.BR egrep (1), +.BR umask (2), +.BR hosts_access (5), +.BR regex (7), +.BR inetd (8). diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c new file mode 100644 index 0000000..1873e70 --- /dev/null +++ b/tftpd/tftpd.c @@ -0,0 +1,1795 @@ +/* + * Copyright (c) 1983 Regents of the University of California. + * Copyright (c) 1999-2009 H. Peter Anvin + * Copyright (c) 2011 Intel Corporation; author: H. Peter Anvin + * 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. + */ + +#include "config.h" /* Must be included first */ +#include "tftpd.h" + +/* + * Trivial file transfer protocol server. + * + * This version includes many modifications by Jim Guyton + */ + +#include +#include +#include +#include +#include +#include + +#include "common/tftpsubs.h" +#include "recvfrom.h" +#include "remap.h" + +#ifdef HAVE_SYS_FILIO_H +#include /* Necessary for FIONBIO on Solaris */ +#endif + +#ifdef HAVE_TCPWRAPPERS +#include + +int deny_severity = LOG_WARNING; +int allow_severity = -1; /* Don't log at all */ + +static struct request_info wrap_request; +#endif + +#ifdef HAVE_IPV6 +static int ai_fam = AF_UNSPEC; +#else +static int ai_fam = AF_INET; +#endif + +#define TIMEOUT 1000000 /* Default timeout (us) */ +#define TRIES 6 /* Number of attempts to send each packet */ +#define TIMEOUT_LIMIT ((1 << TRIES)-1) + +const char *__progname; +static int peer; +static unsigned long timeout = TIMEOUT; /* Current timeout value */ +static unsigned long rexmtval = TIMEOUT; /* Basic timeout value */ +static unsigned long maxtimeout = TIMEOUT_LIMIT * TIMEOUT; +static int timeout_quit = 0; +static sigjmp_buf timeoutbuf; +static uint16_t rollover_val = 0; + +#define PKTSIZE MAX_SEGSIZE+4 +static char buf[PKTSIZE]; +static char ackbuf[PKTSIZE]; +static unsigned int max_blksize = MAX_SEGSIZE; + +static char tmpbuf[INET6_ADDRSTRLEN], *tmp_p; + +static union sock_addr from; +static socklen_t fromlen; +static off_t tsize; +static int tsize_ok; + +static int ndirs; +static const char **dirs; + +static int secure = 0; +int cancreate = 0; +int unixperms = 0; +int portrange = 0; +unsigned int portrange_from, portrange_to; +int verbosity = 0; + +struct formats; +#ifdef WITH_REGEX +static struct rule *rewrite_rules = NULL; +#endif + +int tftp(struct tftphdr *, int); +static void nak(int, const char *); +static void timer(int); +static void do_opt(const char *, const char *, char **); + +static int set_blksize(uintmax_t *); +static int set_blksize2(uintmax_t *); +static int set_tsize(uintmax_t *); +static int set_timeout(uintmax_t *); +static int set_utimeout(uintmax_t *); +static int set_rollover(uintmax_t *); + +struct options { + const char *o_opt; + int (*o_fnc)(uintmax_t *); +} options[] = { + {"blksize", set_blksize}, + {"blksize2", set_blksize2}, + {"tsize", set_tsize}, + {"timeout", set_timeout}, + {"utimeout", set_utimeout}, + {"rollover", set_rollover}, + {NULL, NULL} +}; + +/* Simple handler for SIGHUP */ +static volatile sig_atomic_t caught_sighup = 0; +static void handle_sighup(int sig) +{ + (void)sig; /* Suppress unused warning */ + caught_sighup = 1; +} + +/* Handle exit requests by SIGTERM and SIGINT */ +static volatile sig_atomic_t exit_signal = 0; +static void handle_exit(int sig) +{ + exit_signal = sig; +} + +/* Handle timeout signal or timeout event */ +void timer(int sig) +{ + (void)sig; /* Suppress unused warning */ + timeout <<= 1; + if (timeout >= maxtimeout || timeout_quit) + exit(0); + siglongjmp(timeoutbuf, 1); +} + +#ifdef WITH_REGEX +static struct rule *read_remap_rules(const char *file) +{ + FILE *f; + struct rule *rulep; + + f = fopen(file, "rt"); + if (!f) { + syslog(LOG_ERR, "Cannot open map file: %s: %m", file); + exit(EX_NOINPUT); + } + rulep = parserulefile(f); + fclose(f); + + return rulep; +} +#endif + +/* + * Rules for locking files; return 0 on success, -1 on failure + */ +static int lock_file(int fd, int lock_write) +{ +#if defined(HAVE_FCNTL) && defined(HAVE_F_SETLK_DEFINITION) + struct flock fl; + + fl.l_type = lock_write ? F_WRLCK : F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; /* Whole file */ + return fcntl(fd, F_SETLK, &fl); +#elif defined(HAVE_LOCK_SH_DEFINITION) + return flock(fd, lock_write ? LOCK_EX|LOCK_NB : LOCK_SH|LOCK_NB); +#else + return 0; /* Hope & pray... */ +#endif +} + +static void set_socket_nonblock(int fd, int flag) +{ + int err; + int flags; +#if defined(HAVE_FCNTL) && defined(HAVE_O_NONBLOCK_DEFINITION) + /* Posixly correct */ + err = ((flags = fcntl(fd, F_GETFL, 0)) < 0) || + (fcntl + (fd, F_SETFL, + flag ? flags | O_NONBLOCK : flags & ~O_NONBLOCK) < 0); +#else + flags = flag ? 1 : 0; + err = (ioctl(fd, FIONBIO, &flags) < 0); +#endif + if (err) { + syslog(LOG_ERR, "Cannot set nonblock flag on socket: %m"); + exit(EX_OSERR); + } +} + +static void pmtu_discovery_off(int fd) +{ +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + int pmtu = IP_PMTUDISC_DONT; + + setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); +#endif +} + +/* + * Receive packet with synchronous timeout; timeout is adjusted + * to account for time spent waiting. + */ +static int recv_time(int s, void *rbuf, int len, unsigned int flags, + unsigned long *timeout_us_p) +{ + fd_set fdset; + struct timeval tmv, t0, t1; + int rv, err; + unsigned long timeout_us = *timeout_us_p; + unsigned long timeout_left, dt; + + gettimeofday(&t0, NULL); + timeout_left = timeout_us; + + for (;;) { + FD_ZERO(&fdset); + FD_SET(s, &fdset); + + do { + tmv.tv_sec = timeout_left / 1000000; + tmv.tv_usec = timeout_left % 1000000; + + rv = select(s + 1, &fdset, NULL, NULL, &tmv); + err = errno; + + gettimeofday(&t1, NULL); + + dt = (t1.tv_sec - t0.tv_sec) * 1000000 + + (t1.tv_usec - t0.tv_usec); + *timeout_us_p = timeout_left = + (dt >= timeout_us) ? 1 : (timeout_us - dt); + } while (rv == -1 && err == EINTR); + + if (rv == 0) { + timer(0); /* Should not return */ + return -1; + } + + set_socket_nonblock(s, 1); + rv = recv(s, rbuf, len, flags); + err = errno; + set_socket_nonblock(s, 0); + + if (rv < 0) { + if (E_WOULD_BLOCK(err) || err == EINTR) { + continue; /* Once again, with feeling... */ + } else { + errno = err; + return rv; + } + } else { + return rv; + } + } +} + +static int split_port(char **ap, char **pp) +{ + char *a, *p; + int ret = AF_UNSPEC; + + a = *ap; +#ifdef HAVE_IPV6 + if (is_numeric_ipv6(a)) { + if (*a++ != '[') + return -1; + *ap = a; + p = strrchr(a, ']'); + if (!p) + return -1; + *p++ = 0; + a = p; + ret = AF_INET6; + p = strrchr(a, ':'); + if (p) + *p++ = 0; + } else +#endif + { + struct in_addr in; + + p = strrchr(a, ':'); + if (p) + *p++ = 0; + if (inet_aton(a, &in)) + ret = AF_INET; + } + *pp = p; + return ret; +} + +enum long_only_options { + OPT_VERBOSITY = 256, +}; + +static struct option long_options[] = { + { "ipv4", 0, NULL, '4' }, + { "ipv6", 0, NULL, '6' }, + { "create", 0, NULL, 'c' }, + { "secure", 0, NULL, 's' }, + { "permissive", 0, NULL, 'p' }, + { "verbose", 0, NULL, 'v' }, + { "verbosity", 1, NULL, OPT_VERBOSITY }, + { "version", 0, NULL, 'V' }, + { "listen", 0, NULL, 'l' }, + { "foreground", 0, NULL, 'L' }, + { "address", 1, NULL, 'a' }, + { "blocksize", 1, NULL, 'B' }, + { "user", 1, NULL, 'u' }, + { "umask", 1, NULL, 'U' }, + { "refuse", 1, NULL, 'r' }, + { "timeout", 1, NULL, 't' }, + { "retransmit", 1, NULL, 'T' }, + { "port-range", 1, NULL, 'R' }, + { "map-file", 1, NULL, 'm' }, + { "pidfile", 1, NULL, 'P' }, + { NULL, 0, NULL, 0 } +}; +static const char short_options[] = "46cspvVlLa:B:u:U:r:t:T:R:m:P:"; + +int main(int argc, char **argv) +{ + struct tftphdr *tp; + struct passwd *pw; + struct options *opt; + union sock_addr myaddr; + struct sockaddr_in bindaddr4; +#ifdef HAVE_IPV6 + struct sockaddr_in6 bindaddr6; + int force_ipv6 = 0; +#endif + int n; + int fd = -1; + int fd4 = -1; + int fd6 = -1; + int fdmax = 0; + int standalone = 0; /* Standalone (listen) mode */ + int nodaemon = 0; /* Do not detach process */ + char *address = NULL; /* Address to listen to */ + pid_t pid; + mode_t my_umask = 0; + int spec_umask = 0; + int c; + int setrv; + int waittime = 900; /* Default time to wait for a connect */ + const char *user = "nobody"; /* Default user */ + char *p, *ep; +#ifdef WITH_REGEX + char *rewrite_file = NULL; +#endif + const char *pidfile = NULL; + u_short tp_opcode; + + /* basename() is way too much of a pain from a portability standpoint */ + + p = strrchr(argv[0], '/'); + __progname = (p && p[1]) ? p + 1 : argv[0]; + + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + srand(time(NULL) ^ getpid()); + + while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) + != -1) + switch (c) { + case '4': + ai_fam = AF_INET; + break; +#ifdef HAVE_IPV6 + case '6': + ai_fam = AF_INET6; + force_ipv6 = 1; + break; +#endif + case 'c': + cancreate = 1; + break; + case 's': + secure = 1; + break; + case 'p': + unixperms = 1; + break; + case 'l': + standalone = 1; + break; + case 'L': + standalone = 1; + nodaemon = 1; + break; + case 'a': + address = optarg; + break; + case 't': + waittime = atoi(optarg); + break; + case 'B': + { + char *vp; + max_blksize = (unsigned int)strtoul(optarg, &vp, 10); + if (max_blksize < 512 || max_blksize > MAX_SEGSIZE || *vp) { + syslog(LOG_ERR, + "Bad maximum blocksize value (range 512-%d): %s", + MAX_SEGSIZE, optarg); + exit(EX_USAGE); + } + } + break; + case 'T': + { + char *vp; + unsigned long tov = strtoul(optarg, &vp, 10); + if (tov < 10000UL || tov > 255000000UL || *vp) { + syslog(LOG_ERR, "Bad timeout value: %s", optarg); + exit(EX_USAGE); + } + rexmtval = timeout = tov; + maxtimeout = rexmtval * TIMEOUT_LIMIT; + } + break; + case 'R': + { + if (sscanf(optarg, "%u:%u", &portrange_from, &portrange_to) + != 2 || portrange_from > portrange_to + || portrange_to >= 65535) { + syslog(LOG_ERR, "Bad port range: %s", optarg); + exit(EX_USAGE); + } + portrange = 1; + } + break; + case 'u': + user = optarg; + break; + case 'U': + my_umask = strtoul(optarg, &ep, 8); + if (*ep) { + syslog(LOG_ERR, "Invalid umask: %s", optarg); + exit(EX_USAGE); + } + spec_umask = 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(EX_USAGE); + } + break; +#ifdef WITH_REGEX + case 'm': + if (rewrite_file) { + syslog(LOG_ERR, "Multiple -m options"); + exit(EX_USAGE); + } + rewrite_file = optarg; + break; +#endif + case 'v': + verbosity++; + break; + case OPT_VERBOSITY: + verbosity = atoi(optarg); + break; + case 'V': + /* Print configuration to stdout and exit */ + printf("%s\n", TFTPD_CONFIG_STR); + exit(0); + break; + case 'P': + pidfile = optarg; + break; + default: + syslog(LOG_ERR, "Unknown option: '%c'", optopt); + break; + } + + dirs = xmalloc((argc - optind + 1) * sizeof(char *)); + for (ndirs = 0; optind != argc; optind++) + dirs[ndirs++] = argv[optind]; + + dirs[ndirs] = NULL; + + if (secure) { + if (ndirs == 0) { + syslog(LOG_ERR, "no -s directory"); + exit(EX_USAGE); + } + if (ndirs > 1) { + syslog(LOG_ERR, "too many -s directories"); + exit(EX_USAGE); + } + if (chdir(dirs[0])) { + syslog(LOG_ERR, "%s: %m", dirs[0]); + exit(EX_NOINPUT); + } + } + + pw = getpwnam(user); + if (!pw) { + syslog(LOG_ERR, "no user %s: %m", user); + exit(EX_NOUSER); + } + +#ifdef WITH_REGEX + if (rewrite_file) + rewrite_rules = read_remap_rules(rewrite_file); +#endif + + if (pidfile && !standalone) { + syslog(LOG_WARNING, "not in standalone mode, ignoring pid file"); + pidfile = NULL; + } + + /* If we're running standalone, set up the input port */ + if (standalone) { + FILE *pf; +#ifdef HAVE_IPV6 + if (ai_fam != AF_INET6) { +#endif + fd4 = socket(AF_INET, SOCK_DGRAM, 0); + if (fd4 < 0) { + syslog(LOG_ERR, "cannot open IPv4 socket: %m"); + exit(EX_OSERR); + } +#ifndef __CYGWIN__ + set_socket_nonblock(fd4, 1); +#endif + memset(&bindaddr4, 0, sizeof bindaddr4); + bindaddr4.sin_family = AF_INET; + bindaddr4.sin_addr.s_addr = INADDR_ANY; + bindaddr4.sin_port = htons(IPPORT_TFTP); +#ifdef HAVE_IPV6 + } + if (ai_fam != AF_INET) { + fd6 = socket(AF_INET6, SOCK_DGRAM, 0); + if (fd6 < 0) { + if (fd4 < 0) { + syslog(LOG_ERR, "cannot open IPv6 socket: %m"); + exit(EX_OSERR); + } else { + syslog(LOG_ERR, + "cannot open IPv6 socket, disable IPv6: %m"); + } + } +#ifndef __CYGWIN__ + set_socket_nonblock(fd6, 1); +#endif + memset(&bindaddr6, 0, sizeof bindaddr6); + bindaddr6.sin6_family = AF_INET6; + bindaddr6.sin6_port = htons(IPPORT_TFTP); + } +#endif + if (address) { + char *portptr = NULL, *eportptr; + int err; + struct servent *servent; + unsigned long port; + + address = tfstrdup(address); + err = split_port(&address, &portptr); + switch (err) { + case AF_INET: +#ifdef HAVE_IPV6 + if (fd6 >= 0) { + close(fd6); + fd6 = -1; + if (ai_fam == AF_INET6) { + syslog(LOG_ERR, + "Address %s is not in address family AF_INET6", + address); + exit(EX_USAGE); + } + ai_fam = AF_INET; + } + break; + case AF_INET6: + if (fd4 >= 0) { + close(fd4); + fd4 = -1; + if (ai_fam == AF_INET) { + syslog(LOG_ERR, + "Address %s is not in address family AF_INET", + address); + exit(EX_USAGE); + } + ai_fam = AF_INET6; + } + break; +#endif + case AF_UNSPEC: + break; + default: + syslog(LOG_ERR, + "Numeric IPv6 addresses need to be enclosed in []"); + exit(EX_USAGE); + } + if (!portptr) + portptr = (char *)"tftp"; + if (*address) { + if (fd4 >= 0) { + bindaddr4.sin_family = AF_INET; + err = set_sock_addr(address, + (union sock_addr *)&bindaddr4, NULL); + if (err) { + syslog(LOG_ERR, + "cannot resolve local IPv4 bind address: %s, %s", + address, gai_strerror(err)); + exit(EX_NOINPUT); + } + } +#ifdef HAVE_IPV6 + if (fd6 >= 0) { + bindaddr6.sin6_family = AF_INET6; + err = set_sock_addr(address, + (union sock_addr *)&bindaddr6, NULL); + if (err) { + if (fd4 >= 0) { + syslog(LOG_ERR, + "cannot resolve local IPv6 bind address: %s" + "(%s); using IPv4 only", + address, gai_strerror(err)); + close(fd6); + fd6 = -1; + } else { + syslog(LOG_ERR, + "cannot resolve local IPv6 bind address: %s" + "(%s)", address, gai_strerror(err)); + exit(EX_NOINPUT); + } + } + } +#endif + } else { + /* Default to using INADDR_ANY */ + } + + if (portptr && *portptr) { + servent = getservbyname(portptr, "udp"); + if (servent) { + if (fd4 >= 0) + bindaddr4.sin_port = servent->s_port; +#ifdef HAVE_IPV6 + if (fd6 >= 0) + bindaddr6.sin6_port = servent->s_port; +#endif + } else if ((port = strtoul(portptr, &eportptr, 0)) + && !*eportptr) { + if (fd4 >= 0) + bindaddr4.sin_port = htons(port); +#ifdef HAVE_IPV6 + if (fd6 >= 0) + bindaddr6.sin6_port = htons(port); +#endif + } else if (!strcmp(portptr, "tftp")) { + /* It's TFTP, we're OK */ + } else { + syslog(LOG_ERR, "cannot resolve local bind port: %s", + portptr); + exit(EX_NOINPUT); + } + } + } + + if (fd4 >= 0) { + if (bind(fd4, (struct sockaddr *)&bindaddr4, + sizeof(bindaddr4)) < 0) { + syslog(LOG_ERR, "cannot bind to local IPv4 socket: %m"); + exit(EX_OSERR); + } + } +#ifdef HAVE_IPV6 + if (fd6 >= 0) { +#if defined(IPV6_V6ONLY) + int on = 1; + if (fd4 >= 0 || force_ipv6) + if (setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&on, + sizeof(on))) + syslog(LOG_ERR, "cannot setsockopt IPV6_V6ONLY %m"); +#endif + if (bind(fd6, (struct sockaddr *)&bindaddr6, + sizeof(bindaddr6)) < 0) { + if (fd4 >= 0) { + syslog(LOG_ERR, + "cannot bind to local IPv6 socket," + "IPv6 disabled: %m"); + close(fd6); + fd6 = -1; + } else { + syslog(LOG_ERR, "cannot bind to local IPv6 socket: %m"); + exit(EX_OSERR); + } + } + } +#endif + /* Daemonize this process */ + /* Note: when running in secure mode (-s), we must not chdir, since + we are already in the proper directory. */ + if (!nodaemon && daemon(secure, 0) < 0) { + syslog(LOG_ERR, "cannot daemonize: %m"); + exit(EX_OSERR); + } + set_signal(SIGTERM, handle_exit, 0); + set_signal(SIGINT, handle_exit, 0); + if (pidfile) { + pf = fopen (pidfile, "w"); + if (!pf) { + syslog(LOG_ERR, "cannot open pid file '%s' for writing: %m", pidfile); + pidfile = NULL; + } else { + if (fprintf(pf, "%d\n", getpid()) < 0) + syslog(LOG_ERR, "error writing pid file '%s': %m", pidfile); + if (fclose(pf)) + syslog(LOG_ERR, "error closing pid file '%s': %m", pidfile); + } + } + if (fd6 > fd4) + fdmax = fd6; + else + fdmax = fd4; + } else { + /* 0 is our socket descriptor */ + close(1); + close(2); + fd = 0; + fdmax = 0; + /* Note: on Cygwin, select() on a nonblocking socket becomes + a nonblocking select. */ +#ifndef __CYGWIN__ + set_socket_nonblock(fd, 1); +#endif + } + + /* Disable path MTU discovery */ + pmtu_discovery_off(fd); + + /* This means we don't want to wait() for children */ +#ifdef SA_NOCLDWAIT + set_signal(SIGCHLD, SIG_IGN, SA_NOCLDSTOP | SA_NOCLDWAIT); +#else + set_signal(SIGCHLD, SIG_IGN, SA_NOCLDSTOP); +#endif + + /* Take SIGHUP and use it to set a variable. This + is polled synchronously to make sure we don't + lose packets as a result. */ + set_signal(SIGHUP, handle_sighup, 0); + + if (spec_umask || !unixperms) + umask(my_umask); + + while (1) { + fd_set readset; + struct timeval tv_waittime; + int rv; + + if (exit_signal) { /* happens in standalone mode only */ + if (pidfile && unlink(pidfile)) { + syslog(LOG_WARNING, "error removing pid file '%s': %m", pidfile); + exit(EX_OSERR); + } else { + exit(0); + } + } + + if (caught_sighup) { + caught_sighup = 0; + if (standalone) { +#ifdef WITH_REGEX + if (rewrite_file) { + freerules(rewrite_rules); + rewrite_rules = read_remap_rules(rewrite_file); + } +#endif + } else { + /* Return to inetd for respawn */ + exit(0); + } + } + + FD_ZERO(&readset); + if (standalone) { + if (fd4 >= 0) { + FD_SET(fd4, &readset); +#ifdef __CYGWIN__ + /* On Cygwin, select() on a nonblocking socket returns + immediately, with a rv of 0! */ + set_socket_nonblock(fd4, 0); +#endif + } + if (fd6 >= 0) { + FD_SET(fd6, &readset); +#ifdef __CYGWIN__ + /* On Cygwin, select() on a nonblocking socket returns + immediately, with a rv of 0! */ + set_socket_nonblock(fd6, 0); +#endif + } + } else { /* fd always 0 */ + fd = 0; +#ifdef __CYGWIN__ + /* On Cygwin, select() on a nonblocking socket returns + immediately, with a rv of 0! */ + set_socket_nonblock(fd, 0); +#endif + FD_SET(fd, &readset); + } + tv_waittime.tv_sec = waittime; + tv_waittime.tv_usec = 0; + + + /* Never time out if we're in standalone mode */ + rv = select(fdmax + 1, &readset, NULL, NULL, + standalone ? NULL : &tv_waittime); + if (rv == -1 && errno == EINTR) + continue; /* Signal caught, reloop */ + + if (rv == -1) { + syslog(LOG_ERR, "select loop: %m"); + exit(EX_IOERR); + } else if (rv == 0) { + exit(0); /* Timeout, return to inetd */ + } + + if (standalone) { + if ((fd4 >= 0) && FD_ISSET(fd4, &readset)) + fd = fd4; + else if ((fd6 >= 0) && FD_ISSET(fd6, &readset)) + fd = fd6; + else /* not in set ??? */ + continue; + } +#ifdef __CYGWIN__ + /* On Cygwin, select() on a nonblocking socket returns + immediately, with a rv of 0! */ + set_socket_nonblock(fd, 0); +#endif + + fromlen = sizeof(from); + n = myrecvfrom(fd, buf, sizeof(buf), 0, + (struct sockaddr *)&from, &fromlen, &myaddr); + + if (n < 0) { + if (E_WOULD_BLOCK(errno) || errno == EINTR) { + continue; /* Again, from the top */ + } else { + syslog(LOG_ERR, "recvfrom: %m"); + exit(EX_IOERR); + } + } +#ifdef HAVE_IPV6 + if ((from.sa.sa_family != AF_INET) && (from.sa.sa_family != AF_INET6)) { + syslog(LOG_ERR, "received address was not AF_INET/AF_INET6," + " please check your inetd config"); +#else + if (from.sa.sa_family != AF_INET) { + syslog(LOG_ERR, "received address was not AF_INET," + " please check your inetd config"); +#endif + exit(EX_PROTOCOL); + } + + if (standalone) { + if ((from.sa.sa_family == AF_INET) && + (myaddr.si.sin_addr.s_addr == INADDR_ANY)) { + /* myrecvfrom() didn't capture the source address; but we might + have bound to a specific address, if so we should use it */ + memcpy(SOCKADDR_P(&myaddr), &bindaddr4.sin_addr, + sizeof(bindaddr4.sin_addr)); +#ifdef HAVE_IPV6 + } else if ((from.sa.sa_family == AF_INET6) && + IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *) + SOCKADDR_P(&myaddr))) { + memcpy(SOCKADDR_P(&myaddr), &bindaddr6.sin6_addr, + sizeof(bindaddr6.sin6_addr)); +#endif + } + } + + /* + * Now that we have read the request packet from the UDP + * socket, we fork and go back to listening to the socket. + */ + pid = fork(); + if (pid < 0) { + syslog(LOG_ERR, "fork: %m"); + exit(EX_OSERR); /* Return to inetd, just in case */ + } else if (pid == 0) + break; /* Child exit, parent loop */ + } + + /* Child process: handle the actual request here */ + + /* Ignore SIGHUP */ + set_signal(SIGHUP, SIG_IGN, 0); + + /* Make sure the log socket is still connected. This has to be + done before the chroot, while /dev/log is still accessible. + When not running standalone, there is little chance that the + syslog daemon gets restarted by the time we get here. */ + if (secure && standalone) { + closelog(); + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + } + +#ifdef HAVE_TCPWRAPPERS + /* Verify if this was a legal request for us. This has to be + done before the chroot, while /etc is still accessible. */ + request_init(&wrap_request, + RQ_DAEMON, __progname, + RQ_FILE, fd, + RQ_CLIENT_SIN, &from, RQ_SERVER_SIN, &myaddr, 0); + sock_methods(&wrap_request); + + tmp_p = (char *)inet_ntop(myaddr.sa.sa_family, SOCKADDR_P(&myaddr), + tmpbuf, INET6_ADDRSTRLEN); + if (!tmp_p) { + tmp_p = tmpbuf; + strcpy(tmpbuf, "???"); + } + if (hosts_access(&wrap_request) == 0) { + if (deny_severity != -1) + syslog(deny_severity, "connection refused from %s", tmp_p); + exit(EX_NOPERM); /* Access denied */ + } else if (allow_severity != -1) { + syslog(allow_severity, "connect from %s", tmp_p); + } +#endif + + /* Close file descriptors we don't need */ + close(fd); + + /* Get a socket. This has to be done before the chroot(), since + some systems require access to /dev to create a socket. */ + + peer = socket(myaddr.sa.sa_family, SOCK_DGRAM, 0); + if (peer < 0) { + syslog(LOG_ERR, "socket: %m"); + exit(EX_IOERR); + } + + /* Set up the supplementary group access list if possible */ + /* /etc/group still need to be accessible at this point */ +#ifdef HAVE_INITGROUPS + setrv = initgroups(user, pw->pw_gid); + if (setrv) { + syslog(LOG_ERR, "cannot set groups for user %s", user); + exit(EX_OSERR); + } +#else +#ifdef HAVE_SETGROUPS + if (setgroups(0, NULL)) { + syslog(LOG_ERR, "cannot clear group list"); + } +#endif +#endif + + /* Chroot and drop privileges */ + if (secure) { + if (chroot(".")) { + syslog(LOG_ERR, "chroot: %m"); + exit(EX_OSERR); + } +#ifdef __CYGWIN__ + chdir("/"); /* Cygwin chroot() bug workaround */ +#endif + } +#ifdef HAVE_SETREGID + setrv = setregid(pw->pw_gid, pw->pw_gid); +#else + setrv = setegid(pw->pw_gid) || setgid(pw->pw_gid); +#endif + +#ifdef HAVE_SETREUID + setrv = setrv || setreuid(pw->pw_uid, pw->pw_uid); +#else + /* Important: setuid() must come first */ + setrv = setrv || setuid(pw->pw_uid) || + (geteuid() != pw->pw_uid && seteuid(pw->pw_uid)); +#endif + + if (setrv) { + syslog(LOG_ERR, "cannot drop privileges: %m"); + exit(EX_OSERR); + } + + /* Process the request... */ + if (pick_port_bind(peer, &myaddr, portrange_from, portrange_to) < 0) { + syslog(LOG_ERR, "bind: %m"); + exit(EX_IOERR); + } + + if (connect(peer, &from.sa, SOCKLEN(&from)) < 0) { + syslog(LOG_ERR, "connect: %m"); + exit(EX_IOERR); + } + + /* Disable path MTU discovery */ + pmtu_discovery_off(peer); + + tp = (struct tftphdr *)buf; + tp_opcode = ntohs(tp->th_opcode); + if (tp_opcode == RRQ || tp_opcode == WRQ) + tftp(tp, n); + exit(0); +} + +static char *rewrite_access(char *, int, const char **); +static int validate_access(char *, int, const struct formats *, const char **); +static void tftp_sendfile(const struct formats *, struct tftphdr *, int); +static void tftp_recvfile(const struct formats *, struct tftphdr *, int); + +struct formats { + const char *f_mode; + char *(*f_rewrite) (char *, int, const char **); + int (*f_validate) (char *, int, const struct formats *, const char **); + void (*f_send) (const struct formats *, struct tftphdr *, int); + void (*f_recv) (const struct formats *, struct tftphdr *, int); + int f_convert; +}; +static const struct formats formats[] = { + { + "netascii", rewrite_access, validate_access, tftp_sendfile, + tftp_recvfile, 1}, { + "octet", rewrite_access, validate_access, tftp_sendfile, + tftp_recvfile, 0}, { + NULL, NULL, NULL, NULL, NULL, 0} +}; + +/* + * Handle initial connection protocol. + */ +int tftp(struct tftphdr *tp, int size) +{ + char *cp, *end; + int argn, ecode; + const struct formats *pf = NULL; + char *origfilename; + char *filename, *mode = NULL; + const char *errmsgptr; + u_short tp_opcode = ntohs(tp->th_opcode); + + char *val = NULL, *opt = NULL; + char *ap = ackbuf + 2; + + ((struct tftphdr *)ackbuf)->th_opcode = htons(OACK); + + origfilename = cp = (char *)&(tp->th_stuff); + argn = 0; + + end = (char *)tp + size; + + while (cp < end && *cp) { + do { + cp++; + } while (cp < end && *cp); + + if (*cp) { + nak(EBADOP, "Request not null-terminated"); + 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, "Unknown mode"); + exit(0); + } + if (!(filename = + (*pf->f_rewrite) (origfilename, tp_opcode, + &errmsgptr))) { + nak(EACCESS, errmsgptr); /* File denied by mapping rule */ + exit(0); + } + if (verbosity >= 1) { + tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), + tmpbuf, INET6_ADDRSTRLEN); + if (!tmp_p) { + tmp_p = tmpbuf; + strcpy(tmpbuf, "???"); + } + if (filename == origfilename + || !strcmp(filename, origfilename)) + syslog(LOG_NOTICE, "%s from %s filename %s\n", + tp_opcode == WRQ ? "WRQ" : "RRQ", + tmp_p, filename); + else + syslog(LOG_NOTICE, + "%s from %s filename %s remapped to %s\n", + tp_opcode == WRQ ? "WRQ" : "RRQ", + tmp_p, origfilename, + filename); + } + ecode = + (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr); + if (ecode) { + nak(ecode, errmsgptr); + exit(0); + } + opt = ++cp; + } else if (argn & 1) { + val = ++cp; + } else { + do_opt(opt, val, &ap); + opt = ++cp; + } + } + + if (!pf) { + nak(EBADOP, "Missing mode"); + exit(0); + } + + if (ap != (ackbuf + 2)) { + if (tp_opcode == WRQ) + (*pf->f_recv) (pf, (struct tftphdr *)ackbuf, ap - ackbuf); + else + (*pf->f_send) (pf, (struct tftphdr *)ackbuf, ap - ackbuf); + } else { + if (tp_opcode == WRQ) + (*pf->f_recv) (pf, NULL, 0); + else + (*pf->f_send) (pf, NULL, 0); + } + exit(0); /* Request completed */ +} + +static int blksize_set; + +/* + * Set a non-standard block size (c.f. RFC2348) + */ +static int set_blksize(uintmax_t *vp) +{ + uintmax_t sz = *vp; + + if (blksize_set) + return 0; + + if (sz < 8) + return 0; + else if (sz > max_blksize) + sz = max_blksize; + + *vp = segsize = sz; + blksize_set = 1; + return 1; +} + +/* + * Set a power-of-two block size (nonstandard) + */ +static int set_blksize2(uintmax_t *vp) +{ + uintmax_t sz = *vp; + + if (blksize_set) + return 0; + + if (sz < 8) + return (0); + else if (sz > max_blksize) + sz = max_blksize; + else + + /* Convert to a power of two */ + if (sz & (sz - 1)) { + unsigned int sz1 = 1; + /* Not a power of two - need to convert */ + while (sz >>= 1) + sz1 <<= 1; + sz = sz1; + } + + *vp = segsize = sz; + blksize_set = 1; + return 1; +} + +/* + * Set the block number rollover value + */ +static int set_rollover(uintmax_t *vp) +{ + uintmax_t ro = *vp; + + if (ro > 65535) + return 0; + + rollover_val = (uint16_t)ro; + 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. + */ +static int set_tsize(uintmax_t *vp) +{ + uintmax_t sz = *vp; + + if (!tsize_ok) + return 0; + + if (sz == 0) + sz = tsize; + + *vp = sz; + return 1; +} + +/* + * Set the timeout (c.f. RFC2349). This is supposed + * to be the (default) retransmission timeout, but being an + * integer in seconds it seems a bit limited. + */ +static int set_timeout(uintmax_t *vp) +{ + uintmax_t to = *vp; + + if (to < 1 || to > 255) + return 0; + + rexmtval = timeout = to * 1000000UL; + maxtimeout = rexmtval * TIMEOUT_LIMIT; + + return 1; +} + +/* Similar, but in microseconds. We allow down to 10 ms. */ +static int set_utimeout(uintmax_t *vp) +{ + uintmax_t to = *vp; + + if (to < 10000UL || to > 255000000UL) + return 0; + + rexmtval = timeout = to; + maxtimeout = rexmtval * TIMEOUT_LIMIT; + + return 1; +} + +/* + * Conservative calculation for the size of a buffer which can hold an + * arbitrary integer + */ +#define OPTBUFSIZE (sizeof(uintmax_t) * CHAR_BIT / 3 + 3) + +/* + * Parse RFC2347 style options; we limit the arguments to positive + * integers which matches all our current options. + */ +static void do_opt(const char *opt, const char *val, char **ap) +{ + struct options *po; + char retbuf[OPTBUFSIZE]; + char *p = *ap; + size_t optlen, retlen; + char *vend; + uintmax_t v; + + /* Global option-parsing variables initialization */ + blksize_set = 0; + + if (!*opt || !*val) + return; + + errno = 0; + v = strtoumax(val, &vend, 10); + if (*vend || errno == ERANGE) + return; + + for (po = options; po->o_opt; po++) + if (!strcasecmp(po->o_opt, opt)) { + if (po->o_fnc(&v)) { + optlen = strlen(opt); + retlen = sprintf(retbuf, "%"PRIuMAX, v); + + if (p + optlen + retlen + 2 >= ackbuf + sizeof(ackbuf)) { + nak(EOPTNEG, "Insufficient space for options"); + exit(0); + } + + memcpy(p, opt, optlen+1); + p += optlen+1; + memcpy(p, retbuf, retlen+1); + p += retlen+1; + } else { + nak(EOPTNEG, "Unsupported option(s) requested"); + exit(0); + } + break; + } + + *ap = p; +} + +#ifdef WITH_REGEX + +/* + * This is called by the remap engine when it encounters macros such + * as \i. It should write the output in "output" if non-NULL, and + * return the length of the output (generated or not). + * + * Return -1 on failure. + */ +static int rewrite_macros(char macro, char *output) +{ + char *p, tb[INET6_ADDRSTRLEN]; + int l=0; + + switch (macro) { + case 'i': + p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), + tb, INET6_ADDRSTRLEN); + if (output && p) + strcpy(output, p); + if (!p) + return 0; + else + return strlen(p); + + case 'x': + if (output) { + if (from.sa.sa_family == AF_INET) { + sprintf(output, "%08lX", + (unsigned long)ntohl(from.si.sin_addr.s_addr)); + l = 8; +#ifdef HAVE_IPV6 + } else { + unsigned char *c = (unsigned char *)SOCKADDR_P(&from); + p = tb; + for (l = 0; l < 16; l++) { + sprintf(p, "%02X", *c); + c++; + p += 2; + } + strcpy(output, tb); + l = strlen(tb); +#endif + } + } + return l; + + default: + return -1; + } +} + +/* + * Modify the filename, if applicable. If it returns NULL, deny the access. + */ +static char *rewrite_access(char *filename, int mode, const char **msg) +{ + if (rewrite_rules) { + char *newname = + rewrite_string(filename, rewrite_rules, + mode != RRQ ? 'P' : 'G', + rewrite_macros, msg); + filename = newname; + } + return filename; +} + +#else +static char *rewrite_access(char *filename, int mode, const char **msg) +{ + (void)mode; /* Avoid warning */ + (void)msg; + return filename; +} +#endif + +static FILE *file; +/* + * Validate file access. Since we + * have no uid or gid, for now require + * file to exist and be publicly + * readable/writable, unless -p specified. + * 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. + */ +static int validate_access(char *filename, int mode, + const struct formats *pf, const char **errmsg) +{ + struct stat stbuf; + int i, len; + int fd, wmode, rmode; + char *cp; + const char **dirp; + char stdio_mode[3]; + + tsize_ok = 0; + *errmsg = NULL; + + if (!secure) { + if (*filename != '/') { + *errmsg = "Only absolute filenames allowed"; + return (EACCESS); + } + + /* + * prevent tricksters from getting around the directory + * restrictions + */ + len = strlen(filename); + for (i = 1; i < len - 3; i++) { + cp = filename + i; + if (*cp == '.' && memcmp(cp - 1, "/../", 4) == 0) { + *errmsg = "Reverse path not allowed"; + return (EACCESS); + } + } + + for (dirp = dirs; *dirp; dirp++) + if (strncmp(filename, *dirp, strlen(*dirp)) == 0) + break; + if (*dirp == 0 && dirp != dirs) { + *errmsg = "Forbidden directory"; + return (EACCESS); + } + } + + /* + * We use different a different permissions scheme if `cancreate' is + * set. + */ + wmode = O_WRONLY | (cancreate ? O_CREAT : 0) | (pf->f_convert ? O_TEXT : O_BINARY); + rmode = O_RDONLY | (pf->f_convert ? O_TEXT : O_BINARY); + +#ifndef HAVE_FTRUNCATE + wmode |= O_TRUNC; /* This really sucks on a dupe */ +#endif + + fd = open(filename, mode == RRQ ? rmode : wmode, 0666); + if (fd < 0) { + switch (errno) { + case ENOENT: + case ENOTDIR: + return ENOTFOUND; + case ENOSPC: + return ENOSPACE; + case EEXIST: + return EEXISTS; + default: + return errno + 100; + } + } + + if (fstat(fd, &stbuf) < 0) + exit(EX_OSERR); /* This shouldn't happen */ + + /* A duplicate RRQ or (worse!) WRQ packet could really cause havoc... */ + if (lock_file(fd, mode != RRQ)) + exit(0); + + if (mode == RRQ) { + if (!unixperms && (stbuf.st_mode & (S_IREAD >> 6)) == 0) { + *errmsg = "File must have global read permissions"; + return (EACCESS); + } + tsize = stbuf.st_size; + /* We don't know the tsize if conversion is needed */ + tsize_ok = !pf->f_convert; + } else { + if (!unixperms) { + if ((stbuf.st_mode & (S_IWRITE >> 6)) == 0) { + *errmsg = "File must have global write permissions"; + return (EACCESS); + } + } + +#ifdef HAVE_FTRUNCATE + /* We didn't get to truncate the file at open() time */ + if (ftruncate(fd, (off_t) 0)) { + *errmsg = "Cannot reset file size"; + return (EACCESS); + } +#endif + tsize = 0; + tsize_ok = 1; + } + + stdio_mode[0] = (mode == RRQ) ? 'r' : 'w'; + stdio_mode[1] = (pf->f_convert) ? 't' : 'b'; + stdio_mode[2] = '\0'; + + file = fdopen(fd, stdio_mode); + if (file == NULL) + exit(EX_OSERR); /* Internal error */ + + return (0); +} + +/* + * Send the requested file. + */ +static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oacklen) +{ + struct tftphdr *dp; + struct tftphdr *ap; /* ack packet */ + static u_short block = 1; /* Static to avoid longjmp funnies */ + u_short ap_opcode, ap_block; + unsigned long r_timeout; + int size, n; + + if (oap) { + timeout = rexmtval; + (void)sigsetjmp(timeoutbuf, 1); + oack: + r_timeout = timeout; + if (send(peer, oap, oacklen, 0) != oacklen) { + syslog(LOG_WARNING, "tftpd: oack: %m\n"); + goto abort; + } + for (;;) { + n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout); + if (n < 0) { + syslog(LOG_WARNING, "tftpd: read: %m\n"); + goto abort; + } + ap = (struct tftphdr *)ackbuf; + ap_opcode = ntohs((u_short) ap->th_opcode); + ap_block = ntohs((u_short) ap->th_block); + + if (ap_opcode == ERROR) { + syslog(LOG_WARNING, + "tftp: client does not accept options\n"); + goto abort; + } + if (ap_opcode == ACK) { + if (ap_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, NULL); + goto abort; + } + dp->th_opcode = htons((u_short) DATA); + dp->th_block = htons((u_short) block); + timeout = rexmtval; + (void)sigsetjmp(timeoutbuf, 1); + + r_timeout = timeout; + if (send(peer, dp, size + 4, 0) != size + 4) { + syslog(LOG_WARNING, "tftpd: write: %m"); + goto abort; + } + read_ahead(file, pf->f_convert); + for (;;) { + n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout); + if (n < 0) { + syslog(LOG_WARNING, "tftpd: read(ack): %m"); + goto abort; + } + ap = (struct tftphdr *)ackbuf; + ap_opcode = ntohs((u_short) ap->th_opcode); + ap_block = ntohs((u_short) ap->th_block); + + if (ap_opcode == ERROR) + goto abort; + + if (ap_opcode == ACK) { + if (ap_block == block) { + break; + } + /* Re-synchronize with the other side */ + (void)synchnet(peer); + /* + * RFC1129/RFC1350: We MUST NOT re-send the DATA + * packet in response to an invalid ACK. Doing so + * would cause the Sorcerer's Apprentice bug. + */ + } + + } + if (!++block) + block = rollover_val; + } while (size == segsize); + abort: + (void)fclose(file); +} + +/* + * Receive a file. + */ +static void tftp_recvfile(const struct formats *pf, struct tftphdr *oap, int oacklen) +{ + struct tftphdr *dp; + int n, size; + /* These are "static" to avoid longjmp funnies */ + static struct tftphdr *ap; /* ack buffer */ + static u_short block = 0; + static int acksize; + u_short dp_opcode, dp_block; + unsigned long r_timeout; + + dp = w_init(); + do { + timeout = rexmtval; + + 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; + /* If we're sending a regular ACK, that means we have successfully + * sent the OACK. Clear oap so that we won't try to send another + * OACK when the block number wraps back to 0. */ + oap = NULL; + } + if (!++block) + block = rollover_val; + (void)sigsetjmp(timeoutbuf, 1); + send_ack: + r_timeout = timeout; + if (send(peer, ackbuf, acksize, 0) != acksize) { + syslog(LOG_WARNING, "tftpd: write(ack): %m"); + goto abort; + } + write_behind(file, pf->f_convert); + for (;;) { + n = recv_time(peer, dp, PKTSIZE, 0, &r_timeout); + if (n < 0) { /* really? */ + syslog(LOG_WARNING, "tftpd: read: %m"); + goto abort; + } + dp_opcode = ntohs((u_short) dp->th_opcode); + dp_block = ntohs((u_short) dp->th_block); + if (dp_opcode == ERROR) + goto abort; + if (dp_opcode == DATA) { + if (dp_block == block) { + break; /* normal */ + } + /* Re-synchronize with the other side */ + (void)synchnet(peer); + if (dp_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, NULL); + else + nak(ENOSPACE, NULL); + 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); + + timeout_quit = 1; /* just quit on timeout */ + n = recv_time(peer, buf, sizeof(buf), 0, &timeout); /* normally times out and quits */ + timeout_quit = 0; + + if (n >= 4 && /* if read some data */ + dp_opcode == DATA && /* and got a data block */ + block == dp_block) { /* then my last ack was lost */ + (void)send(peer, ackbuf, 4, 0); /* resend final ack */ + } + abort: + return; +} + +static const char *const errmsgs[] = { + "Undefined error code", /* 0 - EUNDEF */ + "File not found", /* 1 - ENOTFOUND */ + "Access denied", /* 2 - EACCESS */ + "Disk full or allocation exceeded", /* 3 - ENOSPACE */ + "Illegal TFTP operation", /* 4 - EBADOP */ + "Unknown transfer ID", /* 5 - EBADID */ + "File already exists", /* 6 - EEXISTS */ + "No such user", /* 7 - ENOUSER */ + "Failure to negotiate RFC2347 options" /* 8 - EOPTNEG */ +}; + +#define ERR_CNT (sizeof(errmsgs)/sizeof(const char *)) + +/* + * 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, const char *msg) +{ + struct tftphdr *tp; + int length; + + tp = (struct tftphdr *)buf; + tp->th_opcode = htons((u_short) ERROR); + + if (error >= 100) { + /* This is a Unix errno+100 */ + if (!msg) + msg = strerror(error - 100); + error = EUNDEF; + } else { + if ((unsigned)error >= ERR_CNT) + error = EUNDEF; + + if (!msg) + msg = errmsgs[error]; + } + + tp->th_code = htons((u_short) error); + + length = strlen(msg) + 1; + memcpy(tp->th_msg, msg, length); + length += 4; /* Add space for header */ + + if (verbosity >= 2) { + tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), + tmpbuf, INET6_ADDRSTRLEN); + if (!tmp_p) { + tmp_p = tmpbuf; + strcpy(tmpbuf, "???"); + } + syslog(LOG_INFO, "sending NAK (%d, %s) to %s", + error, tp->th_msg, tmp_p); + } + + if (send(peer, buf, length, 0) != length) + syslog(LOG_WARNING, "nak: %m"); +} diff --git a/tftpd/tftpd.h b/tftpd/tftpd.h new file mode 100644 index 0000000..e1d8bf0 --- /dev/null +++ b/tftpd/tftpd.h @@ -0,0 +1,26 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2001 H. Peter Anvin - All Rights Reserved + * + * This program is free software available under the same license + * as the "OpenBSD" operating system, distributed at + * http://www.openbsd.org/. + * + * ----------------------------------------------------------------------- */ + +/* + * tftpd.h + * + * Prototypes for various functions that are part of the tftpd server. + */ + +#ifndef TFTPD_TFTPD_H +#define TFTPD_TFTPD_H + +void set_signal(int, void (*)(int), int); +void *tfmalloc(size_t); +char *tfstrdup(const char *); + +extern int verbosity; + +#endif diff --git a/version b/version new file mode 100644 index 0000000..ef425ca --- /dev/null +++ b/version @@ -0,0 +1 @@ +5.2