1323136Sdes/* $OpenBSD: sftp-client.c,v 1.126 2017/01/03 05:46:51 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
25162852Sdes#include <sys/types.h>
26181111Sdes#ifdef HAVE_SYS_STATVFS_H
27181111Sdes#include <sys/statvfs.h>
28181111Sdes#endif
29106121Sdes#include "openbsd-compat/sys-queue.h"
30162852Sdes#ifdef HAVE_SYS_STAT_H
31162852Sdes# include <sys/stat.h>
32162852Sdes#endif
33162852Sdes#ifdef HAVE_SYS_TIME_H
34162852Sdes# include <sys/time.h>
35162852Sdes#endif
36162852Sdes#include <sys/uio.h>
3792555Sdes
38204917Sdes#include <dirent.h>
39162852Sdes#include <errno.h>
40162852Sdes#include <fcntl.h>
41162852Sdes#include <signal.h>
42162852Sdes#include <stdarg.h>
43162852Sdes#include <stdio.h>
44261320Sdes#include <stdlib.h>
45162852Sdes#include <string.h>
46162852Sdes#include <unistd.h>
47162852Sdes
48162852Sdes#include "xmalloc.h"
49294332Sdes#include "ssherr.h"
50294332Sdes#include "sshbuf.h"
5176259Sgreen#include "log.h"
5276259Sgreen#include "atomicio.h"
53113908Sdes#include "progressmeter.h"
54162852Sdes#include "misc.h"
55323129Sdes#include "utf8.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
70323136Sdes/* Directory separator characters */
71323136Sdes#ifdef HAVE_CYGWIN
72323136Sdes# define SFTP_DIRECTORY_CHARS      "/\\"
73323136Sdes#else /* HAVE_CYGWIN */
74323136Sdes# define SFTP_DIRECTORY_CHARS      "/"
75323136Sdes#endif /* HAVE_CYGWIN */
76323136Sdes
7792555Sdesstruct sftp_conn {
7892555Sdes	int fd_in;
7992555Sdes	int fd_out;
8092555Sdes	u_int transfer_buflen;
8192555Sdes	u_int num_requests;
8292555Sdes	u_int version;
8392555Sdes	u_int msg_id;
84181111Sdes#define SFTP_EXT_POSIX_RENAME	0x00000001
85181111Sdes#define SFTP_EXT_STATVFS	0x00000002
86181111Sdes#define SFTP_EXT_FSTATVFS	0x00000004
87221420Sdes#define SFTP_EXT_HARDLINK	0x00000008
88261320Sdes#define SFTP_EXT_FSYNC		0x00000010
89181111Sdes	u_int exts;
90221420Sdes	u_int64_t limit_kbps;
91221420Sdes	struct bwlimit bwlimit_in, bwlimit_out;
9292555Sdes};
9376259Sgreen
94294332Sdesstatic u_char *
95294332Sdesget_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
96221420Sdes    const char *errfmt, ...) __attribute__((format(printf, 4, 5)));
97204917Sdes
98221420Sdes/* ARGSUSED */
99221420Sdesstatic int
100221420Sdessftpio(void *_bwlimit, size_t amount)
101221420Sdes{
102221420Sdes	struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;
103221420Sdes
104221420Sdes	bandwidth_limit(bwlimit, amount);
105221420Sdes	return 0;
106221420Sdes}
107221420Sdes
10892555Sdesstatic void
109294332Sdessend_msg(struct sftp_conn *conn, struct sshbuf *m)
11076259Sgreen{
111113908Sdes	u_char mlen[4];
112162852Sdes	struct iovec iov[2];
11376259Sgreen
114294332Sdes	if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH)
115294332Sdes		fatal("Outbound message too long %zu", sshbuf_len(m));
11676259Sgreen
117113908Sdes	/* Send length first */
118294332Sdes	put_u32(mlen, sshbuf_len(m));
119162852Sdes	iov[0].iov_base = mlen;
120162852Sdes	iov[0].iov_len = sizeof(mlen);
121294332Sdes	iov[1].iov_base = (u_char *)sshbuf_ptr(m);
122294332Sdes	iov[1].iov_len = sshbuf_len(m);
12376259Sgreen
124221420Sdes	if (atomiciov6(writev, conn->fd_out, iov, 2,
125255767Sdes	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) !=
126294332Sdes	    sshbuf_len(m) + sizeof(mlen))
127113908Sdes		fatal("Couldn't send packet: %s", strerror(errno));
128113908Sdes
129294332Sdes	sshbuf_reset(m);
13076259Sgreen}
13176259Sgreen
13292555Sdesstatic void
133294332Sdesget_msg(struct sftp_conn *conn, struct sshbuf *m)
13476259Sgreen{
135113908Sdes	u_int msg_len;
136294332Sdes	u_char *p;
137294332Sdes	int r;
13876259Sgreen
139294332Sdes	if ((r = sshbuf_reserve(m, 4, &p)) != 0)
140294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
141294332Sdes	if (atomicio6(read, conn->fd_in, p, 4,
142221420Sdes	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) {
143149749Sdes		if (errno == EPIPE)
144149749Sdes			fatal("Connection closed");
145149749Sdes		else
146149749Sdes			fatal("Couldn't read packet: %s", strerror(errno));
147149749Sdes	}
14876259Sgreen
149294332Sdes	if ((r = sshbuf_get_u32(m, &msg_len)) != 0)
150294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
151157016Sdes	if (msg_len > SFTP_MAX_MSG_LENGTH)
15299060Sdes		fatal("Received message too long %u", msg_len);
15376259Sgreen
154294332Sdes	if ((r = sshbuf_reserve(m, msg_len, &p)) != 0)
155294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
156294332Sdes	if (atomicio6(read, conn->fd_in, p, msg_len,
157221420Sdes	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in)
158221420Sdes	    != msg_len) {
159149749Sdes		if (errno == EPIPE)
160149749Sdes			fatal("Connection closed");
161149749Sdes		else
162149749Sdes			fatal("Read packet: %s", strerror(errno));
163149749Sdes	}
16476259Sgreen}
16576259Sgreen
16692555Sdesstatic void
167294332Sdessend_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s,
16876259Sgreen    u_int len)
16976259Sgreen{
170294332Sdes	struct sshbuf *msg;
171294332Sdes	int r;
17276259Sgreen
173294332Sdes	if ((msg = sshbuf_new()) == NULL)
174294332Sdes		fatal("%s: sshbuf_new failed", __func__);
175294332Sdes	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
176294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
177294332Sdes	    (r = sshbuf_put_string(msg, s, len)) != 0)
178294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
179294332Sdes	send_msg(conn, msg);
180221420Sdes	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
181294332Sdes	sshbuf_free(msg);
18276259Sgreen}
18376259Sgreen
18492555Sdesstatic void
185221420Sdessend_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
186294332Sdes    const void *s, u_int len, Attrib *a)
18776259Sgreen{
188294332Sdes	struct sshbuf *msg;
189294332Sdes	int r;
19076259Sgreen
191294332Sdes	if ((msg = sshbuf_new()) == NULL)
192294332Sdes		fatal("%s: sshbuf_new failed", __func__);
193294332Sdes	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
194294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
195294332Sdes	    (r = sshbuf_put_string(msg, s, len)) != 0 ||
196294332Sdes	    (r = encode_attrib(msg, a)) != 0)
197294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
198294332Sdes	send_msg(conn, msg);
199221420Sdes	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
200294332Sdes	sshbuf_free(msg);
20176259Sgreen}
20276259Sgreen
20392555Sdesstatic u_int
204221420Sdesget_status(struct sftp_conn *conn, u_int expected_id)
20576259Sgreen{
206294332Sdes	struct sshbuf *msg;
207294332Sdes	u_char type;
208294332Sdes	u_int id, status;
209294332Sdes	int r;
21076259Sgreen
211294332Sdes	if ((msg = sshbuf_new()) == NULL)
212294332Sdes		fatal("%s: sshbuf_new failed", __func__);
213294332Sdes	get_msg(conn, msg);
214294332Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
215294332Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
216294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
21776259Sgreen
21876259Sgreen	if (id != expected_id)
21999060Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
22076259Sgreen	if (type != SSH2_FXP_STATUS)
22199060Sdes		fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
22276259Sgreen		    SSH2_FXP_STATUS, type);
22376259Sgreen
224294332Sdes	if ((r = sshbuf_get_u32(msg, &status)) != 0)
225294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
226294332Sdes	sshbuf_free(msg);
22776259Sgreen
22899060Sdes	debug3("SSH2_FXP_STATUS %u", status);
22976259Sgreen
230221420Sdes	return status;
23176259Sgreen}
23276259Sgreen
233294332Sdesstatic u_char *
234294332Sdesget_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
235221420Sdes    const char *errfmt, ...)
23676259Sgreen{
237294332Sdes	struct sshbuf *msg;
238294332Sdes	u_int id, status;
239294332Sdes	u_char type;
240294332Sdes	u_char *handle;
241294332Sdes	char errmsg[256];
242204917Sdes	va_list args;
243294332Sdes	int r;
24476259Sgreen
245204917Sdes	va_start(args, errfmt);
246204917Sdes	if (errfmt != NULL)
247204917Sdes		vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
248204917Sdes	va_end(args);
249204917Sdes
250294332Sdes	if ((msg = sshbuf_new()) == NULL)
251294332Sdes		fatal("%s: sshbuf_new failed", __func__);
252294332Sdes	get_msg(conn, msg);
253294332Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
254294332Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
255294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
25676259Sgreen
25776259Sgreen	if (id != expected_id)
258204917Sdes		fatal("%s: ID mismatch (%u != %u)",
259204917Sdes		    errfmt == NULL ? __func__ : errmsg, id, expected_id);
26076259Sgreen	if (type == SSH2_FXP_STATUS) {
261294332Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
262294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
263204917Sdes		if (errfmt != NULL)
264204917Sdes			error("%s: %s", errmsg, fx2txt(status));
265294332Sdes		sshbuf_free(msg);
26676259Sgreen		return(NULL);
26776259Sgreen	} else if (type != SSH2_FXP_HANDLE)
268204917Sdes		fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
269204917Sdes		    errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
27076259Sgreen
271294332Sdes	if ((r = sshbuf_get_string(msg, &handle, len)) != 0)
272294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
273294332Sdes	sshbuf_free(msg);
27476259Sgreen
275294332Sdes	return handle;
27676259Sgreen}
27776259Sgreen
27892555Sdesstatic Attrib *
279221420Sdesget_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
28076259Sgreen{
281294332Sdes	struct sshbuf *msg;
282294332Sdes	u_int id;
283294332Sdes	u_char type;
284294332Sdes	int r;
285294332Sdes	static Attrib a;
28676259Sgreen
287294332Sdes	if ((msg = sshbuf_new()) == NULL)
288294332Sdes		fatal("%s: sshbuf_new failed", __func__);
289294332Sdes	get_msg(conn, msg);
29076259Sgreen
291294332Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
292294332Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
293294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
29476259Sgreen
29599060Sdes	debug3("Received stat reply T:%u I:%u", type, id);
29676259Sgreen	if (id != expected_id)
29799060Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
29876259Sgreen	if (type == SSH2_FXP_STATUS) {
299294332Sdes		u_int status;
30076259Sgreen
301294332Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
302294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
30376259Sgreen		if (quiet)
30476259Sgreen			debug("Couldn't stat remote file: %s", fx2txt(status));
30576259Sgreen		else
30676259Sgreen			error("Couldn't stat remote file: %s", fx2txt(status));
307294332Sdes		sshbuf_free(msg);
30876259Sgreen		return(NULL);
30976259Sgreen	} else if (type != SSH2_FXP_ATTRS) {
31099060Sdes		fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
31176259Sgreen		    SSH2_FXP_ATTRS, type);
31276259Sgreen	}
313294332Sdes	if ((r = decode_attrib(msg, &a)) != 0) {
314294332Sdes		error("%s: couldn't decode attrib: %s", __func__, ssh_err(r));
315294332Sdes		sshbuf_free(msg);
316294332Sdes		return NULL;
317294332Sdes	}
318294332Sdes	sshbuf_free(msg);
31976259Sgreen
320294332Sdes	return &a;
32176259Sgreen}
32276259Sgreen
323181111Sdesstatic int
324221420Sdesget_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
325221420Sdes    u_int expected_id, int quiet)
326181111Sdes{
327294332Sdes	struct sshbuf *msg;
328294332Sdes	u_char type;
329294332Sdes	u_int id;
330294332Sdes	u_int64_t flag;
331294332Sdes	int r;
332181111Sdes
333294332Sdes	if ((msg = sshbuf_new()) == NULL)
334294332Sdes		fatal("%s: sshbuf_new failed", __func__);
335294332Sdes	get_msg(conn, msg);
336181111Sdes
337294332Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
338294332Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
339294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
340181111Sdes
341181111Sdes	debug3("Received statvfs reply T:%u I:%u", type, id);
342181111Sdes	if (id != expected_id)
343181111Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
344181111Sdes	if (type == SSH2_FXP_STATUS) {
345294332Sdes		u_int status;
346181111Sdes
347294332Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
348294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
349181111Sdes		if (quiet)
350181111Sdes			debug("Couldn't statvfs: %s", fx2txt(status));
351181111Sdes		else
352181111Sdes			error("Couldn't statvfs: %s", fx2txt(status));
353294332Sdes		sshbuf_free(msg);
354181111Sdes		return -1;
355181111Sdes	} else if (type != SSH2_FXP_EXTENDED_REPLY) {
356181111Sdes		fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
357181111Sdes		    SSH2_FXP_EXTENDED_REPLY, type);
358181111Sdes	}
359181111Sdes
360263712Sdes	memset(st, 0, sizeof(*st));
361294332Sdes	if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 ||
362294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 ||
363294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 ||
364294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 ||
365294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 ||
366294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_files)) != 0 ||
367294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 ||
368294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 ||
369294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 ||
370294332Sdes	    (r = sshbuf_get_u64(msg, &flag)) != 0 ||
371294332Sdes	    (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0)
372294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
373181111Sdes
374181111Sdes	st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
375181111Sdes	st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
376181111Sdes
377294332Sdes	sshbuf_free(msg);
378181111Sdes
379181111Sdes	return 0;
380181111Sdes}
381181111Sdes
38292555Sdesstruct sftp_conn *
383221420Sdesdo_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
384221420Sdes    u_int64_t limit_kbps)
38576259Sgreen{
386294332Sdes	u_char type;
387294332Sdes	struct sshbuf *msg;
38892555Sdes	struct sftp_conn *ret;
389294332Sdes	int r;
39076259Sgreen
391261320Sdes	ret = xcalloc(1, sizeof(*ret));
392261320Sdes	ret->msg_id = 1;
393221420Sdes	ret->fd_in = fd_in;
394221420Sdes	ret->fd_out = fd_out;
395221420Sdes	ret->transfer_buflen = transfer_buflen;
396221420Sdes	ret->num_requests = num_requests;
397221420Sdes	ret->exts = 0;
398221420Sdes	ret->limit_kbps = 0;
399221420Sdes
400294332Sdes	if ((msg = sshbuf_new()) == NULL)
401294332Sdes		fatal("%s: sshbuf_new failed", __func__);
402294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 ||
403294332Sdes	    (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0)
404294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
405294332Sdes	send_msg(ret, msg);
40676259Sgreen
407294332Sdes	sshbuf_reset(msg);
40876259Sgreen
409294332Sdes	get_msg(ret, msg);
41076259Sgreen
41176259Sgreen	/* Expecting a VERSION reply */
412294332Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0)
413294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
414294332Sdes	if (type != SSH2_FXP_VERSION) {
41599060Sdes		error("Invalid packet back from SSH2_FXP_INIT (type %u)",
41676259Sgreen		    type);
417294332Sdes		sshbuf_free(msg);
418294336Sdes		free(ret);
41992555Sdes		return(NULL);
42076259Sgreen	}
421294332Sdes	if ((r = sshbuf_get_u32(msg, &ret->version)) != 0)
422294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
42376259Sgreen
424221420Sdes	debug2("Remote version: %u", ret->version);
42576259Sgreen
42676259Sgreen	/* Check for extensions */
427294332Sdes	while (sshbuf_len(msg) > 0) {
428294332Sdes		char *name;
429294332Sdes		u_char *value;
430294332Sdes		size_t vlen;
431181111Sdes		int known = 0;
43276259Sgreen
433294332Sdes		if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 ||
434294332Sdes		    (r = sshbuf_get_string(msg, &value, &vlen)) != 0)
435294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
436181111Sdes		if (strcmp(name, "posix-rename@openssh.com") == 0 &&
437294332Sdes		    strcmp((char *)value, "1") == 0) {
438221420Sdes			ret->exts |= SFTP_EXT_POSIX_RENAME;
439181111Sdes			known = 1;
440181111Sdes		} else if (strcmp(name, "statvfs@openssh.com") == 0 &&
441294332Sdes		    strcmp((char *)value, "2") == 0) {
442221420Sdes			ret->exts |= SFTP_EXT_STATVFS;
443181111Sdes			known = 1;
444221420Sdes		} else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
445294332Sdes		    strcmp((char *)value, "2") == 0) {
446221420Sdes			ret->exts |= SFTP_EXT_FSTATVFS;
447181111Sdes			known = 1;
448221420Sdes		} else if (strcmp(name, "hardlink@openssh.com") == 0 &&
449294332Sdes		    strcmp((char *)value, "1") == 0) {
450221420Sdes			ret->exts |= SFTP_EXT_HARDLINK;
451221420Sdes			known = 1;
452294332Sdes		} else if (strcmp(name, "fsync@openssh.com") == 0 &&
453294332Sdes		    strcmp((char *)value, "1") == 0) {
454294332Sdes			ret->exts |= SFTP_EXT_FSYNC;
455294332Sdes			known = 1;
456181111Sdes		}
457181111Sdes		if (known) {
458181111Sdes			debug2("Server supports extension \"%s\" revision %s",
459181111Sdes			    name, value);
460181111Sdes		} else {
461181111Sdes			debug2("Unrecognised server extension \"%s\"", name);
462181111Sdes		}
463255767Sdes		free(name);
464255767Sdes		free(value);
46576259Sgreen	}
46676259Sgreen
467294332Sdes	sshbuf_free(msg);
46876259Sgreen
46992555Sdes	/* Some filexfer v.0 servers don't support large packets */
470221420Sdes	if (ret->version == 0)
471323134Sdes		ret->transfer_buflen = MINIMUM(ret->transfer_buflen, 20480);
47292555Sdes
473221420Sdes	ret->limit_kbps = limit_kbps;
474221420Sdes	if (ret->limit_kbps > 0) {
475221420Sdes		bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
476221420Sdes		    ret->transfer_buflen);
477221420Sdes		bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
478221420Sdes		    ret->transfer_buflen);
479221420Sdes	}
480221420Sdes
481221420Sdes	return ret;
48276259Sgreen}
48376259Sgreen
48492555Sdesu_int
48592555Sdessftp_proto_version(struct sftp_conn *conn)
48692555Sdes{
487221420Sdes	return conn->version;
48892555Sdes}
48992555Sdes
49076259Sgreenint
491294332Sdesdo_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
49276259Sgreen{
49376259Sgreen	u_int id, status;
494294332Sdes	struct sshbuf *msg;
495294332Sdes	int r;
49676259Sgreen
497294332Sdes	if ((msg = sshbuf_new()) == NULL)
498294332Sdes		fatal("%s: sshbuf_new failed", __func__);
49976259Sgreen
50092555Sdes	id = conn->msg_id++;
501294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 ||
502294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
503294332Sdes	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
504294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
505294332Sdes	send_msg(conn, msg);
50699060Sdes	debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
50776259Sgreen
508221420Sdes	status = get_status(conn, id);
50976259Sgreen	if (status != SSH2_FX_OK)
51076259Sgreen		error("Couldn't close file: %s", fx2txt(status));
51176259Sgreen
512294332Sdes	sshbuf_free(msg);
51376259Sgreen
514294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
51576259Sgreen}
51676259Sgreen
51776259Sgreen
51892555Sdesstatic int
519294332Sdesdo_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
52076259Sgreen    SFTP_DIRENT ***dir)
52176259Sgreen{
522294332Sdes	struct sshbuf *msg;
523294332Sdes	u_int count, id, i, expected_id, ents = 0;
524294332Sdes	size_t handle_len;
525323129Sdes	u_char type, *handle;
526261320Sdes	int status = SSH2_FX_FAILURE;
527294332Sdes	int r;
52876259Sgreen
529261320Sdes	if (dir)
530261320Sdes		*dir = NULL;
531261320Sdes
53292555Sdes	id = conn->msg_id++;
53376259Sgreen
534294332Sdes	if ((msg = sshbuf_new()) == NULL)
535294332Sdes		fatal("%s: sshbuf_new failed", __func__);
536294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 ||
537294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
538294332Sdes	    (r = sshbuf_put_cstring(msg, path)) != 0)
539294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
540294332Sdes	send_msg(conn, msg);
54176259Sgreen
542221420Sdes	handle = get_handle(conn, id, &handle_len,
543204917Sdes	    "remote readdir(\"%s\")", path);
544240075Sdes	if (handle == NULL) {
545294332Sdes		sshbuf_free(msg);
546221420Sdes		return -1;
547240075Sdes	}
54876259Sgreen
54976259Sgreen	if (dir) {
55076259Sgreen		ents = 0;
551257954Sdelphij		*dir = xcalloc(1, sizeof(**dir));
55276259Sgreen		(*dir)[0] = NULL;
55376259Sgreen	}
55476259Sgreen
555137015Sdes	for (; !interrupted;) {
55692555Sdes		id = expected_id = conn->msg_id++;
55776259Sgreen
55899060Sdes		debug3("Sending SSH2_FXP_READDIR I:%u", id);
55976259Sgreen
560294332Sdes		sshbuf_reset(msg);
561294332Sdes		if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 ||
562294332Sdes		    (r = sshbuf_put_u32(msg, id)) != 0 ||
563294332Sdes		    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
564294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
565294332Sdes		send_msg(conn, msg);
56676259Sgreen
567294332Sdes		sshbuf_reset(msg);
56876259Sgreen
569294332Sdes		get_msg(conn, msg);
57076259Sgreen
571294332Sdes		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
572294332Sdes		    (r = sshbuf_get_u32(msg, &id)) != 0)
573294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
57476259Sgreen
57599060Sdes		debug3("Received reply T:%u I:%u", type, id);
57676259Sgreen
57776259Sgreen		if (id != expected_id)
57899060Sdes			fatal("ID mismatch (%u != %u)", id, expected_id);
57976259Sgreen
58076259Sgreen		if (type == SSH2_FXP_STATUS) {
581294332Sdes			u_int rstatus;
582294332Sdes
583294332Sdes			if ((r = sshbuf_get_u32(msg, &rstatus)) != 0)
584294332Sdes				fatal("%s: buffer error: %s",
585294332Sdes				    __func__, ssh_err(r));
586294332Sdes			debug3("Received SSH2_FXP_STATUS %d", rstatus);
587294332Sdes			if (rstatus == SSH2_FX_EOF)
58876259Sgreen				break;
589294332Sdes			error("Couldn't read directory: %s", fx2txt(rstatus));
590261320Sdes			goto out;
59176259Sgreen		} else if (type != SSH2_FXP_NAME)
59299060Sdes			fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
59376259Sgreen			    SSH2_FXP_NAME, type);
59476259Sgreen
595294332Sdes		if ((r = sshbuf_get_u32(msg, &count)) != 0)
596294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
597323136Sdes		if (count > SSHBUF_SIZE_MAX)
598323136Sdes			fatal("%s: nonsensical number of entries", __func__);
59976259Sgreen		if (count == 0)
60076259Sgreen			break;
60176259Sgreen		debug3("Received %d SSH2_FXP_NAME responses", count);
60292555Sdes		for (i = 0; i < count; i++) {
60376259Sgreen			char *filename, *longname;
604294332Sdes			Attrib a;
60576259Sgreen
606294332Sdes			if ((r = sshbuf_get_cstring(msg, &filename,
607294332Sdes			    NULL)) != 0 ||
608294332Sdes			    (r = sshbuf_get_cstring(msg, &longname,
609294332Sdes			    NULL)) != 0)
610294332Sdes				fatal("%s: buffer error: %s",
611294332Sdes				    __func__, ssh_err(r));
612294332Sdes			if ((r = decode_attrib(msg, &a)) != 0) {
613294332Sdes				error("%s: couldn't decode attrib: %s",
614294332Sdes				    __func__, ssh_err(r));
615294332Sdes				free(filename);
616294332Sdes				free(longname);
617294332Sdes				sshbuf_free(msg);
618294332Sdes				return -1;
619294332Sdes			}
62076259Sgreen
621261320Sdes			if (print_flag)
622323129Sdes				mprintf("%s\n", longname);
62376259Sgreen
624204917Sdes			/*
625204917Sdes			 * Directory entries should never contain '/'
626204917Sdes			 * These can be used to attack recursive ops
627204917Sdes			 * (e.g. send '../../../../etc/passwd')
628204917Sdes			 */
629323136Sdes			if (strpbrk(filename, SFTP_DIRECTORY_CHARS) != NULL) {
630204917Sdes				error("Server sent suspect path \"%s\" "
631204917Sdes				    "during readdir of \"%s\"", filename, path);
632261320Sdes			} else if (dir) {
633294336Sdes				*dir = xreallocarray(*dir, ents + 2, sizeof(**dir));
634257954Sdelphij				(*dir)[ents] = xcalloc(1, sizeof(***dir));
63576259Sgreen				(*dir)[ents]->filename = xstrdup(filename);
63676259Sgreen				(*dir)[ents]->longname = xstrdup(longname);
637294332Sdes				memcpy(&(*dir)[ents]->a, &a, sizeof(a));
63876259Sgreen				(*dir)[++ents] = NULL;
63976259Sgreen			}
640255767Sdes			free(filename);
641255767Sdes			free(longname);
64276259Sgreen		}
64376259Sgreen	}
644261320Sdes	status = 0;
64576259Sgreen
646261320Sdes out:
647294332Sdes	sshbuf_free(msg);
64892555Sdes	do_close(conn, handle, handle_len);
649255767Sdes	free(handle);
65076259Sgreen
651261320Sdes	if (status != 0 && dir != NULL) {
652261320Sdes		/* Don't return results on error */
653137015Sdes		free_sftp_dirents(*dir);
654261320Sdes		*dir = NULL;
655261320Sdes	} else if (interrupted && dir != NULL && *dir != NULL) {
656261320Sdes		/* Don't return partial matches on interrupt */
657261320Sdes		free_sftp_dirents(*dir);
658257954Sdelphij		*dir = xcalloc(1, sizeof(**dir));
659137015Sdes		**dir = NULL;
660137015Sdes	}
661137015Sdes
662261320Sdes	return status;
66376259Sgreen}
66476259Sgreen
66576259Sgreenint
666294332Sdesdo_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
66776259Sgreen{
66892555Sdes	return(do_lsreaddir(conn, path, 0, dir));
66976259Sgreen}
67076259Sgreen
67176259Sgreenvoid free_sftp_dirents(SFTP_DIRENT **s)
67276259Sgreen{
67376259Sgreen	int i;
67492555Sdes
675261320Sdes	if (s == NULL)
676261320Sdes		return;
67792555Sdes	for (i = 0; s[i]; i++) {
678255767Sdes		free(s[i]->filename);
679255767Sdes		free(s[i]->longname);
680255767Sdes		free(s[i]);
68176259Sgreen	}
682255767Sdes	free(s);
68376259Sgreen}
68476259Sgreen
68576259Sgreenint
686294332Sdesdo_rm(struct sftp_conn *conn, const char *path)
68776259Sgreen{
68876259Sgreen	u_int status, id;
68976259Sgreen
69076259Sgreen	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
69176259Sgreen
69292555Sdes	id = conn->msg_id++;
693221420Sdes	send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
694221420Sdes	status = get_status(conn, id);
69576259Sgreen	if (status != SSH2_FX_OK)
69676259Sgreen		error("Couldn't delete file: %s", fx2txt(status));
697294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
69876259Sgreen}
69976259Sgreen
70076259Sgreenint
701294332Sdesdo_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
70276259Sgreen{
70376259Sgreen	u_int status, id;
70476259Sgreen
70592555Sdes	id = conn->msg_id++;
706221420Sdes	send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
70776259Sgreen	    strlen(path), a);
70876259Sgreen
709221420Sdes	status = get_status(conn, id);
710261320Sdes	if (status != SSH2_FX_OK && print_flag)
71176259Sgreen		error("Couldn't create directory: %s", fx2txt(status));
71276259Sgreen
713294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
71476259Sgreen}
71576259Sgreen
71676259Sgreenint
717294332Sdesdo_rmdir(struct sftp_conn *conn, const char *path)
71876259Sgreen{
71976259Sgreen	u_int status, id;
72076259Sgreen
72192555Sdes	id = conn->msg_id++;
722221420Sdes	send_string_request(conn, id, SSH2_FXP_RMDIR, path,
72392555Sdes	    strlen(path));
72476259Sgreen
725221420Sdes	status = get_status(conn, id);
72676259Sgreen	if (status != SSH2_FX_OK)
72776259Sgreen		error("Couldn't remove directory: %s", fx2txt(status));
72876259Sgreen
729294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
73076259Sgreen}
73176259Sgreen
73276259SgreenAttrib *
733294332Sdesdo_stat(struct sftp_conn *conn, const char *path, int quiet)
73476259Sgreen{
73576259Sgreen	u_int id;
73676259Sgreen
73792555Sdes	id = conn->msg_id++;
73892555Sdes
739221420Sdes	send_string_request(conn, id,
74098675Sdes	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
74192555Sdes	    path, strlen(path));
74292555Sdes
743221420Sdes	return(get_decode_stat(conn, id, quiet));
74476259Sgreen}
74576259Sgreen
74676259SgreenAttrib *
747294332Sdesdo_lstat(struct sftp_conn *conn, const char *path, int quiet)
74876259Sgreen{
74976259Sgreen	u_int id;
75076259Sgreen
75192555Sdes	if (conn->version == 0) {
75292555Sdes		if (quiet)
75392555Sdes			debug("Server version does not support lstat operation");
75492555Sdes		else
755124208Sdes			logit("Server version does not support lstat operation");
75698675Sdes		return(do_stat(conn, path, quiet));
75792555Sdes	}
75892555Sdes
75992555Sdes	id = conn->msg_id++;
760221420Sdes	send_string_request(conn, id, SSH2_FXP_LSTAT, path,
76192555Sdes	    strlen(path));
76292555Sdes
763221420Sdes	return(get_decode_stat(conn, id, quiet));
76476259Sgreen}
76576259Sgreen
766181111Sdes#ifdef notyet
76776259SgreenAttrib *
768294332Sdesdo_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
769294332Sdes    int quiet)
77076259Sgreen{
77176259Sgreen	u_int id;
77276259Sgreen
77392555Sdes	id = conn->msg_id++;
774221420Sdes	send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
77592555Sdes	    handle_len);
77692555Sdes
777221420Sdes	return(get_decode_stat(conn, id, quiet));
77876259Sgreen}
779181111Sdes#endif
78076259Sgreen
78176259Sgreenint
782294332Sdesdo_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
78376259Sgreen{
78476259Sgreen	u_int status, id;
78576259Sgreen
78692555Sdes	id = conn->msg_id++;
787221420Sdes	send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
78876259Sgreen	    strlen(path), a);
78976259Sgreen
790221420Sdes	status = get_status(conn, id);
79176259Sgreen	if (status != SSH2_FX_OK)
79276259Sgreen		error("Couldn't setstat on \"%s\": %s", path,
79376259Sgreen		    fx2txt(status));
79476259Sgreen
795294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
79676259Sgreen}
79776259Sgreen
79876259Sgreenint
799294332Sdesdo_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
80076259Sgreen    Attrib *a)
80176259Sgreen{
80276259Sgreen	u_int status, id;
80376259Sgreen
80492555Sdes	id = conn->msg_id++;
805221420Sdes	send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
80676259Sgreen	    handle_len, a);
80776259Sgreen
808221420Sdes	status = get_status(conn, id);
80976259Sgreen	if (status != SSH2_FX_OK)
81076259Sgreen		error("Couldn't fsetstat: %s", fx2txt(status));
81176259Sgreen
812294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
81376259Sgreen}
81476259Sgreen
81576259Sgreenchar *
816294332Sdesdo_realpath(struct sftp_conn *conn, const char *path)
81776259Sgreen{
818294332Sdes	struct sshbuf *msg;
819294332Sdes	u_int expected_id, count, id;
82076259Sgreen	char *filename, *longname;
821294332Sdes	Attrib a;
822294332Sdes	u_char type;
823294332Sdes	int r;
82476259Sgreen
82592555Sdes	expected_id = id = conn->msg_id++;
826221420Sdes	send_string_request(conn, id, SSH2_FXP_REALPATH, path,
82792555Sdes	    strlen(path));
82876259Sgreen
829294332Sdes	if ((msg = sshbuf_new()) == NULL)
830294332Sdes		fatal("%s: sshbuf_new failed", __func__);
83176259Sgreen
832294332Sdes	get_msg(conn, msg);
833294332Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
834294332Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
835294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
83676259Sgreen
83776259Sgreen	if (id != expected_id)
83899060Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
83976259Sgreen
84076259Sgreen	if (type == SSH2_FXP_STATUS) {
841294332Sdes		u_int status;
84276259Sgreen
843294332Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
844294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
845261320Sdes		error("Couldn't canonicalize: %s", fx2txt(status));
846294332Sdes		sshbuf_free(msg);
847215116Sdes		return NULL;
84876259Sgreen	} else if (type != SSH2_FXP_NAME)
84999060Sdes		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
85076259Sgreen		    SSH2_FXP_NAME, type);
85176259Sgreen
852294332Sdes	if ((r = sshbuf_get_u32(msg, &count)) != 0)
853294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
85476259Sgreen	if (count != 1)
85576259Sgreen		fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
85676259Sgreen
857294332Sdes	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
858294332Sdes	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
859294332Sdes	    (r = decode_attrib(msg, &a)) != 0)
860294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
86176259Sgreen
862240075Sdes	debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename,
863294332Sdes	    (unsigned long)a.size);
86476259Sgreen
865255767Sdes	free(longname);
86676259Sgreen
867294332Sdes	sshbuf_free(msg);
86876259Sgreen
86976259Sgreen	return(filename);
87076259Sgreen}
87176259Sgreen
87276259Sgreenint
873294332Sdesdo_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
874261320Sdes    int force_legacy)
87576259Sgreen{
876294332Sdes	struct sshbuf *msg;
87776259Sgreen	u_int status, id;
878294332Sdes	int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy;
87976259Sgreen
880294332Sdes	if ((msg = sshbuf_new()) == NULL)
881294332Sdes		fatal("%s: sshbuf_new failed", __func__);
88276259Sgreen
88376259Sgreen	/* Send rename request */
88492555Sdes	id = conn->msg_id++;
885261320Sdes	if (use_ext) {
886294332Sdes		if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
887294332Sdes		    (r = sshbuf_put_u32(msg, id)) != 0 ||
888294332Sdes		    (r = sshbuf_put_cstring(msg,
889294332Sdes		    "posix-rename@openssh.com")) != 0)
890294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
891181111Sdes	} else {
892294332Sdes		if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 ||
893294332Sdes		    (r = sshbuf_put_u32(msg, id)) != 0)
894294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
895181111Sdes	}
896294332Sdes	if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
897294332Sdes	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
898294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
899294332Sdes	send_msg(conn, msg);
900181111Sdes	debug3("Sent message %s \"%s\" -> \"%s\"",
901294332Sdes	    use_ext ? "posix-rename@openssh.com" :
902294332Sdes	    "SSH2_FXP_RENAME", oldpath, newpath);
903294332Sdes	sshbuf_free(msg);
90476259Sgreen
905221420Sdes	status = get_status(conn, id);
90676259Sgreen	if (status != SSH2_FX_OK)
90792555Sdes		error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath,
90892555Sdes		    newpath, fx2txt(status));
90976259Sgreen
910294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
91176259Sgreen}
91276259Sgreen
91376259Sgreenint
914294332Sdesdo_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
915221420Sdes{
916294332Sdes	struct sshbuf *msg;
917221420Sdes	u_int status, id;
918294332Sdes	int r;
919221420Sdes
920221420Sdes	if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
921221420Sdes		error("Server does not support hardlink@openssh.com extension");
922221420Sdes		return -1;
923221420Sdes	}
924221420Sdes
925294332Sdes	if ((msg = sshbuf_new()) == NULL)
926294332Sdes		fatal("%s: sshbuf_new failed", __func__);
927240075Sdes
928240075Sdes	/* Send link request */
929240075Sdes	id = conn->msg_id++;
930294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
931294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
932294332Sdes	    (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
933294332Sdes	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
934294332Sdes	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
935294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
936294332Sdes	send_msg(conn, msg);
937221420Sdes	debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
938221420Sdes	       oldpath, newpath);
939294332Sdes	sshbuf_free(msg);
940221420Sdes
941221420Sdes	status = get_status(conn, id);
942221420Sdes	if (status != SSH2_FX_OK)
943221420Sdes		error("Couldn't link file \"%s\" to \"%s\": %s", oldpath,
944221420Sdes		    newpath, fx2txt(status));
945221420Sdes
946294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
947221420Sdes}
948221420Sdes
949221420Sdesint
950294332Sdesdo_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
95176259Sgreen{
952294332Sdes	struct sshbuf *msg;
95376259Sgreen	u_int status, id;
954294332Sdes	int r;
95576259Sgreen
95692555Sdes	if (conn->version < 3) {
95792555Sdes		error("This server does not support the symlink operation");
95892555Sdes		return(SSH2_FX_OP_UNSUPPORTED);
95992555Sdes	}
96092555Sdes
961294332Sdes	if ((msg = sshbuf_new()) == NULL)
962294332Sdes		fatal("%s: sshbuf_new failed", __func__);
96376259Sgreen
964137015Sdes	/* Send symlink request */
96592555Sdes	id = conn->msg_id++;
966294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 ||
967294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
968294332Sdes	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
969294332Sdes	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
970294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
971294332Sdes	send_msg(conn, msg);
97276259Sgreen	debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
97376259Sgreen	    newpath);
974294332Sdes	sshbuf_free(msg);
97576259Sgreen
976221420Sdes	status = get_status(conn, id);
97776259Sgreen	if (status != SSH2_FX_OK)
978113908Sdes		error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath,
97992555Sdes		    newpath, fx2txt(status));
98076259Sgreen
981294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
98276259Sgreen}
98376259Sgreen
984261320Sdesint
985294332Sdesdo_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
986261320Sdes{
987294332Sdes	struct sshbuf *msg;
988261320Sdes	u_int status, id;
989294332Sdes	int r;
990261320Sdes
991261320Sdes	/* Silently return if the extension is not supported */
992261320Sdes	if ((conn->exts & SFTP_EXT_FSYNC) == 0)
993261320Sdes		return -1;
994261320Sdes
995261320Sdes	/* Send fsync request */
996294332Sdes	if ((msg = sshbuf_new()) == NULL)
997294332Sdes		fatal("%s: sshbuf_new failed", __func__);
998261320Sdes	id = conn->msg_id++;
999294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1000294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1001294332Sdes	    (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
1002294332Sdes	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
1003294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1004294332Sdes	send_msg(conn, msg);
1005261320Sdes	debug3("Sent message fsync@openssh.com I:%u", id);
1006294332Sdes	sshbuf_free(msg);
1007261320Sdes
1008261320Sdes	status = get_status(conn, id);
1009261320Sdes	if (status != SSH2_FX_OK)
1010261320Sdes		error("Couldn't sync file: %s", fx2txt(status));
1011261320Sdes
1012261320Sdes	return status;
1013261320Sdes}
1014261320Sdes
1015181111Sdes#ifdef notyet
101676259Sgreenchar *
1017294332Sdesdo_readlink(struct sftp_conn *conn, const char *path)
101876259Sgreen{
1019294332Sdes	struct sshbuf *msg;
1020294332Sdes	u_int expected_id, count, id;
102176259Sgreen	char *filename, *longname;
1022294332Sdes	Attrib a;
1023294332Sdes	u_char type;
1024294332Sdes	int r;
102576259Sgreen
102692555Sdes	expected_id = id = conn->msg_id++;
1027221420Sdes	send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
102876259Sgreen
1029294332Sdes	if ((msg = sshbuf_new()) == NULL)
1030294332Sdes		fatal("%s: sshbuf_new failed", __func__);
103176259Sgreen
1032294332Sdes	get_msg(conn, msg);
1033294332Sdes	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1034294332Sdes	    (r = sshbuf_get_u32(msg, &id)) != 0)
1035294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
103676259Sgreen
103776259Sgreen	if (id != expected_id)
103899060Sdes		fatal("ID mismatch (%u != %u)", id, expected_id);
103976259Sgreen
104076259Sgreen	if (type == SSH2_FXP_STATUS) {
1041294332Sdes		u_int status;
104276259Sgreen
1043294332Sdes		if ((r = sshbuf_get_u32(msg, &status)) != 0)
1044294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
104576259Sgreen		error("Couldn't readlink: %s", fx2txt(status));
1046294332Sdes		sshbuf_free(msg);
104776259Sgreen		return(NULL);
104876259Sgreen	} else if (type != SSH2_FXP_NAME)
104999060Sdes		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
105076259Sgreen		    SSH2_FXP_NAME, type);
105176259Sgreen
1052294332Sdes	if ((r = sshbuf_get_u32(msg, &count)) != 0)
1053294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
105476259Sgreen	if (count != 1)
105576259Sgreen		fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
105676259Sgreen
1057294332Sdes	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1058294332Sdes	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1059294332Sdes	    (r = decode_attrib(msg, &a)) != 0)
1060294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
106176259Sgreen
106276259Sgreen	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
106376259Sgreen
1064255767Sdes	free(longname);
106576259Sgreen
1066294332Sdes	sshbuf_free(msg);
106776259Sgreen
1068294332Sdes	return filename;
106976259Sgreen}
1070181111Sdes#endif
107176259Sgreen
1072181111Sdesint
1073181111Sdesdo_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
1074181111Sdes    int quiet)
1075181111Sdes{
1076294332Sdes	struct sshbuf *msg;
1077181111Sdes	u_int id;
1078294332Sdes	int r;
1079181111Sdes
1080181111Sdes	if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
1081181111Sdes		error("Server does not support statvfs@openssh.com extension");
1082181111Sdes		return -1;
1083181111Sdes	}
1084181111Sdes
1085181111Sdes	id = conn->msg_id++;
1086181111Sdes
1087294332Sdes	if ((msg = sshbuf_new()) == NULL)
1088294332Sdes		fatal("%s: sshbuf_new failed", __func__);
1089294332Sdes	sshbuf_reset(msg);
1090294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1091294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1092294332Sdes	    (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
1093294332Sdes	    (r = sshbuf_put_cstring(msg, path)) != 0)
1094294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1095294332Sdes	send_msg(conn, msg);
1096294332Sdes	sshbuf_free(msg);
1097181111Sdes
1098221420Sdes	return get_decode_statvfs(conn, st, id, quiet);
1099181111Sdes}
1100181111Sdes
1101181111Sdes#ifdef notyet
1102181111Sdesint
1103294332Sdesdo_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
1104181111Sdes    struct sftp_statvfs *st, int quiet)
1105181111Sdes{
1106294332Sdes	struct sshbuf *msg;
1107181111Sdes	u_int id;
1108181111Sdes
1109181111Sdes	if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
1110181111Sdes		error("Server does not support fstatvfs@openssh.com extension");
1111181111Sdes		return -1;
1112181111Sdes	}
1113181111Sdes
1114181111Sdes	id = conn->msg_id++;
1115181111Sdes
1116294332Sdes	if ((msg = sshbuf_new()) == NULL)
1117294332Sdes		fatal("%s: sshbuf_new failed", __func__);
1118294332Sdes	sshbuf_reset(msg);
1119294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1120294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1121294332Sdes	    (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
1122294332Sdes	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
1123294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1124294332Sdes	send_msg(conn, msg);
1125294332Sdes	sshbuf_free(msg);
1126181111Sdes
1127221420Sdes	return get_decode_statvfs(conn, st, id, quiet);
1128181111Sdes}
1129181111Sdes#endif
1130181111Sdes
113192555Sdesstatic void
1132221420Sdessend_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
1133294332Sdes    u_int len, const u_char *handle, u_int handle_len)
113492555Sdes{
1135294332Sdes	struct sshbuf *msg;
1136294332Sdes	int r;
113798675Sdes
1138294332Sdes	if ((msg = sshbuf_new()) == NULL)
1139294332Sdes		fatal("%s: sshbuf_new failed", __func__);
1140294332Sdes	sshbuf_reset(msg);
1141294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 ||
1142294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1143294332Sdes	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0 ||
1144294332Sdes	    (r = sshbuf_put_u64(msg, offset)) != 0 ||
1145294332Sdes	    (r = sshbuf_put_u32(msg, len)) != 0)
1146294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1147294332Sdes	send_msg(conn, msg);
1148294332Sdes	sshbuf_free(msg);
114998675Sdes}
115092555Sdes
115176259Sgreenint
1152294332Sdesdo_download(struct sftp_conn *conn, const char *remote_path,
1153294332Sdes    const char *local_path, Attrib *a, int preserve_flag, int resume_flag,
1154294332Sdes    int fsync_flag)
115576259Sgreen{
1156204917Sdes	Attrib junk;
1157294332Sdes	struct sshbuf *msg;
1158294332Sdes	u_char *handle;
1159294332Sdes	int local_fd = -1, write_error;
1160294332Sdes	int read_error, write_errno, reordered = 0, r;
1161255767Sdes	u_int64_t offset = 0, size, highwater;
1162294332Sdes	u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK;
1163113908Sdes	off_t progress_counter;
1164294332Sdes	size_t handle_len;
1165255767Sdes	struct stat st;
116692555Sdes	struct request {
116792555Sdes		u_int id;
1168294332Sdes		size_t len;
116992555Sdes		u_int64_t offset;
117098675Sdes		TAILQ_ENTRY(request) tq;
117192555Sdes	};
117292555Sdes	TAILQ_HEAD(reqhead, request) requests;
117392555Sdes	struct request *req;
1174294332Sdes	u_char type;
117576259Sgreen
117692555Sdes	TAILQ_INIT(&requests);
117792555Sdes
1178204917Sdes	if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
1179204917Sdes		return -1;
118076259Sgreen
1181181111Sdes	/* Do not preserve set[ug]id here, as we do not preserve ownership */
118276259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1183113908Sdes		mode = a->perm & 0777;
118476259Sgreen	else
118576259Sgreen		mode = 0666;
118676259Sgreen
118776259Sgreen	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
1188113908Sdes	    (!S_ISREG(a->perm))) {
1189113908Sdes		error("Cannot download non-regular file: %s", remote_path);
119076259Sgreen		return(-1);
119176259Sgreen	}
119276259Sgreen
119392555Sdes	if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
119492555Sdes		size = a->size;
119592555Sdes	else
119692555Sdes		size = 0;
119776259Sgreen
119892555Sdes	buflen = conn->transfer_buflen;
1199294332Sdes	if ((msg = sshbuf_new()) == NULL)
1200294332Sdes		fatal("%s: sshbuf_new failed", __func__);
120176259Sgreen
1202294332Sdes	attrib_clear(&junk); /* Send empty attributes */
1203294332Sdes
120476259Sgreen	/* Send open request */
120592555Sdes	id = conn->msg_id++;
1206294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1207294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1208294332Sdes	    (r = sshbuf_put_cstring(msg, remote_path)) != 0 ||
1209294332Sdes	    (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 ||
1210294332Sdes	    (r = encode_attrib(msg, &junk)) != 0)
1211294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1212294332Sdes	send_msg(conn, msg);
121399060Sdes	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
121476259Sgreen
1215221420Sdes	handle = get_handle(conn, id, &handle_len,
1216204917Sdes	    "remote open(\"%s\")", remote_path);
121776259Sgreen	if (handle == NULL) {
1218294332Sdes		sshbuf_free(msg);
121976259Sgreen		return(-1);
122076259Sgreen	}
122176259Sgreen
1222261320Sdes	local_fd = open(local_path,
1223261320Sdes	    O_WRONLY | O_CREAT | (resume_flag ? 0 : O_TRUNC), mode | S_IWUSR);
122492555Sdes	if (local_fd == -1) {
122592555Sdes		error("Couldn't open local file \"%s\" for writing: %s",
122692555Sdes		    local_path, strerror(errno));
1227255767Sdes		goto fail;
122892555Sdes	}
1229255767Sdes	offset = highwater = 0;
1230261320Sdes	if (resume_flag) {
1231255767Sdes		if (fstat(local_fd, &st) == -1) {
1232255767Sdes			error("Unable to stat local file \"%s\": %s",
1233255767Sdes			    local_path, strerror(errno));
1234255767Sdes			goto fail;
1235255767Sdes		}
1236261320Sdes		if (st.st_size < 0) {
1237261320Sdes			error("\"%s\" has negative size", local_path);
1238261320Sdes			goto fail;
1239261320Sdes		}
1240261320Sdes		if ((u_int64_t)st.st_size > size) {
1241255767Sdes			error("Unable to resume download of \"%s\": "
1242255767Sdes			    "local file is larger than remote", local_path);
1243255767Sdes fail:
1244255767Sdes			do_close(conn, handle, handle_len);
1245294332Sdes			sshbuf_free(msg);
1246255767Sdes			free(handle);
1247261320Sdes			if (local_fd != -1)
1248261320Sdes				close(local_fd);
1249255767Sdes			return -1;
1250255767Sdes		}
1251255767Sdes		offset = highwater = st.st_size;
1252255767Sdes	}
125392555Sdes
125476259Sgreen	/* Read from remote and write to local */
1255255767Sdes	write_error = read_error = write_errno = num_req = 0;
125692555Sdes	max_req = 1;
1257255767Sdes	progress_counter = offset;
1258113908Sdes
1259128456Sdes	if (showprogress && size != 0)
1260128456Sdes		start_progress_meter(remote_path, size, &progress_counter);
1261113908Sdes
126292555Sdes	while (num_req > 0 || max_req > 0) {
1263294332Sdes		u_char *data;
1264294332Sdes		size_t len;
126576259Sgreen
1266137015Sdes		/*
1267137015Sdes		 * Simulate EOF on interrupt: stop sending new requests and
1268137015Sdes		 * allow outstanding requests to drain gracefully
1269137015Sdes		 */
1270137015Sdes		if (interrupted) {
1271137015Sdes			if (num_req == 0) /* If we haven't started yet... */
1272137015Sdes				break;
1273137015Sdes			max_req = 0;
1274137015Sdes		}
1275137015Sdes
127692555Sdes		/* Send some more requests */
127792555Sdes		while (num_req < max_req) {
127898675Sdes			debug3("Request range %llu -> %llu (%d/%d)",
127998675Sdes			    (unsigned long long)offset,
128098675Sdes			    (unsigned long long)offset + buflen - 1,
128198675Sdes			    num_req, max_req);
1282257954Sdelphij			req = xcalloc(1, sizeof(*req));
128392555Sdes			req->id = conn->msg_id++;
128492555Sdes			req->len = buflen;
128592555Sdes			req->offset = offset;
128692555Sdes			offset += buflen;
128792555Sdes			num_req++;
128892555Sdes			TAILQ_INSERT_TAIL(&requests, req, tq);
1289221420Sdes			send_read_request(conn, req->id, req->offset,
129092555Sdes			    req->len, handle, handle_len);
129192555Sdes		}
129276259Sgreen
1293294332Sdes		sshbuf_reset(msg);
1294294332Sdes		get_msg(conn, msg);
1295294332Sdes		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1296294332Sdes		    (r = sshbuf_get_u32(msg, &id)) != 0)
1297294332Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
129899060Sdes		debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
129976259Sgreen
130092555Sdes		/* Find the request in our queue */
1301147001Sdes		for (req = TAILQ_FIRST(&requests);
130292555Sdes		    req != NULL && req->id != id;
130392555Sdes		    req = TAILQ_NEXT(req, tq))
130492555Sdes			;
130592555Sdes		if (req == NULL)
130692555Sdes			fatal("Unexpected reply %u", id);
130776259Sgreen
130892555Sdes		switch (type) {
130992555Sdes		case SSH2_FXP_STATUS:
1310294332Sdes			if ((r = sshbuf_get_u32(msg, &status)) != 0)
1311294332Sdes				fatal("%s: buffer error: %s",
1312294332Sdes				    __func__, ssh_err(r));
131392555Sdes			if (status != SSH2_FX_EOF)
131492555Sdes				read_error = 1;
131592555Sdes			max_req = 0;
131692555Sdes			TAILQ_REMOVE(&requests, req, tq);
1317255767Sdes			free(req);
131892555Sdes			num_req--;
131992555Sdes			break;
132092555Sdes		case SSH2_FXP_DATA:
1321294332Sdes			if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
1322294332Sdes				fatal("%s: buffer error: %s",
1323294332Sdes				    __func__, ssh_err(r));
132498675Sdes			debug3("Received data %llu -> %llu",
132598675Sdes			    (unsigned long long)req->offset,
132698675Sdes			    (unsigned long long)req->offset + len - 1);
132792555Sdes			if (len > req->len)
132892555Sdes				fatal("Received more data than asked for "
1329294332Sdes				    "%zu > %zu", len, req->len);
133092555Sdes			if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
1331124208Sdes			    atomicio(vwrite, local_fd, data, len) != len) &&
133292555Sdes			    !write_error) {
133392555Sdes				write_errno = errno;
133492555Sdes				write_error = 1;
133592555Sdes				max_req = 0;
133692555Sdes			}
1337255767Sdes			else if (!reordered && req->offset <= highwater)
1338255767Sdes				highwater = req->offset + len;
1339255767Sdes			else if (!reordered && req->offset > highwater)
1340255767Sdes				reordered = 1;
1341113908Sdes			progress_counter += len;
1342255767Sdes			free(data);
134376259Sgreen
134492555Sdes			if (len == req->len) {
134592555Sdes				TAILQ_REMOVE(&requests, req, tq);
1346255767Sdes				free(req);
134792555Sdes				num_req--;
134892555Sdes			} else {
134992555Sdes				/* Resend the request for the missing data */
135092555Sdes				debug3("Short data block, re-requesting "
135198675Sdes				    "%llu -> %llu (%2d)",
135298675Sdes				    (unsigned long long)req->offset + len,
135398675Sdes				    (unsigned long long)req->offset +
135498675Sdes				    req->len - 1, num_req);
135592555Sdes				req->id = conn->msg_id++;
135692555Sdes				req->len -= len;
135792555Sdes				req->offset += len;
1358221420Sdes				send_read_request(conn, req->id,
135992555Sdes				    req->offset, req->len, handle, handle_len);
136092555Sdes				/* Reduce the request size */
136192555Sdes				if (len < buflen)
1362323134Sdes					buflen = MAXIMUM(MIN_READ_SIZE, len);
136376259Sgreen			}
136492555Sdes			if (max_req > 0) { /* max_req = 0 iff EOF received */
136592555Sdes				if (size > 0 && offset > size) {
136692555Sdes					/* Only one request at a time
136792555Sdes					 * after the expected EOF */
136892555Sdes					debug3("Finish at %llu (%2d)",
136998675Sdes					    (unsigned long long)offset,
137098675Sdes					    num_req);
137192555Sdes					max_req = 1;
1372137015Sdes				} else if (max_req <= conn->num_requests) {
137392555Sdes					++max_req;
137492555Sdes				}
137592555Sdes			}
137692555Sdes			break;
137792555Sdes		default:
137899060Sdes			fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
137976259Sgreen			    SSH2_FXP_DATA, type);
138076259Sgreen		}
138192555Sdes	}
138276259Sgreen
1383113908Sdes	if (showprogress && size)
1384113908Sdes		stop_progress_meter();
1385113908Sdes
138692555Sdes	/* Sanity check */
138792555Sdes	if (TAILQ_FIRST(&requests) != NULL)
138892555Sdes		fatal("Transfer complete, but requests still in queue");
1389255767Sdes	/* Truncate at highest contiguous point to avoid holes on interrupt */
1390255767Sdes	if (read_error || write_error || interrupted) {
1391261320Sdes		if (reordered && resume_flag) {
1392255767Sdes			error("Unable to resume download of \"%s\": "
1393255767Sdes			    "server reordered requests", local_path);
1394255767Sdes		}
1395255767Sdes		debug("truncating at %llu", (unsigned long long)highwater);
1396294336Sdes		if (ftruncate(local_fd, highwater) == -1)
1397294336Sdes			error("ftruncate \"%s\": %s", local_path,
1398294336Sdes			    strerror(errno));
1399255767Sdes	}
140092555Sdes	if (read_error) {
140198675Sdes		error("Couldn't read from remote file \"%s\" : %s",
140292555Sdes		    remote_path, fx2txt(status));
1403261320Sdes		status = -1;
140492555Sdes		do_close(conn, handle, handle_len);
140592555Sdes	} else if (write_error) {
140692555Sdes		error("Couldn't write to \"%s\": %s", local_path,
140792555Sdes		    strerror(write_errno));
1408294332Sdes		status = SSH2_FX_FAILURE;
140992555Sdes		do_close(conn, handle, handle_len);
141092555Sdes	} else {
1411294332Sdes		if (do_close(conn, handle, handle_len) != 0 || interrupted)
1412294332Sdes			status = SSH2_FX_FAILURE;
1413294332Sdes		else
1414294332Sdes			status = SSH2_FX_OK;
141592555Sdes		/* Override umask and utimes if asked */
141698937Sdes#ifdef HAVE_FCHMOD
1417261320Sdes		if (preserve_flag && fchmod(local_fd, mode) == -1)
1418126274Sdes#else
1419261320Sdes		if (preserve_flag && chmod(local_path, mode) == -1)
142098937Sdes#endif /* HAVE_FCHMOD */
142192555Sdes			error("Couldn't set mode on \"%s\": %s", local_path,
1422113908Sdes			    strerror(errno));
1423261320Sdes		if (preserve_flag &&
1424261320Sdes		    (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
142592555Sdes			struct timeval tv[2];
142692555Sdes			tv[0].tv_sec = a->atime;
142792555Sdes			tv[1].tv_sec = a->mtime;
142892555Sdes			tv[0].tv_usec = tv[1].tv_usec = 0;
142992555Sdes			if (utimes(local_path, tv) == -1)
143092555Sdes				error("Can't set times on \"%s\": %s",
1431113908Sdes				    local_path, strerror(errno));
143276259Sgreen		}
1433261320Sdes		if (fsync_flag) {
1434261320Sdes			debug("syncing \"%s\"", local_path);
1435261320Sdes			if (fsync(local_fd) == -1)
1436261320Sdes				error("Couldn't sync file \"%s\": %s",
1437261320Sdes				    local_path, strerror(errno));
1438261320Sdes		}
143976259Sgreen	}
144076259Sgreen	close(local_fd);
1441294332Sdes	sshbuf_free(msg);
1442255767Sdes	free(handle);
144392555Sdes
144492555Sdes	return(status);
144576259Sgreen}
144676259Sgreen
1447204917Sdesstatic int
1448294332Sdesdownload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1449294332Sdes    int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
1450294332Sdes    int resume_flag, int fsync_flag)
1451204917Sdes{
1452204917Sdes	int i, ret = 0;
1453204917Sdes	SFTP_DIRENT **dir_entries;
1454204917Sdes	char *filename, *new_src, *new_dst;
1455204917Sdes	mode_t mode = 0777;
1456204917Sdes
1457204917Sdes	if (depth >= MAX_DIR_DEPTH) {
1458204917Sdes		error("Maximum directory depth exceeded: %d levels", depth);
1459204917Sdes		return -1;
1460204917Sdes	}
1461204917Sdes
1462204917Sdes	if (dirattrib == NULL &&
1463204917Sdes	    (dirattrib = do_stat(conn, src, 1)) == NULL) {
1464204917Sdes		error("Unable to stat remote directory \"%s\"", src);
1465204917Sdes		return -1;
1466204917Sdes	}
1467204917Sdes	if (!S_ISDIR(dirattrib->perm)) {
1468204917Sdes		error("\"%s\" is not a directory", src);
1469204917Sdes		return -1;
1470204917Sdes	}
1471261320Sdes	if (print_flag)
1472323129Sdes		mprintf("Retrieving %s\n", src);
1473204917Sdes
1474204917Sdes	if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1475204917Sdes		mode = dirattrib->perm & 01777;
1476204917Sdes	else {
1477204917Sdes		debug("Server did not send permissions for "
1478204917Sdes		    "directory \"%s\"", dst);
1479204917Sdes	}
1480204917Sdes
1481204917Sdes	if (mkdir(dst, mode) == -1 && errno != EEXIST) {
1482204917Sdes		error("mkdir %s: %s", dst, strerror(errno));
1483204917Sdes		return -1;
1484204917Sdes	}
1485204917Sdes
1486204917Sdes	if (do_readdir(conn, src, &dir_entries) == -1) {
1487204917Sdes		error("%s: Failed to get directory contents", src);
1488204917Sdes		return -1;
1489204917Sdes	}
1490204917Sdes
1491204917Sdes	for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
1492204917Sdes		filename = dir_entries[i]->filename;
1493204917Sdes
1494204917Sdes		new_dst = path_append(dst, filename);
1495204917Sdes		new_src = path_append(src, filename);
1496204917Sdes
1497204917Sdes		if (S_ISDIR(dir_entries[i]->a.perm)) {
1498204917Sdes			if (strcmp(filename, ".") == 0 ||
1499204917Sdes			    strcmp(filename, "..") == 0)
1500204917Sdes				continue;
1501204917Sdes			if (download_dir_internal(conn, new_src, new_dst,
1502261320Sdes			    depth + 1, &(dir_entries[i]->a), preserve_flag,
1503261320Sdes			    print_flag, resume_flag, fsync_flag) == -1)
1504204917Sdes				ret = -1;
1505204917Sdes		} else if (S_ISREG(dir_entries[i]->a.perm) ) {
1506204917Sdes			if (do_download(conn, new_src, new_dst,
1507261320Sdes			    &(dir_entries[i]->a), preserve_flag,
1508261320Sdes			    resume_flag, fsync_flag) == -1) {
1509204917Sdes				error("Download of file %s to %s failed",
1510204917Sdes				    new_src, new_dst);
1511204917Sdes				ret = -1;
1512204917Sdes			}
1513204917Sdes		} else
1514204917Sdes			logit("%s: not a regular file\n", new_src);
1515204917Sdes
1516255767Sdes		free(new_dst);
1517255767Sdes		free(new_src);
1518204917Sdes	}
1519204917Sdes
1520261320Sdes	if (preserve_flag) {
1521204917Sdes		if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1522204917Sdes			struct timeval tv[2];
1523204917Sdes			tv[0].tv_sec = dirattrib->atime;
1524204917Sdes			tv[1].tv_sec = dirattrib->mtime;
1525204917Sdes			tv[0].tv_usec = tv[1].tv_usec = 0;
1526204917Sdes			if (utimes(dst, tv) == -1)
1527204917Sdes				error("Can't set times on \"%s\": %s",
1528204917Sdes				    dst, strerror(errno));
1529204917Sdes		} else
1530204917Sdes			debug("Server did not send times for directory "
1531204917Sdes			    "\"%s\"", dst);
1532204917Sdes	}
1533204917Sdes
1534204917Sdes	free_sftp_dirents(dir_entries);
1535204917Sdes
1536204917Sdes	return ret;
1537204917Sdes}
1538204917Sdes
153976259Sgreenint
1540294332Sdesdownload_dir(struct sftp_conn *conn, const char *src, const char *dst,
1541294332Sdes    Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag,
1542294332Sdes    int fsync_flag)
1543204917Sdes{
1544204917Sdes	char *src_canon;
1545204917Sdes	int ret;
1546204917Sdes
1547204917Sdes	if ((src_canon = do_realpath(conn, src)) == NULL) {
1548261320Sdes		error("Unable to canonicalize path \"%s\"", src);
1549204917Sdes		return -1;
1550204917Sdes	}
1551204917Sdes
1552261320Sdes	ret = download_dir_internal(conn, src_canon, dst, 0,
1553261320Sdes	    dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag);
1554255767Sdes	free(src_canon);
1555204917Sdes	return ret;
1556204917Sdes}
1557204917Sdes
1558204917Sdesint
1559294332Sdesdo_upload(struct sftp_conn *conn, const char *local_path,
1560294332Sdes    const char *remote_path, int preserve_flag, int resume, int fsync_flag)
156176259Sgreen{
1562294332Sdes	int r, local_fd;
1563294332Sdes	u_int status = SSH2_FX_OK;
1564294332Sdes	u_int id;
1565294332Sdes	u_char type;
1566255767Sdes	off_t offset, progress_counter;
1567294332Sdes	u_char *handle, *data;
1568294332Sdes	struct sshbuf *msg;
156976259Sgreen	struct stat sb;
1570294328Sdes	Attrib a, *c = NULL;
157192555Sdes	u_int32_t startid;
157292555Sdes	u_int32_t ackid;
157392555Sdes	struct outstanding_ack {
157492555Sdes		u_int id;
157592555Sdes		u_int len;
1576181111Sdes		off_t offset;
157798675Sdes		TAILQ_ENTRY(outstanding_ack) tq;
157892555Sdes	};
157992555Sdes	TAILQ_HEAD(ackhead, outstanding_ack) acks;
1580137015Sdes	struct outstanding_ack *ack = NULL;
1581294332Sdes	size_t handle_len;
158276259Sgreen
158392555Sdes	TAILQ_INIT(&acks);
158492555Sdes
158576259Sgreen	if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
158676259Sgreen		error("Couldn't open local file \"%s\" for reading: %s",
158776259Sgreen		    local_path, strerror(errno));
158876259Sgreen		return(-1);
158976259Sgreen	}
159076259Sgreen	if (fstat(local_fd, &sb) == -1) {
159176259Sgreen		error("Couldn't fstat local file \"%s\": %s",
159276259Sgreen		    local_path, strerror(errno));
159376259Sgreen		close(local_fd);
159476259Sgreen		return(-1);
159576259Sgreen	}
1596113908Sdes	if (!S_ISREG(sb.st_mode)) {
1597113908Sdes		error("%s is not a regular file", local_path);
1598113908Sdes		close(local_fd);
1599113908Sdes		return(-1);
1600113908Sdes	}
160176259Sgreen	stat_to_attrib(&sb, &a);
160276259Sgreen
160376259Sgreen	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
160476259Sgreen	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
160576259Sgreen	a.perm &= 0777;
1606261320Sdes	if (!preserve_flag)
160776259Sgreen		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
160876259Sgreen
1609294328Sdes	if (resume) {
1610294328Sdes		/* Get remote file size if it exists */
1611294328Sdes		if ((c = do_stat(conn, remote_path, 0)) == NULL) {
1612323129Sdes			close(local_fd);
1613294328Sdes			return -1;
1614294328Sdes		}
1615294328Sdes
1616294328Sdes		if ((off_t)c->size >= sb.st_size) {
1617294328Sdes			error("destination file bigger or same size as "
1618294328Sdes			      "source file");
1619294328Sdes			close(local_fd);
1620294328Sdes			return -1;
1621294328Sdes		}
1622294328Sdes
1623294328Sdes		if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) {
1624294328Sdes			close(local_fd);
1625294328Sdes			return -1;
1626294328Sdes		}
1627294328Sdes	}
1628294328Sdes
1629294332Sdes	if ((msg = sshbuf_new()) == NULL)
1630294332Sdes		fatal("%s: sshbuf_new failed", __func__);
163176259Sgreen
163276259Sgreen	/* Send open request */
163392555Sdes	id = conn->msg_id++;
1634294332Sdes	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1635294332Sdes	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1636294332Sdes	    (r = sshbuf_put_cstring(msg, remote_path)) != 0 ||
1637294332Sdes	    (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|
1638294332Sdes	    (resume ? SSH2_FXF_APPEND : SSH2_FXF_TRUNC))) != 0 ||
1639294332Sdes	    (r = encode_attrib(msg, &a)) != 0)
1640294332Sdes		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1641294332Sdes	send_msg(conn, msg);
164299060Sdes	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
164376259Sgreen
1644294332Sdes	sshbuf_reset(msg);
164576259Sgreen
1646221420Sdes	handle = get_handle(conn, id, &handle_len,
1647204917Sdes	    "remote open(\"%s\")", remote_path);
164876259Sgreen	if (handle == NULL) {
164976259Sgreen		close(local_fd);
1650294332Sdes		sshbuf_free(msg);
1651181111Sdes		return -1;
165276259Sgreen	}
165376259Sgreen
165492555Sdes	startid = ackid = id + 1;
165592555Sdes	data = xmalloc(conn->transfer_buflen);
165692555Sdes
165776259Sgreen	/* Read from local and write to remote */
1658294328Sdes	offset = progress_counter = (resume ? c->size : 0);
1659113908Sdes	if (showprogress)
1660255767Sdes		start_progress_meter(local_path, sb.st_size,
1661255767Sdes		    &progress_counter);
1662113908Sdes
166392555Sdes	for (;;) {
166476259Sgreen		int len;
166576259Sgreen
166676259Sgreen		/*
1667137015Sdes		 * Can't use atomicio here because it returns 0 on EOF,
1668137015Sdes		 * thus losing the last block of the file.
1669137015Sdes		 * Simulate an EOF on interrupt, allowing ACKs from the
1670137015Sdes		 * server to drain.
167176259Sgreen		 */
1672181111Sdes		if (interrupted || status != SSH2_FX_OK)
1673137015Sdes			len = 0;
1674137015Sdes		else do
167592555Sdes			len = read(local_fd, data, conn->transfer_buflen);
1676181111Sdes		while ((len == -1) &&
1677181111Sdes		    (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
167876259Sgreen
167976259Sgreen		if (len == -1)
168076259Sgreen			fatal("Couldn't read from \"%s\": %s", local_path,
168176259Sgreen			    strerror(errno));
168292555Sdes
168392555Sdes		if (len != 0) {
1684257954Sdelphij			ack = xcalloc(1, sizeof(*ack));
168592555Sdes			ack->id = ++id;
168692555Sdes			ack->offset = offset;
168792555Sdes			ack->len = len;
168892555Sdes			TAILQ_INSERT_TAIL(&acks, ack, tq);
168992555Sdes
1690294332Sdes			sshbuf_reset(msg);
1691294332Sdes			if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
1692294332Sdes			    (r = sshbuf_put_u32(msg, ack->id)) != 0 ||
1693294332Sdes			    (r = sshbuf_put_string(msg, handle,
1694294332Sdes			    handle_len)) != 0 ||
1695294332Sdes			    (r = sshbuf_put_u64(msg, offset)) != 0 ||
1696294332Sdes			    (r = sshbuf_put_string(msg, data, len)) != 0)
1697294332Sdes				fatal("%s: buffer error: %s",
1698294332Sdes				    __func__, ssh_err(r));
1699294332Sdes			send_msg(conn, msg);
170099060Sdes			debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
1701113908Sdes			    id, (unsigned long long)offset, len);
170292555Sdes		} else if (TAILQ_FIRST(&acks) == NULL)
170376259Sgreen			break;
170476259Sgreen
170592555Sdes		if (ack == NULL)
170692555Sdes			fatal("Unexpected ACK %u", id);
170776259Sgreen
170898675Sdes		if (id == startid || len == 0 ||
170992555Sdes		    id - ackid >= conn->num_requests) {
1710294332Sdes			u_int rid;
171198675Sdes
1712294332Sdes			sshbuf_reset(msg);
1713294332Sdes			get_msg(conn, msg);
1714294332Sdes			if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1715294332Sdes			    (r = sshbuf_get_u32(msg, &rid)) != 0)
1716294332Sdes				fatal("%s: buffer error: %s",
1717294332Sdes				    __func__, ssh_err(r));
171892555Sdes
171992555Sdes			if (type != SSH2_FXP_STATUS)
172092555Sdes				fatal("Expected SSH2_FXP_STATUS(%d) packet, "
172192555Sdes				    "got %d", SSH2_FXP_STATUS, type);
172292555Sdes
1723294332Sdes			if ((r = sshbuf_get_u32(msg, &status)) != 0)
1724294332Sdes				fatal("%s: buffer error: %s",
1725294332Sdes				    __func__, ssh_err(r));
1726294332Sdes			debug3("SSH2_FXP_STATUS %u", status);
172792555Sdes
172892555Sdes			/* Find the request in our queue */
1729147001Sdes			for (ack = TAILQ_FIRST(&acks);
1730294332Sdes			    ack != NULL && ack->id != rid;
173192555Sdes			    ack = TAILQ_NEXT(ack, tq))
173292555Sdes				;
173392555Sdes			if (ack == NULL)
1734294332Sdes				fatal("Can't find request for ID %u", rid);
173592555Sdes			TAILQ_REMOVE(&acks, ack, tq);
1736181111Sdes			debug3("In write loop, ack for %u %u bytes at %lld",
1737181111Sdes			    ack->id, ack->len, (long long)ack->offset);
173892555Sdes			++ackid;
1739255767Sdes			progress_counter += ack->len;
1740255767Sdes			free(ack);
174176259Sgreen		}
174276259Sgreen		offset += len;
1743181111Sdes		if (offset < 0)
1744181111Sdes			fatal("%s: offset < 0", __func__);
174576259Sgreen	}
1746294332Sdes	sshbuf_free(msg);
1747181111Sdes
1748113908Sdes	if (showprogress)
1749113908Sdes		stop_progress_meter();
1750255767Sdes	free(data);
175176259Sgreen
1752181111Sdes	if (status != SSH2_FX_OK) {
1753181111Sdes		error("Couldn't write to remote file \"%s\": %s",
1754181111Sdes		    remote_path, fx2txt(status));
1755294332Sdes		status = SSH2_FX_FAILURE;
1756181111Sdes	}
1757181111Sdes
175876259Sgreen	if (close(local_fd) == -1) {
175976259Sgreen		error("Couldn't close local file \"%s\": %s", local_path,
176076259Sgreen		    strerror(errno));
1761294332Sdes		status = SSH2_FX_FAILURE;
176276259Sgreen	}
176376259Sgreen
176476259Sgreen	/* Override umask and utimes if asked */
1765261320Sdes	if (preserve_flag)
176692555Sdes		do_fsetstat(conn, handle, handle_len, &a);
176776259Sgreen
1768261320Sdes	if (fsync_flag)
1769261320Sdes		(void)do_fsync(conn, handle, handle_len);
1770261320Sdes
1771296633Sdes	if (do_close(conn, handle, handle_len) != 0)
1772294332Sdes		status = SSH2_FX_FAILURE;
1773294332Sdes
1774255767Sdes	free(handle);
177576259Sgreen
1776294332Sdes	return status == SSH2_FX_OK ? 0 : -1;
177776259Sgreen}
1778204917Sdes
1779204917Sdesstatic int
1780294332Sdesupload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1781294332Sdes    int depth, int preserve_flag, int print_flag, int resume, int fsync_flag)
1782204917Sdes{
1783294332Sdes	int ret = 0;
1784204917Sdes	DIR *dirp;
1785204917Sdes	struct dirent *dp;
1786204917Sdes	char *filename, *new_src, *new_dst;
1787204917Sdes	struct stat sb;
1788296633Sdes	Attrib a, *dirattrib;
1789204917Sdes
1790204917Sdes	if (depth >= MAX_DIR_DEPTH) {
1791204917Sdes		error("Maximum directory depth exceeded: %d levels", depth);
1792204917Sdes		return -1;
1793204917Sdes	}
1794204917Sdes
1795204917Sdes	if (stat(src, &sb) == -1) {
1796204917Sdes		error("Couldn't stat directory \"%s\": %s",
1797204917Sdes		    src, strerror(errno));
1798204917Sdes		return -1;
1799204917Sdes	}
1800204917Sdes	if (!S_ISDIR(sb.st_mode)) {
1801204917Sdes		error("\"%s\" is not a directory", src);
1802204917Sdes		return -1;
1803204917Sdes	}
1804261320Sdes	if (print_flag)
1805323129Sdes		mprintf("Entering %s\n", src);
1806204917Sdes
1807204917Sdes	attrib_clear(&a);
1808204917Sdes	stat_to_attrib(&sb, &a);
1809204917Sdes	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
1810204917Sdes	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
1811204917Sdes	a.perm &= 01777;
1812261320Sdes	if (!preserve_flag)
1813204917Sdes		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
1814255767Sdes
1815204917Sdes	/*
1816296633Sdes	 * sftp lacks a portable status value to match errno EEXIST,
1817296633Sdes	 * so if we get a failure back then we must check whether
1818296633Sdes	 * the path already existed and is a directory.
1819204917Sdes	 */
1820296633Sdes	if (do_mkdir(conn, dst, &a, 0) != 0) {
1821296633Sdes		if ((dirattrib = do_stat(conn, dst, 0)) == NULL)
1822204917Sdes			return -1;
1823296633Sdes		if (!S_ISDIR(dirattrib->perm)) {
1824296633Sdes			error("\"%s\" exists but is not a directory", dst);
1825204917Sdes			return -1;
1826296633Sdes		}
1827204917Sdes	}
1828204917Sdes
1829204917Sdes	if ((dirp = opendir(src)) == NULL) {
1830204917Sdes		error("Failed to open dir \"%s\": %s", src, strerror(errno));
1831204917Sdes		return -1;
1832204917Sdes	}
1833255767Sdes
1834204917Sdes	while (((dp = readdir(dirp)) != NULL) && !interrupted) {
1835204917Sdes		if (dp->d_ino == 0)
1836204917Sdes			continue;
1837204917Sdes		filename = dp->d_name;
1838204917Sdes		new_dst = path_append(dst, filename);
1839204917Sdes		new_src = path_append(src, filename);
1840204917Sdes
1841204917Sdes		if (lstat(new_src, &sb) == -1) {
1842204917Sdes			logit("%s: lstat failed: %s", filename,
1843204917Sdes			    strerror(errno));
1844204917Sdes			ret = -1;
1845204917Sdes		} else if (S_ISDIR(sb.st_mode)) {
1846204917Sdes			if (strcmp(filename, ".") == 0 ||
1847204917Sdes			    strcmp(filename, "..") == 0)
1848204917Sdes				continue;
1849204917Sdes
1850204917Sdes			if (upload_dir_internal(conn, new_src, new_dst,
1851294328Sdes			    depth + 1, preserve_flag, print_flag, resume,
1852261320Sdes			    fsync_flag) == -1)
1853204917Sdes				ret = -1;
1854204917Sdes		} else if (S_ISREG(sb.st_mode)) {
1855261320Sdes			if (do_upload(conn, new_src, new_dst,
1856294328Sdes			    preserve_flag, resume, fsync_flag) == -1) {
1857204917Sdes				error("Uploading of file %s to %s failed!",
1858204917Sdes				    new_src, new_dst);
1859204917Sdes				ret = -1;
1860204917Sdes			}
1861204917Sdes		} else
1862204917Sdes			logit("%s: not a regular file\n", filename);
1863255767Sdes		free(new_dst);
1864255767Sdes		free(new_src);
1865204917Sdes	}
1866204917Sdes
1867204917Sdes	do_setstat(conn, dst, &a);
1868204917Sdes
1869204917Sdes	(void) closedir(dirp);
1870204917Sdes	return ret;
1871204917Sdes}
1872204917Sdes
1873204917Sdesint
1874294332Sdesupload_dir(struct sftp_conn *conn, const char *src, const char *dst,
1875294332Sdes    int preserve_flag, int print_flag, int resume, int fsync_flag)
1876204917Sdes{
1877204917Sdes	char *dst_canon;
1878204917Sdes	int ret;
1879204917Sdes
1880204917Sdes	if ((dst_canon = do_realpath(conn, dst)) == NULL) {
1881261320Sdes		error("Unable to canonicalize path \"%s\"", dst);
1882204917Sdes		return -1;
1883204917Sdes	}
1884204917Sdes
1885261320Sdes	ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag,
1886294328Sdes	    print_flag, resume, fsync_flag);
1887261320Sdes
1888255767Sdes	free(dst_canon);
1889204917Sdes	return ret;
1890204917Sdes}
1891204917Sdes
1892204917Sdeschar *
1893294332Sdespath_append(const char *p1, const char *p2)
1894204917Sdes{
1895204917Sdes	char *ret;
1896204917Sdes	size_t len = strlen(p1) + strlen(p2) + 2;
1897204917Sdes
1898204917Sdes	ret = xmalloc(len);
1899204917Sdes	strlcpy(ret, p1, len);
1900204917Sdes	if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
1901204917Sdes		strlcat(ret, "/", len);
1902204917Sdes	strlcat(ret, p2, len);
1903204917Sdes
1904204917Sdes	return(ret);
1905204917Sdes}
1906204917Sdes
1907