b7aa600c41
I have worked hard to reduce diffs against the vendor branch. One notable change in that respect is that we no longer prefer DSA over RSA - the reasons for doing so went away years ago. This may cause some surprises, as ssh will warn about unknown host keys even for hosts whose keys haven't changed. MFC after: 6 weeks
1316 lines
30 KiB
C
1316 lines
30 KiB
C
/* $OpenBSD: sftp-client.c,v 1.86 2008/06/26 06:10:09 djm Exp $ */
|
|
/*
|
|
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/* XXX: memleaks */
|
|
/* XXX: signed vs unsigned */
|
|
/* XXX: remove all logging, only return status codes */
|
|
/* XXX: copy between two remote sites */
|
|
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#ifdef HAVE_SYS_STATVFS_H
|
|
#include <sys/statvfs.h>
|
|
#endif
|
|
#include "openbsd-compat/sys-queue.h"
|
|
#ifdef HAVE_SYS_STAT_H
|
|
# include <sys/stat.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
#endif
|
|
#include <sys/uio.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "xmalloc.h"
|
|
#include "buffer.h"
|
|
#include "log.h"
|
|
#include "atomicio.h"
|
|
#include "progressmeter.h"
|
|
#include "misc.h"
|
|
|
|
#include "sftp.h"
|
|
#include "sftp-common.h"
|
|
#include "sftp-client.h"
|
|
|
|
extern volatile sig_atomic_t interrupted;
|
|
extern int showprogress;
|
|
|
|
/* Minimum amount of data to read at a time */
|
|
#define MIN_READ_SIZE 512
|
|
|
|
struct sftp_conn {
|
|
int fd_in;
|
|
int fd_out;
|
|
u_int transfer_buflen;
|
|
u_int num_requests;
|
|
u_int version;
|
|
u_int msg_id;
|
|
#define SFTP_EXT_POSIX_RENAME 0x00000001
|
|
#define SFTP_EXT_STATVFS 0x00000002
|
|
#define SFTP_EXT_FSTATVFS 0x00000004
|
|
u_int exts;
|
|
};
|
|
|
|
static void
|
|
send_msg(int fd, Buffer *m)
|
|
{
|
|
u_char mlen[4];
|
|
struct iovec iov[2];
|
|
|
|
if (buffer_len(m) > SFTP_MAX_MSG_LENGTH)
|
|
fatal("Outbound message too long %u", buffer_len(m));
|
|
|
|
/* Send length first */
|
|
put_u32(mlen, buffer_len(m));
|
|
iov[0].iov_base = mlen;
|
|
iov[0].iov_len = sizeof(mlen);
|
|
iov[1].iov_base = buffer_ptr(m);
|
|
iov[1].iov_len = buffer_len(m);
|
|
|
|
if (atomiciov(writev, fd, iov, 2) != buffer_len(m) + sizeof(mlen))
|
|
fatal("Couldn't send packet: %s", strerror(errno));
|
|
|
|
buffer_clear(m);
|
|
}
|
|
|
|
static void
|
|
get_msg(int fd, Buffer *m)
|
|
{
|
|
u_int msg_len;
|
|
|
|
buffer_append_space(m, 4);
|
|
if (atomicio(read, fd, buffer_ptr(m), 4) != 4) {
|
|
if (errno == EPIPE)
|
|
fatal("Connection closed");
|
|
else
|
|
fatal("Couldn't read packet: %s", strerror(errno));
|
|
}
|
|
|
|
msg_len = buffer_get_int(m);
|
|
if (msg_len > SFTP_MAX_MSG_LENGTH)
|
|
fatal("Received message too long %u", msg_len);
|
|
|
|
buffer_append_space(m, msg_len);
|
|
if (atomicio(read, fd, buffer_ptr(m), msg_len) != msg_len) {
|
|
if (errno == EPIPE)
|
|
fatal("Connection closed");
|
|
else
|
|
fatal("Read packet: %s", strerror(errno));
|
|
}
|
|
}
|
|
|
|
static void
|
|
send_string_request(int fd, u_int id, u_int code, char *s,
|
|
u_int len)
|
|
{
|
|
Buffer msg;
|
|
|
|
buffer_init(&msg);
|
|
buffer_put_char(&msg, code);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_string(&msg, s, len);
|
|
send_msg(fd, &msg);
|
|
debug3("Sent message fd %d T:%u I:%u", fd, code, id);
|
|
buffer_free(&msg);
|
|
}
|
|
|
|
static void
|
|
send_string_attrs_request(int fd, u_int id, u_int code, char *s,
|
|
u_int len, Attrib *a)
|
|
{
|
|
Buffer msg;
|
|
|
|
buffer_init(&msg);
|
|
buffer_put_char(&msg, code);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_string(&msg, s, len);
|
|
encode_attrib(&msg, a);
|
|
send_msg(fd, &msg);
|
|
debug3("Sent message fd %d T:%u I:%u", fd, code, id);
|
|
buffer_free(&msg);
|
|
}
|
|
|
|
static u_int
|
|
get_status(int fd, u_int expected_id)
|
|
{
|
|
Buffer msg;
|
|
u_int type, id, status;
|
|
|
|
buffer_init(&msg);
|
|
get_msg(fd, &msg);
|
|
type = buffer_get_char(&msg);
|
|
id = buffer_get_int(&msg);
|
|
|
|
if (id != expected_id)
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
|
if (type != SSH2_FXP_STATUS)
|
|
fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
|
|
SSH2_FXP_STATUS, type);
|
|
|
|
status = buffer_get_int(&msg);
|
|
buffer_free(&msg);
|
|
|
|
debug3("SSH2_FXP_STATUS %u", status);
|
|
|
|
return(status);
|
|
}
|
|
|
|
static char *
|
|
get_handle(int fd, u_int expected_id, u_int *len)
|
|
{
|
|
Buffer msg;
|
|
u_int type, id;
|
|
char *handle;
|
|
|
|
buffer_init(&msg);
|
|
get_msg(fd, &msg);
|
|
type = buffer_get_char(&msg);
|
|
id = buffer_get_int(&msg);
|
|
|
|
if (id != expected_id)
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
|
if (type == SSH2_FXP_STATUS) {
|
|
int status = buffer_get_int(&msg);
|
|
|
|
error("Couldn't get handle: %s", fx2txt(status));
|
|
buffer_free(&msg);
|
|
return(NULL);
|
|
} else if (type != SSH2_FXP_HANDLE)
|
|
fatal("Expected SSH2_FXP_HANDLE(%u) packet, got %u",
|
|
SSH2_FXP_HANDLE, type);
|
|
|
|
handle = buffer_get_string(&msg, len);
|
|
buffer_free(&msg);
|
|
|
|
return(handle);
|
|
}
|
|
|
|
static Attrib *
|
|
get_decode_stat(int fd, u_int expected_id, int quiet)
|
|
{
|
|
Buffer msg;
|
|
u_int type, id;
|
|
Attrib *a;
|
|
|
|
buffer_init(&msg);
|
|
get_msg(fd, &msg);
|
|
|
|
type = buffer_get_char(&msg);
|
|
id = buffer_get_int(&msg);
|
|
|
|
debug3("Received stat reply T:%u I:%u", type, id);
|
|
if (id != expected_id)
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
|
if (type == SSH2_FXP_STATUS) {
|
|
int status = buffer_get_int(&msg);
|
|
|
|
if (quiet)
|
|
debug("Couldn't stat remote file: %s", fx2txt(status));
|
|
else
|
|
error("Couldn't stat remote file: %s", fx2txt(status));
|
|
buffer_free(&msg);
|
|
return(NULL);
|
|
} else if (type != SSH2_FXP_ATTRS) {
|
|
fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
|
|
SSH2_FXP_ATTRS, type);
|
|
}
|
|
a = decode_attrib(&msg);
|
|
buffer_free(&msg);
|
|
|
|
return(a);
|
|
}
|
|
|
|
static int
|
|
get_decode_statvfs(int fd, struct sftp_statvfs *st, u_int expected_id,
|
|
int quiet)
|
|
{
|
|
Buffer msg;
|
|
u_int type, id, flag;
|
|
|
|
buffer_init(&msg);
|
|
get_msg(fd, &msg);
|
|
|
|
type = buffer_get_char(&msg);
|
|
id = buffer_get_int(&msg);
|
|
|
|
debug3("Received statvfs reply T:%u I:%u", type, id);
|
|
if (id != expected_id)
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
|
if (type == SSH2_FXP_STATUS) {
|
|
int status = buffer_get_int(&msg);
|
|
|
|
if (quiet)
|
|
debug("Couldn't statvfs: %s", fx2txt(status));
|
|
else
|
|
error("Couldn't statvfs: %s", fx2txt(status));
|
|
buffer_free(&msg);
|
|
return -1;
|
|
} else if (type != SSH2_FXP_EXTENDED_REPLY) {
|
|
fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
|
|
SSH2_FXP_EXTENDED_REPLY, type);
|
|
}
|
|
|
|
bzero(st, sizeof(*st));
|
|
st->f_bsize = buffer_get_int64(&msg);
|
|
st->f_frsize = buffer_get_int64(&msg);
|
|
st->f_blocks = buffer_get_int64(&msg);
|
|
st->f_bfree = buffer_get_int64(&msg);
|
|
st->f_bavail = buffer_get_int64(&msg);
|
|
st->f_files = buffer_get_int64(&msg);
|
|
st->f_ffree = buffer_get_int64(&msg);
|
|
st->f_favail = buffer_get_int64(&msg);
|
|
st->f_fsid = buffer_get_int64(&msg);
|
|
flag = buffer_get_int64(&msg);
|
|
st->f_namemax = buffer_get_int64(&msg);
|
|
|
|
st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
|
|
st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
|
|
|
|
buffer_free(&msg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct sftp_conn *
|
|
do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests)
|
|
{
|
|
u_int type, exts = 0;
|
|
int version;
|
|
Buffer msg;
|
|
struct sftp_conn *ret;
|
|
|
|
buffer_init(&msg);
|
|
buffer_put_char(&msg, SSH2_FXP_INIT);
|
|
buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
|
|
send_msg(fd_out, &msg);
|
|
|
|
buffer_clear(&msg);
|
|
|
|
get_msg(fd_in, &msg);
|
|
|
|
/* Expecting a VERSION reply */
|
|
if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) {
|
|
error("Invalid packet back from SSH2_FXP_INIT (type %u)",
|
|
type);
|
|
buffer_free(&msg);
|
|
return(NULL);
|
|
}
|
|
version = buffer_get_int(&msg);
|
|
|
|
debug2("Remote version: %d", version);
|
|
|
|
/* Check for extensions */
|
|
while (buffer_len(&msg) > 0) {
|
|
char *name = buffer_get_string(&msg, NULL);
|
|
char *value = buffer_get_string(&msg, NULL);
|
|
int known = 0;
|
|
|
|
if (strcmp(name, "posix-rename@openssh.com") == 0 &&
|
|
strcmp(value, "1") == 0) {
|
|
exts |= SFTP_EXT_POSIX_RENAME;
|
|
known = 1;
|
|
} else if (strcmp(name, "statvfs@openssh.com") == 0 &&
|
|
strcmp(value, "2") == 0) {
|
|
exts |= SFTP_EXT_STATVFS;
|
|
known = 1;
|
|
} if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
|
|
strcmp(value, "2") == 0) {
|
|
exts |= SFTP_EXT_FSTATVFS;
|
|
known = 1;
|
|
}
|
|
if (known) {
|
|
debug2("Server supports extension \"%s\" revision %s",
|
|
name, value);
|
|
} else {
|
|
debug2("Unrecognised server extension \"%s\"", name);
|
|
}
|
|
xfree(name);
|
|
xfree(value);
|
|
}
|
|
|
|
buffer_free(&msg);
|
|
|
|
ret = xmalloc(sizeof(*ret));
|
|
ret->fd_in = fd_in;
|
|
ret->fd_out = fd_out;
|
|
ret->transfer_buflen = transfer_buflen;
|
|
ret->num_requests = num_requests;
|
|
ret->version = version;
|
|
ret->msg_id = 1;
|
|
ret->exts = exts;
|
|
|
|
/* Some filexfer v.0 servers don't support large packets */
|
|
if (version == 0)
|
|
ret->transfer_buflen = MIN(ret->transfer_buflen, 20480);
|
|
|
|
return(ret);
|
|
}
|
|
|
|
u_int
|
|
sftp_proto_version(struct sftp_conn *conn)
|
|
{
|
|
return(conn->version);
|
|
}
|
|
|
|
int
|
|
do_close(struct sftp_conn *conn, char *handle, u_int handle_len)
|
|
{
|
|
u_int id, status;
|
|
Buffer msg;
|
|
|
|
buffer_init(&msg);
|
|
|
|
id = conn->msg_id++;
|
|
buffer_put_char(&msg, SSH2_FXP_CLOSE);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_string(&msg, handle, handle_len);
|
|
send_msg(conn->fd_out, &msg);
|
|
debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
|
|
|
|
status = get_status(conn->fd_in, id);
|
|
if (status != SSH2_FX_OK)
|
|
error("Couldn't close file: %s", fx2txt(status));
|
|
|
|
buffer_free(&msg);
|
|
|
|
return(status);
|
|
}
|
|
|
|
|
|
static int
|
|
do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
|
|
SFTP_DIRENT ***dir)
|
|
{
|
|
Buffer msg;
|
|
u_int count, type, id, handle_len, i, expected_id, ents = 0;
|
|
char *handle;
|
|
|
|
id = conn->msg_id++;
|
|
|
|
buffer_init(&msg);
|
|
buffer_put_char(&msg, SSH2_FXP_OPENDIR);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_cstring(&msg, path);
|
|
send_msg(conn->fd_out, &msg);
|
|
|
|
buffer_clear(&msg);
|
|
|
|
handle = get_handle(conn->fd_in, id, &handle_len);
|
|
if (handle == NULL)
|
|
return(-1);
|
|
|
|
if (dir) {
|
|
ents = 0;
|
|
*dir = xmalloc(sizeof(**dir));
|
|
(*dir)[0] = NULL;
|
|
}
|
|
|
|
for (; !interrupted;) {
|
|
id = expected_id = conn->msg_id++;
|
|
|
|
debug3("Sending SSH2_FXP_READDIR I:%u", id);
|
|
|
|
buffer_clear(&msg);
|
|
buffer_put_char(&msg, SSH2_FXP_READDIR);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_string(&msg, handle, handle_len);
|
|
send_msg(conn->fd_out, &msg);
|
|
|
|
buffer_clear(&msg);
|
|
|
|
get_msg(conn->fd_in, &msg);
|
|
|
|
type = buffer_get_char(&msg);
|
|
id = buffer_get_int(&msg);
|
|
|
|
debug3("Received reply T:%u I:%u", type, id);
|
|
|
|
if (id != expected_id)
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
|
|
|
if (type == SSH2_FXP_STATUS) {
|
|
int status = buffer_get_int(&msg);
|
|
|
|
debug3("Received SSH2_FXP_STATUS %d", status);
|
|
|
|
if (status == SSH2_FX_EOF) {
|
|
break;
|
|
} else {
|
|
error("Couldn't read directory: %s",
|
|
fx2txt(status));
|
|
do_close(conn, handle, handle_len);
|
|
xfree(handle);
|
|
return(status);
|
|
}
|
|
} else if (type != SSH2_FXP_NAME)
|
|
fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
|
|
SSH2_FXP_NAME, type);
|
|
|
|
count = buffer_get_int(&msg);
|
|
if (count == 0)
|
|
break;
|
|
debug3("Received %d SSH2_FXP_NAME responses", count);
|
|
for (i = 0; i < count; i++) {
|
|
char *filename, *longname;
|
|
Attrib *a;
|
|
|
|
filename = buffer_get_string(&msg, NULL);
|
|
longname = buffer_get_string(&msg, NULL);
|
|
a = decode_attrib(&msg);
|
|
|
|
if (printflag)
|
|
printf("%s\n", longname);
|
|
|
|
if (dir) {
|
|
*dir = xrealloc(*dir, ents + 2, sizeof(**dir));
|
|
(*dir)[ents] = xmalloc(sizeof(***dir));
|
|
(*dir)[ents]->filename = xstrdup(filename);
|
|
(*dir)[ents]->longname = xstrdup(longname);
|
|
memcpy(&(*dir)[ents]->a, a, sizeof(*a));
|
|
(*dir)[++ents] = NULL;
|
|
}
|
|
|
|
xfree(filename);
|
|
xfree(longname);
|
|
}
|
|
}
|
|
|
|
buffer_free(&msg);
|
|
do_close(conn, handle, handle_len);
|
|
xfree(handle);
|
|
|
|
/* Don't return partial matches on interrupt */
|
|
if (interrupted && dir != NULL && *dir != NULL) {
|
|
free_sftp_dirents(*dir);
|
|
*dir = xmalloc(sizeof(**dir));
|
|
**dir = NULL;
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
int
|
|
do_readdir(struct sftp_conn *conn, char *path, SFTP_DIRENT ***dir)
|
|
{
|
|
return(do_lsreaddir(conn, path, 0, dir));
|
|
}
|
|
|
|
void free_sftp_dirents(SFTP_DIRENT **s)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; s[i]; i++) {
|
|
xfree(s[i]->filename);
|
|
xfree(s[i]->longname);
|
|
xfree(s[i]);
|
|
}
|
|
xfree(s);
|
|
}
|
|
|
|
int
|
|
do_rm(struct sftp_conn *conn, char *path)
|
|
{
|
|
u_int status, id;
|
|
|
|
debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
|
|
|
|
id = conn->msg_id++;
|
|
send_string_request(conn->fd_out, id, SSH2_FXP_REMOVE, path,
|
|
strlen(path));
|
|
status = get_status(conn->fd_in, id);
|
|
if (status != SSH2_FX_OK)
|
|
error("Couldn't delete file: %s", fx2txt(status));
|
|
return(status);
|
|
}
|
|
|
|
int
|
|
do_mkdir(struct sftp_conn *conn, char *path, Attrib *a)
|
|
{
|
|
u_int status, id;
|
|
|
|
id = conn->msg_id++;
|
|
send_string_attrs_request(conn->fd_out, id, SSH2_FXP_MKDIR, path,
|
|
strlen(path), a);
|
|
|
|
status = get_status(conn->fd_in, id);
|
|
if (status != SSH2_FX_OK)
|
|
error("Couldn't create directory: %s", fx2txt(status));
|
|
|
|
return(status);
|
|
}
|
|
|
|
int
|
|
do_rmdir(struct sftp_conn *conn, char *path)
|
|
{
|
|
u_int status, id;
|
|
|
|
id = conn->msg_id++;
|
|
send_string_request(conn->fd_out, id, SSH2_FXP_RMDIR, path,
|
|
strlen(path));
|
|
|
|
status = get_status(conn->fd_in, id);
|
|
if (status != SSH2_FX_OK)
|
|
error("Couldn't remove directory: %s", fx2txt(status));
|
|
|
|
return(status);
|
|
}
|
|
|
|
Attrib *
|
|
do_stat(struct sftp_conn *conn, char *path, int quiet)
|
|
{
|
|
u_int id;
|
|
|
|
id = conn->msg_id++;
|
|
|
|
send_string_request(conn->fd_out, id,
|
|
conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
|
|
path, strlen(path));
|
|
|
|
return(get_decode_stat(conn->fd_in, id, quiet));
|
|
}
|
|
|
|
Attrib *
|
|
do_lstat(struct sftp_conn *conn, char *path, int quiet)
|
|
{
|
|
u_int id;
|
|
|
|
if (conn->version == 0) {
|
|
if (quiet)
|
|
debug("Server version does not support lstat operation");
|
|
else
|
|
logit("Server version does not support lstat operation");
|
|
return(do_stat(conn, path, quiet));
|
|
}
|
|
|
|
id = conn->msg_id++;
|
|
send_string_request(conn->fd_out, id, SSH2_FXP_LSTAT, path,
|
|
strlen(path));
|
|
|
|
return(get_decode_stat(conn->fd_in, id, quiet));
|
|
}
|
|
|
|
#ifdef notyet
|
|
Attrib *
|
|
do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet)
|
|
{
|
|
u_int id;
|
|
|
|
id = conn->msg_id++;
|
|
send_string_request(conn->fd_out, id, SSH2_FXP_FSTAT, handle,
|
|
handle_len);
|
|
|
|
return(get_decode_stat(conn->fd_in, id, quiet));
|
|
}
|
|
#endif
|
|
|
|
int
|
|
do_setstat(struct sftp_conn *conn, char *path, Attrib *a)
|
|
{
|
|
u_int status, id;
|
|
|
|
id = conn->msg_id++;
|
|
send_string_attrs_request(conn->fd_out, id, SSH2_FXP_SETSTAT, path,
|
|
strlen(path), a);
|
|
|
|
status = get_status(conn->fd_in, id);
|
|
if (status != SSH2_FX_OK)
|
|
error("Couldn't setstat on \"%s\": %s", path,
|
|
fx2txt(status));
|
|
|
|
return(status);
|
|
}
|
|
|
|
int
|
|
do_fsetstat(struct sftp_conn *conn, char *handle, u_int handle_len,
|
|
Attrib *a)
|
|
{
|
|
u_int status, id;
|
|
|
|
id = conn->msg_id++;
|
|
send_string_attrs_request(conn->fd_out, id, SSH2_FXP_FSETSTAT, handle,
|
|
handle_len, a);
|
|
|
|
status = get_status(conn->fd_in, id);
|
|
if (status != SSH2_FX_OK)
|
|
error("Couldn't fsetstat: %s", fx2txt(status));
|
|
|
|
return(status);
|
|
}
|
|
|
|
char *
|
|
do_realpath(struct sftp_conn *conn, char *path)
|
|
{
|
|
Buffer msg;
|
|
u_int type, expected_id, count, id;
|
|
char *filename, *longname;
|
|
Attrib *a;
|
|
|
|
expected_id = id = conn->msg_id++;
|
|
send_string_request(conn->fd_out, id, SSH2_FXP_REALPATH, path,
|
|
strlen(path));
|
|
|
|
buffer_init(&msg);
|
|
|
|
get_msg(conn->fd_in, &msg);
|
|
type = buffer_get_char(&msg);
|
|
id = buffer_get_int(&msg);
|
|
|
|
if (id != expected_id)
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
|
|
|
if (type == SSH2_FXP_STATUS) {
|
|
u_int status = buffer_get_int(&msg);
|
|
|
|
error("Couldn't canonicalise: %s", fx2txt(status));
|
|
return(NULL);
|
|
} else if (type != SSH2_FXP_NAME)
|
|
fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
|
|
SSH2_FXP_NAME, type);
|
|
|
|
count = buffer_get_int(&msg);
|
|
if (count != 1)
|
|
fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
|
|
|
|
filename = buffer_get_string(&msg, NULL);
|
|
longname = buffer_get_string(&msg, NULL);
|
|
a = decode_attrib(&msg);
|
|
|
|
debug3("SSH_FXP_REALPATH %s -> %s", path, filename);
|
|
|
|
xfree(longname);
|
|
|
|
buffer_free(&msg);
|
|
|
|
return(filename);
|
|
}
|
|
|
|
int
|
|
do_rename(struct sftp_conn *conn, char *oldpath, char *newpath)
|
|
{
|
|
Buffer msg;
|
|
u_int status, id;
|
|
|
|
buffer_init(&msg);
|
|
|
|
/* Send rename request */
|
|
id = conn->msg_id++;
|
|
if ((conn->exts & SFTP_EXT_POSIX_RENAME)) {
|
|
buffer_put_char(&msg, SSH2_FXP_EXTENDED);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_cstring(&msg, "posix-rename@openssh.com");
|
|
} else {
|
|
buffer_put_char(&msg, SSH2_FXP_RENAME);
|
|
buffer_put_int(&msg, id);
|
|
}
|
|
buffer_put_cstring(&msg, oldpath);
|
|
buffer_put_cstring(&msg, newpath);
|
|
send_msg(conn->fd_out, &msg);
|
|
debug3("Sent message %s \"%s\" -> \"%s\"",
|
|
(conn->exts & SFTP_EXT_POSIX_RENAME) ? "posix-rename@openssh.com" :
|
|
"SSH2_FXP_RENAME", oldpath, newpath);
|
|
buffer_free(&msg);
|
|
|
|
status = get_status(conn->fd_in, id);
|
|
if (status != SSH2_FX_OK)
|
|
error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath,
|
|
newpath, fx2txt(status));
|
|
|
|
return(status);
|
|
}
|
|
|
|
int
|
|
do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath)
|
|
{
|
|
Buffer msg;
|
|
u_int status, id;
|
|
|
|
if (conn->version < 3) {
|
|
error("This server does not support the symlink operation");
|
|
return(SSH2_FX_OP_UNSUPPORTED);
|
|
}
|
|
|
|
buffer_init(&msg);
|
|
|
|
/* Send symlink request */
|
|
id = conn->msg_id++;
|
|
buffer_put_char(&msg, SSH2_FXP_SYMLINK);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_cstring(&msg, oldpath);
|
|
buffer_put_cstring(&msg, newpath);
|
|
send_msg(conn->fd_out, &msg);
|
|
debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
|
|
newpath);
|
|
buffer_free(&msg);
|
|
|
|
status = get_status(conn->fd_in, id);
|
|
if (status != SSH2_FX_OK)
|
|
error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath,
|
|
newpath, fx2txt(status));
|
|
|
|
return(status);
|
|
}
|
|
|
|
#ifdef notyet
|
|
char *
|
|
do_readlink(struct sftp_conn *conn, char *path)
|
|
{
|
|
Buffer msg;
|
|
u_int type, expected_id, count, id;
|
|
char *filename, *longname;
|
|
Attrib *a;
|
|
|
|
expected_id = id = conn->msg_id++;
|
|
send_string_request(conn->fd_out, id, SSH2_FXP_READLINK, path,
|
|
strlen(path));
|
|
|
|
buffer_init(&msg);
|
|
|
|
get_msg(conn->fd_in, &msg);
|
|
type = buffer_get_char(&msg);
|
|
id = buffer_get_int(&msg);
|
|
|
|
if (id != expected_id)
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
|
|
|
if (type == SSH2_FXP_STATUS) {
|
|
u_int status = buffer_get_int(&msg);
|
|
|
|
error("Couldn't readlink: %s", fx2txt(status));
|
|
return(NULL);
|
|
} else if (type != SSH2_FXP_NAME)
|
|
fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
|
|
SSH2_FXP_NAME, type);
|
|
|
|
count = buffer_get_int(&msg);
|
|
if (count != 1)
|
|
fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
|
|
|
|
filename = buffer_get_string(&msg, NULL);
|
|
longname = buffer_get_string(&msg, NULL);
|
|
a = decode_attrib(&msg);
|
|
|
|
debug3("SSH_FXP_READLINK %s -> %s", path, filename);
|
|
|
|
xfree(longname);
|
|
|
|
buffer_free(&msg);
|
|
|
|
return(filename);
|
|
}
|
|
#endif
|
|
|
|
int
|
|
do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
|
|
int quiet)
|
|
{
|
|
Buffer msg;
|
|
u_int id;
|
|
|
|
if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
|
|
error("Server does not support statvfs@openssh.com extension");
|
|
return -1;
|
|
}
|
|
|
|
id = conn->msg_id++;
|
|
|
|
buffer_init(&msg);
|
|
buffer_clear(&msg);
|
|
buffer_put_char(&msg, SSH2_FXP_EXTENDED);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_cstring(&msg, "statvfs@openssh.com");
|
|
buffer_put_cstring(&msg, path);
|
|
send_msg(conn->fd_out, &msg);
|
|
buffer_free(&msg);
|
|
|
|
return get_decode_statvfs(conn->fd_in, st, id, quiet);
|
|
}
|
|
|
|
#ifdef notyet
|
|
int
|
|
do_fstatvfs(struct sftp_conn *conn, const char *handle, u_int handle_len,
|
|
struct sftp_statvfs *st, int quiet)
|
|
{
|
|
Buffer msg;
|
|
u_int id;
|
|
|
|
if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
|
|
error("Server does not support fstatvfs@openssh.com extension");
|
|
return -1;
|
|
}
|
|
|
|
id = conn->msg_id++;
|
|
|
|
buffer_init(&msg);
|
|
buffer_clear(&msg);
|
|
buffer_put_char(&msg, SSH2_FXP_EXTENDED);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_cstring(&msg, "fstatvfs@openssh.com");
|
|
buffer_put_string(&msg, handle, handle_len);
|
|
send_msg(conn->fd_out, &msg);
|
|
buffer_free(&msg);
|
|
|
|
return get_decode_statvfs(conn->fd_in, st, id, quiet);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len,
|
|
char *handle, u_int handle_len)
|
|
{
|
|
Buffer msg;
|
|
|
|
buffer_init(&msg);
|
|
buffer_clear(&msg);
|
|
buffer_put_char(&msg, SSH2_FXP_READ);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_string(&msg, handle, handle_len);
|
|
buffer_put_int64(&msg, offset);
|
|
buffer_put_int(&msg, len);
|
|
send_msg(fd_out, &msg);
|
|
buffer_free(&msg);
|
|
}
|
|
|
|
int
|
|
do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
|
|
int pflag)
|
|
{
|
|
Attrib junk, *a;
|
|
Buffer msg;
|
|
char *handle;
|
|
int local_fd, status = 0, write_error;
|
|
int read_error, write_errno;
|
|
u_int64_t offset, size;
|
|
u_int handle_len, mode, type, id, buflen, num_req, max_req;
|
|
off_t progress_counter;
|
|
struct request {
|
|
u_int id;
|
|
u_int len;
|
|
u_int64_t offset;
|
|
TAILQ_ENTRY(request) tq;
|
|
};
|
|
TAILQ_HEAD(reqhead, request) requests;
|
|
struct request *req;
|
|
|
|
TAILQ_INIT(&requests);
|
|
|
|
a = do_stat(conn, remote_path, 0);
|
|
if (a == 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;
|
|
else
|
|
mode = 0666;
|
|
|
|
if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
|
|
(!S_ISREG(a->perm))) {
|
|
error("Cannot download non-regular file: %s", remote_path);
|
|
return(-1);
|
|
}
|
|
|
|
if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
|
|
size = a->size;
|
|
else
|
|
size = 0;
|
|
|
|
buflen = conn->transfer_buflen;
|
|
buffer_init(&msg);
|
|
|
|
/* Send open request */
|
|
id = conn->msg_id++;
|
|
buffer_put_char(&msg, SSH2_FXP_OPEN);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_cstring(&msg, remote_path);
|
|
buffer_put_int(&msg, SSH2_FXF_READ);
|
|
attrib_clear(&junk); /* Send empty attributes */
|
|
encode_attrib(&msg, &junk);
|
|
send_msg(conn->fd_out, &msg);
|
|
debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
|
|
|
|
handle = get_handle(conn->fd_in, id, &handle_len);
|
|
if (handle == NULL) {
|
|
buffer_free(&msg);
|
|
return(-1);
|
|
}
|
|
|
|
local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC,
|
|
mode | S_IWRITE);
|
|
if (local_fd == -1) {
|
|
error("Couldn't open local file \"%s\" for writing: %s",
|
|
local_path, strerror(errno));
|
|
do_close(conn, handle, handle_len);
|
|
buffer_free(&msg);
|
|
xfree(handle);
|
|
return(-1);
|
|
}
|
|
|
|
/* Read from remote and write to local */
|
|
write_error = read_error = write_errno = num_req = offset = 0;
|
|
max_req = 1;
|
|
progress_counter = 0;
|
|
|
|
if (showprogress && size != 0)
|
|
start_progress_meter(remote_path, size, &progress_counter);
|
|
|
|
while (num_req > 0 || max_req > 0) {
|
|
char *data;
|
|
u_int len;
|
|
|
|
/*
|
|
* Simulate EOF on interrupt: stop sending new requests and
|
|
* allow outstanding requests to drain gracefully
|
|
*/
|
|
if (interrupted) {
|
|
if (num_req == 0) /* If we haven't started yet... */
|
|
break;
|
|
max_req = 0;
|
|
}
|
|
|
|
/* Send some more requests */
|
|
while (num_req < max_req) {
|
|
debug3("Request range %llu -> %llu (%d/%d)",
|
|
(unsigned long long)offset,
|
|
(unsigned long long)offset + buflen - 1,
|
|
num_req, max_req);
|
|
req = xmalloc(sizeof(*req));
|
|
req->id = conn->msg_id++;
|
|
req->len = buflen;
|
|
req->offset = offset;
|
|
offset += buflen;
|
|
num_req++;
|
|
TAILQ_INSERT_TAIL(&requests, req, tq);
|
|
send_read_request(conn->fd_out, req->id, req->offset,
|
|
req->len, handle, handle_len);
|
|
}
|
|
|
|
buffer_clear(&msg);
|
|
get_msg(conn->fd_in, &msg);
|
|
type = buffer_get_char(&msg);
|
|
id = buffer_get_int(&msg);
|
|
debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
|
|
|
|
/* Find the request in our queue */
|
|
for (req = TAILQ_FIRST(&requests);
|
|
req != NULL && req->id != id;
|
|
req = TAILQ_NEXT(req, tq))
|
|
;
|
|
if (req == NULL)
|
|
fatal("Unexpected reply %u", id);
|
|
|
|
switch (type) {
|
|
case SSH2_FXP_STATUS:
|
|
status = buffer_get_int(&msg);
|
|
if (status != SSH2_FX_EOF)
|
|
read_error = 1;
|
|
max_req = 0;
|
|
TAILQ_REMOVE(&requests, req, tq);
|
|
xfree(req);
|
|
num_req--;
|
|
break;
|
|
case SSH2_FXP_DATA:
|
|
data = buffer_get_string(&msg, &len);
|
|
debug3("Received data %llu -> %llu",
|
|
(unsigned long long)req->offset,
|
|
(unsigned long long)req->offset + len - 1);
|
|
if (len > req->len)
|
|
fatal("Received more data than asked for "
|
|
"%u > %u", len, req->len);
|
|
if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
|
|
atomicio(vwrite, local_fd, data, len) != len) &&
|
|
!write_error) {
|
|
write_errno = errno;
|
|
write_error = 1;
|
|
max_req = 0;
|
|
}
|
|
progress_counter += len;
|
|
xfree(data);
|
|
|
|
if (len == req->len) {
|
|
TAILQ_REMOVE(&requests, req, tq);
|
|
xfree(req);
|
|
num_req--;
|
|
} else {
|
|
/* Resend the request for the missing data */
|
|
debug3("Short data block, re-requesting "
|
|
"%llu -> %llu (%2d)",
|
|
(unsigned long long)req->offset + len,
|
|
(unsigned long long)req->offset +
|
|
req->len - 1, num_req);
|
|
req->id = conn->msg_id++;
|
|
req->len -= len;
|
|
req->offset += len;
|
|
send_read_request(conn->fd_out, req->id,
|
|
req->offset, req->len, handle, handle_len);
|
|
/* Reduce the request size */
|
|
if (len < buflen)
|
|
buflen = MAX(MIN_READ_SIZE, len);
|
|
}
|
|
if (max_req > 0) { /* max_req = 0 iff EOF received */
|
|
if (size > 0 && offset > size) {
|
|
/* Only one request at a time
|
|
* after the expected EOF */
|
|
debug3("Finish at %llu (%2d)",
|
|
(unsigned long long)offset,
|
|
num_req);
|
|
max_req = 1;
|
|
} else if (max_req <= conn->num_requests) {
|
|
++max_req;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
|
|
SSH2_FXP_DATA, type);
|
|
}
|
|
}
|
|
|
|
if (showprogress && size)
|
|
stop_progress_meter();
|
|
|
|
/* Sanity check */
|
|
if (TAILQ_FIRST(&requests) != NULL)
|
|
fatal("Transfer complete, but requests still in queue");
|
|
|
|
if (read_error) {
|
|
error("Couldn't read from remote file \"%s\" : %s",
|
|
remote_path, fx2txt(status));
|
|
do_close(conn, handle, handle_len);
|
|
} else if (write_error) {
|
|
error("Couldn't write to \"%s\": %s", local_path,
|
|
strerror(write_errno));
|
|
status = -1;
|
|
do_close(conn, handle, handle_len);
|
|
} else {
|
|
status = do_close(conn, handle, handle_len);
|
|
|
|
/* Override umask and utimes if asked */
|
|
#ifdef HAVE_FCHMOD
|
|
if (pflag && fchmod(local_fd, mode) == -1)
|
|
#else
|
|
if (pflag && chmod(local_path, mode) == -1)
|
|
#endif /* HAVE_FCHMOD */
|
|
error("Couldn't set mode on \"%s\": %s", local_path,
|
|
strerror(errno));
|
|
if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
|
|
struct timeval tv[2];
|
|
tv[0].tv_sec = a->atime;
|
|
tv[1].tv_sec = a->mtime;
|
|
tv[0].tv_usec = tv[1].tv_usec = 0;
|
|
if (utimes(local_path, tv) == -1)
|
|
error("Can't set times on \"%s\": %s",
|
|
local_path, strerror(errno));
|
|
}
|
|
}
|
|
close(local_fd);
|
|
buffer_free(&msg);
|
|
xfree(handle);
|
|
|
|
return(status);
|
|
}
|
|
|
|
int
|
|
do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
|
|
int pflag)
|
|
{
|
|
int local_fd;
|
|
int status = SSH2_FX_OK;
|
|
u_int handle_len, id, type;
|
|
off_t offset;
|
|
char *handle, *data;
|
|
Buffer msg;
|
|
struct stat sb;
|
|
Attrib a;
|
|
u_int32_t startid;
|
|
u_int32_t ackid;
|
|
struct outstanding_ack {
|
|
u_int id;
|
|
u_int len;
|
|
off_t offset;
|
|
TAILQ_ENTRY(outstanding_ack) tq;
|
|
};
|
|
TAILQ_HEAD(ackhead, outstanding_ack) acks;
|
|
struct outstanding_ack *ack = NULL;
|
|
|
|
TAILQ_INIT(&acks);
|
|
|
|
if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
|
|
error("Couldn't open local file \"%s\" for reading: %s",
|
|
local_path, strerror(errno));
|
|
return(-1);
|
|
}
|
|
if (fstat(local_fd, &sb) == -1) {
|
|
error("Couldn't fstat local file \"%s\": %s",
|
|
local_path, strerror(errno));
|
|
close(local_fd);
|
|
return(-1);
|
|
}
|
|
if (!S_ISREG(sb.st_mode)) {
|
|
error("%s is not a regular file", local_path);
|
|
close(local_fd);
|
|
return(-1);
|
|
}
|
|
stat_to_attrib(&sb, &a);
|
|
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
|
|
a.perm &= 0777;
|
|
if (!pflag)
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
|
|
|
|
buffer_init(&msg);
|
|
|
|
/* Send open request */
|
|
id = conn->msg_id++;
|
|
buffer_put_char(&msg, SSH2_FXP_OPEN);
|
|
buffer_put_int(&msg, id);
|
|
buffer_put_cstring(&msg, remote_path);
|
|
buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC);
|
|
encode_attrib(&msg, &a);
|
|
send_msg(conn->fd_out, &msg);
|
|
debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
|
|
|
|
buffer_clear(&msg);
|
|
|
|
handle = get_handle(conn->fd_in, id, &handle_len);
|
|
if (handle == NULL) {
|
|
close(local_fd);
|
|
buffer_free(&msg);
|
|
return -1;
|
|
}
|
|
|
|
startid = ackid = id + 1;
|
|
data = xmalloc(conn->transfer_buflen);
|
|
|
|
/* Read from local and write to remote */
|
|
offset = 0;
|
|
if (showprogress)
|
|
start_progress_meter(local_path, sb.st_size, &offset);
|
|
|
|
for (;;) {
|
|
int len;
|
|
|
|
/*
|
|
* Can't use atomicio here because it returns 0 on EOF,
|
|
* thus losing the last block of the file.
|
|
* Simulate an EOF on interrupt, allowing ACKs from the
|
|
* server to drain.
|
|
*/
|
|
if (interrupted || status != SSH2_FX_OK)
|
|
len = 0;
|
|
else do
|
|
len = read(local_fd, data, conn->transfer_buflen);
|
|
while ((len == -1) &&
|
|
(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
|
|
|
|
if (len == -1)
|
|
fatal("Couldn't read from \"%s\": %s", local_path,
|
|
strerror(errno));
|
|
|
|
if (len != 0) {
|
|
ack = xmalloc(sizeof(*ack));
|
|
ack->id = ++id;
|
|
ack->offset = offset;
|
|
ack->len = len;
|
|
TAILQ_INSERT_TAIL(&acks, ack, tq);
|
|
|
|
buffer_clear(&msg);
|
|
buffer_put_char(&msg, SSH2_FXP_WRITE);
|
|
buffer_put_int(&msg, ack->id);
|
|
buffer_put_string(&msg, handle, handle_len);
|
|
buffer_put_int64(&msg, offset);
|
|
buffer_put_string(&msg, data, len);
|
|
send_msg(conn->fd_out, &msg);
|
|
debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
|
|
id, (unsigned long long)offset, len);
|
|
} else if (TAILQ_FIRST(&acks) == NULL)
|
|
break;
|
|
|
|
if (ack == NULL)
|
|
fatal("Unexpected ACK %u", id);
|
|
|
|
if (id == startid || len == 0 ||
|
|
id - ackid >= conn->num_requests) {
|
|
u_int r_id;
|
|
|
|
buffer_clear(&msg);
|
|
get_msg(conn->fd_in, &msg);
|
|
type = buffer_get_char(&msg);
|
|
r_id = buffer_get_int(&msg);
|
|
|
|
if (type != SSH2_FXP_STATUS)
|
|
fatal("Expected SSH2_FXP_STATUS(%d) packet, "
|
|
"got %d", SSH2_FXP_STATUS, type);
|
|
|
|
status = buffer_get_int(&msg);
|
|
debug3("SSH2_FXP_STATUS %d", status);
|
|
|
|
/* Find the request in our queue */
|
|
for (ack = TAILQ_FIRST(&acks);
|
|
ack != NULL && ack->id != r_id;
|
|
ack = TAILQ_NEXT(ack, tq))
|
|
;
|
|
if (ack == NULL)
|
|
fatal("Can't find request for ID %u", r_id);
|
|
TAILQ_REMOVE(&acks, ack, tq);
|
|
debug3("In write loop, ack for %u %u bytes at %lld",
|
|
ack->id, ack->len, (long long)ack->offset);
|
|
++ackid;
|
|
xfree(ack);
|
|
}
|
|
offset += len;
|
|
if (offset < 0)
|
|
fatal("%s: offset < 0", __func__);
|
|
}
|
|
buffer_free(&msg);
|
|
|
|
if (showprogress)
|
|
stop_progress_meter();
|
|
xfree(data);
|
|
|
|
if (status != SSH2_FX_OK) {
|
|
error("Couldn't write to remote file \"%s\": %s",
|
|
remote_path, fx2txt(status));
|
|
status = -1;
|
|
}
|
|
|
|
if (close(local_fd) == -1) {
|
|
error("Couldn't close local file \"%s\": %s", local_path,
|
|
strerror(errno));
|
|
status = -1;
|
|
}
|
|
|
|
/* Override umask and utimes if asked */
|
|
if (pflag)
|
|
do_fsetstat(conn, handle, handle_len, &a);
|
|
|
|
if (do_close(conn, handle, handle_len) != SSH2_FX_OK)
|
|
status = -1;
|
|
xfree(handle);
|
|
|
|
return status;
|
|
}
|