The branch, master has been updated
       via  b3a1a0ca Add the ability to negate matches for the daemon's "refuse 
options".
       via  e448d31d Need to flush early errors before we exit.
       via  37de4897 Some pkglib improvements.
      from  08f955e1 A couple more manpage fixes.

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


- Log -----------------------------------------------------------------
commit b3a1a0ca9dc4af481a934ba65b5cedb4f54731ca
Author: Wayne Davison <wa...@opencoder.net>
Date:   Sun May 17 21:29:11 2020 -0700

    Add the ability to negate matches for the daemon's "refuse options".

commit e448d31d6386ddb7686a83cbc85457e5f78cdb34
Author: Wayne Davison <wa...@opencoder.net>
Date:   Sun May 17 20:21:45 2020 -0700

    Need to flush early errors before we exit.

commit 37de48979ea69733343f5f307558293007869c8a
Author: Wayne Davison <wa...@opencoder.net>
Date:   Sun May 17 14:39:18 2020 -0700

    Some pkglib improvements.

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

Summary of changes:
 NEWS                    |   6 ++
 clientserver.c          |   1 +
 main.c                  |   2 -
 options.c               | 200 +++++++++++++++++++++++++++++++-----------------
 packaging/pkglib.py     |  82 +++++++++++++-------
 packaging/release-rsync |   4 +-
 rsyncd.conf.yo          |  69 ++++++++++++++---
 7 files changed, 253 insertions(+), 111 deletions(-)


Changeset truncated at 500 lines:

diff --git a/NEWS b/NEWS
index d2043bae..d29ea3c4 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,9 @@ Changes since 3.1.3:
     - Fixed an issue with --remove-source-files not removing a source symlink
       when combined with --copy-links.
 
+    - Fixed a bug where the daemon would fail to write early fatal error
+      messages to the client, such as refused or unknown command-line options.
+
     - Fixed the block-size validation logic when dealing with older protocols.
 
     - Some rrsync fixes and enhancements to handle the latest options.
@@ -45,6 +48,9 @@ Changes since 3.1.3:
       script. Its value is the user-specified port number (set via --port or an
       rsync:// URL) or 0 if the user didn't override the port.
 
+    - Added negated matching to the daemon's "refuse options" setting by using
+      match strings that start with a "!" (such as "!compress*").
+
     - Added a status output based on a signal (via both SIGINFO & SIGVTALRM).
 
     - Added a --copy-as=USER option to give some extra security to root-run
diff --git a/clientserver.c b/clientserver.c
index 3970f507..e5a631a3 100644
--- a/clientserver.c
+++ b/clientserver.c
@@ -965,6 +965,7 @@ static int rsync_module(int f_in, int f_out, int i, const 
char *addr, const char
                        }
                        if (*err_msg)
                                rprintf(FERROR, "%s\n", err_msg);
+                       io_flush(FULL_FLUSH);
                } else
                        option_error();
                msleep(400);
diff --git a/main.c b/main.c
index 3b37796d..ca96270a 100644
--- a/main.c
+++ b/main.c
@@ -1700,8 +1700,6 @@ int main(int argc,char *argv[])
 #endif
 
        if (!parse_arguments(&argc, (const char ***) &argv)) {
-               /* FIXME: We ought to call the same error-handling
-                * code here, rather than relying on getopt. */
                option_error();
                exit_cleanup(RERR_SYNTAX);
        }
diff --git a/options.c b/options.c
index 66854ca0..ebf31a60 100644
--- a/options.c
+++ b/options.c
@@ -1138,79 +1138,150 @@ void option_error(void)
        }
 
        rprintf(FERROR, RSYNC_NAME ": %s", err_buf);
+       io_flush(FULL_FLUSH);
        msleep(20);
 }
 
 
+static void set_one_refuse_option(int negated, const char *ref, const struct 
poptOption *list_end)
+{
+       struct poptOption *op;
+       char shortName[2];
+       int is_wild = strpbrk(ref, "*?[") != NULL;
+       int found_match = 0;
+
+       shortName[1] = '\0';
+
+       if (strcmp("a", ref) == 0 || strcmp("archive", ref) == 0) {
+               ref = "[ardlptgoD]";
+               is_wild = 1;
+       }
+
+       for (op = long_options; op != list_end; op++) {
+               *shortName = op->shortName;
+               if ((op->longName && wildmatch(ref, op->longName))
+                || (*shortName && wildmatch(ref, shortName))) {
+                       if (*op->descrip == 'a' || *op->descrip == 'r')
+                               op->descrip = negated ? "accepted" : "refused";
+                       else if (!is_wild)
+                               op->descrip = negated ? "ACCEPTED" : "REFUSED";
+                       found_match = 1;
+                       if (!is_wild)
+                               break;
+               }
+       }
+
+       if (!found_match)
+               rprintf(FLOG, "No match for refuse-options string \"%s\"\n", 
ref);
+}
+
+
 /**
  * Tweak the option table to disable all options that the rsyncd.conf
  * file has told us to refuse.
  **/
-static void set_refuse_options(char *bp)
+static void set_refuse_options(void)
 {
-       struct poptOption *op;
-       char *cp, shortname[2];
-       int is_wild, found_match;
+       struct poptOption *op, *list_end = NULL;
+       char *cp, *ref = lp_refuse_options(module_id);
+       int negated;
 
-       shortname[1] = '\0';
+       if (!ref)
+               ref = "";
 
-       while (1) {
-               while (*bp == ' ') bp++;
-               if (!*bp)
+       if (!*ref && !am_daemon) /* A simple optimization */
+               return;
+
+       /* We abuse the descrip field in poptOption to make it easy to flag 
which options
+        * are refused (since we don't use it otherwise).  Start by marking all 
options
+        * as accepted except for some that are marked as ACCEPTED 
(non-wild-matched). */
+       for (op = long_options; ; op++) {
+               const char *longName = op->longName ? op->longName : "";
+               if (!op->longName && !op->shortName) {
+                       list_end = op;
                        break;
-               if ((cp = strchr(bp, ' ')) != NULL)
-                       *cp= '\0';
-               is_wild = strpbrk(bp, "*?[") != NULL;
-               found_match = 0;
-               for (op = long_options; ; op++) {
-                       *shortname = op->shortName;
-                       if (!op->longName && !*shortname)
-                               break;
-                       if ((op->longName && wildmatch(bp, op->longName))
-                           || (*shortname && wildmatch(bp, shortname))) {
-                               if (op->argInfo == POPT_ARG_VAL)
-                                       op->argInfo = POPT_ARG_NONE;
-                               op->val = (op - long_options) + 
OPT_REFUSED_BASE;
-                               found_match = 1;
-                               /* These flags are set to let us easily check
-                                * an implied option later in the code. */
-                               switch (*shortname) {
-                               case 'r': case 'd': case 'l': case 'p':
-                               case 't': case 'g': case 'o': case 'D':
-                                       refused_archive_part = op->val;
-                                       break;
-                               case 'z':
-                                       refused_compress = op->val;
-                                       break;
-                               case '\0':
-                                       if (wildmatch("delete", op->longName))
-                                               refused_delete = op->val;
-                                       else if (wildmatch("delete-before", 
op->longName))
-                                               refused_delete_before = op->val;
-                                       else if (wildmatch("delete-during", 
op->longName))
-                                               refused_delete_during = op->val;
-                                       else if (wildmatch("partial", 
op->longName))
-                                               refused_partial = op->val;
-                                       else if (wildmatch("progress", 
op->longName))
-                                               refused_progress = op->val;
-                                       else if (wildmatch("inplace", 
op->longName))
-                                               refused_inplace = op->val;
-                                       else if (wildmatch("no-iconv", 
op->longName))
-                                               refused_no_iconv = op->val;
-                                       break;
-                               }
-                               if (!is_wild)
-                                       break;
-                       }
-               }
-               if (!found_match) {
-                       rprintf(FLOG, "No match for refuse-options string 
\"%s\"\n",
-                               bp);
                }
+               /* These options are protected from wild-card matching, but the 
user is free to
+                * shoot themselves in the foot if they specify the option 
explicitly. */
+               if (op->shortName == 'e'
+                || op->shortName == '0' /* --from0 just modifies --files-from, 
so refuse that instead (or not) */
+                || op->shortName == 's' /* --protect-args is always OK */
+                || op->shortName == 'n' /* --dry-run is always OK */
+                || strcmp("server", longName) == 0
+                || strcmp("sender", longName) == 0
+                || strcmp("iconv", longName) == 0
+                || strcmp("no-iconv", longName) == 0
+                || strcmp("checksum-seed", longName) == 0
+                || strcmp("write-devices", longName) == 0 /* disable 
wild-match (it gets refused below) */
+                || strcmp("log-format", longName) == 0)
+                       op->descrip = "ACCEPTED";
+               else
+                       op->descrip = "accepted";
+       }
+       assert(list_end != NULL);
+
+       if (am_daemon) /* Refused by default, but can be accepted via 
"!write-devices" */
+               set_one_refuse_option(0, "write-devices", list_end);
+
+       while (1) {
+               while (*ref == ' ') ref++;
+               if (!*ref)
+                       break;
+               if ((cp = strchr(ref, ' ')) != NULL)
+                       *cp = '\0';
+               negated = *ref == '!';
+               if (negated && ref[1])
+                       ref++;
+               set_one_refuse_option(negated, ref, list_end);
                if (!cp)
                        break;
                *cp = ' ';
-               bp = cp + 1;
+               ref = cp + 1;
+       }
+
+       if (am_daemon) {
+#ifdef ICONV_OPTION
+               if (!*lp_charset(module_id))
+                       set_one_refuse_option(0, "iconv", list_end);
+#endif
+               set_one_refuse_option(0, "log-file", list_end);
+       }
+
+       /* Now we use the descrip values to actually mark the options for 
refusal. */
+       for (op = long_options; op != list_end; op++) {
+               int refused = *op->descrip == 'r' || *op->descrip == 'R';
+               op->descrip = NULL;
+               if (!refused)
+                       continue;
+               if (op->argInfo == POPT_ARG_VAL)
+                       op->argInfo = POPT_ARG_NONE;
+               op->val = (op - long_options) + OPT_REFUSED_BASE;
+               /* The following flags are set to let us easily check an 
implied option later in the code. */
+               switch (op->shortName) {
+               case 'r': case 'd': case 'l': case 'p':
+               case 't': case 'g': case 'o': case 'D':
+                       refused_archive_part = op->val;
+                       break;
+               case 'z':
+                       refused_compress = op->val;
+                       break;
+               case '\0':
+                       if (strcmp("delete", op->longName) == 0)
+                               refused_delete = op->val;
+                       else if (strcmp("delete-before", op->longName) == 0)
+                               refused_delete_before = op->val;
+                       else if (strcmp("delete-during", op->longName) == 0)
+                               refused_delete_during = op->val;
+                       else if (strcmp("partial", op->longName) == 0)
+                               refused_partial = op->val;
+                       else if (strcmp("progress", op->longName) == 0)
+                               refused_progress = op->val;
+                       else if (strcmp("inplace", op->longName) == 0)
+                               refused_inplace = op->val;
+                       else if (strcmp("no-iconv", op->longName) == 0)
+                               refused_no_iconv = op->val;
+                       break;
+               }
        }
 }
 
@@ -1326,7 +1397,6 @@ static void popt_unalias(poptContext con, const char *opt)
 int parse_arguments(int *argc_p, const char ***argv_p)
 {
        static poptContext pc;
-       char *ref = lp_refuse_options(module_id);
        const char *arg, **argv = *argv_p;
        int argc = *argc_p;
        int opt;
@@ -1336,16 +1406,8 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                strlcpy(err_buf, "argc is zero!\n", sizeof err_buf);
                return 0;
        }
-       if (ref && *ref)
-               set_refuse_options(ref);
-       if (am_daemon) {
-               set_refuse_options("log-file*");
-#ifdef ICONV_OPTION
-               if (!*lp_charset(module_id))
-                       set_refuse_options("iconv");
-#endif
-               set_refuse_options("write-devices");
-       }
+
+       set_refuse_options();
 
 #ifdef ICONV_OPTION
        if (!am_daemon && protect_args <= 0 && (arg = getenv("RSYNC_ICONV")) != 
NULL && *arg)
@@ -1554,7 +1616,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 
                case 'U':
                        if (++preserve_atimes > 1)
-                           open_noatime = 1;
+                               open_noatime = 1;
                        break;
 
                case 'v':
diff --git a/packaging/pkglib.py b/packaging/pkglib.py
index cf50f1a9..067c5c8e 100644
--- a/packaging/pkglib.py
+++ b/packaging/pkglib.py
@@ -3,6 +3,8 @@ import os, sys, re, subprocess
 # This python3 library provides a few helpful routines that are
 # used by the latest packaging scripts.
 
+default_encoding = 'utf-8'
+
 # Output the msg args to stderr.  Accepts all the args that print() accepts.
 def warn(*msg):
     print(*msg, file=sys.stderr)
@@ -15,54 +17,82 @@ def die(*msg):
     sys.exit(1)
 
 
-def _tweak_opts(cmd, opts):
+# Set this to an encoding name or set it to None to avoid the default encoding 
idiom.
+def set_default_encoding(enc):
+    default_encoding = enc
+
+
+# Set shell=True if the cmd is a string; sets a default encoding unless 
raw=True was specified.
+def _tweak_opts(cmd, opts, **maybe_set):
+    # This sets any maybe_set value that isn't already set AND creates a copy 
of opts for us.
+    opts = {**maybe_set, **opts}
+
     if type(cmd) == str:
-        opts['shell'] = True
-    if not 'encoding' in opts:
-        if opts.get('raw', False):
-            del opts['raw']
-        else:
-            opts['encoding'] = 'utf-8'
+        opts = {'shell': True, **opts}
+
+    want_raw = opts.pop('raw', False)
+    if default_encoding and not want_raw:
+        opts = {'encoding': default_encoding, **opts}
+
+    capture = opts.pop('capture', None)
+    if capture:
+        if capture == 'stdout':
+            opts = {'stdout': subprocess.PIPE, **opts}
+        elif capture == 'stderr':
+            opts = {'stderr': subprocess.PIPE, **opts}
+        elif capture == 'output':
+            opts = {'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE, 
**opts}
+        elif capture == 'combined':
+            opts = {'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT, 
**opts}
+
+    discard = opts.pop('discard', None)
+    if discard:
+        # We DO want to override any already set stdout|stderr values (unlike 
above).
+        if discard == 'stdout' or discard == 'output':
+            opts['stdout'] = subprocess.DEVNULL
+        if discard == 'stderr' or discard == 'output':
+            opts['stderr'] = subprocess.DEVNULL
+
+    return opts
 
 
 # This does a normal subprocess.run() with some auto-args added to make life 
easier.
 def cmd_run(cmd, **opts):
-    _tweak_opts(cmd, opts)
-    return subprocess.run(cmd, **opts)
+    return subprocess.run(cmd, **_tweak_opts(cmd, opts))
 
 
-# Works like cmd_run() with a default check=True specified for you.
+# Like cmd_run() with a default check=True specified.
 def cmd_chk(cmd, **opts):
-    return cmd_run(cmd, check=True, **opts)
+    return subprocess.run(cmd, **_tweak_opts(cmd, opts, check=True))
 
 
-# Captures stdout & stderr together in a string and returns the (output, 
return-code) tuple.
-def cmd_txt(cmd, **opts):
-    opts['stdout'] = subprocess.PIPE
-    opts['stderr'] = subprocess.STDOUT
-    _tweak_opts(cmd, opts)
-    proc = subprocess.Popen(cmd, **opts)
+# Capture stdout in a string and return the (output, return_code) tuple.
+# Use capture='combined' opt to get both stdout and stderr together.
+def cmd_txt_status(cmd, **opts):
+    proc = subprocess.Popen(cmd, **_tweak_opts(cmd, opts, capture='stdout'))
     out = proc.communicate()[0]
     return (out, proc.returncode)
 
 
-# Captures stdout & stderr together in a string and returns the output if the 
command has a 0
-# return-code. Otherwise it throws an exception that indicates the return code 
and all the
-# captured output.
+# Like cmd_txt_status() but just return the output.
+def cmd_txt(cmd, **opts):
+    return cmd_txt_status(cmd, **opts)[0]
+
+
+# Capture stdout in a string and return the output if the command has a 0 
return code.
+# Otherwise it throws an exception that indicates the return code and the 
output.
 def cmd_txt_chk(cmd, **opts):
-    out, rc = cmd_txt(cmd, **opts)
+    out, rc = cmd_txt_status(cmd, **opts)
     if rc != 0:
         cmd_err = f'Command "{cmd}" returned non-zero exit status "{rc}" and 
output:\n{out}'
         raise Exception(cmd_err)
     return out
 
 
-# Starts a piped-output command of just stdout (by default) and leaves it up 
to you to do
-# any incremental reading of the output and to call communicate() on the 
returned object.
+# Starts a piped-output command of stdout (by default) and leaves it up to you 
to read
+# the output and call communicate() on the returned object.
 def cmd_pipe(cmd, **opts):
-    opts['stdout'] = subprocess.PIPE
-    _tweak_opts(cmd, opts)
-    return subprocess.Popen(cmd, **opts)
+    return subprocess.Popen(cmd, **_tweak_opts(cmd, opts, capture='stdout'))
 
 
 # Runs a "git status" command and dies if the checkout is not clean (the
diff --git a/packaging/release-rsync b/packaging/release-rsync
index b0e55387..88ed34ec 100755
--- a/packaging/release-rsync
+++ b/packaging/release-rsync
@@ -298,7 +298,7 @@ About to:
         os.umask(oldmask)
         os.environ['GPG_PASSFILE'] = passfile
 
-        out = cmd_txt(f"git tag -s -m 'Version {version}.' {v_ver}")[0]
+        out = cmd_txt(f"git tag -s -m 'Version {version}.' {v_ver}")
         print(out, end='')
         if 'bad passphrase' in out:
             continue
@@ -306,7 +306,7 @@ About to:
             die('Aborting')
 
         if os.path.isdir('patches/.git'):
-            out = cmd_txt(f"cd patches && git tag -s -m 'Version {version}.' 
{v_ver}")[0]
+            out = cmd_txt(f"cd patches && git tag -s -m 'Version {version}.' 
{v_ver}")
             print(out, end='')
             if 'bad passphrase' in out or 'failed' in out:
                 die('Aborting')
diff --git a/rsyncd.conf.yo b/rsyncd.conf.yo
index 8f004ae6..c3bc3dd1 100644
--- a/rsyncd.conf.yo
+++ b/rsyncd.conf.yo
@@ -735,28 +735,73 @@ is specified in seconds. A value of zero means no timeout 
and is the
 default. A good choice for anonymous rsync daemons may be 600 (giving
 a 10 minute timeout).
 
-dit(bf(refuse options)) This parameter allows you to
-specify a space-separated list of rsync command line options that will
-be refused by your rsync daemon.
+dit(bf(refuse options)) This parameter allows you to specify a space-separated
+list of rsync command line options that will be refused by your rsync daemon.
 You may specify the full option name, its one-letter abbreviation, or a
-wild-card string that matches multiple options.
+wild-card string that matches multiple options. Beginning in 3.2.0, you can
+also negate a match term by starting it with a "!".
+
+When an option is refused, the daemon prints an error message and exits.
+
 For example, this would refuse bf(--checksum) (bf(-c)) and all the various
 delete options:
 
-quote(tt(    refuse options = c delete))
+verb(    refuse options = c delete)
 
 The reason the above refuses all delete options is that the options imply
 bf(--delete), and implied options are refused just like explicit options.
+
+The use of a negated match allows you to fine-tune your refusals after a
+wild-card, such as this:
+
+verb(    refuse options = delete-* !delete-during)
+
+Negated matching can also turn your list of refused options into a list of
+accepted options. To do this, begin the list with a "*" (to refuse all options)
+and then specify one or more negated matches to allow.  For example:
+
+verb(    refuse options = * !a !v !compress*)
+
+Don't worry that the "*" will refuse certain vital options such as
+bf(--server), bf(--no-iconv), bf(--protect-args), etc. These important options
+are not matched by a wild-card, so they must be overridden by their exact name.
+For instance, if you're forcing iconv transfers you could use something like
+this:
+
+verb(    refuse options = * no-iconv !a !v)
+
+As an additional aid (beginning in 3.2.0), refusing (or "!refusing") the "a" or
+"archive"  option also affects all the options that the bf(--archive) option
+implies (bf(-rdlptgoD)), but only if the option  is matched explicitly (not
+using a wildcard). If you want to do something tricky, you can use "archive*"
+to avoid this side-effect, but keep in mind that no normal rsync client ever
+sends the actual archive option to the server.
+
 As an additional safety feature, the refusal of "delete" also refuses
 bf(remove-source-files) when the daemon is the sender; if you want the latter
-without the former, instead refuse "delete-*" -- that refuses all the
-delete modes without affecting bf(--remove-source-files).


-- 
The rsync repository.

_______________________________________________
rsync-cvs mailing list
rsync-cvs@lists.samba.org
https://lists.samba.org/mailman/listinfo/rsync-cvs

Reply via email to