xref: /freebsd/crypto/openssh/sftp-client.c (revision bc5531debefeb54993d01d4f3c8b33ccbe0b4d95)
1*bc5531deSDag-Erling Smørgrav /* $OpenBSD: sftp-client.c,v 1.117 2015/01/20 23:14:00 deraadt 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 
25*bc5531deSDag-Erling Smørgrav #include <sys/param.h>	/* MIN MAX */
26761efaa7SDag-Erling Smørgrav #include <sys/types.h>
27d4af9e69SDag-Erling Smørgrav #ifdef HAVE_SYS_STATVFS_H
28d4af9e69SDag-Erling Smørgrav #include <sys/statvfs.h>
29d4af9e69SDag-Erling Smørgrav #endif
304b17dab0SDag-Erling Smørgrav #include "openbsd-compat/sys-queue.h"
31761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_STAT_H
32761efaa7SDag-Erling Smørgrav # include <sys/stat.h>
33761efaa7SDag-Erling Smørgrav #endif
34761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_TIME_H
35761efaa7SDag-Erling Smørgrav # include <sys/time.h>
36761efaa7SDag-Erling Smørgrav #endif
37761efaa7SDag-Erling Smørgrav #include <sys/uio.h>
38ae1f160dSDag-Erling Smørgrav 
39b15c8340SDag-Erling Smørgrav #include <dirent.h>
40761efaa7SDag-Erling Smørgrav #include <errno.h>
41761efaa7SDag-Erling Smørgrav #include <fcntl.h>
42761efaa7SDag-Erling Smørgrav #include <signal.h>
43761efaa7SDag-Erling Smørgrav #include <stdarg.h>
44761efaa7SDag-Erling Smørgrav #include <stdio.h>
45f7167e0eSDag-Erling Smørgrav #include <stdlib.h>
46761efaa7SDag-Erling Smørgrav #include <string.h>
47761efaa7SDag-Erling Smørgrav #include <unistd.h>
48761efaa7SDag-Erling Smørgrav 
491e8db6e2SBrian Feldman #include "xmalloc.h"
50*bc5531deSDag-Erling Smørgrav #include "ssherr.h"
51*bc5531deSDag-Erling Smørgrav #include "sshbuf.h"
521e8db6e2SBrian Feldman #include "log.h"
531e8db6e2SBrian Feldman #include "atomicio.h"
54d0c8c0bcSDag-Erling Smørgrav #include "progressmeter.h"
55761efaa7SDag-Erling Smørgrav #include "misc.h"
561e8db6e2SBrian Feldman 
571e8db6e2SBrian Feldman #include "sftp.h"
581e8db6e2SBrian Feldman #include "sftp-common.h"
591e8db6e2SBrian Feldman #include "sftp-client.h"
601e8db6e2SBrian Feldman 
61d74d50a8SDag-Erling Smørgrav extern volatile sig_atomic_t interrupted;
62d0c8c0bcSDag-Erling Smørgrav extern int showprogress;
63d0c8c0bcSDag-Erling Smørgrav 
64761efaa7SDag-Erling Smørgrav /* Minimum amount of data to read at a time */
65ae1f160dSDag-Erling Smørgrav #define MIN_READ_SIZE	512
661e8db6e2SBrian Feldman 
67b15c8340SDag-Erling Smørgrav /* Maximum depth to descend in directory trees */
68b15c8340SDag-Erling Smørgrav #define MAX_DIR_DEPTH 64
69b15c8340SDag-Erling Smørgrav 
70ae1f160dSDag-Erling Smørgrav struct sftp_conn {
71ae1f160dSDag-Erling Smørgrav 	int fd_in;
72ae1f160dSDag-Erling Smørgrav 	int fd_out;
73ae1f160dSDag-Erling Smørgrav 	u_int transfer_buflen;
74ae1f160dSDag-Erling Smørgrav 	u_int num_requests;
75ae1f160dSDag-Erling Smørgrav 	u_int version;
76ae1f160dSDag-Erling Smørgrav 	u_int msg_id;
77d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_POSIX_RENAME	0x00000001
78d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_STATVFS	0x00000002
79d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_FSTATVFS	0x00000004
804a421b63SDag-Erling Smørgrav #define SFTP_EXT_HARDLINK	0x00000008
81f7167e0eSDag-Erling Smørgrav #define SFTP_EXT_FSYNC		0x00000010
82d4af9e69SDag-Erling Smørgrav 	u_int exts;
834a421b63SDag-Erling Smørgrav 	u_int64_t limit_kbps;
844a421b63SDag-Erling Smørgrav 	struct bwlimit bwlimit_in, bwlimit_out;
85ae1f160dSDag-Erling Smørgrav };
861e8db6e2SBrian Feldman 
87*bc5531deSDag-Erling Smørgrav static u_char *
88*bc5531deSDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
894a421b63SDag-Erling Smørgrav     const char *errfmt, ...) __attribute__((format(printf, 4, 5)));
904a421b63SDag-Erling Smørgrav 
914a421b63SDag-Erling Smørgrav /* ARGSUSED */
924a421b63SDag-Erling Smørgrav static int
934a421b63SDag-Erling Smørgrav sftpio(void *_bwlimit, size_t amount)
944a421b63SDag-Erling Smørgrav {
954a421b63SDag-Erling Smørgrav 	struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;
964a421b63SDag-Erling Smørgrav 
974a421b63SDag-Erling Smørgrav 	bandwidth_limit(bwlimit, amount);
984a421b63SDag-Erling Smørgrav 	return 0;
994a421b63SDag-Erling Smørgrav }
100b15c8340SDag-Erling Smørgrav 
101ae1f160dSDag-Erling Smørgrav static void
102*bc5531deSDag-Erling Smørgrav send_msg(struct sftp_conn *conn, struct sshbuf *m)
1031e8db6e2SBrian Feldman {
104d0c8c0bcSDag-Erling Smørgrav 	u_char mlen[4];
105761efaa7SDag-Erling Smørgrav 	struct iovec iov[2];
1061e8db6e2SBrian Feldman 
107*bc5531deSDag-Erling Smørgrav 	if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH)
108*bc5531deSDag-Erling Smørgrav 		fatal("Outbound message too long %zu", sshbuf_len(m));
1091e8db6e2SBrian Feldman 
110d0c8c0bcSDag-Erling Smørgrav 	/* Send length first */
111*bc5531deSDag-Erling Smørgrav 	put_u32(mlen, sshbuf_len(m));
112761efaa7SDag-Erling Smørgrav 	iov[0].iov_base = mlen;
113761efaa7SDag-Erling Smørgrav 	iov[0].iov_len = sizeof(mlen);
114*bc5531deSDag-Erling Smørgrav 	iov[1].iov_base = (u_char *)sshbuf_ptr(m);
115*bc5531deSDag-Erling Smørgrav 	iov[1].iov_len = sshbuf_len(m);
1161e8db6e2SBrian Feldman 
1174a421b63SDag-Erling Smørgrav 	if (atomiciov6(writev, conn->fd_out, iov, 2,
1184a421b63SDag-Erling Smørgrav 	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) !=
119*bc5531deSDag-Erling Smørgrav 	    sshbuf_len(m) + sizeof(mlen))
120d0c8c0bcSDag-Erling Smørgrav 		fatal("Couldn't send packet: %s", strerror(errno));
121d0c8c0bcSDag-Erling Smørgrav 
122*bc5531deSDag-Erling Smørgrav 	sshbuf_reset(m);
1231e8db6e2SBrian Feldman }
1241e8db6e2SBrian Feldman 
125ae1f160dSDag-Erling Smørgrav static void
126*bc5531deSDag-Erling Smørgrav get_msg(struct sftp_conn *conn, struct sshbuf *m)
1271e8db6e2SBrian Feldman {
128d0c8c0bcSDag-Erling Smørgrav 	u_int msg_len;
129*bc5531deSDag-Erling Smørgrav 	u_char *p;
130*bc5531deSDag-Erling Smørgrav 	int r;
1311e8db6e2SBrian Feldman 
132*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_reserve(m, 4, &p)) != 0)
133*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
134*bc5531deSDag-Erling Smørgrav 	if (atomicio6(read, conn->fd_in, p, 4,
1354a421b63SDag-Erling Smørgrav 	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) {
136043840dfSDag-Erling Smørgrav 		if (errno == EPIPE)
1371e8db6e2SBrian Feldman 			fatal("Connection closed");
138043840dfSDag-Erling Smørgrav 		else
1391e8db6e2SBrian Feldman 			fatal("Couldn't read packet: %s", strerror(errno));
140043840dfSDag-Erling Smørgrav 	}
1411e8db6e2SBrian Feldman 
142*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u32(m, &msg_len)) != 0)
143*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
144021d409fSDag-Erling Smørgrav 	if (msg_len > SFTP_MAX_MSG_LENGTH)
145ee21a45fSDag-Erling Smørgrav 		fatal("Received message too long %u", msg_len);
1461e8db6e2SBrian Feldman 
147*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_reserve(m, msg_len, &p)) != 0)
148*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
149*bc5531deSDag-Erling Smørgrav 	if (atomicio6(read, conn->fd_in, p, msg_len,
1504a421b63SDag-Erling Smørgrav 	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in)
1514a421b63SDag-Erling Smørgrav 	    != msg_len) {
152043840dfSDag-Erling Smørgrav 		if (errno == EPIPE)
1531e8db6e2SBrian Feldman 			fatal("Connection closed");
154043840dfSDag-Erling Smørgrav 		else
155d0c8c0bcSDag-Erling Smørgrav 			fatal("Read packet: %s", strerror(errno));
1561e8db6e2SBrian Feldman 	}
157043840dfSDag-Erling Smørgrav }
1581e8db6e2SBrian Feldman 
159ae1f160dSDag-Erling Smørgrav static void
160*bc5531deSDag-Erling Smørgrav send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s,
1611e8db6e2SBrian Feldman     u_int len)
1621e8db6e2SBrian Feldman {
163*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
164*bc5531deSDag-Erling Smørgrav 	int r;
1651e8db6e2SBrian Feldman 
166*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
167*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
168*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
169*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
170*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, s, len)) != 0)
171*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
172*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
1734a421b63SDag-Erling Smørgrav 	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
174*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
1751e8db6e2SBrian Feldman }
1761e8db6e2SBrian Feldman 
177ae1f160dSDag-Erling Smørgrav static void
1784a421b63SDag-Erling Smørgrav send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
179*bc5531deSDag-Erling Smørgrav     const void *s, u_int len, Attrib *a)
1801e8db6e2SBrian Feldman {
181*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
182*bc5531deSDag-Erling Smørgrav 	int r;
1831e8db6e2SBrian Feldman 
184*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
185*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
186*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
187*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
188*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, s, len)) != 0 ||
189*bc5531deSDag-Erling Smørgrav 	    (r = encode_attrib(msg, a)) != 0)
190*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
191*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
1924a421b63SDag-Erling Smørgrav 	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
193*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
1941e8db6e2SBrian Feldman }
1951e8db6e2SBrian Feldman 
196ae1f160dSDag-Erling Smørgrav static u_int
1974a421b63SDag-Erling Smørgrav get_status(struct sftp_conn *conn, u_int expected_id)
1981e8db6e2SBrian Feldman {
199*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
200*bc5531deSDag-Erling Smørgrav 	u_char type;
201*bc5531deSDag-Erling Smørgrav 	u_int id, status;
202*bc5531deSDag-Erling Smørgrav 	int r;
2031e8db6e2SBrian Feldman 
204*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
205*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
206*bc5531deSDag-Erling Smørgrav 	get_msg(conn, msg);
207*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
208*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(msg, &id)) != 0)
209*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
2101e8db6e2SBrian Feldman 
2111e8db6e2SBrian Feldman 	if (id != expected_id)
212ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
2131e8db6e2SBrian Feldman 	if (type != SSH2_FXP_STATUS)
214ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
2151e8db6e2SBrian Feldman 		    SSH2_FXP_STATUS, type);
2161e8db6e2SBrian Feldman 
217*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u32(msg, &status)) != 0)
218*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
219*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
2201e8db6e2SBrian Feldman 
221ee21a45fSDag-Erling Smørgrav 	debug3("SSH2_FXP_STATUS %u", status);
2221e8db6e2SBrian Feldman 
2234a421b63SDag-Erling Smørgrav 	return status;
2241e8db6e2SBrian Feldman }
2251e8db6e2SBrian Feldman 
226*bc5531deSDag-Erling Smørgrav static u_char *
227*bc5531deSDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
2284a421b63SDag-Erling Smørgrav     const char *errfmt, ...)
2291e8db6e2SBrian Feldman {
230*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
231*bc5531deSDag-Erling Smørgrav 	u_int id, status;
232*bc5531deSDag-Erling Smørgrav 	u_char type;
233*bc5531deSDag-Erling Smørgrav 	u_char *handle;
234*bc5531deSDag-Erling Smørgrav 	char errmsg[256];
235b15c8340SDag-Erling Smørgrav 	va_list args;
236*bc5531deSDag-Erling Smørgrav 	int r;
237b15c8340SDag-Erling Smørgrav 
238b15c8340SDag-Erling Smørgrav 	va_start(args, errfmt);
239b15c8340SDag-Erling Smørgrav 	if (errfmt != NULL)
240b15c8340SDag-Erling Smørgrav 		vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
241b15c8340SDag-Erling Smørgrav 	va_end(args);
2421e8db6e2SBrian Feldman 
243*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
244*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
245*bc5531deSDag-Erling Smørgrav 	get_msg(conn, msg);
246*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
247*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(msg, &id)) != 0)
248*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
2491e8db6e2SBrian Feldman 
2501e8db6e2SBrian Feldman 	if (id != expected_id)
251b15c8340SDag-Erling Smørgrav 		fatal("%s: ID mismatch (%u != %u)",
252b15c8340SDag-Erling Smørgrav 		    errfmt == NULL ? __func__ : errmsg, id, expected_id);
2531e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
254*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
255*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
256b15c8340SDag-Erling Smørgrav 		if (errfmt != NULL)
257b15c8340SDag-Erling Smørgrav 			error("%s: %s", errmsg, fx2txt(status));
258*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
2591e8db6e2SBrian Feldman 		return(NULL);
2601e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_HANDLE)
261b15c8340SDag-Erling Smørgrav 		fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
262b15c8340SDag-Erling Smørgrav 		    errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
2631e8db6e2SBrian Feldman 
264*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_string(msg, &handle, len)) != 0)
265*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
266*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
2671e8db6e2SBrian Feldman 
268*bc5531deSDag-Erling Smørgrav 	return handle;
2691e8db6e2SBrian Feldman }
2701e8db6e2SBrian Feldman 
271ae1f160dSDag-Erling Smørgrav static Attrib *
2724a421b63SDag-Erling Smørgrav get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
2731e8db6e2SBrian Feldman {
274*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
275*bc5531deSDag-Erling Smørgrav 	u_int id;
276*bc5531deSDag-Erling Smørgrav 	u_char type;
277*bc5531deSDag-Erling Smørgrav 	int r;
278*bc5531deSDag-Erling Smørgrav 	static Attrib a;
2791e8db6e2SBrian Feldman 
280*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
281*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
282*bc5531deSDag-Erling Smørgrav 	get_msg(conn, msg);
2831e8db6e2SBrian Feldman 
284*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
285*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(msg, &id)) != 0)
286*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
2871e8db6e2SBrian Feldman 
288ee21a45fSDag-Erling Smørgrav 	debug3("Received stat reply T:%u I:%u", type, id);
2891e8db6e2SBrian Feldman 	if (id != expected_id)
290ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
2911e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
292*bc5531deSDag-Erling Smørgrav 		u_int status;
2931e8db6e2SBrian Feldman 
294*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
295*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
2961e8db6e2SBrian Feldman 		if (quiet)
2971e8db6e2SBrian Feldman 			debug("Couldn't stat remote file: %s", fx2txt(status));
2981e8db6e2SBrian Feldman 		else
2991e8db6e2SBrian Feldman 			error("Couldn't stat remote file: %s", fx2txt(status));
300*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
3011e8db6e2SBrian Feldman 		return(NULL);
3021e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_ATTRS) {
303ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
3041e8db6e2SBrian Feldman 		    SSH2_FXP_ATTRS, type);
3051e8db6e2SBrian Feldman 	}
306*bc5531deSDag-Erling Smørgrav 	if ((r = decode_attrib(msg, &a)) != 0) {
307*bc5531deSDag-Erling Smørgrav 		error("%s: couldn't decode attrib: %s", __func__, ssh_err(r));
308*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
309*bc5531deSDag-Erling Smørgrav 		return NULL;
310*bc5531deSDag-Erling Smørgrav 	}
311*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
3121e8db6e2SBrian Feldman 
313*bc5531deSDag-Erling Smørgrav 	return &a;
3141e8db6e2SBrian Feldman }
3151e8db6e2SBrian Feldman 
316d4af9e69SDag-Erling Smørgrav static int
3174a421b63SDag-Erling Smørgrav get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
3184a421b63SDag-Erling Smørgrav     u_int expected_id, int quiet)
319d4af9e69SDag-Erling Smørgrav {
320*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
321*bc5531deSDag-Erling Smørgrav 	u_char type;
322*bc5531deSDag-Erling Smørgrav 	u_int id;
323*bc5531deSDag-Erling Smørgrav 	u_int64_t flag;
324*bc5531deSDag-Erling Smørgrav 	int r;
325d4af9e69SDag-Erling Smørgrav 
326*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
327*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
328*bc5531deSDag-Erling Smørgrav 	get_msg(conn, msg);
329d4af9e69SDag-Erling Smørgrav 
330*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
331*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(msg, &id)) != 0)
332*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
333d4af9e69SDag-Erling Smørgrav 
334d4af9e69SDag-Erling Smørgrav 	debug3("Received statvfs reply T:%u I:%u", type, id);
335d4af9e69SDag-Erling Smørgrav 	if (id != expected_id)
336d4af9e69SDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
337d4af9e69SDag-Erling Smørgrav 	if (type == SSH2_FXP_STATUS) {
338*bc5531deSDag-Erling Smørgrav 		u_int status;
339d4af9e69SDag-Erling Smørgrav 
340*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
341*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
342d4af9e69SDag-Erling Smørgrav 		if (quiet)
343d4af9e69SDag-Erling Smørgrav 			debug("Couldn't statvfs: %s", fx2txt(status));
344d4af9e69SDag-Erling Smørgrav 		else
345d4af9e69SDag-Erling Smørgrav 			error("Couldn't statvfs: %s", fx2txt(status));
346*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
347d4af9e69SDag-Erling Smørgrav 		return -1;
348d4af9e69SDag-Erling Smørgrav 	} else if (type != SSH2_FXP_EXTENDED_REPLY) {
349d4af9e69SDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
350d4af9e69SDag-Erling Smørgrav 		    SSH2_FXP_EXTENDED_REPLY, type);
351d4af9e69SDag-Erling Smørgrav 	}
352d4af9e69SDag-Erling Smørgrav 
353b83788ffSDag-Erling Smørgrav 	memset(st, 0, sizeof(*st));
354*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 ||
355*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 ||
356*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 ||
357*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 ||
358*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 ||
359*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_files)) != 0 ||
360*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 ||
361*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 ||
362*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 ||
363*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &flag)) != 0 ||
364*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0)
365*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
366d4af9e69SDag-Erling Smørgrav 
367d4af9e69SDag-Erling Smørgrav 	st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
368d4af9e69SDag-Erling Smørgrav 	st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
369d4af9e69SDag-Erling Smørgrav 
370*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
371d4af9e69SDag-Erling Smørgrav 
372d4af9e69SDag-Erling Smørgrav 	return 0;
373d4af9e69SDag-Erling Smørgrav }
374d4af9e69SDag-Erling Smørgrav 
375ae1f160dSDag-Erling Smørgrav struct sftp_conn *
3764a421b63SDag-Erling Smørgrav do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
3774a421b63SDag-Erling Smørgrav     u_int64_t limit_kbps)
3781e8db6e2SBrian Feldman {
379*bc5531deSDag-Erling Smørgrav 	u_char type;
380*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
381ae1f160dSDag-Erling Smørgrav 	struct sftp_conn *ret;
382*bc5531deSDag-Erling Smørgrav 	int r;
3831e8db6e2SBrian Feldman 
384f7167e0eSDag-Erling Smørgrav 	ret = xcalloc(1, sizeof(*ret));
385f7167e0eSDag-Erling Smørgrav 	ret->msg_id = 1;
3864a421b63SDag-Erling Smørgrav 	ret->fd_in = fd_in;
3874a421b63SDag-Erling Smørgrav 	ret->fd_out = fd_out;
3884a421b63SDag-Erling Smørgrav 	ret->transfer_buflen = transfer_buflen;
3894a421b63SDag-Erling Smørgrav 	ret->num_requests = num_requests;
3904a421b63SDag-Erling Smørgrav 	ret->exts = 0;
3914a421b63SDag-Erling Smørgrav 	ret->limit_kbps = 0;
3924a421b63SDag-Erling Smørgrav 
393*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
394*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
395*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 ||
396*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0)
397*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
398*bc5531deSDag-Erling Smørgrav 	send_msg(ret, msg);
3991e8db6e2SBrian Feldman 
400*bc5531deSDag-Erling Smørgrav 	sshbuf_reset(msg);
4011e8db6e2SBrian Feldman 
402*bc5531deSDag-Erling Smørgrav 	get_msg(ret, msg);
4031e8db6e2SBrian Feldman 
4041e8db6e2SBrian Feldman 	/* Expecting a VERSION reply */
405*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(msg, &type)) != 0)
406*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
407*bc5531deSDag-Erling Smørgrav 	if (type != SSH2_FXP_VERSION) {
408ee21a45fSDag-Erling Smørgrav 		error("Invalid packet back from SSH2_FXP_INIT (type %u)",
4091e8db6e2SBrian Feldman 		    type);
410*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
411ae1f160dSDag-Erling Smørgrav 		return(NULL);
4121e8db6e2SBrian Feldman 	}
413*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u32(msg, &ret->version)) != 0)
414*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
4151e8db6e2SBrian Feldman 
4164a421b63SDag-Erling Smørgrav 	debug2("Remote version: %u", ret->version);
4171e8db6e2SBrian Feldman 
4181e8db6e2SBrian Feldman 	/* Check for extensions */
419*bc5531deSDag-Erling Smørgrav 	while (sshbuf_len(msg) > 0) {
420*bc5531deSDag-Erling Smørgrav 		char *name;
421*bc5531deSDag-Erling Smørgrav 		u_char *value;
422*bc5531deSDag-Erling Smørgrav 		size_t vlen;
423d4af9e69SDag-Erling Smørgrav 		int known = 0;
4241e8db6e2SBrian Feldman 
425*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 ||
426*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_get_string(msg, &value, &vlen)) != 0)
427*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
428d4af9e69SDag-Erling Smørgrav 		if (strcmp(name, "posix-rename@openssh.com") == 0 &&
429*bc5531deSDag-Erling Smørgrav 		    strcmp((char *)value, "1") == 0) {
4304a421b63SDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_POSIX_RENAME;
431d4af9e69SDag-Erling Smørgrav 			known = 1;
432d4af9e69SDag-Erling Smørgrav 		} else if (strcmp(name, "statvfs@openssh.com") == 0 &&
433*bc5531deSDag-Erling Smørgrav 		    strcmp((char *)value, "2") == 0) {
4344a421b63SDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_STATVFS;
435d4af9e69SDag-Erling Smørgrav 			known = 1;
4364a421b63SDag-Erling Smørgrav 		} else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
437*bc5531deSDag-Erling Smørgrav 		    strcmp((char *)value, "2") == 0) {
4384a421b63SDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_FSTATVFS;
4394a421b63SDag-Erling Smørgrav 			known = 1;
4404a421b63SDag-Erling Smørgrav 		} else if (strcmp(name, "hardlink@openssh.com") == 0 &&
441*bc5531deSDag-Erling Smørgrav 		    strcmp((char *)value, "1") == 0) {
4424a421b63SDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_HARDLINK;
443d4af9e69SDag-Erling Smørgrav 			known = 1;
444f7167e0eSDag-Erling Smørgrav 		} else if (strcmp(name, "fsync@openssh.com") == 0 &&
445*bc5531deSDag-Erling Smørgrav 		    strcmp((char *)value, "1") == 0) {
446f7167e0eSDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_FSYNC;
447f7167e0eSDag-Erling Smørgrav 			known = 1;
448d4af9e69SDag-Erling Smørgrav 		}
449d4af9e69SDag-Erling Smørgrav 		if (known) {
450d4af9e69SDag-Erling Smørgrav 			debug2("Server supports extension \"%s\" revision %s",
451d4af9e69SDag-Erling Smørgrav 			    name, value);
452d4af9e69SDag-Erling Smørgrav 		} else {
453d4af9e69SDag-Erling Smørgrav 			debug2("Unrecognised server extension \"%s\"", name);
454d4af9e69SDag-Erling Smørgrav 		}
455e4a9863fSDag-Erling Smørgrav 		free(name);
456e4a9863fSDag-Erling Smørgrav 		free(value);
4571e8db6e2SBrian Feldman 	}
4581e8db6e2SBrian Feldman 
459*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
4601e8db6e2SBrian Feldman 
461ae1f160dSDag-Erling Smørgrav 	/* Some filexfer v.0 servers don't support large packets */
4624a421b63SDag-Erling Smørgrav 	if (ret->version == 0)
463545d5ecaSDag-Erling Smørgrav 		ret->transfer_buflen = MIN(ret->transfer_buflen, 20480);
464ae1f160dSDag-Erling Smørgrav 
4654a421b63SDag-Erling Smørgrav 	ret->limit_kbps = limit_kbps;
4664a421b63SDag-Erling Smørgrav 	if (ret->limit_kbps > 0) {
4674a421b63SDag-Erling Smørgrav 		bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
4684a421b63SDag-Erling Smørgrav 		    ret->transfer_buflen);
4694a421b63SDag-Erling Smørgrav 		bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
4704a421b63SDag-Erling Smørgrav 		    ret->transfer_buflen);
4714a421b63SDag-Erling Smørgrav 	}
4724a421b63SDag-Erling Smørgrav 
4734a421b63SDag-Erling Smørgrav 	return ret;
474ae1f160dSDag-Erling Smørgrav }
475ae1f160dSDag-Erling Smørgrav 
476ae1f160dSDag-Erling Smørgrav u_int
477ae1f160dSDag-Erling Smørgrav sftp_proto_version(struct sftp_conn *conn)
478ae1f160dSDag-Erling Smørgrav {
4794a421b63SDag-Erling Smørgrav 	return conn->version;
4801e8db6e2SBrian Feldman }
4811e8db6e2SBrian Feldman 
4821e8db6e2SBrian Feldman int
483*bc5531deSDag-Erling Smørgrav do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
4841e8db6e2SBrian Feldman {
4851e8db6e2SBrian Feldman 	u_int id, status;
486*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
487*bc5531deSDag-Erling Smørgrav 	int r;
4881e8db6e2SBrian Feldman 
489*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
490*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
4911e8db6e2SBrian Feldman 
492ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
493*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 ||
494*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
495*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
496*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
497*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
498ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
4991e8db6e2SBrian Feldman 
5004a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
5011e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
5021e8db6e2SBrian Feldman 		error("Couldn't close file: %s", fx2txt(status));
5031e8db6e2SBrian Feldman 
504*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
5051e8db6e2SBrian Feldman 
506*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
5071e8db6e2SBrian Feldman }
5081e8db6e2SBrian Feldman 
5091e8db6e2SBrian Feldman 
510ae1f160dSDag-Erling Smørgrav static int
511*bc5531deSDag-Erling Smørgrav do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
5121e8db6e2SBrian Feldman     SFTP_DIRENT ***dir)
5131e8db6e2SBrian Feldman {
514*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
515*bc5531deSDag-Erling Smørgrav 	u_int count, id, i, expected_id, ents = 0;
516*bc5531deSDag-Erling Smørgrav 	size_t handle_len;
517*bc5531deSDag-Erling Smørgrav 	u_char type;
5181e8db6e2SBrian Feldman 	char *handle;
519f7167e0eSDag-Erling Smørgrav 	int status = SSH2_FX_FAILURE;
520*bc5531deSDag-Erling Smørgrav 	int r;
521f7167e0eSDag-Erling Smørgrav 
522f7167e0eSDag-Erling Smørgrav 	if (dir)
523f7167e0eSDag-Erling Smørgrav 		*dir = NULL;
5241e8db6e2SBrian Feldman 
525ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
5261e8db6e2SBrian Feldman 
527*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
528*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
529*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 ||
530*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
531*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, path)) != 0)
532*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
533*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
5341e8db6e2SBrian Feldman 
5354a421b63SDag-Erling Smørgrav 	handle = get_handle(conn, id, &handle_len,
536b15c8340SDag-Erling Smørgrav 	    "remote readdir(\"%s\")", path);
537462c32cbSDag-Erling Smørgrav 	if (handle == NULL) {
538*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
5394a421b63SDag-Erling Smørgrav 		return -1;
540462c32cbSDag-Erling Smørgrav 	}
5411e8db6e2SBrian Feldman 
5421e8db6e2SBrian Feldman 	if (dir) {
5431e8db6e2SBrian Feldman 		ents = 0;
5440a37d4a3SXin LI 		*dir = xcalloc(1, sizeof(**dir));
5451e8db6e2SBrian Feldman 		(*dir)[0] = NULL;
5461e8db6e2SBrian Feldman 	}
5471e8db6e2SBrian Feldman 
548d74d50a8SDag-Erling Smørgrav 	for (; !interrupted;) {
549ae1f160dSDag-Erling Smørgrav 		id = expected_id = conn->msg_id++;
5501e8db6e2SBrian Feldman 
551ee21a45fSDag-Erling Smørgrav 		debug3("Sending SSH2_FXP_READDIR I:%u", id);
5521e8db6e2SBrian Feldman 
553*bc5531deSDag-Erling Smørgrav 		sshbuf_reset(msg);
554*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 ||
555*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_u32(msg, id)) != 0 ||
556*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
557*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
558*bc5531deSDag-Erling Smørgrav 		send_msg(conn, msg);
5591e8db6e2SBrian Feldman 
560*bc5531deSDag-Erling Smørgrav 		sshbuf_reset(msg);
5611e8db6e2SBrian Feldman 
562*bc5531deSDag-Erling Smørgrav 		get_msg(conn, msg);
5631e8db6e2SBrian Feldman 
564*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
565*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_get_u32(msg, &id)) != 0)
566*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
5671e8db6e2SBrian Feldman 
568ee21a45fSDag-Erling Smørgrav 		debug3("Received reply T:%u I:%u", type, id);
5691e8db6e2SBrian Feldman 
5701e8db6e2SBrian Feldman 		if (id != expected_id)
571ee21a45fSDag-Erling Smørgrav 			fatal("ID mismatch (%u != %u)", id, expected_id);
5721e8db6e2SBrian Feldman 
5731e8db6e2SBrian Feldman 		if (type == SSH2_FXP_STATUS) {
574*bc5531deSDag-Erling Smørgrav 			u_int rstatus;
575*bc5531deSDag-Erling Smørgrav 
576*bc5531deSDag-Erling Smørgrav 			if ((r = sshbuf_get_u32(msg, &rstatus)) != 0)
577*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
578*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
579*bc5531deSDag-Erling Smørgrav 			debug3("Received SSH2_FXP_STATUS %d", rstatus);
580*bc5531deSDag-Erling Smørgrav 			if (rstatus == SSH2_FX_EOF)
5811e8db6e2SBrian Feldman 				break;
582*bc5531deSDag-Erling Smørgrav 			error("Couldn't read directory: %s", fx2txt(rstatus));
583f7167e0eSDag-Erling Smørgrav 			goto out;
5841e8db6e2SBrian Feldman 		} else if (type != SSH2_FXP_NAME)
585ee21a45fSDag-Erling Smørgrav 			fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
5861e8db6e2SBrian Feldman 			    SSH2_FXP_NAME, type);
5871e8db6e2SBrian Feldman 
588*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(msg, &count)) != 0)
589*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
5901e8db6e2SBrian Feldman 		if (count == 0)
5911e8db6e2SBrian Feldman 			break;
5921e8db6e2SBrian Feldman 		debug3("Received %d SSH2_FXP_NAME responses", count);
5931e8db6e2SBrian Feldman 		for (i = 0; i < count; i++) {
5941e8db6e2SBrian Feldman 			char *filename, *longname;
595*bc5531deSDag-Erling Smørgrav 			Attrib a;
5961e8db6e2SBrian Feldman 
597*bc5531deSDag-Erling Smørgrav 			if ((r = sshbuf_get_cstring(msg, &filename,
598*bc5531deSDag-Erling Smørgrav 			    NULL)) != 0 ||
599*bc5531deSDag-Erling Smørgrav 			    (r = sshbuf_get_cstring(msg, &longname,
600*bc5531deSDag-Erling Smørgrav 			    NULL)) != 0)
601*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
602*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
603*bc5531deSDag-Erling Smørgrav 			if ((r = decode_attrib(msg, &a)) != 0) {
604*bc5531deSDag-Erling Smørgrav 				error("%s: couldn't decode attrib: %s",
605*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
606*bc5531deSDag-Erling Smørgrav 				free(filename);
607*bc5531deSDag-Erling Smørgrav 				free(longname);
608*bc5531deSDag-Erling Smørgrav 				sshbuf_free(msg);
609*bc5531deSDag-Erling Smørgrav 				return -1;
610*bc5531deSDag-Erling Smørgrav 			}
6111e8db6e2SBrian Feldman 
612f7167e0eSDag-Erling Smørgrav 			if (print_flag)
6131e8db6e2SBrian Feldman 				printf("%s\n", longname);
6141e8db6e2SBrian Feldman 
615b15c8340SDag-Erling Smørgrav 			/*
616b15c8340SDag-Erling Smørgrav 			 * Directory entries should never contain '/'
617b15c8340SDag-Erling Smørgrav 			 * These can be used to attack recursive ops
618b15c8340SDag-Erling Smørgrav 			 * (e.g. send '../../../../etc/passwd')
619b15c8340SDag-Erling Smørgrav 			 */
620b15c8340SDag-Erling Smørgrav 			if (strchr(filename, '/') != NULL) {
621b15c8340SDag-Erling Smørgrav 				error("Server sent suspect path \"%s\" "
622b15c8340SDag-Erling Smørgrav 				    "during readdir of \"%s\"", filename, path);
623f7167e0eSDag-Erling Smørgrav 			} else if (dir) {
624761efaa7SDag-Erling Smørgrav 				*dir = xrealloc(*dir, ents + 2, sizeof(**dir));
6250a37d4a3SXin LI 				(*dir)[ents] = xcalloc(1, sizeof(***dir));
6261e8db6e2SBrian Feldman 				(*dir)[ents]->filename = xstrdup(filename);
6271e8db6e2SBrian Feldman 				(*dir)[ents]->longname = xstrdup(longname);
628*bc5531deSDag-Erling Smørgrav 				memcpy(&(*dir)[ents]->a, &a, sizeof(a));
6291e8db6e2SBrian Feldman 				(*dir)[++ents] = NULL;
6301e8db6e2SBrian Feldman 			}
631e4a9863fSDag-Erling Smørgrav 			free(filename);
632e4a9863fSDag-Erling Smørgrav 			free(longname);
6331e8db6e2SBrian Feldman 		}
6341e8db6e2SBrian Feldman 	}
635f7167e0eSDag-Erling Smørgrav 	status = 0;
6361e8db6e2SBrian Feldman 
637f7167e0eSDag-Erling Smørgrav  out:
638*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
639ae1f160dSDag-Erling Smørgrav 	do_close(conn, handle, handle_len);
640e4a9863fSDag-Erling Smørgrav 	free(handle);
6411e8db6e2SBrian Feldman 
642f7167e0eSDag-Erling Smørgrav 	if (status != 0 && dir != NULL) {
643f7167e0eSDag-Erling Smørgrav 		/* Don't return results on error */
644f7167e0eSDag-Erling Smørgrav 		free_sftp_dirents(*dir);
645f7167e0eSDag-Erling Smørgrav 		*dir = NULL;
646f7167e0eSDag-Erling Smørgrav 	} else if (interrupted && dir != NULL && *dir != NULL) {
647d74d50a8SDag-Erling Smørgrav 		/* Don't return partial matches on interrupt */
648d74d50a8SDag-Erling Smørgrav 		free_sftp_dirents(*dir);
6490a37d4a3SXin LI 		*dir = xcalloc(1, sizeof(**dir));
650d74d50a8SDag-Erling Smørgrav 		**dir = NULL;
651d74d50a8SDag-Erling Smørgrav 	}
652d74d50a8SDag-Erling Smørgrav 
653f7167e0eSDag-Erling Smørgrav 	return status;
6541e8db6e2SBrian Feldman }
6551e8db6e2SBrian Feldman 
6561e8db6e2SBrian Feldman int
657*bc5531deSDag-Erling Smørgrav do_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
6581e8db6e2SBrian Feldman {
659ae1f160dSDag-Erling Smørgrav 	return(do_lsreaddir(conn, path, 0, dir));
6601e8db6e2SBrian Feldman }
6611e8db6e2SBrian Feldman 
6621e8db6e2SBrian Feldman void free_sftp_dirents(SFTP_DIRENT **s)
6631e8db6e2SBrian Feldman {
6641e8db6e2SBrian Feldman 	int i;
6651e8db6e2SBrian Feldman 
666f7167e0eSDag-Erling Smørgrav 	if (s == NULL)
667f7167e0eSDag-Erling Smørgrav 		return;
6681e8db6e2SBrian Feldman 	for (i = 0; s[i]; i++) {
669e4a9863fSDag-Erling Smørgrav 		free(s[i]->filename);
670e4a9863fSDag-Erling Smørgrav 		free(s[i]->longname);
671e4a9863fSDag-Erling Smørgrav 		free(s[i]);
6721e8db6e2SBrian Feldman 	}
673e4a9863fSDag-Erling Smørgrav 	free(s);
6741e8db6e2SBrian Feldman }
6751e8db6e2SBrian Feldman 
6761e8db6e2SBrian Feldman int
677*bc5531deSDag-Erling Smørgrav do_rm(struct sftp_conn *conn, const char *path)
6781e8db6e2SBrian Feldman {
6791e8db6e2SBrian Feldman 	u_int status, id;
6801e8db6e2SBrian Feldman 
6811e8db6e2SBrian Feldman 	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
6821e8db6e2SBrian Feldman 
683ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6844a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
6854a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
6861e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
6871e8db6e2SBrian Feldman 		error("Couldn't delete file: %s", fx2txt(status));
688*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
6891e8db6e2SBrian Feldman }
6901e8db6e2SBrian Feldman 
6911e8db6e2SBrian Feldman int
692*bc5531deSDag-Erling Smørgrav do_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
6931e8db6e2SBrian Feldman {
6941e8db6e2SBrian Feldman 	u_int status, id;
6951e8db6e2SBrian Feldman 
696ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6974a421b63SDag-Erling Smørgrav 	send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
6981e8db6e2SBrian Feldman 	    strlen(path), a);
6991e8db6e2SBrian Feldman 
7004a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
701f7167e0eSDag-Erling Smørgrav 	if (status != SSH2_FX_OK && print_flag)
7021e8db6e2SBrian Feldman 		error("Couldn't create directory: %s", fx2txt(status));
7031e8db6e2SBrian Feldman 
704*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
7051e8db6e2SBrian Feldman }
7061e8db6e2SBrian Feldman 
7071e8db6e2SBrian Feldman int
708*bc5531deSDag-Erling Smørgrav do_rmdir(struct sftp_conn *conn, const char *path)
7091e8db6e2SBrian Feldman {
7101e8db6e2SBrian Feldman 	u_int status, id;
7111e8db6e2SBrian Feldman 
712ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
7134a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_RMDIR, path,
714ae1f160dSDag-Erling Smørgrav 	    strlen(path));
7151e8db6e2SBrian Feldman 
7164a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
7171e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
7181e8db6e2SBrian Feldman 		error("Couldn't remove directory: %s", fx2txt(status));
7191e8db6e2SBrian Feldman 
720*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
7211e8db6e2SBrian Feldman }
7221e8db6e2SBrian Feldman 
7231e8db6e2SBrian Feldman Attrib *
724*bc5531deSDag-Erling Smørgrav do_stat(struct sftp_conn *conn, const char *path, int quiet)
7251e8db6e2SBrian Feldman {
7261e8db6e2SBrian Feldman 	u_int id;
7271e8db6e2SBrian Feldman 
728ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
729ae1f160dSDag-Erling Smørgrav 
7304a421b63SDag-Erling Smørgrav 	send_string_request(conn, id,
731ae1f160dSDag-Erling Smørgrav 	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
732ae1f160dSDag-Erling Smørgrav 	    path, strlen(path));
733ae1f160dSDag-Erling Smørgrav 
7344a421b63SDag-Erling Smørgrav 	return(get_decode_stat(conn, id, quiet));
7351e8db6e2SBrian Feldman }
7361e8db6e2SBrian Feldman 
7371e8db6e2SBrian Feldman Attrib *
738*bc5531deSDag-Erling Smørgrav do_lstat(struct sftp_conn *conn, const char *path, int quiet)
7391e8db6e2SBrian Feldman {
7401e8db6e2SBrian Feldman 	u_int id;
7411e8db6e2SBrian Feldman 
742ae1f160dSDag-Erling Smørgrav 	if (conn->version == 0) {
743ae1f160dSDag-Erling Smørgrav 		if (quiet)
744ae1f160dSDag-Erling Smørgrav 			debug("Server version does not support lstat operation");
745ae1f160dSDag-Erling Smørgrav 		else
746d95e11bfSDag-Erling Smørgrav 			logit("Server version does not support lstat operation");
747545d5ecaSDag-Erling Smørgrav 		return(do_stat(conn, path, quiet));
748ae1f160dSDag-Erling Smørgrav 	}
749ae1f160dSDag-Erling Smørgrav 
750ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
7514a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_LSTAT, path,
752ae1f160dSDag-Erling Smørgrav 	    strlen(path));
753ae1f160dSDag-Erling Smørgrav 
7544a421b63SDag-Erling Smørgrav 	return(get_decode_stat(conn, id, quiet));
7551e8db6e2SBrian Feldman }
7561e8db6e2SBrian Feldman 
757d4af9e69SDag-Erling Smørgrav #ifdef notyet
7581e8db6e2SBrian Feldman Attrib *
759*bc5531deSDag-Erling Smørgrav do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
760*bc5531deSDag-Erling Smørgrav     int quiet)
7611e8db6e2SBrian Feldman {
7621e8db6e2SBrian Feldman 	u_int id;
7631e8db6e2SBrian Feldman 
764ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
7654a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
766ae1f160dSDag-Erling Smørgrav 	    handle_len);
767ae1f160dSDag-Erling Smørgrav 
7684a421b63SDag-Erling Smørgrav 	return(get_decode_stat(conn, id, quiet));
7691e8db6e2SBrian Feldman }
770d4af9e69SDag-Erling Smørgrav #endif
7711e8db6e2SBrian Feldman 
7721e8db6e2SBrian Feldman int
773*bc5531deSDag-Erling Smørgrav do_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
7741e8db6e2SBrian Feldman {
7751e8db6e2SBrian Feldman 	u_int status, id;
7761e8db6e2SBrian Feldman 
777ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
7784a421b63SDag-Erling Smørgrav 	send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
7791e8db6e2SBrian Feldman 	    strlen(path), a);
7801e8db6e2SBrian Feldman 
7814a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
7821e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
7831e8db6e2SBrian Feldman 		error("Couldn't setstat on \"%s\": %s", path,
7841e8db6e2SBrian Feldman 		    fx2txt(status));
7851e8db6e2SBrian Feldman 
786*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
7871e8db6e2SBrian Feldman }
7881e8db6e2SBrian Feldman 
7891e8db6e2SBrian Feldman int
790*bc5531deSDag-Erling Smørgrav do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
7911e8db6e2SBrian Feldman     Attrib *a)
7921e8db6e2SBrian Feldman {
7931e8db6e2SBrian Feldman 	u_int status, id;
7941e8db6e2SBrian Feldman 
795ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
7964a421b63SDag-Erling Smørgrav 	send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
7971e8db6e2SBrian Feldman 	    handle_len, a);
7981e8db6e2SBrian Feldman 
7994a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
8001e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
8011e8db6e2SBrian Feldman 		error("Couldn't fsetstat: %s", fx2txt(status));
8021e8db6e2SBrian Feldman 
803*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
8041e8db6e2SBrian Feldman }
8051e8db6e2SBrian Feldman 
8061e8db6e2SBrian Feldman char *
807*bc5531deSDag-Erling Smørgrav do_realpath(struct sftp_conn *conn, const char *path)
8081e8db6e2SBrian Feldman {
809*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
810*bc5531deSDag-Erling Smørgrav 	u_int expected_id, count, id;
8111e8db6e2SBrian Feldman 	char *filename, *longname;
812*bc5531deSDag-Erling Smørgrav 	Attrib a;
813*bc5531deSDag-Erling Smørgrav 	u_char type;
814*bc5531deSDag-Erling Smørgrav 	int r;
8151e8db6e2SBrian Feldman 
816ae1f160dSDag-Erling Smørgrav 	expected_id = id = conn->msg_id++;
8174a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_REALPATH, path,
818ae1f160dSDag-Erling Smørgrav 	    strlen(path));
8191e8db6e2SBrian Feldman 
820*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
821*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
8221e8db6e2SBrian Feldman 
823*bc5531deSDag-Erling Smørgrav 	get_msg(conn, msg);
824*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
825*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(msg, &id)) != 0)
826*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
8271e8db6e2SBrian Feldman 
8281e8db6e2SBrian Feldman 	if (id != expected_id)
829ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
8301e8db6e2SBrian Feldman 
8311e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
832*bc5531deSDag-Erling Smørgrav 		u_int status;
8331e8db6e2SBrian Feldman 
834*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
835*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
836f7167e0eSDag-Erling Smørgrav 		error("Couldn't canonicalize: %s", fx2txt(status));
837*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
838e2f6069cSDag-Erling Smørgrav 		return NULL;
8391e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_NAME)
840ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
8411e8db6e2SBrian Feldman 		    SSH2_FXP_NAME, type);
8421e8db6e2SBrian Feldman 
843*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u32(msg, &count)) != 0)
844*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
8451e8db6e2SBrian Feldman 	if (count != 1)
8461e8db6e2SBrian Feldman 		fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
8471e8db6e2SBrian Feldman 
848*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
849*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
850*bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(msg, &a)) != 0)
851*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
8521e8db6e2SBrian Feldman 
853462c32cbSDag-Erling Smørgrav 	debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename,
854*bc5531deSDag-Erling Smørgrav 	    (unsigned long)a.size);
8551e8db6e2SBrian Feldman 
856e4a9863fSDag-Erling Smørgrav 	free(longname);
8571e8db6e2SBrian Feldman 
858*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
8591e8db6e2SBrian Feldman 
8601e8db6e2SBrian Feldman 	return(filename);
8611e8db6e2SBrian Feldman }
8621e8db6e2SBrian Feldman 
8631e8db6e2SBrian Feldman int
864*bc5531deSDag-Erling Smørgrav do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
865f7167e0eSDag-Erling Smørgrav     int force_legacy)
8661e8db6e2SBrian Feldman {
867*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
8681e8db6e2SBrian Feldman 	u_int status, id;
869*bc5531deSDag-Erling Smørgrav 	int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy;
8701e8db6e2SBrian Feldman 
871*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
872*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
8731e8db6e2SBrian Feldman 
8741e8db6e2SBrian Feldman 	/* Send rename request */
875ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
876f7167e0eSDag-Erling Smørgrav 	if (use_ext) {
877*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
878*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_u32(msg, id)) != 0 ||
879*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_cstring(msg,
880*bc5531deSDag-Erling Smørgrav 		    "posix-rename@openssh.com")) != 0)
881*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
882d4af9e69SDag-Erling Smørgrav 	} else {
883*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 ||
884*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_put_u32(msg, id)) != 0)
885*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
886d4af9e69SDag-Erling Smørgrav 	}
887*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
888*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
889*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
890*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
891d4af9e69SDag-Erling Smørgrav 	debug3("Sent message %s \"%s\" -> \"%s\"",
892*bc5531deSDag-Erling Smørgrav 	    use_ext ? "posix-rename@openssh.com" :
893*bc5531deSDag-Erling Smørgrav 	    "SSH2_FXP_RENAME", oldpath, newpath);
894*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
8951e8db6e2SBrian Feldman 
8964a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
8971e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
898ae1f160dSDag-Erling Smørgrav 		error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath,
899ae1f160dSDag-Erling Smørgrav 		    newpath, fx2txt(status));
9001e8db6e2SBrian Feldman 
901*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
9021e8db6e2SBrian Feldman }
9031e8db6e2SBrian Feldman 
9041e8db6e2SBrian Feldman int
905*bc5531deSDag-Erling Smørgrav do_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
9064a421b63SDag-Erling Smørgrav {
907*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
9084a421b63SDag-Erling Smørgrav 	u_int status, id;
909*bc5531deSDag-Erling Smørgrav 	int r;
9104a421b63SDag-Erling Smørgrav 
9114a421b63SDag-Erling Smørgrav 	if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
9124a421b63SDag-Erling Smørgrav 		error("Server does not support hardlink@openssh.com extension");
9134a421b63SDag-Erling Smørgrav 		return -1;
9144a421b63SDag-Erling Smørgrav 	}
9154a421b63SDag-Erling Smørgrav 
916*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
917*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
918462c32cbSDag-Erling Smørgrav 
919462c32cbSDag-Erling Smørgrav 	/* Send link request */
920462c32cbSDag-Erling Smørgrav 	id = conn->msg_id++;
921*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
922*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
923*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
924*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
925*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
926*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
927*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
9284a421b63SDag-Erling Smørgrav 	debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
9294a421b63SDag-Erling Smørgrav 	       oldpath, newpath);
930*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
9314a421b63SDag-Erling Smørgrav 
9324a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
9334a421b63SDag-Erling Smørgrav 	if (status != SSH2_FX_OK)
9344a421b63SDag-Erling Smørgrav 		error("Couldn't link file \"%s\" to \"%s\": %s", oldpath,
9354a421b63SDag-Erling Smørgrav 		    newpath, fx2txt(status));
9364a421b63SDag-Erling Smørgrav 
937*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
9384a421b63SDag-Erling Smørgrav }
9394a421b63SDag-Erling Smørgrav 
9404a421b63SDag-Erling Smørgrav int
941*bc5531deSDag-Erling Smørgrav do_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
9421e8db6e2SBrian Feldman {
943*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
9441e8db6e2SBrian Feldman 	u_int status, id;
945*bc5531deSDag-Erling Smørgrav 	int r;
9461e8db6e2SBrian Feldman 
947ae1f160dSDag-Erling Smørgrav 	if (conn->version < 3) {
948ae1f160dSDag-Erling Smørgrav 		error("This server does not support the symlink operation");
949ae1f160dSDag-Erling Smørgrav 		return(SSH2_FX_OP_UNSUPPORTED);
950ae1f160dSDag-Erling Smørgrav 	}
951ae1f160dSDag-Erling Smørgrav 
952*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
953*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
9541e8db6e2SBrian Feldman 
955d74d50a8SDag-Erling Smørgrav 	/* Send symlink request */
956ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
957*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 ||
958*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
959*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
960*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
961*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
962*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
9631e8db6e2SBrian Feldman 	debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
9641e8db6e2SBrian Feldman 	    newpath);
965*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
9661e8db6e2SBrian Feldman 
9674a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
9681e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
969d0c8c0bcSDag-Erling Smørgrav 		error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath,
970ae1f160dSDag-Erling Smørgrav 		    newpath, fx2txt(status));
9711e8db6e2SBrian Feldman 
972*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
9731e8db6e2SBrian Feldman }
9741e8db6e2SBrian Feldman 
975f7167e0eSDag-Erling Smørgrav int
976*bc5531deSDag-Erling Smørgrav do_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
977f7167e0eSDag-Erling Smørgrav {
978*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
979f7167e0eSDag-Erling Smørgrav 	u_int status, id;
980*bc5531deSDag-Erling Smørgrav 	int r;
981f7167e0eSDag-Erling Smørgrav 
982f7167e0eSDag-Erling Smørgrav 	/* Silently return if the extension is not supported */
983f7167e0eSDag-Erling Smørgrav 	if ((conn->exts & SFTP_EXT_FSYNC) == 0)
984f7167e0eSDag-Erling Smørgrav 		return -1;
985f7167e0eSDag-Erling Smørgrav 
986f7167e0eSDag-Erling Smørgrav 	/* Send fsync request */
987*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
988*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
989f7167e0eSDag-Erling Smørgrav 	id = conn->msg_id++;
990*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
991*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
992*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
993*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
994*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
995*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
996f7167e0eSDag-Erling Smørgrav 	debug3("Sent message fsync@openssh.com I:%u", id);
997*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
998f7167e0eSDag-Erling Smørgrav 
999f7167e0eSDag-Erling Smørgrav 	status = get_status(conn, id);
1000f7167e0eSDag-Erling Smørgrav 	if (status != SSH2_FX_OK)
1001f7167e0eSDag-Erling Smørgrav 		error("Couldn't sync file: %s", fx2txt(status));
1002f7167e0eSDag-Erling Smørgrav 
1003f7167e0eSDag-Erling Smørgrav 	return status;
1004f7167e0eSDag-Erling Smørgrav }
1005f7167e0eSDag-Erling Smørgrav 
1006d4af9e69SDag-Erling Smørgrav #ifdef notyet
10071e8db6e2SBrian Feldman char *
1008*bc5531deSDag-Erling Smørgrav do_readlink(struct sftp_conn *conn, const char *path)
10091e8db6e2SBrian Feldman {
1010*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
1011*bc5531deSDag-Erling Smørgrav 	u_int expected_id, count, id;
10121e8db6e2SBrian Feldman 	char *filename, *longname;
1013*bc5531deSDag-Erling Smørgrav 	Attrib a;
1014*bc5531deSDag-Erling Smørgrav 	u_char type;
1015*bc5531deSDag-Erling Smørgrav 	int r;
10161e8db6e2SBrian Feldman 
1017ae1f160dSDag-Erling Smørgrav 	expected_id = id = conn->msg_id++;
10184a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
10191e8db6e2SBrian Feldman 
1020*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
1021*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
10221e8db6e2SBrian Feldman 
1023*bc5531deSDag-Erling Smørgrav 	get_msg(conn, msg);
1024*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1025*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_u32(msg, &id)) != 0)
1026*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
10271e8db6e2SBrian Feldman 
10281e8db6e2SBrian Feldman 	if (id != expected_id)
1029ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
10301e8db6e2SBrian Feldman 
10311e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
1032*bc5531deSDag-Erling Smørgrav 		u_int status;
10331e8db6e2SBrian Feldman 
1034*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
1035*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
10361e8db6e2SBrian Feldman 		error("Couldn't readlink: %s", fx2txt(status));
1037*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
10381e8db6e2SBrian Feldman 		return(NULL);
10391e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_NAME)
1040ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
10411e8db6e2SBrian Feldman 		    SSH2_FXP_NAME, type);
10421e8db6e2SBrian Feldman 
1043*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_u32(msg, &count)) != 0)
1044*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
10451e8db6e2SBrian Feldman 	if (count != 1)
10461e8db6e2SBrian Feldman 		fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
10471e8db6e2SBrian Feldman 
1048*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1049*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1050*bc5531deSDag-Erling Smørgrav 	    (r = decode_attrib(msg, &a)) != 0)
1051*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
10521e8db6e2SBrian Feldman 
10531e8db6e2SBrian Feldman 	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
10541e8db6e2SBrian Feldman 
1055e4a9863fSDag-Erling Smørgrav 	free(longname);
10561e8db6e2SBrian Feldman 
1057*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
10581e8db6e2SBrian Feldman 
1059*bc5531deSDag-Erling Smørgrav 	return filename;
10601e8db6e2SBrian Feldman }
1061d4af9e69SDag-Erling Smørgrav #endif
1062d4af9e69SDag-Erling Smørgrav 
1063d4af9e69SDag-Erling Smørgrav int
1064d4af9e69SDag-Erling Smørgrav do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
1065d4af9e69SDag-Erling Smørgrav     int quiet)
1066d4af9e69SDag-Erling Smørgrav {
1067*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
1068d4af9e69SDag-Erling Smørgrav 	u_int id;
1069*bc5531deSDag-Erling Smørgrav 	int r;
1070d4af9e69SDag-Erling Smørgrav 
1071d4af9e69SDag-Erling Smørgrav 	if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
1072d4af9e69SDag-Erling Smørgrav 		error("Server does not support statvfs@openssh.com extension");
1073d4af9e69SDag-Erling Smørgrav 		return -1;
1074d4af9e69SDag-Erling Smørgrav 	}
1075d4af9e69SDag-Erling Smørgrav 
1076d4af9e69SDag-Erling Smørgrav 	id = conn->msg_id++;
1077d4af9e69SDag-Erling Smørgrav 
1078*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
1079*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
1080*bc5531deSDag-Erling Smørgrav 	sshbuf_reset(msg);
1081*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1082*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1083*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
1084*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, path)) != 0)
1085*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1086*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
1087*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
1088d4af9e69SDag-Erling Smørgrav 
10894a421b63SDag-Erling Smørgrav 	return get_decode_statvfs(conn, st, id, quiet);
1090d4af9e69SDag-Erling Smørgrav }
1091d4af9e69SDag-Erling Smørgrav 
1092d4af9e69SDag-Erling Smørgrav #ifdef notyet
1093d4af9e69SDag-Erling Smørgrav int
1094*bc5531deSDag-Erling Smørgrav do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
1095d4af9e69SDag-Erling Smørgrav     struct sftp_statvfs *st, int quiet)
1096d4af9e69SDag-Erling Smørgrav {
1097*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
1098d4af9e69SDag-Erling Smørgrav 	u_int id;
1099d4af9e69SDag-Erling Smørgrav 
1100d4af9e69SDag-Erling Smørgrav 	if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
1101d4af9e69SDag-Erling Smørgrav 		error("Server does not support fstatvfs@openssh.com extension");
1102d4af9e69SDag-Erling Smørgrav 		return -1;
1103d4af9e69SDag-Erling Smørgrav 	}
1104d4af9e69SDag-Erling Smørgrav 
1105d4af9e69SDag-Erling Smørgrav 	id = conn->msg_id++;
1106d4af9e69SDag-Erling Smørgrav 
1107*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
1108*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
1109*bc5531deSDag-Erling Smørgrav 	sshbuf_reset(msg);
1110*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1111*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1112*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
1113*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
1114*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1115*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
1116*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
1117d4af9e69SDag-Erling Smørgrav 
11184a421b63SDag-Erling Smørgrav 	return get_decode_statvfs(conn, st, id, quiet);
1119d4af9e69SDag-Erling Smørgrav }
1120d4af9e69SDag-Erling Smørgrav #endif
11211e8db6e2SBrian Feldman 
1122ae1f160dSDag-Erling Smørgrav static void
11234a421b63SDag-Erling Smørgrav send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
1124*bc5531deSDag-Erling Smørgrav     u_int len, const u_char *handle, u_int handle_len)
1125ae1f160dSDag-Erling Smørgrav {
1126*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
1127*bc5531deSDag-Erling Smørgrav 	int r;
1128ae1f160dSDag-Erling Smørgrav 
1129*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
1130*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
1131*bc5531deSDag-Erling Smørgrav 	sshbuf_reset(msg);
1132*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 ||
1133*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1134*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0 ||
1135*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u64(msg, offset)) != 0 ||
1136*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, len)) != 0)
1137*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1138*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
1139*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
1140ae1f160dSDag-Erling Smørgrav }
1141ae1f160dSDag-Erling Smørgrav 
11421e8db6e2SBrian Feldman int
1143*bc5531deSDag-Erling Smørgrav do_download(struct sftp_conn *conn, const char *remote_path,
1144*bc5531deSDag-Erling Smørgrav     const char *local_path, Attrib *a, int preserve_flag, int resume_flag,
1145*bc5531deSDag-Erling Smørgrav     int fsync_flag)
11461e8db6e2SBrian Feldman {
1147b15c8340SDag-Erling Smørgrav 	Attrib junk;
1148*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
1149*bc5531deSDag-Erling Smørgrav 	u_char *handle;
1150*bc5531deSDag-Erling Smørgrav 	int local_fd = -1, write_error;
1151*bc5531deSDag-Erling Smørgrav 	int read_error, write_errno, reordered = 0, r;
1152e4a9863fSDag-Erling Smørgrav 	u_int64_t offset = 0, size, highwater;
1153*bc5531deSDag-Erling Smørgrav 	u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK;
1154d0c8c0bcSDag-Erling Smørgrav 	off_t progress_counter;
1155*bc5531deSDag-Erling Smørgrav 	size_t handle_len;
1156e4a9863fSDag-Erling Smørgrav 	struct stat st;
1157ae1f160dSDag-Erling Smørgrav 	struct request {
1158ae1f160dSDag-Erling Smørgrav 		u_int id;
1159*bc5531deSDag-Erling Smørgrav 		size_t len;
1160ae1f160dSDag-Erling Smørgrav 		u_int64_t offset;
1161ae1f160dSDag-Erling Smørgrav 		TAILQ_ENTRY(request) tq;
1162ae1f160dSDag-Erling Smørgrav 	};
1163ae1f160dSDag-Erling Smørgrav 	TAILQ_HEAD(reqhead, request) requests;
1164ae1f160dSDag-Erling Smørgrav 	struct request *req;
1165*bc5531deSDag-Erling Smørgrav 	u_char type;
11661e8db6e2SBrian Feldman 
1167ae1f160dSDag-Erling Smørgrav 	TAILQ_INIT(&requests);
1168ae1f160dSDag-Erling Smørgrav 
1169b15c8340SDag-Erling Smørgrav 	if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
1170b15c8340SDag-Erling Smørgrav 		return -1;
11711e8db6e2SBrian Feldman 
1172d4af9e69SDag-Erling Smørgrav 	/* Do not preserve set[ug]id here, as we do not preserve ownership */
11731e8db6e2SBrian Feldman 	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1174d0c8c0bcSDag-Erling Smørgrav 		mode = a->perm & 0777;
11751e8db6e2SBrian Feldman 	else
11761e8db6e2SBrian Feldman 		mode = 0666;
11771e8db6e2SBrian Feldman 
11781e8db6e2SBrian Feldman 	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
1179d0c8c0bcSDag-Erling Smørgrav 	    (!S_ISREG(a->perm))) {
1180d0c8c0bcSDag-Erling Smørgrav 		error("Cannot download non-regular file: %s", remote_path);
11811e8db6e2SBrian Feldman 		return(-1);
11821e8db6e2SBrian Feldman 	}
11831e8db6e2SBrian Feldman 
1184ae1f160dSDag-Erling Smørgrav 	if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
1185ae1f160dSDag-Erling Smørgrav 		size = a->size;
1186ae1f160dSDag-Erling Smørgrav 	else
1187ae1f160dSDag-Erling Smørgrav 		size = 0;
11881e8db6e2SBrian Feldman 
1189ae1f160dSDag-Erling Smørgrav 	buflen = conn->transfer_buflen;
1190*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
1191*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
1192*bc5531deSDag-Erling Smørgrav 
1193*bc5531deSDag-Erling Smørgrav 	attrib_clear(&junk); /* Send empty attributes */
11941e8db6e2SBrian Feldman 
11951e8db6e2SBrian Feldman 	/* Send open request */
1196ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
1197*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1198*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1199*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, remote_path)) != 0 ||
1200*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 ||
1201*bc5531deSDag-Erling Smørgrav 	    (r = encode_attrib(msg, &junk)) != 0)
1202*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1203*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
1204ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
12051e8db6e2SBrian Feldman 
12064a421b63SDag-Erling Smørgrav 	handle = get_handle(conn, id, &handle_len,
1207b15c8340SDag-Erling Smørgrav 	    "remote open(\"%s\")", remote_path);
12081e8db6e2SBrian Feldman 	if (handle == NULL) {
1209*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
1210ae1f160dSDag-Erling Smørgrav 		return(-1);
1211ae1f160dSDag-Erling Smørgrav 	}
1212ae1f160dSDag-Erling Smørgrav 
1213f7167e0eSDag-Erling Smørgrav 	local_fd = open(local_path,
1214f7167e0eSDag-Erling Smørgrav 	    O_WRONLY | O_CREAT | (resume_flag ? 0 : O_TRUNC), mode | S_IWUSR);
1215ae1f160dSDag-Erling Smørgrav 	if (local_fd == -1) {
1216ae1f160dSDag-Erling Smørgrav 		error("Couldn't open local file \"%s\" for writing: %s",
1217ae1f160dSDag-Erling Smørgrav 		    local_path, strerror(errno));
1218e4a9863fSDag-Erling Smørgrav 		goto fail;
1219e4a9863fSDag-Erling Smørgrav 	}
1220e4a9863fSDag-Erling Smørgrav 	offset = highwater = 0;
1221f7167e0eSDag-Erling Smørgrav 	if (resume_flag) {
1222e4a9863fSDag-Erling Smørgrav 		if (fstat(local_fd, &st) == -1) {
1223e4a9863fSDag-Erling Smørgrav 			error("Unable to stat local file \"%s\": %s",
1224e4a9863fSDag-Erling Smørgrav 			    local_path, strerror(errno));
1225e4a9863fSDag-Erling Smørgrav 			goto fail;
1226e4a9863fSDag-Erling Smørgrav 		}
1227f7167e0eSDag-Erling Smørgrav 		if (st.st_size < 0) {
1228f7167e0eSDag-Erling Smørgrav 			error("\"%s\" has negative size", local_path);
1229f7167e0eSDag-Erling Smørgrav 			goto fail;
1230f7167e0eSDag-Erling Smørgrav 		}
1231f7167e0eSDag-Erling Smørgrav 		if ((u_int64_t)st.st_size > size) {
1232e4a9863fSDag-Erling Smørgrav 			error("Unable to resume download of \"%s\": "
1233e4a9863fSDag-Erling Smørgrav 			    "local file is larger than remote", local_path);
1234e4a9863fSDag-Erling Smørgrav  fail:
1235d4af9e69SDag-Erling Smørgrav 			do_close(conn, handle, handle_len);
1236*bc5531deSDag-Erling Smørgrav 			sshbuf_free(msg);
1237e4a9863fSDag-Erling Smørgrav 			free(handle);
1238f7167e0eSDag-Erling Smørgrav 			if (local_fd != -1)
1239f7167e0eSDag-Erling Smørgrav 				close(local_fd);
1240e4a9863fSDag-Erling Smørgrav 			return -1;
1241e4a9863fSDag-Erling Smørgrav 		}
1242e4a9863fSDag-Erling Smørgrav 		offset = highwater = st.st_size;
12431e8db6e2SBrian Feldman 	}
12441e8db6e2SBrian Feldman 
12451e8db6e2SBrian Feldman 	/* Read from remote and write to local */
1246e4a9863fSDag-Erling Smørgrav 	write_error = read_error = write_errno = num_req = 0;
1247ae1f160dSDag-Erling Smørgrav 	max_req = 1;
1248e4a9863fSDag-Erling Smørgrav 	progress_counter = offset;
1249d0c8c0bcSDag-Erling Smørgrav 
125052028650SDag-Erling Smørgrav 	if (showprogress && size != 0)
125152028650SDag-Erling Smørgrav 		start_progress_meter(remote_path, size, &progress_counter);
1252d0c8c0bcSDag-Erling Smørgrav 
1253ae1f160dSDag-Erling Smørgrav 	while (num_req > 0 || max_req > 0) {
1254*bc5531deSDag-Erling Smørgrav 		u_char *data;
1255*bc5531deSDag-Erling Smørgrav 		size_t len;
12561e8db6e2SBrian Feldman 
1257d74d50a8SDag-Erling Smørgrav 		/*
1258d74d50a8SDag-Erling Smørgrav 		 * Simulate EOF on interrupt: stop sending new requests and
1259d74d50a8SDag-Erling Smørgrav 		 * allow outstanding requests to drain gracefully
1260d74d50a8SDag-Erling Smørgrav 		 */
1261d74d50a8SDag-Erling Smørgrav 		if (interrupted) {
1262d74d50a8SDag-Erling Smørgrav 			if (num_req == 0) /* If we haven't started yet... */
1263d74d50a8SDag-Erling Smørgrav 				break;
1264d74d50a8SDag-Erling Smørgrav 			max_req = 0;
1265d74d50a8SDag-Erling Smørgrav 		}
1266d74d50a8SDag-Erling Smørgrav 
1267ae1f160dSDag-Erling Smørgrav 		/* Send some more requests */
1268ae1f160dSDag-Erling Smørgrav 		while (num_req < max_req) {
1269ae1f160dSDag-Erling Smørgrav 			debug3("Request range %llu -> %llu (%d/%d)",
1270545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)offset,
1271545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)offset + buflen - 1,
1272545d5ecaSDag-Erling Smørgrav 			    num_req, max_req);
12730a37d4a3SXin LI 			req = xcalloc(1, sizeof(*req));
1274ae1f160dSDag-Erling Smørgrav 			req->id = conn->msg_id++;
1275ae1f160dSDag-Erling Smørgrav 			req->len = buflen;
1276ae1f160dSDag-Erling Smørgrav 			req->offset = offset;
1277ae1f160dSDag-Erling Smørgrav 			offset += buflen;
1278ae1f160dSDag-Erling Smørgrav 			num_req++;
1279ae1f160dSDag-Erling Smørgrav 			TAILQ_INSERT_TAIL(&requests, req, tq);
12804a421b63SDag-Erling Smørgrav 			send_read_request(conn, req->id, req->offset,
1281ae1f160dSDag-Erling Smørgrav 			    req->len, handle, handle_len);
1282ae1f160dSDag-Erling Smørgrav 		}
12831e8db6e2SBrian Feldman 
1284*bc5531deSDag-Erling Smørgrav 		sshbuf_reset(msg);
1285*bc5531deSDag-Erling Smørgrav 		get_msg(conn, msg);
1286*bc5531deSDag-Erling Smørgrav 		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1287*bc5531deSDag-Erling Smørgrav 		    (r = sshbuf_get_u32(msg, &id)) != 0)
1288*bc5531deSDag-Erling Smørgrav 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
1289ee21a45fSDag-Erling Smørgrav 		debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
12901e8db6e2SBrian Feldman 
1291ae1f160dSDag-Erling Smørgrav 		/* Find the request in our queue */
1292ae1f160dSDag-Erling Smørgrav 		for (req = TAILQ_FIRST(&requests);
1293ae1f160dSDag-Erling Smørgrav 		    req != NULL && req->id != id;
1294ae1f160dSDag-Erling Smørgrav 		    req = TAILQ_NEXT(req, tq))
1295ae1f160dSDag-Erling Smørgrav 			;
1296ae1f160dSDag-Erling Smørgrav 		if (req == NULL)
1297ae1f160dSDag-Erling Smørgrav 			fatal("Unexpected reply %u", id);
1298ae1f160dSDag-Erling Smørgrav 
1299ae1f160dSDag-Erling Smørgrav 		switch (type) {
1300ae1f160dSDag-Erling Smørgrav 		case SSH2_FXP_STATUS:
1301*bc5531deSDag-Erling Smørgrav 			if ((r = sshbuf_get_u32(msg, &status)) != 0)
1302*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1303*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1304ae1f160dSDag-Erling Smørgrav 			if (status != SSH2_FX_EOF)
1305ae1f160dSDag-Erling Smørgrav 				read_error = 1;
1306ae1f160dSDag-Erling Smørgrav 			max_req = 0;
1307ae1f160dSDag-Erling Smørgrav 			TAILQ_REMOVE(&requests, req, tq);
1308e4a9863fSDag-Erling Smørgrav 			free(req);
1309ae1f160dSDag-Erling Smørgrav 			num_req--;
13101e8db6e2SBrian Feldman 			break;
1311ae1f160dSDag-Erling Smørgrav 		case SSH2_FXP_DATA:
1312*bc5531deSDag-Erling Smørgrav 			if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
1313*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1314*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1315545d5ecaSDag-Erling Smørgrav 			debug3("Received data %llu -> %llu",
1316545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)req->offset,
1317545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)req->offset + len - 1);
1318ae1f160dSDag-Erling Smørgrav 			if (len > req->len)
1319ae1f160dSDag-Erling Smørgrav 				fatal("Received more data than asked for "
1320*bc5531deSDag-Erling Smørgrav 				    "%zu > %zu", len, req->len);
1321ae1f160dSDag-Erling Smørgrav 			if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
1322d95e11bfSDag-Erling Smørgrav 			    atomicio(vwrite, local_fd, data, len) != len) &&
1323ae1f160dSDag-Erling Smørgrav 			    !write_error) {
1324ae1f160dSDag-Erling Smørgrav 				write_errno = errno;
1325ae1f160dSDag-Erling Smørgrav 				write_error = 1;
1326ae1f160dSDag-Erling Smørgrav 				max_req = 0;
13271e8db6e2SBrian Feldman 			}
1328e4a9863fSDag-Erling Smørgrav 			else if (!reordered && req->offset <= highwater)
1329e4a9863fSDag-Erling Smørgrav 				highwater = req->offset + len;
1330e4a9863fSDag-Erling Smørgrav 			else if (!reordered && req->offset > highwater)
1331e4a9863fSDag-Erling Smørgrav 				reordered = 1;
1332d0c8c0bcSDag-Erling Smørgrav 			progress_counter += len;
1333e4a9863fSDag-Erling Smørgrav 			free(data);
1334ae1f160dSDag-Erling Smørgrav 
1335ae1f160dSDag-Erling Smørgrav 			if (len == req->len) {
1336ae1f160dSDag-Erling Smørgrav 				TAILQ_REMOVE(&requests, req, tq);
1337e4a9863fSDag-Erling Smørgrav 				free(req);
1338ae1f160dSDag-Erling Smørgrav 				num_req--;
1339ae1f160dSDag-Erling Smørgrav 			} else {
1340ae1f160dSDag-Erling Smørgrav 				/* Resend the request for the missing data */
1341ae1f160dSDag-Erling Smørgrav 				debug3("Short data block, re-requesting "
1342545d5ecaSDag-Erling Smørgrav 				    "%llu -> %llu (%2d)",
1343545d5ecaSDag-Erling Smørgrav 				    (unsigned long long)req->offset + len,
1344545d5ecaSDag-Erling Smørgrav 				    (unsigned long long)req->offset +
1345545d5ecaSDag-Erling Smørgrav 				    req->len - 1, num_req);
1346ae1f160dSDag-Erling Smørgrav 				req->id = conn->msg_id++;
1347ae1f160dSDag-Erling Smørgrav 				req->len -= len;
1348ae1f160dSDag-Erling Smørgrav 				req->offset += len;
13494a421b63SDag-Erling Smørgrav 				send_read_request(conn, req->id,
1350ae1f160dSDag-Erling Smørgrav 				    req->offset, req->len, handle, handle_len);
1351ae1f160dSDag-Erling Smørgrav 				/* Reduce the request size */
1352ae1f160dSDag-Erling Smørgrav 				if (len < buflen)
1353ae1f160dSDag-Erling Smørgrav 					buflen = MAX(MIN_READ_SIZE, len);
1354ae1f160dSDag-Erling Smørgrav 			}
1355ae1f160dSDag-Erling Smørgrav 			if (max_req > 0) { /* max_req = 0 iff EOF received */
1356ae1f160dSDag-Erling Smørgrav 				if (size > 0 && offset > size) {
1357ae1f160dSDag-Erling Smørgrav 					/* Only one request at a time
1358ae1f160dSDag-Erling Smørgrav 					 * after the expected EOF */
1359ae1f160dSDag-Erling Smørgrav 					debug3("Finish at %llu (%2d)",
1360545d5ecaSDag-Erling Smørgrav 					    (unsigned long long)offset,
1361545d5ecaSDag-Erling Smørgrav 					    num_req);
1362ae1f160dSDag-Erling Smørgrav 					max_req = 1;
1363d74d50a8SDag-Erling Smørgrav 				} else if (max_req <= conn->num_requests) {
1364ae1f160dSDag-Erling Smørgrav 					++max_req;
1365ae1f160dSDag-Erling Smørgrav 				}
1366ae1f160dSDag-Erling Smørgrav 			}
1367ae1f160dSDag-Erling Smørgrav 			break;
1368ae1f160dSDag-Erling Smørgrav 		default:
1369ee21a45fSDag-Erling Smørgrav 			fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
13701e8db6e2SBrian Feldman 			    SSH2_FXP_DATA, type);
13711e8db6e2SBrian Feldman 		}
1372ae1f160dSDag-Erling Smørgrav 	}
13731e8db6e2SBrian Feldman 
1374d0c8c0bcSDag-Erling Smørgrav 	if (showprogress && size)
1375d0c8c0bcSDag-Erling Smørgrav 		stop_progress_meter();
1376d0c8c0bcSDag-Erling Smørgrav 
1377ae1f160dSDag-Erling Smørgrav 	/* Sanity check */
1378ae1f160dSDag-Erling Smørgrav 	if (TAILQ_FIRST(&requests) != NULL)
1379ae1f160dSDag-Erling Smørgrav 		fatal("Transfer complete, but requests still in queue");
1380e4a9863fSDag-Erling Smørgrav 	/* Truncate at highest contiguous point to avoid holes on interrupt */
1381e4a9863fSDag-Erling Smørgrav 	if (read_error || write_error || interrupted) {
1382f7167e0eSDag-Erling Smørgrav 		if (reordered && resume_flag) {
1383e4a9863fSDag-Erling Smørgrav 			error("Unable to resume download of \"%s\": "
1384e4a9863fSDag-Erling Smørgrav 			    "server reordered requests", local_path);
1385e4a9863fSDag-Erling Smørgrav 		}
1386e4a9863fSDag-Erling Smørgrav 		debug("truncating at %llu", (unsigned long long)highwater);
1387e4a9863fSDag-Erling Smørgrav 		ftruncate(local_fd, highwater);
1388e4a9863fSDag-Erling Smørgrav 	}
1389ae1f160dSDag-Erling Smørgrav 	if (read_error) {
1390ae1f160dSDag-Erling Smørgrav 		error("Couldn't read from remote file \"%s\" : %s",
1391ae1f160dSDag-Erling Smørgrav 		    remote_path, fx2txt(status));
1392f7167e0eSDag-Erling Smørgrav 		status = -1;
1393ae1f160dSDag-Erling Smørgrav 		do_close(conn, handle, handle_len);
1394ae1f160dSDag-Erling Smørgrav 	} else if (write_error) {
13951e8db6e2SBrian Feldman 		error("Couldn't write to \"%s\": %s", local_path,
1396ae1f160dSDag-Erling Smørgrav 		    strerror(write_errno));
1397*bc5531deSDag-Erling Smørgrav 		status = SSH2_FX_FAILURE;
1398ae1f160dSDag-Erling Smørgrav 		do_close(conn, handle, handle_len);
1399ae1f160dSDag-Erling Smørgrav 	} else {
1400*bc5531deSDag-Erling Smørgrav 		if (do_close(conn, handle, handle_len) != 0 || interrupted)
1401*bc5531deSDag-Erling Smørgrav 			status = SSH2_FX_FAILURE;
1402*bc5531deSDag-Erling Smørgrav 		else
1403*bc5531deSDag-Erling Smørgrav 			status = SSH2_FX_OK;
14041e8db6e2SBrian Feldman 		/* Override umask and utimes if asked */
140583d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD
1406f7167e0eSDag-Erling Smørgrav 		if (preserve_flag && fchmod(local_fd, mode) == -1)
140783d2307dSDag-Erling Smørgrav #else
1408f7167e0eSDag-Erling Smørgrav 		if (preserve_flag && chmod(local_path, mode) == -1)
140983d2307dSDag-Erling Smørgrav #endif /* HAVE_FCHMOD */
14101e8db6e2SBrian Feldman 			error("Couldn't set mode on \"%s\": %s", local_path,
14111e8db6e2SBrian Feldman 			    strerror(errno));
1412f7167e0eSDag-Erling Smørgrav 		if (preserve_flag &&
1413f7167e0eSDag-Erling Smørgrav 		    (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
14141e8db6e2SBrian Feldman 			struct timeval tv[2];
14151e8db6e2SBrian Feldman 			tv[0].tv_sec = a->atime;
14161e8db6e2SBrian Feldman 			tv[1].tv_sec = a->mtime;
14171e8db6e2SBrian Feldman 			tv[0].tv_usec = tv[1].tv_usec = 0;
14181e8db6e2SBrian Feldman 			if (utimes(local_path, tv) == -1)
1419ae1f160dSDag-Erling Smørgrav 				error("Can't set times on \"%s\": %s",
1420ae1f160dSDag-Erling Smørgrav 				    local_path, strerror(errno));
14211e8db6e2SBrian Feldman 		}
1422f7167e0eSDag-Erling Smørgrav 		if (fsync_flag) {
1423f7167e0eSDag-Erling Smørgrav 			debug("syncing \"%s\"", local_path);
1424f7167e0eSDag-Erling Smørgrav 			if (fsync(local_fd) == -1)
1425f7167e0eSDag-Erling Smørgrav 				error("Couldn't sync file \"%s\": %s",
1426f7167e0eSDag-Erling Smørgrav 				    local_path, strerror(errno));
1427f7167e0eSDag-Erling Smørgrav 		}
1428ae1f160dSDag-Erling Smørgrav 	}
14291e8db6e2SBrian Feldman 	close(local_fd);
1430*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
1431e4a9863fSDag-Erling Smørgrav 	free(handle);
1432ae1f160dSDag-Erling Smørgrav 
1433ae1f160dSDag-Erling Smørgrav 	return(status);
14341e8db6e2SBrian Feldman }
14351e8db6e2SBrian Feldman 
1436b15c8340SDag-Erling Smørgrav static int
1437*bc5531deSDag-Erling Smørgrav download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1438*bc5531deSDag-Erling Smørgrav     int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
1439*bc5531deSDag-Erling Smørgrav     int resume_flag, int fsync_flag)
1440b15c8340SDag-Erling Smørgrav {
1441b15c8340SDag-Erling Smørgrav 	int i, ret = 0;
1442b15c8340SDag-Erling Smørgrav 	SFTP_DIRENT **dir_entries;
1443b15c8340SDag-Erling Smørgrav 	char *filename, *new_src, *new_dst;
1444b15c8340SDag-Erling Smørgrav 	mode_t mode = 0777;
1445b15c8340SDag-Erling Smørgrav 
1446b15c8340SDag-Erling Smørgrav 	if (depth >= MAX_DIR_DEPTH) {
1447b15c8340SDag-Erling Smørgrav 		error("Maximum directory depth exceeded: %d levels", depth);
1448b15c8340SDag-Erling Smørgrav 		return -1;
1449b15c8340SDag-Erling Smørgrav 	}
1450b15c8340SDag-Erling Smørgrav 
1451b15c8340SDag-Erling Smørgrav 	if (dirattrib == NULL &&
1452b15c8340SDag-Erling Smørgrav 	    (dirattrib = do_stat(conn, src, 1)) == NULL) {
1453b15c8340SDag-Erling Smørgrav 		error("Unable to stat remote directory \"%s\"", src);
1454b15c8340SDag-Erling Smørgrav 		return -1;
1455b15c8340SDag-Erling Smørgrav 	}
1456b15c8340SDag-Erling Smørgrav 	if (!S_ISDIR(dirattrib->perm)) {
1457b15c8340SDag-Erling Smørgrav 		error("\"%s\" is not a directory", src);
1458b15c8340SDag-Erling Smørgrav 		return -1;
1459b15c8340SDag-Erling Smørgrav 	}
1460f7167e0eSDag-Erling Smørgrav 	if (print_flag)
1461b15c8340SDag-Erling Smørgrav 		printf("Retrieving %s\n", src);
1462b15c8340SDag-Erling Smørgrav 
1463b15c8340SDag-Erling Smørgrav 	if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1464b15c8340SDag-Erling Smørgrav 		mode = dirattrib->perm & 01777;
1465b15c8340SDag-Erling Smørgrav 	else {
1466b15c8340SDag-Erling Smørgrav 		debug("Server did not send permissions for "
1467b15c8340SDag-Erling Smørgrav 		    "directory \"%s\"", dst);
1468b15c8340SDag-Erling Smørgrav 	}
1469b15c8340SDag-Erling Smørgrav 
1470b15c8340SDag-Erling Smørgrav 	if (mkdir(dst, mode) == -1 && errno != EEXIST) {
1471b15c8340SDag-Erling Smørgrav 		error("mkdir %s: %s", dst, strerror(errno));
1472b15c8340SDag-Erling Smørgrav 		return -1;
1473b15c8340SDag-Erling Smørgrav 	}
1474b15c8340SDag-Erling Smørgrav 
1475b15c8340SDag-Erling Smørgrav 	if (do_readdir(conn, src, &dir_entries) == -1) {
1476b15c8340SDag-Erling Smørgrav 		error("%s: Failed to get directory contents", src);
1477b15c8340SDag-Erling Smørgrav 		return -1;
1478b15c8340SDag-Erling Smørgrav 	}
1479b15c8340SDag-Erling Smørgrav 
1480b15c8340SDag-Erling Smørgrav 	for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
1481b15c8340SDag-Erling Smørgrav 		filename = dir_entries[i]->filename;
1482b15c8340SDag-Erling Smørgrav 
1483b15c8340SDag-Erling Smørgrav 		new_dst = path_append(dst, filename);
1484b15c8340SDag-Erling Smørgrav 		new_src = path_append(src, filename);
1485b15c8340SDag-Erling Smørgrav 
1486b15c8340SDag-Erling Smørgrav 		if (S_ISDIR(dir_entries[i]->a.perm)) {
1487b15c8340SDag-Erling Smørgrav 			if (strcmp(filename, ".") == 0 ||
1488b15c8340SDag-Erling Smørgrav 			    strcmp(filename, "..") == 0)
1489b15c8340SDag-Erling Smørgrav 				continue;
1490b15c8340SDag-Erling Smørgrav 			if (download_dir_internal(conn, new_src, new_dst,
1491f7167e0eSDag-Erling Smørgrav 			    depth + 1, &(dir_entries[i]->a), preserve_flag,
1492f7167e0eSDag-Erling Smørgrav 			    print_flag, resume_flag, fsync_flag) == -1)
1493b15c8340SDag-Erling Smørgrav 				ret = -1;
1494b15c8340SDag-Erling Smørgrav 		} else if (S_ISREG(dir_entries[i]->a.perm) ) {
1495b15c8340SDag-Erling Smørgrav 			if (do_download(conn, new_src, new_dst,
1496f7167e0eSDag-Erling Smørgrav 			    &(dir_entries[i]->a), preserve_flag,
1497f7167e0eSDag-Erling Smørgrav 			    resume_flag, fsync_flag) == -1) {
1498b15c8340SDag-Erling Smørgrav 				error("Download of file %s to %s failed",
1499b15c8340SDag-Erling Smørgrav 				    new_src, new_dst);
1500b15c8340SDag-Erling Smørgrav 				ret = -1;
1501b15c8340SDag-Erling Smørgrav 			}
1502b15c8340SDag-Erling Smørgrav 		} else
1503b15c8340SDag-Erling Smørgrav 			logit("%s: not a regular file\n", new_src);
1504b15c8340SDag-Erling Smørgrav 
1505e4a9863fSDag-Erling Smørgrav 		free(new_dst);
1506e4a9863fSDag-Erling Smørgrav 		free(new_src);
1507b15c8340SDag-Erling Smørgrav 	}
1508b15c8340SDag-Erling Smørgrav 
1509f7167e0eSDag-Erling Smørgrav 	if (preserve_flag) {
1510b15c8340SDag-Erling Smørgrav 		if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1511b15c8340SDag-Erling Smørgrav 			struct timeval tv[2];
1512b15c8340SDag-Erling Smørgrav 			tv[0].tv_sec = dirattrib->atime;
1513b15c8340SDag-Erling Smørgrav 			tv[1].tv_sec = dirattrib->mtime;
1514b15c8340SDag-Erling Smørgrav 			tv[0].tv_usec = tv[1].tv_usec = 0;
1515b15c8340SDag-Erling Smørgrav 			if (utimes(dst, tv) == -1)
1516b15c8340SDag-Erling Smørgrav 				error("Can't set times on \"%s\": %s",
1517b15c8340SDag-Erling Smørgrav 				    dst, strerror(errno));
1518b15c8340SDag-Erling Smørgrav 		} else
1519b15c8340SDag-Erling Smørgrav 			debug("Server did not send times for directory "
1520b15c8340SDag-Erling Smørgrav 			    "\"%s\"", dst);
1521b15c8340SDag-Erling Smørgrav 	}
1522b15c8340SDag-Erling Smørgrav 
1523b15c8340SDag-Erling Smørgrav 	free_sftp_dirents(dir_entries);
1524b15c8340SDag-Erling Smørgrav 
1525b15c8340SDag-Erling Smørgrav 	return ret;
1526b15c8340SDag-Erling Smørgrav }
1527b15c8340SDag-Erling Smørgrav 
1528b15c8340SDag-Erling Smørgrav int
1529*bc5531deSDag-Erling Smørgrav download_dir(struct sftp_conn *conn, const char *src, const char *dst,
1530*bc5531deSDag-Erling Smørgrav     Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag,
1531*bc5531deSDag-Erling Smørgrav     int fsync_flag)
1532b15c8340SDag-Erling Smørgrav {
1533b15c8340SDag-Erling Smørgrav 	char *src_canon;
1534b15c8340SDag-Erling Smørgrav 	int ret;
1535b15c8340SDag-Erling Smørgrav 
1536b15c8340SDag-Erling Smørgrav 	if ((src_canon = do_realpath(conn, src)) == NULL) {
1537f7167e0eSDag-Erling Smørgrav 		error("Unable to canonicalize path \"%s\"", src);
1538b15c8340SDag-Erling Smørgrav 		return -1;
1539b15c8340SDag-Erling Smørgrav 	}
1540b15c8340SDag-Erling Smørgrav 
1541f7167e0eSDag-Erling Smørgrav 	ret = download_dir_internal(conn, src_canon, dst, 0,
1542f7167e0eSDag-Erling Smørgrav 	    dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag);
1543e4a9863fSDag-Erling Smørgrav 	free(src_canon);
1544b15c8340SDag-Erling Smørgrav 	return ret;
1545b15c8340SDag-Erling Smørgrav }
1546b15c8340SDag-Erling Smørgrav 
15471e8db6e2SBrian Feldman int
1548*bc5531deSDag-Erling Smørgrav do_upload(struct sftp_conn *conn, const char *local_path,
1549*bc5531deSDag-Erling Smørgrav     const char *remote_path, int preserve_flag, int resume, int fsync_flag)
15501e8db6e2SBrian Feldman {
1551*bc5531deSDag-Erling Smørgrav 	int r, local_fd;
1552*bc5531deSDag-Erling Smørgrav 	u_int status = SSH2_FX_OK;
1553*bc5531deSDag-Erling Smørgrav 	u_int id;
1554*bc5531deSDag-Erling Smørgrav 	u_char type;
1555e4a9863fSDag-Erling Smørgrav 	off_t offset, progress_counter;
1556*bc5531deSDag-Erling Smørgrav 	u_char *handle, *data;
1557*bc5531deSDag-Erling Smørgrav 	struct sshbuf *msg;
15581e8db6e2SBrian Feldman 	struct stat sb;
1559a0ee8cc6SDag-Erling Smørgrav 	Attrib a, *c = NULL;
1560ae1f160dSDag-Erling Smørgrav 	u_int32_t startid;
1561ae1f160dSDag-Erling Smørgrav 	u_int32_t ackid;
1562ae1f160dSDag-Erling Smørgrav 	struct outstanding_ack {
1563ae1f160dSDag-Erling Smørgrav 		u_int id;
1564ae1f160dSDag-Erling Smørgrav 		u_int len;
1565d4af9e69SDag-Erling Smørgrav 		off_t offset;
1566ae1f160dSDag-Erling Smørgrav 		TAILQ_ENTRY(outstanding_ack) tq;
1567ae1f160dSDag-Erling Smørgrav 	};
1568ae1f160dSDag-Erling Smørgrav 	TAILQ_HEAD(ackhead, outstanding_ack) acks;
1569d74d50a8SDag-Erling Smørgrav 	struct outstanding_ack *ack = NULL;
1570*bc5531deSDag-Erling Smørgrav 	size_t handle_len;
1571ae1f160dSDag-Erling Smørgrav 
1572ae1f160dSDag-Erling Smørgrav 	TAILQ_INIT(&acks);
15731e8db6e2SBrian Feldman 
15741e8db6e2SBrian Feldman 	if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
15751e8db6e2SBrian Feldman 		error("Couldn't open local file \"%s\" for reading: %s",
15761e8db6e2SBrian Feldman 		    local_path, strerror(errno));
15771e8db6e2SBrian Feldman 		return(-1);
15781e8db6e2SBrian Feldman 	}
15791e8db6e2SBrian Feldman 	if (fstat(local_fd, &sb) == -1) {
15801e8db6e2SBrian Feldman 		error("Couldn't fstat local file \"%s\": %s",
15811e8db6e2SBrian Feldman 		    local_path, strerror(errno));
15821e8db6e2SBrian Feldman 		close(local_fd);
15831e8db6e2SBrian Feldman 		return(-1);
15841e8db6e2SBrian Feldman 	}
1585d0c8c0bcSDag-Erling Smørgrav 	if (!S_ISREG(sb.st_mode)) {
1586d0c8c0bcSDag-Erling Smørgrav 		error("%s is not a regular file", local_path);
1587d0c8c0bcSDag-Erling Smørgrav 		close(local_fd);
1588d0c8c0bcSDag-Erling Smørgrav 		return(-1);
1589d0c8c0bcSDag-Erling Smørgrav 	}
15901e8db6e2SBrian Feldman 	stat_to_attrib(&sb, &a);
15911e8db6e2SBrian Feldman 
15921e8db6e2SBrian Feldman 	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
15931e8db6e2SBrian Feldman 	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
15941e8db6e2SBrian Feldman 	a.perm &= 0777;
1595f7167e0eSDag-Erling Smørgrav 	if (!preserve_flag)
15961e8db6e2SBrian Feldman 		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
15971e8db6e2SBrian Feldman 
1598a0ee8cc6SDag-Erling Smørgrav 	if (resume) {
1599a0ee8cc6SDag-Erling Smørgrav 		/* Get remote file size if it exists */
1600a0ee8cc6SDag-Erling Smørgrav 		if ((c = do_stat(conn, remote_path, 0)) == NULL) {
1601a0ee8cc6SDag-Erling Smørgrav 			close(local_fd);
1602a0ee8cc6SDag-Erling Smørgrav 			return -1;
1603a0ee8cc6SDag-Erling Smørgrav 		}
1604a0ee8cc6SDag-Erling Smørgrav 
1605a0ee8cc6SDag-Erling Smørgrav 		if ((off_t)c->size >= sb.st_size) {
1606a0ee8cc6SDag-Erling Smørgrav 			error("destination file bigger or same size as "
1607a0ee8cc6SDag-Erling Smørgrav 			      "source file");
1608a0ee8cc6SDag-Erling Smørgrav 			close(local_fd);
1609a0ee8cc6SDag-Erling Smørgrav 			return -1;
1610a0ee8cc6SDag-Erling Smørgrav 		}
1611a0ee8cc6SDag-Erling Smørgrav 
1612a0ee8cc6SDag-Erling Smørgrav 		if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) {
1613a0ee8cc6SDag-Erling Smørgrav 			close(local_fd);
1614a0ee8cc6SDag-Erling Smørgrav 			return -1;
1615a0ee8cc6SDag-Erling Smørgrav 		}
1616a0ee8cc6SDag-Erling Smørgrav 	}
1617a0ee8cc6SDag-Erling Smørgrav 
1618*bc5531deSDag-Erling Smørgrav 	if ((msg = sshbuf_new()) == NULL)
1619*bc5531deSDag-Erling Smørgrav 		fatal("%s: sshbuf_new failed", __func__);
16201e8db6e2SBrian Feldman 
16211e8db6e2SBrian Feldman 	/* Send open request */
1622ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
1623*bc5531deSDag-Erling Smørgrav 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1624*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1625*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_cstring(msg, remote_path)) != 0 ||
1626*bc5531deSDag-Erling Smørgrav 	    (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|
1627*bc5531deSDag-Erling Smørgrav 	    (resume ? SSH2_FXF_APPEND : SSH2_FXF_TRUNC))) != 0 ||
1628*bc5531deSDag-Erling Smørgrav 	    (r = encode_attrib(msg, &a)) != 0)
1629*bc5531deSDag-Erling Smørgrav 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1630*bc5531deSDag-Erling Smørgrav 	send_msg(conn, msg);
1631ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
16321e8db6e2SBrian Feldman 
1633*bc5531deSDag-Erling Smørgrav 	sshbuf_reset(msg);
16341e8db6e2SBrian Feldman 
16354a421b63SDag-Erling Smørgrav 	handle = get_handle(conn, id, &handle_len,
1636b15c8340SDag-Erling Smørgrav 	    "remote open(\"%s\")", remote_path);
16371e8db6e2SBrian Feldman 	if (handle == NULL) {
16381e8db6e2SBrian Feldman 		close(local_fd);
1639*bc5531deSDag-Erling Smørgrav 		sshbuf_free(msg);
1640d4af9e69SDag-Erling Smørgrav 		return -1;
16411e8db6e2SBrian Feldman 	}
16421e8db6e2SBrian Feldman 
1643ae1f160dSDag-Erling Smørgrav 	startid = ackid = id + 1;
1644ae1f160dSDag-Erling Smørgrav 	data = xmalloc(conn->transfer_buflen);
1645ae1f160dSDag-Erling Smørgrav 
16461e8db6e2SBrian Feldman 	/* Read from local and write to remote */
1647a0ee8cc6SDag-Erling Smørgrav 	offset = progress_counter = (resume ? c->size : 0);
1648d0c8c0bcSDag-Erling Smørgrav 	if (showprogress)
1649e4a9863fSDag-Erling Smørgrav 		start_progress_meter(local_path, sb.st_size,
1650e4a9863fSDag-Erling Smørgrav 		    &progress_counter);
1651d0c8c0bcSDag-Erling Smørgrav 
16521e8db6e2SBrian Feldman 	for (;;) {
16531e8db6e2SBrian Feldman 		int len;
16541e8db6e2SBrian Feldman 
16551e8db6e2SBrian Feldman 		/*
1656d74d50a8SDag-Erling Smørgrav 		 * Can't use atomicio here because it returns 0 on EOF,
1657d74d50a8SDag-Erling Smørgrav 		 * thus losing the last block of the file.
1658d74d50a8SDag-Erling Smørgrav 		 * Simulate an EOF on interrupt, allowing ACKs from the
1659d74d50a8SDag-Erling Smørgrav 		 * server to drain.
16601e8db6e2SBrian Feldman 		 */
1661d4af9e69SDag-Erling Smørgrav 		if (interrupted || status != SSH2_FX_OK)
1662d74d50a8SDag-Erling Smørgrav 			len = 0;
1663d74d50a8SDag-Erling Smørgrav 		else do
1664ae1f160dSDag-Erling Smørgrav 			len = read(local_fd, data, conn->transfer_buflen);
1665d4af9e69SDag-Erling Smørgrav 		while ((len == -1) &&
1666d4af9e69SDag-Erling Smørgrav 		    (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
16671e8db6e2SBrian Feldman 
16681e8db6e2SBrian Feldman 		if (len == -1)
16691e8db6e2SBrian Feldman 			fatal("Couldn't read from \"%s\": %s", local_path,
16701e8db6e2SBrian Feldman 			    strerror(errno));
1671ae1f160dSDag-Erling Smørgrav 
1672ae1f160dSDag-Erling Smørgrav 		if (len != 0) {
16730a37d4a3SXin LI 			ack = xcalloc(1, sizeof(*ack));
1674ae1f160dSDag-Erling Smørgrav 			ack->id = ++id;
1675ae1f160dSDag-Erling Smørgrav 			ack->offset = offset;
1676ae1f160dSDag-Erling Smørgrav 			ack->len = len;
1677ae1f160dSDag-Erling Smørgrav 			TAILQ_INSERT_TAIL(&acks, ack, tq);
16781e8db6e2SBrian Feldman 
1679*bc5531deSDag-Erling Smørgrav 			sshbuf_reset(msg);
1680*bc5531deSDag-Erling Smørgrav 			if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
1681*bc5531deSDag-Erling Smørgrav 			    (r = sshbuf_put_u32(msg, ack->id)) != 0 ||
1682*bc5531deSDag-Erling Smørgrav 			    (r = sshbuf_put_string(msg, handle,
1683*bc5531deSDag-Erling Smørgrav 			    handle_len)) != 0 ||
1684*bc5531deSDag-Erling Smørgrav 			    (r = sshbuf_put_u64(msg, offset)) != 0 ||
1685*bc5531deSDag-Erling Smørgrav 			    (r = sshbuf_put_string(msg, data, len)) != 0)
1686*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1687*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1688*bc5531deSDag-Erling Smørgrav 			send_msg(conn, msg);
1689ee21a45fSDag-Erling Smørgrav 			debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
1690545d5ecaSDag-Erling Smørgrav 			    id, (unsigned long long)offset, len);
1691ae1f160dSDag-Erling Smørgrav 		} else if (TAILQ_FIRST(&acks) == NULL)
1692ae1f160dSDag-Erling Smørgrav 			break;
16931e8db6e2SBrian Feldman 
1694ae1f160dSDag-Erling Smørgrav 		if (ack == NULL)
1695ae1f160dSDag-Erling Smørgrav 			fatal("Unexpected ACK %u", id);
1696ae1f160dSDag-Erling Smørgrav 
1697ae1f160dSDag-Erling Smørgrav 		if (id == startid || len == 0 ||
1698ae1f160dSDag-Erling Smørgrav 		    id - ackid >= conn->num_requests) {
1699*bc5531deSDag-Erling Smørgrav 			u_int rid;
1700545d5ecaSDag-Erling Smørgrav 
1701*bc5531deSDag-Erling Smørgrav 			sshbuf_reset(msg);
1702*bc5531deSDag-Erling Smørgrav 			get_msg(conn, msg);
1703*bc5531deSDag-Erling Smørgrav 			if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1704*bc5531deSDag-Erling Smørgrav 			    (r = sshbuf_get_u32(msg, &rid)) != 0)
1705*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1706*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1707ae1f160dSDag-Erling Smørgrav 
1708ae1f160dSDag-Erling Smørgrav 			if (type != SSH2_FXP_STATUS)
1709ae1f160dSDag-Erling Smørgrav 				fatal("Expected SSH2_FXP_STATUS(%d) packet, "
1710ae1f160dSDag-Erling Smørgrav 				    "got %d", SSH2_FXP_STATUS, type);
1711ae1f160dSDag-Erling Smørgrav 
1712*bc5531deSDag-Erling Smørgrav 			if ((r = sshbuf_get_u32(msg, &status)) != 0)
1713*bc5531deSDag-Erling Smørgrav 				fatal("%s: buffer error: %s",
1714*bc5531deSDag-Erling Smørgrav 				    __func__, ssh_err(r));
1715*bc5531deSDag-Erling Smørgrav 			debug3("SSH2_FXP_STATUS %u", status);
1716ae1f160dSDag-Erling Smørgrav 
1717ae1f160dSDag-Erling Smørgrav 			/* Find the request in our queue */
1718ae1f160dSDag-Erling Smørgrav 			for (ack = TAILQ_FIRST(&acks);
1719*bc5531deSDag-Erling Smørgrav 			    ack != NULL && ack->id != rid;
1720ae1f160dSDag-Erling Smørgrav 			    ack = TAILQ_NEXT(ack, tq))
1721ae1f160dSDag-Erling Smørgrav 				;
1722ae1f160dSDag-Erling Smørgrav 			if (ack == NULL)
1723*bc5531deSDag-Erling Smørgrav 				fatal("Can't find request for ID %u", rid);
1724ae1f160dSDag-Erling Smørgrav 			TAILQ_REMOVE(&acks, ack, tq);
1725d4af9e69SDag-Erling Smørgrav 			debug3("In write loop, ack for %u %u bytes at %lld",
1726d4af9e69SDag-Erling Smørgrav 			    ack->id, ack->len, (long long)ack->offset);
1727ae1f160dSDag-Erling Smørgrav 			++ackid;
1728e4a9863fSDag-Erling Smørgrav 			progress_counter += ack->len;
1729e4a9863fSDag-Erling Smørgrav 			free(ack);
1730ae1f160dSDag-Erling Smørgrav 		}
17311e8db6e2SBrian Feldman 		offset += len;
1732d4af9e69SDag-Erling Smørgrav 		if (offset < 0)
1733d4af9e69SDag-Erling Smørgrav 			fatal("%s: offset < 0", __func__);
17341e8db6e2SBrian Feldman 	}
1735*bc5531deSDag-Erling Smørgrav 	sshbuf_free(msg);
1736d4af9e69SDag-Erling Smørgrav 
1737d0c8c0bcSDag-Erling Smørgrav 	if (showprogress)
1738d0c8c0bcSDag-Erling Smørgrav 		stop_progress_meter();
1739e4a9863fSDag-Erling Smørgrav 	free(data);
17401e8db6e2SBrian Feldman 
1741d4af9e69SDag-Erling Smørgrav 	if (status != SSH2_FX_OK) {
1742d4af9e69SDag-Erling Smørgrav 		error("Couldn't write to remote file \"%s\": %s",
1743d4af9e69SDag-Erling Smørgrav 		    remote_path, fx2txt(status));
1744*bc5531deSDag-Erling Smørgrav 		status = SSH2_FX_FAILURE;
1745d4af9e69SDag-Erling Smørgrav 	}
1746d4af9e69SDag-Erling Smørgrav 
17471e8db6e2SBrian Feldman 	if (close(local_fd) == -1) {
17481e8db6e2SBrian Feldman 		error("Couldn't close local file \"%s\": %s", local_path,
17491e8db6e2SBrian Feldman 		    strerror(errno));
1750*bc5531deSDag-Erling Smørgrav 		status = SSH2_FX_FAILURE;
17511e8db6e2SBrian Feldman 	}
17521e8db6e2SBrian Feldman 
17531e8db6e2SBrian Feldman 	/* Override umask and utimes if asked */
1754f7167e0eSDag-Erling Smørgrav 	if (preserve_flag)
1755ae1f160dSDag-Erling Smørgrav 		do_fsetstat(conn, handle, handle_len, &a);
17561e8db6e2SBrian Feldman 
1757f7167e0eSDag-Erling Smørgrav 	if (fsync_flag)
1758f7167e0eSDag-Erling Smørgrav 		(void)do_fsync(conn, handle, handle_len);
1759f7167e0eSDag-Erling Smørgrav 
1760d4af9e69SDag-Erling Smørgrav 	if (do_close(conn, handle, handle_len) != SSH2_FX_OK)
1761*bc5531deSDag-Erling Smørgrav 		status = SSH2_FX_FAILURE;
1762*bc5531deSDag-Erling Smørgrav 
1763e4a9863fSDag-Erling Smørgrav 	free(handle);
1764d4af9e69SDag-Erling Smørgrav 
1765*bc5531deSDag-Erling Smørgrav 	return status == SSH2_FX_OK ? 0 : -1;
17661e8db6e2SBrian Feldman }
1767b15c8340SDag-Erling Smørgrav 
1768b15c8340SDag-Erling Smørgrav static int
1769*bc5531deSDag-Erling Smørgrav upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1770*bc5531deSDag-Erling Smørgrav     int depth, int preserve_flag, int print_flag, int resume, int fsync_flag)
1771b15c8340SDag-Erling Smørgrav {
1772*bc5531deSDag-Erling Smørgrav 	int ret = 0;
1773*bc5531deSDag-Erling Smørgrav 	u_int status;
1774b15c8340SDag-Erling Smørgrav 	DIR *dirp;
1775b15c8340SDag-Erling Smørgrav 	struct dirent *dp;
1776b15c8340SDag-Erling Smørgrav 	char *filename, *new_src, *new_dst;
1777b15c8340SDag-Erling Smørgrav 	struct stat sb;
1778b15c8340SDag-Erling Smørgrav 	Attrib a;
1779b15c8340SDag-Erling Smørgrav 
1780b15c8340SDag-Erling Smørgrav 	if (depth >= MAX_DIR_DEPTH) {
1781b15c8340SDag-Erling Smørgrav 		error("Maximum directory depth exceeded: %d levels", depth);
1782b15c8340SDag-Erling Smørgrav 		return -1;
1783b15c8340SDag-Erling Smørgrav 	}
1784b15c8340SDag-Erling Smørgrav 
1785b15c8340SDag-Erling Smørgrav 	if (stat(src, &sb) == -1) {
1786b15c8340SDag-Erling Smørgrav 		error("Couldn't stat directory \"%s\": %s",
1787b15c8340SDag-Erling Smørgrav 		    src, strerror(errno));
1788b15c8340SDag-Erling Smørgrav 		return -1;
1789b15c8340SDag-Erling Smørgrav 	}
1790b15c8340SDag-Erling Smørgrav 	if (!S_ISDIR(sb.st_mode)) {
1791b15c8340SDag-Erling Smørgrav 		error("\"%s\" is not a directory", src);
1792b15c8340SDag-Erling Smørgrav 		return -1;
1793b15c8340SDag-Erling Smørgrav 	}
1794f7167e0eSDag-Erling Smørgrav 	if (print_flag)
1795b15c8340SDag-Erling Smørgrav 		printf("Entering %s\n", src);
1796b15c8340SDag-Erling Smørgrav 
1797b15c8340SDag-Erling Smørgrav 	attrib_clear(&a);
1798b15c8340SDag-Erling Smørgrav 	stat_to_attrib(&sb, &a);
1799b15c8340SDag-Erling Smørgrav 	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
1800b15c8340SDag-Erling Smørgrav 	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
1801b15c8340SDag-Erling Smørgrav 	a.perm &= 01777;
1802f7167e0eSDag-Erling Smørgrav 	if (!preserve_flag)
1803b15c8340SDag-Erling Smørgrav 		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
1804b15c8340SDag-Erling Smørgrav 
1805b15c8340SDag-Erling Smørgrav 	status = do_mkdir(conn, dst, &a, 0);
1806b15c8340SDag-Erling Smørgrav 	/*
1807b15c8340SDag-Erling Smørgrav 	 * we lack a portable status for errno EEXIST,
1808b15c8340SDag-Erling Smørgrav 	 * so if we get a SSH2_FX_FAILURE back we must check
1809b15c8340SDag-Erling Smørgrav 	 * if it was created successfully.
1810b15c8340SDag-Erling Smørgrav 	 */
1811b15c8340SDag-Erling Smørgrav 	if (status != SSH2_FX_OK) {
1812b15c8340SDag-Erling Smørgrav 		if (status != SSH2_FX_FAILURE)
1813b15c8340SDag-Erling Smørgrav 			return -1;
1814b15c8340SDag-Erling Smørgrav 		if (do_stat(conn, dst, 0) == NULL)
1815b15c8340SDag-Erling Smørgrav 			return -1;
1816b15c8340SDag-Erling Smørgrav 	}
1817b15c8340SDag-Erling Smørgrav 
1818b15c8340SDag-Erling Smørgrav 	if ((dirp = opendir(src)) == NULL) {
1819b15c8340SDag-Erling Smørgrav 		error("Failed to open dir \"%s\": %s", src, strerror(errno));
1820b15c8340SDag-Erling Smørgrav 		return -1;
1821b15c8340SDag-Erling Smørgrav 	}
1822b15c8340SDag-Erling Smørgrav 
1823b15c8340SDag-Erling Smørgrav 	while (((dp = readdir(dirp)) != NULL) && !interrupted) {
1824b15c8340SDag-Erling Smørgrav 		if (dp->d_ino == 0)
1825b15c8340SDag-Erling Smørgrav 			continue;
1826b15c8340SDag-Erling Smørgrav 		filename = dp->d_name;
1827b15c8340SDag-Erling Smørgrav 		new_dst = path_append(dst, filename);
1828b15c8340SDag-Erling Smørgrav 		new_src = path_append(src, filename);
1829b15c8340SDag-Erling Smørgrav 
1830b15c8340SDag-Erling Smørgrav 		if (lstat(new_src, &sb) == -1) {
1831b15c8340SDag-Erling Smørgrav 			logit("%s: lstat failed: %s", filename,
1832b15c8340SDag-Erling Smørgrav 			    strerror(errno));
1833b15c8340SDag-Erling Smørgrav 			ret = -1;
1834b15c8340SDag-Erling Smørgrav 		} else if (S_ISDIR(sb.st_mode)) {
1835b15c8340SDag-Erling Smørgrav 			if (strcmp(filename, ".") == 0 ||
1836b15c8340SDag-Erling Smørgrav 			    strcmp(filename, "..") == 0)
1837b15c8340SDag-Erling Smørgrav 				continue;
1838b15c8340SDag-Erling Smørgrav 
1839b15c8340SDag-Erling Smørgrav 			if (upload_dir_internal(conn, new_src, new_dst,
1840a0ee8cc6SDag-Erling Smørgrav 			    depth + 1, preserve_flag, print_flag, resume,
1841f7167e0eSDag-Erling Smørgrav 			    fsync_flag) == -1)
1842b15c8340SDag-Erling Smørgrav 				ret = -1;
1843b15c8340SDag-Erling Smørgrav 		} else if (S_ISREG(sb.st_mode)) {
1844f7167e0eSDag-Erling Smørgrav 			if (do_upload(conn, new_src, new_dst,
1845a0ee8cc6SDag-Erling Smørgrav 			    preserve_flag, resume, fsync_flag) == -1) {
1846b15c8340SDag-Erling Smørgrav 				error("Uploading of file %s to %s failed!",
1847b15c8340SDag-Erling Smørgrav 				    new_src, new_dst);
1848b15c8340SDag-Erling Smørgrav 				ret = -1;
1849b15c8340SDag-Erling Smørgrav 			}
1850b15c8340SDag-Erling Smørgrav 		} else
1851b15c8340SDag-Erling Smørgrav 			logit("%s: not a regular file\n", filename);
1852e4a9863fSDag-Erling Smørgrav 		free(new_dst);
1853e4a9863fSDag-Erling Smørgrav 		free(new_src);
1854b15c8340SDag-Erling Smørgrav 	}
1855b15c8340SDag-Erling Smørgrav 
1856b15c8340SDag-Erling Smørgrav 	do_setstat(conn, dst, &a);
1857b15c8340SDag-Erling Smørgrav 
1858b15c8340SDag-Erling Smørgrav 	(void) closedir(dirp);
1859b15c8340SDag-Erling Smørgrav 	return ret;
1860b15c8340SDag-Erling Smørgrav }
1861b15c8340SDag-Erling Smørgrav 
1862b15c8340SDag-Erling Smørgrav int
1863*bc5531deSDag-Erling Smørgrav upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
1864*bc5531deSDag-Erling Smørgrav     int preserve_flag, int print_flag, int resume, int fsync_flag)
1865b15c8340SDag-Erling Smørgrav {
1866b15c8340SDag-Erling Smørgrav 	char *dst_canon;
1867b15c8340SDag-Erling Smørgrav 	int ret;
1868b15c8340SDag-Erling Smørgrav 
1869b15c8340SDag-Erling Smørgrav 	if ((dst_canon = do_realpath(conn, dst)) == NULL) {
1870f7167e0eSDag-Erling Smørgrav 		error("Unable to canonicalize path \"%s\"", dst);
1871b15c8340SDag-Erling Smørgrav 		return -1;
1872b15c8340SDag-Erling Smørgrav 	}
1873b15c8340SDag-Erling Smørgrav 
1874f7167e0eSDag-Erling Smørgrav 	ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag,
1875a0ee8cc6SDag-Erling Smørgrav 	    print_flag, resume, fsync_flag);
1876f7167e0eSDag-Erling Smørgrav 
1877e4a9863fSDag-Erling Smørgrav 	free(dst_canon);
1878b15c8340SDag-Erling Smørgrav 	return ret;
1879b15c8340SDag-Erling Smørgrav }
1880b15c8340SDag-Erling Smørgrav 
1881b15c8340SDag-Erling Smørgrav char *
1882*bc5531deSDag-Erling Smørgrav path_append(const char *p1, const char *p2)
1883b15c8340SDag-Erling Smørgrav {
1884b15c8340SDag-Erling Smørgrav 	char *ret;
1885b15c8340SDag-Erling Smørgrav 	size_t len = strlen(p1) + strlen(p2) + 2;
1886b15c8340SDag-Erling Smørgrav 
1887b15c8340SDag-Erling Smørgrav 	ret = xmalloc(len);
1888b15c8340SDag-Erling Smørgrav 	strlcpy(ret, p1, len);
1889b15c8340SDag-Erling Smørgrav 	if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
1890b15c8340SDag-Erling Smørgrav 		strlcat(ret, "/", len);
1891b15c8340SDag-Erling Smørgrav 	strlcat(ret, p2, len);
1892b15c8340SDag-Erling Smørgrav 
1893b15c8340SDag-Erling Smørgrav 	return(ret);
1894b15c8340SDag-Erling Smørgrav }
1895b15c8340SDag-Erling Smørgrav 
1896