From 318c06bb04ee21a0cfa6b6022a201eacaa53f388 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Sat, 7 Aug 2021 00:06:30 +0000 Subject: upstream: use sftp_client crossloading to implement scp -3 feedback/ok markus@ OpenBSD-Commit-ID: 7db4c0086cfc12afc9cfb71d4c2fd3c7e9416ee9 --- scp.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 189 insertions(+), 40 deletions(-) (limited to 'scp.c') diff --git a/scp.c b/scp.c index 3125d336..9be41a26 100644 --- a/scp.c +++ b/scp.c @@ -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 * uses ssh to do the data transfer (instead of using rcmd). @@ -139,7 +139,7 @@ extern char *__progname; #define COPY_BUFLEN 16384 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 fdin, int fdout); @@ -175,6 +175,7 @@ char *ssh_program = _PATH_SSH_PROGRAM; /* This is used to store the pid of ssh_program */ pid_t do_cmd_pid = -1; +pid_t do_cmd_pid2 = -1; /* Needed for sftp */ volatile sig_atomic_t interrupted = 0; @@ -189,6 +190,10 @@ killchild(int signo) kill(do_cmd_pid, signo ? signo : SIGTERM); 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) _exit(1); @@ -196,19 +201,26 @@ killchild(int signo) } static void -suspchild(int signo) +suspone(int pid, int signo) { int status; - if (do_cmd_pid > 1) { - kill(do_cmd_pid, signo); - while (waitpid(do_cmd_pid, &status, WUNTRACED) == -1 && + if (pid > 1) { + kill(pid, signo); + while (waitpid(pid, &status, WUNTRACED) == -1 && 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 do_local_cmd(arglist *a) { @@ -259,7 +271,7 @@ do_local_cmd(arglist *a) int 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]; @@ -294,8 +306,8 @@ do_cmd(char *program, char *host, char *remuser, int port, char *cmd, ssh_signal(SIGTTOU, suspchild); /* Fork a child to execute the command on the remote host using ssh. */ - do_cmd_pid = fork(); - if (do_cmd_pid == 0) { + *pid = fork(); + if (*pid == 0) { /* Child. */ close(pin[1]); close(pout[0]); @@ -320,7 +332,7 @@ do_cmd(char *program, char *host, char *remuser, int port, char *cmd, execvp(program, args.list); perror(program); exit(1); - } else if (do_cmd_pid == -1) { + } else if (*pid == -1) { fatal("fork: %s", strerror(errno)); } /* 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. */ 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; + pid_t pid; if (verbose_mode) fmprintf(stderr, @@ -403,7 +416,7 @@ void verifydir(char *); struct passwd *pwd; uid_t userid; -int errs, remin, remout; +int errs, remin, remout, remin2, remout2; int Tflag, pflag, iamremote, iamrecursive, targetshouldbedirectory; #define CMDNEEDS 64 @@ -424,6 +437,8 @@ void usage(void); void source_sftp(int, char *, char *, struct sftp_conn *, char **); void sink_sftp(int, char *, const char *, struct sftp_conn *); +void throughlocal_sftp(struct sftp_conn *, struct sftp_conn *, + char *, char *, char **); int main(int argc, char **argv) @@ -943,22 +958,23 @@ brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp) } 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) { addargs(&args, "-s"); if (do_cmd(ssh_program, host, user, port, "sftp", - &remin, &remout) < 0) + reminp, remoutp, pidp) < 0) return NULL; } else { args.list = NULL; addargs(&args, "sftp-server"); if (do_cmd(sftp_direct, host, NULL, -1, "sftp", - &remin, &remout) < 0) + reminp, remoutp, pidp) < 0) return NULL; } - return do_init(remin, remout, 32768, 64, limit_kbps); + return do_init(*reminp, *remoutp, 32768, 64, limit_kbps); } void @@ -968,9 +984,9 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct) char *bp, *tuser, *thost, *targ; char *remote_path = NULL; int sport = -1, tport = -1; - struct sftp_conn *conn = NULL; + struct sftp_conn *conn = NULL, *conn2 = NULL; arglist alist; - int i, r; + int i, r, status; u_int j; memset(&alist, '\0', sizeof(alist)); @@ -1011,21 +1027,64 @@ toremote(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct) continue; } if (host && throughlocal) { /* extended remote to remote */ - /* XXX uses scp; need to support SFTP remote-remote */ - xasprintf(&bp, "%s -f %s%s", cmd, - *src == '-' ? "-- " : "", src); - if (do_cmd(ssh_program, host, suser, sport, bp, - &remin, &remout) < 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; + if (mode == MODE_SFTP) { + if (remin == -1) { + /* Connect to dest now */ + conn = do_sftp_connect(thost, tuser, + tport, sftp_direct, + &remin, &remout, &do_cmd_pid); + if (conn == NULL) { + fatal("Unable to open " + "destination connection"); + } + debug3_f("origin in %d out %d pid %ld", + remin, remout, (long)do_cmd_pid); + } + /* + * 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 */ /* * 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) { /* Connect to remote now */ conn = do_sftp_connect(thost, tuser, - tport, sftp_direct); + tport, sftp_direct, + &remin, &remout, &do_cmd_pid); if (conn == NULL) { fatal("Unable to open sftp " "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, *targ == '-' ? "-- " : "", targ); if (do_cmd(ssh_program, thost, tuser, tport, bp, - &remin, &remout) < 0) + &remin, &remout, &do_cmd_pid) < 0) exit(1); if (response() < 0) exit(1); @@ -1156,7 +1216,8 @@ tolocal(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct) } /* Remote to local. */ 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) { error("Couldn't make sftp connection " "to server"); @@ -1176,8 +1237,8 @@ tolocal(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct) /* SCP */ xasprintf(&bp, "%s -f %s%s", cmd, *src == '-' ? "-- " : "", src); - if (do_cmd(ssh_program, host, suser, sport, bp, &remin, - &remout) < 0) { + if (do_cmd(ssh_program, host, suser, sport, bp, + &remin, &remout, &do_cmd_pid) < 0) { free(bp); ++errs; continue; @@ -1808,6 +1869,94 @@ screwup: 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 response(void) { -- cgit v1.2.1