From: djm Date: Thu, 31 Mar 2022 03:07:03 +0000 (+0000) Subject: add a sftp client "cp" command that supports server-side copying X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=cad943075198de9466fc7b44d0488b9ad61f47c1;p=openbsd add a sftp client "cp" command that supports server-side copying of files. Useful for this task and for testing the copy-data extension. Patch from Mike Frysinger; ok dtucker@ --- diff --git a/usr.bin/ssh/sftp-client.c b/usr.bin/ssh/sftp-client.c index 0666a9fdd1b..403a2a89eff 100644 --- a/usr.bin/ssh/sftp-client.c +++ b/usr.bin/ssh/sftp-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.c,v 1.161 2022/01/17 21:41:04 djm Exp $ */ +/* $OpenBSD: sftp-client.c,v 1.162 2022/03/31 03:07:03 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -83,6 +83,7 @@ struct sftp_conn { #define SFTP_EXT_LSETSTAT 0x00000020 #define SFTP_EXT_LIMITS 0x00000040 #define SFTP_EXT_PATH_EXPAND 0x00000080 +#define SFTP_EXT_COPY_DATA 0x00000100 u_int exts; u_int64_t limit_kbps; struct bwlimit bwlimit_in, bwlimit_out; @@ -514,6 +515,10 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_PATH_EXPAND; known = 1; + } else if (strcmp(name, "copy-data") == 0 && + strcmp((char *)value, "1") == 0) { + ret->exts |= SFTP_EXT_COPY_DATA; + known = 1; } if (known) { debug2("Server supports extension \"%s\" revision %s", @@ -1058,6 +1063,121 @@ do_expand_path(struct sftp_conn *conn, const char *path) return do_realpath_expand(conn, path, 1); } +int +do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath) +{ + Attrib junk, *a; + struct sshbuf *msg; + u_char *old_handle, *new_handle; + u_int mode, status, id; + size_t old_handle_len, new_handle_len; + int r; + + /* Return if the extension is not supported */ + if ((conn->exts & SFTP_EXT_COPY_DATA) == 0) { + error("Server does not support copy-data extension"); + return -1; + } + + /* Make sure the file exists, and we can copy its perms */ + if ((a = do_stat(conn, oldpath, 0)) == NULL) + return -1; + + /* Do not preserve set[ug]id here, as we do not preserve ownership */ + if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { + mode = a->perm & 0777; + + if (!S_ISREG(a->perm)) { + error("Cannot copy non-regular file: %s", oldpath); + return -1; + } + } else { + /* NB: The user's umask will apply to this */ + mode = 0666; + } + + /* Set up the new perms for the new file */ + attrib_clear(a); + a->perm = mode; + a->flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; + + if ((msg = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + + attrib_clear(&junk); /* Send empty attributes */ + + /* Open the old file for reading */ + id = conn->msg_id++; + if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || + (r = sshbuf_put_u32(msg, id)) != 0 || + (r = sshbuf_put_cstring(msg, oldpath)) != 0 || + (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 || + (r = encode_attrib(msg, &junk)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + send_msg(conn, msg); + debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, oldpath); + + sshbuf_reset(msg); + + old_handle = get_handle(conn, id, &old_handle_len, + "remote open(\"%s\")", oldpath); + if (old_handle == NULL) { + sshbuf_free(msg); + return -1; + } + + /* Open the new file for writing */ + id = conn->msg_id++; + if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || + (r = sshbuf_put_u32(msg, id)) != 0 || + (r = sshbuf_put_cstring(msg, newpath)) != 0 || + (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| + SSH2_FXF_TRUNC)) != 0 || + (r = encode_attrib(msg, a)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + send_msg(conn, msg); + debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath); + + sshbuf_reset(msg); + + new_handle = get_handle(conn, id, &new_handle_len, + "remote open(\"%s\")", newpath); + if (new_handle == NULL) { + sshbuf_free(msg); + free(old_handle); + return -1; + } + + /* Copy the file data */ + id = conn->msg_id++; + if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || + (r = sshbuf_put_u32(msg, id)) != 0 || + (r = sshbuf_put_cstring(msg, "copy-data")) != 0 || + (r = sshbuf_put_string(msg, old_handle, old_handle_len)) != 0 || + (r = sshbuf_put_u64(msg, 0)) != 0 || + (r = sshbuf_put_u64(msg, 0)) != 0 || + (r = sshbuf_put_string(msg, new_handle, new_handle_len)) != 0 || + (r = sshbuf_put_u64(msg, 0)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + send_msg(conn, msg); + debug3("Sent message copy-data \"%s\" 0 0 -> \"%s\" 0", + oldpath, newpath); + + status = get_status(conn, id); + if (status != SSH2_FX_OK) + error("Couldn't copy file \"%s\" to \"%s\": %s", oldpath, + newpath, fx2txt(status)); + + /* Clean up everything */ + sshbuf_free(msg); + do_close(conn, old_handle, old_handle_len); + do_close(conn, new_handle, new_handle_len); + free(old_handle); + free(new_handle); + + return status == SSH2_FX_OK ? 0 : -1; +} + int do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, int force_legacy) diff --git a/usr.bin/ssh/sftp-client.h b/usr.bin/ssh/sftp-client.h index 0d5356c6700..6c628210221 100644 --- a/usr.bin/ssh/sftp-client.h +++ b/usr.bin/ssh/sftp-client.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.h,v 1.35 2022/01/01 01:55:30 jsg Exp $ */ +/* $OpenBSD: sftp-client.h,v 1.36 2022/03/31 03:07:03 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller @@ -119,6 +119,9 @@ int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int); /* Rename 'oldpath' to 'newpath' */ int do_rename(struct sftp_conn *, const char *, const char *, int); +/* Copy 'oldpath' to 'newpath' */ +int do_copy(struct sftp_conn *, const char *, const char *); + /* Link 'oldpath' to 'newpath' */ int do_hardlink(struct sftp_conn *, const char *, const char *); diff --git a/usr.bin/ssh/sftp.1 b/usr.bin/ssh/sftp.1 index 7eebeeacbf3..766adceea40 100644 --- a/usr.bin/ssh/sftp.1 +++ b/usr.bin/ssh/sftp.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: sftp.1,v 1.138 2021/07/02 05:11:21 dtucker Exp $ +.\" $OpenBSD: sftp.1,v 1.139 2022/03/31 03:07:03 djm Exp $ .\" .\" Copyright (c) 2001 Damien Miller. All rights reserved. .\" @@ -22,7 +22,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: July 2 2021 $ +.Dd $Mdocdate: March 31 2022 $ .Dt SFTP 1 .Os .Sh NAME @@ -144,7 +144,7 @@ will abort if any of the following commands fail: .Ic get , put , reget , reput , rename , ln , .Ic rm , mkdir , chdir , ls , -.Ic lchdir , chmod , chown , +.Ic lchdir , copy , cp , chmod , chown , .Ic chgrp , lpwd , df , symlink , and .Ic lmkdir . @@ -400,6 +400,18 @@ If the flag is specified, then symlinks will not be followed. Note that this is only supported by servers that implement the "lsetstat@openssh.com" extension. +.It Ic copy Ar oldpath Ar newpath +Copy remote file from +.Ar oldpath +to +.Ar newpath . +.Pp +Note that this is only supported by servers that implement the "copy-data" +extension. +.It Ic cp Ar oldpath Ar newpath +Alias to +.Ic copy +command. .It Xo Ic df .Op Fl hi .Op Ar path diff --git a/usr.bin/ssh/sftp.c b/usr.bin/ssh/sftp.c index bc4ba5e8b32..ad66df93d0e 100644 --- a/usr.bin/ssh/sftp.c +++ b/usr.bin/ssh/sftp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp.c,v 1.213 2022/03/18 02:50:21 djm Exp $ */ +/* $OpenBSD: sftp.c,v 1.214 2022/03/31 03:07:03 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -116,6 +116,7 @@ enum sftp_command { I_CHGRP, I_CHMOD, I_CHOWN, + I_COPY, I_DF, I_GET, I_HELP, @@ -159,6 +160,8 @@ static const struct CMD cmds[] = { { "chgrp", I_CHGRP, REMOTE }, { "chmod", I_CHMOD, REMOTE }, { "chown", I_CHOWN, REMOTE }, + { "copy", I_COPY, REMOTE }, + { "cp", I_COPY, REMOTE }, { "df", I_DF, REMOTE }, { "dir", I_LS, REMOTE }, { "exit", I_QUIT, NOARGS }, @@ -265,6 +268,8 @@ help(void) "chgrp [-h] grp path Change group of file 'path' to 'grp'\n" "chmod [-h] mode path Change permissions of file 'path' to 'mode'\n" "chown [-h] own path Change owner of file 'path' to 'own'\n" + "copy oldpath newpath Copy remote file\n" + "cp oldpath newpath Copy remote file\n" "df [-hi] [path] Display statistics for current directory or\n" " filesystem containing 'path'\n" "exit Quit sftp\n" @@ -1342,6 +1347,10 @@ parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag, if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) return -1; goto parse_two_paths; + case I_COPY: + if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) + return -1; + goto parse_two_paths; case I_RENAME: if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1) return -1; @@ -1509,6 +1518,11 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, err = process_put(conn, path1, path2, *pwd, pflag, rflag, aflag, fflag); break; + case I_COPY: + path1 = make_absolute(path1, *pwd); + path2 = make_absolute(path2, *pwd); + err = do_copy(conn, path1, path2); + break; case I_RENAME: path1 = make_absolute(path1, *pwd); path2 = make_absolute(path2, *pwd);