mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-01-24 14:58:17 -05:00
Add a --copy-as=USER[:GROUP] option
This can be used by a root-run rsync to try to make reading or writing files safer in a situation where you can't run the whole rsync command as a non-root user.
This commit is contained in:
78
main.c
78
main.c
@@ -89,6 +89,7 @@ extern char *shell_cmd;
|
||||
extern char *batch_name;
|
||||
extern char *password_file;
|
||||
extern char *backup_dir;
|
||||
extern char *copy_as;
|
||||
extern char curr_dir[MAXPATHLEN];
|
||||
extern char backup_dir_buf[MAXPATHLEN];
|
||||
extern char *basis_dir[MAX_BASIS_DIRS+1];
|
||||
@@ -231,6 +232,74 @@ void read_del_stats(int f)
|
||||
stats.deleted_files += stats.deleted_specials = read_varint(f);
|
||||
}
|
||||
|
||||
static void become_copy_as_user()
|
||||
{
|
||||
char *gname;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
|
||||
if (!copy_as)
|
||||
return;
|
||||
|
||||
if (DEBUG_GTE(CMD, 2))
|
||||
rprintf(FINFO, "[%s] copy_as=%s\n", who_am_i(), copy_as);
|
||||
|
||||
if ((gname = strchr(copy_as, ':')) != NULL)
|
||||
*gname++ = '\0';
|
||||
|
||||
if (!user_to_uid(copy_as, &uid, True)) {
|
||||
rprintf(FERROR, "Invalid copy-as user: %s\n", copy_as);
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
|
||||
if (gname) {
|
||||
if (!group_to_gid(gname, &gid, True)) {
|
||||
rprintf(FERROR, "Invalid copy-as group: %s\n", gname);
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
} else {
|
||||
struct passwd *pw;
|
||||
if ((pw = getpwuid(uid)) == NULL) {
|
||||
rsyserr(FERROR, errno, "getpwuid failed");
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
gid = pw->pw_gid;
|
||||
}
|
||||
|
||||
if (setgid(gid) < 0) {
|
||||
rsyserr(FERROR, errno, "setgid failed");
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
#ifdef HAVE_SETGROUPS
|
||||
if (setgroups(1, &gid)) {
|
||||
rsyserr(FERROR, errno, "setgroups failed");
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_INITGROUPS
|
||||
if (!gname && initgroups(copy_as, gid) < 0) {
|
||||
rsyserr(FERROR, errno, "initgroups failed");
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (setuid(uid) < 0
|
||||
#ifdef HAVE_SETEUID
|
||||
|| seteuid(uid) < 0
|
||||
#endif
|
||||
) {
|
||||
rsyserr(FERROR, errno, "setuid failed");
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
|
||||
our_uid = MY_UID();
|
||||
our_gid = MY_GID();
|
||||
am_root = (our_uid == 0);
|
||||
|
||||
if (gname)
|
||||
gname[-1] = ':';
|
||||
}
|
||||
|
||||
/* This function gets called from all 3 processes. We want the client side
|
||||
* to actually output the text, but the sender is the only process that has
|
||||
* all the stats we need. So, if we're a client sender, we do the report.
|
||||
@@ -824,6 +893,8 @@ static void do_server_sender(int f_in, int f_out, int argc, char *argv[])
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
|
||||
become_copy_as_user();
|
||||
|
||||
dir = argv[0];
|
||||
if (!relative_paths) {
|
||||
if (!change_dir(dir, CD_NORMAL)) {
|
||||
@@ -1027,6 +1098,8 @@ static void do_server_recv(int f_in, int f_out, int argc, char *argv[])
|
||||
return;
|
||||
}
|
||||
|
||||
become_copy_as_user();
|
||||
|
||||
if (argc > 0) {
|
||||
char *dir = argv[0];
|
||||
argc--;
|
||||
@@ -1186,6 +1259,9 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
|
||||
|
||||
if (write_batch && !am_server)
|
||||
start_write_batch(f_out);
|
||||
|
||||
become_copy_as_user();
|
||||
|
||||
flist = send_file_list(f_out, argc, argv);
|
||||
if (DEBUG_GTE(FLIST, 3))
|
||||
rprintf(FINFO,"file list sent\n");
|
||||
@@ -1219,6 +1295,8 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
|
||||
io_start_buffering_out(f_out);
|
||||
}
|
||||
|
||||
become_copy_as_user();
|
||||
|
||||
send_filter_list(read_batch ? -1 : f_out);
|
||||
|
||||
if (filesfrom_fd >= 0) {
|
||||
|
||||
@@ -126,6 +126,7 @@ int inplace = 0;
|
||||
int delay_updates = 0;
|
||||
long block_size = 0; /* "long" because popt can't set an int32. */
|
||||
char *skip_compress = NULL;
|
||||
char *copy_as = NULL;
|
||||
item_list dparam_list = EMPTY_ITEM_LIST;
|
||||
|
||||
/** Network address family. **/
|
||||
@@ -777,6 +778,7 @@ void usage(enum logcode F)
|
||||
rprintf(F," --files-from=FILE read list of source-file names from FILE\n");
|
||||
rprintf(F," -0, --from0 all *-from/filter files are delimited by 0s\n");
|
||||
rprintf(F," -s, --protect-args no space-splitting; only wildcard special-chars\n");
|
||||
rprintf(F," --copy-as=USER[:GROUP] specify user & optional group for the copy\n");
|
||||
rprintf(F," --address=ADDRESS bind address for outgoing socket to daemon\n");
|
||||
rprintf(F," --port=PORT specify double-colon alternate port number\n");
|
||||
rprintf(F," --sockopts=OPTIONS specify custom TCP options\n");
|
||||
@@ -1030,6 +1032,7 @@ static struct poptOption long_options[] = {
|
||||
{"no-8-bit-output", 0, POPT_ARG_VAL, &allow_8bit_chars, 0, 0, 0 },
|
||||
{"no-8", 0, POPT_ARG_VAL, &allow_8bit_chars, 0, 0, 0 },
|
||||
{"qsort", 0, POPT_ARG_NONE, &use_qsort, 0, 0, 0 },
|
||||
{"copy-as", 0, POPT_ARG_STRING, ©_as, 0, 0, 0 },
|
||||
{"address", 0, POPT_ARG_STRING, &bind_address, 0, 0, 0 },
|
||||
{"port", 0, POPT_ARG_INT, &rsync_port, 0, 0, 0 },
|
||||
{"sockopts", 0, POPT_ARG_STRING, &sockopts, 0, 0, 0 },
|
||||
|
||||
77
rsync.yo
77
rsync.yo
@@ -440,6 +440,7 @@ to the detailed description below for a complete description. verb(
|
||||
--files-from=FILE read list of source-file names from FILE
|
||||
-0, --from0 all *from/filter files are delimited by 0s
|
||||
-s, --protect-args no space-splitting; wildcard chars only
|
||||
--copy-as=USER[:GROUP] specify user & optional group for the copy
|
||||
--address=ADDRESS bind address for outgoing socket to daemon
|
||||
--port=PORT specify double-colon alternate port number
|
||||
--sockopts=OPTIONS specify custom TCP options
|
||||
@@ -624,8 +625,8 @@ resolution (allowing times to differ from the original by up to 1 second).
|
||||
If you want all your transfers to default to comparing nanoseconds, you can
|
||||
create a ~/.popt file and put these lines in it:
|
||||
|
||||
quote(tt( rsync alias -a -a@-1))
|
||||
quote(tt( rsync alias -t -t@-1))
|
||||
verb( rsync alias -a -a@-1)
|
||||
verb( rsync alias -t -t@-1)
|
||||
|
||||
With that as the default, you'd need to specify bf(--modify-window=0) (aka
|
||||
bf(-@0)) to override it and ignore nanoseconds, e.g. if you're copying between
|
||||
@@ -713,12 +714,12 @@ just the last parts of the filenames. This is particularly useful when
|
||||
you want to send several different directories at the same time. For
|
||||
example, if you used this command:
|
||||
|
||||
quote(tt( rsync -av /foo/bar/baz.c remote:/tmp/))
|
||||
verb( rsync -av /foo/bar/baz.c remote:/tmp/)
|
||||
|
||||
... this would create a file named baz.c in /tmp/ on the remote
|
||||
machine. If instead you used
|
||||
|
||||
quote(tt( rsync -avR /foo/bar/baz.c remote:/tmp/))
|
||||
verb( rsync -avR /foo/bar/baz.c remote:/tmp/)
|
||||
|
||||
then a file named /tmp/foo/bar/baz.c would be created on the remote
|
||||
machine, preserving its full path. These extra path elements are called
|
||||
@@ -739,24 +740,22 @@ implied directories for each path you specify. With a modern rsync on the
|
||||
sending side (beginning with 2.6.7), you can insert a dot and a slash into
|
||||
the source path, like this:
|
||||
|
||||
quote(tt( rsync -avR /foo/./bar/baz.c remote:/tmp/))
|
||||
verb( rsync -avR /foo/./bar/baz.c remote:/tmp/)
|
||||
|
||||
That would create /tmp/bar/baz.c on the remote machine. (Note that the
|
||||
dot must be followed by a slash, so "/foo/." would not be abbreviated.)
|
||||
For older rsync versions, you would need to use a chdir to limit the
|
||||
source path. For example, when pushing files:
|
||||
|
||||
quote(tt( (cd /foo; rsync -avR bar/baz.c remote:/tmp/) ))
|
||||
verb( (cd /foo; rsync -avR bar/baz.c remote:/tmp/) )
|
||||
|
||||
(Note that the parens put the two commands into a sub-shell, so that the
|
||||
"cd" command doesn't remain in effect for future commands.)
|
||||
If you're pulling files from an older rsync, use this idiom (but only
|
||||
for a non-daemon transfer):
|
||||
|
||||
quote(
|
||||
tt( rsync -avR --rsync-path="cd /foo; rsync" \ )nl()
|
||||
tt( remote:bar/baz.c /tmp/)
|
||||
)
|
||||
verb( rsync -avR --rsync-path="cd /foo; rsync" \ )
|
||||
verb( remote:bar/baz.c /tmp/)
|
||||
|
||||
dit(bf(--no-implied-dirs)) This option affects the default behavior of the
|
||||
bf(--relative) option. When it is specified, the attributes of the implied
|
||||
@@ -1089,11 +1088,11 @@ behavior easier to type, you could define a popt alias for it, such as
|
||||
putting this line in the file ~/.popt (the following defines the bf(-Z) option,
|
||||
and includes --no-g to use the default group of the destination dir):
|
||||
|
||||
quote(tt( rsync alias -Z --no-p --no-g --chmod=ugo=rwX))
|
||||
verb( rsync alias -Z --no-p --no-g --chmod=ugo=rwX)
|
||||
|
||||
You could then use this new option in a command such as this one:
|
||||
|
||||
quote(tt( rsync -avZ src/ dest/))
|
||||
verb( rsync -avZ src/ dest/)
|
||||
|
||||
(Caveat: make sure that bf(-a) does not follow bf(-Z), or it will re-enable
|
||||
the two "--no-*" options mentioned above.)
|
||||
@@ -1277,7 +1276,7 @@ The bf(--fake-super) option only affects the side where the option is used.
|
||||
To affect the remote side of a remote-shell connection, use the
|
||||
bf(--remote-option) (bf(-M)) option:
|
||||
|
||||
quote(tt( rsync -av -M--fake-super /src/ host:/dest/))
|
||||
verb( rsync -av -M--fake-super /src/ host:/dest/)
|
||||
|
||||
For a local copy, this option affects both the source and the destination.
|
||||
If you wish a local copy to enable this option just for the destination
|
||||
@@ -1592,10 +1591,8 @@ inside a single-quoted string gives you a single-quote; likewise for
|
||||
double-quotes (though you need to pay attention to which quotes your
|
||||
shell is parsing and which quotes rsync is parsing). Some examples:
|
||||
|
||||
quote(
|
||||
tt( -e 'ssh -p 2234')nl()
|
||||
tt( -e 'ssh -o "ProxyCommand nohup ssh firewall nc -w1 %h %p"')nl()
|
||||
)
|
||||
verb( -e 'ssh -p 2234')
|
||||
verb( -e 'ssh -o "ProxyCommand nohup ssh firewall nc -w1 %h %p"')
|
||||
|
||||
(Note that ssh users can alternately customize site-specific connect
|
||||
options in their .ssh/config file.)
|
||||
@@ -1616,20 +1613,20 @@ communicate.
|
||||
One tricky example is to set a different default directory on the remote
|
||||
machine for use with the bf(--relative) option. For instance:
|
||||
|
||||
quote(tt( rsync -avR --rsync-path="cd /a/b && rsync" host:c/d /e/))
|
||||
verb( rsync -avR --rsync-path="cd /a/b && rsync" host:c/d /e/)
|
||||
|
||||
dit(bf(-M, --remote-option=OPTION)) This option is used for more advanced
|
||||
situations where you want certain effects to be limited to one side of the
|
||||
transfer only. For instance, if you want to pass bf(--log-file=FILE) and
|
||||
bf(--fake-super) to the remote system, specify it like this:
|
||||
|
||||
quote(tt( rsync -av -M --log-file=foo -M--fake-super src/ dest/))
|
||||
verb( rsync -av -M --log-file=foo -M--fake-super src/ dest/)
|
||||
|
||||
If you want to have an option affect only the local side of a transfer when
|
||||
it normally affects both sides, send its negation to the remote side. Like
|
||||
this:
|
||||
|
||||
quote(tt( rsync -av -x -M--no-x src/ dest/))
|
||||
verb( rsync -av -x -M--no-x src/ dest/)
|
||||
|
||||
Be cautious using this, as it is possible to toggle an option that will cause
|
||||
rsync to have a different idea about what data to expect next over the socket,
|
||||
@@ -1696,14 +1693,14 @@ See the FILTER RULES section for detailed information on this option.
|
||||
dit(bf(-F)) The bf(-F) option is a shorthand for adding two bf(--filter) rules to
|
||||
your command. The first time it is used is a shorthand for this rule:
|
||||
|
||||
quote(tt( --filter='dir-merge /.rsync-filter'))
|
||||
verb( --filter='dir-merge /.rsync-filter')
|
||||
|
||||
This tells rsync to look for per-directory .rsync-filter files that have
|
||||
been sprinkled through the hierarchy and use their rules to filter the
|
||||
files in the transfer. If bf(-F) is repeated, it is a shorthand for this
|
||||
rule:
|
||||
|
||||
quote(tt( --filter='exclude .rsync-filter'))
|
||||
verb( --filter='exclude .rsync-filter')
|
||||
|
||||
This filters out the .rsync-filter files themselves from the transfer.
|
||||
|
||||
@@ -1757,7 +1754,7 @@ source dir -- any leading slashes are removed and no ".." references are
|
||||
allowed to go higher than the source dir. For example, take this
|
||||
command:
|
||||
|
||||
quote(tt( rsync -a --files-from=/tmp/foo /usr remote:/backup))
|
||||
verb( rsync -a --files-from=/tmp/foo /usr remote:/backup)
|
||||
|
||||
If /tmp/foo contains the string "bin" (or even "/bin"), the /usr/bin
|
||||
directory will be created as /backup/bin on the remote host. If it
|
||||
@@ -1778,7 +1775,7 @@ instead of the local host if you specify a "host:" in front of the file
|
||||
specify just a prefix of ":" to mean "use the remote end of the
|
||||
transfer". For example:
|
||||
|
||||
quote(tt( rsync -a --files-from=:/path/file-list src:/ /tmp/copy))
|
||||
verb( rsync -a --files-from=:/path/file-list src:/ /tmp/copy)
|
||||
|
||||
This would copy all the files specified in the /path/file-list file that
|
||||
was located on the remote "src" host.
|
||||
@@ -1829,6 +1826,36 @@ default (with is overridden by both the environment and the command-line).
|
||||
This option will eventually become a new default setting at some
|
||||
as-yet-undetermined point in the future.
|
||||
|
||||
dit(bf(--copy-as=USER[:GROUP])) This option instructs rsync to use the USER and
|
||||
(if specified after a colon) the GROUP for the copy operations. This only works
|
||||
if the user that is running rsync has the ability to change users. If the group
|
||||
is not specified then the user's default groups are used.
|
||||
|
||||
The option only affects one side of the transfer unless the transfer is local,
|
||||
in which case it affects both sides. Use the bf(--remote-option) to affect the
|
||||
remote side, such as bf(-M--copy-as=joe). For a local transfer, see the "lsh"
|
||||
support file provides a local-shell helper script that can be used to allow a
|
||||
"localhost:" host-spec to be specified without needing to setup any remote
|
||||
shells (allowing you to specify remote options that affect the side of the
|
||||
transfer that is using the host-spec, and local options for the other side).
|
||||
|
||||
This option can help to reduce the risk of an rsync being run as root into or
|
||||
out of a directory that might have live changes happening to it and you want to
|
||||
make sure that root-level read or write actions of system files are not
|
||||
possible. While you could alternatively run all of rsync as the specified user,
|
||||
sometimes you need the root-level host-access credentials to be used, so this
|
||||
allows rsync to drop root for the copying part of the operation after the
|
||||
remote-shell or daemon connection is established.
|
||||
|
||||
For example, the following rsync writes the local files as user "joe":
|
||||
|
||||
verb( sudo rsync -aiv --copy-as=joe host1:backups/joe/ /home/joe/)
|
||||
|
||||
This makes all files owned by user "joe", limits the groups to those that are
|
||||
available to that user, and makes it impossible for the joe user to do a timed
|
||||
exploit of the path to induce a change to a file that the joe use has no
|
||||
permissions to change.
|
||||
|
||||
dit(bf(-T, --temp-dir=DIR)) This option instructs rsync to use DIR as a
|
||||
scratch directory when creating temporary copies of the files transferred
|
||||
on the receiving side. The default behavior is to create each temporary
|
||||
@@ -1924,7 +1951,7 @@ The files must be identical in all preserved attributes (e.g. permissions,
|
||||
possibly ownership) in order for the files to be linked together.
|
||||
An example:
|
||||
|
||||
quote(tt( rsync -av --link-dest=$PWD/prior_dir host:src_dir/ new_dir/))
|
||||
verb( rsync -av --link-dest=$PWD/prior_dir host:src_dir/ new_dir/)
|
||||
|
||||
If file's aren't linking, double-check their attributes. Also check if some
|
||||
attributes are getting forced outside of rsync's control, such a mount option
|
||||
|
||||
Reference in New Issue
Block a user