1
0
mirror of https://github.com/InfrastructureServices/vsftpd.git synced 2025-04-19 01:24:02 +03:00

Updated to v0.9.2

This commit is contained in:
Dag Wieers 2001-09-22 00:00:00 +02:00
commit 20e89955e0
93 changed files with 11744 additions and 0 deletions

35
AUDIT Normal file
View File

@ -0,0 +1,35 @@
This file contains information on the audit status of the code in this program.
Each file has an audit code of between 1 and 5, ranging from 1 being unaudited
and 5 being heavily audited by multiple competent people.
The important rule is that when a file is changed, the audit status goes back
to 1, _unless_ the change(s) are audited very carefully as they go in.
ascii.c 3
dirchange.c 3
filestr.c 3
ftpcmdio.c 3
ftpdataio.c 2
logging.c 3
ls.c 2
main.c 3
netstr.c 3
oneprocess.c 3
parseconf.c 2
postlogin.c 2
postprivparent.c 3
prelogin.c 3
privops.c 2
privparent.c 3
privsock.c 3
secbuf.c 3
secutil.c 3
str.c 2
strlist.c 2
sysdeputil.c 2
sysstr.c 3
sysutil.c 2
tunables.c 3
twoprocess.c 2
utility.c 3

62
BENCHMARKS Normal file
View File

@ -0,0 +1,62 @@
--
Below are some quick benchmark figures vs. wu-ftpd. This is an untuned BETA
version of vsftpd (0.0.10)
The executive summary is that wu-ftpd got a thorough thrashing. The most
telling statistic is wu-ftpd typically failing to sustain 400 users, whereas
vsftpd copes with 1000 with room to spare.
A 2.2.x kernel was used. A 2.4.x kernel should make vsftpd look even better
relative to wu-ftpd thanks to the sendfile() boosts in 2.4.x. A 2.4.x kernel
with zerocopy should be amazing.
Many thanks to Andrew Anderson <andrew@redhat.com>
--
Here's some benchmarks that I did on vsftpd vs. wu-ftpd. The tests were
run with "dkftpbench -hftpserver -n500 -t600 -f/pub/dkftp/<file>". The
attached file are the summary output with time to reach the steady-state
condition.
The interesting things I noticed are:
- In the raw test results, vsftpd had a much higher peak on the x10k.dat
transfer run than wu-ftpd did. Wu-ftpd peaked at ~150 connections and
bled down to ~130 connections, while vsftpd peaked at ~400 connections and
bled down to ~160 connections. I tend to believe the peaks more than the
final steady-state that dkftpbench reports, though.
- For the other tests, our wu-ftpd setup was limited to 400 connections,
but in about half of the x100k/x1000k runs could not even sustain 400
connections, while vsftpd handled 500 easily on those runs.
- During the peak runs at x10k, the machine load with vsftpd looked like
this (I don't have this data still for the wu-ftpd runs):
01:01:00 AM all 4.92 0.00 21.23 73.85
03:31:00 AM all 4.89 0.00 19.53 75.58
05:11:00 AM all 4.19 0.00 16.89 78.92
07:01:00 AM all 5.61 0.00 22.47 71.92
The steady-state loads were more in the 3-5% user, 10-15% system. For the
x100/x1000 loads with vsftpd, the system load looked like this:
x100k.dat:
09:01:00 AM all 2.27 0.00 9.79 87.94
x1000k.dat:
11:01:00 AM all 0.42 0.00 5.75 93.83
Not bad -- 500 concurrent users for ~7% system load.
- Just for kicks I ran the x1000k test with 1000 users. At peak load:
X1000k.dat with 1000 users:
04:41:00 PM all 1.23 0.00 46.59 52.18
Based on what I'm seeing, it looks like if a server had enough bandwidth,
it could indeed sustain ~2000 users with the current 2 process model
that's implemented in vsftpd. I did notice that dkftpbench slowed down
the connection rate after 800 connections. I'm not sure if that was a
dkftpbench issue, or if I ran into something other limit.

25
BUGS Normal file
View File

@ -0,0 +1,25 @@
BUGS
====
This file, surprisingly enough, contains a list of known outstanding bugs
in the program. Bugs that get documented here are typically not particularly
serious, and may never get fixed. Serious bugs will get fixed immediately.
- RFC compliance: if we get no PORT or PASV, looks like we're supposed to
assume a PORT to the same IP as control connection, and port 20.
- RFC compliance: shouldn't include directories in NLST. Note that wu-ftpd
complies here, almost all other FTPd's don't
- ls <existing but unreadable dir> should list nothing, but it lists the
directory name itself
- In ASCII mode, the SIZE command needs to take into account the size of
the file _after_ ASCII linefeed mangling?
- ASCII mode uploads: we're only supposed to remove \r if they preceed \n,
but I rip them out unconditionally.
- "standard" xferlog format: we log dates like Feb 09, wu-ftpd logs like
Feb 9.
- Security model: vsf_privop_get_ftp_port_sock() should probably do the
connect() to the remote location itself.
- If a user's homedir doesn't exist, we ungracefully exit with "OOPS: chdir"
- If someone has one of the timeouts (command, data) setup, but not the other,
then timeout will behave whackily.

500
Changelog Normal file
View File

@ -0,0 +1,500 @@
0.0.1 initial versioned tarball released
----------------------------------------
- Added "-ldl" to LIBS to get linking to work on RedHat6.1
- Add RedHat6.1 on list of tested platforms :)
0.0.2 packaged
--------------
- Emit version in greeting string
- In PORT command, reject numbers <0 or >255. Problem noted by Solar Designer,
<solar@openwall.com>
- Allow an option AND a path for LIST/NLST, e.g. "LIST -al /pub". Reported by
Bill Nottingham <notting@redhat.com>, using ncftp. Further noted by Colin
Hogben <chah@jet.uk> using emacs and James Antill <james@and.org>.
- Don't prepend directory path for LIST (but still so for NLST). Noted by
Colin Hogben <chah@jet.uk> and Ingo Luetkebohle <ingo@blank.pages.de>
- Fix problem listing non-existant or unreadable directories - just return
a blank listing rather than an error. Problem noted by Martin Sillence
<martin.sillence@prnewswire.co.uk>, using squid.
- Fix KDE's downloads (via KFM), it was using the "SIZE" command which I had
not implemented. Reported by Simon Dales <simonD@nuffield.co.uk> and Jo Dillon
<jo@trolltech.com>. Apparently implementing SIZE also fixed lftp's download
time estimator, reported by Ingo Luetkebohle <ingo@blank.pages.de>
- Remove abornal_exit() from utility.c
- Fix so we don't write "500 OOPS: child died" upon QUIT. Reported by Solar
Designer, <solar@openwall.com> and Tim Bagot <tsb@earth.li>
0.0.3 packaged
--------------
- Oops: fix so we don't emit a status 150 mark unless we actually got a
connection from the client (stops some clients hanging trying to list an
inaccessible directory)
0.0.4 packaged
--------------
- In verbose directory listing, report symlink targets. Use the traditional
syntax of: "link_name -> target_path"
- Damn netscape! The comma in the response text to PASV confused it, so it
had to be removed. Discovered with tcpdump!
- Don't require clients to redo PORT or PASV if a RETR or STOR fails due
to inability to open/create file. Fixes Netscape symlink navigation problem.
- Fix for listing absolute paths with only one /, e.g. "ls /.message" was
failing
0.0.5 packaged
--------------
- Remove README.ftpproto
- Add SECURITY/OVERVIEW
- Add SECURITY/DESIGN
- Note that as a security tweak, we should lose more privs if we're configured
for anonymous only logins (TODO)
- Add SECURITY/IMPLEMENTATION, SECURITY/TRUST, but nothing in them yet.
- Convert str.c to vsf_sysutil_*. This leaves the following to do:
checkauth.c, main.c, postprivparent.c, privparent.c, privsock.c, utility.c
- Convert privparent.c to vsf_sysutil_*.
- Create BUGS and move existing listed bugs from TODO into this new file
- Add parseconf.h, parseconf.c to handle parsing of a config file (work in
progress)
- Fix change_full_credentials() in utility.c, to always chdir() even if we
are not going to do a chroot()
- Rename get_random_byte() to vsf_sysutil_get_random_byte(), and move from
utility.c to sysutil.c
- Create new file secutil.c, move change_full_credentials() to it and rename
- Convert utility.c to vsf_sysutil_*.
- handle_local_login(): don't look up username; common_do_login() does it
- implement different tunable umask() values for local/anonymous users
- implement SITE UMASK
- implement SITE CHMOD
- whoops! allow non-anonymous users to overwrite files with STOR
0.0.6 packaged
--------------
- SECURITY: when in anonymous-only mode, reject usernames that aren't the
anonymous usernames. This is hoping some FTP clients will be stopped from
sending a cleartext password. Idea from Gerald Teschl <gt@esi.ac.at>.
- Decided to put "telnet strings" on the back burner :)
- Sprinkling of static in main.c
- Complete parseconf.c config file parsing and plug it into main.c
- Convert main.c to vsf_sysutil_*. This leaves
checkauth.c, postprivparent.c and privsock.c
- Now we have runtime config, make compiled in defaults extra paranoid
- Implement "tunable_anon_world_readable_only" to only serve publicly
readable files anonymously
- Add sample "vsftpd.conf"
- Eww - missing "return" in parseconf.c
- Move ASCII mode transfers out of critical section in TODO
- parseconf.c: if an integer starts with "0", treat it as octal
- Ban "SITE CHMOD" if !tunable_write_enable
- Wrote SECURITY/TRUST
- Wrote SECURITY/IMPLEMENTATION, probably more to come
- Update INSTALL
- Add "tunable_nopriv_user"
- Update parseconf.c with the two latest new config variables
- Add sysdeputil.h, sysdeputil.c for system specific facilities, i.e.
capabilites, authentication.
- Lose checkauth.c,h - they moved into sysdeputil.c,h
- Lose config.h - it moved into sysdeputil.c
- Convert postprivparent.c to vsf_sysutil_* (leaves privsock.c)
- Convert privsock.c to vsf_sysutil_*. All done, yay!! :)
- D'oh! Missing "!" in postlogin.c refused to server publicly readable files:)
- Fix chown() of uploaded files (broken initialization order in main())
- Add SPEED, and fill it with wild speculation
- Rename distribution directory "vsftpd-x.x.x" (note the added "d")
0.0.7 packaged
--------------
- Build with -O2
- Fix "uninitialized" warnings -O2 exposed - the one in capabilities setup
could be nasty!
- Nail warning in vsf_sysutil_sendfile(). We're now "-Wall warning free"
- Build with -Werror to signal intent to _stay_ warning free
- A few int -> long in the area of file sizes and offsets
- Remove comma's at end of enum lists (-pedantic caught it)
- Impact from fixing warnings caused by -pedantic
- Date format %e -> %d in date display, %e isn't everywhere
- Paranoia in vsf_sysutil_malloc()
- Clean up interface to substring searching in str.c
- Cleanups in str.c
- Squash most "unsigned<->signed" conversions exposed by -Wconversion
- Lose "-g" to CFLAGS; after all we're bug-free now ;-)
- Add "AUDIT"
- Fix up a bunch of potential 64-bit issues (maybe >2Gb files will work on
64-bit platforms now, no way to test)
- Implement PR_SET_KEEPCAPS support for 2.2.18+ and 2.4.0+ kernels
- In sysdeputil.c, change NULL -> 0 to help Solaris build problem
- Repair vsf_sysutil_sendfile() and the caller
- Logging: log the username
- Logging: don't log "//" as start of filenames under certain conditions
- Logging: log the date. Logging is almost useful now!
- Logging: log MKD commands too; they are used in anon ftp a fair bit
- Take the trouble to look into partial reads/writes. Looks like we are safe.
- vsf_sysutil_read and vsf_sysutil_write now hide EINTR and retry
- Replace some vsf_sysutil_{read,write} usage with
vsf_sysutil_{read,write)_loop which handles partial reads and writes
- Implement a sendfile() replacement for systems which lack it
- Implement runtime checking for system specific Linux stuff, i.e.
prctl(PR_SET_KEEPCAPS). This is inspired by RedHat7.0 headers claiming to
be a 2.4.0 kernel, but actually you are running on 2.2.x! :-(
- Strip the build executable at link time
0.0.8 packaged
--------------
- A few incorrect sizeof()'s in postlogin.c, thanks to Antonomasia
<ant@notatla.demon.co.uk> for noting these.
- Decide that ASCII support isn't too important for now (waiting for users to
demand it). Also decide that ABOR is a must :( Thanks to Zach Brown
<zab@zabbo.net> for the discussion.
- More TODO items thanks to Stephen White <swhite@ox.compsoc.net> - 2.0.x
issues.
- Provide a definition for SHUT_RDWR in sysutil.c, not all systems have that
definition yet. Thanks Stephen White <swhite@ox.compsoc.net>.
- Tidy privparent.c
- Decide ASCII _is_ quite important, thanks Solar ;-)
- Bit of extra paranoia in sysutil.c: don't call mem*() if size == 0
- Tidy str.c
- Command line: if vsftpd has an argument, it is a path to a config file.
- Set TCP_NODELAY on command stream
- Don't lseek() for RETR in common case with REST set to 0
- Correct error code for transfer after succesful connection (425 -> 426)
- ABOR support. Bah.
- APPE support (why not, it was trivial). Putting off ASCII support ;-)
- Add ASCII transfer support. Bah.
- Tidy up sysutil.c, fix breakage in read_loop and write_loop.
0.0.9 packaged
--------------
- Remove ".message" from distribution. Thanks Mitchell Blank Jr
<mitch@sfgoth.com>
- Note where I can get some load testing software, thanks to Dan Kegel
<dank@alumni.caltech.edu>. I'll do that soon because I hope to waste wu-ftpd.
- Fix an Alpha build warning and check return value from final pam_end().
Reported by Solar Designer <solar@openwall.com>.
- Add xinetd.d/vsftpd, from Kurt Seifried <listuser@seifried.org>.
- Integrate comments/fixes into SECURITY documentation, thanks to Antonomasia
<ant@notatla.demon.co.uk>
- SECURITY: default tunable_chroot_local_user to 0, because it is dangerous to
give users write access to the filesystem root (think of opening trusted
files relative to the root). Thanks again Solar Designer
<solar@openwall.com>.
- Add "make install" target. Currently it is minimal!
- Clearer error message if vsftpd is started manually. Suggestion from
Tom <tom@lemuria.org>.
- Report futuristic or old (>6 months) dates in a different format, showing
the year like /bin/ls does.
- Add KERNEL-2.4.0-WARNING. Whoo-hoo. Why do all my non-trivial programs seem
to trigger kernel bugs?
- SECURITY: refuse to allow anonymous logins if some bonehead has configured
the anonymous ftp user with write access to the ftp root.
- Fix ASCII downloads so that \n UNCONDITIONALLY maps to \r\n. This behaviour
is now consistent with wu-ftpd and results in simpler code.
- Fix ASCII uploads to not to fail to strip some \r characters. Noted by
Mitchell Blank Jr <mitch@sfgoth.com>.
- Add TODO items: log transfer rate and anonymous password. Andrew Anderson
<andrew@redhat.com>.
0.0.10 packaged
---------------
- Remove errant #include <sys/sendfile.h> from sysutil.c. Noted by Jan-Frode
Myklebust <janfrode@parallab.uib.no>
- Use gettimeofday(2) not time(2), for better resolution.
- Add transfer rate to the log
- Add <limits.h> to sysutil.c, spotted by Kevin Vajk <kvajk@cup.hp.com>.
- Spell "LICENSE" correctly: Kevin Vajk <kvajk@cup.hp.com>.
- Use fcntl() for locking instead of flock() because it is much more standard.
flock() usage noted by Kevin Vajk <kvajk@cup.hp.com>.
- Use more portable IPPROTO_* instead of SOL_* (IPPROTO_IP, IPPROTO_TCP).
Thanks to Neil Blakey-Milner <nbm@mithrandr.moria.org> porting to FreeBSD.
- Start of Solaris port, thanks to Kurt Seifried <seifried@securityportal.com>
for access to a Solaris 8 box.
- Portability fix: include <netinet/in_systm.h> before <netinet/ip.h>.
- Port to Solaris 8: new directory port. New file porting_junk.h. New file
solaris_bogons.h
- Add vsf_findlibs.sh to cater for different platform link requirements. Now
builds on Solaris and Linux with "make".
- struct sockaddr casts to kill Solaris warnings.
- sysdeputil.c: remove unused variable warnings.
- sysutil.c: use _exit() instead of exit() to avoid libc doing stuff on exit.
Fixes segfault reported by Joshua Hill <josh@untruth.org>.
- Add BENCHMARKS. Many thanks to Andrew Anderson <andrew@redhat.com>.
- Fix disconnect/crash if SIGURG received whilst blocking on command stream.
- Update INSTALL with more platforms.
0.0.11 packaged
---------------
- Brag about performance in README. And why not.
- Better bail-out message if the "ftp" anonymous user isn't found
- Better bail-out message if the secure chroot directory isn't found
- Introduce tunable_one_process_model and start work on it
- Fix rare segfault on exit - race leading to infinite stack recursion
- Don't bail out if we didn't get an argv[0]. Who cares? Noted by Kurt Seifried
<seifried@securityportal.com>.
- Change logged date format to include the year.
- Add option to log in standard (wu-ftpd like) "xferlog" format.
- Cater for sendfile() returning EINTR in sysdeputil.c
- Use SO_LINGER on data sockets, to get accurate transfer rates!
- Cater for an interrupted blocking close()
- Tuning: eliminate 3 mprotect(), 1 munmap() and 1 mmap() system call per
command read.
- Prevent infinite loops calling sendfile(). Two bugs - we needed to check
the sendfile() return for 0 (doh!!) and also, we sometimes did lseek() on
a file, to beyond its end. Thanks to Daniel Veillard <Daniel.Veillard@imag.fr>
for reporting.
- Tuning: cache fd's for /etc/passwd and /etc/group to avoid syscalls.
- Tuning: "assist" the get*uid(), get*nam() calls to not make lots of useless
syscalls, if /etc/group and /etc/passwd are missing. Thanks to Daniel Veillard
<Daniel.Veillard@imag.fr> for reporting.
- Use SO_LINGER timeout of 5 mins; INT_MAX seemed to do nothing!
- Finally(!) fix transfer rate timing.
0.0.12 packaged
---------------
- Update INSTALL. Mention the config file can be given on the command line.
- Lower VSFTP_MAX_COMMAND_LINE to 4096 (wu-ftpd uses 512 I think).
- Add RedHat/vsftpd-rh7.spec, kindly provided by Emmanuel Galanos
<egalanos@anchor.net.au>.
- Add more RedHat/* spec files etc, kindly provided by Andrew Anderson
<andrew@redhat.com>.
- Cleanup: move two process model code to "twoprocess.c".
- Damn! Make the file lock _block_ if it's busy, in sysutil.c.
- Finish implementing one process model - benchmarks to follow
- Don't log success if the download is ABOR'ed during the blocking close().
- Build on systems without PAM (obviously local logins won't work..)
- Beware of FreeBSD accept() bug: ai32@drexel.edu
- Implemented a customizable ftp banner with "ftpd_banner" config file setting
- Builds on OpenBSD 2.8 - woohoo
- FreeBSD: look for libpam.so* in /usr/lib
- FreeBSD: add #include <sys/param.h> otherwise CMSG_* break.
- Kill privparent.[ch] - merged them into twoprocess.c
- Enable SIGCHLD handler _before_ forking - should nail a race which could lead
to zombies. Inspired by zombie report from Joe Klemmer <klemmerj@webtrek.com>.
- Data connection timeout code.
- ftpcmdio.c: Don't cancel the alarm when we get a command. For safety, we
insist that that the only way to "cancel" the alarm is to reset it. This
prevents hangs blocking on write() to the command stream. Of course, data
transfers are long running operations and have their own timeouts.
- Data transfer timeout now kills session.
- Take care that no writes block once we've decided to abandon ship.
- FreeBSD sendfile() support. I wonder if it works!
0.0.13 packaged
---------------
- Split out directory listing code into ls.c
- Change blocking accept() and connect() code to use select() not SIGALRM!
- Remove alarm() timeout junk from file locking in logging.c
- Cater for signals interrupting the blocking file lock
- Whoops: fix data timeout incorrectly going off. Noted and fixed by Joshua
Hill <josh@untruth.org>.
- Implement tunable_pasv_promiscuous to relax PASV IP checks. Useful if you
are playing with secure tunneling of command connection. Idea, patch from
Seth Vidal <skvidal@phy.duke.edu>.
- Much better line-by-line file reading string buffer functions.
- Use the above better functions for directory messages and config file
reading. This eliminates a probable quadratic algorithm, i.e. it's a speedup.
- Explictly free certain buffers rather than using the static trick. For
example, the config file buffer which is only used once.
- Massive cleanup and refactoring of login code.
- Add ability to specify file containing list of banned e-mail addresses for
anonymous users. Apparently a required feature for big sites trying to avoid
DDoS attacks.
- Add ability to specify file containing list of users to chroot(), request
from helo <helo@neounix.com>, who also persuaded me not to use the homedir
hack in /etc/passwd.
- Add TODO: PASV port range config setting, for firewalled setups. From Rafal
Wojtczuk <nergal@idea.avet.com.pl>.
- Rudimentary support for non-PAM local user authentication, with
encouragement and helpful discussion from D Richard Felker III
<dalias@aerifal.cx>.
- Use MAP_ANON instead of mmap() /dev/zero for anonymous pages. It saves
using a file descriptor. Neither are standard(?) but MAP_ANON seems to work
on a superset of systems compared with mmap() /dev/zero.
- Ability to specify a PASV local port range with pasv_min_port and
pasv_max_port. Request from Rafal Wojtczuk <nergal@idea.avet.com.pl>.
- Non-PAM authentication: check /etc/shells, and support shadow password and
account expiry.
- First cut at a vsftpd.conf man page! (vsftpd.conf.5)
0.0.14 packaged
---------------
- Default to ASCII mode transfers, as per RFC. Bug noted with Macintosh client
by William Day <day@chem.duke.edu>.
- Implement "ls -a".
- Implement "ls -r".
- Implement "ls -l", i.e. "NLST -L" now works
- Implement "ls -t". Superb - now the oft-used "ls -ltr" works!
- setproctitle() support - FreeBSD only in the first cut.
- setproctitle() on Linux support - what a hack! This crap really needs kernel
support. I'm ashamed I bothered.
- Repair the contributed spec files a bit, based on reports from Oleg Drokin
<green@iXcelerator.com> and Jakob Lichtenberg <jl@it-c.dk>.
- Show remote IP and local username in setproctitle() support.
- Add vsftpd.8 man page, thanks to Daniel Jacobowitz <dan@debian.org>.
- In sysdeputil.c, check macros LINUX_VERSION_CODE and KERNEL_VERSION are
defined. From James Antill <james@and.org>.
- Workaround a broken firewall that expects a very precise PASV response. We
now match wu-ftpd. Many many thanks to Jakob Lichtenberg <jl@it-c.dk> for
his help.
- If tunable_anon_world_readable_only (default), don't list directories unless
they are world readable.
- Use qsort() for directory sorting - eliminates gross quadratic sorting.
Turbo charges directory listings with 1000's of entries.
- Fix big memory leak in str_list_free().
- Simplify + reduce heap usage in strlist.c
- Optimize away lots of excessive heap usage and redundant copying in str.c
- By default, show numeric user/group id's in directory listings. Makes
generating directory listings perhaps 4 times(!) faster, and is noticeable
with e.g. 5000 entries in a directory. n.b. this performance figure is as
measured on a glibc-2.2 system, so glibc would seem to be inefficient.
- Don't use MSG_DONTWAIT - prefer the more portable fcntl()/O_NONBLOCK. Fixes
glibc-2.0 build issues.
- Work around broken Linux-2.0 unix fd passing. Now builds/runs on RH5.2.
- Build fixes for FreeBSD 3.5, with help from Jerry Walsh <jerry@aardvark.ie>.
- Only restrict directory listings to world-readable for _anonymous_ users!
Thanks again Jerry Walsh <jerry@aardvark.ie> for the report.
- Add TUNING
- Special case for security/performance: if we need _no_ privilege, then
force one process model. Security: root dropped totally straight away.
Performance: no messing around forking etc.
- Minor performance tweaks, don't leave big mappings lying around from
config file parsing.
0.0.15 packaged
---------------
- Argh. Fix SuSE 6.0 build issue (time_t used but not defined). Reported by
Peter Stern <peter@frontierflying.com>.
- Another SuSE 6.0 issue - another damn system lacking CMSG_SPACE etc.
- Cope with any return value from blocking close(2). Previously, we missed
EAGAIN, which some systems might return (not Linux).
- New wizzy synchronous signal framework, to prevent re-entrancy issues. It
presents an interface very similar to the traditional UNIX async interface.
Technically this is a security fix; imagine a SIGURG (user controllable!)
coming in whilst we are deep inside glibc. The SIGURG handler is non-trivial
and may well re-enter and upset glibc. Specific example: the malloc subsystem.
- When handing SIGURG, account the time taken under the data tranfer timeout.
- Install the command timeout handler before we write anything to the remote.
- Cleanup capabilities handling to be taken care of in secutil.c.
- Fix bug: one_process_model mode could lose supplementary groups.
- Add "SIZE" file.
- Make one_process_model work with the anon deny e-mail list.
- Massive cleanups. Start moving static state into a session structure.
- Oops - fix Solaris 8 build by fixing include order in porting_junk.h, and
include a dirfd() replacement. Noted by William Yodlowsky
<wyodlows@andromeda.rutgers.edu> and Mike Batchelor <mikebat@tmcs.net>.
- Fix return of a void function call in a void function. It upsets Sun's
compiler. (gcc is fine with it, I'm not sure if it's against the rules).
Noted by Mike Batchelor <mikebat@tmcs.net>.
- Make it possible to use port ranges starting lower than 5001, from
Matthew Kirkwood <weejock@ferret.lmh.ox.ac.uk>.
- Use a /dev/zero mmap() fallback if we do not find MAP_ANON. This should
fix the build on Solaris 2.6, 2.7 machines. Reported by Mike Batchelor
<mikebat@tmcs.net>. Also noted as one of the problems facing an IRIX build.
- Add MDTM support, so clients like ncftp can set the date on downloaded files.
- Add irix_bogons.h, trying to port to IRIX 6.5, with help from Jan-Frode
Myklebust <janfrode@parallab.uib.no>.
- Don't reference "struct msghdr.msg_flags", not all systems have it. Clear it
with vsf_sysutil_memclr() instead. Found on IRIX 6.5.11
- Cater for systems lacking getusershell(), e.g. IRIX 6.5.11, by not using it.
- Fix compiler error with header files claiming 2.4 headers but only having
2.2 headers. Reported by Ben Ricker <bricker@wellinx.com>.
- Kill warning on system without capabilities.
- Add -R option to ls (disabled by default), to cater for broken clients which
assume it is present (e.g. mirror).
- Add "Makefile.sun", from Mike Batchelor <mikebat@tmcs.net>.
- Fix PORT transfer crashes with "one_process_model". Reported by
Andrew Anderson <andrew@redhat.com>.
- Cater for HP-UX shared libraries which end in ".sl", from Kevin Vajk
<kvajk@cup.hp.com>.
- Add hpux_bogons.h, and make MAP_ANON a synonym for MAP_ANONYMOUS.
- Move send_fd and recv_fd to sysdeputil.c and provide old-style fd passing
code for IRIX and HP-UX.
- Get it going on HP-UX 11.11 and HP-UX 10.20, thanks to Kevin Vajk
<kvajk@cup.hp.com>. Minor changes to hpux_bogons.h
- Update vsftpd.conf with "ls_recurse_enable".
- Get it going on IRIX 6.5.11, thanks to Jan-Frode Myklebust
<janfrode@parallab.uib.no>.
- Fix reporting of filenames in MKD operations (regression since 0.0.15).
- Wow - lots of contributed .spec files. Adopt those from Seth Vidal
<skvidal@phy.duke.edu>.
- Fix FreeBSD build.
0.9.0 packaged
--------------
- Fix .spec files to include URL, from Seth Vidal <skvidal@phy.duke.edu>.
- Don't let unprintable characters escape into setproctitle(). Thanks to
Solar Designer for the suggestion.
- Make the PAM service name a tunable, suggestion from Solar Designer.
- Add option to log all FTP protocol (log_ftp_protocol).
- Log logins, successful or failed.
- Refuse to download a file in ASCII mode if REST position != 0. Solar
reminded me by looking in the BUGS file.
- Clearly mark an ASCII download in the FTP response string.
- Argh. Fix broken upload timeout again (goes off erroneously).
- Fix logging of FTP protocol, add logging of pid. Reported by Frank Fiamingo
<FiamingF@strsoh.org>.
- Fix bug where logging code bug()'s on the second logged operation, iff
logging is in fact disabled! Reported by Alexander Schreiber
<alexander.schreiber@informatik.tu-chemnitz.de>.
- From Solar: be paranoid about libc implementations of isprint() in sysutil.c
- Careful not to write any unprintable characters into the log.
- fchmod() files that we fchown(), to prevent suid games, etc.
- Cleanups, added comments to some headers.
- Minor speedups to some str.c string handling functions.
- Joe Klemmer <klemmerj@webtrek.com> reports zombies again! Nail a couple of
races: make the SIGCHLD handler async, and cater for an interrupted wait(2)
syscall.
- If chroot_local_user=YES then chroot_list_enable becomes a list of users to
NOT chroot(). With input from Lars Hecking <lhecking@nmrc.ie>.
0.9.1 packaged
--------------
- DAMN! Fix silly "missing newline" logging bug.
0.9.1 repackaged
----------------
- Refuse to start if local_enable and anonymous_enable are NO, hit by
Lars Hecking <lhecking@nmrc.ie>.
- Report anonymous e-mail in the LOGIN log event, idea from Joachim Blaabjerg
<styx@mailbox.as>.
- Fix man page install in vsftpd-rh7.spec, from Matthew Galgoci
<mgalgoci@redhat.com>.
- Fix chown_upload bug noted by brett <beldridg@best.com>.
- Add concept of guest user, idea from Andrew Anderson <andrew@redhat.com>.
- Simple bandwidth limitation, inspired by Mads Martin Jørgensen
<mmj@suse.com>.
- Fix chown_upload bug in a different way.
- Correct *_umask details in vsftpd.conf.5, from brett <beldridg@best.com>.
- Don't show .files unless "ls -a" was specified, n.b. this differs in
behaviour from wu-ftpd, but not proftpd.
- Implement directory write(2) buffering, for a 33% reduction in CPU used to
send big dirs. Activate the bandwidth limit on directory listings.
- HPUX enhancements: setproctitle and sendfile. Thanks to Kevin Vajk
<kvajk@cup.hp.com>.
- We DON'T need to follow symlinks on "ls -R" - phew.
- Add README.solaris. Thanks to Mike Batchelor <mikebat@tmcs.net>.
- Implement passing remote host to PAM (for pam_access etc.), thanks to
Emmanuel Galanos <egalanos@cerberus.anchor.net.au>.
- Fix guest_enable so that this means all non-anonymous users are guest users.
- Add ability to deny selected users before they get the chance to send their
cleartext password!!
- Fix FreeBSD build - use a cast instead of floor() which needs libm.
0.9.2 packaged
--------------

46
INSTALL Normal file
View File

@ -0,0 +1,46 @@
For now, just type "make" and mail me if it doesn't build.
Once built, you will need to run the binary from an inetd of some kind.
The FTP server will refuse to start up unless you satisfy a few prerequisites:
1) You will need the user "ftp" to exist and have a valid home directory.
2) You will need the user "nobody" to exist.
3) You will need an empty directory /usr/share/empty to exist.
Note that "ftp" "nobody" and "/usr/share/empty" are not hard-coded; you may
specify values for these in the config file.
If you are running vsftpd on a PAM enabled machine, you will need to have a
/etc/pam.d/ftp file present, otherwise non-anonymous logins will fail. [NOTE -
if you have an older version of PAM, that file might be /etc/pam.conf]
As well as the above three pre-requisites, you are recommended to install a
config file. The default location for the config file is /etc/vsftpd.conf.
There is a sample vsftpd.conf in the distribution tarball. You probably want
to copy that to /etc/vsftpd.conf as a basis for modification. For example,
the default configuration allows neither local user logins nor anonymous
uploads. You may wish to change these defaults.
If you have virtual hosting running, you may find it useful to specify the
config file on the command line. This is accomplished by specifying a single
command line argument which is a pathname to the config file. If using inetd
as opposed to xinetd, be careful to specify the program name argv[0] before
the config file location argv[1]!
Tested platforms (well, it builds)
- RedHat Linux 7.0
- RedHat Linux 6.1
- RedHat Linux 6.2
- RedHat Linux 5.2
- Solaris 8 / GNU tools (light testing)
- SuSE 6.4
- SuSE 6.0
- Debian 2.2
- OpenBSD 2.8
- FreeBSD 4.2
- FreeBSD 3.5
- HP-UX 11.11 / GNU tools
- HP-UX 10.20 / GNU tools
- Solaris 2.6
- IRIX 6.5.11 / GNU tools

9
KERNEL-2.4.0-WARNING Normal file
View File

@ -0,0 +1,9 @@
WARNING
-------
vsftpd seems to under some conditions kill kernels 2.4.0 and 2.4.1 dead. The
problem has been observed by several people. The problem has been reported,
and was fixed in kernel version 2.4.2-pre1.
The problem seems to trigger when vsftpd exits.

1
LICENSE Normal file
View File

@ -0,0 +1 @@
GPL v2

30
Makefile Normal file
View File

@ -0,0 +1,30 @@
# Makefile for systems with GNU tools
CC = gcc
INSTALL = install
IFLAGS = -idirafter dummyinc
CFLAGS = -O2 -Wall -W -Wshadow #-pedantic -Werror -Wconversion
LIBS = `./vsf_findlibs.sh`
LINK = -s
OBJS = main.o utility.o prelogin.o ftpcmdio.o postlogin.o privsock.o \
tunables.o ftpdataio.o secbuf.o ls.o \
postprivparent.o logging.o str.o netstr.o sysstr.o strlist.o \
dirchange.o filestr.o parseconf.o secutil.o \
ascii.o oneprocess.o twoprocess.o privops.o \
sysutil.o sysdeputil.o
.c.o:
$(CC) -c $*.c $(CFLAGS) $(IFLAGS)
vsftpd: $(OBJS)
$(CC) -o vsftpd $(OBJS) $(LINK) $(LIBS)
install:
$(INSTALL) -m 755 vsftpd /usr/sbin/vsftpd
if [ -x /etc/xinetd.d ]; then \
$(INSTALL) -m 644 xinetd.d/vsftpd /etc/xinetd.d/vsftpd; fi
clean:
rm -f *.o *.swp vsftpd

30
Makefile.sun Normal file
View File

@ -0,0 +1,30 @@
# Makefile for SUNWspro compiler and tools
# Contributed by Mike Batchelor <mikebat@electabuzz.tech.tmcs>
CC = cc
INSTALL = /usr/ucb/install
CFLAGS = -xO5
LIBS = -lsocket -lnsl -lpam
LINK = -s
OBJS = main.o utility.o prelogin.o ftpcmdio.o postlogin.o privsock.o \
tunables.o ftpdataio.o secbuf.o ls.o \
postprivparent.o logging.o str.o netstr.o sysstr.o strlist.o \
dirchange.o filestr.o parseconf.o secutil.o \
ascii.o oneprocess.o twoprocess.o privops.o \
sysutil.o sysdeputil.o
.c.o:
$(CC) -c $*.c $(CFLAGS)
vsftpd: $(OBJS)
$(CC) -o vsftpd $(OBJS) $(LINK) $(LIBS)
install:
$(INSTALL) -m 755 vsftpd /usr/sbin/vsftpd
if [ -x /etc/xinetd.d ]; then \
$(INSTALL) -m 644 xinetd.d/vsftpd /etc/xinetd.d/vsftpd; fi
clean:
rm -f *.o vsftpd

21
README Normal file
View File

@ -0,0 +1,21 @@
This is vsftpd, version 0.9.2
Author: Chris Evans
Contact: chris@scary.beasts.org
What is this?
=============
vsftpd is an FTP server, or daemon. The "vs" stands for Very Secure. Obviously
this is not a guarantee, but a reflection that I have written the entire
codebase with security in mind, and carefully designed the program to be
resilient to attack.
Recent evidence suggests that vsftpd is also extremely fast (and this is
before any explicit performance tuning!) In tests against wu-ftpd, vsftpd
was always faster, supporting over twice as many users in some tests.
Note - this is currently very much a work in progress, so some areas of the
source are a bit of a mess. Please don't take this as indicative of the
quality of my completed work ;-)
This document will be expanded in a future release!

2
README.security Normal file
View File

@ -0,0 +1,2 @@
For documentation about the security of vsftpd, please consult the files
located within the SECURITY directory.

20
README.solaris Normal file
View File

@ -0,0 +1,20 @@
Solaris specific notes
======================
Modern releases of Solaris (2.6+ ?) ship with PAM. PAM is an excellent
generic authentication framework. Unfortunately, Solaris seems to be a
little sparse in the number of PAM modules supported.
Specifically, many ftp daemon users will want to enable /etc/ftpusers control,
as well login control based on the validity of a user's shell (/etc/shells).
To perform these two tasks, pam_listfile and pam_shells are required.
Neither of these ships with Solaris.
Luckily, thanks to Mike Batchelor <mikebat@tmcs.net>, you may locate builds
of these modules at:
ftp://ftp.tmcs.net/pub/PAM-0.75-listfile_shells-sparc-5.8.tar.gz
From Mike:
"To install, just unpack it in /usr/lib/security, and edit /etc/pam.conf,
using "ftp" as the service name."

2
REWARD Normal file
View File

@ -0,0 +1,2 @@
At some stage, a reward may be offered to anyone finding a serious security
hole in vsftpd. Mail me if you have any ideas :)

76
RedHat/vsftpd-rh6.spec Normal file
View File

@ -0,0 +1,76 @@
Summary: vsftpd - Very Secure Ftp Daemon
Name: vsftpd
Version: 0.9.2
Release: rh6_1
Copyright: GPL
Group: System Environment/Daemons
URL: ftp://ferret.lmh.ox.ac.uk/pub/linux/
Source: %{name}-%{version}.tar.gz
Packager: Seth Vidal <skvidal@phy.duke.edu>
BuildRoot: /var/tmp/%{name}-%{version}-root
Requires: inetd, logrotate
Provides: ftpserver
%description
A Very Secure FTP Daemon - written from scratch - by Chris "One Man Security
Audit Team" Evans
%prep
%setup -q -n %{name}-%{version}
%build
make
%install
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/sbin
mkdir -p $RPM_BUILD_ROOT/usr/share/empty
mkdir -p $RPM_BUILD_ROOT/etc
mkdir -p $RPM_BUILD_ROOT/etc/pam.d
mkdir -p $RPM_BUILD_ROOT/etc/logrotate.d
mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man5
mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man8
install -m 755 vsftpd $RPM_BUILD_ROOT/usr/sbin/vsftpd
install -m 600 vsftpd.conf $RPM_BUILD_ROOT/etc/vsftpd.conf
install -m 644 RedHat/vsftpd.pam $RPM_BUILD_ROOT/etc/pam.d/ftp
install -m 644 vsftpd.conf.5 $RPM_BUILD_ROOT/%{_mandir}/man5/
install -m 644 vsftpd.8 $RPM_BUILD_ROOT/%{_mandir}/man8/
install -m 644 RedHat/vsftpd.log $RPM_BUILD_ROOT/etc/logrotate.d/vsftpd.log
%clean
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
/usr/sbin/vsftpd
%dir /usr/share/empty
%config /etc/vsftpd.conf
%config /etc/pam.d/ftp
%config /etc/logrotate.d/vsftpd.log
%doc INSTALL BUGS AUDIT Changelog LICENSE README README.security REWARD SPEED TODO SECURITY/ TUNING SIZE
%{_mandir}/man5/vsftpd.conf.*
%{_mandir}/man8/vsftpd.*
%changelog
* Thu Mar 22 2001 Seth Vidal <skvidal@phy.duke.edu>
- updated to 0.0.15
- added entry for vsftpd.8 man page
- added entry for vsftpd.log logrotate file
- added TUNING file to docs list
* Wed Mar 7 2001 Seth Vidal <skvidal@phy.duke.edu>
- Updated to 0.0.14
- made %files entry for man page
* Wed Feb 21 2001 Seth Vidal <skvidal@phy.duke.edu>
- Updated to 0.0.13
* Mon Feb 12 2001 Seth Vidal <skvidal@phy.duke.edu>
- Updated to 0.0.12
* Wed Feb 7 2001 Seth Vidal <skvidal@phy.duke.edu>
- updated to 0.0.11
* Fri Feb 1 2001 Seth Vidal <skvidal@phy.duke.edu>
- Update to 0.0.10
* Fri Feb 1 2001 Seth Vidal <skvidal@phy.duke.edu>
- First RPM packaging
- Stolen items from wu-ftpd's pam setup
- Separated rh 7 and rh 6.X's packages
- Built for Rh6

80
RedHat/vsftpd-rh7.spec Normal file
View File

@ -0,0 +1,80 @@
Summary: vsftpd - Very Secure Ftp Daemon
Name: vsftpd
Version: 0.9.2
Release: rh7_2
Copyright: GPL
Group: System Environment/Daemons
URL: ftp://ferret.lmh.ox.ac.uk/pub/linux/
Source: %{name}-%{version}.tar.gz
Packager: Seth Vidal <skvidal@phy.duke.edu>
BuildRoot: /var/tmp/%{name}-%{version}-root
Requires: xinetd, /etc/pam.d/system-auth, logrotate
Provides: ftpserver
%description
A Very Secure FTP Daemon - written from scratch - by Chris "One Man Security
Audit Team" Evans
%prep
%setup -q -n %{name}-%{version}
%build
make
%install
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/sbin
mkdir -p $RPM_BUILD_ROOT/usr/share/empty
mkdir -p $RPM_BUILD_ROOT/etc
mkdir -p $RPM_BUILD_ROOT/etc/xinetd.d
mkdir -p $RPM_BUILD_ROOT/etc/pam.d
mkdir -p $RPM_BUILD_ROOT%{_mandir}/man5
mkdir -p $RPM_BUILD_ROOT%{_mandir}/man8
mkdir -p $RPM_BUILD_ROOT/etc/logrotate.d
install -m 755 vsftpd $RPM_BUILD_ROOT/usr/sbin/vsftpd
install -m 600 vsftpd.conf $RPM_BUILD_ROOT/etc/vsftpd.conf
install -m 644 RedHat/vsftpd.pam $RPM_BUILD_ROOT/etc/pam.d/ftp
install -m 644 xinetd.d/vsftpd $RPM_BUILD_ROOT/etc/xinetd.d/vsftpd
install -m 644 vsftpd.conf.5 $RPM_BUILD_ROOT/%{_mandir}/man5/vsftpd.conf.5
install -m 644 vsftpd.8 $RPM_BUILD_ROOT/%{_mandir}/man8/vsftpd.8
install -m 644 RedHat/vsftpd.log $RPM_BUILD_ROOT/etc/logrotate.d/vsftpd.log
%clean
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
/usr/sbin/vsftpd
%dir /usr/share/empty
%config /etc/vsftpd.conf
%config /etc/xinetd.d/vsftpd
%config /etc/pam.d/ftp
%config /etc/logrotate.d/vsftpd.log
%{_mandir}/man5/vsftpd.conf.*
%{_mandir}/man8/vsftpd.*
%doc %attr(755,root,root)INSTALL BUGS AUDIT Changelog LICENSE README README.security REWARD SPEED TODO SECURITY/ TUNING SIZE
%changelog
* Thu Mar 22 2001 Seth Vidal <skvidal@phy.duke.edu>
- updated to 0.0.15
- added entry for vsftpd.8 man page
- added entry for vsftpd.log logrotate file
- added TUNING file to docs list
* Wed Mar 7 2001 Seth Vidal <skvidal@phy.duke.edu>
- updated to 0.0.14
- added entry for man page
* Wed Feb 21 2001 Seth Vidal <skvidal@phy.duke.edu>
- Update to 0.0.13
* Mon Feb 12 2001 Seth Vidal <skvidal@phy.duke.edu>
- Update to 0.0.12
* Wed Feb 7 2001 Seth Vidal <skvidal@phy.duke.edu>
- Update to 0.0.11
- Use vsftpd provided xinetd.d file
* Fri Feb 2 2001 Seth Vidal <skvidal@phy.duke.edu>
- Update to 0.0.10
* Thu Feb 1 2001 Seth Vidal <skvidal@phy.duke.edu>
- First RPM packaging
- Stolen items from wu-ftpd's pam setup
- Separated rh 7 and rh 6.X's packages
- fixed xinetd startup - duh!

4
RedHat/vsftpd.log Normal file
View File

@ -0,0 +1,4 @@
/var/log/vsftpd.log {
# ftpd doesn't handle SIGHUP properly
nocompress
}

6
RedHat/vsftpd.pam Normal file
View File

@ -0,0 +1,6 @@
#%PAM-1.0
auth required /lib/security/pam_listfile.so item=user sense=deny file=/etc/ftpusers onerr=succeed
auth required /lib/security/pam_pwdb.so shadow nullok
auth required /lib/security/pam_shells.so
account required /lib/security/pam_pwdb.so
session required /lib/security/pam_pwdb.so

140
SECURITY/DESIGN Normal file
View File

@ -0,0 +1,140 @@
This document explains the design goals and decisions behind vsftpd.
The importance of a secure design
=================================
In a world full of good, careful coders who do not make mistakes, a secure
design would not be necessary. After all, in the absence of any programming
errors, security would not differ no matter how the program is arranged.
Unfortunately, this is not an ideal world, and coders make plenty of mistakes.
Even the careful coders make mistakes. Code auditing is important, and goes
some way towards eliminating coding mistakes after the fact. However, we
have no guarantee that an audit will catch all the flaws.
So, a secure design acknowledges the possibility of undiscovered flaws, and
takes steps to minimise the security impact these flaws can have. An obvious
example of something we want to do is to apply the principle of least
privilege, which ensure that every part of the program runs with the privilege
it needs and no more.
An example of insecure design
=============================
Examples of insecure design may be found in most other ftpd's. That's one of
the reasons vsftpd has been written. We'll pick on wu-ftpd as a specific
example, since it is rumoured to run about half of all ftp services.
If I log on to wu-ftpd as an anonymous user, a process is run on my behalf to
serve my ftp session. Unfortunately, this process typically runs with full
root privileges on the remote machine. This means that any security flaw
present in parsing the copious ftp protocol will lead to full compromise of
that machine. Two concrete examples are the recent wu-ftpd format string bug
(June 1999), and a buffer overflow dealing with large paths a few months
beforehand.
Even OpenBSD's ftpd-BSD had a format string bug leading to remote root
compromise of the affected machine, illustrating an earlier point about the
requirement for secure design even in the presence of heavy auditing.
Secure design under UNIX
========================
vsftpd is written to run under UNIX-like operating systems, and so its secure
design is constrained by the facilities offered by UNIX. Ideally, UNIX would
have a proper security model which would offer fine grained access control
to all system interactions (files, network, etc). It doesn't, but it does
offer some useful and often overlooked facilities which help us to implement
the principle of least privilege:
- Strong inter-process communication facilities
In UNIX, the process is a strongly defined boundary. Different privilege
credentials may be assigned to different processes, which are not able to
interfere with each other. This is a very basic facility of UNIX.
It makes sense to use this facility to totally separate parts of a program
which do not need to be privileged (most) from those parts that do (typically
minimal).
The privileged and unprivileged parts of the program then communicate via
one of many UNIX IPC mechanisms - perhaps a socketpair or IPC (the former
is attractive because UNIX lets you pass file handles over a socket).
The minimal privileged process exercises the "principle of distrust" - it
carefully filters what the unprivileged process asks it to do, so that even
if the unprivileged process is compromised, it cannot ask the privileged
process to do anything we don't want to allow.
- chroot()
chroot() is an often overlooked but useful tool. It can be used very
effectively as a damage limitation tool.
Imagine a remotely compromised process which does not run as root, but also
does not use chroot(). Now look at what the attacker can do. Amongst the worst
items are pilfering of all publicly readable files, and also attempting to
execute any publicly executable suid-root programs to try and elevate
privilege.
Now imaging the same compromised process with a chroot() to an empty directory.
The attackers options to do unpleasant things are substantially diminished.
No, chroot() is not the ideal way to do what we have just accomplished, but
it is what we have got to work with. In an ideal environment with fine
grained security, we would default to having access to _no_ files at all, and
deliberately not ask for access to any.
- Capabilities (Linux 2.2+)
Like chroot(), capabilities are essentially a damage limitation excercise.
They are also much less widespread than the other UNIX facilities detailled
above. Nonetheless, they warrant mentioning because Linux has them, and they
are used in vsftpd because that is the primary devlopment platform.
Capabilities split up the all powerful root privilege into lots of sometimes
orthogonal privileges. Some of the capabilities represent privileges which
are often the basis for requiring a program to run with full root privileges.
Examples include CAP_NET_RAW (ping, traceroute) and CAP_NET_BIND_SERVICE
(rlogin).
By using capabilities to ensure we only have the privilege we need (within
the somewhat disappointing granularity they offer), we again limit the
potential damage of security holes.
Presenting vsftpd's secure design
=================================
vsftpd employs a secure design. The UNIX facilities outlined above are used
to good effect. The design decisions taken are as follows:
1) All parsing and acting on potentially malicious remote network data is
done in a process running as an unprivileged user. Furthermore, this process
runs in a chroot() jail, ensuring only the ftp files area is accessible.
2) Any privileged operations are handled in a privileged parent process. The
code for this privileged parent process is as small as possible for safety.
3) This same privileged parent process receives requests from the unprivileged
child over a socket. All requests are distrusted. Here are example requests:
- Login request. The child sends username and password. Only if the details
are correct does the privileged parent launch a new child with the appropriate
user credentials.
- chown() request. The child may request a recently uploaded file gets
chown'ed() to root for security purposes. The parent is careful to only allow
chown() to root, and only from files owned by the ftp user.
- Get privileged socket request. The ftp protocol says we are supposed to
emit data connections from port 20. This requires privilege. The privileged
parent process creates the privileged socket and passes it to child over
the socket.
4) This same privileged parent process makes use of capabilities and chroot(),
to run with the least privilege required. After login, depending on what
options have been selected, the privileged parent dynamically calculates what
privileges it requires. In some cases, this amounts to no privilege, and the
privileged parent just exits, leaving no part of vsftpd running with
privilege.
Comments on this document are welcomed.

44
SECURITY/IMPLEMENTATION Normal file
View File

@ -0,0 +1,44 @@
This document details a few steps and decisions taken to ensure vsftpd is free
of common implementation flaws.
Tackling the buffer overflow
============================
Probably the most common implementation flaw causing security problems is the
buffer overflow. Buffer overflows come in many shapes and sizes - overflows
onto the stack, overflows off the end of dynamically malloc()'ed areas,
overflows into static data areas. They range from easy to spot (where a user
can put an arbitrary length string into a fixed size buffer), to very
difficult to spot - buffer size miscalculations or single byte overflows. Or
convoluted code where the buffer's definition and various usages are far
apart.
The problem is that people insist on replicating buffer size handling code
and buffer size security checks many times (or, of course, they omit size
checks altogther). It is little surprise, then, that sometimes errors creep
in to the checks.
The correct solution is to hide the buffer handling code behind an API. All
buffer allocating, copying, size calculations, extending, etc. are done by
a single piece of generic code. The size security checks need to be written
once. You can concentrate on getting this one instance of code correct.
From the client's point of view, they are no longer dealing with a buffer. The
buffer is encapsulated within the buffer API. All modifications to the buffer
safely go through the API. If this sounds familiar, it is because what vsftpd
implements is very similar to a C++ string class. You can do OO programming
in C too, you know ;-)
A key point of having the buffer API is place is that is it MORE DIFFICULT to
abuse the API than it is to use it properly. Try and create a buffer memory
corruption or overflow scenario using just the buffer API.
Unfortunately, secure string/buffer usage through a common API has not caught
on much, despite the benefits it brings. Is it under publicised as a solution?
Or do people have too much sentimental attachment to strcpy(), strlen(),
malloc(), strcat() etc? Of notable exception, it is my understanding that at
least the rather secure qmail program uses secure buffer handling, and I'd
expect that to extend to all Dan Bernstein software. (Let me know of other good
examples).

12
SECURITY/OVERVIEW Normal file
View File

@ -0,0 +1,12 @@
The documents in this directory contain information about the security of
vsftpd. They explain why various aspects of vsftpd were coded the way they
are.
File Contents
DESIGN Comments on the overall architecture of vsftpd, from a
security standpoint.
IMPLEMENTATION Comments on steps taken to ensure a secure implementation.
TRUST Comments on external components trusted or distrusted by
vsftpd.

111
SECURITY/TRUST Normal file
View File

@ -0,0 +1,111 @@
This document describes what the vsftpd code trusts, what it doesn't trust, and
the reasoning behind any trust decisions.
The importance of trust and trust relationships
===============================================
Imagine a largely well written and secure piece of code. Now imagine that this
piece of code delegates a task to an external program, perhaps in the name of
code reuse. Now, if this external program is sloppily coded and insecure, we've
wasted a lot of effort making our original program secure; our erroneous trust
of the buggy external program means we have a security leak, even though we
were careful in _our_ code.
There is a very similar situation with buggy library APIs. Imagine our secure
program calling some complex library function which lets the side down by
containing a security hole.
Lets put some concrete examples on the two similar above considerations. We can
even give examples in the context of FTP daemons.
1) External /bin/ls helper
A very common operation asked of FTP servers is to provide a directory listing.
Unfortunately, convention seems to be to emit the directory listing in UNIX
"/bin/ls -l" format. Even the Microsoft FTP service can be observed to do this.
When writing an FTP server for the UNIX platform, then, this leads to the
temptation to reuse /bin/ls as a child process, to avoid having to rewrite a
load of code to handle directory listings.
Even more unfortunately, FTP server writers seem to want to adopt the
versatility of the average /bin/ls implementation. This means they allow
clients to specify arbitrary parameters to /bin/ls.
By using an external /bin/ls command, we would tie the security of our FTP
server to that of the /bin/ls code. Be careful not to underestimate the amount
of code paths in /bin/ls which are explorable by a remote malicious user. GNU
/bin/ls has a myriad of options. Some of these options are complex such as -I
or the various formatting options. All it takes is a single coding flaw in the
handling of one of these options, and your FTP security is in trouble.
By using an external /bin/ls, you also inherit the risk of any dangerous or
complex APIs it uses. For example, calls to libc's complex fnmatch() or
glob() functions, which will get given arbitrary malicious user controlled
data as the search patterns. Also remember that users (and sometimes remote
users) can upload/create files, and filenames are a very prominent input
to /bin/ls.
To conclude: vsftpd has no intention of using an external /bin/ls program
because of the risks outlined above. Even if I were to audit e.g. GNU
fileutils /bin/ls, and also important parts of glibc, this would still leave
security in an unknown state on other platforms. The solution I have employed
is to write a minimal internal implementation of a /bin/ls listing generator;
it's hardly difficult. As a happy side effect, this will boost performance by
avoiding unneccesary fork()s and exec()s!
Here's some quick data about FTP servers which tend to use external ls
programs:
ftp.wuftpd.org:
ftp> ls --version
227 Entering Passive Mode (x.x.x.x.x.x)
150 Opening ASCII mode data connection for /bin/ls.
ls (GNU fileutils) 3.16
226 Transfer complete.
ftp.digital.com:
ftp> ls -v
227 Entering Passive Mode (x.x.x.x.x.x)
150 Opening ASCII mode data connection for /bin/ls.
/bin/ls: illegal option -- v
usage: ls [ -1ACFLRabcdfgilmnopqrstux ] [files]
226 Transfer complete.
Note that /bin/ls is not the only external program invoked by common FTP
servers such as wu-ftpd. wu-ftpd also has the ability to invoke "tar" and
"gzip" on the fly, so there are trust relationships there too.
2) Complex library APIs
vsftpd is very careful to avoid using library calls which are potentially
dangerous. I would typically classify calls as dangerous if they interact
with the network non-trivially, or take malicious user supplied data and
start parsing it in a major way.
Some examples are clearly required (vsftpd avoids using any of the following):
1) fnmatch(). This is the libc glob pattern matcher. The danger comes
from the fact that the user supplies the glob pattern - "ls *.mp3" would
be a simple example. Furthermore, glob pattern matching is complex and
involves a lot of string handling.
2) gethostbyaddr(). This is a libc call to resolve an IP address to a hostname.
Unfortunately, doing this is quite complicated. When you call gethostbyaddr(),
a lot of work goes on under the covers. This usually involves making a network
call out to the DNS server, and, dangerously, parsing the response.
For clarity (and clarity is a very important part of security), all external
APIs used by vsftpd are encapsulated within two "system interaction" files,
named "sysutil.c", and "sysdeputil.c" (for the more variable/system dependent
calls). This provides a convenient audit point for ascertaining which calls
vsftpd trusts.
Summary
=======
Be very aware of what APIs and/or programs you are trusting, or you might end
up creating a trust relationship which makes your program exploitable --
through no direct fault of your own.

8
SIZE Normal file
View File

@ -0,0 +1,8 @@
I'm not sure what you expected to find in this file :-)
Anyway, this is to explain that vsftpd is not as much code as you might
expect from running a command like "wc -l *.c". Why? Simply because I use
a very verbose style of coding in vsftpd, which consumes a lot of lines.
Verbose code is very important in a secure program. How can you verify a
program's security if it is not readable?

35
SPEED Normal file
View File

@ -0,0 +1,35 @@
This FTPd should be very performant. The reasons for this are below, followed
by specific benchmarks as and when I get them.
1) Generally, it is a fairly minimal FTPd. There should not be much code and/or
syscall bloat.
2) For binary downloads, Linux sendfile() is used. This is a lot lighter on
CPU/syscall usage than your regular read()/write() loop.
3) The "ls" command is fully internal. That is to say, an external "ls" command
does not need to be launch. Launching an external process is costly because
of the fork(), exec(), ELF loader startup, etc.
It is not all good news, of course. Potential sources of poor performance
include
1) Overhead of two processes per session (in common configurations).
2) Excessive heap usage hidden behind the string API.
BENCHMARKS
==========
1) vsftpd downloads ASCII data at at least twice the rate of wu-ftpd.
2) vsftpd has achieved 86Mbyte/sec download over Gigabit ethernet between
Linux-2.4.x boxes (thanks to sendfile())
3) vsftpd has smaller virtual memory usage (and RSS, it seems)
4) Various reports have trickled in and indicate that vsftpd thumps wu-ftpd
in performance tests.

27
TODO Normal file
View File

@ -0,0 +1,27 @@
CRITICAL
========
NOT SO CRITICAL
===============
- IPv6 support
- "make install" should install man pages
ON THE BACK BURNER
==================
- "Minimal" build support
- Small race: signal might come in just before we start a blocking call
- Support for "welcome.msg" on initial connection
- wtmp support
- OpenSSL support
- transparent compression support
- transparent tar support
NOT PLANNED
===========
- syslog() support - I don't want to encourage the broken beast
- telnet strings (no demand)
- better pattern matching in "ls" (no demand)
- standalone support (no demand, connections not a bottleneck)

26
TUNING Normal file
View File

@ -0,0 +1,26 @@
So, you want vsftpd to go quickly?
Here are some random assorted performance tips.
1) vsftpd thrives because of its lightweight RSS and vm usage. If you run
a glibc based system (e.g. RedHat 5+), look in /etc/nsswitch.conf, and
if possible, disable the "nis" and "nisplus" options for "passwd", "shadow"
and "group". This prevents unneeded runtime libraries being added into
the vsftpd virtual memory space.
2) vsftpd will attempt to save CPU power by using sendfile() on capable
operating systems. Currently, Linux 2.2+ and FreeBSD 3.0+ use sendfile().
Consider running on these excellent operating systems.
3) Irritated by vsftpd using _two_ processes per connection? Don't be, it's
a very secure architecture. However, if you run Linux 2.4+, or Linux 2.2.19, a
"one process" security model is possible thanks to nifty security features.
See the vsftpd.conf man page.
4) Avoid large directories (e.g. thousands of entries) if possible. Many
filesystems do not handle such cases efficiently at all. Preparing large
directory listings will require vsftpd to use moderate amounts of memory
and CPU. If you _must_ have large directories, consider either making them
unreadable, or use a filesystem which copes well with large directories such
as reiserfs.

54
ascii.c Normal file
View File

@ -0,0 +1,54 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* ascii.c
*
* Routines to handle ASCII mode tranfers. Yuk.
*/
unsigned int
vsf_ascii_ascii_to_bin(const char* p_in, char* p_out, unsigned int in_len)
{
/* Task: translate all \r\n into plain \n
* For simplicity, I'm cheating and just ripping out all \r. If someone
* complains about it breaking something, it'll get fixed.
*/
unsigned int index = 0;
unsigned int written = 0;
while (index < in_len)
{
char the_char = p_in[index];
if (the_char != '\r')
{
*p_out++ = the_char;
written++;
}
index++;
}
return written;
}
unsigned int
vsf_ascii_bin_to_ascii(const char* p_in, char* p_out, unsigned int in_len)
{
/* Task: translate all \n into \r\n. Note that \r\n becomes \r\r\n. That's
* what wu-ftpd does, and it's easier :-)
*/
unsigned int index = 0;
unsigned int written = 0;
while (index < in_len)
{
char the_char = p_in[index];
if (the_char == '\n')
{
*p_out++ = '\r';
written++;
}
*p_out++ = the_char;
written++;
index++;
}
return written;
}

37
ascii.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef VSFTP_ASCII_H
#define VSFTP_ASCII_H
struct mystr;
/* vsf_ascii_ascii_to_bin()
* PURPOSE
* This function converts an input buffer from ascii format to binary format.
* This entails ripping out all occurences of '\r'. The result is stored in
* "p_out".
* PARAMETERS
* p_in - the input buffer, which is not modified
* p_out - the output buffer, which MUST BE at least as big as "in_len"
* in_len - the length in bytes of the input buffer
* RETURNS
* The number of characters stored in the output buffer.
*/
unsigned int vsf_ascii_ascii_to_bin(const char* p_in, char* p_out,
unsigned int in_len);
/* vsf_ascii_bin_to_ascii()
* PURPOSE
* This function converts an input buffer from binary format to ascii format.
* This entails replacing all occurences of '\n' with '\r\n'. The result is
* stored in "p_out".
* PARAMETERS
* p_in - the input buffer, which is not modified
* p_out - the output buffer, which MUST BE at least TWICE as big as
* "in_len"
* in_len - the length in bytes of the input buffer
* RETURNS
* The number of characters stored in the output buffer
*/
unsigned int vsf_ascii_bin_to_ascii(const char* p_in, char* p_out,
unsigned int in_len);
#endif /* VSFTP_ASCII_H */

20
defs.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef VSF_DEFS_H
#define VSF_DEFS_H
#define VSFTP_DEFAULT_CONFIG "/etc/vsftpd.conf"
#define VSFTP_COMMAND_FD 0
#define VSFTP_PASSWORD_MAX 128
#define VSFTP_USERNAME_MAX 32
#define VSFTP_MAX_COMMAND_LINE 4096
#define VSFTP_PRIVSOCK_MAXSTR 1024
#define VSFTP_DATA_BUFSIZE 65536
#define VSFTP_DIR_BUFSIZE 16384
#define VSFTP_PATH_MAX 4096
#define VSFTP_CONF_FILE_MAX 100000
#define VSFTP_SECURE_UMASK 077
#endif /* VSF_DEFS_H */

69
dirchange.c Normal file
View File

@ -0,0 +1,69 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* dirchange.c
*
* Calls exposed to handle the junk a typical FTP server has to do upon
* entering a new directory (messages, etc).
*/
#include "dirchange.h"
#include "strlist.h"
#include "str.h"
#include "sysstr.h"
#include "tunables.h"
#include "ftpcmdio.h"
#include "filestr.h"
#include "session.h"
#include "sysutil.h"
/* Definitions */
#define VSFTP_MAX_VISIT_REMEMBER 100
#define VSFTP_MAX_MSGFILE_SIZE 1000
void
dir_changed(struct vsf_session* p_sess, int ftpcode)
{
struct mystr dir_str = INIT_MYSTR;
/* Do nothing if .message support is off */
if (!tunable_dirmessage_enable)
{
return;
}
if (p_sess->p_visited_dir_list == 0)
{
struct mystr_list the_list = INIT_STRLIST;
p_sess->p_visited_dir_list = vsf_sysutil_malloc(sizeof(struct mystr_list));
*p_sess->p_visited_dir_list = the_list;
}
str_getcwd(&dir_str);
/* Do nothing if we already visited this directory */
if (!str_list_contains_str(p_sess->p_visited_dir_list, &dir_str))
{
/* Just in case, cap the max. no of visited directories we'll remember */
if (str_list_get_length(p_sess->p_visited_dir_list) <
VSFTP_MAX_VISIT_REMEMBER)
{
str_list_add(p_sess->p_visited_dir_list, &dir_str, 0);
}
/* If we have a .message file, squirt it out prepended by the ftpcode and
* the continuation mark '-'
*/
{
struct mystr msg_file_str = INIT_MYSTR;
struct mystr msg_line_str = INIT_MYSTR;
unsigned int str_pos = 0;
(void) str_fileread(&msg_file_str, tunable_message_file,
VSFTP_MAX_MSGFILE_SIZE);
while (str_getline(&msg_file_str, &msg_line_str, &str_pos))
{
vsf_cmdio_write_str_hyphen(p_sess, ftpcode, &msg_line_str);
}
str_free(&msg_file_str);
str_free(&msg_line_str);
}
}
str_free(&dir_str);
}

20
dirchange.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef VSF_DIRCHANGE_H
#define VSF_DIRCHANGE_H
struct vsf_session;
/* dir_changed()
* PURPOSE
* This function, when called, will check if the current directory has just
* been entered for the first time in this session. If so, and message file
* support is on, a message file is looked for (default .message), and output
* to the FTP control connection with the FTP code prefix specified by
* "ftpcode".
* PARAMETERS
* p_sess - the current FTP session object
* ftpcode - the FTP code to show with the message
*/
void dir_changed(struct vsf_session* p_sess, int ftpcode);
#endif /* VSF_DIRCHANGE_H */

View File

@ -0,0 +1,6 @@
#ifndef VSF_DUMMYINC_PAM_APPL_H
#define VSF_DUMMYINC_PAM_APPL_H
#undef VSF_SYSDEP_HAVE_PAM
#endif /* VSF_DUMMYINC_PAM_APPL_H */

7
dummyinc/shadow.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef VSF_DUMMYINC_SHADOW_H
#define VSF_DUMMYINC_SHADOW_H
#undef VSF_SYSDEP_HAVE_SHADOW
#endif /* VSF_DUMMYINC_SHADOW_H */

54
filestr.c Normal file
View File

@ -0,0 +1,54 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* filestr.c
*
* This file contains extensions to the string/buffer API, to load a file
* into a buffer.
*/
#include "filestr.h"
/* Get access to "private" functions */
#define VSFTP_STRING_HELPER
#include "str.h"
#include "sysutil.h"
#include "secbuf.h"
int
str_fileread(struct mystr* p_str, const char* p_filename, unsigned int maxsize)
{
int fd;
int retval;
unsigned long size;
char* p_sec_buf = 0;
struct vsf_sysutil_statbuf* p_stat = 0;
/* In case we fail, make sure we return an empty string */
str_empty(p_str);
fd = vsf_sysutil_open_file(p_filename, kVSFSysUtilOpenReadOnly);
if (vsf_sysutil_retval_is_error(fd))
{
return fd;
}
vsf_sysutil_fstat(fd, &p_stat);
if (vsf_sysutil_statbuf_is_regfile(p_stat))
{
size = vsf_sysutil_statbuf_get_size(p_stat);
if (size > maxsize)
{
size = maxsize;
}
vsf_secbuf_alloc(&p_sec_buf, (unsigned int) size);
retval = vsf_sysutil_read_loop(fd, p_sec_buf, (unsigned int) size);
if (!vsf_sysutil_retval_is_error(retval) && (unsigned int) retval == size)
{
str_alloc_memchunk(p_str, p_sec_buf, size);
}
}
vsf_sysutil_free(p_stat);
vsf_secbuf_free(&p_sec_buf);
vsf_sysutil_close(fd);
return 0;
}

26
filestr.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef VSF_FILESTR_H
#define VSF_FILESTR_H
/* Forward declares */
struct mystr;
/* str_fileread()
* PURPOSE
* Read the contents of a file into a string buffer, up to a size limit of
* "maxsize"
* PARAMETERS
* p_str - destination buffer object to contain the file
* p_filename - the filename to try and read into the buffer
* maxsize - the maximum amount of buffer we will fill. Larger files will
* be truncated.
* RETURNS
* An integer representing the success/failure of opening the file
* "p_filename". Zero indicates success. If successful, the file is read into
* the "p_str" string object. If not successful, "p_str" will point to an
* empty buffer.
*/
int str_fileread(struct mystr* p_str, const char* p_filename,
unsigned int maxsize);
#endif /* VSF_FILESTR_H */

186
ftpcmdio.c Normal file
View File

@ -0,0 +1,186 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* ftpcmdio.c
*
* Routines applicable to reading and writing the FTP command stream.
*/
#include "ftpcmdio.h"
#include "ftpcodes.h"
#include "str.h"
#include "netstr.h"
#include "sysutil.h"
#include "tunables.h"
#include "defs.h"
#include "secbuf.h"
#include "utility.h"
#include "logging.h"
#include "session.h"
/* Internal functions */
static void ftp_getline(struct mystr* p_str);
static void ftp_write_text_common(struct vsf_session* p_sess, int status,
const char* p_text, int noblock);
static void ftp_write_str_common(struct vsf_session* p_sess, int status,
char sep, const struct mystr* p_str,
int noblock);
static void handle_alarm_timeout(void* p_private);
void
vsf_cmdio_sock_setup(void)
{
vsf_sysutil_activate_keepalive(VSFTP_COMMAND_FD);
vsf_sysutil_set_nodelay(VSFTP_COMMAND_FD);
}
static void
handle_alarm_timeout(void* p_private)
{
struct vsf_session* p_sess = (struct vsf_session*) p_private;
vsf_cmdio_write_noblock(p_sess, FTP_IDLE_TIMEOUT, "Timeout. Pay attention.");
vsf_sysutil_exit(0);
}
void
vsf_cmdio_write(struct vsf_session* p_sess, int status, const char* p_text)
{
ftp_write_text_common(p_sess, status, p_text, 0);
}
void
vsf_cmdio_write_noblock(struct vsf_session* p_sess, int status,
const char* p_text)
{
ftp_write_text_common(p_sess, status, p_text, 1);
}
static void
ftp_write_text_common(struct vsf_session* p_sess, int status,
const char* p_text, int noblock)
{
/* XXX - could optimize */
static struct mystr s_the_str;
str_alloc_text(&s_the_str, p_text);
ftp_write_str_common(p_sess, status, ' ', &s_the_str, noblock);
}
void
vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status,
const struct mystr* p_str)
{
ftp_write_str_common(p_sess, status, '-', p_str, 0);
}
void
vsf_cmdio_write_str(struct vsf_session* p_sess, int status,
const struct mystr* p_str)
{
ftp_write_str_common(p_sess, status, ' ', p_str, 0);
}
static void
ftp_write_str_common(struct vsf_session* p_sess, int status, char sep,
const struct mystr* p_str, int noblock)
{
static struct mystr s_write_buf_str;
static struct mystr s_text_mangle_str;
if (tunable_log_ftp_protocol)
{
str_alloc_ulong(&s_write_buf_str, (unsigned long) status);
str_append_char(&s_write_buf_str, sep);
str_append_str(&s_write_buf_str, p_str);
vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_write_buf_str);
}
str_copy(&s_text_mangle_str, p_str);
/* Process the output response according to the specifications.. */
/* Escape telnet characters properly */
str_replace_text(&s_text_mangle_str, "\377", "\377\377");
/* Change \n for \0 in response */
str_replace_char(&s_text_mangle_str, '\n', '\0');
/* Build string to squirt down network */
str_alloc_ulong(&s_write_buf_str, (unsigned long) status);
str_append_char(&s_write_buf_str, sep);
str_append_str(&s_write_buf_str, &s_text_mangle_str);
str_append_text(&s_write_buf_str, "\r\n");
if (noblock)
{
(void) str_netfd_write_noblock(&s_write_buf_str, VSFTP_COMMAND_FD);
}
else
{
int retval = str_netfd_write(&s_write_buf_str, VSFTP_COMMAND_FD);
if (retval != 0)
{
die("str_netfd_write");
}
}
}
void
vsf_cmdio_set_alarm(struct vsf_session* p_sess)
{
if (tunable_idle_session_timeout > 0)
{
vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM, handle_alarm_timeout,
p_sess);
vsf_sysutil_set_alarm(tunable_idle_session_timeout);
}
}
void
vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str,
struct mystr* p_arg_str, int set_alarm)
{
/* Prepare an alarm to timeout the session.. */
if (set_alarm)
{
vsf_cmdio_set_alarm(p_sess);
}
/* Blocks */
ftp_getline(p_cmd_str);
str_split_char(p_cmd_str, p_arg_str, ' ');
str_upper(p_cmd_str);
if (tunable_log_ftp_protocol)
{
static struct mystr s_log_str;
if (str_equal_text(p_cmd_str, "PASS"))
{
str_alloc_text(&s_log_str, "PASS <password>");
}
else
{
str_copy(&s_log_str, p_cmd_str);
if (!str_isempty(p_arg_str))
{
str_append_char(&s_log_str, ' ');
str_append_str(&s_log_str, p_arg_str);
}
}
vsf_log_line(p_sess, kVSFLogEntryFTPInput, &s_log_str);
}
}
static void
ftp_getline(struct mystr* p_str)
{
static char* s_p_readline_buf;
if (s_p_readline_buf == 0)
{
vsf_secbuf_alloc(&s_p_readline_buf, VSFTP_MAX_COMMAND_LINE);
}
str_netfd_alloc(p_str, VSFTP_COMMAND_FD, '\n', s_p_readline_buf,
VSFTP_MAX_COMMAND_LINE);
/* As mandated by the FTP specifications.. */
str_replace_char(p_str, '\0', '\n');
/* If the last character is a \r, strip it */
{
unsigned int len = str_getlen(p_str);
if (len > 0 && str_get_char_at(p_str, len - 1) == '\r')
{
str_trunc(p_str, len - 1);
}
}
}

75
ftpcmdio.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef VSF_FTPCMDIO_H
#define VSF_FTPCMDIO_H
struct mystr;
struct vsf_session;
/* vsf_cmdio_sock_setup()
* PURPOSE
* Initialise a few socket settings (keepalive, nonagle, etc). on the FTP
* control connection.
*/
void vsf_cmdio_sock_setup(void);
/* vsf_cmdio_write()
* PURPOSE
* Write a response to the FTP control connection.
* PARAMETERS
* p_sess - the current session object
* status - the status code to report
* p_text - the text to report
*/
void vsf_cmdio_write(struct vsf_session* p_sess, int status,
const char* p_text);
/* vsf_cmdio_write_noblock()
* PURPOSE
* The same as vsf_cmdio_write(), apart from the fact we _guarantee_ not to
* block (ditching output if neccessary). This is useful for messages as
* we exit, to avoid getting stuck on exit.
*/
void vsf_cmdio_write_noblock(struct vsf_session* p_sess, int status,
const char* p_text);
/* vsf_cmdio_write_str()
* PURPOSE
* The same as vsf_cmdio_write(), apart from the text is specified as a
* string buffer object "p_str".
*/
void vsf_cmdio_write_str(struct vsf_session* p_sess, int status,
const struct mystr* p_str);
/* vsf_cmdio_write_str_hyphen()
* PURPOSE
* The same as vsf_cmdio_write_str(), apart from the response line is
* output with the continuation indicator '-' between the response code and
* the response text. This indicates there are more lines of response.
*/
void vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status,
const struct mystr* p_str);
/* vsf_cmdio_set_alarm()
* PURPOSE
* Activate the control connection inactivity timeout. This is explicitly
* exposed in the API so that we can play it safe, and activate the alarm
* before _any_ potentially blocking calls.
* PARAMETERS
* p_sess - The current session object
*/
void vsf_cmdio_set_alarm(struct vsf_session* p_sess);
/* vsf_cmdio_get_cmd_and_arg()
* PURPOSE
* Read an FTP command (and optional argument) from the FTP control connection.
* PARAMETERS
* p_sess - The current session object
* p_cmd_str - Where to put the FTP command string (may be empty)
* p_arg_str - Where to put the FTP argument string (may be empty)
* set_alarm - If true, the control connection inactivity monitor is used
*/
void vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess,
struct mystr* p_cmd_str,
struct mystr* p_arg_str, int set_alarm);
#endif /* VSF_FTPCMDIO_H */

46
ftpcodes.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef VSF_FTPCODES_H
#define VSF_FTPCODES_H
#define FTP_DATACONN 150
#define FTP_NOOPOK 200
#define FTP_TYPEOK 200
#define FTP_PORTOK 200
#define FTP_UMASKOK 200
#define FTP_CHMODOK 200
#define FTP_SIZEOK 213
#define FTP_MDTMOK 213
#define FTP_SYSTOK 215
#define FTP_GREET 220
#define FTP_GOODBYE 221
#define FTP_ABOR_NOCONN 225
#define FTP_TRANSFEROK 226
#define FTP_ABOROK 226
#define FTP_PASVOK 227
#define FTP_LOGINOK 230
#define FTP_CWDOK 250
#define FTP_RMDIROK 250
#define FTP_DELEOK 250
#define FTP_RENAMEOK 250
#define FTP_PWDOK 257
#define FTP_MKDIROK 257
#define FTP_GIVEPWORD 331
#define FTP_RESTOK 350
#define FTP_RNFROK 350
#define FTP_IDLE_TIMEOUT 421
#define FTP_DATA_TIMEOUT 421
#define FTP_BADSENDCONN 425
#define FTP_BADSENDNET 426
#define FTP_BADSENDFILE 451
#define FTP_BADCMD 500
#define FTP_BADHELP 502
#define FTP_NEEDUSER 503
#define FTP_NEEDRNFR 503
#define FTP_LOGINERR 530
#define FTP_FILEFAIL 550
#define FTP_UPLOADFAIL 553
#endif /* VSF_FTPCODES_H */

552
ftpdataio.c Normal file
View File

@ -0,0 +1,552 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* ftpdataio.c
*
* Code to handle FTP data connections. This includes both PORT (server
* connects) and PASV (client connects) modes of data transfer. This
* includes sends and receives, files and directories.
*/
#include "ftpdataio.h"
#include "session.h"
#include "ftpcmdio.h"
#include "ftpcodes.h"
#include "utility.h"
#include "tunables.h"
#include "defs.h"
#include "str.h"
#include "strlist.h"
#include "sysutil.h"
#include "logging.h"
#include "secbuf.h"
#include "sysstr.h"
#include "sysdeputil.h"
#include "ascii.h"
#include "oneprocess.h"
#include "twoprocess.h"
#include "ls.h"
#include "netstr.h"
static void init_data_sock_params(struct vsf_session* p_sess, int sock_fd);
static struct vsf_transfer_ret do_file_send_binary(struct vsf_session* p_sess,
int net_fd, int file_fd);
static struct vsf_transfer_ret do_file_send_ascii(struct vsf_session* p_sess,
int net_fd, int file_fd);
static struct vsf_transfer_ret do_file_recv(
struct vsf_session* p_sess, int net_fd, int file_fd, int is_ascii);
static void handle_sigalrm(void* p_private);
static void start_data_alarm(struct vsf_session* p_sess);
static void handle_io(int retval, int fd, void* p_private);
static int transfer_dir_internal(
struct vsf_session* p_sess, const int remote_fd,
struct vsf_sysutil_dir* p_dir, const struct mystr* p_base_dir_str,
const struct mystr* p_option_str, const struct mystr* p_filter_str,
int is_verbose, int is_recurse);
static int write_dir_list(struct mystr_list* p_dir_list, int remote_fd);
void
vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess)
{
int retval;
if (p_sess->data_fd == -1)
{
bug("no data descriptor in vsf_ftpdataio_dispose_transfer_fd");
}
/* Clear the data connection alarm */
vsf_sysutil_clear_alarm();
vsf_sysutil_uninstall_io_handler();
/* This close() blocks because we set SO_LINGER */
retval = vsf_sysutil_close_failok(p_sess->data_fd);
if (vsf_sysutil_retval_is_error(retval))
{
/* Do it again without blocking. */
vsf_sysutil_deactivate_linger(p_sess->data_fd);
vsf_sysutil_close(p_sess->data_fd);
}
p_sess->data_fd = -1;
}
int
vsf_ftpdataio_get_pasv_fd(struct vsf_session* p_sess)
{
static struct vsf_sysutil_sockaddr* s_p_accept_addr = 0;
struct vsf_sysutil_ipv4addr cmd_conn_addr;
struct vsf_sysutil_ipv4addr remote_addr;
int remote_fd = vsf_sysutil_accept_timeout(p_sess->pasv_listen_fd,
&s_p_accept_addr,
tunable_accept_timeout);
if (vsf_sysutil_retval_is_error(remote_fd))
{
vsf_cmdio_write(p_sess, FTP_BADSENDCONN,
"Failed to establish connection.");
return remote_fd;
}
/* SECURITY:
* Reject the connection if it wasn't from the same IP as the
* control connection.
*/
cmd_conn_addr = vsf_sysutil_sockaddr_get_ipaddr(p_sess->p_remote_addr);
remote_addr = vsf_sysutil_sockaddr_get_ipaddr(s_p_accept_addr);
if (!tunable_pasv_promiscuous)
{
if (vsf_sysutil_memcmp(cmd_conn_addr.data, remote_addr.data,
sizeof(cmd_conn_addr)) != 0)
{
vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Security: Bad IP connecting.");
vsf_sysutil_close(remote_fd);
return -1;
}
}
init_data_sock_params(p_sess, remote_fd);
return remote_fd;
}
int
vsf_ftpdataio_get_port_fd(struct vsf_session* p_sess)
{
int retval;
int remote_fd;
if (tunable_connect_from_port_20)
{
if (tunable_one_process_model)
{
remote_fd = vsf_one_process_get_priv_data_sock(p_sess);
}
else
{
remote_fd = vsf_two_process_get_priv_data_sock(p_sess);
}
}
else
{
remote_fd = vsf_sysutil_get_ipv4_sock();
}
retval = vsf_sysutil_connect_timeout(remote_fd, p_sess->p_port_sockaddr,
tunable_connect_timeout);
if (vsf_sysutil_retval_is_error(retval))
{
vsf_cmdio_write(p_sess, FTP_BADSENDCONN,
"Failed to establish connection.");
vsf_sysutil_close(remote_fd);
return -1;
}
init_data_sock_params(p_sess, remote_fd);
return remote_fd;
}
static void
handle_sigalrm(void* p_private)
{
struct vsf_session* p_sess = (struct vsf_session*) p_private;
if (!p_sess->data_progress)
{
vsf_cmdio_write_noblock(p_sess, FTP_DATA_TIMEOUT,
"Data timeout. Reconnect. Sorry.");
vsf_sysutil_exit(0);
}
p_sess->data_progress = 0;
start_data_alarm(p_sess);
}
void
start_data_alarm(struct vsf_session* p_sess)
{
if (tunable_data_connection_timeout > 0)
{
vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM, handle_sigalrm, p_sess);
vsf_sysutil_set_alarm(tunable_data_connection_timeout);
}
}
static void
init_data_sock_params(struct vsf_session* p_sess, int sock_fd)
{
if (p_sess->data_fd != -1)
{
bug("data descriptor still present in init_data_sock_params");
}
p_sess->data_fd = sock_fd;
p_sess->data_progress = 0;
vsf_sysutil_activate_keepalive(sock_fd);
/* And in the vague hope it might help... */
vsf_sysutil_set_iptos_throughput(sock_fd);
/* Set up lingering, so that we wait for all data to transfer, and report
* more accurate transfer rates.
*/
vsf_sysutil_activate_linger(sock_fd);
/* Start the timeout monitor */
vsf_sysutil_install_io_handler(handle_io, p_sess);
start_data_alarm(p_sess);
}
static void
handle_io(int retval, int fd, void* p_private)
{
long curr_sec;
long curr_usec;
unsigned int bw_rate;
double elapsed;
double pause_time;
double rate_ratio;
struct vsf_session* p_sess = (struct vsf_session*) p_private;
if (p_sess->data_fd != fd || vsf_sysutil_retval_is_error(retval) ||
retval == 0)
{
return;
}
/* Note that the session hasn't stalled, i.e. don't time it out */
p_sess->data_progress = 1;
/* Apply bandwidth quotas via a little pause, if necessary */
if (p_sess->bw_rate_max == 0)
{
return;
}
/* Calculate bandwidth rate */
vsf_sysutil_update_cached_time();
curr_sec = vsf_sysutil_get_cached_time_sec();
curr_usec = vsf_sysutil_get_cached_time_usec();
elapsed = (double) (curr_sec - p_sess->bw_send_start_sec);
elapsed += (double) (curr_usec - p_sess->bw_send_start_usec) /
(double) 1000000;
if (elapsed == (double) 0)
{
elapsed = (double) 0.01;
}
bw_rate = (unsigned int) ((double) retval / elapsed);
if (bw_rate <= p_sess->bw_rate_max)
{
p_sess->bw_send_start_sec = curr_sec;
p_sess->bw_send_start_usec = curr_usec;
return;
}
/* Tut! Rate exceeded, calculate a pause to bring things back into line */
rate_ratio = (double) bw_rate / (double) p_sess->bw_rate_max;
pause_time = (rate_ratio - (double) 1) * elapsed;
vsf_sysutil_sleep(pause_time);
vsf_sysutil_update_cached_time();
p_sess->bw_send_start_sec = vsf_sysutil_get_cached_time_sec();
p_sess->bw_send_start_usec = vsf_sysutil_get_cached_time_usec();
}
int
vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess, const int remote_fd,
struct vsf_sysutil_dir* p_dir,
const struct mystr* p_base_dir_str,
const struct mystr* p_option_str,
const struct mystr* p_filter_str,
int is_verbose)
{
return transfer_dir_internal(p_sess, remote_fd, p_dir, p_base_dir_str,
p_option_str, p_filter_str, is_verbose, 0);
}
static int
transfer_dir_internal(struct vsf_session* p_sess, const int remote_fd,
struct vsf_sysutil_dir* p_dir,
const struct mystr* p_base_dir_str,
const struct mystr* p_option_str,
const struct mystr* p_filter_str,
int is_verbose, int is_recurse)
{
struct mystr_list dir_list = INIT_STRLIST;
struct mystr_list subdir_list = INIT_STRLIST;
struct mystr dir_prefix_str = INIT_MYSTR;
struct mystr_list* p_subdir_list = 0;
struct str_locate_result loc_result = str_locate_char(p_option_str, 'R');
int failed = 0;
if (loc_result.found && tunable_ls_recurse_enable)
{
p_subdir_list = &subdir_list;
}
vsf_ls_populate_dir_list(&dir_list, p_subdir_list, p_dir, p_base_dir_str,
p_option_str, p_filter_str, is_verbose);
if (p_subdir_list)
{
int retval;
str_copy(&dir_prefix_str, p_base_dir_str);
str_append_text(&dir_prefix_str, ":\r\n");
retval = str_netfd_write(&dir_prefix_str, remote_fd);
if (retval != 0)
{
failed = 1;
}
}
if (!failed)
{
failed = write_dir_list(&dir_list, remote_fd);
}
/* Recurse into the subdirectories if required... */
if (!failed)
{
struct mystr sub_str = INIT_MYSTR;
unsigned int num_subdirs = str_list_get_length(&subdir_list);
unsigned int subdir_index;
for (subdir_index = 0; subdir_index < num_subdirs; subdir_index++)
{
int retval;
struct vsf_sysutil_dir* p_subdir;
const struct mystr* p_subdir_str =
str_list_get_pstr(&subdir_list, subdir_index);
if (str_equal_text(p_subdir_str, ".") ||
str_equal_text(p_subdir_str, ".."))
{
continue;
}
str_copy(&sub_str, p_base_dir_str);
str_append_char(&sub_str, '/');
str_append_str(&sub_str, p_subdir_str);
p_subdir = str_opendir(&sub_str);
if (p_subdir == 0)
{
/* Unreadable, gone missing, etc. - no matter */
continue;
}
str_alloc_text(&dir_prefix_str, "\r\n");
retval = str_netfd_write(&dir_prefix_str, remote_fd);
if (retval != 0)
{
failed = 1;
break;
}
retval = transfer_dir_internal(p_sess, remote_fd, p_subdir, &sub_str,
p_option_str, p_filter_str,
is_verbose, 1);
vsf_sysutil_closedir(p_subdir);
if (retval != 0)
{
failed = 1;
break;
}
}
str_free(&sub_str);
}
str_list_free(&dir_list);
str_list_free(&subdir_list);
str_free(&dir_prefix_str);
if (!failed)
{
if (!is_recurse)
{
vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Directory send OK.");
}
return 0;
}
else
{
if (!is_recurse)
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET,
"Failure writing network stream.");
}
return -1;
}
}
/* XXX - really, this should be refactored into a "buffered writer" object */
static int
write_dir_list(struct mystr_list* p_dir_list, int remote_fd)
{
/* This function writes out a list of strings to the client, over the
* data socket. We now coalesce the strings into fewer write() syscalls,
* which saved 33% CPU time writing a large directory.
*/
int retval = 0;
unsigned int dir_index_max = str_list_get_length(p_dir_list);
unsigned int dir_index;
struct mystr buf_str = INIT_MYSTR;
str_reserve(&buf_str, VSFTP_DIR_BUFSIZE);
for (dir_index = 0; dir_index < dir_index_max; dir_index++)
{
str_append_str(&buf_str, str_list_get_pstr(p_dir_list, dir_index));
if (dir_index == dir_index_max - 1 ||
str_getlen(&buf_str) +
str_getlen(str_list_get_pstr(p_dir_list, dir_index + 1)) >
VSFTP_DIR_BUFSIZE)
{
/* Writeout needed - we're either at the end, or we filled the buffer */
int writeret = str_netfd_write(&buf_str, remote_fd);
if (writeret != 0)
{
retval = 1;
break;
}
str_empty(&buf_str);
}
}
str_free(&buf_str);
return retval;
}
struct vsf_transfer_ret
vsf_ftpdataio_transfer_file(struct vsf_session* p_sess, int remote_fd,
int file_fd, int is_recv, int is_ascii)
{
if (!is_recv)
{
if (is_ascii)
{
return do_file_send_ascii(p_sess, remote_fd, file_fd);
}
else
{
return do_file_send_binary(p_sess, remote_fd, file_fd);
}
}
else
{
return do_file_recv(p_sess, remote_fd, file_fd, is_ascii);
}
}
static struct vsf_transfer_ret
do_file_send_ascii(struct vsf_session* p_sess, int net_fd, int file_fd)
{
static char* p_readbuf;
static char* p_asciibuf;
struct vsf_transfer_ret ret_struct = { 0, 0 };
if (p_readbuf == 0)
{
/* NOTE!! * 2 factor because we can double the data by doing our ASCII
* linefeed mangling
*/
vsf_secbuf_alloc(&p_asciibuf, VSFTP_DATA_BUFSIZE * 2);
vsf_secbuf_alloc(&p_readbuf, VSFTP_DATA_BUFSIZE);
}
while (1)
{
unsigned int num_to_write;
int retval = vsf_sysutil_read(file_fd, p_readbuf, VSFTP_DATA_BUFSIZE);
if (vsf_sysutil_retval_is_error(retval))
{
vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure reading local file.");
ret_struct.retval = -1;
return ret_struct;
}
else if (retval == 0)
{
/* Success - cool */
vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "File send OK.");
return ret_struct;
}
num_to_write = vsf_ascii_bin_to_ascii(p_readbuf, p_asciibuf,
(unsigned int) retval);
retval = vsf_sysutil_write_loop(net_fd, p_asciibuf, num_to_write);
if (vsf_sysutil_retval_is_error(retval) ||
(unsigned int) retval != num_to_write)
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET,
"Failure writing network stream.");
ret_struct.retval = -1;
return ret_struct;
}
ret_struct.transferred += (unsigned int) retval;
}
}
static struct vsf_transfer_ret
do_file_send_binary(struct vsf_session* p_sess, int net_fd, int file_fd)
{
static struct vsf_sysutil_statbuf* s_p_statbuf;
int retval;
unsigned long bytes_to_send;
unsigned long init_file_offset;
unsigned long curr_file_offset;
unsigned long bytes_sent;
unsigned int chunk_size;
struct vsf_transfer_ret ret_struct = { 0, 0 };
/* Work out how many bytes to send based on file size minus current offset */
/* NOTE - if we're being pedantic here are two duplicated syscalls */
vsf_sysutil_fstat(file_fd, &s_p_statbuf);
bytes_to_send = vsf_sysutil_statbuf_get_size(s_p_statbuf);
init_file_offset = vsf_sysutil_get_file_offset(file_fd);
curr_file_offset = init_file_offset;
/* Don't underflow if some bonehead sets a REST greater than the file size */
if (init_file_offset > bytes_to_send)
{
bytes_to_send = 0;
}
else
{
bytes_to_send -= init_file_offset;
}
if (p_sess->bw_rate_max)
{
chunk_size = VSFTP_DATA_BUFSIZE;
}
else
{
chunk_size = 0;
}
/* Just because I can ;-) */
retval = vsf_sysutil_sendfile(net_fd, file_fd, &curr_file_offset,
bytes_to_send, chunk_size);
bytes_sent = curr_file_offset - init_file_offset;
ret_struct.transferred = bytes_sent;
if (vsf_sysutil_retval_is_error(retval))
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure writing network stream.");
ret_struct.retval = -1;
return ret_struct;
}
else if (bytes_sent != bytes_to_send)
{
vsf_cmdio_write(p_sess, FTP_BADSENDFILE,
"Failure writing network stream.");
ret_struct.retval = -1;
return ret_struct;
}
vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "File send OK.");
return ret_struct;
}
static struct vsf_transfer_ret
do_file_recv(struct vsf_session* p_sess, int net_fd, int file_fd, int is_ascii)
{
static char* p_recvbuf;
unsigned int num_to_write;
struct vsf_transfer_ret ret_struct = { 0, 0 };
if (p_recvbuf == 0)
{
vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE);
}
while (1)
{
int retval = vsf_sysutil_read(net_fd, p_recvbuf, VSFTP_DATA_BUFSIZE);
if (vsf_sysutil_retval_is_error(retval))
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET,
"Failure reading network stream.");
ret_struct.retval = -1;
return ret_struct;
}
else if (retval == 0)
{
/* Transfer done, nifty */
vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "File receive OK.");
return ret_struct;
}
num_to_write = (unsigned int) retval;
ret_struct.transferred += num_to_write;
if (is_ascii)
{
/* Handle ASCII conversion if we have to. Note that using the same
* buffer for source and destination is safe, because the ASCII ->
* binary transform only ever results in a smaller file.
*/
num_to_write = vsf_ascii_ascii_to_bin(p_recvbuf, p_recvbuf,
num_to_write);
}
retval = vsf_sysutil_write_loop(file_fd, p_recvbuf, num_to_write);
if (vsf_sysutil_retval_is_error(retval) ||
(unsigned int) retval != num_to_write)
{
vsf_cmdio_write(p_sess, FTP_BADSENDFILE,
"Failure writing to local file.");
ret_struct.retval = -1;
return ret_struct;
}
}
}

85
ftpdataio.h Normal file
View File

@ -0,0 +1,85 @@
#ifndef VSF_FTPDATAIO_H
#define VSF_FTPDATAIO_H
struct mystr;
struct vsf_sysutil_sockaddr;
struct vsf_sysutil_dir;
struct vsf_session;
/* vsf_ftpdataio_dispose_transfer_fd()
* PURPOSE
* Close down the remote data transfer file descriptor. If unsent data reamins
* on the connection, this method blocks until it is transferred. There is an
* upper limit of 5 minutes.
* PARAMETERS
* p_sess - the current FTP session object
*/
void vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess);
/* vsf_ftpdataio_get_pasv_fd()
* PURPOSE
* Return a connection data file descriptor obtained by the PASV connection
* method. This includes accept()'ing a connection from the remote.
* PARAMETERS
* p_sess - the current FTP session object
* RETURNS
* The file descriptor upon success, or -1 upon error.
*/
int vsf_ftpdataio_get_pasv_fd(struct vsf_session* p_sess);
/* vsf_ftpdataio_get_pasv_fd()
* PURPOSE
* Return a connection data file descriptor obtained by the PORT connection
* method. This includes connect()'ing to the remote.
* PARAMETERS
* p_sess - the current FTP session object
* RETURNS
* The file descriptor upon success, or -1 upon error.
*/
int vsf_ftpdataio_get_port_fd(struct vsf_session* p_sess);
/* vsf_ftpdataio_transfer_file()
* PURPOSE
* Send data between the network and a local file. Send and receive are
* supported, as well as ASCII mangling.
* PARAMETERS
* remote_fd - the file descriptor of the remote data connection
* file_fd - the file descriptor of the local file
* is_recv - 0 for sending to the remote, otherwise receive
* is_ascii - non zero for ASCII mangling
* RETURNS
* A structure, containing
* retval - 0 for success, failure otherwise
* transferred - number of bytes transferred
*/
struct vsf_transfer_ret
{
int retval;
unsigned long transferred;
};
struct vsf_transfer_ret vsf_ftpdataio_transfer_file(
struct vsf_session* p_sess,
int remote_fd, int file_fd, int is_recv, int is_ascii);
/* vsf_ftpdataio_transfer_dir()
* PURPOSE
* Send an ASCII directory lising of the requested directory to the remote
* client.
* PARAMETERS
* p_sess - the current session object
* remote_fd - the file descriptor of the remote data connection
* p_dir - the local directory object
* p_base_dir_str - the directory we opened relative to the current one
* p_option_str - the options list provided to "ls"
* p_filter_str - the filter string provided to "ls"
* is_verbose - set to 0 if NLST used, 1 if LIST used
*/
int vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess,
int remote_fd, struct vsf_sysutil_dir* p_dir,
const struct mystr* p_base_dir_str,
const struct mystr* p_option_str,
const struct mystr* p_filter_str,
int is_verbose);
#endif /* VSF_FTPDATAIO_H */

281
logging.c Normal file
View File

@ -0,0 +1,281 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
*
* logging.c
*/
#include "logging.h"
#include "tunables.h"
#include "utility.h"
#include "str.h"
#include "sysutil.h"
#include "sysstr.h"
#include "session.h"
/* File local functions */
static int vsf_log_type_is_transfer(enum EVSFLogEntryType type);
static void vsf_log_common(struct vsf_session* p_sess, int succeeded,
enum EVSFLogEntryType what,
const struct mystr* p_str);
static void vsf_log_do_log_vsftpd_format(struct vsf_session* p_sess,
struct mystr* p_str, int succeeded,
enum EVSFLogEntryType what,
const struct mystr* p_log_str);
static void vsf_log_do_log_wuftpd_format(struct vsf_session* p_sess,
struct mystr* p_str, int succeeded);
void
vsf_log_init(struct vsf_session* p_sess)
{
int retval;
if (!tunable_xferlog_enable)
{
return;
}
retval = vsf_sysutil_create_or_open_file(tunable_xferlog_file, 0600);
if (vsf_sysutil_retval_is_error(retval))
{
die("failed to open ftp log file");
}
p_sess->log_fd = retval;
}
static int
vsf_log_type_is_transfer(enum EVSFLogEntryType type)
{
return (type == kVSFLogEntryDownload || type == kVSFLogEntryUpload);
}
void
vsf_log_start_entry(struct vsf_session* p_sess, enum EVSFLogEntryType what)
{
if (p_sess->log_type != 0)
{
bug("non null log_type in vsf_log_start_entry");
}
p_sess->log_type = (unsigned long) what;
p_sess->log_start_sec = 0;
p_sess->log_start_usec = 0;
p_sess->transfer_size = 0;
str_empty(&p_sess->log_str);
if (vsf_log_type_is_transfer(what))
{
vsf_sysutil_update_cached_time();
p_sess->log_start_sec = vsf_sysutil_get_cached_time_sec();
p_sess->log_start_usec = vsf_sysutil_get_cached_time_usec();
}
}
void
vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what,
struct mystr* p_str)
{
vsf_log_common(p_sess, 1, what, p_str);
}
void
vsf_log_do_log(struct vsf_session* p_sess, int succeeded)
{
vsf_log_common(p_sess, succeeded, (enum EVSFLogEntryType) p_sess->log_type,
&p_sess->log_str);
p_sess->log_type = 0;
}
static void
vsf_log_common(struct vsf_session* p_sess, int succeeded,
enum EVSFLogEntryType what, const struct mystr* p_str)
{
static struct mystr s_log_str;
int retval;
if (p_sess->log_fd == -1 || (tunable_xferlog_std_format &&
!vsf_log_type_is_transfer(what)))
{
return;
}
retval = vsf_sysutil_lock_file(p_sess->log_fd);
if (vsf_sysutil_retval_is_error(retval))
{
return;
}
if (tunable_xferlog_std_format)
{
vsf_log_do_log_wuftpd_format(p_sess, &s_log_str, succeeded);
}
else
{
vsf_log_do_log_vsftpd_format(p_sess, &s_log_str, succeeded, what, p_str);
}
str_replace_unprintable(&s_log_str, '?');
str_append_char(&s_log_str, '\n');
/* Write it! Ignore write failure; maybe the disk filled or something */
(void) str_write_loop(&s_log_str, p_sess->log_fd);
vsf_sysutil_unlock_file(p_sess->log_fd);
}
static void
vsf_log_do_log_wuftpd_format(struct vsf_session* p_sess, struct mystr* p_str,
int succeeded)
{
long delta_sec;
enum EVSFLogEntryType what = (enum EVSFLogEntryType) p_sess->log_type;
/* Date - vsf_sysutil_get_current_date updates cached time */
str_alloc_text(p_str, vsf_sysutil_get_current_date());
str_append_char(p_str, ' ');
/* Transfer time (in seconds) */
delta_sec = vsf_sysutil_get_cached_time_sec() - p_sess->log_start_sec;
if (delta_sec <= 0)
{
delta_sec = 1;
}
str_append_ulong(p_str, (unsigned long) delta_sec);
str_append_char(p_str, ' ');
/* Remote host name */
str_append_str(p_str, &p_sess->remote_ip_str);
str_append_char(p_str, ' ');
/* Bytes transferred */
str_append_ulong(p_str, p_sess->transfer_size);
str_append_char(p_str, ' ');
/* Filename */
str_append_str(p_str, &p_sess->log_str);
str_append_char(p_str, ' ');
/* Transfer type (ascii/binary) */
if (p_sess->is_ascii)
{
str_append_text(p_str, "a ");
}
else
{
str_append_text(p_str, "b ");
}
/* Special action flag - tar, gzip etc. */
str_append_text(p_str, "_ ");
/* Direction of transfer */
if (what == kVSFLogEntryUpload)
{
str_append_text(p_str, "i ");
}
else
{
str_append_text(p_str, "o ");
}
/* Access mode: anonymous/real user, and identity */
if (p_sess->is_anonymous)
{
str_append_text(p_str, "a ");
str_append_str(p_str, &p_sess->anon_pass_str);
}
else
{
str_append_text(p_str, "r ");
str_append_str(p_str, &p_sess->user_str);
}
str_append_char(p_str, ' ');
/* Service name, authentication method, authentication user id */
str_append_text(p_str, "ftp 0 * ");
/* Completion status */
if (succeeded)
{
str_append_char(p_str, 'c');
}
else
{
str_append_char(p_str, 'i');
}
}
static void
vsf_log_do_log_vsftpd_format(struct vsf_session* p_sess, struct mystr* p_str,
int succeeded, enum EVSFLogEntryType what,
const struct mystr* p_log_str)
{
/* Date - vsf_sysutil_get_current_date updates cached time */
str_alloc_text(p_str, vsf_sysutil_get_current_date());
/* Pid */
str_append_text(p_str, " [pid ");
str_append_ulong(p_str, vsf_sysutil_getpid());
str_append_text(p_str, "] ");
/* User */
if (!str_isempty(&p_sess->user_str))
{
str_append_char(p_str, '[');
str_append_str(p_str, &p_sess->user_str);
str_append_text(p_str, "] ");
}
/* And the action */
if (what != kVSFLogEntryFTPInput && what != kVSFLogEntryFTPOutput)
{
if (succeeded)
{
str_append_text(p_str, "OK ");
}
else
{
str_append_text(p_str, "FAIL ");
}
}
switch (what)
{
case kVSFLogEntryDownload:
str_append_text(p_str, "DOWNLOAD");
break;
case kVSFLogEntryUpload:
str_append_text(p_str, "UPLOAD");
break;
case kVSFLogEntryMkdir:
str_append_text(p_str, "MKDIR");
break;
case kVSFLogEntryLogin:
str_append_text(p_str, "LOGIN");
break;
case kVSFLogEntryFTPInput:
str_append_text(p_str, "FTP command");
break;
case kVSFLogEntryFTPOutput:
str_append_text(p_str, "FTP response");
break;
default:
bug("bad entry_type in vsf_log_do_log");
break;
}
str_append_text(p_str, ": Client \"");
str_append_str(p_str, &p_sess->remote_ip_str);
str_append_char(p_str, '"');
if (what == kVSFLogEntryLogin && !str_isempty(&p_sess->anon_pass_str))
{
str_append_text(p_str, ", anon password \"");
str_append_str(p_str, &p_sess->anon_pass_str);
str_append_char(p_str, '"');
}
if (!str_isempty(p_log_str))
{
str_append_text(p_str, ", \"");
str_append_str(p_str, p_log_str);
str_append_char(p_str, '"');
}
if (what != kVSFLogEntryFTPInput && what != kVSFLogEntryFTPOutput)
{
if (p_sess->transfer_size)
{
str_append_text(p_str, ", ");
str_append_ulong(p_str, p_sess->transfer_size);
str_append_text(p_str, " bytes");
}
if (vsf_log_type_is_transfer(what))
{
long delta_sec = vsf_sysutil_get_cached_time_sec() -
p_sess->log_start_sec;
long delta_usec = vsf_sysutil_get_cached_time_usec() -
p_sess->log_start_usec;
double time_delta = (double) delta_sec + ((double) delta_usec /
(double) 1000000);
double kbyte_rate =
((double) p_sess->transfer_size / time_delta) / (double) 1024;
str_append_text(p_str, ", ");
str_append_double(p_str, kbyte_rate);
str_append_text(p_str, "Kbyte/sec");
}
}
}

64
logging.h Normal file
View File

@ -0,0 +1,64 @@
#ifndef VSF_LOGGING_H
#define VSF_LOGGING_H
/* Forward delcarations */
struct mystr;
struct vsf_session;
enum EVSFLogEntryType
{
kVSFLogEntryNull = 1,
kVSFLogEntryDownload,
kVSFLogEntryUpload,
kVSFLogEntryMkdir,
kVSFLogEntryLogin,
kVSFLogEntryFTPInput,
kVSFLogEntryFTPOutput
};
/* vsf_log_init()
* PURPOSE
* Initialize the logging services, by opening a writable file descriptor to
* the log file (should logging be enabled).
* PARAMETERS
* p_sess - the current session object
*/
void vsf_log_init(struct vsf_session* p_sess);
/* vsf_log_start_entry()
* PURPOSE
* Denote the start of a logged operation. Importantly, timing information
* (if applicable) will be taken starting from this call.
* PARAMETERS
* p_sess - the current session object
* what - the type of operation which just started
*/
void vsf_log_start_entry(struct vsf_session* p_sess,
enum EVSFLogEntryType what);
/* vsf_log_do_log()
* PURPOSE
* Denote the end of a logged operation, specifying whether the operation
* was successful or not.
* PARAMETERS
* p_sess - the current session object
* succeeded - 0 for a failed operation, 1 for a successful operation
*/
void vsf_log_do_log(struct vsf_session* p_sess, int succeeded);
/* vsf_log_line()
* PURPOSE
* Logs a single line of information, without disturbing any pending log
* operations (e.g. a download log spans a period of time).
* This call must be used for any logging calls nested within a call to
* the vsf_log_start_entry() function.
* PARAMETERS
* p_sess - the current session object
* what - the type of operation to log
* p_str - the string to log
*/
void vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what,
struct mystr* p_str);
#endif /* VSF_LOGGING_H */

324
ls.c Normal file
View File

@ -0,0 +1,324 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* ls.c
*
* Would you believe, code to handle directory listing.
*/
#include "ls.h"
#include "str.h"
#include "strlist.h"
#include "sysstr.h"
#include "sysutil.h"
#include "tunables.h"
static int filename_passes_filter(const struct mystr* p_filename_str,
const struct mystr* p_filter_str);
static void build_dir_line(struct mystr* p_str,
const struct mystr* p_filename_str,
const struct vsf_sysutil_statbuf* p_stat);
void
vsf_ls_populate_dir_list(struct mystr_list* p_list,
struct mystr_list* p_subdir_list,
struct vsf_sysutil_dir* p_dir,
const struct mystr* p_base_dir_str,
const struct mystr* p_option_str,
const struct mystr* p_filter_str,
int is_verbose)
{
struct mystr dirline_str = INIT_MYSTR;
struct mystr normalised_base_dir_str = INIT_MYSTR;
struct str_locate_result loc_result;
int a_option;
int r_option;
int t_option;
int do_stat = 0;
loc_result = str_locate_char(p_option_str, 'a');
a_option = loc_result.found;
loc_result = str_locate_char(p_option_str, 'r');
r_option = loc_result.found;
loc_result = str_locate_char(p_option_str, 't');
t_option = loc_result.found;
loc_result = str_locate_char(p_option_str, 'l');
if (loc_result.found)
{
is_verbose = 1;
}
/* Invert "reverse" arg for "-t", the time sorting */
if (t_option)
{
r_option = !r_option;
}
if (is_verbose || t_option || p_subdir_list != 0)
{
do_stat = 1;
}
/* "Normalise" the incoming base directory string by making sure it
* ends in a '/' if it is nonempty
*/
if (!str_equal_text(p_base_dir_str, "."))
{
str_copy(&normalised_base_dir_str, p_base_dir_str);
}
if (!str_isempty(&normalised_base_dir_str))
{
unsigned int len = str_getlen(&normalised_base_dir_str);
if (str_get_char_at(&normalised_base_dir_str, len - 1) != '/')
{
str_append_char(&normalised_base_dir_str, '/');
}
}
/* If we're going to need to do time comparisions, cache the local time */
if (is_verbose)
{
vsf_sysutil_update_cached_time();
}
while (1)
{
static struct mystr s_next_filename_str;
static struct mystr s_next_path_and_filename_str;
static struct vsf_sysutil_statbuf* s_p_statbuf;
str_next_dirent(&s_next_filename_str, p_dir);
if (str_isempty(&s_next_filename_str))
{
break;
}
if (!a_option && str_getlen(&s_next_filename_str) > 0 &&
str_get_char_at(&s_next_filename_str, 0) == '.')
{
continue;
}
/* If we have an ls option which is a filter, apply it */
if (!str_isempty(p_filter_str))
{
if (!filename_passes_filter(&s_next_filename_str, p_filter_str))
{
continue;
}
}
/* Calculate the full path (relative to CWD) for lstat() and
* output purposes
*/
str_copy(&s_next_path_and_filename_str, &normalised_base_dir_str);
str_append_str(&s_next_path_and_filename_str, &s_next_filename_str);
if (do_stat)
{
/* lstat() the file. Of course there's a race condition - the
* directory entry may have gone away whilst we read it, so
* ignore failure to stat
*/
int retval = str_lstat(&s_next_path_and_filename_str, &s_p_statbuf);
if (vsf_sysutil_retval_is_error(retval))
{
continue;
}
}
if (is_verbose)
{
static struct mystr s_final_file_str;
/* If it's a damn symlink, we need to append the target */
str_copy(&s_final_file_str, &s_next_filename_str);
if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf))
{
static struct mystr s_temp_str;
int retval = str_readlink(&s_temp_str, &s_next_path_and_filename_str);
if (retval == 0 && !str_isempty(&s_temp_str))
{
str_append_text(&s_final_file_str, " -> ");
str_append_str(&s_final_file_str, &s_temp_str);
}
}
build_dir_line(&dirline_str, &s_final_file_str, s_p_statbuf);
}
else
{
/* Just emit the filenames - note, we prepend the directory for NLST
* but not for LIST
*/
str_copy(&dirline_str, &s_next_path_and_filename_str);
str_append_text(&dirline_str, "\r\n");
}
/* Add filename into our sorted list - sorting by filename or time. Also,
* if we are required to, maintain a distinct list of direct
* subdirectories.
*/
{
static struct mystr s_temp_str;
const struct mystr* p_sort_str = 0;
const struct mystr* p_sort_subdir_str = 0;
if (!t_option)
{
p_sort_str = &s_next_filename_str;
}
else
{
str_alloc_text(&s_temp_str,
vsf_sysutil_statbuf_get_sortkey_mtime(s_p_statbuf));
p_sort_str = &s_temp_str;
p_sort_subdir_str = &s_temp_str;
}
str_list_add(p_list, &dirline_str, p_sort_str);
if (p_subdir_list != 0 && vsf_sysutil_statbuf_is_dir(s_p_statbuf))
{
str_list_add(p_subdir_list, &s_next_filename_str, p_sort_subdir_str);
}
}
} /* END: while(1) */
str_list_sort(p_list, r_option);
if (p_subdir_list != 0)
{
str_list_sort(p_subdir_list, r_option);
}
str_free(&dirline_str);
str_free(&normalised_base_dir_str);
}
static int
filename_passes_filter(const struct mystr* p_filename_str,
const struct mystr* p_filter_str)
{
/* A simple routine to match a filename against a pattern.
* This routine is used instead of e.g. fnmatch(3), because we should be
* reluctant to trust the latter. fnmatch(3) involves _lots_ of string
* parsing and handling. There is broad potential for any given fnmatch(3)
* implementation to be buggy.
*
* Currently supported pattern(s):
* - any number of wildcards, "*"
*/
static struct mystr s_filter_remain_str;
static struct mystr s_name_remain_str;
static struct mystr s_temp_str;
int last_was_wildcard = 1;
int must_match_at_current_pos = 1;
str_copy(&s_filter_remain_str, p_filter_str);
str_copy(&s_name_remain_str, p_filename_str);
while (!str_isempty(&s_filter_remain_str))
{
static struct mystr s_match_needed_str;
/* Locate next wildcard */
struct str_locate_result locate_result =
str_locate_char(&s_filter_remain_str, '*');
/* Isolate text leading up to wildcard (if any) - needs to be matched */
if (locate_result.found)
{
unsigned int index = locate_result.index;
str_left(&s_filter_remain_str, &s_match_needed_str, index);
str_mid_to_end(&s_filter_remain_str, &s_temp_str, index + 1);
str_copy(&s_filter_remain_str, &s_temp_str);
}
else
{
/* No more wildcards. Must match remaining filter string exactly. */
str_copy(&s_match_needed_str, &s_filter_remain_str);
str_empty(&s_filter_remain_str);
last_was_wildcard = 0;
}
if (!str_isempty(&s_match_needed_str))
{
/* Need to match something.. could be a match which has to start at
* current position, or we could allow it to start anywhere
*/
unsigned int index;
locate_result = str_locate_str(&s_name_remain_str, &s_match_needed_str);
if (!locate_result.found)
{
/* Fail */
return 0;
}
index = locate_result.index;
if (must_match_at_current_pos && index > 0)
{
/* Fail */
return 0;
}
/* Chop matched string out of remainder */
str_mid_to_end(&s_name_remain_str, &s_temp_str,
index + str_getlen(&s_match_needed_str));
str_copy(&s_name_remain_str, &s_temp_str);
}
/* Only the first iteration can require a match at current position -
* subsequent iterations will have seen a '*'
*/
must_match_at_current_pos = 0;
}
/* Any incoming string left means no match unless we ended on a wildcard */
if (!last_was_wildcard && str_getlen(&s_name_remain_str) > 0)
{
return 0;
}
/* OK, a match */
return 1;
}
static void
build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str,
const struct vsf_sysutil_statbuf* p_stat)
{
static struct mystr s_tmp_str;
unsigned long size = vsf_sysutil_statbuf_get_size(p_stat);
/* Permissions */
str_alloc_text(p_str, vsf_sysutil_statbuf_get_perms(p_stat));
str_append_char(p_str, ' ');
/* Hard link count */
str_alloc_ulong(&s_tmp_str, vsf_sysutil_statbuf_get_links(p_stat));
str_lpad(&s_tmp_str, 4);
str_append_str(p_str, &s_tmp_str);
str_append_char(p_str, ' ');
/* User */
{
int uid = vsf_sysutil_statbuf_get_uid(p_stat);
struct vsf_sysutil_user* p_user = 0;
if (tunable_text_userdb_names)
{
p_user = vsf_sysutil_getpwuid(uid);
}
if (p_user == 0)
{
str_alloc_ulong(&s_tmp_str, (unsigned long) uid);
}
else
{
str_alloc_text(&s_tmp_str, vsf_sysutil_user_getname(p_user));
}
}
str_rpad(&s_tmp_str, 8);
str_append_str(p_str, &s_tmp_str);
str_append_char(p_str, ' ');
/* Group */
{
int gid = vsf_sysutil_statbuf_get_gid(p_stat);
struct vsf_sysutil_group* p_group = 0;
if (tunable_text_userdb_names)
{
p_group = vsf_sysutil_getgrgid(gid);
}
if (p_group == 0)
{
str_alloc_ulong(&s_tmp_str, (unsigned long) gid);
}
else
{
str_alloc_text(&s_tmp_str, vsf_sysutil_group_getname(p_group));
}
}
str_rpad(&s_tmp_str, 8);
str_append_str(p_str, &s_tmp_str);
str_append_char(p_str, ' ');
/* Size in bytes */
str_alloc_ulong(&s_tmp_str, size);
str_lpad(&s_tmp_str, 8);
str_append_str(p_str, &s_tmp_str);
str_append_char(p_str, ' ');
/* Date stamp */
str_append_text(p_str, vsf_sysutil_statbuf_get_date(p_stat));
str_append_char(p_str, ' ');
/* Filename */
str_append_str(p_str, p_filename_str);
str_append_text(p_str, "\r\n");
}

31
ls.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef VSF_LS_H
#define VSF_LS_H
struct mystr;
struct mystr_list;
struct vsf_sysutil_dir;
/* vsf_ls_populate_dir_list()
* PURPOSE
* Given a directory handle, populate a formatted directory entry list (/bin/ls
* format). Also optionally populate a list of subdirectories.
* PARAMETERS
* p_list - the string list object for the result list of entries
* p_subdir_list - the string list object for the result list of
* subdirectories. May be 0 if client is not interested.
* p_dir - the directory object to be listed
* p_base_dir_str - the directory name we are listing, relative to current
* p_option_str - the string of options given to the LIST/NLST command
* p_filter_str - the filter string given to LIST/NLST - e.g. "*.mp3"
* is_verbose - set to 1 for LIST, 0 for NLST
*/
void vsf_ls_populate_dir_list(struct mystr_list* p_list,
struct mystr_list* p_subdir_list,
struct vsf_sysutil_dir* p_dir,
const struct mystr* p_base_dir_str,
const struct mystr* p_option_str,
const struct mystr* p_filter_str,
int is_verbose);
#endif /* VSF_LS_H */

229
main.c Normal file
View File

@ -0,0 +1,229 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* main.c
*/
#include "session.h"
#include "utility.h"
#include "tunables.h"
#include "logging.h"
#include "str.h"
#include "filestr.h"
#include "ftpcmdio.h"
#include "sysutil.h"
#include "sysdeputil.h"
#include "defs.h"
#include "parseconf.h"
#include "oneprocess.h"
#include "twoprocess.h"
/*
* Forward decls of helper functions
*/
static void die_unless_privileged(void);
static void do_sanity_checks(void);
static void session_init(struct vsf_session* p_sess);
static void env_init(void);
int
main(int argc, const char* argv[])
{
struct vsf_session the_session =
{
/* Control connection */
0, 0,
/* Data connection */
-1, 0, -1, 0, 0, 0, 0,
/* Login */
1, INIT_MYSTR, INIT_MYSTR,
/* Protocol state */
0, 1, INIT_MYSTR, 0,
/* Session state */
0,
/* Userids */
-1, -1,
/* Pre-chroot() cache */
INIT_MYSTR, INIT_MYSTR,
/* Logging */
-1, INIT_MYSTR, 0, 0, 0, INIT_MYSTR, 0,
/* Buffers */
INIT_MYSTR, INIT_MYSTR,
/* Parent <-> child comms */
0, -1, -1
};
int config_specified = 0;
const char* p_config_name = VSFTP_DEFAULT_CONFIG;
/* Zero or one argument supported. If one argument is passed, it is the
* path to the config file
*/
if (argc > 2)
{
die("vsftpd: too many arguments (I take an optional config file only)");
}
else if (argc == 0)
{
die("vsftpd: missing argv[0]");
}
if (argc == 2)
{
p_config_name = argv[1];
config_specified = 1;
}
/* This might need to open /dev/zero on systems lacking MAP_ANON. Needs
* to be done early (i.e. before config file parse, which may use
* anonymous pages
*/
vsf_sysutil_map_anon_pages_init();
/* Parse config file if it's there */
{
struct vsf_sysutil_statbuf* p_statbuf = 0;
int retval = vsf_sysutil_stat(p_config_name, &p_statbuf);
if (!vsf_sysutil_retval_is_error(retval))
{
vsf_parseconf_load_file(p_config_name);
}
else if (config_specified)
{
die("vsftpd: cannot open specified config file");
}
vsf_sysutil_free(p_statbuf);
}
/* Sanity checks - exit with a graceful error message if our STDIN is not
* a socket. Also check various config options don't collide.
*/
do_sanity_checks();
/* Just get out unless we start with requisite privilege */
die_unless_privileged();
/* Initializes session globals - e.g. IP addr's etc. */
session_init(&the_session);
/* Set up "environment", e.g. process group etc. */
env_init();
/* Set up logging - must come after global init because we need the remote
* address to convert into text
*/
vsf_log_init(&the_session);
str_alloc_text(&the_session.remote_ip_str,
vsf_sysutil_inet_ntoa(the_session.p_remote_addr));
/* Set up options on the command socket */
vsf_cmdio_sock_setup();
if (tunable_setproctitle_enable)
{
/* Warning -- warning -- may nuke argv, environ */
vsf_sysutil_setproctitle_init(argc, argv);
vsf_sysutil_set_proctitle_prefix(&the_session.remote_ip_str);
vsf_sysutil_setproctitle("connected");
}
/* We might chroot() very soon (one process model), so we need to open
* any required config files here.
*/
if (tunable_deny_email_enable)
{
int retval = str_fileread(&the_session.banned_email_str,
tunable_banned_email_file, VSFTP_CONF_FILE_MAX);
if (vsf_sysutil_retval_is_error(retval))
{
die("cannot open banned e-mail list file");
}
}
/* Special case - can force one process model if we've got a setup
* needing _no_ privs
*/
if (!tunable_local_enable && !tunable_connect_from_port_20 &&
!tunable_chown_uploads)
{
tunable_one_process_model = 1;
}
if (tunable_one_process_model)
{
vsf_one_process_start(&the_session);
}
else
{
vsf_two_process_start(&the_session);
}
/* NOTREACHED */
bug("should not get here: main");
return 1;
}
static void
die_unless_privileged(void)
{
if (!vsf_sysutil_running_as_root())
{
die("vsftpd: must be started as root");
}
}
static void
do_sanity_checks(void)
{
{
struct vsf_sysutil_statbuf* p_statbuf = 0;
vsf_sysutil_fstat(VSFTP_COMMAND_FD, &p_statbuf);
if (!vsf_sysutil_statbuf_is_socket(p_statbuf))
{
die("vsftpd: does not run standalone, must be started from inetd");
}
vsf_sysutil_free(p_statbuf);
}
if (tunable_one_process_model)
{
if (tunable_local_enable)
{
die("vsftpd: security: 'tunable_one_process_model' is anonymous only");
}
if (!vsf_sysdep_has_capabilities_as_non_root())
{
die("vsftpd: security: 'tunable_one_process_model' needs a better OS");
}
}
if (!tunable_local_enable && !tunable_anonymous_enable)
{
die("vsftpd: both local and anonymous access disabled!");
}
}
static void
env_init(void)
{
vsf_sysutil_make_session_leader();
/* Set up a secure umask - we'll set the proper one after login */
vsf_sysutil_set_umask(VSFTP_SECURE_UMASK);
/* Fire up libc's timezone initialisation, before we chroot()! */
vsf_sysutil_tzset();
/* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
vsf_sysutil_install_null_sighandler(kVSFSysUtilSigPIPE);
}
static void
session_init(struct vsf_session* p_sess)
{
/* Get the addresses of the control connection */
vsf_sysutil_getpeername(VSFTP_COMMAND_FD, &p_sess->p_remote_addr);
vsf_sysutil_getsockname(VSFTP_COMMAND_FD, &p_sess->p_local_addr);
/* If anonymous mode is active, fetch the uid of the anonymous user */
if (tunable_anonymous_enable)
{
const struct vsf_sysutil_user* p_user =
vsf_sysutil_getpwnam(tunable_ftp_username);
if (p_user == 0)
{
die("vsftpd: cannot locate user specified in 'tunable_ftp_username'");
}
p_sess->anon_ftp_uid = vsf_sysutil_user_getuid(p_user);
if (tunable_chown_uploads)
{
p_user = vsf_sysutil_getpwnam(tunable_chown_username);
if (p_user == 0)
{
die("vsf_sysutil_getpwnam");
}
p_sess->anon_upload_chown_uid = vsf_sysutil_user_getuid(p_user);
}
}
}

125
netstr.c Normal file
View File

@ -0,0 +1,125 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* netstr.c
*
* The netstr interface extends the standard string interface, adding
* functions which can cope safely with building strings from the network,
* and send them out too.
*/
#include "netstr.h"
#include "str.h"
#include "sysstr.h"
#include "utility.h"
#include "sysutil.h"
static int str_netfd_write_common(const struct mystr* p_str, int fd,
int noblock);
void
str_netfd_alloc(struct mystr* p_str, int fd, char term, char* p_readbuf,
unsigned int maxlen)
{
int retval;
unsigned int bytes_read;
unsigned int i;
char* p_readpos = p_readbuf;
unsigned int left = maxlen;
while (1)
{
if (p_readpos + left != p_readbuf + maxlen)
{
bug("poor buffer accounting in str_netfd_alloc");
}
/* Did we hit the max? */
if (left == 0)
{
str_empty(p_str);
return;
}
retval = vsf_sysutil_recv_peek(fd, p_readpos, left);
if (vsf_sysutil_retval_is_error(retval))
{
die("vsf_sysutil_recv_peek");
}
else if (retval == 0)
{
die("vsf_sysutil_recv_peek: no data");
}
bytes_read = (unsigned int) retval;
/* Search for the terminator */
for (i=0; i < bytes_read; i++)
{
if (p_readpos[i] == term)
{
/* Got it! */
retval = vsf_sysutil_read_loop(fd, p_readpos, i + 1);
if (vsf_sysutil_retval_is_error(retval) ||
(unsigned int) retval != i + 1)
{
die("vsf_sysutil_read_loop");
}
if (p_readpos[i] != term)
{
die("missing terminator in str_netfd_alloc");
}
str_alloc_alt_term(p_str, p_readbuf, term);
return;
}
}
/* Not found in this read chunk, so consume the data and re-loop */
if (bytes_read > left)
{
bug("bytes_read > left in str_netfd_alloc");
}
left -= bytes_read;
retval = vsf_sysutil_read_loop(fd, p_readpos, bytes_read);
if (vsf_sysutil_retval_is_error(retval) ||
(unsigned int) retval != bytes_read)
{
die("vsf_sysutil_read_loop");
}
p_readpos += bytes_read;
} /* END: while(1) */
}
static int
str_netfd_write_common(const struct mystr* p_str, int fd, int noblock)
{
int ret = 0;
int retval;
unsigned int str_len = str_getlen(p_str);
if (str_len == 0)
{
bug("zero str_len in str_netfd_write_common");
}
if (noblock)
{
vsf_sysutil_activate_noblock(fd);
}
retval = str_write_loop(p_str, fd);
if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != str_len)
{
ret = -1;
}
if (noblock)
{
vsf_sysutil_deactivate_noblock(fd);
}
return ret;
}
int
str_netfd_write(const struct mystr* p_str, int fd)
{
return str_netfd_write_common(p_str, fd, 0);
}
int
str_netfd_write_noblock(const struct mystr* p_str, int fd)
{
return str_netfd_write_common(p_str, fd, 1);
}

52
netstr.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef VSFTP_NETSTR_H
#define VSFTP_NETSTR_H
struct mystr;
/* str_netfd_alloc()
* PURPOSE
* Read a string from a network socket into a string buffer object. The string
* is delimited by a specified string terminator character.
* If any network related errors occur trying to read the string, this call
* will exit the program.
* This method avoids reading one character at a time from the network.
* PARAMETERS
* p_str - the destination string object
* fd - the file descriptor of the remote network socket
* term - the character which will terminate the string. This character
* is included in the returned string.
* p_readbuf - pointer to a scratch buffer into which to read from the
* network. This buffer must be at least "maxlen" characters!
* maxlen - maximum length of string to return. If this limit is passed,
* an empty string will be returned.
*/
void str_netfd_alloc(struct mystr* p_str, int fd, char term,
char* p_readbuf, unsigned int maxlen);
/* str_netfd_write()
* PURPOSE
* Write the contents of a string buffer object out to a network file
* descriptor. Failure will cause this call to exit the program.
* PARAMETERS
* p_str - the string object to send
* fd - the file descriptor of the remote network socket
* RETURNS
* 0 on success, -1 on failure
*/
int str_netfd_write(const struct mystr* p_str, int fd);
/* str_netfd_write_noblock()
* PURPOSE
* Write the contents of a string buffer object out to a network file
* descriptor. This call will NOT BLOCK. Furthermore, any errors encountered
* will be ignored.
* PARAMETERS
* p_str - the string object to send
* fd - the file descriptor of the remote network socket
* RETURNS
* 0 on success, -1 on failure
*/
int str_netfd_write_noblock(const struct mystr* p_str, int fd);
#endif /* VSFTP_NETSTR_H */

78
oneprocess.c Normal file
View File

@ -0,0 +1,78 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* oneprocess.c
*
* Code for the "one process" security model. The one process security model
* is born for the purposes of raw speed at the expense of compromising the
* purity of the security model.
* The one process model will typically be disabled, for security reasons.
* Only sites with huge numbers of concurrent users are likely to feel the
* pain of two processes per session.
*/
#include "prelogin.h"
#include "postlogin.h"
#include "privops.h"
#include "session.h"
#include "secutil.h"
#include "str.h"
#include "tunables.h"
#include "utility.h"
#include "sysdeputil.h"
void
vsf_one_process_start(struct vsf_session* p_sess)
{
unsigned int caps = 0;
if (tunable_chown_uploads)
{
caps |= kCapabilityCAP_CHOWN;
}
if (tunable_connect_from_port_20)
{
caps |= kCapabilityCAP_NET_BIND_SERVICE;
}
{
struct mystr user_name = INIT_MYSTR;
str_alloc_text(&user_name, tunable_ftp_username);
vsf_secutil_change_credentials(&user_name, 0, 1, 1, caps);
str_free(&user_name);
}
init_connection(p_sess);
}
void
vsf_one_process_login(struct vsf_session* p_sess,
const struct mystr* p_pass_str)
{
enum EVSFPrivopLoginResult login_result =
vsf_privop_do_login(p_sess, p_pass_str);
switch (login_result)
{
case kVSFLoginFail:
return;
break;
case kVSFLoginAnon:
p_sess->is_anonymous = 1;
process_post_login(p_sess);
break;
default:
bug("bad state in vsf_one_process_login");
break;
}
}
int
vsf_one_process_get_priv_data_sock(struct vsf_session* p_sess)
{
return vsf_privop_get_ftp_port_sock(p_sess);
}
void
vsf_one_process_chown_upload(struct vsf_session* p_sess, int fd)
{
vsf_privop_do_file_chown(p_sess, fd);
}

47
oneprocess.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef VSF_ONEPROCESS_H
#define VSF_ONEPROCESS_H
struct mystr;
struct vsf_session;
/* vsf_one_process_start()
* PURPOSE
* Called to start FTP login processing using the one process model. Before
* processing starts, all possible privileges are dropped.
* PARAMETERS
* p_sess - the current session object
*/
void vsf_one_process_start(struct vsf_session* p_sess);
/* vsf_one_process_login()
* PURPOSE
* Called to propose a login using the one process model. Only anonymous
* logins supported!
* PARAMETERS
* p_sess - the current session object
* p_pass_str - the proposed password
*/
void vsf_one_process_login(struct vsf_session* p_sess,
const struct mystr* p_pass_str);
/* vsf_one_process_get_priv_data_sock()
* PURPOSE
* Get a privileged port 20 bound data socket using the one process model.
* PARAMETERS
* p_sess - the current session object
* RETURNS
* The file descriptor of the privileged socket
*/
int vsf_one_process_get_priv_data_sock(struct vsf_session* p_sess);
/* vsf_one_process_chown_upload()
* PURPOSE
* Change ownership of an uploaded file using the one process model.
* PARAMETERS
* p_sess - the current session object
* fd - the file descriptor to change ownership on
*/
void vsf_one_process_chown_upload(struct vsf_session* p_sess, int fd);
#endif /* VSF_ONEPROCESS_H */

211
parseconf.c Normal file
View File

@ -0,0 +1,211 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* parseconf.c
*
* Routines and support to load in a file full of tunable variables and
* settings, populating corresponding runtime variables.
*/
#include "parseconf.h"
#include "tunables.h"
#include "str.h"
#include "filestr.h"
#include "defs.h"
#include "sysutil.h"
#include "utility.h"
/* File local functions */
static void handle_config_setting(struct mystr* p_setting_str,
struct mystr* p_value_str);
/* Tables mapping setting names to runtime variables */
/* Boolean settings */
static struct parseconf_bool_setting
{
const char* p_setting_name;
int* p_variable;
}
parseconf_bool_array[] =
{
{ "anonymous_enable", &tunable_anonymous_enable },
{ "local_enable", &tunable_local_enable },
{ "pasv_enable", &tunable_pasv_enable },
{ "port_enable", &tunable_port_enable },
{ "chroot_local_user", &tunable_chroot_local_user },
{ "write_enable", &tunable_write_enable },
{ "anon_upload_enable", &tunable_anon_upload_enable },
{ "anon_mkdir_write_enable", &tunable_anon_mkdir_write_enable },
{ "anon_other_write_enable", &tunable_anon_other_write_enable },
{ "chown_uploads", &tunable_chown_uploads },
{ "connect_from_port_20", &tunable_connect_from_port_20 },
{ "xferlog_enable", &tunable_xferlog_enable },
{ "dirmessage_enable", &tunable_dirmessage_enable },
{ "anon_world_readable_only", &tunable_anon_world_readable_only },
{ "async_abor_enable", &tunable_async_abor_enable },
{ "ascii_upload_enable", &tunable_ascii_upload_enable },
{ "ascii_download_enable", &tunable_ascii_download_enable },
{ "one_process_model", &tunable_one_process_model },
{ "xferlog_std_format", &tunable_xferlog_std_format },
{ "pasv_promiscuous", &tunable_pasv_promiscuous },
{ "deny_email_enable", &tunable_deny_email_enable },
{ "chroot_list_enable", &tunable_chroot_list_enable },
{ "setproctitle_enable", &tunable_setproctitle_enable },
{ "text_userdb_names", &tunable_text_userdb_names },
{ "ls_recurse_enable", &tunable_ls_recurse_enable },
{ "log_ftp_protocol", &tunable_log_ftp_protocol },
{ "guest_enable", &tunable_guest_enable },
{ "userlist_enable", &tunable_userlist_enable },
{ "userlist_deny", &tunable_userlist_deny },
{ 0, 0 }
};
static struct parseconf_uint_setting
{
const char* p_setting_name;
unsigned int* p_variable;
}
parseconf_uint_array[] =
{
{ "accept_timeout", &tunable_accept_timeout },
{ "connect_timeout", &tunable_connect_timeout },
{ "local_umask", &tunable_local_umask },
{ "anon_umask", &tunable_anon_umask },
{ "ftp_data_port", &tunable_ftp_data_port },
{ "idle_session_timeout", &tunable_idle_session_timeout },
{ "data_connection_timeout", &tunable_data_connection_timeout },
{ "pasv_min_port", &tunable_pasv_min_port },
{ "pasv_max_port", &tunable_pasv_max_port },
{ "anon_max_rate", &tunable_anon_max_rate },
{ "local_max_rate", &tunable_local_max_rate },
{ 0, 0 }
};
static struct parseconf_str_setting
{
const char* p_setting_name;
const char** p_variable;
}
parseconf_str_array[] =
{
{ "secure_chroot_dir", &tunable_secure_chroot_dir },
{ "ftp_username", &tunable_ftp_username },
{ "chown_username", &tunable_chown_username },
{ "xferlog_file", &tunable_xferlog_file },
{ "message_file", &tunable_message_file },
{ "nopriv_user", &tunable_nopriv_user },
{ "ftpd_banner", &tunable_ftpd_banner },
{ "banned_email_file", &tunable_banned_email_file },
{ "chroot_list_file", &tunable_chroot_list_file },
{ "pam_service_name", &tunable_pam_service_name },
{ "guest_username", &tunable_guest_username },
{ "userlist_file", &tunable_userlist_file },
{ 0, 0 }
};
void
vsf_parseconf_load_file(const char* p_filename)
{
struct mystr config_file_str = INIT_MYSTR;
struct mystr config_setting_str = INIT_MYSTR;
struct mystr config_value_str = INIT_MYSTR;
unsigned int str_pos = 0;
int retval = str_fileread(&config_file_str, p_filename, VSFTP_CONF_FILE_MAX);
if (vsf_sysutil_retval_is_error(retval))
{
die("cannot open config file");
}
while (str_getline(&config_file_str, &config_setting_str, &str_pos))
{
if (str_isempty(&config_setting_str) ||
str_get_char_at(&config_setting_str, 0) == '#')
{
continue;
}
/* Split into name=value pair */
str_split_char(&config_setting_str, &config_value_str, '=');
handle_config_setting(&config_setting_str, &config_value_str);
}
str_free(&config_file_str);
str_free(&config_setting_str);
str_free(&config_value_str);
}
static void
handle_config_setting(struct mystr* p_setting_str, struct mystr* p_value_str)
{
if (str_isempty(p_value_str))
{
die("missing value in config file");
}
/* Is it a boolean value? */
{
const struct parseconf_bool_setting* p_bool_setting = parseconf_bool_array;
while (p_bool_setting->p_setting_name != 0)
{
if (str_equal_text(p_setting_str, p_bool_setting->p_setting_name))
{
/* Got it */
str_upper(p_value_str);
if (str_equal_text(p_value_str, "YES") ||
str_equal_text(p_value_str, "TRUE") ||
str_equal_text(p_value_str, "1"))
{
*(p_bool_setting->p_variable) = 1;
}
else if (str_equal_text(p_value_str, "NO") ||
str_equal_text(p_value_str, "FALSE") ||
str_equal_text(p_value_str, "0"))
{
*(p_bool_setting->p_variable) = 0;
}
else
{
die("bad bool value in config file");
}
return;
}
p_bool_setting++;
}
}
/* Is it an unsigned integer setting? */
{
const struct parseconf_uint_setting* p_uint_setting = parseconf_uint_array;
while (p_uint_setting->p_setting_name != 0)
{
if (str_equal_text(p_setting_str, p_uint_setting->p_setting_name))
{
/* Got it */
/* If the value starts with 0, assume it's an octal value */
if (!str_isempty(p_value_str) &&
str_get_char_at(p_value_str, 0) == '0')
{
*(p_uint_setting->p_variable) = str_octal_to_uint(p_value_str);
}
else
{
*(p_uint_setting->p_variable) = str_atoi(p_value_str);
}
return;
}
p_uint_setting++;
}
}
/* Is it a string setting? */
{
const struct parseconf_str_setting* p_str_setting = parseconf_str_array;
while (p_str_setting->p_setting_name != 0)
{
if (str_equal_text(p_setting_str, p_str_setting->p_setting_name))
{
/* Got it */
*(p_str_setting->p_variable) = str_strdup(p_value_str);
return;
}
p_str_setting++;
}
}
die("unrecognised variable in config file");
}

17
parseconf.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef VSF_PARSECONF_H
#define VSF_PARSECONF_H
/* vsf_parseconf_load_file()
* PURPOSE
* Parse the given file as a vsftpd config file. If the file cannot be
* opened for whatever reason, a fatal error is raised. If the file contains
* any syntax errors, a fatal error is raised.
* If the call returns (no fatal error raised), then the config file was
* parsed and the global config settings will have been updated.
* PARAMETERS
* p_filename - the name of the config file to parse
*/
void vsf_parseconf_load_file(const char* p_filename);
#endif /* VSF_PARSECONF_H */

23
port/cmsg_extras.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef VSF_CMSG_EXTRAS_H
#define VSF_CMSG_EXTRAS_H
#include <sys/types.h>
#include <sys/socket.h>
/* These are from Linux glibc-2.2 */
#ifndef CMSG_ALIGN
#define CMSG_ALIGN(len) (((len) + sizeof (size_t) - 1) \
& ~(sizeof (size_t) - 1))
#endif
#ifndef CMSG_SPACE
#define CMSG_SPACE(len) (CMSG_ALIGN (len) \
+ CMSG_ALIGN (sizeof (struct cmsghdr)))
#endif
#ifndef CMSG_LEN
#define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len))
#endif
#endif /* VSF_CMSG_EXTRAS_H */

7
port/dirfd_extras.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef VSF_DIRFD_EXTRAS_H
#define VSF_DIRFD_EXTRAS_H
#define dirfd(x) ((x)->dd_fd)
#endif /* VSF_DIRFD_EXTRAS_H */

23
port/hpux_bogons.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef VSF_HPUX_BOGONS_H
#define VSF_HPUX_BOGONS_H
#include <sys/mman.h>
/* HP-UX has MAP_ANONYMOUS but not MAP_ANON - I'm not sure which is more
* standard!
*/
#ifdef MAP_ANONYMOUS
#ifndef MAP_ANON
#define MAP_ANON MAP_ANONYMOUS
#endif
#endif
/* Ancient versions of HP-UX don't have MAP_FAILED */
#ifndef MAP_FAILED
#define MAP_FAILED (void *) -1L
#endif
/* Need dirfd() */
#include "dirfd_extras.h"
#endif /* VSF_HPUX_BOGONS_H */

8
port/irix_bogons.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef VSF_IRIX_BOGONS_H
#define VSF_IRIX_BOGONS_H
/* Need dirfd() */
#include "dirfd_extras.h"
#endif /* VSF_IRIX_BOGONS_H */

22
port/porting_junk.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef VSF_PORTINGJUNK_H
#define VSF_PORTINGJUNK_H
#ifdef __sun
#include "solaris_bogons.h"
#endif
#ifdef __sgi
#include "irix_bogons.h"
#endif
#ifdef __hpux
#include "hpux_bogons.h"
#endif
/* So many older systems lack these, that it's too much hassle to list all
* the errant systems
*/
#include "cmsg_extras.h"
#endif /* VSF_PORTINGJUNK_H */

14
port/solaris_bogons.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef VSF_SOLARIS_BOGONS_H
#define VSF_SOLARIS_BOGONS_H
/* This bogon ensures we get access to CMSG_DATA, CMSG_FIRSTHDR */
#define _XPG4_2
/* This bogon prevents _XPG4_2 breaking the include of signal.h! */
#define __EXTENSIONS__
/* Need dirfd() */
#include "dirfd_extras.h"
#endif /* VSF_SOLARIS_BOGONS_H */

1125
postlogin.c Normal file

File diff suppressed because it is too large Load Diff

15
postlogin.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef VSF_POSTLOGIN_H
#define VSF_POSTLOGIN_H
struct vsf_session;
/* process_post_login()
* PURPOSE
* Called to begin FTP protocol parsing for a logged in session.
* PARAMETERS
* p_sess - the current session object
*/
void process_post_login(struct vsf_session* p_sess);
#endif /* VSF_POSTLOGIN_H */

113
postprivparent.c Normal file
View File

@ -0,0 +1,113 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* postprivparent.c
*
* This file contains all privileged parent services offered after logging
* in. This includes e.g. chown() of uploaded files, issuing of port 20
* sockets.
*/
#include "postprivparent.h"
#include "session.h"
#include "privops.h"
#include "privsock.h"
#include "utility.h"
#include "tunables.h"
#include "defs.h"
#include "sysutil.h"
#include "str.h"
#include "secutil.h"
#include "sysstr.h"
#include "sysdeputil.h"
static void minimize_privilege(struct vsf_session* p_sess);
static void process_post_login_req(struct vsf_session* p_sess);
static void cmd_process_chown(struct vsf_session* p_sess);
static void cmd_process_get_data_sock(struct vsf_session* p_sess);
void
vsf_priv_parent_postlogin(struct vsf_session* p_sess)
{
minimize_privilege(p_sess);
/* We're still here... */
while (1)
{
process_post_login_req(p_sess);
}
}
static void
process_post_login_req(struct vsf_session* p_sess)
{
/* Blocks */
char cmd = priv_sock_get_cmd(p_sess);
if (tunable_chown_uploads && cmd == PRIV_SOCK_CHOWN)
{
cmd_process_chown(p_sess);
}
else if (tunable_connect_from_port_20 && cmd == PRIV_SOCK_GET_DATA_SOCK)
{
cmd_process_get_data_sock(p_sess);
}
else
{
die("bad post login request");
}
}
static void
minimize_privilege(struct vsf_session* p_sess)
{
/* So, we logged in and forked a totally unprivileged child. Our job
* now is to minimize the privilege we need in order to act as a helper
* to the child.
*
* In some happy circumstances, we can exit and be done with root
* altogether.
*/
if (!(tunable_chown_uploads && p_sess->is_anonymous) &&
!tunable_connect_from_port_20)
{
/* Cool. We're outta here. */
vsf_sysutil_exit(0);
}
{
unsigned int caps = 0;
struct mystr user_str = INIT_MYSTR;
struct mystr dir_str = INIT_MYSTR;
str_alloc_text(&user_str, tunable_nopriv_user);
str_alloc_text(&dir_str, tunable_secure_chroot_dir);
if (tunable_chown_uploads && p_sess->is_anonymous)
{
caps |= kCapabilityCAP_CHOWN;
}
if (tunable_connect_from_port_20)
{
caps |= kCapabilityCAP_NET_BIND_SERVICE;
}
vsf_secutil_change_credentials(&user_str, &dir_str, 1, 0, caps);
str_free(&user_str);
str_free(&dir_str);
}
}
static void
cmd_process_chown(struct vsf_session* p_sess)
{
int the_fd = priv_sock_parent_recv_fd(p_sess);
vsf_privop_do_file_chown(p_sess, the_fd);
vsf_sysutil_close(the_fd);
priv_sock_send_result(p_sess, PRIV_SOCK_RESULT_OK);
}
static void
cmd_process_get_data_sock(struct vsf_session* p_sess)
{
int sock_fd = vsf_privop_get_ftp_port_sock(p_sess);
priv_sock_send_result(p_sess, PRIV_SOCK_RESULT_OK);
priv_sock_parent_send_fd(p_sess, sock_fd);
vsf_sysutil_close(sock_fd);
}

16
postprivparent.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef VSF_LOGINPRIVPARENT_H
#define VSF_LOGINPRIVPARENT_H
struct vsf_session;
/* vsf_priv_parent_postlogin()
* PURPOSE
* Called in the two process security model to commence "listening" for
* requests from the unprivileged child.
* PARAMETERS
* p_sess - the current session object
*/
void vsf_priv_parent_postlogin(struct vsf_session* p_sess);
#endif /* VSF_LOGINPRIVPARENT_H */

138
prelogin.c Normal file
View File

@ -0,0 +1,138 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* prelogin.c
*
* Code to parse the FTP protocol prior to a successful login.
*/
#include "prelogin.h"
#include "ftpcmdio.h"
#include "ftpcodes.h"
#include "str.h"
#include "vsftpver.h"
#include "tunables.h"
#include "oneprocess.h"
#include "twoprocess.h"
#include "sysdeputil.h"
#include "sysutil.h"
#include "session.h"
/* Functions used */
static void emit_greeting(struct vsf_session* p_sess);
static void parse_username_password(struct vsf_session* p_sess);
static void handle_user_command(struct vsf_session* p_sess);
static void handle_pass_command(struct vsf_session* p_sess);
void
init_connection(struct vsf_session* p_sess)
{
if (tunable_setproctitle_enable)
{
vsf_sysutil_setproctitle("not logged in");
}
/* Before we talk to the remote, make sure an alarm is set up in case
* writing the initial greetings should block.
*/
vsf_cmdio_set_alarm(p_sess);
emit_greeting(p_sess);
parse_username_password(p_sess);
}
static void
emit_greeting(struct vsf_session* p_sess)
{
if (tunable_ftpd_banner == 0)
{
vsf_cmdio_write(p_sess, FTP_GREET, "ready, dude (vsFTPd " VSF_VERSION
": beat me, break me)");
}
else
{
vsf_cmdio_write(p_sess, FTP_GREET, tunable_ftpd_banner);
}
}
static void
parse_username_password(struct vsf_session* p_sess)
{
while (1)
{
vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str,
&p_sess->ftp_arg_str, 1);
if (str_equal_text(&p_sess->ftp_cmd_str, "USER"))
{
handle_user_command(p_sess);
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "PASS"))
{
handle_pass_command(p_sess);
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "QUIT"))
{
vsf_cmdio_write(p_sess, FTP_GOODBYE, "Goodbye.");
vsf_sysutil_exit(0);
}
else
{
vsf_cmdio_write(p_sess, FTP_LOGINERR,
"Please login with USER and PASS.");
}
}
}
static void
handle_user_command(struct vsf_session* p_sess)
{
/* SECURITY: If we're in anonymous only-mode, immediately reject
* non-anonymous usernames in the hope we save passwords going plaintext
* over the network
*/
str_copy(&p_sess->user_str, &p_sess->ftp_arg_str);
str_upper(&p_sess->ftp_arg_str);
if (!tunable_local_enable &&
!str_equal_text(&p_sess->ftp_arg_str, "FTP") &&
!str_equal_text(&p_sess->ftp_arg_str, "ANONYMOUS"))
{
vsf_cmdio_write(p_sess, FTP_LOGINERR,
"This FTP server is anonymous only.");
str_empty(&p_sess->user_str);
return;
}
if (!str_isempty(&p_sess->userlist_str))
{
int located = str_contains_line(&p_sess->userlist_str, &p_sess->user_str);
if ((located && tunable_userlist_deny) ||
(!located && !tunable_userlist_deny))
{
vsf_cmdio_write(p_sess, FTP_LOGINERR, "Permission denied.");
str_empty(&p_sess->user_str);
return;
}
}
vsf_cmdio_write(p_sess, FTP_GIVEPWORD, "Please specify the password.");
}
static void
handle_pass_command(struct vsf_session* p_sess)
{
if (str_isempty(&p_sess->user_str))
{
vsf_cmdio_write(p_sess, FTP_NEEDUSER, "Login with USER first.");
return;
}
/* These login calls never return if successful */
if (tunable_one_process_model)
{
vsf_one_process_login(p_sess, &p_sess->ftp_arg_str);
}
else
{
vsf_two_process_login(p_sess, &p_sess->ftp_arg_str);
}
vsf_cmdio_write(p_sess, FTP_LOGINERR, "Login incorrect.");
str_empty(&p_sess->user_str);
/* FALLTHRU if login fails */
}

16
prelogin.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef VSF_PRELOGIN_H
#define VSF_PRELOGIN_H
struct vsf_session;
/* init_connection()
* PURPOSE
* Called as an entry point to FTP protocol processing, when a client connects.
* This function will emit the FTP greeting, then start talking FTP protocol
* to the client.
* PARAMETERS
* p_sess - the current session object
*/
void init_connection(struct vsf_session* p_sess);
#endif /* VSF_PRELOGIN_H */

206
privops.c Normal file
View File

@ -0,0 +1,206 @@
/*
* Part of Very Secure FTPd
* License: GPL
* Author: Chris Evans
* privops.c
*
* Code implementing the privileged operations that the unprivileged client
* might request.
* Look for suitable paranoia in this file.
*/
#include "privops.h"
#include "session.h"
#include "sysdeputil.h"
#include "sysutil.h"
#include "utility.h"
#include "str.h"
#include "tunables.h"
#include "defs.h"
#include "logging.h"
/* File private functions */
static enum EVSFPrivopLoginResult handle_anonymous_login(
struct vsf_session* p_sess, const struct mystr* p_pass_str);
static enum EVSFPrivopLoginResult handle_local_login(
struct vsf_session* p_sess, const struct mystr* p_user_str,
const struct mystr* p_pass_str);
static void setup_username_globals(struct vsf_session* p_sess,
const struct mystr* p_str);
static enum EVSFPrivopLoginResult handle_login(
struct vsf_session* p_sess, const struct mystr* p_user_str,
const struct mystr* p_pass_str);
int
vsf_privop_get_ftp_port_sock(struct vsf_session* p_sess)
{
static struct vsf_sysutil_sockaddr* p_sockaddr;
struct vsf_sysutil_ipv4port the_port;
int retval;
int s = vsf_sysutil_get_ipv4_sock();
vsf_sysutil_activate_reuseaddr(s);
vsf_sysutil_sockaddr_alloc_ipv4(&p_sockaddr);
the_port = vsf_sysutil_ipv4port_from_int(tunable_ftp_data_port);
vsf_sysutil_sockaddr_set_port(p_sockaddr, the_port);
vsf_sysutil_sockaddr_set_ipaddr(p_sockaddr,
vsf_sysutil_sockaddr_get_ipaddr(p_sess->p_local_addr));
retval = vsf_sysutil_bind(s, p_sockaddr);
if (retval != 0)
{
die("vsf_sysutil_bind");
}
return s;
}
void
vsf_privop_do_file_chown(struct vsf_session* p_sess, int fd)
{
static struct vsf_sysutil_statbuf* s_p_statbuf;
vsf_sysutil_fstat(fd, &s_p_statbuf);
/* Drop it like a hot potato unless it's a regular file owned by
* the the anonymous ftp user
*/
if (p_sess->anon_ftp_uid == -1 || p_sess->anon_upload_chown_uid == -1 ||
!vsf_sysutil_statbuf_is_regfile(s_p_statbuf) ||
vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->anon_ftp_uid)
{
die("invalid fd in cmd_process_chown");
}
/* SECURITY! You need an OS which strips SUID/SGID bits on chown(),
* otherwise a compromise of the FTP user will lead to compromise of
* the "anon_upload_chown_uid" user (think chmod +s).
*/
vsf_sysutil_fchown(fd, p_sess->anon_upload_chown_uid, -1);
}
enum EVSFPrivopLoginResult
vsf_privop_do_login(struct vsf_session* p_sess,
const struct mystr* p_pass_str)
{
enum EVSFPrivopLoginResult result =
handle_login(p_sess, &p_sess->user_str, p_pass_str);
vsf_log_start_entry(p_sess, kVSFLogEntryLogin);
if (result == kVSFLoginFail)
{
vsf_log_do_log(p_sess, 0);
}
else
{
vsf_log_do_log(p_sess, 1);
}
return result;
}
static enum EVSFPrivopLoginResult
handle_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
const struct mystr* p_pass_str)
{
/* Do not assume PAM can cope with dodgy input, even though it
* almost certainly can.
*/
int anonymous_login = 0;
unsigned int len = str_getlen(p_user_str);
if (len == 0 || len > VSFTP_USERNAME_MAX)
{
return kVSFLoginFail;
}
/* Throw out dodgy start characters */
if (!vsf_sysutil_isalnum(str_get_char_at(p_user_str, 0)))
{
return kVSFLoginFail;
}
/* Throw out non-printable characters and space in username */
if (str_contains_space(p_user_str) ||
str_contains_unprintable(p_user_str))
{
return kVSFLoginFail;
}
/* Throw out excessive length passwords */
len = str_getlen(p_pass_str);
if (len > VSFTP_PASSWORD_MAX)
{
return kVSFLoginFail;
}
/* Check for an anonymous login or "real" login */
if (tunable_anonymous_enable)
{
struct mystr upper_str = INIT_MYSTR;
str_copy(&upper_str, p_user_str);
str_upper(&upper_str);
if (str_equal_text(&upper_str, "FTP") ||
str_equal_text(&upper_str, "ANONYMOUS"))
{
anonymous_login = 1;
}
str_free(&upper_str);
}
{
enum EVSFPrivopLoginResult result = kVSFLoginFail;
if (anonymous_login)
{
result = handle_anonymous_login(p_sess, p_pass_str);
}
else
{
result = handle_local_login(p_sess, p_user_str, p_pass_str);
}
str_free(&p_sess->banned_email_str);
return result;
}
}
static enum EVSFPrivopLoginResult
handle_anonymous_login(struct vsf_session* p_sess,
const struct mystr* p_pass_str)
{
if (!str_isempty(&p_sess->banned_email_str) &&
str_contains_line(&p_sess->banned_email_str, p_pass_str))
{
return kVSFLoginFail;
}
/* Store the anonymous identity string */
str_copy(&p_sess->anon_pass_str, p_pass_str);
if (str_isempty(&p_sess->anon_pass_str))
{
str_alloc_text(&p_sess->anon_pass_str, "?");
}
/* "Fix" any characters which might upset the log processing */
str_replace_char(&p_sess->anon_pass_str, ' ', '_');
str_replace_char(&p_sess->anon_pass_str, '\n', '?');
{
struct mystr ftp_username_str = INIT_MYSTR;
str_alloc_text(&ftp_username_str, tunable_ftp_username);
setup_username_globals(p_sess, &ftp_username_str);
str_free(&ftp_username_str);
}
return kVSFLoginAnon;
}
static enum EVSFPrivopLoginResult
handle_local_login(struct vsf_session* p_sess,
const struct mystr* p_user_str,
const struct mystr* p_pass_str)
{
if (!vsf_sysdep_check_auth(p_user_str, p_pass_str, &p_sess->remote_ip_str))
{
return kVSFLoginFail;
}
setup_username_globals(p_sess, p_user_str);
return kVSFLoginReal;
}
static void
setup_username_globals(struct vsf_session* p_sess, const struct mystr* p_str)
{
str_copy(&p_sess->user_str, p_str);
if (tunable_setproctitle_enable)
{
struct mystr prefix_str = INIT_MYSTR;
str_copy(&prefix_str, &p_sess->remote_ip_str);
str_append_char(&prefix_str, '/');
str_append_str(&prefix_str, p_str);
vsf_sysutil_set_proctitle_prefix(&prefix_str);
str_free(&prefix_str);
}
}

50
privops.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef VSF_PRIVOPS_H
#define VSF_PRIVOPS_H
struct mystr;
struct vsf_session;
/* vsf_privop_get_ftp_port_sock()
* PURPOSE
* Return a network socket bound to a privileged port (less than 1024).
* PARAMETERS
* p_sess - the current session object
* RETURNS
* A file descriptor which is a socket bound to the privileged port.
*/
int vsf_privop_get_ftp_port_sock(struct vsf_session* p_sess);
/* vsf_privop_do_file_chown()
* PURPOSE
* Takes a file owned by the unprivileged FTP user, and change the ownership
* to the value defined in the config file.
* PARAMETERS
* p_sess - the current session object
* fd - the file descriptor of the regular file
*/
void vsf_privop_do_file_chown(struct vsf_session* p_sess, int fd);
enum EVSFPrivopLoginResult
{
kVSFLoginNull = 0,
kVSFLoginFail,
kVSFLoginAnon,
kVSFLoginReal
};
/* vsf_privop_do_login()
* PURPOSE
* Check if the supplied username/password combination is valid. This
* interface caters for checking both anonymous and real logins.
* PARAMETERS
* p_sess - the current session object
* p_pass_str - the proposed password
* RETURNS
* kVSFLoginFail - access denied
* kVSFLoginAnon - anonymous login credentials OK
* kVSFLoginReal - real login credentials OK
*/
enum EVSFPrivopLoginResult vsf_privop_do_login(
struct vsf_session* p_sess, const struct mystr* p_pass_str);
#endif /* VSF_PRIVOPS_H */

132
privsock.c Normal file
View File

@ -0,0 +1,132 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* privsock.c
*
* This file contains code for a simple message and file descriptor passing
* API, over a pair of UNIX sockets.
* The messages are typically travelling across a privilege boundary, with
* heavy distrust of messages on the side of more privilege.
*/
#include "privsock.h"
#include "utility.h"
#include "defs.h"
#include "str.h"
#include "netstr.h"
#include "sysutil.h"
#include "sysdeputil.h"
#include "secbuf.h"
#include "session.h"
void
priv_sock_init(struct vsf_session* p_sess)
{
const struct vsf_sysutil_socketpair_retval retval =
vsf_sysutil_unix_dgram_socketpair();
if (p_sess->privsock_inited)
{
bug("priv_sock_init called twice");
}
p_sess->parent_fd = retval.socket_one;
p_sess->child_fd = retval.socket_two;
p_sess->privsock_inited = 1;
}
void
priv_sock_send_cmd(struct vsf_session* p_sess, char cmd)
{
/* DGRAM socket -> message boundaries retained -> use plain write */
int retval = vsf_sysutil_write(p_sess->child_fd, &cmd, sizeof(cmd));
if (retval != sizeof(cmd))
{
die("vsf_sysutil_write");
}
}
void
priv_sock_send_str(struct vsf_session* p_sess, const struct mystr* p_str)
{
struct mystr null_term_str = INIT_MYSTR;
str_copy(&null_term_str, p_str);
str_append_char(&null_term_str, '\0');
str_netfd_write(&null_term_str, p_sess->child_fd);
str_free(&null_term_str);
}
char
priv_sock_get_result(struct vsf_session* p_sess)
{
char res;
/* DGRAM socket -> message boundaries retained -> use plain read */
int retval = vsf_sysutil_read(p_sess->child_fd, &res, sizeof(res));
if (retval != sizeof(res))
{
die("vsf_sysutil_read");
}
return res;
}
char
priv_sock_get_cmd(struct vsf_session* p_sess)
{
char res;
/* DGRAM socket -> message boundaries retained -> use plain read */
int retval = vsf_sysutil_read(p_sess->parent_fd, &res, sizeof(res));
if (retval != sizeof(res))
{
die("vsf_sysutil_read");
}
return res;
}
void
priv_sock_get_str(struct vsf_session* p_sess, struct mystr* p_dest)
{
static char* s_p_privsock_str_buf;
if (s_p_privsock_str_buf == 0)
{
vsf_secbuf_alloc(&s_p_privsock_str_buf, VSFTP_PRIVSOCK_MAXSTR);
}
/* XXX - alert - will return truncated string if sender embedded a \0 */
str_netfd_alloc(p_dest, p_sess->parent_fd, '\0', s_p_privsock_str_buf,
VSFTP_PRIVSOCK_MAXSTR);
}
void
priv_sock_send_result(struct vsf_session* p_sess, char res)
{
/* DGRAM socket -> message boundaries retained -> use plain write */
int retval = vsf_sysutil_write(p_sess->parent_fd, &res, sizeof(res));
if (retval != sizeof(res))
{
die("vsf_sysutil_write");
}
}
void
priv_sock_child_send_fd(struct vsf_session* p_sess, int fd)
{
vsf_sysutil_send_fd(p_sess->child_fd, fd);
}
void
priv_sock_parent_send_fd(struct vsf_session* p_sess, int fd)
{
vsf_sysutil_send_fd(p_sess->parent_fd, fd);
}
int
priv_sock_parent_recv_fd(struct vsf_session* p_sess)
{
return vsf_sysutil_recv_fd(p_sess->parent_fd);
}
int
priv_sock_child_recv_fd(struct vsf_session* p_sess)
{
return vsf_sysutil_recv_fd(p_sess->child_fd);
}

117
privsock.h Normal file
View File

@ -0,0 +1,117 @@
#ifndef VSF_PRIVSOCK_H
#define VSF_PRIVSOCK_H
struct mystr;
struct vsf_session;
/* priv_sock_init()
* PURPOSE
* Initialize the priv_sock system, by opening the communications sockets.
* PARAMETERS
* p_sess - the current session object
*/
void priv_sock_init(struct vsf_session* p_sess);
/* priv_sock_send_cmd()
* PURPOSE
* Sends a command to the privileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* cmd - the command to send
*/
void priv_sock_send_cmd(struct vsf_session* p_sess, char cmd);
/* priv_sock_send_str()
* PURPOSE
* Sends a string to the privileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* p_str - the string to send
*/
void priv_sock_send_str(struct vsf_session* p_sess, const struct mystr* p_str);
/* priv_sock_get_result()
* PURPOSE
* Receives a response from the privileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* RETURNS
* The response code.
*/
char priv_sock_get_result(struct vsf_session* p_sess);
/* priv_sock_get_cmd()
* PURPOSE
* Receives a command on the privileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* RETURNS
* The command that was sent.
*/
char priv_sock_get_cmd(struct vsf_session* p_sess);
/* priv_sock_get_str()
* PURPOSE
* Receives a string on the privileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* p_dest - where to copy the received string
*/
void priv_sock_get_str(struct vsf_session* p_sess, struct mystr* p_dest);
/* priv_sock_send_result()
* PURPOSE
* Sends a command result to the unprivileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* res - the result to send
*/
void priv_sock_send_result(struct vsf_session* p_sess, char res);
/* priv_sock_child_send_fd()
* PURPOSE
* Sends a file descriptor to the privileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* fd - the descriptor to send
*/
void priv_sock_child_send_fd(struct vsf_session* p_sess, int fd);
/* priv_sock_parent_recv_fd()
* PURPOSE
* Receives a file descriptor on the privileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* RETURNS
* The received file descriptor
*/
int priv_sock_parent_recv_fd(struct vsf_session* p_sess);
/* priv_sock_parent_send_fd()
* PURPOSE
* Sends a file descriptor to the unprivileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* fd - the descriptor to send
*/
void priv_sock_parent_send_fd(struct vsf_session* p_sess, int fd);
/* priv_sock_child_recv_fd()
* PURPOSE
* Receives a file descriptor on the unprivileged side of the channel.
* PARAMETERS
* p_sess - the current session object
* RETURNS
* The received file descriptor
*/
int priv_sock_child_recv_fd(struct vsf_session* p_sess);
#define PRIV_SOCK_LOGIN 1
#define PRIV_SOCK_CHOWN 2
#define PRIV_SOCK_GET_DATA_SOCK 3
#define PRIV_SOCK_RESULT_OK 1
#define PRIV_SOCK_RESULT_BAD 2
#endif /* VSF_PRIVSOCK_H */

89
secbuf.c Normal file
View File

@ -0,0 +1,89 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* secbuf.c
*
* Here are some routines providing the (possibly silly) concept of a secure
* buffer. A secure buffer may not be overflowed. A single byte overflow
* will cause the program to safely terminate.
*/
#include "secbuf.h"
#include "utility.h"
#include "sysutil.h"
#include "sysdeputil.h"
void
vsf_secbuf_alloc(char** p_ptr, unsigned int size)
{
unsigned int page_offset;
unsigned int round_up;
char* p_mmap;
char* p_no_access_page;
unsigned int page_size = vsf_sysutil_getpagesize();
/* Free any previous buffer */
vsf_secbuf_free(p_ptr);
/* Round up to next page size */
page_offset = size % page_size;
if (page_offset)
{
unsigned int num_pages = size / page_size;
num_pages++;
round_up = num_pages * page_size;
}
else
{
/* Allocation is on a page-size boundary */
round_up = size;
}
/* Add on another two pages to make inaccessible */
round_up += page_size * 2;
p_mmap = vsf_sysutil_map_anon_pages(round_up);
/* Map the first and last page inaccessible */
p_no_access_page = p_mmap + round_up - page_size;
vsf_sysutil_memprotect(p_no_access_page, page_size, kVSFSysUtilMapProtNone);
/* Before we make the "before" page inaccessible, store the size in it.
* A little hack so that we don't need to explicitly be passed the size
* when freeing an existing secure buffer
*/
*((unsigned int*)p_mmap) = round_up;
p_no_access_page = p_mmap;
vsf_sysutil_memprotect(p_no_access_page, page_size, kVSFSysUtilMapProtNone);
p_mmap += page_size;
if (page_offset)
{
p_mmap += (page_size - page_offset);
}
*p_ptr = p_mmap;
}
void
vsf_secbuf_free(char** p_ptr)
{
unsigned int map_size;
unsigned long page_offset;
char* p_mmap = *p_ptr;
unsigned int page_size = vsf_sysutil_getpagesize();
if (p_mmap == 0)
{
return;
}
/* Calculate the actual start of the mmap region */
page_offset = (unsigned long) p_mmap % page_size;
if (page_offset)
{
p_mmap -= page_offset;
}
p_mmap -= page_size;
/* First make the first page readable so we can get the size */
vsf_sysutil_memprotect(p_mmap, page_size, kVSFSysUtilMapProtReadOnly);
/* Extract the mapping size */
map_size = *((unsigned int*)p_mmap);
/* Lose the mapping */
vsf_sysutil_memunmap(p_mmap, map_size);
}

27
secbuf.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef VSF_SECBUF_H
#define VSF_SECBUF_H
/* vsf_secbuf_alloc()
* PURPOSE
* Allocate a "secure buffer". A secure buffer is one which will attempt to
* catch out of bounds accesses by crashing the program (rather than
* corrupting memory). It works by using UNIX memory protection. It isn't
* foolproof.
* PARAMETERS
* p_ptr - pointer to a pointer which is to contain the secure buffer.
* Any previous buffer pointed to is freed.
* size - size in bytes required for the secure buffer.
*/
void vsf_secbuf_alloc(char** p_ptr, unsigned int size);
/* vsf_secbuf_free()
* PURPOSE
* Frees a "secure buffer".
* PARAMETERS
* p_ptr - pointer to a pointer containing the buffer to be freed. The
* buffer pointer is nullified by this call.
*/
void vsf_secbuf_free(char** p_ptr);
#endif /* VSF_SECBUF_H */

94
secutil.c Normal file
View File

@ -0,0 +1,94 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* secutil.c
*/
#include "secutil.h"
#include "str.h"
#include "sysutil.h"
#include "sysstr.h"
#include "utility.h"
#include "sysdeputil.h"
void
vsf_secutil_change_credentials(const struct mystr* p_user_str,
const struct mystr* p_dir_str,
int do_chroot,
int activate_supplementary_groups,
unsigned int caps)
{
struct vsf_sysutil_user* p_user;
if (!vsf_sysutil_running_as_root())
{
bug("vsf_secutil_change_credentials: not running as root");
}
p_user = str_getpwnam(p_user_str);
if (p_user == 0)
{
die("str_getpwnam");
}
{
struct mystr dir_str = INIT_MYSTR;
/* Work out where the chroot() jail is */
if (p_dir_str == 0)
{
str_alloc_text(&dir_str, vsf_sysutil_user_get_homedir(p_user));
}
else
{
str_copy(&dir_str, p_dir_str);
}
/* Sort out supplementary groups before the chroot(). We need to access
* /etc/groups
*/
if (activate_supplementary_groups)
{
vsf_sysutil_initgroups(p_user);
}
else
{
vsf_sysutil_clear_supp_groups();
}
/* Always do the chdir() regardless of whether we are chroot()'ing */
{
int retval = str_chdir(&dir_str);
if (retval != 0)
{
die("chdir");
}
/* Do the chroot() if required */
if (do_chroot)
{
vsf_sysutil_chroot(".");
}
}
str_free(&dir_str);
}
/* Handle capabilities */
if (caps)
{
if (!vsf_sysdep_has_capabilities())
{
/* Need privilege but OS has no capabilities - have to keep root */
return;
}
if (!vsf_sysdep_has_capabilities_as_non_root())
{
vsf_sysdep_adopt_capabilities(caps);
return;
}
vsf_sysdep_keep_capabilities();
}
/* Set group id */
vsf_sysutil_setgid(p_user);
/* Finally set user id */
vsf_sysutil_setuid(p_user);
if (caps)
{
vsf_sysdep_adopt_capabilities(caps);
}
}

29
secutil.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef VSF_SECUTIL_H
#define VSF_SECUTIL_H
struct mystr;
/* vsf_secutil_change_credentials()
* PURPOSE
* This function securely switches process credentials to the user specified.
* There are options to enter a chroot() jail, and supplementary groups may
* or may not be activated.
* PARAMETERS
* p_user_str - the name of the user to become
* p_dir_str - the directory to chdir() and possibly chroot() to.
* (if NULL, the user's home directory is used)
* do_chroot - if non-zero, chroot() the new user into the directory
* activate_supplementary_groups -
* if non-zero, activate any supplementary groups
* caps - bitmap of capabilities to adopt. NOTE, if the underlying
* OS does not support capabilities as a non-root user, and
* the capability bitset is non-empty, then root privileges
* will have to be retained.
*/
void vsf_secutil_change_credentials(const struct mystr* p_user_str,
const struct mystr* p_dir_str,
int do_chroot,
int activate_supplementary_groups,
unsigned int caps);
#endif /* VSF_SECUTIL_H */

71
session.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef VSF_SESSION_H
#define VSF_SESSION_H
#ifndef VSFTP_STR_H
#include "str.h"
#endif
struct vsf_sysutil_sockaddr;
struct mystr_list;
/* This struct contains variables specific to the state of the current FTP
* session
*/
struct vsf_session
{
/* Details of the control connection */
struct vsf_sysutil_sockaddr* p_local_addr;
struct vsf_sysutil_sockaddr* p_remote_addr;
/* Details of the data connection */
int pasv_listen_fd;
struct vsf_sysutil_sockaddr* p_port_sockaddr;
int data_fd;
int data_progress;
unsigned int bw_rate_max;
long bw_send_start_sec;
long bw_send_start_usec;
/* Details of the login */
int is_anonymous;
struct mystr user_str;
struct mystr anon_pass_str;
/* Details of the FTP protocol state */
unsigned long restart_pos;
int is_ascii;
struct mystr rnfr_filename_str;
int abor_received;
/* Details of FTP session state */
struct mystr_list* p_visited_dir_list;
/* Details of userids which are interesting to us */
int anon_ftp_uid;
int anon_upload_chown_uid;
/* Things we need to cache before we chroot() */
struct mystr banned_email_str;
struct mystr userlist_str;
/* Logging related details */
int log_fd;
struct mystr remote_ip_str;
unsigned long log_type;
long log_start_sec;
long log_start_usec;
struct mystr log_str;
unsigned long transfer_size;
/* Buffers */
struct mystr ftp_cmd_str;
struct mystr ftp_arg_str;
/* Parent<->child comms channel */
int privsock_inited;
int parent_fd;
int child_fd;
};
#endif /* VSF_SESSION_H */

618
str.c Normal file
View File

@ -0,0 +1,618 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* str.c
*
* Generic string handling functions. The fact that a string is implemented
* internally using a buffer is not exposed in the API. If you can't see
* the buffers, you can't handle them in a screwed way. Or so goes the
* theory, anyway...
*/
/* Anti-lamer measures deployed, sir! */
#define PRIVATE_HANDS_OFF_p_buf p_buf
#define PRIVATE_HANDS_OFF_len len
#define PRIVATE_HANDS_OFF_alloc_bytes alloc_bytes
#include "str.h"
/* Ick. Its for die() */
#include "utility.h"
#include "sysutil.h"
/* File local functions */
static void str_split_text_common(struct mystr* p_src, struct mystr* p_rhs,
const char* p_text, int is_reverse);
static int str_equal_internal(const char* p_buf1, unsigned int buf1_len,
const char* p_buf2, unsigned int buf2_len);
/* Private functions */
static void
s_setbuf(struct mystr* p_str, char* p_newbuf)
{
if (p_str->p_buf != 0)
{
bug("p_buf not NULL when setting it");
}
p_str->p_buf = p_newbuf;
}
void
private_str_alloc_memchunk(struct mystr* p_str, const char* p_src,
unsigned int len)
{
/* Make sure this will fit in the buffer */
unsigned int buf_needed = len + 1;
if (buf_needed > p_str->alloc_bytes)
{
str_free(p_str);
s_setbuf(p_str, vsf_sysutil_malloc(buf_needed));
p_str->alloc_bytes = buf_needed;
}
vsf_sysutil_memcpy(p_str->p_buf, p_src, len);
p_str->p_buf[len] = '\0';
p_str->len = len;
}
void
private_str_append_memchunk(struct mystr* p_str, const char* p_src,
unsigned int len)
{
unsigned int buf_needed = p_str->len + len + 1;
if (buf_needed > p_str->alloc_bytes)
{
p_str->p_buf = vsf_sysutil_realloc(p_str->p_buf, buf_needed);
p_str->alloc_bytes = buf_needed;
}
vsf_sysutil_memcpy(p_str->p_buf + p_str->len, p_src, len);
p_str->p_buf[p_str->len + len] = '\0';
p_str->len += len;
}
/* Public functions */
void
str_alloc_text(struct mystr* p_str, const char* p_src)
{
unsigned int len = vsf_sysutil_strlen(p_src);
private_str_alloc_memchunk(p_str, p_src, len);
}
void
str_copy(struct mystr* p_dest, const struct mystr* p_src)
{
private_str_alloc_memchunk(p_dest, p_src->p_buf, p_src->len);
}
const char*
str_strdup(const struct mystr* p_str)
{
return vsf_sysutil_strdup(str_getbuf(p_str));
}
void
str_alloc_alt_term(struct mystr* p_str, const char* p_src, char term)
{
const char* p_search = p_src;
unsigned int len = 0;
while (*p_search != term)
{
p_search++;
len++;
}
private_str_alloc_memchunk(p_str, p_src, len);
}
void
str_alloc_ulong(struct mystr* p_str, unsigned long the_long)
{
str_alloc_text(p_str, vsf_sysutil_ulong_to_str(the_long));
}
void
str_free(struct mystr* p_str)
{
if (p_str->p_buf != 0)
{
vsf_sysutil_free(p_str->p_buf);
}
p_str->p_buf = 0;
p_str->len = 0;
p_str->alloc_bytes = 0;
}
void
str_empty(struct mystr* p_str)
{
p_str->len = 0;
}
void
str_trunc(struct mystr* p_str, unsigned int trunc_len)
{
if (trunc_len >= p_str->len)
{
bug("trunc_len not smaller than len in str_trunc");
}
p_str->len = trunc_len;
p_str->p_buf[p_str->len] = '\0';
}
void
str_reserve(struct mystr* p_str, unsigned int res_len)
{
if (res_len > p_str->alloc_bytes)
{
p_str->p_buf = vsf_sysutil_realloc(p_str->p_buf, res_len);
p_str->alloc_bytes = res_len;
}
}
int
str_isempty(const struct mystr* p_str)
{
return (p_str->len == 0);
}
unsigned int
str_getlen(const struct mystr* p_str)
{
return p_str->len;
}
const char*
str_getbuf(const struct mystr* p_str)
{
if (p_str->p_buf == 0)
{
if (p_str->len != 0 || p_str->alloc_bytes != 0)
{
bug("p_buf NULL and len or alloc_bytes != 0 in str_getbuf");
}
private_str_alloc_memchunk((struct mystr*)p_str, 0, 0);
}
return p_str->p_buf;
}
int
str_strcmp(const struct mystr* p_str1, const struct mystr* p_str2)
{
return str_equal_internal(p_str1->p_buf, p_str1->len,
p_str2->p_buf, p_str2->len);
}
static int
str_equal_internal(const char* p_buf1, unsigned int buf1_len,
const char* p_buf2, unsigned int buf2_len)
{
int retval;
unsigned int minlen = buf1_len;
if (buf2_len < minlen)
{
minlen = buf2_len;
}
retval = vsf_sysutil_memcmp(p_buf1, p_buf2, minlen);
if (retval != 0 || buf1_len == buf2_len)
{
return retval;
}
/* Strings equal but lengths differ. The greater one, then, is the longer */
return (int) (buf1_len - buf2_len);
}
int
str_equal(const struct mystr* p_str1, const struct mystr* p_str2)
{
return (str_strcmp(p_str1, p_str2) == 0);
}
int
str_equal_text(const struct mystr* p_str, const char* p_text)
{
unsigned int cmplen = vsf_sysutil_strlen(p_text);
return (str_equal_internal(p_str->p_buf, p_str->len, p_text, cmplen) == 0);
}
void
str_append_str(struct mystr* p_str, const struct mystr* p_other)
{
private_str_append_memchunk(p_str, p_other->p_buf, p_other->len);
}
void
str_append_text(struct mystr* p_str, const char* p_src)
{
unsigned int len = vsf_sysutil_strlen(p_src);
private_str_append_memchunk(p_str, p_src, len);
}
void
str_append_char(struct mystr* p_str, char the_char)
{
private_str_append_memchunk(p_str, &the_char, sizeof(the_char));
}
void
str_append_ulong(struct mystr* p_str, unsigned long the_ulong)
{
str_append_text(p_str, vsf_sysutil_ulong_to_str(the_ulong));
}
void
str_append_double(struct mystr* p_str, double the_double)
{
str_append_text(p_str, vsf_sysutil_double_to_str(the_double));
}
void
str_upper(struct mystr* p_str)
{
unsigned int i;
for (i=0; i < p_str->len; i++)
{
p_str->p_buf[i] = vsf_sysutil_toupper(p_str->p_buf[i]);
}
}
void
str_rpad(struct mystr* p_str, const unsigned int min_width)
{
unsigned int to_pad;
if (p_str->len >= min_width)
{
return;
}
to_pad = min_width - p_str->len;
while (to_pad--)
{
str_append_char(p_str, ' ');
}
}
void
str_lpad(struct mystr* p_str, const unsigned int min_width)
{
static struct mystr s_tmp_str;
unsigned int to_pad;
if (p_str->len >= min_width)
{
return;
}
to_pad = min_width - p_str->len;
str_empty(&s_tmp_str);
while (to_pad--)
{
str_append_char(&s_tmp_str, ' ');
}
str_append_str(&s_tmp_str, p_str);
str_copy(p_str, &s_tmp_str);
}
void
str_replace_char(struct mystr* p_str, char from, char to)
{
unsigned int i;
for (i=0; i < p_str->len; i++)
{
if (p_str->p_buf[i] == from)
{
p_str->p_buf[i] = to;
}
}
}
void
str_replace_text(struct mystr* p_str, const char* p_from, const char* p_to)
{
static struct mystr s_lhs_chunk_str;
static struct mystr s_rhs_chunk_str;
str_copy(&s_lhs_chunk_str, p_str);
str_free(p_str);
do
{
str_split_text(&s_lhs_chunk_str, &s_rhs_chunk_str, p_from);
/* Copy lhs to destination */
str_append_str(p_str, &s_lhs_chunk_str);
/* If this was a 'hit', append the 'to' text */
if (!str_isempty(&s_rhs_chunk_str))
{
str_append_text(p_str, p_to);
}
/* Current rhs becomes new lhs */
str_copy(&s_lhs_chunk_str, &s_rhs_chunk_str);
} while (!str_isempty(&s_lhs_chunk_str));
}
void
str_split_char(struct mystr* p_src, struct mystr* p_rhs, char c)
{
/* Just use str_split_text */
char ministr[2];
ministr[0] = c;
ministr[1] = '\0';
str_split_text(p_src, p_rhs, ministr);
}
void
str_split_char_reverse(struct mystr* p_src, struct mystr* p_rhs, char c)
{
/* Just use str_split_text_reverse */
char ministr[2];
ministr[0] = c;
ministr[1] = '\0';
str_split_text_reverse(p_src, p_rhs, ministr);
}
void
str_split_text(struct mystr* p_src, struct mystr* p_rhs, const char* p_text)
{
str_split_text_common(p_src, p_rhs, p_text, 0);
}
void
str_split_text_reverse(struct mystr* p_src, struct mystr* p_rhs,
const char* p_text)
{
str_split_text_common(p_src, p_rhs, p_text, 1);
}
static void
str_split_text_common(struct mystr* p_src, struct mystr* p_rhs,
const char* p_text, int is_reverse)
{
struct str_locate_result locate_result;
unsigned int index;
if (is_reverse)
{
locate_result = str_locate_text_reverse(p_src, p_text);
}
else
{
locate_result = str_locate_text(p_src, p_text);
}
/* Not found? */
if (!locate_result.found)
{
str_empty(p_rhs);
return;
}
index = locate_result.index;
if (index + vsf_sysutil_strlen(p_text) > p_src->len)
{
bug("index invalid in str_split_text");
}
/* Build rhs */
private_str_alloc_memchunk(p_rhs, p_src->p_buf + index + 1,
p_src->len - index - 1);
/* Build lhs */
str_trunc(p_src, index);
}
struct str_locate_result
str_locate_str(const struct mystr* p_str, const struct mystr* p_look_str)
{
return str_locate_text(p_str, str_getbuf(p_look_str));
}
struct str_locate_result
str_locate_str_reverse(const struct mystr* p_str,
const struct mystr* p_look_str)
{
return str_locate_text_reverse(p_str, str_getbuf(p_look_str));
}
struct str_locate_result
str_locate_char(const struct mystr* p_str, char look_char)
{
char look_str[2];
look_str[0] = look_char;
look_str[1] = '\0';
return str_locate_text(p_str, look_str);
}
struct str_locate_result
str_locate_text(const struct mystr* p_str, const char* p_text)
{
struct str_locate_result retval;
unsigned int i;
unsigned int text_len = vsf_sysutil_strlen(p_text);
retval.found = 0;
retval.index = 0;
if (text_len == 0 || text_len > p_str->len)
{
/* Not found */
return retval;
}
for (i=0; i <= (p_str->len - text_len); i++)
{
if (vsf_sysutil_memcmp(p_str->p_buf + i, p_text, text_len) == 0)
{
retval.found = 1;
retval.index = i;
return retval;
}
}
/* Not found */
return retval;
}
struct str_locate_result
str_locate_text_reverse(const struct mystr* p_str, const char* p_text)
{
struct str_locate_result retval;
unsigned int i;
unsigned int text_len = vsf_sysutil_strlen(p_text);
retval.found = 0;
retval.index = 0;
if (text_len == 0 || text_len > p_str->len)
{
return retval;
}
i = p_str->len - text_len;
/* Want to go through loop once even if i==0 */
while (1)
{
if (vsf_sysutil_memcmp(p_str->p_buf + i, p_text, text_len) == 0)
{
retval.found = 1;
retval.index = i;
return retval;
}
if (i == 0)
{
break;
}
i--;
}
/* Not found */
return retval;
}
void
str_left(const struct mystr* p_str, struct mystr* p_out, unsigned int chars)
{
if (chars > p_str->len)
{
bug("chars invalid in str_left");
}
private_str_alloc_memchunk(p_out, p_str->p_buf, chars);
}
void
str_right(const struct mystr* p_str, struct mystr* p_out, unsigned int chars)
{
unsigned int index = p_str->len - chars;
if (chars > p_str->len)
{
bug("chars invalid in str_right");
}
private_str_alloc_memchunk(p_out, p_str->p_buf + index, chars);
}
void
str_mid_to_end(const struct mystr* p_str, struct mystr* p_out,
unsigned int index)
{
if (index > p_str->len)
{
bug("invalid index in str_mid_to_end");
}
private_str_alloc_memchunk(p_out, p_str->p_buf + index, p_str->len - index);
}
char
str_get_char_at(const struct mystr* p_str, const unsigned int index)
{
if (index >= p_str->len)
{
bug("bad index in str_get_char_at");
}
return p_str->p_buf[index];
}
int
str_contains_space(const struct mystr* p_str)
{
unsigned int i;
for (i=0; i < p_str->len; i++)
{
if (vsf_sysutil_isspace(p_str->p_buf[i]))
{
return 1;
}
}
return 0;
}
int
str_contains_unprintable(const struct mystr* p_str)
{
unsigned int i;
for (i=0; i < p_str->len; i++)
{
if (!vsf_sysutil_isprint(p_str->p_buf[i]))
{
return 1;
}
}
return 0;
}
int
str_atoi(const struct mystr* p_str)
{
return vsf_sysutil_atoi(str_getbuf(p_str));
}
long
str_atol(const struct mystr* p_str)
{
return vsf_sysutil_atol(str_getbuf(p_str));
}
unsigned int
str_octal_to_uint(const struct mystr* p_str)
{
return vsf_sysutil_octal_to_uint(str_getbuf(p_str));
}
int
str_getline(const struct mystr* p_str, struct mystr* p_line_str,
unsigned int* p_pos)
{
unsigned int start_pos = *p_pos;
unsigned int curr_pos = start_pos;
unsigned int buf_len = str_getlen(p_str);
const char* p_buf = str_getbuf(p_str);
unsigned int out_len;
if (start_pos > buf_len)
{
bug("p_pos out of range in str_getline");
}
str_empty(p_line_str);
if (start_pos == buf_len)
{
return 0;
}
while (curr_pos < buf_len && p_buf[curr_pos] != '\n')
{
curr_pos++;
}
out_len = curr_pos - start_pos;
/* If we ended on a \n - skip it */
if (curr_pos < buf_len && p_buf[curr_pos] == '\n')
{
curr_pos++;
}
private_str_alloc_memchunk(p_line_str, p_buf + start_pos, out_len);
*p_pos = curr_pos;
return 1;
}
int
str_contains_line(const struct mystr* p_str, const struct mystr* p_line_str)
{
static struct mystr s_curr_line_str;
unsigned int pos = 0;
while (str_getline(p_str, &s_curr_line_str, &pos))
{
if (str_equal(&s_curr_line_str, p_line_str))
{
return 1;
}
}
return 0;
}
void
str_replace_unprintable(struct mystr* p_str, char new_char)
{
unsigned int i;
for (i=0; i < p_str->len; i++)
{
if (!vsf_sysutil_isprint(p_str->p_buf[i]))
{
p_str->p_buf[i] = new_char;
}
}
}

115
str.h Normal file
View File

@ -0,0 +1,115 @@
#ifndef VSFTP_STR_H
#define VSFTP_STR_H
/* TODO - document these functions ;-) */
struct mystr
{
char* PRIVATE_HANDS_OFF_p_buf;
/* Internally, EXCLUDES trailing null */
unsigned int PRIVATE_HANDS_OFF_len;
unsigned int PRIVATE_HANDS_OFF_alloc_bytes;
};
#define INIT_MYSTR \
{ (void*)0, 0, 0 }
#ifdef VSFTP_STRING_HELPER
#define str_alloc_memchunk private_str_alloc_memchunk
#endif
void private_str_alloc_memchunk(struct mystr* p_str, const char* p_src,
unsigned int len);
void str_alloc_text(struct mystr* p_str, const char* p_src);
/* NOTE: String buffer data does NOT include terminating character */
void str_alloc_alt_term(struct mystr* p_str, const char* p_src, char term);
void str_alloc_ulong(struct mystr* p_str, unsigned long the_ulong);
void str_copy(struct mystr* p_dest, const struct mystr* p_src);
const char* str_strdup(const struct mystr* p_str);
void str_empty(struct mystr* p_str);
void str_free(struct mystr* p_str);
void str_trunc(struct mystr* p_str, unsigned int trunc_len);
void str_reserve(struct mystr* p_str, unsigned int res_len);
int str_isempty(const struct mystr* p_str);
unsigned int str_getlen(const struct mystr* p_str);
const char* str_getbuf(const struct mystr* p_str);
int str_strcmp(const struct mystr* p_str1, const struct mystr* p_str2);
int str_equal(const struct mystr* p_str1, const struct mystr* p_str2);
int str_equal_text(const struct mystr* p_str, const char* p_text);
void str_append_str(struct mystr* p_str, const struct mystr* p_other);
void str_append_text(struct mystr* p_str, const char* p_src);
void str_append_ulong(struct mystr* p_str, unsigned long the_long);
void str_append_char(struct mystr* p_str, char the_char);
void str_append_double(struct mystr* p_str, double the_double);
void str_upper(struct mystr* p_str);
void str_rpad(struct mystr* p_str, const unsigned int min_width);
void str_lpad(struct mystr* p_str, const unsigned int min_width);
void str_replace_char(struct mystr* p_str, char from, char to);
void str_replace_text(struct mystr* p_str, const char* p_from,
const char* p_to);
void str_split_char(struct mystr* p_src, struct mystr* p_rhs, char c);
void str_split_char_reverse(struct mystr* p_src, struct mystr* p_rhs, char c);
void str_split_text(struct mystr* p_src, struct mystr* p_rhs,
const char* p_text);
void str_split_text_reverse(struct mystr* p_src, struct mystr* p_rhs,
const char* p_text);
struct str_locate_result
{
int found;
unsigned int index;
};
struct str_locate_result str_locate_char(
const struct mystr* p_str, char look_char);
struct str_locate_result str_locate_str(
const struct mystr* p_str, const struct mystr* p_look_str);
struct str_locate_result str_locate_str_reverse(
const struct mystr* p_str, const struct mystr* p_look_str);
struct str_locate_result str_locate_text(
const struct mystr* p_str, const char* p_text);
struct str_locate_result str_locate_text_reverse(
const struct mystr* p_str, const char* p_text);
void str_left(const struct mystr* p_str, struct mystr* p_out,
unsigned int chars);
void str_right(const struct mystr* p_str, struct mystr* p_out,
unsigned int chars);
void str_mid_to_end(const struct mystr* p_str, struct mystr* p_out,
unsigned int index);
char str_get_char_at(const struct mystr* p_str, const unsigned int index);
int str_contains_space(const struct mystr* p_str);
int str_contains_unprintable(const struct mystr* p_str);
void str_replace_unprintable(struct mystr* p_str, char new_char);
int str_atoi(const struct mystr* p_str);
long str_atol(const struct mystr* p_str);
unsigned int str_octal_to_uint(const struct mystr* p_str);
/* PURPOSE: Extract a line of text (delimited by \n or EOF) from a string
* buffer, starting at character position 'p_pos'. The extracted line will
* not contain the '\n' terminator.
*
* RETURNS: 0 if no more lines are available, 1 otherwise.
* The extracted text line is stored in 'p_line_str', which is
* emptied if there are no more lines. 'p_pos' is updated to point to the
* first character after the end of the line just extracted.
*/
int str_getline(const struct mystr* p_str, struct mystr* p_line_str,
unsigned int* p_pos);
/* PURPOSE: Detect whether or not a string buffer contains a specific line
* of text (delimited by \n or EOF).
*
* RETURNS: 1 if there is a matching line, 0 otherwise.
*/
int str_contains_line(const struct mystr* p_str,
const struct mystr* p_line_str);
#endif /* VSFTP_STR_H */

174
strlist.c Normal file
View File

@ -0,0 +1,174 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* strlist.c
*/
/* Anti-lamer measures deployed, sir! */
#define PRIVATE_HANDS_OFF_alloc_len alloc_len
#define PRIVATE_HANDS_OFF_list_len list_len
#define PRIVATE_HANDS_OFF_p_nodes p_nodes
#include "strlist.h"
#include "str.h"
#include "utility.h"
#include "sysutil.h"
struct mystr_list_node
{
struct mystr str;
struct mystr sort_key_str;
};
/* File locals */
static struct mystr s_null_str;
static int sort_compare_func(const void* p1, const void* p2);
static int sort_compare_func_reverse(const void* p1, const void* p2);
static int sort_compare_common(const void* p1, const void* p2, int reverse);
void
str_list_free(struct mystr_list* p_list)
{
unsigned int i;
for (i=0; i < p_list->list_len; ++i)
{
str_free(&p_list->p_nodes[i].str);
str_free(&p_list->p_nodes[i].sort_key_str);
}
p_list->list_len = 0;
p_list->alloc_len = 0;
if (p_list->p_nodes)
{
vsf_sysutil_free(p_list->p_nodes);
p_list->p_nodes = 0;
}
}
int
str_list_get_length(const struct mystr_list* p_list)
{
return p_list->list_len;
}
int
str_list_contains_str(const struct mystr_list* p_list,
const struct mystr* p_str)
{
unsigned int i;
for (i=0; i < p_list->list_len; ++i)
{
if (str_equal(p_str, &p_list->p_nodes[i].str))
{
return 1;
}
}
return 0;
}
void
str_list_add(struct mystr_list* p_list, const struct mystr* p_str,
const struct mystr* p_sort_key_str)
{
struct mystr_list_node* p_node;
/* Expand the node allocation if we have to */
if (p_list->list_len == p_list->alloc_len)
{
if (p_list->alloc_len == 0)
{
p_list->alloc_len = 32;
p_list->p_nodes = vsf_sysutil_malloc(p_list->alloc_len *
sizeof(struct mystr_list_node));
}
else
{
p_list->alloc_len *= 2;
p_list->p_nodes = vsf_sysutil_realloc(p_list->p_nodes,
p_list->alloc_len *
sizeof(struct mystr_list_node));
}
}
p_node = &p_list->p_nodes[p_list->list_len];
p_node->str = s_null_str;
p_node->sort_key_str = s_null_str;
str_copy(&p_node->str, p_str);
if (p_sort_key_str)
{
str_copy(&p_node->sort_key_str, p_sort_key_str);
}
p_list->list_len++;
}
void
str_list_sort(struct mystr_list* p_list, int reverse)
{
if (!reverse)
{
vsf_sysutil_qsort(p_list->p_nodes, p_list->list_len,
sizeof(struct mystr_list_node), sort_compare_func);
}
else
{
vsf_sysutil_qsort(p_list->p_nodes, p_list->list_len,
sizeof(struct mystr_list_node),
sort_compare_func_reverse);
}
}
static int
sort_compare_func(const void* p1, const void* p2)
{
return sort_compare_common(p1, p2, 0);
}
static int
sort_compare_func_reverse(const void* p1, const void* p2)
{
return sort_compare_common(p1, p2, 1);
}
static int
sort_compare_common(const void* p1, const void* p2, int reverse)
{
const struct mystr* p_cmp1;
const struct mystr* p_cmp2;
const struct mystr_list_node* p_node1 = (const struct mystr_list_node*) p1;
const struct mystr_list_node* p_node2 = (const struct mystr_list_node*) p2;
if (!str_isempty(&p_node1->sort_key_str))
{
p_cmp1 = &p_node1->sort_key_str;
}
else
{
p_cmp1 = &p_node1->str;
}
if (!str_isempty(&p_node2->sort_key_str))
{
p_cmp2 = &p_node2->sort_key_str;
}
else
{
p_cmp2 = &p_node2->str;
}
if (reverse)
{
return str_strcmp(p_cmp2, p_cmp1);
}
else
{
return str_strcmp(p_cmp1, p_cmp2);
}
}
const struct mystr*
str_list_get_pstr(const struct mystr_list* p_list, unsigned int index)
{
if (index >= p_list->list_len)
{
bug("index out of range in str_list_get_str");
}
return &p_list->p_nodes[index].str;
}

32
strlist.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef VSF_STRLIST_H
#define VSF_STRLIST_H
/* Forward declarations */
struct mystr;
struct mystr_list_node;
struct mystr_list
{
unsigned int PRIVATE_HANDS_OFF_alloc_len;
unsigned int PRIVATE_HANDS_OFF_list_len;
struct mystr_list_node* PRIVATE_HANDS_OFF_p_nodes;
};
#define INIT_STRLIST \
{ 0, 0, (void*)0 }
void str_list_free(struct mystr_list* p_list);
void str_list_add(struct mystr_list* p_list, const struct mystr* p_str,
const struct mystr* p_sort_key_str);
void str_list_sort(struct mystr_list* p_list, int reverse);
int str_list_get_length(const struct mystr_list* p_list);
int str_list_contains_str(const struct mystr_list* p_list,
const struct mystr* p_str);
const struct mystr* str_list_get_pstr(const struct mystr_list* p_list,
unsigned int index);
#endif /* VSF_STRLIST_H */

903
sysdeputil.c Normal file
View File

@ -0,0 +1,903 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* sysdeputil.c
*
* Highly system dependent utilities - e.g. authentication, capabilities.
*/
#include "sysdeputil.h"
#include "str.h"
#include "sysutil.h"
#include "utility.h"
#include "secbuf.h"
#include "defs.h"
#include "tunables.h"
/* For Linux, this adds nothing :-) */
#include "port/porting_junk.h"
/* For INT_MAX */
#include <limits.h>
/* For fd passing */
#include <sys/types.h>
#include <sys/socket.h>
/* For FreeBSD */
#include <sys/param.h>
#include <sys/uio.h>
/* Configuration.. here are the possibilities */
#undef VSF_SYSDEP_HAVE_CAPABILITIES
#undef VSF_SYSDEP_HAVE_SETKEEPCAPS
#undef VSF_SYSDEP_HAVE_LINUX_SENDFILE
#undef VSF_SYSDEP_HAVE_FREEBSD_SENDFILE
#undef VSF_SYSDEP_HAVE_HPUX_SENDFILE
#undef VSF_SYSDEP_HAVE_SETPROCTITLE
#undef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
#undef VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE
#undef VSF_SYSDEP_HAVE_MAP_ANON
#undef VSF_SYSDEP_NEED_OLD_FD_PASSING
#define VSF_SYSDEP_HAVE_PAM
#define VSF_SYSDEP_HAVE_SHADOW
#define VSF_SYSDEP_HAVE_USERSHELL
/* BEGIN config */
#ifdef __linux__
#define VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
#include <linux/version.h>
#if defined(LINUX_VERSION_CODE) && defined(KERNEL_VERSION)
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0))
#define VSF_SYSDEP_HAVE_CAPABILITIES
#define VSF_SYSDEP_HAVE_LINUX_SENDFILE
#include <sys/prctl.h>
#ifdef PR_SET_KEEPCAPS
#define VSF_SYSDEP_HAVE_SETKEEPCAPS
#endif
#endif
#endif
#endif
#if (defined(__FreeBSD__) && __FreeBSD__ >= 3)
#define VSF_SYSDEP_HAVE_FREEBSD_SENDFILE
#define VSF_SYSDEP_HAVE_SETPROCTITLE
#endif
#ifdef __hpux
#include <sys/socket.h>
#ifdef SF_DISCONNECT
#define VSF_SYSDEP_HAVE_HPUX_SENDFILE
#endif
#include <sys/param.h>
#include <sys/pstat.h>
#ifdef PSTAT_SETCMD
#define VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE
#endif
#endif
#include <unistd.h>
#include <sys/mman.h>
#ifdef MAP_ANON
#define VSF_SYSDEP_HAVE_MAP_ANON
#endif
#ifdef __sgi
#undef VSF_SYSDEP_HAVE_USERSHELL
#endif
#if (defined(__sgi) || defined(__hpux))
#define VSF_SYSDEP_NEED_OLD_FD_PASSING
#endif
/* END config */
/* PAM support - we include our own dummy version if the system lacks this */
#include <security/pam_appl.h>
/* No PAM? Try getspnam() with a getpwnam() fallback */
#ifndef VSF_SYSDEP_HAVE_PAM
/* This may hit our own "dummy" include and undef VSF_SYSDEP_HAVE_SHADOW */
#include <shadow.h>
#include <pwd.h>
#include <unistd.h>
#endif
#ifdef VSF_SYSDEP_HAVE_CAPABILITIES
#include <linux/capability.h>
#include <errno.h>
#include <syscall.h>
_syscall2(int, capset, cap_user_header_t, header, const cap_user_data_t, data)
/* Gross HACK to avoid warnings - linux headers overlap glibc headers */
#undef __NFDBITS
#undef __FDMASK
#endif /* VSF_SYSDEP_HAVE_CAPABILITIES */
#ifdef VSF_SYSDEP_HAVE_LINUX_SENDFILE
#include <sys/sendfile.h>
#elif defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE)
#include <sys/types.h>
#include <sys/socket.h>
#elif defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE)
#include <sys/socket.h>
#else /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */
#include <unistd.h>
#endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */
#ifdef VSF_SYSDEP_HAVE_SETPROCTITLE
#include <sys/types.h>
#include <unistd.h>
#endif
#ifdef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
extern char** environ;
static unsigned int s_proctitle_space = 0;
static int s_proctitle_inited = 0;
static char* s_p_proctitle = 0;
#endif
#ifndef VSF_SYSDEP_HAVE_MAP_ANON
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
static int s_zero_fd = -1;
#endif
/* File private functions/variables */
static int do_sendfile(const int out_fd, const int in_fd,
long* p_offset, unsigned int num_send);
static void vsf_sysutil_setproctitle_internal(const char* p_text);
static struct mystr s_proctitle_prefix_str;
#ifndef VSF_SYSDEP_HAVE_PAM
int
vsf_sysdep_check_auth(const struct mystr* p_user_str,
const struct mystr* p_pass_str,
const struct mystr* p_remote_host)
{
const char* p_shell;
const char* p_crypted;
(void) p_remote_host;
const struct passwd* p_pwd = getpwnam(str_getbuf(p_user_str));
if (p_pwd == NULL)
{
return 0;
}
#ifdef VSF_SYSDEP_HAVE_USERSHELL
while ((p_shell = getusershell()) != NULL)
{
if (!vsf_sysutil_strcmp(p_shell, p_pwd->pw_shell))
{
break;
}
}
endusershell();
if (p_shell == NULL)
{
return 0;
}
#endif
#ifdef VSF_SYSDEP_HAVE_SHADOW
{
const struct spwd* p_spwd = getspnam(str_getbuf(p_user_str));
if (p_spwd != NULL)
{
long curr_time;
int days;
vsf_sysutil_update_cached_time();
curr_time = vsf_sysutil_get_cached_time_sec();
days = curr_time / (60 * 60 * 24);
if (p_spwd->sp_expire > 0 && p_spwd->sp_expire < days)
{
return 0;
}
if (p_spwd->sp_lstchg > 0 && p_spwd->sp_max > 0 &&
p_spwd->sp_lstchg + p_spwd->sp_max < days)
{
return 0;
}
p_crypted = crypt(str_getbuf(p_pass_str), p_spwd->sp_pwdp);
if (!vsf_sysutil_strcmp(p_crypted, p_spwd->sp_pwdp))
{
return 1;
}
}
}
#endif /* VSF_SYSDEP_HAVE_SHADOW */
p_crypted = crypt(str_getbuf(p_pass_str), p_pwd->pw_passwd);
if (!vsf_sysutil_strcmp(p_crypted, p_pwd->pw_passwd))
{
return 1;
}
return 0;
}
#else /* VSF_SYSDEP_HAVE_PAM */
static struct mystr s_pword_str;
static int pam_conv_func(int nmsg, const struct pam_message** p_msg,
struct pam_response** p_reply, void* p_addata);
int
vsf_sysdep_check_auth(const struct mystr* p_user_str,
const struct mystr* p_pass_str,
const struct mystr* p_remote_host)
{
int retval;
pam_handle_t* pamh = 0;
struct pam_conv the_conv =
{
&pam_conv_func,
0
};
str_copy(&s_pword_str, p_pass_str);
retval = pam_start(tunable_pam_service_name,
str_getbuf(p_user_str), &the_conv, &pamh);
if (retval != PAM_SUCCESS)
{
pam_end(pamh, 0);
return 0;
}
#ifdef PAM_RHOST
retval = pam_set_item(pamh, PAM_RHOST, str_getbuf(p_remote_host));
if (retval != PAM_SUCCESS)
{
pam_end(pamh, 0);
return 0;
}
#endif
retval = pam_authenticate(pamh, 0);
if (retval != PAM_SUCCESS)
{
pam_end(pamh, 0);
return 0;
}
retval = pam_acct_mgmt(pamh, 0);
if (retval != PAM_SUCCESS)
{
pam_end(pamh, 0);
return 0;
}
retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
if (retval != PAM_SUCCESS)
{
pam_end(pamh, 0);
return 0;
}
retval = pam_end(pamh, PAM_SUCCESS);
if (retval != PAM_SUCCESS)
{
return 0;
}
/* It worked, cool */
return 1;
}
static int
pam_conv_func(int nmsg, const struct pam_message** p_msg,
struct pam_response** p_reply, void* p_addata)
{
int i;
struct pam_response* p_resps = 0;
(void) p_addata;
if (nmsg < 0)
{
bug("dodgy nmsg in pam_conv_func");
}
/* XXX sometimes leaks */
p_resps = vsf_sysutil_malloc(sizeof(struct pam_response) * nmsg);
if (p_resps == 0)
{
return PAM_CONV_ERR;
}
for (i=0; i<nmsg; i++)
{
switch (p_msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
return PAM_CONV_ERR;
break;
case PAM_PROMPT_ECHO_OFF:
p_resps[i].resp_retcode = PAM_SUCCESS;
p_resps[i].resp = (char*) str_strdup(&s_pword_str);
break;
case PAM_TEXT_INFO:
case PAM_ERROR_MSG:
p_resps[i].resp_retcode = PAM_SUCCESS;
p_resps[i].resp = 0;
break;
default:
return PAM_CONV_ERR;
break;
}
}
*p_reply = p_resps;
return PAM_SUCCESS;
}
#endif /* VSF_SYSDEP_HAVE_PAM */
/* Capabilities support (or lack thereof) */
void
vsf_sysdep_keep_capabilities(void)
{
if (!vsf_sysdep_has_capabilities_as_non_root())
{
bug("asked to keep capabilities, but no support exists");
}
#ifdef VSF_SYSDEP_HAVE_SETKEEPCAPS
{
int retval = prctl(PR_SET_KEEPCAPS, 1);
if (vsf_sysutil_retval_is_error(retval))
{
die("prctl");
}
}
#endif /* VSF_SYSDEP_HAVE_SETKEEPCAPS */
}
#ifndef VSF_SYSDEP_HAVE_CAPABILITIES
int
vsf_sysdep_has_capabilities(void)
{
return 0;
}
int
vsf_sysdep_has_capabilities_as_non_root(void)
{
return 0;
}
void
vsf_sysdep_adopt_capabilities(unsigned int caps)
{
(void) caps;
bug("asked to adopt capabilities, but no support exists");
}
#else /* VSF_SYSDEP_HAVE_CAPABILITIES */
int
vsf_sysdep_has_capabilities(void)
{
/* Even though compiled with capabilities, the runtime system may lack them.
* Also, RH7.0 kernel headers advertise a 2.4.0 box, but on a 2.2.x kernel!
*/
static int s_caps_checked;
static int s_runtime_has_caps;
if (!s_caps_checked)
{
/* EFAULT (EINVAL if page 0 mapped) vs. ENOSYS */
int retval = capset(0, 0);
if (!vsf_sysutil_retval_is_error(retval) ||
vsf_sysutil_get_error() != kVSFSysUtilErrNOSYS)
{
s_runtime_has_caps = 1;
}
s_caps_checked = 1;
}
return s_runtime_has_caps;
}
int
vsf_sysdep_has_capabilities_as_non_root(void)
{
static int s_prctl_checked;
static int s_runtime_prctl_works;
if (!s_prctl_checked)
{
#ifdef VSF_SYSDEP_HAVE_SETKEEPCAPS
/* Clarity: note embedded call to prctl() syscall */
if (!vsf_sysutil_retval_is_error(prctl(PR_SET_KEEPCAPS, 0)))
{
s_runtime_prctl_works = 1;
}
#endif /* VSF_SYSDEP_HAVE_SETKEEPCAPS */
s_prctl_checked = 1;
}
return s_runtime_prctl_works;
}
void
vsf_sysdep_adopt_capabilities(unsigned int caps)
{
/* n.b. yes I know I should be using libcap!! */
int retval;
struct __user_cap_header_struct cap_head;
struct __user_cap_data_struct cap_data;
__u32 cap_mask = 0;
if (!caps)
{
bug("asked to adopt no capabilities");
}
vsf_sysutil_memclr(&cap_head, sizeof(cap_head));
vsf_sysutil_memclr(&cap_data, sizeof(cap_data));
cap_head.version = _LINUX_CAPABILITY_VERSION;
cap_head.pid = 0;
if (caps & kCapabilityCAP_CHOWN)
{
cap_mask |= (1 << CAP_CHOWN);
}
if (caps & kCapabilityCAP_NET_BIND_SERVICE)
{
cap_mask |= (1 << CAP_NET_BIND_SERVICE);
}
cap_data.effective = cap_data.permitted = cap_mask;
cap_data.inheritable = 0;
retval = capset(&cap_head, &cap_data);
if (retval != 0)
{
die("capset");
}
}
#endif /* VSF_SYSDEP_HAVE_CAPABILITIES */
int
vsf_sysutil_sendfile(const int out_fd, const int in_fd,
unsigned long* p_offset, unsigned long num_send,
unsigned int max_chunk)
{
/* Grr - why is off_t signed? */
long real_offset = *p_offset;
if (real_offset < 0)
{
die("invalid offset in vsf_sysutil_sendfile");
}
while (num_send > 0)
{
int retval;
unsigned int send_this_time;
/* For 64-bit platforms */
if (num_send > INT_MAX)
{
send_this_time = INT_MAX;
}
else
{
send_this_time = (unsigned int) num_send;
}
if (max_chunk != 0 && send_this_time > max_chunk)
{
send_this_time = max_chunk;
}
retval = do_sendfile(out_fd, in_fd, &real_offset, send_this_time);
if (real_offset < 0)
{
die("invalid offset returned in vsf_sysutil_sendfile");
}
*p_offset = real_offset;
if (vsf_sysutil_retval_is_error(retval) || retval == 0)
{
return retval;
}
num_send -= (unsigned long) retval;
}
return 0;
}
static int do_sendfile(const int out_fd, const int in_fd,
long* p_offset, unsigned int num_send)
{
/* Probably should one day be shared with instance in ftpdataio.c */
static char* p_recvbuf;
unsigned int total_written = 0;
int retval;
#if defined(VSF_SYSDEP_HAVE_LINUX_SENDFILE) || \
defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) || \
defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE)
{
static int s_sendfile_checked;
static int s_runtime_sendfile_works;
if (!s_sendfile_checked || s_runtime_sendfile_works)
{
do
{
#ifdef VSF_SYSDEP_HAVE_LINUX_SENDFILE
retval = sendfile(out_fd, in_fd, p_offset, num_send);
#elif defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE)
{
off_t written = 0;
retval = sendfile(in_fd, out_fd, *p_offset, num_send, NULL,
&written, 0);
/* Translate to Linux-like retval */
if (written > 0)
{
retval = (int) written;
*p_offset += retval;
}
}
#else /* must be VSF_SYSDEP_HAVE_HPUX_SENDFILE */
{
retval = sendfile(out_fd, in_fd, *p_offset, num_send, NULL, 0);
/* Translate to Linux-like retval */
if (retval > 0)
{
*p_offset += retval;
}
}
#endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */
vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, out_fd);
}
while (vsf_sysutil_retval_is_error(retval) &&
vsf_sysutil_get_error() == kVSFSysUtilErrINTR);
if (!s_sendfile_checked)
{
s_sendfile_checked = 1;
if (!vsf_sysutil_retval_is_error(retval) ||
vsf_sysutil_get_error() != kVSFSysUtilErrNOSYS)
{
s_runtime_sendfile_works = 1;
}
}
if (s_runtime_sendfile_works)
{
return retval;
}
/* Fall thru to normal implementation. We won't check again. */
}
}
#endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE || VSF_SYSDEP_HAVE_FREEBSD_SENDFILE */
if (p_recvbuf == 0)
{
vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE);
}
while (1)
{
unsigned int num_read;
unsigned int num_written;
unsigned int num_read_this_time = VSFTP_DATA_BUFSIZE;
if (num_read_this_time > num_send)
{
num_read_this_time = num_send;
}
retval = vsf_sysutil_read(in_fd, p_recvbuf, num_read_this_time);
if (retval < 0)
{
return retval;
}
else if (retval == 0)
{
return -1;
}
num_read = (unsigned int) retval;
*p_offset += num_read;
retval = vsf_sysutil_write_loop(out_fd, p_recvbuf, num_read);
if (retval < 0)
{
return retval;
}
num_written = (unsigned int) retval;
total_written += num_written;
if (num_written != num_read)
{
return -1;
}
if (num_written > num_send)
{
bug("num_written bigger than num_send in do_sendfile");
}
num_send -= num_written;
if (num_send == 0)
{
/* Bingo! */
return total_written;
}
}
}
void
vsf_sysutil_set_proctitle_prefix(const struct mystr* p_str)
{
str_copy(&s_proctitle_prefix_str, p_str);
}
/* This delegation is common to all setproctitle() implementations */
void
vsf_sysutil_setproctitle_str(const struct mystr* p_str)
{
vsf_sysutil_setproctitle(str_getbuf(p_str));
}
void
vsf_sysutil_setproctitle(const char* p_text)
{
struct mystr proctitle_str = INIT_MYSTR;
str_copy(&proctitle_str, &s_proctitle_prefix_str);
if (!str_isempty(&proctitle_str))
{
str_append_text(&proctitle_str, ": ");
}
str_append_text(&proctitle_str, p_text);
vsf_sysutil_setproctitle_internal(str_getbuf(&proctitle_str));
str_free(&proctitle_str);
}
#ifdef VSF_SYSDEP_HAVE_SETPROCTITLE
void
vsf_sysutil_setproctitle_init(int argc, const char* argv[])
{
(void) argc;
(void) argv;
}
void
vsf_sysutil_setproctitle_internal(const char* p_buf)
{
setproctitle("%s", p_buf);
}
#elif defined(VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE)
void
vsf_sysutil_setproctitle_init(int argc, const char* argv[])
{
(void) argc;
(void) argv;
}
void
vsf_sysutil_setproctitle_internal(const char* p_buf)
{
struct mystr proctitle_str = INIT_MYSTR;
union pstun p;
str_alloc_text(&proctitle_str, "vsftpd: ");
str_append_text(&proctitle_str, p_buf);
p.pst_command = str_getbuf(&proctitle_str);
pstat(PSTAT_SETCMD, p, 0, 0, 0);
str_free(&proctitle_str);
}
#elif defined(VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK)
void
vsf_sysutil_setproctitle_init(int argc, const char* argv[])
{
int i;
char** p_env = environ;
if (s_proctitle_inited)
{
bug("vsf_sysutil_setproctitle_init called twice");
}
s_proctitle_inited = 1;
if (argv[0] == 0)
{
die("no argv[0] in vsf_sysutil_setproctitle_init");
}
for (i=0; i<argc; i++)
{
s_proctitle_space += vsf_sysutil_strlen(argv[i]) + 1;
if (i > 0)
{
argv[i] = 0;
}
}
while (*p_env != 0)
{
s_proctitle_space += vsf_sysutil_strlen(*p_env) + 1;
p_env++;
}
/* Oops :-) */
environ = 0;
s_p_proctitle = (char*) argv[0];
vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space);
}
void
vsf_sysutil_setproctitle_internal(const char* p_buf)
{
struct mystr proctitle_str = INIT_MYSTR;
unsigned int to_copy;
if (!s_proctitle_inited)
{
bug("vsf_sysutil_setproctitle: not initialized");
}
vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space);
if (s_proctitle_space < 32)
{
return;
}
str_alloc_text(&proctitle_str, "vsftpd: ");
str_append_text(&proctitle_str, p_buf);
to_copy = str_getlen(&proctitle_str);
if (to_copy > s_proctitle_space - 1)
{
to_copy = s_proctitle_space - 1;
}
vsf_sysutil_memcpy(s_p_proctitle, str_getbuf(&proctitle_str), to_copy);
str_free(&proctitle_str);
s_p_proctitle[to_copy] = '\0';
}
#else /* VSF_SYSDEP_HAVE_SETPROCTITLE */
void
vsf_sysutil_setproctitle_init(int argc, const char* argv[])
{
(void) argc;
(void) argv;
}
void
vsf_sysutil_setproctitle_internal(const char* p_buf)
{
(void) p_buf;
}
#endif /* VSF_SYSDEP_HAVE_SETPROCTITLE */
#ifdef VSF_SYSDEP_HAVE_MAP_ANON
void
vsf_sysutil_map_anon_pages_init(void)
{
}
void*
vsf_sysutil_map_anon_pages(unsigned int length)
{
char* retval = mmap(0, length, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON, -1, 0);
if (retval == MAP_FAILED)
{
die("mmap");
}
return retval;
}
#else /* VSF_SYSDEP_HAVE_MAP_ANON */
void
vsf_sysutil_map_anon_pages_init(void)
{
if (s_zero_fd != -1)
{
bug("vsf_sysutil_map_anon_pages_init called twice");
}
s_zero_fd = open("/dev/zero", O_RDWR);
if (s_zero_fd < 0)
{
die("could not open /dev/zero");
}
}
void*
vsf_sysutil_map_anon_pages(unsigned int length)
{
char* retval = mmap(0, length, PROT_READ | PROT_WRITE,
MAP_PRIVATE, s_zero_fd, 0);
if (retval == MAP_FAILED)
{
die("mmap");
}
return retval;
}
#endif /* VSF_SYSDEP_HAVE_MAP_ANON */
#ifndef VSF_SYSDEP_NEED_OLD_FD_PASSING
void
vsf_sysutil_send_fd(int sock_fd, int send_fd)
{
int retval;
struct msghdr msg;
struct cmsghdr* p_cmsg;
struct iovec vec;
char cmsgbuf[CMSG_SPACE(sizeof(send_fd))];
int* p_fds;
char sendchar = 0;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
p_cmsg = CMSG_FIRSTHDR(&msg);
p_cmsg->cmsg_level = SOL_SOCKET;
p_cmsg->cmsg_type = SCM_RIGHTS;
p_cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd));
p_fds = (int*)CMSG_DATA(p_cmsg);
*p_fds = send_fd;
msg.msg_controllen = p_cmsg->cmsg_len;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
/* "To pass file descriptors or credentials you need to send/read at
* least on byte" (man 7 unix)
*/
vec.iov_base = &sendchar;
vec.iov_len = sizeof(sendchar);
retval = sendmsg(sock_fd, &msg, 0);
if (retval != 1)
{
die("sendmsg");
}
}
int
vsf_sysutil_recv_fd(const int sock_fd)
{
int retval;
struct msghdr msg;
char recvchar;
struct iovec vec;
int recv_fd;
char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
struct cmsghdr* p_cmsg;
int* p_fd;
vec.iov_base = &recvchar;
vec.iov_len = sizeof(recvchar);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
msg.msg_flags = 0;
/* In case something goes wrong, set the fd to -1 before the syscall */
p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg));
*p_fd = -1;
retval = recvmsg(sock_fd, &msg, 0);
if (retval != 1)
{
die("recvmsg");
}
p_cmsg = CMSG_FIRSTHDR(&msg);
if (p_cmsg == NULL)
{
die("no passed fd");
}
/* We used to verify the returned cmsg_level, cmsg_type and cmsg_len here,
* but Linux 2.0 totally uselessly fails to fill these in.
*/
p_fd = (int*)CMSG_DATA(p_cmsg);
recv_fd = *p_fd;
if (recv_fd == -1)
{
die("no passed fd");
}
return recv_fd;
}
#else /* !VSF_SYSDEP_NEED_OLD_FD_PASSING */
void
vsf_sysutil_send_fd(int sock_fd, int send_fd)
{
int retval;
char send_char = 0;
struct msghdr msg;
struct iovec vec;
vec.iov_base = &send_char;
vec.iov_len = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_accrights = (caddr_t) &send_fd;
msg.msg_accrightslen = sizeof(send_fd);
retval = sendmsg(sock_fd, &msg, 0);
if (retval != 1)
{
die("sendmsg");
}
}
int
vsf_sysutil_recv_fd(int sock_fd)
{
int retval;
struct msghdr msg;
struct iovec vec;
char recv_char;
int recv_fd = -1;
vec.iov_base = &recv_char;
vec.iov_len = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_accrights = (caddr_t) &recv_fd;
msg.msg_accrightslen = sizeof(recv_fd);
retval = recvmsg(sock_fd, &msg, 0);
if (retval != 1)
{
die("recvmsg");
}
if (recv_fd == -1)
{
die("no passed fd");
}
return recv_fd;
}
#endif /* !VSF_SYSDEP_NEED_OLD_FD_PASSING */

55
sysdeputil.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef VSF_SYSDEPUTIL_H
#define VSF_SYSDEPUTIL_H
/* VSF_SYSDEPUTIL_H:
* Support for highly system dependent features, and querying for support
* or lack thereof
* TODO: document functions!
*/
struct mystr;
/* Authentication of local users */
/* Return 0 for fail, 1 for success */
int vsf_sysdep_check_auth(const struct mystr* p_user,
const struct mystr* p_pass,
const struct mystr* p_remote_host);
/* Support for fine grained privilege (capabilities) */
int vsf_sysdep_has_capabilities(void);
int vsf_sysdep_has_capabilities_as_non_root(void);
void vsf_sysdep_keep_capabilities(void);
enum ESysdepCapabilities
{
kCapabilityCAP_CHOWN = 1,
kCapabilityCAP_NET_BIND_SERVICE = 2
/* NOTE - next one will be 4, this is a bitfield */
};
void vsf_sysdep_adopt_capabilities(unsigned int caps);
/* Support for sendfile(), Linux-like interface. Collapses to a read/write
* loop under the covers if the target system lacks support.
*/
int vsf_sysutil_sendfile(const int out_fd, const int in_fd,
unsigned long* p_offset, unsigned long num_send,
unsigned int max_chunk);
/* Support for changing the process name as reported by the operating system.
* A useful status monitor. NOTE - we don't guarantee that this call will
* have any effect.
*/
void vsf_sysutil_setproctitle_init(int argc, const char* argv[]);
void vsf_sysutil_setproctitle(const char* p_text);
void vsf_sysutil_setproctitle_str(const struct mystr* p_str);
void vsf_sysutil_set_proctitle_prefix(const struct mystr* p_str);
/* For now, maps read/write private pages. API to be extended.. */
void vsf_sysutil_map_anon_pages_init(void);
void* vsf_sysutil_map_anon_pages(unsigned int length);
/* File descriptor passing/receiving */
void vsf_sysutil_send_fd(int sock_fd, int send_fd);
int vsf_sysutil_recv_fd(int sock_fd);
#endif /* VSF_SYSDEPUTIL_H */

168
sysstr.c Normal file
View File

@ -0,0 +1,168 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* sysstr.c
*
* This file basically wraps system functions so that we can deal in our
* nice abstracted string buffer objects.
*/
#include "sysstr.h"
#include "str.h"
#include "secbuf.h"
#include "sysutil.h"
#include "defs.h"
#include "utility.h"
void
str_getcwd(struct mystr* p_str)
{
static char* p_getcwd_buf;
char* p_ret;
if (p_getcwd_buf == 0)
{
vsf_secbuf_alloc(&p_getcwd_buf, VSFTP_PATH_MAX);
}
/* In case getcwd() fails */
str_empty(p_str);
p_ret = vsf_sysutil_getcwd(p_getcwd_buf, VSFTP_PATH_MAX);
if (p_ret != 0)
{
str_alloc_text(p_str, p_getcwd_buf);
}
}
int
str_write_loop(const struct mystr* p_str, const int fd)
{
return vsf_sysutil_write_loop(fd, str_getbuf(p_str), str_getlen(p_str));
}
int
str_mkdir(const struct mystr* p_str, const unsigned int mode)
{
return vsf_sysutil_mkdir(str_getbuf(p_str), mode);
}
int
str_rmdir(const struct mystr* p_str)
{
return vsf_sysutil_rmdir(str_getbuf(p_str));
}
int
str_unlink(const struct mystr* p_str)
{
return vsf_sysutil_unlink(str_getbuf(p_str));
}
int
str_chdir(const struct mystr* p_str)
{
return vsf_sysutil_chdir(str_getbuf(p_str));
}
int
str_open(const struct mystr* p_str, const enum EVSFSysStrOpenMode mode)
{
enum EVSFSysUtilOpenMode open_mode = kVSFSysStrOpenUnknown;
switch (mode)
{
case kVSFSysStrOpenReadOnly:
open_mode = kVSFSysUtilOpenReadOnly;
break;
default:
bug("unknown mode value in str_open");
break;
}
return vsf_sysutil_open_file(str_getbuf(p_str), open_mode);
}
int
str_stat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr)
{
return vsf_sysutil_stat(str_getbuf(p_str), p_ptr);
}
int
str_lstat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr)
{
return vsf_sysutil_lstat(str_getbuf(p_str), p_ptr);
}
int
str_create(const struct mystr* p_str)
{
return vsf_sysutil_create_file(str_getbuf(p_str));
}
int
str_create_overwrite(const struct mystr* p_str)
{
return vsf_sysutil_create_overwrite_file(str_getbuf(p_str));
}
int
str_create_append(const struct mystr* p_str)
{
return vsf_sysutil_create_or_open_file(str_getbuf(p_str), 0666);
}
int
str_chmod(const struct mystr* p_str, unsigned int mode)
{
return vsf_sysutil_chmod(str_getbuf(p_str), mode);
}
int
str_rename(const struct mystr* p_from_str, const struct mystr* p_to_str)
{
return vsf_sysutil_rename(str_getbuf(p_from_str), str_getbuf(p_to_str));
}
struct vsf_sysutil_dir*
str_opendir(const struct mystr* p_str)
{
return vsf_sysutil_opendir(str_getbuf(p_str));
}
void
str_next_dirent(struct mystr* p_filename_str, struct vsf_sysutil_dir* p_dir)
{
const char* p_filename = vsf_sysutil_next_dirent(p_dir);
str_empty(p_filename_str);
if (p_filename != 0)
{
str_alloc_text(p_filename_str, p_filename);
}
}
int
str_readlink(struct mystr* p_str, const struct mystr* p_filename_str)
{
static char* p_readlink_buf;
int retval;
if (p_readlink_buf == 0)
{
vsf_secbuf_alloc(&p_readlink_buf, VSFTP_PATH_MAX);
}
/* In case readlink() fails */
str_empty(p_str);
/* Note: readlink(2) does not NULL terminate, but our wrapper does */
retval = vsf_sysutil_readlink(str_getbuf(p_filename_str), p_readlink_buf,
VSFTP_PATH_MAX);
if (vsf_sysutil_retval_is_error(retval))
{
return retval;
}
str_alloc_text(p_str, p_readlink_buf);
return 0;
}
struct vsf_sysutil_user*
str_getpwnam(const struct mystr* p_user_str)
{
return vsf_sysutil_getpwnam(str_getbuf(p_user_str));
}

37
sysstr.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef VSF_SYSSTR_H
#define VSF_SYSSTR_H
/* Forward declarations */
struct mystr;
struct vsf_sysutil_statbuf;
struct vsf_sysutil_dir;
struct vsf_sysutil_user;
void str_getcwd(struct mystr* p_str);
int str_readlink(struct mystr* p_str, const struct mystr* p_filename_str);
int str_write_loop(const struct mystr* p_str, const int fd);
int str_mkdir(const struct mystr* p_str, const unsigned int mode);
int str_rmdir(const struct mystr* p_str);
int str_unlink(const struct mystr* p_str);
int str_chdir(const struct mystr* p_str);
enum EVSFSysStrOpenMode
{
kVSFSysStrOpenUnknown = 0,
kVSFSysStrOpenReadOnly = 1
};
int str_open(const struct mystr* p_str, const enum EVSFSysStrOpenMode mode);
int str_create_append(const struct mystr* p_str);
int str_create(const struct mystr* p_str);
int str_create_overwrite(const struct mystr* p_str);
int str_chmod(const struct mystr* p_str, unsigned int mode);
int str_stat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr);
int str_lstat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr);
int str_rename(const struct mystr* p_from_str, const struct mystr* p_to_str);
struct vsf_sysutil_dir* str_opendir(const struct mystr* p_str);
void str_next_dirent(struct mystr* p_filename_str,
struct vsf_sysutil_dir* p_dir);
struct vsf_sysutil_user* str_getpwnam(const struct mystr* p_user_str);
#endif /* VSF_SYSSTR_H */

1830
sysutil.c Normal file

File diff suppressed because it is too large Load Diff

285
sysutil.h Normal file
View File

@ -0,0 +1,285 @@
#ifndef VSF_SYSUTIL_H
#define VSF_SYSUTIL_H
/* TODO: these functions need proper documenting! */
/* Return value queries */
int vsf_sysutil_retval_is_error(int retval);
enum EVSFSysUtilError
{
kVSFSysUtilErrUnknown = 1,
kVSFSysUtilErrADDRINUSE,
kVSFSysUtilErrNOSYS,
kVSFSysUtilErrINTR
};
enum EVSFSysUtilError vsf_sysutil_get_error(void);
/* Signal handling utility functions */
enum EVSFSysUtilSignal
{
kVSFSysUtilSigALRM = 1,
kVSFSysUtilSigTERM,
kVSFSysUtilSigCHLD,
kVSFSysUtilSigPIPE,
kVSFSysUtilSigURG
};
enum EVSFSysUtilInterruptContext
{
kVSFSysUtilUnknown,
kVSFSysUtilIO
};
typedef void (*vsf_sighandle_t)(void*);
typedef void (*vsf_async_sighandle_t)(int);
typedef void (*vsf_context_io_t)(int, int, void*);
void vsf_sysutil_install_null_sighandler(const enum EVSFSysUtilSignal sig);
void vsf_sysutil_install_sighandler(const enum EVSFSysUtilSignal,
vsf_sighandle_t handler, void* p_private);
void vsf_sysutil_install_async_sighandler(const enum EVSFSysUtilSignal sig,
vsf_async_sighandle_t handler);
void vsf_sysutil_default_sig(const enum EVSFSysUtilSignal sig);
void vsf_sysutil_install_io_handler(vsf_context_io_t handler, void* p_private);
void vsf_sysutil_uninstall_io_handler(void);
void vsf_sysutil_check_pending_actions(
const enum EVSFSysUtilInterruptContext context, int retval, int fd);
/* Alarm setting/clearing utility functions */
void vsf_sysutil_set_alarm(const unsigned int trigger_seconds);
void vsf_sysutil_clear_alarm(void);
/* Directory related things */
char* vsf_sysutil_getcwd(char* p_dest, const unsigned int buf_size);
int vsf_sysutil_mkdir(const char* p_dirname, const unsigned int mode);
int vsf_sysutil_rmdir(const char* p_dirname);
int vsf_sysutil_chdir(const char* p_dirname);
int vsf_sysutil_rename(const char* p_from, const char* p_to);
struct vsf_sysutil_dir;
struct vsf_sysutil_dir* vsf_sysutil_opendir(const char* p_dirname);
void vsf_sysutil_closedir(struct vsf_sysutil_dir* p_dir);
const char* vsf_sysutil_next_dirent(struct vsf_sysutil_dir* p_dir);
/* File create/open/close etc. */
enum EVSFSysUtilOpenMode
{
kVSFSysUtilOpenReadOnly = 1,
kVSFSysUtilOpenWriteOnly,
kVSFSysUtilOpenReadWrite
};
int vsf_sysutil_open_file(const char* p_filename,
const enum EVSFSysUtilOpenMode);
/* Fails if file already exists */
int vsf_sysutil_create_file(const char* p_filename);
/* Overwrites if file already exists */
int vsf_sysutil_create_overwrite_file(const char* p_filename);
/* Creates file or appends if already exists */
int vsf_sysutil_create_append_file(const char* p_filename);
/* Creates or appends */
int vsf_sysutil_create_or_open_file(const char* p_filename, unsigned int mode);
void vsf_sysutil_close(int fd);
int vsf_sysutil_close_failok(int fd);
int vsf_sysutil_unlink(const char* p_dead);
int vsf_sysutil_write_access(const char* p_filename);
/* Reading and writing */
unsigned long vsf_sysutil_lseek_to(const int fd, unsigned long seek_pos);
unsigned long vsf_sysutil_get_file_offset(const int file_fd);
int vsf_sysutil_read(const int fd, void* p_buf, const unsigned int size);
int vsf_sysutil_write(const int fd, const void* p_buf,
const unsigned int size);
/* Reading and writing, with handling of interrupted system calls and partial
* reads/writes. Slightly more usable than the standard UNIX API!
*/
int vsf_sysutil_read_loop(const int fd, void* p_buf, unsigned int size);
int vsf_sysutil_write_loop(const int fd, const void* p_buf, unsigned int size);
struct vsf_sysutil_statbuf;
int vsf_sysutil_stat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr);
int vsf_sysutil_lstat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr);
void vsf_sysutil_fstat(int fd, struct vsf_sysutil_statbuf** p_ptr);
void vsf_sysutil_dir_stat(const struct vsf_sysutil_dir* p_dir,
struct vsf_sysutil_statbuf** p_ptr);
int vsf_sysutil_statbuf_is_regfile(const struct vsf_sysutil_statbuf* p_stat);
int vsf_sysutil_statbuf_is_symlink(const struct vsf_sysutil_statbuf* p_stat);
int vsf_sysutil_statbuf_is_socket(const struct vsf_sysutil_statbuf* p_stat);
int vsf_sysutil_statbuf_is_dir(const struct vsf_sysutil_statbuf* p_stat);
unsigned long vsf_sysutil_statbuf_get_size(
const struct vsf_sysutil_statbuf* p_stat);
const char* vsf_sysutil_statbuf_get_perms(
const struct vsf_sysutil_statbuf* p_stat);
const char* vsf_sysutil_statbuf_get_date(
const struct vsf_sysutil_statbuf* p_stat);
const char* vsf_sysutil_statbuf_get_numeric_date(
const struct vsf_sysutil_statbuf* p_stat);
unsigned int vsf_sysutil_statbuf_get_links(
const struct vsf_sysutil_statbuf* p_stat);
int vsf_sysutil_statbuf_get_uid(const struct vsf_sysutil_statbuf* p_stat);
int vsf_sysutil_statbuf_get_gid(const struct vsf_sysutil_statbuf* p_stat);
int vsf_sysutil_statbuf_is_readable_other(
const struct vsf_sysutil_statbuf* p_stat);
const char* vsf_sysutil_statbuf_get_sortkey_mtime(
const struct vsf_sysutil_statbuf* p_stat);
int vsf_sysutil_chmod(const char* p_filename, unsigned int mode);
void vsf_sysutil_fchown(const int fd, const int uid, const int gid);
int vsf_sysutil_readlink(const char* p_filename, char* p_dest,
unsigned int bufsiz);
/* Get an exclusive lock, and blocks */
int vsf_sysutil_lock_file(int fd);
void vsf_sysutil_unlock_file(int fd);
/* Mapping/unmapping */
enum EVSFSysUtilMapPermission
{
kVSFSysUtilMapProtReadOnly = 1,
kVSFSysUtilMapProtNone
};
void vsf_sysutil_memprotect(void* p_addr, unsigned int len,
const enum EVSFSysUtilMapPermission perm);
void vsf_sysutil_memunmap(void* p_start, unsigned int length);
/* Memory allocating/freeing */
void* vsf_sysutil_malloc(unsigned int size);
void* vsf_sysutil_realloc(void* p_ptr, unsigned int size);
void vsf_sysutil_free(void* p_ptr);
/* Process creation/exit/process handling */
unsigned int vsf_sysutil_getpid(void);
int vsf_sysutil_fork(void);
void vsf_sysutil_exit(int exit_code);
struct vsf_sysutil_wait_retval
{
int PRIVATE_HANDS_OFF_syscall_retval;
int PRIVATE_HANDS_OFF_exit_status;
};
struct vsf_sysutil_wait_retval vsf_sysutil_wait(void);
int vsf_sysutil_wait_get_retval(
const struct vsf_sysutil_wait_retval* p_waitret);
int vsf_sysutil_wait_exited_normally(
const struct vsf_sysutil_wait_retval* p_waitret);
int vsf_sysutil_wait_get_exitcode(
const struct vsf_sysutil_wait_retval* p_waitret);
/* Various string functions */
unsigned int vsf_sysutil_strlen(const char* p_text);
char* vsf_sysutil_strdup(const char* p_str);
void vsf_sysutil_memclr(void* p_dest, unsigned int size);
void vsf_sysutil_memcpy(void* p_dest, const void* p_src,
const unsigned int size);
int vsf_sysutil_memcmp(const void* p_src1, const void* p_src2,
unsigned int size);
int vsf_sysutil_strcmp(const char* p_src1, const char* p_src2);
int vsf_sysutil_atoi(const char* p_str);
long vsf_sysutil_atol(const char* p_str);
const char* vsf_sysutil_ulong_to_str(unsigned long the_ulong);
const char* vsf_sysutil_double_to_str(double the_double);
const char* vsf_sysutil_uint_to_octal(unsigned int the_uint);
unsigned int vsf_sysutil_octal_to_uint(const char* p_str);
int vsf_sysutil_toupper(int the_char);
int vsf_sysutil_isspace(int the_char);
int vsf_sysutil_isprint(int the_char);
int vsf_sysutil_isalnum(int the_char);
/* Socket handling */
struct vsf_sysutil_sockaddr;
struct vsf_sysutil_ipv4addr
{
unsigned char data[4];
};
struct vsf_sysutil_ipv4port
{
unsigned char data[2];
};
struct vsf_sysutil_socketpair_retval
{
int socket_one;
int socket_two;
};
void vsf_sysutil_sockaddr_clear(struct vsf_sysutil_sockaddr** p_sockptr);
void vsf_sysutil_sockaddr_alloc_ipv4(struct vsf_sysutil_sockaddr** p_sockptr);
void vsf_sysutil_sockaddr_set_ipaddr(struct vsf_sysutil_sockaddr* p_sockptr,
struct vsf_sysutil_ipv4addr the_addr);
struct vsf_sysutil_ipv4addr vsf_sysutil_sockaddr_get_ipaddr(
const struct vsf_sysutil_sockaddr* p_sockptr);
struct vsf_sysutil_ipv4port vsf_sysutil_ipv4port_from_int(unsigned int port);
void vsf_sysutil_sockaddr_set_port(struct vsf_sysutil_sockaddr* p_sockptr,
struct vsf_sysutil_ipv4port the_port);
struct vsf_sysutil_ipv4port vsf_sysutil_sockaddr_get_port(
const struct vsf_sysutil_sockaddr* p_sockptr);
int vsf_sysutil_is_port_reserved(const struct vsf_sysutil_ipv4port);
int vsf_sysutil_get_ipv4_sock(void);
const struct vsf_sysutil_socketpair_retval
vsf_sysutil_unix_dgram_socketpair(void);
int vsf_sysutil_bind(int fd, const struct vsf_sysutil_sockaddr* p_sockptr);
void vsf_sysutil_listen(int fd, const unsigned int backlog);
void vsf_sysutil_getsockname(int fd, struct vsf_sysutil_sockaddr** p_sockptr);
void vsf_sysutil_getpeername(int fd, struct vsf_sysutil_sockaddr** p_sockptr);
int vsf_sysutil_accept_timeout(int fd, struct vsf_sysutil_sockaddr** p_sockptr,
unsigned int wait_seconds);
int vsf_sysutil_connect_timeout(int fd,
const struct vsf_sysutil_sockaddr* p_sockaddr,
unsigned int wait_seconds);
/* Option setting on sockets */
void vsf_sysutil_activate_keepalive(int fd);
void vsf_sysutil_set_iptos_throughput(int fd);
void vsf_sysutil_activate_reuseaddr(int fd);
void vsf_sysutil_set_nodelay(int fd);
void vsf_sysutil_activate_sigurg(int fd);
void vsf_sysutil_activate_oobinline(int fd);
void vsf_sysutil_activate_linger(int fd);
void vsf_sysutil_deactivate_linger(int fd);
void vsf_sysutil_activate_noblock(int fd);
void vsf_sysutil_deactivate_noblock(int fd);
/* This does SHUT_RDWR */
void vsf_sysutil_shutdown_failok(int fd);
int vsf_sysutil_recv_peek(const int fd, void* p_buf, unsigned int len);
const char* vsf_sysutil_inet_ntoa(
const struct vsf_sysutil_sockaddr* p_sockptr);
/* User database queries etc. */
struct vsf_sysutil_user;
struct vsf_sysutil_group;
struct vsf_sysutil_user* vsf_sysutil_getpwuid(const int uid);
struct vsf_sysutil_user* vsf_sysutil_getpwnam(const char* p_user);
const char* vsf_sysutil_user_getname(const struct vsf_sysutil_user* p_user);
const char* vsf_sysutil_user_get_homedir(
const struct vsf_sysutil_user* p_user);
int vsf_sysutil_user_getuid(const struct vsf_sysutil_user* p_user);
int vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user);
struct vsf_sysutil_group* vsf_sysutil_getgrgid(const int gid);
const char* vsf_sysutil_group_getname(const struct vsf_sysutil_group* p_group);
/* More random things */
unsigned int vsf_sysutil_getpagesize(void);
unsigned char vsf_sysutil_get_random_byte(void);
unsigned int vsf_sysutil_get_umask(void);
void vsf_sysutil_set_umask(unsigned int umask);
void vsf_sysutil_make_session_leader(void);
void vsf_sysutil_tzset(void);
const char* vsf_sysutil_get_current_date(void);
void vsf_sysutil_qsort(void* p_base, unsigned int num_elem,
unsigned int elem_size,
int (*p_compar)(const void *, const void *));
/* Credentials handling */
int vsf_sysutil_running_as_root(void);
void vsf_sysutil_setuid(const struct vsf_sysutil_user* p_user);
void vsf_sysutil_setuid_numeric(int uid);
void vsf_sysutil_setgid(const struct vsf_sysutil_user* p_user);
void vsf_sysutil_setgid_numeric(int gid);
void vsf_sysutil_clear_supp_groups(void);
void vsf_sysutil_initgroups(const struct vsf_sysutil_user* p_user);
void vsf_sysutil_chroot(const char* p_root_path);
/* Time handling */
void vsf_sysutil_update_cached_time(void);
long vsf_sysutil_get_cached_time_sec(void);
long vsf_sysutil_get_cached_time_usec(void);
void vsf_sysutil_sleep(double seconds);
#endif /* VSF_SYSUTIL_H */

66
tunables.c Normal file
View File

@ -0,0 +1,66 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* tunables.c
*/
#include "tunables.h"
int tunable_anonymous_enable = 1;
int tunable_local_enable = 0;
int tunable_pasv_enable = 1;
int tunable_port_enable = 1;
int tunable_chroot_local_user = 0;
int tunable_write_enable = 0;
int tunable_anon_upload_enable = 0;
int tunable_anon_mkdir_write_enable = 0;
int tunable_anon_other_write_enable = 0;
int tunable_chown_uploads = 0;
int tunable_connect_from_port_20 = 0;
int tunable_xferlog_enable = 0;
int tunable_dirmessage_enable = 0;
int tunable_anon_world_readable_only = 1;
int tunable_async_abor_enable = 0;
int tunable_ascii_upload_enable = 0;
int tunable_ascii_download_enable = 0;
int tunable_one_process_model = 0;
int tunable_xferlog_std_format = 0;
int tunable_pasv_promiscuous = 0;
int tunable_deny_email_enable = 0;
int tunable_chroot_list_enable = 0;
int tunable_setproctitle_enable = 0;
int tunable_text_userdb_names = 0;
int tunable_ls_recurse_enable = 0;
int tunable_log_ftp_protocol = 0;
int tunable_guest_enable = 0;
int tunable_userlist_enable = 0;
int tunable_userlist_deny = 1;
unsigned int tunable_accept_timeout = 60;
unsigned int tunable_connect_timeout = 60;
unsigned int tunable_local_umask = 077;
unsigned int tunable_anon_umask = 077;
unsigned int tunable_ftp_data_port = 20;
unsigned int tunable_idle_session_timeout = 300;
unsigned int tunable_data_connection_timeout = 300;
/* IPPORT_USERRESERVED + 1 */
unsigned int tunable_pasv_min_port = 5001;
unsigned int tunable_pasv_max_port = 0;
unsigned int tunable_anon_max_rate = 0;
unsigned int tunable_local_max_rate = 0;
const char* tunable_secure_chroot_dir = "/usr/share/empty";
const char* tunable_ftp_username = "ftp";
const char* tunable_chown_username = "root";
const char* tunable_xferlog_file = "/var/log/vsftpd.log";
const char* tunable_message_file = ".message";
/* XXX -> "secure"? */
const char* tunable_nopriv_user = "nobody";
const char* tunable_ftpd_banner = 0;
const char* tunable_banned_email_file = "/etc/vsftpd.banned_emails";
const char* tunable_chroot_list_file = "/etc/vsftpd.chroot_list";
const char* tunable_pam_service_name = "ftp";
const char* tunable_guest_username = "ftp";
const char* tunable_userlist_file = "/etc/vsftpd.user_list";

64
tunables.h Normal file
View File

@ -0,0 +1,64 @@
#ifndef VSF_TUNABLES_H
#define VSF_TUNABLES_H
/* Configurable preferences */
/* Booleans */
extern int tunable_anonymous_enable; /* Allow anon logins */
extern int tunable_local_enable; /* Allow local logins */
extern int tunable_pasv_enable; /* Allow PASV */
extern int tunable_port_enable; /* Allow PORT */
extern int tunable_chroot_local_user; /* Restrict local to home dir */
extern int tunable_write_enable; /* Global enable writes */
extern int tunable_anon_upload_enable; /* Enable STOR for anon users */
extern int tunable_anon_mkdir_write_enable; /* MKD for anon */
extern int tunable_anon_other_write_enable; /* DELE RMD RNFR RNTO for anon */
extern int tunable_chown_uploads; /* chown() anon uploaded files */
extern int tunable_connect_from_port_20; /* PORT connects from port 20 */
extern int tunable_xferlog_enable; /* Log transfers to a file */
extern int tunable_dirmessage_enable; /* Look for + output .message */
extern int tunable_anon_world_readable_only; /* Only serve world readable */
extern int tunable_async_abor_enable; /* Enable async ABOR requests */
extern int tunable_ascii_upload_enable; /* Permit ASCII upload */
extern int tunable_ascii_download_enable; /* Permit ASCII download */
extern int tunable_one_process_model; /* Go faster stripes ;-) */
extern int tunable_xferlog_std_format; /* Log details like wu-ftpd */
extern int tunable_pasv_promiscuous; /* Allow any PASV connect IP */
extern int tunable_deny_email_enable; /* Ban a list of anon e-mails */
extern int tunable_chroot_list_enable; /* chroot() based on list file */
extern int tunable_setproctitle_enable; /* Try to use setproctitle() */
extern int tunable_text_userdb_names; /* For "ls", lookup text names */
extern int tunable_ls_recurse_enable; /* Allow ls -R */
extern int tunable_log_ftp_protocol; /* Log FTP requests/responses */
extern int tunable_guest_enable; /* Remap guest users */
extern int tunable_userlist_enable; /* Explicit user allow or deny */
extern int tunable_userlist_deny; /* Is user list allow or deny? */
/* Integer/numeric defines */
extern unsigned int tunable_accept_timeout;
extern unsigned int tunable_connect_timeout;
extern unsigned int tunable_local_umask;
extern unsigned int tunable_anon_umask;
extern unsigned int tunable_ftp_data_port;
extern unsigned int tunable_idle_session_timeout;
extern unsigned int tunable_data_connection_timeout;
extern unsigned int tunable_pasv_min_port;
extern unsigned int tunable_pasv_max_port;
extern unsigned int tunable_anon_max_rate;
extern unsigned int tunable_local_max_rate;
/* String defines */
extern const char* tunable_secure_chroot_dir;
extern const char* tunable_ftp_username;
extern const char* tunable_chown_username;
extern const char* tunable_xferlog_file;
extern const char* tunable_message_file;
extern const char* tunable_nopriv_user;
extern const char* tunable_ftpd_banner;
extern const char* tunable_banned_email_file;
extern const char* tunable_chroot_list_file;
extern const char* tunable_pam_service_name;
extern const char* tunable_guest_username;
extern const char* tunable_userlist_file;
#endif /* VSF_TUNABLES_H */

276
twoprocess.c Normal file
View File

@ -0,0 +1,276 @@
/*
* Part of Very Secure FTPd
* License: GPL
* Author: Chris Evans
* twoprocess.c
*
* Code implementing the standard, secure two process security model.
*/
#include "twoprocess.h"
#include "privops.h"
#include "prelogin.h"
#include "postlogin.h"
#include "postprivparent.h"
#include "session.h"
#include "privsock.h"
#include "secutil.h"
#include "sysutil.h"
#include "filestr.h"
#include "str.h"
#include "sysstr.h"
#include "utility.h"
#include "tunables.h"
#include "defs.h"
static void drop_all_privs(void);
static void handle_sigchld(int duff);
static void process_login_req(struct vsf_session* p_sess);
static void common_do_login(struct vsf_session* p_sess,
const struct mystr* p_user_str, int do_chroot,
int anon);
static void
handle_sigchld(int duff)
{
/* WARNING - async handler. Must not call anything which might have
* re-entrancy issues
*/
struct vsf_sysutil_wait_retval wait_retval = vsf_sysutil_wait();
(void) duff;
/* Child died, so we'll do the same! Report it as an error unless the child
* exited normally with zero exit code
*/
if (vsf_sysutil_retval_is_error(vsf_sysutil_wait_get_retval(&wait_retval)) ||
!vsf_sysutil_wait_exited_normally(&wait_retval) ||
vsf_sysutil_wait_get_exitcode(&wait_retval) != 0)
{
die("child died");
}
else
{
vsf_sysutil_exit(0);
}
}
void
vsf_two_process_start(struct vsf_session* p_sess)
{
/* Create the comms channel between privileged parent and no-priv child */
priv_sock_init(p_sess);
vsf_sysutil_install_async_sighandler(kVSFSysUtilSigCHLD, handle_sigchld);
{
int newpid = vsf_sysutil_fork();
if (newpid != 0)
{
/* Parent - go into pre-login parent process mode */
while (1)
{
process_login_req(p_sess);
}
/* NOTREACHED */
bug("should not get here: vsf_two_process_start");
}
}
/* Child process - time to lose as much privilege as possible and do the
* login processing
*/
if (tunable_local_enable && tunable_userlist_enable)
{
int retval = str_fileread(&p_sess->userlist_str, tunable_userlist_file,
VSFTP_CONF_FILE_MAX);
if (vsf_sysutil_retval_is_error(retval))
{
die("cannot open user list file");
}
}
drop_all_privs();
init_connection(p_sess);
/* NOTREACHED */
}
static void
drop_all_privs(void)
{
struct mystr user_str = INIT_MYSTR;
struct mystr dir_str = INIT_MYSTR;
str_alloc_text(&user_str, tunable_nopriv_user);
str_alloc_text(&dir_str, tunable_secure_chroot_dir);
/* Be kind: give good error message if the secure dir is missing */
{
struct vsf_sysutil_statbuf* p_statbuf = 0;
if (vsf_sysutil_retval_is_error(str_lstat(&dir_str, &p_statbuf)))
{
die("vsftpd: not found: directory given in 'tunable_secure_chroot_dir'");
}
vsf_sysutil_free(p_statbuf);
}
vsf_secutil_change_credentials(&user_str, &dir_str, 1, 0, 0);
str_free(&user_str);
str_free(&dir_str);
}
void
vsf_two_process_login(struct vsf_session* p_sess,
const struct mystr* p_pass_str)
{
char result;
priv_sock_send_cmd(p_sess, PRIV_SOCK_LOGIN);
priv_sock_send_str(p_sess, &p_sess->user_str);
priv_sock_send_str(p_sess, p_pass_str);
result = priv_sock_get_result(p_sess);
if (result == PRIV_SOCK_RESULT_OK)
{
/* Miracle. We don't emit the success message here. That is left to
* process_post_login().
* Exit normally, parent will wait for this and launch new child
*/
vsf_sysutil_exit(0);
}
else if (result == PRIV_SOCK_RESULT_BAD)
{
/* Continue the processing loop.. */
return;
}
else
{
die("priv_sock_get_result");
}
}
int
vsf_two_process_get_priv_data_sock(struct vsf_session* p_sess)
{
char res;
priv_sock_send_cmd(p_sess, PRIV_SOCK_GET_DATA_SOCK);
res = priv_sock_get_result(p_sess);
if (res != PRIV_SOCK_RESULT_OK)
{
die("could not get privileged socket");
}
return priv_sock_child_recv_fd(p_sess);
}
void
vsf_two_process_chown_upload(struct vsf_session* p_sess, int fd)
{
char res;
priv_sock_send_cmd(p_sess, PRIV_SOCK_CHOWN);
priv_sock_child_send_fd(p_sess, fd);
res = priv_sock_get_result(p_sess);
if (res != PRIV_SOCK_RESULT_OK)
{
die("unexpected failure in vsf_two_process_chown_upload");
}
}
static void
process_login_req(struct vsf_session* p_sess)
{
enum EVSFPrivopLoginResult e_login_result = kVSFLoginNull;
/* Blocks */
if (priv_sock_get_cmd(p_sess) != PRIV_SOCK_LOGIN)
{
die("bad request");
}
/* Get username and password - we must distrust these */
{
struct mystr password_str = INIT_MYSTR;
priv_sock_get_str(p_sess, &p_sess->user_str);
priv_sock_get_str(p_sess, &password_str);
e_login_result = vsf_privop_do_login(p_sess, &password_str);
str_free(&password_str);
}
switch (e_login_result)
{
case kVSFLoginFail:
priv_sock_send_result(p_sess, PRIV_SOCK_RESULT_BAD);
return;
break;
case kVSFLoginAnon:
str_alloc_text(&p_sess->user_str, tunable_ftp_username);
common_do_login(p_sess, &p_sess->user_str, 1, 1);
break;
case kVSFLoginReal:
{
int do_chroot = 0;
if (tunable_chroot_local_user)
{
do_chroot = 1;
}
if (tunable_chroot_list_enable)
{
struct mystr chroot_list_file = INIT_MYSTR;
int retval = str_fileread(&chroot_list_file,
tunable_chroot_list_file,
VSFTP_CONF_FILE_MAX);
if (vsf_sysutil_retval_is_error(retval))
{
die("cannot open chroot() user list file");
}
if (str_contains_line(&chroot_list_file, &p_sess->user_str))
{
if (do_chroot)
{
do_chroot = 0;
}
else
{
do_chroot = 1;
}
}
str_free(&chroot_list_file);
}
common_do_login(p_sess, &p_sess->user_str, do_chroot, 0);
}
break;
default:
bug("weird state in process_login_request");
break;
}
/* NOTREACHED */
}
static void
common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
int do_chroot, int anon)
{
int newpid;
vsf_sysutil_default_sig(kVSFSysUtilSigCHLD);
/* Asks the pre-login child to go away (by exiting) */
priv_sock_send_result(p_sess, PRIV_SOCK_RESULT_OK);
(void) vsf_sysutil_wait();
vsf_sysutil_install_async_sighandler(kVSFSysUtilSigCHLD, handle_sigchld);
newpid = vsf_sysutil_fork();
if (newpid == 0)
{
struct mystr guest_user_str = INIT_MYSTR;
/* Child - drop privs and start proper FTP! */
if (tunable_guest_enable && !anon)
{
/* Remap to the guest user */
str_alloc_text(&guest_user_str, tunable_guest_username);
p_user_str = &guest_user_str;
/* SECURITY: For now, apply the anonymous restrictions to
* guest users
*/
anon = 1;
}
vsf_secutil_change_credentials(p_user_str, 0, do_chroot, 1, 0);
str_free(&guest_user_str);
/* Guard against the config error of having the anonymous ftp tree owned
* by the user we are running as
*/
if (anon && vsf_sysutil_write_access("/"))
{
die("vsftpd: refusing to run with writable anonymous root");
}
p_sess->is_anonymous = anon;
process_post_login(p_sess);
bug("should not get here: common_do_login");
}
/* Parent */
vsf_priv_parent_postlogin(p_sess);
bug("should not get here in common_do_login");
}

46
twoprocess.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef VSF_TWOPROCESS_H
#define VSF_TWOPROCESS_H
struct mystr;
struct vsf_session;
/* vsf_two_process_start()
* PURPOSE
* Called to start FTP login processing using the two process model. This
* launches the unprivileged child to process the FTP login.
* PARAMETERS
* p_sess - the current session object
*/
void vsf_two_process_start(struct vsf_session* p_sess);
/* vsf_two_process_login()
* PURPOSE
* Called to propose a login using the two process model.
* PARAMETERS
* p_sess - the current session object
* p_pass_str - the proposed password
*/
void vsf_two_process_login(struct vsf_session* p_sess,
const struct mystr* p_pass_str);
/* vsf_two_process_get_priv_data_sock()
* PURPOSE
* Get a privileged port 20 bound data socket using the two process model.
* PARAMETERS
* p_sess - the current session object
* RETURNS
* The file descriptor of the privileged socket
*/
int vsf_two_process_get_priv_data_sock(struct vsf_session* p_sess);
/* vsf_two_process_chown_upload()
* PURPOSE
* Change ownership of an uploaded file using the two process model.
* PARAMETERS
* p_sess - the current session object
* fd - the file descriptor to change ownership on
*/
void vsf_two_process_chown_upload(struct vsf_session* p_sess, int fd);
#endif /* VSF_TWOPROCESS_H */

34
utility.c Normal file
View File

@ -0,0 +1,34 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* utility.c
*/
#include "utility.h"
#include "sysutil.h"
#include "defs.h"
#define DIE_DEBUG
void
die(const char* p_text)
{
#ifdef DIE_DEBUG
bug(p_text);
#endif
vsf_sysutil_exit(1);
}
void
bug(const char* p_text)
{
/* Rats. Try and write the reason to the network for diagnostics */
vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
(void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "500 OOPS: ", 10);
(void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text,
vsf_sysutil_strlen(p_text));
(void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "\r\n", 2);
vsf_sysutil_exit(1);
}

25
utility.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef VSF_UTILITY_H
#define VSF_UTILITY_H
struct mystr;
/* die()
* PURPOSE
* Terminate execution of the process, due to an abnormal (but non-bug)
* situation.
* PARAMETERS
* p_text - text string describing why the process is exiting
*/
void die(const char* p_text);
/* bug()
* PURPOSE
* Terminate execution of the process, due to a suspected bug, trying to emit
* the reason this happened down the network in FTP response format.
* PARAMETERS
* p_text - text string describing what bug trap has triggered
* */
void bug(const char* p_text);
#endif

43
vsf_findlibs.sh Executable file
View File

@ -0,0 +1,43 @@
#!/bin/sh
# Cheesy hacky location of additional link libraries.
# Optimizations for specific platforms, to avoid unneccessary libraries
if [ -r /etc/redhat-release ]; then
grep '7\.' /etc/redhat-release >/dev/null && echo "-lpam" && exit
grep '6\.' /etc/redhat-release >/dev/null && echo "-lpam -ldl" && exit
fi
locate_library() { [ ! "$1*" = "`echo $1*`" ]; }
# Look for PAM
locate_library /lib/libpam.so && echo "-lpam";
# Look for PAM in alternate location (FreeBSD)
locate_library /usr/lib/libpam.so && echo "-lpam";
# Look for the crypt library
# XXX - adds a link library even if it's not needed
locate_library /lib/libcrypt.so && echo "-lcrypt"
# Look for the crypt library (FreeBSD)
locate_library /usr/lib/libcrypt.so && echo "-lcrypt"
# Look for the dynamic linker library. Needed by older RedHat when
# you link in PAM
locate_library /lib/libdl.so && echo "-ldl";
# Look for libsocket. Solaris needs this.
locate_library /lib/libsocket.so && echo "-lsocket";
# Look for libnsl. Solaris needs this.
locate_library /lib/libnsl.so && echo "-lnsl";
# Look for libutil. Older FreeBSD need this for setproctitle().
locate_library /usr/lib/libutil.so && echo "-lutil";
# HP-UX ends shared libraries with .sl
locate_library /usr/lib/libpam.sl && echo "-lpam";
# For older HP-UX...
locate_library /usr/lib/libsec.sl && echo "-lsec";

26
vsftpd.8 Normal file
View File

@ -0,0 +1,26 @@
.\" Copyright (c) 2001 Daniel Jacobowitz <dan@debian.org>
.Dd March 8, 2001
.Dt VSFTPD 8
.Sh NAME
.Nm vsftpd
.Nd Very Secure FTP Daemon
.Sh SYNOPSIS
.Nm vsftpd
.Op Ar configuration file
.Sh DESCRIPTION
.Nm vsftpd
is the Very Secure File Transfer Protocol Daemon. The server should be
invoked from a
.Dq super-server
such as
.Xr inetd 8
or
.Xr xinetd 8 .
.Sh OPTIONS
An optional
.Op configuration file
may be given on the command line. The default configuration file is
.Pa /etc/vsftpd.conf .
.Sh SEE ALSO
.Xr vsftpd.conf 5

100
vsftpd.conf Normal file
View File

@ -0,0 +1,100 @@
# Example config file /etc/vsftpd.conf
#
# The default compiled in settings are very paranoid. This sample file
# loosens things up a bit, to make the ftp daemon more usable.
#
# Allow anonymous FTP?
anonymous_enable=YES
#
# Uncomment this to allow local users to log in.
#local_enable=YES
#
# Uncomment this to enable any form of FTP write command.
#write_enable=YES
#
# Default umask for local users is 077. You may wish to change this to 022,
# if your users expect that (022 is used by most other ftpd's)
#local_umask=022
#
# Uncomment this to allow the anonymous FTP user to upload files. This only
# has an effect if the above global write enable is activated. Also, you will
# obviously need to create a directory writable by the FTP user.
#anon_upload_enable=YES
#
# Uncomment this if you want the anonymous FTP user to be able to create
# new directories.
#anon_mkdir_write_enable=YES
#
# Activate directory messages - messages given to remote users when they
# go into a certain directory.
dirmessage_enable=YES
#
# Activate logging of uploads/downloads.
xferlog_enable=YES
#
# Make sure PORT transfer connections originate from port 20 (ftp-data).
connect_from_port_20=YES
#
# If you want, you can arrange for uploaded anonymous files to be owned by
# a different user. Note! Using "root" for uploaded files is not
# recommended!
#chown_uploads=YES
#chown_username=whoever
#
# You may override where the log file goes if you like. The default is shown
# below.
#xferlog_file=/var/log/vsftpd.log
#
# If you want, you can have your log file in standard ftpd xferlog format
#xferlog_std_format=YES
#
# You may change the default value for timing out an idle session.
#idle_session_timeout=600
#
# You may change the default value for timing out a data connection.
#data_connection_timeout=120
#
# It is recommended that you define on your system a unique user which the
# ftp server can use as a totally isolated and unprivileged user.
#nopriv_user=ftpsecure
#
# Enable this and the server will recognise asynchronous ABOR requests. Not
# recommended for security (the code is non-trivial). Not enabling it,
# however, may confuse older FTP clients.
#async_abor_enable=YES
#
# By default the server will pretend to allow ASCII mode but in fact ignore
# the request. Turn on the below options to have the server actually do ASCII
# mangling on files when in ASCII mode.
# Beware that turning on ascii_download_enable enables malicious remote parties
# to consume your I/O resources, by issuing the command "SIZE /big/file" in
# ASCII mode.
# These ASCII options are split into upload and download because you may wish
# to enable ASCII uploads (to prevent uploaded scripts etc. from breaking),
# without the DoS risk of SIZE and ASCII downloads. ASCII mangling should be
# on the client anyway..
#ascii_upload_enable=YES
#ascii_download_enable=YES
#
# You may fully customise the login banner string:
#ftpd_banner=Welcome to blah FTP service.
#
# You may specify a file of disallowed anonymous e-mail addresses. Apparently
# useful for combatting certain DoS attacks.
#deny_email_enable=YES
# (default follows)
#banned_email_file=/etc/vsftpd.banned_emails
#
# You may specify an explicit list of local users to chroot() to their home
# directory. If chroot_local_user is YES, then this list becomes a list of
# users to NOT chroot().
#chroot_list_enable=YES
# (default follows)
#chroot_list_file=/etc/vsftpd.chroot_list
#
# You may activate the "-R" option to the builtin ls. This is disabled by
# default to avoid remote users being able to cause excessive I/O on large
# sites. However, some broken FTP clients such as "ncftp" and "mirror" assume
# the presence of the "-R" option, so there is a strong case for enabling it.
#ls_recurse_enable=YES

439
vsftpd.conf.5 Normal file
View File

@ -0,0 +1,439 @@
.TH VSFTPD.CONF 5
.SH NAME
vsftpd.conf, the config file for vsftpd
.SH DESCRIPTION
vsftpd.conf may be used to control various aspects of vsftpd's behaviour. By
default, vsftpd looks for this file at the location
.BR /etc/vsftpd.conf .
However, you may override this by specifying a command line argument to
vsftpd. The command line argument is the pathname of the configuration file
for vsftpd. This behaviour is useful because you may wish to use an advanced
inetd such as
.BR xinetd
to launch vsftpd with different configuration files on a per virtual host
basis.
.SH FORMAT
The format of vsftpd.conf is very simple. Each line is either a comment or
a directive. Comment lines start with a # and are ignored. A directive line
has the format:
option=value
It is important to note that it is an error to put any space between the
option, = and value.
Each setting has a compiled in default which may be modified in the
configuration file.
.SH BOOLEAN OPTIONS
Below is a list of boolean options. The value for a boolean option may be set
to
.BR YES
or
.BR NO .
.TP
.B anon_mkdir_write_enable
If set to YES, anonymous users will be permitted to create new directories
under certain conditions. For this to work, the option
.BR write_enable
must be activated, and the anonymous ftp user must have write permission on
the parent directory.
Default: NO
.TP
.B anon_other_write_enable
If set to YES, anonymous users will be permitted to perform write operations
other than upload and create directory, such as deletion and renaming. This
is generally not recommended but included for completeness.
Default: NO
.TP
.B anon_upload_enable
If set to YES, anonymous users will be permitted to upload files under certain
conditions. For this to work, the option
.BR write_enable
must be activated, and the anonymous ftp user must have write permission on
desired upload locations.
Default: NO
.TP
.B anon_world_readable_only
When enabled, anonymous users will only be allowed to download files which
are world readable. This is recognising that the ftp user may own files,
especially in the presence of uploads.
Default: YES
.TP
.B anonymous_enable
Controls whether anonymous logins are permitted or not. If enabled,
both the usernames
.BR ftp
and
.BR anonymous
are recognised as anonymous logins.
Default: YES
.TP
.B ascii_download_enable
When enabled, ASCII mode data transfers will be honoured on downloads.
Default: NO
.TP
.B ascii_upload_enable
When enabled, ASCII mode data transfers will be honoured on uploads.
Default: NO
.TP
.B async_abor_enable
When enabled, a special FTP command known as "async ABOR" will be enabled.
Only ill advised FTP clients will use this feature. Addtionally, this feature
is awkward to handle, so it is disabled by default. Unfortunately, some FTP
clients will hang when cancelling a transfer unless this feature is available,
so you may wish to enable it.
Default: NO
.TP
.B chown_uploads
If enabled, all anonymously uploaded files will have the ownership changed
to the user specified in the setting
.BR chown_username .
This is useful from an administrative, and perhaps security, standpoint.
Default: NO
.TP
.B chroot_list_enable
If activated, you may provide a list of local users who are placed in a
chroot() jail in their home directory upon login. The meaning is slightly
different if chroot_local_user is set to YES. In this case, the list becomes
a list of users which are NOT to be placed in a chroot() jail.
By default, the file containing this list is
/etc/vsftpd.chroot_list, but you may override this with the
.BR chroot_list_file
setting.
Default: NO
.TP
.B chroot_local_user
If set to YES, local users will be placed in a chroot() jail in their home
directory after login.
.BR Warning:
This option has security implications, especially if the users also have
shell access. Only enable if you know what you are doing.
Default: NO
.TP
.B connect_from_port_20
This controls whether PORT style data connections use port 20 (ftp-data) on
the server machine. For security reasons, some clients may insist that this
is the case. Conversely, disabling this option enables vsftpd to run with
slightly less privilege.
Default: NO (but the sample config file enables it)
.TP
.B deny_email_enable
If activated, you may provide a list of anonymous password e-mail responses
which cause login to be denied. By default, the file containing this list is
/etc/vsftpd.banned_emails, but you may override this with the
.BR banned_email_file
setting.
Default: NO
.TP
.B dirmessage_enable
If enabled, users of the FTP server can be shown messages when they first
enter a new directory. By default, a directory is scanned for the
file .message, but that may be overridden with the configuration setting
.BR message_file .
Default: NO (but the sample config file enables it)
.TP
.B guest_enable
If enabled, all non-anonymous logins are classed as "guest" logins. A guest
login is remapped to the user specified in the
.BR guest_username
setting.
Default: NO
.TP
.B local_enable
Controls whether local logins are permitted or not. If enabled, normal
user accounts in /etc/passwd may be used to log in.
Default: NO
.TP
.B log_ftp_protocol
When enabled, all FTP requests and responses are logged, providing the option
xferlog_std_format is not enabled. Useful for debugging.
Default: NO
.TP
.B ls_recurse_enable
When enabled, this setting will allow the use of "ls -R". This is a minor
security risk, because a ls -R at the top level of a large site may consume
a lot of resources.
Default: NO
.TP
.B one_process_model
If you have a Linux 2.4 kernel, it is possible to use a different security
model which only uses one process per connection. It is a less pure security
model, but gains you performance. You really don't want to enable this unless
you know what you are doing, and your site supports huge numbers of
simultaneously connected users.
Default: NO
.TP
.B pasv_enable
Set to NO if you want to disallow the PASV method of obtaining a data
connection.
Default: YES
.TP
.B pasv_promiscuous
Set to YES if you want to disable the PASV security check that ensures the
data connection originates from the same IP address as the control connection.
Only enable if you know what you are doing! The only legitimate use for this
is in some form of secure tunnelling scheme.
Default: NO
.TP
.B port_enable
Set to NO if you want to disallow the PORT method of obtaining a data
connection.
Default: YES
.TP
.B setproctitle_enable
If enabled, vsftpd will try and show session status information in the system
process listing. In other words, the reported name of the process will change
to reflect what a vsftpd session is doing (idle, downloading etc). You
probably want to leave this off for security purposes.
Default: NO
.TP
.B text_userdb_names
By default, numeric IDs are shown in the user and group fields of directory
listings. You can get textual names by enabling this parameter. It is off
by default for performance reasons.
Default: NO
.TP
.B userlist_deny
This option is examined if
.B userlist_enable
is activated. If you set this setting to NO, then users will be denied login
unless they are explicitly listed in the file specified by
.BR userlist_file .
When login is denied, the denial is issued before the user is asked for a
password.
Default: YES
.TP
.B userlist_enable
If enabled, vsftpd will load a list of usernames, from the filename given by
.BR userlist_file .
If a user tries to log in using a name in this file, they will be denied
before they are asked for a password. This may be useful in preventing
cleartext passwords being transmitted. See also
.BR userlist_deny .
Default: NO
.TP
.B write_enable
This controls whether any FTP commands which change the filesystem are allowed
or not. These commands are: STOR, DELE, RNFR, RNTO, MKD, RMD, APPE and SITE.
Default: NO
.TP
.B xferlog_enable
If enabled, a log file will be maintained detailling uploads and downloads.
By default, this file will be placed at /var/log/vsftpd.log, but this location
may be overridden using the configuration setting
.BR xferlog_file .
Default: NO (but the sample config file enables it)
.TP
.B xferlog_std_format
If enabled, the transfer log file will be written in standard xferlog format,
as used by wu-ftpd. This is useful because you can reuse existing transfer
statistics generators. The default format is more readable, however.
Default: NO
.SH NUMERIC OPTIONS
Below is a list of numeric options. A numeric option must be set to a non
negative integer. Octal numbers are supported, for convenience of the umask
options. To specify an octal number, use 0 as the first digit of the number.
.TP
.B accept_timeout
The timeout, in seconds, for a remote client to establish connection with
a PASV style data connection.
Default: 60
.TP
.B anon_max_rate
The maximum data transfer rate permitted, in bytes per second, for anonymous
clients.
Default: 0 (unlimited)
.TP
.B anon_umask
The value that the umask for file creation is set to for anonymous users. NOTE! If you want to specify octal values, remember the "0" prefix otherwise the
value will be treated as a base 10 integer!
Default: 077
.TP
.B connect_timeout
The timeout, in seconds, for a remote client to respond to our PORT style
data connection.
Default: 60
.TP
.B data_connection_timeout
The timeout, in seconds, which is roughly the maximum time we permit data
transfers to stall for with no progress. If the timeout triggers, the remote
client is kicked off.
Default: 300
.TP
.B ftp_data_port
The port from which PORT style connections originate (as long as the poorly
named
.BR connect_from_port_20
is enabled).
Default: 20
.TP
.B idle_session_timeout
The timeout, in seconds, which is the maximum time a remote client may spend
between FTP commands. If the timeout triggers, the remote client is kicked
off.
Default: 300
.TP
.B local_max_rate
The maximum data transfer rate permitted, in bytes per second, for local
authenticated users.
Default: 0 (unlimited)
.TP
.B local_umask
The value that the umask for file creation is set to for local users. NOTE! If
you want to specify octal values, remember the "0" prefix otherwise the value
will be treated as a base 10 integer!
Default: 077
.TP
.B pasv_max_port
The maximum port to allocate for PASV style data connections. Can be used to
specify a narrow port range to assist firewalling.
Default: 0 (use any port)
.TP
.B pasv_min_port
The minimum port to allocate for PASV style data connections. Can be used to
specify a narrow port range to assist firewalling.
Default: 0 (use any port)
.SH STRING OPTIONS
Below is a list of string options.
.TP
.B banned_email_file
This option is the name of a file containing a list of anonymous e-mail
passwords which are not permitted. This file is consulted if the option
.BR deny_email_enable
is enabled.
Default: /etc/vsftpd.banned_emails
.TP
.B chown_username
This is the name of the user who is given ownership of anonymously uploaded
files. This option is only relevant if another option,
.BR chown_uploads ,
is set.
Default: root
.TP
.B chroot_list_file
The option is the name of a file containing a list of local users which
will be placed in a chroot() jail in their home directory. This option is
only relevant if the option
.BR chroot_list_enable
is enabled, and the option
.BR chroot_local_user
is disabled.
Default: /etc/vsftpd.chroot_list
.TP
.B guest_username
See the boolean setting
.BR guest_enable
for a description of what constitutes a guest login. This setting is the
real username which guest users are mapped to.
Default: ftp
.TP
.B ftp_username
This is the name of the user we use for handling anonymous FTP. The home
directory of this user is the root of the anonymous FTP area.
Default: ftp
.TP
.B ftpd_banner
This string option allows you to override the greeting banner displayed
by vsftpd when a connection first comes in.
Default: (none - default vsftpd banner is displayed)
.TP
.B message_file
This option is the name of the file we look for when a new directory is
entered. The contents are displayed to the remote user. This option is
only relevant if the option
.BR dirmessage_enable
is enabled.
Default: .message
.TP
.B nopriv_user
This is the name of the user that is used by vsftpd when it want to be
totally unprivileged. Note that this should be a dedicated user, rather
than nobody. The user nobody tends to be used for rather a lot of important
things on most machines.
Default: nobody
.TP
.B pam_service_name
This string is the name of the PAM service vsftpd will use.
Default: ftp
.TP
.B secure_chroot_dir
This option should be the name of a directory which is empty. Also, the
directory should not be writable by the ftp user. This directory is used
as a secure chroot() jail at times vsftpd does not require filesystem access.
Default: /usr/share/empty
.TP
.B userlist_file
This option is the name of the file loaded when the
.BR userlist_enable
option is active.
Default: /etc/vsftpd.user_list
.TP
.B xferlog_file
This option is the name of the file to which we write the transfer log. The
transfer log is only written if the option
.BR xferlog_enable
is set.
Default: /var/log/vsftpd.log
.SH AUTHOR
chris@scary.beasts.org

7
vsftpver.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef VSF_VERSION_H
#define VSF_VERSION_H
#define VSF_VERSION "0.9.2"
#endif /* VSF_VERSION_H */

18
xinetd.d/vsftpd Normal file
View File

@ -0,0 +1,18 @@
# default: on
# description:
# The vsftpd FTP server serves FTP connections. It uses
# normal, unencrypted usernames and passwords for authentication.
# vsftpd is designed to be secure.
service ftp
{
socket_type = stream
wait = no
user = root
server = /usr/sbin/vsftpd
# server_args =
# log_on_success += DURATION USERID
# log_on_failure += USERID
nice = 10
disable = no
}