Add safety check for local --remove-source-files.
[rsync.git] / options.c
index eb16c4f6de9a4b1f89b677c0ebedeb57b4badc6d..5b3d6dea92de69ca32a65c178a412d812b5a6b72 100644 (file)
--- a/options.c
+++ b/options.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org>
  * Copyright (C) 2000, 2001, 2002 Martin Pool <mbp@samba.org>
- * Copyright (C) 2002-2020 Wayne Davison
+ * Copyright (C) 2002-2022 Wayne Davison
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,6 +27,8 @@
 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;
@@ -47,6 +49,7 @@ int append_mode = 0;
 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;
@@ -63,6 +66,7 @@ int preserve_atimes = 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;
@@ -102,6 +106,7 @@ int filesfrom_fd = -1;
 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;
@@ -291,7 +296,7 @@ static struct output_struct debug_words[COUNT_DEBUG+1] = {
        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"),
@@ -577,7 +582,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
       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};
 
@@ -655,6 +660,7 @@ static struct poptOption long_options[] = {
   {"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,   &copy_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 },
@@ -780,9 +786,12 @@ static struct poptOption long_options[] = {
   {"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},
+  {"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},
   {"protect-args",    's', 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 },
@@ -946,6 +955,7 @@ static void set_refuse_options(void)
                 || 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
@@ -957,6 +967,7 @@ static void set_refuse_options(void)
        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);
        }
 
@@ -1605,6 +1616,13 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        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 != '-') {
@@ -1922,6 +1940,21 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                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,
+                                "--protect-args conflicts with --old-args.\n");
+                       return 0;
+               }
+               protect_args = 0;
+       }
+
        if (protect_args < 0) {
                if (am_server)
                        protect_args = 0;
@@ -2436,6 +2469,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                }
        }
 
+       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;
@@ -2447,6 +2485,73 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 }
 
 
+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 (!trust_sender_args && *arg == '~'
+                && ((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.
@@ -2590,9 +2695,7 @@ void server_options(char **args, int *argc_p)
                        set++;
                else
                        set = iconv_opt;
-               if (asprintf(&arg, "--iconv=%s", set) < 0)
-                       goto oom;
-               args[ac++] = arg;
+               args[ac++] = safe_arg("--iconv", set);
        }
 #endif
 
@@ -2658,33 +2761,24 @@ void server_options(char **args, int *argc_p)
        }
 
        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) {
@@ -2693,14 +2787,10 @@ void server_options(char **args, int *argc_p)
                        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)
@@ -2724,17 +2814,12 @@ void server_options(char **args, int *argc_p)
                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. */
@@ -2759,7 +2844,7 @@ void server_options(char **args, int *argc_p)
        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";
@@ -2782,17 +2867,11 @@ void server_options(char **args, int *argc_p)
                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";
@@ -2803,7 +2882,7 @@ void server_options(char **args, int *argc_p)
 
                if (tmpdir) {
                        args[ac++] = "--temp-dir";
-                       args[ac++] = tmpdir;
+                       args[ac++] = safe_arg("", tmpdir);
                }
 
                if (do_fsync)
@@ -2816,7 +2895,7 @@ void server_options(char **args, int *argc_p)
                         */
                        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]);
                        }
                }
        }
@@ -2834,14 +2913,14 @@ void server_options(char **args, int *argc_p)
        } 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 {
@@ -2863,6 +2942,9 @@ void server_options(char **args, int *argc_p)
        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";
 
@@ -2884,7 +2966,7 @@ void server_options(char **args, int *argc_p)
                        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;