1*ba1276acSMatthew Dillon /* $OpenBSD: sftp-client.c,v 1.176 2024/05/17 02:39:11 jsg 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
71*ba1276acSMatthew Dillon /* Default size of buffer for up/download (fix sftp.1 scp.1 if changed) */
7250a69bb5SSascha Wildner #define DEFAULT_COPY_BUFLEN 32768
7350a69bb5SSascha Wildner
74*ba1276acSMatthew Dillon /* Default number of concurrent xfer requests (fix sftp.1 scp.1 if changed) */
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
106ee116499SAntonio Huete Jimenez #define SFTP_EXT_COPY_DATA 0x00000100
107ee116499SAntonio 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 static int
sftpio(void * _bwlimit,size_t amount)1539f304aafSPeter Avalos sftpio(void *_bwlimit, size_t amount)
1549f304aafSPeter Avalos {
1559f304aafSPeter Avalos struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;
1569f304aafSPeter Avalos
157664f4763Szrj refresh_progress_meter(0);
158664f4763Szrj if (bwlimit != NULL)
1599f304aafSPeter Avalos bandwidth_limit(bwlimit, amount);
1609f304aafSPeter Avalos return 0;
1619f304aafSPeter Avalos }
162856ea928SPeter Avalos
16318de8d7fSPeter Avalos static void
send_msg(struct sftp_conn * conn,struct sshbuf * m)164e9778795SPeter Avalos send_msg(struct sftp_conn *conn, struct sshbuf *m)
16518de8d7fSPeter Avalos {
16618de8d7fSPeter Avalos u_char mlen[4];
16718de8d7fSPeter Avalos struct iovec iov[2];
16818de8d7fSPeter Avalos
169e9778795SPeter Avalos if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH)
170e9778795SPeter Avalos fatal("Outbound message too long %zu", sshbuf_len(m));
17118de8d7fSPeter Avalos
17218de8d7fSPeter Avalos /* Send length first */
173e9778795SPeter Avalos put_u32(mlen, sshbuf_len(m));
17418de8d7fSPeter Avalos iov[0].iov_base = mlen;
17518de8d7fSPeter Avalos iov[0].iov_len = sizeof(mlen);
176e9778795SPeter Avalos iov[1].iov_base = (u_char *)sshbuf_ptr(m);
177e9778795SPeter Avalos iov[1].iov_len = sshbuf_len(m);
17818de8d7fSPeter Avalos
179664f4763Szrj if (atomiciov6(writev, conn->fd_out, iov, 2, sftpio,
180664f4763Szrj conn->limit_kbps > 0 ? &conn->bwlimit_out : NULL) !=
181e9778795SPeter Avalos sshbuf_len(m) + sizeof(mlen))
18218de8d7fSPeter Avalos fatal("Couldn't send packet: %s", strerror(errno));
18318de8d7fSPeter Avalos
184e9778795SPeter Avalos sshbuf_reset(m);
18518de8d7fSPeter Avalos }
18618de8d7fSPeter Avalos
18718de8d7fSPeter Avalos static void
get_msg_extended(struct sftp_conn * conn,struct sshbuf * m,int initial)188664f4763Szrj get_msg_extended(struct sftp_conn *conn, struct sshbuf *m, int initial)
18918de8d7fSPeter Avalos {
19018de8d7fSPeter Avalos u_int msg_len;
191e9778795SPeter Avalos u_char *p;
192e9778795SPeter Avalos int r;
19318de8d7fSPeter Avalos
19450a69bb5SSascha Wildner sshbuf_reset(m);
195e9778795SPeter Avalos if ((r = sshbuf_reserve(m, 4, &p)) != 0)
19650a69bb5SSascha Wildner fatal_fr(r, "reserve");
197664f4763Szrj if (atomicio6(read, conn->fd_in, p, 4, sftpio,
198664f4763Szrj conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL) != 4) {
199ce74bacaSMatthew Dillon if (errno == EPIPE || errno == ECONNRESET)
20018de8d7fSPeter Avalos fatal("Connection closed");
20118de8d7fSPeter Avalos else
20218de8d7fSPeter Avalos fatal("Couldn't read packet: %s", strerror(errno));
20318de8d7fSPeter Avalos }
20418de8d7fSPeter Avalos
205e9778795SPeter Avalos if ((r = sshbuf_get_u32(m, &msg_len)) != 0)
20650a69bb5SSascha Wildner fatal_fr(r, "sshbuf_get_u32");
207664f4763Szrj if (msg_len > SFTP_MAX_MSG_LENGTH) {
208664f4763Szrj do_log2(initial ? SYSLOG_LEVEL_ERROR : SYSLOG_LEVEL_FATAL,
209664f4763Szrj "Received message too long %u", msg_len);
210664f4763Szrj fatal("Ensure the remote shell produces no output "
211664f4763Szrj "for non-interactive sessions.");
212664f4763Szrj }
21318de8d7fSPeter Avalos
214e9778795SPeter Avalos if ((r = sshbuf_reserve(m, msg_len, &p)) != 0)
21550a69bb5SSascha Wildner fatal_fr(r, "reserve");
216664f4763Szrj if (atomicio6(read, conn->fd_in, p, msg_len, sftpio,
217664f4763Szrj conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL)
2189f304aafSPeter Avalos != msg_len) {
21918de8d7fSPeter Avalos if (errno == EPIPE)
22018de8d7fSPeter Avalos fatal("Connection closed");
22118de8d7fSPeter Avalos else
22218de8d7fSPeter Avalos fatal("Read packet: %s", strerror(errno));
22318de8d7fSPeter Avalos }
22418de8d7fSPeter Avalos }
22518de8d7fSPeter Avalos
22618de8d7fSPeter Avalos static void
get_msg(struct sftp_conn * conn,struct sshbuf * m)227664f4763Szrj get_msg(struct sftp_conn *conn, struct sshbuf *m)
228664f4763Szrj {
229664f4763Szrj get_msg_extended(conn, m, 0);
230664f4763Szrj }
231664f4763Szrj
232664f4763Szrj static void
send_string_request(struct sftp_conn * conn,u_int id,u_int code,const char * s,u_int len)233e9778795SPeter Avalos send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s,
23418de8d7fSPeter Avalos u_int len)
23518de8d7fSPeter Avalos {
236e9778795SPeter Avalos struct sshbuf *msg;
237e9778795SPeter Avalos int r;
23818de8d7fSPeter Avalos
239e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
24050a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
241e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, code)) != 0 ||
242e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
243e9778795SPeter Avalos (r = sshbuf_put_string(msg, s, len)) != 0)
24450a69bb5SSascha Wildner fatal_fr(r, "compose");
245e9778795SPeter Avalos send_msg(conn, msg);
2469f304aafSPeter Avalos debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
247e9778795SPeter Avalos sshbuf_free(msg);
24818de8d7fSPeter Avalos }
24918de8d7fSPeter Avalos
25018de8d7fSPeter 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)2519f304aafSPeter Avalos send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
252e9778795SPeter Avalos const void *s, u_int len, Attrib *a)
25318de8d7fSPeter Avalos {
254e9778795SPeter Avalos struct sshbuf *msg;
255e9778795SPeter Avalos int r;
25618de8d7fSPeter Avalos
257e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
25850a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
259e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, code)) != 0 ||
260e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
261e9778795SPeter Avalos (r = sshbuf_put_string(msg, s, len)) != 0 ||
262e9778795SPeter Avalos (r = encode_attrib(msg, a)) != 0)
26350a69bb5SSascha Wildner fatal_fr(r, "compose");
264e9778795SPeter Avalos send_msg(conn, msg);
26550a69bb5SSascha Wildner debug3("Sent message fd %d T:%u I:%u F:0x%04x M:%05o",
26650a69bb5SSascha Wildner conn->fd_out, code, id, a->flags, a->perm);
267e9778795SPeter Avalos sshbuf_free(msg);
26818de8d7fSPeter Avalos }
26918de8d7fSPeter Avalos
27018de8d7fSPeter Avalos static u_int
get_status(struct sftp_conn * conn,u_int expected_id)2719f304aafSPeter Avalos get_status(struct sftp_conn *conn, u_int expected_id)
27218de8d7fSPeter Avalos {
273e9778795SPeter Avalos struct sshbuf *msg;
274e9778795SPeter Avalos u_char type;
275e9778795SPeter Avalos u_int id, status;
276e9778795SPeter Avalos int r;
27718de8d7fSPeter Avalos
278e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
27950a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
280e9778795SPeter Avalos get_msg(conn, msg);
281e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
282e9778795SPeter Avalos (r = sshbuf_get_u32(msg, &id)) != 0)
28350a69bb5SSascha Wildner fatal_fr(r, "compose");
28418de8d7fSPeter Avalos
28518de8d7fSPeter Avalos if (id != expected_id)
28618de8d7fSPeter Avalos fatal("ID mismatch (%u != %u)", id, expected_id);
28718de8d7fSPeter Avalos if (type != SSH2_FXP_STATUS)
28818de8d7fSPeter Avalos fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
28918de8d7fSPeter Avalos SSH2_FXP_STATUS, type);
29018de8d7fSPeter Avalos
291e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &status)) != 0)
29250a69bb5SSascha Wildner fatal_fr(r, "parse");
293e9778795SPeter Avalos sshbuf_free(msg);
29418de8d7fSPeter Avalos
29518de8d7fSPeter Avalos debug3("SSH2_FXP_STATUS %u", status);
29618de8d7fSPeter Avalos
2979f304aafSPeter Avalos return status;
29818de8d7fSPeter Avalos }
29918de8d7fSPeter Avalos
300e9778795SPeter Avalos static u_char *
get_handle(struct sftp_conn * conn,u_int expected_id,size_t * len,const char * errfmt,...)301e9778795SPeter Avalos get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len,
3029f304aafSPeter Avalos const char *errfmt, ...)
30318de8d7fSPeter Avalos {
304e9778795SPeter Avalos struct sshbuf *msg;
305e9778795SPeter Avalos u_int id, status;
306e9778795SPeter Avalos u_char type;
307e9778795SPeter Avalos u_char *handle;
308e9778795SPeter Avalos char errmsg[256];
309856ea928SPeter Avalos va_list args;
310e9778795SPeter Avalos int r;
311856ea928SPeter Avalos
312856ea928SPeter Avalos va_start(args, errfmt);
313856ea928SPeter Avalos if (errfmt != NULL)
314856ea928SPeter Avalos vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
315856ea928SPeter Avalos va_end(args);
31618de8d7fSPeter Avalos
317e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
31850a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
319e9778795SPeter Avalos get_msg(conn, msg);
320e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
321e9778795SPeter Avalos (r = sshbuf_get_u32(msg, &id)) != 0)
32250a69bb5SSascha Wildner fatal_fr(r, "parse");
32318de8d7fSPeter Avalos
32418de8d7fSPeter Avalos if (id != expected_id)
325856ea928SPeter Avalos fatal("%s: ID mismatch (%u != %u)",
326856ea928SPeter Avalos errfmt == NULL ? __func__ : errmsg, id, expected_id);
32718de8d7fSPeter Avalos if (type == SSH2_FXP_STATUS) {
328e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &status)) != 0)
32950a69bb5SSascha Wildner fatal_fr(r, "parse status");
330856ea928SPeter Avalos if (errfmt != NULL)
331856ea928SPeter Avalos error("%s: %s", errmsg, fx2txt(status));
332e9778795SPeter Avalos sshbuf_free(msg);
33318de8d7fSPeter Avalos return(NULL);
33418de8d7fSPeter Avalos } else if (type != SSH2_FXP_HANDLE)
335856ea928SPeter Avalos fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
336856ea928SPeter Avalos errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
33718de8d7fSPeter Avalos
338e9778795SPeter Avalos if ((r = sshbuf_get_string(msg, &handle, len)) != 0)
33950a69bb5SSascha Wildner fatal_fr(r, "parse handle");
340e9778795SPeter Avalos sshbuf_free(msg);
34118de8d7fSPeter Avalos
342e9778795SPeter Avalos return handle;
34318de8d7fSPeter Avalos }
34418de8d7fSPeter Avalos
345*ba1276acSMatthew Dillon static int
get_decode_stat(struct sftp_conn * conn,u_int expected_id,int quiet,Attrib * a)346*ba1276acSMatthew Dillon get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet, Attrib *a)
34718de8d7fSPeter Avalos {
348e9778795SPeter Avalos struct sshbuf *msg;
349e9778795SPeter Avalos u_int id;
350e9778795SPeter Avalos u_char type;
351e9778795SPeter Avalos int r;
352*ba1276acSMatthew Dillon Attrib attr;
35318de8d7fSPeter Avalos
354*ba1276acSMatthew Dillon if (a != NULL)
355*ba1276acSMatthew Dillon memset(a, '\0', sizeof(*a));
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)
372ee116499SAntonio Huete Jimenez debug("stat remote: %s", fx2txt(status));
37318de8d7fSPeter Avalos else
374ee116499SAntonio Huete Jimenez error("stat remote: %s", fx2txt(status));
375e9778795SPeter Avalos sshbuf_free(msg);
376*ba1276acSMatthew Dillon return -1;
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 }
381*ba1276acSMatthew Dillon if ((r = decode_attrib(msg, &attr)) != 0) {
38250a69bb5SSascha Wildner error_fr(r, "decode_attrib");
383e9778795SPeter Avalos sshbuf_free(msg);
384*ba1276acSMatthew Dillon return -1;
385e9778795SPeter Avalos }
386*ba1276acSMatthew Dillon /* success */
387*ba1276acSMatthew Dillon if (a != NULL)
388*ba1276acSMatthew Dillon *a = attr;
389ee116499SAntonio Huete Jimenez debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o",
390*ba1276acSMatthew Dillon type, id, attr.flags, attr.perm);
391e9778795SPeter Avalos sshbuf_free(msg);
39218de8d7fSPeter Avalos
393*ba1276acSMatthew Dillon return 0;
39418de8d7fSPeter Avalos }
39518de8d7fSPeter Avalos
39618de8d7fSPeter Avalos static int
get_decode_statvfs(struct sftp_conn * conn,struct sftp_statvfs * st,u_int expected_id,int quiet)3979f304aafSPeter Avalos get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
3989f304aafSPeter Avalos u_int expected_id, int quiet)
39918de8d7fSPeter Avalos {
400e9778795SPeter Avalos struct sshbuf *msg;
401e9778795SPeter Avalos u_char type;
402e9778795SPeter Avalos u_int id;
403e9778795SPeter Avalos u_int64_t flag;
404e9778795SPeter Avalos int r;
40518de8d7fSPeter Avalos
406e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
40750a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
408e9778795SPeter Avalos get_msg(conn, msg);
40918de8d7fSPeter Avalos
410e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
411e9778795SPeter Avalos (r = sshbuf_get_u32(msg, &id)) != 0)
41250a69bb5SSascha Wildner fatal_fr(r, "parse");
41318de8d7fSPeter Avalos
41418de8d7fSPeter Avalos debug3("Received statvfs reply T:%u I:%u", type, id);
41518de8d7fSPeter Avalos if (id != expected_id)
41618de8d7fSPeter Avalos fatal("ID mismatch (%u != %u)", id, expected_id);
41718de8d7fSPeter Avalos if (type == SSH2_FXP_STATUS) {
418e9778795SPeter Avalos u_int status;
41918de8d7fSPeter Avalos
420e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &status)) != 0)
42150a69bb5SSascha Wildner fatal_fr(r, "parse status");
42218de8d7fSPeter Avalos if (quiet)
423ee116499SAntonio Huete Jimenez debug("remote statvfs: %s", fx2txt(status));
42418de8d7fSPeter Avalos else
425ee116499SAntonio Huete Jimenez error("remote statvfs: %s", fx2txt(status));
426e9778795SPeter Avalos sshbuf_free(msg);
42718de8d7fSPeter Avalos return -1;
42818de8d7fSPeter Avalos } else if (type != SSH2_FXP_EXTENDED_REPLY) {
42918de8d7fSPeter Avalos fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
43018de8d7fSPeter Avalos SSH2_FXP_EXTENDED_REPLY, type);
43118de8d7fSPeter Avalos }
43218de8d7fSPeter Avalos
43336e94dc5SPeter Avalos memset(st, 0, sizeof(*st));
434e9778795SPeter Avalos if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 ||
435e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 ||
436e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 ||
437e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 ||
438e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 ||
439e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_files)) != 0 ||
440e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 ||
441e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 ||
442e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 ||
443e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &flag)) != 0 ||
444e9778795SPeter Avalos (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0)
44550a69bb5SSascha Wildner fatal_fr(r, "parse statvfs");
44618de8d7fSPeter Avalos
44718de8d7fSPeter Avalos st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
44818de8d7fSPeter Avalos st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
44918de8d7fSPeter Avalos
450e9778795SPeter Avalos sshbuf_free(msg);
45118de8d7fSPeter Avalos
45218de8d7fSPeter Avalos return 0;
45318de8d7fSPeter Avalos }
45418de8d7fSPeter Avalos
45518de8d7fSPeter Avalos struct sftp_conn *
sftp_init(int fd_in,int fd_out,u_int transfer_buflen,u_int num_requests,u_int64_t limit_kbps)456*ba1276acSMatthew Dillon sftp_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
4579f304aafSPeter Avalos u_int64_t limit_kbps)
45818de8d7fSPeter Avalos {
459e9778795SPeter Avalos u_char type;
460e9778795SPeter Avalos struct sshbuf *msg;
46118de8d7fSPeter Avalos struct sftp_conn *ret;
462e9778795SPeter Avalos int r;
46318de8d7fSPeter Avalos
46436e94dc5SPeter Avalos ret = xcalloc(1, sizeof(*ret));
46536e94dc5SPeter Avalos ret->msg_id = 1;
4669f304aafSPeter Avalos ret->fd_in = fd_in;
4679f304aafSPeter Avalos ret->fd_out = fd_out;
46850a69bb5SSascha Wildner ret->download_buflen = ret->upload_buflen =
46950a69bb5SSascha Wildner transfer_buflen ? transfer_buflen : DEFAULT_COPY_BUFLEN;
47050a69bb5SSascha Wildner ret->num_requests =
47150a69bb5SSascha Wildner num_requests ? num_requests : DEFAULT_NUM_REQUESTS;
4729f304aafSPeter Avalos ret->exts = 0;
4739f304aafSPeter Avalos ret->limit_kbps = 0;
4749f304aafSPeter Avalos
475e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
47650a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
477e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 ||
478e9778795SPeter Avalos (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0)
47950a69bb5SSascha Wildner fatal_fr(r, "parse");
48018de8d7fSPeter Avalos
48150a69bb5SSascha Wildner send_msg(ret, msg);
48218de8d7fSPeter Avalos
483664f4763Szrj get_msg_extended(ret, msg, 1);
48418de8d7fSPeter Avalos
48518de8d7fSPeter Avalos /* Expecting a VERSION reply */
486e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0)
48750a69bb5SSascha Wildner fatal_fr(r, "parse type");
488e9778795SPeter Avalos if (type != SSH2_FXP_VERSION) {
48918de8d7fSPeter Avalos error("Invalid packet back from SSH2_FXP_INIT (type %u)",
49018de8d7fSPeter Avalos type);
491e9778795SPeter Avalos sshbuf_free(msg);
492e9778795SPeter Avalos free(ret);
49318de8d7fSPeter Avalos return(NULL);
49418de8d7fSPeter Avalos }
495e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &ret->version)) != 0)
49650a69bb5SSascha Wildner fatal_fr(r, "parse version");
49718de8d7fSPeter Avalos
4989f304aafSPeter Avalos debug2("Remote version: %u", ret->version);
49918de8d7fSPeter Avalos
50018de8d7fSPeter Avalos /* Check for extensions */
501e9778795SPeter Avalos while (sshbuf_len(msg) > 0) {
502e9778795SPeter Avalos char *name;
503e9778795SPeter Avalos u_char *value;
504e9778795SPeter Avalos size_t vlen;
50518de8d7fSPeter Avalos int known = 0;
50618de8d7fSPeter Avalos
507e9778795SPeter Avalos if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 ||
508e9778795SPeter Avalos (r = sshbuf_get_string(msg, &value, &vlen)) != 0)
50950a69bb5SSascha Wildner fatal_fr(r, "parse extension");
51018de8d7fSPeter Avalos if (strcmp(name, "posix-rename@openssh.com") == 0 &&
511e9778795SPeter Avalos strcmp((char *)value, "1") == 0) {
5129f304aafSPeter Avalos ret->exts |= SFTP_EXT_POSIX_RENAME;
51318de8d7fSPeter Avalos known = 1;
51418de8d7fSPeter Avalos } else if (strcmp(name, "statvfs@openssh.com") == 0 &&
515e9778795SPeter Avalos strcmp((char *)value, "2") == 0) {
5169f304aafSPeter Avalos ret->exts |= SFTP_EXT_STATVFS;
51718de8d7fSPeter Avalos known = 1;
5189f304aafSPeter Avalos } else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
519e9778795SPeter Avalos strcmp((char *)value, "2") == 0) {
5209f304aafSPeter Avalos ret->exts |= SFTP_EXT_FSTATVFS;
5219f304aafSPeter Avalos known = 1;
5229f304aafSPeter Avalos } else if (strcmp(name, "hardlink@openssh.com") == 0 &&
523e9778795SPeter Avalos strcmp((char *)value, "1") == 0) {
5249f304aafSPeter Avalos ret->exts |= SFTP_EXT_HARDLINK;
52518de8d7fSPeter Avalos known = 1;
52636e94dc5SPeter Avalos } else if (strcmp(name, "fsync@openssh.com") == 0 &&
527e9778795SPeter Avalos strcmp((char *)value, "1") == 0) {
52836e94dc5SPeter Avalos ret->exts |= SFTP_EXT_FSYNC;
52936e94dc5SPeter Avalos known = 1;
530664f4763Szrj } else if (strcmp(name, "lsetstat@openssh.com") == 0 &&
531664f4763Szrj strcmp((char *)value, "1") == 0) {
532664f4763Szrj ret->exts |= SFTP_EXT_LSETSTAT;
533664f4763Szrj known = 1;
53450a69bb5SSascha Wildner } else if (strcmp(name, "limits@openssh.com") == 0 &&
53550a69bb5SSascha Wildner strcmp((char *)value, "1") == 0) {
53650a69bb5SSascha Wildner ret->exts |= SFTP_EXT_LIMITS;
53750a69bb5SSascha Wildner known = 1;
53850a69bb5SSascha Wildner } else if (strcmp(name, "expand-path@openssh.com") == 0 &&
53950a69bb5SSascha Wildner strcmp((char *)value, "1") == 0) {
54050a69bb5SSascha Wildner ret->exts |= SFTP_EXT_PATH_EXPAND;
54150a69bb5SSascha Wildner known = 1;
542ee116499SAntonio Huete Jimenez } else if (strcmp(name, "copy-data") == 0 &&
543ee116499SAntonio Huete Jimenez strcmp((char *)value, "1") == 0) {
544ee116499SAntonio Huete Jimenez ret->exts |= SFTP_EXT_COPY_DATA;
545ee116499SAntonio Huete Jimenez known = 1;
546ee116499SAntonio Huete Jimenez } else if (strcmp(name,
547ee116499SAntonio Huete Jimenez "users-groups-by-id@openssh.com") == 0 &&
548ee116499SAntonio Huete Jimenez strcmp((char *)value, "1") == 0) {
549ee116499SAntonio Huete Jimenez ret->exts |= SFTP_EXT_GETUSERSGROUPS_BY_ID;
550ee116499SAntonio Huete Jimenez known = 1;
55118de8d7fSPeter Avalos }
55218de8d7fSPeter Avalos if (known) {
55318de8d7fSPeter Avalos debug2("Server supports extension \"%s\" revision %s",
55418de8d7fSPeter Avalos name, value);
55518de8d7fSPeter Avalos } else {
55618de8d7fSPeter Avalos debug2("Unrecognised server extension \"%s\"", name);
55718de8d7fSPeter Avalos }
55836e94dc5SPeter Avalos free(name);
55936e94dc5SPeter Avalos free(value);
56018de8d7fSPeter Avalos }
56118de8d7fSPeter Avalos
562e9778795SPeter Avalos sshbuf_free(msg);
56318de8d7fSPeter Avalos
56450a69bb5SSascha Wildner /* Query the server for its limits */
56550a69bb5SSascha Wildner if (ret->exts & SFTP_EXT_LIMITS) {
56650a69bb5SSascha Wildner struct sftp_limits limits;
567*ba1276acSMatthew Dillon if (sftp_get_limits(ret, &limits) != 0)
56850a69bb5SSascha Wildner fatal_f("limits failed");
56950a69bb5SSascha Wildner
57050a69bb5SSascha Wildner /* If the caller did not specify, find a good value */
57150a69bb5SSascha Wildner if (transfer_buflen == 0) {
572*ba1276acSMatthew Dillon ret->download_buflen = MINIMUM(limits.read_length,
573*ba1276acSMatthew Dillon SFTP_MAX_MSG_LENGTH - 1024);
574*ba1276acSMatthew Dillon ret->upload_buflen = MINIMUM(limits.write_length,
575*ba1276acSMatthew Dillon SFTP_MAX_MSG_LENGTH - 1024);
576*ba1276acSMatthew Dillon ret->download_buflen = MAXIMUM(ret->download_buflen, 64);
577*ba1276acSMatthew Dillon ret->upload_buflen = MAXIMUM(ret->upload_buflen, 64);
578*ba1276acSMatthew Dillon debug3("server upload/download buffer sizes "
579*ba1276acSMatthew Dillon "%llu / %llu; using %u / %u",
580*ba1276acSMatthew Dillon (unsigned long long)limits.write_length,
581*ba1276acSMatthew Dillon (unsigned long long)limits.read_length,
582*ba1276acSMatthew Dillon ret->upload_buflen, ret->download_buflen);
58350a69bb5SSascha Wildner }
58450a69bb5SSascha Wildner
58550a69bb5SSascha Wildner /* Use the server limit to scale down our value only */
58650a69bb5SSascha Wildner if (num_requests == 0 && limits.open_handles) {
58750a69bb5SSascha Wildner ret->num_requests =
58850a69bb5SSascha Wildner MINIMUM(DEFAULT_NUM_REQUESTS, limits.open_handles);
589*ba1276acSMatthew Dillon if (ret->num_requests == 0)
590*ba1276acSMatthew Dillon ret->num_requests = 1;
591*ba1276acSMatthew Dillon debug3("server handle limit %llu; using %u",
59250a69bb5SSascha Wildner (unsigned long long)limits.open_handles,
59350a69bb5SSascha Wildner ret->num_requests);
59450a69bb5SSascha Wildner }
59550a69bb5SSascha Wildner }
59650a69bb5SSascha Wildner
59718de8d7fSPeter Avalos /* Some filexfer v.0 servers don't support large packets */
59850a69bb5SSascha Wildner if (ret->version == 0) {
59950a69bb5SSascha Wildner ret->download_buflen = MINIMUM(ret->download_buflen, 20480);
60050a69bb5SSascha Wildner ret->upload_buflen = MINIMUM(ret->upload_buflen, 20480);
60150a69bb5SSascha Wildner }
60218de8d7fSPeter Avalos
6039f304aafSPeter Avalos ret->limit_kbps = limit_kbps;
6049f304aafSPeter Avalos if (ret->limit_kbps > 0) {
6059f304aafSPeter Avalos bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
60650a69bb5SSascha Wildner ret->download_buflen);
6079f304aafSPeter Avalos bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
60850a69bb5SSascha Wildner ret->upload_buflen);
6099f304aafSPeter Avalos }
6109f304aafSPeter Avalos
6119f304aafSPeter Avalos return ret;
61218de8d7fSPeter Avalos }
61318de8d7fSPeter Avalos
61418de8d7fSPeter Avalos u_int
sftp_proto_version(struct sftp_conn * conn)61518de8d7fSPeter Avalos sftp_proto_version(struct sftp_conn *conn)
61618de8d7fSPeter Avalos {
6179f304aafSPeter Avalos return conn->version;
61818de8d7fSPeter Avalos }
61918de8d7fSPeter Avalos
62018de8d7fSPeter Avalos int
sftp_get_limits(struct sftp_conn * conn,struct sftp_limits * limits)621*ba1276acSMatthew Dillon sftp_get_limits(struct sftp_conn *conn, struct sftp_limits *limits)
62250a69bb5SSascha Wildner {
62350a69bb5SSascha Wildner u_int id, msg_id;
62450a69bb5SSascha Wildner u_char type;
62550a69bb5SSascha Wildner struct sshbuf *msg;
62650a69bb5SSascha Wildner int r;
62750a69bb5SSascha Wildner
62850a69bb5SSascha Wildner if ((conn->exts & SFTP_EXT_LIMITS) == 0) {
62950a69bb5SSascha Wildner error("Server does not support limits@openssh.com extension");
63050a69bb5SSascha Wildner return -1;
63150a69bb5SSascha Wildner }
63250a69bb5SSascha Wildner
63350a69bb5SSascha Wildner if ((msg = sshbuf_new()) == NULL)
63450a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
63550a69bb5SSascha Wildner
63650a69bb5SSascha Wildner id = conn->msg_id++;
63750a69bb5SSascha Wildner if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
63850a69bb5SSascha Wildner (r = sshbuf_put_u32(msg, id)) != 0 ||
63950a69bb5SSascha Wildner (r = sshbuf_put_cstring(msg, "limits@openssh.com")) != 0)
64050a69bb5SSascha Wildner fatal_fr(r, "compose");
64150a69bb5SSascha Wildner send_msg(conn, msg);
64250a69bb5SSascha Wildner debug3("Sent message limits@openssh.com I:%u", id);
64350a69bb5SSascha Wildner
64450a69bb5SSascha Wildner get_msg(conn, msg);
64550a69bb5SSascha Wildner
64650a69bb5SSascha Wildner if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
64750a69bb5SSascha Wildner (r = sshbuf_get_u32(msg, &msg_id)) != 0)
64850a69bb5SSascha Wildner fatal_fr(r, "parse");
64950a69bb5SSascha Wildner
65050a69bb5SSascha Wildner debug3("Received limits reply T:%u I:%u", type, msg_id);
65150a69bb5SSascha Wildner if (id != msg_id)
65250a69bb5SSascha Wildner fatal("ID mismatch (%u != %u)", msg_id, id);
65350a69bb5SSascha Wildner if (type != SSH2_FXP_EXTENDED_REPLY) {
65450a69bb5SSascha Wildner debug_f("expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
65550a69bb5SSascha Wildner SSH2_FXP_EXTENDED_REPLY, type);
65650a69bb5SSascha Wildner /* Disable the limits extension */
65750a69bb5SSascha Wildner conn->exts &= ~SFTP_EXT_LIMITS;
65850a69bb5SSascha Wildner sshbuf_free(msg);
659*ba1276acSMatthew Dillon return -1;
66050a69bb5SSascha Wildner }
66150a69bb5SSascha Wildner
66250a69bb5SSascha Wildner memset(limits, 0, sizeof(*limits));
66350a69bb5SSascha Wildner if ((r = sshbuf_get_u64(msg, &limits->packet_length)) != 0 ||
66450a69bb5SSascha Wildner (r = sshbuf_get_u64(msg, &limits->read_length)) != 0 ||
66550a69bb5SSascha Wildner (r = sshbuf_get_u64(msg, &limits->write_length)) != 0 ||
66650a69bb5SSascha Wildner (r = sshbuf_get_u64(msg, &limits->open_handles)) != 0)
66750a69bb5SSascha Wildner fatal_fr(r, "parse limits");
66850a69bb5SSascha Wildner
66950a69bb5SSascha Wildner sshbuf_free(msg);
67050a69bb5SSascha Wildner
67150a69bb5SSascha Wildner return 0;
67250a69bb5SSascha Wildner }
67350a69bb5SSascha Wildner
67450a69bb5SSascha Wildner int
sftp_close(struct sftp_conn * conn,const u_char * handle,u_int handle_len)675*ba1276acSMatthew Dillon sftp_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len)
67618de8d7fSPeter Avalos {
67718de8d7fSPeter Avalos u_int id, status;
678e9778795SPeter Avalos struct sshbuf *msg;
679e9778795SPeter Avalos int r;
68018de8d7fSPeter Avalos
681e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
68250a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
68318de8d7fSPeter Avalos
68418de8d7fSPeter Avalos id = conn->msg_id++;
685e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 ||
686e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
687e9778795SPeter Avalos (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
68850a69bb5SSascha Wildner fatal_fr(r, "parse");
689e9778795SPeter Avalos send_msg(conn, msg);
69018de8d7fSPeter Avalos debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
69118de8d7fSPeter Avalos
6929f304aafSPeter Avalos status = get_status(conn, id);
69318de8d7fSPeter Avalos if (status != SSH2_FX_OK)
694ee116499SAntonio Huete Jimenez error("close remote: %s", fx2txt(status));
69518de8d7fSPeter Avalos
696e9778795SPeter Avalos sshbuf_free(msg);
69718de8d7fSPeter Avalos
698e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
69918de8d7fSPeter Avalos }
70018de8d7fSPeter Avalos
70118de8d7fSPeter Avalos
70218de8d7fSPeter Avalos static int
sftp_lsreaddir(struct sftp_conn * conn,const char * path,int print_flag,SFTP_DIRENT *** dir)703*ba1276acSMatthew Dillon sftp_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
70418de8d7fSPeter Avalos SFTP_DIRENT ***dir)
70518de8d7fSPeter Avalos {
706e9778795SPeter Avalos struct sshbuf *msg;
707e9778795SPeter Avalos u_int count, id, i, expected_id, ents = 0;
708e9778795SPeter Avalos size_t handle_len;
709e9778795SPeter Avalos u_char type, *handle;
71036e94dc5SPeter Avalos int status = SSH2_FX_FAILURE;
711e9778795SPeter Avalos int r;
71236e94dc5SPeter Avalos
71336e94dc5SPeter Avalos if (dir)
71436e94dc5SPeter Avalos *dir = NULL;
71518de8d7fSPeter Avalos
71618de8d7fSPeter Avalos id = conn->msg_id++;
71718de8d7fSPeter Avalos
718e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
71950a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
720e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 ||
721e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
722e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, path)) != 0)
72350a69bb5SSascha Wildner fatal_fr(r, "compose OPENDIR");
724e9778795SPeter Avalos send_msg(conn, msg);
72518de8d7fSPeter Avalos
7269f304aafSPeter Avalos handle = get_handle(conn, id, &handle_len,
727856ea928SPeter Avalos "remote readdir(\"%s\")", path);
72899e85e0dSPeter Avalos if (handle == NULL) {
729e9778795SPeter Avalos sshbuf_free(msg);
7309f304aafSPeter Avalos return -1;
73199e85e0dSPeter Avalos }
73218de8d7fSPeter Avalos
73318de8d7fSPeter Avalos if (dir) {
73418de8d7fSPeter Avalos ents = 0;
73536e94dc5SPeter Avalos *dir = xcalloc(1, sizeof(**dir));
73618de8d7fSPeter Avalos (*dir)[0] = NULL;
73718de8d7fSPeter Avalos }
73818de8d7fSPeter Avalos
73918de8d7fSPeter Avalos for (; !interrupted;) {
74018de8d7fSPeter Avalos id = expected_id = conn->msg_id++;
74118de8d7fSPeter Avalos
74218de8d7fSPeter Avalos debug3("Sending SSH2_FXP_READDIR I:%u", id);
74318de8d7fSPeter Avalos
744e9778795SPeter Avalos sshbuf_reset(msg);
745e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 ||
746e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
747e9778795SPeter Avalos (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
74850a69bb5SSascha Wildner fatal_fr(r, "compose READDIR");
749e9778795SPeter Avalos send_msg(conn, msg);
75018de8d7fSPeter Avalos
751e9778795SPeter Avalos sshbuf_reset(msg);
75218de8d7fSPeter Avalos
753e9778795SPeter Avalos get_msg(conn, msg);
75418de8d7fSPeter Avalos
755e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
756e9778795SPeter Avalos (r = sshbuf_get_u32(msg, &id)) != 0)
75750a69bb5SSascha Wildner fatal_fr(r, "parse");
75818de8d7fSPeter Avalos
75918de8d7fSPeter Avalos debug3("Received reply T:%u I:%u", type, id);
76018de8d7fSPeter Avalos
76118de8d7fSPeter Avalos if (id != expected_id)
76218de8d7fSPeter Avalos fatal("ID mismatch (%u != %u)", id, expected_id);
76318de8d7fSPeter Avalos
76418de8d7fSPeter Avalos if (type == SSH2_FXP_STATUS) {
765e9778795SPeter Avalos u_int rstatus;
766e9778795SPeter Avalos
767e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &rstatus)) != 0)
76850a69bb5SSascha Wildner fatal_fr(r, "parse status");
769e9778795SPeter Avalos debug3("Received SSH2_FXP_STATUS %d", rstatus);
770e9778795SPeter Avalos if (rstatus == SSH2_FX_EOF)
77118de8d7fSPeter Avalos break;
772e9778795SPeter Avalos error("Couldn't read directory: %s", fx2txt(rstatus));
77336e94dc5SPeter Avalos goto out;
77418de8d7fSPeter Avalos } else if (type != SSH2_FXP_NAME)
77518de8d7fSPeter Avalos fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
77618de8d7fSPeter Avalos SSH2_FXP_NAME, type);
77718de8d7fSPeter Avalos
778e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &count)) != 0)
77950a69bb5SSascha Wildner fatal_fr(r, "parse count");
780ce74bacaSMatthew Dillon if (count > SSHBUF_SIZE_MAX)
78150a69bb5SSascha Wildner fatal_f("nonsensical number of entries");
78218de8d7fSPeter Avalos if (count == 0)
78318de8d7fSPeter Avalos break;
78418de8d7fSPeter Avalos debug3("Received %d SSH2_FXP_NAME responses", count);
78518de8d7fSPeter Avalos for (i = 0; i < count; i++) {
78618de8d7fSPeter Avalos char *filename, *longname;
787e9778795SPeter Avalos Attrib a;
78818de8d7fSPeter Avalos
789e9778795SPeter Avalos if ((r = sshbuf_get_cstring(msg, &filename,
790e9778795SPeter Avalos NULL)) != 0 ||
791e9778795SPeter Avalos (r = sshbuf_get_cstring(msg, &longname,
792e9778795SPeter Avalos NULL)) != 0)
79350a69bb5SSascha Wildner fatal_fr(r, "parse filenames");
794e9778795SPeter Avalos if ((r = decode_attrib(msg, &a)) != 0) {
79550a69bb5SSascha Wildner error_fr(r, "couldn't decode attrib");
796e9778795SPeter Avalos free(filename);
797e9778795SPeter Avalos free(longname);
7980cbfa66cSDaniel Fojt goto out;
799e9778795SPeter Avalos }
80018de8d7fSPeter Avalos
80136e94dc5SPeter Avalos if (print_flag)
802e9778795SPeter Avalos mprintf("%s\n", longname);
80318de8d7fSPeter Avalos
804856ea928SPeter Avalos /*
805856ea928SPeter Avalos * Directory entries should never contain '/'
806856ea928SPeter Avalos * These can be used to attack recursive ops
807856ea928SPeter Avalos * (e.g. send '../../../../etc/passwd')
808856ea928SPeter Avalos */
809ce74bacaSMatthew Dillon if (strpbrk(filename, SFTP_DIRECTORY_CHARS) != NULL) {
810856ea928SPeter Avalos error("Server sent suspect path \"%s\" "
811856ea928SPeter Avalos "during readdir of \"%s\"", filename, path);
81236e94dc5SPeter Avalos } else if (dir) {
813e9778795SPeter Avalos *dir = xreallocarray(*dir, ents + 2, sizeof(**dir));
81436e94dc5SPeter Avalos (*dir)[ents] = xcalloc(1, sizeof(***dir));
81518de8d7fSPeter Avalos (*dir)[ents]->filename = xstrdup(filename);
81618de8d7fSPeter Avalos (*dir)[ents]->longname = xstrdup(longname);
817e9778795SPeter Avalos memcpy(&(*dir)[ents]->a, &a, sizeof(a));
81818de8d7fSPeter Avalos (*dir)[++ents] = NULL;
81918de8d7fSPeter Avalos }
82036e94dc5SPeter Avalos free(filename);
82136e94dc5SPeter Avalos free(longname);
82218de8d7fSPeter Avalos }
82318de8d7fSPeter Avalos }
82436e94dc5SPeter Avalos status = 0;
82518de8d7fSPeter Avalos
82636e94dc5SPeter Avalos out:
827e9778795SPeter Avalos sshbuf_free(msg);
828*ba1276acSMatthew Dillon sftp_close(conn, handle, handle_len);
82936e94dc5SPeter Avalos free(handle);
83018de8d7fSPeter Avalos
83136e94dc5SPeter Avalos if (status != 0 && dir != NULL) {
83236e94dc5SPeter Avalos /* Don't return results on error */
833*ba1276acSMatthew Dillon sftp_free_dirents(*dir);
83436e94dc5SPeter Avalos *dir = NULL;
83536e94dc5SPeter Avalos } else if (interrupted && dir != NULL && *dir != NULL) {
83636e94dc5SPeter Avalos /* Don't return partial matches on interrupt */
837*ba1276acSMatthew Dillon sftp_free_dirents(*dir);
83836e94dc5SPeter Avalos *dir = xcalloc(1, sizeof(**dir));
83918de8d7fSPeter Avalos **dir = NULL;
84018de8d7fSPeter Avalos }
84118de8d7fSPeter Avalos
842664f4763Szrj return status == SSH2_FX_OK ? 0 : -1;
84318de8d7fSPeter Avalos }
84418de8d7fSPeter Avalos
84518de8d7fSPeter Avalos int
sftp_readdir(struct sftp_conn * conn,const char * path,SFTP_DIRENT *** dir)846*ba1276acSMatthew Dillon sftp_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir)
84718de8d7fSPeter Avalos {
848*ba1276acSMatthew Dillon return sftp_lsreaddir(conn, path, 0, dir);
84918de8d7fSPeter Avalos }
85018de8d7fSPeter Avalos
sftp_free_dirents(SFTP_DIRENT ** s)851*ba1276acSMatthew Dillon void sftp_free_dirents(SFTP_DIRENT **s)
85218de8d7fSPeter Avalos {
85318de8d7fSPeter Avalos int i;
85418de8d7fSPeter Avalos
85536e94dc5SPeter Avalos if (s == NULL)
85636e94dc5SPeter Avalos return;
85718de8d7fSPeter Avalos for (i = 0; s[i]; i++) {
85836e94dc5SPeter Avalos free(s[i]->filename);
85936e94dc5SPeter Avalos free(s[i]->longname);
86036e94dc5SPeter Avalos free(s[i]);
86118de8d7fSPeter Avalos }
86236e94dc5SPeter Avalos free(s);
86318de8d7fSPeter Avalos }
86418de8d7fSPeter Avalos
86518de8d7fSPeter Avalos int
sftp_rm(struct sftp_conn * conn,const char * path)866*ba1276acSMatthew Dillon sftp_rm(struct sftp_conn *conn, const char *path)
86718de8d7fSPeter Avalos {
86818de8d7fSPeter Avalos u_int status, id;
86918de8d7fSPeter Avalos
87018de8d7fSPeter Avalos debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
87118de8d7fSPeter Avalos
87218de8d7fSPeter Avalos id = conn->msg_id++;
8739f304aafSPeter Avalos send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
8749f304aafSPeter Avalos status = get_status(conn, id);
87518de8d7fSPeter Avalos if (status != SSH2_FX_OK)
876ee116499SAntonio Huete Jimenez error("remote delete %s: %s", path, fx2txt(status));
877e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
87818de8d7fSPeter Avalos }
87918de8d7fSPeter Avalos
88018de8d7fSPeter Avalos int
sftp_mkdir(struct sftp_conn * conn,const char * path,Attrib * a,int print_flag)881*ba1276acSMatthew Dillon sftp_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag)
88218de8d7fSPeter Avalos {
88318de8d7fSPeter Avalos u_int status, id;
88418de8d7fSPeter Avalos
885ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_MKDIR \"%s\"", path);
886ee116499SAntonio Huete Jimenez
88718de8d7fSPeter Avalos id = conn->msg_id++;
8889f304aafSPeter Avalos send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
88918de8d7fSPeter Avalos strlen(path), a);
89018de8d7fSPeter Avalos
8919f304aafSPeter Avalos status = get_status(conn, id);
89236e94dc5SPeter Avalos if (status != SSH2_FX_OK && print_flag)
893ee116499SAntonio Huete Jimenez error("remote mkdir \"%s\": %s", path, fx2txt(status));
89418de8d7fSPeter Avalos
895e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
89618de8d7fSPeter Avalos }
89718de8d7fSPeter Avalos
89818de8d7fSPeter Avalos int
sftp_rmdir(struct sftp_conn * conn,const char * path)899*ba1276acSMatthew Dillon sftp_rmdir(struct sftp_conn *conn, const char *path)
90018de8d7fSPeter Avalos {
90118de8d7fSPeter Avalos u_int status, id;
90218de8d7fSPeter Avalos
903ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_RMDIR \"%s\"", path);
904ee116499SAntonio Huete Jimenez
90518de8d7fSPeter Avalos id = conn->msg_id++;
9069f304aafSPeter Avalos send_string_request(conn, id, SSH2_FXP_RMDIR, path,
90718de8d7fSPeter Avalos strlen(path));
90818de8d7fSPeter Avalos
9099f304aafSPeter Avalos status = get_status(conn, id);
91018de8d7fSPeter Avalos if (status != SSH2_FX_OK)
911ee116499SAntonio Huete Jimenez error("remote rmdir \"%s\": %s", path, fx2txt(status));
91218de8d7fSPeter Avalos
913e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
91418de8d7fSPeter Avalos }
91518de8d7fSPeter Avalos
916*ba1276acSMatthew Dillon int
sftp_stat(struct sftp_conn * conn,const char * path,int quiet,Attrib * a)917*ba1276acSMatthew Dillon sftp_stat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a)
91818de8d7fSPeter Avalos {
91918de8d7fSPeter Avalos u_int id;
92018de8d7fSPeter Avalos
921ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_STAT \"%s\"", path);
922ee116499SAntonio Huete Jimenez
92318de8d7fSPeter Avalos id = conn->msg_id++;
92418de8d7fSPeter Avalos
9259f304aafSPeter Avalos send_string_request(conn, id,
92618de8d7fSPeter Avalos conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
92718de8d7fSPeter Avalos path, strlen(path));
92818de8d7fSPeter Avalos
929*ba1276acSMatthew Dillon return get_decode_stat(conn, id, quiet, a);
93018de8d7fSPeter Avalos }
93118de8d7fSPeter Avalos
932*ba1276acSMatthew Dillon int
sftp_lstat(struct sftp_conn * conn,const char * path,int quiet,Attrib * a)933*ba1276acSMatthew Dillon sftp_lstat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a)
93418de8d7fSPeter Avalos {
93518de8d7fSPeter Avalos u_int id;
93618de8d7fSPeter Avalos
93718de8d7fSPeter Avalos if (conn->version == 0) {
938*ba1276acSMatthew Dillon do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO,
939*ba1276acSMatthew Dillon "Server version does not support lstat operation");
940*ba1276acSMatthew Dillon return sftp_stat(conn, path, quiet, a);
94118de8d7fSPeter Avalos }
94218de8d7fSPeter Avalos
94318de8d7fSPeter Avalos id = conn->msg_id++;
9449f304aafSPeter Avalos send_string_request(conn, id, SSH2_FXP_LSTAT, path,
94518de8d7fSPeter Avalos strlen(path));
94618de8d7fSPeter Avalos
947*ba1276acSMatthew Dillon return get_decode_stat(conn, id, quiet, a);
94818de8d7fSPeter Avalos }
94918de8d7fSPeter Avalos
95018de8d7fSPeter Avalos #ifdef notyet
951*ba1276acSMatthew Dillon int
sftp_fstat(struct sftp_conn * conn,const u_char * handle,u_int handle_len,int quiet,Attrib * a)952*ba1276acSMatthew Dillon sftp_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
953*ba1276acSMatthew Dillon int quiet, Attrib *a)
95418de8d7fSPeter Avalos {
95518de8d7fSPeter Avalos u_int id;
95618de8d7fSPeter Avalos
957ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_FSTAT \"%s\"");
958ee116499SAntonio Huete Jimenez
95918de8d7fSPeter Avalos id = conn->msg_id++;
9609f304aafSPeter Avalos send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
96118de8d7fSPeter Avalos handle_len);
96218de8d7fSPeter Avalos
963*ba1276acSMatthew Dillon return get_decode_stat(conn, id, quiet, a);
96418de8d7fSPeter Avalos }
96518de8d7fSPeter Avalos #endif
96618de8d7fSPeter Avalos
96718de8d7fSPeter Avalos int
sftp_setstat(struct sftp_conn * conn,const char * path,Attrib * a)968*ba1276acSMatthew Dillon sftp_setstat(struct sftp_conn *conn, const char *path, Attrib *a)
96918de8d7fSPeter Avalos {
97018de8d7fSPeter Avalos u_int status, id;
97118de8d7fSPeter Avalos
972ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_SETSTAT \"%s\"", path);
973ee116499SAntonio Huete Jimenez
97418de8d7fSPeter Avalos id = conn->msg_id++;
9759f304aafSPeter Avalos send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
97618de8d7fSPeter Avalos strlen(path), a);
97718de8d7fSPeter Avalos
9789f304aafSPeter Avalos status = get_status(conn, id);
97918de8d7fSPeter Avalos if (status != SSH2_FX_OK)
980ee116499SAntonio Huete Jimenez error("remote setstat \"%s\": %s", path, fx2txt(status));
98118de8d7fSPeter Avalos
982e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
98318de8d7fSPeter Avalos }
98418de8d7fSPeter Avalos
98518de8d7fSPeter Avalos int
sftp_fsetstat(struct sftp_conn * conn,const u_char * handle,u_int handle_len,Attrib * a)986*ba1276acSMatthew Dillon sftp_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
98718de8d7fSPeter Avalos Attrib *a)
98818de8d7fSPeter Avalos {
98918de8d7fSPeter Avalos u_int status, id;
99018de8d7fSPeter Avalos
991ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_FSETSTAT");
992ee116499SAntonio Huete Jimenez
99318de8d7fSPeter Avalos id = conn->msg_id++;
9949f304aafSPeter Avalos send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
99518de8d7fSPeter Avalos handle_len, a);
99618de8d7fSPeter Avalos
9979f304aafSPeter Avalos status = get_status(conn, id);
99818de8d7fSPeter Avalos if (status != SSH2_FX_OK)
999ee116499SAntonio Huete Jimenez error("remote fsetstat: %s", fx2txt(status));
100018de8d7fSPeter Avalos
1001e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
100218de8d7fSPeter Avalos }
100318de8d7fSPeter Avalos
100450a69bb5SSascha Wildner /* Implements both the realpath and expand-path operations */
100550a69bb5SSascha Wildner static char *
sftp_realpath_expand(struct sftp_conn * conn,const char * path,int expand)1006*ba1276acSMatthew Dillon sftp_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
100718de8d7fSPeter Avalos {
1008e9778795SPeter Avalos struct sshbuf *msg;
1009e9778795SPeter Avalos u_int expected_id, count, id;
101018de8d7fSPeter Avalos char *filename, *longname;
1011e9778795SPeter Avalos Attrib a;
1012e9778795SPeter Avalos u_char type;
1013e9778795SPeter Avalos int r;
101450a69bb5SSascha Wildner const char *what = "SSH2_FXP_REALPATH";
101550a69bb5SSascha Wildner
101650a69bb5SSascha Wildner if (expand)
101750a69bb5SSascha Wildner what = "expand-path@openssh.com";
101850a69bb5SSascha Wildner if ((msg = sshbuf_new()) == NULL)
101950a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
102018de8d7fSPeter Avalos
102118de8d7fSPeter Avalos expected_id = id = conn->msg_id++;
102250a69bb5SSascha Wildner if (expand) {
1023ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_EXTENDED(expand-path@openssh.com) "
1024ee116499SAntonio Huete Jimenez "\"%s\"", path);
102550a69bb5SSascha Wildner if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
102650a69bb5SSascha Wildner (r = sshbuf_put_u32(msg, id)) != 0 ||
102750a69bb5SSascha Wildner (r = sshbuf_put_cstring(msg,
102850a69bb5SSascha Wildner "expand-path@openssh.com")) != 0 ||
102950a69bb5SSascha Wildner (r = sshbuf_put_cstring(msg, path)) != 0)
103050a69bb5SSascha Wildner fatal_fr(r, "compose %s", what);
103150a69bb5SSascha Wildner send_msg(conn, msg);
103250a69bb5SSascha Wildner } else {
1033ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_REALPATH \"%s\"", path);
103450a69bb5SSascha Wildner send_string_request(conn, id, SSH2_FXP_REALPATH,
103550a69bb5SSascha Wildner path, strlen(path));
103650a69bb5SSascha Wildner }
1037e9778795SPeter Avalos get_msg(conn, msg);
1038e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1039e9778795SPeter Avalos (r = sshbuf_get_u32(msg, &id)) != 0)
104050a69bb5SSascha Wildner fatal_fr(r, "parse");
104118de8d7fSPeter Avalos
104218de8d7fSPeter Avalos if (id != expected_id)
104318de8d7fSPeter Avalos fatal("ID mismatch (%u != %u)", id, expected_id);
104418de8d7fSPeter Avalos
104518de8d7fSPeter Avalos if (type == SSH2_FXP_STATUS) {
1046e9778795SPeter Avalos u_int status;
1047ee116499SAntonio Huete Jimenez char *errmsg;
104818de8d7fSPeter Avalos
1049ee116499SAntonio Huete Jimenez if ((r = sshbuf_get_u32(msg, &status)) != 0 ||
1050ee116499SAntonio Huete Jimenez (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0)
105150a69bb5SSascha Wildner fatal_fr(r, "parse status");
1052ee116499SAntonio Huete Jimenez error("%s %s: %s", expand ? "expand" : "realpath",
1053ee116499SAntonio Huete Jimenez path, *errmsg == '\0' ? fx2txt(status) : errmsg);
1054ee116499SAntonio Huete Jimenez free(errmsg);
1055e9778795SPeter Avalos sshbuf_free(msg);
1056856ea928SPeter Avalos return NULL;
105718de8d7fSPeter Avalos } else if (type != SSH2_FXP_NAME)
105818de8d7fSPeter Avalos fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
105918de8d7fSPeter Avalos SSH2_FXP_NAME, type);
106018de8d7fSPeter Avalos
1061e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &count)) != 0)
106250a69bb5SSascha Wildner fatal_fr(r, "parse count");
106318de8d7fSPeter Avalos if (count != 1)
106450a69bb5SSascha Wildner fatal("Got multiple names (%d) from %s", count, what);
106518de8d7fSPeter Avalos
1066e9778795SPeter Avalos if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1067e9778795SPeter Avalos (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1068e9778795SPeter Avalos (r = decode_attrib(msg, &a)) != 0)
106950a69bb5SSascha Wildner fatal_fr(r, "parse filename/attrib");
107018de8d7fSPeter Avalos
107150a69bb5SSascha Wildner debug3("%s %s -> %s", what, path, filename);
107218de8d7fSPeter Avalos
107336e94dc5SPeter Avalos free(longname);
107418de8d7fSPeter Avalos
1075e9778795SPeter Avalos sshbuf_free(msg);
107618de8d7fSPeter Avalos
107718de8d7fSPeter Avalos return(filename);
107818de8d7fSPeter Avalos }
107918de8d7fSPeter Avalos
108050a69bb5SSascha Wildner char *
sftp_realpath(struct sftp_conn * conn,const char * path)1081*ba1276acSMatthew Dillon sftp_realpath(struct sftp_conn *conn, const char *path)
108250a69bb5SSascha Wildner {
1083*ba1276acSMatthew Dillon return sftp_realpath_expand(conn, path, 0);
108450a69bb5SSascha Wildner }
108550a69bb5SSascha Wildner
108650a69bb5SSascha Wildner int
sftp_can_expand_path(struct sftp_conn * conn)1087*ba1276acSMatthew Dillon sftp_can_expand_path(struct sftp_conn *conn)
108850a69bb5SSascha Wildner {
108950a69bb5SSascha Wildner return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0;
109050a69bb5SSascha Wildner }
109150a69bb5SSascha Wildner
109250a69bb5SSascha Wildner char *
sftp_expand_path(struct sftp_conn * conn,const char * path)1093*ba1276acSMatthew Dillon sftp_expand_path(struct sftp_conn *conn, const char *path)
109450a69bb5SSascha Wildner {
1095*ba1276acSMatthew Dillon if (!sftp_can_expand_path(conn)) {
109650a69bb5SSascha Wildner debug3_f("no server support, fallback to realpath");
1097*ba1276acSMatthew Dillon return sftp_realpath_expand(conn, path, 0);
109850a69bb5SSascha Wildner }
1099*ba1276acSMatthew Dillon return sftp_realpath_expand(conn, path, 1);
110050a69bb5SSascha Wildner }
110150a69bb5SSascha Wildner
110218de8d7fSPeter Avalos int
sftp_copy(struct sftp_conn * conn,const char * oldpath,const char * newpath)1103*ba1276acSMatthew Dillon sftp_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath)
1104ee116499SAntonio Huete Jimenez {
1105*ba1276acSMatthew Dillon Attrib junk, attr;
1106ee116499SAntonio Huete Jimenez struct sshbuf *msg;
1107ee116499SAntonio Huete Jimenez u_char *old_handle, *new_handle;
1108ee116499SAntonio Huete Jimenez u_int mode, status, id;
1109ee116499SAntonio Huete Jimenez size_t old_handle_len, new_handle_len;
1110ee116499SAntonio Huete Jimenez int r;
1111ee116499SAntonio Huete Jimenez
1112ee116499SAntonio Huete Jimenez /* Return if the extension is not supported */
1113ee116499SAntonio Huete Jimenez if ((conn->exts & SFTP_EXT_COPY_DATA) == 0) {
1114ee116499SAntonio Huete Jimenez error("Server does not support copy-data extension");
1115ee116499SAntonio Huete Jimenez return -1;
1116ee116499SAntonio Huete Jimenez }
1117ee116499SAntonio Huete Jimenez
1118ee116499SAntonio Huete Jimenez /* Make sure the file exists, and we can copy its perms */
1119*ba1276acSMatthew Dillon if (sftp_stat(conn, oldpath, 0, &attr) != 0)
1120ee116499SAntonio Huete Jimenez return -1;
1121ee116499SAntonio Huete Jimenez
1122ee116499SAntonio Huete Jimenez /* Do not preserve set[ug]id here, as we do not preserve ownership */
1123*ba1276acSMatthew Dillon if (attr.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
1124*ba1276acSMatthew Dillon mode = attr.perm & 0777;
1125ee116499SAntonio Huete Jimenez
1126*ba1276acSMatthew Dillon if (!S_ISREG(attr.perm)) {
1127ee116499SAntonio Huete Jimenez error("Cannot copy non-regular file: %s", oldpath);
1128ee116499SAntonio Huete Jimenez return -1;
1129ee116499SAntonio Huete Jimenez }
1130ee116499SAntonio Huete Jimenez } else {
1131ee116499SAntonio Huete Jimenez /* NB: The user's umask will apply to this */
1132ee116499SAntonio Huete Jimenez mode = 0666;
1133ee116499SAntonio Huete Jimenez }
1134ee116499SAntonio Huete Jimenez
1135ee116499SAntonio Huete Jimenez /* Set up the new perms for the new file */
1136*ba1276acSMatthew Dillon attrib_clear(&attr);
1137*ba1276acSMatthew Dillon attr.perm = mode;
1138*ba1276acSMatthew Dillon attr.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1139ee116499SAntonio Huete Jimenez
1140ee116499SAntonio Huete Jimenez if ((msg = sshbuf_new()) == NULL)
1141ee116499SAntonio Huete Jimenez fatal("%s: sshbuf_new failed", __func__);
1142ee116499SAntonio Huete Jimenez
1143ee116499SAntonio Huete Jimenez attrib_clear(&junk); /* Send empty attributes */
1144ee116499SAntonio Huete Jimenez
1145ee116499SAntonio Huete Jimenez /* Open the old file for reading */
1146ee116499SAntonio Huete Jimenez id = conn->msg_id++;
1147ee116499SAntonio Huete Jimenez if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1148ee116499SAntonio Huete Jimenez (r = sshbuf_put_u32(msg, id)) != 0 ||
1149ee116499SAntonio Huete Jimenez (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1150ee116499SAntonio Huete Jimenez (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 ||
1151ee116499SAntonio Huete Jimenez (r = encode_attrib(msg, &junk)) != 0)
1152ee116499SAntonio Huete Jimenez fatal("%s: buffer error: %s", __func__, ssh_err(r));
1153ee116499SAntonio Huete Jimenez send_msg(conn, msg);
1154ee116499SAntonio Huete Jimenez debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, oldpath);
1155ee116499SAntonio Huete Jimenez
1156ee116499SAntonio Huete Jimenez sshbuf_reset(msg);
1157ee116499SAntonio Huete Jimenez
1158ee116499SAntonio Huete Jimenez old_handle = get_handle(conn, id, &old_handle_len,
1159ee116499SAntonio Huete Jimenez "remote open(\"%s\")", oldpath);
1160ee116499SAntonio Huete Jimenez if (old_handle == NULL) {
1161ee116499SAntonio Huete Jimenez sshbuf_free(msg);
1162ee116499SAntonio Huete Jimenez return -1;
1163ee116499SAntonio Huete Jimenez }
1164ee116499SAntonio Huete Jimenez
1165ee116499SAntonio Huete Jimenez /* Open the new file for writing */
1166ee116499SAntonio Huete Jimenez id = conn->msg_id++;
1167ee116499SAntonio Huete Jimenez if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
1168ee116499SAntonio Huete Jimenez (r = sshbuf_put_u32(msg, id)) != 0 ||
1169ee116499SAntonio Huete Jimenez (r = sshbuf_put_cstring(msg, newpath)) != 0 ||
1170ee116499SAntonio Huete Jimenez (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|
1171ee116499SAntonio Huete Jimenez SSH2_FXF_TRUNC)) != 0 ||
1172*ba1276acSMatthew Dillon (r = encode_attrib(msg, &attr)) != 0)
1173ee116499SAntonio Huete Jimenez fatal("%s: buffer error: %s", __func__, ssh_err(r));
1174ee116499SAntonio Huete Jimenez send_msg(conn, msg);
1175ee116499SAntonio Huete Jimenez debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath);
1176ee116499SAntonio Huete Jimenez
1177ee116499SAntonio Huete Jimenez sshbuf_reset(msg);
1178ee116499SAntonio Huete Jimenez
1179ee116499SAntonio Huete Jimenez new_handle = get_handle(conn, id, &new_handle_len,
1180ee116499SAntonio Huete Jimenez "remote open(\"%s\")", newpath);
1181ee116499SAntonio Huete Jimenez if (new_handle == NULL) {
1182ee116499SAntonio Huete Jimenez sshbuf_free(msg);
1183ee116499SAntonio Huete Jimenez free(old_handle);
1184ee116499SAntonio Huete Jimenez return -1;
1185ee116499SAntonio Huete Jimenez }
1186ee116499SAntonio Huete Jimenez
1187ee116499SAntonio Huete Jimenez /* Copy the file data */
1188ee116499SAntonio Huete Jimenez id = conn->msg_id++;
1189ee116499SAntonio Huete Jimenez if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1190ee116499SAntonio Huete Jimenez (r = sshbuf_put_u32(msg, id)) != 0 ||
1191ee116499SAntonio Huete Jimenez (r = sshbuf_put_cstring(msg, "copy-data")) != 0 ||
1192ee116499SAntonio Huete Jimenez (r = sshbuf_put_string(msg, old_handle, old_handle_len)) != 0 ||
1193ee116499SAntonio Huete Jimenez (r = sshbuf_put_u64(msg, 0)) != 0 ||
1194ee116499SAntonio Huete Jimenez (r = sshbuf_put_u64(msg, 0)) != 0 ||
1195ee116499SAntonio Huete Jimenez (r = sshbuf_put_string(msg, new_handle, new_handle_len)) != 0 ||
1196ee116499SAntonio Huete Jimenez (r = sshbuf_put_u64(msg, 0)) != 0)
1197ee116499SAntonio Huete Jimenez fatal("%s: buffer error: %s", __func__, ssh_err(r));
1198ee116499SAntonio Huete Jimenez send_msg(conn, msg);
1199ee116499SAntonio Huete Jimenez debug3("Sent message copy-data \"%s\" 0 0 -> \"%s\" 0",
1200ee116499SAntonio Huete Jimenez oldpath, newpath);
1201ee116499SAntonio Huete Jimenez
1202ee116499SAntonio Huete Jimenez status = get_status(conn, id);
1203ee116499SAntonio Huete Jimenez if (status != SSH2_FX_OK)
1204ee116499SAntonio Huete Jimenez error("Couldn't copy file \"%s\" to \"%s\": %s", oldpath,
1205ee116499SAntonio Huete Jimenez newpath, fx2txt(status));
1206ee116499SAntonio Huete Jimenez
1207ee116499SAntonio Huete Jimenez /* Clean up everything */
1208ee116499SAntonio Huete Jimenez sshbuf_free(msg);
1209*ba1276acSMatthew Dillon sftp_close(conn, old_handle, old_handle_len);
1210*ba1276acSMatthew Dillon sftp_close(conn, new_handle, new_handle_len);
1211ee116499SAntonio Huete Jimenez free(old_handle);
1212ee116499SAntonio Huete Jimenez free(new_handle);
1213ee116499SAntonio Huete Jimenez
1214ee116499SAntonio Huete Jimenez return status == SSH2_FX_OK ? 0 : -1;
1215ee116499SAntonio Huete Jimenez }
1216ee116499SAntonio Huete Jimenez
1217ee116499SAntonio Huete Jimenez int
sftp_rename(struct sftp_conn * conn,const char * oldpath,const char * newpath,int force_legacy)1218*ba1276acSMatthew Dillon sftp_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
121936e94dc5SPeter Avalos int force_legacy)
122018de8d7fSPeter Avalos {
1221e9778795SPeter Avalos struct sshbuf *msg;
122218de8d7fSPeter Avalos u_int status, id;
1223e9778795SPeter Avalos int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy;
122418de8d7fSPeter Avalos
1225e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
122650a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
122718de8d7fSPeter Avalos
122818de8d7fSPeter Avalos /* Send rename request */
122918de8d7fSPeter Avalos id = conn->msg_id++;
123036e94dc5SPeter Avalos if (use_ext) {
1231ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_EXTENDED(posix-rename@openssh.com) "
1232ee116499SAntonio Huete Jimenez "\"%s\" to \"%s\"", oldpath, newpath);
1233e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1234e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
1235e9778795SPeter Avalos (r = sshbuf_put_cstring(msg,
1236e9778795SPeter Avalos "posix-rename@openssh.com")) != 0)
123750a69bb5SSascha Wildner fatal_fr(r, "compose posix-rename");
123818de8d7fSPeter Avalos } else {
1239ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_RENAME \"%s\" to \"%s\"",
1240ee116499SAntonio Huete Jimenez oldpath, newpath);
1241e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 ||
1242e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0)
124350a69bb5SSascha Wildner fatal_fr(r, "compose rename");
124418de8d7fSPeter Avalos }
1245e9778795SPeter Avalos if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1246e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, newpath)) != 0)
124750a69bb5SSascha Wildner fatal_fr(r, "compose paths");
1248e9778795SPeter Avalos send_msg(conn, msg);
124918de8d7fSPeter Avalos debug3("Sent message %s \"%s\" -> \"%s\"",
1250e9778795SPeter Avalos use_ext ? "posix-rename@openssh.com" :
1251e9778795SPeter Avalos "SSH2_FXP_RENAME", oldpath, newpath);
1252e9778795SPeter Avalos sshbuf_free(msg);
125318de8d7fSPeter Avalos
12549f304aafSPeter Avalos status = get_status(conn, id);
125518de8d7fSPeter Avalos if (status != SSH2_FX_OK)
1256ee116499SAntonio Huete Jimenez error("remote rename \"%s\" to \"%s\": %s", oldpath,
125718de8d7fSPeter Avalos newpath, fx2txt(status));
125818de8d7fSPeter Avalos
1259e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
126018de8d7fSPeter Avalos }
126118de8d7fSPeter Avalos
126218de8d7fSPeter Avalos int
sftp_hardlink(struct sftp_conn * conn,const char * oldpath,const char * newpath)1263*ba1276acSMatthew Dillon sftp_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
12649f304aafSPeter Avalos {
1265e9778795SPeter Avalos struct sshbuf *msg;
12669f304aafSPeter Avalos u_int status, id;
1267e9778795SPeter Avalos int r;
12689f304aafSPeter Avalos
12699f304aafSPeter Avalos if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
12709f304aafSPeter Avalos error("Server does not support hardlink@openssh.com extension");
12719f304aafSPeter Avalos return -1;
12729f304aafSPeter Avalos }
1273ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_EXTENDED(hardlink@openssh.com) "
1274ee116499SAntonio Huete Jimenez "\"%s\" to \"%s\"", oldpath, newpath);
12759f304aafSPeter Avalos
1276e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
127750a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
127899e85e0dSPeter Avalos
127999e85e0dSPeter Avalos /* Send link request */
128099e85e0dSPeter Avalos id = conn->msg_id++;
1281e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1282e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
1283e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 ||
1284e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1285e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, newpath)) != 0)
128650a69bb5SSascha Wildner fatal_fr(r, "compose");
1287e9778795SPeter Avalos send_msg(conn, msg);
12889f304aafSPeter Avalos debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
12899f304aafSPeter Avalos oldpath, newpath);
1290e9778795SPeter Avalos sshbuf_free(msg);
12919f304aafSPeter Avalos
12929f304aafSPeter Avalos status = get_status(conn, id);
12939f304aafSPeter Avalos if (status != SSH2_FX_OK)
1294ee116499SAntonio Huete Jimenez error("remote link \"%s\" to \"%s\": %s", oldpath,
12959f304aafSPeter Avalos newpath, fx2txt(status));
12969f304aafSPeter Avalos
1297e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
12989f304aafSPeter Avalos }
12999f304aafSPeter Avalos
13009f304aafSPeter Avalos int
sftp_symlink(struct sftp_conn * conn,const char * oldpath,const char * newpath)1301*ba1276acSMatthew Dillon sftp_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath)
130218de8d7fSPeter Avalos {
1303e9778795SPeter Avalos struct sshbuf *msg;
130418de8d7fSPeter Avalos u_int status, id;
1305e9778795SPeter Avalos int r;
130618de8d7fSPeter Avalos
130718de8d7fSPeter Avalos if (conn->version < 3) {
130818de8d7fSPeter Avalos error("This server does not support the symlink operation");
130918de8d7fSPeter Avalos return(SSH2_FX_OP_UNSUPPORTED);
131018de8d7fSPeter Avalos }
1311ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_SYMLINK \"%s\" to \"%s\"", oldpath, newpath);
131218de8d7fSPeter Avalos
1313e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
131450a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
131518de8d7fSPeter Avalos
131618de8d7fSPeter Avalos /* Send symlink request */
131718de8d7fSPeter Avalos id = conn->msg_id++;
1318e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 ||
1319e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
1320e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, oldpath)) != 0 ||
1321e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, newpath)) != 0)
132250a69bb5SSascha Wildner fatal_fr(r, "compose");
1323e9778795SPeter Avalos send_msg(conn, msg);
132418de8d7fSPeter Avalos debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
132518de8d7fSPeter Avalos newpath);
1326e9778795SPeter Avalos sshbuf_free(msg);
132718de8d7fSPeter Avalos
13289f304aafSPeter Avalos status = get_status(conn, id);
132918de8d7fSPeter Avalos if (status != SSH2_FX_OK)
1330ee116499SAntonio Huete Jimenez error("remote symlink file \"%s\" to \"%s\": %s", oldpath,
133118de8d7fSPeter Avalos newpath, fx2txt(status));
133218de8d7fSPeter Avalos
1333e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
133418de8d7fSPeter Avalos }
133518de8d7fSPeter Avalos
133636e94dc5SPeter Avalos int
sftp_fsync(struct sftp_conn * conn,u_char * handle,u_int handle_len)1337*ba1276acSMatthew Dillon sftp_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len)
133836e94dc5SPeter Avalos {
1339e9778795SPeter Avalos struct sshbuf *msg;
134036e94dc5SPeter Avalos u_int status, id;
1341e9778795SPeter Avalos int r;
134236e94dc5SPeter Avalos
134336e94dc5SPeter Avalos /* Silently return if the extension is not supported */
134436e94dc5SPeter Avalos if ((conn->exts & SFTP_EXT_FSYNC) == 0)
134536e94dc5SPeter Avalos return -1;
1346ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_EXTENDED(fsync@openssh.com)");
134736e94dc5SPeter Avalos
134836e94dc5SPeter Avalos /* Send fsync request */
1349e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
135050a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
135136e94dc5SPeter Avalos id = conn->msg_id++;
1352e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1353e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
1354e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 ||
1355e9778795SPeter Avalos (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
135650a69bb5SSascha Wildner fatal_fr(r, "compose");
1357e9778795SPeter Avalos send_msg(conn, msg);
135836e94dc5SPeter Avalos debug3("Sent message fsync@openssh.com I:%u", id);
1359e9778795SPeter Avalos sshbuf_free(msg);
136036e94dc5SPeter Avalos
136136e94dc5SPeter Avalos status = get_status(conn, id);
136236e94dc5SPeter Avalos if (status != SSH2_FX_OK)
1363ee116499SAntonio Huete Jimenez error("remote fsync: %s", fx2txt(status));
136436e94dc5SPeter Avalos
1365664f4763Szrj return status == SSH2_FX_OK ? 0 : -1;
136636e94dc5SPeter Avalos }
136736e94dc5SPeter Avalos
136818de8d7fSPeter Avalos #ifdef notyet
136918de8d7fSPeter Avalos char *
sftp_readlink(struct sftp_conn * conn,const char * path)1370*ba1276acSMatthew Dillon sftp_readlink(struct sftp_conn *conn, const char *path)
137118de8d7fSPeter Avalos {
1372e9778795SPeter Avalos struct sshbuf *msg;
1373e9778795SPeter Avalos u_int expected_id, count, id;
137418de8d7fSPeter Avalos char *filename, *longname;
1375e9778795SPeter Avalos Attrib a;
1376e9778795SPeter Avalos u_char type;
1377e9778795SPeter Avalos int r;
137818de8d7fSPeter Avalos
1379ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_READLINK \"%s\"", path);
1380ee116499SAntonio Huete Jimenez
138118de8d7fSPeter Avalos expected_id = id = conn->msg_id++;
13829f304aafSPeter Avalos send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
138318de8d7fSPeter Avalos
1384e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
138550a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
138618de8d7fSPeter Avalos
1387e9778795SPeter Avalos get_msg(conn, msg);
1388e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1389e9778795SPeter Avalos (r = sshbuf_get_u32(msg, &id)) != 0)
139050a69bb5SSascha Wildner fatal_fr(r, "parse");
139118de8d7fSPeter Avalos
139218de8d7fSPeter Avalos if (id != expected_id)
139318de8d7fSPeter Avalos fatal("ID mismatch (%u != %u)", id, expected_id);
139418de8d7fSPeter Avalos
139518de8d7fSPeter Avalos if (type == SSH2_FXP_STATUS) {
1396e9778795SPeter Avalos u_int status;
139718de8d7fSPeter Avalos
1398e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &status)) != 0)
139950a69bb5SSascha Wildner fatal_fr(r, "parse status");
140018de8d7fSPeter Avalos error("Couldn't readlink: %s", fx2txt(status));
1401e9778795SPeter Avalos sshbuf_free(msg);
140218de8d7fSPeter Avalos return(NULL);
140318de8d7fSPeter Avalos } else if (type != SSH2_FXP_NAME)
140418de8d7fSPeter Avalos fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
140518de8d7fSPeter Avalos SSH2_FXP_NAME, type);
140618de8d7fSPeter Avalos
1407e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &count)) != 0)
140850a69bb5SSascha Wildner fatal_fr(r, "parse count");
140918de8d7fSPeter Avalos if (count != 1)
141018de8d7fSPeter Avalos fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
141118de8d7fSPeter Avalos
1412e9778795SPeter Avalos if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
1413e9778795SPeter Avalos (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
1414e9778795SPeter Avalos (r = decode_attrib(msg, &a)) != 0)
141550a69bb5SSascha Wildner fatal_fr(r, "parse filenames/attrib");
141618de8d7fSPeter Avalos
141718de8d7fSPeter Avalos debug3("SSH_FXP_READLINK %s -> %s", path, filename);
141818de8d7fSPeter Avalos
141936e94dc5SPeter Avalos free(longname);
142018de8d7fSPeter Avalos
1421e9778795SPeter Avalos sshbuf_free(msg);
142218de8d7fSPeter Avalos
1423e9778795SPeter Avalos return filename;
142418de8d7fSPeter Avalos }
142518de8d7fSPeter Avalos #endif
142618de8d7fSPeter Avalos
142718de8d7fSPeter Avalos int
sftp_statvfs(struct sftp_conn * conn,const char * path,struct sftp_statvfs * st,int quiet)1428*ba1276acSMatthew Dillon sftp_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
142918de8d7fSPeter Avalos int quiet)
143018de8d7fSPeter Avalos {
1431e9778795SPeter Avalos struct sshbuf *msg;
143218de8d7fSPeter Avalos u_int id;
1433e9778795SPeter Avalos int r;
143418de8d7fSPeter Avalos
143518de8d7fSPeter Avalos if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
143618de8d7fSPeter Avalos error("Server does not support statvfs@openssh.com extension");
143718de8d7fSPeter Avalos return -1;
143818de8d7fSPeter Avalos }
143918de8d7fSPeter Avalos
1440ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_EXTENDED(statvfs@openssh.com) \"%s\"", path);
1441ee116499SAntonio Huete Jimenez
144218de8d7fSPeter Avalos id = conn->msg_id++;
144318de8d7fSPeter Avalos
1444e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
144550a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
1446e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1447e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
1448e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 ||
1449e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, path)) != 0)
145050a69bb5SSascha Wildner fatal_fr(r, "compose");
1451e9778795SPeter Avalos send_msg(conn, msg);
1452e9778795SPeter Avalos sshbuf_free(msg);
145318de8d7fSPeter Avalos
14549f304aafSPeter Avalos return get_decode_statvfs(conn, st, id, quiet);
145518de8d7fSPeter Avalos }
145618de8d7fSPeter Avalos
145718de8d7fSPeter Avalos #ifdef notyet
145818de8d7fSPeter Avalos int
sftp_fstatvfs(struct sftp_conn * conn,const u_char * handle,u_int handle_len,struct sftp_statvfs * st,int quiet)1459*ba1276acSMatthew Dillon sftp_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
146018de8d7fSPeter Avalos struct sftp_statvfs *st, int quiet)
146118de8d7fSPeter Avalos {
1462e9778795SPeter Avalos struct sshbuf *msg;
146318de8d7fSPeter Avalos u_int id;
146418de8d7fSPeter Avalos
146518de8d7fSPeter Avalos if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
146618de8d7fSPeter Avalos error("Server does not support fstatvfs@openssh.com extension");
146718de8d7fSPeter Avalos return -1;
146818de8d7fSPeter Avalos }
146918de8d7fSPeter Avalos
1470ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_EXTENDED(fstatvfs@openssh.com)");
1471ee116499SAntonio Huete Jimenez
147218de8d7fSPeter Avalos id = conn->msg_id++;
147318de8d7fSPeter Avalos
1474e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
147550a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
1476e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1477e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
1478e9778795SPeter Avalos (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 ||
1479e9778795SPeter Avalos (r = sshbuf_put_string(msg, handle, handle_len)) != 0)
148050a69bb5SSascha Wildner fatal_fr(r, "compose");
1481e9778795SPeter Avalos send_msg(conn, msg);
1482e9778795SPeter Avalos sshbuf_free(msg);
148318de8d7fSPeter Avalos
14849f304aafSPeter Avalos return get_decode_statvfs(conn, st, id, quiet);
148518de8d7fSPeter Avalos }
148618de8d7fSPeter Avalos #endif
148718de8d7fSPeter Avalos
1488664f4763Szrj int
sftp_lsetstat(struct sftp_conn * conn,const char * path,Attrib * a)1489*ba1276acSMatthew Dillon sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a)
1490664f4763Szrj {
1491664f4763Szrj struct sshbuf *msg;
1492664f4763Szrj u_int status, id;
1493664f4763Szrj int r;
1494664f4763Szrj
1495664f4763Szrj if ((conn->exts & SFTP_EXT_LSETSTAT) == 0) {
1496664f4763Szrj error("Server does not support lsetstat@openssh.com extension");
1497664f4763Szrj return -1;
1498664f4763Szrj }
1499664f4763Szrj
1500ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_EXTENDED(lsetstat@openssh.com) \"%s\"", path);
1501ee116499SAntonio Huete Jimenez
1502664f4763Szrj id = conn->msg_id++;
1503664f4763Szrj if ((msg = sshbuf_new()) == NULL)
150450a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
1505664f4763Szrj if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
1506664f4763Szrj (r = sshbuf_put_u32(msg, id)) != 0 ||
1507664f4763Szrj (r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 ||
1508664f4763Szrj (r = sshbuf_put_cstring(msg, path)) != 0 ||
1509664f4763Szrj (r = encode_attrib(msg, a)) != 0)
151050a69bb5SSascha Wildner fatal_fr(r, "compose");
1511664f4763Szrj send_msg(conn, msg);
1512664f4763Szrj sshbuf_free(msg);
1513664f4763Szrj
1514664f4763Szrj status = get_status(conn, id);
1515664f4763Szrj if (status != SSH2_FX_OK)
1516ee116499SAntonio Huete Jimenez error("remote lsetstat \"%s\": %s", path, fx2txt(status));
1517664f4763Szrj
1518664f4763Szrj return status == SSH2_FX_OK ? 0 : -1;
1519664f4763Szrj }
1520664f4763Szrj
152118de8d7fSPeter 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)15229f304aafSPeter Avalos send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
1523e9778795SPeter Avalos u_int len, const u_char *handle, u_int handle_len)
152418de8d7fSPeter Avalos {
1525e9778795SPeter Avalos struct sshbuf *msg;
1526e9778795SPeter Avalos int r;
152718de8d7fSPeter Avalos
1528e9778795SPeter Avalos if ((msg = sshbuf_new()) == NULL)
152950a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
1530e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 ||
1531e9778795SPeter Avalos (r = sshbuf_put_u32(msg, id)) != 0 ||
1532e9778795SPeter Avalos (r = sshbuf_put_string(msg, handle, handle_len)) != 0 ||
1533e9778795SPeter Avalos (r = sshbuf_put_u64(msg, offset)) != 0 ||
1534e9778795SPeter Avalos (r = sshbuf_put_u32(msg, len)) != 0)
153550a69bb5SSascha Wildner fatal_fr(r, "compose");
1536e9778795SPeter Avalos send_msg(conn, msg);
1537e9778795SPeter Avalos sshbuf_free(msg);
153818de8d7fSPeter Avalos }
153918de8d7fSPeter Avalos
154050a69bb5SSascha 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)154150a69bb5SSascha Wildner send_open(struct sftp_conn *conn, const char *path, const char *tag,
154250a69bb5SSascha Wildner u_int openmode, Attrib *a, u_char **handlep, size_t *handle_lenp)
154350a69bb5SSascha Wildner {
154450a69bb5SSascha Wildner Attrib junk;
154550a69bb5SSascha Wildner u_char *handle;
154650a69bb5SSascha Wildner size_t handle_len;
154750a69bb5SSascha Wildner struct sshbuf *msg;
154850a69bb5SSascha Wildner int r;
154950a69bb5SSascha Wildner u_int id;
155050a69bb5SSascha Wildner
1551ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_OPEN \"%s\"", path);
1552ee116499SAntonio Huete Jimenez
155350a69bb5SSascha Wildner *handlep = NULL;
155450a69bb5SSascha Wildner *handle_lenp = 0;
155550a69bb5SSascha Wildner
155650a69bb5SSascha Wildner if (a == NULL) {
155750a69bb5SSascha Wildner attrib_clear(&junk); /* Send empty attributes */
155850a69bb5SSascha Wildner a = &junk;
155950a69bb5SSascha Wildner }
156050a69bb5SSascha Wildner /* Send open request */
156150a69bb5SSascha Wildner if ((msg = sshbuf_new()) == NULL)
156250a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
156350a69bb5SSascha Wildner id = conn->msg_id++;
156450a69bb5SSascha Wildner if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 ||
156550a69bb5SSascha Wildner (r = sshbuf_put_u32(msg, id)) != 0 ||
156650a69bb5SSascha Wildner (r = sshbuf_put_cstring(msg, path)) != 0 ||
156750a69bb5SSascha Wildner (r = sshbuf_put_u32(msg, openmode)) != 0 ||
156850a69bb5SSascha Wildner (r = encode_attrib(msg, a)) != 0)
156950a69bb5SSascha Wildner fatal_fr(r, "compose %s open", tag);
157050a69bb5SSascha Wildner send_msg(conn, msg);
157150a69bb5SSascha Wildner sshbuf_free(msg);
157250a69bb5SSascha Wildner debug3("Sent %s message SSH2_FXP_OPEN I:%u P:%s M:0x%04x",
157350a69bb5SSascha Wildner tag, id, path, openmode);
157450a69bb5SSascha Wildner if ((handle = get_handle(conn, id, &handle_len,
1575ee116499SAntonio Huete Jimenez "%s open \"%s\"", tag, path)) == NULL)
157650a69bb5SSascha Wildner return -1;
157750a69bb5SSascha Wildner /* success */
157850a69bb5SSascha Wildner *handlep = handle;
157950a69bb5SSascha Wildner *handle_lenp = handle_len;
158050a69bb5SSascha Wildner return 0;
158150a69bb5SSascha Wildner }
158250a69bb5SSascha Wildner
158350a69bb5SSascha Wildner static const char *
progress_meter_path(const char * path)158450a69bb5SSascha Wildner progress_meter_path(const char *path)
158550a69bb5SSascha Wildner {
158650a69bb5SSascha Wildner const char *progresspath;
158750a69bb5SSascha Wildner
158850a69bb5SSascha Wildner if ((progresspath = strrchr(path, '/')) == NULL)
158950a69bb5SSascha Wildner return path;
159050a69bb5SSascha Wildner progresspath++;
159150a69bb5SSascha Wildner if (*progresspath == '\0')
159250a69bb5SSascha Wildner return path;
159350a69bb5SSascha Wildner return progresspath;
159450a69bb5SSascha Wildner }
159550a69bb5SSascha Wildner
159618de8d7fSPeter Avalos int
sftp_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)1597*ba1276acSMatthew Dillon sftp_download(struct sftp_conn *conn, const char *remote_path,
1598e9778795SPeter Avalos const char *local_path, Attrib *a, int preserve_flag, int resume_flag,
1599ee116499SAntonio Huete Jimenez int fsync_flag, int inplace_flag)
160018de8d7fSPeter Avalos {
1601e9778795SPeter Avalos struct sshbuf *msg;
1602e9778795SPeter Avalos u_char *handle;
1603e9778795SPeter Avalos int local_fd = -1, write_error;
16040cbfa66cSDaniel Fojt int read_error, write_errno, lmodified = 0, reordered = 0, r;
1605*ba1276acSMatthew Dillon u_int64_t offset = 0, size, highwater = 0, maxack = 0;
1606e9778795SPeter Avalos u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK;
160718de8d7fSPeter Avalos off_t progress_counter;
1608e9778795SPeter Avalos size_t handle_len;
160936e94dc5SPeter Avalos struct stat st;
161050a69bb5SSascha Wildner struct requests requests;
161118de8d7fSPeter Avalos struct request *req;
1612e9778795SPeter Avalos u_char type;
1613*ba1276acSMatthew Dillon Attrib attr;
161418de8d7fSPeter Avalos
1615ee116499SAntonio Huete Jimenez debug2_f("download remote \"%s\" to local \"%s\"",
1616ee116499SAntonio Huete Jimenez remote_path, local_path);
1617ee116499SAntonio Huete Jimenez
161818de8d7fSPeter Avalos TAILQ_INIT(&requests);
161918de8d7fSPeter Avalos
1620*ba1276acSMatthew Dillon if (a == NULL) {
1621*ba1276acSMatthew Dillon if (sftp_stat(conn, remote_path, 0, &attr) != 0)
1622856ea928SPeter Avalos return -1;
1623*ba1276acSMatthew Dillon a = &attr;
1624*ba1276acSMatthew Dillon }
162518de8d7fSPeter Avalos
162618de8d7fSPeter Avalos /* Do not preserve set[ug]id here, as we do not preserve ownership */
162718de8d7fSPeter Avalos if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
162818de8d7fSPeter Avalos mode = a->perm & 0777;
162918de8d7fSPeter Avalos else
163018de8d7fSPeter Avalos mode = 0666;
163118de8d7fSPeter Avalos
163218de8d7fSPeter Avalos if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
163318de8d7fSPeter Avalos (!S_ISREG(a->perm))) {
1634ee116499SAntonio Huete Jimenez error("download %s: not a regular file", remote_path);
163518de8d7fSPeter Avalos return(-1);
163618de8d7fSPeter Avalos }
163718de8d7fSPeter Avalos
163818de8d7fSPeter Avalos if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
163918de8d7fSPeter Avalos size = a->size;
164018de8d7fSPeter Avalos else
164118de8d7fSPeter Avalos size = 0;
164218de8d7fSPeter Avalos
164350a69bb5SSascha Wildner buflen = conn->download_buflen;
164418de8d7fSPeter Avalos
164518de8d7fSPeter Avalos /* Send open request */
164650a69bb5SSascha Wildner if (send_open(conn, remote_path, "remote", SSH2_FXF_READ, NULL,
164750a69bb5SSascha Wildner &handle, &handle_len) != 0)
164850a69bb5SSascha Wildner return -1;
164918de8d7fSPeter Avalos
1650ee116499SAntonio Huete Jimenez local_fd = open(local_path, O_WRONLY | O_CREAT |
1651ee116499SAntonio Huete Jimenez ((resume_flag || inplace_flag) ? 0 : O_TRUNC), mode | S_IWUSR);
165218de8d7fSPeter Avalos if (local_fd == -1) {
1653ee116499SAntonio Huete Jimenez error("open local \"%s\": %s", local_path, strerror(errno));
165436e94dc5SPeter Avalos goto fail;
165536e94dc5SPeter Avalos }
165636e94dc5SPeter Avalos if (resume_flag) {
165736e94dc5SPeter Avalos if (fstat(local_fd, &st) == -1) {
1658ee116499SAntonio Huete Jimenez error("stat local \"%s\": %s",
165936e94dc5SPeter Avalos local_path, strerror(errno));
166036e94dc5SPeter Avalos goto fail;
166136e94dc5SPeter Avalos }
166236e94dc5SPeter Avalos if (st.st_size < 0) {
166336e94dc5SPeter Avalos error("\"%s\" has negative size", local_path);
166436e94dc5SPeter Avalos goto fail;
166536e94dc5SPeter Avalos }
166636e94dc5SPeter Avalos if ((u_int64_t)st.st_size > size) {
166736e94dc5SPeter Avalos error("Unable to resume download of \"%s\": "
166836e94dc5SPeter Avalos "local file is larger than remote", local_path);
166936e94dc5SPeter Avalos fail:
1670*ba1276acSMatthew Dillon sftp_close(conn, handle, handle_len);
167136e94dc5SPeter Avalos free(handle);
167236e94dc5SPeter Avalos if (local_fd != -1)
167336e94dc5SPeter Avalos close(local_fd);
167436e94dc5SPeter Avalos return -1;
167536e94dc5SPeter Avalos }
1676*ba1276acSMatthew Dillon offset = highwater = maxack = st.st_size;
167718de8d7fSPeter Avalos }
167818de8d7fSPeter Avalos
167918de8d7fSPeter Avalos /* Read from remote and write to local */
168036e94dc5SPeter Avalos write_error = read_error = write_errno = num_req = 0;
168118de8d7fSPeter Avalos max_req = 1;
168236e94dc5SPeter Avalos progress_counter = offset;
168318de8d7fSPeter Avalos
168450a69bb5SSascha Wildner if (showprogress && size != 0) {
168550a69bb5SSascha Wildner start_progress_meter(progress_meter_path(remote_path),
168650a69bb5SSascha Wildner size, &progress_counter);
168750a69bb5SSascha Wildner }
168850a69bb5SSascha Wildner
168950a69bb5SSascha Wildner if ((msg = sshbuf_new()) == NULL)
169050a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
169118de8d7fSPeter Avalos
169218de8d7fSPeter Avalos while (num_req > 0 || max_req > 0) {
1693e9778795SPeter Avalos u_char *data;
1694e9778795SPeter Avalos size_t len;
169518de8d7fSPeter Avalos
169618de8d7fSPeter Avalos /*
169718de8d7fSPeter Avalos * Simulate EOF on interrupt: stop sending new requests and
169818de8d7fSPeter Avalos * allow outstanding requests to drain gracefully
169918de8d7fSPeter Avalos */
170018de8d7fSPeter Avalos if (interrupted) {
170118de8d7fSPeter Avalos if (num_req == 0) /* If we haven't started yet... */
170218de8d7fSPeter Avalos break;
170318de8d7fSPeter Avalos max_req = 0;
170418de8d7fSPeter Avalos }
170518de8d7fSPeter Avalos
170618de8d7fSPeter Avalos /* Send some more requests */
170718de8d7fSPeter Avalos while (num_req < max_req) {
170818de8d7fSPeter Avalos debug3("Request range %llu -> %llu (%d/%d)",
170918de8d7fSPeter Avalos (unsigned long long)offset,
171018de8d7fSPeter Avalos (unsigned long long)offset + buflen - 1,
171118de8d7fSPeter Avalos num_req, max_req);
171250a69bb5SSascha Wildner req = request_enqueue(&requests, conn->msg_id++,
171350a69bb5SSascha Wildner buflen, offset);
171418de8d7fSPeter Avalos offset += buflen;
171518de8d7fSPeter Avalos num_req++;
17169f304aafSPeter Avalos send_read_request(conn, req->id, req->offset,
171718de8d7fSPeter Avalos req->len, handle, handle_len);
171818de8d7fSPeter Avalos }
171918de8d7fSPeter Avalos
1720e9778795SPeter Avalos sshbuf_reset(msg);
1721e9778795SPeter Avalos get_msg(conn, msg);
1722e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
1723e9778795SPeter Avalos (r = sshbuf_get_u32(msg, &id)) != 0)
172450a69bb5SSascha Wildner fatal_fr(r, "parse");
172518de8d7fSPeter Avalos debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
172618de8d7fSPeter Avalos
172718de8d7fSPeter Avalos /* Find the request in our queue */
172850a69bb5SSascha Wildner if ((req = request_find(&requests, id)) == NULL)
172918de8d7fSPeter Avalos fatal("Unexpected reply %u", id);
173018de8d7fSPeter Avalos
173118de8d7fSPeter Avalos switch (type) {
173218de8d7fSPeter Avalos case SSH2_FXP_STATUS:
1733e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &status)) != 0)
173450a69bb5SSascha Wildner fatal_fr(r, "parse status");
173518de8d7fSPeter Avalos if (status != SSH2_FX_EOF)
173618de8d7fSPeter Avalos read_error = 1;
173718de8d7fSPeter Avalos max_req = 0;
173818de8d7fSPeter Avalos TAILQ_REMOVE(&requests, req, tq);
173936e94dc5SPeter Avalos free(req);
174018de8d7fSPeter Avalos num_req--;
174118de8d7fSPeter Avalos break;
174218de8d7fSPeter Avalos case SSH2_FXP_DATA:
1743e9778795SPeter Avalos if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
174450a69bb5SSascha Wildner fatal_fr(r, "parse data");
174518de8d7fSPeter Avalos debug3("Received data %llu -> %llu",
174618de8d7fSPeter Avalos (unsigned long long)req->offset,
174718de8d7fSPeter Avalos (unsigned long long)req->offset + len - 1);
174818de8d7fSPeter Avalos if (len > req->len)
174918de8d7fSPeter Avalos fatal("Received more data than asked for "
1750e9778795SPeter Avalos "%zu > %zu", len, req->len);
17510cbfa66cSDaniel Fojt lmodified = 1;
175218de8d7fSPeter Avalos if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
175318de8d7fSPeter Avalos atomicio(vwrite, local_fd, data, len) != len) &&
175418de8d7fSPeter Avalos !write_error) {
175518de8d7fSPeter Avalos write_errno = errno;
175618de8d7fSPeter Avalos write_error = 1;
175718de8d7fSPeter Avalos max_req = 0;
1758*ba1276acSMatthew Dillon } else {
1759*ba1276acSMatthew Dillon /*
1760*ba1276acSMatthew Dillon * Track both the highest offset acknowledged
1761*ba1276acSMatthew Dillon * and the highest *contiguous* offset
1762*ba1276acSMatthew Dillon * acknowledged.
1763*ba1276acSMatthew Dillon * We'll need the latter for ftruncate()ing
1764*ba1276acSMatthew Dillon * interrupted transfers.
1765*ba1276acSMatthew Dillon */
1766*ba1276acSMatthew Dillon if (maxack < req->offset + len)
1767*ba1276acSMatthew Dillon maxack = req->offset + len;
1768*ba1276acSMatthew Dillon if (!reordered && req->offset <= highwater)
1769*ba1276acSMatthew Dillon highwater = maxack;
177036e94dc5SPeter Avalos else if (!reordered && req->offset > highwater)
177136e94dc5SPeter Avalos reordered = 1;
1772*ba1276acSMatthew Dillon }
177318de8d7fSPeter Avalos progress_counter += len;
177436e94dc5SPeter Avalos free(data);
177518de8d7fSPeter Avalos
177618de8d7fSPeter Avalos if (len == req->len) {
177718de8d7fSPeter Avalos TAILQ_REMOVE(&requests, req, tq);
177836e94dc5SPeter Avalos free(req);
177918de8d7fSPeter Avalos num_req--;
178018de8d7fSPeter Avalos } else {
178118de8d7fSPeter Avalos /* Resend the request for the missing data */
178218de8d7fSPeter Avalos debug3("Short data block, re-requesting "
178318de8d7fSPeter Avalos "%llu -> %llu (%2d)",
178418de8d7fSPeter Avalos (unsigned long long)req->offset + len,
178518de8d7fSPeter Avalos (unsigned long long)req->offset +
178618de8d7fSPeter Avalos req->len - 1, num_req);
178718de8d7fSPeter Avalos req->id = conn->msg_id++;
178818de8d7fSPeter Avalos req->len -= len;
178918de8d7fSPeter Avalos req->offset += len;
17909f304aafSPeter Avalos send_read_request(conn, req->id,
179118de8d7fSPeter Avalos req->offset, req->len, handle, handle_len);
179218de8d7fSPeter Avalos /* Reduce the request size */
179318de8d7fSPeter Avalos if (len < buflen)
1794ce74bacaSMatthew Dillon buflen = MAXIMUM(MIN_READ_SIZE, len);
179518de8d7fSPeter Avalos }
179618de8d7fSPeter Avalos if (max_req > 0) { /* max_req = 0 iff EOF received */
179718de8d7fSPeter Avalos if (size > 0 && offset > size) {
179818de8d7fSPeter Avalos /* Only one request at a time
179918de8d7fSPeter Avalos * after the expected EOF */
180018de8d7fSPeter Avalos debug3("Finish at %llu (%2d)",
180118de8d7fSPeter Avalos (unsigned long long)offset,
180218de8d7fSPeter Avalos num_req);
180318de8d7fSPeter Avalos max_req = 1;
180450a69bb5SSascha Wildner } else if (max_req < conn->num_requests) {
180518de8d7fSPeter Avalos ++max_req;
180618de8d7fSPeter Avalos }
180718de8d7fSPeter Avalos }
180818de8d7fSPeter Avalos break;
180918de8d7fSPeter Avalos default:
181018de8d7fSPeter Avalos fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
181118de8d7fSPeter Avalos SSH2_FXP_DATA, type);
181218de8d7fSPeter Avalos }
181318de8d7fSPeter Avalos }
181418de8d7fSPeter Avalos
181518de8d7fSPeter Avalos if (showprogress && size)
181618de8d7fSPeter Avalos stop_progress_meter();
181718de8d7fSPeter Avalos
181818de8d7fSPeter Avalos /* Sanity check */
181918de8d7fSPeter Avalos if (TAILQ_FIRST(&requests) != NULL)
182018de8d7fSPeter Avalos fatal("Transfer complete, but requests still in queue");
1821*ba1276acSMatthew Dillon
1822*ba1276acSMatthew Dillon if (!read_error && !write_error && !interrupted) {
1823*ba1276acSMatthew Dillon /* we got everything */
1824*ba1276acSMatthew Dillon highwater = maxack;
1825*ba1276acSMatthew Dillon }
1826*ba1276acSMatthew Dillon
1827ee116499SAntonio Huete Jimenez /*
1828ee116499SAntonio Huete Jimenez * Truncate at highest contiguous point to avoid holes on interrupt,
1829ee116499SAntonio Huete Jimenez * or unconditionally if writing in place.
1830ee116499SAntonio Huete Jimenez */
1831ee116499SAntonio Huete Jimenez if (inplace_flag || read_error || write_error || interrupted) {
1832*ba1276acSMatthew Dillon if (reordered && resume_flag &&
1833*ba1276acSMatthew Dillon (read_error || write_error || interrupted)) {
183436e94dc5SPeter Avalos error("Unable to resume download of \"%s\": "
183536e94dc5SPeter Avalos "server reordered requests", local_path);
183636e94dc5SPeter Avalos }
183736e94dc5SPeter Avalos debug("truncating at %llu", (unsigned long long)highwater);
1838e9778795SPeter Avalos if (ftruncate(local_fd, highwater) == -1)
1839ee116499SAntonio Huete Jimenez error("local ftruncate \"%s\": %s", local_path,
1840e9778795SPeter Avalos strerror(errno));
184136e94dc5SPeter Avalos }
184218de8d7fSPeter Avalos if (read_error) {
1843ee116499SAntonio Huete Jimenez error("read remote \"%s\" : %s", remote_path, fx2txt(status));
184436e94dc5SPeter Avalos status = -1;
1845*ba1276acSMatthew Dillon sftp_close(conn, handle, handle_len);
184618de8d7fSPeter Avalos } else if (write_error) {
1847ee116499SAntonio Huete Jimenez error("write local \"%s\": %s", local_path,
184818de8d7fSPeter Avalos strerror(write_errno));
1849e9778795SPeter Avalos status = SSH2_FX_FAILURE;
1850*ba1276acSMatthew Dillon sftp_close(conn, handle, handle_len);
185118de8d7fSPeter Avalos } else {
1852*ba1276acSMatthew Dillon if (sftp_close(conn, handle, handle_len) != 0 || interrupted)
1853e9778795SPeter Avalos status = SSH2_FX_FAILURE;
1854e9778795SPeter Avalos else
1855e9778795SPeter Avalos status = SSH2_FX_OK;
185618de8d7fSPeter Avalos /* Override umask and utimes if asked */
185718de8d7fSPeter Avalos #ifdef HAVE_FCHMOD
185836e94dc5SPeter Avalos if (preserve_flag && fchmod(local_fd, mode) == -1)
185918de8d7fSPeter Avalos #else
186036e94dc5SPeter Avalos if (preserve_flag && chmod(local_path, mode) == -1)
186118de8d7fSPeter Avalos #endif /* HAVE_FCHMOD */
1862ee116499SAntonio Huete Jimenez error("local chmod \"%s\": %s", local_path,
186318de8d7fSPeter Avalos strerror(errno));
186436e94dc5SPeter Avalos if (preserve_flag &&
186536e94dc5SPeter Avalos (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
186618de8d7fSPeter Avalos struct timeval tv[2];
186718de8d7fSPeter Avalos tv[0].tv_sec = a->atime;
186818de8d7fSPeter Avalos tv[1].tv_sec = a->mtime;
186918de8d7fSPeter Avalos tv[0].tv_usec = tv[1].tv_usec = 0;
187018de8d7fSPeter Avalos if (utimes(local_path, tv) == -1)
1871ee116499SAntonio Huete Jimenez error("local set times \"%s\": %s",
187218de8d7fSPeter Avalos local_path, strerror(errno));
187318de8d7fSPeter Avalos }
18740cbfa66cSDaniel Fojt if (resume_flag && !lmodified)
18750cbfa66cSDaniel Fojt logit("File \"%s\" was not modified", local_path);
18760cbfa66cSDaniel Fojt else if (fsync_flag) {
187736e94dc5SPeter Avalos debug("syncing \"%s\"", local_path);
187836e94dc5SPeter Avalos if (fsync(local_fd) == -1)
1879ee116499SAntonio Huete Jimenez error("local sync \"%s\": %s",
188036e94dc5SPeter Avalos local_path, strerror(errno));
188136e94dc5SPeter Avalos }
188218de8d7fSPeter Avalos }
188318de8d7fSPeter Avalos close(local_fd);
1884e9778795SPeter Avalos sshbuf_free(msg);
188536e94dc5SPeter Avalos free(handle);
188618de8d7fSPeter Avalos
1887664f4763Szrj return status == SSH2_FX_OK ? 0 : -1;
188818de8d7fSPeter Avalos }
188918de8d7fSPeter Avalos
1890856ea928SPeter 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)1891e9778795SPeter Avalos download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
1892e9778795SPeter Avalos int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
1893ee116499SAntonio Huete Jimenez int resume_flag, int fsync_flag, int follow_link_flag, int inplace_flag)
1894856ea928SPeter Avalos {
1895856ea928SPeter Avalos int i, ret = 0;
1896856ea928SPeter Avalos SFTP_DIRENT **dir_entries;
1897664f4763Szrj char *filename, *new_src = NULL, *new_dst = NULL;
189850a69bb5SSascha Wildner mode_t mode = 0777, tmpmode = mode;
1899*ba1276acSMatthew Dillon Attrib *a, ldirattrib, lsym;
1900856ea928SPeter Avalos
1901856ea928SPeter Avalos if (depth >= MAX_DIR_DEPTH) {
1902856ea928SPeter Avalos error("Maximum directory depth exceeded: %d levels", depth);
1903856ea928SPeter Avalos return -1;
1904856ea928SPeter Avalos }
1905856ea928SPeter Avalos
1906ee116499SAntonio Huete Jimenez debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst);
1907ee116499SAntonio Huete Jimenez
1908*ba1276acSMatthew Dillon if (dirattrib == NULL) {
1909*ba1276acSMatthew Dillon if (sftp_stat(conn, src, 1, &ldirattrib) != 0) {
1910ee116499SAntonio Huete Jimenez error("stat remote \"%s\" directory failed", src);
1911856ea928SPeter Avalos return -1;
1912856ea928SPeter Avalos }
1913*ba1276acSMatthew Dillon dirattrib = &ldirattrib;
1914*ba1276acSMatthew Dillon }
1915856ea928SPeter Avalos if (!S_ISDIR(dirattrib->perm)) {
1916856ea928SPeter Avalos error("\"%s\" is not a directory", src);
1917856ea928SPeter Avalos return -1;
1918856ea928SPeter Avalos }
191950a69bb5SSascha Wildner if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
1920e9778795SPeter Avalos mprintf("Retrieving %s\n", src);
1921856ea928SPeter Avalos
192250a69bb5SSascha Wildner if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) {
1923856ea928SPeter Avalos mode = dirattrib->perm & 01777;
192450a69bb5SSascha Wildner tmpmode = mode | (S_IWUSR|S_IXUSR);
192550a69bb5SSascha Wildner } else {
1926ee116499SAntonio Huete Jimenez debug("download remote \"%s\": server "
1927ee116499SAntonio Huete Jimenez "did not send permissions", dst);
1928856ea928SPeter Avalos }
1929856ea928SPeter Avalos
193050a69bb5SSascha Wildner if (mkdir(dst, tmpmode) == -1 && errno != EEXIST) {
1931856ea928SPeter Avalos error("mkdir %s: %s", dst, strerror(errno));
1932856ea928SPeter Avalos return -1;
1933856ea928SPeter Avalos }
1934856ea928SPeter Avalos
1935*ba1276acSMatthew Dillon if (sftp_readdir(conn, src, &dir_entries) == -1) {
1936ee116499SAntonio Huete Jimenez error("remote readdir \"%s\" failed", src);
1937856ea928SPeter Avalos return -1;
1938856ea928SPeter Avalos }
1939856ea928SPeter Avalos
1940856ea928SPeter Avalos for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
1941664f4763Szrj free(new_dst);
1942664f4763Szrj free(new_src);
1943856ea928SPeter Avalos
1944664f4763Szrj filename = dir_entries[i]->filename;
1945*ba1276acSMatthew Dillon new_dst = sftp_path_append(dst, filename);
1946*ba1276acSMatthew Dillon new_src = sftp_path_append(src, filename);
1947856ea928SPeter Avalos
1948*ba1276acSMatthew Dillon a = &dir_entries[i]->a;
1949*ba1276acSMatthew Dillon if (S_ISLNK(a->perm)) {
1950*ba1276acSMatthew Dillon if (!follow_link_flag) {
1951*ba1276acSMatthew Dillon logit("download \"%s\": not a regular file",
1952*ba1276acSMatthew Dillon new_src);
1953*ba1276acSMatthew Dillon continue;
1954*ba1276acSMatthew Dillon }
1955*ba1276acSMatthew Dillon /* Replace the stat contents with the symlink target */
1956*ba1276acSMatthew Dillon if (sftp_stat(conn, new_src, 1, &lsym) != 0) {
1957*ba1276acSMatthew Dillon logit("remote stat \"%s\" failed", new_src);
1958*ba1276acSMatthew Dillon ret = -1;
1959*ba1276acSMatthew Dillon continue;
1960*ba1276acSMatthew Dillon }
1961*ba1276acSMatthew Dillon a = &lsym;
1962*ba1276acSMatthew Dillon }
1963*ba1276acSMatthew Dillon
1964*ba1276acSMatthew Dillon if (S_ISDIR(a->perm)) {
1965856ea928SPeter Avalos if (strcmp(filename, ".") == 0 ||
1966856ea928SPeter Avalos strcmp(filename, "..") == 0)
1967856ea928SPeter Avalos continue;
1968856ea928SPeter Avalos if (download_dir_internal(conn, new_src, new_dst,
1969*ba1276acSMatthew Dillon depth + 1, a, preserve_flag,
197050a69bb5SSascha Wildner print_flag, resume_flag,
1971ee116499SAntonio Huete Jimenez fsync_flag, follow_link_flag, inplace_flag) == -1)
1972856ea928SPeter Avalos ret = -1;
1973*ba1276acSMatthew Dillon } else if (S_ISREG(a->perm)) {
1974*ba1276acSMatthew Dillon if (sftp_download(conn, new_src, new_dst, a,
1975ee116499SAntonio Huete Jimenez preserve_flag, resume_flag, fsync_flag,
1976ee116499SAntonio Huete Jimenez inplace_flag) == -1) {
1977856ea928SPeter Avalos error("Download of file %s to %s failed",
1978856ea928SPeter Avalos new_src, new_dst);
1979856ea928SPeter Avalos ret = -1;
1980856ea928SPeter Avalos }
1981856ea928SPeter Avalos } else
1982ee116499SAntonio Huete Jimenez logit("download \"%s\": not a regular file", new_src);
1983856ea928SPeter Avalos
1984664f4763Szrj }
198536e94dc5SPeter Avalos free(new_dst);
198636e94dc5SPeter Avalos free(new_src);
1987856ea928SPeter Avalos
198836e94dc5SPeter Avalos if (preserve_flag) {
1989856ea928SPeter Avalos if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1990856ea928SPeter Avalos struct timeval tv[2];
1991856ea928SPeter Avalos tv[0].tv_sec = dirattrib->atime;
1992856ea928SPeter Avalos tv[1].tv_sec = dirattrib->mtime;
1993856ea928SPeter Avalos tv[0].tv_usec = tv[1].tv_usec = 0;
1994856ea928SPeter Avalos if (utimes(dst, tv) == -1)
1995ee116499SAntonio Huete Jimenez error("local set times on \"%s\": %s",
1996856ea928SPeter Avalos dst, strerror(errno));
1997856ea928SPeter Avalos } else
1998856ea928SPeter Avalos debug("Server did not send times for directory "
1999856ea928SPeter Avalos "\"%s\"", dst);
2000856ea928SPeter Avalos }
2001856ea928SPeter Avalos
200250a69bb5SSascha Wildner if (mode != tmpmode && chmod(dst, mode) == -1)
2003ee116499SAntonio Huete Jimenez error("local chmod directory \"%s\": %s", dst,
200450a69bb5SSascha Wildner strerror(errno));
200550a69bb5SSascha Wildner
2006*ba1276acSMatthew Dillon sftp_free_dirents(dir_entries);
2007856ea928SPeter Avalos
2008856ea928SPeter Avalos return ret;
2009856ea928SPeter Avalos }
2010856ea928SPeter Avalos
2011856ea928SPeter Avalos int
sftp_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)2012*ba1276acSMatthew Dillon sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst,
2013e9778795SPeter Avalos Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag,
2014ee116499SAntonio Huete Jimenez int fsync_flag, int follow_link_flag, int inplace_flag)
2015856ea928SPeter Avalos {
2016856ea928SPeter Avalos char *src_canon;
2017856ea928SPeter Avalos int ret;
2018856ea928SPeter Avalos
2019*ba1276acSMatthew Dillon if ((src_canon = sftp_realpath(conn, src)) == NULL) {
2020ee116499SAntonio Huete Jimenez error("download \"%s\": path canonicalization failed", src);
2021856ea928SPeter Avalos return -1;
2022856ea928SPeter Avalos }
2023856ea928SPeter Avalos
202436e94dc5SPeter Avalos ret = download_dir_internal(conn, src_canon, dst, 0,
202550a69bb5SSascha Wildner dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag,
2026ee116499SAntonio Huete Jimenez follow_link_flag, inplace_flag);
202736e94dc5SPeter Avalos free(src_canon);
2028856ea928SPeter Avalos return ret;
2029856ea928SPeter Avalos }
2030856ea928SPeter Avalos
203118de8d7fSPeter Avalos int
sftp_upload(struct sftp_conn * conn,const char * local_path,const char * remote_path,int preserve_flag,int resume,int fsync_flag,int inplace_flag)2032*ba1276acSMatthew Dillon sftp_upload(struct sftp_conn *conn, const char *local_path,
2033ee116499SAntonio Huete Jimenez const char *remote_path, int preserve_flag, int resume,
2034ee116499SAntonio Huete Jimenez int fsync_flag, int inplace_flag)
203518de8d7fSPeter Avalos {
2036e9778795SPeter Avalos int r, local_fd;
2037ee116499SAntonio Huete Jimenez u_int openmode, id, status = SSH2_FX_OK, reordered = 0;
203836e94dc5SPeter Avalos off_t offset, progress_counter;
2039ee116499SAntonio Huete Jimenez u_char type, *handle, *data;
2040e9778795SPeter Avalos struct sshbuf *msg;
204118de8d7fSPeter Avalos struct stat sb;
2042*ba1276acSMatthew Dillon Attrib a, t, c;
2043ee116499SAntonio Huete Jimenez u_int32_t startid, ackid;
2044*ba1276acSMatthew Dillon u_int64_t highwater = 0, maxack = 0;
204550a69bb5SSascha Wildner struct request *ack = NULL;
204650a69bb5SSascha Wildner struct requests acks;
2047e9778795SPeter Avalos size_t handle_len;
204818de8d7fSPeter Avalos
2049ee116499SAntonio Huete Jimenez debug2_f("upload local \"%s\" to remote \"%s\"",
2050ee116499SAntonio Huete Jimenez local_path, remote_path);
2051ee116499SAntonio Huete Jimenez
205218de8d7fSPeter Avalos TAILQ_INIT(&acks);
205318de8d7fSPeter Avalos
2054ee116499SAntonio Huete Jimenez if ((local_fd = open(local_path, O_RDONLY)) == -1) {
2055ee116499SAntonio Huete Jimenez error("open local \"%s\": %s", local_path, strerror(errno));
205618de8d7fSPeter Avalos return(-1);
205718de8d7fSPeter Avalos }
205818de8d7fSPeter Avalos if (fstat(local_fd, &sb) == -1) {
2059ee116499SAntonio Huete Jimenez error("fstat local \"%s\": %s", local_path, strerror(errno));
206018de8d7fSPeter Avalos close(local_fd);
206118de8d7fSPeter Avalos return(-1);
206218de8d7fSPeter Avalos }
206318de8d7fSPeter Avalos if (!S_ISREG(sb.st_mode)) {
2064ee116499SAntonio Huete Jimenez error("local \"%s\" is not a regular file", local_path);
206518de8d7fSPeter Avalos close(local_fd);
206618de8d7fSPeter Avalos return(-1);
206718de8d7fSPeter Avalos }
206818de8d7fSPeter Avalos stat_to_attrib(&sb, &a);
206918de8d7fSPeter Avalos
207018de8d7fSPeter Avalos a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
207118de8d7fSPeter Avalos a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
207218de8d7fSPeter Avalos a.perm &= 0777;
207336e94dc5SPeter Avalos if (!preserve_flag)
207418de8d7fSPeter Avalos a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
207518de8d7fSPeter Avalos
207636e94dc5SPeter Avalos if (resume) {
207736e94dc5SPeter Avalos /* Get remote file size if it exists */
2078*ba1276acSMatthew Dillon if (sftp_stat(conn, remote_path, 0, &c) != 0) {
207936e94dc5SPeter Avalos close(local_fd);
208036e94dc5SPeter Avalos return -1;
208136e94dc5SPeter Avalos }
208236e94dc5SPeter Avalos
2083*ba1276acSMatthew Dillon if ((off_t)c.size >= sb.st_size) {
2084ee116499SAntonio Huete Jimenez error("resume \"%s\": destination file "
2085ee116499SAntonio Huete Jimenez "same size or larger", local_path);
208636e94dc5SPeter Avalos close(local_fd);
208736e94dc5SPeter Avalos return -1;
208836e94dc5SPeter Avalos }
208936e94dc5SPeter Avalos
2090*ba1276acSMatthew Dillon if (lseek(local_fd, (off_t)c.size, SEEK_SET) == -1) {
209136e94dc5SPeter Avalos close(local_fd);
209236e94dc5SPeter Avalos return -1;
209336e94dc5SPeter Avalos }
209436e94dc5SPeter Avalos }
209536e94dc5SPeter Avalos
2096ee116499SAntonio Huete Jimenez openmode = SSH2_FXF_WRITE|SSH2_FXF_CREAT;
2097ee116499SAntonio Huete Jimenez if (resume)
2098ee116499SAntonio Huete Jimenez openmode |= SSH2_FXF_APPEND;
2099ee116499SAntonio Huete Jimenez else if (!inplace_flag)
2100ee116499SAntonio Huete Jimenez openmode |= SSH2_FXF_TRUNC;
2101ee116499SAntonio Huete Jimenez
210218de8d7fSPeter Avalos /* Send open request */
2103ee116499SAntonio Huete Jimenez if (send_open(conn, remote_path, "dest", openmode, &a,
2104ee116499SAntonio Huete Jimenez &handle, &handle_len) != 0) {
210518de8d7fSPeter Avalos close(local_fd);
210618de8d7fSPeter Avalos return -1;
210718de8d7fSPeter Avalos }
210818de8d7fSPeter Avalos
210950a69bb5SSascha Wildner id = conn->msg_id;
211018de8d7fSPeter Avalos startid = ackid = id + 1;
211150a69bb5SSascha Wildner data = xmalloc(conn->upload_buflen);
211218de8d7fSPeter Avalos
211318de8d7fSPeter Avalos /* Read from local and write to remote */
2114*ba1276acSMatthew Dillon offset = progress_counter = (resume ? c.size : 0);
211550a69bb5SSascha Wildner if (showprogress) {
211650a69bb5SSascha Wildner start_progress_meter(progress_meter_path(local_path),
211750a69bb5SSascha Wildner sb.st_size, &progress_counter);
211850a69bb5SSascha Wildner }
211918de8d7fSPeter Avalos
212050a69bb5SSascha Wildner if ((msg = sshbuf_new()) == NULL)
212150a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
212218de8d7fSPeter Avalos for (;;) {
212318de8d7fSPeter Avalos int len;
212418de8d7fSPeter Avalos
212518de8d7fSPeter Avalos /*
212618de8d7fSPeter Avalos * Can't use atomicio here because it returns 0 on EOF,
212718de8d7fSPeter Avalos * thus losing the last block of the file.
212818de8d7fSPeter Avalos * Simulate an EOF on interrupt, allowing ACKs from the
212918de8d7fSPeter Avalos * server to drain.
213018de8d7fSPeter Avalos */
213118de8d7fSPeter Avalos if (interrupted || status != SSH2_FX_OK)
213218de8d7fSPeter Avalos len = 0;
213318de8d7fSPeter Avalos else do
213450a69bb5SSascha Wildner len = read(local_fd, data, conn->upload_buflen);
213518de8d7fSPeter Avalos while ((len == -1) &&
213618de8d7fSPeter Avalos (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
213718de8d7fSPeter Avalos
2138ee116499SAntonio Huete Jimenez if (len == -1) {
2139ee116499SAntonio Huete Jimenez fatal("read local \"%s\": %s",
2140ee116499SAntonio Huete Jimenez local_path, strerror(errno));
2141ee116499SAntonio Huete Jimenez } else if (len != 0) {
214250a69bb5SSascha Wildner ack = request_enqueue(&acks, ++id, len, offset);
2143e9778795SPeter Avalos sshbuf_reset(msg);
2144e9778795SPeter Avalos if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
2145e9778795SPeter Avalos (r = sshbuf_put_u32(msg, ack->id)) != 0 ||
2146e9778795SPeter Avalos (r = sshbuf_put_string(msg, handle,
2147e9778795SPeter Avalos handle_len)) != 0 ||
2148e9778795SPeter Avalos (r = sshbuf_put_u64(msg, offset)) != 0 ||
2149e9778795SPeter Avalos (r = sshbuf_put_string(msg, data, len)) != 0)
215050a69bb5SSascha Wildner fatal_fr(r, "compose");
2151e9778795SPeter Avalos send_msg(conn, msg);
215218de8d7fSPeter Avalos debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
215318de8d7fSPeter Avalos id, (unsigned long long)offset, len);
215418de8d7fSPeter Avalos } else if (TAILQ_FIRST(&acks) == NULL)
215518de8d7fSPeter Avalos break;
215618de8d7fSPeter Avalos
215718de8d7fSPeter Avalos if (ack == NULL)
215818de8d7fSPeter Avalos fatal("Unexpected ACK %u", id);
215918de8d7fSPeter Avalos
216018de8d7fSPeter Avalos if (id == startid || len == 0 ||
216118de8d7fSPeter Avalos id - ackid >= conn->num_requests) {
2162e9778795SPeter Avalos u_int rid;
216318de8d7fSPeter Avalos
2164e9778795SPeter Avalos sshbuf_reset(msg);
2165e9778795SPeter Avalos get_msg(conn, msg);
2166e9778795SPeter Avalos if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
2167e9778795SPeter Avalos (r = sshbuf_get_u32(msg, &rid)) != 0)
216850a69bb5SSascha Wildner fatal_fr(r, "parse");
216918de8d7fSPeter Avalos
217018de8d7fSPeter Avalos if (type != SSH2_FXP_STATUS)
217118de8d7fSPeter Avalos fatal("Expected SSH2_FXP_STATUS(%d) packet, "
217218de8d7fSPeter Avalos "got %d", SSH2_FXP_STATUS, type);
217318de8d7fSPeter Avalos
2174e9778795SPeter Avalos if ((r = sshbuf_get_u32(msg, &status)) != 0)
217550a69bb5SSascha Wildner fatal_fr(r, "parse status");
2176e9778795SPeter Avalos debug3("SSH2_FXP_STATUS %u", status);
217718de8d7fSPeter Avalos
217818de8d7fSPeter Avalos /* Find the request in our queue */
217950a69bb5SSascha Wildner if ((ack = request_find(&acks, rid)) == NULL)
2180e9778795SPeter Avalos fatal("Can't find request for ID %u", rid);
218118de8d7fSPeter Avalos TAILQ_REMOVE(&acks, ack, tq);
218250a69bb5SSascha Wildner debug3("In write loop, ack for %u %zu bytes at %lld",
218350a69bb5SSascha Wildner ack->id, ack->len, (unsigned long long)ack->offset);
218418de8d7fSPeter Avalos ++ackid;
218536e94dc5SPeter Avalos progress_counter += ack->len;
2186*ba1276acSMatthew Dillon /*
2187*ba1276acSMatthew Dillon * Track both the highest offset acknowledged and the
2188*ba1276acSMatthew Dillon * highest *contiguous* offset acknowledged.
2189*ba1276acSMatthew Dillon * We'll need the latter for ftruncate()ing
2190*ba1276acSMatthew Dillon * interrupted transfers.
2191*ba1276acSMatthew Dillon */
2192*ba1276acSMatthew Dillon if (maxack < ack->offset + ack->len)
2193*ba1276acSMatthew Dillon maxack = ack->offset + ack->len;
2194ee116499SAntonio Huete Jimenez if (!reordered && ack->offset <= highwater)
2195*ba1276acSMatthew Dillon highwater = maxack;
2196ee116499SAntonio Huete Jimenez else if (!reordered && ack->offset > highwater) {
2197ee116499SAntonio Huete Jimenez debug3_f("server reordered ACKs");
2198ee116499SAntonio Huete Jimenez reordered = 1;
2199ee116499SAntonio Huete Jimenez }
220036e94dc5SPeter Avalos free(ack);
220118de8d7fSPeter Avalos }
220218de8d7fSPeter Avalos offset += len;
220318de8d7fSPeter Avalos if (offset < 0)
220450a69bb5SSascha Wildner fatal_f("offset < 0");
220518de8d7fSPeter Avalos }
2206e9778795SPeter Avalos sshbuf_free(msg);
220718de8d7fSPeter Avalos
220818de8d7fSPeter Avalos if (showprogress)
220918de8d7fSPeter Avalos stop_progress_meter();
221036e94dc5SPeter Avalos free(data);
221118de8d7fSPeter Avalos
2212*ba1276acSMatthew Dillon if (status == SSH2_FX_OK && !interrupted) {
2213*ba1276acSMatthew Dillon /* we got everything */
2214*ba1276acSMatthew Dillon highwater = maxack;
2215*ba1276acSMatthew Dillon }
221618de8d7fSPeter Avalos if (status != SSH2_FX_OK) {
2217ee116499SAntonio Huete Jimenez error("write remote \"%s\": %s", remote_path, fx2txt(status));
2218e9778795SPeter Avalos status = SSH2_FX_FAILURE;
221918de8d7fSPeter Avalos }
222018de8d7fSPeter Avalos
2221ee116499SAntonio Huete Jimenez if (inplace_flag || (resume && (status != SSH2_FX_OK || interrupted))) {
2222ee116499SAntonio Huete Jimenez debug("truncating at %llu", (unsigned long long)highwater);
2223ee116499SAntonio Huete Jimenez attrib_clear(&t);
2224ee116499SAntonio Huete Jimenez t.flags = SSH2_FILEXFER_ATTR_SIZE;
2225ee116499SAntonio Huete Jimenez t.size = highwater;
2226*ba1276acSMatthew Dillon sftp_fsetstat(conn, handle, handle_len, &t);
2227ee116499SAntonio Huete Jimenez }
2228ee116499SAntonio Huete Jimenez
222918de8d7fSPeter Avalos if (close(local_fd) == -1) {
2230ee116499SAntonio Huete Jimenez error("close local \"%s\": %s", local_path, strerror(errno));
2231e9778795SPeter Avalos status = SSH2_FX_FAILURE;
223218de8d7fSPeter Avalos }
223318de8d7fSPeter Avalos
223418de8d7fSPeter Avalos /* Override umask and utimes if asked */
223536e94dc5SPeter Avalos if (preserve_flag)
2236*ba1276acSMatthew Dillon sftp_fsetstat(conn, handle, handle_len, &a);
223718de8d7fSPeter Avalos
223836e94dc5SPeter Avalos if (fsync_flag)
2239*ba1276acSMatthew Dillon (void)sftp_fsync(conn, handle, handle_len);
224036e94dc5SPeter Avalos
2241*ba1276acSMatthew Dillon if (sftp_close(conn, handle, handle_len) != 0)
2242e9778795SPeter Avalos status = SSH2_FX_FAILURE;
2243e9778795SPeter Avalos
224436e94dc5SPeter Avalos free(handle);
224518de8d7fSPeter Avalos
2246e9778795SPeter Avalos return status == SSH2_FX_OK ? 0 : -1;
224718de8d7fSPeter Avalos }
2248856ea928SPeter Avalos
2249856ea928SPeter 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)2250e9778795SPeter Avalos upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
225150a69bb5SSascha Wildner int depth, int preserve_flag, int print_flag, int resume, int fsync_flag,
2252ee116499SAntonio Huete Jimenez int follow_link_flag, int inplace_flag)
2253856ea928SPeter Avalos {
2254e9778795SPeter Avalos int ret = 0;
2255856ea928SPeter Avalos DIR *dirp;
2256856ea928SPeter Avalos struct dirent *dp;
2257664f4763Szrj char *filename, *new_src = NULL, *new_dst = NULL;
2258856ea928SPeter Avalos struct stat sb;
2259*ba1276acSMatthew Dillon Attrib a, dirattrib;
226050a69bb5SSascha Wildner u_int32_t saved_perm;
2261856ea928SPeter Avalos
2262ee116499SAntonio Huete Jimenez debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst);
2263ee116499SAntonio Huete Jimenez
2264856ea928SPeter Avalos if (depth >= MAX_DIR_DEPTH) {
2265856ea928SPeter Avalos error("Maximum directory depth exceeded: %d levels", depth);
2266856ea928SPeter Avalos return -1;
2267856ea928SPeter Avalos }
2268856ea928SPeter Avalos
2269856ea928SPeter Avalos if (stat(src, &sb) == -1) {
2270ee116499SAntonio Huete Jimenez error("stat local \"%s\": %s", src, strerror(errno));
2271856ea928SPeter Avalos return -1;
2272856ea928SPeter Avalos }
2273856ea928SPeter Avalos if (!S_ISDIR(sb.st_mode)) {
2274856ea928SPeter Avalos error("\"%s\" is not a directory", src);
2275856ea928SPeter Avalos return -1;
2276856ea928SPeter Avalos }
227750a69bb5SSascha Wildner if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
2278e9778795SPeter Avalos mprintf("Entering %s\n", src);
2279856ea928SPeter Avalos
2280856ea928SPeter Avalos stat_to_attrib(&sb, &a);
2281856ea928SPeter Avalos a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
2282856ea928SPeter Avalos a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
2283856ea928SPeter Avalos a.perm &= 01777;
228436e94dc5SPeter Avalos if (!preserve_flag)
2285856ea928SPeter Avalos a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
2286856ea928SPeter Avalos
2287856ea928SPeter Avalos /*
2288e9778795SPeter Avalos * sftp lacks a portable status value to match errno EEXIST,
2289e9778795SPeter Avalos * so if we get a failure back then we must check whether
229050a69bb5SSascha Wildner * the path already existed and is a directory. Ensure we can
229150a69bb5SSascha Wildner * write to the directory we create for the duration of the transfer.
2292856ea928SPeter Avalos */
229350a69bb5SSascha Wildner saved_perm = a.perm;
229450a69bb5SSascha Wildner a.perm |= (S_IWUSR|S_IXUSR);
2295*ba1276acSMatthew Dillon if (sftp_mkdir(conn, dst, &a, 0) != 0) {
2296*ba1276acSMatthew Dillon if (sftp_stat(conn, dst, 0, &dirattrib) != 0)
2297856ea928SPeter Avalos return -1;
2298*ba1276acSMatthew Dillon if (!S_ISDIR(dirattrib.perm)) {
2299e9778795SPeter Avalos error("\"%s\" exists but is not a directory", dst);
2300856ea928SPeter Avalos return -1;
2301856ea928SPeter Avalos }
2302e9778795SPeter Avalos }
230350a69bb5SSascha Wildner a.perm = saved_perm;
2304856ea928SPeter Avalos
2305856ea928SPeter Avalos if ((dirp = opendir(src)) == NULL) {
2306ee116499SAntonio Huete Jimenez error("local opendir \"%s\": %s", src, strerror(errno));
2307856ea928SPeter Avalos return -1;
2308856ea928SPeter Avalos }
2309856ea928SPeter Avalos
2310856ea928SPeter Avalos while (((dp = readdir(dirp)) != NULL) && !interrupted) {
2311856ea928SPeter Avalos if (dp->d_ino == 0)
2312856ea928SPeter Avalos continue;
2313664f4763Szrj free(new_dst);
2314664f4763Szrj free(new_src);
2315856ea928SPeter Avalos filename = dp->d_name;
2316*ba1276acSMatthew Dillon new_dst = sftp_path_append(dst, filename);
2317*ba1276acSMatthew Dillon new_src = sftp_path_append(src, filename);
2318856ea928SPeter Avalos
2319*ba1276acSMatthew Dillon if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
2320*ba1276acSMatthew Dillon continue;
2321856ea928SPeter Avalos if (lstat(new_src, &sb) == -1) {
2322ee116499SAntonio Huete Jimenez logit("local lstat \"%s\": %s", filename,
2323856ea928SPeter Avalos strerror(errno));
2324856ea928SPeter Avalos ret = -1;
2325856ea928SPeter Avalos continue;
2326*ba1276acSMatthew Dillon }
2327*ba1276acSMatthew Dillon if (S_ISLNK(sb.st_mode)) {
2328*ba1276acSMatthew Dillon if (!follow_link_flag) {
2329*ba1276acSMatthew Dillon logit("%s: not a regular file", filename);
2330*ba1276acSMatthew Dillon continue;
2331*ba1276acSMatthew Dillon }
2332*ba1276acSMatthew Dillon /* Replace the stat contents with the symlink target */
2333*ba1276acSMatthew Dillon if (stat(new_src, &sb) == -1) {
2334*ba1276acSMatthew Dillon logit("local stat \"%s\": %s", filename,
2335*ba1276acSMatthew Dillon strerror(errno));
2336*ba1276acSMatthew Dillon ret = -1;
2337*ba1276acSMatthew Dillon continue;
2338*ba1276acSMatthew Dillon }
2339*ba1276acSMatthew Dillon }
2340*ba1276acSMatthew Dillon if (S_ISDIR(sb.st_mode)) {
2341856ea928SPeter Avalos if (upload_dir_internal(conn, new_src, new_dst,
234236e94dc5SPeter Avalos depth + 1, preserve_flag, print_flag, resume,
2343ee116499SAntonio Huete Jimenez fsync_flag, follow_link_flag, inplace_flag) == -1)
2344856ea928SPeter Avalos ret = -1;
2345*ba1276acSMatthew Dillon } else if (S_ISREG(sb.st_mode)) {
2346*ba1276acSMatthew Dillon if (sftp_upload(conn, new_src, new_dst,
2347ee116499SAntonio Huete Jimenez preserve_flag, resume, fsync_flag,
2348ee116499SAntonio Huete Jimenez inplace_flag) == -1) {
2349ee116499SAntonio Huete Jimenez error("upload \"%s\" to \"%s\" failed",
2350856ea928SPeter Avalos new_src, new_dst);
2351856ea928SPeter Avalos ret = -1;
2352856ea928SPeter Avalos }
2353856ea928SPeter Avalos } else
2354ee116499SAntonio Huete Jimenez logit("%s: not a regular file", filename);
2355664f4763Szrj }
235636e94dc5SPeter Avalos free(new_dst);
235736e94dc5SPeter Avalos free(new_src);
2358856ea928SPeter Avalos
2359*ba1276acSMatthew Dillon sftp_setstat(conn, dst, &a);
2360856ea928SPeter Avalos
2361856ea928SPeter Avalos (void) closedir(dirp);
2362856ea928SPeter Avalos return ret;
2363856ea928SPeter Avalos }
2364856ea928SPeter Avalos
2365856ea928SPeter Avalos int
sftp_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)2366*ba1276acSMatthew Dillon sftp_upload_dir(struct sftp_conn *conn, const char *src, const char *dst,
236750a69bb5SSascha Wildner int preserve_flag, int print_flag, int resume, int fsync_flag,
2368ee116499SAntonio Huete Jimenez int follow_link_flag, int inplace_flag)
2369856ea928SPeter Avalos {
2370856ea928SPeter Avalos char *dst_canon;
2371856ea928SPeter Avalos int ret;
2372856ea928SPeter Avalos
2373*ba1276acSMatthew Dillon if ((dst_canon = sftp_realpath(conn, dst)) == NULL) {
2374ee116499SAntonio Huete Jimenez error("upload \"%s\": path canonicalization failed", dst);
2375856ea928SPeter Avalos return -1;
2376856ea928SPeter Avalos }
2377856ea928SPeter Avalos
237836e94dc5SPeter Avalos ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag,
2379ee116499SAntonio Huete Jimenez print_flag, resume, fsync_flag, follow_link_flag, inplace_flag);
238036e94dc5SPeter Avalos
238136e94dc5SPeter Avalos free(dst_canon);
2382856ea928SPeter Avalos return ret;
2383856ea928SPeter Avalos }
2384856ea928SPeter Avalos
238550a69bb5SSascha Wildner static void
handle_dest_replies(struct sftp_conn * to,const char * to_path,int synchronous,u_int * nreqsp,u_int * write_errorp)238650a69bb5SSascha Wildner handle_dest_replies(struct sftp_conn *to, const char *to_path, int synchronous,
238750a69bb5SSascha Wildner u_int *nreqsp, u_int *write_errorp)
238850a69bb5SSascha Wildner {
238950a69bb5SSascha Wildner struct sshbuf *msg;
239050a69bb5SSascha Wildner u_char type;
239150a69bb5SSascha Wildner u_int id, status;
239250a69bb5SSascha Wildner int r;
239350a69bb5SSascha Wildner struct pollfd pfd;
239450a69bb5SSascha Wildner
239550a69bb5SSascha Wildner if ((msg = sshbuf_new()) == NULL)
239650a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
239750a69bb5SSascha Wildner
239850a69bb5SSascha Wildner /* Try to eat replies from the upload side */
239950a69bb5SSascha Wildner while (*nreqsp > 0) {
240050a69bb5SSascha Wildner debug3_f("%u outstanding replies", *nreqsp);
240150a69bb5SSascha Wildner if (!synchronous) {
240250a69bb5SSascha Wildner /* Bail out if no data is ready to be read */
240350a69bb5SSascha Wildner pfd.fd = to->fd_in;
240450a69bb5SSascha Wildner pfd.events = POLLIN;
240550a69bb5SSascha Wildner if ((r = poll(&pfd, 1, 0)) == -1) {
240650a69bb5SSascha Wildner if (errno == EINTR)
240750a69bb5SSascha Wildner break;
240850a69bb5SSascha Wildner fatal_f("poll: %s", strerror(errno));
240950a69bb5SSascha Wildner } else if (r == 0)
241050a69bb5SSascha Wildner break; /* fd not ready */
241150a69bb5SSascha Wildner }
241250a69bb5SSascha Wildner sshbuf_reset(msg);
241350a69bb5SSascha Wildner get_msg(to, msg);
241450a69bb5SSascha Wildner
241550a69bb5SSascha Wildner if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
241650a69bb5SSascha Wildner (r = sshbuf_get_u32(msg, &id)) != 0)
241750a69bb5SSascha Wildner fatal_fr(r, "dest parse");
241850a69bb5SSascha Wildner debug3("Received dest reply T:%u I:%u R:%u", type, id, *nreqsp);
241950a69bb5SSascha Wildner if (type != SSH2_FXP_STATUS) {
242050a69bb5SSascha Wildner fatal_f("Expected SSH2_FXP_STATUS(%d) packet, got %d",
242150a69bb5SSascha Wildner SSH2_FXP_STATUS, type);
242250a69bb5SSascha Wildner }
242350a69bb5SSascha Wildner if ((r = sshbuf_get_u32(msg, &status)) != 0)
242450a69bb5SSascha Wildner fatal_fr(r, "parse dest status");
242550a69bb5SSascha Wildner debug3("dest SSH2_FXP_STATUS %u", status);
242650a69bb5SSascha Wildner if (status != SSH2_FX_OK) {
242750a69bb5SSascha Wildner /* record first error */
242850a69bb5SSascha Wildner if (*write_errorp == 0)
242950a69bb5SSascha Wildner *write_errorp = status;
243050a69bb5SSascha Wildner }
243150a69bb5SSascha Wildner /*
2432*ba1276acSMatthew Dillon * XXX this doesn't do full reply matching like sftp_upload and
243350a69bb5SSascha Wildner * so cannot gracefully truncate terminated uploads at a
243450a69bb5SSascha Wildner * high-water mark. ATM the only caller of this function (scp)
243550a69bb5SSascha Wildner * doesn't support transfer resumption, so this doesn't matter
243650a69bb5SSascha Wildner * a whole lot.
243750a69bb5SSascha Wildner *
2438*ba1276acSMatthew Dillon * To be safe, sftp_crossload truncates the destination file to
243950a69bb5SSascha Wildner * zero length on upload failure, since we can't trust the
244050a69bb5SSascha Wildner * server not to have reordered replies that could have
244150a69bb5SSascha Wildner * inserted holes where none existed in the source file.
244250a69bb5SSascha Wildner *
2443*ba1276acSMatthew Dillon * XXX we could get a more accurate progress bar if we updated
244450a69bb5SSascha Wildner * the counter based on the reply from the destination...
244550a69bb5SSascha Wildner */
244650a69bb5SSascha Wildner (*nreqsp)--;
244750a69bb5SSascha Wildner }
244850a69bb5SSascha Wildner debug3_f("done: %u outstanding replies", *nreqsp);
2449ee116499SAntonio Huete Jimenez sshbuf_free(msg);
245050a69bb5SSascha Wildner }
245150a69bb5SSascha Wildner
245250a69bb5SSascha Wildner int
sftp_crossload(struct sftp_conn * from,struct sftp_conn * to,const char * from_path,const char * to_path,Attrib * a,int preserve_flag)2453*ba1276acSMatthew Dillon sftp_crossload(struct sftp_conn *from, struct sftp_conn *to,
245450a69bb5SSascha Wildner const char *from_path, const char *to_path,
245550a69bb5SSascha Wildner Attrib *a, int preserve_flag)
245650a69bb5SSascha Wildner {
245750a69bb5SSascha Wildner struct sshbuf *msg;
245850a69bb5SSascha Wildner int write_error, read_error, r;
245950a69bb5SSascha Wildner u_int64_t offset = 0, size;
246050a69bb5SSascha Wildner u_int id, buflen, num_req, max_req, status = SSH2_FX_OK;
246150a69bb5SSascha Wildner u_int num_upload_req;
246250a69bb5SSascha Wildner off_t progress_counter;
246350a69bb5SSascha Wildner u_char *from_handle, *to_handle;
246450a69bb5SSascha Wildner size_t from_handle_len, to_handle_len;
246550a69bb5SSascha Wildner struct requests requests;
246650a69bb5SSascha Wildner struct request *req;
246750a69bb5SSascha Wildner u_char type;
2468*ba1276acSMatthew Dillon Attrib attr;
246950a69bb5SSascha Wildner
2470ee116499SAntonio Huete Jimenez debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path);
2471ee116499SAntonio Huete Jimenez
247250a69bb5SSascha Wildner TAILQ_INIT(&requests);
247350a69bb5SSascha Wildner
2474*ba1276acSMatthew Dillon if (a == NULL) {
2475*ba1276acSMatthew Dillon if (sftp_stat(from, from_path, 0, &attr) != 0)
247650a69bb5SSascha Wildner return -1;
2477*ba1276acSMatthew Dillon a = &attr;
2478*ba1276acSMatthew Dillon }
247950a69bb5SSascha Wildner
248050a69bb5SSascha Wildner if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
248150a69bb5SSascha Wildner (!S_ISREG(a->perm))) {
2482ee116499SAntonio Huete Jimenez error("download \"%s\": not a regular file", from_path);
248350a69bb5SSascha Wildner return(-1);
248450a69bb5SSascha Wildner }
248550a69bb5SSascha Wildner if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
248650a69bb5SSascha Wildner size = a->size;
248750a69bb5SSascha Wildner else
248850a69bb5SSascha Wildner size = 0;
248950a69bb5SSascha Wildner
249050a69bb5SSascha Wildner buflen = from->download_buflen;
249150a69bb5SSascha Wildner if (buflen > to->upload_buflen)
249250a69bb5SSascha Wildner buflen = to->upload_buflen;
249350a69bb5SSascha Wildner
249450a69bb5SSascha Wildner /* Send open request to read side */
249550a69bb5SSascha Wildner if (send_open(from, from_path, "origin", SSH2_FXF_READ, NULL,
249650a69bb5SSascha Wildner &from_handle, &from_handle_len) != 0)
249750a69bb5SSascha Wildner return -1;
249850a69bb5SSascha Wildner
249950a69bb5SSascha Wildner /* Send open request to write side */
250050a69bb5SSascha Wildner a->flags &= ~SSH2_FILEXFER_ATTR_SIZE;
250150a69bb5SSascha Wildner a->flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
250250a69bb5SSascha Wildner a->perm &= 0777;
250350a69bb5SSascha Wildner if (!preserve_flag)
250450a69bb5SSascha Wildner a->flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
250550a69bb5SSascha Wildner if (send_open(to, to_path, "dest",
250650a69bb5SSascha Wildner SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
250750a69bb5SSascha Wildner &to_handle, &to_handle_len) != 0) {
2508*ba1276acSMatthew Dillon sftp_close(from, from_handle, from_handle_len);
250950a69bb5SSascha Wildner return -1;
251050a69bb5SSascha Wildner }
251150a69bb5SSascha Wildner
251250a69bb5SSascha Wildner /* Read from remote "from" and write to remote "to" */
251350a69bb5SSascha Wildner offset = 0;
251450a69bb5SSascha Wildner write_error = read_error = num_req = num_upload_req = 0;
251550a69bb5SSascha Wildner max_req = 1;
251650a69bb5SSascha Wildner progress_counter = 0;
251750a69bb5SSascha Wildner
251850a69bb5SSascha Wildner if (showprogress && size != 0) {
251950a69bb5SSascha Wildner start_progress_meter(progress_meter_path(from_path),
252050a69bb5SSascha Wildner size, &progress_counter);
252150a69bb5SSascha Wildner }
252250a69bb5SSascha Wildner if ((msg = sshbuf_new()) == NULL)
252350a69bb5SSascha Wildner fatal_f("sshbuf_new failed");
252450a69bb5SSascha Wildner while (num_req > 0 || max_req > 0) {
252550a69bb5SSascha Wildner u_char *data;
252650a69bb5SSascha Wildner size_t len;
252750a69bb5SSascha Wildner
252850a69bb5SSascha Wildner /*
252950a69bb5SSascha Wildner * Simulate EOF on interrupt: stop sending new requests and
253050a69bb5SSascha Wildner * allow outstanding requests to drain gracefully
253150a69bb5SSascha Wildner */
253250a69bb5SSascha Wildner if (interrupted) {
253350a69bb5SSascha Wildner if (num_req == 0) /* If we haven't started yet... */
253450a69bb5SSascha Wildner break;
253550a69bb5SSascha Wildner max_req = 0;
253650a69bb5SSascha Wildner }
253750a69bb5SSascha Wildner
253850a69bb5SSascha Wildner /* Send some more requests */
253950a69bb5SSascha Wildner while (num_req < max_req) {
254050a69bb5SSascha Wildner debug3("Request range %llu -> %llu (%d/%d)",
254150a69bb5SSascha Wildner (unsigned long long)offset,
254250a69bb5SSascha Wildner (unsigned long long)offset + buflen - 1,
254350a69bb5SSascha Wildner num_req, max_req);
254450a69bb5SSascha Wildner req = request_enqueue(&requests, from->msg_id++,
254550a69bb5SSascha Wildner buflen, offset);
254650a69bb5SSascha Wildner offset += buflen;
254750a69bb5SSascha Wildner num_req++;
254850a69bb5SSascha Wildner send_read_request(from, req->id, req->offset,
254950a69bb5SSascha Wildner req->len, from_handle, from_handle_len);
255050a69bb5SSascha Wildner }
255150a69bb5SSascha Wildner
255250a69bb5SSascha Wildner /* Try to eat replies from the upload side (nonblocking) */
255350a69bb5SSascha Wildner handle_dest_replies(to, to_path, 0,
255450a69bb5SSascha Wildner &num_upload_req, &write_error);
255550a69bb5SSascha Wildner
255650a69bb5SSascha Wildner sshbuf_reset(msg);
255750a69bb5SSascha Wildner get_msg(from, msg);
255850a69bb5SSascha Wildner if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
255950a69bb5SSascha Wildner (r = sshbuf_get_u32(msg, &id)) != 0)
256050a69bb5SSascha Wildner fatal_fr(r, "parse");
256150a69bb5SSascha Wildner debug3("Received origin reply T:%u I:%u R:%d",
256250a69bb5SSascha Wildner type, id, max_req);
256350a69bb5SSascha Wildner
256450a69bb5SSascha Wildner /* Find the request in our queue */
256550a69bb5SSascha Wildner if ((req = request_find(&requests, id)) == NULL)
256650a69bb5SSascha Wildner fatal("Unexpected reply %u", id);
256750a69bb5SSascha Wildner
256850a69bb5SSascha Wildner switch (type) {
256950a69bb5SSascha Wildner case SSH2_FXP_STATUS:
257050a69bb5SSascha Wildner if ((r = sshbuf_get_u32(msg, &status)) != 0)
257150a69bb5SSascha Wildner fatal_fr(r, "parse status");
257250a69bb5SSascha Wildner if (status != SSH2_FX_EOF)
257350a69bb5SSascha Wildner read_error = 1;
257450a69bb5SSascha Wildner max_req = 0;
257550a69bb5SSascha Wildner TAILQ_REMOVE(&requests, req, tq);
257650a69bb5SSascha Wildner free(req);
257750a69bb5SSascha Wildner num_req--;
257850a69bb5SSascha Wildner break;
257950a69bb5SSascha Wildner case SSH2_FXP_DATA:
258050a69bb5SSascha Wildner if ((r = sshbuf_get_string(msg, &data, &len)) != 0)
258150a69bb5SSascha Wildner fatal_fr(r, "parse data");
258250a69bb5SSascha Wildner debug3("Received data %llu -> %llu",
258350a69bb5SSascha Wildner (unsigned long long)req->offset,
258450a69bb5SSascha Wildner (unsigned long long)req->offset + len - 1);
258550a69bb5SSascha Wildner if (len > req->len)
258650a69bb5SSascha Wildner fatal("Received more data than asked for "
258750a69bb5SSascha Wildner "%zu > %zu", len, req->len);
258850a69bb5SSascha Wildner
258950a69bb5SSascha Wildner /* Write this chunk out to the destination */
259050a69bb5SSascha Wildner sshbuf_reset(msg);
259150a69bb5SSascha Wildner if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 ||
259250a69bb5SSascha Wildner (r = sshbuf_put_u32(msg, to->msg_id++)) != 0 ||
259350a69bb5SSascha Wildner (r = sshbuf_put_string(msg, to_handle,
259450a69bb5SSascha Wildner to_handle_len)) != 0 ||
259550a69bb5SSascha Wildner (r = sshbuf_put_u64(msg, req->offset)) != 0 ||
259650a69bb5SSascha Wildner (r = sshbuf_put_string(msg, data, len)) != 0)
259750a69bb5SSascha Wildner fatal_fr(r, "compose write");
259850a69bb5SSascha Wildner send_msg(to, msg);
259950a69bb5SSascha Wildner debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%zu",
260050a69bb5SSascha Wildner id, (unsigned long long)offset, len);
260150a69bb5SSascha Wildner num_upload_req++;
260250a69bb5SSascha Wildner progress_counter += len;
260350a69bb5SSascha Wildner free(data);
260450a69bb5SSascha Wildner
260550a69bb5SSascha Wildner if (len == req->len) {
260650a69bb5SSascha Wildner TAILQ_REMOVE(&requests, req, tq);
260750a69bb5SSascha Wildner free(req);
260850a69bb5SSascha Wildner num_req--;
260950a69bb5SSascha Wildner } else {
261050a69bb5SSascha Wildner /* Resend the request for the missing data */
261150a69bb5SSascha Wildner debug3("Short data block, re-requesting "
261250a69bb5SSascha Wildner "%llu -> %llu (%2d)",
261350a69bb5SSascha Wildner (unsigned long long)req->offset + len,
261450a69bb5SSascha Wildner (unsigned long long)req->offset +
261550a69bb5SSascha Wildner req->len - 1, num_req);
261650a69bb5SSascha Wildner req->id = from->msg_id++;
261750a69bb5SSascha Wildner req->len -= len;
261850a69bb5SSascha Wildner req->offset += len;
261950a69bb5SSascha Wildner send_read_request(from, req->id,
262050a69bb5SSascha Wildner req->offset, req->len,
262150a69bb5SSascha Wildner from_handle, from_handle_len);
262250a69bb5SSascha Wildner /* Reduce the request size */
262350a69bb5SSascha Wildner if (len < buflen)
262450a69bb5SSascha Wildner buflen = MAXIMUM(MIN_READ_SIZE, len);
262550a69bb5SSascha Wildner }
262650a69bb5SSascha Wildner if (max_req > 0) { /* max_req = 0 iff EOF received */
262750a69bb5SSascha Wildner if (size > 0 && offset > size) {
262850a69bb5SSascha Wildner /* Only one request at a time
262950a69bb5SSascha Wildner * after the expected EOF */
263050a69bb5SSascha Wildner debug3("Finish at %llu (%2d)",
263150a69bb5SSascha Wildner (unsigned long long)offset,
263250a69bb5SSascha Wildner num_req);
263350a69bb5SSascha Wildner max_req = 1;
263450a69bb5SSascha Wildner } else if (max_req < from->num_requests) {
263550a69bb5SSascha Wildner ++max_req;
263650a69bb5SSascha Wildner }
263750a69bb5SSascha Wildner }
263850a69bb5SSascha Wildner break;
263950a69bb5SSascha Wildner default:
264050a69bb5SSascha Wildner fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
264150a69bb5SSascha Wildner SSH2_FXP_DATA, type);
264250a69bb5SSascha Wildner }
264350a69bb5SSascha Wildner }
264450a69bb5SSascha Wildner
264550a69bb5SSascha Wildner if (showprogress && size)
264650a69bb5SSascha Wildner stop_progress_meter();
264750a69bb5SSascha Wildner
264850a69bb5SSascha Wildner /* Drain replies from the server (blocking) */
264950a69bb5SSascha Wildner debug3_f("waiting for %u replies from destination", num_upload_req);
265050a69bb5SSascha Wildner handle_dest_replies(to, to_path, 1, &num_upload_req, &write_error);
265150a69bb5SSascha Wildner
265250a69bb5SSascha Wildner /* Sanity check */
265350a69bb5SSascha Wildner if (TAILQ_FIRST(&requests) != NULL)
265450a69bb5SSascha Wildner fatal("Transfer complete, but requests still in queue");
265550a69bb5SSascha Wildner /* Truncate at 0 length on interrupt or error to avoid holes at dest */
265650a69bb5SSascha Wildner if (read_error || write_error || interrupted) {
265750a69bb5SSascha Wildner debug("truncating \"%s\" at 0", to_path);
2658*ba1276acSMatthew Dillon sftp_close(to, to_handle, to_handle_len);
265950a69bb5SSascha Wildner free(to_handle);
266050a69bb5SSascha Wildner if (send_open(to, to_path, "dest",
266150a69bb5SSascha Wildner SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a,
266250a69bb5SSascha Wildner &to_handle, &to_handle_len) != 0) {
2663ee116499SAntonio Huete Jimenez error("dest truncate \"%s\" failed", to_path);
266450a69bb5SSascha Wildner to_handle = NULL;
266550a69bb5SSascha Wildner }
266650a69bb5SSascha Wildner }
266750a69bb5SSascha Wildner if (read_error) {
2668ee116499SAntonio Huete Jimenez error("read origin \"%s\": %s", from_path, fx2txt(status));
266950a69bb5SSascha Wildner status = -1;
2670*ba1276acSMatthew Dillon sftp_close(from, from_handle, from_handle_len);
267150a69bb5SSascha Wildner if (to_handle != NULL)
2672*ba1276acSMatthew Dillon sftp_close(to, to_handle, to_handle_len);
267350a69bb5SSascha Wildner } else if (write_error) {
2674ee116499SAntonio Huete Jimenez error("write dest \"%s\": %s", to_path, fx2txt(write_error));
267550a69bb5SSascha Wildner status = SSH2_FX_FAILURE;
2676*ba1276acSMatthew Dillon sftp_close(from, from_handle, from_handle_len);
267750a69bb5SSascha Wildner if (to_handle != NULL)
2678*ba1276acSMatthew Dillon sftp_close(to, to_handle, to_handle_len);
267950a69bb5SSascha Wildner } else {
2680*ba1276acSMatthew Dillon if (sftp_close(from, from_handle, from_handle_len) != 0 ||
268150a69bb5SSascha Wildner interrupted)
268250a69bb5SSascha Wildner status = -1;
268350a69bb5SSascha Wildner else
268450a69bb5SSascha Wildner status = SSH2_FX_OK;
268550a69bb5SSascha Wildner if (to_handle != NULL) {
268650a69bb5SSascha Wildner /* Need to resend utimes after write */
268750a69bb5SSascha Wildner if (preserve_flag)
2688*ba1276acSMatthew Dillon sftp_fsetstat(to, to_handle, to_handle_len, a);
2689*ba1276acSMatthew Dillon sftp_close(to, to_handle, to_handle_len);
269050a69bb5SSascha Wildner }
269150a69bb5SSascha Wildner }
269250a69bb5SSascha Wildner sshbuf_free(msg);
269350a69bb5SSascha Wildner free(from_handle);
269450a69bb5SSascha Wildner free(to_handle);
269550a69bb5SSascha Wildner
269650a69bb5SSascha Wildner return status == SSH2_FX_OK ? 0 : -1;
269750a69bb5SSascha Wildner }
269850a69bb5SSascha Wildner
269950a69bb5SSascha 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)270050a69bb5SSascha Wildner crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to,
270150a69bb5SSascha Wildner const char *from_path, const char *to_path,
270250a69bb5SSascha Wildner int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
270350a69bb5SSascha Wildner int follow_link_flag)
270450a69bb5SSascha Wildner {
270550a69bb5SSascha Wildner int i, ret = 0;
270650a69bb5SSascha Wildner SFTP_DIRENT **dir_entries;
270750a69bb5SSascha Wildner char *filename, *new_from_path = NULL, *new_to_path = NULL;
270850a69bb5SSascha Wildner mode_t mode = 0777;
2709*ba1276acSMatthew Dillon Attrib *a, curdir, ldirattrib, newdir, lsym;
271050a69bb5SSascha Wildner
2711ee116499SAntonio Huete Jimenez debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path);
2712ee116499SAntonio Huete Jimenez
271350a69bb5SSascha Wildner if (depth >= MAX_DIR_DEPTH) {
271450a69bb5SSascha Wildner error("Maximum directory depth exceeded: %d levels", depth);
271550a69bb5SSascha Wildner return -1;
271650a69bb5SSascha Wildner }
271750a69bb5SSascha Wildner
2718*ba1276acSMatthew Dillon if (dirattrib == NULL) {
2719*ba1276acSMatthew Dillon if (sftp_stat(from, from_path, 1, &ldirattrib) != 0) {
2720ee116499SAntonio Huete Jimenez error("stat remote \"%s\" failed", from_path);
272150a69bb5SSascha Wildner return -1;
272250a69bb5SSascha Wildner }
2723*ba1276acSMatthew Dillon dirattrib = &ldirattrib;
2724*ba1276acSMatthew Dillon }
272550a69bb5SSascha Wildner if (!S_ISDIR(dirattrib->perm)) {
272650a69bb5SSascha Wildner error("\"%s\" is not a directory", from_path);
272750a69bb5SSascha Wildner return -1;
272850a69bb5SSascha Wildner }
272950a69bb5SSascha Wildner if (print_flag && print_flag != SFTP_PROGRESS_ONLY)
273050a69bb5SSascha Wildner mprintf("Retrieving %s\n", from_path);
273150a69bb5SSascha Wildner
273250a69bb5SSascha Wildner curdir = *dirattrib; /* dirattrib will be clobbered */
273350a69bb5SSascha Wildner curdir.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
273450a69bb5SSascha Wildner curdir.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
273550a69bb5SSascha Wildner if ((curdir.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) == 0) {
273650a69bb5SSascha Wildner debug("Origin did not send permissions for "
273750a69bb5SSascha Wildner "directory \"%s\"", to_path);
273850a69bb5SSascha Wildner curdir.perm = S_IWUSR|S_IXUSR;
273950a69bb5SSascha Wildner curdir.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
274050a69bb5SSascha Wildner }
274150a69bb5SSascha Wildner /* We need to be able to write to the directory while we transfer it */
274250a69bb5SSascha Wildner mode = curdir.perm & 01777;
274350a69bb5SSascha Wildner curdir.perm = mode | (S_IWUSR|S_IXUSR);
274450a69bb5SSascha Wildner
274550a69bb5SSascha Wildner /*
274650a69bb5SSascha Wildner * sftp lacks a portable status value to match errno EEXIST,
274750a69bb5SSascha Wildner * so if we get a failure back then we must check whether
274850a69bb5SSascha Wildner * the path already existed and is a directory. Ensure we can
274950a69bb5SSascha Wildner * write to the directory we create for the duration of the transfer.
275050a69bb5SSascha Wildner */
2751*ba1276acSMatthew Dillon if (sftp_mkdir(to, to_path, &curdir, 0) != 0) {
2752*ba1276acSMatthew Dillon if (sftp_stat(to, to_path, 0, &newdir) != 0)
275350a69bb5SSascha Wildner return -1;
2754*ba1276acSMatthew Dillon if (!S_ISDIR(newdir.perm)) {
275550a69bb5SSascha Wildner error("\"%s\" exists but is not a directory", to_path);
275650a69bb5SSascha Wildner return -1;
275750a69bb5SSascha Wildner }
275850a69bb5SSascha Wildner }
275950a69bb5SSascha Wildner curdir.perm = mode;
276050a69bb5SSascha Wildner
2761*ba1276acSMatthew Dillon if (sftp_readdir(from, from_path, &dir_entries) == -1) {
2762ee116499SAntonio Huete Jimenez error("origin readdir \"%s\" failed", from_path);
276350a69bb5SSascha Wildner return -1;
276450a69bb5SSascha Wildner }
276550a69bb5SSascha Wildner
276650a69bb5SSascha Wildner for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
276750a69bb5SSascha Wildner free(new_from_path);
276850a69bb5SSascha Wildner free(new_to_path);
276950a69bb5SSascha Wildner
277050a69bb5SSascha Wildner filename = dir_entries[i]->filename;
2771*ba1276acSMatthew Dillon new_from_path = sftp_path_append(from_path, filename);
2772*ba1276acSMatthew Dillon new_to_path = sftp_path_append(to_path, filename);
277350a69bb5SSascha Wildner
2774*ba1276acSMatthew Dillon a = &dir_entries[i]->a;
2775*ba1276acSMatthew Dillon if (S_ISLNK(a->perm)) {
2776*ba1276acSMatthew Dillon if (!follow_link_flag) {
2777*ba1276acSMatthew Dillon logit("%s: not a regular file", filename);
2778*ba1276acSMatthew Dillon continue;
2779*ba1276acSMatthew Dillon }
2780*ba1276acSMatthew Dillon /* Replace the stat contents with the symlink target */
2781*ba1276acSMatthew Dillon if (sftp_stat(from, new_from_path, 1, &lsym) != 0) {
2782*ba1276acSMatthew Dillon logit("remote stat \"%s\" failed",
2783*ba1276acSMatthew Dillon new_from_path);
2784*ba1276acSMatthew Dillon ret = -1;
2785*ba1276acSMatthew Dillon continue;
2786*ba1276acSMatthew Dillon }
2787*ba1276acSMatthew Dillon a = &lsym;
2788*ba1276acSMatthew Dillon }
2789*ba1276acSMatthew Dillon if (S_ISDIR(a->perm)) {
279050a69bb5SSascha Wildner if (strcmp(filename, ".") == 0 ||
279150a69bb5SSascha Wildner strcmp(filename, "..") == 0)
279250a69bb5SSascha Wildner continue;
279350a69bb5SSascha Wildner if (crossload_dir_internal(from, to,
279450a69bb5SSascha Wildner new_from_path, new_to_path,
2795*ba1276acSMatthew Dillon depth + 1, a, preserve_flag,
279650a69bb5SSascha Wildner print_flag, follow_link_flag) == -1)
279750a69bb5SSascha Wildner ret = -1;
2798*ba1276acSMatthew Dillon } else if (S_ISREG(a->perm)) {
2799*ba1276acSMatthew Dillon if (sftp_crossload(from, to, new_from_path,
2800*ba1276acSMatthew Dillon new_to_path, a, preserve_flag) == -1) {
2801ee116499SAntonio Huete Jimenez error("crossload \"%s\" to \"%s\" failed",
280250a69bb5SSascha Wildner new_from_path, new_to_path);
280350a69bb5SSascha Wildner ret = -1;
280450a69bb5SSascha Wildner }
2805ee116499SAntonio Huete Jimenez } else {
2806ee116499SAntonio Huete Jimenez logit("origin \"%s\": not a regular file",
2807ee116499SAntonio Huete Jimenez new_from_path);
2808ee116499SAntonio Huete Jimenez }
280950a69bb5SSascha Wildner }
281050a69bb5SSascha Wildner free(new_to_path);
281150a69bb5SSascha Wildner free(new_from_path);
281250a69bb5SSascha Wildner
2813*ba1276acSMatthew Dillon sftp_setstat(to, to_path, &curdir);
281450a69bb5SSascha Wildner
2815*ba1276acSMatthew Dillon sftp_free_dirents(dir_entries);
281650a69bb5SSascha Wildner
281750a69bb5SSascha Wildner return ret;
281850a69bb5SSascha Wildner }
281950a69bb5SSascha Wildner
282050a69bb5SSascha Wildner int
sftp_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)2821*ba1276acSMatthew Dillon sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to,
282250a69bb5SSascha Wildner const char *from_path, const char *to_path,
282350a69bb5SSascha Wildner Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag)
282450a69bb5SSascha Wildner {
282550a69bb5SSascha Wildner char *from_path_canon;
282650a69bb5SSascha Wildner int ret;
282750a69bb5SSascha Wildner
2828*ba1276acSMatthew Dillon if ((from_path_canon = sftp_realpath(from, from_path)) == NULL) {
2829ee116499SAntonio Huete Jimenez error("crossload \"%s\": path canonicalization failed",
2830ee116499SAntonio Huete Jimenez from_path);
283150a69bb5SSascha Wildner return -1;
283250a69bb5SSascha Wildner }
283350a69bb5SSascha Wildner
283450a69bb5SSascha Wildner ret = crossload_dir_internal(from, to, from_path_canon, to_path, 0,
283550a69bb5SSascha Wildner dirattrib, preserve_flag, print_flag, follow_link_flag);
283650a69bb5SSascha Wildner free(from_path_canon);
283750a69bb5SSascha Wildner return ret;
283850a69bb5SSascha Wildner }
283950a69bb5SSascha Wildner
2840ee116499SAntonio Huete Jimenez int
sftp_can_get_users_groups_by_id(struct sftp_conn * conn)2841*ba1276acSMatthew Dillon sftp_can_get_users_groups_by_id(struct sftp_conn *conn)
2842ee116499SAntonio Huete Jimenez {
2843ee116499SAntonio Huete Jimenez return (conn->exts & SFTP_EXT_GETUSERSGROUPS_BY_ID) != 0;
2844ee116499SAntonio Huete Jimenez }
2845ee116499SAntonio Huete Jimenez
2846ee116499SAntonio Huete Jimenez int
sftp_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)2847*ba1276acSMatthew Dillon sftp_get_users_groups_by_id(struct sftp_conn *conn,
2848ee116499SAntonio Huete Jimenez const u_int *uids, u_int nuids,
2849ee116499SAntonio Huete Jimenez const u_int *gids, u_int ngids,
2850ee116499SAntonio Huete Jimenez char ***usernamesp, char ***groupnamesp)
2851ee116499SAntonio Huete Jimenez {
2852ee116499SAntonio Huete Jimenez struct sshbuf *msg, *uidbuf, *gidbuf;
2853ee116499SAntonio Huete Jimenez u_int i, expected_id, id;
2854ee116499SAntonio Huete Jimenez char *name, **usernames = NULL, **groupnames = NULL;
2855ee116499SAntonio Huete Jimenez u_char type;
2856ee116499SAntonio Huete Jimenez int r;
2857ee116499SAntonio Huete Jimenez
2858ee116499SAntonio Huete Jimenez *usernamesp = *groupnamesp = NULL;
2859*ba1276acSMatthew Dillon if (!sftp_can_get_users_groups_by_id(conn))
2860ee116499SAntonio Huete Jimenez return SSH_ERR_FEATURE_UNSUPPORTED;
2861ee116499SAntonio Huete Jimenez
2862ee116499SAntonio Huete Jimenez if ((msg = sshbuf_new()) == NULL ||
2863ee116499SAntonio Huete Jimenez (uidbuf = sshbuf_new()) == NULL ||
2864ee116499SAntonio Huete Jimenez (gidbuf = sshbuf_new()) == NULL)
2865ee116499SAntonio Huete Jimenez fatal_f("sshbuf_new failed");
2866ee116499SAntonio Huete Jimenez expected_id = id = conn->msg_id++;
2867ee116499SAntonio Huete Jimenez debug2("Sending SSH2_FXP_EXTENDED(users-groups-by-id@openssh.com)");
2868ee116499SAntonio Huete Jimenez for (i = 0; i < nuids; i++) {
2869ee116499SAntonio Huete Jimenez if ((r = sshbuf_put_u32(uidbuf, uids[i])) != 0)
2870ee116499SAntonio Huete Jimenez fatal_fr(r, "compose uids");
2871ee116499SAntonio Huete Jimenez }
2872ee116499SAntonio Huete Jimenez for (i = 0; i < ngids; i++) {
2873ee116499SAntonio Huete Jimenez if ((r = sshbuf_put_u32(gidbuf, gids[i])) != 0)
2874ee116499SAntonio Huete Jimenez fatal_fr(r, "compose gids");
2875ee116499SAntonio Huete Jimenez }
2876ee116499SAntonio Huete Jimenez if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
2877ee116499SAntonio Huete Jimenez (r = sshbuf_put_u32(msg, id)) != 0 ||
2878ee116499SAntonio Huete Jimenez (r = sshbuf_put_cstring(msg,
2879ee116499SAntonio Huete Jimenez "users-groups-by-id@openssh.com")) != 0 ||
2880ee116499SAntonio Huete Jimenez (r = sshbuf_put_stringb(msg, uidbuf)) != 0 ||
2881ee116499SAntonio Huete Jimenez (r = sshbuf_put_stringb(msg, gidbuf)) != 0)
2882ee116499SAntonio Huete Jimenez fatal_fr(r, "compose");
2883ee116499SAntonio Huete Jimenez send_msg(conn, msg);
2884ee116499SAntonio Huete Jimenez get_msg(conn, msg);
2885ee116499SAntonio Huete Jimenez if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
2886ee116499SAntonio Huete Jimenez (r = sshbuf_get_u32(msg, &id)) != 0)
2887ee116499SAntonio Huete Jimenez fatal_fr(r, "parse");
2888ee116499SAntonio Huete Jimenez if (id != expected_id)
2889ee116499SAntonio Huete Jimenez fatal("ID mismatch (%u != %u)", id, expected_id);
2890ee116499SAntonio Huete Jimenez if (type == SSH2_FXP_STATUS) {
2891ee116499SAntonio Huete Jimenez u_int status;
2892ee116499SAntonio Huete Jimenez char *errmsg;
2893ee116499SAntonio Huete Jimenez
2894ee116499SAntonio Huete Jimenez if ((r = sshbuf_get_u32(msg, &status)) != 0 ||
2895ee116499SAntonio Huete Jimenez (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0)
2896ee116499SAntonio Huete Jimenez fatal_fr(r, "parse status");
2897ee116499SAntonio Huete Jimenez error("users-groups-by-id %s",
2898ee116499SAntonio Huete Jimenez *errmsg == '\0' ? fx2txt(status) : errmsg);
2899ee116499SAntonio Huete Jimenez free(errmsg);
2900ee116499SAntonio Huete Jimenez sshbuf_free(msg);
2901ee116499SAntonio Huete Jimenez sshbuf_free(uidbuf);
2902ee116499SAntonio Huete Jimenez sshbuf_free(gidbuf);
2903ee116499SAntonio Huete Jimenez return -1;
2904ee116499SAntonio Huete Jimenez } else if (type != SSH2_FXP_EXTENDED_REPLY)
2905ee116499SAntonio Huete Jimenez fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
2906ee116499SAntonio Huete Jimenez SSH2_FXP_EXTENDED_REPLY, type);
2907ee116499SAntonio Huete Jimenez
2908ee116499SAntonio Huete Jimenez /* reuse */
2909ee116499SAntonio Huete Jimenez sshbuf_free(uidbuf);
2910ee116499SAntonio Huete Jimenez sshbuf_free(gidbuf);
2911ee116499SAntonio Huete Jimenez uidbuf = gidbuf = NULL;
2912ee116499SAntonio Huete Jimenez if ((r = sshbuf_froms(msg, &uidbuf)) != 0 ||
2913ee116499SAntonio Huete Jimenez (r = sshbuf_froms(msg, &gidbuf)) != 0)
2914ee116499SAntonio Huete Jimenez fatal_fr(r, "parse response");
2915ee116499SAntonio Huete Jimenez if (nuids > 0) {
2916ee116499SAntonio Huete Jimenez usernames = xcalloc(nuids, sizeof(*usernames));
2917ee116499SAntonio Huete Jimenez for (i = 0; i < nuids; i++) {
2918ee116499SAntonio Huete Jimenez if ((r = sshbuf_get_cstring(uidbuf, &name, NULL)) != 0)
2919ee116499SAntonio Huete Jimenez fatal_fr(r, "parse user name");
2920ee116499SAntonio Huete Jimenez /* Handle unresolved names */
2921ee116499SAntonio Huete Jimenez if (*name == '\0') {
2922ee116499SAntonio Huete Jimenez free(name);
2923ee116499SAntonio Huete Jimenez name = NULL;
2924ee116499SAntonio Huete Jimenez }
2925ee116499SAntonio Huete Jimenez usernames[i] = name;
2926ee116499SAntonio Huete Jimenez }
2927ee116499SAntonio Huete Jimenez }
2928ee116499SAntonio Huete Jimenez if (ngids > 0) {
2929ee116499SAntonio Huete Jimenez groupnames = xcalloc(ngids, sizeof(*groupnames));
2930ee116499SAntonio Huete Jimenez for (i = 0; i < ngids; i++) {
2931ee116499SAntonio Huete Jimenez if ((r = sshbuf_get_cstring(gidbuf, &name, NULL)) != 0)
2932ee116499SAntonio Huete Jimenez fatal_fr(r, "parse user name");
2933ee116499SAntonio Huete Jimenez /* Handle unresolved names */
2934ee116499SAntonio Huete Jimenez if (*name == '\0') {
2935ee116499SAntonio Huete Jimenez free(name);
2936ee116499SAntonio Huete Jimenez name = NULL;
2937ee116499SAntonio Huete Jimenez }
2938ee116499SAntonio Huete Jimenez groupnames[i] = name;
2939ee116499SAntonio Huete Jimenez }
2940ee116499SAntonio Huete Jimenez }
2941ee116499SAntonio Huete Jimenez if (sshbuf_len(uidbuf) != 0)
2942ee116499SAntonio Huete Jimenez fatal_f("unexpected extra username data");
2943ee116499SAntonio Huete Jimenez if (sshbuf_len(gidbuf) != 0)
2944ee116499SAntonio Huete Jimenez fatal_f("unexpected extra groupname data");
2945ee116499SAntonio Huete Jimenez sshbuf_free(uidbuf);
2946ee116499SAntonio Huete Jimenez sshbuf_free(gidbuf);
2947ee116499SAntonio Huete Jimenez sshbuf_free(msg);
2948ee116499SAntonio Huete Jimenez /* success */
2949ee116499SAntonio Huete Jimenez *usernamesp = usernames;
2950ee116499SAntonio Huete Jimenez *groupnamesp = groupnames;
2951ee116499SAntonio Huete Jimenez return 0;
2952ee116499SAntonio Huete Jimenez }
2953ee116499SAntonio Huete Jimenez
2954856ea928SPeter Avalos char *
sftp_path_append(const char * p1,const char * p2)2955*ba1276acSMatthew Dillon sftp_path_append(const char *p1, const char *p2)
2956856ea928SPeter Avalos {
2957856ea928SPeter Avalos char *ret;
2958856ea928SPeter Avalos size_t len = strlen(p1) + strlen(p2) + 2;
2959856ea928SPeter Avalos
2960856ea928SPeter Avalos ret = xmalloc(len);
2961856ea928SPeter Avalos strlcpy(ret, p1, len);
2962856ea928SPeter Avalos if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
2963856ea928SPeter Avalos strlcat(ret, "/", len);
2964856ea928SPeter Avalos strlcat(ret, p2, len);
2965856ea928SPeter Avalos
2966856ea928SPeter Avalos return(ret);
2967856ea928SPeter Avalos }
2968856ea928SPeter Avalos
2969*ba1276acSMatthew Dillon /*
2970*ba1276acSMatthew Dillon * Arg p must be dynamically allocated. It will either be returned or
2971*ba1276acSMatthew Dillon * freed and a replacement allocated. Caller must free returned string.
2972*ba1276acSMatthew Dillon */
297350a69bb5SSascha Wildner char *
sftp_make_absolute(char * p,const char * pwd)2974*ba1276acSMatthew Dillon sftp_make_absolute(char *p, const char *pwd)
297550a69bb5SSascha Wildner {
297650a69bb5SSascha Wildner char *abs_str;
297750a69bb5SSascha Wildner
297850a69bb5SSascha Wildner /* Derelativise */
297950a69bb5SSascha Wildner if (p && !path_absolute(p)) {
2980*ba1276acSMatthew Dillon abs_str = sftp_path_append(pwd, p);
298150a69bb5SSascha Wildner free(p);
298250a69bb5SSascha Wildner return(abs_str);
298350a69bb5SSascha Wildner } else
298450a69bb5SSascha Wildner return(p);
298550a69bb5SSascha Wildner }
298650a69bb5SSascha Wildner
298750a69bb5SSascha Wildner int
sftp_remote_is_dir(struct sftp_conn * conn,const char * path)2988*ba1276acSMatthew Dillon sftp_remote_is_dir(struct sftp_conn *conn, const char *path)
298950a69bb5SSascha Wildner {
2990*ba1276acSMatthew Dillon Attrib a;
299150a69bb5SSascha Wildner
299250a69bb5SSascha Wildner /* XXX: report errors? */
2993*ba1276acSMatthew Dillon if (sftp_stat(conn, path, 1, &a) != 0)
299450a69bb5SSascha Wildner return(0);
2995*ba1276acSMatthew Dillon if (!(a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
299650a69bb5SSascha Wildner return(0);
2997*ba1276acSMatthew Dillon return S_ISDIR(a.perm);
299850a69bb5SSascha Wildner }
299950a69bb5SSascha Wildner
300050a69bb5SSascha Wildner
300150a69bb5SSascha Wildner /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
300250a69bb5SSascha Wildner int
sftp_globpath_is_dir(const char * pathname)3003*ba1276acSMatthew Dillon sftp_globpath_is_dir(const char *pathname)
300450a69bb5SSascha Wildner {
300550a69bb5SSascha Wildner size_t l = strlen(pathname);
300650a69bb5SSascha Wildner
300750a69bb5SSascha Wildner return l > 0 && pathname[l - 1] == '/';
300850a69bb5SSascha Wildner }
300950a69bb5SSascha Wildner
3010