upstream: add support for the "corp-data" protocol extension to

allow server-side copies to be performed without having to go via the client.
Patch by Mike Frysinger, ok dtucker@

OpenBSD-Commit-ID: 00aa510940fedd66dab1843b58682de4eb7156d5
This commit is contained in:
djm@openbsd.org 2022-03-31 03:05:49 +00:00 committed by Damien Miller
parent 32dc1c29a4
commit 7988bfc4b7
2 changed files with 132 additions and 3 deletions

View File

@ -492,7 +492,7 @@ This request asks the server to call fsync(2) on an open file handle.
string "fsync@openssh.com" string "fsync@openssh.com"
string handle string handle
One receiving this request, a server will call fsync(handle_fd) and will On receiving this request, a server will call fsync(handle_fd) and will
respond with a SSH_FXP_STATUS message. respond with a SSH_FXP_STATUS message.
This extension is advertised in the SSH_FXP_VERSION hello with version This extension is advertised in the SSH_FXP_VERSION hello with version
@ -576,6 +576,43 @@ Its reply is the same format as that of SSH2_FXP_REALPATH.
This extension is advertised in the SSH_FXP_VERSION hello with version This extension is advertised in the SSH_FXP_VERSION hello with version
"1". "1".
4.10. sftp: Extension request "copy-data"
This request asks the server to copy data from one open file handle and
write it to a different open file handle. This avoids needing to transfer
the data across the network twice (a download followed by an upload).
byte SSH_FXP_EXTENDED
uint32 id
string "copy-data"
string read-from-handle
uint64 read-from-offset
uint64 read-data-length
string write-to-handle
uint64 write-to-offset
The server will copy read-data-length bytes starting from
read-from-offset from the read-from-handle and write them to
write-to-handle starting from write-to-offset, and then respond with a
SSH_FXP_STATUS message.
It's equivalent to issuing a series of SSH_FXP_READ requests on
read-from-handle and a series of requests of SSH_FXP_WRITE on
write-to-handle.
If read-from-handle and write-to-handle are the same, the server will
fail the request and respond with a SSH_FX_INVALID_PARAMETER message.
If read-data-length is 0, then the server will read data from the
read-from-handle until EOF is reached.
This extension is advertised in the SSH_FXP_VERSION hello with version
"1".
This request is identical to the "copy-data" request documented in:
https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-7
5. Miscellaneous changes 5. Miscellaneous changes
5.1 Public key format 5.1 Public key format
@ -612,4 +649,4 @@ master instance and later clients.
OpenSSH extends the usual agent protocol. These changes are documented OpenSSH extends the usual agent protocol. These changes are documented
in the PROTOCOL.agent file. in the PROTOCOL.agent file.
$OpenBSD: PROTOCOL,v 1.43 2021/12/19 22:15:42 djm Exp $ $OpenBSD: PROTOCOL,v 1.44 2022/03/31 03:05:49 djm Exp $

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sftp-server.c,v 1.139 2022/02/01 23:32:51 djm Exp $ */ /* $OpenBSD: sftp-server.c,v 1.140 2022/03/31 03:05:49 djm Exp $ */
/* /*
* Copyright (c) 2000-2004 Markus Friedl. All rights reserved. * Copyright (c) 2000-2004 Markus Friedl. All rights reserved.
* *
@ -44,6 +44,7 @@
#include <unistd.h> #include <unistd.h>
#include <stdarg.h> #include <stdarg.h>
#include "atomicio.h"
#include "xmalloc.h" #include "xmalloc.h"
#include "sshbuf.h" #include "sshbuf.h"
#include "ssherr.h" #include "ssherr.h"
@ -119,6 +120,7 @@ static void process_extended_fsync(u_int32_t id);
static void process_extended_lsetstat(u_int32_t id); static void process_extended_lsetstat(u_int32_t id);
static void process_extended_limits(u_int32_t id); static void process_extended_limits(u_int32_t id);
static void process_extended_expand(u_int32_t id); static void process_extended_expand(u_int32_t id);
static void process_extended_copy_data(u_int32_t id);
static void process_extended(u_int32_t id); static void process_extended(u_int32_t id);
struct sftp_handler { struct sftp_handler {
@ -164,6 +166,7 @@ static const struct sftp_handler extended_handlers[] = {
{ "limits", "limits@openssh.com", 0, process_extended_limits, 0 }, { "limits", "limits@openssh.com", 0, process_extended_limits, 0 },
{ "expand-path", "expand-path@openssh.com", 0, { "expand-path", "expand-path@openssh.com", 0,
process_extended_expand, 0 }, process_extended_expand, 0 },
{ "copy-data", "copy-data", 0, process_extended_copy_data, 1 },
{ NULL, NULL, 0, NULL, 0 } { NULL, NULL, 0, NULL, 0 }
}; };
@ -720,6 +723,7 @@ process_init(void)
compose_extension(msg, "lsetstat@openssh.com", "1"); compose_extension(msg, "lsetstat@openssh.com", "1");
compose_extension(msg, "limits@openssh.com", "1"); compose_extension(msg, "limits@openssh.com", "1");
compose_extension(msg, "expand-path@openssh.com", "1"); compose_extension(msg, "expand-path@openssh.com", "1");
compose_extension(msg, "copy-data", "1");
send_msg(msg); send_msg(msg);
sshbuf_free(msg); sshbuf_free(msg);
@ -1592,6 +1596,94 @@ process_extended_expand(u_int32_t id)
free(path); free(path);
} }
static void
process_extended_copy_data(u_int32_t id)
{
u_char buf[64*1024];
int read_handle, read_fd, write_handle, write_fd;
u_int64_t len, read_off, read_len, write_off;
int r, copy_until_eof, status = SSH2_FX_OP_UNSUPPORTED;
size_t ret;
if ((r = get_handle(iqueue, &read_handle)) != 0 ||
(r = sshbuf_get_u64(iqueue, &read_off)) != 0 ||
(r = sshbuf_get_u64(iqueue, &read_len)) != 0 ||
(r = get_handle(iqueue, &write_handle)) != 0 ||
(r = sshbuf_get_u64(iqueue, &write_off)) != 0)
fatal("%s: buffer error: %s", __func__, ssh_err(r));
debug("request %u: copy-data from \"%s\" (handle %d) off %llu len %llu "
"to \"%s\" (handle %d) off %llu",
id, handle_to_name(read_handle), read_handle,
(unsigned long long)read_off, (unsigned long long)read_len,
handle_to_name(write_handle), write_handle,
(unsigned long long)write_off);
/* For read length of 0, we read until EOF. */
if (read_len == 0) {
read_len = (u_int64_t)-1 - read_off;
copy_until_eof = 1;
} else
copy_until_eof = 0;
read_fd = handle_to_fd(read_handle);
write_fd = handle_to_fd(write_handle);
/* Disallow reading & writing to the same handle or same path or dirs */
if (read_handle == write_handle || read_fd < 0 || write_fd < 0 ||
!strcmp(handle_to_name(read_handle), handle_to_name(write_handle))) {
status = SSH2_FX_FAILURE;
goto out;
}
if (lseek(read_fd, read_off, SEEK_SET) < 0) {
status = errno_to_portable(errno);
error("%s: read_seek failed", __func__);
goto out;
}
if ((handle_to_flags(write_handle) & O_APPEND) == 0 &&
lseek(write_fd, write_off, SEEK_SET) < 0) {
status = errno_to_portable(errno);
error("%s: write_seek failed", __func__);
goto out;
}
/* Process the request in chunks. */
while (read_len > 0 || copy_until_eof) {
len = MINIMUM(sizeof(buf), read_len);
read_len -= len;
ret = atomicio(read, read_fd, buf, len);
if (ret == 0 && errno == EPIPE) {
status = copy_until_eof ? SSH2_FX_OK : SSH2_FX_EOF;
break;
} else if (ret == 0) {
status = errno_to_portable(errno);
error("%s: read failed: %s", __func__, strerror(errno));
break;
}
len = ret;
handle_update_read(read_handle, len);
ret = atomicio(vwrite, write_fd, buf, len);
if (ret != len) {
status = errno_to_portable(errno);
error("%s: write failed: %llu != %llu: %s", __func__,
(unsigned long long)ret, (unsigned long long)len,
strerror(errno));
break;
}
handle_update_write(write_handle, len);
}
if (read_len == 0)
status = SSH2_FX_OK;
out:
send_status(id, status);
}
static void static void
process_extended(u_int32_t id) process_extended(u_int32_t id)
{ {