sftp-client.c 41.1 KB
Newer Older
1
/* $OpenBSD: sftp-client.c,v 1.113 2014/01/17 00:21:06 djm Exp $ */
2
/*
3
 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
4
 *
5 6 7
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
8
 *
9 10 11 12 13 14 15
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 17 18 19
 */

/* XXX: memleaks */
/* XXX: signed vs unsigned */
20
/* XXX: remove all logging, only return status codes */
21 22 23
/* XXX: copy between two remote sites */

#include "includes.h"
24 25

#include <sys/types.h>
26
#include <sys/param.h>
27
#ifdef HAVE_SYS_STATVFS_H
28
#include <sys/statvfs.h>
29
#endif
30
#include "openbsd-compat/sys-queue.h"
31 32 33
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
34 35 36
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
37
#include <sys/uio.h>
38

39
#include <dirent.h>
40
#include <errno.h>
41 42
#include <fcntl.h>
#include <signal.h>
43
#include <stdarg.h>
44
#include <stdio.h>
45
#include <stdlib.h>
46
#include <string.h>
47
#include <unistd.h>
48

49
#include "xmalloc.h"
50
#include "buffer.h"
51 52
#include "log.h"
#include "atomicio.h"
53
#include "progressmeter.h"
54
#include "misc.h"
55 56 57 58 59

#include "sftp.h"
#include "sftp-common.h"
#include "sftp-client.h"

60
extern volatile sig_atomic_t interrupted;
61 62
extern int showprogress;

63
/* Minimum amount of data to read at a time */
64 65
#define MIN_READ_SIZE	512

66 67 68
/* Maximum depth to descend in directory trees */
#define MAX_DIR_DEPTH 64

69 70 71 72 73 74 75
struct sftp_conn {
	int fd_in;
	int fd_out;
	u_int transfer_buflen;
	u_int num_requests;
	u_int version;
	u_int msg_id;
76 77 78
#define SFTP_EXT_POSIX_RENAME	0x00000001
#define SFTP_EXT_STATVFS	0x00000002
#define SFTP_EXT_FSTATVFS	0x00000004
79
#define SFTP_EXT_HARDLINK	0x00000008
80
#define SFTP_EXT_FSYNC		0x00000010
81
	u_int exts;
82 83
	u_int64_t limit_kbps;
	struct bwlimit bwlimit_in, bwlimit_out;
84
};
85

86
static char *
87 88 89 90 91 92 93 94 95 96 97 98
get_handle(struct sftp_conn *conn, u_int expected_id, u_int *len,
    const char *errfmt, ...) __attribute__((format(printf, 4, 5)));

/* ARGSUSED */
static int
sftpio(void *_bwlimit, size_t amount)
{
	struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit;

	bandwidth_limit(bwlimit, amount);
	return 0;
}
99

100
static void
101
send_msg(struct sftp_conn *conn, Buffer *m)
102
{
103
	u_char mlen[4];
104
	struct iovec iov[2];
105

106
	if (buffer_len(m) > SFTP_MAX_MSG_LENGTH)
107
		fatal("Outbound message too long %u", buffer_len(m));
108

109
	/* Send length first */
110
	put_u32(mlen, buffer_len(m));
111 112 113 114
	iov[0].iov_base = mlen;
	iov[0].iov_len = sizeof(mlen);
	iov[1].iov_base = buffer_ptr(m);
	iov[1].iov_len = buffer_len(m);
115

116
	if (atomiciov6(writev, conn->fd_out, iov, 2,
117
	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) !=
118
	    buffer_len(m) + sizeof(mlen))
119 120
		fatal("Couldn't send packet: %s", strerror(errno));

121
	buffer_clear(m);
122 123
}

124
static void
125
get_msg(struct sftp_conn *conn, Buffer *m)
126
{
127
	u_int msg_len;
128

129
	buffer_append_space(m, 4);
130 131
	if (atomicio6(read, conn->fd_in, buffer_ptr(m), 4,
	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) {
132 133 134 135 136
		if (errno == EPIPE)
			fatal("Connection closed");
		else
			fatal("Couldn't read packet: %s", strerror(errno));
	}
137

138
	msg_len = buffer_get_int(m);
139
	if (msg_len > SFTP_MAX_MSG_LENGTH)
140
		fatal("Received message too long %u", msg_len);
141

142
	buffer_append_space(m, msg_len);
143 144 145
	if (atomicio6(read, conn->fd_in, buffer_ptr(m), msg_len,
	    conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in)
	    != msg_len) {
146 147 148 149 150
		if (errno == EPIPE)
			fatal("Connection closed");
		else
			fatal("Read packet: %s", strerror(errno));
	}
151 152
}

153
static void
154
send_string_request(struct sftp_conn *conn, u_int id, u_int code, char *s,
155 156 157 158 159 160 161 162
    u_int len)
{
	Buffer msg;

	buffer_init(&msg);
	buffer_put_char(&msg, code);
	buffer_put_int(&msg, id);
	buffer_put_string(&msg, s, len);
163 164
	send_msg(conn, &msg);
	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
165 166 167
	buffer_free(&msg);
}

168
static void
169 170
send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
    char *s, u_int len, Attrib *a)
171 172 173 174 175 176 177 178
{
	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);
179 180
	send_msg(conn, &msg);
	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
181 182 183
	buffer_free(&msg);
}

184
static u_int
185
get_status(struct sftp_conn *conn, u_int expected_id)
186 187 188 189 190
{
	Buffer msg;
	u_int type, id, status;

	buffer_init(&msg);
191
	get_msg(conn, &msg);
192 193 194 195
	type = buffer_get_char(&msg);
	id = buffer_get_int(&msg);

	if (id != expected_id)
196
		fatal("ID mismatch (%u != %u)", id, expected_id);
197
	if (type != SSH2_FXP_STATUS)
198
		fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
199 200 201 202 203
		    SSH2_FXP_STATUS, type);

	status = buffer_get_int(&msg);
	buffer_free(&msg);

204
	debug3("SSH2_FXP_STATUS %u", status);
205

206
	return status;
207 208
}

209
static char *
210 211
get_handle(struct sftp_conn *conn, u_int expected_id, u_int *len,
    const char *errfmt, ...)
212 213 214
{
	Buffer msg;
	u_int type, id;
215 216 217 218 219 220 221 222
	char *handle, errmsg[256];
	va_list args;
	int status;

	va_start(args, errfmt);
	if (errfmt != NULL)
		vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
	va_end(args);
223 224

	buffer_init(&msg);
225
	get_msg(conn, &msg);
226 227 228 229
	type = buffer_get_char(&msg);
	id = buffer_get_int(&msg);

	if (id != expected_id)
230 231
		fatal("%s: ID mismatch (%u != %u)",
		    errfmt == NULL ? __func__ : errmsg, id, expected_id);
232
	if (type == SSH2_FXP_STATUS) {
233 234 235
		status = buffer_get_int(&msg);
		if (errfmt != NULL)
			error("%s: %s", errmsg, fx2txt(status));
236
		buffer_free(&msg);
237 238
		return(NULL);
	} else if (type != SSH2_FXP_HANDLE)
239 240
		fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
		    errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
241 242 243 244 245 246 247

	handle = buffer_get_string(&msg, len);
	buffer_free(&msg);

	return(handle);
}

248
static Attrib *
249
get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet)
250 251 252 253 254 255
{
	Buffer msg;
	u_int type, id;
	Attrib *a;

	buffer_init(&msg);
256
	get_msg(conn, &msg);
257 258 259 260

	type = buffer_get_char(&msg);
	id = buffer_get_int(&msg);

261
	debug3("Received stat reply T:%u I:%u", type, id);
262
	if (id != expected_id)
263
		fatal("ID mismatch (%u != %u)", id, expected_id);
264 265 266
	if (type == SSH2_FXP_STATUS) {
		int status = buffer_get_int(&msg);

267 268 269 270
		if (quiet)
			debug("Couldn't stat remote file: %s", fx2txt(status));
		else
			error("Couldn't stat remote file: %s", fx2txt(status));
271
		buffer_free(&msg);
272 273
		return(NULL);
	} else if (type != SSH2_FXP_ATTRS) {
274
		fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
275 276 277 278 279 280 281 282
		    SSH2_FXP_ATTRS, type);
	}
	a = decode_attrib(&msg);
	buffer_free(&msg);

	return(a);
}

283
static int
284 285
get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st,
    u_int expected_id, int quiet)
286 287 288 289 290
{
	Buffer msg;
	u_int type, id, flag;

	buffer_init(&msg);
291
	get_msg(conn, &msg);
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313

	type = buffer_get_char(&msg);
	id = buffer_get_int(&msg);

	debug3("Received statvfs reply T:%u I:%u", type, id);
	if (id != expected_id)
		fatal("ID mismatch (%u != %u)", id, expected_id);
	if (type == SSH2_FXP_STATUS) {
		int status = buffer_get_int(&msg);

		if (quiet)
			debug("Couldn't statvfs: %s", fx2txt(status));
		else
			error("Couldn't statvfs: %s", fx2txt(status));
		buffer_free(&msg);
		return -1;
	} else if (type != SSH2_FXP_EXTENDED_REPLY) {
		fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
		    SSH2_FXP_EXTENDED_REPLY, type);
	}

	bzero(st, sizeof(*st));
314 315
	st->f_bsize = buffer_get_int64(&msg);
	st->f_frsize = buffer_get_int64(&msg);
316 317 318 319 320 321
	st->f_blocks = buffer_get_int64(&msg);
	st->f_bfree = buffer_get_int64(&msg);
	st->f_bavail = buffer_get_int64(&msg);
	st->f_files = buffer_get_int64(&msg);
	st->f_ffree = buffer_get_int64(&msg);
	st->f_favail = buffer_get_int64(&msg);
322
	st->f_fsid = buffer_get_int64(&msg);
323 324
	flag = buffer_get_int64(&msg);
	st->f_namemax = buffer_get_int64(&msg);
325 326 327 328 329 330 331 332 333

	st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
	st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;

	buffer_free(&msg);

	return 0;
}

334
struct sftp_conn *
335 336
do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
    u_int64_t limit_kbps)
337
{
338
	u_int type;
339
	Buffer msg;
340
	struct sftp_conn *ret;
341

342 343
	ret = xcalloc(1, sizeof(*ret));
	ret->msg_id = 1;
344 345 346 347 348 349 350
	ret->fd_in = fd_in;
	ret->fd_out = fd_out;
	ret->transfer_buflen = transfer_buflen;
	ret->num_requests = num_requests;
	ret->exts = 0;
	ret->limit_kbps = 0;

351 352 353
	buffer_init(&msg);
	buffer_put_char(&msg, SSH2_FXP_INIT);
	buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
354
	send_msg(ret, &msg);
355 356 357

	buffer_clear(&msg);

358
	get_msg(ret, &msg);
359

360
	/* Expecting a VERSION reply */
361
	if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) {
362
		error("Invalid packet back from SSH2_FXP_INIT (type %u)",
363 364
		    type);
		buffer_free(&msg);
365
		return(NULL);
366
	}
367
	ret->version = buffer_get_int(&msg);
368

369
	debug2("Remote version: %u", ret->version);
370 371 372 373 374

	/* Check for extensions */
	while (buffer_len(&msg) > 0) {
		char *name = buffer_get_string(&msg, NULL);
		char *value = buffer_get_string(&msg, NULL);
375
		int known = 0;
376

377
		if (strcmp(name, "posix-rename@openssh.com") == 0 &&
378
		    strcmp(value, "1") == 0) {
379
			ret->exts |= SFTP_EXT_POSIX_RENAME;
380 381 382
			known = 1;
		} else if (strcmp(name, "statvfs@openssh.com") == 0 &&
		    strcmp(value, "2") == 0) {
383
			ret->exts |= SFTP_EXT_STATVFS;
384
			known = 1;
385
		} else if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
386
		    strcmp(value, "2") == 0) {
387
			ret->exts |= SFTP_EXT_FSTATVFS;
388
			known = 1;
389 390 391 392
		} else if (strcmp(name, "hardlink@openssh.com") == 0 &&
		    strcmp(value, "1") == 0) {
			ret->exts |= SFTP_EXT_HARDLINK;
			known = 1;
393 394 395 396
 		} else if (strcmp(name, "fsync@openssh.com") == 0 &&
 		    strcmp(value, "1") == 0) {
 			ret->exts |= SFTP_EXT_FSYNC;
 			known = 1;
397 398 399 400 401 402 403
		}
		if (known) {
			debug2("Server supports extension \"%s\" revision %s",
			    name, value);
		} else {
			debug2("Unrecognised server extension \"%s\"", name);
		}
404 405
		free(name);
		free(value);
406 407 408
	}

	buffer_free(&msg);
Damien Miller's avatar
Damien Miller committed
409

410
	/* Some filexfer v.0 servers don't support large packets */
411
	if (ret->version == 0)
412
		ret->transfer_buflen = MIN(ret->transfer_buflen, 20480);
413

414 415 416 417 418 419 420 421 422
	ret->limit_kbps = limit_kbps;
	if (ret->limit_kbps > 0) {
		bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps,
		    ret->transfer_buflen);
		bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps,
		    ret->transfer_buflen);
	}

	return ret;
423 424 425 426 427
}

u_int
sftp_proto_version(struct sftp_conn *conn)
{
428
	return conn->version;
429 430 431
}

int
432
do_close(struct sftp_conn *conn, char *handle, u_int handle_len)
433 434 435 436 437 438
{
	u_int id, status;
	Buffer msg;

	buffer_init(&msg);

439
	id = conn->msg_id++;
440 441 442
	buffer_put_char(&msg, SSH2_FXP_CLOSE);
	buffer_put_int(&msg, id);
	buffer_put_string(&msg, handle, handle_len);
443
	send_msg(conn, &msg);
444
	debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
445

446
	status = get_status(conn, id);
447 448 449 450 451
	if (status != SSH2_FX_OK)
		error("Couldn't close file: %s", fx2txt(status));

	buffer_free(&msg);

452
	return status;
453 454
}

455

456
static int
457
do_lsreaddir(struct sftp_conn *conn, char *path, int print_flag,
458
    SFTP_DIRENT ***dir)
459 460
{
	Buffer msg;
461
	u_int count, type, id, handle_len, i, expected_id, ents = 0;
462
	char *handle;
463 464 465 466
	int status = SSH2_FX_FAILURE;

	if (dir)
		*dir = NULL;
467

468
	id = conn->msg_id++;
469 470 471 472 473

	buffer_init(&msg);
	buffer_put_char(&msg, SSH2_FXP_OPENDIR);
	buffer_put_int(&msg, id);
	buffer_put_cstring(&msg, path);
474
	send_msg(conn, &msg);
475

476
	handle = get_handle(conn, id, &handle_len,
477
	    "remote readdir(\"%s\")", path);
478 479
	if (handle == NULL) {
		buffer_free(&msg);
480
		return -1;
481
	}
482

483 484
	if (dir) {
		ents = 0;
485
		*dir = xcalloc(1, sizeof(**dir));
486 487 488
		(*dir)[0] = NULL;
	}

489
	for (; !interrupted;) {
490
		id = expected_id = conn->msg_id++;
491

492
		debug3("Sending SSH2_FXP_READDIR I:%u", id);
493 494 495 496 497

		buffer_clear(&msg);
		buffer_put_char(&msg, SSH2_FXP_READDIR);
		buffer_put_int(&msg, id);
		buffer_put_string(&msg, handle, handle_len);
498
		send_msg(conn, &msg);
499 500 501

		buffer_clear(&msg);

502
		get_msg(conn, &msg);
503 504 505 506

		type = buffer_get_char(&msg);
		id = buffer_get_int(&msg);

507
		debug3("Received reply T:%u I:%u", type, id);
508 509

		if (id != expected_id)
510
			fatal("ID mismatch (%u != %u)", id, expected_id);
511 512

		if (type == SSH2_FXP_STATUS) {
513
			status = buffer_get_int(&msg);
514
			debug3("Received SSH2_FXP_STATUS %d", status);
515
			if (status == SSH2_FX_EOF)
516
				break;
517 518
			error("Couldn't read directory: %s", fx2txt(status));
			goto out;
519
		} else if (type != SSH2_FXP_NAME)
520
			fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
521 522 523
			    SSH2_FXP_NAME, type);

		count = buffer_get_int(&msg);
524 525 526
		if (count == 0)
			break;
		debug3("Received %d SSH2_FXP_NAME responses", count);
527
		for (i = 0; i < count; i++) {
528 529 530 531 532 533 534
			char *filename, *longname;
			Attrib *a;

			filename = buffer_get_string(&msg, NULL);
			longname = buffer_get_string(&msg, NULL);
			a = decode_attrib(&msg);

535
			if (print_flag)
536 537
				printf("%s\n", longname);

538 539 540 541 542 543 544 545
			/*
			 * Directory entries should never contain '/'
			 * These can be used to attack recursive ops
			 * (e.g. send '../../../../etc/passwd')
			 */
			if (strchr(filename, '/') != NULL) {
				error("Server sent suspect path \"%s\" "
				    "during readdir of \"%s\"", filename, path);
546
			} else if (dir) {
547
				*dir = xrealloc(*dir, ents + 2, sizeof(**dir));
548
				(*dir)[ents] = xcalloc(1, sizeof(***dir));
549 550 551 552 553
				(*dir)[ents]->filename = xstrdup(filename);
				(*dir)[ents]->longname = xstrdup(longname);
				memcpy(&(*dir)[ents]->a, a, sizeof(*a));
				(*dir)[++ents] = NULL;
			}
554 555
			free(filename);
			free(longname);
556 557
		}
	}
558
	status = 0;
559

560
 out:
561
	buffer_free(&msg);
562
	do_close(conn, handle, handle_len);
563
	free(handle);
564

565 566 567 568 569 570
	if (status != 0 && dir != NULL) {
		/* Don't return results on error */
		free_sftp_dirents(*dir);
		*dir = NULL;
	} else if (interrupted && dir != NULL && *dir != NULL) {
		/* Don't return partial matches on interrupt */
571
		free_sftp_dirents(*dir);
572
		*dir = xcalloc(1, sizeof(**dir));
573 574 575
		**dir = NULL;
	}

576
	return status;
577 578
}

579
int
580
do_readdir(struct sftp_conn *conn, char *path, SFTP_DIRENT ***dir)
581
{
582
	return(do_lsreaddir(conn, path, 0, dir));
583 584 585 586 587
}

void free_sftp_dirents(SFTP_DIRENT **s)
{
	int i;
588

589 590
	if (s == NULL)
		return;
591
	for (i = 0; s[i]; i++) {
592 593 594
		free(s[i]->filename);
		free(s[i]->longname);
		free(s[i]);
595
	}
596
	free(s);
597 598
}

599
int
600
do_rm(struct sftp_conn *conn, char *path)
601 602 603 604 605
{
	u_int status, id;

	debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);

606
	id = conn->msg_id++;
607 608
	send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path));
	status = get_status(conn, id);
609 610 611 612 613 614
	if (status != SSH2_FX_OK)
		error("Couldn't delete file: %s", fx2txt(status));
	return(status);
}

int
615
do_mkdir(struct sftp_conn *conn, char *path, Attrib *a, int print_flag)
616 617 618
{
	u_int status, id;

619
	id = conn->msg_id++;
620
	send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path,
621 622
	    strlen(path), a);

623
	status = get_status(conn, id);
624
	if (status != SSH2_FX_OK && print_flag)
625 626 627 628 629 630
		error("Couldn't create directory: %s", fx2txt(status));

	return(status);
}

int
631
do_rmdir(struct sftp_conn *conn, char *path)
632 633 634
{
	u_int status, id;

635
	id = conn->msg_id++;
636
	send_string_request(conn, id, SSH2_FXP_RMDIR, path,
637
	    strlen(path));
638

639
	status = get_status(conn, id);
640 641 642 643 644 645 646
	if (status != SSH2_FX_OK)
		error("Couldn't remove directory: %s", fx2txt(status));

	return(status);
}

Attrib *
647
do_stat(struct sftp_conn *conn, char *path, int quiet)
648 649 650
{
	u_int id;

651 652
	id = conn->msg_id++;

653
	send_string_request(conn, id,
654
	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
655 656
	    path, strlen(path));

657
	return(get_decode_stat(conn, id, quiet));
658 659 660
}

Attrib *
661
do_lstat(struct sftp_conn *conn, char *path, int quiet)
662 663 664
{
	u_int id;

665 666 667 668
	if (conn->version == 0) {
		if (quiet)
			debug("Server version does not support lstat operation");
		else
669
			logit("Server version does not support lstat operation");
670
		return(do_stat(conn, path, quiet));
671 672 673
	}

	id = conn->msg_id++;
674
	send_string_request(conn, id, SSH2_FXP_LSTAT, path,
675 676
	    strlen(path));

677
	return(get_decode_stat(conn, id, quiet));
678 679
}

680
#ifdef notyet
681
Attrib *
682
do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet)
683 684 685
{
	u_int id;

686
	id = conn->msg_id++;
687
	send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
688 689
	    handle_len);

690
	return(get_decode_stat(conn, id, quiet));
691
}
692
#endif
693 694

int
695
do_setstat(struct sftp_conn *conn, char *path, Attrib *a)
696 697 698
{
	u_int status, id;

699
	id = conn->msg_id++;
700
	send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path,
701 702
	    strlen(path), a);

703
	status = get_status(conn, id);
704 705 706 707 708 709 710 711
	if (status != SSH2_FX_OK)
		error("Couldn't setstat on \"%s\": %s", path,
		    fx2txt(status));

	return(status);
}

int
712
do_fsetstat(struct sftp_conn *conn, char *handle, u_int handle_len,
713 714 715 716
    Attrib *a)
{
	u_int status, id;

717
	id = conn->msg_id++;
718
	send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle,
719 720
	    handle_len, a);

721
	status = get_status(conn, id);
722 723 724 725 726 727 728
	if (status != SSH2_FX_OK)
		error("Couldn't fsetstat: %s", fx2txt(status));

	return(status);
}

char *
729
do_realpath(struct sftp_conn *conn, char *path)
730 731 732 733 734 735
{
	Buffer msg;
	u_int type, expected_id, count, id;
	char *filename, *longname;
	Attrib *a;

736
	expected_id = id = conn->msg_id++;
737
	send_string_request(conn, id, SSH2_FXP_REALPATH, path,
738
	    strlen(path));
739 740 741

	buffer_init(&msg);

742
	get_msg(conn, &msg);
743 744 745 746
	type = buffer_get_char(&msg);
	id = buffer_get_int(&msg);

	if (id != expected_id)
747
		fatal("ID mismatch (%u != %u)", id, expected_id);
748 749 750 751

	if (type == SSH2_FXP_STATUS) {
		u_int status = buffer_get_int(&msg);

752
		error("Couldn't canonicalize: %s", fx2txt(status));
753 754
		buffer_free(&msg);
		return NULL;
755
	} else if (type != SSH2_FXP_NAME)
756
		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
757 758 759 760 761 762 763 764 765 766
		    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);

767 768
	debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename,
	    (unsigned long)a->size);
769

770
	free(longname);
771 772 773 774 775 776 777

	buffer_free(&msg);

	return(filename);
}

int
778 779
do_rename(struct sftp_conn *conn, char *oldpath, char *newpath,
    int force_legacy)
780 781 782
{
	Buffer msg;
	u_int status, id;
783
	int use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy;
784 785 786 787

	buffer_init(&msg);

	/* Send rename request */
788
	id = conn->msg_id++;
789
	if (use_ext) {
790 791 792 793 794 795 796
		buffer_put_char(&msg, SSH2_FXP_EXTENDED);
		buffer_put_int(&msg, id);
		buffer_put_cstring(&msg, "posix-rename@openssh.com");
	} else {
		buffer_put_char(&msg, SSH2_FXP_RENAME);
		buffer_put_int(&msg, id);
	}
797 798
	buffer_put_cstring(&msg, oldpath);
	buffer_put_cstring(&msg, newpath);
799
	send_msg(conn, &msg);
800
	debug3("Sent message %s \"%s\" -> \"%s\"",
801 802
	    use_ext ? "posix-rename@openssh.com" : "SSH2_FXP_RENAME",
	    oldpath, newpath);
803 804
	buffer_free(&msg);

805
	status = get_status(conn, id);
806
	if (status != SSH2_FX_OK)
807 808
		error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath,
		    newpath, fx2txt(status));
809 810 811 812

	return(status);
}

813 814 815 816 817 818 819 820 821 822 823
int
do_hardlink(struct sftp_conn *conn, char *oldpath, char *newpath)
{
	Buffer msg;
	u_int status, id;

	if ((conn->exts & SFTP_EXT_HARDLINK) == 0) {
		error("Server does not support hardlink@openssh.com extension");
		return -1;
	}

824 825 826 827
	buffer_init(&msg);

	/* Send link request */
	id = conn->msg_id++;
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
	buffer_put_char(&msg, SSH2_FXP_EXTENDED);
	buffer_put_int(&msg, id);
	buffer_put_cstring(&msg, "hardlink@openssh.com");
	buffer_put_cstring(&msg, oldpath);
	buffer_put_cstring(&msg, newpath);
	send_msg(conn, &msg);
	debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"",
	       oldpath, newpath);
	buffer_free(&msg);

	status = get_status(conn, id);
	if (status != SSH2_FX_OK)
		error("Couldn't link file \"%s\" to \"%s\": %s", oldpath,
		    newpath, fx2txt(status));

	return(status);
}

Damien Miller's avatar
Damien Miller committed
846
int
847
do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath)
Damien Miller's avatar
Damien Miller committed
848 849 850 851
{
	Buffer msg;
	u_int status, id;

852 853 854 855 856
	if (conn->version < 3) {
		error("This server does not support the symlink operation");
		return(SSH2_FX_OP_UNSUPPORTED);
	}

Damien Miller's avatar
Damien Miller committed
857 858
	buffer_init(&msg);

859
	/* Send symlink request */
860
	id = conn->msg_id++;
Damien Miller's avatar
Damien Miller committed
861 862 863 864
	buffer_put_char(&msg, SSH2_FXP_SYMLINK);
	buffer_put_int(&msg, id);
	buffer_put_cstring(&msg, oldpath);
	buffer_put_cstring(&msg, newpath);
865
	send_msg(conn, &msg);
Damien Miller's avatar
Damien Miller committed
866 867 868 869
	debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
	    newpath);
	buffer_free(&msg);

870
	status = get_status(conn, id);
Damien Miller's avatar
Damien Miller committed
871
	if (status != SSH2_FX_OK)
872
		error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath,
873
		    newpath, fx2txt(status));
Damien Miller's avatar
Damien Miller committed
874 875 876 877

	return(status);
}

878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
int
do_fsync(struct sftp_conn *conn, char *handle, u_int handle_len)
{
	Buffer msg;
	u_int status, id;

	/* Silently return if the extension is not supported */
	if ((conn->exts & SFTP_EXT_FSYNC) == 0)
		return -1;

	buffer_init(&msg);

	/* Send fsync request */
	id = conn->msg_id++;

	buffer_put_char(&msg, SSH2_FXP_EXTENDED);
	buffer_put_int(&msg, id);
	buffer_put_cstring(&msg, "fsync@openssh.com");
	buffer_put_string(&msg, handle, handle_len);
	send_msg(conn, &msg);
	debug3("Sent message fsync@openssh.com I:%u", id);
	buffer_free(&msg);

	status = get_status(conn, id);
	if (status != SSH2_FX_OK)
		error("Couldn't sync file: %s", fx2txt(status));

	return status;
}

908
#ifdef notyet
Damien Miller's avatar
Damien Miller committed
909
char *
910
do_readlink(struct sftp_conn *conn, char *path)
Damien Miller's avatar
Damien Miller committed
911 912 913 914 915 916
{
	Buffer msg;
	u_int type, expected_id, count, id;
	char *filename, *longname;
	Attrib *a;

917
	expected_id = id = conn->msg_id++;
918
	send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path));
Damien Miller's avatar
Damien Miller committed
919 920 921

	buffer_init(&msg);

922
	get_msg(conn, &msg);
Damien Miller's avatar
Damien Miller committed
923 924 925 926
	type = buffer_get_char(&msg);
	id = buffer_get_int(&msg);

	if (id != expected_id)
927
		fatal("ID mismatch (%u != %u)", id, expected_id);
Damien Miller's avatar
Damien Miller committed
928 929 930 931 932

	if (type == SSH2_FXP_STATUS) {
		u_int status = buffer_get_int(&msg);

		error("Couldn't readlink: %s", fx2txt(status));
933
		buffer_free(&msg);
Damien Miller's avatar
Damien Miller committed
934 935
		return(NULL);
	} else if (type != SSH2_FXP_NAME)
936
		fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
Damien Miller's avatar
Damien Miller committed
937 938 939 940 941 942 943 944 945 946 947 948
		    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);

	debug3("SSH_FXP_READLINK %s -> %s", path, filename);

949
	free(longname);
Damien Miller's avatar
Damien Miller committed
950 951 952 953 954

	buffer_free(&msg);

	return(filename);
}
955
#endif
Damien Miller's avatar
Damien Miller committed
956

957
int
958
do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
    int quiet)
{
	Buffer msg;
	u_int id;

	if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
		error("Server does not support statvfs@openssh.com extension");
		return -1;
	}

	id = conn->msg_id++;

	buffer_init(&msg);
	buffer_clear(&msg);
	buffer_put_char(&msg, SSH2_FXP_EXTENDED);
	buffer_put_int(&msg, id);
	buffer_put_cstring(&msg, "statvfs@openssh.com");
	buffer_put_cstring(&msg, path);
977
	send_msg(conn, &msg);
978 979
	buffer_free(&msg);

980
	return get_decode_statvfs(conn, st, id, quiet);
981 982 983 984 985
}

#ifdef notyet
int
do_fstatvfs(struct sftp_conn *conn, const char *handle, u_int handle_len,
986
    struct sftp_statvfs *st, int quiet)
987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
{
	Buffer msg;
	u_int id;

	if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
		error("Server does not support fstatvfs@openssh.com extension");
		return -1;
	}

	id = conn->msg_id++;

	buffer_init(&msg);
	buffer_clear(&msg);
	buffer_put_char(&msg, SSH2_FXP_EXTENDED);
	buffer_put_int(&msg, id);
	buffer_put_cstring(&msg, "fstatvfs@openssh.com");
	buffer_put_string(&msg, handle, handle_len);
1004
	send_msg(conn, &msg);
1005 1006
	buffer_free(&msg);

1007
	return get_decode_statvfs(conn, st, id, quiet);
1008 1009 1010
}
#endif

1011
static void
1012 1013
send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
    u_int len, char *handle, u_int handle_len)
1014 1015
{
	Buffer msg;
1016

1017 1018 1019 1020 1021 1022 1023
	buffer_init(&msg);
	buffer_clear(&msg);
	buffer_put_char(&msg, SSH2_FXP_READ);
	buffer_put_int(&msg, id);
	buffer_put_string(&msg, handle, handle_len);
	buffer_put_int64(&msg, offset);
	buffer_put_int(&msg, len);
1024
	send_msg(conn, &msg);
1025
	buffer_free(&msg);
1026
}
1027

1028
int
1029
do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
1030
    Attrib *a, int preserve_flag, int resume_flag, int fsync_flag)
1031
{
1032
	Attrib junk;
1033 1034
	Buffer msg;
	char *handle;
1035 1036 1037
	int local_fd = -1, status = 0, write_error;
	int read_error, write_errno, reordered = 0;
	u_int64_t offset = 0, size, highwater;
1038
	u_int handle_len, mode, type, id, buflen, num_req, max_req;
1039
	off_t progress_counter;
1040
	struct stat st;
1041 1042 1043 1044
	struct request {
		u_int id;
		u_int len;
		u_int64_t offset;
1045
		TAILQ_ENTRY(request) tq;
1046 1047 1048 1049 1050
	};
	TAILQ_HEAD(reqhead, request) requests;
	struct request *req;

	TAILQ_INIT(&requests);
1051

1052 1053
	if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
		return -1;
1054

1055
	/* Do not preserve set[ug]id here, as we do not preserve ownership */
1056
	if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1057
		mode = a->perm & 0777;
1058 1059 1060
	else
		mode = 0666;

1061
	if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
1062 1063
	    (!S_ISREG(a->perm))) {
		error("Cannot download non-regular file: %s", remote_path);
1064 1065 1066
		return(-1);
	}

1067 1068 1069 1070 1071
	if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
		size = a->size;
	else
		size = 0;

1072
	buflen = conn->transfer_buflen;
1073 1074 1075
	buffer_init(&msg);

	/* Send open request */
1076
	id = conn->msg_id++;
1077 1078 1079 1080 1081 1082
	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);
1083
	send_msg(conn, &msg);
1084
	debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
1085

1086
	handle = get_handle(conn, id, &handle_len,
1087
	    "remote open(\"%s\")", remote_path);
1088 1089 1090 1091 1092
	if (handle == NULL) {
		buffer_free(&msg);
		return(-1);
	}

1093 1094
	local_fd = open(local_path,
	    O_WRONLY | O_CREAT | (resume_flag ? 0 : O_TRUNC), mode | S_IWUSR);
1095 1096 1097
	if (local_fd == -1) {
		error("Couldn't open local file \"%s\" for writing: %s",
		    local_path, strerror(errno));
1098 1099 1100
		goto fail;
	}
	offset = highwater = 0;
1101
	if (resume_flag) {
1102 1103 1104 1105 1106
		if (fstat(local_fd, &st) == -1) {
			error("Unable to stat local file \"%s\": %s",
			    local_path, strerror(errno));
			goto fail;
		}
1107 1108 1109 1110 1111
		if (st.st_size < 0) {
			error("\"%s\" has negative size", local_path);
			goto fail;
		}
		if ((u_int64_t)st.st_size > size) {
1112 1113 1114 1115 1116 1117
			error("Unable to resume download of \"%s\": "
			    "local file is larger than remote", local_path);
 fail:
			do_close(conn, handle, handle_len);
			buffer_free(&msg);
			free(handle);
1118 1119
			if (local_fd != -1)
				close(local_fd);
1120 1121 1122
			return -1;
		}
		offset = highwater = st.st_size;
1123 1124
	}

1125
	/* Read from remote and write to local */
1126
	write_error = read_error = write_errno = num_req = 0;
1127
	max_req = 1;
1128
	progress_counter = offset;
1129

1130 1131
	if (showprogress && size != 0)
		start_progress_meter(remote_path, size, &progress_counter);
1132

1133
	while (num_req > 0 || max_req > 0) {
1134
		char *data;
1135
		u_int len;
1136

1137
		/*
1138
		 * Simulate EOF on interrupt: stop sending new requests and
1139 1140 1141 1142 1143 1144 1145 1146
		 * allow outstanding requests to drain gracefully
		 */
		if (interrupted) {
			if (num_req == 0) /* If we haven't started yet... */
				break;
			max_req = 0;
		}

1147 1148
		/* Send some more requests */
		while (num_req < max_req) {
1149
			debug3("Request range %llu -> %llu (%d/%d)",
1150 1151 1152
			    (unsigned long long)offset,
			    (unsigned long long)offset + buflen - 1,
			    num_req, max_req);
1153
			req = xcalloc(1, sizeof(*req));
1154
			req->id = conn->msg_id++;
1155 1156 1157 1158 1159
			req->len = buflen;
			req->offset = offset;
			offset += buflen;
			num_req++;
			TAILQ_INSERT_TAIL(&requests, req, tq);
1160
			send_read_request(conn, req->id, req->offset,
1161 1162
			    req->len, handle, handle_len);
		}
1163 1164

		buffer_clear(&msg);
1165
		get_msg(conn, &msg);
1166 1167
		type = buffer_get_char(&msg);
		id = buffer_get_int(&msg);
1168
		debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
1169 1170

		/* Find the request in our queue */
1171
		for (req = TAILQ_FIRST(&requests);
1172 1173 1174 1175 1176 1177 1178 1179
		    req != NULL && req->id != id;
		    req = TAILQ_NEXT(req, tq))
			;
		if (req == NULL)
			fatal("Unexpected reply %u", id);

		switch (type) {
		case SSH2_FXP_STATUS:
1180
			status = buffer_get_int(&msg);
1181 1182 1183 1184
			if (status != SSH2_FX_EOF)
				read_error = 1;
			max_req = 0;
			TAILQ_REMOVE(&requests, req, tq);
1185
			free(req);
1186 1187 1188 1189
			num_req--;
			break;
		case SSH2_FXP_DATA:
			data = buffer_get_string(&msg, &len);
1190
			debug3("Received data %llu -> %llu",
1191
			    (unsigned long long)req->offset,
1192
			    (unsigned long long)req->offset + len - 1);
1193 1194
			if (len > req->len)
				fatal("Received more data than asked for "
1195
				    "%u > %u", len, req->len);
1196
			if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
1197
			    atomicio(vwrite, local_fd, data, len) != len) &&
1198 1199 1200 1201 1202
			    !write_error) {
				write_errno = errno;
				write_error = 1;
				max_req = 0;
			}
1203 1204 1205 1206
			else if (!reordered && req->offset <= highwater)
				highwater = req->offset + len;
			else if (!reordered && req->offset > highwater)
				reordered = 1;
1207
			progress_counter += len;
1208
			free(data);
1209

1210 1211
			if (len == req->len) {
				TAILQ_REMOVE(&requests, req, tq);
1212
				free(req);
1213 1214 1215 1216
				num_req--;
			} else {
				/* Resend the request for the missing data */
				debug3("Short data block, re-requesting "
1217
				    "%llu -> %llu (%2d)",
1218
				    (unsigned long long)