[Git][debian-proftpd-team/proftpd][master] Patches for #2056 & #2098.
Hilmar Preuße (@hilmar)
gitlab at salsa.debian.org
Wed May 20 23:24:00 BST 2026
Hilmar Preuße pushed to branch master at Debian ProFTPD Team / proftpd
Commits:
e915933d by Hilmar Preuße at 2026-05-21T00:23:52+02:00
Patches for #2056 & #2098.
- - - - -
4 changed files:
- debian/changelog
- + debian/patches/508eb4d1b8c550b5f3059ad5b33d60c6b6c76bc7.diff
- + debian/patches/d4ff72c0c32db68ef2cecfe08c53ac42a7bf85dd.diff
- debian/patches/series
Changes:
=====================================
debian/changelog
=====================================
@@ -1,6 +1,9 @@
proftpd-dfsg (1.3.9a~dfsg-2) UNRELEASED; urgency=medium
* Three more patches for upstream_2052.
+ * Patches:
+ - Upstream Issue #2056
+ - Upstream Issue #2098
* B-D: default-libmysqlclient-dev => libmariadb-dev-compat
* Upgrade d/watch file to version 5.
=====================================
debian/patches/508eb4d1b8c550b5f3059ad5b33d60c6b6c76bc7.diff
=====================================
@@ -0,0 +1,41 @@
+From 508eb4d1b8c550b5f3059ad5b33d60c6b6c76bc7 Mon Sep 17 00:00:00 2001
+From: TJ Saunders <tj at castaglia.org>
+Date: Sun, 10 May 2026 09:37:17 -0700
+Subject: [PATCH] Issue #2056: Initial testing with OpenSSL 3.5.5 shows that
+ the prior changes for Issue #1963 were insufficient; adding a manual refcount
+ increment works.
+
+---
+ contrib/mod_tls.c | 18 ++++++++++++++++--
+ 1 file changed, 16 insertions(+), 2 deletions(-)
+
+diff --git a/contrib/mod_tls.c b/contrib/mod_tls.c
+index e78e4c17b..fede5b8cd 100644
+--- a/contrib/mod_tls.c
++++ b/contrib/mod_tls.c
+@@ -9095,9 +9095,23 @@ static void tls_end_sess(SSL *ssl, conn_t *conn, int flags) {
+ */
+ pr_trace_msg(trace_channel, 29,
+ "data SSL %p being ended has same SSL_SESSION %p as control SSL, "
+- "clearing the data SSL pointer manually (Issue #1963)", ssl,
++ "clearing the data SSL pointer manually (see Issue #1963)", ssl,
+ SSL_get_session(ssl));
+- SSL_set_session(ssl, NULL);
++ if (SSL_set_session(ssl, NULL) != 1) {
++ pr_trace_msg(trace_channel, 29,
++ "error setting NULL session on SSL %p: %s", ssl, tls_get_errors());
++ }
++
++ /* Note that, per findings in Issue #2056, we also need to manually
++ * increment the refcount of the ctrl_ssl session, lest we still
++ * inadvertently corrupt the OpenSSL internal session cache state.
++ * Sigh.
++ */
++ if (SSL_SESSION_up_ref(SSL_get_session(ctrl_ssl)) != 1) {
++ pr_trace_msg(trace_channel, 29,
++ "error incrementing session refcount on SSL %p: %s", ctrl_ssl,
++ tls_get_errors());
++ }
+ }
+
+ SSL_free(ssl);
=====================================
debian/patches/d4ff72c0c32db68ef2cecfe08c53ac42a7bf85dd.diff
=====================================
@@ -0,0 +1,395 @@
+From d4ff72c0c32db68ef2cecfe08c53ac42a7bf85dd Mon Sep 17 00:00:00 2001
+From: TJ Saunders <tj at castaglia.org>
+Date: Wed, 20 May 2026 11:37:10 -0700
+Subject: [PATCH] Issue #2098: Make sure that mod_quotatab provides a
+ `pwrite(2)` FSIO handler as well, so that uploads via SFTP using `pwrite(2)`
+ have hard upload limits enforced.
+
+---
+ contrib/mod_quotatab.c | 68 +++++
+ .../Tests/Modules/mod_sftp/quotatab.pm | 271 ++++++++++++++++++
+ tests/t/modules/mod_sftp/quotatab.t | 11 +
+ 3 files changed, 350 insertions(+)
+ create mode 100644 tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/quotatab.pm
+ create mode 100644 tests/t/modules/mod_sftp/quotatab.t
+
+diff --git a/contrib/mod_quotatab.c b/contrib/mod_quotatab.c
+index 903dca4b61..30d4f0348d 100644
+--- a/contrib/mod_quotatab.c
++++ b/contrib/mod_quotatab.c
+@@ -1432,6 +1432,73 @@ int quotatab_write(quota_tally_t *tally,
+
+ static off_t copied_bytes = 0;
+
++static ssize_t quotatab_fsio_pwrite(pr_fh_t *fh, int fd, const void *buf,
++ size_t bufsz, off_t offset) {
++ ssize_t res;
++ off_t total_bytes;
++
++ res = pwrite(fd, buf, bufsz, offset);
++ if (res < 0) {
++ return res;
++ }
++
++ if (have_quota_update == 0) {
++ return res;
++ }
++
++ /* Check to see if we've exceeded our upload limit. mod_xfer will
++ * have called pr_data_xfer(), which will have updated
++ * session.xfer.total_bytes, before calling pr_fsio_write(), so
++ * we do not have to worry about updated/changing session.xfer.total_bytes
++ * ourselves.
++ *
++ * Note that there is a race condition here: it is possible for the same
++ * user to be writing to the same file in chunks from multiple
++ * simultaneous connections.
++ */
++
++ /* If the client is copying a file (versus uploading a file), then we need
++ * to track the "total bytes" differently.
++ */
++ if (session.curr_cmd_id == PR_CMD_SITE_ID &&
++ (session.curr_cmd_rec->argc >= 2 &&
++ (strcasecmp(session.curr_cmd_rec->argv[1], "CPTO") == 0 ||
++ strcasecmp(session.curr_cmd_rec->argv[1], "COPY") == 0))) {
++ copied_bytes += res;
++ total_bytes = copied_bytes;
++
++ } else {
++ total_bytes = session.xfer.total_bytes;
++ }
++
++ if (sess_limit.bytes_in_avail > 0.0 &&
++ sess_tally.bytes_in_used + total_bytes > sess_limit.bytes_in_avail) {
++ int xerrno;
++ char *errstr = NULL;
++
++ xerrno = get_quota_exceeded_errno(EIO, &errstr);
++ quotatab_log("quotatab write(): limit exceeded, returning %s", errstr);
++
++ errno = xerrno;
++ return -1;
++ }
++
++ if (sess_limit.bytes_xfer_avail > 0.0 &&
++ sess_tally.bytes_xfer_used + total_bytes > sess_limit.bytes_xfer_avail) {
++ int xerrno;
++ char *errstr = NULL;
++
++ xerrno = get_quota_exceeded_errno(EIO, &errstr);
++ quotatab_log("quotatab write(): transfer limit exceeded, returning %s",
++ errstr);
++
++ errno = xerrno;
++ return -1;
++ }
++
++ return res;
++}
++
+ static int quotatab_fsio_write(pr_fh_t *fh, int fd, const char *buf,
+ size_t bufsz) {
+ int res;
+@@ -3214,6 +3281,7 @@ MODRET quotatab_post_pass(cmd_rec *cmd) {
+ * For Issue #1764, this is not a problem due to the fact that
+ * mod_vroot does not override the system FS write callbacks.
+ */
++ fs->pwrite = quotatab_fsio_pwrite;
+ fs->write = quotatab_fsio_write;
+
+ } else {
+diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/quotatab.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/quotatab.pm
+new file mode 100644
+index 0000000000..d3a9a4e93b
+--- /dev/null
++++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp/quotatab.pm
+@@ -0,0 +1,271 @@
++package ProFTPD::Tests::Modules::mod_sftp::quotatab;
++
++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 POSIX qw(:fcntl_h);
++
++use ProFTPD::TestSuite::FTP;
++use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
++
++$| = 1;
++
++my $order = 0;
++
++my $TESTS = {
++ sftp_quotatab_upload_hard_limit_issue2098 => {
++ order => ++$order,
++ test_class => [qw(bug forking mod_quotatab_sql mod_sql_sqlite)],
++ },
++
++};
++
++sub new {
++ return shift()->SUPER::new(@_);
++}
++
++sub list_tests {
++ return testsuite_get_runnable_tests($TESTS);
++}
++
++sub set_up {
++ my $self = shift;
++ $self->SUPER::set_up(@_);
++
++ # Make sure that mod_sftp does not complain about permissions on the hostkey
++ # files.
++
++ my $rsa_host_key = File::Spec->rel2abs('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');
++
++ unless (chmod(0400, $rsa_host_key, $dsa_host_key)) {
++ die("Can't set perms on $rsa_host_key, $dsa_host_key: $!");
++ }
++}
++
++sub sftp_quotatab_upload_hard_limit_issue2098 {
++ my $self = shift;
++ my $tmpdir = $self->{tmpdir};
++ my $setup = test_setup($tmpdir, 'quotatab');
++
++ 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 $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
++
++ my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
++
++ if (open(my $fh, "> $db_script")) {
++ print $fh <<EOS;
++CREATE TABLE quotalimits (
++ name TEXT NOT NULL PRIMARY KEY,
++ quota_type TEXT NOT NULL,
++ per_session TEXT NOT NULL,
++ limit_type TEXT NOT NULL,
++ bytes_in_avail REAL NOT NULL,
++ bytes_out_avail REAL NOT NULL,
++ bytes_xfer_avail REAL NOT NULL,
++ files_in_avail INTEGER NOT NULL,
++ files_out_avail INTEGER NOT NULL,
++ files_xfer_avail INTEGER NOT NULL
++);
++INSERT INTO quotalimits (name, quota_type, per_session, limit_type, bytes_in_avail, bytes_out_avail, bytes_xfer_avail, files_in_avail, files_out_avail, files_xfer_avail) VALUES ('$setup->{user}', 'user', 'false', 'hard', 32, 0, 0, 2, 0, 0);
++
++CREATE TABLE quotatallies (
++ name TEXT NOT NULL PRIMARY KEY,
++ quota_type TEXT NOT NULL,
++ bytes_in_used REAL NOT NULL,
++ bytes_out_used REAL NOT NULL,
++ bytes_xfer_used REAL NOT NULL,
++ files_in_used INTEGER NOT NULL,
++ files_out_used INTEGER NOT NULL,
++ files_xfer_used INTEGER NOT NULL
++);
++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";
++ }
++
++ # 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 => $setup->{pid_file},
++ ScoreboardFile => $setup->{scoreboard_file},
++ SystemLog => $setup->{log_file},
++ TraceLog => $setup->{log_file},
++ Trace => 'fsio:20 quotatab:20 sql:20 ssh2:20 sftp:30',
++
++ AuthUserFile => $setup->{auth_user_file},
++ AuthGroupFile => $setup->{auth_group_file},
++ AuthOrder => 'mod_auth_file.c',
++
++ IfModules => {
++ 'mod_delay.c' => {
++ DelayEngine => 'off',
++ },
++
++ 'mod_quotatab_sql.c' => [
++ 'SQLNamedQuery get-quota-limit SELECT "name, quota_type, per_session, limit_type, bytes_in_avail, bytes_out_avail, bytes_xfer_avail, files_in_avail, files_out_avail, files_xfer_avail FROM quotalimits WHERE name = \'%{0}\' AND quota_type = \'%{1}\'"',
++ 'SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used, files_in_used, files_out_used, files_xfer_used FROM quotatallies WHERE name = \'%{0}\' AND quota_type = \'%{1}\'"',
++ 'SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used = files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name = \'%{6}\' AND quota_type = \'%{7}\'" quotatallies',
++ 'SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4}, %{5}, %{6}, %{7}" quotatallies',
++
++ 'QuotaEngine on',
++ "QuotaLog $setup->{log_file}",
++ 'QuotaLimitTable sql:/get-quota-limit',
++ 'QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally',
++ ],
++
++ 'mod_sftp.c' => [
++ "SFTPEngine on",
++ "SFTPLog $setup->{log_file}",
++ "SFTPHostKey $rsa_host_key",
++ "SFTPHostKey $dsa_host_key",
++ ],
++
++ 'mod_sql.c' => {
++ SQLEngine => 'log',
++ SQLBackend => 'sqlite3',
++ SQLConnectInfo => $db_file,
++ SQLLogFile => $setup->{log_file},
++ },
++ },
++ };
++
++ my ($port, $config_user, $config_group) = config_write($setup->{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;
++
++ # 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($setup->{user}, $setup->{passwd})) {
++ my ($err_code, $err_name, $err_str) = $ssh2->error();
++ die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
++ }
++
++ my $sftp = $ssh2->sftp();
++ unless ($sftp) {
++ my ($err_code, $err_name, $err_str) = $ssh2->error();
++ die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
++ }
++
++ my $fh = $sftp->open('test.dat', O_WRONLY|O_CREAT|O_TRUNC, 0644);
++ unless ($fh) {
++ my ($err_code, $err_name) = $sftp->error();
++ die("Can't open test.txt: [$err_name] ($err_code)");
++ }
++
++ my $count = 20;
++ for (my $i = 0; $i < $count; $i++) {
++ print $fh "ABCD" x 8192;
++ }
++
++ # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle
++ $fh = undef;
++
++ $sftp = undef;
++ $ssh2->disconnect();
++ };
++ if ($@) {
++ $ex = $@;
++ }
++
++ $wfh->print("done\n");
++ $wfh->flush();
++
++ } else {
++ eval { server_wait($setup->{config_file}, $rfh) };
++ if ($@) {
++ warn($@);
++ exit 1;
++ }
++
++ exit 0;
++ }
++
++ # Stop server
++ server_stop($setup->{pid_file});
++ $self->assert_child_ok($pid);
++
++ eval {
++ if (open(my $fh, "< $setup->{log_file}")) {
++ my $ok = 0;
++
++ while (my $line = <$fh>) {
++ chomp($line);
++
++ if ($ENV{TEST_VERBOSE}) {
++ print STDERR "# $line\n";
++ }
++
++ if ($line =~ /limit exceeded/) {
++ $ok = 1;
++ last;
++ }
++ }
++
++ close($fh);
++ $self->assert($ok, test_msg("Did not see expected QuotaLog message"));
++
++ } else {
++ die("Can't read $setup->{log_file}: $!");
++ }
++ };
++ if ($@) {
++ $ex = $@;
++ }
++
++ test_cleanup($setup->{log_file}, $ex);
++}
++
++1;
+diff --git a/tests/t/modules/mod_sftp/quotatab.t b/tests/t/modules/mod_sftp/quotatab.t
+new file mode 100644
+index 0000000000..ddcf1ccf42
+--- /dev/null
++++ b/tests/t/modules/mod_sftp/quotatab.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::Modules::mod_sftp::quotatab");
=====================================
debian/patches/series
=====================================
@@ -15,3 +15,5 @@ odbc
04d89957d8ace325ef76fdfab22049df16a40c0b.diff
7e076e844ab5da63a0887b875aca2c3cfbc83a49.diff
1a5ce6467756e92f42f89c53f0f370dc0f0206d7.diff
+508eb4d1b8c550b5f3059ad5b33d60c6b6c76bc7.diff
+d4ff72c0c32db68ef2cecfe08c53ac42a7bf85dd.diff
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/e915933db4886bdc704b3bb6682bcabcab0b06cf
--
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/e915933db4886bdc704b3bb6682bcabcab0b06cf
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
More information about the Pkg-proftpd-maintainers
mailing list