[Git][debian-proftpd-team/proftpd-mod-procfs][master] 3 commits: New upstream version 0.3
Hilmar Preuße (@hilmar)
gitlab at salsa.debian.org
Sat Aug 1 19:29:40 BST 2026
Hilmar Preuße pushed to branch master at Debian ProFTPD Team / proftpd-mod-procfs
Commits:
b43831b5 by Hilmar Preuße at 2026-08-01T09:15:18+02:00
New upstream version 0.3
- - - - -
b2c75f4d by Hilmar Preuße at 2026-08-01T09:15:18+02:00
Update upstream source from tag 'upstream/0.3'
Update to upstream version '0.3'
with Debian dir 74c1020476ae60d43d9e650846920be50706f620
- - - - -
21cbf964 by Hilmar Preuße at 2026-08-01T20:28:43+02:00
Release 0.3-1.
- - - - -
9 changed files:
- .github/workflows/regressions.yml
- debian/changelog
- mod_procfs.c
- mod_procfs.html
- t/lib/ProFTPD/Tests/Modules/mod_procfs.pm
- + t/lib/ProFTPD/Tests/Modules/mod_procfs/digest.pm
- t/lib/ProFTPD/Tests/Modules/mod_procfs/sftp.pm
- + t/modules/mod_procfs/digest.t
- tests.pl
Changes:
=====================================
.github/workflows/regressions.yml
=====================================
@@ -90,7 +90,7 @@ jobs:
# NOTE: Docker does not have good IPv6 support, hence we disable it.
run: |
cd proftpd
- ./configure --disable-ipv6 --with-modules=mod_site_misc:mod_copy:mod_sftp:mod_procfs
+ ./configure --disable-ipv6 --with-modules=mod_site_misc:mod_copy:mod_digest:mod_sftp:mod_procfs
make
./proftpd -V
make install
=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+proftpd-mod-procfs (0.3-1) unstable; urgency=medium
+
+ * New upstream release.
+ - Prevents access to 'sysfs' file system too.
+
+ -- Hilmar Preuße <hille42 at debian.org> Sat, 01 Aug 2026 10:44:07 +0200
+
proftpd-mod-procfs (0.2-1) unstable; urgency=medium
* New upstream release.
=====================================
mod_procfs.c
=====================================
@@ -52,7 +52,7 @@
# error "ProFTPD 1.3.6rc2 or later required"
#endif
-#define MOD_PROCFS_VERSION "mod_procfs/0.2"
+#define MOD_PROCFS_VERSION "mod_procfs/0.3"
module procfs_module;
@@ -65,6 +65,7 @@ static const char *trace_channel = "procfs";
struct procfs_mount {
const char *path;
size_t path_len;
+ const char *type;
};
static array_header *procfs_mounts = NULL;
@@ -86,20 +87,28 @@ static int get_procfs_mounts(pool *p) {
mnt = getmntent(mountf);
while (mnt != NULL) {
+ struct procfs_mount *mount = NULL;
+ size_t path_len = 0;
+
pr_signals_handle();
if (strcmp(mnt->mnt_type, "proc") == 0) {
- struct procfs_mount *mount;
- size_t path_len;
-
pr_log_debug(DEBUG0, MOD_PROCFS_VERSION
": discovered procfs mounted at '%s'", mnt->mnt_dir);
+ mount = palloc(p, sizeof(struct procfs_mount));
+ mount->type = pstrdup(p, "procfs");
+ } else if (strcmp(mnt->mnt_type, "sysfs") == 0) {
+ pr_log_debug(DEBUG0, MOD_PROCFS_VERSION
+ ": discovered sysfs mounted at '%s'", mnt->mnt_dir);
mount = palloc(p, sizeof(struct procfs_mount));
+ mount->type = pstrdup(p, "sysfs");
+ }
+ if (mount != NULL) {
/* If the mount point path does not end with a trailing slash, add it.
- * We use this property when checking paths that reference this procfs
- * mount point.
+ * We use this property when checking paths that reference this mount
+ * point.
*/
path_len = strlen(mnt->mnt_dir);
if (mnt->mnt_dir[path_len-1] != '/') {
@@ -163,7 +172,7 @@ static int get_procfs_mounts(pool *p) {
return 0;
}
-static int is_procfs_path(pool *p, const char *path) {
+static int is_blocked_path(pool *p, const char *path) {
register unsigned int i;
int res = FALSE;
char *abs_path;
@@ -182,7 +191,8 @@ static int is_procfs_path(pool *p, const char *path) {
mount = mounts[i];
pr_trace_msg(trace_channel, 19,
- "checking path '%s' against procfs mount '%s'", abs_path, mount->path);
+ "checking path '%s' against %s mount '%s'", abs_path, mount->type,
+ mount->path);
if (abs_pathlen >= mount->path_len &&
strncmp(abs_path, mount->path, mount->path_len) == 0) {
@@ -253,7 +263,7 @@ static modret_t *handle_path(cmd_rec *cmd, const char *cmd_name,
const char *path) {
pr_trace_msg(trace_channel, 19, "checking path '%s' for %s", path, cmd_name);
- if (is_procfs_path(cmd->tmp_pool, path) == TRUE) {
+ if (is_blocked_path(cmd->tmp_pool, path) == TRUE) {
const char *proto, *resp_code;
proto = pr_session_get_protocol(0);
@@ -421,6 +431,42 @@ MODRET procfs_sftp_pre_path(cmd_rec *cmd) {
return handle_path(cmd, cmd->argv[0], path);
}
+MODRET procfs_sftp_pre_hardlink(cmd_rec *cmd) {
+ const char *src_path, *dst_path, *proto;
+ char *ptr;
+ modret_t *mr;
+
+ if (procfs_engine == FALSE) {
+ return PR_DECLINED(cmd);
+ }
+
+ proto = pr_session_get_protocol(0);
+ if (strcmp(proto, "sftp") != 0) {
+ return PR_DECLINED(cmd);
+ }
+
+ /* Unfortunately, mod_sftp currently does NOT break the two paths into
+ * the cmd->argv array; it only populates cmd->arg.
+ *
+ * In the future, if/when mod_sftp behavior changes, we can look at the
+ * cmd->argc to determine which style is being used by mod_sftp.
+ */
+ ptr = strchr(cmd->arg, ' ');
+ if (ptr == NULL) {
+ return PR_DECLINED(cmd);
+ }
+
+ src_path = pstrndup(cmd->tmp_pool, cmd->arg, ptr - cmd->arg);
+ dst_path = pstrdup(cmd->tmp_pool, ptr + 1);
+
+ mr = handle_path(cmd, cmd->argv[0], src_path);
+ if (MODRET_ISERROR(mr)) {
+ return mr;
+ }
+
+ return handle_path(cmd, cmd->argv[0], dst_path);
+}
+
MODRET procfs_sftp_pre_symlink(cmd_rec *cmd) {
const char *src_path, *dst_path, *proto;
char *ptr;
@@ -639,7 +685,19 @@ static cmdtable procfs_cmdtab[] = {
{ PRE_CMD, C_STAT, G_NONE, procfs_pre_path, FALSE, FALSE },
{ PRE_CMD, C_STOR, G_NONE, procfs_pre_path, FALSE, FALSE },
+ /* mod_digest FTP commands */
+ { PRE_CMD, "HASH", G_NONE, procfs_pre_path, FALSE, FALSE },
+ { PRE_CMD, "MD5", G_NONE, procfs_pre_path, FALSE, FALSE },
+ { PRE_CMD, "XCRC", G_NONE, procfs_pre_path, FALSE, FALSE },
+ { PRE_CMD, "XMD5", G_NONE, procfs_pre_path, FALSE, FALSE },
+ { PRE_CMD, "XSHA", G_NONE, procfs_pre_path, FALSE, FALSE },
+ { PRE_CMD, "XSHA1", G_NONE, procfs_pre_path, FALSE, FALSE },
+ { PRE_CMD, "XSHA256", G_NONE, procfs_pre_path, FALSE, FALSE },
+ { PRE_CMD, "XSHA512", G_NONE, procfs_pre_path, FALSE, FALSE },
+
/* SFTP */
+ { PRE_CMD, "HARDLINK", G_NONE, procfs_sftp_pre_hardlink, FALSE, FALSE },
+ { PRE_CMD, "LINK", G_NONE, procfs_sftp_pre_hardlink, FALSE, FALSE },
{ PRE_CMD, "LSTAT", G_NONE, procfs_sftp_pre_path, FALSE, FALSE },
{ PRE_CMD, "OPENDIR", G_NONE, procfs_sftp_pre_path, FALSE, FALSE },
{ PRE_CMD, "READLINK", G_NONE, procfs_sftp_pre_path, FALSE, FALSE },
=====================================
mod_procfs.html
=====================================
@@ -14,7 +14,7 @@
<p>
The <code>mod_procfs</code> module affects the visibility and usage of the
-<code>/proc</code> filesystem directory.
+<code>/proc</code> and <code>/sys</code> pseudo-filesystem directories.
<p>
This module is contained in the <code>mod_procfs</code> file for
@@ -49,17 +49,18 @@ questions, concerns, or suggestions regarding this module.
<p>
The <code>ProcfsEngine</code> directive enables or disables the module's
-hiding of the <code>/proc</code> filesystem. When enabled,
-<code>mod_procfs</code> works to prevent access to the <code>/proc</code>
-filesystem, returning "No such file or directory" errors for such access
-attempts.
+hiding of the <code>/proc</code> and <code>/sys</code> filesystems. When
+enabled, <code>mod_procfs</code> works to prevent access to the
+<code>/proc</code> and <code>/sys</code> pseudo-filesystems, returning
+"No such file or directory" errors for such access attempts.
<p>
<b>Note</b> that <code>ProcfsEngine</code> is <em>automatically enabled</em>
-if the module detects, on startup, that the <code>/proc</code> filesystem
-is present. If the <code>/proc</code> filesystem is not present on startup,
-then <code>ProcfsEngine</code> is automatically disabled. Explicitly setting
-this directive overrides the automatic behavior.
+if the module detects, on startup, that the <code>/proc</code> or
+<code>/sys</code> filesystems are present. If the <code>/proc</code> and
+<code>/sys</code> filesystems are not present on startup, then
+<code>ProcfsEngine</code> is automatically disabled. Explicitly setting this
+directive overrides the automatic behavior.
<hr>
<h2><a name="ProcfsLog">ProcfsLog</a></h2>
@@ -121,8 +122,8 @@ your existing server:
<p>
The <code>mod_procfs</code> module works by using the ProFTPD command dispatch
system to intercept all commands/requests that use paths; if the paths
-being used reference the <code>/proc</code> filesystem, those commands will
-fail with a "No such file or directory" error.
+being used reference the <code>/proc</code> or <code>/sys</code> filesystems,
+those commands will fail with a "No such file or directory" error.
<p>
Example configuration:
=====================================
t/lib/ProFTPD/Tests/Modules/mod_procfs.pm
=====================================
@@ -83,7 +83,12 @@ my $TESTS = {
test_class => [qw(forking)],
},
- procfs_retr_rejected => {
+ procfs_retr_procfs_rejected => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ procfs_retr_sysfs_rejected => {
order => ++$order,
test_class => [qw(forking)],
},
@@ -1792,7 +1797,7 @@ sub procfs_nlst_rejected {
test_cleanup($setup, $ex);
}
-sub procfs_retr_rejected {
+sub procfs_retr_procfs_rejected {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'procfs');
@@ -1885,6 +1890,131 @@ sub procfs_retr_rejected {
test_cleanup($setup, $ex);
}
+sub procfs_retr_sysfs_rejected {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/sys/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'command:10 response:10 procfs:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ my $ex;
+
+ # Fork child
+ $self->handle_sigchld();
+ defined(my $pid = fork()) or die("Can't fork: $!");
+ if ($pid) {
+ eval {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ my $conn = $client->retr_raw($test_file);
+ if ($conn) {
+ die("RETR succeeded unexpectedly");
+ }
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+ $client->quit();
+
+ my $expected = 550;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /RETR $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
sub procfs_rmd_rejected {
my $self = shift;
my $tmpdir = $self->{tmpdir};
=====================================
t/lib/ProFTPD/Tests/Modules/mod_procfs/digest.pm
=====================================
@@ -0,0 +1,1151 @@
+package ProFTPD::Tests::Modules::mod_procfs::digest;
+
+use lib qw(t/lib);
+use base qw(ProFTPD::TestSuite::Child);
+use strict;
+
+use Cwd;
+use File::Copy;
+use File::Path qw(mkpath);
+use File::Spec;
+use IO::Handle;
+use IPC::Open3;
+use Socket;
+
+use ProFTPD::TestSuite::FTP;
+use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
+
+$| = 1;
+
+my $order = 0;
+
+my $TESTS = {
+ procfs_digest_hash => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ procfs_digest_md5 => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ procfs_digest_xmd5 => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ procfs_digest_xcrc => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ procfs_digest_xsha => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ procfs_digest_xsha1 => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ procfs_digest_xsha256 => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+ procfs_digest_xsha512 => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
+};
+
+sub new {
+ return shift()->SUPER::new(@_);
+}
+
+sub list_tests {
+ return testsuite_get_runnable_tests($TESTS);
+# procfs_digest_xsha
+# procfs_digest_xsha1
+# procfs_digest_xsha256
+}
+
+sub procfs_digest_hash {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/proc/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'procfs:20 digest:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_digest.c' => {
+ DigestEngine => 'on',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ 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 {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ my $algo = 'CRC32';
+ my ($resp_code, $resp_msg) = $client->opts('HASH', $algo);
+
+ my $expected = 200;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = $algo;
+ $self->assert($expected eq $resp_msg,
+ test_msg("Expected response message '$expected', got '$resp_msg'"));
+
+ eval { $client->quote('HASH', $test_file) };
+ unless ($@) {
+ die("HASH command 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 = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /HASH $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
+sub procfs_digest_md5 {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/proc/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'procfs:20 digest:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_digest.c' => {
+ DigestEngine => 'on',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ 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 {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ eval { $client->quote('MD5', $test_file) };
+ unless ($@) {
+ die("MD5 command succeeded unexpectedly");
+ }
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 550;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /MD5 $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
+sub procfs_digest_xmd5 {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/proc/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'procfs:20 digest:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_digest.c' => {
+ DigestEngine => 'on',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ 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 {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ eval { $client->quote('XMD5', $test_file) };
+ unless ($@) {
+ die("XMD5 command succeeded unexpectedly");
+ }
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 550;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /XMD5 $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
+sub procfs_digest_xcrc {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/proc/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'procfs:20 digest:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_digest.c' => {
+ DigestEngine => 'on',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ 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 {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ eval { $client->quote('XCRC', $test_file) };
+ unless ($@) {
+ die("XCRC command succeeded unexpectedly");
+ }
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 550;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /XCRC $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
+sub procfs_digest_xsha {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/proc/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'procfs:20 digest:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_digest.c' => {
+ DigestEngine => 'on',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ 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 {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ eval { $client->quote('XSHA', $test_file) };
+ unless ($@) {
+ die("XSHA command succeeded unexpectedly");
+ }
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 550;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /XSHA $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
+sub procfs_digest_xsha1 {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/proc/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'procfs:20 digest:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_digest.c' => {
+ DigestEngine => 'on',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ 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 {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ eval { $client->quote('XSHA1', $test_file) };
+ unless ($@) {
+ die("XSHA1 command succeeded unexpectedly");
+ }
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 550;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /XSHA1 $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
+sub procfs_digest_xsha256 {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/proc/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'procfs:20 digest:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_digest.c' => {
+ DigestEngine => 'on',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ 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 {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ eval { $client->quote('XSHA256', $test_file) };
+ unless ($@) {
+ die("XSHA256 command succeeded unexpectedly");
+ }
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 550;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /XSHA256 $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
+sub procfs_digest_xsha512 {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $test_file = '/proc/test.txt';
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'procfs:20 digest:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_digest.c' => {
+ DigestEngine => 'on',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $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: $!");
+ }
+
+ 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 {
+ # Allow for server startup
+ sleep(1);
+
+ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
+ $client->login($setup->{user}, $setup->{passwd});
+
+ eval { $client->quote('XSHA512', $test_file) };
+ unless ($@) {
+ die("XSHA512 command succeeded unexpectedly");
+ }
+
+ my $resp_code = $client->response_code();
+ my $resp_msg = $client->response_msg();
+
+ my $expected = 550;
+ $self->assert($expected == $resp_code,
+ test_msg("Expected response code $expected, got $resp_code"));
+
+ $expected = "$test_file: No such file or directory";
+ $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($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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /XSHA512 $test_file denied by mod_procfs/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
+1;
=====================================
t/lib/ProFTPD/Tests/Modules/mod_procfs/sftp.pm
=====================================
@@ -84,6 +84,11 @@ my $TESTS = {
test_class => [qw(forking)],
},
+ procfs_sftp_ext_hardlink => {
+ order => ++$order,
+ test_class => [qw(forking)],
+ },
+
# TODO
# procfs_sftp_link
};
@@ -2202,4 +2207,215 @@ sub procfs_sftp_symlink {
test_cleanup($setup, $ex);
}
+sub procfs_sftp_ext_hardlink {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'procfs');
+
+ my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key");
+ my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key");
+
+ my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key");
+ my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub");
+ my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/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 $from_file = '/proc/src.txt';
+ my $to_file = '/proc/dst.txt';
+
+ # As Net::SSH2::SFTP (via libssh2) does not implement LINK, we'll use
+ # OpenSSH's sftp(1) and its "hardlink at openssh.com" extension.
+
+ my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf");
+ if (open(my $fh, "> $batch_file")) {
+ print $fh "ln $from_file $to_file\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 => 'procfs:20 sftp:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_procfs.c' => {
+ ProcfsEngine => 'on',
+ ProcfsLog => $setup->{log_file},
+ },
+
+ '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;
+
+ # Ignore SIGPIPE
+ local $SIG{PIPE} = sub { };
+
+ # Fork child
+ $self->handle_sigchld();
+ defined(my $pid = fork()) or die("Can't fork: $!");
+ if ($pid) {
+ eval {
+ # Allow for server startup
+ sleep(1);
+
+ my $sftp = 'sftp';
+
+ my @cmd = (
+ $sftp,
+ '-oBatchMode=yes',
+ '-oCheckHostIP=no',
+ '-oCompression=yes',
+ "-oPort=$port",
+ "-oIdentityFile=$rsa_priv_key",
+ '-oIdentitiesOnly=yes',
+ '-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;
+ }
+
+ if ($res == 0) {
+ die("Hardlink $from_file on server succeeded unexpectedly");
+ }
+ };
+ 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);
+
+ next unless $line =~ /mod_procfs\//;
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /HARDLINK $from_file denied by mod_procfs/) {
+ $ok = 1;
+ }
+ }
+
+ close($fh);
+ $self->assert($ok, test_msg("Did not see expected ProcfsLog messages"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup, $ex);
+}
+
1;
=====================================
t/modules/mod_procfs/digest.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_procfs::digest");
=====================================
tests.pl
=====================================
@@ -62,6 +62,11 @@ if (scalar(@ARGV) > 0) {
my $order = 0;
my $FEATURE_TESTS = {
+ 't/modules/mod_procfs/digest.t' => {
+ order => ++$order,
+ test_class => [qw(mod_digest)],
+ },
+
't/modules/mod_procfs/sftp.t' => {
order => ++$order,
test_class => [qw(mod_sftp)],
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-procfs/-/compare/0e6f324dc3603fdc821dd6d3328a8280c5fc10d1...21cbf9648003193e74e1fb62acaf9bcb869e53f5
--
View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-procfs/-/compare/0e6f324dc3603fdc821dd6d3328a8280c5fc10d1...21cbf9648003193e74e1fb62acaf9bcb869e53f5
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