diff --git a/packaging/cull_options b/packaging/cull_options index 388d33f..56ceb9c 100755 --- a/packaging/cull_options +++ b/packaging/cull_options @@ -77,6 +77,7 @@ foreach my $opt (sort keys %long_opt) { $val = 1 if $opt =~ /^(max-|min-)/; $val = 3 if $opt eq 'files-from'; $val = '$ro ? -1 : ' . $val if $opt =~ /^remove-/; + $val = '$wo ? -1 : ' . $val if $opt eq 'sender'; print " '$opt' => $val,\n"; } diff --git a/support/rrsync b/support/rrsync old mode 100644 new mode 100755 index 9195aa2..d30d002 --- a/support/rrsync +++ b/support/rrsync @@ -8,11 +8,13 @@ use strict; use Socket; use Cwd 'abs_path'; use File::Glob ':glob'; +use Fcntl ':flock'; # You may configure these values to your liking. See also the section # of options if you want to disable any options that rsync accepts. use constant RSYNC => '/usr/bin/rsync'; use constant LOGFILE => 'rrsync.log'; +use constant LOCKFILE => '.rrsync.lock'; my $Usage = <>', LOCKFILE) or die "open lockfile: $!"; +flock LOCK_FH, ($am_sender ? LOCK_SH : LOCK_EX) or die "lock lockfile: $!"; + ### START of options data produced by the cull_options script. ### # These options are the only options that rsync might send to the server, @@ -130,7 +138,7 @@ our %long_opt = ( 'remove-sent-files' => $only eq 'r' ? -1 : 0, 'remove-source-files' => $only eq 'r' ? -1 : 0, 'safe-links' => 0, - 'sender' => 0, + 'sender' => $only eq 'w' ? -1 : 0, 'server' => 0, 'size-only' => 0, 'skip-compress' => 1, @@ -147,6 +155,13 @@ our %long_opt = ( ### END of options data produced by the cull_options script. ### +if ($subdir ne '/') { + # Disable anything that might follow a symlink out of the restricted dir + $short_disabled .= 'LKk'; + $long_opt{'copy-unsafe-links'} = -1; + $long_opt{'no-implied-dirs'} = -1; +} + if ($short_disabled ne '') { $short_no_arg =~ s/[$short_disabled]//go; $short_with_num =~ s/[$short_disabled]//go; @@ -216,6 +231,11 @@ die "$0: invalid rsync-command syntax or options\n" if $in_options; @args = ( '.' ) if !@args; +for (@args) { + die "$0: do not use .. in any path!\n" if m{(^|/)\.\.(/|$)}; + die "$0: arg not under subdir\n" unless abs_is_under($_, $subdir); +} + if ($write_log) { my ($mm,$hh) = (localtime)[1,2]; my $host = $ENV{SSH_CONNECTION} || 'unknown'; @@ -227,7 +247,19 @@ if ($write_log) { } # Note: This assumes that the rsync protocol will not be maliciously hijacked. -exec(RSYNC, @opts, @args) or die "exec(rsync @opts @args) failed: $? $!"; +exit system(RSYNC, @opts, '--', @args); + +sub abs_is_under { + my ($path, $under_abspath) = @_; + for (;;) { + my $a = abs_path($path); + if (defined $a) { + return $a =~ m{^\Q$under_abspath\E(/|$)}; + } + die "abs_path failed on .: $!" if $path eq '.'; + $path =~ s{/[^/]*$}{} or $path = '.'; + } +} sub check_arg { @@ -238,6 +270,8 @@ sub check_arg die "Do not use .. in --$opt; anchor the path at the root of your restricted dir.\n" if $arg =~ m{(^|/)\.\.(/|$)}; $arg =~ s{^/}{$subdir/}; + die "--$opt value outside restricted dir.\n" + unless abs_is_under($arg, $subdir); } $arg; }