upstream: 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@

OpenBSD-Commit-ID: 1bb1b950af0d49f0d5425b1f267e197aa1b57444
This commit is contained in:
djm@openbsd.org 2022-03-31 03:07:03 +00:00 committed by Damien Miller
parent 7988bfc4b7
commit 3fa539c3ff
4 changed files with 155 additions and 6 deletions

View File

@ -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 <djm@openbsd.org> * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
* *
@ -103,6 +103,7 @@ struct sftp_conn {
#define SFTP_EXT_LSETSTAT 0x00000020 #define SFTP_EXT_LSETSTAT 0x00000020
#define SFTP_EXT_LIMITS 0x00000040 #define SFTP_EXT_LIMITS 0x00000040
#define SFTP_EXT_PATH_EXPAND 0x00000080 #define SFTP_EXT_PATH_EXPAND 0x00000080
#define SFTP_EXT_COPY_DATA 0x00000100
u_int exts; u_int exts;
u_int64_t limit_kbps; u_int64_t limit_kbps;
struct bwlimit bwlimit_in, bwlimit_out; struct bwlimit bwlimit_in, bwlimit_out;
@ -534,6 +535,10 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
strcmp((char *)value, "1") == 0) { strcmp((char *)value, "1") == 0) {
ret->exts |= SFTP_EXT_PATH_EXPAND; ret->exts |= SFTP_EXT_PATH_EXPAND;
known = 1; known = 1;
} else if (strcmp(name, "copy-data") == 0 &&
strcmp((char *)value, "1") == 0) {
ret->exts |= SFTP_EXT_COPY_DATA;
known = 1;
} }
if (known) { if (known) {
debug2("Server supports extension \"%s\" revision %s", debug2("Server supports extension \"%s\" revision %s",
@ -1078,6 +1083,121 @@ do_expand_path(struct sftp_conn *conn, const char *path)
return do_realpath_expand(conn, path, 1); 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 int
do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
int force_legacy) int force_legacy)

View File

@ -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 <djm@openbsd.org> * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
@ -125,6 +125,9 @@ int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int);
/* Rename 'oldpath' to 'newpath' */ /* Rename 'oldpath' to 'newpath' */
int do_rename(struct sftp_conn *, const char *, const char *, int); 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' */ /* Link 'oldpath' to 'newpath' */
int do_hardlink(struct sftp_conn *, const char *, const char *); int do_hardlink(struct sftp_conn *, const char *, const char *);

18
sftp.1
View File

@ -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. .\" 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 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.Dd $Mdocdate: July 2 2021 $ .Dd $Mdocdate: March 31 2022 $
.Dt SFTP 1 .Dt SFTP 1
.Os .Os
.Sh NAME .Sh NAME
@ -144,7 +144,7 @@ will abort if any of the following
commands fail: commands fail:
.Ic get , put , reget , reput , rename , ln , .Ic get , put , reget , reput , rename , ln ,
.Ic rm , mkdir , chdir , ls , .Ic rm , mkdir , chdir , ls ,
.Ic lchdir , chmod , chown , .Ic lchdir , copy , cp , chmod , chown ,
.Ic chgrp , lpwd , df , symlink , .Ic chgrp , lpwd , df , symlink ,
and and
.Ic lmkdir . .Ic lmkdir .
@ -400,6 +400,18 @@ If the
flag is specified, then symlinks will not be followed. flag is specified, then symlinks will not be followed.
Note that this is only supported by servers that implement Note that this is only supported by servers that implement
the "lsetstat@openssh.com" extension. 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 .It Xo Ic df
.Op Fl hi .Op Fl hi
.Op Ar path .Op Ar path

16
sftp.c
View File

@ -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 <djm@openbsd.org> * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
* *
@ -137,6 +137,7 @@ enum sftp_command {
I_CHGRP, I_CHGRP,
I_CHMOD, I_CHMOD,
I_CHOWN, I_CHOWN,
I_COPY,
I_DF, I_DF,
I_GET, I_GET,
I_HELP, I_HELP,
@ -180,6 +181,8 @@ static const struct CMD cmds[] = {
{ "chgrp", I_CHGRP, REMOTE }, { "chgrp", I_CHGRP, REMOTE },
{ "chmod", I_CHMOD, REMOTE }, { "chmod", I_CHMOD, REMOTE },
{ "chown", I_CHOWN, REMOTE }, { "chown", I_CHOWN, REMOTE },
{ "copy", I_COPY, REMOTE },
{ "cp", I_COPY, REMOTE },
{ "df", I_DF, REMOTE }, { "df", I_DF, REMOTE },
{ "dir", I_LS, REMOTE }, { "dir", I_LS, REMOTE },
{ "exit", I_QUIT, NOARGS }, { "exit", I_QUIT, NOARGS },
@ -286,6 +289,8 @@ help(void)
"chgrp [-h] grp path Change group of file 'path' to 'grp'\n" "chgrp [-h] grp path Change group of file 'path' to 'grp'\n"
"chmod [-h] mode path Change permissions of file 'path' to 'mode'\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" "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" "df [-hi] [path] Display statistics for current directory or\n"
" filesystem containing 'path'\n" " filesystem containing 'path'\n"
"exit Quit sftp\n" "exit Quit sftp\n"
@ -1369,6 +1374,10 @@ parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag,
if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
return -1; return -1;
goto parse_two_paths; 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: case I_RENAME:
if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1) if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
return -1; return -1;
@ -1536,6 +1545,11 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
err = process_put(conn, path1, path2, *pwd, pflag, err = process_put(conn, path1, path2, *pwd, pflag,
rflag, aflag, fflag); rflag, aflag, fflag);
break; break;
case I_COPY:
path1 = make_absolute(path1, *pwd);
path2 = make_absolute(path2, *pwd);
err = do_copy(conn, path1, path2);
break;
case I_RENAME: case I_RENAME:
path1 = make_absolute(path1, *pwd); path1 = make_absolute(path1, *pwd);
path2 = make_absolute(path2, *pwd); path2 = make_absolute(path2, *pwd);