summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWayne Davison <wayne@opencoder.net>2020-06-10 20:10:53 -0700
committerWayne Davison <wayne@opencoder.net>2020-06-10 21:38:37 -0700
commita3377921ebe651cb7d2b969853cb9fe0e135ff75 (patch)
treeef832936d812ec769d84e6109935a550cdcb2f75
parenta61ffbafe5682b65d4a2a8846e9ec20298ba7e17 (diff)
downloadrsync-a3377921ebe651cb7d2b969853cb9fe0e135ff75.tar.gz
Add `early exec` daemon parameter.
Inspired by Ciprian Dorin Craciun's `bootstrap exec` patch.
-rw-r--r--NEWS.md7
-rw-r--r--clientserver.c293
-rw-r--r--loadparm.c8
-rw-r--r--rsyncd.conf.5.md31
4 files changed, 215 insertions, 124 deletions
diff --git a/NEWS.md b/NEWS.md
index 837775ef..88d4f388 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -47,6 +47,9 @@ Protocol: 31 (unchanged)
- Avoid a hang when an overabundance of messages clogs up all the I/O buffers.
+ - Fixed a mismatch in the RSYNC_PID values when running both a `pre-xfer exec`
+ and a `post-xfer exec`.
+
### ENHANCEMENTS:
- Various checksum enhancements, including the optional use of openssl's MD4 &
@@ -95,6 +98,10 @@ Protocol: 31 (unchanged)
- Added negated matching to the daemon's `refuse options` setting by using
match strings that start with a `!` (such as `!compress*`).
+ - Added an `early exec` daemon parameter that runs a script before the
+ transfer parameters are known, allowing some early setup based on module
+ name.
+
- Added status output in response to a signal (via both SIGINFO & SIGVTALRM).
- Added a `--copy-as=USER` option to give some extra security to root-run
diff --git a/clientserver.c b/clientserver.c
index 91f78b7b..2208e1be 100644
--- a/clientserver.c
+++ b/clientserver.c
@@ -349,11 +349,123 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char
return 0;
}
-static char *finish_pre_exec(pid_t pid, int write_fd, int read_fd, char *request,
- char **early_argv, char **argv)
+#ifdef HAVE_PUTENV
+static int read_arg_from_pipe(int fd, char *buf, int limit)
+{
+ char *bp = buf, *eob = buf + limit - 1;
+
+ while (1) {
+ int got = read(fd, bp, 1);
+ if (got != 1) {
+ if (got < 0 && errno == EINTR)
+ continue;
+ return -1;
+ }
+ if (*bp == '\0')
+ break;
+ if (bp < eob)
+ bp++;
+ }
+ *bp = '\0';
+
+ return bp - buf;
+}
+#endif
+
+static void set_env_str(const char *var, const char *str)
+{
+#ifdef HAVE_PUTENV
+ char *mem;
+ if (asprintf(&mem, "%s=%s", var, str) < 0)
+ out_of_memory("set_env_str");
+ putenv(mem);
+#endif
+}
+
+#ifdef HAVE_PUTENV
+void set_env_num(const char *var, long num)
+{
+ char *mem;
+ if (asprintf(&mem, "%s=%ld", var, num) < 0)
+ out_of_memory("set_env_num");
+ putenv(mem);
+}
+#endif
+
+/* Used for both early exec & pre-xfer exec */
+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, error_fd;
+ pid_t pid;
+
+ if ((error_fd_ptr && pipe(error_fds) < 0) || (arg_fd_ptr && pipe(arg_fds) < 0) || (pid = fork()) < 0)
+ return (pid_t)-1;
+
+ if (pid == 0) {
+ char buf[BIGPATHBUFLEN];
+ int j, len, status;
+
+ if (error_fd_ptr) {
+ close(error_fds[0]);
+ error_fd = error_fds[1];
+ set_blocking(error_fd);
+ }
+
+ if (arg_fd_ptr) {
+ close(arg_fds[1]);
+ arg_fd = arg_fds[0];
+ set_blocking(arg_fd);
+
+ len = read_arg_from_pipe(arg_fd, buf, BIGPATHBUFLEN);
+ if (len <= 0)
+ _exit(1);
+ set_env_str("RSYNC_REQUEST", buf);
+
+ for (j = 0; ; j++) {
+ char *p;
+ len = read_arg_from_pipe(arg_fd, buf, BIGPATHBUFLEN);
+ if (len <= 0) {
+ if (!len)
+ break;
+ _exit(1);
+ }
+ if (asprintf(&p, "RSYNC_ARG%d=%s", j, buf) >= 0)
+ putenv(p);
+ }
+ close(arg_fd);
+ }
+
+ if (error_fd_ptr) {
+ close(STDIN_FILENO);
+ dup2(error_fd, STDOUT_FILENO);
+ close(error_fd);
+ }
+
+ status = shell_exec(cmd);
+
+ if (!WIFEXITED(status))
+ _exit(1);
+ _exit(WEXITSTATUS(status));
+ }
+
+ if (error_fd_ptr) {
+ close(error_fds[1]);
+ error_fd = *error_fd_ptr = error_fds[0];
+ set_blocking(error_fd);
+ }
+
+ if (arg_fd_ptr) {
+ close(arg_fds[0]);
+ arg_fd = *arg_fd_ptr = arg_fds[1];
+ set_blocking(arg_fd);
+ }
+
+ return pid;
+}
+
+static void write_pre_exec_args(int write_fd, char *request, char **early_argv, char **argv)
{
- char buf[BIGPATHBUFLEN], *bp;
- int j = 0, status = -1, msglen = sizeof buf - 1;
+ int j = 0;
if (!request)
request = "(NONE)";
@@ -369,33 +481,51 @@ static char *finish_pre_exec(pid_t pid, int write_fd, int read_fd, char *request
write_byte(write_fd, 0);
close(write_fd);
+}
- /* Read the stdout from the pre-xfer exec program. This it is only
- * displayed to the user if the script also returns an error status. */
- for (bp = buf; msglen > 0; msglen -= j) {
- if ((j = read(read_fd, bp, msglen)) <= 0) {
- if (j == 0)
- break;
- if (errno == EINTR)
- continue;
- break; /* Just ignore the read error for now... */
- }
- bp += j;
- if (j > 1 && bp[-1] == '\n' && bp[-2] == '\r') {
- bp--;
- j--;
- bp[-1] = '\n';
+static char *finish_pre_exec(const char *desc, pid_t pid, int read_fd)
+{
+ char buf[BIGPATHBUFLEN], *bp, *cr;
+ int j, status = -1, msglen = sizeof buf - 1;
+
+ if (read_fd >= 0) {
+ /* Read the stdout from the program. This it is only displayed
+ * to the user if the script also returns an error status. */
+ for (bp = buf, cr = buf; msglen > 0; msglen -= j) {
+ if ((j = read(read_fd, bp, msglen)) <= 0) {
+ if (j == 0)
+ break;
+ if (errno == EINTR)
+ continue;
+ break; /* Just ignore the read error for now... */
+ }
+ bp[j] = '\0';
+ while (1) {
+ if ((cr = strchr(cr, '\r')) == NULL) {
+ cr = bp + j;
+ break;
+ }
+ if (!cr[1])
+ break; /* wait for more data before we decide what to do */
+ if (cr[1] == '\n') {
+ memmove(cr, cr+1, j - (cr - bp));
+ j--;
+ } else
+ cr++;
+ }
+ bp += j;
}
- }
- *bp = '\0';
+ *bp = '\0';
- close(read_fd);
+ close(read_fd);
+ } else
+ *buf = '\0';
if (wait_process(pid, &status, 0) < 0
|| !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
char *e;
- if (asprintf(&e, "pre-xfer exec returned failure (%d)%s%s%s\n%s",
- status, status < 0 ? ": " : "",
+ if (asprintf(&e, "%s returned failure (%d)%s%s%s\n%s",
+ desc, status, status < 0 ? ": " : "",
status < 0 ? strerror(errno) : "",
*buf ? ":" : "", buf) < 0)
return "out_of_memory in finish_pre_exec\n";
@@ -404,29 +534,6 @@ static char *finish_pre_exec(pid_t pid, int write_fd, int read_fd, char *request
return NULL;
}
-#ifdef HAVE_PUTENV
-static int read_arg_from_pipe(int fd, char *buf, int limit)
-{
- char *bp = buf, *eob = buf + limit - 1;
-
- while (1) {
- int got = read(fd, bp, 1);
- if (got != 1) {
- if (got < 0 && errno == EINTR)
- continue;
- return -1;
- }
- if (*bp == '\0')
- break;
- if (bp < eob)
- bp++;
- }
- *bp = '\0';
-
- return bp - buf;
-}
-#endif
-
static int path_failure(int f_out, const char *dir, BOOL was_chdir)
{
if (was_chdir)
@@ -478,26 +585,6 @@ static struct passwd *want_all_groups(int f_out, uid_t uid)
}
#endif
-static void set_env_str(const char *var, const char *str)
-{
-#ifdef HAVE_PUTENV
- char *mem;
- if (asprintf(&mem, "%s=%s", var, str) < 0)
- out_of_memory("set_env_str");
- putenv(mem);
-#endif
-}
-
-#ifdef HAVE_PUTENV
-void set_env_num(const char *var, long num)
-{
- char *mem;
- if (asprintf(&mem, "%s=%ld", var, num) < 0)
- out_of_memory("set_env_num");
- putenv(mem);
-}
-#endif
-
static int rsync_module(int f_in, int f_out, int i, const char *addr, const char *host)
{
int argc;
@@ -690,8 +777,9 @@ 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)) && !getenv("RSYNC_NO_XFER_EXEC")) {
- int status;
+ if ((*lp_early_exec(i) || *lp_prexfer_exec(i) || *lp_postxfer_exec(i))
+ && !getenv("RSYNC_NO_XFER_EXEC")) {
+ set_env_num("RSYNC_PID", (long)getpid());
/* For post-xfer exec, fork a new process to run the rsync
* daemon while this process waits for the exit status and
@@ -704,10 +792,10 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
return -1;
}
if (pid) {
+ int status;
close(f_in);
if (f_out != f_in)
close(f_out);
- set_env_num("RSYNC_PID", (long)pid);
if (wait_process(pid, &status, 0) < 0)
status = -1;
set_env_num("RSYNC_RAW_STATUS", status);
@@ -721,56 +809,33 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
_exit(status);
}
}
+
+ /* For early exec, fork a child process to run the indicated
+ * command and wait for it to exit. */
+ if (*lp_early_exec(i)) {
+ pid_t pid = start_pre_exec(lp_early_exec(i), NULL, NULL);
+ if (pid == (pid_t)-1) {
+ rsyserr(FLOG, errno, "early exec preparation failed");
+ io_printf(f_out, "@ERROR: early exec preparation failed\n");
+ return -1;
+ }
+ if (finish_pre_exec("early exec", pid, -1) != NULL) {
+ rsyserr(FLOG, errno, "early exec failed");
+ io_printf(f_out, "@ERROR: early exec failed\n");
+ return -1;
+ }
+ }
+
/* For pre-xfer exec, fork a child process to run the indicated
* command, though it first waits for the parent process to
* send us the user's request via a pipe. */
if (*lp_prexfer_exec(i)) {
- int arg_fds[2], error_fds[2];
- set_env_num("RSYNC_PID", (long)getpid());
- if (pipe(arg_fds) < 0 || pipe(error_fds) < 0 || (pre_exec_pid = fork()) < 0) {
+ pre_exec_pid = start_pre_exec(lp_prexfer_exec(i), &pre_exec_arg_fd, &pre_exec_error_fd);
+ if (pre_exec_pid == (pid_t)-1) {
rsyserr(FLOG, errno, "pre-xfer exec preparation failed");
io_printf(f_out, "@ERROR: pre-xfer exec preparation failed\n");
return -1;
}
- if (pre_exec_pid == 0) {
- char buf[BIGPATHBUFLEN];
- int j, len;
- close(arg_fds[1]);
- close(error_fds[0]);
- pre_exec_arg_fd = arg_fds[0];
- pre_exec_error_fd = error_fds[1];
- set_blocking(pre_exec_arg_fd);
- set_blocking(pre_exec_error_fd);
- len = read_arg_from_pipe(pre_exec_arg_fd, buf, BIGPATHBUFLEN);
- if (len <= 0)
- _exit(1);
- set_env_str("RSYNC_REQUEST", buf);
- for (j = 0; ; j++) {
- len = read_arg_from_pipe(pre_exec_arg_fd, buf,
- BIGPATHBUFLEN);
- if (len <= 0) {
- if (!len)
- break;
- _exit(1);
- }
- if (asprintf(&p, "RSYNC_ARG%d=%s", j, buf) >= 0)
- putenv(p);
- }
- close(pre_exec_arg_fd);
- close(STDIN_FILENO);
- dup2(pre_exec_error_fd, STDOUT_FILENO);
- close(pre_exec_error_fd);
- status = shell_exec(lp_prexfer_exec(i));
- if (!WIFEXITED(status))
- _exit(1);
- _exit(WEXITSTATUS(status));
- }
- close(arg_fds[0]);
- close(error_fds[1]);
- pre_exec_arg_fd = arg_fds[1];
- pre_exec_error_fd = error_fds[0];
- set_blocking(pre_exec_arg_fd);
- set_blocking(pre_exec_error_fd);
}
}
#endif
@@ -889,8 +954,8 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
msgs2stderr = 0; /* A non-rsh-run daemon doesn't have stderr for msgs. */
if (pre_exec_pid) {
- err_msg = finish_pre_exec(pre_exec_pid, pre_exec_arg_fd, pre_exec_error_fd,
- request, orig_early_argv, orig_argv);
+ write_pre_exec_args(pre_exec_arg_fd, request, orig_early_argv, orig_argv);
+ err_msg = finish_pre_exec("pre-xfer exec", pre_exec_pid, pre_exec_error_fd);
}
if (orig_early_argv)
diff --git a/loadparm.c b/loadparm.c
index bd8a5208..e21bf51e 100644
--- a/loadparm.c
+++ b/loadparm.c
@@ -122,6 +122,7 @@ typedef struct {
char *charset;
char *comment;
char *dont_compress;
+ char *early_exec;
char *exclude;
char *exclude_from;
char *filter;
@@ -150,6 +151,7 @@ typedef struct {
BOOL charset_EXP;
BOOL comment_EXP;
BOOL dont_compress_EXP;
+ BOOL early_exec_EXP;
BOOL exclude_EXP;
BOOL exclude_from_EXP;
BOOL filter_EXP;
@@ -236,7 +238,8 @@ static const all_vars Defaults = {
/* charset; */ NULL,
/* comment; */ NULL,
/* dont_compress; */ DEFAULT_DONT_COMPRESS,
- /* exclude; */ NULL,
+ /* early_exec; */ NULL,
+ /* exclude; */ NULL,
/* exclude_from; */ NULL,
/* filter; */ NULL,
/* gid; */ NULL,
@@ -263,6 +266,7 @@ static const all_vars Defaults = {
/* charset_EXP; */ False,
/* comment_EXP; */ False,
/* dont_compress_EXP; */ False,
+ /* early_exec_EXP; */ False,
/* exclude_EXP; */ False,
/* exclude_from_EXP; */ False,
/* filter_EXP; */ False,
@@ -404,6 +408,7 @@ static struct parm_struct parm_table[] =
{"charset", P_STRING, P_LOCAL, &Vars.l.charset, NULL,0},
{"comment", P_STRING, P_LOCAL, &Vars.l.comment, NULL,0},
{"dont compress", P_STRING, P_LOCAL, &Vars.l.dont_compress, NULL,0},
+ {"early exec", P_STRING, P_LOCAL, &Vars.l.early_exec, NULL,0},
{"exclude from", P_STRING, P_LOCAL, &Vars.l.exclude_from, NULL,0},
{"exclude", P_STRING, P_LOCAL, &Vars.l.exclude, NULL,0},
{"fake super", P_BOOL, P_LOCAL, &Vars.l.fake_super, NULL,0},
@@ -543,6 +548,7 @@ FN_LOCAL_STRING(lp_auth_users, auth_users)
FN_LOCAL_STRING(lp_charset, charset)
FN_LOCAL_STRING(lp_comment, comment)
FN_LOCAL_STRING(lp_dont_compress, dont_compress)
+FN_LOCAL_STRING(lp_early_exec, early_exec)
FN_LOCAL_STRING(lp_exclude, exclude)
FN_LOCAL_STRING(lp_exclude_from, exclude_from)
FN_LOCAL_STRING(lp_filter, filter)
diff --git a/rsyncd.conf.5.md b/rsyncd.conf.5.md
index 41b64e78..e4b673ef 100644
--- a/rsyncd.conf.5.md
+++ b/rsyncd.conf.5.md
@@ -917,15 +917,28 @@ the values of parameters. See the GLOBAL PARAMETERS section for more details.
for the "dont compress" parameter changes the default when the daemon is
the sender.
-0. `pre-xfer exec`, `post-xfer exec`
-
- You may specify a command to be run before and/or after the transfer. If
- the `pre-xfer exec` command fails, the transfer is aborted before it
- begins. Any output from the script on stdout (up to several KB) will be
- displayed to the user when aborting, but is NOT displayed if the script
- returns success. Any output from the script on stderr goes to the daemon's
- stderr, which is typically discarded (though see --no-detatch option for a
- way to see the stderr output, which can assist with debugging).
+0. `early exec`, `pre-xfer exec`, `post-xfer exec`
+
+ You may specify a command to be run in the early stages of the connection,
+ or right before and/or after the transfer. If the `early exec` or
+ `pre-xfer exec` command returns an error code, the transfer is aborted
+ before it begins. Any output from the `pre-xfer exec` command on stdout
+ (up to several KB) will be displayed to the user when aborting, but is
+ _not_ displayed if the script returns success. The other programs cannot
+ send any text to the user. All output except for the `pre-xfer exec`
+ stdout goes to the corresponding daemon's stdout/stderr, which is typically
+ discarded. See the `--no-detatch` option for a way to see the daemon's
+ output, which can assist with debugging.
+
+ Note that the `early exec` command runs before any part of the transfer
+ request is known except for the module name. This helper script can be
+ used to setup a disk mount or decrypt some data into a module dir, but you
+ may need to use `lock file` and `max connections` to avoid concurrency
+ issues.
+
+ Note that the `post-xfer exec` command is still run even if one of the
+ other scripts returns an error code. The `pre-xfer exec` command will _not_
+ be run, however, if the `early exec` command fails.
The following environment variables will be set, though some are specific
to the pre-xfer or the post-xfer environment: