xref: /freebsd/crypto/openssh/sftp-client.c (revision 761efaa70c2ed8d35722b7bc234a46bf2457f876)
1761efaa7SDag-Erling Smørgrav /* $OpenBSD: sftp-client.c,v 1.74 2006/08/03 03:34:42 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 
25761efaa7SDag-Erling Smørgrav #include <sys/types.h>
26761efaa7SDag-Erling Smørgrav #include <sys/param.h>
274b17dab0SDag-Erling Smørgrav #include "openbsd-compat/sys-queue.h"
28761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_STAT_H
29761efaa7SDag-Erling Smørgrav # include <sys/stat.h>
30761efaa7SDag-Erling Smørgrav #endif
31761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_TIME_H
32761efaa7SDag-Erling Smørgrav # include <sys/time.h>
33761efaa7SDag-Erling Smørgrav #endif
34761efaa7SDag-Erling Smørgrav #include <sys/uio.h>
35ae1f160dSDag-Erling Smørgrav 
36761efaa7SDag-Erling Smørgrav #include <errno.h>
37761efaa7SDag-Erling Smørgrav #include <fcntl.h>
38761efaa7SDag-Erling Smørgrav #include <signal.h>
39761efaa7SDag-Erling Smørgrav #include <stdarg.h>
40761efaa7SDag-Erling Smørgrav #include <stdio.h>
41761efaa7SDag-Erling Smørgrav #include <string.h>
42761efaa7SDag-Erling Smørgrav #include <unistd.h>
43761efaa7SDag-Erling Smørgrav 
441e8db6e2SBrian Feldman #include "xmalloc.h"
45761efaa7SDag-Erling Smørgrav #include "buffer.h"
461e8db6e2SBrian Feldman #include "log.h"
471e8db6e2SBrian Feldman #include "atomicio.h"
48d0c8c0bcSDag-Erling Smørgrav #include "progressmeter.h"
49761efaa7SDag-Erling Smørgrav #include "misc.h"
501e8db6e2SBrian Feldman 
511e8db6e2SBrian Feldman #include "sftp.h"
521e8db6e2SBrian Feldman #include "sftp-common.h"
531e8db6e2SBrian Feldman #include "sftp-client.h"
541e8db6e2SBrian Feldman 
55d74d50a8SDag-Erling Smørgrav extern volatile sig_atomic_t interrupted;
56d0c8c0bcSDag-Erling Smørgrav extern int showprogress;
57d0c8c0bcSDag-Erling Smørgrav 
58761efaa7SDag-Erling Smørgrav /* Minimum amount of data to read at a time */
59ae1f160dSDag-Erling Smørgrav #define MIN_READ_SIZE	512
601e8db6e2SBrian Feldman 
61ae1f160dSDag-Erling Smørgrav struct sftp_conn {
62ae1f160dSDag-Erling Smørgrav 	int fd_in;
63ae1f160dSDag-Erling Smørgrav 	int fd_out;
64ae1f160dSDag-Erling Smørgrav 	u_int transfer_buflen;
65ae1f160dSDag-Erling Smørgrav 	u_int num_requests;
66ae1f160dSDag-Erling Smørgrav 	u_int version;
67ae1f160dSDag-Erling Smørgrav 	u_int msg_id;
68ae1f160dSDag-Erling Smørgrav };
691e8db6e2SBrian Feldman 
70ae1f160dSDag-Erling Smørgrav static void
711e8db6e2SBrian Feldman send_msg(int fd, Buffer *m)
721e8db6e2SBrian Feldman {
73d0c8c0bcSDag-Erling Smørgrav 	u_char mlen[4];
74761efaa7SDag-Erling Smørgrav 	struct iovec iov[2];
751e8db6e2SBrian Feldman 
76021d409fSDag-Erling Smørgrav 	if (buffer_len(m) > SFTP_MAX_MSG_LENGTH)
77d0c8c0bcSDag-Erling Smørgrav 		fatal("Outbound message too long %u", buffer_len(m));
781e8db6e2SBrian Feldman 
79d0c8c0bcSDag-Erling Smørgrav 	/* Send length first */
80761efaa7SDag-Erling Smørgrav 	put_u32(mlen, buffer_len(m));
81761efaa7SDag-Erling Smørgrav 	iov[0].iov_base = mlen;
82761efaa7SDag-Erling Smørgrav 	iov[0].iov_len = sizeof(mlen);
83761efaa7SDag-Erling Smørgrav 	iov[1].iov_base = buffer_ptr(m);
84761efaa7SDag-Erling Smørgrav 	iov[1].iov_len = buffer_len(m);
851e8db6e2SBrian Feldman 
86761efaa7SDag-Erling Smørgrav 	if (atomiciov(writev, fd, iov, 2) != buffer_len(m) + sizeof(mlen))
87d0c8c0bcSDag-Erling Smørgrav 		fatal("Couldn't send packet: %s", strerror(errno));
88d0c8c0bcSDag-Erling Smørgrav 
89d0c8c0bcSDag-Erling Smørgrav 	buffer_clear(m);
901e8db6e2SBrian Feldman }
911e8db6e2SBrian Feldman 
92ae1f160dSDag-Erling Smørgrav static void
931e8db6e2SBrian Feldman get_msg(int fd, Buffer *m)
941e8db6e2SBrian Feldman {
95d0c8c0bcSDag-Erling Smørgrav 	u_int msg_len;
961e8db6e2SBrian Feldman 
97d0c8c0bcSDag-Erling Smørgrav 	buffer_append_space(m, 4);
98043840dfSDag-Erling Smørgrav 	if (atomicio(read, fd, buffer_ptr(m), 4) != 4) {
99043840dfSDag-Erling Smørgrav 		if (errno == EPIPE)
1001e8db6e2SBrian Feldman 			fatal("Connection closed");
101043840dfSDag-Erling Smørgrav 		else
1021e8db6e2SBrian Feldman 			fatal("Couldn't read packet: %s", strerror(errno));
103043840dfSDag-Erling Smørgrav 	}
1041e8db6e2SBrian Feldman 
105d0c8c0bcSDag-Erling Smørgrav 	msg_len = buffer_get_int(m);
106021d409fSDag-Erling Smørgrav 	if (msg_len > SFTP_MAX_MSG_LENGTH)
107ee21a45fSDag-Erling Smørgrav 		fatal("Received message too long %u", msg_len);
1081e8db6e2SBrian Feldman 
109d0c8c0bcSDag-Erling Smørgrav 	buffer_append_space(m, msg_len);
110043840dfSDag-Erling Smørgrav 	if (atomicio(read, fd, buffer_ptr(m), msg_len) != msg_len) {
111043840dfSDag-Erling Smørgrav 		if (errno == EPIPE)
1121e8db6e2SBrian Feldman 			fatal("Connection closed");
113043840dfSDag-Erling Smørgrav 		else
114d0c8c0bcSDag-Erling Smørgrav 			fatal("Read packet: %s", strerror(errno));
1151e8db6e2SBrian Feldman 	}
116043840dfSDag-Erling Smørgrav }
1171e8db6e2SBrian Feldman 
118ae1f160dSDag-Erling Smørgrav static void
1191e8db6e2SBrian Feldman send_string_request(int fd, u_int id, u_int code, char *s,
1201e8db6e2SBrian Feldman     u_int len)
1211e8db6e2SBrian Feldman {
1221e8db6e2SBrian Feldman 	Buffer msg;
1231e8db6e2SBrian Feldman 
1241e8db6e2SBrian Feldman 	buffer_init(&msg);
1251e8db6e2SBrian Feldman 	buffer_put_char(&msg, code);
1261e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
1271e8db6e2SBrian Feldman 	buffer_put_string(&msg, s, len);
1281e8db6e2SBrian Feldman 	send_msg(fd, &msg);
129ee21a45fSDag-Erling Smørgrav 	debug3("Sent message fd %d T:%u I:%u", fd, code, id);
1301e8db6e2SBrian Feldman 	buffer_free(&msg);
1311e8db6e2SBrian Feldman }
1321e8db6e2SBrian Feldman 
133ae1f160dSDag-Erling Smørgrav static void
1341e8db6e2SBrian Feldman send_string_attrs_request(int fd, u_int id, u_int code, char *s,
1351e8db6e2SBrian Feldman     u_int len, Attrib *a)
1361e8db6e2SBrian Feldman {
1371e8db6e2SBrian Feldman 	Buffer msg;
1381e8db6e2SBrian Feldman 
1391e8db6e2SBrian Feldman 	buffer_init(&msg);
1401e8db6e2SBrian Feldman 	buffer_put_char(&msg, code);
1411e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
1421e8db6e2SBrian Feldman 	buffer_put_string(&msg, s, len);
1431e8db6e2SBrian Feldman 	encode_attrib(&msg, a);
1441e8db6e2SBrian Feldman 	send_msg(fd, &msg);
145ee21a45fSDag-Erling Smørgrav 	debug3("Sent message fd %d T:%u I:%u", fd, code, id);
1461e8db6e2SBrian Feldman 	buffer_free(&msg);
1471e8db6e2SBrian Feldman }
1481e8db6e2SBrian Feldman 
149ae1f160dSDag-Erling Smørgrav static u_int
150ee21a45fSDag-Erling Smørgrav get_status(int fd, u_int expected_id)
1511e8db6e2SBrian Feldman {
1521e8db6e2SBrian Feldman 	Buffer msg;
1531e8db6e2SBrian Feldman 	u_int type, id, status;
1541e8db6e2SBrian Feldman 
1551e8db6e2SBrian Feldman 	buffer_init(&msg);
1561e8db6e2SBrian Feldman 	get_msg(fd, &msg);
1571e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
1581e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
1591e8db6e2SBrian Feldman 
1601e8db6e2SBrian Feldman 	if (id != expected_id)
161ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
1621e8db6e2SBrian Feldman 	if (type != SSH2_FXP_STATUS)
163ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
1641e8db6e2SBrian Feldman 		    SSH2_FXP_STATUS, type);
1651e8db6e2SBrian Feldman 
1661e8db6e2SBrian Feldman 	status = buffer_get_int(&msg);
1671e8db6e2SBrian Feldman 	buffer_free(&msg);
1681e8db6e2SBrian Feldman 
169ee21a45fSDag-Erling Smørgrav 	debug3("SSH2_FXP_STATUS %u", status);
1701e8db6e2SBrian Feldman 
1711e8db6e2SBrian Feldman 	return(status);
1721e8db6e2SBrian Feldman }
1731e8db6e2SBrian Feldman 
174ae1f160dSDag-Erling Smørgrav static char *
1751e8db6e2SBrian Feldman get_handle(int fd, u_int expected_id, u_int *len)
1761e8db6e2SBrian Feldman {
1771e8db6e2SBrian Feldman 	Buffer msg;
1781e8db6e2SBrian Feldman 	u_int type, id;
1791e8db6e2SBrian Feldman 	char *handle;
1801e8db6e2SBrian Feldman 
1811e8db6e2SBrian Feldman 	buffer_init(&msg);
1821e8db6e2SBrian Feldman 	get_msg(fd, &msg);
1831e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
1841e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
1851e8db6e2SBrian Feldman 
1861e8db6e2SBrian Feldman 	if (id != expected_id)
187ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
1881e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
1891e8db6e2SBrian Feldman 		int status = buffer_get_int(&msg);
1901e8db6e2SBrian Feldman 
1911e8db6e2SBrian Feldman 		error("Couldn't get handle: %s", fx2txt(status));
1925e8dbd04SDag-Erling Smørgrav 		buffer_free(&msg);
1931e8db6e2SBrian Feldman 		return(NULL);
1941e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_HANDLE)
195ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_HANDLE(%u) packet, got %u",
1961e8db6e2SBrian Feldman 		    SSH2_FXP_HANDLE, type);
1971e8db6e2SBrian Feldman 
1981e8db6e2SBrian Feldman 	handle = buffer_get_string(&msg, len);
1991e8db6e2SBrian Feldman 	buffer_free(&msg);
2001e8db6e2SBrian Feldman 
2011e8db6e2SBrian Feldman 	return(handle);
2021e8db6e2SBrian Feldman }
2031e8db6e2SBrian Feldman 
204ae1f160dSDag-Erling Smørgrav static Attrib *
2051e8db6e2SBrian Feldman get_decode_stat(int fd, u_int expected_id, int quiet)
2061e8db6e2SBrian Feldman {
2071e8db6e2SBrian Feldman 	Buffer msg;
2081e8db6e2SBrian Feldman 	u_int type, id;
2091e8db6e2SBrian Feldman 	Attrib *a;
2101e8db6e2SBrian Feldman 
2111e8db6e2SBrian Feldman 	buffer_init(&msg);
2121e8db6e2SBrian Feldman 	get_msg(fd, &msg);
2131e8db6e2SBrian Feldman 
2141e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
2151e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
2161e8db6e2SBrian Feldman 
217ee21a45fSDag-Erling Smørgrav 	debug3("Received stat reply T:%u I:%u", type, id);
2181e8db6e2SBrian Feldman 	if (id != expected_id)
219ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
2201e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
2211e8db6e2SBrian Feldman 		int status = buffer_get_int(&msg);
2221e8db6e2SBrian Feldman 
2231e8db6e2SBrian Feldman 		if (quiet)
2241e8db6e2SBrian Feldman 			debug("Couldn't stat remote file: %s", fx2txt(status));
2251e8db6e2SBrian Feldman 		else
2261e8db6e2SBrian Feldman 			error("Couldn't stat remote file: %s", fx2txt(status));
2275e8dbd04SDag-Erling Smørgrav 		buffer_free(&msg);
2281e8db6e2SBrian Feldman 		return(NULL);
2291e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_ATTRS) {
230ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
2311e8db6e2SBrian Feldman 		    SSH2_FXP_ATTRS, type);
2321e8db6e2SBrian Feldman 	}
2331e8db6e2SBrian Feldman 	a = decode_attrib(&msg);
2341e8db6e2SBrian Feldman 	buffer_free(&msg);
2351e8db6e2SBrian Feldman 
2361e8db6e2SBrian Feldman 	return(a);
2371e8db6e2SBrian Feldman }
2381e8db6e2SBrian Feldman 
239ae1f160dSDag-Erling Smørgrav struct sftp_conn *
240ae1f160dSDag-Erling Smørgrav do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests)
2411e8db6e2SBrian Feldman {
242ee21a45fSDag-Erling Smørgrav 	u_int type;
243ee21a45fSDag-Erling Smørgrav 	int version;
2441e8db6e2SBrian Feldman 	Buffer msg;
245ae1f160dSDag-Erling Smørgrav 	struct sftp_conn *ret;
2461e8db6e2SBrian Feldman 
2471e8db6e2SBrian Feldman 	buffer_init(&msg);
2481e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_INIT);
2491e8db6e2SBrian Feldman 	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
2501e8db6e2SBrian Feldman 	send_msg(fd_out, &msg);
2511e8db6e2SBrian Feldman 
2521e8db6e2SBrian Feldman 	buffer_clear(&msg);
2531e8db6e2SBrian Feldman 
2541e8db6e2SBrian Feldman 	get_msg(fd_in, &msg);
2551e8db6e2SBrian Feldman 
2561e8db6e2SBrian Feldman 	/* Expecting a VERSION reply */
2571e8db6e2SBrian Feldman 	if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) {
258ee21a45fSDag-Erling Smørgrav 		error("Invalid packet back from SSH2_FXP_INIT (type %u)",
2591e8db6e2SBrian Feldman 		    type);
2601e8db6e2SBrian Feldman 		buffer_free(&msg);
261ae1f160dSDag-Erling Smørgrav 		return(NULL);
2621e8db6e2SBrian Feldman 	}
2631e8db6e2SBrian Feldman 	version = buffer_get_int(&msg);
2641e8db6e2SBrian Feldman 
2651e8db6e2SBrian Feldman 	debug2("Remote version: %d", version);
2661e8db6e2SBrian Feldman 
2671e8db6e2SBrian Feldman 	/* Check for extensions */
2681e8db6e2SBrian Feldman 	while (buffer_len(&msg) > 0) {
2691e8db6e2SBrian Feldman 		char *name = buffer_get_string(&msg, NULL);
2701e8db6e2SBrian Feldman 		char *value = buffer_get_string(&msg, NULL);
2711e8db6e2SBrian Feldman 
2721e8db6e2SBrian Feldman 		debug2("Init extension: \"%s\"", name);
2731e8db6e2SBrian Feldman 		xfree(name);
2741e8db6e2SBrian Feldman 		xfree(value);
2751e8db6e2SBrian Feldman 	}
2761e8db6e2SBrian Feldman 
2771e8db6e2SBrian Feldman 	buffer_free(&msg);
2781e8db6e2SBrian Feldman 
279ae1f160dSDag-Erling Smørgrav 	ret = xmalloc(sizeof(*ret));
280ae1f160dSDag-Erling Smørgrav 	ret->fd_in = fd_in;
281ae1f160dSDag-Erling Smørgrav 	ret->fd_out = fd_out;
282ae1f160dSDag-Erling Smørgrav 	ret->transfer_buflen = transfer_buflen;
283ae1f160dSDag-Erling Smørgrav 	ret->num_requests = num_requests;
284ae1f160dSDag-Erling Smørgrav 	ret->version = version;
285ae1f160dSDag-Erling Smørgrav 	ret->msg_id = 1;
286ae1f160dSDag-Erling Smørgrav 
287ae1f160dSDag-Erling Smørgrav 	/* Some filexfer v.0 servers don't support large packets */
288ae1f160dSDag-Erling Smørgrav 	if (version == 0)
289545d5ecaSDag-Erling Smørgrav 		ret->transfer_buflen = MIN(ret->transfer_buflen, 20480);
290ae1f160dSDag-Erling Smørgrav 
291ae1f160dSDag-Erling Smørgrav 	return(ret);
292ae1f160dSDag-Erling Smørgrav }
293ae1f160dSDag-Erling Smørgrav 
294ae1f160dSDag-Erling Smørgrav u_int
295ae1f160dSDag-Erling Smørgrav sftp_proto_version(struct sftp_conn *conn)
296ae1f160dSDag-Erling Smørgrav {
297ae1f160dSDag-Erling Smørgrav 	return(conn->version);
2981e8db6e2SBrian Feldman }
2991e8db6e2SBrian Feldman 
3001e8db6e2SBrian Feldman int
301ae1f160dSDag-Erling Smørgrav do_close(struct sftp_conn *conn, char *handle, u_int handle_len)
3021e8db6e2SBrian Feldman {
3031e8db6e2SBrian Feldman 	u_int id, status;
3041e8db6e2SBrian Feldman 	Buffer msg;
3051e8db6e2SBrian Feldman 
3061e8db6e2SBrian Feldman 	buffer_init(&msg);
3071e8db6e2SBrian Feldman 
308ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
3091e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_CLOSE);
3101e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
3111e8db6e2SBrian Feldman 	buffer_put_string(&msg, handle, handle_len);
312ae1f160dSDag-Erling Smørgrav 	send_msg(conn->fd_out, &msg);
313ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
3141e8db6e2SBrian Feldman 
315ae1f160dSDag-Erling Smørgrav 	status = get_status(conn->fd_in, id);
3161e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
3171e8db6e2SBrian Feldman 		error("Couldn't close file: %s", fx2txt(status));
3181e8db6e2SBrian Feldman 
3191e8db6e2SBrian Feldman 	buffer_free(&msg);
3201e8db6e2SBrian Feldman 
3211e8db6e2SBrian Feldman 	return(status);
3221e8db6e2SBrian Feldman }
3231e8db6e2SBrian Feldman 
3241e8db6e2SBrian Feldman 
325ae1f160dSDag-Erling Smørgrav static int
326ae1f160dSDag-Erling Smørgrav do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
3271e8db6e2SBrian Feldman     SFTP_DIRENT ***dir)
3281e8db6e2SBrian Feldman {
3291e8db6e2SBrian Feldman 	Buffer msg;
330043840dfSDag-Erling Smørgrav 	u_int count, type, id, handle_len, i, expected_id, ents = 0;
3311e8db6e2SBrian Feldman 	char *handle;
3321e8db6e2SBrian Feldman 
333ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
3341e8db6e2SBrian Feldman 
3351e8db6e2SBrian Feldman 	buffer_init(&msg);
3361e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_OPENDIR);
3371e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
3381e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, path);
339ae1f160dSDag-Erling Smørgrav 	send_msg(conn->fd_out, &msg);
3401e8db6e2SBrian Feldman 
3411e8db6e2SBrian Feldman 	buffer_clear(&msg);
3421e8db6e2SBrian Feldman 
343ae1f160dSDag-Erling Smørgrav 	handle = get_handle(conn->fd_in, id, &handle_len);
3441e8db6e2SBrian Feldman 	if (handle == NULL)
3451e8db6e2SBrian Feldman 		return(-1);
3461e8db6e2SBrian Feldman 
3471e8db6e2SBrian Feldman 	if (dir) {
3481e8db6e2SBrian Feldman 		ents = 0;
3491e8db6e2SBrian Feldman 		*dir = xmalloc(sizeof(**dir));
3501e8db6e2SBrian Feldman 		(*dir)[0] = NULL;
3511e8db6e2SBrian Feldman 	}
3521e8db6e2SBrian Feldman 
353d74d50a8SDag-Erling Smørgrav 	for (; !interrupted;) {
354ae1f160dSDag-Erling Smørgrav 		id = expected_id = conn->msg_id++;
3551e8db6e2SBrian Feldman 
356ee21a45fSDag-Erling Smørgrav 		debug3("Sending SSH2_FXP_READDIR I:%u", id);
3571e8db6e2SBrian Feldman 
3581e8db6e2SBrian Feldman 		buffer_clear(&msg);
3591e8db6e2SBrian Feldman 		buffer_put_char(&msg, SSH2_FXP_READDIR);
3601e8db6e2SBrian Feldman 		buffer_put_int(&msg, id);
3611e8db6e2SBrian Feldman 		buffer_put_string(&msg, handle, handle_len);
362ae1f160dSDag-Erling Smørgrav 		send_msg(conn->fd_out, &msg);
3631e8db6e2SBrian Feldman 
3641e8db6e2SBrian Feldman 		buffer_clear(&msg);
3651e8db6e2SBrian Feldman 
366ae1f160dSDag-Erling Smørgrav 		get_msg(conn->fd_in, &msg);
3671e8db6e2SBrian Feldman 
3681e8db6e2SBrian Feldman 		type = buffer_get_char(&msg);
3691e8db6e2SBrian Feldman 		id = buffer_get_int(&msg);
3701e8db6e2SBrian Feldman 
371ee21a45fSDag-Erling Smørgrav 		debug3("Received reply T:%u I:%u", type, id);
3721e8db6e2SBrian Feldman 
3731e8db6e2SBrian Feldman 		if (id != expected_id)
374ee21a45fSDag-Erling Smørgrav 			fatal("ID mismatch (%u != %u)", id, expected_id);
3751e8db6e2SBrian Feldman 
3761e8db6e2SBrian Feldman 		if (type == SSH2_FXP_STATUS) {
3771e8db6e2SBrian Feldman 			int status = buffer_get_int(&msg);
3781e8db6e2SBrian Feldman 
3791e8db6e2SBrian Feldman 			debug3("Received SSH2_FXP_STATUS %d", status);
3801e8db6e2SBrian Feldman 
3811e8db6e2SBrian Feldman 			if (status == SSH2_FX_EOF) {
3821e8db6e2SBrian Feldman 				break;
3831e8db6e2SBrian Feldman 			} else {
3841e8db6e2SBrian Feldman 				error("Couldn't read directory: %s",
3851e8db6e2SBrian Feldman 				    fx2txt(status));
386ae1f160dSDag-Erling Smørgrav 				do_close(conn, handle, handle_len);
387d0c8c0bcSDag-Erling Smørgrav 				xfree(handle);
3881e8db6e2SBrian Feldman 				return(status);
3891e8db6e2SBrian Feldman 			}
3901e8db6e2SBrian Feldman 		} else if (type != SSH2_FXP_NAME)
391ee21a45fSDag-Erling Smørgrav 			fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
3921e8db6e2SBrian Feldman 			    SSH2_FXP_NAME, type);
3931e8db6e2SBrian Feldman 
3941e8db6e2SBrian Feldman 		count = buffer_get_int(&msg);
3951e8db6e2SBrian Feldman 		if (count == 0)
3961e8db6e2SBrian Feldman 			break;
3971e8db6e2SBrian Feldman 		debug3("Received %d SSH2_FXP_NAME responses", count);
3981e8db6e2SBrian Feldman 		for (i = 0; i < count; i++) {
3991e8db6e2SBrian Feldman 			char *filename, *longname;
4001e8db6e2SBrian Feldman 			Attrib *a;
4011e8db6e2SBrian Feldman 
4021e8db6e2SBrian Feldman 			filename = buffer_get_string(&msg, NULL);
4031e8db6e2SBrian Feldman 			longname = buffer_get_string(&msg, NULL);
4041e8db6e2SBrian Feldman 			a = decode_attrib(&msg);
4051e8db6e2SBrian Feldman 
4061e8db6e2SBrian Feldman 			if (printflag)
4071e8db6e2SBrian Feldman 				printf("%s\n", longname);
4081e8db6e2SBrian Feldman 
4091e8db6e2SBrian Feldman 			if (dir) {
410761efaa7SDag-Erling Smørgrav 				*dir = xrealloc(*dir, ents + 2, sizeof(**dir));
4111e8db6e2SBrian Feldman 				(*dir)[ents] = xmalloc(sizeof(***dir));
4121e8db6e2SBrian Feldman 				(*dir)[ents]->filename = xstrdup(filename);
4131e8db6e2SBrian Feldman 				(*dir)[ents]->longname = xstrdup(longname);
4141e8db6e2SBrian Feldman 				memcpy(&(*dir)[ents]->a, a, sizeof(*a));
4151e8db6e2SBrian Feldman 				(*dir)[++ents] = NULL;
4161e8db6e2SBrian Feldman 			}
4171e8db6e2SBrian Feldman 
4181e8db6e2SBrian Feldman 			xfree(filename);
4191e8db6e2SBrian Feldman 			xfree(longname);
4201e8db6e2SBrian Feldman 		}
4211e8db6e2SBrian Feldman 	}
4221e8db6e2SBrian Feldman 
4231e8db6e2SBrian Feldman 	buffer_free(&msg);
424ae1f160dSDag-Erling Smørgrav 	do_close(conn, handle, handle_len);
4251e8db6e2SBrian Feldman 	xfree(handle);
4261e8db6e2SBrian Feldman 
427d74d50a8SDag-Erling Smørgrav 	/* Don't return partial matches on interrupt */
428d74d50a8SDag-Erling Smørgrav 	if (interrupted && dir != NULL && *dir != NULL) {
429d74d50a8SDag-Erling Smørgrav 		free_sftp_dirents(*dir);
430d74d50a8SDag-Erling Smørgrav 		*dir = xmalloc(sizeof(**dir));
431d74d50a8SDag-Erling Smørgrav 		**dir = NULL;
432d74d50a8SDag-Erling Smørgrav 	}
433d74d50a8SDag-Erling Smørgrav 
4341e8db6e2SBrian Feldman 	return(0);
4351e8db6e2SBrian Feldman }
4361e8db6e2SBrian Feldman 
4371e8db6e2SBrian Feldman int
438ae1f160dSDag-Erling Smørgrav do_readdir(struct sftp_conn *conn, char *path, SFTP_DIRENT ***dir)
4391e8db6e2SBrian Feldman {
440ae1f160dSDag-Erling Smørgrav 	return(do_lsreaddir(conn, path, 0, dir));
4411e8db6e2SBrian Feldman }
4421e8db6e2SBrian Feldman 
4431e8db6e2SBrian Feldman void free_sftp_dirents(SFTP_DIRENT **s)
4441e8db6e2SBrian Feldman {
4451e8db6e2SBrian Feldman 	int i;
4461e8db6e2SBrian Feldman 
4471e8db6e2SBrian Feldman 	for (i = 0; s[i]; i++) {
4481e8db6e2SBrian Feldman 		xfree(s[i]->filename);
4491e8db6e2SBrian Feldman 		xfree(s[i]->longname);
4501e8db6e2SBrian Feldman 		xfree(s[i]);
4511e8db6e2SBrian Feldman 	}
4521e8db6e2SBrian Feldman 	xfree(s);
4531e8db6e2SBrian Feldman }
4541e8db6e2SBrian Feldman 
4551e8db6e2SBrian Feldman int
456ae1f160dSDag-Erling Smørgrav do_rm(struct sftp_conn *conn, char *path)
4571e8db6e2SBrian Feldman {
4581e8db6e2SBrian Feldman 	u_int status, id;
4591e8db6e2SBrian Feldman 
4601e8db6e2SBrian Feldman 	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
4611e8db6e2SBrian Feldman 
462ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
463ae1f160dSDag-Erling Smørgrav 	send_string_request(conn->fd_out, id, SSH2_FXP_REMOVE, path,
464ae1f160dSDag-Erling Smørgrav 	    strlen(path));
465ae1f160dSDag-Erling Smørgrav 	status = get_status(conn->fd_in, id);
4661e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
4671e8db6e2SBrian Feldman 		error("Couldn't delete file: %s", fx2txt(status));
4681e8db6e2SBrian Feldman 	return(status);
4691e8db6e2SBrian Feldman }
4701e8db6e2SBrian Feldman 
4711e8db6e2SBrian Feldman int
472ae1f160dSDag-Erling Smørgrav do_mkdir(struct sftp_conn *conn, char *path, Attrib *a)
4731e8db6e2SBrian Feldman {
4741e8db6e2SBrian Feldman 	u_int status, id;
4751e8db6e2SBrian Feldman 
476ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
477ae1f160dSDag-Erling Smørgrav 	send_string_attrs_request(conn->fd_out, id, SSH2_FXP_MKDIR, path,
4781e8db6e2SBrian Feldman 	    strlen(path), a);
4791e8db6e2SBrian Feldman 
480ae1f160dSDag-Erling Smørgrav 	status = get_status(conn->fd_in, id);
4811e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
4821e8db6e2SBrian Feldman 		error("Couldn't create directory: %s", fx2txt(status));
4831e8db6e2SBrian Feldman 
4841e8db6e2SBrian Feldman 	return(status);
4851e8db6e2SBrian Feldman }
4861e8db6e2SBrian Feldman 
4871e8db6e2SBrian Feldman int
488ae1f160dSDag-Erling Smørgrav do_rmdir(struct sftp_conn *conn, char *path)
4891e8db6e2SBrian Feldman {
4901e8db6e2SBrian Feldman 	u_int status, id;
4911e8db6e2SBrian Feldman 
492ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
493ae1f160dSDag-Erling Smørgrav 	send_string_request(conn->fd_out, id, SSH2_FXP_RMDIR, path,
494ae1f160dSDag-Erling Smørgrav 	    strlen(path));
4951e8db6e2SBrian Feldman 
496ae1f160dSDag-Erling Smørgrav 	status = get_status(conn->fd_in, id);
4971e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
4981e8db6e2SBrian Feldman 		error("Couldn't remove directory: %s", fx2txt(status));
4991e8db6e2SBrian Feldman 
5001e8db6e2SBrian Feldman 	return(status);
5011e8db6e2SBrian Feldman }
5021e8db6e2SBrian Feldman 
5031e8db6e2SBrian Feldman Attrib *
504ae1f160dSDag-Erling Smørgrav do_stat(struct sftp_conn *conn, char *path, int quiet)
5051e8db6e2SBrian Feldman {
5061e8db6e2SBrian Feldman 	u_int id;
5071e8db6e2SBrian Feldman 
508ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
509ae1f160dSDag-Erling Smørgrav 
510ae1f160dSDag-Erling Smørgrav 	send_string_request(conn->fd_out, id,
511ae1f160dSDag-Erling Smørgrav 	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
512ae1f160dSDag-Erling Smørgrav 	    path, strlen(path));
513ae1f160dSDag-Erling Smørgrav 
514ae1f160dSDag-Erling Smørgrav 	return(get_decode_stat(conn->fd_in, id, quiet));
5151e8db6e2SBrian Feldman }
5161e8db6e2SBrian Feldman 
5171e8db6e2SBrian Feldman Attrib *
518ae1f160dSDag-Erling Smørgrav do_lstat(struct sftp_conn *conn, char *path, int quiet)
5191e8db6e2SBrian Feldman {
5201e8db6e2SBrian Feldman 	u_int id;
5211e8db6e2SBrian Feldman 
522ae1f160dSDag-Erling Smørgrav 	if (conn->version == 0) {
523ae1f160dSDag-Erling Smørgrav 		if (quiet)
524ae1f160dSDag-Erling Smørgrav 			debug("Server version does not support lstat operation");
525ae1f160dSDag-Erling Smørgrav 		else
526d95e11bfSDag-Erling Smørgrav 			logit("Server version does not support lstat operation");
527545d5ecaSDag-Erling Smørgrav 		return(do_stat(conn, path, quiet));
528ae1f160dSDag-Erling Smørgrav 	}
529ae1f160dSDag-Erling Smørgrav 
530ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
531ae1f160dSDag-Erling Smørgrav 	send_string_request(conn->fd_out, id, SSH2_FXP_LSTAT, path,
532ae1f160dSDag-Erling Smørgrav 	    strlen(path));
533ae1f160dSDag-Erling Smørgrav 
534ae1f160dSDag-Erling Smørgrav 	return(get_decode_stat(conn->fd_in, id, quiet));
5351e8db6e2SBrian Feldman }
5361e8db6e2SBrian Feldman 
5371e8db6e2SBrian Feldman Attrib *
538ae1f160dSDag-Erling Smørgrav do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet)
5391e8db6e2SBrian Feldman {
5401e8db6e2SBrian Feldman 	u_int id;
5411e8db6e2SBrian Feldman 
542ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
543ae1f160dSDag-Erling Smørgrav 	send_string_request(conn->fd_out, id, SSH2_FXP_FSTAT, handle,
544ae1f160dSDag-Erling Smørgrav 	    handle_len);
545ae1f160dSDag-Erling Smørgrav 
546ae1f160dSDag-Erling Smørgrav 	return(get_decode_stat(conn->fd_in, id, quiet));
5471e8db6e2SBrian Feldman }
5481e8db6e2SBrian Feldman 
5491e8db6e2SBrian Feldman int
550ae1f160dSDag-Erling Smørgrav do_setstat(struct sftp_conn *conn, char *path, Attrib *a)
5511e8db6e2SBrian Feldman {
5521e8db6e2SBrian Feldman 	u_int status, id;
5531e8db6e2SBrian Feldman 
554ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
555ae1f160dSDag-Erling Smørgrav 	send_string_attrs_request(conn->fd_out, id, SSH2_FXP_SETSTAT, path,
5561e8db6e2SBrian Feldman 	    strlen(path), a);
5571e8db6e2SBrian Feldman 
558ae1f160dSDag-Erling Smørgrav 	status = get_status(conn->fd_in, id);
5591e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
5601e8db6e2SBrian Feldman 		error("Couldn't setstat on \"%s\": %s", path,
5611e8db6e2SBrian Feldman 		    fx2txt(status));
5621e8db6e2SBrian Feldman 
5631e8db6e2SBrian Feldman 	return(status);
5641e8db6e2SBrian Feldman }
5651e8db6e2SBrian Feldman 
5661e8db6e2SBrian Feldman int
567ae1f160dSDag-Erling Smørgrav do_fsetstat(struct sftp_conn *conn, char *handle, u_int handle_len,
5681e8db6e2SBrian Feldman     Attrib *a)
5691e8db6e2SBrian Feldman {
5701e8db6e2SBrian Feldman 	u_int status, id;
5711e8db6e2SBrian Feldman 
572ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
573ae1f160dSDag-Erling Smørgrav 	send_string_attrs_request(conn->fd_out, id, SSH2_FXP_FSETSTAT, handle,
5741e8db6e2SBrian Feldman 	    handle_len, a);
5751e8db6e2SBrian Feldman 
576ae1f160dSDag-Erling Smørgrav 	status = get_status(conn->fd_in, id);
5771e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
5781e8db6e2SBrian Feldman 		error("Couldn't fsetstat: %s", fx2txt(status));
5791e8db6e2SBrian Feldman 
5801e8db6e2SBrian Feldman 	return(status);
5811e8db6e2SBrian Feldman }
5821e8db6e2SBrian Feldman 
5831e8db6e2SBrian Feldman char *
584ae1f160dSDag-Erling Smørgrav do_realpath(struct sftp_conn *conn, char *path)
5851e8db6e2SBrian Feldman {
5861e8db6e2SBrian Feldman 	Buffer msg;
5871e8db6e2SBrian Feldman 	u_int type, expected_id, count, id;
5881e8db6e2SBrian Feldman 	char *filename, *longname;
5891e8db6e2SBrian Feldman 	Attrib *a;
5901e8db6e2SBrian Feldman 
591ae1f160dSDag-Erling Smørgrav 	expected_id = id = conn->msg_id++;
592ae1f160dSDag-Erling Smørgrav 	send_string_request(conn->fd_out, id, SSH2_FXP_REALPATH, path,
593ae1f160dSDag-Erling Smørgrav 	    strlen(path));
5941e8db6e2SBrian Feldman 
5951e8db6e2SBrian Feldman 	buffer_init(&msg);
5961e8db6e2SBrian Feldman 
597ae1f160dSDag-Erling Smørgrav 	get_msg(conn->fd_in, &msg);
5981e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
5991e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
6001e8db6e2SBrian Feldman 
6011e8db6e2SBrian Feldman 	if (id != expected_id)
602ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
6031e8db6e2SBrian Feldman 
6041e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
6051e8db6e2SBrian Feldman 		u_int status = buffer_get_int(&msg);
6061e8db6e2SBrian Feldman 
6071e8db6e2SBrian Feldman 		error("Couldn't canonicalise: %s", fx2txt(status));
6081e8db6e2SBrian Feldman 		return(NULL);
6091e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_NAME)
610ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
6111e8db6e2SBrian Feldman 		    SSH2_FXP_NAME, type);
6121e8db6e2SBrian Feldman 
6131e8db6e2SBrian Feldman 	count = buffer_get_int(&msg);
6141e8db6e2SBrian Feldman 	if (count != 1)
6151e8db6e2SBrian Feldman 		fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
6161e8db6e2SBrian Feldman 
6171e8db6e2SBrian Feldman 	filename = buffer_get_string(&msg, NULL);
6181e8db6e2SBrian Feldman 	longname = buffer_get_string(&msg, NULL);
6191e8db6e2SBrian Feldman 	a = decode_attrib(&msg);
6201e8db6e2SBrian Feldman 
6211e8db6e2SBrian Feldman 	debug3("SSH_FXP_REALPATH %s -> %s", path, filename);
6221e8db6e2SBrian Feldman 
6231e8db6e2SBrian Feldman 	xfree(longname);
6241e8db6e2SBrian Feldman 
6251e8db6e2SBrian Feldman 	buffer_free(&msg);
6261e8db6e2SBrian Feldman 
6271e8db6e2SBrian Feldman 	return(filename);
6281e8db6e2SBrian Feldman }
6291e8db6e2SBrian Feldman 
6301e8db6e2SBrian Feldman int
631ae1f160dSDag-Erling Smørgrav do_rename(struct sftp_conn *conn, char *oldpath, char *newpath)
6321e8db6e2SBrian Feldman {
6331e8db6e2SBrian Feldman 	Buffer msg;
6341e8db6e2SBrian Feldman 	u_int status, id;
6351e8db6e2SBrian Feldman 
6361e8db6e2SBrian Feldman 	buffer_init(&msg);
6371e8db6e2SBrian Feldman 
6381e8db6e2SBrian Feldman 	/* Send rename request */
639ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6401e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_RENAME);
6411e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
6421e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, oldpath);
6431e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, newpath);
644ae1f160dSDag-Erling Smørgrav 	send_msg(conn->fd_out, &msg);
6451e8db6e2SBrian Feldman 	debug3("Sent message SSH2_FXP_RENAME \"%s\" -> \"%s\"", oldpath,
6461e8db6e2SBrian Feldman 	    newpath);
6471e8db6e2SBrian Feldman 	buffer_free(&msg);
6481e8db6e2SBrian Feldman 
649ae1f160dSDag-Erling Smørgrav 	status = get_status(conn->fd_in, id);
6501e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
651ae1f160dSDag-Erling Smørgrav 		error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath,
652ae1f160dSDag-Erling Smørgrav 		    newpath, fx2txt(status));
6531e8db6e2SBrian Feldman 
6541e8db6e2SBrian Feldman 	return(status);
6551e8db6e2SBrian Feldman }
6561e8db6e2SBrian Feldman 
6571e8db6e2SBrian Feldman int
658ae1f160dSDag-Erling Smørgrav do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath)
6591e8db6e2SBrian Feldman {
6601e8db6e2SBrian Feldman 	Buffer msg;
6611e8db6e2SBrian Feldman 	u_int status, id;
6621e8db6e2SBrian Feldman 
663ae1f160dSDag-Erling Smørgrav 	if (conn->version < 3) {
664ae1f160dSDag-Erling Smørgrav 		error("This server does not support the symlink operation");
665ae1f160dSDag-Erling Smørgrav 		return(SSH2_FX_OP_UNSUPPORTED);
666ae1f160dSDag-Erling Smørgrav 	}
667ae1f160dSDag-Erling Smørgrav 
6681e8db6e2SBrian Feldman 	buffer_init(&msg);
6691e8db6e2SBrian Feldman 
670d74d50a8SDag-Erling Smørgrav 	/* Send symlink request */
671ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
6721e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_SYMLINK);
6731e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
6741e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, oldpath);
6751e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, newpath);
676ae1f160dSDag-Erling Smørgrav 	send_msg(conn->fd_out, &msg);
6771e8db6e2SBrian Feldman 	debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
6781e8db6e2SBrian Feldman 	    newpath);
6791e8db6e2SBrian Feldman 	buffer_free(&msg);
6801e8db6e2SBrian Feldman 
681ae1f160dSDag-Erling Smørgrav 	status = get_status(conn->fd_in, id);
6821e8db6e2SBrian Feldman 	if (status != SSH2_FX_OK)
683d0c8c0bcSDag-Erling Smørgrav 		error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath,
684ae1f160dSDag-Erling Smørgrav 		    newpath, fx2txt(status));
6851e8db6e2SBrian Feldman 
6861e8db6e2SBrian Feldman 	return(status);
6871e8db6e2SBrian Feldman }
6881e8db6e2SBrian Feldman 
6891e8db6e2SBrian Feldman char *
690ae1f160dSDag-Erling Smørgrav do_readlink(struct sftp_conn *conn, char *path)
6911e8db6e2SBrian Feldman {
6921e8db6e2SBrian Feldman 	Buffer msg;
6931e8db6e2SBrian Feldman 	u_int type, expected_id, count, id;
6941e8db6e2SBrian Feldman 	char *filename, *longname;
6951e8db6e2SBrian Feldman 	Attrib *a;
6961e8db6e2SBrian Feldman 
697ae1f160dSDag-Erling Smørgrav 	expected_id = id = conn->msg_id++;
698ae1f160dSDag-Erling Smørgrav 	send_string_request(conn->fd_out, id, SSH2_FXP_READLINK, path,
699ae1f160dSDag-Erling Smørgrav 	    strlen(path));
7001e8db6e2SBrian Feldman 
7011e8db6e2SBrian Feldman 	buffer_init(&msg);
7021e8db6e2SBrian Feldman 
703ae1f160dSDag-Erling Smørgrav 	get_msg(conn->fd_in, &msg);
7041e8db6e2SBrian Feldman 	type = buffer_get_char(&msg);
7051e8db6e2SBrian Feldman 	id = buffer_get_int(&msg);
7061e8db6e2SBrian Feldman 
7071e8db6e2SBrian Feldman 	if (id != expected_id)
708ee21a45fSDag-Erling Smørgrav 		fatal("ID mismatch (%u != %u)", id, expected_id);
7091e8db6e2SBrian Feldman 
7101e8db6e2SBrian Feldman 	if (type == SSH2_FXP_STATUS) {
7111e8db6e2SBrian Feldman 		u_int status = buffer_get_int(&msg);
7121e8db6e2SBrian Feldman 
7131e8db6e2SBrian Feldman 		error("Couldn't readlink: %s", fx2txt(status));
7141e8db6e2SBrian Feldman 		return(NULL);
7151e8db6e2SBrian Feldman 	} else if (type != SSH2_FXP_NAME)
716ee21a45fSDag-Erling Smørgrav 		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
7171e8db6e2SBrian Feldman 		    SSH2_FXP_NAME, type);
7181e8db6e2SBrian Feldman 
7191e8db6e2SBrian Feldman 	count = buffer_get_int(&msg);
7201e8db6e2SBrian Feldman 	if (count != 1)
7211e8db6e2SBrian Feldman 		fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
7221e8db6e2SBrian Feldman 
7231e8db6e2SBrian Feldman 	filename = buffer_get_string(&msg, NULL);
7241e8db6e2SBrian Feldman 	longname = buffer_get_string(&msg, NULL);
7251e8db6e2SBrian Feldman 	a = decode_attrib(&msg);
7261e8db6e2SBrian Feldman 
7271e8db6e2SBrian Feldman 	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
7281e8db6e2SBrian Feldman 
7291e8db6e2SBrian Feldman 	xfree(longname);
7301e8db6e2SBrian Feldman 
7311e8db6e2SBrian Feldman 	buffer_free(&msg);
7321e8db6e2SBrian Feldman 
7331e8db6e2SBrian Feldman 	return(filename);
7341e8db6e2SBrian Feldman }
7351e8db6e2SBrian Feldman 
736ae1f160dSDag-Erling Smørgrav static void
737ae1f160dSDag-Erling Smørgrav send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len,
738ae1f160dSDag-Erling Smørgrav     char *handle, u_int handle_len)
739ae1f160dSDag-Erling Smørgrav {
740ae1f160dSDag-Erling Smørgrav 	Buffer msg;
741ae1f160dSDag-Erling Smørgrav 
742ae1f160dSDag-Erling Smørgrav 	buffer_init(&msg);
743ae1f160dSDag-Erling Smørgrav 	buffer_clear(&msg);
744ae1f160dSDag-Erling Smørgrav 	buffer_put_char(&msg, SSH2_FXP_READ);
745ae1f160dSDag-Erling Smørgrav 	buffer_put_int(&msg, id);
746ae1f160dSDag-Erling Smørgrav 	buffer_put_string(&msg, handle, handle_len);
747ae1f160dSDag-Erling Smørgrav 	buffer_put_int64(&msg, offset);
748ae1f160dSDag-Erling Smørgrav 	buffer_put_int(&msg, len);
749ae1f160dSDag-Erling Smørgrav 	send_msg(fd_out, &msg);
750ae1f160dSDag-Erling Smørgrav 	buffer_free(&msg);
751ae1f160dSDag-Erling Smørgrav }
752ae1f160dSDag-Erling Smørgrav 
7531e8db6e2SBrian Feldman int
754ae1f160dSDag-Erling Smørgrav do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
7551e8db6e2SBrian Feldman     int pflag)
7561e8db6e2SBrian Feldman {
7571e8db6e2SBrian Feldman 	Attrib junk, *a;
758ae1f160dSDag-Erling Smørgrav 	Buffer msg;
759ae1f160dSDag-Erling Smørgrav 	char *handle;
760043840dfSDag-Erling Smørgrav 	int local_fd, status = 0, write_error;
761ae1f160dSDag-Erling Smørgrav 	int read_error, write_errno;
762ae1f160dSDag-Erling Smørgrav 	u_int64_t offset, size;
763043840dfSDag-Erling Smørgrav 	u_int handle_len, mode, type, id, buflen, num_req, max_req;
764d0c8c0bcSDag-Erling Smørgrav 	off_t progress_counter;
765ae1f160dSDag-Erling Smørgrav 	struct request {
766ae1f160dSDag-Erling Smørgrav 		u_int id;
767ae1f160dSDag-Erling Smørgrav 		u_int len;
768ae1f160dSDag-Erling Smørgrav 		u_int64_t offset;
769ae1f160dSDag-Erling Smørgrav 		TAILQ_ENTRY(request) tq;
770ae1f160dSDag-Erling Smørgrav 	};
771ae1f160dSDag-Erling Smørgrav 	TAILQ_HEAD(reqhead, request) requests;
772ae1f160dSDag-Erling Smørgrav 	struct request *req;
7731e8db6e2SBrian Feldman 
774ae1f160dSDag-Erling Smørgrav 	TAILQ_INIT(&requests);
775ae1f160dSDag-Erling Smørgrav 
776ae1f160dSDag-Erling Smørgrav 	a = do_stat(conn, remote_path, 0);
7771e8db6e2SBrian Feldman 	if (a == NULL)
7781e8db6e2SBrian Feldman 		return(-1);
7791e8db6e2SBrian Feldman 
7801e8db6e2SBrian Feldman 	/* XXX: should we preserve set[ug]id? */
7811e8db6e2SBrian Feldman 	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
782d0c8c0bcSDag-Erling Smørgrav 		mode = a->perm & 0777;
7831e8db6e2SBrian Feldman 	else
7841e8db6e2SBrian Feldman 		mode = 0666;
7851e8db6e2SBrian Feldman 
7861e8db6e2SBrian Feldman 	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
787d0c8c0bcSDag-Erling Smørgrav 	    (!S_ISREG(a->perm))) {
788d0c8c0bcSDag-Erling Smørgrav 		error("Cannot download non-regular file: %s", remote_path);
7891e8db6e2SBrian Feldman 		return(-1);
7901e8db6e2SBrian Feldman 	}
7911e8db6e2SBrian Feldman 
792ae1f160dSDag-Erling Smørgrav 	if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
793ae1f160dSDag-Erling Smørgrav 		size = a->size;
794ae1f160dSDag-Erling Smørgrav 	else
795ae1f160dSDag-Erling Smørgrav 		size = 0;
7961e8db6e2SBrian Feldman 
797ae1f160dSDag-Erling Smørgrav 	buflen = conn->transfer_buflen;
7981e8db6e2SBrian Feldman 	buffer_init(&msg);
7991e8db6e2SBrian Feldman 
8001e8db6e2SBrian Feldman 	/* Send open request */
801ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
8021e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_OPEN);
8031e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
8041e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, remote_path);
8051e8db6e2SBrian Feldman 	buffer_put_int(&msg, SSH2_FXF_READ);
8061e8db6e2SBrian Feldman 	attrib_clear(&junk); /* Send empty attributes */
8071e8db6e2SBrian Feldman 	encode_attrib(&msg, &junk);
808ae1f160dSDag-Erling Smørgrav 	send_msg(conn->fd_out, &msg);
809ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
8101e8db6e2SBrian Feldman 
811ae1f160dSDag-Erling Smørgrav 	handle = get_handle(conn->fd_in, id, &handle_len);
8121e8db6e2SBrian Feldman 	if (handle == NULL) {
8131e8db6e2SBrian Feldman 		buffer_free(&msg);
814ae1f160dSDag-Erling Smørgrav 		return(-1);
815ae1f160dSDag-Erling Smørgrav 	}
816ae1f160dSDag-Erling Smørgrav 
817d0c8c0bcSDag-Erling Smørgrav 	local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC,
818d0c8c0bcSDag-Erling Smørgrav 	    mode | S_IWRITE);
819ae1f160dSDag-Erling Smørgrav 	if (local_fd == -1) {
820ae1f160dSDag-Erling Smørgrav 		error("Couldn't open local file \"%s\" for writing: %s",
821ae1f160dSDag-Erling Smørgrav 		    local_path, strerror(errno));
822ae1f160dSDag-Erling Smørgrav 		buffer_free(&msg);
823ae1f160dSDag-Erling Smørgrav 		xfree(handle);
8241e8db6e2SBrian Feldman 		return(-1);
8251e8db6e2SBrian Feldman 	}
8261e8db6e2SBrian Feldman 
8271e8db6e2SBrian Feldman 	/* Read from remote and write to local */
828ae1f160dSDag-Erling Smørgrav 	write_error = read_error = write_errno = num_req = offset = 0;
829ae1f160dSDag-Erling Smørgrav 	max_req = 1;
830d0c8c0bcSDag-Erling Smørgrav 	progress_counter = 0;
831d0c8c0bcSDag-Erling Smørgrav 
83252028650SDag-Erling Smørgrav 	if (showprogress && size != 0)
83352028650SDag-Erling Smørgrav 		start_progress_meter(remote_path, size, &progress_counter);
834d0c8c0bcSDag-Erling Smørgrav 
835ae1f160dSDag-Erling Smørgrav 	while (num_req > 0 || max_req > 0) {
8361e8db6e2SBrian Feldman 		char *data;
837ae1f160dSDag-Erling Smørgrav 		u_int len;
8381e8db6e2SBrian Feldman 
839d74d50a8SDag-Erling Smørgrav 		/*
840d74d50a8SDag-Erling Smørgrav 		 * Simulate EOF on interrupt: stop sending new requests and
841d74d50a8SDag-Erling Smørgrav 		 * allow outstanding requests to drain gracefully
842d74d50a8SDag-Erling Smørgrav 		 */
843d74d50a8SDag-Erling Smørgrav 		if (interrupted) {
844d74d50a8SDag-Erling Smørgrav 			if (num_req == 0) /* If we haven't started yet... */
845d74d50a8SDag-Erling Smørgrav 				break;
846d74d50a8SDag-Erling Smørgrav 			max_req = 0;
847d74d50a8SDag-Erling Smørgrav 		}
848d74d50a8SDag-Erling Smørgrav 
849ae1f160dSDag-Erling Smørgrav 		/* Send some more requests */
850ae1f160dSDag-Erling Smørgrav 		while (num_req < max_req) {
851ae1f160dSDag-Erling Smørgrav 			debug3("Request range %llu -> %llu (%d/%d)",
852545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)offset,
853545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)offset + buflen - 1,
854545d5ecaSDag-Erling Smørgrav 			    num_req, max_req);
855ae1f160dSDag-Erling Smørgrav 			req = xmalloc(sizeof(*req));
856ae1f160dSDag-Erling Smørgrav 			req->id = conn->msg_id++;
857ae1f160dSDag-Erling Smørgrav 			req->len = buflen;
858ae1f160dSDag-Erling Smørgrav 			req->offset = offset;
859ae1f160dSDag-Erling Smørgrav 			offset += buflen;
860ae1f160dSDag-Erling Smørgrav 			num_req++;
861ae1f160dSDag-Erling Smørgrav 			TAILQ_INSERT_TAIL(&requests, req, tq);
862ae1f160dSDag-Erling Smørgrav 			send_read_request(conn->fd_out, req->id, req->offset,
863ae1f160dSDag-Erling Smørgrav 			    req->len, handle, handle_len);
864ae1f160dSDag-Erling Smørgrav 		}
8651e8db6e2SBrian Feldman 
8661e8db6e2SBrian Feldman 		buffer_clear(&msg);
867ae1f160dSDag-Erling Smørgrav 		get_msg(conn->fd_in, &msg);
8681e8db6e2SBrian Feldman 		type = buffer_get_char(&msg);
8691e8db6e2SBrian Feldman 		id = buffer_get_int(&msg);
870ee21a45fSDag-Erling Smørgrav 		debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
8711e8db6e2SBrian Feldman 
872ae1f160dSDag-Erling Smørgrav 		/* Find the request in our queue */
873ae1f160dSDag-Erling Smørgrav 		for (req = TAILQ_FIRST(&requests);
874ae1f160dSDag-Erling Smørgrav 		    req != NULL && req->id != id;
875ae1f160dSDag-Erling Smørgrav 		    req = TAILQ_NEXT(req, tq))
876ae1f160dSDag-Erling Smørgrav 			;
877ae1f160dSDag-Erling Smørgrav 		if (req == NULL)
878ae1f160dSDag-Erling Smørgrav 			fatal("Unexpected reply %u", id);
879ae1f160dSDag-Erling Smørgrav 
880ae1f160dSDag-Erling Smørgrav 		switch (type) {
881ae1f160dSDag-Erling Smørgrav 		case SSH2_FXP_STATUS:
882ae1f160dSDag-Erling Smørgrav 			status = buffer_get_int(&msg);
883ae1f160dSDag-Erling Smørgrav 			if (status != SSH2_FX_EOF)
884ae1f160dSDag-Erling Smørgrav 				read_error = 1;
885ae1f160dSDag-Erling Smørgrav 			max_req = 0;
886ae1f160dSDag-Erling Smørgrav 			TAILQ_REMOVE(&requests, req, tq);
887ae1f160dSDag-Erling Smørgrav 			xfree(req);
888ae1f160dSDag-Erling Smørgrav 			num_req--;
8891e8db6e2SBrian Feldman 			break;
890ae1f160dSDag-Erling Smørgrav 		case SSH2_FXP_DATA:
891ae1f160dSDag-Erling Smørgrav 			data = buffer_get_string(&msg, &len);
892545d5ecaSDag-Erling Smørgrav 			debug3("Received data %llu -> %llu",
893545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)req->offset,
894545d5ecaSDag-Erling Smørgrav 			    (unsigned long long)req->offset + len - 1);
895ae1f160dSDag-Erling Smørgrav 			if (len > req->len)
896ae1f160dSDag-Erling Smørgrav 				fatal("Received more data than asked for "
897ee21a45fSDag-Erling Smørgrav 				    "%u > %u", len, req->len);
898ae1f160dSDag-Erling Smørgrav 			if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
899d95e11bfSDag-Erling Smørgrav 			    atomicio(vwrite, local_fd, data, len) != len) &&
900ae1f160dSDag-Erling Smørgrav 			    !write_error) {
901ae1f160dSDag-Erling Smørgrav 				write_errno = errno;
902ae1f160dSDag-Erling Smørgrav 				write_error = 1;
903ae1f160dSDag-Erling Smørgrav 				max_req = 0;
9041e8db6e2SBrian Feldman 			}
905d0c8c0bcSDag-Erling Smørgrav 			progress_counter += len;
906ae1f160dSDag-Erling Smørgrav 			xfree(data);
907ae1f160dSDag-Erling Smørgrav 
908ae1f160dSDag-Erling Smørgrav 			if (len == req->len) {
909ae1f160dSDag-Erling Smørgrav 				TAILQ_REMOVE(&requests, req, tq);
910ae1f160dSDag-Erling Smørgrav 				xfree(req);
911ae1f160dSDag-Erling Smørgrav 				num_req--;
912ae1f160dSDag-Erling Smørgrav 			} else {
913ae1f160dSDag-Erling Smørgrav 				/* Resend the request for the missing data */
914ae1f160dSDag-Erling Smørgrav 				debug3("Short data block, re-requesting "
915545d5ecaSDag-Erling Smørgrav 				    "%llu -> %llu (%2d)",
916545d5ecaSDag-Erling Smørgrav 				    (unsigned long long)req->offset + len,
917545d5ecaSDag-Erling Smørgrav 				    (unsigned long long)req->offset +
918545d5ecaSDag-Erling Smørgrav 				    req->len - 1, num_req);
919ae1f160dSDag-Erling Smørgrav 				req->id = conn->msg_id++;
920ae1f160dSDag-Erling Smørgrav 				req->len -= len;
921ae1f160dSDag-Erling Smørgrav 				req->offset += len;
922ae1f160dSDag-Erling Smørgrav 				send_read_request(conn->fd_out, req->id,
923ae1f160dSDag-Erling Smørgrav 				    req->offset, req->len, handle, handle_len);
924ae1f160dSDag-Erling Smørgrav 				/* Reduce the request size */
925ae1f160dSDag-Erling Smørgrav 				if (len < buflen)
926ae1f160dSDag-Erling Smørgrav 					buflen = MAX(MIN_READ_SIZE, len);
927ae1f160dSDag-Erling Smørgrav 			}
928ae1f160dSDag-Erling Smørgrav 			if (max_req > 0) { /* max_req = 0 iff EOF received */
929ae1f160dSDag-Erling Smørgrav 				if (size > 0 && offset > size) {
930ae1f160dSDag-Erling Smørgrav 					/* Only one request at a time
931ae1f160dSDag-Erling Smørgrav 					 * after the expected EOF */
932ae1f160dSDag-Erling Smørgrav 					debug3("Finish at %llu (%2d)",
933545d5ecaSDag-Erling Smørgrav 					    (unsigned long long)offset,
934545d5ecaSDag-Erling Smørgrav 					    num_req);
935ae1f160dSDag-Erling Smørgrav 					max_req = 1;
936d74d50a8SDag-Erling Smørgrav 				} else if (max_req <= conn->num_requests) {
937ae1f160dSDag-Erling Smørgrav 					++max_req;
938ae1f160dSDag-Erling Smørgrav 				}
939ae1f160dSDag-Erling Smørgrav 			}
940ae1f160dSDag-Erling Smørgrav 			break;
941ae1f160dSDag-Erling Smørgrav 		default:
942ee21a45fSDag-Erling Smørgrav 			fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
9431e8db6e2SBrian Feldman 			    SSH2_FXP_DATA, type);
9441e8db6e2SBrian Feldman 		}
945ae1f160dSDag-Erling Smørgrav 	}
9461e8db6e2SBrian Feldman 
947d0c8c0bcSDag-Erling Smørgrav 	if (showprogress && size)
948d0c8c0bcSDag-Erling Smørgrav 		stop_progress_meter();
949d0c8c0bcSDag-Erling Smørgrav 
950ae1f160dSDag-Erling Smørgrav 	/* Sanity check */
951ae1f160dSDag-Erling Smørgrav 	if (TAILQ_FIRST(&requests) != NULL)
952ae1f160dSDag-Erling Smørgrav 		fatal("Transfer complete, but requests still in queue");
9531e8db6e2SBrian Feldman 
954ae1f160dSDag-Erling Smørgrav 	if (read_error) {
955ae1f160dSDag-Erling Smørgrav 		error("Couldn't read from remote file \"%s\" : %s",
956ae1f160dSDag-Erling Smørgrav 		    remote_path, fx2txt(status));
957ae1f160dSDag-Erling Smørgrav 		do_close(conn, handle, handle_len);
958ae1f160dSDag-Erling Smørgrav 	} else if (write_error) {
9591e8db6e2SBrian Feldman 		error("Couldn't write to \"%s\": %s", local_path,
960ae1f160dSDag-Erling Smørgrav 		    strerror(write_errno));
9611e8db6e2SBrian Feldman 		status = -1;
962ae1f160dSDag-Erling Smørgrav 		do_close(conn, handle, handle_len);
963ae1f160dSDag-Erling Smørgrav 	} else {
964ae1f160dSDag-Erling Smørgrav 		status = do_close(conn, handle, handle_len);
9651e8db6e2SBrian Feldman 
9661e8db6e2SBrian Feldman 		/* Override umask and utimes if asked */
96783d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD
9681e8db6e2SBrian Feldman 		if (pflag && fchmod(local_fd, mode) == -1)
96983d2307dSDag-Erling Smørgrav #else
97083d2307dSDag-Erling Smørgrav 		if (pflag && chmod(local_path, mode) == -1)
97183d2307dSDag-Erling Smørgrav #endif /* HAVE_FCHMOD */
9721e8db6e2SBrian Feldman 			error("Couldn't set mode on \"%s\": %s", local_path,
9731e8db6e2SBrian Feldman 			    strerror(errno));
9741e8db6e2SBrian Feldman 		if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
9751e8db6e2SBrian Feldman 			struct timeval tv[2];
9761e8db6e2SBrian Feldman 			tv[0].tv_sec = a->atime;
9771e8db6e2SBrian Feldman 			tv[1].tv_sec = a->mtime;
9781e8db6e2SBrian Feldman 			tv[0].tv_usec = tv[1].tv_usec = 0;
9791e8db6e2SBrian Feldman 			if (utimes(local_path, tv) == -1)
980ae1f160dSDag-Erling Smørgrav 				error("Can't set times on \"%s\": %s",
981ae1f160dSDag-Erling Smørgrav 				    local_path, strerror(errno));
9821e8db6e2SBrian Feldman 		}
983ae1f160dSDag-Erling Smørgrav 	}
9841e8db6e2SBrian Feldman 	close(local_fd);
9851e8db6e2SBrian Feldman 	buffer_free(&msg);
9861e8db6e2SBrian Feldman 	xfree(handle);
987ae1f160dSDag-Erling Smørgrav 
988ae1f160dSDag-Erling Smørgrav 	return(status);
9891e8db6e2SBrian Feldman }
9901e8db6e2SBrian Feldman 
9911e8db6e2SBrian Feldman int
992ae1f160dSDag-Erling Smørgrav do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
9931e8db6e2SBrian Feldman     int pflag)
9941e8db6e2SBrian Feldman {
995ae1f160dSDag-Erling Smørgrav 	int local_fd, status;
996ae1f160dSDag-Erling Smørgrav 	u_int handle_len, id, type;
9971e8db6e2SBrian Feldman 	u_int64_t offset;
998ae1f160dSDag-Erling Smørgrav 	char *handle, *data;
9991e8db6e2SBrian Feldman 	Buffer msg;
10001e8db6e2SBrian Feldman 	struct stat sb;
10011e8db6e2SBrian Feldman 	Attrib a;
1002ae1f160dSDag-Erling Smørgrav 	u_int32_t startid;
1003ae1f160dSDag-Erling Smørgrav 	u_int32_t ackid;
1004ae1f160dSDag-Erling Smørgrav 	struct outstanding_ack {
1005ae1f160dSDag-Erling Smørgrav 		u_int id;
1006ae1f160dSDag-Erling Smørgrav 		u_int len;
1007ae1f160dSDag-Erling Smørgrav 		u_int64_t offset;
1008ae1f160dSDag-Erling Smørgrav 		TAILQ_ENTRY(outstanding_ack) tq;
1009ae1f160dSDag-Erling Smørgrav 	};
1010ae1f160dSDag-Erling Smørgrav 	TAILQ_HEAD(ackhead, outstanding_ack) acks;
1011d74d50a8SDag-Erling Smørgrav 	struct outstanding_ack *ack = NULL;
1012ae1f160dSDag-Erling Smørgrav 
1013ae1f160dSDag-Erling Smørgrav 	TAILQ_INIT(&acks);
10141e8db6e2SBrian Feldman 
10151e8db6e2SBrian Feldman 	if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
10161e8db6e2SBrian Feldman 		error("Couldn't open local file \"%s\" for reading: %s",
10171e8db6e2SBrian Feldman 		    local_path, strerror(errno));
10181e8db6e2SBrian Feldman 		return(-1);
10191e8db6e2SBrian Feldman 	}
10201e8db6e2SBrian Feldman 	if (fstat(local_fd, &sb) == -1) {
10211e8db6e2SBrian Feldman 		error("Couldn't fstat local file \"%s\": %s",
10221e8db6e2SBrian Feldman 		    local_path, strerror(errno));
10231e8db6e2SBrian Feldman 		close(local_fd);
10241e8db6e2SBrian Feldman 		return(-1);
10251e8db6e2SBrian Feldman 	}
1026d0c8c0bcSDag-Erling Smørgrav 	if (!S_ISREG(sb.st_mode)) {
1027d0c8c0bcSDag-Erling Smørgrav 		error("%s is not a regular file", local_path);
1028d0c8c0bcSDag-Erling Smørgrav 		close(local_fd);
1029d0c8c0bcSDag-Erling Smørgrav 		return(-1);
1030d0c8c0bcSDag-Erling Smørgrav 	}
10311e8db6e2SBrian Feldman 	stat_to_attrib(&sb, &a);
10321e8db6e2SBrian Feldman 
10331e8db6e2SBrian Feldman 	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
10341e8db6e2SBrian Feldman 	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
10351e8db6e2SBrian Feldman 	a.perm &= 0777;
10361e8db6e2SBrian Feldman 	if (!pflag)
10371e8db6e2SBrian Feldman 		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
10381e8db6e2SBrian Feldman 
10391e8db6e2SBrian Feldman 	buffer_init(&msg);
10401e8db6e2SBrian Feldman 
10411e8db6e2SBrian Feldman 	/* Send open request */
1042ae1f160dSDag-Erling Smørgrav 	id = conn->msg_id++;
10431e8db6e2SBrian Feldman 	buffer_put_char(&msg, SSH2_FXP_OPEN);
10441e8db6e2SBrian Feldman 	buffer_put_int(&msg, id);
10451e8db6e2SBrian Feldman 	buffer_put_cstring(&msg, remote_path);
10461e8db6e2SBrian Feldman 	buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC);
10471e8db6e2SBrian Feldman 	encode_attrib(&msg, &a);
1048ae1f160dSDag-Erling Smørgrav 	send_msg(conn->fd_out, &msg);
1049ee21a45fSDag-Erling Smørgrav 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
10501e8db6e2SBrian Feldman 
10511e8db6e2SBrian Feldman 	buffer_clear(&msg);
10521e8db6e2SBrian Feldman 
1053ae1f160dSDag-Erling Smørgrav 	handle = get_handle(conn->fd_in, id, &handle_len);
10541e8db6e2SBrian Feldman 	if (handle == NULL) {
10551e8db6e2SBrian Feldman 		close(local_fd);
10561e8db6e2SBrian Feldman 		buffer_free(&msg);
10571e8db6e2SBrian Feldman 		return(-1);
10581e8db6e2SBrian Feldman 	}
10591e8db6e2SBrian Feldman 
1060ae1f160dSDag-Erling Smørgrav 	startid = ackid = id + 1;
1061ae1f160dSDag-Erling Smørgrav 	data = xmalloc(conn->transfer_buflen);
1062ae1f160dSDag-Erling Smørgrav 
10631e8db6e2SBrian Feldman 	/* Read from local and write to remote */
10641e8db6e2SBrian Feldman 	offset = 0;
1065d0c8c0bcSDag-Erling Smørgrav 	if (showprogress)
1066d0c8c0bcSDag-Erling Smørgrav 		start_progress_meter(local_path, sb.st_size, &offset);
1067d0c8c0bcSDag-Erling Smørgrav 
10681e8db6e2SBrian Feldman 	for (;;) {
10691e8db6e2SBrian Feldman 		int len;
10701e8db6e2SBrian Feldman 
10711e8db6e2SBrian Feldman 		/*
1072d74d50a8SDag-Erling Smørgrav 		 * Can't use atomicio here because it returns 0 on EOF,
1073d74d50a8SDag-Erling Smørgrav 		 * thus losing the last block of the file.
1074d74d50a8SDag-Erling Smørgrav 		 * Simulate an EOF on interrupt, allowing ACKs from the
1075d74d50a8SDag-Erling Smørgrav 		 * server to drain.
10761e8db6e2SBrian Feldman 		 */
1077d74d50a8SDag-Erling Smørgrav 		if (interrupted)
1078d74d50a8SDag-Erling Smørgrav 			len = 0;
1079d74d50a8SDag-Erling Smørgrav 		else do
1080ae1f160dSDag-Erling Smørgrav 			len = read(local_fd, data, conn->transfer_buflen);
10811e8db6e2SBrian Feldman 		while ((len == -1) && (errno == EINTR || errno == EAGAIN));
10821e8db6e2SBrian Feldman 
10831e8db6e2SBrian Feldman 		if (len == -1)
10841e8db6e2SBrian Feldman 			fatal("Couldn't read from \"%s\": %s", local_path,
10851e8db6e2SBrian Feldman 			    strerror(errno));
1086ae1f160dSDag-Erling Smørgrav 
1087ae1f160dSDag-Erling Smørgrav 		if (len != 0) {
1088ae1f160dSDag-Erling Smørgrav 			ack = xmalloc(sizeof(*ack));
1089ae1f160dSDag-Erling Smørgrav 			ack->id = ++id;
1090ae1f160dSDag-Erling Smørgrav 			ack->offset = offset;
1091ae1f160dSDag-Erling Smørgrav 			ack->len = len;
1092ae1f160dSDag-Erling Smørgrav 			TAILQ_INSERT_TAIL(&acks, ack, tq);
10931e8db6e2SBrian Feldman 
10941e8db6e2SBrian Feldman 			buffer_clear(&msg);
10951e8db6e2SBrian Feldman 			buffer_put_char(&msg, SSH2_FXP_WRITE);
1096ae1f160dSDag-Erling Smørgrav 			buffer_put_int(&msg, ack->id);
10971e8db6e2SBrian Feldman 			buffer_put_string(&msg, handle, handle_len);
10981e8db6e2SBrian Feldman 			buffer_put_int64(&msg, offset);
10991e8db6e2SBrian Feldman 			buffer_put_string(&msg, data, len);
1100ae1f160dSDag-Erling Smørgrav 			send_msg(conn->fd_out, &msg);
1101ee21a45fSDag-Erling Smørgrav 			debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
1102545d5ecaSDag-Erling Smørgrav 			    id, (unsigned long long)offset, len);
1103ae1f160dSDag-Erling Smørgrav 		} else if (TAILQ_FIRST(&acks) == NULL)
1104ae1f160dSDag-Erling Smørgrav 			break;
11051e8db6e2SBrian Feldman 
1106ae1f160dSDag-Erling Smørgrav 		if (ack == NULL)
1107ae1f160dSDag-Erling Smørgrav 			fatal("Unexpected ACK %u", id);
1108ae1f160dSDag-Erling Smørgrav 
1109ae1f160dSDag-Erling Smørgrav 		if (id == startid || len == 0 ||
1110ae1f160dSDag-Erling Smørgrav 		    id - ackid >= conn->num_requests) {
1111545d5ecaSDag-Erling Smørgrav 			u_int r_id;
1112545d5ecaSDag-Erling Smørgrav 
1113ae1f160dSDag-Erling Smørgrav 			buffer_clear(&msg);
1114ae1f160dSDag-Erling Smørgrav 			get_msg(conn->fd_in, &msg);
1115ae1f160dSDag-Erling Smørgrav 			type = buffer_get_char(&msg);
1116545d5ecaSDag-Erling Smørgrav 			r_id = buffer_get_int(&msg);
1117ae1f160dSDag-Erling Smørgrav 
1118ae1f160dSDag-Erling Smørgrav 			if (type != SSH2_FXP_STATUS)
1119ae1f160dSDag-Erling Smørgrav 				fatal("Expected SSH2_FXP_STATUS(%d) packet, "
1120ae1f160dSDag-Erling Smørgrav 				    "got %d", SSH2_FXP_STATUS, type);
1121ae1f160dSDag-Erling Smørgrav 
1122ae1f160dSDag-Erling Smørgrav 			status = buffer_get_int(&msg);
1123ae1f160dSDag-Erling Smørgrav 			debug3("SSH2_FXP_STATUS %d", status);
1124ae1f160dSDag-Erling Smørgrav 
1125ae1f160dSDag-Erling Smørgrav 			/* Find the request in our queue */
1126ae1f160dSDag-Erling Smørgrav 			for (ack = TAILQ_FIRST(&acks);
1127545d5ecaSDag-Erling Smørgrav 			    ack != NULL && ack->id != r_id;
1128ae1f160dSDag-Erling Smørgrav 			    ack = TAILQ_NEXT(ack, tq))
1129ae1f160dSDag-Erling Smørgrav 				;
1130ae1f160dSDag-Erling Smørgrav 			if (ack == NULL)
1131ee21a45fSDag-Erling Smørgrav 				fatal("Can't find request for ID %u", r_id);
1132ae1f160dSDag-Erling Smørgrav 			TAILQ_REMOVE(&acks, ack, tq);
1133ae1f160dSDag-Erling Smørgrav 
11341e8db6e2SBrian Feldman 			if (status != SSH2_FX_OK) {
11351e8db6e2SBrian Feldman 				error("Couldn't write to remote file \"%s\": %s",
11361e8db6e2SBrian Feldman 				    remote_path, fx2txt(status));
1137ae1f160dSDag-Erling Smørgrav 				do_close(conn, handle, handle_len);
11381e8db6e2SBrian Feldman 				close(local_fd);
1139d0c8c0bcSDag-Erling Smørgrav 				xfree(data);
1140d0c8c0bcSDag-Erling Smørgrav 				xfree(ack);
11411e8db6e2SBrian Feldman 				goto done;
11421e8db6e2SBrian Feldman 			}
1143ee21a45fSDag-Erling Smørgrav 			debug3("In write loop, ack for %u %u bytes at %llu",
1144545d5ecaSDag-Erling Smørgrav 			    ack->id, ack->len, (unsigned long long)ack->offset);
1145ae1f160dSDag-Erling Smørgrav 			++ackid;
11464b17dab0SDag-Erling Smørgrav 			xfree(ack);
1147ae1f160dSDag-Erling Smørgrav 		}
11481e8db6e2SBrian Feldman 		offset += len;
11491e8db6e2SBrian Feldman 	}
1150d0c8c0bcSDag-Erling Smørgrav 	if (showprogress)
1151d0c8c0bcSDag-Erling Smørgrav 		stop_progress_meter();
1152ae1f160dSDag-Erling Smørgrav 	xfree(data);
11531e8db6e2SBrian Feldman 
11541e8db6e2SBrian Feldman 	if (close(local_fd) == -1) {
11551e8db6e2SBrian Feldman 		error("Couldn't close local file \"%s\": %s", local_path,
11561e8db6e2SBrian Feldman 		    strerror(errno));
1157ae1f160dSDag-Erling Smørgrav 		do_close(conn, handle, handle_len);
11581e8db6e2SBrian Feldman 		status = -1;
11591e8db6e2SBrian Feldman 		goto done;
11601e8db6e2SBrian Feldman 	}
11611e8db6e2SBrian Feldman 
11621e8db6e2SBrian Feldman 	/* Override umask and utimes if asked */
11631e8db6e2SBrian Feldman 	if (pflag)
1164ae1f160dSDag-Erling Smørgrav 		do_fsetstat(conn, handle, handle_len, &a);
11651e8db6e2SBrian Feldman 
1166ae1f160dSDag-Erling Smørgrav 	status = do_close(conn, handle, handle_len);
11671e8db6e2SBrian Feldman 
11681e8db6e2SBrian Feldman done:
11691e8db6e2SBrian Feldman 	xfree(handle);
11701e8db6e2SBrian Feldman 	buffer_free(&msg);
1171ae1f160dSDag-Erling Smørgrav 	return(status);
11721e8db6e2SBrian Feldman }
1173