-This adds a --usermap and a --groupmap option. See the man page for
-more details.
-
-To use this patch, run these commands for a successful build:
-
- patch -p1 <patches/usermap.diff
- ./configure (optional if already run)
- make
-
-diff --git a/flist.c b/flist.c
---- a/flist.c
-+++ b/flist.c
-@@ -71,6 +71,7 @@ extern int sender_symlink_iconv;
- extern int unsort_ndx;
- extern struct stats stats;
- extern char *filesfrom_host;
-+extern char *usermap, *groupmap;
-
- extern char curr_dir[MAXPATHLEN];
-
-@@ -767,7 +768,7 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
- uid = (uid_t)read_varint(f);
- if (xflags & XMIT_USER_NAME_FOLLOWS)
- uid = recv_user_name(f, uid);
-- else if (inc_recurse && am_root && !numeric_ids)
-+ else if (inc_recurse && am_root && (!numeric_ids || usermap))
- uid = match_uid(uid);
- }
- }
-@@ -779,7 +780,7 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
- gid_flags = 0;
- if (xflags & XMIT_GROUP_NAME_FOLLOWS)
- gid = recv_group_name(f, gid, &gid_flags);
-- else if (inc_recurse && (!am_root || !numeric_ids))
-+ else if (inc_recurse && (!am_root || !numeric_ids || groupmap))
- gid = match_gid(gid, &gid_flags);
- }
- }
-@@ -2248,8 +2249,13 @@ struct file_list *recv_file_list(int f)
- int64 start_read;
- int save_verbose = verbose;
-
-- if (!first_flist)
-+ if (!first_flist) {
- rprintf(FLOG, "receiving file list\n");
-+ if (usermap)
-+ parse_name_map(usermap, 1);
-+ if (groupmap)
-+ parse_name_map(groupmap, 0);
-+ }
- if (show_filelist_p())
- start_filelist_progress("receiving file list");
- else if (inc_recurse && verbose && !am_server && !first_flist)
-diff --git a/options.c b/options.c
---- a/options.c
-+++ b/options.c
-@@ -167,6 +167,8 @@ char *rsync_path = RSYNC_PATH;
- char *backup_dir = NULL;
- char backup_dir_buf[MAXPATHLEN];
- char *sockopts = NULL;
-+char *usermap = NULL;
-+char *groupmap = NULL;
- int rsync_port = 0;
- int compare_dest = 0;
- int copy_dest = 0;
-@@ -385,6 +387,9 @@ void usage(enum logcode F)
- rprintf(F," --delay-updates put all updated files into place at transfer's end\n");
- rprintf(F," -m, --prune-empty-dirs prune empty directory chains from the file-list\n");
- rprintf(F," --numeric-ids don't map uid/gid values by user/group name\n");
-+ rprintf(F," --usermap=STRING custom username mapping\n");
-+ rprintf(F," --groupmap=STRING custom groupname mapping\n");
-+ rprintf(F," --chown=USER:GROUP simple username/groupname mapping\n");
- rprintf(F," --timeout=SECONDS set I/O timeout in seconds\n");
- rprintf(F," --contimeout=SECONDS set daemon connection timeout in seconds\n");
- rprintf(F," -I, --ignore-times don't skip files that match in size and mod-time\n");
-@@ -447,7 +452,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
- OPT_FILTER, OPT_COMPARE_DEST, OPT_COPY_DEST, OPT_LINK_DEST, OPT_HELP,
- OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
- OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
-- OPT_NO_D, OPT_APPEND, OPT_NO_ICONV,
-+ OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN,
- OPT_SERVER, OPT_REFUSED_BASE = 9000};
-
- static struct poptOption long_options[] = {
-@@ -623,6 +628,9 @@ static struct poptOption long_options[] = {
- {"no-s", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0},
- {"numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 1, 0, 0 },
- {"no-numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 0, 0, 0 },
-+ {"usermap", 0, POPT_ARG_STRING, 0, OPT_USERMAP, 0, 0 },
-+ {"groupmap", 0, POPT_ARG_STRING, 0, OPT_GROUPMAP, 0, 0 },
-+ {"chown", 0, POPT_ARG_STRING, 0, OPT_CHOWN, 0, 0 },
- {"timeout", 0, POPT_ARG_INT, &io_timeout, 0, 0, 0 },
- {"no-timeout", 0, POPT_ARG_VAL, &io_timeout, 0, 0, 0 },
- {"contimeout", 0, POPT_ARG_INT, &connect_timeout, 0, 0, 0 },
-@@ -1229,6 +1237,43 @@ int parse_arguments(int *argc_p, const char ***argv_p)
- }
- break;
-
-+ case OPT_USERMAP:
-+ if (usermap) {
-+ snprintf(err_buf, sizeof err_buf,
-+ "You can only specify --usermap once.\n");
-+ return 0;
-+ }
-+ usermap = (char *)poptGetOptArg(pc);
-+ break;
-+
-+ case OPT_GROUPMAP:
-+ if (groupmap) {
-+ snprintf(err_buf, sizeof err_buf,
-+ "You can only specify --groupmap once.\n");
-+ return 0;
-+ }
-+ groupmap = (char *)poptGetOptArg(pc);
-+ break;
-+
-+ case OPT_CHOWN:
-+ if (usermap || groupmap) {
-+ snprintf(err_buf, sizeof err_buf,
-+ "You can only specify --chown once.\n");
-+ return 0;
-+ } else {
-+ const char *chown = poptGetOptArg(pc);
-+ int len;
-+ if ((arg = strchr(chown, ':')) != NULL) {
-+ if (arg[1] && asprintf(&groupmap, "*:%s", arg+1) < 0)
-+ out_of_memory("parse_arguments");
-+ len = arg - chown;
-+ } else
-+ len = strlen(chown);
-+ if (len && asprintf(&usermap, "*:%.*s", len, chown) < 0)
-+ out_of_memory("parse_arguments");
-+ }
-+ break;
-+
- case OPT_HELP:
- usage(FINFO);
- exit_cleanup(0);
-@@ -2007,6 +2052,18 @@ void server_options(char **args, int *argc_p)
- args[ac++] = "--use-qsort";
-
- if (am_sender) {
-+ if (usermap) {
-+ if (asprintf(&arg, "--usermap=%s", usermap) < 0)
-+ goto oom;
-+ args[ac++] = arg;
-+ }
-+
-+ if (groupmap) {
-+ if (asprintf(&arg, "--groupmap=%s", groupmap) < 0)
-+ goto oom;
-+ args[ac++] = arg;
-+ }
-+
- if (ignore_existing)
- args[ac++] = "--ignore-existing";
-
-diff --git a/rsync.yo b/rsync.yo
---- a/rsync.yo
-+++ b/rsync.yo
-@@ -382,6 +382,9 @@ to the detailed description below for a complete description. verb(
- --delay-updates put all updated files into place at end
- -m, --prune-empty-dirs prune empty directory chains from file-list
- --numeric-ids don't map uid/gid values by user/group name
-+ --usermap=STRING custom username mapping
-+ --groupmap=STRING custom groupname mapping
-+ --chown=USER:GROUP simple username/groupname mapping
- --timeout=SECONDS set I/O timeout in seconds
- --contimeout=SECONDS set daemon connection timeout in seconds
- -I, --ignore-times don't skip files that match size and time
-@@ -1624,6 +1627,57 @@ from the source system is used instead. See also the comments on the
- the chroot setting affects rsync's ability to look up the names of the
- users and groups and what you can do about it.
-
-+dit(bf(--usermap=STRING, --groupmap=STRING)) These options allow you to
-+specify users and groups that should be mapped to other values by the
-+receiving side. The bf(STRING) is one or more bf(FROM):bf(TO) pairs of
-+values separated by commas. Any matching bf(FROM) value from the sender is
-+replaced with a bf(TO) value from the receiver. You may specify usernames
-+or user IDs for the bf(FROM) and bf(TO) values, and the bf(FROM) value may
-+also be a wild-card string, which will be matched against the sender's
-+names (wild-cards do NOT match against ID numbers, though see below for
-+why a '*' matches everything). You may instead specify a range of ID
-+numbers via an inclusive range: LOW-HIGH. For example:
-+
-+verb( --usermap=0-99:nobody,wayne:admin,*:normal --groupmap=usr:1,1:usr)
-+
-+The first match in the list is the one that is used. You should specify
-+all your user mappings using a single bf(--usermap) option, and/or all
-+your group mappings using a single bf(--groupmap) option.
-+
-+Note that the sender's name for the 0 user and group are not transmitted
-+to the receiver, so you should either match these values using a 0, or use
-+the names in effect on the receiving side (typically "root"). All other
-+bf(FROM) names match those in use on the sending side. All bf(TO) names
-+match those in use on the receiving side.
-+
-+Any IDs that do not have a name on the sending side are treated as having an
-+empty name for the purpose of matching. This allows them to be matched via
-+a "*" or using an empty name. For instance:
-+
-+verb( --usermap=:nobody --groupmap=*:nobody)
-+
-+When the bf(--numeric-ids) option is used,the sender does not send any
-+names, so all the IDs are treated as having an empty name. This means that
-+you will need to specify numeric bf(FROM) values if you want to map these
-+nameless IDs to different values.
-+
-+For the bf(--usermap) option to have any effect, the bf(-o) (bf(--owner))
-+option must be used (or implied), and the receiver will need to be running
-+as a super-user (see also the bf(--fake-super) option). For the bf(--groupmap)
-+option to have any effect, the bf(-g) (bf(--groups)) option must be used
-+(or implied), and the receiver will need to have permissions to set that
-+group.
-+
-+dit(bf(--chown=USER:GROUP)) This option forces all files to be owned by USER
-+with group GROUP. This is a simpler interface than using bf(--usermap) and
-+bf(--groupmap) directly, but it is implemented using those options internally,
-+so you cannot mix them. If either the USER or GROUP is empty, no mapping for
-+the omitted user/group will occur. If GROUP is empty, the trailing colon may
-+be omitted, but if USER is empty, a leading colon must be supplied.
-+
-+If you specify "--chown=foo:bar, this is exactly the same as specifying
-+"--usermap=*:foo --groupmap=*:bar", only easier.
-+
- dit(bf(--timeout=TIMEOUT)) This option allows you to set a maximum I/O
- timeout in seconds. If no data is transferred for the specified time
- then rsync will exit. The default is 0, which means no timeout.
-diff --git a/support/mapfrom b/support/mapfrom
-new file mode 100755
---- /dev/null
-+++ b/support/mapfrom
-@@ -0,0 +1,5 @@
-+#!/usr/bin/perl
-+while (<>) {
-+ push @_, "$2:$1" if /^(\w+):[^:]+:(\d+)/;
-+}
-+print join(',', @_), "\n";
-diff --git a/support/mapto b/support/mapto
-new file mode 100755
---- /dev/null
-+++ b/support/mapto
-@@ -0,0 +1,5 @@
-+#!/usr/bin/perl
-+while (<>) {
-+ push @_, "$1:$2" if /^(\w+):[^:]+:(\d+)/;
-+}
-+print join(',', @_), "\n";
-diff --git a/uidlist.c b/uidlist.c
---- a/uidlist.c
-+++ b/uidlist.c
-@@ -24,6 +24,7 @@
- * are special. */
-
- #include "rsync.h"
-+#include "ifuncs.h"
- #include "io.h"
-
- extern int verbose;
-@@ -32,6 +33,8 @@ extern int preserve_uid;
- extern int preserve_gid;
- extern int preserve_acls;
- extern int numeric_ids;
-+extern char *usermap;
-+extern char *groupmap;
-
- #ifdef HAVE_GETGROUPS
- # ifndef GETGROUPS_T
-@@ -41,6 +44,9 @@ extern int numeric_ids;
-
- #define GID_NONE ((gid_t)-1)
-
-+#define NFLAGS_WILD_NAME_MATCH (1<<0)
-+#define NFLAGS_NAME_MATCH (1<<1)
-+
- struct idlist {
- struct idlist *next;
- const char *name;
-@@ -48,8 +54,8 @@ struct idlist {
- uint16 flags;
- };
-
--static struct idlist *uidlist;
--static struct idlist *gidlist;
-+static struct idlist *uidlist, *uidmap;
-+static struct idlist *gidlist, *gidmap;
-
- static struct idlist *add_to_list(struct idlist **root, id_t id, const char *name,
- id_t id2, uint16 flags)
-@@ -84,22 +90,6 @@ static const char *gid_to_name(gid_t gid)
- return NULL;
- }
-
--static uid_t map_uid(uid_t id, const char *name)
--{
-- uid_t uid;
-- if (id != 0 && name_to_uid(name, &uid))
-- return uid;
-- return id;
--}
--
--static gid_t map_gid(gid_t id, const char *name)
--{
-- gid_t gid;
-- if (id != 0 && name_to_gid(name, &gid))
-- return gid;
-- return id;
--}
--
- static int is_in_group(gid_t gid)
- {
- #ifdef HAVE_GETGROUPS
-@@ -159,34 +149,53 @@ static int is_in_group(gid_t gid)
- #endif
- }
-
--/* Add a uid to the list of uids. Only called on receiving side. */
--static struct idlist *recv_add_uid(uid_t id, const char *name)
-+/* Add a uid/gid to its list of ids. Only called on receiving side. */
-+static struct idlist *recv_add_id(struct idlist **idlist_ptr, struct idlist *idmap,
-+ id_t id, const char *name)
- {
-- uid_t id2 = name ? map_uid(id, name) : id;
- struct idlist *node;
-+ int flag;
-+ id_t id2;
-
-- node = add_to_list(&uidlist, id, name, id2, 0);
-+ if (!name)
-+ name = "";
-
-- if (verbose > 3) {
-- rprintf(FINFO, "uid %u(%s) maps to %u\n",
-- (unsigned)id, name ? name : "", (unsigned)id2);
-+ for (node = idmap; node; node = node->next) {
-+ if (node->flags & NFLAGS_WILD_NAME_MATCH) {
-+ if (!wildmatch(node->name, name))
-+ continue;
-+ } else if (node->flags & NFLAGS_NAME_MATCH) {
-+ if (strcmp(node->name, name) != 0)
-+ continue;
-+ } else if (node->name) {
-+ if (id < node->id || id > (unsigned long)node->name)
-+ continue;
-+ } else {
-+ if (node->id != id)
-+ continue;
-+ }
-+ break;
- }
-+ if (node)
-+ id2 = node->id2;
-+ else if (*name && id) {
-+ if (idlist_ptr == &uidlist) {
-+ uid_t uid;
-+ id2 = name_to_uid(name, &uid) ? uid : id;
-+ } else {
-+ gid_t gid;
-+ id2 = name_to_gid(name, &gid) ? gid : id;
-+ }
-+ } else
-+ id2 = id;
-
-- return node;
--}
--
--/* Add a gid to the list of gids. Only called on receiving side. */
--static struct idlist *recv_add_gid(gid_t id, const char *name)
--{
-- gid_t id2 = name ? map_gid(id, name) : id;
-- struct idlist *node;
--
-- node = add_to_list(&gidlist, id, name, id2,
-- !am_root && !is_in_group(id2) ? FLAG_SKIP_GROUP : 0);
-+ flag = idlist_ptr == &gidlist && !am_root && !is_in_group(id2) ? FLAG_SKIP_GROUP : 0;
-+ node = add_to_list(idlist_ptr, id, *name ? name : NULL, id2, flag);
-
- if (verbose > 3) {
-- rprintf(FINFO, "gid %u(%s) maps to %u\n",
-- (unsigned)id, name ? name : "", (unsigned)id2);
-+ rprintf(FINFO, "%sid %u(%s) maps to %u\n",
-+ idlist_ptr == &uidlist ? "u" : "g",
-+ (unsigned)id, name, (unsigned)id2);
- }
-
- return node;
-@@ -195,12 +204,9 @@ static struct idlist *recv_add_gid(gid_t id, const char *name)
- /* this function is a definate candidate for a faster algorithm */
- uid_t match_uid(uid_t uid)
- {
-- static uid_t last_in, last_out;
-+ static uid_t last_in = -1, last_out = -1;
- struct idlist *list;
-
-- if (uid == 0)
-- return 0;
--
- if (uid == last_in)
- return last_out;
-
-@@ -208,10 +214,13 @@ uid_t match_uid(uid_t uid)
-
- for (list = uidlist; list; list = list->next) {
- if (list->id == uid)
-- return last_out = list->id2;
-+ break;
- }
-
-- return last_out = uid;
-+ if (!list)
-+ list = recv_add_id(&uidlist, uidmap, uid, NULL);
-+
-+ return last_out = list->id2;
- }
-
- gid_t match_gid(gid_t gid, uint16 *flags_ptr)
-@@ -227,7 +236,7 @@ gid_t match_gid(gid_t gid, uint16 *flags_ptr)
- break;
- }
- if (!list)
-- list = recv_add_gid(gid, NULL);
-+ list = recv_add_id(&gidlist, gidmap, gid, NULL);
- last = list;
- }
-
-@@ -320,7 +329,7 @@ uid_t recv_user_name(int f, uid_t uid)
- free(name);
- name = NULL;
- }
-- node = recv_add_uid(uid, name); /* node keeps name's memory */
-+ node = recv_add_id(&uidlist, uidmap, uid, name); /* node keeps name's memory */
- return node->id2;
- }
-
-@@ -336,7 +345,7 @@ gid_t recv_group_name(int f, gid_t gid, uint16 *flags_ptr)
- free(name);
- name = NULL;
- }
-- node = recv_add_gid(gid, name); /* node keeps name's memory */
-+ node = recv_add_id(&gidlist, gidmap, gid, name); /* node keeps name's memory */
- if (flags_ptr && node->flags & FLAG_SKIP_GROUP)
- *flags_ptr |= FLAG_SKIP_GROUP;
- return node->id2;
-@@ -363,17 +372,103 @@ void recv_id_list(int f, struct file_list *flist)
-
- /* Now convert all the uids/gids from sender values to our values. */
- #ifdef SUPPORT_ACLS
-- if (preserve_acls && !numeric_ids)
-+ if (preserve_acls && (!numeric_ids || usermap || groupmap))
- match_acl_ids();
- #endif
-- if (am_root && preserve_uid && !numeric_ids) {
-+ if (am_root && preserve_uid && (!numeric_ids || usermap)) {
- for (i = 0; i < flist->used; i++)
- F_OWNER(flist->files[i]) = match_uid(F_OWNER(flist->files[i]));
- }
-- if (preserve_gid && (!am_root || !numeric_ids)) {
-+ if (preserve_gid && (!am_root || !numeric_ids || groupmap)) {
- for (i = 0; i < flist->used; i++) {
- F_GROUP(flist->files[i]) = match_gid(F_GROUP(flist->files[i]),
- &flist->files[i]->flags);
- }
- }
- }
-+
-+void parse_name_map(char *map, int usernames)
-+{
-+ struct idlist **idmap_ptr = usernames ? &uidmap : &gidmap;
-+ struct idlist **idlist_ptr = usernames ? &uidlist : &gidlist;
-+ char *colon, *end, *name, *cp = map + strlen(map);
-+ id_t id1;
-+ uint16 flags;
-+
-+ /* Parse the list in reverse, so the order in the struct is right. */
-+ while (1) {
-+ end = cp;
-+ while (cp > map && cp[-1] != ',') cp--;
-+ if (!(colon = strchr(cp, ':'))) {
-+ rprintf(FERROR, "No colon found in --%smap: %s\n",
-+ usernames ? "user" : "group", cp);
-+ exit_cleanup(RERR_SYNTAX);
-+ }
-+ if (!colon[1]) {
-+ rprintf(FERROR, "No name found after colon --%smap: %s\n",
-+ usernames ? "user" : "group", cp);
-+ exit_cleanup(RERR_SYNTAX);
-+ }
-+ *colon = '\0';
-+
-+ if (isDigit(cp)) {
-+ char *dash = strchr(cp, '-');
-+ if (strspn(cp, "0123456789-") != (size_t)(colon - cp)
-+ || (dash && (!dash[1] || strchr(dash+1, '-')))) {
-+ bad_number:
-+ rprintf(FERROR, "Invalid number in --%smap: %s\n",
-+ usernames ? "user" : "group", cp);
-+ exit_cleanup(RERR_SYNTAX);
-+ }
-+ if (dash)
-+ name = (char *)atol(dash+1);
-+ else
-+ name = (char *)0;
-+ flags = 0;
-+ id1 = atol(cp);
-+ } else if (strpbrk(cp, "*[?")) {
-+ flags = NFLAGS_WILD_NAME_MATCH;
-+ name = cp;
-+ id1 = 0;
-+ } else {
-+ flags = NFLAGS_NAME_MATCH;
-+ name = cp;
-+ id1 = 0;
-+ }
-+
-+ if (isDigit(colon+1)) {
-+ if (strspn(colon+1, "0123456789") != (size_t)(end - colon - 1)) {
-+ cp = colon+1;
-+ goto bad_number;
-+ }
-+ add_to_list(idmap_ptr, id1, name, atol(colon+1), flags);
-+ } else if (usernames) {
-+ uid_t uid;
-+ if (name_to_uid(colon+1, &uid))
-+ add_to_list(idmap_ptr, id1, name, uid, flags);
-+ else {
-+ rprintf(FERROR,
-+ "Unknown --usermap name on receiver: %s\n",
-+ colon+1);
-+ }
-+ } else {
-+ gid_t gid;
-+ if (name_to_gid(colon+1, &gid))
-+ add_to_list(idmap_ptr, id1, name, gid, flags);
-+ else {
-+ rprintf(FERROR,
-+ "Unknown --groupmap name on receiver: %s\n",
-+ colon+1);
-+ }
-+ }
-+
-+ if (cp == map)
-+ break;
-+
-+ *--cp = '\0'; /* replace comma */
-+ }
-+
-+ /* The 0 user/group doesn't get its name sent, so add it explicitly. */
-+ recv_add_id(idlist_ptr, *idmap_ptr, 0,
-+ numeric_ids ? NULL : usernames ? uid_to_name(0) : gid_to_name(0));
-+}