summaryrefslogtreecommitdiff
path: root/usr.bin/ssh/sftp-client.c
diff options
context:
space:
mode:
authorDamien Miller <djm@cvs.openbsd.org>2001-02-04 11:11:57 +0000
committerDamien Miller <djm@cvs.openbsd.org>2001-02-04 11:11:57 +0000
commitecfa21621996251ce86b6a655e6840d7919f8911 (patch)
treecc962fade84e3da514ee643a0ea44214f7b47a3c /usr.bin/ssh/sftp-client.c
parent70ffcbe812c4c3495b896aff7e333a1570a5e342 (diff)
Basic interactive sftp client; ok theo@
Diffstat (limited to 'usr.bin/ssh/sftp-client.c')
-rw-r--r--usr.bin/ssh/sftp-client.c792
1 files changed, 792 insertions, 0 deletions
diff --git a/usr.bin/ssh/sftp-client.c b/usr.bin/ssh/sftp-client.c
new file mode 100644
index 00000000000..458d7364a4f
--- /dev/null
+++ b/usr.bin/ssh/sftp-client.c
@@ -0,0 +1,792 @@
+/*
+ * Copyright (c) 2001 Damien Miller. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* XXX: memleaks */
+/* XXX: signed vs unsigned */
+/* XXX: redesign to allow concurrent overlapped operations */
+/* XXX: we use fatal too much, error may be more appropriate in places */
+/* XXX: copy between two remote sites */
+
+#include "includes.h"
+RCSID("$OpenBSD: sftp-client.c,v 1.1 2001/02/04 11:11:54 djm Exp $");
+
+#include "ssh.h"
+#include "buffer.h"
+#include "bufaux.h"
+#include "getput.h"
+#include "xmalloc.h"
+#include "log.h"
+#include "atomicio.h"
+#include "pathnames.h"
+
+#include "sftp.h"
+#include "sftp-common.h"
+#include "sftp-client.h"
+
+/* How much data to read/write at at time during copies */
+/* XXX: what should this be? */
+#define COPY_SIZE 8192
+
+void
+send_msg(int fd, Buffer *m)
+{
+ int mlen = buffer_len(m);
+ int len;
+ Buffer oqueue;
+
+ buffer_init(&oqueue);
+ buffer_put_int(&oqueue, mlen);
+ buffer_append(&oqueue, buffer_ptr(m), mlen);
+ buffer_consume(m, mlen);
+
+ len = atomicio(write, fd, buffer_ptr(&oqueue), buffer_len(&oqueue));
+ if (len <= 0)
+ fatal("Couldn't send packet: %s", strerror(errno));
+
+ buffer_free(&oqueue);
+}
+
+void
+get_msg(int fd, Buffer *m)
+{
+ u_int len, msg_len;
+ unsigned char buf[4096];
+
+ len = atomicio(read, fd, buf, 4);
+ if (len != 4)
+ fatal("Couldn't read packet: %s", strerror(errno));
+
+ msg_len = GET_32BIT(buf);
+ if (msg_len > 256 * 1024)
+ fatal("Received message too long %d", msg_len);
+
+ while (msg_len) {
+ len = atomicio(read, fd, buf, MIN(msg_len, sizeof(buf)));
+ if (len <= 0)
+ fatal("Couldn't read packet: %s", strerror(errno));
+
+ msg_len -= len;
+ buffer_append(m, buf, len);
+ }
+}
+
+void
+send_string_request(int fd, u_int id, u_int code, char *s,
+ u_int len)
+{
+ Buffer msg;
+
+ buffer_init(&msg);
+ buffer_put_char(&msg, code);
+ buffer_put_int(&msg, id);
+ buffer_put_string(&msg, s, len);
+ send_msg(fd, &msg);
+ debug3("Sent message fd %d T:%d I:%d", fd, code, id);
+ buffer_free(&msg);
+}
+
+void
+send_string_attrs_request(int fd, u_int id, u_int code, char *s,
+ u_int len, Attrib *a)
+{
+ Buffer msg;
+
+ buffer_init(&msg);
+ buffer_put_char(&msg, code);
+ buffer_put_int(&msg, id);
+ buffer_put_string(&msg, s, len);
+ encode_attrib(&msg, a);
+ send_msg(fd, &msg);
+ debug3("Sent message fd %d T:%d I:%d", fd, code, id);
+ buffer_free(&msg);
+}
+
+u_int
+get_status(int fd, int expected_id)
+{
+ Buffer msg;
+ u_int type, id, status;
+
+ buffer_init(&msg);
+ get_msg(fd, &msg);
+ type = buffer_get_char(&msg);
+ id = buffer_get_int(&msg);
+
+ if (id != expected_id)
+ fatal("ID mismatch (%d != %d)", id, expected_id);
+ if (type != SSH2_FXP_STATUS)
+ fatal("Expected SSH2_FXP_STATUS(%d) packet, got %d",
+ SSH2_FXP_STATUS, type);
+
+ status = buffer_get_int(&msg);
+ buffer_free(&msg);
+
+ debug3("SSH2_FXP_STATUS %d", status);
+
+ return(status);
+}
+
+char *
+get_handle(int fd, u_int expected_id, u_int *len)
+{
+ Buffer msg;
+ u_int type, id;
+ char *handle;
+
+ buffer_init(&msg);
+ get_msg(fd, &msg);
+ type = buffer_get_char(&msg);
+ id = buffer_get_int(&msg);
+
+ if (id != expected_id)
+ fatal("ID mismatch (%d != %d)", id, expected_id);
+ if (type == SSH2_FXP_STATUS) {
+ int status = buffer_get_int(&msg);
+
+ error("Couldn't get handle: %s", fx2txt(status));
+ return(NULL);
+ } else if (type != SSH2_FXP_HANDLE)
+ fatal("Expected SSH2_FXP_HANDLE(%d) packet, got %d",
+ SSH2_FXP_HANDLE, type);
+
+ handle = buffer_get_string(&msg, len);
+ buffer_free(&msg);
+
+ return(handle);
+}
+
+Attrib *
+get_decode_stat(int fd, u_int expected_id)
+{
+ Buffer msg;
+ u_int type, id;
+ Attrib *a;
+
+ buffer_init(&msg);
+ get_msg(fd, &msg);
+
+ type = buffer_get_char(&msg);
+ id = buffer_get_int(&msg);
+
+ debug3("Received stat reply T:%d I:%d", type, id);
+ if (id != expected_id)
+ fatal("ID mismatch (%d != %d)", id, expected_id);
+ if (type == SSH2_FXP_STATUS) {
+ int status = buffer_get_int(&msg);
+
+ error("Couldn't stat remote file: %s", fx2txt(status));
+ return(NULL);
+ } else if (type != SSH2_FXP_ATTRS) {
+ fatal("Expected SSH2_FXP_ATTRS(%d) packet, got %d",
+ SSH2_FXP_ATTRS, type);
+ }
+ a = decode_attrib(&msg);
+ buffer_free(&msg);
+
+ return(a);
+}
+
+int
+do_init(int fd_in, int fd_out)
+{
+ int type, version;
+ Buffer msg;
+
+ buffer_init(&msg);
+ buffer_put_char(&msg, SSH2_FXP_INIT);
+ buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
+ send_msg(fd_out, &msg);
+
+ buffer_clear(&msg);
+
+ get_msg(fd_in, &msg);
+
+ /* Expecting a VERSION reply */
+ if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) {
+ error("Invalid packet back from SSH2_FXP_INIT (type %d)",
+ type);
+ buffer_free(&msg);
+ return(-1);
+ }
+ version = buffer_get_int(&msg);
+
+ debug2("Remote version: %d", version);
+
+ /* Check for extensions */
+ while (buffer_len(&msg) > 0) {
+ char *name = buffer_get_string(&msg, NULL);
+ char *value = buffer_get_string(&msg, NULL);
+
+ debug2("Init extension: \"%s\"", name);
+ xfree(name);
+ xfree(value);
+ }
+
+ buffer_free(&msg);
+ return(0);
+}
+
+int
+do_close(int fd_in, int fd_out, char *handle, u_int handle_len)
+{
+ u_int id, status;
+ Buffer msg;
+
+ buffer_init(&msg);
+
+ id = arc4random();
+ buffer_put_char(&msg, SSH2_FXP_CLOSE);
+ buffer_put_int(&msg, id);
+ buffer_put_string(&msg, handle, handle_len);
+ send_msg(fd_out, &msg);
+ debug3("Sent message SSH2_FXP_CLOSE I:%d", id);
+
+ status = get_status(fd_in, id);
+ if (status != SSH2_FX_OK)
+ error("Couldn't close file: %s", fx2txt(status));
+
+ buffer_free(&msg);
+
+ return(status);
+}
+
+int
+do_ls(int fd_in, int fd_out, char *path)
+{
+ Buffer msg;
+ u_int type, id, handle_len, i, expected_id;
+ char *handle;
+
+ id = arc4random();
+
+ buffer_init(&msg);
+ buffer_put_char(&msg, SSH2_FXP_OPENDIR);
+ buffer_put_int(&msg, id);
+ buffer_put_cstring(&msg, path);
+ send_msg(fd_out, &msg);
+
+ buffer_clear(&msg);
+
+ handle = get_handle(fd_in, id, &handle_len);
+ if (handle == NULL)
+ return(-1);
+
+ for(;;) {
+ int count;
+
+ expected_id = ++id;
+
+ debug3("Sending SSH2_FXP_READDIR I:%d", id);
+
+ buffer_clear(&msg);
+ buffer_put_char(&msg, SSH2_FXP_READDIR);
+ buffer_put_int(&msg, id);
+ buffer_put_string(&msg, handle, handle_len);
+ send_msg(fd_out, &msg);
+
+ buffer_clear(&msg);
+
+ get_msg(fd_in, &msg);
+
+ type = buffer_get_char(&msg);
+ id = buffer_get_int(&msg);
+
+ debug3("Received reply T:%d I:%d", type, id);
+
+ if (id != expected_id)
+ fatal("ID mismatch (%d != %d)", id, expected_id);
+
+ if (type == SSH2_FXP_STATUS) {
+ int status = buffer_get_int(&msg);
+
+ debug3("Received SSH2_FXP_STATUS %d", status);
+
+ if (status == SSH2_FX_EOF) {
+ break;
+ } else {
+ error("Couldn't read directory: %s",
+ fx2txt(status));
+ do_close(fd_in, fd_out, handle, handle_len);
+ return(NULL);
+ }
+ } else if (type != SSH2_FXP_NAME)
+ fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
+ SSH2_FXP_NAME, type);
+
+ count = buffer_get_int(&msg);
+ debug3("Received %i SSH2_FXP_NAME responses", count);
+ for(i = 0; i < count; i++) {
+ char *filename, *longname;
+ Attrib *a;
+
+ filename = buffer_get_string(&msg, NULL);
+ longname = buffer_get_string(&msg, NULL);
+ a = decode_attrib(&msg);
+
+ printf("%s\n", longname);
+
+ xfree(filename);
+ xfree(longname);
+ }
+ }
+
+ buffer_free(&msg);
+ do_close(fd_in, fd_out, handle, handle_len);
+ xfree(handle);
+
+ return(0);
+}
+
+int
+do_rm(int fd_in, int fd_out, char *path)
+{
+ u_int status, id;
+
+ debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
+
+ id = arc4random();
+ send_string_request(fd_out, id, SSH2_FXP_REMOVE, path, strlen(path));
+ status = get_status(fd_in, id);
+ if (status != SSH2_FX_OK)
+ error("Couldn't delete file: %s", fx2txt(status));
+ return(status);
+}
+
+int
+do_mkdir(int fd_in, int fd_out, char *path, Attrib *a)
+{
+ u_int status, id;
+
+ id = arc4random();
+ send_string_attrs_request(fd_out, id, SSH2_FXP_MKDIR, path,
+ strlen(path), a);
+
+ status = get_status(fd_in, id);
+ if (status != SSH2_FX_OK)
+ error("Couldn't create directory: %s", fx2txt(status));
+
+ return(status);
+}
+
+int
+do_rmdir(int fd_in, int fd_out, char *path)
+{
+ u_int status, id;
+
+ id = arc4random();
+ send_string_request(fd_out, id, SSH2_FXP_RMDIR, path, strlen(path));
+
+ status = get_status(fd_in, id);
+ if (status != SSH2_FX_OK)
+ error("Couldn't remove directory: %s", fx2txt(status));
+
+ return(status);
+}
+
+Attrib *
+do_stat(int fd_in, int fd_out, char *path)
+{
+ u_int id;
+
+ id = arc4random();
+ send_string_request(fd_out, id, SSH2_FXP_STAT, path, strlen(path));
+ return(get_decode_stat(fd_in, id));
+}
+
+Attrib *
+do_lstat(int fd_in, int fd_out, char *path)
+{
+ u_int id;
+
+ id = arc4random();
+ send_string_request(fd_out, id, SSH2_FXP_LSTAT, path, strlen(path));
+ return(get_decode_stat(fd_in, id));
+}
+
+Attrib *
+do_fstat(int fd_in, int fd_out, char *handle,
+ u_int handle_len)
+{
+ u_int id;
+
+ id = arc4random();
+ send_string_request(fd_out, id, SSH2_FXP_FSTAT, handle, handle_len);
+ return(get_decode_stat(fd_in, id));
+}
+
+int
+do_setstat(int fd_in, int fd_out, char *path, Attrib *a)
+{
+ u_int status, id;
+
+ id = arc4random();
+ send_string_attrs_request(fd_out, id, SSH2_FXP_SETSTAT, path,
+ strlen(path), a);
+
+ status = get_status(fd_in, id);
+ if (status != SSH2_FX_OK)
+ error("Couldn't setstat on \"%s\": %s", path,
+ fx2txt(status));
+
+ return(status);
+}
+
+int
+do_fsetstat(int fd_in, int fd_out, char *handle, u_int handle_len,
+ Attrib *a)
+{
+ u_int status, id;
+
+ id = arc4random();
+ send_string_attrs_request(fd_out, id, SSH2_FXP_FSETSTAT, handle,
+ handle_len, a);
+
+ status = get_status(fd_in, id);
+ if (status != SSH2_FX_OK)
+ error("Couldn't fsetstat: %s", fx2txt(status));
+
+ return(status);
+}
+
+char *
+do_realpath(int fd_in, int fd_out, char *path)
+{
+ Buffer msg;
+ u_int type, expected_id, count, id;
+ char *filename, *longname;
+ Attrib *a;
+
+ expected_id = id = arc4random();
+ send_string_request(fd_out, id, SSH2_FXP_REALPATH, path,
+ strlen(path));
+
+ buffer_init(&msg);
+
+ get_msg(fd_in, &msg);
+ type = buffer_get_char(&msg);
+ id = buffer_get_int(&msg);
+
+ if (id != expected_id)
+ fatal("ID mismatch (%d != %d)", id, expected_id);
+
+ if (type == SSH2_FXP_STATUS) {
+ u_int status = buffer_get_int(&msg);
+
+ error("Couldn't canonicalise: %s", fx2txt(status));
+ return(NULL);
+ } else if (type != SSH2_FXP_NAME)
+ fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
+ SSH2_FXP_NAME, type);
+
+ count = buffer_get_int(&msg);
+ if (count != 1)
+ fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
+
+ filename = buffer_get_string(&msg, NULL);
+ longname = buffer_get_string(&msg, NULL);
+ a = decode_attrib(&msg);
+
+ debug3("SSH_FXP_REALPATH %s -> %s", path, filename);
+
+ xfree(longname);
+
+ buffer_free(&msg);
+
+ return(filename);
+}
+
+int
+do_rename(int fd_in, int fd_out, char *oldpath, char *newpath)
+{
+ Buffer msg;
+ u_int status, id;
+
+ buffer_init(&msg);
+
+ /* Send rename request */
+ id = arc4random();
+ buffer_put_char(&msg, SSH2_FXP_RENAME);
+ buffer_put_int(&msg, id);
+ buffer_put_cstring(&msg, oldpath);
+ buffer_put_cstring(&msg, newpath);
+ send_msg(fd_out, &msg);
+ debug3("Sent message SSH2_FXP_RENAME \"%s\" -> \"%s\"", oldpath,
+ newpath);
+ buffer_free(&msg);
+
+ status = get_status(fd_in, id);
+ if (status != SSH2_FX_OK)
+ error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, newpath,
+ fx2txt(status));
+
+ return(status);
+}
+
+int
+do_download(int fd_in, int fd_out, char *remote_path, char *local_path,
+ 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;
+
+ a = do_stat(fd_in, fd_out, remote_path);
+ if (a == NULL)
+ return(-1);
+
+ /* XXX: should we preserve set[ug]id? */
+ if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
+ mode = S_IWRITE | (a->perm & 0777);
+ else
+ mode = 0666;
+
+ 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));
+ return(errno);
+ }
+
+ /* 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;
+
+ tv.tv_sec = a->atime;
+ tv.tv_usec = a->mtime;
+ if (utimes(local_path, &tv) == -1)
+ error("Can't set times on \"%s\": %s", local_path,
+ strerror(errno));
+ }
+
+ buffer_init(&msg);
+
+ /* Send open request */
+ id = arc4random();
+ buffer_put_char(&msg, SSH2_FXP_OPEN);
+ buffer_put_int(&msg, id);
+ buffer_put_cstring(&msg, remote_path);
+ buffer_put_int(&msg, SSH2_FXF_READ);
+ attrib_clear(&junk); /* Send empty attributes */
+ encode_attrib(&msg, &junk);
+ send_msg(fd_out, &msg);
+ debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path);
+
+ handle = get_handle(fd_in, id, &handle_len);
+ if (handle == NULL) {
+ buffer_free(&msg);
+ close(local_fd);
+ return(-1);
+ }
+
+ /* Read from remote and write to local */
+ offset = 0;
+ for(;;) {
+ u_int len;
+ char *data;
+
+ expected_id = ++id;
+
+ 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, COPY_SIZE);
+ send_msg(fd_out, &msg);
+ debug3("Sent message SSH2_FXP_READ I:%d O:%llu S:%u",
+ id, offset, COPY_SIZE);
+
+ buffer_clear(&msg);
+
+ get_msg(fd_in, &msg);
+ type = buffer_get_char(&msg);
+ id = buffer_get_int(&msg);
+ debug3("Received reply T:%d I:%d", type, id);
+ if (id != expected_id)
+ fatal("ID mismatch (%d != %d)", id, expected_id);
+ if (type == SSH2_FXP_STATUS) {
+ int status = buffer_get_int(&msg);
+
+ if (status == SSH2_FX_EOF)
+ break;
+ else {
+ error("Couldn't read from remote "
+ "file \"%s\" : %s", remote_path,
+ fx2txt(status));
+ do_close(fd_in, fd_out, handle, handle_len);
+ xfree(handle);
+ close(local_fd);
+ buffer_free(&msg);
+ return(status);
+ }
+ } else if (type != SSH2_FXP_DATA) {
+ fatal("Expected SSH2_FXP_DATA(%d) packet, got %d",
+ SSH2_FXP_DATA, type);
+ }
+
+ data = buffer_get_string(&msg, &len);
+ if (len > COPY_SIZE)
+ fatal("Received more data than asked for %d > %d",
+ len, COPY_SIZE);
+
+ debug3("In read loop, got %d offset %lld", len, offset);
+ if (atomicio(write, local_fd, data, len) != len) {
+ error("Couldn't write to \"%s\": %s", local_path,
+ strerror(errno));
+ do_close(fd_in, fd_out, handle, handle_len);
+ xfree(handle);
+ close(local_fd);
+ xfree(data);
+ buffer_free(&msg);
+ return(-1);
+ }
+
+ offset += len;
+ xfree(data);
+ }
+ xfree(handle);
+ buffer_free(&msg);
+ close(local_fd);
+
+ return(do_close(fd_in, fd_out, handle, handle_len));
+}
+
+int
+do_upload(int fd_in, int fd_out, char *local_path, char *remote_path,
+ int pflag)
+{
+ int local_fd;
+ u_int handle_len, id;
+ u_int64_t offset;
+ char *handle;
+ Buffer msg;
+ struct stat sb;
+ Attrib a;
+
+ if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
+ error("Couldn't open local file \"%s\" for reading: %s",
+ local_path, strerror(errno));
+ return(-1);
+ }
+ if (fstat(local_fd, &sb) == -1) {
+ error("Couldn't fstat local file \"%s\": %s",
+ local_path, strerror(errno));
+ close(local_fd);
+ return(-1);
+ }
+ stat_to_attrib(&sb, &a);
+
+ a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
+ a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
+ a.perm &= 0777;
+ if (!pflag)
+ a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
+
+ buffer_init(&msg);
+
+ /* Send open request */
+ id = arc4random();
+ buffer_put_char(&msg, SSH2_FXP_OPEN);
+ buffer_put_int(&msg, id);
+ buffer_put_cstring(&msg, remote_path);
+ buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC);
+ encode_attrib(&msg, &a);
+ send_msg(fd_out, &msg);
+ debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path);
+
+ buffer_clear(&msg);
+
+ handle = get_handle(fd_in, id, &handle_len);
+ if (handle == NULL) {
+ close(local_fd);
+ buffer_free(&msg);
+ return(-1);
+ }
+
+ /* Override umask and utimes if asked */
+ if (pflag)
+ do_fsetstat(fd_in, fd_out, handle, handle_len, &a);
+
+ /* Read from local and write to remote */
+ offset = 0;
+ for(;;) {
+ int len;
+ char data[COPY_SIZE];
+ u_int status;
+
+ /*
+ * Can't use atomicio here because it returns 0 on EOF, thus losing
+ * the last block of the file
+ */
+ do
+ len = read(local_fd, data, COPY_SIZE);
+ while ((len == -1) && (errno == EINTR || errno == EAGAIN));
+
+ if (len == -1)
+ fatal("Couldn't read from \"%s\": %s", local_path,
+ strerror(errno));
+ if (len == 0)
+ break;
+
+ buffer_clear(&msg);
+ buffer_put_char(&msg, SSH2_FXP_WRITE);
+ 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, offset, len);
+
+ status = get_status(fd_in, id);
+ if (status != SSH2_FX_OK) {
+ error("Couldn't write to remote file \"%s\": %s",
+ remote_path, fx2txt(status));
+ do_close(fd_in, fd_out, handle, handle_len);
+ xfree(handle);
+ close(local_fd);
+ return(-1);
+ }
+ debug3("In write loop, got %d offset %lld", len, offset);
+
+ offset += len;
+ }
+ xfree(handle);
+ buffer_free(&msg);
+
+ if (close(local_fd) == -1) {
+ error("Couldn't close local file \"%s\": %s", local_path,
+ strerror(errno));
+ do_close(fd_in, fd_out, handle, handle_len);
+ return(-1);
+ }
+
+ return(do_close(fd_in, fd_out, handle, handle_len));
+}