proftpd-mod-tar-0.4/ 0000755 0001750 0001750 00000000000 13063310535 013347 5 ustar hille hille proftpd-mod-tar-0.4/t/ 0000755 0001750 0001750 00000000000 13063310535 013612 5 ustar hille hille proftpd-mod-tar-0.4/t/etc/ 0000755 0001750 0001750 00000000000 13063310535 014365 5 ustar hille hille proftpd-mod-tar-0.4/t/etc/modules/ 0000755 0001750 0001750 00000000000 13063310535 016035 5 ustar hille hille proftpd-mod-tar-0.4/t/etc/modules/mod_tar/ 0000755 0001750 0001750 00000000000 13063310535 017462 5 ustar hille hille proftpd-mod-tar-0.4/t/etc/modules/mod_tar/subdir.tar 0000644 0001750 0001750 00000024000 13063310535 021456 0 ustar hille hille subdir/ 0000755 0001750 0001750 00000000000 11726212312 010157 5 ustar tj tj subdir/test.txt 0000644 0001750 0001750 00000000016 11726212312 011674 0 ustar tj tj Hello, World! proftpd-mod-tar-0.4/t/lib/ 0000755 0001750 0001750 00000000000 13063310535 014360 5 ustar hille hille proftpd-mod-tar-0.4/t/lib/ProFTPD/ 0000755 0001750 0001750 00000000000 13063310535 015576 5 ustar hille hille proftpd-mod-tar-0.4/t/lib/ProFTPD/Tests/ 0000755 0001750 0001750 00000000000 13063310535 016700 5 ustar hille hille proftpd-mod-tar-0.4/t/lib/ProFTPD/Tests/Modules/ 0000755 0001750 0001750 00000000000 13063310535 020310 5 ustar hille hille proftpd-mod-tar-0.4/t/lib/ProFTPD/Tests/Modules/mod_tar.pm 0000644 0001750 0001750 00000214245 13063310535 022303 0 ustar hille hille package ProFTPD::Tests::Modules::mod_tar; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Archive::Tar; use Archive::Tar::File; use Archive::Zip qw(:ERROR_CODES :CONSTANTS); use Cwd; use Digest::MD5; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IO::Zlib; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { tar_retr_file => { order => ++$order, test_class => [qw(forking)], }, tar_retr_tar => { order => ++$order, test_class => [qw(forking)], }, tar_retr_tar_already_exists => { order => ++$order, test_class => [qw(forking)], }, tar_retr_tar_symlinks => { order => ++$order, test_class => [qw(forking)], }, # XXX tar_retr_tar_subdirs # XXX Need test for absolute symlinks, and chrooted session tar_retr_tar_symlinks_opt_dereference => { order => ++$order, test_class => [qw(forking)], }, tar_enable_off => { order => ++$order, test_class => [qw(forking)], }, tar_notar => { order => ++$order, test_class => [qw(forking)], }, tar_retr_tar_gz => { order => ++$order, test_class => [qw(forking)], }, tar_retr_tgz => { order => ++$order, test_class => [qw(forking)], }, tar_retr_tar_bz2 => { order => ++$order, test_class => [qw(forking)], }, tar_xferlog_retr_tar => { order => ++$order, test_class => [qw(forking)], }, tar_tmp_path_cleanup_on_abort => { order => ++$order, test_class => [qw(forking)], }, # XXX tar_tmp_path_dev_full (on Linux), to test out-of-space handling tar_retr_tar_2gb_single_file => { order => ++$order, test_class => [qw(forking slow)], }, # XXX tar_retr_tar_2gb_many_files tar_retr_zip => { order => ++$order, test_class => [qw(forking)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub tar_retr_file { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$home_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("test.txt.tar"); if ($conn) { die("RETR test.txt.tar succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'test.txt.tar: 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($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_tar { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$sub_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.tar"); unless ($conn) { die("RETR subdir.tar failed: " . $client->response_code() . " " . $client->response_msg()); } my $tar = Archive::Tar->new($conn); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect $expected = 2; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'subdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); $expected = 'subdir/test.txt'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); # Make sure the file contents have not been corrupted in transit $ctx = Digest::MD5->new(); $ctx->add($tar->get_content('subdir/test.txt')); my $test_md5 = $ctx->hexdigest(); $self->assert($test_md5 eq $expected_md5, test_msg("Expected MD5 checksum '$expected_md5', got '$test_md5'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_tar_already_exists { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $archive = File::Spec->rel2abs("t/etc/modules/mod_tar/subdir.tar"); my $test_file = File::Spec->rel2abs("$tmpdir/subdir.tar"); unless (copy($archive, $test_file)) { die("Can't copy $archive to $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); $ctx->add("Hello, World!\n"); my $expected_md5 = $ctx->hexdigest(); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.tar"); unless ($conn) { die("RETR subdir.tar failed: " . $client->response_code() . " " . $client->response_msg()); } my $tar = Archive::Tar->new($conn); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect $expected = 2; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'subdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); $expected = 'subdir/test.txt'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); # Make sure the file contents have not been corrupted in transit $ctx = Digest::MD5->new(); $ctx->add($tar->get_content('subdir/test.txt')); my $test_md5 = $ctx->hexdigest(); $self->assert($test_md5 eq $expected_md5, test_msg("Expected MD5 checksum '$expected_md5', got '$test_md5'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_tar_symlinks { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Create three files in subdir for (my $i = 0; $i < 3; $i++) { my $test_file = File::Spec->rel2abs("$sub_dir/test" . ($i + 1) . ".txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } } my $symlink_dir = File::Spec->rel2abs("$tmpdir/symlinkdir"); mkpath($symlink_dir); # Create three symlinks in symlinkdir to the files in subdir (using # relative paths). my $cwd = getcwd(); unless (chdir($symlink_dir)) { die("Can't chdir to $symlink_dir: $!"); } # Create three files in subdir for (my $i = 0; $i < 3; $i++) { my $symlink_src = "../subdir/test" . ($i + 1) . ".txt"; my $symlink_dst = "./test" . ($i + 1) . ".lnk"; unless (symlink($symlink_src, $symlink_dst)) { die("Can't symlink '$symlink_src' to '$symlink_dst': $!"); } } unless (chdir($cwd)) { die("Can't chdir to $cwd: $!"); } # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("symlinkdir.tar"); unless ($conn) { die("RETR symlinkdir.tar failed: " . $client->response_code() . " " . $client->response_msg()); } my $tar = Archive::Tar->new($conn); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect $expected = 4; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'symlinkdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); for (my $i = 0; $i < 3; $i++) { $expected = "symlinkdir/test" . ($i + 1) . '.lnk'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); my $ent = ($tar->get_files($expected))[0]; $self->assert(defined($ent), test_msg("Unable to get info for $expected from archive")); $self->assert($ent->is_symlink(), test_msg("File $expected is not a symlink as expected")); $expected = '../subdir/test' . ($i + 1) . '.txt'; my $ent_linkname = $ent->linkname(); $self->assert($ent_linkname eq $expected, test_msg("Expected linkname '$expected', got '$ent_linkname'")); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_tar_symlinks_opt_dereference { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Create three files in subdir for (my $i = 0; $i < 3; $i++) { my $test_file = File::Spec->rel2abs("$sub_dir/test" . ($i + 1) . ".txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } } my $symlink_dir = File::Spec->rel2abs("$tmpdir/symlinkdir"); mkpath($symlink_dir); # Create three symlinks in symlinkdir to the files in subdir (using # relative paths). my $cwd = getcwd(); unless (chdir($symlink_dir)) { die("Can't chdir to $symlink_dir: $!"); } # Create three files in subdir for (my $i = 0; $i < 3; $i++) { my $symlink_src = "../subdir/test" . ($i + 1) . ".txt"; my $symlink_dst = "./test" . ($i + 1) . ".lnk"; unless (symlink($symlink_src, $symlink_dst)) { die("Can't symlink '$symlink_src' to '$symlink_dst': $!"); } } unless (chdir($cwd)) { die("Can't chdir to $cwd: $!"); } # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, TarOptions => 'dereference', # TarOptions => 'FollowSymlinks', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("symlinkdir.tar"); unless ($conn) { die("RETR symlinkdir.tar failed: " . $client->response_code() . " " . $client->response_msg()); } my $tar = Archive::Tar->new($conn); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect $expected = 4; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'symlinkdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); for (my $i = 0; $i < 3; $i++) { $expected = "symlinkdir/test" . ($i + 1) . '.lnk'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); my $ent = ($tar->get_files($expected))[0]; $self->assert(defined($ent), test_msg("Unable to get info for $expected from archive")); $self->assert($ent->is_file(), test_msg("File $expected is not a symlink as expected")); $expected = 14; my $ent_sz = $ent->size(); $self->assert($ent_sz == $expected, test_msg("Expected file size $expected, got $ent_sz")); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_enable_off { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$sub_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # MacOSX-specific hack if ($^O eq 'darwin') { $sub_dir = ('/private' . $sub_dir); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', Directory => { $sub_dir => { TarEnable => 'off', }, }, IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); # This should fail, since the file doesn't exist, and we configured # "TarEnable off" in that directory. my $conn = $client->retr_raw("subdir.tar"); if ($conn) { die("RETR subdir.tar succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'subdir.tar: 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($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_notar { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$sub_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } my $notar_file = File::Spec->rel2abs("$sub_dir/.notar"); if (open(my $fh, "> $notar_file")) { unless (close($fh)) { die("Can't write $notar_file: $!"); } } else { die("Can't open $notar_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); # This should fail, since the file doesn't exist, and we configured # a .notar file in that directory. my $conn = $client->retr_raw("subdir.tar"); if ($conn) { die("RETR subdir.tar succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'subdir.tar: 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($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_tar_gz { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$sub_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.tar.gz"); unless ($conn) { die("RETR subdir.tar.gz failed: " . $client->response_code() . " " . $client->response_msg()); } my $zio = IO::Zlib->new($conn, 'rb'); my $tar = Archive::Tar->new($zio); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect $expected = 2; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'subdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); $expected = 'subdir/test.txt'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); # Make sure the file contents have not been corrupted in transit $ctx = Digest::MD5->new(); $ctx->add($tar->get_content('subdir/test.txt')); my $test_md5 = $ctx->hexdigest(); $self->assert($test_md5 eq $expected_md5, test_msg("Expected MD5 checksum '$expected_md5', got '$test_md5'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_tgz { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$sub_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.tgz"); unless ($conn) { die("RETR subdir.tgz failed: " . $client->response_code() . " " . $client->response_msg()); } my $zio = IO::Zlib->new($conn, 'rb'); my $tar = Archive::Tar->new($zio); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect $expected = 2; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'subdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); $expected = 'subdir/test.txt'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); # Make sure the file contents have not been corrupted in transit $ctx = Digest::MD5->new(); $ctx->add($tar->get_content('subdir/test.txt')); my $test_md5 = $ctx->hexdigest(); $self->assert($test_md5 eq $expected_md5, test_msg("Expected MD5 checksum '$expected_md5', got '$test_md5'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_tar_bz2 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $src_file = File::Spec->rel2abs("$sub_dir/src.bin"); if (open(my $fh, "> $src_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $src_file: $!"); } } else { die("Can't open $src_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $src_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $src_file: $!"); } my $dst_bz2_file = File::Spec->rel2abs("$tmpdir/dst.tar.bz2"); my $dst_tar_file = File::Spec->rel2abs("$tmpdir/dst.tar"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.tar.bz2"); unless ($conn) { die("RETR subdir.tar.bz2 failed: " . $client->response_code() . " " . $client->response_msg()); } # Download the data to a separate bunzipped file, and let Archive::Tar # work on that. my $dstfh; unless (open($dstfh, "> $dst_bz2_file")) { die("Can't open $dst_bz2_file: $!"); } binmode($dstfh); my $buf; my $buflen = 16384; while ($conn->read($buf, $buflen, 25)) { print $dstfh $buf; } unless (close($dstfh)) { die("Can't write $dst_bz2_file: $!"); } eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $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($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { # Uncompress the file `bunzip2 -q $dst_bz2_file`; my $dstfh; unless (open($dstfh, "< $dst_tar_file")) { die("Can't read $dst_tar_file: $!"); } binmode($dstfh); my $tar = Archive::Tar->new($dstfh); unless (defined($tar)) { die("Can't read tar file from $dst_tar_file: " . $Archive::Tar::error); } my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect my $expected = 2; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'subdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); $expected = 'subdir/src.bin'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); # Make sure the file contents have not been corrupted in transit $ctx = Digest::MD5->new(); $ctx->add($tar->get_content('subdir/src.bin')); my $test_md5 = $ctx->hexdigest(); $self->assert($test_md5 eq $expected_md5, test_msg("Expected MD5 checksum '$expected_md5', got '$test_md5'")); close($dstfh); }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_xferlog_retr_tar { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$sub_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', TransferLog => $xfer_log, IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.tar"); unless ($conn) { die("RETR subdir.tar failed: " . $client->response_code() . " " . $client->response_msg()); } my $tar = Archive::Tar->new($conn); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect $expected = 2; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'subdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); $expected = 'subdir/test.txt'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); # Make sure the file contents have not been corrupted in transit $ctx = Digest::MD5->new(); $ctx->add($tar->get_content('subdir/test.txt')); my $test_md5 = $ctx->hexdigest(); $self->assert($test_md5 eq $expected_md5, test_msg("Expected MD5 checksum '$expected_md5', got '$test_md5'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { # Now read in the TransferLog, make sure that the filename is the originally # requested name, not the name as modified by mod_tar. if (open(my $fh, "< $xfer_log")) { my $line = <$fh>; chomp($line); close($fh); my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+o\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$'; $self->assert(qr/$expected/, $line, test_msg("Expected '$expected', got '$line'")); if ($line =~ /$expected/) { my $remote_host = $1; my $filesz = $2; my $filename = $3; my $xfer_type = $4; my $user_name = $5; $expected = '127.0.0.1'; $self->assert($expected eq $remote_host, test_msg("Expected '$expected', got '$remote_host'")); # The original filename, sans .tar extension $expected = $sub_dir; if ($^O eq 'darwin') { # MacOSX-specific hack dealing with their tmp filesystem shenanigans $expected = ('/private' . $expected); } $self->assert($expected eq $filename, test_msg("Expected '$expected', got '$filename'")); $expected = 'b'; $self->assert($expected eq $xfer_type, test_msg("Expected '$expected', got '$xfer_type'")); $expected = $user; $self->assert($expected eq $user_name, test_msg("Expected '$expected', got '$user_name'")); } else { die("Can't read $xfer_log: $!"); } } }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_tmp_path_cleanup_on_abort { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$sub_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } my $tmp_path = File::Spec->rel2abs("$tmpdir/tarwork"); mkpath($tmp_path); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, TarTempPath => $tmp_path, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.tar"); unless ($conn) { die("RETR subdir.tar failed: " . $client->response_code() . " " . $client->response_msg()); } # Read in a couple of bytes, then abort the connection. my $buf; $conn->read($buf, 4, 25); $conn->abort(); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Abort successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); # Scan the TarTempPath, make sure there are no files in there. my $dirh; unless (opendir($dirh, $tmp_path)) { die("Can't open directory '$tmp_path': $!"); } my $tmp_files = [grep { !/^\.$/ && !/^\.\.$/ } readdir($dirh)]; closedir($dirh); my $nfiles = scalar(@$tmp_files); $self->assert($nfiles == 0, test_msg("Expected no tmp files, found $nfiles")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_tar_2gb_single_file { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $src_file = File::Spec->rel2abs("$sub_dir/src.bin"); # Create a file that is 2GB. my $src_len = (2 ** 31); if (open(my $fh, "> $src_file")) { if ($ENV{TEST_VERBOSE}) { print STDOUT "# Creating test file of $src_len bytes\n"; } my $nchunks = 64; my $chunklen = ($src_len / $nchunks); for (my $i = 0; $i < $nchunks; $i++) { print $fh "A" x $chunklen; } unless (close($fh)) { die("Can't write $src_file: $!"); } } else { die("Can't open $src_file: $!"); } my $dst_file = File::Spec->rel2abs("$tmpdir/dst.tar"); # This test could run for a while, since mod_tar has to read in the large # file. So give the test time to run. my $timeout_idle = 1800; my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', TimeoutIdle => $timeout_idle, IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 10, $timeout_idle); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.tar"); unless ($conn) { die("RETR subdir.tar failed: " . $client->response_code() . " " . $client->response_msg()); } # I'm not sure why, but Archive::Tar does not like reading tar data # directly from the $conn if that tar data contains large files. # # To work around this, read the data from $conn into a local temp # file, then set Archive::Tar to work on that local file. my $dstfh; unless (open($dstfh, "> $dst_file")) { die("Can't write $dst_file: $!"); } binmode($dstfh); my $buf; my $buflen = 16384; while ($conn->read($buf, $buflen, 25)) { print $dstfh $buf; } unless (close($dstfh)) { die("Can't write $dst_file: $!"); } eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); unless (open($dstfh, "< $dst_file")) { die("Can't read $dst_file: $!"); } binmode($dstfh); if ($ENV{TEST_VERBOSE}) { print STDOUT "# Finished downloading to $dst_file, verifying tar format\n"; } my $tar = Archive::Tar->new($dstfh); my $entries = { map { $_ => 1 } $tar->list_files() }; # Make sure the hashref contains the entries we expect $expected = 2; my $nents = scalar(keys(%$entries)); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'subdir/'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); $expected = 'subdir/src.bin'; $self->assert(defined($entries->{$expected}), test_msg("Expected entry for '$expected', did not see one")); my $ent = ($tar->get_files($expected))[0]; $self->assert(defined($ent), test_msg("Expected entry for '$expected', did not see one")); my $ent_len = $ent->size(); $self->assert($ent_len eq $src_len, test_msg("Expected file length $src_len, got $ent_len")); close($dstfh); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh, $timeout_idle + 10) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub tar_retr_zip { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/tar.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/tar.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/tar.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/tar.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/tar.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); mkpath($sub_dir); # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $test_file = File::Spec->rel2abs("$sub_dir/src.bin"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } } else { die("Can't open $test_file: $!"); } # Calculate the MD5 checksum of this file, for comparison with the # downloaded file. my $ctx = Digest::MD5->new(); my $expected_md5; if (open(my $fh, "< $test_file")) { binmode($fh); $ctx->addfile($fh); $expected_md5 = $ctx->hexdigest(); close($fh); } else { die("Can't read $test_file: $!"); } my $dst_zip_file = File::Spec->rel2abs("$tmpdir/dst.zip"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 tar:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_tar.c' => { TarEngine => 'on', TarLog => $log_file, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($user, $passwd); $client->type('binary'); my $conn = $client->retr_raw("subdir.zip"); unless ($conn) { die("RETR subdir.tar failed: " . $client->response_code() . " " . $client->response_msg()); } my $dstfh; unless (open($dstfh, "> $dst_zip_file")) { die("Can't open $dst_zip_file: $!"); } my $buf; my $buflen = 16384; while ($conn->read($buf, $buflen, 25)) { print $dstfh $buf; } unless (close($dstfh)) { die("Can't write $dst_zip_file: $!"); } eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Transfer complete'; $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($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); eval { my $zip = Archive::Zip->new($dst_zip_file); my $entries = [$zip->members()]; # Make sure the arrayref contains the entries we expect my $expected = 2; my $nents = scalar(@$entries); $self->assert($nents == $expected, test_msg("Expected $expected entries, found $nents")); $expected = 'subdir/'; my $ent = $zip->memberNamed($expected); $self->assert(defined($ent), test_msg("Expected entry for '$expected', did not see one")); $expected = 'subdir/src.bin'; $ent = $zip->memberNamed($expected); $self->assert(defined($ent), test_msg("Expected entry for '$expected', did not see one")); # Make sure the file contents have not been corrupted in transit $ctx = Digest::MD5->new(); my $data = $zip->contents('subdir/src.bin'); $ctx->add($data); my $test_md5 = $ctx->hexdigest(); $self->assert($test_md5 eq $expected_md5, test_msg("Expected MD5 checksum '$expected_md5', got '$test_md5'")); }; if ($@) { $ex = $@; } if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } 1; proftpd-mod-tar-0.4/t/modules/ 0000755 0001750 0001750 00000000000 13063310535 015262 5 ustar hille hille proftpd-mod-tar-0.4/t/modules/mod_tar.t 0000644 0001750 0001750 00000000263 13063310535 017075 0 ustar hille hille #!/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_tar"); proftpd-mod-tar-0.4/.travis.yml 0000644 0001750 0001750 00000001677 13063310535 015473 0 ustar hille hille language: c compiler: - gcc - clang install: - sudo apt-get update -qq # for libarchive - sudo apt-get install -y libarchive-dev # for libbz2 - sudo apt-get install -y libbz2-dev # for unit tests - sudo apt-get install -y check # for static code analysis - sudo apt-get install -y cppcheck rats # for test code coverage - sudo apt-get install -y lcov - gem install coveralls-lcov before_script: - cd ${TRAVIS_BUILD_DIR} - lcov --directory . --zerocounters script: # - find . -type f -name "*.c" -print | grep -v t\/ | xargs cppcheck 2>&1 # - find . -type f -name "*.c" -print | grep -v t\/ | xargs rats --language=c - git clone https://github.com/proftpd/proftpd.git - cp mod_tar.c proftpd/contrib/ - cd proftpd - ./configure --enable-devel=coverage --enable-dso --enable-tests --with-shared=mod_tar - make - make clean - ./configure --enable-devel=coverage --enable-tests --with-modules=mod_tar - make proftpd-mod-tar-0.4/doc/ 0000755 0001750 0001750 00000000000 13063310535 014114 5 ustar hille hille proftpd-mod-tar-0.4/doc/NOTES.libarchive 0000644 0001750 0001750 00000010607 13063310535 017042 0 ustar hille hille struct archive *a; a = archive_write_new(); # Set compression: none, bzip2, or gzip archive_write_set_compression_none(a); archive_write_set_compression_bzip2(a); archive_write_set_compression_gzip(a); # Set format archive_write_set_format_ustar(a); OR archive_write_set_format_pax_restricted(a); // Note 1 /* Libarchive's "pax restricted" format is a tar format that uses pax * extensions only when absolutely necessary. Most of the time, it will * write plain ustar entries. This is the recommended tar format for most * uses. You should explicitly use ustar format only when you have to * create archives that will be readable on older systems; you should * explicitly request pax format only when you need to preserve as many * attributes as possible. */ archive_write_set_format_zip(a); // ? See caveats about ZIP64 format, 64-bit platforms: // libarchive/archive_write_set_format_zip.c Reading data from disk: use archive_read_disk_set_gname_lookup(), archive_read_disk_set_uname_lookup() to set callbacks that libarchive will use to resolve UID/GID to names. E.g. set pr_auth_get_pwuid(), pr_auth_get_grgid(). use archive_read_disk_set_symlink_logical() (follows symlinks) or archive_read_disk_set_symlink_physical() (does not follow symlinks) use archive_read_disk_entry_from_file()? Example (from libarchive_read_disk(3)). Note that the libarchive_write_open() man page has a better example: struct archive *a; a = archive_write_new(); if (a == NULL) { ... } archive_write_open(a, custom_data, open_cb, write_cb, close_cb) archive_write_open_filename(a, outname, 10240); void add_file_to_archive(struct archive *a, const char *path) { char buf[8K]; size_t nread; struct archive *lookup; struct archive_entry *entry; int fd; /* Create a lookup archive; set the callbacks we want to use. */ lookup = archive_read_disk_new(); archive_read_disk_set_standard_lookup(lookup); entry = archive_entry_new(); fd = open(path, O_RDONLY); if (fd < 0) { /* cleanup */ } archive_entry_copy_pathname(entry, path); /* The last argument is a struct stat *. If we provide that, * then read_disk_entry_from_file() just copies the stat info * it needs. We can use this, do the pr_fsio_fstat() ourselves. */ archive_read_disk_entry_from_file(lookup, entry fd, NULL); archive_write_header(a, entry); /* XXX If a regular file, copy the file contents */ nread = read(fd, buf, sizeof(buf)); while (nread > 0) { /* Handle signals */ archive_write_data(a, buf, nread); nread = read(fd, buf, sizeof(buf)); } archive_write_finish_entry(a); archive_entry_free(entry); archive_read_free(lookup); close(fd); } archive_write_close(a); // Note 4 archive_write_free(a); // Note 5 This example creates a fresh archive_entry object for each file. For better performance, you can reuse the same archive_entry object by using `archive_entry_clear()` to erase it after each use. Note 3: Size, file type, and pathname are all required attributes here. You can also use `archive_entry_copy_stat()` to copy all information from the `struct stat` to the archive entry, including file type. To get even more complete information, look at the `archive_read_disk` API, which provides an easy way to get more extensive file metadata---including ACLs and extended attributes on some systems---than using the system `stat()` system call. It also works on platforms such as Windows where `stat()` either doesn't exist or is broken. This suggests that mod_tar should use archive_entry_copy_stat(), and have a TarOptions option for enabling the recording of extended attributes (and would switch to using archive_read_disk()). Note 4: The free/finish call will implicitly call `archive_write_close()` if necessary. However, the close call returns an error code and the free/finish call does not, so if you rely on the implicit close, you won't be able to detect any errors that happen with the final writes. Note 5: Beginning with libarchive 3.0, this function is called `archive_write_free()`. The previous name was `archive_write_finish()`. If you want to write software compatible with libarchive 2.x and libarchive 3.x, you should use the old name, but be aware that it will be removed when libarchive 4.x is released. proftpd-mod-tar-0.4/.gitattributes 0000644 0001750 0001750 00000000062 13063310535 016240 0 ustar hille hille *.pl linguist-language=C *.pm linguist-language=C proftpd-mod-tar-0.4/mod_tar.html 0000644 0001750 0001750 00000022104 13063310535 015661 0 ustar hille hille
mod_tar
The mod_tar
module supports on-the-fly creation of tar files.
Whenever a client attempts to download a directory as a tar file, the
mod_tar
module will automatically create a tar file of that
directory.
To provide this feature, the mod_tar
module uses the
libarchive
library; see:
http://libarchive.github.com/
This module is contained in the mod_tar
file for
ProFTPD 1.3.x, and is not compiled by default. Installation
instructions are discussed here. More examples
of mod_tar
usage can be found here.
The most current version of mod_tar
can be found at:
http://www.castaglia.org/proftpd/
Please contact TJ Saunders <tj at castaglia.org> with any questions, concerns, or suggestions regarding this module.
<Directory>
, .ftpaccess
Module: mod_tar
The TarEnable
directive can be used to block or prevent a
directory from being turned into a tar file by mod_tar
.
<VirtualHost>
, <Global>
, <Anonymous>
The TarEngine
directive enables or disables the module's
support for on-the-fly tar file creation.
<VirtualHost>
, <Global>
The TarLog
directive is used to a specify a log file for
mod_tar
reporting and debugging, and can be done a per-server
basis. The file parameter must be the full path to the file to use for
logging. Note that this path must not be to a world-writeable
directory and, unless AllowLogSymlinks
is explicitly set to
on (generally a bad idea), the path must not be a symbolic
link.
If file is "none", no logging will be done at all; this
setting can be used to override a TarLog
setting inherited from
a <Global>
context.
<VirtualHost>
, <Global>
, <Anonymous>
The TarOptions
directive is used to configure various optional
behavior of mod_tar
, usually pertaining to how the
.tar
files are constructed.
Example:
TarOptions FollowSymlinks
The currently implemented options are:
FollowSymlinks
Instead of creating .tar
files which include symlinks,
include the files that the symlinks point to.
<VirtualHost>
, <Global>
, <Anonymous>
The TarTempPath
directive controls the directory where
mod_tar
will writes its temporary .tar
files.
Keep in mind that the TarTempPath
is subject to any chroot
(i.e. use of DefaultRoot
or
<Anonymous>
).
The default TarTempPath
is "./", which means that the
temporary .tar
files are written in the current directory of
the FTP session.
mod_tar
, copy the mod_tar.c
file into:
proftpd-dir/contrib/after unpacking the latest proftpd-1.3.x source code. For including
mod_tar
as a staticly linked module:
./configure --with-modules=mod_tarTo build
mod_tar
as a DSO module:
./configure --enable-dso --with-shared=mod_tarThen follow the usual steps:
make make install
For those with an existing ProFTPD installation, you can use the
prxs
tool to add mod_tar
, as a DSO module, to
your existing server:
# prxs -c -i -d -I /path/to/libarchive/include -L /path/to/libarchive/lib mod_tar.c
Note that in order to support gzip and bzip2 compression, the
mod_tar
module requires linking with the zlib (-lz
)
and bzip2 lib (-lbz2
) libraries. You may need to install
these packages on your system in order to build mod_tar
.
The mod_tar
module works by watching all download requests
(i.e. RETR
commands), looking specifically for requests
like:
RETR $dir.tar.gzThe following extensions will trigger
mod_tar
to attempt
on-the-fly tar file creation:
If the requested tar file already exists, then mod_tar
does
nothing, and lets the download proceed normally. If the requested tar file is
not for a directory, then mod_tar
does nothing.
Next, the mod_tar
module checks for the existence of a
"$dir/.notar" file. If this file is present, then mod_tar
does nothing. (This provides feature compatibility with wu-ftpd's on-the-fly
tar file creation feature.)
The mod_tar
module then checks to see if TarEnable
has been configured for the requested directory. For example, you can
block certain directories from being bundled up by mod_tar
by using:
<Directory $dir> TarEnable off </Directory>
Once these checks have passed, a randomly generated unique filename is
generated for the tar file to be created; the tar file is created in the
session's current working directory (although this can be changed
using the TarTempPath
directive), and
is deleted after the download finishes. This means that the client will need
write privileges in that directory in order for the tar file to be created.
No external commands are used for creating the tar file. Searches
for on-the-fly tar file creation will turn up reports
of vulnerabilities and issues with the tar file feature in wu-ftpd. The
problem there was that wu-ftpd used external commands, such as
/bin/tar
, to create the tar files. These commands take a range
of command-line options; malicious FTP clients could exploit those
command-line options, and wu-ftpd's on-the-fly tar file implementation, to
attack the server. By contrast, the mod_tar
module does
not use any external commands; it uses the libtar
library
for creating the tar file. And mod_tar
ensures that the
requested path is indeed a directory and that that directory path is treated
as-is, with no special interpolation or interpretation.
Example configuration:
<IfModule mod_tar.c> TarEngine on TarLog /var/ftpd/tar.log </IfModule>