xref: /dragonfly/crypto/openssh/sftp-client.c (revision ee116499)
1*ee116499SAntonio Huete Jimenez /* $OpenBSD: sftp-client.c,v 1.165 2022/09/19 10:43:12 djm Exp $ */
218de8d7fSPeter Avalos /*
318de8d7fSPeter Avalos  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
418de8d7fSPeter Avalos  *
518de8d7fSPeter Avalos  * Permission to use, copy, modify, and distribute this software for any
618de8d7fSPeter Avalos  * purpose with or without fee is hereby granted, provided that the above
718de8d7fSPeter Avalos  * copyright notice and this permission notice appear in all copies.
818de8d7fSPeter Avalos  *
918de8d7fSPeter Avalos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1018de8d7fSPeter Avalos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1118de8d7fSPeter Avalos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1218de8d7fSPeter Avalos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1318de8d7fSPeter Avalos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1418de8d7fSPeter Avalos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1518de8d7fSPeter Avalos  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1618de8d7fSPeter Avalos  */
1718de8d7fSPeter Avalos 
1818de8d7fSPeter Avalos /* XXX: memleaks */
1918de8d7fSPeter Avalos /* XXX: signed vs unsigned */
2018de8d7fSPeter Avalos /* XXX: remove all logging, only return status codes */
2118de8d7fSPeter Avalos /* XXX: copy between two remote sites */
2218de8d7fSPeter Avalos 
2318de8d7fSPeter Avalos #include "includes.h"
2418de8d7fSPeter Avalos 
2518de8d7fSPeter Avalos #include <sys/types.h>
2618de8d7fSPeter Avalos #ifdef HAVE_SYS_STATVFS_H
2718de8d7fSPeter Avalos #include <sys/statvfs.h>
2818de8d7fSPeter Avalos #endif
2918de8d7fSPeter Avalos #include "openbsd-compat/sys-queue.h"
3018de8d7fSPeter Avalos #ifdef HAVE_SYS_STAT_H
3118de8d7fSPeter Avalos # include <sys/stat.h>
3218de8d7fSPeter Avalos #endif
3318de8d7fSPeter Avalos #ifdef HAVE_SYS_TIME_H
3418de8d7fSPeter Avalos # include <sys/time.h>
3518de8d7fSPeter Avalos #endif
3618de8d7fSPeter Avalos #include <sys/uio.h>
3718de8d7fSPeter Avalos 
38856ea928SPeter Avalos #include <dirent.h>
3918de8d7fSPeter Avalos #include <errno.h>
4050a69bb5SSascha Wildner #ifdef HAVE_POLL_H
4150a69bb5SSascha Wildner #include <poll.h>
4250a69bb5SSascha Wildner #else
4350a69bb5SSascha Wildner # ifdef HAVE_SYS_POLL_H
4450a69bb5SSascha Wildner #  include <sys/poll.h>
4550a69bb5SSascha Wildner # endif
4650a69bb5SSascha Wildner #endif
4718de8d7fSPeter Avalos #include <fcntl.h>
4818de8d7fSPeter Avalos #include <signal.h>
4918de8d7fSPeter Avalos #include <stdarg.h>
5018de8d7fSPeter Avalos #include <stdio.h>
5136e94dc5SPeter Avalos #include <stdlib.h>
5218de8d7fSPeter Avalos #include <string.h>
5318de8d7fSPeter Avalos #include <unistd.h>
5418de8d7fSPeter Avalos 
5518de8d7fSPeter Avalos #include "xmalloc.h"
56e9778795SPeter Avalos #include "ssherr.h"
57e9778795SPeter Avalos #include "sshbuf.h"
5818de8d7fSPeter Avalos #include "log.h"
5918de8d7fSPeter Avalos #include "atomicio.h"
6018de8d7fSPeter Avalos #include "progressmeter.h"
6118de8d7fSPeter Avalos #include "misc.h"
62e9778795SPeter Avalos #include "utf8.h"
6318de8d7fSPeter Avalos 
6418de8d7fSPeter Avalos #include "sftp.h"
6518de8d7fSPeter Avalos #include "sftp-common.h"
6618de8d7fSPeter Avalos #include "sftp-client.h"
6718de8d7fSPeter Avalos 
6818de8d7fSPeter Avalos extern volatile sig_atomic_t interrupted;
6918de8d7fSPeter Avalos extern int showprogress;
7018de8d7fSPeter Avalos 
7150a69bb5SSascha Wildner /* Default size of buffer for up/download */
7250a69bb5SSascha Wildner #define DEFAULT_COPY_BUFLEN	32768
7350a69bb5SSascha Wildner 
7450a69bb5SSascha Wildner /* Default number of concurrent outstanding requests */
7550a69bb5SSascha Wildner #define DEFAULT_NUM_REQUESTS	64
7650a69bb5SSascha Wildner 
7718de8d7fSPeter Avalos /* Minimum amount of data to read at a time */
7818de8d7fSPeter Avalos #define MIN_READ_SIZE	512
7918de8d7fSPeter Avalos 
80856ea928SPeter Avalos /* Maximum depth to descend in directory trees */
81856ea928SPeter Avalos #define MAX_DIR_DEPTH 64
82856ea928SPeter Avalos 
83ce74bacaSMatthew Dillon /* Directory separator characters */
84ce74bacaSMatthew Dillon #ifdef HAVE_CYGWIN
85ce74bacaSMatthew Dillon # define SFTP_DIRECTORY_CHARS      "/\\"
86ce74bacaSMatthew Dillon #else /* HAVE_CYGWIN */
87ce74bacaSMatthew Dillon # define SFTP_DIRECTORY_CHARS      "/"
88ce74bacaSMatthew Dillon #endif /* HAVE_CYGWIN */
89ce74bacaSMatthew Dillon 
9018de8d7fSPeter Avalos struct sftp_conn {
9118de8d7fSPeter Avalos 	int fd_in;
9218de8d7fSPeter Avalos 	int fd_out;
9350a69bb5SSascha Wildner 	u_int download_buflen;
9450a69bb5SSascha Wildner 	u_int upload_buflen;
9518de8d7fSPeter Avalos 	u_int num_requests;
9618de8d7fSPeter Avalos 	u_int version;
9718de8d7fSPeter Avalos 	u_int msg_id;
9818de8d7fSPeter Avalos #define SFTP_EXT_POSIX_RENAME		0x00000001
9918de8d7fSPeter Avalos #define SFTP_EXT_STATVFS		0x00000002
10018de8d7fSPeter Avalos #define SFTP_EXT_FSTATVFS		0x00000004
1019f304aafSPeter Avalos #define SFTP_EXT_HARDLINK		0x00000008
10236e94dc5SPeter Avalos #define SFTP_EXT_FSYNC			0x00000010
103664f4763Szrj #define SFTP_EXT_LSETSTAT		0x00000020
10450a69bb5SSascha Wildner #define SFTP_EXT_LIMITS			0x00000040
10550a69bb5SSascha Wildner #define SFTP_EXT_PATH_EXPAND		0x00000080
106*ee116499SAntonio Huete Jimenez #define SFTP_EXT_COPY_DATA		0x00000100
107*ee116499SAntonio Huete Jimenez #define SFTP_EXT_GETUSERSGROUPS_BY_ID	0x00000200
10818de8d7fSPeter Avalos 	u_int exts;
1099f304aafSPeter Avalos 	u_int64_t limit_kbps;
1109f304aafSPeter Avalos 	struct bwlimit bwlimit_in, bwlimit_out;
11118de8d7fSPeter Avalos };
11218de8d7fSPeter Avalos 
11350a69bb5SSascha Wildner /* Tracks in-progress requests during file transfers */
11450a69bb5SSascha Wildner struct request {
11550a69bb5SSascha Wildner 	u_int id;
11650a69bb5SSascha Wildner 	size_t len;
11750a69bb5SSascha Wildner 	u_int64_t offset;
11850a69bb5SSascha Wildner 	TAILQ_ENTRY(request) tq;
11950a69bb5SSascha Wildner };
12050a69bb5SSascha Wildner TAILQ_HEAD(requests, request);
12150a69bb5SSascha Wildner 
122e9778795SPeter Avalos static u_char *
123e9778795SPeter Avalos get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
1249f304aafSPeter Avalos     const char *errfmt, ...) __attribute__((format(printf, 4, 5)));
1259f304aafSPeter Avalos 
12650a69bb5SSascha Wildner static struct request *
request_enqueue(struct requests * requests,u_int id,size_t len,uint64_t offset)12750a69bb5SSascha Wildner request_enqueue(struct requests *requests, u_int id, size_t len,
12850a69bb5SSascha Wildner     uint64_t offset)
12950a69bb5SSascha Wildner {
13050a69bb5SSascha Wildner 	struct request *req;
13150a69bb5SSascha Wildner 
13250a69bb5SSascha Wildner 	req = xcalloc(1, sizeof(*req));
13350a69bb5SSascha Wildner 	req->id = id;
13450a69bb5SSascha Wildner 	req->len = len;
13550a69bb5SSascha Wildner 	req->offset = offset;
13650a69bb5SSascha Wildner 	TAILQ_INSERT_TAIL(requests, req, tq);
13750a69bb5SSascha Wildner 	return req;
13850a69bb5SSascha Wildner }
13950a69bb5SSascha Wildner 
14050a69bb5SSascha Wildner static struct request *
request_find(struct requests * requests,u_int id)14150a69bb5SSascha Wildner request_find(struct requests *requests, u_int id)
14250a69bb5SSascha Wildner {
14350a69bb5SSascha Wildner 	struct request *req;
14450a69bb5SSascha Wildner 
14550a69bb5SSascha Wildner 	for (req = TAILQ_FIRST(requests);
14650a69bb5SSascha Wildner 	    req != NULL && req->id != id;
14750a69bb5SSascha Wildner 	    req = TAILQ_NEXT(req, tq))
14850a69bb5SSascha Wildner 		;
14950a69bb5SSascha Wildner 	return req;
15050a69bb5SSascha Wildner }
15150a69bb5SSascha Wildner 
1529f304aafSPeter Avalos /* ARGSUSED */
1539f304aafSPeter Avalos static int
sftpio(void * _bwlimit,size_t amount)1549f304aafSPeter Avalos sftpio(void *_bwlimit, size_t amount)
1559f304aafSPeter Avalos {
1569f304aafSPeter Avalos 	struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;
1579f304aafSPeter Avalos 
158664f4763Szrj 	refresh_progress_meter(0);
159664f4763Szrj 	if (bwlimit != NULL)
1609f304aafSPeter Avalos 		bandwidth_limit(bwlimit, amount);
1619f304aafSPeter Avalos 	return 0;
1629f304aafSPeter Avalos }
163856ea928SPeter Avalos 
16418de8d7fSPeter Avalos static void
send_msg(struct sftp_conn * conn,struct sshbuf * m)165e9778795SPeter Avalos send_msg(struct sftp_conn *conn, struct sshbuf *m)
16618de8d7fSPeter Avalos {
16718de8d7fSPeter Avalos 	u_char mlen[4];
16818de8d7fSPeter Avalos 	struct iovec iov[2];
16918de8d7fSPeter Avalos 
170e9778795SPeter Avalos 	if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH)
171e9778795SPeter Avalos 		fatal("Outbound message too long %zu", sshbuf_len(m));
17218de8d7fSPeter Avalos 
17318de8d7fSPeter Avalos 	/* Send length first */
174e9778795SPeter Avalos 	put_u32(mlen, sshbuf_len(m));
17518de8d7fSPeter Avalos 	iov[0].iov_base = mlen;
17618de8d7fSPeter Avalos 	iov[0].iov_len = sizeof(mlen);
177e9778795SPeter Avalos 	iov[1].iov_base = (u_char *)sshbuf_ptr(m);
178e9778795SPeter Avalos 	iov[1].iov_len = sshbuf_len(m);
17918de8d7fSPeter Avalos 
180664f4763Szrj 	if (atomiciov6(writev, conn->fd_out, iov, 2, sftpio,
181664f4763Szrj 	    conn->limit_kbps > 0 ? &conn->bwlimit_out : NULL) !=
182e9778795SPeter Avalos 	    sshbuf_len(m) + sizeof(mlen))
18318de8d7fSPeter Avalos 		fatal("Couldn't send packet: %s", strerror(errno));
18418de8d7fSPeter Avalos 
185e9778795SPeter Avalos 	sshbuf_reset(m);
18618de8d7fSPeter Avalos }
18718de8d7fSPeter Avalos 
18818de8d7fSPeter Avalos static void
get_msg_extended(struct sftp_conn * conn,struct sshbuf * m,int initial)189664f4763Szrj get_msg_extended(struct sftp_conn *conn, struct sshbuf *m, int initial)
19018de8d7fSPeter Avalos {
19118de8d7fSPeter Avalos 	u_int msg_len;
192e9778795SPeter Avalos 	u_char *p;
193e9778795SPeter Avalos 	int r;
19418de8d7fSPeter Avalos 
19550a69bb5SSascha Wildner 	sshbuf_reset(m);
196e9778795SPeter Avalos 	if ((r = sshbuf_reserve(m, 4, &p)) != 0)
19750a69bb5SSascha Wildner 		fatal_fr(r, "reserve");
198664f4763Szrj 	if (atomicio6(read, conn->fd_in, p, 4, sftpio,
199664f4763Szrj 	    conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL) != 4) {
200ce74bacaSMatthew Dillon 		if (errno == EPIPE || errno == ECONNRESET)
20118de8d7fSPeter Avalos 			fatal("Connection closed");
20218de8d7fSPeter Avalos 		else
20318de8d7fSPeter Avalos 			fatal("Couldn't read packet: %s", strerror(errno));
20418de8d7fSPeter Avalos 	}
20518de8d7fSPeter Avalos 
206e9778795SPeter Avalos 	if ((r = sshbuf_get_u32(m, &msg_len)) != 0)
20750a69bb5SSascha Wildner 		fatal_fr(r, "sshbuf_get_u32");
208664f4763Szrj 	if (msg_len > SFTP_MAX_MSG_LENGTH) {
209664f4763Szrj 		do_log2(initial ? SYSLOG_LEVEL_ERROR : SYSLOG_LEVEL_FATAL,
210664f4763Szrj 		    "Received message too long %u", msg_len);
211664f4763Szrj 		fatal("Ensure the remote shell produces no output "
212664f4763Szrj 		    "for non-interactive sessions.");
213664f4763Szrj 	}
21418de8d7fSPeter Avalos 
215e9778795SPeter Avalos 	if ((r = sshbuf_reserve(m, msg_len, &p)) != 0)
21650a69bb5SSascha Wildner 		fatal_fr(r, "reserve");
217664f4763Szrj 	if (atomicio6(read, conn->fd_in, p, msg_len, sftpio,
218664f4763Szrj 	    conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL)
2199f304aafSPeter Avalos 	    != msg_len) {
22018de8d7fSPeter Avalos 		if (errno == EPIPE)
22118de8d7fSPeter Avalos 			fatal("Connection closed");
22218de8d7fSPeter Avalos 		else
22318de8d7fSPeter Avalos 			fatal("Read packet: %s", strerror(errno));
22418de8d7fSPeter Avalos 	}
22518de8d7fSPeter Avalos }
22618de8d7fSPeter Avalos 
22718de8d7fSPeter Avalos static void
get_msg(struct sftp_conn * conn,struct sshbuf * m)228664f4763Szrj get_msg(struct sftp_conn *conn, struct sshbuf *m)
229664f4763Szrj {
230664f4763Szrj 	get_msg_extended(conn, m, 0);
231664f4763Szrj }
232664f4763Szrj 
233664f4763Szrj static void
send_string_request(struct sftp_conn * conn,u_int id,u_int code,const char * s,u_int len)234e9778795SPeter Avalos send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s,
23518de8d7fSPeter Avalos     u_int len)
23618de8d7fSPeter Avalos {
237e9778795SPeter Avalos 	struct sshbuf *msg;
238e9778795SPeter Avalos 	int r;
23918de8d7fSPeter Avalos 
240e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
24150a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
242e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
243e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
244e9778795SPeter Avalos 	    (r = sshbuf_put_string(msg, s, len)) != 0)
24550a69bb5SSascha Wildner 		fatal_fr(r, "compose");
246e9778795SPeter Avalos 	send_msg(conn, msg);
2479f304aafSPeter Avalos 	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
248e9778795SPeter Avalos 	sshbuf_free(msg);
24918de8d7fSPeter Avalos }
25018de8d7fSPeter Avalos 
25118de8d7fSPeter Avalos static void
send_string_attrs_request(struct sftp_conn * conn,u_int id,u_int code,const void * s,u_int len,Attrib * a)2529f304aafSPeter Avalos send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
253e9778795SPeter Avalos     const void *s, u_int len, Attrib *a)
25418de8d7fSPeter Avalos {
255e9778795SPeter Avalos 	struct sshbuf *msg;
256e9778795SPeter Avalos 	int r;
25718de8d7fSPeter Avalos 
258e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
25950a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
260e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
261e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
262e9778795SPeter Avalos 	    (r = sshbuf_put_string(msg, s, len)) != 0 ||
263e9778795SPeter Avalos 	    (r = encode_attrib(msg, a)) != 0)
26450a69bb5SSascha Wildner 		fatal_fr(r, "compose");
265e9778795SPeter Avalos 	send_msg(conn, msg);
26650a69bb5SSascha Wildner 	debug3("Sent message fd %d T:%u I:%u F:0x%04x M:%05o",
26750a69bb5SSascha Wildner 	    conn->fd_out, code, id, a->flags, a->perm);
268e9778795SPeter Avalos 	sshbuf_free(msg);
26918de8d7fSPeter Avalos }
27018de8d7fSPeter Avalos 
27118de8d7fSPeter Avalos static u_int
get_status(struct sftp_conn * conn,u_int expected_id)2729f304aafSPeter Avalos get_status(struct sftp_conn *conn, u_int expected_id)
27318de8d7fSPeter Avalos {
274e9778795SPeter Avalos 	struct sshbuf *msg;
275e9778795SPeter Avalos 	u_char type;
276e9778795SPeter Avalos 	u_int id, status;
277e9778795SPeter Avalos 	int r;
27818de8d7fSPeter Avalos 
279e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
28050a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
281e9778795SPeter Avalos 	get_msg(conn, msg);
282e9778795SPeter Avalos 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
283e9778795SPeter Avalos 	    (r = sshbuf_get_u32(msg, &id)) != 0)
28450a69bb5SSascha Wildner 		fatal_fr(r, "compose");
28518de8d7fSPeter Avalos 
28618de8d7fSPeter Avalos 	if (id != expected_id)
28718de8d7fSPeter Avalos 		fatal("ID mismatch (%u != %u)", id, expected_id);
28818de8d7fSPeter Avalos 	if (type != SSH2_FXP_STATUS)
28918de8d7fSPeter Avalos 		fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
29018de8d7fSPeter Avalos 		    SSH2_FXP_STATUS, type);
29118de8d7fSPeter Avalos 
292e9778795SPeter Avalos 	if ((r = sshbuf_get_u32(msg, &status)) != 0)
29350a69bb5SSascha Wildner 		fatal_fr(r, "parse");
294e9778795SPeter Avalos 	sshbuf_free(msg);
29518de8d7fSPeter Avalos 
29618de8d7fSPeter Avalos 	debug3("SSH2_FXP_STATUS %u", status);
29718de8d7fSPeter Avalos 
2989f304aafSPeter Avalos 	return status;
29918de8d7fSPeter Avalos }
30018de8d7fSPeter Avalos 
301e9778795SPeter Avalos static u_char *
get_handle(struct sftp_conn * conn,u_int expected_id,size_t * len,const char * errfmt,...)302e9778795SPeter Avalos get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
3039f304aafSPeter Avalos     const char *errfmt, ...)
30418de8d7fSPeter Avalos {
305e9778795SPeter Avalos 	struct sshbuf *msg;
306e9778795SPeter Avalos 	u_int id, status;
307e9778795SPeter Avalos 	u_char type;
308e9778795SPeter Avalos 	u_char *handle;
309e9778795SPeter Avalos 	char errmsg[256];
310856ea928SPeter Avalos 	va_list args;
311e9778795SPeter Avalos 	int r;
312856ea928SPeter Avalos 
313856ea928SPeter Avalos 	va_start(args, errfmt);
314856ea928SPeter Avalos 	if (errfmt != NULL)
315856ea928SPeter Avalos 		vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
316856ea928SPeter Avalos 	va_end(args);
31718de8d7fSPeter Avalos 
318e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
31950a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
320e9778795SPeter Avalos 	get_msg(conn, msg);
321e9778795SPeter Avalos 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
322e9778795SPeter Avalos 	    (r = sshbuf_get_u32(msg, &id)) != 0)
32350a69bb5SSascha Wildner 		fatal_fr(r, "parse");
32418de8d7fSPeter Avalos 
32518de8d7fSPeter Avalos 	if (id != expected_id)
326856ea928SPeter Avalos 		fatal("%s: ID mismatch (%u != %u)",
327856ea928SPeter Avalos 		    errfmt == NULL ? __func__ : errmsg, id, expected_id);
32818de8d7fSPeter Avalos 	if (type == SSH2_FXP_STATUS) {
329e9778795SPeter Avalos 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
33050a69bb5SSascha Wildner 			fatal_fr(r, "parse status");
331856ea928SPeter Avalos 		if (errfmt != NULL)
332856ea928SPeter Avalos 			error("%s: %s", errmsg, fx2txt(status));
333e9778795SPeter Avalos 		sshbuf_free(msg);
33418de8d7fSPeter Avalos 		return(NULL);
33518de8d7fSPeter Avalos 	} else if (type != SSH2_FXP_HANDLE)
336856ea928SPeter Avalos 		fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
337856ea928SPeter Avalos 		    errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
33818de8d7fSPeter Avalos 
339e9778795SPeter Avalos 	if ((r = sshbuf_get_string(msg, &handle, len)) != 0)
34050a69bb5SSascha Wildner 		fatal_fr(r, "parse handle");
341e9778795SPeter Avalos 	sshbuf_free(msg);
34218de8d7fSPeter Avalos 
343e9778795SPeter Avalos 	return handle;
34418de8d7fSPeter Avalos }
34518de8d7fSPeter Avalos 
346*ee116499SAntonio Huete Jimenez /* XXX returning &static is error-prone. Refactor to fill *Attrib argument */
34718de8d7fSPeter Avalos static Attrib *
get_decode_stat(struct sftp_conn * conn,u_int expected_id,int quiet)3489f304aafSPeter Avalos get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
34918de8d7fSPeter Avalos {
350e9778795SPeter Avalos 	struct sshbuf *msg;
351e9778795SPeter Avalos 	u_int id;
352e9778795SPeter Avalos 	u_char type;
353e9778795SPeter Avalos 	int r;
354e9778795SPeter Avalos 	static Attrib a;
35518de8d7fSPeter Avalos 
356e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
35750a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
358e9778795SPeter Avalos 	get_msg(conn, msg);
35918de8d7fSPeter Avalos 
360e9778795SPeter Avalos 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
361e9778795SPeter Avalos 	    (r = sshbuf_get_u32(msg, &id)) != 0)
36250a69bb5SSascha Wildner 		fatal_fr(r, "parse");
36318de8d7fSPeter Avalos 
36418de8d7fSPeter Avalos 	if (id != expected_id)
36518de8d7fSPeter Avalos 		fatal("ID mismatch (%u != %u)", id, expected_id);
36618de8d7fSPeter Avalos 	if (type == SSH2_FXP_STATUS) {
367e9778795SPeter Avalos 		u_int status;
36818de8d7fSPeter Avalos 
369e9778795SPeter Avalos 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
37050a69bb5SSascha Wildner 			fatal_fr(r, "parse status");
37118de8d7fSPeter Avalos 		if (quiet)
372*ee116499SAntonio Huete Jimenez 			debug("stat remote: %s", fx2txt(status));
37318de8d7fSPeter Avalos 		else
374*ee116499SAntonio Huete Jimenez 			error("stat remote: %s", fx2txt(status));
375e9778795SPeter Avalos 		sshbuf_free(msg);
37618de8d7fSPeter Avalos 		return(NULL);
37718de8d7fSPeter Avalos 	} else if (type != SSH2_FXP_ATTRS) {
37818de8d7fSPeter Avalos 		fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
37918de8d7fSPeter Avalos 		    SSH2_FXP_ATTRS, type);
38018de8d7fSPeter Avalos 	}
381e9778795SPeter Avalos 	if ((r = decode_attrib(msg, &a)) != 0) {
38250a69bb5SSascha Wildner 		error_fr(r, "decode_attrib");
383e9778795SPeter Avalos 		sshbuf_free(msg);
384e9778795SPeter Avalos 		return NULL;
385e9778795SPeter Avalos 	}
386*ee116499SAntonio Huete Jimenez 	debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o",
38750a69bb5SSascha Wildner 	    type, id, a.flags, a.perm);
388e9778795SPeter Avalos 	sshbuf_free(msg);
38918de8d7fSPeter Avalos 
390e9778795SPeter Avalos 	return &a;
39118de8d7fSPeter Avalos }
39218de8d7fSPeter Avalos 
39318de8d7fSPeter Avalos static int
get_decode_statvfs(struct sftp_conn * conn,struct sftp_statvfs * st,u_int expected_id,int quiet)3949f304aafSPeter Avalos get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
3959f304aafSPeter Avalos     u_int expected_id, int quiet)
39618de8d7fSPeter Avalos {
397e9778795SPeter Avalos 	struct sshbuf *msg;
398e9778795SPeter Avalos 	u_char type;
399e9778795SPeter Avalos 	u_int id;
400e9778795SPeter Avalos 	u_int64_t flag;
401e9778795SPeter Avalos 	int r;
40218de8d7fSPeter Avalos 
403e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
40450a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
405e9778795SPeter Avalos 	get_msg(conn, msg);
40618de8d7fSPeter Avalos 
407e9778795SPeter Avalos 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
408e9778795SPeter Avalos 	    (r = sshbuf_get_u32(msg, &id)) != 0)
40950a69bb5SSascha Wildner 		fatal_fr(r, "parse");
41018de8d7fSPeter Avalos 
41118de8d7fSPeter Avalos 	debug3("Received statvfs reply T:%u I:%u", type, id);
41218de8d7fSPeter Avalos 	if (id != expected_id)
41318de8d7fSPeter Avalos 		fatal("ID mismatch (%u != %u)", id, expected_id);
41418de8d7fSPeter Avalos 	if (type == SSH2_FXP_STATUS) {
415e9778795SPeter Avalos 		u_int status;
41618de8d7fSPeter Avalos 
417e9778795SPeter Avalos 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
41850a69bb5SSascha Wildner 			fatal_fr(r, "parse status");
41918de8d7fSPeter Avalos 		if (quiet)
420*ee116499SAntonio Huete Jimenez 			debug("remote statvfs: %s", fx2txt(status));
42118de8d7fSPeter Avalos 		else
422*ee116499SAntonio Huete Jimenez 			error("remote statvfs: %s", fx2txt(status));
423e9778795SPeter Avalos 		sshbuf_free(msg);
42418de8d7fSPeter Avalos 		return -1;
42518de8d7fSPeter Avalos 	} else if (type != SSH2_FXP_EXTENDED_REPLY) {
42618de8d7fSPeter Avalos 		fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
42718de8d7fSPeter Avalos 		    SSH2_FXP_EXTENDED_REPLY, type);
42818de8d7fSPeter Avalos 	}
42918de8d7fSPeter Avalos 
43036e94dc5SPeter Avalos 	memset(st, 0, sizeof(*st));
431e9778795SPeter Avalos 	if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 ||
432e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 ||
433e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 ||
434e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 ||
435e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 ||
436e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_files)) != 0 ||
437e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 ||
438e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 ||
439e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 ||
440e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &flag)) != 0 ||
441e9778795SPeter Avalos 	    (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0)
44250a69bb5SSascha Wildner 		fatal_fr(r, "parse statvfs");
44318de8d7fSPeter Avalos 
44418de8d7fSPeter Avalos 	st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
44518de8d7fSPeter Avalos 	st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
44618de8d7fSPeter Avalos 
447e9778795SPeter Avalos 	sshbuf_free(msg);
44818de8d7fSPeter Avalos 
44918de8d7fSPeter Avalos 	return 0;
45018de8d7fSPeter Avalos }
45118de8d7fSPeter Avalos 
45218de8d7fSPeter Avalos struct sftp_conn *
do_init(int fd_in,int fd_out,u_int transfer_buflen,u_int num_requests,u_int64_t limit_kbps)4539f304aafSPeter Avalos do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
4549f304aafSPeter Avalos     u_int64_t limit_kbps)
45518de8d7fSPeter Avalos {
456e9778795SPeter Avalos 	u_char type;
457e9778795SPeter Avalos 	struct sshbuf *msg;
45818de8d7fSPeter Avalos 	struct sftp_conn *ret;
459e9778795SPeter Avalos 	int r;
46018de8d7fSPeter Avalos 
46136e94dc5SPeter Avalos 	ret = xcalloc(1, sizeof(*ret));
46236e94dc5SPeter Avalos 	ret->msg_id = 1;
4639f304aafSPeter Avalos 	ret->fd_in = fd_in;
4649f304aafSPeter Avalos 	ret->fd_out = fd_out;
46550a69bb5SSascha Wildner 	ret->download_buflen = ret->upload_buflen =
46650a69bb5SSascha Wildner 	    transfer_buflen ? transfer_buflen : DEFAULT_COPY_BUFLEN;
46750a69bb5SSascha Wildner 	ret->num_requests =
46850a69bb5SSascha Wildner 	    num_requests ? num_requests : DEFAULT_NUM_REQUESTS;
4699f304aafSPeter Avalos 	ret->exts = 0;
4709f304aafSPeter Avalos 	ret->limit_kbps = 0;
4719f304aafSPeter Avalos 
472e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
47350a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
474e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 ||
475e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0)
47650a69bb5SSascha Wildner 		fatal_fr(r, "parse");
47718de8d7fSPeter Avalos 
47850a69bb5SSascha Wildner 	send_msg(ret, msg);
47918de8d7fSPeter Avalos 
480664f4763Szrj 	get_msg_extended(ret, msg, 1);
48118de8d7fSPeter Avalos 
48218de8d7fSPeter Avalos 	/* Expecting a VERSION reply */
483e9778795SPeter Avalos 	if ((r = sshbuf_get_u8(msg, &type)) != 0)
48450a69bb5SSascha Wildner 		fatal_fr(r, "parse type");
485e9778795SPeter Avalos 	if (type != SSH2_FXP_VERSION) {
48618de8d7fSPeter Avalos 		error("Invalid packet back from SSH2_FXP_INIT (type %u)",
48718de8d7fSPeter Avalos 		    type);
488e9778795SPeter Avalos 		sshbuf_free(msg);
489e9778795SPeter Avalos 		free(ret);
49018de8d7fSPeter Avalos 		return(NULL);
49118de8d7fSPeter Avalos 	}
492e9778795SPeter Avalos 	if ((r = sshbuf_get_u32(msg, &ret->version)) != 0)
49350a69bb5SSascha Wildner 		fatal_fr(r, "parse version");
49418de8d7fSPeter Avalos 
4959f304aafSPeter Avalos 	debug2("Remote version: %u", ret->version);
49618de8d7fSPeter Avalos 
49718de8d7fSPeter Avalos 	/* Check for extensions */
498e9778795SPeter Avalos 	while (sshbuf_len(msg) > 0) {
499e9778795SPeter Avalos 		char *name;
500e9778795SPeter Avalos 		u_char *value;
501e9778795SPeter Avalos 		size_t vlen;
50218de8d7fSPeter Avalos 		int known = 0;
50318de8d7fSPeter Avalos 
504e9778795SPeter Avalos 		if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 ||
505e9778795SPeter Avalos 		    (r = sshbuf_get_string(msg, &value, &vlen)) != 0)
50650a69bb5SSascha Wildner 			fatal_fr(r, "parse extension");
50718de8d7fSPeter Avalos 		if (strcmp(name, "posix-rename@openssh.com") == 0 &&
508e9778795SPeter Avalos 		    strcmp((char *)value, "1") == 0) {
5099f304aafSPeter Avalos 			ret->exts |= SFTP_EXT_POSIX_RENAME;
51018de8d7fSPeter Avalos 			known = 1;
51118de8d7fSPeter Avalos 		} else if (strcmp(name, "statvfs@openssh.com") == 0 &&
512e9778795SPeter Avalos 		    strcmp((char *)value, "2") == 0) {
5139f304aafSPeter Avalos 			ret->exts |= SFTP_EXT_STATVFS;
51418de8d7fSPeter Avalos 			known = 1;
5159f304aafSPeter Avalos 		} else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
516e9778795SPeter Avalos 		    strcmp((char *)value, "2") == 0) {
5179f304aafSPeter Avalos 			ret->exts |= SFTP_EXT_FSTATVFS;
5189f304aafSPeter Avalos 			known = 1;
5199f304aafSPeter Avalos 		} else if (strcmp(name, "hardlink@openssh.com") == 0 &&
520e9778795SPeter Avalos 		    strcmp((char *)value, "1") == 0) {
5219f304aafSPeter Avalos 			ret->exts |= SFTP_EXT_HARDLINK;
52218de8d7fSPeter Avalos 			known = 1;
52336e94dc5SPeter Avalos 		} else if (strcmp(name, "fsync@openssh.com") == 0 &&
524e9778795SPeter Avalos 		    strcmp((char *)value, "1") == 0) {
52536e94dc5SPeter Avalos 			ret->exts |= SFTP_EXT_FSYNC;
52636e94dc5SPeter Avalos 			known = 1;
527664f4763Szrj 		} else if (strcmp(name, "lsetstat@openssh.com") == 0 &&
528664f4763Szrj 		    strcmp((char *)value, "1") == 0) {
529664f4763Szrj 			ret->exts |= SFTP_EXT_LSETSTAT;
530664f4763Szrj 			known = 1;
53150a69bb5SSascha Wildner 		} else if (strcmp(name, "limits@openssh.com") == 0 &&
53250a69bb5SSascha Wildner 		    strcmp((char *)value, "1") == 0) {
53350a69bb5SSascha Wildner 			ret->exts |= SFTP_EXT_LIMITS;
53450a69bb5SSascha Wildner 			known = 1;
53550a69bb5SSascha Wildner 		} else if (strcmp(name, "expand-path@openssh.com") == 0 &&
53650a69bb5SSascha Wildner 		    strcmp((char *)value, "1") == 0) {
53750a69bb5SSascha Wildner 			ret->exts |= SFTP_EXT_PATH_EXPAND;
53850a69bb5SSascha Wildner 			known = 1;
539*ee116499SAntonio Huete Jimenez 		} else if (strcmp(name, "copy-data") == 0 &&
540*ee116499SAntonio Huete Jimenez 		    strcmp((char *)value, "1") == 0) {
541*ee116499SAntonio Huete Jimenez 			ret->exts |= SFTP_EXT_COPY_DATA;
542*ee116499SAntonio Huete Jimenez 			known = 1;
543*ee116499SAntonio Huete Jimenez 		} else if (strcmp(name,
544*ee116499SAntonio Huete Jimenez 		    "users-groups-by-id@openssh.com") == 0 &&
545*ee116499SAntonio Huete Jimenez 		    strcmp((char *)value, "1") == 0) {
546*ee116499SAntonio Huete Jimenez 			ret->exts |= SFTP_EXT_GETUSERSGROUPS_BY_ID;
547*ee116499SAntonio Huete Jimenez 			known = 1;
54818de8d7fSPeter Avalos 		}
54918de8d7fSPeter Avalos 		if (known) {
55018de8d7fSPeter Avalos 			debug2("Server supports extension \"%s\" revision %s",
55118de8d7fSPeter Avalos 			    name, value);
55218de8d7fSPeter Avalos 		} else {
55318de8d7fSPeter Avalos 			debug2("Unrecognised server extension \"%s\"", name);
55418de8d7fSPeter Avalos 		}
55536e94dc5SPeter Avalos 		free(name);
55636e94dc5SPeter Avalos 		free(value);
55718de8d7fSPeter Avalos 	}
55818de8d7fSPeter Avalos 
559e9778795SPeter Avalos 	sshbuf_free(msg);
56018de8d7fSPeter Avalos 
56150a69bb5SSascha Wildner 	/* Query the server for its limits */
56250a69bb5SSascha Wildner 	if (ret->exts & SFTP_EXT_LIMITS) {
56350a69bb5SSascha Wildner 		struct sftp_limits limits;
56450a69bb5SSascha Wildner 		if (do_limits(ret, &limits) != 0)
56550a69bb5SSascha Wildner 			fatal_f("limits failed");
56650a69bb5SSascha Wildner 
56750a69bb5SSascha Wildner 		/* If the caller did not specify, find a good value */
56850a69bb5SSascha Wildner 		if (transfer_buflen == 0) {
56950a69bb5SSascha Wildner 			ret->download_buflen = limits.read_length;
57050a69bb5SSascha Wildner 			ret->upload_buflen = limits.write_length;
57150a69bb5SSascha Wildner 			debug("Using server download size %u", ret->download_buflen);
57250a69bb5SSascha Wildner 			debug("Using server upload size %u", ret->upload_buflen);
57350a69bb5SSascha Wildner 		}
57450a69bb5SSascha Wildner 
57550a69bb5SSascha Wildner 		/* Use the server limit to scale down our value only */
57650a69bb5SSascha Wildner 		if (num_requests == 0 && limits.open_handles) {
57750a69bb5SSascha Wildner 			ret->num_requests =
57850a69bb5SSascha Wildner 			    MINIMUM(DEFAULT_NUM_REQUESTS, limits.open_handles);
57950a69bb5SSascha Wildner 			debug("Server handle limit %llu; using %u",
58050a69bb5SSascha Wildner 			    (unsigned long long)limits.open_handles,
58150a69bb5SSascha Wildner 			    ret->num_requests);
58250a69bb5SSascha Wildner 		}
58350a69bb5SSascha Wildner 	}
58450a69bb5SSascha Wildner 
58518de8d7fSPeter Avalos 	/* Some filexfer v.0 servers don't support large packets */
58650a69bb5SSascha Wildner 	if (ret->version == 0) {
58750a69bb5SSascha Wildner 		ret->download_buflen = MINIMUM(ret->download_buflen, 20480);
58850a69bb5SSascha Wildner 		ret->upload_buflen = MINIMUM(ret->upload_buflen, 20480);
58950a69bb5SSascha Wildner 	}
59018de8d7fSPeter Avalos 
5919f304aafSPeter Avalos 	ret->limit_kbps = limit_kbps;
5929f304aafSPeter Avalos 	if (ret->limit_kbps > 0) {
5939f304aafSPeter Avalos 		bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
59450a69bb5SSascha Wildner 		    ret->download_buflen);
5959f304aafSPeter Avalos 		bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
59650a69bb5SSascha Wildner 		    ret->upload_buflen);
5979f304aafSPeter Avalos 	}
5989f304aafSPeter Avalos 
5999f304aafSPeter Avalos 	return ret;
60018de8d7fSPeter Avalos }
60118de8d7fSPeter Avalos 
60218de8d7fSPeter Avalos u_int
sftp_proto_version(struct sftp_conn * conn)60318de8d7fSPeter Avalos sftp_proto_version(struct sftp_conn *conn)
60418de8d7fSPeter Avalos {
6059f304aafSPeter Avalos 	return conn->version;
60618de8d7fSPeter Avalos }
60718de8d7fSPeter Avalos 
60818de8d7fSPeter Avalos int
do_limits(struct sftp_conn * conn,struct sftp_limits * limits)60950a69bb5SSascha Wildner do_limits(struct sftp_conn *conn, struct sftp_limits *limits)
61050a69bb5SSascha Wildner {
61150a69bb5SSascha Wildner 	u_int id, msg_id;
61250a69bb5SSascha Wildner 	u_char type;
61350a69bb5SSascha Wildner 	struct sshbuf *msg;
61450a69bb5SSascha Wildner 	int r;
61550a69bb5SSascha Wildner 
61650a69bb5SSascha Wildner 	if ((conn->exts & SFTP_EXT_LIMITS) == 0) {
61750a69bb5SSascha Wildner 		error("Server does not support limits@openssh.com extension");
61850a69bb5SSascha Wildner 		return -1;
61950a69bb5SSascha Wildner 	}
62050a69bb5SSascha Wildner 
62150a69bb5SSascha Wildner 	if ((msg = sshbuf_new()) == NULL)
62250a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
62350a69bb5SSascha Wildner 
62450a69bb5SSascha Wildner 	id = conn->msg_id++;
62550a69bb5SSascha Wildner 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
62650a69bb5SSascha Wildner 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
62750a69bb5SSascha Wildner 	    (r = sshbuf_put_cstring(msg, "limits@openssh.com")) != 0)
62850a69bb5SSascha Wildner 		fatal_fr(r, "compose");
62950a69bb5SSascha Wildner 	send_msg(conn, msg);
63050a69bb5SSascha Wildner 	debug3("Sent message limits@openssh.com I:%u", id);
63150a69bb5SSascha Wildner 
63250a69bb5SSascha Wildner 	get_msg(conn, msg);
63350a69bb5SSascha Wildner 
63450a69bb5SSascha Wildner 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
63550a69bb5SSascha Wildner 	    (r = sshbuf_get_u32(msg, &msg_id)) != 0)
63650a69bb5SSascha Wildner 		fatal_fr(r, "parse");
63750a69bb5SSascha Wildner 
63850a69bb5SSascha Wildner 	debug3("Received limits reply T:%u I:%u", type, msg_id);
63950a69bb5SSascha Wildner 	if (id != msg_id)
64050a69bb5SSascha Wildner 		fatal("ID mismatch (%u != %u)", msg_id, id);
64150a69bb5SSascha Wildner 	if (type != SSH2_FXP_EXTENDED_REPLY) {
64250a69bb5SSascha Wildner 		debug_f("expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
64350a69bb5SSascha Wildner 		    SSH2_FXP_EXTENDED_REPLY, type);
64450a69bb5SSascha Wildner 		/* Disable the limits extension */
64550a69bb5SSascha Wildner 		conn->exts &= ~SFTP_EXT_LIMITS;
64650a69bb5SSascha Wildner 		sshbuf_free(msg);
64750a69bb5SSascha Wildner 		return 0;
64850a69bb5SSascha Wildner 	}
64950a69bb5SSascha Wildner 
65050a69bb5SSascha Wildner 	memset(limits, 0, sizeof(*limits));
65150a69bb5SSascha Wildner 	if ((r = sshbuf_get_u64(msg, &limits->packet_length)) != 0 ||
65250a69bb5SSascha Wildner 	    (r = sshbuf_get_u64(msg, &limits->read_length)) != 0 ||
65350a69bb5SSascha Wildner 	    (r = sshbuf_get_u64(msg, &limits->write_length)) != 0 ||
65450a69bb5SSascha Wildner 	    (r = sshbuf_get_u64(msg, &limits->open_handles)) != 0)
65550a69bb5SSascha Wildner 		fatal_fr(r, "parse limits");
65650a69bb5SSascha Wildner 
65750a69bb5SSascha Wildner 	sshbuf_free(msg);
65850a69bb5SSascha Wildner 
65950a69bb5SSascha Wildner 	return 0;
66050a69bb5SSascha Wildner }
66150a69bb5SSascha Wildner 
66250a69bb5SSascha Wildner int
do_close(struct sftp_conn * conn,const u_char * handle,u_int handle_len)663e9778795SPeter Avalos do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
66418de8d7fSPeter Avalos {
66518de8d7fSPeter Avalos 	u_int id, status;
666e9778795SPeter Avalos 	struct sshbuf *msg;
667e9778795SPeter Avalos 	int r;
66818de8d7fSPeter Avalos 
669e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
67050a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
67118de8d7fSPeter Avalos 
67218de8d7fSPeter Avalos 	id = conn->msg_id++;
673e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 ||
674e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
675e9778795SPeter Avalos 	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
67650a69bb5SSascha Wildner 		fatal_fr(r, "parse");
677e9778795SPeter Avalos 	send_msg(conn, msg);
67818de8d7fSPeter Avalos 	debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
67918de8d7fSPeter Avalos 
6809f304aafSPeter Avalos 	status = get_status(conn, id);
68118de8d7fSPeter Avalos 	if (status != SSH2_FX_OK)
682*ee116499SAntonio Huete Jimenez 		error("close remote: %s", fx2txt(status));
68318de8d7fSPeter Avalos 
684e9778795SPeter Avalos 	sshbuf_free(msg);
68518de8d7fSPeter Avalos 
686e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
68718de8d7fSPeter Avalos }
68818de8d7fSPeter Avalos 
68918de8d7fSPeter Avalos 
69018de8d7fSPeter Avalos static int
do_lsreaddir(struct sftp_conn * conn,const char * path,int print_flag,SFTP_DIRENT *** dir)691e9778795SPeter Avalos do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
69218de8d7fSPeter Avalos     SFTP_DIRENT ***dir)
69318de8d7fSPeter Avalos {
694e9778795SPeter Avalos 	struct sshbuf *msg;
695e9778795SPeter Avalos 	u_int count, id, i, expected_id, ents = 0;
696e9778795SPeter Avalos 	size_t handle_len;
697e9778795SPeter Avalos 	u_char type, *handle;
69836e94dc5SPeter Avalos 	int status = SSH2_FX_FAILURE;
699e9778795SPeter Avalos 	int r;
70036e94dc5SPeter Avalos 
70136e94dc5SPeter Avalos 	if (dir)
70236e94dc5SPeter Avalos 		*dir = NULL;
70318de8d7fSPeter Avalos 
70418de8d7fSPeter Avalos 	id = conn->msg_id++;
70518de8d7fSPeter Avalos 
706e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
70750a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
708e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 ||
709e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
710e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, path)) != 0)
71150a69bb5SSascha Wildner 		fatal_fr(r, "compose OPENDIR");
712e9778795SPeter Avalos 	send_msg(conn, msg);
71318de8d7fSPeter Avalos 
7149f304aafSPeter Avalos 	handle = get_handle(conn, id, &handle_len,
715856ea928SPeter Avalos 	    "remote readdir(\"%s\")", path);
71699e85e0dSPeter Avalos 	if (handle == NULL) {
717e9778795SPeter Avalos 		sshbuf_free(msg);
7189f304aafSPeter Avalos 		return -1;
71999e85e0dSPeter Avalos 	}
72018de8d7fSPeter Avalos 
72118de8d7fSPeter Avalos 	if (dir) {
72218de8d7fSPeter Avalos 		ents = 0;
72336e94dc5SPeter Avalos 		*dir = xcalloc(1, sizeof(**dir));
72418de8d7fSPeter Avalos 		(*dir)[0] = NULL;
72518de8d7fSPeter Avalos 	}
72618de8d7fSPeter Avalos 
72718de8d7fSPeter Avalos 	for (; !interrupted;) {
72818de8d7fSPeter Avalos 		id = expected_id = conn->msg_id++;
72918de8d7fSPeter Avalos 
73018de8d7fSPeter Avalos 		debug3("Sending SSH2_FXP_READDIR I:%u", id);
73118de8d7fSPeter Avalos 
732e9778795SPeter Avalos 		sshbuf_reset(msg);
733e9778795SPeter Avalos 		if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 ||
734e9778795SPeter Avalos 		    (r = sshbuf_put_u32(msg, id)) != 0 ||
735e9778795SPeter Avalos 		    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
73650a69bb5SSascha Wildner 			fatal_fr(r, "compose READDIR");
737e9778795SPeter Avalos 		send_msg(conn, msg);
73818de8d7fSPeter Avalos 
739e9778795SPeter Avalos 		sshbuf_reset(msg);
74018de8d7fSPeter Avalos 
741e9778795SPeter Avalos 		get_msg(conn, msg);
74218de8d7fSPeter Avalos 
743e9778795SPeter Avalos 		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
744e9778795SPeter Avalos 		    (r = sshbuf_get_u32(msg, &id)) != 0)
74550a69bb5SSascha Wildner 			fatal_fr(r, "parse");
74618de8d7fSPeter Avalos 
74718de8d7fSPeter Avalos 		debug3("Received reply T:%u I:%u", type, id);
74818de8d7fSPeter Avalos 
74918de8d7fSPeter Avalos 		if (id != expected_id)
75018de8d7fSPeter Avalos 			fatal("ID mismatch (%u != %u)", id, expected_id);
75118de8d7fSPeter Avalos 
75218de8d7fSPeter Avalos 		if (type == SSH2_FXP_STATUS) {
753e9778795SPeter Avalos 			u_int rstatus;
754e9778795SPeter Avalos 
755e9778795SPeter Avalos 			if ((r = sshbuf_get_u32(msg, &rstatus)) != 0)
75650a69bb5SSascha Wildner 				fatal_fr(r, "parse status");
757e9778795SPeter Avalos 			debug3("Received SSH2_FXP_STATUS %d", rstatus);
758e9778795SPeter Avalos 			if (rstatus == SSH2_FX_EOF)
75918de8d7fSPeter Avalos 				break;
760e9778795SPeter Avalos 			error("Couldn't read directory: %s", fx2txt(rstatus));
76136e94dc5SPeter Avalos 			goto out;
76218de8d7fSPeter Avalos 		} else if (type != SSH2_FXP_NAME)
76318de8d7fSPeter Avalos 			fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
76418de8d7fSPeter Avalos 			    SSH2_FXP_NAME, type);
76518de8d7fSPeter Avalos 
766e9778795SPeter Avalos 		if ((r = sshbuf_get_u32(msg, &count)) != 0)
76750a69bb5SSascha Wildner 			fatal_fr(r, "parse count");
768ce74bacaSMatthew Dillon 		if (count > SSHBUF_SIZE_MAX)
76950a69bb5SSascha Wildner 			fatal_f("nonsensical number of entries");
77018de8d7fSPeter Avalos 		if (count == 0)
77118de8d7fSPeter Avalos 			break;
77218de8d7fSPeter Avalos 		debug3("Received %d SSH2_FXP_NAME responses", count);
77318de8d7fSPeter Avalos 		for (i = 0; i < count; i++) {
77418de8d7fSPeter Avalos 			char *filename, *longname;
775e9778795SPeter Avalos 			Attrib a;
77618de8d7fSPeter Avalos 
777e9778795SPeter Avalos 			if ((r = sshbuf_get_cstring(msg, &filename,
778e9778795SPeter Avalos 			    NULL)) != 0 ||
779e9778795SPeter Avalos 			    (r = sshbuf_get_cstring(msg, &longname,
780e9778795SPeter Avalos 			    NULL)) != 0)
78150a69bb5SSascha Wildner 				fatal_fr(r, "parse filenames");
782e9778795SPeter Avalos 			if ((r = decode_attrib(msg, &a)) != 0) {
78350a69bb5SSascha Wildner 				error_fr(r, "couldn't decode attrib");
784e9778795SPeter Avalos 				free(filename);
785e9778795SPeter Avalos 				free(longname);
7860cbfa66cSDaniel Fojt 				goto out;
787e9778795SPeter Avalos 			}
78818de8d7fSPeter Avalos 
78936e94dc5SPeter Avalos 			if (print_flag)
790e9778795SPeter Avalos 				mprintf("%s\n", longname);
79118de8d7fSPeter Avalos 
792856ea928SPeter Avalos 			/*
793856ea928SPeter Avalos 			 * Directory entries should never contain '/'
794856ea928SPeter Avalos 			 * These can be used to attack recursive ops
795856ea928SPeter Avalos 			 * (e.g. send '../../../../etc/passwd')
796856ea928SPeter Avalos 			 */
797ce74bacaSMatthew Dillon 			if (strpbrk(filename, SFTP_DIRECTORY_CHARS) != NULL) {
798856ea928SPeter Avalos 				error("Server sent suspect path \"%s\" "
799856ea928SPeter Avalos 				    "during readdir of \"%s\"", filename, path);
80036e94dc5SPeter Avalos 			} else if (dir) {
801e9778795SPeter Avalos 				*dir = xreallocarray(*dir, ents + 2, sizeof(**dir));
80236e94dc5SPeter Avalos 				(*dir)[ents] = xcalloc(1, sizeof(***dir));
80318de8d7fSPeter Avalos 				(*dir)[ents]->filename = xstrdup(filename);
80418de8d7fSPeter Avalos 				(*dir)[ents]->longname = xstrdup(longname);
805e9778795SPeter Avalos 				memcpy(&(*dir)[ents]->a, &a, sizeof(a));
80618de8d7fSPeter Avalos 				(*dir)[++ents] = NULL;
80718de8d7fSPeter Avalos 			}
80836e94dc5SPeter Avalos 			free(filename);
80936e94dc5SPeter Avalos 			free(longname);
81018de8d7fSPeter Avalos 		}
81118de8d7fSPeter Avalos 	}
81236e94dc5SPeter Avalos 	status = 0;
81318de8d7fSPeter Avalos 
81436e94dc5SPeter Avalos  out:
815e9778795SPeter Avalos 	sshbuf_free(msg);
81618de8d7fSPeter Avalos 	do_close(conn, handle, handle_len);
81736e94dc5SPeter Avalos 	free(handle);
81818de8d7fSPeter Avalos 
81936e94dc5SPeter Avalos 	if (status != 0 && dir != NULL) {
82036e94dc5SPeter Avalos 		/* Don't return results on error */
82118de8d7fSPeter Avalos 		free_sftp_dirents(*dir);
82236e94dc5SPeter Avalos 		*dir = NULL;
82336e94dc5SPeter Avalos 	} else if (interrupted && dir != NULL && *dir != NULL) {
82436e94dc5SPeter Avalos 		/* Don't return partial matches on interrupt */
82536e94dc5SPeter Avalos 		free_sftp_dirents(*dir);
82636e94dc5SPeter Avalos 		*dir = xcalloc(1, sizeof(**dir));
82718de8d7fSPeter Avalos 		**dir = NULL;
82818de8d7fSPeter Avalos 	}
82918de8d7fSPeter Avalos 
830664f4763Szrj 	return status == SSH2_FX_OK ? 0 : -1;
83118de8d7fSPeter Avalos }
83218de8d7fSPeter Avalos 
83318de8d7fSPeter Avalos int
do_readdir(struct sftp_conn * conn,const char * path,SFTP_DIRENT *** dir)834e9778795SPeter Avalos do_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
83518de8d7fSPeter Avalos {
83618de8d7fSPeter Avalos 	return(do_lsreaddir(conn, path, 0, dir));
83718de8d7fSPeter Avalos }
83818de8d7fSPeter Avalos 
free_sftp_dirents(SFTP_DIRENT ** s)83918de8d7fSPeter Avalos void free_sftp_dirents(SFTP_DIRENT **s)
84018de8d7fSPeter Avalos {
84118de8d7fSPeter Avalos 	int i;
84218de8d7fSPeter Avalos 
84336e94dc5SPeter Avalos 	if (s == NULL)
84436e94dc5SPeter Avalos 		return;
84518de8d7fSPeter Avalos 	for (i = 0; s[i]; i++) {
84636e94dc5SPeter Avalos 		free(s[i]->filename);
84736e94dc5SPeter Avalos 		free(s[i]->longname);
84836e94dc5SPeter Avalos 		free(s[i]);
84918de8d7fSPeter Avalos 	}
85036e94dc5SPeter Avalos 	free(s);
85118de8d7fSPeter Avalos }
85218de8d7fSPeter Avalos 
85318de8d7fSPeter Avalos int
do_rm(struct sftp_conn * conn,const char * path)854e9778795SPeter Avalos do_rm(struct sftp_conn *conn, const char *path)
85518de8d7fSPeter Avalos {
85618de8d7fSPeter Avalos 	u_int status, id;
85718de8d7fSPeter Avalos 
85818de8d7fSPeter Avalos 	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
85918de8d7fSPeter Avalos 
86018de8d7fSPeter Avalos 	id = conn->msg_id++;
8619f304aafSPeter Avalos 	send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
8629f304aafSPeter Avalos 	status = get_status(conn, id);
86318de8d7fSPeter Avalos 	if (status != SSH2_FX_OK)
864*ee116499SAntonio Huete Jimenez 		error("remote delete %s: %s", path, fx2txt(status));
865e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
86618de8d7fSPeter Avalos }
86718de8d7fSPeter Avalos 
86818de8d7fSPeter Avalos int
do_mkdir(struct sftp_conn * conn,const char * path,Attrib * a,int print_flag)869e9778795SPeter Avalos do_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
87018de8d7fSPeter Avalos {
87118de8d7fSPeter Avalos 	u_int status, id;
87218de8d7fSPeter Avalos 
873*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_MKDIR \"%s\"", path);
874*ee116499SAntonio Huete Jimenez 
87518de8d7fSPeter Avalos 	id = conn->msg_id++;
8769f304aafSPeter Avalos 	send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
87718de8d7fSPeter Avalos 	    strlen(path), a);
87818de8d7fSPeter Avalos 
8799f304aafSPeter Avalos 	status = get_status(conn, id);
88036e94dc5SPeter Avalos 	if (status != SSH2_FX_OK && print_flag)
881*ee116499SAntonio Huete Jimenez 		error("remote mkdir \"%s\": %s", path, fx2txt(status));
88218de8d7fSPeter Avalos 
883e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
88418de8d7fSPeter Avalos }
88518de8d7fSPeter Avalos 
88618de8d7fSPeter Avalos int
do_rmdir(struct sftp_conn * conn,const char * path)887e9778795SPeter Avalos do_rmdir(struct sftp_conn *conn, const char *path)
88818de8d7fSPeter Avalos {
88918de8d7fSPeter Avalos 	u_int status, id;
89018de8d7fSPeter Avalos 
891*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_RMDIR \"%s\"", path);
892*ee116499SAntonio Huete Jimenez 
89318de8d7fSPeter Avalos 	id = conn->msg_id++;
8949f304aafSPeter Avalos 	send_string_request(conn, id, SSH2_FXP_RMDIR, path,
89518de8d7fSPeter Avalos 	    strlen(path));
89618de8d7fSPeter Avalos 
8979f304aafSPeter Avalos 	status = get_status(conn, id);
89818de8d7fSPeter Avalos 	if (status != SSH2_FX_OK)
899*ee116499SAntonio Huete Jimenez 		error("remote rmdir \"%s\": %s", path, fx2txt(status));
90018de8d7fSPeter Avalos 
901e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
90218de8d7fSPeter Avalos }
90318de8d7fSPeter Avalos 
90418de8d7fSPeter Avalos Attrib *
do_stat(struct sftp_conn * conn,const char * path,int quiet)905e9778795SPeter Avalos do_stat(struct sftp_conn *conn, const char *path, int quiet)
90618de8d7fSPeter Avalos {
90718de8d7fSPeter Avalos 	u_int id;
90818de8d7fSPeter Avalos 
909*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_STAT \"%s\"", path);
910*ee116499SAntonio Huete Jimenez 
91118de8d7fSPeter Avalos 	id = conn->msg_id++;
91218de8d7fSPeter Avalos 
9139f304aafSPeter Avalos 	send_string_request(conn, id,
91418de8d7fSPeter Avalos 	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
91518de8d7fSPeter Avalos 	    path, strlen(path));
91618de8d7fSPeter Avalos 
9179f304aafSPeter Avalos 	return(get_decode_stat(conn, id, quiet));
91818de8d7fSPeter Avalos }
91918de8d7fSPeter Avalos 
92018de8d7fSPeter Avalos Attrib *
do_lstat(struct sftp_conn * conn,const char * path,int quiet)921e9778795SPeter Avalos do_lstat(struct sftp_conn *conn, const char *path, int quiet)
92218de8d7fSPeter Avalos {
92318de8d7fSPeter Avalos 	u_int id;
92418de8d7fSPeter Avalos 
92518de8d7fSPeter Avalos 	if (conn->version == 0) {
92618de8d7fSPeter Avalos 		if (quiet)
92718de8d7fSPeter Avalos 			debug("Server version does not support lstat operation");
92818de8d7fSPeter Avalos 		else
92918de8d7fSPeter Avalos 			logit("Server version does not support lstat operation");
93018de8d7fSPeter Avalos 		return(do_stat(conn, path, quiet));
93118de8d7fSPeter Avalos 	}
93218de8d7fSPeter Avalos 
93318de8d7fSPeter Avalos 	id = conn->msg_id++;
9349f304aafSPeter Avalos 	send_string_request(conn, id, SSH2_FXP_LSTAT, path,
93518de8d7fSPeter Avalos 	    strlen(path));
93618de8d7fSPeter Avalos 
9379f304aafSPeter Avalos 	return(get_decode_stat(conn, id, quiet));
93818de8d7fSPeter Avalos }
93918de8d7fSPeter Avalos 
94018de8d7fSPeter Avalos #ifdef notyet
94118de8d7fSPeter Avalos Attrib *
do_fstat(struct sftp_conn * conn,const u_char * handle,u_int handle_len,int quiet)942e9778795SPeter Avalos do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
943e9778795SPeter Avalos     int quiet)
94418de8d7fSPeter Avalos {
94518de8d7fSPeter Avalos 	u_int id;
94618de8d7fSPeter Avalos 
947*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_FSTAT \"%s\"");
948*ee116499SAntonio Huete Jimenez 
94918de8d7fSPeter Avalos 	id = conn->msg_id++;
9509f304aafSPeter Avalos 	send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
95118de8d7fSPeter Avalos 	    handle_len);
95218de8d7fSPeter Avalos 
9539f304aafSPeter Avalos 	return(get_decode_stat(conn, id, quiet));
95418de8d7fSPeter Avalos }
95518de8d7fSPeter Avalos #endif
95618de8d7fSPeter Avalos 
95718de8d7fSPeter Avalos int
do_setstat(struct sftp_conn * conn,const char * path,Attrib * a)958e9778795SPeter Avalos do_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
95918de8d7fSPeter Avalos {
96018de8d7fSPeter Avalos 	u_int status, id;
96118de8d7fSPeter Avalos 
962*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_SETSTAT \"%s\"", path);
963*ee116499SAntonio Huete Jimenez 
96418de8d7fSPeter Avalos 	id = conn->msg_id++;
9659f304aafSPeter Avalos 	send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
96618de8d7fSPeter Avalos 	    strlen(path), a);
96718de8d7fSPeter Avalos 
9689f304aafSPeter Avalos 	status = get_status(conn, id);
96918de8d7fSPeter Avalos 	if (status != SSH2_FX_OK)
970*ee116499SAntonio Huete Jimenez 		error("remote setstat \"%s\": %s", path, fx2txt(status));
97118de8d7fSPeter Avalos 
972e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
97318de8d7fSPeter Avalos }
97418de8d7fSPeter Avalos 
97518de8d7fSPeter Avalos int
do_fsetstat(struct sftp_conn * conn,const u_char * handle,u_int handle_len,Attrib * a)976e9778795SPeter Avalos do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
97718de8d7fSPeter Avalos     Attrib *a)
97818de8d7fSPeter Avalos {
97918de8d7fSPeter Avalos 	u_int status, id;
98018de8d7fSPeter Avalos 
981*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_FSETSTAT");
982*ee116499SAntonio Huete Jimenez 
98318de8d7fSPeter Avalos 	id = conn->msg_id++;
9849f304aafSPeter Avalos 	send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
98518de8d7fSPeter Avalos 	    handle_len, a);
98618de8d7fSPeter Avalos 
9879f304aafSPeter Avalos 	status = get_status(conn, id);
98818de8d7fSPeter Avalos 	if (status != SSH2_FX_OK)
989*ee116499SAntonio Huete Jimenez 		error("remote fsetstat: %s", fx2txt(status));
99018de8d7fSPeter Avalos 
991e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
99218de8d7fSPeter Avalos }
99318de8d7fSPeter Avalos 
99450a69bb5SSascha Wildner /* Implements both the realpath and expand-path operations */
99550a69bb5SSascha Wildner static char *
do_realpath_expand(struct sftp_conn * conn,const char * path,int expand)99650a69bb5SSascha Wildner do_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
99718de8d7fSPeter Avalos {
998e9778795SPeter Avalos 	struct sshbuf *msg;
999e9778795SPeter Avalos 	u_int expected_id, count, id;
100018de8d7fSPeter Avalos 	char *filename, *longname;
1001e9778795SPeter Avalos 	Attrib a;
1002e9778795SPeter Avalos 	u_char type;
1003e9778795SPeter Avalos 	int r;
100450a69bb5SSascha Wildner 	const char *what = "SSH2_FXP_REALPATH";
100550a69bb5SSascha Wildner 
100650a69bb5SSascha Wildner 	if (expand)
100750a69bb5SSascha Wildner 		what = "expand-path@openssh.com";
100850a69bb5SSascha Wildner 	if ((msg = sshbuf_new()) == NULL)
100950a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
101018de8d7fSPeter Avalos 
101118de8d7fSPeter Avalos 	expected_id = id = conn->msg_id++;
101250a69bb5SSascha Wildner 	if (expand) {
1013*ee116499SAntonio Huete Jimenez 		debug2("Sending SSH2_FXP_EXTENDED(expand-path@openssh.com) "
1014*ee116499SAntonio Huete Jimenez 		    "\"%s\"", path);
101550a69bb5SSascha Wildner 		if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
101650a69bb5SSascha Wildner 		    (r = sshbuf_put_u32(msg, id)) != 0 ||
101750a69bb5SSascha Wildner 		    (r = sshbuf_put_cstring(msg,
101850a69bb5SSascha Wildner 		    "expand-path@openssh.com")) != 0 ||
101950a69bb5SSascha Wildner 		    (r = sshbuf_put_cstring(msg, path)) != 0)
102050a69bb5SSascha Wildner 			fatal_fr(r, "compose %s", what);
102150a69bb5SSascha Wildner 		send_msg(conn, msg);
102250a69bb5SSascha Wildner 	} else {
1023*ee116499SAntonio Huete Jimenez 		debug2("Sending SSH2_FXP_REALPATH \"%s\"", path);
102450a69bb5SSascha Wildner 		send_string_request(conn, id, SSH2_FXP_REALPATH,
102550a69bb5SSascha Wildner 		    path, strlen(path));
102650a69bb5SSascha Wildner 	}
1027e9778795SPeter Avalos 	get_msg(conn, msg);
1028e9778795SPeter Avalos 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1029e9778795SPeter Avalos 	    (r = sshbuf_get_u32(msg, &id)) != 0)
103050a69bb5SSascha Wildner 		fatal_fr(r, "parse");
103118de8d7fSPeter Avalos 
103218de8d7fSPeter Avalos 	if (id != expected_id)
103318de8d7fSPeter Avalos 		fatal("ID mismatch (%u != %u)", id, expected_id);
103418de8d7fSPeter Avalos 
103518de8d7fSPeter Avalos 	if (type == SSH2_FXP_STATUS) {
1036e9778795SPeter Avalos 		u_int status;
1037*ee116499SAntonio Huete Jimenez 		char *errmsg;
103818de8d7fSPeter Avalos 
1039*ee116499SAntonio Huete Jimenez 		if ((r = sshbuf_get_u32(msg, &status)) != 0 ||
1040*ee116499SAntonio Huete Jimenez 		    (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0)
104150a69bb5SSascha Wildner 			fatal_fr(r, "parse status");
1042*ee116499SAntonio Huete Jimenez 		error("%s %s: %s", expand ? "expand" : "realpath",
1043*ee116499SAntonio Huete Jimenez 		    path, *errmsg == '\0' ? fx2txt(status) : errmsg);
1044*ee116499SAntonio Huete Jimenez 		free(errmsg);
1045e9778795SPeter Avalos 		sshbuf_free(msg);
1046856ea928SPeter Avalos 		return NULL;
104718de8d7fSPeter Avalos 	} else if (type != SSH2_FXP_NAME)
104818de8d7fSPeter Avalos 		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
104918de8d7fSPeter Avalos 		    SSH2_FXP_NAME, type);
105018de8d7fSPeter Avalos 
1051e9778795SPeter Avalos 	if ((r = sshbuf_get_u32(msg, &count)) != 0)
105250a69bb5SSascha Wildner 		fatal_fr(r, "parse count");
105318de8d7fSPeter Avalos 	if (count != 1)
105450a69bb5SSascha Wildner 		fatal("Got multiple names (%d) from %s", count, what);
105518de8d7fSPeter Avalos 
1056e9778795SPeter Avalos 	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1057e9778795SPeter Avalos 	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1058e9778795SPeter Avalos 	    (r = decode_attrib(msg, &a)) != 0)
105950a69bb5SSascha Wildner 		fatal_fr(r, "parse filename/attrib");
106018de8d7fSPeter Avalos 
106150a69bb5SSascha Wildner 	debug3("%s %s -> %s", what, path, filename);
106218de8d7fSPeter Avalos 
106336e94dc5SPeter Avalos 	free(longname);
106418de8d7fSPeter Avalos 
1065e9778795SPeter Avalos 	sshbuf_free(msg);
106618de8d7fSPeter Avalos 
106718de8d7fSPeter Avalos 	return(filename);
106818de8d7fSPeter Avalos }
106918de8d7fSPeter Avalos 
107050a69bb5SSascha Wildner char *
do_realpath(struct sftp_conn * conn,const char * path)107150a69bb5SSascha Wildner do_realpath(struct sftp_conn *conn, const char *path)
107250a69bb5SSascha Wildner {
107350a69bb5SSascha Wildner 	return do_realpath_expand(conn, path, 0);
107450a69bb5SSascha Wildner }
107550a69bb5SSascha Wildner 
107650a69bb5SSascha Wildner int
can_expand_path(struct sftp_conn * conn)107750a69bb5SSascha Wildner can_expand_path(struct sftp_conn *conn)
107850a69bb5SSascha Wildner {
107950a69bb5SSascha Wildner 	return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0;
108050a69bb5SSascha Wildner }
108150a69bb5SSascha Wildner 
108250a69bb5SSascha Wildner char *
do_expand_path(struct sftp_conn * conn,const char * path)108350a69bb5SSascha Wildner do_expand_path(struct sftp_conn *conn, const char *path)
108450a69bb5SSascha Wildner {
108550a69bb5SSascha Wildner 	if (!can_expand_path(conn)) {
108650a69bb5SSascha Wildner 		debug3_f("no server support, fallback to realpath");
108750a69bb5SSascha Wildner 		return do_realpath_expand(conn, path, 0);
108850a69bb5SSascha Wildner 	}
108950a69bb5SSascha Wildner 	return do_realpath_expand(conn, path, 1);
109050a69bb5SSascha Wildner }
109150a69bb5SSascha Wildner 
109218de8d7fSPeter Avalos int
do_copy(struct sftp_conn * conn,const char * oldpath,const char * newpath)1093*ee116499SAntonio Huete Jimenez do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
1094*ee116499SAntonio Huete Jimenez {
1095*ee116499SAntonio Huete Jimenez 	Attrib junk, *a;
1096*ee116499SAntonio Huete Jimenez 	struct sshbuf *msg;
1097*ee116499SAntonio Huete Jimenez 	u_char *old_handle, *new_handle;
1098*ee116499SAntonio Huete Jimenez 	u_int mode, status, id;
1099*ee116499SAntonio Huete Jimenez 	size_t old_handle_len, new_handle_len;
1100*ee116499SAntonio Huete Jimenez 	int r;
1101*ee116499SAntonio Huete Jimenez 
1102*ee116499SAntonio Huete Jimenez 	/* Return if the extension is not supported */
1103*ee116499SAntonio Huete Jimenez 	if ((conn->exts & SFTP_EXT_COPY_DATA) == 0) {
1104*ee116499SAntonio Huete Jimenez 		error("Server does not support copy-data extension");
1105*ee116499SAntonio Huete Jimenez 		return -1;
1106*ee116499SAntonio Huete Jimenez 	}
1107*ee116499SAntonio Huete Jimenez 
1108*ee116499SAntonio Huete Jimenez 	/* Make sure the file exists, and we can copy its perms */
1109*ee116499SAntonio Huete Jimenez 	if ((a = do_stat(conn, oldpath, 0)) == NULL)
1110*ee116499SAntonio Huete Jimenez 		return -1;
1111*ee116499SAntonio Huete Jimenez 
1112*ee116499SAntonio Huete Jimenez 	/* Do not preserve set[ug]id here, as we do not preserve ownership */
1113*ee116499SAntonio Huete Jimenez 	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
1114*ee116499SAntonio Huete Jimenez 		mode = a->perm & 0777;
1115*ee116499SAntonio Huete Jimenez 
1116*ee116499SAntonio Huete Jimenez 		if (!S_ISREG(a->perm)) {
1117*ee116499SAntonio Huete Jimenez 			error("Cannot copy non-regular file: %s", oldpath);
1118*ee116499SAntonio Huete Jimenez 			return -1;
1119*ee116499SAntonio Huete Jimenez 		}
1120*ee116499SAntonio Huete Jimenez 	} else {
1121*ee116499SAntonio Huete Jimenez 		/* NB: The user's umask will apply to this */
1122*ee116499SAntonio Huete Jimenez 		mode = 0666;
1123*ee116499SAntonio Huete Jimenez 	}
1124*ee116499SAntonio Huete Jimenez 
1125*ee116499SAntonio Huete Jimenez 	/* Set up the new perms for the new file */
1126*ee116499SAntonio Huete Jimenez 	attrib_clear(a);
1127*ee116499SAntonio Huete Jimenez 	a->perm = mode;
1128*ee116499SAntonio Huete Jimenez 	a->flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1129*ee116499SAntonio Huete Jimenez 
1130*ee116499SAntonio Huete Jimenez 	if ((msg = sshbuf_new()) == NULL)
1131*ee116499SAntonio Huete Jimenez 		fatal("%s: sshbuf_new failed", __func__);
1132*ee116499SAntonio Huete Jimenez 
1133*ee116499SAntonio Huete Jimenez 	attrib_clear(&junk); /* Send empty attributes */
1134*ee116499SAntonio Huete Jimenez 
1135*ee116499SAntonio Huete Jimenez 	/* Open the old file for reading */
1136*ee116499SAntonio Huete Jimenez 	id = conn->msg_id++;
1137*ee116499SAntonio Huete Jimenez 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1138*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1139*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1140*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 ||
1141*ee116499SAntonio Huete Jimenez 	    (r = encode_attrib(msg, &junk)) != 0)
1142*ee116499SAntonio Huete Jimenez 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1143*ee116499SAntonio Huete Jimenez 	send_msg(conn, msg);
1144*ee116499SAntonio Huete Jimenez 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, oldpath);
1145*ee116499SAntonio Huete Jimenez 
1146*ee116499SAntonio Huete Jimenez 	sshbuf_reset(msg);
1147*ee116499SAntonio Huete Jimenez 
1148*ee116499SAntonio Huete Jimenez 	old_handle = get_handle(conn, id, &old_handle_len,
1149*ee116499SAntonio Huete Jimenez 	    "remote open(\"%s\")", oldpath);
1150*ee116499SAntonio Huete Jimenez 	if (old_handle == NULL) {
1151*ee116499SAntonio Huete Jimenez 		sshbuf_free(msg);
1152*ee116499SAntonio Huete Jimenez 		return -1;
1153*ee116499SAntonio Huete Jimenez 	}
1154*ee116499SAntonio Huete Jimenez 
1155*ee116499SAntonio Huete Jimenez 	/* Open the new file for writing */
1156*ee116499SAntonio Huete Jimenez 	id = conn->msg_id++;
1157*ee116499SAntonio Huete Jimenez 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1158*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1159*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_cstring(msg, newpath)) != 0 ||
1160*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|
1161*ee116499SAntonio Huete Jimenez 	    SSH2_FXF_TRUNC)) != 0 ||
1162*ee116499SAntonio Huete Jimenez 	    (r = encode_attrib(msg, a)) != 0)
1163*ee116499SAntonio Huete Jimenez 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1164*ee116499SAntonio Huete Jimenez 	send_msg(conn, msg);
1165*ee116499SAntonio Huete Jimenez 	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath);
1166*ee116499SAntonio Huete Jimenez 
1167*ee116499SAntonio Huete Jimenez 	sshbuf_reset(msg);
1168*ee116499SAntonio Huete Jimenez 
1169*ee116499SAntonio Huete Jimenez 	new_handle = get_handle(conn, id, &new_handle_len,
1170*ee116499SAntonio Huete Jimenez 	    "remote open(\"%s\")", newpath);
1171*ee116499SAntonio Huete Jimenez 	if (new_handle == NULL) {
1172*ee116499SAntonio Huete Jimenez 		sshbuf_free(msg);
1173*ee116499SAntonio Huete Jimenez 		free(old_handle);
1174*ee116499SAntonio Huete Jimenez 		return -1;
1175*ee116499SAntonio Huete Jimenez 	}
1176*ee116499SAntonio Huete Jimenez 
1177*ee116499SAntonio Huete Jimenez 	/* Copy the file data */
1178*ee116499SAntonio Huete Jimenez 	id = conn->msg_id++;
1179*ee116499SAntonio Huete Jimenez 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1180*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1181*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_cstring(msg, "copy-data")) != 0 ||
1182*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_string(msg, old_handle, old_handle_len)) != 0 ||
1183*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u64(msg, 0)) != 0 ||
1184*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u64(msg, 0)) != 0 ||
1185*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_string(msg, new_handle, new_handle_len)) != 0 ||
1186*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u64(msg, 0)) != 0)
1187*ee116499SAntonio Huete Jimenez 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
1188*ee116499SAntonio Huete Jimenez 	send_msg(conn, msg);
1189*ee116499SAntonio Huete Jimenez 	debug3("Sent message copy-data \"%s\" 0 0 -> \"%s\" 0",
1190*ee116499SAntonio Huete Jimenez 	       oldpath, newpath);
1191*ee116499SAntonio Huete Jimenez 
1192*ee116499SAntonio Huete Jimenez 	status = get_status(conn, id);
1193*ee116499SAntonio Huete Jimenez 	if (status != SSH2_FX_OK)
1194*ee116499SAntonio Huete Jimenez 		error("Couldn't copy file \"%s\" to \"%s\": %s", oldpath,
1195*ee116499SAntonio Huete Jimenez 		    newpath, fx2txt(status));
1196*ee116499SAntonio Huete Jimenez 
1197*ee116499SAntonio Huete Jimenez 	/* Clean up everything */
1198*ee116499SAntonio Huete Jimenez 	sshbuf_free(msg);
1199*ee116499SAntonio Huete Jimenez 	do_close(conn, old_handle, old_handle_len);
1200*ee116499SAntonio Huete Jimenez 	do_close(conn, new_handle, new_handle_len);
1201*ee116499SAntonio Huete Jimenez 	free(old_handle);
1202*ee116499SAntonio Huete Jimenez 	free(new_handle);
1203*ee116499SAntonio Huete Jimenez 
1204*ee116499SAntonio Huete Jimenez 	return status == SSH2_FX_OK ? 0 : -1;
1205*ee116499SAntonio Huete Jimenez }
1206*ee116499SAntonio Huete Jimenez 
1207*ee116499SAntonio Huete Jimenez int
do_rename(struct sftp_conn * conn,const char * oldpath,const char * newpath,int force_legacy)1208e9778795SPeter Avalos do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
120936e94dc5SPeter Avalos     int force_legacy)
121018de8d7fSPeter Avalos {
1211e9778795SPeter Avalos 	struct sshbuf *msg;
121218de8d7fSPeter Avalos 	u_int status, id;
1213e9778795SPeter Avalos 	int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy;
121418de8d7fSPeter Avalos 
1215e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
121650a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
121718de8d7fSPeter Avalos 
121818de8d7fSPeter Avalos 	/* Send rename request */
121918de8d7fSPeter Avalos 	id = conn->msg_id++;
122036e94dc5SPeter Avalos 	if (use_ext) {
1221*ee116499SAntonio Huete Jimenez 		debug2("Sending SSH2_FXP_EXTENDED(posix-rename@openssh.com) "
1222*ee116499SAntonio Huete Jimenez 		    "\"%s\" to \"%s\"", oldpath, newpath);
1223e9778795SPeter Avalos 		if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1224e9778795SPeter Avalos 		    (r = sshbuf_put_u32(msg, id)) != 0 ||
1225e9778795SPeter Avalos 		    (r = sshbuf_put_cstring(msg,
1226e9778795SPeter Avalos 		    "posix-rename@openssh.com")) != 0)
122750a69bb5SSascha Wildner 			fatal_fr(r, "compose posix-rename");
122818de8d7fSPeter Avalos 	} else {
1229*ee116499SAntonio Huete Jimenez 		debug2("Sending SSH2_FXP_RENAME \"%s\" to \"%s\"",
1230*ee116499SAntonio Huete Jimenez 		    oldpath, newpath);
1231e9778795SPeter Avalos 		if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 ||
1232e9778795SPeter Avalos 		    (r = sshbuf_put_u32(msg, id)) != 0)
123350a69bb5SSascha Wildner 			fatal_fr(r, "compose rename");
123418de8d7fSPeter Avalos 	}
1235e9778795SPeter Avalos 	if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1236e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
123750a69bb5SSascha Wildner 		fatal_fr(r, "compose paths");
1238e9778795SPeter Avalos 	send_msg(conn, msg);
123918de8d7fSPeter Avalos 	debug3("Sent message %s \"%s\" -> \"%s\"",
1240e9778795SPeter Avalos 	    use_ext ? "posix-rename@openssh.com" :
1241e9778795SPeter Avalos 	    "SSH2_FXP_RENAME", oldpath, newpath);
1242e9778795SPeter Avalos 	sshbuf_free(msg);
124318de8d7fSPeter Avalos 
12449f304aafSPeter Avalos 	status = get_status(conn, id);
124518de8d7fSPeter Avalos 	if (status != SSH2_FX_OK)
1246*ee116499SAntonio Huete Jimenez 		error("remote rename \"%s\" to \"%s\": %s", oldpath,
124718de8d7fSPeter Avalos 		    newpath, fx2txt(status));
124818de8d7fSPeter Avalos 
1249e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
125018de8d7fSPeter Avalos }
125118de8d7fSPeter Avalos 
125218de8d7fSPeter Avalos int
do_hardlink(struct sftp_conn * conn,const char * oldpath,const char * newpath)1253e9778795SPeter Avalos do_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
12549f304aafSPeter Avalos {
1255e9778795SPeter Avalos 	struct sshbuf *msg;
12569f304aafSPeter Avalos 	u_int status, id;
1257e9778795SPeter Avalos 	int r;
12589f304aafSPeter Avalos 
12599f304aafSPeter Avalos 	if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
12609f304aafSPeter Avalos 		error("Server does not support hardlink@openssh.com extension");
12619f304aafSPeter Avalos 		return -1;
12629f304aafSPeter Avalos 	}
1263*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_EXTENDED(hardlink@openssh.com) "
1264*ee116499SAntonio Huete Jimenez 	    "\"%s\" to \"%s\"", oldpath, newpath);
12659f304aafSPeter Avalos 
1266e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
126750a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
126899e85e0dSPeter Avalos 
126999e85e0dSPeter Avalos 	/* Send link request */
127099e85e0dSPeter Avalos 	id = conn->msg_id++;
1271e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1272e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1273e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
1274e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1275e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
127650a69bb5SSascha Wildner 		fatal_fr(r, "compose");
1277e9778795SPeter Avalos 	send_msg(conn, msg);
12789f304aafSPeter Avalos 	debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
12799f304aafSPeter Avalos 	    oldpath, newpath);
1280e9778795SPeter Avalos 	sshbuf_free(msg);
12819f304aafSPeter Avalos 
12829f304aafSPeter Avalos 	status = get_status(conn, id);
12839f304aafSPeter Avalos 	if (status != SSH2_FX_OK)
1284*ee116499SAntonio Huete Jimenez 		error("remote link \"%s\" to \"%s\": %s", oldpath,
12859f304aafSPeter Avalos 		    newpath, fx2txt(status));
12869f304aafSPeter Avalos 
1287e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
12889f304aafSPeter Avalos }
12899f304aafSPeter Avalos 
12909f304aafSPeter Avalos int
do_symlink(struct sftp_conn * conn,const char * oldpath,const char * newpath)1291e9778795SPeter Avalos do_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
129218de8d7fSPeter Avalos {
1293e9778795SPeter Avalos 	struct sshbuf *msg;
129418de8d7fSPeter Avalos 	u_int status, id;
1295e9778795SPeter Avalos 	int r;
129618de8d7fSPeter Avalos 
129718de8d7fSPeter Avalos 	if (conn->version < 3) {
129818de8d7fSPeter Avalos 		error("This server does not support the symlink operation");
129918de8d7fSPeter Avalos 		return(SSH2_FX_OP_UNSUPPORTED);
130018de8d7fSPeter Avalos 	}
1301*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_SYMLINK \"%s\" to \"%s\"", oldpath, newpath);
130218de8d7fSPeter Avalos 
1303e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
130450a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
130518de8d7fSPeter Avalos 
130618de8d7fSPeter Avalos 	/* Send symlink request */
130718de8d7fSPeter Avalos 	id = conn->msg_id++;
1308e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 ||
1309e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1310e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1311e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, newpath)) != 0)
131250a69bb5SSascha Wildner 		fatal_fr(r, "compose");
1313e9778795SPeter Avalos 	send_msg(conn, msg);
131418de8d7fSPeter Avalos 	debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
131518de8d7fSPeter Avalos 	    newpath);
1316e9778795SPeter Avalos 	sshbuf_free(msg);
131718de8d7fSPeter Avalos 
13189f304aafSPeter Avalos 	status = get_status(conn, id);
131918de8d7fSPeter Avalos 	if (status != SSH2_FX_OK)
1320*ee116499SAntonio Huete Jimenez 		error("remote symlink file \"%s\" to \"%s\": %s", oldpath,
132118de8d7fSPeter Avalos 		    newpath, fx2txt(status));
132218de8d7fSPeter Avalos 
1323e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
132418de8d7fSPeter Avalos }
132518de8d7fSPeter Avalos 
132636e94dc5SPeter Avalos int
do_fsync(struct sftp_conn * conn,u_char * handle,u_int handle_len)1327e9778795SPeter Avalos do_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
132836e94dc5SPeter Avalos {
1329e9778795SPeter Avalos 	struct sshbuf *msg;
133036e94dc5SPeter Avalos 	u_int status, id;
1331e9778795SPeter Avalos 	int r;
133236e94dc5SPeter Avalos 
133336e94dc5SPeter Avalos 	/* Silently return if the extension is not supported */
133436e94dc5SPeter Avalos 	if ((conn->exts & SFTP_EXT_FSYNC) == 0)
133536e94dc5SPeter Avalos 		return -1;
1336*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_EXTENDED(fsync@openssh.com)");
133736e94dc5SPeter Avalos 
133836e94dc5SPeter Avalos 	/* Send fsync request */
1339e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
134050a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
134136e94dc5SPeter Avalos 	id = conn->msg_id++;
1342e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1343e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1344e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
1345e9778795SPeter Avalos 	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
134650a69bb5SSascha Wildner 		fatal_fr(r, "compose");
1347e9778795SPeter Avalos 	send_msg(conn, msg);
134836e94dc5SPeter Avalos 	debug3("Sent message fsync@openssh.com I:%u", id);
1349e9778795SPeter Avalos 	sshbuf_free(msg);
135036e94dc5SPeter Avalos 
135136e94dc5SPeter Avalos 	status = get_status(conn, id);
135236e94dc5SPeter Avalos 	if (status != SSH2_FX_OK)
1353*ee116499SAntonio Huete Jimenez 		error("remote fsync: %s", fx2txt(status));
135436e94dc5SPeter Avalos 
1355664f4763Szrj 	return status == SSH2_FX_OK ? 0 : -1;
135636e94dc5SPeter Avalos }
135736e94dc5SPeter Avalos 
135818de8d7fSPeter Avalos #ifdef notyet
135918de8d7fSPeter Avalos char *
do_readlink(struct sftp_conn * conn,const char * path)1360e9778795SPeter Avalos do_readlink(struct sftp_conn *conn, const char *path)
136118de8d7fSPeter Avalos {
1362e9778795SPeter Avalos 	struct sshbuf *msg;
1363e9778795SPeter Avalos 	u_int expected_id, count, id;
136418de8d7fSPeter Avalos 	char *filename, *longname;
1365e9778795SPeter Avalos 	Attrib a;
1366e9778795SPeter Avalos 	u_char type;
1367e9778795SPeter Avalos 	int r;
136818de8d7fSPeter Avalos 
1369*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_READLINK \"%s\"", path);
1370*ee116499SAntonio Huete Jimenez 
137118de8d7fSPeter Avalos 	expected_id = id = conn->msg_id++;
13729f304aafSPeter Avalos 	send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
137318de8d7fSPeter Avalos 
1374e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
137550a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
137618de8d7fSPeter Avalos 
1377e9778795SPeter Avalos 	get_msg(conn, msg);
1378e9778795SPeter Avalos 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1379e9778795SPeter Avalos 	    (r = sshbuf_get_u32(msg, &id)) != 0)
138050a69bb5SSascha Wildner 		fatal_fr(r, "parse");
138118de8d7fSPeter Avalos 
138218de8d7fSPeter Avalos 	if (id != expected_id)
138318de8d7fSPeter Avalos 		fatal("ID mismatch (%u != %u)", id, expected_id);
138418de8d7fSPeter Avalos 
138518de8d7fSPeter Avalos 	if (type == SSH2_FXP_STATUS) {
1386e9778795SPeter Avalos 		u_int status;
138718de8d7fSPeter Avalos 
1388e9778795SPeter Avalos 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
138950a69bb5SSascha Wildner 			fatal_fr(r, "parse status");
139018de8d7fSPeter Avalos 		error("Couldn't readlink: %s", fx2txt(status));
1391e9778795SPeter Avalos 		sshbuf_free(msg);
139218de8d7fSPeter Avalos 		return(NULL);
139318de8d7fSPeter Avalos 	} else if (type != SSH2_FXP_NAME)
139418de8d7fSPeter Avalos 		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
139518de8d7fSPeter Avalos 		    SSH2_FXP_NAME, type);
139618de8d7fSPeter Avalos 
1397e9778795SPeter Avalos 	if ((r = sshbuf_get_u32(msg, &count)) != 0)
139850a69bb5SSascha Wildner 		fatal_fr(r, "parse count");
139918de8d7fSPeter Avalos 	if (count != 1)
140018de8d7fSPeter Avalos 		fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
140118de8d7fSPeter Avalos 
1402e9778795SPeter Avalos 	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1403e9778795SPeter Avalos 	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1404e9778795SPeter Avalos 	    (r = decode_attrib(msg, &a)) != 0)
140550a69bb5SSascha Wildner 		fatal_fr(r, "parse filenames/attrib");
140618de8d7fSPeter Avalos 
140718de8d7fSPeter Avalos 	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
140818de8d7fSPeter Avalos 
140936e94dc5SPeter Avalos 	free(longname);
141018de8d7fSPeter Avalos 
1411e9778795SPeter Avalos 	sshbuf_free(msg);
141218de8d7fSPeter Avalos 
1413e9778795SPeter Avalos 	return filename;
141418de8d7fSPeter Avalos }
141518de8d7fSPeter Avalos #endif
141618de8d7fSPeter Avalos 
141718de8d7fSPeter Avalos int
do_statvfs(struct sftp_conn * conn,const char * path,struct sftp_statvfs * st,int quiet)141818de8d7fSPeter Avalos do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
141918de8d7fSPeter Avalos     int quiet)
142018de8d7fSPeter Avalos {
1421e9778795SPeter Avalos 	struct sshbuf *msg;
142218de8d7fSPeter Avalos 	u_int id;
1423e9778795SPeter Avalos 	int r;
142418de8d7fSPeter Avalos 
142518de8d7fSPeter Avalos 	if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
142618de8d7fSPeter Avalos 		error("Server does not support statvfs@openssh.com extension");
142718de8d7fSPeter Avalos 		return -1;
142818de8d7fSPeter Avalos 	}
142918de8d7fSPeter Avalos 
1430*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_EXTENDED(statvfs@openssh.com) \"%s\"", path);
1431*ee116499SAntonio Huete Jimenez 
143218de8d7fSPeter Avalos 	id = conn->msg_id++;
143318de8d7fSPeter Avalos 
1434e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
143550a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
1436e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1437e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1438e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
1439e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, path)) != 0)
144050a69bb5SSascha Wildner 		fatal_fr(r, "compose");
1441e9778795SPeter Avalos 	send_msg(conn, msg);
1442e9778795SPeter Avalos 	sshbuf_free(msg);
144318de8d7fSPeter Avalos 
14449f304aafSPeter Avalos 	return get_decode_statvfs(conn, st, id, quiet);
144518de8d7fSPeter Avalos }
144618de8d7fSPeter Avalos 
144718de8d7fSPeter Avalos #ifdef notyet
144818de8d7fSPeter Avalos int
do_fstatvfs(struct sftp_conn * conn,const u_char * handle,u_int handle_len,struct sftp_statvfs * st,int quiet)1449e9778795SPeter Avalos do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
145018de8d7fSPeter Avalos     struct sftp_statvfs *st, int quiet)
145118de8d7fSPeter Avalos {
1452e9778795SPeter Avalos 	struct sshbuf *msg;
145318de8d7fSPeter Avalos 	u_int id;
145418de8d7fSPeter Avalos 
145518de8d7fSPeter Avalos 	if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
145618de8d7fSPeter Avalos 		error("Server does not support fstatvfs@openssh.com extension");
145718de8d7fSPeter Avalos 		return -1;
145818de8d7fSPeter Avalos 	}
145918de8d7fSPeter Avalos 
1460*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_EXTENDED(fstatvfs@openssh.com)");
1461*ee116499SAntonio Huete Jimenez 
146218de8d7fSPeter Avalos 	id = conn->msg_id++;
146318de8d7fSPeter Avalos 
1464e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
146550a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
1466e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1467e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1468e9778795SPeter Avalos 	    (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
1469e9778795SPeter Avalos 	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
147050a69bb5SSascha Wildner 		fatal_fr(r, "compose");
1471e9778795SPeter Avalos 	send_msg(conn, msg);
1472e9778795SPeter Avalos 	sshbuf_free(msg);
147318de8d7fSPeter Avalos 
14749f304aafSPeter Avalos 	return get_decode_statvfs(conn, st, id, quiet);
147518de8d7fSPeter Avalos }
147618de8d7fSPeter Avalos #endif
147718de8d7fSPeter Avalos 
1478664f4763Szrj int
do_lsetstat(struct sftp_conn * conn,const char * path,Attrib * a)1479664f4763Szrj do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a)
1480664f4763Szrj {
1481664f4763Szrj 	struct sshbuf *msg;
1482664f4763Szrj 	u_int status, id;
1483664f4763Szrj 	int r;
1484664f4763Szrj 
1485664f4763Szrj 	if ((conn->exts & SFTP_EXT_LSETSTAT) == 0) {
1486664f4763Szrj 		error("Server does not support lsetstat@openssh.com extension");
1487664f4763Szrj 		return -1;
1488664f4763Szrj 	}
1489664f4763Szrj 
1490*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_EXTENDED(lsetstat@openssh.com) \"%s\"", path);
1491*ee116499SAntonio Huete Jimenez 
1492664f4763Szrj 	id = conn->msg_id++;
1493664f4763Szrj 	if ((msg = sshbuf_new()) == NULL)
149450a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
1495664f4763Szrj 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1496664f4763Szrj 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1497664f4763Szrj 	    (r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 ||
1498664f4763Szrj 	    (r = sshbuf_put_cstring(msg, path)) != 0 ||
1499664f4763Szrj 	    (r = encode_attrib(msg, a)) != 0)
150050a69bb5SSascha Wildner 		fatal_fr(r, "compose");
1501664f4763Szrj 	send_msg(conn, msg);
1502664f4763Szrj 	sshbuf_free(msg);
1503664f4763Szrj 
1504664f4763Szrj 	status = get_status(conn, id);
1505664f4763Szrj 	if (status != SSH2_FX_OK)
1506*ee116499SAntonio Huete Jimenez 		error("remote lsetstat \"%s\": %s", path, fx2txt(status));
1507664f4763Szrj 
1508664f4763Szrj 	return status == SSH2_FX_OK ? 0 : -1;
1509664f4763Szrj }
1510664f4763Szrj 
151118de8d7fSPeter Avalos static void
send_read_request(struct sftp_conn * conn,u_int id,u_int64_t offset,u_int len,const u_char * handle,u_int handle_len)15129f304aafSPeter Avalos send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
1513e9778795SPeter Avalos     u_int len, const u_char *handle, u_int handle_len)
151418de8d7fSPeter Avalos {
1515e9778795SPeter Avalos 	struct sshbuf *msg;
1516e9778795SPeter Avalos 	int r;
151718de8d7fSPeter Avalos 
1518e9778795SPeter Avalos 	if ((msg = sshbuf_new()) == NULL)
151950a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
1520e9778795SPeter Avalos 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 ||
1521e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
1522e9778795SPeter Avalos 	    (r = sshbuf_put_string(msg, handle, handle_len)) != 0 ||
1523e9778795SPeter Avalos 	    (r = sshbuf_put_u64(msg, offset)) != 0 ||
1524e9778795SPeter Avalos 	    (r = sshbuf_put_u32(msg, len)) != 0)
152550a69bb5SSascha Wildner 		fatal_fr(r, "compose");
1526e9778795SPeter Avalos 	send_msg(conn, msg);
1527e9778795SPeter Avalos 	sshbuf_free(msg);
152818de8d7fSPeter Avalos }
152918de8d7fSPeter Avalos 
153050a69bb5SSascha Wildner static int
send_open(struct sftp_conn * conn,const char * path,const char * tag,u_int openmode,Attrib * a,u_char ** handlep,size_t * handle_lenp)153150a69bb5SSascha Wildner send_open(struct sftp_conn *conn, const char *path, const char *tag,
153250a69bb5SSascha Wildner     u_int openmode, Attrib *a, u_char **handlep, size_t *handle_lenp)
153350a69bb5SSascha Wildner {
153450a69bb5SSascha Wildner 	Attrib junk;
153550a69bb5SSascha Wildner 	u_char *handle;
153650a69bb5SSascha Wildner 	size_t handle_len;
153750a69bb5SSascha Wildner 	struct sshbuf *msg;
153850a69bb5SSascha Wildner 	int r;
153950a69bb5SSascha Wildner 	u_int id;
154050a69bb5SSascha Wildner 
1541*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_OPEN \"%s\"", path);
1542*ee116499SAntonio Huete Jimenez 
154350a69bb5SSascha Wildner 	*handlep = NULL;
154450a69bb5SSascha Wildner 	*handle_lenp = 0;
154550a69bb5SSascha Wildner 
154650a69bb5SSascha Wildner 	if (a == NULL) {
154750a69bb5SSascha Wildner 		attrib_clear(&junk); /* Send empty attributes */
154850a69bb5SSascha Wildner 		a = &junk;
154950a69bb5SSascha Wildner 	}
155050a69bb5SSascha Wildner 	/* Send open request */
155150a69bb5SSascha Wildner 	if ((msg = sshbuf_new()) == NULL)
155250a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
155350a69bb5SSascha Wildner 	id = conn->msg_id++;
155450a69bb5SSascha Wildner 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
155550a69bb5SSascha Wildner 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
155650a69bb5SSascha Wildner 	    (r = sshbuf_put_cstring(msg, path)) != 0 ||
155750a69bb5SSascha Wildner 	    (r = sshbuf_put_u32(msg, openmode)) != 0 ||
155850a69bb5SSascha Wildner 	    (r = encode_attrib(msg, a)) != 0)
155950a69bb5SSascha Wildner 		fatal_fr(r, "compose %s open", tag);
156050a69bb5SSascha Wildner 	send_msg(conn, msg);
156150a69bb5SSascha Wildner 	sshbuf_free(msg);
156250a69bb5SSascha Wildner 	debug3("Sent %s message SSH2_FXP_OPEN I:%u P:%s M:0x%04x",
156350a69bb5SSascha Wildner 	    tag, id, path, openmode);
156450a69bb5SSascha Wildner 	if ((handle = get_handle(conn, id, &handle_len,
1565*ee116499SAntonio Huete Jimenez 	    "%s open \"%s\"", tag, path)) == NULL)
156650a69bb5SSascha Wildner 		return -1;
156750a69bb5SSascha Wildner 	/* success */
156850a69bb5SSascha Wildner 	*handlep = handle;
156950a69bb5SSascha Wildner 	*handle_lenp = handle_len;
157050a69bb5SSascha Wildner 	return 0;
157150a69bb5SSascha Wildner }
157250a69bb5SSascha Wildner 
157350a69bb5SSascha Wildner static const char *
progress_meter_path(const char * path)157450a69bb5SSascha Wildner progress_meter_path(const char *path)
157550a69bb5SSascha Wildner {
157650a69bb5SSascha Wildner 	const char *progresspath;
157750a69bb5SSascha Wildner 
157850a69bb5SSascha Wildner 	if ((progresspath = strrchr(path, '/')) == NULL)
157950a69bb5SSascha Wildner 		return path;
158050a69bb5SSascha Wildner 	progresspath++;
158150a69bb5SSascha Wildner 	if (*progresspath == '\0')
158250a69bb5SSascha Wildner 		return path;
158350a69bb5SSascha Wildner 	return progresspath;
158450a69bb5SSascha Wildner }
158550a69bb5SSascha Wildner 
158618de8d7fSPeter Avalos int
do_download(struct sftp_conn * conn,const char * remote_path,const char * local_path,Attrib * a,int preserve_flag,int resume_flag,int fsync_flag,int inplace_flag)1587e9778795SPeter Avalos do_download(struct sftp_conn *conn, const char *remote_path,
1588e9778795SPeter Avalos     const char *local_path, Attrib *a, int preserve_flag, int resume_flag,
1589*ee116499SAntonio Huete Jimenez     int fsync_flag, int inplace_flag)
159018de8d7fSPeter Avalos {
1591e9778795SPeter Avalos 	struct sshbuf *msg;
1592e9778795SPeter Avalos 	u_char *handle;
1593e9778795SPeter Avalos 	int local_fd = -1, write_error;
15940cbfa66cSDaniel Fojt 	int read_error, write_errno, lmodified = 0, reordered = 0, r;
159536e94dc5SPeter Avalos 	u_int64_t offset = 0, size, highwater;
1596e9778795SPeter Avalos 	u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK;
159718de8d7fSPeter Avalos 	off_t progress_counter;
1598e9778795SPeter Avalos 	size_t handle_len;
159936e94dc5SPeter Avalos 	struct stat st;
160050a69bb5SSascha Wildner 	struct requests requests;
160118de8d7fSPeter Avalos 	struct request *req;
1602e9778795SPeter Avalos 	u_char type;
160318de8d7fSPeter Avalos 
1604*ee116499SAntonio Huete Jimenez 	debug2_f("download remote \"%s\" to local \"%s\"",
1605*ee116499SAntonio Huete Jimenez 	    remote_path, local_path);
1606*ee116499SAntonio Huete Jimenez 
160718de8d7fSPeter Avalos 	TAILQ_INIT(&requests);
160818de8d7fSPeter Avalos 
1609856ea928SPeter Avalos 	if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
1610856ea928SPeter Avalos 		return -1;
161118de8d7fSPeter Avalos 
161218de8d7fSPeter Avalos 	/* Do not preserve set[ug]id here, as we do not preserve ownership */
161318de8d7fSPeter Avalos 	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
161418de8d7fSPeter Avalos 		mode = a->perm & 0777;
161518de8d7fSPeter Avalos 	else
161618de8d7fSPeter Avalos 		mode = 0666;
161718de8d7fSPeter Avalos 
161818de8d7fSPeter Avalos 	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
161918de8d7fSPeter Avalos 	    (!S_ISREG(a->perm))) {
1620*ee116499SAntonio Huete Jimenez 		error("download %s: not a regular file", remote_path);
162118de8d7fSPeter Avalos 		return(-1);
162218de8d7fSPeter Avalos 	}
162318de8d7fSPeter Avalos 
162418de8d7fSPeter Avalos 	if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
162518de8d7fSPeter Avalos 		size = a->size;
162618de8d7fSPeter Avalos 	else
162718de8d7fSPeter Avalos 		size = 0;
162818de8d7fSPeter Avalos 
162950a69bb5SSascha Wildner 	buflen = conn->download_buflen;
163018de8d7fSPeter Avalos 
163118de8d7fSPeter Avalos 	/* Send open request */
163250a69bb5SSascha Wildner 	if (send_open(conn, remote_path, "remote", SSH2_FXF_READ, NULL,
163350a69bb5SSascha Wildner 	    &handle, &handle_len) != 0)
163450a69bb5SSascha Wildner 		return -1;
163518de8d7fSPeter Avalos 
1636*ee116499SAntonio Huete Jimenez 	local_fd = open(local_path, O_WRONLY | O_CREAT |
1637*ee116499SAntonio Huete Jimenez 	((resume_flag || inplace_flag) ? 0 : O_TRUNC), mode | S_IWUSR);
163818de8d7fSPeter Avalos 	if (local_fd == -1) {
1639*ee116499SAntonio Huete Jimenez 		error("open local \"%s\": %s", local_path, strerror(errno));
164036e94dc5SPeter Avalos 		goto fail;
164136e94dc5SPeter Avalos 	}
164236e94dc5SPeter Avalos 	offset = highwater = 0;
164336e94dc5SPeter Avalos 	if (resume_flag) {
164436e94dc5SPeter Avalos 		if (fstat(local_fd, &st) == -1) {
1645*ee116499SAntonio Huete Jimenez 			error("stat local \"%s\": %s",
164636e94dc5SPeter Avalos 			    local_path, strerror(errno));
164736e94dc5SPeter Avalos 			goto fail;
164836e94dc5SPeter Avalos 		}
164936e94dc5SPeter Avalos 		if (st.st_size < 0) {
165036e94dc5SPeter Avalos 			error("\"%s\" has negative size", local_path);
165136e94dc5SPeter Avalos 			goto fail;
165236e94dc5SPeter Avalos 		}
165336e94dc5SPeter Avalos 		if ((u_int64_t)st.st_size > size) {
165436e94dc5SPeter Avalos 			error("Unable to resume download of \"%s\": "
165536e94dc5SPeter Avalos 			    "local file is larger than remote", local_path);
165636e94dc5SPeter Avalos  fail:
165718de8d7fSPeter Avalos 			do_close(conn, handle, handle_len);
165836e94dc5SPeter Avalos 			free(handle);
165936e94dc5SPeter Avalos 			if (local_fd != -1)
166036e94dc5SPeter Avalos 				close(local_fd);
166136e94dc5SPeter Avalos 			return -1;
166236e94dc5SPeter Avalos 		}
166336e94dc5SPeter Avalos 		offset = highwater = st.st_size;
166418de8d7fSPeter Avalos 	}
166518de8d7fSPeter Avalos 
166618de8d7fSPeter Avalos 	/* Read from remote and write to local */
166736e94dc5SPeter Avalos 	write_error = read_error = write_errno = num_req = 0;
166818de8d7fSPeter Avalos 	max_req = 1;
166936e94dc5SPeter Avalos 	progress_counter = offset;
167018de8d7fSPeter Avalos 
167150a69bb5SSascha Wildner 	if (showprogress && size != 0) {
167250a69bb5SSascha Wildner 		start_progress_meter(progress_meter_path(remote_path),
167350a69bb5SSascha Wildner 		    size, &progress_counter);
167450a69bb5SSascha Wildner 	}
167550a69bb5SSascha Wildner 
167650a69bb5SSascha Wildner 	if ((msg = sshbuf_new()) == NULL)
167750a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
167818de8d7fSPeter Avalos 
167918de8d7fSPeter Avalos 	while (num_req > 0 || max_req > 0) {
1680e9778795SPeter Avalos 		u_char *data;
1681e9778795SPeter Avalos 		size_t len;
168218de8d7fSPeter Avalos 
168318de8d7fSPeter Avalos 		/*
168418de8d7fSPeter Avalos 		 * Simulate EOF on interrupt: stop sending new requests and
168518de8d7fSPeter Avalos 		 * allow outstanding requests to drain gracefully
168618de8d7fSPeter Avalos 		 */
168718de8d7fSPeter Avalos 		if (interrupted) {
168818de8d7fSPeter Avalos 			if (num_req == 0) /* If we haven't started yet... */
168918de8d7fSPeter Avalos 				break;
169018de8d7fSPeter Avalos 			max_req = 0;
169118de8d7fSPeter Avalos 		}
169218de8d7fSPeter Avalos 
169318de8d7fSPeter Avalos 		/* Send some more requests */
169418de8d7fSPeter Avalos 		while (num_req < max_req) {
169518de8d7fSPeter Avalos 			debug3("Request range %llu -> %llu (%d/%d)",
169618de8d7fSPeter Avalos 			    (unsigned long long)offset,
169718de8d7fSPeter Avalos 			    (unsigned long long)offset + buflen - 1,
169818de8d7fSPeter Avalos 			    num_req, max_req);
169950a69bb5SSascha Wildner 			req = request_enqueue(&requests, conn->msg_id++,
170050a69bb5SSascha Wildner 			    buflen, offset);
170118de8d7fSPeter Avalos 			offset += buflen;
170218de8d7fSPeter Avalos 			num_req++;
17039f304aafSPeter Avalos 			send_read_request(conn, req->id, req->offset,
170418de8d7fSPeter Avalos 			    req->len, handle, handle_len);
170518de8d7fSPeter Avalos 		}
170618de8d7fSPeter Avalos 
1707e9778795SPeter Avalos 		sshbuf_reset(msg);
1708e9778795SPeter Avalos 		get_msg(conn, msg);
1709e9778795SPeter Avalos 		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1710e9778795SPeter Avalos 		    (r = sshbuf_get_u32(msg, &id)) != 0)
171150a69bb5SSascha Wildner 			fatal_fr(r, "parse");
171218de8d7fSPeter Avalos 		debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
171318de8d7fSPeter Avalos 
171418de8d7fSPeter Avalos 		/* Find the request in our queue */
171550a69bb5SSascha Wildner 		if ((req = request_find(&requests, id)) == NULL)
171618de8d7fSPeter Avalos 			fatal("Unexpected reply %u", id);
171718de8d7fSPeter Avalos 
171818de8d7fSPeter Avalos 		switch (type) {
171918de8d7fSPeter Avalos 		case SSH2_FXP_STATUS:
1720e9778795SPeter Avalos 			if ((r = sshbuf_get_u32(msg, &status)) != 0)
172150a69bb5SSascha Wildner 				fatal_fr(r, "parse status");
172218de8d7fSPeter Avalos 			if (status != SSH2_FX_EOF)
172318de8d7fSPeter Avalos 				read_error = 1;
172418de8d7fSPeter Avalos 			max_req = 0;
172518de8d7fSPeter Avalos 			TAILQ_REMOVE(&requests, req, tq);
172636e94dc5SPeter Avalos 			free(req);
172718de8d7fSPeter Avalos 			num_req--;
172818de8d7fSPeter Avalos 			break;
172918de8d7fSPeter Avalos 		case SSH2_FXP_DATA:
1730e9778795SPeter Avalos 			if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
173150a69bb5SSascha Wildner 				fatal_fr(r, "parse data");
173218de8d7fSPeter Avalos 			debug3("Received data %llu -> %llu",
173318de8d7fSPeter Avalos 			    (unsigned long long)req->offset,
173418de8d7fSPeter Avalos 			    (unsigned long long)req->offset + len - 1);
173518de8d7fSPeter Avalos 			if (len > req->len)
173618de8d7fSPeter Avalos 				fatal("Received more data than asked for "
1737e9778795SPeter Avalos 				    "%zu > %zu", len, req->len);
17380cbfa66cSDaniel Fojt 			lmodified = 1;
173918de8d7fSPeter Avalos 			if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
174018de8d7fSPeter Avalos 			    atomicio(vwrite, local_fd, data, len) != len) &&
174118de8d7fSPeter Avalos 			    !write_error) {
174218de8d7fSPeter Avalos 				write_errno = errno;
174318de8d7fSPeter Avalos 				write_error = 1;
174418de8d7fSPeter Avalos 				max_req = 0;
174518de8d7fSPeter Avalos 			}
174636e94dc5SPeter Avalos 			else if (!reordered && req->offset <= highwater)
174736e94dc5SPeter Avalos 				highwater = req->offset + len;
174836e94dc5SPeter Avalos 			else if (!reordered && req->offset > highwater)
174936e94dc5SPeter Avalos 				reordered = 1;
175018de8d7fSPeter Avalos 			progress_counter += len;
175136e94dc5SPeter Avalos 			free(data);
175218de8d7fSPeter Avalos 
175318de8d7fSPeter Avalos 			if (len == req->len) {
175418de8d7fSPeter Avalos 				TAILQ_REMOVE(&requests, req, tq);
175536e94dc5SPeter Avalos 				free(req);
175618de8d7fSPeter Avalos 				num_req--;
175718de8d7fSPeter Avalos 			} else {
175818de8d7fSPeter Avalos 				/* Resend the request for the missing data */
175918de8d7fSPeter Avalos 				debug3("Short data block, re-requesting "
176018de8d7fSPeter Avalos 				    "%llu -> %llu (%2d)",
176118de8d7fSPeter Avalos 				    (unsigned long long)req->offset + len,
176218de8d7fSPeter Avalos 				    (unsigned long long)req->offset +
176318de8d7fSPeter Avalos 				    req->len - 1, num_req);
176418de8d7fSPeter Avalos 				req->id = conn->msg_id++;
176518de8d7fSPeter Avalos 				req->len -= len;
176618de8d7fSPeter Avalos 				req->offset += len;
17679f304aafSPeter Avalos 				send_read_request(conn, req->id,
176818de8d7fSPeter Avalos 				    req->offset, req->len, handle, handle_len);
176918de8d7fSPeter Avalos 				/* Reduce the request size */
177018de8d7fSPeter Avalos 				if (len < buflen)
1771ce74bacaSMatthew Dillon 					buflen = MAXIMUM(MIN_READ_SIZE, len);
177218de8d7fSPeter Avalos 			}
177318de8d7fSPeter Avalos 			if (max_req > 0) { /* max_req = 0 iff EOF received */
177418de8d7fSPeter Avalos 				if (size > 0 && offset > size) {
177518de8d7fSPeter Avalos 					/* Only one request at a time
177618de8d7fSPeter Avalos 					 * after the expected EOF */
177718de8d7fSPeter Avalos 					debug3("Finish at %llu (%2d)",
177818de8d7fSPeter Avalos 					    (unsigned long long)offset,
177918de8d7fSPeter Avalos 					    num_req);
178018de8d7fSPeter Avalos 					max_req = 1;
178150a69bb5SSascha Wildner 				} else if (max_req < conn->num_requests) {
178218de8d7fSPeter Avalos 					++max_req;
178318de8d7fSPeter Avalos 				}
178418de8d7fSPeter Avalos 			}
178518de8d7fSPeter Avalos 			break;
178618de8d7fSPeter Avalos 		default:
178718de8d7fSPeter Avalos 			fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
178818de8d7fSPeter Avalos 			    SSH2_FXP_DATA, type);
178918de8d7fSPeter Avalos 		}
179018de8d7fSPeter Avalos 	}
179118de8d7fSPeter Avalos 
179218de8d7fSPeter Avalos 	if (showprogress && size)
179318de8d7fSPeter Avalos 		stop_progress_meter();
179418de8d7fSPeter Avalos 
179518de8d7fSPeter Avalos 	/* Sanity check */
179618de8d7fSPeter Avalos 	if (TAILQ_FIRST(&requests) != NULL)
179718de8d7fSPeter Avalos 		fatal("Transfer complete, but requests still in queue");
1798*ee116499SAntonio Huete Jimenez 	/*
1799*ee116499SAntonio Huete Jimenez 	 * Truncate at highest contiguous point to avoid holes on interrupt,
1800*ee116499SAntonio Huete Jimenez 	 * or unconditionally if writing in place.
1801*ee116499SAntonio Huete Jimenez 	 */
1802*ee116499SAntonio Huete Jimenez 	if (inplace_flag || read_error || write_error || interrupted) {
180336e94dc5SPeter Avalos 		if (reordered && resume_flag) {
180436e94dc5SPeter Avalos 			error("Unable to resume download of \"%s\": "
180536e94dc5SPeter Avalos 			    "server reordered requests", local_path);
180636e94dc5SPeter Avalos 		}
180736e94dc5SPeter Avalos 		debug("truncating at %llu", (unsigned long long)highwater);
1808e9778795SPeter Avalos 		if (ftruncate(local_fd, highwater) == -1)
1809*ee116499SAntonio Huete Jimenez 			error("local ftruncate \"%s\": %s", local_path,
1810e9778795SPeter Avalos 			    strerror(errno));
181136e94dc5SPeter Avalos 	}
181218de8d7fSPeter Avalos 	if (read_error) {
1813*ee116499SAntonio Huete Jimenez 		error("read remote \"%s\" : %s", remote_path, fx2txt(status));
181436e94dc5SPeter Avalos 		status = -1;
181518de8d7fSPeter Avalos 		do_close(conn, handle, handle_len);
181618de8d7fSPeter Avalos 	} else if (write_error) {
1817*ee116499SAntonio Huete Jimenez 		error("write local \"%s\": %s", local_path,
181818de8d7fSPeter Avalos 		    strerror(write_errno));
1819e9778795SPeter Avalos 		status = SSH2_FX_FAILURE;
182018de8d7fSPeter Avalos 		do_close(conn, handle, handle_len);
182118de8d7fSPeter Avalos 	} else {
1822e9778795SPeter Avalos 		if (do_close(conn, handle, handle_len) != 0 || interrupted)
1823e9778795SPeter Avalos 			status = SSH2_FX_FAILURE;
1824e9778795SPeter Avalos 		else
1825e9778795SPeter Avalos 			status = SSH2_FX_OK;
182618de8d7fSPeter Avalos 		/* Override umask and utimes if asked */
182718de8d7fSPeter Avalos #ifdef HAVE_FCHMOD
182836e94dc5SPeter Avalos 		if (preserve_flag && fchmod(local_fd, mode) == -1)
182918de8d7fSPeter Avalos #else
183036e94dc5SPeter Avalos 		if (preserve_flag && chmod(local_path, mode) == -1)
183118de8d7fSPeter Avalos #endif /* HAVE_FCHMOD */
1832*ee116499SAntonio Huete Jimenez 			error("local chmod \"%s\": %s", local_path,
183318de8d7fSPeter Avalos 			    strerror(errno));
183436e94dc5SPeter Avalos 		if (preserve_flag &&
183536e94dc5SPeter Avalos 		    (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
183618de8d7fSPeter Avalos 			struct timeval tv[2];
183718de8d7fSPeter Avalos 			tv[0].tv_sec = a->atime;
183818de8d7fSPeter Avalos 			tv[1].tv_sec = a->mtime;
183918de8d7fSPeter Avalos 			tv[0].tv_usec = tv[1].tv_usec = 0;
184018de8d7fSPeter Avalos 			if (utimes(local_path, tv) == -1)
1841*ee116499SAntonio Huete Jimenez 				error("local set times \"%s\": %s",
184218de8d7fSPeter Avalos 				    local_path, strerror(errno));
184318de8d7fSPeter Avalos 		}
18440cbfa66cSDaniel Fojt 		if (resume_flag && !lmodified)
18450cbfa66cSDaniel Fojt 			logit("File \"%s\" was not modified", local_path);
18460cbfa66cSDaniel Fojt 		else if (fsync_flag) {
184736e94dc5SPeter Avalos 			debug("syncing \"%s\"", local_path);
184836e94dc5SPeter Avalos 			if (fsync(local_fd) == -1)
1849*ee116499SAntonio Huete Jimenez 				error("local sync \"%s\": %s",
185036e94dc5SPeter Avalos 				    local_path, strerror(errno));
185136e94dc5SPeter Avalos 		}
185218de8d7fSPeter Avalos 	}
185318de8d7fSPeter Avalos 	close(local_fd);
1854e9778795SPeter Avalos 	sshbuf_free(msg);
185536e94dc5SPeter Avalos 	free(handle);
185618de8d7fSPeter Avalos 
1857664f4763Szrj 	return status == SSH2_FX_OK ? 0 : -1;
185818de8d7fSPeter Avalos }
185918de8d7fSPeter Avalos 
1860856ea928SPeter Avalos static int
download_dir_internal(struct sftp_conn * conn,const char * src,const char * dst,int depth,Attrib * dirattrib,int preserve_flag,int print_flag,int resume_flag,int fsync_flag,int follow_link_flag,int inplace_flag)1861e9778795SPeter Avalos download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1862e9778795SPeter Avalos     int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
1863*ee116499SAntonio Huete Jimenez     int resume_flag, int fsync_flag, int follow_link_flag, int inplace_flag)
1864856ea928SPeter Avalos {
1865856ea928SPeter Avalos 	int i, ret = 0;
1866856ea928SPeter Avalos 	SFTP_DIRENT **dir_entries;
1867664f4763Szrj 	char *filename, *new_src = NULL, *new_dst = NULL;
186850a69bb5SSascha Wildner 	mode_t mode = 0777, tmpmode = mode;
1869856ea928SPeter Avalos 
1870856ea928SPeter Avalos 	if (depth >= MAX_DIR_DEPTH) {
1871856ea928SPeter Avalos 		error("Maximum directory depth exceeded: %d levels", depth);
1872856ea928SPeter Avalos 		return -1;
1873856ea928SPeter Avalos 	}
1874856ea928SPeter Avalos 
1875*ee116499SAntonio Huete Jimenez 	debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst);
1876*ee116499SAntonio Huete Jimenez 
1877856ea928SPeter Avalos 	if (dirattrib == NULL &&
1878856ea928SPeter Avalos 	    (dirattrib = do_stat(conn, src, 1)) == NULL) {
1879*ee116499SAntonio Huete Jimenez 		error("stat remote \"%s\" directory failed", src);
1880856ea928SPeter Avalos 		return -1;
1881856ea928SPeter Avalos 	}
1882856ea928SPeter Avalos 	if (!S_ISDIR(dirattrib->perm)) {
1883856ea928SPeter Avalos 		error("\"%s\" is not a directory", src);
1884856ea928SPeter Avalos 		return -1;
1885856ea928SPeter Avalos 	}
188650a69bb5SSascha Wildner 	if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
1887e9778795SPeter Avalos 		mprintf("Retrieving %s\n", src);
1888856ea928SPeter Avalos 
188950a69bb5SSascha Wildner 	if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
1890856ea928SPeter Avalos 		mode = dirattrib->perm & 01777;
189150a69bb5SSascha Wildner 		tmpmode = mode | (S_IWUSR|S_IXUSR);
189250a69bb5SSascha Wildner 	} else {
1893*ee116499SAntonio Huete Jimenez 		debug("download remote \"%s\": server "
1894*ee116499SAntonio Huete Jimenez 		    "did not send permissions", dst);
1895856ea928SPeter Avalos 	}
1896856ea928SPeter Avalos 
189750a69bb5SSascha Wildner 	if (mkdir(dst, tmpmode) == -1 && errno != EEXIST) {
1898856ea928SPeter Avalos 		error("mkdir %s: %s", dst, strerror(errno));
1899856ea928SPeter Avalos 		return -1;
1900856ea928SPeter Avalos 	}
1901856ea928SPeter Avalos 
1902856ea928SPeter Avalos 	if (do_readdir(conn, src, &dir_entries) == -1) {
1903*ee116499SAntonio Huete Jimenez 		error("remote readdir \"%s\" failed", src);
1904856ea928SPeter Avalos 		return -1;
1905856ea928SPeter Avalos 	}
1906856ea928SPeter Avalos 
1907856ea928SPeter Avalos 	for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
1908664f4763Szrj 		free(new_dst);
1909664f4763Szrj 		free(new_src);
1910856ea928SPeter Avalos 
1911664f4763Szrj 		filename = dir_entries[i]->filename;
1912856ea928SPeter Avalos 		new_dst = path_append(dst, filename);
1913856ea928SPeter Avalos 		new_src = path_append(src, filename);
1914856ea928SPeter Avalos 
1915856ea928SPeter Avalos 		if (S_ISDIR(dir_entries[i]->a.perm)) {
1916856ea928SPeter Avalos 			if (strcmp(filename, ".") == 0 ||
1917856ea928SPeter Avalos 			    strcmp(filename, "..") == 0)
1918856ea928SPeter Avalos 				continue;
1919856ea928SPeter Avalos 			if (download_dir_internal(conn, new_src, new_dst,
192036e94dc5SPeter Avalos 			    depth + 1, &(dir_entries[i]->a), preserve_flag,
192150a69bb5SSascha Wildner 			    print_flag, resume_flag,
1922*ee116499SAntonio Huete Jimenez 			    fsync_flag, follow_link_flag, inplace_flag) == -1)
1923856ea928SPeter Avalos 				ret = -1;
192450a69bb5SSascha Wildner 		} else if (S_ISREG(dir_entries[i]->a.perm) ||
192550a69bb5SSascha Wildner 		    (follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) {
192650a69bb5SSascha Wildner 			/*
192750a69bb5SSascha Wildner 			 * If this is a symlink then don't send the link's
192850a69bb5SSascha Wildner 			 * Attrib. do_download() will do a FXP_STAT operation
192950a69bb5SSascha Wildner 			 * and get the link target's attributes.
193050a69bb5SSascha Wildner 			 */
1931856ea928SPeter Avalos 			if (do_download(conn, new_src, new_dst,
193250a69bb5SSascha Wildner 			    S_ISLNK(dir_entries[i]->a.perm) ? NULL :
193350a69bb5SSascha Wildner 			    &(dir_entries[i]->a),
1934*ee116499SAntonio Huete Jimenez 			    preserve_flag, resume_flag, fsync_flag,
1935*ee116499SAntonio Huete Jimenez 			    inplace_flag) == -1) {
1936856ea928SPeter Avalos 				error("Download of file %s to %s failed",
1937856ea928SPeter Avalos 				    new_src, new_dst);
1938856ea928SPeter Avalos 				ret = -1;
1939856ea928SPeter Avalos 			}
1940856ea928SPeter Avalos 		} else
1941*ee116499SAntonio Huete Jimenez 			logit("download \"%s\": not a regular file", new_src);
1942856ea928SPeter Avalos 
1943664f4763Szrj 	}
194436e94dc5SPeter Avalos 	free(new_dst);
194536e94dc5SPeter Avalos 	free(new_src);
1946856ea928SPeter Avalos 
194736e94dc5SPeter Avalos 	if (preserve_flag) {
1948856ea928SPeter Avalos 		if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1949856ea928SPeter Avalos 			struct timeval tv[2];
1950856ea928SPeter Avalos 			tv[0].tv_sec = dirattrib->atime;
1951856ea928SPeter Avalos 			tv[1].tv_sec = dirattrib->mtime;
1952856ea928SPeter Avalos 			tv[0].tv_usec = tv[1].tv_usec = 0;
1953856ea928SPeter Avalos 			if (utimes(dst, tv) == -1)
1954*ee116499SAntonio Huete Jimenez 				error("local set times on \"%s\": %s",
1955856ea928SPeter Avalos 				    dst, strerror(errno));
1956856ea928SPeter Avalos 		} else
1957856ea928SPeter Avalos 			debug("Server did not send times for directory "
1958856ea928SPeter Avalos 			    "\"%s\"", dst);
1959856ea928SPeter Avalos 	}
1960856ea928SPeter Avalos 
196150a69bb5SSascha Wildner 	if (mode != tmpmode && chmod(dst, mode) == -1)
1962*ee116499SAntonio Huete Jimenez 		error("local chmod directory \"%s\": %s", dst,
196350a69bb5SSascha Wildner 		    strerror(errno));
196450a69bb5SSascha Wildner 
1965856ea928SPeter Avalos 	free_sftp_dirents(dir_entries);
1966856ea928SPeter Avalos 
1967856ea928SPeter Avalos 	return ret;
1968856ea928SPeter Avalos }
1969856ea928SPeter Avalos 
1970856ea928SPeter Avalos int
download_dir(struct sftp_conn * conn,const char * src,const char * dst,Attrib * dirattrib,int preserve_flag,int print_flag,int resume_flag,int fsync_flag,int follow_link_flag,int inplace_flag)1971e9778795SPeter Avalos download_dir(struct sftp_conn *conn, const char *src, const char *dst,
1972e9778795SPeter Avalos     Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag,
1973*ee116499SAntonio Huete Jimenez     int fsync_flag, int follow_link_flag, int inplace_flag)
1974856ea928SPeter Avalos {
1975856ea928SPeter Avalos 	char *src_canon;
1976856ea928SPeter Avalos 	int ret;
1977856ea928SPeter Avalos 
1978856ea928SPeter Avalos 	if ((src_canon = do_realpath(conn, src)) == NULL) {
1979*ee116499SAntonio Huete Jimenez 		error("download \"%s\": path canonicalization failed", src);
1980856ea928SPeter Avalos 		return -1;
1981856ea928SPeter Avalos 	}
1982856ea928SPeter Avalos 
198336e94dc5SPeter Avalos 	ret = download_dir_internal(conn, src_canon, dst, 0,
198450a69bb5SSascha Wildner 	    dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag,
1985*ee116499SAntonio Huete Jimenez 	    follow_link_flag, inplace_flag);
198636e94dc5SPeter Avalos 	free(src_canon);
1987856ea928SPeter Avalos 	return ret;
1988856ea928SPeter Avalos }
1989856ea928SPeter Avalos 
199018de8d7fSPeter Avalos int
do_upload(struct sftp_conn * conn,const char * local_path,const char * remote_path,int preserve_flag,int resume,int fsync_flag,int inplace_flag)1991e9778795SPeter Avalos do_upload(struct sftp_conn *conn, const char *local_path,
1992*ee116499SAntonio Huete Jimenez     const char *remote_path, int preserve_flag, int resume,
1993*ee116499SAntonio Huete Jimenez     int fsync_flag, int inplace_flag)
199418de8d7fSPeter Avalos {
1995e9778795SPeter Avalos 	int r, local_fd;
1996*ee116499SAntonio Huete Jimenez 	u_int openmode, id, status = SSH2_FX_OK, reordered = 0;
199736e94dc5SPeter Avalos 	off_t offset, progress_counter;
1998*ee116499SAntonio Huete Jimenez 	u_char type, *handle, *data;
1999e9778795SPeter Avalos 	struct sshbuf *msg;
200018de8d7fSPeter Avalos 	struct stat sb;
2001*ee116499SAntonio Huete Jimenez 	Attrib a, t, *c = NULL;
2002*ee116499SAntonio Huete Jimenez 	u_int32_t startid, ackid;
2003*ee116499SAntonio Huete Jimenez 	u_int64_t highwater = 0;
200450a69bb5SSascha Wildner 	struct request *ack = NULL;
200550a69bb5SSascha Wildner 	struct requests acks;
2006e9778795SPeter Avalos 	size_t handle_len;
200718de8d7fSPeter Avalos 
2008*ee116499SAntonio Huete Jimenez 	debug2_f("upload local \"%s\" to remote \"%s\"",
2009*ee116499SAntonio Huete Jimenez 	    local_path, remote_path);
2010*ee116499SAntonio Huete Jimenez 
201118de8d7fSPeter Avalos 	TAILQ_INIT(&acks);
201218de8d7fSPeter Avalos 
2013*ee116499SAntonio Huete Jimenez 	if ((local_fd = open(local_path, O_RDONLY)) == -1) {
2014*ee116499SAntonio Huete Jimenez 		error("open local \"%s\": %s", local_path, strerror(errno));
201518de8d7fSPeter Avalos 		return(-1);
201618de8d7fSPeter Avalos 	}
201718de8d7fSPeter Avalos 	if (fstat(local_fd, &sb) == -1) {
2018*ee116499SAntonio Huete Jimenez 		error("fstat local \"%s\": %s", local_path, strerror(errno));
201918de8d7fSPeter Avalos 		close(local_fd);
202018de8d7fSPeter Avalos 		return(-1);
202118de8d7fSPeter Avalos 	}
202218de8d7fSPeter Avalos 	if (!S_ISREG(sb.st_mode)) {
2023*ee116499SAntonio Huete Jimenez 		error("local \"%s\" is not a regular file", local_path);
202418de8d7fSPeter Avalos 		close(local_fd);
202518de8d7fSPeter Avalos 		return(-1);
202618de8d7fSPeter Avalos 	}
202718de8d7fSPeter Avalos 	stat_to_attrib(&sb, &a);
202818de8d7fSPeter Avalos 
202918de8d7fSPeter Avalos 	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
203018de8d7fSPeter Avalos 	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
203118de8d7fSPeter Avalos 	a.perm &= 0777;
203236e94dc5SPeter Avalos 	if (!preserve_flag)
203318de8d7fSPeter Avalos 		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
203418de8d7fSPeter Avalos 
203536e94dc5SPeter Avalos 	if (resume) {
203636e94dc5SPeter Avalos 		/* Get remote file size if it exists */
203736e94dc5SPeter Avalos 		if ((c = do_stat(conn, remote_path, 0)) == NULL) {
203836e94dc5SPeter Avalos 			close(local_fd);
203936e94dc5SPeter Avalos 			return -1;
204036e94dc5SPeter Avalos 		}
204136e94dc5SPeter Avalos 
204236e94dc5SPeter Avalos 		if ((off_t)c->size >= sb.st_size) {
2043*ee116499SAntonio Huete Jimenez 			error("resume \"%s\": destination file "
2044*ee116499SAntonio Huete Jimenez 			    "same size or larger", local_path);
204536e94dc5SPeter Avalos 			close(local_fd);
204636e94dc5SPeter Avalos 			return -1;
204736e94dc5SPeter Avalos 		}
204836e94dc5SPeter Avalos 
204936e94dc5SPeter Avalos 		if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) {
205036e94dc5SPeter Avalos 			close(local_fd);
205136e94dc5SPeter Avalos 			return -1;
205236e94dc5SPeter Avalos 		}
205336e94dc5SPeter Avalos 	}
205436e94dc5SPeter Avalos 
2055*ee116499SAntonio Huete Jimenez 	openmode = SSH2_FXF_WRITE|SSH2_FXF_CREAT;
2056*ee116499SAntonio Huete Jimenez 	if (resume)
2057*ee116499SAntonio Huete Jimenez 		openmode |= SSH2_FXF_APPEND;
2058*ee116499SAntonio Huete Jimenez 	else if (!inplace_flag)
2059*ee116499SAntonio Huete Jimenez 		openmode |= SSH2_FXF_TRUNC;
2060*ee116499SAntonio Huete Jimenez 
206118de8d7fSPeter Avalos 	/* Send open request */
2062*ee116499SAntonio Huete Jimenez 	if (send_open(conn, remote_path, "dest", openmode, &a,
2063*ee116499SAntonio Huete Jimenez 	    &handle, &handle_len) != 0) {
206418de8d7fSPeter Avalos 		close(local_fd);
206518de8d7fSPeter Avalos 		return -1;
206618de8d7fSPeter Avalos 	}
206718de8d7fSPeter Avalos 
206850a69bb5SSascha Wildner 	id = conn->msg_id;
206918de8d7fSPeter Avalos 	startid = ackid = id + 1;
207050a69bb5SSascha Wildner 	data = xmalloc(conn->upload_buflen);
207118de8d7fSPeter Avalos 
207218de8d7fSPeter Avalos 	/* Read from local and write to remote */
207336e94dc5SPeter Avalos 	offset = progress_counter = (resume ? c->size : 0);
207450a69bb5SSascha Wildner 	if (showprogress) {
207550a69bb5SSascha Wildner 		start_progress_meter(progress_meter_path(local_path),
207650a69bb5SSascha Wildner 		    sb.st_size, &progress_counter);
207750a69bb5SSascha Wildner 	}
207818de8d7fSPeter Avalos 
207950a69bb5SSascha Wildner 	if ((msg = sshbuf_new()) == NULL)
208050a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
208118de8d7fSPeter Avalos 	for (;;) {
208218de8d7fSPeter Avalos 		int len;
208318de8d7fSPeter Avalos 
208418de8d7fSPeter Avalos 		/*
208518de8d7fSPeter Avalos 		 * Can't use atomicio here because it returns 0 on EOF,
208618de8d7fSPeter Avalos 		 * thus losing the last block of the file.
208718de8d7fSPeter Avalos 		 * Simulate an EOF on interrupt, allowing ACKs from the
208818de8d7fSPeter Avalos 		 * server to drain.
208918de8d7fSPeter Avalos 		 */
209018de8d7fSPeter Avalos 		if (interrupted || status != SSH2_FX_OK)
209118de8d7fSPeter Avalos 			len = 0;
209218de8d7fSPeter Avalos 		else do
209350a69bb5SSascha Wildner 			len = read(local_fd, data, conn->upload_buflen);
209418de8d7fSPeter Avalos 		while ((len == -1) &&
209518de8d7fSPeter Avalos 		    (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
209618de8d7fSPeter Avalos 
2097*ee116499SAntonio Huete Jimenez 		if (len == -1) {
2098*ee116499SAntonio Huete Jimenez 			fatal("read local \"%s\": %s",
2099*ee116499SAntonio Huete Jimenez 			    local_path, strerror(errno));
2100*ee116499SAntonio Huete Jimenez 		} else if (len != 0) {
210150a69bb5SSascha Wildner 			ack = request_enqueue(&acks, ++id, len, offset);
2102e9778795SPeter Avalos 			sshbuf_reset(msg);
2103e9778795SPeter Avalos 			if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
2104e9778795SPeter Avalos 			    (r = sshbuf_put_u32(msg, ack->id)) != 0 ||
2105e9778795SPeter Avalos 			    (r = sshbuf_put_string(msg, handle,
2106e9778795SPeter Avalos 			    handle_len)) != 0 ||
2107e9778795SPeter Avalos 			    (r = sshbuf_put_u64(msg, offset)) != 0 ||
2108e9778795SPeter Avalos 			    (r = sshbuf_put_string(msg, data, len)) != 0)
210950a69bb5SSascha Wildner 				fatal_fr(r, "compose");
2110e9778795SPeter Avalos 			send_msg(conn, msg);
211118de8d7fSPeter Avalos 			debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
211218de8d7fSPeter Avalos 			    id, (unsigned long long)offset, len);
211318de8d7fSPeter Avalos 		} else if (TAILQ_FIRST(&acks) == NULL)
211418de8d7fSPeter Avalos 			break;
211518de8d7fSPeter Avalos 
211618de8d7fSPeter Avalos 		if (ack == NULL)
211718de8d7fSPeter Avalos 			fatal("Unexpected ACK %u", id);
211818de8d7fSPeter Avalos 
211918de8d7fSPeter Avalos 		if (id == startid || len == 0 ||
212018de8d7fSPeter Avalos 		    id - ackid >= conn->num_requests) {
2121e9778795SPeter Avalos 			u_int rid;
212218de8d7fSPeter Avalos 
2123e9778795SPeter Avalos 			sshbuf_reset(msg);
2124e9778795SPeter Avalos 			get_msg(conn, msg);
2125e9778795SPeter Avalos 			if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
2126e9778795SPeter Avalos 			    (r = sshbuf_get_u32(msg, &rid)) != 0)
212750a69bb5SSascha Wildner 				fatal_fr(r, "parse");
212818de8d7fSPeter Avalos 
212918de8d7fSPeter Avalos 			if (type != SSH2_FXP_STATUS)
213018de8d7fSPeter Avalos 				fatal("Expected SSH2_FXP_STATUS(%d) packet, "
213118de8d7fSPeter Avalos 				    "got %d", SSH2_FXP_STATUS, type);
213218de8d7fSPeter Avalos 
2133e9778795SPeter Avalos 			if ((r = sshbuf_get_u32(msg, &status)) != 0)
213450a69bb5SSascha Wildner 				fatal_fr(r, "parse status");
2135e9778795SPeter Avalos 			debug3("SSH2_FXP_STATUS %u", status);
213618de8d7fSPeter Avalos 
213718de8d7fSPeter Avalos 			/* Find the request in our queue */
213850a69bb5SSascha Wildner 			if ((ack = request_find(&acks, rid)) == NULL)
2139e9778795SPeter Avalos 				fatal("Can't find request for ID %u", rid);
214018de8d7fSPeter Avalos 			TAILQ_REMOVE(&acks, ack, tq);
214150a69bb5SSascha Wildner 			debug3("In write loop, ack for %u %zu bytes at %lld",
214250a69bb5SSascha Wildner 			    ack->id, ack->len, (unsigned long long)ack->offset);
214318de8d7fSPeter Avalos 			++ackid;
214436e94dc5SPeter Avalos 			progress_counter += ack->len;
2145*ee116499SAntonio Huete Jimenez 			if (!reordered && ack->offset <= highwater)
2146*ee116499SAntonio Huete Jimenez 				highwater = ack->offset + ack->len;
2147*ee116499SAntonio Huete Jimenez 			else if (!reordered && ack->offset > highwater) {
2148*ee116499SAntonio Huete Jimenez 				debug3_f("server reordered ACKs");
2149*ee116499SAntonio Huete Jimenez 				reordered = 1;
2150*ee116499SAntonio Huete Jimenez 			}
215136e94dc5SPeter Avalos 			free(ack);
215218de8d7fSPeter Avalos 		}
215318de8d7fSPeter Avalos 		offset += len;
215418de8d7fSPeter Avalos 		if (offset < 0)
215550a69bb5SSascha Wildner 			fatal_f("offset < 0");
215618de8d7fSPeter Avalos 	}
2157e9778795SPeter Avalos 	sshbuf_free(msg);
215818de8d7fSPeter Avalos 
215918de8d7fSPeter Avalos 	if (showprogress)
216018de8d7fSPeter Avalos 		stop_progress_meter();
216136e94dc5SPeter Avalos 	free(data);
216218de8d7fSPeter Avalos 
216318de8d7fSPeter Avalos 	if (status != SSH2_FX_OK) {
2164*ee116499SAntonio Huete Jimenez 		error("write remote \"%s\": %s", remote_path, fx2txt(status));
2165e9778795SPeter Avalos 		status = SSH2_FX_FAILURE;
216618de8d7fSPeter Avalos 	}
216718de8d7fSPeter Avalos 
2168*ee116499SAntonio Huete Jimenez 	if (inplace_flag || (resume && (status != SSH2_FX_OK || interrupted))) {
2169*ee116499SAntonio Huete Jimenez 		debug("truncating at %llu", (unsigned long long)highwater);
2170*ee116499SAntonio Huete Jimenez 		attrib_clear(&t);
2171*ee116499SAntonio Huete Jimenez 		t.flags = SSH2_FILEXFER_ATTR_SIZE;
2172*ee116499SAntonio Huete Jimenez 		t.size = highwater;
2173*ee116499SAntonio Huete Jimenez 		do_fsetstat(conn, handle, handle_len, &t);
2174*ee116499SAntonio Huete Jimenez 	}
2175*ee116499SAntonio Huete Jimenez 
217618de8d7fSPeter Avalos 	if (close(local_fd) == -1) {
2177*ee116499SAntonio Huete Jimenez 		error("close local \"%s\": %s", local_path, strerror(errno));
2178e9778795SPeter Avalos 		status = SSH2_FX_FAILURE;
217918de8d7fSPeter Avalos 	}
218018de8d7fSPeter Avalos 
218118de8d7fSPeter Avalos 	/* Override umask and utimes if asked */
218236e94dc5SPeter Avalos 	if (preserve_flag)
218318de8d7fSPeter Avalos 		do_fsetstat(conn, handle, handle_len, &a);
218418de8d7fSPeter Avalos 
218536e94dc5SPeter Avalos 	if (fsync_flag)
218636e94dc5SPeter Avalos 		(void)do_fsync(conn, handle, handle_len);
218736e94dc5SPeter Avalos 
2188e9778795SPeter Avalos 	if (do_close(conn, handle, handle_len) != 0)
2189e9778795SPeter Avalos 		status = SSH2_FX_FAILURE;
2190e9778795SPeter Avalos 
219136e94dc5SPeter Avalos 	free(handle);
219218de8d7fSPeter Avalos 
2193e9778795SPeter Avalos 	return status == SSH2_FX_OK ? 0 : -1;
219418de8d7fSPeter Avalos }
2195856ea928SPeter Avalos 
2196856ea928SPeter Avalos static int
upload_dir_internal(struct sftp_conn * conn,const char * src,const char * dst,int depth,int preserve_flag,int print_flag,int resume,int fsync_flag,int follow_link_flag,int inplace_flag)2197e9778795SPeter Avalos upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
219850a69bb5SSascha Wildner     int depth, int preserve_flag, int print_flag, int resume, int fsync_flag,
2199*ee116499SAntonio Huete Jimenez     int follow_link_flag, int inplace_flag)
2200856ea928SPeter Avalos {
2201e9778795SPeter Avalos 	int ret = 0;
2202856ea928SPeter Avalos 	DIR *dirp;
2203856ea928SPeter Avalos 	struct dirent *dp;
2204664f4763Szrj 	char *filename, *new_src = NULL, *new_dst = NULL;
2205856ea928SPeter Avalos 	struct stat sb;
2206e9778795SPeter Avalos 	Attrib a, *dirattrib;
220750a69bb5SSascha Wildner 	u_int32_t saved_perm;
2208856ea928SPeter Avalos 
2209*ee116499SAntonio Huete Jimenez 	debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst);
2210*ee116499SAntonio Huete Jimenez 
2211856ea928SPeter Avalos 	if (depth >= MAX_DIR_DEPTH) {
2212856ea928SPeter Avalos 		error("Maximum directory depth exceeded: %d levels", depth);
2213856ea928SPeter Avalos 		return -1;
2214856ea928SPeter Avalos 	}
2215856ea928SPeter Avalos 
2216856ea928SPeter Avalos 	if (stat(src, &sb) == -1) {
2217*ee116499SAntonio Huete Jimenez 		error("stat local \"%s\": %s", src, strerror(errno));
2218856ea928SPeter Avalos 		return -1;
2219856ea928SPeter Avalos 	}
2220856ea928SPeter Avalos 	if (!S_ISDIR(sb.st_mode)) {
2221856ea928SPeter Avalos 		error("\"%s\" is not a directory", src);
2222856ea928SPeter Avalos 		return -1;
2223856ea928SPeter Avalos 	}
222450a69bb5SSascha Wildner 	if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
2225e9778795SPeter Avalos 		mprintf("Entering %s\n", src);
2226856ea928SPeter Avalos 
2227856ea928SPeter Avalos 	stat_to_attrib(&sb, &a);
2228856ea928SPeter Avalos 	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
2229856ea928SPeter Avalos 	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
2230856ea928SPeter Avalos 	a.perm &= 01777;
223136e94dc5SPeter Avalos 	if (!preserve_flag)
2232856ea928SPeter Avalos 		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
2233856ea928SPeter Avalos 
2234856ea928SPeter Avalos 	/*
2235e9778795SPeter Avalos 	 * sftp lacks a portable status value to match errno EEXIST,
2236e9778795SPeter Avalos 	 * so if we get a failure back then we must check whether
223750a69bb5SSascha Wildner 	 * the path already existed and is a directory.  Ensure we can
223850a69bb5SSascha Wildner 	 * write to the directory we create for the duration of the transfer.
2239856ea928SPeter Avalos 	 */
224050a69bb5SSascha Wildner 	saved_perm = a.perm;
224150a69bb5SSascha Wildner 	a.perm |= (S_IWUSR|S_IXUSR);
2242e9778795SPeter Avalos 	if (do_mkdir(conn, dst, &a, 0) != 0) {
2243e9778795SPeter Avalos 		if ((dirattrib = do_stat(conn, dst, 0)) == NULL)
2244856ea928SPeter Avalos 			return -1;
2245e9778795SPeter Avalos 		if (!S_ISDIR(dirattrib->perm)) {
2246e9778795SPeter Avalos 			error("\"%s\" exists but is not a directory", dst);
2247856ea928SPeter Avalos 			return -1;
2248856ea928SPeter Avalos 		}
2249e9778795SPeter Avalos 	}
225050a69bb5SSascha Wildner 	a.perm = saved_perm;
2251856ea928SPeter Avalos 
2252856ea928SPeter Avalos 	if ((dirp = opendir(src)) == NULL) {
2253*ee116499SAntonio Huete Jimenez 		error("local opendir \"%s\": %s", src, strerror(errno));
2254856ea928SPeter Avalos 		return -1;
2255856ea928SPeter Avalos 	}
2256856ea928SPeter Avalos 
2257856ea928SPeter Avalos 	while (((dp = readdir(dirp)) != NULL) && !interrupted) {
2258856ea928SPeter Avalos 		if (dp->d_ino == 0)
2259856ea928SPeter Avalos 			continue;
2260664f4763Szrj 		free(new_dst);
2261664f4763Szrj 		free(new_src);
2262856ea928SPeter Avalos 		filename = dp->d_name;
2263856ea928SPeter Avalos 		new_dst = path_append(dst, filename);
2264856ea928SPeter Avalos 		new_src = path_append(src, filename);
2265856ea928SPeter Avalos 
2266856ea928SPeter Avalos 		if (lstat(new_src, &sb) == -1) {
2267*ee116499SAntonio Huete Jimenez 			logit("local lstat \"%s\": %s", filename,
2268856ea928SPeter Avalos 			    strerror(errno));
2269856ea928SPeter Avalos 			ret = -1;
2270856ea928SPeter Avalos 		} else if (S_ISDIR(sb.st_mode)) {
2271856ea928SPeter Avalos 			if (strcmp(filename, ".") == 0 ||
2272856ea928SPeter Avalos 			    strcmp(filename, "..") == 0)
2273856ea928SPeter Avalos 				continue;
2274856ea928SPeter Avalos 
2275856ea928SPeter Avalos 			if (upload_dir_internal(conn, new_src, new_dst,
227636e94dc5SPeter Avalos 			    depth + 1, preserve_flag, print_flag, resume,
2277*ee116499SAntonio Huete Jimenez 			    fsync_flag, follow_link_flag, inplace_flag) == -1)
2278856ea928SPeter Avalos 				ret = -1;
227950a69bb5SSascha Wildner 		} else if (S_ISREG(sb.st_mode) ||
228050a69bb5SSascha Wildner 		    (follow_link_flag && S_ISLNK(sb.st_mode))) {
228136e94dc5SPeter Avalos 			if (do_upload(conn, new_src, new_dst,
2282*ee116499SAntonio Huete Jimenez 			    preserve_flag, resume, fsync_flag,
2283*ee116499SAntonio Huete Jimenez 			    inplace_flag) == -1) {
2284*ee116499SAntonio Huete Jimenez 				error("upload \"%s\" to \"%s\" failed",
2285856ea928SPeter Avalos 				    new_src, new_dst);
2286856ea928SPeter Avalos 				ret = -1;
2287856ea928SPeter Avalos 			}
2288856ea928SPeter Avalos 		} else
2289*ee116499SAntonio Huete Jimenez 			logit("%s: not a regular file", filename);
2290664f4763Szrj 	}
229136e94dc5SPeter Avalos 	free(new_dst);
229236e94dc5SPeter Avalos 	free(new_src);
2293856ea928SPeter Avalos 
2294856ea928SPeter Avalos 	do_setstat(conn, dst, &a);
2295856ea928SPeter Avalos 
2296856ea928SPeter Avalos 	(void) closedir(dirp);
2297856ea928SPeter Avalos 	return ret;
2298856ea928SPeter Avalos }
2299856ea928SPeter Avalos 
2300856ea928SPeter Avalos int
upload_dir(struct sftp_conn * conn,const char * src,const char * dst,int preserve_flag,int print_flag,int resume,int fsync_flag,int follow_link_flag,int inplace_flag)2301e9778795SPeter Avalos upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
230250a69bb5SSascha Wildner     int preserve_flag, int print_flag, int resume, int fsync_flag,
2303*ee116499SAntonio Huete Jimenez     int follow_link_flag, int inplace_flag)
2304856ea928SPeter Avalos {
2305856ea928SPeter Avalos 	char *dst_canon;
2306856ea928SPeter Avalos 	int ret;
2307856ea928SPeter Avalos 
2308856ea928SPeter Avalos 	if ((dst_canon = do_realpath(conn, dst)) == NULL) {
2309*ee116499SAntonio Huete Jimenez 		error("upload \"%s\": path canonicalization failed", dst);
2310856ea928SPeter Avalos 		return -1;
2311856ea928SPeter Avalos 	}
2312856ea928SPeter Avalos 
231336e94dc5SPeter Avalos 	ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag,
2314*ee116499SAntonio Huete Jimenez 	    print_flag, resume, fsync_flag, follow_link_flag, inplace_flag);
231536e94dc5SPeter Avalos 
231636e94dc5SPeter Avalos 	free(dst_canon);
2317856ea928SPeter Avalos 	return ret;
2318856ea928SPeter Avalos }
2319856ea928SPeter Avalos 
232050a69bb5SSascha Wildner static void
handle_dest_replies(struct sftp_conn * to,const char * to_path,int synchronous,u_int * nreqsp,u_int * write_errorp)232150a69bb5SSascha Wildner handle_dest_replies(struct sftp_conn *to, const char *to_path, int synchronous,
232250a69bb5SSascha Wildner     u_int *nreqsp, u_int *write_errorp)
232350a69bb5SSascha Wildner {
232450a69bb5SSascha Wildner 	struct sshbuf *msg;
232550a69bb5SSascha Wildner 	u_char type;
232650a69bb5SSascha Wildner 	u_int id, status;
232750a69bb5SSascha Wildner 	int r;
232850a69bb5SSascha Wildner 	struct pollfd pfd;
232950a69bb5SSascha Wildner 
233050a69bb5SSascha Wildner 	if ((msg = sshbuf_new()) == NULL)
233150a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
233250a69bb5SSascha Wildner 
233350a69bb5SSascha Wildner 	/* Try to eat replies from the upload side */
233450a69bb5SSascha Wildner 	while (*nreqsp > 0) {
233550a69bb5SSascha Wildner 		debug3_f("%u outstanding replies", *nreqsp);
233650a69bb5SSascha Wildner 		if (!synchronous) {
233750a69bb5SSascha Wildner 			/* Bail out if no data is ready to be read */
233850a69bb5SSascha Wildner 			pfd.fd = to->fd_in;
233950a69bb5SSascha Wildner 			pfd.events = POLLIN;
234050a69bb5SSascha Wildner 			if ((r = poll(&pfd, 1, 0)) == -1) {
234150a69bb5SSascha Wildner 				if (errno == EINTR)
234250a69bb5SSascha Wildner 					break;
234350a69bb5SSascha Wildner 				fatal_f("poll: %s", strerror(errno));
234450a69bb5SSascha Wildner 			} else if (r == 0)
234550a69bb5SSascha Wildner 				break; /* fd not ready */
234650a69bb5SSascha Wildner 		}
234750a69bb5SSascha Wildner 		sshbuf_reset(msg);
234850a69bb5SSascha Wildner 		get_msg(to, msg);
234950a69bb5SSascha Wildner 
235050a69bb5SSascha Wildner 		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
235150a69bb5SSascha Wildner 		    (r = sshbuf_get_u32(msg, &id)) != 0)
235250a69bb5SSascha Wildner 			fatal_fr(r, "dest parse");
235350a69bb5SSascha Wildner 		debug3("Received dest reply T:%u I:%u R:%u", type, id, *nreqsp);
235450a69bb5SSascha Wildner 		if (type != SSH2_FXP_STATUS) {
235550a69bb5SSascha Wildner 			fatal_f("Expected SSH2_FXP_STATUS(%d) packet, got %d",
235650a69bb5SSascha Wildner 			    SSH2_FXP_STATUS, type);
235750a69bb5SSascha Wildner 		}
235850a69bb5SSascha Wildner 		if ((r = sshbuf_get_u32(msg, &status)) != 0)
235950a69bb5SSascha Wildner 			fatal_fr(r, "parse dest status");
236050a69bb5SSascha Wildner 		debug3("dest SSH2_FXP_STATUS %u", status);
236150a69bb5SSascha Wildner 		if (status != SSH2_FX_OK) {
236250a69bb5SSascha Wildner 			/* record first error */
236350a69bb5SSascha Wildner 			if (*write_errorp == 0)
236450a69bb5SSascha Wildner 				*write_errorp = status;
236550a69bb5SSascha Wildner 		}
236650a69bb5SSascha Wildner 		/*
236750a69bb5SSascha Wildner 		 * XXX this doesn't do full reply matching like do_upload and
236850a69bb5SSascha Wildner 		 * so cannot gracefully truncate terminated uploads at a
236950a69bb5SSascha Wildner 		 * high-water mark. ATM the only caller of this function (scp)
237050a69bb5SSascha Wildner 		 * doesn't support transfer resumption, so this doesn't matter
237150a69bb5SSascha Wildner 		 * a whole lot.
237250a69bb5SSascha Wildner 		 *
237350a69bb5SSascha Wildner 		 * To be safe, do_crossload truncates the destination file to
237450a69bb5SSascha Wildner 		 * zero length on upload failure, since we can't trust the
237550a69bb5SSascha Wildner 		 * server not to have reordered replies that could have
237650a69bb5SSascha Wildner 		 * inserted holes where none existed in the source file.
237750a69bb5SSascha Wildner 		 *
237850a69bb5SSascha Wildner 		 * XXX we could get a more accutate progress bar if we updated
237950a69bb5SSascha Wildner 		 * the counter based on the reply from the destination...
238050a69bb5SSascha Wildner 		 */
238150a69bb5SSascha Wildner 		(*nreqsp)--;
238250a69bb5SSascha Wildner 	}
238350a69bb5SSascha Wildner 	debug3_f("done: %u outstanding replies", *nreqsp);
2384*ee116499SAntonio Huete Jimenez 	sshbuf_free(msg);
238550a69bb5SSascha Wildner }
238650a69bb5SSascha Wildner 
238750a69bb5SSascha Wildner int
do_crossload(struct sftp_conn * from,struct sftp_conn * to,const char * from_path,const char * to_path,Attrib * a,int preserve_flag)238850a69bb5SSascha Wildner do_crossload(struct sftp_conn *from, struct sftp_conn *to,
238950a69bb5SSascha Wildner     const char *from_path, const char *to_path,
239050a69bb5SSascha Wildner     Attrib *a, int preserve_flag)
239150a69bb5SSascha Wildner {
239250a69bb5SSascha Wildner 	struct sshbuf *msg;
239350a69bb5SSascha Wildner 	int write_error, read_error, r;
239450a69bb5SSascha Wildner 	u_int64_t offset = 0, size;
239550a69bb5SSascha Wildner 	u_int id, buflen, num_req, max_req, status = SSH2_FX_OK;
239650a69bb5SSascha Wildner 	u_int num_upload_req;
239750a69bb5SSascha Wildner 	off_t progress_counter;
239850a69bb5SSascha Wildner 	u_char *from_handle, *to_handle;
239950a69bb5SSascha Wildner 	size_t from_handle_len, to_handle_len;
240050a69bb5SSascha Wildner 	struct requests requests;
240150a69bb5SSascha Wildner 	struct request *req;
240250a69bb5SSascha Wildner 	u_char type;
240350a69bb5SSascha Wildner 
2404*ee116499SAntonio Huete Jimenez 	debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path);
2405*ee116499SAntonio Huete Jimenez 
240650a69bb5SSascha Wildner 	TAILQ_INIT(&requests);
240750a69bb5SSascha Wildner 
240850a69bb5SSascha Wildner 	if (a == NULL && (a = do_stat(from, from_path, 0)) == NULL)
240950a69bb5SSascha Wildner 		return -1;
241050a69bb5SSascha Wildner 
241150a69bb5SSascha Wildner 	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
241250a69bb5SSascha Wildner 	    (!S_ISREG(a->perm))) {
2413*ee116499SAntonio Huete Jimenez 		error("download \"%s\": not a regular file", from_path);
241450a69bb5SSascha Wildner 		return(-1);
241550a69bb5SSascha Wildner 	}
241650a69bb5SSascha Wildner 	if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
241750a69bb5SSascha Wildner 		size = a->size;
241850a69bb5SSascha Wildner 	else
241950a69bb5SSascha Wildner 		size = 0;
242050a69bb5SSascha Wildner 
242150a69bb5SSascha Wildner 	buflen = from->download_buflen;
242250a69bb5SSascha Wildner 	if (buflen > to->upload_buflen)
242350a69bb5SSascha Wildner 		buflen = to->upload_buflen;
242450a69bb5SSascha Wildner 
242550a69bb5SSascha Wildner 	/* Send open request to read side */
242650a69bb5SSascha Wildner 	if (send_open(from, from_path, "origin", SSH2_FXF_READ, NULL,
242750a69bb5SSascha Wildner 	    &from_handle, &from_handle_len) != 0)
242850a69bb5SSascha Wildner 		return -1;
242950a69bb5SSascha Wildner 
243050a69bb5SSascha Wildner 	/* Send open request to write side */
243150a69bb5SSascha Wildner 	a->flags &= ~SSH2_FILEXFER_ATTR_SIZE;
243250a69bb5SSascha Wildner 	a->flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
243350a69bb5SSascha Wildner 	a->perm &= 0777;
243450a69bb5SSascha Wildner 	if (!preserve_flag)
243550a69bb5SSascha Wildner 		a->flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
243650a69bb5SSascha Wildner 	if (send_open(to, to_path, "dest",
243750a69bb5SSascha Wildner 	    SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
243850a69bb5SSascha Wildner 	    &to_handle, &to_handle_len) != 0) {
243950a69bb5SSascha Wildner 		do_close(from, from_handle, from_handle_len);
244050a69bb5SSascha Wildner 		return -1;
244150a69bb5SSascha Wildner 	}
244250a69bb5SSascha Wildner 
244350a69bb5SSascha Wildner 	/* Read from remote "from" and write to remote "to" */
244450a69bb5SSascha Wildner 	offset = 0;
244550a69bb5SSascha Wildner 	write_error = read_error = num_req = num_upload_req = 0;
244650a69bb5SSascha Wildner 	max_req = 1;
244750a69bb5SSascha Wildner 	progress_counter = 0;
244850a69bb5SSascha Wildner 
244950a69bb5SSascha Wildner 	if (showprogress && size != 0) {
245050a69bb5SSascha Wildner 		start_progress_meter(progress_meter_path(from_path),
245150a69bb5SSascha Wildner 		    size, &progress_counter);
245250a69bb5SSascha Wildner 	}
245350a69bb5SSascha Wildner 	if ((msg = sshbuf_new()) == NULL)
245450a69bb5SSascha Wildner 		fatal_f("sshbuf_new failed");
245550a69bb5SSascha Wildner 	while (num_req > 0 || max_req > 0) {
245650a69bb5SSascha Wildner 		u_char *data;
245750a69bb5SSascha Wildner 		size_t len;
245850a69bb5SSascha Wildner 
245950a69bb5SSascha Wildner 		/*
246050a69bb5SSascha Wildner 		 * Simulate EOF on interrupt: stop sending new requests and
246150a69bb5SSascha Wildner 		 * allow outstanding requests to drain gracefully
246250a69bb5SSascha Wildner 		 */
246350a69bb5SSascha Wildner 		if (interrupted) {
246450a69bb5SSascha Wildner 			if (num_req == 0) /* If we haven't started yet... */
246550a69bb5SSascha Wildner 				break;
246650a69bb5SSascha Wildner 			max_req = 0;
246750a69bb5SSascha Wildner 		}
246850a69bb5SSascha Wildner 
246950a69bb5SSascha Wildner 		/* Send some more requests */
247050a69bb5SSascha Wildner 		while (num_req < max_req) {
247150a69bb5SSascha Wildner 			debug3("Request range %llu -> %llu (%d/%d)",
247250a69bb5SSascha Wildner 			    (unsigned long long)offset,
247350a69bb5SSascha Wildner 			    (unsigned long long)offset + buflen - 1,
247450a69bb5SSascha Wildner 			    num_req, max_req);
247550a69bb5SSascha Wildner 			req = request_enqueue(&requests, from->msg_id++,
247650a69bb5SSascha Wildner 			    buflen, offset);
247750a69bb5SSascha Wildner 			offset += buflen;
247850a69bb5SSascha Wildner 			num_req++;
247950a69bb5SSascha Wildner 			send_read_request(from, req->id, req->offset,
248050a69bb5SSascha Wildner 			    req->len, from_handle, from_handle_len);
248150a69bb5SSascha Wildner 		}
248250a69bb5SSascha Wildner 
248350a69bb5SSascha Wildner 		/* Try to eat replies from the upload side (nonblocking) */
248450a69bb5SSascha Wildner 		handle_dest_replies(to, to_path, 0,
248550a69bb5SSascha Wildner 		    &num_upload_req, &write_error);
248650a69bb5SSascha Wildner 
248750a69bb5SSascha Wildner 		sshbuf_reset(msg);
248850a69bb5SSascha Wildner 		get_msg(from, msg);
248950a69bb5SSascha Wildner 		if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
249050a69bb5SSascha Wildner 		    (r = sshbuf_get_u32(msg, &id)) != 0)
249150a69bb5SSascha Wildner 			fatal_fr(r, "parse");
249250a69bb5SSascha Wildner 		debug3("Received origin reply T:%u I:%u R:%d",
249350a69bb5SSascha Wildner 		    type, id, max_req);
249450a69bb5SSascha Wildner 
249550a69bb5SSascha Wildner 		/* Find the request in our queue */
249650a69bb5SSascha Wildner 		if ((req = request_find(&requests, id)) == NULL)
249750a69bb5SSascha Wildner 			fatal("Unexpected reply %u", id);
249850a69bb5SSascha Wildner 
249950a69bb5SSascha Wildner 		switch (type) {
250050a69bb5SSascha Wildner 		case SSH2_FXP_STATUS:
250150a69bb5SSascha Wildner 			if ((r = sshbuf_get_u32(msg, &status)) != 0)
250250a69bb5SSascha Wildner 				fatal_fr(r, "parse status");
250350a69bb5SSascha Wildner 			if (status != SSH2_FX_EOF)
250450a69bb5SSascha Wildner 				read_error = 1;
250550a69bb5SSascha Wildner 			max_req = 0;
250650a69bb5SSascha Wildner 			TAILQ_REMOVE(&requests, req, tq);
250750a69bb5SSascha Wildner 			free(req);
250850a69bb5SSascha Wildner 			num_req--;
250950a69bb5SSascha Wildner 			break;
251050a69bb5SSascha Wildner 		case SSH2_FXP_DATA:
251150a69bb5SSascha Wildner 			if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
251250a69bb5SSascha Wildner 				fatal_fr(r, "parse data");
251350a69bb5SSascha Wildner 			debug3("Received data %llu -> %llu",
251450a69bb5SSascha Wildner 			    (unsigned long long)req->offset,
251550a69bb5SSascha Wildner 			    (unsigned long long)req->offset + len - 1);
251650a69bb5SSascha Wildner 			if (len > req->len)
251750a69bb5SSascha Wildner 				fatal("Received more data than asked for "
251850a69bb5SSascha Wildner 				    "%zu > %zu", len, req->len);
251950a69bb5SSascha Wildner 
252050a69bb5SSascha Wildner 			/* Write this chunk out to the destination */
252150a69bb5SSascha Wildner 			sshbuf_reset(msg);
252250a69bb5SSascha Wildner 			if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
252350a69bb5SSascha Wildner 			    (r = sshbuf_put_u32(msg, to->msg_id++)) != 0 ||
252450a69bb5SSascha Wildner 			    (r = sshbuf_put_string(msg, to_handle,
252550a69bb5SSascha Wildner 			    to_handle_len)) != 0 ||
252650a69bb5SSascha Wildner 			    (r = sshbuf_put_u64(msg, req->offset)) != 0 ||
252750a69bb5SSascha Wildner 			    (r = sshbuf_put_string(msg, data, len)) != 0)
252850a69bb5SSascha Wildner 				fatal_fr(r, "compose write");
252950a69bb5SSascha Wildner 			send_msg(to, msg);
253050a69bb5SSascha Wildner 			debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%zu",
253150a69bb5SSascha Wildner 			    id, (unsigned long long)offset, len);
253250a69bb5SSascha Wildner 			num_upload_req++;
253350a69bb5SSascha Wildner 			progress_counter += len;
253450a69bb5SSascha Wildner 			free(data);
253550a69bb5SSascha Wildner 
253650a69bb5SSascha Wildner 			if (len == req->len) {
253750a69bb5SSascha Wildner 				TAILQ_REMOVE(&requests, req, tq);
253850a69bb5SSascha Wildner 				free(req);
253950a69bb5SSascha Wildner 				num_req--;
254050a69bb5SSascha Wildner 			} else {
254150a69bb5SSascha Wildner 				/* Resend the request for the missing data */
254250a69bb5SSascha Wildner 				debug3("Short data block, re-requesting "
254350a69bb5SSascha Wildner 				    "%llu -> %llu (%2d)",
254450a69bb5SSascha Wildner 				    (unsigned long long)req->offset + len,
254550a69bb5SSascha Wildner 				    (unsigned long long)req->offset +
254650a69bb5SSascha Wildner 				    req->len - 1, num_req);
254750a69bb5SSascha Wildner 				req->id = from->msg_id++;
254850a69bb5SSascha Wildner 				req->len -= len;
254950a69bb5SSascha Wildner 				req->offset += len;
255050a69bb5SSascha Wildner 				send_read_request(from, req->id,
255150a69bb5SSascha Wildner 				    req->offset, req->len,
255250a69bb5SSascha Wildner 				    from_handle, from_handle_len);
255350a69bb5SSascha Wildner 				/* Reduce the request size */
255450a69bb5SSascha Wildner 				if (len < buflen)
255550a69bb5SSascha Wildner 					buflen = MAXIMUM(MIN_READ_SIZE, len);
255650a69bb5SSascha Wildner 			}
255750a69bb5SSascha Wildner 			if (max_req > 0) { /* max_req = 0 iff EOF received */
255850a69bb5SSascha Wildner 				if (size > 0 && offset > size) {
255950a69bb5SSascha Wildner 					/* Only one request at a time
256050a69bb5SSascha Wildner 					 * after the expected EOF */
256150a69bb5SSascha Wildner 					debug3("Finish at %llu (%2d)",
256250a69bb5SSascha Wildner 					    (unsigned long long)offset,
256350a69bb5SSascha Wildner 					    num_req);
256450a69bb5SSascha Wildner 					max_req = 1;
256550a69bb5SSascha Wildner 				} else if (max_req < from->num_requests) {
256650a69bb5SSascha Wildner 					++max_req;
256750a69bb5SSascha Wildner 				}
256850a69bb5SSascha Wildner 			}
256950a69bb5SSascha Wildner 			break;
257050a69bb5SSascha Wildner 		default:
257150a69bb5SSascha Wildner 			fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
257250a69bb5SSascha Wildner 			    SSH2_FXP_DATA, type);
257350a69bb5SSascha Wildner 		}
257450a69bb5SSascha Wildner 	}
257550a69bb5SSascha Wildner 
257650a69bb5SSascha Wildner 	if (showprogress && size)
257750a69bb5SSascha Wildner 		stop_progress_meter();
257850a69bb5SSascha Wildner 
257950a69bb5SSascha Wildner 	/* Drain replies from the server (blocking) */
258050a69bb5SSascha Wildner 	debug3_f("waiting for %u replies from destination", num_upload_req);
258150a69bb5SSascha Wildner 	handle_dest_replies(to, to_path, 1, &num_upload_req, &write_error);
258250a69bb5SSascha Wildner 
258350a69bb5SSascha Wildner 	/* Sanity check */
258450a69bb5SSascha Wildner 	if (TAILQ_FIRST(&requests) != NULL)
258550a69bb5SSascha Wildner 		fatal("Transfer complete, but requests still in queue");
258650a69bb5SSascha Wildner 	/* Truncate at 0 length on interrupt or error to avoid holes at dest */
258750a69bb5SSascha Wildner 	if (read_error || write_error || interrupted) {
258850a69bb5SSascha Wildner 		debug("truncating \"%s\" at 0", to_path);
258950a69bb5SSascha Wildner 		do_close(to, to_handle, to_handle_len);
259050a69bb5SSascha Wildner 		free(to_handle);
259150a69bb5SSascha Wildner 		if (send_open(to, to_path, "dest",
259250a69bb5SSascha Wildner 		    SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
259350a69bb5SSascha Wildner 		    &to_handle, &to_handle_len) != 0) {
2594*ee116499SAntonio Huete Jimenez 			error("dest truncate \"%s\" failed", to_path);
259550a69bb5SSascha Wildner 			to_handle = NULL;
259650a69bb5SSascha Wildner 		}
259750a69bb5SSascha Wildner 	}
259850a69bb5SSascha Wildner 	if (read_error) {
2599*ee116499SAntonio Huete Jimenez 		error("read origin \"%s\": %s", from_path, fx2txt(status));
260050a69bb5SSascha Wildner 		status = -1;
260150a69bb5SSascha Wildner 		do_close(from, from_handle, from_handle_len);
260250a69bb5SSascha Wildner 		if (to_handle != NULL)
260350a69bb5SSascha Wildner 			do_close(to, to_handle, to_handle_len);
260450a69bb5SSascha Wildner 	} else if (write_error) {
2605*ee116499SAntonio Huete Jimenez 		error("write dest \"%s\": %s", to_path, fx2txt(write_error));
260650a69bb5SSascha Wildner 		status = SSH2_FX_FAILURE;
260750a69bb5SSascha Wildner 		do_close(from, from_handle, from_handle_len);
260850a69bb5SSascha Wildner 		if (to_handle != NULL)
260950a69bb5SSascha Wildner 			do_close(to, to_handle, to_handle_len);
261050a69bb5SSascha Wildner 	} else {
261150a69bb5SSascha Wildner 		if (do_close(from, from_handle, from_handle_len) != 0 ||
261250a69bb5SSascha Wildner 		    interrupted)
261350a69bb5SSascha Wildner 			status = -1;
261450a69bb5SSascha Wildner 		else
261550a69bb5SSascha Wildner 			status = SSH2_FX_OK;
261650a69bb5SSascha Wildner 		if (to_handle != NULL) {
261750a69bb5SSascha Wildner 			/* Need to resend utimes after write */
261850a69bb5SSascha Wildner 			if (preserve_flag)
261950a69bb5SSascha Wildner 				do_fsetstat(to, to_handle, to_handle_len, a);
262050a69bb5SSascha Wildner 			do_close(to, to_handle, to_handle_len);
262150a69bb5SSascha Wildner 		}
262250a69bb5SSascha Wildner 	}
262350a69bb5SSascha Wildner 	sshbuf_free(msg);
262450a69bb5SSascha Wildner 	free(from_handle);
262550a69bb5SSascha Wildner 	free(to_handle);
262650a69bb5SSascha Wildner 
262750a69bb5SSascha Wildner 	return status == SSH2_FX_OK ? 0 : -1;
262850a69bb5SSascha Wildner }
262950a69bb5SSascha Wildner 
263050a69bb5SSascha Wildner static int
crossload_dir_internal(struct sftp_conn * from,struct sftp_conn * to,const char * from_path,const char * to_path,int depth,Attrib * dirattrib,int preserve_flag,int print_flag,int follow_link_flag)263150a69bb5SSascha Wildner crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
263250a69bb5SSascha Wildner     const char *from_path, const char *to_path,
263350a69bb5SSascha Wildner     int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
263450a69bb5SSascha Wildner     int follow_link_flag)
263550a69bb5SSascha Wildner {
263650a69bb5SSascha Wildner 	int i, ret = 0;
263750a69bb5SSascha Wildner 	SFTP_DIRENT **dir_entries;
263850a69bb5SSascha Wildner 	char *filename, *new_from_path = NULL, *new_to_path = NULL;
263950a69bb5SSascha Wildner 	mode_t mode = 0777;
264050a69bb5SSascha Wildner 	Attrib curdir;
264150a69bb5SSascha Wildner 
2642*ee116499SAntonio Huete Jimenez 	debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path);
2643*ee116499SAntonio Huete Jimenez 
264450a69bb5SSascha Wildner 	if (depth >= MAX_DIR_DEPTH) {
264550a69bb5SSascha Wildner 		error("Maximum directory depth exceeded: %d levels", depth);
264650a69bb5SSascha Wildner 		return -1;
264750a69bb5SSascha Wildner 	}
264850a69bb5SSascha Wildner 
264950a69bb5SSascha Wildner 	if (dirattrib == NULL &&
265050a69bb5SSascha Wildner 	    (dirattrib = do_stat(from, from_path, 1)) == NULL) {
2651*ee116499SAntonio Huete Jimenez 		error("stat remote \"%s\" failed", from_path);
265250a69bb5SSascha Wildner 		return -1;
265350a69bb5SSascha Wildner 	}
265450a69bb5SSascha Wildner 	if (!S_ISDIR(dirattrib->perm)) {
265550a69bb5SSascha Wildner 		error("\"%s\" is not a directory", from_path);
265650a69bb5SSascha Wildner 		return -1;
265750a69bb5SSascha Wildner 	}
265850a69bb5SSascha Wildner 	if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
265950a69bb5SSascha Wildner 		mprintf("Retrieving %s\n", from_path);
266050a69bb5SSascha Wildner 
266150a69bb5SSascha Wildner 	curdir = *dirattrib; /* dirattrib will be clobbered */
266250a69bb5SSascha Wildner 	curdir.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
266350a69bb5SSascha Wildner 	curdir.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
266450a69bb5SSascha Wildner 	if ((curdir.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) == 0) {
266550a69bb5SSascha Wildner 		debug("Origin did not send permissions for "
266650a69bb5SSascha Wildner 		    "directory \"%s\"", to_path);
266750a69bb5SSascha Wildner 		curdir.perm = S_IWUSR|S_IXUSR;
266850a69bb5SSascha Wildner 		curdir.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
266950a69bb5SSascha Wildner 	}
267050a69bb5SSascha Wildner 	/* We need to be able to write to the directory while we transfer it */
267150a69bb5SSascha Wildner 	mode = curdir.perm & 01777;
267250a69bb5SSascha Wildner 	curdir.perm = mode | (S_IWUSR|S_IXUSR);
267350a69bb5SSascha Wildner 
267450a69bb5SSascha Wildner 	/*
267550a69bb5SSascha Wildner 	 * sftp lacks a portable status value to match errno EEXIST,
267650a69bb5SSascha Wildner 	 * so if we get a failure back then we must check whether
267750a69bb5SSascha Wildner 	 * the path already existed and is a directory.  Ensure we can
267850a69bb5SSascha Wildner 	 * write to the directory we create for the duration of the transfer.
267950a69bb5SSascha Wildner 	 */
268050a69bb5SSascha Wildner 	if (do_mkdir(to, to_path, &curdir, 0) != 0) {
268150a69bb5SSascha Wildner 		if ((dirattrib = do_stat(to, to_path, 0)) == NULL)
268250a69bb5SSascha Wildner 			return -1;
268350a69bb5SSascha Wildner 		if (!S_ISDIR(dirattrib->perm)) {
268450a69bb5SSascha Wildner 			error("\"%s\" exists but is not a directory", to_path);
268550a69bb5SSascha Wildner 			return -1;
268650a69bb5SSascha Wildner 		}
268750a69bb5SSascha Wildner 	}
268850a69bb5SSascha Wildner 	curdir.perm = mode;
268950a69bb5SSascha Wildner 
269050a69bb5SSascha Wildner 	if (do_readdir(from, from_path, &dir_entries) == -1) {
2691*ee116499SAntonio Huete Jimenez 		error("origin readdir \"%s\" failed", from_path);
269250a69bb5SSascha Wildner 		return -1;
269350a69bb5SSascha Wildner 	}
269450a69bb5SSascha Wildner 
269550a69bb5SSascha Wildner 	for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
269650a69bb5SSascha Wildner 		free(new_from_path);
269750a69bb5SSascha Wildner 		free(new_to_path);
269850a69bb5SSascha Wildner 
269950a69bb5SSascha Wildner 		filename = dir_entries[i]->filename;
270050a69bb5SSascha Wildner 		new_from_path = path_append(from_path, filename);
270150a69bb5SSascha Wildner 		new_to_path = path_append(to_path, filename);
270250a69bb5SSascha Wildner 
270350a69bb5SSascha Wildner 		if (S_ISDIR(dir_entries[i]->a.perm)) {
270450a69bb5SSascha Wildner 			if (strcmp(filename, ".") == 0 ||
270550a69bb5SSascha Wildner 			    strcmp(filename, "..") == 0)
270650a69bb5SSascha Wildner 				continue;
270750a69bb5SSascha Wildner 			if (crossload_dir_internal(from, to,
270850a69bb5SSascha Wildner 			    new_from_path, new_to_path,
270950a69bb5SSascha Wildner 			    depth + 1, &(dir_entries[i]->a), preserve_flag,
271050a69bb5SSascha Wildner 			    print_flag, follow_link_flag) == -1)
271150a69bb5SSascha Wildner 				ret = -1;
271250a69bb5SSascha Wildner 		} else if (S_ISREG(dir_entries[i]->a.perm) ||
271350a69bb5SSascha Wildner 		    (follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) {
271450a69bb5SSascha Wildner 			/*
271550a69bb5SSascha Wildner 			 * If this is a symlink then don't send the link's
271650a69bb5SSascha Wildner 			 * Attrib. do_download() will do a FXP_STAT operation
271750a69bb5SSascha Wildner 			 * and get the link target's attributes.
271850a69bb5SSascha Wildner 			 */
271950a69bb5SSascha Wildner 			if (do_crossload(from, to, new_from_path, new_to_path,
272050a69bb5SSascha Wildner 			    S_ISLNK(dir_entries[i]->a.perm) ? NULL :
272150a69bb5SSascha Wildner 			    &(dir_entries[i]->a), preserve_flag) == -1) {
2722*ee116499SAntonio Huete Jimenez 				error("crossload \"%s\" to \"%s\" failed",
272350a69bb5SSascha Wildner 				    new_from_path, new_to_path);
272450a69bb5SSascha Wildner 				ret = -1;
272550a69bb5SSascha Wildner 			}
2726*ee116499SAntonio Huete Jimenez 		} else {
2727*ee116499SAntonio Huete Jimenez 			logit("origin \"%s\": not a regular file",
2728*ee116499SAntonio Huete Jimenez 			    new_from_path);
2729*ee116499SAntonio Huete Jimenez 		}
273050a69bb5SSascha Wildner 	}
273150a69bb5SSascha Wildner 	free(new_to_path);
273250a69bb5SSascha Wildner 	free(new_from_path);
273350a69bb5SSascha Wildner 
273450a69bb5SSascha Wildner 	do_setstat(to, to_path, &curdir);
273550a69bb5SSascha Wildner 
273650a69bb5SSascha Wildner 	free_sftp_dirents(dir_entries);
273750a69bb5SSascha Wildner 
273850a69bb5SSascha Wildner 	return ret;
273950a69bb5SSascha Wildner }
274050a69bb5SSascha Wildner 
274150a69bb5SSascha Wildner int
crossload_dir(struct sftp_conn * from,struct sftp_conn * to,const char * from_path,const char * to_path,Attrib * dirattrib,int preserve_flag,int print_flag,int follow_link_flag)274250a69bb5SSascha Wildner crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
274350a69bb5SSascha Wildner     const char *from_path, const char *to_path,
274450a69bb5SSascha Wildner     Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag)
274550a69bb5SSascha Wildner {
274650a69bb5SSascha Wildner 	char *from_path_canon;
274750a69bb5SSascha Wildner 	int ret;
274850a69bb5SSascha Wildner 
274950a69bb5SSascha Wildner 	if ((from_path_canon = do_realpath(from, from_path)) == NULL) {
2750*ee116499SAntonio Huete Jimenez 		error("crossload \"%s\": path canonicalization failed",
2751*ee116499SAntonio Huete Jimenez 		    from_path);
275250a69bb5SSascha Wildner 		return -1;
275350a69bb5SSascha Wildner 	}
275450a69bb5SSascha Wildner 
275550a69bb5SSascha Wildner 	ret = crossload_dir_internal(from, to, from_path_canon, to_path, 0,
275650a69bb5SSascha Wildner 	    dirattrib, preserve_flag, print_flag, follow_link_flag);
275750a69bb5SSascha Wildner 	free(from_path_canon);
275850a69bb5SSascha Wildner 	return ret;
275950a69bb5SSascha Wildner }
276050a69bb5SSascha Wildner 
2761*ee116499SAntonio Huete Jimenez int
can_get_users_groups_by_id(struct sftp_conn * conn)2762*ee116499SAntonio Huete Jimenez can_get_users_groups_by_id(struct sftp_conn *conn)
2763*ee116499SAntonio Huete Jimenez {
2764*ee116499SAntonio Huete Jimenez 	return (conn->exts & SFTP_EXT_GETUSERSGROUPS_BY_ID) != 0;
2765*ee116499SAntonio Huete Jimenez }
2766*ee116499SAntonio Huete Jimenez 
2767*ee116499SAntonio Huete Jimenez int
do_get_users_groups_by_id(struct sftp_conn * conn,const u_int * uids,u_int nuids,const u_int * gids,u_int ngids,char *** usernamesp,char *** groupnamesp)2768*ee116499SAntonio Huete Jimenez do_get_users_groups_by_id(struct sftp_conn *conn,
2769*ee116499SAntonio Huete Jimenez     const u_int *uids, u_int nuids,
2770*ee116499SAntonio Huete Jimenez     const u_int *gids, u_int ngids,
2771*ee116499SAntonio Huete Jimenez     char ***usernamesp, char ***groupnamesp)
2772*ee116499SAntonio Huete Jimenez {
2773*ee116499SAntonio Huete Jimenez 	struct sshbuf *msg, *uidbuf, *gidbuf;
2774*ee116499SAntonio Huete Jimenez 	u_int i, expected_id, id;
2775*ee116499SAntonio Huete Jimenez 	char *name, **usernames = NULL, **groupnames = NULL;
2776*ee116499SAntonio Huete Jimenez 	u_char type;
2777*ee116499SAntonio Huete Jimenez 	int r;
2778*ee116499SAntonio Huete Jimenez 
2779*ee116499SAntonio Huete Jimenez 	*usernamesp = *groupnamesp = NULL;
2780*ee116499SAntonio Huete Jimenez 	if (!can_get_users_groups_by_id(conn))
2781*ee116499SAntonio Huete Jimenez 		return SSH_ERR_FEATURE_UNSUPPORTED;
2782*ee116499SAntonio Huete Jimenez 
2783*ee116499SAntonio Huete Jimenez 	if ((msg = sshbuf_new()) == NULL ||
2784*ee116499SAntonio Huete Jimenez 	    (uidbuf = sshbuf_new()) == NULL ||
2785*ee116499SAntonio Huete Jimenez 	    (gidbuf = sshbuf_new()) == NULL)
2786*ee116499SAntonio Huete Jimenez 		fatal_f("sshbuf_new failed");
2787*ee116499SAntonio Huete Jimenez 	expected_id = id = conn->msg_id++;
2788*ee116499SAntonio Huete Jimenez 	debug2("Sending SSH2_FXP_EXTENDED(users-groups-by-id@openssh.com)");
2789*ee116499SAntonio Huete Jimenez 	for (i = 0; i < nuids; i++) {
2790*ee116499SAntonio Huete Jimenez 		if ((r = sshbuf_put_u32(uidbuf, uids[i])) != 0)
2791*ee116499SAntonio Huete Jimenez 			fatal_fr(r, "compose uids");
2792*ee116499SAntonio Huete Jimenez 	}
2793*ee116499SAntonio Huete Jimenez 	for (i = 0; i < ngids; i++) {
2794*ee116499SAntonio Huete Jimenez 		if ((r = sshbuf_put_u32(gidbuf, gids[i])) != 0)
2795*ee116499SAntonio Huete Jimenez 			fatal_fr(r, "compose gids");
2796*ee116499SAntonio Huete Jimenez 	}
2797*ee116499SAntonio Huete Jimenez 	if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
2798*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_u32(msg, id)) != 0 ||
2799*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_cstring(msg,
2800*ee116499SAntonio Huete Jimenez 	    "users-groups-by-id@openssh.com")) != 0 ||
2801*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_stringb(msg, uidbuf)) != 0 ||
2802*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_put_stringb(msg, gidbuf)) != 0)
2803*ee116499SAntonio Huete Jimenez 		fatal_fr(r, "compose");
2804*ee116499SAntonio Huete Jimenez 	send_msg(conn, msg);
2805*ee116499SAntonio Huete Jimenez 	get_msg(conn, msg);
2806*ee116499SAntonio Huete Jimenez 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
2807*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_get_u32(msg, &id)) != 0)
2808*ee116499SAntonio Huete Jimenez 		fatal_fr(r, "parse");
2809*ee116499SAntonio Huete Jimenez 	if (id != expected_id)
2810*ee116499SAntonio Huete Jimenez 		fatal("ID mismatch (%u != %u)", id, expected_id);
2811*ee116499SAntonio Huete Jimenez 	if (type == SSH2_FXP_STATUS) {
2812*ee116499SAntonio Huete Jimenez 		u_int status;
2813*ee116499SAntonio Huete Jimenez 		char *errmsg;
2814*ee116499SAntonio Huete Jimenez 
2815*ee116499SAntonio Huete Jimenez 		if ((r = sshbuf_get_u32(msg, &status)) != 0 ||
2816*ee116499SAntonio Huete Jimenez 		    (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0)
2817*ee116499SAntonio Huete Jimenez 			fatal_fr(r, "parse status");
2818*ee116499SAntonio Huete Jimenez 		error("users-groups-by-id %s",
2819*ee116499SAntonio Huete Jimenez 		    *errmsg == '\0' ? fx2txt(status) : errmsg);
2820*ee116499SAntonio Huete Jimenez 		free(errmsg);
2821*ee116499SAntonio Huete Jimenez 		sshbuf_free(msg);
2822*ee116499SAntonio Huete Jimenez 		sshbuf_free(uidbuf);
2823*ee116499SAntonio Huete Jimenez 		sshbuf_free(gidbuf);
2824*ee116499SAntonio Huete Jimenez 		return -1;
2825*ee116499SAntonio Huete Jimenez 	} else if (type != SSH2_FXP_EXTENDED_REPLY)
2826*ee116499SAntonio Huete Jimenez 		fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
2827*ee116499SAntonio Huete Jimenez 		    SSH2_FXP_EXTENDED_REPLY, type);
2828*ee116499SAntonio Huete Jimenez 
2829*ee116499SAntonio Huete Jimenez 	/* reuse */
2830*ee116499SAntonio Huete Jimenez 	sshbuf_free(uidbuf);
2831*ee116499SAntonio Huete Jimenez 	sshbuf_free(gidbuf);
2832*ee116499SAntonio Huete Jimenez 	uidbuf = gidbuf = NULL;
2833*ee116499SAntonio Huete Jimenez 	if ((r = sshbuf_froms(msg, &uidbuf)) != 0 ||
2834*ee116499SAntonio Huete Jimenez 	    (r = sshbuf_froms(msg, &gidbuf)) != 0)
2835*ee116499SAntonio Huete Jimenez 		fatal_fr(r, "parse response");
2836*ee116499SAntonio Huete Jimenez 	if (nuids > 0) {
2837*ee116499SAntonio Huete Jimenez 		usernames = xcalloc(nuids, sizeof(*usernames));
2838*ee116499SAntonio Huete Jimenez 		for (i = 0; i < nuids; i++) {
2839*ee116499SAntonio Huete Jimenez 			if ((r = sshbuf_get_cstring(uidbuf, &name, NULL)) != 0)
2840*ee116499SAntonio Huete Jimenez 				fatal_fr(r, "parse user name");
2841*ee116499SAntonio Huete Jimenez 			/* Handle unresolved names */
2842*ee116499SAntonio Huete Jimenez 			if (*name == '\0') {
2843*ee116499SAntonio Huete Jimenez 				free(name);
2844*ee116499SAntonio Huete Jimenez 				name = NULL;
2845*ee116499SAntonio Huete Jimenez 			}
2846*ee116499SAntonio Huete Jimenez 			usernames[i] = name;
2847*ee116499SAntonio Huete Jimenez 		}
2848*ee116499SAntonio Huete Jimenez 	}
2849*ee116499SAntonio Huete Jimenez 	if (ngids > 0) {
2850*ee116499SAntonio Huete Jimenez 		groupnames = xcalloc(ngids, sizeof(*groupnames));
2851*ee116499SAntonio Huete Jimenez 		for (i = 0; i < ngids; i++) {
2852*ee116499SAntonio Huete Jimenez 			if ((r = sshbuf_get_cstring(gidbuf, &name, NULL)) != 0)
2853*ee116499SAntonio Huete Jimenez 				fatal_fr(r, "parse user name");
2854*ee116499SAntonio Huete Jimenez 			/* Handle unresolved names */
2855*ee116499SAntonio Huete Jimenez 			if (*name == '\0') {
2856*ee116499SAntonio Huete Jimenez 				free(name);
2857*ee116499SAntonio Huete Jimenez 				name = NULL;
2858*ee116499SAntonio Huete Jimenez 			}
2859*ee116499SAntonio Huete Jimenez 			groupnames[i] = name;
2860*ee116499SAntonio Huete Jimenez 		}
2861*ee116499SAntonio Huete Jimenez 	}
2862*ee116499SAntonio Huete Jimenez 	if (sshbuf_len(uidbuf) != 0)
2863*ee116499SAntonio Huete Jimenez 		fatal_f("unexpected extra username data");
2864*ee116499SAntonio Huete Jimenez 	if (sshbuf_len(gidbuf) != 0)
2865*ee116499SAntonio Huete Jimenez 		fatal_f("unexpected extra groupname data");
2866*ee116499SAntonio Huete Jimenez 	sshbuf_free(uidbuf);
2867*ee116499SAntonio Huete Jimenez 	sshbuf_free(gidbuf);
2868*ee116499SAntonio Huete Jimenez 	sshbuf_free(msg);
2869*ee116499SAntonio Huete Jimenez 	/* success */
2870*ee116499SAntonio Huete Jimenez 	*usernamesp = usernames;
2871*ee116499SAntonio Huete Jimenez 	*groupnamesp = groupnames;
2872*ee116499SAntonio Huete Jimenez 	return 0;
2873*ee116499SAntonio Huete Jimenez }
2874*ee116499SAntonio Huete Jimenez 
2875856ea928SPeter Avalos char *
path_append(const char * p1,const char * p2)2876e9778795SPeter Avalos path_append(const char *p1, const char *p2)
2877856ea928SPeter Avalos {
2878856ea928SPeter Avalos 	char *ret;
2879856ea928SPeter Avalos 	size_t len = strlen(p1) + strlen(p2) + 2;
2880856ea928SPeter Avalos 
2881856ea928SPeter Avalos 	ret = xmalloc(len);
2882856ea928SPeter Avalos 	strlcpy(ret, p1, len);
2883856ea928SPeter Avalos 	if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
2884856ea928SPeter Avalos 		strlcat(ret, "/", len);
2885856ea928SPeter Avalos 	strlcat(ret, p2, len);
2886856ea928SPeter Avalos 
2887856ea928SPeter Avalos 	return(ret);
2888856ea928SPeter Avalos }
2889856ea928SPeter Avalos 
289050a69bb5SSascha Wildner char *
make_absolute(char * p,const char * pwd)289150a69bb5SSascha Wildner make_absolute(char *p, const char *pwd)
289250a69bb5SSascha Wildner {
289350a69bb5SSascha Wildner 	char *abs_str;
289450a69bb5SSascha Wildner 
289550a69bb5SSascha Wildner 	/* Derelativise */
289650a69bb5SSascha Wildner 	if (p && !path_absolute(p)) {
289750a69bb5SSascha Wildner 		abs_str = path_append(pwd, p);
289850a69bb5SSascha Wildner 		free(p);
289950a69bb5SSascha Wildner 		return(abs_str);
290050a69bb5SSascha Wildner 	} else
290150a69bb5SSascha Wildner 		return(p);
290250a69bb5SSascha Wildner }
290350a69bb5SSascha Wildner 
290450a69bb5SSascha Wildner int
remote_is_dir(struct sftp_conn * conn,const char * path)290550a69bb5SSascha Wildner remote_is_dir(struct sftp_conn *conn, const char *path)
290650a69bb5SSascha Wildner {
290750a69bb5SSascha Wildner 	Attrib *a;
290850a69bb5SSascha Wildner 
290950a69bb5SSascha Wildner 	/* XXX: report errors? */
291050a69bb5SSascha Wildner 	if ((a = do_stat(conn, path, 1)) == NULL)
291150a69bb5SSascha Wildner 		return(0);
291250a69bb5SSascha Wildner 	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
291350a69bb5SSascha Wildner 		return(0);
291450a69bb5SSascha Wildner 	return(S_ISDIR(a->perm));
291550a69bb5SSascha Wildner }
291650a69bb5SSascha Wildner 
291750a69bb5SSascha Wildner 
291850a69bb5SSascha Wildner int
local_is_dir(const char * path)291950a69bb5SSascha Wildner local_is_dir(const char *path)
292050a69bb5SSascha Wildner {
292150a69bb5SSascha Wildner 	struct stat sb;
292250a69bb5SSascha Wildner 
292350a69bb5SSascha Wildner 	/* XXX: report errors? */
292450a69bb5SSascha Wildner 	if (stat(path, &sb) == -1)
292550a69bb5SSascha Wildner 		return(0);
292650a69bb5SSascha Wildner 
292750a69bb5SSascha Wildner 	return(S_ISDIR(sb.st_mode));
292850a69bb5SSascha Wildner }
292950a69bb5SSascha Wildner 
293050a69bb5SSascha Wildner /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
293150a69bb5SSascha Wildner int
globpath_is_dir(const char * pathname)293250a69bb5SSascha Wildner globpath_is_dir(const char *pathname)
293350a69bb5SSascha Wildner {
293450a69bb5SSascha Wildner 	size_t l = strlen(pathname);
293550a69bb5SSascha Wildner 
293650a69bb5SSascha Wildner 	return l > 0 && pathname[l - 1] == '/';
293750a69bb5SSascha Wildner }
293850a69bb5SSascha Wildner 
2939