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:
parent
32dc1c29a4
commit
7988bfc4b7
41
PROTOCOL
41
PROTOCOL
|
@ -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 $
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue