upstream: use sftp_client crossloading to implement scp -3

feedback/ok markus@

OpenBSD-Commit-ID: 7db4c0086cfc12afc9cfb71d4c2fd3c7e9416ee9
This commit is contained in:
djm@openbsd.org 2021-08-07 00:06:30 +00:00 committed by Damien Miller
parent de7115b373
commit 318c06bb04

229
scp.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: scp.c,v 1.218 2021/08/07 00:00:33 djm Exp $ */ /* $OpenBSD: scp.c,v 1.219 2021/08/07 00:06:30 djm Exp $ */
/* /*
* scp - secure remote copy. This is basically patched BSD rcp which * scp - secure remote copy. This is basically patched BSD rcp which
* uses ssh to do the data transfer (instead of using rcmd). * uses ssh to do the data transfer (instead of using rcmd).
@ -139,7 +139,7 @@ extern char *__progname;
#define COPY_BUFLEN 16384 #define COPY_BUFLEN 16384
int do_cmd(char *program, char *host, char *remuser, int port, char *cmd, int do_cmd(char *program, char *host, char *remuser, int port, char *cmd,
int *fdin, int *fdout); int *fdin, int *fdout, pid_t *pidp);
int do_cmd2(char *host, char *remuser, int port, char *cmd, int do_cmd2(char *host, char *remuser, int port, char *cmd,
int fdin, int fdout); int fdin, int fdout);
@ -175,6 +175,7 @@ char *ssh_program = _PATH_SSH_PROGRAM;
/* This is used to store the pid of ssh_program */ /* This is used to store the pid of ssh_program */
pid_t do_cmd_pid = -1; pid_t do_cmd_pid = -1;
pid_t do_cmd_pid2 = -1;
/* Needed for sftp */ /* Needed for sftp */
volatile sig_atomic_t interrupted = 0; volatile sig_atomic_t interrupted = 0;
@ -189,6 +190,10 @@ killchild(int signo)
kill(do_cmd_pid, signo ? signo : SIGTERM); kill(do_cmd_pid, signo ? signo : SIGTERM);
waitpid(do_cmd_pid, NULL, 0); waitpid(do_cmd_pid, NULL, 0);
} }
if (do_cmd_pid2 > 1) {
kill(do_cmd_pid2, signo ? signo : SIGTERM);
waitpid(do_cmd_pid2, NULL, 0);
}
if (signo) if (signo)
_exit(1); _exit(1);
@ -196,19 +201,26 @@ killchild(int signo)
} }
static void static void
suspchild(int signo) suspone(int pid, int signo)
{ {
int status; int status;
if (do_cmd_pid > 1) { if (pid > 1) {
kill(do_cmd_pid, signo); kill(pid, signo);
while (waitpid(do_cmd_pid, &status, WUNTRACED) == -1 && while (waitpid(pid, &status, WUNTRACED) == -1 &&
errno == EINTR) errno == EINTR)
; ;
kill(getpid(), SIGSTOP);
} }
} }
static void
suspchild(int signo)
{
suspone(do_cmd_pid, signo);
suspone(do_cmd_pid2, signo);
kill(getpid(), SIGSTOP);
}
static int static int
do_local_cmd(arglist *a) do_local_cmd(arglist *a)
{ {
@ -259,7 +271,7 @@ do_local_cmd(arglist *a)
int int
do_cmd(char *program, char *host, char *remuser, int port, char *cmd, do_cmd(char *program, char *host, char *remuser, int port, char *cmd,
int *fdin, int *fdout) int *fdin, int *fdout, int *pid)
{ {
int pin[2], pout[2], reserved[2]; int pin[2], pout[2], reserved[2];
@ -294,8 +306,8 @@ do_cmd(char *program, char *host, char *remuser, int port, char *cmd,
ssh_signal(SIGTTOU, suspchild); ssh_signal(SIGTTOU, suspchild);
/* Fork a child to execute the command on the remote host using ssh. */ /* Fork a child to execute the command on the remote host using ssh. */
do_cmd_pid = fork(); *pid = fork();
if (do_cmd_pid == 0) { if (*pid == 0) {
/* Child. */ /* Child. */
close(pin[1]); close(pin[1]);
close(pout[0]); close(pout[0]);
@ -320,7 +332,7 @@ do_cmd(char *program, char *host, char *remuser, int port, char *cmd,
execvp(program, args.list); execvp(program, args.list);
perror(program); perror(program);
exit(1); exit(1);
} else if (do_cmd_pid == -1) { } else if (*pid == -1) {
fatal("fork: %s", strerror(errno)); fatal("fork: %s", strerror(errno));
} }
/* Parent. Close the other side, and return the local side. */ /* Parent. Close the other side, and return the local side. */
@ -340,10 +352,11 @@ do_cmd(char *program, char *host, char *remuser, int port, char *cmd,
* This way the input and output of two commands can be connected. * This way the input and output of two commands can be connected.
*/ */
int int
do_cmd2(char *host, char *remuser, int port, char *cmd, int fdin, int fdout) do_cmd2(char *host, char *remuser, int port, char *cmd,
int fdin, int fdout)
{ {
pid_t pid;
int status; int status;
pid_t pid;
if (verbose_mode) if (verbose_mode)
fmprintf(stderr, fmprintf(stderr,
@ -403,7 +416,7 @@ void verifydir(char *);
struct passwd *pwd; struct passwd *pwd;
uid_t userid; uid_t userid;
int errs, remin, remout; int errs, remin, remout, remin2, remout2;
int Tflag, pflag, iamremote, iamrecursive, targetshouldbedirectory; int Tflag, pflag, iamremote, iamrecursive, targetshouldbedirectory;
#define CMDNEEDS 64 #define CMDNEEDS 64
@ -424,6 +437,8 @@ void usage(void);
void source_sftp(int, char *, char *, struct sftp_conn *, char **); void source_sftp(int, char *, char *, struct sftp_conn *, char **);
void sink_sftp(int, char *, const char *, struct sftp_conn *); void sink_sftp(int, char *, const char *, struct sftp_conn *);
void throughlocal_sftp(struct sftp_conn *, struct sftp_conn *,
char *, char *, char **);
int int
main(int argc, char **argv) main(int argc, char **argv)
@ -943,22 +958,23 @@ brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp)
} }
static struct sftp_conn * static struct sftp_conn *
do_sftp_connect(char *host, char *user, int port, char *sftp_direct) do_sftp_connect(char *host, char *user, int port, char *sftp_direct,
int *reminp, int *remoutp, int *pidp)
{ {
if (sftp_direct == NULL) { if (sftp_direct == NULL) {
addargs(&args, "-s"); addargs(&args, "-s");
if (do_cmd(ssh_program, host, user, port, "sftp", if (do_cmd(ssh_program, host, user, port, "sftp",
&remin, &remout) < 0) reminp, remoutp, pidp) < 0)
return NULL; return NULL;
} else { } else {
args.list = NULL; args.list = NULL;
addargs(&args, "sftp-server"); addargs(&args, "sftp-server");
if (do_cmd(sftp_direct, host, NULL, -1, "sftp", if (do_cmd(sftp_direct, host, NULL, -1, "sftp",
&remin, &remout) < 0) reminp, remoutp, pidp) < 0)
return NULL; return NULL;
} }
return do_init(remin, remout, 32768, 64, limit_kbps); return do_init(*reminp, *remoutp, 32768, 64, limit_kbps);
} }
void void
@ -968,9 +984,9 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
char *bp, *tuser, *thost, *targ; char *bp, *tuser, *thost, *targ;
char *remote_path = NULL; char *remote_path = NULL;
int sport = -1, tport = -1; int sport = -1, tport = -1;
struct sftp_conn *conn = NULL; struct sftp_conn *conn = NULL, *conn2 = NULL;
arglist alist; arglist alist;
int i, r; int i, r, status;
u_int j; u_int j;
memset(&alist, '\0', sizeof(alist)); memset(&alist, '\0', sizeof(alist));
@ -1011,21 +1027,64 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
continue; continue;
} }
if (host && throughlocal) { /* extended remote to remote */ if (host && throughlocal) { /* extended remote to remote */
/* XXX uses scp; need to support SFTP remote-remote */ if (mode == MODE_SFTP) {
xasprintf(&bp, "%s -f %s%s", cmd, if (remin == -1) {
*src == '-' ? "-- " : "", src); /* Connect to dest now */
if (do_cmd(ssh_program, host, suser, sport, bp, conn = do_sftp_connect(thost, tuser,
&remin, &remout) < 0) tport, sftp_direct,
exit(1); &remin, &remout, &do_cmd_pid);
free(bp); if (conn == NULL) {
xasprintf(&bp, "%s -t %s%s", cmd, fatal("Unable to open "
*targ == '-' ? "-- " : "", targ); "destination connection");
if (do_cmd2(thost, tuser, tport, bp, remin, remout) < 0) }
exit(1); debug3_f("origin in %d out %d pid %ld",
free(bp); remin, remout, (long)do_cmd_pid);
(void) close(remin); }
(void) close(remout); /*
remin = remout = -1; * XXX remember suser/host/sport and only
* reconnect if they change between arguments.
* would save reconnections for cases like
* scp -3 hosta:/foo hosta:/bar hostb:
*/
/* Connect to origin now */
conn2 = do_sftp_connect(host, suser,
sport, sftp_direct,
&remin2, &remout2, &do_cmd_pid2);
if (conn2 == NULL) {
fatal("Unable to open "
"source connection");
}
debug3_f("destination in %d out %d pid %ld",
remin2, remout2, (long)do_cmd_pid2);
throughlocal_sftp(conn2, conn, src, targ,
&remote_path);
(void) close(remin2);
(void) close(remout2);
remin2 = remout2 = -1;
if (waitpid(do_cmd_pid2, &status, 0) == -1)
++errs;
else if (!WIFEXITED(status) ||
WEXITSTATUS(status) != 0)
++errs;
do_cmd_pid2 = -1;
continue;
} else {
xasprintf(&bp, "%s -f %s%s", cmd,
*src == '-' ? "-- " : "", src);
if (do_cmd(ssh_program, host, suser, sport,
bp, &remin, &remout, &do_cmd_pid) < 0)
exit(1);
free(bp);
xasprintf(&bp, "%s -t %s%s", cmd,
*targ == '-' ? "-- " : "", targ);
if (do_cmd2(thost, tuser, tport, bp,
remin, remout) < 0)
exit(1);
free(bp);
(void) close(remin);
(void) close(remout);
remin = remout = -1;
}
} else if (host) { /* standard remote to remote */ } else if (host) { /* standard remote to remote */
/* /*
* Second remote user is passed to first remote side * Second remote user is passed to first remote side
@ -1074,7 +1133,8 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
if (remin == -1) { if (remin == -1) {
/* Connect to remote now */ /* Connect to remote now */
conn = do_sftp_connect(thost, tuser, conn = do_sftp_connect(thost, tuser,
tport, sftp_direct); tport, sftp_direct,
&remin, &remout, &do_cmd_pid);
if (conn == NULL) { if (conn == NULL) {
fatal("Unable to open sftp " fatal("Unable to open sftp "
"connection"); "connection");
@ -1091,7 +1151,7 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
xasprintf(&bp, "%s -t %s%s", cmd, xasprintf(&bp, "%s -t %s%s", cmd,
*targ == '-' ? "-- " : "", targ); *targ == '-' ? "-- " : "", targ);
if (do_cmd(ssh_program, thost, tuser, tport, bp, if (do_cmd(ssh_program, thost, tuser, tport, bp,
&remin, &remout) < 0) &remin, &remout, &do_cmd_pid) < 0)
exit(1); exit(1);
if (response() < 0) if (response() < 0)
exit(1); exit(1);
@ -1156,7 +1216,8 @@ tolocal(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
} }
/* Remote to local. */ /* Remote to local. */
if (mode == MODE_SFTP) { if (mode == MODE_SFTP) {
conn = do_sftp_connect(host, suser, sport, sftp_direct); conn = do_sftp_connect(host, suser, sport,
sftp_direct, &remin, &remout, &do_cmd_pid);
if (conn == NULL) { if (conn == NULL) {
error("Couldn't make sftp connection " error("Couldn't make sftp connection "
"to server"); "to server");
@ -1176,8 +1237,8 @@ tolocal(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
/* SCP */ /* SCP */
xasprintf(&bp, "%s -f %s%s", xasprintf(&bp, "%s -f %s%s",
cmd, *src == '-' ? "-- " : "", src); cmd, *src == '-' ? "-- " : "", src);
if (do_cmd(ssh_program, host, suser, sport, bp, &remin, if (do_cmd(ssh_program, host, suser, sport, bp,
&remout) < 0) { &remin, &remout, &do_cmd_pid) < 0) {
free(bp); free(bp);
++errs; ++errs;
continue; continue;
@ -1808,6 +1869,94 @@ screwup:
exit(1); exit(1);
} }
void
throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
char *src, char *targ, char **to_remote_path)
{
char *target = NULL, *filename = NULL, *abs_dst = NULL;
char *abs_src = NULL, *tmp = NULL, *from_remote_path;
glob_t g;
int i, r, targetisdir, err = 0;
if (*to_remote_path == NULL) {
*to_remote_path = do_realpath(to, ".");
if (*to_remote_path == NULL) {
fatal("Unable to determine destination remote "
"working directory");
}
}
if ((from_remote_path = do_realpath(from, ".")) == NULL) {
fatal("Unable to determine source remote "
"working directory");
}
if ((filename = basename(src)) == NULL)
fatal("basename %s: %s", src, strerror(errno));
abs_src = xstrdup(src);
abs_src = make_absolute(abs_src, from_remote_path);
free(from_remote_path);
target = xstrdup(targ);
target = make_absolute(target, *to_remote_path);
memset(&g, 0, sizeof(g));
targetisdir = remote_is_dir(to, target);
if (!targetisdir && targetshouldbedirectory) {
error("Destination path \"%s\" is not a directory", target);
err = -1;
goto out;
}
debug3_f("copying remote %s to remote %s", abs_src, target);
if ((r = remote_glob(from, abs_src, GLOB_MARK, NULL, &g)) != 0) {
if (r == GLOB_NOSPACE)
error("Too many glob matches for \"%s\".", abs_src);
else
error("File \"%s\" not found.", abs_src);
err = -1;
goto out;
}
for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
tmp = xstrdup(g.gl_pathv[i]);
if ((filename = basename(tmp)) == NULL) {
error("basename %s: %s", tmp, strerror(errno));
err = -1;
goto out;
}
if (targetisdir)
abs_dst = path_append(target, filename);
else
abs_dst = xstrdup(target);
debug("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
if (globpath_is_dir(g.gl_pathv[i]) && iamrecursive) {
if (crossload_dir(from, to, g.gl_pathv[i], abs_dst,
NULL, pflag, 1) == -1)
err = -1;
} else {
if (do_crossload(from, to, g.gl_pathv[i], abs_dst, NULL,
pflag) == -1)
err = -1;
}
free(abs_dst);
abs_dst = NULL;
free(tmp);
tmp = NULL;
}
out:
free(abs_src);
free(abs_dst);
free(target);
free(tmp);
globfree(&g);
if (err == -1)
fatal("Failed to download file '%s'", src);
}
int int
response(void) response(void)
{ {