diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..544d21f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[{*.c,*.h}] +indent_style = space +indent_size = 4 +tab_width = 8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb95251 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +/config/MCONFIG +/config/config.h +/config/config.h.in +/autoconf/aclocal.m4 +/autoconf/clean.sh +/autoconf/helpers/ +/autom4te.cache +/config.log +/config.status +/configure +/version.h +/tftp/tftp +/tftpd/tftpd +*.1 +*.8 +*.a +*.o +*.i +*.s +*~ +\#* diff --git a/CHANGES b/CHANGES index 15874ab..6b7b53c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,250 @@ -$Id$ +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 diff --git a/INSTALL.tftp b/INSTALL.tftp index 4bd87a5..914094f 100644 --- a/INSTALL.tftp +++ b/INSTALL.tftp @@ -1,5 +1,3 @@ -$Id$ - Specific installation instructions ================================== diff --git a/Makefile b/Makefile index 4c2369e..2e790fd 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ # 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 tftp tftpd +SUB = lib common tftp tftpd -%.build: +%.build: config/MCONFIG config/config.h version.h $(MAKE) -C $(patsubst %.build, %, $@) -%.install: +%.install: config/MCONFIG config/config.h version.h $(MAKE) -C $(patsubst %.install, %, $@) install %.clean: @@ -15,39 +15,55 @@ SUB = lib tftp tftpd %.distclean: $(MAKE) -C $(patsubst %.distclean, %, $@) distclean -all: MCONFIG $(patsubst %, %.build, $(SUB)) +all: config/MCONFIG $(patsubst %, %.build, $(SUB)) -install: MCONFIG $(patsubst %, %.install, $(SUB)) +tftp.build: lib.build common.build +tftpd.build: lib.build common.build -clean: $(patsubst %, %.clean, $(SUB)) +install: config/MCONFIG $(patsubst %, %.install, $(SUB)) -distclean: $(patsubst %, %.distclean, $(SUB)) - rm -f MCONFIG config.status config.log acconfig.h *~ \#* +clean: localclean $(patsubst %, %.clean, $(SUB)) + +localclean: + rm -f version.h + +distclean: localdistclean $(patsubst %, %.distclean, $(SUB)) + +localdistclean: localclean + rm -f config/config/MCONFIG config.status config.log config/config.h *~ \#* rm -rf *.cache - find . -type f \( -name \*.orig -o -name \*.rej \) | xargs -r rm -f + find . -type f \( -name \*.orig -o -name \*.rej \) | xargs rm -f spotless: distclean - rm -f configure acconfig.h.in + rm -f configure config/config.h.in tftp.spec -autoconf: configure acconfig.h.in +autoconf: configure config/config.h.in -config: MCONFIG acconfig.h +config: config/MCONFIG config/config.h release: $(MAKE) autoconf + $(MAKE) tftp.spec $(MAKE) distclean -MCONFIG: configure MCONFIG.in acconfig.h.in - ./configure +config/MCONFIG: configure config/MCONFIG.in config/config.h.in + if test -x config.status; then \ + ./config.status --recheck && ./config.status ; \ + else \ + ./configure ; \ + fi -acconfig.h: MCONFIG +config/config.h: config/MCONFIG : Generated by side effect -acconfig.h.in: configure.in aclocal.m4 - autoheader -f - touch -c acconfig.h.in - rm -f acconfig.h +configure: configure.ac + sh autogen.sh -configure: configure.in aclocal.m4 - autoconf - rm -f MCONFIG config.cache config.log acconfig.h +config/config.h.in: configure + : Generated by side effect + +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 index fc25395..91afcb3 100644 --- a/README +++ b/README @@ -21,5 +21,7 @@ installation instructions. This software can be discussed on the SYSLINUX mailing list. To -subscribe, send a message containing the word "subscribe" in the body -to . +subscribe, go to the list subscription page at: + + http://www.zytor.com/mailman/listinfo/syslinux + diff --git a/README.security b/README.security index 7db6f2e..644babb 100644 --- a/README.security +++ b/README.security @@ -1,3 +1,26 @@ +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 diff --git a/aclocal.m4 b/aclocal.m4 deleted file mode 100644 index 310d5b0..0000000 --- a/aclocal.m4 +++ /dev/null @@ -1,172 +0,0 @@ -dnl -------------------------------------------------------------------------- -dnl PA_ADD_CFLAGS() -dnl -dnl Attempt to add the given option to CFLAGS, if it doesn't break compilation -dnl -------------------------------------------------------------------------- -AC_DEFUN(PA_ADD_CFLAGS, -[AC_MSG_CHECKING([if $CC accepts $1]) - pa_add_cflags__old_cflags="$CFLAGS" - CFLAGS="$CFLAGS $1" - AC_TRY_COMPILE([#include ], - [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( - [#include ], - [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_MSG_CHECKING([for msg_control in struct msghdr]) - AC_TRY_COMPILE( -[ -#define _XPG4_2 /* Needed on Solaris */ -#include -#include -#include -], -[ - struct msghdr msg; - void *p = (void *) &msg.msg_control; -], -[ - AC_DEFINE(HAVE_MSGHDR_MSG_CONTROL) - AC_MSG_RESULT([yes]) -], -[ - AC_MSG_RESULT([no]) -])]) - -dnl ------------------------------------------------------------------------ -dnl PA_STRUCT_IN_PKTINFO -dnl -dnl Look for definition of struct in_pktinfo. Some versions of glibc -dnl lack struct in_pktinfo; if so we need to include the definition -dnl ourselves -- but we only 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_MSG_CHECKING([for definition of struct in_pktinfo]) - AC_TRY_COMPILE( -[ -#include -#include -#include -#include -#include -#include -], -[ - struct in_pktinfo pktinfo; - int foo = sizeof(struct in_pktinfo); - void *quux = (void *)(&pktinfo.ipi_addr); -], -[ - AC_DEFINE(HAVE_STRUCT_IN_PKTINFO) - AC_MSG_RESULT([yes]) -], -[ - AC_MSG_RESULT([no]) -])]) - -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_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]) - 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) -])]) - diff --git a/autoconf/m4/pa_add_cflags.m4 b/autoconf/m4/pa_add_cflags.m4 new file mode 100644 index 0000000..26d55c4 --- /dev/null +++ b/autoconf/m4/pa_add_cflags.m4 @@ -0,0 +1,9 @@ +dnl -------------------------------------------------------------------------- +dnl PA_ADD_CFLAGS(variable, flag [,actual_flag [,success [,failure]]]]) +dnl +dnl Attempt to add the given option to xFLAGS, if it doesn't break +dnl compilation. If the option to be tested is different than the +dnl option that should actually be added, add the option to be +dnl actually added as a second argument. +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_ADD_CFLAGS], [PA_ADD_FLAGS(CFLAGS, [$1], [$2], [$3], [$4])]) diff --git a/autoconf/m4/pa_add_flags.m4 b/autoconf/m4/pa_add_flags.m4 new file mode 100644 index 0000000..23f96f1 --- /dev/null +++ b/autoconf/m4/pa_add_flags.m4 @@ -0,0 +1,39 @@ +dnl -------------------------------------------------------------------------- +dnl PA_ADD_FLAGS(flagvar, flags) +dnl +dnl Add [flags] to the variable [flagvar] if and only if it is accepted +dnl by all languages affected by [flagvar], if those languages have +dnl been previously seen in the script. +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_ADD_FLAGS], +[ + AS_VAR_PUSHDEF([old], [_$0_$1_orig]) + AS_VAR_PUSHDEF([ok], [_$0_$1_ok]) + AS_VAR_PUSHDEF([flags], [$1]) + + AS_VAR_COPY([old], [flags]) + AS_VAR_SET([flags], ["$flags $2"]) + AS_VAR_SET([ok], [yes]) + + PA_LANG_FOREACH(PA_FLAGS_LANGLIST($1), + [AS_VAR_IF([ok], [yes], + [AC_MSG_CHECKING([if $]_AC_CC[ accepts $2]) + PA_BUILD_IFELSE([], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AS_VAR_SET([ok], [no])])]) + ]) + + AS_VAR_IF([ok], [yes], + [m4_ifnblank([$3],[AS_VAR_SET([flags], ["$old $3"])]) + m4_foreach_w([_pa_add_flags_flag], [m4_ifblank([$3],[$2],[$3])], + [AC_DEFINE(PA_SYM([$1_]_pa_add_flags_flag), 1, + [Define to 1 if compiled with the ]_pa_add_flags_flag[ compiler flag])]) + $4], + [AS_VAR_SET([flags], ["$old"]) + $5]) + + AS_VAR_POPDEF([flags]) + AS_VAR_POPDEF([ok]) + AS_VAR_POPDEF([old]) +]) diff --git a/autoconf/m4/pa_add_headers.m4 b/autoconf/m4/pa_add_headers.m4 new file mode 100644 index 0000000..d3c478a --- /dev/null +++ b/autoconf/m4/pa_add_headers.m4 @@ -0,0 +1,13 @@ +dnl -------------------------------------------------------------------------- +dnl PA_ADD_HEADERS(headers...) +dnl +dnl Call AC_CHECK_HEADERS(), and add to ac_includes_default if found +dnl -------------------------------------------------------------------------- +AC_DEFUN([_PA_ADD_HEADER], +[AC_CHECK_HEADERS([$1],[ac_includes_default="$ac_includes_default +#include <$1>" +]) +]) + +AC_DEFUN([PA_ADD_HEADERS], +[m4_map_args_w([$1],[_PA_ADD_HEADER(],[)])]) diff --git a/autoconf/m4/pa_add_langflags.m4 b/autoconf/m4/pa_add_langflags.m4 new file mode 100644 index 0000000..05c3114 --- /dev/null +++ b/autoconf/m4/pa_add_langflags.m4 @@ -0,0 +1,27 @@ +dnl -------------------------------------------------------------------------- +dnl PA_ADD_LANGFLAGS(flag...) +dnl +dnl Attempt to add the option in the given list to each compiler flags +dnl (CFLAGS, CXXFLAGS, ...), if it doesn't break compilation. +dnl -------------------------------------------------------------------------- +m4_defun([_PA_LANGFLAG_VAR], +[m4_case([$1], + [C], [CFLAGS], + [C++], [CXXFLAGS], + [Fortran 77], [FFLAGS], + [Fortran], [FCFLAGS], + [Erlang], [ERLCFLAGS], + [Objective C], [OBJCFLAGS], + [Objective C++], [OBJCXXFLAGS], + [Go], [GOFLAGS], + [m4_fatal([PA_ADD_LANGFLAGS: Unknown language: $1])])]) + +AC_DEFUN([PA_ADD_LANGFLAGS], +[m4_pushdef([_pa_langflags],m4_dquote($1))dnl +m4_set_foreach(_PA_LANG_SEEN_SET,[_pa_lang],dnl +[_pa_flag_found=no + m4_foreach_w([_pa_flag], _pa_langflags, + [AS_IF([test $_pa_flag_found = no], + [PA_ADD_FLAGS(_PA_LANGFLAG_VAR(_pa_lang),_pa_flag,[],[_pa_flag_found=yes])]) + ])]) +m4_popdef([_pa_langflags])]) diff --git a/autoconf/m4/pa_arg_bool.m4 b/autoconf/m4/pa_arg_bool.m4 new file mode 100644 index 0000000..5289ed4 --- /dev/null +++ b/autoconf/m4/pa_arg_bool.m4 @@ -0,0 +1,19 @@ +dnl -------------------------------------------------------------------------- +dnl PA_ARG_BOOL(option,helptext,default,enabled_action,disabled_action) +dnl +dnl The last three arguments are optional; default can be yes or no. +dnl +dnl Simpler-to-use versions of AC_ARG_ENABLED, that include the +dnl test for $enableval and the AS_HELP_STRING definition. This is only +dnl to be used for boolean options. +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_ARG_BOOL], +[m4_pushdef([pa_default],m4_default(m4_normalize([$3]),[no])) + m4_pushdef([pa_option],m4_case(pa_default,[yes],[disable],[enable])) + AC_ARG_ENABLE([$1], + [AS_HELP_STRING([--]m4_defn([pa_option])[-$1],[$2])], + [pa_arg_bool_enableval="$enableval"], + [pa_arg_bool_enableval="]m4_defn([pa_default])["]) + m4_popdef([pa_option], [pa_default]) + AS_IF([test x"$pa_arg_bool_enableval" != xno], [$4], [$5]) +]) diff --git a/autoconf/m4/pa_arg_disabled.m4 b/autoconf/m4/pa_arg_disabled.m4 new file mode 100644 index 0000000..42f4ce8 --- /dev/null +++ b/autoconf/m4/pa_arg_disabled.m4 @@ -0,0 +1,4 @@ +dnl -------------------------------------------------------------------------- +dnl PA_ARG_DISABLED(option,helptext,disabled_action,enabled_action) +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_ARG_DISABLED],[PA_ARG_BOOL([$1],[$2],yes,[$4],[$3])]) diff --git a/autoconf/m4/pa_arg_enabled.m4 b/autoconf/m4/pa_arg_enabled.m4 new file mode 100644 index 0000000..7d66a21 --- /dev/null +++ b/autoconf/m4/pa_arg_enabled.m4 @@ -0,0 +1,4 @@ +dnl -------------------------------------------------------------------------- +dnl PA_ARG_ENABLED(option,helptext,enabled_action,disabled_action) +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_ARG_ENABLED],[PA_ARG_BOOL([$1],[$2],no,[$3],[$4])]) diff --git a/autoconf/m4/pa_build_ifelse.m4 b/autoconf/m4/pa_build_ifelse.m4 new file mode 100644 index 0000000..1aacfcd --- /dev/null +++ b/autoconf/m4/pa_build_ifelse.m4 @@ -0,0 +1,16 @@ +dnl -------------------------------------------------------------------------- +dnl PA_BUILD_IFELSE(input [,success [,failure]]) +dnl +dnl Same as AC_LINK_IFELSE for languages where linking is applicable, +dnl otherwise AC_COMPILE_IFELSE. +dnl +dnl If the first argument is empty, use _AC_LANG_IO_PROGRAM. +dnl -------------------------------------------------------------------------- +m4_defun([_PA_BUILD_IFELSE], +[m4_case(_AC_LANG, + [Erlang], [AC_COMPILE_IFELSE($@)], + [AC_LINK_IFELSE($@)])]) + +AC_DEFUN([PA_BUILD_IFELSE], +[_PA_BUILD_IFELSE([m4_ifblank([$1],[AC_LANG_SOURCE(_AC_LANG_IO_PROGRAM)], + [$1])],[$2],[$3])]) diff --git a/autoconf/m4/pa_c_typeof.m4 b/autoconf/m4/pa_c_typeof.m4 new file mode 100644 index 0000000..909b171 --- /dev/null +++ b/autoconf/m4/pa_c_typeof.m4 @@ -0,0 +1,32 @@ +dnl -------------------------------------------------------------------------- +dnl PA_C_TYPEOF +dnl +dnl Find if typeof() exists, or an equivalent (__typeof__, decltype, +dnl __decltype__) +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_C_TYPEOF], +[AC_CACHE_CHECK([if $CC supports typeof], [pa_cv_typeof], + [pa_cv_typeof=no + for pa_typeof_try in typeof __typeof __typeof__ decltype __decltype __decltype__ _Decltype + do + AS_IF([test $pa_cv_typeof = no], + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([ +AC_INCLUDES_DEFAULT +int testme(int x); +int testme(int x) +{ + $pa_typeof_try(x) y = x*x; + return y; +} +])], + [pa_cv_typeof=$pa_typeof_try])]) + done + ]) + AS_IF([test $pa_cv_typeof = no], + [], + [AC_DEFINE([HAVE_TYPEOF], 1, + [Define to 1 if you have some version of the typeof operator.]) + AS_IF([test $pa_cv_typeof = typeof], + [], + [AC_DEFINE_UNQUOTED([typeof], [$pa_cv_typeof], + [Define if your typeof operator is not named `typeof'.])])])]) diff --git a/autoconf/m4/pa_check_bad_stdc_inline.m4 b/autoconf/m4/pa_check_bad_stdc_inline.m4 new file mode 100644 index 0000000..3fbc53a --- /dev/null +++ b/autoconf/m4/pa_check_bad_stdc_inline.m4 @@ -0,0 +1,26 @@ +dnl -------------------------------------------------------------------------- +dnl PA_CHECK_BAD_STDC_INLINE +dnl +dnl Some versions of gcc seem to apply -Wmissing-prototypes to C99 +dnl inline functions, which means we need to use GNU inline syntax +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_CHECK_BAD_STDC_INLINE], +[AC_MSG_CHECKING([if $CC supports C99 external inlines]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([ +AC_INCLUDES_DEFAULT + +/* Don't mistake GNU inlines for c99 */ +#if defined(__GNUC__) && !defined(__GNUC_STDC_INLINE__) +# error "Using gnu inline standard" +#endif + +inline int foo(int x) +{ + return x+1; +} + ])], + [AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_STDC_INLINE], 1, + [Define to 1 if your compiler supports C99 extern inline])], + [AC_MSG_RESULT([no]) + PA_ADD_CFLAGS([-fgnu89-inline])])]) diff --git a/autoconf/m4/pa_check_inttypes_h_sane.m4 b/autoconf/m4/pa_check_inttypes_h_sane.m4 new file mode 100644 index 0000000..589e0a2 --- /dev/null +++ b/autoconf/m4/pa_check_inttypes_h_sane.m4 @@ -0,0 +1,17 @@ +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 -------------------------------------------------------------------------- +AC_DEFUN([PA_CHECK_INTTYPES_H_SANE], +[AC_CHECK_HEADERS_ONCE(inttypes.h) +AS_IF([test "x$ac_cv_header_inttypes_h" = xyes],[ + AC_MSG_CHECKING([if inttypes.h is sane]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], + [uintmax_t max = UINTMAX_C(0); + printf("%"PRIuMAX"\n", max);])], + [AC_MSG_RESULT([yes]) + AC_DEFINE(INTTYPES_H_IS_SANE, 1, + [Define if the macros in are usable])], + [AC_MSG_RESULT([no (AIX, eh?)])])])]) diff --git a/autoconf/m4/pa_cross_compile.m4 b/autoconf/m4/pa_cross_compile.m4 new file mode 100644 index 0000000..f082973 --- /dev/null +++ b/autoconf/m4/pa_cross_compile.m4 @@ -0,0 +1,41 @@ +dnl -------------------------------------------------------------------------- +dnl PA_CROSS_COMPILE +dnl +dnl Get the canonical name for the build and host (runtime) systems; +dnl then figure out if this is cross-compilation. Specifically, this +dnl disables invoking WINE on non-Windows systems which are configured +dnl to run WINE automatically. +dnl +dnl Use PA_CROSS_COMPILE_TOOL if the target system (output of a code- +dnl generation tool) is applicable. +dnl +dnl This doesn't explicitly print any messages as that is automatically +dnl done elsewhere. +dnl -------------------------------------------------------------------------- +AC_DEFUN_ONCE([PA_CROSS_COMPILE], +[ + AC_BEFORE([$0], [AC_LANG_COMPILER]) + AC_BEFORE([$0], [AC_LANG]) + AC_BEFORE([$0], [AC_PROG_CC]) + AC_BEFORE([$0], [AC_PROG_CPP]) + AC_BEFORE([$0], [AC_PROG_CXX]) + AC_BEFORE([$0], [AC_PROG_CXXCPP]) + AC_BEFORE([$0], [AC_PROG_OBJC]) + AC_BEFORE([$0], [AC_PROG_OBJCPP]) + AC_BEFORE([$0], [AC_PROG_OBJCXX]) + AC_BEFORE([$0], [AC_PROG_OBJCXXCPP]) + AC_BEFORE([$0], [AC_PROG_F77]) + AC_BEFORE([$0], [AC_PROG_FC]) + AC_BEFORE([$0], [AC_PROG_GO]) + + # Disable WINE + WINELOADER=/dev/null + export WINELOADER + WINESERVER=/dev/null + export WINESERVER + WINEPREFIX=/dev/null + export WINEPREFIX + + AC_CANONICAL_BUILD + AC_CANONICAL_HOST +]) diff --git a/autoconf/m4/pa_flags_langlist.m4 b/autoconf/m4/pa_flags_langlist.m4 new file mode 100644 index 0000000..2ef5ded --- /dev/null +++ b/autoconf/m4/pa_flags_langlist.m4 @@ -0,0 +1,19 @@ +dnl -------------------------------------------------------------------------- +dnl PA_FLAGS_LANGLIST(flagvar) +dnl +dnl Return a list of languages affected by the variable flagvar. +dnl If flagvar is unknown, assume it affects the current language. +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_FLAGS_LANGLIST], +[m4_dquote(m4_case([$1], + [CPPFLAGS], [[C],[C++],[Objective C],[Objective C++]], + [CFLAGS], [[C]], + [CXXFLAGS], [[C++]], + [FFLAGS], [[Fortran 77]], + [FCFLAGS], [[Fortran]], + [ERLCFLAGS], [[Erlang]], + [OBJCFLAGS], [[Objective C]], + [OBJCXXFLAGS], [[Objective C++]], + [GOFLAGS], [[Go]], + [LDFLAGS], [[C],[C++],[Fortran 77],[Fortran],[Objective C],[Objective C++],[Go]], + m4_dquote(_AC_LANG)))]) diff --git a/autoconf/m4/pa_have_tcpwrappers.m4 b/autoconf/m4/pa_have_tcpwrappers.m4 new file mode 100644 index 0000000..fba87ce --- /dev/null +++ b/autoconf/m4/pa_have_tcpwrappers.m4 @@ -0,0 +1,26 @@ +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 -------------------------------------------------------------------------- +AC_DEFUN([PA_HAVE_TCPWRAPPERS], +[AC_CHECK_LIB([wrap], [main]) + AC_MSG_CHECKING([for tcpwrappers]) + AC_LINK_IFELSE([AC_LANG_PROGRAM( + [[ +#include +int allow_severity = 0; +int deny_severity = 0; + ]], + [[ + hosts_ctl("sample_daemon", STRING_UNKNOWN, STRING_UNKNOWN, STRING_UNKNOWN); + ]])], + [ + AC_DEFINE(HAVE_TCPWRAPPERS, 1, + [Define if we have tcpwrappers (-lwrap) and .]) + AC_MSG_RESULT([yes]) + ], + [ + AC_MSG_RESULT([no]) + ])]) diff --git a/autoconf/m4/pa_lang_foreach.m4 b/autoconf/m4/pa_lang_foreach.m4 new file mode 100644 index 0000000..650913d --- /dev/null +++ b/autoconf/m4/pa_lang_foreach.m4 @@ -0,0 +1,15 @@ +dnl -------------------------------------------------------------------------- +dnl PA_LANG_FOREACH(subset, body) +dnl +dnl Expand [body] for each language encountered in the configure script also +dnl present in [subset], or all if [subset] is empty +dnl -------------------------------------------------------------------------- +AC_DEFUN([_PA_LANG_DO],dnl +[AC_LANG([$2])dnl +$1]) + +AC_DEFUN([PA_LANG_FOREACH],dnl +[m4_pushdef([_pa_lang_foreach_current],[_AC_LANG])dnl +m4_map_args([m4_curry([_PA_LANG_DO],[$2])],m4_unquote(PA_LANG_SEEN_LIST($1)))dnl +AC_LANG(_pa_lang_foreach_current)dnl +m4_popdef([_pa_lang_foreach_current])]) diff --git a/autoconf/m4/pa_lang_seen_list.m4 b/autoconf/m4/pa_lang_seen_list.m4 new file mode 100644 index 0000000..d524013 --- /dev/null +++ b/autoconf/m4/pa_lang_seen_list.m4 @@ -0,0 +1,20 @@ +dnl -------------------------------------------------------------------------- +dnl PA_LANG_SEEN_LIST(subset) +dnl +dnl List of the language lang has been used in the configuration +dnl script so far, possibly subset by [subset]. +dnl +dnl This relies on overriding _AC_LANG_SET(from, to), +dnl the internal implementation of _AC_LANG. +dnl -------------------------------------------------------------------------- +m4_ifndef([_PA_LANG_SET], +[m4_rename([_AC_LANG_SET], [_PA_LANG_SET])dnl +m4_defun([_AC_LANG_SET], [m4_set_add([_PA_LANG_SEEN_SET],[$2])dnl +_PA_LANG_SET($@)])]) + +AC_DEFUN([PA_LANG_SEEN_LIST], +[m4_set_delete([_pa_lang_seen_subset])dnl +m4_pushdef([_pa_lang_seen_subset_list],m4_ifnblank([$1],[$1],m4_dquote(m4_set_list([_PA_LANG_SEEN_SET]))))dnl +m4_set_add_all([_pa_lang_seen_subset],_pa_lang_seen_subset_list)dnl +m4_cdr(m4_set_intersection([_pa_lang_seen_subset],[_PA_LANG_SEEN_SET]))dnl +m4_popdef([_pa_lang_seen_subset_list])]) diff --git a/autoconf/m4/pa_option_debug.m4 b/autoconf/m4/pa_option_debug.m4 new file mode 100644 index 0000000..ae7d9db --- /dev/null +++ b/autoconf/m4/pa_option_debug.m4 @@ -0,0 +1,13 @@ +dnl -------------------------------------------------------------------------- +dnl PA_OPTION_DEBUG(with_debug, without_debug) +dnl +dnl Set debug flags and optimization flags depending on if +dnl --enable-debug is set or not. Some flags are set regardless... +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_OPTION_DEBUG], +[PA_ARG_DISABLED([gdb], [disable gdb debug extensions], + [PA_ADD_LANGFLAGS([-g3])], [PA_ADD_LANGFLAGS([-ggdb3 -g3])]) + PA_ARG_ENABLED([debug], [optimize for debugging], + [PA_ADD_LANGFLAGS([-Og -O0]) + $1], + [$2])]) diff --git a/autoconf/m4/pa_option_profiling.m4 b/autoconf/m4/pa_option_profiling.m4 new file mode 100644 index 0000000..39a3f6c --- /dev/null +++ b/autoconf/m4/pa_option_profiling.m4 @@ -0,0 +1,8 @@ +dnl -------------------------------------------------------------------------- +dnl PA_OPTION_PROFILING(with_profiling, without_profiling) +dnl +dnl Try to enable profiling if --enable-profiling is set. +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_OPTION_PROFILING], +[PA_ARG_ENABLED([profiling], [compile with profiling (-pg option)], +[PA_ADD_LANGFLAGS([-pg])])]) diff --git a/autoconf/m4/pa_prog_cc.m4 b/autoconf/m4/pa_prog_cc.m4 new file mode 100644 index 0000000..0debe5d --- /dev/null +++ b/autoconf/m4/pa_prog_cc.m4 @@ -0,0 +1,13 @@ +dnl -------------------------------------------------------------------------- +dnl PA_PROG_CC() +dnl +dnl Similar to AC_PROG_CC, but add a prototype for main() to +dnl AC_INCLUDES_DEFAULT to avoid -Werror from breaking compilation. +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_PROG_CC], +[AC_PROG_CC + AS_IF([test x$ac_cv_prog != xno], + [ac_includes_default="$ac_includes_default +#ifndef __cplusplus +extern int main(void); +#endif"])]) diff --git a/autoconf/m4/pa_search_libs_and_add.m4 b/autoconf/m4/pa_search_libs_and_add.m4 new file mode 100644 index 0000000..b006128 --- /dev/null +++ b/autoconf/m4/pa_search_libs_and_add.m4 @@ -0,0 +1,22 @@ +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/autoconf/m4/pa_sigsetjmp.m4 b/autoconf/m4/pa_sigsetjmp.m4 new file mode 100644 index 0000000..898b562 --- /dev/null +++ b/autoconf/m4/pa_sigsetjmp.m4 @@ -0,0 +1,25 @@ +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_LINK_IFELSE([AC_LANG_SOURCE( +[ +AC_INCLUDES_DEFAULT +#include + +int main(void) { + sigjmp_buf buf; + if (sigsetjmp(buf,1)) + return 0; + siglongjmp(buf,2); + return 1; + } +])], + [AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_SIGSETJMP], 1, + [Define to 1 if your system has sigsetjmp/siglongjmp])], + [AC_MSG_RESULT([no])])]) diff --git a/autoconf/m4/pa_sym.m4 b/autoconf/m4/pa_sym.m4 new file mode 100644 index 0000000..d3a8965 --- /dev/null +++ b/autoconf/m4/pa_sym.m4 @@ -0,0 +1,11 @@ +dnl -------------------------------------------------------------------------- +dnl PA_SYM(prefix, string) +dnl +dnl Convert a (semi-) arbitrary string to a CPP symbol +dnl Compact underscores and convert non-C characters to underscore, +dnl except + which is converted to X (so C++ -> CXX). +dnl -------------------------------------------------------------------------- +AC_DEFUN([PA_SYM], +[m4_bpatsubsts(m4_quote(m4_toupper([$*])), + [,],[],[\+],[X],[[^ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]+],[_],dnl +[^._?\(.*\)_.$],[[\1]])]) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..0c892cc --- /dev/null +++ b/autogen.sh @@ -0,0 +1,83 @@ +#!/bin/sh -x +# +# Run this script to regenerate autoconf files +# +recheck=false +for arg; do + case x"$arg" in + x--recheck) + recheck=true + config=$(sh config.status --config 2>/dev/null) + ;; + x--clearenv) + unset AUTOCONF AUTOMAKE ACLOCAL AUTOHEADER ACLOCAL_PATH + ;; + *) + echo "$0: unknown option: $arg" 1>&2 + ;; + esac +done + +# This allows for overriding the default autoconf programs +AUTOCONF="${AUTOCONF:-${AUTOTOOLS_PREFIX}autoconf}" +AUTOMAKE="${AUTOMAKE:-${AUTOTOOLS_PREFIX}automake}" +ACLOCAL="${ACLOCAL:-${AUTOTOOLS_PREFIX}aclocal}" +AUTOHEADER="${AUTOHEADER:-${AUTOTOOLS_PREFIX}autoheader}" + +mkdir -p autoconf autoconf/helpers config +autolib="`"$AUTOMAKE" --print-libdir`" +if test ! x"$autolib" = x; then + for prg in install-sh compile config.guess config.sub; do + # Update autoconf helpers if and only if newer ones are available + if test -f "$autolib"/"$prg" && \ + ( set -e ; \ + test -f autoconf/helpers/"$prg" && sed -n \ + -e 's/^scriptver=/scriptversion=/' \ + -e 's/^timestamp=/scriptversion=/' \ + -e 's/^scriptversion=['\''"]?\([^'\''"]*\).*$/\1/p' \ + "$autolib"/"$prg" autoconf/helpers/"$prg" | \ + sort -c 2>/dev/null ; \ + test $? -ne 0 ) + then + cp -f "$autolib"/"$prg" autoconf/helpers + fi + done +fi +mv -f autoconf/aclocal.m4 autoconf/aclocal.m4.old +mkdir -p autoconf/m4.old autoconf/m4 +mv -f autoconf/m4/*.m4 autoconf/m4.old/ 2>/dev/null || true +ACLOCAL_PATH="${ACLOCAL_PATH}${ACLOCAL_PATH:+:}`pwd`/autoconf/m4.old" +export ACLOCAL_PATH +"$ACLOCAL" --install --output=autoconf/aclocal.m4 -I autoconf/m4 +if test ! -f autoconf/aclocal.m4; then + # aclocal failed, revert to previous files + mv -f autoconf/m4.old/*.m4 autoconf/m4/ + mv -f autoconf/aclocal.m4.old autoconf/aclocal.m4 + exit 1 +fi +rm -rf autoconf/*m4.old +"$AUTOHEADER" -B autoconf +"$AUTOCONF" -B autoconf +( + echo '#!/bin/sh' + "$AUTOCONF" -B autoconf \ + -t AC_CONFIG_HEADERS:'rm -f $*' \ + -t AC_CONFIG_FILES:'rm -f $*' + echo 'rm -f config.log config.status' + echo 'rm -rf autom4te.cache' +) > autoconf/clean.sh +chmod +x autoconf/clean.sh +sh autoconf/clean.sh + +rm -f configure~ || true + +# Try to regenerate unconfig.h if Perl is available and unconfig.pl +# is present in the autoconf directory. +if [ -n "$(which perl)" -a -f autoconf/unconfig.pl ]; then + perl autoconf/unconfig.pl . config/config.h.in config/unconfig.h +fi + +if $recheck; then + # This bizarre statement has to do with how config.status quotes its output + echo exec sh configure $config | sh - +fi diff --git a/common/Makefile b/common/Makefile new file mode 100644 index 0000000..89e351d --- /dev/null +++ b/common/Makefile @@ -0,0 +1,25 @@ +SRCROOT = .. +VERSION = $(shell cat ../version) + +-include ../config/MCONFIG +include ../MRULES + +OBJS = tftpsubs.$(O) signal.$(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/signal.c b/common/signal.c new file mode 100644 index 0000000..ccd4af8 --- /dev/null +++ b/common/signal.c @@ -0,0 +1,19 @@ +/* + * signal.c + * + * User-friendly wrapper around sigaction(). + */ + +#include "config.h" + +int tftp_signal(int signum, sighandler_t handler, int flags) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = flags; + + return sigaction(signum, &sa, NULL); +} 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/tftp/tftpsubs.h b/common/tftpsubs.h similarity index 55% rename from tftp/tftpsubs.h rename to common/tftpsubs.h index 645c6b9..b3a3bf3 100644 --- a/tftp/tftpsubs.h +++ b/common/tftpsubs.h @@ -1,8 +1,3 @@ -/* $Id$ */ - -/* $OpenBSD: tftpsubs.h,v 1.2 1996/06/26 05:40:37 deraadt Exp $ */ -/* $NetBSD: tftpsubs.h,v 1.2 1994/12/08 09:51:32 jtc Exp $ */ - /* * Copyright (c) 1993 * The Regents of the University of California. All rights reserved. @@ -34,8 +29,6 @@ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. - * - * @(#)tftpsubs.h 8.1 (Berkeley) 6/6/93 */ /* @@ -47,30 +40,82 @@ #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); +void read_ahead(FILE *, int); +int readit(FILE *, struct tftphdr **, int); -int synchnet(int); +int synchnet(int); struct tftphdr *w_init(void); -int write_behind(FILE *, int); -int writeit(FILE *, struct tftphdr **, int, int); +int write_behind(FILE *, int); +int writeit(FILE *, struct tftphdr **, int, int); extern int segsize; #define MAX_SEGSIZE 65464 -/* - * Prototype for xmalloc/xstrdup - */ -extern void *xmalloc(size_t); -extern char *xstrdup(const char *); - -/* - * Signal-related stuff - */ -void (*bsd_signal(int, void (*)(int)))(int); +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 index 7585381..a8c55ac 100644 --- a/config.h +++ b/config.h @@ -1,13 +1,12 @@ /* -*- c -*- ------------------------------------------------------------- * - * - * Copyright 2001 H. Peter Anvin - All Rights Reserved + * + * Copyright 2001-2024 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/. * * ----------------------------------------------------------------------- */ -/* $Id$ */ /* * config.h @@ -18,21 +17,23 @@ #ifndef CONFIG_H #define CONFIG_H 1 +/* Feature enables for specific environments */ +#ifdef __APPLE__ +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1070 +#define __APPLE_USE_RFC_3542 1 +#endif +#endif + /* Must be included before we include any system headers! */ -#include "acconfig.h" - -/* This is necessary on Solaris with gcc */ -#define _XPG4_2 -#define _XOPEN_SOURCE -#define __EXTENSIONS__ - -/* This is necessary on glibc systems */ -#define _BSD_SOURCE -#define _ISO9X_SOURCE +#include "config/config.h" /* autogenerated configuration header */ /* Standard includes */ #include +#include +#include +#include +#include #ifdef HAVE_SYS_TYPES_H #include @@ -42,31 +43,18 @@ #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 +#endif #ifdef HAVE_INTTYPES_H +#ifdef INTTYPES_H_IS_SANE #include +#endif #else #ifdef HAVE_STDINT_H #include @@ -77,9 +65,62 @@ #include #endif +#ifdef HAVE_SETJMP_H #include -#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_GRP_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#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... */ +#if !HAVE_DECL_O_TEXT +#define O_TEXT 0 +#endif +#if !HAVE_DECL_O_BINARY +#define O_BINARY 0 +#endif /* If we don't have intmax_t, try creating it */ @@ -87,9 +128,9 @@ #ifdef HAVE_LONG_LONG typedef long long intmax_t; typedef unsigned long long uintmax_t; -#define PRIdMAX "Ld" -#define PRIuMAX "Lu" -#define PRIxMAX "Lx" +#define PRIdMAX "lld" +#define PRIuMAX "llu" +#define PRIxMAX "llx" #define INTMAX_C(x) (x##LL) #define UINTMAX_C(x) (x##ULL) #else @@ -103,6 +144,37 @@ typedef unsigned long uintmax_t; #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) @@ -132,6 +204,11 @@ typedef unsigned long u_long; #endif #endif +/* socklen_t */ +#ifndef HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + /* sysexits.h */ #ifdef HAVE_SYSEXITS_H @@ -172,12 +249,118 @@ typedef unsigned long u_long; #endif #endif -/* Sometimes IPPORT_TFTP isn't defined */ +/* netinet/in.h, and possible missing pieces */ -#ifndef HAVE_IPPORT_TFTP_DEFINITION -#ifndef IPPORT_TFTP +#include + +#if !HAVE_DECL_IPPORT_TFTP && !defined(IPPORT_TFTP) #define IPPORT_TFTP 69 #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_SIGHANDLER_T +typedef void (*sighandler_t)(int); +#endif +int tftp_signal(int, sighandler_t, int); + +#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/MCONFIG.in b/config/MCONFIG.in similarity index 73% rename from MCONFIG.in rename to config/MCONFIG.in index d9d5718..2660f7e 100644 --- a/MCONFIG.in +++ b/config/MCONFIG.in @@ -1,13 +1,12 @@ ## -*- makefile -*- ------------------------------------------------------ ## -## Copyright 2001 H. Peter Anvin - All Rights Reserved +## 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/. ## ## ----------------------------------------------------------------------- -## $Id$ ## ## MCONFIG.in @@ -15,6 +14,10 @@ ## Basic Makefile definitions ## +# Source and object root +SRCROOT = @SRCROOT@ +OBJROOT = @OBJROOT@ + # Prefixes prefix = @prefix@ exec_prefix = @exec_prefix@ @@ -28,9 +31,19 @@ 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@ @@ -43,8 +56,9 @@ CFLAGS = @CFLAGS@ -I$(SRCROOT) # Link flags LDFLAGS = @LDFLAGS@ -# Libraries -LIBS = @LIBS@ +# 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@ diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..deaa4fc --- /dev/null +++ b/configure.ac @@ -0,0 +1,283 @@ +dnl Process this file with autoconf 2.71 or later to produce +dnl a configure script. +AC_PREREQ([2.71]) +AC_INIT +AC_CONFIG_SRCDIR([MRULES]) +AC_PREFIX_DEFAULT([/usr]) +AC_CONFIG_AUX_DIR([autoconf/helpers]) + +dnl This prevents us from running Wine and thinking we are not +dnl cross-compiling when in fact we are; running Wine here is at +dnl the best very slow and doesn't buy us a single thing at all. +PA_CROSS_COMPILE + +dnl Enable any available C extensions +PA_PROG_CC +AC_USE_SYSTEM_EXTENSIONS + +dnl Options for debugging and profiling +PA_OPTION_DEBUG +PA_OPTION_PROFILING + +dnl LLVM doesn't error out on invalid -W options unless this option is +dnl specified first. Enable this so this script can actually discover +dnl which -W options are possible for this compiler. +PA_ADD_CFLAGS([-Werror=unknown-warning-option]) + +dnl Force gcc and gcc-compatible compilers treat signed integers +dnl as 2's complement +PA_ADD_CFLAGS([-fwrapv]) + +dnl Force clang to behave in a predictable manner, in order to make bugs +dnl possible to track down. gcc appears to have this behavior by default. +PA_ADD_CFLAGS([-ftrivial-auto-var-init=zero]) + +dnl Some environments abuse __STRICT_ANSI__ to disable some +dnl function declarations +PA_ADD_CFLAGS([-U__STRICT_ANSI__]) + +dnl Don't put things in common if we can avoid it. We don't want to +dnl assume all compilers support common, and this will help find those +dnl problems. This also works around an OSX linker problem. +PA_ADD_CFLAGS([-fno-common]) + +dnl Tests which may trigger warnings on some compilers +AC_C_CONST +AC_C_INLINE +AC_C_RESTRICT + +dnl Checks for header files. +AC_CHECK_INCLUDES_DEFAULT + +dnl See if we need extra libraries +XTRA=false + +AC_SEARCH_LIBS([strerror],[cposix]) + +AC_CHECK_HEADERS_ONCE(inttypes.h) +AC_CHECK_HEADERS_ONCE(stdint.h) +AC_CHECK_HEADERS_ONCE(grp.h) +AC_CHECK_HEADERS_ONCE(libgen.h) +AC_CHECK_HEADERS_ONCE(setjmp.h) +AC_CHECK_HEADERS_ONCE(strings.h) +AC_CHECK_HEADERS_ONCE(sysexits.h) +AC_CHECK_HEADERS_ONCE(unistd.h) +AC_CHECK_HEADERS_ONCE(sys/filio.h) +AC_CHECK_HEADERS_ONCE(sys/stat.h) +AC_CHECK_HEADERS_ONCE(sys/time.h) +PA_CHECK_INTTYPES_H_SANE + +dnl This is needed on some versions of FreeBSD... +AC_CHECK_HEADERS_ONCE(machine/param.h) + +dnl Windows... +PA_ADD_HEADERS(windows.h) +PA_ADD_HEADERS(winsock2.h) +AS_IF([test "x$ac_cv_header_winsock2_h" != xyes], + [PA_ADD_HEADERS(winsock.h)]) + +PA_ADD_HEADERS(fcntl.h) +PA_ADD_HEADERS(sys/types.h) +PA_ADD_HEADERS(arpa/inet.h) +PA_ADD_HEADERS(sys/socket.h) +PA_ADD_HEADERS(sys/file.h) +PA_ADD_HEADERS(netinet/in.h) +PA_ADD_HEADERS(sys/uio.h) +PA_ADD_HEADERS(netdb.h) + +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) + +AC_CHECK_TYPES(socklen_t) + +AC_SEARCH_LIBS(socket, [socket ws2_32 wsock32], , + [AC_MSG_ERROR(socket library not found)]) + +AC_CHECK_FUNCS(fcntl) +AC_CHECK_FUNCS(flock) +AC_CHECK_FUNCS(setsid) +AC_CHECK_FUNCS(recvmsg) +AC_CHECK_FUNCS(ftruncate) +AC_CHECK_FUNCS(setresuid) +AC_CHECK_FUNCS(setreuid) +AC_CHECK_FUNCS(setresgid) +AC_CHECK_FUNCS(setregid) +AC_CHECK_FUNCS(initgroups) +AC_CHECK_FUNCS(setgroups) +AC_CHECK_TYPES(sighandler_t) + +dnl Solaris 8 has [u]intmax_t but not strtoumax(). How utterly braindamaged. +AC_CHECK_FUNCS(strtoumax) +AC_CHECK_FUNCS(strtoull) + +AC_CHECK_MEMBERS(struct msghdr.msg_control) +AC_CHECK_MEMBERS(struct in_pktinfo.ipi_addr) +AC_CHECK_MEMBERS(struct addrinfo.ai_addr) + +AC_CHECK_DECLS([O_NONBLOCK, O_BINARY, O_TEXT]) +AC_CHECK_DECLS([F_SETLK]) +AC_CHECK_DECLS([LOCK_SH, LOCK_EX]) + +PA_SIGSETJMP + +dnl +dnl Get common paths +dnl +SRCROOT=`cd $srcdir && pwd` +OBJROOT=`pwd` + +PA_SEARCH_LIBS_AND_ADD(xmalloc, iberty) +PA_SEARCH_LIBS_AND_ADD(xstrdup, iberty) +PA_SEARCH_LIBS_AND_ADD(getopt_long, getopt, getopt_long) +PA_SEARCH_LIBS_AND_ADD(getaddrinfo, [nsl resolv]) +AS_IF([$pa_add_getaddrinfo], +[AC_SEARCH_LIBS(gethostbyname, [nsl resolv], + [AC_SEARCH_LIBS(herror, [nsl resolv], , + [AC_MSG_ERROR(herror not found)])], + [AC_MSG_ERROR(gethostbyname not found)])], +[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)])]) + +PA_SEARCH_LIBS_AND_ADD(inet_ntop, [nsl resolv]) +AS_IF([$pa_add_inet_ntop], + [AC_SEARCH_LIBS(inet_ntoa, [nsl resolv], , + [AC_MSG_ERROR(inet_ntoa not found)])]) + +AC_SEARCH_LIBS(inet_aton, [nsl resolv], ,[AC_MSG_ERROR(inet_aton not found)]) + +PA_SEARCH_LIBS_AND_ADD(daemon) +PA_SEARCH_LIBS_AND_ADD(dup2) + +AS_IF([$XTRA], [XTRALIBS="$OBJROOT/lib/libxtra.a $XTRALIBS"]) + +dnl +dnl These libraries apply to the server only +dnl + +common_libs="$LIBS" + +AC_CHECK_DECLS(IPPORT_TFTP) + +PA_ARG_DISABLED([tcpwrappers], + [disable tcpwrapper permissions checking], [], + [ + AC_SEARCH_LIBS(yp_get_default_domain, [nsl resolv]) + PA_HAVE_TCPWRAPPERS + ]) + +AC_CHECK_HEADERS_ONCE([regex.h]) +PA_ARG_DISABLED([remap], + [disable regex-based filename remapping], [], + [AS_IF([test x"$ac_cv_header_regex_h" = xyes], + [AC_SEARCH_LIBS(regcomp, [regex rx], + [AC_DEFINE([WITH_REGEX], 1, + [Define if we are compiling with regex filename remapping.]) + TFTPDOBJS="remap.\$(O) $TFTPOBJS"])])]) + +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_ARG_DISABLED([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 + +AC_CHECK_MEMBERS(struct sockaddr_in6.sin6_addr) +AC_MSG_CHECKING([for IPv6 support]) +PA_ARG_DISABLED([ipv6], + [disable support for IPv6], + [AC_MSG_RESULT(disabled)], + [AS_IF([test x"$ac_cv_member_struct_sockaddr_in6_sin6_addr$ac_cv_member_struct_addrinfo_ai_addr" = xyesyes], + [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_IPV6, 1, [define if IPv6 support is enabled])], + [AC_MSG_RESULT(no) + AC_MSG_WARN([*** we do not have required IPv6 structs - IPv6 will be 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] + +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(-fno-strict-aliasing) + +dnl +dnl Test compiler features. On some compilers, this can be affected +dnl by -Werror options, so run this *after* those options are added. +dnl +PA_CHECK_BAD_STDC_INLINE +PA_C_TYPEOF + +AC_CONFIG_HEADERS([config/config.h]) +AC_CONFIG_FILES([config/MCONFIG]) +AC_OUTPUT diff --git a/configure.in b/configure.in deleted file mode 100644 index e76decf..0000000 --- a/configure.in +++ /dev/null @@ -1,141 +0,0 @@ -dnl -dnl autoconf input file to generate MCONFIG -dnl - -AC_PREREQ(2.52) -AC_REVISION([$Id$]) -AC_INIT(MCONFIG.in) -AC_PREFIX_DEFAULT(/usr) - -AC_ISC_POSIX -AC_AIX -AC_MINIX -AC_PROG_CC -AC_C_CONST -AC_C_INLINE - -AC_SYS_LARGEFILE - -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) - -AC_HEADER_STDC -AC_CHECK_HEADERS(inttypes.h) -AC_CHECK_HEADERS(libgen.h) -AC_CHECK_HEADERS(memory.h) -AC_CHECK_HEADERS(stddef.h) -AC_CHECK_HEADERS(stdint.h) -AC_CHECK_HEADERS(stdlib.h) -AC_CHECK_HEADERS(string.h) -AC_CHECK_HEADERS(strings.h) -AC_CHECK_HEADERS(sysexits.h) -AC_CHECK_HEADERS(unistd.h) -AC_CHECK_HEADERS(sys/filio.h) -AC_CHECK_HEADERS(sys/stat.h) -AC_CHECK_HEADERS(sys/types.h) -dnl This is needed on some versions of FreeBSD... -AC_CHECK_HEADERS(machine/param.h) - -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) -AC_TYPE_PID_T - -AC_SEARCH_LIBS(socket, socket, , [AC_MSG_ERROR(socket library not found)]) -AC_SEARCH_LIBS(gethostbyname, [nsl resolv], , [AC_MSG_ERROR(gethostbyname not found)]) -AC_SEARCH_LIBS(inet_aton, [nsl resolv], , [AC_MSG_ERROR(inet_aton not found)]) -AC_SEARCH_LIBS(herror, [nsl resolv], , [AC_MSG_ERROR(herror not found)]) - -AC_CHECK_FUNCS(setsid) -AC_CHECK_FUNCS(recvmsg) -AC_CHECK_FUNCS(setreuid) -AC_CHECK_FUNCS(setregid) -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 - - -AH_TEMPLATE([HAVE_IPPORT_TFTP_DEFINITION], -[Define if netinet/in.h defines IPPORT_TFTP.]) -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.o $TFTPDOBJS" - ]) - ]) -],:) - -AH_TEMPLATE([WITH_READLINE], -[Define if we are compiling with readline command-line editing.]) - -PA_WITH_BOOL(readline, 1, -[ --without-readline disable the use of readline command-line editing], -[ - USE_READLINE=true - AC_CHECK_HEADER(readline/readline.h, [], [USE_READLINE=false]) - AC_CHECK_HEADER(readline/history.h, [], [USE_READLINE=false]) - if $USE_READLINE - then - AC_SEARCH_LIBS(readline, [readline history], - [ - AC_DEFINE(WITH_READLINE) - ]) - fi -],:) - -AH_TEMPLATE([HAVE_SIGSETJMP], -[Define if we have sigsetjmp, siglongjmp and sigjmp_buf.]) -PA_SIGSETJMP([AC_DEFINE(HAVE_SIGSETJMP)]) - -LIBXTRA=false -AC_SEARCH_LIBS(xmalloc, iberty, , LIBXTRA=true LIBOBJS="$LIBOBJS xmalloc.o") -AC_SEARCH_LIBS(xstrdup, iberty, , LIBXTRA=true LIBOBJS="$LIBOBJS xstrdup.o") -AC_SEARCH_LIBS(bsd_signal, bsd, , LIBXTRA=true LIBOBJS="$LIBOBJS bsdsignal.o") -if $LIBXTRA; then - LIBS="../lib/libxtra.a $LIBS" -fi - -AC_SUBST(LIBOBJS) -AC_SUBST(TFTPDOBJS) - -AC_PROG_RANLIB -AC_PROG_INSTALL - -AC_CONFIG_HEADER(acconfig.h) -AC_OUTPUT(MCONFIG) diff --git a/lib/Makefile b/lib/Makefile index 4e20ad3..a7fd057 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -4,15 +4,19 @@ SRCROOT = .. --include ../MCONFIG +-include ../config/MCONFIG include ../MRULES +ifeq ($(LIBOBJS),) +all: +else all: libxtra.a +endif install: clean: - -rm -f *.a *.o + -rm -f *.a *.o *.obj *.exe distclean: clean -rm -f *~ @@ -21,5 +25,3 @@ libxtra.a: $(LIBOBJS) -rm -f libxtra.a $(AR) libxtra.a $(LIBOBJS) $(RANLIB) libxtra.a - - diff --git a/lib/bsdsignal.c b/lib/bsdsignal.c deleted file mode 100644 index bc3ceb5..0000000 --- a/lib/bsdsignal.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * bsdsignal.c - * - * Use sigaction() to simulate BSD signal() - */ - -#include -#include -#include - -void (*bsd_signal(int, void (*)(int)))(int); - -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 index 6e8ae24..30704f3 100644 --- a/lib/xmalloc.c +++ b/lib/xmalloc.c @@ -5,17 +5,16 @@ * */ -#include -#include +#include "config.h" void *xmalloc(size_t size) { - void *p = malloc(size); + void *p = malloc(size); - if ( !p ) { - fprintf(stderr, "Out of memory!\n"); - exit(128); - } + if (!p) { + fprintf(stderr, "Out of memory!\n"); + exit(128); + } - return p; + return p; } diff --git a/lib/xstrdup.c b/lib/xstrdup.c index 5d65b7e..05e3054 100644 --- a/lib/xstrdup.c +++ b/lib/xstrdup.c @@ -5,18 +5,16 @@ * */ -#include -#include -#include +#include "config.h" char *xstrdup(const char *s) { - char *p = strdup(s); + char *p = strdup(s); - if ( !p ) { - fprintf(stderr, "Out of memory!\n"); - exit(128); - } + if (!p) { + fprintf(stderr, "Out of memory!\n"); + exit(128); + } - return p; + return p; } diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..1f72922 --- /dev/null +++ b/release.sh @@ -0,0 +1,57 @@ +#!/bin/sh -xe +# +# Script for generating a release +# + +PACKAGE=tftp-hpa + +if [ -z "$1" ]; then + echo "Usage: $0 release-id" 1>&2 + exit 1 +fi + +release="$1" +releasetag=$PACKAGE-$release +releasedir=$PACKAGE-$release + +GIT_DIR=`cd "${GIT_DIR-.git}" && pwd` +export GIT_DIR + +if [ `git diff --cached | wc -l` -ne 0 ]; then + echo "$0: index not clean" 1>&2 + exit 1 +fi + +if [ x"$release" = x'test' ]; then + release=`cat version` + releasetag=HEAD + releasedir=$PACKAGE-$release +else + echo $release > version + if [ `git diff version | wc -l` -ne 0 ]; then + git add version + git commit -m "Update version for release $release" version + else + git checkout version + fi + rm -f "$GIT_DIR"/refs/tags/$releasetag + git tag -a -m "$releasetag" -f "$releasetag" +fi + +here=`pwd` + +tmpdir=/var/tmp/release.$$ +rm -rf $tmpdir +mkdir -p $tmpdir +cd $tmpdir +mkdir -p $releasedir +git archive --format=tar $releasetag | tar -xf - -C $releasedir +cd $releasedir +make release +rm -f release.sh +cd .. +tar cvvf $releasedir.tar $releasedir +gzip -9 $releasedir.tar +mv -f $releasedir.tar.gz $here/.. +cd .. +rm -rf $tmpdir 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.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 index ca91196..9faa62f 100644 --- a/tftp/Makefile +++ b/tftp/Makefile @@ -1,24 +1,28 @@ -all: tftp - SRCROOT = .. +VERSION = $(shell cat ../version) --include ../MCONFIG +-include ../config/MCONFIG include ../MRULES -OBJS = tftp.o main.o tftpsubs.o +OBJS = tftp.$(O) main.$(O) -tftp: $(OBJS) - $(CC) $(LDFLAGS) $^ $(LIBS) -o $@ +all: tftp$(X) tftp.1 -$(OBJS): tftpsubs.h +tftp$(X): $(OBJS) + $(CC) $(LDFLAGS) $^ $(TFTP_LIBS) -o $@ -install: tftp +$(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) -s tftp $(INSTALLROOT)$(BINDIR) - $(INSTALL_DATA) tftp.1 $(INSTALLROOT)$(MANDIR)/man1 + $(INSTALL_PROGRAM) tftp$(X) $(INSTALLROOT)$(BINDIR) + $(INSTALL_DATA) tftp.1 $(INSTALLROOT)$(MANDIR)/man1 clean: - rm -f *.o tftp + rm -f *.o *.obj *.exe tftp tftp.1 distclean: clean rm -f *~ diff --git a/tftp/extern.h b/tftp/extern.h index 60ad0e9..9c578e6 100644 --- a/tftp/extern.h +++ b/tftp/extern.h @@ -1,8 +1,3 @@ -/* $Id$ */ - -/* $OpenBSD: extern.h,v 1.2 1996/06/26 05:40:33 deraadt Exp $ */ -/* $NetBSD: extern.h,v 1.2 1994/12/08 09:51:24 jtc Exp $ */ - /* * Copyright (c) 1993 * The Regents of the University of California. All rights reserved. @@ -34,14 +29,15 @@ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. - * - * @(#)extern.h 8.1 (Berkeley) 6/6/93 */ -#ifndef RECVFILE_H -#define RECVFILE_H +#ifndef EXTERN_H +#define EXTERN_H -void tftp_recvfile (int, char *, char *); -void tftp_sendfile (int, char *, char *); +#include "config.h" + +void tftp_recvfile(int, const char *, const char *); +void tftp_sendfile(int, const char *, const char *); +extern sigjmp_buf toplevel; #endif diff --git a/tftp/main.c b/tftp/main.c index 7ff7d2e..ecdc0e0 100644 --- a/tftp/main.c +++ b/tftp/main.c @@ -1,6 +1,3 @@ -/* $OpenBSD: main.c,v 1.4 1997/01/17 07:13:30 millert Exp $ */ -/* $NetBSD: main.c,v 1.6 1995/05/21 16:54:10 mycroft Exp $ */ - /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. @@ -34,745 +31,916 @@ * SUCH DAMAGE. */ -#include "tftpsubs.h" - -#ifndef lint -static const char *copyright UNUSED = -"@(#) Copyright (c) 1983, 1993\n\ - The Regents of the University of California. All rights reserved.\n"; -/* static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/6/93"; */ -/* static char rcsid[] = "$OpenBSD: main.c,v 1.4 1997/01/17 07:13:30 millert Exp $"; */ -static const char *rcsid UNUSED = - "tftp-hpa $Id$"; -#endif /* not lint */ +#include "common/tftpsubs.h" /* Many bug fixes are from Jim Guyton */ /* * TFTP User Program -- Command Interface. */ -#include -#include #include - -#include - -#include - #include -#include -#include -#include -#include -#include -#include -#include -#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 */ +#define TIMEOUT 5 /* secs between rexmt's */ +#define LBUFLEN 200 /* size of input buffer */ -struct sockaddr_in peeraddr; -int f; -short port; -int trace; -int verbose; -int connected; -char mode[32]; -#ifdef WITH_READLINE -char *line = NULL; +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 -char line[LBUFLEN]; +int ai_fam = AF_INET; +int ai_fam_sock = AF_INET; #endif -int margc; -char *margv[20]; + +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; +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 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 command(void); -static void getusage (char *); -static void makeargv (void); -static void putusage (char *); -static void settftpmode (const char *); +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 **); + 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 }, - { "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 } + {"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 *); +struct cmd *getcmd(char *); +char *tail(char *); char *xstrdup(const char *); -int -main(int argc, char *argv[]) +const char *program; + +static void usage(int errcode) { - struct sockaddr_in s_in; - - sp = getservbyname("tftp", "udp"); - if (sp == 0) { - fprintf(stderr, "tftp: udp/tftp: unknown service\n"); - exit(1); - } - f = socket(AF_INET, SOCK_DGRAM, 0); - if (f < 0) { - perror("tftp: socket"); - exit(3); - } - bzero((char *)&s_in, sizeof (s_in)); - s_in.sin_family = AF_INET; - if (bind(f, (struct sockaddr *)&s_in, sizeof (s_in)) < 0) { - perror("tftp: bind"); - exit(1); - } - strcpy(mode, "netascii"); - bsd_signal(SIGINT, intr); - if (argc > 1) { - if (sigsetjmp(toplevel,1) != 0) - exit(0); - setpeer(argc, argv); - } - if (sigsetjmp(toplevel,1) != 0) - (void)putchar('\n'); - -#ifdef WITH_READLINE - using_history(); + 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 - - command(); - - return 0; /* Never reached */ + program); + exit(errcode); } -char *hostname; +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"; + } + + tftp_signal(SIGINT, intr, 0); + + 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) +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); + char *eline; + int len, elen; - if (line) - free(line); - line = xmalloc(len+elen+1); - strcpy(line, partial); - strcpy(line+len, eline); + len = strlen(partial); + eline = readline(mprompt); + if (!eline) + exit(0); /* EOF */ - add_history(line); + 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 */ + 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[]) +void setpeer(int argc, char *argv[]) { - struct hostent *host; + 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; - } - if (inet_aton(argv[1], &peeraddr.sin_addr) != 0) { - peeraddr.sin_family = AF_INET; - hostname = xstrdup(argv[1]); - } else { - host = gethostbyname(argv[1]); - if (host == 0) { - connected = 0; - printf("%s: unknown host\n", argv[1]); - return; - } - peeraddr.sin_family = host->h_addrtype; - bcopy(host->h_addr, &peeraddr.sin_addr, host->h_length); - hostname = xstrdup(host->h_name); - } - port = sp->s_port; - if (argc == 3) { - port = atoi(argv[2]); - if (port < 0) { - printf("%s: bad port number\n", argv[2]); - connected = 0; - return; - } - port = htons(port); - } - connected = 1; + 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; } -struct modes { - const char *m_name; - const char *m_mode; -} modes[] = { - { "ascii", "netascii" }, - { "netascii", "netascii" }, - { "binary", "octet" }, - { "image", "octet" }, - { "octet", "octet" }, -/* { "mail", "mail" }, */ - { 0, 0 } -}; - -void -modecmd(int argc, char *argv[]) +void modecmd(int argc, char *argv[]) { - struct modes *p; - const char *sep; + const struct modes *p; + const char *sep; - if (argc < 2) { - printf("Using %s mode to transfer files.\n", mode); - return; - } - if (argc == 2) { - for (p = modes; p->m_name; p++) - if (strcmp(argv[1], p->m_name) == 0) - break; - if (p->m_name) { - settftpmode(p->m_mode); - return; - } - printf("%s: unknown mode\n", argv[1]); - /* drop through and print usage message */ - } + 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; + 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("octet"); -} - -void -setascii(int argc, char *argv[]) +void setbinary(int argc, char *argv[]) { - (void)argc; (void)argv; /* Quiet unused warning */ - settftpmode("netascii"); + (void)argc; + (void)argv; /* Quiet unused warning */ + settftpmode(MODE_OCTET); } -static void -settftpmode(const char *newmode) +void setascii(int argc, char *argv[]) { - strcpy(mode, newmode); - if (verbose) - printf("mode set to %s\n", mode); + (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[]) +void put(int argc, char *argv[]) { - int fd; - int n; - char *cp, *targ; + 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 (strchr(argv[argc - 1], ':')) { - struct hostent *hp; - - for (n = 1; n < argc - 1; n++) - if (strchr(argv[n], ':')) { - putusage(argv[0]); - return; - } - cp = argv[argc - 1]; - targ = strchr(cp, ':'); - *targ++ = 0; - hp = gethostbyname(cp); - if (hp == NULL) { - fprintf(stderr, "tftp: %s: ", cp); - herror((char *)NULL); - return; - } - bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr, hp->h_length); - peeraddr.sin_family = hp->h_addrtype; - connected = 1; - hostname = xstrdup(hp->h_name); - } - if (!connected) { - printf("No target machine specified.\n"); - return; - } - if (argc < 4) { - cp = argc == 2 ? tail(targ) : argv[1]; - fd = open(cp, O_RDONLY); - if (fd < 0) { - fprintf(stderr, "tftp: "); perror(cp); - return; - } - if (verbose) - printf("putting %s to %s:%s [%s]\n", - cp, hostname, targ, mode); - peeraddr.sin_port = port; - tftp_sendfile(fd, targ, mode); - return; - } - /* this assumes the target is a directory */ - /* on a remote unix system. hmmmm. */ - cp = strchr(targ, '\0'); - *cp++ = '/'; - for (n = 1; n < argc - 1; n++) { - strcpy(cp, tail(argv[n])); - fd = open(argv[n], O_RDONLY); - if (fd < 0) { - fprintf(stderr, "tftp: "); perror(argv[n]); - continue; - } - if (verbose) - printf("putting %s to %s:%s [%s]\n", - argv[n], hostname, targ, mode); - peeraddr.sin_port = port; - tftp_sendfile(fd, targ, mode); - } + 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) +static void putusage(char *s) { - printf("usage: %s file ... host:target, or\n", s); - printf(" %s file ... target (when already connected)\n", 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[]) +void get(int argc, char *argv[]) { - int fd; - int n; - char *cp; - char *src; + 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 (strchr(argv[n], ':') == 0) { - getusage(argv[0]); - return; - } - } - for (n = 1; n < argc ; n++) { - src = strchr(argv[n], ':'); - if (src == NULL) - src = argv[n]; - else { - struct hostent *hp; + 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; - hp = gethostbyname(argv[n]); - if (hp == NULL) { - fprintf(stderr, "tftp: %s: ", argv[n]); - herror((char *)NULL); - continue; - } - bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr, - hp->h_length); - peeraddr.sin_family = hp->h_addrtype; - connected = 1; - hostname = xstrdup(hp->h_name); - } - if (argc < 4) { - cp = argc == 3 ? argv[2] : tail(src); - fd = creat(cp, 0644); - if (fd < 0) { - fprintf(stderr, "tftp: "); perror(cp); - return; - } - if (verbose) - printf("getting from %s:%s to %s [%s]\n", - hostname, src, cp, mode); - peeraddr.sin_port = port; - tftp_recvfile(fd, src, mode); - break; - } - cp = tail(src); /* new .. jdg */ - fd = creat(cp, 0644); - if (fd < 0) { - fprintf(stderr, "tftp: "); perror(cp); - continue; - } - if (verbose) - printf("getting from %s:%s to %s [%s]\n", - hostname, src, cp, mode); - peeraddr.sin_port = port; - tftp_recvfile(fd, src, mode); - } + *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) +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); + printf("usage: %s host:file host:file ... file, or\n", s); + printf(" %s file file ... file if connected\n", s); } -int rexmtval = TIMEOUT; +int rexmtval = TIMEOUT; -void -setrexmt(int argc, char *argv[]) +void setrexmt(int argc, char *argv[]) { - int t; + 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; + 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; +int maxtimeout = 5 * TIMEOUT; -void -settimeout(int argc, char *argv[]) +void settimeout(int argc, char *argv[]) { - int t; + 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; + 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 -status(int argc, char *argv[]) +void setliteral(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\n", mode, - verbose ? "on" : "off", trace ? "on" : "off"); - printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n", - rexmtval, maxtimeout); + (void)argc; + (void)argv; /* Quiet unused warning */ + literal = !literal; + printf("Literal mode %s.\n", literal ? "on" : "off"); } -void -intr(int sig) +void status(int argc, char *argv[]) { - (void)sig; /* Quiet unused warning */ - - bsd_signal(SIGALRM, SIG_IGN); - alarm(0); - siglongjmp(toplevel, -1); + (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); } -char * -tail(char *filename) +void intr(int sig) { - char *s; - - while (*filename) { - s = strrchr(filename, '/'); - if (s == NULL) - break; - if (s[1]) - return (s + 1); - *s = '\0'; - } - return (filename); + (void)sig; /* Quiet unused warning */ + + alarm(0); + tftp_signal(SIGALRM, SIG_DFL, 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) +static void command(void) { - struct cmd *c; + struct cmd *c; - for (;;) { + for (;;) { #ifdef WITH_READLINE - if ( line ) - free(line); - line = readline(prompt); - if ( !line ) - exit(0); /* EOF */ + 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; - } - } + fputs(prompt, stdout); + fflush(stdout); + if (fgets(line, LBUFLEN, stdin) == 0) { + if (feof(stdin)) { + exit(0); + } else { + continue; + } + } #endif - if ((line[0] == 0) || (line[0] == '\n')) - continue; + if ((line[0] == 0) || (line[0] == '\n')) + continue; #ifdef WITH_READLINE - add_history(line); +#ifdef HAVE_READLINE_HISTORY_H + add_history(line); #endif - makeargv(); - if (margc == 0) - continue; +#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); - } + 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) +struct cmd *getcmd(char *name) { - const char *p; - char *q; - struct cmd *c, *found; - int nmatches, longest; + 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); + 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) +static void makeargv(void) { - char *cp; - char **argp = margv; + 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; + 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 quit(int argc, char *argv[]) { - (void)argc; (void)argv; /* Quiet unused warning */ - exit(0); + (void)argc; + (void)argv; /* Quiet unused warning */ + exit(0); } /* * Help command. */ -void -help(int argc, char *argv[]) +void help(int argc, char *argv[]) { - struct cmd *c; + struct cmd *c; - if (argc == 1) { - printf("Commands may be abbreviated. Commands are:\n\n"); - for (c = cmdtab; c->name; c++) - printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help); - return; - } - while (--argc > 0) { - char *arg; - arg = *++argv; - c = getcmd(arg); - if (c == (struct cmd *)-1) - printf("?Ambiguous help command %s\n", arg); - else if (c == (struct cmd *)0) - printf("?Invalid help command %s\n", arg); - else - printf("%s\n", c->help); - } + 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 settrace(int argc, char *argv[]) { - (void)argc; (void)argv; /* Quiet unused warning */ + (void)argc; + (void)argv; /* Quiet unused warning */ - trace = !trace; - printf("Packet tracing %s.\n", trace ? "on" : "off"); + trace = !trace; + printf("Packet tracing %s.\n", trace ? "on" : "off"); } -void -setverbose(int argc, char *argv[]) +void setverbose(int argc, char *argv[]) { - (void)argc; (void)argv; /* Quiet unused warning */ + (void)argc; + (void)argv; /* Quiet unused warning */ - verbose = !verbose; - printf("Verbose mode %s.\n", verbose ? "on" : "off"); + verbose = !verbose; + printf("Verbose mode %s.\n", verbose ? "on" : "off"); } diff --git a/tftp/tftp.1 b/tftp/tftp.1 deleted file mode 100644 index 41685c8..0000000 --- a/tftp/tftp.1 +++ /dev/null @@ -1,177 +0,0 @@ -.\" $OpenBSD: tftp.1,v 1.4 1999/06/05 01:21:43 aaron Exp $ -.\" $NetBSD: tftp.1,v 1.5 1995/08/18 14:45:44 pk Exp $ -.\" -.\" Copyright (c) 1990, 1993, 1994 -.\" The Regents of the University of California. All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in the -.\" documentation and/or other materials provided with the distribution. -.\" 3. All advertising materials mentioning features or use of this software -.\" must display the following acknowledgement: -.\" This product includes software developed by the University of -.\" California, Berkeley and its contributors. -.\" 4. Neither the name of the University nor the names of its contributors -.\" may be used to endorse or promote products derived from this software -.\" without specific prior written permission. -.\" -.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -.\" SUCH DAMAGE. -.\" -.\" @(#)tftp.1 8.2 (Berkeley) 4/18/94 -.\" -.Dd April 18, 1994 -.Dt TFTP 1 -.Os -.Sh NAME -.Nm tftp -.Nd trivial file transfer program -.Sh SYNOPSIS -.Nm tftp -.Op Ar host -.Sh DESCRIPTION -.Nm tftp -is the user interface to the Internet -.Tn TFTP -(Trivial File Transfer Protocol), -which allows users to transfer files to and from a remote machine. -The remote -.Ar host -may be specified on the command line, in which case -.Nm tftp -uses -.Ar host -as the default host for future transfers (see the -.Ic connect -command below). -.Sh COMMANDS -Once -.Nm tftp -is running, it issues the prompt -.Ql tftp> -and recognizes the following commands: -.Pp -.Bl -tag -width verbose -compact -.It Ic \&? Ar command-name Op Ar ... -Print help information. -.Pp -.It Ic ascii -Shorthand for -.Ic mode ascii . -.Pp -.It Ic binary -Shorthand for -.Ic mode binary . -.Pp -.It Ic connect Ar host Op Ar port -Set the -.Ar host -(and optionally -.Ar port ) -for transfers. -Note that the -.Tn TFTP -protocol, unlike the -.Tn FTP -protocol, -does not maintain connections between transfers; thus, the -.Ic connect -command does not actually create a connection, -but merely remembers what host is to be used for transfers. -You do not have to use the -.Ic connect -command; the remote host can be specified as part of the -.Ic get -or -.Ic put -commands. -.Pp -.It Ic get Ar filename -.It Ic get Ar remotename localname -.It Ic get Ar file Op Ar ... -Get a file or set of files from the specified -.Ar sources . -.Ar source -can be in one of two forms: -a filename on the remote host, if the host has already been specified, -or a string of the form -.Ar hosts:filename -to specify both a host and filename at the same time. -If the latter form is used, -the last hostname specified becomes the default for future transfers. -.Pp -.It Ic mode Ar transfer-mode -Set the mode for transfers; -.Ar transfer-mode -may be one of -.Ic ascii -or -.Ic binary . -The default is -.Ic ascii . -.Pp -.It Ic put Ar file -.It Ic put Ar localfile remotefile -.It Ic put Ar file1 file2 ... fileN remote-directory -Put a file or set of files to the specified -remote file or directory. -The destination -can be in one of two forms: -a filename on the remote host, if the host has already been specified, -or a string of the form -.Ar hosts:filename -to specify both a host and filename at the same time. -If the latter form is used, -the hostname specified becomes the default for future transfers. -If the remote-directory form is used, the remote host is -assumed to be a -.Tn UNIX -machine. -.Pp -.It Ic quit -Exit -.Nm tftp . -An end-of-file also exits. -.Pp -.It Ic rexmt Ar retransmission-timeout -Set the per-packet retransmission timeout, in seconds. -.Pp -.It Ic status -Show current status. -.Pp -.It Ic timeout Ar total-transmission-timeout -Set the total transmission timeout, in seconds. -.Pp -.It Ic trace -Toggle packet tracing. -.Pp -.It Ic verbose -Toggle verbose mode. -.El -.Sh BUGS -Because there is no user login or validation within -the -.Tn TFTP -protocol, the remote site will probably have some -sort of file access restrictions in place. The -exact methods are specific to each site and therefore -difficult to document here. -.Sh HISTORY -The -.Nm -command appeared in -.Bx 4.3 . 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 index 46000d4..9c9c5af 100644 --- a/tftp/tftp.c +++ b/tftp/tftp.c @@ -1,8 +1,3 @@ -/* $Id$ */ - -/* $OpenBSD: tftp.c,v 1.4 1997/08/06 06:43:45 deraadt Exp $ */ -/* $NetBSD: tftp.c,v 1.5 1995/04/29 05:55:25 cgd Exp $ */ - /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. @@ -36,58 +31,26 @@ * SUCH DAMAGE. */ -#include "tftpsubs.h" - -#ifndef lint -/* static char sccsid[] = "@(#)tftp.c 8.1 (Berkeley) 6/6/93"; */ -/* static char rcsid[] = "$OpenBSD: tftp.c,v 1.4 1997/08/06 06:43:45 deraadt Exp $"; */ -static const char *rcsid UNUSED = -"tftp-hpa $Id$"; -#endif /* not lint */ - -/* Many bug fixes are from Jim Guyton */ +#include "common/tftpsubs.h" /* * TFTP User Program -- Protocol Machines */ -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include - #include "extern.h" -extern struct sockaddr_in peeraddr; /* filled in by main */ -extern int f; /* the opened socket */ -extern int trace; -extern int verbose; -extern int rexmtval; -extern int maxtimeout; - -#ifndef EOPTNEG -#define EOPTNEG 8 -#endif -#ifndef OACK -#define OACK 6 -#endif +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; +char ackbuf[PKTSIZE]; +int timeout; +static sigjmp_buf timeoutbuf; -static void nak(int); +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); @@ -98,365 +61,365 @@ static void tpacket(const char *, struct tftphdr *, int); /* * Send the requested file. */ -void -tftp_sendfile(int fd, char *name, char *mode) +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; - struct sockaddr_in from; - int fromlen; - FILE *file; + 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; - file = fdopen(fd, "r"); - convert = !strcmp(mode, "netascii"); - block = 0; - is_request = 1; /* First packet is the actual WRQ */ - amount = 0; + 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); - break; - } - dp->th_opcode = htons((u_short)DATA); - dp->th_block = htons((u_short)block); - } - timeout = 0; - (void) sigsetjmp(timeoutbuf,1); + tftp_signal(SIGALRM, timer, 0); + 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, - (struct sockaddr *)&peeraddr, sizeof(peeraddr)); - if (n != size + 4) { - perror("tftp: sendto"); - goto abort; - } - read_ahead(file, convert); - for ( ; ; ) { - alarm(rexmtval); - do { - fromlen = sizeof(from); - n = recvfrom(f, ackbuf, sizeof(ackbuf), 0, - (struct sockaddr *)&from, &fromlen); - } while (n <= 0); - alarm(0); - if (n < 0) { - perror("tftp: recvfrom"); - goto abort; - } - peeraddr.sin_port = from.sin_port; /* added */ - if (trace) - tpacket("received", ap, n); - /* should verify packet came from server */ - ap->th_opcode = ntohs(ap->th_opcode); - ap->th_block = ntohs(ap->th_block); - if (ap->th_opcode == ERROR) { - printf("Error code %d: %s\n", ap->th_code, - ap->th_msg); - goto abort; - } - if (ap->th_opcode == ACK) { - int j; + if (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->th_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); + 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, char *name, char *mode) +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; - struct sockaddr_in from; - int fromlen; - FILE *file; - volatile int convert; /* true if converting crlf -> lf */ + 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; - file = fdopen(fd, "w"); - convert = !strcmp(mode, "netascii"); - block = 1; - firsttrip = 1; - amount = 0; + 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, (struct sockaddr *)&peeraddr, - sizeof(peeraddr)) != size) { - alarm(0); - perror("tftp: sendto"); - goto abort; - } - write_behind(file, convert); - for ( ; ; ) { - alarm(rexmtval); - do { - fromlen = sizeof(from); - n = recvfrom(f, dp, PKTSIZE, 0, - (struct sockaddr *)&from, &fromlen); - } while (n <= 0); - alarm(0); - if (n < 0) { - perror("tftp: recvfrom"); - goto abort; - } - peeraddr.sin_port = from.sin_port; /* added */ - if (trace) - tpacket("received", dp, n); - /* should verify client address */ - dp->th_opcode = ntohs(dp->th_opcode); - dp->th_block = ntohs(dp->th_block); - if (dp->th_opcode == ERROR) { - printf("Error code %d: %s\n", dp->th_code, - dp->th_msg); - goto abort; - } - if (dp->th_opcode == DATA) { - int j; + tftp_signal(SIGALRM, timer, 0); + 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->th_block == block) { - break; /* have next packet */ - } - /* On an error, try to synchronize - * both sides. - */ - j = synchnet(f); - if (j && trace) { - printf("discarded %d packets\n", j); - } - if (dp->th_block == (block-1)) { - goto send_ack; /* resend ack */ - } - } - } - /* size = write(fd, dp->th_data, n - 4); */ - size = writeit(file, &dp, n - 4, convert); - if (size < 0) { - nak(errno + 100); - break; - } - amount += size; - } while (size == SEGSIZE); -abort: /* ok to ack, since user */ - ap->th_opcode = htons((u_short)ACK); /* has seen err msg */ - ap->th_block = htons((u_short)block); - (void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peeraddr, - sizeof(peeraddr)); - write_behind(file, convert); /* flush last buffer */ - fclose(file); - stopclock(); - if (amount > 0) - printstats("Received", amount); + 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) + struct tftphdr *tp, const char *mode) { - char *cp; + char *cp; + size_t len; - 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); + tp->th_opcode = htons((u_short) request); + cp = (char *)&(tp->th_stuff); + len = strlen(name) + 1; + memcpy(cp, name, len); + cp += len; + len = strlen(mode) + 1; + memcpy(cp, mode, len); + cp += len; + return (cp - (char *)tp); } -struct errmsg { - int e_code; - const char *e_msg; -} errmsgs[] = { - { EUNDEF, "Undefined error code" }, - { ENOTFOUND, "File not found" }, - { EACCESS, "Access violation" }, - { ENOSPACE, "Disk full or allocation exceeded" }, - { EBADOP, "Illegal TFTP operation" }, - { EBADID, "Unknown transfer ID" }, - { EEXISTS, "File already exists" }, - { ENOUSER, "No such user" }, - { EOPTNEG, "Failure to negotiate RFC2347 options" }, - { -1, 0 } +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) +static void nak(int error, const char *msg) { - struct errmsg *pe; - struct tftphdr *tp; - int length; + struct tftphdr *tp; + int length; - tp = (struct tftphdr *)ackbuf; - tp->th_opcode = htons((u_short)ERROR); - tp->th_code = htons((u_short)error); - for (pe = errmsgs; pe->e_code >= 0; pe++) - if (pe->e_code == error) - break; - if (pe->e_code < 0) { - pe->e_msg = strerror(error - 100); - tp->th_code = EUNDEF; - } - strcpy(tp->th_msg, pe->e_msg); - length = strlen(pe->e_msg) + 4; - if (trace) - tpacket("sent", tp, length); - if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr, - sizeof(peeraddr)) != length) - perror("nak"); + 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 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(tp->th_opcode); + 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) { + 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 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 DATA: + printf("\n", ntohs(tp->th_block), n - 4); + break; - case ACK: - printf("\n", ntohs(tp->th_block)); - break; + case ACK: + printf("\n", ntohs(tp->th_block)); + break; - case ERROR: - printf("\n", ntohs(tp->th_code), tp->th_msg); - break; - } + case ERROR: + printf("\n", ntohs(tp->th_code), tp->th_msg); + break; + } } struct timeval tstart; struct timeval tstop; -static void -startclock(void) +static void startclock(void) { - (void)gettimeofday(&tstart, NULL); + (void)gettimeofday(&tstart, NULL); } -static void -stopclock(void) +static void stopclock(void) { - (void)gettimeofday(&tstop, NULL); + (void)gettimeofday(&tstop, NULL); } -static void -printstats(const char *direction, unsigned long amount) +static void printstats(const char *direction, unsigned long amount) { - double delta; - /* compute delta in 1/10's second units */ - delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) - - ((tstart.tv_sec*10.)+(tstart.tv_usec/100000)); - delta = delta/10.; /* back to seconds */ - printf("%s %lu bytes in %.1f seconds", direction, amount, delta); - if (verbose) - printf(" [%.0f bits/sec]", (amount*8.)/delta); - putchar('\n'); + 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) +static void timer(int sig) { - int save_errno = errno; + int save_errno = errno; - (void)sig; /* Shut up unused warning */ + (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); + 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/tftp/tftpsubs.c b/tftp/tftpsubs.c deleted file mode 100644 index c358ccd..0000000 --- a/tftp/tftpsubs.c +++ /dev/null @@ -1,282 +0,0 @@ -/* tftp-hpa: $Id$ */ - -/* $OpenBSD: tftpsubs.c,v 1.2 1996/06/26 05:40:36 deraadt Exp $ */ -/* $NetBSD: tftpsubs.c,v 1.3 1994/12/08 09:51:31 jtc Exp $ */ - -/* - * Copyright (c) 1983, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include "tftpsubs.h" - -#ifndef lint -/* static char sccsid[] = "@(#)tftpsubs.c 8.1 (Berkeley) 6/6/93"; */ -/* static char rcsid[] = "$OpenBSD: tftpsubs.c,v 1.2 1996/06/26 05:40:36 deraadt Exp $"; */ -static const char *rcsid UNUSED = -"tftp-hpa: $Id$"; -#endif /* not lint */ - -/* Simple minded read-ahead/write-behind subroutines for tftp user and - server. Written originally with multiple buffers in mind, but current - implementation has two buffer logic wired in. - - Todo: add some sort of final error check so when the write-buffer - is finally flushed, the caller can detect if the disk filled up - (or had an i/o error) and return a nak to the other side. - - Jim Guyton 10/85 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#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]; - struct sockaddr_in from; - int 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, - (struct sockaddr *)&from, &fromlen); - } - - return pktcount; /* Return packets drained */ -} diff --git a/tftpd/Makefile b/tftpd/Makefile index f538c97..5963581 100644 --- a/tftpd/Makefile +++ b/tftpd/Makefile @@ -1,30 +1,29 @@ -all: tftpd - SRCROOT = .. +VERSION = $(shell cat ../version) --include ../MCONFIG +-include ../config/MCONFIG include ../MRULES -OBJS = tftpd.o tftpsubs.o recvfrom.o misc.o $(TFTPDOBJS) +OBJS = tftpd.$(O) recvfrom.$(O) misc.$(O) $(TFTPDOBJS) -tftpd: $(OBJS) - $(CC) $(LDFLAGS) $^ $(LIBS) -o $@ +all: tftpd$(X) tftpd.8 -tftpsubs.c: - ln -sf ../tftp/tftpsubs.c . -tftpsubs.h: - ln -sf ../tftp/tftpsubs.h . +tftpd$(X): $(OBJS) + $(CC) $(LDFLAGS) $^ $(TFTPD_LIBS) -o $@ -$(OBJS): tftpsubs.h +$(OBJS): ../common/tftpsubs.h -install: tftpd +tftpd.8: tftpd.8.in ../version + sed -e 's/@@VERSION@@/$(VERSION)/g' < $< > $@ + +install: all mkdir -p $(INSTALLROOT)$(SBINDIR) $(INSTALLROOT)$(MANDIR)/man8 - $(INSTALL_PROGRAM) tftpd $(INSTALLROOT)$(SBINDIR)/in.tftpd + $(INSTALL_PROGRAM) tftpd$(X) $(INSTALLROOT)$(SBINDIR)/in.tftpd $(INSTALL_DATA) tftpd.8 $(INSTALLROOT)$(MANDIR)/man8/in.tftpd.8 - ln -sf in.tftpd.8 $(INSTALLROOT)$(MANDIR)/man8/tftpd.8 + cd $(INSTALLROOT)$(MANDIR)/man8 && $(LN_S) -f in.tftpd.8 tftpd.8 clean: - rm -f *.o tftpd tftpsubs.c tftpsubs.h + 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 index 62f2c07..9070f96 100644 --- a/tftpd/misc.c +++ b/tftpd/misc.c @@ -1,7 +1,6 @@ -/* $Id$ */ /* ----------------------------------------------------------------------- * - * - * Copyright 2001 H. Peter Anvin - All Rights Reserved + * + * 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 @@ -15,28 +14,19 @@ * Minor help routines. */ -#include "config.h" /* Must be included first! */ +#include "config.h" /* Must be included first! */ #include -#include #include "tftpd.h" /* - * Set the signal handler and flags. Basically a user-friendly - * wrapper around sigaction(). + * Set the signal handler and flags, and error out on failure. */ -void set_signal(int signum, void (*handler)(int), int flags) +void set_signal(int signum, sighandler_t handler, 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); - } + if (tftp_signal(signum, handler, flags)) { + syslog(LOG_ERR, "sigaction: %m"); + exit(EX_OSERR); + } } /* @@ -44,14 +34,14 @@ void set_signal(int signum, void (*handler)(int), int flags) */ void *tfmalloc(size_t size) { - void *p = malloc(size); + void *p = malloc(size); - if ( !p ) { - syslog(LOG_ERR, "malloc: %m"); - exit(EX_OSERR); - } + if (!p) { + syslog(LOG_ERR, "malloc: %m"); + exit(EX_OSERR); + } - return p; + return p; } /* @@ -59,13 +49,12 @@ void *tfmalloc(size_t size) */ char *tfstrdup(const char *str) { - char *p = strdup(str); + char *p = strdup(str); - if ( !p ) { - syslog(LOG_ERR, "strdup: %m"); - exit(EX_OSERR); - } + if (!p) { + syslog(LOG_ERR, "strdup: %m"); + exit(EX_OSERR); + } - return p; + return p; } - diff --git a/tftpd/recvfrom.c b/tftpd/recvfrom.c index d094a69..320678d 100644 --- a/tftpd/recvfrom.c +++ b/tftpd/recvfrom.c @@ -1,7 +1,6 @@ -/* $Id$ */ /* ----------------------------------------------------------------------- * - * - * Copyright 2001 H. Peter Anvin - All Rights Reserved + * + * 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 @@ -18,28 +17,30 @@ * */ -#include "config.h" /* Must be included first! */ +#include "config.h" /* Must be included first! */ +#include "common/tftpsubs.h" #include "recvfrom.h" -#include "tftpsubs.h" #ifdef HAVE_MACHINE_PARAM_H -#include /* Needed on some versions of FreeBSD */ +#include /* Needed on some versions of FreeBSD */ #endif #if defined(HAVE_RECVMSG) && defined(HAVE_MSGHDR_MSG_CONTROL) -#include +#ifdef HAVE_SYS_UIO_H +# include +#endif #ifdef IP_PKTINFO -# ifndef HAVE_STRUCT_IN_PKTINFO +# ifndef HAVE_STRUCT_IN_PKTINFO_IPI_ADDR # 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; + 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 */ +# undef IP_PKTINFO /* No definition, no way to get it */ # endif # endif #endif @@ -51,102 +52,246 @@ struct in_pktinfo { # define CMSG_SPACE(size) (sizeof(struct cmsghdr) + (size)) #endif -int -myrecvfrom(int s, void *buf, int len, unsigned int flags, - struct sockaddr *from, int *fromlen, - struct sockaddr_in *myaddr) +/* + * 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) { - struct msghdr msg; - struct iovec iov[1]; - 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 - } control_un; - int on = 1; -#ifdef IP_PKTINFO - struct in_pktinfo pktinfo; -#endif + union sock_addr sa1, sa2; + int sockfd = -1; + int e; + int rv = 0; + socklen_t addrlen; - /* Try to enable getting the return address */ -#ifdef IP_RECVDSTADDR - setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)); -#endif -#ifdef IP_PKTINFO - setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); -#endif - - msg.msg_control = control_un.control; - msg.msg_controllen = sizeof(control_un.control); - msg.msg_flags = 0; - - msg.msg_name = from; - msg.msg_namelen = *fromlen; - iov[0].iov_base = buf; - iov[0].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(struct sockaddr_in)); - myaddr->sin_family = AF_INET; - - 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) ) { - -#ifdef IP_RECVSTDADDR - if ( cmptr->cmsg_level == IPPROTO_IP && - cmptr->cmsg_type == IP_RECVDSTADDR ) { - memcpy(&myaddr->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->sin_addr, &pktinfo.ipi_addr, sizeof(struct in_addr)); - } -#endif + 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; - return n; + 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; } -#else /* pointless... */ +static void normalize_ip6_compat(union sock_addr *myaddr) +{ +#ifdef HAVE_IPV6 + static const uint8_t ip6_compat_prefix[12] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; + struct sockaddr_in in; + + if (myaddr->sa.sa_family == AF_INET6 && + !memcmp(&myaddr->s6.sin6_addr, ip6_compat_prefix, + sizeof ip6_compat_prefix)) { + bzero(&in, sizeof in); + in.sin_family = AF_INET; + in.sin_port = myaddr->s6.sin6_port; + memcpy(&in.sin_addr, (const char *)&myaddr->s6.sin6_addr + + sizeof ip6_compat_prefix, sizeof in.sin_addr); + memcpy(&myaddr->si, &in, sizeof in); + } +#else + (void)myaddr; +#endif +} int myrecvfrom(int s, void *buf, int len, unsigned int flags, - struct sockaddr *from, int *fromlen, - struct sockaddr_in *myaddr) + union sock_addr *from, union sock_addr *myaddr) { - /* There is no way we can get the local address, fudge it */ + 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 - bzero(myaddr, sizeof(struct sockaddr_in)); - myaddr->sin_family = AF_INET; + /* Try to enable getting the return address */ +#ifdef IP_RECVDSTADDR + if (from->sa.sa_family == AF_INET) + setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)); +#endif +#ifdef IP_PKTINFO + if (from->sa.sa_family == AF_INET) + setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); +#endif +#ifdef HAVE_IPV6 +#ifdef IPV6_RECVPKTINFO + if (from->sa.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; - myaddr->sin_port = htons(IPPORT_TFTP); - bzero(&myaddr->sin_addr, sizeof(myaddr->sin_addr)); + msg.msg_name = &from->sa; + msg.msg_namelen = sizeof(*from); + iov.iov_base = buf; + iov.iov_len = len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; - return recvfrom(s,buf,len,flags,from,fromlen); + if ((n = recvmsg(s, &msg, flags)) < 0) + return n; /* Error */ + + if (myaddr) { + bzero(myaddr, sizeof(*myaddr)); + myaddr->sa.sa_family = from->sa.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.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.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 && + ( +#ifdef IPV6_RECVPKTINFO + cmptr->cmsg_type == IPV6_RECVPKTINFO || +#endif + 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 + } + + normalize_ip6_compat(myaddr); + + /* 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 + } + } + + normalize_ip6_compat(from); + + return n; +} + +#else /* pointless... */ + +int +myrecvfrom(int s, void *buf, int len, unsigned int flags, + union sock_addr *from, union sock_addr *myaddr) +{ + /* There is no way we can get the local address, fudge it */ + socklen_t fromlen = sizeof(*from); + + bzero(myaddr, sizeof(*myaddr)); + myaddr->sa.sa_family = from->sa.sa_family; + sa_set_port(myaddr, htons(IPPORT_TFTP)); + + return recvfrom(s, buf, len, flags, &from->sa, &fromlen); } #endif diff --git a/tftpd/recvfrom.h b/tftpd/recvfrom.h index 1de145e..7773a0d 100644 --- a/tftpd/recvfrom.h +++ b/tftpd/recvfrom.h @@ -1,7 +1,6 @@ -/* $Id$ */ /* ----------------------------------------------------------------------- * - * - * Copyright 2001 H. Peter Anvin - All Rights Reserved + * + * 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 @@ -16,14 +15,8 @@ * */ -#define _XPG4_2 /* Needed on Solaris */ -#include -#include -#include -#include -#include +#include "config.h" int myrecvfrom(int s, void *buf, int len, unsigned int flags, - struct sockaddr *from, int *fromlen, - struct sockaddr_in *myaddr); + union sock_addr *from, union sock_addr *myaddr); diff --git a/tftpd/remap.c b/tftpd/remap.c index ee93c98..413d117 100644 --- a/tftpd/remap.c +++ b/tftpd/remap.c @@ -1,7 +1,6 @@ -/* $Id$ */ /* ----------------------------------------------------------------------- * - * - * Copyright 2001 H. Peter Anvin - All Rights Reserved + * + * Copyright 2001-2024 H. Peter Anvin - All Rights Reserved * * This program is free software available under the same license * as the "OpenBSD" operating system, distributed at @@ -15,7 +14,7 @@ * Perform regular-expression based filename remapping. */ -#include "config.h" /* Must be included first! */ +#include "config.h" /* Must be included first! */ #include #include #include @@ -23,317 +22,524 @@ #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 DEADMAN_MAX_STEPS 4096 /* 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_GETONLY 0x20 /* Applicable to GET only */ -#define RULE_PUTONLY 0x40 /* Applicable to PUT only */ +#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 */ +#define RULE_IPV4 0x40 /* IPv4 only */ +#define RULE_IPV6 0x80 /* IPv6 only */ + +#define RULE_HASFILE 0x100 /* Valid if rule results in a valid filename */ +#define RULE_RRQ 0x200 /* Get (read) only */ +#define RULE_WRQ 0x400 /* Put (write) only */ +#define RULE_SEDG 0x800 /* sed-style global */ + +int deadman_max_steps = DEADMAN_MAX_STEPS; struct rule { - struct rule *next; - int nrule; - int rule_flags; - regex_t rx; - const char *pattern; + struct rule *next; + int nrule; + unsigned int rule_flags; + regex_t rx; + const char *pattern; }; -/* 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) +static int xform_null(int c) { - int len = 0; - int n, mlen; - 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 ) { - if ( *pattern == '\\' && pattern[1] != '\0' ) { - if ( pattern[1] < '0' || pattern[1] > '9' ) { - len++; - if ( string ) - *string++ = pattern[1]; - } else { - n = pattern[1] - '0'; - - if ( pmatch[n].rm_so != -1 ) { - mlen = pmatch[n].rm_eo - pmatch[n].rm_so; - len += mlen; - if ( string ) { - memcpy(string, input+pmatch[n].rm_so, mlen); - string += mlen; - } - } - } - pattern += 2; - } else { - len++; - if ( string ) - *string++ = *pattern; - pattern++; - } - } - - /* Copy section after match */ - if ( string ) { - memcpy(string, input+pmatch[0].rm_eo, endbytes); - string[endbytes] = '\0'; - } - - return len; + return c; } -/* Extract a string terminated by non-escaped whitespace; ignore leading whitespace */ -/* Consider an unescaped # to be a comment marker, functionally \n */ +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. + * "start" indicates an offset into the input buffer where the pattern + * match was started. + */ +static int do_genmatchstring(char *string, const char *pattern, + const char *ibuf, + const regmatch_t * pmatch, + match_pattern_callback macrosub, + int start, int *nextp) +{ + int (*xform) (int) = xform_null; + int len = 0; + int n, mlen, sublen; + int endbytes; + const char *input = ibuf + start; + + /* Get section before match; note pmatch[0] is the whole match */ + endbytes = strlen(input) - pmatch[0].rm_eo; + len = start + pmatch[0].rm_so; + if (string) { + /* Copy the prefix before "start" as well! */ + memcpy(string, ibuf, start + pmatch[0].rm_so); + string += start + 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 + start + 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++; + } + } + + /* Pointer to post-substitution tail */ + if (nextp) + *nextp = len; + + /* Copy section after match */ + len += endbytes; + if (string) { + memcpy(string, input + pmatch[0].rm_eo, endbytes); + string[endbytes] = '\0'; + } + + return len; +} + +/* + * Ditto, but allocate the string in a new buffer + */ + +static int genmatchstring(char **string, const char *pattern, + const char *ibuf, + const regmatch_t * pmatch, + match_pattern_callback macrosub, + int start, int *nextp) +{ + int len; + char *buf; + + len = do_genmatchstring(NULL, pattern, ibuf, pmatch, + macrosub, start, NULL); + *string = buf = tfmalloc(len + 1); + return do_genmatchstring(buf, pattern, ibuf, pmatch, + macrosub, start, nextp); +} + +/* + * 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; + char *p = *str; + int wasbs = 0, len = 0; - while ( *p && isspace(*p) ) - p++; + 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++; + } - 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; + 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; + char buffer[MAXLINE]; + char *p; + int rv; + int rxflags = REG_EXTENDED; + static int nrule; - memset(r, 0, sizeof r); - r->nrule = nrule; + memset(r, 0, sizeof *r); + r->nrule = nrule; - if ( !readescstring(buffer, &line) ) - return 0; /* No rule found */ + 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 'G': - r->rule_flags |= RULE_GETONLY; - break; - case 'P': - r->rule_flags |= RULE_PUTONLY; - break; - default: - syslog(LOG_ERR, "Remap command \"%s\" on line %d contains invalid char \"%c\"", - buffer, lineno, *p); - return -1; /* Error */ - break; + for (p = buffer; *p; p++) { + switch (*p) { + case 'r': + r->rule_flags |= RULE_REWRITE; + break; + case 'g': + if (r->rule_flags & RULE_GLOBAL) + r->rule_flags |= RULE_SEDG; + else + r->rule_flags |= RULE_GLOBAL; + break; + case 'e': + r->rule_flags |= RULE_EXIT; + break; + case 'E': + r->rule_flags |= RULE_HASFILE; + 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 '4': + r->rule_flags |= RULE_IPV4; + break; + case '6': + r->rule_flags |= RULE_IPV6; + break; + case 'G': + r->rule_flags |= RULE_RRQ; + break; + case 'P': + r->rule_flags |= RULE_WRQ; + 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_REWRITE) { + if (r->rule_flags & RULE_INVERSE) { + syslog(LOG_ERR, "r rules cannot be inverted, line %d: %s\n", + lineno, line); + return -1; /* Error */ + } + if ((r->rule_flags & (RULE_GLOBAL|RULE_SEDG|RULE_HASFILE)) + == (RULE_GLOBAL|RULE_HASFILE)) { + syslog(LOG_ERR, "E rules cannot be combined with g (but gg is OK), line %d: %s\n", + lineno, line); + return -1; /* Error */ + } + } else { + /* RULE_GLOBAL and RULE_SEDG are meaningless without RULE_REWRITE */ + r->rule_flags &= ~(RULE_GLOBAL|RULE_SEDG); + } - /* 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 */ - } + /* 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 */ - } + 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 = ""; - } + /* Read the rewrite pattern, if any */ + if (readescstring(buffer, &line)) { + r->pattern = tfstrdup(buffer); + } else { + r->pattern = ""; + } - nrule++; - return 1; /* Rule found */ + nrule++; + return 1; /* Rule found */ } /* Read a rule file */ -struct rule *parserulefile(FILE *f) +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; + 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)); + 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 */ + free(this_rule); /* Last one is always unused */ - if ( err ) { - /* Bail on error, we have already logged an error message */ - exit(EX_CONFIG); - } + if (err) { + /* Bail on error, we have already logged an error message */ + exit(EX_CONFIG); + } - return first_rule; + return first_rule; } /* Destroy a rule file data structure */ void freerules(struct rule *r) { - struct rule *next; + struct rule *next; - while ( r ) { - next = r->next; + while (r) { + next = r->next; - regfree(&r->rx); + regfree(&r->rx); - /* "" patterns aren't allocated by malloc() */ - if ( r->pattern && *r->pattern ) - free((void *)r->pattern); - - free(r); + /* "" patterns aren't allocated by malloc() */ + if (r->pattern && *r->pattern) + free((void *)r->pattern); - r = next; - } + 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, int is_put) +char *rewrite_string(const struct formats *pf, + const char *input, const struct rule *rules, + int mode, int af, 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; + char *current = tfstrdup(input); + char *newstr, *newerstr; + const char *accerr; + const struct rule *ruleptr = rules; + regmatch_t pmatch[10]; + int i; + int len; + int was_match = 0; + int deadman = deadman_max_steps; + int matchsense; + int pmatches; + unsigned int bad_flags; + int ggoffset; - if ( verbosity >= 3 ) { - syslog(LOG_INFO, "remap: input: %s", current); - } + /* Default error */ + *errmsg = "Remap table failure"; - for ( ruleptr = rules ; ruleptr ; ruleptr = ruleptr->next ) { - if ( ((ruleptr->rule_flags & RULE_GETONLY) && is_put) || - ((ruleptr->rule_flags & RULE_PUTONLY) && !is_put) ) { - continue; /* Rule not applicable, try next */ + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: input: %s", current); } - if ( ! deadman-- ) { - syslog(LOG_WARNING, "remap: Breaking loop, input = %s, last = %s", - input, current); - free(current); - return NULL; /* Did not terminate! */ + bad_flags = 0; + if (mode != RRQ) bad_flags |= RULE_RRQ; + if (mode != WRQ) bad_flags |= RULE_WRQ; + if (af != AF_INET) bad_flags |= RULE_IPV4; + if (af != AF_INET6) bad_flags |= RULE_IPV6; + + for (ruleptr = rules; ruleptr; ruleptr = ruleptr->next) { + if (ruleptr->rule_flags & bad_flags) + continue; /* This rule is excluded by flags */ + + matchsense = ruleptr->rule_flags & RULE_INVERSE ? REG_NOMATCH : 0; + pmatches = ruleptr->rule_flags & RULE_INVERSE ? 0 : 10; + + /* Clear the pmatch[] array */ + for (i = 0; i < 10; i++) + pmatch[i].rm_so = pmatch[i].rm_eo = -1; + + was_match = 0; + + do { + if (!deadman--) + goto dead; + + if (regexec(&ruleptr->rx, current, pmatches, pmatch, 0) + != matchsense) + break; /* No match, break out of do loop */ + + /* Match on this rule */ + was_match = 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 */ + genmatchstring(&newstr, ruleptr->pattern, current, + pmatch, macrosub, 0, NULL); + *errmsg = newstr; + } else { + *errmsg = NULL; + } + free(current); + return (NULL); + } + + if (ruleptr->rule_flags & RULE_REWRITE) { + len = genmatchstring(&newstr, ruleptr->pattern, current, + pmatch, macrosub, 0, &ggoffset); + + if (ruleptr->rule_flags & RULE_SEDG) { + /* sed-style partial-matching global */ + while (ggoffset < len && + regexec(&ruleptr->rx, newstr + ggoffset, + pmatches, pmatch, + ggoffset ? REG_NOTBOL : 0) + == matchsense) { + if (!deadman--) { + free(current); + current = newstr; + goto dead; + } + len = genmatchstring(&newerstr, ruleptr->pattern, + newstr, pmatch, macrosub, + ggoffset, &ggoffset); + free(newstr); + newstr = newerstr; + } + } + + if ((ruleptr->rule_flags & RULE_HASFILE) && + pf->f_validate(newstr, mode, pf, &accerr)) { + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: ignored rewrite (%s): %s", + ruleptr->nrule, accerr, newstr); + } + free(newstr); + was_match = 0; + break; + } + + free(current); + current = newstr; + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: rewrite: %s", + ruleptr->nrule, current); + } + } else if (ruleptr->rule_flags & RULE_HASFILE) { + if (pf->f_validate(current, mode, pf, &accerr)) { + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: rule %d: not exiting (%s)\n", + ruleptr->nrule, accerr); + } + was_match = 0; + break; + } + } + /* If the rule is (old-style) global, keep going until no match */ + } while ((ruleptr->rule_flags & (RULE_GLOBAL|RULE_SEDG)) == RULE_GLOBAL); + + if (!was_match) + continue; /* Next rule */ + + if (ruleptr->rule_flags & (RULE_EXIT|RULE_HASFILE)) { + 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); + } + } } - do { - if ( regexec(&ruleptr->rx, current, 10, pmatch, 0) == 0 ) { - /* Match on this rule */ - was_match = 1; - - if ( ruleptr->rule_flags & RULE_ABORT ) { - if ( verbosity >= 3 ) { - syslog(LOG_INFO, "remap: rule %d: abort: %s", - ruleptr->nrule, current); - } - free(current); - return(NULL); - } - - if ( ruleptr->rule_flags & RULE_REWRITE ) { - len = genmatchstring(NULL, ruleptr->pattern, current, pmatch); - newstr = tfmalloc(len+1); - genmatchstring(newstr, ruleptr->pattern, current, pmatch); - 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; - if ( verbosity >= 3 ) { - syslog(LOG_INFO, "remap: done"); - } - return current; +dead: /* Deadman expired */ + syslog(LOG_ERR, + "remap: Breaking loop after %d steps, input = %s, last = %s", + deadman_max_steps, input, current); + free(current); + return NULL; /* Did not terminate! */ } diff --git a/tftpd/remap.h b/tftpd/remap.h index a562df5..c8d76ff 100644 --- a/tftpd/remap.h +++ b/tftpd/remap.h @@ -1,7 +1,6 @@ -/* $Id$ */ /* ----------------------------------------------------------------------- * - * - * Copyright 2001 H. Peter Anvin - All Rights Reserved + * + * 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 @@ -18,15 +17,17 @@ #ifndef TFTPD_REMAP_H #define TFTPD_REMAP_H -#include -#include -#include "../config.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 *); @@ -34,8 +35,13 @@ struct rule *parserulefile(FILE *); 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 *, int); +struct formats; +char *rewrite_string(const struct formats *, const char *, + const struct rule *, int, int, + match_pattern_callback, const char **); -#endif /* WITH_REGEX */ -#endif /* TFTPD_REMAP_H */ +/* Remapping deadman counter */ +extern int deadman_max_steps; +#endif /* WITH_REGEX */ +#endif /* TFTPD_REMAP_H */ diff --git a/tftpd/sample.rules b/tftpd/sample.rules index 7b59e70..55b56be 100644 --- a/tftpd/sample.rules +++ b/tftpd/sample.rules @@ -1,4 +1,3 @@ -# $Id$ # # Sample rule file for the -m (remapping option) # diff --git a/tftpd/tftpd.8 b/tftpd/tftpd.8 deleted file mode 100644 index 505bacf..0000000 --- a/tftpd/tftpd.8 +++ /dev/null @@ -1,262 +0,0 @@ -.\" tftp-hpa: $Id$ -.\" $OpenBSD: tftpd.8,v 1.7 1999/07/09 13:35:51 aaron Exp $ -.\" -.\" Copyright (c) 1983, 1991 The Regents of the University of California. -.\" All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in the -.\" documentation and/or other materials provided with the distribution. -.\" 3. All advertising materials mentioning features or use of this software -.\" must display the following acknowledgement: -.\" This product includes software developed by the University of -.\" California, Berkeley and its contributors. -.\" 4. Neither the name of the University nor the names of its contributors -.\" may be used to endorse or promote products derived from this software -.\" without specific prior written permission. -.\" -.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -.\" SUCH DAMAGE. -.\" -.\" from: @(#)tftpd.8 6.7 (Berkeley) 5/13/91 -.\" $OpenBSD: tftpd.8,v 1.7 1999/07/09 13:35:51 aaron Exp $ -.\" -.Dd August 6, 2001 -.Dt TFTPD 8 -.Os -.Sh NAME -.Nm tftpd -.Nd -IPv4 Trivial File Transfer Protocol server -.Sh SYNOPSIS -.Nm in.tftpd -.Op Fl v -.Op Fl c -.Op Fl l -.Op Fl a Ar [address][:port] -.Op Fl m Ar mapfile -.Op Fl u Ar userid -.Op Fl t Ar timeout -.Op Fl r Ar option... -.Op Fl s -.Op Ar directory -.Sh DESCRIPTION -.Nm -is a server which supports the -.Tn DARPA -Trivial File Transfer -Protocol. -The -.Tn TFTP -server operates -at the port indicated in the -.Ql tftp -service description; -see -.Xr services 5 . -The server is normally started by -.Xr inetd 8 , -but can also run standalone. -.Pp -The use of -.Xr tftp 1 -does not require an account or password on the remote system. -Due to the lack of authentication information, -.Nm -will allow only publicly readable files to be -accessed. -Files may be written only if they already exist and are publicly writable. -Note that this extends the concept of -.Dq public -to include -all users on all hosts that can be reached through the network; -this may not be appropriate on all systems, and its implications -should be considered before enabling tftp service. -The server should have the user ID with the lowest possible privilege -(see the -.Fl u -flag below.) -.Pp -Access to files may be restricted by invoking -.Nm -with a list of directories by including pathnames -as server program arguments in -.Pa /etc/inetd.conf -or on the standalone server command line. In this case access is -restricted to files whose names are prefixed by the one of the given -directories. -.Pp -If the -.Fl l -flag is used, the server runs in standalone (listen) mode. In listen -mode, the -.Fl t -option is ignored, and the -.Fl a -option can be used to specify a specific local address or port to -listen to. -.Pp -If the -.Fl c -flag is used, -.Nm -will allow new files to be created; otherwise uploaded files must already -exist. Files are created with default permissions allowing anyone to read -or write to them. -.Pp -When using the -.Fl s -flag with a directory name, -.Nm -will -.Xr chroot 2 -on startup; therefore the remote host is not expected to pass the -directory as part of the file name to transfer. This option is -recommended for security, as well as compatibility with boot ROMs -which do not include a directory name. -.Pp -The -.Fl u -option can be used to specify a user ID which -.Nm -will run as; the default is ``nobody''. -.Pp -The -.Fl t -option specifies the server timeout in inetd mode. -.Pp -The -.Fl m -flag specifies a file which contains filename remapping rules. -.Pp -The -.Fl v -flag increases the logging verbosity of -.Nm tftpd , -it can be specified multiple times. -.Pp -This version of -.Nm -supports RFC 2347 option negotiation; the current version supports the -.Pa blksize -(RFC 2348), -.Pa tsize , -(RFC 2349), and -.Pa timeout -(RFC 2349) options. The -.Fl r -flag can be used to disable options individually; this may allow -working around client bugs. -.Sh FILENAME REMAPPING -The -.Fl m -option specifies a file which contains filename remapping rules. Each -non-comment line (comments begin with hash marks, #) contains an -.Ar operation , -a -.Ar regex , -a regular expression in the style of -.Xr egrep 1 , -and optionally a -.Ar "replacement pattern" . -The operation indicated by -.Ar operation -is performed if the -.Ar 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 -.Ar operation -can be any combination of the following letters: -.Pp -.Bl -tag -width verbose -compact -.It Ic r -Replace the substring matched by -.Ar regex -by the -.Ar "replacement pattern" . -The escape sequence -\\0 -can be used to copy the entire matched string, and the sequences -\\1 to \\9 -copies parenthesized subexpressions. To specify a backslash, white -space or hash mark, you need to \\-escape it. -.Pp -.It Ic g -Repeat this rule until it no longer matches. This is always used with -.Ic r . -.Pp -.It Ic i -Match the -.Ar regex -case-insensitively. By default it is case sensitive. -.Pp -.It Ic e -If this rule matches, end rule processing after executing the rule. -.Pp -.It Ic s -If this rule matches, start rule processing over from the very first -rule after executing this rule. -.Pp -.It Ic a -If this rule matches, refuse the request and send an access denied -error to the client. -.Pp -.It Ic G -This rule applies to GET (RRQ) requests only. -.Pp -.It Ic P -This rule applies to PUT (WRQ) requests only. -.El -.Pp -If the mapping file is changed, you need to send SIGHUP -(kill -HUP) to any outstanding -.Nm -process. -.Sh SEE ALSO -.Xr tftp 1 , -.Xr egrep 1 , -.Xr regex 7 , -.Xr inetd 8 -.Sh HISTORY -The -.Nm -command appeared in -.Bx 4.2 . -.Pp -The -.Fl s -flag appeared in NetBSD 0.9a. -.Pp -The -.Fl c -flag was added in OpenBSD 2.1 . -.Pp -The -.Fl r -flag and RFC 2347 options were added by H. Peter Anvin based on -patches by Markus Gutschke and Gero Kulhman. -.Pp -The -.Fl u , -.Fl v -and -.Fl m -flags were added by H. Peter Anvin. - diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in new file mode 100644 index 0000000..3d43325 --- /dev/null +++ b/tftpd/tftpd.8.in @@ -0,0 +1,457 @@ +.\" -*- 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 "7 June 2014" "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\-\-map-file\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\-\-map-steps\fP \fIsteps\fP +Specify the number of remapping rules that may be executed before the +filename mapping fails. The default is 4096. +.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 \-\-map-file +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 gg +Repeat this rule until it no longer matches, but only on the portion +of the string that has not yet been matched, similar to how the +.B s +command with the +.B g +option works in +.BR sed (1). +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 E +If this rule matches, +\fIand the result matches a filename that can be transferred\fP, +end rule processing after executing the rule. If this is combined with +.BR r , +then if the substitution does \fInot\fP result in a valid filename, +the substitution is undone. This cannot be combined with +.BR g , +but \fIcan\fP be combined with +.BR gg . +.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 4 +This rule applies to IPv4 sessions only. +.TP +.B 6 +This rule applies to IPv6 sessions 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 a +.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 for +IPv4 (e.g. 192.0.2.169) or conventional colon form for IPv6 +(e.g. 2001:db8::1). +.TP +\fB\\x\fP +The IP address of the requesting host, in expanded hexadecimal +notation (e.g. C00002A9 for IPv4, or 20010DB8000000000000000000000001 +for IPv6). +.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 whose 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 ( \-\-map-file +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 index 4f700c9..e0041dc 100644 --- a/tftpd/tftpd.c +++ b/tftpd/tftpd.c @@ -1,9 +1,7 @@ -/* tftp-hpa: $Id$ */ - -/* $OpenBSD: tftpd.c,v 1.13 1999/06/23 17:01:36 deraadt Exp $ */ - /* * Copyright (c) 1983 Regents of the University of California. + * Copyright (c) 1999-2009 H. Peter Anvin + * Copyright (c) 2011-2014 Intel Corporation; author: H. Peter Anvin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,616 +33,1173 @@ * SUCH DAMAGE. */ -#include "config.h" /* Must be included first */ +#include "config.h" /* Must be included first */ #include "tftpd.h" -#ifndef lint -static const char *copyright UNUSED = -"@(#) Copyright (c) 1983 Regents of the University of California.\n\ - All rights reserved.\n"; -/*static char sccsid[] = "from: @(#)tftpd.c 5.13 (Berkeley) 2/26/91";*/ -/*static char rcsid[] = "$OpenBSD: tftpd.c,v 1.13 1999/06/23 17:01:36 deraadt Exp $: tftpd.c,v 1.6 1997/02/16 23:49:21 deraadt Exp $";*/ -static const char *rcsid UNUSED = -"tftp-hpa $Id$"; -#endif /* not lint */ - /* * Trivial file transfer protocol server. * * This version includes many modifications by Jim Guyton */ -#include #include -#include -#include #include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include #include -#include -#include #include -#include #include +#include -#include "tftpsubs.h" +#include "common/tftpsubs.h" #include "recvfrom.h" #include "remap.h" #ifdef HAVE_SYS_FILIO_H -#include /* Necessary for FIONBIO on Solaris */ +#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 */ +int deny_severity = LOG_WARNING; +int allow_severity = -1; /* Don't log at all */ -struct request_info wrap_request; +static struct request_info wrap_request; #endif -#define TIMEOUT 5 /* Default timeout (seconds) */ -#define TRIES 4 /* Number of attempts to send each packet */ -#define TIMEOUT_LIMIT (TRIES*(TRIES+1)/2) - -#ifndef OACK -#define OACK 6 -#endif -#ifndef EOPTNEG -#define EOPTNEG 8 +#ifdef HAVE_IPV6 +static int ai_fam = AF_UNSPEC; +#else +static int ai_fam = AF_INET; #endif -char *__progname; -int peer; -int timeout = TIMEOUT; -int rexmtval = TIMEOUT; -int maxtimeout = TIMEOUT_LIMIT*TIMEOUT; +#define TIMEOUT 1000000 /* Default timeout (us) */ +#define TRIES 6 /* Number of attempts to send each packet */ +#define TIMEOUT_LIMIT ((1 << TRIES)-1) + +const char *tftpd_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 -char buf[PKTSIZE]; -char ackbuf[PKTSIZE]; -struct sockaddr_in from; -int fromlen; -off_t tsize; -int tsize_ok; +static char buf[PKTSIZE]; +static char ackbuf[PKTSIZE]; +static unsigned int max_blksize = MAX_SEGSIZE; -int ndirs; -const char **dirs; +static char tmpbuf[INET6_ADDRSTRLEN], *tmp_p; -int secure = 0; -int cancreate = 0; +static union sock_addr from; +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); -void nak(int); -void timer(int); -void justquit(int); -void do_opt(char *, char *, char **); +static FILE *file; -int set_blksize(char *, char **); -int set_blksize2(char *, char **); -int set_tsize(char *, char **); -int set_timeout(char *, char **); +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)(char *, char **); + const char *o_opt; + int (*o_fnc)(uintmax_t *); } options[] = { - { "blksize", set_blksize }, - { "blksize2", set_blksize2 }, - { "tsize", set_tsize }, - { "timeout", set_timeout }, - { NULL, NULL } + {"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; + (void)sig; /* Suppress unused warning */ + caught_sighup = 1; } - -static void -usage(void) +/* Handle exit requests by SIGTERM and SIGINT */ +static volatile sig_atomic_t exit_signal = 0; +static void handle_exit(int sig) { - syslog(LOG_ERR, "Usage: %s [-vcl][-a address][-m mappings][-u user][-t timeout][-r option...] [-s] [directory ...]", - __progname); - exit(EX_USAGE); + exit_signal = sig; } +/* Handle timeout signal or timeout event */ +static 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) +static struct rule *read_remap_rules(const char *rulefile) { - FILE *f; - struct rule *rulep; + 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); + f = fopen(rulefile, "rt"); + if (!f) { + syslog(LOG_ERR, "Cannot open map file: %s: %m", rulefile); + exit(EX_NOINPUT); + } + rulep = parserulefile(f); + fclose(f); - return rulep; + return rulep; } #endif -int -main(int argc, char **argv) +/* + * Rules for locking files; return 0 on success, -1 on failure + */ +static int lock_file(int fd, int lock_write) { - struct tftphdr *tp; - struct passwd *pw; - struct options *opt; - struct sockaddr_in myaddr; - struct sockaddr_in bindaddr; - int n; - int on = 1; - int fd = 0; - int standalone = 0; /* Standalone (listen) mode */ - char *address = NULL; /* Address to listen to */ - pid_t pid; - int c; - int setrv; - int waittime = 900; /* Default time to wait for a connect*/ - const char *user = "nobody"; /* Default user */ - char *p; -#ifdef WITH_REGEX - char *rewrite_file = NULL; + (void)lock_write; +#if defined(HAVE_FCNTL) && HAVE_DECL_F_SETLK + 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_FLOCK) && HAVE_DECL_LOCK_SH && HAVE_DECL_LOCK_EX + return flock(fd, lock_write ? LOCK_EX|LOCK_NB : LOCK_SH|LOCK_NB); +#else + return 0; /* Hope & pray... */ #endif +} - /* 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); - - while ((c = getopt(argc, argv, "csvla:u:r:t:m:")) != -1) - switch (c) { - case 'c': - cancreate = 1; - break; - case 's': - secure = 1; - break; - case 'l': - standalone = 1; - break; - case 'a': - address = optarg; - break; - case 't': - waittime = atoi(optarg); - break; - case 'u': - user = optarg; - 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; +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 - case 'v': - verbosity++; - break; - default: - usage(); - break; + if (err) { + syslog(LOG_ERR, "Cannot set nonblock flag on socket: %m"); + exit(EX_OSERR); } +} - dirs = xmalloc((argc-optind+1)*sizeof(char *)); - for ( ndirs = 0 ; optind != argc ; optind++ ) - dirs[ndirs++] = argv[optind]; +static void pmtu_discovery_off(int fd) +{ +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + int pmtu = IP_PMTUDISC_DONT; - 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); - } - - if (ioctl(fd, FIONBIO, &on) < 0) { - syslog(LOG_ERR, "ioctl(FIONBIO): %m"); - exit(EX_OSERR); - } - -#ifdef WITH_REGEX - if ( rewrite_file ) - rewrite_rules = read_remap_rules(rewrite_file); + setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); #endif +} - /* If we're running standalone, set up the input port */ - if ( standalone ) { - fd = socket(PF_INET, SOCK_DGRAM, 0); - - memset(&bindaddr, 0, sizeof bindaddr); - bindaddr.sin_addr.s_addr = INADDR_ANY; - bindaddr.sin_port = htons(IPPORT_TFTP); +/* + * 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; - if ( address ) { - char *portptr, *eportptr; - struct hostent *hostent; - struct servent *servent; - unsigned long port; + gettimeofday(&t0, NULL); + timeout_left = timeout_us; - address = tfstrdup(address); - portptr = strrchr(address, ':'); - if ( portptr ) - *portptr++ = '\0'; - - if ( *address ) { - hostent = gethostbyname(address); - if ( !hostent || hostent->h_addrtype != AF_INET ) { - syslog(LOG_ERR, "cannot resolve local bind address: %s", address); - exit(EX_NOINPUT); - } - memcpy(&bindaddr.sin_addr, hostent->h_addr, hostent->h_length); - } else { - /* Default to using INADDR_ANY */ - } - - if ( portptr && *portptr ) { - servent = getservbyname(portptr, "udp"); - if ( servent ) { - bindaddr.sin_port = servent->s_port; - } else if ( (port = strtoul(portptr, &eportptr, 0)) && !*eportptr ) { - bindaddr.sin_port = htons(port); - } 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); - } - } + 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; + } } +} - if (bind(fd, (struct sockaddr *)&bindaddr, sizeof bindaddr) < 0) { - syslog(LOG_ERR, "cannot bind to local socket: %m"); - exit(EX_OSERR); - } +static int split_port(char **ap, char **pp) +{ + char *a, *p; + int ret = AF_UNSPEC; - /* Daemonize this process */ + 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 { - pid_t f = fork(); - if ( f > 0 ) - exit(0); - if ( f < 0 ) { - syslog(LOG_ERR, "cannot fork: %m"); - exit(EX_OSERR); - } - close(0); close(1); close(2); -#ifdef HAVE_SETSID - setsid(); -#endif + struct in_addr in; + + p = strrchr(a, ':'); + if (p) + *p++ = 0; + if (inet_aton(a, &in)) + ret = AF_INET; } - } else { - /* 0 is our socket descriptor */ - close(1); close(2); - } - - /* 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); - - while ( 1 ) { - fd_set readset; - struct timeval tv_waittime; - int rv; - - if ( caught_sighup ) { - caught_sighup = 0; - if ( standalone ) { -#ifdef HAVE_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); - 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(fd+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_OSERR); - } else if ( rv == 0 ) { - exit(0); /* Timeout, return to inetd */ - } - - fromlen = sizeof (from); - n = myrecvfrom(fd, buf, sizeof (buf), 0, - (struct sockaddr *)&from, &fromlen, - &myaddr); - - if ( standalone && myaddr.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(&myaddr.sin_addr, &bindaddr.sin_addr, sizeof bindaddr.sin_addr); - } - - if (n < 0) { - syslog(LOG_ERR, "recvfrom: %m"); - exit(EX_IOERR); - } - - /* - * 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); - -#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); - if ( hosts_access(&wrap_request) == 0 ) { - if ( deny_severity != -1 ) - syslog(deny_severity, "connection refused from %s", - inet_ntoa(from.sin_addr)); - exit(EX_NOPERM); /* Access denied */ - } else if ( allow_severity != -1 ) { - syslog(allow_severity, "connect from %s", - inet_ntoa(from.sin_addr)); - } -#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(AF_INET, SOCK_DGRAM, 0); - if (peer < 0) { - syslog(LOG_ERR, "socket: %m"); - exit(EX_IOERR); - } - - /* Chroot and drop privileges */ - - if (secure && chroot(".")) { - syslog(LOG_ERR, "chroot: %m"); - exit(EX_OSERR); - } - -#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); - } - - /* Other basic setup */ - from.sin_family = AF_INET; - alarm(0); - - /* Process the request... */ - - myaddr.sin_port = htons(0); /* We want a new local port */ - if (bind(peer, (struct sockaddr *)&myaddr, sizeof myaddr) < 0) { - syslog(LOG_ERR, "bind: %m"); - exit(EX_IOERR); - } - if (connect(peer, (struct sockaddr *)&from, sizeof from) < 0) { - syslog(LOG_ERR, "connect: %m"); - exit(EX_IOERR); - } - tp = (struct tftphdr *)buf; - tp->th_opcode = ntohs(tp->th_opcode); - if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) - tftp(tp, n); - exit(0); + *pp = p; + return ret; } -char *rewrite_access(char *, int); -int validate_access(char *, int, struct formats *); -void tftp_sendfile(struct formats *, struct tftphdr *, int); -void tftp_recvfile(struct formats *, struct tftphdr *, int); +enum long_only_options { + OPT_VERBOSITY = 256, + OPT_MAP_STEPS +}; -struct formats { - const char *f_mode; - char *(*f_rewrite)(char *, int); - int (*f_validate)(char *, int, struct formats *); - void (*f_send)(struct formats *, struct tftphdr *, int); - void (*f_recv)(struct formats *, struct tftphdr *, int); - int f_convert; -} formats[] = { - { "netascii", 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 } +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' }, + { "map-steps", 1, NULL, OPT_MAP_STEPS }, + { "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 die; + 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], '/'); + tftpd_progname = (p && p[1]) ? p + 1 : argv[0]; + + openlog(tftpd_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; + case OPT_MAP_STEPS: + { + unsigned long steps = strtoul(optarg, &ep, 0); + if (*optarg && *ep && steps > 0 && steps <= INT_MAX) { + deadman_max_steps = steps; + } else { + syslog(LOG_ERR, "Bad --map-steps option: %s", optarg); + exit(EX_USAGE); + } + 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 + + n = myrecvfrom(fd, buf, sizeof(buf), 0, &from, &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(tftpd_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, tftpd_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. + If we get EPERM, this is already a restricted process, e.g. + using user namespaces on Linux. */ + die = 0; +#ifdef HAVE_SETGROUPS + setrv = setgroups(0, NULL); + if (setrv && errno != EPERM) { + syslog(LOG_ERR, "cannot clear group list"); + die = EX_OSERR; + } +#endif +#ifdef HAVE_INITGROUPS + setrv = initgroups(user, pw->pw_gid); + if (!setrv) { + die = 0; + } else if (errno != EPERM) { + syslog(LOG_ERR, "cannot set groups for user %s", user); + die = EX_OSERR; + } +#endif + if (die) + exit(die); + + /* 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_SETRESGID + setrv = setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid); +#elif defined(HAVE_SETREGID) + setrv = setregid(pw->pw_gid, pw->pw_gid); +#else + setrv = setegid(pw->pw_gid) || setgid(pw->pw_gid); +#endif + if (setrv && errno == EPERM) { + setrv = 0; /* Assume already restricted by system policy */ + } + +#ifdef HAVE_SETRESUID + setrv = setrv || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid); +#elif defined(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 && errno == EPERM) { + setrv = 0; /* Assume already restricted by system policy */ + } + + 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(const struct formats *, + char *, int, 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); + +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) +int tftp(struct tftphdr *tp, int size) { - char *cp; - int argn, ecode; - struct formats *pf = NULL; - char *origfilename; - char *filename, *mode = NULL; - - char *val = NULL, *opt = NULL; - char *ap = ackbuf + 2; - - ((struct tftphdr *)ackbuf)->th_opcode = ntohs(OACK); - - origfilename = cp = (char *) &(tp->th_stuff); - argn = 0; - - while ( cp < buf + size && *cp ) { - do { - cp++; - } while (cp < buf + size && *cp); - - if ( *cp ) { - nak(EBADOP); /* Corrupt packet - no final NULL */ - exit(0); + 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); + } + file = NULL; + if (!(filename = (*pf->f_rewrite) + (pf, origfilename, tp_opcode, from.sa.sa_family, &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); + } + /* + * If "file" is already set, then a file was already validated + * and opened during remap processing. + */ + if (!file) { + 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; + } } - - argn++; - if (argn == 1) { - mode = ++cp; - } else if (argn == 2) { - for (cp = mode; *cp; cp++) - *cp = tolower(*cp); - for (pf = formats; pf->f_mode; pf++) { - if (!strcmp(pf->f_mode, mode)) - break; - } - if (!pf->f_mode) { - nak(EBADOP); - exit(0); - } - if ( !(filename = (*pf->f_rewrite)(origfilename, tp->th_opcode)) ) { - nak(EACCESS); /* File denied by mapping rule */ - exit(0); - } - if ( verbosity >= 1 ) { - if ( filename == origfilename || !strcmp(filename, origfilename) ) - syslog(LOG_NOTICE, "%s from %s filename %s\n", - tp->th_opcode == WRQ ? "WRQ" : "RRQ", - inet_ntoa(from.sin_addr), filename); - else - syslog(LOG_NOTICE, "%s from %s filename %s remapped to %s\n", - tp->th_opcode == WRQ ? "WRQ" : "RRQ", - inet_ntoa(from.sin_addr), origfilename, filename); - } - ecode = (*pf->f_validate)(filename, tp->th_opcode, pf); - if (ecode) { - nak(ecode); - exit(0); - } - opt = ++cp; - } else if ( argn & 1 ) { - val = ++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 { - do_opt(opt, val, &ap); - opt = ++cp; + if (tp_opcode == WRQ) + (*pf->f_recv) (pf, NULL, 0); + else + (*pf->f_send) (pf, NULL, 0); } - } - - if (!pf) { - nak(EBADOP); - exit(0); - } - - if ( ap != (ackbuf+2) ) { - if ( tp->th_opcode == WRQ ) - (*pf->f_recv)(pf, (struct tftphdr *)ackbuf, ap-ackbuf); - else - (*pf->f_send)(pf, (struct tftphdr *)ackbuf, ap-ackbuf); - } else { - if (tp->th_opcode == WRQ) - (*pf->f_recv)(pf, NULL, 0); - else - (*pf->f_send)(pf, NULL, 0); - } - exit(0); /* Request completed */ + exit(0); /* Request completed */ } static int blksize_set; @@ -652,66 +1207,65 @@ static int blksize_set; /* * Set a non-standard block size (c.f. RFC2348) */ -int -set_blksize(char *val, char **ret) +static int set_blksize(uintmax_t *vp) { - static char b_ret[6]; - unsigned int sz; - char *vend; - - sz = (unsigned int)strtoul(val, &vend, 10); - - if ( blksize_set || *vend ) - return 0; - - if (sz < 8) - return(0); - else if (sz > MAX_SEGSIZE) - sz = MAX_SEGSIZE; - - segsize = sz; - sprintf(*ret = b_ret, "%u", sz); - - blksize_set = 1; - - return(1); + 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) */ -int -set_blksize2(char *val, char **ret) +static int set_blksize2(uintmax_t *vp) { - static char b_ret[6]; - unsigned int sz; - char *vend; - - sz = (unsigned int)strtoul(val, &vend, 10); - - if ( blksize_set || *vend ) - return 0; - - if (sz < 8) - return(0); - else if (sz > MAX_SEGSIZE) - sz = MAX_SEGSIZE; - - /* 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; - } - - segsize = sz; - sprintf(*ret = b_ret, "%u", sz); - - blksize_set = 1; - - return(1); + 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; } /* @@ -719,449 +1273,560 @@ set_blksize2(char *val, char **ret) * For netascii mode, we don't know the size ahead of time; * so reject the option. */ -int -set_tsize(char *val, char **ret) +static int set_tsize(uintmax_t *vp) { - static char b_ret[sizeof(uintmax_t)*CHAR_BIT/3+2]; - uintmax_t sz; - char *vend; + uintmax_t sz = *vp; - sz = strtoumax(val, &vend, 10); - - if ( !tsize_ok || *vend ) - return 0; - - if (sz == 0) - sz = (uintmax_t)tsize; + if (!tsize_ok) + return 0; - sprintf(*ret = b_ret, "%"PRIuMAX, sz); - return(1); + if (sz == 0) + sz = tsize; + + *vp = sz; + return 1; } /* - * Set the timeout (c.f. RFC2349) + * 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. */ -int -set_timeout(char *val, char **ret) +static int set_timeout(uintmax_t *vp) { - static char b_ret[4]; - unsigned long to; - char *vend; + uintmax_t to = *vp; - to = strtoul(val, &vend, 10); + if (to < 1 || to > 255) + return 0; - if ( to < 1 || to > 255 || *vend ) - return 0; - - timeout = to; - rexmtval = to; - maxtimeout = TIMEOUT_LIMIT*to; - - sprintf(*ret = b_ret, "%lu", to); - return(1); + 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; } /* - * Parse RFC2347 style options + * Conservative calculation for the size of a buffer which can hold an + * arbitrary integer */ -void -do_opt(char *opt, char *val, char **ap) +#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 *ret; - - /* Global option-parsing variables initialization */ - blksize_set = 0; - - if ( !*opt ) - return; - - for (po = options; po->o_opt; po++) - if (!strcasecmp(po->o_opt, opt)) { - if (po->o_fnc(val, &ret)) { - if (*ap + strlen(opt) + strlen(ret) + 2 >= - ackbuf + sizeof(ackbuf)) { - nak(ENOSPACE); /* EOPTNEG? */ - exit(0); - } - *ap = strrchr(strcpy(strrchr(strcpy(*ap, opt),'\0') + 1, - ret),'\0') + 1; - } else { - nak(EOPTNEG); - exit(0); - } - break; + 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; } - return; } /* * Modify the filename, if applicable. If it returns NULL, deny the access. */ -char * -rewrite_access(char *filename, int mode) +static char *rewrite_access(const struct formats *pf, char *filename, + int mode, int af, const char **msg) { -#ifdef WITH_REGEX - if ( rewrite_rules ) { - char *newname = rewrite_string(filename, rewrite_rules, mode != RRQ); - filename = newname; - } -#else - (void)mode; /* Suppress unused warning */ -#endif - return filename; + if (rewrite_rules) { + char *newname = + rewrite_string(pf, filename, rewrite_rules, mode, af, + rewrite_macros, msg); + filename = newname; + } + return filename; } -FILE *file; +#else +static char *rewrite_access(const struct formats *pf, char *filename, + int mode, int af, const char **msg) +{ + (void)pf; + (void)mode; /* Avoid warning */ + (void)msg; + (void)af; + return filename; +} +#endif + /* * Validate file access. Since we * have no uid or gid, for now require * file to exist and be publicly - * readable/writable. + * 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. */ -int -validate_access(char *filename, int mode, struct formats *pf) +static int validate_access(char *filename, int mode, + const struct formats *pf, const char **errmsg) { - struct stat stbuf; - int i, len; - int fd, wmode; - char *cp; - const char **dirp; - - tsize_ok = 0; - - if (!secure) { - if (*filename != '/') - return (EACCESS); + 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); + } + } + /* - * prevent tricksters from getting around the directory - * restrictions + * We use different a different permissions scheme if `cancreate' is + * set. */ - len = strlen(filename); - for ( i = 1 ; i < len-3 ; i++ ) { - cp = filename + i; - if ( *cp == '.' && memcmp(cp-1, "/../", 4) == 0) - return(EACCESS); + 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; + } } - for (dirp = dirs; *dirp; dirp++) - if (strncmp(filename, *dirp, strlen(*dirp)) == 0) - break; - if (*dirp==0 && dirp!=dirs) - return (EACCESS); - } - - /* - * We use different a different permissions scheme if `cancreate' is - * set. - */ - wmode = O_TRUNC; - if (stat(filename, &stbuf) < 0) { - if (!cancreate) - return (errno == ENOENT ? ENOTFOUND : EACCESS); - else { - if ((errno == ENOENT) && (mode != RRQ)) - wmode |= O_CREAT; - else - return(EACCESS); - } - } else { + if (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 ((stbuf.st_mode&(S_IREAD >> 6)) == 0) - return (EACCESS); - tsize = stbuf.st_size; - /* We don't know the tsize if conversion is needed */ - tsize_ok = !pf->f_convert; + 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 ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) - return (EACCESS); - tsize = 0; - tsize_ok = 1; + 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; } - } - fd = open(filename, mode == RRQ ? O_RDONLY : (O_WRONLY|wmode), 0666); - if (fd < 0) - return (errno + 100); - /* - * If the file was created, set default permissions. - */ - if ((wmode & O_CREAT) && fchmod(fd, 0666) < 0) { - int serrno = errno; - - close(fd); - unlink(filename); - - return (serrno + 100); - } - file = fdopen(fd, (mode == RRQ)? "r":"w"); - if (file == NULL) - return (errno + 100); - return (0); -} -int timeout; -sigjmp_buf timeoutbuf; + stdio_mode[0] = (mode == RRQ) ? 'r' : 'w'; + stdio_mode[1] = (pf->f_convert) ? 't' : 'b'; + stdio_mode[2] = '\0'; -/* Handle timeout signal */ -void -timer(int sig) -{ - (void)sig; /* Suppress unused warning */ - timeout += rexmtval; - if (timeout >= maxtimeout) - exit(0); - siglongjmp(timeoutbuf, 1); + file = fdopen(fd, stdio_mode); + if (file == NULL) + exit(EX_OSERR); /* Internal error */ + + return (0); } /* * Send the requested file. */ -void -tftp_sendfile(struct formats *pf, struct tftphdr *oap, int oacklen) +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 */ - int size, n; - - ap = (struct tftphdr *)ackbuf; - - if (oap) { - timeout = 0; - (void)sigsetjmp(timeoutbuf,1); - oack: - if (send(peer, oap, oacklen, 0) != oacklen) { - syslog(LOG_ERR, "tftpd: oack: %m\n"); - goto abort; - } - for ( ; ; ) { - set_signal(SIGALRM, timer, SA_RESTART); - alarm(rexmtval); - n = recv(peer, ackbuf, sizeof(ackbuf), 0); - alarm(0); - if (n < 0) { - syslog(LOG_ERR, "tftpd: read: %m\n"); - goto abort; - } - ap->th_opcode = ntohs((u_short)ap->th_opcode); - ap->th_block = ntohs((u_short)ap->th_block); - - if (ap->th_opcode == ERROR) { - syslog(LOG_ERR, "tftp: client does not accept " - "options\n"); - goto abort; - } - if (ap->th_opcode == ACK) { - if (ap->th_block == 0) - break; - /* Resynchronize with the other side */ - (void)synchnet(peer); - goto oack; - } - } - } + 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; - dp = r_init(); - do { - size = readit(file, &dp, pf->f_convert); - if (size < 0) { - nak(errno + 100); - goto abort; + 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->th_opcode = htons((u_short)DATA); - dp->th_block = htons((u_short)block); - timeout = 0; - (void) sigsetjmp(timeoutbuf,1); - - if (send(peer, dp, size + 4, 0) != size + 4) { - syslog(LOG_ERR, "tftpd: write: %m"); - goto abort; - } - read_ahead(file, pf->f_convert); - for ( ; ; ) { - set_signal(SIGALRM, timer, SA_RESTART); - alarm(rexmtval); /* read the ack */ - n = recv(peer, ackbuf, sizeof (ackbuf), 0); - alarm(0); - if (n < 0) { - syslog(LOG_ERR, "tftpd: read(ack): %m"); - goto abort; - } - ap->th_opcode = ntohs((u_short)ap->th_opcode); - ap->th_block = ntohs((u_short)ap->th_block); - - if (ap->th_opcode == ERROR) - goto abort; - - if (ap->th_opcode == ACK) { - if (ap->th_block == block) { - break; - } - /* Re-synchronize with the other side */ - (void) synchnet(peer); - /* - * 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. - */ - } - - } - block++; - } while (size == segsize); - abort: - (void) fclose(file); + + 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); } -/* Bail out signal handler */ -void -justquit(int sig) -{ - (void)sig; /* Suppress unused warning */ - exit(0); -} - - /* * Receive a file. */ -void -tftp_recvfile(struct formats *pf, struct tftphdr *oap, int oacklen) +static void tftp_recvfile(const struct formats *pf, + struct tftphdr *oack, 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; + struct tftphdr *dp; + int n, size; + /* These are "static" to avoid longjmp funnies */ + static struct tftphdr *oap; + 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 = 0; - - if (!block && oap) { - ap = (struct tftphdr *)ackbuf; - acksize = oacklen; - } else { - ap = (struct tftphdr *)ackbuf; - ap->th_opcode = htons((u_short)ACK); - ap->th_block = htons((u_short)block); - acksize = 4; - } - block++; - (void) sigsetjmp(timeoutbuf,1); - set_signal(SIGALRM, timer, SA_RESTART); - send_ack: - if (send(peer, ackbuf, acksize, 0) != acksize) { - syslog(LOG_ERR, "tftpd: write(ack): %m"); - goto abort; - } + oap = oack; + + 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); - for ( ; ; ) { - set_signal(SIGALRM, timer, SA_RESTART); - alarm(rexmtval); - n = recv(peer, dp, PKTSIZE, 0); - alarm(0); - if (n < 0) { /* really? */ - syslog(LOG_ERR, "tftpd: read: %m"); - goto abort; - } - dp->th_opcode = ntohs((u_short)dp->th_opcode); - dp->th_block = ntohs((u_short)dp->th_block); - if (dp->th_opcode == ERROR) - goto abort; - if (dp->th_opcode == DATA) { - if (dp->th_block == block) { - break; /* normal */ - } - /* Re-synchronize with the other side */ - (void) synchnet(peer); - if (dp->th_block == (block-1)) - goto send_ack; /* rexmit */ - } + (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 */ } - /* size = write(file, dp->th_data, n - 4); */ - size = writeit(file, &dp, n - 4, pf->f_convert); - if (size != (n-4)) { /* ahem */ - if (size < 0) nak(errno + 100); - else nak(ENOSPACE); - goto abort; - } - } while (size == segsize); - write_behind(file, pf->f_convert); - (void) fclose(file); /* close data file */ - - ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ - ap->th_block = htons((u_short)(block)); - (void) send(peer, ackbuf, 4, 0); - - set_signal(SIGALRM, justquit, SA_RESETHAND); /* just quit on timeout */ - alarm(rexmtval); - n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ - alarm(0); - if (n >= 4 && /* if read some data */ - dp->th_opcode == DATA && /* and got a data block */ - block == dp->th_block) { /* then my last ack was lost */ - (void) send(peer, ackbuf, 4, 0); /* resend final ack */ - } - abort: - return; + abort: + return; } -struct errmsg { - int e_code; - const char *e_msg; -} errmsgs[] = { - { EUNDEF, "Undefined error code" }, - { ENOTFOUND, "File not found" }, - { EACCESS, "Access violation" }, - { ENOSPACE, "Disk full or allocation exceeded" }, - { EBADOP, "Illegal TFTP operation" }, - { EBADID, "Unknown transfer ID" }, - { EEXISTS, "File already exists" }, - { ENOUSER, "No such user" }, - { EOPTNEG, "Failure to negotiate RFC2347 options" }, - { -1, 0 } +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. */ -void -nak(int error) +static void nak(int error, const char *msg) { - struct tftphdr *tp; - int length; - struct errmsg *pe; - - tp = (struct tftphdr *)buf; - tp->th_opcode = htons((u_short)ERROR); - tp->th_code = htons((u_short)error); - for (pe = errmsgs; pe->e_code >= 0; pe++) - if (pe->e_code == error) - break; - if (pe->e_code < 0) { - pe->e_msg = strerror(error - 100); - tp->th_code = EUNDEF; /* set 'undef' errorcode */ - } - strcpy(tp->th_msg, pe->e_msg); - length = strlen(pe->e_msg); - tp->th_msg[length] = '\0'; - length += 5; - - if ( verbosity >= 2 ) { - syslog(LOG_INFO, "sending NAK (%d, %s) to %s", - error, tp->th_msg, inet_ntoa(from.sin_addr)); - } - - if (send(peer, buf, length, 0) != length) - syslog(LOG_ERR, "nak: %m"); + 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 index 789ee94..277e5d2 100644 --- a/tftpd/tftpd.h +++ b/tftpd/tftpd.h @@ -1,6 +1,5 @@ -/* $Id$ */ /* ----------------------------------------------------------------------- * - * + * * Copyright 2001 H. Peter Anvin - All Rights Reserved * * This program is free software available under the same license @@ -24,4 +23,13 @@ char *tfstrdup(const char *); extern int verbosity; +struct formats { + const char *f_mode; + char *(*f_rewrite) (const struct formats *, char *, int, 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; +}; + #endif diff --git a/version b/version new file mode 100644 index 0000000..d346e2a --- /dev/null +++ b/version @@ -0,0 +1 @@ +5.3