1*462c32cbSDag-Erling Smørgrav /* $OpenBSD: sftp-client.c,v 1.97 2012/07/02 12:13:26 dtucker 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 25761efaa7SDag-Erling Smørgrav #include <sys/types.h> 26761efaa7SDag-Erling Smørgrav #include <sys/param.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> 45761efaa7SDag-Erling Smørgrav #include <string.h> 46761efaa7SDag-Erling Smørgrav #include <unistd.h> 47761efaa7SDag-Erling Smørgrav 481e8db6e2SBrian Feldman #include "xmalloc.h" 49761efaa7SDag-Erling Smørgrav #include "buffer.h" 501e8db6e2SBrian Feldman #include "log.h" 511e8db6e2SBrian Feldman #include "atomicio.h" 52d0c8c0bcSDag-Erling Smørgrav #include "progressmeter.h" 53761efaa7SDag-Erling Smørgrav #include "misc.h" 541e8db6e2SBrian Feldman 551e8db6e2SBrian Feldman #include "sftp.h" 561e8db6e2SBrian Feldman #include "sftp-common.h" 571e8db6e2SBrian Feldman #include "sftp-client.h" 581e8db6e2SBrian Feldman 59d74d50a8SDag-Erling Smørgrav extern volatile sig_atomic_t interrupted; 60d0c8c0bcSDag-Erling Smørgrav extern int showprogress; 61d0c8c0bcSDag-Erling Smørgrav 62761efaa7SDag-Erling Smørgrav /* Minimum amount of data to read at a time */ 63ae1f160dSDag-Erling Smørgrav #define MIN_READ_SIZE 512 641e8db6e2SBrian Feldman 65b15c8340SDag-Erling Smørgrav /* Maximum depth to descend in directory trees */ 66b15c8340SDag-Erling Smørgrav #define MAX_DIR_DEPTH 64 67b15c8340SDag-Erling Smørgrav 68ae1f160dSDag-Erling Smørgrav struct sftp_conn { 69ae1f160dSDag-Erling Smørgrav int fd_in; 70ae1f160dSDag-Erling Smørgrav int fd_out; 71ae1f160dSDag-Erling Smørgrav u_int transfer_buflen; 72ae1f160dSDag-Erling Smørgrav u_int num_requests; 73ae1f160dSDag-Erling Smørgrav u_int version; 74ae1f160dSDag-Erling Smørgrav u_int msg_id; 75d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_POSIX_RENAME 0x00000001 76d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_STATVFS 0x00000002 77d4af9e69SDag-Erling Smørgrav #define SFTP_EXT_FSTATVFS 0x00000004 784a421b63SDag-Erling Smørgrav #define SFTP_EXT_HARDLINK 0x00000008 79d4af9e69SDag-Erling Smørgrav u_int exts; 804a421b63SDag-Erling Smørgrav u_int64_t limit_kbps; 814a421b63SDag-Erling Smørgrav struct bwlimit bwlimit_in, bwlimit_out; 82ae1f160dSDag-Erling Smørgrav }; 831e8db6e2SBrian Feldman 84b15c8340SDag-Erling Smørgrav static char * 854a421b63SDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, u_int *len, 864a421b63SDag-Erling Smørgrav const char *errfmt, ...) __attribute__((format(printf, 4, 5))); 874a421b63SDag-Erling Smørgrav 884a421b63SDag-Erling Smørgrav /* ARGSUSED */ 894a421b63SDag-Erling Smørgrav static int 904a421b63SDag-Erling Smørgrav sftpio(void *_bwlimit, size_t amount) 914a421b63SDag-Erling Smørgrav { 924a421b63SDag-Erling Smørgrav struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit; 934a421b63SDag-Erling Smørgrav 944a421b63SDag-Erling Smørgrav bandwidth_limit(bwlimit, amount); 954a421b63SDag-Erling Smørgrav return 0; 964a421b63SDag-Erling Smørgrav } 97b15c8340SDag-Erling Smørgrav 98ae1f160dSDag-Erling Smørgrav static void 994a421b63SDag-Erling Smørgrav send_msg(struct sftp_conn *conn, Buffer *m) 1001e8db6e2SBrian Feldman { 101d0c8c0bcSDag-Erling Smørgrav u_char mlen[4]; 102761efaa7SDag-Erling Smørgrav struct iovec iov[2]; 1031e8db6e2SBrian Feldman 104021d409fSDag-Erling Smørgrav if (buffer_len(m) > SFTP_MAX_MSG_LENGTH) 105d0c8c0bcSDag-Erling Smørgrav fatal("Outbound message too long %u", buffer_len(m)); 1061e8db6e2SBrian Feldman 107d0c8c0bcSDag-Erling Smørgrav /* Send length first */ 108761efaa7SDag-Erling Smørgrav put_u32(mlen, buffer_len(m)); 109761efaa7SDag-Erling Smørgrav iov[0].iov_base = mlen; 110761efaa7SDag-Erling Smørgrav iov[0].iov_len = sizeof(mlen); 111761efaa7SDag-Erling Smørgrav iov[1].iov_base = buffer_ptr(m); 112761efaa7SDag-Erling Smørgrav iov[1].iov_len = buffer_len(m); 1131e8db6e2SBrian Feldman 1144a421b63SDag-Erling Smørgrav if (atomiciov6(writev, conn->fd_out, iov, 2, 1154a421b63SDag-Erling Smørgrav conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) != 1164a421b63SDag-Erling Smørgrav buffer_len(m) + sizeof(mlen)) 117d0c8c0bcSDag-Erling Smørgrav fatal("Couldn't send packet: %s", strerror(errno)); 118d0c8c0bcSDag-Erling Smørgrav 119d0c8c0bcSDag-Erling Smørgrav buffer_clear(m); 1201e8db6e2SBrian Feldman } 1211e8db6e2SBrian Feldman 122ae1f160dSDag-Erling Smørgrav static void 1234a421b63SDag-Erling Smørgrav get_msg(struct sftp_conn *conn, Buffer *m) 1241e8db6e2SBrian Feldman { 125d0c8c0bcSDag-Erling Smørgrav u_int msg_len; 1261e8db6e2SBrian Feldman 127d0c8c0bcSDag-Erling Smørgrav buffer_append_space(m, 4); 1284a421b63SDag-Erling Smørgrav if (atomicio6(read, conn->fd_in, buffer_ptr(m), 4, 1294a421b63SDag-Erling Smørgrav conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) { 130043840dfSDag-Erling Smørgrav if (errno == EPIPE) 1311e8db6e2SBrian Feldman fatal("Connection closed"); 132043840dfSDag-Erling Smørgrav else 1331e8db6e2SBrian Feldman fatal("Couldn't read packet: %s", strerror(errno)); 134043840dfSDag-Erling Smørgrav } 1351e8db6e2SBrian Feldman 136d0c8c0bcSDag-Erling Smørgrav msg_len = buffer_get_int(m); 137021d409fSDag-Erling Smørgrav if (msg_len > SFTP_MAX_MSG_LENGTH) 138ee21a45fSDag-Erling Smørgrav fatal("Received message too long %u", msg_len); 1391e8db6e2SBrian Feldman 140d0c8c0bcSDag-Erling Smørgrav buffer_append_space(m, msg_len); 1414a421b63SDag-Erling Smørgrav if (atomicio6(read, conn->fd_in, buffer_ptr(m), msg_len, 1424a421b63SDag-Erling Smørgrav conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) 1434a421b63SDag-Erling Smørgrav != msg_len) { 144043840dfSDag-Erling Smørgrav if (errno == EPIPE) 1451e8db6e2SBrian Feldman fatal("Connection closed"); 146043840dfSDag-Erling Smørgrav else 147d0c8c0bcSDag-Erling Smørgrav fatal("Read packet: %s", strerror(errno)); 1481e8db6e2SBrian Feldman } 149043840dfSDag-Erling Smørgrav } 1501e8db6e2SBrian Feldman 151ae1f160dSDag-Erling Smørgrav static void 1524a421b63SDag-Erling Smørgrav send_string_request(struct sftp_conn *conn, u_int id, u_int code, char *s, 1531e8db6e2SBrian Feldman u_int len) 1541e8db6e2SBrian Feldman { 1551e8db6e2SBrian Feldman Buffer msg; 1561e8db6e2SBrian Feldman 1571e8db6e2SBrian Feldman buffer_init(&msg); 1581e8db6e2SBrian Feldman buffer_put_char(&msg, code); 1591e8db6e2SBrian Feldman buffer_put_int(&msg, id); 1601e8db6e2SBrian Feldman buffer_put_string(&msg, s, len); 1614a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 1624a421b63SDag-Erling Smørgrav debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); 1631e8db6e2SBrian Feldman buffer_free(&msg); 1641e8db6e2SBrian Feldman } 1651e8db6e2SBrian Feldman 166ae1f160dSDag-Erling Smørgrav static void 1674a421b63SDag-Erling Smørgrav send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code, 1684a421b63SDag-Erling Smørgrav char *s, u_int len, Attrib *a) 1691e8db6e2SBrian Feldman { 1701e8db6e2SBrian Feldman Buffer msg; 1711e8db6e2SBrian Feldman 1721e8db6e2SBrian Feldman buffer_init(&msg); 1731e8db6e2SBrian Feldman buffer_put_char(&msg, code); 1741e8db6e2SBrian Feldman buffer_put_int(&msg, id); 1751e8db6e2SBrian Feldman buffer_put_string(&msg, s, len); 1761e8db6e2SBrian Feldman encode_attrib(&msg, a); 1774a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 1784a421b63SDag-Erling Smørgrav debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); 1791e8db6e2SBrian Feldman buffer_free(&msg); 1801e8db6e2SBrian Feldman } 1811e8db6e2SBrian Feldman 182ae1f160dSDag-Erling Smørgrav static u_int 1834a421b63SDag-Erling Smørgrav get_status(struct sftp_conn *conn, u_int expected_id) 1841e8db6e2SBrian Feldman { 1851e8db6e2SBrian Feldman Buffer msg; 1861e8db6e2SBrian Feldman u_int type, id, status; 1871e8db6e2SBrian Feldman 1881e8db6e2SBrian Feldman buffer_init(&msg); 1894a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 1901e8db6e2SBrian Feldman type = buffer_get_char(&msg); 1911e8db6e2SBrian Feldman id = buffer_get_int(&msg); 1921e8db6e2SBrian Feldman 1931e8db6e2SBrian Feldman if (id != expected_id) 194ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 1951e8db6e2SBrian Feldman if (type != SSH2_FXP_STATUS) 196ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u", 1971e8db6e2SBrian Feldman SSH2_FXP_STATUS, type); 1981e8db6e2SBrian Feldman 1991e8db6e2SBrian Feldman status = buffer_get_int(&msg); 2001e8db6e2SBrian Feldman buffer_free(&msg); 2011e8db6e2SBrian Feldman 202ee21a45fSDag-Erling Smørgrav debug3("SSH2_FXP_STATUS %u", status); 2031e8db6e2SBrian Feldman 2044a421b63SDag-Erling Smørgrav return status; 2051e8db6e2SBrian Feldman } 2061e8db6e2SBrian Feldman 207ae1f160dSDag-Erling Smørgrav static char * 2084a421b63SDag-Erling Smørgrav get_handle(struct sftp_conn *conn, u_int expected_id, u_int *len, 2094a421b63SDag-Erling Smørgrav const char *errfmt, ...) 2101e8db6e2SBrian Feldman { 2111e8db6e2SBrian Feldman Buffer msg; 2121e8db6e2SBrian Feldman u_int type, id; 213b15c8340SDag-Erling Smørgrav char *handle, errmsg[256]; 214b15c8340SDag-Erling Smørgrav va_list args; 215b15c8340SDag-Erling Smørgrav int status; 216b15c8340SDag-Erling Smørgrav 217b15c8340SDag-Erling Smørgrav va_start(args, errfmt); 218b15c8340SDag-Erling Smørgrav if (errfmt != NULL) 219b15c8340SDag-Erling Smørgrav vsnprintf(errmsg, sizeof(errmsg), errfmt, args); 220b15c8340SDag-Erling Smørgrav va_end(args); 2211e8db6e2SBrian Feldman 2221e8db6e2SBrian Feldman buffer_init(&msg); 2234a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 2241e8db6e2SBrian Feldman type = buffer_get_char(&msg); 2251e8db6e2SBrian Feldman id = buffer_get_int(&msg); 2261e8db6e2SBrian Feldman 2271e8db6e2SBrian Feldman if (id != expected_id) 228b15c8340SDag-Erling Smørgrav fatal("%s: ID mismatch (%u != %u)", 229b15c8340SDag-Erling Smørgrav errfmt == NULL ? __func__ : errmsg, id, expected_id); 2301e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 231b15c8340SDag-Erling Smørgrav status = buffer_get_int(&msg); 232b15c8340SDag-Erling Smørgrav if (errfmt != NULL) 233b15c8340SDag-Erling Smørgrav error("%s: %s", errmsg, fx2txt(status)); 2345e8dbd04SDag-Erling Smørgrav buffer_free(&msg); 2351e8db6e2SBrian Feldman return(NULL); 2361e8db6e2SBrian Feldman } else if (type != SSH2_FXP_HANDLE) 237b15c8340SDag-Erling Smørgrav fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u", 238b15c8340SDag-Erling Smørgrav errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type); 2391e8db6e2SBrian Feldman 2401e8db6e2SBrian Feldman handle = buffer_get_string(&msg, len); 2411e8db6e2SBrian Feldman buffer_free(&msg); 2421e8db6e2SBrian Feldman 2431e8db6e2SBrian Feldman return(handle); 2441e8db6e2SBrian Feldman } 2451e8db6e2SBrian Feldman 246ae1f160dSDag-Erling Smørgrav static Attrib * 2474a421b63SDag-Erling Smørgrav get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet) 2481e8db6e2SBrian Feldman { 2491e8db6e2SBrian Feldman Buffer msg; 2501e8db6e2SBrian Feldman u_int type, id; 2511e8db6e2SBrian Feldman Attrib *a; 2521e8db6e2SBrian Feldman 2531e8db6e2SBrian Feldman buffer_init(&msg); 2544a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 2551e8db6e2SBrian Feldman 2561e8db6e2SBrian Feldman type = buffer_get_char(&msg); 2571e8db6e2SBrian Feldman id = buffer_get_int(&msg); 2581e8db6e2SBrian Feldman 259ee21a45fSDag-Erling Smørgrav debug3("Received stat reply T:%u I:%u", type, id); 2601e8db6e2SBrian Feldman if (id != expected_id) 261ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 2621e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 2631e8db6e2SBrian Feldman int status = buffer_get_int(&msg); 2641e8db6e2SBrian Feldman 2651e8db6e2SBrian Feldman if (quiet) 2661e8db6e2SBrian Feldman debug("Couldn't stat remote file: %s", fx2txt(status)); 2671e8db6e2SBrian Feldman else 2681e8db6e2SBrian Feldman error("Couldn't stat remote file: %s", fx2txt(status)); 2695e8dbd04SDag-Erling Smørgrav buffer_free(&msg); 2701e8db6e2SBrian Feldman return(NULL); 2711e8db6e2SBrian Feldman } else if (type != SSH2_FXP_ATTRS) { 272ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", 2731e8db6e2SBrian Feldman SSH2_FXP_ATTRS, type); 2741e8db6e2SBrian Feldman } 2751e8db6e2SBrian Feldman a = decode_attrib(&msg); 2761e8db6e2SBrian Feldman buffer_free(&msg); 2771e8db6e2SBrian Feldman 2781e8db6e2SBrian Feldman return(a); 2791e8db6e2SBrian Feldman } 2801e8db6e2SBrian Feldman 281d4af9e69SDag-Erling Smørgrav static int 2824a421b63SDag-Erling Smørgrav get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st, 2834a421b63SDag-Erling Smørgrav u_int expected_id, int quiet) 284d4af9e69SDag-Erling Smørgrav { 285d4af9e69SDag-Erling Smørgrav Buffer msg; 286d4af9e69SDag-Erling Smørgrav u_int type, id, flag; 287d4af9e69SDag-Erling Smørgrav 288d4af9e69SDag-Erling Smørgrav buffer_init(&msg); 2894a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 290d4af9e69SDag-Erling Smørgrav 291d4af9e69SDag-Erling Smørgrav type = buffer_get_char(&msg); 292d4af9e69SDag-Erling Smørgrav id = buffer_get_int(&msg); 293d4af9e69SDag-Erling Smørgrav 294d4af9e69SDag-Erling Smørgrav debug3("Received statvfs reply T:%u I:%u", type, id); 295d4af9e69SDag-Erling Smørgrav if (id != expected_id) 296d4af9e69SDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 297d4af9e69SDag-Erling Smørgrav if (type == SSH2_FXP_STATUS) { 298d4af9e69SDag-Erling Smørgrav int status = buffer_get_int(&msg); 299d4af9e69SDag-Erling Smørgrav 300d4af9e69SDag-Erling Smørgrav if (quiet) 301d4af9e69SDag-Erling Smørgrav debug("Couldn't statvfs: %s", fx2txt(status)); 302d4af9e69SDag-Erling Smørgrav else 303d4af9e69SDag-Erling Smørgrav error("Couldn't statvfs: %s", fx2txt(status)); 304d4af9e69SDag-Erling Smørgrav buffer_free(&msg); 305d4af9e69SDag-Erling Smørgrav return -1; 306d4af9e69SDag-Erling Smørgrav } else if (type != SSH2_FXP_EXTENDED_REPLY) { 307d4af9e69SDag-Erling Smørgrav fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", 308d4af9e69SDag-Erling Smørgrav SSH2_FXP_EXTENDED_REPLY, type); 309d4af9e69SDag-Erling Smørgrav } 310d4af9e69SDag-Erling Smørgrav 311d4af9e69SDag-Erling Smørgrav bzero(st, sizeof(*st)); 312d4af9e69SDag-Erling Smørgrav st->f_bsize = buffer_get_int64(&msg); 313d4af9e69SDag-Erling Smørgrav st->f_frsize = buffer_get_int64(&msg); 314d4af9e69SDag-Erling Smørgrav st->f_blocks = buffer_get_int64(&msg); 315d4af9e69SDag-Erling Smørgrav st->f_bfree = buffer_get_int64(&msg); 316d4af9e69SDag-Erling Smørgrav st->f_bavail = buffer_get_int64(&msg); 317d4af9e69SDag-Erling Smørgrav st->f_files = buffer_get_int64(&msg); 318d4af9e69SDag-Erling Smørgrav st->f_ffree = buffer_get_int64(&msg); 319d4af9e69SDag-Erling Smørgrav st->f_favail = buffer_get_int64(&msg); 320d4af9e69SDag-Erling Smørgrav st->f_fsid = buffer_get_int64(&msg); 321d4af9e69SDag-Erling Smørgrav flag = buffer_get_int64(&msg); 322d4af9e69SDag-Erling Smørgrav st->f_namemax = buffer_get_int64(&msg); 323d4af9e69SDag-Erling Smørgrav 324d4af9e69SDag-Erling Smørgrav st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0; 325d4af9e69SDag-Erling Smørgrav st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0; 326d4af9e69SDag-Erling Smørgrav 327d4af9e69SDag-Erling Smørgrav buffer_free(&msg); 328d4af9e69SDag-Erling Smørgrav 329d4af9e69SDag-Erling Smørgrav return 0; 330d4af9e69SDag-Erling Smørgrav } 331d4af9e69SDag-Erling Smørgrav 332ae1f160dSDag-Erling Smørgrav struct sftp_conn * 3334a421b63SDag-Erling Smørgrav do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, 3344a421b63SDag-Erling Smørgrav u_int64_t limit_kbps) 3351e8db6e2SBrian Feldman { 3364a421b63SDag-Erling Smørgrav u_int type; 3371e8db6e2SBrian Feldman Buffer msg; 338ae1f160dSDag-Erling Smørgrav struct sftp_conn *ret; 3391e8db6e2SBrian Feldman 3404a421b63SDag-Erling Smørgrav ret = xmalloc(sizeof(*ret)); 3414a421b63SDag-Erling Smørgrav ret->fd_in = fd_in; 3424a421b63SDag-Erling Smørgrav ret->fd_out = fd_out; 3434a421b63SDag-Erling Smørgrav ret->transfer_buflen = transfer_buflen; 3444a421b63SDag-Erling Smørgrav ret->num_requests = num_requests; 3454a421b63SDag-Erling Smørgrav ret->exts = 0; 3464a421b63SDag-Erling Smørgrav ret->limit_kbps = 0; 3474a421b63SDag-Erling Smørgrav 3481e8db6e2SBrian Feldman buffer_init(&msg); 3491e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_INIT); 3501e8db6e2SBrian Feldman buffer_put_int(&msg, SSH2_FILEXFER_VERSION); 3514a421b63SDag-Erling Smørgrav send_msg(ret, &msg); 3521e8db6e2SBrian Feldman 3531e8db6e2SBrian Feldman buffer_clear(&msg); 3541e8db6e2SBrian Feldman 3554a421b63SDag-Erling Smørgrav get_msg(ret, &msg); 3561e8db6e2SBrian Feldman 3571e8db6e2SBrian Feldman /* Expecting a VERSION reply */ 3581e8db6e2SBrian Feldman if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) { 359ee21a45fSDag-Erling Smørgrav error("Invalid packet back from SSH2_FXP_INIT (type %u)", 3601e8db6e2SBrian Feldman type); 3611e8db6e2SBrian Feldman buffer_free(&msg); 362ae1f160dSDag-Erling Smørgrav return(NULL); 3631e8db6e2SBrian Feldman } 3644a421b63SDag-Erling Smørgrav ret->version = buffer_get_int(&msg); 3651e8db6e2SBrian Feldman 3664a421b63SDag-Erling Smørgrav debug2("Remote version: %u", ret->version); 3671e8db6e2SBrian Feldman 3681e8db6e2SBrian Feldman /* Check for extensions */ 3691e8db6e2SBrian Feldman while (buffer_len(&msg) > 0) { 3701e8db6e2SBrian Feldman char *name = buffer_get_string(&msg, NULL); 3711e8db6e2SBrian Feldman char *value = buffer_get_string(&msg, NULL); 372d4af9e69SDag-Erling Smørgrav int known = 0; 3731e8db6e2SBrian Feldman 374d4af9e69SDag-Erling Smørgrav if (strcmp(name, "posix-rename@openssh.com") == 0 && 375d4af9e69SDag-Erling Smørgrav strcmp(value, "1") == 0) { 3764a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_POSIX_RENAME; 377d4af9e69SDag-Erling Smørgrav known = 1; 378d4af9e69SDag-Erling Smørgrav } else if (strcmp(name, "statvfs@openssh.com") == 0 && 379d4af9e69SDag-Erling Smørgrav strcmp(value, "2") == 0) { 3804a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_STATVFS; 381d4af9e69SDag-Erling Smørgrav known = 1; 3824a421b63SDag-Erling Smørgrav } else if (strcmp(name, "fstatvfs@openssh.com") == 0 && 383d4af9e69SDag-Erling Smørgrav strcmp(value, "2") == 0) { 3844a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_FSTATVFS; 3854a421b63SDag-Erling Smørgrav known = 1; 3864a421b63SDag-Erling Smørgrav } else if (strcmp(name, "hardlink@openssh.com") == 0 && 3874a421b63SDag-Erling Smørgrav strcmp(value, "1") == 0) { 3884a421b63SDag-Erling Smørgrav ret->exts |= SFTP_EXT_HARDLINK; 389d4af9e69SDag-Erling Smørgrav known = 1; 390d4af9e69SDag-Erling Smørgrav } 391d4af9e69SDag-Erling Smørgrav if (known) { 392d4af9e69SDag-Erling Smørgrav debug2("Server supports extension \"%s\" revision %s", 393d4af9e69SDag-Erling Smørgrav name, value); 394d4af9e69SDag-Erling Smørgrav } else { 395d4af9e69SDag-Erling Smørgrav debug2("Unrecognised server extension \"%s\"", name); 396d4af9e69SDag-Erling Smørgrav } 3971e8db6e2SBrian Feldman xfree(name); 3981e8db6e2SBrian Feldman xfree(value); 3991e8db6e2SBrian Feldman } 4001e8db6e2SBrian Feldman 4011e8db6e2SBrian Feldman buffer_free(&msg); 4021e8db6e2SBrian Feldman 403ae1f160dSDag-Erling Smørgrav /* Some filexfer v.0 servers don't support large packets */ 4044a421b63SDag-Erling Smørgrav if (ret->version == 0) 405545d5ecaSDag-Erling Smørgrav ret->transfer_buflen = MIN(ret->transfer_buflen, 20480); 406ae1f160dSDag-Erling Smørgrav 4074a421b63SDag-Erling Smørgrav ret->limit_kbps = limit_kbps; 4084a421b63SDag-Erling Smørgrav if (ret->limit_kbps > 0) { 4094a421b63SDag-Erling Smørgrav bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps, 4104a421b63SDag-Erling Smørgrav ret->transfer_buflen); 4114a421b63SDag-Erling Smørgrav bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps, 4124a421b63SDag-Erling Smørgrav ret->transfer_buflen); 4134a421b63SDag-Erling Smørgrav } 4144a421b63SDag-Erling Smørgrav 4154a421b63SDag-Erling Smørgrav return ret; 416ae1f160dSDag-Erling Smørgrav } 417ae1f160dSDag-Erling Smørgrav 418ae1f160dSDag-Erling Smørgrav u_int 419ae1f160dSDag-Erling Smørgrav sftp_proto_version(struct sftp_conn *conn) 420ae1f160dSDag-Erling Smørgrav { 4214a421b63SDag-Erling Smørgrav return conn->version; 4221e8db6e2SBrian Feldman } 4231e8db6e2SBrian Feldman 4241e8db6e2SBrian Feldman int 425ae1f160dSDag-Erling Smørgrav do_close(struct sftp_conn *conn, char *handle, u_int handle_len) 4261e8db6e2SBrian Feldman { 4271e8db6e2SBrian Feldman u_int id, status; 4281e8db6e2SBrian Feldman Buffer msg; 4291e8db6e2SBrian Feldman 4301e8db6e2SBrian Feldman buffer_init(&msg); 4311e8db6e2SBrian Feldman 432ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 4331e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_CLOSE); 4341e8db6e2SBrian Feldman buffer_put_int(&msg, id); 4351e8db6e2SBrian Feldman buffer_put_string(&msg, handle, handle_len); 4364a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 437ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_CLOSE I:%u", id); 4381e8db6e2SBrian Feldman 4394a421b63SDag-Erling Smørgrav status = get_status(conn, id); 4401e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 4411e8db6e2SBrian Feldman error("Couldn't close file: %s", fx2txt(status)); 4421e8db6e2SBrian Feldman 4431e8db6e2SBrian Feldman buffer_free(&msg); 4441e8db6e2SBrian Feldman 4454a421b63SDag-Erling Smørgrav return status; 4461e8db6e2SBrian Feldman } 4471e8db6e2SBrian Feldman 4481e8db6e2SBrian Feldman 449ae1f160dSDag-Erling Smørgrav static int 450ae1f160dSDag-Erling Smørgrav do_lsreaddir(struct sftp_conn *conn, char *path, int printflag, 4511e8db6e2SBrian Feldman SFTP_DIRENT ***dir) 4521e8db6e2SBrian Feldman { 4531e8db6e2SBrian Feldman Buffer msg; 454043840dfSDag-Erling Smørgrav u_int count, type, id, handle_len, i, expected_id, ents = 0; 4551e8db6e2SBrian Feldman char *handle; 4561e8db6e2SBrian Feldman 457ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 4581e8db6e2SBrian Feldman 4591e8db6e2SBrian Feldman buffer_init(&msg); 4601e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_OPENDIR); 4611e8db6e2SBrian Feldman buffer_put_int(&msg, id); 4621e8db6e2SBrian Feldman buffer_put_cstring(&msg, path); 4634a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 4641e8db6e2SBrian Feldman 4654a421b63SDag-Erling Smørgrav handle = get_handle(conn, id, &handle_len, 466b15c8340SDag-Erling Smørgrav "remote readdir(\"%s\")", path); 467*462c32cbSDag-Erling Smørgrav if (handle == NULL) { 468*462c32cbSDag-Erling Smørgrav buffer_free(&msg); 4694a421b63SDag-Erling Smørgrav return -1; 470*462c32cbSDag-Erling Smørgrav } 4711e8db6e2SBrian Feldman 4721e8db6e2SBrian Feldman if (dir) { 4731e8db6e2SBrian Feldman ents = 0; 4741e8db6e2SBrian Feldman *dir = xmalloc(sizeof(**dir)); 4751e8db6e2SBrian Feldman (*dir)[0] = NULL; 4761e8db6e2SBrian Feldman } 4771e8db6e2SBrian Feldman 478d74d50a8SDag-Erling Smørgrav for (; !interrupted;) { 479ae1f160dSDag-Erling Smørgrav id = expected_id = conn->msg_id++; 4801e8db6e2SBrian Feldman 481ee21a45fSDag-Erling Smørgrav debug3("Sending SSH2_FXP_READDIR I:%u", id); 4821e8db6e2SBrian Feldman 4831e8db6e2SBrian Feldman buffer_clear(&msg); 4841e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_READDIR); 4851e8db6e2SBrian Feldman buffer_put_int(&msg, id); 4861e8db6e2SBrian Feldman buffer_put_string(&msg, handle, handle_len); 4874a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 4881e8db6e2SBrian Feldman 4891e8db6e2SBrian Feldman buffer_clear(&msg); 4901e8db6e2SBrian Feldman 4914a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 4921e8db6e2SBrian Feldman 4931e8db6e2SBrian Feldman type = buffer_get_char(&msg); 4941e8db6e2SBrian Feldman id = buffer_get_int(&msg); 4951e8db6e2SBrian Feldman 496ee21a45fSDag-Erling Smørgrav debug3("Received reply T:%u I:%u", type, id); 4971e8db6e2SBrian Feldman 4981e8db6e2SBrian Feldman if (id != expected_id) 499ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 5001e8db6e2SBrian Feldman 5011e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 5021e8db6e2SBrian Feldman int status = buffer_get_int(&msg); 5031e8db6e2SBrian Feldman 5041e8db6e2SBrian Feldman debug3("Received SSH2_FXP_STATUS %d", status); 5051e8db6e2SBrian Feldman 5061e8db6e2SBrian Feldman if (status == SSH2_FX_EOF) { 5071e8db6e2SBrian Feldman break; 5081e8db6e2SBrian Feldman } else { 5091e8db6e2SBrian Feldman error("Couldn't read directory: %s", 5101e8db6e2SBrian Feldman fx2txt(status)); 511ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 512d0c8c0bcSDag-Erling Smørgrav xfree(handle); 513*462c32cbSDag-Erling Smørgrav buffer_free(&msg); 5141e8db6e2SBrian Feldman return(status); 5151e8db6e2SBrian Feldman } 5161e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 517ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 5181e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 5191e8db6e2SBrian Feldman 5201e8db6e2SBrian Feldman count = buffer_get_int(&msg); 5211e8db6e2SBrian Feldman if (count == 0) 5221e8db6e2SBrian Feldman break; 5231e8db6e2SBrian Feldman debug3("Received %d SSH2_FXP_NAME responses", count); 5241e8db6e2SBrian Feldman for (i = 0; i < count; i++) { 5251e8db6e2SBrian Feldman char *filename, *longname; 5261e8db6e2SBrian Feldman Attrib *a; 5271e8db6e2SBrian Feldman 5281e8db6e2SBrian Feldman filename = buffer_get_string(&msg, NULL); 5291e8db6e2SBrian Feldman longname = buffer_get_string(&msg, NULL); 5301e8db6e2SBrian Feldman a = decode_attrib(&msg); 5311e8db6e2SBrian Feldman 5321e8db6e2SBrian Feldman if (printflag) 5331e8db6e2SBrian Feldman printf("%s\n", longname); 5341e8db6e2SBrian Feldman 535b15c8340SDag-Erling Smørgrav /* 536b15c8340SDag-Erling Smørgrav * Directory entries should never contain '/' 537b15c8340SDag-Erling Smørgrav * These can be used to attack recursive ops 538b15c8340SDag-Erling Smørgrav * (e.g. send '../../../../etc/passwd') 539b15c8340SDag-Erling Smørgrav */ 540b15c8340SDag-Erling Smørgrav if (strchr(filename, '/') != NULL) { 541b15c8340SDag-Erling Smørgrav error("Server sent suspect path \"%s\" " 542b15c8340SDag-Erling Smørgrav "during readdir of \"%s\"", filename, path); 543b15c8340SDag-Erling Smørgrav goto next; 544b15c8340SDag-Erling Smørgrav } 545b15c8340SDag-Erling Smørgrav 5461e8db6e2SBrian Feldman if (dir) { 547761efaa7SDag-Erling Smørgrav *dir = xrealloc(*dir, ents + 2, sizeof(**dir)); 5481e8db6e2SBrian Feldman (*dir)[ents] = xmalloc(sizeof(***dir)); 5491e8db6e2SBrian Feldman (*dir)[ents]->filename = xstrdup(filename); 5501e8db6e2SBrian Feldman (*dir)[ents]->longname = xstrdup(longname); 5511e8db6e2SBrian Feldman memcpy(&(*dir)[ents]->a, a, sizeof(*a)); 5521e8db6e2SBrian Feldman (*dir)[++ents] = NULL; 5531e8db6e2SBrian Feldman } 554b15c8340SDag-Erling Smørgrav next: 5551e8db6e2SBrian Feldman xfree(filename); 5561e8db6e2SBrian Feldman xfree(longname); 5571e8db6e2SBrian Feldman } 5581e8db6e2SBrian Feldman } 5591e8db6e2SBrian Feldman 5601e8db6e2SBrian Feldman buffer_free(&msg); 561ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 5621e8db6e2SBrian Feldman xfree(handle); 5631e8db6e2SBrian Feldman 564d74d50a8SDag-Erling Smørgrav /* Don't return partial matches on interrupt */ 565d74d50a8SDag-Erling Smørgrav if (interrupted && dir != NULL && *dir != NULL) { 566d74d50a8SDag-Erling Smørgrav free_sftp_dirents(*dir); 567d74d50a8SDag-Erling Smørgrav *dir = xmalloc(sizeof(**dir)); 568d74d50a8SDag-Erling Smørgrav **dir = NULL; 569d74d50a8SDag-Erling Smørgrav } 570d74d50a8SDag-Erling Smørgrav 5714a421b63SDag-Erling Smørgrav return 0; 5721e8db6e2SBrian Feldman } 5731e8db6e2SBrian Feldman 5741e8db6e2SBrian Feldman int 575ae1f160dSDag-Erling Smørgrav do_readdir(struct sftp_conn *conn, char *path, SFTP_DIRENT ***dir) 5761e8db6e2SBrian Feldman { 577ae1f160dSDag-Erling Smørgrav return(do_lsreaddir(conn, path, 0, dir)); 5781e8db6e2SBrian Feldman } 5791e8db6e2SBrian Feldman 5801e8db6e2SBrian Feldman void free_sftp_dirents(SFTP_DIRENT **s) 5811e8db6e2SBrian Feldman { 5821e8db6e2SBrian Feldman int i; 5831e8db6e2SBrian Feldman 5841e8db6e2SBrian Feldman for (i = 0; s[i]; i++) { 5851e8db6e2SBrian Feldman xfree(s[i]->filename); 5861e8db6e2SBrian Feldman xfree(s[i]->longname); 5871e8db6e2SBrian Feldman xfree(s[i]); 5881e8db6e2SBrian Feldman } 5891e8db6e2SBrian Feldman xfree(s); 5901e8db6e2SBrian Feldman } 5911e8db6e2SBrian Feldman 5921e8db6e2SBrian Feldman int 593ae1f160dSDag-Erling Smørgrav do_rm(struct sftp_conn *conn, char *path) 5941e8db6e2SBrian Feldman { 5951e8db6e2SBrian Feldman u_int status, id; 5961e8db6e2SBrian Feldman 5971e8db6e2SBrian Feldman debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); 5981e8db6e2SBrian Feldman 599ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6004a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path)); 6014a421b63SDag-Erling Smørgrav status = get_status(conn, id); 6021e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 6031e8db6e2SBrian Feldman error("Couldn't delete file: %s", fx2txt(status)); 6041e8db6e2SBrian Feldman return(status); 6051e8db6e2SBrian Feldman } 6061e8db6e2SBrian Feldman 6071e8db6e2SBrian Feldman int 608b15c8340SDag-Erling Smørgrav do_mkdir(struct sftp_conn *conn, char *path, Attrib *a, int printflag) 6091e8db6e2SBrian Feldman { 6101e8db6e2SBrian Feldman u_int status, id; 6111e8db6e2SBrian Feldman 612ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6134a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path, 6141e8db6e2SBrian Feldman strlen(path), a); 6151e8db6e2SBrian Feldman 6164a421b63SDag-Erling Smørgrav status = get_status(conn, id); 617b15c8340SDag-Erling Smørgrav if (status != SSH2_FX_OK && printflag) 6181e8db6e2SBrian Feldman error("Couldn't create directory: %s", fx2txt(status)); 6191e8db6e2SBrian Feldman 6201e8db6e2SBrian Feldman return(status); 6211e8db6e2SBrian Feldman } 6221e8db6e2SBrian Feldman 6231e8db6e2SBrian Feldman int 624ae1f160dSDag-Erling Smørgrav do_rmdir(struct sftp_conn *conn, char *path) 6251e8db6e2SBrian Feldman { 6261e8db6e2SBrian Feldman u_int status, id; 6271e8db6e2SBrian Feldman 628ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6294a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_RMDIR, path, 630ae1f160dSDag-Erling Smørgrav strlen(path)); 6311e8db6e2SBrian Feldman 6324a421b63SDag-Erling Smørgrav status = get_status(conn, id); 6331e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 6341e8db6e2SBrian Feldman error("Couldn't remove directory: %s", fx2txt(status)); 6351e8db6e2SBrian Feldman 6361e8db6e2SBrian Feldman return(status); 6371e8db6e2SBrian Feldman } 6381e8db6e2SBrian Feldman 6391e8db6e2SBrian Feldman Attrib * 640ae1f160dSDag-Erling Smørgrav do_stat(struct sftp_conn *conn, char *path, int quiet) 6411e8db6e2SBrian Feldman { 6421e8db6e2SBrian Feldman u_int id; 6431e8db6e2SBrian Feldman 644ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 645ae1f160dSDag-Erling Smørgrav 6464a421b63SDag-Erling Smørgrav send_string_request(conn, id, 647ae1f160dSDag-Erling Smørgrav conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, 648ae1f160dSDag-Erling Smørgrav path, strlen(path)); 649ae1f160dSDag-Erling Smørgrav 6504a421b63SDag-Erling Smørgrav return(get_decode_stat(conn, id, quiet)); 6511e8db6e2SBrian Feldman } 6521e8db6e2SBrian Feldman 6531e8db6e2SBrian Feldman Attrib * 654ae1f160dSDag-Erling Smørgrav do_lstat(struct sftp_conn *conn, char *path, int quiet) 6551e8db6e2SBrian Feldman { 6561e8db6e2SBrian Feldman u_int id; 6571e8db6e2SBrian Feldman 658ae1f160dSDag-Erling Smørgrav if (conn->version == 0) { 659ae1f160dSDag-Erling Smørgrav if (quiet) 660ae1f160dSDag-Erling Smørgrav debug("Server version does not support lstat operation"); 661ae1f160dSDag-Erling Smørgrav else 662d95e11bfSDag-Erling Smørgrav logit("Server version does not support lstat operation"); 663545d5ecaSDag-Erling Smørgrav return(do_stat(conn, path, quiet)); 664ae1f160dSDag-Erling Smørgrav } 665ae1f160dSDag-Erling Smørgrav 666ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6674a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_LSTAT, path, 668ae1f160dSDag-Erling Smørgrav strlen(path)); 669ae1f160dSDag-Erling Smørgrav 6704a421b63SDag-Erling Smørgrav return(get_decode_stat(conn, id, quiet)); 6711e8db6e2SBrian Feldman } 6721e8db6e2SBrian Feldman 673d4af9e69SDag-Erling Smørgrav #ifdef notyet 6741e8db6e2SBrian Feldman Attrib * 675ae1f160dSDag-Erling Smørgrav do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet) 6761e8db6e2SBrian Feldman { 6771e8db6e2SBrian Feldman u_int id; 6781e8db6e2SBrian Feldman 679ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6804a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_FSTAT, handle, 681ae1f160dSDag-Erling Smørgrav handle_len); 682ae1f160dSDag-Erling Smørgrav 6834a421b63SDag-Erling Smørgrav return(get_decode_stat(conn, id, quiet)); 6841e8db6e2SBrian Feldman } 685d4af9e69SDag-Erling Smørgrav #endif 6861e8db6e2SBrian Feldman 6871e8db6e2SBrian Feldman int 688ae1f160dSDag-Erling Smørgrav do_setstat(struct sftp_conn *conn, char *path, Attrib *a) 6891e8db6e2SBrian Feldman { 6901e8db6e2SBrian Feldman u_int status, id; 6911e8db6e2SBrian Feldman 692ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6934a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path, 6941e8db6e2SBrian Feldman strlen(path), a); 6951e8db6e2SBrian Feldman 6964a421b63SDag-Erling Smørgrav status = get_status(conn, id); 6971e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 6981e8db6e2SBrian Feldman error("Couldn't setstat on \"%s\": %s", path, 6991e8db6e2SBrian Feldman fx2txt(status)); 7001e8db6e2SBrian Feldman 7011e8db6e2SBrian Feldman return(status); 7021e8db6e2SBrian Feldman } 7031e8db6e2SBrian Feldman 7041e8db6e2SBrian Feldman int 705ae1f160dSDag-Erling Smørgrav do_fsetstat(struct sftp_conn *conn, char *handle, u_int handle_len, 7061e8db6e2SBrian Feldman Attrib *a) 7071e8db6e2SBrian Feldman { 7081e8db6e2SBrian Feldman u_int status, id; 7091e8db6e2SBrian Feldman 710ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 7114a421b63SDag-Erling Smørgrav send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle, 7121e8db6e2SBrian Feldman handle_len, a); 7131e8db6e2SBrian Feldman 7144a421b63SDag-Erling Smørgrav status = get_status(conn, id); 7151e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 7161e8db6e2SBrian Feldman error("Couldn't fsetstat: %s", fx2txt(status)); 7171e8db6e2SBrian Feldman 7181e8db6e2SBrian Feldman return(status); 7191e8db6e2SBrian Feldman } 7201e8db6e2SBrian Feldman 7211e8db6e2SBrian Feldman char * 722ae1f160dSDag-Erling Smørgrav do_realpath(struct sftp_conn *conn, char *path) 7231e8db6e2SBrian Feldman { 7241e8db6e2SBrian Feldman Buffer msg; 7251e8db6e2SBrian Feldman u_int type, expected_id, count, id; 7261e8db6e2SBrian Feldman char *filename, *longname; 7271e8db6e2SBrian Feldman Attrib *a; 7281e8db6e2SBrian Feldman 729ae1f160dSDag-Erling Smørgrav expected_id = id = conn->msg_id++; 7304a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_REALPATH, path, 731ae1f160dSDag-Erling Smørgrav strlen(path)); 7321e8db6e2SBrian Feldman 7331e8db6e2SBrian Feldman buffer_init(&msg); 7341e8db6e2SBrian Feldman 7354a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 7361e8db6e2SBrian Feldman type = buffer_get_char(&msg); 7371e8db6e2SBrian Feldman id = buffer_get_int(&msg); 7381e8db6e2SBrian Feldman 7391e8db6e2SBrian Feldman if (id != expected_id) 740ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 7411e8db6e2SBrian Feldman 7421e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 7431e8db6e2SBrian Feldman u_int status = buffer_get_int(&msg); 7441e8db6e2SBrian Feldman 7451e8db6e2SBrian Feldman error("Couldn't canonicalise: %s", fx2txt(status)); 746e2f6069cSDag-Erling Smørgrav buffer_free(&msg); 747e2f6069cSDag-Erling Smørgrav return NULL; 7481e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 749ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 7501e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 7511e8db6e2SBrian Feldman 7521e8db6e2SBrian Feldman count = buffer_get_int(&msg); 7531e8db6e2SBrian Feldman if (count != 1) 7541e8db6e2SBrian Feldman fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count); 7551e8db6e2SBrian Feldman 7561e8db6e2SBrian Feldman filename = buffer_get_string(&msg, NULL); 7571e8db6e2SBrian Feldman longname = buffer_get_string(&msg, NULL); 7581e8db6e2SBrian Feldman a = decode_attrib(&msg); 7591e8db6e2SBrian Feldman 760*462c32cbSDag-Erling Smørgrav debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename, 761*462c32cbSDag-Erling Smørgrav (unsigned long)a->size); 7621e8db6e2SBrian Feldman 7631e8db6e2SBrian Feldman xfree(longname); 7641e8db6e2SBrian Feldman 7651e8db6e2SBrian Feldman buffer_free(&msg); 7661e8db6e2SBrian Feldman 7671e8db6e2SBrian Feldman return(filename); 7681e8db6e2SBrian Feldman } 7691e8db6e2SBrian Feldman 7701e8db6e2SBrian Feldman int 771ae1f160dSDag-Erling Smørgrav do_rename(struct sftp_conn *conn, char *oldpath, char *newpath) 7721e8db6e2SBrian Feldman { 7731e8db6e2SBrian Feldman Buffer msg; 7741e8db6e2SBrian Feldman u_int status, id; 7751e8db6e2SBrian Feldman 7761e8db6e2SBrian Feldman buffer_init(&msg); 7771e8db6e2SBrian Feldman 7781e8db6e2SBrian Feldman /* Send rename request */ 779ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 780d4af9e69SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_POSIX_RENAME)) { 781d4af9e69SDag-Erling Smørgrav buffer_put_char(&msg, SSH2_FXP_EXTENDED); 782d4af9e69SDag-Erling Smørgrav buffer_put_int(&msg, id); 783d4af9e69SDag-Erling Smørgrav buffer_put_cstring(&msg, "posix-rename@openssh.com"); 784d4af9e69SDag-Erling Smørgrav } else { 7851e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_RENAME); 7861e8db6e2SBrian Feldman buffer_put_int(&msg, id); 787d4af9e69SDag-Erling Smørgrav } 7881e8db6e2SBrian Feldman buffer_put_cstring(&msg, oldpath); 7891e8db6e2SBrian Feldman buffer_put_cstring(&msg, newpath); 7904a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 791d4af9e69SDag-Erling Smørgrav debug3("Sent message %s \"%s\" -> \"%s\"", 792d4af9e69SDag-Erling Smørgrav (conn->exts & SFTP_EXT_POSIX_RENAME) ? "posix-rename@openssh.com" : 793d4af9e69SDag-Erling Smørgrav "SSH2_FXP_RENAME", oldpath, newpath); 7941e8db6e2SBrian Feldman buffer_free(&msg); 7951e8db6e2SBrian Feldman 7964a421b63SDag-Erling Smørgrav status = get_status(conn, id); 7971e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 798ae1f160dSDag-Erling Smørgrav error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, 799ae1f160dSDag-Erling Smørgrav newpath, fx2txt(status)); 8001e8db6e2SBrian Feldman 8011e8db6e2SBrian Feldman return(status); 8021e8db6e2SBrian Feldman } 8031e8db6e2SBrian Feldman 8041e8db6e2SBrian Feldman int 8054a421b63SDag-Erling Smørgrav do_hardlink(struct sftp_conn *conn, char *oldpath, char *newpath) 8064a421b63SDag-Erling Smørgrav { 8074a421b63SDag-Erling Smørgrav Buffer msg; 8084a421b63SDag-Erling Smørgrav u_int status, id; 8094a421b63SDag-Erling Smørgrav 8104a421b63SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_HARDLINK) == 0) { 8114a421b63SDag-Erling Smørgrav error("Server does not support hardlink@openssh.com extension"); 8124a421b63SDag-Erling Smørgrav return -1; 8134a421b63SDag-Erling Smørgrav } 8144a421b63SDag-Erling Smørgrav 815*462c32cbSDag-Erling Smørgrav buffer_init(&msg); 816*462c32cbSDag-Erling Smørgrav 817*462c32cbSDag-Erling Smørgrav /* Send link request */ 818*462c32cbSDag-Erling Smørgrav id = conn->msg_id++; 8194a421b63SDag-Erling Smørgrav buffer_put_char(&msg, SSH2_FXP_EXTENDED); 8204a421b63SDag-Erling Smørgrav buffer_put_int(&msg, id); 8214a421b63SDag-Erling Smørgrav buffer_put_cstring(&msg, "hardlink@openssh.com"); 8224a421b63SDag-Erling Smørgrav buffer_put_cstring(&msg, oldpath); 8234a421b63SDag-Erling Smørgrav buffer_put_cstring(&msg, newpath); 8244a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 8254a421b63SDag-Erling Smørgrav debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"", 8264a421b63SDag-Erling Smørgrav oldpath, newpath); 8274a421b63SDag-Erling Smørgrav buffer_free(&msg); 8284a421b63SDag-Erling Smørgrav 8294a421b63SDag-Erling Smørgrav status = get_status(conn, id); 8304a421b63SDag-Erling Smørgrav if (status != SSH2_FX_OK) 8314a421b63SDag-Erling Smørgrav error("Couldn't link file \"%s\" to \"%s\": %s", oldpath, 8324a421b63SDag-Erling Smørgrav newpath, fx2txt(status)); 8334a421b63SDag-Erling Smørgrav 8344a421b63SDag-Erling Smørgrav return(status); 8354a421b63SDag-Erling Smørgrav } 8364a421b63SDag-Erling Smørgrav 8374a421b63SDag-Erling Smørgrav int 838ae1f160dSDag-Erling Smørgrav do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath) 8391e8db6e2SBrian Feldman { 8401e8db6e2SBrian Feldman Buffer msg; 8411e8db6e2SBrian Feldman u_int status, id; 8421e8db6e2SBrian Feldman 843ae1f160dSDag-Erling Smørgrav if (conn->version < 3) { 844ae1f160dSDag-Erling Smørgrav error("This server does not support the symlink operation"); 845ae1f160dSDag-Erling Smørgrav return(SSH2_FX_OP_UNSUPPORTED); 846ae1f160dSDag-Erling Smørgrav } 847ae1f160dSDag-Erling Smørgrav 8481e8db6e2SBrian Feldman buffer_init(&msg); 8491e8db6e2SBrian Feldman 850d74d50a8SDag-Erling Smørgrav /* Send symlink request */ 851ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 8521e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_SYMLINK); 8531e8db6e2SBrian Feldman buffer_put_int(&msg, id); 8541e8db6e2SBrian Feldman buffer_put_cstring(&msg, oldpath); 8551e8db6e2SBrian Feldman buffer_put_cstring(&msg, newpath); 8564a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 8571e8db6e2SBrian Feldman debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, 8581e8db6e2SBrian Feldman newpath); 8591e8db6e2SBrian Feldman buffer_free(&msg); 8601e8db6e2SBrian Feldman 8614a421b63SDag-Erling Smørgrav status = get_status(conn, id); 8621e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 863d0c8c0bcSDag-Erling Smørgrav error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath, 864ae1f160dSDag-Erling Smørgrav newpath, fx2txt(status)); 8651e8db6e2SBrian Feldman 8661e8db6e2SBrian Feldman return(status); 8671e8db6e2SBrian Feldman } 8681e8db6e2SBrian Feldman 869d4af9e69SDag-Erling Smørgrav #ifdef notyet 8701e8db6e2SBrian Feldman char * 871ae1f160dSDag-Erling Smørgrav do_readlink(struct sftp_conn *conn, char *path) 8721e8db6e2SBrian Feldman { 8731e8db6e2SBrian Feldman Buffer msg; 8741e8db6e2SBrian Feldman u_int type, expected_id, count, id; 8751e8db6e2SBrian Feldman char *filename, *longname; 8761e8db6e2SBrian Feldman Attrib *a; 8771e8db6e2SBrian Feldman 878ae1f160dSDag-Erling Smørgrav expected_id = id = conn->msg_id++; 8794a421b63SDag-Erling Smørgrav send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path)); 8801e8db6e2SBrian Feldman 8811e8db6e2SBrian Feldman buffer_init(&msg); 8821e8db6e2SBrian Feldman 8834a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 8841e8db6e2SBrian Feldman type = buffer_get_char(&msg); 8851e8db6e2SBrian Feldman id = buffer_get_int(&msg); 8861e8db6e2SBrian Feldman 8871e8db6e2SBrian Feldman if (id != expected_id) 888ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 8891e8db6e2SBrian Feldman 8901e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 8911e8db6e2SBrian Feldman u_int status = buffer_get_int(&msg); 8921e8db6e2SBrian Feldman 8931e8db6e2SBrian Feldman error("Couldn't readlink: %s", fx2txt(status)); 894*462c32cbSDag-Erling Smørgrav buffer_free(&msg); 8951e8db6e2SBrian Feldman return(NULL); 8961e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 897ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 8981e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 8991e8db6e2SBrian Feldman 9001e8db6e2SBrian Feldman count = buffer_get_int(&msg); 9011e8db6e2SBrian Feldman if (count != 1) 9021e8db6e2SBrian Feldman fatal("Got multiple names (%d) from SSH_FXP_READLINK", count); 9031e8db6e2SBrian Feldman 9041e8db6e2SBrian Feldman filename = buffer_get_string(&msg, NULL); 9051e8db6e2SBrian Feldman longname = buffer_get_string(&msg, NULL); 9061e8db6e2SBrian Feldman a = decode_attrib(&msg); 9071e8db6e2SBrian Feldman 9081e8db6e2SBrian Feldman debug3("SSH_FXP_READLINK %s -> %s", path, filename); 9091e8db6e2SBrian Feldman 9101e8db6e2SBrian Feldman xfree(longname); 9111e8db6e2SBrian Feldman 9121e8db6e2SBrian Feldman buffer_free(&msg); 9131e8db6e2SBrian Feldman 9141e8db6e2SBrian Feldman return(filename); 9151e8db6e2SBrian Feldman } 916d4af9e69SDag-Erling Smørgrav #endif 917d4af9e69SDag-Erling Smørgrav 918d4af9e69SDag-Erling Smørgrav int 919d4af9e69SDag-Erling Smørgrav do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, 920d4af9e69SDag-Erling Smørgrav int quiet) 921d4af9e69SDag-Erling Smørgrav { 922d4af9e69SDag-Erling Smørgrav Buffer msg; 923d4af9e69SDag-Erling Smørgrav u_int id; 924d4af9e69SDag-Erling Smørgrav 925d4af9e69SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_STATVFS) == 0) { 926d4af9e69SDag-Erling Smørgrav error("Server does not support statvfs@openssh.com extension"); 927d4af9e69SDag-Erling Smørgrav return -1; 928d4af9e69SDag-Erling Smørgrav } 929d4af9e69SDag-Erling Smørgrav 930d4af9e69SDag-Erling Smørgrav id = conn->msg_id++; 931d4af9e69SDag-Erling Smørgrav 932d4af9e69SDag-Erling Smørgrav buffer_init(&msg); 933d4af9e69SDag-Erling Smørgrav buffer_clear(&msg); 934d4af9e69SDag-Erling Smørgrav buffer_put_char(&msg, SSH2_FXP_EXTENDED); 935d4af9e69SDag-Erling Smørgrav buffer_put_int(&msg, id); 936d4af9e69SDag-Erling Smørgrav buffer_put_cstring(&msg, "statvfs@openssh.com"); 937d4af9e69SDag-Erling Smørgrav buffer_put_cstring(&msg, path); 9384a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 939d4af9e69SDag-Erling Smørgrav buffer_free(&msg); 940d4af9e69SDag-Erling Smørgrav 9414a421b63SDag-Erling Smørgrav return get_decode_statvfs(conn, st, id, quiet); 942d4af9e69SDag-Erling Smørgrav } 943d4af9e69SDag-Erling Smørgrav 944d4af9e69SDag-Erling Smørgrav #ifdef notyet 945d4af9e69SDag-Erling Smørgrav int 946d4af9e69SDag-Erling Smørgrav do_fstatvfs(struct sftp_conn *conn, const char *handle, u_int handle_len, 947d4af9e69SDag-Erling Smørgrav struct sftp_statvfs *st, int quiet) 948d4af9e69SDag-Erling Smørgrav { 949d4af9e69SDag-Erling Smørgrav Buffer msg; 950d4af9e69SDag-Erling Smørgrav u_int id; 951d4af9e69SDag-Erling Smørgrav 952d4af9e69SDag-Erling Smørgrav if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) { 953d4af9e69SDag-Erling Smørgrav error("Server does not support fstatvfs@openssh.com extension"); 954d4af9e69SDag-Erling Smørgrav return -1; 955d4af9e69SDag-Erling Smørgrav } 956d4af9e69SDag-Erling Smørgrav 957d4af9e69SDag-Erling Smørgrav id = conn->msg_id++; 958d4af9e69SDag-Erling Smørgrav 959d4af9e69SDag-Erling Smørgrav buffer_init(&msg); 960d4af9e69SDag-Erling Smørgrav buffer_clear(&msg); 961d4af9e69SDag-Erling Smørgrav buffer_put_char(&msg, SSH2_FXP_EXTENDED); 962d4af9e69SDag-Erling Smørgrav buffer_put_int(&msg, id); 963d4af9e69SDag-Erling Smørgrav buffer_put_cstring(&msg, "fstatvfs@openssh.com"); 964d4af9e69SDag-Erling Smørgrav buffer_put_string(&msg, handle, handle_len); 9654a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 966d4af9e69SDag-Erling Smørgrav buffer_free(&msg); 967d4af9e69SDag-Erling Smørgrav 9684a421b63SDag-Erling Smørgrav return get_decode_statvfs(conn, st, id, quiet); 969d4af9e69SDag-Erling Smørgrav } 970d4af9e69SDag-Erling Smørgrav #endif 9711e8db6e2SBrian Feldman 972ae1f160dSDag-Erling Smørgrav static void 9734a421b63SDag-Erling Smørgrav send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, 9744a421b63SDag-Erling Smørgrav u_int len, char *handle, u_int handle_len) 975ae1f160dSDag-Erling Smørgrav { 976ae1f160dSDag-Erling Smørgrav Buffer msg; 977ae1f160dSDag-Erling Smørgrav 978ae1f160dSDag-Erling Smørgrav buffer_init(&msg); 979ae1f160dSDag-Erling Smørgrav buffer_clear(&msg); 980ae1f160dSDag-Erling Smørgrav buffer_put_char(&msg, SSH2_FXP_READ); 981ae1f160dSDag-Erling Smørgrav buffer_put_int(&msg, id); 982ae1f160dSDag-Erling Smørgrav buffer_put_string(&msg, handle, handle_len); 983ae1f160dSDag-Erling Smørgrav buffer_put_int64(&msg, offset); 984ae1f160dSDag-Erling Smørgrav buffer_put_int(&msg, len); 9854a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 986ae1f160dSDag-Erling Smørgrav buffer_free(&msg); 987ae1f160dSDag-Erling Smørgrav } 988ae1f160dSDag-Erling Smørgrav 9891e8db6e2SBrian Feldman int 990ae1f160dSDag-Erling Smørgrav do_download(struct sftp_conn *conn, char *remote_path, char *local_path, 991b15c8340SDag-Erling Smørgrav Attrib *a, int pflag) 9921e8db6e2SBrian Feldman { 993b15c8340SDag-Erling Smørgrav Attrib junk; 994ae1f160dSDag-Erling Smørgrav Buffer msg; 995ae1f160dSDag-Erling Smørgrav char *handle; 996043840dfSDag-Erling Smørgrav int local_fd, status = 0, write_error; 997ae1f160dSDag-Erling Smørgrav int read_error, write_errno; 998ae1f160dSDag-Erling Smørgrav u_int64_t offset, size; 999043840dfSDag-Erling Smørgrav u_int handle_len, mode, type, id, buflen, num_req, max_req; 1000d0c8c0bcSDag-Erling Smørgrav off_t progress_counter; 1001ae1f160dSDag-Erling Smørgrav struct request { 1002ae1f160dSDag-Erling Smørgrav u_int id; 1003ae1f160dSDag-Erling Smørgrav u_int len; 1004ae1f160dSDag-Erling Smørgrav u_int64_t offset; 1005ae1f160dSDag-Erling Smørgrav TAILQ_ENTRY(request) tq; 1006ae1f160dSDag-Erling Smørgrav }; 1007ae1f160dSDag-Erling Smørgrav TAILQ_HEAD(reqhead, request) requests; 1008ae1f160dSDag-Erling Smørgrav struct request *req; 10091e8db6e2SBrian Feldman 1010ae1f160dSDag-Erling Smørgrav TAILQ_INIT(&requests); 1011ae1f160dSDag-Erling Smørgrav 1012b15c8340SDag-Erling Smørgrav if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL) 1013b15c8340SDag-Erling Smørgrav return -1; 10141e8db6e2SBrian Feldman 1015d4af9e69SDag-Erling Smørgrav /* Do not preserve set[ug]id here, as we do not preserve ownership */ 10161e8db6e2SBrian Feldman if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 1017d0c8c0bcSDag-Erling Smørgrav mode = a->perm & 0777; 10181e8db6e2SBrian Feldman else 10191e8db6e2SBrian Feldman mode = 0666; 10201e8db6e2SBrian Feldman 10211e8db6e2SBrian Feldman if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 1022d0c8c0bcSDag-Erling Smørgrav (!S_ISREG(a->perm))) { 1023d0c8c0bcSDag-Erling Smørgrav error("Cannot download non-regular file: %s", remote_path); 10241e8db6e2SBrian Feldman return(-1); 10251e8db6e2SBrian Feldman } 10261e8db6e2SBrian Feldman 1027ae1f160dSDag-Erling Smørgrav if (a->flags & SSH2_FILEXFER_ATTR_SIZE) 1028ae1f160dSDag-Erling Smørgrav size = a->size; 1029ae1f160dSDag-Erling Smørgrav else 1030ae1f160dSDag-Erling Smørgrav size = 0; 10311e8db6e2SBrian Feldman 1032ae1f160dSDag-Erling Smørgrav buflen = conn->transfer_buflen; 10331e8db6e2SBrian Feldman buffer_init(&msg); 10341e8db6e2SBrian Feldman 10351e8db6e2SBrian Feldman /* Send open request */ 1036ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 10371e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_OPEN); 10381e8db6e2SBrian Feldman buffer_put_int(&msg, id); 10391e8db6e2SBrian Feldman buffer_put_cstring(&msg, remote_path); 10401e8db6e2SBrian Feldman buffer_put_int(&msg, SSH2_FXF_READ); 10411e8db6e2SBrian Feldman attrib_clear(&junk); /* Send empty attributes */ 10421e8db6e2SBrian Feldman encode_attrib(&msg, &junk); 10434a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 1044ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 10451e8db6e2SBrian Feldman 10464a421b63SDag-Erling Smørgrav handle = get_handle(conn, id, &handle_len, 1047b15c8340SDag-Erling Smørgrav "remote open(\"%s\")", remote_path); 10481e8db6e2SBrian Feldman if (handle == NULL) { 10491e8db6e2SBrian Feldman buffer_free(&msg); 1050ae1f160dSDag-Erling Smørgrav return(-1); 1051ae1f160dSDag-Erling Smørgrav } 1052ae1f160dSDag-Erling Smørgrav 1053d0c8c0bcSDag-Erling Smørgrav local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, 1054d0c8c0bcSDag-Erling Smørgrav mode | S_IWRITE); 1055ae1f160dSDag-Erling Smørgrav if (local_fd == -1) { 1056ae1f160dSDag-Erling Smørgrav error("Couldn't open local file \"%s\" for writing: %s", 1057ae1f160dSDag-Erling Smørgrav local_path, strerror(errno)); 1058d4af9e69SDag-Erling Smørgrav do_close(conn, handle, handle_len); 1059ae1f160dSDag-Erling Smørgrav buffer_free(&msg); 1060ae1f160dSDag-Erling Smørgrav xfree(handle); 10611e8db6e2SBrian Feldman return(-1); 10621e8db6e2SBrian Feldman } 10631e8db6e2SBrian Feldman 10641e8db6e2SBrian Feldman /* Read from remote and write to local */ 1065ae1f160dSDag-Erling Smørgrav write_error = read_error = write_errno = num_req = offset = 0; 1066ae1f160dSDag-Erling Smørgrav max_req = 1; 1067d0c8c0bcSDag-Erling Smørgrav progress_counter = 0; 1068d0c8c0bcSDag-Erling Smørgrav 106952028650SDag-Erling Smørgrav if (showprogress && size != 0) 107052028650SDag-Erling Smørgrav start_progress_meter(remote_path, size, &progress_counter); 1071d0c8c0bcSDag-Erling Smørgrav 1072ae1f160dSDag-Erling Smørgrav while (num_req > 0 || max_req > 0) { 10731e8db6e2SBrian Feldman char *data; 1074ae1f160dSDag-Erling Smørgrav u_int len; 10751e8db6e2SBrian Feldman 1076d74d50a8SDag-Erling Smørgrav /* 1077d74d50a8SDag-Erling Smørgrav * Simulate EOF on interrupt: stop sending new requests and 1078d74d50a8SDag-Erling Smørgrav * allow outstanding requests to drain gracefully 1079d74d50a8SDag-Erling Smørgrav */ 1080d74d50a8SDag-Erling Smørgrav if (interrupted) { 1081d74d50a8SDag-Erling Smørgrav if (num_req == 0) /* If we haven't started yet... */ 1082d74d50a8SDag-Erling Smørgrav break; 1083d74d50a8SDag-Erling Smørgrav max_req = 0; 1084d74d50a8SDag-Erling Smørgrav } 1085d74d50a8SDag-Erling Smørgrav 1086ae1f160dSDag-Erling Smørgrav /* Send some more requests */ 1087ae1f160dSDag-Erling Smørgrav while (num_req < max_req) { 1088ae1f160dSDag-Erling Smørgrav debug3("Request range %llu -> %llu (%d/%d)", 1089545d5ecaSDag-Erling Smørgrav (unsigned long long)offset, 1090545d5ecaSDag-Erling Smørgrav (unsigned long long)offset + buflen - 1, 1091545d5ecaSDag-Erling Smørgrav num_req, max_req); 1092ae1f160dSDag-Erling Smørgrav req = xmalloc(sizeof(*req)); 1093ae1f160dSDag-Erling Smørgrav req->id = conn->msg_id++; 1094ae1f160dSDag-Erling Smørgrav req->len = buflen; 1095ae1f160dSDag-Erling Smørgrav req->offset = offset; 1096ae1f160dSDag-Erling Smørgrav offset += buflen; 1097ae1f160dSDag-Erling Smørgrav num_req++; 1098ae1f160dSDag-Erling Smørgrav TAILQ_INSERT_TAIL(&requests, req, tq); 10994a421b63SDag-Erling Smørgrav send_read_request(conn, req->id, req->offset, 1100ae1f160dSDag-Erling Smørgrav req->len, handle, handle_len); 1101ae1f160dSDag-Erling Smørgrav } 11021e8db6e2SBrian Feldman 11031e8db6e2SBrian Feldman buffer_clear(&msg); 11044a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 11051e8db6e2SBrian Feldman type = buffer_get_char(&msg); 11061e8db6e2SBrian Feldman id = buffer_get_int(&msg); 1107ee21a45fSDag-Erling Smørgrav debug3("Received reply T:%u I:%u R:%d", type, id, max_req); 11081e8db6e2SBrian Feldman 1109ae1f160dSDag-Erling Smørgrav /* Find the request in our queue */ 1110ae1f160dSDag-Erling Smørgrav for (req = TAILQ_FIRST(&requests); 1111ae1f160dSDag-Erling Smørgrav req != NULL && req->id != id; 1112ae1f160dSDag-Erling Smørgrav req = TAILQ_NEXT(req, tq)) 1113ae1f160dSDag-Erling Smørgrav ; 1114ae1f160dSDag-Erling Smørgrav if (req == NULL) 1115ae1f160dSDag-Erling Smørgrav fatal("Unexpected reply %u", id); 1116ae1f160dSDag-Erling Smørgrav 1117ae1f160dSDag-Erling Smørgrav switch (type) { 1118ae1f160dSDag-Erling Smørgrav case SSH2_FXP_STATUS: 1119ae1f160dSDag-Erling Smørgrav status = buffer_get_int(&msg); 1120ae1f160dSDag-Erling Smørgrav if (status != SSH2_FX_EOF) 1121ae1f160dSDag-Erling Smørgrav read_error = 1; 1122ae1f160dSDag-Erling Smørgrav max_req = 0; 1123ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&requests, req, tq); 1124ae1f160dSDag-Erling Smørgrav xfree(req); 1125ae1f160dSDag-Erling Smørgrav num_req--; 11261e8db6e2SBrian Feldman break; 1127ae1f160dSDag-Erling Smørgrav case SSH2_FXP_DATA: 1128ae1f160dSDag-Erling Smørgrav data = buffer_get_string(&msg, &len); 1129545d5ecaSDag-Erling Smørgrav debug3("Received data %llu -> %llu", 1130545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset, 1131545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + len - 1); 1132ae1f160dSDag-Erling Smørgrav if (len > req->len) 1133ae1f160dSDag-Erling Smørgrav fatal("Received more data than asked for " 1134ee21a45fSDag-Erling Smørgrav "%u > %u", len, req->len); 1135ae1f160dSDag-Erling Smørgrav if ((lseek(local_fd, req->offset, SEEK_SET) == -1 || 1136d95e11bfSDag-Erling Smørgrav atomicio(vwrite, local_fd, data, len) != len) && 1137ae1f160dSDag-Erling Smørgrav !write_error) { 1138ae1f160dSDag-Erling Smørgrav write_errno = errno; 1139ae1f160dSDag-Erling Smørgrav write_error = 1; 1140ae1f160dSDag-Erling Smørgrav max_req = 0; 11411e8db6e2SBrian Feldman } 1142d0c8c0bcSDag-Erling Smørgrav progress_counter += len; 1143ae1f160dSDag-Erling Smørgrav xfree(data); 1144ae1f160dSDag-Erling Smørgrav 1145ae1f160dSDag-Erling Smørgrav if (len == req->len) { 1146ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&requests, req, tq); 1147ae1f160dSDag-Erling Smørgrav xfree(req); 1148ae1f160dSDag-Erling Smørgrav num_req--; 1149ae1f160dSDag-Erling Smørgrav } else { 1150ae1f160dSDag-Erling Smørgrav /* Resend the request for the missing data */ 1151ae1f160dSDag-Erling Smørgrav debug3("Short data block, re-requesting " 1152545d5ecaSDag-Erling Smørgrav "%llu -> %llu (%2d)", 1153545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + len, 1154545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + 1155545d5ecaSDag-Erling Smørgrav req->len - 1, num_req); 1156ae1f160dSDag-Erling Smørgrav req->id = conn->msg_id++; 1157ae1f160dSDag-Erling Smørgrav req->len -= len; 1158ae1f160dSDag-Erling Smørgrav req->offset += len; 11594a421b63SDag-Erling Smørgrav send_read_request(conn, req->id, 1160ae1f160dSDag-Erling Smørgrav req->offset, req->len, handle, handle_len); 1161ae1f160dSDag-Erling Smørgrav /* Reduce the request size */ 1162ae1f160dSDag-Erling Smørgrav if (len < buflen) 1163ae1f160dSDag-Erling Smørgrav buflen = MAX(MIN_READ_SIZE, len); 1164ae1f160dSDag-Erling Smørgrav } 1165ae1f160dSDag-Erling Smørgrav if (max_req > 0) { /* max_req = 0 iff EOF received */ 1166ae1f160dSDag-Erling Smørgrav if (size > 0 && offset > size) { 1167ae1f160dSDag-Erling Smørgrav /* Only one request at a time 1168ae1f160dSDag-Erling Smørgrav * after the expected EOF */ 1169ae1f160dSDag-Erling Smørgrav debug3("Finish at %llu (%2d)", 1170545d5ecaSDag-Erling Smørgrav (unsigned long long)offset, 1171545d5ecaSDag-Erling Smørgrav num_req); 1172ae1f160dSDag-Erling Smørgrav max_req = 1; 1173d74d50a8SDag-Erling Smørgrav } else if (max_req <= conn->num_requests) { 1174ae1f160dSDag-Erling Smørgrav ++max_req; 1175ae1f160dSDag-Erling Smørgrav } 1176ae1f160dSDag-Erling Smørgrav } 1177ae1f160dSDag-Erling Smørgrav break; 1178ae1f160dSDag-Erling Smørgrav default: 1179ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", 11801e8db6e2SBrian Feldman SSH2_FXP_DATA, type); 11811e8db6e2SBrian Feldman } 1182ae1f160dSDag-Erling Smørgrav } 11831e8db6e2SBrian Feldman 1184d0c8c0bcSDag-Erling Smørgrav if (showprogress && size) 1185d0c8c0bcSDag-Erling Smørgrav stop_progress_meter(); 1186d0c8c0bcSDag-Erling Smørgrav 1187ae1f160dSDag-Erling Smørgrav /* Sanity check */ 1188ae1f160dSDag-Erling Smørgrav if (TAILQ_FIRST(&requests) != NULL) 1189ae1f160dSDag-Erling Smørgrav fatal("Transfer complete, but requests still in queue"); 11901e8db6e2SBrian Feldman 1191ae1f160dSDag-Erling Smørgrav if (read_error) { 1192ae1f160dSDag-Erling Smørgrav error("Couldn't read from remote file \"%s\" : %s", 1193ae1f160dSDag-Erling Smørgrav remote_path, fx2txt(status)); 1194ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 1195ae1f160dSDag-Erling Smørgrav } else if (write_error) { 11961e8db6e2SBrian Feldman error("Couldn't write to \"%s\": %s", local_path, 1197ae1f160dSDag-Erling Smørgrav strerror(write_errno)); 11981e8db6e2SBrian Feldman status = -1; 1199ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 1200ae1f160dSDag-Erling Smørgrav } else { 1201ae1f160dSDag-Erling Smørgrav status = do_close(conn, handle, handle_len); 12021e8db6e2SBrian Feldman 12031e8db6e2SBrian Feldman /* Override umask and utimes if asked */ 120483d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD 12051e8db6e2SBrian Feldman if (pflag && fchmod(local_fd, mode) == -1) 120683d2307dSDag-Erling Smørgrav #else 120783d2307dSDag-Erling Smørgrav if (pflag && chmod(local_path, mode) == -1) 120883d2307dSDag-Erling Smørgrav #endif /* HAVE_FCHMOD */ 12091e8db6e2SBrian Feldman error("Couldn't set mode on \"%s\": %s", local_path, 12101e8db6e2SBrian Feldman strerror(errno)); 12111e8db6e2SBrian Feldman if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { 12121e8db6e2SBrian Feldman struct timeval tv[2]; 12131e8db6e2SBrian Feldman tv[0].tv_sec = a->atime; 12141e8db6e2SBrian Feldman tv[1].tv_sec = a->mtime; 12151e8db6e2SBrian Feldman tv[0].tv_usec = tv[1].tv_usec = 0; 12161e8db6e2SBrian Feldman if (utimes(local_path, tv) == -1) 1217ae1f160dSDag-Erling Smørgrav error("Can't set times on \"%s\": %s", 1218ae1f160dSDag-Erling Smørgrav local_path, strerror(errno)); 12191e8db6e2SBrian Feldman } 1220ae1f160dSDag-Erling Smørgrav } 12211e8db6e2SBrian Feldman close(local_fd); 12221e8db6e2SBrian Feldman buffer_free(&msg); 12231e8db6e2SBrian Feldman xfree(handle); 1224ae1f160dSDag-Erling Smørgrav 1225ae1f160dSDag-Erling Smørgrav return(status); 12261e8db6e2SBrian Feldman } 12271e8db6e2SBrian Feldman 1228b15c8340SDag-Erling Smørgrav static int 1229b15c8340SDag-Erling Smørgrav download_dir_internal(struct sftp_conn *conn, char *src, char *dst, 1230b15c8340SDag-Erling Smørgrav Attrib *dirattrib, int pflag, int printflag, int depth) 1231b15c8340SDag-Erling Smørgrav { 1232b15c8340SDag-Erling Smørgrav int i, ret = 0; 1233b15c8340SDag-Erling Smørgrav SFTP_DIRENT **dir_entries; 1234b15c8340SDag-Erling Smørgrav char *filename, *new_src, *new_dst; 1235b15c8340SDag-Erling Smørgrav mode_t mode = 0777; 1236b15c8340SDag-Erling Smørgrav 1237b15c8340SDag-Erling Smørgrav if (depth >= MAX_DIR_DEPTH) { 1238b15c8340SDag-Erling Smørgrav error("Maximum directory depth exceeded: %d levels", depth); 1239b15c8340SDag-Erling Smørgrav return -1; 1240b15c8340SDag-Erling Smørgrav } 1241b15c8340SDag-Erling Smørgrav 1242b15c8340SDag-Erling Smørgrav if (dirattrib == NULL && 1243b15c8340SDag-Erling Smørgrav (dirattrib = do_stat(conn, src, 1)) == NULL) { 1244b15c8340SDag-Erling Smørgrav error("Unable to stat remote directory \"%s\"", src); 1245b15c8340SDag-Erling Smørgrav return -1; 1246b15c8340SDag-Erling Smørgrav } 1247b15c8340SDag-Erling Smørgrav if (!S_ISDIR(dirattrib->perm)) { 1248b15c8340SDag-Erling Smørgrav error("\"%s\" is not a directory", src); 1249b15c8340SDag-Erling Smørgrav return -1; 1250b15c8340SDag-Erling Smørgrav } 1251b15c8340SDag-Erling Smørgrav if (printflag) 1252b15c8340SDag-Erling Smørgrav printf("Retrieving %s\n", src); 1253b15c8340SDag-Erling Smørgrav 1254b15c8340SDag-Erling Smørgrav if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 1255b15c8340SDag-Erling Smørgrav mode = dirattrib->perm & 01777; 1256b15c8340SDag-Erling Smørgrav else { 1257b15c8340SDag-Erling Smørgrav debug("Server did not send permissions for " 1258b15c8340SDag-Erling Smørgrav "directory \"%s\"", dst); 1259b15c8340SDag-Erling Smørgrav } 1260b15c8340SDag-Erling Smørgrav 1261b15c8340SDag-Erling Smørgrav if (mkdir(dst, mode) == -1 && errno != EEXIST) { 1262b15c8340SDag-Erling Smørgrav error("mkdir %s: %s", dst, strerror(errno)); 1263b15c8340SDag-Erling Smørgrav return -1; 1264b15c8340SDag-Erling Smørgrav } 1265b15c8340SDag-Erling Smørgrav 1266b15c8340SDag-Erling Smørgrav if (do_readdir(conn, src, &dir_entries) == -1) { 1267b15c8340SDag-Erling Smørgrav error("%s: Failed to get directory contents", src); 1268b15c8340SDag-Erling Smørgrav return -1; 1269b15c8340SDag-Erling Smørgrav } 1270b15c8340SDag-Erling Smørgrav 1271b15c8340SDag-Erling Smørgrav for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { 1272b15c8340SDag-Erling Smørgrav filename = dir_entries[i]->filename; 1273b15c8340SDag-Erling Smørgrav 1274b15c8340SDag-Erling Smørgrav new_dst = path_append(dst, filename); 1275b15c8340SDag-Erling Smørgrav new_src = path_append(src, filename); 1276b15c8340SDag-Erling Smørgrav 1277b15c8340SDag-Erling Smørgrav if (S_ISDIR(dir_entries[i]->a.perm)) { 1278b15c8340SDag-Erling Smørgrav if (strcmp(filename, ".") == 0 || 1279b15c8340SDag-Erling Smørgrav strcmp(filename, "..") == 0) 1280b15c8340SDag-Erling Smørgrav continue; 1281b15c8340SDag-Erling Smørgrav if (download_dir_internal(conn, new_src, new_dst, 1282b15c8340SDag-Erling Smørgrav &(dir_entries[i]->a), pflag, printflag, 1283b15c8340SDag-Erling Smørgrav depth + 1) == -1) 1284b15c8340SDag-Erling Smørgrav ret = -1; 1285b15c8340SDag-Erling Smørgrav } else if (S_ISREG(dir_entries[i]->a.perm) ) { 1286b15c8340SDag-Erling Smørgrav if (do_download(conn, new_src, new_dst, 1287b15c8340SDag-Erling Smørgrav &(dir_entries[i]->a), pflag) == -1) { 1288b15c8340SDag-Erling Smørgrav error("Download of file %s to %s failed", 1289b15c8340SDag-Erling Smørgrav new_src, new_dst); 1290b15c8340SDag-Erling Smørgrav ret = -1; 1291b15c8340SDag-Erling Smørgrav } 1292b15c8340SDag-Erling Smørgrav } else 1293b15c8340SDag-Erling Smørgrav logit("%s: not a regular file\n", new_src); 1294b15c8340SDag-Erling Smørgrav 1295b15c8340SDag-Erling Smørgrav xfree(new_dst); 1296b15c8340SDag-Erling Smørgrav xfree(new_src); 1297b15c8340SDag-Erling Smørgrav } 1298b15c8340SDag-Erling Smørgrav 1299b15c8340SDag-Erling Smørgrav if (pflag) { 1300b15c8340SDag-Erling Smørgrav if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 1301b15c8340SDag-Erling Smørgrav struct timeval tv[2]; 1302b15c8340SDag-Erling Smørgrav tv[0].tv_sec = dirattrib->atime; 1303b15c8340SDag-Erling Smørgrav tv[1].tv_sec = dirattrib->mtime; 1304b15c8340SDag-Erling Smørgrav tv[0].tv_usec = tv[1].tv_usec = 0; 1305b15c8340SDag-Erling Smørgrav if (utimes(dst, tv) == -1) 1306b15c8340SDag-Erling Smørgrav error("Can't set times on \"%s\": %s", 1307b15c8340SDag-Erling Smørgrav dst, strerror(errno)); 1308b15c8340SDag-Erling Smørgrav } else 1309b15c8340SDag-Erling Smørgrav debug("Server did not send times for directory " 1310b15c8340SDag-Erling Smørgrav "\"%s\"", dst); 1311b15c8340SDag-Erling Smørgrav } 1312b15c8340SDag-Erling Smørgrav 1313b15c8340SDag-Erling Smørgrav free_sftp_dirents(dir_entries); 1314b15c8340SDag-Erling Smørgrav 1315b15c8340SDag-Erling Smørgrav return ret; 1316b15c8340SDag-Erling Smørgrav } 1317b15c8340SDag-Erling Smørgrav 1318b15c8340SDag-Erling Smørgrav int 1319b15c8340SDag-Erling Smørgrav download_dir(struct sftp_conn *conn, char *src, char *dst, 1320b15c8340SDag-Erling Smørgrav Attrib *dirattrib, int pflag, int printflag) 1321b15c8340SDag-Erling Smørgrav { 1322b15c8340SDag-Erling Smørgrav char *src_canon; 1323b15c8340SDag-Erling Smørgrav int ret; 1324b15c8340SDag-Erling Smørgrav 1325b15c8340SDag-Erling Smørgrav if ((src_canon = do_realpath(conn, src)) == NULL) { 1326b15c8340SDag-Erling Smørgrav error("Unable to canonicalise path \"%s\"", src); 1327b15c8340SDag-Erling Smørgrav return -1; 1328b15c8340SDag-Erling Smørgrav } 1329b15c8340SDag-Erling Smørgrav 1330b15c8340SDag-Erling Smørgrav ret = download_dir_internal(conn, src_canon, dst, 1331b15c8340SDag-Erling Smørgrav dirattrib, pflag, printflag, 0); 1332b15c8340SDag-Erling Smørgrav xfree(src_canon); 1333b15c8340SDag-Erling Smørgrav return ret; 1334b15c8340SDag-Erling Smørgrav } 1335b15c8340SDag-Erling Smørgrav 13361e8db6e2SBrian Feldman int 1337ae1f160dSDag-Erling Smørgrav do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, 13381e8db6e2SBrian Feldman int pflag) 13391e8db6e2SBrian Feldman { 1340d4af9e69SDag-Erling Smørgrav int local_fd; 1341d4af9e69SDag-Erling Smørgrav int status = SSH2_FX_OK; 1342ae1f160dSDag-Erling Smørgrav u_int handle_len, id, type; 1343d4af9e69SDag-Erling Smørgrav off_t offset; 1344ae1f160dSDag-Erling Smørgrav char *handle, *data; 13451e8db6e2SBrian Feldman Buffer msg; 13461e8db6e2SBrian Feldman struct stat sb; 13471e8db6e2SBrian Feldman Attrib a; 1348ae1f160dSDag-Erling Smørgrav u_int32_t startid; 1349ae1f160dSDag-Erling Smørgrav u_int32_t ackid; 1350ae1f160dSDag-Erling Smørgrav struct outstanding_ack { 1351ae1f160dSDag-Erling Smørgrav u_int id; 1352ae1f160dSDag-Erling Smørgrav u_int len; 1353d4af9e69SDag-Erling Smørgrav off_t offset; 1354ae1f160dSDag-Erling Smørgrav TAILQ_ENTRY(outstanding_ack) tq; 1355ae1f160dSDag-Erling Smørgrav }; 1356ae1f160dSDag-Erling Smørgrav TAILQ_HEAD(ackhead, outstanding_ack) acks; 1357d74d50a8SDag-Erling Smørgrav struct outstanding_ack *ack = NULL; 1358ae1f160dSDag-Erling Smørgrav 1359ae1f160dSDag-Erling Smørgrav TAILQ_INIT(&acks); 13601e8db6e2SBrian Feldman 13611e8db6e2SBrian Feldman if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) { 13621e8db6e2SBrian Feldman error("Couldn't open local file \"%s\" for reading: %s", 13631e8db6e2SBrian Feldman local_path, strerror(errno)); 13641e8db6e2SBrian Feldman return(-1); 13651e8db6e2SBrian Feldman } 13661e8db6e2SBrian Feldman if (fstat(local_fd, &sb) == -1) { 13671e8db6e2SBrian Feldman error("Couldn't fstat local file \"%s\": %s", 13681e8db6e2SBrian Feldman local_path, strerror(errno)); 13691e8db6e2SBrian Feldman close(local_fd); 13701e8db6e2SBrian Feldman return(-1); 13711e8db6e2SBrian Feldman } 1372d0c8c0bcSDag-Erling Smørgrav if (!S_ISREG(sb.st_mode)) { 1373d0c8c0bcSDag-Erling Smørgrav error("%s is not a regular file", local_path); 1374d0c8c0bcSDag-Erling Smørgrav close(local_fd); 1375d0c8c0bcSDag-Erling Smørgrav return(-1); 1376d0c8c0bcSDag-Erling Smørgrav } 13771e8db6e2SBrian Feldman stat_to_attrib(&sb, &a); 13781e8db6e2SBrian Feldman 13791e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 13801e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 13811e8db6e2SBrian Feldman a.perm &= 0777; 13821e8db6e2SBrian Feldman if (!pflag) 13831e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 13841e8db6e2SBrian Feldman 13851e8db6e2SBrian Feldman buffer_init(&msg); 13861e8db6e2SBrian Feldman 13871e8db6e2SBrian Feldman /* Send open request */ 1388ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 13891e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_OPEN); 13901e8db6e2SBrian Feldman buffer_put_int(&msg, id); 13911e8db6e2SBrian Feldman buffer_put_cstring(&msg, remote_path); 13921e8db6e2SBrian Feldman buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC); 13931e8db6e2SBrian Feldman encode_attrib(&msg, &a); 13944a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 1395ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 13961e8db6e2SBrian Feldman 13971e8db6e2SBrian Feldman buffer_clear(&msg); 13981e8db6e2SBrian Feldman 13994a421b63SDag-Erling Smørgrav handle = get_handle(conn, id, &handle_len, 1400b15c8340SDag-Erling Smørgrav "remote open(\"%s\")", remote_path); 14011e8db6e2SBrian Feldman if (handle == NULL) { 14021e8db6e2SBrian Feldman close(local_fd); 14031e8db6e2SBrian Feldman buffer_free(&msg); 1404d4af9e69SDag-Erling Smørgrav return -1; 14051e8db6e2SBrian Feldman } 14061e8db6e2SBrian Feldman 1407ae1f160dSDag-Erling Smørgrav startid = ackid = id + 1; 1408ae1f160dSDag-Erling Smørgrav data = xmalloc(conn->transfer_buflen); 1409ae1f160dSDag-Erling Smørgrav 14101e8db6e2SBrian Feldman /* Read from local and write to remote */ 14111e8db6e2SBrian Feldman offset = 0; 1412d0c8c0bcSDag-Erling Smørgrav if (showprogress) 1413d0c8c0bcSDag-Erling Smørgrav start_progress_meter(local_path, sb.st_size, &offset); 1414d0c8c0bcSDag-Erling Smørgrav 14151e8db6e2SBrian Feldman for (;;) { 14161e8db6e2SBrian Feldman int len; 14171e8db6e2SBrian Feldman 14181e8db6e2SBrian Feldman /* 1419d74d50a8SDag-Erling Smørgrav * Can't use atomicio here because it returns 0 on EOF, 1420d74d50a8SDag-Erling Smørgrav * thus losing the last block of the file. 1421d74d50a8SDag-Erling Smørgrav * Simulate an EOF on interrupt, allowing ACKs from the 1422d74d50a8SDag-Erling Smørgrav * server to drain. 14231e8db6e2SBrian Feldman */ 1424d4af9e69SDag-Erling Smørgrav if (interrupted || status != SSH2_FX_OK) 1425d74d50a8SDag-Erling Smørgrav len = 0; 1426d74d50a8SDag-Erling Smørgrav else do 1427ae1f160dSDag-Erling Smørgrav len = read(local_fd, data, conn->transfer_buflen); 1428d4af9e69SDag-Erling Smørgrav while ((len == -1) && 1429d4af9e69SDag-Erling Smørgrav (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); 14301e8db6e2SBrian Feldman 14311e8db6e2SBrian Feldman if (len == -1) 14321e8db6e2SBrian Feldman fatal("Couldn't read from \"%s\": %s", local_path, 14331e8db6e2SBrian Feldman strerror(errno)); 1434ae1f160dSDag-Erling Smørgrav 1435ae1f160dSDag-Erling Smørgrav if (len != 0) { 1436ae1f160dSDag-Erling Smørgrav ack = xmalloc(sizeof(*ack)); 1437ae1f160dSDag-Erling Smørgrav ack->id = ++id; 1438ae1f160dSDag-Erling Smørgrav ack->offset = offset; 1439ae1f160dSDag-Erling Smørgrav ack->len = len; 1440ae1f160dSDag-Erling Smørgrav TAILQ_INSERT_TAIL(&acks, ack, tq); 14411e8db6e2SBrian Feldman 14421e8db6e2SBrian Feldman buffer_clear(&msg); 14431e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_WRITE); 1444ae1f160dSDag-Erling Smørgrav buffer_put_int(&msg, ack->id); 14451e8db6e2SBrian Feldman buffer_put_string(&msg, handle, handle_len); 14461e8db6e2SBrian Feldman buffer_put_int64(&msg, offset); 14471e8db6e2SBrian Feldman buffer_put_string(&msg, data, len); 14484a421b63SDag-Erling Smørgrav send_msg(conn, &msg); 1449ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u", 1450545d5ecaSDag-Erling Smørgrav id, (unsigned long long)offset, len); 1451ae1f160dSDag-Erling Smørgrav } else if (TAILQ_FIRST(&acks) == NULL) 1452ae1f160dSDag-Erling Smørgrav break; 14531e8db6e2SBrian Feldman 1454ae1f160dSDag-Erling Smørgrav if (ack == NULL) 1455ae1f160dSDag-Erling Smørgrav fatal("Unexpected ACK %u", id); 1456ae1f160dSDag-Erling Smørgrav 1457ae1f160dSDag-Erling Smørgrav if (id == startid || len == 0 || 1458ae1f160dSDag-Erling Smørgrav id - ackid >= conn->num_requests) { 1459545d5ecaSDag-Erling Smørgrav u_int r_id; 1460545d5ecaSDag-Erling Smørgrav 1461ae1f160dSDag-Erling Smørgrav buffer_clear(&msg); 14624a421b63SDag-Erling Smørgrav get_msg(conn, &msg); 1463ae1f160dSDag-Erling Smørgrav type = buffer_get_char(&msg); 1464545d5ecaSDag-Erling Smørgrav r_id = buffer_get_int(&msg); 1465ae1f160dSDag-Erling Smørgrav 1466ae1f160dSDag-Erling Smørgrav if (type != SSH2_FXP_STATUS) 1467ae1f160dSDag-Erling Smørgrav fatal("Expected SSH2_FXP_STATUS(%d) packet, " 1468ae1f160dSDag-Erling Smørgrav "got %d", SSH2_FXP_STATUS, type); 1469ae1f160dSDag-Erling Smørgrav 1470ae1f160dSDag-Erling Smørgrav status = buffer_get_int(&msg); 1471ae1f160dSDag-Erling Smørgrav debug3("SSH2_FXP_STATUS %d", status); 1472ae1f160dSDag-Erling Smørgrav 1473ae1f160dSDag-Erling Smørgrav /* Find the request in our queue */ 1474ae1f160dSDag-Erling Smørgrav for (ack = TAILQ_FIRST(&acks); 1475545d5ecaSDag-Erling Smørgrav ack != NULL && ack->id != r_id; 1476ae1f160dSDag-Erling Smørgrav ack = TAILQ_NEXT(ack, tq)) 1477ae1f160dSDag-Erling Smørgrav ; 1478ae1f160dSDag-Erling Smørgrav if (ack == NULL) 1479ee21a45fSDag-Erling Smørgrav fatal("Can't find request for ID %u", r_id); 1480ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&acks, ack, tq); 1481d4af9e69SDag-Erling Smørgrav debug3("In write loop, ack for %u %u bytes at %lld", 1482d4af9e69SDag-Erling Smørgrav ack->id, ack->len, (long long)ack->offset); 1483ae1f160dSDag-Erling Smørgrav ++ackid; 14844b17dab0SDag-Erling Smørgrav xfree(ack); 1485ae1f160dSDag-Erling Smørgrav } 14861e8db6e2SBrian Feldman offset += len; 1487d4af9e69SDag-Erling Smørgrav if (offset < 0) 1488d4af9e69SDag-Erling Smørgrav fatal("%s: offset < 0", __func__); 14891e8db6e2SBrian Feldman } 1490d4af9e69SDag-Erling Smørgrav buffer_free(&msg); 1491d4af9e69SDag-Erling Smørgrav 1492d0c8c0bcSDag-Erling Smørgrav if (showprogress) 1493d0c8c0bcSDag-Erling Smørgrav stop_progress_meter(); 1494ae1f160dSDag-Erling Smørgrav xfree(data); 14951e8db6e2SBrian Feldman 1496d4af9e69SDag-Erling Smørgrav if (status != SSH2_FX_OK) { 1497d4af9e69SDag-Erling Smørgrav error("Couldn't write to remote file \"%s\": %s", 1498d4af9e69SDag-Erling Smørgrav remote_path, fx2txt(status)); 1499d4af9e69SDag-Erling Smørgrav status = -1; 1500d4af9e69SDag-Erling Smørgrav } 1501d4af9e69SDag-Erling Smørgrav 15021e8db6e2SBrian Feldman if (close(local_fd) == -1) { 15031e8db6e2SBrian Feldman error("Couldn't close local file \"%s\": %s", local_path, 15041e8db6e2SBrian Feldman strerror(errno)); 15051e8db6e2SBrian Feldman status = -1; 15061e8db6e2SBrian Feldman } 15071e8db6e2SBrian Feldman 15081e8db6e2SBrian Feldman /* Override umask and utimes if asked */ 15091e8db6e2SBrian Feldman if (pflag) 1510ae1f160dSDag-Erling Smørgrav do_fsetstat(conn, handle, handle_len, &a); 15111e8db6e2SBrian Feldman 1512d4af9e69SDag-Erling Smørgrav if (do_close(conn, handle, handle_len) != SSH2_FX_OK) 1513d4af9e69SDag-Erling Smørgrav status = -1; 15141e8db6e2SBrian Feldman xfree(handle); 1515d4af9e69SDag-Erling Smørgrav 1516d4af9e69SDag-Erling Smørgrav return status; 15171e8db6e2SBrian Feldman } 1518b15c8340SDag-Erling Smørgrav 1519b15c8340SDag-Erling Smørgrav static int 1520b15c8340SDag-Erling Smørgrav upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, 1521b15c8340SDag-Erling Smørgrav int pflag, int printflag, int depth) 1522b15c8340SDag-Erling Smørgrav { 1523b15c8340SDag-Erling Smørgrav int ret = 0, status; 1524b15c8340SDag-Erling Smørgrav DIR *dirp; 1525b15c8340SDag-Erling Smørgrav struct dirent *dp; 1526b15c8340SDag-Erling Smørgrav char *filename, *new_src, *new_dst; 1527b15c8340SDag-Erling Smørgrav struct stat sb; 1528b15c8340SDag-Erling Smørgrav Attrib a; 1529b15c8340SDag-Erling Smørgrav 1530b15c8340SDag-Erling Smørgrav if (depth >= MAX_DIR_DEPTH) { 1531b15c8340SDag-Erling Smørgrav error("Maximum directory depth exceeded: %d levels", depth); 1532b15c8340SDag-Erling Smørgrav return -1; 1533b15c8340SDag-Erling Smørgrav } 1534b15c8340SDag-Erling Smørgrav 1535b15c8340SDag-Erling Smørgrav if (stat(src, &sb) == -1) { 1536b15c8340SDag-Erling Smørgrav error("Couldn't stat directory \"%s\": %s", 1537b15c8340SDag-Erling Smørgrav src, strerror(errno)); 1538b15c8340SDag-Erling Smørgrav return -1; 1539b15c8340SDag-Erling Smørgrav } 1540b15c8340SDag-Erling Smørgrav if (!S_ISDIR(sb.st_mode)) { 1541b15c8340SDag-Erling Smørgrav error("\"%s\" is not a directory", src); 1542b15c8340SDag-Erling Smørgrav return -1; 1543b15c8340SDag-Erling Smørgrav } 1544b15c8340SDag-Erling Smørgrav if (printflag) 1545b15c8340SDag-Erling Smørgrav printf("Entering %s\n", src); 1546b15c8340SDag-Erling Smørgrav 1547b15c8340SDag-Erling Smørgrav attrib_clear(&a); 1548b15c8340SDag-Erling Smørgrav stat_to_attrib(&sb, &a); 1549b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 1550b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 1551b15c8340SDag-Erling Smørgrav a.perm &= 01777; 1552b15c8340SDag-Erling Smørgrav if (!pflag) 1553b15c8340SDag-Erling Smørgrav a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 1554b15c8340SDag-Erling Smørgrav 1555b15c8340SDag-Erling Smørgrav status = do_mkdir(conn, dst, &a, 0); 1556b15c8340SDag-Erling Smørgrav /* 1557b15c8340SDag-Erling Smørgrav * we lack a portable status for errno EEXIST, 1558b15c8340SDag-Erling Smørgrav * so if we get a SSH2_FX_FAILURE back we must check 1559b15c8340SDag-Erling Smørgrav * if it was created successfully. 1560b15c8340SDag-Erling Smørgrav */ 1561b15c8340SDag-Erling Smørgrav if (status != SSH2_FX_OK) { 1562b15c8340SDag-Erling Smørgrav if (status != SSH2_FX_FAILURE) 1563b15c8340SDag-Erling Smørgrav return -1; 1564b15c8340SDag-Erling Smørgrav if (do_stat(conn, dst, 0) == NULL) 1565b15c8340SDag-Erling Smørgrav return -1; 1566b15c8340SDag-Erling Smørgrav } 1567b15c8340SDag-Erling Smørgrav 1568b15c8340SDag-Erling Smørgrav if ((dirp = opendir(src)) == NULL) { 1569b15c8340SDag-Erling Smørgrav error("Failed to open dir \"%s\": %s", src, strerror(errno)); 1570b15c8340SDag-Erling Smørgrav return -1; 1571b15c8340SDag-Erling Smørgrav } 1572b15c8340SDag-Erling Smørgrav 1573b15c8340SDag-Erling Smørgrav while (((dp = readdir(dirp)) != NULL) && !interrupted) { 1574b15c8340SDag-Erling Smørgrav if (dp->d_ino == 0) 1575b15c8340SDag-Erling Smørgrav continue; 1576b15c8340SDag-Erling Smørgrav filename = dp->d_name; 1577b15c8340SDag-Erling Smørgrav new_dst = path_append(dst, filename); 1578b15c8340SDag-Erling Smørgrav new_src = path_append(src, filename); 1579b15c8340SDag-Erling Smørgrav 1580b15c8340SDag-Erling Smørgrav if (lstat(new_src, &sb) == -1) { 1581b15c8340SDag-Erling Smørgrav logit("%s: lstat failed: %s", filename, 1582b15c8340SDag-Erling Smørgrav strerror(errno)); 1583b15c8340SDag-Erling Smørgrav ret = -1; 1584b15c8340SDag-Erling Smørgrav } else if (S_ISDIR(sb.st_mode)) { 1585b15c8340SDag-Erling Smørgrav if (strcmp(filename, ".") == 0 || 1586b15c8340SDag-Erling Smørgrav strcmp(filename, "..") == 0) 1587b15c8340SDag-Erling Smørgrav continue; 1588b15c8340SDag-Erling Smørgrav 1589b15c8340SDag-Erling Smørgrav if (upload_dir_internal(conn, new_src, new_dst, 1590e2f6069cSDag-Erling Smørgrav pflag, printflag, depth + 1) == -1) 1591b15c8340SDag-Erling Smørgrav ret = -1; 1592b15c8340SDag-Erling Smørgrav } else if (S_ISREG(sb.st_mode)) { 1593b15c8340SDag-Erling Smørgrav if (do_upload(conn, new_src, new_dst, pflag) == -1) { 1594b15c8340SDag-Erling Smørgrav error("Uploading of file %s to %s failed!", 1595b15c8340SDag-Erling Smørgrav new_src, new_dst); 1596b15c8340SDag-Erling Smørgrav ret = -1; 1597b15c8340SDag-Erling Smørgrav } 1598b15c8340SDag-Erling Smørgrav } else 1599b15c8340SDag-Erling Smørgrav logit("%s: not a regular file\n", filename); 1600b15c8340SDag-Erling Smørgrav xfree(new_dst); 1601b15c8340SDag-Erling Smørgrav xfree(new_src); 1602b15c8340SDag-Erling Smørgrav } 1603b15c8340SDag-Erling Smørgrav 1604b15c8340SDag-Erling Smørgrav do_setstat(conn, dst, &a); 1605b15c8340SDag-Erling Smørgrav 1606b15c8340SDag-Erling Smørgrav (void) closedir(dirp); 1607b15c8340SDag-Erling Smørgrav return ret; 1608b15c8340SDag-Erling Smørgrav } 1609b15c8340SDag-Erling Smørgrav 1610b15c8340SDag-Erling Smørgrav int 1611b15c8340SDag-Erling Smørgrav upload_dir(struct sftp_conn *conn, char *src, char *dst, int printflag, 1612b15c8340SDag-Erling Smørgrav int pflag) 1613b15c8340SDag-Erling Smørgrav { 1614b15c8340SDag-Erling Smørgrav char *dst_canon; 1615b15c8340SDag-Erling Smørgrav int ret; 1616b15c8340SDag-Erling Smørgrav 1617b15c8340SDag-Erling Smørgrav if ((dst_canon = do_realpath(conn, dst)) == NULL) { 1618b15c8340SDag-Erling Smørgrav error("Unable to canonicalise path \"%s\"", dst); 1619b15c8340SDag-Erling Smørgrav return -1; 1620b15c8340SDag-Erling Smørgrav } 1621b15c8340SDag-Erling Smørgrav 1622b15c8340SDag-Erling Smørgrav ret = upload_dir_internal(conn, src, dst_canon, pflag, printflag, 0); 1623b15c8340SDag-Erling Smørgrav xfree(dst_canon); 1624b15c8340SDag-Erling Smørgrav return ret; 1625b15c8340SDag-Erling Smørgrav } 1626b15c8340SDag-Erling Smørgrav 1627b15c8340SDag-Erling Smørgrav char * 1628b15c8340SDag-Erling Smørgrav path_append(char *p1, char *p2) 1629b15c8340SDag-Erling Smørgrav { 1630b15c8340SDag-Erling Smørgrav char *ret; 1631b15c8340SDag-Erling Smørgrav size_t len = strlen(p1) + strlen(p2) + 2; 1632b15c8340SDag-Erling Smørgrav 1633b15c8340SDag-Erling Smørgrav ret = xmalloc(len); 1634b15c8340SDag-Erling Smørgrav strlcpy(ret, p1, len); 1635b15c8340SDag-Erling Smørgrav if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') 1636b15c8340SDag-Erling Smørgrav strlcat(ret, "/", len); 1637b15c8340SDag-Erling Smørgrav strlcat(ret, p2, len); 1638b15c8340SDag-Erling Smørgrav 1639b15c8340SDag-Erling Smørgrav return(ret); 1640b15c8340SDag-Erling Smørgrav } 1641b15c8340SDag-Erling Smørgrav 1642