1761efaa7SDag-Erling Smørgrav /* $OpenBSD: sftp-client.c,v 1.74 2006/08/03 03:34:42 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 25761efaa7SDag-Erling Smørgrav #include <sys/types.h> 26761efaa7SDag-Erling Smørgrav #include <sys/param.h> 274b17dab0SDag-Erling Smørgrav #include "openbsd-compat/sys-queue.h" 28761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_STAT_H 29761efaa7SDag-Erling Smørgrav # include <sys/stat.h> 30761efaa7SDag-Erling Smørgrav #endif 31761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_TIME_H 32761efaa7SDag-Erling Smørgrav # include <sys/time.h> 33761efaa7SDag-Erling Smørgrav #endif 34761efaa7SDag-Erling Smørgrav #include <sys/uio.h> 35ae1f160dSDag-Erling Smørgrav 36761efaa7SDag-Erling Smørgrav #include <errno.h> 37761efaa7SDag-Erling Smørgrav #include <fcntl.h> 38761efaa7SDag-Erling Smørgrav #include <signal.h> 39761efaa7SDag-Erling Smørgrav #include <stdarg.h> 40761efaa7SDag-Erling Smørgrav #include <stdio.h> 41761efaa7SDag-Erling Smørgrav #include <string.h> 42761efaa7SDag-Erling Smørgrav #include <unistd.h> 43761efaa7SDag-Erling Smørgrav 441e8db6e2SBrian Feldman #include "xmalloc.h" 45761efaa7SDag-Erling Smørgrav #include "buffer.h" 461e8db6e2SBrian Feldman #include "log.h" 471e8db6e2SBrian Feldman #include "atomicio.h" 48d0c8c0bcSDag-Erling Smørgrav #include "progressmeter.h" 49761efaa7SDag-Erling Smørgrav #include "misc.h" 501e8db6e2SBrian Feldman 511e8db6e2SBrian Feldman #include "sftp.h" 521e8db6e2SBrian Feldman #include "sftp-common.h" 531e8db6e2SBrian Feldman #include "sftp-client.h" 541e8db6e2SBrian Feldman 55d74d50a8SDag-Erling Smørgrav extern volatile sig_atomic_t interrupted; 56d0c8c0bcSDag-Erling Smørgrav extern int showprogress; 57d0c8c0bcSDag-Erling Smørgrav 58761efaa7SDag-Erling Smørgrav /* Minimum amount of data to read at a time */ 59ae1f160dSDag-Erling Smørgrav #define MIN_READ_SIZE 512 601e8db6e2SBrian Feldman 61ae1f160dSDag-Erling Smørgrav struct sftp_conn { 62ae1f160dSDag-Erling Smørgrav int fd_in; 63ae1f160dSDag-Erling Smørgrav int fd_out; 64ae1f160dSDag-Erling Smørgrav u_int transfer_buflen; 65ae1f160dSDag-Erling Smørgrav u_int num_requests; 66ae1f160dSDag-Erling Smørgrav u_int version; 67ae1f160dSDag-Erling Smørgrav u_int msg_id; 68ae1f160dSDag-Erling Smørgrav }; 691e8db6e2SBrian Feldman 70ae1f160dSDag-Erling Smørgrav static void 711e8db6e2SBrian Feldman send_msg(int fd, Buffer *m) 721e8db6e2SBrian Feldman { 73d0c8c0bcSDag-Erling Smørgrav u_char mlen[4]; 74761efaa7SDag-Erling Smørgrav struct iovec iov[2]; 751e8db6e2SBrian Feldman 76021d409fSDag-Erling Smørgrav if (buffer_len(m) > SFTP_MAX_MSG_LENGTH) 77d0c8c0bcSDag-Erling Smørgrav fatal("Outbound message too long %u", buffer_len(m)); 781e8db6e2SBrian Feldman 79d0c8c0bcSDag-Erling Smørgrav /* Send length first */ 80761efaa7SDag-Erling Smørgrav put_u32(mlen, buffer_len(m)); 81761efaa7SDag-Erling Smørgrav iov[0].iov_base = mlen; 82761efaa7SDag-Erling Smørgrav iov[0].iov_len = sizeof(mlen); 83761efaa7SDag-Erling Smørgrav iov[1].iov_base = buffer_ptr(m); 84761efaa7SDag-Erling Smørgrav iov[1].iov_len = buffer_len(m); 851e8db6e2SBrian Feldman 86761efaa7SDag-Erling Smørgrav if (atomiciov(writev, fd, iov, 2) != buffer_len(m) + sizeof(mlen)) 87d0c8c0bcSDag-Erling Smørgrav fatal("Couldn't send packet: %s", strerror(errno)); 88d0c8c0bcSDag-Erling Smørgrav 89d0c8c0bcSDag-Erling Smørgrav buffer_clear(m); 901e8db6e2SBrian Feldman } 911e8db6e2SBrian Feldman 92ae1f160dSDag-Erling Smørgrav static void 931e8db6e2SBrian Feldman get_msg(int fd, Buffer *m) 941e8db6e2SBrian Feldman { 95d0c8c0bcSDag-Erling Smørgrav u_int msg_len; 961e8db6e2SBrian Feldman 97d0c8c0bcSDag-Erling Smørgrav buffer_append_space(m, 4); 98043840dfSDag-Erling Smørgrav if (atomicio(read, fd, buffer_ptr(m), 4) != 4) { 99043840dfSDag-Erling Smørgrav if (errno == EPIPE) 1001e8db6e2SBrian Feldman fatal("Connection closed"); 101043840dfSDag-Erling Smørgrav else 1021e8db6e2SBrian Feldman fatal("Couldn't read packet: %s", strerror(errno)); 103043840dfSDag-Erling Smørgrav } 1041e8db6e2SBrian Feldman 105d0c8c0bcSDag-Erling Smørgrav msg_len = buffer_get_int(m); 106021d409fSDag-Erling Smørgrav if (msg_len > SFTP_MAX_MSG_LENGTH) 107ee21a45fSDag-Erling Smørgrav fatal("Received message too long %u", msg_len); 1081e8db6e2SBrian Feldman 109d0c8c0bcSDag-Erling Smørgrav buffer_append_space(m, msg_len); 110043840dfSDag-Erling Smørgrav if (atomicio(read, fd, buffer_ptr(m), msg_len) != msg_len) { 111043840dfSDag-Erling Smørgrav if (errno == EPIPE) 1121e8db6e2SBrian Feldman fatal("Connection closed"); 113043840dfSDag-Erling Smørgrav else 114d0c8c0bcSDag-Erling Smørgrav fatal("Read packet: %s", strerror(errno)); 1151e8db6e2SBrian Feldman } 116043840dfSDag-Erling Smørgrav } 1171e8db6e2SBrian Feldman 118ae1f160dSDag-Erling Smørgrav static void 1191e8db6e2SBrian Feldman send_string_request(int fd, u_int id, u_int code, char *s, 1201e8db6e2SBrian Feldman u_int len) 1211e8db6e2SBrian Feldman { 1221e8db6e2SBrian Feldman Buffer msg; 1231e8db6e2SBrian Feldman 1241e8db6e2SBrian Feldman buffer_init(&msg); 1251e8db6e2SBrian Feldman buffer_put_char(&msg, code); 1261e8db6e2SBrian Feldman buffer_put_int(&msg, id); 1271e8db6e2SBrian Feldman buffer_put_string(&msg, s, len); 1281e8db6e2SBrian Feldman send_msg(fd, &msg); 129ee21a45fSDag-Erling Smørgrav debug3("Sent message fd %d T:%u I:%u", fd, code, id); 1301e8db6e2SBrian Feldman buffer_free(&msg); 1311e8db6e2SBrian Feldman } 1321e8db6e2SBrian Feldman 133ae1f160dSDag-Erling Smørgrav static void 1341e8db6e2SBrian Feldman send_string_attrs_request(int fd, u_int id, u_int code, char *s, 1351e8db6e2SBrian Feldman u_int len, Attrib *a) 1361e8db6e2SBrian Feldman { 1371e8db6e2SBrian Feldman Buffer msg; 1381e8db6e2SBrian Feldman 1391e8db6e2SBrian Feldman buffer_init(&msg); 1401e8db6e2SBrian Feldman buffer_put_char(&msg, code); 1411e8db6e2SBrian Feldman buffer_put_int(&msg, id); 1421e8db6e2SBrian Feldman buffer_put_string(&msg, s, len); 1431e8db6e2SBrian Feldman encode_attrib(&msg, a); 1441e8db6e2SBrian Feldman send_msg(fd, &msg); 145ee21a45fSDag-Erling Smørgrav debug3("Sent message fd %d T:%u I:%u", fd, code, id); 1461e8db6e2SBrian Feldman buffer_free(&msg); 1471e8db6e2SBrian Feldman } 1481e8db6e2SBrian Feldman 149ae1f160dSDag-Erling Smørgrav static u_int 150ee21a45fSDag-Erling Smørgrav get_status(int fd, u_int expected_id) 1511e8db6e2SBrian Feldman { 1521e8db6e2SBrian Feldman Buffer msg; 1531e8db6e2SBrian Feldman u_int type, id, status; 1541e8db6e2SBrian Feldman 1551e8db6e2SBrian Feldman buffer_init(&msg); 1561e8db6e2SBrian Feldman get_msg(fd, &msg); 1571e8db6e2SBrian Feldman type = buffer_get_char(&msg); 1581e8db6e2SBrian Feldman id = buffer_get_int(&msg); 1591e8db6e2SBrian Feldman 1601e8db6e2SBrian Feldman if (id != expected_id) 161ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 1621e8db6e2SBrian Feldman if (type != SSH2_FXP_STATUS) 163ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u", 1641e8db6e2SBrian Feldman SSH2_FXP_STATUS, type); 1651e8db6e2SBrian Feldman 1661e8db6e2SBrian Feldman status = buffer_get_int(&msg); 1671e8db6e2SBrian Feldman buffer_free(&msg); 1681e8db6e2SBrian Feldman 169ee21a45fSDag-Erling Smørgrav debug3("SSH2_FXP_STATUS %u", status); 1701e8db6e2SBrian Feldman 1711e8db6e2SBrian Feldman return(status); 1721e8db6e2SBrian Feldman } 1731e8db6e2SBrian Feldman 174ae1f160dSDag-Erling Smørgrav static char * 1751e8db6e2SBrian Feldman get_handle(int fd, u_int expected_id, u_int *len) 1761e8db6e2SBrian Feldman { 1771e8db6e2SBrian Feldman Buffer msg; 1781e8db6e2SBrian Feldman u_int type, id; 1791e8db6e2SBrian Feldman char *handle; 1801e8db6e2SBrian Feldman 1811e8db6e2SBrian Feldman buffer_init(&msg); 1821e8db6e2SBrian Feldman get_msg(fd, &msg); 1831e8db6e2SBrian Feldman type = buffer_get_char(&msg); 1841e8db6e2SBrian Feldman id = buffer_get_int(&msg); 1851e8db6e2SBrian Feldman 1861e8db6e2SBrian Feldman if (id != expected_id) 187ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 1881e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 1891e8db6e2SBrian Feldman int status = buffer_get_int(&msg); 1901e8db6e2SBrian Feldman 1911e8db6e2SBrian Feldman error("Couldn't get handle: %s", fx2txt(status)); 1925e8dbd04SDag-Erling Smørgrav buffer_free(&msg); 1931e8db6e2SBrian Feldman return(NULL); 1941e8db6e2SBrian Feldman } else if (type != SSH2_FXP_HANDLE) 195ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_HANDLE(%u) packet, got %u", 1961e8db6e2SBrian Feldman SSH2_FXP_HANDLE, type); 1971e8db6e2SBrian Feldman 1981e8db6e2SBrian Feldman handle = buffer_get_string(&msg, len); 1991e8db6e2SBrian Feldman buffer_free(&msg); 2001e8db6e2SBrian Feldman 2011e8db6e2SBrian Feldman return(handle); 2021e8db6e2SBrian Feldman } 2031e8db6e2SBrian Feldman 204ae1f160dSDag-Erling Smørgrav static Attrib * 2051e8db6e2SBrian Feldman get_decode_stat(int fd, u_int expected_id, int quiet) 2061e8db6e2SBrian Feldman { 2071e8db6e2SBrian Feldman Buffer msg; 2081e8db6e2SBrian Feldman u_int type, id; 2091e8db6e2SBrian Feldman Attrib *a; 2101e8db6e2SBrian Feldman 2111e8db6e2SBrian Feldman buffer_init(&msg); 2121e8db6e2SBrian Feldman get_msg(fd, &msg); 2131e8db6e2SBrian Feldman 2141e8db6e2SBrian Feldman type = buffer_get_char(&msg); 2151e8db6e2SBrian Feldman id = buffer_get_int(&msg); 2161e8db6e2SBrian Feldman 217ee21a45fSDag-Erling Smørgrav debug3("Received stat reply T:%u I:%u", type, id); 2181e8db6e2SBrian Feldman if (id != expected_id) 219ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 2201e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 2211e8db6e2SBrian Feldman int status = buffer_get_int(&msg); 2221e8db6e2SBrian Feldman 2231e8db6e2SBrian Feldman if (quiet) 2241e8db6e2SBrian Feldman debug("Couldn't stat remote file: %s", fx2txt(status)); 2251e8db6e2SBrian Feldman else 2261e8db6e2SBrian Feldman error("Couldn't stat remote file: %s", fx2txt(status)); 2275e8dbd04SDag-Erling Smørgrav buffer_free(&msg); 2281e8db6e2SBrian Feldman return(NULL); 2291e8db6e2SBrian Feldman } else if (type != SSH2_FXP_ATTRS) { 230ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", 2311e8db6e2SBrian Feldman SSH2_FXP_ATTRS, type); 2321e8db6e2SBrian Feldman } 2331e8db6e2SBrian Feldman a = decode_attrib(&msg); 2341e8db6e2SBrian Feldman buffer_free(&msg); 2351e8db6e2SBrian Feldman 2361e8db6e2SBrian Feldman return(a); 2371e8db6e2SBrian Feldman } 2381e8db6e2SBrian Feldman 239ae1f160dSDag-Erling Smørgrav struct sftp_conn * 240ae1f160dSDag-Erling Smørgrav do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests) 2411e8db6e2SBrian Feldman { 242ee21a45fSDag-Erling Smørgrav u_int type; 243ee21a45fSDag-Erling Smørgrav int version; 2441e8db6e2SBrian Feldman Buffer msg; 245ae1f160dSDag-Erling Smørgrav struct sftp_conn *ret; 2461e8db6e2SBrian Feldman 2471e8db6e2SBrian Feldman buffer_init(&msg); 2481e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_INIT); 2491e8db6e2SBrian Feldman buffer_put_int(&msg, SSH2_FILEXFER_VERSION); 2501e8db6e2SBrian Feldman send_msg(fd_out, &msg); 2511e8db6e2SBrian Feldman 2521e8db6e2SBrian Feldman buffer_clear(&msg); 2531e8db6e2SBrian Feldman 2541e8db6e2SBrian Feldman get_msg(fd_in, &msg); 2551e8db6e2SBrian Feldman 2561e8db6e2SBrian Feldman /* Expecting a VERSION reply */ 2571e8db6e2SBrian Feldman if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) { 258ee21a45fSDag-Erling Smørgrav error("Invalid packet back from SSH2_FXP_INIT (type %u)", 2591e8db6e2SBrian Feldman type); 2601e8db6e2SBrian Feldman buffer_free(&msg); 261ae1f160dSDag-Erling Smørgrav return(NULL); 2621e8db6e2SBrian Feldman } 2631e8db6e2SBrian Feldman version = buffer_get_int(&msg); 2641e8db6e2SBrian Feldman 2651e8db6e2SBrian Feldman debug2("Remote version: %d", version); 2661e8db6e2SBrian Feldman 2671e8db6e2SBrian Feldman /* Check for extensions */ 2681e8db6e2SBrian Feldman while (buffer_len(&msg) > 0) { 2691e8db6e2SBrian Feldman char *name = buffer_get_string(&msg, NULL); 2701e8db6e2SBrian Feldman char *value = buffer_get_string(&msg, NULL); 2711e8db6e2SBrian Feldman 2721e8db6e2SBrian Feldman debug2("Init extension: \"%s\"", name); 2731e8db6e2SBrian Feldman xfree(name); 2741e8db6e2SBrian Feldman xfree(value); 2751e8db6e2SBrian Feldman } 2761e8db6e2SBrian Feldman 2771e8db6e2SBrian Feldman buffer_free(&msg); 2781e8db6e2SBrian Feldman 279ae1f160dSDag-Erling Smørgrav ret = xmalloc(sizeof(*ret)); 280ae1f160dSDag-Erling Smørgrav ret->fd_in = fd_in; 281ae1f160dSDag-Erling Smørgrav ret->fd_out = fd_out; 282ae1f160dSDag-Erling Smørgrav ret->transfer_buflen = transfer_buflen; 283ae1f160dSDag-Erling Smørgrav ret->num_requests = num_requests; 284ae1f160dSDag-Erling Smørgrav ret->version = version; 285ae1f160dSDag-Erling Smørgrav ret->msg_id = 1; 286ae1f160dSDag-Erling Smørgrav 287ae1f160dSDag-Erling Smørgrav /* Some filexfer v.0 servers don't support large packets */ 288ae1f160dSDag-Erling Smørgrav if (version == 0) 289545d5ecaSDag-Erling Smørgrav ret->transfer_buflen = MIN(ret->transfer_buflen, 20480); 290ae1f160dSDag-Erling Smørgrav 291ae1f160dSDag-Erling Smørgrav return(ret); 292ae1f160dSDag-Erling Smørgrav } 293ae1f160dSDag-Erling Smørgrav 294ae1f160dSDag-Erling Smørgrav u_int 295ae1f160dSDag-Erling Smørgrav sftp_proto_version(struct sftp_conn *conn) 296ae1f160dSDag-Erling Smørgrav { 297ae1f160dSDag-Erling Smørgrav return(conn->version); 2981e8db6e2SBrian Feldman } 2991e8db6e2SBrian Feldman 3001e8db6e2SBrian Feldman int 301ae1f160dSDag-Erling Smørgrav do_close(struct sftp_conn *conn, char *handle, u_int handle_len) 3021e8db6e2SBrian Feldman { 3031e8db6e2SBrian Feldman u_int id, status; 3041e8db6e2SBrian Feldman Buffer msg; 3051e8db6e2SBrian Feldman 3061e8db6e2SBrian Feldman buffer_init(&msg); 3071e8db6e2SBrian Feldman 308ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 3091e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_CLOSE); 3101e8db6e2SBrian Feldman buffer_put_int(&msg, id); 3111e8db6e2SBrian Feldman buffer_put_string(&msg, handle, handle_len); 312ae1f160dSDag-Erling Smørgrav send_msg(conn->fd_out, &msg); 313ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_CLOSE I:%u", id); 3141e8db6e2SBrian Feldman 315ae1f160dSDag-Erling Smørgrav status = get_status(conn->fd_in, id); 3161e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 3171e8db6e2SBrian Feldman error("Couldn't close file: %s", fx2txt(status)); 3181e8db6e2SBrian Feldman 3191e8db6e2SBrian Feldman buffer_free(&msg); 3201e8db6e2SBrian Feldman 3211e8db6e2SBrian Feldman return(status); 3221e8db6e2SBrian Feldman } 3231e8db6e2SBrian Feldman 3241e8db6e2SBrian Feldman 325ae1f160dSDag-Erling Smørgrav static int 326ae1f160dSDag-Erling Smørgrav do_lsreaddir(struct sftp_conn *conn, char *path, int printflag, 3271e8db6e2SBrian Feldman SFTP_DIRENT ***dir) 3281e8db6e2SBrian Feldman { 3291e8db6e2SBrian Feldman Buffer msg; 330043840dfSDag-Erling Smørgrav u_int count, type, id, handle_len, i, expected_id, ents = 0; 3311e8db6e2SBrian Feldman char *handle; 3321e8db6e2SBrian Feldman 333ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 3341e8db6e2SBrian Feldman 3351e8db6e2SBrian Feldman buffer_init(&msg); 3361e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_OPENDIR); 3371e8db6e2SBrian Feldman buffer_put_int(&msg, id); 3381e8db6e2SBrian Feldman buffer_put_cstring(&msg, path); 339ae1f160dSDag-Erling Smørgrav send_msg(conn->fd_out, &msg); 3401e8db6e2SBrian Feldman 3411e8db6e2SBrian Feldman buffer_clear(&msg); 3421e8db6e2SBrian Feldman 343ae1f160dSDag-Erling Smørgrav handle = get_handle(conn->fd_in, id, &handle_len); 3441e8db6e2SBrian Feldman if (handle == NULL) 3451e8db6e2SBrian Feldman return(-1); 3461e8db6e2SBrian Feldman 3471e8db6e2SBrian Feldman if (dir) { 3481e8db6e2SBrian Feldman ents = 0; 3491e8db6e2SBrian Feldman *dir = xmalloc(sizeof(**dir)); 3501e8db6e2SBrian Feldman (*dir)[0] = NULL; 3511e8db6e2SBrian Feldman } 3521e8db6e2SBrian Feldman 353d74d50a8SDag-Erling Smørgrav for (; !interrupted;) { 354ae1f160dSDag-Erling Smørgrav id = expected_id = conn->msg_id++; 3551e8db6e2SBrian Feldman 356ee21a45fSDag-Erling Smørgrav debug3("Sending SSH2_FXP_READDIR I:%u", id); 3571e8db6e2SBrian Feldman 3581e8db6e2SBrian Feldman buffer_clear(&msg); 3591e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_READDIR); 3601e8db6e2SBrian Feldman buffer_put_int(&msg, id); 3611e8db6e2SBrian Feldman buffer_put_string(&msg, handle, handle_len); 362ae1f160dSDag-Erling Smørgrav send_msg(conn->fd_out, &msg); 3631e8db6e2SBrian Feldman 3641e8db6e2SBrian Feldman buffer_clear(&msg); 3651e8db6e2SBrian Feldman 366ae1f160dSDag-Erling Smørgrav get_msg(conn->fd_in, &msg); 3671e8db6e2SBrian Feldman 3681e8db6e2SBrian Feldman type = buffer_get_char(&msg); 3691e8db6e2SBrian Feldman id = buffer_get_int(&msg); 3701e8db6e2SBrian Feldman 371ee21a45fSDag-Erling Smørgrav debug3("Received reply T:%u I:%u", type, id); 3721e8db6e2SBrian Feldman 3731e8db6e2SBrian Feldman if (id != expected_id) 374ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 3751e8db6e2SBrian Feldman 3761e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 3771e8db6e2SBrian Feldman int status = buffer_get_int(&msg); 3781e8db6e2SBrian Feldman 3791e8db6e2SBrian Feldman debug3("Received SSH2_FXP_STATUS %d", status); 3801e8db6e2SBrian Feldman 3811e8db6e2SBrian Feldman if (status == SSH2_FX_EOF) { 3821e8db6e2SBrian Feldman break; 3831e8db6e2SBrian Feldman } else { 3841e8db6e2SBrian Feldman error("Couldn't read directory: %s", 3851e8db6e2SBrian Feldman fx2txt(status)); 386ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 387d0c8c0bcSDag-Erling Smørgrav xfree(handle); 3881e8db6e2SBrian Feldman return(status); 3891e8db6e2SBrian Feldman } 3901e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 391ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 3921e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 3931e8db6e2SBrian Feldman 3941e8db6e2SBrian Feldman count = buffer_get_int(&msg); 3951e8db6e2SBrian Feldman if (count == 0) 3961e8db6e2SBrian Feldman break; 3971e8db6e2SBrian Feldman debug3("Received %d SSH2_FXP_NAME responses", count); 3981e8db6e2SBrian Feldman for (i = 0; i < count; i++) { 3991e8db6e2SBrian Feldman char *filename, *longname; 4001e8db6e2SBrian Feldman Attrib *a; 4011e8db6e2SBrian Feldman 4021e8db6e2SBrian Feldman filename = buffer_get_string(&msg, NULL); 4031e8db6e2SBrian Feldman longname = buffer_get_string(&msg, NULL); 4041e8db6e2SBrian Feldman a = decode_attrib(&msg); 4051e8db6e2SBrian Feldman 4061e8db6e2SBrian Feldman if (printflag) 4071e8db6e2SBrian Feldman printf("%s\n", longname); 4081e8db6e2SBrian Feldman 4091e8db6e2SBrian Feldman if (dir) { 410761efaa7SDag-Erling Smørgrav *dir = xrealloc(*dir, ents + 2, sizeof(**dir)); 4111e8db6e2SBrian Feldman (*dir)[ents] = xmalloc(sizeof(***dir)); 4121e8db6e2SBrian Feldman (*dir)[ents]->filename = xstrdup(filename); 4131e8db6e2SBrian Feldman (*dir)[ents]->longname = xstrdup(longname); 4141e8db6e2SBrian Feldman memcpy(&(*dir)[ents]->a, a, sizeof(*a)); 4151e8db6e2SBrian Feldman (*dir)[++ents] = NULL; 4161e8db6e2SBrian Feldman } 4171e8db6e2SBrian Feldman 4181e8db6e2SBrian Feldman xfree(filename); 4191e8db6e2SBrian Feldman xfree(longname); 4201e8db6e2SBrian Feldman } 4211e8db6e2SBrian Feldman } 4221e8db6e2SBrian Feldman 4231e8db6e2SBrian Feldman buffer_free(&msg); 424ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 4251e8db6e2SBrian Feldman xfree(handle); 4261e8db6e2SBrian Feldman 427d74d50a8SDag-Erling Smørgrav /* Don't return partial matches on interrupt */ 428d74d50a8SDag-Erling Smørgrav if (interrupted && dir != NULL && *dir != NULL) { 429d74d50a8SDag-Erling Smørgrav free_sftp_dirents(*dir); 430d74d50a8SDag-Erling Smørgrav *dir = xmalloc(sizeof(**dir)); 431d74d50a8SDag-Erling Smørgrav **dir = NULL; 432d74d50a8SDag-Erling Smørgrav } 433d74d50a8SDag-Erling Smørgrav 4341e8db6e2SBrian Feldman return(0); 4351e8db6e2SBrian Feldman } 4361e8db6e2SBrian Feldman 4371e8db6e2SBrian Feldman int 438ae1f160dSDag-Erling Smørgrav do_readdir(struct sftp_conn *conn, char *path, SFTP_DIRENT ***dir) 4391e8db6e2SBrian Feldman { 440ae1f160dSDag-Erling Smørgrav return(do_lsreaddir(conn, path, 0, dir)); 4411e8db6e2SBrian Feldman } 4421e8db6e2SBrian Feldman 4431e8db6e2SBrian Feldman void free_sftp_dirents(SFTP_DIRENT **s) 4441e8db6e2SBrian Feldman { 4451e8db6e2SBrian Feldman int i; 4461e8db6e2SBrian Feldman 4471e8db6e2SBrian Feldman for (i = 0; s[i]; i++) { 4481e8db6e2SBrian Feldman xfree(s[i]->filename); 4491e8db6e2SBrian Feldman xfree(s[i]->longname); 4501e8db6e2SBrian Feldman xfree(s[i]); 4511e8db6e2SBrian Feldman } 4521e8db6e2SBrian Feldman xfree(s); 4531e8db6e2SBrian Feldman } 4541e8db6e2SBrian Feldman 4551e8db6e2SBrian Feldman int 456ae1f160dSDag-Erling Smørgrav do_rm(struct sftp_conn *conn, char *path) 4571e8db6e2SBrian Feldman { 4581e8db6e2SBrian Feldman u_int status, id; 4591e8db6e2SBrian Feldman 4601e8db6e2SBrian Feldman debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); 4611e8db6e2SBrian Feldman 462ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 463ae1f160dSDag-Erling Smørgrav send_string_request(conn->fd_out, id, SSH2_FXP_REMOVE, path, 464ae1f160dSDag-Erling Smørgrav strlen(path)); 465ae1f160dSDag-Erling Smørgrav status = get_status(conn->fd_in, id); 4661e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 4671e8db6e2SBrian Feldman error("Couldn't delete file: %s", fx2txt(status)); 4681e8db6e2SBrian Feldman return(status); 4691e8db6e2SBrian Feldman } 4701e8db6e2SBrian Feldman 4711e8db6e2SBrian Feldman int 472ae1f160dSDag-Erling Smørgrav do_mkdir(struct sftp_conn *conn, char *path, Attrib *a) 4731e8db6e2SBrian Feldman { 4741e8db6e2SBrian Feldman u_int status, id; 4751e8db6e2SBrian Feldman 476ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 477ae1f160dSDag-Erling Smørgrav send_string_attrs_request(conn->fd_out, id, SSH2_FXP_MKDIR, path, 4781e8db6e2SBrian Feldman strlen(path), a); 4791e8db6e2SBrian Feldman 480ae1f160dSDag-Erling Smørgrav status = get_status(conn->fd_in, id); 4811e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 4821e8db6e2SBrian Feldman error("Couldn't create directory: %s", fx2txt(status)); 4831e8db6e2SBrian Feldman 4841e8db6e2SBrian Feldman return(status); 4851e8db6e2SBrian Feldman } 4861e8db6e2SBrian Feldman 4871e8db6e2SBrian Feldman int 488ae1f160dSDag-Erling Smørgrav do_rmdir(struct sftp_conn *conn, char *path) 4891e8db6e2SBrian Feldman { 4901e8db6e2SBrian Feldman u_int status, id; 4911e8db6e2SBrian Feldman 492ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 493ae1f160dSDag-Erling Smørgrav send_string_request(conn->fd_out, id, SSH2_FXP_RMDIR, path, 494ae1f160dSDag-Erling Smørgrav strlen(path)); 4951e8db6e2SBrian Feldman 496ae1f160dSDag-Erling Smørgrav status = get_status(conn->fd_in, id); 4971e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 4981e8db6e2SBrian Feldman error("Couldn't remove directory: %s", fx2txt(status)); 4991e8db6e2SBrian Feldman 5001e8db6e2SBrian Feldman return(status); 5011e8db6e2SBrian Feldman } 5021e8db6e2SBrian Feldman 5031e8db6e2SBrian Feldman Attrib * 504ae1f160dSDag-Erling Smørgrav do_stat(struct sftp_conn *conn, char *path, int quiet) 5051e8db6e2SBrian Feldman { 5061e8db6e2SBrian Feldman u_int id; 5071e8db6e2SBrian Feldman 508ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 509ae1f160dSDag-Erling Smørgrav 510ae1f160dSDag-Erling Smørgrav send_string_request(conn->fd_out, id, 511ae1f160dSDag-Erling Smørgrav conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, 512ae1f160dSDag-Erling Smørgrav path, strlen(path)); 513ae1f160dSDag-Erling Smørgrav 514ae1f160dSDag-Erling Smørgrav return(get_decode_stat(conn->fd_in, id, quiet)); 5151e8db6e2SBrian Feldman } 5161e8db6e2SBrian Feldman 5171e8db6e2SBrian Feldman Attrib * 518ae1f160dSDag-Erling Smørgrav do_lstat(struct sftp_conn *conn, char *path, int quiet) 5191e8db6e2SBrian Feldman { 5201e8db6e2SBrian Feldman u_int id; 5211e8db6e2SBrian Feldman 522ae1f160dSDag-Erling Smørgrav if (conn->version == 0) { 523ae1f160dSDag-Erling Smørgrav if (quiet) 524ae1f160dSDag-Erling Smørgrav debug("Server version does not support lstat operation"); 525ae1f160dSDag-Erling Smørgrav else 526d95e11bfSDag-Erling Smørgrav logit("Server version does not support lstat operation"); 527545d5ecaSDag-Erling Smørgrav return(do_stat(conn, path, quiet)); 528ae1f160dSDag-Erling Smørgrav } 529ae1f160dSDag-Erling Smørgrav 530ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 531ae1f160dSDag-Erling Smørgrav send_string_request(conn->fd_out, id, SSH2_FXP_LSTAT, path, 532ae1f160dSDag-Erling Smørgrav strlen(path)); 533ae1f160dSDag-Erling Smørgrav 534ae1f160dSDag-Erling Smørgrav return(get_decode_stat(conn->fd_in, id, quiet)); 5351e8db6e2SBrian Feldman } 5361e8db6e2SBrian Feldman 5371e8db6e2SBrian Feldman Attrib * 538ae1f160dSDag-Erling Smørgrav do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet) 5391e8db6e2SBrian Feldman { 5401e8db6e2SBrian Feldman u_int id; 5411e8db6e2SBrian Feldman 542ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 543ae1f160dSDag-Erling Smørgrav send_string_request(conn->fd_out, id, SSH2_FXP_FSTAT, handle, 544ae1f160dSDag-Erling Smørgrav handle_len); 545ae1f160dSDag-Erling Smørgrav 546ae1f160dSDag-Erling Smørgrav return(get_decode_stat(conn->fd_in, id, quiet)); 5471e8db6e2SBrian Feldman } 5481e8db6e2SBrian Feldman 5491e8db6e2SBrian Feldman int 550ae1f160dSDag-Erling Smørgrav do_setstat(struct sftp_conn *conn, char *path, Attrib *a) 5511e8db6e2SBrian Feldman { 5521e8db6e2SBrian Feldman u_int status, id; 5531e8db6e2SBrian Feldman 554ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 555ae1f160dSDag-Erling Smørgrav send_string_attrs_request(conn->fd_out, id, SSH2_FXP_SETSTAT, path, 5561e8db6e2SBrian Feldman strlen(path), a); 5571e8db6e2SBrian Feldman 558ae1f160dSDag-Erling Smørgrav status = get_status(conn->fd_in, id); 5591e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 5601e8db6e2SBrian Feldman error("Couldn't setstat on \"%s\": %s", path, 5611e8db6e2SBrian Feldman fx2txt(status)); 5621e8db6e2SBrian Feldman 5631e8db6e2SBrian Feldman return(status); 5641e8db6e2SBrian Feldman } 5651e8db6e2SBrian Feldman 5661e8db6e2SBrian Feldman int 567ae1f160dSDag-Erling Smørgrav do_fsetstat(struct sftp_conn *conn, char *handle, u_int handle_len, 5681e8db6e2SBrian Feldman Attrib *a) 5691e8db6e2SBrian Feldman { 5701e8db6e2SBrian Feldman u_int status, id; 5711e8db6e2SBrian Feldman 572ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 573ae1f160dSDag-Erling Smørgrav send_string_attrs_request(conn->fd_out, id, SSH2_FXP_FSETSTAT, handle, 5741e8db6e2SBrian Feldman handle_len, a); 5751e8db6e2SBrian Feldman 576ae1f160dSDag-Erling Smørgrav status = get_status(conn->fd_in, id); 5771e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 5781e8db6e2SBrian Feldman error("Couldn't fsetstat: %s", fx2txt(status)); 5791e8db6e2SBrian Feldman 5801e8db6e2SBrian Feldman return(status); 5811e8db6e2SBrian Feldman } 5821e8db6e2SBrian Feldman 5831e8db6e2SBrian Feldman char * 584ae1f160dSDag-Erling Smørgrav do_realpath(struct sftp_conn *conn, char *path) 5851e8db6e2SBrian Feldman { 5861e8db6e2SBrian Feldman Buffer msg; 5871e8db6e2SBrian Feldman u_int type, expected_id, count, id; 5881e8db6e2SBrian Feldman char *filename, *longname; 5891e8db6e2SBrian Feldman Attrib *a; 5901e8db6e2SBrian Feldman 591ae1f160dSDag-Erling Smørgrav expected_id = id = conn->msg_id++; 592ae1f160dSDag-Erling Smørgrav send_string_request(conn->fd_out, id, SSH2_FXP_REALPATH, path, 593ae1f160dSDag-Erling Smørgrav strlen(path)); 5941e8db6e2SBrian Feldman 5951e8db6e2SBrian Feldman buffer_init(&msg); 5961e8db6e2SBrian Feldman 597ae1f160dSDag-Erling Smørgrav get_msg(conn->fd_in, &msg); 5981e8db6e2SBrian Feldman type = buffer_get_char(&msg); 5991e8db6e2SBrian Feldman id = buffer_get_int(&msg); 6001e8db6e2SBrian Feldman 6011e8db6e2SBrian Feldman if (id != expected_id) 602ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 6031e8db6e2SBrian Feldman 6041e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 6051e8db6e2SBrian Feldman u_int status = buffer_get_int(&msg); 6061e8db6e2SBrian Feldman 6071e8db6e2SBrian Feldman error("Couldn't canonicalise: %s", fx2txt(status)); 6081e8db6e2SBrian Feldman return(NULL); 6091e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 610ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 6111e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 6121e8db6e2SBrian Feldman 6131e8db6e2SBrian Feldman count = buffer_get_int(&msg); 6141e8db6e2SBrian Feldman if (count != 1) 6151e8db6e2SBrian Feldman fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count); 6161e8db6e2SBrian Feldman 6171e8db6e2SBrian Feldman filename = buffer_get_string(&msg, NULL); 6181e8db6e2SBrian Feldman longname = buffer_get_string(&msg, NULL); 6191e8db6e2SBrian Feldman a = decode_attrib(&msg); 6201e8db6e2SBrian Feldman 6211e8db6e2SBrian Feldman debug3("SSH_FXP_REALPATH %s -> %s", path, filename); 6221e8db6e2SBrian Feldman 6231e8db6e2SBrian Feldman xfree(longname); 6241e8db6e2SBrian Feldman 6251e8db6e2SBrian Feldman buffer_free(&msg); 6261e8db6e2SBrian Feldman 6271e8db6e2SBrian Feldman return(filename); 6281e8db6e2SBrian Feldman } 6291e8db6e2SBrian Feldman 6301e8db6e2SBrian Feldman int 631ae1f160dSDag-Erling Smørgrav do_rename(struct sftp_conn *conn, char *oldpath, char *newpath) 6321e8db6e2SBrian Feldman { 6331e8db6e2SBrian Feldman Buffer msg; 6341e8db6e2SBrian Feldman u_int status, id; 6351e8db6e2SBrian Feldman 6361e8db6e2SBrian Feldman buffer_init(&msg); 6371e8db6e2SBrian Feldman 6381e8db6e2SBrian Feldman /* Send rename request */ 639ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6401e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_RENAME); 6411e8db6e2SBrian Feldman buffer_put_int(&msg, id); 6421e8db6e2SBrian Feldman buffer_put_cstring(&msg, oldpath); 6431e8db6e2SBrian Feldman buffer_put_cstring(&msg, newpath); 644ae1f160dSDag-Erling Smørgrav send_msg(conn->fd_out, &msg); 6451e8db6e2SBrian Feldman debug3("Sent message SSH2_FXP_RENAME \"%s\" -> \"%s\"", oldpath, 6461e8db6e2SBrian Feldman newpath); 6471e8db6e2SBrian Feldman buffer_free(&msg); 6481e8db6e2SBrian Feldman 649ae1f160dSDag-Erling Smørgrav status = get_status(conn->fd_in, id); 6501e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 651ae1f160dSDag-Erling Smørgrav error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, 652ae1f160dSDag-Erling Smørgrav newpath, fx2txt(status)); 6531e8db6e2SBrian Feldman 6541e8db6e2SBrian Feldman return(status); 6551e8db6e2SBrian Feldman } 6561e8db6e2SBrian Feldman 6571e8db6e2SBrian Feldman int 658ae1f160dSDag-Erling Smørgrav do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath) 6591e8db6e2SBrian Feldman { 6601e8db6e2SBrian Feldman Buffer msg; 6611e8db6e2SBrian Feldman u_int status, id; 6621e8db6e2SBrian Feldman 663ae1f160dSDag-Erling Smørgrav if (conn->version < 3) { 664ae1f160dSDag-Erling Smørgrav error("This server does not support the symlink operation"); 665ae1f160dSDag-Erling Smørgrav return(SSH2_FX_OP_UNSUPPORTED); 666ae1f160dSDag-Erling Smørgrav } 667ae1f160dSDag-Erling Smørgrav 6681e8db6e2SBrian Feldman buffer_init(&msg); 6691e8db6e2SBrian Feldman 670d74d50a8SDag-Erling Smørgrav /* Send symlink request */ 671ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 6721e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_SYMLINK); 6731e8db6e2SBrian Feldman buffer_put_int(&msg, id); 6741e8db6e2SBrian Feldman buffer_put_cstring(&msg, oldpath); 6751e8db6e2SBrian Feldman buffer_put_cstring(&msg, newpath); 676ae1f160dSDag-Erling Smørgrav send_msg(conn->fd_out, &msg); 6771e8db6e2SBrian Feldman debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, 6781e8db6e2SBrian Feldman newpath); 6791e8db6e2SBrian Feldman buffer_free(&msg); 6801e8db6e2SBrian Feldman 681ae1f160dSDag-Erling Smørgrav status = get_status(conn->fd_in, id); 6821e8db6e2SBrian Feldman if (status != SSH2_FX_OK) 683d0c8c0bcSDag-Erling Smørgrav error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath, 684ae1f160dSDag-Erling Smørgrav newpath, fx2txt(status)); 6851e8db6e2SBrian Feldman 6861e8db6e2SBrian Feldman return(status); 6871e8db6e2SBrian Feldman } 6881e8db6e2SBrian Feldman 6891e8db6e2SBrian Feldman char * 690ae1f160dSDag-Erling Smørgrav do_readlink(struct sftp_conn *conn, char *path) 6911e8db6e2SBrian Feldman { 6921e8db6e2SBrian Feldman Buffer msg; 6931e8db6e2SBrian Feldman u_int type, expected_id, count, id; 6941e8db6e2SBrian Feldman char *filename, *longname; 6951e8db6e2SBrian Feldman Attrib *a; 6961e8db6e2SBrian Feldman 697ae1f160dSDag-Erling Smørgrav expected_id = id = conn->msg_id++; 698ae1f160dSDag-Erling Smørgrav send_string_request(conn->fd_out, id, SSH2_FXP_READLINK, path, 699ae1f160dSDag-Erling Smørgrav strlen(path)); 7001e8db6e2SBrian Feldman 7011e8db6e2SBrian Feldman buffer_init(&msg); 7021e8db6e2SBrian Feldman 703ae1f160dSDag-Erling Smørgrav get_msg(conn->fd_in, &msg); 7041e8db6e2SBrian Feldman type = buffer_get_char(&msg); 7051e8db6e2SBrian Feldman id = buffer_get_int(&msg); 7061e8db6e2SBrian Feldman 7071e8db6e2SBrian Feldman if (id != expected_id) 708ee21a45fSDag-Erling Smørgrav fatal("ID mismatch (%u != %u)", id, expected_id); 7091e8db6e2SBrian Feldman 7101e8db6e2SBrian Feldman if (type == SSH2_FXP_STATUS) { 7111e8db6e2SBrian Feldman u_int status = buffer_get_int(&msg); 7121e8db6e2SBrian Feldman 7131e8db6e2SBrian Feldman error("Couldn't readlink: %s", fx2txt(status)); 7141e8db6e2SBrian Feldman return(NULL); 7151e8db6e2SBrian Feldman } else if (type != SSH2_FXP_NAME) 716ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 7171e8db6e2SBrian Feldman SSH2_FXP_NAME, type); 7181e8db6e2SBrian Feldman 7191e8db6e2SBrian Feldman count = buffer_get_int(&msg); 7201e8db6e2SBrian Feldman if (count != 1) 7211e8db6e2SBrian Feldman fatal("Got multiple names (%d) from SSH_FXP_READLINK", count); 7221e8db6e2SBrian Feldman 7231e8db6e2SBrian Feldman filename = buffer_get_string(&msg, NULL); 7241e8db6e2SBrian Feldman longname = buffer_get_string(&msg, NULL); 7251e8db6e2SBrian Feldman a = decode_attrib(&msg); 7261e8db6e2SBrian Feldman 7271e8db6e2SBrian Feldman debug3("SSH_FXP_READLINK %s -> %s", path, filename); 7281e8db6e2SBrian Feldman 7291e8db6e2SBrian Feldman xfree(longname); 7301e8db6e2SBrian Feldman 7311e8db6e2SBrian Feldman buffer_free(&msg); 7321e8db6e2SBrian Feldman 7331e8db6e2SBrian Feldman return(filename); 7341e8db6e2SBrian Feldman } 7351e8db6e2SBrian Feldman 736ae1f160dSDag-Erling Smørgrav static void 737ae1f160dSDag-Erling Smørgrav send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len, 738ae1f160dSDag-Erling Smørgrav char *handle, u_int handle_len) 739ae1f160dSDag-Erling Smørgrav { 740ae1f160dSDag-Erling Smørgrav Buffer msg; 741ae1f160dSDag-Erling Smørgrav 742ae1f160dSDag-Erling Smørgrav buffer_init(&msg); 743ae1f160dSDag-Erling Smørgrav buffer_clear(&msg); 744ae1f160dSDag-Erling Smørgrav buffer_put_char(&msg, SSH2_FXP_READ); 745ae1f160dSDag-Erling Smørgrav buffer_put_int(&msg, id); 746ae1f160dSDag-Erling Smørgrav buffer_put_string(&msg, handle, handle_len); 747ae1f160dSDag-Erling Smørgrav buffer_put_int64(&msg, offset); 748ae1f160dSDag-Erling Smørgrav buffer_put_int(&msg, len); 749ae1f160dSDag-Erling Smørgrav send_msg(fd_out, &msg); 750ae1f160dSDag-Erling Smørgrav buffer_free(&msg); 751ae1f160dSDag-Erling Smørgrav } 752ae1f160dSDag-Erling Smørgrav 7531e8db6e2SBrian Feldman int 754ae1f160dSDag-Erling Smørgrav do_download(struct sftp_conn *conn, char *remote_path, char *local_path, 7551e8db6e2SBrian Feldman int pflag) 7561e8db6e2SBrian Feldman { 7571e8db6e2SBrian Feldman Attrib junk, *a; 758ae1f160dSDag-Erling Smørgrav Buffer msg; 759ae1f160dSDag-Erling Smørgrav char *handle; 760043840dfSDag-Erling Smørgrav int local_fd, status = 0, write_error; 761ae1f160dSDag-Erling Smørgrav int read_error, write_errno; 762ae1f160dSDag-Erling Smørgrav u_int64_t offset, size; 763043840dfSDag-Erling Smørgrav u_int handle_len, mode, type, id, buflen, num_req, max_req; 764d0c8c0bcSDag-Erling Smørgrav off_t progress_counter; 765ae1f160dSDag-Erling Smørgrav struct request { 766ae1f160dSDag-Erling Smørgrav u_int id; 767ae1f160dSDag-Erling Smørgrav u_int len; 768ae1f160dSDag-Erling Smørgrav u_int64_t offset; 769ae1f160dSDag-Erling Smørgrav TAILQ_ENTRY(request) tq; 770ae1f160dSDag-Erling Smørgrav }; 771ae1f160dSDag-Erling Smørgrav TAILQ_HEAD(reqhead, request) requests; 772ae1f160dSDag-Erling Smørgrav struct request *req; 7731e8db6e2SBrian Feldman 774ae1f160dSDag-Erling Smørgrav TAILQ_INIT(&requests); 775ae1f160dSDag-Erling Smørgrav 776ae1f160dSDag-Erling Smørgrav a = do_stat(conn, remote_path, 0); 7771e8db6e2SBrian Feldman if (a == NULL) 7781e8db6e2SBrian Feldman return(-1); 7791e8db6e2SBrian Feldman 7801e8db6e2SBrian Feldman /* XXX: should we preserve set[ug]id? */ 7811e8db6e2SBrian Feldman if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 782d0c8c0bcSDag-Erling Smørgrav mode = a->perm & 0777; 7831e8db6e2SBrian Feldman else 7841e8db6e2SBrian Feldman mode = 0666; 7851e8db6e2SBrian Feldman 7861e8db6e2SBrian Feldman if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 787d0c8c0bcSDag-Erling Smørgrav (!S_ISREG(a->perm))) { 788d0c8c0bcSDag-Erling Smørgrav error("Cannot download non-regular file: %s", remote_path); 7891e8db6e2SBrian Feldman return(-1); 7901e8db6e2SBrian Feldman } 7911e8db6e2SBrian Feldman 792ae1f160dSDag-Erling Smørgrav if (a->flags & SSH2_FILEXFER_ATTR_SIZE) 793ae1f160dSDag-Erling Smørgrav size = a->size; 794ae1f160dSDag-Erling Smørgrav else 795ae1f160dSDag-Erling Smørgrav size = 0; 7961e8db6e2SBrian Feldman 797ae1f160dSDag-Erling Smørgrav buflen = conn->transfer_buflen; 7981e8db6e2SBrian Feldman buffer_init(&msg); 7991e8db6e2SBrian Feldman 8001e8db6e2SBrian Feldman /* Send open request */ 801ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 8021e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_OPEN); 8031e8db6e2SBrian Feldman buffer_put_int(&msg, id); 8041e8db6e2SBrian Feldman buffer_put_cstring(&msg, remote_path); 8051e8db6e2SBrian Feldman buffer_put_int(&msg, SSH2_FXF_READ); 8061e8db6e2SBrian Feldman attrib_clear(&junk); /* Send empty attributes */ 8071e8db6e2SBrian Feldman encode_attrib(&msg, &junk); 808ae1f160dSDag-Erling Smørgrav send_msg(conn->fd_out, &msg); 809ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 8101e8db6e2SBrian Feldman 811ae1f160dSDag-Erling Smørgrav handle = get_handle(conn->fd_in, id, &handle_len); 8121e8db6e2SBrian Feldman if (handle == NULL) { 8131e8db6e2SBrian Feldman buffer_free(&msg); 814ae1f160dSDag-Erling Smørgrav return(-1); 815ae1f160dSDag-Erling Smørgrav } 816ae1f160dSDag-Erling Smørgrav 817d0c8c0bcSDag-Erling Smørgrav local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, 818d0c8c0bcSDag-Erling Smørgrav mode | S_IWRITE); 819ae1f160dSDag-Erling Smørgrav if (local_fd == -1) { 820ae1f160dSDag-Erling Smørgrav error("Couldn't open local file \"%s\" for writing: %s", 821ae1f160dSDag-Erling Smørgrav local_path, strerror(errno)); 822ae1f160dSDag-Erling Smørgrav buffer_free(&msg); 823ae1f160dSDag-Erling Smørgrav xfree(handle); 8241e8db6e2SBrian Feldman return(-1); 8251e8db6e2SBrian Feldman } 8261e8db6e2SBrian Feldman 8271e8db6e2SBrian Feldman /* Read from remote and write to local */ 828ae1f160dSDag-Erling Smørgrav write_error = read_error = write_errno = num_req = offset = 0; 829ae1f160dSDag-Erling Smørgrav max_req = 1; 830d0c8c0bcSDag-Erling Smørgrav progress_counter = 0; 831d0c8c0bcSDag-Erling Smørgrav 83252028650SDag-Erling Smørgrav if (showprogress && size != 0) 83352028650SDag-Erling Smørgrav start_progress_meter(remote_path, size, &progress_counter); 834d0c8c0bcSDag-Erling Smørgrav 835ae1f160dSDag-Erling Smørgrav while (num_req > 0 || max_req > 0) { 8361e8db6e2SBrian Feldman char *data; 837ae1f160dSDag-Erling Smørgrav u_int len; 8381e8db6e2SBrian Feldman 839d74d50a8SDag-Erling Smørgrav /* 840d74d50a8SDag-Erling Smørgrav * Simulate EOF on interrupt: stop sending new requests and 841d74d50a8SDag-Erling Smørgrav * allow outstanding requests to drain gracefully 842d74d50a8SDag-Erling Smørgrav */ 843d74d50a8SDag-Erling Smørgrav if (interrupted) { 844d74d50a8SDag-Erling Smørgrav if (num_req == 0) /* If we haven't started yet... */ 845d74d50a8SDag-Erling Smørgrav break; 846d74d50a8SDag-Erling Smørgrav max_req = 0; 847d74d50a8SDag-Erling Smørgrav } 848d74d50a8SDag-Erling Smørgrav 849ae1f160dSDag-Erling Smørgrav /* Send some more requests */ 850ae1f160dSDag-Erling Smørgrav while (num_req < max_req) { 851ae1f160dSDag-Erling Smørgrav debug3("Request range %llu -> %llu (%d/%d)", 852545d5ecaSDag-Erling Smørgrav (unsigned long long)offset, 853545d5ecaSDag-Erling Smørgrav (unsigned long long)offset + buflen - 1, 854545d5ecaSDag-Erling Smørgrav num_req, max_req); 855ae1f160dSDag-Erling Smørgrav req = xmalloc(sizeof(*req)); 856ae1f160dSDag-Erling Smørgrav req->id = conn->msg_id++; 857ae1f160dSDag-Erling Smørgrav req->len = buflen; 858ae1f160dSDag-Erling Smørgrav req->offset = offset; 859ae1f160dSDag-Erling Smørgrav offset += buflen; 860ae1f160dSDag-Erling Smørgrav num_req++; 861ae1f160dSDag-Erling Smørgrav TAILQ_INSERT_TAIL(&requests, req, tq); 862ae1f160dSDag-Erling Smørgrav send_read_request(conn->fd_out, req->id, req->offset, 863ae1f160dSDag-Erling Smørgrav req->len, handle, handle_len); 864ae1f160dSDag-Erling Smørgrav } 8651e8db6e2SBrian Feldman 8661e8db6e2SBrian Feldman buffer_clear(&msg); 867ae1f160dSDag-Erling Smørgrav get_msg(conn->fd_in, &msg); 8681e8db6e2SBrian Feldman type = buffer_get_char(&msg); 8691e8db6e2SBrian Feldman id = buffer_get_int(&msg); 870ee21a45fSDag-Erling Smørgrav debug3("Received reply T:%u I:%u R:%d", type, id, max_req); 8711e8db6e2SBrian Feldman 872ae1f160dSDag-Erling Smørgrav /* Find the request in our queue */ 873ae1f160dSDag-Erling Smørgrav for (req = TAILQ_FIRST(&requests); 874ae1f160dSDag-Erling Smørgrav req != NULL && req->id != id; 875ae1f160dSDag-Erling Smørgrav req = TAILQ_NEXT(req, tq)) 876ae1f160dSDag-Erling Smørgrav ; 877ae1f160dSDag-Erling Smørgrav if (req == NULL) 878ae1f160dSDag-Erling Smørgrav fatal("Unexpected reply %u", id); 879ae1f160dSDag-Erling Smørgrav 880ae1f160dSDag-Erling Smørgrav switch (type) { 881ae1f160dSDag-Erling Smørgrav case SSH2_FXP_STATUS: 882ae1f160dSDag-Erling Smørgrav status = buffer_get_int(&msg); 883ae1f160dSDag-Erling Smørgrav if (status != SSH2_FX_EOF) 884ae1f160dSDag-Erling Smørgrav read_error = 1; 885ae1f160dSDag-Erling Smørgrav max_req = 0; 886ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&requests, req, tq); 887ae1f160dSDag-Erling Smørgrav xfree(req); 888ae1f160dSDag-Erling Smørgrav num_req--; 8891e8db6e2SBrian Feldman break; 890ae1f160dSDag-Erling Smørgrav case SSH2_FXP_DATA: 891ae1f160dSDag-Erling Smørgrav data = buffer_get_string(&msg, &len); 892545d5ecaSDag-Erling Smørgrav debug3("Received data %llu -> %llu", 893545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset, 894545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + len - 1); 895ae1f160dSDag-Erling Smørgrav if (len > req->len) 896ae1f160dSDag-Erling Smørgrav fatal("Received more data than asked for " 897ee21a45fSDag-Erling Smørgrav "%u > %u", len, req->len); 898ae1f160dSDag-Erling Smørgrav if ((lseek(local_fd, req->offset, SEEK_SET) == -1 || 899d95e11bfSDag-Erling Smørgrav atomicio(vwrite, local_fd, data, len) != len) && 900ae1f160dSDag-Erling Smørgrav !write_error) { 901ae1f160dSDag-Erling Smørgrav write_errno = errno; 902ae1f160dSDag-Erling Smørgrav write_error = 1; 903ae1f160dSDag-Erling Smørgrav max_req = 0; 9041e8db6e2SBrian Feldman } 905d0c8c0bcSDag-Erling Smørgrav progress_counter += len; 906ae1f160dSDag-Erling Smørgrav xfree(data); 907ae1f160dSDag-Erling Smørgrav 908ae1f160dSDag-Erling Smørgrav if (len == req->len) { 909ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&requests, req, tq); 910ae1f160dSDag-Erling Smørgrav xfree(req); 911ae1f160dSDag-Erling Smørgrav num_req--; 912ae1f160dSDag-Erling Smørgrav } else { 913ae1f160dSDag-Erling Smørgrav /* Resend the request for the missing data */ 914ae1f160dSDag-Erling Smørgrav debug3("Short data block, re-requesting " 915545d5ecaSDag-Erling Smørgrav "%llu -> %llu (%2d)", 916545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + len, 917545d5ecaSDag-Erling Smørgrav (unsigned long long)req->offset + 918545d5ecaSDag-Erling Smørgrav req->len - 1, num_req); 919ae1f160dSDag-Erling Smørgrav req->id = conn->msg_id++; 920ae1f160dSDag-Erling Smørgrav req->len -= len; 921ae1f160dSDag-Erling Smørgrav req->offset += len; 922ae1f160dSDag-Erling Smørgrav send_read_request(conn->fd_out, req->id, 923ae1f160dSDag-Erling Smørgrav req->offset, req->len, handle, handle_len); 924ae1f160dSDag-Erling Smørgrav /* Reduce the request size */ 925ae1f160dSDag-Erling Smørgrav if (len < buflen) 926ae1f160dSDag-Erling Smørgrav buflen = MAX(MIN_READ_SIZE, len); 927ae1f160dSDag-Erling Smørgrav } 928ae1f160dSDag-Erling Smørgrav if (max_req > 0) { /* max_req = 0 iff EOF received */ 929ae1f160dSDag-Erling Smørgrav if (size > 0 && offset > size) { 930ae1f160dSDag-Erling Smørgrav /* Only one request at a time 931ae1f160dSDag-Erling Smørgrav * after the expected EOF */ 932ae1f160dSDag-Erling Smørgrav debug3("Finish at %llu (%2d)", 933545d5ecaSDag-Erling Smørgrav (unsigned long long)offset, 934545d5ecaSDag-Erling Smørgrav num_req); 935ae1f160dSDag-Erling Smørgrav max_req = 1; 936d74d50a8SDag-Erling Smørgrav } else if (max_req <= conn->num_requests) { 937ae1f160dSDag-Erling Smørgrav ++max_req; 938ae1f160dSDag-Erling Smørgrav } 939ae1f160dSDag-Erling Smørgrav } 940ae1f160dSDag-Erling Smørgrav break; 941ae1f160dSDag-Erling Smørgrav default: 942ee21a45fSDag-Erling Smørgrav fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", 9431e8db6e2SBrian Feldman SSH2_FXP_DATA, type); 9441e8db6e2SBrian Feldman } 945ae1f160dSDag-Erling Smørgrav } 9461e8db6e2SBrian Feldman 947d0c8c0bcSDag-Erling Smørgrav if (showprogress && size) 948d0c8c0bcSDag-Erling Smørgrav stop_progress_meter(); 949d0c8c0bcSDag-Erling Smørgrav 950ae1f160dSDag-Erling Smørgrav /* Sanity check */ 951ae1f160dSDag-Erling Smørgrav if (TAILQ_FIRST(&requests) != NULL) 952ae1f160dSDag-Erling Smørgrav fatal("Transfer complete, but requests still in queue"); 9531e8db6e2SBrian Feldman 954ae1f160dSDag-Erling Smørgrav if (read_error) { 955ae1f160dSDag-Erling Smørgrav error("Couldn't read from remote file \"%s\" : %s", 956ae1f160dSDag-Erling Smørgrav remote_path, fx2txt(status)); 957ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 958ae1f160dSDag-Erling Smørgrav } else if (write_error) { 9591e8db6e2SBrian Feldman error("Couldn't write to \"%s\": %s", local_path, 960ae1f160dSDag-Erling Smørgrav strerror(write_errno)); 9611e8db6e2SBrian Feldman status = -1; 962ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 963ae1f160dSDag-Erling Smørgrav } else { 964ae1f160dSDag-Erling Smørgrav status = do_close(conn, handle, handle_len); 9651e8db6e2SBrian Feldman 9661e8db6e2SBrian Feldman /* Override umask and utimes if asked */ 96783d2307dSDag-Erling Smørgrav #ifdef HAVE_FCHMOD 9681e8db6e2SBrian Feldman if (pflag && fchmod(local_fd, mode) == -1) 96983d2307dSDag-Erling Smørgrav #else 97083d2307dSDag-Erling Smørgrav if (pflag && chmod(local_path, mode) == -1) 97183d2307dSDag-Erling Smørgrav #endif /* HAVE_FCHMOD */ 9721e8db6e2SBrian Feldman error("Couldn't set mode on \"%s\": %s", local_path, 9731e8db6e2SBrian Feldman strerror(errno)); 9741e8db6e2SBrian Feldman if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { 9751e8db6e2SBrian Feldman struct timeval tv[2]; 9761e8db6e2SBrian Feldman tv[0].tv_sec = a->atime; 9771e8db6e2SBrian Feldman tv[1].tv_sec = a->mtime; 9781e8db6e2SBrian Feldman tv[0].tv_usec = tv[1].tv_usec = 0; 9791e8db6e2SBrian Feldman if (utimes(local_path, tv) == -1) 980ae1f160dSDag-Erling Smørgrav error("Can't set times on \"%s\": %s", 981ae1f160dSDag-Erling Smørgrav local_path, strerror(errno)); 9821e8db6e2SBrian Feldman } 983ae1f160dSDag-Erling Smørgrav } 9841e8db6e2SBrian Feldman close(local_fd); 9851e8db6e2SBrian Feldman buffer_free(&msg); 9861e8db6e2SBrian Feldman xfree(handle); 987ae1f160dSDag-Erling Smørgrav 988ae1f160dSDag-Erling Smørgrav return(status); 9891e8db6e2SBrian Feldman } 9901e8db6e2SBrian Feldman 9911e8db6e2SBrian Feldman int 992ae1f160dSDag-Erling Smørgrav do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, 9931e8db6e2SBrian Feldman int pflag) 9941e8db6e2SBrian Feldman { 995ae1f160dSDag-Erling Smørgrav int local_fd, status; 996ae1f160dSDag-Erling Smørgrav u_int handle_len, id, type; 9971e8db6e2SBrian Feldman u_int64_t offset; 998ae1f160dSDag-Erling Smørgrav char *handle, *data; 9991e8db6e2SBrian Feldman Buffer msg; 10001e8db6e2SBrian Feldman struct stat sb; 10011e8db6e2SBrian Feldman Attrib a; 1002ae1f160dSDag-Erling Smørgrav u_int32_t startid; 1003ae1f160dSDag-Erling Smørgrav u_int32_t ackid; 1004ae1f160dSDag-Erling Smørgrav struct outstanding_ack { 1005ae1f160dSDag-Erling Smørgrav u_int id; 1006ae1f160dSDag-Erling Smørgrav u_int len; 1007ae1f160dSDag-Erling Smørgrav u_int64_t offset; 1008ae1f160dSDag-Erling Smørgrav TAILQ_ENTRY(outstanding_ack) tq; 1009ae1f160dSDag-Erling Smørgrav }; 1010ae1f160dSDag-Erling Smørgrav TAILQ_HEAD(ackhead, outstanding_ack) acks; 1011d74d50a8SDag-Erling Smørgrav struct outstanding_ack *ack = NULL; 1012ae1f160dSDag-Erling Smørgrav 1013ae1f160dSDag-Erling Smørgrav TAILQ_INIT(&acks); 10141e8db6e2SBrian Feldman 10151e8db6e2SBrian Feldman if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) { 10161e8db6e2SBrian Feldman error("Couldn't open local file \"%s\" for reading: %s", 10171e8db6e2SBrian Feldman local_path, strerror(errno)); 10181e8db6e2SBrian Feldman return(-1); 10191e8db6e2SBrian Feldman } 10201e8db6e2SBrian Feldman if (fstat(local_fd, &sb) == -1) { 10211e8db6e2SBrian Feldman error("Couldn't fstat local file \"%s\": %s", 10221e8db6e2SBrian Feldman local_path, strerror(errno)); 10231e8db6e2SBrian Feldman close(local_fd); 10241e8db6e2SBrian Feldman return(-1); 10251e8db6e2SBrian Feldman } 1026d0c8c0bcSDag-Erling Smørgrav if (!S_ISREG(sb.st_mode)) { 1027d0c8c0bcSDag-Erling Smørgrav error("%s is not a regular file", local_path); 1028d0c8c0bcSDag-Erling Smørgrav close(local_fd); 1029d0c8c0bcSDag-Erling Smørgrav return(-1); 1030d0c8c0bcSDag-Erling Smørgrav } 10311e8db6e2SBrian Feldman stat_to_attrib(&sb, &a); 10321e8db6e2SBrian Feldman 10331e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 10341e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 10351e8db6e2SBrian Feldman a.perm &= 0777; 10361e8db6e2SBrian Feldman if (!pflag) 10371e8db6e2SBrian Feldman a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 10381e8db6e2SBrian Feldman 10391e8db6e2SBrian Feldman buffer_init(&msg); 10401e8db6e2SBrian Feldman 10411e8db6e2SBrian Feldman /* Send open request */ 1042ae1f160dSDag-Erling Smørgrav id = conn->msg_id++; 10431e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_OPEN); 10441e8db6e2SBrian Feldman buffer_put_int(&msg, id); 10451e8db6e2SBrian Feldman buffer_put_cstring(&msg, remote_path); 10461e8db6e2SBrian Feldman buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC); 10471e8db6e2SBrian Feldman encode_attrib(&msg, &a); 1048ae1f160dSDag-Erling Smørgrav send_msg(conn->fd_out, &msg); 1049ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 10501e8db6e2SBrian Feldman 10511e8db6e2SBrian Feldman buffer_clear(&msg); 10521e8db6e2SBrian Feldman 1053ae1f160dSDag-Erling Smørgrav handle = get_handle(conn->fd_in, id, &handle_len); 10541e8db6e2SBrian Feldman if (handle == NULL) { 10551e8db6e2SBrian Feldman close(local_fd); 10561e8db6e2SBrian Feldman buffer_free(&msg); 10571e8db6e2SBrian Feldman return(-1); 10581e8db6e2SBrian Feldman } 10591e8db6e2SBrian Feldman 1060ae1f160dSDag-Erling Smørgrav startid = ackid = id + 1; 1061ae1f160dSDag-Erling Smørgrav data = xmalloc(conn->transfer_buflen); 1062ae1f160dSDag-Erling Smørgrav 10631e8db6e2SBrian Feldman /* Read from local and write to remote */ 10641e8db6e2SBrian Feldman offset = 0; 1065d0c8c0bcSDag-Erling Smørgrav if (showprogress) 1066d0c8c0bcSDag-Erling Smørgrav start_progress_meter(local_path, sb.st_size, &offset); 1067d0c8c0bcSDag-Erling Smørgrav 10681e8db6e2SBrian Feldman for (;;) { 10691e8db6e2SBrian Feldman int len; 10701e8db6e2SBrian Feldman 10711e8db6e2SBrian Feldman /* 1072d74d50a8SDag-Erling Smørgrav * Can't use atomicio here because it returns 0 on EOF, 1073d74d50a8SDag-Erling Smørgrav * thus losing the last block of the file. 1074d74d50a8SDag-Erling Smørgrav * Simulate an EOF on interrupt, allowing ACKs from the 1075d74d50a8SDag-Erling Smørgrav * server to drain. 10761e8db6e2SBrian Feldman */ 1077d74d50a8SDag-Erling Smørgrav if (interrupted) 1078d74d50a8SDag-Erling Smørgrav len = 0; 1079d74d50a8SDag-Erling Smørgrav else do 1080ae1f160dSDag-Erling Smørgrav len = read(local_fd, data, conn->transfer_buflen); 10811e8db6e2SBrian Feldman while ((len == -1) && (errno == EINTR || errno == EAGAIN)); 10821e8db6e2SBrian Feldman 10831e8db6e2SBrian Feldman if (len == -1) 10841e8db6e2SBrian Feldman fatal("Couldn't read from \"%s\": %s", local_path, 10851e8db6e2SBrian Feldman strerror(errno)); 1086ae1f160dSDag-Erling Smørgrav 1087ae1f160dSDag-Erling Smørgrav if (len != 0) { 1088ae1f160dSDag-Erling Smørgrav ack = xmalloc(sizeof(*ack)); 1089ae1f160dSDag-Erling Smørgrav ack->id = ++id; 1090ae1f160dSDag-Erling Smørgrav ack->offset = offset; 1091ae1f160dSDag-Erling Smørgrav ack->len = len; 1092ae1f160dSDag-Erling Smørgrav TAILQ_INSERT_TAIL(&acks, ack, tq); 10931e8db6e2SBrian Feldman 10941e8db6e2SBrian Feldman buffer_clear(&msg); 10951e8db6e2SBrian Feldman buffer_put_char(&msg, SSH2_FXP_WRITE); 1096ae1f160dSDag-Erling Smørgrav buffer_put_int(&msg, ack->id); 10971e8db6e2SBrian Feldman buffer_put_string(&msg, handle, handle_len); 10981e8db6e2SBrian Feldman buffer_put_int64(&msg, offset); 10991e8db6e2SBrian Feldman buffer_put_string(&msg, data, len); 1100ae1f160dSDag-Erling Smørgrav send_msg(conn->fd_out, &msg); 1101ee21a45fSDag-Erling Smørgrav debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u", 1102545d5ecaSDag-Erling Smørgrav id, (unsigned long long)offset, len); 1103ae1f160dSDag-Erling Smørgrav } else if (TAILQ_FIRST(&acks) == NULL) 1104ae1f160dSDag-Erling Smørgrav break; 11051e8db6e2SBrian Feldman 1106ae1f160dSDag-Erling Smørgrav if (ack == NULL) 1107ae1f160dSDag-Erling Smørgrav fatal("Unexpected ACK %u", id); 1108ae1f160dSDag-Erling Smørgrav 1109ae1f160dSDag-Erling Smørgrav if (id == startid || len == 0 || 1110ae1f160dSDag-Erling Smørgrav id - ackid >= conn->num_requests) { 1111545d5ecaSDag-Erling Smørgrav u_int r_id; 1112545d5ecaSDag-Erling Smørgrav 1113ae1f160dSDag-Erling Smørgrav buffer_clear(&msg); 1114ae1f160dSDag-Erling Smørgrav get_msg(conn->fd_in, &msg); 1115ae1f160dSDag-Erling Smørgrav type = buffer_get_char(&msg); 1116545d5ecaSDag-Erling Smørgrav r_id = buffer_get_int(&msg); 1117ae1f160dSDag-Erling Smørgrav 1118ae1f160dSDag-Erling Smørgrav if (type != SSH2_FXP_STATUS) 1119ae1f160dSDag-Erling Smørgrav fatal("Expected SSH2_FXP_STATUS(%d) packet, " 1120ae1f160dSDag-Erling Smørgrav "got %d", SSH2_FXP_STATUS, type); 1121ae1f160dSDag-Erling Smørgrav 1122ae1f160dSDag-Erling Smørgrav status = buffer_get_int(&msg); 1123ae1f160dSDag-Erling Smørgrav debug3("SSH2_FXP_STATUS %d", status); 1124ae1f160dSDag-Erling Smørgrav 1125ae1f160dSDag-Erling Smørgrav /* Find the request in our queue */ 1126ae1f160dSDag-Erling Smørgrav for (ack = TAILQ_FIRST(&acks); 1127545d5ecaSDag-Erling Smørgrav ack != NULL && ack->id != r_id; 1128ae1f160dSDag-Erling Smørgrav ack = TAILQ_NEXT(ack, tq)) 1129ae1f160dSDag-Erling Smørgrav ; 1130ae1f160dSDag-Erling Smørgrav if (ack == NULL) 1131ee21a45fSDag-Erling Smørgrav fatal("Can't find request for ID %u", r_id); 1132ae1f160dSDag-Erling Smørgrav TAILQ_REMOVE(&acks, ack, tq); 1133ae1f160dSDag-Erling Smørgrav 11341e8db6e2SBrian Feldman if (status != SSH2_FX_OK) { 11351e8db6e2SBrian Feldman error("Couldn't write to remote file \"%s\": %s", 11361e8db6e2SBrian Feldman remote_path, fx2txt(status)); 1137ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 11381e8db6e2SBrian Feldman close(local_fd); 1139d0c8c0bcSDag-Erling Smørgrav xfree(data); 1140d0c8c0bcSDag-Erling Smørgrav xfree(ack); 11411e8db6e2SBrian Feldman goto done; 11421e8db6e2SBrian Feldman } 1143ee21a45fSDag-Erling Smørgrav debug3("In write loop, ack for %u %u bytes at %llu", 1144545d5ecaSDag-Erling Smørgrav ack->id, ack->len, (unsigned long long)ack->offset); 1145ae1f160dSDag-Erling Smørgrav ++ackid; 11464b17dab0SDag-Erling Smørgrav xfree(ack); 1147ae1f160dSDag-Erling Smørgrav } 11481e8db6e2SBrian Feldman offset += len; 11491e8db6e2SBrian Feldman } 1150d0c8c0bcSDag-Erling Smørgrav if (showprogress) 1151d0c8c0bcSDag-Erling Smørgrav stop_progress_meter(); 1152ae1f160dSDag-Erling Smørgrav xfree(data); 11531e8db6e2SBrian Feldman 11541e8db6e2SBrian Feldman if (close(local_fd) == -1) { 11551e8db6e2SBrian Feldman error("Couldn't close local file \"%s\": %s", local_path, 11561e8db6e2SBrian Feldman strerror(errno)); 1157ae1f160dSDag-Erling Smørgrav do_close(conn, handle, handle_len); 11581e8db6e2SBrian Feldman status = -1; 11591e8db6e2SBrian Feldman goto done; 11601e8db6e2SBrian Feldman } 11611e8db6e2SBrian Feldman 11621e8db6e2SBrian Feldman /* Override umask and utimes if asked */ 11631e8db6e2SBrian Feldman if (pflag) 1164ae1f160dSDag-Erling Smørgrav do_fsetstat(conn, handle, handle_len, &a); 11651e8db6e2SBrian Feldman 1166ae1f160dSDag-Erling Smørgrav status = do_close(conn, handle, handle_len); 11671e8db6e2SBrian Feldman 11681e8db6e2SBrian Feldman done: 11691e8db6e2SBrian Feldman xfree(handle); 11701e8db6e2SBrian Feldman buffer_free(&msg); 1171ae1f160dSDag-Erling Smørgrav return(status); 11721e8db6e2SBrian Feldman } 1173