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 43a2de5..6b7b53c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,191 @@ -$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. 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 dd860e6..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: MCONFIG acconfig.h version.h +%.build: config/MCONFIG config/config.h version.h $(MAKE) -C $(patsubst %.build, %, $@) -%.install: MCONFIG acconfig.h version.h +%.install: config/MCONFIG config/config.h version.h $(MAKE) -C $(patsubst %.install, %, $@) install %.clean: @@ -15,12 +15,12 @@ SUB = lib tftp tftpd %.distclean: $(MAKE) -C $(patsubst %.distclean, %, $@) distclean -all: MCONFIG $(patsubst %, %.build, $(SUB)) +all: config/MCONFIG $(patsubst %, %.build, $(SUB)) -tftp.build: lib.build -tftpd.build: lib.build +tftp.build: lib.build common.build +tftpd.build: lib.build common.build -install: MCONFIG $(patsubst %, %.install, $(SUB)) +install: config/MCONFIG $(patsubst %, %.install, $(SUB)) clean: localclean $(patsubst %, %.clean, $(SUB)) @@ -30,37 +30,40 @@ localclean: distclean: localdistclean $(patsubst %, %.distclean, $(SUB)) localdistclean: localclean - rm -f MCONFIG config.status config.log acconfig.h *~ \#* + rm -f config/config/MCONFIG config.status config.log config/config.h *~ \#* rm -rf *.cache 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 -# Adding "configure" to the dependencies serializes this with running -# autoconf, because there are apparently race conditions between -# autoconf and autoheader. -acconfig.h.in: configure.in configure aclocal.m4 - rm -f acconfig.h.in acconfig.h - autoheader +configure: configure.ac + sh autogen.sh -configure: configure.in aclocal.m4 - rm -f MCONFIG configure config.log acconfig.h config.cache - autoconf +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 58eab7f..644babb 100644 --- a/README.security +++ b/README.security @@ -1,7 +1,3 @@ -$Id$ - - ======================================= - 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 diff --git a/aclocal.m4 b/aclocal.m4 deleted file mode 100644 index 3fd7844..0000000 --- a/aclocal.m4 +++ /dev/null @@ -1,206 +0,0 @@ -dnl $Id$ -dnl ----------------------------------------------------------------------- -dnl -dnl Copyright 1999-2002 H. Peter Anvin - All Rights Reserved -dnl -dnl This program is free software; you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published by -dnl the Free Software Foundation, Inc., 53 Temple Place Ste 330, -dnl Bostom MA 02111-1307, USA; either version 2 of the License, or -dnl (at your option) any later version; incorporated herein by reference. -dnl -dnl ----------------------------------------------------------------------- - -dnl -------------------------------------------------------------------------- -dnl PA_ADD_CFLAGS() -dnl -dnl Attempt to add the given option to CFLAGS, if it doesn't break compilation -dnl -------------------------------------------------------------------------- -AC_DEFUN(PA_ADD_CFLAGS, -[AC_MSG_CHECKING([if $CC accepts $1]) - pa_add_cflags__old_cflags="$CFLAGS" - CFLAGS="$CFLAGS $1" - AC_TRY_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([ -#ifdef HAVE_SETJMP_H -#include -#endif], - [sigjmp_buf buf; - sigsetjmp(buf,1); - siglongjmp(buf,2);], - AC_MSG_RESULT([yes]) - $1, - AC_MSG_RESULT([no]) - $2)]) - -dnl -------------------------------------------------------------------------- -dnl PA_MSGHDR_MSG_CONTROL -dnl -dnl Does struct msghdr have the msg_control field? -dnl -------------------------------------------------------------------------- -AH_TEMPLATE([HAVE_MSGHDR_MSG_CONTROL], -[Define if struct msghdr has the msg_control field.]) - -AC_DEFUN(PA_MSGHDR_MSG_CONTROL, - [AC_CHECK_MEMBER(struct msghdr.msg_control, - [AC_DEFINE(HAVE_MSGHDR_MSG_CONTROL)], - [], - [ -#include -#include -#include - ])]) - -dnl ------------------------------------------------------------------------ -dnl PA_STRUCT_IN_PKTINFO -dnl -dnl Look for definition of struct in_pktinfo, which at least has an -dnl ipi_addr member. Some versions of glibc lack struct in_pktinfo; -dnl if so we need to include the definition ourselves -- but we only -dnl want to do that if absolutely necessary! -dnl -dnl We don't use AC_CHECK_MEMBER() here, since at least in autoconf 2.52 -dnl this is broken for a member of structure type. -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_CHECK_INTTYPES_H_SANE -dnl -dnl At least some versions of AIX 4 have macros which are -dnl completely broken. Try to detect those. -dnl -------------------------------------------------------------------------- -AH_TEMPLATE([INTTYPES_H_IS_SANE], -[Define if the macros in are usable]) - -AC_DEFUN(PA_CHECK_INTTYPES_H_SANE, -[AC_CHECK_HEADERS(inttypes.h, - [ - AC_MSG_CHECKING([if inttypes.h is sane]) - AC_TRY_LINK( - [ -#include -#include - ], - [uintmax_t max = UINTMAX_C(0); - printf("%"PRIuMAX"\n", max);], - AC_MSG_RESULT([yes]) - AC_DEFINE(INTTYPES_H_IS_SANE), - AC_MSG_RESULT([no (AIX, eh?)])) - ]) -]) - -dnl ------------------------------------------------------------------------ -dnl PA_WITH_BOOL -dnl -dnl PA_WITH_BOOL(option, default, help, enable, disable) -dnl -dnl Provides a more convenient way to specify --with-option and -dnl --without-option, with a default. default should be either 0 or 1. -dnl ------------------------------------------------------------------------ -AC_DEFUN(PA_WITH_BOOL, -[AC_ARG_WITH([$1], [$3], -if test ["$withval"] != no; then -[$4] -else -[$5] -fi, -if test [$2] -ne 0; then -[$4] -else -[$5] -fi)]) - -dnl -------------------------------------------------------------------------- -dnl PA_HEADER_DEFINES -dnl -dnl PA_HEADER_DEFINES(header, type, value) -dnl -------------------------------------------------------------------------- -AC_DEFUN(PA_HEADER_DEFINES, -[AC_MSG_CHECKING([if $1 defines $3]) - AH_TEMPLATE([HAVE_$3_DEFINITION], [Define if $1 defines $3]) - AC_TRY_COMPILE([ -#include <$1> -], -[ -int main() -{ - $2 dummy = $3; - return 0; -} -], -[ - pa_header_define=`echo HAVE_$3_DEFINITION | tr '[a-z]' '[A-Z]'` - AC_DEFINE_UNQUOTED($pa_header_define) - AC_MSG_RESULT(yes) -], -[ - AC_MSG_RESULT(no) -])]) 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 ef0d408..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,12 +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" +#include "config/config.h" /* autogenerated configuration header */ /* Standard includes */ #include +#include +#include +#include +#include #ifdef HAVE_SYS_TYPES_H #include @@ -33,28 +43,13 @@ #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 @@ -74,15 +69,8 @@ #include #endif -#ifdef TIME_WITH_SYS_TIME +#ifdef HAVE_SYS_TIME_H #include -#include -#else -#if HAVE_SYS_TIME_H -#include -#else -#include -#endif #endif #ifdef HAVE_GRP_H @@ -93,10 +81,26 @@ #include #endif -#include -#include - +#ifdef HAVE_SYS_SOCKET_H #include +#else +#ifdef HAVE_WINSOCK2_H +#include +#else +#ifdef HAVE_WINSOCK_H +#include +#endif +#endif +#endif +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_GETOPT_LONG +#include +#else +#include "lib/getopt.h" +#endif /* Test for EAGAIN/EWOULDBLOCK */ #ifdef EAGAIN @@ -111,10 +115,10 @@ /* Some broken systems care about text versus binary, but real Unix systems don't... */ -#ifndef HAVE_O_TEXT_DEFINITION +#if !HAVE_DECL_O_TEXT #define O_TEXT 0 #endif -#ifndef HAVE_O_BINARY_DEFINITION +#if !HAVE_DECL_O_BINARY #define O_BINARY 0 #endif @@ -124,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 @@ -200,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 @@ -244,11 +253,9 @@ typedef unsigned long u_long; #include -#ifndef HAVE_IPPORT_TFTP_DEFINITION -#ifndef IPPORT_TFTP +#if !HAVE_DECL_IPPORT_TFTP && !defined(IPPORT_TFTP) #define IPPORT_TFTP 69 #endif -#endif /* arpa/{inet,tftp}.h, and possible missing pieces */ @@ -265,6 +272,72 @@ typedef unsigned long u_long; #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" diff --git a/MCONFIG.in b/config/MCONFIG.in similarity index 80% rename from MCONFIG.in rename to config/MCONFIG.in index 68aab37..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,6 +31,9 @@ MANDIR = @mandir@ # System binaries SBINDIR = @sbindir@ +# Data root directory +datarootdir = @datarootdir@ + # Binary suffixes O = @OBJEXT@ X = @EXEEXT@ @@ -51,8 +57,8 @@ CFLAGS = @CFLAGS@ -I$(SRCROOT) LDFLAGS = @LDFLAGS@ # Libraries (client and server) -TFTP_LIBS = @TFTP_LIBS@ -TFTPD_LIBS = @TFTPD_LIBS@ +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 eba4d25..0000000 --- a/configure.in +++ /dev/null @@ -1,198 +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 - -dnl -dnl Feature-test macros. These need to be set in CFLAGS, rather in -dnl acconfig.h, or "configure" will run in a different environment than -dnl we eventually we build in. -dnl - -dnl Needed on Solaris/cc or Solaris/gcc -CFLAGS="$CFLAGS -D_XPG4_2" -CFLAGS="$CFLAGS -D_XOPEN_SOURCE" -CFLAGS="$CFLAGS -D__EXTENSIONS__" - -dnl Needed on some glibc systems -CFLAGS="$CFLAGS -D_BSD_SOURCE" -CFLAGS="$CFLAGS -D_ISO9X_SOURCE" - -dnl Needed on Digital Unix -CFLAGS="$CFLAGS -D_OSF_SOURCE" -CFLAGS="$CFLAGS -D_XOPEN_SOURCE_EXTENDED" - -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(stdint.h) -PA_CHECK_INTTYPES_H_SANE -AC_CHECK_HEADERS(fcntl.h) -AC_CHECK_HEADERS(grp.h) -AC_CHECK_HEADERS(libgen.h) -AC_CHECK_HEADERS(memory.h) -AC_CHECK_HEADERS(setjmp.h) -AC_CHECK_HEADERS(stddef.h) -AC_CHECK_HEADERS(stdlib.h) -AC_CHECK_HEADERS(string.h) -AC_CHECK_HEADERS(strings.h) -AC_CHECK_HEADERS(sysexits.h) -AC_CHECK_HEADERS(time.h) -AC_CHECK_HEADERS(unistd.h) -AC_CHECK_HEADERS(sys/filio.h) -AC_CHECK_HEADERS(sys/stat.h) -AC_CHECK_HEADERS(sys/time.h) -AC_CHECK_HEADERS(sys/types.h) -AC_CHECK_HEADERS(arpa/inet.h) -AC_HEADER_TIME -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_OFF_T -AC_TYPE_PID_T -AC_TYPE_MODE_T -AC_TYPE_SIZE_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(dup2) -AC_CHECK_FUNCS(fcntl) -AC_CHECK_FUNCS(setsid) -AC_CHECK_FUNCS(recvmsg) -AC_CHECK_FUNCS(ftruncate) -AC_CHECK_FUNCS(setreuid) -AC_CHECK_FUNCS(setregid) -AC_CHECK_FUNCS(initgroups) -AC_CHECK_FUNCS(setgroups) - -dnl Solaris 8 has [u]intmax_t but not strtoumax(). How utterly braindamaged. -AC_CHECK_FUNCS(strtoumax) -AC_CHECK_FUNCS(strtoull) - -PA_MSGHDR_MSG_CONTROL -PA_STRUCT_IN_PKTINFO - -PA_HEADER_DEFINES(fcntl.h, int, O_NONBLOCK) -PA_HEADER_DEFINES(fcntl.h, int, O_BINARY) -PA_HEADER_DEFINES(fcntl.h, int, O_TEXT) - -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.${OBJEXT}") -AC_SEARCH_LIBS(xstrdup, iberty, , LIBXTRA=true LIBOBJS="$LIBOBJS xstrdup.${OBJEXT}") -AC_SEARCH_LIBS(bsd_signal, bsd, , LIBXTRA=true LIBOBJS="$LIBOBJS bsdsignal.${OBJEXT}") -if $LIBXTRA; then - LIBS="../lib/libxtra.a $LIBS" -fi - -dnl -dnl These libraries apply to the server only -dnl - -common_libs="$LIBS" - -PA_HEADER_DEFINES(netinet/in.h, int, IPPORT_TFTP) - -PA_WITH_BOOL(tcpwrappers, 1, -[ --without-tcpwrappers disable tcpwrapper permissions checking], -[ - AC_SEARCH_LIBS(yp_get_default_domain, [nsl resolv]) - PA_HAVE_TCPWRAPPERS -],:) - - -AH_TEMPLATE([WITH_REGEX], -[Define if we are compiling with regex filename remapping.]) - -PA_WITH_BOOL(remap, 1, -[ --without-remap disable regex-based filename remapping], -[ - AC_CHECK_HEADER(regex.h, - [ - AC_SEARCH_LIBS(regcomp, [regex rx], - [ - AC_DEFINE(WITH_REGEX) - TFTPDOBJS="remap.${OBJEXT} $TFTPDOBJS" - ]) - ]) -],:) - -TFTPD_LIBS="$LIBS" -LIBS="$common_libs" - -dnl -dnl These libraries apply to the client only -dnl - -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, - [ - AC_SEARCH_LIBS(readline, [readline history], - [ - AC_DEFINE(WITH_READLINE) - ]) - AC_CHECK_HEADERS(readline/history.h) - ]) -],:) - -TFTP_LIBS="$LIBS" -LIBS="$common_libs" - -AC_SUBST(TFTP_LIBS) -AC_SUBST(TFTPD_LIBS) -AC_SUBST(LIBOBJS) -AC_SUBST(TFTPDOBJS) - -AC_PROG_LN_S -AC_PROG_RANLIB -AC_PROG_INSTALL - -AC_CONFIG_HEADER(acconfig.h) -AC_OUTPUT(MCONFIG) diff --git a/lib/Makefile b/lib/Makefile index 615a3fa..a7fd057 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -4,10 +4,14 @@ SRCROOT = .. --include ../MCONFIG +-include ../config/MCONFIG include ../MRULES +ifeq ($(LIBOBJS),) +all: +else all: libxtra.a +endif install: @@ -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 index c97c66b..1f72922 100755 --- a/release.sh +++ b/release.sh @@ -1,11 +1,8 @@ -#!/bin/sh -x -# $Id$ +#!/bin/sh -xe # # Script for generating a release # -CVS='cvs-real -d hpa@terminus.zytor.com:/home/hpa/cvsroot' -MODULE=tftp PACKAGE=tftp-hpa if [ -z "$1" ]; then @@ -14,22 +11,41 @@ if [ -z "$1" ]; then fi release="$1" -cvsrelease=$PACKAGE-`echo "$release" | tr '.' '_'` +releasetag=$PACKAGE-$release releasedir=$PACKAGE-$release -echo $release > version -$CVS commit -m 'Update version for release' version +GIT_DIR=`cd "${GIT_DIR-.git}" && pwd` +export GIT_DIR -$CVS tag -F $cvsrelease +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 $tmpdir +mkdir -p $tmpdir cd $tmpdir -$CVS export -r $cvsrelease $MODULE -mv $MODULE $releasedir +mkdir -p $releasedir +git archive --format=tar $releasetag | tar -xf - -C $releasedir cd $releasedir make release rm -f release.sh 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 0ff0451..9faa62f 100644 --- a/tftp/Makefile +++ b/tftp/Makefile @@ -1,17 +1,17 @@ SRCROOT = .. VERSION = $(shell cat ../version) --include ../MCONFIG +-include ../config/MCONFIG include ../MRULES -OBJS = tftp.$(O) main.$(O) tftpsubs.$(O) +OBJS = tftp.$(O) main.$(O) all: tftp$(X) tftp.1 tftp$(X): $(OBJS) $(CC) $(LDFLAGS) $^ $(TFTP_LIBS) -o $@ -$(OBJS): tftpsubs.h +$(OBJS): ../common/tftpsubs.h tftp.1: tftp.1.in ../version sed -e 's/@@VERSION@@/$(VERSION)/g' < $< > $@ diff --git a/tftp/extern.h b/tftp/extern.h index 401608e..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, const char *, const char *); -void tftp_sendfile (int, const char *, const 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 7f383fa..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,17 +31,7 @@ * 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 */ @@ -53,7 +40,6 @@ static const char *rcsid UNUSED = */ #include #include -#include #ifdef WITH_READLINE #include #ifdef HAVE_READLINE_HISTORY_H @@ -63,755 +49,898 @@ static const char *rcsid UNUSED = #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 modes { - const char *m_name; - const char *m_mode; - int m_openflags; + 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 } + {"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 -struct sockaddr_in peeraddr; -int f; +#ifdef HAVE_IPV6 +int ai_fam = AF_UNSPEC; +int ai_fam_sock = AF_UNSPEC; +#else +int ai_fam = AF_INET; +int ai_fam_sock = AF_INET; +#endif + +union sock_addr peeraddr; +int f = -1; u_short port; -int trace; -int verbose; -int connected; +int trace; +int verbose; +int literal; +int connected; const struct modes *mode; #ifdef WITH_READLINE -char *line = NULL; +char *line = NULL; #else -char line[LBUFLEN]; +char line[LBUFLEN]; #endif -int margc; -char *margv[20]; +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 struct modes *); +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; - int c; - int pargc; - char **pargv; - - while ((c = getopt(argc, argv, "Vv")) != -1) { - switch (c) { - case 'v': - verbose = 1; - break; - case 'V': - /* Print version and configuration to stdout and exit */ - printf("%s\n", TFTP_CONFIG_STR); - exit(0); - default: - fprintf(stderr, "Usage: %s [-v] [host]\n", argv[0]); - exit(EX_USAGE); - } - } - - pargc = argc - (optind-1); - pargv = argv + (optind-1); - - sp = getservbyname("tftp", "udp"); - if (sp == 0) { - /* Use canned values */ - 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"; - 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); - } - mode = MODE_DEFAULT; - bsd_signal(SIGINT, intr); - if (pargc > 1) { - if (sigsetjmp(toplevel,1) != 0) - exit(0); - setpeer(pargc, pargv); - } - if (sigsetjmp(toplevel,1) != 0) - (void)putchar('\n'); - -#ifdef WITH_READLINE -#ifdef HAVE_READLINE_HISTORY_H - 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 -#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); - free(eline); + len = strlen(partial); + eline = readline(mprompt); + if (!eline) + exit(0); /* EOF */ + + elen = strlen(eline); + + if (line) { + free(line); + line = NULL; + } + line = xmalloc(len + elen + 1); + strcpy(line, partial); + strcpy(line + len, eline); + free(eline); #ifdef HAVE_READLINE_HISTORY_H - add_history(line); + 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 (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; + } - 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); + 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; - 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); - } - } + 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) { - printf("Connected to %s (%s), port %u\n", - hostname, inet_ntoa(peeraddr.sin_addr), - (unsigned int)ntohs(port)); - } - connected = 1; + if (verbose) { + char tmp[INET6_ADDRSTRLEN], *tp; + tp = (char *)inet_ntop(peeraddr.sa.sa_family, SOCKADDR_P(&peeraddr), + tmp, INET6_ADDRSTRLEN); + if (!tp) + tp = (char *)"???"; + printf("Connected to %s (%s), port %u\n", + hostname, tp, (unsigned int)ntohs(port)); + } + connected = 1; } -void -modecmd(int argc, char *argv[]) +void modecmd(int argc, char *argv[]) { - const 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->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 */ - } + 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(MODE_OCTET); -} - -void -setascii(int argc, char *argv[]) +void setbinary(int argc, char *argv[]) { - (void)argc; (void)argv; /* Quiet unused warning */ - settftpmode(MODE_NETASCII); + (void)argc; + (void)argv; /* Quiet unused warning */ + settftpmode(MODE_OCTET); } -static void -settftpmode(const struct modes *newmode) +void setascii(int argc, char *argv[]) { - mode = newmode; - if (verbose) - printf("mode set to %s\n", mode->m_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|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); - peeraddr.sin_port = 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); - peeraddr.sin_port = port; - tftp_sendfile(fd, targ, mode->m_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 = 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); - peeraddr.sin_port = 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); - peeraddr.sin_port = port; - tftp_recvfile(fd, src, mode->m_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->m_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 #ifdef HAVE_READLINE_HISTORY_H - add_history(line); + add_history(line); #endif #endif - makeargv(); - if (margc == 0) - continue; + makeargv(); + if (margc == 0) + continue; - c = getcmd(margv[0]); - if (c == (struct cmd *)-1) { - printf("?Ambiguous command\n"); - continue; - } - if (c == 0) { - printf("?Invalid command\n"); - continue; - } - (*c->handler)(margc, margv); - } + 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; - printf("%s\n", VERSION); + 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); - } + 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.in b/tftp/tftp.1.in index 7df6ca0..b41f7b5 100644 --- a/tftp/tftp.1.in +++ b/tftp/tftp.1.in @@ -1,5 +1,4 @@ .\" -*- nroff -*- --------------------------------------------------------- * -.\" $Id$ .\" .\" Copyright (c) 1990, 1993, 1994 .\" The Regents of the University of California. All rights reserved. @@ -31,18 +30,19 @@ .\" SUCH DAMAGE. .\" .\"----------------------------------------------------------------------- */ -.TH TFTP 1 "13 November 2001" "tftp-hpa @@VERSION@@" "UNIX User's Manual" +.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 -.RI [ options... ] -.RI [ host ] +[ \fIoptions...\fP ] +[\fIhost\fP [\fIport\fP]] +[\fB\-c\fP \fIcommand\fP] .br .SH DESCRIPTION .B tftp -is a client for the IPv4 Trivial file Transfer Protocol, which can be +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 @@ -55,6 +55,27 @@ as the default host for future transfers (see the 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 @@ -111,7 +132,12 @@ 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. +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; @@ -143,7 +169,9 @@ 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. +as directory separator. Enable +.B literal +mode to prevent special treatment of the ':' character (e.g. C:\\dir\\file). .TP .B quit Exit diff --git a/tftp/tftp.c b/tftp/tftp.c index b35473e..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,36 +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 "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; +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); @@ -76,365 +61,365 @@ static void tpacket(const char *, struct tftphdr *, int); /* * Send the requested file. */ -void -tftp_sendfile(int fd, const char *name, const 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; - convert = !strcmp(mode, "netascii"); - file = fdopen(fd, convert ? "rt" : "rb"); - 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, const char *name, const 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; - convert = !strcmp(mode, "netascii"); - file = fdopen(fd, convert ?"wt":"wb"); - 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 3dd34bd..0000000 --- a/tftp/tftpsubs.c +++ /dev/null @@ -1,274 +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 - -#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 0729a26..5963581 100644 --- a/tftpd/Makefile +++ b/tftpd/Makefile @@ -1,23 +1,17 @@ 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) all: tftpd$(X) tftpd.8 tftpd$(X): $(OBJS) $(CC) $(LDFLAGS) $^ $(TFTPD_LIBS) -o $@ -tftpsubs.c: - $(LN_S) -f ../tftp/tftpsubs.c . - -tftpsubs.h: - $(LN_S) -f ../tftp/tftpsubs.h . - -$(OBJS): tftpsubs.h +$(OBJS): ../common/tftpsubs.h tftpd.8: tftpd.8.in ../version sed -e 's/@@VERSION@@/$(VERSION)/g' < $< > $@ diff --git a/tftpd/misc.c b/tftpd/misc.c index d3c49cd..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,27 +14,19 @@ * Minor help routines. */ -#include "config.h" /* Must be included first! */ +#include "config.h" /* Must be included first! */ #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); + } } /* @@ -43,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; } /* @@ -58,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 1177981..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 @@ -20,5 +19,4 @@ 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 6534b01..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,330 +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, match_pattern_callback macrosub) +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' ) { - char macro = pattern[1]; - if ( macro >= '0' && macro <= '9' ) { - 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; - } - } - } else { - int sublen; - if ( macrosub && - (sublen = macrosub(macro, string)) >= 0 ) { - len += sublen; - if ( string ) - string += sublen; - } else { - len++; - if ( string ) - *string++ = pattern[1]; - } - } - 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, match_pattern_callback macrosub) +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, macrosub); - newstr = tfmalloc(len+1); - genmatchstring(newstr, ruleptr->pattern, current, - pmatch, macrosub); - free(current); - current = newstr; - if ( verbosity >= 3 ) { - syslog(LOG_INFO, "remap: rule %d: rewrite: %s", - ruleptr->nrule, current); - } - } - } else { - break; /* No match, terminate unconditionally */ - } - /* If the rule is global, keep going until no match */ - } while ( ruleptr->rule_flags & RULE_GLOBAL ); - - if ( was_match ) { - was_match = 0; - - if ( ruleptr->rule_flags & RULE_EXIT ) { - if ( verbosity >= 3 ) { - syslog(LOG_INFO, "remap: rule %d: exit", ruleptr->nrule); - } - return current; /* Exit here, we're done */ - } else if ( ruleptr->rule_flags & RULE_RESTART ) { - ruleptr = rules; /* Start from the top */ - if ( verbosity >= 3 ) { - syslog(LOG_INFO, "remap: rule %d: restart", ruleptr->nrule); - } - } + if (verbosity >= 3) { + syslog(LOG_INFO, "remap: done"); } - } + return current; - 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 123b7d7..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 @@ -27,7 +26,7 @@ struct rule; 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 *); +typedef int (*match_pattern_callback) (char, char *); /* Read a rule file */ struct rule *parserulefile(FILE *); @@ -36,9 +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, - match_pattern_callback); +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.in b/tftpd/tftpd.8.in index 388da0a..3d43325 100644 --- a/tftpd/tftpd.8.in +++ b/tftpd/tftpd.8.in @@ -1,10 +1,9 @@ .\" -*- nroff -*- --------------------------------------------------------- * -.\" $Id$ -.\" +.\" .\" Copyright (c) 1990, 1993, 1994 .\" The Regents of the University of California. All rights reserved. .\" -.\" Copyright 2001 H. Peter Anvin - 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 @@ -31,17 +30,17 @@ .\" SUCH DAMAGE. .\" .\"----------------------------------------------------------------------- */ -.TH TFTPD 8 "16 November 2001" "tftp-hpa @@VERSION@@" "UNIX System Manager's Manual" +.TH TFTPD 8 "7 June 2014" "tftp-hpa @@VERSION@@" "System Manager's Manual" .SH NAME .B tftpd -\- IPv4 Trivial File Transfer Protocol server +\- Trivial File Transfer Protocol server .SH SYNOPSIS .B in.tftpd .RI [ options... ] .I directory... .SH DESCRIPTION .B tftpd -is a server for the IPv4 Trivial File Transfer Protocol. The TFTP +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 , @@ -49,52 +48,70 @@ but can also run standalone. .PP .SH OPTIONS .TP -.B \-l +\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 \-t +.B \-\-timeout option is ignored, and the -.B \-a +.B \-\-address option can be used to specify a specific local address or port to listen to. .TP -\fB\-a\fP \fI[address][:port]\fP +\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 \-l +.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 -.B \-c +\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 \-p +.B \-\-permissive or -.B \-U +.B \-\-umask options are specified. .TP -.B \-s +\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 \-s +.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\-u\fP \fIusername\fP +\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 @@ -102,21 +119,26 @@ 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\-U\fP \fIumask\fP +\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 \-p +.B \-\-permissive option is not specified, or inherited from the invoking process if -.B \-p +.B \-\-permissive is specified. .TP -.B \-p +\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 \-u +.B \-\-user option. .TP -\fB\-t\fP \fItimeout\fP +\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 @@ -125,7 +147,15 @@ before terminating the server. will then respawn the server when another request comes in. The default is 900 (15 minutes.) .TP -\fB\-m\fP \fIremap-file\fP +\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 @@ -133,44 +163,86 @@ 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 -.B \-v +.\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\-r\fP \fItftp-option\fP +\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 -.B \-V +\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 -.B blksize -(RFC 2348), -.B blksize2 -(nonstandard), +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 -(RFC 2349), and -.B timeout -(RFC 2349). The nonstandard -.B blksize2 -TFTP option is functionally identical to the -.B blksize -option, with the additional constraint that the -blocksize is constrained to be a power of 2. +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 \-r +.B \-\-refuse option can be used to disable specific options; this may be necessary -to work around bugs in specific TFTP client implementations. +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 \-m +.B \-\-map-file option specifies a file which contains filename remapping rules. Each non-comment line (comments begin with hash marks, .BR # ) @@ -202,7 +274,18 @@ by the 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 +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 @@ -213,6 +296,17 @@ case-insensitively. By default it is case sensitive. .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. @@ -226,8 +320,23 @@ 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 the +The following escape sequences are recognized as part of a .IR "replacement pattern" : .TP \fB\\0\fP @@ -241,12 +350,14 @@ subexpressions, \\( ... \\), of the pattern. .TP \fB\\i\fP -The IP address of the requesting host, in dotted-quad notation -(e.g. 192.0.2.169). +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 hexadecimal notation -(e.g. C00002A9). +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. @@ -254,8 +365,17 @@ Literal backslash. \fB\\\fP\fIwhitespace\fP Literal whitespace. .TP -\fB\\#\fI +\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 @@ -267,17 +387,17 @@ 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 \-p +.B \-\-permissive option is specified. Files may be written only if they already exist and are publicly writable, unless the -.B \-c +.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 \-V" ) +.BR "in.tftpd \-\-version" ) .B tftpd will query the .BR hosts_access (5) @@ -287,7 +407,7 @@ 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 \-u +.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 @@ -297,14 +417,14 @@ Access to files can, and should, be restricted by invoking .B tftpd with a list of directories by including pathnames as server program arguments on the command line. In this case access is restricted to -files whole names are prefixed by one of the given directories. If +files whose names are prefixed by one of the given directories. If possible, it is recommended that the -.B \-s +.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 ( \-m +.RB ( \-\-map-file flag) support can be used to provide a limited amount of additional access control. .SH "CONFORMING TO" diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c index 05337a5..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,19 +33,9 @@ * 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. * @@ -56,666 +44,1162 @@ static const char *rcsid UNUSED = #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) +#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; -int unixperms = 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 -static inline void -set_socket_nonblock(int fd, int flag) +/* + * Rules for locking files; return 0 on success, -1 on failure + */ +static int lock_file(int fd, int lock_write) { - int err; - int flags; + (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 +} + +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); + /* 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); + flags = flag ? 1 : 0; + err = (ioctl(fd, FIONBIO, &flags) < 0); #endif - if ( err ) { - syslog(LOG_ERR, "Cannot set nonblock flag on socket: %m"); - exit(EX_OSERR); - } + if (err) { + syslog(LOG_ERR, "Cannot set nonblock flag on socket: %m"); + exit(EX_OSERR); + } } -int -main(int argc, char **argv) +static void pmtu_discovery_off(int fd) { - struct tftphdr *tp; - struct passwd *pw; - struct options *opt; - struct sockaddr_in myaddr; - struct sockaddr_in bindaddr; - int n; - int fd = 0; - int standalone = 0; /* Standalone (listen) mode */ - char *address = NULL; /* Address to listen to */ - pid_t pid; - mode_t my_umask = 0; - int spec_umask = 0; - int c; - int setrv; - int waittime = 900; /* Default time to wait for a connect*/ - const char *user = "nobody"; /* Default user */ - char *p, *ep; -#ifdef WITH_REGEX - char *rewrite_file = NULL; +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) + int pmtu = IP_PMTUDISC_DONT; + + setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); #endif - - /* 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, "cspvVla:u:U:r:t:m:")) != -1) - switch (c) { - case 'c': - cancreate = 1; - break; - case 's': - secure = 1; - break; - case 'p': - unixperms = 1; - break; - case 'l': - standalone = 1; - break; - case 'a': - address = optarg; - break; - case 't': - waittime = atoi(optarg); - break; - case 'u': - user = optarg; - break; - case 'U': - my_umask = strtoul(optarg, &ep, 8); - if ( *ep ) { - syslog(LOG_ERR, "Invalid umask: %s", optarg); - exit(EX_USAGE); - } - spec_umask = 1; - break; - case 'r': - for ( opt = options ; opt->o_opt ; opt++ ) { - if ( !strcasecmp(optarg, opt->o_opt) ) { - opt->o_opt = ""; /* Don't support this option */ - break; - } - } - if ( !opt->o_opt ) { - syslog(LOG_ERR, "Unknown option: %s", optarg); - exit(EX_USAGE); - } - break; -#ifdef WITH_REGEX - case 'm': - if ( rewrite_file ) { - syslog(LOG_ERR, "Multiple -m options"); - exit(EX_USAGE); - } - rewrite_file = optarg; - break; -#endif - case 'v': - verbosity++; - break; - case 'V': - /* Print configuration to stdout and exit */ - printf("%s\n", TFTPD_CONFIG_STR); - exit(0); - break; - default: - usage(); - 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); - } - - if ( spec_umask || !unixperms ) - umask(my_umask); - - /* Note: on Cygwin, select() on a nonblocking socket becomes - a nonblocking select. */ -#ifndef __CYGWIN__ - set_socket_nonblock(fd, 1); -#endif - -#ifdef WITH_REGEX - if ( rewrite_file ) - rewrite_rules = read_remap_rules(rewrite_file); -#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_family = AF_INET; - bindaddr.sin_addr.s_addr = INADDR_ANY; - bindaddr.sin_port = htons(IPPORT_TFTP); - - if ( address ) { - char *portptr, *eportptr; - struct hostent *hostent; - struct servent *servent; - unsigned long port; - - 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); - } - } - } - - if (bind(fd, (struct sockaddr *)&bindaddr, sizeof bindaddr) < 0) { - syslog(LOG_ERR, "cannot bind to local socket: %m"); - exit(EX_OSERR); - } - - /* Daemonize this process */ - { - pid_t f = fork(); - int nfd; - if ( f > 0 ) - exit(0); - if ( f < 0 ) { - syslog(LOG_ERR, "cannot fork: %m"); - exit(EX_OSERR); - } - nfd = open("/dev/null", O_RDWR); - if ( nfd >= 0 ) { -#ifdef HAVE_DUP2 - dup2(nfd, 0); - dup2(nfd, 1); - dup2(nfd, 2); -#else - close(0); dup(nfd); - close(1); dup(nfd); - close(2); dup(nfd); -#endif - close(nfd); - } else { - close(0); close(1); close(2); - } -#ifdef HAVE_SETSID -#ifndef __CYGWIN__ /* Kills the process on Cygwin? */ - setsid(); -#endif -#endif - } - } 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; - -#ifdef __CYGWIN__ - /* On Cygwin, select() on a nonblocking socket returns immediately, - with a rv of 0! */ - set_socket_nonblock(fd, 0); -#endif - - /* 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_IOERR); - } else if ( rv == 0 ) { - exit(0); /* Timeout, return to inetd */ - } - -#ifdef __CYGWIN__ - set_socket_nonblock(fd, 1); -#endif - - fromlen = sizeof (from); - n = myrecvfrom(fd, buf, sizeof (buf), 0, - (struct sockaddr *)&from, &fromlen, - &myaddr); - - if ( n < 0 ) { - if ( E_WOULD_BLOCK(errno) || errno == EINTR ) { - continue; /* Again, from the top */ - } else { - syslog(LOG_ERR, "recvfrom: %m"); - exit(EX_IOERR); - } - } - - 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); - } - - /* - * 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); - } - - /* Set up the supplementary group access list if possible */ - /* /etc/group still need to be accessible at this point */ -#ifdef HAVE_INITGROUPS - setrv = initgroups(user, pw->pw_gid); - if ( setrv ) { - syslog(LOG_ERR, "cannot set groups for user %s", user); - exit(EX_OSERR); - } -#else -#ifdef HAVE_SETGROUPS - if ( setgroups(0, NULL) ) { - syslog(LOG_ERR, "cannot clear group list"); - } -#endif -#endif - - /* Chroot and drop privileges */ - if (secure) { - if (chroot(".")) { - syslog(LOG_ERR, "chroot: %m"); - exit(EX_OSERR); - } -#ifdef __CYGWIN__ - chdir("/"); /* Cygwin chroot() bug workaround */ -#endif - } - -#ifdef HAVE_SETREGID - setrv = setregid(pw->pw_gid, pw->pw_gid); -#else - setrv = setegid(pw->pw_gid) || setgid(pw->pw_gid); -#endif - -#ifdef HAVE_SETREUID - setrv = setrv || setreuid(pw->pw_uid, pw->pw_uid); -#else - /* Important: setuid() must come first */ - setrv = setrv || setuid(pw->pw_uid) || - (geteuid() != pw->pw_uid && seteuid(pw->pw_uid)); -#endif - - if ( setrv ) { - syslog(LOG_ERR, "cannot drop privileges: %m"); - exit(EX_OSERR); - } - - /* 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); } -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); +/* + * 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; -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 } + gettimeofday(&t0, NULL); + timeout_left = timeout_us; + + for (;;) { + FD_ZERO(&fdset); + FD_SET(s, &fdset); + + do { + tmv.tv_sec = timeout_left / 1000000; + tmv.tv_usec = timeout_left % 1000000; + + rv = select(s + 1, &fdset, NULL, NULL, &tmv); + err = errno; + + gettimeofday(&t1, NULL); + + dt = (t1.tv_sec - t0.tv_sec) * 1000000 + + (t1.tv_usec - t0.tv_usec); + *timeout_us_p = timeout_left = + (dt >= timeout_us) ? 1 : (timeout_us - dt); + } while (rv == -1 && err == EINTR); + + if (rv == 0) { + timer(0); /* Should not return */ + return -1; + } + + set_socket_nonblock(s, 1); + rv = recv(s, rbuf, len, flags); + err = errno; + set_socket_nonblock(s, 0); + + if (rv < 0) { + if (E_WOULD_BLOCK(err) || err == EINTR) { + continue; /* Once again, with feeling... */ + } else { + errno = err; + return rv; + } + } else { + return rv; + } + } +} + +static int split_port(char **ap, char **pp) +{ + char *a, *p; + int ret = AF_UNSPEC; + + a = *ap; +#ifdef HAVE_IPV6 + if (is_numeric_ipv6(a)) { + if (*a++ != '[') + return -1; + *ap = a; + p = strrchr(a, ']'); + if (!p) + return -1; + *p++ = 0; + a = p; + ret = AF_INET6; + p = strrchr(a, ':'); + if (p) + *p++ = 0; + } else +#endif + { + struct in_addr in; + + p = strrchr(a, ':'); + if (p) + *p++ = 0; + if (inet_aton(a, &in)) + ret = AF_INET; + } + *pp = p; + return ret; +} + +enum long_only_options { + OPT_VERBOSITY = 256, + OPT_MAP_STEPS +}; + +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; @@ -723,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; } /* @@ -790,80 +1273,105 @@ 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; - } - return; + 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 @@ -875,56 +1383,76 @@ do_opt(char *opt, char *val, char **ap) * * Return -1 on failure. */ -int -rewrite_macros(char macro, char *output); - -int -rewrite_macros(char macro, char *output) +static int rewrite_macros(char macro, char *output) { - char *p; + char *p, tb[INET6_ADDRSTRLEN]; + int l=0; - switch (macro) { - case 'i': - p = inet_ntoa(from.sin_addr); - if ( output ) - strcpy(output, p); - return strlen(p); - - case 'x': - if ( output ) - sprintf(output, "%08X", - ntohl(from.sin_addr.s_addr)); - return 8; + 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); - default: - return -1; - } + case 'x': + if (output) { + if (from.sa.sa_family == AF_INET) { + sprintf(output, "%08lX", + (unsigned long)ntohl(from.si.sin_addr.s_addr)); + l = 8; +#ifdef HAVE_IPV6 + } else { + unsigned char *c = (unsigned char *)SOCKADDR_P(&from); + p = tb; + for (l = 0; l < 16; l++) { + sprintf(p, "%02X", *c); + c++; + p += 2; + } + strcpy(output, tb); + l = strlen(tb); +#endif + } + } + return l; + + default: + return -1; + } } /* * Modify the filename, if applicable. If it returns NULL, deny the access. */ -char * -rewrite_access(char *filename, int mode) +static char *rewrite_access(const struct formats *pf, char *filename, + int mode, int af, const char **msg) { - if ( rewrite_rules ) { - char *newname = rewrite_string(filename, rewrite_rules, - mode != RRQ, rewrite_macros); - filename = newname; - } - return filename; + if (rewrite_rules) { + char *newname = + rewrite_string(pf, filename, rewrite_rules, mode, af, + rewrite_macros, msg); + filename = newname; + } + return filename; } #else -char * -rewrite_access(char *filename, int mode) +static char *rewrite_access(const struct formats *pf, char *filename, + int mode, int af, const char **msg) { - (void)mode; /* Avoid warning */ - return filename; + (void)pf; + (void)mode; /* Avoid warning */ + (void)msg; + (void)af; + return filename; } #endif -FILE *file; /* * Validate file access. Since we * have no uid or gid, for now require @@ -936,358 +1464,369 @@ FILE *file; * 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, rmode; - char *cp; - const char **dirp; - char stdio_mode[3]; - - 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); - 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_WRONLY | - (cancreate ? O_CREAT : 0) | - (unixperms ? O_TRUNC : 0) | - (pf->f_convert ? O_TEXT : O_BINARY); - rmode = O_RDONLY | - (pf->f_convert ? O_TEXT : O_BINARY); - - 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 EACCESS; - } - } - - if ( fstat(fd, &stbuf) < 0 ) - exit(EX_OSERR); /* This shouldn't happen */ - - if (mode == RRQ) { - if ( !unixperms && (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; - } else { - if ( !unixperms ) { - if ( (stbuf.st_mode & (S_IWRITE >> 6)) == 0 ) - return (EACCESS); - - /* We didn't get to truncate the file at open() time */ -#ifdef HAVE_FTRUNCATE - if ( ftruncate(fd, (off_t)0) ) - return(EACCESS); +#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; + } } - tsize = 0; - tsize_ok = 1; - } - stdio_mode[0] = (mode == RRQ) ? 'r':'w'; - stdio_mode[1] = (pf->f_convert) ? 't':'b'; - stdio_mode[2] = '\0'; + if (fstat(fd, &stbuf) < 0) + exit(EX_OSERR); /* This shouldn't happen */ - file = fdopen(fd, stdio_mode); - if (file == NULL) - exit(EX_OSERR); /* Internal error */ + /* A duplicate RRQ or (worse!) WRQ packet could really cause havoc... */ + if (lock_file(fd, mode != RRQ)) + exit(0); - return (0); -} + if (mode == RRQ) { + if (!unixperms && (stbuf.st_mode & (S_IREAD >> 6)) == 0) { + *errmsg = "File must have global read permissions"; + return (EACCESS); + } + tsize = stbuf.st_size; + /* We don't know the tsize if conversion is needed */ + tsize_ok = !pf->f_convert; + } else { + if (!unixperms) { + if ((stbuf.st_mode & (S_IWRITE >> 6)) == 0) { + *errmsg = "File must have global write permissions"; + return (EACCESS); + } + } -int timeout; -sigjmp_buf timeoutbuf; +#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; + } -/* Handle timeout signal */ -void -timer(int sig) -{ - (void)sig; /* Suppress unused warning */ - timeout += rexmtval; - if (timeout >= maxtimeout) - exit(0); - siglongjmp(timeoutbuf, 1); + stdio_mode[0] = (mode == RRQ) ? 'r' : 'w'; + stdio_mode[1] = (pf->f_convert) ? 't' : 'b'; + stdio_mode[2] = '\0'; + + file = fdopen(fd, stdio_mode); + if (file == NULL) + exit(EX_OSERR); /* Internal error */ + + return (0); } /* * Send the requested file. */ -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, 0); /* 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 index eec15f9..d346e2a 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.29 +5.3