[proftpd-dfsg] 01/03: Imported Upstream version 1.3.5

Francesco Lovergine frankie at moszumanska.debian.org
Fri Sep 5 09:01:46 UTC 2014


This is an automated email from the git hooks/post-receive script.

frankie pushed a commit to branch master
in repository proftpd-dfsg.

commit f80b0f184565169aabab16d1db517de39f62f5b5
Author: Francesco Paolo Lovergine <frankie at debian.org>
Date:   Fri Jun 13 11:51:33 2014 +0200

    Imported Upstream version 1.3.5
---
 ChangeLog                                          | 450 +++++++++++++++-
 NEWS                                               |  24 +-
 RELEASE_NOTES                                      |  56 +-
 contrib/mod_ban.c                                  |  85 ++-
 contrib/mod_exec.c                                 |  11 +-
 contrib/mod_ifsession.c                            |   9 +-
 contrib/mod_sftp/auth-kbdint.c                     |  26 +-
 contrib/mod_sftp/auth-password.c                   |  27 +-
 contrib/mod_sftp/auth.c                            |   7 +-
 contrib/mod_sftp/cipher.c                          |  32 +-
 contrib/mod_sftp/mod_sftp.c                        |  16 +-
 contrib/mod_sftp/mod_sftp.h.in                     |   5 +-
 contrib/mod_sql.c                                  |  55 +-
 contrib/mod_sql_passwd.c                           | 235 ++++++--
 contrib/mod_tls.c                                  | 247 ++++++---
 contrib/mod_wrap2/mod_wrap2.c                      |  70 ++-
 doc/contrib/mod_ban.html                           |  11 +-
 doc/contrib/mod_sftp.html                          |  57 +-
 doc/contrib/mod_site_misc.html                     |   8 +-
 doc/contrib/mod_sql_passwd.html                    |  16 +-
 doc/howto/Chroot.html                              |   6 +-
 doc/howto/TLS.html                                 |   6 +-
 doc/modules/mod_core.html                          | 105 +++-
 doc/modules/mod_rlimit.html                        |  48 +-
 include/fsio.h                                     |   8 +-
 include/version.h                                  |   6 +-
 locale/files.txt                                   |   2 +
 modules/mod_core.c                                 |  32 +-
 modules/mod_delay.c                                |  15 +-
 modules/mod_facl.c                                 |   9 +-
 modules/mod_rlimit.c                               |  87 ++-
 modules/mod_xfer.c                                 |  34 +-
 proftpd.spec                                       |   6 +-
 src/fsio.c                                         | 277 +++++++++-
 src/stash.c                                        |   6 +-
 tests/t/config/passiveports.t                      |  11 +
 tests/t/config/rlimitchroot.t                      |  11 +
 tests/t/lib/ProFTPD/Tests/Commands/MKD.pm          | 141 +++++
 .../ProFTPD/Tests/Config/DeleteAbortedStores.pm    | 304 +++++++++++
 .../{DeleteAbortedStores.pm => PassivePorts.pm}    | 225 ++++----
 tests/t/lib/ProFTPD/Tests/Config/RLimitChroot.pm   | 597 +++++++++++++++++++++
 tests/t/lib/ProFTPD/Tests/Config/SocketOptions.pm  | 144 +++++
 tests/t/lib/ProFTPD/Tests/Logging/ExtendedLog.pm   |  32 +-
 tests/t/lib/ProFTPD/Tests/Modules/mod_ban.pm       | 372 +++++++++++++
 tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm   | 280 +++++++++-
 tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm      | 402 +++++++++++++-
 .../lib/ProFTPD/Tests/Modules/mod_sftp/rewrite.pm  | 176 ++++++
 .../t/lib/ProFTPD/Tests/Modules/mod_sql_passwd.pm  | 201 +++++++
 .../t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm  | 199 +++++++
 tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm       |  91 +++-
 .../t/lib/ProFTPD/Tests/Modules/mod_wrap2_file.pm  | 196 +++++++
 tests/tests.pl                                     |   2 +
 52 files changed, 5041 insertions(+), 437 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 05fe4b8..e353cf8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,445 @@
+2014-05-15 08:53  castaglia
+
+	* contrib/dist/rpm/proftpd.spec, include/version.h:
+	  Preparing files for release.
+
+2014-05-15 08:51  castaglia
+
+	* locale/files.txt:
+	  Updated list of files for localization.
+
+2014-05-09 07:52  castaglia
+
+	* RELEASE_NOTES:
+	  Fleshing out release notes for upcoming release.
+
+2014-05-05 09:15  castaglia
+
+	* contrib/mod_sql_passwd.c:
+	  Use sql_log() instead of pr_log_debug(), so that the logging is
+	  consistently done in the module.  There may still be a few
+	  outliers.
+
+2014-05-04 16:15  castaglia
+
+	* doc/contrib/mod_sql_passwd.html:
+	  Updated SQLPasswordPBKDF2 docs in light of Bug#4052.
+
+2014-05-04 16:11  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_sql_passwd.pm:
+	  Added regression test for Bug#4052.
+
+2014-05-04 16:11  castaglia
+
+	* NEWS, contrib/mod_sql_passwd.c:
+	  Bug#4052 - Enhance SQLPasswordPBKDF2 to support per-user query
+	  for settings.
+
+2014-05-04 12:49  castaglia
+
+	* doc/contrib/mod_site_misc.html:
+	  Emphasize that both acces and mod times are set by SITE UTIME.
+
+2014-05-04 12:27  castaglia
+
+	* NEWS, modules/mod_facl.c:
+	  Backport of fix for Bug#4044 to 1.3.4 branch.
+
+2014-05-04 12:26  castaglia
+
+	* NEWS, modules/mod_facl.c:
+	  Bug#4044 - mod_facl prevents a normal SIGHUP reload.
+
+2014-05-03 15:19  castaglia
+
+	* NEWS, modules/mod_core.c:
+	  Backport of fix for Bug#4042 to 1.3.4 branch.
+
+2014-05-03 15:18  castaglia
+
+	* NEWS, modules/mod_core.c:
+	  Bug#4042 - MIC command between RNFR and RNTO should not be
+	  rejected.
+
+2014-05-02 14:13  castaglia
+
+	* NEWS, contrib/mod_exec.c:
+	  Bug#4049 - mod_exec should include supplemental groups when
+	  running commands as logged-in user.
+
+2014-04-30 10:33  castaglia
+
+	* contrib/mod_ban.c:
+	  As a follow-on to the fix for Bug#4048, add some "headroom" to
+	  the SHM segment, to allow for multiple racing/competing processes
+	  incrementing indices.
+
+2014-04-30 08:58  castaglia
+
+	* NEWS, contrib/mod_ban.c:
+	  Backport of fix for Bug#4048 to 1.3.4 branch.
+
+2014-04-30 08:56  castaglia
+
+	* NEWS, contrib/mod_ban.c:
+	  Bug#4048 - Race condition in mod_ban can lead to segfault of all
+	  new connections.
+
+2014-04-28 10:12  castaglia
+
+	* modules/mod_xfer.c:
+	  Backporting tweaks from Bug#4046 in trunk to 1.3.4 branch.
+
+2014-04-28 10:11  castaglia
+
+	* modules/mod_xfer.c:
+	  Slightly better fix for Bug#4046; less chance of overflowing the
+	  off_t datatype on filesystems with lots of free space.
+
+2014-04-28 10:06  castaglia
+
+	* NEWS, modules/mod_xfer.c:
+	  Backport of fix for Bug#4046 to 1.3.4 branch.
+
+2014-04-28 10:05  castaglia
+
+	* NEWS, modules/mod_xfer.c:
+	  Bug#4046 - ALLO command failed because of bad size check.
+
+2014-03-26 11:22  castaglia
+
+	* doc/howto/TLS.html:
+	  Typo.
+
+2014-03-19 08:35  castaglia
+
+	* contrib/mod_tls.c:
+	  Bug#4039 - Fix compile error in mod_tls, when built using newer
+	  OpenSSL versions.  This is a regression caused by Bug#4029.
+
+2014-03-13 11:06  castaglia
+
+	* doc/howto/Chroot.html:
+	  Fix broken link, per Bug#4038.
+
+2014-03-08 09:46  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Config/DeleteAbortedStores.pm:
+	  Adding some regression tests for the DeleteAbortedStores
+	  directive, for when timeouts kick in and kill the session.
+
+2014-03-08 09:44  castaglia
+
+	* modules/mod_xfer.c:
+	  If a session dies in the middle of a data transfer, make sure
+	  that the path in the fake cmd_rec is correct.  It was empty,
+	  which led to some misleading logging.
+
+2014-03-08 08:58  castaglia
+
+	* NEWS, contrib/mod_tls.c:
+	  Bug#4024 - TLS 1.1/1.2 configurable, but not properly
+	  implemented.
+
+2014-03-05 09:43  castaglia
+
+	* contrib/mod_tls.c:
+	  When seeing OpenSSL's PRNG manually, use gettimeofday(2) rather
+	  than time(3).
+
+2014-03-05 09:43  castaglia
+
+	* contrib/mod_tls.c:
+	  When seeding OpenSSL's PRNG manually, use gettimeofday(2) instead
+	  of time(3).
+
+2014-03-03 23:56  castaglia
+
+	* NEWS, contrib/mod_sftp/auth.c:
+	  Backport of fix for Bug#4034 to 1.3.4 branch.
+
+2014-03-03 23:54  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm:
+	  Adding regression tests for Bug#4034.
+
+2014-03-03 23:54  castaglia
+
+	* NEWS, contrib/mod_sftp/auth.c:
+	  Bug#4034 - SSH publickey authentication fails with
+	  "MaxLoginAttempts 1".
+
+2014-03-02 21:40  castaglia
+
+	* doc/contrib/mod_sftp.html:
+	  Add mod_sftp FAQ about pointing an SSH client (*not* SFTP or SCP)
+	  at mod_sftp, and why that connection would fail.
+
+2014-03-02 14:11  castaglia
+
+	* doc/contrib/mod_sftp.html:
+	  Document the AllowInsecureLogin SFTPOption.
+
+2014-03-02 14:05  castaglia
+
+	* contrib/mod_sftp/: auth-kbdint.c, auth-password.c, mod_sftp.c,
+	  mod_sftp.h.in:
+	  Add a new SFTPOption, called 'AllowInsecureLogin'.  This is
+	  needed for sites which wish to use/allow SSH2 logins that use the
+	  'none' cipher or digest (hopefully just for performance testing).
+
+2014-03-02 08:55  castaglia
+
+	* NEWS, contrib/mod_sftp/mod_sftp.c:
+	  Backport of fix for Bug#4032 to 1.3.4 branch.
+
+2014-03-02 08:49  castaglia
+
+	* NEWS, contrib/mod_sftp/mod_sftp.c:
+	  Bug#4032 - Restarting proftpd with mod_sftp fails due to
+	  permissions on SFTPHostKey file.
+
+2014-03-01 22:09  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm:
+	  Fix the mod_sftp regression tests which SHOULD have caught
+	  Bug#4033.
+
+2014-03-01 22:07  castaglia
+
+	* NEWS, contrib/mod_sftp/cipher.c:
+	  Bug#4033 - mod_sftp fails to create SSH2 session using 'none'
+	  cipher.
+
+2014-03-01 09:34  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_wrap2_file.pm:
+	  Added regression test for Bug#3938.
+
+2014-03-01 09:34  castaglia
+
+	* NEWS, contrib/mod_wrap2/mod_wrap2.c:
+	  Bug#3938 - mod_wrap2 uses reverse DNS regardless "UseReverseDNS
+	  off".
+
+2014-02-28 10:11  castaglia
+
+	* tests/: tests.pl, t/config/passiveports.t,
+	  t/lib/ProFTPD/Tests/Config/PassivePorts.pm:
+	  Added regression tests for the PassivePorts directive in various
+	  configuration contexts, i.e. "server config", <Global>, and
+	  <VirtualHost>.
+
+2014-02-28 07:18  castaglia
+
+	* NEWS, contrib/mod_tls.c:
+	  Bug#4029 - TLSOptions EnableDiags logs "unknown version (771)"
+	  for TLS 1.1/1.2 connections.
+
+2014-02-23 10:29  castaglia
+
+	* contrib/mod_ban.c:
+	  Minor name/string change; no functional change.
+
+2014-02-23 10:07  castaglia
+
+	* contrib/mod_ban.c, tests/t/lib/ProFTPD/Tests/Modules/mod_ban.pm:
+	  Allow for custom, user-specified event names in the BanOnEvent
+	  directive.  This will make it easier to support rules for events
+	  other than the ones that are specially handled, and ease the need
+	  to add special handling in the future.
+
+2014-02-23 09:11  castaglia
+
+	* doc/contrib/mod_ban.html:
+	  Added support for RootLogin bans.
+
+2014-02-23 09:10  castaglia
+
+	* contrib/mod_ban.c, tests/t/lib/ProFTPD/Tests/Modules/mod_ban.pm:
+	  Make it possible to ban clients which attempt to login as root.
+
+2014-02-20 22:06  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm:
+	  Adding reproduction recipe/regression test for Bug#4026.
+
+2014-02-19 14:53  castaglia
+
+	* doc/contrib/mod_sftp.html:
+	  Add "Cipher Implementations" section to "Known Client Issues",
+	  noting that SBB clients don't handle the blowfish-ctr cipher.
+
+2014-02-18 11:28  castaglia
+
+	* doc/contrib/mod_sftp.html:
+	  Fix typo/misnamed cipher in list.
+
+2014-02-15 10:54  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm:
+	  Adding regression test for Bug#4025.
+
+2014-02-15 10:53  castaglia
+
+	* NEWS, contrib/mod_ifsession.c:
+	  Bug#4025 - <IfClass> sections do not work for multiple SQLLog
+	  directives.
+
+2014-02-15 00:31  castaglia
+
+	* contrib/mod_sql.c:
+	  If SQLEngine is not configured, then DO emit the "no SQLAuthTypes
+	  configured" log message if necessary.  Previous commit would not
+	  have handled the case where SQLEngine was not explicitly set.
+
+2014-02-14 21:22  castaglia
+
+	* contrib/mod_sql.c:
+	  If "SQLEngine log" is configured, then do NOT log a message
+	  saying "error: no SQLAuthTypes configured".  Stylistic nits
+	  addressed while there.
+
+2014-02-11 07:21  castaglia
+
+	* NEWS, src/fsio.c:
+	  Backport of fix for Bug#4022 to 1.3.4 branch.
+
+2014-02-11 07:17  castaglia
+
+	* NEWS, src/fsio.c:
+	  Bug#4022 - "Directory not empty" error when creating directory is
+	  misleading.
+
+2014-02-11 07:17  castaglia
+
+	* src/stash.c:
+	  Fix minor compiler warning when --enable-devel is used.
+
+2014-02-09 12:42  castaglia
+
+	* modules/mod_delay.c:
+	  The regression tests caught a small regression in mod_delay,
+	  where the fix for Bug#3622 was reverted partly by the fixes for
+	  Bug#3970.
+
+2014-02-09 11:41  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Commands/MKD.pm:
+	  Another MKD test, making sure that using a preceding CWD doesn't
+	  gum up the works.  Also makes for a nice testbed for
+	  pr_fsio_smkdir() issues.
+
+2014-02-09 11:34  castaglia
+
+	* src/fsio.c:
+	  Add more logging of error conditions in the pr_fsio_smkdir()
+	  function, for better diagnosing some reported MKD errors.
+
+2014-02-09 10:42  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm:
+	  Yet another data point/reproduction recipe for Bug#4017.
+
+2014-02-08 08:45  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Config/SocketOptions.pm:
+	  Add basic test for SocketOption keepalive.
+
+2014-02-04 16:17  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm:
+	  Additional regression/reproduction recipe for Bug#4017, this time
+	  using PCRE regexes.
+
+2014-02-03 11:31  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm:
+	  Regression test/reproduction recipe for Bug#4017.
+
+2014-02-01 13:43  castaglia
+
+	* contrib/mod_tls.c:
+	  Update mod_tls to start using the pr_netio_t notes table for
+	  stashing/retrieving its SSL objects, rather than the strm_data
+	  void pointer.  The table is more flexible, and allows for other
+	  code to use the keys as needed.
+
+2014-01-31 09:29  castaglia
+
+	* modules/mod_rlimit.c,
+	  tests/t/lib/ProFTPD/Tests/Config/RLimitChroot.pm:
+	  Allow for per-user (via <IfUser>) setting of the RLimitChroot
+	  directive.
+
+2014-01-31 09:08  castaglia
+
+	* doc/modules/mod_rlimit.html:
+	  Add description for new RLimitChroot directive to mod_rlimit
+	  docs.
+
+2014-01-31 09:06  castaglia
+
+	* src/fsio.c:
+	  Match URLs for "Roaring Beast" reports.
+
+2014-01-31 08:53  castaglia
+
+	* tests/: tests.pl, t/config/rlimitchroot.t,
+	  t/lib/ProFTPD/Tests/Config/RLimitChroot.pm:
+	  Adding regression tests for the new RLimitChroot directive.
+
+2014-01-31 08:52  castaglia
+
+	* NEWS, RELEASE_NOTES, include/fsio.h, modules/mod_rlimit.c,
+	  src/fsio.c:
+	  Bug#4018 - Implement checks for sensitive directories when
+	  chrooted.
+
+2014-01-30 08:50  castaglia
+
+	* doc/modules/mod_core.html:
+	  Add description of SocketBindTight directive to mod_core docs,
+	  and include FAQ about using it.
+
+2014-01-29 11:50  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/rewrite.pm:
+	  Adding regression/reproduction recipe for Bug#4017.
+
+2014-01-28 20:32  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Logging/ExtendedLog.pm:
+	  Fix broken (only on MacOSX) test.
+
+2014-01-28 19:49  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Logging/ExtendedLog.pm:
+	  Minor tweaks while investigating Bug#3638.
+
+2014-01-28 18:16  castaglia
+
+	* tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm:
+	  Updated mod_tls tests to work around bug in Net-FTPSSL version
+	  0.21's quot() implementation.
+
+2014-01-28 16:07  castaglia
+
+	* contrib/mod_sftp/crypto.c:
+	  Make sure that mod_sftp can compile when using an older OpenSSL
+	  version.  Tested against openssl-0.9.6b.
+
+2014-01-28 10:08  castaglia
+
+	* include/version.h:
+	  Updating version for CVS.
+
+2014-01-28 09:42  castaglia
+
+	* ChangeLog:
+	  Updated ChangeLog.
+
 2014-01-28 09:40  castaglia
 
 	* NEWS, contrib/dist/rpm/proftpd.spec, include/version.h:
@@ -45521,7 +45963,7 @@
 2003-06-11 19:08  castaglia
 
 	* src/: inet.c, modules.c:
-	  Missing $Id: ChangeLog,v 1.153 2014/01/28 17:42:07 castaglia Exp $ keyword.
+	  Missing $Id: ChangeLog,v 1.154 2014/05/15 16:20:23 castaglia Exp $ keyword.
 
 2003-06-11 15:45  castaglia
 
@@ -46700,7 +47142,7 @@
 
 	* contrib/: mod_sql.c, mod_sql.h, mod_sql_mysql.c,
 	  mod_sql_postgres.c:
-	  Adding $Id: ChangeLog,v 1.153 2014/01/28 17:42:07 castaglia Exp $ tags to the mod_sql files.
+	  Adding $Id: ChangeLog,v 1.154 2014/05/15 16:20:23 castaglia Exp $ tags to the mod_sql files.
 
 2003-03-14 07:54  castaglia
 
@@ -47219,12 +47661,12 @@
 
 	* src/netio.c, src/pool.c, src/sets.c, include/ident.h,
 	  include/timers.h:
-	  Adding more $Id: ChangeLog,v 1.153 2014/01/28 17:42:07 castaglia Exp $ keywords.
+	  Adding more $Id: ChangeLog,v 1.154 2014/05/15 16:20:23 castaglia Exp $ keywords.
 
 2003-02-11 23:34  castaglia
 
 	* src/feat.c:
-	  Added $Id: ChangeLog,v 1.153 2014/01/28 17:42:07 castaglia Exp $ keyword.
+	  Added $Id: ChangeLog,v 1.154 2014/05/15 16:20:23 castaglia Exp $ keyword.
 
 2003-02-10 15:34  castaglia
 
diff --git a/NEWS b/NEWS
index 90c0ee9..e345abe 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-$Id: NEWS,v 1.1581 2014/01/28 17:40:46 castaglia Exp $
+$Id: NEWS,v 1.1597 2014/05/15 16:24:13 castaglia Exp $
 
 -----------------------------------------------------------------------------
   More details on the bugs listed below can be found by using the bug number
@@ -9,6 +9,28 @@ $Id: NEWS,v 1.1581 2014/01/28 17:40:46 castaglia Exp $
   where `N' is the bug number.
 -----------------------------------------------------------------------------
 
+1.3.5 - Released 15-May-2014
+--------------------------------
+- Bug 4018 - Implement checks for sensitive directories when chrooted.
+- Bug 4022 - "Directory not empty" error when creating directory is misleading.
+- Bug 4025 - <IfClass> sections do not work for multiple SQLLog directives.
+- Bug 4029 - TLSOptions EnableDiags logs "unknown version (771)" for
+  TLS 1.1/1.2 connections.
+- Bug 3938 - mod_wrap2 uses reverse DNS regardless "UseReverseDNS off".
+- Bug 4032 - Restarting proftpd with mod_sftp fails due to permissions on
+  SFTPHostKey file.
+- Bug 4033 - mod_sftp fails to create SSH2 session using 'none' cipher.
+- Bug 4034 - SSH publickey authentication fails with "MaxLoginAttempts 1".
+- Bug 4024 - TLS 1.1/1.2 configurable, but not properly implemented.
+- Bug 4046 - ALLO command failed because of bad size check.
+- Bug 4048 - Race condition in mod_ban can lead to segfault of all new
+  connections.
+- Bug 4049 - mod_exec should include supplemental groups when running commands
+  as logged-in user.
+- Bug 4042 - MIC command between RNFR and RNTO should not be rejected.
+- Bug 4044 - mod_facl prevents a normal SIGHUP reload.
+- Bug 4052 - Enhance SQLPasswordPBKDF2 to support per-user query for settings.
+
 1.3.5rc4 - Released 28-Jan-2014
 --------------------------------
 - Bug 3945 - Spurious log messages at session close.
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 5f31a9c..889589a 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -6,6 +6,60 @@ This file contains a description of the major changes to ProFTPD for the
 releases.  More information on these changes can be found in the NEWS and
 ChangeLog files.
 
+1.3.5
+---------
+
+  + TLS 1.1/1.2 configuration now works properly.
+
+  + New Configuration Directives
+
+    RLimitChroot
+      When proftpd chroots a session (e.g. via DefaultRoot or <Anonymous>),
+      certain attacks become possible, such as the "Roaring Beast" attack:
+
+        http://auscert.org.au/15286
+        https://auscert.org.au/15526
+
+      To help mitigate these attacks, proftpd now rejects any attempt to do
+      a write of any kind to paths under /etc and /lib, when the session is
+      chrooted to a path other than "/".
+
+      If these restrictions cause problems for any sites, this guard can be
+      disabled via the new RLimitChroot directive, e.g.:
+
+        RLimitChroot off
+
+      See doc/modules/mod_rlimit.html#RLimitChroot for more information.
+
+
+  + Changed Configuration Directives
+
+    SFTPOptions AllowInsecureLogin
+      Some SFTP clients may wish to use the 'none' cipher, and/or 'none' digest,
+      for testing purposes.  For example, disabling the cipher and digest can
+      be used for testing the raw transfer speed over SFTP.
+
+      mod_sftp, by default, will not allow connections which attempt to use the
+      'none' cipher or 'none' digest, even if these are explicitly enabled via
+      the SFTPCiphers and SFTPDigests directive, as use of these algorithms
+      disables the security protections on the transferred data (such as
+      username/password).
+
+      Thus to explicitly allow usage for these insecure algorithms, use:
+
+        SFTPOptions AllowInsecureLogin
+
+      See doc/contrib/mod_sftp.html#SFTPOptions for details.
+
+    SQLPasswordPBKDF2 sql://
+      The mod_sql_passwd module now supports retrieval of PBKDF2 parameters,
+      such as algorithm, iteration count, and output length, on a per-user
+      basis, via a SQLNamedQuery, in addition to staticly configured
+      parameters.
+
+      See doc/contrib/mod_sql_passwd.html#SQLPasswordPBKDF2 for details.
+
+
 1.3.5rc4
 ---------
 
@@ -517,4 +571,4 @@ ChangeLog files.
     and cmdtable.class renamed to cmdtable.cmd_class.  See Bug#3079 for
     details.
 
-Last Updated: $Date: 2014/01/27 01:28:21 $
+Last Updated: $Date: 2014/05/09 14:52:12 $
diff --git a/contrib/mod_ban.c b/contrib/mod_ban.c
index b9295a7..c3dccbe 100644
--- a/contrib/mod_ban.c
+++ b/contrib/mod_ban.c
@@ -25,7 +25,7 @@
  * This is mod_ban, contrib software for proftpd 1.2.x/1.3.x.
  * For more information contact TJ Saunders <tj at castaglia.org>.
  *
- * $Id: mod_ban.c,v 1.70 2014/01/26 17:50:28 castaglia Exp $
+ * $Id: mod_ban.c,v 1.75 2014/04/30 17:33:33 castaglia Exp $
  */
 
 #include "conf.h"
@@ -35,7 +35,7 @@
 #include <sys/ipc.h>
 #include <sys/shm.h>
 
-#define MOD_BAN_VERSION			"mod_ban/0.6.1"
+#define MOD_BAN_VERSION			"mod_ban/0.6.2"
 
 /* Make sure the version of proftpd is as necessary. */
 #if PROFTPD_VERSION_NUMBER < 0x0001030402
@@ -70,6 +70,14 @@
 # define BAN_EVENT_LIST_MAXSZ	512
 #endif
 
+/* This "headroom" is for cases where many concurrent processes are
+ * incrementing the index, possibly past the MAXSZs above.  We thus allocate
+ * some headroom for them, to mitigate/avoid array out-of-bounds faults.
+ */
+#ifndef BAN_LIST_HEADROOMSZ
+# define BAN_LIST_HEADROOMSZ	10
+#endif
+
 /* From src/main.c */
 extern pid_t mpid;
 extern xaset_t *server_list;
@@ -94,7 +102,7 @@ struct ban_entry {
 #define BAN_TYPE_USER		3
 
 struct ban_list {
-  struct ban_entry bl_entries[BAN_LIST_MAXSZ];
+  struct ban_entry bl_entries[BAN_LIST_MAXSZ + BAN_LIST_HEADROOMSZ];
   unsigned int bl_listlen;
   unsigned int bl_next_slot;
 };
@@ -126,9 +134,11 @@ struct ban_event_entry {
 #define BAN_EV_TYPE_MAX_CMD_RATE		13
 #define BAN_EV_TYPE_UNHANDLED_CMD		14
 #define BAN_EV_TYPE_TLS_HANDSHAKE		15
+#define BAN_EV_TYPE_ROOT_LOGIN			16
+#define BAN_EV_TYPE_USER_DEFINED		17
 
 struct ban_event_list {
-  struct ban_event_entry bel_entries[BAN_EVENT_LIST_MAXSZ];
+  struct ban_event_entry bel_entries[BAN_EVENT_LIST_MAXSZ + BAN_LIST_HEADROOMSZ];
   unsigned int bel_listlen;
   unsigned int bel_next_slot;
 };
@@ -210,11 +220,13 @@ static void ban_maxcmdrate_ev(const void *, void *);
 static void ban_maxconnperhost_ev(const void *, void *);
 static void ban_maxhostsperuser_ev(const void *, void *);
 static void ban_maxloginattempts_ev(const void *, void *);
+static void ban_rootlogin_ev(const void *, void *);
 static void ban_timeoutidle_ev(const void *, void *);
 static void ban_timeoutlogin_ev(const void *, void *);
 static void ban_timeoutnoxfer_ev(const void *, void *);
 static void ban_tlshandshake_ev(const void *, void *);
 static void ban_unhandledcmd_ev(const void *, void *);
+static void ban_userdefined_ev(const void *, void *);
 
 static void ban_handle_event(unsigned int, int, const char *,
   struct ban_event_entry *);
@@ -803,7 +815,7 @@ static int ban_list_add(pool *p, unsigned int type, unsigned int sid,
 
     pr_signals_handle();
 
-    if (ban_lists->bans.bl_next_slot == BAN_LIST_MAXSZ)
+    if (ban_lists->bans.bl_next_slot >= BAN_LIST_MAXSZ)
       ban_lists->bans.bl_next_slot = 0;
 
     be = &(ban_lists->bans.bl_entries[ban_lists->bans.bl_next_slot]);
@@ -1164,6 +1176,12 @@ static const char *ban_event_entry_typestr(unsigned int type) {
 
     case BAN_EV_TYPE_TLS_HANDSHAKE:
       return "TLSHandshake";
+
+    case BAN_EV_TYPE_ROOT_LOGIN:
+      return "RootLogin";
+
+    case BAN_EV_TYPE_USER_DEFINED:
+      return "(user-defined)";
   }
 
   return NULL;
@@ -1188,7 +1206,7 @@ static int ban_event_list_add(unsigned int type, unsigned int sid,
 
     pr_signals_handle();
 
-    if (ban_lists->events.bel_next_slot == BAN_EVENT_LIST_MAXSZ)
+    if (ban_lists->events.bel_next_slot >= BAN_EVENT_LIST_MAXSZ)
       ban_lists->events.bel_next_slot = 0;
 
     bee = &(ban_lists->events.bel_entries[ban_lists->events.bel_next_slot]);
@@ -1548,6 +1566,8 @@ static int ban_handle_info(pr_ctrls_t *ctrl, int reqargc, char **reqargv) {
           case BAN_EV_TYPE_MAX_CMD_RATE:
           case BAN_EV_TYPE_UNHANDLED_CMD:
           case BAN_EV_TYPE_TLS_HANDSHAKE:
+          case BAN_EV_TYPE_ROOT_LOGIN:
+          case BAN_EV_TYPE_USER_DEFINED:
             if (!have_banner) {
               pr_ctrls_add_response(ctrl, "Ban Events:");
               have_banner = TRUE;
@@ -2271,9 +2291,10 @@ MODRET set_banonevent(cmd_rec *cmd) {
   bee = pcalloc(ban_pool, sizeof(struct ban_event_entry));
 
   tmp = strchr(cmd->argv[2], '/');
-  if (!tmp)
+  if (tmp == NULL) {
     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "badly formatted freq parameter: '",
       cmd->argv[2], "'", NULL));
+  }
 
   /* The frequency string is formatted as "N/hh:mm:ss", where N is the count
    * to be reached within the given time interval.
@@ -2282,25 +2303,32 @@ MODRET set_banonevent(cmd_rec *cmd) {
   *tmp = '\0';
 
   n = atoi(cmd->argv[2]);
-  if (n < 1)
+  if (n < 1) {
     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
       "freq occurrences must be greater than 0", NULL));
+  }
   bee->bee_count_max = n;
 
   bee->bee_window = ban_parse_timestr(tmp+1);
-  if (bee->bee_window == (time_t) -1)
+  if (bee->bee_window == (time_t) -1) {
     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
       "badly formatted freq parameter: '", cmd->argv[2], "'", NULL));
-  if (bee->bee_window == 0)
+  }
+
+  if (bee->bee_window == 0) {
     CONF_ERROR(cmd, "freq parameter cannot be '00:00:00'");
+  }
 
   /* The duration is the next parameter. */
   bee->bee_expires = ban_parse_timestr(cmd->argv[3]);
-  if (bee->bee_expires == (time_t) -1)
+  if (bee->bee_expires == (time_t) -1) {
     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
       "badly formatted duration parameter: '", cmd->argv[2], "'", NULL));
-  if (bee->bee_expires == 0)
+  }
+
+  if (bee->bee_expires == 0) {
     CONF_ERROR(cmd, "duration parameter cannot be '00:00:00'");
+  }
 
   /* If present, the next parameter is a custom ban message. */
   if (cmd->argc == 5) {
@@ -2360,6 +2388,11 @@ MODRET set_banonevent(cmd_rec *cmd) {
     pr_event_register(&ban_module, "mod_auth.max-login-attempts",
       ban_maxloginattempts_ev, bee);
 
+  } else if (strcasecmp(cmd->argv[1], "RootLogin") == 0) {
+    bee->bee_type = BAN_EV_TYPE_ROOT_LOGIN;
+    pr_event_register(&ban_module, "mod_auth.root-login",
+      ban_rootlogin_ev, bee);
+
   } else if (strcasecmp(cmd->argv[1], "TimeoutIdle") == 0) {
     bee->bee_type = BAN_EV_TYPE_TIMEOUT_IDLE;
     pr_event_register(&ban_module, "core.timeout-idle",
@@ -2386,8 +2419,8 @@ MODRET set_banonevent(cmd_rec *cmd) {
       ban_unhandledcmd_ev, bee);
 
   } else {
-    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown ", cmd->argv[0], " name: '",
-      cmd->argv[1], "'", NULL));
+    bee->bee_type = BAN_EV_TYPE_USER_DEFINED;
+    pr_event_register(&ban_module, cmd->argv[1], ban_userdefined_ev, bee);
   }
 
   return PR_HANDLED(cmd);
@@ -2932,6 +2965,18 @@ static void ban_restart_ev(const void *event_data, void *user_data) {
   return;
 }
 
+static void ban_rootlogin_ev(const void *event_data, void *user_data) {
+  const char *ipstr = pr_netaddr_get_ipstr(session.c->remote_addr);
+
+  /* user_data is a template of the ban event entry. */
+  struct ban_event_entry *tmpl = user_data;
+
+  if (ban_engine != TRUE)
+    return;
+
+  ban_handle_event(BAN_EV_TYPE_ROOT_LOGIN, BAN_TYPE_HOST, ipstr, tmpl);
+}
+
 static void ban_timeoutidle_ev(const void *event_data, void *user_data) {
   const char *ipstr = pr_netaddr_get_ipstr(session.c->remote_addr);
 
@@ -2996,6 +3041,18 @@ static void ban_unhandledcmd_ev(const void *event_data, void *user_data) {
   ban_handle_event(BAN_EV_TYPE_UNHANDLED_CMD, BAN_TYPE_HOST, ipstr, tmpl);
 }
 
+static void ban_userdefined_ev(const void *event_data, void *user_data) {
+  const char *ipstr = pr_netaddr_get_ipstr(session.c->remote_addr);
+
+  /* user_data is a template of the ban event entry. */
+  struct ban_event_entry *tmpl = user_data;
+
+  if (ban_engine != TRUE)
+    return;
+
+  ban_handle_event(BAN_EV_TYPE_USER_DEFINED, BAN_TYPE_HOST, ipstr, tmpl);
+}
+
 /* Initialization routines
  */
 
diff --git a/contrib/mod_exec.c b/contrib/mod_exec.c
index caa176a..f8f9153 100644
--- a/contrib/mod_exec.c
+++ b/contrib/mod_exec.c
@@ -1,7 +1,7 @@
 /*
  * ProFTPD: mod_exec -- a module for executing external scripts
  *
- * Copyright (c) 2002-2013 TJ Saunders
+ * Copyright (c) 2002-2014 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
@@ -24,7 +24,7 @@
  * This is mod_exec, contrib software for proftpd 1.3.x and above.
  * For more information contact TJ Saunders <tj at castaglia.org>.
  *
- * $Id: mod_exec.c,v 1.39 2013/12/12 06:11:27 castaglia Exp $
+ * $Id: mod_exec.c,v 1.40 2014/05/02 21:13:33 castaglia Exp $
  */
 
 #include "conf.h"
@@ -489,6 +489,13 @@ static int exec_ssystem(cmd_rec *cmd, config_rec *c, int flags) {
     sigaction(SIGQUIT, &sa_quit, NULL);
     sigprocmask(SIG_SETMASK, &set_save, NULL);
 
+    /* Per Bug#4049, when running the command as the user, do NOT clear
+     * the supplemental groups.
+     */
+    if (flags & EXEC_FL_RUN_AS_USER) {
+      flags &= ~EXEC_FL_CLEAR_GROUPS;
+    }
+
     /* If requested, clear the supplemental group membership of the process. */
     if (flags & EXEC_FL_CLEAR_GROUPS) {
       PRIVS_ROOT
diff --git a/contrib/mod_ifsession.c b/contrib/mod_ifsession.c
index a774418..23b488d 100644
--- a/contrib/mod_ifsession.c
+++ b/contrib/mod_ifsession.c
@@ -2,7 +2,7 @@
  * ProFTPD: mod_ifsession -- a module supporting conditional
  *                            per-user/group/class configuration contexts.
  *
- * Copyright (c) 2002-2013 TJ Saunders
+ * Copyright (c) 2002-2014 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
@@ -26,7 +26,7 @@
  * This is mod_ifsession, contrib software for proftpd 1.2 and above.
  * For more information contact TJ Saunders <tj at castaglia.org>.
  *
- * $Id: mod_ifsession.c,v 1.55 2013/11/09 18:41:55 castaglia Exp $
+ * $Id: mod_ifsession.c,v 1.56 2014/02/15 18:54:00 castaglia Exp $
  */
 
 #include "conf.h"
@@ -185,9 +185,10 @@ static void ifsess_dup_set(pool *dst_pool, xaset_t *dst, xaset_t *src) {
      */
     if (c->parent->config_type != CONF_LIMIT &&
         c->config_type == CONF_PARAM &&
-        !(c->flags & CF_MERGEDOWN_MULTI)) {
+        !(c->flags & CF_MERGEDOWN_MULTI) &&
+        !(c->flags & CF_MULTI)) {
       pr_trace_msg(trace_channel, 15, "removing '%s' config because "
-        "c->flags does not contain MERGEDOWN_MULTI", c->name);
+        "c->flags does not contain MULTI or MERGEDOWN_MULTI", c->name);
       ifsess_remove_param(dst, c->config_type, c->name);
     }
 
diff --git a/contrib/mod_sftp/auth-kbdint.c b/contrib/mod_sftp/auth-kbdint.c
index f8353a8..c3ef3e6 100644
--- a/contrib/mod_sftp/auth-kbdint.c
+++ b/contrib/mod_sftp/auth-kbdint.c
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp 'keyboard-interactive' user authentication
- * Copyright (c) 2008-2013 TJ Saunders
+ * Copyright (c) 2008-2014 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
@@ -21,7 +21,7 @@
  * resulting executable, without including the source code for OpenSSL in the
  * source distribution.
  *
- * $Id: auth-kbdint.c,v 1.9 2013/03/29 16:29:41 castaglia Exp $
+ * $Id: auth-kbdint.c,v 1.10 2014/03/02 22:05:43 castaglia Exp $
  */
 
 #include "mod_sftp.h"
@@ -91,14 +91,22 @@ int sftp_auth_kbdint(struct ssh2_packet *pkt, cmd_rec *pass_cmd,
    */
   if (strncmp(cipher_algo, "none", 5) == 0 ||
       strncmp(mac_algo, "none", 5) == 0) {
-    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-      "cipher algorithm '%s' or MAC algorithm '%s' unacceptable for "
-      "keyboard-interactive authentication, denying authentication request",
-      cipher_algo, mac_algo);
 
-    *send_userauth_fail = TRUE;
-    errno = EPERM;
-    return 0;
+    if (sftp_opts & SFTP_OPT_ALLOW_INSECURE_LOGIN) {
+      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+        "WARNING: cipher algorithm '%s' or MAC algorithm '%s' INSECURE for "
+        "keyboard-interactive authentication "
+        "(SFTPOption AllowInsecureLogin in effect)", cipher_algo, mac_algo);
+
+    } else {
+      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+        "cipher algorithm '%s' or MAC algorithm '%s' unacceptable for "
+        "keyboard-interactive authentication, denying authentication request",
+        cipher_algo, mac_algo);
+      *send_userauth_fail = TRUE;
+      errno = EPERM;
+      return 0;
+    }
   }
 
   /* XXX Read off the deprecated language string. */
diff --git a/contrib/mod_sftp/auth-password.c b/contrib/mod_sftp/auth-password.c
index 0099d63..17e8a94 100644
--- a/contrib/mod_sftp/auth-password.c
+++ b/contrib/mod_sftp/auth-password.c
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp 'password' user authentication
- * Copyright (c) 2008-2012 TJ Saunders
+ * Copyright (c) 2008-2014 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
@@ -21,7 +21,7 @@
  * resulting executable, without including the source code for OpenSSL in the
  * source distribution.
  *
- * $Id: auth-password.c,v 1.9 2012/07/10 00:52:20 castaglia Exp $
+ * $Id: auth-password.c,v 1.10 2014/03/02 22:05:43 castaglia Exp $
  */
 
 #include "mod_sftp.h"
@@ -45,13 +45,22 @@ int sftp_auth_password(struct ssh2_packet *pkt, cmd_rec *pass_cmd,
 
   if (strncmp(cipher_algo, "none", 5) == 0 ||
       strncmp(mac_algo, "none", 5) == 0) {
-    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
-      "cipher algorithm '%s' or MAC algorithm '%s' unacceptable for "
-      "password authentication, denying password authentication request",
-      cipher_algo, mac_algo);
-    *send_userauth_fail = TRUE;
-    errno = EPERM;
-    return 0;
+
+    if (sftp_opts & SFTP_OPT_ALLOW_INSECURE_LOGIN) {
+      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+        "WARNING: cipher algorithm '%s' or MAC algorithm '%s' INSECURE for "
+        "password authentication (SFTPOption AllowInsecureLogin in effect)",
+        cipher_algo, mac_algo);
+
+    } else {
+      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+        "cipher algorithm '%s' or MAC algorithm '%s' unacceptable for "
+        "password authentication, denying password authentication request",
+        cipher_algo, mac_algo);
+      *send_userauth_fail = TRUE;
+      errno = EPERM;
+      return 0;
+    }
   }
 
   /* XXX We currently don't do anything with this. */
diff --git a/contrib/mod_sftp/auth.c b/contrib/mod_sftp/auth.c
index 411ae9e..a24336a 100644
--- a/contrib/mod_sftp/auth.c
+++ b/contrib/mod_sftp/auth.c
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp user authentication
- * Copyright (c) 2008-2013 TJ Saunders
+ * Copyright (c) 2008-2014 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
@@ -21,7 +21,7 @@
  * resulting executable, without including the source code for OpenSSL in the
  * source distribution.
  *
- * $Id: auth.c,v 1.52 2013/10/13 16:48:08 castaglia Exp $
+ * $Id: auth.c,v 1.53 2014/03/04 07:54:12 castaglia Exp $
  */
 
 #include "mod_sftp.h"
@@ -1254,9 +1254,10 @@ static int handle_userauth_req(struct ssh2_packet *pkt, char **service) {
       if (send_userauth_failure(errno != EPERM ? NULL : method) < 0) {
         return -1;
       }
+
+      incr_auth_attempts(user);
     }
 
-    incr_auth_attempts(user);
     return res;
   }
 
diff --git a/contrib/mod_sftp/cipher.c b/contrib/mod_sftp/cipher.c
index 203bf60..28f65a1 100644
--- a/contrib/mod_sftp/cipher.c
+++ b/contrib/mod_sftp/cipher.c
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp ciphers
- * Copyright (c) 2008-2013 TJ Saunders
+ * Copyright (c) 2008-2014 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
@@ -21,7 +21,7 @@
  * resulting executable, without including the source code for OpenSSL in the
  * source distribution.
  *
- * $Id: cipher.c,v 1.17 2013/12/19 16:32:32 castaglia Exp $
+ * $Id: cipher.c,v 1.18 2014/03/02 06:07:43 castaglia Exp $
  */
 
 #include "mod_sftp.h"
@@ -159,10 +159,17 @@ static int set_cipher_iv(struct sftp_cipher *cipher, const EVP_MD *hash,
 
   EVP_MD_CTX ctx;
   unsigned char *iv = NULL;
-  size_t cipher_iv_len, iv_sz;
+  size_t cipher_iv_len = 0, iv_sz = 0;
   uint32_t iv_len = 0;
 
-  /* Some ciphers do not use IVs; handle this case. */
+  if (strncmp(cipher->algo, "none", 5) == 0) {
+    cipher->iv = iv;
+    cipher->iv_len = iv_len;
+
+    return 0;
+  }
+
+   /* Some ciphers do not use IVs; handle this case. */
   cipher_iv_len = EVP_CIPHER_iv_length(cipher->cipher);
   if (cipher_iv_len != 0) {
     iv_sz = sftp_crypto_get_size(cipher_iv_len, EVP_MD_size(hash));
@@ -174,7 +181,7 @@ static int set_cipher_iv(struct sftp_cipher *cipher, const EVP_MD *hash,
   if (iv_sz == 0) {
     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
       "unable to determine IV length for cipher '%s'", cipher->algo);
-    errno = EINVAL;
+     errno = EINVAL;
     return -1;
   }
 
@@ -224,9 +231,16 @@ static int set_cipher_key(struct sftp_cipher *cipher, const EVP_MD *hash,
 
   EVP_MD_CTX ctx;
   unsigned char *key = NULL;
-  size_t key_sz;
+  size_t key_sz = 0;
   uint32_t key_len = 0;
 
+  if (strncmp(cipher->algo, "none", 5) == 0) {
+    cipher->key = key;
+    cipher->key_len = key_len;
+
+    return 0;
+  }
+
   key_sz = sftp_crypto_get_size(cipher->key_len > 0 ?
       cipher->key_len : EVP_CIPHER_key_length(cipher->cipher),
     EVP_MD_size(hash));
@@ -327,7 +341,8 @@ void sftp_cipher_set_block_size(size_t blocksz) {
 }
 
 const char *sftp_cipher_get_read_algo(void) {
-  if (read_ciphers[read_cipher_idx].key) {
+  if (read_ciphers[read_cipher_idx].key != NULL ||
+      strncmp(read_ciphers[read_cipher_idx].algo, "none", 5) == 0) {
     return read_ciphers[read_cipher_idx].algo;
   }
 
@@ -489,7 +504,8 @@ int sftp_cipher_read_data(pool *p, unsigned char *data, uint32_t data_len,
 }
 
 const char *sftp_cipher_get_write_algo(void) {
-  if (write_ciphers[write_cipher_idx].key) {
+  if (write_ciphers[write_cipher_idx].key != NULL ||
+      strncmp(write_ciphers[write_cipher_idx].algo, "none", 5) == 0) {
     return write_ciphers[write_cipher_idx].algo;
   }
 
diff --git a/contrib/mod_sftp/mod_sftp.c b/contrib/mod_sftp/mod_sftp.c
index ddd65fc..16914ba 100644
--- a/contrib/mod_sftp/mod_sftp.c
+++ b/contrib/mod_sftp/mod_sftp.c
@@ -24,7 +24,7 @@
  * DO NOT EDIT BELOW THIS LINE
  * $Archive: mod_sftp.a $
  * $Libraries: -lcrypto -lz $
- * $Id: mod_sftp.c,v 1.84 2014/01/13 18:23:25 castaglia Exp $
+ * $Id: mod_sftp.c,v 1.86 2014/03/02 22:05:43 castaglia Exp $
  */
 
 #include "mod_sftp.h"
@@ -1058,13 +1058,20 @@ MODRET set_sftphostkey(cmd_rec *cmd) {
   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
 
   if (strncmp(cmd->argv[1], "agent:", 6) != 0) {
+    int res, xerrno;
+
     if (*cmd->argv[1] != '/') {
       CONF_ERROR(cmd, "must be an absolute path");
     }
 
-    if (stat(cmd->argv[1], &st) < 0) {
+    PRIVS_ROOT
+    res = stat(cmd->argv[1], &st);
+    xerrno = errno;
+    PRIVS_RELINQUISH
+
+    if (res < 0) {
       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to check '", cmd->argv[1],
-        "': ", strerror(errno), NULL));
+        "': ", strerror(xerrno), NULL));
     }
 
     if ((st.st_mode & S_IRWXG) ||
@@ -1225,6 +1232,9 @@ MODRET set_sftpoptions(cmd_rec *cmd) {
     } else if (strncmp(cmd->argv[i], "MatchKeySubject", 16) == 0) {
       opts |= SFTP_OPT_MATCH_KEY_SUBJECT;
 
+    } else if (strcmp(cmd->argv[1], "AllowInsecureLogin") == 0) {
+      opts |= SFTP_OPT_ALLOW_INSECURE_LOGIN;
+
     } else {
       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown SFTPOption '",
         cmd->argv[i], "'", NULL));
diff --git a/contrib/mod_sftp/mod_sftp.h.in b/contrib/mod_sftp/mod_sftp.h.in
index 97b1fc4..bc076dd 100644
--- a/contrib/mod_sftp/mod_sftp.h.in
+++ b/contrib/mod_sftp/mod_sftp.h.in
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - mod_sftp
- * Copyright (c) 2008-2013 TJ Saunders
+ * Copyright (c) 2008-2014 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
@@ -21,7 +21,7 @@
  * resulting executable, without including the source code for OpenSSL in the
  * source distribution.
  *
- * $Id: mod_sftp.h.in,v 1.29 2013/12/05 00:50:16 castaglia Exp $
+ * $Id: mod_sftp.h.in,v 1.30 2014/03/02 22:05:43 castaglia Exp $
  */
 
 #ifndef MOD_SFTP_H
@@ -104,6 +104,7 @@
 #define SFTP_OPT_IGNORE_SFTP_SET_TIMES		0x0040
 #define SFTP_OPT_IGNORE_SFTP_SET_OWNERS		0x0080
 #define SFTP_OPT_IGNORE_SCP_UPLOAD_TIMES	0x0100
+#define SFTP_OPT_ALLOW_INSECURE_LOGIN		0x0200
 
 /* mod_sftp service flags */
 #define SFTP_SERVICE_FL_SFTP		0x0001
diff --git a/contrib/mod_sql.c b/contrib/mod_sql.c
index 91efed8..e0f9a4c 100644
--- a/contrib/mod_sql.c
+++ b/contrib/mod_sql.c
@@ -2,7 +2,7 @@
  * ProFTPD: mod_sql -- SQL frontend
  * Copyright (c) 1998-1999 Johnie Ingram.
  * Copyright (c) 2001 Andrew Houghton.
- * Copyright (c) 2004-2013 TJ Saunders
+ * Copyright (c) 2004-2014 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
@@ -23,7 +23,7 @@
  * the resulting executable, without including the source code for OpenSSL in
  * the source distribution.
  *
- * $Id: mod_sql.c,v 1.245 2013/11/11 01:34:03 castaglia Exp $
+ * $Id: mod_sql.c,v 1.247 2014/02/15 08:31:25 castaglia Exp $
  */
 
 #include "conf.h"
@@ -2243,8 +2243,9 @@ static int sql_getgroups(cmd_rec *cmd) {
 MODRET sql_pre_dele(cmd_rec *cmd) {
   char *path;
 
-  if (cmap.engine == 0)
+  if (cmap.engine == 0) {
     return PR_DECLINED(cmd);
+  }
 
   sql_dele_filesz = 0;
 
@@ -2272,8 +2273,9 @@ MODRET sql_pre_pass(cmd_rec *cmd) {
   config_rec *c = NULL;
   char *user = NULL;
 
-  if (cmap.engine == 0)
+  if (cmap.engine == 0) {
     return PR_DECLINED(cmd);
+  }
 
   sql_log(DEBUG_FUNC, "%s", ">>> sql_pre_pass");
 
@@ -2288,14 +2290,14 @@ MODRET sql_pre_pass(cmd_rec *cmd) {
 
     c = find_config(anon_config ? anon_config->subset : main_server->conf,
       CONF_PARAM, "SQLEngine", FALSE);
-    if (c) {
+    if (c != NULL) {
       cmap.engine = *((int *) c->argv[0]);
     }
 
   } else {
     /* Just assume the vhost config. */
     c = find_config(main_server->conf, CONF_PARAM, "SQLEngine", FALSE);
-    if (c) {
+    if (c != NULL) {
       cmap.engine = *((int *) c->argv[0]);
     }
   }
@@ -2316,8 +2318,9 @@ MODRET sql_post_pass(cmd_rec *cmd) {
 }
 
 MODRET sql_post_stor(cmd_rec *cmd) {
-  if (cmap.engine == 0)
+  if (cmap.engine == 0) {
     return PR_DECLINED(cmd);
+  }
 
   sql_log(DEBUG_FUNC, "%s", ">>> sql_post_stor");
 
@@ -2329,8 +2332,9 @@ MODRET sql_post_stor(cmd_rec *cmd) {
 }
 
 MODRET sql_post_retr(cmd_rec *cmd) {
-  if (cmap.engine == 0)
+  if (cmap.engine == 0) {
     return PR_DECLINED(cmd);
+  }
 
   sql_log(DEBUG_FUNC, "%s", ">>> sql_post_retr");
 
@@ -4386,11 +4390,13 @@ MODRET sql_lookup(cmd_rec *cmd) {
   sql_data_t *sd = NULL;
   array_header *ah = NULL;
 
-  if (cmap.engine == 0)
+  if (cmap.engine == 0) {
     return PR_DECLINED(cmd);
+  }
 
-  if (cmd->argc < 1)
+  if (cmd->argc < 1) {
     return PR_ERROR(cmd);
+  }
 
   sql_log(DEBUG_FUNC, "%s", ">>> sql_lookup");
 
@@ -4436,11 +4442,13 @@ MODRET sql_change(cmd_rec *cmd) {
   char *type = NULL;
   modret_t *mr = NULL;
 
-  if (cmap.engine == 0)
+  if (cmap.engine == 0) {
     return PR_DECLINED(cmd);
+  }
 
-  if (cmd->argc < 1)
+  if (cmd->argc < 1) {
     return PR_ERROR(cmd);
+  }
 
   sql_log(DEBUG_FUNC, "%s", ">>> sql_change");
 
@@ -6205,20 +6213,22 @@ MODRET set_sqlengine(cmd_rec *cmd) {
   engine = get_boolean(cmd, 1);
   if (engine == -1) {
     /* The parameter is not a boolean; check for "auth" or "log". */
-    if (strcasecmp(cmd->argv[1], "auth") == 0)
+    if (strcasecmp(cmd->argv[1], "auth") == 0) {
       engine = SQL_ENGINE_FL_AUTH;
 
-    else if (strcasecmp(cmd->argv[1], "log") == 0)
+    } else if (strcasecmp(cmd->argv[1], "log") == 0) {
       engine = SQL_ENGINE_FL_LOG;
 
-    else
+    } else {
       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown SQLEngine parameter '",
         cmd->argv[1], "'", NULL));
+    }
 
   } else {
-    if (engine == 1)
+    if (engine == 1) {
       /* Convert an "on" into a auth|log combination. */
       engine = SQL_ENGINE_FL_AUTH|SQL_ENGINE_FL_LOG;
+    }
   }
 
   c = add_config_param(cmd->argv[0], 1, NULL);
@@ -6440,8 +6450,9 @@ static void sql_exit_ev(const void *event_data, void *user_data) {
   cmd_rec *cmd;
   modret_t *mr;
 
-  if (cmap.engine == 0)
+  if (cmap.engine == 0) {
     return;
+  }
 
   /* handle EXIT queries */
   c = find_config(main_server->conf, CONF_PARAM, "SQLLog_EXIT", FALSE);
@@ -6546,7 +6557,7 @@ static int sql_sess_init(void) {
   cmd_rec *cmd = NULL;
   modret_t *mr = NULL;
   sql_data_t *sd = NULL;
-  int res = 0;
+  int engine = 0, res = 0;
   char *fieldset = NULL;
   pool *tmp_pool = NULL;
 
@@ -6584,6 +6595,11 @@ static int sql_sess_init(void) {
     destroy_pool(tmp_pool);
     return -1;
   }
+
+  c = find_config(main_server->conf, CONF_PARAM, "SQLEngine", FALSE);
+  if (c != NULL) {
+    engine = *((int *) c->argv[0]);
+  }
  
   /* Get our backend info and toss it up */
   cmd = _sql_make_cmd(tmp_pool, 1, "foo");
@@ -6876,7 +6892,8 @@ static int sql_sess_init(void) {
   cmap.auth_list = ptr;
 
   if (cmap.auth_list == NULL &&
-      cmap.authmask != 0) {
+      cmap.authmask != 0 &&
+      (engine > 0 && engine != SQL_ENGINE_FL_LOG)) {
     sql_log(DEBUG_INFO, "%s", "error: no SQLAuthTypes configured");
   }
 
diff --git a/contrib/mod_sql_passwd.c b/contrib/mod_sql_passwd.c
index a7b471f..ac7bfd8 100644
--- a/contrib/mod_sql_passwd.c
+++ b/contrib/mod_sql_passwd.c
@@ -1,6 +1,6 @@
 /*
  * ProFTPD: mod_sql_passwd -- Various SQL password handlers
- * Copyright (c) 2009-2013 TJ Saunders
+ * Copyright (c) 2009-2014 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
@@ -21,14 +21,14 @@
  * resulting executable, without including the source code for OpenSSL in
  * the source distribution.
  *
- * $Id: mod_sql_passwd.c,v 1.20 2013/06/10 17:12:39 castaglia Exp $
+ * $Id: mod_sql_passwd.c,v 1.22 2014/05/05 16:15:02 castaglia Exp $
  */
 
 #include "conf.h"
 #include "privs.h"
 #include "mod_sql.h"
 
-#define MOD_SQL_PASSWD_VERSION		"mod_sql_passwd/0.6"
+#define MOD_SQL_PASSWD_VERSION		"mod_sql_passwd/0.7"
 
 /* Make sure the version of proftpd is as necessary. */
 #if PROFTPD_VERSION_NUMBER < 0x0001030302 
@@ -72,6 +72,10 @@ static unsigned int sql_passwd_nrounds = 1;
 static const EVP_MD *sql_passwd_pbkdf2_digest = NULL;
 static int sql_passwd_pbkdf2_iter = -1;
 static int sql_passwd_pbkdf2_len = -1;
+#define SQL_PASSWD_ERR_PBKDF2_UNKNOWN_DIGEST		-1
+#define SQL_PASSWD_ERR_PBKDF2_UNSUPPORTED_DIGEST	-2
+#define SQL_PASSWD_ERR_PBKDF2_BAD_ROUNDS		-3
+#define SQL_PASSWD_ERR_PBKDF2_BAD_LENGTH		-4
 
 static const char *trace_channel = "sql_passwd";
 
@@ -164,6 +168,36 @@ static const char *get_crypto_errors(void) {
   return str;
 }
 
+static int get_pbkdf2_config(char *algo, const EVP_MD **md,
+    char *iter_str, int *iter, char *len_str, int *len) {
+
+  *md = EVP_get_digestbyname(algo);
+  if (md == NULL) {
+    return SQL_PASSWD_ERR_PBKDF2_UNKNOWN_DIGEST;
+  }
+
+#if OPENSSL_VERSION_NUMBER < 0x1000003f
+  /* The necesary OpenSSL support for non-SHA1 digests for PBKDF2 appeared in
+   * 1.0.0c.
+   */
+  if (EVP_MD_type(*md) != EVP_MD_type(EVP_sha1())) {
+    return SQL_PASSWD_ERR_PBKDF2_UNSUPPORTED_DIGEST;
+  }
+#endif /* OpenSSL-1.0.0b and earlier */
+
+  *iter = atoi(iter_str);
+  if (*iter < 1) {
+    return SQL_PASSWD_ERR_PBKDF2_BAD_ROUNDS;
+  }
+
+  *len = atoi(len_str);
+  if (*len < 1) {
+    return SQL_PASSWD_ERR_PBKDF2_BAD_LENGTH;
+  }
+
+  return 0;
+}
+
 static char *sql_passwd_encode(pool *p, unsigned char *data, size_t data_len) {
   EVP_ENCODE_CTX base64_ctxt;
   char *buf;
@@ -591,10 +625,109 @@ MODRET sql_passwd_pre_pass(cmd_rec *cmd) {
   }
 
   c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordRounds", FALSE);
-  if (c) {
+  if (c != NULL) {
     sql_passwd_nrounds = *((unsigned int *) c->argv[0]);
   }
 
+  c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordPBKDF2", FALSE);
+  if (c != NULL) {
+    if (c->argc == 3) {
+      sql_passwd_pbkdf2_digest = c->argv[0];
+      sql_passwd_pbkdf2_iter = *((int *) c->argv[1]);
+      sql_passwd_pbkdf2_len = *((int *) c->argv[2]);
+
+    } else {
+      char *key;
+      char *named_query, *ptr, *user;
+      cmdtable *sql_cmdtab;
+      cmd_rec *sql_cmd;
+      modret_t *sql_res;
+      array_header *sql_data;
+
+      key = c->argv[0];
+
+      ptr = key + 5; 
+      named_query = pstrcat(cmd->tmp_pool, "SQLNamedQuery_", ptr, NULL);
+
+      c = find_config(main_server->conf, CONF_PARAM, named_query, FALSE);
+      if (c == NULL) {
+        sql_log(DEBUG_WARN, MOD_SQL_PASSWD_VERSION
+          ": unable to resolve SQLNamedQuery '%s'", ptr);
+        return PR_DECLINED(cmd);
+      }
+
+      sql_cmdtab = pr_stash_get_symbol(PR_SYM_HOOK, "sql_lookup", NULL, NULL);
+      if (sql_cmdtab == NULL) {
+        sql_log(DEBUG_WARN, MOD_SQL_PASSWD_VERSION
+          ": unable to find SQL hook symbol 'sql_lookup'");
+        return PR_DECLINED(cmd);
+      }
+
+      user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
+
+      sql_cmd = sql_passwd_cmd_create(cmd->tmp_pool, 3, "sql_lookup", ptr,
+        sql_passwd_get_str(cmd->tmp_pool, user));
+
+      /* Call the handler. */
+      sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
+      if (sql_res == NULL ||
+          MODRET_ISERROR(sql_res)) {
+        sql_log(DEBUG_WARN, MOD_SQL_PASSWD_VERSION
+          ": error processing SQLNamedQuery '%s'", ptr);
+        return PR_DECLINED(cmd);
+      }
+
+      sql_data = (array_header *) sql_res->data;
+
+      if (sql_data->nelts != 3) {
+        sql_log(DEBUG_WARN, MOD_SQL_PASSWD_VERSION
+          ": SQLNamedQuery '%s' returned wrong number of columns (%d)", ptr,
+          sql_data->nelts);
+
+      } else {
+        char **values;
+        int iter, len, res;
+        const EVP_MD *md;
+
+        values = sql_data->elts;
+
+        res = get_pbkdf2_config(values[0], &md, values[1], &iter,
+          values[2], &len);
+        switch (res) {
+          case SQL_PASSWD_ERR_PBKDF2_UNKNOWN_DIGEST:
+            sql_log(DEBUG_WARN, MOD_SQL_PASSWD_VERSION
+              ": SQLNamedQuery '%s' returned unknown PKBDF2 digest: %s",
+              ptr, values[0]);
+            break;
+
+          case SQL_PASSWD_ERR_PBKDF2_UNSUPPORTED_DIGEST:
+            sql_log(DEBUG_WARN, MOD_SQL_PASSWD_VERSION
+              ": SQLNamedQuery '%s' returned unsupported PKBDF2 digest: %s",
+              ptr, values[0]);
+            break;
+
+          case SQL_PASSWD_ERR_PBKDF2_BAD_ROUNDS:
+            sql_log(DEBUG_WARN, MOD_SQL_PASSWD_VERSION
+              ": SQLNamedQuery '%s' returned insufficient number of rounds: %s",
+              ptr, values[1]);
+            break;
+
+          case SQL_PASSWD_ERR_PBKDF2_BAD_LENGTH:
+            sql_log(DEBUG_WARN, MOD_SQL_PASSWD_VERSION
+              ": SQLNamedQuery '%s' returned insufficient length: %s", ptr,
+              values[2]);
+            break;
+
+          case 0:
+            sql_passwd_pbkdf2_digest = md;
+            sql_passwd_pbkdf2_iter = iter;
+            sql_passwd_pbkdf2_len = len;
+            break;
+        }
+      }
+    }
+  }
+
   c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordUserSalt", FALSE);
   if (c) {
     char *key;
@@ -617,7 +750,7 @@ MODRET sql_passwd_pre_pass(cmd_rec *cmd) {
       modret_t *sql_res;
       array_header *sql_data;
 
-      ptr = key + 5; 
+      ptr = key + 5;
       named_query = pstrcat(cmd->tmp_pool, "SQLNamedQuery_", ptr, NULL);
 
       c = find_config(main_server->conf, CONF_PARAM, named_query, FALSE);
@@ -660,7 +793,7 @@ MODRET sql_passwd_pre_pass(cmd_rec *cmd) {
       values = sql_data->elts;
       sql_passwd_salt = pstrdup(session.pool, values[0]);
       sql_passwd_salt_len = strlen(values[0]);
-      
+
     } else {
       return PR_DECLINED(cmd);
     }
@@ -762,50 +895,63 @@ MODRET set_sqlpasswdoptions(cmd_rec *cmd) {
   return PR_HANDLED(cmd);
 }
 
-/* usage: SQLPasswordPBKDF2 algo iter len */
+/* usage: SQLPasswordPBKDF2 "sql:/"named-query|algo iter len */
 MODRET set_sqlpasswdpbkdf2(cmd_rec *cmd) {
   config_rec *c;
-  int iter, len;
-  const EVP_MD *md;
 
-  CHECK_ARGS(cmd, 3);
   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
 
-  md = EVP_get_digestbyname(cmd->argv[1]);
-  if (md == NULL) {
-    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported digest algorithm '",
-      cmd->argv[1], "' configured", NULL));
-  }
+  if (cmd->argc == 4) {
+    int iter, len, res;
+    const EVP_MD *md;
+
+    res = get_pbkdf2_config(cmd->argv[1], &md, cmd->argv[2], &iter,
+      cmd->argv[3], &len);
+    switch (res) {
+      case SQL_PASSWD_ERR_PBKDF2_UNKNOWN_DIGEST:
+        CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported digest algorithm '",
+          cmd->argv[1], "' configured", NULL));
+        break;
+
+      case SQL_PASSWD_ERR_PBKDF2_UNSUPPORTED_DIGEST:
+        CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
+          "Use of non-SHA1 digests for PBKDF2, such as ", cmd->argv[1],
+          ", requires OpenSSL-1.0.0c or later (currently using ",
+          OPENSSL_VERSION_TEXT, ")", NULL));
+        break;
+
+      case SQL_PASSWD_ERR_PBKDF2_BAD_ROUNDS:
+        CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
+          "insufficient number of rounds (", cmd->argv[2], ")", NULL));
+        break;
+
+      case SQL_PASSWD_ERR_PBKDF2_BAD_LENGTH:
+        CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "insufficient length (",
+          cmd->argv[3], ")", NULL));
+        break;
+
+      case 0:
+        break;
+    }
 
-#if OPENSSL_VERSION_NUMBER < 0x1000003f
-  /* The necesary OpenSSL support for non-SHA1 digests for PBKDF2 appeared in
-   * 1.0.0c.
-   */
-  if (EVP_MD_type(md) != EVP_MD_type(EVP_sha1())) {
-    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "Use of non-SHA1 digests for "
-      "PBKDF2 requires OpenSSL-1.0.0c or later (currently using ",
-      OPENSSL_VERSION_TEXT, ")", NULL));
-  }
-#endif /* OpenSSL-1.0.0b and earlier */
+    c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
+    c->argv[0] = (void *) md;
+    c->argv[1] = palloc(c->pool, sizeof(int));
+    *((int *) c->argv[1]) = iter;
+    c->argv[2] = palloc(c->pool, sizeof(int));
+    *((int *) c->argv[2]) = len;
 
-  iter = atoi(cmd->argv[2]);
-  if (iter < 1) {
-    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "insufficient number of rounds (",
-      cmd->argv[2], ")", NULL));
-  }
+  } else if (cmd->argc == 2) {
+    if (strncasecmp(cmd->argv[1], "sql:/", 5) != 0) {
+      CONF_ERROR(cmd, "badly formatted parameter");
+    }
 
-  len = atoi(cmd->argv[3]);
-  if (len < 1) {
-    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "insufficient length (",
-      cmd->argv[3], ")", NULL));
-  }
+    c = add_config_param(cmd->argv[0], 1, NULL);
+    c->argv[0] = pstrdup(c->pool, cmd->argv[1]);
 
-  c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
-  c->argv[0] = (void *) md;
-  c->argv[1] = palloc(c->pool, sizeof(int));
-  *((int *) c->argv[1]) = iter;
-  c->argv[2] = palloc(c->pool, sizeof(int));
-  *((int *) c->argv[2]) = len;
+  } else {
+    CONF_ERROR(cmd, "wrong number of parameters");
+  }
 
   return PR_HANDLED(cmd);
 }
@@ -984,13 +1130,6 @@ static int sql_passwd_sess_init(void) {
     sql_passwd_encoding = *((unsigned int *) c->argv[0]);
   }
 
-  c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordPBKDF2", FALSE);
-  if (c != NULL) {
-    sql_passwd_pbkdf2_digest = c->argv[0];
-    sql_passwd_pbkdf2_iter = *((int *) c->argv[1]);
-    sql_passwd_pbkdf2_len = *((int *) c->argv[2]);
-  }
-
   c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordOptions", FALSE);
   while (c != NULL) {
     unsigned long opts;
diff --git a/contrib/mod_tls.c b/contrib/mod_tls.c
index 6afb398..3803e18 100644
--- a/contrib/mod_tls.c
+++ b/contrib/mod_tls.c
@@ -69,8 +69,8 @@
 #define MOD_TLS_VERSION		"mod_tls/2.6"
 
 /* Make sure the version of proftpd is as necessary. */
-#if PROFTPD_VERSION_NUMBER < 0x0001030402 
-# error "ProFTPD 1.3.4rc2 or later required"
+#if PROFTPD_VERSION_NUMBER < 0x0001030504 
+# error "ProFTPD 1.3.5rc4 or later required"
 #endif
 
 extern session_t session;
@@ -390,7 +390,7 @@ static char *tls_passphrase_provider = NULL;
 #define TLS_PROTO_TLS_V1		0x0002
 #define TLS_PROTO_TLS_V1_1		0x0004
 #define TLS_PROTO_TLS_V1_2		0x0008
-#define TLS_PROTO_DEFAULT		TLS_PROTO_SSL_V3|TLS_PROTO_TLS_V1
+#define TLS_PROTO_DEFAULT		(TLS_PROTO_SSL_V3|TLS_PROTO_TLS_V1)
 
 #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
 static int tls_ssl_opts = (SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_SINGLE_DH_USE)^SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
@@ -478,6 +478,8 @@ static int tls_renegotiate_timeout = 30;
 static unsigned char tls_renegotiate_required = TRUE;
 #endif
 
+#define TLS_NETIO_NOTE		"mod_tls.SSL"
+
 static pr_netio_t *tls_ctrl_netio = NULL;
 static pr_netio_stream_t *tls_ctrl_rd_nstrm = NULL;
 static pr_netio_stream_t *tls_ctrl_wr_nstrm = NULL;
@@ -603,8 +605,9 @@ static void tls_diags_cb(const SSL *ssl, int where, int ret) {
               "aborting connection");
 
             tls_end_sess(ctrl_ssl, PR_NETIO_STRM_CTRL, 0);
-            tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data =
-              ctrl_ssl = NULL;
+            pr_table_remove(tls_ctrl_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+            pr_table_remove(tls_ctrl_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
+            ctrl_ssl = NULL;
 
             pr_session_disconnect(&tls_module, PR_SESS_DISCONNECT_CONFIG_ACL,
               "TLSOption AllowClientRenegotiations");
@@ -638,8 +641,9 @@ static void tls_diags_cb(const SSL *ssl, int where, int ret) {
               "aborting connection");
 
             tls_end_sess(ctrl_ssl, PR_NETIO_STRM_CTRL, 0);
-            tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data =
-              ctrl_ssl = NULL;
+            pr_table_remove(tls_ctrl_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+            pr_table_remove(tls_ctrl_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
+            ctrl_ssl = NULL;
 
             pr_session_disconnect(&tls_module, PR_SESS_DISCONNECT_CONFIG_ACL,
               "TLSOption AllowClientRenegotiations");
@@ -752,9 +756,27 @@ static void tls_msg_cb(int io_flag, int version, int content_type,
     case TLS1_VERSION:
       version_str = "TLSv1";
       break;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+    case TLS1_1_VERSION:
+      version_str = "TLSv1.1";
+      break;
+
+    case TLS1_2_VERSION:
+      version_str = "TLSv1.2";
+      break;
+#endif
+
+    default:
+      tls_log("[msg] unknown/unsupported version: %d", version);
+      break;
   }
 
   if (version == SSL3_VERSION ||
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+      version == TLS1_1_VERSION ||
+      version == TLS1_2_VERSION ||
+#endif
       version == TLS1_VERSION) {
 
     switch (content_type) {
@@ -2322,23 +2344,27 @@ static int tls_renegotiate_timeout_cb(CALLBACK_FRAME) {
       tls_log("%s", "requested TLS renegotiation timed out on control channel");
       tls_log("%s", "shutting down control channel TLS session");
       tls_end_sess(ctrl_ssl, PR_NETIO_STRM_CTRL, 0);
-      tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data =
-        ctrl_ssl = NULL;
+      pr_table_remove(tls_ctrl_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+      pr_table_remove(tls_ctrl_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
+      ctrl_ssl = NULL;
     }
   }
 
   if ((tls_flags & TLS_SESS_ON_DATA) &&
       (tls_flags & TLS_SESS_DATA_RENEGOTIATING)) {
+    SSL *ssl;
 
-    if (!SSL_renegotiate_pending((SSL *) tls_data_wr_nstrm->strm_data)) {
+    ssl = pr_table_get(tls_data_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
+    if (!SSL_renegotiate_pending(ssl)) {
       tls_log("%s", "data channel TLS session renegotiated");
       tls_flags &= ~TLS_SESS_DATA_RENEGOTIATING;
 
     } else if (tls_renegotiate_required) {
       tls_log("%s", "requested TLS renegotiation timed out on data channel");
       tls_log("%s", "shutting down data channel TLS session");
-      tls_end_sess((SSL *) tls_data_wr_nstrm->strm_data, PR_NETIO_STRM_DATA, 0);
-      tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data = NULL;
+      tls_end_sess(ssl, PR_NETIO_STRM_DATA, 0);
+      pr_table_remove(tls_data_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+      pr_table_remove(tls_data_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
     }
   }
 
@@ -2724,6 +2750,32 @@ static int tls_init_ctx(void) {
   return 0;
 }
 
+static const char *tls_get_proto_str(pool *p, unsigned int protos) {
+  char *proto_str = "";
+
+  if (protos & TLS_PROTO_SSL_V3) {
+    proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "",
+      "SSLv3", NULL);
+  }
+
+  if (protos & TLS_PROTO_TLS_V1) {
+    proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "",
+      "TLSv1", NULL);
+  }
+
+  if (protos & TLS_PROTO_TLS_V1_1) {
+    proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "",
+      "TLSv1.1", NULL);
+  }
+
+  if (protos & TLS_PROTO_TLS_V1_2) {
+    proto_str = pstrcat(p, proto_str, *proto_str ? ", " : "",
+      "TLSv1.2", NULL);
+  }
+
+  return proto_str;
+}
+
 static int tls_init_server(void) {
   config_rec *c = NULL;
   char *tls_ca_cert = NULL, *tls_ca_path = NULL, *tls_ca_chain = NULL;
@@ -2736,8 +2788,7 @@ static int tls_init_server(void) {
     tls_protocol = *((unsigned int *) c->argv[0]);
   }
 
-  if ((tls_protocol & TLS_PROTO_SSL_V3) &&
-      (tls_protocol & TLS_PROTO_TLS_V1)) {
+  if (tls_protocol == TLS_PROTO_DEFAULT) {
     /* This is the default, so there is no need to do anything. */
 #if OPENSSL_VERSION_NUMBER >= 0x10001000L
     pr_log_debug(DEBUG8, MOD_TLS_VERSION ": supporting SSLv3, TLSv1, TLSv1.1, TLSv1.2 protocols");
@@ -2745,26 +2796,75 @@ static int tls_init_server(void) {
     pr_log_debug(DEBUG8, MOD_TLS_VERSION ": supporting SSLv3, TLSv1 protocols");
 #endif /* OpenSSL-1.0.1 or later */
 
-  } else if (tls_protocol & TLS_PROTO_SSL_V3) {
+  } else if (tls_protocol == TLS_PROTO_SSL_V3) {
     SSL_CTX_set_ssl_version(ssl_ctx, SSLv3_server_method());
     pr_log_debug(DEBUG8, MOD_TLS_VERSION ": supporting SSLv3 protocol only");
 
-  } else if (tls_protocol & TLS_PROTO_TLS_V1) {
+  } else if (tls_protocol == TLS_PROTO_TLS_V1) {
     SSL_CTX_set_ssl_version(ssl_ctx, TLSv1_server_method());
     pr_log_debug(DEBUG8, MOD_TLS_VERSION ": supporting TLSv1 protocol only");
 
 #if OPENSSL_VERSION_NUMBER >= 0x10001000L
-  } else if (tls_protocol & TLS_PROTO_TLS_V1_1) {
+  } else if (tls_protocol == TLS_PROTO_TLS_V1_1) {
     SSL_CTX_set_ssl_version(ssl_ctx, TLSv1_1_server_method());
     pr_log_debug(DEBUG8, MOD_TLS_VERSION ": supporting TLSv1.1 protocol only");
 
-  } else if (tls_protocol & TLS_PROTO_TLS_V1_2) {
+  } else if (tls_protocol == TLS_PROTO_TLS_V1_2) {
     SSL_CTX_set_ssl_version(ssl_ctx, TLSv1_2_server_method());
     pr_log_debug(DEBUG8, MOD_TLS_VERSION ": supporting TLSv1.2 protocol only");
 
 #endif /* OpenSSL-1.0.1 or later */
+
+  } else {
+    int disable_proto = (SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
+
+#ifdef SSL_OP_NO_TLSv1_1
+    disable_proto |= SSL_OP_NO_TLSv1_1;
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+    disable_proto |= SSL_OP_NO_TLSv1_2;
+#endif
+
+    /* For any other value of tls_protocol, it will be a combination of
+     * protocol versions.  Thus we MUST use SSLv23_server_method(), and then
+     * try to use SSL_CTX_set_options() to restrict/disable the protocol
+     * versions which are NOT requested.
+     */
+
+    if (tls_protocol & TLS_PROTO_SSL_V3) {
+      /* Clear the "no SSLv3" option. */
+      disable_proto &= ~SSL_OP_NO_SSLv3;
+    }
+
+    if (tls_protocol & TLS_PROTO_TLS_V1) {
+      /* Clear the "no TLSv1" option. */
+      disable_proto &= ~SSL_OP_NO_TLSv1;
+    }
+
+    if (tls_protocol & TLS_PROTO_TLS_V1_1) {
+#ifdef SSL_OP_NO_TLSv1_1
+      /* Clear the "no TLSv1.1" option. */
+      disable_proto &= ~SSL_OP_NO_TLSv1_1;
+#endif
+    }
+
+    if (tls_protocol & TLS_PROTO_TLS_V1_2) {
+#ifdef SSL_OP_NO_TLSv1_2
+      /* Clear the "no TLSv1.2" option. */
+      disable_proto &= ~SSL_OP_NO_TLSv1_2;
+#endif
+    }
+
+    /* Per the comments in <ssl/ssl.h>, SSL_CTX_set_options() uses |= on
+     * the previous value.  This means we can easily OR in our new option
+     * values with any previously set values.
+     */
+    pr_log_debug(DEBUG8, MOD_TLS_VERSION ": supporting %s protocols only",
+      tls_get_proto_str(main_server->pool, tls_protocol));
+    SSL_CTX_set_options(ssl_ctx, disable_proto);
   }
 
+
   tls_ca_cert = get_param_ptr(main_server->conf, "TLSCACertificateFile", FALSE);
   tls_ca_path = get_param_ptr(main_server->conf, "TLSCACertificatePath", FALSE);
 
@@ -2790,7 +2890,7 @@ static int tls_init_server(void) {
 
     if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
       tls_log("error setting default verification locations: %s",
-          tls_get_errors());
+        tls_get_errors());
     }
   }
 
@@ -3644,7 +3744,13 @@ static int tls_accept(conn_t *conn, unsigned char on_data) {
     pr_buffer_t *strm_buf;
 
     ctrl_ssl = ssl;
-    tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data = (void *) ssl;
+
+    pr_table_add(tls_ctrl_rd_nstrm->notes,
+      pstrdup(tls_ctrl_rd_nstrm->strm_pool, TLS_NETIO_NOTE),
+      ssl, sizeof(SSL *));
+    pr_table_add(tls_ctrl_wr_nstrm->notes,
+      pstrdup(tls_ctrl_wr_nstrm->strm_pool, TLS_NETIO_NOTE),
+      ssl, sizeof(SSL *));
 
 #if OPENSSL_VERSION_NUMBER >= 0x009080dfL
     if (SSL_get_secure_renegotiation_support(ssl) == 1) {
@@ -3676,7 +3782,12 @@ static int tls_accept(conn_t *conn, unsigned char on_data) {
   } else if (conn == session.d) {
     pr_buffer_t *strm_buf;
 
-    tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data = (void *) ssl;
+    pr_table_add(tls_data_rd_nstrm->notes,
+      pstrdup(tls_data_rd_nstrm->strm_pool, TLS_NETIO_NOTE),
+      ssl, sizeof(SSL *));
+    pr_table_add(tls_data_wr_nstrm->notes,
+      pstrdup(tls_data_wr_nstrm->strm_pool, TLS_NETIO_NOTE),
+      ssl, sizeof(SSL *));
 
     /* Clear any data from the NetIO stream buffers which may have been read
      * in before the SSL/TLS handshake occurred (Bug#3624).
@@ -3764,7 +3875,8 @@ static int tls_accept(conn_t *conn, unsigned char on_data) {
         tls_log("client did not reuse SSL session, rejecting data connection "
           "(see the NoSessionReuseRequired TLSOptions parameter)");
         tls_end_sess(ssl, PR_NETIO_STRM_DATA, 0);
-        tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data = NULL;
+        pr_table_remove(tls_data_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+        pr_table_remove(tls_data_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
         return -1;
 
       } else {
@@ -3802,7 +3914,8 @@ static int tls_accept(conn_t *conn, unsigned char on_data) {
               "rejecting data connection (see the NoSessionReuseRequired "
               "TLSOptions parameter)");
             tls_end_sess(ssl, PR_NETIO_STRM_DATA, 0);
-            tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data = NULL;
+            pr_table_remove(tls_data_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+            pr_table_remove(tls_data_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
             return -1;
 
           } else {
@@ -3845,7 +3958,8 @@ static int tls_accept(conn_t *conn, unsigned char on_data) {
           tls_log("BUG: unable to determine whether client reused SSL session: SSL_get_session() for control connection return NULL");
           tls_log("rejecting data connection (see TLSOption NoSessionReuseRequired)");
           tls_end_sess(ssl, PR_NETIO_STRM_DATA, 0);
-          tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data = NULL;
+          pr_table_remove(tls_data_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+          pr_table_remove(tls_data_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
           return -1;
         }
 
@@ -3854,7 +3968,8 @@ static int tls_accept(conn_t *conn, unsigned char on_data) {
         tls_log("BUG: unable to determine whether client reused SSL session: SSL_get_session() for control connection return NULL");
         tls_log("rejecting data connection (see TLSOption NoSessionReuseRequired)");
         tls_end_sess(ssl, PR_NETIO_STRM_DATA, 0);
-        tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data = NULL;
+        pr_table_remove(tls_data_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+        pr_table_remove(tls_data_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
         return -1;
       }
     }
@@ -4016,7 +4131,12 @@ static int tls_connect(conn_t *conn) {
   if (conn == session.d) {
     pr_buffer_t *strm_buf;
 
-    tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data = (void *) ssl;
+    pr_table_add(tls_data_rd_nstrm->notes,
+      pstrdup(tls_data_rd_nstrm->strm_pool, TLS_NETIO_NOTE),
+      ssl, sizeof(SSL *));
+    pr_table_add(tls_data_wr_nstrm->notes,
+      pstrdup(tls_data_wr_nstrm->strm_pool, TLS_NETIO_NOTE),
+      ssl, sizeof(SSL *));
 
     /* Clear any data from the NetIO stream buffers which may have been read
      * in before the SSL/TLS handshake occurred (Bug#3624).
@@ -4861,8 +4981,7 @@ static int tls_seed_prng(void) {
    */
   if (RAND_load_file(tls_rand_file, 1024) != 1024) {
 #endif
-
-    time_t now;
+    struct timeval tv;
     pid_t pid;
  
 #if OPENSSL_VERSION_NUMBER >= 0x00905100L
@@ -4874,8 +4993,9 @@ static int tls_seed_prng(void) {
 #endif
  
     /* No random file found, create new seed. */
-    now = time(NULL);
-    RAND_seed(&now, sizeof(time_t));
+    gettimeofday(&tv, NULL);
+    RAND_seed(&(tv.tv_sec), sizeof(tv.tv_sec));
+    RAND_seed(&(tv.tv_usec), sizeof(tv.tv_usec));
 
     pid = getpid();
     RAND_seed(&pid, sizeof(pid_t));
@@ -6700,23 +6820,23 @@ static void tls_netio_abort_cb(pr_netio_stream_t *nstrm) {
 
 static int tls_netio_close_cb(pr_netio_stream_t *nstrm) {
   int res = 0;
+  SSL *ssl = NULL;
 
-  if (nstrm->strm_data) {
-
+  ssl = pr_table_get(nstrm->notes, TLS_NETIO_NOTE, NULL);
+  if (ssl != NULL) {
     if (nstrm->strm_type == PR_NETIO_STRM_CTRL &&
         nstrm->strm_mode == PR_NETIO_IO_WR) {
-      tls_end_sess((SSL *) nstrm->strm_data, nstrm->strm_type, 0);
-      tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data =
-        nstrm->strm_data = NULL;
+      tls_end_sess(ssl, nstrm->strm_type, 0);
+      pr_table_remove(tls_ctrl_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+      pr_table_remove(tls_ctrl_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
       tls_ctrl_netio = NULL;
       tls_flags &= ~TLS_SESS_ON_CTRL;
     }
 
     if (nstrm->strm_type == PR_NETIO_STRM_DATA &&
         nstrm->strm_mode == PR_NETIO_IO_WR) {
-      tls_end_sess((SSL *) nstrm->strm_data, nstrm->strm_type, 0);
-      tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data =
-        nstrm->strm_data = NULL;
+      pr_table_remove(tls_data_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+      pr_table_remove(tls_data_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
       tls_data_netio = NULL;
       tls_flags &= ~TLS_SESS_ON_DATA;
     }
@@ -6821,6 +6941,9 @@ static int tls_netio_postopen_cb(pr_netio_stream_t *nstrm) {
     /* Enforce the "data" part of TLSRequired, if configured. */
     if (tls_required_on_data == 1 ||
         (tls_flags & TLS_SESS_NEED_DATA_PROT)) {
+      SSL *ssl = NULL;
+
+      ssl = pr_table_get(nstrm->notes, TLS_NETIO_NOTE, NULL);
 
       /* XXX How to force 421 response code for failed secure FXP/SSCN? */
 
@@ -6847,7 +6970,7 @@ static int tls_netio_postopen_cb(pr_netio_stream_t *nstrm) {
          * This may be too strict of a requirement, though.
          */
         ctrl_cert = SSL_get_peer_certificate(ctrl_ssl);
-        data_cert = SSL_get_peer_certificate((SSL *) nstrm->strm_data);
+        data_cert = SSL_get_peer_certificate(ssl);
 
         if (ctrl_cert != NULL &&
             data_cert != NULL) {
@@ -6856,10 +6979,9 @@ static int tls_netio_postopen_cb(pr_netio_stream_t *nstrm) {
             X509_free(data_cert);
 
             /* Properly shutdown the SSL session. */
-            tls_end_sess((SSL *) nstrm->strm_data, nstrm->strm_type, 0);
-
-            tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data =
-              nstrm->strm_data = NULL;
+            tls_end_sess(ssl, nstrm->strm_type, 0);
+            pr_table_remove(tls_data_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+            pr_table_remove(tls_data_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
 
             tls_log("%s", "unable to open data connection: control/data "
               "certificate mismatch");
@@ -6868,11 +6990,13 @@ static int tls_netio_postopen_cb(pr_netio_stream_t *nstrm) {
             return -1;
           }
 
-          if (ctrl_cert)
+          if (ctrl_cert) {
             X509_free(ctrl_cert);
+          }
 
-          if (data_cert)
+          if (data_cert) {
             X509_free(data_cert);
+          }
         }
 
       } else if (tls_sscn_mode == TLS_SSCN_MODE_CLIENT) {
@@ -6889,7 +7013,7 @@ static int tls_netio_postopen_cb(pr_netio_stream_t *nstrm) {
       /* Make sure blinding is turned on. (For some reason, this only seems
        * to be allowed on SSL objects, not on SSL_CTX objects.  Bummer).
        */
-      tls_blinding_on((SSL *) nstrm->strm_data);
+      tls_blinding_on(ssl);
 #endif
 
       tls_flags |= TLS_SESS_ON_DATA;
@@ -6901,16 +7025,15 @@ static int tls_netio_postopen_cb(pr_netio_stream_t *nstrm) {
 
 static int tls_netio_read_cb(pr_netio_stream_t *nstrm, char *buf,
     size_t buflen) {
+  SSL *ssl;
 
-  if (nstrm->strm_data) {
-    SSL *ssl;
+  ssl = pr_table_get(nstrm->notes, TLS_NETIO_NOTE, NULL);
+  if (ssl != NULL) {
     BIO *rbio, *wbio;
     int bread = 0, bwritten = 0;
     ssize_t res = 0;
     unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
 
-    ssl = nstrm->strm_data;
-
     rbio = SSL_get_rbio(ssl);
     rbio_rbytes = BIO_number_read(rbio);
     rbio_wbytes = BIO_number_written(rbio);
@@ -6948,8 +7071,9 @@ static int tls_netio_read_cb(pr_netio_stream_t *nstrm, char *buf,
 static pr_netio_stream_t *tls_netio_reopen_cb(pr_netio_stream_t *nstrm, int fd,
     int mode) {
 
-  if (nstrm->strm_fd != -1)
+  if (nstrm->strm_fd != -1) {
     close(nstrm->strm_fd);
+  }
 
   nstrm->strm_fd = fd;
   nstrm->strm_mode = mode;
@@ -6972,8 +7096,8 @@ static int tls_netio_shutdown_cb(pr_netio_stream_t *nstrm, int how) {
          nstrm->strm_type == PR_NETIO_STRM_DATA)) {
       SSL *ssl;
 
-      ssl = nstrm->strm_data;
-      if (ssl) {
+      ssl = pr_table_get(nstrm->notes, TLS_NETIO_NOTE, NULL);
+      if (ssl != NULL) {
         BIO *rbio, *wbio;
         int bread = 0, bwritten = 0;
         unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
@@ -7015,16 +7139,15 @@ static int tls_netio_shutdown_cb(pr_netio_stream_t *nstrm, int how) {
 
 static int tls_netio_write_cb(pr_netio_stream_t *nstrm, char *buf,
     size_t buflen) {
+  SSL *ssl;
 
-  if (nstrm->strm_data) {
-    SSL *ssl;
+  ssl = pr_table_get(nstrm->notes, TLS_NETIO_NOTE, NULL);
+  if (ssl != NULL) {
     BIO *rbio, *wbio;
     int bread = 0, bwritten = 0;
     ssize_t res = 0;
     unsigned long rbio_rbytes, rbio_wbytes, wbio_rbytes, wbio_wbytes;
 
-    ssl = nstrm->strm_data;
-
     rbio = SSL_get_rbio(ssl);
     rbio_rbytes = BIO_number_read(rbio);
     rbio_wbytes = BIO_number_written(rbio);
@@ -7577,7 +7700,9 @@ MODRET tls_ccc(cmd_rec *cmd) {
    */
 
   tls_end_sess(ctrl_ssl, PR_NETIO_STRM_CTRL, TLS_SHUTDOWN_BIDIRECTIONAL);
-  ctrl_ssl = tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data = NULL;
+  pr_table_remove(tls_ctrl_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+  pr_table_remove(tls_ctrl_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
+  ctrl_ssl = NULL;
 
   /* Remove our NetIO for the control channel. */
   pr_unregister_netio(PR_NETIO_STRM_CTRL);
@@ -9068,10 +9193,10 @@ static void tls_timeout_ev(const void *event_data, void *user_data) {
      * if there is one.
      */ 
     tls_end_sess(ctrl_ssl, PR_NETIO_STRM_CTRL, 0);
-    tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data =
-      ctrl_ssl = NULL;
+    pr_table_remove(tls_ctrl_rd_nstrm->notes, TLS_NETIO_NOTE, NULL);
+    pr_table_remove(tls_ctrl_wr_nstrm->notes, TLS_NETIO_NOTE, NULL);
+    ctrl_ssl = NULL;
   }
-
 }
 
 static void tls_get_passphrases(void) {
diff --git a/contrib/mod_wrap2/mod_wrap2.c b/contrib/mod_wrap2/mod_wrap2.c
index 6915586..e801106 100644
--- a/contrib/mod_wrap2/mod_wrap2.c
+++ b/contrib/mod_wrap2/mod_wrap2.c
@@ -1,7 +1,7 @@
 /*
  * ProFTPD: mod_wrap2 -- tcpwrappers-like access control
  *
- * Copyright (c) 2000-2013 TJ Saunders
+ * Copyright (c) 2000-2014 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
@@ -135,7 +135,7 @@ int wrap2_log(const char *fmt, ...) {
 }
 
 static int wrap2_openlog(void) {
-  int res = 0;
+  int res = 0, xerrno;
 
   /* Sanity check */
   wrap2_logname = get_param_ptr(main_server->conf, "WrapLog", FALSE);
@@ -151,9 +151,11 @@ static int wrap2_openlog(void) {
   pr_signals_block();
   PRIVS_ROOT
   res = pr_log_openfile(wrap2_logname, &wrap2_logfd, PR_LOG_SYSTEM_MODE);
+  xerrno = errno;
   PRIVS_RELINQUISH
   pr_signals_unblock();
 
+  errno = xerrno;
   return res;
 }
 
@@ -325,32 +327,44 @@ static char *wrap2_get_hostname(wrap2_host_t *host) {
     int reverse_dns;
     size_t namelen;
 
-    /* Manually tweak the UseReverseDNS setting, and any caches, so that
-     * we really do use the DNS name here if possible.
-     */
-    pr_netaddr_clear_cache();
-
     reverse_dns = pr_netaddr_set_reverse_dns(TRUE);
-    session.c->remote_addr->na_have_dnsstr = FALSE;
-    sstrncpy(host->name, pr_netaddr_get_dnsstr(session.c->remote_addr),
-      sizeof(host->name));
-
-    /* If the retrieved hostname ends in a trailing period, trim it off. */
-    namelen = strlen(host->name); 
-    if (host->name[namelen-1] == '.') {
-      host->name[namelen-1] = '\0';
-      namelen--;
-    }
+    if (reverse_dns) {
+      /* If UseReverseDNS is on, then clear any caches, so that we really do
+       * use the DNS name here if possible.
+       */
+      pr_netaddr_clear_cache();
+
+      session.c->remote_addr->na_have_dnsstr = FALSE;
+      sstrncpy(host->name, pr_netaddr_get_dnsstr(session.c->remote_addr),
+        sizeof(host->name));
+
+      /* If the retrieved hostname ends in a trailing period, trim it off. */
+      namelen = strlen(host->name); 
+      if (host->name[namelen-1] == '.') {
+        host->name[namelen-1] = '\0';
+        namelen--;
+      }
+
+      pr_netaddr_set_reverse_dns(reverse_dns);
+      session.c->remote_addr->na_have_dnsstr = TRUE;
+
+    } else {
+      wrap2_log("'UseReverseDNS off' in effect, NOT resolving %s to DNS name "
+        "for comparison", pr_netaddr_get_ipstr(session.c->remote_addr));
 
-    pr_netaddr_set_reverse_dns(reverse_dns);
-    session.c->remote_addr->na_have_dnsstr = TRUE;
+      sstrncpy(host->name, pr_netaddr_get_dnsstr(session.c->remote_addr),
+        sizeof(host->name));
+      pr_netaddr_set_reverse_dns(reverse_dns);
+    }
   }
 
   return host->name;
 }
 
 static char *wrap2_get_hostinfo(wrap2_host_t *host) {
-  char *hostname = wrap2_get_hostname(host);
+  char *hostname;
+
+  hostname = wrap2_get_hostname(host);
 
   if (WRAP2_IS_KNOWN_HOSTNAME(hostname))
     return hostname;
@@ -360,7 +374,9 @@ static char *wrap2_get_hostinfo(wrap2_host_t *host) {
 
 static char *wrap2_get_client(wrap2_conn_t *conn) {
   static char both[WRAP2_BUFFER_SIZE] = {'\0'};
-  char *hostinfo = wrap2_get_hostinfo(conn->client);
+  char *hostinfo;
+
+  hostinfo = wrap2_get_hostinfo(conn->client);
 
   if (strcasecmp(wrap2_get_user(conn), WRAP2_UNKNOWN) != 0) {
     snprintf(both, sizeof(both), "%s@%s", conn->user, hostinfo);
@@ -510,16 +526,18 @@ static unsigned char wrap2_match_host(char *tok, wrap2_host_t *host) {
     return TRUE;
 
   } else if (strcasecmp(tok, "KNOWN") == 0) {
+    char *name;
 
     /* Check address and name. */
-    char *name = wrap2_get_hostname(host);
+    name = wrap2_get_hostname(host);
     return ((strcasecmp(wrap2_get_hostaddr(host), WRAP2_UNKNOWN) != 0) &&
       WRAP2_IS_KNOWN_HOSTNAME(name));
 
   } else if (strcasecmp(tok, "LOCAL") == 0) {
+    char *name;
 
     /* Local: no dots in name. */
-    char *name = wrap2_get_hostname(host);
+    name = wrap2_get_hostname(host);
     return (strchr(name, '.') == NULL && WRAP2_IS_KNOWN_HOSTNAME(name));
 
   } else if (tok[(len = strlen(tok)) - 1] == '.') {
@@ -1790,8 +1808,7 @@ MODRET wrap2_pre_pass(cmd_rec *cmd) {
 
   wrap2_log("%s", "checking access rules for connection");
 
-  if (strcasecmp(wrap2_get_hostname(conn.client), WRAP2_PARANOID) == 0 ||
-      !wrap2_allow_access(&conn)) {
+  if (wrap2_allow_access(&conn) == FALSE) {
     char *msg = NULL;
 
     /* Log the denied connection */
@@ -1976,8 +1993,7 @@ static int wrap2_sess_init(void) {
 
       wrap2_log("%s", "checking access rules for connection");
 
-      if (strcasecmp(wrap2_get_hostname(conn.client), WRAP2_PARANOID) == 0 ||
-          !wrap2_allow_access(&conn)) {
+      if (wrap2_allow_access(&conn) == FALSE) {
         char *msg = NULL;
 
         /* Log the denied connection */
diff --git a/doc/contrib/mod_ban.html b/doc/contrib/mod_ban.html
index 09788ab..e0e4f32 100644
--- a/doc/contrib/mod_ban.html
+++ b/doc/contrib/mod_ban.html
@@ -1,4 +1,4 @@
-<!-- $Id: mod_ban.html,v 1.20 2013/08/14 21:40:17 castaglia Exp $ -->
+<!-- $Id: mod_ban.html,v 1.21 2014/02/23 17:11:36 castaglia Exp $ -->
 <!-- $Source: /cvsroot/proftp/proftpd/doc/contrib/mod_ban.html,v $ -->
 
 <html>
@@ -235,6 +235,11 @@ supported events are:
   </tr>
 
   <tr>
+    <td><code>RootLogin</code></td>
+    <td>Host ban</td>
+  </tr>
+
+  <tr>
     <td><code>TimeoutIdle</code></td>
     <td>Host ban</td>
   </tr>
@@ -623,12 +628,12 @@ login as root.  How would I do this?<br>
 <hr><br>
 
 Author: <i>$Author: castaglia $</i><br>
-Last Updated: <i>$Date: 2013/08/14 21:40:17 $</i><br>
+Last Updated: <i>$Date: 2014/02/23 17:11:36 $</i><br>
 
 <br><hr>
 
 <font size=2><b><i>
-© Copyright 2004-2013 TJ Saunders<br>
+© Copyright 2004-2014 TJ Saunders<br>
  All Rights Reserved<br>
 </i></b></font>
 
diff --git a/doc/contrib/mod_sftp.html b/doc/contrib/mod_sftp.html
index c2309ff..62ffbc0 100644
--- a/doc/contrib/mod_sftp.html
+++ b/doc/contrib/mod_sftp.html
@@ -1,4 +1,4 @@
-<!-- $Id: mod_sftp.html,v 1.78 2014/01/13 18:40:20 castaglia Exp $ -->
+<!-- $Id: mod_sftp.html,v 1.82 2014/03/03 05:40:13 castaglia Exp $ -->
 <!-- $Source: /cvsroot/proftp/proftpd/doc/contrib/mod_sftp.html,v $ -->
 
 <html>
@@ -302,7 +302,7 @@ of supported cipher algorithms is, in the default order of preference:
   <li>aes192-ctr
   <li>aes128-ctr
   <li>aes256-cbc
-  <li>aes196-cbc
+  <li>aes192-cbc
   <li>aes128-cbc
   <li>blowfish-ctr
   <li>blowfish-cbc
@@ -843,6 +843,21 @@ For example:
 <p>
 The currently implemented options are:
 <ul>
+  <li><code>AllowInsecureLogin</code><br>
+    <p>
+    By default, <code>mod_sftp</code> will <b>not</b> allow password or
+    keyboard-interactive authentication <b>if</b> either the 'none' cipher
+    <b>or</b> MAC/digest is selected.  (These must be explicitly allowed via
+    <code>SFTPCiphers</code> and <code>SFTPDigests</code> as well.)  However,
+    some sites may deliberately wish to allow such logins, as for <i>e.g.</i>
+    performance testing.  To allow such logins/sessions, then, use this option.
+
+    <p>
+    <b>Note</b> that this option first appeared in
+    <code>proftpd-1.3.5</code>.
+  </li>
+
+  <p>
   <li><code>IgnoreSCPUploadPerms</code><br>
     <p>
     When an SCP client uploads a file, the desired permissions on the file
@@ -851,6 +866,7 @@ The currently implemented options are:
     If you need more FTP-like functionality for any reason and wish to
     have <code>mod_sftp</code> silently ignore any permissions sent by the
     SCP client, use this option.
+  </li>
 
   <p>
   <li><code>IgnoreSCPUploadTimes</code><br>
@@ -1448,6 +1464,23 @@ For these clients, use this configuration to disable the optimization:
 </pre>
 
 <p>
+<i>Cipher Implementations</i><br>
+Some SFTP clients (most notably the SecureBlackBox libraries) do not implement
+the <code>blowfish-ctr</code> cipher correctly.  If your site needs to
+support such clients, then you will need to use the
+<a href="#SFTPCiphers"><code>SFTPCiphers</code></a> directive to configure
+a list of ciphers which does <b>not</b> include <code>blowfish-ctr</code>.
+Clients with this bug will show up in the <code>SFTPLog</code> with this
+log message:
+<pre>
+  client sent buggy/malicious packet payload length, ignoring
+</pre>
+<b>However</b>, this log message can also be caused by other factors.  It
+is the combination of this error message, <b>and</b> the negotation of
+the <code>blowfish-ctr</code> cipher, that indicates the use of these
+buggy clients.
+
+<p>
 <b>FIPS Compliance</b><br>
 FIPS stands for "Federal Information Processing Standard", and refers to
 a series of information processing standards issued and used by the US
@@ -2074,6 +2107,22 @@ virtual host/port).  So either <i>a)</i> the SFTP client is connecting to
 the wrong port on your server, or <i>b)</i> perhaps "SFTPEngine on" is not
 set in the virtual host for that port.
 
+<p><a name="SFTPPTYAllocationFailed">
+<font color=red>Question</font>: I try to connect to <code>proftpd</code>
+with <code>mod_sftp</code> and the connection fails with an error messages
+like "PTY allocation request failed on channel 0" or "Server refused to
+allocate pty".  What is going wrong?<br>
+<font color=blue>Answer</font>: This error message happens when you
+try to connect to <code>mod_sftp</code> using an SSH client which wants
+a <em>shell</em> session, rather than an SFTP session or an SCP transfer.
+The <code>mod_sftp</code> module does <b>not</b> support shell sessions
+(unlike OpenSSH).  In the <code>SFTPLog</code>, you would see such
+connection attempts logged like so:
+<pre>
+  mod_sftp/0.9.9[6304]: unsupported 'pty-req' channel requested, ignoring
+  mod_sftp/0.9.9[6304]: unsupported 'shell' channel requested, ignoring
+</pre>
+
 <p><a name="SFTPDirectorySGID">
 <font color=red>Question</font>: When I create a directory via SFTP, in
 a directory with the SGID bit set, the created directory does <b>not</b>
@@ -2090,13 +2139,13 @@ In addition, if your <code>proftpd</code> is running on Linux, you may
 need to disable the <a href="../modules/mod_cap.html"><code>mod_cap</code></a>
 module, or configure it to preserve the SGID bit (<i>e.g.</i> via the
 <a href="../modules/mod_cap.html#CapabilitiesSet"><code>CAP_FSETID</code></a>
-capability.
+capability).
 
 <p>
 <hr><br>
 
 Author: <i>$Author: castaglia $</i><br>
-Last Updated: <i>$Date: 2014/01/13 18:40:20 $</i><br>
+Last Updated: <i>$Date: 2014/03/03 05:40:13 $</i><br>
 
 <br><hr>
 
diff --git a/doc/contrib/mod_site_misc.html b/doc/contrib/mod_site_misc.html
index eda4456..9f4716f 100644
--- a/doc/contrib/mod_site_misc.html
+++ b/doc/contrib/mod_site_misc.html
@@ -1,4 +1,4 @@
-<!-- $Id: mod_site_misc.html,v 1.4 2013/08/14 21:40:18 castaglia Exp $ -->
+<!-- $Id: mod_site_misc.html,v 1.5 2014/05/04 19:49:57 castaglia Exp $ -->
 <!-- $Source: /cvsroot/proftp/proftpd/doc/contrib/mod_site_misc.html,v $ -->
 
 <html>
@@ -159,7 +159,7 @@ For example:
   SITE UTIME 200402240836 file.txt
   SITE UTIME 20040224083655 file.txt
 </pre>
-would set the access and modification timestamps on <code>file.txt</code>
+would set the access <b>and</b> modification timestamps on <code>file.txt</code>
 to 8:36 AM, Febrary 24, 2004 (or 8:36:55 AM, Febrary 24, 2004, respectively).
 
 <p>
@@ -206,12 +206,12 @@ module:
 <hr><br>
 
 Author: <i>$Author: castaglia $</i><br>
-Last Updated: <i>$Date: 2013/08/14 21:40:18 $</i><br>
+Last Updated: <i>$Date: 2014/05/04 19:49:57 $</i><br>
 
 <br><hr>
 
 <font size=2><b><i>
-© Copyright 2004-2013 TJ Saunders<br>
+© Copyright 2004-2014 TJ Saunders<br>
  All Rights Reserved<br>
 </i></b></font>
 
diff --git a/doc/contrib/mod_sql_passwd.html b/doc/contrib/mod_sql_passwd.html
index cece351..d2618bb 100644
--- a/doc/contrib/mod_sql_passwd.html
+++ b/doc/contrib/mod_sql_passwd.html
@@ -1,4 +1,4 @@
-<!-- $Id: mod_sql_passwd.html,v 1.13 2013/06/10 17:13:59 castaglia Exp $ -->
+<!-- $Id: mod_sql_passwd.html,v 1.14 2014/05/04 23:15:00 castaglia Exp $ -->
 <!-- $Source: /cvsroot/proftp/proftpd/doc/contrib/mod_sql_passwd.html,v $ -->
 
 <html>
@@ -181,6 +181,16 @@ Use of <em>digest</em> algorithms other than SHA1 for
 <code>SQLPasswordPBKDF2</code> requires OpenSSL-1.0.0c or later; earlier
 versions did not have the necessary APIs.
 
+<p>
+As of <code>proftpd-1.3.5</code>, the <code>SQLPasswordPBKDF2</code> directive
+can instead take a named query, for determining the digest algorithm,
+iteration count, and output length <i>on a per-user basis</i>.  For example:
+<pre>
+  SQLNamedQuery get-user-pbkdf2 SELECT "algo, iter, len FROM user_pbkdf2 WHERE
+user = '%{0}'
+  SQLPasswordPBKDF2 sql://get-user-pbkdf2
+</pre>
+
 <hr>
 <h2><a name="SQLPasswordRounds">SQLPasswordRounds</a></h2>
 <strong>Syntax:</strong> SQLPasswordRounds <em>count</em><br>
@@ -553,12 +563,12 @@ values can be supported by the <code>mod_sql_passwd</code> module.
 <hr><br>
 
 Author: <i>$Author: castaglia $</i><br>
-Last Updated: <i>$Date: 2013/06/10 17:13:59 $</i><br>
+Last Updated: <i>$Date: 2014/05/04 23:15:00 $</i><br>
 
 <br><hr>
 
 <font size=2><b><i>
-© Copyright 2009-2013 TJ Saunders<br>
+© Copyright 2009-2014 TJ Saunders<br>
  All Rights Reserved<br>
 </i></b></font>
 
diff --git a/doc/howto/Chroot.html b/doc/howto/Chroot.html
index f267b1c..ef69a0f 100644
--- a/doc/howto/Chroot.html
+++ b/doc/howto/Chroot.html
@@ -1,4 +1,4 @@
-<!-- $Id: Chroot.html,v 1.8 2011/04/06 20:52:58 castaglia Exp $ -->
+<!-- $Id: Chroot.html,v 1.9 2014/03/13 18:06:26 castaglia Exp $ -->
 <!-- $Source: /cvsroot/proftp/proftpd/doc/howto/Chroot.html,v $ -->
 
 <html>
@@ -18,7 +18,7 @@ One of the most common questions for new users of ProFTPD is "How do I
 restrict my users to only certain directories?" or, phrased another
 way, "How can I put my users in a chroot jail?"  As a common
 question, it definitely has a place in the
-<a href="http://www.proftpd.org/docs/faq/proftpdfaq-5.html#ss5.12">FAQ</a>.
+<a href="http://www.proftpd.org/docs/faq/linked/faq-ch5.html#AEN524">FAQ</a>.
 Many users, I fear, do not read the FAQ carefully, and so miss that section.
 The answer is ProFTPD's <a href="http://www.proftpd.org/docs/directives/linked/config_ref_DefaultRoot.html"><code>DefaultRoot</code></a> configuration
 directive, which accomplishes this functionality by using the
@@ -325,7 +325,7 @@ edited, or maybe the <code>DefaultRoot</code> directive is not in a
 
 <p>
 <hr>
-Last Updated: $Date: 2011/04/06 20:52:58 $<br>
+Last Updated: $Date: 2014/03/13 18:06:26 $<br>
 <hr>
 
 </body>
diff --git a/doc/howto/TLS.html b/doc/howto/TLS.html
index 97b7017..ea26385 100644
--- a/doc/howto/TLS.html
+++ b/doc/howto/TLS.html
@@ -1,4 +1,4 @@
-<!-- $Id: TLS.html,v 1.38 2014/01/22 17:51:53 castaglia Exp $ -->
+<!-- $Id: TLS.html,v 1.39 2014/03/26 18:22:32 castaglia Exp $ -->
 <!-- $Source: /cvsroot/proftp/proftpd/doc/howto/TLS.html,v $ -->
 
 <html>
@@ -701,7 +701,7 @@ my next directory listing fails:
 </pre>
 The <code>TLSLog</code> contains:
 <pre>
-  client did not reuse SSL session, rejecting data connection (see the NoSessionReuseRequired TLSOptions parameter
+  client did not reuse SSL session, rejecting data connection (see the NoSessionReuseRequired TLSOptions parameter)
 </pre>
 but I do <i>not</i> want to use that option, and would like to rely on the
 additional security protection provided by requring SSL session reuse.
@@ -1191,7 +1191,7 @@ configured files were not properly matched up.
 
 <p>
 <hr>
-Last Updated: <i>$Date: 2014/01/22 17:51:53 $</i><br>
+Last Updated: <i>$Date: 2014/03/26 18:22:32 $</i><br>
 <hr>
 
 </body>
diff --git a/doc/modules/mod_core.html b/doc/modules/mod_core.html
index 5c6f4ef..01f0c11 100644
--- a/doc/modules/mod_core.html
+++ b/doc/modules/mod_core.html
@@ -1,4 +1,4 @@
-<!-- $Id: mod_core.html,v 1.42 2013/11/09 23:14:30 castaglia Exp $ -->
+<!-- $Id: mod_core.html,v 1.43 2014/01/30 16:50:10 castaglia Exp $ -->
 <!-- $Source: /cvsroot/proftp/proftpd/doc/modules/mod_core.html,v $ -->
 
 <html>
@@ -46,6 +46,7 @@ The <code>mod_core</code> module handles most of the core FTP commands.
   <li><a href="#ScoreboardMutex">ScoreboardMutex</a>
   <li><a href="#ServerIdent">ServerIdent</a>
   <li><a href="#ServerType">ServerType</a>
+  <li><a href="#SocketBindTight">SocketBindTight</a>
   <li><a href="#SocketOptions">SocketOptions</a>
   <li><a href="#SyslogFacility">SyslogFacility</a>
   <li><a href="#SyslogLevel">SyslogLevel</a>
@@ -1003,6 +1004,82 @@ dedicated to processing all requests from the newly connected client.
 
 <p>
 <hr>
+<h2><a name="SocketBindTight">SocketBindTight</a></h2>
+<strong>Syntax:</strong> SocketBindTight <em>on|off</em><br>
+<strong>Default:</strong> <em>off</em><br>
+<strong>Context:</strong> server config<br>
+<strong>Module:</strong> mod_core<br>
+<strong>Compatibility:</strong> 0.99.0pl6 and later
+
+<p>
+The <code>SocketBindTight</code> directive controls how <code>proftpd</code>
+creates and binds its initial TCP listening sockets in "ServerType standalone"
+mode (see <a href="#ServerType"><code>ServerType</code></a>).  This directive
+has no effect upon servers running with "ServerType inetd", because
+the TCP listening sockets in that mode are not needed or created by
+<code>proftpd</code>.
+
+<p>
+When <code>SocketBindTight</code> is set to <em>off</em> (the default), a
+single TCP listening socket is created for each port that the server must
+listen on, regardless of the number of IP addresses being used by
+<code><VirtualHost></code> configurations.  This has the benefit of
+requiring a relatively small number of file descriptors (one for each socket)
+for the master daemon process, even if a large number of virtual servers are
+configured.  Each of these listening sockets is bound to the "wildcard"
+address, meaning that on all IP addresses on that port (<i>e.g.</i> "*:21").
+
+<p>
+When <code>SocketBindTight</code> is set to <em>on</em>, a TCP listening socket
+is created and bound to <i>a specific IP address</i> for the main "server
+config" server and all configured virtual servers.  This allows for situations
+where an administrator may wish to have a particular port be used by both
+<code>proftpd</code> (on one IP address) and another daemon (on a different IP
+address).  The drawback is that considerably more file descriptors will be
+required <b>if a large number</b> of virtual servers must be supported.
+
+<p>
+Here's an example.  Two servers have been configured (one "server config" and
+one <code><VirtualHost></code>), with the IP addresses 10.0.0.1 and
+10.0.0.2, respectively.  The 10.0.0.1 server runs on port 21, while 10.0.0.2
+runs on port 2001.
+
+<p>
+If we use:
+<pre>
+  SocketBindTight off
+</pre>
+then <code>proftpd</code> creates two sockets, both bound to <b>all</b>
+available addresses; one socket listens on port 21 (<i>i.e.</i> "*:21"), the
+other on port 2001 (<i>i.e.</i> "*.2001").  Since each socket is bound to all
+available addresses, no other daemon or process will be allowed to bind to
+ports 21 or 2001.
+
+<p>
+On the other hand, if we use:
+<pre>
+  SocketBindTight on
+</pre>
+then <code>proftpd</code> again creates two sockets.  However one is bound to
+10.0.0.1, port 21 (<i>i.e.</i> "10.0.0.1:21") and the other is bound to
+10.0.0.2, port 2001 (<i>i.e.</i> "10.0.0.2:2001").  Thus these sockets are
+<em>"tightly"</em> bound to the IP addresses.  This means that port 21 can be
+reused on any address <i>other</i> than 10.0.0.1, and similarly for port 2001
+and 10.0.0.2.
+
+<p>
+One side effect of setting <code>SocketBindTight</code> to <em>on</em> is that
+connections to non-bound addresses will result in a "connection refused"
+message rather than the more common:
+<pre>
+  500 Sorry, no server available to handle request on <i>a.b.c.d.</i>
+</pre>
+due to the fact that no TCP listening socket has been bound to the particular
+address/port pair.  This may or may not be aesthetically desirable, depending
+on your circumstances.
+
+<p>
+<hr>
 <h2><a name="SocketOptions">SocketOptions</a></h2>
 <strong>Syntax:</strong> SocketOptions <em>[maxseg <i>byte-count</i>] [rcvbuf <i>byte-count</i>] [sndbuf <i>byte-count</i>] [keepalive "on"|"off"|spec]</em><br>
 <strong>Default:</strong> None<br>
@@ -1589,16 +1666,38 @@ See also: <a href="#DefaultAddress"><code>DefaultAddress</code></a>
 <h2><a name="Installation">Installation</a></h2>
 The <code>mod_core</code> module is <b>always</b> installed.
 
+<p><a name="FAQ">
+<hr>
+<b>Frequently Asked Questions</b><br>
+
+<p><a name="ListenOnOneAddressOnly">
+<font color=red>Question</font>: How do I configure <code>proftpd</code> to
+only listen to connections on one address, <i>e.g.</i> 127.0.0.1?  If I use
+the following in my <code>proftpd.conf</code>:
+<pre>
+  DefaultAddress localhost
+</pre>
+I am still able to connect to <code>proftpd</code> from another machine.<br>
+<font color=blue>Answer</font>: The solution is to use the
+<a href="#SocketBindTight"><code>SocketBindTight</code></a>, like this:
+<pre>
+  DefaultAddress localhost
+  SocketBindTight on
+</pre>
+The <code>SocketBindTight</code> directive tells <code>proftpd</code> to
+listen <b>only</b> on that 'localhost' IP address, rather than on all
+addresses.
+
 <p>
 <hr><br>
 
 Author: <i>$Author: castaglia $</i><br>
-Last Updated: <i>$Date: 2013/11/09 23:14:30 $</i><br>
+Last Updated: <i>$Date: 2014/01/30 16:50:10 $</i><br>
 
 <br><hr>
 
 <font size=2><b><i>
-© Copyright 2000-2013 The ProFTPD Project<br>
+© Copyright 2000-2014 The ProFTPD Project<br>
  All Rights Reserved<br>
 </i></b></font>
 
diff --git a/doc/modules/mod_rlimit.html b/doc/modules/mod_rlimit.html
index 18dd9cc..8ae12af 100644
--- a/doc/modules/mod_rlimit.html
+++ b/doc/modules/mod_rlimit.html
@@ -1,4 +1,4 @@
-<!-- $Id: mod_rlimit.html,v 1.3 2013/06/10 16:06:13 castaglia Exp $ -->
+<!-- $Id: mod_rlimit.html,v 1.4 2014/01/31 17:08:10 castaglia Exp $ -->
 <!-- $Source: /cvsroot/proftp/proftpd/doc/modules/mod_rlimit.html,v $ -->
 
 <html>
@@ -40,6 +40,7 @@ ProFTPD source distribution:
 
 <h2>Directives</h2>
 <ul>
+  <li><a href="#RLimitChroot">RLimitChroot</a>
   <li><a href="#RLimitCPU">RLimitCPU</a>
   <li><a href="#RLimitMemory">RLimitMemory</a>
   <li><a href="#RLimitOpenFiles">RLimitOpenFiles</a>
@@ -47,6 +48,47 @@ ProFTPD source distribution:
 
 <p>
 <hr>
+<h2><a name="RLimitChroot">RLimitChroot</a></h2>
+<strong>Syntax:</strong> RLimitChroot <em>on|off</em><br>
+<strong>Default:</strong> <em>on</em><br>
+<strong>Context:</strong> "server config", <code><VirtualHost></code>, <code><Global></code><br>
+<strong>Module:</strong> mod_rlimit<br>
+<strong>Compatibility:</strong> 1.3.5rc5
+
+<p>
+The <code>RLimitChroot</code> directive is used to enable/disable checks
+for modifications to "sensitive" directories when a session is chrooted.  These
+checks are designed to mitigate and guard against attacks such as the
+"Roaring Beast" attack; see:
+<ul>
+  <li><a href="https://auscert.org.au/15286">https://auscert.org.au/15286</a>
+  <li><a href="https://auscert.org.au/15526">https://auscert.org.au/15526</a>
+</ul>
+
+<p>
+When a session is chrooted, <i>e.g.</i> via the <code>DefaultRoot</code>
+directive <i>or</i> by <code><Anonymous></code> login, the checks
+for the "sensitive" directories are automatically enabled.  To disable these
+checks, use:
+<pre>
+  RLimitChroot off
+</pre>
+<b>Note</b>: We <b>strongly recommend</b> that you do <b>not</b> disable
+these checks.
+
+<p>
+The checks in question will specifically prevent any attempts to upload
+files into the <code>/etc</code> and <code>/lib</code> directories, or
+attempts to delete, create, rename, link, or otherwise try to change anything
+in these directories.  All attempts to make modifications will be rejected
+with "Permission denied" errors.  In addition, the following message will
+be logged (at debug level 2):
+<pre>
+  WARNING: attempt to use sensitive path '<i>/etc/file</i>' within chroot '<i>/home/user</i>', rejecting
+</pre>
+
+<p>
+<hr>
 <h2><a name="RLimitCPU">RLimitCPU</a></h2>
 <strong>Syntax:</strong> RLimitCPU <em>[scope] soft-limit|"max" [hard-limit|"max"]</em><br>
 <strong>Default:</strong> System defaults<br>
@@ -170,12 +212,12 @@ default.
 <hr><br>
 
 Author: <i>$Author: castaglia $</i><br>
-Last Updated: <i>$Date: 2013/06/10 16:06:13 $</i><br>
+Last Updated: <i>$Date: 2014/01/31 17:08:10 $</i><br>
 
 <br><hr>
 
 <font size=2><b><i>
-© Copyright 2013 TJ Saunders<br>
+© Copyright 2013-2014 TJ Saunders<br>
  All Rights Reserved<br>
 </i></b></font>
 
diff --git a/include/fsio.h b/include/fsio.h
index 9a37200..3f5b7b9 100644
--- a/include/fsio.h
+++ b/include/fsio.h
@@ -25,7 +25,7 @@
  */
 
 /* ProFTPD virtual/modular filesystem support.
- * $Id: fsio.h,v 1.36 2014/01/20 19:36:27 castaglia Exp $
+ * $Id: fsio.h,v 1.37 2014/01/31 16:52:33 castaglia Exp $
  */
 
 #ifndef PR_FSIO_H
@@ -277,6 +277,12 @@ int pr_fsio_utimes(const char *, struct timeval *);
 int pr_fsio_futimes(pr_fh_t *, struct timeval *);
 off_t pr_fsio_lseek(pr_fh_t *, off_t, int);
 
+/* Set a flag determining whether we guard against write operations in
+ * certain sensitive directories while we are chrooted, e.g. "Roaring Beast"
+ * style attacks.
+ */
+int pr_fsio_guard_chroot(int);
+
 /* Set a flag determining whether to use mkdtemp(3) (if available) or not.
  * Returns the previously-set value.
  */
diff --git a/include/version.h b/include/version.h
index 9f03597..8a62fb4 100644
--- a/include/version.h
+++ b/include/version.h
@@ -1,8 +1,8 @@
 #include "buildstamp.h"
 
 /* Application version (in various forms) */
-#define PROFTPD_VERSION_NUMBER		0x0001030504
-#define PROFTPD_VERSION_TEXT		"1.3.5rc4"
+#define PROFTPD_VERSION_NUMBER		0x0001030505
+#define PROFTPD_VERSION_TEXT		"1.3.5"
 
 /* Module API version */
 #define PR_MODULE_API_VERSION		0x20
@@ -12,4 +12,4 @@ unsigned long pr_version_get_number(void);
 const char *pr_version_get_str(void);
 
 /* PR_STATUS is reported by --version-status -- don't ask why */
-#define PR_STATUS          		"(devel)"
+#define PR_STATUS          		"(stable)"
diff --git a/locale/files.txt b/locale/files.txt
index c4c2168..978a76b 100644
--- a/locale/files.txt
+++ b/locale/files.txt
@@ -2,6 +2,7 @@
 ../contrib/mod_copy.c
 ../contrib/mod_ctrls_admin.c
 ../contrib/mod_deflate.c
+../contrib/mod_dnsbl/mod_dnsbl.c
 ../contrib/mod_dynmasq.c
 ../contrib/mod_exec.c
 ../contrib/mod_geoip.c
@@ -153,6 +154,7 @@
 ../include/memcache.h
 ../include/mkhome.h
 ../include/mod_ctrls.h
+../include/mod_log.h
 ../include/modules.h
 ../include/netacl.h
 ../include/netaddr.h
diff --git a/modules/mod_core.c b/modules/mod_core.c
index 4dd5927..e33ee11 100644
--- a/modules/mod_core.c
+++ b/modules/mod_core.c
@@ -25,7 +25,7 @@
  */
 
 /* Core FTPD module
- * $Id: mod_core.c,v 1.460 2014/01/25 16:39:58 castaglia Exp $
+ * $Id: mod_core.c,v 1.461 2014/05/03 22:18:12 castaglia Exp $
  */
 
 #include "conf.h"
@@ -3243,6 +3243,8 @@ MODRET core_pre_any(cmd_rec *cmd) {
    *  NOOP
    *  QUIT
    *  STAT
+   *
+   *  and RFC 2228 commands.
    */
   rnfr_path = pr_table_get(session.notes, "mod_core.rnfr-path", NULL);
   if (rnfr_path != NULL) {
@@ -3251,11 +3253,29 @@ MODRET core_pre_any(cmd_rec *cmd) {
         pr_cmd_cmp(cmd, PR_CMD_NOOP_ID) != 0 &&
         pr_cmd_cmp(cmd, PR_CMD_QUIT_ID) != 0 &&
         pr_cmd_cmp(cmd, PR_CMD_STAT_ID) != 0) {
-      pr_log_debug(DEBUG3,
-        "RNFR followed immediately by %s rather than RNTO, rejecting command",
-        cmd->argv[0]);
-      pr_response_add_err(R_501, _("Bad sequence of commands"));
-      return PR_ERROR(cmd);
+      int reject_cmd = TRUE;
+
+      /* Perform additional checks if an RFC 2228 auth mechanism (TLS, GSSAPI)
+       * has been negotiated/used.
+       */
+      if (session.rfc2228_mech != NULL) {
+        if (pr_cmd_cmp(cmd, PR_CMD_CCC_ID) == 0 ||
+            pr_cmd_cmp(cmd, PR_CMD_CONF_ID) == 0 ||
+            pr_cmd_cmp(cmd, PR_CMD_ENC_ID) == 0 ||
+            pr_cmd_cmp(cmd, PR_CMD_MIC_ID) == 0 ||
+            pr_cmd_cmp(cmd, PR_CMD_PBSZ_ID) == 0 ||
+            pr_cmd_cmp(cmd, PR_CMD_PROT_ID) == 0) {
+          reject_cmd = FALSE;
+        }
+      }
+
+      if (reject_cmd) {
+        pr_log_debug(DEBUG3,
+          "RNFR followed immediately by %s rather than RNTO, rejecting command",
+          cmd->argv[0]);
+        pr_response_add_err(R_501, _("Bad sequence of commands"));
+        return PR_ERROR(cmd);
+      }
     }
   }
 
diff --git a/modules/mod_delay.c b/modules/mod_delay.c
index b59584e..3a1cc6d 100644
--- a/modules/mod_delay.c
+++ b/modules/mod_delay.c
@@ -2,7 +2,7 @@
  * ProFTPD: mod_delay -- a module for adding arbitrary delays to the FTP
  *                       session lifecycle
  *
- * Copyright (c) 2004-2013 TJ Saunders
+ * Copyright (c) 2004-2014 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
@@ -26,7 +26,7 @@
  * This is mod_delay, contrib software for proftpd 1.2.10 and above.
  * For more information contact TJ Saunders <tj at castaglia.org>.
  *
- * $Id: mod_delay.c,v 1.72 2013/12/09 19:16:13 castaglia Exp $
+ * $Id: mod_delay.c,v 1.73 2014/02/09 20:42:23 castaglia Exp $
  */
 
 #include "conf.h"
@@ -1313,11 +1313,19 @@ MODRET delay_post_pass(cmd_rec *cmd) {
   unsigned int rownum;
   long interval, median;
   const char *proto;
+  unsigned char *authenticated;
 
   if (delay_engine == FALSE) {
     return PR_DECLINED(cmd);
   }
 
+  /* Has the client already authenticated? */
+  authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE);
+  if (authenticated != NULL &&
+      *authenticated == TRUE) {
+    return PR_DECLINED(cmd);
+  }
+
   rownum = delay_get_pass_rownum(main_server->sid);
 
   /* Prepare for manipulating the table. */
@@ -1442,8 +1450,9 @@ MODRET delay_post_user(cmd_rec *cmd) {
   const char *proto;
   unsigned char *authenticated;
 
-  if (!delay_engine)
+  if (delay_engine == FALSE) {
     return PR_DECLINED(cmd);
+  }
 
   /* Has the client already authenticated? */
   authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE);
diff --git a/modules/mod_facl.c b/modules/mod_facl.c
index 6fa7e6e..a8de477 100644
--- a/modules/mod_facl.c
+++ b/modules/mod_facl.c
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2004-2013 The ProFTPD Project team
+ * Copyright (c) 2004-2014 The ProFTPD Project team
  *
  * 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
@@ -23,7 +23,7 @@
  */
 
 /* POSIX ACL checking code (aka POSIX.1e hell)
- * $Id: mod_facl.c,v 1.16 2013/10/09 05:03:38 castaglia Exp $
+ * $Id: mod_facl.c,v 1.17 2014/05/04 19:26:26 castaglia Exp $
  */
 
 #include "conf.h"
@@ -1072,7 +1072,10 @@ static int facl_fsio_faccess(pr_fh_t *fh, int mode, uid_t uid, gid_t gid,
 static void facl_mod_unload_ev(const void *event_data, void *user_data) {
   if (strcmp("mod_facl.c", (const char *) event_data) == 0) {
     pr_event_unregister(&facl_module, NULL, NULL);
-    pr_unregister_fs("facl");
+    if (pr_unregister_fs("/") < 0) {
+      pr_log_debug(DEBUG0, MOD_FACL_VERSION
+        ": error unregistering 'facl' FS: %s", strerror(errno));
+    }
   }
 }
 #endif /* !PR_SHARED_MODULE */
diff --git a/modules/mod_rlimit.c b/modules/mod_rlimit.c
index 5368b33..b6a1812 100644
--- a/modules/mod_rlimit.c
+++ b/modules/mod_rlimit.c
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2013 The ProFTPD Project team
+ * Copyright (c) 2013-2014 The ProFTPD Project team
  *
  * 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
@@ -23,12 +23,14 @@
  */
 
 /* Resource limit module
- * $Id: mod_rlimit.c,v 1.6 2013/06/10 16:05:32 castaglia Exp $
+ * $Id: mod_rlimit.c,v 1.8 2014/01/31 17:29:27 castaglia Exp $
  */
 
 #include "conf.h"
 #include "privs.h"
 
+#define MOD_RLIMIT_VERSION		"mod_rlimit/1.0"
+
 module rlimit_module;
 
 #define DAEMON_SCOPE		3
@@ -94,6 +96,26 @@ static int get_num_bytes(const char *nbytes_str, rlim_t *nbytes) {
   return -1;
 }
 
+/* usage: RLimitChroot on|off */
+MODRET set_rlimitchroot(cmd_rec *cmd) {
+  config_rec *c;
+  int use_guard = 0;
+
+  CHECK_ARGS(cmd, 1);
+  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
+
+  use_guard = get_boolean(cmd, 1);
+  if (use_guard == -1) {
+    CONF_ERROR(cmd, "expected Boolean parameter");
+  }
+
+  c = add_config_param(cmd->argv[0], 1, NULL);
+  c->argv[0] = palloc(c->pool, sizeof(int));
+  *((int *) c->argv[0]) = use_guard;
+
+  return PR_HANDLED(cmd);
+}
+
 /* usage: RLimitCPU ["daemon"|"session"] soft-limit [hard-limit] */
 MODRET set_rlimitcpu(cmd_rec *cmd) {
 #ifdef RLIMIT_CPU
@@ -490,6 +512,22 @@ MODRET set_rlimitopenfiles(cmd_rec *cmd) {
 #endif
 }
 
+MODRET rlimit_post_pass(cmd_rec *cmd) {
+  config_rec *c;
+
+  c = find_config(main_server->conf, CONF_PARAM, "RLimitChroot", FALSE);
+  if (c != NULL) {
+    int guard_chroot;
+
+    guard_chroot = *((int *) c->argv[0]);
+    if (guard_chroot == FALSE) {
+      pr_fsio_guard_chroot(FALSE);
+    }
+  }
+
+  return PR_DECLINED(cmd);
+}
+
 static int rlimit_set_core(int scope) {
   rlim_t current, max;
   int res, xerrno;
@@ -698,6 +736,22 @@ static int rlimit_set_memory(int scope) {
 
 /* Event listeners */
 
+static void rlimit_chroot_ev(const void *event_data, void *user_data) {
+  const char *path;
+  size_t path_len;
+
+  /* If we are chrooted, AND the chroot path is anything other than "/",
+   * then set the FSIO API flag to guard certain sensitive directories.
+   */
+
+  path = event_data;
+  path_len = strlen(path);
+
+  if (path_len > 1) {
+    pr_fsio_guard_chroot(TRUE);
+  }
+}
+
 static void rlimit_postparse_ev(const void *event_data, void *user_data) {
   /* Since we're the parent process, we do not want to set the process
    * resource limits; we would prevent future session processes.
@@ -718,10 +772,26 @@ static int rlimit_init(void) {
 }
 
 static int rlimit_sess_init(void) {
+  config_rec *c;
+  int guard_chroot = TRUE;
+
   rlimit_set_cpu(SESSION_SCOPE);
   rlimit_set_memory(SESSION_SCOPE);
   rlimit_set_files(SESSION_SCOPE);
 
+  c = find_config(main_server->conf, CONF_PARAM, "RLimitChroot", FALSE);
+  if (c != NULL) {
+    guard_chroot = *((int *) c->argv[0]);
+  }
+
+  if (guard_chroot) {
+    /* Register an event listener for the 'core.chroot' event, so that we
+     * can set the switch (if necessary) for guarding against attacks like
+     * "Roaring Beast" when we are chrooted.
+     */
+    pr_event_register(&rlimit_module, "core.chroot", rlimit_chroot_ev, NULL);
+  }
+
   return 0;
 }
 
@@ -729,6 +799,7 @@ static int rlimit_sess_init(void) {
  */
 
 static conftable rlimit_conftab[] = {
+  { "RLimitChroot",		set_rlimitchroot,		NULL },
   { "RLimitCPU",		set_rlimitcpu,			NULL },
   { "RLimitMemory",		set_rlimitmemory,		NULL },
   { "RLimitOpenFiles",		set_rlimitopenfiles,		NULL },
@@ -736,6 +807,11 @@ static conftable rlimit_conftab[] = {
   { NULL, NULL, NULL }
 };
 
+static cmdtable rlimit_cmdtab[] = {
+  { POST_CMD,	C_PASS,	G_NONE,	rlimit_post_pass, FALSE, FALSE,	CL_AUTH },
+  { 0, NULL }
+};
+
 module rlimit_module = {
   NULL, NULL,
 
@@ -749,7 +825,7 @@ module rlimit_module = {
   rlimit_conftab,
 
   /* Module command handler table */
-  NULL,
+  rlimit_cmdtab,
 
   /* Module authentication handler table */
   NULL,
@@ -758,5 +834,8 @@ module rlimit_module = {
   rlimit_init,
 
   /* Session initialization function */
-  rlimit_sess_init
+  rlimit_sess_init,
+
+  /* Module version */
+  MOD_RLIMIT_VERSION
 };
diff --git a/modules/mod_xfer.c b/modules/mod_xfer.c
index 2a6343f..bcfb487 100644
--- a/modules/mod_xfer.c
+++ b/modules/mod_xfer.c
@@ -2,7 +2,7 @@
  * ProFTPD - FTP server daemon
  * Copyright (c) 1997, 1998 Public Flood Software
  * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver at tos.net>
- * Copyright (c) 2001-2013 The ProFTPD Project team
+ * Copyright (c) 2001-2014 The ProFTPD Project team
  *
  * 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
@@ -26,7 +26,7 @@
 
 /* Data transfer module for ProFTPD
  *
- * $Id: mod_xfer.c,v 1.331 2013/12/12 05:40:42 castaglia Exp $
+ * $Id: mod_xfer.c,v 1.334 2014/04/28 17:11:18 castaglia Exp $
  */
 
 #include "conf.h"
@@ -2397,12 +2397,12 @@ MODRET xfer_allo(cmd_rec *cmd) {
 
   if (xfer_opts & PR_XFER_OPT_HANDLE_ALLO) {
     const char *path;
-    off_t avail_sz;
+    off_t avail_kb;
     int res;
 
     path = pr_fs_getcwd();
 
-    res = pr_fs_getsize2((char *) path, &avail_sz);
+    res = pr_fs_getsize2((char *) path, &avail_kb);
     if (res < 0) {
       /* If we can't check the filesystem stats for any reason, let the request
        * proceed anyway.
@@ -2413,10 +2413,17 @@ MODRET xfer_allo(cmd_rec *cmd) {
       pr_response_add(R_202, _("No storage allocation necessary"));
 
     } else {
-      if (requested_sz > avail_sz) {
-        pr_log_debug(DEBUG5, "%s requested %" PR_LU " bytes, only %" PR_LU
-          " bytes available on '%s'", cmd->argv[0], (pr_off_t) requested_sz,
-          (pr_off_t) avail_sz, path);
+      off_t requested_kb;
+
+      /* The requested size is in bytes; the size returned from
+       * pr_fs_getsize2() is in KB.
+       */
+      requested_kb = requested_sz / 1024;
+
+      if (requested_kb > avail_kb) {
+        pr_log_debug(DEBUG5, "%s requested %" PR_LU " KB, only %" PR_LU
+          " KB available on '%s'", cmd->argv[0], (pr_off_t) requested_kb,
+          (pr_off_t) avail_kb, path);
         pr_response_add_err(R_552, "%s: %s", cmd->arg, strerror(ENOSPC));
         return PR_ERROR(cmd);
       }
@@ -3302,30 +3309,21 @@ static void xfer_exit_ev(const void *event_data, void *user_data) {
 
   if (session.sf_flags & SF_XFER) {
     cmd_rec *cmd;
-    char *path = NULL;
 
     if (session.xfer.direction == PR_NETIO_IO_RD) {
        /* An upload is occurring... */
-      if (stor_fh != NULL) {
-        path = stor_fh->fh_path;
-      }
-
       pr_trace_msg(trace_channel, 6, "session exiting, aborting upload");
       stor_abort();
 
     } else {
       /* A download is occurring... */
-      if (retr_fh != NULL) {
-        path = retr_fh->fh_path;
-      }
-
       pr_trace_msg(trace_channel, 6, "session exiting, aborting download");
       retr_abort();
     }
 
     pr_data_abort(0, FALSE);
 
-    cmd = pr_cmd_alloc(session.pool, 2, session.curr_cmd, path);
+    cmd = pr_cmd_alloc(session.pool, 2, session.curr_cmd, session.xfer.path);
     (void) pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0);
     (void) pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0);
   }
diff --git a/proftpd.spec b/proftpd.spec
index c09d0ef..156d73c 100644
--- a/proftpd.spec
+++ b/proftpd.spec
@@ -1,4 +1,4 @@
-# $Id: proftpd.spec,v 1.89 2014/01/28 17:40:46 castaglia Exp $
+# $Id: proftpd.spec,v 1.90 2014/05/15 15:53:13 castaglia Exp $
 
 # Module List:
 #
@@ -50,12 +50,12 @@
 #
 # NOTE: rpmbuild is really bloody stupid, and CANNOT handle a leading '#'
 # character followed by a '%' character.  
-%global release_cand_version	rc4
+#global release_cand_version	
 
 %global usecvsversion             0%{?_with_cvs:1}
 
 %global proftpd_cvs_version_main	1.3.5
-%global proftpd_cvs_version_date  20140128
+%global proftpd_cvs_version_date  20140515
 
 # Spec default assumes that a gzipped tarball is used, since nightly CVS builds,
 # release candidates and stable/maint releases are all available in that form;
diff --git a/src/fsio.c b/src/fsio.c
index 494ab2f..04285ed 100644
--- a/src/fsio.c
+++ b/src/fsio.c
@@ -25,7 +25,7 @@
  */
 
 /* ProFTPD virtual/modular file-system support
- * $Id: fsio.c,v 1.155 2014/01/27 18:25:15 castaglia Exp $
+ * $Id: fsio.c,v 1.159 2014/02/11 15:17:54 castaglia Exp $
  */
 
 #include "conf.h"
@@ -96,6 +96,8 @@ static unsigned char chk_fs_map = FALSE;
 static char vwd[PR_TUNABLE_PATH_MAX + 1] = "/";
 static char cwd[PR_TUNABLE_PATH_MAX + 1] = "/";
 
+static int guard_chroot = FALSE;
+
 /* Runtime enabling/disabling of mkdtemp(3) use. */
 #ifdef HAVE_MKDTEMP
 static int use_mkdtemp = TRUE;
@@ -106,6 +108,53 @@ static int use_mkdtemp = FALSE;
 /* Runtime enabling/disabling of encoding of paths. */
 static int use_encoding = TRUE;
 
+/* Guard against attacks like "Roaring Beast" when we are chrooted.  See:
+ *
+ *  https://auscert.org.au/15286
+ *  https://auscert.org.au/15526
+ *
+ * Currently, we guard the /etc and /lib directories.
+ */
+static int chroot_allow_path(const char *path) {
+  size_t path_len;
+  int res = 0;
+
+  /* Note: we expect to get (and DO get) the absolute path here.  Should that
+   * ever not be the case, this check will not work.
+   */
+
+  path_len = strlen(path);
+  if (path_len < 4) {
+    /* Path is not long enough to include one of the guarded directories. */
+    return 0;
+  }
+
+  if (path_len == 4) {
+    if (strcmp(path, "/etc") == 0 ||
+        strcmp(path, "/lib") == 0) {
+      res = -1;
+    }
+
+  } else {
+    if (strncmp(path, "/etc/", 5) == 0 ||
+        strncmp(path, "/lib/", 5) == 0) {
+      res = -1;
+    }
+  }
+
+  if (res < 0) {
+    pr_trace_msg(trace_channel, 1, "rejecting path '%s' within chroot '%s'",
+      path, session.chroot_path);
+    pr_log_debug(DEBUG2,
+      "WARNING: attempt to use sensitive path '%s' within chroot '%s', "
+      "rejecting", path, session.chroot_path);
+
+    errno = EACCES;
+  }
+
+  return res;
+}
+
 /* The following static functions are simply wrappers for system functions
  */
 
@@ -122,14 +171,40 @@ static int sys_lstat(pr_fs_t *fs, const char *path, struct stat *sbuf) {
 }
 
 static int sys_rename(pr_fs_t *fs, const char *rnfm, const char *rnto) {
-  return rename(rnfm, rnto);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(rnfm);
+    if (res < 0) {
+      return -1;
+    }
+
+    res = chroot_allow_path(rnto);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = rename(rnfm, rnto);
+  return res;
 }
 
 static int sys_unlink(pr_fs_t *fs, const char *path) {
-  return unlink(path);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = unlink(path);
+  return res;
 }
 
 static int sys_open(pr_fh_t *fh, const char *path, int flags) {
+  int res;
 
 #ifdef O_BINARY
   /* On Cygwin systems, we need the open(2) equivalent of fopen(3)'s "b"
@@ -138,11 +213,32 @@ static int sys_open(pr_fh_t *fh, const char *path, int flags) {
   flags |= O_BINARY;
 #endif
 
-  return open(path, flags, PR_OPEN_MODE);
+  if (guard_chroot) {
+    /* If we are creating (or truncating) a file, then we need to check. */
+    if (flags & (O_APPEND|O_CREAT|O_TRUNC)) {
+      res = chroot_allow_path(path);
+      if (res < 0) {
+        return -1;
+      }
+    }
+  }
+
+  res = open(path, flags, PR_OPEN_MODE);
+  return res;
 }
 
 static int sys_creat(pr_fh_t *fh, const char *path, mode_t mode) {
-  return creat(path, mode);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = creat(path, mode);
+  return res;
 }
 
 static int sys_close(pr_fh_t *fh, int fd) {
@@ -162,11 +258,31 @@ static off_t sys_lseek(pr_fh_t *fh, int fd, off_t offset, int whence) {
 }
 
 static int sys_link(pr_fs_t *fs, const char *path1, const char *path2) {
-  return link(path1, path2);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path2);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = link(path1, path2);
+  return res;
 }
 
 static int sys_symlink(pr_fs_t *fs, const char *path1, const char *path2) {
-  return symlink(path1, path2);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path2);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = symlink(path1, path2);
+  return res;
 }
 
 static int sys_readlink(pr_fs_t *fs, const char *path, char *buf,
@@ -179,11 +295,31 @@ static int sys_ftruncate(pr_fh_t *fh, int fd, off_t len) {
 }
 
 static int sys_truncate(pr_fs_t *fs, const char *path, off_t len) {
-  return truncate(path, len);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = truncate(path, len);
+  return res;
 }
 
 static int sys_chmod(pr_fs_t *fs, const char *path, mode_t mode) {
-  return chmod(path, mode);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = chmod(path, mode);
+  return res;
 }
 
 static int sys_fchmod(pr_fh_t *fh, int fd, mode_t mode) {
@@ -191,7 +327,17 @@ static int sys_fchmod(pr_fh_t *fh, int fd, mode_t mode) {
 }
 
 static int sys_chown(pr_fs_t *fs, const char *path, uid_t uid, gid_t gid) {
-  return chown(path, uid, gid);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = chown(path, uid, gid);
+  return res;
 }
 
 static int sys_fchown(pr_fh_t *fh, int fd, uid_t uid, gid_t gid) {
@@ -199,7 +345,17 @@ static int sys_fchown(pr_fh_t *fh, int fd, uid_t uid, gid_t gid) {
 }
 
 static int sys_lchown(pr_fs_t *fs, const char *path, uid_t uid, gid_t gid) {
-  return lchown(path, uid, gid);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = lchown(path, uid, gid);
+  return res;
 }
 
 /* We provide our own equivalent of access(2) here, rather than using
@@ -284,7 +440,17 @@ static int sys_faccess(pr_fh_t *fh, int mode, uid_t uid, gid_t gid,
 }
 
 static int sys_utimes(pr_fs_t *fs, const char *path, struct timeval *tvs) {
-  return utimes(path, tvs);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = utimes(path, tvs);
+  return res;
 }
 
 static int sys_futimes(pr_fh_t *fh, int fd, struct timeval *tvs) {
@@ -336,11 +502,31 @@ static struct dirent *sys_readdir(pr_fs_t *fs, void *dir) {
 }
 
 static int sys_mkdir(pr_fs_t *fs, const char *path, mode_t mode) {
-  return mkdir(path, mode);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = mkdir(path, mode);
+  return res;
 }
 
 static int sys_rmdir(pr_fs_t *fs, const char *path) {
-  return rmdir(path);
+  int res;
+
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
+  res = rmdir(path);
+  return res;
 }
 
 static int fs_cmp(const void *a, const void *b) {
@@ -2691,6 +2877,15 @@ int pr_fsio_mkdir(const char *path, mode_t mode) {
   return res;
 }
 
+int pr_fsio_guard_chroot(int guard) {
+  int prev;
+
+  prev = guard_chroot;
+  guard_chroot = guard;
+
+  return prev;
+}
+
 int pr_fsio_set_use_mkdtemp(int value) {
   int prev_value;
 
@@ -2836,6 +3031,13 @@ int pr_fsio_smkdir(pool *p, const char *path, mode_t mode, uid_t uid,
     "smkdir: path '%s', mode %04o, UID %lu, GID %lu", path, (unsigned int) mode,
     (unsigned long) uid, (unsigned long) gid);
 
+  if (guard_chroot) {
+    res = chroot_allow_path(path);
+    if (res < 0) {
+      return -1;
+    }
+  }
+
 #ifdef HAVE_MKDTEMP
   if (use_mkdtemp == TRUE) {
     char *ptr;
@@ -2858,6 +3060,16 @@ int pr_fsio_smkdir(pool *p, const char *path, mode_t mode, uid_t uid,
 
     res = lstat(dst_dir, &st);
     if (res < 0) {
+      xerrno = errno;
+
+      pr_log_pri(PR_LOG_WARNING,
+        "smkdir: unable to lstat(2) parent directory '%s': %s", dst_dir,
+        strerror(xerrno));
+      pr_trace_msg(trace_channel, 1,
+        "smkdir: unable to lstat(2) parent directory '%s': %s", dst_dir,
+        strerror(xerrno));
+
+      errno = xerrno;
       return -1;
     }
 
@@ -2885,12 +3097,29 @@ int pr_fsio_smkdir(pool *p, const char *path, mode_t mode, uid_t uid,
      */
     tmpl_path = mkdtemp(tmpl);
     if (tmpl_path == NULL) {
+      xerrno = errno;
+
+      pr_log_pri(PR_LOG_WARNING,
+        "smkdir: mkdtemp(3) failed to create directory using '%s': %s", tmpl,
+        strerror(xerrno));
+      pr_trace_msg(trace_channel, 1,
+        "smkdir: mkdtemp(3) failed to create directory using '%s': %s", tmpl,
+        strerror(xerrno));
+
+      errno = xerrno;
       return -1;
     }
 
   } else {
     res = pr_fsio_mkdir(path, mode);
     if (res < 0) {
+      xerrno = errno;
+
+      pr_trace_msg(trace_channel, 1,
+        "mkdir(2) fail to create directory '%s' with perms %04o: %s", path,
+        mode, strerror(xerrno));
+
+      errno = xerrno;
       return -1;
     }
 
@@ -2900,6 +3129,13 @@ int pr_fsio_smkdir(pool *p, const char *path, mode_t mode, uid_t uid,
 
   res = pr_fsio_mkdir(path, mode);
   if (res < 0) {
+    xerrno = errno;
+
+    pr_trace_msg(trace_channel, 1,
+      "mkdir(2) fail to create directory '%s' with perms %04o: %s", path,
+      mode, strerror(xerrno));
+        
+    errno = xerrno;
     return -1;
   }
 
@@ -3026,11 +3262,22 @@ int pr_fsio_smkdir(pool *p, const char *path, mode_t mode, uid_t uid,
     if (res < 0) {
       xerrno = errno;
 
-      pr_log_pri(PR_LOG_WARNING, "renaming '%s' to '%s' failed: %s", tmpl_path,
+      pr_log_pri(PR_LOG_INFO, "renaming '%s' to '%s' failed: %s", tmpl_path,
         path, strerror(xerrno));
 
       (void) rmdir(tmpl_path);
 
+#ifdef ENOTEMPTY
+      if (xerrno == ENOTEMPTY) {
+        /* If the rename(2) failed with "Directory not empty" (ENOTEMPTY),
+         * then change the errno to "File exists" (EEXIST), so that the
+         * error reported to the client is more indicative of the actual
+         * cause.
+         */
+        xerrno = EEXIST;
+      }
+#endif /* ENOTEMPTY */
+ 
       errno = xerrno;
       return -1;
     }
diff --git a/src/stash.c b/src/stash.c
index ce93f3c..e8ecd34 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -1,6 +1,6 @@
 /*
  * ProFTPD - FTP server daemon
- * Copyright (c) 2010-2012 The ProFTPD Project team
+ * Copyright (c) 2010-2014 The ProFTPD Project team
  *
  * 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
@@ -23,7 +23,7 @@
  */
 
 /* Symbol table hashes
- * $Id: stash.c,v 1.11 2012/04/24 23:27:39 castaglia Exp $
+ * $Id: stash.c,v 1.12 2014/02/11 15:17:04 castaglia Exp $
  */
 
 #include "conf.h"
@@ -700,7 +700,6 @@ void pr_stash_dump(void (*dumpf)(const char *, ...)) {
           break;
       }
 
-#if 0
       if (sym->sym_module != NULL) {
         dumpf(" + %s symbol: %s (mod_%s.c)", type, sym->sym_name,
           sym->sym_module->name);
@@ -708,7 +707,6 @@ void pr_stash_dump(void (*dumpf)(const char *, ...)) {
       } else {
         dumpf(" + %s symbol: %s (core)", type, sym->sym_name);
       }
-#endif
     }
   }
 
diff --git a/tests/t/config/passiveports.t b/tests/t/config/passiveports.t
new file mode 100644
index 0000000..fef0093
--- /dev/null
+++ b/tests/t/config/passiveports.t
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+
+use lib qw(t/lib);
+use strict;
+
+use Test::Unit::HarnessUnit;
+
+$| = 1;
+
+my $r = Test::Unit::HarnessUnit->new();
+$r->start("ProFTPD::Tests::Config::PassivePorts");
diff --git a/tests/t/config/rlimitchroot.t b/tests/t/config/rlimitchroot.t
new file mode 100644
index 0000000..feb9974
--- /dev/null
+++ b/tests/t/config/rlimitchroot.t
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+
+use lib qw(t/lib);
+use strict;
+
+use Test::Unit::HarnessUnit;
+
+$| = 1;
+
+my $r = Test::Unit::HarnessUnit->new();
+$r->start("ProFTPD::Tests::Config::RLimitChroot");
diff --git a/tests/t/lib/ProFTPD/Tests/Commands/MKD.pm b/tests/t/lib/ProFTPD/Tests/Commands/MKD.pm
index 9517eee..868dc6f 100644
--- a/tests/t/lib/ProFTPD/Tests/Commands/MKD.pm
+++ b/tests/t/lib/ProFTPD/Tests/Commands/MKD.pm
@@ -47,6 +47,11 @@ my $TESTS = {
     test_class => [qw(forking rootprivs)],
   },
 
+  mkd_chrooted_with_cwd_ok => {
+    order => ++$order,
+    test_class => [qw(forking rootprivs)],
+  },
+
   mkd_sgid_umask_one_param_ok => {
     order => ++$order,
     test_class => [qw(forking)],
@@ -853,6 +858,137 @@ sub mkd_chrooted_ok {
   unlink($log_file);
 }
 
+sub mkd_chrooted_with_cwd_ok {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/cmds.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/cmds.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/cmds.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/cmds.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/cmds.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $group = 'ftpd';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  my $sub_dir = File::Spec->rel2abs("$tmpdir/foo");
+  mkpath($sub_dir);
+
+  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/bar");
+ 
+  # 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, $sub_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir, $sub_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,
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    DefaultRoot => '~',
+
+    IfModules => {
+      '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');
+
+      my ($resp_code, $resp_msg) = $client->xmkd('bar');
+
+      my $expected;
+
+      $expected = 257;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected $expected, got $resp_code"));
+
+      $expected = "\"/foo/bar\" - Directory successfully created";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected '$expected', got '$resp_msg'"));
+
+      $self->assert(-d $test_dir,
+        test_msg("$test_dir directory does not exist as expected"));
+
+      my $perms = ((stat($sub_dir))[2] & 07777);
+      $expected = 0755;
+      $self->assert($expected == $perms,
+        test_msg("Expected perms $expected, got $perms"));
+    };
+
+    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 mkd_sgid_umask_one_param_ok {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
@@ -1602,6 +1738,11 @@ sub mkd_digits_ok {
       $self->assert($expected == $resp_code,
         test_msg("Expected $expected, got $resp_code"));
 
+      if ($^O eq 'darwin') {
+        # MacOSX hack
+        $sub_dir = '/private' . $sub_dir;
+      }
+
       $expected = "\"$sub_dir\" - Directory successfully created";
       $self->assert($expected eq $resp_msg,
         test_msg("Expected '$expected', got '$resp_msg'"));
diff --git a/tests/t/lib/ProFTPD/Tests/Config/DeleteAbortedStores.pm b/tests/t/lib/ProFTPD/Tests/Config/DeleteAbortedStores.pm
index 104487f..83b80ba 100644
--- a/tests/t/lib/ProFTPD/Tests/Config/DeleteAbortedStores.pm
+++ b/tests/t/lib/ProFTPD/Tests/Config/DeleteAbortedStores.pm
@@ -30,6 +30,16 @@ my $TESTS = {
     test_class => [qw(bug forking)],
   },
 
+  deleteabortedstores_timeout_idle_bug4035 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
+
+  deleteabortedstores_timeout_stalled_bug4035 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
+
 };
 
 sub new {
@@ -464,4 +474,298 @@ sub deleteabortedstores_conn_aborted_bug3917 {
   unlink($log_file);
 }
 
+sub deleteabortedstores_timeout_idle_bug4035 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/config.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.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 $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+
+  my $timeout_idle = 2;
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    DefaultChdir => '~',
+
+    HiddenStores => 'on',
+    DeleteAbortedStores => 'on',
+    TimeoutIdle => $timeout_idle,
+
+    IfModules => {
+      '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 $conn = $client->stor_raw('test.txt');
+      unless ($conn) {
+        die("Failed to STOR test.txt: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      my $buf = "Hello, World!\n";
+      $conn->write($buf, length($buf), 25);
+
+      unless (-f $hidden_file) {
+        die("File $hidden_file does not exist as expected");
+      }
+
+      # Now wait for more than the TimeoutIdle time
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "+ sleeping for more than TimeoutIdle $timeout_idle secs\n";
+      }
+
+      sleep($timeout_idle + 2);
+
+      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();
+
+      $self->assert(-f $test_file,
+        test_msg("File $test_file does not exist as expected"));
+
+      $self->assert(!-f $hidden_file,
+        test_msg("File $hidden_file exists unexpectedly"));
+    };
+
+    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 deleteabortedstores_timeout_stalled_bug4035 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/config.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.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 $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+
+  my $timeout_stalled = 2;
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    DefaultChdir => '~',
+
+    HiddenStores => 'on',
+    DeleteAbortedStores => 'on',
+    TimeoutStalled => $timeout_stalled,
+
+    IfModules => {
+      '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 $conn = $client->stor_raw('test.txt');
+      unless ($conn) {
+        die("Failed to STOR test.txt: " . $client->response_code() . " " .
+          $client->response_msg());
+      }
+
+      my $buf = "Hello, World!\n";
+      $conn->write($buf, length($buf), 25);
+
+      unless (-f $hidden_file) {
+        die("File $hidden_file does not exist as expected");
+      }
+
+      # Now wait for more than the TimeoutStalled time
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "+ sleeping for more than TimeoutStalled $timeout_stalled secs\n";
+      }
+
+      sleep($timeout_stalled + 2);
+
+      eval { $conn->close() };
+
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+      $self->assert_transfer_ok($resp_code, $resp_msg);
+
+      $self->assert(!-f $test_file,
+        test_msg("File $test_file exists unexpectedly"));
+
+      $self->assert(!-f $hidden_file,
+        test_msg("File $hidden_file exists unexpectedly"));
+    };
+
+    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);
+}
+
 1;
diff --git a/tests/t/lib/ProFTPD/Tests/Config/DeleteAbortedStores.pm b/tests/t/lib/ProFTPD/Tests/Config/PassivePorts.pm
similarity index 66%
copy from tests/t/lib/ProFTPD/Tests/Config/DeleteAbortedStores.pm
copy to tests/t/lib/ProFTPD/Tests/Config/PassivePorts.pm
index 104487f..7102ed8 100644
--- a/tests/t/lib/ProFTPD/Tests/Config/DeleteAbortedStores.pm
+++ b/tests/t/lib/ProFTPD/Tests/Config/PassivePorts.pm
@@ -1,4 +1,4 @@
-package ProFTPD::Tests::Config::DeleteAbortedStores;
+package ProFTPD::Tests::Config::PassivePorts;
 
 use lib qw(t/lib);
 use base qw(ProFTPD::TestSuite::Child);
@@ -15,19 +15,19 @@ $| = 1;
 my $order = 0;
 
 my $TESTS = {
-  deleteabortedstores_conn_aborted_ok => {
+  pasv_ports_server_config => {
     order => ++$order,
     test_class => [qw(forking)],
   },
 
-  deleteabortedstores_cmd_abort_ok => {
+  pasv_ports_global => {
     order => ++$order,
     test_class => [qw(forking)],
   },
 
-  deleteabortedstores_conn_aborted_bug3917 => {
+  pasv_ports_vhost => {
     order => ++$order,
-    test_class => [qw(bug forking)],
+    test_class => [qw(forking)],
   },
 
 };
@@ -40,7 +40,7 @@ sub list_tests {
   return testsuite_get_runnable_tests($TESTS);
 }
 
-sub deleteabortedstores_conn_aborted_ok {
+sub pasv_ports_server_config {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -52,7 +52,7 @@ sub deleteabortedstores_conn_aborted_ok {
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
-
+  
   my $user = 'proftpd';
   my $passwd = 'test';
   my $group = 'ftpd';
@@ -73,23 +73,33 @@ sub deleteabortedstores_conn_aborted_ok {
   }
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
-    '/bin/bash');
+    '/bin/bash'); 
   auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
   my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+  if (open(my $fh, "> $test_file")) {
+    print $fh "ABCD" x 8192;
+    unless (close($fh)) {
+      die("Can't write $test_file: $!");
+    }
+
+  } else {
+    die("Can't open $test_file: $!");
+  }
+
+  my $min_port = 40100;
+  my $max_port = 40200;
 
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'DEFAULT:0 data:10',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
-    DefaultChdir => '~',
-
-    HiddenStores => 'on',
-    DeleteAbortedStores => 'on',
+    PassivePorts => "$min_port $max_port",
 
     IfModules => {
       'mod_delay.c' => {
@@ -118,34 +128,27 @@ sub deleteabortedstores_conn_aborted_ok {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
       $client->login($user, $passwd);
 
-      my $conn = $client->stor_raw('test.txt');
-      unless ($conn) {
-        die("Failed to STOR test.txt: " . $client->response_code() . " " .
-          $client->response_msg());
-      }
-
-      my $buf = "Hello, World!\n";
-      $conn->write($buf, length($buf), 25);
-
-      unless (-f $hidden_file) {
-        die("File $hidden_file does not exist as expected");
-      }
+      my ($resp_code, $resp_msg) = $client->pasv();
+      $client->quit();
 
-      eval { $conn->abort() };
+      my $expected;
 
-      my $resp_code = $client->response_code();
-      my $resp_msg = $client->response_msg();
-      $self->assert_transfer_ok($resp_code, $resp_msg, 1);
+      $expected = 227;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
 
-      $client->quit();
+      $expected = '\(\d+,\d+,\d+,\d+,\d+,\d+\)';
+      $self->assert(qr/$expected/, $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
 
-      if (-f $test_file) {
-        die("File $test_file exists unexpectedly");
+      unless ($resp_msg =~ /\(\d+,\d+,\d+,\d+,(\d+),(\d+)\)/) {
+        die("Response '$resp_msg' does not match expected pattern");
       }
 
-      if (-f $hidden_file) {
-        die("File $hidden_file exists unexpectedly");
-      }
+      my $pasv_port = ($1 * 256) + $2;
+
+      $self->assert($min_port <= $pasv_port && $max_port >= $pasv_port,
+        test_msg("Expected port from $min_port to $max_port, got $pasv_port"));
     };
 
     if ($@) {
@@ -180,7 +183,7 @@ sub deleteabortedstores_conn_aborted_ok {
   unlink($log_file);
 }
 
-sub deleteabortedstores_cmd_abort_ok {
+sub pasv_ports_global {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -192,7 +195,7 @@ sub deleteabortedstores_cmd_abort_ok {
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
-
+  
   my $user = 'proftpd';
   my $passwd = 'test';
   my $group = 'ftpd';
@@ -213,26 +216,36 @@ sub deleteabortedstores_cmd_abort_ok {
   }
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
-    '/bin/bash');
+    '/bin/bash'); 
   auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
   my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+  if (open(my $fh, "> $test_file")) {
+    print $fh "ABCD" x 8192;
+    unless (close($fh)) {
+      die("Can't write $test_file: $!");
+    }
+
+  } else {
+    die("Can't open $test_file: $!");
+  }
+
+  my $min_port = 40100;
+  my $max_port = 40200;
 
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
     TraceLog => $log_file,
-    Trace => 'DEFAULT:10',
+    Trace => 'DEFAULT:0 data:10',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
-    DefaultChdir => '~',
 
-    HiddenStores => 'on',
-    DeleteAbortedStores => 'on',
-    TimeoutLinger => 1,
+    Global => {
+      PassivePorts => "$min_port $max_port",
+    },
 
     IfModules => {
       'mod_delay.c' => {
@@ -261,36 +274,27 @@ sub deleteabortedstores_cmd_abort_ok {
       my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
       $client->login($user, $passwd);
 
-      my $conn = $client->stor_raw('test.txt');
-      unless ($conn) {
-        die("Failed to STOR test.txt: " . $client->response_code() . " " .
-          $client->response_msg());
-      }
-
-      my $buf = "Hello, World!\n";
-      $conn->write($buf, length($buf), 25);
-
-      unless (-f $hidden_file) {
-        die("File $hidden_file does not exist as expected");
-      }
-
-      $client->quote('ABOR');
+      my ($resp_code, $resp_msg) = $client->pasv();
+      $client->quit();
 
-      my $resp_code = $client->response_code();
-      my $resp_msg = $client->response_msg();
-      $self->assert_transfer_ok($resp_code, $resp_msg, 1);
+      my $expected;
 
-      eval { $conn->close() };
+      $expected = 227;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
 
-      $client->quit();
+      $expected = '\(\d+,\d+,\d+,\d+,\d+,\d+\)';
+      $self->assert(qr/$expected/, $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
 
-      if (-f $test_file) {
-        die("File $test_file exists unexpectedly");
+      unless ($resp_msg =~ /\(\d+,\d+,\d+,\d+,(\d+),(\d+)\)/) {
+        die("Response '$resp_msg' does not match expected pattern");
       }
 
-      if (-f $hidden_file) {
-        die("File $hidden_file exists unexpectedly");
-      }
+      my $pasv_port = ($1 * 256) + $2;
+
+      $self->assert($min_port <= $pasv_port && $max_port >= $pasv_port,
+        test_msg("Expected port from $min_port to $max_port, got $pasv_port"));
     };
 
     if ($@) {
@@ -325,7 +329,7 @@ sub deleteabortedstores_cmd_abort_ok {
   unlink($log_file);
 }
 
-sub deleteabortedstores_conn_aborted_bug3917 {
+sub pasv_ports_vhost {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -337,7 +341,7 @@ sub deleteabortedstores_conn_aborted_bug3917 {
 
   my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
   my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
-
+  
   my $user = 'proftpd';
   my $passwd = 'test';
   my $group = 'ftpd';
@@ -358,22 +362,34 @@ sub deleteabortedstores_conn_aborted_bug3917 {
   }
 
   auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
-    '/bin/bash');
+    '/bin/bash'); 
   auth_group_write($auth_group_file, $group, $gid, $user);
 
-  my $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
   my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+  if (open(my $fh, "> $test_file")) {
+    print $fh "ABCD" x 8192;
+    unless (close($fh)) {
+      die("Can't write $test_file: $!");
+    }
+
+  } else {
+    die("Can't open $test_file: $!");
+  }
+
+  my $min_port = 40100;
+  my $max_port = 40200;
 
   my $config = {
     PidFile => $pid_file,
     ScoreboardFile => $scoreboard_file,
     SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'DEFAULT:0 data:10',
 
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
-    DefaultChdir => '~',
-
-    HiddenStores => 'on',
+    Port => '0',
+    SocketBindTight => 'on',
 
     IfModules => {
       'mod_delay.c' => {
@@ -384,6 +400,26 @@ sub deleteabortedstores_conn_aborted_bug3917 {
 
   my ($port, $config_user, $config_group) = config_write($config_file, $config);
 
+  my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
+
+  if (open(my $fh, ">> $config_file")) {
+    print $fh <<EOC;
+<VirtualHost 127.0.0.1>
+  ServerName "Vhost"
+  Port $vhost_port
+  AuthUserFile $auth_user_file
+  AuthGroupFile $auth_group_file
+  PassivePorts $min_port $max_port
+</VirtualHost>
+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.
@@ -399,37 +435,30 @@ sub deleteabortedstores_conn_aborted_bug3917 {
   defined(my $pid = fork()) or die("Can't fork: $!");
   if ($pid) {
     eval {
-      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $vhost_port);
       $client->login($user, $passwd);
 
-      my $conn = $client->stor_raw('test.txt');
-      unless ($conn) {
-        die("Failed to STOR test.txt: " . $client->response_code() . " " .
-          $client->response_msg());
-      }
-
-      my $buf = "Hello, World!\n";
-      $conn->write($buf, length($buf), 25);
-
-      unless (-f $hidden_file) {
-        die("File $hidden_file does not exist as expected");
-      }
+      my ($resp_code, $resp_msg) = $client->pasv();
+      $client->quit();
 
-      eval { $conn->abort() };
+      my $expected;
 
-      my $resp_code = $client->response_code();
-      my $resp_msg = $client->response_msg();
-      $self->assert_transfer_ok($resp_code, $resp_msg, 1);
+      $expected = 227;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
 
-      $client->quit();
+      $expected = '\(\d+,\d+,\d+,\d+,\d+,\d+\)';
+      $self->assert(qr/$expected/, $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
 
-      if (-f $test_file) {
-        die("File $test_file exists unexpectedly");
+      unless ($resp_msg =~ /\(\d+,\d+,\d+,\d+,(\d+),(\d+)\)/) {
+        die("Response '$resp_msg' does not match expected pattern");
       }
 
-      if (-f $hidden_file) {
-        die("File $hidden_file exists unexpectedly");
-      }
+      my $pasv_port = ($1 * 256) + $2;
+
+      $self->assert($min_port <= $pasv_port && $max_port >= $pasv_port,
+        test_msg("Expected port from $min_port to $max_port, got $pasv_port"));
     };
 
     if ($@) {
diff --git a/tests/t/lib/ProFTPD/Tests/Config/RLimitChroot.pm b/tests/t/lib/ProFTPD/Tests/Config/RLimitChroot.pm
new file mode 100644
index 0000000..ab15ac7
--- /dev/null
+++ b/tests/t/lib/ProFTPD/Tests/Config/RLimitChroot.pm
@@ -0,0 +1,597 @@
+package ProFTPD::Tests::Config::RLimitChroot;
+
+use lib qw(t/lib);
+use base qw(ProFTPD::TestSuite::Child);
+use strict;
+
+use File::Path qw(mkpath);
+use File::Spec;
+use IO::Handle;
+
+use ProFTPD::TestSuite::FTP;
+use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
+
+$| = 1;
+
+my $order = 0;
+
+my $TESTS = {
+  rlimitchroot_on => {
+    order => ++$order,
+    test_class => [qw(forking rootprivs)],
+  },
+
+  rlimitchroot_off => {
+    order => ++$order,
+    test_class => [qw(forking rootprivs)],
+  },
+
+  rlimitchroot_ifuser_off => {
+    order => ++$order,
+    test_class => [qw(forking mod_ifsession rootprivs)],
+  }
+};
+
+sub new {
+  return shift()->SUPER::new(@_);
+}
+
+sub list_tests {
+  return testsuite_get_runnable_tests($TESTS);
+}
+
+sub rlimitchroot_on {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/config.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.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 $etc_dir = File::Spec->rel2abs("$tmpdir/etc");
+  my $lib_dir = File::Spec->rel2abs("$tmpdir/lib");
+  mkpath($etc_dir, $lib_dir);
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    DefaultRoot => '~',
+    RLimitChroot => 'on',
+
+    IfModules => {
+      '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, $expected);
+
+      # First, try to remove sensitive directories.
+
+      eval { $client->rmd('/etc') };
+      unless ($@) {
+        die("RMD /etc succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+ 
+      $expected = '/etc: Permission denied';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      eval { $client->rmd('etc') };
+      unless ($@) {
+        die("RMD etc succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+ 
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'etc: Permission denied';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      eval { $client->rmd('/lib') };
+      unless ($@) {
+        die("RMD /lib succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+ 
+      $expected = '/lib: Permission denied';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      eval { $client->rmd('lib') };
+      unless ($@) {
+        die("RMD lib succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+ 
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'lib: Permission denied';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      # Next, try to upload new files into those directories.
+
+      my $conn = $client->stor_raw('/etc/nsswitch.conf');
+      if ($conn) {
+        die("STOR /etc/nsswitch.conf succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = '/etc/nsswitch.conf: Permission denied';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      $conn = $client->stor_raw('lib/nss_compat.so.1');
+      if ($conn) {
+        die("STOR lib/nss_compat.so.1 succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'lib/nss_compat.so.1: Permission denied';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      # Last, try to rename them.
+      ($resp_code, $resp_msg) = $client->rnfr('/etc');
+
+      $expected = 350;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'File or directory exists, ready for destination name';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      eval { $client->rnto('/tmp') };
+      unless ($@) {
+        die("RNTO /tmp succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'Rename /tmp: Permission denied';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->rnfr('lib');
+
+      $expected = 350;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'File or directory exists, ready for destination name';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      eval { $client->rnto('foo') };
+      unless ($@) {
+        die("RNTO foo succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $expected = 550;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'Rename foo: Permission denied';
+      $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);
+
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub rlimitchroot_off {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/config.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.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 $etc_dir = File::Spec->rel2abs("$tmpdir/etc");
+  my $lib_dir = File::Spec->rel2abs("$tmpdir/lib");
+  mkpath($etc_dir, $lib_dir);
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    DefaultRoot => '~',
+    RLimitChroot => 'off',
+
+    IfModules => {
+      '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, $expected);
+
+      ($resp_code, $resp_msg) = $client->rmd('/etc');
+
+      $expected = 250;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+ 
+      $expected = 'RMD command successful';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->rmd('lib');
+
+      $expected = 250;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'RMD command successful';
+      $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);
+
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+sub rlimitchroot_ifuser_off {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/config.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.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 $etc_dir = File::Spec->rel2abs("$tmpdir/etc");
+  my $lib_dir = File::Spec->rel2abs("$tmpdir/lib");
+  mkpath($etc_dir, $lib_dir);
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    DefaultRoot => '~',
+    RLimitChroot => 'on',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  if (open(my $fh, ">> $config_file")) {
+    print $fh <<EOC;
+<IfModule mod_ifsession.c>
+  <IfUser $user>
+    RLimitChroot off
+  </IfUser>
+</IfModule>
+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.
+  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, $expected);
+
+      ($resp_code, $resp_msg) = $client->rmd('/etc');
+
+      $expected = 250;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+ 
+      $expected = 'RMD command successful';
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+      ($resp_code, $resp_msg) = $client->rmd('lib');
+
+      $expected = 250;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = 'RMD command successful';
+      $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);
+
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
+1;
diff --git a/tests/t/lib/ProFTPD/Tests/Config/SocketOptions.pm b/tests/t/lib/ProFTPD/Tests/Config/SocketOptions.pm
index 9be295f..2b888b3 100644
--- a/tests/t/lib/ProFTPD/Tests/Config/SocketOptions.pm
+++ b/tests/t/lib/ProFTPD/Tests/Config/SocketOptions.pm
@@ -30,6 +30,11 @@ my $TESTS = {
     test_class => [qw(bug forking)],
   },
 
+  socketoptions_keepalive_on => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
+
 };
 
 sub new {
@@ -529,4 +534,143 @@ sub socketoptions_sndbuf_bug3607 {
   unlink($log_file);
 }
 
+sub socketoptions_keepalive_on {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/config.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
+
+  my $log_file = File::Spec->rel2abs('tests.log');
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.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 $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+  if (open(my $fh, "> $test_file")) {
+    print $fh "ABCD" x 8192;
+    unless (close($fh)) {
+      die("Can't write $test_file: $!");
+    }
+
+  } else {
+    die("Can't open $test_file: $!");
+  }
+
+  # See:
+  #   https://forums.proftpd.org/smf/index.php/topic,11514.0.html
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'DEFAULT:0 data:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    PassivePorts => "41200 43400",
+    SocketOptions => "keepalive on",
+
+    IfModules => {
+      '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);
+
+      for (my $i = 0; $i < 250; $i++) {
+        my $conn = $client->retr_raw('test.txt');
+        unless ($conn) {
+          die("Failed to RETR: " . $client->response_code() . " " .
+            $client->response_msg());
+        }
+
+        my $buf;
+        while ($conn->read($buf, 16384, 25)) {
+        }
+        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) {
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
 1;
diff --git a/tests/t/lib/ProFTPD/Tests/Logging/ExtendedLog.pm b/tests/t/lib/ProFTPD/Tests/Logging/ExtendedLog.pm
index 95f2496..4f711f2 100644
--- a/tests/t/lib/ProFTPD/Tests/Logging/ExtendedLog.pm
+++ b/tests/t/lib/ProFTPD/Tests/Logging/ExtendedLog.pm
@@ -1795,7 +1795,7 @@ sub extlog_rename_from {
     AuthUserFile => $auth_user_file,
     AuthGroupFile => $auth_group_file,
 
-    LogFormat => 'custom "%w %f"',
+    LogFormat => 'custom "%m: %w %f"',
     ExtendedLog => "$ext_log WRITE custom",
 
     IfModules => {
@@ -1832,11 +1832,11 @@ sub extlog_rename_from {
 
       $expected = 250;
       $self->assert($expected == $resp_code,
-        test_msg("Expected $expected, got $resp_code"));
+        test_msg("Expected response code $expected, got $resp_code"));
 
       $expected = "Rename successful";
       $self->assert($expected eq $resp_msg,
-        test_msg("Expected '$expected', got '$resp_msg'"));
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
 
       $client->quit();
     };
@@ -1869,20 +1869,28 @@ sub extlog_rename_from {
     my $line = <$fh>;
     chomp($line);
 
+    if ($ENV{TEST_VERBOSE}) {
+      print STDERR "logged: $line\n";
+    }
+
     if ($^O eq 'darwin') {
       # MacOSX-specific hack
       $src_file = '/private' . $src_file;
       $dst_file = '/private' . $dst_file;
     }
 
-    my $expected = "- $src_file";
+    my $expected = "RNFR: - $src_file";
     $self->assert($expected eq $line,
       test_msg("Expected '$expected', got '$line'"));
 
     $line = <$fh>;
     chomp($line);
 
-    $expected = "$src_file $dst_file";
+    if ($ENV{TEST_VERBOSE}) {
+      print STDERR "logged: $line\n";
+    }
+
+    $expected = "RNTO: $src_file $dst_file";
     $self->assert($expected eq $line,
       test_msg("Expected '$expected', got '$line'"));
 
@@ -11886,6 +11894,13 @@ sub extlog_dirs_class_var_f_bug3966 {
     if (open(my $fh, "< $ext_log")) {
       my $ok = 1;
 
+      if ($^O eq 'darwin') {
+        # MacOSX-specific hack
+        $home_dir = '/private' . $home_dir;
+        $test_file = '/private' . $test_file;
+        $test_dir = '/private' . $test_dir;
+      }
+
       while (my $line = <$fh>) {
         chomp($line);
 
@@ -11896,13 +11911,6 @@ sub extlog_dirs_class_var_f_bug3966 {
           # We don't mind if the path ends in a trailing slash; ignore it
           $file_path =~ s/\/$//;
 
-          if ($^O eq 'darwin') {
-            # MacOSX-specific hack
-            $home_dir = '/private' . $home_dir;
-            $test_file = '/private' . $test_file;
-            $test_dir = '/private' . $test_dir;
-          }
-
           if ($cmd eq 'CWD' || $cmd eq 'XCWD' ||
               $cmd eq 'LIST' || $cmd eq 'MLSD' || $cmd eq 'NLST') {
             $self->assert($test_dir eq $file_path,
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_ban.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_ban.pm
index e05e62a..f2f88bf 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_ban.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_ban.pm
@@ -71,6 +71,16 @@ my $TESTS = {
     test_class => [qw(forking mod_tls)],
   },
 
+  ban_on_event_rootlogin => {
+    order => ++$order,
+    test_class => [qw(forking)],
+  },
+
+  ban_on_event_rootlogin_userdefined => {
+    order => ++$order,
+    test_class => [qw(forking)],
+  },
+
 };
 
 sub new {
@@ -1982,4 +1992,366 @@ sub ban_on_event_tlshandshake {
   unlink($log_file);
 }
 
+sub ban_on_event_rootlogin {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/ban.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/ban.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ban.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $ban_tab = File::Spec->rel2abs("$tmpdir/ban.tab");
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/ban.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/ban.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $group = 'ftpd';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  my $root_user = 'root';
+  my $root_passwd = 'root';
+  my $root_group = 'ftpd';
+  my $root_home_dir = File::Spec->rel2abs($tmpdir);
+  my $root_uid = 0;
+  my $root_gid = 0;
+
+  # 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);
+
+  auth_user_write($auth_user_file, $root_user, $root_passwd, $root_uid,
+    $root_gid, $root_home_dir, '/bin/bash');
+  auth_group_write($auth_group_file, $root_group, $root_gid, $root_user);
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'event:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    RootLogin => 'off',
+
+    IfModules => {
+      'mod_ban.c' => {
+        BanEngine => 'on',
+        BanLog => $log_file,
+
+        # This says to ban a client which requests a root login more than twice
+        # in the last 1 minute will be banned for 5 secs
+        BanOnEvent => 'RootLogin 2/00:01:00 00:00:05',
+
+        BanTable => $ban_tab,
+      },
+
+      '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::FTPSSL;
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      # Give server time to start up
+      sleep(2);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      eval { $client->login($root_user, $root_passwd) };
+      unless ($@) {
+        die("Login succeeded unexpectedly");
+      }
+
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+
+      my $expected;
+
+      $expected = 530;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = "Login incorrect.";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+      $client->quit();
+
+      $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->user($root_user);
+      eval { $client->pass($root_passwd) };
+      unless ($@) {
+        die("PASS succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $expected = 000;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      # Now try again with the correct info; we should be banned.  Note
+      # that we have to create a separate connection for this.
+      eval { $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port,
+        undef, 0) };
+      unless ($@) {
+        die("Connect succeeded unexpectedly");
+      }
+
+      my $conn_ex = ProFTPD::TestSuite::FTP::get_connect_exception();
+
+      $expected = "";
+      $self->assert($expected eq $conn_ex,
+        test_msg("Expected '$expected', got '$conn_ex'"));
+    };
+
+    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 ban_on_event_rootlogin_userdefined {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/ban.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/ban.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ban.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $ban_tab = File::Spec->rel2abs("$tmpdir/ban.tab");
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/ban.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/ban.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $group = 'ftpd';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  my $root_user = 'root';
+  my $root_passwd = 'root';
+  my $root_group = 'ftpd';
+  my $root_home_dir = File::Spec->rel2abs($tmpdir);
+  my $root_uid = 0;
+  my $root_gid = 0;
+
+  # 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);
+
+  auth_user_write($auth_user_file, $root_user, $root_passwd, $root_uid,
+    $root_gid, $root_home_dir, '/bin/bash');
+  auth_group_write($auth_group_file, $root_group, $root_gid, $root_user);
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'event:10',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    RootLogin => 'off',
+
+    IfModules => {
+      'mod_ban.c' => {
+        BanEngine => 'on',
+        BanLog => $log_file,
+
+        # This says to ban a client which requests a root login more than twice
+        # in the last 1 minute will be banned for 5 secs
+        BanOnEvent => 'mod_auth.root-login 2/00:01:00 00:00:05',
+
+        BanTable => $ban_tab,
+      },
+
+      '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::FTPSSL;
+
+  my $ex;
+
+  # Fork child
+  $self->handle_sigchld();
+  defined(my $pid = fork()) or die("Can't fork: $!");
+  if ($pid) {
+    eval {
+      # Give server time to start up
+      sleep(2);
+
+      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      eval { $client->login($root_user, $root_passwd) };
+      unless ($@) {
+        die("Login succeeded unexpectedly");
+      }
+
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+
+      my $expected;
+
+      $expected = 530;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = "Login incorrect.";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+      $client->quit();
+
+      $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+      $client->user($root_user);
+      eval { $client->pass($root_passwd) };
+      unless ($@) {
+        die("PASS succeeded unexpectedly");
+      }
+
+      $resp_code = $client->response_code();
+      $resp_msg = $client->response_msg();
+
+      $expected = 000;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      # Now try again with the correct info; we should be banned.  Note
+      # that we have to create a separate connection for this.
+      eval { $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port,
+        undef, 0) };
+      unless ($@) {
+        die("Connect succeeded unexpectedly");
+      }
+
+      my $conn_ex = ProFTPD::TestSuite::FTP::get_connect_exception();
+
+      $expected = "";
+      $self->assert($expected eq $conn_ex,
+        test_msg("Expected '$expected', got '$conn_ex'"));
+    };
+
+    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);
+}
+
 1;
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm
index 80b554e..25dc0be 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_rewrite.pm
@@ -198,6 +198,16 @@ my $TESTS = {
     test_class => [qw(bug forking)],
   },
 
+  rewrite_bug4017 => {
+    order => ++$order,
+    test_class => [qw(bug forking rootprivs)],
+  },
+
+  rewrite_using_pcre_bug4017 => {
+    order => ++$order,
+    test_class => [qw(bug feature_pcre forking rootprivs)],
+  },
+
 };
 
 sub new {
@@ -5533,7 +5543,6 @@ sub rewrite_bug3767 {
         DelayEngine => 'off',
       },
 
-
       'mod_rewrite.c' => [
         'RewriteEngine on',
         "RewriteLog $log_file",
@@ -5615,4 +5624,273 @@ sub rewrite_bug3767 {
   unlink($log_file);
 }
 
+sub rewrite_bug4017 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/rewrite.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/rewrite.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/rewrite.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/rewrite.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/rewrite.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_dir = File::Spec->rel2abs("$tmpdir/foo.d");
+  mkpath($test_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)) {
+      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,
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    DefaultRoot => '~',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_rewrite.c' => [
+        'RewriteEngine on',
+        "RewriteLog $log_file",
+        'RewriteMap replace int:replaceall',
+
+        'RewriteCondition %m !PASS',
+        'RewriteCondition %m !USER',
+        'RewriteRule (^/[^\\\\]*\\\\) /$1',
+
+        'RewriteCondition %m !PASS',
+        'RewriteCondition %m !USER',
+        'RewriteRule (.*) ${replace:!$1!\\!/}',
+      ],
+    },
+  };
+
+  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->stat('/foo.d/\bar.d/\baz.d');
+
+      my $expected;
+
+      $expected = 211;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      ($resp_code, $resp_msg) = $client->stat('/foo.d\bar.d/\baz.d');
+
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $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 rewrite_using_pcre_bug4017 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/rewrite.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/rewrite.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/rewrite.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/rewrite.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/rewrite.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_dir = File::Spec->rel2abs("$tmpdir/foo.d");
+  mkpath($test_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)) {
+      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,
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    DefaultRoot => '~',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_rewrite.c' => [
+        'RewriteEngine on',
+        "RewriteLog $log_file",
+        'RewriteMap replace int:replaceall',
+
+        'RewriteCondition %m !PASS',
+        'RewriteCondition %m !USER',
+        'RewriteRule ^(.*?\\/\\\\)(.*) /$1',
+
+        'RewriteCondition %m !PASS',
+        'RewriteCondition %m !USER',
+        'RewriteRule (.*) ${replace:!$1!\\!/}',
+      ],
+    },
+  };
+
+  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->stat("/foo.d/\\bar.d/\\baz.d");
+
+      my $expected;
+
+      $expected = 211;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $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);
+}
+
 1;
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
index 7f06750..147db24 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
@@ -698,7 +698,12 @@ my $TESTS = {
     test_class => [qw(forking rootprivs sftp ssh2)],
   },
 
-  sftp_config_max_login_attempts => {
+  sftp_config_max_login_attempts_via_password => {
+    order => ++$order,
+    test_class => [qw(forking sftp ssh2)],
+  },
+
+  sftp_config_max_login_attempts_via_publickey => {
     order => ++$order,
     test_class => [qw(forking sftp ssh2)],
   },
@@ -1188,6 +1193,11 @@ my $TESTS = {
     test_class => [qw(forking scp ssh2)],
   },
 
+  scp_ext_upload_file_with_timestamp_bug4026 => {
+    order => ++$order,
+    test_class => [qw(forking rootprivs scp ssh2)],
+  },
+
   scp_download => {
     order => ++$order,
     test_class => [qw(forking scp ssh2)],
@@ -5682,6 +5692,7 @@ sub ssh2_cipher_c2s_none {
         "SFTPLog $log_file",
         "SFTPHostKey $rsa_host_key",
         "SFTPHostKey $dsa_host_key",
+        "SFTPCiphers none",
       ],
     },
   };
@@ -5718,7 +5729,7 @@ sub ssh2_cipher_c2s_none {
       }
 
       my $cipher_used = $ssh2->method('crypt_cs');
-      $self->assert($cipher ne $cipher_used,
+      $self->assert($cipher eq $cipher_used,
         test_msg("Expected '$cipher', got '$cipher_used'"));
 
       $ssh2->disconnect();
@@ -6751,6 +6762,7 @@ sub ssh2_cipher_s2c_none {
         "SFTPLog $log_file",
         "SFTPHostKey $rsa_host_key",
         "SFTPHostKey $dsa_host_key",
+        "SFTPCiphers none",
       ],
     },
   };
@@ -6787,7 +6799,7 @@ sub ssh2_cipher_s2c_none {
       }
 
       my $cipher_used = $ssh2->method('crypt_sc');
-      $self->assert($cipher ne $cipher_used,
+      $self->assert($cipher eq $cipher_used,
         test_msg("Expected '$cipher', got '$cipher_used'"));
 
       $ssh2->disconnect();
@@ -7549,6 +7561,7 @@ sub ssh2_mac_c2s_none {
         "SFTPLog $log_file",
         "SFTPHostKey $rsa_host_key",
         "SFTPHostKey $dsa_host_key",
+        "SFTPDigests none",
       ],
     },
   };
@@ -7585,7 +7598,7 @@ sub ssh2_mac_c2s_none {
       }
 
       my $mac_used = $ssh2->method('mac_cs');
-      $self->assert($mac ne $mac_used,
+      $self->assert($mac eq $mac_used,
         test_msg("Expected '$mac', got '$mac_used'"));
 
       $ssh2->disconnect();
@@ -8347,6 +8360,7 @@ sub ssh2_mac_s2c_none {
         "SFTPLog $log_file",
         "SFTPHostKey $rsa_host_key",
         "SFTPHostKey $dsa_host_key",
+        "SFTPDigests none",
       ],
     },
   };
@@ -8383,7 +8397,7 @@ sub ssh2_mac_s2c_none {
       }
 
       my $mac_used = $ssh2->method('mac_sc');
-      $self->assert($mac ne $mac_used,
+      $self->assert($mac eq $mac_used,
         test_msg("Expected '$mac', got '$mac_used'"));
 
       $ssh2->disconnect();
@@ -25705,7 +25719,7 @@ EOC
   unlink($log_file);
 }
 
-sub sftp_config_max_login_attempts {
+sub sftp_config_max_login_attempts_via_password {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
 
@@ -25862,6 +25876,169 @@ sub sftp_config_max_login_attempts {
   unlink($log_file);
 }
 
+sub sftp_config_max_login_attempts_via_publickey {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/sftp.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/sftp.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sftp.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/sftp.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/sftp.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 $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+  my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+  my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key');
+  my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub');
+  my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys');
+
+  my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys");
+  unless (copy($rsa_rfc4716_key, $authorized_keys)) {
+    die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!");
+  }
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    MaxLoginAttempts => 1,
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+        "SFTPAuthorizedUserKeys file:~/.authorized_keys",
+      ],
+    },
+  };
+
+  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_publickey($user, $rsa_pub_key, $rsa_priv_key)) {
+        my ($err_code, $err_name, $err_str) = $ssh2->error();
+        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
+      }
+
+      $ssh2->disconnect();
+
+      # Now connect again, try to authenticate via 'password', which should
+      # fail, and then again via 'publickey', which should also fail, since
+      # it exceeds the MaxLoginAttempts of 1.
+
+      $ssh2 = Net::SSH2->new();
+      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");
+      }
+
+      if ($ssh2->auth_password($user, 'foobar')) {
+        die("Password auth succeeded unexpectedly");
+      }
+
+      if ($ssh2->auth_publickey($user, $rsa_pub_key, $rsa_priv_key)) {
+        die("Publickey auth succeeded unexpectedly");
+      }
+    };
+
+    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 sftp_config_pathdenyfilter_file {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
@@ -42061,6 +42238,219 @@ sub scp_ext_upload_shorter_file_bug4013 {
   unlink($log_file);
 }
 
+sub scp_ext_upload_file_with_timestamp_bug4026 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/sftp.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/sftp.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sftp.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/sftp.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/sftp.group");
+
+  my $user = 'proftpd';
+  my $passwd = 'test';
+  my $group = 'ftpd';
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  my $sub_dir = File::Spec->rel2abs("$tmpdir/sub.d");
+  mkpath($sub_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, $sub_dir)) {
+      die("Can't set perms on $home_dir to 0755: $!");
+    }
+
+    unless (chown($uid, $gid, $home_dir, $sub_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 $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+  my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+  my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key');
+  my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub');
+  my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys');
+
+  my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys");
+  unless (copy($rsa_rfc4716_key, $authorized_keys)) {
+    die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!");
+  }
+
+  my $src_file = File::Spec->rel2abs("$tmpdir/src.txt");
+  if (open(my $fh, "> $src_file")) {
+    print $fh "ABCDefgh\n";
+    unless (close($fh)) {
+      die("Can't write $src_file: $!");
+    }
+
+  } else {
+    die("Can't open $src_file: $!");
+  }
+
+  unless (utime(0, 0, $src_file)) {
+    die("Can't set timestamps on $src_file: $!");
+  }
+
+  my $dst_file = File::Spec->rel2abs("$sub_dir/dst.txt");
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    AllowOverwrite => 'on',
+    DefaultRoot => '~',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+        "SFTPAuthorizedUserKeys file:~/.authorized_keys",
+      ],
+    },
+  };
+
+  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 @cmd = (
+        'scp',
+        '-v',
+        '-p',
+        '-oBatchMode=yes',
+        '-oCheckHostIP=no',
+        "-oPort=$port",
+        "-oIdentityFile=$rsa_priv_key",
+        '-oPubkeyAuthentication=yes',
+        '-oStrictHostKeyChecking=no',
+        "$src_file",
+        "$user\@127.0.0.1:sub.d/dst.txt",
+      );
+
+      my $scp_rh = IO::Handle->new();
+      my $scp_wh = IO::Handle->new();
+      my $scp_eh = IO::Handle->new();
+
+      $scp_wh->autoflush(1);
+
+      sleep(1);
+
+      local $SIG{CHLD} = 'DEFAULT';
+
+      # Make sure that the perms on the priv key are what OpenSSH wants
+      unless (chmod(0400, $rsa_priv_key)) {
+        die("Can't set perms on $rsa_priv_key to 0400: $!");
+      }
+
+      if ($ENV{TEST_VERBOSE}) {
+        print STDERR "Executing: ", join(' ', @cmd), "\n";
+      }
+
+      my $scp_pid = open3($scp_wh, $scp_rh, $scp_eh, @cmd);
+      waitpid($scp_pid, 0);
+      my $exit_status = $?;
+
+      # Restore the perms on the priv key
+      unless (chmod(0644, $rsa_priv_key)) {
+        die("Can't set perms on $rsa_priv_key to 0644: $!");
+      }
+
+      my ($res, $errstr);
+
+      $errstr = join('', <$scp_eh>);
+      if ($exit_status >> 8 == 0) {
+        $res = 0;
+
+      } else {
+        $res = 1;
+      }
+
+      unless ($res == 0) {
+        die("Can't upload $src_file to server: $errstr");
+      }
+
+      $self->assert(-f $dst_file,
+        test_msg("File $dst_file does not exist as expected"));
+
+      my ($atime, $mtime) = (stat($dst_file))[8,9]; 
+      $self->assert($atime == 0, test_msg("Expected atime 0, got $atime"));
+      $self->assert($mtime == 0, test_msg("Expected mtime 0, got $mtime"));
+    };
+
+    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 scp_download {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/rewrite.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/rewrite.pm
index 33d08cd..14f885b 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/rewrite.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/rewrite.pm
@@ -46,6 +46,11 @@ my $TESTS = {
     test_class => [qw(forking mod_rewrite sftp ssh2)],
   },
 
+  sftp_rewrite_realpath_backslashes_bug4017 => {
+    order => ++$order,
+    test_class => [qw(bug forking mod_rewrite sftp ssh2)],
+  },
+
   sftp_rewrite_upload => {
     order => ++$order,
     test_class => [qw(forking mod_rewrite sftp ssh2)],
@@ -1044,6 +1049,177 @@ sub sftp_rewrite_realpath {
   unlink($log_file);
 }
 
+sub sftp_rewrite_realpath_backslashes_bug4017 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/sftp.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/sftp.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sftp.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/sftp.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/sftp.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 $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+  my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_rewrite.c' => [
+        'RewriteEngine on',
+        "RewriteLog $log_file",
+
+        'RewriteMap replace int:replaceall',
+        'RewriteCondition %m REALPATH',
+        'RewriteRule (.*) "${replace:!$1!\\\\!/}"',
+      ],
+
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $log_file",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($config_file, $config);
+
+  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
+  if (open(my $fh, "> $test_file")) {
+    unless (close($fh)) {
+      die("Can't write $test_file: $!");
+    }
+
+  } else {
+    die("Can't open $test_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.
+  my ($rfh, $wfh);
+  unless (pipe($rfh, $wfh)) {
+    die("Can't open pipe: $!");
+  }
+
+  require Net::SSH2;
+
+  my $ex;
+
+  # 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 $munged = $test_file;
+      $munged =~ s/\//\\/g;
+      
+      my $resolved = $sftp->realpath($munged);
+      unless ($resolved) {
+        my ($err_code, $err_name) = $sftp->error();
+        die("FXP_REALPATH failed: [$err_name] ($err_code)");
+      }
+
+      my $expected;
+
+      $expected = $test_file;
+      $self->assert($expected eq $resolved,
+        test_msg("Expected '$expected', got '$resolved'"));
+
+      $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) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
 sub sftp_rewrite_upload {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_passwd.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_passwd.pm
index a5d1b00..cb636eb 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_passwd.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_passwd.pm
@@ -160,6 +160,11 @@ my $TESTS = {
     test_class => [qw(forking)],
   },
 
+  sql_passwd_pbkdf2_per_user_bug4052 => {
+    order => ++$order,
+    test_class => [qw(forking bug)],
+  },
+
 };
 
 sub new {
@@ -5258,4 +5263,200 @@ EOS
   unlink($log_file);
 }
 
+sub sql_passwd_pbkdf2_per_user_bug4052 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/sqlpasswd.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $user = 'proftpd';
+  my $group = 'ftpd';
+
+  # RFC 6070: PKCS#5 PBKDF2 Test Vectors
+  #
+  # Input:
+  #   P = "password" (8 octets)
+  #   S = "salt" (4 octets)
+  #   c = 4096
+  #   dkLen = 20
+  #
+  # Output:
+  #   DK = 4b 00 79 01 b7 65 48 9a
+  #        be ad 49 d9 26 f7 21 d0
+  #        65 a4 29 c1             (20 octets)
+  #
+  # Base64:
+  #   DK = SwB5AbdlSJq+rUnZJvch0GWkKcE=
+  #
+  my $passwd = "SwB5AbdlSJq+rUnZJvch0GWkKcE=";
+
+  my $home_dir = File::Spec->rel2abs($tmpdir);
+  my $uid = 500;
+  my $gid = 500;
+
+  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
+
+  # Build up sqlite3 command to create users, groups tables and populate them
+  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
+
+  if (open(my $fh, "> $db_script")) {
+    print $fh <<EOS;
+CREATE TABLE users (
+  userid TEXT,
+  passwd TEXT,
+  uid INTEGER,
+  gid INTEGER,
+  homedir TEXT, 
+  shell TEXT
+);
+INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
+
+CREATE TABLE groups (
+  groupname TEXT,
+  gid INTEGER,
+  members TEXT
+);
+INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
+
+CREATE TABLE user_pbkdf2 (
+  userid TEXT,
+  algo TEXT,
+  rounds INTEGER,
+  len INTEGER
+);
+INSERT INTO user_pbkdf2 (userid, algo, rounds, len) VALUES ('$user', 'sha1', 4096, 20);
+EOS
+
+    unless (close($fh)) {
+      die("Can't write $db_script: $!");
+    }
+
+  } else {
+    die("Can't open $db_script: $!");
+  }
+
+  my $cmd = "sqlite3 $db_file < $db_script";
+
+  if ($ENV{TEST_VERBOSE}) {
+    print STDERR "Executing sqlite3: $cmd\n";
+  }
+
+  my @output = `$cmd`;
+  if (scalar(@output) &&
+      $ENV{TEST_VERBOSE}) {
+    print STDERR "Output: ", join('', @output), "\n";
+  }
+
+  my $salt = 'salt';
+
+  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
+  if (open(my $fh, "> $salt_file")) {
+    binmode($fh);
+    print $fh $salt;
+
+    unless (close($fh)) {
+      die("Can't write $salt_file: $!");
+    }
+
+  } else {
+    die("Can't open $salt_file: $!");
+  }
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_sql.c' => {
+        SQLAuthTypes => 'pbkdf2',
+        SQLBackend => 'sqlite3',
+        SQLConnectInfo => $db_file,
+        SQLLogFile => $log_file,
+        SQLNamedQuery => 'get-user-pbkdf2 SELECT "algo, rounds, len FROM user_pbkdf2 WHERE userid = \'%{0}\'"',
+      },
+
+      'mod_sql_passwd.c' => {
+        SQLPasswordEngine => 'on',
+        SQLPasswordEncoding => 'base64',
+        SQLPasswordPBKDF2 => 'sql:/get-user-pbkdf2',
+        SQLPasswordSaltFile => $salt_file,
+      },
+    },
+  };
+
+  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, "password");
+
+      my $resp_msgs = $client->response_msgs();
+      my $nmsgs = scalar(@$resp_msgs);
+
+      my $expected;
+
+      $expected = 1;
+      $self->assert($expected == $nmsgs,
+        test_msg("Expected $expected, got $nmsgs")); 
+
+      $expected = "User proftpd logged in";
+      $self->assert($expected eq $resp_msgs->[0],
+        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
+    };
+
+    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);
+}
+
 1;
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm
index 493c4f0..533d549 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm
@@ -262,6 +262,11 @@ my $TESTS = {
     test_class => [qw(forking mod_ifsession)],
   },
 
+  sql_sqllog_multi_pass_ifclass_bug4025 => {
+    order => ++$order,
+    test_class => [qw(forking mod_ifsession)],
+  },
+
   sql_opt_no_disconnect_on_error_with_extlog_bug3633 => {
     order => ++$order,
     test_class => [qw(bug forking)],
@@ -8916,6 +8921,200 @@ EOC
   unlink($log_file);
 }
 
+sub sql_sqllog_multi_pass_ifclass_bug4025 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/sqlite.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlite.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlite.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/sqlite.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/sqlite.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 $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
+
+  # Build up sqlite3 command to create users, groups tables and populate them
+  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
+
+  if (open(my $fh, "> $db_script")) {
+    print $fh <<EOS;
+CREATE TABLE ftpsessions (
+  user TEXT,
+  ip_addr TEXT
+);
+EOS
+
+    unless (close($fh)) {
+      die("Can't write $db_script: $!");
+    }
+
+  } else {
+    die("Can't open $db_script: $!");
+  }
+
+  my $cmd = "sqlite3 $db_file < $db_script";
+  build_db($cmd, $db_script);
+
+  # Make sure that, if we're running as root, the database file has
+  # the permissions/privs set for use by proftpd
+  if ($< == 0) {
+    unless (chmod(0666, $db_file)) {
+      die("Can't set perms on $db_file to 0666: $!");
+    }
+  }
+
+  my $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'DEFAULT:10 ifsession:20',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+    AuthOrder => 'mod_auth_file.c',
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_sql.c' => [
+        'SQLEngine log',
+        'SQLBackend sqlite3',
+        "SQLConnectInfo $db_file",
+        "SQLLogFile $log_file",
+        'SQLNamedQuery login_ip FREEFORM "INSERT INTO ftpsessions (ip_addr) VALUES (\'%L\')"',
+        'SQLNamedQuery login_user FREEFORM "INSERT INTO ftpsessions (user) VALUES (\'%u\')"',
+      ],
+    },
+  };
+
+  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>
+
+<IfModule mod_ifsession.c>
+  <IfClass test>
+    SQLLog PASS login_ip
+    SQLLog PASS login_user
+  </IfClass>
+</IfModule>
+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.
+  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->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);
+  }
+
+  my $query = "SELECT user, ip_addr FROM ftpsessions";
+  $cmd = "sqlite3 $db_file \"$query\"";
+
+  if ($ENV{TEST_VERBOSE}) {
+    print STDERR "Executing sqlite3: $cmd\n";
+  }
+
+  my @res = `$cmd`;
+  my $res = join('', @res);
+  $res =~ s/\n//g;
+  chomp($res);
+
+  my ($login, $ip_addr) = split(/\|+/, $res);
+
+  my $expected;
+
+  $expected = $user;
+  $self->assert($expected eq $login,
+    test_msg("Expected '$expected', got '$login'"));
+
+  $expected = '127.0.0.1';
+  $self->assert($expected eq $ip_addr,
+    test_msg("Expected '$expected', got '$ip_addr'"));
+
+  unlink($log_file);
+}
+
 sub sql_opt_no_disconnect_on_error_with_extlog_bug3633 {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm
index 7d7df37..a42d12e 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm
@@ -4286,9 +4286,17 @@ sub tls_rest_2gb_last_byte {
 
       my $rest_len = $test_len - 1;
 
-      # Note: The duplicated arguments here are to work around a bug in
-      # Net::FTPSSL, version 0.21, in the quot() function.
-      unless ($client->quot('REST', $rest_len, $rest_len)) {
+      my $res;
+      if ($Net::FTPSSL::VERSION == 0.21) {
+        # Note: The duplicated arguments here are to work around a bug in
+        # Net::FTPSSL, version 0.21, in the quot() function.
+        $res = $client->quot('REST', $rest_len, $rest_len);
+
+      } else {
+        $res = $client->quot('REST', $rest_len);
+      }
+
+      unless ($res) {
         die("Can't REST $rest_len: " . $client->last_message());
       }
 
@@ -4491,9 +4499,17 @@ sub tls_rest_4gb_last_byte {
 
       my $rest_len = $test_len - 1;
 
-      # Note: The duplicated arguments here are to work around a bug in
-      # Net::FTPSSL, version 0.21, in the quot() function.
-      unless ($client->quot('REST', $rest_len, $rest_len)) {
+      my $res;
+      if ($Net::FTPSSL::VERSION == 0.21) {
+        # Note: The duplicated arguments here are to work around a bug in
+        # Net::FTPSSL, version 0.21, in the quot() function.
+        $res = $client->quot('REST', $rest_len, $rest_len);
+
+      } else {
+        $res = $client->quot('REST', $rest_len);
+      }
+
+      unless ($res) {
         die("Can't REST $rest_len: " . $client->last_message());
       }
 
@@ -9018,7 +9034,17 @@ EOC
         die("Can't login: " . $client->last_message());
       }
 
-      unless ($client->quot('PROT', 'P')) {
+      my $res;
+      if ($Net::FTPSSL::VERSION == 0.21) {
+        # Note: The duplicated arguments here are to work around a bug in
+        # Net::FTPSSL, version 0.21, in the quot() function.
+        $res = $client->quot('PROT', 'P', 'P');
+
+      } else {
+        $res = $client->quot('PROT', 'P');
+      }
+
+      unless ($res) {
         die("PROT failed unexpectedly: " . $client->last_message());
       }
 
@@ -9603,9 +9629,16 @@ sub tls_sscn_bad_arg_bug3955 {
         die("Can't login: " . $client->last_message());
       }
 
-      # Note: The duplicated arguments here are to work around a bug in
-      # Net::FTPSSL, version 0.21, in the quot() function.
-      my $resp_code = $client->quot('SSCN', 'true', 'true');
+      my $resp_code;
+      if ($Net::FTPSSL::VERSION == 0.21) {
+        # Note: The duplicated arguments here are to work around a bug in
+        # Net::FTPSSL, version 0.21, in the quot() function.
+        $resp_code = $client->quot('SSCN', 'true', 'true');
+
+      } else {
+        $resp_code = $client->quot('SSCN', 'true');
+      }
+
       if ($resp_code == 2) {
         die("SSCN succeeded unexpectedly");
       }
@@ -9749,9 +9782,16 @@ sub tls_sscn_toggle_bug3955 {
         die("Can't login: " . $client->last_message());
       }
 
-      # Note: The duplicated arguments here are to work around a bug in
-      # Net::FTPSSL, version 0.21, in the quot() function.
-      my $resp_code = $client->quot('SSCN', 'ON', 'ON');
+      my $resp_code;
+      if ($Net::FTPSSL::VERSION == 0.21) {
+        # Note: The duplicated arguments here are to work around a bug in
+        # Net::FTPSSL, version 0.21, in the quot() function.
+        $resp_code = $client->quot('SSCN', 'ON', 'ON');
+
+      } else {
+        $resp_code = $client->quot('SSCN', 'ON');
+      } 
+      
       unless ($resp_code == 2) {
         die("SSCN failed: " . $client->last_message());
       }
@@ -9773,9 +9813,15 @@ sub tls_sscn_toggle_bug3955 {
       $self->assert($expected eq $resp_msg,
         test_msg("Expected response message '$expected', got '$resp_msg'"));
 
-      # Note: The duplicated arguments here are to work around a bug in
-      # Net::FTPSSL, version 0.21, in the quot() function.
-      $resp_code = $client->quot('SSCN', 'OFF', 'OFF');
+      if ($Net::FTPSSL::VERSION == 0.21) {
+        # Note: The duplicated arguments here are to work around a bug in
+        # Net::FTPSSL, version 0.21, in the quot() function.
+        $resp_code = $client->quot('SSCN', 'OFF', 'OFF');
+
+      } else {
+        $resp_code = $client->quot('SSCN', 'OFF');
+      }
+
       unless ($resp_code == 2) {
         die("SSCN failed: " . $client->last_message());
       }
@@ -9936,9 +9982,16 @@ sub tls_config_limit_sscn_bug3955 {
         die("Can't login: " . $client->last_message());
       }
 
-      # Note: The duplicated arguments here are to work around a bug in
-      # Net::FTPSSL, version 0.21, in the quot() function.
-      my $resp_code = $client->quot('SSCN', 'ON', 'ON');
+      my $resp_code;
+      if ($Net::FTPSSL::VERSION == 0.21) {
+        # Note: The duplicated arguments here are to work around a bug in
+        # Net::FTPSSL, version 0.21, in the quot() function.
+        $resp_code = $client->quot('SSCN', 'ON', 'ON');
+
+      } else {
+        $resp_code = $client->quot('SSCN', 'ON');
+      }
+
       if ($resp_code == 2) {
         die("SSCN succeeded unexpectedly");
       }
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_wrap2_file.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_wrap2_file.pm
index ff86a5d..90bc2da 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_wrap2_file.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_wrap2_file.pm
@@ -136,6 +136,11 @@ my $TESTS = {
     test_class => [qw(bug forking mod_sftp mod_wrap2)],
   },
 
+  wrap2_file_user_table_reverse_dns_bug3938 => {
+    order => ++$order,
+    test_class => [qw(bug forking)],
+  },
+
 };
 
 sub new {
@@ -4495,4 +4500,195 @@ sub wrap2_sftp_extlog_user_bug3727 {
   unlink($log_file);
 }
 
+sub wrap2_file_user_table_reverse_dns_bug3938 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+
+  my $config_file = "$tmpdir/wrap2.conf";
+  my $pid_file = File::Spec->rel2abs("$tmpdir/wrap2.pid");
+  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/wrap2.scoreboard");
+
+  my $log_file = test_get_logfile();
+
+  my $auth_user_file = File::Spec->rel2abs("$tmpdir/wrap2.passwd");
+  my $auth_group_file = File::Spec->rel2abs("$tmpdir/wrap2.group");
+
+  my $fh;
+  my $allow_file = File::Spec->rel2abs("$tmpdir/wrap2.allow");
+  if (open($fh, "> $allow_file")) {
+    unless (close($fh)) {
+      die("Can't write $allow_file: $!");
+    }
+
+  } else {
+    die("Can't open $allow_file: $!");
+  }
+
+  my $deny_file = File::Spec->rel2abs("$tmpdir/wrap2.deny");
+  if (open($fh, "> $deny_file")) {
+    print $fh "ALL: 127.0.0.1\n";
+
+    unless (close($fh)) {
+      die("Can't write $deny_file: $!");
+    }
+
+  } else {
+    die("Can't open $deny_file: $!");
+  }
+
+  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 $config = {
+    PidFile => $pid_file,
+    ScoreboardFile => $scoreboard_file,
+    SystemLog => $log_file,
+    TraceLog => $log_file,
+    Trace => 'dns:20',
+
+    AuthUserFile => $auth_user_file,
+    AuthGroupFile => $auth_group_file,
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_wrap2.c' => {
+        WrapEngine => 'on',
+        WrapUserTables => "!other file:$allow_file file:$deny_file",
+        WrapLog => $log_file,
+      },
+    },
+  };
+
+  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);
+      eval { $client->login($user, $passwd) };
+      unless ($@) {
+        die("Login succeeded unexpectedly");
+      }
+
+      my $resp_code = $client->response_code();
+      my $resp_msg = $client->response_msg();
+
+      my $expected;
+
+      $expected = 530;
+      $self->assert($expected == $resp_code,
+        test_msg("Expected response code $expected, got $resp_code"));
+
+      $expected = "Access denied";
+      $self->assert($expected eq $resp_msg,
+        test_msg("Expected response message '$expected', got '$resp_msg'"));
+    };
+
+    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);
+  }
+
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  eval {
+    if (open(my $fh, "< $log_file")) {
+      my $ok = 1;
+
+      while (my $line = <$fh>) {
+        chomp($line);
+
+        if ($line =~ /via reverse DNS/) {
+          $ok = 0;
+          last;
+        }
+      }
+
+      close($fh);
+
+      $self->assert($ok,
+        test_msg("Saw unexpected 'via reverse DNS' log message in TraceLog"));
+
+    } else {
+      die("Can't read $log_file: $!");
+    }
+  };
+  if ($@) {
+    $ex = $@;
+  }
+
+  if ($ex) {
+    test_append_logfile($log_file, $ex);
+    unlink($log_file);
+
+    die($ex);
+  }
+
+  unlink($log_file);
+}
+
 1;
diff --git a/tests/tests.pl b/tests/tests.pl
index f4fd113..4fe18dc 100644
--- a/tests/tests.pl
+++ b/tests/tests.pl
@@ -149,11 +149,13 @@ if (scalar(@ARGV) > 0) {
     t/config/maxstorefilesize.t
     t/config/multilinerfc2228.t
     t/config/order.t
+    t/config/passiveports.t
     t/config/pathallowfilter.t
     t/config/pathdenyfilter.t
     t/config/protocols.t
     t/config/requirevalidshell.t
     t/config/rewritehome.t
+    t/config/rlimitchroot.t
     t/config/rlimitcpu.t
     t/config/rlimitmemory.t
     t/config/rlimitopenfiles.t

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-proftpd/proftpd-dfsg.git



More information about the Pkg-proftpd-maintainers mailing list