1323136Sdes/* $OpenBSD: sftp-client.c,v 1.126 2017/01/03 05:46:51 djm Exp $ */ 276259Sgreen/* 3126274Sdes * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 476259Sgreen * 5126274Sdes * Permission to use, copy, modify, and distribute this software for any 6126274Sdes * purpose with or without fee is hereby granted, provided that the above 7126274Sdes * copyright notice and this permission notice appear in all copies. 876259Sgreen * 9126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1676259Sgreen */ 1776259Sgreen 1876259Sgreen/* XXX: memleaks */ 1976259Sgreen/* XXX: signed vs unsigned */ 2092555Sdes/* XXX: remove all logging, only return status codes */ 2176259Sgreen/* XXX: copy between two remote sites */ 2276259Sgreen 2376259Sgreen#include "includes.h" 2476259Sgreen 25162852Sdes#include <sys/types.h> 26181111Sdes#ifdef HAVE_SYS_STATVFS_H 27181111Sdes#include <sys/statvfs.h> 28181111Sdes#endif 29106121Sdes#include "openbsd-compat/sys-queue.h" 30162852Sdes#ifdef HAVE_SYS_STAT_H 31162852Sdes# include <sys/stat.h> 32162852Sdes#endif 33162852Sdes#ifdef HAVE_SYS_TIME_H 34162852Sdes# include <sys/time.h> 35162852Sdes#endif 36162852Sdes#include <sys/uio.h> 3792555Sdes 38204917Sdes#include <dirent.h> 39162852Sdes#include <errno.h> 40162852Sdes#include <fcntl.h> 41162852Sdes#include <signal.h> 42162852Sdes#include <stdarg.h> 43162852Sdes#include <stdio.h> 44261320Sdes#include <stdlib.h> 45162852Sdes#include <string.h> 46162852Sdes#include <unistd.h> 47162852Sdes 48162852Sdes#include "xmalloc.h" 49294332Sdes#include "ssherr.h" 50294332Sdes#include "sshbuf.h" 5176259Sgreen#include "log.h" 5276259Sgreen#include "atomicio.h" 53113908Sdes#include "progressmeter.h" 54162852Sdes#include "misc.h" 55323129Sdes#include "utf8.h" 5676259Sgreen 5776259Sgreen#include "sftp.h" 5876259Sgreen#include "sftp-common.h" 5976259Sgreen#include "sftp-client.h" 6076259Sgreen 61137015Sdesextern volatile sig_atomic_t interrupted; 62113908Sdesextern int showprogress; 63113908Sdes 64162852Sdes/* Minimum amount of data to read at a time */ 6592555Sdes#define MIN_READ_SIZE 512 6676259Sgreen 67204917Sdes/* Maximum depth to descend in directory trees */ 68204917Sdes#define MAX_DIR_DEPTH 64 69204917Sdes 70323136Sdes/* Directory separator characters */ 71323136Sdes#ifdef HAVE_CYGWIN 72323136Sdes# define SFTP_DIRECTORY_CHARS "/\\" 73323136Sdes#else /* HAVE_CYGWIN */ 74323136Sdes# define SFTP_DIRECTORY_CHARS "/" 75323136Sdes#endif /* HAVE_CYGWIN */ 76323136Sdes 7792555Sdesstruct sftp_conn { 7892555Sdes int fd_in; 7992555Sdes int fd_out; 8092555Sdes u_int transfer_buflen; 8192555Sdes u_int num_requests; 8292555Sdes u_int version; 8392555Sdes u_int msg_id; 84181111Sdes#define SFTP_EXT_POSIX_RENAME 0x00000001 85181111Sdes#define SFTP_EXT_STATVFS 0x00000002 86181111Sdes#define SFTP_EXT_FSTATVFS 0x00000004 87221420Sdes#define SFTP_EXT_HARDLINK 0x00000008 88261320Sdes#define SFTP_EXT_FSYNC 0x00000010 89181111Sdes u_int exts; 90221420Sdes u_int64_t limit_kbps; 91221420Sdes struct bwlimit bwlimit_in, bwlimit_out; 9292555Sdes}; 9376259Sgreen 94294332Sdesstatic u_char * 95294332Sdesget_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, 96221420Sdes const char *errfmt, ...) __attribute__((format(printf, 4, 5))); 97204917Sdes 98221420Sdes/* ARGSUSED */ 99221420Sdesstatic int 100221420Sdessftpio(void *_bwlimit, size_t amount) 101221420Sdes{ 102221420Sdes struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit; 103221420Sdes 104221420Sdes bandwidth_limit(bwlimit, amount); 105221420Sdes return 0; 106221420Sdes} 107221420Sdes 10892555Sdesstatic void 109294332Sdessend_msg(struct sftp_conn *conn, struct sshbuf *m) 11076259Sgreen{ 111113908Sdes u_char mlen[4]; 112162852Sdes struct iovec iov[2]; 11376259Sgreen 114294332Sdes if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH) 115294332Sdes fatal("Outbound message too long %zu", sshbuf_len(m)); 11676259Sgreen 117113908Sdes /* Send length first */ 118294332Sdes put_u32(mlen, sshbuf_len(m)); 119162852Sdes iov[0].iov_base = mlen; 120162852Sdes iov[0].iov_len = sizeof(mlen); 121294332Sdes iov[1].iov_base = (u_char *)sshbuf_ptr(m); 122294332Sdes iov[1].iov_len = sshbuf_len(m); 12376259Sgreen 124221420Sdes if (atomiciov6(writev, conn->fd_out, iov, 2, 125255767Sdes conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) != 126294332Sdes sshbuf_len(m) + sizeof(mlen)) 127113908Sdes fatal("Couldn't send packet: %s", strerror(errno)); 128113908Sdes 129294332Sdes sshbuf_reset(m); 13076259Sgreen} 13176259Sgreen 13292555Sdesstatic void 133294332Sdesget_msg(struct sftp_conn *conn, struct sshbuf *m) 13476259Sgreen{ 135113908Sdes u_int msg_len; 136294332Sdes u_char *p; 137294332Sdes int r; 13876259Sgreen 139294332Sdes if ((r = sshbuf_reserve(m, 4, &p)) != 0) 140294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 141294332Sdes if (atomicio6(read, conn->fd_in, p, 4, 142221420Sdes conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) { 143149749Sdes if (errno == EPIPE) 144149749Sdes fatal("Connection closed"); 145149749Sdes else 146149749Sdes fatal("Couldn't read packet: %s", strerror(errno)); 147149749Sdes } 14876259Sgreen 149294332Sdes if ((r = sshbuf_get_u32(m, &msg_len)) != 0) 150294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 151157016Sdes if (msg_len > SFTP_MAX_MSG_LENGTH) 15299060Sdes fatal("Received message too long %u", msg_len); 15376259Sgreen 154294332Sdes if ((r = sshbuf_reserve(m, msg_len, &p)) != 0) 155294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 156294332Sdes if (atomicio6(read, conn->fd_in, p, msg_len, 157221420Sdes conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) 158221420Sdes != msg_len) { 159149749Sdes if (errno == EPIPE) 160149749Sdes fatal("Connection closed"); 161149749Sdes else 162149749Sdes fatal("Read packet: %s", strerror(errno)); 163149749Sdes } 16476259Sgreen} 16576259Sgreen 16692555Sdesstatic void 167294332Sdessend_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s, 16876259Sgreen u_int len) 16976259Sgreen{ 170294332Sdes struct sshbuf *msg; 171294332Sdes int r; 17276259Sgreen 173294332Sdes if ((msg = sshbuf_new()) == NULL) 174294332Sdes fatal("%s: sshbuf_new failed", __func__); 175294332Sdes if ((r = sshbuf_put_u8(msg, code)) != 0 || 176294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 177294332Sdes (r = sshbuf_put_string(msg, s, len)) != 0) 178294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 179294332Sdes send_msg(conn, msg); 180221420Sdes debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); 181294332Sdes sshbuf_free(msg); 18276259Sgreen} 18376259Sgreen 18492555Sdesstatic void 185221420Sdessend_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code, 186294332Sdes const void *s, u_int len, Attrib *a) 18776259Sgreen{ 188294332Sdes struct sshbuf *msg; 189294332Sdes int r; 19076259Sgreen 191294332Sdes if ((msg = sshbuf_new()) == NULL) 192294332Sdes fatal("%s: sshbuf_new failed", __func__); 193294332Sdes if ((r = sshbuf_put_u8(msg, code)) != 0 || 194294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 195294332Sdes (r = sshbuf_put_string(msg, s, len)) != 0 || 196294332Sdes (r = encode_attrib(msg, a)) != 0) 197294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 198294332Sdes send_msg(conn, msg); 199221420Sdes debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); 200294332Sdes sshbuf_free(msg); 20176259Sgreen} 20276259Sgreen 20392555Sdesstatic u_int 204221420Sdesget_status(struct sftp_conn *conn, u_int expected_id) 20576259Sgreen{ 206294332Sdes struct sshbuf *msg; 207294332Sdes u_char type; 208294332Sdes u_int id, status; 209294332Sdes int r; 21076259Sgreen 211294332Sdes if ((msg = sshbuf_new()) == NULL) 212294332Sdes fatal("%s: sshbuf_new failed", __func__); 213294332Sdes get_msg(conn, msg); 214294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 215294332Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 216294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 21776259Sgreen 21876259Sgreen if (id != expected_id) 21999060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 22076259Sgreen if (type != SSH2_FXP_STATUS) 22199060Sdes fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u", 22276259Sgreen SSH2_FXP_STATUS, type); 22376259Sgreen 224294332Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 225294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 226294332Sdes sshbuf_free(msg); 22776259Sgreen 22899060Sdes debug3("SSH2_FXP_STATUS %u", status); 22976259Sgreen 230221420Sdes return status; 23176259Sgreen} 23276259Sgreen 233294332Sdesstatic u_char * 234294332Sdesget_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, 235221420Sdes const char *errfmt, ...) 23676259Sgreen{ 237294332Sdes struct sshbuf *msg; 238294332Sdes u_int id, status; 239294332Sdes u_char type; 240294332Sdes u_char *handle; 241294332Sdes char errmsg[256]; 242204917Sdes va_list args; 243294332Sdes int r; 24476259Sgreen 245204917Sdes va_start(args, errfmt); 246204917Sdes if (errfmt != NULL) 247204917Sdes vsnprintf(errmsg, sizeof(errmsg), errfmt, args); 248204917Sdes va_end(args); 249204917Sdes 250294332Sdes if ((msg = sshbuf_new()) == NULL) 251294332Sdes fatal("%s: sshbuf_new failed", __func__); 252294332Sdes get_msg(conn, msg); 253294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 254294332Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 255294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 25676259Sgreen 25776259Sgreen if (id != expected_id) 258204917Sdes fatal("%s: ID mismatch (%u != %u)", 259204917Sdes errfmt == NULL ? __func__ : errmsg, id, expected_id); 26076259Sgreen if (type == SSH2_FXP_STATUS) { 261294332Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 262294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 263204917Sdes if (errfmt != NULL) 264204917Sdes error("%s: %s", errmsg, fx2txt(status)); 265294332Sdes sshbuf_free(msg); 26676259Sgreen return(NULL); 26776259Sgreen } else if (type != SSH2_FXP_HANDLE) 268204917Sdes fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u", 269204917Sdes errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type); 27076259Sgreen 271294332Sdes if ((r = sshbuf_get_string(msg, &handle, len)) != 0) 272294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 273294332Sdes sshbuf_free(msg); 27476259Sgreen 275294332Sdes return handle; 27676259Sgreen} 27776259Sgreen 27892555Sdesstatic Attrib * 279221420Sdesget_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet) 28076259Sgreen{ 281294332Sdes struct sshbuf *msg; 282294332Sdes u_int id; 283294332Sdes u_char type; 284294332Sdes int r; 285294332Sdes static Attrib a; 28676259Sgreen 287294332Sdes if ((msg = sshbuf_new()) == NULL) 288294332Sdes fatal("%s: sshbuf_new failed", __func__); 289294332Sdes get_msg(conn, msg); 29076259Sgreen 291294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 292294332Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 293294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 29476259Sgreen 29599060Sdes debug3("Received stat reply T:%u I:%u", type, id); 29676259Sgreen if (id != expected_id) 29799060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 29876259Sgreen if (type == SSH2_FXP_STATUS) { 299294332Sdes u_int status; 30076259Sgreen 301294332Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 302294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 30376259Sgreen if (quiet) 30476259Sgreen debug("Couldn't stat remote file: %s", fx2txt(status)); 30576259Sgreen else 30676259Sgreen error("Couldn't stat remote file: %s", fx2txt(status)); 307294332Sdes sshbuf_free(msg); 30876259Sgreen return(NULL); 30976259Sgreen } else if (type != SSH2_FXP_ATTRS) { 31099060Sdes fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", 31176259Sgreen SSH2_FXP_ATTRS, type); 31276259Sgreen } 313294332Sdes if ((r = decode_attrib(msg, &a)) != 0) { 314294332Sdes error("%s: couldn't decode attrib: %s", __func__, ssh_err(r)); 315294332Sdes sshbuf_free(msg); 316294332Sdes return NULL; 317294332Sdes } 318294332Sdes sshbuf_free(msg); 31976259Sgreen 320294332Sdes return &a; 32176259Sgreen} 32276259Sgreen 323181111Sdesstatic int 324221420Sdesget_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st, 325221420Sdes u_int expected_id, int quiet) 326181111Sdes{ 327294332Sdes struct sshbuf *msg; 328294332Sdes u_char type; 329294332Sdes u_int id; 330294332Sdes u_int64_t flag; 331294332Sdes int r; 332181111Sdes 333294332Sdes if ((msg = sshbuf_new()) == NULL) 334294332Sdes fatal("%s: sshbuf_new failed", __func__); 335294332Sdes get_msg(conn, msg); 336181111Sdes 337294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 338294332Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 339294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 340181111Sdes 341181111Sdes debug3("Received statvfs reply T:%u I:%u", type, id); 342181111Sdes if (id != expected_id) 343181111Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 344181111Sdes if (type == SSH2_FXP_STATUS) { 345294332Sdes u_int status; 346181111Sdes 347294332Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 348294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 349181111Sdes if (quiet) 350181111Sdes debug("Couldn't statvfs: %s", fx2txt(status)); 351181111Sdes else 352181111Sdes error("Couldn't statvfs: %s", fx2txt(status)); 353294332Sdes sshbuf_free(msg); 354181111Sdes return -1; 355181111Sdes } else if (type != SSH2_FXP_EXTENDED_REPLY) { 356181111Sdes fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", 357181111Sdes SSH2_FXP_EXTENDED_REPLY, type); 358181111Sdes } 359181111Sdes 360263712Sdes memset(st, 0, sizeof(*st)); 361294332Sdes if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 || 362294332Sdes (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 || 363294332Sdes (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 || 364294332Sdes (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 || 365294332Sdes (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 || 366294332Sdes (r = sshbuf_get_u64(msg, &st->f_files)) != 0 || 367294332Sdes (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 || 368294332Sdes (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 || 369294332Sdes (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 || 370294332Sdes (r = sshbuf_get_u64(msg, &flag)) != 0 || 371294332Sdes (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0) 372294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 373181111Sdes 374181111Sdes st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0; 375181111Sdes st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0; 376181111Sdes 377294332Sdes sshbuf_free(msg); 378181111Sdes 379181111Sdes return 0; 380181111Sdes} 381181111Sdes 38292555Sdesstruct sftp_conn * 383221420Sdesdo_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, 384221420Sdes u_int64_t limit_kbps) 38576259Sgreen{ 386294332Sdes u_char type; 387294332Sdes struct sshbuf *msg; 38892555Sdes struct sftp_conn *ret; 389294332Sdes int r; 39076259Sgreen 391261320Sdes ret = xcalloc(1, sizeof(*ret)); 392261320Sdes ret->msg_id = 1; 393221420Sdes ret->fd_in = fd_in; 394221420Sdes ret->fd_out = fd_out; 395221420Sdes ret->transfer_buflen = transfer_buflen; 396221420Sdes ret->num_requests = num_requests; 397221420Sdes ret->exts = 0; 398221420Sdes ret->limit_kbps = 0; 399221420Sdes 400294332Sdes if ((msg = sshbuf_new()) == NULL) 401294332Sdes fatal("%s: sshbuf_new failed", __func__); 402294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 || 403294332Sdes (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0) 404294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 405294332Sdes send_msg(ret, msg); 40676259Sgreen 407294332Sdes sshbuf_reset(msg); 40876259Sgreen 409294332Sdes get_msg(ret, msg); 41076259Sgreen 41176259Sgreen /* Expecting a VERSION reply */ 412294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0) 413294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 414294332Sdes if (type != SSH2_FXP_VERSION) { 41599060Sdes error("Invalid packet back from SSH2_FXP_INIT (type %u)", 41676259Sgreen type); 417294332Sdes sshbuf_free(msg); 418294336Sdes free(ret); 41992555Sdes return(NULL); 42076259Sgreen } 421294332Sdes if ((r = sshbuf_get_u32(msg, &ret->version)) != 0) 422294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 42376259Sgreen 424221420Sdes debug2("Remote version: %u", ret->version); 42576259Sgreen 42676259Sgreen /* Check for extensions */ 427294332Sdes while (sshbuf_len(msg) > 0) { 428294332Sdes char *name; 429294332Sdes u_char *value; 430294332Sdes size_t vlen; 431181111Sdes int known = 0; 43276259Sgreen 433294332Sdes if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 || 434294332Sdes (r = sshbuf_get_string(msg, &value, &vlen)) != 0) 435294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 436181111Sdes if (strcmp(name, "posix-rename@openssh.com") == 0 && 437294332Sdes strcmp((char *)value, "1") == 0) { 438221420Sdes ret->exts |= SFTP_EXT_POSIX_RENAME; 439181111Sdes known = 1; 440181111Sdes } else if (strcmp(name, "statvfs@openssh.com") == 0 && 441294332Sdes strcmp((char *)value, "2") == 0) { 442221420Sdes ret->exts |= SFTP_EXT_STATVFS; 443181111Sdes known = 1; 444221420Sdes } else if (strcmp(name, "fstatvfs@openssh.com") == 0 && 445294332Sdes strcmp((char *)value, "2") == 0) { 446221420Sdes ret->exts |= SFTP_EXT_FSTATVFS; 447181111Sdes known = 1; 448221420Sdes } else if (strcmp(name, "hardlink@openssh.com") == 0 && 449294332Sdes strcmp((char *)value, "1") == 0) { 450221420Sdes ret->exts |= SFTP_EXT_HARDLINK; 451221420Sdes known = 1; 452294332Sdes } else if (strcmp(name, "fsync@openssh.com") == 0 && 453294332Sdes strcmp((char *)value, "1") == 0) { 454294332Sdes ret->exts |= SFTP_EXT_FSYNC; 455294332Sdes known = 1; 456181111Sdes } 457181111Sdes if (known) { 458181111Sdes debug2("Server supports extension \"%s\" revision %s", 459181111Sdes name, value); 460181111Sdes } else { 461181111Sdes debug2("Unrecognised server extension \"%s\"", name); 462181111Sdes } 463255767Sdes free(name); 464255767Sdes free(value); 46576259Sgreen } 46676259Sgreen 467294332Sdes sshbuf_free(msg); 46876259Sgreen 46992555Sdes /* Some filexfer v.0 servers don't support large packets */ 470221420Sdes if (ret->version == 0) 471323134Sdes ret->transfer_buflen = MINIMUM(ret->transfer_buflen, 20480); 47292555Sdes 473221420Sdes ret->limit_kbps = limit_kbps; 474221420Sdes if (ret->limit_kbps > 0) { 475221420Sdes bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps, 476221420Sdes ret->transfer_buflen); 477221420Sdes bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps, 478221420Sdes ret->transfer_buflen); 479221420Sdes } 480221420Sdes 481221420Sdes return ret; 48276259Sgreen} 48376259Sgreen 48492555Sdesu_int 48592555Sdessftp_proto_version(struct sftp_conn *conn) 48692555Sdes{ 487221420Sdes return conn->version; 48892555Sdes} 48992555Sdes 49076259Sgreenint 491294332Sdesdo_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) 49276259Sgreen{ 49376259Sgreen u_int id, status; 494294332Sdes struct sshbuf *msg; 495294332Sdes int r; 49676259Sgreen 497294332Sdes if ((msg = sshbuf_new()) == NULL) 498294332Sdes fatal("%s: sshbuf_new failed", __func__); 49976259Sgreen 50092555Sdes id = conn->msg_id++; 501294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 || 502294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 503294332Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 504294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 505294332Sdes send_msg(conn, msg); 50699060Sdes debug3("Sent message SSH2_FXP_CLOSE I:%u", id); 50776259Sgreen 508221420Sdes status = get_status(conn, id); 50976259Sgreen if (status != SSH2_FX_OK) 51076259Sgreen error("Couldn't close file: %s", fx2txt(status)); 51176259Sgreen 512294332Sdes sshbuf_free(msg); 51376259Sgreen 514294332Sdes return status == SSH2_FX_OK ? 0 : -1; 51576259Sgreen} 51676259Sgreen 51776259Sgreen 51892555Sdesstatic int 519294332Sdesdo_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag, 52076259Sgreen SFTP_DIRENT ***dir) 52176259Sgreen{ 522294332Sdes struct sshbuf *msg; 523294332Sdes u_int count, id, i, expected_id, ents = 0; 524294332Sdes size_t handle_len; 525323129Sdes u_char type, *handle; 526261320Sdes int status = SSH2_FX_FAILURE; 527294332Sdes int r; 52876259Sgreen 529261320Sdes if (dir) 530261320Sdes *dir = NULL; 531261320Sdes 53292555Sdes id = conn->msg_id++; 53376259Sgreen 534294332Sdes if ((msg = sshbuf_new()) == NULL) 535294332Sdes fatal("%s: sshbuf_new failed", __func__); 536294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 || 537294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 538294332Sdes (r = sshbuf_put_cstring(msg, path)) != 0) 539294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 540294332Sdes send_msg(conn, msg); 54176259Sgreen 542221420Sdes handle = get_handle(conn, id, &handle_len, 543204917Sdes "remote readdir(\"%s\")", path); 544240075Sdes if (handle == NULL) { 545294332Sdes sshbuf_free(msg); 546221420Sdes return -1; 547240075Sdes } 54876259Sgreen 54976259Sgreen if (dir) { 55076259Sgreen ents = 0; 551257954Sdelphij *dir = xcalloc(1, sizeof(**dir)); 55276259Sgreen (*dir)[0] = NULL; 55376259Sgreen } 55476259Sgreen 555137015Sdes for (; !interrupted;) { 55692555Sdes id = expected_id = conn->msg_id++; 55776259Sgreen 55899060Sdes debug3("Sending SSH2_FXP_READDIR I:%u", id); 55976259Sgreen 560294332Sdes sshbuf_reset(msg); 561294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 || 562294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 563294332Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 564294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 565294332Sdes send_msg(conn, msg); 56676259Sgreen 567294332Sdes sshbuf_reset(msg); 56876259Sgreen 569294332Sdes get_msg(conn, msg); 57076259Sgreen 571294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 572294332Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 573294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 57476259Sgreen 57599060Sdes debug3("Received reply T:%u I:%u", type, id); 57676259Sgreen 57776259Sgreen if (id != expected_id) 57899060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 57976259Sgreen 58076259Sgreen if (type == SSH2_FXP_STATUS) { 581294332Sdes u_int rstatus; 582294332Sdes 583294332Sdes if ((r = sshbuf_get_u32(msg, &rstatus)) != 0) 584294332Sdes fatal("%s: buffer error: %s", 585294332Sdes __func__, ssh_err(r)); 586294332Sdes debug3("Received SSH2_FXP_STATUS %d", rstatus); 587294332Sdes if (rstatus == SSH2_FX_EOF) 58876259Sgreen break; 589294332Sdes error("Couldn't read directory: %s", fx2txt(rstatus)); 590261320Sdes goto out; 59176259Sgreen } else if (type != SSH2_FXP_NAME) 59299060Sdes fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 59376259Sgreen SSH2_FXP_NAME, type); 59476259Sgreen 595294332Sdes if ((r = sshbuf_get_u32(msg, &count)) != 0) 596294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 597323136Sdes if (count > SSHBUF_SIZE_MAX) 598323136Sdes fatal("%s: nonsensical number of entries", __func__); 59976259Sgreen if (count == 0) 60076259Sgreen break; 60176259Sgreen debug3("Received %d SSH2_FXP_NAME responses", count); 60292555Sdes for (i = 0; i < count; i++) { 60376259Sgreen char *filename, *longname; 604294332Sdes Attrib a; 60576259Sgreen 606294332Sdes if ((r = sshbuf_get_cstring(msg, &filename, 607294332Sdes NULL)) != 0 || 608294332Sdes (r = sshbuf_get_cstring(msg, &longname, 609294332Sdes NULL)) != 0) 610294332Sdes fatal("%s: buffer error: %s", 611294332Sdes __func__, ssh_err(r)); 612294332Sdes if ((r = decode_attrib(msg, &a)) != 0) { 613294332Sdes error("%s: couldn't decode attrib: %s", 614294332Sdes __func__, ssh_err(r)); 615294332Sdes free(filename); 616294332Sdes free(longname); 617294332Sdes sshbuf_free(msg); 618294332Sdes return -1; 619294332Sdes } 62076259Sgreen 621261320Sdes if (print_flag) 622323129Sdes mprintf("%s\n", longname); 62376259Sgreen 624204917Sdes /* 625204917Sdes * Directory entries should never contain '/' 626204917Sdes * These can be used to attack recursive ops 627204917Sdes * (e.g. send '../../../../etc/passwd') 628204917Sdes */ 629323136Sdes if (strpbrk(filename, SFTP_DIRECTORY_CHARS) != NULL) { 630204917Sdes error("Server sent suspect path \"%s\" " 631204917Sdes "during readdir of \"%s\"", filename, path); 632261320Sdes } else if (dir) { 633294336Sdes *dir = xreallocarray(*dir, ents + 2, sizeof(**dir)); 634257954Sdelphij (*dir)[ents] = xcalloc(1, sizeof(***dir)); 63576259Sgreen (*dir)[ents]->filename = xstrdup(filename); 63676259Sgreen (*dir)[ents]->longname = xstrdup(longname); 637294332Sdes memcpy(&(*dir)[ents]->a, &a, sizeof(a)); 63876259Sgreen (*dir)[++ents] = NULL; 63976259Sgreen } 640255767Sdes free(filename); 641255767Sdes free(longname); 64276259Sgreen } 64376259Sgreen } 644261320Sdes status = 0; 64576259Sgreen 646261320Sdes out: 647294332Sdes sshbuf_free(msg); 64892555Sdes do_close(conn, handle, handle_len); 649255767Sdes free(handle); 65076259Sgreen 651261320Sdes if (status != 0 && dir != NULL) { 652261320Sdes /* Don't return results on error */ 653137015Sdes free_sftp_dirents(*dir); 654261320Sdes *dir = NULL; 655261320Sdes } else if (interrupted && dir != NULL && *dir != NULL) { 656261320Sdes /* Don't return partial matches on interrupt */ 657261320Sdes free_sftp_dirents(*dir); 658257954Sdelphij *dir = xcalloc(1, sizeof(**dir)); 659137015Sdes **dir = NULL; 660137015Sdes } 661137015Sdes 662261320Sdes return status; 66376259Sgreen} 66476259Sgreen 66576259Sgreenint 666294332Sdesdo_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir) 66776259Sgreen{ 66892555Sdes return(do_lsreaddir(conn, path, 0, dir)); 66976259Sgreen} 67076259Sgreen 67176259Sgreenvoid free_sftp_dirents(SFTP_DIRENT **s) 67276259Sgreen{ 67376259Sgreen int i; 67492555Sdes 675261320Sdes if (s == NULL) 676261320Sdes return; 67792555Sdes for (i = 0; s[i]; i++) { 678255767Sdes free(s[i]->filename); 679255767Sdes free(s[i]->longname); 680255767Sdes free(s[i]); 68176259Sgreen } 682255767Sdes free(s); 68376259Sgreen} 68476259Sgreen 68576259Sgreenint 686294332Sdesdo_rm(struct sftp_conn *conn, const char *path) 68776259Sgreen{ 68876259Sgreen u_int status, id; 68976259Sgreen 69076259Sgreen debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); 69176259Sgreen 69292555Sdes id = conn->msg_id++; 693221420Sdes send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path)); 694221420Sdes status = get_status(conn, id); 69576259Sgreen if (status != SSH2_FX_OK) 69676259Sgreen error("Couldn't delete file: %s", fx2txt(status)); 697294332Sdes return status == SSH2_FX_OK ? 0 : -1; 69876259Sgreen} 69976259Sgreen 70076259Sgreenint 701294332Sdesdo_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) 70276259Sgreen{ 70376259Sgreen u_int status, id; 70476259Sgreen 70592555Sdes id = conn->msg_id++; 706221420Sdes send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path, 70776259Sgreen strlen(path), a); 70876259Sgreen 709221420Sdes status = get_status(conn, id); 710261320Sdes if (status != SSH2_FX_OK && print_flag) 71176259Sgreen error("Couldn't create directory: %s", fx2txt(status)); 71276259Sgreen 713294332Sdes return status == SSH2_FX_OK ? 0 : -1; 71476259Sgreen} 71576259Sgreen 71676259Sgreenint 717294332Sdesdo_rmdir(struct sftp_conn *conn, const char *path) 71876259Sgreen{ 71976259Sgreen u_int status, id; 72076259Sgreen 72192555Sdes id = conn->msg_id++; 722221420Sdes send_string_request(conn, id, SSH2_FXP_RMDIR, path, 72392555Sdes strlen(path)); 72476259Sgreen 725221420Sdes status = get_status(conn, id); 72676259Sgreen if (status != SSH2_FX_OK) 72776259Sgreen error("Couldn't remove directory: %s", fx2txt(status)); 72876259Sgreen 729294332Sdes return status == SSH2_FX_OK ? 0 : -1; 73076259Sgreen} 73176259Sgreen 73276259SgreenAttrib * 733294332Sdesdo_stat(struct sftp_conn *conn, const char *path, int quiet) 73476259Sgreen{ 73576259Sgreen u_int id; 73676259Sgreen 73792555Sdes id = conn->msg_id++; 73892555Sdes 739221420Sdes send_string_request(conn, id, 74098675Sdes conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, 74192555Sdes path, strlen(path)); 74292555Sdes 743221420Sdes return(get_decode_stat(conn, id, quiet)); 74476259Sgreen} 74576259Sgreen 74676259SgreenAttrib * 747294332Sdesdo_lstat(struct sftp_conn *conn, const char *path, int quiet) 74876259Sgreen{ 74976259Sgreen u_int id; 75076259Sgreen 75192555Sdes if (conn->version == 0) { 75292555Sdes if (quiet) 75392555Sdes debug("Server version does not support lstat operation"); 75492555Sdes else 755124208Sdes logit("Server version does not support lstat operation"); 75698675Sdes return(do_stat(conn, path, quiet)); 75792555Sdes } 75892555Sdes 75992555Sdes id = conn->msg_id++; 760221420Sdes send_string_request(conn, id, SSH2_FXP_LSTAT, path, 76192555Sdes strlen(path)); 76292555Sdes 763221420Sdes return(get_decode_stat(conn, id, quiet)); 76476259Sgreen} 76576259Sgreen 766181111Sdes#ifdef notyet 76776259SgreenAttrib * 768294332Sdesdo_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 769294332Sdes int quiet) 77076259Sgreen{ 77176259Sgreen u_int id; 77276259Sgreen 77392555Sdes id = conn->msg_id++; 774221420Sdes send_string_request(conn, id, SSH2_FXP_FSTAT, handle, 77592555Sdes handle_len); 77692555Sdes 777221420Sdes return(get_decode_stat(conn, id, quiet)); 77876259Sgreen} 779181111Sdes#endif 78076259Sgreen 78176259Sgreenint 782294332Sdesdo_setstat(struct sftp_conn *conn, const char *path, Attrib *a) 78376259Sgreen{ 78476259Sgreen u_int status, id; 78576259Sgreen 78692555Sdes id = conn->msg_id++; 787221420Sdes send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path, 78876259Sgreen strlen(path), a); 78976259Sgreen 790221420Sdes status = get_status(conn, id); 79176259Sgreen if (status != SSH2_FX_OK) 79276259Sgreen error("Couldn't setstat on \"%s\": %s", path, 79376259Sgreen fx2txt(status)); 79476259Sgreen 795294332Sdes return status == SSH2_FX_OK ? 0 : -1; 79676259Sgreen} 79776259Sgreen 79876259Sgreenint 799294332Sdesdo_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 80076259Sgreen Attrib *a) 80176259Sgreen{ 80276259Sgreen u_int status, id; 80376259Sgreen 80492555Sdes id = conn->msg_id++; 805221420Sdes send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle, 80676259Sgreen handle_len, a); 80776259Sgreen 808221420Sdes status = get_status(conn, id); 80976259Sgreen if (status != SSH2_FX_OK) 81076259Sgreen error("Couldn't fsetstat: %s", fx2txt(status)); 81176259Sgreen 812294332Sdes return status == SSH2_FX_OK ? 0 : -1; 81376259Sgreen} 81476259Sgreen 81576259Sgreenchar * 816294332Sdesdo_realpath(struct sftp_conn *conn, const char *path) 81776259Sgreen{ 818294332Sdes struct sshbuf *msg; 819294332Sdes u_int expected_id, count, id; 82076259Sgreen char *filename, *longname; 821294332Sdes Attrib a; 822294332Sdes u_char type; 823294332Sdes int r; 82476259Sgreen 82592555Sdes expected_id = id = conn->msg_id++; 826221420Sdes send_string_request(conn, id, SSH2_FXP_REALPATH, path, 82792555Sdes strlen(path)); 82876259Sgreen 829294332Sdes if ((msg = sshbuf_new()) == NULL) 830294332Sdes fatal("%s: sshbuf_new failed", __func__); 83176259Sgreen 832294332Sdes get_msg(conn, msg); 833294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 834294332Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 835294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 83676259Sgreen 83776259Sgreen if (id != expected_id) 83899060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 83976259Sgreen 84076259Sgreen if (type == SSH2_FXP_STATUS) { 841294332Sdes u_int status; 84276259Sgreen 843294332Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 844294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 845261320Sdes error("Couldn't canonicalize: %s", fx2txt(status)); 846294332Sdes sshbuf_free(msg); 847215116Sdes return NULL; 84876259Sgreen } else if (type != SSH2_FXP_NAME) 84999060Sdes fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 85076259Sgreen SSH2_FXP_NAME, type); 85176259Sgreen 852294332Sdes if ((r = sshbuf_get_u32(msg, &count)) != 0) 853294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 85476259Sgreen if (count != 1) 85576259Sgreen fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count); 85676259Sgreen 857294332Sdes if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || 858294332Sdes (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || 859294332Sdes (r = decode_attrib(msg, &a)) != 0) 860294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 86176259Sgreen 862240075Sdes debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename, 863294332Sdes (unsigned long)a.size); 86476259Sgreen 865255767Sdes free(longname); 86676259Sgreen 867294332Sdes sshbuf_free(msg); 86876259Sgreen 86976259Sgreen return(filename); 87076259Sgreen} 87176259Sgreen 87276259Sgreenint 873294332Sdesdo_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, 874261320Sdes int force_legacy) 87576259Sgreen{ 876294332Sdes struct sshbuf *msg; 87776259Sgreen u_int status, id; 878294332Sdes int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy; 87976259Sgreen 880294332Sdes if ((msg = sshbuf_new()) == NULL) 881294332Sdes fatal("%s: sshbuf_new failed", __func__); 88276259Sgreen 88376259Sgreen /* Send rename request */ 88492555Sdes id = conn->msg_id++; 885261320Sdes if (use_ext) { 886294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 887294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 888294332Sdes (r = sshbuf_put_cstring(msg, 889294332Sdes "posix-rename@openssh.com")) != 0) 890294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 891181111Sdes } else { 892294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 || 893294332Sdes (r = sshbuf_put_u32(msg, id)) != 0) 894294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 895181111Sdes } 896294332Sdes if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 || 897294332Sdes (r = sshbuf_put_cstring(msg, newpath)) != 0) 898294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 899294332Sdes send_msg(conn, msg); 900181111Sdes debug3("Sent message %s \"%s\" -> \"%s\"", 901294332Sdes use_ext ? "posix-rename@openssh.com" : 902294332Sdes "SSH2_FXP_RENAME", oldpath, newpath); 903294332Sdes sshbuf_free(msg); 90476259Sgreen 905221420Sdes status = get_status(conn, id); 90676259Sgreen if (status != SSH2_FX_OK) 90792555Sdes error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, 90892555Sdes newpath, fx2txt(status)); 90976259Sgreen 910294332Sdes return status == SSH2_FX_OK ? 0 : -1; 91176259Sgreen} 91276259Sgreen 91376259Sgreenint 914294332Sdesdo_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) 915221420Sdes{ 916294332Sdes struct sshbuf *msg; 917221420Sdes u_int status, id; 918294332Sdes int r; 919221420Sdes 920221420Sdes if ((conn->exts & SFTP_EXT_HARDLINK) == 0) { 921221420Sdes error("Server does not support hardlink@openssh.com extension"); 922221420Sdes return -1; 923221420Sdes } 924221420Sdes 925294332Sdes if ((msg = sshbuf_new()) == NULL) 926294332Sdes fatal("%s: sshbuf_new failed", __func__); 927240075Sdes 928240075Sdes /* Send link request */ 929240075Sdes id = conn->msg_id++; 930294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 931294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 932294332Sdes (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 || 933294332Sdes (r = sshbuf_put_cstring(msg, oldpath)) != 0 || 934294332Sdes (r = sshbuf_put_cstring(msg, newpath)) != 0) 935294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 936294332Sdes send_msg(conn, msg); 937221420Sdes debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"", 938221420Sdes oldpath, newpath); 939294332Sdes sshbuf_free(msg); 940221420Sdes 941221420Sdes status = get_status(conn, id); 942221420Sdes if (status != SSH2_FX_OK) 943221420Sdes error("Couldn't link file \"%s\" to \"%s\": %s", oldpath, 944221420Sdes newpath, fx2txt(status)); 945221420Sdes 946294332Sdes return status == SSH2_FX_OK ? 0 : -1; 947221420Sdes} 948221420Sdes 949221420Sdesint 950294332Sdesdo_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) 95176259Sgreen{ 952294332Sdes struct sshbuf *msg; 95376259Sgreen u_int status, id; 954294332Sdes int r; 95576259Sgreen 95692555Sdes if (conn->version < 3) { 95792555Sdes error("This server does not support the symlink operation"); 95892555Sdes return(SSH2_FX_OP_UNSUPPORTED); 95992555Sdes } 96092555Sdes 961294332Sdes if ((msg = sshbuf_new()) == NULL) 962294332Sdes fatal("%s: sshbuf_new failed", __func__); 96376259Sgreen 964137015Sdes /* Send symlink request */ 96592555Sdes id = conn->msg_id++; 966294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 || 967294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 968294332Sdes (r = sshbuf_put_cstring(msg, oldpath)) != 0 || 969294332Sdes (r = sshbuf_put_cstring(msg, newpath)) != 0) 970294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 971294332Sdes send_msg(conn, msg); 97276259Sgreen debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, 97376259Sgreen newpath); 974294332Sdes sshbuf_free(msg); 97576259Sgreen 976221420Sdes status = get_status(conn, id); 97776259Sgreen if (status != SSH2_FX_OK) 978113908Sdes error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath, 97992555Sdes newpath, fx2txt(status)); 98076259Sgreen 981294332Sdes return status == SSH2_FX_OK ? 0 : -1; 98276259Sgreen} 98376259Sgreen 984261320Sdesint 985294332Sdesdo_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len) 986261320Sdes{ 987294332Sdes struct sshbuf *msg; 988261320Sdes u_int status, id; 989294332Sdes int r; 990261320Sdes 991261320Sdes /* Silently return if the extension is not supported */ 992261320Sdes if ((conn->exts & SFTP_EXT_FSYNC) == 0) 993261320Sdes return -1; 994261320Sdes 995261320Sdes /* Send fsync request */ 996294332Sdes if ((msg = sshbuf_new()) == NULL) 997294332Sdes fatal("%s: sshbuf_new failed", __func__); 998261320Sdes id = conn->msg_id++; 999294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 1000294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1001294332Sdes (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 || 1002294332Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 1003294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1004294332Sdes send_msg(conn, msg); 1005261320Sdes debug3("Sent message fsync@openssh.com I:%u", id); 1006294332Sdes sshbuf_free(msg); 1007261320Sdes 1008261320Sdes status = get_status(conn, id); 1009261320Sdes if (status != SSH2_FX_OK) 1010261320Sdes error("Couldn't sync file: %s", fx2txt(status)); 1011261320Sdes 1012261320Sdes return status; 1013261320Sdes} 1014261320Sdes 1015181111Sdes#ifdef notyet 101676259Sgreenchar * 1017294332Sdesdo_readlink(struct sftp_conn *conn, const char *path) 101876259Sgreen{ 1019294332Sdes struct sshbuf *msg; 1020294332Sdes u_int expected_id, count, id; 102176259Sgreen char *filename, *longname; 1022294332Sdes Attrib a; 1023294332Sdes u_char type; 1024294332Sdes int r; 102576259Sgreen 102692555Sdes expected_id = id = conn->msg_id++; 1027221420Sdes send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path)); 102876259Sgreen 1029294332Sdes if ((msg = sshbuf_new()) == NULL) 1030294332Sdes fatal("%s: sshbuf_new failed", __func__); 103176259Sgreen 1032294332Sdes get_msg(conn, msg); 1033294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1034294332Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 1035294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 103676259Sgreen 103776259Sgreen if (id != expected_id) 103899060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 103976259Sgreen 104076259Sgreen if (type == SSH2_FXP_STATUS) { 1041294332Sdes u_int status; 104276259Sgreen 1043294332Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 1044294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 104576259Sgreen error("Couldn't readlink: %s", fx2txt(status)); 1046294332Sdes sshbuf_free(msg); 104776259Sgreen return(NULL); 104876259Sgreen } else if (type != SSH2_FXP_NAME) 104999060Sdes fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 105076259Sgreen SSH2_FXP_NAME, type); 105176259Sgreen 1052294332Sdes if ((r = sshbuf_get_u32(msg, &count)) != 0) 1053294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 105476259Sgreen if (count != 1) 105576259Sgreen fatal("Got multiple names (%d) from SSH_FXP_READLINK", count); 105676259Sgreen 1057294332Sdes if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || 1058294332Sdes (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || 1059294332Sdes (r = decode_attrib(msg, &a)) != 0) 1060294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 106176259Sgreen 106276259Sgreen debug3("SSH_FXP_READLINK %s -> %s", path, filename); 106376259Sgreen 1064255767Sdes free(longname); 106576259Sgreen 1066294332Sdes sshbuf_free(msg); 106776259Sgreen 1068294332Sdes return filename; 106976259Sgreen} 1070181111Sdes#endif 107176259Sgreen 1072181111Sdesint 1073181111Sdesdo_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, 1074181111Sdes int quiet) 1075181111Sdes{ 1076294332Sdes struct sshbuf *msg; 1077181111Sdes u_int id; 1078294332Sdes int r; 1079181111Sdes 1080181111Sdes if ((conn->exts & SFTP_EXT_STATVFS) == 0) { 1081181111Sdes error("Server does not support statvfs@openssh.com extension"); 1082181111Sdes return -1; 1083181111Sdes } 1084181111Sdes 1085181111Sdes id = conn->msg_id++; 1086181111Sdes 1087294332Sdes if ((msg = sshbuf_new()) == NULL) 1088294332Sdes fatal("%s: sshbuf_new failed", __func__); 1089294332Sdes sshbuf_reset(msg); 1090294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 1091294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1092294332Sdes (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 || 1093294332Sdes (r = sshbuf_put_cstring(msg, path)) != 0) 1094294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1095294332Sdes send_msg(conn, msg); 1096294332Sdes sshbuf_free(msg); 1097181111Sdes 1098221420Sdes return get_decode_statvfs(conn, st, id, quiet); 1099181111Sdes} 1100181111Sdes 1101181111Sdes#ifdef notyet 1102181111Sdesint 1103294332Sdesdo_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 1104181111Sdes struct sftp_statvfs *st, int quiet) 1105181111Sdes{ 1106294332Sdes struct sshbuf *msg; 1107181111Sdes u_int id; 1108181111Sdes 1109181111Sdes if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) { 1110181111Sdes error("Server does not support fstatvfs@openssh.com extension"); 1111181111Sdes return -1; 1112181111Sdes } 1113181111Sdes 1114181111Sdes id = conn->msg_id++; 1115181111Sdes 1116294332Sdes if ((msg = sshbuf_new()) == NULL) 1117294332Sdes fatal("%s: sshbuf_new failed", __func__); 1118294332Sdes sshbuf_reset(msg); 1119294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 1120294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1121294332Sdes (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 || 1122294332Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 1123294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1124294332Sdes send_msg(conn, msg); 1125294332Sdes sshbuf_free(msg); 1126181111Sdes 1127221420Sdes return get_decode_statvfs(conn, st, id, quiet); 1128181111Sdes} 1129181111Sdes#endif 1130181111Sdes 113192555Sdesstatic void 1132221420Sdessend_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, 1133294332Sdes u_int len, const u_char *handle, u_int handle_len) 113492555Sdes{ 1135294332Sdes struct sshbuf *msg; 1136294332Sdes int r; 113798675Sdes 1138294332Sdes if ((msg = sshbuf_new()) == NULL) 1139294332Sdes fatal("%s: sshbuf_new failed", __func__); 1140294332Sdes sshbuf_reset(msg); 1141294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 || 1142294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1143294332Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0 || 1144294332Sdes (r = sshbuf_put_u64(msg, offset)) != 0 || 1145294332Sdes (r = sshbuf_put_u32(msg, len)) != 0) 1146294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1147294332Sdes send_msg(conn, msg); 1148294332Sdes sshbuf_free(msg); 114998675Sdes} 115092555Sdes 115176259Sgreenint 1152294332Sdesdo_download(struct sftp_conn *conn, const char *remote_path, 1153294332Sdes const char *local_path, Attrib *a, int preserve_flag, int resume_flag, 1154294332Sdes int fsync_flag) 115576259Sgreen{ 1156204917Sdes Attrib junk; 1157294332Sdes struct sshbuf *msg; 1158294332Sdes u_char *handle; 1159294332Sdes int local_fd = -1, write_error; 1160294332Sdes int read_error, write_errno, reordered = 0, r; 1161255767Sdes u_int64_t offset = 0, size, highwater; 1162294332Sdes u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK; 1163113908Sdes off_t progress_counter; 1164294332Sdes size_t handle_len; 1165255767Sdes struct stat st; 116692555Sdes struct request { 116792555Sdes u_int id; 1168294332Sdes size_t len; 116992555Sdes u_int64_t offset; 117098675Sdes TAILQ_ENTRY(request) tq; 117192555Sdes }; 117292555Sdes TAILQ_HEAD(reqhead, request) requests; 117392555Sdes struct request *req; 1174294332Sdes u_char type; 117576259Sgreen 117692555Sdes TAILQ_INIT(&requests); 117792555Sdes 1178204917Sdes if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL) 1179204917Sdes return -1; 118076259Sgreen 1181181111Sdes /* Do not preserve set[ug]id here, as we do not preserve ownership */ 118276259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 1183113908Sdes mode = a->perm & 0777; 118476259Sgreen else 118576259Sgreen mode = 0666; 118676259Sgreen 118776259Sgreen if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 1188113908Sdes (!S_ISREG(a->perm))) { 1189113908Sdes error("Cannot download non-regular file: %s", remote_path); 119076259Sgreen return(-1); 119176259Sgreen } 119276259Sgreen 119392555Sdes if (a->flags & SSH2_FILEXFER_ATTR_SIZE) 119492555Sdes size = a->size; 119592555Sdes else 119692555Sdes size = 0; 119776259Sgreen 119892555Sdes buflen = conn->transfer_buflen; 1199294332Sdes if ((msg = sshbuf_new()) == NULL) 1200294332Sdes fatal("%s: sshbuf_new failed", __func__); 120176259Sgreen 1202294332Sdes attrib_clear(&junk); /* Send empty attributes */ 1203294332Sdes 120476259Sgreen /* Send open request */ 120592555Sdes id = conn->msg_id++; 1206294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || 1207294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1208294332Sdes (r = sshbuf_put_cstring(msg, remote_path)) != 0 || 1209294332Sdes (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 || 1210294332Sdes (r = encode_attrib(msg, &junk)) != 0) 1211294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1212294332Sdes send_msg(conn, msg); 121399060Sdes debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 121476259Sgreen 1215221420Sdes handle = get_handle(conn, id, &handle_len, 1216204917Sdes "remote open(\"%s\")", remote_path); 121776259Sgreen if (handle == NULL) { 1218294332Sdes sshbuf_free(msg); 121976259Sgreen return(-1); 122076259Sgreen } 122176259Sgreen 1222261320Sdes local_fd = open(local_path, 1223261320Sdes O_WRONLY | O_CREAT | (resume_flag ? 0 : O_TRUNC), mode | S_IWUSR); 122492555Sdes if (local_fd == -1) { 122592555Sdes error("Couldn't open local file \"%s\" for writing: %s", 122692555Sdes local_path, strerror(errno)); 1227255767Sdes goto fail; 122892555Sdes } 1229255767Sdes offset = highwater = 0; 1230261320Sdes if (resume_flag) { 1231255767Sdes if (fstat(local_fd, &st) == -1) { 1232255767Sdes error("Unable to stat local file \"%s\": %s", 1233255767Sdes local_path, strerror(errno)); 1234255767Sdes goto fail; 1235255767Sdes } 1236261320Sdes if (st.st_size < 0) { 1237261320Sdes error("\"%s\" has negative size", local_path); 1238261320Sdes goto fail; 1239261320Sdes } 1240261320Sdes if ((u_int64_t)st.st_size > size) { 1241255767Sdes error("Unable to resume download of \"%s\": " 1242255767Sdes "local file is larger than remote", local_path); 1243255767Sdes fail: 1244255767Sdes do_close(conn, handle, handle_len); 1245294332Sdes sshbuf_free(msg); 1246255767Sdes free(handle); 1247261320Sdes if (local_fd != -1) 1248261320Sdes close(local_fd); 1249255767Sdes return -1; 1250255767Sdes } 1251255767Sdes offset = highwater = st.st_size; 1252255767Sdes } 125392555Sdes 125476259Sgreen /* Read from remote and write to local */ 1255255767Sdes write_error = read_error = write_errno = num_req = 0; 125692555Sdes max_req = 1; 1257255767Sdes progress_counter = offset; 1258113908Sdes 1259128456Sdes if (showprogress && size != 0) 1260128456Sdes start_progress_meter(remote_path, size, &progress_counter); 1261113908Sdes 126292555Sdes while (num_req > 0 || max_req > 0) { 1263294332Sdes u_char *data; 1264294332Sdes size_t len; 126576259Sgreen 1266137015Sdes /* 1267137015Sdes * Simulate EOF on interrupt: stop sending new requests and 1268137015Sdes * allow outstanding requests to drain gracefully 1269137015Sdes */ 1270137015Sdes if (interrupted) { 1271137015Sdes if (num_req == 0) /* If we haven't started yet... */ 1272137015Sdes break; 1273137015Sdes max_req = 0; 1274137015Sdes } 1275137015Sdes 127692555Sdes /* Send some more requests */ 127792555Sdes while (num_req < max_req) { 127898675Sdes debug3("Request range %llu -> %llu (%d/%d)", 127998675Sdes (unsigned long long)offset, 128098675Sdes (unsigned long long)offset + buflen - 1, 128198675Sdes num_req, max_req); 1282257954Sdelphij req = xcalloc(1, sizeof(*req)); 128392555Sdes req->id = conn->msg_id++; 128492555Sdes req->len = buflen; 128592555Sdes req->offset = offset; 128692555Sdes offset += buflen; 128792555Sdes num_req++; 128892555Sdes TAILQ_INSERT_TAIL(&requests, req, tq); 1289221420Sdes send_read_request(conn, req->id, req->offset, 129092555Sdes req->len, handle, handle_len); 129192555Sdes } 129276259Sgreen 1293294332Sdes sshbuf_reset(msg); 1294294332Sdes get_msg(conn, msg); 1295294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1296294332Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 1297294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 129899060Sdes debug3("Received reply T:%u I:%u R:%d", type, id, max_req); 129976259Sgreen 130092555Sdes /* Find the request in our queue */ 1301147001Sdes for (req = TAILQ_FIRST(&requests); 130292555Sdes req != NULL && req->id != id; 130392555Sdes req = TAILQ_NEXT(req, tq)) 130492555Sdes ; 130592555Sdes if (req == NULL) 130692555Sdes fatal("Unexpected reply %u", id); 130776259Sgreen 130892555Sdes switch (type) { 130992555Sdes case SSH2_FXP_STATUS: 1310294332Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 1311294332Sdes fatal("%s: buffer error: %s", 1312294332Sdes __func__, ssh_err(r)); 131392555Sdes if (status != SSH2_FX_EOF) 131492555Sdes read_error = 1; 131592555Sdes max_req = 0; 131692555Sdes TAILQ_REMOVE(&requests, req, tq); 1317255767Sdes free(req); 131892555Sdes num_req--; 131992555Sdes break; 132092555Sdes case SSH2_FXP_DATA: 1321294332Sdes if ((r = sshbuf_get_string(msg, &data, &len)) != 0) 1322294332Sdes fatal("%s: buffer error: %s", 1323294332Sdes __func__, ssh_err(r)); 132498675Sdes debug3("Received data %llu -> %llu", 132598675Sdes (unsigned long long)req->offset, 132698675Sdes (unsigned long long)req->offset + len - 1); 132792555Sdes if (len > req->len) 132892555Sdes fatal("Received more data than asked for " 1329294332Sdes "%zu > %zu", len, req->len); 133092555Sdes if ((lseek(local_fd, req->offset, SEEK_SET) == -1 || 1331124208Sdes atomicio(vwrite, local_fd, data, len) != len) && 133292555Sdes !write_error) { 133392555Sdes write_errno = errno; 133492555Sdes write_error = 1; 133592555Sdes max_req = 0; 133692555Sdes } 1337255767Sdes else if (!reordered && req->offset <= highwater) 1338255767Sdes highwater = req->offset + len; 1339255767Sdes else if (!reordered && req->offset > highwater) 1340255767Sdes reordered = 1; 1341113908Sdes progress_counter += len; 1342255767Sdes free(data); 134376259Sgreen 134492555Sdes if (len == req->len) { 134592555Sdes TAILQ_REMOVE(&requests, req, tq); 1346255767Sdes free(req); 134792555Sdes num_req--; 134892555Sdes } else { 134992555Sdes /* Resend the request for the missing data */ 135092555Sdes debug3("Short data block, re-requesting " 135198675Sdes "%llu -> %llu (%2d)", 135298675Sdes (unsigned long long)req->offset + len, 135398675Sdes (unsigned long long)req->offset + 135498675Sdes req->len - 1, num_req); 135592555Sdes req->id = conn->msg_id++; 135692555Sdes req->len -= len; 135792555Sdes req->offset += len; 1358221420Sdes send_read_request(conn, req->id, 135992555Sdes req->offset, req->len, handle, handle_len); 136092555Sdes /* Reduce the request size */ 136192555Sdes if (len < buflen) 1362323134Sdes buflen = MAXIMUM(MIN_READ_SIZE, len); 136376259Sgreen } 136492555Sdes if (max_req > 0) { /* max_req = 0 iff EOF received */ 136592555Sdes if (size > 0 && offset > size) { 136692555Sdes /* Only one request at a time 136792555Sdes * after the expected EOF */ 136892555Sdes debug3("Finish at %llu (%2d)", 136998675Sdes (unsigned long long)offset, 137098675Sdes num_req); 137192555Sdes max_req = 1; 1372137015Sdes } else if (max_req <= conn->num_requests) { 137392555Sdes ++max_req; 137492555Sdes } 137592555Sdes } 137692555Sdes break; 137792555Sdes default: 137899060Sdes fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", 137976259Sgreen SSH2_FXP_DATA, type); 138076259Sgreen } 138192555Sdes } 138276259Sgreen 1383113908Sdes if (showprogress && size) 1384113908Sdes stop_progress_meter(); 1385113908Sdes 138692555Sdes /* Sanity check */ 138792555Sdes if (TAILQ_FIRST(&requests) != NULL) 138892555Sdes fatal("Transfer complete, but requests still in queue"); 1389255767Sdes /* Truncate at highest contiguous point to avoid holes on interrupt */ 1390255767Sdes if (read_error || write_error || interrupted) { 1391261320Sdes if (reordered && resume_flag) { 1392255767Sdes error("Unable to resume download of \"%s\": " 1393255767Sdes "server reordered requests", local_path); 1394255767Sdes } 1395255767Sdes debug("truncating at %llu", (unsigned long long)highwater); 1396294336Sdes if (ftruncate(local_fd, highwater) == -1) 1397294336Sdes error("ftruncate \"%s\": %s", local_path, 1398294336Sdes strerror(errno)); 1399255767Sdes } 140092555Sdes if (read_error) { 140198675Sdes error("Couldn't read from remote file \"%s\" : %s", 140292555Sdes remote_path, fx2txt(status)); 1403261320Sdes status = -1; 140492555Sdes do_close(conn, handle, handle_len); 140592555Sdes } else if (write_error) { 140692555Sdes error("Couldn't write to \"%s\": %s", local_path, 140792555Sdes strerror(write_errno)); 1408294332Sdes status = SSH2_FX_FAILURE; 140992555Sdes do_close(conn, handle, handle_len); 141092555Sdes } else { 1411294332Sdes if (do_close(conn, handle, handle_len) != 0 || interrupted) 1412294332Sdes status = SSH2_FX_FAILURE; 1413294332Sdes else 1414294332Sdes status = SSH2_FX_OK; 141592555Sdes /* Override umask and utimes if asked */ 141698937Sdes#ifdef HAVE_FCHMOD 1417261320Sdes if (preserve_flag && fchmod(local_fd, mode) == -1) 1418126274Sdes#else 1419261320Sdes if (preserve_flag && chmod(local_path, mode) == -1) 142098937Sdes#endif /* HAVE_FCHMOD */ 142192555Sdes error("Couldn't set mode on \"%s\": %s", local_path, 1422113908Sdes strerror(errno)); 1423261320Sdes if (preserve_flag && 1424261320Sdes (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { 142592555Sdes struct timeval tv[2]; 142692555Sdes tv[0].tv_sec = a->atime; 142792555Sdes tv[1].tv_sec = a->mtime; 142892555Sdes tv[0].tv_usec = tv[1].tv_usec = 0; 142992555Sdes if (utimes(local_path, tv) == -1) 143092555Sdes error("Can't set times on \"%s\": %s", 1431113908Sdes local_path, strerror(errno)); 143276259Sgreen } 1433261320Sdes if (fsync_flag) { 1434261320Sdes debug("syncing \"%s\"", local_path); 1435261320Sdes if (fsync(local_fd) == -1) 1436261320Sdes error("Couldn't sync file \"%s\": %s", 1437261320Sdes local_path, strerror(errno)); 1438261320Sdes } 143976259Sgreen } 144076259Sgreen close(local_fd); 1441294332Sdes sshbuf_free(msg); 1442255767Sdes free(handle); 144392555Sdes 144492555Sdes return(status); 144576259Sgreen} 144676259Sgreen 1447204917Sdesstatic int 1448294332Sdesdownload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, 1449294332Sdes int depth, Attrib *dirattrib, int preserve_flag, int print_flag, 1450294332Sdes int resume_flag, int fsync_flag) 1451204917Sdes{ 1452204917Sdes int i, ret = 0; 1453204917Sdes SFTP_DIRENT **dir_entries; 1454204917Sdes char *filename, *new_src, *new_dst; 1455204917Sdes mode_t mode = 0777; 1456204917Sdes 1457204917Sdes if (depth >= MAX_DIR_DEPTH) { 1458204917Sdes error("Maximum directory depth exceeded: %d levels", depth); 1459204917Sdes return -1; 1460204917Sdes } 1461204917Sdes 1462204917Sdes if (dirattrib == NULL && 1463204917Sdes (dirattrib = do_stat(conn, src, 1)) == NULL) { 1464204917Sdes error("Unable to stat remote directory \"%s\"", src); 1465204917Sdes return -1; 1466204917Sdes } 1467204917Sdes if (!S_ISDIR(dirattrib->perm)) { 1468204917Sdes error("\"%s\" is not a directory", src); 1469204917Sdes return -1; 1470204917Sdes } 1471261320Sdes if (print_flag) 1472323129Sdes mprintf("Retrieving %s\n", src); 1473204917Sdes 1474204917Sdes if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 1475204917Sdes mode = dirattrib->perm & 01777; 1476204917Sdes else { 1477204917Sdes debug("Server did not send permissions for " 1478204917Sdes "directory \"%s\"", dst); 1479204917Sdes } 1480204917Sdes 1481204917Sdes if (mkdir(dst, mode) == -1 && errno != EEXIST) { 1482204917Sdes error("mkdir %s: %s", dst, strerror(errno)); 1483204917Sdes return -1; 1484204917Sdes } 1485204917Sdes 1486204917Sdes if (do_readdir(conn, src, &dir_entries) == -1) { 1487204917Sdes error("%s: Failed to get directory contents", src); 1488204917Sdes return -1; 1489204917Sdes } 1490204917Sdes 1491204917Sdes for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { 1492204917Sdes filename = dir_entries[i]->filename; 1493204917Sdes 1494204917Sdes new_dst = path_append(dst, filename); 1495204917Sdes new_src = path_append(src, filename); 1496204917Sdes 1497204917Sdes if (S_ISDIR(dir_entries[i]->a.perm)) { 1498204917Sdes if (strcmp(filename, ".") == 0 || 1499204917Sdes strcmp(filename, "..") == 0) 1500204917Sdes continue; 1501204917Sdes if (download_dir_internal(conn, new_src, new_dst, 1502261320Sdes depth + 1, &(dir_entries[i]->a), preserve_flag, 1503261320Sdes print_flag, resume_flag, fsync_flag) == -1) 1504204917Sdes ret = -1; 1505204917Sdes } else if (S_ISREG(dir_entries[i]->a.perm) ) { 1506204917Sdes if (do_download(conn, new_src, new_dst, 1507261320Sdes &(dir_entries[i]->a), preserve_flag, 1508261320Sdes resume_flag, fsync_flag) == -1) { 1509204917Sdes error("Download of file %s to %s failed", 1510204917Sdes new_src, new_dst); 1511204917Sdes ret = -1; 1512204917Sdes } 1513204917Sdes } else 1514204917Sdes logit("%s: not a regular file\n", new_src); 1515204917Sdes 1516255767Sdes free(new_dst); 1517255767Sdes free(new_src); 1518204917Sdes } 1519204917Sdes 1520261320Sdes if (preserve_flag) { 1521204917Sdes if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 1522204917Sdes struct timeval tv[2]; 1523204917Sdes tv[0].tv_sec = dirattrib->atime; 1524204917Sdes tv[1].tv_sec = dirattrib->mtime; 1525204917Sdes tv[0].tv_usec = tv[1].tv_usec = 0; 1526204917Sdes if (utimes(dst, tv) == -1) 1527204917Sdes error("Can't set times on \"%s\": %s", 1528204917Sdes dst, strerror(errno)); 1529204917Sdes } else 1530204917Sdes debug("Server did not send times for directory " 1531204917Sdes "\"%s\"", dst); 1532204917Sdes } 1533204917Sdes 1534204917Sdes free_sftp_dirents(dir_entries); 1535204917Sdes 1536204917Sdes return ret; 1537204917Sdes} 1538204917Sdes 153976259Sgreenint 1540294332Sdesdownload_dir(struct sftp_conn *conn, const char *src, const char *dst, 1541294332Sdes Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag, 1542294332Sdes int fsync_flag) 1543204917Sdes{ 1544204917Sdes char *src_canon; 1545204917Sdes int ret; 1546204917Sdes 1547204917Sdes if ((src_canon = do_realpath(conn, src)) == NULL) { 1548261320Sdes error("Unable to canonicalize path \"%s\"", src); 1549204917Sdes return -1; 1550204917Sdes } 1551204917Sdes 1552261320Sdes ret = download_dir_internal(conn, src_canon, dst, 0, 1553261320Sdes dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag); 1554255767Sdes free(src_canon); 1555204917Sdes return ret; 1556204917Sdes} 1557204917Sdes 1558204917Sdesint 1559294332Sdesdo_upload(struct sftp_conn *conn, const char *local_path, 1560294332Sdes const char *remote_path, int preserve_flag, int resume, int fsync_flag) 156176259Sgreen{ 1562294332Sdes int r, local_fd; 1563294332Sdes u_int status = SSH2_FX_OK; 1564294332Sdes u_int id; 1565294332Sdes u_char type; 1566255767Sdes off_t offset, progress_counter; 1567294332Sdes u_char *handle, *data; 1568294332Sdes struct sshbuf *msg; 156976259Sgreen struct stat sb; 1570294328Sdes Attrib a, *c = NULL; 157192555Sdes u_int32_t startid; 157292555Sdes u_int32_t ackid; 157392555Sdes struct outstanding_ack { 157492555Sdes u_int id; 157592555Sdes u_int len; 1576181111Sdes off_t offset; 157798675Sdes TAILQ_ENTRY(outstanding_ack) tq; 157892555Sdes }; 157992555Sdes TAILQ_HEAD(ackhead, outstanding_ack) acks; 1580137015Sdes struct outstanding_ack *ack = NULL; 1581294332Sdes size_t handle_len; 158276259Sgreen 158392555Sdes TAILQ_INIT(&acks); 158492555Sdes 158576259Sgreen if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) { 158676259Sgreen error("Couldn't open local file \"%s\" for reading: %s", 158776259Sgreen local_path, strerror(errno)); 158876259Sgreen return(-1); 158976259Sgreen } 159076259Sgreen if (fstat(local_fd, &sb) == -1) { 159176259Sgreen error("Couldn't fstat local file \"%s\": %s", 159276259Sgreen local_path, strerror(errno)); 159376259Sgreen close(local_fd); 159476259Sgreen return(-1); 159576259Sgreen } 1596113908Sdes if (!S_ISREG(sb.st_mode)) { 1597113908Sdes error("%s is not a regular file", local_path); 1598113908Sdes close(local_fd); 1599113908Sdes return(-1); 1600113908Sdes } 160176259Sgreen stat_to_attrib(&sb, &a); 160276259Sgreen 160376259Sgreen a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 160476259Sgreen a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 160576259Sgreen a.perm &= 0777; 1606261320Sdes if (!preserve_flag) 160776259Sgreen a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 160876259Sgreen 1609294328Sdes if (resume) { 1610294328Sdes /* Get remote file size if it exists */ 1611294328Sdes if ((c = do_stat(conn, remote_path, 0)) == NULL) { 1612323129Sdes close(local_fd); 1613294328Sdes return -1; 1614294328Sdes } 1615294328Sdes 1616294328Sdes if ((off_t)c->size >= sb.st_size) { 1617294328Sdes error("destination file bigger or same size as " 1618294328Sdes "source file"); 1619294328Sdes close(local_fd); 1620294328Sdes return -1; 1621294328Sdes } 1622294328Sdes 1623294328Sdes if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) { 1624294328Sdes close(local_fd); 1625294328Sdes return -1; 1626294328Sdes } 1627294328Sdes } 1628294328Sdes 1629294332Sdes if ((msg = sshbuf_new()) == NULL) 1630294332Sdes fatal("%s: sshbuf_new failed", __func__); 163176259Sgreen 163276259Sgreen /* Send open request */ 163392555Sdes id = conn->msg_id++; 1634294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || 1635294332Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1636294332Sdes (r = sshbuf_put_cstring(msg, remote_path)) != 0 || 1637294332Sdes (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| 1638294332Sdes (resume ? SSH2_FXF_APPEND : SSH2_FXF_TRUNC))) != 0 || 1639294332Sdes (r = encode_attrib(msg, &a)) != 0) 1640294332Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1641294332Sdes send_msg(conn, msg); 164299060Sdes debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 164376259Sgreen 1644294332Sdes sshbuf_reset(msg); 164576259Sgreen 1646221420Sdes handle = get_handle(conn, id, &handle_len, 1647204917Sdes "remote open(\"%s\")", remote_path); 164876259Sgreen if (handle == NULL) { 164976259Sgreen close(local_fd); 1650294332Sdes sshbuf_free(msg); 1651181111Sdes return -1; 165276259Sgreen } 165376259Sgreen 165492555Sdes startid = ackid = id + 1; 165592555Sdes data = xmalloc(conn->transfer_buflen); 165692555Sdes 165776259Sgreen /* Read from local and write to remote */ 1658294328Sdes offset = progress_counter = (resume ? c->size : 0); 1659113908Sdes if (showprogress) 1660255767Sdes start_progress_meter(local_path, sb.st_size, 1661255767Sdes &progress_counter); 1662113908Sdes 166392555Sdes for (;;) { 166476259Sgreen int len; 166576259Sgreen 166676259Sgreen /* 1667137015Sdes * Can't use atomicio here because it returns 0 on EOF, 1668137015Sdes * thus losing the last block of the file. 1669137015Sdes * Simulate an EOF on interrupt, allowing ACKs from the 1670137015Sdes * server to drain. 167176259Sgreen */ 1672181111Sdes if (interrupted || status != SSH2_FX_OK) 1673137015Sdes len = 0; 1674137015Sdes else do 167592555Sdes len = read(local_fd, data, conn->transfer_buflen); 1676181111Sdes while ((len == -1) && 1677181111Sdes (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); 167876259Sgreen 167976259Sgreen if (len == -1) 168076259Sgreen fatal("Couldn't read from \"%s\": %s", local_path, 168176259Sgreen strerror(errno)); 168292555Sdes 168392555Sdes if (len != 0) { 1684257954Sdelphij ack = xcalloc(1, sizeof(*ack)); 168592555Sdes ack->id = ++id; 168692555Sdes ack->offset = offset; 168792555Sdes ack->len = len; 168892555Sdes TAILQ_INSERT_TAIL(&acks, ack, tq); 168992555Sdes 1690294332Sdes sshbuf_reset(msg); 1691294332Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 || 1692294332Sdes (r = sshbuf_put_u32(msg, ack->id)) != 0 || 1693294332Sdes (r = sshbuf_put_string(msg, handle, 1694294332Sdes handle_len)) != 0 || 1695294332Sdes (r = sshbuf_put_u64(msg, offset)) != 0 || 1696294332Sdes (r = sshbuf_put_string(msg, data, len)) != 0) 1697294332Sdes fatal("%s: buffer error: %s", 1698294332Sdes __func__, ssh_err(r)); 1699294332Sdes send_msg(conn, msg); 170099060Sdes debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u", 1701113908Sdes id, (unsigned long long)offset, len); 170292555Sdes } else if (TAILQ_FIRST(&acks) == NULL) 170376259Sgreen break; 170476259Sgreen 170592555Sdes if (ack == NULL) 170692555Sdes fatal("Unexpected ACK %u", id); 170776259Sgreen 170898675Sdes if (id == startid || len == 0 || 170992555Sdes id - ackid >= conn->num_requests) { 1710294332Sdes u_int rid; 171198675Sdes 1712294332Sdes sshbuf_reset(msg); 1713294332Sdes get_msg(conn, msg); 1714294332Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1715294332Sdes (r = sshbuf_get_u32(msg, &rid)) != 0) 1716294332Sdes fatal("%s: buffer error: %s", 1717294332Sdes __func__, ssh_err(r)); 171892555Sdes 171992555Sdes if (type != SSH2_FXP_STATUS) 172092555Sdes fatal("Expected SSH2_FXP_STATUS(%d) packet, " 172192555Sdes "got %d", SSH2_FXP_STATUS, type); 172292555Sdes 1723294332Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 1724294332Sdes fatal("%s: buffer error: %s", 1725294332Sdes __func__, ssh_err(r)); 1726294332Sdes debug3("SSH2_FXP_STATUS %u", status); 172792555Sdes 172892555Sdes /* Find the request in our queue */ 1729147001Sdes for (ack = TAILQ_FIRST(&acks); 1730294332Sdes ack != NULL && ack->id != rid; 173192555Sdes ack = TAILQ_NEXT(ack, tq)) 173292555Sdes ; 173392555Sdes if (ack == NULL) 1734294332Sdes fatal("Can't find request for ID %u", rid); 173592555Sdes TAILQ_REMOVE(&acks, ack, tq); 1736181111Sdes debug3("In write loop, ack for %u %u bytes at %lld", 1737181111Sdes ack->id, ack->len, (long long)ack->offset); 173892555Sdes ++ackid; 1739255767Sdes progress_counter += ack->len; 1740255767Sdes free(ack); 174176259Sgreen } 174276259Sgreen offset += len; 1743181111Sdes if (offset < 0) 1744181111Sdes fatal("%s: offset < 0", __func__); 174576259Sgreen } 1746294332Sdes sshbuf_free(msg); 1747181111Sdes 1748113908Sdes if (showprogress) 1749113908Sdes stop_progress_meter(); 1750255767Sdes free(data); 175176259Sgreen 1752181111Sdes if (status != SSH2_FX_OK) { 1753181111Sdes error("Couldn't write to remote file \"%s\": %s", 1754181111Sdes remote_path, fx2txt(status)); 1755294332Sdes status = SSH2_FX_FAILURE; 1756181111Sdes } 1757181111Sdes 175876259Sgreen if (close(local_fd) == -1) { 175976259Sgreen error("Couldn't close local file \"%s\": %s", local_path, 176076259Sgreen strerror(errno)); 1761294332Sdes status = SSH2_FX_FAILURE; 176276259Sgreen } 176376259Sgreen 176476259Sgreen /* Override umask and utimes if asked */ 1765261320Sdes if (preserve_flag) 176692555Sdes do_fsetstat(conn, handle, handle_len, &a); 176776259Sgreen 1768261320Sdes if (fsync_flag) 1769261320Sdes (void)do_fsync(conn, handle, handle_len); 1770261320Sdes 1771296633Sdes if (do_close(conn, handle, handle_len) != 0) 1772294332Sdes status = SSH2_FX_FAILURE; 1773294332Sdes 1774255767Sdes free(handle); 177576259Sgreen 1776294332Sdes return status == SSH2_FX_OK ? 0 : -1; 177776259Sgreen} 1778204917Sdes 1779204917Sdesstatic int 1780294332Sdesupload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, 1781294332Sdes int depth, int preserve_flag, int print_flag, int resume, int fsync_flag) 1782204917Sdes{ 1783294332Sdes int ret = 0; 1784204917Sdes DIR *dirp; 1785204917Sdes struct dirent *dp; 1786204917Sdes char *filename, *new_src, *new_dst; 1787204917Sdes struct stat sb; 1788296633Sdes Attrib a, *dirattrib; 1789204917Sdes 1790204917Sdes if (depth >= MAX_DIR_DEPTH) { 1791204917Sdes error("Maximum directory depth exceeded: %d levels", depth); 1792204917Sdes return -1; 1793204917Sdes } 1794204917Sdes 1795204917Sdes if (stat(src, &sb) == -1) { 1796204917Sdes error("Couldn't stat directory \"%s\": %s", 1797204917Sdes src, strerror(errno)); 1798204917Sdes return -1; 1799204917Sdes } 1800204917Sdes if (!S_ISDIR(sb.st_mode)) { 1801204917Sdes error("\"%s\" is not a directory", src); 1802204917Sdes return -1; 1803204917Sdes } 1804261320Sdes if (print_flag) 1805323129Sdes mprintf("Entering %s\n", src); 1806204917Sdes 1807204917Sdes attrib_clear(&a); 1808204917Sdes stat_to_attrib(&sb, &a); 1809204917Sdes a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 1810204917Sdes a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 1811204917Sdes a.perm &= 01777; 1812261320Sdes if (!preserve_flag) 1813204917Sdes a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 1814255767Sdes 1815204917Sdes /* 1816296633Sdes * sftp lacks a portable status value to match errno EEXIST, 1817296633Sdes * so if we get a failure back then we must check whether 1818296633Sdes * the path already existed and is a directory. 1819204917Sdes */ 1820296633Sdes if (do_mkdir(conn, dst, &a, 0) != 0) { 1821296633Sdes if ((dirattrib = do_stat(conn, dst, 0)) == NULL) 1822204917Sdes return -1; 1823296633Sdes if (!S_ISDIR(dirattrib->perm)) { 1824296633Sdes error("\"%s\" exists but is not a directory", dst); 1825204917Sdes return -1; 1826296633Sdes } 1827204917Sdes } 1828204917Sdes 1829204917Sdes if ((dirp = opendir(src)) == NULL) { 1830204917Sdes error("Failed to open dir \"%s\": %s", src, strerror(errno)); 1831204917Sdes return -1; 1832204917Sdes } 1833255767Sdes 1834204917Sdes while (((dp = readdir(dirp)) != NULL) && !interrupted) { 1835204917Sdes if (dp->d_ino == 0) 1836204917Sdes continue; 1837204917Sdes filename = dp->d_name; 1838204917Sdes new_dst = path_append(dst, filename); 1839204917Sdes new_src = path_append(src, filename); 1840204917Sdes 1841204917Sdes if (lstat(new_src, &sb) == -1) { 1842204917Sdes logit("%s: lstat failed: %s", filename, 1843204917Sdes strerror(errno)); 1844204917Sdes ret = -1; 1845204917Sdes } else if (S_ISDIR(sb.st_mode)) { 1846204917Sdes if (strcmp(filename, ".") == 0 || 1847204917Sdes strcmp(filename, "..") == 0) 1848204917Sdes continue; 1849204917Sdes 1850204917Sdes if (upload_dir_internal(conn, new_src, new_dst, 1851294328Sdes depth + 1, preserve_flag, print_flag, resume, 1852261320Sdes fsync_flag) == -1) 1853204917Sdes ret = -1; 1854204917Sdes } else if (S_ISREG(sb.st_mode)) { 1855261320Sdes if (do_upload(conn, new_src, new_dst, 1856294328Sdes preserve_flag, resume, fsync_flag) == -1) { 1857204917Sdes error("Uploading of file %s to %s failed!", 1858204917Sdes new_src, new_dst); 1859204917Sdes ret = -1; 1860204917Sdes } 1861204917Sdes } else 1862204917Sdes logit("%s: not a regular file\n", filename); 1863255767Sdes free(new_dst); 1864255767Sdes free(new_src); 1865204917Sdes } 1866204917Sdes 1867204917Sdes do_setstat(conn, dst, &a); 1868204917Sdes 1869204917Sdes (void) closedir(dirp); 1870204917Sdes return ret; 1871204917Sdes} 1872204917Sdes 1873204917Sdesint 1874294332Sdesupload_dir(struct sftp_conn *conn, const char *src, const char *dst, 1875294332Sdes int preserve_flag, int print_flag, int resume, int fsync_flag) 1876204917Sdes{ 1877204917Sdes char *dst_canon; 1878204917Sdes int ret; 1879204917Sdes 1880204917Sdes if ((dst_canon = do_realpath(conn, dst)) == NULL) { 1881261320Sdes error("Unable to canonicalize path \"%s\"", dst); 1882204917Sdes return -1; 1883204917Sdes } 1884204917Sdes 1885261320Sdes ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag, 1886294328Sdes print_flag, resume, fsync_flag); 1887261320Sdes 1888255767Sdes free(dst_canon); 1889204917Sdes return ret; 1890204917Sdes} 1891204917Sdes 1892204917Sdeschar * 1893294332Sdespath_append(const char *p1, const char *p2) 1894204917Sdes{ 1895204917Sdes char *ret; 1896204917Sdes size_t len = strlen(p1) + strlen(p2) + 2; 1897204917Sdes 1898204917Sdes ret = xmalloc(len); 1899204917Sdes strlcpy(ret, p1, len); 1900204917Sdes if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') 1901204917Sdes strlcat(ret, "/", len); 1902204917Sdes strlcat(ret, p2, len); 1903204917Sdes 1904204917Sdes return(ret); 1905204917Sdes} 1906204917Sdes 1907