[draft PATCH] whitelist support for refuse options
Nick Cleaton
nick at cleaton.net
Sun Feb 9 10:29:10 UTC 2020
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
More information about the rsync
mailing list