This adds support for whitelisting the acceptable options in the
"refuse options" setting in rsyncd.conf. It introduces "!" as a
special option string that refuses most options and interprets
any following strings as patterns of options to allow.

For example, to allow only verbose and archive:

  refuse options = ! verbose archive

The "!" does't refuse no-iconv, but you can still refuse it and
use a whitelist if you want:

  refuse options = no-iconv ! verbose archive

It's not finished (needs tests and doc) I just wanted to see if
there'd be any interest in merging something of this shape
before I put more work into it.

My use case is setting up a restricted trust relationship by
allowing host A to ssh to host B with a forced command of
"rsync --server --daemon --config=/path/to/rsyncd.conf ." and
configuring the restictions in rsyncd.conf. I know what options
I want to use, it'd be nice to enforce that on the server side
without listing every other option in "refuse options".


---
 options.c | 114 +++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 82 insertions(+), 32 deletions(-)

diff --git a/options.c b/options.c
index e5b0cb68..02d1b174 100644
--- a/options.c
+++ b/options.c
@@ -1133,39 +1133,101 @@ static void set_refuse_options(char *bp)
 {
        struct poptOption *op;
        char *cp, shortname[2];
-       int is_wild, found_match;
+       int is_wild, found_match, whitelist_mode, archive_whitelisted;
 
        shortname[1] = '\0';
+       whitelist_mode = 0;
+       archive_whitelisted = 0;
 
+       /* We flag options for refusal by abusing the "descrip" field of
+        * struct poptOption (which we don't use) to temporarily store
+        * a refuse flag. Refused options may be un-refused later in the
+        * loop if whitelist mode is triggered. */
        while (1) {
                while (*bp == ' ') bp++;
                if (!*bp)
                        break;
                if ((cp = strchr(bp, ' ')) != NULL)
                        *cp= '\0';
-               is_wild = strpbrk(bp, "*?[") != NULL;
-               found_match = 0;
+               if (!strcmp(bp, "!")) {
+                       whitelist_mode = 1;
+                       for (op = long_options; ; op++) {
+                               *shortname = op->shortName;
+                               if (!op->longName && !*shortname)
+                                       break;
+                               if (*shortname != 'e' && (!op->longName ||(
+                                   strcmp("server", op->longName) &&
+                                   strcmp("sender", op->longName) &&
+                                   strcmp("no-iconv", op->longName))))
+                                       op->descrip = "refused";
+                       }
+               } else {
+                       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))) {
+                                       op->descrip = whitelist_mode ? 0 : 
"refused";
+                                       found_match = 1;
+                                       if (whitelist_mode && *shortname == 'a')
+                                               archive_whitelisted = 1;
+                                       if (!is_wild)
+                                               break;
+                               }
+                       }
+                       if (!found_match) {
+                               rprintf(FLOG, "No match for refuse-options 
string \"%s\"\n",
+                                       bp);
+                       }
+               }
+               if (!cp)
+                       break;
+               *cp = ' ';
+               bp = cp + 1;
+       }
+
+       /* For the --archive option, the client sends the implied options
+        * explicitly to the server, so if --archive is whitelisted then
+        * we must individually whitelist the implied options as well. */
+       if (archive_whitelisted) {
                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':
+                       if (*shortname && strchr("rdlptgoD", *shortname))
+                               op->descrip = 0;
+               }
+       }
+
+       /* The actual mechanics of marking options for refusal, now
+        * that the set of refused options is finalized. */
+       for (op = long_options; ; op++) {
+               *shortname = op->shortName;
+               if (!op->longName && !*shortname)
+                       break;
+               if (op->descrip) {
+                       op->descrip = 0;
+                       if (op->argInfo == POPT_ARG_VAL)
+                               op->argInfo = POPT_ARG_NONE;
+                       op->val = (op - long_options) + OPT_REFUSED_BASE;
+                       /* 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':
+                               if (!whitelist_mode)
                                        refused_compress = op->val;
-                                       break;
-                               case '\0':
+                               break;
+                       case '\0':
+                               if (wildmatch("no-iconv", op->longName))
+                                       refused_no_iconv = op->val;
+                               else if (!whitelist_mode) {
                                        if (wildmatch("delete", op->longName))
                                                refused_delete = op->val;
                                        else if (wildmatch("delete-before", 
op->longName))
@@ -1178,22 +1240,10 @@ static void set_refuse_options(char *bp)
                                                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;
+                               break;
                        }
                }
-               if (!found_match) {
-                       rprintf(FLOG, "No match for refuse-options string 
\"%s\"\n",
-                               bp);
-               }
-               if (!cp)
-                       break;
-               *cp = ' ';
-               bp = cp + 1;
        }
 }
 
-- 
2.17.1

-- 
Please use reply-all for most replies to avoid omitting the mailing list.
To unsubscribe or change options: https://lists.samba.org/mailman/listinfo/rsync
Before posting, read: http://www.catb.org/~esr/faqs/smart-questions.html

Reply via email to