1296853Sdes/* $OpenBSD: sftp-client.c,v 1.121 2016/02/11 02:21:34 djm Exp $ */
276259Sgreen/*
3126274Sdes * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
476259Sgreen *
5126274Sdes * Permission to use, copy, modify, and distribute this software for any
6126274Sdes * purpose with or without fee is hereby granted, provided that the above
7126274Sdes * copyright notice and this permission notice appear in all copies.
876259Sgreen *
9126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1676259Sgreen */
1776259Sgreen
1876259Sgreen/* XXX: memleaks */
1976259Sgreen/* XXX: signed vs unsigned */
2092555Sdes/* XXX: remove all logging, only return status codes */
2176259Sgreen/* XXX: copy between two remote sites */
2276259Sgreen
2376259Sgreen#include "includes.h"
2476259Sgreen
25295367Sdes#include <sys/param.h>	/* MIN MAX */
26162852Sdes#include <sys/types.h>
27181111Sdes#ifdef HAVE_SYS_STATVFS_H
28181111Sdes#include <sys/statvfs.h>
29181111Sdes#endif
30106121Sdes#include "openbsd-compat/sys-queue.h"
31162852Sdes#ifdef HAVE_SYS_STAT_H
32162852Sdes# include <sys/stat.h>
33162852Sdes#endif
34162852Sdes#ifdef HAVE_SYS_TIME_H
35162852Sdes# include <sys/time.h>
36162852Sdes#endif
37162852Sdes#include <sys/uio.h>
3892555Sdes
39204917Sdes#include <dirent.h>
40162852Sdes#include <errno.h>
41162852Sdes#include <fcntl.h>
42162852Sdes#include <signal.h>
43162852Sdes#include <stdarg.h>
44162852Sdes#include <stdio.h>
45262566Sdes#include <stdlib.h>
46162852Sdes#include <string.h>
47162852Sdes#include <unistd.h>
48162852Sdes
49162852Sdes#include "xmalloc.h"
50295367Sdes#include "ssherr.h"
51295367Sdes#include "sshbuf.h"
5276259Sgreen#include "log.h"
5376259Sgreen#include "atomicio.h"
54113908Sdes#include "progressmeter.h"
55162852Sdes#include "misc.h"
5676259Sgreen
5776259Sgreen#include "sftp.h"
5876259Sgreen#include "sftp-common.h"
5976259Sgreen#include "sftp-client.h"
6076259Sgreen
61137015Sdesextern volatile sig_atomic_t interrupted;
62113908Sdesextern int showprogress;
63113908Sdes
64162852Sdes/* Minimum amount of data to read at a time */
6592555Sdes#define MIN_READ_SIZE	512
6676259Sgreen
67204917Sdes/* Maximum depth to descend in directory trees */
68204917Sdes#define MAX_DIR_DEPTH 64
69204917Sdes
7092555Sdesstruct sftp_conn {
7192555Sdes	int fd_in;
7292555Sdes	int fd_out;
7392555Sdes	u_int transfer_buflen;
7492555Sdes	u_int num_requests;
7592555Sdes	u_int version;
7692555Sdes	u_int msg_id;
77181111Sdes#define SFTP_EXT_POSIX_RENAME	0x00000001
78181111Sdes#define SFTP_EXT_STATVFS	0x00000002
79181111Sdes#define SFTP_EXT_FSTATVFS	0x00000004
80221420Sdes#define SFTP_EXT_HARDLINK	0x00000008
81262566Sdes#define SFTP_EXT_FSYNC		0x00000010
82181111Sdes	u_int exts;
83221420Sdes	u_int64_t limit_kbps;
84221420Sdes	struct bwlimit bwlimit_in, bwlimit_out;
8592555Sdes};
8676259Sgreen
87295367Sdesstatic u_char *
88295367Sdesget_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
89221420Sdes    const char *errfmt, ...) __attribute__((format(printf, 4, 5)));
90204917Sdes
91221420Sdes/* ARGSUSED */
92221420Sdesstatic int
93221420Sdessftpio(void *_bwlimit, size_t amount)
94221420Sdes{
95221420Sdes	struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;
96221420Sdes
97221420Sdes	bandwidth_limit(bwlimit, amount);
98221420Sdes	return 0;
99221420Sdes}
100221420Sdes
10192555Sdesstatic void
102295367Sdessend_msg(struct sftp_conn *conn, struct sshbuf *m)
10376259Sgreen{
104113908Sdes	u_char mlen[4];
105162852Sdes	struct iovec iov[2];
10676259Sgreen
107295367Sdes	if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH)
108295367Sdes		fatal("Outbound message too long %zu", sshbuf_len(m));
10976259Sgreen
110113908Sdes	/* Send length first */
111295367Sdes	put_u32(mlen, sshbuf_len(m));
112162852Sdes	iov[0].iov_base = mlen;
113162852Sdes	iov[0].iov_len = sizeof(mlen);
114295367Sdes	iov[1].iov_base = (u_char *)sshbuf_ptr(m);
115295367Sdes	iov[1].iov_len = sshbuf_len(m);
11676259Sgreen
117221420Sdes	if (atomiciov6(writev, conn->fd_out, iov, 2,
118255767Sdes	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) !=
119295367Sdes	    sshbuf_len(m) + sizeof(mlen))
120113908Sdes		fatal("Couldn't send packet: %s", strerror(errno));
121113908Sdes
122295367Sdes	sshbuf_reset(m);
12376259Sgreen}
12476259Sgreen
12592555Sdesstatic void
126295367Sdesget_msg(struct sftp_conn *conn, struct sshbuf *m)
12776259Sgreen{
128113908Sdes	u_int msg_len;
129295367Sdes	u_char *p;
130295367Sdes	int r;
13176259Sgreen
132295367Sdes	if ((r = sshbuf_reserve(m, 4, &p)) != 0)
133295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
134295367Sdes	if (atomicio6(read, conn->fd_in, p, 4,
135221420Sdes	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) {
136149749Sdes		if (errno == EPIPE)
137149749Sdes			fatal("Connection closed");
138149749Sdes		else
139149749Sdes			fatal("Couldn't read packet: %s", strerror(errno));
140149749Sdes	}
14176259Sgreen
142295367Sdes	if ((r = sshbuf_get_u32(m, &msg_len)) != 0)
143295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
144157016Sdes	if (msg_len > SFTP_MAX_MSG_LENGTH)
14599060Sdes		fatal("Received message too long %u", msg_len);
14676259Sgreen
147295367Sdes	if ((r = sshbuf_reserve(m, msg_len, &p)) != 0)
148295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
149295367Sdes	if (atomicio6(read, conn->fd_in, p, msg_len,
150221420Sdes	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in)
151221420Sdes	    != msg_len) {
152149749Sdes		if (errno == EPIPE)
153149749Sdes			fatal("Connection closed");
154149749Sdes		else
155149749Sdes			fatal("Read packet: %s", strerror(errno));
156149749Sdes	}
15776259Sgreen}
15876259Sgreen
15992555Sdesstatic void
160295367Sdessend_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s,
16176259Sgreen    u_int len)
16276259Sgreen{
163295367Sdes	struct sshbuf *msg;
164295367Sdes	int r;
16576259Sgreen
166295367Sdes	if ((msg = sshbuf_new()) == NULL)
167295367Sdes		fatal("%s: sshbuf_new failed", __func__);
168295367Sdes	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
169295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
170295367Sdes	    (r = sshbuf_put_string(msg, s, len)) != 0)
171295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
172295367Sdes	send_msg(conn, msg);
173221420Sdes	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
174295367Sdes	sshbuf_free(msg);
17576259Sgreen}
17676259Sgreen
17792555Sdesstatic void
178221420Sdessend_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
179295367Sdes    const void *s, u_int len, Attrib *a)
18076259Sgreen{
181295367Sdes	struct sshbuf *msg;
182295367Sdes	int r;
18376259Sgreen
184295367Sdes	if ((msg = sshbuf_new()) == NULL)
185295367Sdes		fatal("%s: sshbuf_new failed", __func__);
186295367Sdes	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
187295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
188295367Sdes	    (r = sshbuf_put_string(msg, s, len)) != 0 ||
189295367Sdes	    (r = encode_attrib(msg, a)) != 0)
190295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
191295367Sdes	send_msg(conn, msg);
192221420Sdes	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
193295367Sdes	sshbuf_free(msg);
19476259Sgreen}
19576259Sgreen
19692555Sdesstatic u_int
197221420Sdesget_status(struct sftp_conn *conn, u_int expected_id)
19876259Sgreen{
199295367Sdes	struct sshbuf *msg;
200295367Sdes	u_char type;
201295367Sdes	u_int id, status;
202295367Sdes	int r;
20376259Sgreen
204295367Sdes	if ((msg = sshbuf_new()) == NULL)
205295367Sdes		fatal("%s: sshbuf_new failed", __func__);
206295367Sdes	get_msg(conn, msg);
207295367Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
208295367Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
209295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
21076259Sgreen
21176259Sgreen	if (id != expected_id)
21299060Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
21376259Sgreen	if (type != SSH2_FXP_STATUS)
21499060Sdes		fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
21576259Sgreen		    SSH2_FXP_STATUS, type);
21676259Sgreen
217295367Sdes	if ((r = sshbuf_get_u32(msg, &status)) != 0)
218295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
219295367Sdes	sshbuf_free(msg);
22076259Sgreen
22199060Sdes	debug3("SSH2_FXP_STATUS %u", status);
22276259Sgreen
223221420Sdes	return status;
22476259Sgreen}
22576259Sgreen
226295367Sdesstatic u_char *
227295367Sdesget_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
228221420Sdes    const char *errfmt, ...)
22976259Sgreen{
230295367Sdes	struct sshbuf *msg;
231295367Sdes	u_int id, status;
232295367Sdes	u_char type;
233295367Sdes	u_char *handle;
234295367Sdes	char errmsg[256];
235204917Sdes	va_list args;
236295367Sdes	int r;
23776259Sgreen
238204917Sdes	va_start(args, errfmt);
239204917Sdes	if (errfmt != NULL)
240204917Sdes		vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
241204917Sdes	va_end(args);
242204917Sdes
243295367Sdes	if ((msg = sshbuf_new()) == NULL)
244295367Sdes		fatal("%s: sshbuf_new failed", __func__);
245295367Sdes	get_msg(conn, msg);
246295367Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
247295367Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
248295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
24976259Sgreen
25076259Sgreen	if (id != expected_id)
251204917Sdes		fatal("%s: ID mismatch (%u != %u)",
252204917Sdes		    errfmt == NULL ? __func__ : errmsg, id, expected_id);
25376259Sgreen	if (type == SSH2_FXP_STATUS) {
254295367Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
255295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
256204917Sdes		if (errfmt != NULL)
257204917Sdes			error("%s: %s", errmsg, fx2txt(status));
258295367Sdes		sshbuf_free(msg);
25976259Sgreen		return(NULL);
26076259Sgreen	} else if (type != SSH2_FXP_HANDLE)
261204917Sdes		fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
262204917Sdes		    errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
26376259Sgreen
264295367Sdes	if ((r = sshbuf_get_string(msg, &handle, len)) != 0)
265295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
266295367Sdes	sshbuf_free(msg);
26776259Sgreen
268295367Sdes	return handle;
26976259Sgreen}
27076259Sgreen
27192555Sdesstatic Attrib *
272221420Sdesget_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
27376259Sgreen{
274295367Sdes	struct sshbuf *msg;
275295367Sdes	u_int id;
276295367Sdes	u_char type;
277295367Sdes	int r;
278295367Sdes	static Attrib a;
27976259Sgreen
280295367Sdes	if ((msg = sshbuf_new()) == NULL)
281295367Sdes		fatal("%s: sshbuf_new failed", __func__);
282295367Sdes	get_msg(conn, msg);
28376259Sgreen
284295367Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
285295367Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
286295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
28776259Sgreen
28899060Sdes	debug3("Received stat reply T:%u I:%u", type, id);
28976259Sgreen	if (id != expected_id)
29099060Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
29176259Sgreen	if (type == SSH2_FXP_STATUS) {
292295367Sdes		u_int status;
29376259Sgreen
294295367Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
295295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
29676259Sgreen		if (quiet)
29776259Sgreen			debug("Couldn't stat remote file: %s", fx2txt(status));
29876259Sgreen		else
29976259Sgreen			error("Couldn't stat remote file: %s", fx2txt(status));
300295367Sdes		sshbuf_free(msg);
30176259Sgreen		return(NULL);
30276259Sgreen	} else if (type != SSH2_FXP_ATTRS) {
30399060Sdes		fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
30476259Sgreen		    SSH2_FXP_ATTRS, type);
30576259Sgreen	}
306295367Sdes	if ((r = decode_attrib(msg, &a)) != 0) {
307295367Sdes		error("%s: couldn't decode attrib: %s", __func__, ssh_err(r));
308295367Sdes		sshbuf_free(msg);
309295367Sdes		return NULL;
310295367Sdes	}
311295367Sdes	sshbuf_free(msg);
31276259Sgreen
313295367Sdes	return &a;
31476259Sgreen}
31576259Sgreen
316181111Sdesstatic int
317221420Sdesget_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
318221420Sdes    u_int expected_id, int quiet)
319181111Sdes{
320295367Sdes	struct sshbuf *msg;
321295367Sdes	u_char type;
322295367Sdes	u_int id;
323295367Sdes	u_int64_t flag;
324295367Sdes	int r;
325181111Sdes
326295367Sdes	if ((msg = sshbuf_new()) == NULL)
327295367Sdes		fatal("%s: sshbuf_new failed", __func__);
328295367Sdes	get_msg(conn, msg);
329181111Sdes
330295367Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
331295367Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
332295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
333181111Sdes
334181111Sdes	debug3("Received statvfs reply T:%u I:%u", type, id);
335181111Sdes	if (id != expected_id)
336181111Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
337181111Sdes	if (type == SSH2_FXP_STATUS) {
338295367Sdes		u_int status;
339181111Sdes
340295367Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
341295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
342181111Sdes		if (quiet)
343181111Sdes			debug("Couldn't statvfs: %s", fx2txt(status));
344181111Sdes		else
345181111Sdes			error("Couldn't statvfs: %s", fx2txt(status));
346295367Sdes		sshbuf_free(msg);
347181111Sdes		return -1;
348181111Sdes	} else if (type != SSH2_FXP_EXTENDED_REPLY) {
349181111Sdes		fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
350181111Sdes		    SSH2_FXP_EXTENDED_REPLY, type);
351181111Sdes	}
352181111Sdes
353264377Sdes	memset(st, 0, sizeof(*st));
354295367Sdes	if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 ||
355295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 ||
356295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 ||
357295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 ||
358295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 ||
359295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_files)) != 0 ||
360295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 ||
361295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 ||
362295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 ||
363295367Sdes	    (r = sshbuf_get_u64(msg, &flag)) != 0 ||
364295367Sdes	    (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0)
365295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
366181111Sdes
367181111Sdes	st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
368181111Sdes	st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
369181111Sdes
370295367Sdes	sshbuf_free(msg);
371181111Sdes
372181111Sdes	return 0;
373181111Sdes}
374181111Sdes
37592555Sdesstruct sftp_conn *
376221420Sdesdo_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
377221420Sdes    u_int64_t limit_kbps)
37876259Sgreen{
379295367Sdes	u_char type;
380295367Sdes	struct sshbuf *msg;
38192555Sdes	struct sftp_conn *ret;
382295367Sdes	int r;
38376259Sgreen
384262566Sdes	ret = xcalloc(1, sizeof(*ret));
385262566Sdes	ret->msg_id = 1;
386221420Sdes	ret->fd_in = fd_in;
387221420Sdes	ret->fd_out = fd_out;
388221420Sdes	ret->transfer_buflen = transfer_buflen;
389221420Sdes	ret->num_requests = num_requests;
390221420Sdes	ret->exts = 0;
391221420Sdes	ret->limit_kbps = 0;
392221420Sdes
393295367Sdes	if ((msg = sshbuf_new()) == NULL)
394295367Sdes		fatal("%s: sshbuf_new failed", __func__);
395295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 ||
396295367Sdes	    (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0)
397295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
398295367Sdes	send_msg(ret, msg);
39976259Sgreen
400295367Sdes	sshbuf_reset(msg);
40176259Sgreen
402295367Sdes	get_msg(ret, msg);
40376259Sgreen
40476259Sgreen	/* Expecting a VERSION reply */
405295367Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0)
406295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
407295367Sdes	if (type != SSH2_FXP_VERSION) {
40899060Sdes		error("Invalid packet back from SSH2_FXP_INIT (type %u)",
40976259Sgreen		    type);
410295367Sdes		sshbuf_free(msg);
411295367Sdes		free(ret);
41292555Sdes		return(NULL);
41376259Sgreen	}
414295367Sdes	if ((r = sshbuf_get_u32(msg, &ret->version)) != 0)
415295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
41676259Sgreen
417221420Sdes	debug2("Remote version: %u", ret->version);
41876259Sgreen
41976259Sgreen	/* Check for extensions */
420295367Sdes	while (sshbuf_len(msg) > 0) {
421295367Sdes		char *name;
422295367Sdes		u_char *value;
423295367Sdes		size_t vlen;
424181111Sdes		int known = 0;
42576259Sgreen
426295367Sdes		if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 ||
427295367Sdes		    (r = sshbuf_get_string(msg, &value, &vlen)) != 0)
428295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
429181111Sdes		if (strcmp(name, "posix-rename@openssh.com") == 0 &&
430295367Sdes		    strcmp((char *)value, "1") == 0) {
431221420Sdes			ret->exts |= SFTP_EXT_POSIX_RENAME;
432181111Sdes			known = 1;
433181111Sdes		} else if (strcmp(name, "statvfs@openssh.com") == 0 &&
434295367Sdes		    strcmp((char *)value, "2") == 0) {
435221420Sdes			ret->exts |= SFTP_EXT_STATVFS;
436181111Sdes			known = 1;
437221420Sdes		} else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
438295367Sdes		    strcmp((char *)value, "2") == 0) {
439221420Sdes			ret->exts |= SFTP_EXT_FSTATVFS;
440181111Sdes			known = 1;
441221420Sdes		} else if (strcmp(name, "hardlink@openssh.com") == 0 &&
442295367Sdes		    strcmp((char *)value, "1") == 0) {
443221420Sdes			ret->exts |= SFTP_EXT_HARDLINK;
444221420Sdes			known = 1;
445295367Sdes		} else if (strcmp(name, "fsync@openssh.com") == 0 &&
446295367Sdes		    strcmp((char *)value, "1") == 0) {
447295367Sdes			ret->exts |= SFTP_EXT_FSYNC;
448295367Sdes			known = 1;
449181111Sdes		}
450181111Sdes		if (known) {
451181111Sdes			debug2("Server supports extension \"%s\" revision %s",
452181111Sdes			    name, value);
453181111Sdes		} else {
454181111Sdes			debug2("Unrecognised server extension \"%s\"", name);
455181111Sdes		}
456255767Sdes		free(name);
457255767Sdes		free(value);
45876259Sgreen	}
45976259Sgreen
460295367Sdes	sshbuf_free(msg);
46176259Sgreen
46292555Sdes	/* Some filexfer v.0 servers don't support large packets */
463221420Sdes	if (ret->version == 0)
46498675Sdes		ret->transfer_buflen = MIN(ret->transfer_buflen, 20480);
46592555Sdes
466221420Sdes	ret->limit_kbps = limit_kbps;
467221420Sdes	if (ret->limit_kbps > 0) {
468221420Sdes		bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
469221420Sdes		    ret->transfer_buflen);
470221420Sdes		bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
471221420Sdes		    ret->transfer_buflen);
472221420Sdes	}
473221420Sdes
474221420Sdes	return ret;
47576259Sgreen}
47676259Sgreen
47792555Sdesu_int
47892555Sdessftp_proto_version(struct sftp_conn *conn)
47992555Sdes{
480221420Sdes	return conn->version;
48192555Sdes}
48292555Sdes
48376259Sgreenint
484295367Sdesdo_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
48576259Sgreen{
48676259Sgreen	u_int id, status;
487295367Sdes	struct sshbuf *msg;
488295367Sdes	int r;
48976259Sgreen
490295367Sdes	if ((msg = sshbuf_new()) == NULL)
491295367Sdes		fatal("%s: sshbuf_new failed", __func__);
49276259Sgreen
49392555Sdes	id = conn->msg_id++;
494295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 ||
495295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
496295367Sdes	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
497295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
498295367Sdes	send_msg(conn, msg);
49999060Sdes	debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
50076259Sgreen
501221420Sdes	status = get_status(conn, id);
50276259Sgreen	if (status != SSH2_FX_OK)
50376259Sgreen		error("Couldn't close file: %s", fx2txt(status));
50476259Sgreen
505295367Sdes	sshbuf_free(msg);
50676259Sgreen
507295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
50876259Sgreen}
50976259Sgreen
51076259Sgreen
51192555Sdesstatic int
512295367Sdesdo_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
51376259Sgreen    SFTP_DIRENT ***dir)
51476259Sgreen{
515295367Sdes	struct sshbuf *msg;
516295367Sdes	u_int count, id, i, expected_id, ents = 0;
517295367Sdes	size_t handle_len;
518295367Sdes	u_char type;
51976259Sgreen	char *handle;
520262566Sdes	int status = SSH2_FX_FAILURE;
521295367Sdes	int r;
52276259Sgreen
523262566Sdes	if (dir)
524262566Sdes		*dir = NULL;
525262566Sdes
52692555Sdes	id = conn->msg_id++;
52776259Sgreen
528295367Sdes	if ((msg = sshbuf_new()) == NULL)
529295367Sdes		fatal("%s: sshbuf_new failed", __func__);
530295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 ||
531295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
532295367Sdes	    (r = sshbuf_put_cstring(msg, path)) != 0)
533295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
534295367Sdes	send_msg(conn, msg);
53576259Sgreen
536221420Sdes	handle = get_handle(conn, id, &handle_len,
537204917Sdes	    "remote readdir(\"%s\")", path);
538240075Sdes	if (handle == NULL) {
539295367Sdes		sshbuf_free(msg);
540221420Sdes		return -1;
541240075Sdes	}
54276259Sgreen
54376259Sgreen	if (dir) {
54476259Sgreen		ents = 0;
545258343Sdes		*dir = xcalloc(1, sizeof(**dir));
54676259Sgreen		(*dir)[0] = NULL;
54776259Sgreen	}
54876259Sgreen
549137015Sdes	for (; !interrupted;) {
55092555Sdes		id = expected_id = conn->msg_id++;
55176259Sgreen
55299060Sdes		debug3("Sending SSH2_FXP_READDIR I:%u", id);
55376259Sgreen
554295367Sdes		sshbuf_reset(msg);
555295367Sdes		if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 ||
556295367Sdes		    (r = sshbuf_put_u32(msg, id)) != 0 ||
557295367Sdes		    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
558295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
559295367Sdes		send_msg(conn, msg);
56076259Sgreen
561295367Sdes		sshbuf_reset(msg);
56276259Sgreen
563295367Sdes		get_msg(conn, msg);
56476259Sgreen
565295367Sdes		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
566295367Sdes		    (r = sshbuf_get_u32(msg, &id)) != 0)
567295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
56876259Sgreen
56999060Sdes		debug3("Received reply T:%u I:%u", type, id);
57076259Sgreen
57176259Sgreen		if (id != expected_id)
57299060Sdes			fatal("ID mismatch (%u != %u)", id, expected_id);
57376259Sgreen
57476259Sgreen		if (type == SSH2_FXP_STATUS) {
575295367Sdes			u_int rstatus;
576295367Sdes
577295367Sdes			if ((r = sshbuf_get_u32(msg, &rstatus)) != 0)
578295367Sdes				fatal("%s: buffer error: %s",
579295367Sdes				    __func__, ssh_err(r));
580295367Sdes			debug3("Received SSH2_FXP_STATUS %d", rstatus);
581295367Sdes			if (rstatus == SSH2_FX_EOF)
58276259Sgreen				break;
583295367Sdes			error("Couldn't read directory: %s", fx2txt(rstatus));
584262566Sdes			goto out;
58576259Sgreen		} else if (type != SSH2_FXP_NAME)
58699060Sdes			fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
58776259Sgreen			    SSH2_FXP_NAME, type);
58876259Sgreen
589295367Sdes		if ((r = sshbuf_get_u32(msg, &count)) != 0)
590295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
59176259Sgreen		if (count == 0)
59276259Sgreen			break;
59376259Sgreen		debug3("Received %d SSH2_FXP_NAME responses", count);
59492555Sdes		for (i = 0; i < count; i++) {
59576259Sgreen			char *filename, *longname;
596295367Sdes			Attrib a;
59776259Sgreen
598295367Sdes			if ((r = sshbuf_get_cstring(msg, &filename,
599295367Sdes			    NULL)) != 0 ||
600295367Sdes			    (r = sshbuf_get_cstring(msg, &longname,
601295367Sdes			    NULL)) != 0)
602295367Sdes				fatal("%s: buffer error: %s",
603295367Sdes				    __func__, ssh_err(r));
604295367Sdes			if ((r = decode_attrib(msg, &a)) != 0) {
605295367Sdes				error("%s: couldn't decode attrib: %s",
606295367Sdes				    __func__, ssh_err(r));
607295367Sdes				free(filename);
608295367Sdes				free(longname);
609295367Sdes				sshbuf_free(msg);
610295367Sdes				return -1;
611295367Sdes			}
61276259Sgreen
613262566Sdes			if (print_flag)
61476259Sgreen				printf("%s\n", longname);
61576259Sgreen
616204917Sdes			/*
617204917Sdes			 * Directory entries should never contain '/'
618204917Sdes			 * These can be used to attack recursive ops
619204917Sdes			 * (e.g. send '../../../../etc/passwd')
620204917Sdes			 */
621204917Sdes			if (strchr(filename, '/') != NULL) {
622204917Sdes				error("Server sent suspect path \"%s\" "
623204917Sdes				    "during readdir of \"%s\"", filename, path);
624262566Sdes			} else if (dir) {
625295367Sdes				*dir = xreallocarray(*dir, ents + 2, sizeof(**dir));
626258343Sdes				(*dir)[ents] = xcalloc(1, sizeof(***dir));
62776259Sgreen				(*dir)[ents]->filename = xstrdup(filename);
62876259Sgreen				(*dir)[ents]->longname = xstrdup(longname);
629295367Sdes				memcpy(&(*dir)[ents]->a, &a, sizeof(a));
63076259Sgreen				(*dir)[++ents] = NULL;
63176259Sgreen			}
632255767Sdes			free(filename);
633255767Sdes			free(longname);
63476259Sgreen		}
63576259Sgreen	}
636262566Sdes	status = 0;
63776259Sgreen
638262566Sdes out:
639295367Sdes	sshbuf_free(msg);
64092555Sdes	do_close(conn, handle, handle_len);
641255767Sdes	free(handle);
64276259Sgreen
643262566Sdes	if (status != 0 && dir != NULL) {
644262566Sdes		/* Don't return results on error */
645137015Sdes		free_sftp_dirents(*dir);
646262566Sdes		*dir = NULL;
647262566Sdes	} else if (interrupted && dir != NULL && *dir != NULL) {
648262566Sdes		/* Don't return partial matches on interrupt */
649262566Sdes		free_sftp_dirents(*dir);
650258343Sdes		*dir = xcalloc(1, sizeof(**dir));
651137015Sdes		**dir = NULL;
652137015Sdes	}
653137015Sdes
654262566Sdes	return status;
65576259Sgreen}
65676259Sgreen
65776259Sgreenint
658295367Sdesdo_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
65976259Sgreen{
66092555Sdes	return(do_lsreaddir(conn, path, 0, dir));
66176259Sgreen}
66276259Sgreen
66376259Sgreenvoid free_sftp_dirents(SFTP_DIRENT **s)
66476259Sgreen{
66576259Sgreen	int i;
66692555Sdes
667262566Sdes	if (s == NULL)
668262566Sdes		return;
66992555Sdes	for (i = 0; s[i]; i++) {
670255767Sdes		free(s[i]->filename);
671255767Sdes		free(s[i]->longname);
672255767Sdes		free(s[i]);
67376259Sgreen	}
674255767Sdes	free(s);
67576259Sgreen}
67676259Sgreen
67776259Sgreenint
678295367Sdesdo_rm(struct sftp_conn *conn, const char *path)
67976259Sgreen{
68076259Sgreen	u_int status, id;
68176259Sgreen
68276259Sgreen	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
68376259Sgreen
68492555Sdes	id = conn->msg_id++;
685221420Sdes	send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
686221420Sdes	status = get_status(conn, id);
68776259Sgreen	if (status != SSH2_FX_OK)
68876259Sgreen		error("Couldn't delete file: %s", fx2txt(status));
689295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
69076259Sgreen}
69176259Sgreen
69276259Sgreenint
693295367Sdesdo_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
69476259Sgreen{
69576259Sgreen	u_int status, id;
69676259Sgreen
69792555Sdes	id = conn->msg_id++;
698221420Sdes	send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
69976259Sgreen	    strlen(path), a);
70076259Sgreen
701221420Sdes	status = get_status(conn, id);
702262566Sdes	if (status != SSH2_FX_OK && print_flag)
70376259Sgreen		error("Couldn't create directory: %s", fx2txt(status));
70476259Sgreen
705295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
70676259Sgreen}
70776259Sgreen
70876259Sgreenint
709295367Sdesdo_rmdir(struct sftp_conn *conn, const char *path)
71076259Sgreen{
71176259Sgreen	u_int status, id;
71276259Sgreen
71392555Sdes	id = conn->msg_id++;
714221420Sdes	send_string_request(conn, id, SSH2_FXP_RMDIR, path,
71592555Sdes	    strlen(path));
71676259Sgreen
717221420Sdes	status = get_status(conn, id);
71876259Sgreen	if (status != SSH2_FX_OK)
71976259Sgreen		error("Couldn't remove directory: %s", fx2txt(status));
72076259Sgreen
721295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
72276259Sgreen}
72376259Sgreen
72476259SgreenAttrib *
725295367Sdesdo_stat(struct sftp_conn *conn, const char *path, int quiet)
72676259Sgreen{
72776259Sgreen	u_int id;
72876259Sgreen
72992555Sdes	id = conn->msg_id++;
73092555Sdes
731221420Sdes	send_string_request(conn, id,
73298675Sdes	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
73392555Sdes	    path, strlen(path));
73492555Sdes
735221420Sdes	return(get_decode_stat(conn, id, quiet));
73676259Sgreen}
73776259Sgreen
73876259SgreenAttrib *
739295367Sdesdo_lstat(struct sftp_conn *conn, const char *path, int quiet)
74076259Sgreen{
74176259Sgreen	u_int id;
74276259Sgreen
74392555Sdes	if (conn->version == 0) {
74492555Sdes		if (quiet)
74592555Sdes			debug("Server version does not support lstat operation");
74692555Sdes		else
747124208Sdes			logit("Server version does not support lstat operation");
74898675Sdes		return(do_stat(conn, path, quiet));
74992555Sdes	}
75092555Sdes
75192555Sdes	id = conn->msg_id++;
752221420Sdes	send_string_request(conn, id, SSH2_FXP_LSTAT, path,
75392555Sdes	    strlen(path));
75492555Sdes
755221420Sdes	return(get_decode_stat(conn, id, quiet));
75676259Sgreen}
75776259Sgreen
758181111Sdes#ifdef notyet
75976259SgreenAttrib *
760295367Sdesdo_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
761295367Sdes    int quiet)
76276259Sgreen{
76376259Sgreen	u_int id;
76476259Sgreen
76592555Sdes	id = conn->msg_id++;
766221420Sdes	send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
76792555Sdes	    handle_len);
76892555Sdes
769221420Sdes	return(get_decode_stat(conn, id, quiet));
77076259Sgreen}
771181111Sdes#endif
77276259Sgreen
77376259Sgreenint
774295367Sdesdo_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
77576259Sgreen{
77676259Sgreen	u_int status, id;
77776259Sgreen
77892555Sdes	id = conn->msg_id++;
779221420Sdes	send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
78076259Sgreen	    strlen(path), a);
78176259Sgreen
782221420Sdes	status = get_status(conn, id);
78376259Sgreen	if (status != SSH2_FX_OK)
78476259Sgreen		error("Couldn't setstat on \"%s\": %s", path,
78576259Sgreen		    fx2txt(status));
78676259Sgreen
787295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
78876259Sgreen}
78976259Sgreen
79076259Sgreenint
791295367Sdesdo_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
79276259Sgreen    Attrib *a)
79376259Sgreen{
79476259Sgreen	u_int status, id;
79576259Sgreen
79692555Sdes	id = conn->msg_id++;
797221420Sdes	send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
79876259Sgreen	    handle_len, a);
79976259Sgreen
800221420Sdes	status = get_status(conn, id);
80176259Sgreen	if (status != SSH2_FX_OK)
80276259Sgreen		error("Couldn't fsetstat: %s", fx2txt(status));
80376259Sgreen
804295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
80576259Sgreen}
80676259Sgreen
80776259Sgreenchar *
808295367Sdesdo_realpath(struct sftp_conn *conn, const char *path)
80976259Sgreen{
810295367Sdes	struct sshbuf *msg;
811295367Sdes	u_int expected_id, count, id;
81276259Sgreen	char *filename, *longname;
813295367Sdes	Attrib a;
814295367Sdes	u_char type;
815295367Sdes	int r;
81676259Sgreen
81792555Sdes	expected_id = id = conn->msg_id++;
818221420Sdes	send_string_request(conn, id, SSH2_FXP_REALPATH, path,
81992555Sdes	    strlen(path));
82076259Sgreen
821295367Sdes	if ((msg = sshbuf_new()) == NULL)
822295367Sdes		fatal("%s: sshbuf_new failed", __func__);
82376259Sgreen
824295367Sdes	get_msg(conn, msg);
825295367Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
826295367Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
827295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
82876259Sgreen
82976259Sgreen	if (id != expected_id)
83099060Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
83176259Sgreen
83276259Sgreen	if (type == SSH2_FXP_STATUS) {
833295367Sdes		u_int status;
83476259Sgreen
835295367Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
836295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
837262566Sdes		error("Couldn't canonicalize: %s", fx2txt(status));
838295367Sdes		sshbuf_free(msg);
839215116Sdes		return NULL;
84076259Sgreen	} else if (type != SSH2_FXP_NAME)
84199060Sdes		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
84276259Sgreen		    SSH2_FXP_NAME, type);
84376259Sgreen
844295367Sdes	if ((r = sshbuf_get_u32(msg, &count)) != 0)
845295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
84676259Sgreen	if (count != 1)
84776259Sgreen		fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
84876259Sgreen
849295367Sdes	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
850295367Sdes	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
851295367Sdes	    (r = decode_attrib(msg, &a)) != 0)
852295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
85376259Sgreen
854240075Sdes	debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename,
855295367Sdes	    (unsigned long)a.size);
85676259Sgreen
857255767Sdes	free(longname);
85876259Sgreen
859295367Sdes	sshbuf_free(msg);
86076259Sgreen
86176259Sgreen	return(filename);
86276259Sgreen}
86376259Sgreen
86476259Sgreenint
865295367Sdesdo_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
866262566Sdes    int force_legacy)
86776259Sgreen{
868295367Sdes	struct sshbuf *msg;
86976259Sgreen	u_int status, id;
870295367Sdes	int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy;
87176259Sgreen
872295367Sdes	if ((msg = sshbuf_new()) == NULL)
873295367Sdes		fatal("%s: sshbuf_new failed", __func__);
87476259Sgreen
87576259Sgreen	/* Send rename request */
87692555Sdes	id = conn->msg_id++;
877262566Sdes	if (use_ext) {
878295367Sdes		if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
879295367Sdes		    (r = sshbuf_put_u32(msg, id)) != 0 ||
880295367Sdes		    (r = sshbuf_put_cstring(msg,
881295367Sdes		    "posix-rename@openssh.com")) != 0)
882295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
883181111Sdes	} else {
884295367Sdes		if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 ||
885295367Sdes		    (r = sshbuf_put_u32(msg, id)) != 0)
886295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
887181111Sdes	}
888295367Sdes	if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
889295367Sdes	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
890295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
891295367Sdes	send_msg(conn, msg);
892181111Sdes	debug3("Sent message %s \"%s\" -> \"%s\"",
893295367Sdes	    use_ext ? "posix-rename@openssh.com" :
894295367Sdes	    "SSH2_FXP_RENAME", oldpath, newpath);
895295367Sdes	sshbuf_free(msg);
89676259Sgreen
897221420Sdes	status = get_status(conn, id);
89876259Sgreen	if (status != SSH2_FX_OK)
89992555Sdes		error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath,
90092555Sdes		    newpath, fx2txt(status));
90176259Sgreen
902295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
90376259Sgreen}
90476259Sgreen
90576259Sgreenint
906295367Sdesdo_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
907221420Sdes{
908295367Sdes	struct sshbuf *msg;
909221420Sdes	u_int status, id;
910295367Sdes	int r;
911221420Sdes
912221420Sdes	if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
913221420Sdes		error("Server does not support hardlink@openssh.com extension");
914221420Sdes		return -1;
915221420Sdes	}
916221420Sdes
917295367Sdes	if ((msg = sshbuf_new()) == NULL)
918295367Sdes		fatal("%s: sshbuf_new failed", __func__);
919240075Sdes
920240075Sdes	/* Send link request */
921240075Sdes	id = conn->msg_id++;
922295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
923295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
924295367Sdes	    (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
925295367Sdes	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
926295367Sdes	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
927295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
928295367Sdes	send_msg(conn, msg);
929221420Sdes	debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
930221420Sdes	       oldpath, newpath);
931295367Sdes	sshbuf_free(msg);
932221420Sdes
933221420Sdes	status = get_status(conn, id);
934221420Sdes	if (status != SSH2_FX_OK)
935221420Sdes		error("Couldn't link file \"%s\" to \"%s\": %s", oldpath,
936221420Sdes		    newpath, fx2txt(status));
937221420Sdes
938295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
939221420Sdes}
940221420Sdes
941221420Sdesint
942295367Sdesdo_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
94376259Sgreen{
944295367Sdes	struct sshbuf *msg;
94576259Sgreen	u_int status, id;
946295367Sdes	int r;
94776259Sgreen
94892555Sdes	if (conn->version < 3) {
94992555Sdes		error("This server does not support the symlink operation");
95092555Sdes		return(SSH2_FX_OP_UNSUPPORTED);
95192555Sdes	}
95292555Sdes
953295367Sdes	if ((msg = sshbuf_new()) == NULL)
954295367Sdes		fatal("%s: sshbuf_new failed", __func__);
95576259Sgreen
956137015Sdes	/* Send symlink request */
95792555Sdes	id = conn->msg_id++;
958295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 ||
959295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
960295367Sdes	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
961295367Sdes	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
962295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
963295367Sdes	send_msg(conn, msg);
96476259Sgreen	debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
96576259Sgreen	    newpath);
966295367Sdes	sshbuf_free(msg);
96776259Sgreen
968221420Sdes	status = get_status(conn, id);
96976259Sgreen	if (status != SSH2_FX_OK)
970113908Sdes		error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath,
97192555Sdes		    newpath, fx2txt(status));
97276259Sgreen
973295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
97476259Sgreen}
97576259Sgreen
976262566Sdesint
977295367Sdesdo_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
978262566Sdes{
979295367Sdes	struct sshbuf *msg;
980262566Sdes	u_int status, id;
981295367Sdes	int r;
982262566Sdes
983262566Sdes	/* Silently return if the extension is not supported */
984262566Sdes	if ((conn->exts & SFTP_EXT_FSYNC) == 0)
985262566Sdes		return -1;
986262566Sdes
987262566Sdes	/* Send fsync request */
988295367Sdes	if ((msg = sshbuf_new()) == NULL)
989295367Sdes		fatal("%s: sshbuf_new failed", __func__);
990262566Sdes	id = conn->msg_id++;
991295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
992295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
993295367Sdes	    (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
994295367Sdes	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
995295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
996295367Sdes	send_msg(conn, msg);
997262566Sdes	debug3("Sent message fsync@openssh.com I:%u", id);
998295367Sdes	sshbuf_free(msg);
999262566Sdes
1000262566Sdes	status = get_status(conn, id);
1001262566Sdes	if (status != SSH2_FX_OK)
1002262566Sdes		error("Couldn't sync file: %s", fx2txt(status));
1003262566Sdes
1004262566Sdes	return status;
1005262566Sdes}
1006262566Sdes
1007181111Sdes#ifdef notyet
100876259Sgreenchar *
1009295367Sdesdo_readlink(struct sftp_conn *conn, const char *path)
101076259Sgreen{
1011295367Sdes	struct sshbuf *msg;
1012295367Sdes	u_int expected_id, count, id;
101376259Sgreen	char *filename, *longname;
1014295367Sdes	Attrib a;
1015295367Sdes	u_char type;
1016295367Sdes	int r;
101776259Sgreen
101892555Sdes	expected_id = id = conn->msg_id++;
1019221420Sdes	send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
102076259Sgreen
1021295367Sdes	if ((msg = sshbuf_new()) == NULL)
1022295367Sdes		fatal("%s: sshbuf_new failed", __func__);
102376259Sgreen
1024295367Sdes	get_msg(conn, msg);
1025295367Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1026295367Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
1027295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
102876259Sgreen
102976259Sgreen	if (id != expected_id)
103099060Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
103176259Sgreen
103276259Sgreen	if (type == SSH2_FXP_STATUS) {
1033295367Sdes		u_int status;
103476259Sgreen
1035295367Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
1036295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
103776259Sgreen		error("Couldn't readlink: %s", fx2txt(status));
1038295367Sdes		sshbuf_free(msg);
103976259Sgreen		return(NULL);
104076259Sgreen	} else if (type != SSH2_FXP_NAME)
104199060Sdes		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
104276259Sgreen		    SSH2_FXP_NAME, type);
104376259Sgreen
1044295367Sdes	if ((r = sshbuf_get_u32(msg, &count)) != 0)
1045295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
104676259Sgreen	if (count != 1)
104776259Sgreen		fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
104876259Sgreen
1049295367Sdes	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1050295367Sdes	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1051295367Sdes	    (r = decode_attrib(msg, &a)) != 0)
1052295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
105376259Sgreen
105476259Sgreen	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
105576259Sgreen
1056255767Sdes	free(longname);
105776259Sgreen
1058295367Sdes	sshbuf_free(msg);
105976259Sgreen
1060295367Sdes	return filename;
106176259Sgreen}
1062181111Sdes#endif
106376259Sgreen
1064181111Sdesint
1065181111Sdesdo_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
1066181111Sdes    int quiet)
1067181111Sdes{
1068295367Sdes	struct sshbuf *msg;
1069181111Sdes	u_int id;
1070295367Sdes	int r;
1071181111Sdes
1072181111Sdes	if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
1073181111Sdes		error("Server does not support statvfs@openssh.com extension");
1074181111Sdes		return -1;
1075181111Sdes	}
1076181111Sdes
1077181111Sdes	id = conn->msg_id++;
1078181111Sdes
1079295367Sdes	if ((msg = sshbuf_new()) == NULL)
1080295367Sdes		fatal("%s: sshbuf_new failed", __func__);
1081295367Sdes	sshbuf_reset(msg);
1082295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1083295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1084295367Sdes	    (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
1085295367Sdes	    (r = sshbuf_put_cstring(msg, path)) != 0)
1086295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1087295367Sdes	send_msg(conn, msg);
1088295367Sdes	sshbuf_free(msg);
1089181111Sdes
1090221420Sdes	return get_decode_statvfs(conn, st, id, quiet);
1091181111Sdes}
1092181111Sdes
1093181111Sdes#ifdef notyet
1094181111Sdesint
1095295367Sdesdo_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
1096181111Sdes    struct sftp_statvfs *st, int quiet)
1097181111Sdes{
1098295367Sdes	struct sshbuf *msg;
1099181111Sdes	u_int id;
1100181111Sdes
1101181111Sdes	if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
1102181111Sdes		error("Server does not support fstatvfs@openssh.com extension");
1103181111Sdes		return -1;
1104181111Sdes	}
1105181111Sdes
1106181111Sdes	id = conn->msg_id++;
1107181111Sdes
1108295367Sdes	if ((msg = sshbuf_new()) == NULL)
1109295367Sdes		fatal("%s: sshbuf_new failed", __func__);
1110295367Sdes	sshbuf_reset(msg);
1111295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1112295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1113295367Sdes	    (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
1114295367Sdes	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
1115295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1116295367Sdes	send_msg(conn, msg);
1117295367Sdes	sshbuf_free(msg);
1118181111Sdes
1119221420Sdes	return get_decode_statvfs(conn, st, id, quiet);
1120181111Sdes}
1121181111Sdes#endif
1122181111Sdes
112392555Sdesstatic void
1124221420Sdessend_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
1125295367Sdes    u_int len, const u_char *handle, u_int handle_len)
112692555Sdes{
1127295367Sdes	struct sshbuf *msg;
1128295367Sdes	int r;
112998675Sdes
1130295367Sdes	if ((msg = sshbuf_new()) == NULL)
1131295367Sdes		fatal("%s: sshbuf_new failed", __func__);
1132295367Sdes	sshbuf_reset(msg);
1133295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 ||
1134295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1135295367Sdes	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0 ||
1136295367Sdes	    (r = sshbuf_put_u64(msg, offset)) != 0 ||
1137295367Sdes	    (r = sshbuf_put_u32(msg, len)) != 0)
1138295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1139295367Sdes	send_msg(conn, msg);
1140295367Sdes	sshbuf_free(msg);
114198675Sdes}
114292555Sdes
114376259Sgreenint
1144295367Sdesdo_download(struct sftp_conn *conn, const char *remote_path,
1145295367Sdes    const char *local_path, Attrib *a, int preserve_flag, int resume_flag,
1146295367Sdes    int fsync_flag)
114776259Sgreen{
1148204917Sdes	Attrib junk;
1149295367Sdes	struct sshbuf *msg;
1150295367Sdes	u_char *handle;
1151295367Sdes	int local_fd = -1, write_error;
1152295367Sdes	int read_error, write_errno, reordered = 0, r;
1153255767Sdes	u_int64_t offset = 0, size, highwater;
1154295367Sdes	u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK;
1155113908Sdes	off_t progress_counter;
1156295367Sdes	size_t handle_len;
1157255767Sdes	struct stat st;
115892555Sdes	struct request {
115992555Sdes		u_int id;
1160295367Sdes		size_t len;
116192555Sdes		u_int64_t offset;
116298675Sdes		TAILQ_ENTRY(request) tq;
116392555Sdes	};
116492555Sdes	TAILQ_HEAD(reqhead, request) requests;
116592555Sdes	struct request *req;
1166295367Sdes	u_char type;
116776259Sgreen
116892555Sdes	TAILQ_INIT(&requests);
116992555Sdes
1170204917Sdes	if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
1171204917Sdes		return -1;
117276259Sgreen
1173181111Sdes	/* Do not preserve set[ug]id here, as we do not preserve ownership */
117476259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1175113908Sdes		mode = a->perm & 0777;
117676259Sgreen	else
117776259Sgreen		mode = 0666;
117876259Sgreen
117976259Sgreen	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
1180113908Sdes	    (!S_ISREG(a->perm))) {
1181113908Sdes		error("Cannot download non-regular file: %s", remote_path);
118276259Sgreen		return(-1);
118376259Sgreen	}
118476259Sgreen
118592555Sdes	if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
118692555Sdes		size = a->size;
118792555Sdes	else
118892555Sdes		size = 0;
118976259Sgreen
119092555Sdes	buflen = conn->transfer_buflen;
1191295367Sdes	if ((msg = sshbuf_new()) == NULL)
1192295367Sdes		fatal("%s: sshbuf_new failed", __func__);
119376259Sgreen
1194295367Sdes	attrib_clear(&junk); /* Send empty attributes */
1195295367Sdes
119676259Sgreen	/* Send open request */
119792555Sdes	id = conn->msg_id++;
1198295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1199295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1200295367Sdes	    (r = sshbuf_put_cstring(msg, remote_path)) != 0 ||
1201295367Sdes	    (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 ||
1202295367Sdes	    (r = encode_attrib(msg, &junk)) != 0)
1203295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1204295367Sdes	send_msg(conn, msg);
120599060Sdes	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
120676259Sgreen
1207221420Sdes	handle = get_handle(conn, id, &handle_len,
1208204917Sdes	    "remote open(\"%s\")", remote_path);
120976259Sgreen	if (handle == NULL) {
1210295367Sdes		sshbuf_free(msg);
121176259Sgreen		return(-1);
121276259Sgreen	}
121376259Sgreen
1214262566Sdes	local_fd = open(local_path,
1215262566Sdes	    O_WRONLY | O_CREAT | (resume_flag ? 0 : O_TRUNC), mode | S_IWUSR);
121692555Sdes	if (local_fd == -1) {
121792555Sdes		error("Couldn't open local file \"%s\" for writing: %s",
121892555Sdes		    local_path, strerror(errno));
1219255767Sdes		goto fail;
122092555Sdes	}
1221255767Sdes	offset = highwater = 0;
1222262566Sdes	if (resume_flag) {
1223255767Sdes		if (fstat(local_fd, &st) == -1) {
1224255767Sdes			error("Unable to stat local file \"%s\": %s",
1225255767Sdes			    local_path, strerror(errno));
1226255767Sdes			goto fail;
1227255767Sdes		}
1228262566Sdes		if (st.st_size < 0) {
1229262566Sdes			error("\"%s\" has negative size", local_path);
1230262566Sdes			goto fail;
1231262566Sdes		}
1232262566Sdes		if ((u_int64_t)st.st_size > size) {
1233255767Sdes			error("Unable to resume download of \"%s\": "
1234255767Sdes			    "local file is larger than remote", local_path);
1235255767Sdes fail:
1236255767Sdes			do_close(conn, handle, handle_len);
1237295367Sdes			sshbuf_free(msg);
1238255767Sdes			free(handle);
1239262566Sdes			if (local_fd != -1)
1240262566Sdes				close(local_fd);
1241255767Sdes			return -1;
1242255767Sdes		}
1243255767Sdes		offset = highwater = st.st_size;
1244255767Sdes	}
124592555Sdes
124676259Sgreen	/* Read from remote and write to local */
1247255767Sdes	write_error = read_error = write_errno = num_req = 0;
124892555Sdes	max_req = 1;
1249255767Sdes	progress_counter = offset;
1250113908Sdes
1251128456Sdes	if (showprogress && size != 0)
1252128456Sdes		start_progress_meter(remote_path, size, &progress_counter);
1253113908Sdes
125492555Sdes	while (num_req > 0 || max_req > 0) {
1255295367Sdes		u_char *data;
1256295367Sdes		size_t len;
125776259Sgreen
1258137015Sdes		/*
1259137015Sdes		 * Simulate EOF on interrupt: stop sending new requests and
1260137015Sdes		 * allow outstanding requests to drain gracefully
1261137015Sdes		 */
1262137015Sdes		if (interrupted) {
1263137015Sdes			if (num_req == 0) /* If we haven't started yet... */
1264137015Sdes				break;
1265137015Sdes			max_req = 0;
1266137015Sdes		}
1267137015Sdes
126892555Sdes		/* Send some more requests */
126992555Sdes		while (num_req < max_req) {
127098675Sdes			debug3("Request range %llu -> %llu (%d/%d)",
127198675Sdes			    (unsigned long long)offset,
127298675Sdes			    (unsigned long long)offset + buflen - 1,
127398675Sdes			    num_req, max_req);
1274258343Sdes			req = xcalloc(1, sizeof(*req));
127592555Sdes			req->id = conn->msg_id++;
127692555Sdes			req->len = buflen;
127792555Sdes			req->offset = offset;
127892555Sdes			offset += buflen;
127992555Sdes			num_req++;
128092555Sdes			TAILQ_INSERT_TAIL(&requests, req, tq);
1281221420Sdes			send_read_request(conn, req->id, req->offset,
128292555Sdes			    req->len, handle, handle_len);
128392555Sdes		}
128476259Sgreen
1285295367Sdes		sshbuf_reset(msg);
1286295367Sdes		get_msg(conn, msg);
1287295367Sdes		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1288295367Sdes		    (r = sshbuf_get_u32(msg, &id)) != 0)
1289295367Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
129099060Sdes		debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
129176259Sgreen
129292555Sdes		/* Find the request in our queue */
1293147001Sdes		for (req = TAILQ_FIRST(&requests);
129492555Sdes		    req != NULL && req->id != id;
129592555Sdes		    req = TAILQ_NEXT(req, tq))
129692555Sdes			;
129792555Sdes		if (req == NULL)
129892555Sdes			fatal("Unexpected reply %u", id);
129976259Sgreen
130092555Sdes		switch (type) {
130192555Sdes		case SSH2_FXP_STATUS:
1302295367Sdes			if ((r = sshbuf_get_u32(msg, &status)) != 0)
1303295367Sdes				fatal("%s: buffer error: %s",
1304295367Sdes				    __func__, ssh_err(r));
130592555Sdes			if (status != SSH2_FX_EOF)
130692555Sdes				read_error = 1;
130792555Sdes			max_req = 0;
130892555Sdes			TAILQ_REMOVE(&requests, req, tq);
1309255767Sdes			free(req);
131092555Sdes			num_req--;
131192555Sdes			break;
131292555Sdes		case SSH2_FXP_DATA:
1313295367Sdes			if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
1314295367Sdes				fatal("%s: buffer error: %s",
1315295367Sdes				    __func__, ssh_err(r));
131698675Sdes			debug3("Received data %llu -> %llu",
131798675Sdes			    (unsigned long long)req->offset,
131898675Sdes			    (unsigned long long)req->offset + len - 1);
131992555Sdes			if (len > req->len)
132092555Sdes				fatal("Received more data than asked for "
1321295367Sdes				    "%zu > %zu", len, req->len);
132292555Sdes			if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
1323124208Sdes			    atomicio(vwrite, local_fd, data, len) != len) &&
132492555Sdes			    !write_error) {
132592555Sdes				write_errno = errno;
132692555Sdes				write_error = 1;
132792555Sdes				max_req = 0;
132892555Sdes			}
1329255767Sdes			else if (!reordered && req->offset <= highwater)
1330255767Sdes				highwater = req->offset + len;
1331255767Sdes			else if (!reordered && req->offset > highwater)
1332255767Sdes				reordered = 1;
1333113908Sdes			progress_counter += len;
1334255767Sdes			free(data);
133576259Sgreen
133692555Sdes			if (len == req->len) {
133792555Sdes				TAILQ_REMOVE(&requests, req, tq);
1338255767Sdes				free(req);
133992555Sdes				num_req--;
134092555Sdes			} else {
134192555Sdes				/* Resend the request for the missing data */
134292555Sdes				debug3("Short data block, re-requesting "
134398675Sdes				    "%llu -> %llu (%2d)",
134498675Sdes				    (unsigned long long)req->offset + len,
134598675Sdes				    (unsigned long long)req->offset +
134698675Sdes				    req->len - 1, num_req);
134792555Sdes				req->id = conn->msg_id++;
134892555Sdes				req->len -= len;
134992555Sdes				req->offset += len;
1350221420Sdes				send_read_request(conn, req->id,
135192555Sdes				    req->offset, req->len, handle, handle_len);
135292555Sdes				/* Reduce the request size */
135392555Sdes				if (len < buflen)
135492555Sdes					buflen = MAX(MIN_READ_SIZE, len);
135576259Sgreen			}
135692555Sdes			if (max_req > 0) { /* max_req = 0 iff EOF received */
135792555Sdes				if (size > 0 && offset > size) {
135892555Sdes					/* Only one request at a time
135992555Sdes					 * after the expected EOF */
136092555Sdes					debug3("Finish at %llu (%2d)",
136198675Sdes					    (unsigned long long)offset,
136298675Sdes					    num_req);
136392555Sdes					max_req = 1;
1364137015Sdes				} else if (max_req <= conn->num_requests) {
136592555Sdes					++max_req;
136692555Sdes				}
136792555Sdes			}
136892555Sdes			break;
136992555Sdes		default:
137099060Sdes			fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
137176259Sgreen			    SSH2_FXP_DATA, type);
137276259Sgreen		}
137392555Sdes	}
137476259Sgreen
1375113908Sdes	if (showprogress && size)
1376113908Sdes		stop_progress_meter();
1377113908Sdes
137892555Sdes	/* Sanity check */
137992555Sdes	if (TAILQ_FIRST(&requests) != NULL)
138092555Sdes		fatal("Transfer complete, but requests still in queue");
1381255767Sdes	/* Truncate at highest contiguous point to avoid holes on interrupt */
1382255767Sdes	if (read_error || write_error || interrupted) {
1383262566Sdes		if (reordered && resume_flag) {
1384255767Sdes			error("Unable to resume download of \"%s\": "
1385255767Sdes			    "server reordered requests", local_path);
1386255767Sdes		}
1387255767Sdes		debug("truncating at %llu", (unsigned long long)highwater);
1388295367Sdes		if (ftruncate(local_fd, highwater) == -1)
1389295367Sdes			error("ftruncate \"%s\": %s", local_path,
1390295367Sdes			    strerror(errno));
1391255767Sdes	}
139292555Sdes	if (read_error) {
139398675Sdes		error("Couldn't read from remote file \"%s\" : %s",
139492555Sdes		    remote_path, fx2txt(status));
1395262566Sdes		status = -1;
139692555Sdes		do_close(conn, handle, handle_len);
139792555Sdes	} else if (write_error) {
139892555Sdes		error("Couldn't write to \"%s\": %s", local_path,
139992555Sdes		    strerror(write_errno));
1400295367Sdes		status = SSH2_FX_FAILURE;
140192555Sdes		do_close(conn, handle, handle_len);
140292555Sdes	} else {
1403295367Sdes		if (do_close(conn, handle, handle_len) != 0 || interrupted)
1404295367Sdes			status = SSH2_FX_FAILURE;
1405295367Sdes		else
1406295367Sdes			status = SSH2_FX_OK;
140792555Sdes		/* Override umask and utimes if asked */
140898937Sdes#ifdef HAVE_FCHMOD
1409262566Sdes		if (preserve_flag && fchmod(local_fd, mode) == -1)
1410126274Sdes#else
1411262566Sdes		if (preserve_flag && chmod(local_path, mode) == -1)
141298937Sdes#endif /* HAVE_FCHMOD */
141392555Sdes			error("Couldn't set mode on \"%s\": %s", local_path,
1414113908Sdes			    strerror(errno));
1415262566Sdes		if (preserve_flag &&
1416262566Sdes		    (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
141792555Sdes			struct timeval tv[2];
141892555Sdes			tv[0].tv_sec = a->atime;
141992555Sdes			tv[1].tv_sec = a->mtime;
142092555Sdes			tv[0].tv_usec = tv[1].tv_usec = 0;
142192555Sdes			if (utimes(local_path, tv) == -1)
142292555Sdes				error("Can't set times on \"%s\": %s",
1423113908Sdes				    local_path, strerror(errno));
142476259Sgreen		}
1425262566Sdes		if (fsync_flag) {
1426262566Sdes			debug("syncing \"%s\"", local_path);
1427262566Sdes			if (fsync(local_fd) == -1)
1428262566Sdes				error("Couldn't sync file \"%s\": %s",
1429262566Sdes				    local_path, strerror(errno));
1430262566Sdes		}
143176259Sgreen	}
143276259Sgreen	close(local_fd);
1433295367Sdes	sshbuf_free(msg);
1434255767Sdes	free(handle);
143592555Sdes
143692555Sdes	return(status);
143776259Sgreen}
143876259Sgreen
1439204917Sdesstatic int
1440295367Sdesdownload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1441295367Sdes    int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
1442295367Sdes    int resume_flag, int fsync_flag)
1443204917Sdes{
1444204917Sdes	int i, ret = 0;
1445204917Sdes	SFTP_DIRENT **dir_entries;
1446204917Sdes	char *filename, *new_src, *new_dst;
1447204917Sdes	mode_t mode = 0777;
1448204917Sdes
1449204917Sdes	if (depth >= MAX_DIR_DEPTH) {
1450204917Sdes		error("Maximum directory depth exceeded: %d levels", depth);
1451204917Sdes		return -1;
1452204917Sdes	}
1453204917Sdes
1454204917Sdes	if (dirattrib == NULL &&
1455204917Sdes	    (dirattrib = do_stat(conn, src, 1)) == NULL) {
1456204917Sdes		error("Unable to stat remote directory \"%s\"", src);
1457204917Sdes		return -1;
1458204917Sdes	}
1459204917Sdes	if (!S_ISDIR(dirattrib->perm)) {
1460204917Sdes		error("\"%s\" is not a directory", src);
1461204917Sdes		return -1;
1462204917Sdes	}
1463262566Sdes	if (print_flag)
1464204917Sdes		printf("Retrieving %s\n", src);
1465204917Sdes
1466204917Sdes	if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1467204917Sdes		mode = dirattrib->perm & 01777;
1468204917Sdes	else {
1469204917Sdes		debug("Server did not send permissions for "
1470204917Sdes		    "directory \"%s\"", dst);
1471204917Sdes	}
1472204917Sdes
1473204917Sdes	if (mkdir(dst, mode) == -1 && errno != EEXIST) {
1474204917Sdes		error("mkdir %s: %s", dst, strerror(errno));
1475204917Sdes		return -1;
1476204917Sdes	}
1477204917Sdes
1478204917Sdes	if (do_readdir(conn, src, &dir_entries) == -1) {
1479204917Sdes		error("%s: Failed to get directory contents", src);
1480204917Sdes		return -1;
1481204917Sdes	}
1482204917Sdes
1483204917Sdes	for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
1484204917Sdes		filename = dir_entries[i]->filename;
1485204917Sdes
1486204917Sdes		new_dst = path_append(dst, filename);
1487204917Sdes		new_src = path_append(src, filename);
1488204917Sdes
1489204917Sdes		if (S_ISDIR(dir_entries[i]->a.perm)) {
1490204917Sdes			if (strcmp(filename, ".") == 0 ||
1491204917Sdes			    strcmp(filename, "..") == 0)
1492204917Sdes				continue;
1493204917Sdes			if (download_dir_internal(conn, new_src, new_dst,
1494262566Sdes			    depth + 1, &(dir_entries[i]->a), preserve_flag,
1495262566Sdes			    print_flag, resume_flag, fsync_flag) == -1)
1496204917Sdes				ret = -1;
1497204917Sdes		} else if (S_ISREG(dir_entries[i]->a.perm) ) {
1498204917Sdes			if (do_download(conn, new_src, new_dst,
1499262566Sdes			    &(dir_entries[i]->a), preserve_flag,
1500262566Sdes			    resume_flag, fsync_flag) == -1) {
1501204917Sdes				error("Download of file %s to %s failed",
1502204917Sdes				    new_src, new_dst);
1503204917Sdes				ret = -1;
1504204917Sdes			}
1505204917Sdes		} else
1506204917Sdes			logit("%s: not a regular file\n", new_src);
1507204917Sdes
1508255767Sdes		free(new_dst);
1509255767Sdes		free(new_src);
1510204917Sdes	}
1511204917Sdes
1512262566Sdes	if (preserve_flag) {
1513204917Sdes		if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1514204917Sdes			struct timeval tv[2];
1515204917Sdes			tv[0].tv_sec = dirattrib->atime;
1516204917Sdes			tv[1].tv_sec = dirattrib->mtime;
1517204917Sdes			tv[0].tv_usec = tv[1].tv_usec = 0;
1518204917Sdes			if (utimes(dst, tv) == -1)
1519204917Sdes				error("Can't set times on \"%s\": %s",
1520204917Sdes				    dst, strerror(errno));
1521204917Sdes		} else
1522204917Sdes			debug("Server did not send times for directory "
1523204917Sdes			    "\"%s\"", dst);
1524204917Sdes	}
1525204917Sdes
1526204917Sdes	free_sftp_dirents(dir_entries);
1527204917Sdes
1528204917Sdes	return ret;
1529204917Sdes}
1530204917Sdes
153176259Sgreenint
1532295367Sdesdownload_dir(struct sftp_conn *conn, const char *src, const char *dst,
1533295367Sdes    Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag,
1534295367Sdes    int fsync_flag)
1535204917Sdes{
1536204917Sdes	char *src_canon;
1537204917Sdes	int ret;
1538204917Sdes
1539204917Sdes	if ((src_canon = do_realpath(conn, src)) == NULL) {
1540262566Sdes		error("Unable to canonicalize path \"%s\"", src);
1541204917Sdes		return -1;
1542204917Sdes	}
1543204917Sdes
1544262566Sdes	ret = download_dir_internal(conn, src_canon, dst, 0,
1545262566Sdes	    dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag);
1546255767Sdes	free(src_canon);
1547204917Sdes	return ret;
1548204917Sdes}
1549204917Sdes
1550204917Sdesint
1551295367Sdesdo_upload(struct sftp_conn *conn, const char *local_path,
1552295367Sdes    const char *remote_path, int preserve_flag, int resume, int fsync_flag)
155376259Sgreen{
1554295367Sdes	int r, local_fd;
1555295367Sdes	u_int status = SSH2_FX_OK;
1556295367Sdes	u_int id;
1557295367Sdes	u_char type;
1558255767Sdes	off_t offset, progress_counter;
1559295367Sdes	u_char *handle, *data;
1560295367Sdes	struct sshbuf *msg;
156176259Sgreen	struct stat sb;
1562295367Sdes	Attrib a, *c = NULL;
156392555Sdes	u_int32_t startid;
156492555Sdes	u_int32_t ackid;
156592555Sdes	struct outstanding_ack {
156692555Sdes		u_int id;
156792555Sdes		u_int len;
1568181111Sdes		off_t offset;
156998675Sdes		TAILQ_ENTRY(outstanding_ack) tq;
157092555Sdes	};
157192555Sdes	TAILQ_HEAD(ackhead, outstanding_ack) acks;
1572137015Sdes	struct outstanding_ack *ack = NULL;
1573295367Sdes	size_t handle_len;
157476259Sgreen
157592555Sdes	TAILQ_INIT(&acks);
157692555Sdes
157776259Sgreen	if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
157876259Sgreen		error("Couldn't open local file \"%s\" for reading: %s",
157976259Sgreen		    local_path, strerror(errno));
158076259Sgreen		return(-1);
158176259Sgreen	}
158276259Sgreen	if (fstat(local_fd, &sb) == -1) {
158376259Sgreen		error("Couldn't fstat local file \"%s\": %s",
158476259Sgreen		    local_path, strerror(errno));
158576259Sgreen		close(local_fd);
158676259Sgreen		return(-1);
158776259Sgreen	}
1588113908Sdes	if (!S_ISREG(sb.st_mode)) {
1589113908Sdes		error("%s is not a regular file", local_path);
1590113908Sdes		close(local_fd);
1591113908Sdes		return(-1);
1592113908Sdes	}
159376259Sgreen	stat_to_attrib(&sb, &a);
159476259Sgreen
159576259Sgreen	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
159676259Sgreen	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
159776259Sgreen	a.perm &= 0777;
1598262566Sdes	if (!preserve_flag)
159976259Sgreen		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
160076259Sgreen
1601295367Sdes	if (resume) {
1602295367Sdes		/* Get remote file size if it exists */
1603295367Sdes		if ((c = do_stat(conn, remote_path, 0)) == NULL) {
1604295367Sdes			close(local_fd);
1605295367Sdes			return -1;
1606295367Sdes		}
160776259Sgreen
1608295367Sdes		if ((off_t)c->size >= sb.st_size) {
1609295367Sdes			error("destination file bigger or same size as "
1610295367Sdes			      "source file");
1611295367Sdes			close(local_fd);
1612295367Sdes			return -1;
1613295367Sdes		}
1614295367Sdes
1615295367Sdes		if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) {
1616295367Sdes			close(local_fd);
1617295367Sdes			return -1;
1618295367Sdes		}
1619295367Sdes	}
1620295367Sdes
1621295367Sdes	if ((msg = sshbuf_new()) == NULL)
1622295367Sdes		fatal("%s: sshbuf_new failed", __func__);
1623295367Sdes
162476259Sgreen	/* Send open request */
162592555Sdes	id = conn->msg_id++;
1626295367Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1627295367Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1628295367Sdes	    (r = sshbuf_put_cstring(msg, remote_path)) != 0 ||
1629295367Sdes	    (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|
1630295367Sdes	    (resume ? SSH2_FXF_APPEND : SSH2_FXF_TRUNC))) != 0 ||
1631295367Sdes	    (r = encode_attrib(msg, &a)) != 0)
1632295367Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1633295367Sdes	send_msg(conn, msg);
163499060Sdes	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
163576259Sgreen
1636295367Sdes	sshbuf_reset(msg);
163776259Sgreen
1638221420Sdes	handle = get_handle(conn, id, &handle_len,
1639204917Sdes	    "remote open(\"%s\")", remote_path);
164076259Sgreen	if (handle == NULL) {
164176259Sgreen		close(local_fd);
1642295367Sdes		sshbuf_free(msg);
1643181111Sdes		return -1;
164476259Sgreen	}
164576259Sgreen
164692555Sdes	startid = ackid = id + 1;
164792555Sdes	data = xmalloc(conn->transfer_buflen);
164892555Sdes
164976259Sgreen	/* Read from local and write to remote */
1650295367Sdes	offset = progress_counter = (resume ? c->size : 0);
1651113908Sdes	if (showprogress)
1652255767Sdes		start_progress_meter(local_path, sb.st_size,
1653255767Sdes		    &progress_counter);
1654113908Sdes
165592555Sdes	for (;;) {
165676259Sgreen		int len;
165776259Sgreen
165876259Sgreen		/*
1659137015Sdes		 * Can't use atomicio here because it returns 0 on EOF,
1660137015Sdes		 * thus losing the last block of the file.
1661137015Sdes		 * Simulate an EOF on interrupt, allowing ACKs from the
1662137015Sdes		 * server to drain.
166376259Sgreen		 */
1664181111Sdes		if (interrupted || status != SSH2_FX_OK)
1665137015Sdes			len = 0;
1666137015Sdes		else do
166792555Sdes			len = read(local_fd, data, conn->transfer_buflen);
1668181111Sdes		while ((len == -1) &&
1669181111Sdes		    (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
167076259Sgreen
167176259Sgreen		if (len == -1)
167276259Sgreen			fatal("Couldn't read from \"%s\": %s", local_path,
167376259Sgreen			    strerror(errno));
167492555Sdes
167592555Sdes		if (len != 0) {
1676258343Sdes			ack = xcalloc(1, sizeof(*ack));
167792555Sdes			ack->id = ++id;
167892555Sdes			ack->offset = offset;
167992555Sdes			ack->len = len;
168092555Sdes			TAILQ_INSERT_TAIL(&acks, ack, tq);
168192555Sdes
1682295367Sdes			sshbuf_reset(msg);
1683295367Sdes			if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
1684295367Sdes			    (r = sshbuf_put_u32(msg, ack->id)) != 0 ||
1685295367Sdes			    (r = sshbuf_put_string(msg, handle,
1686295367Sdes			    handle_len)) != 0 ||
1687295367Sdes			    (r = sshbuf_put_u64(msg, offset)) != 0 ||
1688295367Sdes			    (r = sshbuf_put_string(msg, data, len)) != 0)
1689295367Sdes				fatal("%s: buffer error: %s",
1690295367Sdes				    __func__, ssh_err(r));
1691295367Sdes			send_msg(conn, msg);
169299060Sdes			debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
1693113908Sdes			    id, (unsigned long long)offset, len);
169492555Sdes		} else if (TAILQ_FIRST(&acks) == NULL)
169576259Sgreen			break;
169676259Sgreen
169792555Sdes		if (ack == NULL)
169892555Sdes			fatal("Unexpected ACK %u", id);
169976259Sgreen
170098675Sdes		if (id == startid || len == 0 ||
170192555Sdes		    id - ackid >= conn->num_requests) {
1702295367Sdes			u_int rid;
170398675Sdes
1704295367Sdes			sshbuf_reset(msg);
1705295367Sdes			get_msg(conn, msg);
1706295367Sdes			if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1707295367Sdes			    (r = sshbuf_get_u32(msg, &rid)) != 0)
1708295367Sdes				fatal("%s: buffer error: %s",
1709295367Sdes				    __func__, ssh_err(r));
171092555Sdes
171192555Sdes			if (type != SSH2_FXP_STATUS)
171292555Sdes				fatal("Expected SSH2_FXP_STATUS(%d) packet, "
171392555Sdes				    "got %d", SSH2_FXP_STATUS, type);
171492555Sdes
1715295367Sdes			if ((r = sshbuf_get_u32(msg, &status)) != 0)
1716295367Sdes				fatal("%s: buffer error: %s",
1717295367Sdes				    __func__, ssh_err(r));
1718295367Sdes			debug3("SSH2_FXP_STATUS %u", status);
171992555Sdes
172092555Sdes			/* Find the request in our queue */
1721147001Sdes			for (ack = TAILQ_FIRST(&acks);
1722295367Sdes			    ack != NULL && ack->id != rid;
172392555Sdes			    ack = TAILQ_NEXT(ack, tq))
172492555Sdes				;
172592555Sdes			if (ack == NULL)
1726295367Sdes				fatal("Can't find request for ID %u", rid);
172792555Sdes			TAILQ_REMOVE(&acks, ack, tq);
1728181111Sdes			debug3("In write loop, ack for %u %u bytes at %lld",
1729181111Sdes			    ack->id, ack->len, (long long)ack->offset);
173092555Sdes			++ackid;
1731255767Sdes			progress_counter += ack->len;
1732255767Sdes			free(ack);
173376259Sgreen		}
173476259Sgreen		offset += len;
1735181111Sdes		if (offset < 0)
1736181111Sdes			fatal("%s: offset < 0", __func__);
173776259Sgreen	}
1738295367Sdes	sshbuf_free(msg);
1739181111Sdes
1740113908Sdes	if (showprogress)
1741113908Sdes		stop_progress_meter();
1742255767Sdes	free(data);
174376259Sgreen
1744181111Sdes	if (status != SSH2_FX_OK) {
1745181111Sdes		error("Couldn't write to remote file \"%s\": %s",
1746181111Sdes		    remote_path, fx2txt(status));
1747295367Sdes		status = SSH2_FX_FAILURE;
1748181111Sdes	}
1749181111Sdes
175076259Sgreen	if (close(local_fd) == -1) {
175176259Sgreen		error("Couldn't close local file \"%s\": %s", local_path,
175276259Sgreen		    strerror(errno));
1753295367Sdes		status = SSH2_FX_FAILURE;
175476259Sgreen	}
175576259Sgreen
175676259Sgreen	/* Override umask and utimes if asked */
1757262566Sdes	if (preserve_flag)
175892555Sdes		do_fsetstat(conn, handle, handle_len, &a);
175976259Sgreen
1760262566Sdes	if (fsync_flag)
1761262566Sdes		(void)do_fsync(conn, handle, handle_len);
1762262566Sdes
1763296853Sdes	if (do_close(conn, handle, handle_len) != 0)
1764295367Sdes		status = SSH2_FX_FAILURE;
1765295367Sdes
1766255767Sdes	free(handle);
176776259Sgreen
1768295367Sdes	return status == SSH2_FX_OK ? 0 : -1;
176976259Sgreen}
1770204917Sdes
1771204917Sdesstatic int
1772295367Sdesupload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1773295367Sdes    int depth, int preserve_flag, int print_flag, int resume, int fsync_flag)
1774204917Sdes{
1775295367Sdes	int ret = 0;
1776204917Sdes	DIR *dirp;
1777204917Sdes	struct dirent *dp;
1778204917Sdes	char *filename, *new_src, *new_dst;
1779204917Sdes	struct stat sb;
1780296853Sdes	Attrib a, *dirattrib;
1781204917Sdes
1782204917Sdes	if (depth >= MAX_DIR_DEPTH) {
1783204917Sdes		error("Maximum directory depth exceeded: %d levels", depth);
1784204917Sdes		return -1;
1785204917Sdes	}
1786204917Sdes
1787204917Sdes	if (stat(src, &sb) == -1) {
1788204917Sdes		error("Couldn't stat directory \"%s\": %s",
1789204917Sdes		    src, strerror(errno));
1790204917Sdes		return -1;
1791204917Sdes	}
1792204917Sdes	if (!S_ISDIR(sb.st_mode)) {
1793204917Sdes		error("\"%s\" is not a directory", src);
1794204917Sdes		return -1;
1795204917Sdes	}
1796262566Sdes	if (print_flag)
1797204917Sdes		printf("Entering %s\n", src);
1798204917Sdes
1799204917Sdes	attrib_clear(&a);
1800204917Sdes	stat_to_attrib(&sb, &a);
1801204917Sdes	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
1802204917Sdes	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
1803204917Sdes	a.perm &= 01777;
1804262566Sdes	if (!preserve_flag)
1805204917Sdes		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
1806255767Sdes
1807204917Sdes	/*
1808296853Sdes	 * sftp lacks a portable status value to match errno EEXIST,
1809296853Sdes	 * so if we get a failure back then we must check whether
1810296853Sdes	 * the path already existed and is a directory.
1811204917Sdes	 */
1812296853Sdes	if (do_mkdir(conn, dst, &a, 0) != 0) {
1813296853Sdes		if ((dirattrib = do_stat(conn, dst, 0)) == NULL)
1814204917Sdes			return -1;
1815296853Sdes		if (!S_ISDIR(dirattrib->perm)) {
1816296853Sdes			error("\"%s\" exists but is not a directory", dst);
1817204917Sdes			return -1;
1818296853Sdes		}
1819204917Sdes	}
1820204917Sdes
1821204917Sdes	if ((dirp = opendir(src)) == NULL) {
1822204917Sdes		error("Failed to open dir \"%s\": %s", src, strerror(errno));
1823204917Sdes		return -1;
1824204917Sdes	}
1825255767Sdes
1826204917Sdes	while (((dp = readdir(dirp)) != NULL) && !interrupted) {
1827204917Sdes		if (dp->d_ino == 0)
1828204917Sdes			continue;
1829204917Sdes		filename = dp->d_name;
1830204917Sdes		new_dst = path_append(dst, filename);
1831204917Sdes		new_src = path_append(src, filename);
1832204917Sdes
1833204917Sdes		if (lstat(new_src, &sb) == -1) {
1834204917Sdes			logit("%s: lstat failed: %s", filename,
1835204917Sdes			    strerror(errno));
1836204917Sdes			ret = -1;
1837204917Sdes		} else if (S_ISDIR(sb.st_mode)) {
1838204917Sdes			if (strcmp(filename, ".") == 0 ||
1839204917Sdes			    strcmp(filename, "..") == 0)
1840204917Sdes				continue;
1841204917Sdes
1842204917Sdes			if (upload_dir_internal(conn, new_src, new_dst,
1843295367Sdes			    depth + 1, preserve_flag, print_flag, resume,
1844262566Sdes			    fsync_flag) == -1)
1845204917Sdes				ret = -1;
1846204917Sdes		} else if (S_ISREG(sb.st_mode)) {
1847262566Sdes			if (do_upload(conn, new_src, new_dst,
1848295367Sdes			    preserve_flag, resume, fsync_flag) == -1) {
1849204917Sdes				error("Uploading of file %s to %s failed!",
1850204917Sdes				    new_src, new_dst);
1851204917Sdes				ret = -1;
1852204917Sdes			}
1853204917Sdes		} else
1854204917Sdes			logit("%s: not a regular file\n", filename);
1855255767Sdes		free(new_dst);
1856255767Sdes		free(new_src);
1857204917Sdes	}
1858204917Sdes
1859204917Sdes	do_setstat(conn, dst, &a);
1860204917Sdes
1861204917Sdes	(void) closedir(dirp);
1862204917Sdes	return ret;
1863204917Sdes}
1864204917Sdes
1865204917Sdesint
1866295367Sdesupload_dir(struct sftp_conn *conn, const char *src, const char *dst,
1867295367Sdes    int preserve_flag, int print_flag, int resume, int fsync_flag)
1868204917Sdes{
1869204917Sdes	char *dst_canon;
1870204917Sdes	int ret;
1871204917Sdes
1872204917Sdes	if ((dst_canon = do_realpath(conn, dst)) == NULL) {
1873262566Sdes		error("Unable to canonicalize path \"%s\"", dst);
1874204917Sdes		return -1;
1875204917Sdes	}
1876204917Sdes
1877262566Sdes	ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag,
1878295367Sdes	    print_flag, resume, fsync_flag);
1879262566Sdes
1880255767Sdes	free(dst_canon);
1881204917Sdes	return ret;
1882204917Sdes}
1883204917Sdes
1884204917Sdeschar *
1885295367Sdespath_append(const char *p1, const char *p2)
1886204917Sdes{
1887204917Sdes	char *ret;
1888204917Sdes	size_t len = strlen(p1) + strlen(p2) + 2;
1889204917Sdes
1890204917Sdes	ret = xmalloc(len);
1891204917Sdes	strlcpy(ret, p1, len);
1892204917Sdes	if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
1893204917Sdes		strlcat(ret, "/", len);
1894204917Sdes	strlcat(ret, p2, len);
1895204917Sdes
1896204917Sdes	return(ret);
1897204917Sdes}
1898204917Sdes
1899