]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
Add a default single-access lock.
authorWayne Davison <wayne@opencoder.net>
Tue, 28 Dec 2021 01:44:32 +0000 (17:44 -0800)
committerWayne Davison <wayne@opencoder.net>
Tue, 28 Dec 2021 01:57:53 +0000 (17:57 -0800)
NEWS.md
packaging/cull_options
support/lsh
support/rrsync

diff --git a/NEWS.md b/NEWS.md
index b88208c500733a4f645d7bdb0289eee4134a6b93..735dec1d66dc7e5365036643d0b3ff3f3e64a9a2 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
 
  - 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.
 
index d4e1c626d2f43baca211d2eb9498b8d147f11d95..ca0611211e5c1b1cb4b86b251718c546c2f0e184 100755 (executable)
@@ -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')
    
index 40fe3d73850d08f8df4f52dd6aab4f17ff93aad2..7b3c0656d673e078cd0b6edceb6f538baca70d31 100755 (executable)
@@ -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
index 5b43a819855e2bf81738e88c328a047db6aacf13..469288b9f5550e8d5e939a4365ff9552b23f9361 100755 (executable)
@@ -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