mirror of
https://git.libssh.org/projects/libssh.git
synced 2025-11-26 01:03:15 +03:00
This changes amends f561e6bcb3 which
introduces same check in one place, but miss it in other two places.
We encountered this issue with qemu using SFTP to transfer large
data chunks and in some cases, the file transfer was interrupted
without any reason. From the debug messages, it showed up that
last part of data message/packet was not handled in the time
of the sftp_read() call, therefore the ssh_channel_read() returned
zero (there was no more data to read yet), which made the whole
transfer fail hard instead of retrying later.
The proposed change is reusing the code from previously referenced
commit also in the other places.
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
3377 lines
82 KiB
C
3377 lines
82 KiB
C
/*
|
|
* 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 <asn@cryptomilk.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 "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <limits.h>
|
|
|
|
#ifndef _WIN32
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#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
|
|
|
|
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 = calloc(1, sizeof(struct sftp_ext_struct));
|
|
if (ext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
SAFE_FREE(sftp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
sftp->session = session;
|
|
sftp->channel = ssh_channel_new(session);
|
|
if (sftp->channel == NULL) {
|
|
sftp_ext_free(sftp->ext);
|
|
SAFE_FREE(sftp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (ssh_channel_open_session(sftp->channel)) {
|
|
ssh_channel_free(sftp->channel);
|
|
sftp_ext_free(sftp->ext);
|
|
SAFE_FREE(sftp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (ssh_channel_request_sftp(sftp->channel)) {
|
|
sftp_free(sftp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return sftp;
|
|
}
|
|
|
|
sftp_session sftp_new_channel(ssh_session session, ssh_channel channel){
|
|
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);
|
|
SAFE_FREE(sftp);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
sftp->session = session;
|
|
sftp->channel = channel;
|
|
|
|
return sftp;
|
|
}
|
|
|
|
#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->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;
|
|
|
|
packet = sftp_packet_read(sftp);
|
|
if (packet == NULL) {
|
|
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);
|
|
return -1;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT");
|
|
|
|
ssh_buffer_get_u32(packet->payload, &version);
|
|
version = ntohl(version);
|
|
SSH_LOG(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);
|
|
return -1;
|
|
}
|
|
|
|
if (ssh_buffer_add_u32(reply, ntohl(LIBSFTP_VERSION)) < 0) {
|
|
ssh_set_error_oom(session);
|
|
ssh_buffer_free(reply);
|
|
return -1;
|
|
}
|
|
|
|
if (sftp_packet_write(sftp, SSH_FXP_VERSION, reply) < 0) {
|
|
ssh_buffer_free(reply);
|
|
return -1;
|
|
}
|
|
ssh_buffer_free(reply);
|
|
|
|
SSH_LOG(SSH_LOG_RARE, "Server version sent");
|
|
|
|
if (version > LIBSFTP_VERSION) {
|
|
sftp->version = LIBSFTP_VERSION;
|
|
} else {
|
|
sftp->version=version;
|
|
}
|
|
|
|
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);
|
|
|
|
SAFE_FREE(sftp->handles);
|
|
|
|
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 (ssh_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 (ssh_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(payload),
|
|
ssh_buffer_get_len(payload));
|
|
if (size < 0) {
|
|
return -1;
|
|
} else if((uint32_t) size != ssh_buffer_get_len(payload)) {
|
|
SSH_LOG(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) {
|
|
unsigned char buffer[MAX_BUF_SIZE];
|
|
sftp_packet packet = NULL;
|
|
uint32_t tmp;
|
|
size_t size;
|
|
int r, s, is_eof;
|
|
|
|
packet = calloc(1, 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;
|
|
}
|
|
|
|
r = ssh_buffer_allocate_size(packet->payload, 4);
|
|
if (r < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
goto error;
|
|
}
|
|
r=0;
|
|
do {
|
|
// read from channel until 4 bytes have been read or an error occurs
|
|
s=ssh_channel_read(sftp->channel, buffer+r, 4-r, 0);
|
|
if (s < 0) {
|
|
goto error;
|
|
} else if (s == 0) {
|
|
is_eof = ssh_channel_is_eof(sftp->channel);
|
|
if (is_eof) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
r += s;
|
|
}
|
|
} while (r<4);
|
|
ssh_buffer_add_data(packet->payload, buffer, r);
|
|
if (ssh_buffer_get_u32(packet->payload, &tmp) != sizeof(uint32_t)) {
|
|
ssh_set_error(sftp->session, SSH_FATAL, "Short sftp packet!");
|
|
goto error;
|
|
}
|
|
|
|
do {
|
|
r = ssh_channel_read(sftp->channel, buffer, 1, 0);
|
|
if (r < 0) {
|
|
goto error;
|
|
} else if (s == 0) {
|
|
is_eof = ssh_channel_is_eof(sftp->channel);
|
|
if (is_eof) {
|
|
goto error;
|
|
}
|
|
}
|
|
} while (r < 1);
|
|
ssh_buffer_add_data(packet->payload, buffer, r);
|
|
ssh_buffer_get_u8(packet->payload, &packet->type);
|
|
|
|
size = ntohl(tmp);
|
|
if (size == 0 || size > UINT32_MAX) {
|
|
return packet;
|
|
}
|
|
size--;
|
|
|
|
r = ssh_buffer_allocate_size(packet->payload, size);
|
|
if (r < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
goto error;
|
|
}
|
|
while (size > 0 && size < UINT_MAX) {
|
|
r=ssh_channel_read(sftp->channel,buffer,
|
|
sizeof(buffer)>size ? size:sizeof(buffer),0);
|
|
|
|
if (r < 0) {
|
|
/* TODO: check if there are cases where an error needs to be set here */
|
|
goto error;
|
|
} else if (r == 0) {
|
|
/* Retry the reading unless the remote was closed */
|
|
is_eof = ssh_channel_is_eof(sftp->channel);
|
|
if (is_eof) {
|
|
goto error;
|
|
}
|
|
} else if (ssh_buffer_add_data(packet->payload, buffer, r) == SSH_ERROR) {
|
|
ssh_set_error_oom(sftp->session);
|
|
goto error;
|
|
}
|
|
size -= r;
|
|
}
|
|
|
|
return packet;
|
|
error:
|
|
ssh_buffer_free(packet->payload);
|
|
SAFE_FREE(packet);
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
|
|
msg = calloc(1, sizeof(struct sftp_message_struct));
|
|
if (msg == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
return NULL;
|
|
}
|
|
|
|
msg->payload = ssh_buffer_new();
|
|
if (msg->payload == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
SAFE_FREE(msg);
|
|
return NULL;
|
|
}
|
|
msg->sftp = sftp;
|
|
|
|
return msg;
|
|
}
|
|
|
|
static void sftp_message_free(sftp_message msg) {
|
|
if (msg == NULL) {
|
|
return;
|
|
}
|
|
|
|
ssh_buffer_free(msg->payload);
|
|
SAFE_FREE(msg);
|
|
}
|
|
|
|
static sftp_message sftp_get_message(sftp_packet packet) {
|
|
sftp_session sftp = packet->sftp;
|
|
sftp_message msg = NULL;
|
|
int rc;
|
|
|
|
msg = sftp_message_new(sftp);
|
|
if (msg == NULL) {
|
|
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);
|
|
return NULL;
|
|
}
|
|
|
|
rc = ssh_buffer_unpack(packet->payload, "d", &msg->id);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error(packet->sftp->session, SSH_FATAL,
|
|
"Invalid packet %d: no ID", packet->type);
|
|
sftp_message_free(msg);
|
|
return NULL;
|
|
}
|
|
|
|
SSH_LOG(SSH_LOG_PACKET,
|
|
"Packet with id %d type %d",
|
|
msg->id,
|
|
msg->packet_type);
|
|
|
|
if (ssh_buffer_add_data(msg->payload, ssh_buffer_get(packet->payload),
|
|
ssh_buffer_get_len(packet->payload)) < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
sftp_message_free(msg);
|
|
return NULL;
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
static int sftp_read_and_dispatch(sftp_session sftp) {
|
|
sftp_packet packet = NULL;
|
|
sftp_message msg = NULL;
|
|
|
|
packet = sftp_packet_read(sftp);
|
|
if (packet == NULL) {
|
|
return -1; /* something nasty happened reading the packet */
|
|
}
|
|
|
|
msg = sftp_get_message(packet);
|
|
sftp_packet_free(packet);
|
|
if (msg == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (sftp_enqueue(sftp, msg) < 0) {
|
|
sftp_message_free(msg);
|
|
return -1;
|
|
}
|
|
|
|
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;
|
|
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);
|
|
return -1;
|
|
}
|
|
|
|
rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
return -1;
|
|
}
|
|
if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) {
|
|
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);
|
|
sftp_packet_free(packet);
|
|
return -1;
|
|
}
|
|
|
|
/* TODO: are we sure there are 4 bytes ready? */
|
|
rc = ssh_buffer_unpack(packet->payload, "d", &version);
|
|
if (rc != SSH_OK){
|
|
return -1;
|
|
}
|
|
SSH_LOG(SSH_LOG_RARE,
|
|
"SFTP server version %d",
|
|
version);
|
|
rc = ssh_buffer_unpack(packet->payload, "s", &ext_name);
|
|
while (rc == SSH_OK) {
|
|
int count = sftp->ext->count;
|
|
char **tmp;
|
|
|
|
rc = ssh_buffer_unpack(packet->payload, "s", &ext_data);
|
|
if (rc == SSH_ERROR) {
|
|
break;
|
|
}
|
|
|
|
SSH_LOG(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);
|
|
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);
|
|
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_packet_free(packet);
|
|
|
|
sftp->version = sftp->server_version = version;
|
|
|
|
|
|
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) {
|
|
int 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_request_queue request_queue_new(sftp_message msg) {
|
|
sftp_request_queue queue = NULL;
|
|
|
|
queue = calloc(1, sizeof(struct sftp_request_queue_struct));
|
|
if (queue == NULL) {
|
|
ssh_set_error_oom(msg->sftp->session);
|
|
return NULL;
|
|
}
|
|
|
|
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(SSH_LOG_PACKET,
|
|
"Queued msg id %d type %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(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;
|
|
int rc;
|
|
|
|
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 = calloc(1, sizeof(struct sftp_status_message_struct));
|
|
if (status == NULL) {
|
|
ssh_set_error_oom(msg->sftp->session);
|
|
return NULL;
|
|
}
|
|
|
|
status->id = msg->id;
|
|
rc = ssh_buffer_unpack(msg->payload, "d",
|
|
&status->status);
|
|
if (rc != SSH_OK){
|
|
SAFE_FREE(status);
|
|
ssh_set_error(msg->sftp->session, SSH_FATAL,
|
|
"Invalid SSH_FXP_STATUS message");
|
|
return NULL;
|
|
}
|
|
rc = ssh_buffer_unpack(msg->payload, "ss",
|
|
&status->errormsg,
|
|
&status->langmsg);
|
|
|
|
if(rc != SSH_OK && msg->sftp->version >=3){
|
|
/* These are mandatory from version 3 */
|
|
SAFE_FREE(status);
|
|
ssh_set_error(msg->sftp->session, SSH_FATAL,
|
|
"Invalid SSH_FXP_STATUS message");
|
|
return NULL;
|
|
}
|
|
if (status->errormsg == NULL)
|
|
status->errormsg = strdup("No error message in packet");
|
|
if (status->langmsg == NULL)
|
|
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;
|
|
}
|
|
|
|
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 = calloc(1, sizeof(struct sftp_file_struct));
|
|
if (file == NULL) {
|
|
ssh_set_error_oom(msg->sftp->session);
|
|
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);
|
|
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;
|
|
int rc;
|
|
|
|
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);
|
|
rc = ssh_buffer_allocate_size(payload,
|
|
sizeof(uint32_t) * 2 + ssh_string_len(path_s));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(payload);
|
|
ssh_string_free(path_s);
|
|
return NULL;
|
|
}
|
|
if (ssh_buffer_add_u32(payload, htonl(id)) < 0 ||
|
|
ssh_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 = 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;
|
|
}
|
|
|
|
/*
|
|
* 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 = calloc(1, sizeof(struct sftp_attributes_struct));
|
|
if (attr == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
return NULL;
|
|
}
|
|
|
|
/* This isn't really a loop, but it is like a try..catch.. */
|
|
do {
|
|
if (ssh_buffer_get_u32(buf, &flags) != 4) {
|
|
break;
|
|
}
|
|
|
|
flags = ntohl(flags);
|
|
attr->flags = flags;
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_SIZE) {
|
|
if (ssh_buffer_get_u64(buf, &attr->size) != 8) {
|
|
break;
|
|
}
|
|
attr->size = ntohll(attr->size);
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) {
|
|
owner = ssh_buffer_get_ssh_string(buf);
|
|
if (owner == NULL) {
|
|
break;
|
|
}
|
|
attr->owner = ssh_string_to_char(owner);
|
|
ssh_string_free(owner);
|
|
if (attr->owner == NULL) {
|
|
break;
|
|
}
|
|
|
|
group = ssh_buffer_get_ssh_string(buf);
|
|
if (group == NULL) {
|
|
break;
|
|
}
|
|
attr->group = ssh_string_to_char(group);
|
|
ssh_string_free(group);
|
|
if (attr->group == NULL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
|
|
if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) {
|
|
break;
|
|
}
|
|
attr->permissions = ntohl(attr->permissions);
|
|
|
|
/* FIXME on windows! */
|
|
switch (attr->permissions & SSH_S_IFMT) {
|
|
case SSH_S_IFSOCK:
|
|
case SSH_S_IFBLK:
|
|
case SSH_S_IFCHR:
|
|
case SSH_S_IFIFO:
|
|
attr->type = SSH_FILEXFER_TYPE_SPECIAL;
|
|
break;
|
|
case SSH_S_IFLNK:
|
|
attr->type = SSH_FILEXFER_TYPE_SYMLINK;
|
|
break;
|
|
case SSH_S_IFREG:
|
|
attr->type = SSH_FILEXFER_TYPE_REGULAR;
|
|
break;
|
|
case SSH_S_IFDIR:
|
|
attr->type = SSH_FILEXFER_TYPE_DIRECTORY;
|
|
break;
|
|
default:
|
|
attr->type = SSH_FILEXFER_TYPE_UNKNOWN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) {
|
|
if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) {
|
|
break;
|
|
}
|
|
attr->atime64 = ntohll(attr->atime64);
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
|
|
if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) {
|
|
break;
|
|
}
|
|
attr->atime_nseconds = ntohl(attr->atime_nseconds);
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_CREATETIME) {
|
|
if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) {
|
|
break;
|
|
}
|
|
attr->createtime = ntohll(attr->createtime);
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
|
|
if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) {
|
|
break;
|
|
}
|
|
attr->createtime_nseconds = ntohl(attr->createtime_nseconds);
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) {
|
|
if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) {
|
|
break;
|
|
}
|
|
attr->mtime64 = ntohll(attr->mtime64);
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
|
|
if (ssh_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 = ssh_buffer_get_ssh_string(buf)) == NULL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
|
|
if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) {
|
|
break;
|
|
}
|
|
attr->extended_count = ntohl(attr->extended_count);
|
|
|
|
while(attr->extended_count &&
|
|
(attr->extended_type = ssh_buffer_get_ssh_string(buf)) &&
|
|
(attr->extended_data = ssh_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(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 = calloc(1, 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) {
|
|
sftp_attributes attr;
|
|
int rc;
|
|
|
|
attr = calloc(1, sizeof(struct sftp_attributes_struct));
|
|
if (attr == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
return NULL;
|
|
}
|
|
|
|
if (expectname) {
|
|
rc = ssh_buffer_unpack(buf, "ss",
|
|
&attr->name,
|
|
&attr->longname);
|
|
if (rc != SSH_OK){
|
|
goto error;
|
|
}
|
|
SSH_LOG(SSH_LOG_RARE, "Name: %s", attr->name);
|
|
|
|
/* 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) {
|
|
goto error;
|
|
}
|
|
|
|
attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP);
|
|
if (attr->group == NULL) {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = ssh_buffer_unpack(buf, "d", &attr->flags);
|
|
if (rc != SSH_OK){
|
|
goto error;
|
|
}
|
|
SSH_LOG(SSH_LOG_RARE,
|
|
"Flags: %.8lx\n", (long unsigned int) attr->flags);
|
|
|
|
if (attr->flags & SSH_FILEXFER_ATTR_SIZE) {
|
|
rc = ssh_buffer_unpack(buf, "q", &attr->size);
|
|
if(rc != SSH_OK) {
|
|
goto error;
|
|
}
|
|
SSH_LOG(SSH_LOG_RARE,
|
|
"Size: %llu\n",
|
|
(long long unsigned int) attr->size);
|
|
}
|
|
|
|
if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) {
|
|
rc = ssh_buffer_unpack(buf, "dd",
|
|
&attr->uid,
|
|
&attr->gid);
|
|
if (rc != SSH_OK){
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
|
|
rc = ssh_buffer_unpack(buf, "d", &attr->permissions);
|
|
if (rc != SSH_OK){
|
|
goto error;
|
|
}
|
|
|
|
switch (attr->permissions & SSH_S_IFMT) {
|
|
case SSH_S_IFSOCK:
|
|
case SSH_S_IFBLK:
|
|
case SSH_S_IFCHR:
|
|
case SSH_S_IFIFO:
|
|
attr->type = SSH_FILEXFER_TYPE_SPECIAL;
|
|
break;
|
|
case SSH_S_IFLNK:
|
|
attr->type = SSH_FILEXFER_TYPE_SYMLINK;
|
|
break;
|
|
case SSH_S_IFREG:
|
|
attr->type = SSH_FILEXFER_TYPE_REGULAR;
|
|
break;
|
|
case SSH_S_IFDIR:
|
|
attr->type = SSH_FILEXFER_TYPE_DIRECTORY;
|
|
break;
|
|
default:
|
|
attr->type = SSH_FILEXFER_TYPE_UNKNOWN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) {
|
|
rc = ssh_buffer_unpack(buf, "dd",
|
|
&attr->atime,
|
|
&attr->mtime);
|
|
if (rc != SSH_OK){
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) {
|
|
rc = ssh_buffer_unpack(buf, "d", &attr->extended_count);
|
|
if (rc != SSH_OK){
|
|
goto error;
|
|
}
|
|
|
|
if (attr->extended_count > 0){
|
|
rc = ssh_buffer_unpack(buf, "ss",
|
|
&attr->extended_type,
|
|
&attr->extended_data);
|
|
if (rc != SSH_OK){
|
|
goto error;
|
|
}
|
|
attr->extended_count--;
|
|
}
|
|
/* just ignore the remaining extensions */
|
|
|
|
while (attr->extended_count > 0){
|
|
ssh_string tmp1,tmp2;
|
|
rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2);
|
|
if (rc != SSH_OK){
|
|
goto error;
|
|
}
|
|
SAFE_FREE(tmp1);
|
|
SAFE_FREE(tmp2);
|
|
attr->extended_count--;
|
|
}
|
|
}
|
|
|
|
return attr;
|
|
|
|
error:
|
|
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;
|
|
}
|
|
|
|
/* 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);
|
|
int rc;
|
|
|
|
flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID |
|
|
SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME);
|
|
|
|
rc = ssh_buffer_pack(buffer, "d", flags);
|
|
if (rc != SSH_OK) {
|
|
return -1;
|
|
}
|
|
|
|
if (attr != NULL) {
|
|
if (flags & SSH_FILEXFER_ATTR_SIZE) {
|
|
rc = ssh_buffer_pack(buffer, "q", attr->size);
|
|
if (rc != SSH_OK) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_UIDGID) {
|
|
rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid);
|
|
if (rc != SSH_OK) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
|
|
rc = ssh_buffer_pack(buffer, "d", attr->permissions);
|
|
if (rc != SSH_OK) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (flags & SSH_FILEXFER_ATTR_ACMODTIME) {
|
|
rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime);
|
|
if (rc != SSH_OK) {
|
|
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;
|
|
int rc;
|
|
|
|
if (dir->buffer == NULL) {
|
|
payload = ssh_buffer_new();
|
|
if (payload == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
return NULL;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(payload,
|
|
sizeof(uint32_t) * 2 +
|
|
ssh_string_len(dir->handle));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(payload);
|
|
return NULL;
|
|
}
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(payload, htonl(id)) < 0 ||
|
|
ssh_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(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:
|
|
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);
|
|
|
|
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_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;
|
|
int rc;
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
return -1;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 2 +
|
|
ssh_string_len(handle));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
return -1;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_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;
|
|
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;
|
|
}
|
|
|
|
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_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 %x",file,sftp_flags);
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 4 +
|
|
ssh_string_len(filename));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(filename);
|
|
return NULL;
|
|
}
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_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 (ssh_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);
|
|
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_close(handle);
|
|
return NULL;
|
|
}
|
|
|
|
handle->offset = stat_data->size;
|
|
}
|
|
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;
|
|
size_t datalen;
|
|
ssh_buffer buffer;
|
|
int id;
|
|
int rc;
|
|
|
|
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);
|
|
|
|
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);
|
|
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: "
|
|
"%" PRIdS " and asked for %" PRIdS,
|
|
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);
|
|
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);
|
|
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);
|
|
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: "
|
|
"%" PRIdS " and asked for %u",
|
|
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);
|
|
return SSH_ERROR;
|
|
}
|
|
|
|
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_buffer buffer;
|
|
uint32_t id;
|
|
int len;
|
|
int packetlen;
|
|
int rc;
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
return -1;
|
|
}
|
|
|
|
id = sftp_get_new_id(file->sftp);
|
|
|
|
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);
|
|
return -1;
|
|
}
|
|
packetlen=ssh_buffer_get_len(buffer);
|
|
len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer);
|
|
ssh_buffer_free(buffer);
|
|
if (len < 0) {
|
|
return -1;
|
|
} else if (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);
|
|
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);
|
|
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);
|
|
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);
|
|
}
|
|
|
|
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);
|
|
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);
|
|
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;
|
|
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;
|
|
int rc;
|
|
|
|
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;
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 2 +
|
|
ssh_string_len(path));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(path);
|
|
return -1;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_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);
|
|
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_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;
|
|
uint32_t id;
|
|
int rc;
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
return -1;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
|
|
rc = ssh_buffer_pack(buffer,
|
|
"dss",
|
|
id,
|
|
original,
|
|
newname);
|
|
if (rc != SSH_OK) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
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);
|
|
}
|
|
|
|
if (sftp_packet_write(sftp, SSH_FXP_RENAME, 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;
|
|
}
|
|
/*
|
|
* 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;
|
|
int rc;
|
|
|
|
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;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 2 +
|
|
ssh_string_len(path));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(path);
|
|
return -1;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_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_buffer buffer;
|
|
uint32_t id;
|
|
int rc;
|
|
|
|
if (sftp == NULL)
|
|
return -1;
|
|
if (target == NULL || dest == NULL) {
|
|
ssh_set_error_invalid(sftp->session);
|
|
return -1;
|
|
}
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
return -1;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
|
|
/* TODO check for version number if they ever fix it. */
|
|
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);
|
|
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);
|
|
}
|
|
|
|
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;
|
|
int rc;
|
|
|
|
if (sftp == NULL)
|
|
return NULL;
|
|
if (path == NULL) {
|
|
ssh_set_error_invalid(sftp);
|
|
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;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 2 +
|
|
ssh_string_len(path_s));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(path_s);
|
|
return NULL;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_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" */
|
|
ssh_buffer_get_u32(msg->payload, &ignored);
|
|
/* we only care about the file name string */
|
|
link_s = ssh_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;
|
|
int rc;
|
|
|
|
statvfs = calloc(1, sizeof(struct sftp_statvfs_struct));
|
|
if (statvfs == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
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");
|
|
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;
|
|
int rc;
|
|
|
|
if (sftp == NULL)
|
|
return NULL;
|
|
if (path == NULL) {
|
|
ssh_set_error_invalid(sftp->session);
|
|
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;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 3 +
|
|
ssh_string_len(ext) +
|
|
ssh_string_len(pathstr));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(ext);
|
|
ssh_string_free(pathstr);
|
|
return NULL;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_buffer_add_ssh_string(buffer, ext) < 0 ||
|
|
ssh_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;
|
|
}
|
|
|
|
int sftp_fsync(sftp_file file)
|
|
{
|
|
sftp_session sftp;
|
|
sftp_message msg = NULL;
|
|
ssh_string ext;
|
|
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);
|
|
return -1;
|
|
}
|
|
|
|
ext = ssh_string_from_char("fsync@openssh.com");
|
|
if (ext == NULL) {
|
|
ssh_set_error_oom(sftp->session);
|
|
rc = -1;
|
|
goto done;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 3 +
|
|
ssh_string_len(ext) +
|
|
ssh_string_len(file->handle));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
goto done;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
rc = ssh_buffer_add_u32(buffer, htonl(id));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
goto done;
|
|
}
|
|
|
|
rc = ssh_buffer_add_ssh_string(buffer, ext);
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
goto done;
|
|
}
|
|
|
|
rc = ssh_buffer_add_ssh_string(buffer, file->handle);
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
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);
|
|
}
|
|
|
|
rc = -1;
|
|
done:
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(ext);
|
|
|
|
return rc;
|
|
}
|
|
|
|
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;
|
|
int rc;
|
|
|
|
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;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 3 +
|
|
ssh_string_len(ext) +
|
|
ssh_string_len(file->handle));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(ext);
|
|
return NULL;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_buffer_add_ssh_string(buffer, ext) < 0 ||
|
|
ssh_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;
|
|
int rc;
|
|
|
|
if (sftp == NULL)
|
|
return NULL;
|
|
if (path == NULL) {
|
|
ssh_set_error_invalid(sftp->session);
|
|
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;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 2 +
|
|
ssh_string_len(pathstr));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(pathstr);
|
|
return NULL;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_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" */
|
|
ssh_buffer_get_u32(msg->payload, &ignored);
|
|
/* we only care about the file name string */
|
|
name = ssh_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;
|
|
int rc;
|
|
|
|
if (path == NULL) {
|
|
ssh_set_error_invalid(sftp->session);
|
|
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;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 2 +
|
|
ssh_string_len(pathstr));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
ssh_string_free(pathstr);
|
|
return NULL;
|
|
}
|
|
|
|
id = sftp_get_new_id(sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_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) {
|
|
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);
|
|
|
|
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;
|
|
|
|
buffer = ssh_buffer_new();
|
|
if (buffer == NULL) {
|
|
ssh_set_error_oom(file->sftp->session);
|
|
return NULL;
|
|
}
|
|
|
|
rc = ssh_buffer_allocate_size(buffer,
|
|
sizeof(uint32_t) * 2 +
|
|
ssh_string_len(file->handle));
|
|
if (rc < 0) {
|
|
ssh_set_error_oom(file->sftp->session);
|
|
ssh_buffer_free(buffer);
|
|
return NULL;
|
|
}
|
|
|
|
id = sftp_get_new_id(file->sftp);
|
|
if (ssh_buffer_add_u32(buffer, htonl(id)) < 0 ||
|
|
ssh_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){
|
|
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;
|
|
}
|
|
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 */
|