diff options
author | Wayne Davison <wayne@opencoder.net> | 2020-05-17 21:29:11 -0700 |
---|---|---|
committer | Wayne Davison <wayne@opencoder.net> | 2020-05-17 21:29:11 -0700 |
commit | b3a1a0ca9dc4af481a934ba65b5cedb4f54731ca (patch) | |
tree | 26edf65efd142190dc226e852e2e43926d19bc7b | |
parent | e448d31d6386ddb7686a83cbc85457e5f78cdb34 (diff) | |
download | rsync-b3a1a0ca9dc4af481a934ba65b5cedb4f54731ca.tar.gz |
Add the ability to negate matches for the daemon's "refuse options".
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | options.c | 199 | ||||
-rw-r--r-- | rsyncd.conf.yo | 69 |
3 files changed, 190 insertions, 81 deletions
@@ -48,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 @@ -1143,75 +1143,145 @@ void option_error(void) } +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; + } } } @@ -1327,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; @@ -1337,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) @@ -1555,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/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). +without the former, instead refuse "delete-*" as that refuses all the delete +modes without affecting bf(--remove-source-files). (Keep in mind that the +client's bf(--delete) option typically enables bf(--delete-during).) -When an option is refused, the daemon prints an error message and exits. -To prevent all compression when serving files, -you can use "dont compress = *" (see below) -instead of "refuse options = compress" to avoid returning an error to a -client that requests compression. +When un-refusing delete options, you should either specify "!delete*" (to +accept all delete options) or specify a limited set that includes "delete", +such as: + +verb( refuse options = * !a !delete !delete-during) + +... whereas this accepts any delete option except bf(--delete-after): + +verb( refuse options = * !a !delete* delete-after) + +A note on refusing "compress" -- it is better to set the "dont compress" daemon +option to "*" because that disables compression silently instead of returning +an error that forces the client to remove the bf(-z) option. + +If you are un-refusing the compress option, you probably want to match +"!compress*" so that you also allow the bf(--compress-level) option. + +Finally, the "write-devices" option is refused by default, but can be +explicitly enabled with "!write-devices". dit(bf(dont compress)) This parameter allows you to select filenames based on wildcard patterns that should not be compressed |