[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