extern int module_id;
extern int local_server;
extern int sanitize_paths;
+extern int trust_sender_args;
+extern int trust_sender_filter;
extern unsigned int module_dirlen;
extern filter_rule_list filter_list;
extern filter_rule_list daemon_filter_list;
int keep_dirlinks = 0;
int copy_dirlinks = 0;
int copy_links = 0;
+int copy_devices = 0;
int write_devices = 0;
int preserve_links = 0;
int preserve_hard_links = 0;
int preserve_crtimes = 0;
int omit_dir_times = 0;
int omit_link_times = 0;
+int trust_sender = 0;
int update_only = 0;
int open_noatime = 0;
int cvs_exclude = 0;
char *filesfrom_host = NULL;
int eol_nulls = 0;
int protect_args = -1;
+int old_style_args = -1;
int human_readable = 1;
int recurse = 0;
int mkpath_dest_arg = 0;
DEBUG_WORD(DELTASUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"),
DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"),
DEBUG_WORD(EXIT, W_CLI|W_SRV, "Debug exit events (levels 1-3)"),
- DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-2)"),
+ DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-3)"),
DEBUG_WORD(FLIST, W_SND|W_REC, "Debug file-list operations (levels 1-4)"),
DEBUG_WORD(FUZZY, W_REC, "Debug fuzzy scoring (levels 1-2)"),
DEBUG_WORD(GENR, W_REC, "Debug generator functions"),
OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR,
- OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS,
+ OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, OPT_OLD_ARGS,
OPT_STOP_AFTER, OPT_STOP_AT,
OPT_REFUSED_BASE = 9000};
{"no-D", 0, POPT_ARG_NONE, 0, OPT_NO_D, 0, 0 },
{"devices", 0, POPT_ARG_VAL, &preserve_devices, 1, 0, 0 },
{"no-devices", 0, POPT_ARG_VAL, &preserve_devices, 0, 0, 0 },
+ {"copy-devices", 0, POPT_ARG_NONE, ©_devices, 0, 0, 0 },
{"write-devices", 0, POPT_ARG_VAL, &write_devices, 1, 0, 0 },
{"no-write-devices", 0, POPT_ARG_VAL, &write_devices, 0, 0, 0 },
{"specials", 0, POPT_ARG_VAL, &preserve_specials, 1, 0, 0 },
{"files-from", 0, POPT_ARG_STRING, &files_from, 0, 0, 0 },
{"from0", '0', POPT_ARG_VAL, &eol_nulls, 1, 0, 0},
{"no-from0", 0, POPT_ARG_VAL, &eol_nulls, 0, 0, 0},
- {"protect-args", 's', POPT_ARG_VAL, &protect_args, 1, 0, 0},
+ {"old-args", 0, POPT_ARG_NONE, 0, OPT_OLD_ARGS, 0, 0},
+ {"no-old-args", 0, POPT_ARG_VAL, &old_style_args, 0, 0, 0},
+ {"secluded-args", 's', POPT_ARG_VAL, &protect_args, 1, 0, 0},
+ {"no-secluded-args", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0},
+ {"protect-args", 0, POPT_ARG_VAL, &protect_args, 1, 0, 0},
{"no-protect-args", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0},
{"no-s", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0},
+ {"trust-sender", 0, POPT_ARG_VAL, &trust_sender, 1, 0, 0},
{"numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 1, 0, 0 },
{"no-numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 0, 0, 0 },
{"usermap", 0, POPT_ARG_STRING, 0, OPT_USERMAP, 0, 0 },
if (!am_daemon
|| op->shortName == 'e' /* Required for compatibility flags */
|| op->shortName == '0' /* --from0 just modifies --files-from, so refuse that instead (or not) */
- || op->shortName == 's' /* --protect-args is always OK */
+ || op->shortName == 's' /* --secluded-args is always OK */
|| op->shortName == 'n' /* --dry-run is always OK */
|| strcmp("iconv", longName) == 0
|| strcmp("no-iconv", longName) == 0
|| strcmp("checksum-seed", longName) == 0
+ || strcmp("copy-devices", longName) == 0 /* disable wild-match (it gets refused below) */
|| strcmp("write-devices", longName) == 0 /* disable wild-match (it gets refused below) */
|| strcmp("log-format", longName) == 0 /* aka out-format (NOT log-file-format) */
|| strcmp("sender", longName) == 0
assert(list_end != NULL);
if (am_daemon) { /* Refused by default, but can be accepted via a negated exact match. */
+ parse_one_refuse_match(0, "copy-devices", list_end);
parse_one_refuse_match(0, "write-devices", list_end);
}
compress_choice = NULL;
break;
+ case OPT_OLD_ARGS:
+ if (old_style_args <= 0)
+ old_style_args = 1;
+ else
+ old_style_args++;
+ break;
+
case 'M':
arg = poptGetOptArg(pc);
if (*arg != '-') {
saw_stderr_opt = 1;
if (version_opt_cnt) {
- print_rsync_version(FINFO);
+ print_rsync_version(version_opt_cnt > 1 && !am_server ? FNONE : FINFO);
exit_cleanup(0);
}
max_alloc = size;
}
+ if (old_style_args < 0) {
+ if (!am_server && protect_args <= 0 && (arg = getenv("RSYNC_OLD_ARGS")) != NULL && *arg) {
+ protect_args = 0;
+ old_style_args = atoi(arg);
+ } else
+ old_style_args = 0;
+ } else if (old_style_args) {
+ if (protect_args > 0) {
+ snprintf(err_buf, sizeof err_buf,
+ "--secluded-args conflicts with --old-args.\n");
+ return 0;
+ }
+ protect_args = 0;
+ }
+
if (protect_args < 0) {
if (am_server)
protect_args = 0;
else if ((arg = getenv("RSYNC_PROTECT_ARGS")) != NULL && *arg)
protect_args = atoi(arg) ? 1 : 0;
else {
-#ifdef RSYNC_USE_PROTECTED_ARGS
+#ifdef RSYNC_USE_SECLUDED_ARGS
protect_args = 1;
#else
protect_args = 0;
}
}
+ if (trust_sender || am_server || read_batch)
+ trust_sender_args = trust_sender_filter = 1;
+ else if (old_style_args || filesfrom_host != NULL)
+ trust_sender_args = 1;
+
am_starting_up = 0;
return 1;
}
+static char SPLIT_ARG_WHEN_OLD[1];
+
+/**
+ * Do backslash quoting of any weird chars in "arg", append the resulting
+ * string to the end of the "opt" (which gets a "=" appended if it is not
+ * an empty or NULL string), and return the (perhaps malloced) result.
+ * If opt is NULL, arg is considered a filename arg that allows wildcards.
+ * If it is "" or any other value, it is considered an option.
+ **/
+char *safe_arg(const char *opt, const char *arg)
+{
+#define SHELL_CHARS "!#$&;|<>(){}\"' \t\\"
+#define WILD_CHARS "*?[]" /* We don't allow remote brace expansion */
+ BOOL is_filename_arg = !opt;
+ char *escapes = is_filename_arg ? SHELL_CHARS : WILD_CHARS SHELL_CHARS;
+ BOOL escape_leading_dash = is_filename_arg && *arg == '-';
+ BOOL escape_leading_tilde = 0;
+ int len1 = opt && *opt ? strlen(opt) + 1 : 0;
+ int len2 = strlen(arg);
+ int extras = escape_leading_dash ? 2 : 0;
+ char *ret;
+ if (!protect_args && old_style_args < 2 && (!old_style_args || (!is_filename_arg && opt != SPLIT_ARG_WHEN_OLD))) {
+ const char *f;
+ if (*arg == '~' && is_filename_arg && !am_sender && !trust_sender_args
+ && ((relative_paths && !strstr(arg, "/./"))
+ || !strchr(arg, '/'))) {
+ extras++;
+ escape_leading_tilde = 1;
+ }
+ for (f = arg; *f; f++) {
+ if (strchr(escapes, *f))
+ extras++;
+ }
+ }
+ if (!len1 && !extras)
+ return (char*)arg;
+ ret = new_array(char, len1 + len2 + extras + 1);
+ if (len1) {
+ memcpy(ret, opt, len1-1);
+ ret[len1-1] = '=';
+ }
+ if (escape_leading_dash) {
+ ret[len1++] = '.';
+ ret[len1++] = '/';
+ extras -= 2;
+ }
+ if (!extras)
+ memcpy(ret + len1, arg, len2);
+ else {
+ const char *f = arg;
+ char *t = ret + len1;
+ if (escape_leading_tilde)
+ *t++ = '\\';
+ while (*f) {
+ if (*f == '\\') {
+ if (!is_filename_arg || !strchr(WILD_CHARS, f[1]))
+ *t++ = '\\';
+ } else if (strchr(escapes, *f))
+ *t++ = '\\';
+ *t++ = *f++;
+ }
+ }
+ ret[len1+len2+extras] = '\0';
+ return ret;
+}
+
+
/**
* Construct a filtered list of options to pass through from the
* client to the server.
set++;
else
set = iconv_opt;
- if (asprintf(&arg, "--iconv=%s", set) < 0)
- goto oom;
- args[ac++] = arg;
+ args[ac++] = safe_arg("--iconv", set);
}
#endif
}
if (backup_dir) {
+ /* This split idiom allows for ~/path expansion via the shell. */
args[ac++] = "--backup-dir";
- args[ac++] = backup_dir;
+ args[ac++] = safe_arg("", backup_dir);
}
/* Only send --suffix if it specifies a non-default value. */
- if (strcmp(backup_suffix, backup_dir ? "" : BACKUP_SUFFIX) != 0) {
- /* We use the following syntax to avoid weirdness with '~'. */
- if (asprintf(&arg, "--suffix=%s", backup_suffix) < 0)
- goto oom;
- args[ac++] = arg;
- }
+ if (strcmp(backup_suffix, backup_dir ? "" : BACKUP_SUFFIX) != 0)
+ args[ac++] = safe_arg("--suffix", backup_suffix);
- if (checksum_choice) {
- if (asprintf(&arg, "--checksum-choice=%s", checksum_choice) < 0)
- goto oom;
- args[ac++] = arg;
- }
+ if (checksum_choice)
+ args[ac++] = safe_arg("--checksum-choice", checksum_choice);
if (do_compression == CPRES_ZLIBX)
args[ac++] = "--new-compress";
else if (compress_choice && do_compression == CPRES_ZLIB)
args[ac++] = "--old-compress";
- else if (compress_choice) {
- if (asprintf(&arg, "--compress-choice=%s", compress_choice) < 0)
- goto oom;
- args[ac++] = arg;
- }
+ else if (compress_choice)
+ args[ac++] = safe_arg("--compress-choice", compress_choice);
if (am_sender) {
if (max_delete > 0) {
args[ac++] = arg;
} else if (max_delete == 0)
args[ac++] = "--max-delete=-1";
- if (min_size >= 0) {
- args[ac++] = "--min-size";
- args[ac++] = min_size_arg;
- }
- if (max_size >= 0) {
- args[ac++] = "--max-size";
- args[ac++] = max_size_arg;
- }
+ if (min_size >= 0)
+ args[ac++] = safe_arg("--min-size", min_size_arg);
+ if (max_size >= 0)
+ args[ac++] = safe_arg("--max-size", max_size_arg);
if (delete_before)
args[ac++] = "--delete-before";
else if (delete_during == 2)
if (do_stats)
args[ac++] = "--stats";
} else {
- if (skip_compress) {
- if (asprintf(&arg, "--skip-compress=%s", skip_compress) < 0)
- goto oom;
- args[ac++] = arg;
- }
+ if (skip_compress)
+ args[ac++] = safe_arg("--skip-compress", skip_compress);
}
- if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC) {
- args[ac++] = "--max-alloc";
- args[ac++] = max_alloc_arg;
- }
+ if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC)
+ args[ac++] = safe_arg("--max-alloc", max_alloc_arg);
/* --delete-missing-args needs the cooperation of both sides, but
* the sender can handle --ignore-missing-args by itself. */
if (partial_dir && am_sender) {
if (partial_dir != tmp_partialdir) {
args[ac++] = "--partial-dir";
- args[ac++] = partial_dir;
+ args[ac++] = safe_arg("", partial_dir);
}
if (delay_updates)
args[ac++] = "--delay-updates";
args[ac++] = "--use-qsort";
if (am_sender) {
- if (usermap) {
- if (asprintf(&arg, "--usermap=%s", usermap) < 0)
- goto oom;
- args[ac++] = arg;
- }
+ if (usermap)
+ args[ac++] = safe_arg("--usermap", usermap);
- if (groupmap) {
- if (asprintf(&arg, "--groupmap=%s", groupmap) < 0)
- goto oom;
- args[ac++] = arg;
- }
+ if (groupmap)
+ args[ac++] = safe_arg("--groupmap", groupmap);
if (ignore_existing)
args[ac++] = "--ignore-existing";
if (tmpdir) {
args[ac++] = "--temp-dir";
- args[ac++] = tmpdir;
+ args[ac++] = safe_arg("", tmpdir);
}
if (do_fsync)
*/
for (i = 0; i < basis_dir_cnt; i++) {
args[ac++] = alt_dest_opt(0);
- args[ac++] = basis_dir[i];
+ args[ac++] = safe_arg("", basis_dir[i]);
}
}
}
} else if (inplace) {
args[ac++] = "--inplace";
/* Work around a bug in older rsync versions (on the remote side) for --inplace --sparse */
- if (sparse_files && !whole_file)
+ if (sparse_files && !whole_file && am_sender)
args[ac++] = "--no-W";
}
if (files_from && (!am_sender || filesfrom_host)) {
if (filesfrom_host) {
args[ac++] = "--files-from";
- args[ac++] = files_from;
+ args[ac++] = safe_arg("", files_from);
if (eol_nulls)
args[ac++] = "--from0";
} else {
else if (remove_source_files)
args[ac++] = "--remove-sent-files";
+ if (copy_devices && !am_sender)
+ args[ac++] = "--copy-devices";
+
if (preallocate_files && am_sender)
args[ac++] = "--preallocate";
exit_cleanup(RERR_SYNTAX);
}
for (j = 1; j <= remote_option_cnt; j++)
- args[ac++] = (char*)remote_options[j];
+ args[ac++] = safe_arg(SPLIT_ARG_WHEN_OLD, remote_options[j]);
}
*argc_p = ac;