Prevent the -g option from preserving groups that a non-root receiver
authorDavid Dykstra <dwd@samba.org>
Mon, 1 Mar 1999 19:24:39 +0000 (19:24 +0000)
committerDavid Dykstra <dwd@samba.org>
Mon, 1 Mar 1999 19:24:39 +0000 (19:24 +0000)
does not belong to, in these two ways:
    1. If a group mapping doesn't exist for a group name, do not preserve
it for a non-root receiver.  This is especially evident with the
sender is a daemon using chroot because then no mappings are
available.
    2. Before setting the group on a file make sure that it is in the list
of groups returned by getgroups().  The same thing is done by chgrp
on systems that support bsd-style chown/chgrp, and this enforces
that it happens the same way on all systems.  Overhead is very
little, especially since most systems don't allow more then 16
groups per user.

configure.in
rsync.c
rsync.yo
uidlist.c

index 11d6fae..be39200 100644 (file)
@@ -52,7 +52,7 @@ AC_FUNC_UTIME_NULL
 AC_CHECK_FUNCS(waitpid wait4 getcwd strdup strerror chown chmod mknod)
 AC_CHECK_FUNCS(fchmod fstat strchr readlink link utime utimes strftime)
 AC_CHECK_FUNCS(memmove getopt_long lchown vsnprintf snprintf setsid glob strpbrk)
-AC_CHECK_FUNCS(strlcat strlcpy)
+AC_CHECK_FUNCS(strlcat strlcpy getgroups)
 
 echo $ac_n "checking for working fnmatch... $ac_c"
 AC_TRY_RUN([#include <fnmatch.h>
diff --git a/rsync.c b/rsync.c
index 3f79f50..ceabfda 100644 (file)
--- a/rsync.c
+++ b/rsync.c
@@ -113,12 +113,46 @@ int delete_file(char *fname)
        return 0;
 }
 
+static int is_in_group(gid_t gid)
+{
+#ifdef HAVE_GETGROUPS
+       static gid_t last_in = (gid_t) -2, last_out;
+       static int ngroups = -2;
+       static gid_t *gidset;
+       int n;
+
+       if (gid == last_in)
+               return last_out;
+       if (ngroups < -1) {
+               /* treat failure (-1) as if not member of any group */
+               ngroups = getgroups(0, 0);
+               if (ngroups > 0) {
+                       gidset = (gid_t *) malloc(ngroups * sizeof(gid_t));
+                       ngroups = getgroups(ngroups, gidset);
+               }
+       }
+
+       last_in = gid;
+       last_out = 0;
+       for (n = 0; n < ngroups; n++) {
+               if (gidset[n] == gid) {
+                       last_out = 1;
+                       break;
+               }
+       }
+       return last_out;
+
+#else
+       return 0;
+#endif
+}
 
 int set_perms(char *fname,struct file_struct *file,STRUCT_STAT *st,
              int report)
 {
        int updated = 0;
        STRUCT_STAT st2;
+       int change_uid, change_gid;
        extern int am_daemon;
 
        if (dry_run) return 0;
@@ -145,22 +179,24 @@ int set_perms(char *fname,struct file_struct *file,STRUCT_STAT *st,
                }
        }
 
-       if ((am_root || !am_daemon) &&
-           ((am_root && preserve_uid && st->st_uid != file->uid) || 
-            (preserve_gid && st->st_gid != file->gid))) {
+       change_uid = am_root && preserve_uid && st->st_uid != file->uid;
+       change_gid = !am_daemon && preserve_gid && file->gid != -1 \
+                               && st->st_gid != file->gid;
+       if (change_gid && !am_root) {
+               /* enforce bsd-style group semantics: non-root can only
+                   change to groups that the user is a member of */
+               change_gid = is_in_group(file->gid);
+       }
+       if (change_uid || change_gid) {
                if (do_lchown(fname,
-                             (am_root&&preserve_uid)?file->uid:st->st_uid,
-                             preserve_gid?file->gid:st->st_gid) != 0) {
-                       if (preserve_uid && st->st_uid != file->uid)
-                               updated = 1;
-                       if (verbose>1 || preserve_uid) {
-                               rprintf(FERROR,"chown %s : %s\n",
-                                       fname,strerror(errno));
-                               return 0;
-                       }
-               } else {
-                       updated = 1;
+                             change_uid?file->uid:st->st_uid,
+                             change_gid?file->gid:st->st_gid) != 0) {
+                       /* shouldn't have attempted to change uid or gid
+                            unless have the privilege */
+                       rprintf(FERROR,"chown %s : %s\n", fname,strerror(errno));
+                       return 0;
                }
+               updated = 1;
        }
 
 #ifdef HAVE_CHMOD
index 9f27cf9..30bdca6 100644 (file)
--- a/rsync.yo
+++ b/rsync.yo
@@ -1,5 +1,5 @@
 mailto(rsync-bugs@samba.org)
-manpage(rsync)(1)(22 Feb 1999)()()
+manpage(rsync)(1)(1 Mar 1999)()()
 manpagename(rsync)(faster, flexible replacement for rcp)
 manpagesynopsis()
 
@@ -319,11 +319,11 @@ explicitly checked on the receiver and any files of the same name
 which already exist and have the same checksum and size on the
 receiver are skipped.  This option can be quite slow.
 
-dit(bf(-a, --archive)) This is equivalent to -rlptDg. It is a quick way
+dit(bf(-a, --archive)) This is equivalent to -rlptg. It is a quick way
 of saying you want recursion and want to preserve everything.
 
-Note: if the user launching rsync is root then the -o option (preserve
-uid) is also implied.
+Note: if the user launching rsync is root then the -o (preserve
+uid) and -D (preserve devices) options are also implied.
 
 dit(bf(-r, --recursive)) This tells rsync to copy directories recursively.
 
@@ -400,9 +400,9 @@ the --numeric-ids option is implied because the source system cannot get
 access to the usernames.
 
 dit(bf(-g, --group)) This option causes rsync to update the  remote  group
-of the file to be the same as the local group.  Note that if the source
-system is a daemon using chroot, the --numeric-ids option is implied because
-the source system cannot get access to the group names.
+of the file to be the same as the local group.  If the receving system is
+not running as the super-user, only groups that the receiver is a member of
+will be preserved (by group name, not group id number).
 
 dit(bf(-D, --devices)) This option causes rsync to transfer character and
 block device information to the remote system to recreate these
index eff8749..e4b61b8 100644 (file)
--- a/uidlist.c
+++ b/uidlist.c
@@ -28,6 +28,7 @@
 extern int preserve_uid;
 extern int preserve_gid;
 extern int numeric_ids;
+extern int am_root;
 
 struct idlist {
        struct idlist *next;
@@ -122,7 +123,10 @@ static gid_t match_gid(gid_t gid)
                list = list->next;
        }
        
-       last_out = gid;
+       if (am_root)
+               last_out = gid;
+       else
+               last_out = -1;
        return last_out;
 }
 
@@ -276,12 +280,12 @@ void recv_uid_list(int f, struct file_list *flist)
                }
        }
 
-       if (!uidlist && !gidlist) return;
+       if (!(am_root && preserve_uid) && !preserve_gid) return;
 
        /* now convert the uid/gid of all files in the list to the mapped
           uid/gid */
        for (i=0;i<flist->count;i++) {
-               if (preserve_uid && flist->files[i]->uid != 0) {
+               if (am_root && preserve_uid && flist->files[i]->uid != 0) {
                        flist->files[i]->uid = match_uid(flist->files[i]->uid);
                }
                if (preserve_gid && flist->files[i]->gid != 0) {