Add the `name converter` daemon parameter.
authorWayne Davison <wayne@opencoder.net>
Fri, 17 Jul 2020 17:22:27 +0000 (10:22 -0700)
committerWayne Davison <wayne@opencoder.net>
Fri, 17 Jul 2020 17:30:59 +0000 (10:30 -0700)
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".

NEWS.md
clientserver.c
daemon-parm.txt
rsyncd.conf.5.md
support/nameconvert [new file with mode: 0755]
uidlist.c

diff --git a/NEWS.md b/NEWS.md
index c83e0367c1b80b44a590f513743c5c08c4c260a6..6ab5386160cce11ae2b05bba0fb6b62549b55e28 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
 
  - 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.
index 372f9ab3eb3b2a505c3b6e1216e394fcda757b68..f324a9891c9486a74086b32bf77d3d8896463939 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "rsync.h"
 #include "itypes.h"
+#include "ifuncs.h"
 
 extern int quiet;
 extern int dry_run;
@@ -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="
@@ -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;
@@ -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;
@@ -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;
 
@@ -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)
@@ -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());
 
@@ -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
 
@@ -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);
 
@@ -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))
@@ -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)
index 0f8f01e51ebd1416ee802b1d2a7377e1faaede78..3b438b0225a0d68ded6e79780c39d624bc0cb2e5 100644 (file)
@@ -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
index 8b696b40750f3f38b2cdc43920a062b5b47ea812..b70532b102659ae706eeb186b835b186ed5e1143 100644 (file)
@@ -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`
 
@@ -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
@@ -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`
 
diff --git a/support/nameconvert b/support/nameconvert
new file mode 100755 (executable)
index 0000000..968fdbe
--- /dev/null
@@ -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
index 4506de2e033a7ca1016a271facbb663c822445ae..88847424d8f5c585c62493d68bfec7c2c83dfd63 100644 (file)
--- a/uidlist.c
+++ b/uidlist.c
@@ -34,6 +34,7 @@ extern int preserve_gid;
 extern int preserve_acls;
 extern int numeric_ids;
 extern int xmit_id0_names;
+extern pid_t namecvt_pid;
 extern gid_t our_gid;
 extern char *usermap;
 extern char *groupmap;
@@ -98,50 +99,86 @@ static struct idlist *add_to_list(struct idlist **root, id_t id, union name_or_i
 /* turn a uid into a user name */
 const char *uid_to_user(uid_t uid)
 {
-       struct passwd *pass = getpwuid(uid);
-       if (pass)
-               return strdup(pass->pw_name);
-       return NULL;
+       const char *name = NULL;
+
+       if (namecvt_pid) {
+               id_t id = uid;
+               namecvt_call("uid", &name, &id);
+       } else {
+               struct passwd *pass = getpwuid(uid);
+               if (pass)
+                       name = strdup(pass->pw_name);
+       }
+
+       return name;
 }
 
 /* turn a gid into a group name */
 const char *gid_to_group(gid_t gid)
 {
-       struct group *grp = getgrgid(gid);
-       if (grp)
-               return strdup(grp->gr_name);
-       return NULL;
+       const char *name = NULL;
+
+       if (namecvt_pid) {
+               id_t id = gid;
+               namecvt_call("gid", &name, &id);
+       } else {
+               struct group *grp = getgrgid(gid);
+               if (grp)
+                       name = strdup(grp->gr_name);
+       }
+
+       return name;
 }
 
 /* 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;
        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) {
+               id_t id;
+               if (!namecvt_call("usr", &name, &id))
+                       return 0;
+               *uid_p = id;
+       } else {
+               struct passwd *pass = getpwnam(name);
+               if (!pass)
+                       return 0;
+               *uid_p = pass->pw_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;
        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) {
+               id_t id;
+               if (!namecvt_call("grp", &name, &id))
+                       return 0;
+               *gid_p = id;
+       } else {
+               struct group *grp = getgrnam(name);
+               if (!grp)
+                       return 0;
+               *gid_p = grp->gr_gid;
+       }
+
        return 1;
 }