| /* Copyright (c) 2004-2008, Sara Golemon <sarag@libssh2.org> |
| * Copyright (c) 2009 by Daniel Stenberg |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, |
| * with or without modification, are permitted provided |
| * that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the |
| * following disclaimer. |
| * |
| * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * Neither the name of the copyright holder nor the names |
| * of any other contributors may be used to endorse or |
| * promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
| * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY |
| * OF SUCH DAMAGE. |
| */ |
| |
| #include <assert.h> |
| |
| #include "libssh2_priv.h" |
| #include "libssh2_sftp.h" |
| #include "channel.h" |
| |
| /* Note: Version 6 was documented at the time of writing |
| * However it was marked as "DO NOT IMPLEMENT" due to pending changes |
| * |
| * This release of libssh2 implements Version 5 with automatic downgrade |
| * based on server's declaration |
| */ |
| |
| /* SFTP packet types */ |
| #define SSH_FXP_INIT 1 |
| #define SSH_FXP_VERSION 2 |
| #define SSH_FXP_OPEN 3 |
| #define SSH_FXP_CLOSE 4 |
| #define SSH_FXP_READ 5 |
| #define SSH_FXP_WRITE 6 |
| #define SSH_FXP_LSTAT 7 |
| #define SSH_FXP_FSTAT 8 |
| #define SSH_FXP_SETSTAT 9 |
| #define SSH_FXP_FSETSTAT 10 |
| #define SSH_FXP_OPENDIR 11 |
| #define SSH_FXP_READDIR 12 |
| #define SSH_FXP_REMOVE 13 |
| #define SSH_FXP_MKDIR 14 |
| #define SSH_FXP_RMDIR 15 |
| #define SSH_FXP_REALPATH 16 |
| #define SSH_FXP_STAT 17 |
| #define SSH_FXP_RENAME 18 |
| #define SSH_FXP_READLINK 19 |
| #define SSH_FXP_SYMLINK 20 |
| #define SSH_FXP_STATUS 101 |
| #define SSH_FXP_HANDLE 102 |
| #define SSH_FXP_DATA 103 |
| #define SSH_FXP_NAME 104 |
| #define SSH_FXP_ATTRS 105 |
| #define SSH_FXP_EXTENDED 200 |
| #define SSH_FXP_EXTENDED_REPLY 201 |
| |
| #define LIBSSH2_SFTP_HANDLE_FILE 0 |
| #define LIBSSH2_SFTP_HANDLE_DIR 1 |
| |
| /* S_IFREG */ |
| #define LIBSSH2_SFTP_ATTR_PFILETYPE_FILE 0100000 |
| /* S_IFDIR */ |
| #define LIBSSH2_SFTP_ATTR_PFILETYPE_DIR 0040000 |
| |
| static int sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle); |
| |
| /* libssh2_htonu64 |
| */ |
| static void |
| _libssh2_htonu64(unsigned char *buf, libssh2_uint64_t value) |
| { |
| unsigned long msl = (unsigned long)(value >> 32); |
| |
| buf[0] = (unsigned char)((msl >> 24) & 0xFF); |
| buf[1] = (unsigned char)((msl >> 16) & 0xFF); |
| buf[2] = (unsigned char)((msl >> 8) & 0xFF); |
| buf[3] = (unsigned char)( msl & 0xFF); |
| |
| buf[4] = (unsigned char)((value >> 24) & 0xFF); |
| buf[5] = (unsigned char)((value >> 16) & 0xFF); |
| buf[6] = (unsigned char)((value >> 8) & 0xFF); |
| buf[7] = (unsigned char)( value & 0xFF); |
| } |
| |
| /* |
| * sftp_packet_add |
| * |
| * Add a packet to the SFTP packet brigade |
| */ |
| static int |
| sftp_packet_add(LIBSSH2_SFTP *sftp, unsigned char *data, |
| unsigned long data_len) |
| { |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| LIBSSH2_PACKET *packet; |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Received packet %d (len %d)", |
| (int) data[0], data_len); |
| packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); |
| if (!packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate datablock for SFTP packet", 0); |
| return LIBSSH2_ERROR_ALLOC; |
| } |
| memset(packet, 0, sizeof(LIBSSH2_PACKET)); |
| |
| packet->data = data; |
| packet->data_len = data_len; |
| packet->data_head = 5; |
| |
| _libssh2_list_add(&sftp->packets, &packet->node); |
| |
| return 0; |
| } |
| |
| /* |
| * sftp_packet_read |
| * |
| * Frame an SFTP packet off the channel |
| */ |
| static int |
| sftp_packet_read(LIBSSH2_SFTP *sftp) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned char buffer[4]; /* To store the packet length */ |
| unsigned char *packet; |
| unsigned long packet_len, packet_received; |
| ssize_t bytes_received; |
| int rc; |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "recv packet"); |
| |
| /* If there was a previous partial, start using it */ |
| if (sftp->partial_packet) { |
| |
| packet = sftp->partial_packet; |
| packet_len = sftp->partial_len; |
| packet_received = sftp->partial_received; |
| sftp->partial_packet = NULL; |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "partial read cont, len: %lu", packet_len); |
| } |
| else { |
| rc = _libssh2_channel_read(channel, 0, (char *) buffer, 4); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } |
| else if (4 != rc) { |
| /* TODO: this is stupid since we can in fact get 1-3 bytes in a |
| legitimate working case as well if the connection happens to be |
| super slow or something */ |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, |
| "Read part of packet", 0); |
| return LIBSSH2_ERROR_CHANNEL_FAILURE; |
| } |
| |
| packet_len = _libssh2_ntohu32(buffer); |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Data begin - Packet Length: %lu", packet_len); |
| if (packet_len > LIBSSH2_SFTP_PACKET_MAXLEN) { |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, |
| "SFTP packet too large", 0); |
| return LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED; |
| } |
| |
| packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate SFTP packet", 0); |
| return LIBSSH2_ERROR_ALLOC; |
| } |
| |
| packet_received = 0; |
| } |
| |
| /* Read as much of the packet as we can */ |
| while (packet_len > packet_received) { |
| bytes_received = |
| _libssh2_channel_read(channel, 0, |
| (char *) packet + packet_received, |
| packet_len - packet_received); |
| |
| if (bytes_received == PACKET_EAGAIN) { |
| /* |
| * We received EAGAIN, save what we have and |
| * return to EAGAIN to the caller |
| */ |
| sftp->partial_packet = packet; |
| sftp->partial_len = packet_len; |
| sftp->partial_received = packet_received; |
| packet = NULL; |
| |
| return bytes_received; |
| } |
| else if (bytes_received < 0) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Receive error waiting for SFTP packet", 0); |
| LIBSSH2_FREE(session, packet); |
| return bytes_received; |
| } |
| packet_received += bytes_received; |
| } |
| |
| rc = sftp_packet_add(sftp, packet, packet_len); |
| if (rc) { |
| LIBSSH2_FREE(session, packet); |
| return rc; |
| } |
| |
| return packet[0]; |
| } |
| |
| /* |
| * sftp_packet_ask() |
| * |
| * Checks if there's a matching SFTP packet available. |
| */ |
| static int |
| sftp_packet_ask(LIBSSH2_SFTP *sftp, unsigned char packet_type, |
| unsigned long request_id, unsigned char **data, |
| unsigned long *data_len) |
| { |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| LIBSSH2_PACKET *packet = _libssh2_list_first(&sftp->packets); |
| unsigned char match_buf[5]; |
| int match_len; |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Asking for %d packet", |
| (int) packet_type); |
| |
| match_buf[0] = packet_type; |
| if (packet_type == SSH_FXP_VERSION) { |
| /* Special consideration when matching VERSION packet */ |
| match_len = 1; |
| } else { |
| match_len = 5; |
| _libssh2_htonu32(match_buf + 1, request_id); |
| } |
| |
| while (packet) { |
| if (!memcmp((char *) packet->data, (char *) match_buf, match_len)) { |
| |
| /* Match! Fetch the data */ |
| *data = packet->data; |
| *data_len = packet->data_len; |
| |
| /* unlink and free this struct */ |
| _libssh2_list_remove(&packet->node); |
| LIBSSH2_FREE(session, packet); |
| |
| return 0; |
| } |
| /* check next struct in the list */ |
| packet = _libssh2_list_next(&packet->node); |
| } |
| return -1; |
| } |
| |
| /* sftp_packet_require |
| * A la libssh2_packet_require |
| */ |
| static int |
| sftp_packet_require(LIBSSH2_SFTP *sftp, unsigned char packet_type, |
| unsigned long request_id, unsigned char **data, |
| unsigned long *data_len) |
| { |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| int ret; |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Requiring packet %d id %ld", |
| (int) packet_type, request_id); |
| |
| if (sftp_packet_ask(sftp, packet_type, request_id, data, data_len) == 0) { |
| /* The right packet was available in the packet brigade */ |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Got %d", |
| (int) packet_type); |
| return 0; |
| } |
| |
| while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { |
| ret = sftp_packet_read(sftp); |
| if (ret == PACKET_EAGAIN) { |
| return ret; |
| } else if (ret <= 0) { |
| return -1; |
| } |
| |
| /* data was read, check the queue again */ |
| if (!sftp_packet_ask(sftp, packet_type, request_id, data, data_len)) { |
| /* The right packet was available in the packet brigade */ |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Got %d", |
| (int) packet_type); |
| return 0; |
| } |
| } |
| |
| /* Only reached if the socket died */ |
| return LIBSSH2_ERROR_SOCKET_DISCONNECT; |
| } |
| |
| /* sftp_packet_requirev |
| * Require one of N possible reponses |
| */ |
| static int |
| sftp_packet_requirev(LIBSSH2_SFTP *sftp, int num_valid_responses, |
| const unsigned char *valid_responses, |
| unsigned long request_id, unsigned char **data, |
| unsigned long *data_len) |
| { |
| int i; |
| int ret; |
| |
| /* If no timeout is active, start a new one */ |
| if (sftp->requirev_start == 0) { |
| sftp->requirev_start = time(NULL); |
| } |
| |
| while (sftp->channel->session->socket_state == LIBSSH2_SOCKET_CONNECTED) { |
| for(i = 0; i < num_valid_responses; i++) { |
| if (sftp_packet_ask(sftp, valid_responses[i], request_id, |
| data, data_len) == 0) { |
| /* |
| * Set to zero before all returns to say |
| * the timeout is not active |
| */ |
| sftp->requirev_start = 0; |
| return 0; |
| } |
| } |
| |
| ret = sftp_packet_read(sftp); |
| if ((ret < 0) && (ret != PACKET_EAGAIN)) { |
| sftp->requirev_start = 0; |
| return -1; |
| } else if (ret <= 0) { |
| /* prevent busy-looping */ |
| long left = |
| LIBSSH2_READ_TIMEOUT - (long)(time(NULL) - sftp->requirev_start); |
| |
| if (left <= 0) { |
| sftp->requirev_start = 0; |
| return PACKET_TIMEOUT; |
| } |
| else if (ret == PACKET_EAGAIN) { |
| return ret; |
| } |
| } |
| } |
| |
| sftp->requirev_start = 0; |
| return -1; |
| } |
| |
| /* sftp_attrsize |
| * Size that attr will occupy when turned into a bin struct |
| */ |
| static int |
| sftp_attrsize(const LIBSSH2_SFTP_ATTRIBUTES * attrs) |
| { |
| int attrsize = 4; /* flags(4) */ |
| |
| if (!attrs) { |
| return attrsize; |
| } |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) |
| attrsize += 8; |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) |
| attrsize += 8; |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) |
| attrsize += 4; |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) |
| attrsize += 8; /* atime + mtime as u32 */ |
| |
| return attrsize; |
| } |
| |
| /* sftp_attr2bin |
| * Populate attributes into an SFTP block |
| */ |
| static int |
| sftp_attr2bin(unsigned char *p, const LIBSSH2_SFTP_ATTRIBUTES * attrs) |
| { |
| unsigned char *s = p; |
| unsigned long flag_mask = |
| LIBSSH2_SFTP_ATTR_SIZE | LIBSSH2_SFTP_ATTR_UIDGID | |
| LIBSSH2_SFTP_ATTR_PERMISSIONS | LIBSSH2_SFTP_ATTR_ACMODTIME; |
| |
| /* TODO: When we add SFTP4+ functionality flag_mask can get additional |
| bits */ |
| |
| if (!attrs) { |
| _libssh2_htonu32(s, 0); |
| return 4; |
| } |
| |
| _libssh2_htonu32(s, attrs->flags & flag_mask); |
| s += 4; |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { |
| _libssh2_htonu64(s, attrs->filesize); |
| s += 8; |
| } |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { |
| _libssh2_htonu32(s, attrs->uid); |
| s += 4; |
| _libssh2_htonu32(s, attrs->gid); |
| s += 4; |
| } |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { |
| _libssh2_htonu32(s, attrs->permissions); |
| s += 4; |
| } |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { |
| _libssh2_htonu32(s, attrs->atime); |
| s += 4; |
| _libssh2_htonu32(s, attrs->mtime); |
| s += 4; |
| } |
| |
| return (s - p); |
| } |
| |
| /* sftp_bin2attr |
| */ |
| static int |
| sftp_bin2attr(LIBSSH2_SFTP_ATTRIBUTES * attrs, const unsigned char *p) |
| { |
| const unsigned char *s = p; |
| |
| memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); |
| attrs->flags = _libssh2_ntohu32(s); |
| s += 4; |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { |
| attrs->filesize = _libssh2_ntohu64(s); |
| s += 8; |
| } |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { |
| attrs->uid = _libssh2_ntohu32(s); |
| s += 4; |
| attrs->gid = _libssh2_ntohu32(s); |
| s += 4; |
| } |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { |
| attrs->permissions = _libssh2_ntohu32(s); |
| s += 4; |
| } |
| |
| if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { |
| attrs->atime = _libssh2_ntohu32(s); |
| s += 4; |
| attrs->mtime = _libssh2_ntohu32(s); |
| s += 4; |
| } |
| |
| return (s - p); |
| } |
| |
| /* ************ |
| * SFTP API * |
| ************ */ |
| |
| LIBSSH2_CHANNEL_CLOSE_FUNC(libssh2_sftp_dtor); |
| |
| /* libssh2_sftp_dtor |
| * Shutdown an SFTP stream when the channel closes |
| */ |
| LIBSSH2_CHANNEL_CLOSE_FUNC(libssh2_sftp_dtor) |
| { |
| LIBSSH2_SFTP *sftp = (LIBSSH2_SFTP *) (*channel_abstract); |
| |
| (void) session_abstract; |
| (void) channel; |
| |
| /* Free the partial packet storage for sftp_packet_read */ |
| if (sftp->partial_packet) { |
| LIBSSH2_FREE(session, sftp->partial_packet); |
| } |
| |
| /* Free the packet storage for _libssh2_sftp_packet_readdir */ |
| if (sftp->readdir_packet) { |
| LIBSSH2_FREE(session, sftp->readdir_packet); |
| } |
| |
| LIBSSH2_FREE(session, sftp); |
| } |
| |
| /* |
| * sftp_init |
| * |
| * Startup an SFTP session |
| */ |
| static LIBSSH2_SFTP *sftp_init(LIBSSH2_SESSION *session) |
| { |
| unsigned char *data, *s; |
| unsigned long data_len; |
| int rc; |
| LIBSSH2_SFTP *sftp_handle; |
| |
| if (session->sftpInit_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Initializing SFTP subsystem"); |
| |
| /* |
| * The 'sftpInit_sftp' and 'sftpInit_channel' struct fields within the |
| * session struct are only to be used during the setup phase. As soon |
| * as the SFTP session is created they are cleared and can thus be |
| * re-used again to allow any amount of SFTP handles per sessions. |
| * |
| * Note that you MUST NOT try to call libssh2_sftp_init() again to get |
| * another handle until the previous call has finished and either |
| * succesffully made a handle or failed and returned error (not |
| * including *EAGAIN). |
| */ |
| |
| assert(session->sftpInit_sftp == NULL); |
| session->sftpInit_sftp = NULL; |
| session->sftpInit_state = libssh2_NB_state_created; |
| } |
| |
| sftp_handle = session->sftpInit_sftp; |
| |
| if (session->sftpInit_state == libssh2_NB_state_created) { |
| session->sftpInit_channel = |
| _libssh2_channel_open(session, "session", sizeof("session") - 1, |
| LIBSSH2_CHANNEL_WINDOW_DEFAULT, |
| LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL, 0); |
| if (!session->sftpInit_channel) { |
| if (libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) { |
| libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block starting up channel", 0); |
| } |
| else { |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, |
| "Unable to startup channel", 0); |
| session->sftpInit_state = libssh2_NB_state_idle; |
| } |
| return NULL; |
| } |
| |
| session->sftpInit_state = libssh2_NB_state_sent; |
| } |
| |
| if (session->sftpInit_state == libssh2_NB_state_sent) { |
| rc = _libssh2_channel_process_startup(session->sftpInit_channel, |
| "subsystem", |
| sizeof("subsystem") - 1, "sftp", |
| strlen("sftp")); |
| if (rc == PACKET_EAGAIN) { |
| libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block to request SFTP subsystem", 0); |
| return NULL; |
| } else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, |
| "Unable to request SFTP subsystem", 0); |
| goto sftp_init_error; |
| } |
| |
| session->sftpInit_state = libssh2_NB_state_sent1; |
| } |
| |
| if (session->sftpInit_state == libssh2_NB_state_sent1) { |
| rc = _libssh2_channel_extended_data(session->sftpInit_channel, |
| LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE); |
| if (rc == PACKET_EAGAIN) { |
| libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block requesting handle extended data", 0); |
| return NULL; |
| } |
| |
| sftp_handle = |
| session->sftpInit_sftp = |
| LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP)); |
| if (!sftp_handle) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate a new SFTP structure", 0); |
| goto sftp_init_error; |
| } |
| memset(sftp_handle, 0, sizeof(LIBSSH2_SFTP)); |
| sftp_handle->channel = session->sftpInit_channel; |
| sftp_handle->request_id = 0; |
| |
| _libssh2_htonu32(session->sftpInit_buffer, 5); |
| session->sftpInit_buffer[4] = SSH_FXP_INIT; |
| _libssh2_htonu32(session->sftpInit_buffer + 5, LIBSSH2_SFTP_VERSION); |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Sending FXP_INIT packet advertising version %d support", |
| (int) LIBSSH2_SFTP_VERSION); |
| |
| session->sftpInit_state = libssh2_NB_state_sent2; |
| } |
| |
| if (session->sftpInit_state == libssh2_NB_state_sent2) { |
| rc = _libssh2_channel_write(session->sftpInit_channel, 0, |
| (char *) session->sftpInit_buffer, 9); |
| if (rc == PACKET_EAGAIN) { |
| libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block sending SSH_FXP_INIT", 0); |
| return NULL; |
| } else if (9 != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send SSH_FXP_INIT", 0); |
| goto sftp_init_error; |
| } |
| |
| session->sftpInit_state = libssh2_NB_state_sent3; |
| } |
| |
| rc = sftp_packet_require(sftp_handle, SSH_FXP_VERSION, |
| 0, &data, &data_len); |
| if (rc == PACKET_EAGAIN) { |
| libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block waiting for response from SFTP subsystem", |
| 0); |
| return NULL; |
| } else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for response from SFTP subsystem", 0); |
| goto sftp_init_error; |
| } |
| if (data_len < 5) { |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Invalid SSH_FXP_VERSION response", 0); |
| goto sftp_init_error; |
| } |
| |
| s = data + 1; |
| sftp_handle->version = _libssh2_ntohu32(s); |
| s += 4; |
| if (sftp_handle->version > LIBSSH2_SFTP_VERSION) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Truncating remote SFTP version from %lu", |
| sftp_handle->version); |
| sftp_handle->version = LIBSSH2_SFTP_VERSION; |
| } |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Enabling SFTP version %lu compatability", |
| sftp_handle->version); |
| while (s < (data + data_len)) { |
| unsigned char *extension_name, *extension_data; |
| unsigned long extname_len, extdata_len; |
| |
| extname_len = _libssh2_ntohu32(s); |
| s += 4; |
| extension_name = s; |
| s += extname_len; |
| |
| extdata_len = _libssh2_ntohu32(s); |
| s += 4; |
| extension_data = s; |
| s += extdata_len; |
| |
| /* TODO: Actually process extensions */ |
| } |
| LIBSSH2_FREE(session, data); |
| |
| /* Make sure that when the channel gets closed, the SFTP service is shut |
| down too */ |
| sftp_handle->channel->abstract = sftp_handle; |
| sftp_handle->channel->close_cb = libssh2_sftp_dtor; |
| |
| session->sftpInit_state = libssh2_NB_state_idle; |
| |
| /* clear the sftp and channel pointers in this session struct now */ |
| session->sftpInit_sftp = NULL; |
| session->sftpInit_channel = NULL; |
| |
| _libssh2_list_init(&sftp_handle->sftp_handles); |
| |
| return sftp_handle; |
| |
| sftp_init_error: |
| while (_libssh2_channel_free(session->sftpInit_channel) == PACKET_EAGAIN); |
| session->sftpInit_channel = NULL; |
| if (session->sftpInit_sftp) { |
| LIBSSH2_FREE(session, session->sftpInit_sftp); |
| session->sftpInit_sftp = NULL; |
| } |
| session->sftpInit_state = libssh2_NB_state_idle; |
| return NULL; |
| } |
| |
| /* |
| * libssh2_sftp_init |
| * |
| * Startup an SFTP session |
| */ |
| LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) |
| { |
| LIBSSH2_SFTP *ptr; |
| BLOCK_ADJUST_ERRNO(ptr, session, sftp_init(session)); |
| return ptr; |
| } |
| |
| /* |
| * sftp_shutdown |
| * |
| * Shutsdown the SFTP subsystem |
| */ |
| static int |
| sftp_shutdown(LIBSSH2_SFTP *sftp) |
| { |
| int rc; |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| /* |
| * Make sure all memory used in the state variables are free |
| */ |
| if (sftp->partial_packet) { |
| LIBSSH2_FREE(session, sftp->partial_packet); |
| sftp->partial_packet = NULL; |
| } |
| if (sftp->open_packet) { |
| LIBSSH2_FREE(session, sftp->open_packet); |
| sftp->open_packet = NULL; |
| } |
| if (sftp->readdir_packet) { |
| LIBSSH2_FREE(session, sftp->readdir_packet); |
| sftp->readdir_packet = NULL; |
| } |
| if (sftp->write_packet) { |
| LIBSSH2_FREE(session, sftp->write_packet); |
| sftp->write_packet = NULL; |
| } |
| if (sftp->fstat_packet) { |
| LIBSSH2_FREE(session, sftp->fstat_packet); |
| sftp->fstat_packet = NULL; |
| } |
| if (sftp->unlink_packet) { |
| LIBSSH2_FREE(session, sftp->unlink_packet); |
| sftp->unlink_packet = NULL; |
| } |
| if (sftp->rename_packet) { |
| LIBSSH2_FREE(session, sftp->rename_packet); |
| sftp->rename_packet = NULL; |
| } |
| if (sftp->mkdir_packet) { |
| LIBSSH2_FREE(session, sftp->mkdir_packet); |
| sftp->mkdir_packet = NULL; |
| } |
| if (sftp->rmdir_packet) { |
| LIBSSH2_FREE(session, sftp->rmdir_packet); |
| sftp->rmdir_packet = NULL; |
| } |
| if (sftp->stat_packet) { |
| LIBSSH2_FREE(session, sftp->stat_packet); |
| sftp->stat_packet = NULL; |
| } |
| if (sftp->symlink_packet) { |
| LIBSSH2_FREE(session, sftp->symlink_packet); |
| sftp->symlink_packet = NULL; |
| } |
| |
| /* TODO: We should consider walking over the sftp_handles list and kill |
| * any remaining sftp handles ... */ |
| |
| rc = _libssh2_channel_free(sftp->channel); |
| |
| return rc; |
| } |
| |
| /* libssh2_sftp_shutdown |
| * Shutsdown the SFTP subsystem |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, sftp->channel->session, sftp_shutdown(sftp)); |
| return rc; |
| } |
| |
| /* ******************************* |
| * SFTP File and Directory Ops * |
| ******************************* */ |
| |
| /* sftp_open |
| */ |
| static LIBSSH2_SFTP_HANDLE * |
| sftp_open(LIBSSH2_SFTP *sftp, const char *filename, |
| unsigned int filename_len, unsigned long flags, long mode, |
| int open_type) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| LIBSSH2_SFTP_HANDLE *fp; |
| LIBSSH2_SFTP_ATTRIBUTES attrs = { |
| LIBSSH2_SFTP_ATTR_PERMISSIONS, 0, 0, 0, 0, 0, 0 |
| }; |
| unsigned long data_len; |
| unsigned char *data, *s; |
| static const unsigned char fopen_responses[2] = |
| { SSH_FXP_HANDLE, SSH_FXP_STATUS }; |
| int rc; |
| |
| if (sftp->open_state == libssh2_NB_state_idle) { |
| /* packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) + |
| flags(4) */ |
| sftp->open_packet_len = filename_len + 13 + |
| ((open_type == |
| LIBSSH2_SFTP_OPENFILE) ? (4 + sftp_attrsize(&attrs)) : 0); |
| |
| s = sftp->open_packet = LIBSSH2_ALLOC(session, sftp->open_packet_len); |
| if (!sftp->open_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_OPEN or " |
| "FXP_OPENDIR packet", |
| 0); |
| return NULL; |
| } |
| /* Filetype in SFTP 3 and earlier */ |
| attrs.permissions = mode | |
| ((open_type == |
| LIBSSH2_SFTP_OPENFILE) ? LIBSSH2_SFTP_ATTR_PFILETYPE_FILE : |
| LIBSSH2_SFTP_ATTR_PFILETYPE_DIR); |
| |
| _libssh2_htonu32(s, sftp->open_packet_len - 4); |
| s += 4; |
| *(s++) = |
| (open_type == |
| LIBSSH2_SFTP_OPENFILE) ? SSH_FXP_OPEN : SSH_FXP_OPENDIR; |
| sftp->open_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->open_request_id); |
| s += 4; |
| _libssh2_htonu32(s, filename_len); |
| s += 4; |
| memcpy(s, filename, filename_len); |
| s += filename_len; |
| if (open_type == LIBSSH2_SFTP_OPENFILE) { |
| _libssh2_htonu32(s, flags); |
| s += 4; |
| s += sftp_attr2bin(s, &attrs); |
| } |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Sending %s open request", |
| (open_type == |
| LIBSSH2_SFTP_OPENFILE) ? "file" : "directory"); |
| |
| sftp->open_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->open_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) sftp->open_packet, |
| sftp->open_packet_len); |
| if (rc == PACKET_EAGAIN) { |
| libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block sending FXP_OPEN or FXP_OPENDIR command", |
| 0); |
| return NULL; |
| } |
| else if (sftp->open_packet_len != rc) { |
| /* TODO: partial writes should be fine too and is not a sign of |
| an error when in non-blocking mode! */ |
| |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_OPEN or FXP_OPENDIR command", 0); |
| LIBSSH2_FREE(session, sftp->open_packet); |
| sftp->open_packet = NULL; |
| sftp->open_state = libssh2_NB_state_idle; |
| return NULL; |
| } |
| LIBSSH2_FREE(session, sftp->open_packet); |
| sftp->open_packet = NULL; |
| |
| sftp->open_state = libssh2_NB_state_sent; |
| } |
| |
| if (sftp->open_state == libssh2_NB_state_sent) { |
| rc = sftp_packet_requirev(sftp, 2, fopen_responses, |
| sftp->open_request_id, &data, |
| &data_len); |
| if (rc == PACKET_EAGAIN) { |
| libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block waiting for status message", 0); |
| return NULL; |
| } |
| else if (rc) { |
| libssh2_error(session, rc, |
| "Timeout waiting for status message", 0); |
| sftp->open_state = libssh2_NB_state_idle; |
| return NULL; |
| } |
| } |
| |
| sftp->open_state = libssh2_NB_state_idle; |
| |
| /* OPEN can basically get STATUS or HANDLE back, where HANDLE implies a |
| fine response while STATUS means error. It seems though that at times |
| we get an SSH_FX_OK back in a STATUS, followed the "real" HANDLE so |
| we need to properly deal with that. */ |
| if (data[0] == SSH_FXP_STATUS) { |
| int badness = 1; |
| sftp->last_errno = _libssh2_ntohu32(data + 5); |
| |
| if(LIBSSH2_FX_OK == sftp->last_errno) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "got HANDLE FXOK!"); |
| |
| /* silly situation, but check for a HANDLE */ |
| rc = sftp_packet_require(sftp, SSH_FXP_HANDLE, |
| sftp->open_request_id, &data, &data_len); |
| if(rc == PACKET_EAGAIN) { |
| /* go back to sent state and wait for something else */ |
| sftp->open_state = libssh2_NB_state_sent; |
| return NULL; |
| } |
| else if(!rc) |
| /* we got the handle so this is not a bad situation */ |
| badness = 0; |
| } |
| |
| if(badness) { |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Failed opening remote file", 0); |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "got FXP_STATUS %d", |
| sftp->last_errno); |
| LIBSSH2_FREE(session, data); |
| return NULL; |
| } |
| } |
| |
| fp = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP_HANDLE)); |
| if (!fp) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate new SFTP handle structure", 0); |
| LIBSSH2_FREE(session, data); |
| return NULL; |
| } |
| memset(fp, 0, sizeof(LIBSSH2_SFTP_HANDLE)); |
| fp->handle_type = |
| (open_type == |
| LIBSSH2_SFTP_OPENFILE) ? LIBSSH2_SFTP_HANDLE_FILE : |
| LIBSSH2_SFTP_HANDLE_DIR; |
| |
| fp->handle_len = _libssh2_ntohu32(data + 5); |
| if (fp->handle_len > SFTP_HANDLE_MAXLEN) { |
| /* SFTP doesn't allow handles longer than 256 characters */ |
| fp->handle_len = SFTP_HANDLE_MAXLEN; |
| } |
| |
| memcpy(fp->handle, data + 9, fp->handle_len); |
| LIBSSH2_FREE(session, data); |
| |
| /* add this file handle to the list kept in the sftp session */ |
| _libssh2_list_add(&sftp->sftp_handles, &fp->node); |
| |
| fp->sftp = sftp; /* point to the parent struct */ |
| |
| fp->u.file.offset = 0; |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Open command successful"); |
| return fp; |
| } |
| |
| /* libssh2_sftp_open_ex |
| */ |
| LIBSSH2_API LIBSSH2_SFTP_HANDLE * |
| libssh2_sftp_open_ex(LIBSSH2_SFTP *sftp, const char *filename, |
| unsigned int filename_len, unsigned long flags, long mode, |
| int open_type) |
| { |
| LIBSSH2_SFTP_HANDLE *hnd; |
| BLOCK_ADJUST_ERRNO(hnd, sftp->channel->session, |
| sftp_open(sftp, filename, filename_len, flags, mode, |
| open_type)); |
| return hnd; |
| } |
| |
| /* sftp_read |
| * Read from an SFTP file handle |
| */ |
| static ssize_t sftp_read(LIBSSH2_SFTP_HANDLE * handle, char *buffer, |
| size_t buffer_maxlen) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len, request_id = 0; |
| /* 25 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) + |
| offset(8) + length(4) */ |
| ssize_t packet_len = handle->handle_len + 25; |
| unsigned char *packet, *s, *data; |
| static const unsigned char read_responses[2] = |
| { SSH_FXP_DATA, SSH_FXP_STATUS }; |
| size_t bytes_read = 0; |
| size_t bytes_requested = 0; |
| size_t total_read = 0; |
| int retcode; |
| |
| if (sftp->read_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Reading %lu bytes from SFTP handle", |
| (unsigned long) buffer_maxlen); |
| packet = handle->request_packet; |
| sftp->read_state = libssh2_NB_state_allocated; |
| } else { |
| packet = sftp->read_packet; |
| request_id = sftp->read_request_id; |
| total_read = sftp->read_total_read; |
| } |
| |
| while (total_read < buffer_maxlen) { |
| s = packet; |
| /* |
| * If buffer_maxlen bytes will be requested, server may return all |
| * with one packet. But libssh2 have packet length limit. |
| * So we request data by pieces. |
| */ |
| bytes_requested = buffer_maxlen - total_read; |
| /* 10 = packet_type(1) + request_id(4) + data_length(4) + |
| end_of_line_flag(1) |
| |
| 10 is changed to 13 below simple because it seems there's a |
| "GlobalScape" SFTP server that responds with a slightly too big |
| buffer at times and we can apparently compensate for that by doing |
| this trick. |
| |
| Further details on this issue: |
| |
| https://sourceforge.net/mailarchive/forum.php?thread_name=9c3275a90811261517v6c0b1da2u918cc1b8370abf83%40mail.gmail.com&forum_name=libssh2-devel |
| |
| http://forums.globalscape.com/tm.aspx?m=15249 |
| |
| */ |
| if (bytes_requested > LIBSSH2_SFTP_PACKET_MAXLEN - 13) { |
| bytes_requested = LIBSSH2_SFTP_PACKET_MAXLEN - 13; |
| } |
| #ifdef LIBSSH2_DEBUG_SFTP |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Requesting %lu bytes from SFTP handle", |
| (unsigned long) bytes_requested); |
| #endif |
| |
| if (sftp->read_state == libssh2_NB_state_allocated) { |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| *(s++) = SSH_FXP_READ; |
| request_id = sftp->request_id++; |
| _libssh2_htonu32(s, request_id); |
| s += 4; |
| _libssh2_htonu32(s, handle->handle_len); |
| s += 4; |
| |
| memcpy(s, handle->handle, handle->handle_len); |
| s += handle->handle_len; |
| |
| _libssh2_htonu64(s, handle->u.file.offset); |
| s += 8; |
| |
| _libssh2_htonu32(s, bytes_requested); |
| s += 4; |
| |
| sftp->read_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->read_state == libssh2_NB_state_created) { |
| retcode = _libssh2_channel_write(channel, 0, (char *) packet, |
| packet_len); |
| if (retcode == PACKET_EAGAIN) { |
| sftp->read_packet = packet; |
| sftp->read_request_id = request_id; |
| sftp->read_total_read = total_read; |
| return retcode; |
| } else if (packet_len != retcode) { |
| /* TODO: a partial write is not a critical error when in |
| non-blocking mode! */ |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "_libssh2_channel_write() failed", 0); |
| sftp->read_packet = NULL; |
| sftp->read_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| sftp->read_packet = packet; |
| sftp->read_request_id = request_id; |
| sftp->read_total_read = total_read; |
| sftp->read_state = libssh2_NB_state_sent; |
| } |
| |
| if (sftp->read_state == libssh2_NB_state_sent) { |
| retcode = |
| sftp_packet_requirev(sftp, 2, read_responses, |
| request_id, &data, &data_len); |
| if (retcode == PACKET_EAGAIN) { |
| libssh2_error(session, retcode, |
| "Would block waiting for status message", 0); |
| return retcode; |
| } else if (retcode) { |
| libssh2_error(session, retcode, |
| "Timeout waiting for status message", 0); |
| sftp->read_packet = NULL; |
| sftp->read_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| sftp->read_state = libssh2_NB_state_sent1; |
| } |
| else |
| /* internal error, 'data' is not assigned */ |
| return -1; |
| |
| switch (data[0]) { |
| case SSH_FXP_STATUS: |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| sftp->read_packet = NULL; |
| sftp->read_state = libssh2_NB_state_idle; |
| |
| if (retcode == LIBSSH2_FX_EOF) { |
| return total_read; |
| } else { |
| sftp->last_errno = retcode; |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| return -1; |
| } |
| |
| case SSH_FXP_DATA: |
| bytes_read = _libssh2_ntohu32(data + 5); |
| if (bytes_read > (data_len - 9)) { |
| sftp->read_packet = NULL; |
| sftp->read_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| #ifdef LIBSSH2_DEBUG_SFTP |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "%lu bytes returned", |
| (unsigned long) bytes_read); |
| #endif |
| memcpy(buffer + total_read, data + 9, bytes_read); |
| handle->u.file.offset += bytes_read; |
| total_read += bytes_read; |
| LIBSSH2_FREE(session, data); |
| /* |
| * Set the state back to allocated, so a new one will be |
| * created to either request more data or get EOF |
| */ |
| sftp->read_state = libssh2_NB_state_allocated; |
| } |
| } |
| |
| sftp->read_packet = NULL; |
| sftp->read_state = libssh2_NB_state_idle; |
| return total_read; |
| } |
| |
| /* libssh2_sftp_read |
| * Read from an SFTP file handle |
| */ |
| LIBSSH2_API ssize_t |
| libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *hnd, char *buffer, |
| size_t buffer_maxlen) |
| { |
| ssize_t rc; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, |
| sftp_read(hnd, buffer, buffer_maxlen)); |
| return rc; |
| } |
| |
| /* sftp_readdir |
| * Read from an SFTP directory handle |
| */ |
| static int sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, char *buffer, |
| size_t buffer_maxlen, char *longentry, |
| size_t longentry_maxlen, |
| LIBSSH2_SFTP_ATTRIBUTES *attrs) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| LIBSSH2_SFTP_ATTRIBUTES attrs_dummy; |
| unsigned long data_len, filename_len, longentry_len, num_names; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ |
| ssize_t packet_len = handle->handle_len + 13; |
| unsigned char *s, *data; |
| unsigned char read_responses[2] = { SSH_FXP_NAME, SSH_FXP_STATUS }; |
| int retcode; |
| |
| if (sftp->readdir_state == libssh2_NB_state_idle) { |
| if (handle->u.dir.names_left) { |
| /* |
| * A prior request returned more than one directory entry, |
| * feed it back from the buffer |
| */ |
| unsigned char *s = (unsigned char *) handle->u.dir.next_name; |
| unsigned long real_filename_len = _libssh2_ntohu32(s); |
| |
| filename_len = real_filename_len; |
| s += 4; |
| if (filename_len > buffer_maxlen) { |
| filename_len = buffer_maxlen; |
| } |
| memcpy(buffer, s, filename_len); |
| s += real_filename_len; |
| |
| /* The filename is not null terminated, make it so if possible */ |
| if (filename_len < buffer_maxlen) { |
| buffer[filename_len] = '\0'; |
| } |
| |
| if ((longentry == NULL) || (longentry_maxlen == 0)) { |
| /* Skip longname */ |
| s += 4 + _libssh2_ntohu32(s); |
| } else { |
| unsigned long real_longentry_len = _libssh2_ntohu32(s); |
| |
| longentry_len = real_longentry_len; |
| s += 4; |
| if (longentry_len > longentry_maxlen) { |
| longentry_len = longentry_maxlen; |
| } |
| memcpy(longentry, s, longentry_len); |
| s += real_longentry_len; |
| |
| /* The longentry is not null terminated, make it so if possible */ |
| if (longentry_len < longentry_maxlen) { |
| longentry[longentry_len] = '\0'; |
| } |
| } |
| |
| if (attrs) { |
| memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); |
| } |
| s += sftp_bin2attr(attrs ? attrs : &attrs_dummy, s); |
| |
| handle->u.dir.next_name = (char *) s; |
| if ((--handle->u.dir.names_left) == 0) { |
| LIBSSH2_FREE(session, handle->u.dir.names_packet); |
| } |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "libssh2_sftp_readdir_ex() return %d", |
| filename_len); |
| return filename_len; |
| } |
| |
| /* Request another entry(entries?) */ |
| |
| s = sftp->readdir_packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!sftp->readdir_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_READDIR packet", |
| 0); |
| return -1; |
| } |
| |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| *(s++) = SSH_FXP_READDIR; |
| sftp->readdir_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->readdir_request_id); |
| s += 4; |
| _libssh2_htonu32(s, handle->handle_len); |
| s += 4; |
| memcpy(s, handle->handle, handle->handle_len); |
| s += handle->handle_len; |
| |
| sftp->readdir_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->readdir_state == libssh2_NB_state_created) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Reading entries from directory handle"); |
| retcode = _libssh2_channel_write(channel, 0, |
| (char *) sftp->readdir_packet, |
| packet_len); |
| if (retcode == PACKET_EAGAIN) { |
| return retcode; |
| } |
| else if (packet_len != retcode) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "_libssh2_channel_write() failed", 0); |
| LIBSSH2_FREE(session, sftp->readdir_packet); |
| sftp->readdir_packet = NULL; |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| LIBSSH2_FREE(session, sftp->readdir_packet); |
| sftp->readdir_packet = NULL; |
| |
| sftp->readdir_state = libssh2_NB_state_sent; |
| } |
| |
| retcode = |
| sftp_packet_requirev(sftp, 2, read_responses, |
| sftp->readdir_request_id, &data, |
| &data_len); |
| if (retcode == PACKET_EAGAIN) { |
| return retcode; |
| } else if (retcode) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| if (data[0] == SSH_FXP_STATUS) { |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| if (retcode == LIBSSH2_FX_EOF) { |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return 0; |
| } else { |
| sftp->last_errno = retcode; |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| } |
| |
| num_names = _libssh2_ntohu32(data + 5); |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "%lu entries returned", |
| num_names); |
| if (num_names <= 0) { |
| LIBSSH2_FREE(session, data); |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return (num_names == 0) ? 0 : -1; |
| } |
| |
| if (num_names == 1) { |
| unsigned long real_filename_len = _libssh2_ntohu32(data + 9); |
| |
| filename_len = real_filename_len; |
| if (filename_len > buffer_maxlen) { |
| filename_len = buffer_maxlen; |
| } |
| memcpy(buffer, data + 13, filename_len); |
| |
| /* The filename is not null terminated, make it so if possible */ |
| if (filename_len < buffer_maxlen) { |
| buffer[filename_len] = '\0'; |
| } |
| |
| if (attrs) { |
| memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); |
| sftp_bin2attr(attrs, data + 13 + real_filename_len + |
| (4 + _libssh2_ntohu32(data + 13 + |
| real_filename_len))); |
| } |
| LIBSSH2_FREE(session, data); |
| |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return filename_len; |
| } |
| |
| handle->u.dir.names_left = num_names; |
| handle->u.dir.names_packet = data; |
| handle->u.dir.next_name = (char *) data + 9; |
| |
| sftp->readdir_state = libssh2_NB_state_idle; |
| |
| /* Be lazy, just use the name popping mechanism from the start of the |
| function */ |
| return libssh2_sftp_readdir_ex(handle, buffer, buffer_maxlen, longentry, |
| longentry_maxlen, attrs); |
| } |
| |
| /* libssh2_sftp_readdir_ex |
| * Read from an SFTP directory handle |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_readdir_ex(LIBSSH2_SFTP_HANDLE *hnd, char *buffer, |
| size_t buffer_maxlen, char *longentry, |
| size_t longentry_maxlen, |
| LIBSSH2_SFTP_ATTRIBUTES *attrs) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, |
| sftp_readdir(hnd, buffer, buffer_maxlen, longentry, |
| longentry_maxlen, attrs)); |
| return rc; |
| } |
| |
| /* |
| * sftp_write |
| * |
| * Write data to an SFTP handle. Returns the number of bytes written, or |
| * a negative error code. |
| */ |
| static ssize_t sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer, |
| size_t count) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len, retcode; |
| /* 25 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) + |
| offset(8) + count(4) */ |
| ssize_t packet_len; |
| unsigned char *s, *data; |
| int rc; |
| |
| /* There's no point in us accepting a VERY large packet here since we |
| cannot send it anyway. We just accept 4 times the big size to fill up |
| the queue somewhat. */ |
| |
| if(count > (MAX_SSH_PACKET_LEN*4)) |
| count = MAX_SSH_PACKET_LEN*4; |
| |
| packet_len = handle->handle_len + count + 25; |
| |
| if (sftp->write_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Writing %lu bytes", |
| (unsigned long) count); |
| s = sftp->write_packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!sftp->write_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_WRITE", 0); |
| return LIBSSH2_ERROR_ALLOC; |
| } |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| *(s++) = SSH_FXP_WRITE; |
| sftp->write_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->write_request_id); |
| s += 4; |
| _libssh2_htonu32(s, handle->handle_len); |
| s += 4; |
| memcpy(s, handle->handle, handle->handle_len); |
| s += handle->handle_len; |
| _libssh2_htonu64(s, handle->u.file.offset); |
| s += 8; |
| _libssh2_htonu32(s, count); |
| s += 4; |
| memcpy(s, buffer, count); |
| s += count; |
| |
| sftp->write_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->write_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *)sftp->write_packet, |
| packet_len); |
| if(rc < 0) { |
| /* error */ |
| return rc; |
| } |
| else if(0 == rc) { |
| /* nothing sent is an error */ |
| return LIBSSH2_ERROR_SOCKET_SEND; |
| } |
| else if (packet_len != rc) { |
| return rc; |
| } |
| LIBSSH2_FREE(session, sftp->write_packet); |
| sftp->write_packet = NULL; |
| sftp->write_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, |
| sftp->write_request_id, &data, &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } |
| else if (rc) { |
| libssh2_error(session, rc, |
| "Timeout waiting for status message", 0); |
| sftp->write_state = libssh2_NB_state_idle; |
| return rc; |
| } |
| |
| sftp->write_state = libssh2_NB_state_idle; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| if (retcode == LIBSSH2_FX_OK) { |
| handle->u.file.offset += count; |
| return count; |
| } |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "SFTP Protocol Error", |
| 0); |
| sftp->last_errno = retcode; |
| |
| return LIBSSH2_ERROR_SFTP_PROTOCOL; |
| } |
| |
| /* libssh2_sftp_write |
| * Write data to a file handle |
| */ |
| LIBSSH2_API ssize_t |
| libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *hnd, const char *buffer, |
| size_t count) |
| { |
| ssize_t rc; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, |
| sftp_write(hnd, buffer, count)); |
| return rc; |
| |
| } |
| |
| /* |
| * sftp_fstat |
| * |
| * Get or Set stat on a file |
| */ |
| static int sftp_fstat(LIBSSH2_SFTP_HANDLE *handle, |
| LIBSSH2_SFTP_ATTRIBUTES *attrs, int setstat) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ |
| ssize_t packet_len = |
| handle->handle_len + 13 + (setstat ? sftp_attrsize(attrs) : 0); |
| unsigned char *s, *data; |
| static const unsigned char fstat_responses[2] = |
| { SSH_FXP_ATTRS, SSH_FXP_STATUS }; |
| int rc; |
| |
| if (sftp->fstat_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Issuing %s command", |
| setstat ? "set-stat" : "stat"); |
| s = sftp->fstat_packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!sftp->fstat_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FSTAT/FSETSTAT packet", |
| 0); |
| return -1; |
| } |
| |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| *(s++) = setstat ? SSH_FXP_FSETSTAT : SSH_FXP_FSTAT; |
| sftp->fstat_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->fstat_request_id); |
| s += 4; |
| _libssh2_htonu32(s, handle->handle_len); |
| s += 4; |
| memcpy(s, handle->handle, handle->handle_len); |
| s += handle->handle_len; |
| if (setstat) { |
| s += sftp_attr2bin(s, attrs); |
| } |
| |
| sftp->fstat_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->fstat_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) sftp->fstat_packet, |
| packet_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (packet_len != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| (setstat ? "Unable to send FXP_FSETSTAT" |
| : "Unable to send FXP_FSTAT command"), 0); |
| LIBSSH2_FREE(session, sftp->fstat_packet); |
| sftp->fstat_packet = NULL; |
| sftp->fstat_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| LIBSSH2_FREE(session, sftp->fstat_packet); |
| sftp->fstat_packet = NULL; |
| |
| sftp->fstat_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_requirev(sftp, 2, fstat_responses, |
| sftp->fstat_request_id, &data, |
| &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| sftp->fstat_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| sftp->fstat_state = libssh2_NB_state_idle; |
| |
| if (data[0] == SSH_FXP_STATUS) { |
| int retcode; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| if (retcode == LIBSSH2_FX_OK) { |
| return 0; |
| } else { |
| sftp->last_errno = retcode; |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| return -1; |
| } |
| } |
| |
| sftp_bin2attr(attrs, data + 5); |
| |
| return 0; |
| } |
| |
| /* libssh2_sftp_fstat_ex |
| * Get or Set stat on a file |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_fstat_ex(LIBSSH2_SFTP_HANDLE * hnd, |
| LIBSSH2_SFTP_ATTRIBUTES *attrs, int setstat) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, |
| sftp_fstat(hnd, attrs, setstat)); |
| return rc; |
| } |
| |
| /* libssh2_sftp_seek |
| * Set the read/write pointer to an arbitrary position within the file |
| */ |
| LIBSSH2_API void |
| libssh2_sftp_seek(LIBSSH2_SFTP_HANDLE * handle, size_t offset) |
| { |
| handle->u.file.offset = offset; |
| } |
| |
| /* libssh2_sftp_seek64 |
| * Set the read/write pointer to an arbitrary position within the file |
| */ |
| LIBSSH2_API void |
| libssh2_sftp_seek64(LIBSSH2_SFTP_HANDLE * handle, libssh2_uint64_t offset) |
| { |
| handle->u.file.offset = offset; |
| } |
| |
| /* libssh2_sftp_tell |
| * Return the current read/write pointer's offset |
| */ |
| LIBSSH2_API size_t |
| libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE * handle) |
| { |
| /* NOTE: this may very well truncate the size if it is larger than what |
| size_t can hold, so libssh2_sftp_tell64() is really the function you |
| should use */ |
| return (size_t)(handle->u.file.offset); |
| } |
| |
| /* libssh2_sftp_tell64 |
| * Return the current read/write pointer's offset |
| */ |
| LIBSSH2_API libssh2_uint64_t |
| libssh2_sftp_tell64(LIBSSH2_SFTP_HANDLE * handle) |
| { |
| return handle->u.file.offset; |
| } |
| |
| /* sftp_close_handle |
| * |
| * Close a file or directory handle |
| * Also frees handle resource and unlinks it from the SFTP structure |
| */ |
| static int |
| sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len, retcode; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ |
| ssize_t packet_len = handle->handle_len + 13; |
| unsigned char *s, *data; |
| int rc; |
| |
| if (handle->close_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Closing handle"); |
| s = handle->close_packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!handle->close_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_CLOSE packet", 0); |
| return -1; |
| } |
| |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| *(s++) = SSH_FXP_CLOSE; |
| handle->close_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, handle->close_request_id); |
| s += 4; |
| _libssh2_htonu32(s, handle->handle_len); |
| s += 4; |
| memcpy(s, handle->handle, handle->handle_len); |
| s += handle->handle_len; |
| |
| handle->close_state = libssh2_NB_state_created; |
| } |
| |
| if (handle->close_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) handle->close_packet, |
| packet_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (packet_len != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_CLOSE command", 0); |
| LIBSSH2_FREE(session, handle->close_packet); |
| handle->close_packet = NULL; |
| handle->close_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| LIBSSH2_FREE(session, handle->close_packet); |
| handle->close_packet = NULL; |
| |
| handle->close_state = libssh2_NB_state_sent; |
| } |
| |
| if (handle->close_state == libssh2_NB_state_sent) { |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, |
| handle->close_request_id, &data, |
| &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| handle->close_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| handle->close_state = libssh2_NB_state_sent1; |
| } |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| if (retcode != LIBSSH2_FX_OK) { |
| sftp->last_errno = retcode; |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| handle->close_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| /* remove this handle from the parent's list */ |
| _libssh2_list_remove(&handle->node); |
| |
| if ((handle->handle_type == LIBSSH2_SFTP_HANDLE_DIR) |
| && handle->u.dir.names_left) { |
| LIBSSH2_FREE(session, handle->u.dir.names_packet); |
| } |
| |
| handle->close_state = libssh2_NB_state_idle; |
| |
| LIBSSH2_FREE(session, handle); |
| |
| return 0; |
| } |
| |
| /* libssh2_sftp_close_handle |
| * |
| * Close a file or directory handle |
| * Also frees handle resource and unlinks it from the SFTP structure |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_close_handle(LIBSSH2_SFTP_HANDLE *hnd) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, sftp_close_handle(hnd)); |
| return rc; |
| } |
| |
| /* sftp_unlink |
| * Delete a file from the remote server |
| */ |
| static int sftp_unlink(LIBSSH2_SFTP *sftp, const char *filename, |
| unsigned int filename_len) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len, retcode; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) */ |
| ssize_t packet_len = filename_len + 13; |
| unsigned char *s, *data; |
| int rc; |
| |
| if (sftp->unlink_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Unlinking %s", filename); |
| s = sftp->unlink_packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!sftp->unlink_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_REMOVE packet", |
| 0); |
| return -1; |
| } |
| |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| *(s++) = SSH_FXP_REMOVE; |
| sftp->unlink_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->unlink_request_id); |
| s += 4; |
| _libssh2_htonu32(s, filename_len); |
| s += 4; |
| memcpy(s, filename, filename_len); |
| s += filename_len; |
| |
| sftp->unlink_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->unlink_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) sftp->unlink_packet, |
| packet_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (packet_len != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_REMOVE command", 0); |
| LIBSSH2_FREE(session, sftp->unlink_packet); |
| sftp->unlink_packet = NULL; |
| sftp->unlink_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| LIBSSH2_FREE(session, sftp->unlink_packet); |
| sftp->unlink_packet = NULL; |
| |
| sftp->unlink_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, |
| sftp->unlink_request_id, &data, |
| &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } |
| else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| sftp->unlink_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| sftp->unlink_state = libssh2_NB_state_idle; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| if (retcode == LIBSSH2_FX_OK) { |
| return 0; |
| } else { |
| sftp->last_errno = retcode; |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| return -1; |
| } |
| } |
| |
| /* libssh2_sftp_unlink_ex |
| * Delete a file from the remote server |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, const char *filename, |
| unsigned int filename_len) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, sftp->channel->session, |
| sftp_unlink(sftp, filename, filename_len)); |
| return rc; |
| } |
| |
| /* |
| * sftp_rename |
| * |
| * Rename a file on the remote server |
| */ |
| static int sftp_rename(LIBSSH2_SFTP *sftp, const char *source_filename, |
| unsigned int source_filename_len, |
| const char *dest_filename, |
| unsigned int dest_filename_len, long flags) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len; |
| int retcode; |
| ssize_t packet_len = |
| source_filename_len + dest_filename_len + 17 + (sftp->version >= |
| 5 ? 4 : 0); |
| /* packet_len(4) + packet_type(1) + request_id(4) + |
| source_filename_len(4) + dest_filename_len(4) + flags(4){SFTP5+) */ |
| unsigned char *data; |
| int rc; |
| |
| if (sftp->version < 2) { |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Server does not support RENAME", 0); |
| return -1; |
| } |
| |
| if (sftp->rename_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Renaming %s to %s", |
| source_filename, dest_filename); |
| sftp->rename_s = sftp->rename_packet = |
| LIBSSH2_ALLOC(session, packet_len); |
| if (!sftp->rename_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_RENAME packet", |
| 0); |
| return -1; |
| } |
| |
| _libssh2_htonu32(sftp->rename_s, packet_len - 4); |
| sftp->rename_s += 4; |
| *(sftp->rename_s++) = SSH_FXP_RENAME; |
| sftp->rename_request_id = sftp->request_id++; |
| _libssh2_htonu32(sftp->rename_s, sftp->rename_request_id); |
| sftp->rename_s += 4; |
| _libssh2_htonu32(sftp->rename_s, source_filename_len); |
| sftp->rename_s += 4; |
| memcpy(sftp->rename_s, source_filename, source_filename_len); |
| sftp->rename_s += source_filename_len; |
| _libssh2_htonu32(sftp->rename_s, dest_filename_len); |
| sftp->rename_s += 4; |
| memcpy(sftp->rename_s, dest_filename, dest_filename_len); |
| sftp->rename_s += dest_filename_len; |
| |
| if (sftp->version >= 5) { |
| _libssh2_htonu32(sftp->rename_s, flags); |
| sftp->rename_s += 4; |
| } |
| |
| sftp->rename_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->rename_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) sftp->rename_packet, |
| sftp->rename_s - sftp->rename_packet); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (packet_len != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_RENAME command", 0); |
| LIBSSH2_FREE(session, sftp->rename_packet); |
| sftp->rename_packet = NULL; |
| sftp->rename_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| LIBSSH2_FREE(session, sftp->rename_packet); |
| sftp->rename_packet = NULL; |
| |
| sftp->rename_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, |
| sftp->rename_request_id, &data, |
| &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| sftp->rename_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| sftp->rename_state = libssh2_NB_state_idle; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| switch (retcode) { |
| case LIBSSH2_FX_OK: |
| retcode = 0; |
| break; |
| |
| case LIBSSH2_FX_FILE_ALREADY_EXISTS: |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "File already exists and SSH_FXP_RENAME_OVERWRITE not specified", |
| 0); |
| sftp->last_errno = retcode; |
| retcode = -1; |
| break; |
| |
| case LIBSSH2_FX_OP_UNSUPPORTED: |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Operation Not Supported", 0); |
| sftp->last_errno = retcode; |
| retcode = -1; |
| break; |
| |
| default: |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| sftp->last_errno = retcode; |
| retcode = -1; |
| } |
| |
| return retcode; |
| } |
| |
| /* libssh2_sftp_rename_ex |
| * Rename a file on the remote server |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, const char *source_filename, |
| unsigned int source_filename_len, |
| const char *dest_filename, |
| unsigned int dest_filename_len, long flags) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, sftp->channel->session, |
| sftp_rename(sftp, source_filename, source_filename_len, |
| dest_filename, dest_filename_len, flags)); |
| return rc; |
| } |
| |
| /* |
| * sftp_mkdir |
| * |
| * Create an SFTP directory |
| */ |
| static int sftp_mkdir(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len, long mode) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| LIBSSH2_SFTP_ATTRIBUTES attrs = { |
| LIBSSH2_SFTP_ATTR_PERMISSIONS, 0, 0, 0, 0, 0, 0 |
| }; |
| unsigned long data_len, retcode; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ |
| ssize_t packet_len = path_len + 13 + sftp_attrsize(&attrs); |
| unsigned char *packet, *s, *data; |
| int rc; |
| |
| if (sftp->mkdir_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, |
| "Creating directory %s with mode 0%lo", path, mode); |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_MKDIR packet", 0); |
| return -1; |
| } |
| /* Filetype in SFTP 3 and earlier */ |
| attrs.permissions = mode | LIBSSH2_SFTP_ATTR_PFILETYPE_DIR; |
| |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| *(s++) = SSH_FXP_MKDIR; |
| sftp->mkdir_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->mkdir_request_id); |
| s += 4; |
| _libssh2_htonu32(s, path_len); |
| s += 4; |
| memcpy(s, path, path_len); |
| s += path_len; |
| s += sftp_attr2bin(s, &attrs); |
| |
| sftp->mkdir_state = libssh2_NB_state_created; |
| } |
| else { |
| packet = sftp->mkdir_packet; |
| } |
| |
| if (sftp->mkdir_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) packet, packet_len); |
| if (rc == PACKET_EAGAIN) { |
| sftp->mkdir_packet = packet; |
| return rc; |
| } |
| if (packet_len != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "_libssh2_channel_write() failed", 0); |
| LIBSSH2_FREE(session, packet); |
| sftp->mkdir_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| LIBSSH2_FREE(session, packet); |
| sftp->mkdir_state = libssh2_NB_state_sent; |
| sftp->mkdir_packet = NULL; |
| } |
| |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, sftp->mkdir_request_id, |
| &data, &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| sftp->mkdir_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| sftp->mkdir_state = libssh2_NB_state_idle; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| if (retcode == LIBSSH2_FX_OK) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "OK!"); |
| return 0; |
| } else { |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| sftp->last_errno = retcode; |
| return -1; |
| } |
| } |
| |
| /* |
| * libssh2_sftp_mkdir_ex |
| * |
| * Create an SFTP directory |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len, long mode) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, sftp->channel->session, |
| sftp_mkdir(sftp, path, path_len, mode)); |
| return rc; |
| } |
| |
| /* sftp_rmdir |
| * Remove a directory |
| */ |
| static int sftp_rmdir(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len, retcode; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ |
| ssize_t packet_len = path_len + 13; |
| unsigned char *s, *data; |
| int rc; |
| |
| if (sftp->rmdir_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Removing directory: %s", |
| path); |
| s = sftp->rmdir_packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!sftp->rmdir_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_RMDIR packet", 0); |
| return -1; |
| } |
| |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| *(s++) = SSH_FXP_RMDIR; |
| sftp->rmdir_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->rmdir_request_id); |
| s += 4; |
| _libssh2_htonu32(s, path_len); |
| s += 4; |
| memcpy(s, path, path_len); |
| s += path_len; |
| |
| sftp->rmdir_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->rmdir_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) sftp->rmdir_packet, |
| packet_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (packet_len != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_RMDIR command", 0); |
| LIBSSH2_FREE(session, sftp->rmdir_packet); |
| sftp->rmdir_packet = NULL; |
| sftp->rmdir_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| LIBSSH2_FREE(session, sftp->rmdir_packet); |
| sftp->rmdir_packet = NULL; |
| |
| sftp->rmdir_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, |
| sftp->rmdir_request_id, &data, &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| sftp->rmdir_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| sftp->rmdir_state = libssh2_NB_state_idle; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| if (retcode == LIBSSH2_FX_OK) { |
| return 0; |
| } else { |
| sftp->last_errno = retcode; |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| return -1; |
| } |
| } |
| |
| /* libssh2_sftp_rmdir_ex |
| * Remove a directory |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, sftp->channel->session, |
| sftp_rmdir(sftp, path, path_len)); |
| return rc; |
| } |
| |
| /* sftp_stat |
| * Stat a file or symbolic link |
| */ |
| static int sftp_stat(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len, int stat_type, |
| LIBSSH2_SFTP_ATTRIBUTES * attrs) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ |
| ssize_t packet_len = |
| path_len + 13 + |
| ((stat_type == |
| LIBSSH2_SFTP_SETSTAT) ? sftp_attrsize(attrs) : 0); |
| unsigned char *s, *data; |
| static const unsigned char stat_responses[2] = |
| { SSH_FXP_ATTRS, SSH_FXP_STATUS }; |
| int rc; |
| |
| if (sftp->stat_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "%s %s", |
| (stat_type == LIBSSH2_SFTP_SETSTAT) ? "Set-statting" : |
| (stat_type == |
| LIBSSH2_SFTP_LSTAT ? "LStatting" : "Statting"), path); |
| s = sftp->stat_packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!sftp->stat_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_*STAT packet", 0); |
| return -1; |
| } |
| |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| switch (stat_type) { |
| case LIBSSH2_SFTP_SETSTAT: |
| *(s++) = SSH_FXP_SETSTAT; |
| break; |
| |
| case LIBSSH2_SFTP_LSTAT: |
| *(s++) = SSH_FXP_LSTAT; |
| break; |
| |
| case LIBSSH2_SFTP_STAT: |
| default: |
| *(s++) = SSH_FXP_STAT; |
| } |
| sftp->stat_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->stat_request_id); |
| s += 4; |
| _libssh2_htonu32(s, path_len); |
| s += 4; |
| memcpy(s, path, path_len); |
| s += path_len; |
| if (stat_type == LIBSSH2_SFTP_SETSTAT) { |
| s += sftp_attr2bin(s, attrs); |
| } |
| |
| sftp->stat_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->stat_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) sftp->stat_packet, |
| packet_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (packet_len != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send STAT/LSTAT/SETSTAT command", 0); |
| LIBSSH2_FREE(session, sftp->stat_packet); |
| sftp->stat_packet = NULL; |
| sftp->stat_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| LIBSSH2_FREE(session, sftp->stat_packet); |
| sftp->stat_packet = NULL; |
| |
| sftp->stat_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_requirev(sftp, 2, stat_responses, |
| sftp->stat_request_id, &data, &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| sftp->stat_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| sftp->stat_state = libssh2_NB_state_idle; |
| |
| if (data[0] == SSH_FXP_STATUS) { |
| int retcode; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| if (retcode == LIBSSH2_FX_OK) { |
| return 0; |
| } else { |
| sftp->last_errno = retcode; |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| return -1; |
| } |
| } |
| |
| memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); |
| sftp_bin2attr(attrs, data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| return 0; |
| } |
| |
| /* libssh2_sftp_stat_ex |
| * Stat a file or symbolic link |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_stat_ex(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len, int stat_type, |
| LIBSSH2_SFTP_ATTRIBUTES *attrs) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, sftp->channel->session, |
| sftp_stat(sftp, path, path_len, stat_type, attrs)); |
| return rc; |
| } |
| |
| /* sftp_symlink |
| * Read or set a symlink |
| */ |
| static int sftp_symlink(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len, char *target, |
| unsigned int target_len, int link_type) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| unsigned long data_len, link_len; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ |
| ssize_t packet_len = |
| path_len + 13 + |
| ((link_type == LIBSSH2_SFTP_SYMLINK) ? (4 + target_len) : 0); |
| unsigned char *s, *data; |
| static const unsigned char link_responses[2] = |
| { SSH_FXP_NAME, SSH_FXP_STATUS }; |
| int rc; |
| |
| if ((sftp->version < 3) && (link_type != LIBSSH2_SFTP_REALPATH)) { |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Server does not support SYMLINK or READLINK", 0); |
| return -1; |
| } |
| |
| if (sftp->symlink_state == libssh2_NB_state_idle) { |
| s = sftp->symlink_packet = LIBSSH2_ALLOC(session, packet_len); |
| if (!sftp->symlink_packet) { |
| libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for SYMLINK/READLINK" |
| "/REALPATH packet", 0); |
| return -1; |
| } |
| |
| _libssh2_debug(session, LIBSSH2_DBG_SFTP, "%s %s on %s", |
| (link_type == |
| LIBSSH2_SFTP_SYMLINK) ? "Creating" : "Reading", |
| (link_type == |
| LIBSSH2_SFTP_REALPATH) ? "realpath" : "symlink", path); |
| |
| _libssh2_htonu32(s, packet_len - 4); |
| s += 4; |
| switch (link_type) { |
| case LIBSSH2_SFTP_REALPATH: |
| *(s++) = SSH_FXP_REALPATH; |
| break; |
| |
| case LIBSSH2_SFTP_SYMLINK: |
| *(s++) = SSH_FXP_SYMLINK; |
| break; |
| |
| case LIBSSH2_SFTP_READLINK: |
| default: |
| *(s++) = SSH_FXP_READLINK; |
| } |
| sftp->symlink_request_id = sftp->request_id++; |
| _libssh2_htonu32(s, sftp->symlink_request_id); |
| s += 4; |
| _libssh2_htonu32(s, path_len); |
| s += 4; |
| memcpy(s, path, path_len); |
| s += path_len; |
| if (link_type == LIBSSH2_SFTP_SYMLINK) { |
| _libssh2_htonu32(s, target_len); |
| s += 4; |
| memcpy(s, target, target_len); |
| s += target_len; |
| } |
| |
| sftp->symlink_state = libssh2_NB_state_created; |
| } |
| |
| if (sftp->symlink_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, (char *) sftp->symlink_packet, |
| packet_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } else if (packet_len != rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send SYMLINK/READLINK command", 0); |
| LIBSSH2_FREE(session, sftp->symlink_packet); |
| sftp->symlink_packet = NULL; |
| sftp->symlink_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| LIBSSH2_FREE(session, sftp->symlink_packet); |
| sftp->symlink_packet = NULL; |
| |
| sftp->symlink_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_requirev(sftp, 2, link_responses, |
| sftp->symlink_request_id, &data, |
| &data_len); |
| if (rc == PACKET_EAGAIN) { |
| return rc; |
| } |
| else if (rc) { |
| libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, |
| "Timeout waiting for status message", 0); |
| sftp->symlink_state = libssh2_NB_state_idle; |
| return -1; |
| } |
| |
| sftp->symlink_state = libssh2_NB_state_idle; |
| |
| if (data[0] == SSH_FXP_STATUS) { |
| int retcode; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| if (retcode == LIBSSH2_FX_OK) { |
| return 0; |
| } else { |
| sftp->last_errno = retcode; |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error", 0); |
| return -1; |
| } |
| } |
| |
| if (_libssh2_ntohu32(data + 5) < 1) { |
| libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Invalid READLINK/REALPATH response, no name entries", |
| 0); |
| LIBSSH2_FREE(session, data); |
| return -1; |
| } |
| |
| link_len = _libssh2_ntohu32(data + 9); |
| if (link_len >= target_len) { |
| link_len = target_len - 1; |
| } |
| memcpy(target, data + 13, link_len); |
| target[link_len] = 0; |
| LIBSSH2_FREE(session, data); |
| |
| return link_len; |
| } |
| |
| /* libssh2_sftp_symlink_ex |
| * Read or set a symlink |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_symlink_ex(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len, char *target, |
| unsigned int target_len, int link_type) |
| { |
| int rc; |
| BLOCK_ADJUST(rc, sftp->channel->session, |
| sftp_symlink(sftp, path, path_len, target, target_len, |
| link_type)); |
| return rc; |
| } |
| |
| /* libssh2_sftp_last_error |
| * Returns the last error code reported by SFTP |
| */ |
| LIBSSH2_API unsigned long |
| libssh2_sftp_last_error(LIBSSH2_SFTP *sftp) |
| { |
| return sftp->last_errno; |
| } |
| |
| |