[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