aboutsummaryrefslogtreecommitdiff
path: root/src/sftp.c
diff options
context:
space:
mode:
authorAndreas Schneider <asn@cynapses.org>2010-09-06 14:28:38 +0200
committerAndreas Schneider <asn@cynapses.org>2010-09-06 14:28:38 +0200
commitf7842e3a4b9acea2126ff725f993c299aef0e6db (patch)
tree18239f819a5edbcfc7f2961c48f3f9297314ef22 /src/sftp.c
parent38421403d2dc45636e597f2a909daa6ae31976de (diff)
downloadlibssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.gz
libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.xz
libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.zip
misc: Rename libssh/ to src/
Diffstat (limited to 'src/sftp.c')
-rw-r--r--src/sftp.c3207
1 files changed, 3207 insertions, 0 deletions
diff --git a/src/sftp.c b/src/sftp.c
new file mode 100644
index 00000000..e5c91af3
--- /dev/null
+++ b/src/sftp.c
@@ -0,0 +1,3207 @@
+/*
+ * sftp.c - Secure FTP functions
+ *
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2005-2008 by Aris Adamantiadis
+ * Copyright (c) 2008-2009 by Andreas Schneider <mail@cynapses.org>
+ *
+ * The SSH Library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * The SSH Library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the SSH Library; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/* This file contains code written by Nick Zitzmann */
+
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#else
+#define S_IFSOCK 0140000
+#define S_IFLNK 0120000
+
+#ifdef _MSC_VER
+#define S_IFBLK 0060000
+#define S_IFIFO 0010000
+#endif
+#endif
+
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/sftp.h"
+#include "libssh/buffer.h"
+#include "libssh/channels.h"
+#include "libssh/session.h"
+#include "libssh/misc.h"
+
+#ifdef WITH_SFTP
+
+#define sftp_enter_function() _enter_function(sftp->channel->session)
+#define sftp_leave_function() _leave_function(sftp->channel->session)
+
+struct sftp_ext_struct {
+ unsigned int count;
+ char **name;
+ char **data;
+};
+
+/* functions */
+static int sftp_enqueue(sftp_session session, sftp_message msg);
+static void sftp_message_free(sftp_message msg);
+static void sftp_set_error(sftp_session sftp, int errnum);
+static void status_msg_free(sftp_status_message status);
+
+static sftp_ext sftp_ext_new(void) {
+ sftp_ext ext;
+
+ ext = malloc(sizeof(struct sftp_ext_struct));
+ if (ext == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(ext);
+
+ return ext;
+}
+
+static void sftp_ext_free(sftp_ext ext) {
+ unsigned int i;
+
+ if (ext == NULL) {
+ return;
+ }
+
+ if (ext->count) {
+ for (i = 0; i < ext->count; i++) {
+ SAFE_FREE(ext->name[i]);
+ SAFE_FREE(ext->data[i]);
+ }
+ SAFE_FREE(ext->name);
+ SAFE_FREE(ext->data);
+ }
+
+ SAFE_FREE(ext);
+}
+
+sftp_session sftp_new(ssh_session session){
+ sftp_session sftp;
+
+ if (session == NULL) {
+ return NULL;
+ }
+ enter_function();
+
+ sftp = malloc(sizeof(struct sftp_session_struct));
+ if (sftp == NULL) {
+ ssh_set_error_oom(session);
+ leave_function();
+ return NULL;
+ }
+ ZERO_STRUCTP(sftp);
+
+ sftp->ext = sftp_ext_new();
+ if (sftp->ext == NULL) {
+ ssh_set_error_oom(session);
+ SAFE_FREE(sftp);
+ leave_function();
+ return NULL;
+ }
+
+ sftp->session = session;
+ sftp->channel = ssh_channel_new(session);
+ if (sftp->channel == NULL) {
+ SAFE_FREE(sftp);
+ leave_function();
+ return NULL;
+ }
+
+ if (ssh_channel_open_session(sftp->channel)) {
+ ssh_channel_free(sftp->channel);
+ SAFE_FREE(sftp);
+ leave_function();
+ return NULL;
+ }
+
+ if (ssh_channel_request_sftp(sftp->channel)) {
+ sftp_free(sftp);
+ leave_function();
+ return NULL;
+ }
+
+ leave_function();
+ return sftp;
+}
+
+#ifdef WITH_SERVER
+sftp_session sftp_server_new(ssh_session session, ssh_channel chan){
+ sftp_session sftp = NULL;
+
+ sftp = malloc(sizeof(struct sftp_session_struct));
+ if (sftp == NULL) {
+ ssh_set_error_oom(session);
+ return NULL;
+ }
+ ZERO_STRUCTP(sftp);
+
+ sftp->session = session;
+ sftp->channel = chan;
+
+ return sftp;
+}
+
+int sftp_server_init(sftp_session sftp){
+ ssh_session session = sftp->session;
+ sftp_packet packet = NULL;
+ ssh_buffer reply = NULL;
+ uint32_t version;
+
+ sftp_enter_function();
+
+ packet = sftp_packet_read(sftp);
+ if (packet == NULL) {
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (packet->type != SSH_FXP_INIT) {
+ ssh_set_error(session, SSH_FATAL,
+ "Packet read of type %d instead of SSH_FXP_INIT",
+ packet->type);
+
+ sftp_packet_free(packet);
+ sftp_leave_function();
+ return -1;
+ }
+
+ ssh_log(session, SSH_LOG_PACKET, "Received SSH_FXP_INIT");
+
+ buffer_get_u32(packet->payload, &version);
+ version = ntohl(version);
+ ssh_log(session, SSH_LOG_PACKET, "Client version: %d", version);
+ sftp->client_version = version;
+
+ sftp_packet_free(packet);
+
+ reply = ssh_buffer_new();
+ if (reply == NULL) {
+ ssh_set_error_oom(session);
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (buffer_add_u32(reply, ntohl(LIBSFTP_VERSION)) < 0) {
+ ssh_set_error_oom(session);
+ ssh_buffer_free(reply);
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (sftp_packet_write(sftp, SSH_FXP_VERSION, reply) < 0) {
+ ssh_buffer_free(reply);
+ sftp_leave_function();
+ return -1;
+ }
+ ssh_buffer_free(reply);
+
+ ssh_log(session, SSH_LOG_RARE, "Server version sent");
+
+ if (version > LIBSFTP_VERSION) {
+ sftp->version = LIBSFTP_VERSION;
+ } else {
+ sftp->version=version;
+ }
+
+ sftp_leave_function();
+ return 0;
+}
+#endif /* WITH_SERVER */
+
+void sftp_free(sftp_session sftp){
+ sftp_request_queue ptr;
+
+ if (sftp == NULL) {
+ return;
+ }
+
+ ssh_channel_send_eof(sftp->channel);
+ ptr = sftp->queue;
+ while(ptr) {
+ sftp_request_queue old;
+ sftp_message_free(ptr->message);
+ old = ptr->next;
+ SAFE_FREE(ptr);
+ ptr = old;
+ }
+
+ ssh_channel_free(sftp->channel);
+ sftp_ext_free(sftp->ext);
+ ZERO_STRUCTP(sftp);
+
+ SAFE_FREE(sftp);
+}
+
+int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload){
+ int size;
+
+ if (buffer_prepend_data(payload, &type, sizeof(uint8_t)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ size = htonl(ssh_buffer_get_len(payload));
+ if (buffer_prepend_data(payload, &size, sizeof(uint32_t)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ size = ssh_channel_write(sftp->channel, ssh_buffer_get_begin(payload),
+ ssh_buffer_get_len(payload));
+ if (size < 0) {
+ return -1;
+ } else if((uint32_t) size != ssh_buffer_get_len(payload)) {
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Had to write %d bytes, wrote only %d",
+ ssh_buffer_get_len(payload),
+ size);
+ }
+
+ return size;
+}
+
+sftp_packet sftp_packet_read(sftp_session sftp) {
+ sftp_packet packet = NULL;
+ uint32_t size;
+
+ sftp_enter_function();
+
+ packet = malloc(sizeof(struct sftp_packet_struct));
+ if (packet == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ packet->sftp = sftp;
+ packet->payload = ssh_buffer_new();
+ if (packet->payload == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(packet);
+ return NULL;
+ }
+
+ if (channel_read_buffer(sftp->channel, packet->payload, 4, 0) <= 0) {
+ ssh_buffer_free(packet->payload);
+ SAFE_FREE(packet);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ if (buffer_get_u32(packet->payload, &size) != sizeof(uint32_t)) {
+ ssh_set_error(sftp->session, SSH_FATAL, "Short sftp packet!");
+ ssh_buffer_free(packet->payload);
+ SAFE_FREE(packet);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ size = ntohl(size);
+ if (channel_read_buffer(sftp->channel, packet->payload, 1, 0) <= 0) {
+ /* TODO: check if there are cases where an error needs to be set here */
+ ssh_buffer_free(packet->payload);
+ SAFE_FREE(packet);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ buffer_get_u8(packet->payload, &packet->type);
+ if (size > 1) {
+ if (channel_read_buffer(sftp->channel, packet->payload, size - 1, 0) <= 0) {
+ /* TODO: check if there are cases where an error needs to be set here */
+ ssh_buffer_free(packet->payload);
+ SAFE_FREE(packet);
+ sftp_leave_function();
+ return NULL;
+ }
+ }
+
+ sftp_leave_function();
+ return packet;
+}
+
+static void sftp_set_error(sftp_session sftp, int errnum) {
+ if (sftp != NULL) {
+ sftp->errnum = errnum;
+ }
+}
+
+/* Get the last sftp error */
+int sftp_get_error(sftp_session sftp) {
+ if (sftp == NULL) {
+ return -1;
+ }
+
+ return sftp->errnum;
+}
+
+static sftp_message sftp_message_new(sftp_session sftp){
+ sftp_message msg = NULL;
+
+ sftp_enter_function();
+
+ msg = malloc(sizeof(struct sftp_message_struct));
+ if (msg == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(msg);
+
+ msg->payload = ssh_buffer_new();
+ if (msg->payload == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(msg);
+ return NULL;
+ }
+ msg->sftp = sftp;
+
+ sftp_leave_function();
+ return msg;
+}
+
+static void sftp_message_free(sftp_message msg) {
+ sftp_session sftp;
+
+ if (msg == NULL) {
+ return;
+ }
+
+ sftp = msg->sftp;
+ sftp_enter_function();
+
+ ssh_buffer_free(msg->payload);
+ SAFE_FREE(msg);
+
+ sftp_leave_function();
+}
+
+static sftp_message sftp_get_message(sftp_packet packet) {
+ sftp_session sftp = packet->sftp;
+ sftp_message msg = NULL;
+
+ sftp_enter_function();
+
+ msg = sftp_message_new(sftp);
+ if (msg == NULL) {
+ sftp_leave_function();
+ return NULL;
+ }
+
+ msg->sftp = packet->sftp;
+ msg->packet_type = packet->type;
+
+ if ((packet->type != SSH_FXP_STATUS) && (packet->type!=SSH_FXP_HANDLE) &&
+ (packet->type != SSH_FXP_DATA) && (packet->type != SSH_FXP_ATTRS) &&
+ (packet->type != SSH_FXP_NAME) && (packet->type != SSH_FXP_EXTENDED_REPLY)) {
+ ssh_set_error(packet->sftp->session, SSH_FATAL,
+ "Unknown packet type %d", packet->type);
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ if (buffer_get_u32(packet->payload, &msg->id) != sizeof(uint32_t)) {
+ ssh_set_error(packet->sftp->session, SSH_FATAL,
+ "Invalid packet %d: no ID", packet->type);
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ ssh_log(packet->sftp->session, SSH_LOG_PACKET,
+ "Packet with id %d type %d",
+ msg->id,
+ msg->packet_type);
+
+ if (buffer_add_data(msg->payload, buffer_get_rest(packet->payload),
+ buffer_get_rest_len(packet->payload)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return NULL;
+ }
+
+ sftp_leave_function();
+ return msg;
+}
+
+static int sftp_read_and_dispatch(sftp_session sftp) {
+ sftp_packet packet = NULL;
+ sftp_message msg = NULL;
+
+ sftp_enter_function();
+
+ packet = sftp_packet_read(sftp);
+ if (packet == NULL) {
+ sftp_leave_function();
+ return -1; /* something nasty happened reading the packet */
+ }
+
+ msg = sftp_get_message(packet);
+ sftp_packet_free(packet);
+ if (msg == NULL) {
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (sftp_enqueue(sftp, msg) < 0) {
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return -1;
+ }
+
+ sftp_leave_function();
+ return 0;
+}
+
+void sftp_packet_free(sftp_packet packet) {
+ if (packet == NULL) {
+ return;
+ }
+
+ ssh_buffer_free(packet->payload);
+ free(packet);
+}
+
+/* Initialize the sftp session with the server. */
+int sftp_init(sftp_session sftp) {
+ sftp_packet packet = NULL;
+ ssh_buffer buffer = NULL;
+ ssh_string ext_name_s = NULL;
+ ssh_string ext_data_s = NULL;
+ char *ext_name = NULL;
+ char *ext_data = NULL;
+ uint32_t version = htonl(LIBSFTP_VERSION);
+
+ sftp_enter_function();
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (buffer_add_u32(buffer, version) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ sftp_leave_function();
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ sftp_leave_function();
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+
+ packet = sftp_packet_read(sftp);
+ if (packet == NULL) {
+ sftp_leave_function();
+ return -1;
+ }
+
+ if (packet->type != SSH_FXP_VERSION) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received a %d messages instead of SSH_FXP_VERSION", packet->type);
+ sftp_packet_free(packet);
+ sftp_leave_function();
+ return -1;
+ }
+
+ /* TODO: are we sure there are 4 bytes ready? */
+ buffer_get_u32(packet->payload, &version);
+ version = ntohl(version);
+ ssh_log(sftp->session, SSH_LOG_RARE,
+ "SFTP server version %d",
+ version);
+
+ ext_name_s = buffer_get_ssh_string(packet->payload);
+ while (ext_name_s != NULL) {
+ int count = sftp->ext->count;
+ char **tmp;
+
+ ext_data_s = buffer_get_ssh_string(packet->payload);
+ if (ext_data_s == NULL) {
+ ssh_string_free(ext_name_s);
+ break;
+ }
+
+ ext_name = ssh_string_to_char(ext_name_s);
+ ext_data = ssh_string_to_char(ext_data_s);
+ if (ext_name == NULL || ext_data == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(ext_name);
+ SAFE_FREE(ext_data);
+ ssh_string_free(ext_name_s);
+ ssh_string_free(ext_data_s);
+ return -1;
+ }
+ ssh_log(sftp->session, SSH_LOG_RARE,
+ "SFTP server extension: %s, version: %s",
+ ext_name, ext_data);
+
+ count++;
+ tmp = realloc(sftp->ext->name, count * sizeof(char *));
+ if (tmp == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(ext_name);
+ SAFE_FREE(ext_data);
+ ssh_string_free(ext_name_s);
+ ssh_string_free(ext_data_s);
+ return -1;
+ }
+ tmp[count - 1] = ext_name;
+ sftp->ext->name = tmp;
+
+ tmp = realloc(sftp->ext->data, count * sizeof(char *));
+ if (tmp == NULL) {
+ ssh_set_error_oom(sftp->session);
+ SAFE_FREE(ext_name);
+ SAFE_FREE(ext_data);
+ ssh_string_free(ext_name_s);
+ ssh_string_free(ext_data_s);
+ return -1;
+ }
+ tmp[count - 1] = ext_data;
+ sftp->ext->data = tmp;
+
+ sftp->ext->count = count;
+
+ ssh_string_free(ext_name_s);
+ ssh_string_free(ext_data_s);
+
+ ext_name_s = buffer_get_ssh_string(packet->payload);
+ }
+
+ sftp_packet_free(packet);
+
+ sftp->version = sftp->server_version = version;
+
+ sftp_leave_function();
+
+ return 0;
+}
+
+unsigned int sftp_extensions_get_count(sftp_session sftp) {
+ if (sftp == NULL || sftp->ext == NULL) {
+ return 0;
+ }
+
+ return sftp->ext->count;
+}
+
+const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) {
+ if (sftp == NULL)
+ return NULL;
+ if (sftp->ext == NULL || sftp->ext->name == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ if (idx > sftp->ext->count) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ return sftp->ext->name[idx];
+}
+
+const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) {
+ if (sftp == NULL)
+ return NULL;
+ if (sftp->ext == NULL || sftp->ext->name == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ if (idx > sftp->ext->count) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ return sftp->ext->data[idx];
+}
+
+int sftp_extension_supported(sftp_session sftp, const char *name,
+ const char *data) {
+ int i, n;
+
+ n = sftp_extensions_get_count(sftp);
+ for (i = 0; i < n; i++) {
+ if (strcmp(sftp_extensions_get_name(sftp, i), name) == 0 &&
+ strcmp(sftp_extensions_get_data(sftp, i), data) == 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static sftp_request_queue request_queue_new(sftp_message msg) {
+ sftp_request_queue queue = NULL;
+
+ queue = malloc(sizeof(struct sftp_request_queue_struct));
+ if (queue == NULL) {
+ ssh_set_error_oom(msg->sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(queue);
+
+ queue->message = msg;
+
+ return queue;
+}
+
+static void request_queue_free(sftp_request_queue queue) {
+ if (queue == NULL) {
+ return;
+ }
+
+ ZERO_STRUCTP(queue);
+ SAFE_FREE(queue);
+}
+
+static int sftp_enqueue(sftp_session sftp, sftp_message msg) {
+ sftp_request_queue queue = NULL;
+ sftp_request_queue ptr;
+
+ queue = request_queue_new(msg);
+ if (queue == NULL) {
+ return -1;
+ }
+
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Queued msg type %d id %d",
+ msg->id, msg->packet_type);
+
+ if(sftp->queue == NULL) {
+ sftp->queue = queue;
+ } else {
+ ptr = sftp->queue;
+ while(ptr->next) {
+ ptr=ptr->next; /* find end of linked list */
+ }
+ ptr->next = queue; /* add it on bottom */
+ }
+
+ return 0;
+}
+
+/*
+ * Pulls of a message from the queue based on the ID.
+ * Returns NULL if no message has been found.
+ */
+static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){
+ sftp_request_queue prev = NULL;
+ sftp_request_queue queue;
+ sftp_message msg;
+
+ if(sftp->queue == NULL) {
+ return NULL;
+ }
+
+ queue = sftp->queue;
+ while (queue) {
+ if(queue->message->id == id) {
+ /* remove from queue */
+ if (prev == NULL) {
+ sftp->queue = queue->next;
+ } else {
+ prev->next = queue->next;
+ }
+ msg = queue->message;
+ request_queue_free(queue);
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Dequeued msg id %d type %d",
+ msg->id,
+ msg->packet_type);
+ return msg;
+ }
+ prev = queue;
+ queue = queue->next;
+ }
+
+ return NULL;
+}
+
+/*
+ * Assigns a new SFTP ID for new requests and assures there is no collision
+ * between them.
+ * Returns a new ID ready to use in a request
+ */
+static inline uint32_t sftp_get_new_id(sftp_session session) {
+ return ++session->id_counter;
+}
+
+static sftp_status_message parse_status_msg(sftp_message msg){
+ sftp_status_message status;
+
+ if (msg->packet_type != SSH_FXP_STATUS) {
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Not a ssh_fxp_status message passed in!");
+ return NULL;
+ }
+
+ status = malloc(sizeof(struct sftp_status_message_struct));
+ if (status == NULL) {
+ ssh_set_error_oom(msg->sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(status);
+
+ status->id = msg->id;
+ if (buffer_get_u32(msg->payload,&status->status) != 4){
+ SAFE_FREE(status);
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Invalid SSH_FXP_STATUS message");
+ return NULL;
+ }
+ status->error = buffer_get_ssh_string(msg->payload);
+ status->lang = buffer_get_ssh_string(msg->payload);
+ if(status->error == NULL || status->lang == NULL){
+ if(msg->sftp->version >=3){
+ /* These are mandatory from version 3 */
+ ssh_string_free(status->error);
+ /* status->lang never get allocated if something failed */
+ SAFE_FREE(status);
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Invalid SSH_FXP_STATUS message");
+ return NULL;
+ }
+ }
+
+ status->status = ntohl(status->status);
+ if(status->error)
+ status->errormsg = ssh_string_to_char(status->error);
+ else
+ status->errormsg = strdup("No error message in packet");
+ if(status->lang)
+ status->langmsg = ssh_string_to_char(status->lang);
+ else
+ status->langmsg = strdup("");
+ if (status->errormsg == NULL || status->langmsg == NULL) {
+ ssh_set_error_oom(msg->sftp->session);
+ status_msg_free(status);
+ return NULL;
+ }
+
+ return status;
+}
+
+static void status_msg_free(sftp_status_message status){
+ if (status == NULL) {
+ return;
+ }
+
+ ssh_string_free(status->error);
+ ssh_string_free(status->lang);
+ SAFE_FREE(status->errormsg);
+ SAFE_FREE(status->langmsg);
+ SAFE_FREE(status);
+}
+
+static sftp_file parse_handle_msg(sftp_message msg){
+ sftp_file file;
+
+ if(msg->packet_type != SSH_FXP_HANDLE) {
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Not a ssh_fxp_handle message passed in!");
+ return NULL;
+ }
+
+ file = malloc(sizeof(struct sftp_file_struct));
+ if (file == NULL) {
+ ssh_set_error_oom(msg->sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(file);
+
+ file->handle = buffer_get_ssh_string(msg->payload);
+ if (file->handle == NULL) {
+ ssh_set_error(msg->sftp->session, SSH_FATAL,
+ "Invalid SSH_FXP_HANDLE message");
+ SAFE_FREE(file);
+ return NULL;
+ }
+
+ file->sftp = msg->sftp;
+ file->offset = 0;
+ file->eof = 0;
+
+ return file;
+}
+
+/* Open a directory */
+sftp_dir sftp_opendir(sftp_session sftp, const char *path){
+ sftp_message msg = NULL;
+ sftp_file file = NULL;
+ sftp_dir dir = NULL;
+ sftp_status_message status;
+ ssh_string path_s;
+ ssh_buffer payload;
+ uint32_t id;
+
+ payload = ssh_buffer_new();
+ if (payload == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ path_s = ssh_string_from_char(path);
+ if (path_s == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(payload);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(payload, id) < 0 ||
+ buffer_add_ssh_string(payload, path_s) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(payload);
+ ssh_string_free(path_s);
+ return NULL;
+ }
+ ssh_string_free(path_s);
+
+ if (sftp_packet_write(sftp, SSH_FXP_OPENDIR, payload) < 0) {
+ ssh_buffer_free(payload);
+ return NULL;
+ }
+ ssh_buffer_free(payload);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ sftp_set_error(sftp, status->status);
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return NULL;
+ case SSH_FXP_HANDLE:
+ file = parse_handle_msg(msg);
+ sftp_message_free(msg);
+ if (file != NULL) {
+ dir = malloc(sizeof(struct sftp_dir_struct));
+ if (dir == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(dir);
+
+ dir->sftp = sftp;
+ dir->name = strdup(path);
+ if (dir->name == NULL) {
+ SAFE_FREE(dir);
+ SAFE_FREE(file);
+ return NULL;
+ }
+ dir->handle = file->handle;
+ SAFE_FREE(file);
+ }
+ return dir;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during opendir!", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+/*
+ * Parse the attributes from a payload from some messages. It is coded on
+ * baselines from the protocol version 4.
+ * This code is more or less dead but maybe we need it in future.
+ */
+static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf,
+ int expectnames) {
+ sftp_attributes attr;
+ ssh_string owner = NULL;
+ ssh_string group = NULL;
+ uint32_t flags = 0;
+ int ok = 0;
+
+ /* unused member variable */
+ (void) expectnames;
+
+ attr = malloc(sizeof(struct sftp_attributes_struct));
+ if (attr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(attr);
+
+ /* This isn't really a loop, but it is like a try..catch.. */
+ do {
+ if (buffer_get_u32(buf, &flags) != 4) {
+ break;
+ }
+
+ flags = ntohl(flags);
+ attr->flags = flags;
+
+ if (flags & SSH_FILEXFER_ATTR_SIZE) {
+ if (buffer_get_u64(buf, &attr->size) != 8) {
+ break;
+ }
+ attr->size = ntohll(attr->size);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) {
+ if((owner = buffer_get_ssh_string(buf)) == NULL ||
+ (attr->owner = ssh_string_to_char(owner)) == NULL) {
+ break;
+ }
+ if ((group = buffer_get_ssh_string(buf)) == NULL ||
+ (attr->group = ssh_string_to_char(group)) == NULL) {
+ break;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+ if (buffer_get_u32(buf, &attr->permissions) != 4) {
+ break;
+ }
+ attr->permissions = ntohl(attr->permissions);
+
+ /* FIXME on windows! */
+ switch (attr->permissions & S_IFMT) {
+ case S_IFSOCK:
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFIFO:
+ attr->type = SSH_FILEXFER_TYPE_SPECIAL;
+ break;
+ case S_IFLNK:
+ attr->type = SSH_FILEXFER_TYPE_SYMLINK;
+ break;
+ case S_IFREG:
+ attr->type = SSH_FILEXFER_TYPE_REGULAR;
+ break;
+ case S_IFDIR:
+ attr->type = SSH_FILEXFER_TYPE_DIRECTORY;
+ break;
+ default:
+ attr->type = SSH_FILEXFER_TYPE_UNKNOWN;
+ break;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) {
+ if (buffer_get_u64(buf, &attr->atime64) != 8) {
+ break;
+ }
+ attr->atime64 = ntohll(attr->atime64);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
+ if (buffer_get_u32(buf, &attr->atime_nseconds) != 4) {
+ break;
+ }
+ attr->atime_nseconds = ntohl(attr->atime_nseconds);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_CREATETIME) {
+ if (buffer_get_u64(buf, &attr->createtime) != 8) {
+ break;
+ }
+ attr->createtime = ntohll(attr->createtime);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
+ if (buffer_get_u32(buf, &attr->createtime_nseconds) != 4) {
+ break;
+ }
+ attr->createtime_nseconds = ntohl(attr->createtime_nseconds);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) {
+ if (buffer_get_u64(buf, &attr->mtime64) != 8) {
+ break;
+ }
+ attr->mtime64 = ntohll(attr->mtime64);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
+ if (buffer_get_u32(buf, &attr->mtime_nseconds) != 4) {
+ break;
+ }
+ attr->mtime_nseconds = ntohl(attr->mtime_nseconds);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_ACL) {
+ if ((attr->acl = buffer_get_ssh_string(buf)) == NULL) {
+ break;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
+ if (buffer_get_u32(buf,&attr->extended_count) != 4) {
+ break;
+ }
+ attr->extended_count = ntohl(attr->extended_count);
+
+ while(attr->extended_count &&
+ (attr->extended_type = buffer_get_ssh_string(buf)) &&
+ (attr->extended_data = buffer_get_ssh_string(buf))){
+ attr->extended_count--;
+ }
+
+ if (attr->extended_count) {
+ break;
+ }
+ }
+ ok = 1;
+ } while (0);
+
+ if (ok == 0) {
+ /* break issued somewhere */
+ ssh_string_free(owner);
+ ssh_string_free(group);
+ ssh_string_free(attr->acl);
+ ssh_string_free(attr->extended_type);
+ ssh_string_free(attr->extended_data);
+ SAFE_FREE(attr->owner);
+ SAFE_FREE(attr->group);
+ SAFE_FREE(attr);
+
+ ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure");
+
+ return NULL;
+ }
+
+ return attr;
+}
+
+enum sftp_longname_field_e {
+ SFTP_LONGNAME_PERM = 0,
+ SFTP_LONGNAME_FIXME,
+ SFTP_LONGNAME_OWNER,
+ SFTP_LONGNAME_GROUP,
+ SFTP_LONGNAME_SIZE,
+ SFTP_LONGNAME_DATE,
+ SFTP_LONGNAME_TIME,
+ SFTP_LONGNAME_NAME,
+};
+
+static char *sftp_parse_longname(const char *longname,
+ enum sftp_longname_field_e longname_field) {
+ const char *p, *q;
+ size_t len, field = 0;
+ char *x;
+
+ p = longname;
+ /* Find the beginning of the field which is specified by sftp_longanme_field_e. */
+ while(field != longname_field) {
+ if(isspace(*p)) {
+ field++;
+ p++;
+ while(*p && isspace(*p)) {
+ p++;
+ }
+ } else {
+ p++;
+ }
+ }
+
+ q = p;
+ while (! isspace(*q)) {
+ q++;
+ }
+
+ /* There is no strndup on windows */
+ len = q - p + 1;
+ x = malloc(len);
+ if (x == NULL) {
+ return NULL;
+ }
+
+ snprintf(x, len, "%s", p);
+
+ return x;
+}
+
+/* sftp version 0-3 code. It is different from the v4 */
+/* maybe a paste of the draft is better than the code */
+/*
+ uint32 flags
+ uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
+ uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID
+ uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID
+ uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
+ uint32 atime present only if flag SSH_FILEXFER_ACMODTIME
+ uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME
+ uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
+ string extended_type
+ string extended_data
+ ... more extended data (extended_type - extended_data pairs),
+ so that number of pairs equals extended_count */
+static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf,
+ int expectname) {
+ ssh_string longname = NULL;
+ ssh_string name = NULL;
+ sftp_attributes attr;
+ uint32_t flags = 0;
+ int ok = 0;
+
+ attr = malloc(sizeof(struct sftp_attributes_struct));
+ if (attr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(attr);
+
+ /* This isn't really a loop, but it is like a try..catch.. */
+ do {
+ if (expectname) {
+ if ((name = buffer_get_ssh_string(buf)) == NULL ||
+ (attr->name = ssh_string_to_char(name)) == NULL) {
+ break;
+ }
+ ssh_string_free(name);
+
+ ssh_log(sftp->session, SSH_LOG_RARE, "Name: %s", attr->name);
+
+ if ((longname=buffer_get_ssh_string(buf)) == NULL ||
+ (attr->longname=ssh_string_to_char(longname)) == NULL) {
+ break;
+ }
+ ssh_string_free(longname);
+
+ /* Set owner and group if we talk to openssh and have the longname */
+ if (ssh_get_openssh_version(sftp->session)) {
+ attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER);
+ if (attr->owner == NULL) {
+ break;
+ }
+
+ attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP);
+ if (attr->group == NULL) {
+ break;
+ }
+ }
+ }
+
+ if (buffer_get_u32(buf, &flags) != sizeof(uint32_t)) {
+ break;
+ }
+ flags = ntohl(flags);
+ attr->flags = flags;
+ ssh_log(sftp->session, SSH_LOG_RARE,
+ "Flags: %.8lx\n", (long unsigned int) flags);
+
+ if (flags & SSH_FILEXFER_ATTR_SIZE) {
+ if(buffer_get_u64(buf, &attr->size) != sizeof(uint64_t)) {
+ break;
+ }
+ attr->size = ntohll(attr->size);
+ ssh_log(sftp->session, SSH_LOG_RARE,
+ "Size: %llu\n",
+ (long long unsigned int) attr->size);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_UIDGID) {
+ if (buffer_get_u32(buf, &attr->uid) != sizeof(uint32_t)) {
+ break;
+ }
+ if (buffer_get_u32(buf, &attr->gid) != sizeof(uint32_t)) {
+ break;
+ }
+ attr->uid = ntohl(attr->uid);
+ attr->gid = ntohl(attr->gid);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+ if (buffer_get_u32(buf, &attr->permissions) != sizeof(uint32_t)) {
+ break;
+ }
+ attr->permissions = ntohl(attr->permissions);
+
+ switch (attr->permissions & S_IFMT) {
+ case S_IFSOCK:
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFIFO:
+ attr->type = SSH_FILEXFER_TYPE_SPECIAL;
+ break;
+ case S_IFLNK:
+ attr->type = SSH_FILEXFER_TYPE_SYMLINK;
+ break;
+ case S_IFREG:
+ attr->type = SSH_FILEXFER_TYPE_REGULAR;
+ break;
+ case S_IFDIR:
+ attr->type = SSH_FILEXFER_TYPE_DIRECTORY;
+ break;
+ default:
+ attr->type = SSH_FILEXFER_TYPE_UNKNOWN;
+ break;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_ACMODTIME) {
+ if (buffer_get_u32(buf, &attr->atime) != sizeof(uint32_t)) {
+ break;
+ }
+ attr->atime = ntohl(attr->atime);
+ if (buffer_get_u32(buf, &attr->mtime) != sizeof(uint32_t)) {
+ break;
+ }
+ attr->mtime = ntohl(attr->mtime);
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
+ if (buffer_get_u32(buf, &attr->extended_count) != sizeof(uint32_t)) {
+ break;
+ }
+
+ attr->extended_count = ntohl(attr->extended_count);
+ while (attr->extended_count &&
+ (attr->extended_type = buffer_get_ssh_string(buf))
+ && (attr->extended_data = buffer_get_ssh_string(buf))) {
+ attr->extended_count--;
+ }
+
+ if (attr->extended_count) {
+ break;
+ }
+ }
+ ok = 1;
+ } while (0);
+
+ if (!ok) {
+ /* break issued somewhere */
+ ssh_string_free(name);
+ ssh_string_free(longname);
+ ssh_string_free(attr->extended_type);
+ ssh_string_free(attr->extended_data);
+ SAFE_FREE(attr->name);
+ SAFE_FREE(attr->longname);
+ SAFE_FREE(attr->owner);
+ SAFE_FREE(attr->group);
+ SAFE_FREE(attr);
+
+ ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure");
+
+ return NULL;
+ }
+
+ /* everything went smoothly */
+ return attr;
+}
+
+/* FIXME is this really needed as a public function? */
+int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) {
+ uint32_t flags = (attr ? attr->flags : 0);
+
+ flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID |
+ SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME);
+
+ if (buffer_add_u32(buffer, htonl(flags)) < 0) {
+ return -1;
+ }
+
+ if (attr) {
+ if (flags & SSH_FILEXFER_ATTR_SIZE) {
+ if (buffer_add_u64(buffer, htonll(attr->size)) < 0) {
+ return -1;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_UIDGID) {
+ if (buffer_add_u32(buffer,htonl(attr->uid)) < 0 ||
+ buffer_add_u32(buffer,htonl(attr->gid)) < 0) {
+ return -1;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
+ if (buffer_add_u32(buffer, htonl(attr->permissions)) < 0) {
+ return -1;
+ }
+ }
+
+ if (flags & SSH_FILEXFER_ATTR_ACMODTIME) {
+ if (buffer_add_u32(buffer, htonl(attr->atime)) < 0 ||
+ buffer_add_u32(buffer, htonl(attr->mtime)) < 0) {
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+sftp_attributes sftp_parse_attr(sftp_session session, ssh_buffer buf,
+ int expectname) {
+ switch(session->version) {
+ case 4:
+ return sftp_parse_attr_4(session, buf, expectname);
+ case 3:
+ case 2:
+ case 1:
+ case 0:
+ return sftp_parse_attr_3(session, buf, expectname);
+ default:
+ ssh_set_error(session->session, SSH_FATAL,
+ "Version %d unsupported by client", session->server_version);
+ return NULL;
+ }
+
+ return NULL;
+}
+
+/* Get the version of the SFTP protocol supported by the server */
+int sftp_server_version(sftp_session sftp) {
+ return sftp->server_version;
+}
+
+/* Get a single file attributes structure of a directory. */
+sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) {
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ sftp_attributes attr;
+ ssh_buffer payload;
+ uint32_t id;
+
+ if (dir->buffer == NULL) {
+ payload = ssh_buffer_new();
+ if (payload == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(payload, id) < 0 ||
+ buffer_add_ssh_string(payload, dir->handle) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(payload);
+ return NULL;
+ }
+
+ if (sftp_packet_write(sftp, SSH_FXP_READDIR, payload) < 0) {
+ ssh_buffer_free(payload);
+ return NULL;
+ }
+ ssh_buffer_free(payload);
+
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Sent a ssh_fxp_readdir with id %d", id);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ switch (msg->packet_type){
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_EOF:
+ dir->eof = 1;
+ status_msg_free(status);
+ return NULL;
+ default:
+ break;
+ }
+
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Unknown error status: %d", status->status);
+ status_msg_free(status);
+
+ return NULL;
+ case SSH_FXP_NAME:
+ buffer_get_u32(msg->payload, &dir->count);
+ dir->count = ntohl(dir->count);
+ dir->buffer = msg->payload;
+ msg->payload = NULL;
+ sftp_message_free(msg);
+ break;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Unsupported message back %d", msg->packet_type);
+ sftp_message_free(msg);
+
+ return NULL;
+ }
+ }
+
+ /* now dir->buffer contains a buffer and dir->count != 0 */
+ if (dir->count == 0) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Count of files sent by the server is zero, which is invalid, or "
+ "libsftp bug");
+ return NULL;
+ }
+
+ ssh_log(sftp->session, SSH_LOG_RARE, "Count is %d", dir->count);
+
+ attr = sftp_parse_attr(sftp, dir->buffer, 1);
+ if (attr == NULL) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Couldn't parse the SFTP attributes");
+ return NULL;
+ }
+
+ dir->count--;
+ if (dir->count == 0) {
+ ssh_buffer_free(dir->buffer);
+ dir->buffer = NULL;
+ }
+
+ return attr;
+}
+
+/* Tell if the directory has reached EOF (End Of File). */
+int sftp_dir_eof(sftp_dir dir) {
+ return dir->eof;
+}
+
+/* Free a SFTP_ATTRIBUTE handle */
+void sftp_attributes_free(sftp_attributes file){
+ if (file == NULL) {
+ return;
+ }
+
+ ssh_string_free(file->acl);
+ ssh_string_free(file->extended_data);
+ ssh_string_free(file->extended_type);
+
+ SAFE_FREE(file->name);
+ SAFE_FREE(file->longname);
+ SAFE_FREE(file->group);
+ SAFE_FREE(file->owner);
+
+ SAFE_FREE(file);
+}
+
+static int sftp_handle_close(sftp_session sftp, ssh_string handle) {
+ sftp_status_message status;
+ sftp_message msg = NULL;
+ ssh_buffer buffer = NULL;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, handle) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_CLOSE ,buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return -1;
+ }
+ msg = sftp_dequeue(sftp,id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if(status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ break;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during sftp_handle_close!", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* Close an open file handle. */
+int sftp_close(sftp_file file){
+ int err = SSH_NO_ERROR;
+
+ SAFE_FREE(file->name);
+ if (file->handle){
+ err = sftp_handle_close(file->sftp,file->handle);
+ ssh_string_free(file->handle);
+ }
+ /* FIXME: check server response and implement errno */
+ SAFE_FREE(file);
+
+ return err;
+}
+
+/* Close an open directory. */
+int sftp_closedir(sftp_dir dir){
+ int err = SSH_NO_ERROR;
+
+ SAFE_FREE(dir->name);
+ if (dir->handle) {
+ err = sftp_handle_close(dir->sftp, dir->handle);
+ ssh_string_free(dir->handle);
+ }
+ /* FIXME: check server response and implement errno */
+ ssh_buffer_free(dir->buffer);
+ SAFE_FREE(dir);
+
+ return err;
+}
+
+/* Open a file on the server. */
+sftp_file sftp_open(sftp_session sftp, const char *file, int flags,
+ mode_t mode) {
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ struct sftp_attributes_struct attr;
+ sftp_file handle;
+ ssh_string filename;
+ ssh_buffer buffer;
+ uint32_t sftp_flags = 0;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ filename = ssh_string_from_char(file);
+ if (filename == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ ZERO_STRUCT(attr);
+ attr.permissions = mode;
+ attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
+
+ if (flags == O_RDONLY)
+ sftp_flags |= SSH_FXF_READ; /* if any of the other flag is set,
+ READ should not be set initialy */
+ if (flags & O_WRONLY)
+ sftp_flags |= SSH_FXF_WRITE;
+ if (flags & O_RDWR)
+ sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ);
+ if (flags & O_CREAT)
+ sftp_flags |= SSH_FXF_CREAT;
+ if (flags & O_TRUNC)
+ sftp_flags |= SSH_FXF_TRUNC;
+ if (flags & O_EXCL)
+ sftp_flags |= SSH_FXF_EXCL;
+ ssh_log(sftp->session,SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags);
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, filename) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ return NULL;
+ }
+ ssh_string_free(filename);
+
+ if (buffer_add_u32(buffer, htonl(sftp_flags)) < 0 ||
+ buffer_add_attributes(buffer, &attr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_OPEN, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ sftp_set_error(sftp, status->status);
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+
+ return NULL;
+ case SSH_FXP_HANDLE:
+ handle = parse_handle_msg(msg);
+ sftp_message_free(msg);
+ return handle;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during open!", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+void sftp_file_set_nonblocking(sftp_file handle){
+ handle->nonblocking=1;
+}
+void sftp_file_set_blocking(sftp_file handle){
+ handle->nonblocking=0;
+}
+
+/* Read from a file using an opened sftp file handle. */
+ssize_t sftp_read(sftp_file handle, void *buf, size_t count) {
+ sftp_session sftp = handle->sftp;
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ ssh_string datastring;
+ ssh_buffer buffer;
+ int id;
+
+ if (handle->eof) {
+ return 0;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+ id = sftp_get_new_id(handle->sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, handle->handle) < 0 ||
+ buffer_add_u64(buffer, htonll(handle->offset)) < 0 ||
+ buffer_add_u32(buffer,htonl(count)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (handle->nonblocking) {
+ if (ssh_channel_poll(handle->sftp->channel, 0) == 0) {
+ /* we cannot block */
+ return 0;
+ }
+ }
+ if (sftp_read_and_dispatch(handle->sftp) < 0) {
+ /* something nasty has happened */
+ return -1;
+ }
+ msg = sftp_dequeue(handle->sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_EOF:
+ handle->eof = 1;
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session,SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ case SSH_FXP_DATA:
+ datastring = buffer_get_ssh_string(msg->payload);
+ sftp_message_free(msg);
+ if (datastring == NULL) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received invalid DATA packet from sftp server");
+ return -1;
+ }
+
+ if (ssh_string_len(datastring) > count) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received a too big DATA packet from sftp server: "
+ "%zu and asked for %zu",
+ ssh_string_len(datastring), count);
+ ssh_string_free(datastring);
+ return -1;
+ }
+ count = ssh_string_len(datastring);
+ handle->offset += count;
+ memcpy(buf, ssh_string_data(datastring), count);
+ ssh_string_free(datastring);
+ return count;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during read!", msg->packet_type);
+ sftp_message_free(msg);
+ return -1;
+ }
+
+ return -1; /* not reached */
+}
+
+/* Start an asynchronous read from a file using an opened sftp file handle. */
+int sftp_async_read_begin(sftp_file file, uint32_t len){
+ sftp_session sftp = file->sftp;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ sftp_enter_function();
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, file->handle) < 0 ||
+ buffer_add_u64(buffer, htonll(file->offset)) < 0 ||
+ buffer_add_u32(buffer, htonl(len)) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+
+ file->offset += len; /* assume we'll read len bytes */
+
+ sftp_leave_function();
+ return id;
+}
+
+/* Wait for an asynchronous read to complete and save the data. */
+int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){
+ sftp_session sftp = file->sftp;
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ ssh_string datastring;
+ int err = SSH_OK;
+ uint32_t len;
+
+ sftp_enter_function();
+
+ if (file->eof) {
+ sftp_leave_function();
+ return 0;
+ }
+
+ /* handle an existing request */
+ while (msg == NULL) {
+ if (file->nonblocking){
+ if (ssh_channel_poll(sftp->channel, 0) == 0) {
+ /* we cannot block */
+ return SSH_AGAIN;
+ }
+ }
+
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ sftp_leave_function();
+ return SSH_ERROR;
+ }
+
+ msg = sftp_dequeue(sftp,id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ sftp_leave_function();
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ if (status->status != SSH_FX_EOF) {
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server : %s", status->errormsg);
+ sftp_leave_function();
+ err = SSH_ERROR;
+ } else {
+ file->eof = 1;
+ }
+ status_msg_free(status);
+ sftp_leave_function();
+ return err;
+ case SSH_FXP_DATA:
+ datastring = buffer_get_ssh_string(msg->payload);
+ sftp_message_free(msg);
+ if (datastring == NULL) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received invalid DATA packet from sftp server");
+ sftp_leave_function();
+ return SSH_ERROR;
+ }
+ if (ssh_string_len(datastring) > size) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received a too big DATA packet from sftp server: "
+ "%zu and asked for %u",
+ ssh_string_len(datastring), size);
+ ssh_string_free(datastring);
+ sftp_leave_function();
+ return SSH_ERROR;
+ }
+ len = ssh_string_len(datastring);
+ //handle->offset+=len;
+ /* We already have set the offset previously. All we can do is warn that the expected len
+ * and effective lengths are different */
+ memcpy(data, ssh_string_data(datastring), len);
+ ssh_string_free(datastring);
+ sftp_leave_function();
+ return len;
+ default:
+ ssh_set_error(sftp->session,SSH_FATAL,"Received message %d during read!",msg->packet_type);
+ sftp_message_free(msg);
+ sftp_leave_function();
+ return SSH_ERROR;
+ }
+
+ sftp_leave_function();
+ return SSH_ERROR;
+}
+
+ssize_t sftp_write(sftp_file file, const void *buf, size_t count) {
+ sftp_session sftp = file->sftp;
+ sftp_message msg = NULL;
+ sftp_status_message status;
+ ssh_string datastring;
+ ssh_buffer buffer;
+ uint32_t id;
+ int len;
+ int packetlen;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ datastring = ssh_string_new(count);
+ if (datastring == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+ ssh_string_fill(datastring, buf, count);
+
+ id = sftp_get_new_id(file->sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, file->handle) < 0 ||
+ buffer_add_u64(buffer, htonll(file->offset)) < 0 ||
+ buffer_add_ssh_string(buffer, datastring) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(datastring);
+ return -1;
+ }
+ ssh_string_free(datastring);
+ len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer);
+ packetlen=ssh_buffer_get_len(buffer);
+ ssh_buffer_free(buffer);
+ if (len < 0) {
+ return -1;
+ } else if (len != packetlen) {
+ ssh_log(sftp->session, SSH_LOG_PACKET,
+ "Could not write as much data as expected");
+ }
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(file->sftp) < 0) {
+ /* something nasty has happened */
+ return -1;
+ }
+ msg = sftp_dequeue(file->sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ file->offset += count;
+ status_msg_free(status);
+ return count;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ file->offset += count;
+ status_msg_free(status);
+ return -1;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during write!", msg->packet_type);
+ sftp_message_free(msg);
+ return -1;
+ }
+
+ return -1; /* not reached */
+}
+
+/* Seek to a specific location in a file. */
+int sftp_seek(sftp_file file, uint32_t new_offset) {
+ if (file == NULL) {
+ return -1;
+ }
+
+ file->offset = new_offset;
+
+ return 0;
+}
+
+int sftp_seek64(sftp_file file, uint64_t new_offset) {
+ if (file == NULL) {
+ return -1;
+ }
+
+ file->offset = new_offset;
+
+ return 0;
+}
+
+/* Report current byte position in file. */
+unsigned long sftp_tell(sftp_file file) {
+ return (unsigned long)file->offset;
+}
+/* Report current byte position in file. */
+uint64_t sftp_tell64(sftp_file file) {
+ return (uint64_t) file->offset;
+}
+
+/* Rewinds the position of the file pointer to the beginning of the file.*/
+void sftp_rewind(sftp_file file) {
+ file->offset = 0;
+}
+
+/* code written by Nick */
+int sftp_unlink(sftp_session sftp, const char *file) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string filename;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ filename = ssh_string_from_char(file);
+ if (filename == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, filename) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ }
+ ssh_string_free(filename);
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp)) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ /* by specification, this command's only supposed to return SSH_FXP_STATUS */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+
+ /*
+ * The status should be SSH_FX_OK if the command was successful, if it
+ * didn't, then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session,SSH_FATAL,
+ "Received message %d when attempting to remove file", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* code written by Nick */
+int sftp_rmdir(sftp_session sftp, const char *directory) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string filename;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ filename = ssh_string_from_char(directory);
+ if (filename == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, filename) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(filename);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ break;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to remove directory",
+ msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* Code written by Nick */
+int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ sftp_attributes errno_attr = NULL;
+ struct sftp_attributes_struct attr;
+ ssh_buffer buffer;
+ ssh_string path;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ path = ssh_string_from_char(directory);
+ if (path == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ ZERO_STRUCT(attr);
+ attr.permissions = mode;
+ attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, path) < 0 ||
+ buffer_add_attributes(buffer, &attr) < 0 ||
+ sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command only returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_FAILURE:
+ /*
+ * mkdir always returns a failure, even if the path already exists.
+ * To be POSIX conform and to be able to map it to EEXIST a stat
+ * call is needed here.
+ */
+ errno_attr = sftp_lstat(sftp, directory);
+ if (errno_attr != NULL) {
+ SAFE_FREE(errno_attr);
+ sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS);
+ }
+ break;
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ break;
+ default:
+ break;
+ }
+ /*
+ * The status should be SSH_FX_OK if the command was successful, if it
+ * didn't, then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to make directory",
+ msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* code written by nick */
+int sftp_rename(sftp_session sftp, const char *original, const char *newname) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_buffer buffer;
+ ssh_string oldpath;
+ ssh_string newpath;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ oldpath = ssh_string_from_char(original);
+ if (oldpath == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ newpath = ssh_string_from_char(newname);
+ if (newpath == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(oldpath);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, oldpath) < 0 ||
+ buffer_add_ssh_string(buffer, newpath) < 0 ||
+ /* POSIX rename atomically replaces newpath, we should do the same
+ * only available on >=v4 */
+ sftp->version>=4 ? (buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE) < 0):0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(oldpath);
+ ssh_string_free(newpath);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_RENAME, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(oldpath);
+ ssh_string_free(newpath);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(oldpath);
+ ssh_string_free(newpath);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command only returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+ /*
+ * Status should be SSH_FX_OK if the command was successful, if it didn't,
+ * then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to rename",
+ msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* Code written by Nick */
+/* Set file attributes on a file, directory or symbolic link. */
+int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) {
+ uint32_t id;
+ ssh_buffer buffer;
+ ssh_string path;
+ sftp_message msg = NULL;
+ sftp_status_message status = NULL;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ path = ssh_string_from_char(file);
+ if (path == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, path) < 0 ||
+ buffer_add_attributes(buffer, attr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+ return -1;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(path);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command only returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+ /*
+ * The status should be SSH_FX_OK if the command was successful, if it
+ * didn't, then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+/* Change the file owner and group */
+int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) {
+ struct sftp_attributes_struct attr;
+ ZERO_STRUCT(attr);
+
+ attr.uid = owner;
+ attr.gid = group;
+
+ attr.flags = SSH_FILEXFER_ATTR_UIDGID;
+
+ return sftp_setstat(sftp, file, &attr);
+}
+
+/* Change permissions of a file */
+int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) {
+ struct sftp_attributes_struct attr;
+ ZERO_STRUCT(attr);
+ attr.permissions = mode;
+ attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
+
+ return sftp_setstat(sftp, file, &attr);
+}
+
+/* Change the last modification and access time of a file. */
+int sftp_utimes(sftp_session sftp, const char *file,
+ const struct timeval *times) {
+ struct sftp_attributes_struct attr;
+ ZERO_STRUCT(attr);
+
+ attr.atime = times[0].tv_sec;
+ attr.atime_nseconds = times[0].tv_usec;
+
+ attr.mtime = times[1].tv_sec;
+ attr.mtime_nseconds = times[1].tv_usec;
+
+ attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME |
+ SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
+
+ return sftp_setstat(sftp, file, &attr);
+}
+
+int sftp_symlink(sftp_session sftp, const char *target, const char *dest) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string target_s;
+ ssh_string dest_s;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ if (sftp == NULL)
+ return -1;
+ if (target == NULL || dest == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return -1;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ target_s = ssh_string_from_char(target);
+ if (target_s == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ dest_s = ssh_string_from_char(dest);
+ if (dest_s == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_string_free(target_s);
+ ssh_buffer_free(buffer);
+ return -1;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+ return -1;
+ }
+ if (ssh_get_openssh_version(sftp->session)) {
+ /* TODO check for version number if they ever fix it. */
+ if (buffer_add_ssh_string(buffer, target_s) < 0 ||
+ buffer_add_ssh_string(buffer, dest_s) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+ return -1;
+ }
+ } else {
+ if (buffer_add_ssh_string(buffer, dest_s) < 0 ||
+ buffer_add_ssh_string(buffer, target_s) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+ return -1;
+ }
+ }
+
+ if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+ return -1;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(dest_s);
+ ssh_string_free(target_s);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return -1;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ /* By specification, this command only returns SSH_FXP_STATUS */
+ if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return -1;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return 0;
+ default:
+ break;
+ }
+ /*
+ * The status should be SSH_FX_OK if the command was successful, if it
+ * didn't, then there was an error
+ */
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return -1;
+ } else {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return -1;
+}
+
+char *sftp_readlink(sftp_session sftp, const char *path) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string path_s = NULL;
+ ssh_string link_s = NULL;
+ ssh_buffer buffer;
+ char *lnk;
+ uint32_t ignored;
+ uint32_t id;
+
+ if (sftp == NULL)
+ return NULL;
+ if (path == NULL) {
+ ssh_set_error_invalid(sftp, __FUNCTION__);
+ return NULL;
+ }
+ if (sftp->version < 3){
+ ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version);
+ return NULL;
+ }
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ path_s = ssh_string_from_char(path);
+ if (path_s == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, path_s) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(path_s);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_READLINK, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(path_s);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(path_s);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_NAME) {
+ /* we don't care about "count" */
+ buffer_get_u32(msg->payload, &ignored);
+ /* we only care about the file name string */
+ link_s = buffer_get_ssh_string(msg->payload);
+ sftp_message_free(msg);
+ if (link_s == NULL) {
+ /* TODO: what error to set here? */
+ return NULL;
+ }
+ lnk = ssh_string_to_char(link_s);
+ ssh_string_free(link_s);
+
+ return lnk;
+ } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ } else { /* this shouldn't happen */
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) {
+ sftp_statvfs_t statvfs;
+ uint64_t tmp;
+ int ok = 0;
+
+ statvfs = malloc(sizeof(struct sftp_statvfs_struct));
+ if (statvfs == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+ ZERO_STRUCTP(statvfs);
+
+ /* try .. catch */
+ do {
+ /* file system block size */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_bsize = ntohll(tmp);
+
+ /* fundamental fs block size */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_frsize = ntohll(tmp);
+
+ /* number of blocks (unit f_frsize) */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_blocks = ntohll(tmp);
+
+ /* free blocks in file system */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_bfree = ntohll(tmp);
+
+ /* free blocks for non-root */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_bavail = ntohll(tmp);
+
+ /* total file inodes */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_files = ntohll(tmp);
+
+ /* free file inodes */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_ffree = ntohll(tmp);
+
+ /* free file inodes for to non-root */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_favail = ntohll(tmp);
+
+ /* file system id */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_fsid = ntohll(tmp);
+
+ /* bit mask of f_flag values */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_flag = ntohll(tmp);
+
+ /* maximum filename length */
+ if (buffer_get_u64(buf, &tmp) != sizeof(uint64_t)) {
+ break;
+ }
+ statvfs->f_namemax = ntohll(tmp);
+
+ ok = 1;
+ } while(0);
+
+ if (!ok) {
+ SAFE_FREE(statvfs);
+ ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure");
+ return NULL;
+ }
+
+ return statvfs;
+}
+
+sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string pathstr;
+ ssh_string ext;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ if (sftp == NULL)
+ return NULL;
+ if (path == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+ if (sftp->version < 3){
+ ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version);
+ return NULL;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ ext = ssh_string_from_char("statvfs@openssh.com");
+ if (ext == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ pathstr = ssh_string_from_char(path);
+ if (pathstr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, ext) < 0 ||
+ buffer_add_ssh_string(buffer, pathstr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ ssh_string_free(pathstr);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) {
+ sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload);
+ sftp_message_free(msg);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ return buf;
+ } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ } else { /* this shouldn't happen */
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to get statvfs", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+sftp_statvfs_t sftp_fstatvfs(sftp_file file) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ sftp_session sftp;
+ ssh_string ext;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ if (file == NULL) {
+ return NULL;
+ }
+ sftp = file->sftp;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ ext = ssh_string_from_char("fstatvfs@openssh.com");
+ if (ext == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, ext) < 0 ||
+ buffer_add_ssh_string(buffer, file->handle) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(ext);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) {
+ sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload);
+ sftp_message_free(msg);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ return buf;
+ } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ } else { /* this shouldn't happen */
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+void sftp_statvfs_free(sftp_statvfs_t statvfs) {
+ if (statvfs == NULL) {
+ return;
+ }
+
+ SAFE_FREE(statvfs);
+}
+
+/* another code written by Nick */
+char *sftp_canonicalize_path(sftp_session sftp, const char *path) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string name = NULL;
+ ssh_string pathstr;
+ ssh_buffer buffer;
+ char *cname;
+ uint32_t ignored;
+ uint32_t id;
+
+ if (sftp == NULL)
+ return NULL;
+ if (path == NULL) {
+ ssh_set_error_invalid(sftp->session, __FUNCTION__);
+ return NULL;
+ }
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ pathstr = ssh_string_from_char(path);
+ if (pathstr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, pathstr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_NAME) {
+ /* we don't care about "count" */
+ buffer_get_u32(msg->payload, &ignored);
+ /* we only care about the file name string */
+ name = buffer_get_ssh_string(msg->payload);
+ sftp_message_free(msg);
+ if (name == NULL) {
+ /* TODO: error message? */
+ return NULL;
+ }
+ cname = ssh_string_to_char(name);
+ ssh_string_free(name);
+ if (cname == NULL) {
+ ssh_set_error_oom(sftp->session);
+ }
+ return cname;
+ } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ } else { /* this shouldn't happen */
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d when attempting to set stats", msg->packet_type);
+ sftp_message_free(msg);
+ }
+
+ return NULL;
+}
+
+static sftp_attributes sftp_xstat(sftp_session sftp, const char *path,
+ int param) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_string pathstr;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return NULL;
+ }
+
+ pathstr = ssh_string_from_char(path);
+ if (pathstr == NULL) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, pathstr) < 0) {
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ if (sftp_packet_write(sftp, param, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+ ssh_string_free(pathstr);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_ATTRS) {
+ return sftp_parse_attr(sftp, msg->payload, 0);
+ } else if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ sftp_set_error(sftp, status->status);
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return NULL;
+ }
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received mesg %d during stat()", msg->packet_type);
+ sftp_message_free(msg);
+
+ return NULL;
+}
+
+sftp_attributes sftp_stat(sftp_session session, const char *path) {
+ return sftp_xstat(session, path, SSH_FXP_STAT);
+}
+
+sftp_attributes sftp_lstat(sftp_session session, const char *path) {
+ return sftp_xstat(session, path, SSH_FXP_LSTAT);
+}
+
+sftp_attributes sftp_fstat(sftp_file file) {
+ sftp_status_message status = NULL;
+ sftp_message msg = NULL;
+ ssh_buffer buffer;
+ uint32_t id;
+
+ buffer = ssh_buffer_new();
+ if (buffer == NULL) {
+ ssh_set_error_oom(file->sftp->session);
+ return NULL;
+ }
+
+ id = sftp_get_new_id(file->sftp);
+ if (buffer_add_u32(buffer, id) < 0 ||
+ buffer_add_ssh_string(buffer, file->handle) < 0) {
+ ssh_set_error_oom(file->sftp->session);
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+ if (sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer) < 0) {
+ ssh_buffer_free(buffer);
+ return NULL;
+ }
+ ssh_buffer_free(buffer);
+
+ while (msg == NULL) {
+ if (sftp_read_and_dispatch(file->sftp) < 0) {
+ return NULL;
+ }
+ msg = sftp_dequeue(file->sftp, id);
+ }
+
+ if (msg->packet_type == SSH_FXP_ATTRS){
+ return sftp_parse_attr(file->sftp, msg->payload, 0);
+ } else if (msg->packet_type == SSH_FXP_STATUS) {
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return NULL;
+ }
+ ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+
+ return NULL;
+ }
+ ssh_set_error(file->sftp->session, SSH_FATAL,
+ "Received msg %d during fstat()", msg->packet_type);
+ sftp_message_free(msg);
+
+ return NULL;
+}
+
+#endif /* WITH_SFTP */
+/* vim: set ts=2 sw=2 et cindent: */