[SCM] The rsync repository. - branch master updated

Rsync CVS commit messages rsync-cvs at lists.samba.org
Sun Dec 26 20:33:28 UTC 2021


The branch, master has been updated
       via  72adf49b rrsync improvements
      from  73ceea6a Convert atomic-rsync to python.

https://git.samba.org/?p=rsync.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 72adf49ba8cb81426e2b9799fbd43c6284b013a9
Author: Wayne Davison <wayne at opencoder.net>
Date:   Sun Dec 26 12:29:00 2021 -0800

    rrsync improvements
    
    - Convert rrsync to python.
    - Enhance security of arg & option checking.
    - Reject `-L` (`--copy-links`) by default.
    - Add `-munge` and `-no-del` options.
    - Tweak the logfile line format.
    - Created an rrsync man page.
    - Use `configure --with-rrsync` if you want `make install` to install
      rrsync and its man page.
    - Give lsh more rrsync testing support.

-----------------------------------------------------------------------

Summary of changes:
 Makefile.in            |  10 +-
 NEWS.md                |  18 +-
 configure.ac           |   7 +
 maybe-make-man         |   2 +-
 md2man                 |  13 +-
 packaging/cull_options |  36 +--
 support/lsh            |  12 +-
 support/rrsync         | 595 +++++++++++++++++++++++++++----------------------
 support/rrsync.1.md    |  89 ++++++++
 9 files changed, 488 insertions(+), 294 deletions(-)
 create mode 100644 support/rrsync.1.md


Changeset truncated at 500 lines:

diff --git a/Makefile.in b/Makefile.in
index 3c8c2240..5eed339e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -6,6 +6,7 @@ exec_prefix=@exec_prefix@
 bindir=@bindir@
 libdir=@libdir@/rsync
 mandir=@mandir@
+with_rrsync=@with_rrsync@
 
 LIBS=@LIBS@
 CC=@CC@
@@ -80,6 +81,10 @@ install: all
 	if test -f rsync.1; then $(INSTALLMAN) -m 644 rsync.1 $(DESTDIR)$(mandir)/man1; fi
 	if test -f rsync-ssl.1; then $(INSTALLMAN) -m 644 rsync-ssl.1 $(DESTDIR)$(mandir)/man1; fi
 	if test -f rsyncd.conf.5; then $(INSTALLMAN) -m 644 rsyncd.conf.5 $(DESTDIR)$(mandir)/man5; fi
+	if test "$(with_rrsync)" = yes; then \
+	    $(INSTALLCMD) -m 755 $(srcdir)/support/rrsync $(DESTDIR)$(bindir); \
+	    if test -f rrsync.1; then $(INSTALLMAN) -m 644 rrsync.1 $(DESTDIR)$(mandir)/man1; fi; \
+	fi
 
 install-ssl-daemon: stunnel-rsyncd.conf
 	-$(MKDIR_P) $(DESTDIR)/etc/stunnel
@@ -247,7 +252,7 @@ proto.h-tstamp: $(srcdir)/*.c $(srcdir)/lib/compat.c daemon-parm.h
 	$(AWK) -f $(srcdir)/mkproto.awk $(srcdir)/*.c $(srcdir)/lib/compat.c daemon-parm.h
 
 .PHONY: man
-man: rsync.1 rsync-ssl.1 rsyncd.conf.5
+man: rsync.1 rsync-ssl.1 rsyncd.conf.5 rrsync.1
 
 rsync.1: rsync.1.md md2man version.h Makefile
 	@$(srcdir)/maybe-make-man $(srcdir) rsync.1.md
@@ -258,6 +263,9 @@ rsync-ssl.1: rsync-ssl.1.md md2man version.h Makefile
 rsyncd.conf.5: rsyncd.conf.5.md md2man version.h Makefile
 	@$(srcdir)/maybe-make-man $(srcdir) rsyncd.conf.5.md
 
+rrsync.1: support/rrsync.1.md md2man Makefile
+	@$(srcdir)/maybe-make-man $(srcdir) support/rrsync.1.md
+
 .PHONY: clean
 clean: cleantests
 	rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) \
diff --git a/NEWS.md b/NEWS.md
index eaa82b39..b3002e89 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -97,9 +97,15 @@
 
  - More ASM optimizations from Shark64.
 
- - Make rrsync pass --munge-links to rsync by default to make the restricted
-   dir extra safe (with an option to turn it off if you trust your users).
-   Also updated the known options list.
+ - 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.
 
  - Work around a glibc bug where lchmod() breaks in a chroot w/o /proc mounted.
 
@@ -107,6 +113,12 @@
 
 ### PACKAGING RELATED:
 
+ - Give configure the --with-rrsync option if you want `make install` to
+   install the (now python3) rrsync script and its (new) man page.
+
+ - If the rrsync script is installed, make its package depend on python3 and
+   (suggested but not required) the python3 braceexpand lib.
+
  - When creating a package from a non-release version (w/o a git checkout), the
    packager can elect to create git-version.h and define RSYNC_GITVER to the
    string they want `--version` to output.  (The file is still auto-generated
diff --git a/configure.ac b/configure.ac
index 9e7338cf..84111de8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -136,6 +136,13 @@ if test x"$GCC" = x"yes"; then
 	CFLAGS="$CFLAGS -Wall -W"
 fi
 
+AC_ARG_WITH(rrsync,
+        AS_HELP_STRING([--with-rrsync],[also install the rrsync script and its man page]))
+if test x"$with_rrsync" != x"yes"; then
+    with_rrsync=no
+fi
+AC_SUBST(with_rrsync)
+
 AC_ARG_WITH(included-popt,
         AS_HELP_STRING([--with-included-popt],[use bundled popt library, not from system]))
 
diff --git a/maybe-make-man b/maybe-make-man
index b7f0a9f1..59f2dce4 100755
--- a/maybe-make-man
+++ b/maybe-make-man
@@ -37,4 +37,4 @@ if [ ! -f "$flagfile" ]; then
     fi
 fi
 
-"$srcdir/md2man" "$srcdir/$inname"
+"$srcdir/md2man" -s "$srcdir" "$srcdir/$inname"
diff --git a/md2man b/md2man
index fa1d2e82..fd546f19 100755
--- a/md2man
+++ b/md2man
@@ -85,7 +85,9 @@ def main():
         die('Failed to parse NAME.NUM.md out of input file:', args.mdfile)
     fi = argparse.Namespace(**fi.groupdict())
 
-    if not fi.srcdir:
+    if args.srcdir:
+        fi.srcdir = args.srcdir + '/'
+    elif not fi.srcdir:
         fi.srcdir = './'
 
     fi.title = fi.prog + '(' + fi.sect + ') man page'
@@ -105,7 +107,7 @@ def main():
         for fn in (fi.srcdir + 'version.h', 'Makefile'):
             try:
                 st = os.lstat(fn)
-            except:
+            except OSError:
                 die('Failed to find', fi.srcdir + fn)
             if not fi.mtime:
                 fi.mtime = st.st_mtime
@@ -129,6 +131,10 @@ def main():
                 if var == 'srcdir':
                     break
 
+    fi.prog_ver = 'rsync ' + env_subs['VERSION']
+    if fi.prog != 'rsync':
+        fi.prog_ver = fi.prog + ' from ' + fi.prog_ver
+
     with open(fi.fn, 'r', encoding='utf-8') as fh:
         txt = fh.read()
 
@@ -140,7 +146,7 @@ def main():
     txt = None
 
     fi.date = time.strftime('%d %b %Y', time.localtime(fi.mtime))
-    fi.man_headings = (fi.prog, fi.sect, fi.date, fi.prog + ' ' + env_subs['VERSION'], env_subs['prefix'])
+    fi.man_headings = (fi.prog, fi.sect, fi.date, fi.prog_ver, env_subs['prefix'])
 
     HtmlToManPage(fi)
 
@@ -374,6 +380,7 @@ def die(*msg):
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description='Transform a NAME.NUM.md markdown file into a NAME.NUM.html web page & a NAME.NUM man page.', add_help=False)
+    parser.add_argument('--srcdir', '-s', help='Specify the source dir if the input file is not in it.')
     parser.add_argument('--test', action='store_true', help='Test if we can parse the input w/o updating any files.')
     parser.add_argument('--debug', '-D', action='count', default=0, help='Output copious info on the html parsing. Repeat for even more.')
     parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
diff --git a/packaging/cull_options b/packaging/cull_options
index 85311c7c..d4e1c626 100755
--- a/packaging/cull_options
+++ b/packaging/cull_options
@@ -7,7 +7,7 @@ import re, argparse
 
 short_no_arg = { }
 short_with_num = { '@': 1 };
-long_opt = { # These include some extra long-args that BackupPC uses:
+long_opts = { # These include some extra long-args that BackupPC uses:
         'block-size': 1,
         'daemon': -1,
         'debug': 1,
@@ -25,6 +25,7 @@ long_opt = { # These include some extra long-args that BackupPC uses:
         'owner': 0,
         'perms': 0,
         'recursive': 0,
+        'stderr': 1,
         'times': 0,
         'write-devices': -1,
         }
@@ -49,8 +50,8 @@ def main():
             m = re.search(r'args\[ac\+\+\] = "--([^"=]+)"', line)
             if m:
                 last_long_opt = m.group(1)
-                if last_long_opt not in long_opt:
-                    long_opt[last_long_opt] = 0
+                if last_long_opt not in long_opts:
+                    long_opts[last_long_opt] = 0
                 else:
                     last_long_opt = None
                 continue
@@ -58,13 +59,13 @@ def main():
             if last_long_opt:
                 m = re.search(r'args\[ac\+\+\] = ([^["\s]+);', line)
                 if m:
-                    long_opt[last_long_opt] = 2
+                    long_opts[last_long_opt] = 2
                     last_long_opt = None
                     continue
 
             m = re.search(r'return "--([^"]+-dest)";', line)
             if m:
-                long_opt[m.group(1)] = 2
+                long_opts[m.group(1)] = 2
                 last_long_opt = None
                 continue
 
@@ -74,19 +75,18 @@ def main():
                 if not m:
                     m = re.search(r'fmt = .*: "--([^"=]+)=', line)
             if m:
-                long_opt[m.group(1)] = 1
+                long_opts[m.group(1)] = 1
                 last_long_opt = None
 
-    long_opt['files-from'] = 3
+    long_opts['files-from'] = 3
 
-    txt = """
-# These options are the only options that rsync might send to the server,
-# and only in the option format that the stock rsync produces.
+    txt = """\
+### START of options data produced by the cull_options script. ###
 
 # To disable a short-named option, add its letter to this string:
 """
 
-    txt += str_assign('short_disabled', 's') + "\n"
+    txt += str_assign('short_disabled', 'Ls') + "\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')
    
@@ -99,24 +99,24 @@ def main():
     print(txt, end='')
 
     if args.python:
-        print("long_opt = {")
+        print("long_opts = {")
         sep = ':'
     else:
         print("our %long_opt = (")
         sep = ' =>'
 
-    for opt in sorted(long_opt):
+    for opt in sorted(long_opts):
         if opt.startswith(('min-', 'max-')):
             val = 1
         else:
-            val = long_opt[opt]
+            val = long_opts[opt]
         print(' ', repr(opt) + sep, str(val) + ',')
 
     if args.python:
         print("}")
     else:
         print(");")
-    print('')
+    print("\n### END of options data produced by the cull_options script. ###")
 
 
 def str_assign(name, val, comment=None):
@@ -129,10 +129,12 @@ def str_assign(name, val, comment=None):
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description="Output culled rsync options for rrsync.", add_help=False)
     out_group = parser.add_mutually_exclusive_group()
-    out_group.add_argument('--perl', action='store_true', help="Output perl code (the default).")
-    out_group.add_argument('--python', action='store_true', help="Output python code.")
+    out_group.add_argument('--perl', action='store_true', help="Output perl code.")
+    out_group.add_argument('--python', action='store_true', help="Output python code (the default).")
     parser.add_argument('--help', '-h', action='help', help="Output this help message and exit.")
     args = parser.parse_args()
+    if not args.perl:
+        args.python = True
     main()
 
 # vim: sw=4 et
diff --git a/support/lsh b/support/lsh
index ebfe898c..40fe3d73 100755
--- a/support/lsh
+++ b/support/lsh
@@ -18,6 +18,8 @@ GetOptions(
     'rrsync=s' => \( my $rrsync_dir ),
     'ro' => \( my $rrsync_ro = '' ),
     'wo' => \( my $rrsync_wo = '' ),
+    'munge' => \( my $rrsync_munge = '' ),
+    'no-del' => \( my $rrsync_no_del = '' ),
 ) or &usage;
 &usage unless @ARGV > 1;
 
@@ -71,16 +73,12 @@ unless ($no_chdir) {
 }
 
 if ($rrsync_dir) {
-    my $cmd = '';
-    foreach (@ARGV) {
-	(my $arg = $_) =~ s/(['";|()\[\]{}\$!*?<> \t&~\\])/\\$1/g;
-	$cmd .= ' ' . $arg;
-    }
-    $cmd =~ s/^\s+//;
-    $ENV{SSH_ORIGINAL_COMMAND} = $cmd;
+    $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;
     push @cmd, $rrsync_dir;
 } else {
     push @cmd, '/bin/sh', '-c', "@ARGV";
diff --git a/support/rrsync b/support/rrsync
index 4c5dd2aa..5b43a819 100755
--- a/support/rrsync
+++ b/support/rrsync
@@ -1,282 +1,353 @@
-#!/usr/bin/env perl
-# Name: /usr/local/bin/rrsync (should also have a symlink in /usr/bin)
-# Purpose: Restricts rsync to subdirectory declared in .ssh/authorized_keys
-# Author: Joe Smith <js-cgi at inwap.com> 30-Sep-2004
-# Modified by: Wayne Davison <wayne at opencoder.net>
-use strict;
-
-use Socket;
-use Cwd 'abs_path';
-use File::Glob ':glob';
-
-# 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';
-
-my $Usage = <<EOM;
-Use 'command="$0 [-ro|-wo|-no-munge] SUBDIR"'
-        in front of lines in $ENV{HOME}/.ssh/authorized_keys
-EOM
-
-# Handle the -ro, -wo, & -no-munge options.
-our $only = '';
-our $force_munge = 1;
-while (@ARGV) {
-  if ($ARGV[0] =~ /^-([rw])o$/) {
-    my $r_or_w = $1;
-    if ($only && $only ne $r_or_w) {
-      die "$0: the -ro and -wo options conflict.\n";
-    }
-    $only = $r_or_w;
-  } elsif ($ARGV[0] eq '-no-munge') {
-    $force_munge = 0;
-  } else {
-    last;
-  }
-  shift;
-}
+#!/usr/bin/env python3
 
-our $subdir = shift;
-die "$0: No subdirectory specified\n$Usage" unless defined $subdir;
-$subdir = abs_path($subdir);
-die "$0: Restricted directory does not exist!\n" if $subdir ne '/' && !-d $subdir;
-
-# The client uses "rsync -av -e ssh src/ server:dir/", and sshd on the server
-# executes this program when .ssh/authorized_keys has 'command="..."'.
-# For example:
-# command="rrsync logs/client" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAzGhEeNlPr...
-# command="rrsync -ro results" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAmkHG1WCjC...
-#
-# Format of the environment variables set by sshd:
-# SSH_ORIGINAL_COMMAND=rsync --server          -vlogDtpr --partial . ARG # push
-# SSH_ORIGINAL_COMMAND=rsync --server --sender -vlogDtpr --partial . ARGS # pull
-# SSH_CONNECTION=client_addr client_port server_port
-
-my $command = $ENV{SSH_ORIGINAL_COMMAND};
-die "$0: Not invoked via sshd\n$Usage" unless defined $command;
-die "$0: SSH_ORIGINAL_COMMAND='$command' is not rsync\n" unless $command =~ s/^rsync\s+//;
-die "$0: --server option is not first\n" unless $command =~ /^--server\s/;
-our $am_sender = $command =~ /^--server\s+--sender\s/; # Restrictive on purpose!
-die "$0 sending to read-only server not allowed\n" if $only eq 'r' && !$am_sender;
-die "$0 reading from write-only server not allowed\n" if $only eq 'w' && $am_sender;
+# Restricts rsync to subdirectory declared in .ssh/authorized_keys.  See
+# the rrsync man page for details of how to make use of this script.
 
-### START of options data produced by the cull_options script. ###
+# NOTE: install python3 braceexpand to support brace expansion in the args!
+
+# Originally a perl script by: Joe Smith <js-cgi at inwap.com> 30-Sep-2004
+# Python version by: Wayne Davison <wayne at opencoder.net>
+
+# You may configure these 2 values to your liking.  See also the section of
+# short & long options if you want to disable any options that rsync accepts.
+RSYNC = '/usr/bin/rsync'
+LOGFILE = 'rrsync.log' # NOTE: the file must exist for a line to be appended!
+
+# The following options are mainly the options that a client rsync can send
+# to the server, and usually just in the one option format that the stock
+# rsync produces. However, there are some additional convenience options
+# added as well, and thus a few options are present in both the short and
+# long lists (such as --group, --owner, and --perms).
 
-# These options are the only options that rsync might send to the server,
-# and only in the option format that the stock rsync produces.
+# NOTE when disabling: check for both a short & long version of the option!
+
+### START of options data produced by the cull_options script. ###
 
 # To disable a short-named option, add its letter to this string:
-our $short_disabled = 's';
+short_disabled = 'Ls'
 
-our $short_no_arg = 'ACDEHIJKLNORSUWXbcdgklmnopqrstuvxyz'; # DO NOT REMOVE ANY
-our $short_with_num = '@B'; # DO NOT REMOVE ANY
+short_no_arg = 'ACDEHIJKLNORSUWXbcdgklmnopqrstuvxyz' # DO NOT REMOVE ANY
+short_with_num = '@B' # DO NOT REMOVE ANY
 
 # To disable a long-named option, change its value to a -1.  The values mean:
 # 0 = the option has no arg; 1 = the arg doesn't need any checking; 2 = only
 # check the arg when receiving; and 3 = always check the arg.
-our %long_opt = (
-  'append' => 0,
-  'backup-dir' => 2,
-  'block-size' => 1,
-  'bwlimit' => 1,
-  'checksum-choice' => 1,
-  'checksum-seed' => 1,
-  'compare-dest' => 2,
-  'compress-choice' => 1,
-  'compress-level' => 1,
-  'copy-dest' => 2,
-  'copy-unsafe-links' => 0,
-  'daemon' => -1,
-  'debug' => 1,
-  'delay-updates' => 0,
-  'delete' => 0,
-  'delete-after' => 0,
-  'delete-before' => 0,
-  'delete-delay' => 0,
-  'delete-during' => 0,
-  'delete-excluded' => 0,
-  'delete-missing-args' => 0,
-  'existing' => 0,
-  'fake-super' => 0,
-  'files-from' => 3,
-  'force' => 0,
-  'from0' => 0,
-  'fsync' => 2,
-  'fuzzy' => 0,
-  'group' => 0,
-  'groupmap' => 1,
-  'hard-links' => 0,
-  'iconv' => 1,
-  'ignore-errors' => 0,
-  'ignore-existing' => 0,
-  'ignore-missing-args' => 0,
-  'ignore-times' => 0,
-  'info' => 1,
-  'inplace' => 0,
-  'link-dest' => 2,
-  'links' => 0,
-  'list-only' => 0,
-  'log-file' => 3,
-  'log-format' => 1,
-  'max-alloc' => 1,
-  'max-delete' => 1,
-  'max-size' => 1,
-  'min-size' => 1,
-  'mkpath' => 0,
-  'modify-window' => 1,
-  'msgs2stderr' => 0,
-  'munge-links' => 0,
-  'new-compress' => 0,
-  'no-W' => 0,
-  'no-implied-dirs' => 0,
-  'no-msgs2stderr' => 0,
-  'no-munge-links' => -1,
-  'no-r' => 0,
-  'no-relative' => 0,
-  'no-specials' => 0,
-  'numeric-ids' => 0,
-  'old-compress' => 0,
-  'one-file-system' => 0,
-  'only-write-batch' => 1,
-  'open-noatime' => 0,
-  'owner' => 0,
-  'partial' => 0,
-  'partial-dir' => 2,
-  'perms' => 0,
-  'preallocate' => 0,
-  'recursive' => 0,
-  'remove-sent-files' => 0,
-  'remove-source-files' => 0,
-  'safe-links' => 0,
-  'sender' => 0,
-  'server' => 0,
-  'size-only' => 0,
-  'skip-compress' => 1,
-  'specials' => 0,
-  'stats' => 0,
-  'suffix' => 1,
-  'super' => 0,
-  'temp-dir' => 2,
-  'timeout' => 1,
-  'times' => 0,
-  'use-qsort' => 0,


-- 
The rsync repository.



More information about the rsync-cvs mailing list