| /* Copyright (c) 2004-2008, Sara Golemon <[email protected]> |
| * Copyright (c) 2007 Eli Fant <[email protected]> |
| * Copyright (c) 2009-2019 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" |
| #include "session.h" |
| #include "sftp.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 |
| |
| /* S_IFREG */ |
| #define LIBSSH2_SFTP_ATTR_PFILETYPE_FILE 0100000 |
| /* S_IFDIR */ |
| #define LIBSSH2_SFTP_ATTR_PFILETYPE_DIR 0040000 |
| |
| #define SSH_FXE_STATVFS_ST_RDONLY 0x00000001 |
| #define SSH_FXE_STATVFS_ST_NOSUID 0x00000002 |
| |
| /* This is the maximum packet length to accept, as larger than this indicate |
| some kind of server problem. */ |
| #define LIBSSH2_SFTP_PACKET_MAXLEN (256 * 1024) |
| |
| static int sftp_packet_ask(LIBSSH2_SFTP *sftp, unsigned char packet_type, |
| uint32_t request_id, unsigned char **data, |
| size_t *data_len); |
| static void sftp_packet_flush(LIBSSH2_SFTP *sftp); |
| |
| /* sftp_attrsize |
| * Size that attr with this flagset will occupy when turned into a bin struct |
| */ |
| static int sftp_attrsize(unsigned long flags) |
| { |
| return (4 + /* flags(4) */ |
| ((flags & LIBSSH2_SFTP_ATTR_SIZE) ? 8 : 0) + |
| ((flags & LIBSSH2_SFTP_ATTR_UIDGID) ? 8 : 0) + |
| ((flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) ? 4 : 0) + |
| ((flags & LIBSSH2_SFTP_ATTR_ACMODTIME) ? 8 : 0)); |
| /* atime + mtime as u32 */ |
| } |
| |
| /* _libssh2_store_u64 |
| */ |
| static void _libssh2_store_u64(unsigned char **ptr, libssh2_uint64_t value) |
| { |
| uint32_t msl = (uint32_t)(value >> 32); |
| unsigned char *buf = *ptr; |
| |
| 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); |
| |
| *ptr += 8; |
| } |
| |
| /* |
| * Search list of zombied FXP_READ request IDs. |
| * |
| * Returns NULL if ID not in list. |
| */ |
| static struct sftp_zombie_requests * |
| find_zombie_request(LIBSSH2_SFTP *sftp, uint32_t request_id) |
| { |
| struct sftp_zombie_requests *zombie = |
| _libssh2_list_first(&sftp->zombie_requests); |
| |
| while(zombie) { |
| if(zombie->request_id == request_id) |
| break; |
| else |
| zombie = _libssh2_list_next(&zombie->node); |
| } |
| |
| return zombie; |
| } |
| |
| static void |
| remove_zombie_request(LIBSSH2_SFTP *sftp, uint32_t request_id) |
| { |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| |
| struct sftp_zombie_requests *zombie = find_zombie_request(sftp, |
| request_id); |
| if(zombie) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Removing request ID %ld from the list of " |
| "zombie requests", |
| request_id); |
| |
| _libssh2_list_remove(&zombie->node); |
| LIBSSH2_FREE(session, zombie); |
| } |
| } |
| |
| static int |
| add_zombie_request(LIBSSH2_SFTP *sftp, uint32_t request_id) |
| { |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| |
| struct sftp_zombie_requests *zombie; |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Marking request ID %ld as a zombie request", request_id); |
| |
| zombie = LIBSSH2_ALLOC(sftp->channel->session, |
| sizeof(struct sftp_zombie_requests)); |
| if(!zombie) |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "malloc fail for zombie request ID"); |
| else { |
| zombie->request_id = request_id; |
| _libssh2_list_add(&sftp->zombie_requests, &zombie->node); |
| return LIBSSH2_ERROR_NONE; |
| } |
| } |
| |
| /* |
| * sftp_packet_add |
| * |
| * Add a packet to the SFTP packet brigade |
| */ |
| static int |
| sftp_packet_add(LIBSSH2_SFTP *sftp, unsigned char *data, |
| size_t data_len) |
| { |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| LIBSSH2_SFTP_PACKET *packet; |
| uint32_t request_id; |
| |
| if(data_len < 5) { |
| return LIBSSH2_ERROR_OUT_OF_BOUNDARY; |
| } |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Received packet type %d (len %d)", |
| (int) data[0], data_len); |
| |
| /* |
| * Experience shows that if we mess up EAGAIN handling somewhere or |
| * otherwise get out of sync with the channel, this is where we first get |
| * a wrong byte and if so we need to bail out at once to aid tracking the |
| * problem better. |
| */ |
| |
| switch(data[0]) { |
| case SSH_FXP_INIT: |
| case SSH_FXP_VERSION: |
| case SSH_FXP_OPEN: |
| case SSH_FXP_CLOSE: |
| case SSH_FXP_READ: |
| case SSH_FXP_WRITE: |
| case SSH_FXP_LSTAT: |
| case SSH_FXP_FSTAT: |
| case SSH_FXP_SETSTAT: |
| case SSH_FXP_FSETSTAT: |
| case SSH_FXP_OPENDIR: |
| case SSH_FXP_READDIR: |
| case SSH_FXP_REMOVE: |
| case SSH_FXP_MKDIR: |
| case SSH_FXP_RMDIR: |
| case SSH_FXP_REALPATH: |
| case SSH_FXP_STAT: |
| case SSH_FXP_RENAME: |
| case SSH_FXP_READLINK: |
| case SSH_FXP_SYMLINK: |
| case SSH_FXP_STATUS: |
| case SSH_FXP_HANDLE: |
| case SSH_FXP_DATA: |
| case SSH_FXP_NAME: |
| case SSH_FXP_ATTRS: |
| case SSH_FXP_EXTENDED: |
| case SSH_FXP_EXTENDED_REPLY: |
| break; |
| default: |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Out of sync with the world"); |
| } |
| |
| request_id = _libssh2_ntohu32(&data[1]); |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Received packet id %d", |
| request_id); |
| |
| /* Don't add the packet if it answers a request we've given up on. */ |
| if((data[0] == SSH_FXP_STATUS || data[0] == SSH_FXP_DATA) |
| && find_zombie_request(sftp, request_id)) { |
| |
| /* If we get here, the file ended before the response arrived. We |
| are no longer interested in the request so we discard it */ |
| |
| LIBSSH2_FREE(session, data); |
| |
| remove_zombie_request(sftp, request_id); |
| return LIBSSH2_ERROR_NONE; |
| } |
| |
| packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP_PACKET)); |
| if(!packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate datablock for SFTP packet"); |
| } |
| |
| packet->data = data; |
| packet->data_len = data_len; |
| packet->request_id = request_id; |
| |
| _libssh2_list_add(&sftp->packets, &packet->node); |
| |
| return LIBSSH2_ERROR_NONE; |
| } |
| |
| /* |
| * 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 *packet = NULL; |
| ssize_t rc; |
| unsigned long recv_window; |
| int packet_type; |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "recv packet"); |
| |
| switch(sftp->packet_state) { |
| case libssh2_NB_state_sent: /* EAGAIN from window adjusting */ |
| sftp->packet_state = libssh2_NB_state_idle; |
| |
| packet = sftp->partial_packet; |
| goto window_adjust; |
| |
| case libssh2_NB_state_sent1: /* EAGAIN from channel read */ |
| sftp->packet_state = libssh2_NB_state_idle; |
| |
| packet = sftp->partial_packet; |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "partial read cont, len: %lu", sftp->partial_len); |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "partial read cont, already recvd: %lu", |
| sftp->partial_received); |
| /* fall-through */ |
| default: |
| if(!packet) { |
| /* only do this if there's not already a packet buffer allocated |
| to use */ |
| |
| /* each packet starts with a 32 bit length field */ |
| rc = _libssh2_channel_read(channel, 0, |
| (char *)&sftp->partial_size[ |
| sftp->partial_size_len], |
| 4 - sftp->partial_size_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) |
| return rc; |
| else if(rc < 0) |
| return _libssh2_error(session, rc, "channel read"); |
| |
| sftp->partial_size_len += rc; |
| |
| if(4 != sftp->partial_size_len) |
| /* we got a short read for the length part */ |
| return LIBSSH2_ERROR_EAGAIN; |
| |
| sftp->partial_len = _libssh2_ntohu32(sftp->partial_size); |
| /* make sure we don't proceed if the packet size is unreasonably |
| large */ |
| if(sftp->partial_len > LIBSSH2_SFTP_PACKET_MAXLEN) { |
| libssh2_channel_flush(channel); |
| sftp->partial_size_len = 0; |
| return _libssh2_error(session, |
| LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, |
| "SFTP packet too large"); |
| } |
| |
| if(sftp->partial_len == 0) |
| return _libssh2_error(session, |
| LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate empty SFTP packet"); |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Data begin - Packet Length: %lu", |
| sftp->partial_len); |
| packet = LIBSSH2_ALLOC(session, sftp->partial_len); |
| if(!packet) |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate SFTP packet"); |
| sftp->partial_size_len = 0; |
| sftp->partial_received = 0; /* how much of the packet already |
| received */ |
| sftp->partial_packet = packet; |
| |
| window_adjust: |
| recv_window = libssh2_channel_window_read_ex(channel, NULL, NULL); |
| |
| if(sftp->partial_len > recv_window) { |
| /* ask for twice the data amount we need at once */ |
| rc = _libssh2_channel_receive_window_adjust(channel, |
| sftp->partial_len |
| * 2, |
| 1, NULL); |
| /* store the state so that we continue with the correct |
| operation at next invoke */ |
| sftp->packet_state = (rc == LIBSSH2_ERROR_EAGAIN)? |
| libssh2_NB_state_sent: |
| libssh2_NB_state_idle; |
| |
| if(rc == LIBSSH2_ERROR_EAGAIN) |
| return rc; |
| } |
| } |
| |
| /* Read as much of the packet as we can */ |
| while(sftp->partial_len > sftp->partial_received) { |
| rc = _libssh2_channel_read(channel, 0, |
| (char *)&packet[sftp->partial_received], |
| sftp->partial_len - |
| sftp->partial_received); |
| |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| /* |
| * We received EAGAIN, save what we have and return EAGAIN to |
| * the caller. Set 'partial_packet' so that this function |
| * knows how to continue on the next invoke. |
| */ |
| sftp->packet_state = libssh2_NB_state_sent1; |
| return rc; |
| } |
| else if(rc < 0) { |
| LIBSSH2_FREE(session, packet); |
| sftp->partial_packet = NULL; |
| return _libssh2_error(session, rc, |
| "Error waiting for SFTP packet"); |
| } |
| sftp->partial_received += rc; |
| } |
| |
| sftp->partial_packet = NULL; |
| |
| /* sftp_packet_add takes ownership of the packet and might free it |
| so we take a copy of the packet type before we call it. */ |
| packet_type = packet[0]; |
| rc = sftp_packet_add(sftp, packet, sftp->partial_len); |
| if(rc) { |
| LIBSSH2_FREE(session, packet); |
| return rc; |
| } |
| else { |
| return packet_type; |
| } |
| } |
| /* WON'T REACH */ |
| } |
| /* |
| * sftp_packetlist_flush |
| * |
| * Remove all pending packets in the packet_list and the corresponding one(s) |
| * in the SFTP packet brigade. |
| */ |
| static void sftp_packetlist_flush(LIBSSH2_SFTP_HANDLE *handle) |
| { |
| struct sftp_pipeline_chunk *chunk; |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| |
| /* remove pending packets, if any */ |
| chunk = _libssh2_list_first(&handle->packet_list); |
| while(chunk) { |
| unsigned char *data; |
| size_t data_len; |
| int rc; |
| struct sftp_pipeline_chunk *next = _libssh2_list_next(&chunk->node); |
| |
| rc = sftp_packet_ask(sftp, SSH_FXP_STATUS, |
| chunk->request_id, &data, &data_len); |
| if(rc) |
| rc = sftp_packet_ask(sftp, SSH_FXP_DATA, |
| chunk->request_id, &data, &data_len); |
| |
| if(!rc) |
| /* we found a packet, free it */ |
| LIBSSH2_FREE(session, data); |
| else if(chunk->sent) |
| /* there was no incoming packet for this request, mark this |
| request as a zombie if it ever sent the request */ |
| add_zombie_request(sftp, chunk->request_id); |
| |
| _libssh2_list_remove(&chunk->node); |
| LIBSSH2_FREE(session, chunk); |
| chunk = next; |
| } |
| } |
| |
| |
| /* |
| * sftp_packet_ask() |
| * |
| * Checks if there's a matching SFTP packet available. |
| */ |
| static int |
| sftp_packet_ask(LIBSSH2_SFTP *sftp, unsigned char packet_type, |
| uint32_t request_id, unsigned char **data, |
| size_t *data_len) |
| { |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| LIBSSH2_SFTP_PACKET *packet = _libssh2_list_first(&sftp->packets); |
| |
| if(!packet) |
| return -1; |
| |
| /* Special consideration when getting VERSION packet */ |
| |
| while(packet) { |
| if((packet->data[0] == packet_type) && |
| ((packet_type == SSH_FXP_VERSION) || |
| (packet->request_id == request_id))) { |
| |
| /* 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, |
| uint32_t request_id, unsigned char **data, |
| size_t *data_len, size_t required_size) |
| { |
| LIBSSH2_SESSION *session = sftp->channel->session; |
| int rc; |
| |
| if(data == NULL || data_len == NULL || required_size == 0) { |
| return LIBSSH2_ERROR_BAD_USE; |
| } |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_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_TRACE_SFTP, "Got %d", |
| (int) packet_type); |
| |
| if (*data_len < required_size) { |
| return LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| } |
| |
| return LIBSSH2_ERROR_NONE; |
| } |
| |
| while(session->socket_state == LIBSSH2_SOCKET_CONNECTED) { |
| rc = sftp_packet_read(sftp); |
| if(rc < 0) |
| return rc; |
| |
| /* 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_TRACE_SFTP, "Got %d", |
| (int) packet_type); |
| |
| if (*data_len < required_size) { |
| return LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| } |
| |
| return LIBSSH2_ERROR_NONE; |
| } |
| } |
| |
| /* Only reached if the socket died */ |
| return LIBSSH2_ERROR_SOCKET_DISCONNECT; |
| } |
| |
| /* sftp_packet_requirev |
| * Require one of N possible responses |
| */ |
| static int |
| sftp_packet_requirev(LIBSSH2_SFTP *sftp, int num_valid_responses, |
| const unsigned char *valid_responses, |
| uint32_t request_id, unsigned char **data, |
| size_t *data_len, size_t required_size) |
| { |
| int i; |
| int rc; |
| |
| if(data == NULL || data_len == NULL || required_size == 0) { |
| return LIBSSH2_ERROR_BAD_USE; |
| } |
| |
| /* 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; |
| |
| if (*data_len < required_size) { |
| return LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| } |
| |
| return LIBSSH2_ERROR_NONE; |
| } |
| } |
| |
| rc = sftp_packet_read(sftp); |
| if((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) { |
| sftp->requirev_start = 0; |
| return rc; |
| } |
| else if(rc <= 0) { |
| /* prevent busy-looping */ |
| long left = |
| LIBSSH2_READ_TIMEOUT - |
| (long)(time(NULL) - sftp->requirev_start); |
| |
| if(left <= 0) { |
| sftp->requirev_start = 0; |
| return LIBSSH2_ERROR_TIMEOUT; |
| } |
| else if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| } |
| } |
| |
| sftp->requirev_start = 0; |
| |
| /* Only reached if the socket died */ |
| return LIBSSH2_ERROR_SOCKET_DISCONNECT; |
| } |
| |
| /* sftp_attr2bin |
| * Populate attributes into an SFTP block |
| */ |
| static ssize_t |
| sftp_attr2bin(unsigned char *p, const LIBSSH2_SFTP_ATTRIBUTES * attrs) |
| { |
| unsigned char *s = p; |
| uint32_t 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_store_u32(&s, attrs->flags & flag_mask); |
| |
| if(attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { |
| _libssh2_store_u64(&s, attrs->filesize); |
| } |
| |
| if(attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { |
| _libssh2_store_u32(&s, attrs->uid); |
| _libssh2_store_u32(&s, attrs->gid); |
| } |
| |
| if(attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { |
| _libssh2_store_u32(&s, attrs->permissions); |
| } |
| |
| if(attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { |
| _libssh2_store_u32(&s, attrs->atime); |
| _libssh2_store_u32(&s, attrs->mtime); |
| } |
| |
| return (s - p); |
| } |
| |
| /* sftp_bin2attr |
| */ |
| static int |
| sftp_bin2attr(LIBSSH2_SFTP_ATTRIBUTES *attrs, const unsigned char *p, |
| size_t data_len) |
| { |
| struct string_buf buf; |
| uint32_t flags = 0; |
| buf.data = (unsigned char *)p; |
| buf.dataptr = buf.data; |
| buf.len = data_len; |
| |
| if(_libssh2_get_u32(&buf, &flags) != 0) { |
| return LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| } |
| attrs->flags = flags; |
| |
| if(attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { |
| if(_libssh2_get_u64(&buf, &(attrs->filesize)) != 0) { |
| return LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| } |
| } |
| |
| if(attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { |
| uint32_t uid = 0; |
| uint32_t gid = 0; |
| if(_libssh2_get_u32(&buf, &uid) != 0 || |
| _libssh2_get_u32(&buf, &gid) != 0) { |
| return LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| } |
| attrs->uid = uid; |
| attrs->gid = gid; |
| } |
| |
| if(attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { |
| uint32_t permissions; |
| if(_libssh2_get_u32(&buf, &permissions) != 0) { |
| return LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| } |
| attrs->permissions = permissions; |
| } |
| |
| if(attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { |
| uint32_t atime; |
| uint32_t mtime; |
| if(_libssh2_get_u32(&buf, &atime) != 0 || |
| _libssh2_get_u32(&buf, &mtime) != 0) { |
| return LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| } |
| attrs->atime = atime; |
| attrs->mtime = mtime; |
| } |
| |
| return (buf.dataptr - buf.data); |
| } |
| |
| /* ************ |
| * 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; |
| size_t data_len; |
| ssize_t rc; |
| LIBSSH2_SFTP *sftp_handle; |
| struct string_buf buf; |
| unsigned char *endp; |
| |
| if(session->sftpInit_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_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 |
| * successfully 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"); |
| } |
| else { |
| _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, |
| "Unable to startup channel"); |
| session->sftpInit_state = libssh2_NB_state_idle; |
| } |
| return NULL; |
| } |
| |
| session->sftpInit_state = libssh2_NB_state_sent; |
| } |
| |
| if(session->sftpInit_state == libssh2_NB_state_sent) { |
| int ret = _libssh2_channel_process_startup(session->sftpInit_channel, |
| "subsystem", |
| sizeof("subsystem") - 1, |
| "sftp", |
| strlen("sftp")); |
| if(ret == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block to request SFTP subsystem"); |
| return NULL; |
| } |
| else if(ret) { |
| _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, |
| "Unable to request SFTP subsystem"); |
| 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 == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block requesting handle extended data"); |
| return NULL; |
| } |
| |
| sftp_handle = |
| session->sftpInit_sftp = |
| LIBSSH2_CALLOC(session, sizeof(LIBSSH2_SFTP)); |
| if(!sftp_handle) { |
| _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate a new SFTP structure"); |
| goto sftp_init_error; |
| } |
| 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); |
| session->sftpInit_sent = 0; /* nothing's sent yet */ |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_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) { |
| /* sent off what's left of the init buffer to send */ |
| rc = _libssh2_channel_write(session->sftpInit_channel, 0, |
| session->sftpInit_buffer + |
| session->sftpInit_sent, |
| 9 - session->sftpInit_sent); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block sending SSH_FXP_INIT"); |
| return NULL; |
| } |
| else if(rc < 0) { |
| _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send SSH_FXP_INIT"); |
| goto sftp_init_error; |
| } |
| else { |
| /* add up the number of bytes sent */ |
| session->sftpInit_sent += rc; |
| |
| if(session->sftpInit_sent == 9) |
| /* move on */ |
| session->sftpInit_state = libssh2_NB_state_sent3; |
| |
| /* if less than 9, we remain in this state to send more later on */ |
| } |
| } |
| |
| rc = sftp_packet_require(sftp_handle, SSH_FXP_VERSION, |
| 0, &data, &data_len, 5); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block receiving SSH_FXP_VERSION"); |
| return NULL; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Invalid SSH_FXP_VERSION response"); |
| goto sftp_init_error; |
| } |
| else if(rc) { |
| _libssh2_error(session, rc, |
| "Timeout waiting for response from SFTP subsystem"); |
| goto sftp_init_error; |
| } |
| |
| buf.data = data; |
| buf.dataptr = buf.data + 1; |
| buf.len = data_len; |
| endp = &buf.data[data_len]; |
| |
| if(_libssh2_get_u32(&buf, &(sftp_handle->version)) != 0) { |
| LIBSSH2_FREE(session, data); |
| rc = LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| goto sftp_init_error; |
| } |
| |
| if(sftp_handle->version > LIBSSH2_SFTP_VERSION) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Truncating remote SFTP version from %lu", |
| sftp_handle->version); |
| sftp_handle->version = LIBSSH2_SFTP_VERSION; |
| } |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Enabling SFTP version %lu compatibility", |
| sftp_handle->version); |
| while(buf.dataptr < endp) { |
| unsigned char *extname, *extdata; |
| |
| if(_libssh2_get_string(&buf, &extname, NULL)) { |
| LIBSSH2_FREE(session, data); |
| _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, |
| "Data too short when extracting extname"); |
| goto sftp_init_error; |
| } |
| |
| if(_libssh2_get_string(&buf, &extdata, NULL)) { |
| LIBSSH2_FREE(session, data); |
| _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, |
| "Data too short when extracting extdata"); |
| goto sftp_init_error; |
| } |
| } |
| 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) == |
| LIBSSH2_ERROR_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; |
| |
| if(!session) |
| return NULL; |
| |
| if(!(session->state & LIBSSH2_STATE_AUTHENTICATED)) { |
| _libssh2_error(session, LIBSSH2_ERROR_INVAL, |
| "session not authenticated yet"); |
| return NULL; |
| } |
| |
| BLOCK_ADJUST_ERRNO(ptr, session, sftp_init(session)); |
| return ptr; |
| } |
| |
| /* |
| * sftp_shutdown |
| * |
| * Shuts down 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->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->fstatvfs_packet) { |
| LIBSSH2_FREE(session, sftp->fstatvfs_packet); |
| sftp->fstatvfs_packet = NULL; |
| } |
| if(sftp->statvfs_packet) { |
| LIBSSH2_FREE(session, sftp->statvfs_packet); |
| sftp->statvfs_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; |
| } |
| if(sftp->fsync_packet) { |
| LIBSSH2_FREE(session, sftp->fsync_packet); |
| sftp->fsync_packet = NULL; |
| } |
| |
| sftp_packet_flush(sftp); |
| |
| /* 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; |
| if(!sftp) |
| return LIBSSH2_ERROR_BAD_USE; |
| 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, |
| size_t filename_len, uint32_t 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 char *s; |
| ssize_t rc; |
| int open_file = (open_type == LIBSSH2_SFTP_OPENFILE)?1:0; |
| |
| 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_file? (4 + |
| sftp_attrsize(LIBSSH2_SFTP_ATTR_PERMISSIONS)) : 0); |
| |
| /* surprise! this starts out with nothing sent */ |
| sftp->open_packet_sent = 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"); |
| return NULL; |
| } |
| /* Filetype in SFTP 3 and earlier */ |
| attrs.permissions = mode | |
| (open_file ? LIBSSH2_SFTP_ATTR_PFILETYPE_FILE : |
| LIBSSH2_SFTP_ATTR_PFILETYPE_DIR); |
| |
| _libssh2_store_u32(&s, sftp->open_packet_len - 4); |
| *(s++) = open_file? SSH_FXP_OPEN : SSH_FXP_OPENDIR; |
| sftp->open_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->open_request_id); |
| _libssh2_store_str(&s, filename, filename_len); |
| |
| if(open_file) { |
| _libssh2_store_u32(&s, flags); |
| s += sftp_attr2bin(s, &attrs); |
| } |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Sending %s open request", |
| open_file? "file" : "directory"); |
| |
| sftp->open_state = libssh2_NB_state_created; |
| } |
| |
| if(sftp->open_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, sftp->open_packet+ |
| sftp->open_packet_sent, |
| sftp->open_packet_len - |
| sftp->open_packet_sent); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block sending FXP_OPEN or " |
| "FXP_OPENDIR command"); |
| return NULL; |
| } |
| else if(rc < 0) { |
| _libssh2_error(session, rc, "Unable to send FXP_OPEN*"); |
| LIBSSH2_FREE(session, sftp->open_packet); |
| sftp->open_packet = NULL; |
| sftp->open_state = libssh2_NB_state_idle; |
| return NULL; |
| } |
| |
| /* bump the sent counter and remain in this state until the whole |
| data is off */ |
| sftp->open_packet_sent += rc; |
| |
| if(sftp->open_packet_len == sftp->open_packet_sent) { |
| 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) { |
| size_t data_len; |
| unsigned char *data; |
| static const unsigned char fopen_responses[2] = |
| { SSH_FXP_HANDLE, SSH_FXP_STATUS }; |
| rc = sftp_packet_requirev(sftp, 2, fopen_responses, |
| sftp->open_request_id, &data, |
| &data_len, 1); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| _libssh2_error(session, LIBSSH2_ERROR_EAGAIN, |
| "Would block waiting for status message"); |
| return NULL; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Response too small"); |
| return NULL; |
| } |
| sftp->open_state = libssh2_NB_state_idle; |
| if(rc) { |
| _libssh2_error(session, rc, "Timeout waiting for status message"); |
| return NULL; |
| } |
| |
| /* 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; |
| |
| if(data_len < 9) { |
| _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Too small FXP_STATUS"); |
| LIBSSH2_FREE(session, data); |
| return NULL; |
| } |
| |
| sftp->last_errno = _libssh2_ntohu32(data + 5); |
| |
| if(LIBSSH2_FX_OK == sftp->last_errno) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "got HANDLE FXOK!"); |
| |
| LIBSSH2_FREE(session, data); |
| |
| /* silly situation, but check for a HANDLE */ |
| rc = sftp_packet_require(sftp, SSH_FXP_HANDLE, |
| sftp->open_request_id, &data, |
| &data_len, 10); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| /* go back to sent state and wait for something else */ |
| sftp->open_state = libssh2_NB_state_sent; |
| return NULL; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Too small FXP_HANDLE"); |
| 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"); |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "got FXP_STATUS %d", |
| sftp->last_errno); |
| LIBSSH2_FREE(session, data); |
| return NULL; |
| } |
| } |
| |
| if(data_len < 10) { |
| _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Too small FXP_HANDLE"); |
| LIBSSH2_FREE(session, data); |
| return NULL; |
| } |
| |
| fp = LIBSSH2_CALLOC(session, sizeof(LIBSSH2_SFTP_HANDLE)); |
| if(!fp) { |
| _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate new SFTP handle structure"); |
| LIBSSH2_FREE(session, data); |
| return NULL; |
| } |
| fp->handle_type = open_file ? 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; |
| |
| if(fp->handle_len > (data_len - 9)) |
| /* do not reach beyond the end of the data we got */ |
| fp->handle_len = data_len - 9; |
| |
| 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; |
| fp->u.file.offset_sent = 0; |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Open command successful"); |
| return fp; |
| } |
| return NULL; |
| } |
| |
| /* 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; |
| |
| if(!sftp) |
| return NULL; |
| |
| 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_size) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| size_t count = 0; |
| struct sftp_pipeline_chunk *chunk; |
| struct sftp_pipeline_chunk *next; |
| ssize_t rc; |
| struct _libssh2_sftp_handle_file_data *filep = |
| &handle->u.file; |
| size_t bytes_in_buffer = 0; |
| char *sliding_bufferp = buffer; |
| |
| /* This function can be interrupted in three different places where it |
| might need to wait for data from the network. It returns EAGAIN to |
| allow non-blocking clients to do other work but these client are |
| expected to call this function again (possibly many times) to finish |
| the operation. |
| |
| The tricky part is that if we previously aborted a sftp_read due to |
| EAGAIN, we must continue at the same spot to continue the previously |
| interrupted operation. This is done using a state machine to record |
| what phase of execution we were at. The state is stored in |
| sftp->read_state. |
| |
| libssh2_NB_state_idle: The first phase is where we prepare multiple |
| FXP_READ packets to do optimistic read-ahead. We send off as many as |
| possible in the second phase without waiting for a response to each |
| one; this is the key to fast reads. But we may have to adjust the |
| channel window size to do this which may interrupt this function while |
| waiting. The state machine saves the phase as libssh2_NB_state_idle so |
| it returns here on the next call. |
| |
| libssh2_NB_state_sent: The second phase is where we send the FXP_READ |
| packets. Writing them to the channel can be interrupted with EAGAIN |
| but the state machine ensures we skip the first phase on the next call |
| and resume sending. |
| |
| libssh2_NB_state_sent2: In the third phase (indicated by ) we read the |
| data from the responses that have arrived so far. Reading can be |
| interrupted with EAGAIN but the state machine ensures we skip the first |
| and second phases on the next call and resume sending. |
| */ |
| |
| switch(sftp->read_state) { |
| case libssh2_NB_state_idle: |
| |
| /* Some data may already have been read from the server in the |
| previous call but didn't fit in the buffer at the time. If so, we |
| return that now as we can't risk being interrupted later with data |
| partially written to the buffer. */ |
| if(filep->data_left) { |
| size_t copy = MIN(buffer_size, filep->data_left); |
| |
| memcpy(buffer, &filep->data[ filep->data_len - filep->data_left], |
| copy); |
| |
| filep->data_left -= copy; |
| filep->offset += copy; |
| |
| if(!filep->data_left) { |
| LIBSSH2_FREE(session, filep->data); |
| filep->data = NULL; |
| } |
| |
| return copy; |
| } |
| |
| if(filep->eof) { |
| return 0; |
| } |
| else { |
| /* We allow a number of bytes being requested at any given time |
| without having been acked - until we reach EOF. */ |
| |
| /* Number of bytes asked for that haven't been acked yet */ |
| size_t already = (size_t)(filep->offset_sent - filep->offset); |
| |
| size_t max_read_ahead = buffer_size*4; |
| unsigned long recv_window; |
| |
| if(max_read_ahead > LIBSSH2_CHANNEL_WINDOW_DEFAULT*4) |
| max_read_ahead = LIBSSH2_CHANNEL_WINDOW_DEFAULT*4; |
| |
| /* if the buffer_size passed in now is smaller than what has |
| already been sent, we risk getting count become a very large |
| number */ |
| if(max_read_ahead > already) |
| count = max_read_ahead - already; |
| |
| /* 'count' is how much more data to ask for, and 'already' is how |
| much data that already has been asked for but not yet returned. |
| Specifically, 'count' means how much data that have or will be |
| asked for by the nodes that are already added to the linked |
| list. Some of those read requests may not actually have been |
| sent off successfully yet. |
| |
| If 'already' is very large it should be perfectly fine to have |
| count set to 0 as then we don't have to ask for more data |
| (right now). |
| |
| buffer_size*4 is just picked more or less out of the air. The |
| idea is that when reading SFTP from a remote server, we send |
| away multiple read requests guessing that the client will read |
| more than only this 'buffer_size' amount of memory. So we ask |
| for maximum buffer_size*4 amount of data so that we can return |
| them very fast in subsequent calls. |
| */ |
| |
| recv_window = libssh2_channel_window_read_ex(sftp->channel, |
| NULL, NULL); |
| if(max_read_ahead > recv_window) { |
| /* more data will be asked for than what the window currently |
| allows, expand it! */ |
| |
| rc = _libssh2_channel_receive_window_adjust(sftp->channel, |
| max_read_ahead*8, |
| 1, NULL); |
| /* if this returns EAGAIN, we will get back to this function |
| at next call */ |
| assert(rc != LIBSSH2_ERROR_EAGAIN || !filep->data_left); |
| assert(rc != LIBSSH2_ERROR_EAGAIN || !filep->eof); |
| if(rc) |
| return rc; |
| } |
| } |
| |
| while(count > 0) { |
| unsigned char *s; |
| |
| /* 25 = packet_len(4) + packet_type(1) + request_id(4) + |
| handle_len(4) + offset(8) + count(4) */ |
| uint32_t packet_len = (uint32_t)handle->handle_len + 25; |
| uint32_t request_id; |
| |
| uint32_t size = count; |
| if(size < buffer_size) |
| size = buffer_size; |
| if(size > MAX_SFTP_READ_SIZE) |
| size = MAX_SFTP_READ_SIZE; |
| |
| chunk = LIBSSH2_ALLOC(session, packet_len + |
| sizeof(struct sftp_pipeline_chunk)); |
| if(!chunk) |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "malloc fail for FXP_WRITE"); |
| |
| chunk->offset = filep->offset_sent; |
| chunk->len = size; |
| chunk->lefttosend = packet_len; |
| chunk->sent = 0; |
| |
| s = chunk->packet; |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *s++ = SSH_FXP_READ; |
| request_id = sftp->request_id++; |
| chunk->request_id = request_id; |
| _libssh2_store_u32(&s, request_id); |
| _libssh2_store_str(&s, handle->handle, handle->handle_len); |
| _libssh2_store_u64(&s, filep->offset_sent); |
| filep->offset_sent += size; /* advance offset at once */ |
| _libssh2_store_u32(&s, size); |
| |
| /* add this new entry LAST in the list */ |
| _libssh2_list_add(&handle->packet_list, &chunk->node); |
| count -= MIN(size, count); /* deduct the size we used, as we might |
| * have to create more packets */ |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "read request id %d sent (offset: %d, size: %d)", |
| request_id, (int)chunk->offset, (int)chunk->len); |
| } |
| /* FALL-THROUGH */ |
| case libssh2_NB_state_sent: |
| |
| sftp->read_state = libssh2_NB_state_idle; |
| |
| /* move through the READ packets that haven't been sent and send as |
| many as possible - remember that we don't block */ |
| chunk = _libssh2_list_first(&handle->packet_list); |
| |
| while(chunk) { |
| if(chunk->lefttosend) { |
| |
| rc = _libssh2_channel_write(channel, 0, |
| &chunk->packet[chunk->sent], |
| chunk->lefttosend); |
| if(rc < 0) { |
| sftp->read_state = libssh2_NB_state_sent; |
| return rc; |
| } |
| |
| /* remember where to continue sending the next time */ |
| chunk->lefttosend -= rc; |
| chunk->sent += rc; |
| |
| if(chunk->lefttosend) { |
| /* We still have data left to send for this chunk. |
| * If there is at least one completely sent chunk, |
| * we can get out of this loop and start reading. */ |
| if(chunk != _libssh2_list_first(&handle->packet_list)) { |
| break; |
| } |
| else { |
| continue; |
| } |
| } |
| } |
| |
| /* move on to the next chunk with data to send */ |
| chunk = _libssh2_list_next(&chunk->node); |
| } |
| /* FALL-THROUGH */ |
| |
| case libssh2_NB_state_sent2: |
| |
| sftp->read_state = libssh2_NB_state_idle; |
| |
| /* |
| * Count all ACKed packets and act on the contents of them. |
| */ |
| chunk = _libssh2_list_first(&handle->packet_list); |
| |
| while(chunk) { |
| unsigned char *data; |
| size_t data_len; |
| uint32_t rc32; |
| static const unsigned char read_responses[2] = { |
| SSH_FXP_DATA, SSH_FXP_STATUS |
| }; |
| |
| if(chunk->lefttosend) { |
| /* if the chunk still has data left to send, we shouldn't wait |
| for an ACK for it just yet */ |
| if(bytes_in_buffer > 0) { |
| return bytes_in_buffer; |
| } |
| else { |
| /* we should never reach this point */ |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "sftp_read() internal error"); |
| } |
| } |
| |
| rc = sftp_packet_requirev(sftp, 2, read_responses, |
| chunk->request_id, &data, &data_len, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN && bytes_in_buffer != 0) { |
| /* do not return EAGAIN if we have already |
| * written data into the buffer */ |
| return bytes_in_buffer; |
| } |
| |
| if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Response too small"); |
| } |
| else if(rc < 0) { |
| sftp->read_state = libssh2_NB_state_sent2; |
| return rc; |
| } |
| |
| /* |
| * We get DATA or STATUS back. STATUS can be error, or it is |
| * FX_EOF when we reach the end of the file. |
| */ |
| |
| switch(data[0]) { |
| case SSH_FXP_STATUS: |
| /* remove the chunk we just processed */ |
| |
| _libssh2_list_remove(&chunk->node); |
| LIBSSH2_FREE(session, chunk); |
| |
| /* we must remove all outstanding READ requests, as either we |
| got an error or we're at end of file */ |
| sftp_packetlist_flush(handle); |
| |
| rc32 = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| if(rc32 == LIBSSH2_FX_EOF) { |
| filep->eof = TRUE; |
| return bytes_in_buffer; |
| } |
| else { |
| sftp->last_errno = rc32; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP READ error"); |
| } |
| break; |
| |
| case SSH_FXP_DATA: |
| if(chunk->offset != filep->offset) { |
| /* This could happen if the server returns less bytes than |
| requested, which shouldn't happen for normal files. See: |
| https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 |
| #section-6.4 |
| */ |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Read Packet At Unexpected Offset"); |
| } |
| |
| rc32 = _libssh2_ntohu32(data + 5); |
| if(rc32 > (data_len - 9)) |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol badness"); |
| |
| if(rc32 > chunk->len) { |
| /* A chunk larger than we requested was returned to us. |
| This is a protocol violation and we don't know how to |
| deal with it. Bail out! */ |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "FXP_READ response too big"); |
| } |
| |
| if(rc32 != chunk->len) { |
| /* a short read does not imply end of file, but we must |
| adjust the offset_sent since it was advanced with a |
| full chunk->len before */ |
| filep->offset_sent -= (chunk->len - rc32); |
| } |
| |
| if((bytes_in_buffer + rc32) > buffer_size) { |
| /* figure out the overlap amount */ |
| filep->data_left = (bytes_in_buffer + rc32) - buffer_size; |
| |
| /* getting the full packet would overflow the buffer, so |
| only get the correct amount and keep the remainder */ |
| rc32 = (uint32_t)buffer_size - bytes_in_buffer; |
| |
| /* store data to keep for next call */ |
| filep->data = data; |
| filep->data_len = data_len; |
| } |
| else |
| filep->data_len = 0; |
| |
| /* copy the received data from the received FXP_DATA packet to |
| the buffer at the correct index */ |
| memcpy(sliding_bufferp, data + 9, rc32); |
| filep->offset += rc32; |
| bytes_in_buffer += rc32; |
| sliding_bufferp += rc32; |
| |
| if(filep->data_len == 0) |
| /* free the allocated data if not stored to keep */ |
| LIBSSH2_FREE(session, data); |
| |
| /* remove the chunk we just processed keeping track of the |
| * next one in case we need it */ |
| next = _libssh2_list_next(&chunk->node); |
| _libssh2_list_remove(&chunk->node); |
| LIBSSH2_FREE(session, chunk); |
| |
| /* check if we have space left in the buffer |
| * and either continue to the next chunk or stop |
| */ |
| if(bytes_in_buffer < buffer_size) { |
| chunk = next; |
| } |
| else { |
| chunk = NULL; |
| } |
| |
| break; |
| default: |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol badness: unrecognised " |
| "read request response"); |
| } |
| } |
| |
| if(bytes_in_buffer > 0) |
| return bytes_in_buffer; |
| |
| break; |
| |
| default: |
| assert(!"State machine error; unrecognised read state"); |
| } |
| |
| /* we should never reach this point */ |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "sftp_read() internal error"); |
| } |
| |
| /* 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; |
| if(!hnd) |
| return LIBSSH2_ERROR_BAD_USE; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, |
| sftp_read(hnd, buffer, buffer_maxlen)); |
| return rc; |
| } |
| |
| /* sftp_readdir |
| * Read from an SFTP directory handle |
| */ |
| static ssize_t 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; |
| size_t data_len; |
| uint32_t num_names; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ |
| uint32_t packet_len = handle->handle_len + 13; |
| unsigned char *s, *data; |
| static const unsigned char read_responses[2] = { |
| SSH_FXP_NAME, SSH_FXP_STATUS }; |
| ssize_t 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 |
| */ |
| LIBSSH2_SFTP_ATTRIBUTES attrs_dummy; |
| size_t real_longentry_len; |
| size_t real_filename_len; |
| size_t filename_len; |
| size_t longentry_len; |
| size_t names_packet_len = handle->u.dir.names_packet_len; |
| int attr_len = 0; |
| |
| if(names_packet_len >= 4) { |
| s = (unsigned char *) handle->u.dir.next_name; |
| real_filename_len = _libssh2_ntohu32(s); |
| s += 4; |
| names_packet_len -= 4; |
| } |
| else { |
| filename_len = (size_t)LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| goto end; |
| } |
| |
| filename_len = real_filename_len; |
| if(filename_len >= buffer_maxlen) { |
| filename_len = (size_t)LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| goto end; |
| } |
| |
| if(buffer_maxlen >= filename_len && names_packet_len >= |
| filename_len) { |
| memcpy(buffer, s, filename_len); |
| buffer[filename_len] = '\0'; /* zero terminate */ |
| s += real_filename_len; |
| names_packet_len -= real_filename_len; |
| } |
| else { |
| filename_len = (size_t)LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| goto end; |
| } |
| |
| if(names_packet_len >= 4) { |
| real_longentry_len = _libssh2_ntohu32(s); |
| s += 4; |
| names_packet_len -= 4; |
| } |
| else { |
| filename_len = (size_t)LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| goto end; |
| } |
| |
| if(longentry && (longentry_maxlen>1)) { |
| longentry_len = real_longentry_len; |
| |
| if(longentry_len >= longentry_maxlen || |
| longentry_len > names_packet_len) { |
| filename_len = (size_t)LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| goto end; |
| } |
| |
| memcpy(longentry, s, longentry_len); |
| longentry[longentry_len] = '\0'; /* zero terminate */ |
| } |
| |
| if(real_longentry_len <= names_packet_len) { |
| s += real_longentry_len; |
| names_packet_len -= real_longentry_len; |
| } |
| else { |
| filename_len = (size_t)LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| goto end; |
| } |
| |
| if(attrs) |
| memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); |
| |
| attr_len = sftp_bin2attr(attrs ? attrs : &attrs_dummy, s, |
| names_packet_len); |
| |
| if(attr_len >= 0) { |
| s += attr_len; |
| names_packet_len -= attr_len; |
| } |
| else { |
| filename_len = (size_t)LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| goto end; |
| } |
| |
| handle->u.dir.next_name = (char *) s; |
| handle->u.dir.names_packet_len = names_packet_len; |
| end: |
| |
| if((--handle->u.dir.names_left) == 0) |
| LIBSSH2_FREE(session, handle->u.dir.names_packet); |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "libssh2_sftp_readdir_ex() return %d", |
| filename_len); |
| return (ssize_t)filename_len; |
| } |
| |
| /* Request another entry(entries?) */ |
| |
| s = sftp->readdir_packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!sftp->readdir_packet) |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for " |
| "FXP_READDIR packet"); |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = SSH_FXP_READDIR; |
| sftp->readdir_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->readdir_request_id); |
| _libssh2_store_str(&s, handle->handle, handle->handle_len); |
| |
| sftp->readdir_state = libssh2_NB_state_created; |
| } |
| |
| if(sftp->readdir_state == libssh2_NB_state_created) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Reading entries from directory handle"); |
| retcode = _libssh2_channel_write(channel, 0, sftp->readdir_packet, |
| packet_len); |
| if(retcode == LIBSSH2_ERROR_EAGAIN) { |
| return retcode; |
| } |
| else if((ssize_t)packet_len != retcode) { |
| LIBSSH2_FREE(session, sftp->readdir_packet); |
| sftp->readdir_packet = NULL; |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "_libssh2_channel_write() failed"); |
| } |
| |
| 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, 9); |
| if(retcode == LIBSSH2_ERROR_EAGAIN) |
| return retcode; |
| else if(retcode == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Status message too short"); |
| } |
| else if(retcode) { |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, retcode, |
| "Timeout waiting for status message"); |
| } |
| |
| 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; |
| sftp->readdir_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| } |
| |
| sftp->readdir_state = libssh2_NB_state_idle; |
| |
| num_names = _libssh2_ntohu32(data + 5); |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "%lu entries returned", |
| num_names); |
| if(!num_names) { |
| LIBSSH2_FREE(session, data); |
| return 0; |
| } |
| |
| handle->u.dir.names_left = num_names; |
| handle->u.dir.names_packet = data; |
| handle->u.dir.next_name = (char *) data + 9; |
| handle->u.dir.names_packet_len = data_len - 9; |
| |
| /* use the name popping mechanism from the start of the function */ |
| return sftp_readdir(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; |
| if(!hnd) |
| return LIBSSH2_ERROR_BAD_USE; |
| 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. |
| * |
| * We recommend sending very large data buffers to this function! |
| * |
| * Concept: |
| * |
| * - Detect how much of the given buffer that was already sent in a previous |
| * call by inspecting the linked list of outgoing chunks. Make sure to skip |
| * passed the data that has already been taken care of. |
| * |
| * - Split all (new) outgoing data in chunks no larger than N. |
| * |
| * - Each N bytes chunk gets created as a separate SFTP packet. |
| * |
| * - Add all created outgoing packets to the linked list. |
| * |
| * - Walk through the list and send the chunks that haven't been sent, |
| * as many as possible until EAGAIN. Some of the chunks may have been put |
| * in the list in a previous invoke. |
| * |
| * - For all the chunks in the list that have been completely sent off, check |
| * for ACKs. If a chunk has been ACKed, it is removed from the linked |
| * list and the "acked" counter gets increased with that data amount. |
| * |
| * - Return TOTAL bytes acked so far. |
| * |
| * Caveats: |
| * - be careful: we must not return a higher number than what was given! |
| * |
| * TODO: |
| * Introduce an option that disables this sort of "speculative" ahead writing |
| * as there's a risk that it will do harm to some app. |
| */ |
| |
| 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; |
| size_t data_len; |
| uint32_t retcode; |
| uint32_t packet_len; |
| unsigned char *s, *data; |
| ssize_t rc; |
| struct sftp_pipeline_chunk *chunk; |
| struct sftp_pipeline_chunk *next; |
| size_t acked = 0; |
| size_t org_count = count; |
| size_t already; |
| |
| switch(sftp->write_state) { |
| default: |
| case libssh2_NB_state_idle: |
| |
| /* Number of bytes sent off that haven't been acked and therefore we |
| will get passed in here again. |
| |
| Also, add up the number of bytes that actually already have been |
| acked but we haven't been able to return as such yet, so we will |
| get that data as well passed in here again. |
| */ |
| already = (size_t) (handle->u.file.offset_sent - |
| handle->u.file.offset)+ |
| handle->u.file.acked; |
| |
| if(count >= already) { |
| /* skip the part already made into packets */ |
| buffer += already; |
| count -= already; |
| } |
| else |
| /* there is more data already fine than what we got in this call */ |
| count = 0; |
| |
| sftp->write_state = libssh2_NB_state_idle; |
| while(count) { |
| /* TODO: Possibly this should have some logic to prevent a very |
| very small fraction to be left but lets ignore that for now */ |
| uint32_t size = MIN(MAX_SFTP_OUTGOING_SIZE, count); |
| uint32_t request_id; |
| |
| /* 25 = packet_len(4) + packet_type(1) + request_id(4) + |
| handle_len(4) + offset(8) + count(4) */ |
| packet_len = handle->handle_len + size + 25; |
| |
| chunk = LIBSSH2_ALLOC(session, packet_len + |
| sizeof(struct sftp_pipeline_chunk)); |
| if(!chunk) |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "malloc fail for FXP_WRITE"); |
| |
| chunk->len = size; |
| chunk->sent = 0; |
| chunk->lefttosend = packet_len; |
| |
| s = chunk->packet; |
| _libssh2_store_u32(&s, packet_len - 4); |
| |
| *(s++) = SSH_FXP_WRITE; |
| request_id = sftp->request_id++; |
| chunk->request_id = request_id; |
| _libssh2_store_u32(&s, request_id); |
| _libssh2_store_str(&s, handle->handle, handle->handle_len); |
| _libssh2_store_u64(&s, handle->u.file.offset_sent); |
| handle->u.file.offset_sent += size; /* advance offset at once */ |
| _libssh2_store_str(&s, buffer, size); |
| |
| /* add this new entry LAST in the list */ |
| _libssh2_list_add(&handle->packet_list, &chunk->node); |
| |
| buffer += size; |
| count -= size; /* deduct the size we used, as we might have |
| to create more packets */ |
| } |
| |
| /* move through the WRITE packets that haven't been sent and send as |
| many as possible - remember that we don't block */ |
| chunk = _libssh2_list_first(&handle->packet_list); |
| |
| while(chunk) { |
| if(chunk->lefttosend) { |
| rc = _libssh2_channel_write(channel, 0, |
| &chunk->packet[chunk->sent], |
| chunk->lefttosend); |
| if(rc < 0) |
| /* remain in idle state */ |
| return rc; |
| |
| /* remember where to continue sending the next time */ |
| chunk->lefttosend -= rc; |
| chunk->sent += rc; |
| |
| if(chunk->lefttosend) |
| /* data left to send, get out of loop */ |
| break; |
| } |
| |
| /* move on to the next chunk with data to send */ |
| chunk = _libssh2_list_next(&chunk->node); |
| } |
| |
| /* fall-through */ |
| case libssh2_NB_state_sent: |
| |
| sftp->write_state = libssh2_NB_state_idle; |
| /* |
| * Count all ACKed packets |
| */ |
| chunk = _libssh2_list_first(&handle->packet_list); |
| |
| while(chunk) { |
| if(chunk->lefttosend) |
| /* if the chunk still has data left to send, we shouldn't wait |
| for an ACK for it just yet */ |
| break; |
| |
| else if(acked) |
| /* if we have sent data that is acked, we must return that |
| info before we call a function that might return EAGAIN */ |
| break; |
| |
| /* we check the packets in order */ |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, |
| chunk->request_id, &data, &data_len, 9); |
| if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "FXP write packet too short"); |
| } |
| else if(rc < 0) { |
| if(rc == LIBSSH2_ERROR_EAGAIN) |
| sftp->write_state = libssh2_NB_state_sent; |
| return rc; |
| } |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| sftp->last_errno = retcode; |
| if(retcode == LIBSSH2_FX_OK) { |
| acked += chunk->len; /* number of payload data that was acked |
| here */ |
| |
| /* we increase the offset value for all acks */ |
| handle->u.file.offset += chunk->len; |
| |
| next = _libssh2_list_next(&chunk->node); |
| |
| _libssh2_list_remove(&chunk->node); /* remove from list */ |
| LIBSSH2_FREE(session, chunk); /* free memory */ |
| |
| chunk = next; |
| } |
| else { |
| /* flush all pending packets from the outgoing list */ |
| sftp_packetlist_flush(handle); |
| |
| /* since we return error now, the application will not get any |
| outstanding data acked, so we need to rewind the offset to |
| where the application knows it has reached with acked |
| data */ |
| handle->u.file.offset -= handle->u.file.acked; |
| |
| /* then reset the offset_sent to be the same as the offset */ |
| handle->u.file.offset_sent = handle->u.file.offset; |
| |
| /* clear the acked counter since we can have no pending data to |
| ack after an error */ |
| handle->u.file.acked = 0; |
| |
| /* the server returned an error for that written chunk, |
| propagate this back to our parent function */ |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "FXP write failed"); |
| } |
| } |
| break; |
| } |
| |
| /* if there were acked data in a previous call that wasn't returned then, |
| add that up and try to return it all now. This can happen if the app |
| first sends a huge buffer of data, and then in a second call it sends a |
| smaller one. */ |
| acked += handle->u.file.acked; |
| |
| if(acked) { |
| ssize_t ret = MIN(acked, org_count); |
| /* we got data acked so return that amount, but no more than what |
| was asked to get sent! */ |
| |
| /* store the remainder. 'ret' is always equal to or less than 'acked' |
| here */ |
| handle->u.file.acked = acked - ret; |
| |
| return ret; |
| } |
| |
| else |
| return 0; /* nothing was acked, and no EAGAIN was received! */ |
| } |
| |
| /* 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; |
| if(!hnd) |
| return LIBSSH2_ERROR_BAD_USE; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, |
| sftp_write(hnd, buffer, count)); |
| return rc; |
| |
| } |
| |
| static int sftp_fsync(LIBSSH2_SFTP_HANDLE *handle) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| /* 34 = packet_len(4) + packet_type(1) + request_id(4) + |
| string_len(4) + strlen("[email protected]")(17) + handle_len(4) */ |
| uint32_t packet_len = handle->handle_len + 34; |
| size_t data_len; |
| unsigned char *packet, *s, *data; |
| ssize_t rc; |
| uint32_t retcode; |
| |
| if(sftp->fsync_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Issuing fsync command"); |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_EXTENDED " |
| "packet"); |
| } |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = SSH_FXP_EXTENDED; |
| sftp->fsync_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->fsync_request_id); |
| _libssh2_store_str(&s, "[email protected]", 17); |
| _libssh2_store_str(&s, handle->handle, handle->handle_len); |
| |
| sftp->fsync_state = libssh2_NB_state_created; |
| } |
| else { |
| packet = sftp->fsync_packet; |
| } |
| |
| if(sftp->fsync_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, packet, packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN || |
| (0 <= rc && rc < (ssize_t)packet_len)) { |
| sftp->fsync_packet = packet; |
| return LIBSSH2_ERROR_EAGAIN; |
| } |
| |
| LIBSSH2_FREE(session, packet); |
| sftp->fsync_packet = NULL; |
| |
| if(rc < 0) { |
| sftp->fsync_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "_libssh2_channel_write() failed"); |
| } |
| sftp->fsync_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, |
| sftp->fsync_request_id, &data, &data_len, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP fsync packet too short"); |
| } |
| else if(rc) { |
| sftp->fsync_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Error waiting for FXP EXTENDED REPLY"); |
| } |
| |
| sftp->fsync_state = libssh2_NB_state_idle; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| if(retcode != LIBSSH2_FX_OK) { |
| sftp->last_errno = retcode; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "fsync failed"); |
| } |
| |
| return 0; |
| } |
| |
| /* libssh2_sftp_fsync |
| * Commit data on the handle to disk. |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_fsync(LIBSSH2_SFTP_HANDLE *hnd) |
| { |
| int rc; |
| if(!hnd) |
| return LIBSSH2_ERROR_BAD_USE; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, |
| sftp_fsync(hnd)); |
| 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; |
| size_t data_len; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ |
| uint32_t packet_len = |
| handle->handle_len + 13 + (setstat ? sftp_attrsize(attrs->flags) : 0); |
| unsigned char *s, *data; |
| static const unsigned char fstat_responses[2] = |
| { SSH_FXP_ATTRS, SSH_FXP_STATUS }; |
| ssize_t rc; |
| |
| if(sftp->fstat_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Issuing %s command", |
| setstat ? "set-stat" : "stat"); |
| s = sftp->fstat_packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!sftp->fstat_packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for " |
| "FSTAT/FSETSTAT packet"); |
| } |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = setstat ? SSH_FXP_FSETSTAT : SSH_FXP_FSTAT; |
| sftp->fstat_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->fstat_request_id); |
| _libssh2_store_str(&s, handle->handle, 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, sftp->fstat_packet, |
| packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if((ssize_t)packet_len != rc) { |
| LIBSSH2_FREE(session, sftp->fstat_packet); |
| sftp->fstat_packet = NULL; |
| sftp->fstat_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| (setstat ? "Unable to send FXP_FSETSTAT" |
| : "Unable to send FXP_FSTAT command")); |
| } |
| 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, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) |
| return rc; |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP fstat packet too short"); |
| } |
| else if(rc) { |
| sftp->fstat_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Timeout waiting for status message"); |
| } |
| |
| sftp->fstat_state = libssh2_NB_state_idle; |
| |
| if(data[0] == SSH_FXP_STATUS) { |
| uint32_t retcode; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| if(retcode == LIBSSH2_FX_OK) { |
| return 0; |
| } |
| else { |
| sftp->last_errno = retcode; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| } |
| |
| if(sftp_bin2attr(attrs, data + 5, data_len - 5) < 0) { |
| LIBSSH2_FREE(session, data); |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Attributes too short in SFTP fstat"); |
| } |
| |
| LIBSSH2_FREE(session, data); |
| |
| 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; |
| if(!hnd || !attrs) |
| return LIBSSH2_ERROR_BAD_USE; |
| BLOCK_ADJUST(rc, hnd->sftp->channel->session, |
| sftp_fstat(hnd, attrs, setstat)); |
| return rc; |
| } |
| |
| |
| /* 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) |
| { |
| if(!handle) |
| return; |
| if(handle->u.file.offset == offset && handle->u.file.offset_sent == offset) |
| return; |
| |
| handle->u.file.offset = handle->u.file.offset_sent = offset; |
| /* discard all pending requests and currently read data */ |
| sftp_packetlist_flush(handle); |
| |
| /* free the left received buffered data */ |
| if(handle->u.file.data_left) { |
| LIBSSH2_FREE(handle->sftp->channel->session, handle->u.file.data); |
| handle->u.file.data_left = handle->u.file.data_len = 0; |
| handle->u.file.data = NULL; |
| } |
| |
| /* reset EOF to False */ |
| handle->u.file.eof = FALSE; |
| } |
| |
| /* 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) |
| { |
| libssh2_sftp_seek64(handle, (libssh2_uint64_t)offset); |
| } |
| |
| /* libssh2_sftp_tell |
| * Return the current read/write pointer's offset |
| */ |
| LIBSSH2_API size_t |
| libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE *handle) |
| { |
| if(!handle) |
| return 0; /* no handle, no size */ |
| |
| /* 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) |
| { |
| if(!handle) |
| return 0; /* no handle, no size */ |
| |
| return handle->u.file.offset; |
| } |
| |
| /* |
| * Flush all remaining incoming SFTP packets and zombies. |
| */ |
| static void sftp_packet_flush(LIBSSH2_SFTP *sftp) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| LIBSSH2_SFTP_PACKET *packet = _libssh2_list_first(&sftp->packets); |
| struct sftp_zombie_requests *zombie = |
| _libssh2_list_first(&sftp->zombie_requests); |
| |
| while(packet) { |
| LIBSSH2_SFTP_PACKET *next; |
| |
| /* check next struct in the list */ |
| next = _libssh2_list_next(&packet->node); |
| _libssh2_list_remove(&packet->node); |
| LIBSSH2_FREE(session, packet->data); |
| LIBSSH2_FREE(session, packet); |
| |
| packet = next; |
| } |
| |
| while(zombie) { |
| /* figure out the next node */ |
| struct sftp_zombie_requests *next = _libssh2_list_next(&zombie->node); |
| /* unlink the current one */ |
| _libssh2_list_remove(&zombie->node); |
| /* free the memory */ |
| LIBSSH2_FREE(session, zombie); |
| zombie = next; |
| } |
| |
| } |
| |
| /* sftp_close_handle |
| * |
| * Close a file or directory handle. |
| * Also frees handle resource and unlinks it from the SFTP structure. |
| * The handle is no longer usable after return of this function, unless |
| * the return value is LIBSSH2_ERROR_EAGAIN in which case this function |
| * should be called again. |
| */ |
| static int |
| sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| size_t data_len; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ |
| uint32_t packet_len = handle->handle_len + 13; |
| unsigned char *s, *data = NULL; |
| int rc = 0; |
| |
| if(handle->close_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Closing handle"); |
| s = handle->close_packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!handle->close_packet) { |
| handle->close_state = libssh2_NB_state_idle; |
| rc = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_CLOSE " |
| "packet"); |
| } |
| else { |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = SSH_FXP_CLOSE; |
| handle->close_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, handle->close_request_id); |
| _libssh2_store_str(&s, handle->handle, handle->handle_len); |
| handle->close_state = libssh2_NB_state_created; |
| } |
| } |
| |
| if(handle->close_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, handle->close_packet, |
| packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if((ssize_t)packet_len != rc) { |
| handle->close_state = libssh2_NB_state_idle; |
| rc = _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_CLOSE command"); |
| } |
| else |
| handle->close_state = libssh2_NB_state_sent; |
| |
| LIBSSH2_FREE(session, handle->close_packet); |
| handle->close_packet = NULL; |
| } |
| |
| if(handle->close_state == libssh2_NB_state_sent) { |
| rc = sftp_packet_require(sftp, SSH_FXP_STATUS, |
| handle->close_request_id, &data, |
| &data_len, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| data = NULL; |
| _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Packet too short in FXP_CLOSE command"); |
| } |
| else if(rc) { |
| _libssh2_error(session, rc, |
| "Error waiting for status message"); |
| } |
| |
| handle->close_state = libssh2_NB_state_sent1; |
| } |
| |
| if(!data) { |
| /* if it reaches this point with data unset, something unwanted |
| happened for which we should have set an error code */ |
| assert(rc); |
| |
| } |
| else { |
| int retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| if(retcode != LIBSSH2_FX_OK) { |
| sftp->last_errno = retcode; |
| handle->close_state = libssh2_NB_state_idle; |
| rc = _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| } |
| |
| /* remove this handle from the parent's list */ |
| _libssh2_list_remove(&handle->node); |
| |
| if(handle->handle_type == LIBSSH2_SFTP_HANDLE_DIR) { |
| if(handle->u.dir.names_left) |
| LIBSSH2_FREE(session, handle->u.dir.names_packet); |
| } |
| else if(handle->handle_type == LIBSSH2_SFTP_HANDLE_FILE) { |
| if(handle->u.file.data) |
| LIBSSH2_FREE(session, handle->u.file.data); |
| } |
| |
| sftp_packetlist_flush(handle); |
| sftp->read_state = libssh2_NB_state_idle; |
| |
| handle->close_state = libssh2_NB_state_idle; |
| |
| LIBSSH2_FREE(session, handle); |
| |
| return rc; |
| } |
| |
| /* 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; |
| if(!hnd) |
| return LIBSSH2_ERROR_BAD_USE; |
| 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, |
| size_t filename_len) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| size_t data_len; |
| int retcode; |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) */ |
| uint32_t packet_len = filename_len + 13; |
| unsigned char *s, *data; |
| int rc; |
| |
| if(sftp->unlink_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Unlinking %s", filename); |
| s = sftp->unlink_packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!sftp->unlink_packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_REMOVE " |
| "packet"); |
| } |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = SSH_FXP_REMOVE; |
| sftp->unlink_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->unlink_request_id); |
| _libssh2_store_str(&s, filename, filename_len); |
| sftp->unlink_state = libssh2_NB_state_created; |
| } |
| |
| if(sftp->unlink_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, sftp->unlink_packet, |
| packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if((ssize_t)packet_len != rc) { |
| LIBSSH2_FREE(session, sftp->unlink_packet); |
| sftp->unlink_packet = NULL; |
| sftp->unlink_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_REMOVE command"); |
| } |
| 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, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP unlink packet too short"); |
| } |
| else if(rc) { |
| sftp->unlink_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Error waiting for FXP STATUS"); |
| } |
| |
| 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; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| } |
| |
| /* 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; |
| if(!sftp) |
| return LIBSSH2_ERROR_BAD_USE; |
| 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; |
| size_t data_len; |
| int retcode; |
| uint32_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; |
| ssize_t rc; |
| |
| if(sftp->version < 2) { |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Server does not support RENAME"); |
| } |
| |
| if(sftp->rename_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "Renaming %s to %s", |
| source_filename, dest_filename); |
| sftp->rename_s = sftp->rename_packet = |
| LIBSSH2_ALLOC(session, packet_len); |
| if(!sftp->rename_packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_RENAME " |
| "packet"); |
| } |
| |
| _libssh2_store_u32(&sftp->rename_s, packet_len - 4); |
| *(sftp->rename_s++) = SSH_FXP_RENAME; |
| sftp->rename_request_id = sftp->request_id++; |
| _libssh2_store_u32(&sftp->rename_s, sftp->rename_request_id); |
| _libssh2_store_str(&sftp->rename_s, source_filename, |
| source_filename_len); |
| _libssh2_store_str(&sftp->rename_s, dest_filename, dest_filename_len); |
| |
| if(sftp->version >= 5) |
| _libssh2_store_u32(&sftp->rename_s, flags); |
| |
| sftp->rename_state = libssh2_NB_state_created; |
| } |
| |
| if(sftp->rename_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, sftp->rename_packet, |
| sftp->rename_s - sftp->rename_packet); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if((ssize_t)packet_len != rc) { |
| LIBSSH2_FREE(session, sftp->rename_packet); |
| sftp->rename_packet = NULL; |
| sftp->rename_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_RENAME command"); |
| } |
| 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, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP rename packet too short"); |
| } |
| else if(rc) { |
| sftp->rename_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Error waiting for FXP STATUS"); |
| } |
| |
| sftp->rename_state = libssh2_NB_state_idle; |
| |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| |
| sftp->last_errno = retcode; |
| |
| /* now convert the SFTP error code to libssh2 return code or error |
| message */ |
| switch(retcode) { |
| case LIBSSH2_FX_OK: |
| retcode = LIBSSH2_ERROR_NONE; |
| break; |
| |
| case LIBSSH2_FX_FILE_ALREADY_EXISTS: |
| retcode = _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "File already exists and " |
| "SSH_FXP_RENAME_OVERWRITE not specified"); |
| break; |
| |
| case LIBSSH2_FX_OP_UNSUPPORTED: |
| retcode = _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Operation Not Supported"); |
| break; |
| |
| default: |
| retcode = _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| break; |
| } |
| |
| 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; |
| if(!sftp) |
| return LIBSSH2_ERROR_BAD_USE; |
| BLOCK_ADJUST(rc, sftp->channel->session, |
| sftp_rename(sftp, source_filename, source_filename_len, |
| dest_filename, dest_filename_len, flags)); |
| return rc; |
| } |
| |
| /* |
| * sftp_fstatvfs |
| * |
| * Get file system statistics |
| */ |
| static int sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_STATVFS *st) |
| { |
| LIBSSH2_SFTP *sftp = handle->sftp; |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| size_t data_len; |
| /* 17 = packet_len(4) + packet_type(1) + request_id(4) + ext_len(4) |
| + handle_len (4) */ |
| /* 20 = strlen ("[email protected]") */ |
| uint32_t packet_len = handle->handle_len + 20 + 17; |
| unsigned char *packet, *s, *data; |
| ssize_t rc; |
| unsigned int flag; |
| static const unsigned char responses[2] = |
| { SSH_FXP_EXTENDED_REPLY, SSH_FXP_STATUS }; |
| |
| if(sftp->fstatvfs_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Getting file system statistics"); |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_EXTENDED " |
| "packet"); |
| } |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = SSH_FXP_EXTENDED; |
| sftp->fstatvfs_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->fstatvfs_request_id); |
| _libssh2_store_str(&s, "[email protected]", 20); |
| _libssh2_store_str(&s, handle->handle, handle->handle_len); |
| |
| sftp->fstatvfs_state = libssh2_NB_state_created; |
| } |
| else { |
| packet = sftp->fstatvfs_packet; |
| } |
| |
| if(sftp->fstatvfs_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, packet, packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN || |
| (0 <= rc && rc < (ssize_t)packet_len)) { |
| sftp->fstatvfs_packet = packet; |
| return LIBSSH2_ERROR_EAGAIN; |
| } |
| |
| LIBSSH2_FREE(session, packet); |
| sftp->fstatvfs_packet = NULL; |
| |
| if(rc < 0) { |
| sftp->fstatvfs_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "_libssh2_channel_write() failed"); |
| } |
| sftp->fstatvfs_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_requirev(sftp, 2, responses, sftp->fstatvfs_request_id, |
| &data, &data_len, 9); |
| |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP rename packet too short"); |
| } |
| else if(rc) { |
| sftp->fstatvfs_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Error waiting for FXP EXTENDED REPLY"); |
| } |
| |
| if(data[0] == SSH_FXP_STATUS) { |
| int retcode = _libssh2_ntohu32(data + 5); |
| sftp->fstatvfs_state = libssh2_NB_state_idle; |
| LIBSSH2_FREE(session, data); |
| sftp->last_errno = retcode; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| |
| if(data_len < 93) { |
| LIBSSH2_FREE(session, data); |
| sftp->fstatvfs_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error: short response"); |
| } |
| |
| sftp->fstatvfs_state = libssh2_NB_state_idle; |
| |
| st->f_bsize = _libssh2_ntohu64(data + 5); |
| st->f_frsize = _libssh2_ntohu64(data + 13); |
| st->f_blocks = _libssh2_ntohu64(data + 21); |
| st->f_bfree = _libssh2_ntohu64(data + 29); |
| st->f_bavail = _libssh2_ntohu64(data + 37); |
| st->f_files = _libssh2_ntohu64(data + 45); |
| st->f_ffree = _libssh2_ntohu64(data + 53); |
| st->f_favail = _libssh2_ntohu64(data + 61); |
| st->f_fsid = _libssh2_ntohu64(data + 69); |
| flag = (unsigned int)_libssh2_ntohu64(data + 77); |
| st->f_namemax = _libssh2_ntohu64(data + 85); |
| |
| st->f_flag = (flag & SSH_FXE_STATVFS_ST_RDONLY) |
| ? LIBSSH2_SFTP_ST_RDONLY : 0; |
| st->f_flag |= (flag & SSH_FXE_STATVFS_ST_NOSUID) |
| ? LIBSSH2_SFTP_ST_NOSUID : 0; |
| |
| LIBSSH2_FREE(session, data); |
| return 0; |
| } |
| |
| /* libssh2_sftp_fstatvfs |
| * Get filesystem space and inode utilization (requires [email protected] |
| * support on the server) |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_fstatvfs(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_STATVFS *st) |
| { |
| int rc; |
| if(!handle || !st) |
| return LIBSSH2_ERROR_BAD_USE; |
| BLOCK_ADJUST(rc, handle->sftp->channel->session, |
| sftp_fstatvfs(handle, st)); |
| return rc; |
| } |
| |
| /* |
| * sftp_statvfs |
| * |
| * Get file system statistics |
| */ |
| static int sftp_statvfs(LIBSSH2_SFTP *sftp, const char *path, |
| unsigned int path_len, LIBSSH2_SFTP_STATVFS *st) |
| { |
| LIBSSH2_CHANNEL *channel = sftp->channel; |
| LIBSSH2_SESSION *session = channel->session; |
| size_t data_len; |
| /* 17 = packet_len(4) + packet_type(1) + request_id(4) + ext_len(4) |
| + path_len (4) */ |
| /* 19 = strlen ("[email protected]") */ |
| uint32_t packet_len = path_len + 19 + 17; |
| unsigned char *packet, *s, *data; |
| ssize_t rc; |
| unsigned int flag; |
| static const unsigned char responses[2] = |
| { SSH_FXP_EXTENDED_REPLY, SSH_FXP_STATUS }; |
| |
| if(sftp->statvfs_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Getting file system statistics of %s", path); |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_EXTENDED " |
| "packet"); |
| } |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = SSH_FXP_EXTENDED; |
| sftp->statvfs_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->statvfs_request_id); |
| _libssh2_store_str(&s, "[email protected]", 19); |
| _libssh2_store_str(&s, path, path_len); |
| |
| sftp->statvfs_state = libssh2_NB_state_created; |
| } |
| else { |
| packet = sftp->statvfs_packet; |
| } |
| |
| if(sftp->statvfs_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, packet, packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN || |
| (0 <= rc && rc < (ssize_t)packet_len)) { |
| sftp->statvfs_packet = packet; |
| return LIBSSH2_ERROR_EAGAIN; |
| } |
| |
| LIBSSH2_FREE(session, packet); |
| sftp->statvfs_packet = NULL; |
| |
| if(rc < 0) { |
| sftp->statvfs_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "_libssh2_channel_write() failed"); |
| } |
| sftp->statvfs_state = libssh2_NB_state_sent; |
| } |
| |
| rc = sftp_packet_requirev(sftp, 2, responses, sftp->statvfs_request_id, |
| &data, &data_len, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP fstat packet too short"); |
| } |
| else if(rc) { |
| sftp->statvfs_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Error waiting for FXP EXTENDED REPLY"); |
| } |
| |
| if(data[0] == SSH_FXP_STATUS) { |
| int retcode = _libssh2_ntohu32(data + 5); |
| sftp->statvfs_state = libssh2_NB_state_idle; |
| LIBSSH2_FREE(session, data); |
| sftp->last_errno = retcode; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| |
| if(data_len < 93) { |
| LIBSSH2_FREE(session, data); |
| sftp->statvfs_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error: short response"); |
| } |
| |
| sftp->statvfs_state = libssh2_NB_state_idle; |
| |
| st->f_bsize = _libssh2_ntohu64(data + 5); |
| st->f_frsize = _libssh2_ntohu64(data + 13); |
| st->f_blocks = _libssh2_ntohu64(data + 21); |
| st->f_bfree = _libssh2_ntohu64(data + 29); |
| st->f_bavail = _libssh2_ntohu64(data + 37); |
| st->f_files = _libssh2_ntohu64(data + 45); |
| st->f_ffree = _libssh2_ntohu64(data + 53); |
| st->f_favail = _libssh2_ntohu64(data + 61); |
| st->f_fsid = _libssh2_ntohu64(data + 69); |
| flag = (unsigned int)_libssh2_ntohu64(data + 77); |
| st->f_namemax = _libssh2_ntohu64(data + 85); |
| |
| st->f_flag = (flag & SSH_FXE_STATVFS_ST_RDONLY) |
| ? LIBSSH2_SFTP_ST_RDONLY : 0; |
| st->f_flag |= (flag & SSH_FXE_STATVFS_ST_NOSUID) |
| ? LIBSSH2_SFTP_ST_NOSUID : 0; |
| |
| LIBSSH2_FREE(session, data); |
| return 0; |
| } |
| |
| /* libssh2_sftp_statvfs_ex |
| * Get filesystem space and inode utilization (requires [email protected] |
| * support on the server) |
| */ |
| LIBSSH2_API int |
| libssh2_sftp_statvfs(LIBSSH2_SFTP *sftp, const char *path, |
| size_t path_len, LIBSSH2_SFTP_STATVFS *st) |
| { |
| int rc; |
| if(!sftp || !st) |
| return LIBSSH2_ERROR_BAD_USE; |
| BLOCK_ADJUST(rc, sftp->channel->session, sftp_statvfs(sftp, path, path_len, |
| st)); |
| 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 = { |
| 0, 0, 0, 0, 0, 0, 0 |
| }; |
| size_t data_len; |
| int retcode; |
| ssize_t packet_len; |
| unsigned char *packet, *s, *data; |
| int rc; |
| |
| if(mode != LIBSSH2_SFTP_DEFAULT_MODE) { |
| /* Filetype in SFTP 3 and earlier */ |
| attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; |
| attrs.permissions = mode | LIBSSH2_SFTP_ATTR_PFILETYPE_DIR; |
| } |
| |
| /* 13 = packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ |
| packet_len = path_len + 13 + sftp_attrsize(attrs.flags); |
| |
| if(sftp->mkdir_state == libssh2_NB_state_idle) { |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, |
| "Creating directory %s with mode 0%lo", path, mode); |
| s = packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_MKDIR " |
| "packet"); |
| } |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = SSH_FXP_MKDIR; |
| sftp->mkdir_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->mkdir_request_id); |
| _libssh2_store_str(&s, path, 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, packet, packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| sftp->mkdir_packet = packet; |
| return rc; |
| } |
| if(packet_len != rc) { |
| LIBSSH2_FREE(session, packet); |
| sftp->mkdir_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "_libssh2_channel_write() failed"); |
| } |
| 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, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP mkdir packet too short"); |
| } |
| else if(rc) { |
| sftp->mkdir_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Error waiting for FXP STATUS"); |
| } |
| |
| 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_TRACE_SFTP, "OK!"); |
| return 0; |
| } |
| else { |
| sftp->last_errno = retcode; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| } |
| |
| /* |
| * 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; |
| if(!sftp) |
| return LIBSSH2_ERROR_BAD_USE; |
| 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; |
| size_t data_len; |
| int 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_TRACE_SFTP, "Removing directory: %s", |
| path); |
| s = sftp->rmdir_packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!sftp->rmdir_packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_RMDIR " |
| "packet"); |
| } |
| |
| _libssh2_store_u32(&s, packet_len - 4); |
| *(s++) = SSH_FXP_RMDIR; |
| sftp->rmdir_request_id = sftp->request_id++; |
| _libssh2_store_u32(&s, sftp->rmdir_request_id); |
| _libssh2_store_str(&s, path, path_len); |
| |
| sftp->rmdir_state = libssh2_NB_state_created; |
| } |
| |
| if(sftp->rmdir_state == libssh2_NB_state_created) { |
| rc = _libssh2_channel_write(channel, 0, sftp->rmdir_packet, |
| packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(packet_len != rc) { |
| LIBSSH2_FREE(session, sftp->rmdir_packet); |
| sftp->rmdir_packet = NULL; |
| sftp->rmdir_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send FXP_RMDIR command"); |
| } |
| 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, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP rmdir packet too short"); |
| } |
| else if(rc) { |
| sftp->rmdir_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Error waiting for FXP STATUS"); |
| } |
| |
| 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; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| } |
| |
| /* 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; |
| if(!sftp) |
| return LIBSSH2_ERROR_BAD_USE; |
| 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; |
| size_t 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->flags) : 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_TRACE_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) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for FXP_*STAT " |
| "packet"); |
| } |
| |
| _libssh2_store_u32(&s, packet_len - 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_store_u32(&s, sftp->stat_request_id); |
| _libssh2_store_str(&s, path, 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, sftp->stat_packet, packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) { |
| return rc; |
| } |
| else if(packet_len != rc) { |
| LIBSSH2_FREE(session, sftp->stat_packet); |
| sftp->stat_packet = NULL; |
| sftp->stat_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send STAT/LSTAT/SETSTAT command"); |
| } |
| 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, 9); |
| if(rc == LIBSSH2_ERROR_EAGAIN) |
| return rc; |
| else if(rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP stat packet too short"); |
| } |
| else if(rc) { |
| sftp->stat_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, rc, |
| "Timeout waiting for status message"); |
| } |
| |
| 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) { |
| memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); |
| return 0; |
| } |
| else { |
| sftp->last_errno = retcode; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| } |
| |
| memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); |
| if(sftp_bin2attr(attrs, data + 5, data_len - 5) < 0) { |
| LIBSSH2_FREE(session, data); |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Attributes too short in SFTP fstat"); |
| } |
| |
| 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; |
| if(!sftp) |
| return LIBSSH2_ERROR_BAD_USE; |
| 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; |
| size_t 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 retcode; |
| |
| if((sftp->version < 3) && (link_type != LIBSSH2_SFTP_REALPATH)) { |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Server does not support SYMLINK or READLINK"); |
| } |
| |
| if(sftp->symlink_state == libssh2_NB_state_idle) { |
| s = sftp->symlink_packet = LIBSSH2_ALLOC(session, packet_len); |
| if(!sftp->symlink_packet) { |
| return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
| "Unable to allocate memory for " |
| "SYMLINK/READLINK/REALPATH packet"); |
| } |
| |
| _libssh2_debug(session, LIBSSH2_TRACE_SFTP, "%s %s on %s", |
| (link_type == |
| LIBSSH2_SFTP_SYMLINK) ? "Creating" : "Reading", |
| (link_type == |
| LIBSSH2_SFTP_REALPATH) ? "realpath" : "symlink", path); |
| |
| _libssh2_store_u32(&s, packet_len - 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_store_u32(&s, sftp->symlink_request_id); |
| _libssh2_store_str(&s, path, path_len); |
| |
| if(link_type == LIBSSH2_SFTP_SYMLINK) |
| _libssh2_store_str(&s, target, target_len); |
| |
| sftp->symlink_state = libssh2_NB_state_created; |
| } |
| |
| if(sftp->symlink_state == libssh2_NB_state_created) { |
| ssize_t rc = _libssh2_channel_write(channel, 0, sftp->symlink_packet, |
| packet_len); |
| if(rc == LIBSSH2_ERROR_EAGAIN) |
| return rc; |
| else if(packet_len != rc) { |
| LIBSSH2_FREE(session, sftp->symlink_packet); |
| sftp->symlink_packet = NULL; |
| sftp->symlink_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, |
| "Unable to send SYMLINK/READLINK command"); |
| } |
| LIBSSH2_FREE(session, sftp->symlink_packet); |
| sftp->symlink_packet = NULL; |
| |
| sftp->symlink_state = libssh2_NB_state_sent; |
| } |
| |
| retcode = sftp_packet_requirev(sftp, 2, link_responses, |
| sftp->symlink_request_id, &data, |
| &data_len, 9); |
| if(retcode == LIBSSH2_ERROR_EAGAIN) |
| return retcode; |
| else if(retcode == LIBSSH2_ERROR_BUFFER_TOO_SMALL) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP symlink packet too short"); |
| } |
| else if(retcode) { |
| sftp->symlink_state = libssh2_NB_state_idle; |
| return _libssh2_error(session, retcode, |
| "Error waiting for status message"); |
| } |
| |
| sftp->symlink_state = libssh2_NB_state_idle; |
| |
| if(data[0] == SSH_FXP_STATUS) { |
| retcode = _libssh2_ntohu32(data + 5); |
| LIBSSH2_FREE(session, data); |
| if(retcode == LIBSSH2_FX_OK) |
| return LIBSSH2_ERROR_NONE; |
| else { |
| sftp->last_errno = retcode; |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP Protocol Error"); |
| } |
| } |
| |
| if(_libssh2_ntohu32(data + 5) < 1) { |
| LIBSSH2_FREE(session, data); |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "Invalid READLINK/REALPATH response, " |
| "no name entries"); |
| } |
| |
| if(data_len < 13) { |
| if(data_len > 0) { |
| LIBSSH2_FREE(session, data); |
| } |
| return _libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, |
| "SFTP stat packet too short"); |
| } |
| |
| /* this reads a u32 and stores it into a signed 32bit value */ |
| link_len = _libssh2_ntohu32(data + 9); |
| if(link_len < target_len) { |
| memcpy(target, data + 13, link_len); |
| target[link_len] = 0; |
| retcode = (int)link_len; |
| } |
| else |
| retcode = LIBSSH2_ERROR_BUFFER_TOO_SMALL; |
| LIBSSH2_FREE(session, data); |
| |
| return retcode; |
| } |
| |
| /* 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; |
| if(!sftp) |
| return LIBSSH2_ERROR_BAD_USE; |
| 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) |
| { |
| if(!sftp) |
| return 0; |
| |
| return sftp->last_errno; |
| } |
| |
| /* libssh2_sftp_get_channel |
| * Return the channel of sftp, then caller can control the channel's behavior. |
| */ |
| LIBSSH2_API LIBSSH2_CHANNEL * |
| libssh2_sftp_get_channel(LIBSSH2_SFTP *sftp) |
| { |
| if(!sftp) |
| return NULL; |
| |
| return sftp->channel; |
| } |