From 525a18103707bcb8974f9dc2e6871b12a3ed8a99 Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Wed, 22 Dec 2004 00:20:02 +0000 Subject: [PATCH] Add SFTP support --- Makefile.in | 4 +- README | 2 + include/libssh2.h | 3 +- include/libssh2_priv.h | 2 + include/libssh2_sftp.h | 189 ++++++ src/Makefile.in | 5 +- src/misc.c | 33 ++ src/sftp.c | 1280 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1515 insertions(+), 3 deletions(-) create mode 100644 include/libssh2_sftp.h create mode 100644 src/sftp.c diff --git a/Makefile.in b/Makefile.in index 16a3218..af2157b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -28,6 +28,7 @@ install: || case "$(MFLAGS)" in *k*) fail=yes;; *) exit 1;; esac; \ done && test -z "$$fail" $(INSTALL) -m 644 include/libssh2.h $(incldir)/ + $(INSTALL) -m 644 include/libssh2_sftp.h $(incldir)/ clean: @for dir in ${subdirs}; do \ (cd $$dir && $(MAKE) clean) \ @@ -44,5 +45,6 @@ dist: $(DISTLIB)/LICENSE $(DISTLIB)/README $(DISTLIB)/TODO $(DISTLIB)/INSTALL \ $(DISTLIB)/mkinstalldirs $(DISTLIB)/install-sh \ $(DISTLIB)/src/*.c $(DISTLIB)/src/Makefile.in \ - $(DISTLIB)/include/libssh2.h $(DISTLIB)/include/libssh2_priv.h $(DISTLIB)/include/libssh2_config.h.in + $(DISTLIB)/include/libssh2.h $(DISTLIB)/include/libssh2_priv.h $(DISTLIB)/include/libssh2_sftp.h \ + $(DISTLIB)/include/libssh2_config.h.in rm -f $(DISTLIB) diff --git a/README b/README index 343a1b0..4b02224 100644 --- a/README +++ b/README @@ -12,6 +12,8 @@ Version 0.3 Added channel close callback. + Added SFTP support (Using its own header file: libssh2_sftp.h) + Version 0.2 ----------- diff --git a/include/libssh2.h b/include/libssh2.h index ae219b8..6044e79 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -43,7 +43,7 @@ #include #define LIBSSH2_VERSION "0.2" -#define LIBSSH2_APINO 200412161500 +#define LIBSSH2_APINO 200412211608 /* Part of every banner, user specified or not */ #define LIBSSH2_SSH_BANNER "SSH-2.0-libssh2_" LIBSSH2_VERSION @@ -217,6 +217,7 @@ typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; #define LIBSSH2_ERROR_SCP_PROTOCOL -28 #define LIBSSH2_ERROR_ZLIB -29 #define LIBSSH2_ERROR_SOCKET_TIMEOUT -30 +#define LIBSSH2_ERROR_SFTP_PROTOCOL -31 /* Session API */ LIBSSH2_API LIBSSH2_SESSION *libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), LIBSSH2_FREE_FUNC((*my_free)), LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract); diff --git a/include/libssh2_priv.h b/include/libssh2_priv.h index e2fcf0f..8752958 100644 --- a/include/libssh2_priv.h +++ b/include/libssh2_priv.h @@ -360,7 +360,9 @@ struct _LIBSSH2_MAC_METHOD { void libssh2_session_shutdown(LIBSSH2_SESSION *session); unsigned long libssh2_ntohu32(const unsigned char *buf); +unsigned long long libssh2_ntohu64(const unsigned char *buf); void libssh2_htonu32(unsigned char *buf, unsigned long val); +void libssh2_htonu64(unsigned char *buf, unsigned long long val); int libssh2_packet_read(LIBSSH2_SESSION *session, int block); int libssh2_packet_ask_ex(LIBSSH2_SESSION *session, unsigned char packet_type, unsigned char **data, unsigned long *data_len, unsigned long match_ofs, const unsigned char *match_buf, unsigned long match_len, int poll_socket); diff --git a/include/libssh2_sftp.h b/include/libssh2_sftp.h new file mode 100644 index 0000000..96f855a --- /dev/null +++ b/include/libssh2_sftp.h @@ -0,0 +1,189 @@ +/* Copyright (c) 2004, Sara Golemon + * 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. + */ + +#ifndef LIBSSH2_SFTP_H +#define LIBSSH2_SFTP_H 1 + +/* Note: Version 6 was documented at the time of writing + * However it was marked as "DO NOT IMPLEMENT" due to pending changes + * + * Let's start with Version 3 (The version found in OpenSSH) and go from there + */ +#define LIBSSH2_SFTP_VERSION 3 +#define LIBSSH2_SFTP_PACKET_MAXLEN 40000 + +typedef struct _LIBSSH2_SFTP LIBSSH2_SFTP; +typedef struct _LIBSSH2_SFTP_HANDLE LIBSSH2_SFTP_HANDLE; +typedef struct _LIBSSH2_SFTP_ATTRIBUTES LIBSSH2_SFTP_ATTRIBUTES; + +/* Flags for open_ex() */ +#define LIBSSH2_SFTP_OPENFILE 0 +#define LIBSSH2_SFTP_OPENDIR 1 + +/* Flags for rename_ex() */ +#define LIBSSH2_SFTP_RENAME_OVERWRITE 0x00000001 +#define LIBSSH2_SFTP_RENAME_ATOMIC 0x00000002 +#define LIBSSH2_SFTP_RENAME_NATIVE 0x00000004 + +/* Flags for stat_ex() */ +#define LIBSSH2_SFTP_STAT 0 +#define LIBSSH2_SFTP_LSTAT 1 +#define LIBSSH2_SFTP_SETSTAT 2 + +/* Flags for symlink_ex() */ +#define LIBSSH2_SFTP_SYMLINK 0 +#define LIBSSH2_SFTP_READLINK 1 +#define LIBSSH2_SFTP_REALPATH 2 + +/* SFTP attribute flag bits */ +#define LIBSSH2_SFTP_ATTR_SIZE 0x00000001 +#define LIBSSH2_SFTP_ATTR_UIDGID 0x00000002 +#define LIBSSH2_SFTP_ATTR_PERMISSIONS 0x00000004 +#define LIBSSH2_SFTP_ATTR_ACMODTIME 0x00000008 +#define LIBSSH2_SFTP_ATTR_EXTENDED 0x80000000 + +struct _LIBSSH2_SFTP_ATTRIBUTES { + /* If flags & ATTR_* bit is set, then the value in this struct will be meaningful + * Otherwise it should be ignored + */ + unsigned long flags; + + unsigned long long filesize; + unsigned long uid, gid; + unsigned long permissions; + unsigned long atime, mtime; +}; + +/* SFTP filetypes */ +#define LIBSSH2_SFTP_TYPE_REGULAR 1 +#define LIBSSH2_SFTP_TYPE_DIRECTORY 2 +#define LIBSSH2_SFTP_TYPE_SYMLINK 3 +#define LIBSSH2_SFTP_TYPE_SPECIAL 4 +#define LIBSSH2_SFTP_TYPE_UNKNOWN 5 +#define LIBSSH2_SFTP_TYPE_SOCKET 6 +#define LIBSSH2_SFTP_TYPE_CHAR_DEVICE 7 +#define LIBSSH2_SFTP_TYPE_BLOCK_DEVICE 8 +#define LIBSSH2_SFTP_TYPE_FIFO 9 + +/* SFTP File Transfer Flags -- (e.g. flags parameter to sftp_open()) + * Danger will robinson... APPEND doesn't have any effect on OpenSSH servers */ +#define LIBSSH2_FXF_READ 0x00000001 +#define LIBSSH2_FXF_WRITE 0x00000002 +#define LIBSSH2_FXF_APPEND 0x00000004 +#define LIBSSH2_FXF_CREAT 0x00000008 +#define LIBSSH2_FXF_TRUNC 0x00000010 +#define LIBSSH2_FXF_EXCL 0x00000020 + +/* SFTP Status Codes */ +#define LIBSSH2_FX_OK 0 +#define LIBSSH2_FX_EOF 1 +#define LIBSSH2_FX_NO_SUCH_FILE 2 +#define LIBSSH2_FX_PERMISSION_DENIED 3 +#define LIBSSH2_FX_FAILURE 4 +#define LIBSSH2_FX_BAD_MESSAGE 5 +#define LIBSSH2_FX_NO_CONNECTION 6 +#define LIBSSH2_FX_CONNECTION_LOST 7 +#define LIBSSH2_FX_OP_UNSUPPORTED 8 +#define LIBSSH2_FX_INVALID_HANDLE 9 +#define LIBSSH2_FX_NO_SUCH_PATH 10 +#define LIBSSH2_FX_FILE_ALREADY_EXISTS 11 +#define LIBSSH2_FX_WRITE_PROTECT 12 +#define LIBSSH2_FX_NO_MEDIA 13 +#define LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM 14 +#define LIBSSH2_FX_QUOTA_EXCEEDED 15 +#define LIBSSH2_FX_UNKNOWN_PRINCIPLE 16 +#define LIBSSH2_FX_LOCK_CONFlICT 17 +#define LIBSSH2_FX_DIR_NOT_EMPTY 18 +#define LIBSSH2_FX_NOT_A_DIRECTORY 19 +#define LIBSSH2_FX_INVALID_FILENAME 20 +#define LIBSSH2_FX_LINK_LOOP 21 + +/* SFTP API */ +LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session); +LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp); + +/* File / Directory Ops */ +LIBSSH2_API LIBSSH2_SFTP_HANDLE *libssh2_sftp_open_ex(LIBSSH2_SFTP *sftp, char *filename, int filename_len, unsigned long flags, long mode, int open_type); +#define libssh2_sftp_open(sftp, filename, flags, mode) libssh2_sftp_open_ex((sftp), (filename), strlen(filename), (flags), (mode), LIBSSH2_SFTP_OPENFILE) +#define libssh2_sftp_opendir(sftp, path) libssh2_sftp_open_ex((sftp), (path), strlen(path), 0, 0, LIBSSH2_SFTP_OPENDIR) + +LIBSSH2_API size_t libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen); +LIBSSH2_API int libssh2_sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen, LIBSSH2_SFTP_ATTRIBUTES *attrs); +LIBSSH2_API size_t libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer, size_t count); + +LIBSSH2_API int libssh2_sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle); +#define libssh2_sftp_close(handle) libssh2_sftp_close_handle(handle) +#define libssh2_sftp_closedir(handle) libssh2_sftp_close_handle(handle) + +LIBSSH2_API void libssh2_sftp_seek(LIBSSH2_SFTP_HANDLE *handle, size_t offset); +#define libssh2_sftp_rewind(handle) libssh2_sftp_seek((handle), 0) + +LIBSSH2_API size_t libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE *handle); + +LIBSSH2_API int libssh2_sftp_fstat_ex(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_ATTRIBUTES *attrs, int setstat); +#define libssh2_sftp_fstat(handle, attrs) libssh2_sftp_fstat_ex((handle), (attrs), 0) +#define libssh2_sftp_fsetstat(handle, attrs) libssh2_sftp_fstat_ex((handle), (attrs), 1) + + + +/* Miscellaneous Ops */ +LIBSSH2_API int libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, char *source_filename, int srouce_filename_len, + char *dest_filename, int dest_filename_len, + long flags); +#define libssh2_sftp_rename(sftp, sourcefile, destfile) libssh2_sftp_rename_ex((sftp), (sourcefile), strlen(sourcefile), (destfile), strlen(destfile), \ + LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE) + +LIBSSH2_API int libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, char *filename, int filename_len); +#define libssh2_sftp_unlink(sftp, filename) libssh2_sftp_unlink_ex((sftp), (filename), strlen(filename)) + +LIBSSH2_API int libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, char *path, int path_len, long mode); +#define libssh2_sftp_mkdir(sftp, path, mode) libssh2_sftp_mkdir_ex((sftp), (path), strlen(path), (mode)) + +LIBSSH2_API int libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, char *path, int path_len); +#define libssh2_sftp_rmdir(sftp, path) libssh2_sftp_rmdir_ex((sftp), (path), strlen(path)) + +LIBSSH2_API int libssh2_sftp_stat_ex(LIBSSH2_SFTP *sftp, char *path, int path_len, int stat_type, LIBSSH2_SFTP_ATTRIBUTES *attrs); +#define libssh2_sftp_stat(sftp, path, attrs) libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_STAT, (attrs)) +#define libssh2_sftp_lstat(sftp, path, attrs) libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_LSTAT, (attrs)) +#define libssh2_sftp_setstat(sftp, path, attrs) libssh2_sftp_stat_ex((sftp), (path), strlen(path), LIBSSH2_SFTP_SETSTAT, (attrs)) + +LIBSSH2_API int libssh2_sftp_symlink_ex(LIBSSH2_SFTP *sftp, const char *path, int path_len, char *target, int target_len, int link_type); +#define libssh2_sftp_symlink(sftp, orig, linkpath) libssh2_sftp_symlink_ex((sftp), (orig), strlen(orig), (linkpath), strlen(linkpath), LIBSSH2_SFTP_SYMLINK) +#define libssh2_sftp_readlink(sftp, path, target, maxlen) libssh2_sftp_symlink_ex((sftp), (path), strlen(path), (target), (maxlen), LIBSSH2_SFTP_READLINK) +#define libssh2_sftp_realpath(sftp, path, target, maxlen) libssh2_sftp_symlink_ex((sftp), (path), strlen(path), (target), (maxlen), LIBSSH2_SFTP_REALPATH) + +#endif /* LIBSSH2_SFTP_H */ diff --git a/src/Makefile.in b/src/Makefile.in index 66e2c78..22299a1 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,4 +1,4 @@ -OBJECTS = channel.o comp.o crypt.o hostkey.o kex.o mac.o misc.o packet.o scp.o session.o userauth.o +OBJECTS = channel.o comp.o crypt.o hostkey.o kex.o mac.o misc.o packet.o scp.o session.o sftp.o userauth.o top_srcdir = @top_srcdir@ prefix = @prefix@ @@ -42,6 +42,9 @@ scp.o: scp.c session.o: session.c $(CC) -o session.o session.c $(CFLAGS) $(LIBS) +sftp.o: sftp.c + $(CC) -o sftp.o sftp.c $(CFLAGS) $(LIBS) + userauth.o: userauth.c $(CC) -o userauth.o userauth.c $(CFLAGS) $(LIBS) diff --git a/src/misc.c b/src/misc.c index 13e6da6..1d53f48 100644 --- a/src/misc.c +++ b/src/misc.c @@ -45,6 +45,21 @@ unsigned long libssh2_ntohu32(const unsigned char *buf) } /* }}} */ +/* {{{ libssh2_ntohu64 + * Note: Some 32-bit platforms have issues with bitops on long longs + * Work around this by doing expensive (but safer) arithmetic ops with optimization defying parentheses + */ +unsigned long long libssh2_ntohu64(const unsigned char *buf) +{ + unsigned long msl, lsl; + + msl = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + lsl = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; + + return ((msl * 65536) * 65536) + lsl; +} +/* }}} */ + /* {{{ libssh2_htonu32 */ void libssh2_htonu32(unsigned char *buf, unsigned long value) @@ -56,6 +71,24 @@ void libssh2_htonu32(unsigned char *buf, unsigned long value) } /* }}} */ +/* {{{ libssh2_htonu64 + */ +void libssh2_htonu64(unsigned char *buf, unsigned long long value) +{ + unsigned long msl = (value / 65536) / 65536; + + buf[0] = (msl >> 24) & 0xFF; + buf[1] = (msl >> 16) & 0xFF; + buf[2] = (msl >> 8) & 0xFF; + buf[3] = msl & 0xFF; + + buf[4] = (value >> 24) & 0xFF; + buf[5] = (value >> 16) & 0xFF; + buf[6] = (value >> 8) & 0xFF; + buf[7] = value & 0xFF; +} +/* }}} */ + /* Base64 Conversion */ /* {{{ */ diff --git a/src/sftp.c b/src/sftp.c new file mode 100644 index 0000000..14dd953 --- /dev/null +++ b/src/sftp.c @@ -0,0 +1,1280 @@ +/* Copyright (c) 2004, Sara Golemon + * 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 "libssh2_priv.h" +#include "libssh2_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 + +struct _LIBSSH2_SFTP { + LIBSSH2_CHANNEL *channel; + + unsigned long request_id, version; + + LIBSSH2_PACKET_BRIGADE packets; + + LIBSSH2_SFTP_HANDLE *handles; +}; + +#define LIBSSH2_SFTP_HANDLE_FILE 0 +#define LIBSSH2_SFTP_HANDLE_DIR 1 + +struct _LIBSSH2_SFTP_HANDLE { + LIBSSH2_SFTP *sftp; + LIBSSH2_SFTP_HANDLE *prev, *next; + + char *handle; + int handle_len; + + char handle_type; + + union _libssh2_sftp_handle_data { + struct _libssh2_sftp_handle_file_data { + unsigned long long offset; + } file; + struct _libssh2_sftp_handle_dir_data { + unsigned long names_left; + void *names_packet; + char *next_name; + } dir; + } u; +}; + +/* {{{ libssh2_sftp_packet_add + * Add a packet to the SFTP packet brigade + */ +static int libssh2_sftp_packet_add(LIBSSH2_SFTP *sftp, unsigned char *data, unsigned long data_len) +{ + LIBSSH2_SESSION *session = sftp->channel->session; + LIBSSH2_PACKET *packet; + + packet = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PACKET)); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate datablock for SFTP packet", 0); + return -1; + } + memset(packet, 0, sizeof(LIBSSH2_PACKET)); + + packet->data = data; + packet->data_len = data_len; + packet->data_head = 5; + packet->brigade = &sftp->packets; + packet->next = NULL; + packet->prev = sftp->packets.tail; + if (packet->prev) { + packet->prev->next = packet; + } else { + sftp->packets.head = packet; + } + sftp->packets.tail = packet; + + return 0; +} +/* }}} */ + +/* {{{ libssh2_sftp_packet_read + * Frame an SFTP packet off the channel + */ +static int libssh2_sftp_packet_read(LIBSSH2_SFTP *sftp, int should_block) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned char buffer[4]; /* To store the packet length */ + unsigned char *packet; + unsigned long packet_len; + + if (should_block) { + libssh2_channel_set_blocking(channel, 1); + if (4 != libssh2_channel_read(channel, buffer, 4)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for FXP packet", 0); + return -1; + } + } else { + libssh2_channel_set_blocking(channel, 0); + if (1 != libssh2_channel_read(channel, buffer, 1)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for FXP packet", 0); + return 0; + } + libssh2_channel_set_blocking(channel, 1); + if (3 != libssh2_channel_read(channel, buffer + 1, 3)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for FXP packet", 0); + return -1; + } + } + packet_len = libssh2_ntohu32(buffer); + if (packet_len > LIBSSH2_SFTP_PACKET_MAXLEN) { + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_PACKET_EXCEEDED, "SFTP packet too large", 0); + return -1; + } + + packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate SFTP packet", 0); + return -1; + } + + if (packet_len != libssh2_channel_read(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for SFTP packet", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + + if (libssh2_sftp_packet_add(sftp, packet, packet_len)) { + LIBSSH2_FREE(session, packet); + return -1; + } + + return packet[0]; +} +/* }}} */ + +/* {{{ libssh2_sftp_packet_ask + * A la libssh2_packet_ask() + */ +static int libssh2_sftp_packet_ask(LIBSSH2_SFTP *sftp, unsigned char packet_type, unsigned long request_id, unsigned char **data, unsigned long *data_len, int poll_channel) +{ + LIBSSH2_SESSION *session = sftp->channel->session; + LIBSSH2_PACKET *packet = sftp->packets.head; + unsigned char match_buf[5]; + + if (poll_channel) { + if (libssh2_sftp_packet_read(sftp, 0) < 0) { + return -1; + } + } + + match_buf[0] = packet_type; + libssh2_htonu32(match_buf + 1, request_id); + + while (packet) { + if (strncmp(packet->data, match_buf, 5) == 0) { + *data = packet->data; + *data_len = packet->data_len; + + if (packet->prev) { + packet->prev->next = packet->next; + } else { + sftp->packets.head = packet->next; + } + + if (packet->next) { + packet->next->prev = packet->prev; + } else { + sftp->packets.tail = packet->prev; + } + + LIBSSH2_FREE(session, packet); + + return 0; + } + packet = packet->next; + } + return -1; +} +/* }}} */ + +/* {{{ libssh2_sftp_packet_require + * A la libssh2_packet_require + */ +static int libssh2_sftp_packet_require(LIBSSH2_SFTP *sftp, unsigned char packet_type, unsigned long request_id, unsigned char **data, unsigned long *data_len) +{ + LIBSSH2_SESSION *session = sftp->channel->session; + + if (libssh2_sftp_packet_ask(sftp, packet_type, request_id, data, data_len, 0) == 0) { + /* A packet was available in the packet brigade */ + return 0; + } + + while (session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + int ret = libssh2_sftp_packet_read(sftp, 1); + if (ret < 0) { + return -1; + } + if (ret == 0) continue; + + if (packet_type == ret) { + /* Be lazy, let packet_ask pull it out of the brigade */ + return libssh2_sftp_packet_ask(sftp, packet_type, request_id, data, data_len, 0); + } + } + + /* Only reached if the socket died */ + return -1; +} +/* }}} */ + +/* {{{ libssh2_sftp_packet_requirev + * Requie one of N possible reponses + */ +static int libssh2_sftp_packet_requirev(LIBSSH2_SFTP *sftp, int num_valid_responses, unsigned char *valid_responses, unsigned long request_id, unsigned char **data, unsigned long *data_len) +{ + int i; + + while (sftp->channel->session->socket_state == LIBSSH2_SOCKET_CONNECTED) { + for(i = 0; i < num_valid_responses; i++) { + if (libssh2_sftp_packet_ask(sftp, valid_responses[i], request_id, data, data_len, !i) == 0) { + return 0; + } + } + } + + return -1; +} +/* }}} */ + +/* {{{ libssh2_sftp_attrsize + * Size that attr will occupy when turned into a bin struct + */ +static int libssh2_sftp_attrsize(LIBSSH2_SFTP_ATTRIBUTES *attrs) +{ + int attrsize = 4; /* flags(4) */ + + if (!attrs) { + return attrsize; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) attrsize += 8; + if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) attrsize += 8; + if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) attrsize += 4; + if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) attrsize += 8; /* atime + mtime as u32 */ + + return attrsize; +} +/* }}} */ + +/* {{{ libssh2_sftp_attr2bin + * Populate attributes into an SFTP block + */ +static int libssh2_sftp_attr2bin(unsigned char *p, LIBSSH2_SFTP_ATTRIBUTES *attrs) +{ + unsigned char *s = p; + + if (!attrs) { + libssh2_htonu32(s, 0); + return 4; + } + + libssh2_htonu32(s, attrs->flags & 0x0000000); s += 4; + + if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { + libssh2_htonu64(s, attrs->filesize); s += 8; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { + libssh2_htonu32(s, attrs->uid); s += 4; + libssh2_htonu32(s, attrs->gid); s += 4; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { + libssh2_htonu32(s, attrs->permissions); s += 4; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { + libssh2_htonu32(s, attrs->atime); s += 4; + libssh2_htonu32(s, attrs->mtime); s += 4; + } + + return (s - p); +} +/* }}} */ + +/* {{{ libssh2_sftp_bin2attr + */ +static int libssh2_sftp_bin2attr(LIBSSH2_SFTP_ATTRIBUTES *attrs, unsigned char *p) +{ + unsigned char *s = p; + + memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + attrs->flags = libssh2_ntohu32(s); s += 4; + + if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { + attrs->filesize = libssh2_ntohu64(s); s += 8; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { + attrs->uid = libssh2_ntohu32(s); s += 4; + attrs->gid = libssh2_ntohu32(s); s += 4; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { + attrs->permissions = libssh2_ntohu32(s); s += 4; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { + attrs->atime = libssh2_ntohu32(s); s += 4; + attrs->mtime = libssh2_ntohu32(s); s += 4; + } + + return (s - p); +} +/* }}} */ + +/* ************ + * SFTP API * + ************ */ + +/* {{{ libssh2_sftp_dtor + * Shutdown an SFTP stream when the channel closes + */ +LIBSSH2_CHANNEL_CLOSE_FUNC(libssh2_sftp_dtor) { + LIBSSH2_SFTP *sftp = (LIBSSH2_SFTP*)(*channel_abstract); + + /* Loop through handles closing them */ + while (sftp->handles) { + libssh2_sftp_close_handle(sftp->handles); + } + + LIBSSH2_FREE(session, sftp); +} +/* }}} */ + +/* {{{ libssh2_sftp_init + * Startup an SFTP session + */ +LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) +{ + LIBSSH2_SFTP *sftp; + LIBSSH2_CHANNEL *channel; + unsigned char *data, *s, buffer[13]; /* sftp_header(9) + version_id(4) */ + unsigned long data_len, request_id; + + channel = libssh2_channel_open_session(session); + if (!channel) { + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to startup channel", 0); + return NULL; + } + if (libssh2_channel_subsystem(channel, "sftp")) { + libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to request SFTP subsystem", 0); + libssh2_channel_free(channel); + return NULL; + } + + libssh2_channel_set_blocking(channel, 1); + + sftp = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP)); + if (!sftp) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a new SFTP structure", 0); + libssh2_channel_free(channel); + return NULL; + } + memset(sftp, 0, sizeof(LIBSSH2_SFTP)); + sftp->channel = channel; + + request_id = sftp->request_id++; + libssh2_htonu32(buffer, 4 + 5); + buffer[4] = SSH_FXP_INIT; + libssh2_htonu32(buffer + 5, request_id); + libssh2_htonu32(buffer + 9, 6); + + if (13 != libssh2_channel_write(channel, buffer, 13)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send SSH_FXP_INIT", 0); + libssh2_channel_free(channel); + LIBSSH2_FREE(session, sftp); + return NULL; + } + + if (libssh2_sftp_packet_require(sftp, SSH_FXP_VERSION, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from SFTP subsystem", 0); + libssh2_channel_free(channel); + LIBSSH2_FREE(session, sftp); + return NULL; + } + if (data_len < 5) { + libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Invalid SSH_FXP_VERSION response", 0); + libssh2_channel_free(channel); + LIBSSH2_FREE(session, sftp); + return NULL; + } + + s = data + 1; + sftp->version = libssh2_ntohu32(s); s += 4; + if (sftp->version > LIBSSH2_SFTP_VERSION) { + sftp->version = LIBSSH2_SFTP_VERSION; + } + while (s < (data + data_len)) { + char *extension_name, *extension_data; + unsigned long extname_len, extdata_len; + + extname_len = libssh2_ntohu32(s); s += 4; + extension_name = s; s += extname_len; + + extdata_len = libssh2_ntohu32(s); s += 4; + extension_data = s; s += extdata_len; + + /* TODO: Actually process extensions */ + } + LIBSSH2_FREE(session, data); + + /* Make sure that when the channel gets closed, the SFTP service is shut down too */ + sftp->channel->abstract = sftp; + sftp->channel->close_cb = libssh2_sftp_dtor; + + return sftp; +} +/* }}} */ + +/* {{{ libssh2_sftp_shutdown + * Shutsdown the SFTP subsystem + */ +LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp) { + return libssh2_channel_free(sftp->channel); +} +/* }}} */ + +/* ******************************* + * SFTP File and Directory Ops * + ******************************* */ + +/* {{{ libssh2_sftp_open_ex + * + */ +LIBSSH2_API LIBSSH2_SFTP_HANDLE *libssh2_sftp_open_ex(LIBSSH2_SFTP *sftp, char *filename, int filename_len, unsigned long flags, long mode, int open_type) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_SFTP_HANDLE *fp; + LIBSSH2_SFTP_ATTRIBUTES attrs = { LIBSSH2_SFTP_ATTR_PERMISSIONS }; + unsigned long data_len, packet_len = filename_len + 13 + ((open_type == LIBSSH2_SFTP_OPENFILE) ? (4 + libssh2_sftp_attrsize(&attrs)) : 0); + /* packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) + flags(4) */ + unsigned char *packet, *data, *s; + unsigned char fopen_responses[2] = { SSH_FXP_HANDLE, SSH_FXP_STATUS }; + unsigned long request_id; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_REMOVE packet", 0); + return NULL; + } + attrs.permissions = mode; + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = (open_type == LIBSSH2_SFTP_OPENFILE) ? SSH_FXP_OPEN : SSH_FXP_OPENDIR; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, filename_len); s += 4; + memcpy(s, filename, filename_len); s += filename_len; + if (open_type == LIBSSH2_SFTP_OPENFILE) { + libssh2_htonu32(s, flags); s += 4; + s += libssh2_sftp_attr2bin(s, &attrs); + } + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_REMOVE command", 0); + LIBSSH2_FREE(session, packet); + return NULL; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_requirev(sftp, 2, fopen_responses, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return NULL; + } + + if (data[0] == SSH_FXP_STATUS) { + libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Failed opening remote file", 0); + LIBSSH2_FREE(session, data); + return NULL; + } + + fp = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP_HANDLE)); + if (!fp) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate new SFTP handle structure", 0); + LIBSSH2_FREE(session, data); + return NULL; + } + memset(fp, 0, sizeof(LIBSSH2_SFTP_HANDLE)); + fp->handle_type = (open_type == LIBSSH2_SFTP_OPENFILE) ? LIBSSH2_SFTP_HANDLE_FILE : LIBSSH2_SFTP_HANDLE_DIR; + + fp->handle_len = libssh2_ntohu32(data + 5); + if (fp->handle_len > 256) { + /* SFTP doesn't allow handles longer than 256 characters */ + fp->handle_len = 256; + } + fp->handle = LIBSSH2_ALLOC(session, fp->handle_len); + if (!fp->handle) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate space for SFTP file/dir handle", 0); + LIBSSH2_FREE(session, data); + LIBSSH2_FREE(session, fp); + return NULL; + } + memcpy(fp->handle, data + 9, fp->handle_len); + LIBSSH2_FREE(session, data); + + /* Link the file and the sftp session together */ + fp->next = sftp->handles; + if (fp->next) { + fp->next->prev = fp; + } + fp->sftp = sftp; + + fp->u.file.offset = 0; + + return fp; +} +/* }}} */ + +/* {{{ libssh2_sftp_read + * Read from an SFTP file handle + */ +LIBSSH2_API size_t libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, request_id; + unsigned long packet_len = handle->handle_len + 25; /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) + offset(8) + length(4) */ + unsigned char *packet, *s, *data; + unsigned char read_responses[2] = { SSH_FXP_DATA, SSH_FXP_STATUS }; + size_t bytes_read = 0; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_CLOSE packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = SSH_FXP_READ; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, handle->handle_len); s += 4; + memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; + libssh2_htonu64(s, handle->u.file.offset); s += 8; + libssh2_htonu32(s, buffer_maxlen); s += 4; + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_READ command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_requirev(sftp, 2, read_responses, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + switch (data[0]) { + case SSH_FXP_STATUS: + LIBSSH2_FREE(session, data); + return -1; + case SSH_FXP_DATA: + bytes_read = libssh2_ntohu32(data + 5); + if (bytes_read > (data_len - 9)) { + return -1; + } + memcpy(buffer, data + 9, bytes_read); + handle->u.file.offset += bytes_read; + LIBSSH2_FREE(session, data); + return bytes_read; + } + + return -1; +} +/* }}} */ + +/* {{{ libssh2_sftp_readdir + * Read from an SFTP directory handle + */ +LIBSSH2_API int libssh2_sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen, LIBSSH2_SFTP_ATTRIBUTES *attrs) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_SFTP_ATTRIBUTES attrs_dummy; + unsigned long data_len, request_id, filename_len, num_names; + unsigned long packet_len = handle->handle_len + 13; /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ + unsigned char *packet, *s, *data; + unsigned char read_responses[2] = { SSH_FXP_NAME, SSH_FXP_STATUS }; + + if (handle->u.dir.names_left) { + /* A prior request returned more than one directory entry, feed it back from the buffer */ + unsigned char *s = handle->u.dir.next_name; + unsigned long real_filename_len = libssh2_ntohu32(s); + + filename_len = real_filename_len; s += 4; + if (filename_len > buffer_maxlen) { + filename_len = buffer_maxlen; + } + memcpy(buffer, s, filename_len); s += real_filename_len; + + /* Skip longname */ + s += 4 + libssh2_ntohu32(s); + + if (attrs) { + memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + } + s += libssh2_sftp_bin2attr(attrs ? attrs : &attrs_dummy, s); + + handle->u.dir.next_name = s; + if ((--handle->u.dir.names_left) == 0) { + LIBSSH2_FREE(session, handle->u.dir.names_packet); + } + + return filename_len; + } + + /* Request another entry(entries?) */ + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_READDIR packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = SSH_FXP_READDIR; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, handle->handle_len); s += 4; + memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_READ command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_requirev(sftp, 2, read_responses, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + if (data[0] == SSH_FXP_STATUS) { + int retcode = (LIBSSH2_FX_EOF == libssh2_ntohu32(data + 5)) ? 0 : -1; + + LIBSSH2_FREE(session, data); + return retcode; + } + + num_names = libssh2_ntohu32(data + 5); + if (num_names <= 0) { + LIBSSH2_FREE(session, data); + return (num_names == 0) ? 0 : -1; + } + + if (num_names == 1) { + unsigned long real_filename_len = libssh2_ntohu32(data + 9); + + filename_len = real_filename_len; + if (filename_len > buffer_maxlen) { + filename_len = buffer_maxlen; + } + memcpy(buffer, data + 13, filename_len); + + if (attrs) { + memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + libssh2_sftp_bin2attr(attrs, data + 13 + real_filename_len + (4 + libssh2_ntohu32(data + 13 + real_filename_len))); + } + LIBSSH2_FREE(session, data); + + return filename_len; + } + + handle->u.dir.names_left = num_names; + handle->u.dir.names_packet = data; + handle->u.dir.next_name = data + 9; + + /* Be lazy, just use the name popping mechanism from the start of the function */ + return libssh2_sftp_readdir(handle, buffer, buffer_maxlen, attrs); +} +/* }}} */ + +/* {{{ libssh2_sftp_write + * Write data to a file handle + */ +LIBSSH2_API size_t libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer, size_t count) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, request_id, retcode; + unsigned long packet_len = handle->handle_len + count + 25; /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) + offset(8) + count(4) */ + unsigned char *packet, *s, *data; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_WRITE packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = SSH_FXP_WRITE; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, handle->handle_len); s += 4; + memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; + libssh2_htonu64(s, handle->u.file.offset); s += 8; + libssh2_htonu32(s, count); s += 4; + memcpy(s, buffer, count); s += count; + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_WRITE command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + retcode = libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + if (retcode == LIBSSH2_FX_OK) { + handle->u.file.offset += count; + return count; + } + + return -1; +} +/* }}} */ + +/* {{{ libssh2_sftp_fstat_ex + * Get or Set stat on a file + */ +LIBSSH2_API int libssh2_sftp_fstat_ex(LIBSSH2_SFTP_HANDLE *handle, LIBSSH2_SFTP_ATTRIBUTES *attrs, int setstat) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, request_id; + unsigned long packet_len = handle->handle_len + 13 + (setstat ? libssh2_sftp_attrsize(attrs) : 0); + /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ + unsigned char *packet, *s, *data; + unsigned char fstat_responses[2] = { SSH_FXP_ATTRS, SSH_FXP_STATUS }; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FSTAT/FSETSTAT packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = setstat ? SSH_FXP_FSETSTAT : SSH_FXP_FSTAT; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, handle->handle_len); s += 4; + memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; + if (setstat) { + s += libssh2_sftp_attr2bin(s, attrs); + } + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, setstat ? "Unable to send FXP_FSETSTAT" : "Unable to send FXP_FSTAT command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_requirev(sftp, 2, fstat_responses, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + if (data[0] == SSH_FXP_STATUS) { + int retcode; + + retcode = libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + return (retcode == LIBSSH2_FX_OK) ? 0 : -1; + } + + libssh2_sftp_bin2attr(attrs, data + 5); + + return 0; +} +/* }}} */ + +/* {{{ libssh2_sftp_seek + * Set the read/write pointer to an arbitrary position within the file + */ +LIBSSH2_API void libssh2_sftp_seek(LIBSSH2_SFTP_HANDLE *handle, size_t offset) +{ + handle->u.file.offset = offset; +} +/* }}} */ + +/* {{{ libssh2_sftp_tell + * Return the current read/write pointer's offset + */ +LIBSSH2_API size_t libssh2_sftp_tell(LIBSSH2_SFTP_HANDLE *handle) +{ + return handle->u.file.offset; +} +/* }}} */ + + +/* {{{ 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 *handle) +{ + LIBSSH2_SFTP *sftp = handle->sftp; + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, retcode, request_id; + unsigned long packet_len = handle->handle_len + 13; /* packet_len(4) + packet_type(1) + request_id(4) + handle_len(4) */ + unsigned char *packet, *s, *data; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_CLOSE packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = SSH_FXP_CLOSE; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, handle->handle_len); s += 4; + memcpy(s, handle->handle, handle->handle_len); s += handle->handle_len; + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_CLOSE command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + retcode = libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + if (retcode != LIBSSH2_FX_OK) { + return -1; + } + + if (handle == sftp->handles) { + sftp->handles = handle->next; + } + if (handle->next) { + handle->next->prev = NULL; + } + + if (handle->u.dir.names_left) { + LIBSSH2_FREE(session, handle->u.dir.names_packet); + } + + LIBSSH2_FREE(session, handle); + + return 0; +} +/* }}} */ + +/* ********************** + * SFTP Miscellaneous * + ********************** */ + +/* {{{ libssh2_sftp_unlink_ex + * Delete a file from the remote server + */ +LIBSSH2_API int libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, char *filename, int filename_len) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, retcode, request_id; + unsigned long packet_len = filename_len + 13; /* packet_len(4) + packet_type(1) + request_id(4) + filename_len(4) */ + unsigned char *packet, *s, *data; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_REMOVE packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = SSH_FXP_REMOVE; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, filename_len); s += 4; + memcpy(s, filename, filename_len); s += filename_len; + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_REMOVE command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + retcode = libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + return (retcode == LIBSSH2_FX_OK) ? 0 : -1; +} +/* }}} */ + +/* {{{ libssh2_sftp_rename_ex + * Rename a file on the remote server + */ +LIBSSH2_API int libssh2_sftp_rename_ex(LIBSSH2_SFTP *sftp, char *source_filename, int source_filename_len, + char *dest_filename, int dest_filename_len, + long flags) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, retcode = -1, request_id; + unsigned long packet_len = source_filename_len + dest_filename_len + 21; /* packet_len(4) + packet_type(1) + request_id(4) + + source_filename_len(4) + dest_filename_len(4) + flags(4) */ + unsigned char *packet, *s, *data; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_RENAME packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = SSH_FXP_RENAME; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, source_filename_len); s += 4; + memcpy(s, source_filename, source_filename_len); s += source_filename_len; + libssh2_htonu32(s, dest_filename_len); s += 4; + memcpy(s, dest_filename, dest_filename_len); s += dest_filename_len; + libssh2_htonu32(s, flags); s += 4; + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_REMOVE command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + switch (libssh2_ntohu32(data + 5)) { + case LIBSSH2_FX_OK: + retcode = 0; + LIBSSH2_FREE(session, data); + break; + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "File already exists and SSH_FXP_RENAME_OVERWRITE not specified", 0); + retcode = -1; + break; + case LIBSSH2_FX_OP_UNSUPPORTED: + libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Operation Not Supported", 0); + retcode = -1; + break; + } + + return retcode; +} +/* }}} */ + +/* {{{ libssh2_sftp_mkdir_ex + * Create a directory + */ +LIBSSH2_API int libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, char *path, int path_len, long mode) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_SFTP_ATTRIBUTES attrs = { LIBSSH2_SFTP_ATTR_PERMISSIONS }; + unsigned long data_len, retcode, request_id; + unsigned long packet_len = path_len + 13 + libssh2_sftp_attrsize(&attrs); + /* packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ + unsigned char *packet, *s, *data; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_MKDIR packet", 0); + return -1; + } + attrs.permissions = mode; + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = SSH_FXP_MKDIR; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, path_len); s += 4; + memcpy(s, path, path_len); s += path_len; + s += libssh2_sftp_attr2bin(s, &attrs); + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_REMOVE command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + retcode = libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + return (retcode == LIBSSH2_FX_OK) ? 0 : -1; +} +/* }}} */ + +/* {{{ libssh2_sftp_rmdir_ex + * Remove a directory + */ +LIBSSH2_API int libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, char *path, int path_len) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, retcode, request_id; + unsigned long packet_len = path_len + 13; /* packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ + unsigned char *packet, *s, *data; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_MKDIR packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + *(s++) = SSH_FXP_RMDIR; + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, path_len); s += 4; + memcpy(s, path, path_len); s += path_len; + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send FXP_REMOVE command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_require(sftp, SSH_FXP_STATUS, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + retcode = libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + + return (retcode == LIBSSH2_FX_OK) ? 0 : -1; +} +/* }}} */ + +/* {{{ libssh2_sftp_stat_ex + * Stat a file or symbolic link + */ +LIBSSH2_API int libssh2_sftp_stat_ex(LIBSSH2_SFTP *sftp, char *path, int path_len, int stat_type, LIBSSH2_SFTP_ATTRIBUTES *attrs) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, request_id; + unsigned long packet_len = path_len + 13 + ((stat_type == LIBSSH2_SFTP_SETSTAT) ? libssh2_sftp_attrsize(attrs) : 0); + /* packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ + unsigned char *packet, *s, *data; + unsigned char stat_responses[2] = { SSH_FXP_ATTRS, SSH_FXP_STATUS }; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for FXP_MKDIR packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + switch (stat_type) { + case LIBSSH2_SFTP_SETSTAT: + *(s++) = SSH_FXP_SETSTAT; + break; + case LIBSSH2_SFTP_LSTAT: + *(s++) = SSH_FXP_LSTAT; + break; + case LIBSSH2_SFTP_STAT: + default: + *(s++) = SSH_FXP_STAT; + } + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, path_len); s += 4; + memcpy(s, path, path_len); s += path_len; + if (stat_type == LIBSSH2_SFTP_SETSTAT) { + s += libssh2_sftp_attr2bin(s, attrs); + } + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send STAT/LSTAT/SETSTAT command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_requirev(sftp, 2, stat_responses, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + if (data[0] == SSH_FXP_STATUS) { + int retcode; + + retcode = libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + return (retcode == LIBSSH2_FX_OK) ? 0 : 1; + } + + memset(attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + libssh2_sftp_bin2attr(attrs, data + 5); + LIBSSH2_FREE(session, data); + + return 0; +} +/* }}} */ + +/* {{{ libssh2_sftp_symlink_ex + * Read or set a symlink + */ +LIBSSH2_API int libssh2_sftp_symlink_ex(LIBSSH2_SFTP *sftp, const char *path, int path_len, char *target, int target_len, int link_type) +{ + LIBSSH2_CHANNEL *channel = sftp->channel; + LIBSSH2_SESSION *session = channel->session; + unsigned long data_len, request_id, link_len; + unsigned long packet_len = path_len + 13 + ((link_type == LIBSSH2_SFTP_SYMLINK) ? (4 + target_len) : 0); + /* packet_len(4) + packet_type(1) + request_id(4) + path_len(4) */ + unsigned char *packet, *s, *data; + unsigned char link_responses[2] = { SSH_FXP_NAME, SSH_FXP_STATUS }; + + s = packet = LIBSSH2_ALLOC(session, packet_len); + if (!packet) { + libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for SYMLINK/READLINK/REALPATH packet", 0); + return -1; + } + + libssh2_htonu32(s, packet_len - 4); s += 4; + switch (link_type) { + case LIBSSH2_SFTP_REALPATH: + *(s++) = SSH_FXP_REALPATH; + break; + case LIBSSH2_SFTP_SYMLINK: + *(s++) = SSH_FXP_SYMLINK; + break; + case LIBSSH2_SFTP_READLINK: + default: + *(s++) = SSH_FXP_READLINK; + } + request_id = sftp->request_id++; + libssh2_htonu32(s, request_id); s += 4; + libssh2_htonu32(s, path_len); s += 4; + memcpy(s, path, path_len); s += path_len; + if (link_type == LIBSSH2_SFTP_SYMLINK) { + libssh2_htonu32(s, target_len); s += 4; + memcpy(s, target, target_len); s += target_len; + } + + if (packet_len != libssh2_channel_write(channel, packet, packet_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send SYMLINK/READLINK command", 0); + LIBSSH2_FREE(session, packet); + return -1; + } + LIBSSH2_FREE(session, packet); + + if (libssh2_sftp_packet_requirev(sftp, 2, link_responses, request_id, &data, &data_len)) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for status message", 0); + return -1; + } + + if (data[0] == SSH_FXP_STATUS) { + int retcode; + + retcode = libssh2_ntohu32(data + 5); + LIBSSH2_FREE(session, data); + return (retcode == LIBSSH2_FX_OK) ? 0 : 1; + } + + if (libssh2_ntohu32(data + 5) < 1) { + libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Invalid READLINK/REALPATH response, no name entries", 0); + LIBSSH2_FREE(session, data); + return -1; + } + + link_len = libssh2_ntohu32(data + 9); + if (link_len > target_len) { + link_len = target_len; + } + memcpy(target, data + 13, link_len); + + return link_len; +} +/* }}} */