[SCM] proftpd-mod-vroot branch, master, updated. debian/0.9.2-1-11-g9649971

Mahyuddin Susanto udienz at gmail.com
Fri Dec 20 04:10:45 UTC 2013


The following commit has been merged in the master branch:
commit 74a910471e7afc5114eaf45ffd7fff62bc8a5eb4
Author: Mahyuddin Susanto <udienz at gmail.com>
Date:   Fri Dec 20 11:09:53 2013 +0700

    Imported Upstream version 0.9.3

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5fd47f1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+proftpd-mod_vroot
+=================
+
+ProFTPD module that creates "virtual" chroots
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 423b64f..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,24 +0,0 @@
-proftpd-mod-vroot (0.9.2-3) UNRELEASED; urgency=low
-
-  * Removing libacl1-dev as BD and increasing proftpd-dev to (>= 1.3.4~rc3-2~)
-  * Moved to binNMU 
-  * Fixed spelling-error-in-description lintian message
-  * Fixed unversioned-copyright-format 
-
- -- Fabrizio Regalli <fabreg at fabreg.it>  Tue, 22 Nov 2011 15:51:26 +0100
-
-proftpd-mod-vroot (0.9.2-2) unstable; urgency=low
-
-  * Fixed spelling-error-in-description lintian message 
-  * Update d/copyright to latest .174 DEP5 revision
-  * Fixed timewarp-standards-version lintian message
-
- -- Fabrizio Regalli <fabreg at fabreg.it>  Thu, 29 Sep 2011 15:24:55 +0200
-
-proftpd-mod-vroot (0.9.2-1) unstable; urgency=low
-
-  * Initial Release. (closes: #618765)
-  * Added doc-base
-  * Added dh_clean to d/rules
-
- -- Fabrizio Regalli <fabreg at fabreg.it>  Thu, 15 Mar 2011 18:14:35 +0100
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index 7f8f011..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-7
diff --git a/debian/control b/debian/control
deleted file mode 100644
index a1b1cb0..0000000
--- a/debian/control
+++ /dev/null
@@ -1,24 +0,0 @@
-Source: proftpd-mod-vroot
-Section: net
-Priority: optional
-Maintainer: ProFTPD Maintainance Team <pkg-proftpd-maintainers at lists.alioth.debian.org>
-Uploaders: Fabrizio Regalli <fabreg at fabreg.it>,
-    Francesco Paolo Lovergine <frankie at debian.org>
-DM-Upload-Allowed: yes
-Build-Depends: debhelper (>= 7.0.50~), proftpd-dev (>= 1.3.4~rc3-2~)
-Standards-Version: 3.9.2
-Homepage: http://www.castaglia.org/proftpd/modules/mod_vroot.html
-Vcs-Git: git://git.debian.org/pkg-proftpd/proftpd-mod-vroot.git
-Vcs-Browser: http://git.debian.org/?p=pkg-proftpd/proftpd-mod-vroot.git;a=summary
-
-
-Package: proftpd-mod-vroot
-Architecture: any
-Depends: proftpd-basic (>= 1.3.4~rc2-2~), ${misc:Depends}, ${shlibs:Depends}, ${proftpd:Depends}
-Breaks: proftpd-basic (<< 1.3.4~rc2-2~)
-Description: ProFTPD module mod_vroot
- The purpose of this module to is to implement a virtual chroot
- capability that does not require root privileges.
- The mod_vroot module provides this capability by using ProFTPD's 
- FS API. It also allows one to map a directory out of the user chroot
- as an alias in the chroot for sharing common directories.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index cf44c82..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,30 +0,0 @@
-Format: http://anonscm.debian.org/viewvc/dep/web/deps/dep5.mdwn?revision=174
-Upstream-Name: mod_vroot
-Upstream-Contact: TJ Saunders
-Source: http://www.castaglia.org/proftpd/modules/mod_vroot.html
-
-Files: *
-Copyright: 	2007-2010, TJ Saunders
-License: GPL-2+
-
-License: GPL-2+
- This package is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- .
- This package is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
- .
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>
- .
- On Debian systems, the complete text of the GNU General
- Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
-
-Files: debian/*
-Copyright: 2011, Fabrizio Regalli <fabreg at fabreg.it>
-License: GPL-2+
-
diff --git a/debian/proftpd-mod-vroot.dirs b/debian/proftpd-mod-vroot.dirs
deleted file mode 100644
index f0e8d4e..0000000
--- a/debian/proftpd-mod-vroot.dirs
+++ /dev/null
@@ -1,2 +0,0 @@
-usr/lib/proftpd
-usr/share/proftpd/templates
diff --git a/debian/proftpd-mod-vroot.doc-base b/debian/proftpd-mod-vroot.doc-base
deleted file mode 100644
index 8ce9ae0..0000000
--- a/debian/proftpd-mod-vroot.doc-base
+++ /dev/null
@@ -1,9 +0,0 @@
-Document: proftpd-mod-vroot
-Title: Documentation for the mod_vroot ProFTPD
-Author: 
-Abstract: Configuration reference, modules documentation, FAQs and HOWTOs
-Section: System/Administration
-
-Format: HTML
-Index: /usr/share/doc/proftpd-mod-vroot/mod_vroot.html
-Files: /usr/share/doc/proftpd-mod-vroot/*.html
diff --git a/debian/proftpd-mod-vroot.docs b/debian/proftpd-mod-vroot.docs
deleted file mode 100644
index 3048e6b..0000000
--- a/debian/proftpd-mod-vroot.docs
+++ /dev/null
@@ -1 +0,0 @@
-mod_vroot.html
diff --git a/debian/proftpd-mod-vroot.install b/debian/proftpd-mod-vroot.install
deleted file mode 100644
index 6b6e368..0000000
--- a/debian/proftpd-mod-vroot.install
+++ /dev/null
@@ -1 +0,0 @@
-debian/templates/vroot.conf usr/share/doc/proftpd-mod-vroot/
diff --git a/debian/proftpd-mod-vroot.links b/debian/proftpd-mod-vroot.links
deleted file mode 100644
index 53f7a33..0000000
--- a/debian/proftpd-mod-vroot.links
+++ /dev/null
@@ -1 +0,0 @@
-usr/share/doc/proftpd-mod-vroot/vroot.conf usr/share/proftpd/templates/vroot.conf
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 3c139c8..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/make -f
-# -*- makefile -*-
-
-export DH_VERBOSE=1
-VERSION=$(shell grep PROFTPD_VERSION_TEXT /usr/include/proftpd/version.h|cut -d\" -f2)
-MODULE_NAME=mod_vroot
-DEBNAME=proftpd-mod-vroot
-
-%:
-	dh $@
-
-override_dh_auto_build:
-	DESTDIR=$(CURDIR)/debian/$(DEBNAME) prxs -c $(MODULE_NAME).c
-	
-override_dh_auto_install:
-	DESTDIR=$(CURDIR)/debian/$(DEBNAME) prxs -i -c $(MODULE_NAME).c
-	rm -f $(CURDIR)/debian/$(DEBNAME)/usr/lib/proftpd/*.la \
-		  $(CURDIR)/debian/$(DEBNAME)/usr/lib/proftpd/*.a
-
-override_dh_gencontrol:
-	cat /usr/share/proftpd/proftpd-substvars >>$(CURDIR)/debian/$(DEBNAME).substvars
-	dh_gencontrol
-
-clean:
-	dh_clean
-	$(RM) $(MODULE_NAME).a *.o *.la *.lo
-	$(LIBTOOL) --mode=clean $(RM) "$(MODULE_NAME).o"
-	$(LIBTOOL) --mode=clean $(RM) `echo "$(MODULE_NAME).la" | sed 's/\.la$\/.lo/g'`
-	$(RM) -rf $(CURDIR)/debian/proftpd-mod-vroot $(CURDIR)/debian/*.log $(CURDIR)/debian/files $(CURDIR)/debian/$(DEBNAME).substvars
-
-distclean: clean
-	$(RM) -r .libs/ 
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/templates/vroot.conf b/debian/templates/vroot.conf
deleted file mode 100644
index ed7533f..0000000
--- a/debian/templates/vroot.conf
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Proftpd sample configuration for vroot.
-#
-
-<IfModule mod_vroot.c>
-
-# VRootAlias
-# Syntax: VRootAlias src-path dst-path
-# Default: None
-# The VRootAlias directive is used to create an "alias" of a directory outside of the chroot area into 
-# the chroot. The dst-path parameter is a relative path, relative to the chroot area (i.e. the directory
-# in which the session starts). The src-path parameter, on the other hand, is an absolute path, and may be to a file or directory.
-# For example, you might map a shared upload directory into a user's home directory using:
-#  <IfModule mod_vroot.c>
-#    VRootEngine on
-#
-#    DefaultRoot ~
-#    VRootAlias /var/ftp/upload ~/upload
-#  </IfModule>
-# This will automatically create an "upload" directory to appear in the chroot area (in this case, the user's home directory).
-# Note that this directive will not work if the VRootServerRoot is used. 
-
-# VRootEngine
-# Syntax: VRootEngine on|off
-# Default: None
-# The VRootEngine directive enables the virtual chroot engine implemented by mod_vroot. 
-# If enabled, the virtual chroot will be used in place of the operating system's chroot(2).
-# This directive affects any DefaultRoot directives and any <Anonymous> contexts within the
-# server context in which the VRootEngine directive appears.
-VRootEngine on
-DefaultRoot ~
-VRootAlias /var/ftp/upload ~/upload
-
-# VRootLog
-# Syntax: VRootLog file
-# Default: None
-# The VRootLog directive is used to specify a log file for mod_vroot's reporting on a
-# per-server basis. The file parameter given must be the full path to the file to use for logging.
-
-# VRootOptions
-# Syntax: VRootOptions opt1 ...
-# Default: None
-# The VRootOptions directive is used to configure various optional behavior of mod_vroot.
-# Example:
-#   VRootOptions allowSymlinks
-# The currently implemented options are:
-#     * allowSymlinks
-# Normally, any symlinks that point outside of the vroot area simply do not work.
-# When the allowSymlinks option is enabled, these symlinks will be allowed. 
-# Note that by enabling symlinks, the efficacy of the vroot "jail" is reduced. 
-
-# VRootServerRoot
-# Syntax: VRootServerRoot path
-# Default: None
-# The VRootServerRoot directive is used to configure a directory to which the mod_vroot module will perform a real chroot.
-# The idea is that each <VirtualHost> can have its own directory to which a real chroot(2) system call is made;
-# the user-specific home directories will be virtual roots underneath this directory. Thus some measure of security,
-# via the chroot(2) system call, is provided by the kernel, while still allowing symlinked shared folders among users of this <VirtualHost>.
-# For example:
-#  <VirtualHost a.b.c.d>
-#    VRootEngine on
-#    VRootServerRoot /etc/ftpd/a.b.c.d/
-#    VRootOptions allowSymlinks
-#    DefaultRoot ~
-#    ...
-#
-#  </VirtualHost>
-
-</IfModule>
-
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index 2db693a..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,2 +0,0 @@
-version=3
-http://githubredir.debian.net/github/Castaglia/proftpd-mod_vroot/ (.*).tar.gz
\ No newline at end of file
diff --git a/mod_vroot.c b/mod_vroot.c
index 0b864ab..3c27be5 100644
--- a/mod_vroot.c
+++ b/mod_vroot.c
@@ -2,7 +2,7 @@
  * ProFTPD: mod_vroot -- a module implementing a virtual chroot capability
  *                       via the FSIO API
  *
- * Copyright (c) 2002-2011 TJ Saunders
+ * Copyright (c) 2002-2013 TJ Saunders
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,11 +32,11 @@
 #include "conf.h"
 #include "privs.h"
 
-#define MOD_VROOT_VERSION 	"mod_vroot/0.9.2"
+#define MOD_VROOT_VERSION 	"mod_vroot/0.9.3"
 
 /* Make sure the version of proftpd is as necessary. */
-#if PROFTPD_VERSION_NUMBER < 0x0001030201
-# error "ProFTPD 1.3.2rc1 or later required"
+#if PROFTPD_VERSION_NUMBER < 0x0001030406
+# error "ProFTPD 1.3.4b or later required"
 #endif
 
 static const char *vroot_log = NULL;
@@ -53,9 +53,16 @@ static pr_table_t *vroot_aliastab = NULL;
 static pool *vroot_dir_pool = NULL;
 static pr_table_t *vroot_dirtab = NULL;
 
+#if PROFTPD_VERSION_NUMBER >= 0x0001030407
+static int vroot_use_mkdtemp = FALSE;
+#endif /* ProFTPD 1.3.4c or later */
+
 static unsigned int vroot_opts = 0;
 #define	VROOT_OPT_ALLOW_SYMLINKS	0x0001
 
+/* vroot_realpath() flags */
+#define VROOT_REALPATH_FL_ABS_PATH	0x001
+
 /* vroot_lookup_path() flags */
 #define VROOT_LOOKUP_FL_NO_ALIASES	0x0001
 
@@ -68,8 +75,10 @@ static int vroot_is_alias(const char *);
  */
 
 static void strmove(register char *dst, register const char *src) {
-  if (!dst || !src)
+  if (dst == NULL ||
+      src == NULL) {
     return;
+  }
 
   while (*src != 0) {
     *dst++ = *src++;
@@ -79,25 +88,31 @@ static void strmove(register char *dst, register const char *src) {
 }
 
 static void vroot_clean_path(char *path) {
-  char *p;
+  char *p = NULL;
 
-  if (path == NULL || *path == 0)
+  if (path == NULL ||
+      *path == 0) {
       return;
+  }
 
-  while ((p = strstr(path, "//")) != NULL)
+  while ((p = strstr(path, "//")) != NULL) {
     strmove(p, p + 1);
+  }
 
-  while ((p = strstr(path, "/./")) != NULL)
+  while ((p = strstr(path, "/./")) != NULL) {
     strmove(p, p + 2);
+  }
 
-  while (strncmp(path, "../", 3) == 0)
+  while (strncmp(path, "../", 3) == 0) {
     path += 3;
+  }
 
   p = strstr(path, "/../");
   if (p != NULL) {
     if (p == path) {
-      while (strncmp(path, "/../", 4) == 0)
+      while (strncmp(path, "/../", 4) == 0) {
         strmove(path, path + 3);
+      }
 
       p = strstr(path, "/../");
     }
@@ -105,14 +120,19 @@ static void vroot_clean_path(char *path) {
     while (p != NULL) {
       char *next_elem = p + 4;
 
-      if (p != path && *p == '/') 
+      if (p != path &&
+          *p == '/') {
         p--;
+      }
 
-      while (p != path && *p != '/')
+      while (p != path &&
+             *p != '/') {
         p--;
+      }
 
-      if (*p == '/')
+      if (*p == '/') {
         p++;
+      }
 
       strmove(p, next_elem);
       p = strstr(path, "/../");
@@ -124,36 +144,45 @@ static void vroot_clean_path(char *path) {
   if (*p == '.') {
     p++;
 
-    if (*p == '\0')
+    if (*p == '\0') {
       return;
+    }
 
     if (*p == '/') {
-      while (*p == '/') 
+      while (*p == '/') {
         p++;
+      }
 
       strmove(path, p);
     }
   }
 
-  if (*p == '\0')
+  if (*p == '\0') {
     return;
+  }
 
   p = path + strlen(path) - 1;
-  if (*p != '.' || p == path)
+  if (*p != '.' ||
+      p == path) {
     return;
+  }
 
   p--;
-  if (*p == '/' || p == path) {
+  if (*p == '/' ||
+      p == path) {
     p[1] = '\0';
     return;
   }
 
-  if (*p != '.' || p == path)
+  if (*p != '.' ||
+      p == path) {
     return;
+  }
 
   p--;
-  if (*p != '/')
+  if (*p != '/') {
     return;
+  }
 
   *p = '\0';
   p = strrchr(path, '/');
@@ -166,6 +195,38 @@ static void vroot_clean_path(char *path) {
   p[1] = '\0';
 }
 
+static char *vroot_realpath(pool *p, const char *path, int flags) {
+  char *real_path = NULL;
+  size_t real_pathlen;
+
+  if (flags & VROOT_REALPATH_FL_ABS_PATH) {
+    /* If not an absolute path, prepend the current location. */
+    if (*path != '/') {
+      real_path = pdircat(p, pr_fs_getvwd(), path, NULL);
+
+    } else {
+      real_path = pstrdup(p, path);
+    }
+
+  } else {
+    real_path = pstrdup(p, path);
+  }
+
+  vroot_clean_path(real_path);
+
+  /* If the given path ends in a slash, remove it.  The handling of
+   * VRootAliases is sensitive to such things.
+   */
+  real_pathlen = strlen(real_path);
+  if (real_pathlen > 1 &&
+      real_path[real_pathlen-1] == '/') {
+    real_path[real_pathlen-1] = '\0';
+    real_pathlen--;
+  }
+
+  return real_path;
+}
+
 static int vroot_lookup_path(pool *p, char *path, size_t pathlen,
     const char *dir, int flags, char **alias_path) {
   char buf[PR_TUNABLE_PATH_MAX + 1], *bufp = NULL;
@@ -219,13 +280,12 @@ loop:
 
   } else if (*bufp == '/') {
     snprintf(path, pathlen, "%s/", vroot_base);
-
     bufp += 1;
     goto loop;
 
   } else if (*bufp != '\0') {
     size_t buflen, tmplen;
-    char *ptr;
+    char *ptr = NULL;
 
     ptr = strstr(bufp, "..");
     if (ptr != NULL) {
@@ -280,9 +340,15 @@ loop:
     if (vroot_aliastab != NULL) {
       char *start_ptr = NULL, *end_ptr = NULL, *src_path = NULL;
 
+      /* buf is used here for storing the "suffix", to be appended later when
+       * aliases are found.
+       */
+      bufp = buf;
+
       start_ptr = path;
+
       while (start_ptr != NULL) {
-        char *ptr;
+        char *ptr = NULL;
 
         pr_signals_handle();
 
@@ -312,8 +378,8 @@ loop:
           sstrncpy(path, src_path, pathlen);
 
           if (end_ptr != NULL) {
-            sstrcat(path, "/", pathlen);
-            sstrcat(path, end_ptr + 1, pathlen);
+            /* Now tack on our suffix from the scratchpad. */
+            sstrcat(path, bufp, pathlen);
           }
 
           break;
@@ -334,6 +400,8 @@ loop:
           break;
         }
 
+        /* Store the suffix in the buf scratchpad. */
+        sstrncpy(buf, ptr, sizeof(buf));
         end_ptr = ptr;
         *end_ptr = '\0';
       }
@@ -354,7 +422,7 @@ static int vroot_is_alias(const char *path) {
 
 static int handle_vroot_alias(void) {
   config_rec *c;
-  pool *tmp_pool;
+  pool *tmp_pool = NULL;
 
   /* Handle any VRootAlias settings. */
 
@@ -373,10 +441,20 @@ static int handle_vroot_alias(void) {
      */
 
     memset(src_path, '\0', sizeof(src_path));
-    sstrncpy(src_path, c->argv[0], sizeof(src_path)-1);
+    ptr = c->argv[0];
+
+    /* Check for any expandable variables. */
+    ptr = path_subst_uservar(tmp_pool, &ptr);
+
+    sstrncpy(src_path, ptr, sizeof(src_path)-1);
     vroot_clean_path(src_path);
 
-    ptr = dir_best_path(tmp_pool, c->argv[1]);
+    ptr = c->argv[1];
+
+    /* Check for any expandable variables. */
+    ptr = path_subst_uservar(tmp_pool, &ptr);
+
+    ptr = dir_best_path(tmp_pool, ptr);
     vroot_lookup_path(NULL, dst_path, sizeof(dst_path)-1, ptr,
       VROOT_LOOKUP_FL_NO_ALIASES, NULL);
 
@@ -431,10 +509,9 @@ static int handle_vroot_alias(void) {
 /* FS callbacks
  */
 
-static int vroot_stat(pr_fs_t *fs, const char *orig_path, struct stat *st) {
+static int vroot_stat(pr_fs_t *fs, const char *stat_path, struct stat *st) {
   int res;
-  char vpath[PR_TUNABLE_PATH_MAX + 1], *path;
-  size_t path_len = 0;
+  char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL;
   pool *tmp_pool = NULL;
 
   if (session.curr_phase == LOG_CMD ||
@@ -444,23 +521,11 @@ static int vroot_stat(pr_fs_t *fs, const char *orig_path, struct stat *st) {
     /* NOTE: once stackable FS modules are supported, have this fall through
      * to the next module in the stack.
      */
-    return stat(orig_path, st);
+    return stat(stat_path, st);
   }
 
   tmp_pool = make_sub_pool(session.pool);
-
-  path = pstrdup(tmp_pool, orig_path);
-  vroot_clean_path(path);
-
-  /* If the given path ends in a slash, remove it.  The handling of
-   * VRootAliases is sensitive to such things.
-   */
-  path_len = strlen(path);
-  if (path_len > 1 &&
-      path[path_len-1] == '/') {
-    path[path_len-1] = '\0';
-    path_len--;
-  }
+  path = vroot_realpath(tmp_pool, stat_path, 0);
 
   if (vroot_lookup_path(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
     destroy_pool(tmp_pool);
@@ -474,8 +539,8 @@ static int vroot_stat(pr_fs_t *fs, const char *orig_path, struct stat *st) {
 
 static int vroot_lstat(pr_fs_t *fs, const char *orig_path, struct stat *st) {
   int res;
-  char vpath[PR_TUNABLE_PATH_MAX + 1], *path;
-  size_t path_len = 0;
+  char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL;
+  size_t pathlen = 0;
   pool *tmp_pool = NULL;
 
   if (session.curr_phase == LOG_CMD ||
@@ -496,11 +561,11 @@ static int vroot_lstat(pr_fs_t *fs, const char *orig_path, struct stat *st) {
   /* If the given path ends in a slash, remove it.  The handling of
    * VRootAliases is sensitive to such things.
    */
-  path_len = strlen(path);
-  if (path_len > 1 &&
-      path[path_len-1] == '/') {
-    path[path_len-1] = '\0';
-    path_len--;
+  pathlen = strlen(path);
+  if (pathlen > 1 &&
+      path[pathlen-1] == '/') {
+    path[pathlen-1] = '\0';
+    pathlen--;
   }
 
   if (vroot_lookup_path(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
@@ -557,10 +622,7 @@ static int vroot_unlink(pr_fs_t *fs, const char *path) {
   int res;
   char vpath[PR_TUNABLE_PATH_MAX + 1];
 
-  if (session.curr_phase == LOG_CMD ||
-      session.curr_phase == LOG_CMD_ERR ||
-      (session.sf_flags & SF_ABORT) ||
-      *vroot_base == '\0') {
+  if (vroot_base[0] == '\0') {
     /* NOTE: once stackable FS modules are supported, have this fall through
      * to the next module in the stack.
      */
@@ -689,10 +751,11 @@ static int vroot_symlink(pr_fs_t *fs, const char *path1, const char *path2) {
   return res;
 }
 
-static int vroot_readlink(pr_fs_t *fs, const char *path, char *buf,
+static int vroot_readlink(pr_fs_t *fs, const char *readlink_path, char *buf,
     size_t max) {
   int res;
-  char vpath[PR_TUNABLE_PATH_MAX + 1];
+  char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL, *alias_path = NULL;
+  pool *tmp_pool = NULL;
 
   if (session.curr_phase == LOG_CMD ||
       session.curr_phase == LOG_CMD_ERR ||
@@ -701,13 +764,33 @@ static int vroot_readlink(pr_fs_t *fs, const char *path, char *buf,
     /* NOTE: once stackable FS modules are supported, have this fall through
      * to the next module in the stack.
      */
-    return readlink(path, buf, max);
+    return readlink(readlink_path, buf, max);
   }
 
-  if (vroot_lookup_path(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0)
+  /* In order to find any VRootAlias paths, we need to use the full path.
+   * However, if we do NOT find any VRootAlias, then we do NOT want to use
+   * the full path.
+   */
+
+  tmp_pool = make_sub_pool(session.pool);
+  path = vroot_realpath(tmp_pool, readlink_path, VROOT_REALPATH_FL_ABS_PATH);
+
+  if (vroot_lookup_path(tmp_pool, vpath, sizeof(vpath)-1, path, 0,
+      &alias_path) < 0) {
+    destroy_pool(tmp_pool);
     return -1;
+  }
+
+  if (alias_path == NULL) {
+    if (vroot_lookup_path(NULL, vpath, sizeof(vpath)-1, readlink_path, 0,
+        NULL) < 0) {
+      destroy_pool(tmp_pool);
+      return -1;
+    }
+  }
 
   res = readlink(vpath, buf, max);
+  destroy_pool(tmp_pool);
   return res;
 }
 
@@ -777,6 +860,30 @@ static int vroot_chown(pr_fs_t *fs, const char *path, uid_t uid, gid_t gid) {
   return res;
 }
 
+#if PROFTPD_VERSION_NUMBER >= 0x0001030407
+static int vroot_lchown(pr_fs_t *fs, const char *path, uid_t uid, gid_t gid) {
+  int res;
+  char vpath[PR_TUNABLE_PATH_MAX + 1];
+
+  if (session.curr_phase == LOG_CMD ||
+      session.curr_phase == LOG_CMD_ERR ||
+      (session.sf_flags & SF_ABORT) ||
+      *vroot_base == '\0') {
+    /* NOTE: once stackable FS modules are supported, have this fall through
+     * to the next module in the stack.
+     */
+    res = lchown(path, uid, gid);
+    return res;
+  }
+
+  if (vroot_lookup_path(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0)
+    return -1;
+
+  res = lchown(vpath, uid, gid);
+  return res;
+}
+#endif /* ProFTPD 1.3.4c or later */
+
 static int vroot_chroot(pr_fs_t *fs, const char *path) {
   char *chroot_path = "/", *tmp = NULL;
   config_rec *c;
@@ -931,14 +1038,57 @@ static int vroot_chdir(pr_fs_t *fs, const char *path) {
   return 0;
 }
 
-static struct dirent vroot_dent;
+static int vroot_utimes(pr_fs_t *fs, const char *utimes_path,
+    struct timeval *tvs) {
+  int res;
+  char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL;
+  pool *tmp_pool = NULL;
+
+  if (session.curr_phase == LOG_CMD ||
+      session.curr_phase == LOG_CMD_ERR ||
+      (session.sf_flags & SF_ABORT) ||
+      *vroot_base == '\0') {
+    /* NOTE: once stackable FS modules are supported, have this fall through
+     * to the next module in the stack.
+     */
+    res = utimes(utimes_path, tvs);
+    return res;
+  }
+
+  tmp_pool = make_sub_pool(session.pool);
+  path = vroot_realpath(tmp_pool, utimes_path, VROOT_REALPATH_FL_ABS_PATH);
+  
+  if (vroot_lookup_path(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
+    destroy_pool(tmp_pool);
+    return -1;
+  }
+
+  res = utimes(vpath, tvs);
+  destroy_pool(tmp_pool);
+  return res;
+}
+
+static struct dirent *vroot_dent = NULL;
+static size_t vroot_dentsz = 0;
+
+/* On most systems, dirent.d_name is an array into which we can copy the
+ * name we want.
+ *
+ * However, on other systems (e.g. Solaris 2), dirent.d_name is an array size
+ * of 1.  This approach makes use of the fact that the d_name member is the
+ * last member of the struct, meaning that the actual size is variable.
+ *
+ * We need to Do The Right Thing(tm) in either case.
+ */
+static size_t vroot_dent_namesz = 0;
+
 static array_header *vroot_dir_aliases = NULL;
 static int vroot_dir_idx = -1;
 
 static int vroot_alias_dirscan(const void *key_data, size_t key_datasz,
     void *value_data, size_t value_datasz, void *user_data) {
-  const char *alias_path, *dir_path, *real_path;
-  char *ptr;
+  const char *alias_path = NULL, *dir_path = NULL, *real_path = NULL;
+  char *ptr = NULL;
 
   alias_path = key_data;
   real_path = value_data;
@@ -994,10 +1144,10 @@ static unsigned int vroot_dirtab_hash_cb(const void *key, size_t keysz) {
 
 static void *vroot_opendir(pr_fs_t *fs, const char *orig_path) {
   int res;
-  char vpath[PR_TUNABLE_PATH_MAX + 1], *path;
-  void *dirh;
+  char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL;
+  void *dirh = NULL;
   struct stat st;
-  size_t path_len = 0;
+  size_t pathlen = 0;
   pool *tmp_pool = NULL;
 
   if (session.curr_phase == LOG_CMD ||
@@ -1022,11 +1172,11 @@ static void *vroot_opendir(pr_fs_t *fs, const char *orig_path) {
   /* If the given path ends in a slash, remove it.  The handling of
    * VRootAliases is sensitive to such things.
    */
-  path_len = strlen(path);
-  if (path_len > 1 &&
-      path[path_len-1] == '/') {
-    path[path_len-1] = '\0';
-    path_len--;
+  pathlen = strlen(path);
+  if (pathlen > 1 &&
+      path[pathlen-1] == '/') {
+    path[pathlen-1] = '\0';
+    pathlen--;
   }
 
   if (vroot_lookup_path(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
@@ -1127,7 +1277,7 @@ static void *vroot_opendir(pr_fs_t *fs, const char *orig_path) {
 }
 
 static struct dirent *vroot_readdir(pr_fs_t *fs, void *dirh) {
-  struct dirent *dent;
+  struct dirent *dent = NULL;
 
 next_dent:
   dent = readdir((DIR *) dirh);
@@ -1165,11 +1315,18 @@ next_dent:
         return NULL;
       }
 
-      memset(&vroot_dent, 0, sizeof(vroot_dent));
-      sstrncpy(vroot_dent.d_name, elts[vroot_dir_idx++],
-        sizeof(vroot_dent.d_name));
+      memset(vroot_dent, 0, vroot_dentsz);
 
-      return &vroot_dent;
+      if (vroot_dent_namesz == 0) {
+        sstrncpy(vroot_dent->d_name, elts[vroot_dir_idx++],
+          sizeof(vroot_dent->d_name));
+
+      } else {
+        sstrncpy(vroot_dent->d_name, elts[vroot_dir_idx++],
+          vroot_dent_namesz);
+      }
+
+      return vroot_dent;
     }
   }
 
@@ -1390,6 +1547,90 @@ MODRET set_vrootserverroot(cmd_rec *cmd) {
 /* Command handlers
  */
 
+MODRET vroot_log_retr(cmd_rec *cmd) {
+  const char *key;
+  char *path;
+
+  if (vroot_engine == FALSE ||
+      session.chroot_path == NULL) {
+    return PR_DECLINED(cmd);
+  }
+
+  key = "mod_xfer.retr-path";
+
+  path = pr_table_get(cmd->notes, key, NULL);
+  if (path != NULL) {
+    char *real_path;
+
+    if (*path == '/') {
+      real_path = pdircat(cmd->pool, vroot_base, path, NULL);
+      vroot_clean_path(real_path);
+
+    } else {
+      real_path = vroot_realpath(cmd->pool, path, VROOT_REALPATH_FL_ABS_PATH);
+    }
+
+    pr_table_set(cmd->notes, key, real_path, 0);
+  }
+
+  return PR_DECLINED(cmd);
+}
+
+MODRET vroot_log_stor(cmd_rec *cmd) {
+  const char *key;
+  char *path;
+
+  if (vroot_engine == FALSE ||
+      session.chroot_path == NULL) {
+    return PR_DECLINED(cmd);
+  }
+
+  key = "mod_xfer.store-path";
+
+  path = pr_table_get(cmd->notes, key, NULL);
+  if (path != NULL) {
+    char *real_path;
+
+    if (*path == '/') {
+      real_path = pdircat(cmd->pool, vroot_base, path, NULL);
+      vroot_clean_path(real_path);
+
+    } else {
+      real_path = vroot_realpath(cmd->pool, path, VROOT_REALPATH_FL_ABS_PATH);
+    }
+
+    pr_table_set(cmd->notes, key, real_path, 0);
+  }
+
+  return PR_DECLINED(cmd);
+}
+
+MODRET vroot_pre_mkd(cmd_rec *cmd) {
+  if (vroot_engine == FALSE ||
+      session.chroot_path == NULL) {
+    return PR_DECLINED(cmd);
+  }
+
+#if PROFTPD_VERSION_NUMBER >= 0x0001030407
+  vroot_use_mkdtemp = pr_fsio_set_use_mkdtemp(FALSE);
+#endif /* ProFTPD 1.3.4c or later */
+
+  return PR_DECLINED(cmd);
+}
+
+MODRET vroot_post_mkd(cmd_rec *cmd) {
+  if (vroot_engine == FALSE ||
+      session.chroot_path == NULL) {
+    return PR_DECLINED(cmd);
+  }
+
+#if PROFTPD_VERSION_NUMBER >= 0x0001030407
+  pr_fsio_set_use_mkdtemp(vroot_use_mkdtemp);
+#endif /* ProFTPD 1.3.4c or later */
+
+  return PR_DECLINED(cmd);
+}
+
 MODRET vroot_pre_pass(cmd_rec *cmd) {
   pr_fs_t *fs = NULL;
   unsigned char *use_vroot = NULL;
@@ -1433,8 +1674,12 @@ MODRET vroot_pre_pass(cmd_rec *cmd) {
   fs->truncate = vroot_truncate;
   fs->chmod = vroot_chmod;
   fs->chown = vroot_chown;
+#if PROFTPD_VERSION_NUMBER >= 0x0001030407
+  fs->lchown = vroot_lchown;
+#endif /* ProFTPD 1.3.4c or later */
   fs->chdir = vroot_chdir;
   fs->chroot = vroot_chroot;
+  fs->utimes = vroot_utimes;
   fs->opendir = vroot_opendir;
   fs->readdir = vroot_readdir;
   fs->closedir = vroot_closedir;
@@ -1509,6 +1754,7 @@ MODRET vroot_post_pass_err(cmd_rec *cmd) {
 
 static int vroot_sess_init(void) {
   config_rec *c;
+  struct dirent dent;
 
   c = find_config(main_server->conf, CONF_PARAM, "VRootLog", FALSE);
   if (c) {
@@ -1545,6 +1791,18 @@ static int vroot_sess_init(void) {
     }
   }
 
+  /* Allocate the memory for the static struct dirent that we use, including
+   * determining the necessary sizes.
+   */
+  vroot_dentsz = sizeof(dent);
+  if (sizeof(dent.d_name) == 1) {
+    /* Allocate extra space for the dent path name. */
+    vroot_dent_namesz = PR_TUNABLE_PATH_MAX;
+  }
+
+  vroot_dentsz += vroot_dent_namesz;
+  vroot_dent = palloc(session.pool, vroot_dentsz);
+
   return 0;
 }
 
@@ -1564,6 +1822,34 @@ static cmdtable vroot_cmdtab[] = {
   { PRE_CMD,		C_PASS,	G_NONE,	vroot_pre_pass, FALSE, FALSE },
   { POST_CMD,		C_PASS,	G_NONE,	vroot_post_pass, FALSE, FALSE },
   { POST_CMD_ERR,	C_PASS,	G_NONE,	vroot_post_pass_err, FALSE, FALSE },
+
+  { PRE_CMD,		C_MKD,	G_NONE,	vroot_pre_mkd, FALSE, FALSE },
+  { POST_CMD,		C_MKD,	G_NONE,	vroot_post_mkd, FALSE, FALSE },
+  { POST_CMD_ERR,	C_MKD,	G_NONE,	vroot_post_mkd, FALSE, FALSE },
+  { PRE_CMD,		C_XMKD,	G_NONE,	vroot_pre_mkd, FALSE, FALSE },
+  { POST_CMD,		C_XMKD,	G_NONE,	vroot_post_mkd, FALSE, FALSE },
+  { POST_CMD_ERR,	C_XMKD,	G_NONE,	vroot_post_mkd, FALSE, FALSE },
+
+  /* These command handlers are for manipulating cmd->notes, to get
+   * paths properly logged.
+   *
+   * Ideally these would be LOG_CMD/LOG_CMD_ERR phase handlers.  HOWEVER,
+   * we need to transform things before the cmd is dispatched to mod_log,
+   * and mod_log uses a C_ANY handler for logging.  And when dispatching,
+   * C_ANY handlers are run before named handlers.  This means that using
+   * LOG_CMD/LOG_CMD_ERR handlers would be run AFTER mod_log's handler,
+   * even though we appear BEFORE mod_log in the module load order.
+   *
+   * Thus to do the transformation, we actually use POST_CMD/POST_CMD_ERR
+   * phase handlers here.
+   */
+  { POST_CMD,		C_APPE,	G_NONE, vroot_log_stor, FALSE, FALSE },
+  { POST_CMD_ERR,	C_APPE,	G_NONE, vroot_log_stor, FALSE, FALSE },
+  { POST_CMD,		C_RETR,	G_NONE, vroot_log_retr, FALSE, FALSE },
+  { POST_CMD_ERR,	C_RETR,	G_NONE, vroot_log_retr, FALSE, FALSE },
+  { POST_CMD,		C_STOR,	G_NONE, vroot_log_stor, FALSE, FALSE },
+  { POST_CMD_ERR,	C_STOR,	G_NONE, vroot_log_stor, FALSE, FALSE },
+
   { 0, NULL }
 };
 
diff --git a/mod_vroot.html b/mod_vroot.html
index bb77699..f980f89 100644
--- a/mod_vroot.html
+++ b/mod_vroot.html
@@ -26,7 +26,7 @@ provides this capability by using ProFTPD's FS API, available as of 1.2.8rc1.
 <p>
 The most current version of <code>mod_vroot</code> can be found at:
 <pre>
-  <a href="http://www.castaglia.org/proftpd/">http://www.castaglia.org/proftpd/</a>
+  <a href="https://github.com/Castaglia/proftpd-mod_vroot.git">https://github.com/Castaglia/proftpd-mod_vroot.git</a>
 </pre>
 
 <h2>Author</h2>
@@ -79,6 +79,12 @@ This will automatically create an "upload" directory to appear in the
 chroot area (in this case, the user's home directory).
 
 <p>
+The <code>VRootAlias</code> directive is only needed for files/directories
+that are going to be accessed by remote clients.  It is <b>not</b> needed
+for configuration files (<i>e.g.</i> PAM configuration files like <code>pam_env.conf</code>) needed by libraries.  Using the <code>VRootAlias</code> for
+such library configuration files is pointless and wasteful.
+
+<p>
 Note that this directive will <b>not</b> work if the
 <code>VRootServerRoot</code> is used.
 
@@ -201,7 +207,7 @@ Last Updated: <i>$Date: 2009/10/19 16:30:18 $</i><br>
 <hr>
 
 <font size=2><b><i>
-© Copyright 2000-2009 TJ Saunders<br>
+© Copyright 2000-2013 TJ Saunders<br>
  All Rights Reserved<br>
 </i></b></font>
 
diff --git a/t/lib/ProFTPD/Tests/Modules/mod_vroot.pm b/t/lib/ProFTPD/Tests/Modules/mod_vroot.pm
index da96c66..010070b 100644
--- a/t/lib/ProFTPD/Tests/Modules/mod_vroot.pm
+++ b/t/lib/ProFTPD/Tests/Modules/mod_vroot.pm
@@ -64,11 +64,21 @@ my $TESTS = {
     test_class => [qw(forking)],
   },
 
+  vroot_dir_mkd => {
+    order => ++$order,
+    test_class => [qw(forking)],
+  },
+
   vroot_server_root => {
     order => ++$order,
     test_class => [qw(forking rootprivs)],
   },
 
+  vroot_server_root_mkd => {
+    order => ++$order,
+    test_class => [qw(bug forking rootprivs)],
+  },
+
   vroot_alias_file_list => {
     order => ++$order,
     test_class => [qw(forking)],
@@ -149,6 +159,11 @@ my $TESTS = {
     test_class => [qw(forking)],
   },
 
+  vroot_alias_dir_cwd_stor => {
+    order => ++$order,
+    test_class => [qw(forking)],
+  },
+
   vroot_alias_dir_cwd_cdup => {
     order => ++$order,
     test_class => [qw(forking)],
@@ -229,89 +244,88 @@ my $TESTS = {
     test_class => [qw(forking mod_ifsession)],
   },
 
-  vroot_alias_ifclass => {
+  vroot_alias_ifgroup_list_stor => {
     order => ++$order,
     test_class => [qw(forking mod_ifsession)],
   },
 
-  vroot_alias_file_sftp_read => {
-    order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
-  },
-
-  vroot_alias_file_sftp_write_no_overwrite => {
-    order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
-  },
-
-  vroot_alias_file_sftp_write => {
-    order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
-  },
-
-  vroot_alias_file_sftp_stat => {
-    order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
-  },
-
-  vroot_alias_file_sftp_lstat => {
+  vroot_alias_ifclass => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
+    test_class => [qw(forking mod_ifsession)],
   },
 
-  vroot_alias_file_sftp_realpath => {
+  vroot_showsymlinks_on => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
+    test_class => [qw(bug forking)],
   },
 
-  vroot_alias_file_sftp_remove => {
+  vroot_hiddenstores_on_double_dot => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
+    test_class => [qw(bug forking)],
   },
 
-  vroot_alias_dir_sftp_readdir => {
+  vroot_mfmt => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
+    test_class => [qw(bug forking)],
   },
 
-  vroot_alias_dir_sftp_rmdir => {
+  vroot_log_extlog_retr => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
+    test_class => [qw(forking)],
   },
 
-  vroot_alias_symlink_sftp_stat => {
+  vroot_log_extlog_stor => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
+    test_class => [qw(forking)],
   },
 
-  vroot_alias_symlink_sftp_lstat => {
+  # XXX Currently does not work, since TransferLog logging can't be filtered
+#  vroot_log_xferlog_retr => {
+#    order => ++$order,
+#    test_class => [qw(inprogress forking)],
+#  },
+
+  # XXX Currently does not work, since TransferLog logging can't be filtered
+#  vroot_log_xferlog_stor => {
+#    order => ++$order,
+#    test_class => [qw(inprogress forking)],
+#  },
+
+  # XXX Currently does not work due to <Directory> matching logic, and to
+  # mod_vroot's session.chroot_path machinations.
+#  vroot_config_limit_write => {
+#    order => ++$order,
+#    test_class => [qw(bug forking)],
+#  },
+
+  vroot_config_deleteabortedstores_conn_aborted => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
+    test_class => [qw(bug forking)],
   },
 
-  vroot_alias_symlink_sftp_realpath => {
+  vroot_config_deleteabortedstores_cmd_aborted => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp sftp)],
+    test_class => [qw(bug forking)],
   },
 
-  vroot_alias_file_scp_download => {
+  vroot_alias_var_u_file => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp scp)],
+    test_class => [qw(forking)],
   },
 
-  vroot_alias_file_scp_upload => {
+  vroot_alias_var_u_dir => {
     order => ++$order,
-    test_class => [qw(forking mod_sftp scp)],
+    test_class => [qw(forking)],
   },
 
-  vroot_showsymlinks_on => {
+  vroot_alias_var_u_dir_with_stor_mff => {
     order => ++$order,
-    test_class => [qw(bug forking)],
+    test_class => [qw(forking)],
   },
 
-  vroot_hiddenstores_on_double_dot => {
+  vroot_alias_var_u_symlink_dir => {
     order => ++$order,
-    test_class => [qw(bug forking)],
+    test_class => [qw(forking)],
   },
 
 };
@@ -330,21 +344,6 @@ sub list_tests {
 #    alias but not traversal?
 }
 
-sub set_up {
-  my $self = shift;
-  $self->SUPER::set_up(@_);
-
-  # Make sure that mod_sftp does not complain about permissions on the hostkey
-  # files.
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
-
-  unless (chmod(0400, $rsa_host_key, $dsa_host_key)) {
-    die("Can't set perms on $rsa_host_key, $dsa_host_key: $!");
-  }
-}
-
 sub vroot_engine {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
@@ -353,13 +352,14 @@ sub vroot_engine {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -378,7 +378,7 @@ sub vroot_engine {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $config = {
     PidFile => $pid_file,
@@ -419,7 +419,6 @@ sub vroot_engine {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -443,7 +442,7 @@ sub vroot_engine {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -510,7 +509,7 @@ sub vroot_engine {
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -575,7 +574,7 @@ sub vroot_engine {
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -640,6 +639,9 @@ sub vroot_engine {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -654,7 +656,7 @@ sub vroot_anon {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
@@ -756,7 +758,7 @@ EOC
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -823,7 +825,7 @@ EOC
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -888,7 +890,7 @@ EOC
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -953,6 +955,9 @@ EOC
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -967,13 +972,14 @@ sub vroot_symlink {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
@@ -994,7 +1000,7 @@ sub vroot_symlink {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   # Create a symlink to a file that is outside of the vroot
   my $test_file = File::Spec->rel2abs("$tmpdir/bar.txt");
@@ -1063,7 +1069,6 @@ sub vroot_symlink {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my $conn = $client->list_raw();
@@ -1073,7 +1078,7 @@ sub vroot_symlink {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -1133,6 +1138,9 @@ sub vroot_symlink {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -1147,13 +1155,14 @@ sub vroot_symlink_eloop {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
@@ -1174,7 +1183,7 @@ sub vroot_symlink_eloop {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   # Create a symlink to a file that is outside of the vroot
   my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
@@ -1243,7 +1252,6 @@ sub vroot_symlink_eloop {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my $conn = $client->list_raw();
@@ -1253,7 +1261,7 @@ sub vroot_symlink_eloop {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -1316,6 +1324,9 @@ sub vroot_symlink_eloop {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -1330,13 +1341,14 @@ sub vroot_opt_allow_symlinks_file {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
@@ -1357,7 +1369,7 @@ sub vroot_opt_allow_symlinks_file {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   # Create a symlink to a file that is outside of the vroot
   my $test_file = File::Spec->rel2abs("$tmpdir/bar.txt");
@@ -1428,7 +1440,6 @@ sub vroot_opt_allow_symlinks_file {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my $conn = $client->list_raw();
@@ -1438,7 +1449,7 @@ sub vroot_opt_allow_symlinks_file {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -1480,19 +1491,12 @@ sub vroot_opt_allow_symlinks_file {
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       my $resp_code = $client->response_code();
       my $resp_msg = $client->response_msg();
-
-      $expected = 226;
-      $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
-
-      $expected = "Transfer complete";
-      $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
       $client->quit();
     };
@@ -1520,6 +1524,9 @@ sub vroot_opt_allow_symlinks_file {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -1534,13 +1541,14 @@ sub vroot_opt_allow_symlinks_dir_retr {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
@@ -1564,7 +1572,7 @@ sub vroot_opt_allow_symlinks_dir_retr {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   # Create a symlink to a directory that is outside of the vroot
   my $test_file = File::Spec->rel2abs("$public_dir/test.txt");
@@ -1635,7 +1643,6 @@ sub vroot_opt_allow_symlinks_dir_retr {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my $conn = $client->list_raw();
@@ -1645,7 +1652,7 @@ sub vroot_opt_allow_symlinks_dir_retr {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -1687,19 +1694,12 @@ sub vroot_opt_allow_symlinks_dir_retr {
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       my $resp_code = $client->response_code();
       my $resp_msg = $client->response_msg();
-
-      $expected = 226;
-      $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
-
-      $expected = "Transfer complete";
-      $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
       $client->quit();
     };
@@ -1727,6 +1727,9 @@ sub vroot_opt_allow_symlinks_dir_retr {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -1741,13 +1744,14 @@ sub vroot_opt_allow_symlinks_dir_stor_no_overwrite {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
@@ -1771,7 +1775,7 @@ sub vroot_opt_allow_symlinks_dir_stor_no_overwrite {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   # Create a symlink to a directory that is outside of the vroot
   my $test_file = File::Spec->rel2abs("$public_dir/test.txt");
@@ -1842,7 +1846,6 @@ sub vroot_opt_allow_symlinks_dir_stor_no_overwrite {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my $conn = $client->list_raw();
@@ -1852,7 +1855,7 @@ sub vroot_opt_allow_symlinks_dir_stor_no_overwrite {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -1930,6 +1933,9 @@ sub vroot_opt_allow_symlinks_dir_stor_no_overwrite {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -1944,13 +1950,14 @@ sub vroot_opt_allow_symlinks_dir_stor {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
@@ -1974,7 +1981,7 @@ sub vroot_opt_allow_symlinks_dir_stor {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   # Create a symlink to a directory that is outside of the vroot
   my $test_file = File::Spec->rel2abs("$public_dir/test.txt");
@@ -2046,7 +2053,6 @@ sub vroot_opt_allow_symlinks_dir_stor {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my $conn = $client->list_raw();
@@ -2056,7 +2062,7 @@ sub vroot_opt_allow_symlinks_dir_stor {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -2099,19 +2105,12 @@ sub vroot_opt_allow_symlinks_dir_stor {
       }
 
       my $buf = "Farewell cruel world!";
-      $conn->write($buf, length($buf), 30);
+      $conn->write($buf, length($buf), 25);
       eval { $conn->close() };
 
       my $resp_code = $client->response_code();
       my $resp_msg = $client->response_msg();
-
-      $expected = 226;
-      $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
-
-      $expected = "Transfer complete";
-      $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
       $client->quit();
     };
@@ -2139,6 +2138,9 @@ sub vroot_opt_allow_symlinks_dir_stor {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -2153,13 +2155,14 @@ sub vroot_opt_allow_symlinks_dir_cwd {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
@@ -2183,7 +2186,7 @@ sub vroot_opt_allow_symlinks_dir_cwd {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   # Create a symlink to a directory that is outside of the vroot
   my $test_file = File::Spec->rel2abs("$public_dir/test.txt");
@@ -2254,7 +2257,6 @@ sub vroot_opt_allow_symlinks_dir_cwd {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my $conn = $client->list_raw();
@@ -2264,7 +2266,7 @@ sub vroot_opt_allow_symlinks_dir_cwd {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -2317,7 +2319,7 @@ sub vroot_opt_allow_symlinks_dir_cwd {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -2409,6 +2411,195 @@ sub vroot_opt_allow_symlinks_dir_cwd {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_dir_mkd {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $group = 'ftpd';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, $group, $gid, $user);
+
+  my $sub_dir = File::Spec->rel2abs("$tmpdir/foo.d");
+  mkpath($sub_dir);
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10 vroot:20',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    Directory => {
+      # BUG: This should be $sub_dir.  But due to how mod_vroot currently
+      # works, the <Directory> path has to be modified to match the
+      # mod_vroot.  (I.e. for the purposes of this test, just '/foo.d').
+      # Sigh.
+
+      # '/foo.d' => {
+      $sub_dir => {
+        # Test the UserOwner directive in the <Directory> setting
+        UserOwner => 'root',
+      },
+    },
+
+    IfModules => {
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
+      $client->cwd('foo.d');
+      $client->mkd('bar.d');
+
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+
+      my $expected;
+
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = "\"/foo.d/bar.d\" - Directory successfully created";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      $client->quit();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  eval {
+    if (open(my $fh, "< $log_file")) {
+      # Look for the 'smkdir' fsio channel trace message; it will tell us
+      # whether the UserOwner directive from the <Directory> section was
+      # successfully found.
+
+      my $have_smkdir_line = 0;
+      my $line;
+      while ($line = <$fh>) {
+        chomp($line);
+
+        if ($line =~ /smkdir/) {
+          $have_smkdir_line = 1;
+          last;
+        }
+      }
+
+      close($fh);
+
+      $self->assert($have_smkdir_line,
+        test_msg("Did not find expected 'fsio' channel TraceLog line in $log_file"));
+
+      if ($line =~ /UID (\d+)/) {
+        my $smkdir_uid = $1;
+
+        if ($< == 0) {
+          $self->assert($smkdir_uid == 0,
+            test_msg("Expected UID 0, got $smkdir_uid"));
+        }
+
+      } else {
+        die("Unexpectedly formatted 'fsio' channel TraceLog line '$line'");
+      }
+
+    } else {
+      die("Can't read $log_file: $!");
+    }
+  };
+  if ($@) {
+    $ex = $@;
+  }
+ 
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -2423,13 +2614,14 @@ sub vroot_server_root {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
@@ -2452,7 +2644,7 @@ sub vroot_server_root {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $config = {
     PidFile => $pid_file,
@@ -2496,7 +2688,6 @@ sub vroot_server_root {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -2520,7 +2711,7 @@ sub vroot_server_root {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -2564,7 +2755,7 @@ sub vroot_server_root {
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -2607,7 +2798,7 @@ sub vroot_server_root {
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -2649,13 +2840,16 @@ sub vroot_server_root {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_list {
+sub vroot_server_root_mkd {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -2663,13 +2857,173 @@ sub vroot_alias_file_list {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
+  my $home_dir = File::Spec->rel2abs("$tmpdir/home");
+  my $uid = 500;
+  my $gid = 500;
+
+  mkpath($home_dir);
+
+  my $abs_tmpdir = File::Spec->rel2abs($tmpdir);
+
+  my $test_dir = File::Spec->rel2abs("$home_dir/test.d");
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, $group, $gid, $user);
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        VRootServerRoot => $abs_tmpdir,
+        DefaultRoot => '~',
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
+
+      my ($resp_code, $resp_msg) = $client->pwd();
+
+      my $expected;
+
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "\"/\" is the current directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->mkd('test.d');
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "\"/test.d\" - Directory successfully created";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->cwd('test.d');
+      $expected = 250;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = 'CWD command successful';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->pwd();
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "\"/test.d\" is the current directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      $client->quit();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_list {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -2688,7 +3042,7 @@ sub vroot_alias_file_list {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -2746,7 +3100,6 @@ sub vroot_alias_file_list {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -2770,7 +3123,7 @@ sub vroot_alias_file_list {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -2837,6 +3190,9 @@ sub vroot_alias_file_list {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -2851,13 +3207,14 @@ sub vroot_alias_file_list_multi {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -2876,7 +3233,7 @@ sub vroot_alias_file_list_multi {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -2937,7 +3294,6 @@ sub vroot_alias_file_list_multi {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -2961,7 +3317,7 @@ sub vroot_alias_file_list_multi {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -3029,6 +3385,9 @@ sub vroot_alias_file_list_multi {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -3043,13 +3402,14 @@ sub vroot_alias_file_retr {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -3068,9 +3428,12 @@ sub vroot_alias_file_retr {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
+  mkpath($test_dir);
+
+  my $src_file = File::Spec->rel2abs("$test_dir/foo.txt");
   if (open(my $fh, "> $src_file")) {
     print $fh "Hello, World!\n";
     unless (close($fh)) {
@@ -3088,7 +3451,7 @@ sub vroot_alias_file_retr {
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'fsio:10',
+    Trace => 'fsio:10 vroot:20',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
@@ -3136,25 +3499,16 @@ sub vroot_alias_file_retr {
       }
 
       my $buf;
-      my $count = $conn->read($buf, 8192, 30);
+      my $count = $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       my $resp_code = $client->response_code();
       my $resp_msg = $client->response_msg();
-
-      my $expected;
-
-      $expected = 226;
-      $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
-
-      $expected = 'Transfer complete';
-      $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
       $client->quit();
 
-      $expected = 14;
+      my $expected = 14;
       $self->assert($expected == $count,
         test_msg("Expected $expected, got $count"));
     };
@@ -3181,6 +3535,9 @@ sub vroot_alias_file_retr {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -3195,13 +3552,14 @@ sub vroot_alias_file_stor_no_overwrite {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -3220,7 +3578,7 @@ sub vroot_alias_file_stor_no_overwrite {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -3324,6 +3682,9 @@ sub vroot_alias_file_stor_no_overwrite {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -3338,13 +3699,14 @@ sub vroot_alias_file_stor {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -3363,7 +3725,7 @@ sub vroot_alias_file_stor {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -3433,21 +3795,12 @@ sub vroot_alias_file_stor {
       }
 
       my $buf = "Farewell, cruel world";
-      $conn->write($buf, length($buf), 30);
+      $conn->write($buf, length($buf), 25);
       eval { $conn->close() };
 
       my $resp_code = $client->response_code();
       my $resp_msg = $client->response_msg();
-
-      my $expected;
-
-      $expected = 226;
-      $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
-
-      $expected = 'Transfer complete';
-      $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
       $client->quit();
     };
@@ -3474,6 +3827,9 @@ sub vroot_alias_file_stor {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -3488,13 +3844,14 @@ sub vroot_alias_file_dele {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -3513,7 +3870,7 @@ sub vroot_alias_file_dele {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -3617,6 +3974,9 @@ sub vroot_alias_file_dele {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -3631,13 +3991,14 @@ sub vroot_alias_file_mlsd {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -3656,7 +4017,7 @@ sub vroot_alias_file_mlsd {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -3759,6 +4120,9 @@ sub vroot_alias_file_mlsd {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -3773,13 +4137,14 @@ sub vroot_alias_file_mlst {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -3798,7 +4163,7 @@ sub vroot_alias_file_mlst {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -3895,6 +4260,9 @@ sub vroot_alias_file_mlst {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -3909,13 +4277,14 @@ sub vroot_alias_dup_same_name {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -3934,7 +4303,7 @@ sub vroot_alias_dup_same_name {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -3992,7 +4361,6 @@ sub vroot_alias_dup_same_name {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -4016,7 +4384,7 @@ sub vroot_alias_dup_same_name {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -4082,6 +4450,9 @@ sub vroot_alias_dup_same_name {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -4096,13 +4467,14 @@ sub vroot_alias_dup_colliding_aliases {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -4121,7 +4493,7 @@ sub vroot_alias_dup_colliding_aliases {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -4180,7 +4552,6 @@ sub vroot_alias_dup_colliding_aliases {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -4204,7 +4575,7 @@ sub vroot_alias_dup_colliding_aliases {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -4271,6 +4642,9 @@ sub vroot_alias_dup_colliding_aliases {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -4285,13 +4659,14 @@ sub vroot_alias_delete_source {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -4310,7 +4685,7 @@ sub vroot_alias_delete_source {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -4368,7 +4743,6 @@ sub vroot_alias_delete_source {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -4405,7 +4779,7 @@ sub vroot_alias_delete_source {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -4470,6 +4844,9 @@ sub vroot_alias_delete_source {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -4484,13 +4861,14 @@ sub vroot_alias_no_source {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -4509,7 +4887,7 @@ sub vroot_alias_no_source {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   my $dst_file = '~/bar.txt';
@@ -4557,7 +4935,6 @@ sub vroot_alias_no_source {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -4581,7 +4958,7 @@ sub vroot_alias_no_source {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -4646,6 +5023,9 @@ sub vroot_alias_no_source {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -4660,13 +5040,14 @@ sub vroot_alias_dir_list_no_trailing_slash {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -4685,7 +5066,7 @@ sub vroot_alias_dir_list_no_trailing_slash {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -4735,7 +5116,6 @@ sub vroot_alias_dir_list_no_trailing_slash {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -4759,7 +5139,7 @@ sub vroot_alias_dir_list_no_trailing_slash {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -4826,6 +5206,9 @@ sub vroot_alias_dir_list_no_trailing_slash {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -4840,13 +5223,14 @@ sub vroot_alias_dir_list_with_trailing_slash {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -4865,7 +5249,7 @@ sub vroot_alias_dir_list_with_trailing_slash {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -4915,7 +5299,6 @@ sub vroot_alias_dir_list_with_trailing_slash {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -4939,7 +5322,7 @@ sub vroot_alias_dir_list_with_trailing_slash {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -5006,6 +5389,9 @@ sub vroot_alias_dir_list_with_trailing_slash {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -5020,13 +5406,14 @@ sub vroot_alias_dir_list_from_above {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -5045,7 +5432,7 @@ sub vroot_alias_dir_list_from_above {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -5119,7 +5506,6 @@ sub vroot_alias_dir_list_from_above {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -5142,7 +5528,7 @@ sub vroot_alias_dir_list_from_above {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -5204,6 +5590,9 @@ sub vroot_alias_dir_list_from_above {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -5218,13 +5607,14 @@ sub vroot_alias_dir_cwd_list {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -5243,7 +5633,7 @@ sub vroot_alias_dir_cwd_list {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -5317,7 +5707,6 @@ sub vroot_alias_dir_cwd_list {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -5360,7 +5749,7 @@ sub vroot_alias_dir_cwd_list {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -5422,13 +5811,16 @@ sub vroot_alias_dir_cwd_list {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_dir_cwd_cdup {
+sub vroot_alias_dir_cwd_stor {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -5436,13 +5828,14 @@ sub vroot_alias_dir_cwd_cdup {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -5461,9 +5854,9 @@ sub vroot_alias_dir_cwd_cdup {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
+  my $src_dir = File::Spec->rel2abs("$tmpdir/sub1.d/sub2.d/foo.d");
   mkpath($src_dir);
 
   my $dst_dir = '~/bar.d';
@@ -5473,7 +5866,7 @@ sub vroot_alias_dir_cwd_cdup {
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'fsio:10',
+    Trace => 'fsio:10 vroot:20',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
@@ -5511,7 +5904,6 @@ sub vroot_alias_dir_cwd_cdup {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -5547,8 +5939,175 @@ sub vroot_alias_dir_cwd_cdup {
       $self->assert($expected eq $resp_msg,
         test_msg("Expected '$expected', got '$resp_msg'"));
 
-      ($resp_code, $resp_msg) = $client->cdup();
-
+      my $conn = $client->stor_raw('test.txt');
+      unless ($conn) {
+        die("Failed to STOR: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      my $buf = "Hello, World!";
+      $conn->write($buf, length($buf), 5);
+      eval { $conn->close() };
+
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+
+      $client->quit();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_dir_cwd_cdup {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $group = 'ftpd';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, $group, $gid, $user);
+
+  my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
+  mkpath($src_dir);
+
+  my $dst_dir = '~/bar.d';
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10 vroot:20',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_dir $dst_dir",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
+
+      my ($resp_code, $resp_msg);
+      ($resp_code, $resp_msg) = $client->pwd();
+
+      my $expected;
+
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "\"/\" is the current directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->cwd('bar.d');
+
+      $expected = 250;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "CWD command successful";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->pwd();
+
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "\"/bar.d\" is the current directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->cdup();
+
       $expected = 250;
       $self->assert($expected == $resp_code,
         test_msg("Expected $expected, got $resp_code"));
@@ -5592,6 +6151,9 @@ sub vroot_alias_dir_cwd_cdup {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -5606,13 +6168,14 @@ sub vroot_alias_dir_mkd {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -5631,7 +6194,7 @@ sub vroot_alias_dir_mkd {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -5643,7 +6206,7 @@ sub vroot_alias_dir_mkd {
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'fsio:10',
+    Trace => 'fsio:10 vroot:20',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
@@ -5726,6 +6289,9 @@ sub vroot_alias_dir_mkd {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -5740,13 +6306,14 @@ sub vroot_alias_dir_rmd {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -5765,7 +6332,7 @@ sub vroot_alias_dir_rmd {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -5860,6 +6427,9 @@ sub vroot_alias_dir_rmd {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -5874,13 +6444,14 @@ sub vroot_alias_dir_cwd_mlsd {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -5899,7 +6470,7 @@ sub vroot_alias_dir_cwd_mlsd {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -5993,7 +6564,7 @@ sub vroot_alias_dir_cwd_mlsd {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -6057,6 +6628,9 @@ sub vroot_alias_dir_cwd_mlsd {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -6071,13 +6645,14 @@ sub vroot_alias_dir_mlsd_from_above {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -6096,7 +6671,7 @@ sub vroot_alias_dir_mlsd_from_above {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -6179,7 +6754,7 @@ sub vroot_alias_dir_mlsd_from_above {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -6243,6 +6818,9 @@ sub vroot_alias_dir_mlsd_from_above {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -6257,13 +6835,14 @@ sub vroot_alias_dir_outside_root_cwd_mlsd {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/baz");
   mkpath($home_dir);
   my $uid = 500;
@@ -6283,7 +6862,7 @@ sub vroot_alias_dir_outside_root_cwd_mlsd {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -6377,7 +6956,7 @@ sub vroot_alias_dir_outside_root_cwd_mlsd {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -6441,6 +7020,9 @@ sub vroot_alias_dir_outside_root_cwd_mlsd {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -6455,13 +7037,14 @@ sub vroot_alias_dir_outside_root_cwd_mlsd_cwd_ls {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs("$tmpdir/baz");
   mkpath($home_dir);
   my $uid = 500;
@@ -6481,7 +7064,7 @@ sub vroot_alias_dir_outside_root_cwd_mlsd_cwd_ls {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -6589,7 +7172,7 @@ sub vroot_alias_dir_outside_root_cwd_mlsd_cwd_ls {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -6646,7 +7229,7 @@ sub vroot_alias_dir_outside_root_cwd_mlsd_cwd_ls {
       }
 
       $buf = '';
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -6716,6 +7299,9 @@ sub vroot_alias_dir_outside_root_cwd_mlsd_cwd_ls {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -6730,13 +7316,14 @@ sub vroot_alias_dir_mlst {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -6755,7 +7342,7 @@ sub vroot_alias_dir_mlst {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
   mkpath($src_dir);
@@ -6844,6 +7431,9 @@ sub vroot_alias_dir_mlst {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -6858,13 +7448,14 @@ sub vroot_alias_symlink_list {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -6883,7 +7474,7 @@ sub vroot_alias_symlink_list {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -6957,7 +7548,6 @@ sub vroot_alias_symlink_list {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -6981,7 +7571,7 @@ sub vroot_alias_symlink_list {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -7049,6 +7639,9 @@ sub vroot_alias_symlink_list {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -7063,13 +7656,14 @@ sub vroot_alias_symlink_retr {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -7088,7 +7682,7 @@ sub vroot_alias_symlink_retr {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -7172,25 +7766,16 @@ sub vroot_alias_symlink_retr {
       }
 
       my $buf;
-      my $count = $conn->read($buf, 8192, 30);
+      my $count = $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       my $resp_code = $client->response_code();
       my $resp_msg = $client->response_msg();
-
-      my $expected;
-
-      $expected = 226;
-      $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
-
-      $expected = 'Transfer complete';
-      $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
       $client->quit();
 
-      $expected = 14;
+      my $expected = 14;
       $self->assert($expected == $count,
         test_msg("Expected $expected, got $count"));
     };
@@ -7217,6 +7802,9 @@ sub vroot_alias_symlink_retr {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -7231,13 +7819,14 @@ sub vroot_alias_symlink_stor_no_overwrite {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -7256,7 +7845,7 @@ sub vroot_alias_symlink_stor_no_overwrite {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -7376,6 +7965,9 @@ sub vroot_alias_symlink_stor_no_overwrite {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -7390,13 +7982,14 @@ sub vroot_alias_symlink_stor {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -7415,7 +8008,7 @@ sub vroot_alias_symlink_stor {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -7501,21 +8094,12 @@ sub vroot_alias_symlink_stor {
       }
 
       my $buf = "Farewell, cruel world";
-      $conn->write($buf, length($buf), 30);
+      $conn->write($buf, length($buf), 25);
       eval { $conn->close() };
 
       my $resp_code = $client->response_code();
       my $resp_msg = $client->response_msg();
-
-      my $expected;
-
-      $expected = 226;
-      $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
-
-      $expected = 'Transfer complete';
-      $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
       $client->quit();
     };
@@ -7542,6 +8126,9 @@ sub vroot_alias_symlink_stor {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -7556,13 +8143,14 @@ sub vroot_alias_symlink_mlsd {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -7581,7 +8169,7 @@ sub vroot_alias_symlink_mlsd {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -7700,6 +8288,9 @@ sub vroot_alias_symlink_mlsd {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -7714,13 +8305,14 @@ sub vroot_alias_symlink_mlst {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -7739,7 +8331,7 @@ sub vroot_alias_symlink_mlst {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -7852,6 +8444,9 @@ sub vroot_alias_symlink_mlst {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -7866,13 +8461,14 @@ sub vroot_alias_ifuser {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -7891,7 +8487,7 @@ sub vroot_alias_ifuser {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -7966,7 +8562,6 @@ EOC
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
@@ -7990,7 +8585,7 @@ EOC
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -8057,6 +8652,9 @@ EOC
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
@@ -8071,7 +8669,7 @@ sub vroot_alias_ifgroup {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
@@ -8142,11 +8740,11 @@ sub vroot_alias_ifgroup {
     print $fh <<EOC;
 <IfGroup $group>
   VRootAlias $src_file $dst_file1
-</IfUser>
+</IfGroup>
 
 <IfGroup !$group>
   VRootAlias $src_file $dst_file2
-</IfUser>
+</IfGroup>
 EOC
     unless (close($fh)) {
       die("Can't write $config_file: $!");
@@ -8172,11 +8770,9 @@ EOC
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
-
       ($resp_code, $resp_msg) = $client->pwd();
 
       my $expected;
@@ -8196,7 +8792,7 @@ EOC
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
       # We have to be careful of the fact that readdir returns directory
@@ -8263,13 +8859,16 @@ EOC
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_ifclass {
+sub vroot_alias_ifgroup_list_stor {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -8277,7 +8876,7 @@ sub vroot_alias_ifclass {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
@@ -8285,18 +8884,22 @@ sub vroot_alias_ifclass {
   my $user = 'proftpd';
   my $passwd = 'test';
   my $group = 'ftpd';
-  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $home_dir = File::Spec->rel2abs("$tmpdir/home/$user");
+  mkpath($home_dir);
   my $uid = 500;
   my $gid = 500;
 
+  my $shared_dir = File::Spec->rel2abs("$tmpdir/shared");
+  mkpath($shared_dir);
+
   # Make sure that, if we're running as root, that the home directory has
   # permissions/privs set for the account we create
   if ($< == 0) {
-    unless (chmod(0755, $home_dir)) {
+    unless (chmod(0755, $home_dir, $shared_dir)) {
       die("Can't set perms on $home_dir to 0755: $!");
     }
 
-    unless (chown($uid, $gid, $home_dir)) {
+    unless (chown($uid, $gid, $home_dir, $shared_dir)) {
       die("Can't set owner of $home_dir to $uid/$gid: $!");
     }
   }
@@ -8305,20 +8908,17 @@ sub vroot_alias_ifclass {
     '/bin/bash');
   auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
+  my $test_file = File::Spec->rel2abs("$shared_dir/test.txt");
+  if (open(my $fh, "> $test_file")) {
     print $fh "Hello, World!\n";
     unless (close($fh)) {
-      die("Can't write $src_file: $!");
+      die("Can't write $test_file: $!");
     }
 
   } else {
-    die("Can't open $src_file: $!");
+    die("Can't open $test_file: $!");
   }
 
-  my $dst_file1 = '~/bar.txt';
-  my $dst_file2 = '~/baz.txt';
-
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
@@ -8346,17 +8946,9 @@ sub vroot_alias_ifclass {
 
   if (open(my $fh, ">> $config_file")) {
     print $fh <<EOC;
-<Class test>
-  From 127.0.0.1
-</Class>
-
-<IfClass test>
-  VRootAlias $src_file $dst_file1
-</IfUser>
-
-<IfClass !test>
-  VRootAlias $src_file $dst_file2
-</IfUser>
+<IfGroup $group>
+  VRootAlias $shared_dir ~/shared
+</IfGroup>
 EOC
     unless (close($fh)) {
       die("Can't write $config_file: $!");
@@ -8382,11 +8974,9 @@ EOC
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
       my ($resp_code, $resp_msg);
-
       ($resp_code, $resp_msg) = $client->pwd();
 
       my $expected;
@@ -8406,9 +8996,13 @@ EOC
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
 
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+
       # We have to be careful of the fact that readdir returns directory
       # entries in an unordered fashion.
       my $res = {};
@@ -8430,8 +9024,7 @@ EOC
         'vroot.pid' => 1,
         'vroot.scoreboard' => 1,
         'vroot.scoreboard.lck' => 1,
-        'foo.txt' => 1,
-        'bar.txt' => 1,
+        'shared' => 1,
       };
 
       my $ok = 1;
@@ -8444,10 +9037,80 @@ EOC
         }
       }
 
-      unless ($ok) {
-        die("Unexpected name '$mismatch' appeared in LIST data")
+      $self->assert($ok,
+        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
+
+      ($resp_code, $resp_msg) = $client->cwd('shared');
+
+      $expected = 250;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = 'CWD command successful';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      $conn = $client->list_raw('-al');
+      unless ($conn) {
+        die("Failed to LIST: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      $buf = '';
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      $res = {};
+      $lines = [split(/\n/, $buf)];
+      foreach my $line (@$lines) {
+        if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
+          $res->{$1} = 1;
+        }
+      }
+
+      unless (scalar(keys(%$res)) > 0) {
+        die("LIST data unexpectedly empty");
+      }
+
+      $expected = {
+        '.' => 1,
+        '..' => 1,
+        'test.txt' => 1,
+      };
+
+      $ok = 1;
+      $mismatch = undef;
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
+      }
+
+      $self->assert($ok,
+        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
+
+      $conn = $client->stor_raw('bar.txt');
+      unless ($conn) {
+        die("Failed to STOR bar.txt: " . $client->response_code() . " " .
+          $client->response_msg());
       }
 
+      $buf = "Farewell, cruel world!\n";
+      $conn->write($buf, length($buf), 5);
+      eval { $conn->close() };
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+
       $client->quit();
     };
 
@@ -8473,13 +9136,16 @@ EOC
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_sftp_read {
+sub vroot_alias_ifclass {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -8487,13 +9153,14 @@ sub vroot_alias_file_sftp_read {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -8512,7 +9179,7 @@ sub vroot_alias_file_sftp_read {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
   if (open(my $fh, "> $src_file")) {
@@ -8525,10 +9192,8 @@ sub vroot_alias_file_sftp_read {
     die("Can't open $src_file: $!");
   }
 
-  my $dst_file = '~/bar.txt';
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  my $dst_file1 = '~/bar.txt';
+  my $dst_file2 = '~/baz.txt';
 
   my $config = {
     PidFile => $pid_file,
@@ -8541,19 +9206,10 @@ sub vroot_alias_file_sftp_read {
     AuthGroupFile => $auth_group_file,
 
     IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
-
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
         DefaultRoot => '~',
-
-        VRootAlias => "$src_file $dst_file",
       },
 
       'mod_delay.c' => {
@@ -8564,6 +9220,28 @@ sub vroot_alias_file_sftp_read {
 
   my ($port, $config_user, $config_group) = config_write($config_file, $config);
 
+  if (open(my $fh, ">> $config_file")) {
+    print $fh <<EOC;
+<Class test>
+  From 127.0.0.1
+</Class>
+
+<IfClass test>
+  VRootAlias $src_file $dst_file1
+</IfClass>
+
+<IfClass !test>
+  VRootAlias $src_file $dst_file2
+</IfClass>
+EOC
+    unless (close($fh)) {
+      die("Can't write $config_file: $!");
+    }
+
+  } else {
+    die("Can't open $config_file: $!");
+  }
+
   # Open pipes, for use between the parent and child processes.  Specifically,
   # the child will indicate when it's done with its test by writing a message
   # to the parent.
@@ -8572,62 +9250,79 @@ sub vroot_alias_file_sftp_read {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      sleep(1);
+      my ($resp_code, $resp_msg);
+      ($resp_code, $resp_msg) = $client->pwd();
 
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $expected;
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
 
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      $expected = "\"/\" is the current directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
 
-      my $fh = $sftp->open('bar.txt', O_RDONLY);
-      unless ($fh) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("Can't open bar.txt: [$err_name] ($err_code)");
+      my $conn = $client->list_raw();
+      unless ($conn) {
+        die("Failed to LIST: " . $client->response_code() . " " .
+          $client->response_msg());
       }
 
       my $buf;
-      my $size = 0;
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
 
-      my $res = $fh->read($buf, 8192, 30);
-      while ($res) {
-        $size += $res;
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      my $res = {};
+      my $lines = [split(/\n/, $buf)];
+      foreach my $line (@$lines) {
+        if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
+          $res->{$1} = 1;
+        }
+      }
 
-        $res = $fh->read($buf, 8192, 30);
+      unless (scalar(keys(%$res)) > 0) {
+        die("LIST data unexpectedly empty");
       }
 
-      # To issue the FXP_CLOSE, we have to explicit destroy the filehandle
-      $fh = undef;
+      my $expected = {
+        'vroot.conf' => 1,
+        'vroot.group' => 1,
+        'vroot.passwd' => 1,
+        'vroot.pid' => 1,
+        'vroot.scoreboard' => 1,
+        'vroot.scoreboard.lck' => 1,
+        'foo.txt' => 1,
+        'bar.txt' => 1,
+      };
 
-      my $expected = 14;
-      $self->assert($expected == $size,
-        test_msg("Expected $expected, got $size"));
+      my $ok = 1;
+      my $mismatch;
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
+      }
+
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in LIST data")
+      }
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -8652,13 +9347,16 @@ sub vroot_alias_file_sftp_read {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_sftp_write_no_overwrite {
+sub vroot_showsymlinks_on {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -8666,17 +9364,19 @@ sub vroot_alias_file_sftp_write_no_overwrite {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
-  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $home_dir = File::Spec->rel2abs("$tmpdir/home");
   my $uid = 500;
   my $gid = 500;
 
+  mkpath($home_dir);
+
   # Make sure that, if we're running as root, that the home directory has
   # permissions/privs set for the account we create
   if ($< == 0) {
@@ -8693,21 +9393,61 @@ sub vroot_alias_file_sftp_write_no_overwrite {
     '/bin/bash');
   auth_group_write($auth_group_file, 'ftpd', $gid, $user);
 
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
+  # See:
+  #
+  #  http://forums.proftpd.org/smf/index.php/topic,5207.0.html
+
+  # Create a symlink to a file that is outside of the vroot
+  my $outside_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $outside_file")) {
     print $fh "Hello, World!\n";
     unless (close($fh)) {
-      die("Can't write $src_file: $!");
+      die("Can't write $outside_file: $!");
     }
 
   } else {
-    die("Can't open $src_file: $!");
+    die("Can't open $outside_file: $!");
   }
 
-  my $dst_file = '~/bar.txt';
+  my $cwd = getcwd();
+
+  unless (chdir($home_dir)) {
+    die("Can't chdir to $home_dir: $!");
+  }
+
+  unless (symlink("../foo.txt", "foo.lnk")) {
+    die("Can't symlink '../foo.txt' to 'foo.lnk': $!");
+  }
+
+  unless (chdir($cwd)) {
+    die("Can't chdir to $cwd: $!");
+  }
+
+  # Now create a symlink which points inside the vroot
+  my $inside_file = File::Spec->rel2abs("$tmpdir/home/bar.txt");
+  if (open(my $fh, "> $inside_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $inside_file: $!");
+    }
+
+  } else {
+    die("Can't open $inside_file: $!");
+  }
+
+  my $cwd = getcwd();
+
+  unless (chdir($home_dir)) {
+    die("Can't chdir to $home_dir: $!");
+  }
+
+  unless (symlink("./bar.txt", "bar.lnk")) {
+    die("Can't symlink './bar.txt' to 'bar.lnk': $!");
+  }
 
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  unless (chdir($cwd)) {
+    die("Can't chdir to $cwd: $!");
+  }
 
   my $config = {
     PidFile => $pid_file,
@@ -8719,20 +9459,16 @@ sub vroot_alias_file_sftp_write_no_overwrite {
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
-    IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
+    # ShowSymlinks is on by default, but explicitly list it here for
+    # completeness
+    ShowSymlinks => 'on',
 
+    IfModules => {
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_file $dst_file",
+        DefaultRoot => $home_dir,
       },
 
       'mod_delay.c' => {
@@ -8751,50 +9487,73 @@ sub vroot_alias_file_sftp_write_no_overwrite {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      my $conn = $client->list_raw();
+      unless ($conn) {
+        die("Failed to LIST: " . $client->response_code() . " " .
+          $client->response_msg());
       }
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $buf;
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
 
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      my $res = {};
+      my $lines = [split(/\n/, $buf)];
+      foreach my $line (@$lines) {
+        if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
+          $res->{$1} = 1;
+        }
       }
 
-      my $fh = $sftp->open('bar.txt', O_WRONLY|O_TRUNC, 0644);
-      if ($fh) {
-        die("OPEN bar.txt succeeded unexpectedly");
+      unless (scalar(keys(%$res)) > 0) {
+        die("LIST data unexpectedly empty");
+      }
+
+      my $expected = {
+        'bar.txt' => 1,
+      };
+
+      my $ok = 1;
+      my $mismatch;
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
       }
 
-      my ($err_code, $err_name) = $sftp->error();
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in LIST data")
+      }
+
+      # Try to download from the symlink
+      my $conn = $client->retr_raw('bar.txt');
+      unless ($conn) {
+        die("RETR bar.txt failed: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
 
-      my $expected = 'SSH_FX_PERMISSION_DENIED';
-      $self->assert($expected eq $err_name,
-        test_msg("Expected '$expected', got '$err_name'"));
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -8806,7 +9565,8 @@ sub vroot_alias_file_sftp_write_no_overwrite {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -8819,13 +9579,16 @@ sub vroot_alias_file_sftp_write_no_overwrite {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_sftp_write {
+sub vroot_hiddenstores_on_double_dot {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -8833,7 +9596,7 @@ sub vroot_alias_file_sftp_write {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
@@ -8860,22 +9623,6 @@ sub vroot_alias_file_sftp_write {
     '/bin/bash');
   auth_group_write($auth_group_file, 'ftpd', $gid, $user);
 
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $src_file: $!");
-    }
-
-  } else {
-    die("Can't open $src_file: $!");
-  }
-
-  my $dst_file = '~/bar.txt';
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
-
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
@@ -8887,21 +9634,14 @@ sub vroot_alias_file_sftp_write {
     AuthGroupFile => $auth_group_file,
 
     AllowOverwrite => 'on',
+    HiddenStores => 'on',
 
     IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
-
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_file $dst_file",
+        DefaultRoot => '~',
       },
 
       'mod_delay.c' => {
@@ -8920,53 +9660,32 @@ sub vroot_alias_file_sftp_write {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
-
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      my $fh = $sftp->open('bar.txt', O_WRONLY|O_TRUNC, 0644);
-      unless ($fh) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("Can't open bar.txt: [$err_name] ($err_code)");
+      # Try to upload a file whose name starts with a period
+      my $conn = $client->stor_raw('.foo');
+      unless ($conn) {
+        die("STOR .foo failed: " . $client->response_code() . ' ' .
+          $client->response_msg());
       }
 
-      my $count = 20;
-      for (my $i = 0; $i < $count; $i++) {
-        print $fh "ABCD" x 4096;
-      }
+      my $buf = "Farewell, cruel world";
+      $conn->write($buf, length($buf), 25);
+      eval { $conn->close() };
 
-      # To issue the FXP_CLOSE, we have to explicit destroy the filehandle
-      $fh = undef;
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -8991,13 +9710,16 @@ sub vroot_alias_file_sftp_write {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_sftp_stat {
+sub vroot_mfmt {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -9005,17 +9727,29 @@ sub vroot_alias_file_sftp_stat {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
 
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+  if (open(my $fh, "> $test_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $test_file: $!");
+    }
+
+  } else {
+    die("Can't open $test_file: $!");
+  }
+
   # Make sure that, if we're running as root, that the home directory has
   # permissions/privs set for the account we create
   if ($< == 0) {
@@ -9030,48 +9764,23 @@ sub vroot_alias_file_sftp_stat {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $src_file: $!");
-    }
-
-  } else {
-    die("Can't open $src_file: $!");
-  }
-
-  my $dst_file = '~/bar.txt';
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'fsio:10',
+    Trace => 'fsio:10 vroot:20',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
     IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
-
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
         DefaultRoot => '~',
-
-        VRootAlias => "$src_file $dst_file",
       },
 
       'mod_delay.c' => {
@@ -9090,60 +9799,74 @@ sub vroot_alias_file_sftp_stat {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      sleep(1);
+      my ($resp_code, $resp_msg);
 
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      # First try MFMT using relative paths
+      my $path = './test.txt';
+      ($resp_code, $resp_msg) = $client->mfmt('20020717210715', $path);
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $expected;
 
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      $expected = 213;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "Modify=20020717210715; $path";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      $path = "test.txt";
+      ($resp_code, $resp_msg) = $client->mfmt('20020717210715', $path);
+
+      $expected = 213;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "Modify=20020717210715; $path";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
 
-      my $attrs = $sftp->stat('bar.txt', 1);
-      unless ($attrs) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
+      # Next try an absolute path (from client perspective)
+      $path = "/test.txt";
+      ($resp_code, $resp_msg) = $client->mfmt('20020717210715', $path);
+
+      $expected = 213;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "Modify=20020717210715; $path";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      # Last try the real absolute path
+      $path = $test_file;
+      eval { $client->mfmt('20020717210715', $path) };
+      unless ($@) {
+        die("MFMT $path succeeded unexpectedly");
       }
 
-      my $expected = 14;
-      my $file_size = $attrs->{size};
-      $self->assert($expected == $file_size,
-        test_msg("Expected $expected, got $file_size"));
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
 
-      $expected = $<;
-      my $file_uid = $attrs->{uid};
-      $self->assert($expected == $file_uid,
-        test_msg("Expected '$expected', got '$file_uid'"));
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
 
-      $expected = $(;
-      my $file_gid = $attrs->{gid};
-      $self->assert($expected == $file_gid,
-        test_msg("Expected '$expected', got '$file_gid'"));
+      $expected = "$path: No such file or directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -9155,7 +9878,8 @@ sub vroot_alias_file_sftp_stat {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -9168,13 +9892,16 @@ sub vroot_alias_file_sftp_stat {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_sftp_lstat {
+sub vroot_log_extlog_retr {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -9182,13 +9909,14 @@ sub vroot_alias_file_sftp_lstat {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -9207,23 +9935,20 @@ sub vroot_alias_file_sftp_lstat {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+  if (open(my $fh, "> $test_file")) {
     print $fh "Hello, World!\n";
     unless (close($fh)) {
-      die("Can't write $src_file: $!");
+      die("Can't write $test_file: $!");
     }
 
   } else {
-    die("Can't open $src_file: $!");
+    die("Can't open $test_file: $!");
   }
 
-  my $dst_file = '~/bar.txt';
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  my $ext_log = File::Spec->rel2abs("$tmpdir/custom.log");
 
   my $config = {
     PidFile => $pid_file,
@@ -9235,20 +9960,15 @@ sub vroot_alias_file_sftp_lstat {
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
-    IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
+    LogFormat => 'custom "%f"',
+    ExtendedLog => "$ext_log READ custom",
 
+    IfModules => {
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_file $dst_file",
+        DefaultRoot => '~',
       },
 
       'mod_delay.c' => {
@@ -9267,60 +9987,31 @@ sub vroot_alias_file_sftp_lstat {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
-
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      my $attrs = $sftp->stat('bar.txt', 0);
-      unless ($attrs) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
+      my $conn = $client->retr_raw('test.txt');
+      unless ($conn) {
+        die("Failed to RETR test.txt: " . $client->response_code() . " " .
+          $client->response_msg());
       }
 
-      my $expected = 14;
-      my $file_size = $attrs->{size};
-      $self->assert($expected == $file_size,
-        test_msg("Expected $expected, got $file_size"));
-
-      $expected = $<;
-      my $file_uid = $attrs->{uid};
-      $self->assert($expected == $file_uid,
-        test_msg("Expected '$expected', got '$file_uid'"));
+      my $buf;
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
 
-      $expected = $(;
-      my $file_gid = $attrs->{gid};
-      $self->assert($expected == $file_gid,
-        test_msg("Expected '$expected', got '$file_gid'"));
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -9332,7 +10023,8 @@ sub vroot_alias_file_sftp_lstat {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -9344,14 +10036,36 @@ sub vroot_alias_file_sftp_lstat {
 
   $self->assert_child_ok($pid);
 
+  # Now, read in the ExtendedLog, and see whether the %f variable was
+  # properly written out.
+  if (open(my $fh, "< $ext_log")) {
+    my $line = <$fh>;
+    chomp($line);
+    close($fh);
+
+    if ($^O eq 'darwin') {
+      # MacOSX-specific hack, due to how it handles tmp files
+      $test_file = ('/private' . $test_file);
+    }
+
+    $self->assert($test_file eq $line,
+      test_msg("Expected '$test_file', got '$line'"));
+
+  } else {
+    die("Can't read $ext_log: $!");
+  }
+
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_sftp_realpath {
+sub vroot_log_extlog_stor {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -9359,13 +10073,14 @@ sub vroot_alias_file_sftp_realpath {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -9384,23 +10099,11 @@ sub vroot_alias_file_sftp_realpath {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $src_file: $!");
-    }
-
-  } else {
-    die("Can't open $src_file: $!");
-  }
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $dst_file = '~/bar.txt';
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
 
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  my $ext_log = File::Spec->rel2abs("$tmpdir/custom.log");
 
   my $config = {
     PidFile => $pid_file,
@@ -9412,20 +10115,15 @@ sub vroot_alias_file_sftp_realpath {
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
-    IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
+    LogFormat => 'custom "%f"',
+    ExtendedLog => "$ext_log WRITE custom",
 
+    IfModules => {
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_file $dst_file",
+        DefaultRoot => '~',
       },
 
       'mod_delay.c' => {
@@ -9444,51 +10142,31 @@ sub vroot_alias_file_sftp_realpath {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
-
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      my $real_path = $sftp->realpath('bar.txt');
-      unless ($real_path) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("Can't get real path for 'bar.txt': [$err_name] ($err_code)");
+      my $conn = $client->stor_raw('test.txt');
+      unless ($conn) {
+        die("Failed to STOR test.txt: " . $client->response_code() . " " .
+          $client->response_msg());
       }
 
-      my $expected;
+      my $buf = "Hello, World!";
+      $conn->write($buf, length($buf), 5);
+      eval { $conn->close() };
 
-      $expected = '/bar.txt';
-      $self->assert($expected eq $real_path,
-        test_msg("Expected '$expected', got '$real_path'"));
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -9500,7 +10178,8 @@ sub vroot_alias_file_sftp_realpath {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -9512,14 +10191,36 @@ sub vroot_alias_file_sftp_realpath {
 
   $self->assert_child_ok($pid);
 
-  if ($ex) {
-    die($ex);
-  }
+  # Now, read in the ExtendedLog, and see whether the %f variable was
+  # properly written out.
+  if (open(my $fh, "< $ext_log")) {
+    my $line = <$fh>;
+    chomp($line);
+    close($fh);
+
+    if ($^O eq 'darwin') {
+      # MacOSX-specific hack, due to how it handles tmp files
+      $test_file = ('/private' . $test_file);
+    }
+
+    $self->assert($test_file eq $line,
+      test_msg("Expected '$test_file', got '$line'"));
+
+  } else {
+    die("Can't read $ext_log: $!");
+  }
+
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_sftp_remove {
+sub vroot_log_xferlog_retr {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -9527,13 +10228,14 @@ sub vroot_alias_file_sftp_remove {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -9552,23 +10254,20 @@ sub vroot_alias_file_sftp_remove {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+  if (open(my $fh, "> $test_file")) {
     print $fh "Hello, World!\n";
     unless (close($fh)) {
-      die("Can't write $src_file: $!");
+      die("Can't write $test_file: $!");
     }
 
   } else {
-    die("Can't open $src_file: $!");
+    die("Can't open $test_file: $!");
   }
 
-  my $dst_file = '~/bar.txt';
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log");
 
   my $config = {
     PidFile => $pid_file,
@@ -9580,20 +10279,14 @@ sub vroot_alias_file_sftp_remove {
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
-    IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
+    TransferLog => $xfer_log,
 
+    IfModules => {
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_file $dst_file",
+        DefaultRoot => '~',
       },
 
       'mod_delay.c' => {
@@ -9612,50 +10305,32 @@ sub vroot_alias_file_sftp_remove {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
-
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
+      $client->type('binary');
 
-      my $res = $sftp->unlink('bar.txt');
-      if ($res) {
-        die("REMOVE bar.txt succeeded unexpectedly");
+      my $conn = $client->retr_raw('test.txt');
+      unless ($conn) {
+        die("Failed to RETR test.txt: " . $client->response_code() . " " .
+          $client->response_msg());
       }
 
-      my ($err_code, $err_name) = $sftp->error();
+      my $buf;
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
 
-      my $expected = 'SSH_FX_PERMISSION_DENIED';
-      $self->assert($expected eq $err_name,
-        test_msg("Expected '$expected', got '$err_name'"));
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -9667,7 +10342,8 @@ sub vroot_alias_file_sftp_remove {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -9679,14 +10355,64 @@ sub vroot_alias_file_sftp_remove {
 
   $self->assert_child_ok($pid);
 
+  if (open(my $fh, "< $xfer_log")) {
+    my $line = <$fh>;
+    chomp($line);
+    close($fh);
+
+    my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+o\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$';
+
+    $self->assert(qr/$expected/, $line,
+      test_msg("Expected '$expected', got '$line'"));
+
+    if ($line =~ /$expected/) {
+      my $remote_host = $1;
+      my $filesz = $2;
+      my $filename = $3;
+      my $xfer_type = $4;
+      my $user_name = $5;
+
+      if ($^O eq 'darwin') {
+        # MacOSX-specific hack, due to how it handles tmp files
+        $test_file = ('/private' . $test_file);
+      }
+
+      $expected = '127.0.0.1';
+      $self->assert($expected eq $remote_host,
+        test_msg("Expected '$expected', got '$remote_host'"));
+
+      $expected = -s $test_file;
+      $self->assert($expected == $filesz,
+        test_msg("Expected '$expected', got '$filesz'"));
+
+      $expected = $test_file;
+      $self->assert($expected eq $filename,
+        test_msg("Expected '$expected', got '$filename'"));
+
+      $expected = 'b';
+      $self->assert($expected eq $xfer_type,
+        test_msg("Expected '$expected', got '$xfer_type'"));
+
+      $expected = $user;
+      $self->assert($expected eq $user_name,
+        test_msg("Expected '$expected', got '$user_name'"));
+    }
+
+  } else {
+    die("Can't read $xfer_log: $!");
+  }
+
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_dir_sftp_readdir {
+sub vroot_log_xferlog_stor {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -9694,13 +10420,14 @@ sub vroot_alias_dir_sftp_readdir {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -9719,39 +10446,11 @@ sub vroot_alias_dir_sftp_readdir {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
-  mkpath($src_dir);
-
-  my $dst_dir = '~/bar.d';
-
-  my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
-  if (open(my $fh, "> $test_file1")) {
-    close($fh);
-
-  } else {
-    die("Can't open $test_file1: $!");
-  }
-
-  my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
-  if (open(my $fh, "> $test_file2")) {
-    close($fh);
-
-  } else {
-    die("Can't open $test_file2: $!");
-  }
-
-  my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
-  if (open(my $fh, "> $test_file3")) {
-    close($fh);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  } else {
-    die("Can't open $test_file3: $!");
-  }
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
 
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log");
 
   my $config = {
     PidFile => $pid_file,
@@ -9763,20 +10462,14 @@ sub vroot_alias_dir_sftp_readdir {
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
-    IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
+    TransferLog => $xfer_log,
 
+    IfModules => {
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_dir $dst_dir",
+        DefaultRoot => '~',
       },
 
       'mod_delay.c' => {
@@ -9795,92 +10488,32 @@ sub vroot_alias_dir_sftp_readdir {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
-
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
-
-      my $dir = $sftp->opendir('bar.d');
-      unless ($dir) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("Can't open directory 'bar.d': [$err_name] ($err_code)");
-      }
-
-      my $res = {};
-
-      my $file = $dir->read();
-      while ($file) {
-        $res->{$file->{name}} = $file;
-        $file = $dir->read();
-      }
-
-      my $expected = {
-        '.' => 1,
-        '..' => 1,
-        'a.txt' => 1,
-        'b.txt' => 1,
-        'c.txt' => 1,
-      };
-
-      # To issue the FXP_CLOSE, we have to explicit destroy the dirhandle
-      $dir = undef;
-
-      $ssh2->disconnect();
-
-      my $ok = 1;
-      my $mismatch;
-
-      my $seen = [];
-      foreach my $name (keys(%$res)) {
-        push(@$seen, $name);
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
+      $client->type('binary');
 
-        unless (defined($expected->{$name})) {
-          $mismatch = $name;
-          $ok = 0;
-          last;
-        }
+      my $conn = $client->stor_raw('test.txt');
+      unless ($conn) {
+        die("Failed to STOR test.txt: " . $client->response_code() . " " .
+          $client->response_msg());
       }
 
-      unless ($ok) {
-        die("Unexpected name '$mismatch' appeared in READDIR data")
-      }
+      my $buf = "Hello, World!";
+      $conn->write($buf, length($buf), 5);
+      eval { $conn->close() };
 
-      # Now remove from $expected all of the paths we saw; if there are
-      # any entries remaining in $expected, something went wrong.
-      foreach my $name (@$seen) {
-        delete($expected->{$name});
-      }
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
-      my $remaining = scalar(keys(%$expected));
-      $self->assert(0 == $remaining,
-        test_msg("Expected 0, got $remaining"));
+      $client->quit();
     };
 
     if ($@) {
@@ -9892,7 +10525,8 @@ sub vroot_alias_dir_sftp_readdir {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -9904,14 +10538,64 @@ sub vroot_alias_dir_sftp_readdir {
 
   $self->assert_child_ok($pid);
 
+  if (open(my $fh, "< $xfer_log")) {
+    my $line = <$fh>;
+    chomp($line);
+    close($fh);
+
+    my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+i\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$';
+
+    $self->assert(qr/$expected/, $line,
+      test_msg("Expected '$expected', got '$line'"));
+
+    if ($line =~ /$expected/) {
+      my $remote_host = $1;
+      my $filesz = $2;
+      my $filename = $3;
+      my $xfer_type = $4;
+      my $user_name = $5;
+
+      if ($^O eq 'darwin') {
+        # MacOSX-specific hack, due to how it handles tmp files
+        $test_file = ('/private' . $test_file);
+      }
+
+      $expected = '127.0.0.1';
+      $self->assert($expected eq $remote_host,
+        test_msg("Expected '$expected', got '$remote_host'"));
+
+      $expected = -s $test_file;
+      $self->assert($expected == $filesz,
+        test_msg("Expected '$expected', got '$filesz'"));
+
+      $expected = $test_file;
+      $self->assert($expected eq $filename,
+        test_msg("Expected '$expected', got '$filename'"));
+
+      $expected = 'b';
+      $self->assert($expected eq $xfer_type,
+        test_msg("Expected '$expected', got '$xfer_type'"));
+
+      $expected = $user;
+      $self->assert($expected eq $user_name,
+        test_msg("Expected '$expected', got '$user_name'"));
+    }
+
+  } else {
+    die("Can't read $xfer_log: $!");
+  }
+
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_dir_sftp_rmdir {
+sub vroot_config_limit_write {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -9919,13 +10603,14 @@ sub vroot_alias_dir_sftp_rmdir {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -9944,40 +10629,24 @@ sub vroot_alias_dir_sftp_rmdir {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
-  mkpath($src_dir);
-
-  my $dst_dir = '~/bar.d';
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'fsio:10',
+    Trace => 'directory:20 fsio:10',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
     IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
-
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_dir $dst_dir",
+        DefaultRoot => '~',
       },
 
       'mod_delay.c' => {
@@ -9988,6 +10657,22 @@ sub vroot_alias_dir_sftp_rmdir {
 
   my ($port, $config_user, $config_group) = config_write($config_file, $config);
 
+  if (open(my $fh, ">> $config_file")) {
+    print $fh <<EOC;
+<Directory $home_dir>
+  <Limit WRITE>
+    DenyAll
+  </Limit>
+</Directory>
+EOC
+    unless (close($fh)) {
+      die("Can't write $config_file: $!");
+    }
+
+  } else {
+    die("Can't open $config_file: $!");
+  }
+
   # Open pipes, for use between the parent and child processes.  Specifically,
   # the child will indicate when it's done with its test by writing a message
   # to the parent.
@@ -9996,50 +10681,36 @@ sub vroot_alias_dir_sftp_rmdir {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
-
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      my $conn = $client->stor_raw('test.txt');
+      if ($conn) {
+        eval { $conn->close() };
+        die("STOR test.txt succeeded unexpectedly");
       }
 
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
 
-      my $res = $sftp->rmdir('/bar.d');
-      if ($res) {
-        die("RMDIR bar.d succeeded unexpectedly");
-      }
+      my $expected;
 
-      my ($err_code, $err_name) = $sftp->error();
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
 
-      my $expected = 'SSH_FX_PERMISSION_DENIED';
-      $self->assert($expected eq $err_name,
-        test_msg("Expected '$expected', got '$err_name'"));
+      $expected = "test.txt: Permission denied";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -10051,7 +10722,8 @@ sub vroot_alias_dir_sftp_rmdir {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -10064,13 +10736,16 @@ sub vroot_alias_dir_sftp_rmdir {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_symlink_sftp_stat {
+sub vroot_config_deleteabortedstores_conn_aborted {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -10078,13 +10753,14 @@ sub vroot_alias_symlink_sftp_stat {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -10103,64 +10779,30 @@ sub vroot_alias_symlink_sftp_stat {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $src_file: $!");
-    }
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  } else {
-    die("Can't open $src_file: $!");
-  }
+  my $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
 
-  my $cwd = getcwd();
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10 vroot:20',
 
-  unless (chdir($home_dir)) {
-    die("Can't chdir to $home_dir: $!");
-  }
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
 
-  unless (symlink("./foo.txt", "foo.lnk")) {
-    die("Can't symlink './foo.txt' to 'foo.lnk': $!");
-  }
-
-  unless (chdir($cwd)) {
-    die("Can't chdir to $cwd: $!");
-  }
-
-  my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
-  my $dst_file = '~/bar.lnk';
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
-
-  my $config = {
-    PidFile => $pid_file,
-    ScoreboardFile => $scoreboard_file,
-    SystemLog => $log_file,
-    TraceLog => $log_file,
-    Trace => 'fsio:10',
-
-    AuthUserFile => $auth_user_file,
-    AuthGroupFile => $auth_group_file,
+    HiddenStores => 'on',
+    DeleteAbortedStores => 'on',
 
     IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
-
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        VRootOptions => 'allowSymlinks',
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_symlink $dst_file",
+        DefaultRoot => '~',
       },
 
       'mod_delay.c' => {
@@ -10179,60 +10821,42 @@ sub vroot_alias_symlink_sftp_stat {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
-
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      my $conn = $client->stor_raw('test.txt');
+      unless ($conn) {
+        die("Failed to STOR test.txt: " . $client->response_code() . ' ' .
+          $client->response_msg());
       }
 
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $buf = "Hello, World!";
+      $conn->write($buf, length($buf), 5);
 
-      my $attrs = $sftp->stat('bar.lnk', 1);
-      unless ($attrs) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
+      unless (-f $hidden_file) {
+        die("File $hidden_file does not exist as expected");
       }
 
-      my $expected = 14;
-      my $file_size = $attrs->{size};
-      $self->assert($expected == $file_size,
-        test_msg("Expected $expected, got $file_size"));
+      eval { $conn->abort() };
 
-      $expected = $<;
-      my $file_uid = $attrs->{uid};
-      $self->assert($expected == $file_uid,
-        test_msg("Expected '$expected', got '$file_uid'"));
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg, 1);
 
-      $expected = $(;
-      my $file_gid = $attrs->{gid};
-      $self->assert($expected == $file_gid,
-        test_msg("Expected '$expected', got '$file_gid'"));
+      if (-f $test_file) {
+        die("File $test_file exists unexpectedly");
+      }
 
-      $ssh2->disconnect();
+      if (-f $hidden_file) {
+        die("File $hidden_file exists unexpectedly");
+      }
     };
 
     if ($@) {
@@ -10244,7 +10868,8 @@ sub vroot_alias_symlink_sftp_stat {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -10257,13 +10882,16 @@ sub vroot_alias_symlink_sftp_stat {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_symlink_sftp_lstat {
+sub vroot_config_deleteabortedstores_cmd_aborted {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -10271,13 +10899,14 @@ sub vroot_alias_symlink_sftp_lstat {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -10296,64 +10925,31 @@ sub vroot_alias_symlink_sftp_lstat {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $src_file: $!");
-    }
-
-  } else {
-    die("Can't open $src_file: $!");
-  }
-
-  my $cwd = getcwd();
-
-  unless (chdir($home_dir)) {
-    die("Can't chdir to $home_dir: $!");
-  }
-
-  unless (symlink("./foo.txt", "foo.lnk")) {
-    die("Can't symlink './foo.txt' to 'foo.lnk': $!");
-  }
-
-  unless (chdir($cwd)) {
-    die("Can't chdir to $cwd: $!");
-  }
-
-  my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
-  my $dst_file = '~/bar.lnk';
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  my $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
 
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'fsio:10',
+    Trace => 'fsio:10 vroot:20',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
-    IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
+    HiddenStores => 'on',
+    DeleteAbortedStores => 'on',
+    TimeoutLinger => 1,
 
+    IfModules => {
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        VRootOptions => 'allowSymlinks',
-        DefaultRoot => '~',
 
-        VRootAlias => "$src_symlink $dst_file",
+        DefaultRoot => '~',
       },
 
       'mod_delay.c' => {
@@ -10372,60 +10968,45 @@ sub vroot_alias_symlink_sftp_lstat {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
-
-      sleep(1);
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      my $conn = $client->stor_raw('test.txt');
+      unless ($conn) {
+        die("Failed to STOR test.txt: " . $client->response_code() . ' ' .
+          $client->response_msg());
       }
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $buf = "Hello, World!";
+      $conn->write($buf, length($buf), 5);
 
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      unless (-f $hidden_file) {
+        die("File $hidden_file does not exist as expected");
       }
 
-      my $attrs = $sftp->stat('bar.lnk', 0);
-      unless ($attrs) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
-      }
+      $client->quote('ABOR');
 
-      my $expected = 14;
-      my $file_size = $attrs->{size};
-      $self->assert($expected == $file_size,
-        test_msg("Expected $expected, got $file_size"));
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg, 1);
 
-      $expected = $<;
-      my $file_uid = $attrs->{uid};
-      $self->assert($expected == $file_uid,
-        test_msg("Expected '$expected', got '$file_uid'"));
+      eval { $conn->close() };
+      $client->quit();
 
-      $expected = $(;
-      my $file_gid = $attrs->{gid};
-      $self->assert($expected == $file_gid,
-        test_msg("Expected '$expected', got '$file_gid'"));
+      if (-f $test_file) {
+        die("File $test_file exists unexpectedly");
+      }
 
-      $ssh2->disconnect();
+      if (-f $hidden_file) {
+        die("File $hidden_file exists unexpectedly");
+      }
     };
 
     if ($@) {
@@ -10437,7 +11018,8 @@ sub vroot_alias_symlink_sftp_lstat {
 
   } else {
     eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
+    if ($@) {
+      warn($@);
       exit 1;
     }
 
@@ -10450,13 +11032,16 @@ sub vroot_alias_symlink_sftp_lstat {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_symlink_sftp_realpath {
+sub vroot_alias_var_u_file {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -10464,13 +11049,14 @@ sub vroot_alias_symlink_sftp_realpath {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -10489,38 +11075,24 @@ sub vroot_alias_symlink_sftp_realpath {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
+  my $sub_dir = File::Spec->rel2abs("$tmpdir/$user");
+  mkpath($sub_dir);
+
+  my $src_path = File::Spec->rel2abs("$sub_dir/foo.txt");
+  if (open(my $fh, "> $src_path")) {
     print $fh "Hello, World!\n";
     unless (close($fh)) {
-      die("Can't write $src_file: $!");
+      die("Can't write $src_path: $!");
     }
 
   } else {
-    die("Can't open $src_file: $!");
-  }
-
-  my $cwd = getcwd();
-
-  unless (chdir($home_dir)) {
-    die("Can't chdir to $home_dir: $!");
-  }
-
-  unless (symlink("./foo.txt", "foo.lnk")) {
-    die("Can't symlink './foo.txt' to 'foo.lnk': $!");
-  }
-
-  unless (chdir($cwd)) {
-    die("Can't chdir to $cwd: $!");
+    die("Can't open $src_path: $!");
   }
 
-  my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
-  my $dst_file = '~/bar.lnk';
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  my $src_file = File::Spec->rel2abs($tmpdir) . '/%u/foo.txt';
+  my $dst_file = '~/bar.txt';
 
   my $config = {
     PidFile => $pid_file,
@@ -10533,20 +11105,12 @@ sub vroot_alias_symlink_sftp_realpath {
     AuthGroupFile => $auth_group_file,
 
     IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
-
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-        VRootOptions => 'allowSymlinks',
         DefaultRoot => '~',
 
-        VRootAlias => "$src_symlink $dst_file",
+        VRootAlias => "$src_file $dst_file",
       },
 
       'mod_delay.c' => {
@@ -10565,49 +11129,84 @@ sub vroot_alias_symlink_sftp_realpath {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      sleep(1);
+      my ($resp_code, $resp_msg);
+
+      ($resp_code, $resp_msg) = $client->pwd();
+
+      my $expected;
+
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "\"/\" is the current directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      my $conn = $client->list_raw();
+      unless ($conn) {
+        die("Failed to LIST: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      my $buf;
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
 
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      my $res = {};
+      my $lines = [split(/\n/, $buf)];
+      foreach my $line (@$lines) {
+        if ($line =~ /^(\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
+          $res->{$2} = $1;
+        }
       }
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      unless (scalar(keys(%$res)) > 0) {
+        die("LIST data unexpectedly empty");
       }
 
-      my $sftp = $ssh2->sftp();
-      unless ($sftp) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      $expected = {
+        'vroot.conf' => 1,
+        'vroot.group' => 1,
+        'vroot.passwd' => 1,
+        'vroot.pid' => 1,
+        'vroot.scoreboard' => 1,
+        'vroot.scoreboard.lck' => 1,
+        'bar.txt' => 1,
+        'proftpd' => 1,
+      };
+
+      my $ok = 1;
+      my $mismatch;
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
       }
 
-      my $real_path = $sftp->realpath('bar.lnk');
-      unless ($real_path) {
-        my ($err_code, $err_name) = $sftp->error();
-        die("FXP_REALPATH bar.lnk failed: [$err_name] ($err_code)");
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in LIST data")
       }
 
-      my $expected = '/bar.lnk';
-      $self->assert($expected eq $real_path,
-        test_msg("Expected '$expected', got '$real_path'"));
+      my $mode = '-rw-r--r--';
+      $self->assert($mode eq $res->{'bar.txt'},
+        test_msg("Expected '$mode' for 'bar.txt', got '$res->{'bar.txt'}'"));
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -10632,13 +11231,16 @@ sub vroot_alias_symlink_sftp_realpath {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_alias_file_scp_download {
+sub vroot_alias_var_u_dir {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -10646,13 +11248,14 @@ sub vroot_alias_file_scp_download {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -10671,25 +11274,16 @@ sub vroot_alias_file_scp_download {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $src_file: $!");
-    }
-
-  } else {
-    die("Can't open $src_file: $!");
-  }
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $dst_file = '~/bar.txt';
+  my $sub_dir = File::Spec->rel2abs("$tmpdir/$user");
+  mkpath($sub_dir);
 
-  my $test_file = File::Spec->rel2abs("tmpdir/test.txt");
+  my $src_path = File::Spec->rel2abs("$sub_dir/foo.d");
+  mkpath($src_path);
 
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+  my $src_file = File::Spec->rel2abs($tmpdir) . '/%u/foo.d';
+  my $dst_file = '~/bar.d';
 
   my $config = {
     PidFile => $pid_file,
@@ -10702,13 +11296,6 @@ sub vroot_alias_file_scp_download {
     AuthGroupFile => $auth_group_file,
 
     IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
-
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
@@ -10733,202 +11320,84 @@ sub vroot_alias_file_scp_download {
     die("Can't open pipe: $!");
   }
 
-  require Net::SSH2;
-
   my $ex;
 
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
   # Fork child
   $self->handle_sigchld();
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $ssh2 = Net::SSH2->new();
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->login($user, $passwd);
 
-      sleep(1);
+      my ($resp_code, $resp_msg);
 
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      ($resp_code, $resp_msg) = $client->pwd();
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
-      }
+      my $expected;
 
-      my $res = $ssh2->scp_get('/bar.txt', $test_file);
-      unless ($res) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't download bar.txt from server: [$err_name] ($err_code) $err_str");
-      }
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
 
-      $ssh2->disconnect();
+      $expected = "\"/\" is the current directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
 
-      unless (-f $test_file) {
-        die("$test_file file does not exist as expected");
+      my $conn = $client->list_raw();
+      unless ($conn) {
+        die("Failed to LIST: " . $client->response_code() . " " .
+          $client->response_msg());
       }
-    };
-
-    if ($@) {
-      $ex = $@;
-    }
 
-    $wfh->print("done\n");
-    $wfh->flush();
+      my $buf;
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
 
-  } else {
-    eval { server_wait($config_file, $rfh) };
-    if ($@) { warn($@);
-      exit 1;
-    }
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      my $res = {};
+      my $lines = [split(/\n/, $buf)];
+      foreach my $line (@$lines) {
+        if ($line =~ /^(\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
+          $res->{$2} = $1;
+        }
+      }
 
-    exit 0;
-  }
-
-  # Stop server
-  server_stop($pid_file);
-
-  $self->assert_child_ok($pid);
-
-  if ($ex) {
-    die($ex);
-  }
-
-  unlink($log_file);
-}
-
-sub vroot_alias_file_scp_upload {
-  my $self = shift;
-  my $tmpdir = $self->{tmpdir};
-
-  my $config_file = "$tmpdir/vroot.conf";
-  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
-  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
-
-  my $log_file = File::Spec->rel2abs('tests.log');
-
-  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
-  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
-
-  my $user = 'proftpd';
-  my $passwd = 'test';
-  my $home_dir = File::Spec->rel2abs($tmpdir);
-  my $uid = 500;
-  my $gid = 500;
-
-  # Make sure that, if we're running as root, that the home directory has
-  # permissions/privs set for the account we create
-  if ($< == 0) {
-    unless (chmod(0755, $home_dir)) {
-      die("Can't set perms on $home_dir to 0755: $!");
-    }
-
-    unless (chown($uid, $gid, $home_dir)) {
-      die("Can't set owner of $home_dir to $uid/$gid: $!");
-    }
-  }
-
-  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
-    '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $src_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $src_file: $!");
-    }
-
-  } else {
-    die("Can't open $src_file: $!");
-  }
-
-  my $dst_file = '~/bar.txt';
-
-  my $test_file = File::Spec->rel2abs("tmpdir/test.txt");
-
-  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
-  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
-
-  my $config = {
-    PidFile => $pid_file,
-    ScoreboardFile => $scoreboard_file,
-    SystemLog => $log_file,
-    TraceLog => $log_file,
-    Trace => 'fsio:10',
-
-    AuthUserFile => $auth_user_file,
-    AuthGroupFile => $auth_group_file,
-    AllowOverwrite => 'on',
-
-    IfModules => {
-      'mod_sftp.c' => [
-        "SFTPEngine on",
-        "SFTPLog $log_file",
-        "SFTPHostKey $rsa_host_key",
-        "SFTPHostKey $dsa_host_key",
-      ],
-
-      'mod_vroot.c' => {
-        VRootEngine => 'on',
-        VRootLog => $log_file,
-        DefaultRoot => '~',
-
-        VRootAlias => "$src_file $dst_file",
-      },
-
-      'mod_delay.c' => {
-        DelayEngine => 'off',
-      },
-    },
-  };
-
-  my ($port, $config_user, $config_group) = config_write($config_file, $config);
-
-  # Open pipes, for use between the parent and child processes.  Specifically,
-  # the child will indicate when it's done with its test by writing a message
-  # to the parent.
-  my ($rfh, $wfh);
-  unless (pipe($rfh, $wfh)) {
-    die("Can't open pipe: $!");
-  }
-
-  require Net::SSH2;
-
-  my $ex;
-
-  # Ignore SIGPIPE
-  local $SIG{PIPE} = sub { };
-
-  # Fork child
-  $self->handle_sigchld();
-  defined(my $pid = fork()) or die("Can't fork: $!");
-  if ($pid) {
-    eval {
-      my $ssh2 = Net::SSH2->new();
+      unless (scalar(keys(%$res)) > 0) {
+        die("LIST data unexpectedly empty");
+      }
 
-      sleep(1);
+      $expected = {
+        'vroot.conf' => 1,
+        'vroot.group' => 1,
+        'vroot.passwd' => 1,
+        'vroot.pid' => 1,
+        'vroot.scoreboard' => 1,
+        'vroot.scoreboard.lck' => 1,
+        'bar.d' => 1,
+        'proftpd' => 1,
+      };
 
-      unless ($ssh2->connect('127.0.0.1', $port)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      my $ok = 1;
+      my $mismatch;
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
       }
 
-      unless ($ssh2->auth_password($user, $passwd)) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in LIST data")
       }
 
-      my $res = $ssh2->scp_put($config_file, '/bar.txt');
-      unless ($res) {
-        my ($err_code, $err_name, $err_str) = $ssh2->error();
-        die("Can't upload bar.txt to server: [$err_name] ($err_code) $err_str");
-      }
+      my $mode = 'drwxr-xr-x';
+      $self->assert($mode eq $res->{'bar.d'},
+        test_msg("Expected '$mode' for 'bar.d', got '$res->{'bar.d'}'"));
 
-      $ssh2->disconnect();
+      $client->quit();
     };
 
     if ($@) {
@@ -10953,13 +11422,16 @@ sub vroot_alias_file_scp_upload {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_showsymlinks_on {
+sub vroot_alias_var_u_dir_with_stor_mff {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -10967,19 +11439,18 @@ sub vroot_showsymlinks_on {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
-  my $home_dir = File::Spec->rel2abs("$tmpdir/home");
+  my $group = 'ftpd';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
 
-  mkpath($home_dir);
-
   # Make sure that, if we're running as root, that the home directory has
   # permissions/privs set for the account we create
   if ($< == 0) {
@@ -10994,84 +11465,34 @@ sub vroot_showsymlinks_on {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
-
-  # See:
-  #
-  #  http://forums.proftpd.org/smf/index.php/topic,5207.0.html
-
-  # Create a symlink to a file that is outside of the vroot
-  my $outside_file = File::Spec->rel2abs("$tmpdir/foo.txt");
-  if (open(my $fh, "> $outside_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $outside_file: $!");
-    }
-
-  } else {
-    die("Can't open $outside_file: $!");
-  }
-
-  my $cwd = getcwd();
-
-  unless (chdir($home_dir)) {
-    die("Can't chdir to $home_dir: $!");
-  }
-
-  unless (symlink("../foo.txt", "foo.lnk")) {
-    die("Can't symlink '../foo.txt' to 'foo.lnk': $!");
-  }
-
-  unless (chdir($cwd)) {
-    die("Can't chdir to $cwd: $!");
-  }
-
-  # Now create a symlink which points inside the vroot
-  my $inside_file = File::Spec->rel2abs("$tmpdir/home/bar.txt");
-  if (open(my $fh, "> $inside_file")) {
-    print $fh "Hello, World!\n";
-    unless (close($fh)) {
-      die("Can't write $inside_file: $!");
-    }
-
-  } else {
-    die("Can't open $inside_file: $!");
-  }
-
-  my $cwd = getcwd();
+  auth_group_write($auth_group_file, $group, $gid, $user);
 
-  unless (chdir($home_dir)) {
-    die("Can't chdir to $home_dir: $!");
-  }
+  my $sub_dir = File::Spec->rel2abs("$tmpdir/sub.d");
+  mkpath($sub_dir);
 
-  unless (symlink("./bar.txt", "bar.lnk")) {
-    die("Can't symlink './bar.txt' to 'bar.lnk': $!");
-  }
+  my $src_path = File::Spec->rel2abs("$sub_dir/foo.d");
+  mkpath($src_path);
 
-  unless (chdir($cwd)) {
-    die("Can't chdir to $cwd: $!");
-  }
+  my $src_file = File::Spec->rel2abs($tmpdir) . '/sub.d/foo.d/';
+  my $dst_file = '/%u';
 
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'fsio:10',
+    Trace => 'DEFAULT:10 vroot:20 sql:10 fileperms:20',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
-    # ShowSymlinks is on by default, but explicitly list it here for
-    # completeness
-    ShowSymlinks => 'on',
-
     IfModules => {
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
+        DefaultRoot => '~',
 
-        DefaultRoot => $home_dir,
+        VRootAlias => "$src_file $dst_file",
       },
 
       'mod_delay.c' => {
@@ -11098,9 +11519,20 @@ sub vroot_showsymlinks_on {
   if ($pid) {
     eval {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
-
       $client->login($user, $passwd);
 
+      my ($resp_code, $resp_msg) = $client->pwd();
+
+      my $expected;
+
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "\"/\" is the current directory";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
       my $conn = $client->list_raw();
       unless ($conn) {
         die("Failed to LIST: " . $client->response_code() . " " .
@@ -11108,16 +11540,21 @@ sub vroot_showsymlinks_on {
       }
 
       my $buf;
-      $conn->read($buf, 8192, 30);
+      $conn->read($buf, 8192, 5);
       eval { $conn->close() };
+      sleep(1);
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
       # We have to be careful of the fact that readdir returns directory
       # entries in an unordered fashion.
       my $res = {};
       my $lines = [split(/\n/, $buf)];
       foreach my $line (@$lines) {
-        if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
-          $res->{$1} = 1;
+        if ($line =~ /^(\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
+          $res->{$2} = $1;
         }
       }
 
@@ -11125,8 +11562,15 @@ sub vroot_showsymlinks_on {
         die("LIST data unexpectedly empty");
       }
 
-      my $expected = {
-        'bar.txt' => 1,
+      $expected = {
+        'vroot.conf' => 1,
+        'vroot.group' => 1,
+        'vroot.passwd' => 1,
+        'vroot.pid' => 1,
+        'vroot.scoreboard' => 1,
+        'vroot.scoreboard.lck' => 1,
+        'sub.d' => 1,
+        'proftpd' => 1,
       };
 
       my $ok = 1;
@@ -11143,26 +11587,40 @@ sub vroot_showsymlinks_on {
         die("Unexpected name '$mismatch' appeared in LIST data")
       }
 
-      # Try to download from the symlink
-      my $conn = $client->retr_raw('bar.txt');
+      my $mode = 'drwxr-xr-x';
+      $self->assert($mode eq $res->{'proftpd'},
+        test_msg("Expected '$mode' for 'proftpd', got '$res->{'proftpd'}'"));
+
+      # Now change into the aliased directory, and upload a file there
+      ($resp_code, $resp_msg) = $client->cwd('proftpd');
+      sleep(1);
+
+      my $file = 'test.txt';
+      $conn = $client->stor_raw($file);
       unless ($conn) {
-        die("RETR bar.txt failed: " . $client->response_code() . " " .
+        die("STOR $file failed: " . $client->response_code() . " " .
           $client->response_msg());
       }
 
-      $conn->read($buf, 8192, 30);
+      $buf = "Hello, World!\n";
+      $conn->write($buf, length($buf), 25);
       eval { $conn->close() };
+      sleep(1);
 
-      my $resp_code = $client->response_code();
-      my $resp_msg = $client->response_msg();
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
 
-      $expected = 226;
+      my $facts = 'Modify=20020717210715;Create=20120807064710';
+      ($resp_code, $resp_msg) = $client->mff($facts, $file);
+
+      $expected = 213;
       $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
+        test_msg("Expected response code $expected, got $resp_code"));
 
-      $expected = "Transfer complete";
+      $expected = "$facts $file";
       $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
 
       $client->quit();
     };
@@ -11175,9 +11633,8 @@ sub vroot_showsymlinks_on {
     $wfh->flush();
 
   } else {
-    eval { server_wait($config_file, $rfh) };
-    if ($@) {
-      warn($@);
+    eval { server_wait($config_file, $rfh, 45) };
+    if ($@) { warn($@);
       exit 1;
     }
 
@@ -11190,13 +11647,16 @@ sub vroot_showsymlinks_on {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
   unlink($log_file);
 }
 
-sub vroot_hiddenstores_on_double_dot {
+sub vroot_alias_var_u_symlink_dir {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -11204,13 +11664,14 @@ sub vroot_hiddenstores_on_double_dot {
   my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
   my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
 
-  my $log_file = File::Spec->rel2abs('tests.log');
+  my $log_file = test_get_logfile();
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
 
   my $user = 'proftpd';
   my $passwd = 'test';
+  my $group = 'ftpd';
   my $home_dir = File::Spec->rel2abs($tmpdir);
   my $uid = 500;
   my $gid = 500;
@@ -11229,27 +11690,49 @@ sub vroot_hiddenstores_on_double_dot {
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
     '/bin/bash');
-  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+  auth_group_write($auth_group_file, $group, $gid, $user);
+
+  my $sub_dir = File::Spec->rel2abs("$tmpdir/$user");
+  mkpath($sub_dir);
+
+  my $src_path = File::Spec->rel2abs("$sub_dir/foo.d");
+  mkpath($src_path);
+
+  my $cwd = getcwd();
+
+  unless (chdir($sub_dir)) {
+    die("Can't chdir to $sub_dir: $!");
+  }
+
+  unless (symlink('foo.d', "./foo.lnk")) {
+    die("Can't symlink 'foo.d' to './foo.lnk': $!");
+  }
+
+  unless (chdir($cwd)) {
+    die("Can't chdir to $cwd: $!");
+  }
+
+  my $src_file = File::Spec->rel2abs($tmpdir) . '/%u/foo.lnk';
+  my $dst_file = '/%u.lnk';
 
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'fsio:10',
+    Trace => 'fsio:10 vroot:20',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
-
-    AllowOverwrite => 'on',
-    HiddenStores => 'on',
+    ShowSymlinks => 'off',
 
     IfModules => {
       'mod_vroot.c' => {
         VRootEngine => 'on',
         VRootLog => $log_file,
-
         DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
       },
 
       'mod_delay.c' => {
@@ -11278,28 +11761,73 @@ sub vroot_hiddenstores_on_double_dot {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
       $client->login($user, $passwd);
 
-      # Try to upload a file whose name starts with a period
-      my $conn = $client->stor_raw('.foo');
-      unless ($conn) {
-        die("STOR .foo failed: " . $client->response_code() . ' ' .
-          $client->response_msg());
-      }
+      my ($resp_code, $resp_msg);
 
-      my $buf = "Farewell, cruel world";
-      $conn->write($buf, length($buf), 30);
-      eval { $conn->close() };
+      ($resp_code, $resp_msg) = $client->pwd();
 
-      my $resp_code = $client->response_code();
-      my $resp_msg = $client->response_msg();
+      my $expected;
 
-      my $expected = 226;
+      $expected = 257;
       $self->assert($expected == $resp_code,
         test_msg("Expected $expected, got $resp_code"));
 
-      $expected = 'Transfer complete';
+      $expected = "\"/\" is the current directory";
       $self->assert($expected eq $resp_msg,
         test_msg("Expected '$expected', got '$resp_msg'"));
 
+      my $conn = $client->list_raw();
+      unless ($conn) {
+        die("Failed to LIST: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      my $buf;
+      $conn->read($buf, 8192, 5);
+      eval { $conn->close() };
+
+      # We have to be careful of the fact that readdir returns directory
+      # entries in an unordered fashion.
+      my $res = {};
+      my $lines = [split(/\n/, $buf)];
+      foreach my $line (@$lines) {
+        if ($line =~ /^(\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
+          $res->{$2} = $1;
+        }
+      }
+
+      unless (scalar(keys(%$res)) > 0) {
+        die("LIST data unexpectedly empty");
+      }
+
+      $expected = {
+        'vroot.conf' => 1,
+        'vroot.group' => 1,
+        'vroot.passwd' => 1,
+        'vroot.pid' => 1,
+        'vroot.scoreboard' => 1,
+        'vroot.scoreboard.lck' => 1,
+        'proftpd.lnk' => 1,
+        'proftpd' => 1,
+      };
+
+      my $ok = 1;
+      my $mismatch;
+      foreach my $name (keys(%$res)) {
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
+      }
+
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in LIST data")
+      }
+
+      my $mode = 'drwxr-xr-x';
+      $self->assert($mode eq $res->{'proftpd.lnk'},
+        test_msg("Expected '$mode' for 'proftpd.lnk', got '$res->{'proftpd.lnk'}'"));
+
       $client->quit();
     };
 
@@ -11325,6 +11853,9 @@ sub vroot_hiddenstores_on_double_dot {
   $self->assert_child_ok($pid);
 
   if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
     die($ex);
   }
 
diff --git a/t/lib/ProFTPD/Tests/Modules/mod_vroot/sftp.pm b/t/lib/ProFTPD/Tests/Modules/mod_vroot/sftp.pm
new file mode 100644
index 0000000..18086aa
--- /dev/null
+++ b/t/lib/ProFTPD/Tests/Modules/mod_vroot/sftp.pm
@@ -0,0 +1,2603 @@
+package ProFTPD::Tests::Modules::mod_vroot::sftp;
+
+use lib qw(t/lib);
+use base qw(ProFTPD::TestSuite::Child);
+use strict;
+
+use Cwd;
+use Digest::MD5;
+use File::Path qw(mkpath rmtree);
+use File::Spec;
+use IO::Handle;
+use POSIX qw(:fcntl_h);
+
+use ProFTPD::TestSuite::FTP;
+use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
+
+$| = 1;
+
+my $order = 0;
+
+my $TESTS = {
+  vroot_alias_file_sftp_read => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_file_sftp_write_no_overwrite => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_file_sftp_write => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_file_sftp_stat => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_file_sftp_lstat => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_file_sftp_realpath => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_file_sftp_remove => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_dir_sftp_readdir => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_dir_sftp_rmdir => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_symlink_sftp_stat => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_symlink_sftp_lstat => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_symlink_sftp_realpath => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp sftp)],
+  },
+
+  vroot_alias_file_scp_download => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp scp)],
+  },
+
+  vroot_alias_file_scp_upload => {
+    order => ++$order,
+    test_class => [qw(forking mod_sftp scp)],
+  },
+
+};
+
+sub new {
+  return shift()->SUPER::new(@_);
+}
+
+sub list_tests {
+  return testsuite_get_runnable_tests($TESTS);
+
+#    XXX test file aliases where the alias includes directories which do not
+#    exist.  Should we allow traversal of these kinds of aliases (if so, what
+#    real directory do we use for perms, ownership?  What would a CWD into
+#    such a path component mean?), or only allow retrieval/storage to that
+#    alias but not traversal?
+}
+
+sub set_up {
+  my $self = shift;
+  $self->SUPER::set_up(@_);
+
+  # Make sure that mod_sftp does not complain about permissions on the hostkey
+  # files.
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  unless (chmod(0400, $rsa_host_key, $dsa_host_key)) {
+    die("Can't set perms on $rsa_host_key, $dsa_host_key: $!");
+  }
+}
+
+sub vroot_alias_file_sftp_read {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $fh = $sftp->open('bar.txt', O_RDONLY);
+      unless ($fh) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("Can't open bar.txt: [$err_name] ($err_code)");
+      }
+
+      my $buf;
+      my $size = 0;
+
+      my $res = $fh->read($buf, 8192, 30);
+      while ($res) {
+        $size += $res;
+
+        $res = $fh->read($buf, 8192, 30);
+      }
+
+      # To issue the FXP_CLOSE, we have to explicit destroy the filehandle
+      $fh = undef;
+
+      my $expected = 14;
+      $self->assert($expected == $size,
+        test_msg("Expected $expected, got $size"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_sftp_write_no_overwrite {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $fh = $sftp->open('bar.txt', O_WRONLY|O_TRUNC, 0644);
+      if ($fh) {
+        die("OPEN bar.txt succeeded unexpectedly");
+      }
+
+      my ($err_code, $err_name) = $sftp->error();
+
+      my $expected = 'SSH_FX_PERMISSION_DENIED';
+      $self->assert($expected eq $err_name,
+        test_msg("Expected '$expected', got '$err_name'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_sftp_write {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    AllowOverwrite => 'on',
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $fh = $sftp->open('bar.txt', O_WRONLY|O_TRUNC, 0644);
+      unless ($fh) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("Can't open bar.txt: [$err_name] ($err_code)");
+      }
+
+      my $count = 20;
+      for (my $i = 0; $i < $count; $i++) {
+        print $fh "ABCD" x 4096;
+      }
+
+      # To issue the FXP_CLOSE, we have to explicit destroy the filehandle
+      $fh = undef;
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_sftp_stat {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $attrs = $sftp->stat('bar.txt', 1);
+      unless ($attrs) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
+      }
+
+      my $expected = 14;
+      my $file_size = $attrs->{size};
+      $self->assert($expected == $file_size,
+        test_msg("Expected $expected, got $file_size"));
+
+      $expected = $<;
+      my $file_uid = $attrs->{uid};
+      $self->assert($expected == $file_uid,
+        test_msg("Expected '$expected', got '$file_uid'"));
+
+      $expected = $(;
+      my $file_gid = $attrs->{gid};
+      $self->assert($expected == $file_gid,
+        test_msg("Expected '$expected', got '$file_gid'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_sftp_lstat {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $attrs = $sftp->stat('bar.txt', 0);
+      unless ($attrs) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
+      }
+
+      my $expected = 14;
+      my $file_size = $attrs->{size};
+      $self->assert($expected == $file_size,
+        test_msg("Expected $expected, got $file_size"));
+
+      $expected = $<;
+      my $file_uid = $attrs->{uid};
+      $self->assert($expected == $file_uid,
+        test_msg("Expected '$expected', got '$file_uid'"));
+
+      $expected = $(;
+      my $file_gid = $attrs->{gid};
+      $self->assert($expected == $file_gid,
+        test_msg("Expected '$expected', got '$file_gid'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_sftp_realpath {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $real_path = $sftp->realpath('bar.txt');
+      unless ($real_path) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("Can't get real path for 'bar.txt': [$err_name] ($err_code)");
+      }
+
+      my $expected;
+
+      $expected = '/bar.txt';
+      $self->assert($expected eq $real_path,
+        test_msg("Expected '$expected', got '$real_path'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_sftp_remove {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $res = $sftp->unlink('bar.txt');
+      if ($res) {
+        die("REMOVE bar.txt succeeded unexpectedly");
+      }
+
+      my ($err_code, $err_name) = $sftp->error();
+
+      my $expected = 'SSH_FX_PERMISSION_DENIED';
+      $self->assert($expected eq $err_name,
+        test_msg("Expected '$expected', got '$err_name'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_dir_sftp_readdir {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
+  mkpath($src_dir);
+
+  my $dst_dir = '~/bar.d';
+
+  my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
+  if (open(my $fh, "> $test_file1")) {
+    close($fh);
+
+  } else {
+    die("Can't open $test_file1: $!");
+  }
+
+  my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
+  if (open(my $fh, "> $test_file2")) {
+    close($fh);
+
+  } else {
+    die("Can't open $test_file2: $!");
+  }
+
+  my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
+  if (open(my $fh, "> $test_file3")) {
+    close($fh);
+
+  } else {
+    die("Can't open $test_file3: $!");
+  }
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_dir $dst_dir",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $dir = $sftp->opendir('bar.d');
+      unless ($dir) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("Can't open directory 'bar.d': [$err_name] ($err_code)");
+      }
+
+      my $res = {};
+
+      my $file = $dir->read();
+      while ($file) {
+        $res->{$file->{name}} = $file;
+        $file = $dir->read();
+      }
+
+      my $expected = {
+        '.' => 1,
+        '..' => 1,
+        'a.txt' => 1,
+        'b.txt' => 1,
+        'c.txt' => 1,
+      };
+
+      # To issue the FXP_CLOSE, we have to explicit destroy the dirhandle
+      $dir = undef;
+
+      $ssh2->disconnect();
+
+      my $ok = 1;
+      my $mismatch;
+
+      my $seen = [];
+      foreach my $name (keys(%$res)) {
+        push(@$seen, $name);
+
+        unless (defined($expected->{$name})) {
+          $mismatch = $name;
+          $ok = 0;
+          last;
+        }
+      }
+
+      unless ($ok) {
+        die("Unexpected name '$mismatch' appeared in READDIR data")
+      }
+
+      # Now remove from $expected all of the paths we saw; if there are
+      # any entries remaining in $expected, something went wrong.
+      foreach my $name (@$seen) {
+        delete($expected->{$name});
+      }
+
+      my $remaining = scalar(keys(%$expected));
+      $self->assert(0 == $remaining,
+        test_msg("Expected 0, got $remaining"));
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_dir_sftp_rmdir {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
+  mkpath($src_dir);
+
+  my $dst_dir = '~/bar.d';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_dir $dst_dir",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $res = $sftp->rmdir('/bar.d');
+      if ($res) {
+        die("RMDIR bar.d succeeded unexpectedly");
+      }
+
+      my ($err_code, $err_name) = $sftp->error();
+
+      my $expected = 'SSH_FX_PERMISSION_DENIED';
+      $self->assert($expected eq $err_name,
+        test_msg("Expected '$expected', got '$err_name'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_symlink_sftp_stat {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $cwd = getcwd();
+
+  unless (chdir($home_dir)) {
+    die("Can't chdir to $home_dir: $!");
+  }
+
+  unless (symlink("./foo.txt", "foo.lnk")) {
+    die("Can't symlink './foo.txt' to 'foo.lnk': $!");
+  }
+
+  unless (chdir($cwd)) {
+    die("Can't chdir to $cwd: $!");
+  }
+
+  my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
+  my $dst_file = '~/bar.lnk';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        VRootOptions => 'allowSymlinks',
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_symlink $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $attrs = $sftp->stat('bar.lnk', 1);
+      unless ($attrs) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
+      }
+
+      my $expected = 14;
+      my $file_size = $attrs->{size};
+      $self->assert($expected == $file_size,
+        test_msg("Expected $expected, got $file_size"));
+
+      $expected = $<;
+      my $file_uid = $attrs->{uid};
+      $self->assert($expected == $file_uid,
+        test_msg("Expected '$expected', got '$file_uid'"));
+
+      $expected = $(;
+      my $file_gid = $attrs->{gid};
+      $self->assert($expected == $file_gid,
+        test_msg("Expected '$expected', got '$file_gid'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_symlink_sftp_lstat {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $cwd = getcwd();
+
+  unless (chdir($home_dir)) {
+    die("Can't chdir to $home_dir: $!");
+  }
+
+  unless (symlink("./foo.txt", "foo.lnk")) {
+    die("Can't symlink './foo.txt' to 'foo.lnk': $!");
+  }
+
+  unless (chdir($cwd)) {
+    die("Can't chdir to $cwd: $!");
+  }
+
+  my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
+  my $dst_file = '~/bar.lnk';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        VRootOptions => 'allowSymlinks',
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_symlink $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $attrs = $sftp->stat('bar.lnk', 0);
+      unless ($attrs) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
+      }
+
+      my $expected = 14;
+      my $file_size = $attrs->{size};
+      $self->assert($expected == $file_size,
+        test_msg("Expected $expected, got $file_size"));
+
+      $expected = $<;
+      my $file_uid = $attrs->{uid};
+      $self->assert($expected == $file_uid,
+        test_msg("Expected '$expected', got '$file_uid'"));
+
+      $expected = $(;
+      my $file_gid = $attrs->{gid};
+      $self->assert($expected == $file_gid,
+        test_msg("Expected '$expected', got '$file_gid'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_symlink_sftp_realpath {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $cwd = getcwd();
+
+  unless (chdir($home_dir)) {
+    die("Can't chdir to $home_dir: $!");
+  }
+
+  unless (symlink("./foo.txt", "foo.lnk")) {
+    die("Can't symlink './foo.txt' to 'foo.lnk': $!");
+  }
+
+  unless (chdir($cwd)) {
+    die("Can't chdir to $cwd: $!");
+  }
+
+  my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
+  my $dst_file = '~/bar.lnk';
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        VRootOptions => 'allowSymlinks',
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_symlink $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $sftp = $ssh2->sftp();
+      unless ($sftp) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $real_path = $sftp->realpath('bar.lnk');
+      unless ($real_path) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("FXP_REALPATH bar.lnk failed: [$err_name] ($err_code)");
+      }
+
+      my $expected = '/bar.lnk';
+      $self->assert($expected eq $real_path,
+        test_msg("Expected '$expected', got '$real_path'"));
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_scp_download {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $test_file = File::Spec->rel2abs("tmpdir/test.txt");
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $res = $ssh2->scp_get('/bar.txt', $test_file);
+      unless ($res) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't download bar.txt from server: [$err_name] ($err_code) $err_str");
+      }
+
+      $ssh2->disconnect();
+
+      unless (-f $test_file) {
+        die("$test_file file does not exist as expected");
+      }
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub vroot_alias_file_scp_upload {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/vroot.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  # Make sure that, if we're running as root, that the home directory has
+  # permissions/privs set for the account we create
+  if ($< == 0) {
+    unless (chmod(0755, $home_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir)) {
+      die("Can't set owner of $home_dir to $uid/$gid: $!");
+    }
+  }
+
+  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
+    '/bin/bash');
+  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "Hello, World!\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  my $dst_file = '~/bar.txt';
+
+  my $test_file = File::Spec->rel2abs("tmpdir/test.txt");
+
+  my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+  my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'fsio:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    AllowOverwrite => 'on',
+
+    IfModules => {
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+
+      'mod_vroot.c' => {
+        VRootEngine => 'on',
+        VRootLog => $log_file,
+        DefaultRoot => '~',
+
+        VRootAlias => "$src_file $dst_file",
+      },
+
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  # Open pipes, for use between the parent and child processes.  Specifically,
+  # the child will indicate when it's done with its test by writing a message
+  # to the parent.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # Ignore SIGPIPE
+  local $SIG{PIPE} = sub { };
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      my $ssh2 = Net::SSH2->new();
+
+      sleep(1);
+
+      unless ($ssh2->connect('127.0.0.1', $port)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      unless ($ssh2->auth_password($user, $passwd)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      my $res = $ssh2->scp_put($config_file, '/bar.txt');
+      unless ($res) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't upload bar.txt to server: [$err_name] ($err_code) $err_str");
+      }
+
+      $ssh2->disconnect();
+    };
+
+    if ($@) {
+      $ex = $@;
+    }
+
+    $wfh->print("done\n");
+    $wfh->flush();
+
+  } else {
+    eval { server_wait($config_file, $rfh) };
+    if ($@) { warn($@);
+      exit 1;
+    }
+
+    exit 0;
+  }
+
+  # Stop server
+  server_stop($pid_file);
+
+  $self->assert_child_ok($pid);
+
+  if ($ex) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+1;
diff --git a/t/modules/mod_vroot.t b/t/modules/mod_vroot/sftp.t
similarity index 70%
copy from t/modules/mod_vroot.t
copy to t/modules/mod_vroot/sftp.t
index ce6599d..3c8cc91 100644
--- a/t/modules/mod_vroot.t
+++ b/t/modules/mod_vroot/sftp.t
@@ -8,4 +8,4 @@ use Test::Unit::HarnessUnit;
 $| = 1;
 
 my $r = Test::Unit::HarnessUnit->new();
-$r->start("ProFTPD::Tests::Modules::mod_vroot");
+$r->start("ProFTPD::Tests::Modules::mod_vroot::sftp");

-- 
proftpd module for mod_vroot



More information about the Pkg-proftpd-maintainers mailing list