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

Updated to v1.2.1

This commit is contained in:
Dag Wieers 2003-11-13 00:00:00 +01:00
parent 562d5b3c42
commit 8c2b3b01b5
33 changed files with 928 additions and 192 deletions

1
AUDIT
View File

@ -5,6 +5,7 @@ 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.
access.c 2
ascii.c 3
banner.c 2
dirchange.c 3

View File

@ -695,3 +695,51 @@ per-user config stuff.
At this point: v1.2.0 released!
===============================
- Apply NetBSD patch to sysdeputil.c to activate a few features. Thanks to
Lubomir Sedlacik <salo@netbsd.org>.
- Apply fix for broken clients that terminate commands with \r\r\n. Thanks
to Andrey Chernomyrdin <andrey@excom.spb.su>.
- AIX send_file support, thanks to Tomas Ogren <stric@ing.umu.se>.
- Fix typos in vsftpd.conf.5, thanks to SEKINE Tatsuo <tsekine@sdri.co.jp>.
- Simple -F flag support to LIST and NLST. Needed for some broken clients.
- Add simple ? wildcard in pattern matching.
- Make pasv_min_port and pasv_max_port work if they are the same value. Thanks
to Marvin Solomon <solomon@cs.wisc.edu>.
- Paranoia: ignore user_config_dir if username has a / in it.
- Implement stub ALLO command to keep busybox/ftpput happy.
- Implement REIN, ACCT and SMNT stubs.
- Implement FEAT along with an OPTS stub.
- Implement STAT (no-args version).
- Implement STAT (file/dir).
- Add very simple access control via hide_file and deny_file. These should
NOT be used for securing content as they are very dumb! Filesystem permissions
are still the recommended way for securing important content.
- Allow unsetting of string values with option= (i.e. blank).
- Default virtual users to being chroot()'ed to the guest_user's home
directory, if virtual_use_local_privs is not set.
- Add support for "user_sub_token", where you can set the home directory of
guest_user to "/home/virtual/$USER", and "user_sub_token" to "$USER" to
have a root directory auto generated based on username logging in, e.g.
fred logs in and gets chroot()'ed in /home/virtual/fred.
- Fix bug in str_replace_text if replace token matches at end of string.
- Recognize P@SW as PASV; works around an SMC router bug.
- Accept an async ABOR sequence if it arrives via non-urgent data. Fixes issue
with Cisco routers. Thanks to Eddie Corns <E.Corns@ed.ac.uk>.
- Implement simple {,} support in pattern matcher (nested not handled). Handy
to use with hide_file and deny_file options.
(v1.2.1pre2)
- Fix port range with pasv_min_port and pasv_max_port to use the full range
(the upper limit wasn't being used very often!).
- Activate SO_REUSEADDR on passive listen sockets - makes servers with
restricted port ranges much more useable!
- Add secure_email_list_enable, to provide simple anonymous password control.
For some cases, it's better than the hassle of virtual users. Idea thanks to
Malcolm O'Callaghan, <mjo@stamps.com>.
- Add some FAQ entries.
(v1.2.1pre3)
- Fix issue with failure to call openlog() before using tcp_wrappers. Part
of RH bugzilla #89765. (The more serious part was fixed with v1.2.0).
At this point: v1.2.1 released!
===============================

View File

@ -17,6 +17,11 @@ Whilst logged in as root, create the actual database file like this:
db_load -T -t hash -f logins.txt /etc/vsftpd_login.db
(Requires the Berkeley db program installed).
NOTE: Many systems have multiple versions of "db" installed, so you may
need to use e.g. db3_load for correct operation. This is known to affect
some Debian systems. The core issue is that pam_userdb expects its login
database to be a specific db version (often db3, whereas db4 may be installed
on your system).
This will create /etc/vsftpd_login.db. Obviously, you may want to make sure
the permissions are restricted:

30
FAQ
View File

@ -146,8 +146,36 @@ integration.
Q) Help! Does vsftpd support IPv6?
A) Yes, as of version 1.2.0. Read the vsftpd.conf.5 man page.
Q) Help! vsftpd doesn't build, it fails with an error about being unable to
find -lcap.
A) Install the libcap package and retry the build. Seems to affect Debian
users a lot.
Q) Help! I've put settings in /etc/vsftpd.conf, but they are not taking
effect!
A) This is affecting some RedHat users - some RedHat versions put the config
file in /etc/vsftpd/vsftpd.conf.
Q) Help! vsftpd doesn't build, it complains about problems with incomplete
types in sysutil.c.
A) Your system probably doesn't have IPv6 support. Either use a more modern
system, use an older vsftpd (e.g. v1.1.3), or wait for a version of vsftpd
without this problem!
Q) Help! I'm getting messages along the lines of 500 OOPS: vsf_sysutil_bind
when trying to do downloads (particularly lots of small files).
A) vsftpd-1.2.1 should sort this out.
Q) Help! Does vsftpd support hiding or denying certain files?
A) Yes. Look at the hide_file and deny_file options in the manual page.
Q) Help! Does vsftpd support FXP?
A) Yes. An FTP server does not have to do anything special to support FXP.
However, you many get tripped up by vsftpd's security precautions on IP
addresses. In order to relax these precautions, have a look in the
vsftpd.conf.5 for pasv_promiscuous (and the less advisable port_promiscuous).
Q) Blah.. blah..
A) For a good idea of what vsftpd can do, read the vsftpd.conf.5 man page
and the EXAMPLES.

View File

@ -98,10 +98,12 @@ drwxr-sr-x 2 0 50 4096 Jul 26 22:58 pub
226 Directory send OK.
ftp>
Step 5) Run from an inetd of some kind (optional, recommended)
Step 5) Run from an inetd of some kind (optional - standalone mode is now
recommended)
You may want to run the binary from an inetd of some kind, because this can
give you extra features - e.g. per-IP connection limiting if you use xinetd.
give you extra features - e.g. xinetd has a lot of settings. (Note that
vsftpd's inbuilt listener covers most of the more useful xinetd settings).
5a) If using standard "inetd", you will need to edit /etc/inetd.conf, and add
a line such as:
@ -146,7 +148,8 @@ Other notes
===========
Tested platforms (well, it builds)
- Any modern, well featured platform should work fine!
- Any modern, well featured platform should work fine! More recent versions of
the platforms listed below should be fine.
- RedHat Linux 8.0
- RedHat Linux 7.3
- RedHat Linux 7.2

View File

@ -13,7 +13,7 @@ OBJS = main.o utility.o prelogin.o ftpcmdio.o postlogin.o privsock.o \
postprivparent.o logging.o str.o netstr.o sysstr.o strlist.o \
banner.o filestr.o parseconf.o secutil.o \
ascii.o oneprocess.o twoprocess.o privops.o standalone.o hash.o \
tcpwrap.o ipv6parse.o \
tcpwrap.o ipv6parse.o access.o \
sysutil.o sysdeputil.o
.c.o:
@ -28,14 +28,14 @@ install:
else \
$(INSTALL) -m 755 vsftpd /usr/sbin/vsftpd; fi
if [ -x /usr/local/man ]; then \
$(INSTALL) -D -m 644 vsftpd.8 /usr/local/man/man8/vsftpd.8; \
$(INSTALL) -D -m 644 vsftpd.conf.5 /usr/local/man/man5/vsftpd.conf.5; \
$(INSTALL) -m 644 vsftpd.8 /usr/local/man/man8/vsftpd.8; \
$(INSTALL) -m 644 vsftpd.conf.5 /usr/local/man/man5/vsftpd.conf.5; \
elif [ -x /usr/share/man ]; then \
$(INSTALL) -D -m 644 vsftpd.8 /usr/share/man/man8/vsftpd.8; \
$(INSTALL) -D -m 644 vsftpd.conf.5 /usr/share/man/man5/vsftpd.conf.5; \
$(INSTALL) -m 644 vsftpd.8 /usr/share/man/man8/vsftpd.8; \
$(INSTALL) -m 644 vsftpd.conf.5 /usr/share/man/man5/vsftpd.conf.5; \
else \
$(INSTALL) -D -m 644 vsftpd.8 /usr/man/man8/vsftpd.8; \
$(INSTALL) -D -m 644 vsftpd.conf.5 /usr/man/man5/vsftpd.conf.5; fi
$(INSTALL) -m 644 vsftpd.8 /usr/man/man8/vsftpd.8; \
$(INSTALL) -m 644 vsftpd.conf.5 /usr/man/man5/vsftpd.conf.5; fi
if [ -x /etc/xinetd.d ]; then \
$(INSTALL) -m 644 xinetd.d/vsftpd /etc/xinetd.d/vsftpd; fi

3
README
View File

@ -1,4 +1,4 @@
This is vsftpd, version 1.2.0
This is vsftpd, version 1.2.1
Author: Chris Evans
Contact: chris@scary.beasts.org
@ -29,4 +29,5 @@ Configuration
All configuration options are documented in the manual page vsftpd.conf.5.
Various example configurations are discussed in the EXAMPLE directory.
Frequently asked questions are tackled in the FAQ file.

View File

@ -15,7 +15,7 @@ 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
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,

13
TODO
View File

@ -1,17 +1,21 @@
CRITICAL
========
- Improve FAQ, docs.
- Improve FAQ, docs (ongoing..)
- Integrated test suite (I'm so lazy..)
NOT SO CRITICAL
===============
- OpenSSL support.
- Fix for systems with no IPv6 (e.g. Solaris 7).
- PASV auto address guessing?
- option to chroot to home dir and THEN apply init_dir
- pasv_addr_file option (patch from Adam Luter)
- Sweedish characters showing as ? in the log.
- Limits on GIDs allowed to authenticate?
- Handle SIGINT.
- More stuff in the STAT output (session stats, etc. Perhaps even an "admin"
mode which dumps session details?).
- separate upload/download max rates
- select() is assuming Linux behaviour (not threatening stability)
@ -24,17 +28,14 @@ ON THE BACK BURNER
==================
- lock the files being modified?
- repeated PORT problem?
- transfer stats
- Small race: signal might come in just before we start a blocking call
- OpenSSL support
- log logout (pam session support provides this for locals)
- Limits on GIDs allowed to authenticate?
NOT PLANNED
===========
- telnet strings (no demand)
- better pattern matching in "ls" (no demand)
- "Minimal" build support
- transparent tar / compression support (no demand)

72
access.c Normal file
View File

@ -0,0 +1,72 @@
/*
* Part of Very Secure FTPd
* Licence: GPL
* Author: Chris Evans
* access.c
*
* Routines to do very very simple access control based on filenames.
*/
#include "access.h"
#include "ls.h"
#include "tunables.h"
#include "str.h"
int
vsf_access_check_file(const struct mystr* p_filename_str)
{
static struct mystr s_access_str;
if (!tunable_deny_file)
{
return 1;
}
if (str_isempty(&s_access_str))
{
str_alloc_text(&s_access_str, tunable_deny_file);
}
if (vsf_filename_passes_filter(p_filename_str, &s_access_str))
{
return 0;
}
else
{
struct str_locate_result loc_res =
str_locate_str(p_filename_str, &s_access_str);
if (loc_res.found)
{
return 0;
}
}
return 1;
}
int
vsf_access_check_file_visible(const struct mystr* p_filename_str)
{
static struct mystr s_access_str;
if (!tunable_hide_file)
{
return 1;
}
if (str_isempty(&s_access_str))
{
str_alloc_text(&s_access_str, tunable_hide_file);
}
if (vsf_filename_passes_filter(p_filename_str, &s_access_str))
{
return 0;
}
else
{
struct str_locate_result loc_res =
str_locate_str(p_filename_str, &s_access_str);
if (loc_res.found)
{
return 0;
}
}
return 1;
}

29
access.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef VSF_ACCESS_H
#define VSF_ACCESS_H
struct mystr;
/* vsf_access_check_file()
* PURPOSE
* Check whether the current session has permission to access the given
* filename.
* PARAMETERS
* p_filename_str - the filename to check access for
* RETURNS
* Returns 1 if access is granted, otherwise 0.
*/
int vsf_access_check_file(const struct mystr* p_filename_str);
/* vsf_access_check_file_visible()
* PURPOSE
* Check whether the current session has permission to view the given
* filename in directory listings.
* PARAMETERS
* p_filename_str - the filename to check visibility for
* RETURNS
* Returns 1 if the file should be visible, otherwise 0.
*/
int vsf_access_check_file_visible(const struct mystr* p_filename_str);
#endif /* VSF_ACCESS_H */

View File

@ -202,9 +202,10 @@ ftp_getline(struct mystr* p_str)
/* 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')
while (len > 0 && str_get_char_at(p_str, len - 1) == '\r')
{
str_trunc(p_str, len - 1);
--len;
}
}
}

View File

@ -12,8 +12,12 @@
#define FTP_EPSVALLOK 200
#define FTP_STRUOK 200
#define FTP_MODEOK 200
#define FTP_ALLOOK 202
#define FTP_FEAT 211
#define FTP_STATOK 211
#define FTP_SIZEOK 213
#define FTP_MDTMOK 213
#define FTP_STATFILE_OK 213
#define FTP_SITEHELP 214
#define FTP_HELP 214
#define FTP_SYSTOK 215
@ -46,6 +50,8 @@
#define FTP_BADSENDFILE 451
#define FTP_BADCMD 500
#define FTP_BADOPTS 501
#define FTP_COMMANDNOTIMPL 502
#define FTP_NEEDUSER 503
#define FTP_NEEDRNFR 503
#define FTP_BADSTRU 504

View File

@ -43,7 +43,7 @@ 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);
int is_verbose);
static int write_dir_list(struct mystr_list* p_dir_list, int remote_fd);
void
@ -246,7 +246,7 @@ vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess, const int remote_fd,
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);
p_option_str, p_filter_str, is_verbose);
}
static int
@ -255,7 +255,7 @@ transfer_dir_internal(struct vsf_session* p_sess, const int remote_fd,
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)
int is_verbose)
{
struct mystr_list dir_list = INIT_STRLIST;
struct mystr_list subdir_list = INIT_STRLIST;
@ -318,8 +318,7 @@ transfer_dir_internal(struct vsf_session* p_sess, const int remote_fd,
break;
}
retval = transfer_dir_internal(p_sess, remote_fd, p_subdir, &sub_str,
p_option_str, p_filter_str,
is_verbose, 1);
p_option_str, p_filter_str, is_verbose);
vsf_sysutil_closedir(p_subdir);
if (retval != 0)
{
@ -334,19 +333,10 @@ transfer_dir_internal(struct vsf_session* p_sess, const int remote_fd,
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;
}
}

View File

@ -31,6 +31,10 @@ void
vsf_log_init(struct vsf_session* p_sess)
{
int retval;
if (tunable_syslog_enable || tunable_tcp_wrappers)
{
vsf_sysutil_openlog();
}
if (!tunable_xferlog_enable && !tunable_dual_log_enable)
{
return;

166
ls.c
View File

@ -8,14 +8,13 @@
*/
#include "ls.h"
#include "access.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);
@ -35,6 +34,7 @@ vsf_ls_populate_dir_list(struct mystr_list* p_list,
int a_option;
int r_option;
int t_option;
int F_option;
int do_stat = 0;
loc_result = str_locate_char(p_option_str, 'a');
a_option = loc_result.found;
@ -42,6 +42,8 @@ vsf_ls_populate_dir_list(struct mystr_list* p_list,
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, 'F');
F_option = loc_result.found;
loc_result = str_locate_char(p_option_str, 'l');
if (loc_result.found)
{
@ -52,7 +54,7 @@ vsf_ls_populate_dir_list(struct mystr_list* p_list,
{
r_option = !r_option;
}
if (is_verbose || t_option || p_subdir_list != 0)
if (is_verbose || t_option || F_option || p_subdir_list != 0)
{
do_stat = 1;
}
@ -106,10 +108,15 @@ vsf_ls_populate_dir_list(struct mystr_list* p_list,
continue;
}
}
/* Don't show hidden directory entries */
if (!vsf_access_check_file_visible(&s_next_filename_str))
{
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))
if (!vsf_filename_passes_filter(&s_next_filename_str, p_filter_str))
{
continue;
}
@ -146,6 +153,10 @@ vsf_ls_populate_dir_list(struct mystr_list* p_list,
str_append_str(&s_final_file_str, &s_temp_str);
}
}
if (F_option && vsf_sysutil_statbuf_is_dir(s_p_statbuf))
{
str_append_char(&s_final_file_str, '/');
}
build_dir_line(&dirline_str, &s_final_file_str, s_p_statbuf);
}
else
@ -154,6 +165,17 @@ vsf_ls_populate_dir_list(struct mystr_list* p_list,
* but not for LIST
*/
str_copy(&dirline_str, &s_next_path_and_filename_str);
if (F_option)
{
if (vsf_sysutil_statbuf_is_dir(s_p_statbuf))
{
str_append_char(&dirline_str, '/');
}
else if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf))
{
str_append_char(&dirline_str, '@');
}
}
str_append_text(&dirline_str, "\r\n");
}
/* Add filename into our sorted list - sorting by filename or time. Also,
@ -191,9 +213,9 @@ vsf_ls_populate_dir_list(struct mystr_list* p_list,
str_free(&normalised_base_dir_str);
}
static int
filename_passes_filter(const struct mystr* p_filename_str,
const struct mystr* p_filter_str)
int
vsf_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
@ -202,36 +224,45 @@ filename_passes_filter(const struct mystr* p_filename_str,
* implementation to be buggy.
*
* Currently supported pattern(s):
* - any number of wildcards, "*"
* - any number of wildcards, "*" or "?"
* - {,} syntax (not nested)
*
* Note that pattern matching is only supported within the last path
* component. For example, searching for /a/b/? will work, but searching
* for /a/?/c will not.
*/
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;
struct mystr filter_remain_str = INIT_MYSTR;
struct mystr name_remain_str = INIT_MYSTR;
struct mystr temp_str = INIT_MYSTR;
struct mystr brace_list_str = INIT_MYSTR;
struct mystr new_filter_str = INIT_MYSTR;
int ret = 0;
char last_token = 0;
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);
str_copy(&filter_remain_str, p_filter_str);
str_copy(&name_remain_str, p_filename_str);
while (!str_isempty(&s_filter_remain_str))
while (!str_isempty(&filter_remain_str))
{
static struct mystr s_match_needed_str;
/* Locate next wildcard */
/* Locate next special token */
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 */
str_locate_chars(&filter_remain_str, "*?{");
/* Isolate text leading up to token (if any) - needs to be matched */
if (locate_result.found)
{
unsigned int indexx = locate_result.index;
str_left(&s_filter_remain_str, &s_match_needed_str, indexx);
str_mid_to_end(&s_filter_remain_str, &s_temp_str, indexx + 1);
str_copy(&s_filter_remain_str, &s_temp_str);
str_left(&filter_remain_str, &s_match_needed_str, indexx);
str_mid_to_end(&filter_remain_str, &temp_str, indexx + 1);
str_copy(&filter_remain_str, &temp_str);
last_token = locate_result.char_found;
}
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;
/* No more tokens. Must match remaining filter string exactly. */
str_copy(&s_match_needed_str, &filter_remain_str);
str_empty(&filter_remain_str);
last_token = 0;
}
if (!str_isempty(&s_match_needed_str))
{
@ -239,35 +270,92 @@ filename_passes_filter(const struct mystr* p_filename_str,
* current position, or we could allow it to start anywhere
*/
unsigned int indexx;
locate_result = str_locate_str(&s_name_remain_str, &s_match_needed_str);
locate_result = str_locate_str(&name_remain_str, &s_match_needed_str);
if (!locate_result.found)
{
/* Fail */
return 0;
goto out;
}
indexx = locate_result.index;
if (must_match_at_current_pos && indexx > 0)
{
/* Fail */
return 0;
goto out;
}
/* Chop matched string out of remainder */
str_mid_to_end(&s_name_remain_str, &s_temp_str,
str_mid_to_end(&name_remain_str, &temp_str,
indexx + str_getlen(&s_match_needed_str));
str_copy(&s_name_remain_str, &s_temp_str);
str_copy(&name_remain_str, &temp_str);
}
if (last_token == '?')
{
if (str_isempty(&name_remain_str))
{
goto out;
}
str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1);
str_copy(&name_remain_str, &temp_str);
must_match_at_current_pos = 1;
}
else if (last_token == '{')
{
struct str_locate_result end_brace =
str_locate_char(&filter_remain_str, '}');
struct str_locate_result comma =
str_locate_char(&filter_remain_str, ',');
must_match_at_current_pos = 1;
if (end_brace.found && comma.found && comma.index < end_brace.index)
{
str_split_char(&filter_remain_str, &temp_str, '}');
str_copy(&brace_list_str, &filter_remain_str);
str_copy(&filter_remain_str, &temp_str);
str_split_char(&brace_list_str, &temp_str, ',');
while (!str_isempty(&brace_list_str))
{
str_copy(&new_filter_str, &brace_list_str);
str_append_str(&new_filter_str, &filter_remain_str);
if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str))
{
ret = 1;
goto out;
}
str_copy(&brace_list_str, &temp_str);
str_split_char(&brace_list_str, &temp_str, ',');
}
goto out;
}
else if (str_isempty(&name_remain_str) ||
str_get_char_at(&name_remain_str, 0) != '{')
{
return 0;
}
else
{
str_right(&name_remain_str, &temp_str,
str_getlen(&name_remain_str) - 1);
str_copy(&name_remain_str, &temp_str);
}
}
else
{
must_match_at_current_pos = 0;
}
/* 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)
/* Any incoming string left means no match unless we ended on the correct
* type of wildcard.
*/
if (str_getlen(&name_remain_str) > 0 && last_token != '*')
{
return 0;
goto out;
}
/* OK, a match */
return 1;
ret = 1;
out:
str_free(&filter_remain_str);
str_free(&name_remain_str);
str_free(&temp_str);
str_free(&brace_list_str);
str_free(&new_filter_str);
return ret;
}
static void

14
ls.h
View File

@ -27,5 +27,19 @@ void vsf_ls_populate_dir_list(struct mystr_list* p_list,
const struct mystr* p_filter_str,
int is_verbose);
/* vsf_filename_passes_filter()
* PURPOSE
* Determine whether the given filename is matched by the given filter string.
* The format of the filter string is a small subset of a regular expression.
* Currently, just * and ? are supported.
* PARAMETERS
* p_filename_str - the filename to match
* p_filter_str - the filter to match against
* RETURNS
* Returns 1 if there is a match, 0 otherwise.
*/
int vsf_filename_passes_filter(const struct mystr* p_filename_str,
const struct mystr* p_filter_str);
#endif /* VSF_LS_H */

14
main.c
View File

@ -47,7 +47,7 @@ main(int argc, const char* argv[])
/* Userids */
-1, -1, -1,
/* Pre-chroot() cache */
INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, 1,
INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, 1,
/* Logging */
-1, -1, INIT_MYSTR, 0, 0, 0, INIT_MYSTR, 0,
/* Buffers */
@ -149,7 +149,7 @@ main(int argc, const char* argv[])
tunable_banned_email_file, VSFTP_CONF_FILE_MAX);
if (vsf_sysutil_retval_is_error(retval))
{
die2("cannot open banned e-mail list file:", tunable_banned_email_file);
die2("cannot open anon e-mail list file:", tunable_banned_email_file);
}
}
if (tunable_banner_file)
@ -161,6 +161,16 @@ main(int argc, const char* argv[])
die2("cannot open banner file:", tunable_banner_file);
}
}
if (tunable_secure_email_list_enable)
{
int retval = str_fileread(&the_session.email_passwords_str,
tunable_email_password_file,
VSFTP_CONF_FILE_MAX);
if (vsf_sysutil_retval_is_error(retval))
{
die2("cannot open email passwords file:", tunable_email_password_file);
}
}
/* Special case - can force one process model if we've got a setup
* needing _no_ privs
*/

View File

@ -83,6 +83,7 @@ parseconf_bool_array[] =
{ "download_enable", &tunable_download_enable },
{ "dirlist_enable", &tunable_dirlist_enable },
{ "chmod_enable", &tunable_chmod_enable },
{ "secure_email_list_enable", &tunable_secure_email_list_enable },
{ 0, 0 }
};
@ -140,6 +141,10 @@ parseconf_str_array[] =
{ "user_config_dir", &tunable_user_config_dir },
{ "listen_address6", &tunable_listen_address6 },
{ "cmds_allowed", &tunable_cmds_allowed },
{ "hide_file", &tunable_hide_file },
{ "deny_file", &tunable_deny_file },
{ "user_sub_token", &tunable_user_sub_token },
{ "email_password_file", &tunable_email_password_file },
{ 0, 0 }
};
@ -208,6 +213,32 @@ static void
handle_config_setting(struct mystr* p_setting_str, struct mystr* p_value_str,
int errs_fatal)
{
/* 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 */
const char** p_curr_setting = p_str_setting->p_variable;
if (*p_curr_setting)
{
vsf_sysutil_free((char*)*p_curr_setting);
}
if (str_isempty(p_value_str))
{
*p_curr_setting = 0;
}
else
{
*p_curr_setting = str_strdup(p_value_str);
}
return;
}
p_str_setting++;
}
}
if (str_isempty(p_value_str))
{
if (errs_fatal)
@ -273,25 +304,6 @@ handle_config_setting(struct mystr* p_setting_str, struct mystr* p_value_str,
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 */
const char** p_curr_setting = p_str_setting->p_variable;
if (*p_curr_setting)
{
vsf_sysutil_free((char*)*p_curr_setting);
}
*p_curr_setting = str_strdup(p_value_str);
return;
}
p_str_setting++;
}
}
if (errs_fatal)
{
die2("unrecognised variable in config file: ", str_getbuf(p_setting_str));

View File

@ -22,6 +22,7 @@
#include "logging.h"
#include "sysdeputil.h"
#include "ipv6parse.h"
#include "access.h"
/* Private local functions */
static void handle_pwd(struct vsf_session* p_sess);
@ -51,12 +52,15 @@ static void handle_site_umask(struct vsf_session* p_sess,
static void handle_eprt(struct vsf_session* p_sess);
static void handle_help(struct vsf_session* p_sess);
static void handle_stou(struct vsf_session* p_sess);
static void handle_stat(struct vsf_session* p_sess);
static void handle_stat_file(struct vsf_session* p_sess);
static int pasv_active(struct vsf_session* p_sess);
static int port_active(struct vsf_session* p_sess);
static void pasv_cleanup(struct vsf_session* p_sess);
static void port_cleanup(struct vsf_session* p_sess);
static void handle_dir_common(struct vsf_session* p_sess, int full_details);
static void handle_dir_common(struct vsf_session* p_sess, int full_details,
int stat_cmd);
static void prepend_path_to_filename(struct mystr* p_str);
static int get_remote_transfer_fd(struct vsf_session* p_sess);
static int dispose_remote_transfer_fd(struct vsf_session* p_sess);
@ -158,7 +162,8 @@ process_post_login(struct vsf_session* p_sess)
}
else if (tunable_pasv_enable &&
!p_sess->epsv_all &&
str_equal_text(&p_sess->ftp_cmd_str, "PASV"))
(str_equal_text(&p_sess->ftp_cmd_str, "PASV") ||
str_equal_text(&p_sess->ftp_cmd_str, "P@SW")))
{
handle_pasv(p_sess, 0);
}
@ -255,7 +260,11 @@ process_post_login(struct vsf_session* p_sess)
{
handle_site(p_sess);
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "ABOR"))
/* Note - the weird ABOR string is checking for an async ABOR arriving
* without a SIGURG condition.
*/
else if (str_equal_text(&p_sess->ftp_cmd_str, "ABOR") ||
str_equal_text(&p_sess->ftp_cmd_str, "\377\364\377\362ABOR"))
{
vsf_cmdio_write(p_sess, FTP_ABOR_NOCONN, "No transfer to ABOR.");
}
@ -302,6 +311,44 @@ process_post_login(struct vsf_session* p_sess)
{
handle_stou(p_sess);
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "ALLO"))
{
vsf_cmdio_write(p_sess, FTP_ALLOOK, "ALLO command ignored.");
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "REIN"))
{
vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "REIN not implemented.");
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "ACCT"))
{
vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "ACCT not implemented.");
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "SMNT"))
{
vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "SMNT not implemented.");
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "FEAT"))
{
vsf_cmdio_write_hyphen(p_sess, FTP_FEAT, "Features:");
vsf_cmdio_write_raw(p_sess, " MDTM\r\n");
vsf_cmdio_write_raw(p_sess, " REST STREAM\r\n");
vsf_cmdio_write_raw(p_sess, " SIZE\r\n");
vsf_cmdio_write(p_sess, FTP_FEAT, "End");
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "OPTS"))
{
vsf_cmdio_write(p_sess, FTP_BADOPTS, "Option not understood.");
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "STAT") &&
str_isempty(&p_sess->ftp_arg_str))
{
handle_stat(p_sess);
}
else if (tunable_dirlist_enable &&
str_equal_text(&p_sess->ftp_cmd_str, "STAT"))
{
handle_stat_file(p_sess);
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "PASV") ||
str_equal_text(&p_sess->ftp_cmd_str, "PORT") ||
str_equal_text(&p_sess->ftp_cmd_str, "STOR") ||
@ -319,7 +366,14 @@ process_post_login(struct vsf_session* p_sess)
str_equal_text(&p_sess->ftp_cmd_str, "RETR") ||
str_equal_text(&p_sess->ftp_cmd_str, "LIST") ||
str_equal_text(&p_sess->ftp_cmd_str, "NLST") ||
str_equal_text(&p_sess->ftp_cmd_str, "STOU"))
str_equal_text(&p_sess->ftp_cmd_str, "STOU") ||
str_equal_text(&p_sess->ftp_cmd_str, "ALLO") ||
str_equal_text(&p_sess->ftp_cmd_str, "REIN") ||
str_equal_text(&p_sess->ftp_cmd_str, "ACCT") ||
str_equal_text(&p_sess->ftp_cmd_str, "SMNT") ||
str_equal_text(&p_sess->ftp_cmd_str, "FEAT") ||
str_equal_text(&p_sess->ftp_cmd_str, "OPTS") ||
str_equal_text(&p_sess->ftp_cmd_str, "STAT"))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
}
@ -348,7 +402,13 @@ handle_pwd(struct vsf_session* p_sess)
static void
handle_cwd(struct vsf_session* p_sess)
{
int retval = str_chdir(&p_sess->ftp_arg_str);
int retval;
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
retval = str_chdir(&p_sess->ftp_arg_str);
if (retval == 0)
{
/* Handle any messages */
@ -456,6 +516,7 @@ handle_pasv(struct vsf_session* p_sess, int is_epsv)
{
p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock();
}
vsf_sysutil_activate_reuseaddr(p_sess->pasv_listen_fd);
while (--bind_retries)
{
int retval;
@ -463,11 +524,11 @@ handle_pasv(struct vsf_session* p_sess, int is_epsv)
/* IPPORT_RESERVED */
unsigned short min_port = 1024;
unsigned short max_port = 65535;
if (tunable_pasv_min_port > min_port && tunable_pasv_min_port < max_port)
if (tunable_pasv_min_port > min_port && tunable_pasv_min_port <= max_port)
{
min_port = tunable_pasv_min_port;
}
if (tunable_pasv_max_port > min_port && tunable_pasv_max_port < max_port)
if (tunable_pasv_max_port >= min_port && tunable_pasv_max_port < max_port)
{
max_port = tunable_pasv_max_port;
}
@ -475,8 +536,8 @@ handle_pasv(struct vsf_session* p_sess, int is_epsv)
the_port <<= 8;
the_port |= vsf_sysutil_get_random_byte();
scaled_port = (double) min_port;
scaled_port += ((double) the_port / (double) 65535) *
((double) max_port - min_port);
scaled_port += ((double) the_port / (double) 65536) *
((double) max_port - min_port + 1);
the_port = (unsigned short) scaled_port;
vsf_sysutil_sockaddr_clone(&s_p_sockaddr, p_sess->p_local_addr);
vsf_sysutil_sockaddr_set_port(s_p_sockaddr, the_port);
@ -557,6 +618,11 @@ handle_retr(struct vsf_session* p_sess)
"No support for resume of ASCII transfer.");
return;
}
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
opened_file = str_open(&p_sess->ftp_arg_str, kVSFSysStrOpenReadOnly);
if (vsf_sysutil_retval_is_error(opened_file))
{
@ -631,11 +697,11 @@ file_close_out:
static void
handle_list(struct vsf_session* p_sess)
{
handle_dir_common(p_sess, 1);
handle_dir_common(p_sess, 1, 0);
}
static void
handle_dir_common(struct vsf_session* p_sess, int full_details)
handle_dir_common(struct vsf_session* p_sess, int full_details, int stat_cmd)
{
static struct mystr s_option_str;
static struct mystr s_filter_str;
@ -644,11 +710,12 @@ handle_dir_common(struct vsf_session* p_sess, int full_details)
int remote_fd;
int dir_allow_read = 1;
struct vsf_sysutil_dir* p_dir = 0;
int retval = 0;
str_empty(&s_option_str);
str_empty(&s_filter_str);
/* By default open the current directory */
str_alloc_text(&s_dir_name_str, ".");
if (!pasv_active(p_sess) && !port_active(p_sess))
if (!stat_cmd && !pasv_active(p_sess) && !port_active(p_sess))
{
vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Use PORT or PASV first.");
return;
@ -671,6 +738,11 @@ handle_dir_common(struct vsf_session* p_sess, int full_details)
}
if (!str_isempty(&s_filter_str))
{
if (!vsf_access_check_file(&s_filter_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
/* First check - is it an outright directory, as in "ls /pub" */
p_dir = str_opendir(&s_filter_str);
if (p_dir != 0)
@ -706,12 +778,27 @@ handle_dir_common(struct vsf_session* p_sess, int full_details)
p_dir = str_opendir(&s_dir_name_str);
}
/* Fine, do it */
remote_fd = get_remote_transfer_fd(p_sess);
if (stat_cmd)
{
remote_fd = VSFTP_COMMAND_FD;
str_append_char(&s_option_str, 'a');
}
else
{
remote_fd = get_remote_transfer_fd(p_sess);
}
if (vsf_sysutil_retval_is_error(remote_fd))
{
goto dir_close_out;
}
vsf_cmdio_write(p_sess, FTP_DATACONN, "Here comes the directory listing.");
if (stat_cmd)
{
vsf_cmdio_write_hyphen(p_sess, FTP_STATFILE_OK, "Status follows:");
}
else
{
vsf_cmdio_write(p_sess, FTP_DATACONN, "Here comes the directory listing.");
}
if (p_sess->is_anonymous && p_dir && tunable_anon_world_readable_only)
{
vsf_sysutil_dir_stat(p_dir, &s_p_dirstat);
@ -720,25 +807,43 @@ handle_dir_common(struct vsf_session* p_sess, int full_details)
dir_allow_read = 0;
}
}
if (p_dir == 0 || !dir_allow_read)
if (p_dir != 0 && dir_allow_read)
{
retval = vsf_ftpdataio_transfer_dir(p_sess, remote_fd, p_dir,
&s_dir_name_str, &s_option_str,
&s_filter_str, full_details);
}
if (stat_cmd)
{
vsf_cmdio_write(p_sess, FTP_STATFILE_OK, "End of status");
}
else if (p_dir == 0 || !dir_allow_read)
{
vsf_cmdio_write(p_sess, FTP_TRANSFEROK,
"Transfer done (but failed to open directory).");
}
else if (retval == 0)
{
vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Directory send OK.");
}
else
{
(void) vsf_ftpdataio_transfer_dir(p_sess, remote_fd, p_dir,
&s_dir_name_str, &s_option_str,
&s_filter_str, full_details);
vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure writing network stream.");
}
if (!stat_cmd)
{
(void) dispose_remote_transfer_fd(p_sess);
}
(void) dispose_remote_transfer_fd(p_sess);
dir_close_out:
if (p_dir)
{
vsf_sysutil_closedir(p_dir);
}
port_cleanup(p_sess);
pasv_cleanup(p_sess);
if (!stat_cmd)
{
port_cleanup(p_sess);
pasv_cleanup(p_sess);
}
}
static void
@ -852,6 +957,11 @@ handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique)
get_unique_filename(&s_filename, p_filename);
p_filename = &s_filename;
}
if (!vsf_access_check_file(p_filename))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
/* NOTE - actual file permissions will be governed by the tunable umask */
/* XXX - do we care about race between create and chown() of anonymous
* upload?
@ -947,6 +1057,11 @@ static void
handle_mkd(struct vsf_session* p_sess)
{
int retval;
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
vsf_log_start_entry(p_sess, kVSFLogEntryMkdir);
str_copy(&p_sess->log_str, &p_sess->ftp_arg_str);
prepend_path_to_filename(&p_sess->log_str);
@ -978,7 +1093,13 @@ handle_mkd(struct vsf_session* p_sess)
static void
handle_rmd(struct vsf_session* p_sess)
{
int retval = str_rmdir(&p_sess->ftp_arg_str);
int retval;
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
retval = str_rmdir(&p_sess->ftp_arg_str);
if (retval != 0)
{
vsf_cmdio_write(p_sess, FTP_FILEFAIL,
@ -994,7 +1115,13 @@ handle_rmd(struct vsf_session* p_sess)
static void
handle_dele(struct vsf_session* p_sess)
{
int retval = str_unlink(&p_sess->ftp_arg_str);
int retval;
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
retval = str_unlink(&p_sess->ftp_arg_str);
if (retval != 0)
{
vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Delete operation failed.");
@ -1028,6 +1155,11 @@ handle_rnfr(struct vsf_session* p_sess)
int retval;
/* Clear old value */
str_free(&p_sess->rnfr_filename_str);
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
/* Does it exist? */
retval = str_stat(&p_sess->ftp_arg_str, &p_statbuf);
if (retval == 0)
@ -1053,6 +1185,11 @@ handle_rnto(struct vsf_session* p_sess)
"RNFR required first.");
return;
}
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
/* NOTE - might overwrite destination file. Not a concern because the same
* could be accomplished with DELE.
*/
@ -1072,7 +1209,7 @@ handle_rnto(struct vsf_session* p_sess)
static void
handle_nlst(struct vsf_session* p_sess)
{
handle_dir_common(p_sess, 0);
handle_dir_common(p_sess, 0, 0);
}
static void
@ -1180,7 +1317,13 @@ handle_size(struct vsf_session* p_sess)
* I will not do it because it is a potential I/O DoS.
*/
static struct vsf_sysutil_statbuf* s_p_statbuf;
int retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf);
int retval;
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf);
if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf))
{
vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not get file size.");
@ -1238,6 +1381,11 @@ handle_site_chmod(struct vsf_session* p_sess, struct mystr* p_arg_str)
vsf_cmdio_write(p_sess, FTP_BADCMD, "SITE CHMOD needs 2 arguments.");
return;
}
if (!vsf_access_check_file(&s_chmod_file_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
/* Don't worry - our chmod() implementation only allows 0 - 0777 */
perms = str_octal_to_uint(p_arg_str);
retval = str_chmod(&s_chmod_file_str, perms);
@ -1284,7 +1432,13 @@ static void
handle_mdtm(struct vsf_session* p_sess)
{
static struct vsf_sysutil_statbuf* s_p_statbuf;
int retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf);
int retval;
if (!vsf_access_check_file(&p_sess->ftp_arg_str))
{
vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied.");
return;
}
retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf);
if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf))
{
vsf_cmdio_write(p_sess, FTP_FILEFAIL,
@ -1372,13 +1526,15 @@ static void
handle_help(struct vsf_session* p_sess)
{
vsf_cmdio_write_hyphen(p_sess, FTP_HELP,
"The following commands are implemented.");
"The following commands are recognized.");
vsf_cmdio_write_raw(p_sess,
" ABOR APPE CDUP CWD DELE EPRT EPSV HELP LIST MDTM MKD MODE NLST NOOP\r\n");
" ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD\r\n");
vsf_cmdio_write_raw(p_sess,
" PASS PASV PORT PWD QUIT REST RETR RMD RNFR RNTO SITE SIZE STOR STOU\r\n");
" MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR\r\n");
vsf_cmdio_write_raw(p_sess,
" STRU SYST TYPE USER XCUP XCWD XMKD XPWD XRMD\r\n");
" RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD\r\n");
vsf_cmdio_write_raw(p_sess,
" XPWD XRMD\r\n");
vsf_cmdio_write(p_sess, FTP_HELP, "Help OK.");
}
@ -1409,3 +1565,59 @@ get_unique_filename(struct mystr* p_outstr, const struct mystr* p_base_str)
}
}
static void
handle_stat(struct vsf_session* p_sess)
{
vsf_cmdio_write_hyphen(p_sess, FTP_STATOK, "FTP server status:");
vsf_cmdio_write_raw(p_sess, " Connected to ");
vsf_cmdio_write_raw(p_sess, str_getbuf(&p_sess->remote_ip_str));
vsf_cmdio_write_raw(p_sess, "\r\n");
vsf_cmdio_write_raw(p_sess, " Logged in as ");
vsf_cmdio_write_raw(p_sess, str_getbuf(&p_sess->user_str));
vsf_cmdio_write_raw(p_sess, "\r\n");
vsf_cmdio_write_raw(p_sess, " TYPE: ");
if (p_sess->is_ascii)
{
vsf_cmdio_write_raw(p_sess, "ASCII\r\n");
}
else
{
vsf_cmdio_write_raw(p_sess, "BINARY\r\n");
}
if (p_sess->bw_rate_max == 0)
{
vsf_cmdio_write_raw(p_sess, " No session bandwidth limit\r\n");
}
else
{
vsf_cmdio_write_raw(p_sess, " Session bandwidth limit in byte/s is ");
vsf_cmdio_write_raw(p_sess, vsf_sysutil_ulong_to_str(p_sess->bw_rate_max));
vsf_cmdio_write_raw(p_sess, "\r\n");
}
if (tunable_idle_session_timeout == 0)
{
vsf_cmdio_write_raw(p_sess, " No session timeout\r\n");
}
else
{
vsf_cmdio_write_raw(p_sess, " Session timeout in seconds is ");
vsf_cmdio_write_raw(p_sess,
vsf_sysutil_ulong_to_str(tunable_idle_session_timeout));
vsf_cmdio_write_raw(p_sess, "\r\n");
}
if (p_sess->num_clients > 0)
{
vsf_cmdio_write_raw(p_sess, " At session startup, client count was ");
vsf_cmdio_write_raw(p_sess, vsf_sysutil_ulong_to_str(p_sess->num_clients));
vsf_cmdio_write_raw(p_sess, "\r\n");
}
vsf_cmdio_write_raw(p_sess, " vsFTPd - secure, fast, stable\r\n");
vsf_cmdio_write(p_sess, FTP_STATOK, "End of status");
}
static void
handle_stat_file(struct vsf_session* p_sess)
{
handle_dir_common(p_sess, 1, 1);
}

View File

@ -160,6 +160,12 @@ handle_anonymous_login(struct vsf_session* p_sess,
{
return kVSFLoginFail;
}
if (!str_isempty(&p_sess->email_passwords_str) &&
(!str_contains_line(&p_sess->email_passwords_str, p_pass_str) ||
str_isempty(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))
@ -176,6 +182,7 @@ handle_anonymous_login(struct vsf_session* p_sess,
str_free(&ftp_username_str);
}
str_free(&p_sess->banned_email_str);
str_free(&p_sess->email_passwords_str);
return kVSFLoginAnon;
}

View File

@ -31,7 +31,7 @@ vsf_secutil_change_credentials(const struct mystr* p_user_str,
{
struct mystr dir_str = INIT_MYSTR;
/* Work out where the chroot() jail is */
if (p_dir_str == 0)
if (p_dir_str == 0 || str_isempty(p_dir_str))
{
str_alloc_text(&dir_str, vsf_sysutil_user_get_homedir(p_user));
}

View File

@ -52,6 +52,7 @@ struct vsf_session
/* Things we need to cache before we chroot() */
struct mystr banned_email_str;
struct mystr email_passwords_str;
struct mystr userlist_str;
struct mystr banner_str;
int tcp_wrapper_ok;

29
str.c
View File

@ -317,15 +317,17 @@ 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;
unsigned int lhs_len;
str_copy(&s_lhs_chunk_str, p_str);
str_free(p_str);
do
{
lhs_len = str_getlen(&s_lhs_chunk_str);
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))
if (str_getlen(&s_lhs_chunk_str) < lhs_len)
{
str_append_text(p_str, p_to);
}
@ -422,6 +424,31 @@ str_locate_char(const struct mystr* p_str, char look_char)
return str_locate_text(p_str, look_str);
}
struct str_locate_result
str_locate_chars(const struct mystr* p_str, const char* p_chars)
{
struct str_locate_result retval;
unsigned int num_chars = vsf_sysutil_strlen(p_chars);
unsigned int i = 0;
retval.found = 0;
for (; i < p_str->len; ++i)
{
unsigned int j = 0;
char this_char = p_str->p_buf[i];
for (; j < num_chars; ++j)
{
if (p_chars[j] == this_char)
{
retval.found = 1;
retval.index = i;
retval.char_found = p_chars[j];
return retval;
}
}
}
return retval;
}
struct str_locate_result
str_locate_text(const struct mystr* p_str, const char* p_text)
{

3
str.h
View File

@ -69,6 +69,7 @@ struct str_locate_result
{
int found;
unsigned int index;
char char_found;
};
struct str_locate_result str_locate_char(
@ -81,6 +82,8 @@ 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);
struct str_locate_result str_locate_chars(
const struct mystr* p_str, const char* p_chars);
void str_left(const struct mystr* p_str, struct mystr* p_out,
unsigned int chars);

View File

@ -35,6 +35,7 @@
#undef VSF_SYSDEP_HAVE_LINUX_SENDFILE
#undef VSF_SYSDEP_HAVE_FREEBSD_SENDFILE
#undef VSF_SYSDEP_HAVE_HPUX_SENDFILE
#undef VSF_SYSDEP_HAVE_AIX_SENDFILE
#undef VSF_SYSDEP_HAVE_SETPROCTITLE
#undef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
#undef VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE
@ -70,6 +71,17 @@
#define VSF_SYSDEP_HAVE_SETPROCTITLE
#endif
#if defined(__NetBSD__)
#include <stdlib.h>
#define VSF_SYSDEP_HAVE_SETPROCTITLE
#include <sys/param.h>
#if __NetBSD_Version__ >= 106070000
#define WTMPX_FILE _PATH_WTMPX
#else
#undef VSF_SYSDEP_HAVE_UTMPX
#endif
#endif
#ifdef __hpux
#include <sys/socket.h>
#ifdef SF_DISCONNECT
@ -93,6 +105,18 @@
#undef VSF_SYSDEP_HAVE_LIBCAP
#endif
#ifdef _AIX
#undef VSF_SYSDEP_HAVE_USERSHELL
#undef VSF_SYSDEP_HAVE_LIBCAP
#undef VSF_SYSDEP_HAVE_UTMPX
#undef VSF_SYSDEP_HAVE_PAM
#undef VSF_SYSDEP_HAVE_SHADOW
#undef VSF_SYSDEP_HAVE_SETPROCTITLE
#define VSF_SYSDEP_HAVE_AIX_SENDFILE
#define VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK
#define VSF_SYSDEP_HAVE_MAP_ANON
#endif
#ifdef __osf__
#undef VSF_SYSDEP_HAVE_USERSHELL
#endif
@ -593,6 +617,7 @@ static int do_sendfile(const int out_fd, const int in_fd,
#if defined(VSF_SYSDEP_HAVE_LINUX_SENDFILE) || \
defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) || \
defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE) || \
defined(VSF_SYSDEP_HAVE_AIX_SENDFILE) || \
defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE)
if (tunable_use_sendfile)
{
@ -633,6 +658,24 @@ static int do_sendfile(const int out_fd, const int in_fd,
retval = (int) written;
}
}
#elif defined(VSF_SYSDEP_HAVE_AIX_SENDFILE)
{
struct sf_parms sf_iobuf;
vsf_sysutil_memclr(&sf_iobuf, sizeof(sf_iobuf));
sf_iobuf.header_data = NULL;
sf_iobuf.header_length = 0;
sf_iobuf.trailer_data = NULL;
sf_iobuf.trailer_length = 0;
sf_iobuf.file_descriptor = in_fd;
sf_iobuf.file_offset = start_pos;
sf_iobuf.file_bytes = num_send;
retval = send_file((int*)&out_fd, &sf_iobuf, 0);
if (retval >= 0)
{
retval = sf_iobuf.bytes_sent;
}
}
#else /* must be VSF_SYSDEP_HAVE_HPUX_SENDFILE */
{
retval = sendfile(out_fd, in_fd, start_pos, num_send, NULL, 0);
@ -701,7 +744,7 @@ static int do_sendfile(const int out_fd, const int in_fd,
total_written += num_written;
if (num_written != num_read)
{
return -1;
return num_written;
}
if (num_written > num_send)
{

View File

@ -18,6 +18,8 @@
/* Activate 64-bit file support on Linux/32bit */
#define _FILE_OFFSET_BITS 64
/* And Solaris.. */
/*#define _LARGEFILE64_SOURCE 1*/
/* For Linux, this adds nothing :-) */
#include "port/porting_junk.h"
@ -2066,6 +2068,7 @@ vsf_sysutil_inet_ntop(const struct vsf_sysutil_sockaddr* p_sockptr)
else
{
die("can only support ipv4 and ipv6 currently");
return 0;
}
}

View File

@ -55,6 +55,7 @@ int tunable_session_support = 0;
int tunable_download_enable = 1;
int tunable_dirlist_enable = 1;
int tunable_chmod_enable = 1;
int tunable_secure_email_list_enable = 0;
unsigned int tunable_accept_timeout = 60;
unsigned int tunable_connect_timeout = 60;
@ -97,3 +98,8 @@ const char* tunable_listen_address = 0;
const char* tunable_user_config_dir = 0;
const char* tunable_listen_address6 = 0;
const char* tunable_cmds_allowed = 0;
const char* tunable_hide_file = 0;
const char* tunable_deny_file = 0;
const char* tunable_user_sub_token = 0;
const char* tunable_email_password_file = "/etc/vsftpd.email_passwords";

View File

@ -51,6 +51,7 @@ extern int tunable_session_support; /* utmp, wtmp, pam_session */
extern int tunable_download_enable; /* Can download anything? */
extern int tunable_dirlist_enable; /* Can see any dirs? */
extern int tunable_chmod_enable; /* Is CHMOD allowed? (local) */
extern int tunable_secure_email_list_enable; /* Require specific anon email */
/* Integer/numeric defines */
extern unsigned int tunable_accept_timeout;
@ -92,6 +93,10 @@ extern const char* tunable_listen_address;
extern const char* tunable_user_config_dir;
extern const char* tunable_listen_address6;
extern const char* tunable_cmds_allowed;
extern const char* tunable_hide_file;
extern const char* tunable_deny_file;
extern const char* tunable_user_sub_token;
extern const char* tunable_email_password_file;
#endif /* VSF_TUNABLES_H */

View File

@ -31,9 +31,11 @@ static void common_do_login(struct vsf_session* p_sess,
const struct mystr* p_user_str, int do_chroot,
int anon);
static void handle_per_user_config(const struct mystr* p_user_str);
static void calculate_chdir_dir(int anon, struct mystr* p_chroot_str,
static void calculate_chdir_dir(int anon, struct mystr* p_userdir_str,
struct mystr* p_chroot_str,
struct mystr* p_chdir_str,
const struct mystr* p_user_str);
const struct mystr* p_user_str,
const struct mystr* p_orig_user_str);
static void
handle_sigchld(int duff)
@ -243,6 +245,7 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
int do_chroot, int anon)
{
int was_anon = anon;
const struct mystr* p_orig_user_str = p_user_str;
int newpid;
vsf_sysutil_install_null_sighandler(kVSFSysUtilSigCHLD);
/* Asks the pre-login child to go away (by exiting) */
@ -261,12 +264,8 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
struct mystr guest_user_str = INIT_MYSTR;
struct mystr chroot_str = INIT_MYSTR;
struct mystr chdir_str = INIT_MYSTR;
struct mystr userdir_str = INIT_MYSTR;
unsigned int secutil_option = VSF_SECUTIL_OPTION_USE_GROUPS;
calculate_chdir_dir(anon, &chroot_str, &chdir_str, p_user_str);
if (do_chroot)
{
secutil_option |= VSF_SECUTIL_OPTION_CHROOT;
}
/* Child - drop privs and start proper FTP! */
if (tunable_guest_enable && !anon)
{
@ -276,13 +275,20 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
if (!tunable_virtual_use_local_privs)
{
anon = 1;
do_chroot = 1;
}
}
if (do_chroot)
{
secutil_option |= VSF_SECUTIL_OPTION_CHROOT;
}
if (!anon)
{
secutil_option |= VSF_SECUTIL_OPTION_CHANGE_EUID;
}
vsf_secutil_change_credentials(p_user_str, 0, &chroot_str,
calculate_chdir_dir(was_anon, &userdir_str, &chroot_str, &chdir_str,
p_user_str, p_orig_user_str);
vsf_secutil_change_credentials(p_user_str, &userdir_str, &chroot_str,
0, secutil_option);
if (!str_isempty(&chdir_str))
{
@ -291,6 +297,7 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
str_free(&guest_user_str);
str_free(&chroot_str);
str_free(&chdir_str);
str_free(&userdir_str);
/* Guard against the config error of having the anonymous ftp tree owned
* by the user we are running as
*/
@ -310,59 +317,80 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
static void
handle_per_user_config(const struct mystr* p_user_str)
{
if (tunable_user_config_dir)
struct mystr filename_str = INIT_MYSTR;
struct vsf_sysutil_statbuf* p_statbuf = 0;
struct str_locate_result loc_result;
int retval;
if (!tunable_user_config_dir)
{
struct mystr filename_str = INIT_MYSTR;
struct vsf_sysutil_statbuf* p_statbuf = 0;
int retval;
str_alloc_text(&filename_str, tunable_user_config_dir);
str_append_char(&filename_str, '/');
str_append_str(&filename_str, p_user_str);
retval = str_stat(&filename_str, &p_statbuf);
/* Security - ignore unless owned by root */
if (!vsf_sysutil_retval_is_error(retval) &&
vsf_sysutil_statbuf_get_uid(p_statbuf) == VSFTP_ROOT_UID)
{
vsf_parseconf_load_file(str_getbuf(&filename_str), 1);
}
str_free(&filename_str);
vsf_sysutil_free(p_statbuf);
return;
}
/* Security paranoia - ignore if user has a / in it. */
loc_result = str_locate_char(p_user_str, '/');
if (loc_result.found)
{
return;
}
str_alloc_text(&filename_str, tunable_user_config_dir);
str_append_char(&filename_str, '/');
str_append_str(&filename_str, p_user_str);
retval = str_stat(&filename_str, &p_statbuf);
/* Security - ignore unless owned by root */
if (!vsf_sysutil_retval_is_error(retval) &&
vsf_sysutil_statbuf_get_uid(p_statbuf) == VSFTP_ROOT_UID)
{
vsf_parseconf_load_file(str_getbuf(&filename_str), 1);
}
str_free(&filename_str);
vsf_sysutil_free(p_statbuf);
}
static void
calculate_chdir_dir(int anon, struct mystr* p_chroot_str,
calculate_chdir_dir(int anon_login, struct mystr* p_userdir_str,
struct mystr* p_chroot_str,
struct mystr* p_chdir_str,
const struct mystr* p_user_str)
const struct mystr* p_user_str,
const struct mystr* p_orig_user_str)
{
if (anon && tunable_anon_root)
if (!anon_login)
{
str_alloc_text(p_chroot_str, tunable_anon_root);
}
else if (!anon && tunable_local_root)
{
str_alloc_text(p_chroot_str, tunable_local_root);
}
/* If enabled, the chroot() location embedded in the HOMEDIR takes
* precedence.
*/
if (!anon && tunable_passwd_chroot_enable)
{
struct mystr homedir_str = INIT_MYSTR;
const struct vsf_sysutil_user* p_user = str_getpwnam(p_user_str);
struct str_locate_result loc_result;
if (p_user == 0)
{
die2("cannot locate user entry:", str_getbuf(p_user_str));
}
str_alloc_text(&homedir_str, vsf_sysutil_user_get_homedir(p_user));
loc_result = str_locate_text(&homedir_str, "/./");
str_alloc_text(p_userdir_str, vsf_sysutil_user_get_homedir(p_user));
if (tunable_user_sub_token)
{
str_replace_text(p_userdir_str, tunable_user_sub_token,
str_getbuf(p_orig_user_str));
}
}
if (anon_login && tunable_anon_root)
{
str_alloc_text(p_chroot_str, tunable_anon_root);
}
else if (!anon_login && tunable_local_root)
{
str_alloc_text(p_chroot_str, tunable_local_root);
if (tunable_user_sub_token)
{
str_replace_text(p_chroot_str, tunable_user_sub_token,
str_getbuf(p_orig_user_str));
}
}
/* If enabled, the chroot() location embedded in the HOMEDIR takes
* precedence.
*/
if (!anon_login && tunable_passwd_chroot_enable)
{
struct str_locate_result loc_result;
loc_result = str_locate_text(p_userdir_str, "/./");
if (loc_result.found)
{
str_split_text(&homedir_str, p_chdir_str, "/./");
str_copy(p_chroot_str, &homedir_str);
str_split_text(p_userdir_str, p_chdir_str, "/./");
str_copy(p_chroot_str, p_userdir_str);
}
str_free(&homedir_str);
}
}

View File

@ -9,13 +9,22 @@
.Op Ar configuration file
.Sh DESCRIPTION
.Nm vsftpd
is the Very Secure File Transfer Protocol Daemon. The server should be
invoked from a
is the Very Secure File Transfer Protocol Daemon. The server can be launched
via a
.Dq super-server
such as
.Xr inetd 8
or
.Xr xinetd 8 .
Alternatively, vsftpd can be launched in standalone mode, in which case vsftpd
itself will listen on the network. This latter mode is easier to use, and
recommended. It is activated by setting
.Pa listen=YES
in
.Pa /etc/vsftpd.conf .
Direct execution of the
.Nm vsftpd
binary will then launch the FTP service ready for immediate client connections.
.Sh OPTIONS
An optional
.Op configuration file

View File

@ -88,7 +88,7 @@ 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
Only ill advised FTP clients will use this feature. Additionally, 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.
@ -135,8 +135,8 @@ 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.
If set to YES, local users will be (by default) placed in a chroot() jail in
their home directory after login.
.BR Warning:
This option has security implications, especially if the users have upload
permission, or shell access. Only enable if you know what you are doing.
@ -193,7 +193,7 @@ Default: NO
.B force_dot_files
If activated, files and directories starting with . will be shown in directory
listings even if the "a" flag was not used by the client. This override
exludes the "." and ".." entries.
excludes the "." and ".." entries.
Default: NO
.TP
@ -222,7 +222,7 @@ Default: NO
.B listen_ipv6
Like the listen parameter, except vsftpd will listen on an IPv6 socket instead
of an IPv4 one. This parameter and the listen parameter are mutually
exlusive.
exclusive.
Default: NO
.TP
@ -265,7 +265,7 @@ If enabled, along with
.BR chroot_local_user
, then a chroot() jail location may be specified on a per-user basis. Each
user's jail is derived from their home directory string in /etc/passwd. The
occurence of /./ in the home directory string denotes that the jail is at that
occurrence of /./ in the home directory string denotes that the jail is at that
particular location in the path.
Default: NO
@ -280,7 +280,8 @@ Default: YES
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.
is in some form of secure tunnelling scheme, or perhaps to facilitate FXP
support.
Default: NO
.TP
@ -295,6 +296,18 @@ Set to YES if you want to disable the PORT security check that ensures that
outgoing data connections can only connect to the client. Only enable if
you know what you are doing!
Default: NO
.TP
.B secure_email_list_enable
Set to YES if you want only a specified list of e-mail passwords for anonymous
logins to be accepted. This is useful as a low-hassle way of restricting
access to low-security content without needing virtual users. When enabled,
anonymous logins are prevented unless the password provided is listed in the
file specified by the
.BR email_password_file
setting. The file format is one password per line, no extra whitespace. The
default filename is /etc/vsftpd.email_passwords.
Default: NO
.TP
.B session_support
@ -552,9 +565,10 @@ 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
is enabled. If the option
.BR chroot_local_user
is disabled.
is enabled, then the list file becomes a list of users to NOT place in a
chroot() jail.
Default: /etc/vsftpd.chroot_list
.TP
@ -566,13 +580,33 @@ FTP server. Example: cmds_allowed=PASV,RETR,QUIT
Default: (none)
.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.
.B deny_file
This option can be used to set a pattern for filenames (and directory names
etc.) which should not be accessible in any way. The affected items are not
hidden, but any attempt to do anything to them (download, change into
directory, affect something within directory etc.) will be denied. This option
is very simple, and should not be used for serious access control - the
filesystem's permissions should be used in preference. However, this option
may be useful in certain virtual user setups. In particular aware that if
a filename is accessible by a variety of names (perhaps due to symbolic
links or hard links), then care must be taken to deny access to all the names.
Access will be denied to items if their name contains the string given by
hide_file, or if they match the regular expression specified by hide_file.
Note that vsftpd's regular expression matching code is a simple implementation
which is a subset of full regular expression functionality. Because of this,
you will need to carefully and exhaustively test any application of this
option. And you are recommended to use filesystem permissions for any
important security policies due to their greater reliability. Example:
deny_file={*.mp3,*.mov,.private}
Default: ftp
Default: (none)
.TP
.B email_password_file
This option can be used to provide an alternate file for usage by the
.BR secure_email_list_enable
setting.
Default: /etc/vsftpd.email_passwords
.TP
.B ftp_username
This is the name of the user we use for handling anonymous FTP. The home
@ -586,6 +620,26 @@ by vsftpd when a connection first comes in.
Default: (none - default vsftpd banner is displayed)
.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 hide_file
This option can be used to set a pattern for filenames (and directory names
etc.) which should be hidden from directory listings. Despite being hidden,
the files / directories etc. are fully accessible to clients who know what
names to actually use. Items will be hidden if their names contain the string
given by hide_file, or if they match the regular expression specified by
hide_file. Note that vsftpd's regular expression matching code is a simple
implementation which is a subset of full regular expression functionality.
Example: hide_file={*.mp3,.hidden,hide*,h?}
Default: (none)
.TP
.B listen_address
If vsftpd is in standalone mode, the default listen address (of all local
interfaces) may be overridden by this setting. Provide a numeric IP address.
@ -651,7 +705,32 @@ and then log on as the user "chris", then vsftpd will apply the settings in
the file
.BR /etc/vsftpd_user_conf/chris
for the duration of the session. The format of this file is as detailed in
this manual page!
this manual page! PLEASE NOTE that not all settings are effective on a
per-user basis. For example, many settings only prior to the user's session
being started. Examples of settings which will not affect any behviour on
a per-user basis include listen_address, banner_file, max_per_ip, max_clients,
xferlog_file, etc.
Default: (none)
.TP
.B user_sub_token
This option is useful is conjunction with virtual users. It is used to
automatically generate a home directory for each virtual user, based on a
template. For example, if the home directory of the real user specified via
.BR guest_username
is
.BR /home/virtual/$USER ,
and
.BR user_sub_token
is set to
.BR $USER ,
then when virtual user fred logs in, he will end up (usually chroot()'ed) in
the directory
.BR /home/virtual/fred .
This option also takes affect if
.BR local_root
contains
.BR user_sub_token .
Default: (none)
.TP
@ -661,7 +740,7 @@ This option is the name of the file loaded when the
option is active.
Default: /etc/vsftpd.user_list
.BR
.TP
.B vsftpd_log_file
This option is the name of the file to which we write the vsftpd style
log file. This log is only written if the option

View File

@ -1,7 +1,7 @@
#ifndef VSF_VERSION_H
#define VSF_VERSION_H
#define VSF_VERSION "1.2.0"
#define VSF_VERSION "1.2.1"
#endif /* VSF_VERSION_H */