SFTP Status Bar..

mouring mouring at etoh.eviladmin.org
Thu Feb 7 08:29:48 EST 2002


> For large files the bar stops at the second * and then does not update
> until the transfer is complete (put).
> 
That can easily be resolved.. Just unsure why the same code for
scp does not do the samething.  The solution is to remove the
alarm() and family and just move the callback into the big transfer
loop.  Not my favorate idea because it updates TOO quickly IMHO.

Damien, what are you views on how the sftp progressmeter callback
should work?

The reason why 'get' for a single case did not show the bar was because
<smile> I put NULL instead of the callback function and forgot to
change it back.

> Also, I did "put *"; * matched, among other things, a directory, and
> sftp exited (not cored).
>
sftp-int.c:/* XXX: recursive operations */

Would you like to implement it?
 
> It would be nice if the protocol had a find-style protocol command.
> 
UGH.. You want sftpfs not sftp. =P

> It would also be nice if the client could copy entire filsystem
> structures.
> 
See above comment about "put *"

> It would also be nice if the protocol supported rdiff-style ops (i.e.,
> get rsync signature for a remote file, get delta for a remote file given
> a local file's rsync signature, patch a file with an rsync patch file).
> 
See above comment about sftpfs.

> Then we could have a clean replacement for rsync :)
> 
> The protocol wishlist belongs at the IETF SECSH WG, I know.
> 

Attacked is a new diff against sftp-int.c and sftp-client.c.  Restore
your old version first before applying (I won't do intermediate diffs
too much of a pain <smile>).

corrects:
 - put single request uses callback
 - callback moved into big data push/pull loops for assured updated.

- Ben

--- sftp-int.c	Wed Feb  6 15:14:05 2002
+++ sftp-client.c	Wed Feb  6 15:16:28 2002
@@ -22,918 +22,923 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-/* XXX: globbed ls */
-/* XXX: recursive operations */
+/* 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-int.c,v 1.41 2001/12/19 07:18:56 deraadt Exp $");
-
-#include <glob.h>
+RCSID("$OpenBSD: sftp-client.c,v 1.19 2001/12/19 07:18:56 deraadt Exp $");
 
 #include "buffer.h"
+#include "bufaux.h"
+#include "getput.h"
 #include "xmalloc.h"
 #include "log.h"
-#include "pathnames.h"
-#include "misc.h"
+#include "atomicio.h"
 
 #include "sftp.h"
 #include "sftp-common.h"
-#include "sftp-glob.h"
 #include "sftp-client.h"
-#include "sftp-int.h"
-
-/* File to read commands from */
-extern FILE *infile;
 
-/* Version of server we are speaking to */
-int version;
-
-/* Seperators for interactive commands */
-#define WHITESPACE " \t\r\n"
-
-/* Commands for interactive mode */
-#define I_CHDIR		1
-#define I_CHGRP		2
-#define I_CHMOD		3
-#define I_CHOWN		4
-#define I_GET		5
-#define I_HELP		6
-#define I_LCHDIR	7
-#define I_LLS		8
-#define I_LMKDIR	9
-#define I_LPWD		10
-#define I_LS		11
-#define I_LUMASK	12
-#define I_MKDIR		13
-#define I_PUT		14
-#define I_PWD		15
-#define I_QUIT		16
-#define I_RENAME	17
-#define I_RM		18
-#define I_RMDIR		19
-#define I_SHELL		20
-#define I_SYMLINK	21
-#define I_VERSION	22
-
-struct CMD {
-	const char *c;
-	const int n;
-};
-
-const struct CMD cmds[] = {
-	{ "bye",	I_QUIT },
-	{ "cd",		I_CHDIR },
-	{ "chdir",	I_CHDIR },
-	{ "chgrp",	I_CHGRP },
-	{ "chmod",	I_CHMOD },
-	{ "chown",	I_CHOWN },
-	{ "dir",	I_LS },
-	{ "exit",	I_QUIT },
-	{ "get",	I_GET },
-	{ "mget",	I_GET },
-	{ "help",	I_HELP },
-	{ "lcd",	I_LCHDIR },
-	{ "lchdir",	I_LCHDIR },
-	{ "lls",	I_LLS },
-	{ "lmkdir",	I_LMKDIR },
-	{ "ln",		I_SYMLINK },
-	{ "lpwd",	I_LPWD },
-	{ "ls",		I_LS },
-	{ "lumask",	I_LUMASK },
-	{ "mkdir",	I_MKDIR },
-	{ "put",	I_PUT },
-	{ "mput",	I_PUT },
-	{ "pwd",	I_PWD },
-	{ "quit",	I_QUIT },
-	{ "rename",	I_RENAME },
-	{ "rm",		I_RM },
-	{ "rmdir",	I_RMDIR },
-	{ "symlink",	I_SYMLINK },
-	{ "version",	I_VERSION },
-	{ "!",		I_SHELL },
-	{ "?",		I_HELP },
-	{ NULL,			-1}
-};
+/* How much data to read/write at at time during copies */
+/* XXX: what should this be? */
+#define COPY_SIZE	8192
+
+/* Message ID */
+static u_int msg_id = 1;
+
+/* Progress Meter items */
+off_t statbytes = 0;
+off_t totalbytes = 0;
+char *curfile = NULL;
 
 static void
-updateprogressmeter(int done)
+send_msg(int fd, Buffer *m)
 {
-        int save_errno = errno;
-	extern off_t statbytes;
-	extern off_t totalbytes;
-	extern char *curfile;
-
-        progressmeter(statbytes, totalbytes, curfile);
-/*
-	if (done == 0) {
-        	signal(SIGALRM, updateprogressmeter);
-        	alarm(PROGRESSTIME);
-	} else if (done == 1)
-		alarm(0);
-*/
-        errno = save_errno;
+	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);
 }
 
 static void
-help(void)
+get_msg(int fd, Buffer *m)
 {
-	printf("Available commands:\n");
-	printf("cd path                       Change remote directory to 'path'\n");
-	printf("lcd path                      Change local directory to 'path'\n");
-	printf("chgrp grp path                Change group of file 'path' to 'grp'\n");
-	printf("chmod mode path               Change permissions of file 'path' to 'mode'\n");
-	printf("chown own path                Change owner of file 'path' to 'own'\n");
-	printf("help                          Display this help text\n");
-	printf("get remote-path [local-path]  Download file\n");
-	printf("lls [ls-options [path]]       Display local directory listing\n");
-	printf("ln oldpath newpath            Symlink remote file\n");
-	printf("lmkdir path                   Create local directory\n");
-	printf("lpwd                          Print local working directory\n");
-	printf("ls [path]                     Display remote directory listing\n");
-	printf("lumask umask                  Set local umask to 'umask'\n");
-	printf("mkdir path                    Create remote directory\n");
-	printf("put local-path [remote-path]  Upload file\n");
-	printf("pwd                           Display remote working directory\n");
-	printf("exit                          Quit sftp\n");
-	printf("quit                          Quit sftp\n");
-	printf("rename oldpath newpath        Rename remote file\n");
-	printf("rmdir path                    Remove remote directory\n");
-	printf("rm path                       Delete remote file\n");
-	printf("symlink oldpath newpath       Symlink remote file\n");
-	printf("version                       Show SFTP version\n");
-	printf("!command                      Execute 'command' in local shell\n");
-	printf("!                             Escape to local shell\n");
-	printf("?                             Synonym for help\n");
+	u_int len, msg_len;
+	unsigned char buf[4096];
+
+	len = atomicio(read, fd, buf, 4);
+	if (len == 0)
+		fatal("Connection closed");
+	else if (len == -1)
+		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("Connection closed");
+		else if (len == -1)
+			fatal("Couldn't read packet: %s", strerror(errno));
+
+		msg_len -= len;
+		buffer_append(m, buf, len);
+	}
 }
 
 static void
-local_do_shell(const char *args)
+send_string_request(int fd, u_int id, u_int code, char *s,
+    u_int len)
 {
-	int status;
-	char *shell;
-	pid_t pid;
+	Buffer msg;
 
-	if (!*args)
-		args = NULL;
-
-	if ((shell = getenv("SHELL")) == NULL)
-		shell = _PATH_BSHELL;
-
-	if ((pid = fork()) == -1)
-		fatal("Couldn't fork: %s", strerror(errno));
-
-	if (pid == 0) {
-		/* XXX: child has pipe fds to ssh subproc open - issue? */
-		if (args) {
-			debug3("Executing %s -c \"%s\"", shell, args);
-			execl(shell, shell, "-c", args, (char *)NULL);
-		} else {
-			debug3("Executing %s", shell);
-			execl(shell, shell, (char *)NULL);
-		}
-		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
-		    strerror(errno));
-		_exit(1);
-	}
-	if (waitpid(pid, &status, 0) == -1)
-		fatal("Couldn't wait for child: %s", strerror(errno));
-	if (!WIFEXITED(status))
-		error("Shell exited abormally");
-	else if (WEXITSTATUS(status))
-		error("Shell exited with status %d", WEXITSTATUS(status));
+	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);
 }
 
 static void
-local_do_ls(const char *args)
+send_string_attrs_request(int fd, u_int id, u_int code, char *s,
+    u_int len, Attrib *a)
 {
-	if (!args || !*args)
-		local_do_shell(_PATH_LS);
-	else {
-		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
-		char *buf = xmalloc(len);
-
-		/* XXX: quoting - rip quoting code from ftp? */
-		snprintf(buf, len, _PATH_LS " %s", args);
-		local_do_shell(buf);
-		xfree(buf);
-	}
+	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);
 }
 
-static char *
-path_append(char *p1, char *p2)
+static u_int
+get_status(int fd, int expected_id)
 {
-	char *ret;
-	int len = strlen(p1) + strlen(p2) + 2;
+	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);
 
-	ret = xmalloc(len);
-	strlcpy(ret, p1, len);
-	if (strcmp(p1, "/") != 0)
-		strlcat(ret, "/", len);
-	strlcat(ret, p2, len);
+	debug3("SSH2_FXP_STATUS %d", status);
 
-	return(ret);
+	return(status);
 }
 
 static char *
-make_absolute(char *p, char *pwd)
+get_handle(int fd, u_int expected_id, u_int *len)
 {
-	char *abs;
+	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);
 
-	/* Derelativise */
-	if (p && p[0] != '/') {
-		abs = path_append(pwd, p);
-		xfree(p);
-		return(abs);
-	} else
-		return(p);
+	handle = buffer_get_string(&msg, len);
+	buffer_free(&msg);
+
+	return(handle);
 }
 
-static int
-infer_path(const char *p, char **ifp)
+static Attrib *
+get_decode_stat(int fd, u_int expected_id, int quiet)
 {
-	char *cp;
+	Buffer msg;
+	u_int type, id;
+	Attrib *a;
+
+	buffer_init(&msg);
+	get_msg(fd, &msg);
 
-	cp = strrchr(p, '/');
-	if (cp == NULL) {
-		*ifp = xstrdup(p);
-		return(0);
+	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);
+
+		if (quiet)
+			debug("Couldn't stat remote file: %s", fx2txt(status));
+		else
+			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);
 
-	if (!cp[1]) {
-		error("Invalid path");
+	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);
 
-	*ifp = xstrdup(cp + 1);
-	return(0);
+	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(version);
+}
+
+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 = msg_id++;
+	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);
 }
 
+
 static int
-parse_getput_flags(const char **cpp, int *pflag)
+do_lsreaddir(int fd_in, int fd_out, char *path, int printflag,
+    SFTP_DIRENT ***dir)
 {
-	const char *cp = *cpp;
+	Buffer msg;
+	u_int type, id, handle_len, i, expected_id, ents = 0;
+	char *handle;
 
-	/* Check for flags */
-	if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
-		switch (cp[1]) {
-		case 'p':
-		case 'P':
-			*pflag = 1;
-			break;
-		default:
-			error("Invalid flag -%c", cp[1]);
-			return(-1);
+	id = msg_id++;
+
+	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);
+
+	if (dir) {
+		ents = 0;
+		*dir = xmalloc(sizeof(**dir));
+		(*dir)[0] = NULL;
+	}
+
+	for (;;) {
+		int count;
+
+		id = expected_id = msg_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(status);
+			}
+		} 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 == 0)
+			break;
+		debug3("Received %d 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);
+
+			if (printflag)
+				printf("%s\n", longname);
+
+			if (dir) {
+				*dir = xrealloc(*dir, sizeof(**dir) *
+				    (ents + 2));
+				(*dir)[ents] = xmalloc(sizeof(***dir));
+				(*dir)[ents]->filename = xstrdup(filename);
+				(*dir)[ents]->longname = xstrdup(longname);
+				memcpy(&(*dir)[ents]->a, a, sizeof(*a));
+				(*dir)[++ents] = NULL;
+			}
+
+			xfree(filename);
+			xfree(longname);
 		}
-		cp += 2;
-		*cpp = cp + strspn(cp, WHITESPACE);
 	}
 
+	buffer_free(&msg);
+	do_close(fd_in, fd_out, handle, handle_len);
+	xfree(handle);
+
 	return(0);
 }
 
-static int
-get_pathname(const char **cpp, char **path)
+int
+do_ls(int fd_in, int fd_out, char *path)
+{
+	return(do_lsreaddir(fd_in, fd_out, path, 1, NULL));
+}
+
+int
+do_readdir(int fd_in, int fd_out, char *path, SFTP_DIRENT ***dir)
+{
+	return(do_lsreaddir(fd_in, fd_out, path, 0, dir));
+}
+
+void free_sftp_dirents(SFTP_DIRENT **s)
 {
-	const char *cp = *cpp, *end;
-	char quot;
 	int i;
 
-	cp += strspn(cp, WHITESPACE);
-	if (!*cp) {
-		*cpp = cp;
-		*path = NULL;
-		return (0);
+	for (i = 0; s[i]; i++) {
+		xfree(s[i]->filename);
+		xfree(s[i]->longname);
+		xfree(s[i]);
 	}
+	xfree(s);
+}
 
-	/* Check for quoted filenames */
-	if (*cp == '\"' || *cp == '\'') {
-		quot = *cp++;
-
-		end = strchr(cp, quot);
-		if (end == NULL) {
-			error("Unterminated quote");
-			goto fail;
-		}
-		if (cp == end) {
-			error("Empty quotes");
-			goto fail;
-		}
-		*cpp = end + 1 + strspn(end + 1, WHITESPACE);
-	} else {
-		/* Read to end of filename */
-		end = strpbrk(cp, WHITESPACE);
-		if (end == NULL)
-			end = strchr(cp, '\0');
-		*cpp = end + strspn(end, WHITESPACE);
-	}
+int
+do_rm(int fd_in, int fd_out, char *path)
+{
+	u_int status, id;
 
-	i = end - cp;
-
-	*path = xmalloc(i + 1);
-	memcpy(*path, cp, i);
-	(*path)[i] = '\0';
-	return(0);
+	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
 
- fail:
-	*path = NULL;
-	return (-1);
+	id = msg_id++;
+	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);
 }
 
-static int
-is_dir(char *path)
+int
+do_mkdir(int fd_in, int fd_out, char *path, Attrib *a)
 {
-	struct stat sb;
+	u_int status, id;
 
-	/* XXX: report errors? */
-	if (stat(path, &sb) == -1)
-		return(0);
+	id = msg_id++;
+	send_string_attrs_request(fd_out, id, SSH2_FXP_MKDIR, path,
+	    strlen(path), a);
 
-	return(sb.st_mode & S_IFDIR);
+	status = get_status(fd_in, id);
+	if (status != SSH2_FX_OK)
+		error("Couldn't create directory: %s", fx2txt(status));
+
+	return(status);
 }
 
-static int
-remote_is_dir(int in, int out, char *path)
+int
+do_rmdir(int fd_in, int fd_out, char *path)
+{
+	u_int status, id;
+
+	id = msg_id++;
+	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, int quiet)
+{
+	u_int id;
+
+	id = msg_id++;
+	send_string_request(fd_out, id, SSH2_FXP_STAT, path, strlen(path));
+	return(get_decode_stat(fd_in, id, quiet));
+}
+
+Attrib *
+do_lstat(int fd_in, int fd_out, char *path, int quiet)
+{
+	u_int id;
+
+	id = msg_id++;
+	send_string_request(fd_out, id, SSH2_FXP_LSTAT, path, strlen(path));
+	return(get_decode_stat(fd_in, id, quiet));
+}
+
+Attrib *
+do_fstat(int fd_in, int fd_out, char *handle, u_int handle_len, int quiet)
+{
+	u_int id;
+
+	id = msg_id++;
+	send_string_request(fd_out, id, SSH2_FXP_FSTAT, handle, handle_len);
+	return(get_decode_stat(fd_in, id, quiet));
+}
+
+int
+do_setstat(int fd_in, int fd_out, char *path, Attrib *a)
 {
+	u_int status, id;
+
+	id = msg_id++;
+	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 = msg_id++;
+	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;
 
-	/* XXX: report errors? */
-	if ((a = do_stat(in, out, path, 1)) == NULL)
-		return(0);
-	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
-		return(0);
-	return(a->perm & S_IFDIR);
+	expected_id = id = msg_id++;
+	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);
 }
 
-static int
-process_get(int in, int out, char *src, char *dst, char *pwd, int pflag)
+int
+do_rename(int fd_in, int fd_out, char *oldpath, char *newpath)
 {
-	char *abs_src = NULL;
-	char *abs_dst = NULL;
-	char *tmp;
-	glob_t g;
-	int err = 0;
-	int i;
+	Buffer msg;
+	u_int status, id;
 
-	abs_src = xstrdup(src);
-	abs_src = make_absolute(abs_src, pwd);
+	buffer_init(&msg);
 
-	memset(&g, 0, sizeof(g));
-	debug3("Looking up %s", abs_src);
-	if (remote_glob(in, out, abs_src, 0, NULL, &g)) {
-		error("File \"%s\" not found.", abs_src);
-		err = -1;
-		goto out;
-	}
-
-	/* Only one match, dst may be file, directory or unspecified */
-	if (g.gl_pathv[0] && g.gl_matchc == 1) {
-		if (dst) {
-			/* If directory specified, append filename */
-			if (is_dir(dst)) {
-				if (infer_path(g.gl_pathv[0], &tmp)) {
-					err = 1;
-					goto out;
-				}
-				abs_dst = path_append(dst, tmp);
-				xfree(tmp);
-			} else
-				abs_dst = xstrdup(dst);
-		} else if (infer_path(g.gl_pathv[0], &abs_dst)) {
-			err = -1;
-			goto out;
-		}
-		err = do_download(in, out, g.gl_pathv[0], abs_dst, pflag,
-		    updateprogressmeter);
-		goto out;
-	}
-
-	/* Multiple matches, dst may be directory or unspecified */
-	if (dst && !is_dir(dst)) {
-		error("Multiple files match, but \"%s\" is not a directory",
-		    dst);
-		err = -1;
-		goto out;
-	}
-
-	for (i = 0; g.gl_pathv[i]; i++) {
-		if (infer_path(g.gl_pathv[i], &tmp)) {
-			err = -1;
-			goto out;
-		}
-		if (dst) {
-			abs_dst = path_append(dst, tmp);
-			xfree(tmp);
-		} else
-			abs_dst = tmp;
-
-		if (do_download(in, out, g.gl_pathv[i], abs_dst, pflag, 
-		    updateprogressmeter) == -1)
-			err = -1;
-		xfree(abs_dst);
-		abs_dst = NULL;
-	}
-
-out:
-	xfree(abs_src);
-	if (abs_dst)
-		xfree(abs_dst);
-	globfree(&g);
-	return(err);
+	/* Send rename request */
+	id = msg_id++;
+	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);
 }
 
-static int
-process_put(int in, int out, char *src, char *dst, char *pwd, int pflag)
+int
+do_symlink(int fd_in, int fd_out, char *oldpath, char *newpath)
 {
-	char *tmp_dst = NULL;
-	char *abs_dst = NULL;
-	char *tmp;
-	glob_t g;
-	int err = 0;
-	int i;
+	Buffer msg;
+	u_int status, id;
 
-	if (dst) {
-		tmp_dst = xstrdup(dst);
-		tmp_dst = make_absolute(tmp_dst, pwd);
-	}
-
-	memset(&g, 0, sizeof(g));
-	debug3("Looking up %s", src);
-	if (glob(src, 0, NULL, &g)) {
-		error("File \"%s\" not found.", src);
-		err = -1;
-		goto out;
-	}
-
-	/* Only one match, dst may be file, directory or unspecified */
-	if (g.gl_pathv[0] && g.gl_matchc == 1) {
-		if (tmp_dst) {
-			/* If directory specified, append filename */
-			if (remote_is_dir(in, out, tmp_dst)) {
-				if (infer_path(g.gl_pathv[0], &tmp)) {
-					err = 1;
-					goto out;
-				}
-				abs_dst = path_append(tmp_dst, tmp);
-				xfree(tmp);
-			} else
-				abs_dst = xstrdup(tmp_dst);
-		} else {
-			if (infer_path(g.gl_pathv[0], &abs_dst)) {
-				err = -1;
-				goto out;
-			}
-			abs_dst = make_absolute(abs_dst, pwd);
-		}
-		err = do_upload(in, out, g.gl_pathv[0], abs_dst, pflag,
-		    updateprogressmeter);
-		goto out;
-	}
-
-	/* Multiple matches, dst may be directory or unspecified */
-	if (tmp_dst && !remote_is_dir(in, out, tmp_dst)) {
-		error("Multiple files match, but \"%s\" is not a directory",
-		    tmp_dst);
-		err = -1;
-		goto out;
-	}
-
-	for (i = 0; g.gl_pathv[i]; i++) {
-		if (infer_path(g.gl_pathv[i], &tmp)) {
-			err = -1;
-			goto out;
-		}
-		if (tmp_dst) {
-			abs_dst = path_append(tmp_dst, tmp);
-			xfree(tmp);
-		} else
-			abs_dst = make_absolute(tmp, pwd);
-
-		if (do_upload(in, out, g.gl_pathv[i], abs_dst, pflag,
-		    updateprogressmeter) == -1)
-			err = -1;
-	}
-
-out:
-	if (abs_dst)
-		xfree(abs_dst);
-	if (tmp_dst)
-		xfree(tmp_dst);
-	return(err);
+	buffer_init(&msg);
+
+	/* Send rename request */
+	id = msg_id++;
+	buffer_put_char(&msg, SSH2_FXP_SYMLINK);
+	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_SYMLINK \"%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);
 }
 
-static int
-parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
-    char **path1, char **path2)
+char *
+do_readlink(int fd_in, int fd_out, char *path)
 {
-	const char *cmd, *cp = *cpp;
-	char *cp2;
-	int base = 0;
-	long l;
-	int i, cmdnum;
+	Buffer msg;
+	u_int type, expected_id, count, id;
+	char *filename, *longname;
+	Attrib *a;
+
+	expected_id = id = msg_id++;
+	send_string_request(fd_out, id, SSH2_FXP_READLINK, 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 readlink: %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_READLINK", count);
+
+	filename = buffer_get_string(&msg, NULL);
+	longname = buffer_get_string(&msg, NULL);
+	a = decode_attrib(&msg);
 
-	/* Skip leading whitespace */
-	cp = cp + strspn(cp, WHITESPACE);
+	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
 
-	/* Ignore blank lines */
-	if (!*cp)
+	xfree(longname);
+
+	buffer_free(&msg);
+
+	return(filename);
+}
+
+int
+do_download(int fd_in, int fd_out, char *remote_path, char *local_path,
+    int pflag, void (*progressbar)(int))
+{
+	int local_fd;
+	u_int expected_id, handle_len, mode, type, id;
+	u_int64_t offset;
+	char *handle;
+	Buffer msg;
+	Attrib junk, *a;
+	int status;
+
+	a = do_stat(fd_in, fd_out, remote_path, 0);
+	if (a == NULL)
 		return(-1);
 
-	/* Figure out which command we have */
-	for (i = 0; cmds[i].c; i++) {
-		int cmdlen = strlen(cmds[i].c);
-
-		/* Check for command followed by whitespace */
-		if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
-		    strchr(WHITESPACE, cp[cmdlen])) {
-			cp += cmdlen;
-			cp = cp + strspn(cp, WHITESPACE);
-			break;
-		}
+	/* XXX: should we preserve set[ug]id? */
+	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
+		mode = S_IWRITE | (a->perm & 0777);
+	else
+		mode = 0666;
+
+	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
+	    (a->perm & S_IFDIR)) {
+		error("Cannot download a directory: %s", remote_path);
+		return(-1);
 	}
-	cmdnum = cmds[i].n;
-	cmd = cmds[i].c;
 
-	/* Special case */
-	if (*cp == '!') {
-		cp++;
-		cmdnum = I_SHELL;
-	} else if (cmdnum == -1) {
-		error("Invalid command.");
+	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(-1);
 	}
 
-	/* Get arguments and parse flags */
-	*pflag = *n_arg = 0;
-	*path1 = *path2 = NULL;
-	switch (cmdnum) {
-	case I_GET:
-	case I_PUT:
-		if (parse_getput_flags(&cp, pflag))
-			return(-1);
-		/* Get first pathname (mandatory) */
-		if (get_pathname(&cp, path1))
-			return(-1);
-		if (*path1 == NULL) {
-			error("You must specify at least one path after a "
-			    "%s command.", cmd);
-			return(-1);
-		}
-		/* Try to get second pathname (optional) */
-		if (get_pathname(&cp, path2))
-			return(-1);
-		break;
-	case I_RENAME:
-	case I_SYMLINK:
-		if (get_pathname(&cp, path1))
-			return(-1);
-		if (get_pathname(&cp, path2))
-			return(-1);
-		if (!*path1 || !*path2) {
-			error("You must specify two paths after a %s "
-			    "command.", cmd);
-			return(-1);
-		}
-		break;
-	case I_RM:
-	case I_MKDIR:
-	case I_RMDIR:
-	case I_CHDIR:
-	case I_LCHDIR:
-	case I_LMKDIR:
-		/* Get pathname (mandatory) */
-		if (get_pathname(&cp, path1))
-			return(-1);
-		if (*path1 == NULL) {
-			error("You must specify a path after a %s command.",
-			    cmd);
-			return(-1);
-		}
-		break;
-	case I_LS:
-		/* Path is optional */
-		if (get_pathname(&cp, path1))
-			return(-1);
-		break;
-	case I_LLS:
-	case I_SHELL:
-		/* Uses the rest of the line */
-		break;
-	case I_LUMASK:
-		base = 8;
-	case I_CHMOD:
-		base = 8;
-	case I_CHOWN:
-	case I_CHGRP:
-		/* Get numeric arg (mandatory) */
-		l = strtol(cp, &cp2, base);
-		if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
-		    errno == ERANGE) || l < 0) {
-			error("You must supply a numeric argument "
-			    "to the %s command.", cmd);
-			return(-1);
-		}
-		cp = cp2;
-		*n_arg = l;
-		if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
-			break;
-		if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
-			error("You must supply a numeric argument "
-			    "to the %s command.", cmd);
-			return(-1);
-		}
-		cp += strspn(cp, WHITESPACE);
+	buffer_init(&msg);
 
-		/* Get pathname (mandatory) */
-		if (get_pathname(&cp, path1))
-			return(-1);
-		if (*path1 == NULL) {
-			error("You must specify a path after a %s command.",
-			    cmd);
-			return(-1);
-		}
-		break;
-	case I_QUIT:
-	case I_PWD:
-	case I_LPWD:
-	case I_HELP:
-	case I_VERSION:
-		break;
-	default:
-		fatal("Command not implemented");
+	/* Send open request */
+	id = msg_id++;
+	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);
 	}
 
-	*cpp = cp;
-	return(cmdnum);
-}
+	totalbytes  = a->size;
+	curfile = remote_path;
 
-static int
-parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
-{
-	char *path1, *path2, *tmp;
-	int pflag, cmdnum, i;
-	unsigned long n_arg;
-	Attrib a, *aa;
-	char path_buf[MAXPATHLEN];
-	int err = 0;
-	glob_t g;
-
-	path1 = path2 = NULL;
-	cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
-
-	memset(&g, 0, sizeof(g));
-
-	/* Perform command */
-	switch (cmdnum) {
-	case -1:
-		break;
-	case I_GET:
-		err = process_get(in, out, path1, path2, *pwd, pflag);
-		break;
-	case I_PUT:
-		err = process_put(in, out, path1, path2, *pwd, pflag);
-		break;
-	case I_RENAME:
-		path1 = make_absolute(path1, *pwd);
-		path2 = make_absolute(path2, *pwd);
-		err = do_rename(in, out, path1, path2);
-		break;
-	case I_SYMLINK:
-		if (version < 3) {
-			error("The server (version %d) does not support "
-			    "this operation", version);
-			err = -1;
-		} else {
-			path2 = make_absolute(path2, *pwd);
-			err = do_symlink(in, out, path1, path2);
-		}
-		break;
-	case I_RM:
-		path1 = make_absolute(path1, *pwd);
-		remote_glob(in, out, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i]; i++) {
-			printf("Removing %s\n", g.gl_pathv[i]);
-			if (do_rm(in, out, g.gl_pathv[i]) == -1)
-				err = -1;
-		}
-		break;
-	case I_MKDIR:
-		path1 = make_absolute(path1, *pwd);
-		attrib_clear(&a);
-		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
-		a.perm = 0777;
-		err = do_mkdir(in, out, path1, &a);
-		break;
-	case I_RMDIR:
-		path1 = make_absolute(path1, *pwd);
-		err = do_rmdir(in, out, path1);
-		break;
-	case I_CHDIR:
-		path1 = make_absolute(path1, *pwd);
-		if ((tmp = do_realpath(in, out, path1)) == NULL) {
-			err = 1;
-			break;
-		}
-		if ((aa = do_stat(in, out, tmp, 0)) == NULL) {
-			xfree(tmp);
-			err = 1;
-			break;
-		}
-		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
-			error("Can't change directory: Can't check target");
-			xfree(tmp);
-			err = 1;
-			break;
-		}
-		if (!S_ISDIR(aa->perm)) {
-			error("Can't change directory: \"%s\" is not "
-			    "a directory", tmp);
-			xfree(tmp);
-			err = 1;
-			break;
-		}
-		xfree(*pwd);
-		*pwd = tmp;
-		break;
-	case I_LS:
-		if (!path1) {
-			do_ls(in, out, *pwd);
-			break;
-		}
-		path1 = make_absolute(path1, *pwd);
-		if ((tmp = do_realpath(in, out, path1)) == NULL)
-			break;
-		xfree(path1);
-		path1 = tmp;
-		if ((aa = do_stat(in, out, path1, 0)) == NULL)
-			break;
-		if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
-		    !S_ISDIR(aa->perm)) {
-			error("Can't ls: \"%s\" is not a directory", path1);
-			break;
-		}
-		do_ls(in, out, path1);
-		break;
-	case I_LCHDIR:
-		if (chdir(path1) == -1) {
-			error("Couldn't change local directory to "
-			    "\"%s\": %s", path1, strerror(errno));
-			err = 1;
-		}
-		break;
-	case I_LMKDIR:
-		if (mkdir(path1, 0777) == -1) {
-			error("Couldn't create local directory "
-			    "\"%s\": %s", path1, strerror(errno));
-			err = 1;
-		}
-		break;
-	case I_LLS:
-		local_do_ls(cmd);
-		break;
-	case I_SHELL:
-		local_do_shell(cmd);
-		break;
-	case I_LUMASK:
-		umask(n_arg);
-		printf("Local umask: %03lo\n", n_arg);
-		break;
-	case I_CHMOD:
-		path1 = make_absolute(path1, *pwd);
-		attrib_clear(&a);
-		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
-		a.perm = n_arg;
-		remote_glob(in, out, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i]; i++) {
-			printf("Changing mode on %s\n", g.gl_pathv[i]);
-			do_setstat(in, out, g.gl_pathv[i], &a);
-		}
-		break;
-	case I_CHOWN:
-		path1 = make_absolute(path1, *pwd);
-		remote_glob(in, out, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i]; i++) {
-			if (!(aa = do_stat(in, out, g.gl_pathv[i], 0)))
-				continue;
-			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
-				error("Can't get current ownership of "
-				    "remote file \"%s\"", g.gl_pathv[i]);
-				continue;
-			}
-			printf("Changing owner on %s\n", g.gl_pathv[i]);
-			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
-			aa->uid = n_arg;
-			do_setstat(in, out, g.gl_pathv[i], aa);
-		}
-		break;
-	case I_CHGRP:
-		path1 = make_absolute(path1, *pwd);
-		remote_glob(in, out, path1, GLOB_NOCHECK, NULL, &g);
-		for (i = 0; g.gl_pathv[i]; i++) {
-			if (!(aa = do_stat(in, out, g.gl_pathv[i], 0)))
-				continue;
-			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
-				error("Can't get current ownership of "
-				    "remote file \"%s\"", g.gl_pathv[i]);
-				continue;
+	/* Read from remote and write to local */
+	offset = 0;
+	for (;;) {
+		u_int len;
+		char *data;
+
+		id = expected_id = msg_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, (unsigned long long)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) {
+			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);
+				goto done;
 			}
-			printf("Changing group on %s\n", g.gl_pathv[i]);
-			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
-			aa->gid = n_arg;
-			do_setstat(in, out, g.gl_pathv[i], aa);
+		} else if (type != SSH2_FXP_DATA) {
+			fatal("Expected SSH2_FXP_DATA(%d) packet, got %d",
+			    SSH2_FXP_DATA, type);
 		}
-		break;
-	case I_PWD:
-		printf("Remote working directory: %s\n", *pwd);
-		break;
-	case I_LPWD:
-		if (!getcwd(path_buf, sizeof(path_buf)))
-			error("Couldn't get local cwd: %s",
+
+		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 %llu", len,
+		    (unsigned long long)offset);
+		if (atomicio(write, local_fd, data, len) != len) {
+			error("Couldn't write to \"%s\": %s", local_path,
 			    strerror(errno));
-		else
-			printf("Local working directory: %s\n",
-			    path_buf);
-		break;
-	case I_QUIT:
-		return(-1);
-	case I_HELP:
-		help();
-		break;
-	case I_VERSION:
-		printf("SFTP protocol version %d\n", version);
-		break;
-	default:
-		fatal("%d is not implemented", cmdnum);
-	}
-
-	if (g.gl_pathc)
-		globfree(&g);
-	if (path1)
-		xfree(path1);
-	if (path2)
-		xfree(path2);
-
-	/* If an error occurs in batch mode we should abort. */
-	if (infile != stdin && err > 0)
-		return -1;
+			do_close(fd_in, fd_out, handle, handle_len);
+			status = -1;
+			xfree(data);
+			goto done;
+		}
+
+		offset += len;
+		xfree(data);
+		statbytes = offset;
+		if (progressbar)
+			(progressbar)(0);
+	}
+	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));
+	}
 
-	return(0);
-}
+done:
+	close(local_fd);
+	buffer_free(&msg);
+	xfree(handle);
+
+	return status;
+}
+
+int
+do_upload(int fd_in, int fd_out, char *local_path, char *remote_path,
+    int pflag, void (*progressbar)(int))
+{
+	int local_fd;
+	u_int handle_len, id;
+	u_int64_t offset;
+	char *handle;
+	Buffer msg;
+	struct stat sb;
+	Attrib a;
+	int status;
 
-void
-interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
-{
-	char *pwd;
-	char *dir = NULL;
-	char cmd[2048];
-
-	version = do_init(fd_in, fd_out);
-	if (version == -1)
-		fatal("Couldn't initialise connection to server");
-
-	pwd = do_realpath(fd_in, fd_out, ".");
-	if (pwd == NULL)
-		fatal("Need cwd");
-
-	if (file1 != NULL) {
-		dir = xstrdup(file1);
-		dir = make_absolute(dir, pwd);
-
-		if (remote_is_dir(fd_in, fd_out, dir) && file2 == NULL) {
-			printf("Changing to: %s\n", dir);
-			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
-			parse_dispatch_command(fd_in, fd_out, cmd, &pwd);
-		} else {
-			if (file2 == NULL)
-				snprintf(cmd, sizeof cmd, "get %s", dir);
-			else
-				snprintf(cmd, sizeof cmd, "get %s %s", dir,
-				    file2);
+	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);
 
-			parse_dispatch_command(fd_in, fd_out, cmd, &pwd);
-			return;
-		}
+	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 = msg_id++;
+	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);
 	}
-	setvbuf(stdout, NULL, _IOLBF, 0);
-	setvbuf(infile, NULL, _IOLBF, 0);
+	totalbytes  = a.size;
+	curfile = local_path;
 
+	/* Read from local and write to remote */
+	offset = 0;
 	for (;;) {
-		char *cp;
+		int len;
+		char data[COPY_SIZE];
 
-		printf("sftp> ");
+		/*
+		 * 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));
 
-		/* XXX: use libedit */
-		if (fgets(cmd, sizeof(cmd), infile) == NULL) {
-			printf("\n");
+		if (len == -1)
+			fatal("Couldn't read from \"%s\": %s", local_path,
+			    strerror(errno));
+		if (len == 0)
 			break;
-		} else if (infile != stdin) /* Bluff typing */
-			printf("%s", cmd);
 
-		cp = strrchr(cmd, '\n');
-		if (cp)
-			*cp = '\0';
+		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, (unsigned long long)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);
+			close(local_fd);
+			goto done;
+		}
+		debug3("In write loop, got %d offset %llu", len,
+		    (unsigned long long)offset);
+
+		offset += len;
+		statbytes = offset;
+		if (progressbar);
+			(progressbar)(0);
+	}
 
-		if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
-			break;
+	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);
+		status = -1;
+		goto done;
 	}
-	xfree(pwd);
+
+	/* Override umask and utimes if asked */
+	if (pflag)
+		do_fsetstat(fd_in, fd_out, handle, handle_len, &a);
+
+	status = do_close(fd_in, fd_out, handle, handle_len);
+
+done:
+	xfree(handle);
+	buffer_free(&msg);
+
+	return status;
 }



More information about the openssh-unix-dev mailing list