1*bc5531deSDag-Erling Smørgrav /* $OpenBSD: sftp-client.c,v 1.117 2015/01/20 23:14:00 deraadt Exp $ */ 21e8db6e2SBrian Feldman /* 3efcad6b7SDag-Erling Smørgrav * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 41e8db6e2SBrian Feldman * 5efcad6b7SDag-Erling Smørgrav * Permission to use, copy, modify, and distribute this software for any 6efcad6b7SDag-Erling Smørgrav * purpose with or without fee is hereby granted, provided that the above 7efcad6b7SDag-Erling Smørgrav * copyright notice and this permission notice appear in all copies. 81e8db6e2SBrian Feldman * 9efcad6b7SDag-Erling Smørgrav * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10efcad6b7SDag-Erling Smørgrav * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11efcad6b7SDag-Erling Smørgrav * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12efcad6b7SDag-Erling Smørgrav * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13efcad6b7SDag-Erling Smørgrav * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14efcad6b7SDag-Erling Smørgrav * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15efcad6b7SDag-Erling Smørgrav * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 161e8db6e2SBrian Feldman */ 171e8db6e2SBrian Feldman 181e8db6e2SBrian Feldman /* XXX: memleaks */ 191e8db6e2SBrian Feldman /* XXX: signed vs unsigned */ 20ae1f160dSDag-Erling Smørgrav /* XXX: remove all logging, only return status codes */ 211e8db6e2SBrian Feldman /* XXX: copy between two remote sites */ 221e8db6e2SBrian Feldman 231e8db6e2SBrian Feldman #include "includes.h" 241e8db6e2SBrian Feldman 25*bc5531deSDag-Erling Smørgrav #include <sys/param.h> /* MIN MAX */ 26761efaa7SDag-Erling Smørgrav #include <sys/types.h> 27d4af9e69SDag-Erling Smørgrav #ifdef HAVE_SYS_STATVFS_H 28d4af9e69SDag-Erling Smørgrav #include <sys/statvfs.h> 29d4af9e69SDag-Erling Smørgrav #endif 304b17dab0SDag-Erling Smørgrav #include "openbsd-compat/sys-queue.h" 31761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_STAT_H 32761efaa7SDag-Erling Smørgrav # include <sys/stat.h> 33761efaa7SDag-Erling Smørgrav #endif 34761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_TIME_H 35761efaa7SDag-Erling Smørgrav # include <sys/time.h> 36761efaa7SDag-Erling Smørgrav #endif 37761efaa7SDag-Erling Smørgrav #include <sys/uio.h> 38ae1f160dSDag-Erling Smørgrav 39b15c8340SDag-Erling Smørgrav #include <dirent.h> 40761efaa7SDag-Erling Smørgrav #include <errno.h> 41761efaa7SDag-Erling Smørgrav #include <fcntl.h> 42761efaa7SDag-Erling Smørgrav #include <signal.h> 43761efaa7SDag-Erling Smørgrav #include <stdarg.h> 44761efaa7SDag-Erling Smørgrav #include <stdio.h> 45f7167e0eSDag-Erling Smørgrav #include <stdlib.h> 46761efaa7SDag-Erling Smørgrav #include <string.h> 47761efaa7SDag-Erling Smørgrav #include <unistd.h> 48761efaa7SDag-Erling Smørgrav 491e8db6e2SBrian Feldman #include "xmalloc.h" 50*bc5531deSDag-Erling Smørgrav #include "ssherr.h" 51*bc5531deSDag-Erling Smørgrav #include "sshbuf.h" 521e8db6e2SBrian Feldman #include "log.h" 531e8db6e2SBrian Feldman #include "atomicio.h" 54d0c8c0bcSDag-Erling Smørgrav #include "progressmeter.h" 55761efaa7SDag-Erling Smørgrav #include "misc.h" 561e8db6e2SBrian Feldman 571e8db6e2SBrian Feldman #include "sftp.h" 581e8db6e2SBrian Feldman #include "sftp-common.h" 591e8db6e2SBrian Feldman #include "sftp-client.h" 601e8db6e2SBrian Feldman 61d74d50a8SDag-Erling Smørgrav extern volatile sig_atomic_t interrupted; 62d0c8c0bcSDag-Erling Smørgrav extern int showprogress; 63d0c8c0bcSDag-Erling Smørgrav 64761efaa7SDag-Erling Smørgrav /* Minimum amount of data to read at a time */ 65ae1f160dSDag-Erling Smørgrav #define MIN_READ_SIZE 512 661e8db6e2SBrian Feldman 67b15c8340SDag-Erling Smørgrav /* Maximum depth to descend in directory trees */ 68b15c8340SDag-Erling Smørgrav #define MAX_DIR_DEPTH 64 69b15c8340SDag-Erling Smørgrav 70ae1f160dSDag-Erling Smørgrav struct sftp_conn { 71ae1f160dSDag-Erling Smørgrav int fd_in; 72ae1f160dSDag-Erling Smørgrav int fd_out; 73ae1f160dSDag-Erling Smørgrav u_int transfer_buflen; 74ae1f160dSDag-Erling Smørgrav u_int num_requests; 75ae1f160dSDag-Erling Smørgrav u_int version; 76ae1f160dSDag-Erling Smørgrav u_int msg_id; 77d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_POSIX_RENAME 0x00000001 78d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_STATVFS 0x00000002 79d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_FSTATVFS 0x00000004 804a421b63SDag-Erling Smørgrav #define SFTP_EXT_HARDLINK 0x00000008 81f7167e0eSDag-Erling Smørgrav #define SFTP_EXT_FSYNC 0x00000010 82d4af9e69SDag-Erling Smørgrav u_int exts; 834a421b63SDag-Erling Smørgrav u_int64_t limit_kbps; 844a421b63SDag-Erling Smørgrav struct bwlimit bwlimit_in, bwlimit_out; 85ae1f160dSDag-Erling Smørgrav }; 861e8db6e2SBrian Feldman 87*bc5531deSDag-Erling Smørgrav static u_char * 88*bc5531deSDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, 894a421b63SDag-Erling Smørgrav const char *errfmt, ...) __attribute__((format(printf, 4, 5))); 904a421b63SDag-Erling Smørgrav 914a421b63SDag-Erling Smørgrav /* ARGSUSED */ 924a421b63SDag-Erling Smørgrav static int 934a421b63SDag-Erling Smørgrav sftpio(void *_bwlimit, size_t amount) 944a421b63SDag-Erling Smørgrav { 954a421b63SDag-Erling Smørgrav struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit; 964a421b63SDag-Erling Smørgrav 974a421b63SDag-Erling Smørgrav bandwidth_limit(bwlimit, amount); 984a421b63SDag-Erling Smørgrav return 0; 994a421b63SDag-Erling Smørgrav } 100b15c8340SDag-Erling Smørgrav 101ae1f160dSDag-Erling Smørgrav static void 102*bc5531deSDag-Erling Smørgrav send_msg(struct sftp_conn *conn, struct sshbuf *m) 1031e8db6e2SBrian Feldman { 104d0c8c0bcSDag-Erling Smørgrav u_char mlen[4]; 105761efaa7SDag-Erling Smørgrav struct iovec iov[2]; 1061e8db6e2SBrian Feldman 107*bc5531deSDag-Erling Smørgrav if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH) 108*bc5531deSDag-Erling Smørgrav fatal("Outbound message too long %zu", sshbuf_len(m)); 1091e8db6e2SBrian Feldman 110d0c8c0bcSDag-Erling Smørgrav /* Send length first */ 111*bc5531deSDag-Erling Smørgrav put_u32(mlen, sshbuf_len(m)); 112761efaa7SDag-Erling Smørgrav iov[0].iov_base = mlen; 113761efaa7SDag-Erling Smørgrav iov[0].iov_len = sizeof(mlen); 114*bc5531deSDag-Erling Smørgrav iov[1].iov_base = (u_char *)sshbuf_ptr(m); 115*bc5531deSDag-Erling Smørgrav iov[1].iov_len = sshbuf_len(m); 1161e8db6e2SBrian Feldman 1174a421b63SDag-Erling Smørgrav if (atomiciov6(writev, conn->fd_out, iov, 2, 1184a421b63SDag-Erling Smørgrav conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) != 119*bc5531deSDag-Erling Smørgrav sshbuf_len(m) + sizeof(mlen)) 120d0c8c0bcSDag-Erling Smørgrav fatal("Couldn't send packet: %s", strerror(errno)); 121d0c8c0bcSDag-Erling Smørgrav 122*bc5531deSDag-Erling Smørgrav sshbuf_reset(m); 1231e8db6e2SBrian Feldman } 1241e8db6e2SBrian Feldman 125ae1f160dSDag-Erling Smørgrav static void 126*bc5531deSDag-Erling Smørgrav get_msg(struct sftp_conn *conn, struct sshbuf *m) 1271e8db6e2SBrian Feldman { 128d0c8c0bcSDag-Erling Smørgrav u_int msg_len; 129*bc5531deSDag-Erling Smørgrav u_char *p; 130*bc5531deSDag-Erling Smørgrav int r; 1311e8db6e2SBrian Feldman 132*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_reserve(m, 4, &p)) != 0) 133*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 134*bc5531deSDag-Erling Smørgrav if (atomicio6(read, conn->fd_in, p, 4, 1354a421b63SDag-Erling Smørgrav conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) { 136043840dfSDag-Erling Smørgrav if (errno == EPIPE) 1371e8db6e2SBrian Feldman fatal("Connection closed"); 138043840dfSDag-Erling Smørgrav else 1391e8db6e2SBrian Feldman fatal("Couldn't read packet: %s", strerror(errno)); 140043840dfSDag-Erling Smørgrav } 1411e8db6e2SBrian Feldman 142*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(m, &msg_len)) != 0) 143*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 144021d409fSDag-Erling Smørgrav if (msg_len > SFTP_MAX_MSG_LENGTH) 145ee21a45fSDag-Erling Smørgrav fatal("Received message too long %u", msg_len); 1461e8db6e2SBrian Feldman 147*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_reserve(m, msg_len, &p)) != 0) 148*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 149*bc5531deSDag-Erling Smørgrav if (atomicio6(read, conn->fd_in, p, msg_len, 1504a421b63SDag-Erling Smørgrav conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) 1514a421b63SDag-Erling Smørgrav != msg_len) { 152043840dfSDag-Erling Smørgrav if (errno == EPIPE) 1531e8db6e2SBrian Feldman fatal("Connection closed"); 154043840dfSDag-Erling Smørgrav else 155d0c8c0bcSDag-Erling Smørgrav fatal("Read packet: %s", strerror(errno)); 1561e8db6e2SBrian Feldman } 157043840dfSDag-Erling Smørgrav } 1581e8db6e2SBrian Feldman 159ae1f160dSDag-Erling Smørgrav static void 160*bc5531deSDag-Erling Smørgrav send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s, 1611e8db6e2SBrian Feldman u_int len) 1621e8db6e2SBrian Feldman { 163*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 164*bc5531deSDag-Erling Smørgrav int r; 1651e8db6e2SBrian Feldman 166*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 167*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 168*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, code)) != 0 || 169*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 170*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, s, len)) != 0) 171*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 172*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 1734a421b63SDag-Erling Smørgrav debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); 174*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1751e8db6e2SBrian Feldman } 1761e8db6e2SBrian Feldman 177ae1f160dSDag-Erling Smørgrav static void 1784a421b63SDag-Erling Smørgrav send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code, 179*bc5531deSDag-Erling Smørgrav const void *s, u_int len, Attrib *a) 1801e8db6e2SBrian Feldman { 181*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 182*bc5531deSDag-Erling Smørgrav int r; 1831e8db6e2SBrian Feldman 184*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 185*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 186*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, code)) != 0 || 187*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 188*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, s, len)) != 0 || 189*bc5531deSDag-Erling Smørgrav (r = encode_attrib(msg, a)) != 0) 190*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 191*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 1924a421b63SDag-Erling Smørgrav debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); 193*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1941e8db6e2SBrian Feldman } 1951e8db6e2SBrian Feldman 196ae1f160dSDag-Erling Smørgrav static u_int 1974a421b63SDag-Erling Smørgrav get_status(struct sftp_conn *conn, u_int expected_id) 1981e8db6e2SBrian Feldman { 199*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 200*bc5531deSDag-Erling Smørgrav u_char type; 201*bc5531deSDag-Erling Smørgrav u_int id, status; 202*bc5531deSDag-Erling Smørgrav int r; 2031e8db6e2SBrian Feldman 204*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 205*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 206*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 207*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 208*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0) 209*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 2101e8db6e2SBrian Feldman 2111e8db6e2SBrian Feldman if (id != expected_id) 212ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 2131e8db6e2SBrian Feldman if (type != SSH2_FXP_STATUS) 214ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u", 2151e8db6e2SBrian Feldman SSH2_FXP_STATUS, type); 2161e8db6e2SBrian Feldman 217*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0) 218*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 219*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 2201e8db6e2SBrian Feldman 221ee21a45fSDag-Erling Smørgrav debug3("SSH2_FXP_STATUS %u", status); 2221e8db6e2SBrian Feldman 2234a421b63SDag-Erling Smørgrav return status; 2241e8db6e2SBrian Feldman } 2251e8db6e2SBrian Feldman 226*bc5531deSDag-Erling Smørgrav static u_char * 227*bc5531deSDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, 2284a421b63SDag-Erling Smørgrav const char *errfmt, ...) 2291e8db6e2SBrian Feldman { 230*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 231*bc5531deSDag-Erling Smørgrav u_int id, status; 232*bc5531deSDag-Erling Smørgrav u_char type; 233*bc5531deSDag-Erling Smørgrav u_char *handle; 234*bc5531deSDag-Erling Smørgrav char errmsg[256]; 235b15c8340SDag-Erling Smørgrav va_list args; 236*bc5531deSDag-Erling Smørgrav int r; 237b15c8340SDag-Erling Smørgrav 238b15c8340SDag-Erling Smørgrav va_start(args, errfmt); 239b15c8340SDag-Erling Smørgrav if (errfmt != NULL) 240b15c8340SDag-Erling Smørgrav vsnprintf(errmsg, sizeof(errmsg), errfmt, args); 241b15c8340SDag-Erling Smørgrav va_end(args); 2421e8db6e2SBrian Feldman 243*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 244*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 245*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 246*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 247*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0) 248*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 2491e8db6e2SBrian Feldman 2501e8db6e2SBrian Feldman if (id != expected_id) 251b15c8340SDag-Erling Smørgrav fatal("%s: ID mismatch (%u != %u)", 252b15c8340SDag-Erling Smørgrav errfmt == NULL ? __func__ : errmsg, id, expected_id); 2531e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 254*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0) 255*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 256b15c8340SDag-Erling Smørgrav if (errfmt != NULL) 257b15c8340SDag-Erling Smørgrav error("%s: %s", errmsg, fx2txt(status)); 258*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 2591e8db6e2SBrian Feldman return(NULL); 2601e8db6e2SBrian Feldman } else if (type != SSH2_FXP_HANDLE) 261b15c8340SDag-Erling Smørgrav fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u", 262b15c8340SDag-Erling Smørgrav errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type); 2631e8db6e2SBrian Feldman 264*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_string(msg, &handle, len)) != 0) 265*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 266*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 2671e8db6e2SBrian Feldman 268*bc5531deSDag-Erling Smørgrav return handle; 2691e8db6e2SBrian Feldman } 2701e8db6e2SBrian Feldman 271ae1f160dSDag-Erling Smørgrav static Attrib * 2724a421b63SDag-Erling Smørgrav get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet) 2731e8db6e2SBrian Feldman { 274*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 275*bc5531deSDag-Erling Smørgrav u_int id; 276*bc5531deSDag-Erling Smørgrav u_char type; 277*bc5531deSDag-Erling Smørgrav int r; 278*bc5531deSDag-Erling Smørgrav static Attrib a; 2791e8db6e2SBrian Feldman 280*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 281*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 282*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 2831e8db6e2SBrian Feldman 284*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 285*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0) 286*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 2871e8db6e2SBrian Feldman 288ee21a45fSDag-Erling Smørgrav debug3("Received stat reply T:%u I:%u", type, id); 2891e8db6e2SBrian Feldman if (id != expected_id) 290ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 2911e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 292*bc5531deSDag-Erling Smørgrav u_int status; 2931e8db6e2SBrian Feldman 294*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0) 295*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 2961e8db6e2SBrian Feldman if (quiet) 2971e8db6e2SBrian Feldman debug("Couldn't stat remote file: %s", fx2txt(status)); 2981e8db6e2SBrian Feldman else 2991e8db6e2SBrian Feldman error("Couldn't stat remote file: %s", fx2txt(status)); 300*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 3011e8db6e2SBrian Feldman return(NULL); 3021e8db6e2SBrian Feldman } else if (type != SSH2_FXP_ATTRS) { 303ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", 3041e8db6e2SBrian Feldman SSH2_FXP_ATTRS, type); 3051e8db6e2SBrian Feldman } 306*bc5531deSDag-Erling Smørgrav if ((r = decode_attrib(msg, &a)) != 0) { 307*bc5531deSDag-Erling Smørgrav error("%s: couldn't decode attrib: %s", __func__, ssh_err(r)); 308*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 309*bc5531deSDag-Erling Smørgrav return NULL; 310*bc5531deSDag-Erling Smørgrav } 311*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 3121e8db6e2SBrian Feldman 313*bc5531deSDag-Erling Smørgrav return &a; 3141e8db6e2SBrian Feldman } 3151e8db6e2SBrian Feldman 316d4af9e69SDag-Erling Smørgrav static int 3174a421b63SDag-Erling Smørgrav get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st, 3184a421b63SDag-Erling Smørgrav u_int expected_id, int quiet) 319d4af9e69SDag-Erling Smørgrav { 320*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 321*bc5531deSDag-Erling Smørgrav u_char type; 322*bc5531deSDag-Erling Smørgrav u_int id; 323*bc5531deSDag-Erling Smørgrav u_int64_t flag; 324*bc5531deSDag-Erling Smørgrav int r; 325d4af9e69SDag-Erling Smørgrav 326*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 327*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 328*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 329d4af9e69SDag-Erling Smørgrav 330*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 331*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0) 332*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 333d4af9e69SDag-Erling Smørgrav 334d4af9e69SDag-Erling Smørgrav debug3("Received statvfs reply T:%u I:%u", type, id); 335d4af9e69SDag-Erling Smørgrav if (id != expected_id) 336d4af9e69SDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 337d4af9e69SDag-Erling Smørgrav if (type == SSH2_FXP_STATUS) { 338*bc5531deSDag-Erling Smørgrav u_int status; 339d4af9e69SDag-Erling Smørgrav 340*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0) 341*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 342d4af9e69SDag-Erling Smørgrav if (quiet) 343d4af9e69SDag-Erling Smørgrav debug("Couldn't statvfs: %s", fx2txt(status)); 344d4af9e69SDag-Erling Smørgrav else 345d4af9e69SDag-Erling Smørgrav error("Couldn't statvfs: %s", fx2txt(status)); 346*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 347d4af9e69SDag-Erling Smørgrav return -1; 348d4af9e69SDag-Erling Smørgrav } else if (type != SSH2_FXP_EXTENDED_REPLY) { 349d4af9e69SDag-Erling Smørgrav fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", 350d4af9e69SDag-Erling Smørgrav SSH2_FXP_EXTENDED_REPLY, type); 351d4af9e69SDag-Erling Smørgrav } 352d4af9e69SDag-Erling Smørgrav 353b83788ffSDag-Erling Smørgrav memset(st, 0, sizeof(*st)); 354*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 || 355*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 || 356*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 || 357*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 || 358*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 || 359*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_files)) != 0 || 360*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 || 361*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 || 362*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 || 363*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &flag)) != 0 || 364*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0) 365*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 366d4af9e69SDag-Erling Smørgrav 367d4af9e69SDag-Erling Smørgrav st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0; 368d4af9e69SDag-Erling Smørgrav st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0; 369d4af9e69SDag-Erling Smørgrav 370*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 371d4af9e69SDag-Erling Smørgrav 372d4af9e69SDag-Erling Smørgrav return 0; 373d4af9e69SDag-Erling Smørgrav } 374d4af9e69SDag-Erling Smørgrav 375ae1f160dSDag-Erling Smørgrav struct sftp_conn * 3764a421b63SDag-Erling Smørgrav do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, 3774a421b63SDag-Erling Smørgrav u_int64_t limit_kbps) 3781e8db6e2SBrian Feldman { 379*bc5531deSDag-Erling Smørgrav u_char type; 380*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 381ae1f160dSDag-Erling Smørgrav struct sftp_conn *ret; 382*bc5531deSDag-Erling Smørgrav int r; 3831e8db6e2SBrian Feldman 384f7167e0eSDag-Erling Smørgrav ret = xcalloc(1, sizeof(*ret)); 385f7167e0eSDag-Erling Smørgrav ret->msg_id = 1; 3864a421b63SDag-Erling Smørgrav ret->fd_in = fd_in; 3874a421b63SDag-Erling Smørgrav ret->fd_out = fd_out; 3884a421b63SDag-Erling Smørgrav ret->transfer_buflen = transfer_buflen; 3894a421b63SDag-Erling Smørgrav ret->num_requests = num_requests; 3904a421b63SDag-Erling Smørgrav ret->exts = 0; 3914a421b63SDag-Erling Smørgrav ret->limit_kbps = 0; 3924a421b63SDag-Erling Smørgrav 393*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 394*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 395*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 || 396*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0) 397*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 398*bc5531deSDag-Erling Smørgrav send_msg(ret, msg); 3991e8db6e2SBrian Feldman 400*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 4011e8db6e2SBrian Feldman 402*bc5531deSDag-Erling Smørgrav get_msg(ret, msg); 4031e8db6e2SBrian Feldman 4041e8db6e2SBrian Feldman /* Expecting a VERSION reply */ 405*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0) 406*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 407*bc5531deSDag-Erling Smørgrav if (type != SSH2_FXP_VERSION) { 408ee21a45fSDag-Erling Smørgrav error("Invalid packet back from SSH2_FXP_INIT (type %u)", 4091e8db6e2SBrian Feldman type); 410*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 411ae1f160dSDag-Erling Smørgrav return(NULL); 4121e8db6e2SBrian Feldman } 413*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &ret->version)) != 0) 414*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 4151e8db6e2SBrian Feldman 4164a421b63SDag-Erling Smørgrav debug2("Remote version: %u", ret->version); 4171e8db6e2SBrian Feldman 4181e8db6e2SBrian Feldman /* Check for extensions */ 419*bc5531deSDag-Erling Smørgrav while (sshbuf_len(msg) > 0) { 420*bc5531deSDag-Erling Smørgrav char *name; 421*bc5531deSDag-Erling Smørgrav u_char *value; 422*bc5531deSDag-Erling Smørgrav size_t vlen; 423d4af9e69SDag-Erling Smørgrav int known = 0; 4241e8db6e2SBrian Feldman 425*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 || 426*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_string(msg, &value, &vlen)) != 0) 427*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 428d4af9e69SDag-Erling Smørgrav if (strcmp(name, "posix-rename@openssh.com") == 0 && 429*bc5531deSDag-Erling Smørgrav strcmp((char *)value, "1") == 0) { 4304a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_POSIX_RENAME; 431d4af9e69SDag-Erling Smørgrav known = 1; 432d4af9e69SDag-Erling Smørgrav } else if (strcmp(name, "statvfs@openssh.com") == 0 && 433*bc5531deSDag-Erling Smørgrav strcmp((char *)value, "2") == 0) { 4344a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_STATVFS; 435d4af9e69SDag-Erling Smørgrav known = 1; 4364a421b63SDag-Erling Smørgrav } else if (strcmp(name, "fstatvfs@openssh.com") == 0 && 437*bc5531deSDag-Erling Smørgrav strcmp((char *)value, "2") == 0) { 4384a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_FSTATVFS; 4394a421b63SDag-Erling Smørgrav known = 1; 4404a421b63SDag-Erling Smørgrav } else if (strcmp(name, "hardlink@openssh.com") == 0 && 441*bc5531deSDag-Erling Smørgrav strcmp((char *)value, "1") == 0) { 4424a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_HARDLINK; 443d4af9e69SDag-Erling Smørgrav known = 1; 444f7167e0eSDag-Erling Smørgrav } else if (strcmp(name, "fsync@openssh.com") == 0 && 445*bc5531deSDag-Erling Smørgrav strcmp((char *)value, "1") == 0) { 446f7167e0eSDag-Erling Smørgrav ret->exts |= SFTP_EXT_FSYNC; 447f7167e0eSDag-Erling Smørgrav known = 1; 448d4af9e69SDag-Erling Smørgrav } 449d4af9e69SDag-Erling Smørgrav if (known) { 450d4af9e69SDag-Erling Smørgrav debug2("Server supports extension \"%s\" revision %s", 451d4af9e69SDag-Erling Smørgrav name, value); 452d4af9e69SDag-Erling Smørgrav } else { 453d4af9e69SDag-Erling Smørgrav debug2("Unrecognised server extension \"%s\"", name); 454d4af9e69SDag-Erling Smørgrav } 455e4a9863fSDag-Erling Smørgrav free(name); 456e4a9863fSDag-Erling Smørgrav free(value); 4571e8db6e2SBrian Feldman } 4581e8db6e2SBrian Feldman 459*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 4601e8db6e2SBrian Feldman 461ae1f160dSDag-Erling Smørgrav /* Some filexfer v.0 servers don't support large packets */ 4624a421b63SDag-Erling Smørgrav if (ret->version == 0) 463545d5ecaSDag-Erling Smørgrav ret->transfer_buflen = MIN(ret->transfer_buflen, 20480); 464ae1f160dSDag-Erling Smørgrav 4654a421b63SDag-Erling Smørgrav ret->limit_kbps = limit_kbps; 4664a421b63SDag-Erling Smørgrav if (ret->limit_kbps > 0) { 4674a421b63SDag-Erling Smørgrav bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps, 4684a421b63SDag-Erling Smørgrav ret->transfer_buflen); 4694a421b63SDag-Erling Smørgrav bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps, 4704a421b63SDag-Erling Smørgrav ret->transfer_buflen); 4714a421b63SDag-Erling Smørgrav } 4724a421b63SDag-Erling Smørgrav 4734a421b63SDag-Erling Smørgrav return ret; 474ae1f160dSDag-Erling Smørgrav } 475ae1f160dSDag-Erling Smørgrav 476ae1f160dSDag-Erling Smørgrav u_int 477ae1f160dSDag-Erling Smørgrav sftp_proto_version(struct sftp_conn *conn) 478ae1f160dSDag-Erling Smørgrav { 4794a421b63SDag-Erling Smørgrav return conn->version; 4801e8db6e2SBrian Feldman } 4811e8db6e2SBrian Feldman 4821e8db6e2SBrian Feldman int 483*bc5531deSDag-Erling Smørgrav do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) 4841e8db6e2SBrian Feldman { 4851e8db6e2SBrian Feldman u_int id, status; 486*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 487*bc5531deSDag-Erling Smørgrav int r; 4881e8db6e2SBrian Feldman 489*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 490*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 4911e8db6e2SBrian Feldman 492ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 493*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 || 494*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 495*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 496*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 497*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 498ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_CLOSE I:%u", id); 4991e8db6e2SBrian Feldman 5004a421b63SDag-Erling Smørgrav status = get_status(conn, id); 5011e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 5021e8db6e2SBrian Feldman error("Couldn't close file: %s", fx2txt(status)); 5031e8db6e2SBrian Feldman 504*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 5051e8db6e2SBrian Feldman 506*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 5071e8db6e2SBrian Feldman } 5081e8db6e2SBrian Feldman 5091e8db6e2SBrian Feldman 510ae1f160dSDag-Erling Smørgrav static int 511*bc5531deSDag-Erling Smørgrav do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag, 5121e8db6e2SBrian Feldman SFTP_DIRENT ***dir) 5131e8db6e2SBrian Feldman { 514*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 515*bc5531deSDag-Erling Smørgrav u_int count, id, i, expected_id, ents = 0; 516*bc5531deSDag-Erling Smørgrav size_t handle_len; 517*bc5531deSDag-Erling Smørgrav u_char type; 5181e8db6e2SBrian Feldman char *handle; 519f7167e0eSDag-Erling Smørgrav int status = SSH2_FX_FAILURE; 520*bc5531deSDag-Erling Smørgrav int r; 521f7167e0eSDag-Erling Smørgrav 522f7167e0eSDag-Erling Smørgrav if (dir) 523f7167e0eSDag-Erling Smørgrav *dir = NULL; 5241e8db6e2SBrian Feldman 525ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 5261e8db6e2SBrian Feldman 527*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 528*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 529*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 || 530*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 531*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, path)) != 0) 532*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 533*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 5341e8db6e2SBrian Feldman 5354a421b63SDag-Erling Smørgrav handle = get_handle(conn, id, &handle_len, 536b15c8340SDag-Erling Smørgrav "remote readdir(\"%s\")", path); 537462c32cbSDag-Erling Smørgrav if (handle == NULL) { 538*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 5394a421b63SDag-Erling Smørgrav return -1; 540462c32cbSDag-Erling Smørgrav } 5411e8db6e2SBrian Feldman 5421e8db6e2SBrian Feldman if (dir) { 5431e8db6e2SBrian Feldman ents = 0; 5440a37d4a3SXin LI *dir = xcalloc(1, sizeof(**dir)); 5451e8db6e2SBrian Feldman (*dir)[0] = NULL; 5461e8db6e2SBrian Feldman } 5471e8db6e2SBrian Feldman 548d74d50a8SDag-Erling Smørgrav for (; !interrupted;) { 549ae1f160dSDag-Erling Smørgrav id = expected_id = conn->msg_id++; 5501e8db6e2SBrian Feldman 551ee21a45fSDag-Erling Smørgrav debug3("Sending SSH2_FXP_READDIR I:%u", id); 5521e8db6e2SBrian Feldman 553*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 554*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 || 555*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 556*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 557*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 558*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 5591e8db6e2SBrian Feldman 560*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 5611e8db6e2SBrian Feldman 562*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 5631e8db6e2SBrian Feldman 564*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 565*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0) 566*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 5671e8db6e2SBrian Feldman 568ee21a45fSDag-Erling Smørgrav debug3("Received reply T:%u I:%u", type, id); 5691e8db6e2SBrian Feldman 5701e8db6e2SBrian Feldman if (id != expected_id) 571ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 5721e8db6e2SBrian Feldman 5731e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 574*bc5531deSDag-Erling Smørgrav u_int rstatus; 575*bc5531deSDag-Erling Smørgrav 576*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &rstatus)) != 0) 577*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", 578*bc5531deSDag-Erling Smørgrav __func__, ssh_err(r)); 579*bc5531deSDag-Erling Smørgrav debug3("Received SSH2_FXP_STATUS %d", rstatus); 580*bc5531deSDag-Erling Smørgrav if (rstatus == SSH2_FX_EOF) 5811e8db6e2SBrian Feldman break; 582*bc5531deSDag-Erling Smørgrav error("Couldn't read directory: %s", fx2txt(rstatus)); 583f7167e0eSDag-Erling Smørgrav goto out; 5841e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 585ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 5861e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 5871e8db6e2SBrian Feldman 588*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &count)) != 0) 589*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 5901e8db6e2SBrian Feldman if (count == 0) 5911e8db6e2SBrian Feldman break; 5921e8db6e2SBrian Feldman debug3("Received %d SSH2_FXP_NAME responses", count); 5931e8db6e2SBrian Feldman for (i = 0; i < count; i++) { 5941e8db6e2SBrian Feldman char *filename, *longname; 595*bc5531deSDag-Erling Smørgrav Attrib a; 5961e8db6e2SBrian Feldman 597*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(msg, &filename, 598*bc5531deSDag-Erling Smørgrav NULL)) != 0 || 599*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_cstring(msg, &longname, 600*bc5531deSDag-Erling Smørgrav NULL)) != 0) 601*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", 602*bc5531deSDag-Erling Smørgrav __func__, ssh_err(r)); 603*bc5531deSDag-Erling Smørgrav if ((r = decode_attrib(msg, &a)) != 0) { 604*bc5531deSDag-Erling Smørgrav error("%s: couldn't decode attrib: %s", 605*bc5531deSDag-Erling Smørgrav __func__, ssh_err(r)); 606*bc5531deSDag-Erling Smørgrav free(filename); 607*bc5531deSDag-Erling Smørgrav free(longname); 608*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 609*bc5531deSDag-Erling Smørgrav return -1; 610*bc5531deSDag-Erling Smørgrav } 6111e8db6e2SBrian Feldman 612f7167e0eSDag-Erling Smørgrav if (print_flag) 6131e8db6e2SBrian Feldman printf("%s\n", longname); 6141e8db6e2SBrian Feldman 615b15c8340SDag-Erling Smørgrav /* 616b15c8340SDag-Erling Smørgrav * Directory entries should never contain '/' 617b15c8340SDag-Erling Smørgrav * These can be used to attack recursive ops 618b15c8340SDag-Erling Smørgrav * (e.g. send '../../../../etc/passwd') 619b15c8340SDag-Erling Smørgrav */ 620b15c8340SDag-Erling Smørgrav if (strchr(filename, '/') != NULL) { 621b15c8340SDag-Erling Smørgrav error("Server sent suspect path \"%s\" " 622b15c8340SDag-Erling Smørgrav "during readdir of \"%s\"", filename, path); 623f7167e0eSDag-Erling Smørgrav } else if (dir) { 624761efaa7SDag-Erling Smørgrav *dir = xrealloc(*dir, ents + 2, sizeof(**dir)); 6250a37d4a3SXin LI (*dir)[ents] = xcalloc(1, sizeof(***dir)); 6261e8db6e2SBrian Feldman (*dir)[ents]->filename = xstrdup(filename); 6271e8db6e2SBrian Feldman (*dir)[ents]->longname = xstrdup(longname); 628*bc5531deSDag-Erling Smørgrav memcpy(&(*dir)[ents]->a, &a, sizeof(a)); 6291e8db6e2SBrian Feldman (*dir)[++ents] = NULL; 6301e8db6e2SBrian Feldman } 631e4a9863fSDag-Erling Smørgrav free(filename); 632e4a9863fSDag-Erling Smørgrav free(longname); 6331e8db6e2SBrian Feldman } 6341e8db6e2SBrian Feldman } 635f7167e0eSDag-Erling Smørgrav status = 0; 6361e8db6e2SBrian Feldman 637f7167e0eSDag-Erling Smørgrav out: 638*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 639ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 640e4a9863fSDag-Erling Smørgrav free(handle); 6411e8db6e2SBrian Feldman 642f7167e0eSDag-Erling Smørgrav if (status != 0 && dir != NULL) { 643f7167e0eSDag-Erling Smørgrav /* Don't return results on error */ 644f7167e0eSDag-Erling Smørgrav free_sftp_dirents(*dir); 645f7167e0eSDag-Erling Smørgrav *dir = NULL; 646f7167e0eSDag-Erling Smørgrav } else if (interrupted && dir != NULL && *dir != NULL) { 647d74d50a8SDag-Erling Smørgrav /* Don't return partial matches on interrupt */ 648d74d50a8SDag-Erling Smørgrav free_sftp_dirents(*dir); 6490a37d4a3SXin LI *dir = xcalloc(1, sizeof(**dir)); 650d74d50a8SDag-Erling Smørgrav **dir = NULL; 651d74d50a8SDag-Erling Smørgrav } 652d74d50a8SDag-Erling Smørgrav 653f7167e0eSDag-Erling Smørgrav return status; 6541e8db6e2SBrian Feldman } 6551e8db6e2SBrian Feldman 6561e8db6e2SBrian Feldman int 657*bc5531deSDag-Erling Smørgrav do_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir) 6581e8db6e2SBrian Feldman { 659ae1f160dSDag-Erling Smørgrav return(do_lsreaddir(conn, path, 0, dir)); 6601e8db6e2SBrian Feldman } 6611e8db6e2SBrian Feldman 6621e8db6e2SBrian Feldman void free_sftp_dirents(SFTP_DIRENT **s) 6631e8db6e2SBrian Feldman { 6641e8db6e2SBrian Feldman int i; 6651e8db6e2SBrian Feldman 666f7167e0eSDag-Erling Smørgrav if (s == NULL) 667f7167e0eSDag-Erling Smørgrav return; 6681e8db6e2SBrian Feldman for (i = 0; s[i]; i++) { 669e4a9863fSDag-Erling Smørgrav free(s[i]->filename); 670e4a9863fSDag-Erling Smørgrav free(s[i]->longname); 671e4a9863fSDag-Erling Smørgrav free(s[i]); 6721e8db6e2SBrian Feldman } 673e4a9863fSDag-Erling Smørgrav free(s); 6741e8db6e2SBrian Feldman } 6751e8db6e2SBrian Feldman 6761e8db6e2SBrian Feldman int 677*bc5531deSDag-Erling Smørgrav do_rm(struct sftp_conn *conn, const char *path) 6781e8db6e2SBrian Feldman { 6791e8db6e2SBrian Feldman u_int status, id; 6801e8db6e2SBrian Feldman 6811e8db6e2SBrian Feldman debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); 6821e8db6e2SBrian Feldman 683ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6844a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path)); 6854a421b63SDag-Erling Smørgrav status = get_status(conn, id); 6861e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 6871e8db6e2SBrian Feldman error("Couldn't delete file: %s", fx2txt(status)); 688*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 6891e8db6e2SBrian Feldman } 6901e8db6e2SBrian Feldman 6911e8db6e2SBrian Feldman int 692*bc5531deSDag-Erling Smørgrav do_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) 6931e8db6e2SBrian Feldman { 6941e8db6e2SBrian Feldman u_int status, id; 6951e8db6e2SBrian Feldman 696ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6974a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path, 6981e8db6e2SBrian Feldman strlen(path), a); 6991e8db6e2SBrian Feldman 7004a421b63SDag-Erling Smørgrav status = get_status(conn, id); 701f7167e0eSDag-Erling Smørgrav if (status != SSH2_FX_OK && print_flag) 7021e8db6e2SBrian Feldman error("Couldn't create directory: %s", fx2txt(status)); 7031e8db6e2SBrian Feldman 704*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 7051e8db6e2SBrian Feldman } 7061e8db6e2SBrian Feldman 7071e8db6e2SBrian Feldman int 708*bc5531deSDag-Erling Smørgrav do_rmdir(struct sftp_conn *conn, const char *path) 7091e8db6e2SBrian Feldman { 7101e8db6e2SBrian Feldman u_int status, id; 7111e8db6e2SBrian Feldman 712ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 7134a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_RMDIR, path, 714ae1f160dSDag-Erling Smørgrav strlen(path)); 7151e8db6e2SBrian Feldman 7164a421b63SDag-Erling Smørgrav status = get_status(conn, id); 7171e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 7181e8db6e2SBrian Feldman error("Couldn't remove directory: %s", fx2txt(status)); 7191e8db6e2SBrian Feldman 720*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 7211e8db6e2SBrian Feldman } 7221e8db6e2SBrian Feldman 7231e8db6e2SBrian Feldman Attrib * 724*bc5531deSDag-Erling Smørgrav do_stat(struct sftp_conn *conn, const char *path, int quiet) 7251e8db6e2SBrian Feldman { 7261e8db6e2SBrian Feldman u_int id; 7271e8db6e2SBrian Feldman 728ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 729ae1f160dSDag-Erling Smørgrav 7304a421b63SDag-Erling Smørgrav send_string_request(conn, id, 731ae1f160dSDag-Erling Smørgrav conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, 732ae1f160dSDag-Erling Smørgrav path, strlen(path)); 733ae1f160dSDag-Erling Smørgrav 7344a421b63SDag-Erling Smørgrav return(get_decode_stat(conn, id, quiet)); 7351e8db6e2SBrian Feldman } 7361e8db6e2SBrian Feldman 7371e8db6e2SBrian Feldman Attrib * 738*bc5531deSDag-Erling Smørgrav do_lstat(struct sftp_conn *conn, const char *path, int quiet) 7391e8db6e2SBrian Feldman { 7401e8db6e2SBrian Feldman u_int id; 7411e8db6e2SBrian Feldman 742ae1f160dSDag-Erling Smørgrav if (conn->version == 0) { 743ae1f160dSDag-Erling Smørgrav if (quiet) 744ae1f160dSDag-Erling Smørgrav debug("Server version does not support lstat operation"); 745ae1f160dSDag-Erling Smørgrav else 746d95e11bfSDag-Erling Smørgrav logit("Server version does not support lstat operation"); 747545d5ecaSDag-Erling Smørgrav return(do_stat(conn, path, quiet)); 748ae1f160dSDag-Erling Smørgrav } 749ae1f160dSDag-Erling Smørgrav 750ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 7514a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_LSTAT, path, 752ae1f160dSDag-Erling Smørgrav strlen(path)); 753ae1f160dSDag-Erling Smørgrav 7544a421b63SDag-Erling Smørgrav return(get_decode_stat(conn, id, quiet)); 7551e8db6e2SBrian Feldman } 7561e8db6e2SBrian Feldman 757d4af9e69SDag-Erling Smørgrav #ifdef notyet 7581e8db6e2SBrian Feldman Attrib * 759*bc5531deSDag-Erling Smørgrav do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 760*bc5531deSDag-Erling Smørgrav int quiet) 7611e8db6e2SBrian Feldman { 7621e8db6e2SBrian Feldman u_int id; 7631e8db6e2SBrian Feldman 764ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 7654a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_FSTAT, handle, 766ae1f160dSDag-Erling Smørgrav handle_len); 767ae1f160dSDag-Erling Smørgrav 7684a421b63SDag-Erling Smørgrav return(get_decode_stat(conn, id, quiet)); 7691e8db6e2SBrian Feldman } 770d4af9e69SDag-Erling Smørgrav #endif 7711e8db6e2SBrian Feldman 7721e8db6e2SBrian Feldman int 773*bc5531deSDag-Erling Smørgrav do_setstat(struct sftp_conn *conn, const char *path, Attrib *a) 7741e8db6e2SBrian Feldman { 7751e8db6e2SBrian Feldman u_int status, id; 7761e8db6e2SBrian Feldman 777ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 7784a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path, 7791e8db6e2SBrian Feldman strlen(path), a); 7801e8db6e2SBrian Feldman 7814a421b63SDag-Erling Smørgrav status = get_status(conn, id); 7821e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 7831e8db6e2SBrian Feldman error("Couldn't setstat on \"%s\": %s", path, 7841e8db6e2SBrian Feldman fx2txt(status)); 7851e8db6e2SBrian Feldman 786*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 7871e8db6e2SBrian Feldman } 7881e8db6e2SBrian Feldman 7891e8db6e2SBrian Feldman int 790*bc5531deSDag-Erling Smørgrav do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 7911e8db6e2SBrian Feldman Attrib *a) 7921e8db6e2SBrian Feldman { 7931e8db6e2SBrian Feldman u_int status, id; 7941e8db6e2SBrian Feldman 795ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 7964a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle, 7971e8db6e2SBrian Feldman handle_len, a); 7981e8db6e2SBrian Feldman 7994a421b63SDag-Erling Smørgrav status = get_status(conn, id); 8001e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 8011e8db6e2SBrian Feldman error("Couldn't fsetstat: %s", fx2txt(status)); 8021e8db6e2SBrian Feldman 803*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 8041e8db6e2SBrian Feldman } 8051e8db6e2SBrian Feldman 8061e8db6e2SBrian Feldman char * 807*bc5531deSDag-Erling Smørgrav do_realpath(struct sftp_conn *conn, const char *path) 8081e8db6e2SBrian Feldman { 809*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 810*bc5531deSDag-Erling Smørgrav u_int expected_id, count, id; 8111e8db6e2SBrian Feldman char *filename, *longname; 812*bc5531deSDag-Erling Smørgrav Attrib a; 813*bc5531deSDag-Erling Smørgrav u_char type; 814*bc5531deSDag-Erling Smørgrav int r; 8151e8db6e2SBrian Feldman 816ae1f160dSDag-Erling Smørgrav expected_id = id = conn->msg_id++; 8174a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_REALPATH, path, 818ae1f160dSDag-Erling Smørgrav strlen(path)); 8191e8db6e2SBrian Feldman 820*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 821*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 8221e8db6e2SBrian Feldman 823*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 824*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 825*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0) 826*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 8271e8db6e2SBrian Feldman 8281e8db6e2SBrian Feldman if (id != expected_id) 829ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 8301e8db6e2SBrian Feldman 8311e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 832*bc5531deSDag-Erling Smørgrav u_int status; 8331e8db6e2SBrian Feldman 834*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0) 835*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 836f7167e0eSDag-Erling Smørgrav error("Couldn't canonicalize: %s", fx2txt(status)); 837*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 838e2f6069cSDag-Erling Smørgrav return NULL; 8391e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 840ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 8411e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 8421e8db6e2SBrian Feldman 843*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &count)) != 0) 844*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 8451e8db6e2SBrian Feldman if (count != 1) 8461e8db6e2SBrian Feldman fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count); 8471e8db6e2SBrian Feldman 848*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || 849*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || 850*bc5531deSDag-Erling Smørgrav (r = decode_attrib(msg, &a)) != 0) 851*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 8521e8db6e2SBrian Feldman 853462c32cbSDag-Erling Smørgrav debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename, 854*bc5531deSDag-Erling Smørgrav (unsigned long)a.size); 8551e8db6e2SBrian Feldman 856e4a9863fSDag-Erling Smørgrav free(longname); 8571e8db6e2SBrian Feldman 858*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 8591e8db6e2SBrian Feldman 8601e8db6e2SBrian Feldman return(filename); 8611e8db6e2SBrian Feldman } 8621e8db6e2SBrian Feldman 8631e8db6e2SBrian Feldman int 864*bc5531deSDag-Erling Smørgrav do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, 865f7167e0eSDag-Erling Smørgrav int force_legacy) 8661e8db6e2SBrian Feldman { 867*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 8681e8db6e2SBrian Feldman u_int status, id; 869*bc5531deSDag-Erling Smørgrav int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy; 8701e8db6e2SBrian Feldman 871*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 872*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 8731e8db6e2SBrian Feldman 8741e8db6e2SBrian Feldman /* Send rename request */ 875ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 876f7167e0eSDag-Erling Smørgrav if (use_ext) { 877*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 878*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 879*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, 880*bc5531deSDag-Erling Smørgrav "posix-rename@openssh.com")) != 0) 881*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 882d4af9e69SDag-Erling Smørgrav } else { 883*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 || 884*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0) 885*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 886d4af9e69SDag-Erling Smørgrav } 887*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 || 888*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, newpath)) != 0) 889*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 890*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 891d4af9e69SDag-Erling Smørgrav debug3("Sent message %s \"%s\" -> \"%s\"", 892*bc5531deSDag-Erling Smørgrav use_ext ? "posix-rename@openssh.com" : 893*bc5531deSDag-Erling Smørgrav "SSH2_FXP_RENAME", oldpath, newpath); 894*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 8951e8db6e2SBrian Feldman 8964a421b63SDag-Erling Smørgrav status = get_status(conn, id); 8971e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 898ae1f160dSDag-Erling Smørgrav error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, 899ae1f160dSDag-Erling Smørgrav newpath, fx2txt(status)); 9001e8db6e2SBrian Feldman 901*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 9021e8db6e2SBrian Feldman } 9031e8db6e2SBrian Feldman 9041e8db6e2SBrian Feldman int 905*bc5531deSDag-Erling Smørgrav do_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) 9064a421b63SDag-Erling Smørgrav { 907*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 9084a421b63SDag-Erling Smørgrav u_int status, id; 909*bc5531deSDag-Erling Smørgrav int r; 9104a421b63SDag-Erling Smørgrav 9114a421b63SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_HARDLINK) == 0) { 9124a421b63SDag-Erling Smørgrav error("Server does not support hardlink@openssh.com extension"); 9134a421b63SDag-Erling Smørgrav return -1; 9144a421b63SDag-Erling Smørgrav } 9154a421b63SDag-Erling Smørgrav 916*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 917*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 918462c32cbSDag-Erling Smørgrav 919462c32cbSDag-Erling Smørgrav /* Send link request */ 920462c32cbSDag-Erling Smørgrav id = conn->msg_id++; 921*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 922*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 923*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 || 924*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, oldpath)) != 0 || 925*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, newpath)) != 0) 926*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 927*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 9284a421b63SDag-Erling Smørgrav debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"", 9294a421b63SDag-Erling Smørgrav oldpath, newpath); 930*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 9314a421b63SDag-Erling Smørgrav 9324a421b63SDag-Erling Smørgrav status = get_status(conn, id); 9334a421b63SDag-Erling Smørgrav if (status != SSH2_FX_OK) 9344a421b63SDag-Erling Smørgrav error("Couldn't link file \"%s\" to \"%s\": %s", oldpath, 9354a421b63SDag-Erling Smørgrav newpath, fx2txt(status)); 9364a421b63SDag-Erling Smørgrav 937*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 9384a421b63SDag-Erling Smørgrav } 9394a421b63SDag-Erling Smørgrav 9404a421b63SDag-Erling Smørgrav int 941*bc5531deSDag-Erling Smørgrav do_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) 9421e8db6e2SBrian Feldman { 943*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 9441e8db6e2SBrian Feldman u_int status, id; 945*bc5531deSDag-Erling Smørgrav int r; 9461e8db6e2SBrian Feldman 947ae1f160dSDag-Erling Smørgrav if (conn->version < 3) { 948ae1f160dSDag-Erling Smørgrav error("This server does not support the symlink operation"); 949ae1f160dSDag-Erling Smørgrav return(SSH2_FX_OP_UNSUPPORTED); 950ae1f160dSDag-Erling Smørgrav } 951ae1f160dSDag-Erling Smørgrav 952*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 953*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 9541e8db6e2SBrian Feldman 955d74d50a8SDag-Erling Smørgrav /* Send symlink request */ 956ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 957*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 || 958*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 959*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, oldpath)) != 0 || 960*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, newpath)) != 0) 961*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 962*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 9631e8db6e2SBrian Feldman debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, 9641e8db6e2SBrian Feldman newpath); 965*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 9661e8db6e2SBrian Feldman 9674a421b63SDag-Erling Smørgrav status = get_status(conn, id); 9681e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 969d0c8c0bcSDag-Erling Smørgrav error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath, 970ae1f160dSDag-Erling Smørgrav newpath, fx2txt(status)); 9711e8db6e2SBrian Feldman 972*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 9731e8db6e2SBrian Feldman } 9741e8db6e2SBrian Feldman 975f7167e0eSDag-Erling Smørgrav int 976*bc5531deSDag-Erling Smørgrav do_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len) 977f7167e0eSDag-Erling Smørgrav { 978*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 979f7167e0eSDag-Erling Smørgrav u_int status, id; 980*bc5531deSDag-Erling Smørgrav int r; 981f7167e0eSDag-Erling Smørgrav 982f7167e0eSDag-Erling Smørgrav /* Silently return if the extension is not supported */ 983f7167e0eSDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_FSYNC) == 0) 984f7167e0eSDag-Erling Smørgrav return -1; 985f7167e0eSDag-Erling Smørgrav 986f7167e0eSDag-Erling Smørgrav /* Send fsync request */ 987*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 988*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 989f7167e0eSDag-Erling Smørgrav id = conn->msg_id++; 990*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 991*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 992*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 || 993*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 994*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 995*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 996f7167e0eSDag-Erling Smørgrav debug3("Sent message fsync@openssh.com I:%u", id); 997*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 998f7167e0eSDag-Erling Smørgrav 999f7167e0eSDag-Erling Smørgrav status = get_status(conn, id); 1000f7167e0eSDag-Erling Smørgrav if (status != SSH2_FX_OK) 1001f7167e0eSDag-Erling Smørgrav error("Couldn't sync file: %s", fx2txt(status)); 1002f7167e0eSDag-Erling Smørgrav 1003f7167e0eSDag-Erling Smørgrav return status; 1004f7167e0eSDag-Erling Smørgrav } 1005f7167e0eSDag-Erling Smørgrav 1006d4af9e69SDag-Erling Smørgrav #ifdef notyet 10071e8db6e2SBrian Feldman char * 1008*bc5531deSDag-Erling Smørgrav do_readlink(struct sftp_conn *conn, const char *path) 10091e8db6e2SBrian Feldman { 1010*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 1011*bc5531deSDag-Erling Smørgrav u_int expected_id, count, id; 10121e8db6e2SBrian Feldman char *filename, *longname; 1013*bc5531deSDag-Erling Smørgrav Attrib a; 1014*bc5531deSDag-Erling Smørgrav u_char type; 1015*bc5531deSDag-Erling Smørgrav int r; 10161e8db6e2SBrian Feldman 1017ae1f160dSDag-Erling Smørgrav expected_id = id = conn->msg_id++; 10184a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path)); 10191e8db6e2SBrian Feldman 1020*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 1021*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 10221e8db6e2SBrian Feldman 1023*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 1024*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1025*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0) 1026*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 10271e8db6e2SBrian Feldman 10281e8db6e2SBrian Feldman if (id != expected_id) 1029ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 10301e8db6e2SBrian Feldman 10311e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 1032*bc5531deSDag-Erling Smørgrav u_int status; 10331e8db6e2SBrian Feldman 1034*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0) 1035*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 10361e8db6e2SBrian Feldman error("Couldn't readlink: %s", fx2txt(status)); 1037*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 10381e8db6e2SBrian Feldman return(NULL); 10391e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 1040ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 10411e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 10421e8db6e2SBrian Feldman 1043*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &count)) != 0) 1044*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 10451e8db6e2SBrian Feldman if (count != 1) 10461e8db6e2SBrian Feldman fatal("Got multiple names (%d) from SSH_FXP_READLINK", count); 10471e8db6e2SBrian Feldman 1048*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || 1049*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || 1050*bc5531deSDag-Erling Smørgrav (r = decode_attrib(msg, &a)) != 0) 1051*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 10521e8db6e2SBrian Feldman 10531e8db6e2SBrian Feldman debug3("SSH_FXP_READLINK %s -> %s", path, filename); 10541e8db6e2SBrian Feldman 1055e4a9863fSDag-Erling Smørgrav free(longname); 10561e8db6e2SBrian Feldman 1057*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 10581e8db6e2SBrian Feldman 1059*bc5531deSDag-Erling Smørgrav return filename; 10601e8db6e2SBrian Feldman } 1061d4af9e69SDag-Erling Smørgrav #endif 1062d4af9e69SDag-Erling Smørgrav 1063d4af9e69SDag-Erling Smørgrav int 1064d4af9e69SDag-Erling Smørgrav do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, 1065d4af9e69SDag-Erling Smørgrav int quiet) 1066d4af9e69SDag-Erling Smørgrav { 1067*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 1068d4af9e69SDag-Erling Smørgrav u_int id; 1069*bc5531deSDag-Erling Smørgrav int r; 1070d4af9e69SDag-Erling Smørgrav 1071d4af9e69SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_STATVFS) == 0) { 1072d4af9e69SDag-Erling Smørgrav error("Server does not support statvfs@openssh.com extension"); 1073d4af9e69SDag-Erling Smørgrav return -1; 1074d4af9e69SDag-Erling Smørgrav } 1075d4af9e69SDag-Erling Smørgrav 1076d4af9e69SDag-Erling Smørgrav id = conn->msg_id++; 1077d4af9e69SDag-Erling Smørgrav 1078*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 1079*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 1080*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 1081*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 1082*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 1083*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 || 1084*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, path)) != 0) 1085*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1086*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 1087*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1088d4af9e69SDag-Erling Smørgrav 10894a421b63SDag-Erling Smørgrav return get_decode_statvfs(conn, st, id, quiet); 1090d4af9e69SDag-Erling Smørgrav } 1091d4af9e69SDag-Erling Smørgrav 1092d4af9e69SDag-Erling Smørgrav #ifdef notyet 1093d4af9e69SDag-Erling Smørgrav int 1094*bc5531deSDag-Erling Smørgrav do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 1095d4af9e69SDag-Erling Smørgrav struct sftp_statvfs *st, int quiet) 1096d4af9e69SDag-Erling Smørgrav { 1097*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 1098d4af9e69SDag-Erling Smørgrav u_int id; 1099d4af9e69SDag-Erling Smørgrav 1100d4af9e69SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) { 1101d4af9e69SDag-Erling Smørgrav error("Server does not support fstatvfs@openssh.com extension"); 1102d4af9e69SDag-Erling Smørgrav return -1; 1103d4af9e69SDag-Erling Smørgrav } 1104d4af9e69SDag-Erling Smørgrav 1105d4af9e69SDag-Erling Smørgrav id = conn->msg_id++; 1106d4af9e69SDag-Erling Smørgrav 1107*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 1108*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 1109*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 1110*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 1111*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 1112*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 || 1113*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 1114*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1115*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 1116*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1117d4af9e69SDag-Erling Smørgrav 11184a421b63SDag-Erling Smørgrav return get_decode_statvfs(conn, st, id, quiet); 1119d4af9e69SDag-Erling Smørgrav } 1120d4af9e69SDag-Erling Smørgrav #endif 11211e8db6e2SBrian Feldman 1122ae1f160dSDag-Erling Smørgrav static void 11234a421b63SDag-Erling Smørgrav send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, 1124*bc5531deSDag-Erling Smørgrav u_int len, const u_char *handle, u_int handle_len) 1125ae1f160dSDag-Erling Smørgrav { 1126*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 1127*bc5531deSDag-Erling Smørgrav int r; 1128ae1f160dSDag-Erling Smørgrav 1129*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 1130*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 1131*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 1132*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 || 1133*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 1134*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, handle_len)) != 0 || 1135*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u64(msg, offset)) != 0 || 1136*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, len)) != 0) 1137*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1138*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 1139*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1140ae1f160dSDag-Erling Smørgrav } 1141ae1f160dSDag-Erling Smørgrav 11421e8db6e2SBrian Feldman int 1143*bc5531deSDag-Erling Smørgrav do_download(struct sftp_conn *conn, const char *remote_path, 1144*bc5531deSDag-Erling Smørgrav const char *local_path, Attrib *a, int preserve_flag, int resume_flag, 1145*bc5531deSDag-Erling Smørgrav int fsync_flag) 11461e8db6e2SBrian Feldman { 1147b15c8340SDag-Erling Smørgrav Attrib junk; 1148*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 1149*bc5531deSDag-Erling Smørgrav u_char *handle; 1150*bc5531deSDag-Erling Smørgrav int local_fd = -1, write_error; 1151*bc5531deSDag-Erling Smørgrav int read_error, write_errno, reordered = 0, r; 1152e4a9863fSDag-Erling Smørgrav u_int64_t offset = 0, size, highwater; 1153*bc5531deSDag-Erling Smørgrav u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK; 1154d0c8c0bcSDag-Erling Smørgrav off_t progress_counter; 1155*bc5531deSDag-Erling Smørgrav size_t handle_len; 1156e4a9863fSDag-Erling Smørgrav struct stat st; 1157ae1f160dSDag-Erling Smørgrav struct request { 1158ae1f160dSDag-Erling Smørgrav u_int id; 1159*bc5531deSDag-Erling Smørgrav size_t len; 1160ae1f160dSDag-Erling Smørgrav u_int64_t offset; 1161ae1f160dSDag-Erling Smørgrav TAILQ_ENTRY(request) tq; 1162ae1f160dSDag-Erling Smørgrav }; 1163ae1f160dSDag-Erling Smørgrav TAILQ_HEAD(reqhead, request) requests; 1164ae1f160dSDag-Erling Smørgrav struct request *req; 1165*bc5531deSDag-Erling Smørgrav u_char type; 11661e8db6e2SBrian Feldman 1167ae1f160dSDag-Erling Smørgrav TAILQ_INIT(&requests); 1168ae1f160dSDag-Erling Smørgrav 1169b15c8340SDag-Erling Smørgrav if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL) 1170b15c8340SDag-Erling Smørgrav return -1; 11711e8db6e2SBrian Feldman 1172d4af9e69SDag-Erling Smørgrav /* Do not preserve set[ug]id here, as we do not preserve ownership */ 11731e8db6e2SBrian Feldman if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 1174d0c8c0bcSDag-Erling Smørgrav mode = a->perm & 0777; 11751e8db6e2SBrian Feldman else 11761e8db6e2SBrian Feldman mode = 0666; 11771e8db6e2SBrian Feldman 11781e8db6e2SBrian Feldman if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 1179d0c8c0bcSDag-Erling Smørgrav (!S_ISREG(a->perm))) { 1180d0c8c0bcSDag-Erling Smørgrav error("Cannot download non-regular file: %s", remote_path); 11811e8db6e2SBrian Feldman return(-1); 11821e8db6e2SBrian Feldman } 11831e8db6e2SBrian Feldman 1184ae1f160dSDag-Erling Smørgrav if (a->flags & SSH2_FILEXFER_ATTR_SIZE) 1185ae1f160dSDag-Erling Smørgrav size = a->size; 1186ae1f160dSDag-Erling Smørgrav else 1187ae1f160dSDag-Erling Smørgrav size = 0; 11881e8db6e2SBrian Feldman 1189ae1f160dSDag-Erling Smørgrav buflen = conn->transfer_buflen; 1190*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 1191*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 1192*bc5531deSDag-Erling Smørgrav 1193*bc5531deSDag-Erling Smørgrav attrib_clear(&junk); /* Send empty attributes */ 11941e8db6e2SBrian Feldman 11951e8db6e2SBrian Feldman /* Send open request */ 1196ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 1197*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || 1198*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 1199*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, remote_path)) != 0 || 1200*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 || 1201*bc5531deSDag-Erling Smørgrav (r = encode_attrib(msg, &junk)) != 0) 1202*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1203*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 1204ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 12051e8db6e2SBrian Feldman 12064a421b63SDag-Erling Smørgrav handle = get_handle(conn, id, &handle_len, 1207b15c8340SDag-Erling Smørgrav "remote open(\"%s\")", remote_path); 12081e8db6e2SBrian Feldman if (handle == NULL) { 1209*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1210ae1f160dSDag-Erling Smørgrav return(-1); 1211ae1f160dSDag-Erling Smørgrav } 1212ae1f160dSDag-Erling Smørgrav 1213f7167e0eSDag-Erling Smørgrav local_fd = open(local_path, 1214f7167e0eSDag-Erling Smørgrav O_WRONLY | O_CREAT | (resume_flag ? 0 : O_TRUNC), mode | S_IWUSR); 1215ae1f160dSDag-Erling Smørgrav if (local_fd == -1) { 1216ae1f160dSDag-Erling Smørgrav error("Couldn't open local file \"%s\" for writing: %s", 1217ae1f160dSDag-Erling Smørgrav local_path, strerror(errno)); 1218e4a9863fSDag-Erling Smørgrav goto fail; 1219e4a9863fSDag-Erling Smørgrav } 1220e4a9863fSDag-Erling Smørgrav offset = highwater = 0; 1221f7167e0eSDag-Erling Smørgrav if (resume_flag) { 1222e4a9863fSDag-Erling Smørgrav if (fstat(local_fd, &st) == -1) { 1223e4a9863fSDag-Erling Smørgrav error("Unable to stat local file \"%s\": %s", 1224e4a9863fSDag-Erling Smørgrav local_path, strerror(errno)); 1225e4a9863fSDag-Erling Smørgrav goto fail; 1226e4a9863fSDag-Erling Smørgrav } 1227f7167e0eSDag-Erling Smørgrav if (st.st_size < 0) { 1228f7167e0eSDag-Erling Smørgrav error("\"%s\" has negative size", local_path); 1229f7167e0eSDag-Erling Smørgrav goto fail; 1230f7167e0eSDag-Erling Smørgrav } 1231f7167e0eSDag-Erling Smørgrav if ((u_int64_t)st.st_size > size) { 1232e4a9863fSDag-Erling Smørgrav error("Unable to resume download of \"%s\": " 1233e4a9863fSDag-Erling Smørgrav "local file is larger than remote", local_path); 1234e4a9863fSDag-Erling Smørgrav fail: 1235d4af9e69SDag-Erling Smørgrav do_close(conn, handle, handle_len); 1236*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1237e4a9863fSDag-Erling Smørgrav free(handle); 1238f7167e0eSDag-Erling Smørgrav if (local_fd != -1) 1239f7167e0eSDag-Erling Smørgrav close(local_fd); 1240e4a9863fSDag-Erling Smørgrav return -1; 1241e4a9863fSDag-Erling Smørgrav } 1242e4a9863fSDag-Erling Smørgrav offset = highwater = st.st_size; 12431e8db6e2SBrian Feldman } 12441e8db6e2SBrian Feldman 12451e8db6e2SBrian Feldman /* Read from remote and write to local */ 1246e4a9863fSDag-Erling Smørgrav write_error = read_error = write_errno = num_req = 0; 1247ae1f160dSDag-Erling Smørgrav max_req = 1; 1248e4a9863fSDag-Erling Smørgrav progress_counter = offset; 1249d0c8c0bcSDag-Erling Smørgrav 125052028650SDag-Erling Smørgrav if (showprogress && size != 0) 125152028650SDag-Erling Smørgrav start_progress_meter(remote_path, size, &progress_counter); 1252d0c8c0bcSDag-Erling Smørgrav 1253ae1f160dSDag-Erling Smørgrav while (num_req > 0 || max_req > 0) { 1254*bc5531deSDag-Erling Smørgrav u_char *data; 1255*bc5531deSDag-Erling Smørgrav size_t len; 12561e8db6e2SBrian Feldman 1257d74d50a8SDag-Erling Smørgrav /* 1258d74d50a8SDag-Erling Smørgrav * Simulate EOF on interrupt: stop sending new requests and 1259d74d50a8SDag-Erling Smørgrav * allow outstanding requests to drain gracefully 1260d74d50a8SDag-Erling Smørgrav */ 1261d74d50a8SDag-Erling Smørgrav if (interrupted) { 1262d74d50a8SDag-Erling Smørgrav if (num_req == 0) /* If we haven't started yet... */ 1263d74d50a8SDag-Erling Smørgrav break; 1264d74d50a8SDag-Erling Smørgrav max_req = 0; 1265d74d50a8SDag-Erling Smørgrav } 1266d74d50a8SDag-Erling Smørgrav 1267ae1f160dSDag-Erling Smørgrav /* Send some more requests */ 1268ae1f160dSDag-Erling Smørgrav while (num_req < max_req) { 1269ae1f160dSDag-Erling Smørgrav debug3("Request range %llu -> %llu (%d/%d)", 1270545d5ecaSDag-Erling Smørgrav (unsigned long long)offset, 1271545d5ecaSDag-Erling Smørgrav (unsigned long long)offset + buflen - 1, 1272545d5ecaSDag-Erling Smørgrav num_req, max_req); 12730a37d4a3SXin LI req = xcalloc(1, sizeof(*req)); 1274ae1f160dSDag-Erling Smørgrav req->id = conn->msg_id++; 1275ae1f160dSDag-Erling Smørgrav req->len = buflen; 1276ae1f160dSDag-Erling Smørgrav req->offset = offset; 1277ae1f160dSDag-Erling Smørgrav offset += buflen; 1278ae1f160dSDag-Erling Smørgrav num_req++; 1279ae1f160dSDag-Erling Smørgrav TAILQ_INSERT_TAIL(&requests, req, tq); 12804a421b63SDag-Erling Smørgrav send_read_request(conn, req->id, req->offset, 1281ae1f160dSDag-Erling Smørgrav req->len, handle, handle_len); 1282ae1f160dSDag-Erling Smørgrav } 12831e8db6e2SBrian Feldman 1284*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 1285*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 1286*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1287*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &id)) != 0) 1288*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1289ee21a45fSDag-Erling Smørgrav debug3("Received reply T:%u I:%u R:%d", type, id, max_req); 12901e8db6e2SBrian Feldman 1291ae1f160dSDag-Erling Smørgrav /* Find the request in our queue */ 1292ae1f160dSDag-Erling Smørgrav for (req = TAILQ_FIRST(&requests); 1293ae1f160dSDag-Erling Smørgrav req != NULL && req->id != id; 1294ae1f160dSDag-Erling Smørgrav req = TAILQ_NEXT(req, tq)) 1295ae1f160dSDag-Erling Smørgrav ; 1296ae1f160dSDag-Erling Smørgrav if (req == NULL) 1297ae1f160dSDag-Erling Smørgrav fatal("Unexpected reply %u", id); 1298ae1f160dSDag-Erling Smørgrav 1299ae1f160dSDag-Erling Smørgrav switch (type) { 1300ae1f160dSDag-Erling Smørgrav case SSH2_FXP_STATUS: 1301*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0) 1302*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", 1303*bc5531deSDag-Erling Smørgrav __func__, ssh_err(r)); 1304ae1f160dSDag-Erling Smørgrav if (status != SSH2_FX_EOF) 1305ae1f160dSDag-Erling Smørgrav read_error = 1; 1306ae1f160dSDag-Erling Smørgrav max_req = 0; 1307ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&requests, req, tq); 1308e4a9863fSDag-Erling Smørgrav free(req); 1309ae1f160dSDag-Erling Smørgrav num_req--; 13101e8db6e2SBrian Feldman break; 1311ae1f160dSDag-Erling Smørgrav case SSH2_FXP_DATA: 1312*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_string(msg, &data, &len)) != 0) 1313*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", 1314*bc5531deSDag-Erling Smørgrav __func__, ssh_err(r)); 1315545d5ecaSDag-Erling Smørgrav debug3("Received data %llu -> %llu", 1316545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset, 1317545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + len - 1); 1318ae1f160dSDag-Erling Smørgrav if (len > req->len) 1319ae1f160dSDag-Erling Smørgrav fatal("Received more data than asked for " 1320*bc5531deSDag-Erling Smørgrav "%zu > %zu", len, req->len); 1321ae1f160dSDag-Erling Smørgrav if ((lseek(local_fd, req->offset, SEEK_SET) == -1 || 1322d95e11bfSDag-Erling Smørgrav atomicio(vwrite, local_fd, data, len) != len) && 1323ae1f160dSDag-Erling Smørgrav !write_error) { 1324ae1f160dSDag-Erling Smørgrav write_errno = errno; 1325ae1f160dSDag-Erling Smørgrav write_error = 1; 1326ae1f160dSDag-Erling Smørgrav max_req = 0; 13271e8db6e2SBrian Feldman } 1328e4a9863fSDag-Erling Smørgrav else if (!reordered && req->offset <= highwater) 1329e4a9863fSDag-Erling Smørgrav highwater = req->offset + len; 1330e4a9863fSDag-Erling Smørgrav else if (!reordered && req->offset > highwater) 1331e4a9863fSDag-Erling Smørgrav reordered = 1; 1332d0c8c0bcSDag-Erling Smørgrav progress_counter += len; 1333e4a9863fSDag-Erling Smørgrav free(data); 1334ae1f160dSDag-Erling Smørgrav 1335ae1f160dSDag-Erling Smørgrav if (len == req->len) { 1336ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&requests, req, tq); 1337e4a9863fSDag-Erling Smørgrav free(req); 1338ae1f160dSDag-Erling Smørgrav num_req--; 1339ae1f160dSDag-Erling Smørgrav } else { 1340ae1f160dSDag-Erling Smørgrav /* Resend the request for the missing data */ 1341ae1f160dSDag-Erling Smørgrav debug3("Short data block, re-requesting " 1342545d5ecaSDag-Erling Smørgrav "%llu -> %llu (%2d)", 1343545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + len, 1344545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + 1345545d5ecaSDag-Erling Smørgrav req->len - 1, num_req); 1346ae1f160dSDag-Erling Smørgrav req->id = conn->msg_id++; 1347ae1f160dSDag-Erling Smørgrav req->len -= len; 1348ae1f160dSDag-Erling Smørgrav req->offset += len; 13494a421b63SDag-Erling Smørgrav send_read_request(conn, req->id, 1350ae1f160dSDag-Erling Smørgrav req->offset, req->len, handle, handle_len); 1351ae1f160dSDag-Erling Smørgrav /* Reduce the request size */ 1352ae1f160dSDag-Erling Smørgrav if (len < buflen) 1353ae1f160dSDag-Erling Smørgrav buflen = MAX(MIN_READ_SIZE, len); 1354ae1f160dSDag-Erling Smørgrav } 1355ae1f160dSDag-Erling Smørgrav if (max_req > 0) { /* max_req = 0 iff EOF received */ 1356ae1f160dSDag-Erling Smørgrav if (size > 0 && offset > size) { 1357ae1f160dSDag-Erling Smørgrav /* Only one request at a time 1358ae1f160dSDag-Erling Smørgrav * after the expected EOF */ 1359ae1f160dSDag-Erling Smørgrav debug3("Finish at %llu (%2d)", 1360545d5ecaSDag-Erling Smørgrav (unsigned long long)offset, 1361545d5ecaSDag-Erling Smørgrav num_req); 1362ae1f160dSDag-Erling Smørgrav max_req = 1; 1363d74d50a8SDag-Erling Smørgrav } else if (max_req <= conn->num_requests) { 1364ae1f160dSDag-Erling Smørgrav ++max_req; 1365ae1f160dSDag-Erling Smørgrav } 1366ae1f160dSDag-Erling Smørgrav } 1367ae1f160dSDag-Erling Smørgrav break; 1368ae1f160dSDag-Erling Smørgrav default: 1369ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", 13701e8db6e2SBrian Feldman SSH2_FXP_DATA, type); 13711e8db6e2SBrian Feldman } 1372ae1f160dSDag-Erling Smørgrav } 13731e8db6e2SBrian Feldman 1374d0c8c0bcSDag-Erling Smørgrav if (showprogress && size) 1375d0c8c0bcSDag-Erling Smørgrav stop_progress_meter(); 1376d0c8c0bcSDag-Erling Smørgrav 1377ae1f160dSDag-Erling Smørgrav /* Sanity check */ 1378ae1f160dSDag-Erling Smørgrav if (TAILQ_FIRST(&requests) != NULL) 1379ae1f160dSDag-Erling Smørgrav fatal("Transfer complete, but requests still in queue"); 1380e4a9863fSDag-Erling Smørgrav /* Truncate at highest contiguous point to avoid holes on interrupt */ 1381e4a9863fSDag-Erling Smørgrav if (read_error || write_error || interrupted) { 1382f7167e0eSDag-Erling Smørgrav if (reordered && resume_flag) { 1383e4a9863fSDag-Erling Smørgrav error("Unable to resume download of \"%s\": " 1384e4a9863fSDag-Erling Smørgrav "server reordered requests", local_path); 1385e4a9863fSDag-Erling Smørgrav } 1386e4a9863fSDag-Erling Smørgrav debug("truncating at %llu", (unsigned long long)highwater); 1387e4a9863fSDag-Erling Smørgrav ftruncate(local_fd, highwater); 1388e4a9863fSDag-Erling Smørgrav } 1389ae1f160dSDag-Erling Smørgrav if (read_error) { 1390ae1f160dSDag-Erling Smørgrav error("Couldn't read from remote file \"%s\" : %s", 1391ae1f160dSDag-Erling Smørgrav remote_path, fx2txt(status)); 1392f7167e0eSDag-Erling Smørgrav status = -1; 1393ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 1394ae1f160dSDag-Erling Smørgrav } else if (write_error) { 13951e8db6e2SBrian Feldman error("Couldn't write to \"%s\": %s", local_path, 1396ae1f160dSDag-Erling Smørgrav strerror(write_errno)); 1397*bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE; 1398ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 1399ae1f160dSDag-Erling Smørgrav } else { 1400*bc5531deSDag-Erling Smørgrav if (do_close(conn, handle, handle_len) != 0 || interrupted) 1401*bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE; 1402*bc5531deSDag-Erling Smørgrav else 1403*bc5531deSDag-Erling Smørgrav status = SSH2_FX_OK; 14041e8db6e2SBrian Feldman /* Override umask and utimes if asked */ 140583d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD 1406f7167e0eSDag-Erling Smørgrav if (preserve_flag && fchmod(local_fd, mode) == -1) 140783d2307dSDag-Erling Smørgrav #else 1408f7167e0eSDag-Erling Smørgrav if (preserve_flag && chmod(local_path, mode) == -1) 140983d2307dSDag-Erling Smørgrav #endif /* HAVE_FCHMOD */ 14101e8db6e2SBrian Feldman error("Couldn't set mode on \"%s\": %s", local_path, 14111e8db6e2SBrian Feldman strerror(errno)); 1412f7167e0eSDag-Erling Smørgrav if (preserve_flag && 1413f7167e0eSDag-Erling Smørgrav (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { 14141e8db6e2SBrian Feldman struct timeval tv[2]; 14151e8db6e2SBrian Feldman tv[0].tv_sec = a->atime; 14161e8db6e2SBrian Feldman tv[1].tv_sec = a->mtime; 14171e8db6e2SBrian Feldman tv[0].tv_usec = tv[1].tv_usec = 0; 14181e8db6e2SBrian Feldman if (utimes(local_path, tv) == -1) 1419ae1f160dSDag-Erling Smørgrav error("Can't set times on \"%s\": %s", 1420ae1f160dSDag-Erling Smørgrav local_path, strerror(errno)); 14211e8db6e2SBrian Feldman } 1422f7167e0eSDag-Erling Smørgrav if (fsync_flag) { 1423f7167e0eSDag-Erling Smørgrav debug("syncing \"%s\"", local_path); 1424f7167e0eSDag-Erling Smørgrav if (fsync(local_fd) == -1) 1425f7167e0eSDag-Erling Smørgrav error("Couldn't sync file \"%s\": %s", 1426f7167e0eSDag-Erling Smørgrav local_path, strerror(errno)); 1427f7167e0eSDag-Erling Smørgrav } 1428ae1f160dSDag-Erling Smørgrav } 14291e8db6e2SBrian Feldman close(local_fd); 1430*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1431e4a9863fSDag-Erling Smørgrav free(handle); 1432ae1f160dSDag-Erling Smørgrav 1433ae1f160dSDag-Erling Smørgrav return(status); 14341e8db6e2SBrian Feldman } 14351e8db6e2SBrian Feldman 1436b15c8340SDag-Erling Smørgrav static int 1437*bc5531deSDag-Erling Smørgrav download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, 1438*bc5531deSDag-Erling Smørgrav int depth, Attrib *dirattrib, int preserve_flag, int print_flag, 1439*bc5531deSDag-Erling Smørgrav int resume_flag, int fsync_flag) 1440b15c8340SDag-Erling Smørgrav { 1441b15c8340SDag-Erling Smørgrav int i, ret = 0; 1442b15c8340SDag-Erling Smørgrav SFTP_DIRENT **dir_entries; 1443b15c8340SDag-Erling Smørgrav char *filename, *new_src, *new_dst; 1444b15c8340SDag-Erling Smørgrav mode_t mode = 0777; 1445b15c8340SDag-Erling Smørgrav 1446b15c8340SDag-Erling Smørgrav if (depth >= MAX_DIR_DEPTH) { 1447b15c8340SDag-Erling Smørgrav error("Maximum directory depth exceeded: %d levels", depth); 1448b15c8340SDag-Erling Smørgrav return -1; 1449b15c8340SDag-Erling Smørgrav } 1450b15c8340SDag-Erling Smørgrav 1451b15c8340SDag-Erling Smørgrav if (dirattrib == NULL && 1452b15c8340SDag-Erling Smørgrav (dirattrib = do_stat(conn, src, 1)) == NULL) { 1453b15c8340SDag-Erling Smørgrav error("Unable to stat remote directory \"%s\"", src); 1454b15c8340SDag-Erling Smørgrav return -1; 1455b15c8340SDag-Erling Smørgrav } 1456b15c8340SDag-Erling Smørgrav if (!S_ISDIR(dirattrib->perm)) { 1457b15c8340SDag-Erling Smørgrav error("\"%s\" is not a directory", src); 1458b15c8340SDag-Erling Smørgrav return -1; 1459b15c8340SDag-Erling Smørgrav } 1460f7167e0eSDag-Erling Smørgrav if (print_flag) 1461b15c8340SDag-Erling Smørgrav printf("Retrieving %s\n", src); 1462b15c8340SDag-Erling Smørgrav 1463b15c8340SDag-Erling Smørgrav if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 1464b15c8340SDag-Erling Smørgrav mode = dirattrib->perm & 01777; 1465b15c8340SDag-Erling Smørgrav else { 1466b15c8340SDag-Erling Smørgrav debug("Server did not send permissions for " 1467b15c8340SDag-Erling Smørgrav "directory \"%s\"", dst); 1468b15c8340SDag-Erling Smørgrav } 1469b15c8340SDag-Erling Smørgrav 1470b15c8340SDag-Erling Smørgrav if (mkdir(dst, mode) == -1 && errno != EEXIST) { 1471b15c8340SDag-Erling Smørgrav error("mkdir %s: %s", dst, strerror(errno)); 1472b15c8340SDag-Erling Smørgrav return -1; 1473b15c8340SDag-Erling Smørgrav } 1474b15c8340SDag-Erling Smørgrav 1475b15c8340SDag-Erling Smørgrav if (do_readdir(conn, src, &dir_entries) == -1) { 1476b15c8340SDag-Erling Smørgrav error("%s: Failed to get directory contents", src); 1477b15c8340SDag-Erling Smørgrav return -1; 1478b15c8340SDag-Erling Smørgrav } 1479b15c8340SDag-Erling Smørgrav 1480b15c8340SDag-Erling Smørgrav for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { 1481b15c8340SDag-Erling Smørgrav filename = dir_entries[i]->filename; 1482b15c8340SDag-Erling Smørgrav 1483b15c8340SDag-Erling Smørgrav new_dst = path_append(dst, filename); 1484b15c8340SDag-Erling Smørgrav new_src = path_append(src, filename); 1485b15c8340SDag-Erling Smørgrav 1486b15c8340SDag-Erling Smørgrav if (S_ISDIR(dir_entries[i]->a.perm)) { 1487b15c8340SDag-Erling Smørgrav if (strcmp(filename, ".") == 0 || 1488b15c8340SDag-Erling Smørgrav strcmp(filename, "..") == 0) 1489b15c8340SDag-Erling Smørgrav continue; 1490b15c8340SDag-Erling Smørgrav if (download_dir_internal(conn, new_src, new_dst, 1491f7167e0eSDag-Erling Smørgrav depth + 1, &(dir_entries[i]->a), preserve_flag, 1492f7167e0eSDag-Erling Smørgrav print_flag, resume_flag, fsync_flag) == -1) 1493b15c8340SDag-Erling Smørgrav ret = -1; 1494b15c8340SDag-Erling Smørgrav } else if (S_ISREG(dir_entries[i]->a.perm) ) { 1495b15c8340SDag-Erling Smørgrav if (do_download(conn, new_src, new_dst, 1496f7167e0eSDag-Erling Smørgrav &(dir_entries[i]->a), preserve_flag, 1497f7167e0eSDag-Erling Smørgrav resume_flag, fsync_flag) == -1) { 1498b15c8340SDag-Erling Smørgrav error("Download of file %s to %s failed", 1499b15c8340SDag-Erling Smørgrav new_src, new_dst); 1500b15c8340SDag-Erling Smørgrav ret = -1; 1501b15c8340SDag-Erling Smørgrav } 1502b15c8340SDag-Erling Smørgrav } else 1503b15c8340SDag-Erling Smørgrav logit("%s: not a regular file\n", new_src); 1504b15c8340SDag-Erling Smørgrav 1505e4a9863fSDag-Erling Smørgrav free(new_dst); 1506e4a9863fSDag-Erling Smørgrav free(new_src); 1507b15c8340SDag-Erling Smørgrav } 1508b15c8340SDag-Erling Smørgrav 1509f7167e0eSDag-Erling Smørgrav if (preserve_flag) { 1510b15c8340SDag-Erling Smørgrav if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 1511b15c8340SDag-Erling Smørgrav struct timeval tv[2]; 1512b15c8340SDag-Erling Smørgrav tv[0].tv_sec = dirattrib->atime; 1513b15c8340SDag-Erling Smørgrav tv[1].tv_sec = dirattrib->mtime; 1514b15c8340SDag-Erling Smørgrav tv[0].tv_usec = tv[1].tv_usec = 0; 1515b15c8340SDag-Erling Smørgrav if (utimes(dst, tv) == -1) 1516b15c8340SDag-Erling Smørgrav error("Can't set times on \"%s\": %s", 1517b15c8340SDag-Erling Smørgrav dst, strerror(errno)); 1518b15c8340SDag-Erling Smørgrav } else 1519b15c8340SDag-Erling Smørgrav debug("Server did not send times for directory " 1520b15c8340SDag-Erling Smørgrav "\"%s\"", dst); 1521b15c8340SDag-Erling Smørgrav } 1522b15c8340SDag-Erling Smørgrav 1523b15c8340SDag-Erling Smørgrav free_sftp_dirents(dir_entries); 1524b15c8340SDag-Erling Smørgrav 1525b15c8340SDag-Erling Smørgrav return ret; 1526b15c8340SDag-Erling Smørgrav } 1527b15c8340SDag-Erling Smørgrav 1528b15c8340SDag-Erling Smørgrav int 1529*bc5531deSDag-Erling Smørgrav download_dir(struct sftp_conn *conn, const char *src, const char *dst, 1530*bc5531deSDag-Erling Smørgrav Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag, 1531*bc5531deSDag-Erling Smørgrav int fsync_flag) 1532b15c8340SDag-Erling Smørgrav { 1533b15c8340SDag-Erling Smørgrav char *src_canon; 1534b15c8340SDag-Erling Smørgrav int ret; 1535b15c8340SDag-Erling Smørgrav 1536b15c8340SDag-Erling Smørgrav if ((src_canon = do_realpath(conn, src)) == NULL) { 1537f7167e0eSDag-Erling Smørgrav error("Unable to canonicalize path \"%s\"", src); 1538b15c8340SDag-Erling Smørgrav return -1; 1539b15c8340SDag-Erling Smørgrav } 1540b15c8340SDag-Erling Smørgrav 1541f7167e0eSDag-Erling Smørgrav ret = download_dir_internal(conn, src_canon, dst, 0, 1542f7167e0eSDag-Erling Smørgrav dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag); 1543e4a9863fSDag-Erling Smørgrav free(src_canon); 1544b15c8340SDag-Erling Smørgrav return ret; 1545b15c8340SDag-Erling Smørgrav } 1546b15c8340SDag-Erling Smørgrav 15471e8db6e2SBrian Feldman int 1548*bc5531deSDag-Erling Smørgrav do_upload(struct sftp_conn *conn, const char *local_path, 1549*bc5531deSDag-Erling Smørgrav const char *remote_path, int preserve_flag, int resume, int fsync_flag) 15501e8db6e2SBrian Feldman { 1551*bc5531deSDag-Erling Smørgrav int r, local_fd; 1552*bc5531deSDag-Erling Smørgrav u_int status = SSH2_FX_OK; 1553*bc5531deSDag-Erling Smørgrav u_int id; 1554*bc5531deSDag-Erling Smørgrav u_char type; 1555e4a9863fSDag-Erling Smørgrav off_t offset, progress_counter; 1556*bc5531deSDag-Erling Smørgrav u_char *handle, *data; 1557*bc5531deSDag-Erling Smørgrav struct sshbuf *msg; 15581e8db6e2SBrian Feldman struct stat sb; 1559a0ee8cc6SDag-Erling Smørgrav Attrib a, *c = NULL; 1560ae1f160dSDag-Erling Smørgrav u_int32_t startid; 1561ae1f160dSDag-Erling Smørgrav u_int32_t ackid; 1562ae1f160dSDag-Erling Smørgrav struct outstanding_ack { 1563ae1f160dSDag-Erling Smørgrav u_int id; 1564ae1f160dSDag-Erling Smørgrav u_int len; 1565d4af9e69SDag-Erling Smørgrav off_t offset; 1566ae1f160dSDag-Erling Smørgrav TAILQ_ENTRY(outstanding_ack) tq; 1567ae1f160dSDag-Erling Smørgrav }; 1568ae1f160dSDag-Erling Smørgrav TAILQ_HEAD(ackhead, outstanding_ack) acks; 1569d74d50a8SDag-Erling Smørgrav struct outstanding_ack *ack = NULL; 1570*bc5531deSDag-Erling Smørgrav size_t handle_len; 1571ae1f160dSDag-Erling Smørgrav 1572ae1f160dSDag-Erling Smørgrav TAILQ_INIT(&acks); 15731e8db6e2SBrian Feldman 15741e8db6e2SBrian Feldman if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) { 15751e8db6e2SBrian Feldman error("Couldn't open local file \"%s\" for reading: %s", 15761e8db6e2SBrian Feldman local_path, strerror(errno)); 15771e8db6e2SBrian Feldman return(-1); 15781e8db6e2SBrian Feldman } 15791e8db6e2SBrian Feldman if (fstat(local_fd, &sb) == -1) { 15801e8db6e2SBrian Feldman error("Couldn't fstat local file \"%s\": %s", 15811e8db6e2SBrian Feldman local_path, strerror(errno)); 15821e8db6e2SBrian Feldman close(local_fd); 15831e8db6e2SBrian Feldman return(-1); 15841e8db6e2SBrian Feldman } 1585d0c8c0bcSDag-Erling Smørgrav if (!S_ISREG(sb.st_mode)) { 1586d0c8c0bcSDag-Erling Smørgrav error("%s is not a regular file", local_path); 1587d0c8c0bcSDag-Erling Smørgrav close(local_fd); 1588d0c8c0bcSDag-Erling Smørgrav return(-1); 1589d0c8c0bcSDag-Erling Smørgrav } 15901e8db6e2SBrian Feldman stat_to_attrib(&sb, &a); 15911e8db6e2SBrian Feldman 15921e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 15931e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 15941e8db6e2SBrian Feldman a.perm &= 0777; 1595f7167e0eSDag-Erling Smørgrav if (!preserve_flag) 15961e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 15971e8db6e2SBrian Feldman 1598a0ee8cc6SDag-Erling Smørgrav if (resume) { 1599a0ee8cc6SDag-Erling Smørgrav /* Get remote file size if it exists */ 1600a0ee8cc6SDag-Erling Smørgrav if ((c = do_stat(conn, remote_path, 0)) == NULL) { 1601a0ee8cc6SDag-Erling Smørgrav close(local_fd); 1602a0ee8cc6SDag-Erling Smørgrav return -1; 1603a0ee8cc6SDag-Erling Smørgrav } 1604a0ee8cc6SDag-Erling Smørgrav 1605a0ee8cc6SDag-Erling Smørgrav if ((off_t)c->size >= sb.st_size) { 1606a0ee8cc6SDag-Erling Smørgrav error("destination file bigger or same size as " 1607a0ee8cc6SDag-Erling Smørgrav "source file"); 1608a0ee8cc6SDag-Erling Smørgrav close(local_fd); 1609a0ee8cc6SDag-Erling Smørgrav return -1; 1610a0ee8cc6SDag-Erling Smørgrav } 1611a0ee8cc6SDag-Erling Smørgrav 1612a0ee8cc6SDag-Erling Smørgrav if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) { 1613a0ee8cc6SDag-Erling Smørgrav close(local_fd); 1614a0ee8cc6SDag-Erling Smørgrav return -1; 1615a0ee8cc6SDag-Erling Smørgrav } 1616a0ee8cc6SDag-Erling Smørgrav } 1617a0ee8cc6SDag-Erling Smørgrav 1618*bc5531deSDag-Erling Smørgrav if ((msg = sshbuf_new()) == NULL) 1619*bc5531deSDag-Erling Smørgrav fatal("%s: sshbuf_new failed", __func__); 16201e8db6e2SBrian Feldman 16211e8db6e2SBrian Feldman /* Send open request */ 1622ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 1623*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || 1624*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, id)) != 0 || 1625*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_cstring(msg, remote_path)) != 0 || 1626*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| 1627*bc5531deSDag-Erling Smørgrav (resume ? SSH2_FXF_APPEND : SSH2_FXF_TRUNC))) != 0 || 1628*bc5531deSDag-Erling Smørgrav (r = encode_attrib(msg, &a)) != 0) 1629*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1630*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 1631ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 16321e8db6e2SBrian Feldman 1633*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 16341e8db6e2SBrian Feldman 16354a421b63SDag-Erling Smørgrav handle = get_handle(conn, id, &handle_len, 1636b15c8340SDag-Erling Smørgrav "remote open(\"%s\")", remote_path); 16371e8db6e2SBrian Feldman if (handle == NULL) { 16381e8db6e2SBrian Feldman close(local_fd); 1639*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1640d4af9e69SDag-Erling Smørgrav return -1; 16411e8db6e2SBrian Feldman } 16421e8db6e2SBrian Feldman 1643ae1f160dSDag-Erling Smørgrav startid = ackid = id + 1; 1644ae1f160dSDag-Erling Smørgrav data = xmalloc(conn->transfer_buflen); 1645ae1f160dSDag-Erling Smørgrav 16461e8db6e2SBrian Feldman /* Read from local and write to remote */ 1647a0ee8cc6SDag-Erling Smørgrav offset = progress_counter = (resume ? c->size : 0); 1648d0c8c0bcSDag-Erling Smørgrav if (showprogress) 1649e4a9863fSDag-Erling Smørgrav start_progress_meter(local_path, sb.st_size, 1650e4a9863fSDag-Erling Smørgrav &progress_counter); 1651d0c8c0bcSDag-Erling Smørgrav 16521e8db6e2SBrian Feldman for (;;) { 16531e8db6e2SBrian Feldman int len; 16541e8db6e2SBrian Feldman 16551e8db6e2SBrian Feldman /* 1656d74d50a8SDag-Erling Smørgrav * Can't use atomicio here because it returns 0 on EOF, 1657d74d50a8SDag-Erling Smørgrav * thus losing the last block of the file. 1658d74d50a8SDag-Erling Smørgrav * Simulate an EOF on interrupt, allowing ACKs from the 1659d74d50a8SDag-Erling Smørgrav * server to drain. 16601e8db6e2SBrian Feldman */ 1661d4af9e69SDag-Erling Smørgrav if (interrupted || status != SSH2_FX_OK) 1662d74d50a8SDag-Erling Smørgrav len = 0; 1663d74d50a8SDag-Erling Smørgrav else do 1664ae1f160dSDag-Erling Smørgrav len = read(local_fd, data, conn->transfer_buflen); 1665d4af9e69SDag-Erling Smørgrav while ((len == -1) && 1666d4af9e69SDag-Erling Smørgrav (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); 16671e8db6e2SBrian Feldman 16681e8db6e2SBrian Feldman if (len == -1) 16691e8db6e2SBrian Feldman fatal("Couldn't read from \"%s\": %s", local_path, 16701e8db6e2SBrian Feldman strerror(errno)); 1671ae1f160dSDag-Erling Smørgrav 1672ae1f160dSDag-Erling Smørgrav if (len != 0) { 16730a37d4a3SXin LI ack = xcalloc(1, sizeof(*ack)); 1674ae1f160dSDag-Erling Smørgrav ack->id = ++id; 1675ae1f160dSDag-Erling Smørgrav ack->offset = offset; 1676ae1f160dSDag-Erling Smørgrav ack->len = len; 1677ae1f160dSDag-Erling Smørgrav TAILQ_INSERT_TAIL(&acks, ack, tq); 16781e8db6e2SBrian Feldman 1679*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 1680*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 || 1681*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u32(msg, ack->id)) != 0 || 1682*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, handle, 1683*bc5531deSDag-Erling Smørgrav handle_len)) != 0 || 1684*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_u64(msg, offset)) != 0 || 1685*bc5531deSDag-Erling Smørgrav (r = sshbuf_put_string(msg, data, len)) != 0) 1686*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", 1687*bc5531deSDag-Erling Smørgrav __func__, ssh_err(r)); 1688*bc5531deSDag-Erling Smørgrav send_msg(conn, msg); 1689ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u", 1690545d5ecaSDag-Erling Smørgrav id, (unsigned long long)offset, len); 1691ae1f160dSDag-Erling Smørgrav } else if (TAILQ_FIRST(&acks) == NULL) 1692ae1f160dSDag-Erling Smørgrav break; 16931e8db6e2SBrian Feldman 1694ae1f160dSDag-Erling Smørgrav if (ack == NULL) 1695ae1f160dSDag-Erling Smørgrav fatal("Unexpected ACK %u", id); 1696ae1f160dSDag-Erling Smørgrav 1697ae1f160dSDag-Erling Smørgrav if (id == startid || len == 0 || 1698ae1f160dSDag-Erling Smørgrav id - ackid >= conn->num_requests) { 1699*bc5531deSDag-Erling Smørgrav u_int rid; 1700545d5ecaSDag-Erling Smørgrav 1701*bc5531deSDag-Erling Smørgrav sshbuf_reset(msg); 1702*bc5531deSDag-Erling Smørgrav get_msg(conn, msg); 1703*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1704*bc5531deSDag-Erling Smørgrav (r = sshbuf_get_u32(msg, &rid)) != 0) 1705*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", 1706*bc5531deSDag-Erling Smørgrav __func__, ssh_err(r)); 1707ae1f160dSDag-Erling Smørgrav 1708ae1f160dSDag-Erling Smørgrav if (type != SSH2_FXP_STATUS) 1709ae1f160dSDag-Erling Smørgrav fatal("Expected SSH2_FXP_STATUS(%d) packet, " 1710ae1f160dSDag-Erling Smørgrav "got %d", SSH2_FXP_STATUS, type); 1711ae1f160dSDag-Erling Smørgrav 1712*bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_u32(msg, &status)) != 0) 1713*bc5531deSDag-Erling Smørgrav fatal("%s: buffer error: %s", 1714*bc5531deSDag-Erling Smørgrav __func__, ssh_err(r)); 1715*bc5531deSDag-Erling Smørgrav debug3("SSH2_FXP_STATUS %u", status); 1716ae1f160dSDag-Erling Smørgrav 1717ae1f160dSDag-Erling Smørgrav /* Find the request in our queue */ 1718ae1f160dSDag-Erling Smørgrav for (ack = TAILQ_FIRST(&acks); 1719*bc5531deSDag-Erling Smørgrav ack != NULL && ack->id != rid; 1720ae1f160dSDag-Erling Smørgrav ack = TAILQ_NEXT(ack, tq)) 1721ae1f160dSDag-Erling Smørgrav ; 1722ae1f160dSDag-Erling Smørgrav if (ack == NULL) 1723*bc5531deSDag-Erling Smørgrav fatal("Can't find request for ID %u", rid); 1724ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&acks, ack, tq); 1725d4af9e69SDag-Erling Smørgrav debug3("In write loop, ack for %u %u bytes at %lld", 1726d4af9e69SDag-Erling Smørgrav ack->id, ack->len, (long long)ack->offset); 1727ae1f160dSDag-Erling Smørgrav ++ackid; 1728e4a9863fSDag-Erling Smørgrav progress_counter += ack->len; 1729e4a9863fSDag-Erling Smørgrav free(ack); 1730ae1f160dSDag-Erling Smørgrav } 17311e8db6e2SBrian Feldman offset += len; 1732d4af9e69SDag-Erling Smørgrav if (offset < 0) 1733d4af9e69SDag-Erling Smørgrav fatal("%s: offset < 0", __func__); 17341e8db6e2SBrian Feldman } 1735*bc5531deSDag-Erling Smørgrav sshbuf_free(msg); 1736d4af9e69SDag-Erling Smørgrav 1737d0c8c0bcSDag-Erling Smørgrav if (showprogress) 1738d0c8c0bcSDag-Erling Smørgrav stop_progress_meter(); 1739e4a9863fSDag-Erling Smørgrav free(data); 17401e8db6e2SBrian Feldman 1741d4af9e69SDag-Erling Smørgrav if (status != SSH2_FX_OK) { 1742d4af9e69SDag-Erling Smørgrav error("Couldn't write to remote file \"%s\": %s", 1743d4af9e69SDag-Erling Smørgrav remote_path, fx2txt(status)); 1744*bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE; 1745d4af9e69SDag-Erling Smørgrav } 1746d4af9e69SDag-Erling Smørgrav 17471e8db6e2SBrian Feldman if (close(local_fd) == -1) { 17481e8db6e2SBrian Feldman error("Couldn't close local file \"%s\": %s", local_path, 17491e8db6e2SBrian Feldman strerror(errno)); 1750*bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE; 17511e8db6e2SBrian Feldman } 17521e8db6e2SBrian Feldman 17531e8db6e2SBrian Feldman /* Override umask and utimes if asked */ 1754f7167e0eSDag-Erling Smørgrav if (preserve_flag) 1755ae1f160dSDag-Erling Smørgrav do_fsetstat(conn, handle, handle_len, &a); 17561e8db6e2SBrian Feldman 1757f7167e0eSDag-Erling Smørgrav if (fsync_flag) 1758f7167e0eSDag-Erling Smørgrav (void)do_fsync(conn, handle, handle_len); 1759f7167e0eSDag-Erling Smørgrav 1760d4af9e69SDag-Erling Smørgrav if (do_close(conn, handle, handle_len) != SSH2_FX_OK) 1761*bc5531deSDag-Erling Smørgrav status = SSH2_FX_FAILURE; 1762*bc5531deSDag-Erling Smørgrav 1763e4a9863fSDag-Erling Smørgrav free(handle); 1764d4af9e69SDag-Erling Smørgrav 1765*bc5531deSDag-Erling Smørgrav return status == SSH2_FX_OK ? 0 : -1; 17661e8db6e2SBrian Feldman } 1767b15c8340SDag-Erling Smørgrav 1768b15c8340SDag-Erling Smørgrav static int 1769*bc5531deSDag-Erling Smørgrav upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, 1770*bc5531deSDag-Erling Smørgrav int depth, int preserve_flag, int print_flag, int resume, int fsync_flag) 1771b15c8340SDag-Erling Smørgrav { 1772*bc5531deSDag-Erling Smørgrav int ret = 0; 1773*bc5531deSDag-Erling Smørgrav u_int status; 1774b15c8340SDag-Erling Smørgrav DIR *dirp; 1775b15c8340SDag-Erling Smørgrav struct dirent *dp; 1776b15c8340SDag-Erling Smørgrav char *filename, *new_src, *new_dst; 1777b15c8340SDag-Erling Smørgrav struct stat sb; 1778b15c8340SDag-Erling Smørgrav Attrib a; 1779b15c8340SDag-Erling Smørgrav 1780b15c8340SDag-Erling Smørgrav if (depth >= MAX_DIR_DEPTH) { 1781b15c8340SDag-Erling Smørgrav error("Maximum directory depth exceeded: %d levels", depth); 1782b15c8340SDag-Erling Smørgrav return -1; 1783b15c8340SDag-Erling Smørgrav } 1784b15c8340SDag-Erling Smørgrav 1785b15c8340SDag-Erling Smørgrav if (stat(src, &sb) == -1) { 1786b15c8340SDag-Erling Smørgrav error("Couldn't stat directory \"%s\": %s", 1787b15c8340SDag-Erling Smørgrav src, strerror(errno)); 1788b15c8340SDag-Erling Smørgrav return -1; 1789b15c8340SDag-Erling Smørgrav } 1790b15c8340SDag-Erling Smørgrav if (!S_ISDIR(sb.st_mode)) { 1791b15c8340SDag-Erling Smørgrav error("\"%s\" is not a directory", src); 1792b15c8340SDag-Erling Smørgrav return -1; 1793b15c8340SDag-Erling Smørgrav } 1794f7167e0eSDag-Erling Smørgrav if (print_flag) 1795b15c8340SDag-Erling Smørgrav printf("Entering %s\n", src); 1796b15c8340SDag-Erling Smørgrav 1797b15c8340SDag-Erling Smørgrav attrib_clear(&a); 1798b15c8340SDag-Erling Smørgrav stat_to_attrib(&sb, &a); 1799b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 1800b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 1801b15c8340SDag-Erling Smørgrav a.perm &= 01777; 1802f7167e0eSDag-Erling Smørgrav if (!preserve_flag) 1803b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 1804b15c8340SDag-Erling Smørgrav 1805b15c8340SDag-Erling Smørgrav status = do_mkdir(conn, dst, &a, 0); 1806b15c8340SDag-Erling Smørgrav /* 1807b15c8340SDag-Erling Smørgrav * we lack a portable status for errno EEXIST, 1808b15c8340SDag-Erling Smørgrav * so if we get a SSH2_FX_FAILURE back we must check 1809b15c8340SDag-Erling Smørgrav * if it was created successfully. 1810b15c8340SDag-Erling Smørgrav */ 1811b15c8340SDag-Erling Smørgrav if (status != SSH2_FX_OK) { 1812b15c8340SDag-Erling Smørgrav if (status != SSH2_FX_FAILURE) 1813b15c8340SDag-Erling Smørgrav return -1; 1814b15c8340SDag-Erling Smørgrav if (do_stat(conn, dst, 0) == NULL) 1815b15c8340SDag-Erling Smørgrav return -1; 1816b15c8340SDag-Erling Smørgrav } 1817b15c8340SDag-Erling Smørgrav 1818b15c8340SDag-Erling Smørgrav if ((dirp = opendir(src)) == NULL) { 1819b15c8340SDag-Erling Smørgrav error("Failed to open dir \"%s\": %s", src, strerror(errno)); 1820b15c8340SDag-Erling Smørgrav return -1; 1821b15c8340SDag-Erling Smørgrav } 1822b15c8340SDag-Erling Smørgrav 1823b15c8340SDag-Erling Smørgrav while (((dp = readdir(dirp)) != NULL) && !interrupted) { 1824b15c8340SDag-Erling Smørgrav if (dp->d_ino == 0) 1825b15c8340SDag-Erling Smørgrav continue; 1826b15c8340SDag-Erling Smørgrav filename = dp->d_name; 1827b15c8340SDag-Erling Smørgrav new_dst = path_append(dst, filename); 1828b15c8340SDag-Erling Smørgrav new_src = path_append(src, filename); 1829b15c8340SDag-Erling Smørgrav 1830b15c8340SDag-Erling Smørgrav if (lstat(new_src, &sb) == -1) { 1831b15c8340SDag-Erling Smørgrav logit("%s: lstat failed: %s", filename, 1832b15c8340SDag-Erling Smørgrav strerror(errno)); 1833b15c8340SDag-Erling Smørgrav ret = -1; 1834b15c8340SDag-Erling Smørgrav } else if (S_ISDIR(sb.st_mode)) { 1835b15c8340SDag-Erling Smørgrav if (strcmp(filename, ".") == 0 || 1836b15c8340SDag-Erling Smørgrav strcmp(filename, "..") == 0) 1837b15c8340SDag-Erling Smørgrav continue; 1838b15c8340SDag-Erling Smørgrav 1839b15c8340SDag-Erling Smørgrav if (upload_dir_internal(conn, new_src, new_dst, 1840a0ee8cc6SDag-Erling Smørgrav depth + 1, preserve_flag, print_flag, resume, 1841f7167e0eSDag-Erling Smørgrav fsync_flag) == -1) 1842b15c8340SDag-Erling Smørgrav ret = -1; 1843b15c8340SDag-Erling Smørgrav } else if (S_ISREG(sb.st_mode)) { 1844f7167e0eSDag-Erling Smørgrav if (do_upload(conn, new_src, new_dst, 1845a0ee8cc6SDag-Erling Smørgrav preserve_flag, resume, fsync_flag) == -1) { 1846b15c8340SDag-Erling Smørgrav error("Uploading of file %s to %s failed!", 1847b15c8340SDag-Erling Smørgrav new_src, new_dst); 1848b15c8340SDag-Erling Smørgrav ret = -1; 1849b15c8340SDag-Erling Smørgrav } 1850b15c8340SDag-Erling Smørgrav } else 1851b15c8340SDag-Erling Smørgrav logit("%s: not a regular file\n", filename); 1852e4a9863fSDag-Erling Smørgrav free(new_dst); 1853e4a9863fSDag-Erling Smørgrav free(new_src); 1854b15c8340SDag-Erling Smørgrav } 1855b15c8340SDag-Erling Smørgrav 1856b15c8340SDag-Erling Smørgrav do_setstat(conn, dst, &a); 1857b15c8340SDag-Erling Smørgrav 1858b15c8340SDag-Erling Smørgrav (void) closedir(dirp); 1859b15c8340SDag-Erling Smørgrav return ret; 1860b15c8340SDag-Erling Smørgrav } 1861b15c8340SDag-Erling Smørgrav 1862b15c8340SDag-Erling Smørgrav int 1863*bc5531deSDag-Erling Smørgrav upload_dir(struct sftp_conn *conn, const char *src, const char *dst, 1864*bc5531deSDag-Erling Smørgrav int preserve_flag, int print_flag, int resume, int fsync_flag) 1865b15c8340SDag-Erling Smørgrav { 1866b15c8340SDag-Erling Smørgrav char *dst_canon; 1867b15c8340SDag-Erling Smørgrav int ret; 1868b15c8340SDag-Erling Smørgrav 1869b15c8340SDag-Erling Smørgrav if ((dst_canon = do_realpath(conn, dst)) == NULL) { 1870f7167e0eSDag-Erling Smørgrav error("Unable to canonicalize path \"%s\"", dst); 1871b15c8340SDag-Erling Smørgrav return -1; 1872b15c8340SDag-Erling Smørgrav } 1873b15c8340SDag-Erling Smørgrav 1874f7167e0eSDag-Erling Smørgrav ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag, 1875a0ee8cc6SDag-Erling Smørgrav print_flag, resume, fsync_flag); 1876f7167e0eSDag-Erling Smørgrav 1877e4a9863fSDag-Erling Smørgrav free(dst_canon); 1878b15c8340SDag-Erling Smørgrav return ret; 1879b15c8340SDag-Erling Smørgrav } 1880b15c8340SDag-Erling Smørgrav 1881b15c8340SDag-Erling Smørgrav char * 1882*bc5531deSDag-Erling Smørgrav path_append(const char *p1, const char *p2) 1883b15c8340SDag-Erling Smørgrav { 1884b15c8340SDag-Erling Smørgrav char *ret; 1885b15c8340SDag-Erling Smørgrav size_t len = strlen(p1) + strlen(p2) + 2; 1886b15c8340SDag-Erling Smørgrav 1887b15c8340SDag-Erling Smørgrav ret = xmalloc(len); 1888b15c8340SDag-Erling Smørgrav strlcpy(ret, p1, len); 1889b15c8340SDag-Erling Smørgrav if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') 1890b15c8340SDag-Erling Smørgrav strlcat(ret, "/", len); 1891b15c8340SDag-Erling Smørgrav strlcat(ret, p2, len); 1892b15c8340SDag-Erling Smørgrav 1893b15c8340SDag-Erling Smørgrav return(ret); 1894b15c8340SDag-Erling Smørgrav } 1895b15c8340SDag-Erling Smørgrav 1896