mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-10-21 05:14:15 -04:00
Add the ability to negate matches for the daemon's "refuse options".
This commit is contained in:
1 parent
e448d31d63
commit
b3a1a0ca9d
3 files changed
+190
-81
No files matched your search
@@ -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 = "";
|
||||
|
||||
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;
|
||||
}
|
||||
/* 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 (*bp == ' ') bp++;
|
||||
if (!*bp)
|
||||
while (*ref == ' ') ref++;
|
||||
if (!*ref)
|
||||
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);
|
||||
}
|
||||
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':
|
||||
|
||||
+57
-12
@@ -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
|
||||
|
||||
Reference in new issue
Block a user