version 1.18, 2001/07/14 15:10:16 |
version 1.18.2.3, 2002/06/22 07:23:17 |
|
|
/* |
/* |
* Copyright (c) 2001 Damien Miller. All rights reserved. |
* Copyright (c) 2001,2002 Damien Miller. All rights reserved. |
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* modification, are permitted provided that the following conditions |
|
|
|
|
/* XXX: memleaks */ |
/* XXX: memleaks */ |
/* XXX: signed vs unsigned */ |
/* XXX: signed vs unsigned */ |
/* XXX: redesign to allow concurrent overlapped operations */ |
/* XXX: remove all logging, only return status codes */ |
/* XXX: we use fatal too much, error may be more appropriate in places */ |
|
/* XXX: copy between two remote sites */ |
/* XXX: copy between two remote sites */ |
|
|
#include "includes.h" |
#include "includes.h" |
RCSID("$OpenBSD$"); |
RCSID("$OpenBSD$"); |
|
|
|
#include <sys/queue.h> |
|
|
#include "buffer.h" |
#include "buffer.h" |
#include "bufaux.h" |
#include "bufaux.h" |
#include "getput.h" |
#include "getput.h" |
|
|
#include "sftp-common.h" |
#include "sftp-common.h" |
#include "sftp-client.h" |
#include "sftp-client.h" |
|
|
/* How much data to read/write at at time during copies */ |
/* Minimum amount of data to read at at time */ |
/* XXX: what should this be? */ |
#define MIN_READ_SIZE 512 |
#define COPY_SIZE 8192 |
|
|
|
/* Message ID */ |
struct sftp_conn { |
static u_int msg_id = 1; |
int fd_in; |
|
int fd_out; |
|
u_int transfer_buflen; |
|
u_int num_requests; |
|
u_int version; |
|
u_int msg_id; |
|
}; |
|
|
static void |
static void |
send_msg(int fd, Buffer *m) |
send_msg(int fd, Buffer *m) |
|
|
return(a); |
return(a); |
} |
} |
|
|
int |
struct sftp_conn * |
do_init(int fd_in, int fd_out) |
do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests) |
{ |
{ |
int type, version; |
int type, version; |
Buffer msg; |
Buffer msg; |
|
struct sftp_conn *ret; |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
buffer_put_char(&msg, SSH2_FXP_INIT); |
buffer_put_char(&msg, SSH2_FXP_INIT); |
|
|
error("Invalid packet back from SSH2_FXP_INIT (type %d)", |
error("Invalid packet back from SSH2_FXP_INIT (type %d)", |
type); |
type); |
buffer_free(&msg); |
buffer_free(&msg); |
return(-1); |
return(NULL); |
} |
} |
version = buffer_get_int(&msg); |
version = buffer_get_int(&msg); |
|
|
|
|
|
|
buffer_free(&msg); |
buffer_free(&msg); |
|
|
return(version); |
ret = xmalloc(sizeof(*ret)); |
|
ret->fd_in = fd_in; |
|
ret->fd_out = fd_out; |
|
ret->transfer_buflen = transfer_buflen; |
|
ret->num_requests = num_requests; |
|
ret->version = version; |
|
ret->msg_id = 1; |
|
|
|
/* Some filexfer v.0 servers don't support large packets */ |
|
if (version == 0) |
|
ret->transfer_buflen = MIN(ret->transfer_buflen, 20480); |
|
|
|
return(ret); |
} |
} |
|
|
|
u_int |
|
sftp_proto_version(struct sftp_conn *conn) |
|
{ |
|
return(conn->version); |
|
} |
|
|
int |
int |
do_close(int fd_in, int fd_out, char *handle, u_int handle_len) |
do_close(struct sftp_conn *conn, char *handle, u_int handle_len) |
{ |
{ |
u_int id, status; |
u_int id, status; |
Buffer msg; |
Buffer msg; |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
|
|
id = msg_id++; |
id = conn->msg_id++; |
buffer_put_char(&msg, SSH2_FXP_CLOSE); |
buffer_put_char(&msg, SSH2_FXP_CLOSE); |
buffer_put_int(&msg, id); |
buffer_put_int(&msg, id); |
buffer_put_string(&msg, handle, handle_len); |
buffer_put_string(&msg, handle, handle_len); |
send_msg(fd_out, &msg); |
send_msg(conn->fd_out, &msg); |
debug3("Sent message SSH2_FXP_CLOSE I:%d", id); |
debug3("Sent message SSH2_FXP_CLOSE I:%d", id); |
|
|
status = get_status(fd_in, id); |
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK) |
error("Couldn't close file: %s", fx2txt(status)); |
error("Couldn't close file: %s", fx2txt(status)); |
|
|
|
|
|
|
|
|
static int |
static int |
do_lsreaddir(int fd_in, int fd_out, char *path, int printflag, |
do_lsreaddir(struct sftp_conn *conn, char *path, int printflag, |
SFTP_DIRENT ***dir) |
SFTP_DIRENT ***dir) |
{ |
{ |
Buffer msg; |
Buffer msg; |
u_int type, id, handle_len, i, expected_id, ents = 0; |
u_int type, id, handle_len, i, expected_id, ents = 0; |
char *handle; |
char *handle; |
|
|
id = msg_id++; |
id = conn->msg_id++; |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
buffer_put_char(&msg, SSH2_FXP_OPENDIR); |
buffer_put_char(&msg, SSH2_FXP_OPENDIR); |
buffer_put_int(&msg, id); |
buffer_put_int(&msg, id); |
buffer_put_cstring(&msg, path); |
buffer_put_cstring(&msg, path); |
send_msg(fd_out, &msg); |
send_msg(conn->fd_out, &msg); |
|
|
buffer_clear(&msg); |
buffer_clear(&msg); |
|
|
handle = get_handle(fd_in, id, &handle_len); |
handle = get_handle(conn->fd_in, id, &handle_len); |
if (handle == NULL) |
if (handle == NULL) |
return(-1); |
return(-1); |
|
|
|
|
*dir = xmalloc(sizeof(**dir)); |
*dir = xmalloc(sizeof(**dir)); |
(*dir)[0] = NULL; |
(*dir)[0] = NULL; |
} |
} |
|
|
|
|
for(;;) { |
for (;;) { |
int count; |
int count; |
|
|
id = expected_id = msg_id++; |
id = expected_id = conn->msg_id++; |
|
|
debug3("Sending SSH2_FXP_READDIR I:%d", id); |
debug3("Sending SSH2_FXP_READDIR I:%d", id); |
|
|
|
|
buffer_put_char(&msg, SSH2_FXP_READDIR); |
buffer_put_char(&msg, SSH2_FXP_READDIR); |
buffer_put_int(&msg, id); |
buffer_put_int(&msg, id); |
buffer_put_string(&msg, handle, handle_len); |
buffer_put_string(&msg, handle, handle_len); |
send_msg(fd_out, &msg); |
send_msg(conn->fd_out, &msg); |
|
|
buffer_clear(&msg); |
buffer_clear(&msg); |
|
|
get_msg(fd_in, &msg); |
get_msg(conn->fd_in, &msg); |
|
|
type = buffer_get_char(&msg); |
type = buffer_get_char(&msg); |
id = buffer_get_int(&msg); |
id = buffer_get_int(&msg); |
|
|
} else { |
} else { |
error("Couldn't read directory: %s", |
error("Couldn't read directory: %s", |
fx2txt(status)); |
fx2txt(status)); |
do_close(fd_in, fd_out, handle, handle_len); |
do_close(conn, handle, handle_len); |
return(status); |
return(status); |
} |
} |
} else if (type != SSH2_FXP_NAME) |
} else if (type != SSH2_FXP_NAME) |
|
|
if (count == 0) |
if (count == 0) |
break; |
break; |
debug3("Received %d SSH2_FXP_NAME responses", count); |
debug3("Received %d SSH2_FXP_NAME responses", count); |
for(i = 0; i < count; i++) { |
for (i = 0; i < count; i++) { |
char *filename, *longname; |
char *filename, *longname; |
Attrib *a; |
Attrib *a; |
|
|
|
|
} |
} |
|
|
buffer_free(&msg); |
buffer_free(&msg); |
do_close(fd_in, fd_out, handle, handle_len); |
do_close(conn, handle, handle_len); |
xfree(handle); |
xfree(handle); |
|
|
return(0); |
return(0); |
} |
} |
|
|
int |
int |
do_ls(int fd_in, int fd_out, char *path) |
do_ls(struct sftp_conn *conn, char *path) |
{ |
{ |
return(do_lsreaddir(fd_in, fd_out, path, 1, NULL)); |
return(do_lsreaddir(conn, path, 1, NULL)); |
} |
} |
|
|
int |
int |
do_readdir(int fd_in, int fd_out, char *path, SFTP_DIRENT ***dir) |
do_readdir(struct sftp_conn *conn, char *path, SFTP_DIRENT ***dir) |
{ |
{ |
return(do_lsreaddir(fd_in, fd_out, path, 0, dir)); |
return(do_lsreaddir(conn, path, 0, dir)); |
} |
} |
|
|
void free_sftp_dirents(SFTP_DIRENT **s) |
void free_sftp_dirents(SFTP_DIRENT **s) |
{ |
{ |
int i; |
int i; |
|
|
for(i = 0; s[i]; i++) { |
for (i = 0; s[i]; i++) { |
xfree(s[i]->filename); |
xfree(s[i]->filename); |
xfree(s[i]->longname); |
xfree(s[i]->longname); |
xfree(s[i]); |
xfree(s[i]); |
|
|
} |
} |
|
|
int |
int |
do_rm(int fd_in, int fd_out, char *path) |
do_rm(struct sftp_conn *conn, char *path) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); |
debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); |
|
|
id = msg_id++; |
id = conn->msg_id++; |
send_string_request(fd_out, id, SSH2_FXP_REMOVE, path, strlen(path)); |
send_string_request(conn->fd_out, id, SSH2_FXP_REMOVE, path, |
status = get_status(fd_in, id); |
strlen(path)); |
|
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK) |
error("Couldn't delete file: %s", fx2txt(status)); |
error("Couldn't delete file: %s", fx2txt(status)); |
return(status); |
return(status); |
} |
} |
|
|
int |
int |
do_mkdir(int fd_in, int fd_out, char *path, Attrib *a) |
do_mkdir(struct sftp_conn *conn, char *path, Attrib *a) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
id = msg_id++; |
id = conn->msg_id++; |
send_string_attrs_request(fd_out, id, SSH2_FXP_MKDIR, path, |
send_string_attrs_request(conn->fd_out, id, SSH2_FXP_MKDIR, path, |
strlen(path), a); |
strlen(path), a); |
|
|
status = get_status(fd_in, id); |
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK) |
error("Couldn't create directory: %s", fx2txt(status)); |
error("Couldn't create directory: %s", fx2txt(status)); |
|
|
|
|
} |
} |
|
|
int |
int |
do_rmdir(int fd_in, int fd_out, char *path) |
do_rmdir(struct sftp_conn *conn, char *path) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
id = msg_id++; |
id = conn->msg_id++; |
send_string_request(fd_out, id, SSH2_FXP_RMDIR, path, strlen(path)); |
send_string_request(conn->fd_out, id, SSH2_FXP_RMDIR, path, |
|
strlen(path)); |
|
|
status = get_status(fd_in, id); |
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK) |
error("Couldn't remove directory: %s", fx2txt(status)); |
error("Couldn't remove directory: %s", fx2txt(status)); |
|
|
|
|
} |
} |
|
|
Attrib * |
Attrib * |
do_stat(int fd_in, int fd_out, char *path, int quiet) |
do_stat(struct sftp_conn *conn, char *path, int quiet) |
{ |
{ |
u_int id; |
u_int id; |
|
|
id = msg_id++; |
id = conn->msg_id++; |
send_string_request(fd_out, id, SSH2_FXP_STAT, path, strlen(path)); |
|
return(get_decode_stat(fd_in, id, quiet)); |
send_string_request(conn->fd_out, id, |
|
conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, |
|
path, strlen(path)); |
|
|
|
return(get_decode_stat(conn->fd_in, id, quiet)); |
} |
} |
|
|
Attrib * |
Attrib * |
do_lstat(int fd_in, int fd_out, char *path, int quiet) |
do_lstat(struct sftp_conn *conn, char *path, int quiet) |
{ |
{ |
u_int id; |
u_int id; |
|
|
id = msg_id++; |
if (conn->version == 0) { |
send_string_request(fd_out, id, SSH2_FXP_LSTAT, path, strlen(path)); |
if (quiet) |
return(get_decode_stat(fd_in, id, quiet)); |
debug("Server version does not support lstat operation"); |
|
else |
|
log("Server version does not support lstat operation"); |
|
return(do_stat(conn, path, quiet)); |
|
} |
|
|
|
id = conn->msg_id++; |
|
send_string_request(conn->fd_out, id, SSH2_FXP_LSTAT, path, |
|
strlen(path)); |
|
|
|
return(get_decode_stat(conn->fd_in, id, quiet)); |
} |
} |
|
|
Attrib * |
Attrib * |
do_fstat(int fd_in, int fd_out, char *handle, u_int handle_len, int quiet) |
do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet) |
{ |
{ |
u_int id; |
u_int id; |
|
|
id = msg_id++; |
id = conn->msg_id++; |
send_string_request(fd_out, id, SSH2_FXP_FSTAT, handle, handle_len); |
send_string_request(conn->fd_out, id, SSH2_FXP_FSTAT, handle, |
return(get_decode_stat(fd_in, id, quiet)); |
handle_len); |
|
|
|
return(get_decode_stat(conn->fd_in, id, quiet)); |
} |
} |
|
|
int |
int |
do_setstat(int fd_in, int fd_out, char *path, Attrib *a) |
do_setstat(struct sftp_conn *conn, char *path, Attrib *a) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
id = msg_id++; |
id = conn->msg_id++; |
send_string_attrs_request(fd_out, id, SSH2_FXP_SETSTAT, path, |
send_string_attrs_request(conn->fd_out, id, SSH2_FXP_SETSTAT, path, |
strlen(path), a); |
strlen(path), a); |
|
|
status = get_status(fd_in, id); |
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK) |
error("Couldn't setstat on \"%s\": %s", path, |
error("Couldn't setstat on \"%s\": %s", path, |
fx2txt(status)); |
fx2txt(status)); |
|
|
} |
} |
|
|
int |
int |
do_fsetstat(int fd_in, int fd_out, char *handle, u_int handle_len, |
do_fsetstat(struct sftp_conn *conn, char *handle, u_int handle_len, |
Attrib *a) |
Attrib *a) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
id = msg_id++; |
id = conn->msg_id++; |
send_string_attrs_request(fd_out, id, SSH2_FXP_FSETSTAT, handle, |
send_string_attrs_request(conn->fd_out, id, SSH2_FXP_FSETSTAT, handle, |
handle_len, a); |
handle_len, a); |
|
|
status = get_status(fd_in, id); |
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK) |
error("Couldn't fsetstat: %s", fx2txt(status)); |
error("Couldn't fsetstat: %s", fx2txt(status)); |
|
|
|
|
} |
} |
|
|
char * |
char * |
do_realpath(int fd_in, int fd_out, char *path) |
do_realpath(struct sftp_conn *conn, char *path) |
{ |
{ |
Buffer msg; |
Buffer msg; |
u_int type, expected_id, count, id; |
u_int type, expected_id, count, id; |
char *filename, *longname; |
char *filename, *longname; |
Attrib *a; |
Attrib *a; |
|
|
expected_id = id = msg_id++; |
expected_id = id = conn->msg_id++; |
send_string_request(fd_out, id, SSH2_FXP_REALPATH, path, strlen(path)); |
send_string_request(conn->fd_out, id, SSH2_FXP_REALPATH, path, |
|
strlen(path)); |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
|
|
get_msg(fd_in, &msg); |
get_msg(conn->fd_in, &msg); |
type = buffer_get_char(&msg); |
type = buffer_get_char(&msg); |
id = buffer_get_int(&msg); |
id = buffer_get_int(&msg); |
|
|
|
|
} |
} |
|
|
int |
int |
do_rename(int fd_in, int fd_out, char *oldpath, char *newpath) |
do_rename(struct sftp_conn *conn, char *oldpath, char *newpath) |
{ |
{ |
Buffer msg; |
Buffer msg; |
u_int status, id; |
u_int status, id; |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
|
|
/* Send rename request */ |
/* Send rename request */ |
id = msg_id++; |
id = conn->msg_id++; |
buffer_put_char(&msg, SSH2_FXP_RENAME); |
buffer_put_char(&msg, SSH2_FXP_RENAME); |
buffer_put_int(&msg, id); |
buffer_put_int(&msg, id); |
buffer_put_cstring(&msg, oldpath); |
buffer_put_cstring(&msg, oldpath); |
buffer_put_cstring(&msg, newpath); |
buffer_put_cstring(&msg, newpath); |
send_msg(fd_out, &msg); |
send_msg(conn->fd_out, &msg); |
debug3("Sent message SSH2_FXP_RENAME \"%s\" -> \"%s\"", oldpath, |
debug3("Sent message SSH2_FXP_RENAME \"%s\" -> \"%s\"", oldpath, |
newpath); |
newpath); |
buffer_free(&msg); |
buffer_free(&msg); |
|
|
status = get_status(fd_in, id); |
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK) |
error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, newpath, |
error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, |
fx2txt(status)); |
newpath, fx2txt(status)); |
|
|
return(status); |
return(status); |
} |
} |
|
|
int |
int |
do_symlink(int fd_in, int fd_out, char *oldpath, char *newpath) |
do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath) |
{ |
{ |
Buffer msg; |
Buffer msg; |
u_int status, id; |
u_int status, id; |
|
|
|
if (conn->version < 3) { |
|
error("This server does not support the symlink operation"); |
|
return(SSH2_FX_OP_UNSUPPORTED); |
|
} |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
|
|
/* Send rename request */ |
/* Send rename request */ |
id = msg_id++; |
id = conn->msg_id++; |
buffer_put_char(&msg, SSH2_FXP_SYMLINK); |
buffer_put_char(&msg, SSH2_FXP_SYMLINK); |
buffer_put_int(&msg, id); |
buffer_put_int(&msg, id); |
buffer_put_cstring(&msg, oldpath); |
buffer_put_cstring(&msg, oldpath); |
buffer_put_cstring(&msg, newpath); |
buffer_put_cstring(&msg, newpath); |
send_msg(fd_out, &msg); |
send_msg(conn->fd_out, &msg); |
debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, |
debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, |
newpath); |
newpath); |
buffer_free(&msg); |
buffer_free(&msg); |
|
|
status = get_status(fd_in, id); |
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK) |
error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, newpath, |
error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, |
fx2txt(status)); |
newpath, fx2txt(status)); |
|
|
return(status); |
return(status); |
} |
} |
|
|
char * |
char * |
do_readlink(int fd_in, int fd_out, char *path) |
do_readlink(struct sftp_conn *conn, char *path) |
{ |
{ |
Buffer msg; |
Buffer msg; |
u_int type, expected_id, count, id; |
u_int type, expected_id, count, id; |
char *filename, *longname; |
char *filename, *longname; |
Attrib *a; |
Attrib *a; |
|
|
expected_id = id = msg_id++; |
expected_id = id = conn->msg_id++; |
send_string_request(fd_out, id, SSH2_FXP_READLINK, path, strlen(path)); |
send_string_request(conn->fd_out, id, SSH2_FXP_READLINK, path, |
|
strlen(path)); |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
|
|
get_msg(fd_in, &msg); |
get_msg(conn->fd_in, &msg); |
type = buffer_get_char(&msg); |
type = buffer_get_char(&msg); |
id = buffer_get_int(&msg); |
id = buffer_get_int(&msg); |
|
|
|
|
return(filename); |
return(filename); |
} |
} |
|
|
|
static void |
|
send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len, |
|
char *handle, u_int handle_len) |
|
{ |
|
Buffer msg; |
|
|
|
buffer_init(&msg); |
|
buffer_clear(&msg); |
|
buffer_put_char(&msg, SSH2_FXP_READ); |
|
buffer_put_int(&msg, id); |
|
buffer_put_string(&msg, handle, handle_len); |
|
buffer_put_int64(&msg, offset); |
|
buffer_put_int(&msg, len); |
|
send_msg(fd_out, &msg); |
|
buffer_free(&msg); |
|
} |
|
|
int |
int |
do_download(int fd_in, int fd_out, char *remote_path, char *local_path, |
do_download(struct sftp_conn *conn, char *remote_path, char *local_path, |
int pflag) |
int pflag) |
{ |
{ |
int local_fd; |
|
u_int expected_id, handle_len, mode, type, id; |
|
u_int64_t offset; |
|
char *handle; |
|
Buffer msg; |
|
Attrib junk, *a; |
Attrib junk, *a; |
int status; |
Buffer msg; |
|
char *handle; |
|
int local_fd, status, num_req, max_req, write_error; |
|
int read_error, write_errno; |
|
u_int64_t offset, size; |
|
u_int handle_len, mode, type, id, buflen; |
|
struct request { |
|
u_int id; |
|
u_int len; |
|
u_int64_t offset; |
|
TAILQ_ENTRY(request) tq; |
|
}; |
|
TAILQ_HEAD(reqhead, request) requests; |
|
struct request *req; |
|
|
a = do_stat(fd_in, fd_out, remote_path, 0); |
TAILQ_INIT(&requests); |
|
|
|
a = do_stat(conn, remote_path, 0); |
if (a == NULL) |
if (a == NULL) |
return(-1); |
return(-1); |
|
|
|
|
return(-1); |
return(-1); |
} |
} |
|
|
local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, mode); |
if (a->flags & SSH2_FILEXFER_ATTR_SIZE) |
if (local_fd == -1) { |
size = a->size; |
error("Couldn't open local file \"%s\" for writing: %s", |
else |
local_path, strerror(errno)); |
size = 0; |
return(-1); |
|
} |
|
|
|
|
buflen = conn->transfer_buflen; |
buffer_init(&msg); |
buffer_init(&msg); |
|
|
/* Send open request */ |
/* Send open request */ |
id = msg_id++; |
id = conn->msg_id++; |
buffer_put_char(&msg, SSH2_FXP_OPEN); |
buffer_put_char(&msg, SSH2_FXP_OPEN); |
buffer_put_int(&msg, id); |
buffer_put_int(&msg, id); |
buffer_put_cstring(&msg, remote_path); |
buffer_put_cstring(&msg, remote_path); |
buffer_put_int(&msg, SSH2_FXF_READ); |
buffer_put_int(&msg, SSH2_FXF_READ); |
attrib_clear(&junk); /* Send empty attributes */ |
attrib_clear(&junk); /* Send empty attributes */ |
encode_attrib(&msg, &junk); |
encode_attrib(&msg, &junk); |
send_msg(fd_out, &msg); |
send_msg(conn->fd_out, &msg); |
debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path); |
debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path); |
|
|
handle = get_handle(fd_in, id, &handle_len); |
handle = get_handle(conn->fd_in, id, &handle_len); |
if (handle == NULL) { |
if (handle == NULL) { |
buffer_free(&msg); |
buffer_free(&msg); |
close(local_fd); |
|
return(-1); |
return(-1); |
} |
} |
|
|
|
local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, mode); |
|
if (local_fd == -1) { |
|
error("Couldn't open local file \"%s\" for writing: %s", |
|
local_path, strerror(errno)); |
|
buffer_free(&msg); |
|
xfree(handle); |
|
return(-1); |
|
} |
|
|
/* Read from remote and write to local */ |
/* Read from remote and write to local */ |
offset = 0; |
write_error = read_error = write_errno = num_req = offset = 0; |
for(;;) { |
max_req = 1; |
u_int len; |
while (num_req > 0 || max_req > 0) { |
char *data; |
char *data; |
|
u_int len; |
|
|
id = expected_id = msg_id++; |
/* Send some more requests */ |
|
while (num_req < max_req) { |
|
debug3("Request range %llu -> %llu (%d/%d)", |
|
(unsigned long long)offset, |
|
(unsigned long long)offset + buflen - 1, |
|
num_req, max_req); |
|
req = xmalloc(sizeof(*req)); |
|
req->id = conn->msg_id++; |
|
req->len = buflen; |
|
req->offset = offset; |
|
offset += buflen; |
|
num_req++; |
|
TAILQ_INSERT_TAIL(&requests, req, tq); |
|
send_read_request(conn->fd_out, req->id, req->offset, |
|
req->len, handle, handle_len); |
|
} |
|
|
buffer_clear(&msg); |
buffer_clear(&msg); |
buffer_put_char(&msg, SSH2_FXP_READ); |
get_msg(conn->fd_in, &msg); |
buffer_put_int(&msg, id); |
|
buffer_put_string(&msg, handle, handle_len); |
|
buffer_put_int64(&msg, offset); |
|
buffer_put_int(&msg, COPY_SIZE); |
|
send_msg(fd_out, &msg); |
|
debug3("Sent message SSH2_FXP_READ I:%d O:%llu S:%u", |
|
id, (unsigned long long)offset, COPY_SIZE); |
|
|
|
buffer_clear(&msg); |
|
|
|
get_msg(fd_in, &msg); |
|
type = buffer_get_char(&msg); |
type = buffer_get_char(&msg); |
id = buffer_get_int(&msg); |
id = buffer_get_int(&msg); |
debug3("Received reply T:%d I:%d", type, id); |
debug3("Received reply T:%d I:%d R:%d", type, id, max_req); |
if (id != expected_id) |
|
fatal("ID mismatch (%d != %d)", id, expected_id); |
/* Find the request in our queue */ |
if (type == SSH2_FXP_STATUS) { |
for(req = TAILQ_FIRST(&requests); |
|
req != NULL && req->id != id; |
|
req = TAILQ_NEXT(req, tq)) |
|
; |
|
if (req == NULL) |
|
fatal("Unexpected reply %u", id); |
|
|
|
switch (type) { |
|
case SSH2_FXP_STATUS: |
status = buffer_get_int(&msg); |
status = buffer_get_int(&msg); |
|
if (status != SSH2_FX_EOF) |
|
read_error = 1; |
|
max_req = 0; |
|
TAILQ_REMOVE(&requests, req, tq); |
|
xfree(req); |
|
num_req--; |
|
break; |
|
case SSH2_FXP_DATA: |
|
data = buffer_get_string(&msg, &len); |
|
debug3("Received data %llu -> %llu", |
|
(unsigned long long)req->offset, |
|
(unsigned long long)req->offset + len - 1); |
|
if (len > req->len) |
|
fatal("Received more data than asked for " |
|
"%d > %d", len, req->len); |
|
if ((lseek(local_fd, req->offset, SEEK_SET) == -1 || |
|
atomicio(write, local_fd, data, len) != len) && |
|
!write_error) { |
|
write_errno = errno; |
|
write_error = 1; |
|
max_req = 0; |
|
} |
|
xfree(data); |
|
|
if (status == SSH2_FX_EOF) |
if (len == req->len) { |
break; |
TAILQ_REMOVE(&requests, req, tq); |
else { |
xfree(req); |
error("Couldn't read from remote " |
num_req--; |
"file \"%s\" : %s", remote_path, |
} else { |
fx2txt(status)); |
/* Resend the request for the missing data */ |
do_close(fd_in, fd_out, handle, handle_len); |
debug3("Short data block, re-requesting " |
goto done; |
"%llu -> %llu (%2d)", |
|
(unsigned long long)req->offset + len, |
|
(unsigned long long)req->offset + |
|
req->len - 1, num_req); |
|
req->id = conn->msg_id++; |
|
req->len -= len; |
|
req->offset += len; |
|
send_read_request(conn->fd_out, req->id, |
|
req->offset, req->len, handle, handle_len); |
|
/* Reduce the request size */ |
|
if (len < buflen) |
|
buflen = MAX(MIN_READ_SIZE, len); |
} |
} |
} else if (type != SSH2_FXP_DATA) { |
if (max_req > 0) { /* max_req = 0 iff EOF received */ |
|
if (size > 0 && offset > size) { |
|
/* Only one request at a time |
|
* after the expected EOF */ |
|
debug3("Finish at %llu (%2d)", |
|
(unsigned long long)offset, |
|
num_req); |
|
max_req = 1; |
|
} |
|
else if (max_req < conn->num_requests + 1) { |
|
++max_req; |
|
} |
|
} |
|
break; |
|
default: |
fatal("Expected SSH2_FXP_DATA(%d) packet, got %d", |
fatal("Expected SSH2_FXP_DATA(%d) packet, got %d", |
SSH2_FXP_DATA, type); |
SSH2_FXP_DATA, type); |
} |
} |
|
} |
|
|
data = buffer_get_string(&msg, &len); |
/* Sanity check */ |
if (len > COPY_SIZE) |
if (TAILQ_FIRST(&requests) != NULL) |
fatal("Received more data than asked for %d > %d", |
fatal("Transfer complete, but requests still in queue"); |
len, COPY_SIZE); |
|
|
|
debug3("In read loop, got %d offset %llu", len, |
if (read_error) { |
(unsigned long long)offset); |
error("Couldn't read from remote file \"%s\" : %s", |
if (atomicio(write, local_fd, data, len) != len) { |
remote_path, fx2txt(status)); |
error("Couldn't write to \"%s\": %s", local_path, |
do_close(conn, handle, handle_len); |
strerror(errno)); |
} else if (write_error) { |
do_close(fd_in, fd_out, handle, handle_len); |
error("Couldn't write to \"%s\": %s", local_path, |
status = -1; |
strerror(write_errno)); |
xfree(data); |
status = -1; |
goto done; |
do_close(conn, handle, handle_len); |
} |
} else { |
|
status = do_close(conn, handle, handle_len); |
|
|
offset += len; |
/* Override umask and utimes if asked */ |
xfree(data); |
if (pflag && fchmod(local_fd, mode) == -1) |
|
error("Couldn't set mode on \"%s\": %s", local_path, |
|
strerror(errno)); |
|
if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { |
|
struct timeval tv[2]; |
|
tv[0].tv_sec = a->atime; |
|
tv[1].tv_sec = a->mtime; |
|
tv[0].tv_usec = tv[1].tv_usec = 0; |
|
if (utimes(local_path, tv) == -1) |
|
error("Can't set times on \"%s\": %s", |
|
local_path, strerror(errno)); |
|
} |
} |
} |
status = do_close(fd_in, fd_out, handle, handle_len); |
|
|
|
/* Override umask and utimes if asked */ |
|
if (pflag && fchmod(local_fd, mode) == -1) |
|
error("Couldn't set mode on \"%s\": %s", local_path, |
|
strerror(errno)); |
|
if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { |
|
struct timeval tv[2]; |
|
tv[0].tv_sec = a->atime; |
|
tv[1].tv_sec = a->mtime; |
|
tv[0].tv_usec = tv[1].tv_usec = 0; |
|
if (utimes(local_path, tv) == -1) |
|
error("Can't set times on \"%s\": %s", local_path, |
|
strerror(errno)); |
|
} |
|
|
|
done: |
|
close(local_fd); |
close(local_fd); |
buffer_free(&msg); |
buffer_free(&msg); |
xfree(handle); |
xfree(handle); |
return status; |
|
|
return(status); |
} |
} |
|
|
int |
int |
do_upload(int fd_in, int fd_out, char *local_path, char *remote_path, |
do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, |
int pflag) |
int pflag) |
{ |
{ |
int local_fd; |
int local_fd, status; |
u_int handle_len, id; |
u_int handle_len, id, type; |
u_int64_t offset; |
u_int64_t offset; |
char *handle; |
char *handle, *data; |
Buffer msg; |
Buffer msg; |
struct stat sb; |
struct stat sb; |
Attrib a; |
Attrib a; |
int status; |
u_int32_t startid; |
|
u_int32_t ackid; |
|
struct outstanding_ack { |
|
u_int id; |
|
u_int len; |
|
u_int64_t offset; |
|
TAILQ_ENTRY(outstanding_ack) tq; |
|
}; |
|
TAILQ_HEAD(ackhead, outstanding_ack) acks; |
|
struct outstanding_ack *ack; |
|
|
|
TAILQ_INIT(&acks); |
|
|
if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) { |
if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) { |
error("Couldn't open local file \"%s\" for reading: %s", |
error("Couldn't open local file \"%s\" for reading: %s", |
local_path, strerror(errno)); |
local_path, strerror(errno)); |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
|
|
/* Send open request */ |
/* Send open request */ |
id = msg_id++; |
id = conn->msg_id++; |
buffer_put_char(&msg, SSH2_FXP_OPEN); |
buffer_put_char(&msg, SSH2_FXP_OPEN); |
buffer_put_int(&msg, id); |
buffer_put_int(&msg, id); |
buffer_put_cstring(&msg, remote_path); |
buffer_put_cstring(&msg, remote_path); |
buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC); |
buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC); |
encode_attrib(&msg, &a); |
encode_attrib(&msg, &a); |
send_msg(fd_out, &msg); |
send_msg(conn->fd_out, &msg); |
debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path); |
debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path); |
|
|
buffer_clear(&msg); |
buffer_clear(&msg); |
|
|
handle = get_handle(fd_in, id, &handle_len); |
handle = get_handle(conn->fd_in, id, &handle_len); |
if (handle == NULL) { |
if (handle == NULL) { |
close(local_fd); |
close(local_fd); |
buffer_free(&msg); |
buffer_free(&msg); |
return(-1); |
return(-1); |
} |
} |
|
|
|
startid = ackid = id + 1; |
|
data = xmalloc(conn->transfer_buflen); |
|
|
/* Read from local and write to remote */ |
/* Read from local and write to remote */ |
offset = 0; |
offset = 0; |
for(;;) { |
for (;;) { |
int len; |
int len; |
char data[COPY_SIZE]; |
|
|
|
/* |
/* |
* Can't use atomicio here because it returns 0 on EOF, thus losing |
* Can't use atomicio here because it returns 0 on EOF, thus losing |
* the last block of the file |
* the last block of the file |
*/ |
*/ |
do |
do |
len = read(local_fd, data, COPY_SIZE); |
len = read(local_fd, data, conn->transfer_buflen); |
while ((len == -1) && (errno == EINTR || errno == EAGAIN)); |
while ((len == -1) && (errno == EINTR || errno == EAGAIN)); |
|
|
if (len == -1) |
if (len == -1) |
fatal("Couldn't read from \"%s\": %s", local_path, |
fatal("Couldn't read from \"%s\": %s", local_path, |
strerror(errno)); |
strerror(errno)); |
if (len == 0) |
|
|
if (len != 0) { |
|
ack = xmalloc(sizeof(*ack)); |
|
ack->id = ++id; |
|
ack->offset = offset; |
|
ack->len = len; |
|
TAILQ_INSERT_TAIL(&acks, ack, tq); |
|
|
|
buffer_clear(&msg); |
|
buffer_put_char(&msg, SSH2_FXP_WRITE); |
|
buffer_put_int(&msg, ack->id); |
|
buffer_put_string(&msg, handle, handle_len); |
|
buffer_put_int64(&msg, offset); |
|
buffer_put_string(&msg, data, len); |
|
send_msg(conn->fd_out, &msg); |
|
debug3("Sent message SSH2_FXP_WRITE I:%d O:%llu S:%u", |
|
id, (unsigned long long)offset, len); |
|
} else if (TAILQ_FIRST(&acks) == NULL) |
break; |
break; |
|
|
buffer_clear(&msg); |
if (ack == NULL) |
buffer_put_char(&msg, SSH2_FXP_WRITE); |
fatal("Unexpected ACK %u", id); |
buffer_put_int(&msg, ++id); |
|
buffer_put_string(&msg, handle, handle_len); |
|
buffer_put_int64(&msg, offset); |
|
buffer_put_string(&msg, data, len); |
|
send_msg(fd_out, &msg); |
|
debug3("Sent message SSH2_FXP_WRITE I:%d O:%llu S:%u", |
|
id, (unsigned long long)offset, len); |
|
|
|
status = get_status(fd_in, id); |
if (id == startid || len == 0 || |
if (status != SSH2_FX_OK) { |
id - ackid >= conn->num_requests) { |
error("Couldn't write to remote file \"%s\": %s", |
u_int r_id; |
remote_path, fx2txt(status)); |
|
do_close(fd_in, fd_out, handle, handle_len); |
|
close(local_fd); |
|
goto done; |
|
} |
|
debug3("In write loop, got %d offset %llu", len, |
|
(unsigned long long)offset); |
|
|
|
|
buffer_clear(&msg); |
|
get_msg(conn->fd_in, &msg); |
|
type = buffer_get_char(&msg); |
|
r_id = buffer_get_int(&msg); |
|
|
|
if (type != SSH2_FXP_STATUS) |
|
fatal("Expected SSH2_FXP_STATUS(%d) packet, " |
|
"got %d", SSH2_FXP_STATUS, type); |
|
|
|
status = buffer_get_int(&msg); |
|
debug3("SSH2_FXP_STATUS %d", status); |
|
|
|
/* Find the request in our queue */ |
|
for(ack = TAILQ_FIRST(&acks); |
|
ack != NULL && ack->id != r_id; |
|
ack = TAILQ_NEXT(ack, tq)) |
|
; |
|
if (ack == NULL) |
|
fatal("Can't find request for ID %d", r_id); |
|
TAILQ_REMOVE(&acks, ack, tq); |
|
|
|
if (status != SSH2_FX_OK) { |
|
error("Couldn't write to remote file \"%s\": %s", |
|
remote_path, fx2txt(status)); |
|
do_close(conn, handle, handle_len); |
|
close(local_fd); |
|
goto done; |
|
} |
|
debug3("In write loop, ack for %u %d bytes at %llu", |
|
ack->id, ack->len, (unsigned long long)ack->offset); |
|
++ackid; |
|
free(ack); |
|
} |
offset += len; |
offset += len; |
} |
} |
|
xfree(data); |
|
|
if (close(local_fd) == -1) { |
if (close(local_fd) == -1) { |
error("Couldn't close local file \"%s\": %s", local_path, |
error("Couldn't close local file \"%s\": %s", local_path, |
strerror(errno)); |
strerror(errno)); |
do_close(fd_in, fd_out, handle, handle_len); |
do_close(conn, handle, handle_len); |
status = -1; |
status = -1; |
goto done; |
goto done; |
} |
} |
|
|
/* Override umask and utimes if asked */ |
/* Override umask and utimes if asked */ |
if (pflag) |
if (pflag) |
do_fsetstat(fd_in, fd_out, handle, handle_len, &a); |
do_fsetstat(conn, handle, handle_len, &a); |
|
|
status = do_close(fd_in, fd_out, handle, handle_len); |
status = do_close(conn, handle, handle_len); |
|
|
done: |
done: |
xfree(handle); |
xfree(handle); |
buffer_free(&msg); |
buffer_free(&msg); |
return status; |
return(status); |
} |
} |
|
|