Blob Blame History Raw
This patch adds a "name converter" daemon option that allows you
to specify a user-/group- name converter program that converts
between ID numbers and names.  This only works in daemon mode,
and is useful for both chroot use (since the converter runs
outside the chroot) or to specify a converter that doesn't use
the normal passwd/group setup.

The converter must use a null char ('\0') as the line terminator
for input/output on stdin/stdout.  A sample converter written in
perl is supplied in the support dir: nameconvert.  To use it,
specify this daemon option:

    name converter = /path/nameconvert

If /path/ is omitted, the script will be found on the $PATH.

To use this patch, run these commands for a successful build:

    patch -p1 <patches/nameconverter.diff
    ./configure                         (optional if already run)
    make

based-on: d73762eea3f15f2c56bb3fa9394ad1883c25c949
diff --git a/authenticate.c b/authenticate.c
--- a/authenticate.c
+++ b/authenticate.c
@@ -226,7 +226,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
 	char *users = lp_auth_users(module);
 	char challenge[MAX_DIGEST_LEN*2];
 	char line[BIGPATHBUFLEN];
-	char **auth_uid_groups = NULL;
+	const char **auth_uid_groups = NULL;
 	int auth_uid_groups_cnt = -1;
 	const char *err = NULL;
 	int group_match = -1;
@@ -287,7 +287,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
 				else {
 					gid_t *gid_array = gid_list.items;
 					auth_uid_groups_cnt = gid_list.count;
-					if ((auth_uid_groups = new_array(char *, auth_uid_groups_cnt)) == NULL)
+					if ((auth_uid_groups = new_array(const char *, auth_uid_groups_cnt)) == NULL)
 						out_of_memory("auth_server");
 					for (j = 0; j < auth_uid_groups_cnt; j++)
 						auth_uid_groups[j] = gid_to_group(gid_array[j]);
@@ -314,7 +314,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
 	else if (opt_ch == 'd')
 		err = "denied by rule";
 	else {
-		char *group = group_match >= 0 ? auth_uid_groups[group_match] : NULL;
+		const char *group = group_match >= 0 ? auth_uid_groups[group_match] : NULL;
 		err = check_secret(module, line, group, challenge, pass);
 	}
 
@@ -325,7 +325,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
 		int j;
 		for (j = 0; j < auth_uid_groups_cnt; j++) {
 			if (auth_uid_groups[j])
-				free(auth_uid_groups[j]);
+				free((char*)auth_uid_groups[j]);
 		}
 		free(auth_uid_groups);
 	}
diff --git a/clientserver.c b/clientserver.c
--- a/clientserver.c
+++ b/clientserver.c
@@ -65,6 +65,7 @@ extern gid_t our_gid;
 char *auth_user;
 int read_only = 0;
 int module_id = -1;
+pid_t namecvt_pid = 0;
 struct chmod_mode_struct *daemon_chmod_modes;
 
 /* module_dirlen is the length of the module_dir string when in daemon
@@ -76,6 +77,7 @@ unsigned int module_dirlen = 0;
 char *full_module_path;
 
 static int rl_nulls = 0;
+static int namecvt_fd_req = -1, namecvt_fd_ans = -1;
 
 #ifdef HAVE_SIGACTION
 static struct sigaction sigact;
@@ -688,7 +690,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
 	log_init(1);
 
 #ifdef HAVE_PUTENV
-	if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i)) {
+	if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i) || *lp_name_converter(i)) {
 		int status;
 
 		/* For post-xfer exec, fork a new process to run the rsync
@@ -770,6 +772,44 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
 			set_blocking(pre_exec_arg_fd);
 			set_blocking(pre_exec_error_fd);
 		}
+		if (*lp_name_converter(i)) {
+			int fds_to[2], fds_from[2];
+			if (pipe(fds_to) < 0 || pipe(fds_from) < 0
+			 || (namecvt_pid = fork()) < 0) {
+				rsyserr(FLOG, errno, "name-converter exec preparation failed");
+				io_printf(f_out, "@ERROR: name-converter exec preparation failed\n");
+				return -1;
+			}
+			if (namecvt_pid == 0) {
+				char *args[100], *run = lp_name_converter(i);
+				int cnt = 0;
+				close(fds_to[1]);
+				close(fds_from[0]);
+				set_blocking(fds_to[0]);
+				set_blocking(fds_from[1]);
+				close(STDIN_FILENO);
+				close(STDOUT_FILENO);
+				dup2(fds_to[0], STDIN_FILENO);
+				dup2(fds_from[1], STDOUT_FILENO);
+				while (cnt+1 < (int)(sizeof args / sizeof (char *))) {
+					char *space = strchr(run, ' ');
+					args[cnt++] = run;
+					if (!space)
+						break;
+					*space = '\0';
+					run = space + 1;
+				}
+				args[cnt] = NULL;
+				execvp(args[0], args);
+				_exit(1);
+			}
+			close(fds_to[0]);
+			close(fds_from[1]);
+			set_blocking(fds_to[1]);
+			set_blocking(fds_from[0]);
+			namecvt_fd_req = fds_to[1];
+			namecvt_fd_ans = fds_from[0];
+		}
 	}
 #endif
 
@@ -1009,6 +1049,44 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
 	return 0;
 }
 
+int namecvt_name(const char *cmd, const char *name)
+{
+	char buf[1024];
+	int got, len = snprintf(buf, sizeof buf, "%s %s", cmd, name);
+	if (len >= (int)sizeof buf) {
+		rprintf(FERROR, "namecvt_name() request was too large.\n");
+		exit_cleanup(RERR_UNSUPPORTED);
+	}
+	while ((got = write(namecvt_fd_req, buf, len + 1)) != len + 1) {
+		if (got < 0 && errno == EINTR)
+			continue;
+		rprintf(FERROR, "Connection to name-converter failed.\n");
+		exit_cleanup(RERR_SOCKETIO);
+	}
+	if (!(len = read_arg_from_pipe(namecvt_fd_ans, buf, sizeof buf)))
+		return 0;
+	return atoi(buf);
+}
+
+const char *namecvt_id(const char *cmd, int id)
+{
+	char buf[1024];
+	int got, len = snprintf(buf, sizeof buf, "%s %d", cmd, id);
+	if (len >= (int)sizeof buf) {
+		rprintf(FERROR, "namecvt_id() request was too large.\n");
+		exit_cleanup(RERR_UNSUPPORTED);
+	}
+	while ((got = write(namecvt_fd_req, buf, len + 1)) != len + 1) {
+		if (got < 0 && errno == EINTR)
+			continue;
+		rprintf(FERROR, "Connection to name-converter failed.\n");
+		exit_cleanup(RERR_SOCKETIO);
+	}
+	if (!(len = read_arg_from_pipe(namecvt_fd_ans, buf, sizeof buf)))
+		return NULL;
+	return strdup(buf);
+}
+
 /* send a list of available modules to the client. Don't list those
    with "list = False". */
 static void send_listing(int fd)
diff --git a/loadparm.c b/loadparm.c
--- a/loadparm.c
+++ b/loadparm.c
@@ -126,6 +126,7 @@ typedef struct {
 	char *log_file;
 	char *log_format;
 	char *name;
+	char *name_converter;
 	char *outgoing_chmod;
 	char *path;
 	char *postxfer_exec;
@@ -206,6 +207,7 @@ static const all_vars Defaults = {
  /* log_file; */		NULL,
  /* log_format; */		"%o %h [%a] %m (%u) %f %l",
  /* name; */			NULL,
+ /* name_converter; */		NULL,
  /* outgoing_chmod; */		NULL,
  /* path; */			NULL,
  /* postxfer_exec; */		NULL,
@@ -355,6 +357,7 @@ static struct parm_struct parm_table[] =
  {"max verbosity",     P_INTEGER,P_LOCAL, &Vars.l.max_verbosity,       NULL,0},
  {"munge symlinks",    P_BOOL,   P_LOCAL, &Vars.l.munge_symlinks,      NULL,0},
  {"name",              P_STRING, P_LOCAL, &Vars.l.name,                NULL,0},
+ {"name converter",    P_STRING, P_LOCAL, &Vars.l.name_converter,      NULL,0},
  {"numeric ids",       P_BOOL,   P_LOCAL, &Vars.l.numeric_ids,         NULL,0},
  {"outgoing chmod",    P_STRING, P_LOCAL, &Vars.l.outgoing_chmod,      NULL,0},
  {"path",              P_PATH,   P_LOCAL, &Vars.l.path,                NULL,0},
@@ -487,6 +490,7 @@ FN_LOCAL_STRING(lp_outgoing_chmod, outgoing_chmod)
 FN_LOCAL_STRING(lp_path, path)
 FN_LOCAL_STRING(lp_postxfer_exec, postxfer_exec)
 FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec)
+FN_LOCAL_STRING(lp_name_converter, name_converter)
 FN_LOCAL_STRING(lp_refuse_options, refuse_options)
 FN_LOCAL_STRING(lp_secrets_file, secrets_file)
 FN_LOCAL_STRING(lp_syslog_tag, syslog_tag)
diff --git a/rsyncd.conf.yo b/rsyncd.conf.yo
--- a/rsyncd.conf.yo
+++ b/rsyncd.conf.yo
@@ -277,6 +277,27 @@ path elements that rsync believes will allow a symlink to escape the module's
 hierarchy.  There are tricky ways to work around this, though, so you had
 better trust your users if you choose this combination of parameters.
 
+dit(bf(name converter))  This parameter lets you specify a
+program that will be run by the rsync daemon (prior to bf(use chroot), if
+that parameter is enabled) to convert user/group names into numbers or visa
+versa.  There is a sample perl script in the support directory named
+"nameconvert" that you can use to enable the use of the normal passwd/group
+lookup calls in a chroot daemon (which does not require any extra files
+be placed in the chroot area).  This use is configured as follows:
+
+verb(    name converter = /path/nameconvert)
+
+You could alternately specify a program that responds to each request using
+a lookup table to find the names and numbers, this allows you to configure
+per-module name conversion.  See the support/nameconvert script for the
+details of what requests can be sent to the program.
+
+The program will have access to some of the environment variables that are
+described in the section on bf(pre-xfer exec): bf(RSYNC_MODULE_NAME),
+bf(RSYNC_MODULE_PATH), bf(RSYNC_HOST_ADDR), bf(RSYNC_HOST_NAME), and
+bf(RSYNC_USER_NAME).  This is useful if you want to customize the
+conversion using a single program invocation.
+
 dit(bf(charset)) This specifies the name of the character set in which the
 module's filenames are stored.  If the client uses an bf(--iconv) option,
 the daemon will use the value of the "charset" parameter regardless of the
diff --git a/support/nameconvert b/support/nameconvert
new file mode 100755
--- /dev/null
+++ b/support/nameconvert
@@ -0,0 +1,42 @@
+#!/usr/bin/perl -w
+# This implements a simple protocol to do {user,group}-{name,id}
+# conversions.  All input and output consists of simple strings
+# with a terminating null char (or newline for debugging).  If
+# the conversion fails, an empty string is returned.
+#
+# The requests can be:
+#
+# uid ID_NUM\0  ->  NAME\0
+# gid ID_NUM\0  ->  NAME\0
+# usr NAME\0    ->  ID_NUM\0
+# grp NAME\0    ->  ID_NUM\0
+#
+# An unknown ID_NUM or NAME results in an empty return value.
+#
+# This is used by an rsync daemon when configured with the
+# "name converter" setting.
+
+use strict;
+
+my $eol = grep(/^--debug$/, @ARGV) ? "\n" : "\0";
+$/ = $eol;
+
+$| = 1;
+
+while (<STDIN>) {
+    chomp;
+    my $ans;
+    if (/^uid (\d+)$/) {
+	$ans = getpwuid($1);
+    } elsif (/^gid (\d+)$/) {
+	$ans = getgrgid($1);
+    } elsif (/^usr (\S+)$/) {
+	$ans = getpwnam($1);
+    } elsif (/^grp (\S+)$/) {
+	$ans = getgrnam($1);
+    } else {
+	die "Invalid request: $_";
+    }
+    $ans = '' unless defined $ans;
+    print $ans, $eol;
+}
diff --git a/t_stub.c b/t_stub.c
--- a/t_stub.c
+++ b/t_stub.c
@@ -33,6 +33,7 @@ int preserve_times = 0;
 int preserve_xattrs = 0;
 char *partial_dir;
 char *module_dir;
+pid_t namecvt_pid;
 filter_rule_list daemon_filter_list;
 
  void rprintf(UNUSED(enum logcode code), const char *format, ...)
@@ -83,6 +84,11 @@ filter_rule_list daemon_filter_list;
 	return;
 }
 
+ int namecvt_name(UNUSED(const char *cmd), UNUSED(const char *name))
+{
+	return 0;
+}
+
  char *lp_name(UNUSED(int mod))
 {
 	return NULL;
diff --git a/uidlist.c b/uidlist.c
--- a/uidlist.c
+++ b/uidlist.c
@@ -33,6 +33,7 @@ extern int preserve_uid;
 extern int preserve_gid;
 extern int preserve_acls;
 extern int numeric_ids;
+extern pid_t namecvt_pid;
 extern gid_t our_gid;
 extern char *usermap;
 extern char *groupmap;
@@ -97,19 +98,27 @@ static struct idlist *add_to_list(struct idlist **root, id_t id, union name_or_i
 }
 
 /* turn a uid into a user name */
-char *uid_to_user(uid_t uid)
+const char *uid_to_user(uid_t uid)
 {
-	struct passwd *pass = getpwuid(uid);
-	if (pass)
+	struct passwd *pass;
+
+	if (namecvt_pid)
+		return namecvt_id("uid", (int)uid);
+
+	if ((pass = getpwuid(uid)) != NULL)
 		return strdup(pass->pw_name);
 	return NULL;
 }
 
 /* turn a gid into a group name */
-char *gid_to_group(gid_t gid)
+const char *gid_to_group(gid_t gid)
 {
-	struct group *grp = getgrgid(gid);
-	if (grp)
+	struct group *grp;
+
+	if (namecvt_pid)
+		return namecvt_id("gid", (int)gid);
+
+	if ((grp = getgrgid(gid)) != NULL)
 		return strdup(grp->gr_name);
 	return NULL;
 }
@@ -117,32 +126,54 @@ char *gid_to_group(gid_t gid)
 /* Parse a user name or (optionally) a number into a uid */
 int user_to_uid(const char *name, uid_t *uid_p, BOOL num_ok)
 {
-	struct passwd *pass;
+	uid_t uid;
+
 	if (!name || !*name)
 		return 0;
+
 	if (num_ok && name[strspn(name, "0123456789")] == '\0') {
 		*uid_p = id_parse(name);
 		return 1;
 	}
-	if (!(pass = getpwnam(name)))
-		return 0;
-	*uid_p = pass->pw_uid;
+
+	if (namecvt_pid) {
+		if (!(uid = namecvt_name("usr", name)))
+			return 0;
+	} else {
+		struct passwd *pass;
+		if (!(pass = getpwnam(name)))
+			return 0;
+		uid = pass->pw_uid;
+	}
+
+	*uid_p = uid;
 	return 1;
 }
 
 /* Parse a group name or (optionally) a number into a gid */
 int group_to_gid(const char *name, gid_t *gid_p, BOOL num_ok)
 {
-	struct group *grp;
+	gid_t gid;
+
 	if (!name || !*name)
 		return 0;
+
 	if (num_ok && name[strspn(name, "0123456789")] == '\0') {
 		*gid_p = id_parse(name);
 		return 1;
 	}
-	if (!(grp = getgrnam(name)))
-		return 0;
-	*gid_p = grp->gr_gid;
+
+	if (namecvt_pid) {
+		if (!(gid = namecvt_name("grp", name)))
+			return 0;
+	} else {
+		struct group *grp;
+		if (!(grp = getgrnam(name)))
+			return 0;
+		gid = grp->gr_gid;
+	}
+
+	*gid_p = gid;
 	return 1;
 }
 
diff --git a/util.c b/util.c
--- a/util.c
+++ b/util.c
@@ -36,6 +36,8 @@ extern int preallocate_files;
 extern char *module_dir;
 extern unsigned int module_dirlen;
 extern char *partial_dir;
+extern pid_t namecvt_pid;
+extern unsigned int module_dirlen;
 extern filter_rule_list daemon_filter_list;
 
 int sanitize_paths = 0;
diff -Nurp a/proto.h b/proto.h
--- a/proto.h
+++ b/proto.h
@@ -57,6 +57,8 @@ int check_name(int fd,
 int start_socket_client(char *host, int remote_argc, char *remote_argv[],
 			int argc, char *argv[]);
 int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char *argv[]);
+int namecvt_name(const char *cmd, const char *name);
+const char *namecvt_id(const char *cmd, int id);
 int start_daemon(int f_in, int f_out);
 int daemon_main(void);
 void set_allow_inc_recurse(void);
@@ -219,6 +221,7 @@ char *lp_outgoing_chmod(int module_id);
 char *lp_path(int module_id);
 char *lp_postxfer_exec(int module_id);
 char *lp_prexfer_exec(int module_id);
+char *lp_name_converter(int module_id);
 char *lp_refuse_options(int module_id);
 char *lp_secrets_file(int module_id);
 char *lp_syslog_tag(int module_id);
@@ -349,8 +352,8 @@ void send_token(int f, int32 token, stru
 		int32 n, int32 toklen);
 int32 recv_token(int f, char **data);
 void see_token(char *data, int32 toklen);
-char *uid_to_user(uid_t uid);
-char *gid_to_group(gid_t gid);
+const char *uid_to_user(uid_t uid);
+const char *gid_to_group(gid_t gid);
 int user_to_uid(const char *name, uid_t *uid_p, BOOL num_ok);
 int group_to_gid(const char *name, gid_t *gid_p, BOOL num_ok);
 uid_t match_uid(uid_t uid);
diff -Nurp a/rsyncd.conf.5 b/rsyncd.conf.5
--- a/rsyncd.conf.5
+++ b/rsyncd.conf.5
@@ -307,6 +307,31 @@ path elements that rsync believes will a
 hierarchy.  There are tricky ways to work around this, though, so you had
 better trust your users if you choose this combination of parameters.
 .IP 
+.IP "\fBname converter\fP"
+This parameter lets you specify a
+program that will be run by the rsync daemon (prior to \fBuse chroot\fP, if
+that parameter is enabled) to convert user/group names into numbers or visa
+versa.  There is a sample perl script in the support directory named
+\(dq\&nameconvert\(dq\& that you can use to enable the use of the normal passwd/group
+lookup calls in a chroot daemon (which does not require any extra files
+be placed in the chroot area).  This use is configured as follows:
+.IP 
+.nf 
+    name converter = /path/nameconvert
+.fi 
+
+.IP 
+You could alternately specify a program that responds to each request using
+a lookup table to find the names and numbers, this allows you to configure
+per\-module name conversion.  See the support/nameconvert script for the
+details of what requests can be sent to the program.
+.IP 
+The program will have access to some of the environment variables that are
+described in the section on \fBpre\-xfer exec\fP: \fBRSYNC_MODULE_NAME\fP,
+\fBRSYNC_MODULE_PATH\fP, \fBRSYNC_HOST_ADDR\fP, \fBRSYNC_HOST_NAME\fP, and
+\fBRSYNC_USER_NAME\fP.  This is useful if you want to customize the
+conversion using a single program invocation.
+.IP 
 .IP "\fBcharset\fP"
 This specifies the name of the character set in which the
 module\(cq\&s filenames are stored.  If the client uses an \fB\-\-iconv\fP option,