xref: /freebsd/crypto/openssh/sftp-client.c (revision 462c32cb8d7a451c999a3f1e7d00f9c89e96700c)
1*462c32cbSDag-Erling Smørgrav /* $OpenBSD: sftp-client.c,v 1.97 2012/07/02 12:13:26 dtucker Exp $ */
21e8db6e2SBrian Feldman /*
3efcad6b7SDag-Erling Smørgrav  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
41e8db6e2SBrian Feldman  *
5efcad6b7SDag-Erling Smørgrav  * Permission to use, copy, modify, and distribute this software for any
6efcad6b7SDag-Erling Smørgrav  * purpose with or without fee is hereby granted, provided that the above
7efcad6b7SDag-Erling Smørgrav  * copyright notice and this permission notice appear in all copies.
81e8db6e2SBrian Feldman  *
9efcad6b7SDag-Erling Smørgrav  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10efcad6b7SDag-Erling Smørgrav  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11efcad6b7SDag-Erling Smørgrav  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12efcad6b7SDag-Erling Smørgrav  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13efcad6b7SDag-Erling Smørgrav  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14efcad6b7SDag-Erling Smørgrav  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15efcad6b7SDag-Erling Smørgrav  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
161e8db6e2SBrian Feldman  */
171e8db6e2SBrian Feldman 
181e8db6e2SBrian Feldman /* XXX: memleaks */
191e8db6e2SBrian Feldman /* XXX: signed vs unsigned */
20ae1f160dSDag-Erling Smørgrav /* XXX: remove all logging, only return status codes */
211e8db6e2SBrian Feldman /* XXX: copy between two remote sites */
221e8db6e2SBrian Feldman 
231e8db6e2SBrian Feldman #include "includes.h"
241e8db6e2SBrian Feldman 
25761efaa7SDag-Erling Smørgrav #include <sys/types.h>
26761efaa7SDag-Erling Smørgrav #include <sys/param.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>
45761efaa7SDag-Erling Smørgrav #include <string.h>
46761efaa7SDag-Erling Smørgrav #include <unistd.h>
47761efaa7SDag-Erling Smørgrav 
481e8db6e2SBrian Feldman #include "xmalloc.h"
49761efaa7SDag-Erling Smørgrav #include "buffer.h"
501e8db6e2SBrian Feldman #include "log.h"
511e8db6e2SBrian Feldman #include "atomicio.h"
52d0c8c0bcSDag-Erling Smørgrav #include "progressmeter.h"
53761efaa7SDag-Erling Smørgrav #include "misc.h"
541e8db6e2SBrian Feldman 
551e8db6e2SBrian Feldman #include "sftp.h"
561e8db6e2SBrian Feldman #include "sftp-common.h"
571e8db6e2SBrian Feldman #include "sftp-client.h"
581e8db6e2SBrian Feldman 
59d74d50a8SDag-Erling Smørgrav extern volatile sig_atomic_t interrupted;
60d0c8c0bcSDag-Erling Smørgrav extern int showprogress;
61d0c8c0bcSDag-Erling Smørgrav 
62761efaa7SDag-Erling Smørgrav /* Minimum amount of data to read at a time */
63ae1f160dSDag-Erling Smørgrav #define MIN_READ_SIZE	512
641e8db6e2SBrian Feldman 
65b15c8340SDag-Erling Smørgrav /* Maximum depth to descend in directory trees */
66b15c8340SDag-Erling Smørgrav #define MAX_DIR_DEPTH 64
67b15c8340SDag-Erling Smørgrav 
68ae1f160dSDag-Erling Smørgrav struct sftp_conn {
69ae1f160dSDag-Erling Smørgrav 	int fd_in;
70ae1f160dSDag-Erling Smørgrav 	int fd_out;
71ae1f160dSDag-Erling Smørgrav 	u_int transfer_buflen;
72ae1f160dSDag-Erling Smørgrav 	u_int num_requests;
73ae1f160dSDag-Erling Smørgrav 	u_int version;
74ae1f160dSDag-Erling Smørgrav 	u_int msg_id;
75d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_POSIX_RENAME	0x00000001
76d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_STATVFS	0x00000002
77d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_FSTATVFS	0x00000004
784a421b63SDag-Erling Smørgrav #define SFTP_EXT_HARDLINK	0x00000008
79d4af9e69SDag-Erling Smørgrav 	u_int exts;
804a421b63SDag-Erling Smørgrav 	u_int64_t limit_kbps;
814a421b63SDag-Erling Smørgrav 	struct bwlimit bwlimit_in, bwlimit_out;
82ae1f160dSDag-Erling Smørgrav };
831e8db6e2SBrian Feldman 
84b15c8340SDag-Erling Smørgrav static char *
854a421b63SDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, u_int *len,
864a421b63SDag-Erling Smørgrav     const char *errfmt, ...) __attribute__((format(printf, 4, 5)));
874a421b63SDag-Erling Smørgrav 
884a421b63SDag-Erling Smørgrav /* ARGSUSED */
894a421b63SDag-Erling Smørgrav static int
904a421b63SDag-Erling Smørgrav sftpio(void *_bwlimit, size_t amount)
914a421b63SDag-Erling Smørgrav {
924a421b63SDag-Erling Smørgrav 	struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;
934a421b63SDag-Erling Smørgrav 
944a421b63SDag-Erling Smørgrav 	bandwidth_limit(bwlimit, amount);
954a421b63SDag-Erling Smørgrav 	return 0;
964a421b63SDag-Erling Smørgrav }
97b15c8340SDag-Erling Smørgrav 
98ae1f160dSDag-Erling Smørgrav static void
994a421b63SDag-Erling Smørgrav send_msg(struct sftp_conn *conn, Buffer *m)
1001e8db6e2SBrian Feldman {
101d0c8c0bcSDag-Erling Smørgrav 	u_char mlen[4];
102761efaa7SDag-Erling Smørgrav 	struct iovec iov[2];
1031e8db6e2SBrian Feldman 
104021d409fSDag-Erling Smørgrav 	if (buffer_len(m) > SFTP_MAX_MSG_LENGTH)
105d0c8c0bcSDag-Erling Smørgrav 		fatal("Outbound message too long %u", buffer_len(m));
1061e8db6e2SBrian Feldman 
107d0c8c0bcSDag-Erling Smørgrav 	/* Send length first */
108761efaa7SDag-Erling Smørgrav 	put_u32(mlen, buffer_len(m));
109761efaa7SDag-Erling Smørgrav 	iov[0].iov_base = mlen;
110761efaa7SDag-Erling Smørgrav 	iov[0].iov_len = sizeof(mlen);
111761efaa7SDag-Erling Smørgrav 	iov[1].iov_base = buffer_ptr(m);
112761efaa7SDag-Erling Smørgrav 	iov[1].iov_len = buffer_len(m);
1131e8db6e2SBrian Feldman 
1144a421b63SDag-Erling Smørgrav 	if (atomiciov6(writev, conn->fd_out, iov, 2,
1154a421b63SDag-Erling Smørgrav 	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) !=
1164a421b63SDag-Erling Smørgrav 	    buffer_len(m) + sizeof(mlen))
117d0c8c0bcSDag-Erling Smørgrav 		fatal("Couldn't send packet: %s", strerror(errno));
118d0c8c0bcSDag-Erling Smørgrav 
119d0c8c0bcSDag-Erling Smørgrav 	buffer_clear(m);
1201e8db6e2SBrian Feldman }
1211e8db6e2SBrian Feldman 
122ae1f160dSDag-Erling Smørgrav static void
1234a421b63SDag-Erling Smørgrav get_msg(struct sftp_conn *conn, Buffer *m)
1241e8db6e2SBrian Feldman {
125d0c8c0bcSDag-Erling Smørgrav 	u_int msg_len;
1261e8db6e2SBrian Feldman 
127d0c8c0bcSDag-Erling Smørgrav 	buffer_append_space(m, 4);
1284a421b63SDag-Erling Smørgrav 	if (atomicio6(read, conn->fd_in, buffer_ptr(m), 4,
1294a421b63SDag-Erling Smørgrav 	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) {
130043840dfSDag-Erling Smørgrav 		if (errno == EPIPE)
1311e8db6e2SBrian Feldman 			fatal("Connection closed");
132043840dfSDag-Erling Smørgrav 		else
1331e8db6e2SBrian Feldman 			fatal("Couldn't read packet: %s", strerror(errno));
134043840dfSDag-Erling Smørgrav 	}
1351e8db6e2SBrian Feldman 
136d0c8c0bcSDag-Erling Smørgrav 	msg_len = buffer_get_int(m);
137021d409fSDag-Erling Smørgrav 	if (msg_len > SFTP_MAX_MSG_LENGTH)
138ee21a45fSDag-Erling Smørgrav 		fatal("Received message too long %u", msg_len);
1391e8db6e2SBrian Feldman 
140d0c8c0bcSDag-Erling Smørgrav 	buffer_append_space(m, msg_len);
1414a421b63SDag-Erling Smørgrav 	if (atomicio6(read, conn->fd_in, buffer_ptr(m), msg_len,
1424a421b63SDag-Erling Smørgrav 	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in)
1434a421b63SDag-Erling Smørgrav 	    != msg_len) {
144043840dfSDag-Erling Smørgrav 		if (errno == EPIPE)
1451e8db6e2SBrian Feldman 			fatal("Connection closed");
146043840dfSDag-Erling Smørgrav 		else
147d0c8c0bcSDag-Erling Smørgrav 			fatal("Read packet: %s", strerror(errno));
1481e8db6e2SBrian Feldman 	}
149043840dfSDag-Erling Smørgrav }
1501e8db6e2SBrian Feldman 
151ae1f160dSDag-Erling Smørgrav static void
1524a421b63SDag-Erling Smørgrav send_string_request(struct sftp_conn *conn, u_int id, u_int code, char *s,
1531e8db6e2SBrian Feldman     u_int len)
1541e8db6e2SBrian Feldman {
1551e8db6e2SBrian Feldman 	Buffer msg;
1561e8db6e2SBrian Feldman 
1571e8db6e2SBrian Feldman 	buffer_init(&msg);
1581e8db6e2SBrian Feldman 	buffer_put_char(&msg, code);
1591e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
1601e8db6e2SBrian Feldman 	buffer_put_string(&msg, s, len);
1614a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
1624a421b63SDag-Erling Smørgrav 	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
1631e8db6e2SBrian Feldman 	buffer_free(&msg);
1641e8db6e2SBrian Feldman }
1651e8db6e2SBrian Feldman 
166ae1f160dSDag-Erling Smørgrav static void
1674a421b63SDag-Erling Smørgrav send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
1684a421b63SDag-Erling Smørgrav     char *s, u_int len, Attrib *a)
1691e8db6e2SBrian Feldman {
1701e8db6e2SBrian Feldman 	Buffer msg;
1711e8db6e2SBrian Feldman 
1721e8db6e2SBrian Feldman 	buffer_init(&msg);
1731e8db6e2SBrian Feldman 	buffer_put_char(&msg, code);
1741e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
1751e8db6e2SBrian Feldman 	buffer_put_string(&msg, s, len);
1761e8db6e2SBrian Feldman 	encode_attrib(&msg, a);
1774a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
1784a421b63SDag-Erling Smørgrav 	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
1791e8db6e2SBrian Feldman 	buffer_free(&msg);
1801e8db6e2SBrian Feldman }
1811e8db6e2SBrian Feldman 
182ae1f160dSDag-Erling Smørgrav static u_int
1834a421b63SDag-Erling Smørgrav get_status(struct sftp_conn *conn, u_int expected_id)
1841e8db6e2SBrian Feldman {
1851e8db6e2SBrian Feldman 	Buffer msg;
1861e8db6e2SBrian Feldman 	u_int type, id, status;
1871e8db6e2SBrian Feldman 
1881e8db6e2SBrian Feldman 	buffer_init(&msg);
1894a421b63SDag-Erling Smørgrav 	get_msg(conn, &msg);
1901e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
1911e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
1921e8db6e2SBrian Feldman 
1931e8db6e2SBrian Feldman 	if (id != expected_id)
194ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
1951e8db6e2SBrian Feldman 	if (type != SSH2_FXP_STATUS)
196ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
1971e8db6e2SBrian Feldman 		    SSH2_FXP_STATUS, type);
1981e8db6e2SBrian Feldman 
1991e8db6e2SBrian Feldman 	status = buffer_get_int(&msg);
2001e8db6e2SBrian Feldman 	buffer_free(&msg);
2011e8db6e2SBrian Feldman 
202ee21a45fSDag-Erling Smørgrav 	debug3("SSH2_FXP_STATUS %u", status);
2031e8db6e2SBrian Feldman 
2044a421b63SDag-Erling Smørgrav 	return status;
2051e8db6e2SBrian Feldman }
2061e8db6e2SBrian Feldman 
207ae1f160dSDag-Erling Smørgrav static char *
2084a421b63SDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, u_int *len,
2094a421b63SDag-Erling Smørgrav     const char *errfmt, ...)
2101e8db6e2SBrian Feldman {
2111e8db6e2SBrian Feldman 	Buffer msg;
2121e8db6e2SBrian Feldman 	u_int type, id;
213b15c8340SDag-Erling Smørgrav 	char *handle, errmsg[256];
214b15c8340SDag-Erling Smørgrav 	va_list args;
215b15c8340SDag-Erling Smørgrav 	int status;
216b15c8340SDag-Erling Smørgrav 
217b15c8340SDag-Erling Smørgrav 	va_start(args, errfmt);
218b15c8340SDag-Erling Smørgrav 	if (errfmt != NULL)
219b15c8340SDag-Erling Smørgrav 		vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
220b15c8340SDag-Erling Smørgrav 	va_end(args);
2211e8db6e2SBrian Feldman 
2221e8db6e2SBrian Feldman 	buffer_init(&msg);
2234a421b63SDag-Erling Smørgrav 	get_msg(conn, &msg);
2241e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
2251e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
2261e8db6e2SBrian Feldman 
2271e8db6e2SBrian Feldman 	if (id != expected_id)
228b15c8340SDag-Erling Smørgrav 		fatal("%s: ID mismatch (%u != %u)",
229b15c8340SDag-Erling Smørgrav 		    errfmt == NULL ? __func__ : errmsg, id, expected_id);
2301e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
231b15c8340SDag-Erling Smørgrav 		status = buffer_get_int(&msg);
232b15c8340SDag-Erling Smørgrav 		if (errfmt != NULL)
233b15c8340SDag-Erling Smørgrav 			error("%s: %s", errmsg, fx2txt(status));
2345e8dbd04SDag-Erling Smørgrav 		buffer_free(&msg);
2351e8db6e2SBrian Feldman 		return(NULL);
2361e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_HANDLE)
237b15c8340SDag-Erling Smørgrav 		fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
238b15c8340SDag-Erling Smørgrav 		    errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
2391e8db6e2SBrian Feldman 
2401e8db6e2SBrian Feldman 	handle = buffer_get_string(&msg, len);
2411e8db6e2SBrian Feldman 	buffer_free(&msg);
2421e8db6e2SBrian Feldman 
2431e8db6e2SBrian Feldman 	return(handle);
2441e8db6e2SBrian Feldman }
2451e8db6e2SBrian Feldman 
246ae1f160dSDag-Erling Smørgrav static Attrib *
2474a421b63SDag-Erling Smørgrav get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
2481e8db6e2SBrian Feldman {
2491e8db6e2SBrian Feldman 	Buffer msg;
2501e8db6e2SBrian Feldman 	u_int type, id;
2511e8db6e2SBrian Feldman 	Attrib *a;
2521e8db6e2SBrian Feldman 
2531e8db6e2SBrian Feldman 	buffer_init(&msg);
2544a421b63SDag-Erling Smørgrav 	get_msg(conn, &msg);
2551e8db6e2SBrian Feldman 
2561e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
2571e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
2581e8db6e2SBrian Feldman 
259ee21a45fSDag-Erling Smørgrav 	debug3("Received stat reply T:%u I:%u", type, id);
2601e8db6e2SBrian Feldman 	if (id != expected_id)
261ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
2621e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
2631e8db6e2SBrian Feldman 		int status = buffer_get_int(&msg);
2641e8db6e2SBrian Feldman 
2651e8db6e2SBrian Feldman 		if (quiet)
2661e8db6e2SBrian Feldman 			debug("Couldn't stat remote file: %s", fx2txt(status));
2671e8db6e2SBrian Feldman 		else
2681e8db6e2SBrian Feldman 			error("Couldn't stat remote file: %s", fx2txt(status));
2695e8dbd04SDag-Erling Smørgrav 		buffer_free(&msg);
2701e8db6e2SBrian Feldman 		return(NULL);
2711e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_ATTRS) {
272ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
2731e8db6e2SBrian Feldman 		    SSH2_FXP_ATTRS, type);
2741e8db6e2SBrian Feldman 	}
2751e8db6e2SBrian Feldman 	a = decode_attrib(&msg);
2761e8db6e2SBrian Feldman 	buffer_free(&msg);
2771e8db6e2SBrian Feldman 
2781e8db6e2SBrian Feldman 	return(a);
2791e8db6e2SBrian Feldman }
2801e8db6e2SBrian Feldman 
281d4af9e69SDag-Erling Smørgrav static int
2824a421b63SDag-Erling Smørgrav get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
2834a421b63SDag-Erling Smørgrav     u_int expected_id, int quiet)
284d4af9e69SDag-Erling Smørgrav {
285d4af9e69SDag-Erling Smørgrav 	Buffer msg;
286d4af9e69SDag-Erling Smørgrav 	u_int type, id, flag;
287d4af9e69SDag-Erling Smørgrav 
288d4af9e69SDag-Erling Smørgrav 	buffer_init(&msg);
2894a421b63SDag-Erling Smørgrav 	get_msg(conn, &msg);
290d4af9e69SDag-Erling Smørgrav 
291d4af9e69SDag-Erling Smørgrav 	type = buffer_get_char(&msg);
292d4af9e69SDag-Erling Smørgrav 	id = buffer_get_int(&msg);
293d4af9e69SDag-Erling Smørgrav 
294d4af9e69SDag-Erling Smørgrav 	debug3("Received statvfs reply T:%u I:%u", type, id);
295d4af9e69SDag-Erling Smørgrav 	if (id != expected_id)
296d4af9e69SDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
297d4af9e69SDag-Erling Smørgrav 	if (type == SSH2_FXP_STATUS) {
298d4af9e69SDag-Erling Smørgrav 		int status = buffer_get_int(&msg);
299d4af9e69SDag-Erling Smørgrav 
300d4af9e69SDag-Erling Smørgrav 		if (quiet)
301d4af9e69SDag-Erling Smørgrav 			debug("Couldn't statvfs: %s", fx2txt(status));
302d4af9e69SDag-Erling Smørgrav 		else
303d4af9e69SDag-Erling Smørgrav 			error("Couldn't statvfs: %s", fx2txt(status));
304d4af9e69SDag-Erling Smørgrav 		buffer_free(&msg);
305d4af9e69SDag-Erling Smørgrav 		return -1;
306d4af9e69SDag-Erling Smørgrav 	} else if (type != SSH2_FXP_EXTENDED_REPLY) {
307d4af9e69SDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
308d4af9e69SDag-Erling Smørgrav 		    SSH2_FXP_EXTENDED_REPLY, type);
309d4af9e69SDag-Erling Smørgrav 	}
310d4af9e69SDag-Erling Smørgrav 
311d4af9e69SDag-Erling Smørgrav 	bzero(st, sizeof(*st));
312d4af9e69SDag-Erling Smørgrav 	st->f_bsize = buffer_get_int64(&msg);
313d4af9e69SDag-Erling Smørgrav 	st->f_frsize = buffer_get_int64(&msg);
314d4af9e69SDag-Erling Smørgrav 	st->f_blocks = buffer_get_int64(&msg);
315d4af9e69SDag-Erling Smørgrav 	st->f_bfree = buffer_get_int64(&msg);
316d4af9e69SDag-Erling Smørgrav 	st->f_bavail = buffer_get_int64(&msg);
317d4af9e69SDag-Erling Smørgrav 	st->f_files = buffer_get_int64(&msg);
318d4af9e69SDag-Erling Smørgrav 	st->f_ffree = buffer_get_int64(&msg);
319d4af9e69SDag-Erling Smørgrav 	st->f_favail = buffer_get_int64(&msg);
320d4af9e69SDag-Erling Smørgrav 	st->f_fsid = buffer_get_int64(&msg);
321d4af9e69SDag-Erling Smørgrav 	flag = buffer_get_int64(&msg);
322d4af9e69SDag-Erling Smørgrav 	st->f_namemax = buffer_get_int64(&msg);
323d4af9e69SDag-Erling Smørgrav 
324d4af9e69SDag-Erling Smørgrav 	st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
325d4af9e69SDag-Erling Smørgrav 	st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
326d4af9e69SDag-Erling Smørgrav 
327d4af9e69SDag-Erling Smørgrav 	buffer_free(&msg);
328d4af9e69SDag-Erling Smørgrav 
329d4af9e69SDag-Erling Smørgrav 	return 0;
330d4af9e69SDag-Erling Smørgrav }
331d4af9e69SDag-Erling Smørgrav 
332ae1f160dSDag-Erling Smørgrav struct sftp_conn *
3334a421b63SDag-Erling Smørgrav do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
3344a421b63SDag-Erling Smørgrav     u_int64_t limit_kbps)
3351e8db6e2SBrian Feldman {
3364a421b63SDag-Erling Smørgrav 	u_int type;
3371e8db6e2SBrian Feldman 	Buffer msg;
338ae1f160dSDag-Erling Smørgrav 	struct sftp_conn *ret;
3391e8db6e2SBrian Feldman 
3404a421b63SDag-Erling Smørgrav 	ret = xmalloc(sizeof(*ret));
3414a421b63SDag-Erling Smørgrav 	ret->fd_in = fd_in;
3424a421b63SDag-Erling Smørgrav 	ret->fd_out = fd_out;
3434a421b63SDag-Erling Smørgrav 	ret->transfer_buflen = transfer_buflen;
3444a421b63SDag-Erling Smørgrav 	ret->num_requests = num_requests;
3454a421b63SDag-Erling Smørgrav 	ret->exts = 0;
3464a421b63SDag-Erling Smørgrav 	ret->limit_kbps = 0;
3474a421b63SDag-Erling Smørgrav 
3481e8db6e2SBrian Feldman 	buffer_init(&msg);
3491e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_INIT);
3501e8db6e2SBrian Feldman 	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
3514a421b63SDag-Erling Smørgrav 	send_msg(ret, &msg);
3521e8db6e2SBrian Feldman 
3531e8db6e2SBrian Feldman 	buffer_clear(&msg);
3541e8db6e2SBrian Feldman 
3554a421b63SDag-Erling Smørgrav 	get_msg(ret, &msg);
3561e8db6e2SBrian Feldman 
3571e8db6e2SBrian Feldman 	/* Expecting a VERSION reply */
3581e8db6e2SBrian Feldman 	if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) {
359ee21a45fSDag-Erling Smørgrav 		error("Invalid packet back from SSH2_FXP_INIT (type %u)",
3601e8db6e2SBrian Feldman 		    type);
3611e8db6e2SBrian Feldman 		buffer_free(&msg);
362ae1f160dSDag-Erling Smørgrav 		return(NULL);
3631e8db6e2SBrian Feldman 	}
3644a421b63SDag-Erling Smørgrav 	ret->version = buffer_get_int(&msg);
3651e8db6e2SBrian Feldman 
3664a421b63SDag-Erling Smørgrav 	debug2("Remote version: %u", ret->version);
3671e8db6e2SBrian Feldman 
3681e8db6e2SBrian Feldman 	/* Check for extensions */
3691e8db6e2SBrian Feldman 	while (buffer_len(&msg) > 0) {
3701e8db6e2SBrian Feldman 		char *name = buffer_get_string(&msg, NULL);
3711e8db6e2SBrian Feldman 		char *value = buffer_get_string(&msg, NULL);
372d4af9e69SDag-Erling Smørgrav 		int known = 0;
3731e8db6e2SBrian Feldman 
374d4af9e69SDag-Erling Smørgrav 		if (strcmp(name, "posix-rename@openssh.com") == 0 &&
375d4af9e69SDag-Erling Smørgrav 		    strcmp(value, "1") == 0) {
3764a421b63SDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_POSIX_RENAME;
377d4af9e69SDag-Erling Smørgrav 			known = 1;
378d4af9e69SDag-Erling Smørgrav 		} else if (strcmp(name, "statvfs@openssh.com") == 0 &&
379d4af9e69SDag-Erling Smørgrav 		    strcmp(value, "2") == 0) {
3804a421b63SDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_STATVFS;
381d4af9e69SDag-Erling Smørgrav 			known = 1;
3824a421b63SDag-Erling Smørgrav 		} else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
383d4af9e69SDag-Erling Smørgrav 		    strcmp(value, "2") == 0) {
3844a421b63SDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_FSTATVFS;
3854a421b63SDag-Erling Smørgrav 			known = 1;
3864a421b63SDag-Erling Smørgrav 		} else if (strcmp(name, "hardlink@openssh.com") == 0 &&
3874a421b63SDag-Erling Smørgrav 		    strcmp(value, "1") == 0) {
3884a421b63SDag-Erling Smørgrav 			ret->exts |= SFTP_EXT_HARDLINK;
389d4af9e69SDag-Erling Smørgrav 			known = 1;
390d4af9e69SDag-Erling Smørgrav 		}
391d4af9e69SDag-Erling Smørgrav 		if (known) {
392d4af9e69SDag-Erling Smørgrav 			debug2("Server supports extension \"%s\" revision %s",
393d4af9e69SDag-Erling Smørgrav 			    name, value);
394d4af9e69SDag-Erling Smørgrav 		} else {
395d4af9e69SDag-Erling Smørgrav 			debug2("Unrecognised server extension \"%s\"", name);
396d4af9e69SDag-Erling Smørgrav 		}
3971e8db6e2SBrian Feldman 		xfree(name);
3981e8db6e2SBrian Feldman 		xfree(value);
3991e8db6e2SBrian Feldman 	}
4001e8db6e2SBrian Feldman 
4011e8db6e2SBrian Feldman 	buffer_free(&msg);
4021e8db6e2SBrian Feldman 
403ae1f160dSDag-Erling Smørgrav 	/* Some filexfer v.0 servers don't support large packets */
4044a421b63SDag-Erling Smørgrav 	if (ret->version == 0)
405545d5ecaSDag-Erling Smørgrav 		ret->transfer_buflen = MIN(ret->transfer_buflen, 20480);
406ae1f160dSDag-Erling Smørgrav 
4074a421b63SDag-Erling Smørgrav 	ret->limit_kbps = limit_kbps;
4084a421b63SDag-Erling Smørgrav 	if (ret->limit_kbps > 0) {
4094a421b63SDag-Erling Smørgrav 		bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
4104a421b63SDag-Erling Smørgrav 		    ret->transfer_buflen);
4114a421b63SDag-Erling Smørgrav 		bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
4124a421b63SDag-Erling Smørgrav 		    ret->transfer_buflen);
4134a421b63SDag-Erling Smørgrav 	}
4144a421b63SDag-Erling Smørgrav 
4154a421b63SDag-Erling Smørgrav 	return ret;
416ae1f160dSDag-Erling Smørgrav }
417ae1f160dSDag-Erling Smørgrav 
418ae1f160dSDag-Erling Smørgrav u_int
419ae1f160dSDag-Erling Smørgrav sftp_proto_version(struct sftp_conn *conn)
420ae1f160dSDag-Erling Smørgrav {
4214a421b63SDag-Erling Smørgrav 	return conn->version;
4221e8db6e2SBrian Feldman }
4231e8db6e2SBrian Feldman 
4241e8db6e2SBrian Feldman int
425ae1f160dSDag-Erling Smørgrav do_close(struct sftp_conn *conn, char *handle, u_int handle_len)
4261e8db6e2SBrian Feldman {
4271e8db6e2SBrian Feldman 	u_int id, status;
4281e8db6e2SBrian Feldman 	Buffer msg;
4291e8db6e2SBrian Feldman 
4301e8db6e2SBrian Feldman 	buffer_init(&msg);
4311e8db6e2SBrian Feldman 
432ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
4331e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_CLOSE);
4341e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
4351e8db6e2SBrian Feldman 	buffer_put_string(&msg, handle, handle_len);
4364a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
437ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
4381e8db6e2SBrian Feldman 
4394a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
4401e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
4411e8db6e2SBrian Feldman 		error("Couldn't close file: %s", fx2txt(status));
4421e8db6e2SBrian Feldman 
4431e8db6e2SBrian Feldman 	buffer_free(&msg);
4441e8db6e2SBrian Feldman 
4454a421b63SDag-Erling Smørgrav 	return status;
4461e8db6e2SBrian Feldman }
4471e8db6e2SBrian Feldman 
4481e8db6e2SBrian Feldman 
449ae1f160dSDag-Erling Smørgrav static int
450ae1f160dSDag-Erling Smørgrav do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
4511e8db6e2SBrian Feldman     SFTP_DIRENT ***dir)
4521e8db6e2SBrian Feldman {
4531e8db6e2SBrian Feldman 	Buffer msg;
454043840dfSDag-Erling Smørgrav 	u_int count, type, id, handle_len, i, expected_id, ents = 0;
4551e8db6e2SBrian Feldman 	char *handle;
4561e8db6e2SBrian Feldman 
457ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
4581e8db6e2SBrian Feldman 
4591e8db6e2SBrian Feldman 	buffer_init(&msg);
4601e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_OPENDIR);
4611e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
4621e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, path);
4634a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
4641e8db6e2SBrian Feldman 
4654a421b63SDag-Erling Smørgrav 	handle = get_handle(conn, id, &handle_len,
466b15c8340SDag-Erling Smørgrav 	    "remote readdir(\"%s\")", path);
467*462c32cbSDag-Erling Smørgrav 	if (handle == NULL) {
468*462c32cbSDag-Erling Smørgrav 		buffer_free(&msg);
4694a421b63SDag-Erling Smørgrav 		return -1;
470*462c32cbSDag-Erling Smørgrav 	}
4711e8db6e2SBrian Feldman 
4721e8db6e2SBrian Feldman 	if (dir) {
4731e8db6e2SBrian Feldman 		ents = 0;
4741e8db6e2SBrian Feldman 		*dir = xmalloc(sizeof(**dir));
4751e8db6e2SBrian Feldman 		(*dir)[0] = NULL;
4761e8db6e2SBrian Feldman 	}
4771e8db6e2SBrian Feldman 
478d74d50a8SDag-Erling Smørgrav 	for (; !interrupted;) {
479ae1f160dSDag-Erling Smørgrav 		id = expected_id = conn->msg_id++;
4801e8db6e2SBrian Feldman 
481ee21a45fSDag-Erling Smørgrav 		debug3("Sending SSH2_FXP_READDIR I:%u", id);
4821e8db6e2SBrian Feldman 
4831e8db6e2SBrian Feldman 		buffer_clear(&msg);
4841e8db6e2SBrian Feldman 		buffer_put_char(&msg, SSH2_FXP_READDIR);
4851e8db6e2SBrian Feldman 		buffer_put_int(&msg, id);
4861e8db6e2SBrian Feldman 		buffer_put_string(&msg, handle, handle_len);
4874a421b63SDag-Erling Smørgrav 		send_msg(conn, &msg);
4881e8db6e2SBrian Feldman 
4891e8db6e2SBrian Feldman 		buffer_clear(&msg);
4901e8db6e2SBrian Feldman 
4914a421b63SDag-Erling Smørgrav 		get_msg(conn, &msg);
4921e8db6e2SBrian Feldman 
4931e8db6e2SBrian Feldman 		type = buffer_get_char(&msg);
4941e8db6e2SBrian Feldman 		id = buffer_get_int(&msg);
4951e8db6e2SBrian Feldman 
496ee21a45fSDag-Erling Smørgrav 		debug3("Received reply T:%u I:%u", type, id);
4971e8db6e2SBrian Feldman 
4981e8db6e2SBrian Feldman 		if (id != expected_id)
499ee21a45fSDag-Erling Smørgrav 			fatal("ID mismatch (%u != %u)", id, expected_id);
5001e8db6e2SBrian Feldman 
5011e8db6e2SBrian Feldman 		if (type == SSH2_FXP_STATUS) {
5021e8db6e2SBrian Feldman 			int status = buffer_get_int(&msg);
5031e8db6e2SBrian Feldman 
5041e8db6e2SBrian Feldman 			debug3("Received SSH2_FXP_STATUS %d", status);
5051e8db6e2SBrian Feldman 
5061e8db6e2SBrian Feldman 			if (status == SSH2_FX_EOF) {
5071e8db6e2SBrian Feldman 				break;
5081e8db6e2SBrian Feldman 			} else {
5091e8db6e2SBrian Feldman 				error("Couldn't read directory: %s",
5101e8db6e2SBrian Feldman 				    fx2txt(status));
511ae1f160dSDag-Erling Smørgrav 				do_close(conn, handle, handle_len);
512d0c8c0bcSDag-Erling Smørgrav 				xfree(handle);
513*462c32cbSDag-Erling Smørgrav 				buffer_free(&msg);
5141e8db6e2SBrian Feldman 				return(status);
5151e8db6e2SBrian Feldman 			}
5161e8db6e2SBrian Feldman 		} else if (type != SSH2_FXP_NAME)
517ee21a45fSDag-Erling Smørgrav 			fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
5181e8db6e2SBrian Feldman 			    SSH2_FXP_NAME, type);
5191e8db6e2SBrian Feldman 
5201e8db6e2SBrian Feldman 		count = buffer_get_int(&msg);
5211e8db6e2SBrian Feldman 		if (count == 0)
5221e8db6e2SBrian Feldman 			break;
5231e8db6e2SBrian Feldman 		debug3("Received %d SSH2_FXP_NAME responses", count);
5241e8db6e2SBrian Feldman 		for (i = 0; i < count; i++) {
5251e8db6e2SBrian Feldman 			char *filename, *longname;
5261e8db6e2SBrian Feldman 			Attrib *a;
5271e8db6e2SBrian Feldman 
5281e8db6e2SBrian Feldman 			filename = buffer_get_string(&msg, NULL);
5291e8db6e2SBrian Feldman 			longname = buffer_get_string(&msg, NULL);
5301e8db6e2SBrian Feldman 			a = decode_attrib(&msg);
5311e8db6e2SBrian Feldman 
5321e8db6e2SBrian Feldman 			if (printflag)
5331e8db6e2SBrian Feldman 				printf("%s\n", longname);
5341e8db6e2SBrian Feldman 
535b15c8340SDag-Erling Smørgrav 			/*
536b15c8340SDag-Erling Smørgrav 			 * Directory entries should never contain '/'
537b15c8340SDag-Erling Smørgrav 			 * These can be used to attack recursive ops
538b15c8340SDag-Erling Smørgrav 			 * (e.g. send '../../../../etc/passwd')
539b15c8340SDag-Erling Smørgrav 			 */
540b15c8340SDag-Erling Smørgrav 			if (strchr(filename, '/') != NULL) {
541b15c8340SDag-Erling Smørgrav 				error("Server sent suspect path \"%s\" "
542b15c8340SDag-Erling Smørgrav 				    "during readdir of \"%s\"", filename, path);
543b15c8340SDag-Erling Smørgrav 				goto next;
544b15c8340SDag-Erling Smørgrav 			}
545b15c8340SDag-Erling Smørgrav 
5461e8db6e2SBrian Feldman 			if (dir) {
547761efaa7SDag-Erling Smørgrav 				*dir = xrealloc(*dir, ents + 2, sizeof(**dir));
5481e8db6e2SBrian Feldman 				(*dir)[ents] = xmalloc(sizeof(***dir));
5491e8db6e2SBrian Feldman 				(*dir)[ents]->filename = xstrdup(filename);
5501e8db6e2SBrian Feldman 				(*dir)[ents]->longname = xstrdup(longname);
5511e8db6e2SBrian Feldman 				memcpy(&(*dir)[ents]->a, a, sizeof(*a));
5521e8db6e2SBrian Feldman 				(*dir)[++ents] = NULL;
5531e8db6e2SBrian Feldman 			}
554b15c8340SDag-Erling Smørgrav  next:
5551e8db6e2SBrian Feldman 			xfree(filename);
5561e8db6e2SBrian Feldman 			xfree(longname);
5571e8db6e2SBrian Feldman 		}
5581e8db6e2SBrian Feldman 	}
5591e8db6e2SBrian Feldman 
5601e8db6e2SBrian Feldman 	buffer_free(&msg);
561ae1f160dSDag-Erling Smørgrav 	do_close(conn, handle, handle_len);
5621e8db6e2SBrian Feldman 	xfree(handle);
5631e8db6e2SBrian Feldman 
564d74d50a8SDag-Erling Smørgrav 	/* Don't return partial matches on interrupt */
565d74d50a8SDag-Erling Smørgrav 	if (interrupted && dir != NULL && *dir != NULL) {
566d74d50a8SDag-Erling Smørgrav 		free_sftp_dirents(*dir);
567d74d50a8SDag-Erling Smørgrav 		*dir = xmalloc(sizeof(**dir));
568d74d50a8SDag-Erling Smørgrav 		**dir = NULL;
569d74d50a8SDag-Erling Smørgrav 	}
570d74d50a8SDag-Erling Smørgrav 
5714a421b63SDag-Erling Smørgrav 	return 0;
5721e8db6e2SBrian Feldman }
5731e8db6e2SBrian Feldman 
5741e8db6e2SBrian Feldman int
575ae1f160dSDag-Erling Smørgrav do_readdir(struct sftp_conn *conn, char *path, SFTP_DIRENT ***dir)
5761e8db6e2SBrian Feldman {
577ae1f160dSDag-Erling Smørgrav 	return(do_lsreaddir(conn, path, 0, dir));
5781e8db6e2SBrian Feldman }
5791e8db6e2SBrian Feldman 
5801e8db6e2SBrian Feldman void free_sftp_dirents(SFTP_DIRENT **s)
5811e8db6e2SBrian Feldman {
5821e8db6e2SBrian Feldman 	int i;
5831e8db6e2SBrian Feldman 
5841e8db6e2SBrian Feldman 	for (i = 0; s[i]; i++) {
5851e8db6e2SBrian Feldman 		xfree(s[i]->filename);
5861e8db6e2SBrian Feldman 		xfree(s[i]->longname);
5871e8db6e2SBrian Feldman 		xfree(s[i]);
5881e8db6e2SBrian Feldman 	}
5891e8db6e2SBrian Feldman 	xfree(s);
5901e8db6e2SBrian Feldman }
5911e8db6e2SBrian Feldman 
5921e8db6e2SBrian Feldman int
593ae1f160dSDag-Erling Smørgrav do_rm(struct sftp_conn *conn, char *path)
5941e8db6e2SBrian Feldman {
5951e8db6e2SBrian Feldman 	u_int status, id;
5961e8db6e2SBrian Feldman 
5971e8db6e2SBrian Feldman 	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
5981e8db6e2SBrian Feldman 
599ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6004a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
6014a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
6021e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
6031e8db6e2SBrian Feldman 		error("Couldn't delete file: %s", fx2txt(status));
6041e8db6e2SBrian Feldman 	return(status);
6051e8db6e2SBrian Feldman }
6061e8db6e2SBrian Feldman 
6071e8db6e2SBrian Feldman int
608b15c8340SDag-Erling Smørgrav do_mkdir(struct sftp_conn *conn, char *path, Attrib *a, int printflag)
6091e8db6e2SBrian Feldman {
6101e8db6e2SBrian Feldman 	u_int status, id;
6111e8db6e2SBrian Feldman 
612ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6134a421b63SDag-Erling Smørgrav 	send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
6141e8db6e2SBrian Feldman 	    strlen(path), a);
6151e8db6e2SBrian Feldman 
6164a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
617b15c8340SDag-Erling Smørgrav 	if (status != SSH2_FX_OK && printflag)
6181e8db6e2SBrian Feldman 		error("Couldn't create directory: %s", fx2txt(status));
6191e8db6e2SBrian Feldman 
6201e8db6e2SBrian Feldman 	return(status);
6211e8db6e2SBrian Feldman }
6221e8db6e2SBrian Feldman 
6231e8db6e2SBrian Feldman int
624ae1f160dSDag-Erling Smørgrav do_rmdir(struct sftp_conn *conn, char *path)
6251e8db6e2SBrian Feldman {
6261e8db6e2SBrian Feldman 	u_int status, id;
6271e8db6e2SBrian Feldman 
628ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6294a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_RMDIR, path,
630ae1f160dSDag-Erling Smørgrav 	    strlen(path));
6311e8db6e2SBrian Feldman 
6324a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
6331e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
6341e8db6e2SBrian Feldman 		error("Couldn't remove directory: %s", fx2txt(status));
6351e8db6e2SBrian Feldman 
6361e8db6e2SBrian Feldman 	return(status);
6371e8db6e2SBrian Feldman }
6381e8db6e2SBrian Feldman 
6391e8db6e2SBrian Feldman Attrib *
640ae1f160dSDag-Erling Smørgrav do_stat(struct sftp_conn *conn, char *path, int quiet)
6411e8db6e2SBrian Feldman {
6421e8db6e2SBrian Feldman 	u_int id;
6431e8db6e2SBrian Feldman 
644ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
645ae1f160dSDag-Erling Smørgrav 
6464a421b63SDag-Erling Smørgrav 	send_string_request(conn, id,
647ae1f160dSDag-Erling Smørgrav 	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
648ae1f160dSDag-Erling Smørgrav 	    path, strlen(path));
649ae1f160dSDag-Erling Smørgrav 
6504a421b63SDag-Erling Smørgrav 	return(get_decode_stat(conn, id, quiet));
6511e8db6e2SBrian Feldman }
6521e8db6e2SBrian Feldman 
6531e8db6e2SBrian Feldman Attrib *
654ae1f160dSDag-Erling Smørgrav do_lstat(struct sftp_conn *conn, char *path, int quiet)
6551e8db6e2SBrian Feldman {
6561e8db6e2SBrian Feldman 	u_int id;
6571e8db6e2SBrian Feldman 
658ae1f160dSDag-Erling Smørgrav 	if (conn->version == 0) {
659ae1f160dSDag-Erling Smørgrav 		if (quiet)
660ae1f160dSDag-Erling Smørgrav 			debug("Server version does not support lstat operation");
661ae1f160dSDag-Erling Smørgrav 		else
662d95e11bfSDag-Erling Smørgrav 			logit("Server version does not support lstat operation");
663545d5ecaSDag-Erling Smørgrav 		return(do_stat(conn, path, quiet));
664ae1f160dSDag-Erling Smørgrav 	}
665ae1f160dSDag-Erling Smørgrav 
666ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6674a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_LSTAT, path,
668ae1f160dSDag-Erling Smørgrav 	    strlen(path));
669ae1f160dSDag-Erling Smørgrav 
6704a421b63SDag-Erling Smørgrav 	return(get_decode_stat(conn, id, quiet));
6711e8db6e2SBrian Feldman }
6721e8db6e2SBrian Feldman 
673d4af9e69SDag-Erling Smørgrav #ifdef notyet
6741e8db6e2SBrian Feldman Attrib *
675ae1f160dSDag-Erling Smørgrav do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet)
6761e8db6e2SBrian Feldman {
6771e8db6e2SBrian Feldman 	u_int id;
6781e8db6e2SBrian Feldman 
679ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6804a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
681ae1f160dSDag-Erling Smørgrav 	    handle_len);
682ae1f160dSDag-Erling Smørgrav 
6834a421b63SDag-Erling Smørgrav 	return(get_decode_stat(conn, id, quiet));
6841e8db6e2SBrian Feldman }
685d4af9e69SDag-Erling Smørgrav #endif
6861e8db6e2SBrian Feldman 
6871e8db6e2SBrian Feldman int
688ae1f160dSDag-Erling Smørgrav do_setstat(struct sftp_conn *conn, char *path, Attrib *a)
6891e8db6e2SBrian Feldman {
6901e8db6e2SBrian Feldman 	u_int status, id;
6911e8db6e2SBrian Feldman 
692ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6934a421b63SDag-Erling Smørgrav 	send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
6941e8db6e2SBrian Feldman 	    strlen(path), a);
6951e8db6e2SBrian Feldman 
6964a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
6971e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
6981e8db6e2SBrian Feldman 		error("Couldn't setstat on \"%s\": %s", path,
6991e8db6e2SBrian Feldman 		    fx2txt(status));
7001e8db6e2SBrian Feldman 
7011e8db6e2SBrian Feldman 	return(status);
7021e8db6e2SBrian Feldman }
7031e8db6e2SBrian Feldman 
7041e8db6e2SBrian Feldman int
705ae1f160dSDag-Erling Smørgrav do_fsetstat(struct sftp_conn *conn, char *handle, u_int handle_len,
7061e8db6e2SBrian Feldman     Attrib *a)
7071e8db6e2SBrian Feldman {
7081e8db6e2SBrian Feldman 	u_int status, id;
7091e8db6e2SBrian Feldman 
710ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
7114a421b63SDag-Erling Smørgrav 	send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
7121e8db6e2SBrian Feldman 	    handle_len, a);
7131e8db6e2SBrian Feldman 
7144a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
7151e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
7161e8db6e2SBrian Feldman 		error("Couldn't fsetstat: %s", fx2txt(status));
7171e8db6e2SBrian Feldman 
7181e8db6e2SBrian Feldman 	return(status);
7191e8db6e2SBrian Feldman }
7201e8db6e2SBrian Feldman 
7211e8db6e2SBrian Feldman char *
722ae1f160dSDag-Erling Smørgrav do_realpath(struct sftp_conn *conn, char *path)
7231e8db6e2SBrian Feldman {
7241e8db6e2SBrian Feldman 	Buffer msg;
7251e8db6e2SBrian Feldman 	u_int type, expected_id, count, id;
7261e8db6e2SBrian Feldman 	char *filename, *longname;
7271e8db6e2SBrian Feldman 	Attrib *a;
7281e8db6e2SBrian Feldman 
729ae1f160dSDag-Erling Smørgrav 	expected_id = id = conn->msg_id++;
7304a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_REALPATH, path,
731ae1f160dSDag-Erling Smørgrav 	    strlen(path));
7321e8db6e2SBrian Feldman 
7331e8db6e2SBrian Feldman 	buffer_init(&msg);
7341e8db6e2SBrian Feldman 
7354a421b63SDag-Erling Smørgrav 	get_msg(conn, &msg);
7361e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
7371e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
7381e8db6e2SBrian Feldman 
7391e8db6e2SBrian Feldman 	if (id != expected_id)
740ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
7411e8db6e2SBrian Feldman 
7421e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
7431e8db6e2SBrian Feldman 		u_int status = buffer_get_int(&msg);
7441e8db6e2SBrian Feldman 
7451e8db6e2SBrian Feldman 		error("Couldn't canonicalise: %s", fx2txt(status));
746e2f6069cSDag-Erling Smørgrav 		buffer_free(&msg);
747e2f6069cSDag-Erling Smørgrav 		return NULL;
7481e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_NAME)
749ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
7501e8db6e2SBrian Feldman 		    SSH2_FXP_NAME, type);
7511e8db6e2SBrian Feldman 
7521e8db6e2SBrian Feldman 	count = buffer_get_int(&msg);
7531e8db6e2SBrian Feldman 	if (count != 1)
7541e8db6e2SBrian Feldman 		fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
7551e8db6e2SBrian Feldman 
7561e8db6e2SBrian Feldman 	filename = buffer_get_string(&msg, NULL);
7571e8db6e2SBrian Feldman 	longname = buffer_get_string(&msg, NULL);
7581e8db6e2SBrian Feldman 	a = decode_attrib(&msg);
7591e8db6e2SBrian Feldman 
760*462c32cbSDag-Erling Smørgrav 	debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename,
761*462c32cbSDag-Erling Smørgrav 	    (unsigned long)a->size);
7621e8db6e2SBrian Feldman 
7631e8db6e2SBrian Feldman 	xfree(longname);
7641e8db6e2SBrian Feldman 
7651e8db6e2SBrian Feldman 	buffer_free(&msg);
7661e8db6e2SBrian Feldman 
7671e8db6e2SBrian Feldman 	return(filename);
7681e8db6e2SBrian Feldman }
7691e8db6e2SBrian Feldman 
7701e8db6e2SBrian Feldman int
771ae1f160dSDag-Erling Smørgrav do_rename(struct sftp_conn *conn, char *oldpath, char *newpath)
7721e8db6e2SBrian Feldman {
7731e8db6e2SBrian Feldman 	Buffer msg;
7741e8db6e2SBrian Feldman 	u_int status, id;
7751e8db6e2SBrian Feldman 
7761e8db6e2SBrian Feldman 	buffer_init(&msg);
7771e8db6e2SBrian Feldman 
7781e8db6e2SBrian Feldman 	/* Send rename request */
779ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
780d4af9e69SDag-Erling Smørgrav 	if ((conn->exts & SFTP_EXT_POSIX_RENAME)) {
781d4af9e69SDag-Erling Smørgrav 		buffer_put_char(&msg, SSH2_FXP_EXTENDED);
782d4af9e69SDag-Erling Smørgrav 		buffer_put_int(&msg, id);
783d4af9e69SDag-Erling Smørgrav 		buffer_put_cstring(&msg, "posix-rename@openssh.com");
784d4af9e69SDag-Erling Smørgrav 	} else {
7851e8db6e2SBrian Feldman 		buffer_put_char(&msg, SSH2_FXP_RENAME);
7861e8db6e2SBrian Feldman 		buffer_put_int(&msg, id);
787d4af9e69SDag-Erling Smørgrav 	}
7881e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, oldpath);
7891e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, newpath);
7904a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
791d4af9e69SDag-Erling Smørgrav 	debug3("Sent message %s \"%s\" -> \"%s\"",
792d4af9e69SDag-Erling Smørgrav 	    (conn->exts & SFTP_EXT_POSIX_RENAME) ? "posix-rename@openssh.com" :
793d4af9e69SDag-Erling Smørgrav 	    "SSH2_FXP_RENAME", oldpath, newpath);
7941e8db6e2SBrian Feldman 	buffer_free(&msg);
7951e8db6e2SBrian Feldman 
7964a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
7971e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
798ae1f160dSDag-Erling Smørgrav 		error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath,
799ae1f160dSDag-Erling Smørgrav 		    newpath, fx2txt(status));
8001e8db6e2SBrian Feldman 
8011e8db6e2SBrian Feldman 	return(status);
8021e8db6e2SBrian Feldman }
8031e8db6e2SBrian Feldman 
8041e8db6e2SBrian Feldman int
8054a421b63SDag-Erling Smørgrav do_hardlink(struct sftp_conn *conn, char *oldpath, char *newpath)
8064a421b63SDag-Erling Smørgrav {
8074a421b63SDag-Erling Smørgrav 	Buffer msg;
8084a421b63SDag-Erling Smørgrav 	u_int status, id;
8094a421b63SDag-Erling Smørgrav 
8104a421b63SDag-Erling Smørgrav 	if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
8114a421b63SDag-Erling Smørgrav 		error("Server does not support hardlink@openssh.com extension");
8124a421b63SDag-Erling Smørgrav 		return -1;
8134a421b63SDag-Erling Smørgrav 	}
8144a421b63SDag-Erling Smørgrav 
815*462c32cbSDag-Erling Smørgrav 	buffer_init(&msg);
816*462c32cbSDag-Erling Smørgrav 
817*462c32cbSDag-Erling Smørgrav 	/* Send link request */
818*462c32cbSDag-Erling Smørgrav 	id = conn->msg_id++;
8194a421b63SDag-Erling Smørgrav 	buffer_put_char(&msg, SSH2_FXP_EXTENDED);
8204a421b63SDag-Erling Smørgrav 	buffer_put_int(&msg, id);
8214a421b63SDag-Erling Smørgrav 	buffer_put_cstring(&msg, "hardlink@openssh.com");
8224a421b63SDag-Erling Smørgrav 	buffer_put_cstring(&msg, oldpath);
8234a421b63SDag-Erling Smørgrav 	buffer_put_cstring(&msg, newpath);
8244a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
8254a421b63SDag-Erling Smørgrav 	debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
8264a421b63SDag-Erling Smørgrav 	       oldpath, newpath);
8274a421b63SDag-Erling Smørgrav 	buffer_free(&msg);
8284a421b63SDag-Erling Smørgrav 
8294a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
8304a421b63SDag-Erling Smørgrav 	if (status != SSH2_FX_OK)
8314a421b63SDag-Erling Smørgrav 		error("Couldn't link file \"%s\" to \"%s\": %s", oldpath,
8324a421b63SDag-Erling Smørgrav 		    newpath, fx2txt(status));
8334a421b63SDag-Erling Smørgrav 
8344a421b63SDag-Erling Smørgrav 	return(status);
8354a421b63SDag-Erling Smørgrav }
8364a421b63SDag-Erling Smørgrav 
8374a421b63SDag-Erling Smørgrav int
838ae1f160dSDag-Erling Smørgrav do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath)
8391e8db6e2SBrian Feldman {
8401e8db6e2SBrian Feldman 	Buffer msg;
8411e8db6e2SBrian Feldman 	u_int status, id;
8421e8db6e2SBrian Feldman 
843ae1f160dSDag-Erling Smørgrav 	if (conn->version < 3) {
844ae1f160dSDag-Erling Smørgrav 		error("This server does not support the symlink operation");
845ae1f160dSDag-Erling Smørgrav 		return(SSH2_FX_OP_UNSUPPORTED);
846ae1f160dSDag-Erling Smørgrav 	}
847ae1f160dSDag-Erling Smørgrav 
8481e8db6e2SBrian Feldman 	buffer_init(&msg);
8491e8db6e2SBrian Feldman 
850d74d50a8SDag-Erling Smørgrav 	/* Send symlink request */
851ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
8521e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_SYMLINK);
8531e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
8541e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, oldpath);
8551e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, newpath);
8564a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
8571e8db6e2SBrian Feldman 	debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
8581e8db6e2SBrian Feldman 	    newpath);
8591e8db6e2SBrian Feldman 	buffer_free(&msg);
8601e8db6e2SBrian Feldman 
8614a421b63SDag-Erling Smørgrav 	status = get_status(conn, id);
8621e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
863d0c8c0bcSDag-Erling Smørgrav 		error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath,
864ae1f160dSDag-Erling Smørgrav 		    newpath, fx2txt(status));
8651e8db6e2SBrian Feldman 
8661e8db6e2SBrian Feldman 	return(status);
8671e8db6e2SBrian Feldman }
8681e8db6e2SBrian Feldman 
869d4af9e69SDag-Erling Smørgrav #ifdef notyet
8701e8db6e2SBrian Feldman char *
871ae1f160dSDag-Erling Smørgrav do_readlink(struct sftp_conn *conn, char *path)
8721e8db6e2SBrian Feldman {
8731e8db6e2SBrian Feldman 	Buffer msg;
8741e8db6e2SBrian Feldman 	u_int type, expected_id, count, id;
8751e8db6e2SBrian Feldman 	char *filename, *longname;
8761e8db6e2SBrian Feldman 	Attrib *a;
8771e8db6e2SBrian Feldman 
878ae1f160dSDag-Erling Smørgrav 	expected_id = id = conn->msg_id++;
8794a421b63SDag-Erling Smørgrav 	send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
8801e8db6e2SBrian Feldman 
8811e8db6e2SBrian Feldman 	buffer_init(&msg);
8821e8db6e2SBrian Feldman 
8834a421b63SDag-Erling Smørgrav 	get_msg(conn, &msg);
8841e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
8851e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
8861e8db6e2SBrian Feldman 
8871e8db6e2SBrian Feldman 	if (id != expected_id)
888ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
8891e8db6e2SBrian Feldman 
8901e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
8911e8db6e2SBrian Feldman 		u_int status = buffer_get_int(&msg);
8921e8db6e2SBrian Feldman 
8931e8db6e2SBrian Feldman 		error("Couldn't readlink: %s", fx2txt(status));
894*462c32cbSDag-Erling Smørgrav 		buffer_free(&msg);
8951e8db6e2SBrian Feldman 		return(NULL);
8961e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_NAME)
897ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
8981e8db6e2SBrian Feldman 		    SSH2_FXP_NAME, type);
8991e8db6e2SBrian Feldman 
9001e8db6e2SBrian Feldman 	count = buffer_get_int(&msg);
9011e8db6e2SBrian Feldman 	if (count != 1)
9021e8db6e2SBrian Feldman 		fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
9031e8db6e2SBrian Feldman 
9041e8db6e2SBrian Feldman 	filename = buffer_get_string(&msg, NULL);
9051e8db6e2SBrian Feldman 	longname = buffer_get_string(&msg, NULL);
9061e8db6e2SBrian Feldman 	a = decode_attrib(&msg);
9071e8db6e2SBrian Feldman 
9081e8db6e2SBrian Feldman 	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
9091e8db6e2SBrian Feldman 
9101e8db6e2SBrian Feldman 	xfree(longname);
9111e8db6e2SBrian Feldman 
9121e8db6e2SBrian Feldman 	buffer_free(&msg);
9131e8db6e2SBrian Feldman 
9141e8db6e2SBrian Feldman 	return(filename);
9151e8db6e2SBrian Feldman }
916d4af9e69SDag-Erling Smørgrav #endif
917d4af9e69SDag-Erling Smørgrav 
918d4af9e69SDag-Erling Smørgrav int
919d4af9e69SDag-Erling Smørgrav do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
920d4af9e69SDag-Erling Smørgrav     int quiet)
921d4af9e69SDag-Erling Smørgrav {
922d4af9e69SDag-Erling Smørgrav 	Buffer msg;
923d4af9e69SDag-Erling Smørgrav 	u_int id;
924d4af9e69SDag-Erling Smørgrav 
925d4af9e69SDag-Erling Smørgrav 	if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
926d4af9e69SDag-Erling Smørgrav 		error("Server does not support statvfs@openssh.com extension");
927d4af9e69SDag-Erling Smørgrav 		return -1;
928d4af9e69SDag-Erling Smørgrav 	}
929d4af9e69SDag-Erling Smørgrav 
930d4af9e69SDag-Erling Smørgrav 	id = conn->msg_id++;
931d4af9e69SDag-Erling Smørgrav 
932d4af9e69SDag-Erling Smørgrav 	buffer_init(&msg);
933d4af9e69SDag-Erling Smørgrav 	buffer_clear(&msg);
934d4af9e69SDag-Erling Smørgrav 	buffer_put_char(&msg, SSH2_FXP_EXTENDED);
935d4af9e69SDag-Erling Smørgrav 	buffer_put_int(&msg, id);
936d4af9e69SDag-Erling Smørgrav 	buffer_put_cstring(&msg, "statvfs@openssh.com");
937d4af9e69SDag-Erling Smørgrav 	buffer_put_cstring(&msg, path);
9384a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
939d4af9e69SDag-Erling Smørgrav 	buffer_free(&msg);
940d4af9e69SDag-Erling Smørgrav 
9414a421b63SDag-Erling Smørgrav 	return get_decode_statvfs(conn, st, id, quiet);
942d4af9e69SDag-Erling Smørgrav }
943d4af9e69SDag-Erling Smørgrav 
944d4af9e69SDag-Erling Smørgrav #ifdef notyet
945d4af9e69SDag-Erling Smørgrav int
946d4af9e69SDag-Erling Smørgrav do_fstatvfs(struct sftp_conn *conn, const char *handle, u_int handle_len,
947d4af9e69SDag-Erling Smørgrav     struct sftp_statvfs *st, int quiet)
948d4af9e69SDag-Erling Smørgrav {
949d4af9e69SDag-Erling Smørgrav 	Buffer msg;
950d4af9e69SDag-Erling Smørgrav 	u_int id;
951d4af9e69SDag-Erling Smørgrav 
952d4af9e69SDag-Erling Smørgrav 	if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
953d4af9e69SDag-Erling Smørgrav 		error("Server does not support fstatvfs@openssh.com extension");
954d4af9e69SDag-Erling Smørgrav 		return -1;
955d4af9e69SDag-Erling Smørgrav 	}
956d4af9e69SDag-Erling Smørgrav 
957d4af9e69SDag-Erling Smørgrav 	id = conn->msg_id++;
958d4af9e69SDag-Erling Smørgrav 
959d4af9e69SDag-Erling Smørgrav 	buffer_init(&msg);
960d4af9e69SDag-Erling Smørgrav 	buffer_clear(&msg);
961d4af9e69SDag-Erling Smørgrav 	buffer_put_char(&msg, SSH2_FXP_EXTENDED);
962d4af9e69SDag-Erling Smørgrav 	buffer_put_int(&msg, id);
963d4af9e69SDag-Erling Smørgrav 	buffer_put_cstring(&msg, "fstatvfs@openssh.com");
964d4af9e69SDag-Erling Smørgrav 	buffer_put_string(&msg, handle, handle_len);
9654a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
966d4af9e69SDag-Erling Smørgrav 	buffer_free(&msg);
967d4af9e69SDag-Erling Smørgrav 
9684a421b63SDag-Erling Smørgrav 	return get_decode_statvfs(conn, st, id, quiet);
969d4af9e69SDag-Erling Smørgrav }
970d4af9e69SDag-Erling Smørgrav #endif
9711e8db6e2SBrian Feldman 
972ae1f160dSDag-Erling Smørgrav static void
9734a421b63SDag-Erling Smørgrav send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
9744a421b63SDag-Erling Smørgrav     u_int len, char *handle, u_int handle_len)
975ae1f160dSDag-Erling Smørgrav {
976ae1f160dSDag-Erling Smørgrav 	Buffer msg;
977ae1f160dSDag-Erling Smørgrav 
978ae1f160dSDag-Erling Smørgrav 	buffer_init(&msg);
979ae1f160dSDag-Erling Smørgrav 	buffer_clear(&msg);
980ae1f160dSDag-Erling Smørgrav 	buffer_put_char(&msg, SSH2_FXP_READ);
981ae1f160dSDag-Erling Smørgrav 	buffer_put_int(&msg, id);
982ae1f160dSDag-Erling Smørgrav 	buffer_put_string(&msg, handle, handle_len);
983ae1f160dSDag-Erling Smørgrav 	buffer_put_int64(&msg, offset);
984ae1f160dSDag-Erling Smørgrav 	buffer_put_int(&msg, len);
9854a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
986ae1f160dSDag-Erling Smørgrav 	buffer_free(&msg);
987ae1f160dSDag-Erling Smørgrav }
988ae1f160dSDag-Erling Smørgrav 
9891e8db6e2SBrian Feldman int
990ae1f160dSDag-Erling Smørgrav do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
991b15c8340SDag-Erling Smørgrav     Attrib *a, int pflag)
9921e8db6e2SBrian Feldman {
993b15c8340SDag-Erling Smørgrav 	Attrib junk;
994ae1f160dSDag-Erling Smørgrav 	Buffer msg;
995ae1f160dSDag-Erling Smørgrav 	char *handle;
996043840dfSDag-Erling Smørgrav 	int local_fd, status = 0, write_error;
997ae1f160dSDag-Erling Smørgrav 	int read_error, write_errno;
998ae1f160dSDag-Erling Smørgrav 	u_int64_t offset, size;
999043840dfSDag-Erling Smørgrav 	u_int handle_len, mode, type, id, buflen, num_req, max_req;
1000d0c8c0bcSDag-Erling Smørgrav 	off_t progress_counter;
1001ae1f160dSDag-Erling Smørgrav 	struct request {
1002ae1f160dSDag-Erling Smørgrav 		u_int id;
1003ae1f160dSDag-Erling Smørgrav 		u_int len;
1004ae1f160dSDag-Erling Smørgrav 		u_int64_t offset;
1005ae1f160dSDag-Erling Smørgrav 		TAILQ_ENTRY(request) tq;
1006ae1f160dSDag-Erling Smørgrav 	};
1007ae1f160dSDag-Erling Smørgrav 	TAILQ_HEAD(reqhead, request) requests;
1008ae1f160dSDag-Erling Smørgrav 	struct request *req;
10091e8db6e2SBrian Feldman 
1010ae1f160dSDag-Erling Smørgrav 	TAILQ_INIT(&requests);
1011ae1f160dSDag-Erling Smørgrav 
1012b15c8340SDag-Erling Smørgrav 	if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
1013b15c8340SDag-Erling Smørgrav 		return -1;
10141e8db6e2SBrian Feldman 
1015d4af9e69SDag-Erling Smørgrav 	/* Do not preserve set[ug]id here, as we do not preserve ownership */
10161e8db6e2SBrian Feldman 	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1017d0c8c0bcSDag-Erling Smørgrav 		mode = a->perm & 0777;
10181e8db6e2SBrian Feldman 	else
10191e8db6e2SBrian Feldman 		mode = 0666;
10201e8db6e2SBrian Feldman 
10211e8db6e2SBrian Feldman 	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
1022d0c8c0bcSDag-Erling Smørgrav 	    (!S_ISREG(a->perm))) {
1023d0c8c0bcSDag-Erling Smørgrav 		error("Cannot download non-regular file: %s", remote_path);
10241e8db6e2SBrian Feldman 		return(-1);
10251e8db6e2SBrian Feldman 	}
10261e8db6e2SBrian Feldman 
1027ae1f160dSDag-Erling Smørgrav 	if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
1028ae1f160dSDag-Erling Smørgrav 		size = a->size;
1029ae1f160dSDag-Erling Smørgrav 	else
1030ae1f160dSDag-Erling Smørgrav 		size = 0;
10311e8db6e2SBrian Feldman 
1032ae1f160dSDag-Erling Smørgrav 	buflen = conn->transfer_buflen;
10331e8db6e2SBrian Feldman 	buffer_init(&msg);
10341e8db6e2SBrian Feldman 
10351e8db6e2SBrian Feldman 	/* Send open request */
1036ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
10371e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_OPEN);
10381e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
10391e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, remote_path);
10401e8db6e2SBrian Feldman 	buffer_put_int(&msg, SSH2_FXF_READ);
10411e8db6e2SBrian Feldman 	attrib_clear(&junk); /* Send empty attributes */
10421e8db6e2SBrian Feldman 	encode_attrib(&msg, &junk);
10434a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
1044ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
10451e8db6e2SBrian Feldman 
10464a421b63SDag-Erling Smørgrav 	handle = get_handle(conn, id, &handle_len,
1047b15c8340SDag-Erling Smørgrav 	    "remote open(\"%s\")", remote_path);
10481e8db6e2SBrian Feldman 	if (handle == NULL) {
10491e8db6e2SBrian Feldman 		buffer_free(&msg);
1050ae1f160dSDag-Erling Smørgrav 		return(-1);
1051ae1f160dSDag-Erling Smørgrav 	}
1052ae1f160dSDag-Erling Smørgrav 
1053d0c8c0bcSDag-Erling Smørgrav 	local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC,
1054d0c8c0bcSDag-Erling Smørgrav 	    mode | S_IWRITE);
1055ae1f160dSDag-Erling Smørgrav 	if (local_fd == -1) {
1056ae1f160dSDag-Erling Smørgrav 		error("Couldn't open local file \"%s\" for writing: %s",
1057ae1f160dSDag-Erling Smørgrav 		    local_path, strerror(errno));
1058d4af9e69SDag-Erling Smørgrav 		do_close(conn, handle, handle_len);
1059ae1f160dSDag-Erling Smørgrav 		buffer_free(&msg);
1060ae1f160dSDag-Erling Smørgrav 		xfree(handle);
10611e8db6e2SBrian Feldman 		return(-1);
10621e8db6e2SBrian Feldman 	}
10631e8db6e2SBrian Feldman 
10641e8db6e2SBrian Feldman 	/* Read from remote and write to local */
1065ae1f160dSDag-Erling Smørgrav 	write_error = read_error = write_errno = num_req = offset = 0;
1066ae1f160dSDag-Erling Smørgrav 	max_req = 1;
1067d0c8c0bcSDag-Erling Smørgrav 	progress_counter = 0;
1068d0c8c0bcSDag-Erling Smørgrav 
106952028650SDag-Erling Smørgrav 	if (showprogress && size != 0)
107052028650SDag-Erling Smørgrav 		start_progress_meter(remote_path, size, &progress_counter);
1071d0c8c0bcSDag-Erling Smørgrav 
1072ae1f160dSDag-Erling Smørgrav 	while (num_req > 0 || max_req > 0) {
10731e8db6e2SBrian Feldman 		char *data;
1074ae1f160dSDag-Erling Smørgrav 		u_int len;
10751e8db6e2SBrian Feldman 
1076d74d50a8SDag-Erling Smørgrav 		/*
1077d74d50a8SDag-Erling Smørgrav 		 * Simulate EOF on interrupt: stop sending new requests and
1078d74d50a8SDag-Erling Smørgrav 		 * allow outstanding requests to drain gracefully
1079d74d50a8SDag-Erling Smørgrav 		 */
1080d74d50a8SDag-Erling Smørgrav 		if (interrupted) {
1081d74d50a8SDag-Erling Smørgrav 			if (num_req == 0) /* If we haven't started yet... */
1082d74d50a8SDag-Erling Smørgrav 				break;
1083d74d50a8SDag-Erling Smørgrav 			max_req = 0;
1084d74d50a8SDag-Erling Smørgrav 		}
1085d74d50a8SDag-Erling Smørgrav 
1086ae1f160dSDag-Erling Smørgrav 		/* Send some more requests */
1087ae1f160dSDag-Erling Smørgrav 		while (num_req < max_req) {
1088ae1f160dSDag-Erling Smørgrav 			debug3("Request range %llu -> %llu (%d/%d)",
1089545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)offset,
1090545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)offset + buflen - 1,
1091545d5ecaSDag-Erling Smørgrav 			    num_req, max_req);
1092ae1f160dSDag-Erling Smørgrav 			req = xmalloc(sizeof(*req));
1093ae1f160dSDag-Erling Smørgrav 			req->id = conn->msg_id++;
1094ae1f160dSDag-Erling Smørgrav 			req->len = buflen;
1095ae1f160dSDag-Erling Smørgrav 			req->offset = offset;
1096ae1f160dSDag-Erling Smørgrav 			offset += buflen;
1097ae1f160dSDag-Erling Smørgrav 			num_req++;
1098ae1f160dSDag-Erling Smørgrav 			TAILQ_INSERT_TAIL(&requests, req, tq);
10994a421b63SDag-Erling Smørgrav 			send_read_request(conn, req->id, req->offset,
1100ae1f160dSDag-Erling Smørgrav 			    req->len, handle, handle_len);
1101ae1f160dSDag-Erling Smørgrav 		}
11021e8db6e2SBrian Feldman 
11031e8db6e2SBrian Feldman 		buffer_clear(&msg);
11044a421b63SDag-Erling Smørgrav 		get_msg(conn, &msg);
11051e8db6e2SBrian Feldman 		type = buffer_get_char(&msg);
11061e8db6e2SBrian Feldman 		id = buffer_get_int(&msg);
1107ee21a45fSDag-Erling Smørgrav 		debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
11081e8db6e2SBrian Feldman 
1109ae1f160dSDag-Erling Smørgrav 		/* Find the request in our queue */
1110ae1f160dSDag-Erling Smørgrav 		for (req = TAILQ_FIRST(&requests);
1111ae1f160dSDag-Erling Smørgrav 		    req != NULL && req->id != id;
1112ae1f160dSDag-Erling Smørgrav 		    req = TAILQ_NEXT(req, tq))
1113ae1f160dSDag-Erling Smørgrav 			;
1114ae1f160dSDag-Erling Smørgrav 		if (req == NULL)
1115ae1f160dSDag-Erling Smørgrav 			fatal("Unexpected reply %u", id);
1116ae1f160dSDag-Erling Smørgrav 
1117ae1f160dSDag-Erling Smørgrav 		switch (type) {
1118ae1f160dSDag-Erling Smørgrav 		case SSH2_FXP_STATUS:
1119ae1f160dSDag-Erling Smørgrav 			status = buffer_get_int(&msg);
1120ae1f160dSDag-Erling Smørgrav 			if (status != SSH2_FX_EOF)
1121ae1f160dSDag-Erling Smørgrav 				read_error = 1;
1122ae1f160dSDag-Erling Smørgrav 			max_req = 0;
1123ae1f160dSDag-Erling Smørgrav 			TAILQ_REMOVE(&requests, req, tq);
1124ae1f160dSDag-Erling Smørgrav 			xfree(req);
1125ae1f160dSDag-Erling Smørgrav 			num_req--;
11261e8db6e2SBrian Feldman 			break;
1127ae1f160dSDag-Erling Smørgrav 		case SSH2_FXP_DATA:
1128ae1f160dSDag-Erling Smørgrav 			data = buffer_get_string(&msg, &len);
1129545d5ecaSDag-Erling Smørgrav 			debug3("Received data %llu -> %llu",
1130545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)req->offset,
1131545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)req->offset + len - 1);
1132ae1f160dSDag-Erling Smørgrav 			if (len > req->len)
1133ae1f160dSDag-Erling Smørgrav 				fatal("Received more data than asked for "
1134ee21a45fSDag-Erling Smørgrav 				    "%u > %u", len, req->len);
1135ae1f160dSDag-Erling Smørgrav 			if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
1136d95e11bfSDag-Erling Smørgrav 			    atomicio(vwrite, local_fd, data, len) != len) &&
1137ae1f160dSDag-Erling Smørgrav 			    !write_error) {
1138ae1f160dSDag-Erling Smørgrav 				write_errno = errno;
1139ae1f160dSDag-Erling Smørgrav 				write_error = 1;
1140ae1f160dSDag-Erling Smørgrav 				max_req = 0;
11411e8db6e2SBrian Feldman 			}
1142d0c8c0bcSDag-Erling Smørgrav 			progress_counter += len;
1143ae1f160dSDag-Erling Smørgrav 			xfree(data);
1144ae1f160dSDag-Erling Smørgrav 
1145ae1f160dSDag-Erling Smørgrav 			if (len == req->len) {
1146ae1f160dSDag-Erling Smørgrav 				TAILQ_REMOVE(&requests, req, tq);
1147ae1f160dSDag-Erling Smørgrav 				xfree(req);
1148ae1f160dSDag-Erling Smørgrav 				num_req--;
1149ae1f160dSDag-Erling Smørgrav 			} else {
1150ae1f160dSDag-Erling Smørgrav 				/* Resend the request for the missing data */
1151ae1f160dSDag-Erling Smørgrav 				debug3("Short data block, re-requesting "
1152545d5ecaSDag-Erling Smørgrav 				    "%llu -> %llu (%2d)",
1153545d5ecaSDag-Erling Smørgrav 				    (unsigned long long)req->offset + len,
1154545d5ecaSDag-Erling Smørgrav 				    (unsigned long long)req->offset +
1155545d5ecaSDag-Erling Smørgrav 				    req->len - 1, num_req);
1156ae1f160dSDag-Erling Smørgrav 				req->id = conn->msg_id++;
1157ae1f160dSDag-Erling Smørgrav 				req->len -= len;
1158ae1f160dSDag-Erling Smørgrav 				req->offset += len;
11594a421b63SDag-Erling Smørgrav 				send_read_request(conn, req->id,
1160ae1f160dSDag-Erling Smørgrav 				    req->offset, req->len, handle, handle_len);
1161ae1f160dSDag-Erling Smørgrav 				/* Reduce the request size */
1162ae1f160dSDag-Erling Smørgrav 				if (len < buflen)
1163ae1f160dSDag-Erling Smørgrav 					buflen = MAX(MIN_READ_SIZE, len);
1164ae1f160dSDag-Erling Smørgrav 			}
1165ae1f160dSDag-Erling Smørgrav 			if (max_req > 0) { /* max_req = 0 iff EOF received */
1166ae1f160dSDag-Erling Smørgrav 				if (size > 0 && offset > size) {
1167ae1f160dSDag-Erling Smørgrav 					/* Only one request at a time
1168ae1f160dSDag-Erling Smørgrav 					 * after the expected EOF */
1169ae1f160dSDag-Erling Smørgrav 					debug3("Finish at %llu (%2d)",
1170545d5ecaSDag-Erling Smørgrav 					    (unsigned long long)offset,
1171545d5ecaSDag-Erling Smørgrav 					    num_req);
1172ae1f160dSDag-Erling Smørgrav 					max_req = 1;
1173d74d50a8SDag-Erling Smørgrav 				} else if (max_req <= conn->num_requests) {
1174ae1f160dSDag-Erling Smørgrav 					++max_req;
1175ae1f160dSDag-Erling Smørgrav 				}
1176ae1f160dSDag-Erling Smørgrav 			}
1177ae1f160dSDag-Erling Smørgrav 			break;
1178ae1f160dSDag-Erling Smørgrav 		default:
1179ee21a45fSDag-Erling Smørgrav 			fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
11801e8db6e2SBrian Feldman 			    SSH2_FXP_DATA, type);
11811e8db6e2SBrian Feldman 		}
1182ae1f160dSDag-Erling Smørgrav 	}
11831e8db6e2SBrian Feldman 
1184d0c8c0bcSDag-Erling Smørgrav 	if (showprogress && size)
1185d0c8c0bcSDag-Erling Smørgrav 		stop_progress_meter();
1186d0c8c0bcSDag-Erling Smørgrav 
1187ae1f160dSDag-Erling Smørgrav 	/* Sanity check */
1188ae1f160dSDag-Erling Smørgrav 	if (TAILQ_FIRST(&requests) != NULL)
1189ae1f160dSDag-Erling Smørgrav 		fatal("Transfer complete, but requests still in queue");
11901e8db6e2SBrian Feldman 
1191ae1f160dSDag-Erling Smørgrav 	if (read_error) {
1192ae1f160dSDag-Erling Smørgrav 		error("Couldn't read from remote file \"%s\" : %s",
1193ae1f160dSDag-Erling Smørgrav 		    remote_path, fx2txt(status));
1194ae1f160dSDag-Erling Smørgrav 		do_close(conn, handle, handle_len);
1195ae1f160dSDag-Erling Smørgrav 	} else if (write_error) {
11961e8db6e2SBrian Feldman 		error("Couldn't write to \"%s\": %s", local_path,
1197ae1f160dSDag-Erling Smørgrav 		    strerror(write_errno));
11981e8db6e2SBrian Feldman 		status = -1;
1199ae1f160dSDag-Erling Smørgrav 		do_close(conn, handle, handle_len);
1200ae1f160dSDag-Erling Smørgrav 	} else {
1201ae1f160dSDag-Erling Smørgrav 		status = do_close(conn, handle, handle_len);
12021e8db6e2SBrian Feldman 
12031e8db6e2SBrian Feldman 		/* Override umask and utimes if asked */
120483d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD
12051e8db6e2SBrian Feldman 		if (pflag && fchmod(local_fd, mode) == -1)
120683d2307dSDag-Erling Smørgrav #else
120783d2307dSDag-Erling Smørgrav 		if (pflag && chmod(local_path, mode) == -1)
120883d2307dSDag-Erling Smørgrav #endif /* HAVE_FCHMOD */
12091e8db6e2SBrian Feldman 			error("Couldn't set mode on \"%s\": %s", local_path,
12101e8db6e2SBrian Feldman 			    strerror(errno));
12111e8db6e2SBrian Feldman 		if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
12121e8db6e2SBrian Feldman 			struct timeval tv[2];
12131e8db6e2SBrian Feldman 			tv[0].tv_sec = a->atime;
12141e8db6e2SBrian Feldman 			tv[1].tv_sec = a->mtime;
12151e8db6e2SBrian Feldman 			tv[0].tv_usec = tv[1].tv_usec = 0;
12161e8db6e2SBrian Feldman 			if (utimes(local_path, tv) == -1)
1217ae1f160dSDag-Erling Smørgrav 				error("Can't set times on \"%s\": %s",
1218ae1f160dSDag-Erling Smørgrav 				    local_path, strerror(errno));
12191e8db6e2SBrian Feldman 		}
1220ae1f160dSDag-Erling Smørgrav 	}
12211e8db6e2SBrian Feldman 	close(local_fd);
12221e8db6e2SBrian Feldman 	buffer_free(&msg);
12231e8db6e2SBrian Feldman 	xfree(handle);
1224ae1f160dSDag-Erling Smørgrav 
1225ae1f160dSDag-Erling Smørgrav 	return(status);
12261e8db6e2SBrian Feldman }
12271e8db6e2SBrian Feldman 
1228b15c8340SDag-Erling Smørgrav static int
1229b15c8340SDag-Erling Smørgrav download_dir_internal(struct sftp_conn *conn, char *src, char *dst,
1230b15c8340SDag-Erling Smørgrav     Attrib *dirattrib, int pflag, int printflag, int depth)
1231b15c8340SDag-Erling Smørgrav {
1232b15c8340SDag-Erling Smørgrav 	int i, ret = 0;
1233b15c8340SDag-Erling Smørgrav 	SFTP_DIRENT **dir_entries;
1234b15c8340SDag-Erling Smørgrav 	char *filename, *new_src, *new_dst;
1235b15c8340SDag-Erling Smørgrav 	mode_t mode = 0777;
1236b15c8340SDag-Erling Smørgrav 
1237b15c8340SDag-Erling Smørgrav 	if (depth >= MAX_DIR_DEPTH) {
1238b15c8340SDag-Erling Smørgrav 		error("Maximum directory depth exceeded: %d levels", depth);
1239b15c8340SDag-Erling Smørgrav 		return -1;
1240b15c8340SDag-Erling Smørgrav 	}
1241b15c8340SDag-Erling Smørgrav 
1242b15c8340SDag-Erling Smørgrav 	if (dirattrib == NULL &&
1243b15c8340SDag-Erling Smørgrav 	    (dirattrib = do_stat(conn, src, 1)) == NULL) {
1244b15c8340SDag-Erling Smørgrav 		error("Unable to stat remote directory \"%s\"", src);
1245b15c8340SDag-Erling Smørgrav 		return -1;
1246b15c8340SDag-Erling Smørgrav 	}
1247b15c8340SDag-Erling Smørgrav 	if (!S_ISDIR(dirattrib->perm)) {
1248b15c8340SDag-Erling Smørgrav 		error("\"%s\" is not a directory", src);
1249b15c8340SDag-Erling Smørgrav 		return -1;
1250b15c8340SDag-Erling Smørgrav 	}
1251b15c8340SDag-Erling Smørgrav 	if (printflag)
1252b15c8340SDag-Erling Smørgrav 		printf("Retrieving %s\n", src);
1253b15c8340SDag-Erling Smørgrav 
1254b15c8340SDag-Erling Smørgrav 	if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1255b15c8340SDag-Erling Smørgrav 		mode = dirattrib->perm & 01777;
1256b15c8340SDag-Erling Smørgrav 	else {
1257b15c8340SDag-Erling Smørgrav 		debug("Server did not send permissions for "
1258b15c8340SDag-Erling Smørgrav 		    "directory \"%s\"", dst);
1259b15c8340SDag-Erling Smørgrav 	}
1260b15c8340SDag-Erling Smørgrav 
1261b15c8340SDag-Erling Smørgrav 	if (mkdir(dst, mode) == -1 && errno != EEXIST) {
1262b15c8340SDag-Erling Smørgrav 		error("mkdir %s: %s", dst, strerror(errno));
1263b15c8340SDag-Erling Smørgrav 		return -1;
1264b15c8340SDag-Erling Smørgrav 	}
1265b15c8340SDag-Erling Smørgrav 
1266b15c8340SDag-Erling Smørgrav 	if (do_readdir(conn, src, &dir_entries) == -1) {
1267b15c8340SDag-Erling Smørgrav 		error("%s: Failed to get directory contents", src);
1268b15c8340SDag-Erling Smørgrav 		return -1;
1269b15c8340SDag-Erling Smørgrav 	}
1270b15c8340SDag-Erling Smørgrav 
1271b15c8340SDag-Erling Smørgrav 	for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
1272b15c8340SDag-Erling Smørgrav 		filename = dir_entries[i]->filename;
1273b15c8340SDag-Erling Smørgrav 
1274b15c8340SDag-Erling Smørgrav 		new_dst = path_append(dst, filename);
1275b15c8340SDag-Erling Smørgrav 		new_src = path_append(src, filename);
1276b15c8340SDag-Erling Smørgrav 
1277b15c8340SDag-Erling Smørgrav 		if (S_ISDIR(dir_entries[i]->a.perm)) {
1278b15c8340SDag-Erling Smørgrav 			if (strcmp(filename, ".") == 0 ||
1279b15c8340SDag-Erling Smørgrav 			    strcmp(filename, "..") == 0)
1280b15c8340SDag-Erling Smørgrav 				continue;
1281b15c8340SDag-Erling Smørgrav 			if (download_dir_internal(conn, new_src, new_dst,
1282b15c8340SDag-Erling Smørgrav 			    &(dir_entries[i]->a), pflag, printflag,
1283b15c8340SDag-Erling Smørgrav 			    depth + 1) == -1)
1284b15c8340SDag-Erling Smørgrav 				ret = -1;
1285b15c8340SDag-Erling Smørgrav 		} else if (S_ISREG(dir_entries[i]->a.perm) ) {
1286b15c8340SDag-Erling Smørgrav 			if (do_download(conn, new_src, new_dst,
1287b15c8340SDag-Erling Smørgrav 			    &(dir_entries[i]->a), pflag) == -1) {
1288b15c8340SDag-Erling Smørgrav 				error("Download of file %s to %s failed",
1289b15c8340SDag-Erling Smørgrav 				    new_src, new_dst);
1290b15c8340SDag-Erling Smørgrav 				ret = -1;
1291b15c8340SDag-Erling Smørgrav 			}
1292b15c8340SDag-Erling Smørgrav 		} else
1293b15c8340SDag-Erling Smørgrav 			logit("%s: not a regular file\n", new_src);
1294b15c8340SDag-Erling Smørgrav 
1295b15c8340SDag-Erling Smørgrav 		xfree(new_dst);
1296b15c8340SDag-Erling Smørgrav 		xfree(new_src);
1297b15c8340SDag-Erling Smørgrav 	}
1298b15c8340SDag-Erling Smørgrav 
1299b15c8340SDag-Erling Smørgrav 	if (pflag) {
1300b15c8340SDag-Erling Smørgrav 		if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1301b15c8340SDag-Erling Smørgrav 			struct timeval tv[2];
1302b15c8340SDag-Erling Smørgrav 			tv[0].tv_sec = dirattrib->atime;
1303b15c8340SDag-Erling Smørgrav 			tv[1].tv_sec = dirattrib->mtime;
1304b15c8340SDag-Erling Smørgrav 			tv[0].tv_usec = tv[1].tv_usec = 0;
1305b15c8340SDag-Erling Smørgrav 			if (utimes(dst, tv) == -1)
1306b15c8340SDag-Erling Smørgrav 				error("Can't set times on \"%s\": %s",
1307b15c8340SDag-Erling Smørgrav 				    dst, strerror(errno));
1308b15c8340SDag-Erling Smørgrav 		} else
1309b15c8340SDag-Erling Smørgrav 			debug("Server did not send times for directory "
1310b15c8340SDag-Erling Smørgrav 			    "\"%s\"", dst);
1311b15c8340SDag-Erling Smørgrav 	}
1312b15c8340SDag-Erling Smørgrav 
1313b15c8340SDag-Erling Smørgrav 	free_sftp_dirents(dir_entries);
1314b15c8340SDag-Erling Smørgrav 
1315b15c8340SDag-Erling Smørgrav 	return ret;
1316b15c8340SDag-Erling Smørgrav }
1317b15c8340SDag-Erling Smørgrav 
1318b15c8340SDag-Erling Smørgrav int
1319b15c8340SDag-Erling Smørgrav download_dir(struct sftp_conn *conn, char *src, char *dst,
1320b15c8340SDag-Erling Smørgrav     Attrib *dirattrib, int pflag, int printflag)
1321b15c8340SDag-Erling Smørgrav {
1322b15c8340SDag-Erling Smørgrav 	char *src_canon;
1323b15c8340SDag-Erling Smørgrav 	int ret;
1324b15c8340SDag-Erling Smørgrav 
1325b15c8340SDag-Erling Smørgrav 	if ((src_canon = do_realpath(conn, src)) == NULL) {
1326b15c8340SDag-Erling Smørgrav 		error("Unable to canonicalise path \"%s\"", src);
1327b15c8340SDag-Erling Smørgrav 		return -1;
1328b15c8340SDag-Erling Smørgrav 	}
1329b15c8340SDag-Erling Smørgrav 
1330b15c8340SDag-Erling Smørgrav 	ret = download_dir_internal(conn, src_canon, dst,
1331b15c8340SDag-Erling Smørgrav 	    dirattrib, pflag, printflag, 0);
1332b15c8340SDag-Erling Smørgrav 	xfree(src_canon);
1333b15c8340SDag-Erling Smørgrav 	return ret;
1334b15c8340SDag-Erling Smørgrav }
1335b15c8340SDag-Erling Smørgrav 
13361e8db6e2SBrian Feldman int
1337ae1f160dSDag-Erling Smørgrav do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
13381e8db6e2SBrian Feldman     int pflag)
13391e8db6e2SBrian Feldman {
1340d4af9e69SDag-Erling Smørgrav 	int local_fd;
1341d4af9e69SDag-Erling Smørgrav 	int status = SSH2_FX_OK;
1342ae1f160dSDag-Erling Smørgrav 	u_int handle_len, id, type;
1343d4af9e69SDag-Erling Smørgrav 	off_t offset;
1344ae1f160dSDag-Erling Smørgrav 	char *handle, *data;
13451e8db6e2SBrian Feldman 	Buffer msg;
13461e8db6e2SBrian Feldman 	struct stat sb;
13471e8db6e2SBrian Feldman 	Attrib a;
1348ae1f160dSDag-Erling Smørgrav 	u_int32_t startid;
1349ae1f160dSDag-Erling Smørgrav 	u_int32_t ackid;
1350ae1f160dSDag-Erling Smørgrav 	struct outstanding_ack {
1351ae1f160dSDag-Erling Smørgrav 		u_int id;
1352ae1f160dSDag-Erling Smørgrav 		u_int len;
1353d4af9e69SDag-Erling Smørgrav 		off_t offset;
1354ae1f160dSDag-Erling Smørgrav 		TAILQ_ENTRY(outstanding_ack) tq;
1355ae1f160dSDag-Erling Smørgrav 	};
1356ae1f160dSDag-Erling Smørgrav 	TAILQ_HEAD(ackhead, outstanding_ack) acks;
1357d74d50a8SDag-Erling Smørgrav 	struct outstanding_ack *ack = NULL;
1358ae1f160dSDag-Erling Smørgrav 
1359ae1f160dSDag-Erling Smørgrav 	TAILQ_INIT(&acks);
13601e8db6e2SBrian Feldman 
13611e8db6e2SBrian Feldman 	if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
13621e8db6e2SBrian Feldman 		error("Couldn't open local file \"%s\" for reading: %s",
13631e8db6e2SBrian Feldman 		    local_path, strerror(errno));
13641e8db6e2SBrian Feldman 		return(-1);
13651e8db6e2SBrian Feldman 	}
13661e8db6e2SBrian Feldman 	if (fstat(local_fd, &sb) == -1) {
13671e8db6e2SBrian Feldman 		error("Couldn't fstat local file \"%s\": %s",
13681e8db6e2SBrian Feldman 		    local_path, strerror(errno));
13691e8db6e2SBrian Feldman 		close(local_fd);
13701e8db6e2SBrian Feldman 		return(-1);
13711e8db6e2SBrian Feldman 	}
1372d0c8c0bcSDag-Erling Smørgrav 	if (!S_ISREG(sb.st_mode)) {
1373d0c8c0bcSDag-Erling Smørgrav 		error("%s is not a regular file", local_path);
1374d0c8c0bcSDag-Erling Smørgrav 		close(local_fd);
1375d0c8c0bcSDag-Erling Smørgrav 		return(-1);
1376d0c8c0bcSDag-Erling Smørgrav 	}
13771e8db6e2SBrian Feldman 	stat_to_attrib(&sb, &a);
13781e8db6e2SBrian Feldman 
13791e8db6e2SBrian Feldman 	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
13801e8db6e2SBrian Feldman 	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
13811e8db6e2SBrian Feldman 	a.perm &= 0777;
13821e8db6e2SBrian Feldman 	if (!pflag)
13831e8db6e2SBrian Feldman 		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
13841e8db6e2SBrian Feldman 
13851e8db6e2SBrian Feldman 	buffer_init(&msg);
13861e8db6e2SBrian Feldman 
13871e8db6e2SBrian Feldman 	/* Send open request */
1388ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
13891e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_OPEN);
13901e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
13911e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, remote_path);
13921e8db6e2SBrian Feldman 	buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC);
13931e8db6e2SBrian Feldman 	encode_attrib(&msg, &a);
13944a421b63SDag-Erling Smørgrav 	send_msg(conn, &msg);
1395ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
13961e8db6e2SBrian Feldman 
13971e8db6e2SBrian Feldman 	buffer_clear(&msg);
13981e8db6e2SBrian Feldman 
13994a421b63SDag-Erling Smørgrav 	handle = get_handle(conn, id, &handle_len,
1400b15c8340SDag-Erling Smørgrav 	    "remote open(\"%s\")", remote_path);
14011e8db6e2SBrian Feldman 	if (handle == NULL) {
14021e8db6e2SBrian Feldman 		close(local_fd);
14031e8db6e2SBrian Feldman 		buffer_free(&msg);
1404d4af9e69SDag-Erling Smørgrav 		return -1;
14051e8db6e2SBrian Feldman 	}
14061e8db6e2SBrian Feldman 
1407ae1f160dSDag-Erling Smørgrav 	startid = ackid = id + 1;
1408ae1f160dSDag-Erling Smørgrav 	data = xmalloc(conn->transfer_buflen);
1409ae1f160dSDag-Erling Smørgrav 
14101e8db6e2SBrian Feldman 	/* Read from local and write to remote */
14111e8db6e2SBrian Feldman 	offset = 0;
1412d0c8c0bcSDag-Erling Smørgrav 	if (showprogress)
1413d0c8c0bcSDag-Erling Smørgrav 		start_progress_meter(local_path, sb.st_size, &offset);
1414d0c8c0bcSDag-Erling Smørgrav 
14151e8db6e2SBrian Feldman 	for (;;) {
14161e8db6e2SBrian Feldman 		int len;
14171e8db6e2SBrian Feldman 
14181e8db6e2SBrian Feldman 		/*
1419d74d50a8SDag-Erling Smørgrav 		 * Can't use atomicio here because it returns 0 on EOF,
1420d74d50a8SDag-Erling Smørgrav 		 * thus losing the last block of the file.
1421d74d50a8SDag-Erling Smørgrav 		 * Simulate an EOF on interrupt, allowing ACKs from the
1422d74d50a8SDag-Erling Smørgrav 		 * server to drain.
14231e8db6e2SBrian Feldman 		 */
1424d4af9e69SDag-Erling Smørgrav 		if (interrupted || status != SSH2_FX_OK)
1425d74d50a8SDag-Erling Smørgrav 			len = 0;
1426d74d50a8SDag-Erling Smørgrav 		else do
1427ae1f160dSDag-Erling Smørgrav 			len = read(local_fd, data, conn->transfer_buflen);
1428d4af9e69SDag-Erling Smørgrav 		while ((len == -1) &&
1429d4af9e69SDag-Erling Smørgrav 		    (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
14301e8db6e2SBrian Feldman 
14311e8db6e2SBrian Feldman 		if (len == -1)
14321e8db6e2SBrian Feldman 			fatal("Couldn't read from \"%s\": %s", local_path,
14331e8db6e2SBrian Feldman 			    strerror(errno));
1434ae1f160dSDag-Erling Smørgrav 
1435ae1f160dSDag-Erling Smørgrav 		if (len != 0) {
1436ae1f160dSDag-Erling Smørgrav 			ack = xmalloc(sizeof(*ack));
1437ae1f160dSDag-Erling Smørgrav 			ack->id = ++id;
1438ae1f160dSDag-Erling Smørgrav 			ack->offset = offset;
1439ae1f160dSDag-Erling Smørgrav 			ack->len = len;
1440ae1f160dSDag-Erling Smørgrav 			TAILQ_INSERT_TAIL(&acks, ack, tq);
14411e8db6e2SBrian Feldman 
14421e8db6e2SBrian Feldman 			buffer_clear(&msg);
14431e8db6e2SBrian Feldman 			buffer_put_char(&msg, SSH2_FXP_WRITE);
1444ae1f160dSDag-Erling Smørgrav 			buffer_put_int(&msg, ack->id);
14451e8db6e2SBrian Feldman 			buffer_put_string(&msg, handle, handle_len);
14461e8db6e2SBrian Feldman 			buffer_put_int64(&msg, offset);
14471e8db6e2SBrian Feldman 			buffer_put_string(&msg, data, len);
14484a421b63SDag-Erling Smørgrav 			send_msg(conn, &msg);
1449ee21a45fSDag-Erling Smørgrav 			debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
1450545d5ecaSDag-Erling Smørgrav 			    id, (unsigned long long)offset, len);
1451ae1f160dSDag-Erling Smørgrav 		} else if (TAILQ_FIRST(&acks) == NULL)
1452ae1f160dSDag-Erling Smørgrav 			break;
14531e8db6e2SBrian Feldman 
1454ae1f160dSDag-Erling Smørgrav 		if (ack == NULL)
1455ae1f160dSDag-Erling Smørgrav 			fatal("Unexpected ACK %u", id);
1456ae1f160dSDag-Erling Smørgrav 
1457ae1f160dSDag-Erling Smørgrav 		if (id == startid || len == 0 ||
1458ae1f160dSDag-Erling Smørgrav 		    id - ackid >= conn->num_requests) {
1459545d5ecaSDag-Erling Smørgrav 			u_int r_id;
1460545d5ecaSDag-Erling Smørgrav 
1461ae1f160dSDag-Erling Smørgrav 			buffer_clear(&msg);
14624a421b63SDag-Erling Smørgrav 			get_msg(conn, &msg);
1463ae1f160dSDag-Erling Smørgrav 			type = buffer_get_char(&msg);
1464545d5ecaSDag-Erling Smørgrav 			r_id = buffer_get_int(&msg);
1465ae1f160dSDag-Erling Smørgrav 
1466ae1f160dSDag-Erling Smørgrav 			if (type != SSH2_FXP_STATUS)
1467ae1f160dSDag-Erling Smørgrav 				fatal("Expected SSH2_FXP_STATUS(%d) packet, "
1468ae1f160dSDag-Erling Smørgrav 				    "got %d", SSH2_FXP_STATUS, type);
1469ae1f160dSDag-Erling Smørgrav 
1470ae1f160dSDag-Erling Smørgrav 			status = buffer_get_int(&msg);
1471ae1f160dSDag-Erling Smørgrav 			debug3("SSH2_FXP_STATUS %d", status);
1472ae1f160dSDag-Erling Smørgrav 
1473ae1f160dSDag-Erling Smørgrav 			/* Find the request in our queue */
1474ae1f160dSDag-Erling Smørgrav 			for (ack = TAILQ_FIRST(&acks);
1475545d5ecaSDag-Erling Smørgrav 			    ack != NULL && ack->id != r_id;
1476ae1f160dSDag-Erling Smørgrav 			    ack = TAILQ_NEXT(ack, tq))
1477ae1f160dSDag-Erling Smørgrav 				;
1478ae1f160dSDag-Erling Smørgrav 			if (ack == NULL)
1479ee21a45fSDag-Erling Smørgrav 				fatal("Can't find request for ID %u", r_id);
1480ae1f160dSDag-Erling Smørgrav 			TAILQ_REMOVE(&acks, ack, tq);
1481d4af9e69SDag-Erling Smørgrav 			debug3("In write loop, ack for %u %u bytes at %lld",
1482d4af9e69SDag-Erling Smørgrav 			    ack->id, ack->len, (long long)ack->offset);
1483ae1f160dSDag-Erling Smørgrav 			++ackid;
14844b17dab0SDag-Erling Smørgrav 			xfree(ack);
1485ae1f160dSDag-Erling Smørgrav 		}
14861e8db6e2SBrian Feldman 		offset += len;
1487d4af9e69SDag-Erling Smørgrav 		if (offset < 0)
1488d4af9e69SDag-Erling Smørgrav 			fatal("%s: offset < 0", __func__);
14891e8db6e2SBrian Feldman 	}
1490d4af9e69SDag-Erling Smørgrav 	buffer_free(&msg);
1491d4af9e69SDag-Erling Smørgrav 
1492d0c8c0bcSDag-Erling Smørgrav 	if (showprogress)
1493d0c8c0bcSDag-Erling Smørgrav 		stop_progress_meter();
1494ae1f160dSDag-Erling Smørgrav 	xfree(data);
14951e8db6e2SBrian Feldman 
1496d4af9e69SDag-Erling Smørgrav 	if (status != SSH2_FX_OK) {
1497d4af9e69SDag-Erling Smørgrav 		error("Couldn't write to remote file \"%s\": %s",
1498d4af9e69SDag-Erling Smørgrav 		    remote_path, fx2txt(status));
1499d4af9e69SDag-Erling Smørgrav 		status = -1;
1500d4af9e69SDag-Erling Smørgrav 	}
1501d4af9e69SDag-Erling Smørgrav 
15021e8db6e2SBrian Feldman 	if (close(local_fd) == -1) {
15031e8db6e2SBrian Feldman 		error("Couldn't close local file \"%s\": %s", local_path,
15041e8db6e2SBrian Feldman 		    strerror(errno));
15051e8db6e2SBrian Feldman 		status = -1;
15061e8db6e2SBrian Feldman 	}
15071e8db6e2SBrian Feldman 
15081e8db6e2SBrian Feldman 	/* Override umask and utimes if asked */
15091e8db6e2SBrian Feldman 	if (pflag)
1510ae1f160dSDag-Erling Smørgrav 		do_fsetstat(conn, handle, handle_len, &a);
15111e8db6e2SBrian Feldman 
1512d4af9e69SDag-Erling Smørgrav 	if (do_close(conn, handle, handle_len) != SSH2_FX_OK)
1513d4af9e69SDag-Erling Smørgrav 		status = -1;
15141e8db6e2SBrian Feldman 	xfree(handle);
1515d4af9e69SDag-Erling Smørgrav 
1516d4af9e69SDag-Erling Smørgrav 	return status;
15171e8db6e2SBrian Feldman }
1518b15c8340SDag-Erling Smørgrav 
1519b15c8340SDag-Erling Smørgrav static int
1520b15c8340SDag-Erling Smørgrav upload_dir_internal(struct sftp_conn *conn, char *src, char *dst,
1521b15c8340SDag-Erling Smørgrav     int pflag, int printflag, int depth)
1522b15c8340SDag-Erling Smørgrav {
1523b15c8340SDag-Erling Smørgrav 	int ret = 0, status;
1524b15c8340SDag-Erling Smørgrav 	DIR *dirp;
1525b15c8340SDag-Erling Smørgrav 	struct dirent *dp;
1526b15c8340SDag-Erling Smørgrav 	char *filename, *new_src, *new_dst;
1527b15c8340SDag-Erling Smørgrav 	struct stat sb;
1528b15c8340SDag-Erling Smørgrav 	Attrib a;
1529b15c8340SDag-Erling Smørgrav 
1530b15c8340SDag-Erling Smørgrav 	if (depth >= MAX_DIR_DEPTH) {
1531b15c8340SDag-Erling Smørgrav 		error("Maximum directory depth exceeded: %d levels", depth);
1532b15c8340SDag-Erling Smørgrav 		return -1;
1533b15c8340SDag-Erling Smørgrav 	}
1534b15c8340SDag-Erling Smørgrav 
1535b15c8340SDag-Erling Smørgrav 	if (stat(src, &sb) == -1) {
1536b15c8340SDag-Erling Smørgrav 		error("Couldn't stat directory \"%s\": %s",
1537b15c8340SDag-Erling Smørgrav 		    src, strerror(errno));
1538b15c8340SDag-Erling Smørgrav 		return -1;
1539b15c8340SDag-Erling Smørgrav 	}
1540b15c8340SDag-Erling Smørgrav 	if (!S_ISDIR(sb.st_mode)) {
1541b15c8340SDag-Erling Smørgrav 		error("\"%s\" is not a directory", src);
1542b15c8340SDag-Erling Smørgrav 		return -1;
1543b15c8340SDag-Erling Smørgrav 	}
1544b15c8340SDag-Erling Smørgrav 	if (printflag)
1545b15c8340SDag-Erling Smørgrav 		printf("Entering %s\n", src);
1546b15c8340SDag-Erling Smørgrav 
1547b15c8340SDag-Erling Smørgrav 	attrib_clear(&a);
1548b15c8340SDag-Erling Smørgrav 	stat_to_attrib(&sb, &a);
1549b15c8340SDag-Erling Smørgrav 	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
1550b15c8340SDag-Erling Smørgrav 	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
1551b15c8340SDag-Erling Smørgrav 	a.perm &= 01777;
1552b15c8340SDag-Erling Smørgrav 	if (!pflag)
1553b15c8340SDag-Erling Smørgrav 		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
1554b15c8340SDag-Erling Smørgrav 
1555b15c8340SDag-Erling Smørgrav 	status = do_mkdir(conn, dst, &a, 0);
1556b15c8340SDag-Erling Smørgrav 	/*
1557b15c8340SDag-Erling Smørgrav 	 * we lack a portable status for errno EEXIST,
1558b15c8340SDag-Erling Smørgrav 	 * so if we get a SSH2_FX_FAILURE back we must check
1559b15c8340SDag-Erling Smørgrav 	 * if it was created successfully.
1560b15c8340SDag-Erling Smørgrav 	 */
1561b15c8340SDag-Erling Smørgrav 	if (status != SSH2_FX_OK) {
1562b15c8340SDag-Erling Smørgrav 		if (status != SSH2_FX_FAILURE)
1563b15c8340SDag-Erling Smørgrav 			return -1;
1564b15c8340SDag-Erling Smørgrav 		if (do_stat(conn, dst, 0) == NULL)
1565b15c8340SDag-Erling Smørgrav 			return -1;
1566b15c8340SDag-Erling Smørgrav 	}
1567b15c8340SDag-Erling Smørgrav 
1568b15c8340SDag-Erling Smørgrav 	if ((dirp = opendir(src)) == NULL) {
1569b15c8340SDag-Erling Smørgrav 		error("Failed to open dir \"%s\": %s", src, strerror(errno));
1570b15c8340SDag-Erling Smørgrav 		return -1;
1571b15c8340SDag-Erling Smørgrav 	}
1572b15c8340SDag-Erling Smørgrav 
1573b15c8340SDag-Erling Smørgrav 	while (((dp = readdir(dirp)) != NULL) && !interrupted) {
1574b15c8340SDag-Erling Smørgrav 		if (dp->d_ino == 0)
1575b15c8340SDag-Erling Smørgrav 			continue;
1576b15c8340SDag-Erling Smørgrav 		filename = dp->d_name;
1577b15c8340SDag-Erling Smørgrav 		new_dst = path_append(dst, filename);
1578b15c8340SDag-Erling Smørgrav 		new_src = path_append(src, filename);
1579b15c8340SDag-Erling Smørgrav 
1580b15c8340SDag-Erling Smørgrav 		if (lstat(new_src, &sb) == -1) {
1581b15c8340SDag-Erling Smørgrav 			logit("%s: lstat failed: %s", filename,
1582b15c8340SDag-Erling Smørgrav 			    strerror(errno));
1583b15c8340SDag-Erling Smørgrav 			ret = -1;
1584b15c8340SDag-Erling Smørgrav 		} else if (S_ISDIR(sb.st_mode)) {
1585b15c8340SDag-Erling Smørgrav 			if (strcmp(filename, ".") == 0 ||
1586b15c8340SDag-Erling Smørgrav 			    strcmp(filename, "..") == 0)
1587b15c8340SDag-Erling Smørgrav 				continue;
1588b15c8340SDag-Erling Smørgrav 
1589b15c8340SDag-Erling Smørgrav 			if (upload_dir_internal(conn, new_src, new_dst,
1590e2f6069cSDag-Erling Smørgrav 			    pflag, printflag, depth + 1) == -1)
1591b15c8340SDag-Erling Smørgrav 				ret = -1;
1592b15c8340SDag-Erling Smørgrav 		} else if (S_ISREG(sb.st_mode)) {
1593b15c8340SDag-Erling Smørgrav 			if (do_upload(conn, new_src, new_dst, pflag) == -1) {
1594b15c8340SDag-Erling Smørgrav 				error("Uploading of file %s to %s failed!",
1595b15c8340SDag-Erling Smørgrav 				    new_src, new_dst);
1596b15c8340SDag-Erling Smørgrav 				ret = -1;
1597b15c8340SDag-Erling Smørgrav 			}
1598b15c8340SDag-Erling Smørgrav 		} else
1599b15c8340SDag-Erling Smørgrav 			logit("%s: not a regular file\n", filename);
1600b15c8340SDag-Erling Smørgrav 		xfree(new_dst);
1601b15c8340SDag-Erling Smørgrav 		xfree(new_src);
1602b15c8340SDag-Erling Smørgrav 	}
1603b15c8340SDag-Erling Smørgrav 
1604b15c8340SDag-Erling Smørgrav 	do_setstat(conn, dst, &a);
1605b15c8340SDag-Erling Smørgrav 
1606b15c8340SDag-Erling Smørgrav 	(void) closedir(dirp);
1607b15c8340SDag-Erling Smørgrav 	return ret;
1608b15c8340SDag-Erling Smørgrav }
1609b15c8340SDag-Erling Smørgrav 
1610b15c8340SDag-Erling Smørgrav int
1611b15c8340SDag-Erling Smørgrav upload_dir(struct sftp_conn *conn, char *src, char *dst, int printflag,
1612b15c8340SDag-Erling Smørgrav     int pflag)
1613b15c8340SDag-Erling Smørgrav {
1614b15c8340SDag-Erling Smørgrav 	char *dst_canon;
1615b15c8340SDag-Erling Smørgrav 	int ret;
1616b15c8340SDag-Erling Smørgrav 
1617b15c8340SDag-Erling Smørgrav 	if ((dst_canon = do_realpath(conn, dst)) == NULL) {
1618b15c8340SDag-Erling Smørgrav 		error("Unable to canonicalise path \"%s\"", dst);
1619b15c8340SDag-Erling Smørgrav 		return -1;
1620b15c8340SDag-Erling Smørgrav 	}
1621b15c8340SDag-Erling Smørgrav 
1622b15c8340SDag-Erling Smørgrav 	ret = upload_dir_internal(conn, src, dst_canon, pflag, printflag, 0);
1623b15c8340SDag-Erling Smørgrav 	xfree(dst_canon);
1624b15c8340SDag-Erling Smørgrav 	return ret;
1625b15c8340SDag-Erling Smørgrav }
1626b15c8340SDag-Erling Smørgrav 
1627b15c8340SDag-Erling Smørgrav char *
1628b15c8340SDag-Erling Smørgrav path_append(char *p1, char *p2)
1629b15c8340SDag-Erling Smørgrav {
1630b15c8340SDag-Erling Smørgrav 	char *ret;
1631b15c8340SDag-Erling Smørgrav 	size_t len = strlen(p1) + strlen(p2) + 2;
1632b15c8340SDag-Erling Smørgrav 
1633b15c8340SDag-Erling Smørgrav 	ret = xmalloc(len);
1634b15c8340SDag-Erling Smørgrav 	strlcpy(ret, p1, len);
1635b15c8340SDag-Erling Smørgrav 	if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
1636b15c8340SDag-Erling Smørgrav 		strlcat(ret, "/", len);
1637b15c8340SDag-Erling Smørgrav 	strlcat(ret, p2, len);
1638b15c8340SDag-Erling Smørgrav 
1639b15c8340SDag-Erling Smørgrav 	return(ret);
1640b15c8340SDag-Erling Smørgrav }
1641b15c8340SDag-Erling Smørgrav 
1642