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/backup.c +++ new/backup.c @@ -129,7 +129,7 @@ static int make_bak_dir(char *fullpath) if (p >= rel) { /* Try to transfer the directory settings of the * actual dir that the files are coming from. */ - if (do_stat(rel, &sx.st) < 0) { + if (x_stat(rel, &sx.st, NULL) < 0) { rsyserr(FERROR, errno, "make_bak_dir stat %s failed", full_fname(rel)); @@ -200,7 +200,7 @@ static int keep_backup(char *fname) int ret_code; /* return if no file to keep */ - if (do_lstat(fname, &sx.st) < 0) + if (x_lstat(fname, &sx.st, NULL) < 0) return 1; #ifdef SUPPORT_ACLS sx.acc_acl = sx.def_acl = NULL; --- old/clientserver.c +++ new/clientserver.c @@ -625,6 +625,11 @@ static int rsync_module(int f_in, int f_ ret = parse_arguments(&argc, (const char ***) &argv, 0); quiet = 0; /* Don't let someone try to be tricky. */ + if (lp_fake_super(i)) + am_root = -1; + else if (am_root < 0) /* Treat --fake-super from client as --super. */ + am_root = 2; + if (filesfrom_fd == 0) filesfrom_fd = f_in; --- old/flist.c +++ new/flist.c @@ -181,7 +181,7 @@ static int readlink_stat(const char *pat } return 0; #else - return do_stat(path, stp); + return x_stat(path, stp, NULL); #endif } @@ -189,17 +189,17 @@ int link_stat(const char *path, STRUCT_S { #ifdef SUPPORT_LINKS if (copy_links) - return do_stat(path, stp); - if (do_lstat(path, stp) < 0) + return x_stat(path, stp, NULL); + if (x_lstat(path, stp, NULL) < 0) return -1; if (follow_dirlinks && S_ISLNK(stp->st_mode)) { STRUCT_STAT st; - if (do_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode)) *stp = st; } return 0; #else - return do_stat(path, stp); + return x_stat(path, stp, NULL); #endif } @@ -793,7 +793,7 @@ struct file_struct *make_file(char *fnam if (save_errno == ENOENT) { #ifdef SUPPORT_LINKS /* Avoid "vanished" error if symlink points nowhere. */ - if (copy_links && do_lstat(thisname, &st) == 0 + if (copy_links && x_lstat(thisname, &st, NULL) == 0 && S_ISLNK(st.st_mode)) { io_error |= IOERR_GENERAL; rprintf(FERROR, "symlink has no referent: %s\n", @@ -963,7 +963,7 @@ struct file_struct *make_file(char *fnam int save_mode = file->mode; file->mode = S_IFDIR; /* Find a directory with our name. */ if (flist_find(the_file_list, file) >= 0 - && do_stat(thisname, &st2) == 0 && S_ISDIR(st2.st_mode)) { + && x_stat(thisname, &st2, NULL) == 0 && S_ISDIR(st2.st_mode)) { file->modtime = st2.st_mtime; file->length = st2.st_size; file->mode = st2.st_mode; --- 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/receiver.c +++ new/receiver.c @@ -528,7 +528,7 @@ int recv_files(int f_in, struct file_lis if (fd1 == -1) { st.st_mode = 0; st.st_size = 0; - } else if (do_fstat(fd1,&st) != 0) { + } else if (x_fstat(fd1, &st, NULL) != 0) { rsyserr(FERROR, errno, "fstat %s failed", full_fname(fnamecmp)); discard_receive_data(f_in, file->length); --- old/rsync.c +++ new/rsync.c @@ -197,7 +197,9 @@ int set_file_attrs(char *fname, struct f (long)sxp->st.st_gid, (long)file->gid); } } - if (do_lchown(fname, + if (am_root < 0) + ; + else if (do_lchown(fname, change_uid ? file->uid : sxp->st.st_uid, change_gid ? file->gid : sxp->st.st_gid) != 0) { /* shouldn't have attempted to change uid or gid @@ -206,7 +208,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 */ @@ -224,6 +226,24 @@ int set_file_attrs(char *fname, struct f if (preserve_xattrs && set_xattr(fname, file, sxp) == 0) updated = 1; #endif + + if (am_root < 0 && !S_ISLNK(file->mode)) { + switch (set_stat_xattr(fname, file)) { + case 0: + break; + case -1: + rsyserr(FERROR, errno, + "write of stat xattr failed for %s", + full_fname(fname)); + break; + case -2: + rsyserr(FERROR, errno, + "delete of stat xattr failed for %s", + full_fname(fname)); + break; + } + } + #ifdef SUPPORT_ACLS /* It's OK to call set_acl() now, even for a dir, as the generator * will enable owner-writability using chmod, if necessary. @@ -237,7 +257,15 @@ 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 = (S_ISDIR(file->mode) ? 0777 : 0666) & ~orig_umask; + if ((sxp->st.st_mode & CHMOD_BITS) != mode) + ret = do_chmod(fname, mode); + else + ret = 0; + } else + ret = do_chmod(fname, new_mode); if (ret < 0) { rsyserr(FERROR, errno, "failed to set permissions on %s", --- old/syscall.c +++ new/syscall.c @@ -28,6 +28,7 @@ #endif extern int dry_run; +extern int am_root; extern int read_only; extern int list_only; extern int preserve_perms; @@ -79,6 +80,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); --- 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,11 +26,15 @@ #ifdef SUPPORT_XATTRS extern int dry_run; +extern int am_root; +extern mode_t orig_umask; extern unsigned int file_struct_len; #define RSYNC_XAL_INITIAL 5 #define RSYNC_XAL_LIST_INITIAL 100 +#define FAKE_XATTR "user.rsync%stat" + typedef struct { char *name; char *datum; @@ -130,9 +134,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[10] == '%' && 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 +295,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[10] == '%' && 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; @@ -365,4 +384,100 @@ int set_xattr(const char *fname, const s return rsync_xal_set(fname, lst + ndx); /* TODO: This needs to return 1 if no xattrs changed! */ } +int get_stat_xattr(const char *fname, int fd, STRUCT_STAT *st) +{ + int mode, rdev_major, rdev_minor, uid, gid, len; + char buf[256]; + + if (fname) + len = sys_lgetxattr(fname, FAKE_XATTR, buf, sizeof buf - 1); + else + len = sys_fgetxattr(fd, FAKE_XATTR, buf, sizeof buf - 1); + if (len < 0 || len >= (int)sizeof buf) { + if (errno == ENOTSUP || errno == ENOATTR) + return -1; + return -1; + } + buf[len] = '\0'; + + if (sscanf(buf, "%o %d,%d %d:%d", + &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) { + errno = EINVAL; + return -1; + } + + st->st_mode = mode; + st->st_rdev = MAKEDEV(rdev_major, rdev_minor); + st->st_uid = uid; + st->st_gid = gid; + + return 0; +} + +int set_stat_xattr(const char *fname, struct file_struct *file) +{ + STRUCT_STAT fst, xst; + dev_t rdev; + + if (dry_run) + return 0; + + if (x_stat(fname, &fst, &xst) < 0) + return -1; + + if (IS_DEVICE(file->mode) || IS_SPECIAL(file->mode)) + rdev = file->u.rdev; + else + rdev = 0; + + /* This is the mode that the file will be. */ + fst.st_mode &= ~CHMOD_BITS; + fst.st_mode |= (S_ISDIR(file->mode) ? 0777 : 0666) & ~orig_umask; + if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode)) + fst.st_rdev = 0; /* just in case */ + + if (fst.st_mode == file->mode && fst.st_rdev == rdev + && fst.st_uid == file->uid && fst.st_gid == file->gid) { + /* xst.st_mode will be 0 if there's no current stat xattr */ + if (xst.st_mode && sys_lremovexattr(fname, FAKE_XATTR) < 0) + return -2; + return 0; + } + + if (xst.st_mode != file->mode || xst.st_rdev != rdev + || xst.st_uid != file->uid || xst.st_gid != file->gid) { + char buf[256]; + int len = snprintf(buf, sizeof buf, "%o %u,%u %u:%u", + (int)file->mode, + (int)major(rdev), (int)minor(rdev), + (int)file->uid, (int)file->gid); + return sys_lsetxattr(fname, FAKE_XATTR, buf, len, 0); + } + return 0; +} + +int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst) +{ + int ret = do_stat(fname, fst); + if (get_stat_xattr(fname, -1, xst? xst : fst) != 0 && xst) + xst->st_mode = 0; + return ret; +} + +int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst) +{ + int ret = do_lstat(fname, fst); + if (get_stat_xattr(fname, -1, xst? xst : fst) != 0 && xst) + xst->st_mode = 0; + return ret; +} + +int x_fstat(int fd, STRUCT_STAT *fst, STRUCT_STAT *xst) +{ + int ret = do_fstat(fd, fst); + if (get_stat_xattr(NULL, fd, xst? xst : fst) != 0 && xst) + xst->st_mode = 0; + return ret; +} + #endif /* SUPPORT_XATTRS */