[Git][debian-proftpd-team/proftpd][bullseye] Add changes for 1.3.7a+dfsg-12+deb11u3.

Hilmar Preuße (@hilmar) gitlab at salsa.debian.org
Sat Nov 30 22:49:24 GMT 2024



Hilmar Preuße pushed to branch bullseye at Debian ProFTPD Team / proftpd


Commits:
93016330 by Hilmar Preuße at 2024-11-30T23:48:19+01:00
Add changes for 1.3.7a+dfsg-12+deb11u3.

- - - - -


6 changed files:

- + debian/.gitlab-ci.yml
- debian/changelog
- + debian/patches/0021-PATCH-Issue-1830-When-no-supplemental-groups-are-pro.patch
- + debian/patches/97bbe68363ccf2de0c07f67170ec64a8b4d62592.diff
- + debian/patches/bcec15efe6c53dac40420731013f1cd2fd54123b.diff
- debian/patches/series


Changes:

=====================================
debian/.gitlab-ci.yml
=====================================
@@ -0,0 +1,8 @@
+include:
+  - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml
+
+variables:
+    RELEASE: 'bullseye'
+    SALSA_CI_COMPONENTS: 'main contrib non-free'
+    SALSA_CI_DISABLE_REPROTEST: 1
+    SALSA_CI_DISABLE_LINTIAN: 1


=====================================
debian/changelog
=====================================
@@ -1,3 +1,16 @@
+proftpd-dfsg (1.3.7a+dfsg-12+deb11u3) bullseye-security; urgency=medium
+
+   * LTS Team upload
+   * Fix CVE-2023-48795: Add patch for Terrapin attack.
+   * Fix CVE-2023-51713: make_ftp_cmd in main.c in ProFTPD has a one-byte
+     out-of-bounds read, and daemon crash, because of mishandling of
+     quote/backslash semantics.
+   * Fix CVE-2024-48651: Supplemental Group Inheritance Grants
+     Unintended Access to GID 0
+     (Closes: #1082326)
+
+ -- Bastien Roucariès <rouca at debian.org>  Mon, 25 Nov 2024 21:20:02 +0000
+
 proftpd-dfsg (1.3.7a+dfsg-12+deb11u2) bullseye; urgency=medium
 
   * Add patch for upstream issue #1149 (Closes: #993784).


=====================================
debian/patches/0021-PATCH-Issue-1830-When-no-supplemental-groups-are-pro.patch
=====================================
@@ -0,0 +1,337 @@
+From: TJ Saunders <tj at castaglia.org>
+Date: Wed, 13 Nov 2024 06:33:35 -0800
+Subject: [PATCH] Issue #1830: When no supplemental groups are provided by the
+  underlying authentication providers,
+ fall back to using the primary  group/GID. (#1835)
+
+This prevents surprise due to inheritance of the parent processes' supplemental group membership, which might inadvertently provided undesired access.
+
+origin: backport, https://github.com/proftpd/proftpd/commit/5031d498a71c493b9659e2b5ccafde58b0897e30
+bug: https://github.com/proftpd/proftpd/issues/1830#issuecomment-2501942173
+bug-cve: https://www.cve.org/CVERecord?id=CVE-2024-48651
+---
+ contrib/mod_sftp/auth.c                            |  14 +-
+ modules/mod_auth.c                                 |  21 ++-
+ src/auth.c                                         |  16 +-
+ .../t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm  | 174 +++++++++++++++++++++
+ 4 files changed, 211 insertions(+), 14 deletions(-)
+
+diff --git a/contrib/mod_sftp/auth.c b/contrib/mod_sftp/auth.c
+index 321b267..969b049 100644
+--- a/contrib/mod_sftp/auth.c
++++ b/contrib/mod_sftp/auth.c
+@@ -382,8 +382,20 @@ static int setup_env(pool *p, char *user) {
+       session.groups == NULL) {
+     res = pr_auth_getgroups(p, pw->pw_name, &session.gids, &session.groups);
+     if (res < 1) {
++      /* If no supplemental groups are provided, default to using the process
++       * primary GID as the supplemental group.  This prevents access
++       * regressions as seen in Issue #1830.
++       */
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+-        "no supplemental groups found for user '%s'", pw->pw_name);
++        "no supplemental groups found for user '%s', "
++        "using primary group %s (GID %lu)", pw->pw_name, session.group,
++        (unsigned long) session.login_gid);
++
++      session.gids = make_array(p, 2, sizeof(gid_t));
++      session.groups = make_array(p, 2, sizeof(char *));
++
++      *((gid_t *) push_array(session.gids)) = session.login_gid;
++      *((char **) push_array(session.groups)) = pstrdup(p, session.group);
+     }
+   }
+ 
+diff --git a/modules/mod_auth.c b/modules/mod_auth.c
+index 6bfe642..d8a5106 100644
+--- a/modules/mod_auth.c
++++ b/modules/mod_auth.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-2020 The ProFTPD Project team
++ * Copyright (c) 2001-2024 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
+@@ -1096,8 +1096,8 @@ static int setup_env(pool *p, cmd_rec *cmd, const char *user, char *pass) {
+     session.groups = NULL;
+   }
+ 
+-  if (!session.gids &&
+-      !session.groups) {
++  if (session.gids == NULL &&
++      session.groups == NULL) {
+     /* Get the supplemental groups.  Note that we only look up the
+      * supplemental group credentials if we have not cached the group
+      * credentials before, in session.gids and session.groups.  
+@@ -1107,8 +1107,19 @@ static int setup_env(pool *p, cmd_rec *cmd, const char *user, char *pass) {
+      */
+      res = pr_auth_getgroups(p, pw->pw_name, &session.gids, &session.groups);
+      if (res < 1) {
+-       pr_log_debug(DEBUG5, "no supplemental groups found for user '%s'",
+-         pw->pw_name);
++       /* If no supplemental groups are provided, default to using the process
++        * primary GID as the supplemental group.  This prevents access
++        * regressions as seen in Issue #1830.
++        */
++       pr_log_debug(DEBUG5, "no supplemental groups found for user '%s', "
++         "using primary group %s (GID %lu)", pw->pw_name, session.group,
++         (unsigned long) session.login_gid);
++
++       session.gids = make_array(p, 2, sizeof(gid_t));
++       session.groups = make_array(p, 2, sizeof(char *));
++
++       *((gid_t *) push_array(session.gids)) = session.login_gid;
++       *((char **) push_array(session.groups)) = pstrdup(p, session.group);
+      }
+   }
+ 
+diff --git a/src/auth.c b/src/auth.c
+index 494a479..7b610d8 100644
+--- a/src/auth.c
++++ b/src/auth.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-2020 The ProFTPD Project team
++ * Copyright (c) 2001-2024 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
+@@ -1512,12 +1512,12 @@ int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
+   }
+ 
+   /* Allocate memory for the array_headers of GIDs and group names. */
+-  if (group_ids) {
+-    *group_ids = make_array(permanent_pool, 2, sizeof(gid_t));
++  if (group_ids != NULL) {
++    *group_ids = make_array(p, 2, sizeof(gid_t));
+   }
+ 
+-  if (group_names) {
+-    *group_names = make_array(permanent_pool, 2, sizeof(char *));
++  if (group_names != NULL) {
++    *group_names = make_array(p, 2, sizeof(char *));
+   }
+ 
+   cmd = make_cmd(p, 3, name, group_ids ? *group_ids : NULL,
+@@ -1536,7 +1536,7 @@ int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
+      * for the benefit of auth_getgroup() implementors.
+      */
+ 
+-    if (group_ids) {
++    if (group_ids != NULL) {
+       register unsigned int i;
+       char *strgids = "";
+       gid_t *gids = (*group_ids)->elts;
+@@ -1552,7 +1552,7 @@ int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
+         *strgids ? strgids : "(None; corrupted group file?)");
+     }
+ 
+-    if (group_names) {
++    if (group_names != NULL) {
+       register unsigned int i;
+       char *strgroups = ""; 
+       char **groups = (*group_names)->elts;
+@@ -1568,7 +1568,7 @@ int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
+     }
+   }
+ 
+-  if (cmd->tmp_pool) {
++  if (cmd->tmp_pool != NULL) {
+     destroy_pool(cmd->tmp_pool);
+     cmd->tmp_pool = NULL;
+   }
+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 f56ac4a..b82da7c 100644
+--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm
++++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_sql_sqlite.pm
+@@ -457,6 +457,11 @@ my $TESTS = {
+     order => ++$order,
+     test_class => [qw(forking bug mod_tls)],
+   },
++
++  sql_user_info_no_suppl_groups_issue1830 => {
++    order => ++$order,
++    test_class => [qw(forking bug rootprivs)],
++  },
+ };
+ 
+ sub new {
+@@ -15393,4 +15398,173 @@ EOC
+   test_cleanup($setup->{log_file}, $ex);
+ }
+ 
++sub sql_user_info_no_suppl_groups_issue1830 {
++  my $self = shift;
++  my $tmpdir = $self->{tmpdir};
++  my $setup = test_setup($tmpdir, 'sqlite');
++
++  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 ('$setup->{user}', '$setup->{passwd}', $setup->{uid}, $setup->{gid}, '$setup->{home_dir}', '/bin/bash');
++
++CREATE TABLE groups (
++  groupname TEXT,
++  gid INTEGER,
++  members TEXT
++);
++INSERT INTO groups (groupname, gid, members) VALUES ('$setup->{group}', $setup->{gid}, '$setup->{user}');
++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 => $setup->{pid_file},
++    ScoreboardFile => $setup->{scoreboard_file},
++    SystemLog => $setup->{log_file},
++    TraceLog => $setup->{log_file},
++    Trace => 'auth:20 sql:20',
++
++    # Required for logging the expected message
++    DebugLevel => 5,
++
++    IfModules => {
++      'mod_delay.c' => {
++        DelayEngine => 'off',
++      },
++
++      'mod_sql.c' => {
++        AuthOrder => 'mod_sql.c',
++
++        SQLAuthenticate => 'users',
++        SQLAuthTypes => 'plaintext',
++        SQLBackend => 'sqlite3',
++        SQLConnectInfo => $db_file,
++        SQLLogFile => $setup->{log_file},
++
++        # Set these, so that our lower UID/GID will be used
++        SQLMinUserUID => 100,
++        SQLMinUserGID => 100,
++      },
++    },
++  };
++
++  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: $!");
++  }
++
++  my $ex;
++
++  # Fork child
++  $self->handle_sigchld();
++  defined(my $pid = fork()) or die("Can't fork: $!");
++  if ($pid) {
++    eval {
++      sleep(2);
++
++      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
++      $client->login($setup->{user}, $setup->{passwd});
++
++      my $resp_msgs = $client->response_msgs();
++      my $nmsgs = scalar(@$resp_msgs);
++
++      my $expected = 1;
++      $self->assert($expected == $nmsgs,
++        test_msg("Expected $expected, got $nmsgs"));
++
++      $expected = "User $setup->{user} logged in";
++      $self->assert($expected eq $resp_msgs->[0],
++        test_msg("Expected response '$expected', got '$resp_msgs->[0]'"));
++
++      $client->quit();
++    };
++    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 =~ /no supplemental groups found for user '$setup->{user}', using primary group/) {
++          $ok = 1;
++          last;
++        }
++      }
++
++      close($fh);
++
++      $self->assert($ok, test_msg("Did not see expected log message"));
++
++    } else {
++      die("Can't read $setup->{log_file}: $!");
++    }
++  };
++  if ($@) {
++    $ex = $@ unless $ex;
++  }
++
++  test_cleanup($setup->{log_file}, $ex);
++}
++
+ 1;


=====================================
debian/patches/97bbe68363ccf2de0c07f67170ec64a8b4d62592.diff
=====================================
@@ -0,0 +1,270 @@
+From 97bbe68363ccf2de0c07f67170ec64a8b4d62592 Mon Sep 17 00:00:00 2001
+From: TJ Saunders <tj at castaglia.org>
+Date: Sun, 6 Aug 2023 13:16:26 -0700
+Subject: [PATCH] Issue #1683: Avoid an edge case when handling unexpectedly
+ formatted input text from client, caused by quote/backslash semantics, by
+ skipping those semantics.
+
+---
+ include/str.h   |  3 ++-
+ src/main.c      | 34 ++++++++++++++++++++++++++++++----
+ src/str.c       | 22 +++++++++++++---------
+ tests/api/str.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++-
+ 4 files changed, 93 insertions(+), 15 deletions(-)
+
+Index: proftpd-dfsg/include/str.h
+===================================================================
+--- proftpd-dfsg.orig/include/str.h
++++ proftpd-dfsg/include/str.h
+@@ -1,6 +1,6 @@
+ /*
+  * ProFTPD - FTP server daemon
+- * Copyright (c) 2008-2020 The ProFTPD Project team
++ * Copyright (c) 2008-2023 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
+@@ -131,6 +131,7 @@ const char *pr_gid2str(pool *, gid_t);
+ #define PR_STR_FL_PRESERVE_COMMENTS		0x0001
+ #define PR_STR_FL_PRESERVE_WHITESPACE		0x0002
+ #define PR_STR_FL_IGNORE_CASE			0x0004
++#define PR_STR_FL_IGNORE_QUOTES			0x0008
+ 
+ char *pr_str_get_token(char **, char *);
+ char *pr_str_get_token2(char **, char *, size_t *);
+Index: proftpd-dfsg/src/main.c
+===================================================================
+--- proftpd-dfsg.orig/src/main.c
++++ proftpd-dfsg/src/main.c
+@@ -811,8 +811,24 @@ static cmd_rec *make_ftp_cmd(pool *p, ch
+     return NULL;
+   }
+ 
++  /* By default, pr_str_get_word will handle quotes and backslashes for
++   * escaping characters.  This can produce words which are shorter, use
++   * fewer bytes than the corresponding input buffer.
++   *
++   * In this particular situation, we use the length of this initial word
++   * for determining the length of the remaining buffer bytes, assumed to
++   * contain the FTP command arguments.  If this initial word is thus
++   * unexpectedly "shorter", due to nonconformant FTP text, it can lead
++   * the subsequent buffer scan, looking for CRNUL sequencees, to access
++   * unexpected memory addresses (Issue #1683).
++   *
++   * Thus for this particular situation, we tell the function to ignore/skip
++   * such quote/backslash semantics, and treat them as any other character
++   * using the IGNORE_QUOTES flag.
++   */
++
+   ptr = buf;
+-  wrd = pr_str_get_word(&ptr, str_flags);
++  wrd = pr_str_get_word(&ptr, str_flags|PR_STR_FL_IGNORE_QUOTES);
+   if (wrd == NULL) {
+     /* Nothing there...bail out. */
+     pr_trace_msg("ctrl", 5, "command '%s' is empty, ignoring", buf);
+@@ -820,6 +836,11 @@ static cmd_rec *make_ftp_cmd(pool *p, ch
+     return NULL;
+   }
+ 
++  /* Note that this first word is the FTP command.  This is why we make
++   * use of the ptr buffer, which advances through the input buffer as
++   * we read words from the buffer.
++   */
++
+   subpool = make_sub_pool(p);
+   pr_pool_tag(subpool, "make_ftp_cmd pool");
+   cmd = pcalloc(subpool, sizeof(cmd_rec));
+@@ -846,6 +867,7 @@ static cmd_rec *make_ftp_cmd(pool *p, ch
+   arg_len = buflen - strlen(wrd);
+   arg = pcalloc(cmd->pool, arg_len + 1);
+ 
++  /* Remember that ptr here is advanced past the first word. */
+   for (i = 0, j = 0; i < arg_len; i++) {
+     pr_signals_handle();
+     if (i > 1 &&
+@@ -854,14 +876,13 @@ static cmd_rec *make_ftp_cmd(pool *p, ch
+ 
+       /* Strip out the NUL by simply not copying it into the new buffer. */
+       have_crnul = TRUE;
++
+     } else {
+       arg[j++] = ptr[i];
+     }
+   }
+ 
+-  cmd->arg = arg;
+-
+-  if (have_crnul) {
++  if (have_crnul == TRUE) {
+     char *dup_arg;
+ 
+     /* Now make a copy of the stripped argument; this is what we need to
+@@ -871,6 +892,11 @@ static cmd_rec *make_ftp_cmd(pool *p, ch
+     ptr = dup_arg;
+   }
+ 
++  cmd->arg = arg;
++
++  /* Now we can read the remamining words, as command arguments, from the
++   * input buffer.
++   */
+   while ((wrd = pr_str_get_word(&ptr, str_flags)) != NULL) {
+     pr_signals_handle();
+     *((char **) push_array(tarr)) = pstrdup(cmd->pool, wrd);
+Index: proftpd-dfsg/src/str.c
+===================================================================
+--- proftpd-dfsg.orig/src/str.c
++++ proftpd-dfsg/src/str.c
+@@ -1,6 +1,6 @@
+ /*
+  * ProFTPD - FTP server daemon
+- * Copyright (c) 2008-2017 The ProFTPD Project team
++ * Copyright (c) 2008-2023 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
+@@ -1209,7 +1209,7 @@ int pr_str_get_nbytes(const char *str, c
+ 
+ char *pr_str_get_word(char **cp, int flags) {
+   char *res, *dst;
+-  char quote_mode = 0;
++  int quote_mode = FALSE;
+ 
+   if (cp == NULL ||
+      !*cp ||
+@@ -1238,24 +1238,28 @@ char *pr_str_get_word(char **cp, int fla
+     }
+   }
+ 
+-  if (**cp == '\"') {
+-    quote_mode++;
+-    (*cp)++;
++  if (!(flags & PR_STR_FL_IGNORE_QUOTES)) {
++    if (**cp == '\"') {
++      quote_mode = TRUE;
++      (*cp)++;
++    }
+   }
+ 
+   while (**cp && (quote_mode ? (**cp != '\"') : !PR_ISSPACE(**cp))) {
+     pr_signals_handle();
+ 
+-    if (**cp == '\\' && quote_mode) {
+-
++    if (**cp == '\\' &&
++        quote_mode == TRUE) {
+       /* Escaped char */
+       if (*((*cp)+1)) {
+-        *dst = *(++(*cp));
++        *dst++ = *(++(*cp));
++        (*cp)++;
++        continue;
+       }
+     }
+ 
+     *dst++ = **cp;
+-    ++(*cp);
++    (*cp)++;
+   }
+ 
+   if (**cp) {
+Index: proftpd-dfsg/tests/api/str.c
+===================================================================
+--- proftpd-dfsg.orig/tests/api/str.c
++++ proftpd-dfsg/tests/api/str.c
+@@ -1,6 +1,6 @@
+ /*
+  * ProFTPD - FTP server testsuite
+- * Copyright (c) 2008-2017 The ProFTPD Project team
++ * Copyright (c) 2008-2023 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
+@@ -695,19 +695,23 @@ END_TEST
+ START_TEST (get_word_test) {
+   char *ok, *res, *str;
+ 
++  mark_point();
+   res = pr_str_get_word(NULL, 0);
+   fail_unless(res == NULL, "Failed to handle null arguments");
+   fail_unless(errno == EINVAL, "Failed to set errno to EINVAL");
+ 
++  mark_point();
+   str = NULL;
+   res = pr_str_get_word(&str, 0);
+   fail_unless(res == NULL, "Failed to handle null str argument");
+   fail_unless(errno == EINVAL, "Failed to set errno to EINVAL");
+ 
++  mark_point();
+   str = pstrdup(p, "  ");
+   res = pr_str_get_word(&str, 0);
+   fail_unless(res == NULL, "Failed to handle whitespace argument");
+ 
++  mark_point();
+   str = pstrdup(p, " foo");
+   res = pr_str_get_word(&str, PR_STR_FL_PRESERVE_WHITESPACE);
+   fail_unless(res != NULL, "Failed to handle whitespace argument: %s",
+@@ -723,6 +727,7 @@ START_TEST (get_word_test) {
+   ok = "foo";
+   fail_unless(strcmp(res, ok) == 0, "Expected '%s', got '%s'", ok, res);
+ 
++  mark_point();
+   str = pstrdup(p, "  # foo");
+   res = pr_str_get_word(&str, 0);
+   fail_unless(res == NULL, "Failed to handle commented argument");
+@@ -742,6 +747,8 @@ START_TEST (get_word_test) {
+   fail_unless(strcmp(res, ok) == 0, "Expected '%s', got '%s'", ok, res);
+ 
+   /* Test multiple embedded quotes. */
++
++  mark_point();
+   str = pstrdup(p, "foo \"bar baz\" qux \"quz norf\"");
+   res = pr_str_get_word(&str, 0);
+   fail_unless(res != NULL, "Failed to handle quoted argument: %s",
+@@ -770,6 +777,46 @@ START_TEST (get_word_test) {
+ 
+   ok = "quz norf";
+   fail_unless(strcmp(res, ok) == 0, "Expected '%s', got '%s'", ok, res);
++
++  /* Test embedded quotes with backslashes (Issue #1683). */
++  mark_point();
++
++  str = pstrdup(p, "\"\\\\SYST\"");
++  res = pr_str_get_word(&str, 0);
++  fail_unless(res != NULL, "Failed to handle quoted argument: %s",
++    strerror(errno));
++
++  ok = "\\SYST";
++  fail_unless(strcmp(res, ok) == 0, "Expected '%s', got '%s'", ok, res);
++
++  mark_point();
++  str = pstrdup(p, "\"\"\\\\SYST");
++  res = pr_str_get_word(&str, 0);
++  fail_unless(res != NULL, "Failed to handle quoted argument: %s",
++    strerror(errno));
++
++  /* Note that pr_str_get_word() is intended to be called multiple times
++   * on an advancing buffer, effectively tokenizing the buffer.  This is
++   * why the function does NOT decrement its quote mode.
++   */
++  ok = "";
++  fail_unless(strcmp(res, ok) == 0, "Expected '%s', got '%s'", ok, res);
++
++  /* Now do the same tests with the IGNORE_QUOTES flag */
++  mark_point();
++
++  str = ok = pstrdup(p, "\"\\\\SYST\"");
++  res = pr_str_get_word(&str, PR_STR_FL_IGNORE_QUOTES);
++  fail_unless(res != NULL, "Failed to handle quoted argument: %s",
++    strerror(errno));
++  fail_unless(strcmp(res, ok) == 0, "Expected '%s', got '%s'", ok, res);
++
++  mark_point();
++  str = ok = pstrdup(p, "\"\"\\\\SYST");
++  res = pr_str_get_word(&str, PR_STR_FL_IGNORE_QUOTES);
++  fail_unless(res != NULL, "Failed to handle quoted argument: %s",
++    strerror(errno));
++  fail_unless(strcmp(res, ok) == 0, "Expected '%s', got '%s'", ok, res);
+ }
+ END_TEST
+ 


=====================================
debian/patches/bcec15efe6c53dac40420731013f1cd2fd54123b.diff
=====================================
@@ -0,0 +1,727 @@
+Index: proftpd-dfsg/contrib/mod_sftp/kex.c
+===================================================================
+--- proftpd-dfsg.orig/contrib/mod_sftp/kex.c
++++ proftpd-dfsg/contrib/mod_sftp/kex.c
+@@ -149,6 +149,13 @@ static struct sftp_kex *kex_first_kex =
+ static struct sftp_kex *kex_rekey_kex = NULL;
+ static int kex_sent_kexinit = FALSE;
+ 
++/* Using strict kex?  Note that we maintain this value here, rather than
++ * in the sftp_kex struct, so that any "use strict KEX" flag set via the
++ * first KEXINIT is used through any subsequent KEXINITs.
++ */
++static int use_strict_kex = FALSE;
++static int kex_done_first_kex = FALSE;
++
+ /* Diffie-Hellman group moduli */
+ 
+ static const char *dh_group1_str =
+@@ -1593,6 +1600,16 @@ static const char *get_kexinit_exchange_
+     res = pstrcat(p, res, *res ? "," : "", pstrdup(p, "ext-info-s"), NULL);
+   }
+ 
++  if (!(sftp_opts & SFTP_OPT_NO_STRICT_KEX)) {
++    /* Indicate support for OpenSSH's custom "strict KEX" mode extension,
++     * but only if we have not done/completed our first KEX.
++     */
++    if (kex_done_first_kex == FALSE) {
++      res = pstrcat(p, res, *res ? "," : "",
++        pstrdup(p, "kex-strict-s-v00 at openssh.com"), NULL);
++    }
++  }
++
+   return res;
+ }
+ 
+@@ -2174,6 +2191,21 @@ static int get_session_names(struct sftp
+     pr_trace_msg(trace_channel, 20, "client %s EXT_INFO support",
+       kex->use_ext_info ? "signaled" : "did not signal" );
+ 
++    if (!(sftp_opts & SFTP_OPT_NO_STRICT_KEX)) {
++      /* Did the client indicate "strict kex" support (Issue 1760)?
++       *
++       * Note that we only check for this if it is our first KEXINIT.
++       * The "strict kex" extension is ignored in any subsequent KEXINITs, as
++       * for rekeys.
++       */
++      if (kex_done_first_kex == FALSE) {
++        use_strict_kex = sftp_misc_namelist_contains(kex->pool,
++          client_list, "kex-strict-c-v00 at openssh.com");
++        pr_trace_msg(trace_channel, 20, "client %s strict KEX support",
++          use_strict_kex ? "signaled" : "did not signal" );
++      }
++    }
++
+   } else {
+     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+       "no shared key exchange algorithm found (client sent '%s', server sent "
+@@ -4406,7 +4438,6 @@ static int handle_kex_ecdh(struct ssh2_p
+   destroy_pool(pkt->pool);
+   return 0;
+ }
+-
+ #endif /* PR_USE_OPENSSL_ECC */
+ 
+ static struct ssh2_packet *read_kex_packet(pool *p, struct sftp_kex *kex,
+@@ -4457,6 +4488,10 @@ static struct ssh2_packet *read_kex_pack
+     /* Per RFC 4253, Section 11, DEBUG, DISCONNECT, IGNORE, and UNIMPLEMENTED
+      * messages can occur at any time, even during KEX.  We have to be prepared
+      * for this, and Do The Right Thing(tm).
++     *
++     * However, due to the Terrapin attack, if we are using a "strict KEX"
++     * mode, then only DISCONNECT messages can occur during KEX; DEBUG,
++     * IGNORE, and UNIMPLEMENTED messages will lead to disconnecting.
+      */
+ 
+     mesg_type = sftp_ssh2_packet_get_mesg_type(pkt);
+@@ -4485,35 +4520,43 @@ static struct ssh2_packet *read_kex_pack
+     }
+ 
+     switch (mesg_type) {
+-      case SFTP_SSH2_MSG_DEBUG:
+-        sftp_ssh2_packet_handle_debug(pkt);
+-        pr_response_set_pool(NULL);
+-        pkt = NULL;
+-        break;
+-
++      /* DISCONNECT messages are always allowed. */
+       case SFTP_SSH2_MSG_DISCONNECT:
+         sftp_ssh2_packet_handle_disconnect(pkt);
+         pr_response_set_pool(NULL);
+         pkt = NULL;
+         break;
+ 
++      case SFTP_SSH2_MSG_DEBUG:
++        if (use_strict_kex == FALSE) {
++          sftp_ssh2_packet_handle_debug(pkt);
++          pr_response_set_pool(NULL);
++          pkt = NULL;
++          break;
++        }
++
+       case SFTP_SSH2_MSG_IGNORE:
+-        sftp_ssh2_packet_handle_ignore(pkt);
+-        pr_response_set_pool(NULL);
+-        pkt = NULL;
+-        break;
++        if (use_strict_kex == FALSE) {
++          sftp_ssh2_packet_handle_ignore(pkt);
++          pr_response_set_pool(NULL);
++          pkt = NULL;
++          break;
++        }
+ 
+       case SFTP_SSH2_MSG_UNIMPLEMENTED:
+-        sftp_ssh2_packet_handle_unimplemented(pkt);
+-        pr_response_set_pool(NULL);
+-        pkt = NULL;
+-        break;
++        if (use_strict_kex == FALSE) {
++          sftp_ssh2_packet_handle_unimplemented(pkt);
++          pr_response_set_pool(NULL);
++          pkt = NULL;
++          break;
++        }
+ 
+       default:
+         /* For any other message type, it's considered a protocol error. */
+         (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+-          "received %s (%d) unexpectedly, disconnecting",
+-          sftp_ssh2_packet_get_mesg_type_desc(mesg_type), mesg_type);
++          "received %s (%d) unexpectedly%s, disconnecting",
++          sftp_ssh2_packet_get_mesg_type_desc(mesg_type), mesg_type,
++          use_strict_kex ? " during strict KEX" : "");
+         pr_response_set_pool(NULL);
+         destroy_kex(kex);
+         destroy_pool(pkt->pool);
+@@ -4534,7 +4577,7 @@ int sftp_kex_handle(struct ssh2_packet *
+    * initial connect (kex_first_kex not null), or because we
+    * are in a server-initiated rekeying (kex_rekey_kex not null).
+    */
+-  if (kex_first_kex) {
++  if (kex_first_kex != NULL) {
+     kex = kex_first_kex;
+ 
+     /* We need to assign the client/server versions, which this struct
+@@ -4543,7 +4586,7 @@ int sftp_kex_handle(struct ssh2_packet *
+     kex->client_version = kex_client_version;
+     kex->server_version = kex_server_version;
+ 
+-  } else if (kex_rekey_kex) {
++  } else if (kex_rekey_kex != NULL) {
+     kex = kex_rekey_kex;
+ 
+   } else {
+@@ -4579,6 +4622,24 @@ int sftp_kex_handle(struct ssh2_packet *
+     return -1;
+   }
+ 
++  if (use_strict_kex == TRUE &&
++      kex_done_first_kex == FALSE) {
++    uint32_t client_seqno;
++
++    client_seqno = sftp_ssh2_packet_get_client_seqno();
++    if (client_seqno != 1) {
++      /* Receiving any messages other than a KEXINIT as the first client
++       * message indicates the possibility of the Terrapin attack being
++       * conducted (Issue 1760).  Thus we disconnect the client in such
++       * cases.
++       */
++      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
++        "'strict KEX' violation, as KEXINIT was not the first message; disconnecting");
++      destroy_kex(kex);
++      SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
++    }
++  }
++
+   /* Once we have received the client KEXINIT message, we can compare what we
+    * want to send against what we already received from the client.
+    *
+@@ -4637,7 +4698,7 @@ int sftp_kex_handle(struct ssh2_packet *
+ 
+       destroy_pool(pkt->pool);
+ 
+-      if (!kex_sent_kexinit) {
++      if (kex_sent_kexinit == FALSE) {
+         pkt = sftp_ssh2_packet_create(kex_pool);
+         res = write_kexinit(pkt, kex);
+         if (res < 0) {
+@@ -4660,7 +4721,7 @@ int sftp_kex_handle(struct ssh2_packet *
+       }
+     }
+ 
+-    if (!kex_sent_kexinit) {
++    if (kex_sent_kexinit == FALSE) {
+       pkt = sftp_ssh2_packet_create(kex_pool);
+       res = write_kexinit(pkt, kex);
+       if (res < 0) {
+@@ -4785,7 +4846,7 @@ int sftp_kex_handle(struct ssh2_packet *
+     NULL, 1, SFTP_SSH2_MSG_NEWKEYS);
+ 
+   /* If we didn't send our NEWKEYS message earlier, do it now. */
+-  if (!sent_newkeys) {
++  if (sent_newkeys == FALSE) {
+     struct ssh2_packet *pkt2;
+ 
+     pr_trace_msg(trace_channel, 9, "sending NEWKEYS message to client");
+@@ -4809,6 +4870,11 @@ int sftp_kex_handle(struct ssh2_packet *
+     destroy_pool(pkt2->pool);
+   }
+ 
++  if (use_strict_kex == TRUE) {
++    sftp_ssh2_packet_reset_client_seqno();
++    sftp_ssh2_packet_reset_server_seqno();
++  }
++
+   /* Last but certainly not least, set up the keys for encryption and
+    * authentication, based on H and K.
+    */
+@@ -4828,6 +4894,9 @@ int sftp_kex_handle(struct ssh2_packet *
+   destroy_pool(pkt->pool);
+   cmd = NULL;
+ 
++  /* We've now completed our KEX, possibly our first. */
++  kex_done_first_kex = TRUE;
++
+   /* If extension negotiation has not been disabled, AND if we have not
+    * received a service request, AND if the client sent "ext-info-c", THEN
+    * send our EXT_INFO.  We do not want send this during rekeys.
+@@ -4864,6 +4933,12 @@ int sftp_kex_handle(struct ssh2_packet *
+     cmd = NULL;
+   }
+ 
++  /* Only start the TAP timer after we have completed our first KEX.
++   * Otherwise, we risk sending "illegal" packets prior to, or during,
++   * a "strict KEX" session (Issue 1760).
++   */
++  sftp_tap_start_policy();
++
+   /* Reset this flag for the next time through. */
+   kex_sent_kexinit = FALSE;
+ 
+@@ -4893,7 +4968,7 @@ int sftp_kex_free(void) {
+     destroy_kex(rekey_kex);
+   }
+ 
+-  if (kex_pool) {
++  if (kex_pool != NULL) {
+     destroy_pool(kex_pool);
+     kex_pool = NULL;
+   }
+@@ -5065,7 +5140,7 @@ int sftp_kex_send_first_kexinit(void) {
+   struct ssh2_packet *pkt;
+   int res;
+ 
+-  if (!kex_pool) {
++  if (kex_pool == NULL) {
+     kex_pool = make_sub_pool(sftp_pool);
+     pr_pool_tag(kex_pool, "Kex Pool");
+   }
+@@ -5100,4 +5175,3 @@ int sftp_kex_send_first_kexinit(void) {
+   destroy_pool(pkt->pool);
+   return 0;
+ }
+-
+Index: proftpd-dfsg/contrib/mod_sftp/mod_sftp.c
+===================================================================
+--- proftpd-dfsg.orig/contrib/mod_sftp/mod_sftp.c
++++ proftpd-dfsg/contrib/mod_sftp/mod_sftp.c
+@@ -1,6 +1,6 @@
+ /*
+  * ProFTPD - mod_sftp
+- * Copyright (c) 2008-2020 TJ Saunders
++ * Copyright (c) 2008-2023 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
+@@ -1407,8 +1407,9 @@ MODRET set_sftpoptions(cmd_rec *cmd) {
+   config_rec *c;
+   unsigned long opts = 0UL;
+ 
+-  if (cmd->argc-1 == 0)
++  if (cmd->argc-1 == 0) {
+     CONF_ERROR(cmd, "wrong number of parameters");
++  }
+ 
+   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
+ 
+@@ -1472,6 +1473,9 @@ MODRET set_sftpoptions(cmd_rec *cmd) {
+     } else if (strcmp(cmd->argv[i], "NoExtensionNegotiation") == 0) {
+       opts |= SFTP_OPT_NO_EXT_INFO;
+ 
++    } else if (strcmp(cmd->argv[i], "NoStrictKex") == 0) {
++      opts |= SFTP_OPT_NO_STRICT_KEX;
++
+     } else {
+       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown SFTPOption '",
+         cmd->argv[i], "'", NULL));
+Index: proftpd-dfsg/contrib/mod_sftp/mod_sftp.h.in
+===================================================================
+--- proftpd-dfsg.orig/contrib/mod_sftp/mod_sftp.h.in
++++ proftpd-dfsg/contrib/mod_sftp/mod_sftp.h.in
+@@ -131,6 +131,7 @@
+ #define SFTP_OPT_IGNORE_SFTP_SET_XATTRS		0x04000
+ #define SFTP_OPT_INCLUDE_SFTP_TIMES		0x08000
+ #define SFTP_OPT_NO_EXT_INFO			0x10000
++#define SFTP_OPT_NO_STRICT_KEX			0x040000
+ 
+ /* mod_sftp service flags */
+ #define SFTP_SERVICE_FL_SFTP		0x0001
+Index: proftpd-dfsg/contrib/mod_sftp/packet.c
+===================================================================
+--- proftpd-dfsg.orig/contrib/mod_sftp/packet.c
++++ proftpd-dfsg/contrib/mod_sftp/packet.c
+@@ -1742,6 +1742,18 @@ int sftp_ssh2_packet_rekey_set_size(off_
+   return 0;
+ }
+ 
++uint32_t sftp_ssh2_packet_get_client_seqno(void) {
++  return packet_client_seqno;
++}
++
++void sftp_ssh2_packet_reset_client_seqno(void) {
++  packet_client_seqno = 0;
++}
++
++void sftp_ssh2_packet_reset_server_seqno(void) {
++  packet_server_seqno = 0;
++}
++
+ int sftp_ssh2_packet_send_version(void) {
+   if (!sent_version_id) {
+     int res;
+Index: proftpd-dfsg/contrib/mod_sftp/packet.h
+===================================================================
+--- proftpd-dfsg.orig/contrib/mod_sftp/packet.h
++++ proftpd-dfsg/contrib/mod_sftp/packet.h
+@@ -1,6 +1,6 @@
+ /*
+  * ProFTPD - mod_sftp packet IO
+- * Copyright (c) 2008-2016 TJ Saunders
++ * Copyright (c) 2008-2023 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
+@@ -106,6 +106,13 @@ int sftp_ssh2_packet_rekey_reset(void);
+ int sftp_ssh2_packet_rekey_set_seqno(uint32_t);
+ int sftp_ssh2_packet_rekey_set_size(off_t);
+ 
++/* These are used for implementing the "strict KEX" mitigations of the Terrapin
++ * attack (Issue 1760).
++ */
++uint32_t sftp_ssh2_packet_get_client_seqno(void);
++void sftp_ssh2_packet_reset_client_seqno(void);
++void sftp_ssh2_packet_reset_server_seqno(void);
++
+ int sftp_ssh2_packet_send_version(void);
+ int sftp_ssh2_packet_set_poll_timeout(int);
+ int sftp_ssh2_packet_set_version(const char *);
+Index: proftpd-dfsg/contrib/mod_sftp/tap.c
+===================================================================
+--- proftpd-dfsg.orig/contrib/mod_sftp/tap.c
++++ proftpd-dfsg/contrib/mod_sftp/tap.c
+@@ -1,6 +1,6 @@
+ /*
+  * ProFTPD - mod_sftp traffic analysis protection
+- * Copyright (c) 2008-2016 TJ Saunders
++ * Copyright (c) 2008-2023 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
+@@ -149,7 +149,6 @@ static void set_policy_chance(struct sft
+ }
+ 
+ static void set_policy_timer(struct sftp_tap_policy *policy) {
+-
+   /* Start a timer which checks the last times we received and sent packets.
+    * From there, we may want to inject a TAP message, depending on the
+    * policy.
+@@ -177,6 +176,16 @@ int sftp_tap_send_packet(void) {
+   int rnd;
+   unsigned int chance;
+ 
++  /* Due to chances of violating client-side "strict KEX" Terrapin
++   * mitigations, we will not send packets if we are in the middle of a KEX.
++   */
++  if (!(sftp_sess_state & SFTP_SESS_STATE_HAVE_KEX) ||
++      (sftp_sess_state & SFTP_SESS_STATE_REKEYING)) {
++    pr_trace_msg(trace_channel, 11,
++      "unwilling to send TAP packet during KEX");
++    return 0;
++  }
++
+   if (!sftp_interop_supports_feature(SFTP_SSH2_FEAT_IGNORE_MSG)) {
+     pr_trace_msg(trace_channel, 3,
+       "unable to send TAP packet: IGNORE not supported by client");
+@@ -205,7 +214,7 @@ int sftp_tap_send_packet(void) {
+     struct ssh2_packet *pkt;
+     unsigned int max_datalen = 8192;
+ 
+-    if (curr_policy.max_datalen) {
++    if (curr_policy.max_datalen > 0) {
+       max_datalen = curr_policy.max_datalen;
+     }
+ 
+@@ -246,15 +255,15 @@ int sftp_tap_send_packet(void) {
+ int sftp_tap_set_policy(const char *policy) {
+   register unsigned int i;
+ 
+-  if (tap_pool) {
++  if (tap_pool != NULL) {
+ 
+     /* Special case: IFF the existing policy is 'none' AND the given
+      * policy is 'rogaway', just return.  The 'none' policy must have been
+      * explicitly configured, and it should override the automatic use of
+      * the 'rogaway' policy.
+      */
+-    if (strncmp(curr_policy.policy, "none", 5) == 0 &&
+-        strncasecmp(policy, "rogaway", 8) == 0) {
++    if (strcasecmp(curr_policy.policy, "none") == 0 &&
++        strcasecmp(policy, "rogaway") == 0) {
+       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
+         "'none' traffic policy explicitly configured, ignoring '%s' policy",
+         policy);
+@@ -278,7 +287,6 @@ int sftp_tap_set_policy(const char *poli
+     if (strcasecmp(tap_policies[i].policy, policy) == 0) {
+       copy_policy(&curr_policy, &(tap_policies[i]));
+       set_policy_chance(&curr_policy);
+-      set_policy_timer(&curr_policy);
+       return 0;
+     }
+   }
+@@ -286,3 +294,7 @@ int sftp_tap_set_policy(const char *poli
+   errno = ENOENT;
+   return -1;
+ }
++
++void sftp_tap_start_policy(void) {
++  set_policy_timer(&curr_policy);
++}
+Index: proftpd-dfsg/contrib/mod_sftp/tap.h
+===================================================================
+--- proftpd-dfsg.orig/contrib/mod_sftp/tap.h
++++ proftpd-dfsg/contrib/mod_sftp/tap.h
+@@ -1,6 +1,6 @@
+ /*
+  * ProFTPD - mod_sftp traffic analysis protection
+- * Copyright (c) 2008-2016 TJ Saunders
++ * Copyright (c) 2008-2013 TJ Saunders
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+@@ -63,4 +63,7 @@ int sftp_tap_send_packet(void);
+  */
+ int sftp_tap_set_policy(const char *);
+ 
++/* Sets the configured TAP policy in motion. */
++void sftp_tap_start_policy(void);
++
+ #endif /* MOD_SFTP_TAP_H */
+Index: proftpd-dfsg/doc/contrib/mod_sftp.html
+===================================================================
+--- proftpd-dfsg.orig/doc/contrib/mod_sftp.html
++++ proftpd-dfsg/doc/contrib/mod_sftp.html
+@@ -1164,6 +1164,19 @@ The currently implemented options are:
+   </li>
+ 
+   <p>
++  <li><code>NoStrictKex</code><br>
++    <p>
++    By default, <code>mod_sftp</code> will honor/support the OpenSSH
++    "strict KEX" mode extension, "kex-strict-c-v00 at openssh.com" and
++    "kex-strict-s-v00 at openssh.com".  Use this option to disable support for
++    these custom OpenSSH extensions.
++
++    <p>
++    <b>Note</b> that this option first appeared in
++    <code>proftpd-1.3.9rc1</code>.
++  </li>
++
++  <p>
+   <li><code>OldProtocolCompat</code><br>
+     <p>
+     Older clients identity their protocol versions as "1.99", rather than as
+@@ -2617,7 +2630,7 @@ deal with this issue, then, you can hope
+ <p>
+ <hr>
+ <font size=2><b><i>
+-© Copyright 2008-2020 TJ Saunders<br>
++© Copyright 2008-2023 TJ Saunders<br>
+  All Rights Reserved<br>
+ </i></b></font>
+ <hr>
+Index: proftpd-dfsg/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
+===================================================================
+--- proftpd-dfsg.orig/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
++++ proftpd-dfsg/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
+@@ -87,6 +87,11 @@ my $TESTS = {
+     test_class => [qw(forking ssh2)],
+   },
+ 
++  ssh2_ext_kex_strict_terrapin_issue1760 => {
++    order => ++$order,
++    test_class => [qw(bug forking ssh2)],
++  },
++
+   ssh2_hostkey_rsa => {
+     order => ++$order,
+     test_class => [qw(forking ssh2)],
+@@ -3885,6 +3890,218 @@ EOC
+   unlink($log_file);
+ }
+ 
++sub ssh2_ext_kex_strict_terrapin_issue1760 {
++  my $self = shift;
++  my $tmpdir = $self->{tmpdir};
++  my $setup = test_setup($tmpdir, 'sftp');
++
++  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 $ssh_config = File::Spec->rel2abs("$tmpdir/ssh.conf");
++  if (open(my $fh, "> $ssh_config")) {
++    print $fh <<EOC;
++HostKeyAlgorithms rsa-sha2-256
++IdentityAgent none
++PubkeyAcceptedKeyTypes rsa-sha2-256
++EOC
++    unless (close($fh)) {
++      die("Can't write $ssh_config: $!");
++    }
++
++  } else {
++    die("Can't open $ssh_config: $!");
++  }
++
++  my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf");
++  if (open(my $fh, "> $batch_file")) {
++    print $fh "ls -l\n";
++
++    unless (close($fh)) {
++      die("Can't write $batch_file: $!");
++    }
++
++  } else {
++    die("Can't open $batch_file: $!");
++  }
++
++  my $config = {
++    PidFile => $setup->{pid_file},
++    ScoreboardFile => $setup->{scoreboard_file},
++    SystemLog => $setup->{log_file},
++    TraceLog => $setup->{log_file},
++    Trace => 'ssh2:30 sftp:20 scp:20',
++
++    AuthUserFile => $setup->{auth_user_file},
++    AuthGroupFile => $setup->{auth_group_file},
++    AuthOrder => 'mod_auth_file.c',
++
++    IfModules => {
++      'mod_delay.c' => {
++        DelayEngine => 'off',
++      },
++
++      'mod_sftp.c' => [
++        "SFTPEngine on",
++        "SFTPLog $setup->{log_file}",
++
++        "SFTPHostKey $rsa_host_key",
++        "SFTPHostKey $dsa_host_key",
++
++        "SFTPAuthorizedUserKeys file:~/.authorized_keys",
++      ],
++    },
++  };
++
++  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 {
++      # We use OpenSSH-9.6p1 to test our "strict KEX" Terrapin mitigations.
++      my $sftp = '/Users/tj/local/openssh-9.6p1/bin/sftp';
++
++      my @cmd = (
++        $sftp,
++        '-F',
++        $ssh_config,
++        '-oBatchMode=yes',
++        '-oCheckHostIP=no',
++        '-oCompression=yes',
++        "-oPort=$port",
++        "-oIdentityFile=$rsa_priv_key",
++        '-oPubkeyAuthentication=yes',
++        '-oStrictHostKeyChecking=no',
++        '-oUserKnownHostsFile=/dev/null',
++        '-vvv',
++        '-b',
++        $batch_file,
++        "$setup->{user}\@127.0.0.1",
++      );
++
++      my $sftp_rh = IO::Handle->new();
++      my $sftp_wh = IO::Handle->new();
++      my $sftp_eh = IO::Handle->new();
++
++      $sftp_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 $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd);
++      waitpid($sftp_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);
++      if ($exit_status >> 8 == 0) {
++        $errstr = join('', <$sftp_eh>);
++        $res = 0;
++
++      } else {
++        $errstr = join('', <$sftp_eh>);
++        if ($ENV{TEST_VERBOSE}) {
++          print STDERR "Stderr: $errstr\n";
++        }
++
++        $res = 1;
++      }
++
++      unless ($res == 0) {
++        die("Can't list files on server: $errstr");
++      }
++    };
++    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 =~ /client signaled strict KEX support/) {
++          $ok = 1;
++          last;
++        }
++      }
++
++      close($fh);
++
++      $self->assert($ok, test_msg("Did not see expected 'strict KEX' TraceLog message"));
++
++    } else {
++      die("Can't read $setup->{log_file}: $!");
++    }
++  };
++  if ($@) {
++    $ex = $@;
++  }
++
++  test_cleanup($setup->{log_file}, $ex);
++}
++
+ sub ssh2_hostkey_rsa {
+   my $self = shift;
+   my $tmpdir = $self->{tmpdir};


=====================================
debian/patches/series
=====================================
@@ -4,12 +4,8 @@ ftpasswd.cracklib.location
 mod_sql_mysql.c
 mod_wrap_noparanoid
 ftpstats
-#mod_cap
 odbc
-#reproducible_build
-#spelling_errors
 wrong-path-for-interpreter_perl.diff
-#cd9036f4ef7a05c107f0ffcb19a018b20267c531.patch
 proftpd-mysql-password-backend.diff
 upstream_1063
 upstream_1070
@@ -20,3 +16,6 @@ pr_1094.diff
 upstream_1284
 upstream_1181
 upstream_1149
+bcec15efe6c53dac40420731013f1cd2fd54123b.diff
+97bbe68363ccf2de0c07f67170ec64a8b4d62592.diff
+0021-PATCH-Issue-1830-When-no-supplemental-groups-are-pro.patch



View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/93016330915d99e77d91ff325d33693d50105d48

-- 
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd/-/commit/93016330915d99e77d91ff325d33693d50105d48
You're receiving this email because of your account on salsa.debian.org.




More information about the Pkg-proftpd-maintainers mailing list