1*069ac184SEd Maste /* $OpenBSD: sftp-client.c,v 1.175 2023/11/13 09:18:19 tobhe Exp $ */
21e8db6e2SBrian Feldman /*
3efcad6b7SDag-Erling Smørgrav * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
41e8db6e2SBrian Feldman *
5efcad6b7SDag-Erling Smørgrav * Permission to use, copy, modify, and distribute this software for any
6efcad6b7SDag-Erling Smørgrav * purpose with or without fee is hereby granted, provided that the above
7efcad6b7SDag-Erling Smørgrav * copyright notice and this permission notice appear in all copies.
81e8db6e2SBrian Feldman *
9efcad6b7SDag-Erling Smørgrav * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10efcad6b7SDag-Erling Smørgrav * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11efcad6b7SDag-Erling Smørgrav * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12efcad6b7SDag-Erling Smørgrav * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13efcad6b7SDag-Erling Smørgrav * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14efcad6b7SDag-Erling Smørgrav * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15efcad6b7SDag-Erling Smørgrav * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
161e8db6e2SBrian Feldman */
171e8db6e2SBrian Feldman
181e8db6e2SBrian Feldman /* XXX: memleaks */
191e8db6e2SBrian Feldman /* XXX: signed vs unsigned */
20ae1f160dSDag-Erling Smørgrav /* XXX: remove all logging, only return status codes */
211e8db6e2SBrian Feldman /* XXX: copy between two remote sites */
221e8db6e2SBrian Feldman
231e8db6e2SBrian Feldman #include "includes.h"
241e8db6e2SBrian Feldman
25761efaa7SDag-Erling Smørgrav #include <sys/types.h>
26d4af9e69SDag-Erling Smørgrav #ifdef HAVE_SYS_STATVFS_H
27d4af9e69SDag-Erling Smørgrav #include <sys/statvfs.h>
28d4af9e69SDag-Erling Smørgrav #endif
294b17dab0SDag-Erling Smørgrav #include "openbsd-compat/sys-queue.h"
30761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_STAT_H
31761efaa7SDag-Erling Smørgrav # include <sys/stat.h>
32761efaa7SDag-Erling Smørgrav #endif
33761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_TIME_H
34761efaa7SDag-Erling Smørgrav # include <sys/time.h>
35761efaa7SDag-Erling Smørgrav #endif
36761efaa7SDag-Erling Smørgrav #include <sys/uio.h>
37ae1f160dSDag-Erling Smørgrav
38b15c8340SDag-Erling Smørgrav #include <dirent.h>
39761efaa7SDag-Erling Smørgrav #include <errno.h>
4019261079SEd Maste #ifdef HAVE_POLL_H
4119261079SEd Maste #include <poll.h>
4219261079SEd Maste #else
4319261079SEd Maste # ifdef HAVE_SYS_POLL_H
4419261079SEd Maste # include <sys/poll.h>
4519261079SEd Maste # endif
4619261079SEd Maste #endif
47761efaa7SDag-Erling Smørgrav #include <fcntl.h>
48761efaa7SDag-Erling Smørgrav #include <signal.h>
49761efaa7SDag-Erling Smørgrav #include <stdarg.h>
50761efaa7SDag-Erling Smørgrav #include <stdio.h>
51f7167e0eSDag-Erling Smørgrav #include <stdlib.h>
52761efaa7SDag-Erling Smørgrav #include <string.h>
53761efaa7SDag-Erling Smørgrav #include <unistd.h>
54761efaa7SDag-Erling Smørgrav
551e8db6e2SBrian Feldman #include "xmalloc.h"
56bc5531deSDag-Erling Smørgrav #include "ssherr.h"
57bc5531deSDag-Erling Smørgrav #include "sshbuf.h"
581e8db6e2SBrian Feldman #include "log.h"
591e8db6e2SBrian Feldman #include "atomicio.h"
60d0c8c0bcSDag-Erling Smørgrav #include "progressmeter.h"
61761efaa7SDag-Erling Smørgrav #include "misc.h"
62076ad2f8SDag-Erling Smørgrav #include "utf8.h"
631e8db6e2SBrian Feldman
641e8db6e2SBrian Feldman #include "sftp.h"
651e8db6e2SBrian Feldman #include "sftp-common.h"
661e8db6e2SBrian Feldman #include "sftp-client.h"
671e8db6e2SBrian Feldman
68d74d50a8SDag-Erling Smørgrav extern volatile sig_atomic_t interrupted;
69d0c8c0bcSDag-Erling Smørgrav extern int showprogress;
70d0c8c0bcSDag-Erling Smørgrav
71f374ba41SEd Maste /* Default size of buffer for up/download (fix sftp.1 scp.1 if changed) */
7219261079SEd Maste #define DEFAULT_COPY_BUFLEN 32768
7319261079SEd Maste
74f374ba41SEd Maste /* Default number of concurrent xfer requests (fix sftp.1 scp.1 if changed) */
7519261079SEd Maste #define DEFAULT_NUM_REQUESTS 64
7619261079SEd Maste
77761efaa7SDag-Erling Smørgrav /* Minimum amount of data to read at a time */
78ae1f160dSDag-Erling Smørgrav #define MIN_READ_SIZE 512
791e8db6e2SBrian Feldman
80b15c8340SDag-Erling Smørgrav /* Maximum depth to descend in directory trees */
81b15c8340SDag-Erling Smørgrav #define MAX_DIR_DEPTH 64
82b15c8340SDag-Erling Smørgrav
83d93a896eSDag-Erling Smørgrav /* Directory separator characters */
84d93a896eSDag-Erling Smørgrav #ifdef HAVE_CYGWIN
85d93a896eSDag-Erling Smørgrav # define SFTP_DIRECTORY_CHARS "/\\"
86d93a896eSDag-Erling Smørgrav #else /* HAVE_CYGWIN */
87d93a896eSDag-Erling Smørgrav # define SFTP_DIRECTORY_CHARS "/"
88d93a896eSDag-Erling Smørgrav #endif /* HAVE_CYGWIN */
89d93a896eSDag-Erling Smørgrav
90ae1f160dSDag-Erling Smørgrav struct sftp_conn {
91ae1f160dSDag-Erling Smørgrav int fd_in;
92ae1f160dSDag-Erling Smørgrav int fd_out;
9319261079SEd Maste u_int download_buflen;
9419261079SEd Maste u_int upload_buflen;
95ae1f160dSDag-Erling Smørgrav u_int num_requests;
96ae1f160dSDag-Erling Smørgrav u_int version;
97ae1f160dSDag-Erling Smørgrav u_int msg_id;
98d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_POSIX_RENAME 0x00000001
99d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_STATVFS 0x00000002
100d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_FSTATVFS 0x00000004
1014a421b63SDag-Erling Smørgrav #define SFTP_EXT_HARDLINK 0x00000008
102f7167e0eSDag-Erling Smørgrav #define SFTP_EXT_FSYNC 0x00000010
10319261079SEd Maste #define SFTP_EXT_LSETSTAT 0x00000020
10419261079SEd Maste #define SFTP_EXT_LIMITS 0x00000040
10519261079SEd Maste #define SFTP_EXT_PATH_EXPAND 0x00000080
10687c1498dSEd Maste #define SFTP_EXT_COPY_DATA 0x00000100
10738a52bd3SEd Maste #define SFTP_EXT_GETUSERSGROUPS_BY_ID 0x00000200
108d4af9e69SDag-Erling Smørgrav u_int exts;
1094a421b63SDag-Erling Smørgrav u_int64_t limit_kbps;
1104a421b63SDag-Erling Smørgrav struct bwlimit bwlimit_in, bwlimit_out;
111ae1f160dSDag-Erling Smørgrav };
1121e8db6e2SBrian Feldman
11319261079SEd Maste /* Tracks in-progress requests during file transfers */
11419261079SEd Maste struct request {
11519261079SEd Maste u_int id;
11619261079SEd Maste size_t len;
11719261079SEd Maste u_int64_t offset;
11819261079SEd Maste TAILQ_ENTRY(request) tq;
11919261079SEd Maste };
12019261079SEd Maste TAILQ_HEAD(requests, request);
12119261079SEd Maste
122bc5531deSDag-Erling Smørgrav static u_char *
123bc5531deSDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
1244a421b63SDag-Erling Smørgrav const char *errfmt, ...) __attribute__((format(printf, 4, 5)));
1254a421b63SDag-Erling Smørgrav
12619261079SEd Maste static struct request *
request_enqueue(struct requests * requests,u_int id,size_t len,uint64_t offset)12719261079SEd Maste request_enqueue(struct requests *requests, u_int id, size_t len,
12819261079SEd Maste uint64_t offset)
12919261079SEd Maste {
13019261079SEd Maste struct request *req;
13119261079SEd Maste
13219261079SEd Maste req = xcalloc(1, sizeof(*req));
13319261079SEd Maste req->id = id;
13419261079SEd Maste req->len = len;
13519261079SEd Maste req->offset = offset;
13619261079SEd Maste TAILQ_INSERT_TAIL(requests, req, tq);
13719261079SEd Maste return req;
13819261079SEd Maste }
13919261079SEd Maste
14019261079SEd Maste static struct request *
request_find(struct requests * requests,u_int id)14119261079SEd Maste request_find(struct requests *requests, u_int id)
14219261079SEd Maste {
14319261079SEd Maste struct request *req;
14419261079SEd Maste
14519261079SEd Maste for (req = TAILQ_FIRST(requests);
14619261079SEd Maste req != NULL && req->id != id;
14719261079SEd Maste req = TAILQ_NEXT(req, tq))
14819261079SEd Maste ;
14919261079SEd Maste return req;
15019261079SEd Maste }
15119261079SEd Maste
1524a421b63SDag-Erling Smørgrav static int
sftpio(void * _bwlimit,size_t amount)1534a421b63SDag-Erling Smørgrav sftpio(void *_bwlimit, size_t amount)
1544a421b63SDag-Erling Smørgrav {
1554a421b63SDag-Erling Smørgrav struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;
1564a421b63SDag-Erling Smørgrav
15719261079SEd Maste refresh_progress_meter(0);
15819261079SEd Maste if (bwlimit != NULL)
1594a421b63SDag-Erling Smørgrav bandwidth_limit(bwlimit, amount);
1604a421b63SDag-Erling Smørgrav return 0;
1614a421b63SDag-Erling Smørgrav }
162b15c8340SDag-Erling Smørgrav
163ae1f160dSDag-Erling Smørgrav static void
send_msg(struct sftp_conn * conn,struct sshbuf * m)164bc5531deSDag-Erling Smørgrav send_msg(struct sftp_conn *conn, struct sshbuf *m)
1651e8db6e2SBrian Feldman {
166d0c8c0bcSDag-Erling Smørgrav u_char mlen[4];
167761efaa7SDag-Erling Smørgrav struct iovec iov[2];
1681e8db6e2SBrian Feldman
169bc5531deSDag-Erling Smørgrav if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH)
170bc5531deSDag-Erling Smørgrav fatal("Outbound message too long %zu", sshbuf_len(m));
1711e8db6e2SBrian Feldman
172d0c8c0bcSDag-Erling Smørgrav /* Send length first */
173bc5531deSDag-Erling Smørgrav put_u32(mlen, sshbuf_len(m));
174761efaa7SDag-Erling Smørgrav iov[0].iov_base = mlen;
175761efaa7SDag-Erling Smørgrav iov[0].iov_len = sizeof(mlen);
176bc5531deSDag-Erling Smørgrav iov[1].iov_base = (u_char *)sshbuf_ptr(m);
177bc5531deSDag-Erling Smørgrav iov[1].iov_len = sshbuf_len(m);
1781e8db6e2SBrian Feldman
17919261079SEd Maste if (atomiciov6(writev, conn->fd_out, iov, 2, sftpio,
18019261079SEd Maste conn->limit_kbps > 0 ? &conn->bwlimit_out : NULL) !=
181bc5531deSDag-Erling Smørgrav sshbuf_len(m) + sizeof(mlen))
182d0c8c0bcSDag-Erling Smørgrav fatal("Couldn't send packet: %s", strerror(errno));
183d0c8c0bcSDag-Erling Smørgrav
184bc5531deSDag-Erling Smørgrav sshbuf_reset(m);
1851e8db6e2SBrian Feldman }
1861e8db6e2SBrian Feldman
187ae1f160dSDag-Erling Smørgrav static void
get_msg_extended(struct sftp_conn * conn,struct sshbuf * m,int initial)18847dd1d1bSDag-Erling Smørgrav get_msg_extended(struct sftp_conn *conn, struct sshbuf *m, int initial)
1891e8db6e2SBrian Feldman {
190d0c8c0bcSDag-Erling Smørgrav u_int msg_len;
191bc5531deSDag-Erling Smørgrav u_char *p;
192bc5531deSDag-Erling Smørgrav int r;
1931e8db6e2SBrian Feldman
19419261079SEd Maste sshbuf_reset(m);
195bc5531deSDag-Erling Smørgrav if ((r = sshbuf_reserve(m, 4, &p)) != 0)
19619261079SEd Maste fatal_fr(r, "reserve");
19719261079SEd Maste if (atomicio6(read, conn->fd_in, p, 4, sftpio,
19819261079SEd Maste conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL) != 4) {
1994f52dfbbSDag-Erling Smørgrav if (errno == EPIPE || errno == ECONNRESET)
2001e8db6e2SBrian Feldman fatal("Connection closed");
201043840dfSDag-Erling Smørgrav else
2021e8db6e2SBrian Feldman fatal("Couldn't read packet: %s", strerror(errno));
203043840dfSDag-Erling Smørgrav }
2041e8db6e2SBrian Feldman
205bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(m, &msg_len)) != 0)
20619261079SEd Maste fatal_fr(r, "sshbuf_get_u32");
20747dd1d1bSDag-Erling Smørgrav if (msg_len > SFTP_MAX_MSG_LENGTH) {
20847dd1d1bSDag-Erling Smørgrav do_log2(initial ? SYSLOG_LEVEL_ERROR : SYSLOG_LEVEL_FATAL,
20947dd1d1bSDag-Erling Smørgrav "Received message too long %u", msg_len);
21047dd1d1bSDag-Erling Smørgrav fatal("Ensure the remote shell produces no output "
21147dd1d1bSDag-Erling Smørgrav "for non-interactive sessions.");
21247dd1d1bSDag-Erling Smørgrav }
2131e8db6e2SBrian Feldman
214bc5531deSDag-Erling Smørgrav if ((r = sshbuf_reserve(m, msg_len, &p)) != 0)
21519261079SEd Maste fatal_fr(r, "reserve");
21619261079SEd Maste if (atomicio6(read, conn->fd_in, p, msg_len, sftpio,
21719261079SEd Maste conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL)
2184a421b63SDag-Erling Smørgrav != msg_len) {
219043840dfSDag-Erling Smørgrav if (errno == EPIPE)
2201e8db6e2SBrian Feldman fatal("Connection closed");
221043840dfSDag-Erling Smørgrav else
222d0c8c0bcSDag-Erling Smørgrav fatal("Read packet: %s", strerror(errno));
2231e8db6e2SBrian Feldman }
224043840dfSDag-Erling Smørgrav }
2251e8db6e2SBrian Feldman
226ae1f160dSDag-Erling Smørgrav static void
get_msg(struct sftp_conn * conn,struct sshbuf * m)22747dd1d1bSDag-Erling Smørgrav get_msg(struct sftp_conn *conn, struct sshbuf *m)
22847dd1d1bSDag-Erling Smørgrav {
22947dd1d1bSDag-Erling Smørgrav get_msg_extended(conn, m, 0);
23047dd1d1bSDag-Erling Smørgrav }
23147dd1d1bSDag-Erling Smørgrav
23247dd1d1bSDag-Erling Smørgrav static void
send_string_request(struct sftp_conn * conn,u_int id,u_int code,const char * s,u_int len)233bc5531deSDag-Erling Smørgrav send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s,
2341e8db6e2SBrian Feldman u_int len)
2351e8db6e2SBrian Feldman {
236bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
237bc5531deSDag-Erling Smørgrav int r;
2381e8db6e2SBrian Feldman
239bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
24019261079SEd Maste fatal_f("sshbuf_new failed");
241bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, code)) != 0 ||
242bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
243bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, s, len)) != 0)
24419261079SEd Maste fatal_fr(r, "compose");
245bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
2464a421b63SDag-Erling Smørgrav debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
247bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
2481e8db6e2SBrian Feldman }
2491e8db6e2SBrian Feldman
250ae1f160dSDag-Erling Smørgrav static void
send_string_attrs_request(struct sftp_conn * conn,u_int id,u_int code,const void * s,u_int len,Attrib * a)2514a421b63SDag-Erling Smørgrav send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
252bc5531deSDag-Erling Smørgrav const void *s, u_int len, Attrib *a)
2531e8db6e2SBrian Feldman {
254bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
255bc5531deSDag-Erling Smørgrav int r;
2561e8db6e2SBrian Feldman
257bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
25819261079SEd Maste fatal_f("sshbuf_new failed");
259bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, code)) != 0 ||
260bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
261bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, s, len)) != 0 ||
262bc5531deSDag-Erling Smørgrav (r = encode_attrib(msg, a)) != 0)
26319261079SEd Maste fatal_fr(r, "compose");
264bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
26519261079SEd Maste debug3("Sent message fd %d T:%u I:%u F:0x%04x M:%05o",
26619261079SEd Maste conn->fd_out, code, id, a->flags, a->perm);
267bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
2681e8db6e2SBrian Feldman }
2691e8db6e2SBrian Feldman
270ae1f160dSDag-Erling Smørgrav static u_int
get_status(struct sftp_conn * conn,u_int expected_id)2714a421b63SDag-Erling Smørgrav get_status(struct sftp_conn *conn, u_int expected_id)
2721e8db6e2SBrian Feldman {
273bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
274bc5531deSDag-Erling Smørgrav u_char type;
275bc5531deSDag-Erling Smørgrav u_int id, status;
276bc5531deSDag-Erling Smørgrav int r;
2771e8db6e2SBrian Feldman
278bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
27919261079SEd Maste fatal_f("sshbuf_new failed");
280bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
281bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
282bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0)
28319261079SEd Maste fatal_fr(r, "compose");
2841e8db6e2SBrian Feldman
2851e8db6e2SBrian Feldman if (id != expected_id)
286ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id);
2871e8db6e2SBrian Feldman if (type != SSH2_FXP_STATUS)
288ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
2891e8db6e2SBrian Feldman SSH2_FXP_STATUS, type);
2901e8db6e2SBrian Feldman
291bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0)
29219261079SEd Maste fatal_fr(r, "parse");
293bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
2941e8db6e2SBrian Feldman
295ee21a45fSDag-Erling Smørgrav debug3("SSH2_FXP_STATUS %u", status);
2961e8db6e2SBrian Feldman
2974a421b63SDag-Erling Smørgrav return status;
2981e8db6e2SBrian Feldman }
2991e8db6e2SBrian Feldman
300bc5531deSDag-Erling Smørgrav static u_char *
get_handle(struct sftp_conn * conn,u_int expected_id,size_t * len,const char * errfmt,...)301bc5531deSDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
3024a421b63SDag-Erling Smørgrav const char *errfmt, ...)
3031e8db6e2SBrian Feldman {
304bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
305bc5531deSDag-Erling Smørgrav u_int id, status;
306bc5531deSDag-Erling Smørgrav u_char type;
307bc5531deSDag-Erling Smørgrav u_char *handle;
308bc5531deSDag-Erling Smørgrav char errmsg[256];
309b15c8340SDag-Erling Smørgrav va_list args;
310bc5531deSDag-Erling Smørgrav int r;
311b15c8340SDag-Erling Smørgrav
312b15c8340SDag-Erling Smørgrav va_start(args, errfmt);
313b15c8340SDag-Erling Smørgrav if (errfmt != NULL)
314b15c8340SDag-Erling Smørgrav vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
315b15c8340SDag-Erling Smørgrav va_end(args);
3161e8db6e2SBrian Feldman
317bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
31819261079SEd Maste fatal_f("sshbuf_new failed");
319bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
320bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
321bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0)
32219261079SEd Maste fatal_fr(r, "parse");
3231e8db6e2SBrian Feldman
3241e8db6e2SBrian Feldman if (id != expected_id)
325b15c8340SDag-Erling Smørgrav fatal("%s: ID mismatch (%u != %u)",
326b15c8340SDag-Erling Smørgrav errfmt == NULL ? __func__ : errmsg, id, expected_id);
3271e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) {
328bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0)
32919261079SEd Maste fatal_fr(r, "parse status");
330b15c8340SDag-Erling Smørgrav if (errfmt != NULL)
331b15c8340SDag-Erling Smørgrav error("%s: %s", errmsg, fx2txt(status));
332bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
3331e8db6e2SBrian Feldman return(NULL);
3341e8db6e2SBrian Feldman } else if (type != SSH2_FXP_HANDLE)
335b15c8340SDag-Erling Smørgrav fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
336b15c8340SDag-Erling Smørgrav errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
3371e8db6e2SBrian Feldman
338bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_string(msg, &handle, len)) != 0)
33919261079SEd Maste fatal_fr(r, "parse handle");
340bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
3411e8db6e2SBrian Feldman
342bc5531deSDag-Erling Smørgrav return handle;
3431e8db6e2SBrian Feldman }
3441e8db6e2SBrian Feldman
345edf85781SEd Maste static int
get_decode_stat(struct sftp_conn * conn,u_int expected_id,int quiet,Attrib * a)346edf85781SEd Maste get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet, Attrib *a)
3471e8db6e2SBrian Feldman {
348bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
349bc5531deSDag-Erling Smørgrav u_int id;
350bc5531deSDag-Erling Smørgrav u_char type;
351bc5531deSDag-Erling Smørgrav int r;
352edf85781SEd Maste Attrib attr;
3531e8db6e2SBrian Feldman
354edf85781SEd Maste if (a != NULL)
355edf85781SEd Maste memset(a, '\0', sizeof(*a));
356bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
35719261079SEd Maste fatal_f("sshbuf_new failed");
358bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
3591e8db6e2SBrian Feldman
360bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
361bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0)
36219261079SEd Maste fatal_fr(r, "parse");
3631e8db6e2SBrian Feldman
3641e8db6e2SBrian Feldman if (id != expected_id)
365ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id);
3661e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) {
367bc5531deSDag-Erling Smørgrav u_int status;
3681e8db6e2SBrian Feldman
369bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0)
37019261079SEd Maste fatal_fr(r, "parse status");
3711e8db6e2SBrian Feldman if (quiet)
3721323ec57SEd Maste debug("stat remote: %s", fx2txt(status));
3731e8db6e2SBrian Feldman else
3741323ec57SEd Maste error("stat remote: %s", fx2txt(status));
375bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
376edf85781SEd Maste return -1;
3771e8db6e2SBrian Feldman } else if (type != SSH2_FXP_ATTRS) {
378ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
3791e8db6e2SBrian Feldman SSH2_FXP_ATTRS, type);
3801e8db6e2SBrian Feldman }
381edf85781SEd Maste if ((r = decode_attrib(msg, &attr)) != 0) {
38219261079SEd Maste error_fr(r, "decode_attrib");
383bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
384edf85781SEd Maste return -1;
385bc5531deSDag-Erling Smørgrav }
386edf85781SEd Maste /* success */
387edf85781SEd Maste if (a != NULL)
388edf85781SEd Maste *a = attr;
3891323ec57SEd Maste debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o",
390edf85781SEd Maste type, id, attr.flags, attr.perm);
391bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
3921e8db6e2SBrian Feldman
393edf85781SEd Maste return 0;
3941e8db6e2SBrian Feldman }
3951e8db6e2SBrian Feldman
396d4af9e69SDag-Erling Smørgrav static int
get_decode_statvfs(struct sftp_conn * conn,struct sftp_statvfs * st,u_int expected_id,int quiet)3974a421b63SDag-Erling Smørgrav get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
3984a421b63SDag-Erling Smørgrav u_int expected_id, int quiet)
399d4af9e69SDag-Erling Smørgrav {
400bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
401bc5531deSDag-Erling Smørgrav u_char type;
402bc5531deSDag-Erling Smørgrav u_int id;
403bc5531deSDag-Erling Smørgrav u_int64_t flag;
404bc5531deSDag-Erling Smørgrav int r;
405d4af9e69SDag-Erling Smørgrav
406bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
40719261079SEd Maste fatal_f("sshbuf_new failed");
408bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
409d4af9e69SDag-Erling Smørgrav
410bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
411bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0)
41219261079SEd Maste fatal_fr(r, "parse");
413d4af9e69SDag-Erling Smørgrav
414d4af9e69SDag-Erling Smørgrav debug3("Received statvfs reply T:%u I:%u", type, id);
415d4af9e69SDag-Erling Smørgrav if (id != expected_id)
416d4af9e69SDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id);
417d4af9e69SDag-Erling Smørgrav if (type == SSH2_FXP_STATUS) {
418bc5531deSDag-Erling Smørgrav u_int status;
419d4af9e69SDag-Erling Smørgrav
420bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0)
42119261079SEd Maste fatal_fr(r, "parse status");
422d4af9e69SDag-Erling Smørgrav if (quiet)
4231323ec57SEd Maste debug("remote statvfs: %s", fx2txt(status));
424d4af9e69SDag-Erling Smørgrav else
4251323ec57SEd Maste error("remote statvfs: %s", fx2txt(status));
426bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
427d4af9e69SDag-Erling Smørgrav return -1;
428d4af9e69SDag-Erling Smørgrav } else if (type != SSH2_FXP_EXTENDED_REPLY) {
429d4af9e69SDag-Erling Smørgrav fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
430d4af9e69SDag-Erling Smørgrav SSH2_FXP_EXTENDED_REPLY, type);
431d4af9e69SDag-Erling Smørgrav }
432d4af9e69SDag-Erling Smørgrav
433b83788ffSDag-Erling Smørgrav memset(st, 0, sizeof(*st));
434bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 ||
435bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 ||
436bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 ||
437bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 ||
438bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 ||
439bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_files)) != 0 ||
440bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 ||
441bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 ||
442bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 ||
443bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &flag)) != 0 ||
444bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0)
44519261079SEd Maste fatal_fr(r, "parse statvfs");
446d4af9e69SDag-Erling Smørgrav
447d4af9e69SDag-Erling Smørgrav st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
448d4af9e69SDag-Erling Smørgrav st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
449d4af9e69SDag-Erling Smørgrav
450bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
451d4af9e69SDag-Erling Smørgrav
452d4af9e69SDag-Erling Smørgrav return 0;
453d4af9e69SDag-Erling Smørgrav }
454d4af9e69SDag-Erling Smørgrav
455ae1f160dSDag-Erling Smørgrav struct sftp_conn *
sftp_init(int fd_in,int fd_out,u_int transfer_buflen,u_int num_requests,u_int64_t limit_kbps)456edf85781SEd Maste sftp_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
4574a421b63SDag-Erling Smørgrav u_int64_t limit_kbps)
4581e8db6e2SBrian Feldman {
459bc5531deSDag-Erling Smørgrav u_char type;
460bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
461ae1f160dSDag-Erling Smørgrav struct sftp_conn *ret;
462bc5531deSDag-Erling Smørgrav int r;
4631e8db6e2SBrian Feldman
464f7167e0eSDag-Erling Smørgrav ret = xcalloc(1, sizeof(*ret));
465f7167e0eSDag-Erling Smørgrav ret->msg_id = 1;
4664a421b63SDag-Erling Smørgrav ret->fd_in = fd_in;
4674a421b63SDag-Erling Smørgrav ret->fd_out = fd_out;
46819261079SEd Maste ret->download_buflen = ret->upload_buflen =
46919261079SEd Maste transfer_buflen ? transfer_buflen : DEFAULT_COPY_BUFLEN;
47019261079SEd Maste ret->num_requests =
47119261079SEd Maste num_requests ? num_requests : DEFAULT_NUM_REQUESTS;
4724a421b63SDag-Erling Smørgrav ret->exts = 0;
4734a421b63SDag-Erling Smørgrav ret->limit_kbps = 0;
4744a421b63SDag-Erling Smørgrav
475bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
47619261079SEd Maste fatal_f("sshbuf_new failed");
477bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 ||
478bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0)
47919261079SEd Maste fatal_fr(r, "parse");
4801e8db6e2SBrian Feldman
48119261079SEd Maste send_msg(ret, msg);
4821e8db6e2SBrian Feldman
48347dd1d1bSDag-Erling Smørgrav get_msg_extended(ret, msg, 1);
4841e8db6e2SBrian Feldman
4851e8db6e2SBrian Feldman /* Expecting a VERSION reply */
486bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0)
48719261079SEd Maste fatal_fr(r, "parse type");
488bc5531deSDag-Erling Smørgrav if (type != SSH2_FXP_VERSION) {
489ee21a45fSDag-Erling Smørgrav error("Invalid packet back from SSH2_FXP_INIT (type %u)",
4901e8db6e2SBrian Feldman type);
491bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
492557f75e5SDag-Erling Smørgrav free(ret);
493ae1f160dSDag-Erling Smørgrav return(NULL);
4941e8db6e2SBrian Feldman }
495bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &ret->version)) != 0)
49619261079SEd Maste fatal_fr(r, "parse version");
4971e8db6e2SBrian Feldman
4984a421b63SDag-Erling Smørgrav debug2("Remote version: %u", ret->version);
4991e8db6e2SBrian Feldman
5001e8db6e2SBrian Feldman /* Check for extensions */
501bc5531deSDag-Erling Smørgrav while (sshbuf_len(msg) > 0) {
502bc5531deSDag-Erling Smørgrav char *name;
503bc5531deSDag-Erling Smørgrav u_char *value;
504bc5531deSDag-Erling Smørgrav size_t vlen;
505d4af9e69SDag-Erling Smørgrav int known = 0;
5061e8db6e2SBrian Feldman
507bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 ||
508bc5531deSDag-Erling Smørgrav (r = sshbuf_get_string(msg, &value, &vlen)) != 0)
50919261079SEd Maste fatal_fr(r, "parse extension");
510d4af9e69SDag-Erling Smørgrav if (strcmp(name, "posix-rename@openssh.com") == 0 &&
511bc5531deSDag-Erling Smørgrav strcmp((char *)value, "1") == 0) {
5124a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_POSIX_RENAME;
513d4af9e69SDag-Erling Smørgrav known = 1;
514d4af9e69SDag-Erling Smørgrav } else if (strcmp(name, "statvfs@openssh.com") == 0 &&
515bc5531deSDag-Erling Smørgrav strcmp((char *)value, "2") == 0) {
5164a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_STATVFS;
517d4af9e69SDag-Erling Smørgrav known = 1;
5184a421b63SDag-Erling Smørgrav } else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
519bc5531deSDag-Erling Smørgrav strcmp((char *)value, "2") == 0) {
5204a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_FSTATVFS;
5214a421b63SDag-Erling Smørgrav known = 1;
5224a421b63SDag-Erling Smørgrav } else if (strcmp(name, "hardlink@openssh.com") == 0 &&
523bc5531deSDag-Erling Smørgrav strcmp((char *)value, "1") == 0) {
5244a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_HARDLINK;
525d4af9e69SDag-Erling Smørgrav known = 1;
526f7167e0eSDag-Erling Smørgrav } else if (strcmp(name, "fsync@openssh.com") == 0 &&
527bc5531deSDag-Erling Smørgrav strcmp((char *)value, "1") == 0) {
528f7167e0eSDag-Erling Smørgrav ret->exts |= SFTP_EXT_FSYNC;
529f7167e0eSDag-Erling Smørgrav known = 1;
53019261079SEd Maste } else if (strcmp(name, "lsetstat@openssh.com") == 0 &&
53119261079SEd Maste strcmp((char *)value, "1") == 0) {
53219261079SEd Maste ret->exts |= SFTP_EXT_LSETSTAT;
53319261079SEd Maste known = 1;
53419261079SEd Maste } else if (strcmp(name, "limits@openssh.com") == 0 &&
53519261079SEd Maste strcmp((char *)value, "1") == 0) {
53619261079SEd Maste ret->exts |= SFTP_EXT_LIMITS;
53719261079SEd Maste known = 1;
53819261079SEd Maste } else if (strcmp(name, "expand-path@openssh.com") == 0 &&
53919261079SEd Maste strcmp((char *)value, "1") == 0) {
54019261079SEd Maste ret->exts |= SFTP_EXT_PATH_EXPAND;
54119261079SEd Maste known = 1;
54287c1498dSEd Maste } else if (strcmp(name, "copy-data") == 0 &&
54387c1498dSEd Maste strcmp((char *)value, "1") == 0) {
54487c1498dSEd Maste ret->exts |= SFTP_EXT_COPY_DATA;
54587c1498dSEd Maste known = 1;
54638a52bd3SEd Maste } else if (strcmp(name,
54738a52bd3SEd Maste "users-groups-by-id@openssh.com") == 0 &&
54838a52bd3SEd Maste strcmp((char *)value, "1") == 0) {
54938a52bd3SEd Maste ret->exts |= SFTP_EXT_GETUSERSGROUPS_BY_ID;
55038a52bd3SEd Maste known = 1;
551d4af9e69SDag-Erling Smørgrav }
552d4af9e69SDag-Erling Smørgrav if (known) {
553d4af9e69SDag-Erling Smørgrav debug2("Server supports extension \"%s\" revision %s",
554d4af9e69SDag-Erling Smørgrav name, value);
555d4af9e69SDag-Erling Smørgrav } else {
556d4af9e69SDag-Erling Smørgrav debug2("Unrecognised server extension \"%s\"", name);
557d4af9e69SDag-Erling Smørgrav }
558e4a9863fSDag-Erling Smørgrav free(name);
559e4a9863fSDag-Erling Smørgrav free(value);
5601e8db6e2SBrian Feldman }
5611e8db6e2SBrian Feldman
562bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
5631e8db6e2SBrian Feldman
56419261079SEd Maste /* Query the server for its limits */
56519261079SEd Maste if (ret->exts & SFTP_EXT_LIMITS) {
56619261079SEd Maste struct sftp_limits limits;
567edf85781SEd Maste if (sftp_get_limits(ret, &limits) != 0)
56819261079SEd Maste fatal_f("limits failed");
56919261079SEd Maste
57019261079SEd Maste /* If the caller did not specify, find a good value */
57119261079SEd Maste if (transfer_buflen == 0) {
572f374ba41SEd Maste ret->download_buflen = MINIMUM(limits.read_length,
573f374ba41SEd Maste SFTP_MAX_MSG_LENGTH - 1024);
574f374ba41SEd Maste ret->upload_buflen = MINIMUM(limits.write_length,
575f374ba41SEd Maste SFTP_MAX_MSG_LENGTH - 1024);
576f374ba41SEd Maste ret->download_buflen = MAXIMUM(ret->download_buflen, 64);
577f374ba41SEd Maste ret->upload_buflen = MAXIMUM(ret->upload_buflen, 64);
578f374ba41SEd Maste debug3("server upload/download buffer sizes "
579f374ba41SEd Maste "%llu / %llu; using %u / %u",
580f374ba41SEd Maste (unsigned long long)limits.write_length,
581f374ba41SEd Maste (unsigned long long)limits.read_length,
582f374ba41SEd Maste ret->upload_buflen, ret->download_buflen);
58319261079SEd Maste }
58419261079SEd Maste
58519261079SEd Maste /* Use the server limit to scale down our value only */
58619261079SEd Maste if (num_requests == 0 && limits.open_handles) {
58719261079SEd Maste ret->num_requests =
58819261079SEd Maste MINIMUM(DEFAULT_NUM_REQUESTS, limits.open_handles);
589f374ba41SEd Maste if (ret->num_requests == 0)
590f374ba41SEd Maste ret->num_requests = 1;
591f374ba41SEd Maste debug3("server handle limit %llu; using %u",
59219261079SEd Maste (unsigned long long)limits.open_handles,
59319261079SEd Maste ret->num_requests);
59419261079SEd Maste }
59519261079SEd Maste }
59619261079SEd Maste
597ae1f160dSDag-Erling Smørgrav /* Some filexfer v.0 servers don't support large packets */
59819261079SEd Maste if (ret->version == 0) {
59919261079SEd Maste ret->download_buflen = MINIMUM(ret->download_buflen, 20480);
60019261079SEd Maste ret->upload_buflen = MINIMUM(ret->upload_buflen, 20480);
60119261079SEd Maste }
602ae1f160dSDag-Erling Smørgrav
6034a421b63SDag-Erling Smørgrav ret->limit_kbps = limit_kbps;
6044a421b63SDag-Erling Smørgrav if (ret->limit_kbps > 0) {
6054a421b63SDag-Erling Smørgrav bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
60619261079SEd Maste ret->download_buflen);
6074a421b63SDag-Erling Smørgrav bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
60819261079SEd Maste ret->upload_buflen);
6094a421b63SDag-Erling Smørgrav }
6104a421b63SDag-Erling Smørgrav
6114a421b63SDag-Erling Smørgrav return ret;
612ae1f160dSDag-Erling Smørgrav }
613ae1f160dSDag-Erling Smørgrav
614ae1f160dSDag-Erling Smørgrav u_int
sftp_proto_version(struct sftp_conn * conn)615ae1f160dSDag-Erling Smørgrav sftp_proto_version(struct sftp_conn *conn)
616ae1f160dSDag-Erling Smørgrav {
6174a421b63SDag-Erling Smørgrav return conn->version;
6181e8db6e2SBrian Feldman }
6191e8db6e2SBrian Feldman
6201e8db6e2SBrian Feldman int
sftp_get_limits(struct sftp_conn * conn,struct sftp_limits * limits)621edf85781SEd Maste sftp_get_limits(struct sftp_conn *conn, struct sftp_limits *limits)
62219261079SEd Maste {
62319261079SEd Maste u_int id, msg_id;
62419261079SEd Maste u_char type;
62519261079SEd Maste struct sshbuf *msg;
62619261079SEd Maste int r;
62719261079SEd Maste
62819261079SEd Maste if ((conn->exts & SFTP_EXT_LIMITS) == 0) {
62919261079SEd Maste error("Server does not support limits@openssh.com extension");
63019261079SEd Maste return -1;
63119261079SEd Maste }
63219261079SEd Maste
63319261079SEd Maste if ((msg = sshbuf_new()) == NULL)
63419261079SEd Maste fatal_f("sshbuf_new failed");
63519261079SEd Maste
63619261079SEd Maste id = conn->msg_id++;
63719261079SEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
63819261079SEd Maste (r = sshbuf_put_u32(msg, id)) != 0 ||
63919261079SEd Maste (r = sshbuf_put_cstring(msg, "limits@openssh.com")) != 0)
64019261079SEd Maste fatal_fr(r, "compose");
64119261079SEd Maste send_msg(conn, msg);
64219261079SEd Maste debug3("Sent message limits@openssh.com I:%u", id);
64319261079SEd Maste
64419261079SEd Maste get_msg(conn, msg);
64519261079SEd Maste
64619261079SEd Maste if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
64719261079SEd Maste (r = sshbuf_get_u32(msg, &msg_id)) != 0)
64819261079SEd Maste fatal_fr(r, "parse");
64919261079SEd Maste
65019261079SEd Maste debug3("Received limits reply T:%u I:%u", type, msg_id);
65119261079SEd Maste if (id != msg_id)
65219261079SEd Maste fatal("ID mismatch (%u != %u)", msg_id, id);
65319261079SEd Maste if (type != SSH2_FXP_EXTENDED_REPLY) {
65419261079SEd Maste debug_f("expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
65519261079SEd Maste SSH2_FXP_EXTENDED_REPLY, type);
65619261079SEd Maste /* Disable the limits extension */
65719261079SEd Maste conn->exts &= ~SFTP_EXT_LIMITS;
65819261079SEd Maste sshbuf_free(msg);
659*069ac184SEd Maste return -1;
66019261079SEd Maste }
66119261079SEd Maste
66219261079SEd Maste memset(limits, 0, sizeof(*limits));
66319261079SEd Maste if ((r = sshbuf_get_u64(msg, &limits->packet_length)) != 0 ||
66419261079SEd Maste (r = sshbuf_get_u64(msg, &limits->read_length)) != 0 ||
66519261079SEd Maste (r = sshbuf_get_u64(msg, &limits->write_length)) != 0 ||
66619261079SEd Maste (r = sshbuf_get_u64(msg, &limits->open_handles)) != 0)
66719261079SEd Maste fatal_fr(r, "parse limits");
66819261079SEd Maste
66919261079SEd Maste sshbuf_free(msg);
67019261079SEd Maste
67119261079SEd Maste return 0;
67219261079SEd Maste }
67319261079SEd Maste
67419261079SEd Maste int
sftp_close(struct sftp_conn * conn,const u_char * handle,u_int handle_len)675edf85781SEd Maste sftp_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
6761e8db6e2SBrian Feldman {
6771e8db6e2SBrian Feldman u_int id, status;
678bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
679bc5531deSDag-Erling Smørgrav int r;
6801e8db6e2SBrian Feldman
681bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
68219261079SEd Maste fatal_f("sshbuf_new failed");
6831e8db6e2SBrian Feldman
684ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
685bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 ||
686bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
687bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
68819261079SEd Maste fatal_fr(r, "parse");
689bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
690ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
6911e8db6e2SBrian Feldman
6924a421b63SDag-Erling Smørgrav status = get_status(conn, id);
6931e8db6e2SBrian Feldman if (status != SSH2_FX_OK)
6941323ec57SEd Maste error("close remote: %s", fx2txt(status));
6951e8db6e2SBrian Feldman
696bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
6971e8db6e2SBrian Feldman
698bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
6991e8db6e2SBrian Feldman }
7001e8db6e2SBrian Feldman
7011e8db6e2SBrian Feldman
702ae1f160dSDag-Erling Smørgrav static int
sftp_lsreaddir(struct sftp_conn * conn,const char * path,int print_flag,SFTP_DIRENT *** dir)703edf85781SEd Maste sftp_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
7041e8db6e2SBrian Feldman SFTP_DIRENT ***dir)
7051e8db6e2SBrian Feldman {
706bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
707bc5531deSDag-Erling Smørgrav u_int count, id, i, expected_id, ents = 0;
708bc5531deSDag-Erling Smørgrav size_t handle_len;
709076ad2f8SDag-Erling Smørgrav u_char type, *handle;
710f7167e0eSDag-Erling Smørgrav int status = SSH2_FX_FAILURE;
711bc5531deSDag-Erling Smørgrav int r;
712f7167e0eSDag-Erling Smørgrav
713f7167e0eSDag-Erling Smørgrav if (dir)
714f7167e0eSDag-Erling Smørgrav *dir = NULL;
7151e8db6e2SBrian Feldman
716ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
7171e8db6e2SBrian Feldman
718bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
71919261079SEd Maste fatal_f("sshbuf_new failed");
720bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 ||
721bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
722bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, path)) != 0)
72319261079SEd Maste fatal_fr(r, "compose OPENDIR");
724bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
7251e8db6e2SBrian Feldman
7264a421b63SDag-Erling Smørgrav handle = get_handle(conn, id, &handle_len,
727b15c8340SDag-Erling Smørgrav "remote readdir(\"%s\")", path);
728462c32cbSDag-Erling Smørgrav if (handle == NULL) {
729bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
7304a421b63SDag-Erling Smørgrav return -1;
731462c32cbSDag-Erling Smørgrav }
7321e8db6e2SBrian Feldman
7331e8db6e2SBrian Feldman if (dir) {
7341e8db6e2SBrian Feldman ents = 0;
7350a37d4a3SXin LI *dir = xcalloc(1, sizeof(**dir));
7361e8db6e2SBrian Feldman (*dir)[0] = NULL;
7371e8db6e2SBrian Feldman }
7381e8db6e2SBrian Feldman
739d74d50a8SDag-Erling Smørgrav for (; !interrupted;) {
740ae1f160dSDag-Erling Smørgrav id = expected_id = conn->msg_id++;
7411e8db6e2SBrian Feldman
742ee21a45fSDag-Erling Smørgrav debug3("Sending SSH2_FXP_READDIR I:%u", id);
7431e8db6e2SBrian Feldman
744bc5531deSDag-Erling Smørgrav sshbuf_reset(msg);
745bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 ||
746bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
747bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
74819261079SEd Maste fatal_fr(r, "compose READDIR");
749bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
7501e8db6e2SBrian Feldman
751bc5531deSDag-Erling Smørgrav sshbuf_reset(msg);
7521e8db6e2SBrian Feldman
753bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
7541e8db6e2SBrian Feldman
755bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
756bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0)
75719261079SEd Maste fatal_fr(r, "parse");
7581e8db6e2SBrian Feldman
759ee21a45fSDag-Erling Smørgrav debug3("Received reply T:%u I:%u", type, id);
7601e8db6e2SBrian Feldman
7611e8db6e2SBrian Feldman if (id != expected_id)
762ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id);
7631e8db6e2SBrian Feldman
7641e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) {
765bc5531deSDag-Erling Smørgrav u_int rstatus;
766bc5531deSDag-Erling Smørgrav
767bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &rstatus)) != 0)
76819261079SEd Maste fatal_fr(r, "parse status");
769bc5531deSDag-Erling Smørgrav debug3("Received SSH2_FXP_STATUS %d", rstatus);
770bc5531deSDag-Erling Smørgrav if (rstatus == SSH2_FX_EOF)
7711e8db6e2SBrian Feldman break;
772bc5531deSDag-Erling Smørgrav error("Couldn't read directory: %s", fx2txt(rstatus));
773f7167e0eSDag-Erling Smørgrav goto out;
7741e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME)
775ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
7761e8db6e2SBrian Feldman SSH2_FXP_NAME, type);
7771e8db6e2SBrian Feldman
778bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &count)) != 0)
77919261079SEd Maste fatal_fr(r, "parse count");
780d93a896eSDag-Erling Smørgrav if (count > SSHBUF_SIZE_MAX)
78119261079SEd Maste fatal_f("nonsensical number of entries");
7821e8db6e2SBrian Feldman if (count == 0)
7831e8db6e2SBrian Feldman break;
7841e8db6e2SBrian Feldman debug3("Received %d SSH2_FXP_NAME responses", count);
7851e8db6e2SBrian Feldman for (i = 0; i < count; i++) {
7861e8db6e2SBrian Feldman char *filename, *longname;
787bc5531deSDag-Erling Smørgrav Attrib a;
7881e8db6e2SBrian Feldman
789bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(msg, &filename,
790bc5531deSDag-Erling Smørgrav NULL)) != 0 ||
791bc5531deSDag-Erling Smørgrav (r = sshbuf_get_cstring(msg, &longname,
792bc5531deSDag-Erling Smørgrav NULL)) != 0)
79319261079SEd Maste fatal_fr(r, "parse filenames");
794bc5531deSDag-Erling Smørgrav if ((r = decode_attrib(msg, &a)) != 0) {
79519261079SEd Maste error_fr(r, "couldn't decode attrib");
796bc5531deSDag-Erling Smørgrav free(filename);
797bc5531deSDag-Erling Smørgrav free(longname);
79819261079SEd Maste goto out;
799bc5531deSDag-Erling Smørgrav }
8001e8db6e2SBrian Feldman
801f7167e0eSDag-Erling Smørgrav if (print_flag)
802076ad2f8SDag-Erling Smørgrav mprintf("%s\n", longname);
8031e8db6e2SBrian Feldman
804b15c8340SDag-Erling Smørgrav /*
805b15c8340SDag-Erling Smørgrav * Directory entries should never contain '/'
806b15c8340SDag-Erling Smørgrav * These can be used to attack recursive ops
807b15c8340SDag-Erling Smørgrav * (e.g. send '../../../../etc/passwd')
808b15c8340SDag-Erling Smørgrav */
809d93a896eSDag-Erling Smørgrav if (strpbrk(filename, SFTP_DIRECTORY_CHARS) != NULL) {
810b15c8340SDag-Erling Smørgrav error("Server sent suspect path \"%s\" "
811b15c8340SDag-Erling Smørgrav "during readdir of \"%s\"", filename, path);
812f7167e0eSDag-Erling Smørgrav } else if (dir) {
813557f75e5SDag-Erling Smørgrav *dir = xreallocarray(*dir, ents + 2, sizeof(**dir));
8140a37d4a3SXin LI (*dir)[ents] = xcalloc(1, sizeof(***dir));
8151e8db6e2SBrian Feldman (*dir)[ents]->filename = xstrdup(filename);
8161e8db6e2SBrian Feldman (*dir)[ents]->longname = xstrdup(longname);
817bc5531deSDag-Erling Smørgrav memcpy(&(*dir)[ents]->a, &a, sizeof(a));
8181e8db6e2SBrian Feldman (*dir)[++ents] = NULL;
8191e8db6e2SBrian Feldman }
820e4a9863fSDag-Erling Smørgrav free(filename);
821e4a9863fSDag-Erling Smørgrav free(longname);
8221e8db6e2SBrian Feldman }
8231e8db6e2SBrian Feldman }
824f7167e0eSDag-Erling Smørgrav status = 0;
8251e8db6e2SBrian Feldman
826f7167e0eSDag-Erling Smørgrav out:
827bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
828edf85781SEd Maste sftp_close(conn, handle, handle_len);
829e4a9863fSDag-Erling Smørgrav free(handle);
8301e8db6e2SBrian Feldman
831f7167e0eSDag-Erling Smørgrav if (status != 0 && dir != NULL) {
832f7167e0eSDag-Erling Smørgrav /* Don't return results on error */
833edf85781SEd Maste sftp_free_dirents(*dir);
834f7167e0eSDag-Erling Smørgrav *dir = NULL;
835f7167e0eSDag-Erling Smørgrav } else if (interrupted && dir != NULL && *dir != NULL) {
836d74d50a8SDag-Erling Smørgrav /* Don't return partial matches on interrupt */
837edf85781SEd Maste sftp_free_dirents(*dir);
8380a37d4a3SXin LI *dir = xcalloc(1, sizeof(**dir));
839d74d50a8SDag-Erling Smørgrav **dir = NULL;
840d74d50a8SDag-Erling Smørgrav }
841d74d50a8SDag-Erling Smørgrav
842190cef3dSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
8431e8db6e2SBrian Feldman }
8441e8db6e2SBrian Feldman
8451e8db6e2SBrian Feldman int
sftp_readdir(struct sftp_conn * conn,const char * path,SFTP_DIRENT *** dir)846edf85781SEd Maste sftp_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
8471e8db6e2SBrian Feldman {
848edf85781SEd Maste return sftp_lsreaddir(conn, path, 0, dir);
8491e8db6e2SBrian Feldman }
8501e8db6e2SBrian Feldman
sftp_free_dirents(SFTP_DIRENT ** s)851edf85781SEd Maste void sftp_free_dirents(SFTP_DIRENT **s)
8521e8db6e2SBrian Feldman {
8531e8db6e2SBrian Feldman int i;
8541e8db6e2SBrian Feldman
855f7167e0eSDag-Erling Smørgrav if (s == NULL)
856f7167e0eSDag-Erling Smørgrav return;
8571e8db6e2SBrian Feldman for (i = 0; s[i]; i++) {
858e4a9863fSDag-Erling Smørgrav free(s[i]->filename);
859e4a9863fSDag-Erling Smørgrav free(s[i]->longname);
860e4a9863fSDag-Erling Smørgrav free(s[i]);
8611e8db6e2SBrian Feldman }
862e4a9863fSDag-Erling Smørgrav free(s);
8631e8db6e2SBrian Feldman }
8641e8db6e2SBrian Feldman
8651e8db6e2SBrian Feldman int
sftp_rm(struct sftp_conn * conn,const char * path)866edf85781SEd Maste sftp_rm(struct sftp_conn *conn, const char *path)
8671e8db6e2SBrian Feldman {
8681e8db6e2SBrian Feldman u_int status, id;
8691e8db6e2SBrian Feldman
8701e8db6e2SBrian Feldman debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
8711e8db6e2SBrian Feldman
872ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
8734a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
8744a421b63SDag-Erling Smørgrav status = get_status(conn, id);
8751e8db6e2SBrian Feldman if (status != SSH2_FX_OK)
8761323ec57SEd Maste error("remote delete %s: %s", path, fx2txt(status));
877bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
8781e8db6e2SBrian Feldman }
8791e8db6e2SBrian Feldman
8801e8db6e2SBrian Feldman int
sftp_mkdir(struct sftp_conn * conn,const char * path,Attrib * a,int print_flag)881edf85781SEd Maste sftp_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
8821e8db6e2SBrian Feldman {
8831e8db6e2SBrian Feldman u_int status, id;
8841e8db6e2SBrian Feldman
8851323ec57SEd Maste debug2("Sending SSH2_FXP_MKDIR \"%s\"", path);
8861323ec57SEd Maste
887ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
8884a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
8891e8db6e2SBrian Feldman strlen(path), a);
8901e8db6e2SBrian Feldman
8914a421b63SDag-Erling Smørgrav status = get_status(conn, id);
892f7167e0eSDag-Erling Smørgrav if (status != SSH2_FX_OK && print_flag)
8931323ec57SEd Maste error("remote mkdir \"%s\": %s", path, fx2txt(status));
8941e8db6e2SBrian Feldman
895bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
8961e8db6e2SBrian Feldman }
8971e8db6e2SBrian Feldman
8981e8db6e2SBrian Feldman int
sftp_rmdir(struct sftp_conn * conn,const char * path)899edf85781SEd Maste sftp_rmdir(struct sftp_conn *conn, const char *path)
9001e8db6e2SBrian Feldman {
9011e8db6e2SBrian Feldman u_int status, id;
9021e8db6e2SBrian Feldman
9031323ec57SEd Maste debug2("Sending SSH2_FXP_RMDIR \"%s\"", path);
9041323ec57SEd Maste
905ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
9064a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_RMDIR, path,
907ae1f160dSDag-Erling Smørgrav strlen(path));
9081e8db6e2SBrian Feldman
9094a421b63SDag-Erling Smørgrav status = get_status(conn, id);
9101e8db6e2SBrian Feldman if (status != SSH2_FX_OK)
9111323ec57SEd Maste error("remote rmdir \"%s\": %s", path, fx2txt(status));
9121e8db6e2SBrian Feldman
913bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
9141e8db6e2SBrian Feldman }
9151e8db6e2SBrian Feldman
916edf85781SEd Maste int
sftp_stat(struct sftp_conn * conn,const char * path,int quiet,Attrib * a)917edf85781SEd Maste sftp_stat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a)
9181e8db6e2SBrian Feldman {
9191e8db6e2SBrian Feldman u_int id;
9201e8db6e2SBrian Feldman
9211323ec57SEd Maste debug2("Sending SSH2_FXP_STAT \"%s\"", path);
9221323ec57SEd Maste
923ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
924ae1f160dSDag-Erling Smørgrav
9254a421b63SDag-Erling Smørgrav send_string_request(conn, id,
926ae1f160dSDag-Erling Smørgrav conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
927ae1f160dSDag-Erling Smørgrav path, strlen(path));
928ae1f160dSDag-Erling Smørgrav
929edf85781SEd Maste return get_decode_stat(conn, id, quiet, a);
9301e8db6e2SBrian Feldman }
9311e8db6e2SBrian Feldman
932edf85781SEd Maste int
sftp_lstat(struct sftp_conn * conn,const char * path,int quiet,Attrib * a)933edf85781SEd Maste sftp_lstat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a)
9341e8db6e2SBrian Feldman {
9351e8db6e2SBrian Feldman u_int id;
9361e8db6e2SBrian Feldman
937ae1f160dSDag-Erling Smørgrav if (conn->version == 0) {
938edf85781SEd Maste do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO,
939edf85781SEd Maste "Server version does not support lstat operation");
940edf85781SEd Maste return sftp_stat(conn, path, quiet, a);
941ae1f160dSDag-Erling Smørgrav }
942ae1f160dSDag-Erling Smørgrav
943ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
9444a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_LSTAT, path,
945ae1f160dSDag-Erling Smørgrav strlen(path));
946ae1f160dSDag-Erling Smørgrav
947edf85781SEd Maste return get_decode_stat(conn, id, quiet, a);
9481e8db6e2SBrian Feldman }
9491e8db6e2SBrian Feldman
950d4af9e69SDag-Erling Smørgrav #ifdef notyet
951edf85781SEd Maste int
sftp_fstat(struct sftp_conn * conn,const u_char * handle,u_int handle_len,int quiet,Attrib * a)952edf85781SEd Maste sftp_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
953edf85781SEd Maste int quiet, Attrib *a)
9541e8db6e2SBrian Feldman {
9551e8db6e2SBrian Feldman u_int id;
9561e8db6e2SBrian Feldman
9571323ec57SEd Maste debug2("Sending SSH2_FXP_FSTAT \"%s\"");
9581323ec57SEd Maste
959ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
9604a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
961ae1f160dSDag-Erling Smørgrav handle_len);
962ae1f160dSDag-Erling Smørgrav
963edf85781SEd Maste return get_decode_stat(conn, id, quiet, a);
9641e8db6e2SBrian Feldman }
965d4af9e69SDag-Erling Smørgrav #endif
9661e8db6e2SBrian Feldman
9671e8db6e2SBrian Feldman int
sftp_setstat(struct sftp_conn * conn,const char * path,Attrib * a)968edf85781SEd Maste sftp_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
9691e8db6e2SBrian Feldman {
9701e8db6e2SBrian Feldman u_int status, id;
9711e8db6e2SBrian Feldman
9721323ec57SEd Maste debug2("Sending SSH2_FXP_SETSTAT \"%s\"", path);
9731323ec57SEd Maste
974ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
9754a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
9761e8db6e2SBrian Feldman strlen(path), a);
9771e8db6e2SBrian Feldman
9784a421b63SDag-Erling Smørgrav status = get_status(conn, id);
9791e8db6e2SBrian Feldman if (status != SSH2_FX_OK)
9801323ec57SEd Maste error("remote setstat \"%s\": %s", path, fx2txt(status));
9811e8db6e2SBrian Feldman
982bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
9831e8db6e2SBrian Feldman }
9841e8db6e2SBrian Feldman
9851e8db6e2SBrian Feldman int
sftp_fsetstat(struct sftp_conn * conn,const u_char * handle,u_int handle_len,Attrib * a)986edf85781SEd Maste sftp_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
9871e8db6e2SBrian Feldman Attrib *a)
9881e8db6e2SBrian Feldman {
9891e8db6e2SBrian Feldman u_int status, id;
9901e8db6e2SBrian Feldman
9911323ec57SEd Maste debug2("Sending SSH2_FXP_FSETSTAT");
9921323ec57SEd Maste
993ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
9944a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
9951e8db6e2SBrian Feldman handle_len, a);
9961e8db6e2SBrian Feldman
9974a421b63SDag-Erling Smørgrav status = get_status(conn, id);
9981e8db6e2SBrian Feldman if (status != SSH2_FX_OK)
9991323ec57SEd Maste error("remote fsetstat: %s", fx2txt(status));
10001e8db6e2SBrian Feldman
1001bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
10021e8db6e2SBrian Feldman }
10031e8db6e2SBrian Feldman
100419261079SEd Maste /* Implements both the realpath and expand-path operations */
100519261079SEd Maste static char *
sftp_realpath_expand(struct sftp_conn * conn,const char * path,int expand)1006edf85781SEd Maste sftp_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
10071e8db6e2SBrian Feldman {
1008bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
1009bc5531deSDag-Erling Smørgrav u_int expected_id, count, id;
10101e8db6e2SBrian Feldman char *filename, *longname;
1011bc5531deSDag-Erling Smørgrav Attrib a;
1012bc5531deSDag-Erling Smørgrav u_char type;
1013bc5531deSDag-Erling Smørgrav int r;
101419261079SEd Maste const char *what = "SSH2_FXP_REALPATH";
101519261079SEd Maste
101619261079SEd Maste if (expand)
101719261079SEd Maste what = "expand-path@openssh.com";
101819261079SEd Maste if ((msg = sshbuf_new()) == NULL)
101919261079SEd Maste fatal_f("sshbuf_new failed");
10201e8db6e2SBrian Feldman
1021ae1f160dSDag-Erling Smørgrav expected_id = id = conn->msg_id++;
102219261079SEd Maste if (expand) {
10231323ec57SEd Maste debug2("Sending SSH2_FXP_EXTENDED(expand-path@openssh.com) "
10241323ec57SEd Maste "\"%s\"", path);
102519261079SEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
102619261079SEd Maste (r = sshbuf_put_u32(msg, id)) != 0 ||
102719261079SEd Maste (r = sshbuf_put_cstring(msg,
102819261079SEd Maste "expand-path@openssh.com")) != 0 ||
102919261079SEd Maste (r = sshbuf_put_cstring(msg, path)) != 0)
103019261079SEd Maste fatal_fr(r, "compose %s", what);
103119261079SEd Maste send_msg(conn, msg);
103219261079SEd Maste } else {
10331323ec57SEd Maste debug2("Sending SSH2_FXP_REALPATH \"%s\"", path);
103419261079SEd Maste send_string_request(conn, id, SSH2_FXP_REALPATH,
103519261079SEd Maste path, strlen(path));
103619261079SEd Maste }
1037bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
1038bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1039bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0)
104019261079SEd Maste fatal_fr(r, "parse");
10411e8db6e2SBrian Feldman
10421e8db6e2SBrian Feldman if (id != expected_id)
1043ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id);
10441e8db6e2SBrian Feldman
10451e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) {
1046bc5531deSDag-Erling Smørgrav u_int status;
10471323ec57SEd Maste char *errmsg;
10481e8db6e2SBrian Feldman
10491323ec57SEd Maste if ((r = sshbuf_get_u32(msg, &status)) != 0 ||
10501323ec57SEd Maste (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0)
105119261079SEd Maste fatal_fr(r, "parse status");
10521323ec57SEd Maste error("%s %s: %s", expand ? "expand" : "realpath",
10531323ec57SEd Maste path, *errmsg == '\0' ? fx2txt(status) : errmsg);
10541323ec57SEd Maste free(errmsg);
1055bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
1056e2f6069cSDag-Erling Smørgrav return NULL;
10571e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME)
1058ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
10591e8db6e2SBrian Feldman SSH2_FXP_NAME, type);
10601e8db6e2SBrian Feldman
1061bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &count)) != 0)
106219261079SEd Maste fatal_fr(r, "parse count");
10631e8db6e2SBrian Feldman if (count != 1)
106419261079SEd Maste fatal("Got multiple names (%d) from %s", count, what);
10651e8db6e2SBrian Feldman
1066bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1067bc5531deSDag-Erling Smørgrav (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1068bc5531deSDag-Erling Smørgrav (r = decode_attrib(msg, &a)) != 0)
106919261079SEd Maste fatal_fr(r, "parse filename/attrib");
10701e8db6e2SBrian Feldman
107119261079SEd Maste debug3("%s %s -> %s", what, path, filename);
10721e8db6e2SBrian Feldman
1073e4a9863fSDag-Erling Smørgrav free(longname);
10741e8db6e2SBrian Feldman
1075bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
10761e8db6e2SBrian Feldman
10771e8db6e2SBrian Feldman return(filename);
10781e8db6e2SBrian Feldman }
10791e8db6e2SBrian Feldman
108019261079SEd Maste char *
sftp_realpath(struct sftp_conn * conn,const char * path)1081edf85781SEd Maste sftp_realpath(struct sftp_conn *conn, const char *path)
108219261079SEd Maste {
1083edf85781SEd Maste return sftp_realpath_expand(conn, path, 0);
108419261079SEd Maste }
108519261079SEd Maste
108619261079SEd Maste int
sftp_can_expand_path(struct sftp_conn * conn)1087edf85781SEd Maste sftp_can_expand_path(struct sftp_conn *conn)
108819261079SEd Maste {
108919261079SEd Maste return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0;
109019261079SEd Maste }
109119261079SEd Maste
109219261079SEd Maste char *
sftp_expand_path(struct sftp_conn * conn,const char * path)1093edf85781SEd Maste sftp_expand_path(struct sftp_conn *conn, const char *path)
109419261079SEd Maste {
1095edf85781SEd Maste if (!sftp_can_expand_path(conn)) {
109619261079SEd Maste debug3_f("no server support, fallback to realpath");
1097edf85781SEd Maste return sftp_realpath_expand(conn, path, 0);
109819261079SEd Maste }
1099edf85781SEd Maste return sftp_realpath_expand(conn, path, 1);
110019261079SEd Maste }
110119261079SEd Maste
11021e8db6e2SBrian Feldman int
sftp_copy(struct sftp_conn * conn,const char * oldpath,const char * newpath)1103edf85781SEd Maste sftp_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
110487c1498dSEd Maste {
1105edf85781SEd Maste Attrib junk, attr;
110687c1498dSEd Maste struct sshbuf *msg;
110787c1498dSEd Maste u_char *old_handle, *new_handle;
110887c1498dSEd Maste u_int mode, status, id;
110987c1498dSEd Maste size_t old_handle_len, new_handle_len;
111087c1498dSEd Maste int r;
111187c1498dSEd Maste
111287c1498dSEd Maste /* Return if the extension is not supported */
111387c1498dSEd Maste if ((conn->exts & SFTP_EXT_COPY_DATA) == 0) {
111487c1498dSEd Maste error("Server does not support copy-data extension");
111587c1498dSEd Maste return -1;
111687c1498dSEd Maste }
111787c1498dSEd Maste
111887c1498dSEd Maste /* Make sure the file exists, and we can copy its perms */
1119edf85781SEd Maste if (sftp_stat(conn, oldpath, 0, &attr) != 0)
112087c1498dSEd Maste return -1;
112187c1498dSEd Maste
112287c1498dSEd Maste /* Do not preserve set[ug]id here, as we do not preserve ownership */
1123edf85781SEd Maste if (attr.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
1124edf85781SEd Maste mode = attr.perm & 0777;
112587c1498dSEd Maste
1126edf85781SEd Maste if (!S_ISREG(attr.perm)) {
112787c1498dSEd Maste error("Cannot copy non-regular file: %s", oldpath);
112887c1498dSEd Maste return -1;
112987c1498dSEd Maste }
113087c1498dSEd Maste } else {
113187c1498dSEd Maste /* NB: The user's umask will apply to this */
113287c1498dSEd Maste mode = 0666;
113387c1498dSEd Maste }
113487c1498dSEd Maste
113587c1498dSEd Maste /* Set up the new perms for the new file */
1136edf85781SEd Maste attrib_clear(&attr);
1137edf85781SEd Maste attr.perm = mode;
1138edf85781SEd Maste attr.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
113987c1498dSEd Maste
114087c1498dSEd Maste if ((msg = sshbuf_new()) == NULL)
114187c1498dSEd Maste fatal("%s: sshbuf_new failed", __func__);
114287c1498dSEd Maste
114387c1498dSEd Maste attrib_clear(&junk); /* Send empty attributes */
114487c1498dSEd Maste
114587c1498dSEd Maste /* Open the old file for reading */
114687c1498dSEd Maste id = conn->msg_id++;
114787c1498dSEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
114887c1498dSEd Maste (r = sshbuf_put_u32(msg, id)) != 0 ||
114987c1498dSEd Maste (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
115087c1498dSEd Maste (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 ||
115187c1498dSEd Maste (r = encode_attrib(msg, &junk)) != 0)
115287c1498dSEd Maste fatal("%s: buffer error: %s", __func__, ssh_err(r));
115387c1498dSEd Maste send_msg(conn, msg);
115487c1498dSEd Maste debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, oldpath);
115587c1498dSEd Maste
115687c1498dSEd Maste sshbuf_reset(msg);
115787c1498dSEd Maste
115887c1498dSEd Maste old_handle = get_handle(conn, id, &old_handle_len,
115987c1498dSEd Maste "remote open(\"%s\")", oldpath);
116087c1498dSEd Maste if (old_handle == NULL) {
116187c1498dSEd Maste sshbuf_free(msg);
116287c1498dSEd Maste return -1;
116387c1498dSEd Maste }
116487c1498dSEd Maste
116587c1498dSEd Maste /* Open the new file for writing */
116687c1498dSEd Maste id = conn->msg_id++;
116787c1498dSEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
116887c1498dSEd Maste (r = sshbuf_put_u32(msg, id)) != 0 ||
116987c1498dSEd Maste (r = sshbuf_put_cstring(msg, newpath)) != 0 ||
117087c1498dSEd Maste (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|
117187c1498dSEd Maste SSH2_FXF_TRUNC)) != 0 ||
1172edf85781SEd Maste (r = encode_attrib(msg, &attr)) != 0)
117387c1498dSEd Maste fatal("%s: buffer error: %s", __func__, ssh_err(r));
117487c1498dSEd Maste send_msg(conn, msg);
117587c1498dSEd Maste debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath);
117687c1498dSEd Maste
117787c1498dSEd Maste sshbuf_reset(msg);
117887c1498dSEd Maste
117987c1498dSEd Maste new_handle = get_handle(conn, id, &new_handle_len,
118087c1498dSEd Maste "remote open(\"%s\")", newpath);
118187c1498dSEd Maste if (new_handle == NULL) {
118287c1498dSEd Maste sshbuf_free(msg);
118387c1498dSEd Maste free(old_handle);
118487c1498dSEd Maste return -1;
118587c1498dSEd Maste }
118687c1498dSEd Maste
118787c1498dSEd Maste /* Copy the file data */
118887c1498dSEd Maste id = conn->msg_id++;
118987c1498dSEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
119087c1498dSEd Maste (r = sshbuf_put_u32(msg, id)) != 0 ||
119187c1498dSEd Maste (r = sshbuf_put_cstring(msg, "copy-data")) != 0 ||
119287c1498dSEd Maste (r = sshbuf_put_string(msg, old_handle, old_handle_len)) != 0 ||
119387c1498dSEd Maste (r = sshbuf_put_u64(msg, 0)) != 0 ||
119487c1498dSEd Maste (r = sshbuf_put_u64(msg, 0)) != 0 ||
119587c1498dSEd Maste (r = sshbuf_put_string(msg, new_handle, new_handle_len)) != 0 ||
119687c1498dSEd Maste (r = sshbuf_put_u64(msg, 0)) != 0)
119787c1498dSEd Maste fatal("%s: buffer error: %s", __func__, ssh_err(r));
119887c1498dSEd Maste send_msg(conn, msg);
119987c1498dSEd Maste debug3("Sent message copy-data \"%s\" 0 0 -> \"%s\" 0",
120087c1498dSEd Maste oldpath, newpath);
120187c1498dSEd Maste
120287c1498dSEd Maste status = get_status(conn, id);
120387c1498dSEd Maste if (status != SSH2_FX_OK)
120487c1498dSEd Maste error("Couldn't copy file \"%s\" to \"%s\": %s", oldpath,
120587c1498dSEd Maste newpath, fx2txt(status));
120687c1498dSEd Maste
120787c1498dSEd Maste /* Clean up everything */
120887c1498dSEd Maste sshbuf_free(msg);
1209edf85781SEd Maste sftp_close(conn, old_handle, old_handle_len);
1210edf85781SEd Maste sftp_close(conn, new_handle, new_handle_len);
121187c1498dSEd Maste free(old_handle);
121287c1498dSEd Maste free(new_handle);
121387c1498dSEd Maste
121487c1498dSEd Maste return status == SSH2_FX_OK ? 0 : -1;
121587c1498dSEd Maste }
121687c1498dSEd Maste
121787c1498dSEd Maste int
sftp_rename(struct sftp_conn * conn,const char * oldpath,const char * newpath,int force_legacy)1218edf85781SEd Maste sftp_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
1219f7167e0eSDag-Erling Smørgrav int force_legacy)
12201e8db6e2SBrian Feldman {
1221bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
12221e8db6e2SBrian Feldman u_int status, id;
1223bc5531deSDag-Erling Smørgrav int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy;
12241e8db6e2SBrian Feldman
1225bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
122619261079SEd Maste fatal_f("sshbuf_new failed");
12271e8db6e2SBrian Feldman
12281e8db6e2SBrian Feldman /* Send rename request */
1229ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
1230f7167e0eSDag-Erling Smørgrav if (use_ext) {
12311323ec57SEd Maste debug2("Sending SSH2_FXP_EXTENDED(posix-rename@openssh.com) "
12321323ec57SEd Maste "\"%s\" to \"%s\"", oldpath, newpath);
1233bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1234bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
1235bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg,
1236bc5531deSDag-Erling Smørgrav "posix-rename@openssh.com")) != 0)
123719261079SEd Maste fatal_fr(r, "compose posix-rename");
1238d4af9e69SDag-Erling Smørgrav } else {
12391323ec57SEd Maste debug2("Sending SSH2_FXP_RENAME \"%s\" to \"%s\"",
12401323ec57SEd Maste oldpath, newpath);
1241bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 ||
1242bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0)
124319261079SEd Maste fatal_fr(r, "compose rename");
1244d4af9e69SDag-Erling Smørgrav }
1245bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1246bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, newpath)) != 0)
124719261079SEd Maste fatal_fr(r, "compose paths");
1248bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
1249d4af9e69SDag-Erling Smørgrav debug3("Sent message %s \"%s\" -> \"%s\"",
1250bc5531deSDag-Erling Smørgrav use_ext ? "posix-rename@openssh.com" :
1251bc5531deSDag-Erling Smørgrav "SSH2_FXP_RENAME", oldpath, newpath);
1252bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
12531e8db6e2SBrian Feldman
12544a421b63SDag-Erling Smørgrav status = get_status(conn, id);
12551e8db6e2SBrian Feldman if (status != SSH2_FX_OK)
12561323ec57SEd Maste error("remote rename \"%s\" to \"%s\": %s", oldpath,
1257ae1f160dSDag-Erling Smørgrav newpath, fx2txt(status));
12581e8db6e2SBrian Feldman
1259bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
12601e8db6e2SBrian Feldman }
12611e8db6e2SBrian Feldman
12621e8db6e2SBrian Feldman int
sftp_hardlink(struct sftp_conn * conn,const char * oldpath,const char * newpath)1263edf85781SEd Maste sftp_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
12644a421b63SDag-Erling Smørgrav {
1265bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
12664a421b63SDag-Erling Smørgrav u_int status, id;
1267bc5531deSDag-Erling Smørgrav int r;
12684a421b63SDag-Erling Smørgrav
12694a421b63SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
12704a421b63SDag-Erling Smørgrav error("Server does not support hardlink@openssh.com extension");
12714a421b63SDag-Erling Smørgrav return -1;
12724a421b63SDag-Erling Smørgrav }
12731323ec57SEd Maste debug2("Sending SSH2_FXP_EXTENDED(hardlink@openssh.com) "
12741323ec57SEd Maste "\"%s\" to \"%s\"", oldpath, newpath);
12754a421b63SDag-Erling Smørgrav
1276bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
127719261079SEd Maste fatal_f("sshbuf_new failed");
1278462c32cbSDag-Erling Smørgrav
1279462c32cbSDag-Erling Smørgrav /* Send link request */
1280462c32cbSDag-Erling Smørgrav id = conn->msg_id++;
1281bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1282bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
1283bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
1284bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1285bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, newpath)) != 0)
128619261079SEd Maste fatal_fr(r, "compose");
1287bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
12884a421b63SDag-Erling Smørgrav debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
12894a421b63SDag-Erling Smørgrav oldpath, newpath);
1290bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
12914a421b63SDag-Erling Smørgrav
12924a421b63SDag-Erling Smørgrav status = get_status(conn, id);
12934a421b63SDag-Erling Smørgrav if (status != SSH2_FX_OK)
12941323ec57SEd Maste error("remote link \"%s\" to \"%s\": %s", oldpath,
12954a421b63SDag-Erling Smørgrav newpath, fx2txt(status));
12964a421b63SDag-Erling Smørgrav
1297bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
12984a421b63SDag-Erling Smørgrav }
12994a421b63SDag-Erling Smørgrav
13004a421b63SDag-Erling Smørgrav int
sftp_symlink(struct sftp_conn * conn,const char * oldpath,const char * newpath)1301edf85781SEd Maste sftp_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
13021e8db6e2SBrian Feldman {
1303bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
13041e8db6e2SBrian Feldman u_int status, id;
1305bc5531deSDag-Erling Smørgrav int r;
13061e8db6e2SBrian Feldman
1307ae1f160dSDag-Erling Smørgrav if (conn->version < 3) {
1308ae1f160dSDag-Erling Smørgrav error("This server does not support the symlink operation");
1309ae1f160dSDag-Erling Smørgrav return(SSH2_FX_OP_UNSUPPORTED);
1310ae1f160dSDag-Erling Smørgrav }
13111323ec57SEd Maste debug2("Sending SSH2_FXP_SYMLINK \"%s\" to \"%s\"", oldpath, newpath);
1312ae1f160dSDag-Erling Smørgrav
1313bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
131419261079SEd Maste fatal_f("sshbuf_new failed");
13151e8db6e2SBrian Feldman
1316d74d50a8SDag-Erling Smørgrav /* Send symlink request */
1317ae1f160dSDag-Erling Smørgrav id = conn->msg_id++;
1318bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 ||
1319bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
1320bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1321bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, newpath)) != 0)
132219261079SEd Maste fatal_fr(r, "compose");
1323bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
13241e8db6e2SBrian Feldman debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
13251e8db6e2SBrian Feldman newpath);
1326bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
13271e8db6e2SBrian Feldman
13284a421b63SDag-Erling Smørgrav status = get_status(conn, id);
13291e8db6e2SBrian Feldman if (status != SSH2_FX_OK)
13301323ec57SEd Maste error("remote symlink file \"%s\" to \"%s\": %s", oldpath,
1331ae1f160dSDag-Erling Smørgrav newpath, fx2txt(status));
13321e8db6e2SBrian Feldman
1333bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
13341e8db6e2SBrian Feldman }
13351e8db6e2SBrian Feldman
1336f7167e0eSDag-Erling Smørgrav int
sftp_fsync(struct sftp_conn * conn,u_char * handle,u_int handle_len)1337edf85781SEd Maste sftp_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
1338f7167e0eSDag-Erling Smørgrav {
1339bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
1340f7167e0eSDag-Erling Smørgrav u_int status, id;
1341bc5531deSDag-Erling Smørgrav int r;
1342f7167e0eSDag-Erling Smørgrav
1343f7167e0eSDag-Erling Smørgrav /* Silently return if the extension is not supported */
1344f7167e0eSDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_FSYNC) == 0)
1345f7167e0eSDag-Erling Smørgrav return -1;
13461323ec57SEd Maste debug2("Sending SSH2_FXP_EXTENDED(fsync@openssh.com)");
1347f7167e0eSDag-Erling Smørgrav
1348f7167e0eSDag-Erling Smørgrav /* Send fsync request */
1349bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
135019261079SEd Maste fatal_f("sshbuf_new failed");
1351f7167e0eSDag-Erling Smørgrav id = conn->msg_id++;
1352bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1353bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
1354bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
1355bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
135619261079SEd Maste fatal_fr(r, "compose");
1357bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
1358f7167e0eSDag-Erling Smørgrav debug3("Sent message fsync@openssh.com I:%u", id);
1359bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
1360f7167e0eSDag-Erling Smørgrav
1361f7167e0eSDag-Erling Smørgrav status = get_status(conn, id);
1362f7167e0eSDag-Erling Smørgrav if (status != SSH2_FX_OK)
13631323ec57SEd Maste error("remote fsync: %s", fx2txt(status));
1364f7167e0eSDag-Erling Smørgrav
1365190cef3dSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
1366f7167e0eSDag-Erling Smørgrav }
1367f7167e0eSDag-Erling Smørgrav
1368d4af9e69SDag-Erling Smørgrav #ifdef notyet
13691e8db6e2SBrian Feldman char *
sftp_readlink(struct sftp_conn * conn,const char * path)1370edf85781SEd Maste sftp_readlink(struct sftp_conn *conn, const char *path)
13711e8db6e2SBrian Feldman {
1372bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
1373bc5531deSDag-Erling Smørgrav u_int expected_id, count, id;
13741e8db6e2SBrian Feldman char *filename, *longname;
1375bc5531deSDag-Erling Smørgrav Attrib a;
1376bc5531deSDag-Erling Smørgrav u_char type;
1377bc5531deSDag-Erling Smørgrav int r;
13781e8db6e2SBrian Feldman
13791323ec57SEd Maste debug2("Sending SSH2_FXP_READLINK \"%s\"", path);
13801323ec57SEd Maste
1381ae1f160dSDag-Erling Smørgrav expected_id = id = conn->msg_id++;
13824a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
13831e8db6e2SBrian Feldman
1384bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
138519261079SEd Maste fatal_f("sshbuf_new failed");
13861e8db6e2SBrian Feldman
1387bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
1388bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1389bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0)
139019261079SEd Maste fatal_fr(r, "parse");
13911e8db6e2SBrian Feldman
13921e8db6e2SBrian Feldman if (id != expected_id)
1393ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id);
13941e8db6e2SBrian Feldman
13951e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) {
1396bc5531deSDag-Erling Smørgrav u_int status;
13971e8db6e2SBrian Feldman
1398bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0)
139919261079SEd Maste fatal_fr(r, "parse status");
14001e8db6e2SBrian Feldman error("Couldn't readlink: %s", fx2txt(status));
1401bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
14021e8db6e2SBrian Feldman return(NULL);
14031e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME)
1404ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
14051e8db6e2SBrian Feldman SSH2_FXP_NAME, type);
14061e8db6e2SBrian Feldman
1407bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &count)) != 0)
140819261079SEd Maste fatal_fr(r, "parse count");
14091e8db6e2SBrian Feldman if (count != 1)
14101e8db6e2SBrian Feldman fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
14111e8db6e2SBrian Feldman
1412bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1413bc5531deSDag-Erling Smørgrav (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1414bc5531deSDag-Erling Smørgrav (r = decode_attrib(msg, &a)) != 0)
141519261079SEd Maste fatal_fr(r, "parse filenames/attrib");
14161e8db6e2SBrian Feldman
14171e8db6e2SBrian Feldman debug3("SSH_FXP_READLINK %s -> %s", path, filename);
14181e8db6e2SBrian Feldman
1419e4a9863fSDag-Erling Smørgrav free(longname);
14201e8db6e2SBrian Feldman
1421bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
14221e8db6e2SBrian Feldman
1423bc5531deSDag-Erling Smørgrav return filename;
14241e8db6e2SBrian Feldman }
1425d4af9e69SDag-Erling Smørgrav #endif
1426d4af9e69SDag-Erling Smørgrav
1427d4af9e69SDag-Erling Smørgrav int
sftp_statvfs(struct sftp_conn * conn,const char * path,struct sftp_statvfs * st,int quiet)1428edf85781SEd Maste sftp_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
1429d4af9e69SDag-Erling Smørgrav int quiet)
1430d4af9e69SDag-Erling Smørgrav {
1431bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
1432d4af9e69SDag-Erling Smørgrav u_int id;
1433bc5531deSDag-Erling Smørgrav int r;
1434d4af9e69SDag-Erling Smørgrav
1435d4af9e69SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
1436d4af9e69SDag-Erling Smørgrav error("Server does not support statvfs@openssh.com extension");
1437d4af9e69SDag-Erling Smørgrav return -1;
1438d4af9e69SDag-Erling Smørgrav }
1439d4af9e69SDag-Erling Smørgrav
14401323ec57SEd Maste debug2("Sending SSH2_FXP_EXTENDED(statvfs@openssh.com) \"%s\"", path);
14411323ec57SEd Maste
1442d4af9e69SDag-Erling Smørgrav id = conn->msg_id++;
1443d4af9e69SDag-Erling Smørgrav
1444bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
144519261079SEd Maste fatal_f("sshbuf_new failed");
1446bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1447bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
1448bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
1449bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, path)) != 0)
145019261079SEd Maste fatal_fr(r, "compose");
1451bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
1452bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
1453d4af9e69SDag-Erling Smørgrav
14544a421b63SDag-Erling Smørgrav return get_decode_statvfs(conn, st, id, quiet);
1455d4af9e69SDag-Erling Smørgrav }
1456d4af9e69SDag-Erling Smørgrav
1457d4af9e69SDag-Erling Smørgrav #ifdef notyet
1458d4af9e69SDag-Erling Smørgrav int
sftp_fstatvfs(struct sftp_conn * conn,const u_char * handle,u_int handle_len,struct sftp_statvfs * st,int quiet)1459edf85781SEd Maste sftp_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
1460d4af9e69SDag-Erling Smørgrav struct sftp_statvfs *st, int quiet)
1461d4af9e69SDag-Erling Smørgrav {
1462bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
1463d4af9e69SDag-Erling Smørgrav u_int id;
1464d4af9e69SDag-Erling Smørgrav
1465d4af9e69SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
1466d4af9e69SDag-Erling Smørgrav error("Server does not support fstatvfs@openssh.com extension");
1467d4af9e69SDag-Erling Smørgrav return -1;
1468d4af9e69SDag-Erling Smørgrav }
1469d4af9e69SDag-Erling Smørgrav
14701323ec57SEd Maste debug2("Sending SSH2_FXP_EXTENDED(fstatvfs@openssh.com)");
14711323ec57SEd Maste
1472d4af9e69SDag-Erling Smørgrav id = conn->msg_id++;
1473d4af9e69SDag-Erling Smørgrav
1474bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
147519261079SEd Maste fatal_f("sshbuf_new failed");
1476bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1477bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
1478bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
1479bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
148019261079SEd Maste fatal_fr(r, "compose");
1481bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
1482bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
1483d4af9e69SDag-Erling Smørgrav
14844a421b63SDag-Erling Smørgrav return get_decode_statvfs(conn, st, id, quiet);
1485d4af9e69SDag-Erling Smørgrav }
1486d4af9e69SDag-Erling Smørgrav #endif
14871e8db6e2SBrian Feldman
148819261079SEd Maste int
sftp_lsetstat(struct sftp_conn * conn,const char * path,Attrib * a)1489edf85781SEd Maste sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a)
149019261079SEd Maste {
149119261079SEd Maste struct sshbuf *msg;
149219261079SEd Maste u_int status, id;
149319261079SEd Maste int r;
149419261079SEd Maste
149519261079SEd Maste if ((conn->exts & SFTP_EXT_LSETSTAT) == 0) {
149619261079SEd Maste error("Server does not support lsetstat@openssh.com extension");
149719261079SEd Maste return -1;
149819261079SEd Maste }
149919261079SEd Maste
15001323ec57SEd Maste debug2("Sending SSH2_FXP_EXTENDED(lsetstat@openssh.com) \"%s\"", path);
15011323ec57SEd Maste
150219261079SEd Maste id = conn->msg_id++;
150319261079SEd Maste if ((msg = sshbuf_new()) == NULL)
150419261079SEd Maste fatal_f("sshbuf_new failed");
150519261079SEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
150619261079SEd Maste (r = sshbuf_put_u32(msg, id)) != 0 ||
150719261079SEd Maste (r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 ||
150819261079SEd Maste (r = sshbuf_put_cstring(msg, path)) != 0 ||
150919261079SEd Maste (r = encode_attrib(msg, a)) != 0)
151019261079SEd Maste fatal_fr(r, "compose");
151119261079SEd Maste send_msg(conn, msg);
151219261079SEd Maste sshbuf_free(msg);
151319261079SEd Maste
151419261079SEd Maste status = get_status(conn, id);
151519261079SEd Maste if (status != SSH2_FX_OK)
15161323ec57SEd Maste error("remote lsetstat \"%s\": %s", path, fx2txt(status));
151719261079SEd Maste
151819261079SEd Maste return status == SSH2_FX_OK ? 0 : -1;
151919261079SEd Maste }
152019261079SEd Maste
1521ae1f160dSDag-Erling Smørgrav static void
send_read_request(struct sftp_conn * conn,u_int id,u_int64_t offset,u_int len,const u_char * handle,u_int handle_len)15224a421b63SDag-Erling Smørgrav send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
1523bc5531deSDag-Erling Smørgrav u_int len, const u_char *handle, u_int handle_len)
1524ae1f160dSDag-Erling Smørgrav {
1525bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
1526bc5531deSDag-Erling Smørgrav int r;
1527ae1f160dSDag-Erling Smørgrav
1528bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL)
152919261079SEd Maste fatal_f("sshbuf_new failed");
1530bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 ||
1531bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 ||
1532bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0 ||
1533bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u64(msg, offset)) != 0 ||
1534bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, len)) != 0)
153519261079SEd Maste fatal_fr(r, "compose");
1536bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
1537bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
1538ae1f160dSDag-Erling Smørgrav }
1539ae1f160dSDag-Erling Smørgrav
154019261079SEd Maste 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)154119261079SEd Maste send_open(struct sftp_conn *conn, const char *path, const char *tag,
154219261079SEd Maste u_int openmode, Attrib *a, u_char **handlep, size_t *handle_lenp)
154319261079SEd Maste {
154419261079SEd Maste Attrib junk;
154519261079SEd Maste u_char *handle;
154619261079SEd Maste size_t handle_len;
154719261079SEd Maste struct sshbuf *msg;
154819261079SEd Maste int r;
154919261079SEd Maste u_int id;
155019261079SEd Maste
15511323ec57SEd Maste debug2("Sending SSH2_FXP_OPEN \"%s\"", path);
15521323ec57SEd Maste
155319261079SEd Maste *handlep = NULL;
155419261079SEd Maste *handle_lenp = 0;
155519261079SEd Maste
155619261079SEd Maste if (a == NULL) {
155719261079SEd Maste attrib_clear(&junk); /* Send empty attributes */
155819261079SEd Maste a = &junk;
155919261079SEd Maste }
156019261079SEd Maste /* Send open request */
156119261079SEd Maste if ((msg = sshbuf_new()) == NULL)
156219261079SEd Maste fatal_f("sshbuf_new failed");
156319261079SEd Maste id = conn->msg_id++;
156419261079SEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
156519261079SEd Maste (r = sshbuf_put_u32(msg, id)) != 0 ||
156619261079SEd Maste (r = sshbuf_put_cstring(msg, path)) != 0 ||
156719261079SEd Maste (r = sshbuf_put_u32(msg, openmode)) != 0 ||
156819261079SEd Maste (r = encode_attrib(msg, a)) != 0)
156919261079SEd Maste fatal_fr(r, "compose %s open", tag);
157019261079SEd Maste send_msg(conn, msg);
157119261079SEd Maste sshbuf_free(msg);
157219261079SEd Maste debug3("Sent %s message SSH2_FXP_OPEN I:%u P:%s M:0x%04x",
157319261079SEd Maste tag, id, path, openmode);
157419261079SEd Maste if ((handle = get_handle(conn, id, &handle_len,
15751323ec57SEd Maste "%s open \"%s\"", tag, path)) == NULL)
157619261079SEd Maste return -1;
157719261079SEd Maste /* success */
157819261079SEd Maste *handlep = handle;
157919261079SEd Maste *handle_lenp = handle_len;
158019261079SEd Maste return 0;
158119261079SEd Maste }
158219261079SEd Maste
158319261079SEd Maste static const char *
progress_meter_path(const char * path)158419261079SEd Maste progress_meter_path(const char *path)
158519261079SEd Maste {
158619261079SEd Maste const char *progresspath;
158719261079SEd Maste
158819261079SEd Maste if ((progresspath = strrchr(path, '/')) == NULL)
158919261079SEd Maste return path;
159019261079SEd Maste progresspath++;
159119261079SEd Maste if (*progresspath == '\0')
159219261079SEd Maste return path;
159319261079SEd Maste return progresspath;
159419261079SEd Maste }
159519261079SEd Maste
15961e8db6e2SBrian Feldman int
sftp_download(struct sftp_conn * conn,const char * remote_path,const char * local_path,Attrib * a,int preserve_flag,int resume_flag,int fsync_flag,int inplace_flag)1597edf85781SEd Maste sftp_download(struct sftp_conn *conn, const char *remote_path,
1598bc5531deSDag-Erling Smørgrav const char *local_path, Attrib *a, int preserve_flag, int resume_flag,
159938a52bd3SEd Maste int fsync_flag, int inplace_flag)
16001e8db6e2SBrian Feldman {
1601bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
1602bc5531deSDag-Erling Smørgrav u_char *handle;
1603bc5531deSDag-Erling Smørgrav int local_fd = -1, write_error;
160419261079SEd Maste int read_error, write_errno, lmodified = 0, reordered = 0, r;
1605535af610SEd Maste u_int64_t offset = 0, size, highwater = 0, maxack = 0;
1606bc5531deSDag-Erling Smørgrav u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK;
1607d0c8c0bcSDag-Erling Smørgrav off_t progress_counter;
1608bc5531deSDag-Erling Smørgrav size_t handle_len;
1609e4a9863fSDag-Erling Smørgrav struct stat st;
161019261079SEd Maste struct requests requests;
1611ae1f160dSDag-Erling Smørgrav struct request *req;
1612bc5531deSDag-Erling Smørgrav u_char type;
1613edf85781SEd Maste Attrib attr;
16141e8db6e2SBrian Feldman
16151323ec57SEd Maste debug2_f("download remote \"%s\" to local \"%s\"",
16161323ec57SEd Maste remote_path, local_path);
16171323ec57SEd Maste
1618ae1f160dSDag-Erling Smørgrav TAILQ_INIT(&requests);
1619ae1f160dSDag-Erling Smørgrav
1620edf85781SEd Maste if (a == NULL) {
1621edf85781SEd Maste if (sftp_stat(conn, remote_path, 0, &attr) != 0)
1622b15c8340SDag-Erling Smørgrav return -1;
1623edf85781SEd Maste a = &attr;
1624edf85781SEd Maste }
16251e8db6e2SBrian Feldman
1626d4af9e69SDag-Erling Smørgrav /* Do not preserve set[ug]id here, as we do not preserve ownership */
16271e8db6e2SBrian Feldman if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1628d0c8c0bcSDag-Erling Smørgrav mode = a->perm & 0777;
16291e8db6e2SBrian Feldman else
16301e8db6e2SBrian Feldman mode = 0666;
16311e8db6e2SBrian Feldman
16321e8db6e2SBrian Feldman if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
1633d0c8c0bcSDag-Erling Smørgrav (!S_ISREG(a->perm))) {
16341323ec57SEd Maste error("download %s: not a regular file", remote_path);
16351e8db6e2SBrian Feldman return(-1);
16361e8db6e2SBrian Feldman }
16371e8db6e2SBrian Feldman
1638ae1f160dSDag-Erling Smørgrav if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
1639ae1f160dSDag-Erling Smørgrav size = a->size;
1640ae1f160dSDag-Erling Smørgrav else
1641ae1f160dSDag-Erling Smørgrav size = 0;
16421e8db6e2SBrian Feldman
164319261079SEd Maste buflen = conn->download_buflen;
16441e8db6e2SBrian Feldman
16451e8db6e2SBrian Feldman /* Send open request */
164619261079SEd Maste if (send_open(conn, remote_path, "remote", SSH2_FXF_READ, NULL,
164719261079SEd Maste &handle, &handle_len) != 0)
164819261079SEd Maste return -1;
1649ae1f160dSDag-Erling Smørgrav
165038a52bd3SEd Maste local_fd = open(local_path, O_WRONLY | O_CREAT |
165138a52bd3SEd Maste ((resume_flag || inplace_flag) ? 0 : O_TRUNC), mode | S_IWUSR);
1652ae1f160dSDag-Erling Smørgrav if (local_fd == -1) {
16531323ec57SEd Maste error("open local \"%s\": %s", local_path, strerror(errno));
1654e4a9863fSDag-Erling Smørgrav goto fail;
1655e4a9863fSDag-Erling Smørgrav }
1656f7167e0eSDag-Erling Smørgrav if (resume_flag) {
1657e4a9863fSDag-Erling Smørgrav if (fstat(local_fd, &st) == -1) {
16581323ec57SEd Maste error("stat local \"%s\": %s",
1659e4a9863fSDag-Erling Smørgrav local_path, strerror(errno));
1660e4a9863fSDag-Erling Smørgrav goto fail;
1661e4a9863fSDag-Erling Smørgrav }
1662f7167e0eSDag-Erling Smørgrav if (st.st_size < 0) {
1663f7167e0eSDag-Erling Smørgrav error("\"%s\" has negative size", local_path);
1664f7167e0eSDag-Erling Smørgrav goto fail;
1665f7167e0eSDag-Erling Smørgrav }
1666f7167e0eSDag-Erling Smørgrav if ((u_int64_t)st.st_size > size) {
1667e4a9863fSDag-Erling Smørgrav error("Unable to resume download of \"%s\": "
1668e4a9863fSDag-Erling Smørgrav "local file is larger than remote", local_path);
1669e4a9863fSDag-Erling Smørgrav fail:
1670edf85781SEd Maste sftp_close(conn, handle, handle_len);
1671e4a9863fSDag-Erling Smørgrav free(handle);
1672f7167e0eSDag-Erling Smørgrav if (local_fd != -1)
1673f7167e0eSDag-Erling Smørgrav close(local_fd);
1674e4a9863fSDag-Erling Smørgrav return -1;
1675e4a9863fSDag-Erling Smørgrav }
1676535af610SEd Maste offset = highwater = maxack = st.st_size;
16771e8db6e2SBrian Feldman }
16781e8db6e2SBrian Feldman
16791e8db6e2SBrian Feldman /* Read from remote and write to local */
1680e4a9863fSDag-Erling Smørgrav write_error = read_error = write_errno = num_req = 0;
1681ae1f160dSDag-Erling Smørgrav max_req = 1;
1682e4a9863fSDag-Erling Smørgrav progress_counter = offset;
1683d0c8c0bcSDag-Erling Smørgrav
168419261079SEd Maste if (showprogress && size != 0) {
168519261079SEd Maste start_progress_meter(progress_meter_path(remote_path),
168619261079SEd Maste size, &progress_counter);
168719261079SEd Maste }
168819261079SEd Maste
168919261079SEd Maste if ((msg = sshbuf_new()) == NULL)
169019261079SEd Maste fatal_f("sshbuf_new failed");
1691d0c8c0bcSDag-Erling Smørgrav
1692ae1f160dSDag-Erling Smørgrav while (num_req > 0 || max_req > 0) {
1693bc5531deSDag-Erling Smørgrav u_char *data;
1694bc5531deSDag-Erling Smørgrav size_t len;
16951e8db6e2SBrian Feldman
1696d74d50a8SDag-Erling Smørgrav /*
1697d74d50a8SDag-Erling Smørgrav * Simulate EOF on interrupt: stop sending new requests and
1698d74d50a8SDag-Erling Smørgrav * allow outstanding requests to drain gracefully
1699d74d50a8SDag-Erling Smørgrav */
1700d74d50a8SDag-Erling Smørgrav if (interrupted) {
1701d74d50a8SDag-Erling Smørgrav if (num_req == 0) /* If we haven't started yet... */
1702d74d50a8SDag-Erling Smørgrav break;
1703d74d50a8SDag-Erling Smørgrav max_req = 0;
1704d74d50a8SDag-Erling Smørgrav }
1705d74d50a8SDag-Erling Smørgrav
1706ae1f160dSDag-Erling Smørgrav /* Send some more requests */
1707ae1f160dSDag-Erling Smørgrav while (num_req < max_req) {
1708ae1f160dSDag-Erling Smørgrav debug3("Request range %llu -> %llu (%d/%d)",
1709545d5ecaSDag-Erling Smørgrav (unsigned long long)offset,
1710545d5ecaSDag-Erling Smørgrav (unsigned long long)offset + buflen - 1,
1711545d5ecaSDag-Erling Smørgrav num_req, max_req);
171219261079SEd Maste req = request_enqueue(&requests, conn->msg_id++,
171319261079SEd Maste buflen, offset);
1714ae1f160dSDag-Erling Smørgrav offset += buflen;
1715ae1f160dSDag-Erling Smørgrav num_req++;
17164a421b63SDag-Erling Smørgrav send_read_request(conn, req->id, req->offset,
1717ae1f160dSDag-Erling Smørgrav req->len, handle, handle_len);
1718ae1f160dSDag-Erling Smørgrav }
17191e8db6e2SBrian Feldman
1720bc5531deSDag-Erling Smørgrav sshbuf_reset(msg);
1721bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
1722bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1723bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0)
172419261079SEd Maste fatal_fr(r, "parse");
1725ee21a45fSDag-Erling Smørgrav debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
17261e8db6e2SBrian Feldman
1727ae1f160dSDag-Erling Smørgrav /* Find the request in our queue */
172819261079SEd Maste if ((req = request_find(&requests, id)) == NULL)
1729ae1f160dSDag-Erling Smørgrav fatal("Unexpected reply %u", id);
1730ae1f160dSDag-Erling Smørgrav
1731ae1f160dSDag-Erling Smørgrav switch (type) {
1732ae1f160dSDag-Erling Smørgrav case SSH2_FXP_STATUS:
1733bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0)
173419261079SEd Maste fatal_fr(r, "parse status");
1735ae1f160dSDag-Erling Smørgrav if (status != SSH2_FX_EOF)
1736ae1f160dSDag-Erling Smørgrav read_error = 1;
1737ae1f160dSDag-Erling Smørgrav max_req = 0;
1738ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&requests, req, tq);
1739e4a9863fSDag-Erling Smørgrav free(req);
1740ae1f160dSDag-Erling Smørgrav num_req--;
17411e8db6e2SBrian Feldman break;
1742ae1f160dSDag-Erling Smørgrav case SSH2_FXP_DATA:
1743bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
174419261079SEd Maste fatal_fr(r, "parse data");
1745545d5ecaSDag-Erling Smørgrav debug3("Received data %llu -> %llu",
1746545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset,
1747545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + len - 1);
1748ae1f160dSDag-Erling Smørgrav if (len > req->len)
1749ae1f160dSDag-Erling Smørgrav fatal("Received more data than asked for "
1750bc5531deSDag-Erling Smørgrav "%zu > %zu", len, req->len);
175119261079SEd Maste lmodified = 1;
1752ae1f160dSDag-Erling Smørgrav if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
1753d95e11bfSDag-Erling Smørgrav atomicio(vwrite, local_fd, data, len) != len) &&
1754ae1f160dSDag-Erling Smørgrav !write_error) {
1755ae1f160dSDag-Erling Smørgrav write_errno = errno;
1756ae1f160dSDag-Erling Smørgrav write_error = 1;
1757ae1f160dSDag-Erling Smørgrav max_req = 0;
1758535af610SEd Maste } else {
1759535af610SEd Maste /*
1760535af610SEd Maste * Track both the highest offset acknowledged
1761535af610SEd Maste * and the highest *contiguous* offset
1762535af610SEd Maste * acknowledged.
1763535af610SEd Maste * We'll need the latter for ftruncate()ing
1764535af610SEd Maste * interrupted transfers.
1765535af610SEd Maste */
1766535af610SEd Maste if (maxack < req->offset + len)
1767535af610SEd Maste maxack = req->offset + len;
1768535af610SEd Maste if (!reordered && req->offset <= highwater)
1769535af610SEd Maste highwater = maxack;
1770e4a9863fSDag-Erling Smørgrav else if (!reordered && req->offset > highwater)
1771e4a9863fSDag-Erling Smørgrav reordered = 1;
1772535af610SEd Maste }
1773d0c8c0bcSDag-Erling Smørgrav progress_counter += len;
1774e4a9863fSDag-Erling Smørgrav free(data);
1775ae1f160dSDag-Erling Smørgrav
1776ae1f160dSDag-Erling Smørgrav if (len == req->len) {
1777ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&requests, req, tq);
1778e4a9863fSDag-Erling Smørgrav free(req);
1779ae1f160dSDag-Erling Smørgrav num_req--;
1780ae1f160dSDag-Erling Smørgrav } else {
1781ae1f160dSDag-Erling Smørgrav /* Resend the request for the missing data */
1782ae1f160dSDag-Erling Smørgrav debug3("Short data block, re-requesting "
1783545d5ecaSDag-Erling Smørgrav "%llu -> %llu (%2d)",
1784545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + len,
1785545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset +
1786545d5ecaSDag-Erling Smørgrav req->len - 1, num_req);
1787ae1f160dSDag-Erling Smørgrav req->id = conn->msg_id++;
1788ae1f160dSDag-Erling Smørgrav req->len -= len;
1789ae1f160dSDag-Erling Smørgrav req->offset += len;
17904a421b63SDag-Erling Smørgrav send_read_request(conn, req->id,
1791ae1f160dSDag-Erling Smørgrav req->offset, req->len, handle, handle_len);
1792ae1f160dSDag-Erling Smørgrav /* Reduce the request size */
1793ae1f160dSDag-Erling Smørgrav if (len < buflen)
1794ca86bcf2SDag-Erling Smørgrav buflen = MAXIMUM(MIN_READ_SIZE, len);
1795ae1f160dSDag-Erling Smørgrav }
1796ae1f160dSDag-Erling Smørgrav if (max_req > 0) { /* max_req = 0 iff EOF received */
1797ae1f160dSDag-Erling Smørgrav if (size > 0 && offset > size) {
1798ae1f160dSDag-Erling Smørgrav /* Only one request at a time
1799ae1f160dSDag-Erling Smørgrav * after the expected EOF */
1800ae1f160dSDag-Erling Smørgrav debug3("Finish at %llu (%2d)",
1801545d5ecaSDag-Erling Smørgrav (unsigned long long)offset,
1802545d5ecaSDag-Erling Smørgrav num_req);
1803ae1f160dSDag-Erling Smørgrav max_req = 1;
180419261079SEd Maste } else if (max_req < conn->num_requests) {
1805ae1f160dSDag-Erling Smørgrav ++max_req;
1806ae1f160dSDag-Erling Smørgrav }
1807ae1f160dSDag-Erling Smørgrav }
1808ae1f160dSDag-Erling Smørgrav break;
1809ae1f160dSDag-Erling Smørgrav default:
1810ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
18111e8db6e2SBrian Feldman SSH2_FXP_DATA, type);
18121e8db6e2SBrian Feldman }
1813ae1f160dSDag-Erling Smørgrav }
18141e8db6e2SBrian Feldman
1815d0c8c0bcSDag-Erling Smørgrav if (showprogress && size)
1816d0c8c0bcSDag-Erling Smørgrav stop_progress_meter();
1817d0c8c0bcSDag-Erling Smørgrav
1818ae1f160dSDag-Erling Smørgrav /* Sanity check */
1819ae1f160dSDag-Erling Smørgrav if (TAILQ_FIRST(&requests) != NULL)
1820ae1f160dSDag-Erling Smørgrav fatal("Transfer complete, but requests still in queue");
1821535af610SEd Maste
1822535af610SEd Maste if (!read_error && !write_error && !interrupted) {
1823535af610SEd Maste /* we got everything */
1824535af610SEd Maste highwater = maxack;
1825535af610SEd Maste }
1826535af610SEd Maste
182738a52bd3SEd Maste /*
182838a52bd3SEd Maste * Truncate at highest contiguous point to avoid holes on interrupt,
182938a52bd3SEd Maste * or unconditionally if writing in place.
183038a52bd3SEd Maste */
183138a52bd3SEd Maste if (inplace_flag || read_error || write_error || interrupted) {
1832535af610SEd Maste if (reordered && resume_flag &&
1833535af610SEd Maste (read_error || write_error || interrupted)) {
1834e4a9863fSDag-Erling Smørgrav error("Unable to resume download of \"%s\": "
1835e4a9863fSDag-Erling Smørgrav "server reordered requests", local_path);
1836e4a9863fSDag-Erling Smørgrav }
1837e4a9863fSDag-Erling Smørgrav debug("truncating at %llu", (unsigned long long)highwater);
1838557f75e5SDag-Erling Smørgrav if (ftruncate(local_fd, highwater) == -1)
18391323ec57SEd Maste error("local ftruncate \"%s\": %s", local_path,
1840557f75e5SDag-Erling Smørgrav strerror(errno));
1841e4a9863fSDag-Erling Smørgrav }
1842ae1f160dSDag-Erling Smørgrav if (read_error) {
18431323ec57SEd Maste error("read remote \"%s\" : %s", remote_path, fx2txt(status));
1844f7167e0eSDag-Erling Smørgrav status = -1;
1845edf85781SEd Maste sftp_close(conn, handle, handle_len);
1846ae1f160dSDag-Erling Smørgrav } else if (write_error) {
18471323ec57SEd Maste error("write local \"%s\": %s", local_path,
1848ae1f160dSDag-Erling Smørgrav strerror(write_errno));
1849bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE;
1850edf85781SEd Maste sftp_close(conn, handle, handle_len);
1851ae1f160dSDag-Erling Smørgrav } else {
1852edf85781SEd Maste if (sftp_close(conn, handle, handle_len) != 0 || interrupted)
1853bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE;
1854bc5531deSDag-Erling Smørgrav else
1855bc5531deSDag-Erling Smørgrav status = SSH2_FX_OK;
18561e8db6e2SBrian Feldman /* Override umask and utimes if asked */
185783d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD
1858f7167e0eSDag-Erling Smørgrav if (preserve_flag && fchmod(local_fd, mode) == -1)
185983d2307dSDag-Erling Smørgrav #else
1860f7167e0eSDag-Erling Smørgrav if (preserve_flag && chmod(local_path, mode) == -1)
186183d2307dSDag-Erling Smørgrav #endif /* HAVE_FCHMOD */
18621323ec57SEd Maste error("local chmod \"%s\": %s", local_path,
18631e8db6e2SBrian Feldman strerror(errno));
1864f7167e0eSDag-Erling Smørgrav if (preserve_flag &&
1865f7167e0eSDag-Erling Smørgrav (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
18661e8db6e2SBrian Feldman struct timeval tv[2];
18671e8db6e2SBrian Feldman tv[0].tv_sec = a->atime;
18681e8db6e2SBrian Feldman tv[1].tv_sec = a->mtime;
18691e8db6e2SBrian Feldman tv[0].tv_usec = tv[1].tv_usec = 0;
18701e8db6e2SBrian Feldman if (utimes(local_path, tv) == -1)
18711323ec57SEd Maste error("local set times \"%s\": %s",
1872ae1f160dSDag-Erling Smørgrav local_path, strerror(errno));
18731e8db6e2SBrian Feldman }
187419261079SEd Maste if (resume_flag && !lmodified)
187519261079SEd Maste logit("File \"%s\" was not modified", local_path);
187619261079SEd Maste else if (fsync_flag) {
1877f7167e0eSDag-Erling Smørgrav debug("syncing \"%s\"", local_path);
1878f7167e0eSDag-Erling Smørgrav if (fsync(local_fd) == -1)
18791323ec57SEd Maste error("local sync \"%s\": %s",
1880f7167e0eSDag-Erling Smørgrav local_path, strerror(errno));
1881f7167e0eSDag-Erling Smørgrav }
1882ae1f160dSDag-Erling Smørgrav }
18831e8db6e2SBrian Feldman close(local_fd);
1884bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
1885e4a9863fSDag-Erling Smørgrav free(handle);
1886ae1f160dSDag-Erling Smørgrav
1887190cef3dSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
18881e8db6e2SBrian Feldman }
18891e8db6e2SBrian Feldman
1890b15c8340SDag-Erling Smørgrav static int
download_dir_internal(struct sftp_conn * conn,const char * src,const char * dst,int depth,Attrib * dirattrib,int preserve_flag,int print_flag,int resume_flag,int fsync_flag,int follow_link_flag,int inplace_flag)1891bc5531deSDag-Erling Smørgrav download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1892bc5531deSDag-Erling Smørgrav int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
189338a52bd3SEd Maste int resume_flag, int fsync_flag, int follow_link_flag, int inplace_flag)
1894b15c8340SDag-Erling Smørgrav {
1895b15c8340SDag-Erling Smørgrav int i, ret = 0;
1896b15c8340SDag-Erling Smørgrav SFTP_DIRENT **dir_entries;
1897190cef3dSDag-Erling Smørgrav char *filename, *new_src = NULL, *new_dst = NULL;
189819261079SEd Maste mode_t mode = 0777, tmpmode = mode;
1899edf85781SEd Maste Attrib *a, ldirattrib, lsym;
1900b15c8340SDag-Erling Smørgrav
1901b15c8340SDag-Erling Smørgrav if (depth >= MAX_DIR_DEPTH) {
1902b15c8340SDag-Erling Smørgrav error("Maximum directory depth exceeded: %d levels", depth);
1903b15c8340SDag-Erling Smørgrav return -1;
1904b15c8340SDag-Erling Smørgrav }
1905b15c8340SDag-Erling Smørgrav
19061323ec57SEd Maste debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst);
19071323ec57SEd Maste
1908edf85781SEd Maste if (dirattrib == NULL) {
1909edf85781SEd Maste if (sftp_stat(conn, src, 1, &ldirattrib) != 0) {
19101323ec57SEd Maste error("stat remote \"%s\" directory failed", src);
1911b15c8340SDag-Erling Smørgrav return -1;
1912b15c8340SDag-Erling Smørgrav }
1913edf85781SEd Maste dirattrib = &ldirattrib;
1914edf85781SEd Maste }
1915b15c8340SDag-Erling Smørgrav if (!S_ISDIR(dirattrib->perm)) {
1916b15c8340SDag-Erling Smørgrav error("\"%s\" is not a directory", src);
1917b15c8340SDag-Erling Smørgrav return -1;
1918b15c8340SDag-Erling Smørgrav }
191919261079SEd Maste if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
1920076ad2f8SDag-Erling Smørgrav mprintf("Retrieving %s\n", src);
1921b15c8340SDag-Erling Smørgrav
192219261079SEd Maste if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
1923b15c8340SDag-Erling Smørgrav mode = dirattrib->perm & 01777;
192419261079SEd Maste tmpmode = mode | (S_IWUSR|S_IXUSR);
192519261079SEd Maste } else {
19261323ec57SEd Maste debug("download remote \"%s\": server "
19271323ec57SEd Maste "did not send permissions", dst);
1928b15c8340SDag-Erling Smørgrav }
1929b15c8340SDag-Erling Smørgrav
193019261079SEd Maste if (mkdir(dst, tmpmode) == -1 && errno != EEXIST) {
1931b15c8340SDag-Erling Smørgrav error("mkdir %s: %s", dst, strerror(errno));
1932b15c8340SDag-Erling Smørgrav return -1;
1933b15c8340SDag-Erling Smørgrav }
1934b15c8340SDag-Erling Smørgrav
1935edf85781SEd Maste if (sftp_readdir(conn, src, &dir_entries) == -1) {
19361323ec57SEd Maste error("remote readdir \"%s\" failed", src);
1937b15c8340SDag-Erling Smørgrav return -1;
1938b15c8340SDag-Erling Smørgrav }
1939b15c8340SDag-Erling Smørgrav
1940b15c8340SDag-Erling Smørgrav for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
1941190cef3dSDag-Erling Smørgrav free(new_dst);
1942190cef3dSDag-Erling Smørgrav free(new_src);
1943b15c8340SDag-Erling Smørgrav
1944190cef3dSDag-Erling Smørgrav filename = dir_entries[i]->filename;
1945edf85781SEd Maste new_dst = sftp_path_append(dst, filename);
1946edf85781SEd Maste new_src = sftp_path_append(src, filename);
1947b15c8340SDag-Erling Smørgrav
1948edf85781SEd Maste a = &dir_entries[i]->a;
1949edf85781SEd Maste if (S_ISLNK(a->perm)) {
1950edf85781SEd Maste if (!follow_link_flag) {
1951edf85781SEd Maste logit("download \"%s\": not a regular file",
1952edf85781SEd Maste new_src);
1953edf85781SEd Maste continue;
1954edf85781SEd Maste }
1955edf85781SEd Maste /* Replace the stat contents with the symlink target */
1956edf85781SEd Maste if (sftp_stat(conn, new_src, 1, &lsym) != 0) {
1957edf85781SEd Maste logit("remote stat \"%s\" failed", new_src);
1958edf85781SEd Maste ret = -1;
1959edf85781SEd Maste continue;
1960edf85781SEd Maste }
1961edf85781SEd Maste a = &lsym;
1962edf85781SEd Maste }
1963edf85781SEd Maste
1964edf85781SEd Maste if (S_ISDIR(a->perm)) {
1965b15c8340SDag-Erling Smørgrav if (strcmp(filename, ".") == 0 ||
1966b15c8340SDag-Erling Smørgrav strcmp(filename, "..") == 0)
1967b15c8340SDag-Erling Smørgrav continue;
1968b15c8340SDag-Erling Smørgrav if (download_dir_internal(conn, new_src, new_dst,
1969edf85781SEd Maste depth + 1, a, preserve_flag,
197019261079SEd Maste print_flag, resume_flag,
197138a52bd3SEd Maste fsync_flag, follow_link_flag, inplace_flag) == -1)
1972b15c8340SDag-Erling Smørgrav ret = -1;
1973edf85781SEd Maste } else if (S_ISREG(a->perm)) {
1974edf85781SEd Maste if (sftp_download(conn, new_src, new_dst, a,
197538a52bd3SEd Maste preserve_flag, resume_flag, fsync_flag,
197638a52bd3SEd Maste inplace_flag) == -1) {
1977b15c8340SDag-Erling Smørgrav error("Download of file %s to %s failed",
1978b15c8340SDag-Erling Smørgrav new_src, new_dst);
1979b15c8340SDag-Erling Smørgrav ret = -1;
1980b15c8340SDag-Erling Smørgrav }
1981b15c8340SDag-Erling Smørgrav } else
19821323ec57SEd Maste logit("download \"%s\": not a regular file", new_src);
1983b15c8340SDag-Erling Smørgrav
1984190cef3dSDag-Erling Smørgrav }
1985e4a9863fSDag-Erling Smørgrav free(new_dst);
1986e4a9863fSDag-Erling Smørgrav free(new_src);
1987b15c8340SDag-Erling Smørgrav
1988f7167e0eSDag-Erling Smørgrav if (preserve_flag) {
1989b15c8340SDag-Erling Smørgrav if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1990b15c8340SDag-Erling Smørgrav struct timeval tv[2];
1991b15c8340SDag-Erling Smørgrav tv[0].tv_sec = dirattrib->atime;
1992b15c8340SDag-Erling Smørgrav tv[1].tv_sec = dirattrib->mtime;
1993b15c8340SDag-Erling Smørgrav tv[0].tv_usec = tv[1].tv_usec = 0;
1994b15c8340SDag-Erling Smørgrav if (utimes(dst, tv) == -1)
19951323ec57SEd Maste error("local set times on \"%s\": %s",
1996b15c8340SDag-Erling Smørgrav dst, strerror(errno));
1997b15c8340SDag-Erling Smørgrav } else
1998b15c8340SDag-Erling Smørgrav debug("Server did not send times for directory "
1999b15c8340SDag-Erling Smørgrav "\"%s\"", dst);
2000b15c8340SDag-Erling Smørgrav }
2001b15c8340SDag-Erling Smørgrav
200219261079SEd Maste if (mode != tmpmode && chmod(dst, mode) == -1)
20031323ec57SEd Maste error("local chmod directory \"%s\": %s", dst,
200419261079SEd Maste strerror(errno));
200519261079SEd Maste
2006edf85781SEd Maste sftp_free_dirents(dir_entries);
2007b15c8340SDag-Erling Smørgrav
2008b15c8340SDag-Erling Smørgrav return ret;
2009b15c8340SDag-Erling Smørgrav }
2010b15c8340SDag-Erling Smørgrav
2011b15c8340SDag-Erling Smørgrav int
sftp_download_dir(struct sftp_conn * conn,const char * src,const char * dst,Attrib * dirattrib,int preserve_flag,int print_flag,int resume_flag,int fsync_flag,int follow_link_flag,int inplace_flag)2012edf85781SEd Maste sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst,
2013bc5531deSDag-Erling Smørgrav Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag,
201438a52bd3SEd Maste int fsync_flag, int follow_link_flag, int inplace_flag)
2015b15c8340SDag-Erling Smørgrav {
2016b15c8340SDag-Erling Smørgrav char *src_canon;
2017b15c8340SDag-Erling Smørgrav int ret;
2018b15c8340SDag-Erling Smørgrav
2019edf85781SEd Maste if ((src_canon = sftp_realpath(conn, src)) == NULL) {
20201323ec57SEd Maste error("download \"%s\": path canonicalization failed", src);
2021b15c8340SDag-Erling Smørgrav return -1;
2022b15c8340SDag-Erling Smørgrav }
2023b15c8340SDag-Erling Smørgrav
2024f7167e0eSDag-Erling Smørgrav ret = download_dir_internal(conn, src_canon, dst, 0,
202519261079SEd Maste dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag,
202638a52bd3SEd Maste follow_link_flag, inplace_flag);
2027e4a9863fSDag-Erling Smørgrav free(src_canon);
2028b15c8340SDag-Erling Smørgrav return ret;
2029b15c8340SDag-Erling Smørgrav }
2030b15c8340SDag-Erling Smørgrav
20311e8db6e2SBrian Feldman int
sftp_upload(struct sftp_conn * conn,const char * local_path,const char * remote_path,int preserve_flag,int resume,int fsync_flag,int inplace_flag)2032edf85781SEd Maste sftp_upload(struct sftp_conn *conn, const char *local_path,
203338a52bd3SEd Maste const char *remote_path, int preserve_flag, int resume,
203438a52bd3SEd Maste int fsync_flag, int inplace_flag)
20351e8db6e2SBrian Feldman {
2036bc5531deSDag-Erling Smørgrav int r, local_fd;
203738a52bd3SEd Maste u_int openmode, id, status = SSH2_FX_OK, reordered = 0;
2038e4a9863fSDag-Erling Smørgrav off_t offset, progress_counter;
203938a52bd3SEd Maste u_char type, *handle, *data;
2040bc5531deSDag-Erling Smørgrav struct sshbuf *msg;
20411e8db6e2SBrian Feldman struct stat sb;
2042edf85781SEd Maste Attrib a, t, c;
204338a52bd3SEd Maste u_int32_t startid, ackid;
2044535af610SEd Maste u_int64_t highwater = 0, maxack = 0;
204519261079SEd Maste struct request *ack = NULL;
204619261079SEd Maste struct requests acks;
2047bc5531deSDag-Erling Smørgrav size_t handle_len;
2048ae1f160dSDag-Erling Smørgrav
20491323ec57SEd Maste debug2_f("upload local \"%s\" to remote \"%s\"",
20501323ec57SEd Maste local_path, remote_path);
20511323ec57SEd Maste
2052ae1f160dSDag-Erling Smørgrav TAILQ_INIT(&acks);
20531e8db6e2SBrian Feldman
20541323ec57SEd Maste if ((local_fd = open(local_path, O_RDONLY)) == -1) {
20551323ec57SEd Maste error("open local \"%s\": %s", local_path, strerror(errno));
20561e8db6e2SBrian Feldman return(-1);
20571e8db6e2SBrian Feldman }
20581e8db6e2SBrian Feldman if (fstat(local_fd, &sb) == -1) {
20591323ec57SEd Maste error("fstat local \"%s\": %s", local_path, strerror(errno));
20601e8db6e2SBrian Feldman close(local_fd);
20611e8db6e2SBrian Feldman return(-1);
20621e8db6e2SBrian Feldman }
2063d0c8c0bcSDag-Erling Smørgrav if (!S_ISREG(sb.st_mode)) {
20641323ec57SEd Maste error("local \"%s\" is not a regular file", local_path);
2065d0c8c0bcSDag-Erling Smørgrav close(local_fd);
2066d0c8c0bcSDag-Erling Smørgrav return(-1);
2067d0c8c0bcSDag-Erling Smørgrav }
20681e8db6e2SBrian Feldman stat_to_attrib(&sb, &a);
20691e8db6e2SBrian Feldman
20701e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
20711e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
20721e8db6e2SBrian Feldman a.perm &= 0777;
2073f7167e0eSDag-Erling Smørgrav if (!preserve_flag)
20741e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
20751e8db6e2SBrian Feldman
2076a0ee8cc6SDag-Erling Smørgrav if (resume) {
2077a0ee8cc6SDag-Erling Smørgrav /* Get remote file size if it exists */
2078edf85781SEd Maste if (sftp_stat(conn, remote_path, 0, &c) != 0) {
2079a0ee8cc6SDag-Erling Smørgrav close(local_fd);
2080a0ee8cc6SDag-Erling Smørgrav return -1;
2081a0ee8cc6SDag-Erling Smørgrav }
2082a0ee8cc6SDag-Erling Smørgrav
2083edf85781SEd Maste if ((off_t)c.size >= sb.st_size) {
20841323ec57SEd Maste error("resume \"%s\": destination file "
20851323ec57SEd Maste "same size or larger", local_path);
2086a0ee8cc6SDag-Erling Smørgrav close(local_fd);
2087a0ee8cc6SDag-Erling Smørgrav return -1;
2088a0ee8cc6SDag-Erling Smørgrav }
2089a0ee8cc6SDag-Erling Smørgrav
2090edf85781SEd Maste if (lseek(local_fd, (off_t)c.size, SEEK_SET) == -1) {
2091a0ee8cc6SDag-Erling Smørgrav close(local_fd);
2092a0ee8cc6SDag-Erling Smørgrav return -1;
2093a0ee8cc6SDag-Erling Smørgrav }
2094a0ee8cc6SDag-Erling Smørgrav }
2095a0ee8cc6SDag-Erling Smørgrav
209638a52bd3SEd Maste openmode = SSH2_FXF_WRITE|SSH2_FXF_CREAT;
209738a52bd3SEd Maste if (resume)
209838a52bd3SEd Maste openmode |= SSH2_FXF_APPEND;
209938a52bd3SEd Maste else if (!inplace_flag)
210038a52bd3SEd Maste openmode |= SSH2_FXF_TRUNC;
210138a52bd3SEd Maste
21021e8db6e2SBrian Feldman /* Send open request */
210338a52bd3SEd Maste if (send_open(conn, remote_path, "dest", openmode, &a,
210438a52bd3SEd Maste &handle, &handle_len) != 0) {
21051e8db6e2SBrian Feldman close(local_fd);
2106d4af9e69SDag-Erling Smørgrav return -1;
21071e8db6e2SBrian Feldman }
21081e8db6e2SBrian Feldman
210919261079SEd Maste id = conn->msg_id;
2110ae1f160dSDag-Erling Smørgrav startid = ackid = id + 1;
211119261079SEd Maste data = xmalloc(conn->upload_buflen);
2112ae1f160dSDag-Erling Smørgrav
21131e8db6e2SBrian Feldman /* Read from local and write to remote */
2114edf85781SEd Maste offset = progress_counter = (resume ? c.size : 0);
211519261079SEd Maste if (showprogress) {
211619261079SEd Maste start_progress_meter(progress_meter_path(local_path),
211719261079SEd Maste sb.st_size, &progress_counter);
211819261079SEd Maste }
2119d0c8c0bcSDag-Erling Smørgrav
212019261079SEd Maste if ((msg = sshbuf_new()) == NULL)
212119261079SEd Maste fatal_f("sshbuf_new failed");
21221e8db6e2SBrian Feldman for (;;) {
21231e8db6e2SBrian Feldman int len;
21241e8db6e2SBrian Feldman
21251e8db6e2SBrian Feldman /*
2126d74d50a8SDag-Erling Smørgrav * Can't use atomicio here because it returns 0 on EOF,
2127d74d50a8SDag-Erling Smørgrav * thus losing the last block of the file.
2128d74d50a8SDag-Erling Smørgrav * Simulate an EOF on interrupt, allowing ACKs from the
2129d74d50a8SDag-Erling Smørgrav * server to drain.
21301e8db6e2SBrian Feldman */
2131d4af9e69SDag-Erling Smørgrav if (interrupted || status != SSH2_FX_OK)
2132d74d50a8SDag-Erling Smørgrav len = 0;
2133d74d50a8SDag-Erling Smørgrav else do
213419261079SEd Maste len = read(local_fd, data, conn->upload_buflen);
2135d4af9e69SDag-Erling Smørgrav while ((len == -1) &&
2136d4af9e69SDag-Erling Smørgrav (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
21371e8db6e2SBrian Feldman
21381323ec57SEd Maste if (len == -1) {
21391323ec57SEd Maste fatal("read local \"%s\": %s",
21401323ec57SEd Maste local_path, strerror(errno));
21411323ec57SEd Maste } else if (len != 0) {
214219261079SEd Maste ack = request_enqueue(&acks, ++id, len, offset);
2143bc5531deSDag-Erling Smørgrav sshbuf_reset(msg);
2144bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
2145bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, ack->id)) != 0 ||
2146bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle,
2147bc5531deSDag-Erling Smørgrav handle_len)) != 0 ||
2148bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u64(msg, offset)) != 0 ||
2149bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, data, len)) != 0)
215019261079SEd Maste fatal_fr(r, "compose");
2151bc5531deSDag-Erling Smørgrav send_msg(conn, msg);
2152ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
2153545d5ecaSDag-Erling Smørgrav id, (unsigned long long)offset, len);
2154ae1f160dSDag-Erling Smørgrav } else if (TAILQ_FIRST(&acks) == NULL)
2155ae1f160dSDag-Erling Smørgrav break;
21561e8db6e2SBrian Feldman
2157ae1f160dSDag-Erling Smørgrav if (ack == NULL)
2158ae1f160dSDag-Erling Smørgrav fatal("Unexpected ACK %u", id);
2159ae1f160dSDag-Erling Smørgrav
2160ae1f160dSDag-Erling Smørgrav if (id == startid || len == 0 ||
2161ae1f160dSDag-Erling Smørgrav id - ackid >= conn->num_requests) {
2162bc5531deSDag-Erling Smørgrav u_int rid;
2163545d5ecaSDag-Erling Smørgrav
2164bc5531deSDag-Erling Smørgrav sshbuf_reset(msg);
2165bc5531deSDag-Erling Smørgrav get_msg(conn, msg);
2166bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
2167bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &rid)) != 0)
216819261079SEd Maste fatal_fr(r, "parse");
2169ae1f160dSDag-Erling Smørgrav
2170ae1f160dSDag-Erling Smørgrav if (type != SSH2_FXP_STATUS)
2171ae1f160dSDag-Erling Smørgrav fatal("Expected SSH2_FXP_STATUS(%d) packet, "
2172ae1f160dSDag-Erling Smørgrav "got %d", SSH2_FXP_STATUS, type);
2173ae1f160dSDag-Erling Smørgrav
2174bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0)
217519261079SEd Maste fatal_fr(r, "parse status");
2176bc5531deSDag-Erling Smørgrav debug3("SSH2_FXP_STATUS %u", status);
2177ae1f160dSDag-Erling Smørgrav
2178ae1f160dSDag-Erling Smørgrav /* Find the request in our queue */
217919261079SEd Maste if ((ack = request_find(&acks, rid)) == NULL)
2180bc5531deSDag-Erling Smørgrav fatal("Can't find request for ID %u", rid);
2181ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&acks, ack, tq);
218219261079SEd Maste debug3("In write loop, ack for %u %zu bytes at %lld",
218319261079SEd Maste ack->id, ack->len, (unsigned long long)ack->offset);
2184ae1f160dSDag-Erling Smørgrav ++ackid;
2185e4a9863fSDag-Erling Smørgrav progress_counter += ack->len;
2186535af610SEd Maste /*
2187535af610SEd Maste * Track both the highest offset acknowledged and the
2188535af610SEd Maste * highest *contiguous* offset acknowledged.
2189535af610SEd Maste * We'll need the latter for ftruncate()ing
2190535af610SEd Maste * interrupted transfers.
2191535af610SEd Maste */
2192535af610SEd Maste if (maxack < ack->offset + ack->len)
2193535af610SEd Maste maxack = ack->offset + ack->len;
219438a52bd3SEd Maste if (!reordered && ack->offset <= highwater)
2195535af610SEd Maste highwater = maxack;
219638a52bd3SEd Maste else if (!reordered && ack->offset > highwater) {
219738a52bd3SEd Maste debug3_f("server reordered ACKs");
219838a52bd3SEd Maste reordered = 1;
219938a52bd3SEd Maste }
2200e4a9863fSDag-Erling Smørgrav free(ack);
2201ae1f160dSDag-Erling Smørgrav }
22021e8db6e2SBrian Feldman offset += len;
2203d4af9e69SDag-Erling Smørgrav if (offset < 0)
220419261079SEd Maste fatal_f("offset < 0");
22051e8db6e2SBrian Feldman }
2206bc5531deSDag-Erling Smørgrav sshbuf_free(msg);
2207d4af9e69SDag-Erling Smørgrav
2208d0c8c0bcSDag-Erling Smørgrav if (showprogress)
2209d0c8c0bcSDag-Erling Smørgrav stop_progress_meter();
2210e4a9863fSDag-Erling Smørgrav free(data);
22111e8db6e2SBrian Feldman
2212535af610SEd Maste if (status == SSH2_FX_OK && !interrupted) {
2213535af610SEd Maste /* we got everything */
2214535af610SEd Maste highwater = maxack;
2215535af610SEd Maste }
2216d4af9e69SDag-Erling Smørgrav if (status != SSH2_FX_OK) {
22171323ec57SEd Maste error("write remote \"%s\": %s", remote_path, fx2txt(status));
2218bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE;
2219d4af9e69SDag-Erling Smørgrav }
2220d4af9e69SDag-Erling Smørgrav
222138a52bd3SEd Maste if (inplace_flag || (resume && (status != SSH2_FX_OK || interrupted))) {
222238a52bd3SEd Maste debug("truncating at %llu", (unsigned long long)highwater);
222338a52bd3SEd Maste attrib_clear(&t);
222438a52bd3SEd Maste t.flags = SSH2_FILEXFER_ATTR_SIZE;
222538a52bd3SEd Maste t.size = highwater;
2226edf85781SEd Maste sftp_fsetstat(conn, handle, handle_len, &t);
222738a52bd3SEd Maste }
222838a52bd3SEd Maste
22291e8db6e2SBrian Feldman if (close(local_fd) == -1) {
22301323ec57SEd Maste error("close local \"%s\": %s", local_path, strerror(errno));
2231bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE;
22321e8db6e2SBrian Feldman }
22331e8db6e2SBrian Feldman
22341e8db6e2SBrian Feldman /* Override umask and utimes if asked */
2235f7167e0eSDag-Erling Smørgrav if (preserve_flag)
2236edf85781SEd Maste sftp_fsetstat(conn, handle, handle_len, &a);
22371e8db6e2SBrian Feldman
2238f7167e0eSDag-Erling Smørgrav if (fsync_flag)
2239edf85781SEd Maste (void)sftp_fsync(conn, handle, handle_len);
2240f7167e0eSDag-Erling Smørgrav
2241edf85781SEd Maste if (sftp_close(conn, handle, handle_len) != 0)
2242bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE;
2243bc5531deSDag-Erling Smørgrav
2244e4a9863fSDag-Erling Smørgrav free(handle);
2245d4af9e69SDag-Erling Smørgrav
2246bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1;
22471e8db6e2SBrian Feldman }
2248b15c8340SDag-Erling Smørgrav
2249b15c8340SDag-Erling Smørgrav static int
upload_dir_internal(struct sftp_conn * conn,const char * src,const char * dst,int depth,int preserve_flag,int print_flag,int resume,int fsync_flag,int follow_link_flag,int inplace_flag)2250bc5531deSDag-Erling Smørgrav upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
225119261079SEd Maste int depth, int preserve_flag, int print_flag, int resume, int fsync_flag,
225238a52bd3SEd Maste int follow_link_flag, int inplace_flag)
2253b15c8340SDag-Erling Smørgrav {
2254bc5531deSDag-Erling Smørgrav int ret = 0;
2255b15c8340SDag-Erling Smørgrav DIR *dirp;
2256b15c8340SDag-Erling Smørgrav struct dirent *dp;
2257190cef3dSDag-Erling Smørgrav char *filename, *new_src = NULL, *new_dst = NULL;
2258b15c8340SDag-Erling Smørgrav struct stat sb;
2259edf85781SEd Maste Attrib a, dirattrib;
226019261079SEd Maste u_int32_t saved_perm;
2261b15c8340SDag-Erling Smørgrav
22621323ec57SEd Maste debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst);
22631323ec57SEd Maste
2264b15c8340SDag-Erling Smørgrav if (depth >= MAX_DIR_DEPTH) {
2265b15c8340SDag-Erling Smørgrav error("Maximum directory depth exceeded: %d levels", depth);
2266b15c8340SDag-Erling Smørgrav return -1;
2267b15c8340SDag-Erling Smørgrav }
2268b15c8340SDag-Erling Smørgrav
2269b15c8340SDag-Erling Smørgrav if (stat(src, &sb) == -1) {
22701323ec57SEd Maste error("stat local \"%s\": %s", src, strerror(errno));
2271b15c8340SDag-Erling Smørgrav return -1;
2272b15c8340SDag-Erling Smørgrav }
2273b15c8340SDag-Erling Smørgrav if (!S_ISDIR(sb.st_mode)) {
2274b15c8340SDag-Erling Smørgrav error("\"%s\" is not a directory", src);
2275b15c8340SDag-Erling Smørgrav return -1;
2276b15c8340SDag-Erling Smørgrav }
227719261079SEd Maste if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
2278076ad2f8SDag-Erling Smørgrav mprintf("Entering %s\n", src);
2279b15c8340SDag-Erling Smørgrav
2280b15c8340SDag-Erling Smørgrav stat_to_attrib(&sb, &a);
2281b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
2282b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
2283b15c8340SDag-Erling Smørgrav a.perm &= 01777;
2284f7167e0eSDag-Erling Smørgrav if (!preserve_flag)
2285b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
2286b15c8340SDag-Erling Smørgrav
2287b15c8340SDag-Erling Smørgrav /*
2288acc1a9efSDag-Erling Smørgrav * sftp lacks a portable status value to match errno EEXIST,
2289acc1a9efSDag-Erling Smørgrav * so if we get a failure back then we must check whether
229019261079SEd Maste * the path already existed and is a directory. Ensure we can
229119261079SEd Maste * write to the directory we create for the duration of the transfer.
2292b15c8340SDag-Erling Smørgrav */
229319261079SEd Maste saved_perm = a.perm;
229419261079SEd Maste a.perm |= (S_IWUSR|S_IXUSR);
2295edf85781SEd Maste if (sftp_mkdir(conn, dst, &a, 0) != 0) {
2296edf85781SEd Maste if (sftp_stat(conn, dst, 0, &dirattrib) != 0)
2297b15c8340SDag-Erling Smørgrav return -1;
2298edf85781SEd Maste if (!S_ISDIR(dirattrib.perm)) {
2299acc1a9efSDag-Erling Smørgrav error("\"%s\" exists but is not a directory", dst);
2300b15c8340SDag-Erling Smørgrav return -1;
2301b15c8340SDag-Erling Smørgrav }
2302acc1a9efSDag-Erling Smørgrav }
230319261079SEd Maste a.perm = saved_perm;
2304b15c8340SDag-Erling Smørgrav
2305b15c8340SDag-Erling Smørgrav if ((dirp = opendir(src)) == NULL) {
23061323ec57SEd Maste error("local opendir \"%s\": %s", src, strerror(errno));
2307b15c8340SDag-Erling Smørgrav return -1;
2308b15c8340SDag-Erling Smørgrav }
2309b15c8340SDag-Erling Smørgrav
2310b15c8340SDag-Erling Smørgrav while (((dp = readdir(dirp)) != NULL) && !interrupted) {
2311b15c8340SDag-Erling Smørgrav if (dp->d_ino == 0)
2312b15c8340SDag-Erling Smørgrav continue;
2313190cef3dSDag-Erling Smørgrav free(new_dst);
2314190cef3dSDag-Erling Smørgrav free(new_src);
2315b15c8340SDag-Erling Smørgrav filename = dp->d_name;
2316edf85781SEd Maste new_dst = sftp_path_append(dst, filename);
2317edf85781SEd Maste new_src = sftp_path_append(src, filename);
2318b15c8340SDag-Erling Smørgrav
2319edf85781SEd Maste if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
2320edf85781SEd Maste continue;
2321b15c8340SDag-Erling Smørgrav if (lstat(new_src, &sb) == -1) {
23221323ec57SEd Maste logit("local lstat \"%s\": %s", filename,
2323b15c8340SDag-Erling Smørgrav strerror(errno));
2324b15c8340SDag-Erling Smørgrav ret = -1;
2325b15c8340SDag-Erling Smørgrav continue;
2326edf85781SEd Maste }
2327edf85781SEd Maste if (S_ISLNK(sb.st_mode)) {
2328edf85781SEd Maste if (!follow_link_flag) {
2329edf85781SEd Maste logit("%s: not a regular file", filename);
2330edf85781SEd Maste continue;
2331edf85781SEd Maste }
2332edf85781SEd Maste /* Replace the stat contents with the symlink target */
2333edf85781SEd Maste if (stat(new_src, &sb) == -1) {
2334edf85781SEd Maste logit("local stat \"%s\": %s", filename,
2335edf85781SEd Maste strerror(errno));
2336edf85781SEd Maste ret = -1;
2337edf85781SEd Maste continue;
2338edf85781SEd Maste }
2339edf85781SEd Maste }
2340edf85781SEd Maste if (S_ISDIR(sb.st_mode)) {
2341b15c8340SDag-Erling Smørgrav if (upload_dir_internal(conn, new_src, new_dst,
2342a0ee8cc6SDag-Erling Smørgrav depth + 1, preserve_flag, print_flag, resume,
234338a52bd3SEd Maste fsync_flag, follow_link_flag, inplace_flag) == -1)
2344b15c8340SDag-Erling Smørgrav ret = -1;
2345edf85781SEd Maste } else if (S_ISREG(sb.st_mode)) {
2346edf85781SEd Maste if (sftp_upload(conn, new_src, new_dst,
234738a52bd3SEd Maste preserve_flag, resume, fsync_flag,
234838a52bd3SEd Maste inplace_flag) == -1) {
23491323ec57SEd Maste error("upload \"%s\" to \"%s\" failed",
2350b15c8340SDag-Erling Smørgrav new_src, new_dst);
2351b15c8340SDag-Erling Smørgrav ret = -1;
2352b15c8340SDag-Erling Smørgrav }
2353b15c8340SDag-Erling Smørgrav } else
23541323ec57SEd Maste logit("%s: not a regular file", filename);
2355190cef3dSDag-Erling Smørgrav }
2356e4a9863fSDag-Erling Smørgrav free(new_dst);
2357e4a9863fSDag-Erling Smørgrav free(new_src);
2358b15c8340SDag-Erling Smørgrav
2359edf85781SEd Maste sftp_setstat(conn, dst, &a);
2360b15c8340SDag-Erling Smørgrav
2361b15c8340SDag-Erling Smørgrav (void) closedir(dirp);
2362b15c8340SDag-Erling Smørgrav return ret;
2363b15c8340SDag-Erling Smørgrav }
2364b15c8340SDag-Erling Smørgrav
2365b15c8340SDag-Erling Smørgrav int
sftp_upload_dir(struct sftp_conn * conn,const char * src,const char * dst,int preserve_flag,int print_flag,int resume,int fsync_flag,int follow_link_flag,int inplace_flag)2366edf85781SEd Maste sftp_upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
236719261079SEd Maste int preserve_flag, int print_flag, int resume, int fsync_flag,
236838a52bd3SEd Maste int follow_link_flag, int inplace_flag)
2369b15c8340SDag-Erling Smørgrav {
2370b15c8340SDag-Erling Smørgrav char *dst_canon;
2371b15c8340SDag-Erling Smørgrav int ret;
2372b15c8340SDag-Erling Smørgrav
2373edf85781SEd Maste if ((dst_canon = sftp_realpath(conn, dst)) == NULL) {
23741323ec57SEd Maste error("upload \"%s\": path canonicalization failed", dst);
2375b15c8340SDag-Erling Smørgrav return -1;
2376b15c8340SDag-Erling Smørgrav }
2377b15c8340SDag-Erling Smørgrav
2378f7167e0eSDag-Erling Smørgrav ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag,
237938a52bd3SEd Maste print_flag, resume, fsync_flag, follow_link_flag, inplace_flag);
2380f7167e0eSDag-Erling Smørgrav
2381e4a9863fSDag-Erling Smørgrav free(dst_canon);
2382b15c8340SDag-Erling Smørgrav return ret;
2383b15c8340SDag-Erling Smørgrav }
2384b15c8340SDag-Erling Smørgrav
238519261079SEd Maste static void
handle_dest_replies(struct sftp_conn * to,const char * to_path,int synchronous,u_int * nreqsp,u_int * write_errorp)238619261079SEd Maste handle_dest_replies(struct sftp_conn *to, const char *to_path, int synchronous,
238719261079SEd Maste u_int *nreqsp, u_int *write_errorp)
238819261079SEd Maste {
238919261079SEd Maste struct sshbuf *msg;
239019261079SEd Maste u_char type;
239119261079SEd Maste u_int id, status;
239219261079SEd Maste int r;
239319261079SEd Maste struct pollfd pfd;
239419261079SEd Maste
239519261079SEd Maste if ((msg = sshbuf_new()) == NULL)
239619261079SEd Maste fatal_f("sshbuf_new failed");
239719261079SEd Maste
239819261079SEd Maste /* Try to eat replies from the upload side */
239919261079SEd Maste while (*nreqsp > 0) {
240019261079SEd Maste debug3_f("%u outstanding replies", *nreqsp);
240119261079SEd Maste if (!synchronous) {
240219261079SEd Maste /* Bail out if no data is ready to be read */
240319261079SEd Maste pfd.fd = to->fd_in;
240419261079SEd Maste pfd.events = POLLIN;
240519261079SEd Maste if ((r = poll(&pfd, 1, 0)) == -1) {
240619261079SEd Maste if (errno == EINTR)
240719261079SEd Maste break;
240819261079SEd Maste fatal_f("poll: %s", strerror(errno));
240919261079SEd Maste } else if (r == 0)
241019261079SEd Maste break; /* fd not ready */
241119261079SEd Maste }
241219261079SEd Maste sshbuf_reset(msg);
241319261079SEd Maste get_msg(to, msg);
241419261079SEd Maste
241519261079SEd Maste if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
241619261079SEd Maste (r = sshbuf_get_u32(msg, &id)) != 0)
241719261079SEd Maste fatal_fr(r, "dest parse");
241819261079SEd Maste debug3("Received dest reply T:%u I:%u R:%u", type, id, *nreqsp);
241919261079SEd Maste if (type != SSH2_FXP_STATUS) {
242019261079SEd Maste fatal_f("Expected SSH2_FXP_STATUS(%d) packet, got %d",
242119261079SEd Maste SSH2_FXP_STATUS, type);
242219261079SEd Maste }
242319261079SEd Maste if ((r = sshbuf_get_u32(msg, &status)) != 0)
242419261079SEd Maste fatal_fr(r, "parse dest status");
242519261079SEd Maste debug3("dest SSH2_FXP_STATUS %u", status);
242619261079SEd Maste if (status != SSH2_FX_OK) {
242719261079SEd Maste /* record first error */
242819261079SEd Maste if (*write_errorp == 0)
242919261079SEd Maste *write_errorp = status;
243019261079SEd Maste }
243119261079SEd Maste /*
2432edf85781SEd Maste * XXX this doesn't do full reply matching like sftp_upload and
243319261079SEd Maste * so cannot gracefully truncate terminated uploads at a
243419261079SEd Maste * high-water mark. ATM the only caller of this function (scp)
243519261079SEd Maste * doesn't support transfer resumption, so this doesn't matter
243619261079SEd Maste * a whole lot.
243719261079SEd Maste *
2438edf85781SEd Maste * To be safe, sftp_crossload truncates the destination file to
243919261079SEd Maste * zero length on upload failure, since we can't trust the
244019261079SEd Maste * server not to have reordered replies that could have
244119261079SEd Maste * inserted holes where none existed in the source file.
244219261079SEd Maste *
244319261079SEd Maste * XXX we could get a more accutate progress bar if we updated
244419261079SEd Maste * the counter based on the reply from the destination...
244519261079SEd Maste */
244619261079SEd Maste (*nreqsp)--;
244719261079SEd Maste }
244819261079SEd Maste debug3_f("done: %u outstanding replies", *nreqsp);
24491323ec57SEd Maste sshbuf_free(msg);
245019261079SEd Maste }
245119261079SEd Maste
245219261079SEd Maste int
sftp_crossload(struct sftp_conn * from,struct sftp_conn * to,const char * from_path,const char * to_path,Attrib * a,int preserve_flag)2453edf85781SEd Maste sftp_crossload(struct sftp_conn *from, struct sftp_conn *to,
245419261079SEd Maste const char *from_path, const char *to_path,
245519261079SEd Maste Attrib *a, int preserve_flag)
245619261079SEd Maste {
245719261079SEd Maste struct sshbuf *msg;
245819261079SEd Maste int write_error, read_error, r;
245919261079SEd Maste u_int64_t offset = 0, size;
246019261079SEd Maste u_int id, buflen, num_req, max_req, status = SSH2_FX_OK;
246119261079SEd Maste u_int num_upload_req;
246219261079SEd Maste off_t progress_counter;
246319261079SEd Maste u_char *from_handle, *to_handle;
246419261079SEd Maste size_t from_handle_len, to_handle_len;
246519261079SEd Maste struct requests requests;
246619261079SEd Maste struct request *req;
246719261079SEd Maste u_char type;
2468edf85781SEd Maste Attrib attr;
246919261079SEd Maste
24701323ec57SEd Maste debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path);
24711323ec57SEd Maste
247219261079SEd Maste TAILQ_INIT(&requests);
247319261079SEd Maste
2474edf85781SEd Maste if (a == NULL) {
2475edf85781SEd Maste if (sftp_stat(from, from_path, 0, &attr) != 0)
247619261079SEd Maste return -1;
2477edf85781SEd Maste a = &attr;
2478edf85781SEd Maste }
247919261079SEd Maste
248019261079SEd Maste if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
248119261079SEd Maste (!S_ISREG(a->perm))) {
24821323ec57SEd Maste error("download \"%s\": not a regular file", from_path);
248319261079SEd Maste return(-1);
248419261079SEd Maste }
248519261079SEd Maste if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
248619261079SEd Maste size = a->size;
248719261079SEd Maste else
248819261079SEd Maste size = 0;
248919261079SEd Maste
249019261079SEd Maste buflen = from->download_buflen;
249119261079SEd Maste if (buflen > to->upload_buflen)
249219261079SEd Maste buflen = to->upload_buflen;
249319261079SEd Maste
249419261079SEd Maste /* Send open request to read side */
249519261079SEd Maste if (send_open(from, from_path, "origin", SSH2_FXF_READ, NULL,
249619261079SEd Maste &from_handle, &from_handle_len) != 0)
249719261079SEd Maste return -1;
249819261079SEd Maste
249919261079SEd Maste /* Send open request to write side */
250019261079SEd Maste a->flags &= ~SSH2_FILEXFER_ATTR_SIZE;
250119261079SEd Maste a->flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
250219261079SEd Maste a->perm &= 0777;
250319261079SEd Maste if (!preserve_flag)
250419261079SEd Maste a->flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
250519261079SEd Maste if (send_open(to, to_path, "dest",
250619261079SEd Maste SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
250719261079SEd Maste &to_handle, &to_handle_len) != 0) {
2508edf85781SEd Maste sftp_close(from, from_handle, from_handle_len);
250919261079SEd Maste return -1;
251019261079SEd Maste }
251119261079SEd Maste
251219261079SEd Maste /* Read from remote "from" and write to remote "to" */
251319261079SEd Maste offset = 0;
251419261079SEd Maste write_error = read_error = num_req = num_upload_req = 0;
251519261079SEd Maste max_req = 1;
251619261079SEd Maste progress_counter = 0;
251719261079SEd Maste
251819261079SEd Maste if (showprogress && size != 0) {
251919261079SEd Maste start_progress_meter(progress_meter_path(from_path),
252019261079SEd Maste size, &progress_counter);
252119261079SEd Maste }
252219261079SEd Maste if ((msg = sshbuf_new()) == NULL)
252319261079SEd Maste fatal_f("sshbuf_new failed");
252419261079SEd Maste while (num_req > 0 || max_req > 0) {
252519261079SEd Maste u_char *data;
252619261079SEd Maste size_t len;
252719261079SEd Maste
252819261079SEd Maste /*
252919261079SEd Maste * Simulate EOF on interrupt: stop sending new requests and
253019261079SEd Maste * allow outstanding requests to drain gracefully
253119261079SEd Maste */
253219261079SEd Maste if (interrupted) {
253319261079SEd Maste if (num_req == 0) /* If we haven't started yet... */
253419261079SEd Maste break;
253519261079SEd Maste max_req = 0;
253619261079SEd Maste }
253719261079SEd Maste
253819261079SEd Maste /* Send some more requests */
253919261079SEd Maste while (num_req < max_req) {
254019261079SEd Maste debug3("Request range %llu -> %llu (%d/%d)",
254119261079SEd Maste (unsigned long long)offset,
254219261079SEd Maste (unsigned long long)offset + buflen - 1,
254319261079SEd Maste num_req, max_req);
254419261079SEd Maste req = request_enqueue(&requests, from->msg_id++,
254519261079SEd Maste buflen, offset);
254619261079SEd Maste offset += buflen;
254719261079SEd Maste num_req++;
254819261079SEd Maste send_read_request(from, req->id, req->offset,
254919261079SEd Maste req->len, from_handle, from_handle_len);
255019261079SEd Maste }
255119261079SEd Maste
255219261079SEd Maste /* Try to eat replies from the upload side (nonblocking) */
255319261079SEd Maste handle_dest_replies(to, to_path, 0,
255419261079SEd Maste &num_upload_req, &write_error);
255519261079SEd Maste
255619261079SEd Maste sshbuf_reset(msg);
255719261079SEd Maste get_msg(from, msg);
255819261079SEd Maste if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
255919261079SEd Maste (r = sshbuf_get_u32(msg, &id)) != 0)
256019261079SEd Maste fatal_fr(r, "parse");
256119261079SEd Maste debug3("Received origin reply T:%u I:%u R:%d",
256219261079SEd Maste type, id, max_req);
256319261079SEd Maste
256419261079SEd Maste /* Find the request in our queue */
256519261079SEd Maste if ((req = request_find(&requests, id)) == NULL)
256619261079SEd Maste fatal("Unexpected reply %u", id);
256719261079SEd Maste
256819261079SEd Maste switch (type) {
256919261079SEd Maste case SSH2_FXP_STATUS:
257019261079SEd Maste if ((r = sshbuf_get_u32(msg, &status)) != 0)
257119261079SEd Maste fatal_fr(r, "parse status");
257219261079SEd Maste if (status != SSH2_FX_EOF)
257319261079SEd Maste read_error = 1;
257419261079SEd Maste max_req = 0;
257519261079SEd Maste TAILQ_REMOVE(&requests, req, tq);
257619261079SEd Maste free(req);
257719261079SEd Maste num_req--;
257819261079SEd Maste break;
257919261079SEd Maste case SSH2_FXP_DATA:
258019261079SEd Maste if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
258119261079SEd Maste fatal_fr(r, "parse data");
258219261079SEd Maste debug3("Received data %llu -> %llu",
258319261079SEd Maste (unsigned long long)req->offset,
258419261079SEd Maste (unsigned long long)req->offset + len - 1);
258519261079SEd Maste if (len > req->len)
258619261079SEd Maste fatal("Received more data than asked for "
258719261079SEd Maste "%zu > %zu", len, req->len);
258819261079SEd Maste
258919261079SEd Maste /* Write this chunk out to the destination */
259019261079SEd Maste sshbuf_reset(msg);
259119261079SEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
259219261079SEd Maste (r = sshbuf_put_u32(msg, to->msg_id++)) != 0 ||
259319261079SEd Maste (r = sshbuf_put_string(msg, to_handle,
259419261079SEd Maste to_handle_len)) != 0 ||
259519261079SEd Maste (r = sshbuf_put_u64(msg, req->offset)) != 0 ||
259619261079SEd Maste (r = sshbuf_put_string(msg, data, len)) != 0)
259719261079SEd Maste fatal_fr(r, "compose write");
259819261079SEd Maste send_msg(to, msg);
259919261079SEd Maste debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%zu",
260019261079SEd Maste id, (unsigned long long)offset, len);
260119261079SEd Maste num_upload_req++;
260219261079SEd Maste progress_counter += len;
260319261079SEd Maste free(data);
260419261079SEd Maste
260519261079SEd Maste if (len == req->len) {
260619261079SEd Maste TAILQ_REMOVE(&requests, req, tq);
260719261079SEd Maste free(req);
260819261079SEd Maste num_req--;
260919261079SEd Maste } else {
261019261079SEd Maste /* Resend the request for the missing data */
261119261079SEd Maste debug3("Short data block, re-requesting "
261219261079SEd Maste "%llu -> %llu (%2d)",
261319261079SEd Maste (unsigned long long)req->offset + len,
261419261079SEd Maste (unsigned long long)req->offset +
261519261079SEd Maste req->len - 1, num_req);
261619261079SEd Maste req->id = from->msg_id++;
261719261079SEd Maste req->len -= len;
261819261079SEd Maste req->offset += len;
261919261079SEd Maste send_read_request(from, req->id,
262019261079SEd Maste req->offset, req->len,
262119261079SEd Maste from_handle, from_handle_len);
262219261079SEd Maste /* Reduce the request size */
262319261079SEd Maste if (len < buflen)
262419261079SEd Maste buflen = MAXIMUM(MIN_READ_SIZE, len);
262519261079SEd Maste }
262619261079SEd Maste if (max_req > 0) { /* max_req = 0 iff EOF received */
262719261079SEd Maste if (size > 0 && offset > size) {
262819261079SEd Maste /* Only one request at a time
262919261079SEd Maste * after the expected EOF */
263019261079SEd Maste debug3("Finish at %llu (%2d)",
263119261079SEd Maste (unsigned long long)offset,
263219261079SEd Maste num_req);
263319261079SEd Maste max_req = 1;
263419261079SEd Maste } else if (max_req < from->num_requests) {
263519261079SEd Maste ++max_req;
263619261079SEd Maste }
263719261079SEd Maste }
263819261079SEd Maste break;
263919261079SEd Maste default:
264019261079SEd Maste fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
264119261079SEd Maste SSH2_FXP_DATA, type);
264219261079SEd Maste }
264319261079SEd Maste }
264419261079SEd Maste
264519261079SEd Maste if (showprogress && size)
264619261079SEd Maste stop_progress_meter();
264719261079SEd Maste
264819261079SEd Maste /* Drain replies from the server (blocking) */
264919261079SEd Maste debug3_f("waiting for %u replies from destination", num_upload_req);
265019261079SEd Maste handle_dest_replies(to, to_path, 1, &num_upload_req, &write_error);
265119261079SEd Maste
265219261079SEd Maste /* Sanity check */
265319261079SEd Maste if (TAILQ_FIRST(&requests) != NULL)
265419261079SEd Maste fatal("Transfer complete, but requests still in queue");
265519261079SEd Maste /* Truncate at 0 length on interrupt or error to avoid holes at dest */
265619261079SEd Maste if (read_error || write_error || interrupted) {
265719261079SEd Maste debug("truncating \"%s\" at 0", to_path);
2658edf85781SEd Maste sftp_close(to, to_handle, to_handle_len);
265919261079SEd Maste free(to_handle);
266019261079SEd Maste if (send_open(to, to_path, "dest",
266119261079SEd Maste SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
266219261079SEd Maste &to_handle, &to_handle_len) != 0) {
26631323ec57SEd Maste error("dest truncate \"%s\" failed", to_path);
266419261079SEd Maste to_handle = NULL;
266519261079SEd Maste }
266619261079SEd Maste }
266719261079SEd Maste if (read_error) {
26681323ec57SEd Maste error("read origin \"%s\": %s", from_path, fx2txt(status));
266919261079SEd Maste status = -1;
2670edf85781SEd Maste sftp_close(from, from_handle, from_handle_len);
267119261079SEd Maste if (to_handle != NULL)
2672edf85781SEd Maste sftp_close(to, to_handle, to_handle_len);
267319261079SEd Maste } else if (write_error) {
26741323ec57SEd Maste error("write dest \"%s\": %s", to_path, fx2txt(write_error));
267519261079SEd Maste status = SSH2_FX_FAILURE;
2676edf85781SEd Maste sftp_close(from, from_handle, from_handle_len);
267719261079SEd Maste if (to_handle != NULL)
2678edf85781SEd Maste sftp_close(to, to_handle, to_handle_len);
267919261079SEd Maste } else {
2680edf85781SEd Maste if (sftp_close(from, from_handle, from_handle_len) != 0 ||
268119261079SEd Maste interrupted)
268219261079SEd Maste status = -1;
268319261079SEd Maste else
268419261079SEd Maste status = SSH2_FX_OK;
268519261079SEd Maste if (to_handle != NULL) {
268619261079SEd Maste /* Need to resend utimes after write */
268719261079SEd Maste if (preserve_flag)
2688edf85781SEd Maste sftp_fsetstat(to, to_handle, to_handle_len, a);
2689edf85781SEd Maste sftp_close(to, to_handle, to_handle_len);
269019261079SEd Maste }
269119261079SEd Maste }
269219261079SEd Maste sshbuf_free(msg);
269319261079SEd Maste free(from_handle);
269419261079SEd Maste free(to_handle);
269519261079SEd Maste
269619261079SEd Maste return status == SSH2_FX_OK ? 0 : -1;
269719261079SEd Maste }
269819261079SEd Maste
269919261079SEd Maste static int
crossload_dir_internal(struct sftp_conn * from,struct sftp_conn * to,const char * from_path,const char * to_path,int depth,Attrib * dirattrib,int preserve_flag,int print_flag,int follow_link_flag)270019261079SEd Maste crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
270119261079SEd Maste const char *from_path, const char *to_path,
270219261079SEd Maste int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
270319261079SEd Maste int follow_link_flag)
270419261079SEd Maste {
270519261079SEd Maste int i, ret = 0;
270619261079SEd Maste SFTP_DIRENT **dir_entries;
270719261079SEd Maste char *filename, *new_from_path = NULL, *new_to_path = NULL;
270819261079SEd Maste mode_t mode = 0777;
2709edf85781SEd Maste Attrib *a, curdir, ldirattrib, newdir, lsym;
271019261079SEd Maste
27111323ec57SEd Maste debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path);
27121323ec57SEd Maste
271319261079SEd Maste if (depth >= MAX_DIR_DEPTH) {
271419261079SEd Maste error("Maximum directory depth exceeded: %d levels", depth);
271519261079SEd Maste return -1;
271619261079SEd Maste }
271719261079SEd Maste
2718edf85781SEd Maste if (dirattrib == NULL) {
2719edf85781SEd Maste if (sftp_stat(from, from_path, 1, &ldirattrib) != 0) {
27201323ec57SEd Maste error("stat remote \"%s\" failed", from_path);
272119261079SEd Maste return -1;
272219261079SEd Maste }
2723edf85781SEd Maste dirattrib = &ldirattrib;
2724edf85781SEd Maste }
272519261079SEd Maste if (!S_ISDIR(dirattrib->perm)) {
272619261079SEd Maste error("\"%s\" is not a directory", from_path);
272719261079SEd Maste return -1;
272819261079SEd Maste }
272919261079SEd Maste if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
273019261079SEd Maste mprintf("Retrieving %s\n", from_path);
273119261079SEd Maste
273219261079SEd Maste curdir = *dirattrib; /* dirattrib will be clobbered */
273319261079SEd Maste curdir.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
273419261079SEd Maste curdir.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
273519261079SEd Maste if ((curdir.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) == 0) {
273619261079SEd Maste debug("Origin did not send permissions for "
273719261079SEd Maste "directory \"%s\"", to_path);
273819261079SEd Maste curdir.perm = S_IWUSR|S_IXUSR;
273919261079SEd Maste curdir.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
274019261079SEd Maste }
274119261079SEd Maste /* We need to be able to write to the directory while we transfer it */
274219261079SEd Maste mode = curdir.perm & 01777;
274319261079SEd Maste curdir.perm = mode | (S_IWUSR|S_IXUSR);
274419261079SEd Maste
274519261079SEd Maste /*
274619261079SEd Maste * sftp lacks a portable status value to match errno EEXIST,
274719261079SEd Maste * so if we get a failure back then we must check whether
274819261079SEd Maste * the path already existed and is a directory. Ensure we can
274919261079SEd Maste * write to the directory we create for the duration of the transfer.
275019261079SEd Maste */
2751edf85781SEd Maste if (sftp_mkdir(to, to_path, &curdir, 0) != 0) {
2752edf85781SEd Maste if (sftp_stat(to, to_path, 0, &newdir) != 0)
275319261079SEd Maste return -1;
2754edf85781SEd Maste if (!S_ISDIR(newdir.perm)) {
275519261079SEd Maste error("\"%s\" exists but is not a directory", to_path);
275619261079SEd Maste return -1;
275719261079SEd Maste }
275819261079SEd Maste }
275919261079SEd Maste curdir.perm = mode;
276019261079SEd Maste
2761edf85781SEd Maste if (sftp_readdir(from, from_path, &dir_entries) == -1) {
27621323ec57SEd Maste error("origin readdir \"%s\" failed", from_path);
276319261079SEd Maste return -1;
276419261079SEd Maste }
276519261079SEd Maste
276619261079SEd Maste for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
276719261079SEd Maste free(new_from_path);
276819261079SEd Maste free(new_to_path);
276919261079SEd Maste
277019261079SEd Maste filename = dir_entries[i]->filename;
2771edf85781SEd Maste new_from_path = sftp_path_append(from_path, filename);
2772edf85781SEd Maste new_to_path = sftp_path_append(to_path, filename);
277319261079SEd Maste
2774edf85781SEd Maste a = &dir_entries[i]->a;
2775edf85781SEd Maste if (S_ISLNK(a->perm)) {
2776edf85781SEd Maste if (!follow_link_flag) {
2777edf85781SEd Maste logit("%s: not a regular file", filename);
2778edf85781SEd Maste continue;
2779edf85781SEd Maste }
2780edf85781SEd Maste /* Replace the stat contents with the symlink target */
2781edf85781SEd Maste if (sftp_stat(from, new_from_path, 1, &lsym) != 0) {
2782edf85781SEd Maste logit("remote stat \"%s\" failed",
2783edf85781SEd Maste new_from_path);
2784edf85781SEd Maste ret = -1;
2785edf85781SEd Maste continue;
2786edf85781SEd Maste }
2787edf85781SEd Maste a = &lsym;
2788edf85781SEd Maste }
2789edf85781SEd Maste if (S_ISDIR(a->perm)) {
279019261079SEd Maste if (strcmp(filename, ".") == 0 ||
279119261079SEd Maste strcmp(filename, "..") == 0)
279219261079SEd Maste continue;
279319261079SEd Maste if (crossload_dir_internal(from, to,
279419261079SEd Maste new_from_path, new_to_path,
2795edf85781SEd Maste depth + 1, a, preserve_flag,
279619261079SEd Maste print_flag, follow_link_flag) == -1)
279719261079SEd Maste ret = -1;
2798edf85781SEd Maste } else if (S_ISREG(a->perm)) {
2799edf85781SEd Maste if (sftp_crossload(from, to, new_from_path,
2800edf85781SEd Maste new_to_path, a, preserve_flag) == -1) {
28011323ec57SEd Maste error("crossload \"%s\" to \"%s\" failed",
280219261079SEd Maste new_from_path, new_to_path);
280319261079SEd Maste ret = -1;
280419261079SEd Maste }
28051323ec57SEd Maste } else {
28061323ec57SEd Maste logit("origin \"%s\": not a regular file",
28071323ec57SEd Maste new_from_path);
28081323ec57SEd Maste }
280919261079SEd Maste }
281019261079SEd Maste free(new_to_path);
281119261079SEd Maste free(new_from_path);
281219261079SEd Maste
2813edf85781SEd Maste sftp_setstat(to, to_path, &curdir);
281419261079SEd Maste
2815edf85781SEd Maste sftp_free_dirents(dir_entries);
281619261079SEd Maste
281719261079SEd Maste return ret;
281819261079SEd Maste }
281919261079SEd Maste
282019261079SEd Maste int
sftp_crossload_dir(struct sftp_conn * from,struct sftp_conn * to,const char * from_path,const char * to_path,Attrib * dirattrib,int preserve_flag,int print_flag,int follow_link_flag)2821edf85781SEd Maste sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
282219261079SEd Maste const char *from_path, const char *to_path,
282319261079SEd Maste Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag)
282419261079SEd Maste {
282519261079SEd Maste char *from_path_canon;
282619261079SEd Maste int ret;
282719261079SEd Maste
2828edf85781SEd Maste if ((from_path_canon = sftp_realpath(from, from_path)) == NULL) {
28291323ec57SEd Maste error("crossload \"%s\": path canonicalization failed",
28301323ec57SEd Maste from_path);
283119261079SEd Maste return -1;
283219261079SEd Maste }
283319261079SEd Maste
283419261079SEd Maste ret = crossload_dir_internal(from, to, from_path_canon, to_path, 0,
283519261079SEd Maste dirattrib, preserve_flag, print_flag, follow_link_flag);
283619261079SEd Maste free(from_path_canon);
283719261079SEd Maste return ret;
283819261079SEd Maste }
283919261079SEd Maste
284038a52bd3SEd Maste int
sftp_can_get_users_groups_by_id(struct sftp_conn * conn)2841edf85781SEd Maste sftp_can_get_users_groups_by_id(struct sftp_conn *conn)
284238a52bd3SEd Maste {
284338a52bd3SEd Maste return (conn->exts & SFTP_EXT_GETUSERSGROUPS_BY_ID) != 0;
284438a52bd3SEd Maste }
284538a52bd3SEd Maste
284638a52bd3SEd Maste int
sftp_get_users_groups_by_id(struct sftp_conn * conn,const u_int * uids,u_int nuids,const u_int * gids,u_int ngids,char *** usernamesp,char *** groupnamesp)2847edf85781SEd Maste sftp_get_users_groups_by_id(struct sftp_conn *conn,
284838a52bd3SEd Maste const u_int *uids, u_int nuids,
284938a52bd3SEd Maste const u_int *gids, u_int ngids,
285038a52bd3SEd Maste char ***usernamesp, char ***groupnamesp)
285138a52bd3SEd Maste {
285238a52bd3SEd Maste struct sshbuf *msg, *uidbuf, *gidbuf;
285338a52bd3SEd Maste u_int i, expected_id, id;
285438a52bd3SEd Maste char *name, **usernames = NULL, **groupnames = NULL;
285538a52bd3SEd Maste u_char type;
285638a52bd3SEd Maste int r;
285738a52bd3SEd Maste
285838a52bd3SEd Maste *usernamesp = *groupnamesp = NULL;
2859edf85781SEd Maste if (!sftp_can_get_users_groups_by_id(conn))
286038a52bd3SEd Maste return SSH_ERR_FEATURE_UNSUPPORTED;
286138a52bd3SEd Maste
286238a52bd3SEd Maste if ((msg = sshbuf_new()) == NULL ||
286338a52bd3SEd Maste (uidbuf = sshbuf_new()) == NULL ||
286438a52bd3SEd Maste (gidbuf = sshbuf_new()) == NULL)
286538a52bd3SEd Maste fatal_f("sshbuf_new failed");
286638a52bd3SEd Maste expected_id = id = conn->msg_id++;
286738a52bd3SEd Maste debug2("Sending SSH2_FXP_EXTENDED(users-groups-by-id@openssh.com)");
286838a52bd3SEd Maste for (i = 0; i < nuids; i++) {
286938a52bd3SEd Maste if ((r = sshbuf_put_u32(uidbuf, uids[i])) != 0)
287038a52bd3SEd Maste fatal_fr(r, "compose uids");
287138a52bd3SEd Maste }
287238a52bd3SEd Maste for (i = 0; i < ngids; i++) {
287338a52bd3SEd Maste if ((r = sshbuf_put_u32(gidbuf, gids[i])) != 0)
287438a52bd3SEd Maste fatal_fr(r, "compose gids");
287538a52bd3SEd Maste }
287638a52bd3SEd Maste if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
287738a52bd3SEd Maste (r = sshbuf_put_u32(msg, id)) != 0 ||
287838a52bd3SEd Maste (r = sshbuf_put_cstring(msg,
287938a52bd3SEd Maste "users-groups-by-id@openssh.com")) != 0 ||
288038a52bd3SEd Maste (r = sshbuf_put_stringb(msg, uidbuf)) != 0 ||
288138a52bd3SEd Maste (r = sshbuf_put_stringb(msg, gidbuf)) != 0)
288238a52bd3SEd Maste fatal_fr(r, "compose");
288338a52bd3SEd Maste send_msg(conn, msg);
288438a52bd3SEd Maste get_msg(conn, msg);
288538a52bd3SEd Maste if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
288638a52bd3SEd Maste (r = sshbuf_get_u32(msg, &id)) != 0)
288738a52bd3SEd Maste fatal_fr(r, "parse");
288838a52bd3SEd Maste if (id != expected_id)
288938a52bd3SEd Maste fatal("ID mismatch (%u != %u)", id, expected_id);
289038a52bd3SEd Maste if (type == SSH2_FXP_STATUS) {
289138a52bd3SEd Maste u_int status;
289238a52bd3SEd Maste char *errmsg;
289338a52bd3SEd Maste
289438a52bd3SEd Maste if ((r = sshbuf_get_u32(msg, &status)) != 0 ||
289538a52bd3SEd Maste (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0)
289638a52bd3SEd Maste fatal_fr(r, "parse status");
289738a52bd3SEd Maste error("users-groups-by-id %s",
289838a52bd3SEd Maste *errmsg == '\0' ? fx2txt(status) : errmsg);
289938a52bd3SEd Maste free(errmsg);
290038a52bd3SEd Maste sshbuf_free(msg);
290138a52bd3SEd Maste sshbuf_free(uidbuf);
290238a52bd3SEd Maste sshbuf_free(gidbuf);
290338a52bd3SEd Maste return -1;
290438a52bd3SEd Maste } else if (type != SSH2_FXP_EXTENDED_REPLY)
290538a52bd3SEd Maste fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
290638a52bd3SEd Maste SSH2_FXP_EXTENDED_REPLY, type);
290738a52bd3SEd Maste
290838a52bd3SEd Maste /* reuse */
290938a52bd3SEd Maste sshbuf_free(uidbuf);
291038a52bd3SEd Maste sshbuf_free(gidbuf);
291138a52bd3SEd Maste uidbuf = gidbuf = NULL;
291238a52bd3SEd Maste if ((r = sshbuf_froms(msg, &uidbuf)) != 0 ||
291338a52bd3SEd Maste (r = sshbuf_froms(msg, &gidbuf)) != 0)
291438a52bd3SEd Maste fatal_fr(r, "parse response");
291538a52bd3SEd Maste if (nuids > 0) {
291638a52bd3SEd Maste usernames = xcalloc(nuids, sizeof(*usernames));
291738a52bd3SEd Maste for (i = 0; i < nuids; i++) {
291838a52bd3SEd Maste if ((r = sshbuf_get_cstring(uidbuf, &name, NULL)) != 0)
291938a52bd3SEd Maste fatal_fr(r, "parse user name");
292038a52bd3SEd Maste /* Handle unresolved names */
292138a52bd3SEd Maste if (*name == '\0') {
292238a52bd3SEd Maste free(name);
292338a52bd3SEd Maste name = NULL;
292438a52bd3SEd Maste }
292538a52bd3SEd Maste usernames[i] = name;
292638a52bd3SEd Maste }
292738a52bd3SEd Maste }
292838a52bd3SEd Maste if (ngids > 0) {
292938a52bd3SEd Maste groupnames = xcalloc(ngids, sizeof(*groupnames));
293038a52bd3SEd Maste for (i = 0; i < ngids; i++) {
293138a52bd3SEd Maste if ((r = sshbuf_get_cstring(gidbuf, &name, NULL)) != 0)
293238a52bd3SEd Maste fatal_fr(r, "parse user name");
293338a52bd3SEd Maste /* Handle unresolved names */
293438a52bd3SEd Maste if (*name == '\0') {
293538a52bd3SEd Maste free(name);
293638a52bd3SEd Maste name = NULL;
293738a52bd3SEd Maste }
293838a52bd3SEd Maste groupnames[i] = name;
293938a52bd3SEd Maste }
294038a52bd3SEd Maste }
294138a52bd3SEd Maste if (sshbuf_len(uidbuf) != 0)
294238a52bd3SEd Maste fatal_f("unexpected extra username data");
294338a52bd3SEd Maste if (sshbuf_len(gidbuf) != 0)
294438a52bd3SEd Maste fatal_f("unexpected extra groupname data");
294538a52bd3SEd Maste sshbuf_free(uidbuf);
294638a52bd3SEd Maste sshbuf_free(gidbuf);
294738a52bd3SEd Maste sshbuf_free(msg);
294838a52bd3SEd Maste /* success */
294938a52bd3SEd Maste *usernamesp = usernames;
295038a52bd3SEd Maste *groupnamesp = groupnames;
295138a52bd3SEd Maste return 0;
295238a52bd3SEd Maste }
295338a52bd3SEd Maste
2954b15c8340SDag-Erling Smørgrav char *
sftp_path_append(const char * p1,const char * p2)2955edf85781SEd Maste sftp_path_append(const char *p1, const char *p2)
2956b15c8340SDag-Erling Smørgrav {
2957b15c8340SDag-Erling Smørgrav char *ret;
2958b15c8340SDag-Erling Smørgrav size_t len = strlen(p1) + strlen(p2) + 2;
2959b15c8340SDag-Erling Smørgrav
2960b15c8340SDag-Erling Smørgrav ret = xmalloc(len);
2961b15c8340SDag-Erling Smørgrav strlcpy(ret, p1, len);
2962b15c8340SDag-Erling Smørgrav if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
2963b15c8340SDag-Erling Smørgrav strlcat(ret, "/", len);
2964b15c8340SDag-Erling Smørgrav strlcat(ret, p2, len);
2965b15c8340SDag-Erling Smørgrav
2966b15c8340SDag-Erling Smørgrav return(ret);
2967b15c8340SDag-Erling Smørgrav }
2968b15c8340SDag-Erling Smørgrav
2969535af610SEd Maste /*
2970535af610SEd Maste * Arg p must be dynamically allocated. It will either be returned or
2971535af610SEd Maste * freed and a replacement allocated. Caller must free returned string.
2972535af610SEd Maste */
297319261079SEd Maste char *
sftp_make_absolute(char * p,const char * pwd)2974edf85781SEd Maste sftp_make_absolute(char *p, const char *pwd)
297519261079SEd Maste {
297619261079SEd Maste char *abs_str;
297719261079SEd Maste
297819261079SEd Maste /* Derelativise */
297919261079SEd Maste if (p && !path_absolute(p)) {
2980edf85781SEd Maste abs_str = sftp_path_append(pwd, p);
298119261079SEd Maste free(p);
298219261079SEd Maste return(abs_str);
298319261079SEd Maste } else
298419261079SEd Maste return(p);
298519261079SEd Maste }
298619261079SEd Maste
298719261079SEd Maste int
sftp_remote_is_dir(struct sftp_conn * conn,const char * path)2988edf85781SEd Maste sftp_remote_is_dir(struct sftp_conn *conn, const char *path)
298919261079SEd Maste {
2990edf85781SEd Maste Attrib a;
299119261079SEd Maste
299219261079SEd Maste /* XXX: report errors? */
2993edf85781SEd Maste if (sftp_stat(conn, path, 1, &a) != 0)
299419261079SEd Maste return(0);
2995edf85781SEd Maste if (!(a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
299619261079SEd Maste return(0);
2997edf85781SEd Maste return S_ISDIR(a.perm);
299819261079SEd Maste }
299919261079SEd Maste
300019261079SEd Maste
300119261079SEd Maste /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
300219261079SEd Maste int
sftp_globpath_is_dir(const char * pathname)3003edf85781SEd Maste sftp_globpath_is_dir(const char *pathname)
300419261079SEd Maste {
300519261079SEd Maste size_t l = strlen(pathname);
300619261079SEd Maste
300719261079SEd Maste return l > 0 && pathname[l - 1] == '/';
300819261079SEd Maste }
300919261079SEd Maste
3010