| 1 | Depends-On-Patch: acls.diff |
| 2 | Depends-On-Patch: xattrs.diff |
| 3 | |
| 4 | This patch adds a new option: --fake-super, which tells rsync to copy in a |
| 5 | fake super-user mode that stores various file attributes in an extended- |
| 6 | attribute value instead of as real file-system attributes. The items |
| 7 | affected are: |
| 8 | |
| 9 | mode the real mode of a file is always (666 & umask) while |
| 10 | the real mode of a directory is always (777 & umask). |
| 11 | |
| 12 | rdev devices and special files are created as zero-length |
| 13 | normal files. |
| 14 | |
| 15 | uid the real owner is always left unchanged. |
| 16 | |
| 17 | gid the real group is always left unchanged. |
| 18 | |
| 19 | A daemon can set "fake super = yes" in the rsync.conf file for any module |
| 20 | that you'd like to run without root perms while pretending it has them (the |
| 21 | client cannot affect this). |
| 22 | |
| 23 | The --fake-super option only affects the side where the option is used. To |
| 24 | affect the remote side of a remote-shell connection, specify an rsync path: |
| 25 | |
| 26 | rsync -av --rsync-path='rsync --fake-super' /src/ host:/dest/ |
| 27 | |
| 28 | For a local copy where you want to affect only one side or the other, |
| 29 | you'll need to turn the copy into a remote copy to localhost. |
| 30 | |
| 31 | After applying this patch, run these commands for a successful build: |
| 32 | |
| 33 | ./prepare-source |
| 34 | ./configure |
| 35 | make |
| 36 | |
| 37 | --- old/Makefile.in |
| 38 | +++ new/Makefile.in |
| 39 | @@ -41,7 +41,7 @@ popt_OBJS=popt/findme.o popt/popt.o po |
| 40 | popt/popthelp.o popt/poptparse.o |
| 41 | OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@ |
| 42 | |
| 43 | -TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o |
| 44 | +TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattr.o |
| 45 | |
| 46 | # Programs we must have to run the test cases |
| 47 | CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \ |
| 48 | @@ -83,11 +83,11 @@ getgroups$(EXEEXT): getgroups.o |
| 49 | getfsdev$(EXEEXT): getfsdev.o |
| 50 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS) |
| 51 | |
| 52 | -TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o |
| 53 | +TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o lib/sysxattr.o |
| 54 | trimslash$(EXEEXT): $(TRIMSLASH_OBJ) |
| 55 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS) |
| 56 | |
| 57 | -T_UNSAFE_OBJ = t_unsafe.o syscall.o util.o t_stub.o lib/compat.o lib/snprintf.o |
| 58 | +T_UNSAFE_OBJ = t_unsafe.o syscall.o util.o t_stub.o lib/compat.o lib/snprintf.o lib/sysxattr.o |
| 59 | t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ) |
| 60 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS) |
| 61 | |
| 62 | --- old/clientserver.c |
| 63 | +++ new/clientserver.c |
| 64 | @@ -625,6 +625,11 @@ static int rsync_module(int f_in, int f_ |
| 65 | ret = parse_arguments(&argc, (const char ***) &argv, 0); |
| 66 | quiet = 0; /* Don't let someone try to be tricky. */ |
| 67 | |
| 68 | + if (lp_fake_super(i)) |
| 69 | + am_root = -1; |
| 70 | + else if (am_root < 0) /* Treat --fake-super from client as --super. */ |
| 71 | + am_root = 2; |
| 72 | + |
| 73 | if (filesfrom_fd == 0) |
| 74 | filesfrom_fd = f_in; |
| 75 | |
| 76 | --- old/generator.c |
| 77 | +++ new/generator.c |
| 78 | @@ -1510,13 +1510,14 @@ void generate_files(int f_out, struct fi |
| 79 | recv_generator(fbuf, file, i, itemizing, maybe_ATTRS_REPORT, |
| 80 | code, f_out); |
| 81 | |
| 82 | - /* We need to ensure that any dirs we create have writeable |
| 83 | + /* We need to ensure that any dirs we create have rwx |
| 84 | * permissions during the time we are putting files within |
| 85 | * them. This is then fixed after the transfer is done. */ |
| 86 | #ifdef HAVE_CHMOD |
| 87 | - if (!am_root && S_ISDIR(file->mode) && !(file->mode & S_IWUSR) |
| 88 | + if (am_root <= 0 && S_ISDIR(file->mode) |
| 89 | + && (file->mode & S_IRWXU) != S_IRWXU |
| 90 | && dir_tweaking) { |
| 91 | - mode_t mode = file->mode | S_IWUSR; /* user write */ |
| 92 | + mode_t mode = file->mode | S_IRWXU; /* user rwx */ |
| 93 | char *fname = local_name ? local_name : fbuf; |
| 94 | if (do_chmod(fname, mode) < 0) { |
| 95 | rsyserr(FERROR, errno, |
| 96 | --- old/loadparm.c |
| 97 | +++ new/loadparm.c |
| 98 | @@ -150,6 +150,7 @@ typedef struct |
| 99 | int syslog_facility; |
| 100 | int timeout; |
| 101 | |
| 102 | + BOOL fake_super; |
| 103 | BOOL ignore_errors; |
| 104 | BOOL ignore_nonreadable; |
| 105 | BOOL list; |
| 106 | @@ -197,6 +198,7 @@ static service sDefault = |
| 107 | /* syslog_facility; */ LOG_DAEMON, |
| 108 | /* timeout; */ 0, |
| 109 | |
| 110 | + /* fake_super; */ False, |
| 111 | /* ignore_errors; */ False, |
| 112 | /* ignore_nonreadable; */ False, |
| 113 | /* list; */ True, |
| 114 | @@ -298,6 +300,7 @@ static struct parm_struct parm_table[] = |
| 115 | {"dont compress", P_STRING, P_LOCAL, &sDefault.dont_compress, NULL,0}, |
| 116 | {"exclude from", P_STRING, P_LOCAL, &sDefault.exclude_from, NULL,0}, |
| 117 | {"exclude", P_STRING, P_LOCAL, &sDefault.exclude, NULL,0}, |
| 118 | + {"fake super", P_BOOL, P_LOCAL, &sDefault.fake_super, NULL,0}, |
| 119 | {"filter", P_STRING, P_LOCAL, &sDefault.filter, NULL,0}, |
| 120 | {"gid", P_STRING, P_LOCAL, &sDefault.gid, NULL,0}, |
| 121 | {"hosts allow", P_STRING, P_LOCAL, &sDefault.hosts_allow, NULL,0}, |
| 122 | @@ -412,6 +415,7 @@ FN_LOCAL_INTEGER(lp_max_connections, max |
| 123 | FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity) |
| 124 | FN_LOCAL_INTEGER(lp_timeout, timeout) |
| 125 | |
| 126 | +FN_LOCAL_BOOL(lp_fake_super, fake_super) |
| 127 | FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors) |
| 128 | FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable) |
| 129 | FN_LOCAL_BOOL(lp_list, list) |
| 130 | @@ -816,7 +820,7 @@ BOOL lp_load(char *pszFname, int globals |
| 131 | |
| 132 | if (pszFname) |
| 133 | pstrcpy(n2,pszFname); |
| 134 | - else if (am_server && !am_root) |
| 135 | + else if (am_server && am_root <= 0) |
| 136 | pstrcpy(n2,RSYNCD_USERCONF); |
| 137 | else |
| 138 | pstrcpy(n2,RSYNCD_SYSCONF); |
| 139 | --- old/options.c |
| 140 | +++ new/options.c |
| 141 | @@ -73,7 +73,7 @@ int protocol_version = PROTOCOL_VERSION; |
| 142 | int sparse_files = 0; |
| 143 | int do_compression = 0; |
| 144 | int def_compress_level = Z_DEFAULT_COMPRESSION; |
| 145 | -int am_root = 0; |
| 146 | +int am_root = 0; /* 0 = normal, 1 = super, 2 = --super, -1 = --fake-super */ |
| 147 | int am_server = 0; |
| 148 | int am_sender = 0; |
| 149 | int am_generator = 0; |
| 150 | @@ -330,6 +330,7 @@ void usage(enum logcode F) |
| 151 | rprintf(F," -t, --times preserve times\n"); |
| 152 | rprintf(F," -O, --omit-dir-times omit directories when preserving times\n"); |
| 153 | rprintf(F," --super receiver attempts super-user activities\n"); |
| 154 | + rprintf(F," --fake-super fake root by storing/reading ownership/etc in EAs\n"); |
| 155 | rprintf(F," -S, --sparse handle sparse files efficiently\n"); |
| 156 | rprintf(F," -n, --dry-run show what would have been transferred\n"); |
| 157 | rprintf(F," -W, --whole-file copy files whole (without rsync algorithm)\n"); |
| 158 | @@ -454,6 +455,7 @@ static struct poptOption long_options[] |
| 159 | {"modify-window", 0, POPT_ARG_INT, &modify_window, OPT_MODIFY_WINDOW, 0, 0 }, |
| 160 | {"super", 0, POPT_ARG_VAL, &am_root, 2, 0, 0 }, |
| 161 | {"no-super", 0, POPT_ARG_VAL, &am_root, 0, 0, 0 }, |
| 162 | + {"fake-super", 0, POPT_ARG_VAL, &am_root, -1, 0, 0 }, |
| 163 | {"owner", 'o', POPT_ARG_VAL, &preserve_uid, 1, 0, 0 }, |
| 164 | {"no-owner", 0, POPT_ARG_VAL, &preserve_uid, 0, 0, 0 }, |
| 165 | {"no-o", 0, POPT_ARG_VAL, &preserve_uid, 0, 0, 0 }, |
| 166 | --- old/rsync.c |
| 167 | +++ new/rsync.c |
| 168 | @@ -197,7 +197,9 @@ int set_file_attrs(char *fname, struct f |
| 169 | (long)sxp->st.st_gid, (long)file->gid); |
| 170 | } |
| 171 | } |
| 172 | - if (do_lchown(fname, |
| 173 | + if (am_root < 0) |
| 174 | + ; |
| 175 | + else if (do_lchown(fname, |
| 176 | change_uid ? file->uid : sxp->st.st_uid, |
| 177 | change_gid ? file->gid : sxp->st.st_gid) != 0) { |
| 178 | /* shouldn't have attempted to change uid or gid |
| 179 | @@ -206,7 +208,7 @@ int set_file_attrs(char *fname, struct f |
| 180 | change_uid ? "chown" : "chgrp", |
| 181 | full_fname(fname)); |
| 182 | goto cleanup; |
| 183 | - } |
| 184 | + } else |
| 185 | /* a lchown had been done - we have to re-stat if the |
| 186 | * destination had the setuid or setgid bits set due |
| 187 | * to the side effect of the chown call */ |
| 188 | @@ -237,7 +239,15 @@ int set_file_attrs(char *fname, struct f |
| 189 | |
| 190 | #ifdef HAVE_CHMOD |
| 191 | if ((sxp->st.st_mode & CHMOD_BITS) != (new_mode & CHMOD_BITS)) { |
| 192 | - int ret = do_chmod(fname, new_mode); |
| 193 | + int ret; |
| 194 | + if (am_root < 0) { |
| 195 | + mode_t mode = 0666 & ~orig_umask; |
| 196 | + if ((sxp->st.st_mode & CHMOD_BITS) != mode) |
| 197 | + ret = do_chmod(fname, mode); |
| 198 | + else |
| 199 | + ret = 0; |
| 200 | + } else |
| 201 | + ret = do_chmod(fname, new_mode); |
| 202 | if (ret < 0) { |
| 203 | rsyserr(FERROR, errno, |
| 204 | "failed to set permissions on %s", |
| 205 | @@ -249,6 +259,23 @@ int set_file_attrs(char *fname, struct f |
| 206 | } |
| 207 | #endif |
| 208 | |
| 209 | + if (am_root < 0) { |
| 210 | + switch (set_stat_xattr(fname, file)) { |
| 211 | + case 0: |
| 212 | + break; |
| 213 | + case -1: |
| 214 | + rsyserr(FERROR, errno, |
| 215 | + "write of stat xattr failed for %s", |
| 216 | + full_fname(fname)); |
| 217 | + break; |
| 218 | + case -2: |
| 219 | + rsyserr(FERROR, errno, |
| 220 | + "delete of stat xattr failed for %s", |
| 221 | + full_fname(fname)); |
| 222 | + break; |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | if (verbose > 1 && flags & ATTRS_REPORT) { |
| 227 | if (updated) |
| 228 | rprintf(FCLIENT, "%s\n", fname); |
| 229 | --- old/rsync.h |
| 230 | +++ new/rsync.h |
| 231 | @@ -35,6 +35,8 @@ |
| 232 | |
| 233 | #define BACKUP_SUFFIX "~" |
| 234 | |
| 235 | +#define FAKE_XATTR "user.rsync%stat" |
| 236 | + |
| 237 | /* a non-zero CHAR_OFFSET makes the rolling sum stronger, but is |
| 238 | incompatible with older versions :-( */ |
| 239 | #define CHAR_OFFSET 0 |
| 240 | --- old/syscall.c |
| 241 | +++ new/syscall.c |
| 242 | @@ -22,12 +22,14 @@ |
| 243 | */ |
| 244 | |
| 245 | #include "rsync.h" |
| 246 | +#include "lib/sysxattr.h" |
| 247 | |
| 248 | #if !defined MKNOD_CREATES_SOCKETS && defined HAVE_SYS_UN_H |
| 249 | #include <sys/un.h> |
| 250 | #endif |
| 251 | |
| 252 | extern int dry_run; |
| 253 | +extern int am_root; |
| 254 | extern int read_only; |
| 255 | extern int list_only; |
| 256 | extern int preserve_perms; |
| 257 | @@ -79,6 +81,15 @@ int do_mknod(char *pathname, mode_t mode |
| 258 | { |
| 259 | if (dry_run) return 0; |
| 260 | RETURN_ERROR_IF_RO_OR_LO; |
| 261 | + |
| 262 | + /* For --fake-super, we create a normal file with mode 0600. */ |
| 263 | + if (am_root < 0) { |
| 264 | + int fd = open(pathname, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR); |
| 265 | + if (fd < 0 || close(fd) < 0) |
| 266 | + return -1; |
| 267 | + return 0; |
| 268 | + } |
| 269 | + |
| 270 | #if !defined MKNOD_CREATES_FIFOS && defined HAVE_MKFIFO |
| 271 | if (S_ISFIFO(mode)) |
| 272 | return mkfifo(pathname, mode); |
| 273 | @@ -215,23 +226,98 @@ int do_mkstemp(char *template, mode_t pe |
| 274 | #endif |
| 275 | } |
| 276 | |
| 277 | +int get_stat_xattr(const char *fname, STRUCT_STAT *st) |
| 278 | +{ |
| 279 | + int mode, rdev_major, rdev_minor, uid, gid, len; |
| 280 | + char buf[256]; |
| 281 | + |
| 282 | + len = sys_lgetxattr(fname, FAKE_XATTR, buf, sizeof buf - 1); |
| 283 | + if (len < 0 || len >= (int)sizeof buf) { |
| 284 | + if (errno == ENOTSUP || errno == ENOATTR) |
| 285 | + return -1; |
| 286 | + return -1; |
| 287 | + } |
| 288 | + buf[len] = '\0'; |
| 289 | + |
| 290 | + if (sscanf(buf, "%o %d,%d %d:%d", |
| 291 | + &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) { |
| 292 | + errno = EINVAL; |
| 293 | + return -1; |
| 294 | + } |
| 295 | + |
| 296 | + st->st_mode = mode; |
| 297 | + st->st_rdev = MAKEDEV(rdev_major, rdev_minor); |
| 298 | + st->st_uid = uid; |
| 299 | + st->st_gid = gid; |
| 300 | + |
| 301 | + return 0; |
| 302 | +} |
| 303 | + |
| 304 | +int set_stat_xattr(const char *fname, struct file_struct *file) |
| 305 | +{ |
| 306 | + STRUCT_STAT fst, xst; |
| 307 | + int have_xattr; |
| 308 | + dev_t rdev; |
| 309 | + if (dry_run) return 0; |
| 310 | + RETURN_ERROR_IF_RO_OR_LO; |
| 311 | + |
| 312 | + am_root = 2; /* get real stat() w/o xattr overlay */ |
| 313 | + do_stat(fname, &fst); |
| 314 | + am_root = -1; |
| 315 | + have_xattr = get_stat_xattr(fname, &xst) == 0; |
| 316 | + |
| 317 | + if (IS_DEVICE(file->mode) || IS_SPECIAL(file->mode)) |
| 318 | + rdev = file->u.rdev; |
| 319 | + else |
| 320 | + rdev = 0; |
| 321 | + if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode)) |
| 322 | + fst.st_rdev = 0; /* just in case */ |
| 323 | + |
| 324 | + if (fst.st_mode == file->mode && fst.st_rdev == rdev |
| 325 | + && fst.st_uid == file->uid && fst.st_gid == file->gid) { |
| 326 | + if (have_xattr && sys_lremovexattr(fname, FAKE_XATTR) < 0) |
| 327 | + return -2; |
| 328 | + return 0; |
| 329 | + } |
| 330 | + |
| 331 | + if (!have_xattr |
| 332 | + || xst.st_mode != file->mode || xst.st_rdev != rdev |
| 333 | + || xst.st_uid != file->uid || xst.st_gid != file->gid) { |
| 334 | + char buf[256]; |
| 335 | + int len = snprintf(buf, sizeof buf, "%o %u,%u %u:%u", |
| 336 | + (int)file->mode, |
| 337 | + (int)major(rdev), (int)minor(rdev), |
| 338 | + (int)file->uid, (int)file->gid); |
| 339 | + return sys_lsetxattr(fname, FAKE_XATTR, buf, len, 0); |
| 340 | + } |
| 341 | + return 0; |
| 342 | +} |
| 343 | + |
| 344 | int do_stat(const char *fname, STRUCT_STAT *st) |
| 345 | { |
| 346 | + int ret; |
| 347 | #ifdef USE_STAT64_FUNCS |
| 348 | - return stat64(fname, st); |
| 349 | + ret = stat64(fname, st); |
| 350 | #else |
| 351 | - return stat(fname, st); |
| 352 | + ret = stat(fname, st); |
| 353 | #endif |
| 354 | + if (am_root < 0 && ret == 0) |
| 355 | + get_stat_xattr(fname, st); |
| 356 | + return ret; |
| 357 | } |
| 358 | |
| 359 | int do_lstat(const char *fname, STRUCT_STAT *st) |
| 360 | { |
| 361 | #ifdef SUPPORT_LINKS |
| 362 | + int ret; |
| 363 | # ifdef USE_STAT64_FUNCS |
| 364 | - return lstat64(fname, st); |
| 365 | + ret = lstat64(fname, st); |
| 366 | # else |
| 367 | - return lstat(fname, st); |
| 368 | + ret = lstat(fname, st); |
| 369 | # endif |
| 370 | + if (am_root < 0 && ret == 0) |
| 371 | + get_stat_xattr(fname, st); |
| 372 | + return ret; |
| 373 | #else |
| 374 | return do_stat(fname, st); |
| 375 | #endif |
| 376 | --- old/t_unsafe.c |
| 377 | +++ new/t_unsafe.c |
| 378 | @@ -24,7 +24,11 @@ |
| 379 | |
| 380 | #include "rsync.h" |
| 381 | |
| 382 | -int dry_run, read_only, list_only, verbose; |
| 383 | +int dry_run = 0; |
| 384 | +int am_root = 0; |
| 385 | +int read_only = 0; |
| 386 | +int list_only = 0; |
| 387 | +int verbose = 0; |
| 388 | int preserve_perms = 0; |
| 389 | |
| 390 | int |
| 391 | --- old/tls.c |
| 392 | +++ new/tls.c |
| 393 | @@ -39,6 +39,7 @@ |
| 394 | |
| 395 | /* These are to make syscall.o shut up. */ |
| 396 | int dry_run = 0; |
| 397 | +int am_root = 0; /* TODO: add option to set this to -1. */ |
| 398 | int read_only = 1; |
| 399 | int list_only = 0; |
| 400 | int preserve_perms = 0; |
| 401 | --- old/trimslash.c |
| 402 | +++ new/trimslash.c |
| 403 | @@ -23,6 +23,7 @@ |
| 404 | |
| 405 | /* These are to make syscall.o shut up. */ |
| 406 | int dry_run = 0; |
| 407 | +int am_root = 0; |
| 408 | int read_only = 1; |
| 409 | int list_only = 0; |
| 410 | int preserve_perms = 0; |
| 411 | --- old/xattr.c |
| 412 | +++ new/xattr.c |
| 413 | @@ -26,6 +26,7 @@ |
| 414 | #ifdef SUPPORT_XATTRS |
| 415 | |
| 416 | extern int dry_run; |
| 417 | +extern int am_root; |
| 418 | extern unsigned int file_struct_len; |
| 419 | |
| 420 | #define RSYNC_XAL_INITIAL 5 |
| 421 | @@ -130,9 +131,15 @@ static int rsync_xal_get(const char *fna |
| 422 | if (name_size == 0) |
| 423 | return 0; |
| 424 | for (left = name_size, name = namebuf; left > 0 ; left -= len, name += len) { |
| 425 | - rsync_xa *rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL); |
| 426 | + rsync_xa *rxas; |
| 427 | |
| 428 | len = strlen(name) + 1; |
| 429 | + if (am_root < 0 && len == sizeof FAKE_XATTR |
| 430 | + && name[10] == '%' && strcmp(name, FAKE_XATTR) == 0) |
| 431 | + continue; |
| 432 | + |
| 433 | + rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL); |
| 434 | + |
| 435 | datum_size = sys_lgetxattr(fname, name, NULL, 0); |
| 436 | if (datum_size < 0) { |
| 437 | if (errno == ENOTSUP) |
| 438 | @@ -285,10 +292,19 @@ void receive_xattr(struct file_struct *f |
| 439 | out_of_memory("receive_xattr"); |
| 440 | read_buf(f, ptr, name_len); |
| 441 | read_buf(f, ptr + name_len, datum_len); |
| 442 | + |
| 443 | + if (am_root < 0 && name_len == sizeof FAKE_XATTR |
| 444 | + && ptr[10] == '%' && strcmp(ptr, FAKE_XATTR) == 0) { |
| 445 | + free(ptr); |
| 446 | + temp_xattr.count--; |
| 447 | + continue; |
| 448 | + } |
| 449 | + |
| 450 | rxa->name_len = name_len; |
| 451 | rxa->datum_len = datum_len; |
| 452 | rxa->name = ptr; |
| 453 | rxa->datum = ptr + name_len; |
| 454 | + |
| 455 | #ifdef HAVE_OSX_XATTRS |
| 456 | if (strncmp(rxa->name, unique_prefix, upre_len) == 0) { |
| 457 | rxa->name_len -= upre_len; |