Skip to content

Commit

Permalink
Add the name converter daemon parameter.
Browse files Browse the repository at this point in the history
This is based on the long-standing patch but with the protocol changed
to just use newlines as delimiters instead of null chars (since names
should not contain a newline AND it makes it easier to write a helper
script).  Lots of other small improvements and a better default value
for "numeric ids" when using "use chroot" with "name converter".
  • Loading branch information
WayneD committed Jul 17, 2020
1 parent be11a49 commit 7e07a32
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 43 deletions.
14 changes: 10 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,22 @@

- Allow `--max-alloc=0` to specify no limit to the alloc sanity check.

- Allow `--block-size=SIZE` to specify the size using units such as "100K".
- Allow `--block-size=SIZE` to specify the size using units (e.g. "100K").

- The name of the id-0 user & group is now sent to the receiver along with the
other user/group names in the transfer (instead of assuming that both sides
have the same id-0 names).
- The name of the id-0 user & group are now sent to the receiver along with
the other user/group names in the transfer (instead of assuming that both
sides have the same id-0 names).

- Added the `--stop-after=MINS` and `--stop-at=DATE_TIME` options (with the
`--time-limit=MINS` option accepted as an alias for `--stop-after`). This
is an enhanced version of the time-limit patch from the patches repo.

- Added the `name converter` daemon parameter to make it easier to convert
user & group names inside a chrooted daemon module. This is based on the
nameconverter patch with some improvements, including a tweak to the request
protocol (so if you used this patch in the past, be sure to update your
converter script).

- Added the ability to specify "@netgroup" names to the `hosts allow` and
`hosts deny` daemon parameters. This is a finalized version of the
netgroup-auth patch from the patches repo.
Expand Down
62 changes: 56 additions & 6 deletions clientserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "rsync.h"
#include "itypes.h"
#include "ifuncs.h"

extern int quiet;
extern int dry_run;
Expand Down Expand Up @@ -71,6 +72,7 @@ int module_id = -1;
int pid_file_fd = -1;
int early_input_len = 0;
char *early_input = NULL;
pid_t namecvt_pid = 0;
struct chmod_mode_struct *daemon_chmod_modes;

#define EARLY_INPUT_CMD "#early_input="
Expand All @@ -85,6 +87,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;
Expand Down Expand Up @@ -425,7 +428,7 @@ void set_env_num(const char *var, long num)
}
#endif

/* Used for both early exec & pre-xfer exec */
/* Used for "early exec", "pre-xfer exec", and the "name converter" script. */
static pid_t start_pre_exec(const char *cmd, int *arg_fd_ptr, int *error_fd_ptr)
{
int arg_fds[2], error_fds[2], arg_fd;
Expand Down Expand Up @@ -492,7 +495,7 @@ static pid_t start_pre_exec(const char *cmd, int *arg_fd_ptr, int *error_fd_ptr)
return pid;
}

static void write_pre_exec_args(int write_fd, char *request, char **early_argv, char **argv, int am_early)
static void write_pre_exec_args(int write_fd, char *request, char **early_argv, char **argv, int exec_type)
{
int j = 0;

Expand All @@ -511,10 +514,11 @@ static void write_pre_exec_args(int write_fd, char *request, char **early_argv,
}
write_byte(write_fd, 0);

if (am_early && early_input_len)
if (exec_type == 1 && early_input_len)
write_buf(write_fd, early_input, early_input_len);

close(write_fd);
if (exec_type != 2) /* the name converter needs this left open */
close(write_fd);
}

static char *finish_pre_exec(const char *desc, pid_t pid, int read_fd)
Expand Down Expand Up @@ -811,7 +815,8 @@ 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_early_exec(module_id) || *lp_prexfer_exec(module_id) || *lp_postxfer_exec(module_id))
if ((*lp_early_exec(module_id) || *lp_prexfer_exec(module_id)
|| *lp_postxfer_exec(module_id) || *lp_name_converter(module_id))
&& !getenv("RSYNC_NO_XFER_EXEC")) {
set_env_num("RSYNC_PID", (long)getpid());

Expand Down Expand Up @@ -873,6 +878,15 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
return -1;
}
}

if (*lp_name_converter(module_id)) {
namecvt_pid = start_pre_exec(lp_name_converter(module_id), &namecvt_fd_req, &namecvt_fd_ans);
if (namecvt_pid == (pid_t)-1) {
rsyserr(FLOG, errno, "name-converter exec preparation failed");
io_printf(f_out, "@ERROR: name-converter exec preparation failed\n");
return -1;
}
}
}
#endif

Expand Down Expand Up @@ -1004,6 +1018,9 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
err_msg = finish_pre_exec("pre-xfer exec", pre_exec_pid, pre_exec_error_fd);
}

if (namecvt_pid)
write_pre_exec_args(namecvt_fd_req, request, orig_early_argv, orig_argv, 2);

if (orig_early_argv)
free(orig_early_argv);

Expand Down Expand Up @@ -1100,7 +1117,8 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
#endif

if (!numeric_ids
&& (use_chroot ? lp_numeric_ids(module_id) != False : lp_numeric_ids(module_id) == True))
&& (use_chroot ? lp_numeric_ids(module_id) != False && !*lp_name_converter(module_id)
: lp_numeric_ids(module_id) == True))
numeric_ids = -1; /* Set --numeric-ids w/o breaking protocol. */

if (lp_timeout(module_id) && (!io_timeout || lp_timeout(module_id) < io_timeout))
Expand All @@ -1124,6 +1142,38 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
return 0;
}

BOOL namecvt_call(const char *cmd, const char **name_p, id_t *id_p)
{
char buf[1024];
int got, len;

if (*name_p)
len = snprintf(buf, sizeof buf, "%s %s\n", cmd, *name_p);
else
len = snprintf(buf, sizeof buf, "%s %ld\n", cmd, (long)*id_p);
if (len >= (int)sizeof buf) {
rprintf(FERROR, "namecvt_call() request was too large.\n");
exit_cleanup(RERR_UNSUPPORTED);
}

while ((got = write(namecvt_fd_req, buf, len)) != len) {
if (got < 0 && errno == EINTR)
continue;
rprintf(FERROR, "Connection to name-converter failed.\n");
exit_cleanup(RERR_SOCKETIO);
}

if (!read_line_old(namecvt_fd_ans, buf, sizeof buf, 0))
return False;

if (*name_p)
*id_p = (id_t)atol(buf);
else
*name_p = strdup(buf);

return True;
}

/* send a list of available modules to the client. Don't list those
with "list = False". */
static void send_listing(int fd)
Expand Down
1 change: 1 addition & 0 deletions daemon-parm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ STRING lock_file DEFAULT_LOCK_FILE
STRING log_file NULL
STRING log_format "%o %h [%a] %m (%u) %f %l"
STRING name NULL
STRING name_converter NULL
STRING outgoing_chmod NULL
STRING post-xfer_exec NULL
STRING pre-xfer_exec NULL
Expand Down
49 changes: 32 additions & 17 deletions rsyncd.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,21 +207,18 @@ the values of parameters. See the GLOBAL PARAMETERS section for more details.
they would escape the module hierarchy. The default for "use chroot" is
true, and is the safer choice (especially if the module is not read-only).

When this parameter is enabled, the "numeric-ids" option will also default
to being enabled (disabling name lookups). See below for what a chroot
needs in order for name lookups to succeed.
When this parameter is enabled *and* the "name converter" parameter is
*not* set, the "numeric ids" parameter will default to being enabled
(disabling name lookups). This means that if you manually setup
name-lookup libraries in your chroot (instead of using a name converter)
that you need to explicitly set `numeric ids = false` for rsync to do name
lookups.

If you copy library resources into the module's chroot area, you should
protect them through your OS's normal user/group or ACL settings (to
prevent the rsync module's user from being able to change them), and then
hide them from the user's view via "exclude" (see how in the discussion of
that parameter). At that point it will be safe to enable the mapping of
users and groups by name using the "numeric ids" daemon parameter (see
below).

Note also that you are free to setup custom user/group information in the
chroot area that is different from your normal system. For example, you
could abbreviate the list of users and groups.
that parameter). However, it's easier and safer to setup a name converter.

0. `daemon chroot`

Expand Down Expand Up @@ -258,6 +255,27 @@ the values of parameters. See the GLOBAL PARAMETERS section for more details.
others, then you will need to setup multiple rsync daemon processes on
different ports.

0. `name converter`

This parameter lets you specify a program that will be run by the rsync
daemon to do user & group conversions between names & ids. This script
is started prior to any chroot being setup, and runs as the daemon user
(not the transfer user). You can specify a fully qualified pathname or
a program name that is on the $PATH.

The program can be used to do normal user & group lookups without having to
put any extra files into the chroot area of the module *or* you can do
customized conversions.

The nameconvert program has access to all of the environment variables that
are described in the section on `pre-xfer exec`. This is useful if you
want to customize the conversion using information about the module and/or
the copy request.

There is a sample python script in the support dir named "nameconvert" that
implements the normal user & group lookups. Feel free to customize it or
just use it as documentation to implement your own.

0. `numeric ids`

Enabling this parameter disables the mapping of users and groups by name
Expand All @@ -269,13 +287,10 @@ the values of parameters. See the GLOBAL PARAMETERS section for more details.
uid/gid preservation requires the module to be running as root (see "uid")
or for "fake super" to be configured.

A chroot-enabled module should not have this parameter enabled unless
you've taken steps to ensure that the module has the necessary resources it
needs to translate names, and that it is not possible for a user to change
those resources. That includes being the code being able to call functions
like **getpwuid()**, **getgrgid()**, **getpwname()**, and **getgrnam()**.
You should test what libraries and config files are required for your OS
and get those setup before starting to test name mapping in rsync.
A chroot-enabled module should not have this parameter set to false unless
you're using a "name converter" program *or* you've taken steps to ensure
that the module has the necessary resources it needs to translate names and
that it is not possible for a user to change those resources.

0. `munge symlinks`

Expand Down
50 changes: 50 additions & 0 deletions support/nameconvert
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3

# This implements a simple protocol to do user & group conversions between
# names & ids. All input and output consists of simple strings with a
# terminating newline.
#
# The requests can be:
#
# uid ID_NUM\n -> NAME\n
# gid ID_NUM\n -> NAME\n
# usr NAME\n -> ID_NUM\n
# grp NAME\n -> ID_NUM\n
#
# 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" and
# "use chroot = true". While this converter uses real user & group lookups you
# could change it to use any mapping idiom you'd like.

import sys, argparse, pwd, grp

def main():
for line in sys.stdin:
try:
req, arg = line.rstrip().split(' ', 1)
except:
req = None
try:
if req == 'uid':
ans = pwd.getpwuid(int(arg)).pw_name
elif req == 'gid':
ans = grp.getgrgid(int(arg)).gr_name
elif req == 'usr':
ans = pwd.getpwnam(arg).pw_uid
elif req == 'grp':
ans = grp.getgrnam(arg).gr_gid
else:
print("Invalid request", file=sys.stderr)
sys.exit(1)
except KeyError:
ans = ''
print(ans, flush=True)


if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Convert users & groups between names & numbers for an rsync daemon.")
args = parser.parse_args()
main()

# vim: sw=4 et
Loading

0 comments on commit 7e07a32

Please sign in to comment.