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