Depends-On-Patch: acls.diff
Depends-On-Patch: xattrs.diff

This patch adds a new option:  --fake-super, which tells rsync to copy in a
fake super-user mode that stores various file attributes in an extended-
attribute value instead of as real file-system attributes.  The items
affected are:

  mode  the real mode of a file is always (666 & umask) while
        the real mode of a directory is always (777 & umask).

  rdev  devices and special files are created as zero-length
        normal files.

  uid   the real owner is always left unchanged.

  gid   the real group is always left unchanged.

A daemon can set "fake super = yes" in the rsync.conf file for any module
that you'd like to run without root perms while pretending it has them (the
client cannot affect this).

The --fake-super option only affects the side where the option is used.  To
affect the remote side of a remote-shell connection, specify an rsync path:

  rsync -av --rsync-path='rsync --fake-super' /src/ host:/dest/

For a local copy where you want to affect only one side or the other,
you'll need to turn the copy into a remote copy to localhost.

After applying this patch, run these commands for a successful build:

    ./prepare-source
    ./configure
    make

--- old/Makefile.in
+++ new/Makefile.in
@@ -41,7 +41,7 @@ popt_OBJS=popt/findme.o  popt/popt.o  po
 	popt/popthelp.o popt/poptparse.o
 OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
 
-TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o
+TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattr.o
 
 # Programs we must have to run the test cases
 CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
@@ -83,11 +83,11 @@ getgroups$(EXEEXT): getgroups.o
 getfsdev$(EXEEXT): getfsdev.o
 	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
 
-TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o
+TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o lib/sysxattr.o
 trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
 	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
 
-T_UNSAFE_OBJ = t_unsafe.o syscall.o util.o t_stub.o lib/compat.o lib/snprintf.o
+T_UNSAFE_OBJ = t_unsafe.o syscall.o util.o t_stub.o lib/compat.o lib/snprintf.o lib/sysxattr.o
 t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
 	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)
 
--- old/clientserver.c
+++ new/clientserver.c
@@ -284,6 +284,9 @@ static int rsync_module(int f_in, int f_
 	pid_t pre_exec_pid = 0;
 	char *request = NULL;
 
+	if (lp_fake_super(i) != 0)
+		am_root = -1;
+
 	if (!allow_access(addr, host, lp_hosts_allow(i), lp_hosts_deny(i))) {
 		rprintf(FLOG, "rsync denied on module %s from %s (%s)\n",
 			name, host, addr);
--- old/generator.c
+++ new/generator.c
@@ -1510,13 +1510,14 @@ void generate_files(int f_out, struct fi
 		recv_generator(fbuf, file, i, itemizing, maybe_ATTRS_REPORT,
 			       code, f_out);
 
-		/* We need to ensure that any dirs we create have writeable
+		/* We need to ensure that any dirs we create have rwx
 		 * permissions during the time we are putting files within
 		 * them.  This is then fixed after the transfer is done. */
 #ifdef HAVE_CHMOD
-		if (!am_root && S_ISDIR(file->mode) && !(file->mode & S_IWUSR)
+		if (am_root <= 0 && S_ISDIR(file->mode)
+		    && (file->mode & S_IRWXU) != S_IRWXU
 		 && dir_tweaking) {
-			mode_t mode = file->mode | S_IWUSR; /* user write */
+			mode_t mode = file->mode | S_IRWXU; /* user rwx */
 			char *fname = local_name ? local_name : fbuf;
 			if (do_chmod(fname, mode) < 0) {
 				rsyserr(FERROR, errno,
--- old/loadparm.c
+++ new/loadparm.c
@@ -150,6 +150,7 @@ typedef struct
 	int syslog_facility;
 	int timeout;
 
+	BOOL fake_super;
 	BOOL ignore_errors;
 	BOOL ignore_nonreadable;
 	BOOL list;
@@ -197,6 +198,7 @@ static service sDefault =
  /* syslog_facility; */		LOG_DAEMON,
  /* timeout; */			0,
 
+ /* fake_super; */		False,
  /* ignore_errors; */		False,
  /* ignore_nonreadable; */	False,
  /* list; */			True,
@@ -298,6 +300,7 @@ static struct parm_struct parm_table[] =
  {"dont compress",     P_STRING, P_LOCAL, &sDefault.dont_compress,     NULL,0},
  {"exclude from",      P_STRING, P_LOCAL, &sDefault.exclude_from,      NULL,0},
  {"exclude",           P_STRING, P_LOCAL, &sDefault.exclude,           NULL,0},
+ {"fake super",        P_BOOL,   P_LOCAL, &sDefault.fake_super,        NULL,0},
  {"filter",            P_STRING, P_LOCAL, &sDefault.filter,            NULL,0},
  {"gid",               P_STRING, P_LOCAL, &sDefault.gid,               NULL,0},
  {"hosts allow",       P_STRING, P_LOCAL, &sDefault.hosts_allow,       NULL,0},
@@ -412,6 +415,7 @@ FN_LOCAL_INTEGER(lp_max_connections, max
 FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
 FN_LOCAL_INTEGER(lp_timeout, timeout)
 
+FN_LOCAL_BOOL(lp_fake_super, fake_super)
 FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
 FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
 FN_LOCAL_BOOL(lp_list, list)
@@ -816,7 +820,7 @@ BOOL lp_load(char *pszFname, int globals
 
 	if (pszFname)
 	    pstrcpy(n2,pszFname);
-	else if (am_server && !am_root)
+	else if (am_server && am_root <= 0)
 	    pstrcpy(n2,RSYNCD_USERCONF);
 	else
 	    pstrcpy(n2,RSYNCD_SYSCONF);
--- old/options.c
+++ new/options.c
@@ -73,7 +73,7 @@ int protocol_version = PROTOCOL_VERSION;
 int sparse_files = 0;
 int do_compression = 0;
 int def_compress_level = Z_DEFAULT_COMPRESSION;
-int am_root = 0;
+int am_root = 0; /* 0 = normal, 1 = super, 2 = --super, -1 = --fake-super */
 int am_server = 0;
 int am_sender = 0;
 int am_generator = 0;
@@ -330,6 +330,7 @@ void usage(enum logcode F)
   rprintf(F," -t, --times                 preserve times\n");
   rprintf(F," -O, --omit-dir-times        omit directories when preserving times\n");
   rprintf(F,"     --super                 receiver attempts super-user activities\n");
+  rprintf(F,"     --fake-super            fake root by storing/reading ownership/etc in EAs\n");
   rprintf(F," -S, --sparse                handle sparse files efficiently\n");
   rprintf(F," -n, --dry-run               show what would have been transferred\n");
   rprintf(F," -W, --whole-file            copy files whole (without rsync algorithm)\n");
@@ -454,6 +455,7 @@ static struct poptOption long_options[] 
   {"modify-window",    0,  POPT_ARG_INT,    &modify_window, OPT_MODIFY_WINDOW, 0, 0 },
   {"super",            0,  POPT_ARG_VAL,    &am_root, 2, 0, 0 },
   {"no-super",         0,  POPT_ARG_VAL,    &am_root, 0, 0, 0 },
+  {"fake-super",       0,  POPT_ARG_VAL,    &am_root, -1, 0, 0 },
   {"owner",           'o', POPT_ARG_VAL,    &preserve_uid, 1, 0, 0 },
   {"no-owner",         0,  POPT_ARG_VAL,    &preserve_uid, 0, 0, 0 },
   {"no-o",             0,  POPT_ARG_VAL,    &preserve_uid, 0, 0, 0 },
--- old/rsync.c
+++ new/rsync.c
@@ -197,6 +197,12 @@ int set_file_attrs(char *fname, struct f
 					(long)sxp->st.st_gid, (long)file->gid);
 			}
 		}
+		if (am_root < 0) {
+			if (change_uid)
+				sxp->st.st_uid = file->uid;
+			if (change_gid)
+				sxp->st.st_gid = file->gid;
+		} else
 		if (do_lchown(fname,
 		    change_uid ? file->uid : sxp->st.st_uid,
 		    change_gid ? file->gid : sxp->st.st_gid) != 0) {
@@ -206,7 +212,7 @@ int set_file_attrs(char *fname, struct f
 			    change_uid ? "chown" : "chgrp",
 			    full_fname(fname));
 			goto cleanup;
-		}
+		} else
 		/* a lchown had been done - we have to re-stat if the
 		 * destination had the setuid or setgid bits set due
 		 * to the side effect of the chown call */
@@ -237,7 +243,16 @@ int set_file_attrs(char *fname, struct f
 
 #ifdef HAVE_CHMOD
 	if ((sxp->st.st_mode & CHMOD_BITS) != (new_mode & CHMOD_BITS)) {
-		int ret = do_chmod(fname, new_mode);
+		int ret;
+		if (am_root < 0) {
+			mode_t mode = 0666 & ~orig_umask;
+			if ((sxp->st.st_mode & CHMOD_BITS) != mode)
+				ret = do_chmod(fname, mode);
+			else
+				ret = 0;
+			sxp->st.st_mode = new_mode;
+		} else
+			ret = do_chmod(fname, new_mode);
 		if (ret < 0) {
 			rsyserr(FERROR, errno,
 				"failed to set permissions on %s",
@@ -249,6 +264,22 @@ int set_file_attrs(char *fname, struct f
 	}
 #endif
 
+	if (am_root < 0) {
+		int write_it = updated;
+		if (IS_DEVICE(file->mode) || IS_SPECIAL(file->mode)) {
+			if (file->u.rdev != sxp->st.st_rdev) {
+				sxp->st.st_rdev = file->u.rdev;
+				write_it = 1;
+			}
+		} else
+			sxp->st.st_rdev = 0;
+		if (write_it && set_stat_xattr(fname, &sxp->st) < 0) {
+			rsyserr(FERROR, errno,
+				"write of stat xattr failed for %s",
+				full_fname(fname));
+		}
+	}
+
 	if (verbose > 1 && flags & ATTRS_REPORT) {
 		if (updated)
 			rprintf(FCLIENT, "%s\n", fname);
--- old/rsync.h
+++ new/rsync.h
@@ -35,6 +35,8 @@
 
 #define BACKUP_SUFFIX "~"
 
+#define FAKE_XATTR "user.fake%stat"
+
 /* a non-zero CHAR_OFFSET makes the rolling sum stronger, but is
    incompatible with older versions :-( */
 #define CHAR_OFFSET 0
--- old/syscall.c
+++ new/syscall.c
@@ -22,12 +22,14 @@
  */
 
 #include "rsync.h"
+#include "lib/sysxattr.h"
 
 #if !defined MKNOD_CREATES_SOCKETS && defined HAVE_SYS_UN_H
 #include <sys/un.h>
 #endif
 
 extern int dry_run;
+extern int am_root;
 extern int read_only;
 extern int list_only;
 extern int preserve_perms;
@@ -79,6 +81,15 @@ int do_mknod(char *pathname, mode_t mode
 {
 	if (dry_run) return 0;
 	RETURN_ERROR_IF_RO_OR_LO;
+
+	/* For --fake-super, we create a normal file with mode 0600. */
+	if (am_root < 0) {
+		int fd = open(pathname, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR);
+		if (fd < 0 || close(fd) < 0)
+			return -1;
+		return 0;
+	}
+
 #if !defined MKNOD_CREATES_FIFOS && defined HAVE_MKFIFO
 	if (S_ISFIFO(mode))
 		return mkfifo(pathname, mode);
@@ -133,6 +144,7 @@ int do_open(const char *pathname, int fl
 }
 
 #ifdef HAVE_CHMOD
+
 int do_chmod(const char *path, mode_t mode)
 {
 	int code;
@@ -215,23 +227,69 @@ int do_mkstemp(char *template, mode_t pe
 #endif
 }
 
+int get_stat_xattr(const char *fname, STRUCT_STAT *st)
+{
+	int mode, rdev, uid, gid, len;
+	char buf[256];
+
+	len = sys_lgetxattr(fname, FAKE_XATTR, buf, sizeof buf - 1);
+	if (len < 0 || len >= (int)sizeof buf) {
+		if (errno == ENOTSUP || errno == ENOATTR)
+			return 0;
+		return -1;
+	}
+	buf[len] = '\0';
+
+	if (sscanf(buf, "%o %d %d:%d", &mode, &rdev, &uid, &gid) != 4) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	st->st_mode = mode;
+	st->st_rdev = rdev;
+	st->st_uid = uid;
+	st->st_gid = gid;
+
+	return 0;
+}
+
+int set_stat_xattr(const char *fname, STRUCT_STAT *st)
+{
+	char buf[256];
+	int len;
+	if (dry_run) return 0;
+	RETURN_ERROR_IF_RO_OR_LO;
+	len = snprintf(buf, sizeof buf, "%o %d %d:%d",
+		       (int)st->st_mode, (int)st->st_rdev,
+		       (int)st->st_uid, (int)st->st_gid);
+	return sys_lsetxattr(fname, FAKE_XATTR, buf, len, 0);
+}
+
 int do_stat(const char *fname, STRUCT_STAT *st)
 {
+	int ret;
 #ifdef USE_STAT64_FUNCS
-	return stat64(fname, st);
+	ret = stat64(fname, st);
 #else
-	return stat(fname, st);
+	ret = stat(fname, st);
 #endif
+	if (am_root < 0 && ret == 0)
+		get_stat_xattr(fname, st);
+	return ret;
 }
 
 int do_lstat(const char *fname, STRUCT_STAT *st)
 {
 #ifdef SUPPORT_LINKS
+	int ret;
 # ifdef USE_STAT64_FUNCS
-	return lstat64(fname, st);
+	ret = lstat64(fname, st);
 # else
-	return lstat(fname, st);
+	ret = lstat(fname, st);
 # endif
+	if (am_root < 0 && ret == 0)
+		get_stat_xattr(fname, st);
+	return ret;
 #else
 	return do_stat(fname, st);
 #endif
--- old/t_unsafe.c
+++ new/t_unsafe.c
@@ -24,7 +24,11 @@
 
 #include "rsync.h"
 
-int dry_run, read_only, list_only, verbose;
+int dry_run = 0;
+int am_root = 0;
+int read_only = 0;
+int list_only = 0;
+int verbose = 0;
 int preserve_perms = 0;
 
 int
--- old/tls.c
+++ new/tls.c
@@ -39,6 +39,7 @@
 
 /* These are to make syscall.o shut up. */
 int dry_run = 0;
+int am_root = 0; /* TODO: add option to set this to -1. */
 int read_only = 1;
 int list_only = 0;
 int preserve_perms = 0;
--- old/trimslash.c
+++ new/trimslash.c
@@ -23,6 +23,7 @@
 
 /* These are to make syscall.o shut up. */
 int dry_run = 0;
+int am_root = 0;
 int read_only = 1;
 int list_only = 0;
 int preserve_perms = 0;
--- old/xattr.c
+++ new/xattr.c
@@ -26,6 +26,7 @@
 #ifdef SUPPORT_XATTRS
 
 extern int dry_run;
+extern int am_root;
 extern unsigned int file_struct_len;
 
 #define RSYNC_XAL_INITIAL 5
@@ -130,9 +131,15 @@ static int rsync_xal_get(const char *fna
 	if (name_size == 0)
 		return 0;
 	for (left = name_size, name = namebuf; left > 0 ; left -= len, name += len) {
-		rsync_xa *rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL);
+		rsync_xa *rxas;
 
 		len = strlen(name) + 1;
+		if (am_root < 0 && len == sizeof FAKE_XATTR
+		 && name[9] == '%' && strcmp(name, FAKE_XATTR) == 0)
+			continue;
+
+		rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL);
+
 		datum_size = sys_lgetxattr(fname, name, NULL, 0);
 		if (datum_size < 0) {
 			if (errno == ENOTSUP)
@@ -285,10 +292,19 @@ void receive_xattr(struct file_struct *f
 				out_of_memory("receive_xattr");
 			read_buf(f, ptr, name_len);
 			read_buf(f, ptr + name_len, datum_len);
+
+			if (am_root < 0 && name_len == sizeof FAKE_XATTR
+			 && ptr[9] == '%' && strcmp(ptr, FAKE_XATTR) == 0) {
+				free(ptr);
+				temp_xattr.count--;
+				continue;
+			}
+
 			rxa->name_len = name_len;
 			rxa->datum_len = datum_len;
 			rxa->name = ptr;
 			rxa->datum = ptr + name_len;
+
 #ifdef HAVE_OSX_XATTRS
 			if (strncmp(rxa->name, unique_prefix, upre_len) == 0) {
 				rxa->name_len -= upre_len;
