diff -up openssh-8.7p1/scp.c.sftpdirs openssh-8.7p1/scp.c --- openssh-8.7p1/scp.c.sftpdirs 2022-02-02 14:11:12.553447509 +0100 +++ openssh-8.7p1/scp.c 2022-02-02 14:12:56.081316414 +0100 @@ -130,6 +130,7 @@ #include "misc.h" #include "progressmeter.h" #include "utf8.h" +#include "sftp.h" #include "sftp-common.h" #include "sftp-client.h" @@ -660,7 +661,7 @@ main(int argc, char **argv) * Finally check the exit status of the ssh process, if one was forked * and no error has occurred yet */ - if (do_cmd_pid != -1 && errs == 0) { + if (do_cmd_pid != -1 && (mode == MODE_SFTP || errs == 0)) { if (remin != -1) (void) close(remin); if (remout != -1) @@ -1264,13 +1265,18 @@ tolocal(int argc, char **argv, enum scp_ static char * prepare_remote_path(struct sftp_conn *conn, const char *path) { + size_t nslash; + /* Handle ~ prefixed paths */ - if (*path != '~') - return xstrdup(path); if (*path == '\0' || strcmp(path, "~") == 0) return xstrdup("."); - if (strncmp(path, "~/", 2) == 0) - return xstrdup(path + 2); + if (*path != '~') + return xstrdup(path); + if (strncmp(path, "~/", 2) == 0) { + if ((nslash = strspn(path + 2, "/")) == strlen(path + 2)) + return xstrdup("."); + return xstrdup(path + 2 + nslash); + } if (can_expand_path(conn)) return do_expand_path(conn, path); /* No protocol extension */ @@ -1282,10 +1288,16 @@ void source_sftp(int argc, char *src, char *targ, struct sftp_conn *conn) { char *target = NULL, *filename = NULL, *abs_dst = NULL; - int target_is_dir; - + int src_is_dir, target_is_dir; + Attrib a; + struct stat st; + + memset(&a, '\0', sizeof(a)); + if (stat(src, &st) != 0) + fatal("stat local \"%s\": %s", src, strerror(errno)); + src_is_dir = S_ISDIR(st.st_mode); if ((filename = basename(src)) == NULL) - fatal("basename %s: %s", src, strerror(errno)); + fatal("basename \"%s\": %s", src, strerror(errno)); /* * No need to glob here - the local shell already took care of @@ -1295,8 +1307,12 @@ source_sftp(int argc, char *src, char *t cleanup_exit(255); target_is_dir = remote_is_dir(conn, target); if (targetshouldbedirectory && !target_is_dir) { - fatal("Target is not a directory, but more files selected " - "for upload"); + debug("target directory \"%s\" does not exist", target); + a.flags = SSH2_FILEXFER_ATTR_PERMISSIONS; + a.perm = st.st_mode | 0700; /* ensure writable */ + if (do_mkdir(conn, target, &a, 1) != 0) + cleanup_exit(255); /* error already logged */ + target_is_dir = 1; } if (target_is_dir) abs_dst = path_append(target, filename); @@ -1306,14 +1322,17 @@ source_sftp(int argc, char *src, char *t } debug3_f("copying local %s to remote %s", src, abs_dst); - if (local_is_dir(src) && iamrecursive) { + if (src_is_dir && iamrecursive) { if (upload_dir(conn, src, abs_dst, pflag, SFTP_PROGRESS_ONLY, 0, 0, 1) != 0) { - fatal("failed to upload directory %s to %s", + error("failed to upload directory %s to %s", src, abs_dst); + errs = 1; } - } else if (do_upload(conn, src, abs_dst, pflag, 0, 0) != 0) - fatal("failed to upload file %s to %s", src, abs_dst); + } else if (do_upload(conn, src, abs_dst, pflag, 0, 0) != 0) { + error("failed to upload file %s to %s", src, abs_dst); + errs = 1; + } free(abs_dst); free(target); @@ -1487,14 +1506,15 @@ sink_sftp(int argc, char *dst, const cha char *abs_dst = NULL; glob_t g; char *filename, *tmp = NULL; - int i, r, err = 0; + int i, r, err = 0, dst_is_dir; + struct stat st; memset(&g, 0, sizeof(g)); + /* * Here, we need remote glob as SFTP can not depend on remote shell * expansions */ - if ((abs_src = prepare_remote_path(conn, src)) == NULL) { err = -1; goto out; @@ -1510,11 +1530,24 @@ sink_sftp(int argc, char *dst, const cha goto out; } - if (g.gl_matchc > 1 && !local_is_dir(dst)) { - error("Multiple files match pattern, but destination " - "\"%s\" is not a directory", dst); - err = -1; - goto out; + if ((r = stat(dst, &st)) != 0) + debug2_f("stat local \"%s\": %s", dst, strerror(errno)); + dst_is_dir = r == 0 && S_ISDIR(st.st_mode); + + if (g.gl_matchc > 1 && !dst_is_dir) { + if (r == 0) { + error("Multiple files match pattern, but destination " + "\"%s\" is not a directory", dst); + err = -1; + goto out; + } + debug2_f("creating destination \"%s\"", dst); + if (mkdir(dst, 0777) != 0) { + error("local mkdir \"%s\": %s", dst, strerror(errno)); + err = -1; + goto out; + } + dst_is_dir = 1; } for (i = 0; g.gl_pathv[i] && !interrupted; i++) { @@ -1525,7 +1558,7 @@ sink_sftp(int argc, char *dst, const cha goto out; } - if (local_is_dir(dst)) + if (dst_is_dir) abs_dst = path_append(dst, filename); else abs_dst = xstrdup(dst); @@ -1551,7 +1584,8 @@ out: free(tmp); globfree(&g); if (err == -1) { - fatal("Failed to download file '%s'", src); + error("Failed to download '%s'", src); + errs = 1; } }