From noreply at release.debian.org Sat Aug 1 05:39:11 2026 From: noreply at release.debian.org (Debian testing watch) Date: Sat, 01 Aug 2026 04:39:11 +0000 Subject: proftpd-mod-procfs 0.2-1 MIGRATED to testing Message-ID: FYI: The status of the proftpd-mod-procfs source package in Debian's testing distribution has changed. Previous version: 0.1-1 Current version: 0.2-1 -- This email is automatically generated once a day. As the installation of new packages into testing happens multiple times a day you will receive later changes on the next day. See https://release.debian.org/testing-watch/ for more information. From ftpmaster at ftp-master.debian.org Sat Aug 1 09:59:18 2026 From: ftpmaster at ftp-master.debian.org (Debian FTP Masters) Date: Sat, 01 Aug 2026 08:59:18 +0000 Subject: Processing of proftpd-mod-procfs_0.3-1_source.changes Message-ID: proftpd-mod-procfs_0.3-1_source.changes uploaded successfully to localhost along with the files: proftpd-mod-procfs_0.3-1.dsc proftpd-mod-procfs_0.3.orig.tar.gz proftpd-mod-procfs_0.3-1.debian.tar.xz proftpd-mod-procfs_0.3-1_source.buildinfo Greetings, Your Debian queue daemon (running on host usper.debian.org) From gitlab at salsa.debian.org Sat Aug 1 19:29:26 2026 From: gitlab at salsa.debian.org (=?UTF-8?B?SGlsbWFyIFByZXXDn2UgKEBoaWxtYXIp?=) Date: Sat, 01 Aug 2026 18:29:26 +0000 Subject: [Git][debian-proftpd-team/proftpd-mod-procfs] Pushed new tag debian/0.3-1 Message-ID: <6a6e3b0680740_551fb7484391de@godard.mail> Hilmar Preu?e pushed new tag debian/0.3-1 at Debian ProFTPD Team / proftpd-mod-procfs -- View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-procfs/-/tree/debian/0.3-1 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 From gitlab at salsa.debian.org Sat Aug 1 19:29:28 2026 From: gitlab at salsa.debian.org (=?UTF-8?B?SGlsbWFyIFByZXXDn2UgKEBoaWxtYXIp?=) Date: Sat, 01 Aug 2026 18:29:28 +0000 Subject: [Git][debian-proftpd-team/proftpd-mod-procfs] Pushed new tag upstream/0.3 Message-ID: <6a6e3b08ba509_551fb61c4394f8@godard.mail> Hilmar Preu?e pushed new tag upstream/0.3 at Debian ProFTPD Team / proftpd-mod-procfs -- View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-procfs/-/tree/upstream/0.3 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 From gitlab at salsa.debian.org Sat Aug 1 19:29:40 2026 From: gitlab at salsa.debian.org (=?UTF-8?B?SGlsbWFyIFByZXXDn2UgKEBoaWxtYXIp?=) Date: Sat, 01 Aug 2026 18:29:40 +0000 Subject: [Git][debian-proftpd-team/proftpd-mod-procfs][master] 3 commits: New upstream version 0.3 Message-ID: <6a6e3b145b1_551fb6bc439751@godard.mail> 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 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 @@

The mod_procfs module affects the visibility and usage of the -/proc filesystem directory. +/proc and /sys pseudo-filesystem directories.

This module is contained in the mod_procfs file for @@ -49,17 +49,18 @@ questions, concerns, or suggestions regarding this module.

The ProcfsEngine directive enables or disables the module's -hiding of the /proc filesystem. When enabled, -mod_procfs works to prevent access to the /proc -filesystem, returning "No such file or directory" errors for such access -attempts. +hiding of the /proc and /sys filesystems. When +enabled, mod_procfs works to prevent access to the +/proc and /sys pseudo-filesystems, returning +"No such file or directory" errors for such access attempts.

Note that ProcfsEngine is automatically enabled -if the module detects, on startup, that the /proc filesystem -is present. If the /proc filesystem is not present on startup, -then ProcfsEngine is automatically disabled. Explicitly setting -this directive overrides the automatic behavior. +if the module detects, on startup, that the /proc or +/sys filesystems are present. If the /proc and +/sys filesystems are not present on startup, then +ProcfsEngine is automatically disabled. Explicitly setting this +directive overrides the automatic behavior.


ProcfsLog

@@ -121,8 +122,8 @@ your existing server:

The mod_procfs module works by using the ProFTPD command dispatch system to intercept all commands/requests that use paths; if the paths -being used reference the /proc filesystem, those commands will -fail with a "No such file or directory" error. +being used reference the /proc or /sys filesystems, +those commands will fail with a "No such file or directory" error.

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 From gitlab at salsa.debian.org Sat Aug 1 19:29:46 2026 From: gitlab at salsa.debian.org (=?UTF-8?B?SGlsbWFyIFByZXXDn2UgKEBoaWxtYXIp?=) Date: Sat, 01 Aug 2026 18:29:46 +0000 Subject: [Git][debian-proftpd-team/proftpd-mod-procfs][pristine-tar] pristine-tar data for proftpd-mod-procfs_0.3.orig.tar.gz Message-ID: <6a6e3b1a3efe2_551faec4440034@godard.mail> Hilmar Preu?e pushed to branch pristine-tar at Debian ProFTPD Team / proftpd-mod-procfs Commits: 99a8bc61 by Hilmar Preu?e at 2026-08-01T09:15:18+02:00 pristine-tar data for proftpd-mod-procfs_0.3.orig.tar.gz - - - - - 2 changed files: - + proftpd-mod-procfs_0.3.orig.tar.gz.delta - + proftpd-mod-procfs_0.3.orig.tar.gz.id Changes: ===================================== proftpd-mod-procfs_0.3.orig.tar.gz.delta ===================================== Binary files /dev/null and b/proftpd-mod-procfs_0.3.orig.tar.gz.delta differ ===================================== proftpd-mod-procfs_0.3.orig.tar.gz.id ===================================== @@ -0,0 +1 @@ +f112f1a03b1d25706fae88080137e9f3a83f91b4 View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-procfs/-/commit/99a8bc61b033631a1b885caea54271c60f927d09 -- View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-procfs/-/commit/99a8bc61b033631a1b885caea54271c60f927d09 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 From gitlab at salsa.debian.org Sat Aug 1 19:30:00 2026 From: gitlab at salsa.debian.org (=?UTF-8?B?SGlsbWFyIFByZXXDn2UgKEBoaWxtYXIp?=) Date: Sat, 01 Aug 2026 18:30:00 +0000 Subject: [Git][debian-proftpd-team/proftpd-mod-procfs][upstream] New upstream version 0.3 Message-ID: <6a6e3b288a49a_551fb74844037f@godard.mail> Hilmar Preu?e pushed to branch upstream 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 - - - - - 8 changed files: - .github/workflows/regressions.yml - 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 ===================================== 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 @@

The mod_procfs module affects the visibility and usage of the -/proc filesystem directory. +/proc and /sys pseudo-filesystem directories.

This module is contained in the mod_procfs file for @@ -49,17 +49,18 @@ questions, concerns, or suggestions regarding this module.

The ProcfsEngine directive enables or disables the module's -hiding of the /proc filesystem. When enabled, -mod_procfs works to prevent access to the /proc -filesystem, returning "No such file or directory" errors for such access -attempts. +hiding of the /proc and /sys filesystems. When +enabled, mod_procfs works to prevent access to the +/proc and /sys pseudo-filesystems, returning +"No such file or directory" errors for such access attempts.

Note that ProcfsEngine is automatically enabled -if the module detects, on startup, that the /proc filesystem -is present. If the /proc filesystem is not present on startup, -then ProcfsEngine is automatically disabled. Explicitly setting -this directive overrides the automatic behavior. +if the module detects, on startup, that the /proc or +/sys filesystems are present. If the /proc and +/sys filesystems are not present on startup, then +ProcfsEngine is automatically disabled. Explicitly setting this +directive overrides the automatic behavior.


ProcfsLog

@@ -121,8 +122,8 @@ your existing server:

The mod_procfs module works by using the ProFTPD command dispatch system to intercept all commands/requests that use paths; if the paths -being used reference the /proc filesystem, those commands will -fail with a "No such file or directory" error. +being used reference the /proc or /sys filesystems, +those commands will fail with a "No such file or directory" error.

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/-/commit/b43831b540487b4ce6fd3a6ecd409d06df8066b4 -- View it on GitLab: https://salsa.debian.org/debian-proftpd-team/proftpd-mod-procfs/-/commit/b43831b540487b4ce6fd3a6ecd409d06df8066b4 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