85c032b7c36fe4234402dd7cbf377075b34db20a
[rsync-patches.git] / nameconverter.diff
1 This patch adds a "name converter" daemon option that allows you
2 to specify a user-/group- name converter program that converts
3 between ID numbers and names.  This only works in daemon mode,
4 and is useful for both chroot use (since the converter runs
5 outside the chroot) or to specify a converter that doesn't use
6 the normal passwd/group setup.
7
8 The converter must use a null char ('\0') as the line terminator
9 for input/output on stdin/stdout.  A sample converter written in
10 perl is supplied in the support dir: nameconvert.  To use it,
11 specify this daemon option:
12
13     name converter = /path/nameconvert
14
15 If /path/ is omitted, the script will be found on the $PATH.
16
17 To use this patch, run these commands for a successful build:
18
19     patch -p1 <patches/nameconverter.diff
20     ./configure                         (optional if already run)
21     make
22
23 based-on: 63f91976112b8b2118cc17eb5fc8142175566f4f
24 diff --git a/authenticate.c b/authenticate.c
25 --- a/authenticate.c
26 +++ b/authenticate.c
27 @@ -224,7 +224,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
28         char *users = lp_auth_users(module);
29         char challenge[MAX_DIGEST_LEN*2];
30         char line[BIGPATHBUFLEN];
31 -       char **auth_uid_groups = NULL;
32 +       const char **auth_uid_groups = NULL;
33         int auth_uid_groups_cnt = -1;
34         const char *err = NULL;
35         int group_match = -1;
36 @@ -284,7 +284,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
37                                  || getallgroups(auth_uid, gid_list, &auth_uid_groups_cnt) != NULL)
38                                         auth_uid_groups_cnt = 0;
39                                 else {
40 -                                       if ((auth_uid_groups = new_array(char *, auth_uid_groups_cnt)) == NULL)
41 +                                       if ((auth_uid_groups = new_array(const char *, auth_uid_groups_cnt)) == NULL)
42                                                 out_of_memory("auth_server");
43                                         for (j = 0; j < auth_uid_groups_cnt; j++)
44                                                 auth_uid_groups[j] = gid_to_group(gid_list[j]);
45 @@ -311,7 +311,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
46         else if (opt_ch == 'd')
47                 err = "denied by rule";
48         else {
49 -               char *group = group_match >= 0 ? auth_uid_groups[group_match] : NULL;
50 +               const char *group = group_match >= 0 ? auth_uid_groups[group_match] : NULL;
51                 err = check_secret(module, line, group, challenge, pass);
52         }
53  
54 @@ -322,7 +322,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
55                 int j;
56                 for (j = 0; j < auth_uid_groups_cnt; j++) {
57                         if (auth_uid_groups[j])
58 -                               free(auth_uid_groups[j]);
59 +                               free((char*)auth_uid_groups[j]);
60                 }
61                 free(auth_uid_groups);
62         }
63 diff --git a/clientserver.c b/clientserver.c
64 --- a/clientserver.c
65 +++ b/clientserver.c
66 @@ -65,6 +65,7 @@ extern iconv_t ic_send, ic_recv;
67  char *auth_user;
68  int read_only = 0;
69  int module_id = -1;
70 +pid_t namecvt_pid = 0;
71  struct chmod_mode_struct *daemon_chmod_modes;
72  
73  /* module_dirlen is the length of the module_dir string when in daemon
74 @@ -76,6 +77,7 @@ unsigned int module_dirlen = 0;
75  char *full_module_path;
76  
77  static int rl_nulls = 0;
78 +static int namecvt_fd_req = -1, namecvt_fd_ans = -1;
79  
80  #ifdef HAVE_SIGACTION
81  static struct sigaction sigact;
82 @@ -693,7 +695,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
83         log_init(1);
84  
85  #ifdef HAVE_PUTENV
86 -       if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i)) {
87 +       if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i) || *lp_name_converter(i)) {
88                 int status;
89  
90                 /* For post-xfer exec, fork a new process to run the rsync
91 @@ -775,6 +777,44 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
92                         set_blocking(pre_exec_arg_fd);
93                         set_blocking(pre_exec_error_fd);
94                 }
95 +               if (*lp_name_converter(i)) {
96 +                       int fds_to[2], fds_from[2];
97 +                       if (pipe(fds_to) < 0 || pipe(fds_from) < 0
98 +                        || (namecvt_pid = fork()) < 0) {
99 +                               rsyserr(FLOG, errno, "name-converter exec preparation failed");
100 +                               io_printf(f_out, "@ERROR: name-converter exec preparation failed\n");
101 +                               return -1;
102 +                       }
103 +                       if (namecvt_pid == 0) {
104 +                               char *args[100], *run = lp_name_converter(i);
105 +                               int cnt = 0;
106 +                               close(fds_to[1]);
107 +                               close(fds_from[0]);
108 +                               set_blocking(fds_to[0]);
109 +                               set_blocking(fds_from[1]);
110 +                               close(STDIN_FILENO);
111 +                               close(STDOUT_FILENO);
112 +                               dup2(fds_to[0], STDIN_FILENO);
113 +                               dup2(fds_from[1], STDOUT_FILENO);
114 +                               while (cnt+1 < (int)(sizeof args / sizeof (char *))) {
115 +                                       char *space = strchr(run, ' ');
116 +                                       args[cnt++] = run;
117 +                                       if (!space)
118 +                                               break;
119 +                                       *space = '\0';
120 +                                       run = space + 1;
121 +                               }
122 +                               args[cnt] = NULL;
123 +                               execvp(args[0], args);
124 +                               _exit(1);
125 +                       }
126 +                       close(fds_to[0]);
127 +                       close(fds_from[1]);
128 +                       set_blocking(fds_to[1]);
129 +                       set_blocking(fds_from[0]);
130 +                       namecvt_fd_req = fds_to[1];
131 +                       namecvt_fd_ans = fds_from[0];
132 +               }
133         }
134  #endif
135  
136 @@ -1011,6 +1051,44 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
137         return 0;
138  }
139  
140 +int namecvt_name(const char *cmd, const char *name)
141 +{
142 +       char buf[1024];
143 +       int got, len = snprintf(buf, sizeof buf, "%s %s", cmd, name);
144 +       if (len >= (int)sizeof buf) {
145 +               rprintf(FERROR, "namecvt_name() request was too large.\n");
146 +               exit_cleanup(RERR_UNSUPPORTED);
147 +       }
148 +       while ((got = write(namecvt_fd_req, buf, len + 1)) != len + 1) {
149 +               if (got < 0 && errno == EINTR)
150 +                       continue;
151 +               rprintf(FERROR, "Connection to name-converter failed.\n");
152 +               exit_cleanup(RERR_SOCKETIO);
153 +       }
154 +       if (!(len = read_arg_from_pipe(namecvt_fd_ans, buf, sizeof buf)))
155 +               return 0;
156 +       return atoi(buf);
157 +}
158 +
159 +const char *namecvt_id(const char *cmd, int id)
160 +{
161 +       char buf[1024];
162 +       int got, len = snprintf(buf, sizeof buf, "%s %d", cmd, id);
163 +       if (len >= (int)sizeof buf) {
164 +               rprintf(FERROR, "namecvt_id() request was too large.\n");
165 +               exit_cleanup(RERR_UNSUPPORTED);
166 +       }
167 +       while ((got = write(namecvt_fd_req, buf, len + 1)) != len + 1) {
168 +               if (got < 0 && errno == EINTR)
169 +                       continue;
170 +               rprintf(FERROR, "Connection to name-converter failed.\n");
171 +               exit_cleanup(RERR_SOCKETIO);
172 +       }
173 +       if (!(len = read_arg_from_pipe(namecvt_fd_ans, buf, sizeof buf)))
174 +               return NULL;
175 +       return strdup(buf);
176 +}
177 +
178  /* send a list of available modules to the client. Don't list those
179     with "list = False". */
180  static void send_listing(int fd)
181 diff --git a/loadparm.c b/loadparm.c
182 --- a/loadparm.c
183 +++ b/loadparm.c
184 @@ -123,6 +123,7 @@ typedef struct {
185         char *log_file;
186         char *log_format;
187         char *name;
188 +       char *name_converter;
189         char *outgoing_chmod;
190         char *path;
191         char *postxfer_exec;
192 @@ -199,6 +200,7 @@ static const all_vars Defaults = {
193   /* log_file; */               NULL,
194   /* log_format; */             "%o %h [%a] %m (%u) %f %l",
195   /* name; */                   NULL,
196 + /* name_converter; */         NULL,
197   /* outgoing_chmod; */         NULL,
198   /* path; */                   NULL,
199   /* postxfer_exec; */          NULL,
200 @@ -344,6 +346,7 @@ static struct parm_struct parm_table[] =
201   {"max verbosity",     P_INTEGER,P_LOCAL, &Vars.l.max_verbosity,       NULL,0},
202   {"munge symlinks",    P_BOOL,   P_LOCAL, &Vars.l.munge_symlinks,      NULL,0},
203   {"name",              P_STRING, P_LOCAL, &Vars.l.name,                NULL,0},
204 + {"name converter",    P_STRING, P_LOCAL, &Vars.l.name_converter,      NULL,0},
205   {"numeric ids",       P_BOOL,   P_LOCAL, &Vars.l.numeric_ids,         NULL,0},
206   {"outgoing chmod",    P_STRING, P_LOCAL, &Vars.l.outgoing_chmod,      NULL,0},
207   {"path",              P_PATH,   P_LOCAL, &Vars.l.path,                NULL,0},
208 @@ -472,6 +475,7 @@ FN_LOCAL_STRING(lp_outgoing_chmod, outgoing_chmod)
209  FN_LOCAL_STRING(lp_path, path)
210  FN_LOCAL_STRING(lp_postxfer_exec, postxfer_exec)
211  FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec)
212 +FN_LOCAL_STRING(lp_name_converter, name_converter)
213  FN_LOCAL_STRING(lp_refuse_options, refuse_options)
214  FN_LOCAL_STRING(lp_secrets_file, secrets_file)
215  FN_LOCAL_STRING(lp_temp_dir, temp_dir)
216 diff --git a/rsyncd.conf.yo b/rsyncd.conf.yo
217 --- a/rsyncd.conf.yo
218 +++ b/rsyncd.conf.yo
219 @@ -195,10 +195,11 @@ if the module is not read-only).
220  
221  When this parameter is enabled, rsync will not attempt to map users and groups
222  by name (by default), but instead copy IDs as though bf(--numeric-ids) had
223 -been specified.  In order to enable name-mapping, rsync needs to be able to
224 +been specified.  In order to enable name-mapping, rsync needs either the
225 +bf(name converter) parameter to specify a conversion program, or it needs to
226  use the standard library functions for looking up names and IDs (i.e.
227  code(getpwuid()), code(getgrgid()), code(getpwname()), and code(getgrnam())).
228 -This means the rsync
229 +The latter choice means the rsync
230  process in the chroot hierarchy will need to have access to the resources
231  used by these library functions (traditionally /etc/passwd and
232  /etc/group, but perhaps additional dynamic libraries as well).
233 @@ -264,6 +265,27 @@ path elements that rsync believes will allow a symlink to escape the module's
234  hierarchy.  There are tricky ways to work around this, though, so you had
235  better trust your users if you choose this combination of parameters.
236  
237 +dit(bf(name converter))  This parameter lets you specify a
238 +program that will be run by the rsync daemon (prior to bf(use chroot), if
239 +that parameter is enabled) to convert user/group names into numbers or visa
240 +versa.  There is a sample perl script in the support directory named
241 +"nameconvert" that you can use to enable the use of the normal passwd/group
242 +lookup calls in a chroot daemon (which does not require any extra files
243 +be placed in the chroot area).  This use is configured as follows:
244 +
245 +verb(    name converter = /path/nameconvert)
246 +
247 +You could alternately specify a program that responds to each request using
248 +a lookup table to find the names and numbers, this allows you to configure
249 +per-module name conversion.  See the support/nameconvert script for the
250 +details of what requests can be sent to the program.
251 +
252 +The program will have access to some of the environment variables that are
253 +described in the section on bf(pre-xfer exec): bf(RSYNC_MODULE_NAME),
254 +bf(RSYNC_MODULE_PATH), bf(RSYNC_HOST_ADDR), bf(RSYNC_HOST_NAME), and
255 +bf(RSYNC_USER_NAME).  This is useful if you want to customize the
256 +conversion using a single program invocation.
257 +
258  dit(bf(charset)) This specifies the name of the character set in which the
259  module's filenames are stored.  If the client uses an bf(--iconv) option,
260  the daemon will use the value of the "charset" parameter regardless of the
261 diff --git a/support/nameconvert b/support/nameconvert
262 new file mode 100755
263 --- /dev/null
264 +++ b/support/nameconvert
265 @@ -0,0 +1,42 @@
266 +#!/usr/bin/perl -w
267 +# This implements a simple protocol to do {user,group}-{name,id}
268 +# conversions.  All input and output consists of simple strings
269 +# with a terminating null char (or newline for debugging).  If
270 +# the conversion fails, an empty string is returned.
271 +#
272 +# The requests can be:
273 +#
274 +# uid ID_NUM\0  ->  NAME\0
275 +# gid ID_NUM\0  ->  NAME\0
276 +# usr NAME\0    ->  ID_NUM\0
277 +# grp NAME\0    ->  ID_NUM\0
278 +#
279 +# An unknown ID_NUM or NAME results in an empty return value.
280 +#
281 +# This is used by an rsync daemon when configured with the
282 +# "name converter" setting.
283 +
284 +use strict;
285 +
286 +my $eol = grep(/^--debug$/, @ARGV) ? "\n" : "\0";
287 +$/ = $eol;
288 +
289 +$| = 1;
290 +
291 +while (<STDIN>) {
292 +    chomp;
293 +    my $ans;
294 +    if (/^uid (\d+)$/) {
295 +       $ans = getpwuid($1);
296 +    } elsif (/^gid (\d+)$/) {
297 +       $ans = getgrgid($1);
298 +    } elsif (/^usr (\S+)$/) {
299 +       $ans = getpwnam($1);
300 +    } elsif (/^grp (\S+)$/) {
301 +       $ans = getgrnam($1);
302 +    } else {
303 +       die "Invalid request: $_";
304 +    }
305 +    $ans = '' unless defined $ans;
306 +    print $ans, $eol;
307 +}
308 diff --git a/t_stub.c b/t_stub.c
309 --- a/t_stub.c
310 +++ b/t_stub.c
311 @@ -33,6 +33,7 @@ int preserve_times = 0;
312  int preserve_xattrs = 0;
313  char *partial_dir;
314  char *module_dir;
315 +pid_t namecvt_pid;
316  filter_rule_list daemon_filter_list;
317  
318   void rprintf(UNUSED(enum logcode code), const char *format, ...)
319 @@ -83,6 +84,11 @@ filter_rule_list daemon_filter_list;
320         return;
321  }
322  
323 + int namecvt_name(UNUSED(const char *cmd), UNUSED(const char *name))
324 +{
325 +       return 0;
326 +}
327 +
328   char *lp_name(UNUSED(int mod))
329  {
330         return NULL;
331 diff --git a/uidlist.c b/uidlist.c
332 --- a/uidlist.c
333 +++ b/uidlist.c
334 @@ -33,6 +33,7 @@ extern int preserve_uid;
335  extern int preserve_gid;
336  extern int preserve_acls;
337  extern int numeric_ids;
338 +extern pid_t namecvt_pid;
339  extern gid_t our_gid;
340  extern char *usermap;
341  extern char *groupmap;
342 @@ -97,19 +98,27 @@ static struct idlist *add_to_list(struct idlist **root, id_t id, union name_or_i
343  }
344  
345  /* turn a uid into a user name */
346 -char *uid_to_user(uid_t uid)
347 +const char *uid_to_user(uid_t uid)
348  {
349 -       struct passwd *pass = getpwuid(uid);
350 -       if (pass)
351 +       struct passwd *pass;
352 +
353 +       if (namecvt_pid)
354 +               return namecvt_id("uid", (int)uid);
355 +
356 +       if ((pass = getpwuid(uid)) != NULL)
357                 return strdup(pass->pw_name);
358         return NULL;
359  }
360  
361  /* turn a gid into a group name */
362 -char *gid_to_group(gid_t gid)
363 +const char *gid_to_group(gid_t gid)
364  {
365 -       struct group *grp = getgrgid(gid);
366 -       if (grp)
367 +       struct group *grp;
368 +
369 +       if (namecvt_pid)
370 +               return namecvt_id("gid", (int)gid);
371 +
372 +       if ((grp = getgrgid(gid)) != NULL)
373                 return strdup(grp->gr_name);
374         return NULL;
375  }
376 @@ -117,32 +126,54 @@ char *gid_to_group(gid_t gid)
377  /* Parse a user name or (optionally) a number into a uid */
378  int user_to_uid(const char *name, uid_t *uid_p, BOOL num_ok)
379  {
380 -       struct passwd *pass;
381 +       uid_t uid;
382 +
383         if (!name || !*name)
384                 return 0;
385 +
386         if (num_ok && name[strspn(name, "0123456789")] == '\0') {
387                 *uid_p = id_parse(name);
388                 return 1;
389         }
390 -       if (!(pass = getpwnam(name)))
391 -               return 0;
392 -       *uid_p = pass->pw_uid;
393 +
394 +       if (namecvt_pid) {
395 +               if (!(uid = namecvt_name("usr", name)))
396 +                       return 0;
397 +       } else {
398 +               struct passwd *pass;
399 +               if (!(pass = getpwnam(name)))
400 +                       return 0;
401 +               uid = pass->pw_uid;
402 +       }
403 +
404 +       *uid_p = uid;
405         return 1;
406  }
407  
408  /* Parse a group name or (optionally) a number into a gid */
409  int group_to_gid(const char *name, gid_t *gid_p, BOOL num_ok)
410  {
411 -       struct group *grp;
412 +       gid_t gid;
413 +
414         if (!name || !*name)
415                 return 0;
416 +
417         if (num_ok && name[strspn(name, "0123456789")] == '\0') {
418                 *gid_p = id_parse(name);
419                 return 1;
420         }
421 -       if (!(grp = getgrnam(name)))
422 -               return 0;
423 -       *gid_p = grp->gr_gid;
424 +
425 +       if (namecvt_pid) {
426 +               if (!(gid = namecvt_name("grp", name)))
427 +                       return 0;
428 +       } else {
429 +               struct group *grp;
430 +               if (!(grp = getgrnam(name)))
431 +                       return 0;
432 +               gid = grp->gr_gid;
433 +       }
434 +
435 +       *gid_p = gid;
436         return 1;
437  }
438  
439 diff --git a/util.c b/util.c
440 --- a/util.c
441 +++ b/util.c
442 @@ -35,6 +35,8 @@ extern int preallocate_files;
443  extern char *module_dir;
444  extern unsigned int module_dirlen;
445  extern char *partial_dir;
446 +extern pid_t namecvt_pid;
447 +extern unsigned int module_dirlen;
448  extern filter_rule_list daemon_filter_list;
449  
450  int sanitize_paths = 0;