/* * sftp.c - Secure FTP functions * * This file is part of the SSH Library * * Copyright (c) 2005-2008 by Aris Adamantiadis * Copyright (c) 2008-2018 by Andreas Schneider * * 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 "config.h" #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif /* HAVE_SYS_TIME_H */ #include #include #include #ifndef _WIN32 #include #include #endif #include "libssh/priv.h" #include "libssh/ssh2.h" #include "libssh/sftp.h" #include "libssh/sftp_priv.h" #include "libssh/buffer.h" #include "libssh/channels.h" #include "libssh/session.h" #include "libssh/misc.h" #include "libssh/bytearray.h" #ifdef WITH_SFTP struct sftp_ext_struct { uint32_t count; char **name; char **data; }; static sftp_ext sftp_ext_new(void) { sftp_ext ext; ext = calloc(1, sizeof(struct sftp_ext_struct)); if (ext == NULL) { return NULL; } return ext; } static void sftp_ext_free(sftp_ext ext) { size_t i; if (ext == NULL) { return; } if (ext->count > 0) { if (ext->name != NULL) { for (i = 0; i < ext->count; i++) { SAFE_FREE(ext->name[i]); } SAFE_FREE(ext->name); } if (ext->data != NULL) { for (i = 0; i < ext->count; i++) { SAFE_FREE(ext->data[i]); } SAFE_FREE(ext->data); } } SAFE_FREE(ext); } sftp_session sftp_new(ssh_session session) { sftp_session sftp; if (session == NULL) { return NULL; } sftp = calloc(1, sizeof(struct sftp_session_struct)); if (sftp == NULL) { ssh_set_error_oom(session); return NULL; } sftp->ext = sftp_ext_new(); if (sftp->ext == NULL) { ssh_set_error_oom(session); goto error; } sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); if (sftp->read_packet == NULL) { ssh_set_error_oom(session); goto error; } sftp->read_packet->payload = ssh_buffer_new(); if (sftp->read_packet->payload == NULL) { ssh_set_error_oom(session); goto error; } sftp->session = session; sftp->channel = ssh_channel_new(session); if (sftp->channel == NULL) { ssh_set_error_oom(session); goto error; } if (ssh_channel_open_session(sftp->channel)) { goto error; } if (ssh_channel_request_sftp(sftp->channel)) { goto error; } return sftp; error: if (sftp->ext != NULL) { sftp_ext_free(sftp->ext); } if (sftp->channel != NULL) { ssh_channel_free(sftp->channel); } if (sftp->read_packet != NULL) { if (sftp->read_packet->payload != NULL) { SSH_BUFFER_FREE(sftp->read_packet->payload); } SAFE_FREE(sftp->read_packet); } SAFE_FREE(sftp); return NULL; } sftp_session sftp_new_channel(ssh_session session, ssh_channel channel) { sftp_session sftp = NULL; if (session == NULL) { return NULL; } sftp = calloc(1, sizeof(struct sftp_session_struct)); if (sftp == NULL) { ssh_set_error_oom(session); return NULL; } sftp->ext = sftp_ext_new(); if (sftp->ext == NULL) { ssh_set_error_oom(session); goto error; } sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); if (sftp->read_packet == NULL) { ssh_set_error_oom(session); goto error; } sftp->read_packet->payload = ssh_buffer_new(); if (sftp->read_packet->payload == NULL) { ssh_set_error_oom(session); goto error; } sftp->session = session; sftp->channel = channel; return sftp; error: if (sftp->ext != NULL) { sftp_ext_free(sftp->ext); } if (sftp->read_packet != NULL) { if (sftp->read_packet->payload != NULL) { SSH_BUFFER_FREE(sftp->read_packet->payload); } SAFE_FREE(sftp->read_packet); } SAFE_FREE(sftp); return NULL; } #ifdef WITH_SERVER sftp_session sftp_server_new(ssh_session session, ssh_channel chan) { sftp_session sftp = NULL; sftp = calloc(1, sizeof(struct sftp_session_struct)); if (sftp == NULL) { ssh_set_error_oom(session); return NULL; } sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); if (sftp->read_packet == NULL) { goto error; } sftp->read_packet->payload = ssh_buffer_new(); if (sftp->read_packet->payload == NULL) { goto error; } sftp->session = session; sftp->channel = chan; return sftp; error: ssh_set_error_oom(session); if (sftp->read_packet != NULL) { if (sftp->read_packet->payload != NULL) { SSH_BUFFER_FREE(sftp->read_packet->payload); } SAFE_FREE(sftp->read_packet); } SAFE_FREE(sftp); return NULL; } /* @deprecated in favor of sftp_server_new() and callbacks based sftp server */ int sftp_server_init(sftp_session sftp) { ssh_session session = sftp->session; sftp_client_message msg = NULL; int rc; /* handles setting the sftp->client_version */ msg = sftp_get_client_message(sftp); if (msg == NULL) { return -1; } if (msg->type != SSH_FXP_INIT) { ssh_set_error(session, SSH_FATAL, "Packet read of type %d instead of SSH_FXP_INIT", msg->type); return -1; } SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT"); rc = sftp_reply_version(msg); if (rc != SSH_OK) { ssh_set_error(session, SSH_FATAL, "Failed to process the SSH_FXP_INIT message"); return -1; } return 0; } void sftp_server_free(sftp_session sftp) { sftp_request_queue ptr; if (sftp == NULL) { return; } ptr = sftp->queue; while(ptr) { sftp_request_queue old; sftp_message_free(ptr->message); old = ptr->next; SAFE_FREE(ptr); ptr = old; } SAFE_FREE(sftp->handles); SSH_BUFFER_FREE(sftp->read_packet->payload); SAFE_FREE(sftp->read_packet); sftp_ext_free(sftp->ext); SAFE_FREE(sftp); } #endif /* WITH_SERVER */ void sftp_free(sftp_session sftp) { sftp_request_queue ptr; if (sftp == NULL) { return; } if (sftp->channel != NULL) { 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->channel = NULL; } SAFE_FREE(sftp->handles); SSH_BUFFER_FREE(sftp->read_packet->payload); SAFE_FREE(sftp->read_packet); sftp_ext_free(sftp->ext); sftp_limits_free(sftp->limits); SAFE_FREE(sftp); } /* @internal * Process the incoming data and copy them from the SSH packet buffer to the * SFTP packet buffer. * @returns number of decoded bytes. */ int sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) { sftp_packet packet = sftp->read_packet; int nread; int payload_len; unsigned int data_offset; int to_read, rc; if (packet->sftp == NULL) { packet->sftp = sftp; } data_offset = sizeof(uint32_t) + sizeof(uint8_t); /* not enough bytes to read */ if (len < data_offset) { return SSH_ERROR; } payload_len = PULL_BE_U32(data, 0); packet->type = PULL_BE_U8(data, 4); /* We should check the legality of payload length */ if (payload_len + sizeof(uint32_t) > len || payload_len < 0) { return SSH_ERROR; } to_read = payload_len - sizeof(uint8_t); rc = ssh_buffer_add_data(packet->payload, (void*)((uint8_t *)data + data_offset), to_read); if (rc != 0) { return SSH_ERROR; } nread = ssh_buffer_get_len(packet->payload); /* We should check if we copied the whole data */ if (nread != to_read) { return SSH_ERROR; } /* * We should return how many bytes we decoded, including packet length header * and the payload length. */ return payload_len + sizeof(uint32_t); } /* Get the last sftp error */ int sftp_get_error(sftp_session sftp) { if (sftp == NULL) { return -1; } return sftp->errnum; } static sftp_limits_t sftp_limits_use_extension(sftp_session sftp); static sftp_limits_t sftp_limits_use_default(sftp_session sftp); /* Initialize the sftp session with the server. */ int sftp_init(sftp_session sftp) { sftp_packet packet = NULL; ssh_buffer buffer = NULL; char *ext_name = NULL; char *ext_data = NULL; uint32_t version; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); if (rc == SSH_ERROR) { SSH_BUFFER_FREE(buffer); return -1; } SSH_BUFFER_FREE(buffer); packet = sftp_packet_read(sftp); if (packet == NULL) { 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); return -1; } /* TODO: are we sure there are 4 bytes ready? */ rc = ssh_buffer_unpack(packet->payload, "d", &version); if (rc != SSH_OK) { ssh_set_error(sftp->session, SSH_FATAL, "Unable to unpack SSH_FXP_VERSION packet"); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } SSH_LOG(SSH_LOG_DEBUG, "SFTP server version %" PRIu32, version); rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); while (rc == SSH_OK) { uint32_t count = sftp->ext->count; char **tmp; rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); if (rc == SSH_ERROR) { break; } SSH_LOG(SSH_LOG_DEBUG, "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); sftp_set_error(sftp, SSH_FX_FAILURE); 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); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } tmp[count - 1] = ext_data; sftp->ext->data = tmp; sftp->ext->count = count; rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); } sftp->version = sftp->server_version = (int)version; /* Set the limits */ rc = sftp_extension_supported(sftp, "limits@openssh.com", "1"); if (rc == 1) { /* Get the ssh and sftp errors */ const char *static_ssh_err_msg = ssh_get_error(sftp->session); int ssh_err_code = ssh_get_error_code(sftp->session); int sftp_err_code = sftp_get_error(sftp); char *ssh_err_msg = strdup(static_ssh_err_msg); if (ssh_err_msg == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } sftp->limits = sftp_limits_use_extension(sftp); if (sftp->limits == NULL) { /* fallback and use the default limits on failure */ SSH_LOG(SSH_LOG_TRACE, "Failed to get the limits from a server claiming to " "support the limits@openssh.com extension, falling back " "and using the default limits"); /* Restore the sftp and ssh errors to their previous state */ ssh_set_error(sftp->session, ssh_err_code, "%s", ssh_err_msg); sftp_set_error(sftp, sftp_err_code); SAFE_FREE(ssh_err_msg); sftp->limits = sftp_limits_use_default(sftp); if (sftp->limits == NULL) { return -1; } } else { SAFE_FREE(ssh_err_msg); } } else { sftp->limits = sftp_limits_use_default(sftp); if (sftp->limits == NULL) { return -1; } } 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); return NULL; } if (idx > sftp->ext->count) { ssh_set_error_invalid(sftp->session); 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); return NULL; } if (idx > sftp->ext->count) { ssh_set_error_invalid(sftp->session); return NULL; } return sftp->ext->data[idx]; } int sftp_extension_supported(sftp_session sftp, const char *name, const char *data) { size_t i, n; if (sftp == NULL || name == NULL || data == NULL) { return 0; } n = sftp_extensions_get_count(sftp); for (i = 0; i < n; i++) { const char *ext_name = sftp_extensions_get_name(sftp, i); const char *ext_data = sftp_extensions_get_data(sftp, i); if (ext_name != NULL && ext_data != NULL && strcmp(ext_name, name) == 0 && strcmp(ext_data, data) == 0) { return 1; } } return 0; } 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 = calloc(1, sizeof(struct sftp_file_struct)); if (file == NULL) { ssh_set_error_oom(msg->sftp->session); sftp_set_error(msg->sftp, SSH_FX_FAILURE); return NULL; } file->handle = ssh_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); sftp_set_error(msg->sftp, SSH_FX_FAILURE); 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_buffer payload = NULL; uint32_t id; int rc; if (sftp == NULL) { return NULL; } payload = ssh_buffer_new(); if (payload == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(payload, "ds", id, path); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(payload); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_OPENDIR, payload); SSH_BUFFER_FREE(payload); if (rc < 0) { return NULL; } 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 = calloc(1, sizeof(struct sftp_dir_struct)); if (dir == NULL) { ssh_set_error_oom(sftp->session); free(file); return NULL; } 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; } /* 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; int rc; if (dir->buffer == NULL) { payload = ssh_buffer_new(); if (payload == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(payload, "dS", id, dir->handle); if (rc != 0) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); SSH_BUFFER_FREE(payload); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_READDIR, payload); SSH_BUFFER_FREE(payload); if (rc < 0) { return NULL; } SSH_LOG(SSH_LOG_PACKET, "Sent a ssh_fxp_readdir with id %" PRIu32, 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: %" PRIu32, status->status); status_msg_free(status); return NULL; case SSH_FXP_NAME: ssh_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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); 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(SSH_LOG_DEBUG, "Count is %" PRIu32, 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; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dS", id, handle); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } rc = sftp_packet_write(sftp, SSH_FXP_CLOSE, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return -1; } 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return -1; } /* Close an open file handle. */ int sftp_close(sftp_file file){ int err = SSH_NO_ERROR; if (file == NULL) { return err; } 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_buffer buffer = NULL; sftp_attributes stat_data; uint32_t sftp_flags = 0; uint32_t id; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); return NULL; } ZERO_STRUCT(attr); attr.permissions = mode; attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; if ((flags & O_RDWR) == O_RDWR) { sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ); } else if ((flags & O_WRONLY) == O_WRONLY) { sftp_flags |= SSH_FXF_WRITE; } else { sftp_flags |= SSH_FXF_READ; } if ((flags & O_CREAT) == O_CREAT) sftp_flags |= SSH_FXF_CREAT; if ((flags & O_TRUNC) == O_TRUNC) sftp_flags |= SSH_FXF_TRUNC; if ((flags & O_EXCL) == O_EXCL) sftp_flags |= SSH_FXF_EXCL; if ((flags & O_APPEND) == O_APPEND) { sftp_flags |= SSH_FXF_APPEND; } SSH_LOG(SSH_LOG_PACKET, "Opening file %s with sftp flags %" PRIx32, file, sftp_flags); id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dsd", id, file, sftp_flags); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = buffer_add_attributes(buffer, &attr); if (rc < 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_OPEN, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } 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); if (handle == NULL) { return NULL; } sftp_message_free(msg); if ((flags & O_APPEND) == O_APPEND) { stat_data = sftp_stat(sftp, file); if (stat_data == NULL) { sftp_close(handle); return NULL; } if ((stat_data->flags & SSH_FILEXFER_ATTR_SIZE) != SSH_FILEXFER_ATTR_SIZE) { ssh_set_error(sftp->session, SSH_FATAL, "Cannot open in append mode. Unknown file size."); sftp_attributes_free(stat_data); sftp_close(handle); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } handle->offset = stat_data->size; sftp_attributes_free(stat_data); } return handle; default: ssh_set_error(sftp->session, SSH_FATAL, "Received message %d during open!", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } 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; sftp_message msg = NULL; sftp_status_message status; ssh_string datastring; size_t datalen; ssh_buffer buffer; uint32_t id; int rc; if (handle == NULL) { return -1; } sftp = handle->sftp; if (handle->eof) { return 0; } /* * limit the reads to the maximum specified in Section 3 of * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 * or to the values provided by the limits@openssh.com extension. * * TODO: We should iterate over the blocks rather than writing less than * requested to provide less surprises to the calling applications. */ if (count > sftp->limits->max_read_length) { count = sftp->limits->max_read_length; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); return -1; } id = sftp_get_new_id(handle->sftp); rc = ssh_buffer_pack(buffer, "dSqd", id, handle->handle, handle->offset, count); if (rc != SSH_OK){ ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); 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 = ssh_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; } datalen = ssh_string_len(datastring); if (datalen > count) { ssh_set_error(sftp->session, SSH_FATAL, "Received a too big DATA packet from sftp server: " "%zu and asked for %zu", datalen, count); SSH_STRING_FREE(datastring); return -1; } handle->offset += (uint64_t)datalen; memcpy(buf, ssh_string_data(datastring), datalen); SSH_STRING_FREE(datastring); return datalen; default: ssh_set_error(sftp->session, SSH_FATAL, "Received message %d during read!", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); 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; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dSqd", id, file->handle, file->offset, len); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); 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 */ 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; sftp_message msg = NULL; sftp_status_message status; ssh_string datastring; int err = SSH_OK; uint32_t len; if (file == NULL) { return SSH_ERROR; } sftp = file->sftp; if (file->eof) { 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 */ 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) { 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); err = SSH_ERROR; } else { file->eof = 1; } status_msg_free(status); return err; case SSH_FXP_DATA: datastring = ssh_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 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 %" PRIu32, ssh_string_len(datastring), size); SSH_STRING_FREE(datastring); return SSH_ERROR; } len = ssh_string_len(datastring); /* Update the offset with the correct value */ file->offset = file->offset - (size - len); memcpy(data, ssh_string_data(datastring), len); SSH_STRING_FREE(datastring); return len; default: ssh_set_error(sftp->session,SSH_FATAL,"Received message %d during read!",msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); return SSH_ERROR; } return SSH_ERROR; } ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { sftp_session sftp; sftp_message msg = NULL; sftp_status_message status; ssh_buffer buffer; uint32_t id; ssize_t len; size_t packetlen; int rc; if (file == NULL) { return -1; } sftp = file->sftp; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(file->sftp); /* * limit the writes to the maximum specified in Section 3 of * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 * or to the values provided by the limits@openssh.com extension. * * TODO: We should iterate over the blocks rather than writing less than * requested to provide less surprises to the calling applications. */ if (count > sftp->limits->max_write_length) { count = sftp->limits->max_write_length; } rc = ssh_buffer_pack(buffer, "dSqdP", id, file->handle, file->offset, count, /* len of datastring */ (size_t)count, buf); if (rc != SSH_OK){ ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } 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 ((size_t)len != packetlen) { SSH_LOG(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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); 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; file->eof = 0; return 0; } int sftp_seek64(sftp_file file, uint64_t new_offset) { if (file == NULL) { return -1; } file->offset = new_offset; file->eof = 0; 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; file->eof = 0; } /* code written by Nick */ int sftp_unlink(sftp_session sftp, const char *file) { sftp_status_message status = NULL; sftp_message msg = NULL; ssh_buffer buffer; uint32_t id; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, file); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) { SSH_BUFFER_FREE(buffer); return -1; } 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } 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_buffer buffer; uint32_t id; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, directory); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) { SSH_BUFFER_FREE(buffer); return -1; } SSH_BUFFER_FREE(buffer); 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; 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } 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; uint32_t id; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } ZERO_STRUCT(attr); attr.permissions = mode; attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, directory); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } rc = buffer_add_attributes(buffer, &attr); if (rc < 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } rc = sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return -1; } 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; 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } 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 = NULL; uint32_t id; const char *extension_name = "posix-rename@openssh.com"; int request_type; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); /* * posix-rename@openssh.com extension will be used * if it is supported by sftp */ if (sftp_extension_supported(sftp, extension_name, "1")) { rc = ssh_buffer_pack(buffer, "dsss", id, extension_name, original, newname); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } request_type = SSH_FXP_EXTENDED; } else { rc = ssh_buffer_pack(buffer, "dss", id, original, newname); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } if (sftp->version >= 4) { /* * POSIX rename atomically replaces newpath, * we should do the same only available on >=v4 */ ssh_buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE); } request_type = SSH_FXP_RENAME; } rc = sftp_packet_write(sftp, request_type, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return -1; } 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } 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; sftp_message msg = NULL; sftp_status_message status = NULL; int rc; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, file); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } rc = buffer_add_attributes(buffer, attr); if (rc != 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } rc = sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return -1; } 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } 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_buffer buffer; uint32_t id; int rc; if (sftp == NULL) return -1; if (target == NULL || dest == NULL) { ssh_set_error_invalid(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); /* The OpenSSH sftp server has order of the arguments reversed, see the * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL * for more information */ if (ssh_get_openssh_version(sftp->session)) { rc = ssh_buffer_pack(buffer, "dss", id, target, dest); } else { rc = ssh_buffer_pack(buffer, "dss", id, dest, target); } if (rc != SSH_OK){ ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) { SSH_BUFFER_FREE(buffer); return -1; } SSH_BUFFER_FREE(buffer); 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return -1; } char *sftp_readlink(sftp_session sftp, const char *path) { sftp_status_message status = NULL; sftp_message msg = NULL; ssh_buffer buffer; uint32_t id; int rc; if (sftp == NULL) { return NULL; } if (path == NULL) { ssh_set_error_invalid(sftp); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } if (sftp->version < 3){ ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, path); if (rc < 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_READLINK, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } while (msg == NULL) { if (sftp_read_and_dispatch(sftp) < 0) { return NULL; } msg = sftp_dequeue(sftp, id); } if (msg->packet_type == SSH_FXP_NAME) { uint32_t ignored = 0; char *lnk = NULL; rc = ssh_buffer_unpack(msg->payload, "ds", &ignored, &lnk); sftp_message_free(msg); if (rc != SSH_OK) { ssh_set_error(sftp->session, SSH_ERROR, "Failed to retrieve link"); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } 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; } sftp_set_error(sftp, status->status); 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return NULL; } int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath) { ssh_buffer buffer = NULL; uint32_t id; const char *extension_name = "hardlink@openssh.com"; sftp_status_message status = NULL; sftp_message msg = NULL; int rc; if (sftp == NULL) { return -1; } if (oldpath == NULL || newpath == NULL) { ssh_set_error_invalid(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dsss", id, extension_name, oldpath, newpath); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return -1; } 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 create hardlink", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return -1; } static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { sftp_statvfs_t statvfs; int rc; statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); if (statvfs == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = ssh_buffer_unpack(buf, "qqqqqqqqqqq", &statvfs->f_bsize, /* file system block size */ &statvfs->f_frsize, /* fundamental fs block size */ &statvfs->f_blocks, /* number of blocks (unit f_frsize) */ &statvfs->f_bfree, /* free blocks in file system */ &statvfs->f_bavail, /* free blocks for non-root */ &statvfs->f_files, /* total file inodes */ &statvfs->f_ffree, /* free file inodes */ &statvfs->f_favail, /* free file inodes for to non-root */ &statvfs->f_fsid, /* file system id */ &statvfs->f_flag, /* bit mask of f_flag values */ &statvfs->f_namemax/* maximum filename length */ ); if (rc != SSH_OK) { SAFE_FREE(statvfs); ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure"); sftp_set_error(sftp, SSH_FX_FAILURE); 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_buffer buffer; uint32_t id; int rc; if (sftp == NULL) return NULL; if (path == NULL) { ssh_set_error_invalid(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } if (sftp->version < 3){ ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dss", id, "statvfs@openssh.com", path); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } 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; } sftp_set_error(sftp, status->status); 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return NULL; } int sftp_fsync(sftp_file file) { sftp_session sftp; sftp_message msg = NULL; ssh_buffer buffer; uint32_t id; int rc; if (file == NULL) { return -1; } sftp = file->sftp; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dsS", id, "fsync@openssh.com", file->handle); if (rc < 0) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); goto done; } rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); if (rc < 0) { ssh_set_error_oom(sftp->session); goto done; } do { rc = sftp_read_and_dispatch(sftp); if (rc < 0) { ssh_set_error_oom(sftp->session); rc = -1; goto done; } msg = sftp_dequeue(sftp, id); } while (msg == NULL); /* By specification, this command only returns SSH_FXP_STATUS */ if (msg->packet_type == SSH_FXP_STATUS) { sftp_status_message status; status = parse_status_msg(msg); sftp_message_free(msg); if (status == NULL) { rc = -1; goto done; } sftp_set_error(sftp, status->status); switch (status->status) { case SSH_FX_OK: /* SUCCESS, LEAVE */ status_msg_free(status); rc = 0; goto done; 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); rc = -1; goto done; } else { ssh_set_error(sftp->session, SSH_FATAL, "Received message %d when attempting to set stats", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } rc = -1; done: SSH_BUFFER_FREE(buffer); return rc; } sftp_statvfs_t sftp_fstatvfs(sftp_file file) { sftp_status_message status = NULL; sftp_message msg = NULL; sftp_session sftp; ssh_buffer buffer; uint32_t id; int rc; if (file == NULL) { return NULL; } sftp = file->sftp; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dsS", id, "fstatvfs@openssh.com", file->handle); if (rc < 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } 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; } sftp_set_error(sftp, status->status); 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return NULL; } void sftp_statvfs_free(sftp_statvfs_t statvfs) { if (statvfs == NULL) { return; } SAFE_FREE(statvfs); } static sftp_limits_t sftp_limits_new(void) { return calloc(1, sizeof(struct sftp_limits_struct)); } static sftp_limits_t sftp_parse_limits(sftp_session sftp, ssh_buffer buf) { sftp_limits_t limits = NULL; int rc; limits = sftp_limits_new(); if (limits == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = ssh_buffer_unpack(buf, "qqqq", &limits->max_packet_length, /** maximum number of bytes in a single sftp packet */ &limits->max_read_length, /** maximum length in a SSH_FXP_READ packet */ &limits->max_write_length, /** maximum length in a SSH_FXP_WRITE packet */ &limits->max_open_handles /** maximum number of active handles allowed by server */ ); if (rc != SSH_OK) { SAFE_FREE(limits); ssh_set_error(sftp->session, SSH_FATAL, "Invalid limits structure"); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } return limits; } static sftp_limits_t sftp_limits_use_extension(sftp_session sftp) { sftp_status_message status = NULL; sftp_message msg = NULL; ssh_buffer buffer; uint32_t id; int rc; if (sftp == NULL) return NULL; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, "limits@openssh.com"); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } 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_limits_t limits = sftp_parse_limits(sftp, msg->payload); sftp_message_free(msg); if (limits == NULL) { return NULL; } return limits; } 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; } sftp_set_error(sftp, status->status); 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 limits", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return NULL; } static sftp_limits_t sftp_limits_use_default(sftp_session sftp) { sftp_limits_t limits = NULL; if (sftp == NULL) { return NULL; } limits = sftp_limits_new(); if (limits == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } limits->max_packet_length = 34000; limits->max_read_length = 32768; limits->max_write_length = 32768; /* * For max-open-handles field openssh says : * If the server doesn't enforce a specific limit, then the field may * be set to 0. This implies the server relies on the OS to enforce * limits (e.g. available memory or file handles), and such limits * might be dynamic. The client SHOULD take care to not try to exceed * reasonable limits. */ limits->max_open_handles = 0; return limits; } sftp_limits_t sftp_limits(sftp_session sftp) { sftp_limits_t limits = NULL; if (sftp == NULL) { return NULL; } if (sftp->limits == NULL) { ssh_set_error(sftp, SSH_FATAL, "Uninitialized sftp session, " "sftp_init() was not called or failed"); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } limits = sftp_limits_new(); if (limits == NULL) { ssh_set_error_oom(sftp); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } memcpy(limits, sftp->limits, sizeof(struct sftp_limits_struct)); return limits; } void sftp_limits_free(sftp_limits_t limits) { if (limits == NULL) { return; } SAFE_FREE(limits); } /* 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_buffer buffer; uint32_t id; int rc; if (sftp == NULL) return NULL; if (path == NULL) { ssh_set_error_invalid(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, path); if (rc < 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } while (msg == NULL) { if (sftp_read_and_dispatch(sftp) < 0) { return NULL; } msg = sftp_dequeue(sftp, id); } if (msg->packet_type == SSH_FXP_NAME) { uint32_t ignored = 0; char *cname = NULL; rc = ssh_buffer_unpack(msg->payload, "ds", &ignored, &cname); sftp_message_free(msg); if (rc != SSH_OK) { ssh_set_error(sftp->session, SSH_ERROR, "Failed to parse canonicalized path"); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } 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; } sftp_set_error(sftp, status->status); 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } 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_buffer buffer; uint32_t id; int rc; if (sftp == NULL) { return NULL; } if (path == NULL) { ssh_set_error_invalid(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, path); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, param, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } while (msg == NULL) { if (sftp_read_and_dispatch(sftp) < 0) { return NULL; } msg = sftp_dequeue(sftp, id); } if (msg->packet_type == SSH_FXP_ATTRS) { sftp_attributes attr = sftp_parse_attr(sftp, msg->payload, 0); sftp_message_free(msg); return attr; } 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); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); 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; int rc; if (file == NULL) { return NULL; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(file->sftp->session); sftp_set_error(file->sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(file->sftp); rc = ssh_buffer_pack(buffer, "dS", id, file->handle); if (rc != SSH_OK) { ssh_set_error_oom(file->sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(file->sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } 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){ sftp_attributes attr = sftp_parse_attr(file->sftp, msg->payload, 0); sftp_message_free(msg); return attr; } 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(file->sftp, status->status); 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); sftp_set_error(file->sftp, SSH_FX_BAD_MESSAGE); return NULL; } char *sftp_expand_path(sftp_session sftp, const char *path) { sftp_status_message status = NULL; sftp_message msg = NULL; ssh_buffer buffer = NULL; uint32_t id; int rc; if (sftp == NULL) { return NULL; } if (path == NULL) { ssh_set_error(sftp->session, SSH_FATAL, "NULL received as an argument instead of the path to expand"); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dss", id, "expand-path@openssh.com", path); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } while (msg == NULL) { rc = sftp_read_and_dispatch(sftp); if (rc < 0) { return NULL; } msg = sftp_dequeue(sftp, id); } if (msg->packet_type == SSH_FXP_NAME) { uint32_t ignored = 0; char *cname = NULL; rc = ssh_buffer_unpack(msg->payload, "ds", &ignored, &cname); sftp_message_free(msg); if (rc != SSH_OK) { ssh_set_error(sftp->session, SSH_ERROR, "Failed to parse expanded path"); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } return cname; } 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); } else { ssh_set_error(sftp->session, SSH_FATAL, "Received message %d when attempting to expand path", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } return NULL; } #endif /* WITH_SFTP */