Lets keep our version of opftpd in the CVS
authorWerner Koch <wk@gnupg.org>
Mon, 11 Aug 2003 13:14:51 +0000 (13:14 +0000)
committerWerner Koch <wk@gnupg.org>
Mon, 11 Aug 2003 13:14:51 +0000 (13:14 +0000)
61 files changed:
AUTHORS [new file with mode: 0644]
BUGS [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
FAQ [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
TODO [new file with mode: 0644]
acconfig.h [new file with mode: 0644]
configure.in [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/conffiles [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/dirs [new file with mode: 0644]
debian/docs [new file with mode: 0644]
debian/files [new file with mode: 0644]
debian/oftpd.config [new file with mode: 0644]
debian/oftpd.postinst.debhelper [new file with mode: 0644]
debian/oftpd.postrm.debhelper [new file with mode: 0644]
debian/oftpd.prerm.debhelper [new file with mode: 0644]
debian/oftpd.substvars [new file with mode: 0644]
debian/oftpd.templates [new file with mode: 0644]
debian/postinst [new file with mode: 0644]
debian/postrm [new file with mode: 0644]
debian/prerm [new file with mode: 0644]
debian/rules [new file with mode: 0755]
dist/README [new file with mode: 0644]
dist/oftpd.spec-mauro [new file with mode: 0644]
dist/oftpd.spec-petr [new file with mode: 0644]
init/oftpd.redhat7 [new file with mode: 0644]
install-sh [new file with mode: 0755]
man/Makefile.am [new file with mode: 0644]
man/oftpd.8 [new file with mode: 0644]
missing [new file with mode: 0755]
mkinstalldirs [new file with mode: 0755]
oftpd.conf [new file with mode: 0644]
oftpd.startup [new file with mode: 0644]
src/Makefile.am [new file with mode: 0644]
src/af_portability.h [new file with mode: 0644]
src/config.h.in [new file with mode: 0644]
src/daemon_assert.c [new file with mode: 0644]
src/daemon_assert.h [new file with mode: 0644]
src/error.c [new file with mode: 0644]
src/error.h [new file with mode: 0644]
src/file_list.c [new file with mode: 0644]
src/file_list.h [new file with mode: 0644]
src/ftp_command.c [new file with mode: 0644]
src/ftp_command.h [new file with mode: 0644]
src/ftp_listener.c [new file with mode: 0644]
src/ftp_listener.h [new file with mode: 0644]
src/ftp_session.c [new file with mode: 0644]
src/ftp_session.h [new file with mode: 0644]
src/oftpd.c [new file with mode: 0644]
src/oftpd.h [new file with mode: 0644]
src/telnet_session.c [new file with mode: 0644]
src/telnet_session.h [new file with mode: 0644]
src/watchdog.c [new file with mode: 0644]
src/watchdog.h [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..124a3c3
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,15 @@
+Authors of oftpd:
+
+Shane Kerr <shane@time-travellers.org>
+
+
+Contributors:
+
+Beau Kuiper <kuiperba@cs.curtin.edu.au> (Netscape timing bug fix)
+Mauro Tortonesi <mauro@ferrara.linux.it> (IPv6 support)
+Matthew Danish <mdanish@andrew.cmu.edu> (Debian compile help, 
+    command-line options, address parse consolidation, configure
+    modifications to deal with ss_family variations)
+Eric Jensen (Red Hat 7.0 init script)
+Anders Nordby <anders@fix.no> (FreeBSD port)
+
diff --git a/BUGS b/BUGS
new file mode 100644 (file)
index 0000000..6daec23
--- /dev/null
+++ b/BUGS
@@ -0,0 +1,17 @@
+$Id$
+
+- On Solaris, new POSIX threads cannot be created from a chroot() jail!
+  pthread_create() uses mmap() on /dev/mem (or something like that) to
+  create the stack for new threads.  However, in a chroot() jail, no
+  /dev files are visible (hopefully).  It would still be possible to
+  support Solaris by using malloc() and specifying the stack explicitly,
+  but then there's the hassle of cleaning up this memory and so on.
+  Yuck.  So for now, no Solaris.  In 0.5.x I expect to support Solaris
+  again, as I'll only need a very small number of threads.
+
+- The pthread_cancel() does not actually meet POSIX compliance.  It is 
+  forbidden to use PTHREAD_CANCEL_ASYNCHRONOUS on anything that makes
+  system calls.  This seems to work fine on Linux, probably due to its
+  bizarre thread model.  This will also be fixed on 0.3.x, as I'll pay
+  careful attention to cancel points.
+
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..680dc21
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,26 @@
+Copyright 2000, 2001 Shane Kerr.  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.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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. 
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..3faf0c3
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,258 @@
+2003-08-09  Werner Koch  <wk@gnupg.org>
+
+       * src/oftpd.c (main): Give LOG_FACILITY file scope.
+       (init_syslog_hack): New
+       (reopen_syslog_hack): New.
+       * src/ftp_session.c (set_pasv, open_connection): Insert
+       reopen_syslog_hack calls.
+       * src/ftp_listener.c (connection_acceptor, ftp_listener_init): Ditto.
+       * src/ftp_session.c (open_connection): Ditto.
+       
+       * src/file_list.c: As an extra safeguard check the provided
+       dir_len.
+       (file_nlst): Ditto.
+       (fdprintf): Extra paranoid safeguard for buggy vsnprintfs.
+
+2003-05-22  Werner Koch  <wk@gnupg.org>
+
+       * src/ftp_command.c (parse_offset): Fixed offset checking.
+
+       * configure.in (AC_CHECK_SIZEOF): Add tests for off_t,
+       unsigned long and unsigned long long. 
+
+2003-05-06  Werner Koch  <wk@gnupg.org>
+
+       * src/ftp_session.c (do_lprt): Do not set the port after emitting
+       an error.  Fixes an assertion failure in ip_equal.
+
+2003-05-05  Werner Koch  <wk@gnupg.org>
+
+- New option -r, --pasv-range to limit the range for passive ports to
+  specific firewall rules.
+
+2001-05-28, Shane Kerr <shane@time-travellers.org>
+- Version 0.3.6 released.
+- Beat FreeBSD into submission.  Now compiles (and runs!) on FreeBSD
+  out of the box.
+
+2001-05-11, Shane Kerr <shane@time-travellers.org>
+- Changed REST to only work for IMAGE (i.e. binary) mode.
+- Added the SIZE command.
+- Added the MDTM (modification time) command.
+
+2001-04-20, Shane Kerr <shane@time-travellers.org>
+- Moved SIGPIPE ignore to run even in --nodetach mode.  Needed to avoid
+  unwanted signals on TCP disconnects.
+
+2001-04-19, Shane Kerr <shane@time-travellers.org>
+- Added more logging: all client messages are logged with address and
+  port, and all server responses are also logged.  This occurs at DEBUG
+  level, as it is probably not normally of interest.
+
+2001-04-18, Shane Kerr <shane@time-travellers.org>
+- Version 0.3.5 released.
+- Changed resume code to reset offset to 0 after each file transfer.
+- Set accept() socket to NONBLOCK to insure the listener doesn't get
+  stuck waiting for a connection.  This is documented in the NOTES
+  section for the accept() call in Linux.
+- Added sendfile() support on Linux systems.
+
+2001-04-14, Shane Kerr <shane@time-travellers.org>
+- Fixed a bug caused by parsing EPRT commands.  The server doesn't
+  actually allow these commands, but it bravely attempts to parse them
+  anyway.  A special shout out to Anders Nordby for finding this.  The
+  error caused the server to terminate on an assert() fail, which is
+  good because no server corruption happened, but it's bad because the
+  server terminated without any indication.  Therefore....
+- Changed most assert() calls to daemon_assert() calls.  These terminate
+  the application as before, but log to syslog and STDERR.
+- Added ability to run as a non-daemon process.  This will allow the
+  server to be started from init, for instance.
+- Added support for SIGTERM and SIGINT.  If one of these is received,
+  then the server closes the port 21 socket - meaning that it will
+  accept no further connections.  When all existing connections have
+  closed, then the server exits.  A new server may be started before
+  this occurs, to handle new connections.  This allows very short
+  downtimes:
+      # killall oftpd; sleep 1; /etc/init.d/oftpd start
+  This will result in a 1 second period of time where no new connections
+  are accepted, with no existing connections closed.  It's probably best
+  to wait for this second, to allow the signal time to arrive.  :)
+
+2001-04-08, Shane Kerr <shane@time-travellers.org>
+- Changed check for '/' in path - was improperly allowing escaped '/'
+  characters through the check!
+- Set TCP_NODELAY flag on socket - much reduced latency on control
+  channel for high-speed connections.
+- New and improved init script from Eric Jensen.
+
+2001-04-07, Anders Nordby <anders@fix.no>
+- FreeBSD port.
+
+2001-04-03, Shane Kerr <shane@time-travellers.org>
+- Version 0.3.4 released.
+- Changed FTP listener code to attempt to continue processing when
+  formerly fatal errors occur.
+- Added man page.
+- Added Red Hat init script donated by Eric Jensen to release.
+
+2001-03-28, Shane Kerr <shane@time-travellers.org>
+- Version 0.3.3 released.
+- Use IP address and port of the control connection port for default
+  Data Transfer Port, as defined by RFC 959.  Note that the default
+  server process DTP is never used in oftpd, but that's not a problem as
+  I read the RFC.
+- Use config.h
+
+2001-03-27, Matthew Danish <mdanish@andrew.cmu.edu>
+- configure.in and af_portability.h fixes to allow compilation on
+  systems with both RFC 2553 and XNET formats
+
+2001-03-27, Shane Kerr <shane@time-travellers.org>
+- Fixed bug where wrong server address was used on initalization of
+  ftp_server class.  Passive probably didn't work in the 0.3.x series
+  for IPv4!
+
+2001-03-26, Shane Kerr <shane@time-travellers.org>
+- Version 0.3.2 released.
+- Minor additions for better error reporting on error creating threads
+  in listener, as well as checking success on setgid() and setuid().  I
+  can't believe I wasn't!!!
+- Set syslog() to use FTP_DAEMON
+
+2001-03-23, Shane Kerr <shane@time-travellers.org>
+- Fixed configure bug on Debian systems.  Special thanks to Matthew
+  Danish for helping me on this one.
+
+2001-03-20, Matthew Danish <mdanish@andrew.cmu.edu>
+- Clean up parsing of FTP address in ftp_listener.c
+- Added options to specify interface and maximum number of client
+  connections at startup.
+- Added --long-style command-line options.
+
+2001-03-21, Shane Kerr <shane@time-travellers.org>
+- The "Spring Fever" edition, version 0.3.1 released.
+
+  Added workaround for the evil glob() denial-of-service attack.  I
+  chose the simple method of preventing file listing with both path
+  separator and wildcards.  Either is okay, but not both.  I don't
+  expect this to cause problems with legitimate use, but I could be
+  wrong.  It was much simpler than my other thought.  :)
+
+  Changed code so that ECONNABORTED and ECONNRESET errors don't increase
+  the error count.  This is to prevent a malicious client from taking a
+  server down by sending a large number of connection requests that it
+  then aborts.
+
+  Integrated code from Matthew Danish <mdanish@andrew.cmu.edu> that
+  allows the user to specify the port to use from the command line.
+  Thanks!
+
+  Warnings from 0.3.0 still apply.  Use with caution.  Spring is in the
+  air, after all.
+
+2001-03-20, Shane Kerr <shane@time-travellers.org>
+- Version 0.3.0 released.  This is almost guaranteed not to work, as
+  I've not tested it with clients that support the new features.  But I
+  decided that after a year, I wasn't living up to the "release early,
+  release often" motto of open source.
+
+  In addition to working in the changes since the last release, I've
+  done:
+
+  o removed free list from ftp_listener; simple is better
+  o use a random port rather than incrememting sequence; useful for
+    preventing classes of data hijacking attacks
+
+  There is a potential DOS attack against all versions, see the BUGS
+  file.  This will be fixed ASAP (hopefully tomorrow).
+
+2001-02-22, Matthew Danish <mdanish@andrew.cmu.edu>
+- Fix for missing <sys/types.h> check in configure.in
+
+2000-12-13, Mauro Tortonesi <mauro@ferrara.linux.it>
+- Added IPv6 support (EPRT & EPSV - RFC2428)
+- Added LPRT and LPSV support (RFC1639)
+
+2000-08-25, Shane Kerr <shane@time-travellers.org>
+- Fixed telnet module to properly handle telnet escape sequences (since
+  no clients actually use this, it wasn't really a problem)
+
+2000-06-01, Shane Kerr <shane@time-travellers.org>
+- Beau Kuiper, author of muddleftpd, sent me a fix for the Netscape
+  timing bug.  Thanks!!!
+
+2000-04-03, Shane Kerr <shane@time-travellers.org>
+- Version 0.2.0 released
+
+2000-03-30, Shane Kerr <shane@time-travellers.org>
+- Changed README send to only send on directory change if you actually
+  change to a different directory.  That is, "CWD ." does not send a
+  README file.  The reason for this is that many clients perform a 
+  "CWD /" as soon as it connects, which cause the README to be sent
+  twice - yuck!
+- Added missing check for closed file descriptor in write_fully().
+- Converted use of FILE pointers to file descriptors.  This wasn't done
+  in the most efficient possible method (i.e. with buffers), but it does
+  remove the maximum FILE limitation from the server.
+
+2000-03-29, Shane Kerr <shane@time-travellers.org>
+- Added -D_REENTRANT flag to compile options.
+- Added an error data type to return details about errors that occur 
+  in module initialization.
+- Changed telnet module to never drop characters on outbound, even if
+  we have a ton of DO and WILL commands from the other end.
+
+2000-03-27, Shane Kerr <shane@time-travellers.org>
+- Fixed bug in telnet code when sending "" string.
+- Now sends README to client if file is in directory on connect
+  or directory change.
+- Simplified watchdog a bit by adding pointer to watchdog in the
+  watched structure.
+- Wrapped invariant() methods with #ifndef NDEBUG so they won't
+  get compiled in non-debug code (in case there's ever a 
+  non-debug version)
+
+2000-03-22, Shane Kerr <shane@time-travellers.org>
+- Changed NLST and LIST to silently drop a file specification that
+  starts with a '-'.  This will ignore attempts to pass an argument
+  to "ls" that some clients try - it probably won't do what they 
+  expect, but at least they'll get a list of files.
+
+2000-03-19, Shane Kerr <shane@time-travellers.org>
+- Set file descriptors 0, 1, and 2 to go to "/dev/null", so that
+  any error messages sent by, say, the kernel don't accidentally
+  go to a user.
+- Changed watchdog to have a single thread to watch all connections, 
+  rather than a thread per connection.  This is considerably more
+  complex, but effectively doubles the number of connections that
+  can be supported (due to thread limits).
+
+2000-03-16, Shane Kerr <shane@time-travellers.org>
+- Added support for REST command.  (Note that this is *not* the REST
+  exactly as described by RFC.  The REST in the RFC only applies to
+  block or compressed mode transfer, which oftpd does not currently
+  support.  However, it appears that Unix systems interpret the 
+  parameter to the REST command as the offset into the file to resume
+  from.
+
+2000-03-13, Shane Kerr <shane@time-travellers.org>
+- Version 0.1.3 released
+
+2000-03-12, Shane Kerr <shane@time-travellers.org>
+- Fixed bug when connection limit reached
+- Fixed bug when attempt to bind() an already bound port
+
+2000-03-12, Shane Kerr <shane@time-travellers.org>
+- Version 0.1.2 released
+
+2000-03-11, Shane Kerr <shane@time-travellers.org>
+- Move configuration values into oftpd.h
+- Wrap source to 80 columns
+- Fix pthread_create() error check (code had incorrectly used errno
+  rather than the return of pthread_create() to determine error)
+- Fix threads to run in detached mode (when appropriate)
+- Solaris port completed
+- Support for STOR added (reply with 553 error)
+- Added free list for per-thread information structures.
+
diff --git a/FAQ b/FAQ
new file mode 100644 (file)
index 0000000..709d8e9
--- /dev/null
+++ b/FAQ
@@ -0,0 +1,35 @@
+Q: When I try to upload I get an error.  What am I doing wrong?
+
+A: Nothing.  oftpd does not allow you to upload files, create
+   directories, use any other FTP comands that modify the filesystem of
+   the server.  This is the way it is supposed to work.  If the code
+   doesn't write files, there's no way it can write them incorrectly!
+
+
+Q: Okay, can you add the STOR command, so I can upload files?  I
+   promise, I'll only use it on a secure network.
+
+A: This is possible, and I may add this some day.  (Or, if you have some
+   C programming background, you can add it yourself - the code's not
+   that bad, I promise.)  I don't have any dates in mind, however.  ;)
+
+
+Q: Is there a Debian/Red Hat/whatever package for oftpd?
+
+A: Matthew Danish has a preliminary Debian package available at:
+
+    ftp://emu.res.cmu.edu/pub/new-packages/
+
+   There are not any RPM's that I know of.  The install should be fairly
+   painless, but I'll probably put together an RPM at some point.
+
+
+Q: It doesn't compile on my system.  What am I doing wrong?
+
+A: Probably nothing.  All known compiler issues have been resolved, but
+   there may still be problems.  Send me an e-mail and I'll try to help
+   you through any problems.
+
+
+Shane Kerr <shane@time-travellers.org>
+$Id$
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..b42a17a
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,182 @@
+Basic Installation
+==================
+
+   These are generic installation instructions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+   The file `configure.in' is used to create `configure' by a program
+called `autoconf'.  You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.  If you're
+     using `csh' on an old version of System V, you might need to type
+     `sh ./configure' instead to prevent `csh' from trying to execute
+     `configure' itself.
+
+     Running `configure' takes awhile.  While running, it prints some
+     messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.
+
+  5. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the `configure' script does not know about.  You can give `configure'
+initial values for variables by setting them in the environment.  Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+     CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+     env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory.  After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+   By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc.  You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=PATH' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+   Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+   There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on.  Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+     CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+   If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+   If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+   `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+     Use and save the results of the tests in FILE instead of
+     `./config.cache'.  Set FILE to `/dev/null' to disable caching, for
+     debugging `configure'.
+
+`--help'
+     Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`--version'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..7ae283c
--- /dev/null
@@ -0,0 +1,3 @@
+## Process this file with automake to produce Makefile.in
+EXTRA_DIST = BUGS FAQ acconfig.h init/oftpd* dist/README dist/oftpd*
+SUBDIRS = src man
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..7a82c0c
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,9 @@
+2000-12-13
+
+  IPv6 code is maintained by Mauro Tortonesi <mauro@ferrara.linux.it>.
+  You can download the latest patches for oftpd from the Project6
+  website:
+
+    http://project6.ferrara.linux.it
+
+  Thanks Mauro!
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e202f54
--- /dev/null
+++ b/README
@@ -0,0 +1,64 @@
+$Id$
+
+
+Installation
+
+See the INSTALL file for directions on compiling and installing the
+binary.  Short version (as root):
+
+  # ./configure
+  # make
+  # make install
+
+This will install the oftpd daemon itself.  To run the server via the
+standard Unix startup mechanism, you'll need to add it to your startup
+files.  In most Linux systems, this means putting a shell script in the
+/etc/rc.d/init.d directory and linking to it from the directories for
+your various run levels.  If you have a Red Hat 7.0 (or similiar)
+system, you can use the oftpd.redhat7 script for this purpose:
+
+  # cp init/oftpd.redhat7 /etc/rc.d/init.d/oftpd
+  # chkconfig --add oftpd
+
+Be sure to read the FAQ if you have any questions!
+
+
+Introduction
+
+oftpd is designed to be as secure as an anonymous FTP server can
+possibly be.  It runs as non-root for most of the time, and uses the
+Unix chroot() command to hide most of the systems directories from
+external users - they cannot change into them even if the server is
+totally compromised!  It contains its own directory change code, so that
+it can run efficiently as a threaded server, and its own directory
+listing code (many FTP servers execute the system "ls" command to list
+files).  It is currently being code-reviewed for buffer overflows, and
+being load-tested.
+
+
+History
+
+I wrote oftpd to fill a need we had at my company.  Our public FTP site
+was a mess, and in addition to reorganizing organizing the hierarchy and
+file layout I wanted to get the latest version of our FTP server
+software.  It turns out that the version we had had had a number of
+security issues.  So I decided to find an anonymous-only, secure FTP
+server.  None of the ones I found were fully baked.  Time to write my
+own.  :)
+
+
+Portability
+
+oftpd currently runs on modern Linux systems, including Red Hat-derived
+(Mandrake, Trustix, etc.) and Debian systems.  oftpd has been ported to
+FreeBSD and is in the FreeBSD ports collection.  I expect to install
+FreeBSD and Solaris sometime in 2001 to properly support those
+environments.
+
+
+Don't hesitate to e-mail if you have questions or suggestions.  
+Good luck!
+
+
+Shane Kerr
+shane@time-travellers.org
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..4e29147
--- /dev/null
+++ b/TODO
@@ -0,0 +1,77 @@
+$Id$
+
+User requested:
+
+- STOR
+
+- User login
+
+- Bandwidth limits
+
+From RIPE:
+
+- Limit maximum download time
+
+Externally visible:
+
+- Add support for SIZE command (RFC 959?)
+
+- Add support for STAT command (RFC 959?)
+
+- Add support for the RECORD structure in transfer.  
+  Surely someone must use it.  :)
+
+- Allow server messages to be configurable.  I'm not sure of the best way
+  to do this.  The problem is that this entails putting the messages in a
+  single location, which means that you *don't* see what the message is 
+  when you are looking through the code.  Hm.  I may just make it a 
+  comment in the code to allow readability.
+
+- Better LIST and NLST support.
+
+- Drop browser connections before the 15-minute timeout.  You can detect
+  this by looking for users who logged in as "mozilla@" or "IEUser@".
+  In Perl, this would be /^.+\@$/
+
+
+Internal use only:
+
+- Handle glob() returns more elegantly.  Right now if a glob() returns 0 files,
+  it's okay.  This should return an error unless a wildcard was specified.
+
+- Possibly write our own glob() to reduce memory fragmentation.
+
+- Perhaps buffer NLST and LIST output.
+
+- Use getrlimit()/setrlimit() to cap the amount of memory allowable, and
+  possibly other resources as well.
+
+
+Auditing requirements:
+
+- Run through its4, and find other lint-like tools to pound code with.
+
+- Sprinkle assert() more fully.
+
+
+Testing:
+
+- Find more ftp clients
+
+- Create automated test
+
+- Run on multi-CPU box
+
+
+Porting:
+
+- Compile on FreeBSD
+
+- Port to Windows?
+
+
+Other stuff:
+
+- Document design
+
+- pth support?
diff --git a/acconfig.h b/acconfig.h
new file mode 100644 (file)
index 0000000..56ea66f
--- /dev/null
@@ -0,0 +1,13 @@
+#undef HAVE_NEW_SS_FAMILY
+/* new ss_family or old (meaning __ss_family) in sockaddr structure */
+#undef HAVE_NEW_SS_FAMILY
+
+/* sendfile() supported */
+#undef HAVE_SENDFILE
+
+/* Linux-style sendfile() supported */
+#undef HAVE_LINUX_SENDFILE
+
+/* FreeBSD-style sendfile() supported */
+#undef HAVE_FREEBSD_SENDFILE
+
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..7e4fc92
--- /dev/null
@@ -0,0 +1,108 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(src/file_list.c)
+AM_INIT_AUTOMAKE(oftpd, 0.3.6)
+AM_CONFIG_HEADER(src/config.h)
+
+dnl Checks for programs.
+AC_PROG_AWK
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LN_S
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(fcntl.h limits.h sys/time.h syslog.h unistd.h sys/types.h)
+
+dnl Check for ss_family
+AC_EGREP_CPP(__ss_family,
+[#include<sys/socket.h>
+],,AC_DEFINE(HAVE_NEW_SS_FAMILY))
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_TYPE_MODE_T
+AC_TYPE_OFF_T
+AC_TYPE_SIZE_T
+AC_STRUCT_ST_BLOCKS
+AC_STRUCT_ST_RDEV
+AC_HEADER_STAT
+AC_HEADER_TIME
+AC_STRUCT_ST_BLOCKS
+AC_STRUCT_ST_RDEV
+AC_STRUCT_TM
+
+# We need the size of some types to be able to detect overflows 
+# in parsing numeric values.
+AC_CHECK_SIZEOF(off_t)
+AC_CHECK_SIZEOF(unsigned long)
+AC_CHECK_SIZEOF(unsigned long long)
+
+dnl Checks for library functions.
+AC_FUNC_ALLOCA
+AC_FUNC_MEMCMP
+AC_TYPE_SIGNAL
+AC_FUNC_STRFTIME
+AC_CHECK_FUNCS(getcwd gettimeofday select socket strerror localtime_r gmtime_r)
+dnl AC_CHECK_LIB(pthread, pthread_create)
+dnl AC_SEARCH_LIBS(pthread_create, [ pthread pthreads thread threads ])
+AC_SEARCH_LIBS(socket, socket)
+AC_SEARCH_LIBS(inet_ntoa, nsl)
+AC_CHECK_FUNCS(inet_aton)
+
+
+dnl check for efficient file transfer mechanisms (i.e. sendfile())
+have_sendfile=no
+AC_TRY_LINK([
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+], [
+off_t offset;
+sendfile(0, 1, &offset);
+], 
+AC_DEFINE(HAVE_LINUX_SENDFILE) AC_DEFINE(HAVE_SENDFILE) have_sendfile=yes
+)
+if test $have_sendfile = no; then
+AC_TRY_LINK([
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+], [
+off_t offset;
+off_t sbytes;
+sendfile(0, 1, &offset, 0, NULL, &sbytes, 0);
+],
+AC_DEFINE(HAVE_FREEBSD_SENDFILE) AC_DEFINE(HAVE_SENDFILE) have_sendfile=yes
+)
+fi
+
+
+
+dnl decipher pthread compilation model
+
+dnl first, try linking a threaded application with "-pthread", 
+dnl which will work on FreeBSD (and OpenBSD) systems
+save_LDFLAGS="$LDFLAGS"
+LDFLAGS="-pthread $LDFLAGS"
+AC_TRY_LINK([ 
+#include <pthread.h>
+], [
+pthread_create();
+],
+,
+LIBS="$save_LDFLAGS"
+)
+
+dnl then, try looking in various libraries, which will work on other systems
+AC_SEARCH_LIBS(pthread_create, [ pthread pthreads thread threads ])
+
+dnl add reentrant flags
+CFLAGS="$CFLAGS -D_REENTRANT -D_THREAD_SAFE"
+
+dnl Check whether to enable IPv6 support
+AC_ARG_ENABLE([ipv6],[  --enable-ipv6           Enable IPv6 support (disabled by default)],
+              CFLAGS="$CFLAGS -DINET6",)
+
+AC_SUBST(HAVE_NEW_SS_FAMILY)
+AC_OUTPUT(Makefile src/Makefile man/Makefile)
+dnl AM_CONFIG_HEADER(src/config.h)
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..49985c2
--- /dev/null
@@ -0,0 +1,113 @@
+oftpd (0.3.6-5) unstable; urgency=low
+
+  * Priority overridden to extra; changed control file to concur.
+  * Fixed lintian error: depends on debconf >= 0.5.00 now
+  * /etc/default/oftpd no longer distributed with .deb
+  * Extra special care now taken to ensure that the sysadmin can modify
+    the file, without debconf, and have changes preserved. (Closes: #132169)
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Sun,  3 Feb 2002 16:48:05 -0500
+
+oftpd (0.3.6-4) unstable; urgency=low
+
+  * Fixed error in templates, the default directory should now be /home/oftpd
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Fri, 21 Sep 2001 19:44:29 -0400
+
+oftpd (0.3.6-3) unstable; urgency=low
+
+  * Removed /etc/default/oftpd from conffiles, because it is really isn't one.
+  * Now disables FTP service from inetd, in the postinst. (Closes: #107508)
+  * Updated Standards-Version to 3.5.6.0
+  * Uses 'oftpd' user by default, will create it if it does not exist.
+    Will not allow you to choose a user that does not exist.
+    (Closes: #107507)
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Fri,  3 Aug 2001 22:21:38 -0400
+
+oftpd (0.3.6-2) unstable; urgency=low
+
+  * Removed Build-Dependency on libc6-dev
+  * Modified logging template message to mention other sysloggers.
+  * Updated Standards-Version
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Tue, 10 Jul 2001 09:44:20 -0400
+
+oftpd (0.3.6-1) unstable; urgency=low
+
+  * New upstream release
+  * Updated Standards Version
+  * Added debconf note about setting up FTP syslog facility
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Thu, 31 May 2001 23:51:12 -0400
+
+oftpd (0.3.5-1) unstable; urgency=low
+
+  * New upstream release
+  * Upload sponsored by Lenart Janos <ocsi@debian.org>.
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Wed, 18 Apr 2001 20:29:04 -0400
+
+oftpd (0.3.4-2) unstable; urgency=low
+
+  * Moved oftpd.conf to /etc/default
+  * Added dependency on sysklogd | system-log-daemon
+  * Upload sponsored by Lenart Janos <ocsi@debian.org>.
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Tue, 17 Apr 2001 19:36:16 -0400
+
+oftpd (0.3.4-1) unstable; urgency=low
+
+  * New upstream release
+  * Included new upstream man page
+  * Upload sponsored by Lenart Janos <ocsi@debian.org>.
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Mon,  9 Apr 2001 10:07:05 -0400
+
+oftpd (0.3.3-1) unstable; urgency=low
+
+  * New upstream release
+  * Moved oftpd to /usr/sbin 
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Thu, 29 Mar 2001 22:18:18 -0500
+
+oftpd (0.3.2-1) unstable; urgency=low
+
+  * New upstream release
+  * Patched against ss_family bug
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Tue, 27 Mar 2001 14:21:05 -0500
+
+oftpd (0.3.1-3) unstable; urgency=low
+
+  * Updated Standards-Version
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Sun, 25 Mar 2001 23:07:00 -0500
+
+oftpd (0.3.1-2) unstable; urgency=low
+
+  * Added new options to debconf script
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Sun, 25 Mar 2001 21:29:21 -0500
+
+oftpd (0.3.1-1) unstable; urgency=low
+
+  * New upstream release
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Fri, 23 Mar 2001 00:28:29 -0500
+
+oftpd (0.2.0-2) unstable; urgency=low
+
+  * Uses debconf now 
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Sun, 18 Mar 2001 13:18:34 -0500
+
+oftpd (0.2.0-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- Matthew Danish <mdanish@andrew.cmu.edu>  Thu, 22 Feb 2001 00:56:19 -0500
+
+Local variables:
+mode: debian-changelog
+End:
diff --git a/debian/conffiles b/debian/conffiles
new file mode 100644 (file)
index 0000000..55cdf02
--- /dev/null
@@ -0,0 +1 @@
+/etc/init.d/oftpd
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..25d6f31
--- /dev/null
@@ -0,0 +1,16 @@
+Source: oftpd
+Section: net
+Priority: extra
+Maintainer: Matthew Danish <mdanish@andrew.cmu.edu>
+Build-Depends: debhelper (>> 2.0.0), debconf (>= 0.5.00)
+Standards-Version: 3.5.6.0
+
+Package: oftpd
+Architecture: any
+Depends: ${shlibs:Depends}, debconf (>= 0.5.00), sysklogd | system-log-daemon
+Provides: ftp-server
+Description: A secure anonymous FTP server
+ Designed from the ground up to be as secure as an anonymous FTP server
+ can be.  It runs as non-root and uses chroot() to hide the rest of the
+ system from the ftp daemon.  It is designed to work efficiently with
+ threads.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..75bb3bc
--- /dev/null
@@ -0,0 +1,12 @@
+This package was debianized by Matthew Danish <mdanish@andrew.cmu.edu> on
+Thu, 22 Feb 2001 00:56:19 -0500.
+
+It was downloaded from http://www.time-travellers.org/oftpd/
+
+Upstream Author(s): Shane Kerr <shane@time-travellers.org>
+
+Copyright:
+
+This program is distributed under the terms of the BSD license.
+The full text to this license may be found on Debian systems
+under /usr/share/common-licenses/BSD
diff --git a/debian/dirs b/debian/dirs
new file mode 100644 (file)
index 0000000..1489e65
--- /dev/null
@@ -0,0 +1,4 @@
+etc/default
+etc/init.d
+usr/bin
+usr/sbin
diff --git a/debian/docs b/debian/docs
new file mode 100644 (file)
index 0000000..d5aaf52
--- /dev/null
@@ -0,0 +1,3 @@
+BUGS
+README
+TODO
diff --git a/debian/files b/debian/files
new file mode 100644 (file)
index 0000000..5490292
--- /dev/null
@@ -0,0 +1 @@
+oftpd_0.3.6-5_i386.deb net extra
diff --git a/debian/oftpd.config b/debian/oftpd.config
new file mode 100644 (file)
index 0000000..ff8863f
--- /dev/null
@@ -0,0 +1,101 @@
+#!/bin/sh -e
+
+. /usr/share/debconf/confmodule
+
+if [ -e /etc/default/oftpd ]; then
+       USER=""
+       ENABLE="no"
+       DIR=""
+       PORT=""
+       IFACE=""
+       MAXCON=""
+       . /etc/default/oftpd || true
+       if [ "$USER" ]; then
+               db_set oftpd/which_user $USER
+       fi
+       if [ "$ENABLE" = "yes" ]; then
+               db_set oftpd/enable_oftpd "true"
+       else
+               db_set oftpd/enable_oftpd "false"
+       fi
+       if [ "$DIR" ]; then
+               db_set oftpd/which_dir $DIR
+       fi
+       if [ "$PORT" ]; then
+               db_set oftpd/which_port $PORT
+       fi
+       if [ "$PASV_RANGE" ]; then
+               db_set oftpd/which_pasv_range $PASV_RANGE
+       fi
+       if [ "$IFACE" ]; then
+               db_set oftpd/which_iface $IFACE
+       fi
+       if [ "$MAXCON" ]; then
+               db_set oftpd/max_conn $MAXCON
+       fi
+fi     
+
+db_input high oftpd/log_file_msg || true
+db_go
+
+db_input medium oftpd/enable_oftpd || true
+db_go
+
+db_get oftpd/enable_oftpd
+
+if [ "$RET" = "true" ]; then
+       EXIT_LOOP="false"
+       while [ "$EXIT_LOOP" != "true" ]; do
+               db_input medium oftpd/option_menu || true
+               db_go
+
+               db_get oftpd/option_menu
+               case "$RET" in
+                       "User to run the server as")
+                               USER_OKAY="false"
+                               while [ "$USER_OKAY" != "true" ]; do
+                                       db_input medium oftpd/which_user || true
+                                       db_go
+                                       db_get oftpd/which_user
+                                       [ -n "$RET" ] && USER_TEST=$(cut -d ':' -f 1 < /etc/passwd | grep -w "$RET") || true
+                                       if [ -n "$USER_TEST" -o "$RET" = "oftpd" -o -z "$RET" ]; then
+                                               USER_OKAY="true"
+                                       else
+                                               db_input medium oftpd/user_does_not_exist || true
+                                               db_go
+                                               db_fset oftpd/which_user seen false
+                                       fi
+                               done
+                               RET="User to run the server as"
+                               ;;
+                       "Directory to serve")
+                               db_input medium oftpd/which_dir || true
+                               db_go
+                               ;;
+                       "Port to listen on")
+                               db_input medium oftpd/which_port || true
+                               db_go
+                               ;;
+                       "Passive port range")
+                               db_input medium oftpd/which_pasv_range || true
+                               db_go
+                               ;;
+                       "Interface to listen on")
+                               db_input medium oftpd/which_iface || true
+                               db_go
+                               ;;
+                       "Max number of connections")
+                               db_input medium oftpd/max_conn || true
+                               db_go
+                               ;;
+                       "Exit")
+                               EXIT_LOOP="true"
+                               ;;
+               esac
+               if [ "$EXIT_LOOP" != "true" ]; then
+                       db_fset oftpd/option_menu seen false
+               fi
+       done
+fi
+
+exit 0
diff --git a/debian/oftpd.postinst.debhelper b/debian/oftpd.postinst.debhelper
new file mode 100644 (file)
index 0000000..7e7fdcb
--- /dev/null
@@ -0,0 +1,7 @@
+# Automatically added by dh_installdocs
+if [ "$1" = "configure" ]; then
+       if [ -d /usr/doc -a ! -e /usr/doc/oftpd -a -d /usr/share/doc/oftpd ]; then
+               ln -sf ../share/doc/oftpd /usr/doc/oftpd
+       fi
+fi
+# End automatically added section
diff --git a/debian/oftpd.postrm.debhelper b/debian/oftpd.postrm.debhelper
new file mode 100644 (file)
index 0000000..c764ad7
--- /dev/null
@@ -0,0 +1,6 @@
+# Automatically added by dh_installdebconf
+if [ "$1" = purge -a -e /usr/share/debconf/confmodule ]; then
+       . /usr/share/debconf/confmodule
+       db_purge
+fi
+# End automatically added section
diff --git a/debian/oftpd.prerm.debhelper b/debian/oftpd.prerm.debhelper
new file mode 100644 (file)
index 0000000..1328beb
--- /dev/null
@@ -0,0 +1,5 @@
+# Automatically added by dh_installdocs
+if [ \( "$1" = "upgrade" -o "$1" = "remove" \) -a -L /usr/doc/oftpd ]; then
+       rm -f /usr/doc/oftpd
+fi
+# End automatically added section
diff --git a/debian/oftpd.substvars b/debian/oftpd.substvars
new file mode 100644 (file)
index 0000000..387965d
--- /dev/null
@@ -0,0 +1,2 @@
+misc:Depends=debconf (>= 0.5)
+shlibs:Depends=libc6 (>= 2.2.4-4)
diff --git a/debian/oftpd.templates b/debian/oftpd.templates
new file mode 100644 (file)
index 0000000..22d3e58
--- /dev/null
@@ -0,0 +1,94 @@
+Template: oftpd/enable_oftpd
+Type: boolean
+Default: false
+Description: Do you want oftpd set up to start at boot-time?
+ This option lets you decide whether you want oftpd to be configured to
+ start automatically whenever you reboot.
+
+Template: oftpd/option_menu
+Type: select
+Choices: User to run the server as, Directory to serve, Port to listen on, Passive port range, Interface to listen on, Max number of connections, Exit
+Default: Exit
+Description: Choose a menu item to configure
+ This menu lets you configure various parameters to pass to oftpd.  Each
+ one contains a better description of its purpose, so select each one to
+ find out more.  If you don't want to set it, leave the default value.
+
+Template: oftpd/which_user
+Type: string
+Default: oftpd
+Description: What user would you like oftpd to run as?
+ oftpd is capable of running as a normal system user, this decreases
+ the security risk from any possible security flaws in the program.
+
+Template: oftpd/user_does_not_exist
+Type: note
+Description: That user does not exist.
+ You have selected a user which does not exist.  Please enter in another
+ username, or 'oftpd' for the default.
+
+Template: oftpd/which_dir
+Type: string
+Default: /home/oftpd
+Description: From what directory will oftpd serve files?
+ oftpd is a file server, and it needs to know from what directory in the
+ file system it will serve files from.  To all users of the FTP service,
+ it will appear as if that directory and its contents were the only
+ available files and directories on your system.
+ .
+ Note that oftpd runs chroot'd to this directory as well, this helps
+ decrease security risks in the possibility of a breach.
+
+Template: oftpd/which_port
+Type: string
+Default: 21
+Description: Which TCP port will oftpd listen on?
+ oftpd is a TCP/IP network server, which means that it needs to know what
+ TCP port to listen for connections on.  Port 21 is the standard FTP
+ server port, and is highly recommended if oftpd is to be your main FTP
+ server.  Any other port may be chosen as well, perhaps for personal
+ use or other reason.
+
+Template: oftpd/which_pasv_range
+Type: string
+Default: 1024 65535
+Description: Which TCP port range will oftpd use in passive mode?
+ Most modern FTP clients use the passive mode to contact an FTP
+ server.  The server therefore tells the client a port to connect to.
+ The port is a random one chosen from the range given here.  To adjust
+ firewall rules on the server some administrators like to limit the
+ ports a client may connect to.  Use this setting here to match the
+ firewall rules.  Most users keep the default.
+
+Template: oftpd/which_iface
+Type: string
+Default: 0.0.0.0
+Description: Which interface will oftpd listen on?
+ A machine may have one or multiple network interfaces.  Interfaces include
+ loopback (127.0.0.1), ethernet, PPP connections over modems, wireless
+ networking, and many more.  To have oftpd only listen on one of those
+ interfaces, enter the IP address of that interface (for example, to
+ have oftpd only be available to local users, make it listen to the
+ interface '127.0.0.1').  The default value is '0.0.0.0' which means to
+ listen to all interfaces.
+
+Template: oftpd/max_conn
+Type: string
+Default: 250
+Description: What shall be the maximum number of users allowed at once?
+ oftpd can limit the maximum number of users logged into your FTP server,
+ from 1 to 300.  The default is 250.
+
+Template: oftpd/log_file_msg
+Type: note
+Description: oftpd will be logging to the FTP syslog facility
+ This means that you should make sure that there is a line that looks like:
+ .
+ ftp.*                 -/var/log/ftp.log
+ .
+ In your /etc/syslog.conf file, if you are running sysklogd.  Consult
+ your documentation if using syslog-ng, msyslog, or others.
+ Then all the logging from oftpd will be sent to the file /var/log/ftp.log.  
+ If you do not do this, then the output will probably end up scattered 
+ throughout /var/log/debug.  If this has already been set in your
+ syslog configuration, then modify it to suit your tastes.
diff --git a/debian/postinst b/debian/postinst
new file mode 100644 (file)
index 0000000..b9dea6d
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+set -e
+. /usr/share/debconf/confmodule
+
+CONFFILE=/etc/default/oftpd
+LOGFILE=/var/log/oftpd.log
+TMPFILE=$(tempfile)
+
+#DEBHELPER#
+
+update_value() {
+       if [ -e "$CONFFILE" ]; then
+               if [ $(grep -c "$1" "$CONFFILE") = "0" ]; then
+                       echo "$1=\"$2\"" >> $CONFFILE
+               else
+                       TMPVAL=$(echo $2 | sed -e "s/\\//\\\\\\//g")
+                       sed -e "s/$1=.*/$1=\"$TMPVAL\"/" < $CONFFILE > $TMPFILE
+                       mv -f $TMPFILE $CONFFILE
+               fi
+       else
+               echo "$1=\"$2\"" >> $TMPFILE
+       fi
+}
+
+
+if [ "$1" = "configure" ]; then
+       update-inetd --disable ftp || true
+       
+       update-rc.d oftpd defaults > /dev/null 2>/dev/null
+
+       /etc/init.d/oftpd stop
+
+#      . $CONFFILE
+
+       db_get oftpd/enable_oftpd
+       if [ "$RET" = "true" ]; then
+               update_value "ENABLE" "yes"
+               db_get oftpd/which_user
+               if [ -z "$RET" -o "$RET" = "oftpd" ]; then
+                       USER_TEST=$(cut -d ':' -f 1 < /etc/passwd | grep -w oftpd) || true
+                       if [ -z "$USER_TEST" ]; then
+                               adduser --system oftpd
+                       fi
+                       update_value "USER" "oftpd"
+               else
+                       update_value "USER" "$RET"
+               fi
+               db_get oftpd/which_dir
+               if [ -z "$RET" ]; then
+                       update_value "DIR" "/home/oftpd"
+               else
+                       update_value "DIR" "$RET"
+               fi
+               db_get oftpd/which_port
+               if [ -n "$RET" ]; then
+                       update_value "PORT" "$RET"
+               else
+                       update_value "PORT" "21"
+               fi
+               db_get oftpd/which_iface
+               if [ -n "$RET" ]; then
+                       update_value "IFACE" "$RET"
+               else
+                       update_value "IFACE" "0.0.0.0"
+               fi
+               db_get oftpd/max_conn
+               if [ -n "$RET" ]; then
+                       update_value "MAXCON" "$RET"
+               else
+                       update_value "MAXCON" "250"
+               fi
+
+       else
+               update_value "ENABLE" "no"
+               db_get oftpd/which_user
+               update_value "USER" "$RET"
+               db_get oftpd/which_dir
+               update_value "DIR" "$RET"
+       fi
+       
+       if [ ! -e "$CONFFILE" ]; then
+               mv -f $TMPFILE $CONFFILE
+       fi
+
+       /etc/init.d/oftpd start
+
+fi
+
+exit 0;
diff --git a/debian/postrm b/debian/postrm
new file mode 100644 (file)
index 0000000..6111627
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -e
+#DEBHELPER#
+
+CONFFILE=/etc/default/oftpd
+
+if [ $1 == "purge" ]; then
+       update-rc.d oftpd remove >/dev/null 2>/dev/null
+       rm -f "$CONFFILE"
+fi
+
+exit 0;
+
diff --git a/debian/prerm b/debian/prerm
new file mode 100644 (file)
index 0000000..caed645
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -e
+
+#DEBHELPER#
+
+
+if [ \( "$1" = "upgrade" -o "$1" = "remove" \) ]; then
+       /etc/init.d/oftpd stop  
+fi
+
+       
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..6783a9e
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/make -f
+export DH_COMPAT=2
+
+configure: configure-stamp
+configure-stamp:
+       dh_testdir
+       ./configure --prefix=/usr --mandir=\$${prefix}/share/man --infodir=\$${prefix}/share/info
+
+       touch configure-stamp
+
+build: configure-stamp build-stamp
+build-stamp:
+       dh_testdir
+
+       $(MAKE)
+
+       touch build-stamp
+
+clean:
+       dh_testdir
+       dh_testroot
+       rm -f build-stamp configure-stamp
+
+       -$(MAKE) distclean
+
+       dh_clean
+
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k
+       dh_installdirs
+
+       $(MAKE) install prefix=$(CURDIR)/debian/oftpd/usr
+       mv $(CURDIR)/debian/oftpd/usr/bin/oftpd $(CURDIR)/debian/oftpd/usr/sbin 
+       #cp oftpd.conf $(CURDIR)/debian/oftpd/etc/default/oftpd
+       cp oftpd.startup $(CURDIR)/debian/oftpd/etc/init.d/oftpd
+       chmod 755 $(CURDIR)/debian/oftpd/etc/init.d/oftpd 
+
+binary-indep: build install
+
+binary-arch: build install
+       dh_testdir
+       dh_testroot
+       dh_installdebconf       
+       dh_installdocs
+       dh_installexamples
+       dh_installmenu
+       dh_installcron
+       dh_installmanpages
+       dh_installinfo
+       dh_installchangelogs ChangeLog
+       dh_link
+       dh_strip
+       dh_compress
+       dh_fixperms
+       dh_installdeb
+       dh_shlibdeps
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/dist/README b/dist/README
new file mode 100644 (file)
index 0000000..5a218d5
--- /dev/null
@@ -0,0 +1,19 @@
+This directory contains various files used to build binary
+distributions.
+
+RPM specifications:
+
+  oftpd.spec-petr
+
+  Submitted by Petr Kristof, culled from unknown sources on the
+  Internet.  You can find this file and pre-built RPM's here:
+
+      ftp://crash.fce.vutbr.cz/pub/redhat-cz/oftpd/
+
+  ----------------------------------------------------------------------
+
+  oftpd.spec-mauro
+
+  Submitted by Mauro Tortonesi
+
+
diff --git a/dist/oftpd.spec-mauro b/dist/oftpd.spec-mauro
new file mode 100644 (file)
index 0000000..7589d42
--- /dev/null
@@ -0,0 +1,62 @@
+Summary: oftpd server for Linux
+Name: oftpd
+Version: 0.3.5
+Release: 1prj6
+Copyright: GPL
+Group: System/Servers
+Source: %{name}-%{version}.tar.gz
+BuildRoot: /tmp/%{name}-%{version}-root
+Provides: ftpserver
+
+%description
+oftpd is designed to be as secure as an anonymous FTP server can
+possibly be.  It runs as non-root for most of the time, and uses the
+Unix chroot() command to hide most of the systems directories from
+external users - they cannot change into them even if the server is
+totally compromised!  It contains its own directory change code, so that
+it can run efficiently as a threaded server, and its own directory
+listing code (many FTP servers execute the system "ls" command to list
+files).  It is currently being code-reviewed for buffer overflows, and
+being load-tested.
+
+%prep
+%setup
+
+%build
+CFLAGS=${RPM_OPT_FLAGS} ./configure 
+
+if [ -n `uname -v | grep SMP | sed "s/ /_/g"` ] 
+then
+    make -j 2
+else
+    make 
+fi
+
+
+%install
+rm -rf $RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/usr/{sbin,man/man8} $RPM_BUILD_ROOT/etc/rc.d/init.d $RPM_BUILD_ROOT/var/oftp
+install -s src/oftpd $RPM_BUILD_ROOT/usr/sbin/oftpd
+install man/oftpd.8 $RPM_BUILD_ROOT/usr/man/man8/oftpd.8
+perl -pi -e "s/OFTPD=\/opt\/bin\/oftpd/OFTPD=\/usr\/sbin\/oftpd/g" init/oftpd.redhat7
+install init/oftpd.redhat7 $RPM_BUILD_ROOT/etc/rc.d/init.d/oftpd
+bzip2 -9 $RPM_BUILD_ROOT/usr/man/man8/oftpd.8
+                                                  
+
+%clean
+rm -rf $RPM_BUILD_ROOT 
+
+
+%files
+%defattr(-,root,root,-)
+%doc AUTHORS BUGS FAQ README
+%attr(755,ftp,ftp)   /var/oftp
+%attr(755,root,root) /usr/sbin/oftpd
+%attr(755,root,root) %config /etc/rc.d/init.d/oftpd
+%attr(664,root,man)  %doc /usr/man/man8/oftpd.8.bz2
+
+
+%changelog
+
+* Tue May  1 2001 Mauro Tortonesi <mauro@ferrara.linux.it>
+- first release
diff --git a/dist/oftpd.spec-petr b/dist/oftpd.spec-petr
new file mode 100644 (file)
index 0000000..43f0519
--- /dev/null
@@ -0,0 +1,65 @@
+Summary: oftpd is fast, secure, anonymous-only FTP server.
+Name: oftpd
+Version: 0.3.5
+Release: 1
+Copyright: GPL
+Group: System Environment/Daemons
+URL: http://www.time-travellers.org/oftpd/
+Source0: http://www.time-travellers.com/oftpd/oftpd-%{version}.tar.gz
+Source1: oftpd.sh
+#Source2: oftpd-logrotate
+#Patch0: oftpd-home.patch
+BuildRoot: %{_tmppath}/%{name}-root
+Provides: anonftp
+Conflicts: ftpserver
+
+%description
+oftpd is designed to be as secure as an anonymous FTP server can
+possibly be.  It runs as non-root for most of the time, and uses the
+Unix chroot() command to hide most of the systems directories from
+external users - they cannot change into them even if the server is
+totally compromised!  It contains its own directory change code, so that
+it can run efficiently as a threaded server, and its own directory
+listing code (many FTP servers execute the system "ls" command to list
+files).
+
+
+%prep
+%setup  -q
+#%patch0 -p1 -b .rh
+
+
+%build
+./configure    --prefix=/usr --sysconfdir=/etc --mandir=/usr/share/man \
+               --bindir=/usr/sbin
+make
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT
+
+mkdir -p $RPM_BUILD_ROOT/var/ftp/pub
+
+mkdir -p $RPM_BUILD_ROOT/etc/init.d
+install %{SOURCE1} $RPM_BUILD_ROOT/etc/init.d/oftpd
+
+#mkdir -p $RPM_BUILD_ROOT/etc/logrotate.d
+#install %{SOURCE2} $RPM_BUILD_ROOT/etc/logrotate.d/oftpd
+
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(644,root,root)
+%doc AUTHORS BUGS COPYING ChangeLog FAQ INSTALL NEWS README TODO
+%attr(0755,root,root)  %config /etc/init.d/oftpd
+# %attr(0644,root,root)        %config /etc/logrotate.d/oftpd
+%attr(0755,root,root)  %dir /var/ftp/pub
+%attr(0755,root,root)  /usr/sbin/oftpd
+/usr/share/man/man8/oftpd.8*
+
+
+%changelog
+* Mon Apr 16 2001 Petr KriĀ¹tof <Petr@Kristof.CZ>
+- Initial release
+
diff --git a/init/oftpd.redhat7 b/init/oftpd.redhat7
new file mode 100644 (file)
index 0000000..de3e4b9
--- /dev/null
@@ -0,0 +1,67 @@
+#! /bin/sh
+#
+# RedHat 7.0 init style script
+# oftpd        Start/Stop anon ftp server
+# Uses chkconfig tool to manage runlevel startup
+# To install do the following:
+# cp oftpd /etc/rc.d/init.d/.;chkconfig --add oftpd
+#
+# chkconfig: 345 50 50
+# description: Basic chroot jailed anon ftp server \
+#               see: http://www.time-travellers.com/oftpd/
+#
+# processname: oftpd
+# pidfile: none
+# config: none, set ==> $OFTPD, $FTPROOT, $USER
+#
+
+#Change these three lines to suit your needs
+OFTPD=/opt/bin/oftpd
+FTPROOT=/var/oftp
+USER=ftp
+
+# Source function library.
+. /etc/init.d/functions
+
+[ -x $OFTPD ] || exit 0
+
+
+start() {
+        echo -n "Starting oftpd: "
+        daemon $OFTPD $USER $FTPROOT
+        RETVAL=$?
+        echo
+        [ $RETVAL -eq 0 ]
+       return $RETVAL
+}
+
+stop() {
+        echo -n "Stopping oftpd services: "
+        killproc oftpd
+        RETVAL=$?
+        echo
+        [ $RETVAL -eq 0 ]
+       return $RETVAL
+}
+
+# See how we were called.
+case "$1" in
+  start)
+       start
+       ;;
+  stop)
+       stop
+       ;;
+  status)
+       status oftpd
+       ;;
+  restart|reload)
+       stop
+       start
+       ;;
+  *)
+       echo "Usage: oftpd {start|stop|status|restart|reload|}"
+       exit 1
+esac
+
+exit $RETVAL
diff --git a/install-sh b/install-sh
new file mode 100755 (executable)
index 0000000..e9de238
--- /dev/null
@@ -0,0 +1,251 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission.  M.I.T. makes no representations about the
+# suitability of this software for any purpose.  It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+       -c) instcmd="$cpprog"
+           shift
+           continue;;
+
+       -d) dir_arg=true
+           shift
+           continue;;
+
+       -m) chmodcmd="$chmodprog $2"
+           shift
+           shift
+           continue;;
+
+       -o) chowncmd="$chownprog $2"
+           shift
+           shift
+           continue;;
+
+       -g) chgrpcmd="$chgrpprog $2"
+           shift
+           shift
+           continue;;
+
+       -s) stripcmd="$stripprog"
+           shift
+           continue;;
+
+       -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+           shift
+           continue;;
+
+       -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+           shift
+           continue;;
+
+       *)  if [ x"$src" = x ]
+           then
+               src=$1
+           else
+               # this colon is to work around a 386BSD /bin/sh bug
+               :
+               dst=$1
+           fi
+           shift
+           continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+       echo "install:  no input file specified"
+       exit 1
+else
+       true
+fi
+
+if [ x"$dir_arg" != x ]; then
+       dst=$src
+       src=""
+       
+       if [ -d $dst ]; then
+               instcmd=:
+               chmodcmd=""
+       else
+               instcmd=mkdir
+       fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
+
+       if [ -f $src -o -d $src ]
+       then
+               true
+       else
+               echo "install:  $src does not exist"
+               exit 1
+       fi
+       
+       if [ x"$dst" = x ]
+       then
+               echo "install:  no destination specified"
+               exit 1
+       else
+               true
+       fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+       if [ -d $dst ]
+       then
+               dst="$dst"/`basename $src`
+       else
+               true
+       fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='   
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+       pathcomp="${pathcomp}${1}"
+       shift
+
+       if [ ! -d "${pathcomp}" ] ;
+        then
+               $mkdirprog "${pathcomp}"
+       else
+               true
+       fi
+
+       pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+       $doit $instcmd $dst &&
+
+       if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+       if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+       if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+       if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+       if [ x"$transformarg" = x ] 
+       then
+               dstfile=`basename $dst`
+       else
+               dstfile=`basename $dst $transformbasename | 
+                       sed $transformarg`$transformbasename
+       fi
+
+# don't allow the sed command to completely eliminate the filename
+
+       if [ x"$dstfile" = x ] 
+       then
+               dstfile=`basename $dst`
+       else
+               true
+       fi
+
+# Make a temp file name in the proper directory.
+
+       dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+       $doit $instcmd $src $dsttmp &&
+
+       trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+       if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+       if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+       if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+       if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+       $doit $rmcmd -f $dstdir/$dstfile &&
+       $doit $mvcmd $dsttmp $dstdir/$dstfile 
+
+fi &&
+
+
+exit 0
diff --git a/man/Makefile.am b/man/Makefile.am
new file mode 100644 (file)
index 0000000..3de9b2d
--- /dev/null
@@ -0,0 +1,2 @@
+man_MANS = oftpd.8
+EXTRA_DIST = $(man_MANS)
diff --git a/man/oftpd.8 b/man/oftpd.8
new file mode 100644 (file)
index 0000000..e1ac7a1
--- /dev/null
@@ -0,0 +1,124 @@
+.TH OFTPD 8 "2001-04-03" Linux "Linux System Administration"
+.SH NAME 
+oftpd \- anonymous, read-only FTP server
+.SH SYNOPSIS
+.B oftpd [-p|--port
+.I port
+.B ] [-r|--pasv-range
+.I low high
+.B ] [-i|--interface
+.I ip-address
+.B ] [-m|--max-clients
+.I num
+.B ] [-l|--local
+.I local-logging
+.B ] [-N|--nodetach]
+.I user-name root-directory
+.SH DESCRIPTION
+.B oftpd
+is an anonymous-only FTP server.  
+
+Although it must be started by the root user, after initialization it
+runs as the user specified by 
+.I user-name
+parameter.  This should be a user with minimal permissions, preferably
+from an account set up for this purpose only.
+
+The server uses
+.BR chroot (2)
+to change the root directory of the server to the 
+.I root-directory
+directory.  When a user connects, this is the directory that they will
+start in, and is the top of their directory tree.
+
+When FTP clients connect, they may log in as "ftp" or "anonymous".
+Typically they will then send their e-mail address as password (most web
+browsers send a browser identifier rather than the user's e-mail
+address).  Standard Unix file permissions are enforced for downloading,
+meaning clients may download any file 
+.I user-name
+has permission to read.  No uploads are permitted.  All client activity
+is logged (see 
+.B DIAGNOSTICS
+below).
+.SH OPTIONS
+.IP "-p|--port port"
+Use the specified
+.I port 
+to listen for client connections.  If not specified, the default FTP
+port (number 21) is used.
+.IP "-r|--pasv-range low high"
+Limit the range for ports used in passive mode from
+.I low
+to
+.I high
+ .  If not specified 1024 and 65535 are used.
+.IP "-i|--interface ip-address"
+Use the interface connected to the IP address
+.I ip-address
+to accept connections.  If not specified, the server listens on all
+interfaces.
+.IP "-m|--max-clients num"
+Accept at most 
+.I num
+simultaneous clients.  If not specified, 250 will be the limit.
+.IP "-l|--local local-logging"
+Normally oftpd logs messages to syslog as the FTP daemon.  With this
+option, the specified 
+.I local-logging
+level will be used instead.  Valid numbers are 0-8.
+.IP "-N|--nodetach"
+Do not run in the background; for running from
+.BR init (8)
+or for testing.
+.SH DIAGNOSTICS
+Syntax errors will result in a help message being displayed, and a
+non-zero exit code returned.  Otherwise the server will exit without
+output, and return zero.
+
+After the server has started, you should check the appropriate log
+produced by 
+.BR sysklogd (8)
+for the FTP daemon to insure that startup completed correctly.  Certain
+errors occur only after the server has disconnected from the TTY, so
+can only be recorded via the log mechanism.
+
+If the server must terminate for any reason, it will also be logged, as
+will other non-fatal internal errors.  They are logged with attention to
+how serious the condition is believed to be, as documented in the
+.BR syslog (3)
+library call.
+
+Client activity will also be logged through this mechanism.  This
+includes connect and disconnect (or rejection due to too many
+simultaneous users), the e-mail address reported as password, and file
+transfers.  All client commands are logged as sent at the 
+.B DEBUG 
+level.  To fully monitor client activity you may configure
+.BR sysklogd (8)
+to record these.
+.SH NOTES
+.B oftpd
+does not use 
+.BR inetd (8)
+to run.  It is a stand-alone server.  There is no need to configure the 
+.I /etc/inetd.conf
+file to run it.  In fact, there should be no entry for FTP there at all.
+
+You can use the
+.BR kill (1)
+command to stop
+.B oftpd.
+When the server receives SIGHUP or SIGINT, it will stop listening for
+new FTP connections, and a new FTP server may be started.  Any existing
+connections to the old server will continue to function normally until
+the client disconnects or times out.  After all clients connections have
+closed, the server will exit.  
+
+To shutdown the server and close all client connections immediately, use
+SIGKILL.
+.SH AUTHOR
+Shane Kerr <shane@time-travellers.org>
+.SH "SEE ALSO"
+.BR ftp (1)
+
diff --git a/missing b/missing
new file mode 100755 (executable)
index 0000000..7789652
--- /dev/null
+++ b/missing
@@ -0,0 +1,190 @@
+#! /bin/sh
+# Common stub for a few missing GNU programs while installing.
+# Copyright (C) 1996, 1997 Free Software Foundation, Inc.
+# Franc,ois Pinard <pinard@iro.umontreal.ca>, 1996.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+
+if test $# -eq 0; then
+  echo 1>&2 "Try \`$0 --help' for more information"
+  exit 1
+fi
+
+case "$1" in
+
+  -h|--h|--he|--hel|--help)
+    echo "\
+$0 [OPTION]... PROGRAM [ARGUMENT]...
+
+Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
+error status if there is no known handling for PROGRAM.
+
+Options:
+  -h, --help      display this help and exit
+  -v, --version   output version information and exit
+
+Supported PROGRAM values:
+  aclocal      touch file \`aclocal.m4'
+  autoconf     touch file \`configure'
+  autoheader   touch file \`config.h.in'
+  automake     touch all \`Makefile.in' files
+  bison        create \`y.tab.[ch]', if possible, from existing .[ch]
+  flex         create \`lex.yy.c', if possible, from existing .c
+  lex          create \`lex.yy.c', if possible, from existing .c
+  makeinfo     touch the output file
+  yacc         create \`y.tab.[ch]', if possible, from existing .[ch]"
+    ;;
+
+  -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
+    echo "missing - GNU libit 0.0"
+    ;;
+
+  -*)
+    echo 1>&2 "$0: Unknown \`$1' option"
+    echo 1>&2 "Try \`$0 --help' for more information"
+    exit 1
+    ;;
+
+  aclocal)
+    echo 1>&2 "\
+WARNING: \`$1' is missing on your system.  You should only need it if
+         you modified \`acinclude.m4' or \`configure.in'.  You might want
+         to install the \`Automake' and \`Perl' packages.  Grab them from
+         any GNU archive site."
+    touch aclocal.m4
+    ;;
+
+  autoconf)
+    echo 1>&2 "\
+WARNING: \`$1' is missing on your system.  You should only need it if
+         you modified \`configure.in'.  You might want to install the
+         \`Autoconf' and \`GNU m4' packages.  Grab them from any GNU
+         archive site."
+    touch configure
+    ;;
+
+  autoheader)
+    echo 1>&2 "\
+WARNING: \`$1' is missing on your system.  You should only need it if
+         you modified \`acconfig.h' or \`configure.in'.  You might want
+         to install the \`Autoconf' and \`GNU m4' packages.  Grab them
+         from any GNU archive site."
+    files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' configure.in`
+    test -z "$files" && files="config.h"
+    touch_files=
+    for f in $files; do
+      case "$f" in
+      *:*) touch_files="$touch_files "`echo "$f" |
+                                      sed -e 's/^[^:]*://' -e 's/:.*//'`;;
+      *) touch_files="$touch_files $f.in";;
+      esac
+    done
+    touch $touch_files
+    ;;
+
+  automake)
+    echo 1>&2 "\
+WARNING: \`$1' is missing on your system.  You should only need it if
+         you modified \`Makefile.am', \`acinclude.m4' or \`configure.in'.
+         You might want to install the \`Automake' and \`Perl' packages.
+         Grab them from any GNU archive site."
+    find . -type f -name Makefile.am -print |
+          sed 's/\.am$/.in/' |
+          while read f; do touch "$f"; done
+    ;;
+
+  bison|yacc)
+    echo 1>&2 "\
+WARNING: \`$1' is missing on your system.  You should only need it if
+         you modified a \`.y' file.  You may need the \`Bison' package
+         in order for those modifications to take effect.  You can get
+         \`Bison' from any GNU archive site."
+    rm -f y.tab.c y.tab.h
+    if [ $# -ne 1 ]; then
+        eval LASTARG="\${$#}"
+       case "$LASTARG" in
+       *.y)
+           SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
+           if [ -f "$SRCFILE" ]; then
+                cp "$SRCFILE" y.tab.c
+           fi
+           SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
+           if [ -f "$SRCFILE" ]; then
+                cp "$SRCFILE" y.tab.h
+           fi
+         ;;
+       esac
+    fi
+    if [ ! -f y.tab.h ]; then
+       echo >y.tab.h
+    fi
+    if [ ! -f y.tab.c ]; then
+       echo 'main() { return 0; }' >y.tab.c
+    fi
+    ;;
+
+  lex|flex)
+    echo 1>&2 "\
+WARNING: \`$1' is missing on your system.  You should only need it if
+         you modified a \`.l' file.  You may need the \`Flex' package
+         in order for those modifications to take effect.  You can get
+         \`Flex' from any GNU archive site."
+    rm -f lex.yy.c
+    if [ $# -ne 1 ]; then
+        eval LASTARG="\${$#}"
+       case "$LASTARG" in
+       *.l)
+           SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
+           if [ -f "$SRCFILE" ]; then
+                cp "$SRCFILE" lex.yy.c
+           fi
+         ;;
+       esac
+    fi
+    if [ ! -f lex.yy.c ]; then
+       echo 'main() { return 0; }' >lex.yy.c
+    fi
+    ;;
+
+  makeinfo)
+    echo 1>&2 "\
+WARNING: \`$1' is missing on your system.  You should only need it if
+         you modified a \`.texi' or \`.texinfo' file, or any other file
+         indirectly affecting the aspect of the manual.  The spurious
+         call might also be the consequence of using a buggy \`make' (AIX,
+         DU, IRIX).  You might want to install the \`Texinfo' package or
+         the \`GNU make' package.  Grab either from any GNU archive site."
+    file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
+    if test -z "$file"; then
+      file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
+      file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file`
+    fi
+    touch $file
+    ;;
+
+  *)
+    echo 1>&2 "\
+WARNING: \`$1' is needed, and you do not seem to have it handy on your
+         system.  You might have modified some files without having the
+         proper tools for further handling them.  Check the \`README' file,
+         it often tells you about the needed prerequirements for installing
+         this package.  You may also peek at any GNU archive site, in case
+         some other package would contain this missing \`$1' program."
+    exit 1
+    ;;
+esac
+
+exit 0
diff --git a/mkinstalldirs b/mkinstalldirs
new file mode 100755 (executable)
index 0000000..6b3b5fc
--- /dev/null
@@ -0,0 +1,40 @@
+#! /bin/sh
+# mkinstalldirs --- make directory hierarchy
+# Author: Noah Friedman <friedman@prep.ai.mit.edu>
+# Created: 1993-05-16
+# Public domain
+
+# $Id$
+
+errstatus=0
+
+for file
+do
+   set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+   shift
+
+   pathcomp=
+   for d
+   do
+     pathcomp="$pathcomp$d"
+     case "$pathcomp" in
+       -* ) pathcomp=./$pathcomp ;;
+     esac
+
+     if test ! -d "$pathcomp"; then
+        echo "mkdir $pathcomp"
+
+        mkdir "$pathcomp" || lasterr=$?
+
+        if test ! -d "$pathcomp"; then
+         errstatus=$lasterr
+        fi
+     fi
+
+     pathcomp="$pathcomp/"
+   done
+done
+
+exit $errstatus
+
+# mkinstalldirs ends here
diff --git a/oftpd.conf b/oftpd.conf
new file mode 100644 (file)
index 0000000..08b9a42
--- /dev/null
@@ -0,0 +1,9 @@
+# This file should be editted using 'dpkg-reconfigure oftpd' rather than
+# manually.
+ENABLE="no"
+USER=oftpd
+DIR=/home/oftpd/
+PORT=""
+PASV_RANGE=""
+IFACE="0.0.0.0"
+MAXCON="250"
diff --git a/oftpd.startup b/oftpd.startup
new file mode 100644 (file)
index 0000000..88abb9f
--- /dev/null
@@ -0,0 +1,104 @@
+#! /bin/sh
+#
+# oftpd                init script for oftpd
+#              written by Matthew Danish <mdanish@andrew.cmu.edu>
+#              based on skeleton
+#
+#
+# skeleton     example file to build /etc/init.d/ scripts.
+#              This file should be used to construct scripts for /etc/init.d.
+#
+#              Written by Miquel van Smoorenburg <miquels@cistron.nl>.
+#              Modified for Debian GNU/Linux
+#              by Ian Murdock <imurdock@gnu.ai.mit.edu>.
+#
+# Version:     @(#)skeleton  1.8  03-Mar-1998  miquels@cistron.nl
+#
+
+CONFFILE=/etc/default/oftpd
+
+ENABLE="no"
+if [ -f "$CONFFILE" ]; then
+       source "$CONFFILE" 
+else
+       echo "$CONFFILE not present, skipping oftpd"
+       echo "Use 'dpkg-reconfigure oftpd' to create a $CONFFILE"
+       exit 0;
+fi
+
+if [ "$ENABLE" == "no" ]; then
+       echo "oftpd not enabled, continuing."
+       exit 0;
+fi
+
+ARGS="$USER $DIR"
+
+if [ -n "$PORT" ]; then
+       ARGS="-p $PORT $ARGS"
+fi
+
+if [ -n "$IFACE" ]; then
+       ARGS="-i $IFACE $ARGS"
+fi
+
+if [ -n "$MAXCON" ]; then
+       ARGS="-m $MAXCON $ARGS"
+fi
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/oftpd
+NAME=oftpd
+DESC="oftpd"
+
+test -f $DAEMON || exit 0
+
+#set -e
+
+case "$1" in
+  start)
+       echo -n "Starting $DESC: "
+       start-stop-daemon --start --quiet --name $NAME \
+               --exec $DAEMON -- $ARGS
+       echo "$NAME."
+       ;;
+  stop)
+       echo -n "Stopping $DESC: "
+       start-stop-daemon --stop --quiet --name $NAME \
+               --exec $DAEMON -- $ARGS > /dev/null 2>&1
+       echo "$NAME."
+       ;;
+  #reload)
+       #
+       #       If the daemon can reload its config files on the fly
+       #       for example by sending it SIGHUP, do it here.
+       #
+       #       If the daemon responds to changes in its config file
+       #       directly anyway, make this a do-nothing entry.
+       #
+       # echo "Reloading $DESC configuration files."
+       # start-stop-daemon --stop --signal 1 --quiet --pidfile \
+       #       /var/run/$NAME.pid --exec $DAEMON
+  #;;
+  restart|force-reload)
+       #
+       #       If the "reload" option is implemented, move the "force-reload"
+       #       option to the "reload" entry above. If not, "force-reload" is
+       #       just the same as "restart".
+       #
+       echo -n "Restarting $DESC: "
+       start-stop-daemon --stop --quiet --name $NAME \
+               --exec $DAEMON -- $ARGS
+       sleep 1
+       start-stop-daemon --start --quiet --name $NAME \
+               --exec $DAEMON -- $ARGS
+       echo "$NAME."
+       ;;
+  *)
+       N=/etc/init.d/$NAME
+       # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $N {start|stop|restart|force-reload}" >&2
+       exit 1
+       ;;
+esac
+
+exit 0
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644 (file)
index 0000000..d7b61c0
--- /dev/null
@@ -0,0 +1,2 @@
+bin_PROGRAMS = oftpd
+oftpd_SOURCES = file_list.c file_list.h ftp_command.c ftp_command.h ftp_listener.c ftp_listener.h ftp_session.c ftp_session.h oftpd.c oftpd.h telnet_session.c telnet_session.h watchdog.c watchdog.h error.c error.h af_portability.h daemon_assert.c daemon_assert.h
diff --git a/src/af_portability.h b/src/af_portability.h
new file mode 100644 (file)
index 0000000..6251894
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef AF_PORTABILITY_H
+#define AF_PORTABILITY_H
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+/* _x_ must be a pointer to a sockaddr structure */
+
+#define SAFAM(_x_)     (((struct sockaddr *)(_x_))->sa_family)
+
+#ifdef HAVE_NEW_SS_FAMILY
+#define SSFAM(_x_)     (((struct sockaddr_storage *)(_x_))->ss_family)
+#else
+#define SSFAM(_x_)     (((struct sockaddr_storage *)(_x_))->__ss_family)
+#endif
+
+#define SIN4ADDR(_x_)  (((struct sockaddr_in *)(_x_))->sin_addr)
+#define SIN4PORT(_x_)  (((struct sockaddr_in *)(_x_))->sin_port)
+#define SIN6ADDR(_x_)  (((struct sockaddr_in6 *)(_x_))->sin6_addr)
+#define SIN6PORT(_x_)  (((struct sockaddr_in6 *)(_x_))->sin6_port)
+
+#ifdef INET6
+#define SINADDR(_x_)   ((SAFAM(_x_)==AF_INET6) ? SIN6ADDR(_x_) : SIN4ADDR(_x_))
+#define SINPORT(_x_)   ((SAFAM(_x_)==AF_INET6) ? SIN6PORT(_x_) : SIN4PORT(_x_))
+#else
+#define SINADDR(_x_)    SIN4ADDR(_x_)
+#define SINPORT(_x_)    SIN4PORT(_x_)
+#endif
+
+#ifndef INET_ADDRSTRLEN
+#define INET_ADDRSTRLEN 16
+#endif
+
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46 
+#endif
+
+#ifdef INET6
+#define IP6_ADDRSTRLEN INET6_ADDRSTRLEN
+#define IP4_ADDRSTRLEN INET_ADDRSTRLEN
+#define IP_ADDRSTRLEN INET6_ADDRSTRLEN
+#else
+#define IP_ADDRSTRLEN INET_ADDRSTRLEN
+#endif
+
+#ifdef INET6
+typedef struct sockaddr_storage sockaddr_storage_t;
+#else
+typedef struct sockaddr_in sockaddr_storage_t;
+#endif 
+
+#endif /* AF_PORTABILITY_H */
diff --git a/src/config.h.in b/src/config.h.in
new file mode 100644 (file)
index 0000000..6f5daae
--- /dev/null
@@ -0,0 +1,174 @@
+/* src/config.h.in.  Generated from configure.in by autoheader.  */
+#undef HAVE_NEW_SS_FAMILY
+/* new ss_family or old (meaning __ss_family) in sockaddr structure */
+#undef HAVE_NEW_SS_FAMILY
+
+/* sendfile() supported */
+#undef HAVE_SENDFILE
+
+/* Linux-style sendfile() supported */
+#undef HAVE_LINUX_SENDFILE
+
+/* FreeBSD-style sendfile() supported */
+#undef HAVE_FREEBSD_SENDFILE
+
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+   systems. This function is required for `alloca.c' support on those systems.
+   */
+#undef CRAY_STACKSEG_END
+
+/* Define to 1 if using `alloca.c'. */
+#undef C_ALLOCA
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#undef HAVE_ALLOCA
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+   */
+#undef HAVE_ALLOCA_H
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#undef HAVE_FCNTL_H
+
+/* Define to 1 if you have the `getcwd' function. */
+#undef HAVE_GETCWD
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#undef HAVE_GETTIMEOFDAY
+
+/* Define to 1 if you have the `gmtime_r' function. */
+#undef HAVE_GMTIME_R
+
+/* Define to 1 if you have the `inet_aton' function. */
+#undef HAVE_INET_ATON
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <limits.h> header file. */
+#undef HAVE_LIMITS_H
+
+/* Define to 1 if you have the `localtime_r' function. */
+#undef HAVE_LOCALTIME_R
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the `select' function. */
+#undef HAVE_SELECT
+
+/* Define to 1 if you have the `socket' function. */
+#undef HAVE_SOCKET
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the `strerror' function. */
+#undef HAVE_STRERROR
+
+/* Define to 1 if you have the `strftime' function. */
+#undef HAVE_STRFTIME
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if `st_blocks' is member of `struct stat'. */
+#undef HAVE_STRUCT_STAT_ST_BLOCKS
+
+/* Define to 1 if `st_rdev' is member of `struct stat'. */
+#undef HAVE_STRUCT_STAT_ST_RDEV
+
+/* Define to 1 if your `struct stat' has `st_blocks'. Deprecated, use
+   `HAVE_STRUCT_STAT_ST_BLOCKS' instead. */
+#undef HAVE_ST_BLOCKS
+
+/* Define to 1 if your `struct stat' has `st_rdev'. Deprecated, use
+   `HAVE_STRUCT_STAT_ST_RDEV' instead. */
+#undef HAVE_ST_RDEV
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#undef HAVE_SYSLOG_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#undef HAVE_SYS_TIME_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#undef RETSIGTYPE
+
+/* The size of a `off_t', as computed by sizeof. */
+#undef SIZEOF_OFF_T
+
+/* The size of a `unsigned long', as computed by sizeof. */
+#undef SIZEOF_UNSIGNED_LONG
+
+/* The size of a `unsigned long long', as computed by sizeof. */
+#undef SIZEOF_UNSIGNED_LONG_LONG
+
+/* If using the C implementation of alloca, define if you know the
+   direction of stack growth for your system; otherwise it will be
+   automatically deduced at run-time.
+        STACK_DIRECTION > 0 => grows toward higher addresses
+        STACK_DIRECTION < 0 => grows toward lower addresses
+        STACK_DIRECTION = 0 => direction of growth unknown */
+#undef STACK_DIRECTION
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+#undef STAT_MACROS_BROKEN
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#undef TIME_WITH_SYS_TIME
+
+/* Define to 1 if your <sys/time.h> declares `struct tm'. */
+#undef TM_IN_SYS_TIME
+
+/* Version number of package */
+#undef VERSION
+
+/* Define to empty if `const' does not conform to ANSI C. */
+#undef const
+
+/* Define to `int' if <sys/types.h> does not define. */
+#undef mode_t
+
+/* Define to `long' if <sys/types.h> does not define. */
+#undef off_t
+
+/* Define to `unsigned' if <sys/types.h> does not define. */
+#undef size_t
diff --git a/src/daemon_assert.c b/src/daemon_assert.c
new file mode 100644 (file)
index 0000000..caa1828
--- /dev/null
@@ -0,0 +1,18 @@
+#include <config.h>
+#include "daemon_assert.h"
+#include <pthread.h>
+#include <syslog.h>
+#include <stdio.h>
+
+#ifndef NDEBUG
+void daemon_assert_fail(const char *assertion,
+                        const char *file,
+                        int line,
+                        const char *function)
+{
+    syslog(LOG_CRIT, "%s:%d: %s: %s", file, line, function, assertion);
+    fprintf(stderr, "%s:%d: %s: %s\n", file, line, function, assertion);
+    exit(1);
+}
+#endif
+
diff --git a/src/daemon_assert.h b/src/daemon_assert.h
new file mode 100644 (file)
index 0000000..7016ce0
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef DAEMON_ASSERT_H
+#define DAEMON_ASSERT_H
+
+#ifdef NDEBUG
+
+#define daemon_assert(expr)
+
+#else
+
+void daemon_assert_fail(const char *assertion,
+                        const char *file,
+                        int line,
+                        const char *function);
+
+#define daemon_assert(expr)                                                   \
+           ((expr) ? 0 :                                                      \
+            (daemon_assert_fail(__STRING(expr), __FILE__, __LINE__, __func__)))
+
+#endif
+
+#endif /* DAEMON_ASSERT_H */
diff --git a/src/error.c b/src/error.c
new file mode 100644 (file)
index 0000000..f649ea4
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * $Id$
+ *
+ * This data type allows modules to cleanly return error information in a 
+ * relatively clean fashion.  It only includes an error number and a
+ * description string right now.  It could be modified to include a large
+ * number of other data, e.g. module, file/line, timestamp.  I don't
+ * need that for my program right now, so I'm going to keep it simple. 
+ *
+ * -Shane
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include "daemon_assert.h"
+#include "error.h"
+
+static int invariant(const error_t *err);
+
+void error_init(error_t *err, int error_code, const char *desc_fmt, ...)
+{
+    va_list args;
+
+    daemon_assert(err != NULL);
+    daemon_assert(error_code >= 0);
+    daemon_assert(desc_fmt != NULL);
+
+    err->error_code = error_code;
+    va_start(args, desc_fmt);
+    vsnprintf(err->desc, sizeof(err->desc), desc_fmt, args);
+    va_end(args);
+
+    daemon_assert(invariant(err));
+}
+
+int error_get_error_code(const error_t *err)
+{
+    daemon_assert(invariant(err));
+    return err->error_code;
+}
+
+const char *error_get_desc(const error_t *err)
+{
+    daemon_assert(invariant(err));
+    return err->desc;
+}
+
+#ifndef NDEBUG
+static int invariant(const error_t *err)
+{
+    if (err == NULL) {
+        return 0;
+    }
+    if (err->error_code < 0) {
+        return 0;
+    }
+    if (strlen(err->desc) >= sizeof(err->desc)) {
+        return 0;
+    }
+    return 1;
+}
+#endif /* NDEBUG */
+
diff --git a/src/error.h b/src/error.h
new file mode 100644 (file)
index 0000000..04e69e0
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * $Id$
+ */
+
+#ifndef ERROR_H
+#define ERROR_H
+
+typedef struct {
+    int error_code;
+    char desc[128];
+} error_t;
+
+/* methods */
+void error_init(error_t *err, int error_code, const char *desc_fmt, ...);
+int error_get_error_code(const error_t *err);
+const char *error_get_desc(const error_t *err);
+
+#endif /* ERROR_H */
diff --git a/src/file_list.c b/src/file_list.c
new file mode 100644 (file)
index 0000000..a66ad17
--- /dev/null
@@ -0,0 +1,396 @@
+/*
+ * $Id$
+
+ * [wk: code reviewed 2003-08-09]
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <glob.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#include "file_list.h"
+#include "daemon_assert.h"
+
+/* AIX requires this to be the first thing in the file.  */
+#ifndef __GNUC__
+# if HAVE_ALLOCA_H
+#  include <alloca.h>
+# else
+#  ifdef _AIX
+#pragma alloca
+#  else
+#   ifndef alloca /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#   endif
+#  endif
+# endif
+#endif
+
+/* GLOB_PERIOD is defined in Linux but not Solaris */
+#ifndef GLOB_PERIOD
+#define GLOB_PERIOD 0
+#endif /* GLOB_PERIOD */
+
+/* GLOB_ABORTED is defined in Linux but not on FreeBSD */
+#ifndef GLOB_ABORTED
+#define GLOB_ABORTED GLOB_ABEND
+#endif
+
+static int is_valid_dir(const char *dir);
+static void fdprintf(int fd, const char *fmt, ...);
+
+/* if no localtime_r() is available, provide one */
+#ifndef HAVE_LOCALTIME_R
+#include <pthread.h>
+
+struct tm *localtime_r(const time_t *timep, struct tm *timeptr) 
+{
+    static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
+
+    pthread_mutex_lock(&time_lock);
+    *timeptr = *(localtime(timep));
+    pthread_mutex_unlock(&time_lock);
+    return timeptr;
+}
+#endif /* HAVE_LOCALTIME_R */
+
+int file_nlst(int out, const char *cur_dir, const char *filespec)
+{
+    int dir_len;
+    char pattern[PATH_MAX+1];
+    int glob_ret;
+    glob_t glob_buf;
+    int i;
+    char *file_name;
+
+    daemon_assert(out >= 0);
+    daemon_assert(is_valid_dir(cur_dir));
+    daemon_assert(filespec != NULL);
+
+    if (filespec[0] == '/') {
+        cur_dir = "";
+        dir_len = 0;
+    } else {
+        strcpy(pattern, cur_dir);
+        dir_len = strlen(pattern);
+        if ((dir_len + 1) > PATH_MAX) {
+            fdprintf(out, "Error; Directory name too long\r\n");
+            return 0;
+        }
+       if ((cur_dir[0] != '/') || (cur_dir[1] != '\0')) {
+            strcat(pattern, "/");
+            dir_len++;
+       }
+    }
+
+    /* make sure we have enough space */
+    if ((dir_len + 1 + strlen(filespec)) > PATH_MAX) {
+        fdprintf(out, "Error; Path name too long\r\n");
+       return 0;
+    }
+    strcat(pattern, filespec);
+
+    /* do a glob() */
+    memset(&glob_buf, 0, sizeof(glob_buf));
+    glob_ret = glob(pattern, 
+                    GLOB_ERR | GLOB_NOSORT | GLOB_PERIOD, 
+                   NULL, 
+                   &glob_buf);
+    if (glob_ret == GLOB_NOSPACE) {
+        fdprintf(out, "Error; Out of memory\r\n");
+       return 0;
+#ifdef GLOB_NOMATCH  /* not present in FreeBSD */
+    } else if (glob_ret == GLOB_NOMATCH) {
+        return 1;
+#endif /* GLOB_NOMATCH */
+    } else if (glob_ret == GLOB_ABORTED) {
+        fdprintf(out, "Error; Read error\r\n");
+       return 0;
+    } else if (glob_ret != 0) {
+        fdprintf(out, "Error; Unknown glob() error %d\r\n", glob_ret);
+       return 0;
+    }
+
+    /* print our results */
+    for (i=0; i<glob_buf.gl_pathc; i++) {
+        file_name = glob_buf.gl_pathv[i];
+       if (memcmp(file_name, pattern, dir_len) == 0) {
+           file_name += dir_len;
+       }
+       fdprintf(out, "%s\r\n", file_name);
+    }
+
+    /* free and return */
+    globfree(&glob_buf);
+    return 1;
+}
+
+typedef struct {
+    char *name;
+    char *full_path;
+    struct stat stat;
+} file_info_t;
+
+int file_list(int out, const char *cur_dir, const char *filespec)
+{
+    int dir_len;
+    char pattern[PATH_MAX+1];
+    int glob_ret;
+    glob_t glob_buf;
+    int i;
+    file_info_t *file_info;
+    int num_files;
+    unsigned long total_blocks;
+    char *file_name;
+
+    mode_t mode;
+    time_t now;
+    struct tm tm_now;
+    double age;
+    char date_buf[13];
+    char link[PATH_MAX+1];
+    int link_len;
+    
+    daemon_assert(out >= 0);
+    daemon_assert(is_valid_dir(cur_dir));
+    daemon_assert(filespec != NULL);
+
+    if (filespec[0] == '/') {
+        cur_dir = "";
+        dir_len = 0;
+    } else {
+        dir_len = strlen(pattern);
+        if ((dir_len + 1) > PATH_MAX) {
+            fdprintf(out, "Error; Directory name too long\r\n");
+            return 0;
+        }
+        strcpy(pattern, cur_dir);
+       if ((cur_dir[0] != '/') || (cur_dir[1] != '\0')) {
+            strcat(pattern, "/");
+            dir_len++;
+       }
+    }
+
+    /* make sure we have enough space */
+    if ((dir_len + 1 + strlen(filespec)) > PATH_MAX) {
+        fdprintf(out, "Error; Path name too long\r\n");
+       return 0;
+    }
+    strcat(pattern, filespec);
+
+    /* do a glob() */
+    memset(&glob_buf, 0, sizeof(glob_buf));
+    glob_ret = glob(pattern, GLOB_ERR, NULL, &glob_buf);
+#ifndef GLOB_NOMATCH /* FreeBSD */
+    if (glob_ret == GLOB_NOCHECK) {
+#else
+    if (glob_ret == GLOB_NOMATCH) {
+#endif
+        fdprintf(out, "total 0\r\n");
+        return 1;
+    } else if (glob_ret == GLOB_NOSPACE) {
+        fdprintf(out, "Error; Out of memory\r\n");
+       return 0;
+    } else if (glob_ret == GLOB_ABORTED) {
+        fdprintf(out, "Error; Read error\r\n");
+       return 0;
+    } else if (glob_ret != 0) {
+        fdprintf(out, "Error; Unknown glob() error %d\r\n", glob_ret);
+       return 0;
+    }
+
+    /* make a buffer to store our information */
+#ifdef HAVE_ALLOCA
+    file_info = (file_info_t *)alloca(sizeof(file_info_t) * glob_buf.gl_pathc);
+#else
+    file_info = (file_info_t *)malloc(sizeof(file_info_t) * glob_buf.gl_pathc);
+#endif
+    if (file_info == NULL) {
+        fdprintf(out, "Error; Out of memory\r\n");
+       globfree(&glob_buf);
+       return 0;
+    }
+
+    /* collect information */
+    num_files = 0;
+    total_blocks = 0;
+    for (i=0; i<glob_buf.gl_pathc; i++) {
+        file_name = glob_buf.gl_pathv[i];
+       if (memcmp(file_name, pattern, dir_len) == 0) {
+           file_name += dir_len;
+       }
+       if (lstat(glob_buf.gl_pathv[i], &file_info[num_files].stat) == 0) {
+#ifdef AC_STRUCT_ST_BLKSIZE
+           total_blocks += file_info[num_files].stat.st_blocks;
+#endif
+           file_info[num_files].name = file_name;
+           file_info[num_files].full_path = glob_buf.gl_pathv[i];
+           num_files++;
+       }
+    }
+
+    /* okay, we have information, now display it */
+    fdprintf(out, "total %lu\r\n", total_blocks);
+    time(&now);
+    for (i=0; i<num_files; i++) {
+
+       mode = file_info[i].stat.st_mode;
+
+        /* output file type */
+       switch (mode & S_IFMT) {
+           case S_IFSOCK:  fdprintf(out, "s"); break;
+           case S_IFLNK:   fdprintf(out, "l"); break;
+           case S_IFBLK:   fdprintf(out, "b"); break;
+           case S_IFDIR:   fdprintf(out, "d"); break;
+           case S_IFCHR:   fdprintf(out, "c"); break;
+           case S_IFIFO:   fdprintf(out, "p"); break;
+           default:        fdprintf(out, "-"); 
+       }
+
+       /* output permissions */
+       fdprintf(out, (mode & S_IRUSR) ? "r" : "-");
+       fdprintf(out, (mode & S_IWUSR) ? "w" : "-");
+       if (mode & S_ISUID) { 
+           fdprintf(out, (mode & S_IXUSR) ? "s" : "S");
+       } else {
+           fdprintf(out, (mode & S_IXUSR) ? "x" : "-");
+       }
+       fdprintf(out, (mode & S_IRGRP) ? "r" : "-");
+       fdprintf(out, (mode & S_IWGRP) ? "w" : "-");
+       if (mode & S_ISGID) { 
+           fdprintf(out, (mode & S_IXGRP) ? "s" : "S");
+       } else {
+           fdprintf(out, (mode & S_IXGRP) ? "x" : "-");
+       }
+       fdprintf(out, (mode & S_IROTH) ? "r" : "-");
+       fdprintf(out, (mode & S_IWOTH) ? "w" : "-");
+       if (mode & S_ISVTX) {
+           fdprintf(out, (mode & S_IXOTH) ? "t" : "T");
+       } else {
+           fdprintf(out, (mode & S_IXOTH) ? "x" : "-");
+       }
+
+        /* output link & ownership information */
+       fdprintf(out, " %3d %-8d %-8d ", 
+           file_info[i].stat.st_nlink, 
+           file_info[i].stat.st_uid, 
+           file_info[i].stat.st_gid);
+
+        /* output either i-node information or size */
+#ifdef AC_STRUCT_ST_RDEV
+       if (((mode & S_IFMT) == S_IFBLK) || ((mode & S_IFMT) == S_IFCHR)) {
+           fdprintf(out, "%3d, %3d ", 
+               (int)((file_info[i].stat.st_rdev >> 8) & 0xff),
+               (int)(file_info[i].stat.st_rdev & 0xff));
+       } else {
+           fdprintf(out, "%8lu ", 
+               (unsigned long)file_info[i].stat.st_size);
+       }
+#else
+       fdprintf(out, "%8lu ", (unsigned long)file_info[i].stat.st_size);
+#endif
+           
+        /* output date */
+       localtime_r(&file_info[i].stat.st_mtime, &tm_now);
+        age = difftime(now, file_info[i].stat.st_mtime);
+       if ((age > 60 * 60 * 24 * 30 * 6) || (age < -(60 * 60 * 24 * 30 * 6))) {
+           strftime(date_buf, sizeof(date_buf), "%b %e  %Y", &tm_now);
+       } else {
+           strftime(date_buf, sizeof(date_buf), "%b %e %H:%M", &tm_now);
+       }
+        fdprintf(out, "%s ", date_buf);
+
+       /* output filename */
+       fdprintf(out, "%s", file_info[i].name);
+  
+        /* display symbolic link information */
+       if ((mode & S_IFMT) == S_IFLNK) {
+           link_len = readlink(file_info[i].full_path, link, sizeof(link));
+           if (link_len > 0) {
+               fdprintf(out, " -> ");
+               link[link_len] = '\0';
+               fdprintf(out, "%s", link);
+           }
+       }
+
+       /* advance to next line */
+       fdprintf(out, "\r\n");
+    }
+
+    /* free memory & return */
+#ifndef HAVE_ALLOCA
+    free(file_info);
+#endif 
+    globfree(&glob_buf);
+    return 1;
+}
+
+static int is_valid_dir(const char *dir)
+{
+    /* directory can not be NULL (of course) */
+    if (dir == NULL) {
+        return 0;
+    }
+    /* directory must be absolute (i.e. start with '/') */
+    if (dir[0] != '/') {
+        return 0;
+    }
+
+    /* length cannot be greater than PATH_MAX */
+    if (strlen(dir) > PATH_MAX) {
+        return 0;
+    }
+
+    /* assume okay */
+    return 1;
+}
+
+static void fdprintf(int fd, const char *fmt, ...)
+{
+    char buf[PATH_MAX+1];
+    int buflen;
+    va_list ap;
+    int amt_written;
+    int write_ret;
+
+    daemon_assert(fd >= 0);
+    daemon_assert(fmt != NULL);
+
+    va_start(ap, fmt);
+    buflen = vsnprintf(buf, sizeof(buf)-1, fmt, ap);
+    buf[PATH_MAX] = 0; /* Make extra sure that the string is terminated. */
+    va_end(ap);
+    if (buflen <= 0) {
+        return;
+    }
+    if (buflen >= sizeof(buf)) {
+        buflen = sizeof(buf)-1;
+    }
+
+    amt_written = 0;
+    while (amt_written < buflen) {
+        write_ret = write(fd, buf+amt_written, buflen-amt_written);
+       if (write_ret <= 0) {
+           return;
+       }
+       amt_written += write_ret;
+    }
+}
diff --git a/src/file_list.h b/src/file_list.h
new file mode 100644 (file)
index 0000000..c6bbed4
--- /dev/null
@@ -0,0 +1,12 @@
+/* 
+ * $Id$
+ */
+
+#ifndef FILE_LIST_H
+#define FILE_LIST_H
+
+int file_nlst(int out, const char *cur_dir, const char *filespec);
+int file_list(int out, const char *cur_dir, const char *filespec);
+
+#endif /* FILE_LIST_H */
+
diff --git a/src/ftp_command.c b/src/ftp_command.c
new file mode 100644 (file)
index 0000000..a764a93
--- /dev/null
@@ -0,0 +1,669 @@
+/*
+ * $Id$
+ */
+
+#include <config.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include "ftp_command.h"
+#include "af_portability.h"
+#include "daemon_assert.h"
+
+/* argument types */
+#define ARG_NONE              0
+#define ARG_STRING            1
+#define ARG_OPTIONAL_STRING   2
+#define ARG_HOST_PORT         3
+#define ARG_TYPE              4
+#define ARG_STRUCTURE         5
+#define ARG_MODE              6
+#define ARG_OFFSET            7
+#define ARG_HOST_PORT_LONG    8
+#define ARG_HOST_PORT_EXT     9
+#define ARG_OPTIONAL_NUMBER  10
+
+/* our FTP commands */
+struct {
+    char *name;
+    int arg_type;
+} command_def[] = {
+    { "USER", ARG_STRING          },
+    { "PASS", ARG_STRING          },
+    { "CWD",  ARG_STRING          },
+    { "CDUP", ARG_NONE            },
+    { "QUIT", ARG_NONE            },
+    { "PORT", ARG_HOST_PORT       },
+    { "LPRT", ARG_HOST_PORT_LONG  },
+    { "EPRT", ARG_HOST_PORT_EXT   },
+    { "PASV", ARG_NONE            },
+    { "LPSV", ARG_NONE            },
+    { "EPSV", ARG_OPTIONAL_NUMBER },
+    { "TYPE", ARG_TYPE            },
+    { "STRU", ARG_STRUCTURE       },
+    { "MODE", ARG_MODE            },
+    { "RETR", ARG_STRING          },
+    { "STOR", ARG_STRING          },
+    { "PWD",  ARG_NONE            },
+    { "LIST", ARG_OPTIONAL_STRING },
+    { "NLST", ARG_OPTIONAL_STRING },
+    { "SYST", ARG_NONE            },
+    { "HELP", ARG_OPTIONAL_STRING },
+    { "NOOP", ARG_NONE            },
+    { "REST", ARG_OFFSET          },
+    { "SIZE", ARG_STRING          },
+    { "MDTM", ARG_STRING          }
+};
+
+#define NUM_COMMAND (sizeof(command_def) / sizeof(command_def[0]))
+
+/* prototypes */
+static const char *copy_string(char *dst, const char *src);
+static const char *parse_host_port(struct sockaddr_in *addr, const char *s);
+static const char *parse_number(int *num, const char *s, int max_num);
+static const char *parse_offset(off_t *ofs, const char *s);
+static const char *parse_host_port_long(sockaddr_storage_t *sa, const char *s);
+static const char *parse_host_port_ext(sockaddr_storage_t *sa, const char *s); 
+
+int ftp_command_parse(const char *input, ftp_command_t *cmd)
+{
+    int i;
+    int len;
+    int match;
+    ftp_command_t tmp;
+    int c;
+    const char *optional_number;
+
+    daemon_assert(input != NULL);
+    daemon_assert(cmd != NULL);
+
+    /* see if our input starts with a valid command */
+    match = -1;
+    for (i=0; (i<NUM_COMMAND) && (match == -1); i++) {
+        len = strlen(command_def[i].name);
+        if (strncasecmp(input, command_def[i].name, len) == 0) {
+           match = i;
+       }
+    }
+
+    /* if we didn't find a match, return error */
+    if (match == -1) {
+        return 0;
+    }
+    daemon_assert(match >= 0);
+    daemon_assert(match < NUM_COMMAND);
+
+    /* copy our command */
+    strcpy(tmp.command, command_def[match].name);
+
+    /* advance input past the command */
+    input += strlen(command_def[match].name);
+
+    /* now act based on the command */
+    switch (command_def[match].arg_type) {
+
+        case ARG_NONE:
+           tmp.num_arg = 0;
+           break;
+
+        case ARG_STRING:
+           if (*input != ' ') {
+               return 0;
+           }
+            ++input;
+            input = copy_string(tmp.arg[0].string, input);
+           tmp.num_arg = 1;
+           break;
+
+        case ARG_OPTIONAL_STRING:
+           if (*input == ' ') {
+               ++input;
+               input = copy_string(tmp.arg[0].string, input);
+               tmp.num_arg = 1;
+           } else {
+               tmp.num_arg = 0;
+           }
+           break;
+
+        case ARG_HOST_PORT:
+           if (*input != ' ') {
+               return 0;
+           }
+           input++;
+
+            /* parse the host & port information (if any) */
+           input = parse_host_port(&tmp.arg[0].host_port, input);
+           if (input == NULL) {
+               return 0;
+           }
+           tmp.num_arg = 1;
+           break;
+
+       case ARG_HOST_PORT_LONG:
+            if (*input != ' ') {
+                return 0;
+            }
+            input++;
+            /* parse the host & port information (if any) */
+            input = parse_host_port_long(&tmp.arg[0].host_port, input);
+            if (input == NULL) {
+                return 0;
+            }
+            tmp.num_arg = 1;
+            break;    
+
+        case ARG_HOST_PORT_EXT:
+            if (*input != ' ') {
+                return 0;
+            }
+            input++;
+            /* parse the host & port information (if any) */
+            input = parse_host_port_ext(&tmp.arg[0].host_port, input);
+            if (input == NULL) {
+                return 0;
+            }
+            tmp.num_arg = 1;
+            break;
+        /* the optional number may also be "ALL" */
+        case ARG_OPTIONAL_NUMBER:
+            if (*input == ' ') {
+                ++input;
+                optional_number = parse_number(&tmp.arg[0].num, input, 255);
+                if (optional_number != NULL) {
+                    input = optional_number;
+                } else {
+                    if ((tolower(input[0]) == 'a') &&
+                        (tolower(input[1]) == 'l') &&
+                        (tolower(input[2]) == 'l')) 
+                    {
+                       tmp.arg[0].num = EPSV_ALL;
+                        input += 3;
+                    } else {
+                        return 0;
+                    }
+                }
+                tmp.num_arg = 1;
+            } else {
+                tmp.num_arg = 0;
+            }
+            break;     
+
+        case ARG_TYPE:
+           if (*input != ' ') {
+               return 0;
+           }
+           input++;
+
+            c = toupper(*input);
+            if ((c == 'A') || (c == 'E')) {
+               tmp.arg[0].string[0] = c;
+               tmp.arg[0].string[1] = '\0';
+               input++;
+
+               if (*input == ' ') {
+                   input++;
+                   c = toupper(*input);
+                   if ((c != 'N') && (c != 'T') && (c != 'C')) {
+                       return 0;
+                   }
+                   tmp.arg[1].string[0] = c;
+                   tmp.arg[1].string[1] = '\0';
+                   input++;
+                   tmp.num_arg = 2;
+               } else {
+                   tmp.num_arg = 1;
+               }
+           } else if (c == 'I') {
+               tmp.arg[0].string[0] = 'I';
+               tmp.arg[0].string[1] = '\0';
+               input++;
+               tmp.num_arg = 1;
+           } else if (c == 'L') {
+               tmp.arg[0].string[0] = 'L';
+               tmp.arg[0].string[1] = '\0';
+               input++;
+               input = parse_number(&tmp.arg[1].num, input, 255);
+               if (input == NULL) {
+                   return 0;
+               }
+               tmp.num_arg = 2;
+           } else {
+               return 0;
+           }
+
+           break;
+
+        case ARG_STRUCTURE:
+           if (*input != ' ') {
+               return 0;
+           }
+           input++;
+
+            c = toupper(*input);
+           if ((c != 'F') && (c != 'R') && (c != 'P')) {
+               return 0;
+           }
+            input++;
+           tmp.arg[0].string[0] = c;
+           tmp.arg[0].string[1] = '\0';
+           tmp.num_arg = 1;
+           break;
+
+        case ARG_MODE:
+           if (*input != ' ') {
+               return 0;
+           }
+           input++;
+
+            c = toupper(*input);
+           if ((c != 'S') && (c != 'B') && (c != 'C')) {
+               return 0;
+           }
+            input++;
+           tmp.arg[0].string[0] = c;
+           tmp.arg[0].string[1] = '\0';
+           tmp.num_arg = 1;
+           break;
+
+        case ARG_OFFSET:
+           if (*input != ' ') {
+               return 0;
+           }
+           input++;
+           input = parse_offset(&tmp.arg[0].offset, input);
+           if (input == NULL) {
+               return 0;
+           }
+           tmp.num_arg = 1;
+           break;
+
+        default:
+           daemon_assert(0);
+    } 
+
+    /* check for our ending newline */
+    if (*input != '\n') {
+        return 0;
+    }
+
+    /* return our result */
+    *cmd = tmp;
+    return 1;
+}
+
+/* copy a string terminated with a newline */
+static const char *copy_string(char *dst, const char *src)
+{
+    int i;
+
+    daemon_assert(dst != NULL);
+    daemon_assert(src != NULL);
+
+    for (i=0; (i<=MAX_STRING_LEN) && (src[i]!='\0') && (src[i]!='\n'); i++) {
+        dst[i] = src[i];
+    }
+    dst[i] = '\0';
+
+    return src+i;
+}
+
+
+static const char *parse_host_port(struct sockaddr_in *addr, const char *s)
+{
+    int i;
+    int octets[6];
+    char addr_str[16];
+    int port;
+    struct in_addr in_addr;
+
+    daemon_assert(addr != NULL);
+    daemon_assert(s != NULL);
+
+    /* scan in 5 pairs of "#," */
+    for (i=0; i<5; i++) {
+        s = parse_number(&octets[i], s, 255);
+       if (s == NULL) {
+           return NULL;
+       }
+       if (*s != ',') {
+           return NULL;
+       }
+       s++;
+    }
+
+    /* scan in ending "#" */
+    s = parse_number(&octets[5], s, 255);
+    if (s == NULL) {
+        return NULL;
+    }
+
+    daemon_assert(octets[0] >= 0);
+    daemon_assert(octets[0] <= 255);
+    daemon_assert(octets[1] >= 0);
+    daemon_assert(octets[1] <= 255);
+    daemon_assert(octets[2] >= 0);
+    daemon_assert(octets[2] <= 255);
+    daemon_assert(octets[3] >= 0);
+    daemon_assert(octets[3] <= 255);
+
+    /* convert our number to a IP/port */
+    sprintf(addr_str, "%d.%d.%d.%d", 
+            octets[0], octets[1], octets[2], octets[3]);
+    port = (octets[4] << 8) | octets[5];
+#ifdef HAVE_INET_ATON
+    if (inet_aton(addr_str, &in_addr) == 0) {
+        return NULL;
+    }
+#else
+    in_addr.s_addr = inet_addr(addr_str);
+    if (in_addr.s_addr == -1) {
+        return NULL;
+    }
+#endif
+    addr->sin_family = AF_INET;
+    addr->sin_addr = in_addr;
+    addr->sin_port = htons(port);
+
+    /* return success */
+    return s;
+}
+
+/* note: returns success even for unknown address families */
+/*       this is okay, as long as subsequent uses VERIFY THE FAMILY first */
+static const char *parse_host_port_long(sockaddr_storage_t *sa, const char *s)
+{   
+    int i;
+    int family;
+    int tmp;
+    int addr_len;
+    unsigned char addr[255];
+    int port_len;
+    unsigned char port[255];
+
+    /* we are family */
+    s = parse_number(&family, s, 255);
+    if (s == NULL) {
+        return NULL;
+    }
+    if (*s != ',') {
+        return NULL;
+    }
+    s++;
+
+    /* parse host length */
+    s = parse_number(&addr_len, s, 255);
+    if (s == NULL) {
+        return NULL;
+    }
+    if (*s != ',') {
+        return NULL;
+    }
+    s++;
+
+    /* parse address */
+    for (i=0; i<addr_len; i++) {
+       daemon_assert(i < sizeof(addr)/sizeof(addr[0]));
+        s = parse_number(&tmp, s, 255);
+       addr[i] = tmp;
+       if (s == NULL) {
+           return NULL;
+       }
+       if (*s != ',') {
+           return NULL;
+       }
+        s++;
+    }
+
+    /* parse port length */
+    s = parse_number(&port_len, s, 255);
+    if (s == NULL) {
+        return NULL;
+    }
+
+    /* parse port */
+    for (i=0; i<port_len; i++) {
+        if (*s != ',') {
+           return NULL;
+       }
+       s++;
+       daemon_assert(i < sizeof(port)/sizeof(port[0]));
+       s = parse_number(&tmp, s, 255);
+        port[i] = tmp;
+    }
+
+    /* okay, everything parses, load the address if possible */
+    if (family == 4) {
+        SAFAM(sa) = AF_INET;
+       if (addr_len != sizeof(struct in_addr)) {
+           return NULL;
+       }
+       if (port_len != 2) {
+           return NULL;
+       }
+       memcpy(&SINADDR(sa), addr, addr_len);
+       SINPORT(sa) = htons((port[0] << 8) + port[1]);
+    }
+#ifdef INET6
+    else if (family == 6) {
+        SAFAM(sa) = AF_INET6;
+       if (addr_len != sizeof(struct in6_addr)) {
+           return NULL;
+       }
+       if (port_len != 2) {
+           return NULL;
+       }
+       memcpy(&SIN6ADDR(sa), addr, addr_len);
+       SINPORT(sa) = htons((port[0] << 8) + port[1]);
+    }
+#endif
+    else {
+        SAFAM(sa) = -1;
+    }
+
+    /* return new pointer */
+    return s;
+}
+
+static const char *parse_host_port_ext(sockaddr_storage_t *sa, const char *s)
+{ 
+    int delimeter;
+    int family;
+    char *p;
+    int len;
+    char addr_str[256];
+    int port;
+
+    /* get delimeter character */
+    if ((*s < 33) || (*s > 126)) {
+        return NULL;
+    }
+    delimeter = *s;
+    s++;
+
+    /* get address family */
+    if (*s == '1') {
+        family = AF_INET;
+    } 
+#ifdef INET6
+    else if (*s == '2') {
+        family = AF_INET6;
+    }
+#endif
+    else {
+        return NULL;
+    }
+    s++;
+    if (*s != delimeter) { 
+        return NULL;
+    }
+    s++;
+
+    /* get address string */
+    p = strchr(s, delimeter);
+    if (p == NULL) {
+        return NULL;
+    }
+    len = p - s;
+    if (len >= sizeof(addr_str)) {
+        return NULL;
+    }
+    memcpy(addr_str, s, len);
+    addr_str[len] = '\0';
+    s = p+1;
+
+    /* get port */
+    s = parse_number(&port, s, 65535);
+    if (s == NULL) {
+        return NULL;
+    }
+    if (*s != delimeter) {
+        return NULL;
+    }
+    s++;
+
+    /* now parse the value passed */
+#ifndef INET6
+    {
+        struct in_addr in_addr;
+
+#ifdef HAVE_INET_ATON
+        if (inet_aton(addr_str, &in_addr) == 0) {
+            return NULL;
+        }
+#else
+        in_addr.s_addr = inet_addr(addr_str);
+        if (in_addr.s_addr == -1) {
+            return NULL;
+        }
+#endif /* HAVE_INET_ATON */
+
+        SIN4ADDR(sa) = in_addr;
+    }
+#else
+    {
+        struct addrinfo hints;
+       struct *res;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_flags = AI_NUMERICHOST;
+       hints.ai_family = family;
+
+       if (getaddrinfo(addr_str, NULL, &hints, &res) != 0) {
+           return NULL;
+       }
+
+       memcpy(sa, res->ai_addr, res->ai_addrlen);
+
+       freeaddrinfo(res);
+    }
+#endif /* INET6 */
+
+    SAFAM(sa) = family;
+    SINPORT(sa) = htons(port);
+
+    /* return new pointer */
+    return s;
+}
+
+/* scan the string for a number from 0 to max_num */
+/* returns the next non-numberic character */
+/* returns NULL if not at least one digit */
+static const char *parse_number(int *num, const char *s, int max_num)
+{
+    int tmp;
+    int cur_digit;
+    
+    daemon_assert(s != NULL);
+    daemon_assert(num != NULL);
+    
+    /* handle first character */
+    if (!isdigit(*s)) {
+        return NULL;
+    }
+    tmp = (*s - '0');
+    s++;
+
+    /* handle subsequent characters */
+    while (isdigit(*s)) {
+        cur_digit = (*s - '0');
+
+        /* check for overflow */
+        if ((max_num - cur_digit) < tmp) {
+           return NULL;
+       }
+
+        tmp *= 10;
+       tmp += cur_digit;
+        s++;
+    }
+
+    daemon_assert(tmp >= 0);
+    daemon_assert(tmp <= max_num);
+
+    /* return the result */
+    *num = tmp;
+    return s;
+}
+
+static const char *parse_offset(off_t *ofs, const char *s)
+{
+    off_t tmp_ofs;
+    int cur_digit;
+    off_t max_ofs;
+#if SIZEOF_UNSIGNED_LONG >= SIZEOF_OFF_T
+    unsigned long cutoff, cutlim;
+#elif SIZEOF_UNSIGNED_LONG_LONG && SIZEOF_UNSIGNED_LONG_LONG >= SIZEOF_OFF_T
+    unsigned long long cutoff, cutlim;
+#else
+# error cannot figure out a type larger or equal to off_t
+#endif
+
+    daemon_assert(ofs != NULL);
+    daemon_assert(s != NULL);
+
+    /* calculate maximum allowable offset based on size of off_t */
+#if SIZEOF_OFF_T == 8
+    max_ofs = (off_t)9223372036854775807LL;
+#elif SIZEOF_OFF_T == 4
+    max_ofs = (off_t)2147483647LL;
+#elif SIZEOF_OFF_T == 2
+    max_ofs = (off_t)32767LL;
+#else
+# error sizeof(off_t) unknown
+#endif
+
+    cutoff = max_ofs / 10;
+    cutlim = max_ofs % 10;
+
+    /* handle first character */
+    if (!isdigit(*s)) {
+        return NULL;
+    }
+    tmp_ofs = (*s - '0');
+    s++;
+
+    /* handle subsequent characters */
+    while (isdigit(*s)) {
+        cur_digit = (*s - '0');
+
+        if (tmp_ofs > cutoff || (tmp_ofs == cutoff && cur_digit > cutlim))
+          return NULL; /*overflow*/
+
+        tmp_ofs *= 10;
+       tmp_ofs += cur_digit;
+        s++;
+    }
+
+    /* return */
+    *ofs = tmp_ofs;
+    return s;
+}
+
diff --git a/src/ftp_command.h b/src/ftp_command.h
new file mode 100644 (file)
index 0000000..18b0731
--- /dev/null
@@ -0,0 +1,63 @@
+/* 
+ * $Id$
+ *
+ * The following comands are parsed:
+ *
+ * USER <SP> <username>
+ * PASS <SP> <password>
+ * CWD  <SP> <pathname> 
+ * CDUP
+ * QUIT
+ * PORT <SP> <host-port>
+ * LPRT <SP> <host-port-long>
+ * EPRT <SP> <host-port-ext>
+ * PASV
+ * LPSV
+ * EPSV [ <SP> <optional-number-or-all> ]
+ * TYPE <SP> <type-code>
+ * STRU <SP> <structure-code>
+ * MODE <SP> <mode-code>
+ * RETR <SP> <pathname>
+ * STOR <SP> <pathname>
+ * PWD
+ * LIST [ <SP> <pathname> ]
+ * NLST [ <SP> <pathname> ]
+ * SYST
+ * HELP [ <SP> <string> ]
+ * NOOP
+ * REST <SP> <offset>
+ */
+
+#ifndef FTP_COMMAND_H
+#define FTP_COMMAND_H
+
+#include <netinet/in.h>
+#include <limits.h>
+#include <sys/types.h>
+#include "af_portability.h"
+
+/* special macro for handling EPSV ALL requests */
+#define EPSV_ALL (-1)
+
+/* maximum possible number of arguments */
+#define MAX_ARG 2
+
+/* maximum string length */
+#define MAX_STRING_LEN PATH_MAX
+
+typedef struct {
+    char command[5];
+    int num_arg;
+    union {
+        char string[MAX_STRING_LEN+1];
+        sockaddr_storage_t host_port;
+        int num;
+       off_t offset;
+    } arg[MAX_ARG];
+} ftp_command_t;
+
+
+int ftp_command_parse(const char *input, ftp_command_t *cmd);
+
+#endif /* FTP_COMMAND_H */
+
diff --git a/src/ftp_listener.c b/src/ftp_listener.c
new file mode 100644 (file)
index 0000000..a3a6fa7
--- /dev/null
@@ -0,0 +1,578 @@
+/*
+
+$Id$
+This is the code that waits for client connections and creates the
+threads that handle them.  When ftp_listener_init() is called, it binds
+to the appropriate socket and sets up the other values for the
+structure.  Then, when ftp_listener_start() is called, a thread is
+started dedicatd to accepting connections.
+
+This thread waits for input on two file descriptors.  If input arrives
+on the socket, then it accepts the connection and creates a thread to
+handle it.  If input arrives on the shutdown_request pipe, then the
+thread terminates.  This is how ftp_listener_stop() signals the listener
+to end.
+
+*/
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <syslog.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <fcntl.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#include "daemon_assert.h"
+#include "telnet_session.h"
+#include "ftp_session.h"
+#include "ftp_listener.h"
+#include "watchdog.h"
+#include "af_portability.h"
+
+
+
+/* maximum number of consecutive errors in accept()
+   before we terminate listener                     */
+#define MAX_ACCEPT_ERROR 10
+
+/* buffer to hold address string */
+#define ADDR_BUF_LEN 100
+
+/* information for a specific connection */
+typedef struct connection_info {
+    ftp_listener_t *ftp_listener;
+    telnet_session_t telnet_session;
+    ftp_session_t ftp_session;
+    watched_t watched;
+    
+    struct connection_info *next;
+} connection_info_t;
+
+/* prototypes */
+static int invariant(const ftp_listener_t *f);
+static void *connection_acceptor(ftp_listener_t *f);
+static void addr_to_string(const sockaddr_storage_t *s, char *addr);
+static void *connection_handler(connection_info_t *info);
+static void connection_handler_cleanup(connection_info_t *info);
+
+/* initialize an FTP listener */
+int ftp_listener_init(ftp_listener_t *f, 
+                      char *address, 
+                      int port, 
+                      int max_connections,
+                      int inactivity_timeout,
+                      error_t *err)
+{
+    sockaddr_storage_t sock_addr;
+    int fd;
+    int flags;
+    int pipefds[2];
+    int reuseaddr;
+    char dir[PATH_MAX+1];
+    char buf[ADDR_BUF_LEN+1];
+    const char *inet_ntop_ret;
+
+    daemon_assert(f != NULL);
+    daemon_assert(port >= 0);
+    daemon_assert(port < 65536);
+    daemon_assert(max_connections > 0);
+    daemon_assert(err != NULL);
+
+    /* get our current directory */
+    if (getcwd(dir, sizeof(dir)) == NULL) {
+        error_init(err, errno, "error getting current directory; %s",
+                   strerror(errno));
+        return 0;
+    }
+
+    /* set up our socket address */
+    memset(&sock_addr, 0, sizeof(sockaddr_storage_t));
+
+    if (address == NULL) {
+#ifdef INET6
+        SAFAM(&sock_addr) = AF_INET6;
+        memcpy(&SIN6ADDR(&sock_addr), &in6addr_any, sizeof(struct in6_addr));
+#else
+        SAFAM(&sock_addr) = AF_INET;
+        sock_addr.sin_addr.s_addr = INADDR_ANY;
+#endif
+    } else {
+        int gai_err;
+        struct addrinfo hints;
+        struct addrinfo *res;
+
+        memset(&hints, 0, sizeof(hints));
+
+/* - This code should be able to parse both IPv4 and IPv6 internet
+ * addresses and put them in the sock_addr variable.
+ * - Much neater now.
+ * - Bug: Can't handle hostnames, only IP addresses.  Not sure
+ * exactly why.  But then again, I'm not sure exactly why
+ * there is a man page for getipnodebyname (which getaddrinfo
+ * says it uses) but no corresponding function in libc.
+ * -- Matthew Danish [3/20/2001]
+ */
+
+#ifdef INET6
+        hints.ai_family = AF_INET6;
+#else
+        hints.ai_family = AF_INET;
+#endif
+
+        hints.ai_flags  = AI_PASSIVE;
+
+        gai_err = getaddrinfo(address, NULL, &hints, &res);
+        if (gai_err != 0) {
+            error_init(err, gai_err, "error parsing server socket address; %s", 
+                       gai_strerror(gai_err));
+            return 0;
+        }
+
+        memcpy(&sock_addr, res->ai_addr, res->ai_addrlen);
+        freeaddrinfo(res);
+    } 
+
+    if (port == 0) {
+        SINPORT(&sock_addr) = htons(DEFAULT_FTP_PORT);
+    } else {
+        SINPORT(&sock_addr) = htons(port);
+    }
+
+
+    inet_ntop_ret = inet_ntop(SAFAM(&sock_addr), 
+                              (void *)&SINADDR(&sock_addr), 
+                              buf, 
+                              sizeof(buf));
+    if (inet_ntop_ret == NULL) {
+        error_init(err, errno, "error converting server address to ASCII; %s", 
+                   strerror(errno));
+        return 0;
+    }
+    
+    /* Put some information in syslog */
+    syslog(LOG_INFO, "Binding interface '%s', port %d, max clients %d\n", buf, 
+        ntohs(SINPORT(&sock_addr)), max_connections);    
+    
+
+    /* okay, finally do some socket manipulation */
+    fd = socket(AF_INET, SOCK_STREAM, 0);
+    reopen_syslog_hack (fd);
+    if (fd == -1) {
+        error_init(err, errno, "error creating socket; %s", strerror(errno));
+        return 0;
+    }
+
+    reuseaddr = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&reuseaddr, 
+                   sizeof(int)) !=0) 
+    {
+        close(fd);
+        error_init(err, errno, "error setting socket to reuse address; %s", 
+                   strerror(errno));
+        return 0;
+    }
+
+    if (bind(fd, (struct sockaddr *)&sock_addr, 
+             sizeof(struct sockaddr_in)) != 0) 
+    {
+        close(fd);
+        error_init(err, errno, "error binding address; %s", strerror(errno));
+        return 0;
+    }
+
+    if (listen(fd, SOMAXCONN) != 0) {
+        close(fd);
+        error_init(err, errno, "error setting socket to listen; %s", 
+                   strerror(errno));
+        return 0;
+    }
+
+    /* prevent socket from blocking on accept() */
+    flags = fcntl(fd, F_GETFL);
+    if (flags == -1) {
+        close(fd);
+        error_init(err, errno, "error getting flags on socket; %s", 
+                   strerror(errno));
+        return 0;
+    }
+    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) {
+        close(fd);
+        error_init(err, errno, "error setting socket to non-blocking; %s", 
+                   strerror(errno));
+        return 0;
+    }
+
+    /* create a pipe to wake up our listening thread */
+    if (pipe(pipefds) != 0) {
+        close(fd);
+        error_init(err, errno, "error creating pipe for internal use; %s", 
+                   strerror(errno));
+        return 0;
+    }
+
+    /* now load the values into the structure, since we can't fail from
+       here */
+    f->fd = fd;
+    f->max_connections = max_connections;
+    f->num_connections = 0;
+    f->inactivity_timeout = inactivity_timeout;
+    pthread_mutex_init(&f->mutex, NULL);
+
+    daemon_assert(strlen(dir) < sizeof(f->dir));
+    strcpy(f->dir, dir);
+    f->listener_running = 0;
+
+    f->shutdown_request_send_fd = pipefds[1];
+    f->shutdown_request_recv_fd = pipefds[0];
+    pthread_cond_init(&f->shutdown_cond, NULL);
+
+    daemon_assert(invariant(f));
+    return 1;
+}
+
+/* receive connections */
+int ftp_listener_start(ftp_listener_t *f, error_t *err)
+{
+    pthread_t thread_id;
+    int ret_val;
+    int error_code;
+
+    daemon_assert(invariant(f));
+    daemon_assert(err != NULL);
+
+    error_code = pthread_create(&thread_id, 
+                                NULL, 
+                                (void *(*)())connection_acceptor, 
+                                f);
+
+    if (error_code == 0) {
+        f->listener_running = 1;
+        f->listener_thread = thread_id;
+        ret_val = 1;
+    } else {
+        error_init(err, error_code, "unable to create thread");
+        ret_val = 0;
+    }
+
+    daemon_assert(invariant(f));
+
+    return ret_val;
+}
+
+
+#ifndef NDEBUG
+static int invariant(const ftp_listener_t *f) 
+{
+    int dir_len;
+
+    if (f == NULL) {
+        return 0;
+    }
+    if (f->fd < 0) {
+        return 0;
+    }
+    if (f->max_connections <= 0) {
+        return 0;
+    }
+    if ((f->num_connections < 0) || (f->num_connections > f->max_connections)) {
+        return 0;
+    }
+    dir_len = strlen(f->dir);
+    if ((dir_len <= 0) || (dir_len > PATH_MAX)) {
+        return 0;
+    }
+    if (f->shutdown_request_send_fd < 0) {
+        return 0;
+    }
+    if (f->shutdown_request_recv_fd < 0) {
+        return 0;
+    }
+    return 1;
+}
+#endif /* NDEBUG */
+
+/* handle incoming connections */
+static void *connection_acceptor(ftp_listener_t *f)
+{
+    error_t err;
+    int num_error;
+
+    int fd;
+    int tcp_nodelay;
+    sockaddr_storage_t client_addr;
+    sockaddr_storage_t server_addr;
+    unsigned addr_len;
+    
+    connection_info_t *info;
+    pthread_t thread_id;
+    int error_code;
+
+    fd_set readfds;
+
+    daemon_assert(invariant(f));
+
+    if (!watchdog_init(&f->watchdog, f->inactivity_timeout, &err)) {
+        syslog(LOG_ERR, "Error initializing watchdog thread; %s", 
+            error_get_desc(&err));
+        return NULL;
+    }
+
+    num_error = 0;
+    for (;;) {
+
+         /* wait for something to happen */
+         FD_ZERO(&readfds);
+         FD_SET(f->fd, &readfds);
+         FD_SET(f->shutdown_request_recv_fd, &readfds);
+         select(FD_SETSIZE, &readfds, NULL, NULL, NULL);
+
+         /* if data arrived on our pipe, we've been asked to exit */
+         if (FD_ISSET(f->shutdown_request_recv_fd, &readfds)) {
+             close(f->fd);
+             syslog(LOG_INFO, "listener no longer accepting connections");
+             pthread_exit(NULL);
+         }
+
+         /* otherwise accept our pending connection (if any) */
+         addr_len = sizeof(sockaddr_storage_t);
+         fd = accept(f->fd, (struct sockaddr *)&client_addr, &addr_len);
+         reopen_syslog_hack (fd);
+         if (fd >= 0) {
+
+             tcp_nodelay = 1;
+             if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&tcp_nodelay,
+                 sizeof(int)) != 0)
+             {
+                 syslog(LOG_ERR,
+                   "error in setsockopt(), FTP server dropping connection; %s",
+                   strerror(errno));
+                 close(fd);
+                 continue;
+             }
+
+             addr_len = sizeof(sockaddr_storage_t);
+             if (getsockname(fd, (struct sockaddr *)&server_addr, 
+                 &addr_len) == -1) 
+             {
+                 syslog(LOG_ERR, 
+                   "error in getsockname(), FTP server dropping connection; %s",
+                   strerror(errno));
+                 close(fd);
+                 continue;
+             }
+
+             info = (connection_info_t *)malloc(sizeof(connection_info_t));
+             if (info == NULL) {
+                 syslog(LOG_CRIT, 
+                     "out of memory, FTP server dropping connection");
+                 close(fd);
+                 continue;
+             }
+             info->ftp_listener = f;
+
+             telnet_session_init(&info->telnet_session, fd, fd);
+
+             if (!ftp_session_init(&info->ftp_session, 
+                                   &client_addr, 
+                                   &server_addr, 
+                                   &info->telnet_session,  
+                                   f->dir, 
+                                   &err)) 
+             {
+                 syslog(LOG_ERR, 
+                     "error initializing FTP session, FTP server exiting; %s",
+                     error_get_desc(&err));
+                 close(fd);
+                 telnet_session_destroy(&info->telnet_session);
+                 free(info);
+                 continue;
+             }
+
+
+             error_code = pthread_create(&thread_id, 
+                          NULL, 
+                          (void *(*)())connection_handler, 
+                          info);
+
+             if (error_code != 0) {
+                 syslog(LOG_ERR, "error creating new thread; %d", error_code);
+                 close(fd);
+                 telnet_session_destroy(&info->telnet_session);
+                 free(info);
+             }
+
+             num_error = 0;
+         } else {
+             if ((errno == ECONNABORTED) || (errno == ECONNRESET)) {
+                 syslog(LOG_NOTICE, 
+                     "interruption accepting FTP connection; %s", 
+                     strerror(errno));
+             } else {
+                 syslog(LOG_WARNING, 
+                     "error accepting FTP connection; %s", 
+                     strerror(errno));
+                 ++num_error;
+             }
+             if (num_error >= MAX_ACCEPT_ERROR) {
+                 syslog(LOG_ERR, 
+                   "too many consecutive errors, FTP server exiting");
+                 return NULL;
+             }
+         }
+    }
+}
+
+/* convert an address to a printable string */
+/* NOT THREADSAFE - wrap with a mutex before calling! */
+static char *addr2string(const sockaddr_storage_t *s)
+{
+    static char addr[IP_ADDRSTRLEN+1];
+    int error;
+    char *ret_val;
+
+    daemon_assert(s != NULL);
+
+#ifdef INET6
+    error = getnameinfo((struct sockaddr *)s, 
+                         sizeof(sockaddr_storage_t),
+                         addr, 
+                         sizeof(addr), 
+                         NULL,
+                         0, 
+                         NI_NUMERICHOST);
+    if (error != 0) {
+        syslog(LOG_WARN, "getnameinfo error; %s", gai_strerror(error));
+        ret_val = "Unknown IP";
+    } else {
+        ret_val = addr;
+    }
+#else
+    ret_val = inet_ntoa(s->sin_addr);
+#endif
+
+    return ret_val;
+}
+
+
+static void *connection_handler(connection_info_t *info) 
+{
+    ftp_listener_t *f;
+    int num_connections;
+    char drop_reason[80];
+
+    /* for ease of use only */
+    f = info->ftp_listener;
+
+    /* don't save state for pthread_join() */
+    pthread_detach(pthread_self());
+
+    /* set up our watchdog */
+    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+    watchdog_add_watched(&f->watchdog, &info->watched);
+
+    /* set up our cleanup handler */
+    pthread_cleanup_push((void (*)())connection_handler_cleanup, info);
+
+    /* process global data */
+    pthread_mutex_lock(&f->mutex);
+    num_connections = ++f->num_connections;
+    syslog(LOG_INFO, "%s port %d connection", 
+        addr2string(&info->ftp_session.client_addr), 
+        ntohs(SINPORT(&info->ftp_session.client_addr)));
+    pthread_mutex_unlock(&f->mutex);
+
+    /* handle the session */
+    if (num_connections <= f->max_connections) {
+
+        ftp_session_run(&info->ftp_session, &info->watched);
+
+    } else {
+
+        /* too many users */
+        sprintf(drop_reason, 
+          "Too many users logged in, dropping connection (%d logins maximum)", 
+          f->max_connections);
+        ftp_session_drop(&info->ftp_session, drop_reason);
+
+        /* log the rejection */
+        pthread_mutex_lock(&f->mutex);
+        syslog(LOG_WARNING, 
+          "%s port %d exceeds max users (%d), dropping connection",
+          addr2string(&info->ftp_session.client_addr), 
+          ntohs(SINPORT(&info->ftp_session.client_addr)),
+          num_connections);
+        pthread_mutex_unlock(&f->mutex);
+
+    }
+
+    /* exunt (pop calls cleanup function) */
+    pthread_cleanup_pop(1);
+
+    /* return for form :) */
+    return NULL;
+}
+
+/* clean up a connection */
+static void connection_handler_cleanup(connection_info_t *info) 
+{
+    ftp_listener_t *f;
+
+    f = info->ftp_listener;
+
+    watchdog_remove_watched(&info->watched);
+
+    pthread_mutex_lock(&f->mutex);
+
+    f->num_connections--;
+    pthread_cond_signal(&f->shutdown_cond);
+
+    syslog(LOG_INFO, 
+      "%s port %d disconnected", 
+      addr2string(&info->ftp_session.client_addr),
+      ntohs(SINPORT(&info->ftp_session.client_addr)));
+
+    pthread_mutex_unlock(&f->mutex);
+
+    ftp_session_destroy(&info->ftp_session);
+    telnet_session_destroy(&info->telnet_session);
+
+    free(info);
+}
+
+void ftp_listener_stop(ftp_listener_t *f)
+{
+    daemon_assert(invariant(f));
+
+    /* write a byte to the listening thread - this will wake it up */
+    write(f->shutdown_request_send_fd, "", 1);
+
+    /* wait for client connections to complete */
+    pthread_mutex_lock(&f->mutex);
+    while (f->num_connections > 0) {
+        pthread_cond_wait(&f->shutdown_cond, &f->mutex);
+    }
+    pthread_mutex_unlock(&f->mutex);
+}
+
diff --git a/src/ftp_listener.h b/src/ftp_listener.h
new file mode 100644 (file)
index 0000000..9ea4864
--- /dev/null
@@ -0,0 +1,65 @@
+/* 
+ * $Id$
+ */
+
+#ifndef FTP_LISTENER_H
+#define FTP_LISTENER_H
+
+#include <limits.h>
+#include <pthread.h>
+#include "error.h"
+#include "watchdog.h"
+
+#define DEFAULT_FTP_PORT 21
+
+typedef struct {
+
+    /* file descriptor incoming connections arrive on */
+    int fd;
+
+    /* maximum number of connections */
+    int max_connections;
+
+    /* current number of connections */
+    int num_connections;
+
+    /* timeout (in seconds) for connections */
+    int inactivity_timeout;
+
+    /* watchdog monitoring this listener's connections */
+    watchdog_t watchdog;
+
+    /* mutext to lock changes to this structure */
+    pthread_mutex_t mutex;
+
+    /* starting directory */
+    char dir[PATH_MAX+1];
+
+    /* boolean defining whether listener is running or not */
+    int listener_running;
+
+    /* thread identifier for listener */
+    pthread_t listener_thread;
+
+    /* end of pipe to wake up listening thread with */
+    int shutdown_request_send_fd;
+
+    /* end of pipe listening thread waits on */
+    int shutdown_request_recv_fd;
+
+    /* condition to signal thread requesting shutdown */
+    pthread_cond_t shutdown_cond;
+
+} ftp_listener_t;
+
+int ftp_listener_init(ftp_listener_t *f, 
+                      char *address, 
+                      int port, 
+                      int max_connections,
+                      int inactivity_timeout, 
+                      error_t *err);
+int ftp_listener_start(ftp_listener_t *f, error_t *err);
+void ftp_listener_stop(ftp_listener_t *f);
+
+#endif /* FTP_LISTENER_H */
+
diff --git a/src/ftp_session.c b/src/ftp_session.c
new file mode 100644 (file)
index 0000000..94b533f
--- /dev/null
@@ -0,0 +1,1830 @@
+/* 
+ * $Id$
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <pthread.h>
+#include <sys/utsname.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#include "daemon_assert.h"
+#include "telnet_session.h"
+#include "ftp_command.h"
+#include "file_list.h"
+#include "ftp_session.h"
+#include "watchdog.h"
+#include "oftpd.h"
+#include "af_portability.h"
+
+/* space requirements */
+#define ADDRPORT_STRLEN 58
+
+/* prototypes */
+static int invariant(const ftp_session_t *f);
+static void reply(ftp_session_t *f, int code, const char *fmt, ...);
+static void change_dir(ftp_session_t *f, const char *new_dir);
+static int open_connection(ftp_session_t *f);
+static int write_fully(int fd, const char *buf, int buflen);
+static void init_passive_port();
+static int get_passive_port();
+static int convert_newlines(char *dst, const char *src, int srclen);
+static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz);
+static void send_readme(const ftp_session_t *f, int code);
+static void netscape_hack(int fd);
+static void set_port(ftp_session_t *f, const sockaddr_storage_t *host_port);
+static int set_pasv(ftp_session_t *f, sockaddr_storage_t *host_port);
+static int ip_equal(const sockaddr_storage_t *a, const sockaddr_storage_t *b);
+static void get_absolute_fname(char *fname, 
+                               int fname_len,
+                               const char *dir,
+                               const char *file);
+
+/* command handlers */
+static void do_user(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_pass(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_cwd(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_cdup(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_quit(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_port(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_pasv(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_type(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_stru(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_mode(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_retr(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_stor(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_pwd(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_nlst(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_list(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_syst(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_noop(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_rest(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_lprt(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_lpsv(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_eprt(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_epsv(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_size(ftp_session_t *f, const ftp_command_t *cmd);
+static void do_mdtm(ftp_session_t *f, const ftp_command_t *cmd);
+
+static struct {
+    char *name;
+    void (*func)(ftp_session_t *f, const ftp_command_t *cmd);
+} command_func[] = {
+    { "USER", do_user },
+    { "PASS", do_pass },
+    { "CWD",  do_cwd },
+    { "CDUP", do_cdup },
+    { "QUIT", do_quit },
+    { "PORT", do_port },
+    { "PASV", do_pasv },
+    { "LPRT", do_lprt },
+    { "LPSV", do_lpsv },
+    { "EPRT", do_eprt },
+    { "EPSV", do_epsv },
+    { "TYPE", do_type },
+    { "STRU", do_stru },
+    { "MODE", do_mode },
+    { "RETR", do_retr },
+    { "STOR", do_stor },
+    { "PWD",  do_pwd },
+    { "NLST", do_nlst },
+    { "LIST", do_list },
+    { "SYST", do_syst },
+    { "NOOP", do_noop },
+    { "REST", do_rest },
+    { "SIZE", do_size },
+    { "MDTM", do_mdtm }
+};
+
+#define NUM_COMMAND_FUNC (sizeof(command_func) / sizeof(command_func[0]))
+
+
+int ftp_session_init(ftp_session_t *f, 
+                     const sockaddr_storage_t *client_addr, 
+                     const sockaddr_storage_t *server_addr, 
+                     telnet_session_t *t, 
+                     const char *dir,
+                    error_t *err)
+{
+    daemon_assert(f != NULL);
+    daemon_assert(client_addr != NULL);
+    daemon_assert(server_addr != NULL);
+    daemon_assert(t != NULL);
+    daemon_assert(dir != NULL);
+    daemon_assert(strlen(dir) <= PATH_MAX);
+    daemon_assert(err != NULL);
+
+#ifdef INET6
+    /* if the control connection is on IPv6, we need to get an IPv4 address */
+    /* to bind the socket to */
+    if (SSFAM(server_addr) == AF_INET6) {
+       struct addrinfo hints;
+       struct addrinfo *res;
+       int errcode;
+
+        /* getaddrinfo() does the job nicely */
+        memset(&hints, 0, sizeof(struct addrinfo));
+        hints.ai_family = AF_INET;
+       hints.ai_flags = AI_PASSIVE;
+       if (getaddrinfo(NULL, "ftp", &hints, &res) != 0) {
+           error_init(err, 0, "unable to determing IPv4 address; %s",
+               gai_strerror(errcode));
+           return 0;
+       }
+
+        /* let's sanity check */
+       daemon_assert(res != NULL);
+       daemon_assert(sizeof(f->server_ipv4_addr) >= res->ai_addrlen);
+        daemon_assert(SSFAM(host_port) == AF_INET);
+
+        /* copy the result and free memory as necessary */
+       memcpy(&f->server_ipv4_addr, res->ai_addr, res->ai_addrlen);
+        freeaddrinfo(res);
+    } else {
+        daemon_assert(SSFAM(host_port) == AF_INET);
+        f->server_ipv4_addr = *server_addr;
+    }
+#else
+    f->server_ipv4_addr = *server_addr;
+#endif
+
+    f->session_active = 1;
+    f->command_number = 0;
+
+    f->data_type = TYPE_ASCII;
+    f->file_structure = STRU_FILE;
+
+    f->file_offset = 0;
+    f->file_offset_command_number = ULONG_MAX;
+
+    f->epsv_all_set = 0;
+
+    f->client_addr = *client_addr;
+    get_addr_str(client_addr, f->client_addr_str, sizeof(f->client_addr_str));
+
+    f->server_addr = *server_addr;
+
+    f->telnet_session = t;
+    daemon_assert(strlen(dir) < sizeof(f->dir));
+    strcpy(f->dir, dir);
+
+    f->data_channel = DATA_PORT;
+    f->data_port = *client_addr;
+    f->server_fd = -1;
+
+    daemon_assert(invariant(f));
+
+    return 1;
+}
+
+void ftp_session_drop(ftp_session_t *f, const char *reason)
+{
+    daemon_assert(invariant(f));
+    daemon_assert(reason != NULL);
+
+    /* say goodbye */
+    reply(f, 421, "%s.", reason);
+
+    daemon_assert(invariant(f));
+}
+
+void ftp_session_run(ftp_session_t *f, watched_t *watched)
+{
+    char buf[2048];
+    int len;
+    ftp_command_t cmd;
+    int i;
+
+    daemon_assert(invariant(f));
+    daemon_assert(watched != NULL);
+
+    /* record our watchdog */
+    f->watched = watched;
+
+    /* say hello */
+    send_readme(f, 220);
+    reply(f, 220, "Service ready for new user.");
+
+    /* process commands */
+    while (f->session_active && 
+        telnet_session_readln(f->telnet_session, buf, sizeof(buf))) 
+    {
+     
+        /* delay our timeout based on this input */
+       watchdog_defer_watched(f->watched);
+
+       /* increase our command count */
+       if (f->command_number == ULONG_MAX) {
+           f->command_number = 0;
+       } else {
+           f->command_number++;
+       }
+    
+        /* make sure we read a whole line */
+        len = strlen(buf);
+       if (buf[len-1] != '\n') {
+            reply(f, 500, "Command line too long.");
+            while (telnet_session_readln(f->telnet_session, buf, sizeof(buf))) {
+                len = strlen(buf);
+               if (buf[len-1] == '\n') {
+                   break;
+               }
+            }
+           goto next_command;
+       }
+
+        syslog(LOG_DEBUG, "%s %s", f->client_addr_str, buf);
+
+       /* parse the line */
+       if (!ftp_command_parse(buf, &cmd)) {
+           reply(f, 500, "Syntax error, command unrecognized.");
+           goto next_command;
+       }
+
+       /* dispatch the command */
+       for (i=0; i<NUM_COMMAND_FUNC; i++) {
+           if (strcmp(cmd.command, command_func[i].name) == 0) {
+               (command_func[i].func)(f, &cmd);
+               goto next_command;
+           }
+       }
+
+        /* oops, we don't have this command (shouldn't happen - shrug) */
+       reply(f, 502, "Command not implemented.");
+
+next_command: {}
+    }
+
+    daemon_assert(invariant(f));
+}
+
+void ftp_session_destroy(ftp_session_t *f) 
+{
+    daemon_assert(invariant(f));
+
+    if (f->server_fd != -1) {
+        close(f->server_fd);
+       f->server_fd = -1;
+    }
+}
+
+#ifndef NDEBUG
+static int invariant(const ftp_session_t *f)
+{
+    int len;
+
+    if (f == NULL) {
+        return 0;
+    }
+    if ((f->session_active != 0) && (f->session_active != 1)) {
+        return 0;
+    }
+    if ((f->data_type != TYPE_ASCII) && (f->data_type != TYPE_IMAGE)) {
+        return 0;
+    }
+    if ((f->file_structure != STRU_FILE) && (f->file_structure != STRU_RECORD)){
+        return 0;
+    }
+    if (f->file_offset < 0) {
+        return 0;
+    }
+    if ((f->epsv_all_set != 0) && (f->epsv_all_set != 1)) {
+        return 0;
+    }
+    len = strlen(f->client_addr_str);
+    if ((len <= 0) || (len >= ADDRPORT_STRLEN)) {
+        return 0;
+    }
+    if (f->telnet_session == NULL) {
+        return 0;
+    }
+    switch (f->data_channel) {
+       case DATA_PORT:
+            /* If the client specifies a port, verify that it is from the   */
+           /* host the client connected from.  This prevents a client from */
+           /* using the server to open connections to arbritrary hosts.    */
+           if (!ip_equal(&f->client_addr, &f->data_port)) {
+               return 0;
+           }
+           if (f->server_fd != -1) {
+               return 0;
+           }
+           break;
+       case DATA_PASSIVE:
+           if (f->server_fd < 0) {
+               return 0;
+           }
+           break;
+        default:
+           return 0;
+    }
+    return 1;
+}
+#endif /* NDEBUG */
+
+static void reply(ftp_session_t *f, int code, const char *fmt, ...)
+{
+    char buf[256];
+    va_list ap;
+
+    daemon_assert(invariant(f));
+    daemon_assert(code >= 100);
+    daemon_assert(code <= 559);
+    daemon_assert(fmt != NULL);
+
+    /* prepend our code to the buffer */
+    sprintf(buf, "%d", code);
+    buf[3] = ' ';
+
+    /* add the formatted output of the caller to the buffer */
+    va_start(ap, fmt);
+    vsnprintf(buf+4, sizeof(buf)-4, fmt, ap);
+    va_end(ap);
+
+    /* log our reply */
+    syslog(LOG_DEBUG, "%s %s", f->client_addr_str, buf);
+
+    /* send the output to the other side */
+    telnet_session_println(f->telnet_session, buf);
+
+    daemon_assert(invariant(f));
+}
+
+static void do_user(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    const char *user;
+    char addr_port[ADDRPORT_STRLEN];
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    user = cmd->arg[0].string;
+    if (strcasecmp(user, "ftp") && strcasecmp(user, "anonymous")) {
+       syslog(LOG_WARNING, "%s attempted to log in as \"%s\"", 
+            f->client_addr_str, user);
+        reply(f, 530, "Only anonymous FTP supported.");
+    } else {
+        reply(f, 331, "Send e-mail address as password.");
+    }
+    daemon_assert(invariant(f));
+}
+
+
+static void do_pass(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    const char *password;
+    char addr_port[ADDRPORT_STRLEN];
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    password = cmd->arg[0].string;
+    syslog(LOG_INFO, "%s reports e-mail address \"%s\"", 
+        f->client_addr_str, password);
+    reply(f, 230, "User logged in, proceed.");
+
+    daemon_assert(invariant(f));
+}
+
+#ifdef INET6
+static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz)
+{
+    int port;
+    int error;
+    int len;
+
+    daemon_assert(s != NULL);
+    daemon_assert(buf != NULL);
+
+    /* buf must be able to contain (at least) a string representing an
+     * ipv4 addr, followed by the string " port " (6 chars) and the port
+     * number (which is 5 chars max), plus the '\0' character. */ 
+    daemon_assert(bufsiz >= (INET_ADDRSTRLEN + 12));
+
+    error = getnameinfo(client_addr, sizeof(sockaddr_storage_t), buf, 
+                bufsiz, NULL, 0, NI_NUMERICHOST);
+    /* getnameinfo() should never fail when called with NI_NUMERICHOST */
+    daemon_assert(error == 0);
+
+    len = strlen(buf);
+    daemon_assert(bufsiz >= len+12);
+    snprintf(buf+len, bufsiz-len, " port %d", ntohs(SINPORT(&f->client_addr)));
+}
+#else
+static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz)
+{
+    unsigned int addr;
+    int port;
+
+    daemon_assert(s != NULL);
+    daemon_assert(buf != NULL);
+
+    /* buf must be able to contain (at least) a string representing an
+     * ipv4 addr, followed by the string " port " (6 chars) and the port
+     * number (which is 5 chars max), plus the '\0' character. */ 
+    daemon_assert(bufsiz >= (INET_ADDRSTRLEN + 12));
+
+    addr = ntohl(s->sin_addr.s_addr);
+    port = ntohs(s->sin_port);
+    snprintf(buf, bufsiz, "%d.%d.%d.%d port %d", 
+        (addr >> 24) & 0xff, 
+       (addr >> 16) & 0xff,
+       (addr >> 8)  & 0xff,
+       addr & 0xff,
+        port);
+}
+#endif
+
+static void do_cwd(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    const char *new_dir;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    new_dir = cmd->arg[0].string;
+    change_dir(f, new_dir);
+
+    daemon_assert(invariant(f));
+}
+
+static void do_cdup(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 0);
+
+    change_dir(f, "..");
+
+    daemon_assert(invariant(f));
+}
+
+static void change_dir(ftp_session_t *f, const char *new_dir)
+{
+    char target[PATH_MAX+1];
+    const char *p, *n;
+    int len;
+    char *prev_dir;
+    char *target_end;
+
+    struct stat stat_buf;
+    int dir_okay;
+
+    daemon_assert(invariant(f));
+    daemon_assert(new_dir != NULL);
+    daemon_assert(strlen(new_dir) <= PATH_MAX);
+
+    /* set up our "base" directory that we build from */
+    p = new_dir;
+    if (*p == '/') {
+        /* if this starts with a '/' it is an absolute path */
+        strcpy(target, "/");
+       do {
+           p++;
+       } while (*p == '/');
+    } else {
+        /* otherwise it's a relative path */
+       daemon_assert(strlen(f->dir) < sizeof(target));
+       strcpy(target, f->dir);
+    }
+
+    /* add on each directory, handling "." and ".." */
+    while (*p != '\0') {
+
+        /* find the end of the next directory (either at '/' or '\0') */
+        n = strchr(p, '/');
+       if (n == NULL) {
+           n = strchr(p, '\0');
+       }
+       len = n - p;
+
+        if ((len == 1) && (p[0] == '.')) {
+
+           /* do nothing with "." */
+
+       } else if ((len == 2) && (p[0] == '.') && (p[1] == '.')) {
+
+           /* change to previous directory with ".." */
+            prev_dir = strrchr(target, '/');
+           daemon_assert(prev_dir != NULL);
+           *prev_dir = '\0';
+           if (prev_dir == target) {
+                strcpy(target, "/");
+           }
+
+       } else {
+
+           /* otherwise add to current directory */
+           if ((strlen(target) + 1 + len) > PATH_MAX) {
+               reply(f, 550, "Error changing directory; path is too long.");
+               return;
+           }
+
+           /* append a '/' unless we were at the root directory */
+           target_end = strchr(target, '\0');
+           if (target_end != target+1) {
+               *target_end++ = '/';
+           }
+
+           /* add the directory itself */
+           while (p != n) {
+               *target_end++ = *p++;
+           }
+           *target_end = '\0';
+
+       }
+
+       /* advance to next directory to check */
+       p = n;
+
+        /* skip '/' characters */
+        while (*p == '/') {
+           p++;
+       }
+    }
+
+    /* see if this is a directory we can change into */
+    dir_okay = 0;
+    if (stat(target, &stat_buf) == 0) {
+#ifndef STAT_MACROS_BROKEN
+        if (!S_ISDIR(stat_buf.st_mode)) {
+#else
+        if (S_ISDIR(stat_buf.st_mode)) {
+#endif
+           reply(f, 550,"Directory change failed; target is not a directory.");
+       } else { 
+           if (S_IXOTH & stat_buf.st_mode) {
+               dir_okay = 1;
+           } else if ((stat_buf.st_gid == getegid()) && 
+               (S_IXGRP & stat_buf.st_mode)) 
+           {
+               dir_okay = 1;
+           } else if ((stat_buf.st_uid == geteuid()) && 
+               (S_IXUSR & stat_buf.st_mode)) 
+           {
+               dir_okay = 1;
+           } else {
+               reply(f, 550, "Directory change failed; permission denied.");
+           }
+       }
+    } else {
+        reply(f, 550, "Directory change failed; directory does not exist.");
+    }
+
+    /* if everything is okay, change into the directory */
+    if (dir_okay) {
+       daemon_assert(strlen(target) < sizeof(f->dir));
+       /* send a readme unless we changed to our current directory */
+       if (strcmp(f->dir, target) != 0) {
+           strcpy(f->dir, target);
+            send_readme(f, 250);
+        } else {
+           strcpy(f->dir, target);
+       }
+        reply(f, 250, "Directory change successful.");
+    }
+
+    daemon_assert(invariant(f));
+}
+
+static void do_quit(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 0);
+
+    reply(f, 221, "Service closing control connection.");
+    f->session_active = 0;
+
+    daemon_assert(invariant(f));
+}
+
+/* support for the various port setting functions */
+static void set_port(ftp_session_t *f, const sockaddr_storage_t *host_port)
+{
+    daemon_assert(invariant(f));
+    daemon_assert(host_port != NULL);
+
+    if (f->epsv_all_set) {
+        reply(f, 500, "After EPSV ALL, only EPSV allowed.");
+    } else if (!ip_equal(&f->client_addr, host_port)) {
+        reply(f, 500, "Port must be on command channel IP.");
+    } else if (ntohs(SINPORT(host_port)) < IPPORT_RESERVED) {
+        reply(f, 500, "Port may not be less than 1024, which is reserved.");
+    } else {
+        /* close any outstanding PASSIVE port */
+        if (f->data_channel == DATA_PASSIVE) {
+            close(f->server_fd);
+           f->server_fd = -1;
+        }
+
+        f->data_channel = DATA_PORT;
+       f->data_port = *host_port;
+       reply(f, 200, "Command okay.");
+    }
+
+    daemon_assert(invariant(f));
+}
+
+/* set IP and port for client to receive data on */
+static void do_port(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    const sockaddr_storage_t *host_port;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    host_port = &cmd->arg[0].host_port;
+    daemon_assert(SSFAM(host_port) == AF_INET);
+
+    set_port(f, host_port);
+
+    daemon_assert(invariant(f));
+}
+
+/* set IP and port for client to receive data on, transport independent */
+static void do_lprt(ftp_session_t *f, const ftp_command_t *cmd)  
+{
+    const sockaddr_storage_t *host_port;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);                                   
+
+    host_port = &cmd->arg[0].host_port;
+
+#ifdef INET6
+    if ((SSFAM(host_port) != AF_INET) && (SSFAM(host_port) != AF_INET6)) {
+        reply(f, 521, "Only IPv4 and IPv6 supported, address families (4,6)");
+    }
+#else
+    if (SSFAM(host_port) != AF_INET) {
+        reply(f, 521, "Only IPv4 supported, address family (4)");
+    }
+#endif
+    else
+       set_port(f, host_port);
+
+    daemon_assert(invariant(f));
+}
+
+/* set IP and port for the client to receive data on, IPv6 extension */
+/*                                                                   */
+/* RFC 2428 specifies that if the data connection is going to be on  */
+/* the same IP as the control connection, EPSV must be used.  Since  */
+/* that is the only mode of transfer we support, we reject all EPRT  */
+/* requests.                                                         */
+static void do_eprt(ftp_session_t *f, const ftp_command_t *cmd)  
+{
+    const sockaddr_storage_t *host_port;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);                                   
+
+    reply(f, 500, "EPRT not supported, use EPSV.");
+
+    daemon_assert(invariant(f));
+}
+
+/* support for the various pasv setting functions */
+/* returns the file descriptor of the bound port, or -1 on error */
+/* note: the "host_port" parameter will be modified, having its port set */
+static int set_pasv(ftp_session_t *f, sockaddr_storage_t *bind_addr)
+{
+    int socket_fd;
+    int port;
+
+    daemon_assert(invariant(f));
+    daemon_assert(bind_addr != NULL);
+
+    socket_fd = socket(SSFAM(bind_addr), SOCK_STREAM, 0);
+    reopen_syslog_hack (socket_fd);
+    if (socket_fd == -1) {
+        reply(f, 500, "Error creating server socket; %s.", strerror(errno));
+       return -1;
+    } 
+
+    for (;;) {
+        port = get_passive_port();
+        SINPORT(bind_addr) = htons(port);
+       if (bind(socket_fd, (struct sockaddr *)bind_addr, 
+           sizeof(struct sockaddr)) == 0) 
+       {
+           break;
+       }
+       if (errno != EADDRINUSE) {
+            reply(f, 500, "Error binding server port; %s.", strerror(errno));
+            close(socket_fd);
+            return -1;
+       }
+    }
+
+    if (listen(socket_fd, 1) != 0) {
+        reply(f, 500, "Error listening on server port; %s.", strerror(errno));
+        close(socket_fd);
+       return -1;
+    }
+
+    return socket_fd;
+}
+
+/* pick a server port to listen for connection on */
+static void do_pasv(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    int socket_fd;
+    unsigned int addr;
+    int port;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 0);
+
+    if (f->epsv_all_set) {
+        reply(f, 500, "After EPSV ALL, only EPSV allowed.");
+        goto exit_pasv;
+    }
+
+    socket_fd = set_pasv(f, &f->server_ipv4_addr);
+    if (socket_fd == -1) {
+        goto exit_pasv;
+    }
+
+    /* report port to client */
+    addr = ntohl(f->server_ipv4_addr.sin_addr.s_addr);
+    port = ntohs(f->server_ipv4_addr.sin_port);
+    reply(f, 227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d).",
+        addr >> 24, 
+       (addr >> 16) & 0xff,
+       (addr >> 8)  & 0xff,
+       addr & 0xff,
+        port >> 8, 
+       port & 0xff);
+
+   /* close any outstanding PASSIVE port */
+   if (f->data_channel == DATA_PASSIVE) {
+       close(f->server_fd);
+   }
+   f->data_channel = DATA_PASSIVE;
+   f->server_fd = socket_fd;
+
+exit_pasv:
+    daemon_assert(invariant(f));
+}
+
+/* pick a server port to listen for connection on, including IPv6 */
+static void do_lpsv(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    int socket_fd;
+    char addr[80];
+    uint8_t *a;
+    uint8_t *p;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 0);
+
+    if (f->epsv_all_set) {
+        reply(f, 500, "After EPSV ALL, only EPSV allowed.");
+        goto exit_lpsv;
+    }
+
+    socket_fd = set_pasv(f, &f->server_addr);
+    if (socket_fd == -1) {
+        goto exit_lpsv;
+    }
+
+    /* report address and port to client */
+#ifdef INET6
+    if (SSFAM(&f->server_addr) == AF_INET6) {
+        a = (uint8_t *)&SIN6ADDR(&f->server_addr);
+        p = (uint8_t *)&SIN6PORT(&f->server_addr);
+       snprintf(addr, sizeof(addr),
+           "(6,16,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,2,%d,%d)",
+           a[0],  a[1],  a[2],  a[3],  a[4],  a[5],  a[6],  a[7],  a[8],
+           a[9], a[10], a[11], a[12], a[13], a[14], a[15],  p[0],  p[1]);
+    } else 
+#endif
+    {
+        a = (uint8_t *)&SIN4ADDR(&f->server_addr);
+        p = (uint8_t *)&SIN4PORT(&f->server_addr);
+       snprintf(addr, sizeof(addr), "(4,4,%d,%d,%d,%d,2,%d,%d)",
+           a[0], a[1], a[2], a[3], p[0], p[1]);    
+    }
+
+    reply(f, 228, "Entering Long Passive Mode %s", addr);
+
+   /* close any outstanding PASSIVE port */
+   if (f->data_channel == DATA_PASSIVE) {
+       close(f->server_fd);
+   }
+   f->data_channel = DATA_PASSIVE;
+   f->server_fd = socket_fd;
+
+exit_lpsv:
+    daemon_assert(invariant(f));
+}
+
+/* pick a server port to listen for connection on, new IPv6 method */
+static void do_epsv(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    int socket_fd;
+    sockaddr_storage_t *addr;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
+
+    /* check our argument, if any,  and use the appropriate address */
+    if (cmd->num_arg == 0) {
+        addr = &f->server_addr;
+    } else {
+        switch (cmd->arg[0].num) {
+           /* EPSV_ALL is a special number indicating the client sent */
+           /* the command "EPSV ALL" - this is not a request to assign */
+           /* a new passive port, but rather to deny all future port */
+           /* assignment requests other than EPSV */
+           case EPSV_ALL:
+                f->epsv_all_set = 1;
+               reply(f, 200, "EPSV ALL command successful.");
+               goto exit_epsv;
+           case 1:
+               addr = (sockaddr_storage_t *)&f->server_ipv4_addr;
+               break;
+#ifdef INET6
+           case 2:
+               addr = &f->server_addr;
+               break;
+           default:
+               reply(f, 522, "Only IPv4 and IPv6 supported, use (1,2)");
+               goto exit_epsv;
+#else
+           default:
+               reply(f, 522, "Only IPv4 supported, use (1)");
+               goto exit_epsv;
+#endif
+        }
+    }
+
+    /* bind port and so on */
+    socket_fd = set_pasv(f, addr);
+    if (socket_fd == -1) {
+        goto exit_epsv;
+    }
+
+    /* report port to client */
+    reply(f, 229, "Entering Extended Passive Mode (|||%d|)", 
+        ntohs(SINPORT(&f->server_addr)));
+
+    /* close any outstanding PASSIVE port */
+    if (f->data_channel == DATA_PASSIVE) {
+        close(f->server_fd);
+    }
+    f->data_channel = DATA_PASSIVE;
+    f->server_fd = socket_fd;  
+
+exit_epsv:
+    daemon_assert(invariant(f));
+}
+
+/* seed the random number generator used to pick a port */
+static void init_passive_port()
+{
+    struct timeval tv;
+    unsigned short int seed[3];
+
+    gettimeofday(&tv, NULL);
+    seed[0] = (tv.tv_sec >> 16) & 0xFFFF;
+    seed[1] = tv.tv_sec & 0xFFFF;
+    seed[2] = tv.tv_usec & 0xFFFF;
+    seed48(seed);
+}
+
+/* pick a port to try to bind() for passive FTP connections */
+static int get_passive_port()
+{
+    static pthread_once_t once_control = PTHREAD_ONCE_INIT;
+    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+    int port;
+
+    /* initialize the random number generator the first time we're called */
+    pthread_once(&once_control, init_passive_port);
+
+    /* pick a random port between 1024 (pasv_port_low) and 65535
+       (pasv_port_high), inclusive */
+    pthread_mutex_lock(&mutex);
+    port = (lrand48() % (pasv_port_high-pasv_port_low+1)) + pasv_port_low;
+    pthread_mutex_unlock(&mutex);
+
+    daemon_assert (port >= 1024);
+
+    return port;
+}
+
+static void do_type(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    char type;
+    char form;
+    int cmd_okay;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg >= 1);
+    daemon_assert(cmd->num_arg <= 2);
+
+    type = cmd->arg[0].string[0];
+    if (cmd->num_arg == 2) {
+        form = cmd->arg[1].string[0];
+    } else {
+        form = 0;
+    }
+
+    cmd_okay = 0;
+    if (type == 'A') {
+        if ((cmd->num_arg == 1) || ((cmd->num_arg == 2) && (form == 'N'))) {
+            f->data_type = TYPE_ASCII;
+           cmd_okay = 1;
+       }
+    } else if (type == 'I') {
+        f->data_type = TYPE_IMAGE;
+       cmd_okay = 1;
+    }
+
+    if (cmd_okay) {
+        reply(f, 200, "Command okay.");
+    } else { 
+        reply(f, 504, "Command not implemented for that parameter.");
+    }
+
+    daemon_assert(invariant(f));
+}
+
+static void do_stru(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    char structure;
+    int cmd_okay;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    structure = cmd->arg[0].string[0];
+    cmd_okay = 0;
+    if (structure == 'F') {
+        f->file_structure = STRU_FILE;
+        cmd_okay = 1;
+    } else if (structure == 'R') {
+        f->file_structure = STRU_RECORD;
+        cmd_okay = 1;
+    }
+
+    if (cmd_okay) {
+        reply(f, 200, "Command okay.");
+    } else {
+        reply(f, 504, "Command not implemented for that parameter.");
+    }
+
+    daemon_assert(invariant(f));
+}
+
+static void do_mode(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    char mode;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    mode = cmd->arg[0].string[0];
+    if (mode == 'S') {
+        reply(f, 200, "Command okay.");
+    } else {
+        reply(f, 504, "Command not implemented for that parameter.");
+    }
+
+    daemon_assert(invariant(f));
+}
+
+/* convert the user-entered file name into a full path on our local drive */
+static void get_absolute_fname(char *fname, 
+                               int fname_len,
+                               const char *dir,
+                               const char *file)
+{
+    daemon_assert(fname != NULL);
+    daemon_assert(dir != NULL);
+    daemon_assert(file != NULL);
+
+    if (*file == '/') {
+
+        /* absolute path, use as input */
+        daemon_assert(strlen(file) < fname_len);
+       strcpy(fname, file);
+
+    } else {
+
+        /* construct a file name based on our current directory */
+        daemon_assert(strlen(dir) + 1 + strlen(file) < fname_len);
+        strcpy(fname, dir);
+
+       /* add a seperating '/' if we're not at the root */
+       if (fname[1] != '\0') {
+            strcat(fname, "/");
+       }
+
+        /* and of course the actual file name */
+        strcat(fname, file);
+
+    }
+}
+
+static void do_retr(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    const char *file_name;
+    char full_path[PATH_MAX+1+MAX_STRING_LEN];
+    int file_fd;
+    struct stat stat_buf;
+    int socket_fd;
+    int read_ret;
+    char buf[4096];
+    char converted_buf[8192];
+    int converted_buflen;
+    char addr_port[ADDRPORT_STRLEN];
+    struct timeval start_timestamp;
+    struct timeval end_timestamp;
+    struct timeval transfer_time;
+    off_t file_size;
+    off_t offset;
+    off_t amt_to_send;
+    int sendfile_ret;
+    off_t amt_sent;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    /* set up for exit */
+    file_fd = -1;
+    socket_fd = -1;
+
+    /* create an absolute name for our file */
+    file_name = cmd->arg[0].string;
+    get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
+
+    /* open file */
+    file_fd = open(full_path, O_RDONLY);
+    reopen_syslog_hack (file_fd);
+    if (file_fd == -1) {
+        reply(f, 550, "Error opening file; %s.", strerror(errno));
+       goto exit_retr;
+    }
+    if (fstat(file_fd, &stat_buf) != 0) {
+        reply(f, 550, "Error getting file information; %s.", strerror(errno));
+       goto exit_retr;
+    }
+#ifndef STATS_MACRO_BROKEN
+    if (S_ISDIR(stat_buf.st_mode)) {
+#else
+    if (!S_ISDIR(stat_buf.st_mode)) {
+#endif
+        reply(f, 550, "Error, file is a directory.");
+       goto exit_retr;
+    }
+
+    /* if the last command was a REST command, restart at the */
+    /* requested position in the file                         */
+    if ((f->file_offset_command_number == (f->command_number - 1)) && 
+        (f->file_offset > 0))
+    {
+       if (lseek(file_fd, f->file_offset, SEEK_SET) == -1) {
+           reply(f, 550, "Error seeking to restart position; %s.", 
+               strerror(errno));
+            goto exit_retr;
+       }
+    }
+
+    /* ready to transfer */
+    reply(f, 150, "About to open data connection.");
+
+    /* mark start time */
+    gettimeofday(&start_timestamp, NULL);
+
+    /* open data path */
+    socket_fd = open_connection(f);
+    if (socket_fd == -1) {
+       goto exit_retr;
+    }
+
+    /* we're golden, send the file */
+    file_size = 0;
+    if (f->data_type == TYPE_ASCII) {
+        for (;;) {
+            read_ret = read(file_fd, buf, sizeof(buf));
+           if (read_ret == -1) {
+               reply(f, 550, "Error reading from file; %s.", strerror(errno));
+               goto exit_retr;
+           }
+           if (read_ret == 0) {
+               break;
+           }
+           converted_buflen = convert_newlines(converted_buf, buf, read_ret);
+
+            if (write_fully(socket_fd, converted_buf, converted_buflen) == -1) {
+                reply(f, 550, "Error writing to data connection; %s.", 
+                 strerror(errno));
+                goto exit_retr;
+            }
+
+            file_size += converted_buflen;
+       } 
+    } else {
+        daemon_assert(f->data_type == TYPE_IMAGE);
+        
+        /* for sendfile(), we still have to use a loop to avoid 
+           having our watchdog time us out on large files - it does
+           allow us to avoid an extra copy to/from user space */
+#ifdef HAVE_SENDFILE
+        offset = f->file_offset;
+        file_size = stat_buf.st_size - offset;
+        while (offset < stat_buf.st_size) {
+
+            amt_to_send = stat_buf.st_size - offset;
+            if (amt_to_send > 65536) {
+                amt_to_send = 65536;
+            }
+#ifdef HAVE_LINUX_SENDFILE
+            sendfile_ret = sendfile(socket_fd, 
+                                    file_fd, 
+                                    &offset, 
+                                    amt_to_send);
+            if (sendfile_ret != amt_to_send) {
+                reply(f, 550, "Error sending file; %s.", strerror(errno));
+                goto exit_retr;
+            }
+#elif HAVE_FREEBSD_SENDFILE
+            sendfile_ret = sendfile(file_fd, 
+                                    socket_fd, 
+                                    offset,
+                                    amt_to_send,
+                                    NULL,
+                                    &amt_sent,
+                                    0);
+            if (sendfile_ret != 0) {
+                reply(f, 550, "Error sending file; %s.", strerror(errno));
+                goto exit_retr;
+            }
+            offset += amt_sent;
+#endif
+
+           watchdog_defer_watched(f->watched);
+        }
+#else
+        for (;;) {
+            read_ret = read(file_fd, buf, sizeof(buf));
+           if (read_ret == -1) {
+               reply(f, 550, "Error reading from file; %s.", strerror(errno));
+               goto exit_retr;
+           }
+           if (read_ret == 0) {
+               break;
+           }
+           if (write_fully(socket_fd, buf, read_ret) == -1) {
+               reply(f, 550, "Error writing to data connection; %s.", 
+                 strerror(errno));
+               goto exit_retr;
+           }
+            file_size += read_ret;
+
+           watchdog_defer_watched(f->watched);
+       }
+#endif  /* HAVE_SENDFILE */
+    }
+
+    /* disconnect */
+    close(socket_fd);
+    socket_fd = -1;
+
+    /* hey, it worked, let the other side know */
+    reply(f, 226, "File transfer complete.");
+
+    /* mark end time */
+    gettimeofday(&end_timestamp, NULL);
+
+    /* calculate transfer rate */
+    transfer_time.tv_sec = end_timestamp.tv_sec - start_timestamp.tv_sec;
+    transfer_time.tv_usec = end_timestamp.tv_usec - start_timestamp.tv_usec;
+    while (transfer_time.tv_usec >= 1000000) {
+        transfer_time.tv_sec++;
+        transfer_time.tv_usec -= 1000000;
+    }
+    while (transfer_time.tv_usec < 0) {
+        transfer_time.tv_sec--;
+        transfer_time.tv_usec += 1000000;
+    }
+
+    /* note the transfer */
+    syslog(LOG_INFO, 
+      "%s retrieved \"%s\", %ld bytes in %d.%06d seconds", 
+      f->client_addr_str, 
+      full_path,
+      file_size,
+      transfer_time.tv_sec,
+      transfer_time.tv_usec);
+
+exit_retr:
+    f->file_offset = 0;
+    if (socket_fd != -1) {
+        close(socket_fd);
+    }
+    if (file_fd != -1) {
+        close(file_fd);
+    }
+    daemon_assert(invariant(f));
+}
+
+static void do_stor(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    reply(f, 553, "Server will not store files.");
+
+    daemon_assert(invariant(f));
+}
+
+static int open_connection(ftp_session_t *f)
+{
+    int socket_fd;
+    struct sockaddr_in addr;
+    unsigned addr_len;
+
+    daemon_assert((f->data_channel == DATA_PORT) || 
+                  (f->data_channel == DATA_PASSIVE));
+
+    if (f->data_channel == DATA_PORT) {
+        socket_fd = socket(SSFAM(&f->data_port), SOCK_STREAM, 0);
+        reopen_syslog_hack (socket_fd);
+       if (socket_fd == -1) {
+           reply(f, 425, "Error creating socket; %s.", strerror(errno));
+           return -1;
+       }
+       if (connect(socket_fd, (struct sockaddr *)&f->data_port, 
+           sizeof(sockaddr_storage_t)) != 0)
+       {
+           reply(f, 425, "Error connecting; %s.", strerror(errno));
+           close(socket_fd);
+           return -1;
+       }
+    } else {
+        daemon_assert(f->data_channel == DATA_PASSIVE);
+
+        addr_len = sizeof(struct sockaddr_in);
+        socket_fd = accept(f->server_fd, (struct sockaddr *)&addr, &addr_len);
+        reopen_syslog_hack (socket_fd);
+       if (socket_fd == -1) {
+           reply(f, 425, "Error accepting connection; %s.", strerror(errno));
+           return -1;
+       }
+#ifdef INET6
+        /* in IPv6, the client can connect to a channel using a different */
+       /* protocol - in that case, we'll just blindly let the connection */
+       /* through, otherwise verify addresses match */
+        if (SAFAM(addr) == SSFAM(&f->client_addr)) {
+           if (memcmp(&SINADDR(&f->client_addr), &SINADDR(&addr), 
+                      sizeof(SINADDR(&addr))))
+           {
+               reply(f, 425, 
+                 "Error accepting connection; connection from invalid IP.");
+               close(socket_fd);
+               return -1;
+            }
+       }
+#else
+       if (memcmp(&f->client_addr.sin_addr, 
+           &addr.sin_addr, sizeof(struct in_addr))) 
+       {
+           reply(f, 425, 
+             "Error accepting connection; connection from invalid IP.");
+           close(socket_fd);
+           return -1;
+       }
+#endif
+    }
+
+    return socket_fd;
+}
+
+/* convert any '\n' to '\r\n' */
+/* destination should be twice the size of the source for safety */
+static int convert_newlines(char *dst, const char *src, int srclen)
+{
+    int i;
+    int dstlen;
+
+    daemon_assert(dst != NULL);
+    daemon_assert(src != NULL);
+
+    dstlen = 0;
+    for (i=0; i<srclen; i++) {
+        if (src[i] == '\n') {
+           dst[dstlen++] = '\r';
+       }
+       dst[dstlen++] = src[i];
+    }
+    return dstlen;
+}
+
+static int write_fully(int fd, const char *buf, int buflen)
+{
+    int amt_written;
+    int write_ret;
+
+    amt_written = 0;
+    while (amt_written < buflen) {
+        write_ret = write(fd, buf+amt_written, buflen-amt_written);
+       if (write_ret <= 0) {
+           return -1;
+       }
+       amt_written += write_ret;
+    }
+    return amt_written;
+}
+
+static void do_pwd(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 0);
+
+    reply(f, 257, "\"%s\" is current directory.", f->dir);
+
+    daemon_assert(invariant(f));
+}
+
+#if 0
+/* 
+  because oftpd uses glob(), it is possible for users to launch a 
+  denial-of-service attack by sending certain wildcard expressions that
+  create extremely large lists of files, e.g. "*/../*/../*/../*/../*/../*"
+
+  in order to prevent this, a user may pass wildcards, or paths, but not
+  both as arguments to LIST or NLST - at most all the files from a single 
+  directory will be returned
+*/
+#endif
+
+/* check if a filespec has a wildcard in it */
+static int filespec_has_wildcard(const char *filespec)
+{
+    daemon_assert(filespec != NULL);
+
+    /* check each character for wildcard */
+    while (*filespec != '\0') {
+        switch (*filespec) {
+           /* wildcards */
+           case '*':
+           case '?':
+           case '[':
+               return 1;
+
+            /* backslash escapes next character unless at end of string */
+            case '\\':
+               if (*(filespec+1) != '\0') {
+                   filespec++;
+               }
+               break;
+       }
+       filespec++;
+    }
+
+    return 0;
+}
+
+/* filespec includes path separator, i.e. '/' */
+static int filespec_has_path_separator(const char *filespec)
+{
+    daemon_assert(filespec != NULL);
+
+    /* check each character for path separator */
+    if (strchr(filespec, '/') != NULL) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+/* returns whether filespec is legal or not */
+static int filespec_is_legal(const char *filespec)
+{
+    daemon_assert(filespec != NULL);
+
+    if (filespec_has_wildcard(filespec)) {
+        if (filespec_has_path_separator(filespec)) {
+           return 0;
+       }
+    }
+    return 1;
+}
+
+static void do_nlst(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    int fd;
+    const char *param;
+    int send_ok;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
+
+    /* set up for exit */
+    fd = -1;
+
+    /* figure out what parameters to use */
+    if (cmd->num_arg == 0) {
+        param = "*";
+    } else {
+        daemon_assert(cmd->num_arg == 1);
+
+       /* ignore attempts to send options to "ls" by silently dropping */
+       if (cmd->arg[0].string[0] == '-') {
+            param = "*";
+       } else {
+            param = cmd->arg[0].string;
+       }
+    }
+
+    /* check spec passed */
+    if (!filespec_is_legal(param)) {
+        reply(f, 550, "Illegal filename passed.");
+       goto exit_nlst;
+    }
+
+    /* ready to list */
+    reply(f, 150, "About to send name list.");
+
+    /* open our data connection */
+    fd = open_connection(f);
+    if (fd == -1) {
+        goto exit_nlst;
+    }
+
+    /* send any files */
+    send_ok = file_nlst(fd, f->dir, param);
+
+    /* strange handshake for Netscape's benefit */
+    netscape_hack(fd);
+
+    if (send_ok) {
+        reply(f, 226, "Transfer complete.");
+    } else {
+        reply(f, 451, "Error sending name list.");
+    }
+
+    /* clean up and exit */
+exit_nlst:
+    if (fd != -1) {
+        close(fd);
+    }
+    daemon_assert(invariant(f));
+}
+
+static void do_list(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    int fd;
+    const char *param;
+    int send_ok;
+
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
+
+    /* set up for exit */
+    fd = -1;
+
+    /* figure out what parameters to use */
+    if (cmd->num_arg == 0) {
+        param = "*";
+    } else {
+        daemon_assert(cmd->num_arg == 1);
+
+       /* ignore attempts to send options to "ls" by silently dropping */
+       if (cmd->arg[0].string[0] == '-') {
+            param = "*";
+       } else {
+            param = cmd->arg[0].string;
+       }
+    }
+
+    /* check spec passed */
+    if (!filespec_is_legal(param)) {
+        reply(f, 550, "Illegal filename passed.");
+       goto exit_list;
+    }
+
+    /* ready to list */
+    reply(f, 150, "About to send file list.");
+
+    /* open our data connection */
+    fd = open_connection(f);
+    if (fd == -1) {
+        goto exit_list;
+    }
+
+    /* send any files */
+    send_ok = file_list(fd, f->dir, param);
+
+    /* strange handshake for Netscape's benefit */
+    netscape_hack(fd);
+
+    if (send_ok) {
+        reply(f, 226, "Transfer complete.");
+    } else {
+        reply(f, 451, "Error sending file list.");
+    }
+
+    /* clean up and exit */
+exit_list:
+    if (fd != -1) {
+        close(fd);
+    }
+    daemon_assert(invariant(f));
+}
+
+static void do_syst(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 0);
+
+    reply(f, 215, "UNIX");
+
+    daemon_assert(invariant(f));
+}
+
+
+static void do_noop(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 0);
+
+    reply(f, 200, "Command okay.");
+
+    daemon_assert(invariant(f));
+}
+
+static void do_rest(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    if (f->data_type != TYPE_IMAGE) {
+        reply(f, 555, "Restart not possible in ASCII mode.");
+    } else if (f->file_structure != STRU_FILE) {
+        reply(f, 555, "Restart only possible with FILE structure.");
+    } else {
+        f->file_offset = cmd->arg[0].offset;
+        f->file_offset_command_number = f->command_number;
+        reply(f, 350, "Restart okay, awaiting file retrieval request.");
+    }
+
+    daemon_assert(invariant(f));
+}
+
+static void do_size(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    const char *file_name;
+    char full_path[PATH_MAX+1+MAX_STRING_LEN];
+    struct stat stat_buf;
+    
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    if (f->data_type != TYPE_IMAGE) {
+        reply(f, 550, "Size cannot be determined in ASCII mode.");
+    } else if (f->file_structure != STRU_FILE) {
+        reply(f, 550, "Size cannot be determined with FILE structure.");
+    } else {
+
+        /* create an absolute name for our file */
+        file_name = cmd->arg[0].string;
+        get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
+
+        /* get the file information */
+        if (stat(full_path, &stat_buf) != 0) {
+            reply(f, 550, "Error getting file status; %s.", strerror(errno));
+        } else {
+            /* output the size */
+            if (sizeof(off_t) == 8) {
+                reply(f, 213, "%llu", stat_buf.st_size);
+            } else {
+                reply(f, 213, "%lu", stat_buf.st_size);
+            } 
+        }
+
+    }
+
+    daemon_assert(invariant(f));
+}
+
+/* if no gmtime_r() is available, provide one */
+#ifndef HAVE_GMTIME_R
+struct tm *gmtime_r(const time_t *timep, struct tm *timeptr) 
+{
+    static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
+
+    pthread_mutex_lock(&time_lock);
+    *timeptr = *(gmtime(timep));
+    pthread_mutex_unlock(&time_lock);
+    return timeptr;
+}
+#endif /* HAVE_GMTIME_R */
+
+static void do_mdtm(ftp_session_t *f, const ftp_command_t *cmd) 
+{
+    const char *file_name;
+    char full_path[PATH_MAX+1+MAX_STRING_LEN];
+    struct stat stat_buf;
+    struct tm mtime;
+    char time_buf[16];
+    
+    daemon_assert(invariant(f));
+    daemon_assert(cmd != NULL);
+    daemon_assert(cmd->num_arg == 1);
+
+    /* create an absolute name for our file */
+    file_name = cmd->arg[0].string;
+    get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
+
+    /* get the file information */
+    if (stat(full_path, &stat_buf) != 0) {
+        reply(f, 550, "Error getting file status; %s.", strerror(errno));
+    } else {
+        gmtime_r(&stat_buf.st_mtime, &mtime);
+        strftime(time_buf, sizeof(time_buf), "%Y%m%d%H%M%S", &mtime);
+        reply(f, 213, time_buf);
+    }
+
+    daemon_assert(invariant(f));
+}
+
+
+static void send_readme(const ftp_session_t *f, int code)
+{
+    char file_name[PATH_MAX+1];
+    int dir_len;
+    struct stat stat_buf;
+    int fd;
+    int read_ret;
+    char buf[4096];
+    char code_str[8];
+    char *p;
+    int len;
+    char *nl;
+    int line_len;
+
+    daemon_assert(invariant(f));
+    daemon_assert(code >= 100);
+    daemon_assert(code <= 559);
+
+    /* set up for early exit */
+    fd = -1;
+
+    /* verify our README wouldn't be too long */
+    dir_len = strlen(f->dir);
+    if ((dir_len + 1 + sizeof(README_FILE_NAME)) > sizeof(file_name)) {
+        goto exit_send_readme;
+    }
+
+    /* create a README file name */
+    strcpy(file_name, f->dir);
+    strcat(file_name, "/");
+    strcat(file_name, README_FILE_NAME);
+
+    /* open our file */
+    fd = open(file_name, O_RDONLY);
+    reopen_syslog_hack (fd);
+    if (fd == -1) {
+        goto exit_send_readme;
+    }
+
+    /* verify this isn't a directory */
+    if (fstat(fd, &stat_buf) != 0) {
+        goto exit_send_readme;
+    }
+#ifndef STATS_MACRO_BROKEN
+    if (S_ISDIR(stat_buf.st_mode)) {
+#else
+    if (!S_ISDIR(stat_buf.st_mode)) {
+#endif
+       goto exit_send_readme;
+    }
+
+    /* convert our code to a buffer */
+    daemon_assert(code >= 100);
+    daemon_assert(code <= 999);
+    sprintf(code_str, "%03d-", code);
+
+    /* read and send */
+    read_ret = read(fd, buf, sizeof(buf));
+    if (read_ret > 0) {
+        telnet_session_print(f->telnet_session, code_str);
+        while (read_ret > 0) {
+            p = buf;
+           len = read_ret;
+            nl = memchr(p, '\n', len);
+           while ((len > 0) && (nl != NULL)) {
+               *nl = '\0';
+               telnet_session_println(f->telnet_session, p);
+               line_len = nl - p;
+               len -= line_len + 1;
+               if (len > 0) {
+                   telnet_session_print(f->telnet_session, code_str);
+                }
+               p = nl+1;
+                nl = memchr(p, '\n', len);
+           }
+           if (len > 0) {
+               telnet_session_print(f->telnet_session, p);
+           }
+
+            read_ret = read(fd, buf, sizeof(buf));
+        }
+    }
+
+    /* cleanup and exit */
+exit_send_readme:
+    if (fd != -1) {
+        close(fd);
+    }
+    daemon_assert(invariant(f));
+}
+
+/* hack which prevents Netscape error in file list */
+static void netscape_hack(int fd)
+{
+    fd_set readfds;
+    struct timeval timeout;
+    int select_ret;
+    char c;
+
+    daemon_assert(fd >= 0);
+
+    shutdown(fd, 1);
+    FD_ZERO(&readfds);
+    FD_SET(fd, &readfds);
+    timeout.tv_sec = 15;
+    timeout.tv_usec = 0;
+    select_ret = select(fd+1, &readfds, NULL, NULL, &timeout);
+    if (select_ret > 0) {
+        read(fd, &c, 1);
+    }
+}
+
+/* compare two addresses to see if they contain the same IP address */
+static int ip_equal(const sockaddr_storage_t *a, const sockaddr_storage_t *b)
+{
+    daemon_assert(a != NULL);
+    daemon_assert(b != NULL);
+    daemon_assert((SSFAM(a) == AF_INET) || (SSFAM(a) == AF_INET6));
+    daemon_assert((SSFAM(b) == AF_INET) || (SSFAM(b) == AF_INET6));
+
+    if (SSFAM(a) != SSFAM(b)) {
+        return 0;
+    }
+    if (memcmp(&SINADDR(a), &SINADDR(b), sizeof(SINADDR(a))) != 0) {
+        return 0;
+    }
+    return 1;
+}
+
diff --git a/src/ftp_session.h b/src/ftp_session.h
new file mode 100644 (file)
index 0000000..0246c03
--- /dev/null
@@ -0,0 +1,97 @@
+/* 
+ * $Id$
+ *
+ * Restrictions:
+ *  - Only stream MODE is supported.
+ *  - Only "ftp" or "anonymous" accepted as a user.
+ */
+
+#ifndef FTP_SESSION_H
+#define FTP_SESSION_H
+
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <limits.h>
+#include "af_portability.h"
+#include "watchdog.h"
+#include "error.h"
+
+/* data representation types supported */
+#define TYPE_ASCII  0
+#define TYPE_IMAGE  1
+
+/* file structure types supported */
+#define STRU_FILE   0
+#define STRU_RECORD 1
+
+/* data path chosen */
+#define DATA_PORT     0
+#define DATA_PASSIVE  1
+
+/* space required for text representation of address and port, 
+   e.g. "192.168.0.1 port 1024" or 
+        "2001:3333:DEAD:BEEF:0666:0013:0069:0042 port 65535" */
+#define ADDRPORT_STRLEN 58
+
+/* structure encapsulating an FTP session's information */
+typedef struct {
+    /* flag whether session is active */
+    int session_active;
+  
+    /* incremented for each command */
+    unsigned long command_number;
+
+    /* options about transfer set by user */
+    int data_type;
+    int file_structure;
+
+    /* offset to begin sending file from */
+    off_t file_offset;
+    unsigned long file_offset_command_number;
+
+    /* flag set if client requests ESPV ALL - this prevents subsequent 
+       use of PORT, PASV, LPRT, LPSV, or EPRT */
+    int epsv_all_set;
+
+    /* address of client */
+    sockaddr_storage_t client_addr;
+    char client_addr_str[ADDRPORT_STRLEN];
+
+    /* address of server (including IPv4 version) */
+    sockaddr_storage_t server_addr;
+    struct sockaddr_in server_ipv4_addr;
+
+    /* telnet session to encapsulate control channel logic */
+    telnet_session_t *telnet_session;
+
+    /* current working directory of this connection */
+    char dir[PATH_MAX+1];
+
+    /* data channel information, including type, 
+      and client address or server port depending on type */
+    int data_channel;
+    sockaddr_storage_t data_port;
+    int server_fd;
+
+    /* watchdog to handle timeout */
+    watched_t *watched;
+} ftp_session_t;
+
+
+/* Range of passive ports to use.  Defined by oftpd.c */
+extern int pasv_port_low;
+extern int pasv_port_high;
+
+
+int ftp_session_init(ftp_session_t *f, 
+                     const sockaddr_storage_t *client_addr, 
+                     const sockaddr_storage_t *server_addr, 
+                     telnet_session_t *t,
+                     const char *dir,
+                     error_t *err);
+void ftp_session_drop(ftp_session_t *f, const char *reason);
+void ftp_session_run(ftp_session_t *f, watched_t *watched);
+void ftp_session_destroy(ftp_session_t *f);
+
+#endif /* FTP_SESSION_H */
+
diff --git a/src/oftpd.c b/src/oftpd.c
new file mode 100644 (file)
index 0000000..f00e43e
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ * $Id$
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <pwd.h>
+#include <syslog.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "oftpd.h"
+#include "ftp_listener.h"
+#include "error.h"
+
+/* put our executable name here where everybody can see it */
+static const char *exe_name = "oftpd";
+static int log_facility;
+
+int pasv_port_low = 1024;
+int pasv_port_high = MAX_PORT;
+
+/* This is used by the reopen_syslog_hack. */
+static int my_syslog_fd = -1;
+
+static void daemonize();
+static void print_usage(const char *error);
+static void init_syslog_hack (void);
+
+int main(int argc, char *argv[])
+{
+    int i;
+    
+    long num;
+    char *endptr;
+    int port;
+    int max_clients;
+
+    char *user_ptr;
+    char *dir_ptr;
+    char *address;
+    
+    char temp_buf[256];
+
+    struct passwd *user_info;
+    error_t err;
+
+    ftp_listener_t ftp_listener;
+
+    int detach;
+
+    sigset_t term_signal;
+    int sig;
+
+    /* grab our executable name */
+    if (argc > 0) {
+        exe_name = argv[0];
+    }
+
+    /* verify we're running as root */
+    if (geteuid() != 0) {
+        fprintf(stderr, "%s: program needs root permission to run\n", exe_name);
+        exit(1);
+    }
+
+    /* default command-line arguments */
+    port = FTP_PORT;
+    user_ptr = NULL;
+    dir_ptr = NULL;
+    address = FTP_ADDRESS;
+    max_clients = MAX_CLIENTS;
+    detach = 1;
+    log_facility = LOG_FTP;
+
+    /* check our command-line arguments */
+    /* we're stubbornly refusing to use getopt(), because we can */
+    /* :) */ 
+    for (i=1; i<argc; i++) {
+        
+        /* flags/optional arguments */
+        if (argv[i][0] == '-') {
+            if (strcmp(argv[i], "-p") == 0 
+                || strcmp(argv[i], "--port") == 0) {
+                if (++i >= argc) {
+                    print_usage("missing port number");
+                    exit(1);
+                }
+                num = strtol(argv[i], &endptr, 0);
+                if ((num < MIN_PORT) || (num > MAX_PORT) || (*endptr != '\0')) {
+
+                    snprintf(temp_buf, sizeof(temp_buf), 
+                             "port must be a number between %d and %d",
+                             MIN_PORT, MAX_PORT);
+                    print_usage(temp_buf);
+
+                    exit(1);
+                }
+                port = num;
+            }
+            else if (strcmp(argv[i], "-r") == 0 
+                     || strcmp(argv[i], "--pasv-range") == 0) {
+                i += 2;  
+                if (i >= argc) {
+                    print_usage("invalid passive port range");
+                    exit(1);
+                }
+                num = strtol(argv[i-1], &endptr, 0);
+                if ((num < 1024) || (num > MAX_PORT) || (*endptr != '\0')) {
+
+                    snprintf(temp_buf, sizeof(temp_buf), 
+                             "low passive port must be a number between 1024 and %d",
+                             MAX_PORT);
+                    print_usage(temp_buf);
+
+                    exit(1);
+                }
+                pasv_port_low = num;
+                num = strtol(argv[i], &endptr, 0);
+                if ((num < pasv_port_low) || (num > MAX_PORT) || (*endptr != '\0')) {
+
+                    snprintf(temp_buf, sizeof(temp_buf), 
+                             "high passive port must be a number between %d and %d",
+                             pasv_port_low, MAX_PORT);
+                    print_usage(temp_buf);
+
+                    exit(1);
+                }
+                pasv_port_high = num;
+            } else if (strcmp(argv[i], "-h") == 0
+                       || strcmp(argv[i], "--help") == 0) {
+                print_usage(NULL);
+                exit(0);
+            } else if (strcmp(argv[i], "-i") == 0
+                       || strcmp(argv[i], "--interface") == 0) {
+                if (++i >= argc) {
+                    print_usage("missing interface");
+                    exit(1);
+                }
+                address = argv[i];
+            } else if (strcmp(argv[i], "-m") == 0
+                       || strcmp(argv[i], "--max-clients") == 0) {
+                if (++i >= argc) {
+                    print_usage("missing number of max clients");
+                    exit(1);
+                }
+                num = strtol(argv[i], &endptr, 0);
+                if ((num < MIN_NUM_CLIENTS) || (num > MAX_NUM_CLIENTS) 
+                    || (*endptr != '\0')) {
+
+                    snprintf(temp_buf, sizeof(temp_buf),
+                        "max clients must be a number between %d and %d",
+                        MIN_NUM_CLIENTS, MAX_NUM_CLIENTS);
+                    print_usage(temp_buf);
+
+                    exit(1);
+                }
+                max_clients = num;
+            } else if (strcmp(argv[i], "-N") == 0 
+                       || strcmp(argv[i], "--nodetach") == 0) {
+                detach = 0;
+            } else if (strcmp(argv[i], "-l") == 0
+                       || strcmp(argv[i], "--local") == 0) {
+                if (++i >= argc) {
+                    print_usage("missing number for local facility logging");
+                    exit(1);
+                }
+                switch (argv[i][0]) {
+                    case '0': 
+                        log_facility = LOG_LOCAL0;
+                        break;
+                    case '1': 
+                        log_facility = LOG_LOCAL1;
+                        break;
+                    case '2': 
+                        log_facility = LOG_LOCAL2;
+                        break;
+                    case '3': 
+                        log_facility = LOG_LOCAL3;
+                        break;
+                    case '4': 
+                        log_facility = LOG_LOCAL4;
+                        break;
+                    case '5': 
+                        log_facility = LOG_LOCAL5;
+                        break;
+                    case '6': 
+                        log_facility = LOG_LOCAL6;
+                        break;
+                    case '7': 
+                        log_facility = LOG_LOCAL7;
+                        break;
+                }
+            } else {
+                print_usage("unknown option");
+                exit(1);
+            }
+
+        /* required parameters */
+        } else {
+            if (user_ptr == NULL) {
+                user_ptr = argv[i];
+            } else if (dir_ptr == NULL) {
+                dir_ptr = argv[i];
+            } else {
+                print_usage("too many arguments on the command line");
+                exit(1);
+            }
+        }
+    }
+    if ((user_ptr == NULL) || (dir_ptr == NULL)) {
+        print_usage("missing user and/or directory name");
+        exit(1);
+    }
+
+    user_info = getpwnam(user_ptr);
+    if (user_info == NULL) {
+        fprintf(stderr, "%s: invalid user name\n", exe_name);
+        exit(1);
+    }
+
+    /* become a daemon */
+    if (detach) {
+        daemonize();
+    }
+
+    /* avoid SIGPIPE on socket activity */
+    signal(SIGPIPE, SIG_IGN);         
+
+    /* log the start time */
+    openlog(NULL, LOG_NDELAY, log_facility);
+    syslog(LOG_INFO,"Starting, version %s, as PID %d", VERSION, getpid());
+    init_syslog_hack ();
+
+    /* change to root directory */
+    if (chroot(dir_ptr) != 0) {
+        syslog(LOG_ERR, "error with root directory; %s\n", exe_name, 
+          strerror(errno));
+        exit(1);
+    }
+    if (chdir("/") != 0) {
+        syslog(LOG_ERR, "error changing directory; %s\n", strerror(errno));
+        exit(1);
+    }
+
+    /* create our main listener */
+    if (!ftp_listener_init(&ftp_listener, 
+                           address,
+                           port,
+                           max_clients,
+                           INACTIVITY_TIMEOUT, 
+                           &err)) 
+    {
+        syslog(LOG_ERR, "error initializing FTP listener; %s",
+          error_get_desc(&err));
+        exit(1);
+    }
+
+    /* set user to be as inoffensive as possible */
+    if (setgid(user_info->pw_gid) != 0) {
+        syslog(LOG_ERR, "error changing group; %s", strerror(errno));
+        exit(1);
+    }
+    if (setuid(user_info->pw_uid) != 0) {
+        syslog(LOG_ERR, "error changing group; %s", strerror(errno));
+        exit(1);
+    }
+
+    /* start our listener */
+    if (ftp_listener_start(&ftp_listener, &err) == 0) {
+        syslog(LOG_ERR, "error starting FTP service; %s", error_get_desc(&err));
+        exit(1);
+    }
+
+    /* wait for a SIGTERM and exit gracefully */
+    sigemptyset(&term_signal);
+    sigaddset(&term_signal, SIGTERM);
+    sigaddset(&term_signal, SIGINT);
+    pthread_sigmask(SIG_BLOCK, &term_signal, NULL);
+    sigwait(&term_signal, &sig);
+    if (sig == SIGTERM) {
+        syslog(LOG_INFO, "SIGTERM received, shutting down");
+    } else { 
+        syslog(LOG_INFO, "SIGINT received, shutting down");
+    }
+    ftp_listener_stop(&ftp_listener);
+    syslog(LOG_INFO, "all connections finished, FTP server exiting");
+    exit(0);
+}
+
+static void print_usage(const char *error)
+{
+    if (error != NULL) {
+        fprintf(stderr, "%s: %s\n", exe_name, error);
+    }
+    fprintf(stderr, 
+           " Syntax: %s [ options... ] user_name root_directory\n", exe_name);
+    fprintf(stderr, 
+           " Options:\n"
+           " -p, --port <num>\n"
+           "     Set the port to listen on (Default: %d)\n"
+           " -i, --interface <IP Address>\n"
+           "     Set the interface to listen on (Default: all)\n"
+           " -m, --max-clients <num>\n"
+           "     Set the number of clients allowed at one time (Default: %d)\n"
+           "-l, --local <local-logging>\n"
+           "     Use LOCAL facility for syslog, local-logging is 0 to 7\n"
+           " -N, --nodetach\n"
+           "     Do not detach from TTY and become a daemon\n",
+           DEFAULT_FTP_PORT, MAX_CLIENTS);
+}
+
+static void daemonize()
+{
+    int fork_ret;
+    int max_fd;
+    int null_fd;
+    int fd;
+
+    null_fd = open("/dev/null", O_RDWR);
+    if (null_fd == -1) {
+        fprintf(stderr, "%s: error opening null output device; %s\n", exe_name, 
+          strerror(errno));
+        exit(1);
+    }
+
+    max_fd = sysconf(_SC_OPEN_MAX);
+    if (max_fd == -1) {
+        fprintf(stderr, "%s: error getting maximum open file; %s\n", exe_name, 
+          strerror(errno));
+        exit(1);
+    }
+
+
+    fork_ret = fork();
+    if (fork_ret == -1) {
+        fprintf(stderr, "%s: error forking; %s\n", exe_name, strerror(errno));
+        exit(1);
+    }
+    if (fork_ret != 0) {
+        exit(0);
+    }
+    if (setsid() == -1) {
+        fprintf(stderr, "%s: error creating process group; %s\n", exe_name, 
+          strerror(errno));
+        exit(1);
+    }
+    fork_ret = fork();
+    if (fork_ret == -1) {
+        fprintf(stderr, "%s: error forking; %s\n", exe_name, strerror(errno));
+        exit(1);
+    }
+    if (fork_ret != 0) {
+        exit(0);
+    }
+    if (dup2(null_fd, 0) == -1) {
+        syslog(LOG_ERR, "error setting input to null; %s", 
+          strerror(errno));
+        exit(1);
+    }
+    if (dup2(null_fd, 1) == -1) {
+        syslog(LOG_ERR, "error setting output to null; %s", 
+          strerror(errno));
+        exit(1);
+    }
+    if (dup2(null_fd, 2) == -1) {
+        syslog(LOG_ERR, "error setting error output to null; %s", 
+          strerror(errno));
+        exit(1);
+    }
+    for (fd=3; fd<max_fd; fd++) {
+        close(fd);
+    }
+}
+
+
+/* Figure out the syslog fd and store it away. */
+static void init_syslog_hack ()
+{
+    struct sockaddr_un addr;
+    socklen_t len;
+    int fd;
+
+    my_syslog_fd = -1;
+    /* We know that stdin, stdout and stderr are connected to
+     * /dev/null, so we can start with 3.  FIXME: We should use a
+     * POSIX thing to figure out the highest possible fd. */
+    for (fd=3; fd < 1024; fd++) {
+        len = sizeof addr;
+        if (!getsockname (fd, (struct sockaddr*)&addr, &len)) {
+            if (addr.sun_family == PF_LOCAL) {
+                /* Found a unix domain socket.  This is what we were
+                 * looking for. */
+                my_syslog_fd = fd;
+                syslog(LOG_INFO,"Found my syslog file descriptor (%d).", fd);
+                return;
+            }
+        }
+    }
+}
+
+
+void reopen_syslog_hack (int fd)
+{
+    if (fd < 0)
+        return;
+    if (my_syslog_fd == -1)
+        return; /* not initialized, so we can't use the hack. */
+    if (fd != my_syslog_fd)
+        return; /* We did not lose the fd in the meantime, otherwise
+                 * the next file descriptor should get the same fd. */
+
+    my_syslog_fd = -1;
+    /* fixme: If we would employ a syslog() wrapper and use a lock
+     * there we could easily avoid to lose messages.  But OTOH, this
+     * is just a glibc fix, so we will live with that.  Anyway, we can
+     * detect a lost syslog file descriptor only after an open or
+     * socket call - so it does not matter at all. */
+    openlog(NULL, LOG_NDELAY, log_facility);
+    syslog(LOG_INFO,"Redone the openlog call to fix a glibc bug."
+                    "Some messages might have been lost.");
+    init_syslog_hack ();
+}
diff --git a/src/oftpd.h b/src/oftpd.h
new file mode 100644 (file)
index 0000000..9987482
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * $Id$
+ */
+
+#ifndef OFTPD_H
+#define OFTPD_H
+
+/* address to listen on (use NULL to listen on all addresses) */
+#define FTP_ADDRESS NULL
+
+/* default port FTP server listens on (use 0 to listen on default port) */
+#define FTP_PORT 0
+
+/* ranges possible for command-line specified port numbers */
+#define MIN_PORT 0
+#define MAX_PORT 65535
+
+/* default maximum number of clients */
+#define MAX_CLIENTS 250
+
+/* bounds on command-line specified number of clients */
+#define MIN_NUM_CLIENTS 1
+#define MAX_NUM_CLIENTS 300
+
+/* timeout (in seconds) before dropping inactive clients */
+#define INACTIVITY_TIMEOUT (15 * 60)
+
+/* README file name (sent automatically as a response to users) */
+#define README_FILE_NAME "README"
+
+
+/*-- oftpd.c --*/
+void reopen_syslog_hack (int fd);
+
+
+#endif /* OFTPD_H */
+
diff --git a/src/telnet_session.c b/src/telnet_session.c
new file mode 100644 (file)
index 0000000..b196272
--- /dev/null
@@ -0,0 +1,558 @@
+/*
+ * $Id$
+ */
+
+#include <config.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include "daemon_assert.h"
+#include "telnet_session.h"
+
+/* characters to process */
+#define SE         240
+#define NOP        241
+#define DATA_MARK  242
+#define BRK        243
+#define IP         244
+#define AO         245
+#define AYT        246
+#define EC         247
+#define EL         248
+#define GA         249
+#define SB         250
+#define WILL       251
+#define WONT       252
+#define DO         253
+#define DONT       254
+#define IAC        255
+
+/* input states */
+#define NORMAL    0
+#define GOT_IAC   1
+#define GOT_WILL  2
+#define GOT_WONT  3
+#define GOT_DO    4
+#define GOT_DONT  5
+#define GOT_CR    6
+
+/* prototypes */
+static int invariant(const telnet_session_t *t);
+static void process_data(telnet_session_t *t, int wait_flag);
+static void read_incoming_data(telnet_session_t *t);
+static void process_incoming_char(telnet_session_t *t, int c);
+static void add_incoming_char(telnet_session_t *t, int c);
+static int use_incoming_char(telnet_session_t *t);
+static void write_outgoing_data(telnet_session_t *t);
+static void add_outgoing_char(telnet_session_t *t, int c);
+static int max_input_read(telnet_session_t *t);
+
+/* initialize a telnet session */
+void telnet_session_init(telnet_session_t *t, int in, int out)
+{
+    daemon_assert(t != NULL);
+    daemon_assert(in >= 0);
+    daemon_assert(out >= 0);
+
+    t->in_fd = in;
+    t->in_errno = 0;
+    t->in_eof = 0;
+    t->in_take = t->in_add = 0;
+    t->in_buflen = 0;
+
+    t->in_status = NORMAL;
+
+    t->out_fd = out;
+    t->out_errno = 0;
+    t->out_eof = 0;
+    t->out_take = t->out_add = 0;
+    t->out_buflen = 0;
+
+    process_data(t, 0);
+
+    daemon_assert(invariant(t));
+}
+
+/* print output */
+int telnet_session_print(telnet_session_t *t, const char *s)
+{
+    int len;
+    int amt_printed;
+
+    daemon_assert(invariant(t));
+
+    len = strlen(s);
+    if (len == 0) {
+        daemon_assert(invariant(t));
+       return 1;
+    }
+
+    amt_printed = 0;
+    do {
+        if ((t->out_errno != 0) || (t->out_eof != 0)) {
+            daemon_assert(invariant(t));
+            return 0;
+        }
+        while ((amt_printed < len) && (t->out_buflen < BUF_LEN))
+       {
+            daemon_assert(s[amt_printed] != '\0');
+            add_outgoing_char(t, s[amt_printed]);
+            amt_printed++;
+       }
+        process_data(t, 1);
+    } while (amt_printed < len);
+
+    while (t->out_buflen > 0) {
+        if ((t->out_errno != 0) || (t->out_eof != 0)) {
+            daemon_assert(invariant(t));
+            return 0;
+        }
+        process_data(t, 1);
+    } 
+
+    daemon_assert(invariant(t));
+    return 1;
+}
+
+/* print a line output */
+int telnet_session_println(telnet_session_t *t, const char *s)
+{
+    daemon_assert(invariant(t));
+    if (!telnet_session_print(t, s)) {
+        daemon_assert(invariant(t));
+        return 0;
+    }
+    if (!telnet_session_print(t, "\015\012")) {
+        daemon_assert(invariant(t));
+        return 0;
+    }
+    daemon_assert(invariant(t));
+    return 1;
+}
+
+/* read a line */
+int telnet_session_readln(telnet_session_t *t, char *buf, int buflen) 
+{
+    int amt_read;
+
+    daemon_assert(invariant(t));
+    amt_read = 0;
+    for (;;) {
+        if ((t->in_errno != 0) || (t->in_eof != 0)) {
+            daemon_assert(invariant(t));
+           return 0;
+       }
+       while (t->in_buflen > 0) {
+           if (amt_read == buflen-1) {
+               buf[amt_read] = '\0';
+                daemon_assert(invariant(t));
+               return 1;
+           }
+            daemon_assert(amt_read >= 0);
+            daemon_assert(amt_read < buflen);
+           buf[amt_read] = use_incoming_char(t);
+           if (buf[amt_read] == '\012') {
+                daemon_assert(amt_read+1 >= 0);
+                daemon_assert(amt_read+1 < buflen);
+               buf[amt_read+1] = '\0';
+                daemon_assert(invariant(t));
+               return 1;
+           }
+            amt_read++;
+        }
+       process_data(t, 1);
+    }
+}
+
+void telnet_session_destroy(telnet_session_t *t)
+{
+    daemon_assert(invariant(t));
+
+    close(t->in_fd);
+    if (t->out_fd != t->in_fd) {
+        close(t->out_fd);
+    }
+    t->in_fd = -1;
+    t->out_fd = -1;
+}
+
+/* receive any incoming data, send any pending data */
+static void process_data(telnet_session_t *t, int wait_flag)
+{
+    fd_set readfds;
+    fd_set writefds;
+    fd_set exceptfds;
+    struct timeval tv_zero;
+    struct timeval *tv;
+
+    /* set up our select() variables */
+    FD_ZERO(&readfds);
+    FD_ZERO(&writefds);
+    FD_ZERO(&exceptfds);
+    if (wait_flag) {
+        tv = NULL;
+    } else {
+        tv_zero.tv_sec = 0;
+        tv_zero.tv_usec = 0;
+        tv = &tv_zero;
+    }
+
+    /* only check for input if we can accept input */
+    if ((t->in_errno == 0) && 
+        (t->in_eof == 0) && 
+       (max_input_read(t) > 0)) 
+    {
+        FD_SET(t->in_fd, &readfds);
+        FD_SET(t->in_fd, &exceptfds);
+    }
+
+    /* only check for output if we have pending output */
+    if ((t->out_errno == 0) && (t->out_eof == 0) && (t->out_buflen > 0)) {
+        FD_SET(t->out_fd, &writefds);
+        FD_SET(t->out_fd, &exceptfds);
+    }
+
+    /* see if there's anything to do */
+    if (select(FD_SETSIZE, &readfds, &writefds, &exceptfds, tv) > 0) {
+
+        if (FD_ISSET(t->in_fd, &exceptfds)) {
+           t->in_eof = 1;
+       } else if (FD_ISSET(t->in_fd, &readfds)) {
+           read_incoming_data(t);
+       }
+
+        if (FD_ISSET(t->out_fd, &exceptfds)) {
+           t->out_eof = 1;
+       } else if (FD_ISSET(t->out_fd, &writefds)) {
+           write_outgoing_data(t);
+        }
+    }
+}
+
+static void read_incoming_data(telnet_session_t *t)
+{
+    size_t read_ret;
+    char buf[BUF_LEN];
+    size_t i;
+
+    /* read as much data as we have buffer space for */
+    daemon_assert(max_input_read(t) <= BUF_LEN);
+    read_ret = read(t->in_fd, buf, max_input_read(t));
+
+    /* handle three possible read results */
+    if (read_ret == -1) {
+        t->in_errno = errno;
+    } else if (read_ret == 0) {
+        t->in_eof = 1;
+    } else {
+        for (i=0; i<read_ret; i++) {
+           process_incoming_char(t, (unsigned char)buf[i]);
+       }
+    }
+}
+
+
+/* process a single character */
+static void process_incoming_char(telnet_session_t *t, int c)
+{
+    switch (t->in_status) {
+        case GOT_IAC:
+           switch (c) {
+                case WILL:
+                   t->in_status = GOT_WILL;
+                   break;
+                case WONT:
+                   t->in_status = GOT_WONT;
+                   break;
+                case DO:
+                   t->in_status = GOT_DO;
+                   break;
+                case DONT:
+                   t->in_status = GOT_DONT;
+                   break;
+               case IAC:
+                   add_incoming_char(t, IAC);
+                   t->in_status = NORMAL;
+                   break;
+                default:
+                   t->in_status = NORMAL;
+           }
+           break;
+        case GOT_WILL:
+           add_outgoing_char(t, IAC);
+           add_outgoing_char(t, DONT);
+           add_outgoing_char(t, c);
+           t->in_status = NORMAL;
+           break;
+        case GOT_WONT:
+           t->in_status = NORMAL;
+           break;
+        case GOT_DO:
+           add_outgoing_char(t, IAC);
+           add_outgoing_char(t, WONT);
+           add_outgoing_char(t, c);
+           t->in_status = NORMAL;
+           break;
+        case GOT_DONT:
+           t->in_status = NORMAL;
+           break;
+        case GOT_CR:
+           add_incoming_char(t, '\012');
+            if (c != '\012') {
+               add_incoming_char(t, c);
+           }
+           t->in_status = NORMAL;
+           break;
+       case NORMAL:
+            if (c == IAC) {
+               t->in_status = GOT_IAC;
+           } else if (c == '\015') {
+               t->in_status = GOT_CR;
+            } else {
+               add_incoming_char(t, c);
+           }
+    }
+}
+
+/* add a single character, wrapping buffer if necessary (should never occur) */
+static void add_incoming_char(telnet_session_t *t, int c)
+{
+    daemon_assert(t->in_add >= 0);
+    daemon_assert(t->in_add < BUF_LEN);
+    t->in_buf[t->in_add] = c;
+    t->in_add++;
+    if (t->in_add == BUF_LEN) {
+        t->in_add = 0;
+    }
+    if (t->in_buflen == BUF_LEN) {
+        t->in_take++;
+       if (t->in_take == BUF_LEN) {
+           t->in_take = 0;
+       }
+    } else {
+        t->in_buflen++;
+    }
+}
+
+/* remove a single character */
+static int use_incoming_char(telnet_session_t *t)
+{
+    int c;
+
+    daemon_assert(t->in_take >= 0);
+    daemon_assert(t->in_take < BUF_LEN);
+    c = t->in_buf[t->in_take];
+    t->in_take++;
+    if (t->in_take == BUF_LEN) {
+        t->in_take = 0;
+    }
+    t->in_buflen--;
+
+    return c;
+}
+
+/* add a single character, hopefully will never happen :) */
+static void add_outgoing_char(telnet_session_t *t, int c)
+{
+    daemon_assert(t->out_add >= 0);
+    daemon_assert(t->out_add < BUF_LEN);
+    t->out_buf[t->out_add] = c;
+    t->out_add++;
+    if (t->out_add == BUF_LEN) {
+        t->out_add = 0;
+    }
+    if (t->out_buflen == BUF_LEN) {
+        t->out_take++;
+       if (t->out_take == BUF_LEN) {
+           t->out_take = 0;
+       }
+    } else {
+        t->out_buflen++;
+    }
+}
+
+
+static void write_outgoing_data(telnet_session_t *t)
+{
+    size_t write_ret;
+
+    if (t->out_take < t->out_add) {
+        /* handle a buffer that looks like this:       */
+       /*     |-- empty --|-- data --|-- empty --|    */
+        daemon_assert(t->out_take >= 0);
+        daemon_assert(t->out_take < BUF_LEN);
+        daemon_assert(t->out_buflen > 0);
+        daemon_assert(t->out_buflen + t->out_take <= BUF_LEN);
+        write_ret = write(t->out_fd, t->out_buf + t->out_take, t->out_buflen);
+    } else {
+        /* handle a buffer that looks like this:       */
+       /*     |-- data --|-- empty --|-- data --|     */
+        daemon_assert(t->out_take >= 0);
+        daemon_assert(t->out_take < BUF_LEN);
+        daemon_assert((BUF_LEN - t->out_take) > 0);
+        write_ret = write(t->out_fd, 
+                         t->out_buf + t->out_take, 
+                         BUF_LEN - t->out_take);
+    }
+
+    /* handle three possible write results */
+    if (write_ret == -1) {
+        t->out_errno = errno;
+    } else if (write_ret == 0) {
+        t->out_eof = 1;
+    } else {
+        t->out_buflen -= write_ret;
+       t->out_take += write_ret;
+       if (t->out_take >= BUF_LEN) {
+           t->out_take -= BUF_LEN;
+       }
+    }
+}
+
+/* return the amount of a read */
+static int max_input_read(telnet_session_t *t)
+{
+    int max_in;
+    int max_out;
+
+    daemon_assert(invariant(t));
+
+    /* figure out how much space is available in the input buffer */
+    if (t->in_buflen < BUF_LEN) {
+        max_in = BUF_LEN - t->in_buflen;
+    } else {
+        max_in = 0;
+    }
+
+    /* worry about space in the output buffer (for DONT/WONT replies) */
+    if (t->out_buflen < BUF_LEN) {
+        max_out = BUF_LEN - t->out_buflen;
+    } else {
+        max_out = 0;
+    }
+
+    daemon_assert(invariant(t));
+
+    /* return the minimum of the two values */
+    return (max_in < max_out) ? max_in : max_out;
+}
+
+#ifndef NDEBUG
+static int invariant(const telnet_session_t *t) 
+{
+    if (t == NULL) {
+        return 0;
+    }
+    if (t->in_fd < 0) {
+        return 0;
+    }
+    if ((t->in_take < 0) || (t->in_take >= BUF_LEN)) {
+        return 0;
+    }
+    if ((t->in_add < 0) || (t->in_add >= BUF_LEN)) {
+        return 0;
+    }
+    if ((t->in_buflen < 0) || (t->in_buflen > BUF_LEN)) {
+        return 0;
+    }
+
+    switch (t->in_status) {
+        case NORMAL:
+           break;
+        case GOT_IAC:
+           break;
+        case GOT_WILL:
+           break;
+        case GOT_WONT:
+           break;
+        case GOT_DO:
+           break;
+        case GOT_DONT:
+           break;
+        case GOT_CR:
+           break;
+        default:
+           return 0;
+    }
+
+    if (t->out_fd < 0) {
+        return 0;
+    }
+    if ((t->out_take < 0) || (t->out_take >= BUF_LEN)) {
+        return 0;
+    }
+    if ((t->out_add < 0) || (t->out_add >= BUF_LEN)) {
+        return 0;
+    }
+    if ((t->out_buflen < 0) || (t->out_buflen > BUF_LEN)) {
+        return 0;
+    }
+
+    return 1;
+}
+#endif /* NDEBUG */
+
+#ifdef STUB_TEST
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <ctype.h>
+
+int main()
+{
+    telnet_session_t t;
+    char buf[64];
+    int fd;
+    int val;
+    struct sockaddr_in addr;
+    int newfd;
+    struct sockaddr_in newaddr;
+    unsigned newaddrlen;
+    int i;
+
+    daemon_assert((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1);
+    val = 1;
+    daemon_assert(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) 
+        == 0);
+    addr.sin_port = htons(23);
+    addr.sin_addr.s_addr = INADDR_ANY;
+    daemon_assert(bind(fd, &addr, sizeof(addr)) == 0);
+    daemon_assert(listen(fd, SOMAXCONN) == 0);
+
+    signal(SIGPIPE, SIG_IGN);
+    while ((newfd = accept(fd, &newaddr, &newaddrlen)) >= 0) {
+        reopen_syslog_hack (newfd);
+        telnet_session_init(&t, newfd, newfd);
+        telnet_session_print(&t, "Password:");
+        telnet_session_readln(&t, buf, sizeof(buf));
+       for (i=0; buf[i] != '\0'; i++) {
+           printf("[%02d]: 0x%02X ", i, buf[i] & 0xFF);
+           if (isprint(buf[i])) {
+               printf("'%c'\n", buf[i]);
+           } else {
+               printf("'\\x%02X'\n", buf[i] & 0xFF);
+           }
+       }
+        telnet_session_println(&t, "Hello, world.");
+        telnet_session_println(&t, "Ipso, facto");
+        telnet_session_println(&t, "quid pro quo");
+        sleep(1);
+        close(newfd);
+    }
+    return 0;
+}
+#endif /* STUB_TEST */
+
diff --git a/src/telnet_session.h b/src/telnet_session.h
new file mode 100644 (file)
index 0000000..dd026b8
--- /dev/null
@@ -0,0 +1,40 @@
+/* 
+ * $Id$
+ */
+
+#ifndef TELNET_SESSION_H
+#define TELNET_SESSION_H
+
+/* size of buffer */
+#define BUF_LEN 2048
+
+/* information on a telnet session */
+typedef struct {
+    int in_fd;
+    int in_errno; 
+    int in_eof; 
+    int in_take;
+    int in_add;
+    char in_buf[BUF_LEN];
+    int in_buflen;
+
+    int in_status;
+
+    int out_fd;
+    int out_errno; 
+    int out_eof; 
+    int out_take;
+    int out_add;
+    char out_buf[BUF_LEN];
+    int out_buflen;
+} telnet_session_t;
+
+/* functions */
+void telnet_session_init(telnet_session_t *t, int in, int out);
+int telnet_session_print(telnet_session_t *t, const char *s);
+int telnet_session_println(telnet_session_t *t, const char *s);
+int telnet_session_readln(telnet_session_t *t, char *buf, int buflen);
+void telnet_session_destroy(telnet_session_t *t);
+
+#endif /* TELNET_SESSION_H */
+
diff --git a/src/watchdog.c b/src/watchdog.c
new file mode 100644 (file)
index 0000000..767a8c1
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ * $Id$
+ */
+
+#include <config.h>
+#include <unistd.h>
+#include "daemon_assert.h"
+#include "watchdog.h"
+
+static int invariant(watchdog_t *w);
+static void insert(watchdog_t *w, watched_t *watched);
+static void delete(watchdog_t *w, watched_t *watched);
+static void *watcher(void *void_w);
+
+int watchdog_init(watchdog_t *w, int inactivity_timeout, error_t *err)
+{
+    pthread_t thread_id;
+    int error_code;
+
+    daemon_assert(w != NULL);
+    daemon_assert(inactivity_timeout > 0);
+    daemon_assert(err != NULL);
+
+    pthread_mutex_init(&w->mutex, NULL);
+    w->inactivity_timeout = inactivity_timeout;
+    w->oldest = NULL;
+    w->newest = NULL;
+
+    error_code = pthread_create(&thread_id, NULL, watcher, w);
+    if (error_code != 0) {
+       error_init(err, error_code, "error %d from pthread_create()", 
+         error_code);
+        return 0;
+    }
+    pthread_detach(thread_id);
+
+    daemon_assert(invariant(w));
+
+    return 1;
+}
+
+void watchdog_add_watched(watchdog_t *w, watched_t *watched)
+{
+    daemon_assert(invariant(w));
+
+    pthread_mutex_lock(&w->mutex);
+
+    watched->watched_thread = pthread_self();
+    watched->watchdog = w;
+    insert(w, watched);
+
+    pthread_mutex_unlock(&w->mutex);
+
+    daemon_assert(invariant(w));
+}
+
+void watchdog_defer_watched(watched_t *watched)
+{
+    watchdog_t *w;
+
+    daemon_assert(invariant(watched->watchdog));
+
+    w = watched->watchdog;
+    pthread_mutex_lock(&w->mutex);
+
+    delete(w, watched);
+    insert(w, watched);
+
+    pthread_mutex_unlock(&w->mutex);
+    daemon_assert(invariant(w));
+}
+
+void watchdog_remove_watched(watched_t *watched)
+{
+    watchdog_t *w;
+
+    daemon_assert(invariant(watched->watchdog));
+
+    w = watched->watchdog;
+    pthread_mutex_lock(&w->mutex);
+
+    delete(w, watched);
+
+    pthread_mutex_unlock(&w->mutex);
+    daemon_assert(invariant(w));
+}
+
+static void insert(watchdog_t *w, watched_t *watched)
+{
+   /*********************************************************************
+    Set alarm to current time + timeout duration.  Note that this is not 
+    strictly legal, since time_t is an abstract data type.
+    *********************************************************************/
+    watched->alarm_time = time(NULL) + w->inactivity_timeout;
+
+   /*********************************************************************
+    If the system clock got set backwards, we really should search for 
+    the correct location, instead of just inserting at the end.  However, 
+    this happens very rarely (ntp and other synchronization protocols 
+    speed up or slow down the clock to adjust the time), so we'll just 
+    set our alarm to the time of the newest alarm - giving any watched
+    processes added some extra time.
+    *********************************************************************/
+    if (w->newest != NULL) {
+        if (w->newest->alarm_time > watched->alarm_time) {
+           watched->alarm_time = w->newest->alarm_time;
+       }
+    }
+
+    /* set our pointers */
+    watched->older = w->newest;
+    watched->newer = NULL;
+
+    /* add to list */
+    if (w->oldest == NULL) {
+        w->oldest = watched;
+    } else {
+        w->newest->newer = watched;
+    }
+    w->newest = watched;
+    watched->in_list = 1;
+}
+
+static void delete(watchdog_t *w, watched_t *watched)
+{
+    if (!watched->in_list) {
+        return;
+    }
+
+    if (watched->newer == NULL) {
+        daemon_assert(w->newest == watched);
+       w->newest = w->newest->older;
+        if (w->newest != NULL) {
+           w->newest->newer = NULL;
+       }
+    } else {
+        daemon_assert(w->newest != watched);
+       watched->newer->older = watched->older;
+    }
+
+    if (watched->older == NULL) {
+        daemon_assert(w->oldest == watched);
+       w->oldest = w->oldest->newer;
+        if (w->oldest != NULL) {
+           w->oldest->older = NULL;
+       }
+    } else {
+        daemon_assert(w->oldest != watched);
+       watched->older->newer = watched->newer;
+    }
+
+    watched->older = NULL;
+    watched->newer = NULL;
+    watched->in_list = 0;
+}
+
+static void *watcher(void *void_w)
+{
+    watchdog_t *w;
+    struct timeval tv;
+    time_t now;
+    watched_t *watched;
+
+    w = (watchdog_t *)void_w;
+    for (;;) {
+        tv.tv_sec = 1;
+       tv.tv_usec = 0;
+       select(0, NULL, NULL, NULL, &tv);
+
+       time(&now);
+
+        pthread_mutex_lock(&w->mutex);
+       while ((w->oldest != NULL) && 
+           (difftime(now, w->oldest->alarm_time) > 0))
+       {
+           watched = w->oldest;
+
+            /*******************************************************
+            This might seem like a memory leak, but in oftpd the 
+            watched_t structure is held in the thread itself, so 
+            canceling the thread effectively frees the memory.  I'm
+            not sure whether this is elegant or a hack.  :)
+             *******************************************************/
+           delete(w, watched);
+
+           pthread_cancel(watched->watched_thread);
+       }
+        pthread_mutex_unlock(&w->mutex);
+    }
+}
+
+#ifndef NDEBUG
+static int invariant(watchdog_t *w)
+{
+    int ret_val;
+
+    watched_t *ptr;
+    int old_to_new_count;
+    int new_to_old_count;
+
+
+    if (w == NULL) {
+        return 0;
+    }
+
+    ret_val = 0;
+    pthread_mutex_lock(&w->mutex);
+
+    if (w->inactivity_timeout <= 0) {
+        goto exit_invariant;
+    }
+
+    /* either oldest and newest are both NULL, or neither is */
+    if (w->oldest != NULL) {
+        if (w->newest == NULL) {
+            goto exit_invariant;
+       }
+
+        /* check list from oldest to newest */
+       old_to_new_count = 0;
+       ptr = w->oldest;
+        while (ptr != NULL) {
+           old_to_new_count++;
+           if (ptr->older != NULL) {
+               if (ptr->alarm_time < ptr->older->alarm_time) {
+                    goto exit_invariant;
+               }
+           }
+           if (ptr->newer != NULL) {
+               if (ptr->alarm_time > ptr->newer->alarm_time) {
+                    goto exit_invariant;
+               }
+           }
+           ptr = ptr->newer;
+        }
+
+        /* check list from newest to oldest */
+       new_to_old_count = 0;
+       ptr = w->newest;
+       while (ptr != NULL) {
+           new_to_old_count++;
+           if (ptr->older != NULL) {
+               if (ptr->alarm_time < ptr->older->alarm_time) {
+                    goto exit_invariant;
+               }
+           }
+           if (ptr->newer != NULL) {
+               if (ptr->alarm_time > ptr->newer->alarm_time) {
+                    goto exit_invariant;
+               }
+           }
+           ptr = ptr->older;
+       }
+
+       /* verify forward and backward lists at least have the same count */
+       if (old_to_new_count != new_to_old_count) {
+            goto exit_invariant;
+       }
+
+    } else {
+        if (w->newest != NULL) {
+            goto exit_invariant;
+       }
+    }
+
+    /* at this point, we're probably okay */
+    ret_val = 1;
+
+exit_invariant:
+    pthread_mutex_unlock(&w->mutex);
+    return ret_val;
+}
+#endif /* NDEBUG */
+
diff --git a/src/watchdog.h b/src/watchdog.h
new file mode 100644 (file)
index 0000000..6117030
--- /dev/null
@@ -0,0 +1,58 @@
+/* 
+ * $Id$
+ */
+
+#ifndef WATCHDOG_H
+#define WATCHDOG_H
+
+#include <pthread.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#include "error.h"
+
+/* each watched thread gets one of these structures */
+typedef struct watched {
+    /* thread to monitor */
+    pthread_t watched_thread;
+
+    /* flag whether in a watchdog list */
+    int in_list;
+
+    /* time when to cancel thread if no activity */
+    time_t alarm_time;
+
+    /* for location in doubly-linked list */
+    struct watched *older;
+    struct watched *newer;
+
+    /* watchdog that this watched_t is in */
+    void *watchdog;
+} watched_t;
+
+/* the watchdog keeps track of all information */
+typedef struct {
+    pthread_mutex_t mutex;
+    int inactivity_timeout;
+
+    /* the head and tail of our list */
+    watched_t *oldest;
+    watched_t *newest;
+} watchdog_t;
+
+int watchdog_init(watchdog_t *w, int inactivity_timeout, error_t *err);
+void watchdog_add_watched(watchdog_t *w, watched_t *watched);
+void watchdog_defer_watched(watched_t *watched);
+void watchdog_remove_watched(watched_t *watched);
+
+#endif /* WATCHDOG_H */
+