sftp-client.c revision 76259
176259Sgreen/*
276259Sgreen * Copyright (c) 2001 Damien Miller.  All rights reserved.
376259Sgreen *
476259Sgreen * Redistribution and use in source and binary forms, with or without
576259Sgreen * modification, are permitted provided that the following conditions
676259Sgreen * are met:
776259Sgreen * 1. Redistributions of source code must retain the above copyright
876259Sgreen *    notice, this list of conditions and the following disclaimer.
976259Sgreen * 2. Redistributions in binary form must reproduce the above copyright
1076259Sgreen *    notice, this list of conditions and the following disclaimer in the
1176259Sgreen *    documentation and/or other materials provided with the distribution.
1276259Sgreen *
1376259Sgreen * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1476259Sgreen * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1576259Sgreen * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1676259Sgreen * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1776259Sgreen * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
1876259Sgreen * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1976259Sgreen * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2076259Sgreen * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2176259Sgreen * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2276259Sgreen * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2376259Sgreen */
2476259Sgreen
2576259Sgreen/* XXX: memleaks */
2676259Sgreen/* XXX: signed vs unsigned */
2776259Sgreen/* XXX: redesign to allow concurrent overlapped operations */
2876259Sgreen/* XXX: we use fatal too much, error may be more appropriate in places */
2976259Sgreen/* XXX: copy between two remote sites */
3076259Sgreen
3176259Sgreen#include "includes.h"
3276259SgreenRCSID("$OpenBSD: sftp-client.c,v 1.16 2001/04/05 10:42:52 markus Exp $");
3376259Sgreen
3476259Sgreen#include "ssh.h"
3576259Sgreen#include "buffer.h"
3676259Sgreen#include "bufaux.h"
3776259Sgreen#include "getput.h"
3876259Sgreen#include "xmalloc.h"
3976259Sgreen#include "log.h"
4076259Sgreen#include "atomicio.h"
4176259Sgreen#include "pathnames.h"
4276259Sgreen
4376259Sgreen#include "sftp.h"
4476259Sgreen#include "sftp-common.h"
4576259Sgreen#include "sftp-client.h"
4676259Sgreen
4776259Sgreen/* How much data to read/write at at time during copies */
4876259Sgreen/* XXX: what should this be? */
4976259Sgreen#define COPY_SIZE	8192
5076259Sgreen
5176259Sgreen/* Message ID */
5276259Sgreenstatic u_int msg_id = 1;
5376259Sgreen
5476259Sgreenvoid
5576259Sgreensend_msg(int fd, Buffer *m)
5676259Sgreen{
5776259Sgreen	int mlen = buffer_len(m);
5876259Sgreen	int len;
5976259Sgreen	Buffer oqueue;
6076259Sgreen
6176259Sgreen	buffer_init(&oqueue);
6276259Sgreen	buffer_put_int(&oqueue, mlen);
6376259Sgreen	buffer_append(&oqueue, buffer_ptr(m), mlen);
6476259Sgreen	buffer_consume(m, mlen);
6576259Sgreen
6676259Sgreen	len = atomicio(write, fd, buffer_ptr(&oqueue), buffer_len(&oqueue));
6776259Sgreen	if (len <= 0)
6876259Sgreen		fatal("Couldn't send packet: %s", strerror(errno));
6976259Sgreen
7076259Sgreen	buffer_free(&oqueue);
7176259Sgreen}
7276259Sgreen
7376259Sgreenvoid
7476259Sgreenget_msg(int fd, Buffer *m)
7576259Sgreen{
7676259Sgreen	u_int len, msg_len;
7776259Sgreen	unsigned char buf[4096];
7876259Sgreen
7976259Sgreen	len = atomicio(read, fd, buf, 4);
8076259Sgreen	if (len == 0)
8176259Sgreen		fatal("Connection closed");
8276259Sgreen	else if (len == -1)
8376259Sgreen		fatal("Couldn't read packet: %s", strerror(errno));
8476259Sgreen
8576259Sgreen	msg_len = GET_32BIT(buf);
8676259Sgreen	if (msg_len > 256 * 1024)
8776259Sgreen		fatal("Received message too long %d", msg_len);
8876259Sgreen
8976259Sgreen	while (msg_len) {
9076259Sgreen		len = atomicio(read, fd, buf, MIN(msg_len, sizeof(buf)));
9176259Sgreen		if (len == 0)
9276259Sgreen			fatal("Connection closed");
9376259Sgreen		else if (len == -1)
9476259Sgreen			fatal("Couldn't read packet: %s", strerror(errno));
9576259Sgreen
9676259Sgreen		msg_len -= len;
9776259Sgreen		buffer_append(m, buf, len);
9876259Sgreen	}
9976259Sgreen}
10076259Sgreen
10176259Sgreenvoid
10276259Sgreensend_string_request(int fd, u_int id, u_int code, char *s,
10376259Sgreen    u_int len)
10476259Sgreen{
10576259Sgreen	Buffer msg;
10676259Sgreen
10776259Sgreen	buffer_init(&msg);
10876259Sgreen	buffer_put_char(&msg, code);
10976259Sgreen	buffer_put_int(&msg, id);
11076259Sgreen	buffer_put_string(&msg, s, len);
11176259Sgreen	send_msg(fd, &msg);
11276259Sgreen	debug3("Sent message fd %d T:%d I:%d", fd, code, id);
11376259Sgreen	buffer_free(&msg);
11476259Sgreen}
11576259Sgreen
11676259Sgreenvoid
11776259Sgreensend_string_attrs_request(int fd, u_int id, u_int code, char *s,
11876259Sgreen    u_int len, Attrib *a)
11976259Sgreen{
12076259Sgreen	Buffer msg;
12176259Sgreen
12276259Sgreen	buffer_init(&msg);
12376259Sgreen	buffer_put_char(&msg, code);
12476259Sgreen	buffer_put_int(&msg, id);
12576259Sgreen	buffer_put_string(&msg, s, len);
12676259Sgreen	encode_attrib(&msg, a);
12776259Sgreen	send_msg(fd, &msg);
12876259Sgreen	debug3("Sent message fd %d T:%d I:%d", fd, code, id);
12976259Sgreen	buffer_free(&msg);
13076259Sgreen}
13176259Sgreen
13276259Sgreenu_int
13376259Sgreenget_status(int fd, int expected_id)
13476259Sgreen{
13576259Sgreen	Buffer msg;
13676259Sgreen	u_int type, id, status;
13776259Sgreen
13876259Sgreen	buffer_init(&msg);
13976259Sgreen	get_msg(fd, &msg);
14076259Sgreen	type = buffer_get_char(&msg);
14176259Sgreen	id = buffer_get_int(&msg);
14276259Sgreen
14376259Sgreen	if (id != expected_id)
14476259Sgreen		fatal("ID mismatch (%d != %d)", id, expected_id);
14576259Sgreen	if (type != SSH2_FXP_STATUS)
14676259Sgreen		fatal("Expected SSH2_FXP_STATUS(%d) packet, got %d",
14776259Sgreen		    SSH2_FXP_STATUS, type);
14876259Sgreen
14976259Sgreen	status = buffer_get_int(&msg);
15076259Sgreen	buffer_free(&msg);
15176259Sgreen
15276259Sgreen	debug3("SSH2_FXP_STATUS %d", status);
15376259Sgreen
15476259Sgreen	return(status);
15576259Sgreen}
15676259Sgreen
15776259Sgreenchar *
15876259Sgreenget_handle(int fd, u_int expected_id, u_int *len)
15976259Sgreen{
16076259Sgreen	Buffer msg;
16176259Sgreen	u_int type, id;
16276259Sgreen	char *handle;
16376259Sgreen
16476259Sgreen	buffer_init(&msg);
16576259Sgreen	get_msg(fd, &msg);
16676259Sgreen	type = buffer_get_char(&msg);
16776259Sgreen	id = buffer_get_int(&msg);
16876259Sgreen
16976259Sgreen	if (id != expected_id)
17076259Sgreen		fatal("ID mismatch (%d != %d)", id, expected_id);
17176259Sgreen	if (type == SSH2_FXP_STATUS) {
17276259Sgreen		int status = buffer_get_int(&msg);
17376259Sgreen
17476259Sgreen		error("Couldn't get handle: %s", fx2txt(status));
17576259Sgreen		return(NULL);
17676259Sgreen	} else if (type != SSH2_FXP_HANDLE)
17776259Sgreen		fatal("Expected SSH2_FXP_HANDLE(%d) packet, got %d",
17876259Sgreen		    SSH2_FXP_HANDLE, type);
17976259Sgreen
18076259Sgreen	handle = buffer_get_string(&msg, len);
18176259Sgreen	buffer_free(&msg);
18276259Sgreen
18376259Sgreen	return(handle);
18476259Sgreen}
18576259Sgreen
18676259SgreenAttrib *
18776259Sgreenget_decode_stat(int fd, u_int expected_id, int quiet)
18876259Sgreen{
18976259Sgreen	Buffer msg;
19076259Sgreen	u_int type, id;
19176259Sgreen	Attrib *a;
19276259Sgreen
19376259Sgreen	buffer_init(&msg);
19476259Sgreen	get_msg(fd, &msg);
19576259Sgreen
19676259Sgreen	type = buffer_get_char(&msg);
19776259Sgreen	id = buffer_get_int(&msg);
19876259Sgreen
19976259Sgreen	debug3("Received stat reply T:%d I:%d", type, id);
20076259Sgreen	if (id != expected_id)
20176259Sgreen		fatal("ID mismatch (%d != %d)", id, expected_id);
20276259Sgreen	if (type == SSH2_FXP_STATUS) {
20376259Sgreen		int status = buffer_get_int(&msg);
20476259Sgreen
20576259Sgreen		if (quiet)
20676259Sgreen			debug("Couldn't stat remote file: %s", fx2txt(status));
20776259Sgreen		else
20876259Sgreen			error("Couldn't stat remote file: %s", fx2txt(status));
20976259Sgreen		return(NULL);
21076259Sgreen	} else if (type != SSH2_FXP_ATTRS) {
21176259Sgreen		fatal("Expected SSH2_FXP_ATTRS(%d) packet, got %d",
21276259Sgreen		    SSH2_FXP_ATTRS, type);
21376259Sgreen	}
21476259Sgreen	a = decode_attrib(&msg);
21576259Sgreen	buffer_free(&msg);
21676259Sgreen
21776259Sgreen	return(a);
21876259Sgreen}
21976259Sgreen
22076259Sgreenint
22176259Sgreendo_init(int fd_in, int fd_out)
22276259Sgreen{
22376259Sgreen	int type, version;
22476259Sgreen	Buffer msg;
22576259Sgreen
22676259Sgreen	buffer_init(&msg);
22776259Sgreen	buffer_put_char(&msg, SSH2_FXP_INIT);
22876259Sgreen	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
22976259Sgreen	send_msg(fd_out, &msg);
23076259Sgreen
23176259Sgreen	buffer_clear(&msg);
23276259Sgreen
23376259Sgreen	get_msg(fd_in, &msg);
23476259Sgreen
23576259Sgreen	/* Expecting a VERSION reply */
23676259Sgreen	if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) {
23776259Sgreen		error("Invalid packet back from SSH2_FXP_INIT (type %d)",
23876259Sgreen		    type);
23976259Sgreen		buffer_free(&msg);
24076259Sgreen		return(-1);
24176259Sgreen	}
24276259Sgreen	version = buffer_get_int(&msg);
24376259Sgreen
24476259Sgreen	debug2("Remote version: %d", version);
24576259Sgreen
24676259Sgreen	/* Check for extensions */
24776259Sgreen	while (buffer_len(&msg) > 0) {
24876259Sgreen		char *name = buffer_get_string(&msg, NULL);
24976259Sgreen		char *value = buffer_get_string(&msg, NULL);
25076259Sgreen
25176259Sgreen		debug2("Init extension: \"%s\"", name);
25276259Sgreen		xfree(name);
25376259Sgreen		xfree(value);
25476259Sgreen	}
25576259Sgreen
25676259Sgreen	buffer_free(&msg);
25776259Sgreen
25876259Sgreen	return(version);
25976259Sgreen}
26076259Sgreen
26176259Sgreenint
26276259Sgreendo_close(int fd_in, int fd_out, char *handle, u_int handle_len)
26376259Sgreen{
26476259Sgreen	u_int id, status;
26576259Sgreen	Buffer msg;
26676259Sgreen
26776259Sgreen	buffer_init(&msg);
26876259Sgreen
26976259Sgreen	id = msg_id++;
27076259Sgreen	buffer_put_char(&msg, SSH2_FXP_CLOSE);
27176259Sgreen	buffer_put_int(&msg, id);
27276259Sgreen	buffer_put_string(&msg, handle, handle_len);
27376259Sgreen	send_msg(fd_out, &msg);
27476259Sgreen	debug3("Sent message SSH2_FXP_CLOSE I:%d", id);
27576259Sgreen
27676259Sgreen	status = get_status(fd_in, id);
27776259Sgreen	if (status != SSH2_FX_OK)
27876259Sgreen		error("Couldn't close file: %s", fx2txt(status));
27976259Sgreen
28076259Sgreen	buffer_free(&msg);
28176259Sgreen
28276259Sgreen	return(status);
28376259Sgreen}
28476259Sgreen
28576259Sgreen
28676259Sgreenint
28776259Sgreendo_lsreaddir(int fd_in, int fd_out, char *path, int printflag,
28876259Sgreen    SFTP_DIRENT ***dir)
28976259Sgreen{
29076259Sgreen	Buffer msg;
29176259Sgreen	u_int type, id, handle_len, i, expected_id, ents = 0;
29276259Sgreen	char *handle;
29376259Sgreen
29476259Sgreen	id = msg_id++;
29576259Sgreen
29676259Sgreen	buffer_init(&msg);
29776259Sgreen	buffer_put_char(&msg, SSH2_FXP_OPENDIR);
29876259Sgreen	buffer_put_int(&msg, id);
29976259Sgreen	buffer_put_cstring(&msg, path);
30076259Sgreen	send_msg(fd_out, &msg);
30176259Sgreen
30276259Sgreen	buffer_clear(&msg);
30376259Sgreen
30476259Sgreen	handle = get_handle(fd_in, id, &handle_len);
30576259Sgreen	if (handle == NULL)
30676259Sgreen		return(-1);
30776259Sgreen
30876259Sgreen	if (dir) {
30976259Sgreen		ents = 0;
31076259Sgreen		*dir = xmalloc(sizeof(**dir));
31176259Sgreen		(*dir)[0] = NULL;
31276259Sgreen	}
31376259Sgreen
31476259Sgreen
31576259Sgreen	for(;;) {
31676259Sgreen		int count;
31776259Sgreen
31876259Sgreen		id = expected_id = msg_id++;
31976259Sgreen
32076259Sgreen		debug3("Sending SSH2_FXP_READDIR I:%d", id);
32176259Sgreen
32276259Sgreen		buffer_clear(&msg);
32376259Sgreen		buffer_put_char(&msg, SSH2_FXP_READDIR);
32476259Sgreen		buffer_put_int(&msg, id);
32576259Sgreen		buffer_put_string(&msg, handle, handle_len);
32676259Sgreen		send_msg(fd_out, &msg);
32776259Sgreen
32876259Sgreen		buffer_clear(&msg);
32976259Sgreen
33076259Sgreen		get_msg(fd_in, &msg);
33176259Sgreen
33276259Sgreen		type = buffer_get_char(&msg);
33376259Sgreen		id = buffer_get_int(&msg);
33476259Sgreen
33576259Sgreen		debug3("Received reply T:%d I:%d", type, id);
33676259Sgreen
33776259Sgreen		if (id != expected_id)
33876259Sgreen			fatal("ID mismatch (%d != %d)", id, expected_id);
33976259Sgreen
34076259Sgreen		if (type == SSH2_FXP_STATUS) {
34176259Sgreen			int status = buffer_get_int(&msg);
34276259Sgreen
34376259Sgreen			debug3("Received SSH2_FXP_STATUS %d", status);
34476259Sgreen
34576259Sgreen			if (status == SSH2_FX_EOF) {
34676259Sgreen				break;
34776259Sgreen			} else {
34876259Sgreen				error("Couldn't read directory: %s",
34976259Sgreen				    fx2txt(status));
35076259Sgreen				do_close(fd_in, fd_out, handle, handle_len);
35176259Sgreen				return(status);
35276259Sgreen			}
35376259Sgreen		} else if (type != SSH2_FXP_NAME)
35476259Sgreen			fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
35576259Sgreen			    SSH2_FXP_NAME, type);
35676259Sgreen
35776259Sgreen		count = buffer_get_int(&msg);
35876259Sgreen		if (count == 0)
35976259Sgreen			break;
36076259Sgreen		debug3("Received %d SSH2_FXP_NAME responses", count);
36176259Sgreen		for(i = 0; i < count; i++) {
36276259Sgreen			char *filename, *longname;
36376259Sgreen			Attrib *a;
36476259Sgreen
36576259Sgreen			filename = buffer_get_string(&msg, NULL);
36676259Sgreen			longname = buffer_get_string(&msg, NULL);
36776259Sgreen			a = decode_attrib(&msg);
36876259Sgreen
36976259Sgreen			if (printflag)
37076259Sgreen				printf("%s\n", longname);
37176259Sgreen
37276259Sgreen			if (dir) {
37376259Sgreen				*dir = xrealloc(*dir, sizeof(**dir) *
37476259Sgreen				    (ents + 2));
37576259Sgreen				(*dir)[ents] = xmalloc(sizeof(***dir));
37676259Sgreen				(*dir)[ents]->filename = xstrdup(filename);
37776259Sgreen				(*dir)[ents]->longname = xstrdup(longname);
37876259Sgreen				memcpy(&(*dir)[ents]->a, a, sizeof(*a));
37976259Sgreen				(*dir)[++ents] = NULL;
38076259Sgreen			}
38176259Sgreen
38276259Sgreen			xfree(filename);
38376259Sgreen			xfree(longname);
38476259Sgreen		}
38576259Sgreen	}
38676259Sgreen
38776259Sgreen	buffer_free(&msg);
38876259Sgreen	do_close(fd_in, fd_out, handle, handle_len);
38976259Sgreen	xfree(handle);
39076259Sgreen
39176259Sgreen	return(0);
39276259Sgreen}
39376259Sgreen
39476259Sgreenint
39576259Sgreendo_ls(int fd_in, int fd_out, char *path)
39676259Sgreen{
39776259Sgreen	return(do_lsreaddir(fd_in, fd_out, path, 1, NULL));
39876259Sgreen}
39976259Sgreen
40076259Sgreenint
40176259Sgreendo_readdir(int fd_in, int fd_out, char *path, SFTP_DIRENT ***dir)
40276259Sgreen{
40376259Sgreen	return(do_lsreaddir(fd_in, fd_out, path, 0, dir));
40476259Sgreen}
40576259Sgreen
40676259Sgreenvoid free_sftp_dirents(SFTP_DIRENT **s)
40776259Sgreen{
40876259Sgreen	int i;
40976259Sgreen
41076259Sgreen	for(i = 0; s[i]; i++) {
41176259Sgreen		xfree(s[i]->filename);
41276259Sgreen		xfree(s[i]->longname);
41376259Sgreen		xfree(s[i]);
41476259Sgreen	}
41576259Sgreen	xfree(s);
41676259Sgreen}
41776259Sgreen
41876259Sgreenint
41976259Sgreendo_rm(int fd_in, int fd_out, char *path)
42076259Sgreen{
42176259Sgreen	u_int status, id;
42276259Sgreen
42376259Sgreen	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
42476259Sgreen
42576259Sgreen	id = msg_id++;
42676259Sgreen	send_string_request(fd_out, id, SSH2_FXP_REMOVE, path, strlen(path));
42776259Sgreen	status = get_status(fd_in, id);
42876259Sgreen	if (status != SSH2_FX_OK)
42976259Sgreen		error("Couldn't delete file: %s", fx2txt(status));
43076259Sgreen	return(status);
43176259Sgreen}
43276259Sgreen
43376259Sgreenint
43476259Sgreendo_mkdir(int fd_in, int fd_out, char *path, Attrib *a)
43576259Sgreen{
43676259Sgreen	u_int status, id;
43776259Sgreen
43876259Sgreen	id = msg_id++;
43976259Sgreen	send_string_attrs_request(fd_out, id, SSH2_FXP_MKDIR, path,
44076259Sgreen	    strlen(path), a);
44176259Sgreen
44276259Sgreen	status = get_status(fd_in, id);
44376259Sgreen	if (status != SSH2_FX_OK)
44476259Sgreen		error("Couldn't create directory: %s", fx2txt(status));
44576259Sgreen
44676259Sgreen	return(status);
44776259Sgreen}
44876259Sgreen
44976259Sgreenint
45076259Sgreendo_rmdir(int fd_in, int fd_out, char *path)
45176259Sgreen{
45276259Sgreen	u_int status, id;
45376259Sgreen
45476259Sgreen	id = msg_id++;
45576259Sgreen	send_string_request(fd_out, id, SSH2_FXP_RMDIR, path, strlen(path));
45676259Sgreen
45776259Sgreen	status = get_status(fd_in, id);
45876259Sgreen	if (status != SSH2_FX_OK)
45976259Sgreen		error("Couldn't remove directory: %s", fx2txt(status));
46076259Sgreen
46176259Sgreen	return(status);
46276259Sgreen}
46376259Sgreen
46476259SgreenAttrib *
46576259Sgreendo_stat(int fd_in, int fd_out, char *path, int quiet)
46676259Sgreen{
46776259Sgreen	u_int id;
46876259Sgreen
46976259Sgreen	id = msg_id++;
47076259Sgreen	send_string_request(fd_out, id, SSH2_FXP_STAT, path, strlen(path));
47176259Sgreen	return(get_decode_stat(fd_in, id, quiet));
47276259Sgreen}
47376259Sgreen
47476259SgreenAttrib *
47576259Sgreendo_lstat(int fd_in, int fd_out, char *path, int quiet)
47676259Sgreen{
47776259Sgreen	u_int id;
47876259Sgreen
47976259Sgreen	id = msg_id++;
48076259Sgreen	send_string_request(fd_out, id, SSH2_FXP_LSTAT, path, strlen(path));
48176259Sgreen	return(get_decode_stat(fd_in, id, quiet));
48276259Sgreen}
48376259Sgreen
48476259SgreenAttrib *
48576259Sgreendo_fstat(int fd_in, int fd_out, char *handle, u_int handle_len, int quiet)
48676259Sgreen{
48776259Sgreen	u_int id;
48876259Sgreen
48976259Sgreen	id = msg_id++;
49076259Sgreen	send_string_request(fd_out, id, SSH2_FXP_FSTAT, handle, handle_len);
49176259Sgreen	return(get_decode_stat(fd_in, id, quiet));
49276259Sgreen}
49376259Sgreen
49476259Sgreenint
49576259Sgreendo_setstat(int fd_in, int fd_out, char *path, Attrib *a)
49676259Sgreen{
49776259Sgreen	u_int status, id;
49876259Sgreen
49976259Sgreen	id = msg_id++;
50076259Sgreen	send_string_attrs_request(fd_out, id, SSH2_FXP_SETSTAT, path,
50176259Sgreen	    strlen(path), a);
50276259Sgreen
50376259Sgreen	status = get_status(fd_in, id);
50476259Sgreen	if (status != SSH2_FX_OK)
50576259Sgreen		error("Couldn't setstat on \"%s\": %s", path,
50676259Sgreen		    fx2txt(status));
50776259Sgreen
50876259Sgreen	return(status);
50976259Sgreen}
51076259Sgreen
51176259Sgreenint
51276259Sgreendo_fsetstat(int fd_in, int fd_out, char *handle, u_int handle_len,
51376259Sgreen    Attrib *a)
51476259Sgreen{
51576259Sgreen	u_int status, id;
51676259Sgreen
51776259Sgreen	id = msg_id++;
51876259Sgreen	send_string_attrs_request(fd_out, id, SSH2_FXP_FSETSTAT, handle,
51976259Sgreen	    handle_len, a);
52076259Sgreen
52176259Sgreen	status = get_status(fd_in, id);
52276259Sgreen	if (status != SSH2_FX_OK)
52376259Sgreen		error("Couldn't fsetstat: %s", fx2txt(status));
52476259Sgreen
52576259Sgreen	return(status);
52676259Sgreen}
52776259Sgreen
52876259Sgreenchar *
52976259Sgreendo_realpath(int fd_in, int fd_out, char *path)
53076259Sgreen{
53176259Sgreen	Buffer msg;
53276259Sgreen	u_int type, expected_id, count, id;
53376259Sgreen	char *filename, *longname;
53476259Sgreen	Attrib *a;
53576259Sgreen
53676259Sgreen	expected_id = id = msg_id++;
53776259Sgreen	send_string_request(fd_out, id, SSH2_FXP_REALPATH, path, strlen(path));
53876259Sgreen
53976259Sgreen	buffer_init(&msg);
54076259Sgreen
54176259Sgreen	get_msg(fd_in, &msg);
54276259Sgreen	type = buffer_get_char(&msg);
54376259Sgreen	id = buffer_get_int(&msg);
54476259Sgreen
54576259Sgreen	if (id != expected_id)
54676259Sgreen		fatal("ID mismatch (%d != %d)", id, expected_id);
54776259Sgreen
54876259Sgreen	if (type == SSH2_FXP_STATUS) {
54976259Sgreen		u_int status = buffer_get_int(&msg);
55076259Sgreen
55176259Sgreen		error("Couldn't canonicalise: %s", fx2txt(status));
55276259Sgreen		return(NULL);
55376259Sgreen	} else if (type != SSH2_FXP_NAME)
55476259Sgreen		fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
55576259Sgreen		    SSH2_FXP_NAME, type);
55676259Sgreen
55776259Sgreen	count = buffer_get_int(&msg);
55876259Sgreen	if (count != 1)
55976259Sgreen		fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
56076259Sgreen
56176259Sgreen	filename = buffer_get_string(&msg, NULL);
56276259Sgreen	longname = buffer_get_string(&msg, NULL);
56376259Sgreen	a = decode_attrib(&msg);
56476259Sgreen
56576259Sgreen	debug3("SSH_FXP_REALPATH %s -> %s", path, filename);
56676259Sgreen
56776259Sgreen	xfree(longname);
56876259Sgreen
56976259Sgreen	buffer_free(&msg);
57076259Sgreen
57176259Sgreen	return(filename);
57276259Sgreen}
57376259Sgreen
57476259Sgreenint
57576259Sgreendo_rename(int fd_in, int fd_out, char *oldpath, char *newpath)
57676259Sgreen{
57776259Sgreen	Buffer msg;
57876259Sgreen	u_int status, id;
57976259Sgreen
58076259Sgreen	buffer_init(&msg);
58176259Sgreen
58276259Sgreen	/* Send rename request */
58376259Sgreen	id = msg_id++;
58476259Sgreen	buffer_put_char(&msg, SSH2_FXP_RENAME);
58576259Sgreen	buffer_put_int(&msg, id);
58676259Sgreen	buffer_put_cstring(&msg, oldpath);
58776259Sgreen	buffer_put_cstring(&msg, newpath);
58876259Sgreen	send_msg(fd_out, &msg);
58976259Sgreen	debug3("Sent message SSH2_FXP_RENAME \"%s\" -> \"%s\"", oldpath,
59076259Sgreen	    newpath);
59176259Sgreen	buffer_free(&msg);
59276259Sgreen
59376259Sgreen	status = get_status(fd_in, id);
59476259Sgreen	if (status != SSH2_FX_OK)
59576259Sgreen		error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, newpath,
59676259Sgreen		    fx2txt(status));
59776259Sgreen
59876259Sgreen	return(status);
59976259Sgreen}
60076259Sgreen
60176259Sgreenint
60276259Sgreendo_symlink(int fd_in, int fd_out, char *oldpath, char *newpath)
60376259Sgreen{
60476259Sgreen	Buffer msg;
60576259Sgreen	u_int status, id;
60676259Sgreen
60776259Sgreen	buffer_init(&msg);
60876259Sgreen
60976259Sgreen	/* Send rename request */
61076259Sgreen	id = msg_id++;
61176259Sgreen	buffer_put_char(&msg, SSH2_FXP_SYMLINK);
61276259Sgreen	buffer_put_int(&msg, id);
61376259Sgreen	buffer_put_cstring(&msg, oldpath);
61476259Sgreen	buffer_put_cstring(&msg, newpath);
61576259Sgreen	send_msg(fd_out, &msg);
61676259Sgreen	debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
61776259Sgreen	    newpath);
61876259Sgreen	buffer_free(&msg);
61976259Sgreen
62076259Sgreen	status = get_status(fd_in, id);
62176259Sgreen	if (status != SSH2_FX_OK)
62276259Sgreen		error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, newpath,
62376259Sgreen		    fx2txt(status));
62476259Sgreen
62576259Sgreen	return(status);
62676259Sgreen}
62776259Sgreen
62876259Sgreenchar *
62976259Sgreendo_readlink(int fd_in, int fd_out, char *path)
63076259Sgreen{
63176259Sgreen	Buffer msg;
63276259Sgreen	u_int type, expected_id, count, id;
63376259Sgreen	char *filename, *longname;
63476259Sgreen	Attrib *a;
63576259Sgreen
63676259Sgreen	expected_id = id = msg_id++;
63776259Sgreen	send_string_request(fd_out, id, SSH2_FXP_READLINK, path, strlen(path));
63876259Sgreen
63976259Sgreen	buffer_init(&msg);
64076259Sgreen
64176259Sgreen	get_msg(fd_in, &msg);
64276259Sgreen	type = buffer_get_char(&msg);
64376259Sgreen	id = buffer_get_int(&msg);
64476259Sgreen
64576259Sgreen	if (id != expected_id)
64676259Sgreen		fatal("ID mismatch (%d != %d)", id, expected_id);
64776259Sgreen
64876259Sgreen	if (type == SSH2_FXP_STATUS) {
64976259Sgreen		u_int status = buffer_get_int(&msg);
65076259Sgreen
65176259Sgreen		error("Couldn't readlink: %s", fx2txt(status));
65276259Sgreen		return(NULL);
65376259Sgreen	} else if (type != SSH2_FXP_NAME)
65476259Sgreen		fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
65576259Sgreen		    SSH2_FXP_NAME, type);
65676259Sgreen
65776259Sgreen	count = buffer_get_int(&msg);
65876259Sgreen	if (count != 1)
65976259Sgreen		fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
66076259Sgreen
66176259Sgreen	filename = buffer_get_string(&msg, NULL);
66276259Sgreen	longname = buffer_get_string(&msg, NULL);
66376259Sgreen	a = decode_attrib(&msg);
66476259Sgreen
66576259Sgreen	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
66676259Sgreen
66776259Sgreen	xfree(longname);
66876259Sgreen
66976259Sgreen	buffer_free(&msg);
67076259Sgreen
67176259Sgreen	return(filename);
67276259Sgreen}
67376259Sgreen
67476259Sgreenint
67576259Sgreendo_download(int fd_in, int fd_out, char *remote_path, char *local_path,
67676259Sgreen    int pflag)
67776259Sgreen{
67876259Sgreen	int local_fd;
67976259Sgreen	u_int expected_id, handle_len, mode, type, id;
68076259Sgreen	u_int64_t offset;
68176259Sgreen	char *handle;
68276259Sgreen	Buffer msg;
68376259Sgreen	Attrib junk, *a;
68476259Sgreen	int status;
68576259Sgreen
68676259Sgreen	a = do_stat(fd_in, fd_out, remote_path, 0);
68776259Sgreen	if (a == NULL)
68876259Sgreen		return(-1);
68976259Sgreen
69076259Sgreen	/* XXX: should we preserve set[ug]id? */
69176259Sgreen	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
69276259Sgreen		mode = S_IWRITE | (a->perm & 0777);
69376259Sgreen	else
69476259Sgreen		mode = 0666;
69576259Sgreen
69676259Sgreen	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
69776259Sgreen	    (a->perm & S_IFDIR)) {
69876259Sgreen		error("Cannot download a directory: %s", remote_path);
69976259Sgreen		return(-1);
70076259Sgreen	}
70176259Sgreen
70276259Sgreen	local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, mode);
70376259Sgreen	if (local_fd == -1) {
70476259Sgreen		error("Couldn't open local file \"%s\" for writing: %s",
70576259Sgreen		    local_path, strerror(errno));
70676259Sgreen		return(-1);
70776259Sgreen	}
70876259Sgreen
70976259Sgreen	buffer_init(&msg);
71076259Sgreen
71176259Sgreen	/* Send open request */
71276259Sgreen	id = msg_id++;
71376259Sgreen	buffer_put_char(&msg, SSH2_FXP_OPEN);
71476259Sgreen	buffer_put_int(&msg, id);
71576259Sgreen	buffer_put_cstring(&msg, remote_path);
71676259Sgreen	buffer_put_int(&msg, SSH2_FXF_READ);
71776259Sgreen	attrib_clear(&junk); /* Send empty attributes */
71876259Sgreen	encode_attrib(&msg, &junk);
71976259Sgreen	send_msg(fd_out, &msg);
72076259Sgreen	debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path);
72176259Sgreen
72276259Sgreen	handle = get_handle(fd_in, id, &handle_len);
72376259Sgreen	if (handle == NULL) {
72476259Sgreen		buffer_free(&msg);
72576259Sgreen		close(local_fd);
72676259Sgreen		return(-1);
72776259Sgreen	}
72876259Sgreen
72976259Sgreen	/* Read from remote and write to local */
73076259Sgreen	offset = 0;
73176259Sgreen	for(;;) {
73276259Sgreen		u_int len;
73376259Sgreen		char *data;
73476259Sgreen
73576259Sgreen		id = expected_id = msg_id++;
73676259Sgreen
73776259Sgreen		buffer_clear(&msg);
73876259Sgreen		buffer_put_char(&msg, SSH2_FXP_READ);
73976259Sgreen		buffer_put_int(&msg, id);
74076259Sgreen		buffer_put_string(&msg, handle, handle_len);
74176259Sgreen		buffer_put_int64(&msg, offset);
74276259Sgreen		buffer_put_int(&msg, COPY_SIZE);
74376259Sgreen		send_msg(fd_out, &msg);
74476259Sgreen		debug3("Sent message SSH2_FXP_READ I:%d O:%llu S:%u",
74576259Sgreen		    id, (unsigned long long)offset, COPY_SIZE);
74676259Sgreen
74776259Sgreen		buffer_clear(&msg);
74876259Sgreen
74976259Sgreen		get_msg(fd_in, &msg);
75076259Sgreen		type = buffer_get_char(&msg);
75176259Sgreen		id = buffer_get_int(&msg);
75276259Sgreen		debug3("Received reply T:%d I:%d", type, id);
75376259Sgreen		if (id != expected_id)
75476259Sgreen			fatal("ID mismatch (%d != %d)", id, expected_id);
75576259Sgreen		if (type == SSH2_FXP_STATUS) {
75676259Sgreen			status = buffer_get_int(&msg);
75776259Sgreen
75876259Sgreen			if (status == SSH2_FX_EOF)
75976259Sgreen				break;
76076259Sgreen			else {
76176259Sgreen				error("Couldn't read from remote "
76276259Sgreen				    "file \"%s\" : %s", remote_path,
76376259Sgreen				     fx2txt(status));
76476259Sgreen				do_close(fd_in, fd_out, handle, handle_len);
76576259Sgreen				goto done;
76676259Sgreen			}
76776259Sgreen		} else if (type != SSH2_FXP_DATA) {
76876259Sgreen			fatal("Expected SSH2_FXP_DATA(%d) packet, got %d",
76976259Sgreen			    SSH2_FXP_DATA, type);
77076259Sgreen		}
77176259Sgreen
77276259Sgreen		data = buffer_get_string(&msg, &len);
77376259Sgreen		if (len > COPY_SIZE)
77476259Sgreen			fatal("Received more data than asked for %d > %d",
77576259Sgreen			    len, COPY_SIZE);
77676259Sgreen
77776259Sgreen		debug3("In read loop, got %d offset %llu", len,
77876259Sgreen		    (unsigned long long)offset);
77976259Sgreen		if (atomicio(write, local_fd, data, len) != len) {
78076259Sgreen			error("Couldn't write to \"%s\": %s", local_path,
78176259Sgreen			    strerror(errno));
78276259Sgreen			do_close(fd_in, fd_out, handle, handle_len);
78376259Sgreen			status = -1;
78476259Sgreen			xfree(data);
78576259Sgreen			goto done;
78676259Sgreen		}
78776259Sgreen
78876259Sgreen		offset += len;
78976259Sgreen		xfree(data);
79076259Sgreen	}
79176259Sgreen	status = do_close(fd_in, fd_out, handle, handle_len);
79276259Sgreen
79376259Sgreen	/* Override umask and utimes if asked */
79476259Sgreen	if (pflag && fchmod(local_fd, mode) == -1)
79576259Sgreen		error("Couldn't set mode on \"%s\": %s", local_path,
79676259Sgreen		    strerror(errno));
79776259Sgreen	if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
79876259Sgreen		struct timeval tv[2];
79976259Sgreen		tv[0].tv_sec = a->atime;
80076259Sgreen		tv[1].tv_sec = a->mtime;
80176259Sgreen		tv[0].tv_usec = tv[1].tv_usec = 0;
80276259Sgreen		if (utimes(local_path, tv) == -1)
80376259Sgreen			error("Can't set times on \"%s\": %s", local_path,
80476259Sgreen			    strerror(errno));
80576259Sgreen	}
80676259Sgreen
80776259Sgreendone:
80876259Sgreen	close(local_fd);
80976259Sgreen	buffer_free(&msg);
81076259Sgreen	xfree(handle);
81176259Sgreen	return status;
81276259Sgreen}
81376259Sgreen
81476259Sgreenint
81576259Sgreendo_upload(int fd_in, int fd_out, char *local_path, char *remote_path,
81676259Sgreen    int pflag)
81776259Sgreen{
81876259Sgreen	int local_fd;
81976259Sgreen	u_int handle_len, id;
82076259Sgreen	u_int64_t offset;
82176259Sgreen	char *handle;
82276259Sgreen	Buffer msg;
82376259Sgreen	struct stat sb;
82476259Sgreen	Attrib a;
82576259Sgreen	int status;
82676259Sgreen
82776259Sgreen	if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
82876259Sgreen		error("Couldn't open local file \"%s\" for reading: %s",
82976259Sgreen		    local_path, strerror(errno));
83076259Sgreen		return(-1);
83176259Sgreen	}
83276259Sgreen	if (fstat(local_fd, &sb) == -1) {
83376259Sgreen		error("Couldn't fstat local file \"%s\": %s",
83476259Sgreen		    local_path, strerror(errno));
83576259Sgreen		close(local_fd);
83676259Sgreen		return(-1);
83776259Sgreen	}
83876259Sgreen	stat_to_attrib(&sb, &a);
83976259Sgreen
84076259Sgreen	a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
84176259Sgreen	a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
84276259Sgreen	a.perm &= 0777;
84376259Sgreen	if (!pflag)
84476259Sgreen		a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
84576259Sgreen
84676259Sgreen	buffer_init(&msg);
84776259Sgreen
84876259Sgreen	/* Send open request */
84976259Sgreen	id = msg_id++;
85076259Sgreen	buffer_put_char(&msg, SSH2_FXP_OPEN);
85176259Sgreen	buffer_put_int(&msg, id);
85276259Sgreen	buffer_put_cstring(&msg, remote_path);
85376259Sgreen	buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC);
85476259Sgreen	encode_attrib(&msg, &a);
85576259Sgreen	send_msg(fd_out, &msg);
85676259Sgreen	debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path);
85776259Sgreen
85876259Sgreen	buffer_clear(&msg);
85976259Sgreen
86076259Sgreen	handle = get_handle(fd_in, id, &handle_len);
86176259Sgreen	if (handle == NULL) {
86276259Sgreen		close(local_fd);
86376259Sgreen		buffer_free(&msg);
86476259Sgreen		return(-1);
86576259Sgreen	}
86676259Sgreen
86776259Sgreen	/* Read from local and write to remote */
86876259Sgreen	offset = 0;
86976259Sgreen	for(;;) {
87076259Sgreen		int len;
87176259Sgreen		char data[COPY_SIZE];
87276259Sgreen
87376259Sgreen		/*
87476259Sgreen		 * Can't use atomicio here because it returns 0 on EOF, thus losing
87576259Sgreen		 * the last block of the file
87676259Sgreen		 */
87776259Sgreen		do
87876259Sgreen			len = read(local_fd, data, COPY_SIZE);
87976259Sgreen		while ((len == -1) && (errno == EINTR || errno == EAGAIN));
88076259Sgreen
88176259Sgreen		if (len == -1)
88276259Sgreen			fatal("Couldn't read from \"%s\": %s", local_path,
88376259Sgreen			    strerror(errno));
88476259Sgreen		if (len == 0)
88576259Sgreen			break;
88676259Sgreen
88776259Sgreen		buffer_clear(&msg);
88876259Sgreen		buffer_put_char(&msg, SSH2_FXP_WRITE);
88976259Sgreen		buffer_put_int(&msg, ++id);
89076259Sgreen		buffer_put_string(&msg, handle, handle_len);
89176259Sgreen		buffer_put_int64(&msg, offset);
89276259Sgreen		buffer_put_string(&msg, data, len);
89376259Sgreen		send_msg(fd_out, &msg);
89476259Sgreen		debug3("Sent message SSH2_FXP_WRITE I:%d O:%llu S:%u",
89576259Sgreen		    id, (unsigned long long)offset, len);
89676259Sgreen
89776259Sgreen		status = get_status(fd_in, id);
89876259Sgreen		if (status != SSH2_FX_OK) {
89976259Sgreen			error("Couldn't write to remote file \"%s\": %s",
90076259Sgreen			    remote_path, fx2txt(status));
90176259Sgreen			do_close(fd_in, fd_out, handle, handle_len);
90276259Sgreen			close(local_fd);
90376259Sgreen			goto done;
90476259Sgreen		}
90576259Sgreen		debug3("In write loop, got %d offset %llu", len,
90676259Sgreen		    (unsigned long long)offset);
90776259Sgreen
90876259Sgreen		offset += len;
90976259Sgreen	}
91076259Sgreen
91176259Sgreen	if (close(local_fd) == -1) {
91276259Sgreen		error("Couldn't close local file \"%s\": %s", local_path,
91376259Sgreen		    strerror(errno));
91476259Sgreen		do_close(fd_in, fd_out, handle, handle_len);
91576259Sgreen		status = -1;
91676259Sgreen		goto done;
91776259Sgreen	}
91876259Sgreen
91976259Sgreen	/* Override umask and utimes if asked */
92076259Sgreen	if (pflag)
92176259Sgreen		do_fsetstat(fd_in, fd_out, handle, handle_len, &a);
92276259Sgreen
92376259Sgreen	status = do_close(fd_in, fd_out, handle, handle_len);
92476259Sgreen
92576259Sgreendone:
92676259Sgreen	xfree(handle);
92776259Sgreen	buffer_free(&msg);
92876259Sgreen	return status;
92976259Sgreen}
93076259Sgreen
931