summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.md23
-rwxr-xr-xpackaging/cull_options5
-rwxr-xr-xsupport/lsh19
-rwxr-xr-xsupport/rrsync35
4 files changed, 57 insertions, 25 deletions
diff --git a/NEWS.md b/NEWS.md
index b88208c5..735dec1d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -97,15 +97,20 @@
- More ASM optimizations from Shark64.
- - Transformed rrsync into a python script with improvements: security has been
- beefed up; the known rsync options were updated to include recent additions;
- rrsync rejects `-L` (`--copy-links`) by default to make it harder to exploit
- any out-of-subdir symlinks; a new rrsync option of `-munge` tells rrsync to
- always enable the `--munge-links` rsync option on the server side; a new
- rrsync option of `-no-del` disables all `--remove*` and `--delete*` rsync
- options on the server side; the log format has been tweaked slightly to add
- seconds to the timestamp and output the command executed as a tuple; an
- rrsync.1 manpage is now created.
+ - Transformed rrsync into a python script with improvements:
+ - Security has been beefed up.
+ - The known rsync options were updated to include recent additions.
+ - Make rrsync reject `-L`, `-K`, & `-k` by default to make it harder to
+ exploit any out-of-subdir symlinks.
+ - A new rrsync option of `-munge` tells rrsync to always enable rsync's
+ `--munge-links` option on the server side.
+ - A new rrsync option of `-no-lock` disables a new single-use locking idiom
+ that is the default when `-ro` is not used (useful with `-munge`).
+ - A new rrsync option of `-no-del` disables all `--remove*` and `--delete*`
+ options on the server side.
+ - The log format has been tweaked slightly to add seconds to the timestamp
+ and to output the command executed as a tuple (making the args clearer).
+ - An rrsync.1 manpage was added.
- Work around a glibc bug where lchmod() breaks in a chroot w/o /proc mounted.
diff --git a/packaging/cull_options b/packaging/cull_options
index d4e1c626..ca061121 100755
--- a/packaging/cull_options
+++ b/packaging/cull_options
@@ -86,7 +86,10 @@ def main():
# To disable a short-named option, add its letter to this string:
"""
- txt += str_assign('short_disabled', 'Ls') + "\n"
+ txt += str_assign('short_disabled', 's') + "\n"
+ txt += '# These are also disabled when the restricted dir is not "/":\n'
+ txt += str_assign('short_disabled_subdir', 'KLk') + "\n"
+ txt += '# These are all possible short options that we will accept (when not disabled above):\n'
txt += str_assign('short_no_arg', ''.join(sorted(short_no_arg)), 'DO NOT REMOVE ANY')
txt += str_assign('short_with_num', ''.join(sorted(short_with_num)), 'DO NOT REMOVE ANY')
diff --git a/support/lsh b/support/lsh
index 40fe3d73..7b3c0656 100755
--- a/support/lsh
+++ b/support/lsh
@@ -16,10 +16,7 @@ GetOptions(
'no-cd' => \( my $no_chdir ),
'sudo' => \( my $use_sudo ),
'rrsync=s' => \( my $rrsync_dir ),
- 'ro' => \( my $rrsync_ro = '' ),
- 'wo' => \( my $rrsync_wo = '' ),
- 'munge' => \( my $rrsync_munge = '' ),
- 'no-del' => \( my $rrsync_no_del = '' ),
+ 'rropts=s' => \( my $rrsync_opts ),
) or &usage;
&usage unless @ARGV > 1;
@@ -75,10 +72,12 @@ unless ($no_chdir) {
if ($rrsync_dir) {
$ENV{SSH_ORIGINAL_COMMAND} = join(' ', @ARGV);
push @cmd, 'rrsync';
- push @cmd, '-ro' if $rrsync_ro;
- push @cmd, '-wo' if $rrsync_wo;
- push @cmd, '-munge' if $rrsync_munge;
- push @cmd, '-no-del' if $rrsync_no_del;
+ if ($rrsync_opts) {
+ foreach my $opt (split(/[ ,]+/, $rrsync_opts)) {
+ $opt = "-$opt" unless $opt =~ /^-/;
+ push @cmd, $opt;
+ }
+ }
push @cmd, $rrsync_dir;
} else {
push @cmd, '/bin/sh', '-c', "@ARGV";
@@ -101,8 +100,8 @@ Options:
--no-cd Skip the chdir \$HOME (the default with hostname "lh")
--sudo Use sudo -H -l USER to become root or the specified USER.
--rrsync=DIR Test rrsync restricted copying without using ssh.
---ro Passes -ro to rrsync (when --rrsync is specified).
---wo Passes -wo to rrsync (when --rrsync is specified.
+--rropts=STR The string "munge,no-del,no-lock" would pass 3 options to
+ rrsync (must be combined with --rrsync=DIR).
The script also ignores a bunch of single-letter ssh options.
EOT
diff --git a/support/rrsync b/support/rrsync
index 5b43a819..469288b9 100755
--- a/support/rrsync
+++ b/support/rrsync
@@ -24,8 +24,12 @@ LOGFILE = 'rrsync.log' # NOTE: the file must exist for a line to be appended!
### START of options data produced by the cull_options script. ###
# To disable a short-named option, add its letter to this string:
-short_disabled = 'Ls'
+short_disabled = 's'
+# These are also disabled when the restricted dir is not "/":
+short_disabled_subdir = 'KLk'
+
+# These are all possible short options that we will accept (when not disabled above):
short_no_arg = 'ACDEHIJKLNORSUWXbcdgklmnopqrstuvxyz' # DO NOT REMOVE ANY
short_with_num = '@B' # DO NOT REMOVE ANY
@@ -125,7 +129,7 @@ long_opts = {
### END of options data produced by the cull_options script. ###
-import os, sys, re, argparse, glob, socket, time
+import os, sys, re, argparse, glob, socket, time, subprocess
from argparse import RawTextHelpFormatter
try:
@@ -174,6 +178,10 @@ def main():
if args.ro:
long_opts['log-file'] = -1
+ if args.dir != '/':
+ global short_disabled
+ short_disabled += short_disabled_subdir
+
short_no_arg_re = short_no_arg
short_with_num_re = short_with_num
if short_disabled:
@@ -268,8 +276,12 @@ def main():
log_fh.close()
# NOTE: This assumes that the rsync protocol will not be maliciously hijacked.
- os.execlp(RSYNC, *cmd)
- die("execlp(", RSYNC, *cmd, ') failed')
+ if args.no_lock:
+ os.execlp(RSYNC, *cmd)
+ die("execlp(", RSYNC, *cmd, ') failed')
+ child = subprocess.run(cmd)
+ if child.returncode != 0:
+ sys.exit(child.returncode)
def validated_arg(opt, arg, typ=3, wild=False):
@@ -319,6 +331,16 @@ def validated_arg(opt, arg, typ=3, wild=False):
return ret if wild else ret[0]
+def lock_or_die(dirname):
+ import fcntl
+ global lock_handle
+ lock_handle = os.open(dirname, os.O_RDONLY)
+ try:
+ fcntl.flock(lock_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except:
+ die('Another instance of rrsync is already accessing this directory.')
+
+
def die(*msg):
print(sys.argv[0], 'error:', *msg, file=sys.stderr)
if sys.stdin.isatty():
@@ -336,9 +358,10 @@ if __name__ == '__main__':
our_desc = """Use "man rrsync" to learn how to restrict ssh users to using a restricted rsync command."""
arg_parser = OurArgParser(description=our_desc, add_help=False)
only_group = arg_parser.add_mutually_exclusive_group()
- only_group.add_argument('-ro', action='store_true', help="Allow only reading from the DIR. Implies -no-del.")
+ only_group.add_argument('-ro', action='store_true', help="Allow only reading from the DIR. Implies -no-del and -no-lock.")
only_group.add_argument('-wo', action='store_true', help="Allow only writing to the DIR.")
arg_parser.add_argument('-no-del', action='store_true', help="Disable rsync's --delete* and --remove* options.")
+ arg_parser.add_argument('-no-lock', action='store_true', help="Avoid the single-run (per-user) lock check.")
arg_parser.add_argument('-munge', action='store_true', help="Enable rsync's --munge-links on the server side.")
arg_parser.add_argument('-help', '-h', action='help', help="Output this help message and exit.")
arg_parser.add_argument('dir', metavar='DIR', help="The restricted directory to use.")
@@ -348,6 +371,8 @@ if __name__ == '__main__':
args.dir_slash_len = len(args.dir)
if args.ro:
args.no_del = True
+ elif not args.no_lock:
+ lock_or_die(args.dir)
main()
# vim: sw=4 et