2022-01-17 22:41:04 +01:00
|
|
|
/* $OpenBSD: sftp-client.c,v 1.161 2022/01/17 21:41:04 djm Exp $ */
|
2001-02-04 13:20:18 +01:00
|
|
|
/*
|
2004-02-17 07:07:59 +01:00
|
|
|
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
|
2001-02-04 13:20:18 +01:00
|
|
|
*
|
2004-02-17 07:07:59 +01:00
|
|
|
* 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.
|
2001-02-04 13:20:18 +01:00
|
|
|
*
|
2004-02-17 07:07:59 +01:00
|
|
|
* 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.
|
2001-02-04 13:20:18 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
/* XXX: memleaks */
|
|
|
|
/* XXX: signed vs unsigned */
|
2002-02-13 04:10:32 +01:00
|
|
|
/* XXX: remove all logging, only return status codes */
|
2001-02-04 13:20:18 +01:00
|
|
|
/* XXX: copy between two remote sites */
|
|
|
|
|
|
|
|
#include "includes.h"
|
2006-03-15 01:45:54 +01:00
|
|
|
|
|
|
|
#include <sys/types.h>
|
2008-06-08 01:25:28 +02:00
|
|
|
#ifdef HAVE_SYS_STATVFS_H
|
2008-05-19 06:53:33 +02:00
|
|
|
#include <sys/statvfs.h>
|
2008-06-08 01:25:28 +02:00
|
|
|
#endif
|
2006-08-05 04:39:39 +02:00
|
|
|
#include "openbsd-compat/sys-queue.h"
|
2006-03-15 01:45:54 +01:00
|
|
|
#ifdef HAVE_SYS_STAT_H
|
|
|
|
# include <sys/stat.h>
|
|
|
|
#endif
|
2006-08-05 02:57:45 +02:00
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
|
|
# include <sys/time.h>
|
|
|
|
#endif
|
2006-08-05 04:39:39 +02:00
|
|
|
#include <sys/uio.h>
|
2006-07-10 13:13:46 +02:00
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
#include <dirent.h>
|
2006-07-12 14:22:46 +02:00
|
|
|
#include <errno.h>
|
2021-08-07 03:30:57 +02:00
|
|
|
#ifdef HAVE_POLL_H
|
|
|
|
#include <poll.h>
|
|
|
|
#else
|
|
|
|
# ifdef HAVE_SYS_POLL_H
|
|
|
|
# include <sys/poll.h>
|
|
|
|
# endif
|
|
|
|
#endif
|
2006-07-10 13:13:46 +02:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <signal.h>
|
2006-08-05 04:39:39 +02:00
|
|
|
#include <stdarg.h>
|
2006-08-05 03:37:59 +02:00
|
|
|
#include <stdio.h>
|
2013-11-21 03:55:43 +01:00
|
|
|
#include <stdlib.h>
|
2006-07-24 06:13:33 +02:00
|
|
|
#include <string.h>
|
2006-07-24 06:01:23 +02:00
|
|
|
#include <unistd.h>
|
2002-02-13 04:03:56 +01:00
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
#include "xmalloc.h"
|
2015-01-14 14:54:13 +01:00
|
|
|
#include "ssherr.h"
|
|
|
|
#include "sshbuf.h"
|
2001-02-04 13:20:18 +01:00
|
|
|
#include "log.h"
|
|
|
|
#include "atomicio.h"
|
2003-01-10 11:43:24 +01:00
|
|
|
#include "progressmeter.h"
|
2006-03-31 14:13:02 +02:00
|
|
|
#include "misc.h"
|
2016-05-26 01:48:45 +02:00
|
|
|
#include "utf8.h"
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
#include "sftp.h"
|
|
|
|
#include "sftp-common.h"
|
|
|
|
#include "sftp-client.h"
|
|
|
|
|
2004-05-24 02:12:19 +02:00
|
|
|
extern volatile sig_atomic_t interrupted;
|
2003-01-10 11:43:24 +01:00
|
|
|
extern int showprogress;
|
|
|
|
|
2021-04-01 00:16:34 +02:00
|
|
|
/* Default size of buffer for up/download */
|
|
|
|
#define DEFAULT_COPY_BUFLEN 32768
|
|
|
|
|
|
|
|
/* Default number of concurrent outstanding requests */
|
|
|
|
#define DEFAULT_NUM_REQUESTS 64
|
|
|
|
|
2006-03-15 01:34:25 +01:00
|
|
|
/* Minimum amount of data to read at a time */
|
2002-02-13 04:03:56 +01:00
|
|
|
#define MIN_READ_SIZE 512
|
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
/* Maximum depth to descend in directory trees */
|
|
|
|
#define MAX_DIR_DEPTH 64
|
|
|
|
|
2017-03-20 01:53:34 +01:00
|
|
|
/* Directory separator characters */
|
|
|
|
#ifdef HAVE_CYGWIN
|
|
|
|
# define SFTP_DIRECTORY_CHARS "/\\"
|
|
|
|
#else /* HAVE_CYGWIN */
|
|
|
|
# define SFTP_DIRECTORY_CHARS "/"
|
|
|
|
#endif /* HAVE_CYGWIN */
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
struct sftp_conn {
|
|
|
|
int fd_in;
|
|
|
|
int fd_out;
|
2021-04-01 00:16:34 +02:00
|
|
|
u_int download_buflen;
|
|
|
|
u_int upload_buflen;
|
2002-02-13 04:10:32 +01:00
|
|
|
u_int num_requests;
|
|
|
|
u_int version;
|
|
|
|
u_int msg_id;
|
2008-05-19 06:53:33 +02:00
|
|
|
#define SFTP_EXT_POSIX_RENAME 0x00000001
|
|
|
|
#define SFTP_EXT_STATVFS 0x00000002
|
|
|
|
#define SFTP_EXT_FSTATVFS 0x00000004
|
2010-12-04 23:02:47 +01:00
|
|
|
#define SFTP_EXT_HARDLINK 0x00000008
|
2013-10-17 02:48:52 +02:00
|
|
|
#define SFTP_EXT_FSYNC 0x00000010
|
2019-01-17 00:23:45 +01:00
|
|
|
#define SFTP_EXT_LSETSTAT 0x00000020
|
2021-04-01 00:16:34 +02:00
|
|
|
#define SFTP_EXT_LIMITS 0x00000040
|
2021-08-10 01:47:44 +02:00
|
|
|
#define SFTP_EXT_PATH_EXPAND 0x00000080
|
2008-03-27 00:59:57 +01:00
|
|
|
u_int exts;
|
2010-09-24 14:15:11 +02:00
|
|
|
u_int64_t limit_kbps;
|
|
|
|
struct bwlimit bwlimit_in, bwlimit_out;
|
2002-02-13 04:10:32 +01:00
|
|
|
};
|
2001-02-09 03:58:04 +01:00
|
|
|
|
2021-08-07 02:07:18 +02:00
|
|
|
/* Tracks in-progress requests during file transfers */
|
|
|
|
struct request {
|
|
|
|
u_int id;
|
|
|
|
size_t len;
|
|
|
|
u_int64_t offset;
|
|
|
|
TAILQ_ENTRY(request) tq;
|
|
|
|
};
|
|
|
|
TAILQ_HEAD(requests, request);
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
static u_char *
|
|
|
|
get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
|
2010-09-24 14:15:11 +02:00
|
|
|
const char *errfmt, ...) __attribute__((format(printf, 4, 5)));
|
|
|
|
|
2021-08-07 02:14:17 +02:00
|
|
|
static struct request *
|
|
|
|
request_enqueue(struct requests *requests, u_int id, size_t len,
|
|
|
|
uint64_t offset)
|
|
|
|
{
|
|
|
|
struct request *req;
|
|
|
|
|
|
|
|
req = xcalloc(1, sizeof(*req));
|
|
|
|
req->id = id;
|
|
|
|
req->len = len;
|
|
|
|
req->offset = offset;
|
|
|
|
TAILQ_INSERT_TAIL(requests, req, tq);
|
|
|
|
return req;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct request *
|
|
|
|
request_find(struct requests *requests, u_int id)
|
|
|
|
{
|
|
|
|
struct request *req;
|
|
|
|
|
|
|
|
for (req = TAILQ_FIRST(requests);
|
|
|
|
req != NULL && req->id != id;
|
|
|
|
req = TAILQ_NEXT(req, tq))
|
|
|
|
;
|
|
|
|
return req;
|
|
|
|
}
|
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
/* ARGSUSED */
|
|
|
|
static int
|
|
|
|
sftpio(void *_bwlimit, size_t amount)
|
|
|
|
{
|
|
|
|
struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;
|
|
|
|
|
2019-01-24 17:52:17 +01:00
|
|
|
refresh_progress_meter(0);
|
2019-01-23 09:01:46 +01:00
|
|
|
if (bwlimit != NULL)
|
|
|
|
bandwidth_limit(bwlimit, amount);
|
2010-09-24 14:15:11 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2009-10-06 23:24:19 +02:00
|
|
|
|
2001-06-25 07:01:22 +02:00
|
|
|
static void
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(struct sftp_conn *conn, struct sshbuf *m)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2003-01-10 11:43:58 +01:00
|
|
|
u_char mlen[4];
|
2006-04-23 04:06:35 +02:00
|
|
|
struct iovec iov[2];
|
2003-01-10 11:43:58 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH)
|
|
|
|
fatal("Outbound message too long %zu", sshbuf_len(m));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2003-01-10 11:43:58 +01:00
|
|
|
/* Send length first */
|
2015-01-14 14:54:13 +01:00
|
|
|
put_u32(mlen, sshbuf_len(m));
|
2006-04-23 04:06:35 +02:00
|
|
|
iov[0].iov_base = mlen;
|
|
|
|
iov[0].iov_len = sizeof(mlen);
|
2015-01-14 14:54:13 +01:00
|
|
|
iov[1].iov_base = (u_char *)sshbuf_ptr(m);
|
|
|
|
iov[1].iov_len = sshbuf_len(m);
|
2006-08-05 04:39:39 +02:00
|
|
|
|
2019-01-23 09:01:46 +01:00
|
|
|
if (atomiciov6(writev, conn->fd_out, iov, 2, sftpio,
|
|
|
|
conn->limit_kbps > 0 ? &conn->bwlimit_out : NULL) !=
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_len(m) + sizeof(mlen))
|
2001-02-04 13:20:18 +01:00
|
|
|
fatal("Couldn't send packet: %s", strerror(errno));
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_reset(m);
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2001-06-25 07:01:22 +02:00
|
|
|
static void
|
2017-11-28 22:10:22 +01:00
|
|
|
get_msg_extended(struct sftp_conn *conn, struct sshbuf *m, int initial)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2003-01-10 11:43:58 +01:00
|
|
|
u_int msg_len;
|
2015-01-14 14:54:13 +01:00
|
|
|
u_char *p;
|
|
|
|
int r;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2021-08-07 02:01:29 +02:00
|
|
|
sshbuf_reset(m);
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_reserve(m, 4, &p)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "reserve");
|
2019-01-23 09:01:46 +01:00
|
|
|
if (atomicio6(read, conn->fd_in, p, 4, sftpio,
|
|
|
|
conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL) != 4) {
|
2017-08-11 06:41:08 +02:00
|
|
|
if (errno == EPIPE || errno == ECONNRESET)
|
2005-05-26 04:23:44 +02:00
|
|
|
fatal("Connection closed");
|
|
|
|
else
|
|
|
|
fatal("Couldn't read packet: %s", strerror(errno));
|
|
|
|
}
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(m, &msg_len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "sshbuf_get_u32");
|
2017-11-28 22:10:22 +01:00
|
|
|
if (msg_len > SFTP_MAX_MSG_LENGTH) {
|
|
|
|
do_log2(initial ? SYSLOG_LEVEL_ERROR : SYSLOG_LEVEL_FATAL,
|
|
|
|
"Received message too long %u", msg_len);
|
|
|
|
fatal("Ensure the remote shell produces no output "
|
|
|
|
"for non-interactive sessions.");
|
|
|
|
}
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_reserve(m, msg_len, &p)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "reserve");
|
2019-01-23 09:01:46 +01:00
|
|
|
if (atomicio6(read, conn->fd_in, p, msg_len, sftpio,
|
|
|
|
conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL)
|
2010-09-24 14:15:11 +02:00
|
|
|
!= msg_len) {
|
2005-05-26 04:23:44 +02:00
|
|
|
if (errno == EPIPE)
|
|
|
|
fatal("Connection closed");
|
|
|
|
else
|
|
|
|
fatal("Read packet: %s", strerror(errno));
|
|
|
|
}
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2017-11-28 22:10:22 +01:00
|
|
|
static void
|
|
|
|
get_msg(struct sftp_conn *conn, struct sshbuf *m)
|
|
|
|
{
|
|
|
|
get_msg_extended(conn, m, 0);
|
|
|
|
}
|
|
|
|
|
2001-06-25 07:01:22 +02:00
|
|
|
static void
|
2015-01-14 14:54:13 +01:00
|
|
|
send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s,
|
2001-02-04 13:20:18 +01:00
|
|
|
u_int len)
|
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, code)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, s, len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2010-09-24 14:15:11 +02:00
|
|
|
debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2001-06-25 07:01:22 +02:00
|
|
|
static void
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
|
2015-01-14 14:54:13 +01:00
|
|
|
const void *s, u_int len, Attrib *a)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, code)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, s, len)) != 0 ||
|
|
|
|
(r = encode_attrib(msg, a)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2021-08-07 02:09:57 +02:00
|
|
|
debug3("Sent message fd %d T:%u I:%u F:0x%04x M:%05o",
|
|
|
|
conn->fd_out, code, id, a->flags, a->perm);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2001-06-25 07:01:22 +02:00
|
|
|
static u_int
|
2010-09-24 14:15:11 +02:00
|
|
|
get_status(struct sftp_conn *conn, u_int expected_id)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
u_char type;
|
|
|
|
u_int id, status;
|
|
|
|
int r;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
get_msg(conn, msg);
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
if (id != expected_id)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (type != SSH2_FXP_STATUS)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
|
2001-02-04 13:20:18 +01:00
|
|
|
SSH2_FXP_STATUS, type);
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2002-06-23 23:27:18 +02:00
|
|
|
debug3("SSH2_FXP_STATUS %u", status);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
return status;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
static u_char *
|
|
|
|
get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
|
2010-09-24 14:15:11 +02:00
|
|
|
const char *errfmt, ...)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
u_int id, status;
|
|
|
|
u_char type;
|
|
|
|
u_char *handle;
|
|
|
|
char errmsg[256];
|
2009-10-06 23:24:19 +02:00
|
|
|
va_list args;
|
2015-01-14 14:54:13 +01:00
|
|
|
int r;
|
2009-10-06 23:24:19 +02:00
|
|
|
|
|
|
|
va_start(args, errfmt);
|
|
|
|
if (errfmt != NULL)
|
|
|
|
vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
|
|
|
|
va_end(args);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
get_msg(conn, msg);
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
if (id != expected_id)
|
2009-10-06 23:24:19 +02:00
|
|
|
fatal("%s: ID mismatch (%u != %u)",
|
|
|
|
errfmt == NULL ? __func__ : errmsg, id, expected_id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (type == SSH2_FXP_STATUS) {
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse status");
|
2009-10-06 23:24:19 +02:00
|
|
|
if (errfmt != NULL)
|
|
|
|
error("%s: %s", errmsg, fx2txt(status));
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
return(NULL);
|
|
|
|
} else if (type != SSH2_FXP_HANDLE)
|
2009-10-06 23:24:19 +02:00
|
|
|
fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
|
|
|
|
errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_string(msg, &handle, len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse handle");
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return handle;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2022-01-01 02:55:30 +01:00
|
|
|
/* XXX returning &static is error-prone. Refactor to fill *Attrib argument */
|
2001-06-25 07:01:22 +02:00
|
|
|
static Attrib *
|
2010-09-24 14:15:11 +02:00
|
|
|
get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
u_int id;
|
|
|
|
u_char type;
|
|
|
|
int r;
|
|
|
|
static Attrib a;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
get_msg(conn, msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
if (id != expected_id)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (type == SSH2_FXP_STATUS) {
|
2015-01-14 14:54:13 +01:00
|
|
|
u_int status;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse status");
|
2001-03-17 01:34:46 +01:00
|
|
|
if (quiet)
|
2022-01-17 22:41:04 +01:00
|
|
|
debug("stat remote: %s", fx2txt(status));
|
2001-03-17 01:34:46 +01:00
|
|
|
else
|
2022-01-17 22:41:04 +01:00
|
|
|
error("stat remote: %s", fx2txt(status));
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
return(NULL);
|
|
|
|
} else if (type != SSH2_FXP_ATTRS) {
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
|
2001-02-04 13:20:18 +01:00
|
|
|
SSH2_FXP_ATTRS, type);
|
|
|
|
}
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = decode_attrib(msg, &a)) != 0) {
|
2020-10-18 13:32:01 +02:00
|
|
|
error_fr(r, "decode_attrib");
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
|
|
|
return NULL;
|
|
|
|
}
|
2022-01-01 02:55:30 +01:00
|
|
|
debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o",
|
2021-08-07 02:09:57 +02:00
|
|
|
type, id, a.flags, a.perm);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return &a;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2008-05-19 06:53:33 +02:00
|
|
|
static int
|
2010-09-24 14:15:11 +02:00
|
|
|
get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
|
|
|
|
u_int expected_id, int quiet)
|
2008-05-19 06:53:33 +02:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
u_char type;
|
|
|
|
u_int id;
|
|
|
|
u_int64_t flag;
|
|
|
|
int r;
|
2008-05-19 06:53:33 +02:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
get_msg(conn, msg);
|
2008-05-19 06:53:33 +02:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2008-05-19 06:53:33 +02:00
|
|
|
|
|
|
|
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) {
|
2015-01-14 14:54:13 +01:00
|
|
|
u_int status;
|
2008-05-19 06:53:33 +02:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse status");
|
2008-05-19 06:53:33 +02:00
|
|
|
if (quiet)
|
2022-01-17 22:41:04 +01:00
|
|
|
debug("remote statvfs: %s", fx2txt(status));
|
2008-05-19 06:53:33 +02:00
|
|
|
else
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote statvfs: %s", fx2txt(status));
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2008-05-19 06:53:33 +02:00
|
|
|
return -1;
|
|
|
|
} else if (type != SSH2_FXP_EXTENDED_REPLY) {
|
|
|
|
fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
|
|
|
|
SSH2_FXP_EXTENDED_REPLY, type);
|
|
|
|
}
|
|
|
|
|
2014-02-04 01:18:20 +01:00
|
|
|
memset(st, 0, sizeof(*st));
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_files)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_favail)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &flag)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &st->f_namemax)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse statvfs");
|
2008-05-19 06:53:33 +02:00
|
|
|
|
|
|
|
st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
|
|
|
|
st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2008-05-19 06:53:33 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
struct sftp_conn *
|
2010-09-24 14:15:11 +02:00
|
|
|
do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
|
|
|
|
u_int64_t limit_kbps)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
u_char type;
|
|
|
|
struct sshbuf *msg;
|
2002-02-13 04:10:32 +01:00
|
|
|
struct sftp_conn *ret;
|
2015-01-14 14:54:13 +01:00
|
|
|
int r;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2013-08-20 18:42:12 +02:00
|
|
|
ret = xcalloc(1, sizeof(*ret));
|
|
|
|
ret->msg_id = 1;
|
2010-09-24 14:15:11 +02:00
|
|
|
ret->fd_in = fd_in;
|
|
|
|
ret->fd_out = fd_out;
|
2021-04-01 00:16:34 +02:00
|
|
|
ret->download_buflen = ret->upload_buflen =
|
|
|
|
transfer_buflen ? transfer_buflen : DEFAULT_COPY_BUFLEN;
|
|
|
|
ret->num_requests =
|
|
|
|
num_requests ? num_requests : DEFAULT_NUM_REQUESTS;
|
2010-09-24 14:15:11 +02:00
|
|
|
ret->exts = 0;
|
|
|
|
ret->limit_kbps = 0;
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(ret, msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2017-11-28 22:10:22 +01:00
|
|
|
get_msg_extended(ret, msg, 1);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2001-02-05 13:42:17 +01:00
|
|
|
/* Expecting a VERSION reply */
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse type");
|
2015-01-14 14:54:13 +01:00
|
|
|
if (type != SSH2_FXP_VERSION) {
|
2002-06-23 23:27:18 +02:00
|
|
|
error("Invalid packet back from SSH2_FXP_INIT (type %u)",
|
2001-02-04 13:20:18 +01:00
|
|
|
type);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2015-05-23 16:28:37 +02:00
|
|
|
free(ret);
|
2002-02-13 04:10:32 +01:00
|
|
|
return(NULL);
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &ret->version)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse version");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
debug2("Remote version: %u", ret->version);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
/* Check for extensions */
|
2015-01-14 14:54:13 +01:00
|
|
|
while (sshbuf_len(msg) > 0) {
|
|
|
|
char *name;
|
|
|
|
u_char *value;
|
|
|
|
size_t vlen;
|
2008-06-12 23:01:29 +02:00
|
|
|
int known = 0;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 ||
|
|
|
|
(r = sshbuf_get_string(msg, &value, &vlen)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse extension");
|
2008-05-19 06:53:33 +02:00
|
|
|
if (strcmp(name, "posix-rename@openssh.com") == 0 &&
|
2015-01-14 14:54:13 +01:00
|
|
|
strcmp((char *)value, "1") == 0) {
|
2010-09-24 14:15:11 +02:00
|
|
|
ret->exts |= SFTP_EXT_POSIX_RENAME;
|
2008-06-12 23:01:29 +02:00
|
|
|
known = 1;
|
|
|
|
} else if (strcmp(name, "statvfs@openssh.com") == 0 &&
|
2015-01-14 14:54:13 +01:00
|
|
|
strcmp((char *)value, "2") == 0) {
|
2010-09-24 14:15:11 +02:00
|
|
|
ret->exts |= SFTP_EXT_STATVFS;
|
2008-06-12 23:01:29 +02:00
|
|
|
known = 1;
|
2010-12-04 23:02:47 +01:00
|
|
|
} else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
|
2015-01-14 14:54:13 +01:00
|
|
|
strcmp((char *)value, "2") == 0) {
|
2010-09-24 14:15:11 +02:00
|
|
|
ret->exts |= SFTP_EXT_FSTATVFS;
|
2008-06-12 23:01:29 +02:00
|
|
|
known = 1;
|
2010-12-04 23:02:47 +01:00
|
|
|
} else if (strcmp(name, "hardlink@openssh.com") == 0 &&
|
2015-01-14 14:54:13 +01:00
|
|
|
strcmp((char *)value, "1") == 0) {
|
2010-12-04 23:02:47 +01:00
|
|
|
ret->exts |= SFTP_EXT_HARDLINK;
|
|
|
|
known = 1;
|
2015-01-14 14:54:13 +01:00
|
|
|
} else if (strcmp(name, "fsync@openssh.com") == 0 &&
|
|
|
|
strcmp((char *)value, "1") == 0) {
|
|
|
|
ret->exts |= SFTP_EXT_FSYNC;
|
|
|
|
known = 1;
|
2019-01-17 00:23:45 +01:00
|
|
|
} else if (strcmp(name, "lsetstat@openssh.com") == 0 &&
|
|
|
|
strcmp((char *)value, "1") == 0) {
|
|
|
|
ret->exts |= SFTP_EXT_LSETSTAT;
|
|
|
|
known = 1;
|
2021-04-01 00:16:34 +02:00
|
|
|
} else if (strcmp(name, "limits@openssh.com") == 0 &&
|
|
|
|
strcmp((char *)value, "1") == 0) {
|
|
|
|
ret->exts |= SFTP_EXT_LIMITS;
|
|
|
|
known = 1;
|
2021-08-10 01:47:44 +02:00
|
|
|
} else if (strcmp(name, "expand-path@openssh.com") == 0 &&
|
|
|
|
strcmp((char *)value, "1") == 0) {
|
|
|
|
ret->exts |= SFTP_EXT_PATH_EXPAND;
|
|
|
|
known = 1;
|
2008-06-12 23:01:29 +02:00
|
|
|
}
|
|
|
|
if (known) {
|
|
|
|
debug2("Server supports extension \"%s\" revision %s",
|
|
|
|
name, value);
|
|
|
|
} else {
|
|
|
|
debug2("Unrecognised server extension \"%s\"", name);
|
|
|
|
}
|
2013-06-01 23:31:17 +02:00
|
|
|
free(name);
|
|
|
|
free(value);
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2021-04-01 00:16:34 +02:00
|
|
|
/* Query the server for its limits */
|
|
|
|
if (ret->exts & SFTP_EXT_LIMITS) {
|
|
|
|
struct sftp_limits limits;
|
|
|
|
if (do_limits(ret, &limits) != 0)
|
|
|
|
fatal_f("limits failed");
|
|
|
|
|
|
|
|
/* If the caller did not specify, find a good value */
|
|
|
|
if (transfer_buflen == 0) {
|
|
|
|
ret->download_buflen = limits.read_length;
|
|
|
|
ret->upload_buflen = limits.write_length;
|
|
|
|
debug("Using server download size %u", ret->download_buflen);
|
|
|
|
debug("Using server upload size %u", ret->upload_buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Use the server limit to scale down our value only */
|
|
|
|
if (num_requests == 0 && limits.open_handles) {
|
|
|
|
ret->num_requests =
|
|
|
|
MINIMUM(DEFAULT_NUM_REQUESTS, limits.open_handles);
|
|
|
|
debug("Server handle limit %llu; using %u",
|
2021-04-03 08:18:40 +02:00
|
|
|
(unsigned long long)limits.open_handles,
|
|
|
|
ret->num_requests);
|
2021-04-01 00:16:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
/* Some filexfer v.0 servers don't support large packets */
|
2021-04-01 00:16:34 +02:00
|
|
|
if (ret->version == 0) {
|
|
|
|
ret->download_buflen = MINIMUM(ret->download_buflen, 20480);
|
|
|
|
ret->upload_buflen = MINIMUM(ret->upload_buflen, 20480);
|
|
|
|
}
|
2002-02-13 04:10:32 +01:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
ret->limit_kbps = limit_kbps;
|
|
|
|
if (ret->limit_kbps > 0) {
|
|
|
|
bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
|
2021-04-01 00:16:34 +02:00
|
|
|
ret->download_buflen);
|
2010-09-24 14:15:11 +02:00
|
|
|
bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
|
2021-04-01 00:16:34 +02:00
|
|
|
ret->upload_buflen);
|
2010-09-24 14:15:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2002-02-13 04:10:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
u_int
|
|
|
|
sftp_proto_version(struct sftp_conn *conn)
|
|
|
|
{
|
2010-09-24 14:15:11 +02:00
|
|
|
return conn->version;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2021-04-01 00:16:34 +02:00
|
|
|
int
|
|
|
|
do_limits(struct sftp_conn *conn, struct sftp_limits *limits)
|
|
|
|
{
|
|
|
|
u_int id, msg_id;
|
|
|
|
u_char type;
|
|
|
|
struct sshbuf *msg;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if ((conn->exts & SFTP_EXT_LIMITS) == 0) {
|
|
|
|
error("Server does not support limits@openssh.com extension");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
|
|
fatal_f("sshbuf_new failed");
|
|
|
|
|
|
|
|
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, "limits@openssh.com")) != 0)
|
|
|
|
fatal_fr(r, "compose");
|
|
|
|
send_msg(conn, msg);
|
|
|
|
debug3("Sent message limits@openssh.com I:%u", id);
|
|
|
|
|
|
|
|
get_msg(conn, msg);
|
|
|
|
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &msg_id)) != 0)
|
|
|
|
fatal_fr(r, "parse");
|
|
|
|
|
|
|
|
debug3("Received limits reply T:%u I:%u", type, msg_id);
|
|
|
|
if (id != msg_id)
|
|
|
|
fatal("ID mismatch (%u != %u)", msg_id, id);
|
|
|
|
if (type != SSH2_FXP_EXTENDED_REPLY) {
|
2021-06-06 05:17:02 +02:00
|
|
|
debug_f("expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
|
2021-04-03 08:18:40 +02:00
|
|
|
SSH2_FXP_EXTENDED_REPLY, type);
|
2021-06-06 05:17:02 +02:00
|
|
|
/* Disable the limits extension */
|
|
|
|
conn->exts &= ~SFTP_EXT_LIMITS;
|
|
|
|
sshbuf_free(msg);
|
|
|
|
return 0;
|
2021-04-01 00:16:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
memset(limits, 0, sizeof(*limits));
|
|
|
|
if ((r = sshbuf_get_u64(msg, &limits->packet_length)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &limits->read_length)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &limits->write_length)) != 0 ||
|
|
|
|
(r = sshbuf_get_u64(msg, &limits->open_handles)) != 0)
|
|
|
|
fatal_fr(r, "parse limits");
|
|
|
|
|
|
|
|
sshbuf_free(msg);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
|
|
|
u_int id, status;
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
int r;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, handle, handle_len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2002-06-23 23:27:18 +02:00
|
|
|
debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
status = get_status(conn, id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("close remote: %s", fx2txt(status));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2001-03-14 00:27:09 +01:00
|
|
|
|
2001-06-25 07:01:22 +02:00
|
|
|
static int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
|
2001-03-14 00:27:09 +01:00
|
|
|
SFTP_DIRENT ***dir)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
u_int count, id, i, expected_id, ents = 0;
|
|
|
|
size_t handle_len;
|
2016-05-02 10:49:03 +02:00
|
|
|
u_char type, *handle;
|
2013-12-07 00:31:08 +01:00
|
|
|
int status = SSH2_FX_FAILURE;
|
2015-01-14 14:54:13 +01:00
|
|
|
int r;
|
2013-12-07 00:31:08 +01:00
|
|
|
|
|
|
|
if (dir)
|
|
|
|
*dir = NULL;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, path)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose OPENDIR");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
handle = get_handle(conn, id, &handle_len,
|
2009-10-06 23:24:19 +02:00
|
|
|
"remote readdir(\"%s\")", path);
|
2011-09-22 13:42:45 +02:00
|
|
|
if (handle == NULL) {
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2010-09-24 14:15:11 +02:00
|
|
|
return -1;
|
2011-09-22 13:42:45 +02:00
|
|
|
}
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2001-03-14 00:27:09 +01:00
|
|
|
if (dir) {
|
|
|
|
ents = 0;
|
2013-11-08 02:19:55 +01:00
|
|
|
*dir = xcalloc(1, sizeof(**dir));
|
2001-03-14 00:27:09 +01:00
|
|
|
(*dir)[0] = NULL;
|
|
|
|
}
|
|
|
|
|
2004-05-24 02:12:19 +02:00
|
|
|
for (; !interrupted;) {
|
2002-02-13 04:10:32 +01:00
|
|
|
id = expected_id = conn->msg_id++;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2002-06-23 23:27:18 +02:00
|
|
|
debug3("Sending SSH2_FXP_READDIR I:%u", id);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_reset(msg);
|
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, handle, handle_len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose READDIR");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_reset(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
get_msg(conn, msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2002-06-23 23:27:18 +02:00
|
|
|
debug3("Received reply T:%u I:%u", type, id);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
if (id != expected_id)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
if (type == SSH2_FXP_STATUS) {
|
2015-01-14 14:54:13 +01:00
|
|
|
u_int rstatus;
|
|
|
|
|
|
|
|
if ((r = sshbuf_get_u32(msg, &rstatus)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse status");
|
2015-01-14 14:54:13 +01:00
|
|
|
debug3("Received SSH2_FXP_STATUS %d", rstatus);
|
|
|
|
if (rstatus == SSH2_FX_EOF)
|
2001-02-04 13:20:18 +01:00
|
|
|
break;
|
2015-01-14 14:54:13 +01:00
|
|
|
error("Couldn't read directory: %s", fx2txt(rstatus));
|
2013-12-07 00:31:08 +01:00
|
|
|
goto out;
|
2001-02-04 13:20:18 +01:00
|
|
|
} else if (type != SSH2_FXP_NAME)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
|
2001-02-04 13:20:18 +01:00
|
|
|
SSH2_FXP_NAME, type);
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &count)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse count");
|
2017-01-03 06:46:51 +01:00
|
|
|
if (count > SSHBUF_SIZE_MAX)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("nonsensical number of entries");
|
2001-02-09 14:40:03 +01:00
|
|
|
if (count == 0)
|
|
|
|
break;
|
|
|
|
debug3("Received %d SSH2_FXP_NAME responses", count);
|
2001-12-21 04:45:46 +01:00
|
|
|
for (i = 0; i < count; i++) {
|
2001-02-04 13:20:18 +01:00
|
|
|
char *filename, *longname;
|
2015-01-14 14:54:13 +01:00
|
|
|
Attrib a;
|
|
|
|
|
|
|
|
if ((r = sshbuf_get_cstring(msg, &filename,
|
|
|
|
NULL)) != 0 ||
|
|
|
|
(r = sshbuf_get_cstring(msg, &longname,
|
|
|
|
NULL)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse filenames");
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = decode_attrib(msg, &a)) != 0) {
|
2020-10-18 13:32:01 +02:00
|
|
|
error_fr(r, "couldn't decode attrib");
|
2015-01-14 14:54:13 +01:00
|
|
|
free(filename);
|
|
|
|
free(longname);
|
2019-10-04 06:31:59 +02:00
|
|
|
goto out;
|
2015-01-14 14:54:13 +01:00
|
|
|
}
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2013-10-15 03:05:58 +02:00
|
|
|
if (print_flag)
|
2016-05-26 01:48:45 +02:00
|
|
|
mprintf("%s\n", longname);
|
2001-03-14 00:27:09 +01:00
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
/*
|
|
|
|
* Directory entries should never contain '/'
|
|
|
|
* These can be used to attack recursive ops
|
|
|
|
* (e.g. send '../../../../etc/passwd')
|
|
|
|
*/
|
2017-03-20 01:53:34 +01:00
|
|
|
if (strpbrk(filename, SFTP_DIRECTORY_CHARS) != NULL) {
|
2009-10-06 23:37:48 +02:00
|
|
|
error("Server sent suspect path \"%s\" "
|
|
|
|
"during readdir of \"%s\"", filename, path);
|
2013-12-07 00:31:08 +01:00
|
|
|
} else if (dir) {
|
2015-04-24 03:36:00 +02:00
|
|
|
*dir = xreallocarray(*dir, ents + 2, sizeof(**dir));
|
2013-11-08 02:19:55 +01:00
|
|
|
(*dir)[ents] = xcalloc(1, sizeof(***dir));
|
2001-03-14 00:27:09 +01:00
|
|
|
(*dir)[ents]->filename = xstrdup(filename);
|
|
|
|
(*dir)[ents]->longname = xstrdup(longname);
|
2015-01-14 14:54:13 +01:00
|
|
|
memcpy(&(*dir)[ents]->a, &a, sizeof(a));
|
2001-03-14 00:27:09 +01:00
|
|
|
(*dir)[++ents] = NULL;
|
|
|
|
}
|
2013-06-01 23:31:17 +02:00
|
|
|
free(filename);
|
|
|
|
free(longname);
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
}
|
2013-12-07 00:31:08 +01:00
|
|
|
status = 0;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2013-12-07 00:31:08 +01:00
|
|
|
out:
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2002-02-13 04:10:32 +01:00
|
|
|
do_close(conn, handle, handle_len);
|
2013-06-01 23:31:17 +02:00
|
|
|
free(handle);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2013-12-07 00:31:08 +01:00
|
|
|
if (status != 0 && dir != NULL) {
|
|
|
|
/* Don't return results on error */
|
|
|
|
free_sftp_dirents(*dir);
|
|
|
|
*dir = NULL;
|
|
|
|
} else if (interrupted && dir != NULL && *dir != NULL) {
|
|
|
|
/* Don't return partial matches on interrupt */
|
2004-05-24 02:12:19 +02:00
|
|
|
free_sftp_dirents(*dir);
|
2013-11-08 02:19:55 +01:00
|
|
|
*dir = xcalloc(1, sizeof(**dir));
|
2004-05-24 02:12:19 +02:00
|
|
|
**dir = NULL;
|
|
|
|
}
|
|
|
|
|
2018-05-25 06:25:46 +02:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2001-03-14 00:27:09 +01:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
|
2001-03-14 00:27:09 +01:00
|
|
|
{
|
2002-02-13 04:10:32 +01:00
|
|
|
return(do_lsreaddir(conn, path, 0, dir));
|
2001-03-14 00:27:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void free_sftp_dirents(SFTP_DIRENT **s)
|
|
|
|
{
|
|
|
|
int i;
|
2001-12-21 04:45:46 +01:00
|
|
|
|
2013-12-07 00:31:08 +01:00
|
|
|
if (s == NULL)
|
|
|
|
return;
|
2001-12-21 04:45:46 +01:00
|
|
|
for (i = 0; s[i]; i++) {
|
2013-06-01 23:31:17 +02:00
|
|
|
free(s[i]->filename);
|
|
|
|
free(s[i]->longname);
|
|
|
|
free(s[i]);
|
2001-03-14 00:27:09 +01:00
|
|
|
}
|
2013-06-01 23:31:17 +02:00
|
|
|
free(s);
|
2001-03-14 00:27:09 +01:00
|
|
|
}
|
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_rm(struct sftp_conn *conn, const char *path)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
|
|
|
u_int status, id;
|
|
|
|
|
|
|
|
debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
|
|
|
|
status = get_status(conn, id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote delete %s: %s", path, fx2txt(status));
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
|
|
|
u_int status, id;
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_MKDIR \"%s\"", path);
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
|
2001-02-04 13:20:18 +01:00
|
|
|
strlen(path), a);
|
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
status = get_status(conn, id);
|
2013-10-15 03:05:58 +02:00
|
|
|
if (status != SSH2_FX_OK && print_flag)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote mkdir \"%s\": %s", path, fx2txt(status));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_rmdir(struct sftp_conn *conn, const char *path)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
|
|
|
u_int status, id;
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_RMDIR \"%s\"", path);
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_request(conn, id, SSH2_FXP_RMDIR, path,
|
2002-02-13 04:10:32 +01:00
|
|
|
strlen(path));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
status = get_status(conn, id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote rmdir \"%s\": %s", path, fx2txt(status));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Attrib *
|
2015-01-14 14:54:13 +01:00
|
|
|
do_stat(struct sftp_conn *conn, const char *path, int quiet)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
|
|
|
u_int id;
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_STAT \"%s\"", path);
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_request(conn, id,
|
2002-03-22 03:54:23 +01:00
|
|
|
conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
|
2002-02-13 04:10:32 +01:00
|
|
|
path, strlen(path));
|
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
return(get_decode_stat(conn, id, quiet));
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Attrib *
|
2015-01-14 14:54:13 +01:00
|
|
|
do_lstat(struct sftp_conn *conn, const char *path, int quiet)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
|
|
|
u_int id;
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
if (conn->version == 0) {
|
|
|
|
if (quiet)
|
|
|
|
debug("Server version does not support lstat operation");
|
|
|
|
else
|
2003-04-09 12:59:48 +02:00
|
|
|
logit("Server version does not support lstat operation");
|
2002-04-02 23:00:31 +02:00
|
|
|
return(do_stat(conn, path, quiet));
|
2002-02-13 04:10:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
id = conn->msg_id++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_request(conn, id, SSH2_FXP_LSTAT, path,
|
2002-02-13 04:10:32 +01:00
|
|
|
strlen(path));
|
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
return(get_decode_stat(conn, id, quiet));
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2008-02-10 12:20:44 +01:00
|
|
|
#ifdef notyet
|
2001-02-04 13:20:18 +01:00
|
|
|
Attrib *
|
2015-01-14 14:54:13 +01:00
|
|
|
do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
|
|
|
|
int quiet)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
|
|
|
u_int id;
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_FSTAT \"%s\"");
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
|
2002-02-13 04:10:32 +01:00
|
|
|
handle_len);
|
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
return(get_decode_stat(conn, id, quiet));
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
2008-02-10 12:20:44 +01:00
|
|
|
#endif
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
|
|
|
u_int status, id;
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_SETSTAT \"%s\"", path);
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
|
2001-02-04 13:20:18 +01:00
|
|
|
strlen(path), a);
|
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
status = get_status(conn, id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote setstat \"%s\": %s", path, fx2txt(status));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
|
2001-02-04 13:20:18 +01:00
|
|
|
Attrib *a)
|
|
|
|
{
|
|
|
|
u_int status, id;
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_FSETSTAT");
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
|
2001-02-04 13:20:18 +01:00
|
|
|
handle_len, a);
|
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
status = get_status(conn, id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote fsetstat: %s", fx2txt(status));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2021-08-10 01:47:44 +02:00
|
|
|
/* Implements both the realpath and expand-path operations */
|
|
|
|
static char *
|
|
|
|
do_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
u_int expected_id, count, id;
|
2001-02-04 13:20:18 +01:00
|
|
|
char *filename, *longname;
|
2015-01-14 14:54:13 +01:00
|
|
|
Attrib a;
|
|
|
|
u_char type;
|
|
|
|
int r;
|
2021-08-10 01:47:44 +02:00
|
|
|
const char *what = "SSH2_FXP_REALPATH";
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2021-08-10 01:47:44 +02:00
|
|
|
if (expand)
|
|
|
|
what = "expand-path@openssh.com";
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2021-08-10 01:47:44 +02:00
|
|
|
expected_id = id = conn->msg_id++;
|
|
|
|
if (expand) {
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_EXTENDED(expand-path@openssh.com) "
|
|
|
|
"\"%s\"", path);
|
2021-08-10 01:47:44 +02:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg,
|
|
|
|
"expand-path@openssh.com")) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, path)) != 0)
|
|
|
|
fatal_fr(r, "compose %s", what);
|
|
|
|
send_msg(conn, msg);
|
|
|
|
} else {
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_REALPATH \"%s\"", path);
|
2021-08-10 01:47:44 +02:00
|
|
|
send_string_request(conn, id, SSH2_FXP_REALPATH,
|
|
|
|
path, strlen(path));
|
|
|
|
}
|
2015-01-14 14:54:13 +01:00
|
|
|
get_msg(conn, msg);
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
if (id != expected_id)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
if (type == SSH2_FXP_STATUS) {
|
2015-01-14 14:54:13 +01:00
|
|
|
u_int status;
|
2022-01-08 08:37:32 +01:00
|
|
|
char *errmsg;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2022-01-08 08:37:32 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0 ||
|
|
|
|
(r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse status");
|
2022-01-08 08:37:32 +01:00
|
|
|
error("%s %s: %s", expand ? "expand" : "realpath",
|
|
|
|
path, *errmsg == '\0' ? fx2txt(status) : errmsg);
|
|
|
|
free(errmsg);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2010-06-26 01:38:23 +02:00
|
|
|
return NULL;
|
2001-02-04 13:20:18 +01:00
|
|
|
} else if (type != SSH2_FXP_NAME)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
|
2001-02-04 13:20:18 +01:00
|
|
|
SSH2_FXP_NAME, type);
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &count)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse count");
|
2001-02-04 13:20:18 +01:00
|
|
|
if (count != 1)
|
2021-08-10 01:47:44 +02:00
|
|
|
fatal("Got multiple names (%d) from %s", count, what);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
|
|
|
|
(r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
|
|
|
|
(r = decode_attrib(msg, &a)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse filename/attrib");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2021-08-10 01:47:44 +02:00
|
|
|
debug3("%s %s -> %s", what, path, filename);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2013-06-01 23:31:17 +02:00
|
|
|
free(longname);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
return(filename);
|
|
|
|
}
|
|
|
|
|
2021-08-10 01:47:44 +02:00
|
|
|
char *
|
|
|
|
do_realpath(struct sftp_conn *conn, const char *path)
|
|
|
|
{
|
|
|
|
return do_realpath_expand(conn, path, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
can_expand_path(struct sftp_conn *conn)
|
|
|
|
{
|
|
|
|
return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
|
|
do_expand_path(struct sftp_conn *conn, const char *path)
|
|
|
|
{
|
|
|
|
if (!can_expand_path(conn)) {
|
|
|
|
debug3_f("no server support, fallback to realpath");
|
|
|
|
return do_realpath_expand(conn, path, 0);
|
|
|
|
}
|
|
|
|
return do_realpath_expand(conn, path, 1);
|
|
|
|
}
|
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
|
2013-08-20 18:41:15 +02:00
|
|
|
int force_legacy)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
2001-02-04 13:20:18 +01:00
|
|
|
u_int status, id;
|
2015-01-14 14:54:13 +01:00
|
|
|
int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
/* Send rename request */
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2013-08-20 18:41:15 +02:00
|
|
|
if (use_ext) {
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_EXTENDED(posix-rename@openssh.com) "
|
|
|
|
"\"%s\" to \"%s\"", oldpath, newpath);
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg,
|
|
|
|
"posix-rename@openssh.com")) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose posix-rename");
|
2008-03-27 00:59:57 +01:00
|
|
|
} else {
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_RENAME \"%s\" to \"%s\"",
|
|
|
|
oldpath, newpath);
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose rename");
|
2008-03-27 00:59:57 +01:00
|
|
|
}
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, newpath)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose paths");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2008-03-27 00:59:57 +01:00
|
|
|
debug3("Sent message %s \"%s\" -> \"%s\"",
|
2015-01-14 14:54:13 +01:00
|
|
|
use_ext ? "posix-rename@openssh.com" :
|
|
|
|
"SSH2_FXP_RENAME", oldpath, newpath);
|
|
|
|
sshbuf_free(msg);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
status = get_status(conn, id);
|
2001-02-04 13:20:18 +01:00
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote rename \"%s\" to \"%s\": %s", oldpath,
|
2002-02-13 04:10:32 +01:00
|
|
|
newpath, fx2txt(status));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2010-12-04 23:02:47 +01:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
|
2010-12-04 23:02:47 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
2010-12-04 23:02:47 +01:00
|
|
|
u_int status, id;
|
2015-01-14 14:54:13 +01:00
|
|
|
int r;
|
2010-12-04 23:02:47 +01:00
|
|
|
|
|
|
|
if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
|
|
|
|
error("Server does not support hardlink@openssh.com extension");
|
|
|
|
return -1;
|
|
|
|
}
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_EXTENDED(hardlink@openssh.com) "
|
|
|
|
"\"%s\" to \"%s\"", oldpath, newpath);
|
2010-12-04 23:02:47 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2011-09-22 13:41:05 +02:00
|
|
|
|
|
|
|
/* Send link request */
|
|
|
|
id = conn->msg_id++;
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, newpath)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2010-12-04 23:02:47 +01:00
|
|
|
debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
|
2021-04-03 08:18:40 +02:00
|
|
|
oldpath, newpath);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2010-12-04 23:02:47 +01:00
|
|
|
|
|
|
|
status = get_status(conn, id);
|
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote link \"%s\" to \"%s\": %s", oldpath,
|
2010-12-04 23:02:47 +01:00
|
|
|
newpath, fx2txt(status));
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2010-12-04 23:02:47 +01:00
|
|
|
}
|
|
|
|
|
2001-03-08 00:08:49 +01:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
|
2001-03-08 00:08:49 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
2001-03-08 00:08:49 +01:00
|
|
|
u_int status, id;
|
2015-01-14 14:54:13 +01:00
|
|
|
int r;
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
if (conn->version < 3) {
|
|
|
|
error("This server does not support the symlink operation");
|
|
|
|
return(SSH2_FX_OP_UNSUPPORTED);
|
|
|
|
}
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_SYMLINK \"%s\" to \"%s\"", oldpath, newpath);
|
2002-02-13 04:10:32 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2004-04-19 14:10:52 +02:00
|
|
|
/* Send symlink request */
|
2002-02-13 04:10:32 +01:00
|
|
|
id = conn->msg_id++;
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, newpath)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2001-03-08 00:08:49 +01:00
|
|
|
debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
|
|
|
|
newpath);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
status = get_status(conn, id);
|
2001-03-08 00:08:49 +01:00
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote symlink file \"%s\" to \"%s\": %s", oldpath,
|
2002-02-13 04:10:32 +01:00
|
|
|
newpath, fx2txt(status));
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-03-08 00:08:49 +01:00
|
|
|
}
|
|
|
|
|
2013-10-17 02:48:52 +02:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
|
2013-10-17 02:48:52 +02:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
2013-10-17 02:48:52 +02:00
|
|
|
u_int status, id;
|
2015-01-14 14:54:13 +01:00
|
|
|
int r;
|
2013-10-17 02:48:52 +02:00
|
|
|
|
|
|
|
/* Silently return if the extension is not supported */
|
|
|
|
if ((conn->exts & SFTP_EXT_FSYNC) == 0)
|
|
|
|
return -1;
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_EXTENDED(fsync@openssh.com)");
|
2013-10-17 02:48:52 +02:00
|
|
|
|
|
|
|
/* Send fsync request */
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2013-10-17 02:48:52 +02:00
|
|
|
id = conn->msg_id++;
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, handle, handle_len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2013-10-17 02:48:52 +02:00
|
|
|
debug3("Sent message fsync@openssh.com I:%u", id);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2013-10-17 02:48:52 +02:00
|
|
|
|
|
|
|
status = get_status(conn, id);
|
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote fsync: %s", fx2txt(status));
|
2013-10-17 02:48:52 +02:00
|
|
|
|
2018-05-25 06:25:46 +02:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2013-10-17 02:48:52 +02:00
|
|
|
}
|
|
|
|
|
2008-02-10 12:20:44 +01:00
|
|
|
#ifdef notyet
|
2001-03-08 00:08:49 +01:00
|
|
|
char *
|
2015-01-14 14:54:13 +01:00
|
|
|
do_readlink(struct sftp_conn *conn, const char *path)
|
2001-03-08 00:08:49 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
u_int expected_id, count, id;
|
2001-03-08 00:08:49 +01:00
|
|
|
char *filename, *longname;
|
2015-01-14 14:54:13 +01:00
|
|
|
Attrib a;
|
|
|
|
u_char type;
|
|
|
|
int r;
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_READLINK \"%s\"", path);
|
|
|
|
|
2002-02-13 04:10:32 +01:00
|
|
|
expected_id = id = conn->msg_id++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
get_msg(conn, msg);
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2001-03-08 00:08:49 +01:00
|
|
|
|
|
|
|
if (id != expected_id)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("ID mismatch (%u != %u)", id, expected_id);
|
2001-03-08 00:08:49 +01:00
|
|
|
|
|
|
|
if (type == SSH2_FXP_STATUS) {
|
2015-01-14 14:54:13 +01:00
|
|
|
u_int status;
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse status");
|
2001-03-08 00:08:49 +01:00
|
|
|
error("Couldn't readlink: %s", fx2txt(status));
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-03-08 00:08:49 +01:00
|
|
|
return(NULL);
|
|
|
|
} else if (type != SSH2_FXP_NAME)
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
|
2001-03-08 00:08:49 +01:00
|
|
|
SSH2_FXP_NAME, type);
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &count)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse count");
|
2001-03-08 00:08:49 +01:00
|
|
|
if (count != 1)
|
|
|
|
fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
|
|
|
|
(r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
|
|
|
|
(r = decode_attrib(msg, &a)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse filenames/attrib");
|
2001-03-08 00:08:49 +01:00
|
|
|
|
|
|
|
debug3("SSH_FXP_READLINK %s -> %s", path, filename);
|
|
|
|
|
2013-06-01 23:31:17 +02:00
|
|
|
free(longname);
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return filename;
|
2001-03-08 00:08:49 +01:00
|
|
|
}
|
2008-02-10 12:20:44 +01:00
|
|
|
#endif
|
2001-03-08 00:08:49 +01:00
|
|
|
|
2008-05-19 06:53:33 +02:00
|
|
|
int
|
2008-06-09 14:49:36 +02:00
|
|
|
do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
|
2008-05-19 06:53:33 +02:00
|
|
|
int quiet)
|
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
2008-05-19 06:53:33 +02:00
|
|
|
u_int id;
|
2015-01-14 14:54:13 +01:00
|
|
|
int r;
|
2008-05-19 06:53:33 +02:00
|
|
|
|
|
|
|
if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
|
|
|
|
error("Server does not support statvfs@openssh.com extension");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_EXTENDED(statvfs@openssh.com) \"%s\"", path);
|
|
|
|
|
2008-05-19 06:53:33 +02:00
|
|
|
id = conn->msg_id++;
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, path)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
|
|
|
sshbuf_free(msg);
|
2008-05-19 06:53:33 +02:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
return get_decode_statvfs(conn, st, id, quiet);
|
2008-05-19 06:53:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef notyet
|
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
|
2008-06-09 14:49:36 +02:00
|
|
|
struct sftp_statvfs *st, int quiet)
|
2008-05-19 06:53:33 +02:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
2008-05-19 06:53:33 +02:00
|
|
|
u_int id;
|
|
|
|
|
|
|
|
if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
|
|
|
|
error("Server does not support fstatvfs@openssh.com extension");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_EXTENDED(fstatvfs@openssh.com)");
|
|
|
|
|
2008-05-19 06:53:33 +02:00
|
|
|
id = conn->msg_id++;
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, handle, handle_len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
|
|
|
sshbuf_free(msg);
|
2008-05-19 06:53:33 +02:00
|
|
|
|
2010-09-24 14:15:11 +02:00
|
|
|
return get_decode_statvfs(conn, st, id, quiet);
|
2008-05-19 06:53:33 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-01-17 00:23:45 +01:00
|
|
|
int
|
|
|
|
do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a)
|
|
|
|
{
|
|
|
|
struct sshbuf *msg;
|
|
|
|
u_int status, id;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if ((conn->exts & SFTP_EXT_LSETSTAT) == 0) {
|
|
|
|
error("Server does not support lsetstat@openssh.com extension");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_EXTENDED(lsetstat@openssh.com) \"%s\"", path);
|
|
|
|
|
2019-01-17 00:23:45 +01:00
|
|
|
id = conn->msg_id++;
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2019-01-17 00:23:45 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 ||
|
|
|
|
(r = sshbuf_put_cstring(msg, path)) != 0 ||
|
|
|
|
(r = encode_attrib(msg, a)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2019-01-17 00:23:45 +01:00
|
|
|
send_msg(conn, msg);
|
|
|
|
sshbuf_free(msg);
|
|
|
|
|
|
|
|
status = get_status(conn, id);
|
|
|
|
if (status != SSH2_FX_OK)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote lsetstat \"%s\": %s", path, fx2txt(status));
|
2019-01-17 00:23:45 +01:00
|
|
|
|
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
|
|
|
}
|
|
|
|
|
2002-02-13 04:03:56 +01:00
|
|
|
static void
|
2010-09-24 14:15:11 +02:00
|
|
|
send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
|
2015-01-14 14:54:13 +01:00
|
|
|
u_int len, const u_char *handle, u_int handle_len)
|
2002-02-13 04:03:56 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("sshbuf_new failed");
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, id)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, handle, handle_len)) != 0 ||
|
|
|
|
(r = sshbuf_put_u64(msg, offset)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
|
|
|
sshbuf_free(msg);
|
2002-03-22 03:54:23 +01:00
|
|
|
}
|
2002-02-13 04:03:56 +01:00
|
|
|
|
2021-08-07 02:01:29 +02:00
|
|
|
static int
|
|
|
|
send_open(struct sftp_conn *conn, const char *path, const char *tag,
|
|
|
|
u_int openmode, Attrib *a, u_char **handlep, size_t *handle_lenp)
|
|
|
|
{
|
|
|
|
Attrib junk;
|
|
|
|
u_char *handle;
|
|
|
|
size_t handle_len;
|
|
|
|
struct sshbuf *msg;
|
|
|
|
int r;
|
|
|
|
u_int id;
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2("Sending SSH2_FXP_OPEN \"%s\"", path);
|
|
|
|
|
2021-08-07 02:01:29 +02:00
|
|
|
*handlep = NULL;
|
|
|
|
*handle_lenp = 0;
|
|
|
|
|
|
|
|
if (a == NULL) {
|
|
|
|
attrib_clear(&junk); /* Send empty attributes */
|
|
|
|
a = &junk;
|
|
|
|
}
|
|
|
|
/* Send open request */
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
|
|
fatal_f("sshbuf_new failed");
|
|
|
|
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, path)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, openmode)) != 0 ||
|
|
|
|
(r = encode_attrib(msg, a)) != 0)
|
|
|
|
fatal_fr(r, "compose %s open", tag);
|
|
|
|
send_msg(conn, msg);
|
|
|
|
sshbuf_free(msg);
|
|
|
|
debug3("Sent %s message SSH2_FXP_OPEN I:%u P:%s M:0x%04x",
|
|
|
|
tag, id, path, openmode);
|
|
|
|
if ((handle = get_handle(conn, id, &handle_len,
|
2022-01-08 08:34:57 +01:00
|
|
|
"%s open \"%s\"", tag, path)) == NULL)
|
2021-08-07 02:01:29 +02:00
|
|
|
return -1;
|
|
|
|
/* success */
|
|
|
|
*handlep = handle;
|
|
|
|
*handle_lenp = handle_len;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-08-09 09:16:09 +02:00
|
|
|
static const char *
|
|
|
|
progress_meter_path(const char *path)
|
|
|
|
{
|
|
|
|
const char *progresspath;
|
|
|
|
|
|
|
|
if ((progresspath = strrchr(path, '/')) == NULL)
|
|
|
|
return path;
|
|
|
|
progresspath++;
|
|
|
|
if (*progresspath == '\0')
|
|
|
|
return path;
|
|
|
|
return progresspath;
|
|
|
|
}
|
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_download(struct sftp_conn *conn, const char *remote_path,
|
|
|
|
const char *local_path, Attrib *a, int preserve_flag, int resume_flag,
|
|
|
|
int fsync_flag)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
struct sshbuf *msg;
|
|
|
|
u_char *handle;
|
|
|
|
int local_fd = -1, write_error;
|
2019-07-12 05:56:21 +02:00
|
|
|
int read_error, write_errno, lmodified = 0, reordered = 0, r;
|
2013-07-25 03:56:52 +02:00
|
|
|
u_int64_t offset = 0, size, highwater;
|
2015-01-14 14:54:13 +01:00
|
|
|
u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK;
|
2003-01-10 11:43:24 +01:00
|
|
|
off_t progress_counter;
|
2015-01-14 14:54:13 +01:00
|
|
|
size_t handle_len;
|
2013-07-25 03:56:52 +02:00
|
|
|
struct stat st;
|
2021-08-07 02:07:18 +02:00
|
|
|
struct requests requests;
|
2002-02-13 04:03:56 +01:00
|
|
|
struct request *req;
|
2015-01-14 14:54:13 +01:00
|
|
|
u_char type;
|
2002-02-13 04:03:56 +01:00
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2_f("download remote \"%s\" to local \"%s\"",
|
|
|
|
remote_path, local_path);
|
|
|
|
|
2002-02-13 04:03:56 +01:00
|
|
|
TAILQ_INIT(&requests);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
|
|
|
|
return -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2008-06-29 14:46:35 +02:00
|
|
|
/* Do not preserve set[ug]id here, as we do not preserve ownership */
|
2001-02-04 13:20:18 +01:00
|
|
|
if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
|
2003-01-08 04:04:53 +01:00
|
|
|
mode = a->perm & 0777;
|
2001-02-04 13:20:18 +01:00
|
|
|
else
|
|
|
|
mode = 0666;
|
|
|
|
|
2001-03-17 01:34:46 +01:00
|
|
|
if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
|
2003-01-14 12:24:47 +01:00
|
|
|
(!S_ISREG(a->perm))) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("download %s: not a regular file", remote_path);
|
2001-03-17 01:34:46 +01:00
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
|
2002-02-13 04:03:56 +01:00
|
|
|
if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
|
|
|
|
size = a->size;
|
|
|
|
else
|
|
|
|
size = 0;
|
|
|
|
|
2021-04-01 00:16:34 +02:00
|
|
|
buflen = conn->download_buflen;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
|
|
|
/* Send open request */
|
2021-08-07 02:01:29 +02:00
|
|
|
if (send_open(conn, remote_path, "remote", SSH2_FXF_READ, NULL,
|
|
|
|
&handle, &handle_len) != 0)
|
|
|
|
return -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2013-10-15 03:05:58 +02:00
|
|
|
local_fd = open(local_path,
|
|
|
|
O_WRONLY | O_CREAT | (resume_flag ? 0 : O_TRUNC), mode | S_IWUSR);
|
2002-02-13 04:10:32 +01:00
|
|
|
if (local_fd == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("open local \"%s\": %s", local_path, strerror(errno));
|
2013-07-25 03:56:52 +02:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
offset = highwater = 0;
|
2013-10-15 03:05:58 +02:00
|
|
|
if (resume_flag) {
|
2013-07-25 03:56:52 +02:00
|
|
|
if (fstat(local_fd, &st) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("stat local \"%s\": %s",
|
2013-07-25 03:56:52 +02:00
|
|
|
local_path, strerror(errno));
|
|
|
|
goto fail;
|
|
|
|
}
|
2014-01-17 01:20:26 +01:00
|
|
|
if (st.st_size < 0) {
|
|
|
|
error("\"%s\" has negative size", local_path);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
if ((u_int64_t)st.st_size > size) {
|
2013-07-25 03:56:52 +02:00
|
|
|
error("Unable to resume download of \"%s\": "
|
|
|
|
"local file is larger than remote", local_path);
|
|
|
|
fail:
|
|
|
|
do_close(conn, handle, handle_len);
|
|
|
|
free(handle);
|
2013-12-05 00:26:32 +01:00
|
|
|
if (local_fd != -1)
|
|
|
|
close(local_fd);
|
2013-07-25 03:56:52 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
offset = highwater = st.st_size;
|
2002-02-13 04:10:32 +01:00
|
|
|
}
|
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
/* Read from remote and write to local */
|
2013-07-25 03:56:52 +02:00
|
|
|
write_error = read_error = write_errno = num_req = 0;
|
2002-02-13 04:03:56 +01:00
|
|
|
max_req = 1;
|
2013-07-25 03:56:52 +02:00
|
|
|
progress_counter = offset;
|
2003-01-10 11:43:24 +01:00
|
|
|
|
2021-08-09 09:16:09 +02:00
|
|
|
if (showprogress && size != 0) {
|
|
|
|
start_progress_meter(progress_meter_path(remote_path),
|
|
|
|
size, &progress_counter);
|
|
|
|
}
|
2003-01-10 11:43:24 +01:00
|
|
|
|
2021-08-07 02:01:29 +02:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
|
|
fatal_f("sshbuf_new failed");
|
|
|
|
|
2002-02-13 04:03:56 +01:00
|
|
|
while (num_req > 0 || max_req > 0) {
|
2015-01-14 14:54:13 +01:00
|
|
|
u_char *data;
|
|
|
|
size_t len;
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2004-05-24 02:12:19 +02:00
|
|
|
/*
|
2004-07-17 08:12:08 +02:00
|
|
|
* Simulate EOF on interrupt: stop sending new requests and
|
2004-05-24 02:12:19 +02:00
|
|
|
* allow outstanding requests to drain gracefully
|
|
|
|
*/
|
|
|
|
if (interrupted) {
|
|
|
|
if (num_req == 0) /* If we haven't started yet... */
|
|
|
|
break;
|
|
|
|
max_req = 0;
|
|
|
|
}
|
|
|
|
|
2002-02-13 04:03:56 +01:00
|
|
|
/* Send some more requests */
|
|
|
|
while (num_req < max_req) {
|
2002-03-22 03:54:23 +01:00
|
|
|
debug3("Request range %llu -> %llu (%d/%d)",
|
2002-03-22 02:00:57 +01:00
|
|
|
(unsigned long long)offset,
|
|
|
|
(unsigned long long)offset + buflen - 1,
|
|
|
|
num_req, max_req);
|
2021-08-07 02:14:17 +02:00
|
|
|
req = request_enqueue(&requests, conn->msg_id++,
|
|
|
|
buflen, offset);
|
2002-02-13 04:03:56 +01:00
|
|
|
offset += buflen;
|
|
|
|
num_req++;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_read_request(conn, req->id, req->offset,
|
2002-02-13 04:03:56 +01:00
|
|
|
req->len, handle, handle_len);
|
|
|
|
}
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_reset(msg);
|
|
|
|
get_msg(conn, msg);
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2002-06-23 23:27:18 +02:00
|
|
|
debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
|
2002-02-13 04:03:56 +01:00
|
|
|
|
|
|
|
/* Find the request in our queue */
|
2021-08-07 02:14:17 +02:00
|
|
|
if ((req = request_find(&requests, id)) == NULL)
|
2002-02-13 04:03:56 +01:00
|
|
|
fatal("Unexpected reply %u", id);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case SSH2_FXP_STATUS:
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse status");
|
2002-02-13 04:03:56 +01:00
|
|
|
if (status != SSH2_FX_EOF)
|
|
|
|
read_error = 1;
|
|
|
|
max_req = 0;
|
|
|
|
TAILQ_REMOVE(&requests, req, tq);
|
2013-06-01 23:31:17 +02:00
|
|
|
free(req);
|
2002-02-13 04:03:56 +01:00
|
|
|
num_req--;
|
|
|
|
break;
|
|
|
|
case SSH2_FXP_DATA:
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse data");
|
2002-03-22 02:03:15 +01:00
|
|
|
debug3("Received data %llu -> %llu",
|
2002-03-22 03:54:23 +01:00
|
|
|
(unsigned long long)req->offset,
|
2002-03-22 02:03:15 +01:00
|
|
|
(unsigned long long)req->offset + len - 1);
|
2002-02-13 04:03:56 +01:00
|
|
|
if (len > req->len)
|
|
|
|
fatal("Received more data than asked for "
|
2015-01-14 14:54:13 +01:00
|
|
|
"%zu > %zu", len, req->len);
|
2019-07-12 05:56:21 +02:00
|
|
|
lmodified = 1;
|
2002-02-13 04:03:56 +01:00
|
|
|
if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
|
2003-07-03 05:46:56 +02:00
|
|
|
atomicio(vwrite, local_fd, data, len) != len) &&
|
2002-02-13 04:03:56 +01:00
|
|
|
!write_error) {
|
|
|
|
write_errno = errno;
|
|
|
|
write_error = 1;
|
|
|
|
max_req = 0;
|
|
|
|
}
|
2013-07-25 03:56:52 +02:00
|
|
|
else if (!reordered && req->offset <= highwater)
|
|
|
|
highwater = req->offset + len;
|
|
|
|
else if (!reordered && req->offset > highwater)
|
|
|
|
reordered = 1;
|
2003-01-10 11:43:24 +01:00
|
|
|
progress_counter += len;
|
2013-06-01 23:31:17 +02:00
|
|
|
free(data);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2002-02-13 04:03:56 +01:00
|
|
|
if (len == req->len) {
|
|
|
|
TAILQ_REMOVE(&requests, req, tq);
|
2013-06-01 23:31:17 +02:00
|
|
|
free(req);
|
2002-02-13 04:03:56 +01:00
|
|
|
num_req--;
|
|
|
|
} else {
|
|
|
|
/* Resend the request for the missing data */
|
|
|
|
debug3("Short data block, re-requesting "
|
2002-03-22 02:03:15 +01:00
|
|
|
"%llu -> %llu (%2d)",
|
2002-03-22 03:54:23 +01:00
|
|
|
(unsigned long long)req->offset + len,
|
2002-03-22 02:05:27 +01:00
|
|
|
(unsigned long long)req->offset +
|
|
|
|
req->len - 1, num_req);
|
2002-02-13 04:10:32 +01:00
|
|
|
req->id = conn->msg_id++;
|
2002-02-13 04:03:56 +01:00
|
|
|
req->len -= len;
|
|
|
|
req->offset += len;
|
2010-09-24 14:15:11 +02:00
|
|
|
send_read_request(conn, req->id,
|
2002-02-13 04:10:32 +01:00
|
|
|
req->offset, req->len, handle, handle_len);
|
2002-02-13 04:03:56 +01:00
|
|
|
/* Reduce the request size */
|
|
|
|
if (len < buflen)
|
2016-09-12 03:22:38 +02:00
|
|
|
buflen = MAXIMUM(MIN_READ_SIZE, len);
|
2002-02-13 04:03:56 +01:00
|
|
|
}
|
|
|
|
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)",
|
2002-03-22 02:03:15 +01:00
|
|
|
(unsigned long long)offset,
|
|
|
|
num_req);
|
2002-02-13 04:03:56 +01:00
|
|
|
max_req = 1;
|
2020-05-15 05:57:33 +02:00
|
|
|
} else if (max_req < conn->num_requests) {
|
2002-02-13 04:03:56 +01:00
|
|
|
++max_req;
|
|
|
|
}
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
2002-02-13 04:03:56 +01:00
|
|
|
break;
|
|
|
|
default:
|
2002-06-23 23:27:18 +02:00
|
|
|
fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
|
2001-02-04 13:20:18 +01:00
|
|
|
SSH2_FXP_DATA, type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-01-10 11:43:24 +01:00
|
|
|
if (showprogress && size)
|
|
|
|
stop_progress_meter();
|
|
|
|
|
2002-02-13 04:03:56 +01:00
|
|
|
/* Sanity check */
|
|
|
|
if (TAILQ_FIRST(&requests) != NULL)
|
|
|
|
fatal("Transfer complete, but requests still in queue");
|
2013-07-25 03:56:52 +02:00
|
|
|
/* Truncate at highest contiguous point to avoid holes on interrupt */
|
|
|
|
if (read_error || write_error || interrupted) {
|
2013-10-15 03:05:58 +02:00
|
|
|
if (reordered && resume_flag) {
|
2013-07-25 03:56:52 +02:00
|
|
|
error("Unable to resume download of \"%s\": "
|
|
|
|
"server reordered requests", local_path);
|
|
|
|
}
|
|
|
|
debug("truncating at %llu", (unsigned long long)highwater);
|
2015-05-28 06:50:53 +02:00
|
|
|
if (ftruncate(local_fd, highwater) == -1)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("local ftruncate \"%s\": %s", local_path,
|
2015-05-28 06:50:53 +02:00
|
|
|
strerror(errno));
|
2013-07-25 03:56:52 +02:00
|
|
|
}
|
2002-02-13 04:03:56 +01:00
|
|
|
if (read_error) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("read remote \"%s\" : %s", remote_path, fx2txt(status));
|
2013-08-20 18:42:12 +02:00
|
|
|
status = -1;
|
2002-02-13 04:10:32 +01:00
|
|
|
do_close(conn, handle, handle_len);
|
2002-02-13 04:03:56 +01:00
|
|
|
} else if (write_error) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("write local \"%s\": %s", local_path,
|
2002-02-13 04:10:32 +01:00
|
|
|
strerror(write_errno));
|
2015-01-14 14:54:13 +01:00
|
|
|
status = SSH2_FX_FAILURE;
|
2002-02-13 04:10:32 +01:00
|
|
|
do_close(conn, handle, handle_len);
|
2002-02-13 04:03:56 +01:00
|
|
|
} else {
|
2015-01-14 14:54:13 +01:00
|
|
|
if (do_close(conn, handle, handle_len) != 0 || interrupted)
|
|
|
|
status = SSH2_FX_FAILURE;
|
|
|
|
else
|
|
|
|
status = SSH2_FX_OK;
|
2002-02-13 04:03:56 +01:00
|
|
|
/* Override umask and utimes if asked */
|
2001-02-17 17:47:47 +01:00
|
|
|
#ifdef HAVE_FCHMOD
|
2013-10-15 03:05:58 +02:00
|
|
|
if (preserve_flag && fchmod(local_fd, mode) == -1)
|
2003-11-21 13:48:55 +01:00
|
|
|
#else
|
2013-10-15 03:05:58 +02:00
|
|
|
if (preserve_flag && chmod(local_path, mode) == -1)
|
2001-02-17 17:47:47 +01:00
|
|
|
#endif /* HAVE_FCHMOD */
|
2022-01-17 22:41:04 +01:00
|
|
|
error("local chmod \"%s\": %s", local_path,
|
2002-12-23 03:06:19 +01:00
|
|
|
strerror(errno));
|
2013-10-15 03:05:58 +02:00
|
|
|
if (preserve_flag &&
|
|
|
|
(a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
|
2002-02-13 04:03:56 +01:00
|
|
|
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)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("local set times \"%s\": %s",
|
2002-12-23 03:06:19 +01:00
|
|
|
local_path, strerror(errno));
|
2002-02-13 04:03:56 +01:00
|
|
|
}
|
2019-07-12 05:56:21 +02:00
|
|
|
if (resume_flag && !lmodified)
|
|
|
|
logit("File \"%s\" was not modified", local_path);
|
|
|
|
else if (fsync_flag) {
|
2013-10-17 02:48:52 +02:00
|
|
|
debug("syncing \"%s\"", local_path);
|
|
|
|
if (fsync(local_fd) == -1)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("local sync \"%s\": %s",
|
2013-10-17 02:48:52 +02:00
|
|
|
local_path, strerror(errno));
|
|
|
|
}
|
2001-02-15 04:22:45 +01:00
|
|
|
}
|
2001-02-09 14:40:03 +01:00
|
|
|
close(local_fd);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2013-06-01 23:31:17 +02:00
|
|
|
free(handle);
|
2002-02-13 04:10:32 +01:00
|
|
|
|
2018-05-25 06:25:46 +02:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
static int
|
2015-01-14 14:54:13 +01:00
|
|
|
download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
|
|
|
|
int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
|
2021-08-07 02:12:09 +02:00
|
|
|
int resume_flag, int fsync_flag, int follow_link_flag)
|
2009-10-06 23:37:48 +02:00
|
|
|
{
|
|
|
|
int i, ret = 0;
|
|
|
|
SFTP_DIRENT **dir_entries;
|
2018-07-31 05:07:24 +02:00
|
|
|
char *filename, *new_src = NULL, *new_dst = NULL;
|
2020-11-20 04:16:56 +01:00
|
|
|
mode_t mode = 0777, tmpmode = mode;
|
2009-10-06 23:37:48 +02:00
|
|
|
|
|
|
|
if (depth >= MAX_DIR_DEPTH) {
|
|
|
|
error("Maximum directory depth exceeded: %d levels", depth);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst);
|
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
if (dirattrib == NULL &&
|
|
|
|
(dirattrib = do_stat(conn, src, 1)) == NULL) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("stat remote \"%s\" directory failed", src);
|
2009-10-06 23:37:48 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!S_ISDIR(dirattrib->perm)) {
|
|
|
|
error("\"%s\" is not a directory", src);
|
|
|
|
return -1;
|
|
|
|
}
|
2021-08-07 02:08:52 +02:00
|
|
|
if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
|
2016-05-26 01:48:45 +02:00
|
|
|
mprintf("Retrieving %s\n", src);
|
2009-10-06 23:37:48 +02:00
|
|
|
|
2020-11-20 04:16:56 +01:00
|
|
|
if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
|
2009-10-06 23:37:48 +02:00
|
|
|
mode = dirattrib->perm & 01777;
|
2020-11-20 04:16:56 +01:00
|
|
|
tmpmode = mode | (S_IWUSR|S_IXUSR);
|
|
|
|
} else {
|
2022-01-17 22:41:04 +01:00
|
|
|
debug("download remote \"%s\": server "
|
|
|
|
"did not send permissions", dst);
|
2009-10-06 23:37:48 +02:00
|
|
|
}
|
|
|
|
|
2020-11-20 04:16:56 +01:00
|
|
|
if (mkdir(dst, tmpmode) == -1 && errno != EEXIST) {
|
2009-10-06 23:37:48 +02:00
|
|
|
error("mkdir %s: %s", dst, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (do_readdir(conn, src, &dir_entries) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("remote readdir \"%s\" failed", src);
|
2009-10-06 23:37:48 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
|
2018-07-31 05:07:24 +02:00
|
|
|
free(new_dst);
|
|
|
|
free(new_src);
|
2009-10-06 23:37:48 +02:00
|
|
|
|
2018-07-31 05:07:24 +02:00
|
|
|
filename = dir_entries[i]->filename;
|
2009-10-06 23:37:48 +02:00
|
|
|
new_dst = path_append(dst, filename);
|
|
|
|
new_src = path_append(src, filename);
|
|
|
|
|
|
|
|
if (S_ISDIR(dir_entries[i]->a.perm)) {
|
|
|
|
if (strcmp(filename, ".") == 0 ||
|
|
|
|
strcmp(filename, "..") == 0)
|
|
|
|
continue;
|
|
|
|
if (download_dir_internal(conn, new_src, new_dst,
|
2013-10-15 03:05:58 +02:00
|
|
|
depth + 1, &(dir_entries[i]->a), preserve_flag,
|
2021-08-07 02:12:09 +02:00
|
|
|
print_flag, resume_flag,
|
|
|
|
fsync_flag, follow_link_flag) == -1)
|
2009-10-06 23:37:48 +02:00
|
|
|
ret = -1;
|
2021-08-07 02:12:09 +02:00
|
|
|
} else if (S_ISREG(dir_entries[i]->a.perm) ||
|
|
|
|
(follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) {
|
|
|
|
/*
|
|
|
|
* If this is a symlink then don't send the link's
|
|
|
|
* Attrib. do_download() will do a FXP_STAT operation
|
|
|
|
* and get the link target's attributes.
|
|
|
|
*/
|
2009-10-06 23:37:48 +02:00
|
|
|
if (do_download(conn, new_src, new_dst,
|
2021-08-07 02:12:09 +02:00
|
|
|
S_ISLNK(dir_entries[i]->a.perm) ? NULL :
|
|
|
|
&(dir_entries[i]->a),
|
|
|
|
preserve_flag, resume_flag, fsync_flag) == -1) {
|
2009-10-06 23:37:48 +02:00
|
|
|
error("Download of file %s to %s failed",
|
|
|
|
new_src, new_dst);
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
} else
|
2022-01-17 22:41:04 +01:00
|
|
|
logit("download \"%s\": not a regular file", new_src);
|
2009-10-06 23:37:48 +02:00
|
|
|
|
|
|
|
}
|
2018-07-31 05:07:24 +02:00
|
|
|
free(new_dst);
|
|
|
|
free(new_src);
|
2009-10-06 23:37:48 +02:00
|
|
|
|
2013-10-15 03:05:58 +02:00
|
|
|
if (preserve_flag) {
|
2009-10-06 23:37:48 +02:00
|
|
|
if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
|
|
|
|
struct timeval tv[2];
|
|
|
|
tv[0].tv_sec = dirattrib->atime;
|
|
|
|
tv[1].tv_sec = dirattrib->mtime;
|
|
|
|
tv[0].tv_usec = tv[1].tv_usec = 0;
|
|
|
|
if (utimes(dst, tv) == -1)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("local set times on \"%s\": %s",
|
2009-10-06 23:37:48 +02:00
|
|
|
dst, strerror(errno));
|
|
|
|
} else
|
|
|
|
debug("Server did not send times for directory "
|
|
|
|
"\"%s\"", dst);
|
|
|
|
}
|
|
|
|
|
2020-11-20 04:16:56 +01:00
|
|
|
if (mode != tmpmode && chmod(dst, mode) == -1)
|
2022-01-17 22:41:04 +01:00
|
|
|
error("local chmod directory \"%s\": %s", dst,
|
2020-11-20 04:16:56 +01:00
|
|
|
strerror(errno));
|
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
free_sftp_dirents(dir_entries);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
download_dir(struct sftp_conn *conn, const char *src, const char *dst,
|
|
|
|
Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag,
|
2021-08-07 02:12:09 +02:00
|
|
|
int fsync_flag, int follow_link_flag)
|
2009-10-06 23:37:48 +02:00
|
|
|
{
|
|
|
|
char *src_canon;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if ((src_canon = do_realpath(conn, src)) == NULL) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("download \"%s\": path canonicalization failed", src);
|
2009-10-06 23:37:48 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-10-15 03:05:58 +02:00
|
|
|
ret = download_dir_internal(conn, src_canon, dst, 0,
|
2021-08-07 02:12:09 +02:00
|
|
|
dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag,
|
|
|
|
follow_link_flag);
|
2013-06-01 23:31:17 +02:00
|
|
|
free(src_canon);
|
2009-10-06 23:37:48 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
do_upload(struct sftp_conn *conn, const char *local_path,
|
|
|
|
const char *remote_path, int preserve_flag, int resume, int fsync_flag)
|
2001-02-04 13:20:18 +01:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
int r, local_fd;
|
|
|
|
u_int status = SSH2_FX_OK;
|
|
|
|
u_int id;
|
|
|
|
u_char type;
|
2013-06-02 00:37:05 +02:00
|
|
|
off_t offset, progress_counter;
|
2015-01-14 14:54:13 +01:00
|
|
|
u_char *handle, *data;
|
|
|
|
struct sshbuf *msg;
|
2001-02-04 13:20:18 +01:00
|
|
|
struct stat sb;
|
2014-05-15 05:46:25 +02:00
|
|
|
Attrib a, *c = NULL;
|
2002-02-13 04:03:56 +01:00
|
|
|
u_int32_t startid;
|
|
|
|
u_int32_t ackid;
|
2021-08-07 02:14:17 +02:00
|
|
|
struct request *ack = NULL;
|
|
|
|
struct requests acks;
|
2015-01-14 14:54:13 +01:00
|
|
|
size_t handle_len;
|
2002-02-13 04:04:37 +01:00
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2_f("upload local \"%s\" to remote \"%s\"",
|
|
|
|
local_path, remote_path);
|
|
|
|
|
2002-02-13 04:04:37 +01:00
|
|
|
TAILQ_INIT(&acks);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
upstream: For open/openat, if the flags parameter does not contain
O_CREAT, the 3rd (variadic) mode_t parameter is irrelevant. Many developers
in the past have passed mode_t (0, 044, 0644, or such), which might lead
future people to copy this broken idiom, and perhaps even believe this
parameter has some meaning or implication or application. Delete them all.
This comes out of a conversation where tb@ noticed that a strange (but
intentional) pledge behaviour is to always knock-out high-bits from mode_t on
a number of system calls as a safety factor, and his bewilderment that this
appeared to be happening against valid modes (at least visually), but no
sorry, they are all irrelevant junk. They could all be 0xdeafbeef. ok
millert
OpenBSD-Commit-ID: 503d11633497115688c0c6952686524f01f53121
2021-10-24 23:24:17 +02:00
|
|
|
if ((local_fd = open(local_path, O_RDONLY)) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("open local \"%s\": %s", local_path, strerror(errno));
|
2001-02-04 13:20:18 +01:00
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
if (fstat(local_fd, &sb) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("fstat local \"%s\": %s", local_path, strerror(errno));
|
2001-02-04 13:20:18 +01:00
|
|
|
close(local_fd);
|
|
|
|
return(-1);
|
|
|
|
}
|
2003-01-14 12:24:47 +01:00
|
|
|
if (!S_ISREG(sb.st_mode)) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("local \"%s\" is not a regular file", local_path);
|
2003-01-14 12:24:47 +01:00
|
|
|
close(local_fd);
|
|
|
|
return(-1);
|
|
|
|
}
|
2001-02-04 13:20:18 +01:00
|
|
|
stat_to_attrib(&sb, &a);
|
|
|
|
|
|
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
|
|
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
|
|
|
|
a.perm &= 0777;
|
2013-10-15 03:05:58 +02:00
|
|
|
if (!preserve_flag)
|
2001-02-04 13:20:18 +01:00
|
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
|
|
|
|
|
2014-05-15 05:46:25 +02:00
|
|
|
if (resume) {
|
|
|
|
/* Get remote file size if it exists */
|
|
|
|
if ((c = do_stat(conn, remote_path, 0)) == NULL) {
|
2016-04-08 10:19:17 +02:00
|
|
|
close(local_fd);
|
2014-05-15 05:46:25 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((off_t)c->size >= sb.st_size) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("resume \"%s\": destination file "
|
|
|
|
"same size or larger", local_path);
|
2014-05-15 05:46:25 +02:00
|
|
|
close(local_fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) {
|
|
|
|
close(local_fd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
/* Send open request */
|
2021-08-07 02:01:29 +02:00
|
|
|
if (send_open(conn, remote_path, "dest", SSH2_FXF_WRITE|SSH2_FXF_CREAT|
|
|
|
|
(resume ? SSH2_FXF_APPEND : SSH2_FXF_TRUNC),
|
|
|
|
&a, &handle, &handle_len) != 0) {
|
2001-02-04 13:20:18 +01:00
|
|
|
close(local_fd);
|
2008-02-10 12:27:24 +01:00
|
|
|
return -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2021-08-07 02:01:29 +02:00
|
|
|
id = conn->msg_id;
|
2002-02-13 04:03:56 +01:00
|
|
|
startid = ackid = id + 1;
|
2021-04-01 00:16:34 +02:00
|
|
|
data = xmalloc(conn->upload_buflen);
|
2002-02-08 12:04:05 +01:00
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
/* Read from local and write to remote */
|
2014-05-15 05:46:25 +02:00
|
|
|
offset = progress_counter = (resume ? c->size : 0);
|
2021-08-09 09:16:09 +02:00
|
|
|
if (showprogress) {
|
|
|
|
start_progress_meter(progress_meter_path(local_path),
|
|
|
|
sb.st_size, &progress_counter);
|
|
|
|
}
|
2003-01-10 11:43:24 +01:00
|
|
|
|
2021-08-07 02:01:29 +02:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
|
|
fatal_f("sshbuf_new failed");
|
2001-12-21 04:45:46 +01:00
|
|
|
for (;;) {
|
2001-02-04 13:20:18 +01:00
|
|
|
int len;
|
|
|
|
|
|
|
|
/*
|
2004-07-17 08:12:08 +02:00
|
|
|
* Can't use atomicio here because it returns 0 on EOF,
|
2004-05-24 02:12:19 +02:00
|
|
|
* thus losing the last block of the file.
|
2004-07-17 08:12:08 +02:00
|
|
|
* Simulate an EOF on interrupt, allowing ACKs from the
|
2004-05-24 02:12:19 +02:00
|
|
|
* server to drain.
|
2001-02-04 13:20:18 +01:00
|
|
|
*/
|
2008-02-10 12:27:24 +01:00
|
|
|
if (interrupted || status != SSH2_FX_OK)
|
2004-05-24 02:12:19 +02:00
|
|
|
len = 0;
|
|
|
|
else do
|
2021-04-01 00:16:34 +02:00
|
|
|
len = read(local_fd, data, conn->upload_buflen);
|
2008-07-04 15:10:49 +02:00
|
|
|
while ((len == -1) &&
|
|
|
|
(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
if (len == -1) {
|
|
|
|
fatal("read local \"%s\": %s",
|
|
|
|
local_path, strerror(errno));
|
|
|
|
} else if (len != 0) {
|
2021-08-07 02:14:17 +02:00
|
|
|
ack = request_enqueue(&acks, ++id, len, offset);
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_reset(msg);
|
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, ack->id)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, handle,
|
|
|
|
handle_len)) != 0 ||
|
|
|
|
(r = sshbuf_put_u64(msg, offset)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, data, len)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "compose");
|
2015-01-14 14:54:13 +01:00
|
|
|
send_msg(conn, msg);
|
2002-06-23 23:27:18 +02:00
|
|
|
debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
|
2002-12-23 03:06:19 +01:00
|
|
|
id, (unsigned long long)offset, len);
|
2002-02-13 04:04:37 +01:00
|
|
|
} else if (TAILQ_FIRST(&acks) == NULL)
|
2001-02-04 13:20:18 +01:00
|
|
|
break;
|
|
|
|
|
2002-02-13 04:04:37 +01:00
|
|
|
if (ack == NULL)
|
|
|
|
fatal("Unexpected ACK %u", id);
|
|
|
|
|
2002-03-22 03:54:23 +01:00
|
|
|
if (id == startid || len == 0 ||
|
2002-02-13 04:10:32 +01:00
|
|
|
id - ackid >= conn->num_requests) {
|
2015-01-14 14:54:13 +01:00
|
|
|
u_int rid;
|
2002-04-06 06:16:45 +02:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_reset(msg);
|
|
|
|
get_msg(conn, msg);
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &rid)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse");
|
2002-02-13 04:04:37 +01:00
|
|
|
|
|
|
|
if (type != SSH2_FXP_STATUS)
|
|
|
|
fatal("Expected SSH2_FXP_STATUS(%d) packet, "
|
|
|
|
"got %d", SSH2_FXP_STATUS, type);
|
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_fr(r, "parse status");
|
2015-01-14 14:54:13 +01:00
|
|
|
debug3("SSH2_FXP_STATUS %u", status);
|
2002-02-13 04:04:37 +01:00
|
|
|
|
|
|
|
/* Find the request in our queue */
|
2021-08-07 02:14:17 +02:00
|
|
|
if ((ack = request_find(&acks, rid)) == NULL)
|
2015-01-14 14:54:13 +01:00
|
|
|
fatal("Can't find request for ID %u", rid);
|
2002-02-13 04:04:37 +01:00
|
|
|
TAILQ_REMOVE(&acks, ack, tq);
|
2021-08-07 02:14:17 +02:00
|
|
|
debug3("In write loop, ack for %u %zu bytes at %lld",
|
|
|
|
ack->id, ack->len, (unsigned long long)ack->offset);
|
2002-02-13 04:03:56 +01:00
|
|
|
++ackid;
|
2013-06-02 00:37:05 +02:00
|
|
|
progress_counter += ack->len;
|
2013-06-01 23:31:17 +02:00
|
|
|
free(ack);
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
offset += len;
|
2007-09-17 08:12:03 +02:00
|
|
|
if (offset < 0)
|
2020-10-18 13:32:01 +02:00
|
|
|
fatal_f("offset < 0");
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
2015-01-14 14:54:13 +01:00
|
|
|
sshbuf_free(msg);
|
2008-02-10 12:27:24 +01:00
|
|
|
|
2003-01-10 11:43:24 +01:00
|
|
|
if (showprogress)
|
|
|
|
stop_progress_meter();
|
2013-06-01 23:31:17 +02:00
|
|
|
free(data);
|
2001-02-04 13:20:18 +01:00
|
|
|
|
2008-02-10 12:27:24 +01:00
|
|
|
if (status != SSH2_FX_OK) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("write remote \"%s\": %s", remote_path, fx2txt(status));
|
2015-01-14 14:54:13 +01:00
|
|
|
status = SSH2_FX_FAILURE;
|
2008-02-10 12:27:24 +01:00
|
|
|
}
|
|
|
|
|
2001-02-04 13:20:18 +01:00
|
|
|
if (close(local_fd) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("close local \"%s\": %s", local_path, strerror(errno));
|
2015-01-14 14:54:13 +01:00
|
|
|
status = SSH2_FX_FAILURE;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
|
|
|
|
2001-02-15 04:22:45 +01:00
|
|
|
/* Override umask and utimes if asked */
|
2013-10-15 03:05:58 +02:00
|
|
|
if (preserve_flag)
|
2002-02-13 04:10:32 +01:00
|
|
|
do_fsetstat(conn, handle, handle_len, &a);
|
2001-02-15 04:22:45 +01:00
|
|
|
|
2013-10-17 02:48:52 +02:00
|
|
|
if (fsync_flag)
|
|
|
|
(void)do_fsync(conn, handle, handle_len);
|
|
|
|
|
2016-02-11 03:21:34 +01:00
|
|
|
if (do_close(conn, handle, handle_len) != 0)
|
2015-01-14 14:54:13 +01:00
|
|
|
status = SSH2_FX_FAILURE;
|
|
|
|
|
2013-06-01 23:31:17 +02:00
|
|
|
free(handle);
|
2008-02-10 12:27:24 +01:00
|
|
|
|
2015-01-14 14:54:13 +01:00
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
2001-02-04 13:20:18 +01:00
|
|
|
}
|
2009-10-06 23:37:48 +02:00
|
|
|
|
|
|
|
static int
|
2015-01-14 14:54:13 +01:00
|
|
|
upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
|
2021-08-07 02:12:09 +02:00
|
|
|
int depth, int preserve_flag, int print_flag, int resume, int fsync_flag,
|
|
|
|
int follow_link_flag)
|
2009-10-06 23:37:48 +02:00
|
|
|
{
|
2015-01-14 14:54:13 +01:00
|
|
|
int ret = 0;
|
2009-10-06 23:37:48 +02:00
|
|
|
DIR *dirp;
|
|
|
|
struct dirent *dp;
|
2018-07-31 05:07:24 +02:00
|
|
|
char *filename, *new_src = NULL, *new_dst = NULL;
|
2009-10-06 23:37:48 +02:00
|
|
|
struct stat sb;
|
2016-02-11 03:21:34 +01:00
|
|
|
Attrib a, *dirattrib;
|
2020-11-20 04:16:56 +01:00
|
|
|
u_int32_t saved_perm;
|
2009-10-06 23:37:48 +02:00
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst);
|
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
if (depth >= MAX_DIR_DEPTH) {
|
|
|
|
error("Maximum directory depth exceeded: %d levels", depth);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stat(src, &sb) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("stat local \"%s\": %s", src, strerror(errno));
|
2009-10-06 23:37:48 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!S_ISDIR(sb.st_mode)) {
|
|
|
|
error("\"%s\" is not a directory", src);
|
|
|
|
return -1;
|
|
|
|
}
|
2021-08-07 02:08:52 +02:00
|
|
|
if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
|
2016-05-26 01:48:45 +02:00
|
|
|
mprintf("Entering %s\n", src);
|
2009-10-06 23:37:48 +02:00
|
|
|
|
|
|
|
stat_to_attrib(&sb, &a);
|
|
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
|
|
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
|
|
|
|
a.perm &= 01777;
|
2013-10-15 03:05:58 +02:00
|
|
|
if (!preserve_flag)
|
2009-10-06 23:37:48 +02:00
|
|
|
a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
|
2013-07-25 03:56:52 +02:00
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
/*
|
2016-02-11 03:21:34 +01:00
|
|
|
* sftp lacks a portable status value to match errno EEXIST,
|
|
|
|
* so if we get a failure back then we must check whether
|
2020-11-20 04:16:56 +01:00
|
|
|
* the path already existed and is a directory. Ensure we can
|
|
|
|
* write to the directory we create for the duration of the transfer.
|
2009-10-06 23:37:48 +02:00
|
|
|
*/
|
2020-11-20 04:16:56 +01:00
|
|
|
saved_perm = a.perm;
|
|
|
|
a.perm |= (S_IWUSR|S_IXUSR);
|
2016-02-11 03:21:34 +01:00
|
|
|
if (do_mkdir(conn, dst, &a, 0) != 0) {
|
|
|
|
if ((dirattrib = do_stat(conn, dst, 0)) == NULL)
|
2009-10-06 23:37:48 +02:00
|
|
|
return -1;
|
2016-02-11 03:21:34 +01:00
|
|
|
if (!S_ISDIR(dirattrib->perm)) {
|
|
|
|
error("\"%s\" exists but is not a directory", dst);
|
2009-10-06 23:37:48 +02:00
|
|
|
return -1;
|
2016-02-11 03:21:34 +01:00
|
|
|
}
|
2009-10-06 23:37:48 +02:00
|
|
|
}
|
2020-11-20 04:16:56 +01:00
|
|
|
a.perm = saved_perm;
|
2009-10-06 23:37:48 +02:00
|
|
|
|
|
|
|
if ((dirp = opendir(src)) == NULL) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("local opendir \"%s\": %s", src, strerror(errno));
|
2009-10-06 23:37:48 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2013-07-25 03:56:52 +02:00
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
while (((dp = readdir(dirp)) != NULL) && !interrupted) {
|
|
|
|
if (dp->d_ino == 0)
|
|
|
|
continue;
|
2018-07-31 05:07:24 +02:00
|
|
|
free(new_dst);
|
|
|
|
free(new_src);
|
2009-10-06 23:37:48 +02:00
|
|
|
filename = dp->d_name;
|
|
|
|
new_dst = path_append(dst, filename);
|
|
|
|
new_src = path_append(src, filename);
|
|
|
|
|
2009-10-11 12:52:10 +02:00
|
|
|
if (lstat(new_src, &sb) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
logit("local lstat \"%s\": %s", filename,
|
2009-10-11 12:52:10 +02:00
|
|
|
strerror(errno));
|
|
|
|
ret = -1;
|
|
|
|
} else if (S_ISDIR(sb.st_mode)) {
|
2009-10-06 23:37:48 +02:00
|
|
|
if (strcmp(filename, ".") == 0 ||
|
|
|
|
strcmp(filename, "..") == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (upload_dir_internal(conn, new_src, new_dst,
|
2014-05-15 05:46:25 +02:00
|
|
|
depth + 1, preserve_flag, print_flag, resume,
|
2021-08-07 02:12:09 +02:00
|
|
|
fsync_flag, follow_link_flag) == -1)
|
2009-10-06 23:37:48 +02:00
|
|
|
ret = -1;
|
2021-08-07 02:12:09 +02:00
|
|
|
} else if (S_ISREG(sb.st_mode) ||
|
|
|
|
(follow_link_flag && S_ISLNK(sb.st_mode))) {
|
2013-10-15 03:05:58 +02:00
|
|
|
if (do_upload(conn, new_src, new_dst,
|
2014-05-15 05:46:25 +02:00
|
|
|
preserve_flag, resume, fsync_flag) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("upload \"%s\" to \"%s\" failed",
|
2009-10-06 23:37:48 +02:00
|
|
|
new_src, new_dst);
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
} else
|
2022-01-17 22:41:04 +01:00
|
|
|
logit("%s: not a regular file", filename);
|
2009-10-06 23:37:48 +02:00
|
|
|
}
|
2018-07-31 05:07:24 +02:00
|
|
|
free(new_dst);
|
|
|
|
free(new_src);
|
2009-10-06 23:37:48 +02:00
|
|
|
|
|
|
|
do_setstat(conn, dst, &a);
|
|
|
|
|
|
|
|
(void) closedir(dirp);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2015-01-14 14:54:13 +01:00
|
|
|
upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
|
2021-08-07 02:12:09 +02:00
|
|
|
int preserve_flag, int print_flag, int resume, int fsync_flag,
|
|
|
|
int follow_link_flag)
|
2009-10-06 23:37:48 +02:00
|
|
|
{
|
|
|
|
char *dst_canon;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if ((dst_canon = do_realpath(conn, dst)) == NULL) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("upload \"%s\": path canonicalization failed", dst);
|
2009-10-06 23:37:48 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-10-15 03:06:27 +02:00
|
|
|
ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag,
|
2021-08-07 02:12:09 +02:00
|
|
|
print_flag, resume, fsync_flag, follow_link_flag);
|
2013-10-17 02:48:52 +02:00
|
|
|
|
2013-06-01 23:31:17 +02:00
|
|
|
free(dst_canon);
|
2009-10-06 23:37:48 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-08-07 02:02:41 +02:00
|
|
|
static void
|
|
|
|
handle_dest_replies(struct sftp_conn *to, const char *to_path, int synchronous,
|
|
|
|
u_int *nreqsp, u_int *write_errorp)
|
|
|
|
{
|
|
|
|
struct sshbuf *msg;
|
|
|
|
u_char type;
|
|
|
|
u_int id, status;
|
|
|
|
int r;
|
|
|
|
struct pollfd pfd;
|
|
|
|
|
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
|
|
fatal_f("sshbuf_new failed");
|
|
|
|
|
|
|
|
/* Try to eat replies from the upload side */
|
|
|
|
while (*nreqsp > 0) {
|
|
|
|
debug3_f("%u outstanding replies", *nreqsp);
|
|
|
|
if (!synchronous) {
|
|
|
|
/* Bail out if no data is ready to be read */
|
|
|
|
pfd.fd = to->fd_in;
|
|
|
|
pfd.events = POLLIN;
|
|
|
|
if ((r = poll(&pfd, 1, 0)) == -1) {
|
|
|
|
if (errno == EINTR)
|
|
|
|
break;
|
|
|
|
fatal_f("poll: %s", strerror(errno));
|
|
|
|
} else if (r == 0)
|
|
|
|
break; /* fd not ready */
|
|
|
|
}
|
|
|
|
sshbuf_reset(msg);
|
|
|
|
get_msg(to, msg);
|
|
|
|
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
|
|
|
fatal_fr(r, "dest parse");
|
|
|
|
debug3("Received dest reply T:%u I:%u R:%u", type, id, *nreqsp);
|
|
|
|
if (type != SSH2_FXP_STATUS) {
|
|
|
|
fatal_f("Expected SSH2_FXP_STATUS(%d) packet, got %d",
|
|
|
|
SSH2_FXP_STATUS, type);
|
|
|
|
}
|
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
|
|
|
fatal_fr(r, "parse dest status");
|
|
|
|
debug3("dest SSH2_FXP_STATUS %u", status);
|
|
|
|
if (status != SSH2_FX_OK) {
|
|
|
|
/* record first error */
|
|
|
|
if (*write_errorp == 0)
|
|
|
|
*write_errorp = status;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* XXX this doesn't do full reply matching like do_upload and
|
|
|
|
* so cannot gracefully truncate terminated uploads at a
|
|
|
|
* high-water mark. ATM the only caller of this function (scp)
|
|
|
|
* doesn't support transfer resumption, so this doesn't matter
|
|
|
|
* a whole lot.
|
|
|
|
*
|
|
|
|
* To be safe, do_crossload truncates the destination file to
|
|
|
|
* zero length on upload failure, since we can't trust the
|
|
|
|
* server not to have reordered replies that could have
|
|
|
|
* inserted holes where none existed in the source file.
|
|
|
|
*
|
|
|
|
* XXX we could get a more accutate progress bar if we updated
|
|
|
|
* the counter based on the reply from the destination...
|
|
|
|
*/
|
|
|
|
(*nreqsp)--;
|
|
|
|
}
|
|
|
|
debug3_f("done: %u outstanding replies", *nreqsp);
|
2021-11-06 11:13:39 +01:00
|
|
|
sshbuf_free(msg);
|
2021-08-07 02:02:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
do_crossload(struct sftp_conn *from, struct sftp_conn *to,
|
|
|
|
const char *from_path, const char *to_path,
|
|
|
|
Attrib *a, int preserve_flag)
|
|
|
|
{
|
|
|
|
struct sshbuf *msg;
|
2021-08-07 03:55:01 +02:00
|
|
|
int write_error, read_error, r;
|
2021-08-07 02:02:41 +02:00
|
|
|
u_int64_t offset = 0, size;
|
|
|
|
u_int id, buflen, num_req, max_req, status = SSH2_FX_OK;
|
|
|
|
u_int num_upload_req;
|
|
|
|
off_t progress_counter;
|
|
|
|
u_char *from_handle, *to_handle;
|
|
|
|
size_t from_handle_len, to_handle_len;
|
2021-08-07 02:07:18 +02:00
|
|
|
struct requests requests;
|
2021-08-07 02:02:41 +02:00
|
|
|
struct request *req;
|
|
|
|
u_char type;
|
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path);
|
|
|
|
|
2021-08-07 02:02:41 +02:00
|
|
|
TAILQ_INIT(&requests);
|
|
|
|
|
|
|
|
if (a == NULL && (a = do_stat(from, from_path, 0)) == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
|
|
|
|
(!S_ISREG(a->perm))) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("download \"%s\": not a regular file", from_path);
|
2021-08-07 02:02:41 +02:00
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
|
|
|
|
size = a->size;
|
|
|
|
else
|
|
|
|
size = 0;
|
|
|
|
|
|
|
|
buflen = from->download_buflen;
|
|
|
|
if (buflen > to->upload_buflen)
|
|
|
|
buflen = to->upload_buflen;
|
|
|
|
|
|
|
|
/* Send open request to read side */
|
|
|
|
if (send_open(from, from_path, "origin", SSH2_FXF_READ, NULL,
|
|
|
|
&from_handle, &from_handle_len) != 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* Send open request to write side */
|
|
|
|
a->flags &= ~SSH2_FILEXFER_ATTR_SIZE;
|
|
|
|
a->flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
|
|
|
|
a->perm &= 0777;
|
|
|
|
if (!preserve_flag)
|
|
|
|
a->flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
|
|
|
|
if (send_open(to, to_path, "dest",
|
|
|
|
SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
|
|
|
|
&to_handle, &to_handle_len) != 0) {
|
|
|
|
do_close(from, from_handle, from_handle_len);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read from remote "from" and write to remote "to" */
|
|
|
|
offset = 0;
|
|
|
|
write_error = read_error = num_req = num_upload_req = 0;
|
|
|
|
max_req = 1;
|
|
|
|
progress_counter = 0;
|
|
|
|
|
2021-08-09 09:16:09 +02:00
|
|
|
if (showprogress && size != 0) {
|
|
|
|
start_progress_meter(progress_meter_path(from_path),
|
|
|
|
size, &progress_counter);
|
|
|
|
}
|
2021-08-07 02:02:41 +02:00
|
|
|
if ((msg = sshbuf_new()) == NULL)
|
|
|
|
fatal_f("sshbuf_new failed");
|
|
|
|
while (num_req > 0 || max_req > 0) {
|
|
|
|
u_char *data;
|
|
|
|
size_t 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);
|
2021-08-07 02:14:17 +02:00
|
|
|
req = request_enqueue(&requests, from->msg_id++,
|
|
|
|
buflen, offset);
|
2021-08-07 02:02:41 +02:00
|
|
|
offset += buflen;
|
|
|
|
num_req++;
|
|
|
|
send_read_request(from, req->id, req->offset,
|
|
|
|
req->len, from_handle, from_handle_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Try to eat replies from the upload side (nonblocking) */
|
|
|
|
handle_dest_replies(to, to_path, 0,
|
|
|
|
&num_upload_req, &write_error);
|
|
|
|
|
|
|
|
sshbuf_reset(msg);
|
|
|
|
get_msg(from, msg);
|
|
|
|
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
|
|
|
|
(r = sshbuf_get_u32(msg, &id)) != 0)
|
|
|
|
fatal_fr(r, "parse");
|
|
|
|
debug3("Received origin reply T:%u I:%u R:%d",
|
|
|
|
type, id, max_req);
|
|
|
|
|
|
|
|
/* Find the request in our queue */
|
2021-08-07 02:14:17 +02:00
|
|
|
if ((req = request_find(&requests, id)) == NULL)
|
2021-08-07 02:02:41 +02:00
|
|
|
fatal("Unexpected reply %u", id);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case SSH2_FXP_STATUS:
|
|
|
|
if ((r = sshbuf_get_u32(msg, &status)) != 0)
|
|
|
|
fatal_fr(r, "parse status");
|
|
|
|
if (status != SSH2_FX_EOF)
|
|
|
|
read_error = 1;
|
|
|
|
max_req = 0;
|
|
|
|
TAILQ_REMOVE(&requests, req, tq);
|
|
|
|
free(req);
|
|
|
|
num_req--;
|
|
|
|
break;
|
|
|
|
case SSH2_FXP_DATA:
|
|
|
|
if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
|
|
|
|
fatal_fr(r, "parse data");
|
|
|
|
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 "
|
|
|
|
"%zu > %zu", len, req->len);
|
|
|
|
|
|
|
|
/* Write this chunk out to the destination */
|
|
|
|
sshbuf_reset(msg);
|
|
|
|
if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
|
|
|
|
(r = sshbuf_put_u32(msg, to->msg_id++)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, to_handle,
|
|
|
|
to_handle_len)) != 0 ||
|
|
|
|
(r = sshbuf_put_u64(msg, req->offset)) != 0 ||
|
|
|
|
(r = sshbuf_put_string(msg, data, len)) != 0)
|
|
|
|
fatal_fr(r, "compose write");
|
|
|
|
send_msg(to, msg);
|
|
|
|
debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%zu",
|
|
|
|
id, (unsigned long long)offset, len);
|
|
|
|
num_upload_req++;
|
|
|
|
progress_counter += len;
|
|
|
|
free(data);
|
|
|
|
|
|
|
|
if (len == req->len) {
|
|
|
|
TAILQ_REMOVE(&requests, req, tq);
|
|
|
|
free(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 = from->msg_id++;
|
|
|
|
req->len -= len;
|
|
|
|
req->offset += len;
|
|
|
|
send_read_request(from, req->id,
|
|
|
|
req->offset, req->len,
|
|
|
|
from_handle, from_handle_len);
|
|
|
|
/* Reduce the request size */
|
|
|
|
if (len < buflen)
|
|
|
|
buflen = MAXIMUM(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 < from->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();
|
|
|
|
|
|
|
|
/* Drain replies from the server (blocking) */
|
|
|
|
debug3_f("waiting for %u replies from destination", num_upload_req);
|
|
|
|
handle_dest_replies(to, to_path, 1, &num_upload_req, &write_error);
|
|
|
|
|
|
|
|
/* Sanity check */
|
|
|
|
if (TAILQ_FIRST(&requests) != NULL)
|
|
|
|
fatal("Transfer complete, but requests still in queue");
|
|
|
|
/* Truncate at 0 length on interrupt or error to avoid holes at dest */
|
|
|
|
if (read_error || write_error || interrupted) {
|
|
|
|
debug("truncating \"%s\" at 0", to_path);
|
|
|
|
do_close(to, to_handle, to_handle_len);
|
|
|
|
free(to_handle);
|
|
|
|
if (send_open(to, to_path, "dest",
|
|
|
|
SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
|
|
|
|
&to_handle, &to_handle_len) != 0) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("dest truncate \"%s\" failed", to_path);
|
2021-08-07 02:02:41 +02:00
|
|
|
to_handle = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (read_error) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("read origin \"%s\": %s", from_path, fx2txt(status));
|
2021-08-07 02:02:41 +02:00
|
|
|
status = -1;
|
|
|
|
do_close(from, from_handle, from_handle_len);
|
|
|
|
if (to_handle != NULL)
|
|
|
|
do_close(to, to_handle, to_handle_len);
|
|
|
|
} else if (write_error) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("write dest \"%s\": %s", to_path, fx2txt(write_error));
|
2021-08-07 02:02:41 +02:00
|
|
|
status = SSH2_FX_FAILURE;
|
|
|
|
do_close(from, from_handle, from_handle_len);
|
|
|
|
if (to_handle != NULL)
|
|
|
|
do_close(to, to_handle, to_handle_len);
|
|
|
|
} else {
|
|
|
|
if (do_close(from, from_handle, from_handle_len) != 0 ||
|
|
|
|
interrupted)
|
|
|
|
status = -1;
|
|
|
|
else
|
|
|
|
status = SSH2_FX_OK;
|
|
|
|
if (to_handle != NULL) {
|
|
|
|
/* Need to resend utimes after write */
|
|
|
|
if (preserve_flag)
|
|
|
|
do_fsetstat(to, to_handle, to_handle_len, a);
|
|
|
|
do_close(to, to_handle, to_handle_len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sshbuf_free(msg);
|
|
|
|
free(from_handle);
|
|
|
|
free(to_handle);
|
|
|
|
|
|
|
|
return status == SSH2_FX_OK ? 0 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
|
|
|
|
const char *from_path, const char *to_path,
|
2021-08-07 02:12:09 +02:00
|
|
|
int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
|
|
|
|
int follow_link_flag)
|
2021-08-07 02:02:41 +02:00
|
|
|
{
|
|
|
|
int i, ret = 0;
|
|
|
|
SFTP_DIRENT **dir_entries;
|
|
|
|
char *filename, *new_from_path = NULL, *new_to_path = NULL;
|
|
|
|
mode_t mode = 0777;
|
2021-08-07 02:10:49 +02:00
|
|
|
Attrib curdir;
|
2021-08-07 02:02:41 +02:00
|
|
|
|
2022-01-17 22:41:04 +01:00
|
|
|
debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path);
|
|
|
|
|
2021-08-07 02:02:41 +02:00
|
|
|
if (depth >= MAX_DIR_DEPTH) {
|
|
|
|
error("Maximum directory depth exceeded: %d levels", depth);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dirattrib == NULL &&
|
|
|
|
(dirattrib = do_stat(from, from_path, 1)) == NULL) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("stat remote \"%s\" failed", from_path);
|
2021-08-07 02:02:41 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!S_ISDIR(dirattrib->perm)) {
|
|
|
|
error("\"%s\" is not a directory", from_path);
|
|
|
|
return -1;
|
|
|
|
}
|
2021-08-07 02:12:09 +02:00
|
|
|
if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
|
2021-08-07 02:02:41 +02:00
|
|
|
mprintf("Retrieving %s\n", from_path);
|
|
|
|
|
2021-08-07 02:10:49 +02:00
|
|
|
curdir = *dirattrib; /* dirattrib will be clobbered */
|
|
|
|
curdir.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
|
|
|
|
curdir.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
|
|
|
|
if ((curdir.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) == 0) {
|
|
|
|
debug("Origin did not send permissions for "
|
2021-08-07 02:02:41 +02:00
|
|
|
"directory \"%s\"", to_path);
|
2021-08-07 02:10:49 +02:00
|
|
|
curdir.perm = S_IWUSR|S_IXUSR;
|
|
|
|
curdir.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
|
2021-08-07 02:02:41 +02:00
|
|
|
}
|
2021-08-07 02:10:49 +02:00
|
|
|
/* We need to be able to write to the directory while we transfer it */
|
|
|
|
mode = curdir.perm & 01777;
|
|
|
|
curdir.perm = mode | (S_IWUSR|S_IXUSR);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* sftp lacks a portable status value to match errno EEXIST,
|
|
|
|
* so if we get a failure back then we must check whether
|
|
|
|
* the path already existed and is a directory. Ensure we can
|
|
|
|
* write to the directory we create for the duration of the transfer.
|
|
|
|
*/
|
|
|
|
if (do_mkdir(to, to_path, &curdir, 0) != 0) {
|
|
|
|
if ((dirattrib = do_stat(to, to_path, 0)) == NULL)
|
|
|
|
return -1;
|
|
|
|
if (!S_ISDIR(dirattrib->perm)) {
|
|
|
|
error("\"%s\" exists but is not a directory", to_path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
curdir.perm = mode;
|
2021-08-07 02:02:41 +02:00
|
|
|
|
|
|
|
if (do_readdir(from, from_path, &dir_entries) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("origin readdir \"%s\" failed", from_path);
|
2021-08-07 02:02:41 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
|
|
|
|
free(new_from_path);
|
|
|
|
free(new_to_path);
|
|
|
|
|
|
|
|
filename = dir_entries[i]->filename;
|
|
|
|
new_from_path = path_append(from_path, filename);
|
|
|
|
new_to_path = path_append(to_path, filename);
|
|
|
|
|
|
|
|
if (S_ISDIR(dir_entries[i]->a.perm)) {
|
|
|
|
if (strcmp(filename, ".") == 0 ||
|
|
|
|
strcmp(filename, "..") == 0)
|
|
|
|
continue;
|
|
|
|
if (crossload_dir_internal(from, to,
|
|
|
|
new_from_path, new_to_path,
|
|
|
|
depth + 1, &(dir_entries[i]->a), preserve_flag,
|
2021-08-07 02:12:09 +02:00
|
|
|
print_flag, follow_link_flag) == -1)
|
2021-08-07 02:02:41 +02:00
|
|
|
ret = -1;
|
2021-08-07 02:12:09 +02:00
|
|
|
} else if (S_ISREG(dir_entries[i]->a.perm) ||
|
|
|
|
(follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) {
|
|
|
|
/*
|
|
|
|
* If this is a symlink then don't send the link's
|
|
|
|
* Attrib. do_download() will do a FXP_STAT operation
|
|
|
|
* and get the link target's attributes.
|
|
|
|
*/
|
2021-08-07 02:02:41 +02:00
|
|
|
if (do_crossload(from, to, new_from_path, new_to_path,
|
2021-08-07 02:12:09 +02:00
|
|
|
S_ISLNK(dir_entries[i]->a.perm) ? NULL :
|
2021-08-07 02:02:41 +02:00
|
|
|
&(dir_entries[i]->a), preserve_flag) == -1) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("crossload \"%s\" to \"%s\" failed",
|
2021-08-07 02:02:41 +02:00
|
|
|
new_from_path, new_to_path);
|
|
|
|
ret = -1;
|
|
|
|
}
|
2022-01-17 22:41:04 +01:00
|
|
|
} else {
|
|
|
|
logit("origin \"%s\": not a regular file",
|
|
|
|
new_from_path);
|
|
|
|
}
|
2021-08-07 02:02:41 +02:00
|
|
|
}
|
|
|
|
free(new_to_path);
|
|
|
|
free(new_from_path);
|
|
|
|
|
2021-08-07 02:10:49 +02:00
|
|
|
do_setstat(to, to_path, &curdir);
|
2021-08-07 02:02:41 +02:00
|
|
|
|
|
|
|
free_sftp_dirents(dir_entries);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
|
|
|
|
const char *from_path, const char *to_path,
|
2021-08-07 02:12:09 +02:00
|
|
|
Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag)
|
2021-08-07 02:02:41 +02:00
|
|
|
{
|
|
|
|
char *from_path_canon;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if ((from_path_canon = do_realpath(from, from_path)) == NULL) {
|
2022-01-17 22:41:04 +01:00
|
|
|
error("crossload \"%s\": path canonicalization failed",
|
|
|
|
from_path);
|
2021-08-07 02:02:41 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = crossload_dir_internal(from, to, from_path_canon, to_path, 0,
|
2021-08-07 02:12:09 +02:00
|
|
|
dirattrib, preserve_flag, print_flag, follow_link_flag);
|
2021-08-07 02:02:41 +02:00
|
|
|
free(from_path_canon);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-10-06 23:37:48 +02:00
|
|
|
char *
|
2015-01-14 14:54:13 +01:00
|
|
|
path_append(const char *p1, const char *p2)
|
2009-10-06 23:37:48 +02:00
|
|
|
{
|
|
|
|
char *ret;
|
|
|
|
size_t len = strlen(p1) + strlen(p2) + 2;
|
|
|
|
|
|
|
|
ret = xmalloc(len);
|
|
|
|
strlcpy(ret, p1, len);
|
|
|
|
if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
|
|
|
|
strlcat(ret, "/", len);
|
|
|
|
strlcat(ret, p2, len);
|
|
|
|
|
|
|
|
return(ret);
|
|
|
|
}
|
|
|
|
|
2020-12-04 03:41:10 +01:00
|
|
|
char *
|
|
|
|
make_absolute(char *p, const char *pwd)
|
|
|
|
{
|
|
|
|
char *abs_str;
|
|
|
|
|
|
|
|
/* Derelativise */
|
|
|
|
if (p && !path_absolute(p)) {
|
|
|
|
abs_str = path_append(pwd, p);
|
|
|
|
free(p);
|
|
|
|
return(abs_str);
|
|
|
|
} else
|
|
|
|
return(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
remote_is_dir(struct sftp_conn *conn, const char *path)
|
|
|
|
{
|
|
|
|
Attrib *a;
|
|
|
|
|
|
|
|
/* XXX: report errors? */
|
|
|
|
if ((a = do_stat(conn, path, 1)) == NULL)
|
|
|
|
return(0);
|
|
|
|
if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
|
|
|
|
return(0);
|
|
|
|
return(S_ISDIR(a->perm));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
local_is_dir(const char *path)
|
|
|
|
{
|
|
|
|
struct stat sb;
|
|
|
|
|
|
|
|
/* XXX: report errors? */
|
|
|
|
if (stat(path, &sb) == -1)
|
|
|
|
return(0);
|
|
|
|
|
|
|
|
return(S_ISDIR(sb.st_mode));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
|
|
|
|
int
|
|
|
|
globpath_is_dir(const char *pathname)
|
|
|
|
{
|
|
|
|
size_t l = strlen(pathname);
|
|
|
|
|
|
|
|
return l > 0 && pathname[l - 1] == '/';
|
|
|
|
}
|
|
|
|
|