1296853Sdes/* $OpenBSD: sftp-client.c,v 1.121 2016/02/11 02:21:34 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 25295367Sdes#include <sys/param.h> /* MIN MAX */ 26162852Sdes#include <sys/types.h> 27181111Sdes#ifdef HAVE_SYS_STATVFS_H 28181111Sdes#include <sys/statvfs.h> 29181111Sdes#endif 30106121Sdes#include "openbsd-compat/sys-queue.h" 31162852Sdes#ifdef HAVE_SYS_STAT_H 32162852Sdes# include <sys/stat.h> 33162852Sdes#endif 34162852Sdes#ifdef HAVE_SYS_TIME_H 35162852Sdes# include <sys/time.h> 36162852Sdes#endif 37162852Sdes#include <sys/uio.h> 3892555Sdes 39204917Sdes#include <dirent.h> 40162852Sdes#include <errno.h> 41162852Sdes#include <fcntl.h> 42162852Sdes#include <signal.h> 43162852Sdes#include <stdarg.h> 44162852Sdes#include <stdio.h> 45262566Sdes#include <stdlib.h> 46162852Sdes#include <string.h> 47162852Sdes#include <unistd.h> 48162852Sdes 49162852Sdes#include "xmalloc.h" 50295367Sdes#include "ssherr.h" 51295367Sdes#include "sshbuf.h" 5276259Sgreen#include "log.h" 5376259Sgreen#include "atomicio.h" 54113908Sdes#include "progressmeter.h" 55162852Sdes#include "misc.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 7092555Sdesstruct sftp_conn { 7192555Sdes int fd_in; 7292555Sdes int fd_out; 7392555Sdes u_int transfer_buflen; 7492555Sdes u_int num_requests; 7592555Sdes u_int version; 7692555Sdes u_int msg_id; 77181111Sdes#define SFTP_EXT_POSIX_RENAME 0x00000001 78181111Sdes#define SFTP_EXT_STATVFS 0x00000002 79181111Sdes#define SFTP_EXT_FSTATVFS 0x00000004 80221420Sdes#define SFTP_EXT_HARDLINK 0x00000008 81262566Sdes#define SFTP_EXT_FSYNC 0x00000010 82181111Sdes u_int exts; 83221420Sdes u_int64_t limit_kbps; 84221420Sdes struct bwlimit bwlimit_in, bwlimit_out; 8592555Sdes}; 8676259Sgreen 87295367Sdesstatic u_char * 88295367Sdesget_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, 89221420Sdes const char *errfmt, ...) __attribute__((format(printf, 4, 5))); 90204917Sdes 91221420Sdes/* ARGSUSED */ 92221420Sdesstatic int 93221420Sdessftpio(void *_bwlimit, size_t amount) 94221420Sdes{ 95221420Sdes struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit; 96221420Sdes 97221420Sdes bandwidth_limit(bwlimit, amount); 98221420Sdes return 0; 99221420Sdes} 100221420Sdes 10192555Sdesstatic void 102295367Sdessend_msg(struct sftp_conn *conn, struct sshbuf *m) 10376259Sgreen{ 104113908Sdes u_char mlen[4]; 105162852Sdes struct iovec iov[2]; 10676259Sgreen 107295367Sdes if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH) 108295367Sdes fatal("Outbound message too long %zu", sshbuf_len(m)); 10976259Sgreen 110113908Sdes /* Send length first */ 111295367Sdes put_u32(mlen, sshbuf_len(m)); 112162852Sdes iov[0].iov_base = mlen; 113162852Sdes iov[0].iov_len = sizeof(mlen); 114295367Sdes iov[1].iov_base = (u_char *)sshbuf_ptr(m); 115295367Sdes iov[1].iov_len = sshbuf_len(m); 11676259Sgreen 117221420Sdes if (atomiciov6(writev, conn->fd_out, iov, 2, 118255767Sdes conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) != 119295367Sdes sshbuf_len(m) + sizeof(mlen)) 120113908Sdes fatal("Couldn't send packet: %s", strerror(errno)); 121113908Sdes 122295367Sdes sshbuf_reset(m); 12376259Sgreen} 12476259Sgreen 12592555Sdesstatic void 126295367Sdesget_msg(struct sftp_conn *conn, struct sshbuf *m) 12776259Sgreen{ 128113908Sdes u_int msg_len; 129295367Sdes u_char *p; 130295367Sdes int r; 13176259Sgreen 132295367Sdes if ((r = sshbuf_reserve(m, 4, &p)) != 0) 133295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 134295367Sdes if (atomicio6(read, conn->fd_in, p, 4, 135221420Sdes conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) { 136149749Sdes if (errno == EPIPE) 137149749Sdes fatal("Connection closed"); 138149749Sdes else 139149749Sdes fatal("Couldn't read packet: %s", strerror(errno)); 140149749Sdes } 14176259Sgreen 142295367Sdes if ((r = sshbuf_get_u32(m, &msg_len)) != 0) 143295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 144157016Sdes if (msg_len > SFTP_MAX_MSG_LENGTH) 14599060Sdes fatal("Received message too long %u", msg_len); 14676259Sgreen 147295367Sdes if ((r = sshbuf_reserve(m, msg_len, &p)) != 0) 148295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 149295367Sdes if (atomicio6(read, conn->fd_in, p, msg_len, 150221420Sdes conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) 151221420Sdes != msg_len) { 152149749Sdes if (errno == EPIPE) 153149749Sdes fatal("Connection closed"); 154149749Sdes else 155149749Sdes fatal("Read packet: %s", strerror(errno)); 156149749Sdes } 15776259Sgreen} 15876259Sgreen 15992555Sdesstatic void 160295367Sdessend_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s, 16176259Sgreen u_int len) 16276259Sgreen{ 163295367Sdes struct sshbuf *msg; 164295367Sdes int r; 16576259Sgreen 166295367Sdes if ((msg = sshbuf_new()) == NULL) 167295367Sdes fatal("%s: sshbuf_new failed", __func__); 168295367Sdes if ((r = sshbuf_put_u8(msg, code)) != 0 || 169295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 170295367Sdes (r = sshbuf_put_string(msg, s, len)) != 0) 171295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 172295367Sdes send_msg(conn, msg); 173221420Sdes debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); 174295367Sdes sshbuf_free(msg); 17576259Sgreen} 17676259Sgreen 17792555Sdesstatic void 178221420Sdessend_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code, 179295367Sdes const void *s, u_int len, Attrib *a) 18076259Sgreen{ 181295367Sdes struct sshbuf *msg; 182295367Sdes int r; 18376259Sgreen 184295367Sdes if ((msg = sshbuf_new()) == NULL) 185295367Sdes fatal("%s: sshbuf_new failed", __func__); 186295367Sdes if ((r = sshbuf_put_u8(msg, code)) != 0 || 187295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 188295367Sdes (r = sshbuf_put_string(msg, s, len)) != 0 || 189295367Sdes (r = encode_attrib(msg, a)) != 0) 190295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 191295367Sdes send_msg(conn, msg); 192221420Sdes debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); 193295367Sdes sshbuf_free(msg); 19476259Sgreen} 19576259Sgreen 19692555Sdesstatic u_int 197221420Sdesget_status(struct sftp_conn *conn, u_int expected_id) 19876259Sgreen{ 199295367Sdes struct sshbuf *msg; 200295367Sdes u_char type; 201295367Sdes u_int id, status; 202295367Sdes int r; 20376259Sgreen 204295367Sdes if ((msg = sshbuf_new()) == NULL) 205295367Sdes fatal("%s: sshbuf_new failed", __func__); 206295367Sdes get_msg(conn, msg); 207295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 208295367Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 209295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 21076259Sgreen 21176259Sgreen if (id != expected_id) 21299060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 21376259Sgreen if (type != SSH2_FXP_STATUS) 21499060Sdes fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u", 21576259Sgreen SSH2_FXP_STATUS, type); 21676259Sgreen 217295367Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 218295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 219295367Sdes sshbuf_free(msg); 22076259Sgreen 22199060Sdes debug3("SSH2_FXP_STATUS %u", status); 22276259Sgreen 223221420Sdes return status; 22476259Sgreen} 22576259Sgreen 226295367Sdesstatic u_char * 227295367Sdesget_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, 228221420Sdes const char *errfmt, ...) 22976259Sgreen{ 230295367Sdes struct sshbuf *msg; 231295367Sdes u_int id, status; 232295367Sdes u_char type; 233295367Sdes u_char *handle; 234295367Sdes char errmsg[256]; 235204917Sdes va_list args; 236295367Sdes int r; 23776259Sgreen 238204917Sdes va_start(args, errfmt); 239204917Sdes if (errfmt != NULL) 240204917Sdes vsnprintf(errmsg, sizeof(errmsg), errfmt, args); 241204917Sdes va_end(args); 242204917Sdes 243295367Sdes if ((msg = sshbuf_new()) == NULL) 244295367Sdes fatal("%s: sshbuf_new failed", __func__); 245295367Sdes get_msg(conn, msg); 246295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 247295367Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 248295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 24976259Sgreen 25076259Sgreen if (id != expected_id) 251204917Sdes fatal("%s: ID mismatch (%u != %u)", 252204917Sdes errfmt == NULL ? __func__ : errmsg, id, expected_id); 25376259Sgreen if (type == SSH2_FXP_STATUS) { 254295367Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 255295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 256204917Sdes if (errfmt != NULL) 257204917Sdes error("%s: %s", errmsg, fx2txt(status)); 258295367Sdes sshbuf_free(msg); 25976259Sgreen return(NULL); 26076259Sgreen } else if (type != SSH2_FXP_HANDLE) 261204917Sdes fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u", 262204917Sdes errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type); 26376259Sgreen 264295367Sdes if ((r = sshbuf_get_string(msg, &handle, len)) != 0) 265295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 266295367Sdes sshbuf_free(msg); 26776259Sgreen 268295367Sdes return handle; 26976259Sgreen} 27076259Sgreen 27192555Sdesstatic Attrib * 272221420Sdesget_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet) 27376259Sgreen{ 274295367Sdes struct sshbuf *msg; 275295367Sdes u_int id; 276295367Sdes u_char type; 277295367Sdes int r; 278295367Sdes static Attrib a; 27976259Sgreen 280295367Sdes if ((msg = sshbuf_new()) == NULL) 281295367Sdes fatal("%s: sshbuf_new failed", __func__); 282295367Sdes get_msg(conn, msg); 28376259Sgreen 284295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 285295367Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 286295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 28776259Sgreen 28899060Sdes debug3("Received stat reply T:%u I:%u", type, id); 28976259Sgreen if (id != expected_id) 29099060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 29176259Sgreen if (type == SSH2_FXP_STATUS) { 292295367Sdes u_int status; 29376259Sgreen 294295367Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 295295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 29676259Sgreen if (quiet) 29776259Sgreen debug("Couldn't stat remote file: %s", fx2txt(status)); 29876259Sgreen else 29976259Sgreen error("Couldn't stat remote file: %s", fx2txt(status)); 300295367Sdes sshbuf_free(msg); 30176259Sgreen return(NULL); 30276259Sgreen } else if (type != SSH2_FXP_ATTRS) { 30399060Sdes fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", 30476259Sgreen SSH2_FXP_ATTRS, type); 30576259Sgreen } 306295367Sdes if ((r = decode_attrib(msg, &a)) != 0) { 307295367Sdes error("%s: couldn't decode attrib: %s", __func__, ssh_err(r)); 308295367Sdes sshbuf_free(msg); 309295367Sdes return NULL; 310295367Sdes } 311295367Sdes sshbuf_free(msg); 31276259Sgreen 313295367Sdes return &a; 31476259Sgreen} 31576259Sgreen 316181111Sdesstatic int 317221420Sdesget_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st, 318221420Sdes u_int expected_id, int quiet) 319181111Sdes{ 320295367Sdes struct sshbuf *msg; 321295367Sdes u_char type; 322295367Sdes u_int id; 323295367Sdes u_int64_t flag; 324295367Sdes int r; 325181111Sdes 326295367Sdes if ((msg = sshbuf_new()) == NULL) 327295367Sdes fatal("%s: sshbuf_new failed", __func__); 328295367Sdes get_msg(conn, msg); 329181111Sdes 330295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 331295367Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 332295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 333181111Sdes 334181111Sdes debug3("Received statvfs reply T:%u I:%u", type, id); 335181111Sdes if (id != expected_id) 336181111Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 337181111Sdes if (type == SSH2_FXP_STATUS) { 338295367Sdes u_int status; 339181111Sdes 340295367Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 341295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 342181111Sdes if (quiet) 343181111Sdes debug("Couldn't statvfs: %s", fx2txt(status)); 344181111Sdes else 345181111Sdes error("Couldn't statvfs: %s", fx2txt(status)); 346295367Sdes sshbuf_free(msg); 347181111Sdes return -1; 348181111Sdes } else if (type != SSH2_FXP_EXTENDED_REPLY) { 349181111Sdes fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", 350181111Sdes SSH2_FXP_EXTENDED_REPLY, type); 351181111Sdes } 352181111Sdes 353264377Sdes memset(st, 0, sizeof(*st)); 354295367Sdes if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 || 355295367Sdes (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 || 356295367Sdes (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 || 357295367Sdes (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 || 358295367Sdes (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 || 359295367Sdes (r = sshbuf_get_u64(msg, &st->f_files)) != 0 || 360295367Sdes (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 || 361295367Sdes (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 || 362295367Sdes (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 || 363295367Sdes (r = sshbuf_get_u64(msg, &flag)) != 0 || 364295367Sdes (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0) 365295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 366181111Sdes 367181111Sdes st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0; 368181111Sdes st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0; 369181111Sdes 370295367Sdes sshbuf_free(msg); 371181111Sdes 372181111Sdes return 0; 373181111Sdes} 374181111Sdes 37592555Sdesstruct sftp_conn * 376221420Sdesdo_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, 377221420Sdes u_int64_t limit_kbps) 37876259Sgreen{ 379295367Sdes u_char type; 380295367Sdes struct sshbuf *msg; 38192555Sdes struct sftp_conn *ret; 382295367Sdes int r; 38376259Sgreen 384262566Sdes ret = xcalloc(1, sizeof(*ret)); 385262566Sdes ret->msg_id = 1; 386221420Sdes ret->fd_in = fd_in; 387221420Sdes ret->fd_out = fd_out; 388221420Sdes ret->transfer_buflen = transfer_buflen; 389221420Sdes ret->num_requests = num_requests; 390221420Sdes ret->exts = 0; 391221420Sdes ret->limit_kbps = 0; 392221420Sdes 393295367Sdes if ((msg = sshbuf_new()) == NULL) 394295367Sdes fatal("%s: sshbuf_new failed", __func__); 395295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 || 396295367Sdes (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0) 397295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 398295367Sdes send_msg(ret, msg); 39976259Sgreen 400295367Sdes sshbuf_reset(msg); 40176259Sgreen 402295367Sdes get_msg(ret, msg); 40376259Sgreen 40476259Sgreen /* Expecting a VERSION reply */ 405295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0) 406295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 407295367Sdes if (type != SSH2_FXP_VERSION) { 40899060Sdes error("Invalid packet back from SSH2_FXP_INIT (type %u)", 40976259Sgreen type); 410295367Sdes sshbuf_free(msg); 411295367Sdes free(ret); 41292555Sdes return(NULL); 41376259Sgreen } 414295367Sdes if ((r = sshbuf_get_u32(msg, &ret->version)) != 0) 415295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 41676259Sgreen 417221420Sdes debug2("Remote version: %u", ret->version); 41876259Sgreen 41976259Sgreen /* Check for extensions */ 420295367Sdes while (sshbuf_len(msg) > 0) { 421295367Sdes char *name; 422295367Sdes u_char *value; 423295367Sdes size_t vlen; 424181111Sdes int known = 0; 42576259Sgreen 426295367Sdes if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 || 427295367Sdes (r = sshbuf_get_string(msg, &value, &vlen)) != 0) 428295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 429181111Sdes if (strcmp(name, "posix-rename@openssh.com") == 0 && 430295367Sdes strcmp((char *)value, "1") == 0) { 431221420Sdes ret->exts |= SFTP_EXT_POSIX_RENAME; 432181111Sdes known = 1; 433181111Sdes } else if (strcmp(name, "statvfs@openssh.com") == 0 && 434295367Sdes strcmp((char *)value, "2") == 0) { 435221420Sdes ret->exts |= SFTP_EXT_STATVFS; 436181111Sdes known = 1; 437221420Sdes } else if (strcmp(name, "fstatvfs@openssh.com") == 0 && 438295367Sdes strcmp((char *)value, "2") == 0) { 439221420Sdes ret->exts |= SFTP_EXT_FSTATVFS; 440181111Sdes known = 1; 441221420Sdes } else if (strcmp(name, "hardlink@openssh.com") == 0 && 442295367Sdes strcmp((char *)value, "1") == 0) { 443221420Sdes ret->exts |= SFTP_EXT_HARDLINK; 444221420Sdes known = 1; 445295367Sdes } else if (strcmp(name, "fsync@openssh.com") == 0 && 446295367Sdes strcmp((char *)value, "1") == 0) { 447295367Sdes ret->exts |= SFTP_EXT_FSYNC; 448295367Sdes known = 1; 449181111Sdes } 450181111Sdes if (known) { 451181111Sdes debug2("Server supports extension \"%s\" revision %s", 452181111Sdes name, value); 453181111Sdes } else { 454181111Sdes debug2("Unrecognised server extension \"%s\"", name); 455181111Sdes } 456255767Sdes free(name); 457255767Sdes free(value); 45876259Sgreen } 45976259Sgreen 460295367Sdes sshbuf_free(msg); 46176259Sgreen 46292555Sdes /* Some filexfer v.0 servers don't support large packets */ 463221420Sdes if (ret->version == 0) 46498675Sdes ret->transfer_buflen = MIN(ret->transfer_buflen, 20480); 46592555Sdes 466221420Sdes ret->limit_kbps = limit_kbps; 467221420Sdes if (ret->limit_kbps > 0) { 468221420Sdes bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps, 469221420Sdes ret->transfer_buflen); 470221420Sdes bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps, 471221420Sdes ret->transfer_buflen); 472221420Sdes } 473221420Sdes 474221420Sdes return ret; 47576259Sgreen} 47676259Sgreen 47792555Sdesu_int 47892555Sdessftp_proto_version(struct sftp_conn *conn) 47992555Sdes{ 480221420Sdes return conn->version; 48192555Sdes} 48292555Sdes 48376259Sgreenint 484295367Sdesdo_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) 48576259Sgreen{ 48676259Sgreen u_int id, status; 487295367Sdes struct sshbuf *msg; 488295367Sdes int r; 48976259Sgreen 490295367Sdes if ((msg = sshbuf_new()) == NULL) 491295367Sdes fatal("%s: sshbuf_new failed", __func__); 49276259Sgreen 49392555Sdes id = conn->msg_id++; 494295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 || 495295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 496295367Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 497295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 498295367Sdes send_msg(conn, msg); 49999060Sdes debug3("Sent message SSH2_FXP_CLOSE I:%u", id); 50076259Sgreen 501221420Sdes status = get_status(conn, id); 50276259Sgreen if (status != SSH2_FX_OK) 50376259Sgreen error("Couldn't close file: %s", fx2txt(status)); 50476259Sgreen 505295367Sdes sshbuf_free(msg); 50676259Sgreen 507295367Sdes return status == SSH2_FX_OK ? 0 : -1; 50876259Sgreen} 50976259Sgreen 51076259Sgreen 51192555Sdesstatic int 512295367Sdesdo_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag, 51376259Sgreen SFTP_DIRENT ***dir) 51476259Sgreen{ 515295367Sdes struct sshbuf *msg; 516295367Sdes u_int count, id, i, expected_id, ents = 0; 517295367Sdes size_t handle_len; 518295367Sdes u_char type; 51976259Sgreen char *handle; 520262566Sdes int status = SSH2_FX_FAILURE; 521295367Sdes int r; 52276259Sgreen 523262566Sdes if (dir) 524262566Sdes *dir = NULL; 525262566Sdes 52692555Sdes id = conn->msg_id++; 52776259Sgreen 528295367Sdes if ((msg = sshbuf_new()) == NULL) 529295367Sdes fatal("%s: sshbuf_new failed", __func__); 530295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 || 531295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 532295367Sdes (r = sshbuf_put_cstring(msg, path)) != 0) 533295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 534295367Sdes send_msg(conn, msg); 53576259Sgreen 536221420Sdes handle = get_handle(conn, id, &handle_len, 537204917Sdes "remote readdir(\"%s\")", path); 538240075Sdes if (handle == NULL) { 539295367Sdes sshbuf_free(msg); 540221420Sdes return -1; 541240075Sdes } 54276259Sgreen 54376259Sgreen if (dir) { 54476259Sgreen ents = 0; 545258343Sdes *dir = xcalloc(1, sizeof(**dir)); 54676259Sgreen (*dir)[0] = NULL; 54776259Sgreen } 54876259Sgreen 549137015Sdes for (; !interrupted;) { 55092555Sdes id = expected_id = conn->msg_id++; 55176259Sgreen 55299060Sdes debug3("Sending SSH2_FXP_READDIR I:%u", id); 55376259Sgreen 554295367Sdes sshbuf_reset(msg); 555295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 || 556295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 557295367Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 558295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 559295367Sdes send_msg(conn, msg); 56076259Sgreen 561295367Sdes sshbuf_reset(msg); 56276259Sgreen 563295367Sdes get_msg(conn, msg); 56476259Sgreen 565295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 566295367Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 567295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 56876259Sgreen 56999060Sdes debug3("Received reply T:%u I:%u", type, id); 57076259Sgreen 57176259Sgreen if (id != expected_id) 57299060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 57376259Sgreen 57476259Sgreen if (type == SSH2_FXP_STATUS) { 575295367Sdes u_int rstatus; 576295367Sdes 577295367Sdes if ((r = sshbuf_get_u32(msg, &rstatus)) != 0) 578295367Sdes fatal("%s: buffer error: %s", 579295367Sdes __func__, ssh_err(r)); 580295367Sdes debug3("Received SSH2_FXP_STATUS %d", rstatus); 581295367Sdes if (rstatus == SSH2_FX_EOF) 58276259Sgreen break; 583295367Sdes error("Couldn't read directory: %s", fx2txt(rstatus)); 584262566Sdes goto out; 58576259Sgreen } else if (type != SSH2_FXP_NAME) 58699060Sdes fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 58776259Sgreen SSH2_FXP_NAME, type); 58876259Sgreen 589295367Sdes if ((r = sshbuf_get_u32(msg, &count)) != 0) 590295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 59176259Sgreen if (count == 0) 59276259Sgreen break; 59376259Sgreen debug3("Received %d SSH2_FXP_NAME responses", count); 59492555Sdes for (i = 0; i < count; i++) { 59576259Sgreen char *filename, *longname; 596295367Sdes Attrib a; 59776259Sgreen 598295367Sdes if ((r = sshbuf_get_cstring(msg, &filename, 599295367Sdes NULL)) != 0 || 600295367Sdes (r = sshbuf_get_cstring(msg, &longname, 601295367Sdes NULL)) != 0) 602295367Sdes fatal("%s: buffer error: %s", 603295367Sdes __func__, ssh_err(r)); 604295367Sdes if ((r = decode_attrib(msg, &a)) != 0) { 605295367Sdes error("%s: couldn't decode attrib: %s", 606295367Sdes __func__, ssh_err(r)); 607295367Sdes free(filename); 608295367Sdes free(longname); 609295367Sdes sshbuf_free(msg); 610295367Sdes return -1; 611295367Sdes } 61276259Sgreen 613262566Sdes if (print_flag) 61476259Sgreen printf("%s\n", longname); 61576259Sgreen 616204917Sdes /* 617204917Sdes * Directory entries should never contain '/' 618204917Sdes * These can be used to attack recursive ops 619204917Sdes * (e.g. send '../../../../etc/passwd') 620204917Sdes */ 621204917Sdes if (strchr(filename, '/') != NULL) { 622204917Sdes error("Server sent suspect path \"%s\" " 623204917Sdes "during readdir of \"%s\"", filename, path); 624262566Sdes } else if (dir) { 625295367Sdes *dir = xreallocarray(*dir, ents + 2, sizeof(**dir)); 626258343Sdes (*dir)[ents] = xcalloc(1, sizeof(***dir)); 62776259Sgreen (*dir)[ents]->filename = xstrdup(filename); 62876259Sgreen (*dir)[ents]->longname = xstrdup(longname); 629295367Sdes memcpy(&(*dir)[ents]->a, &a, sizeof(a)); 63076259Sgreen (*dir)[++ents] = NULL; 63176259Sgreen } 632255767Sdes free(filename); 633255767Sdes free(longname); 63476259Sgreen } 63576259Sgreen } 636262566Sdes status = 0; 63776259Sgreen 638262566Sdes out: 639295367Sdes sshbuf_free(msg); 64092555Sdes do_close(conn, handle, handle_len); 641255767Sdes free(handle); 64276259Sgreen 643262566Sdes if (status != 0 && dir != NULL) { 644262566Sdes /* Don't return results on error */ 645137015Sdes free_sftp_dirents(*dir); 646262566Sdes *dir = NULL; 647262566Sdes } else if (interrupted && dir != NULL && *dir != NULL) { 648262566Sdes /* Don't return partial matches on interrupt */ 649262566Sdes free_sftp_dirents(*dir); 650258343Sdes *dir = xcalloc(1, sizeof(**dir)); 651137015Sdes **dir = NULL; 652137015Sdes } 653137015Sdes 654262566Sdes return status; 65576259Sgreen} 65676259Sgreen 65776259Sgreenint 658295367Sdesdo_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir) 65976259Sgreen{ 66092555Sdes return(do_lsreaddir(conn, path, 0, dir)); 66176259Sgreen} 66276259Sgreen 66376259Sgreenvoid free_sftp_dirents(SFTP_DIRENT **s) 66476259Sgreen{ 66576259Sgreen int i; 66692555Sdes 667262566Sdes if (s == NULL) 668262566Sdes return; 66992555Sdes for (i = 0; s[i]; i++) { 670255767Sdes free(s[i]->filename); 671255767Sdes free(s[i]->longname); 672255767Sdes free(s[i]); 67376259Sgreen } 674255767Sdes free(s); 67576259Sgreen} 67676259Sgreen 67776259Sgreenint 678295367Sdesdo_rm(struct sftp_conn *conn, const char *path) 67976259Sgreen{ 68076259Sgreen u_int status, id; 68176259Sgreen 68276259Sgreen debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); 68376259Sgreen 68492555Sdes id = conn->msg_id++; 685221420Sdes send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path)); 686221420Sdes status = get_status(conn, id); 68776259Sgreen if (status != SSH2_FX_OK) 68876259Sgreen error("Couldn't delete file: %s", fx2txt(status)); 689295367Sdes return status == SSH2_FX_OK ? 0 : -1; 69076259Sgreen} 69176259Sgreen 69276259Sgreenint 693295367Sdesdo_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) 69476259Sgreen{ 69576259Sgreen u_int status, id; 69676259Sgreen 69792555Sdes id = conn->msg_id++; 698221420Sdes send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path, 69976259Sgreen strlen(path), a); 70076259Sgreen 701221420Sdes status = get_status(conn, id); 702262566Sdes if (status != SSH2_FX_OK && print_flag) 70376259Sgreen error("Couldn't create directory: %s", fx2txt(status)); 70476259Sgreen 705295367Sdes return status == SSH2_FX_OK ? 0 : -1; 70676259Sgreen} 70776259Sgreen 70876259Sgreenint 709295367Sdesdo_rmdir(struct sftp_conn *conn, const char *path) 71076259Sgreen{ 71176259Sgreen u_int status, id; 71276259Sgreen 71392555Sdes id = conn->msg_id++; 714221420Sdes send_string_request(conn, id, SSH2_FXP_RMDIR, path, 71592555Sdes strlen(path)); 71676259Sgreen 717221420Sdes status = get_status(conn, id); 71876259Sgreen if (status != SSH2_FX_OK) 71976259Sgreen error("Couldn't remove directory: %s", fx2txt(status)); 72076259Sgreen 721295367Sdes return status == SSH2_FX_OK ? 0 : -1; 72276259Sgreen} 72376259Sgreen 72476259SgreenAttrib * 725295367Sdesdo_stat(struct sftp_conn *conn, const char *path, int quiet) 72676259Sgreen{ 72776259Sgreen u_int id; 72876259Sgreen 72992555Sdes id = conn->msg_id++; 73092555Sdes 731221420Sdes send_string_request(conn, id, 73298675Sdes conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, 73392555Sdes path, strlen(path)); 73492555Sdes 735221420Sdes return(get_decode_stat(conn, id, quiet)); 73676259Sgreen} 73776259Sgreen 73876259SgreenAttrib * 739295367Sdesdo_lstat(struct sftp_conn *conn, const char *path, int quiet) 74076259Sgreen{ 74176259Sgreen u_int id; 74276259Sgreen 74392555Sdes if (conn->version == 0) { 74492555Sdes if (quiet) 74592555Sdes debug("Server version does not support lstat operation"); 74692555Sdes else 747124208Sdes logit("Server version does not support lstat operation"); 74898675Sdes return(do_stat(conn, path, quiet)); 74992555Sdes } 75092555Sdes 75192555Sdes id = conn->msg_id++; 752221420Sdes send_string_request(conn, id, SSH2_FXP_LSTAT, path, 75392555Sdes strlen(path)); 75492555Sdes 755221420Sdes return(get_decode_stat(conn, id, quiet)); 75676259Sgreen} 75776259Sgreen 758181111Sdes#ifdef notyet 75976259SgreenAttrib * 760295367Sdesdo_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 761295367Sdes int quiet) 76276259Sgreen{ 76376259Sgreen u_int id; 76476259Sgreen 76592555Sdes id = conn->msg_id++; 766221420Sdes send_string_request(conn, id, SSH2_FXP_FSTAT, handle, 76792555Sdes handle_len); 76892555Sdes 769221420Sdes return(get_decode_stat(conn, id, quiet)); 77076259Sgreen} 771181111Sdes#endif 77276259Sgreen 77376259Sgreenint 774295367Sdesdo_setstat(struct sftp_conn *conn, const char *path, Attrib *a) 77576259Sgreen{ 77676259Sgreen u_int status, id; 77776259Sgreen 77892555Sdes id = conn->msg_id++; 779221420Sdes send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path, 78076259Sgreen strlen(path), a); 78176259Sgreen 782221420Sdes status = get_status(conn, id); 78376259Sgreen if (status != SSH2_FX_OK) 78476259Sgreen error("Couldn't setstat on \"%s\": %s", path, 78576259Sgreen fx2txt(status)); 78676259Sgreen 787295367Sdes return status == SSH2_FX_OK ? 0 : -1; 78876259Sgreen} 78976259Sgreen 79076259Sgreenint 791295367Sdesdo_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 79276259Sgreen Attrib *a) 79376259Sgreen{ 79476259Sgreen u_int status, id; 79576259Sgreen 79692555Sdes id = conn->msg_id++; 797221420Sdes send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle, 79876259Sgreen handle_len, a); 79976259Sgreen 800221420Sdes status = get_status(conn, id); 80176259Sgreen if (status != SSH2_FX_OK) 80276259Sgreen error("Couldn't fsetstat: %s", fx2txt(status)); 80376259Sgreen 804295367Sdes return status == SSH2_FX_OK ? 0 : -1; 80576259Sgreen} 80676259Sgreen 80776259Sgreenchar * 808295367Sdesdo_realpath(struct sftp_conn *conn, const char *path) 80976259Sgreen{ 810295367Sdes struct sshbuf *msg; 811295367Sdes u_int expected_id, count, id; 81276259Sgreen char *filename, *longname; 813295367Sdes Attrib a; 814295367Sdes u_char type; 815295367Sdes int r; 81676259Sgreen 81792555Sdes expected_id = id = conn->msg_id++; 818221420Sdes send_string_request(conn, id, SSH2_FXP_REALPATH, path, 81992555Sdes strlen(path)); 82076259Sgreen 821295367Sdes if ((msg = sshbuf_new()) == NULL) 822295367Sdes fatal("%s: sshbuf_new failed", __func__); 82376259Sgreen 824295367Sdes get_msg(conn, msg); 825295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 826295367Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 827295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 82876259Sgreen 82976259Sgreen if (id != expected_id) 83099060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 83176259Sgreen 83276259Sgreen if (type == SSH2_FXP_STATUS) { 833295367Sdes u_int status; 83476259Sgreen 835295367Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 836295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 837262566Sdes error("Couldn't canonicalize: %s", fx2txt(status)); 838295367Sdes sshbuf_free(msg); 839215116Sdes return NULL; 84076259Sgreen } else if (type != SSH2_FXP_NAME) 84199060Sdes fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 84276259Sgreen SSH2_FXP_NAME, type); 84376259Sgreen 844295367Sdes if ((r = sshbuf_get_u32(msg, &count)) != 0) 845295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 84676259Sgreen if (count != 1) 84776259Sgreen fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count); 84876259Sgreen 849295367Sdes if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || 850295367Sdes (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || 851295367Sdes (r = decode_attrib(msg, &a)) != 0) 852295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 85376259Sgreen 854240075Sdes debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename, 855295367Sdes (unsigned long)a.size); 85676259Sgreen 857255767Sdes free(longname); 85876259Sgreen 859295367Sdes sshbuf_free(msg); 86076259Sgreen 86176259Sgreen return(filename); 86276259Sgreen} 86376259Sgreen 86476259Sgreenint 865295367Sdesdo_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, 866262566Sdes int force_legacy) 86776259Sgreen{ 868295367Sdes struct sshbuf *msg; 86976259Sgreen u_int status, id; 870295367Sdes int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy; 87176259Sgreen 872295367Sdes if ((msg = sshbuf_new()) == NULL) 873295367Sdes fatal("%s: sshbuf_new failed", __func__); 87476259Sgreen 87576259Sgreen /* Send rename request */ 87692555Sdes id = conn->msg_id++; 877262566Sdes if (use_ext) { 878295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 879295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 880295367Sdes (r = sshbuf_put_cstring(msg, 881295367Sdes "posix-rename@openssh.com")) != 0) 882295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 883181111Sdes } else { 884295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 || 885295367Sdes (r = sshbuf_put_u32(msg, id)) != 0) 886295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 887181111Sdes } 888295367Sdes if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 || 889295367Sdes (r = sshbuf_put_cstring(msg, newpath)) != 0) 890295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 891295367Sdes send_msg(conn, msg); 892181111Sdes debug3("Sent message %s \"%s\" -> \"%s\"", 893295367Sdes use_ext ? "posix-rename@openssh.com" : 894295367Sdes "SSH2_FXP_RENAME", oldpath, newpath); 895295367Sdes sshbuf_free(msg); 89676259Sgreen 897221420Sdes status = get_status(conn, id); 89876259Sgreen if (status != SSH2_FX_OK) 89992555Sdes error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, 90092555Sdes newpath, fx2txt(status)); 90176259Sgreen 902295367Sdes return status == SSH2_FX_OK ? 0 : -1; 90376259Sgreen} 90476259Sgreen 90576259Sgreenint 906295367Sdesdo_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) 907221420Sdes{ 908295367Sdes struct sshbuf *msg; 909221420Sdes u_int status, id; 910295367Sdes int r; 911221420Sdes 912221420Sdes if ((conn->exts & SFTP_EXT_HARDLINK) == 0) { 913221420Sdes error("Server does not support hardlink@openssh.com extension"); 914221420Sdes return -1; 915221420Sdes } 916221420Sdes 917295367Sdes if ((msg = sshbuf_new()) == NULL) 918295367Sdes fatal("%s: sshbuf_new failed", __func__); 919240075Sdes 920240075Sdes /* Send link request */ 921240075Sdes id = conn->msg_id++; 922295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 923295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 924295367Sdes (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 || 925295367Sdes (r = sshbuf_put_cstring(msg, oldpath)) != 0 || 926295367Sdes (r = sshbuf_put_cstring(msg, newpath)) != 0) 927295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 928295367Sdes send_msg(conn, msg); 929221420Sdes debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"", 930221420Sdes oldpath, newpath); 931295367Sdes sshbuf_free(msg); 932221420Sdes 933221420Sdes status = get_status(conn, id); 934221420Sdes if (status != SSH2_FX_OK) 935221420Sdes error("Couldn't link file \"%s\" to \"%s\": %s", oldpath, 936221420Sdes newpath, fx2txt(status)); 937221420Sdes 938295367Sdes return status == SSH2_FX_OK ? 0 : -1; 939221420Sdes} 940221420Sdes 941221420Sdesint 942295367Sdesdo_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) 94376259Sgreen{ 944295367Sdes struct sshbuf *msg; 94576259Sgreen u_int status, id; 946295367Sdes int r; 94776259Sgreen 94892555Sdes if (conn->version < 3) { 94992555Sdes error("This server does not support the symlink operation"); 95092555Sdes return(SSH2_FX_OP_UNSUPPORTED); 95192555Sdes } 95292555Sdes 953295367Sdes if ((msg = sshbuf_new()) == NULL) 954295367Sdes fatal("%s: sshbuf_new failed", __func__); 95576259Sgreen 956137015Sdes /* Send symlink request */ 95792555Sdes id = conn->msg_id++; 958295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 || 959295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 960295367Sdes (r = sshbuf_put_cstring(msg, oldpath)) != 0 || 961295367Sdes (r = sshbuf_put_cstring(msg, newpath)) != 0) 962295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 963295367Sdes send_msg(conn, msg); 96476259Sgreen debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, 96576259Sgreen newpath); 966295367Sdes sshbuf_free(msg); 96776259Sgreen 968221420Sdes status = get_status(conn, id); 96976259Sgreen if (status != SSH2_FX_OK) 970113908Sdes error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath, 97192555Sdes newpath, fx2txt(status)); 97276259Sgreen 973295367Sdes return status == SSH2_FX_OK ? 0 : -1; 97476259Sgreen} 97576259Sgreen 976262566Sdesint 977295367Sdesdo_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len) 978262566Sdes{ 979295367Sdes struct sshbuf *msg; 980262566Sdes u_int status, id; 981295367Sdes int r; 982262566Sdes 983262566Sdes /* Silently return if the extension is not supported */ 984262566Sdes if ((conn->exts & SFTP_EXT_FSYNC) == 0) 985262566Sdes return -1; 986262566Sdes 987262566Sdes /* Send fsync request */ 988295367Sdes if ((msg = sshbuf_new()) == NULL) 989295367Sdes fatal("%s: sshbuf_new failed", __func__); 990262566Sdes id = conn->msg_id++; 991295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 992295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 993295367Sdes (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 || 994295367Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 995295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 996295367Sdes send_msg(conn, msg); 997262566Sdes debug3("Sent message fsync@openssh.com I:%u", id); 998295367Sdes sshbuf_free(msg); 999262566Sdes 1000262566Sdes status = get_status(conn, id); 1001262566Sdes if (status != SSH2_FX_OK) 1002262566Sdes error("Couldn't sync file: %s", fx2txt(status)); 1003262566Sdes 1004262566Sdes return status; 1005262566Sdes} 1006262566Sdes 1007181111Sdes#ifdef notyet 100876259Sgreenchar * 1009295367Sdesdo_readlink(struct sftp_conn *conn, const char *path) 101076259Sgreen{ 1011295367Sdes struct sshbuf *msg; 1012295367Sdes u_int expected_id, count, id; 101376259Sgreen char *filename, *longname; 1014295367Sdes Attrib a; 1015295367Sdes u_char type; 1016295367Sdes int r; 101776259Sgreen 101892555Sdes expected_id = id = conn->msg_id++; 1019221420Sdes send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path)); 102076259Sgreen 1021295367Sdes if ((msg = sshbuf_new()) == NULL) 1022295367Sdes fatal("%s: sshbuf_new failed", __func__); 102376259Sgreen 1024295367Sdes get_msg(conn, msg); 1025295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1026295367Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 1027295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 102876259Sgreen 102976259Sgreen if (id != expected_id) 103099060Sdes fatal("ID mismatch (%u != %u)", id, expected_id); 103176259Sgreen 103276259Sgreen if (type == SSH2_FXP_STATUS) { 1033295367Sdes u_int status; 103476259Sgreen 1035295367Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 1036295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 103776259Sgreen error("Couldn't readlink: %s", fx2txt(status)); 1038295367Sdes sshbuf_free(msg); 103976259Sgreen return(NULL); 104076259Sgreen } else if (type != SSH2_FXP_NAME) 104199060Sdes fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", 104276259Sgreen SSH2_FXP_NAME, type); 104376259Sgreen 1044295367Sdes if ((r = sshbuf_get_u32(msg, &count)) != 0) 1045295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 104676259Sgreen if (count != 1) 104776259Sgreen fatal("Got multiple names (%d) from SSH_FXP_READLINK", count); 104876259Sgreen 1049295367Sdes if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || 1050295367Sdes (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || 1051295367Sdes (r = decode_attrib(msg, &a)) != 0) 1052295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 105376259Sgreen 105476259Sgreen debug3("SSH_FXP_READLINK %s -> %s", path, filename); 105576259Sgreen 1056255767Sdes free(longname); 105776259Sgreen 1058295367Sdes sshbuf_free(msg); 105976259Sgreen 1060295367Sdes return filename; 106176259Sgreen} 1062181111Sdes#endif 106376259Sgreen 1064181111Sdesint 1065181111Sdesdo_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, 1066181111Sdes int quiet) 1067181111Sdes{ 1068295367Sdes struct sshbuf *msg; 1069181111Sdes u_int id; 1070295367Sdes int r; 1071181111Sdes 1072181111Sdes if ((conn->exts & SFTP_EXT_STATVFS) == 0) { 1073181111Sdes error("Server does not support statvfs@openssh.com extension"); 1074181111Sdes return -1; 1075181111Sdes } 1076181111Sdes 1077181111Sdes id = conn->msg_id++; 1078181111Sdes 1079295367Sdes if ((msg = sshbuf_new()) == NULL) 1080295367Sdes fatal("%s: sshbuf_new failed", __func__); 1081295367Sdes sshbuf_reset(msg); 1082295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 1083295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1084295367Sdes (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 || 1085295367Sdes (r = sshbuf_put_cstring(msg, path)) != 0) 1086295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1087295367Sdes send_msg(conn, msg); 1088295367Sdes sshbuf_free(msg); 1089181111Sdes 1090221420Sdes return get_decode_statvfs(conn, st, id, quiet); 1091181111Sdes} 1092181111Sdes 1093181111Sdes#ifdef notyet 1094181111Sdesint 1095295367Sdesdo_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, 1096181111Sdes struct sftp_statvfs *st, int quiet) 1097181111Sdes{ 1098295367Sdes struct sshbuf *msg; 1099181111Sdes u_int id; 1100181111Sdes 1101181111Sdes if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) { 1102181111Sdes error("Server does not support fstatvfs@openssh.com extension"); 1103181111Sdes return -1; 1104181111Sdes } 1105181111Sdes 1106181111Sdes id = conn->msg_id++; 1107181111Sdes 1108295367Sdes if ((msg = sshbuf_new()) == NULL) 1109295367Sdes fatal("%s: sshbuf_new failed", __func__); 1110295367Sdes sshbuf_reset(msg); 1111295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || 1112295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1113295367Sdes (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 || 1114295367Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0) 1115295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1116295367Sdes send_msg(conn, msg); 1117295367Sdes sshbuf_free(msg); 1118181111Sdes 1119221420Sdes return get_decode_statvfs(conn, st, id, quiet); 1120181111Sdes} 1121181111Sdes#endif 1122181111Sdes 112392555Sdesstatic void 1124221420Sdessend_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, 1125295367Sdes u_int len, const u_char *handle, u_int handle_len) 112692555Sdes{ 1127295367Sdes struct sshbuf *msg; 1128295367Sdes int r; 112998675Sdes 1130295367Sdes if ((msg = sshbuf_new()) == NULL) 1131295367Sdes fatal("%s: sshbuf_new failed", __func__); 1132295367Sdes sshbuf_reset(msg); 1133295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 || 1134295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1135295367Sdes (r = sshbuf_put_string(msg, handle, handle_len)) != 0 || 1136295367Sdes (r = sshbuf_put_u64(msg, offset)) != 0 || 1137295367Sdes (r = sshbuf_put_u32(msg, len)) != 0) 1138295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1139295367Sdes send_msg(conn, msg); 1140295367Sdes sshbuf_free(msg); 114198675Sdes} 114292555Sdes 114376259Sgreenint 1144295367Sdesdo_download(struct sftp_conn *conn, const char *remote_path, 1145295367Sdes const char *local_path, Attrib *a, int preserve_flag, int resume_flag, 1146295367Sdes int fsync_flag) 114776259Sgreen{ 1148204917Sdes Attrib junk; 1149295367Sdes struct sshbuf *msg; 1150295367Sdes u_char *handle; 1151295367Sdes int local_fd = -1, write_error; 1152295367Sdes int read_error, write_errno, reordered = 0, r; 1153255767Sdes u_int64_t offset = 0, size, highwater; 1154295367Sdes u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK; 1155113908Sdes off_t progress_counter; 1156295367Sdes size_t handle_len; 1157255767Sdes struct stat st; 115892555Sdes struct request { 115992555Sdes u_int id; 1160295367Sdes size_t len; 116192555Sdes u_int64_t offset; 116298675Sdes TAILQ_ENTRY(request) tq; 116392555Sdes }; 116492555Sdes TAILQ_HEAD(reqhead, request) requests; 116592555Sdes struct request *req; 1166295367Sdes u_char type; 116776259Sgreen 116892555Sdes TAILQ_INIT(&requests); 116992555Sdes 1170204917Sdes if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL) 1171204917Sdes return -1; 117276259Sgreen 1173181111Sdes /* Do not preserve set[ug]id here, as we do not preserve ownership */ 117476259Sgreen if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 1175113908Sdes mode = a->perm & 0777; 117676259Sgreen else 117776259Sgreen mode = 0666; 117876259Sgreen 117976259Sgreen if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 1180113908Sdes (!S_ISREG(a->perm))) { 1181113908Sdes error("Cannot download non-regular file: %s", remote_path); 118276259Sgreen return(-1); 118376259Sgreen } 118476259Sgreen 118592555Sdes if (a->flags & SSH2_FILEXFER_ATTR_SIZE) 118692555Sdes size = a->size; 118792555Sdes else 118892555Sdes size = 0; 118976259Sgreen 119092555Sdes buflen = conn->transfer_buflen; 1191295367Sdes if ((msg = sshbuf_new()) == NULL) 1192295367Sdes fatal("%s: sshbuf_new failed", __func__); 119376259Sgreen 1194295367Sdes attrib_clear(&junk); /* Send empty attributes */ 1195295367Sdes 119676259Sgreen /* Send open request */ 119792555Sdes id = conn->msg_id++; 1198295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || 1199295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1200295367Sdes (r = sshbuf_put_cstring(msg, remote_path)) != 0 || 1201295367Sdes (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 || 1202295367Sdes (r = encode_attrib(msg, &junk)) != 0) 1203295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1204295367Sdes send_msg(conn, msg); 120599060Sdes debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 120676259Sgreen 1207221420Sdes handle = get_handle(conn, id, &handle_len, 1208204917Sdes "remote open(\"%s\")", remote_path); 120976259Sgreen if (handle == NULL) { 1210295367Sdes sshbuf_free(msg); 121176259Sgreen return(-1); 121276259Sgreen } 121376259Sgreen 1214262566Sdes local_fd = open(local_path, 1215262566Sdes O_WRONLY | O_CREAT | (resume_flag ? 0 : O_TRUNC), mode | S_IWUSR); 121692555Sdes if (local_fd == -1) { 121792555Sdes error("Couldn't open local file \"%s\" for writing: %s", 121892555Sdes local_path, strerror(errno)); 1219255767Sdes goto fail; 122092555Sdes } 1221255767Sdes offset = highwater = 0; 1222262566Sdes if (resume_flag) { 1223255767Sdes if (fstat(local_fd, &st) == -1) { 1224255767Sdes error("Unable to stat local file \"%s\": %s", 1225255767Sdes local_path, strerror(errno)); 1226255767Sdes goto fail; 1227255767Sdes } 1228262566Sdes if (st.st_size < 0) { 1229262566Sdes error("\"%s\" has negative size", local_path); 1230262566Sdes goto fail; 1231262566Sdes } 1232262566Sdes if ((u_int64_t)st.st_size > size) { 1233255767Sdes error("Unable to resume download of \"%s\": " 1234255767Sdes "local file is larger than remote", local_path); 1235255767Sdes fail: 1236255767Sdes do_close(conn, handle, handle_len); 1237295367Sdes sshbuf_free(msg); 1238255767Sdes free(handle); 1239262566Sdes if (local_fd != -1) 1240262566Sdes close(local_fd); 1241255767Sdes return -1; 1242255767Sdes } 1243255767Sdes offset = highwater = st.st_size; 1244255767Sdes } 124592555Sdes 124676259Sgreen /* Read from remote and write to local */ 1247255767Sdes write_error = read_error = write_errno = num_req = 0; 124892555Sdes max_req = 1; 1249255767Sdes progress_counter = offset; 1250113908Sdes 1251128456Sdes if (showprogress && size != 0) 1252128456Sdes start_progress_meter(remote_path, size, &progress_counter); 1253113908Sdes 125492555Sdes while (num_req > 0 || max_req > 0) { 1255295367Sdes u_char *data; 1256295367Sdes size_t len; 125776259Sgreen 1258137015Sdes /* 1259137015Sdes * Simulate EOF on interrupt: stop sending new requests and 1260137015Sdes * allow outstanding requests to drain gracefully 1261137015Sdes */ 1262137015Sdes if (interrupted) { 1263137015Sdes if (num_req == 0) /* If we haven't started yet... */ 1264137015Sdes break; 1265137015Sdes max_req = 0; 1266137015Sdes } 1267137015Sdes 126892555Sdes /* Send some more requests */ 126992555Sdes while (num_req < max_req) { 127098675Sdes debug3("Request range %llu -> %llu (%d/%d)", 127198675Sdes (unsigned long long)offset, 127298675Sdes (unsigned long long)offset + buflen - 1, 127398675Sdes num_req, max_req); 1274258343Sdes req = xcalloc(1, sizeof(*req)); 127592555Sdes req->id = conn->msg_id++; 127692555Sdes req->len = buflen; 127792555Sdes req->offset = offset; 127892555Sdes offset += buflen; 127992555Sdes num_req++; 128092555Sdes TAILQ_INSERT_TAIL(&requests, req, tq); 1281221420Sdes send_read_request(conn, req->id, req->offset, 128292555Sdes req->len, handle, handle_len); 128392555Sdes } 128476259Sgreen 1285295367Sdes sshbuf_reset(msg); 1286295367Sdes get_msg(conn, msg); 1287295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1288295367Sdes (r = sshbuf_get_u32(msg, &id)) != 0) 1289295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 129099060Sdes debug3("Received reply T:%u I:%u R:%d", type, id, max_req); 129176259Sgreen 129292555Sdes /* Find the request in our queue */ 1293147001Sdes for (req = TAILQ_FIRST(&requests); 129492555Sdes req != NULL && req->id != id; 129592555Sdes req = TAILQ_NEXT(req, tq)) 129692555Sdes ; 129792555Sdes if (req == NULL) 129892555Sdes fatal("Unexpected reply %u", id); 129976259Sgreen 130092555Sdes switch (type) { 130192555Sdes case SSH2_FXP_STATUS: 1302295367Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 1303295367Sdes fatal("%s: buffer error: %s", 1304295367Sdes __func__, ssh_err(r)); 130592555Sdes if (status != SSH2_FX_EOF) 130692555Sdes read_error = 1; 130792555Sdes max_req = 0; 130892555Sdes TAILQ_REMOVE(&requests, req, tq); 1309255767Sdes free(req); 131092555Sdes num_req--; 131192555Sdes break; 131292555Sdes case SSH2_FXP_DATA: 1313295367Sdes if ((r = sshbuf_get_string(msg, &data, &len)) != 0) 1314295367Sdes fatal("%s: buffer error: %s", 1315295367Sdes __func__, ssh_err(r)); 131698675Sdes debug3("Received data %llu -> %llu", 131798675Sdes (unsigned long long)req->offset, 131898675Sdes (unsigned long long)req->offset + len - 1); 131992555Sdes if (len > req->len) 132092555Sdes fatal("Received more data than asked for " 1321295367Sdes "%zu > %zu", len, req->len); 132292555Sdes if ((lseek(local_fd, req->offset, SEEK_SET) == -1 || 1323124208Sdes atomicio(vwrite, local_fd, data, len) != len) && 132492555Sdes !write_error) { 132592555Sdes write_errno = errno; 132692555Sdes write_error = 1; 132792555Sdes max_req = 0; 132892555Sdes } 1329255767Sdes else if (!reordered && req->offset <= highwater) 1330255767Sdes highwater = req->offset + len; 1331255767Sdes else if (!reordered && req->offset > highwater) 1332255767Sdes reordered = 1; 1333113908Sdes progress_counter += len; 1334255767Sdes free(data); 133576259Sgreen 133692555Sdes if (len == req->len) { 133792555Sdes TAILQ_REMOVE(&requests, req, tq); 1338255767Sdes free(req); 133992555Sdes num_req--; 134092555Sdes } else { 134192555Sdes /* Resend the request for the missing data */ 134292555Sdes debug3("Short data block, re-requesting " 134398675Sdes "%llu -> %llu (%2d)", 134498675Sdes (unsigned long long)req->offset + len, 134598675Sdes (unsigned long long)req->offset + 134698675Sdes req->len - 1, num_req); 134792555Sdes req->id = conn->msg_id++; 134892555Sdes req->len -= len; 134992555Sdes req->offset += len; 1350221420Sdes send_read_request(conn, req->id, 135192555Sdes req->offset, req->len, handle, handle_len); 135292555Sdes /* Reduce the request size */ 135392555Sdes if (len < buflen) 135492555Sdes buflen = MAX(MIN_READ_SIZE, len); 135576259Sgreen } 135692555Sdes if (max_req > 0) { /* max_req = 0 iff EOF received */ 135792555Sdes if (size > 0 && offset > size) { 135892555Sdes /* Only one request at a time 135992555Sdes * after the expected EOF */ 136092555Sdes debug3("Finish at %llu (%2d)", 136198675Sdes (unsigned long long)offset, 136298675Sdes num_req); 136392555Sdes max_req = 1; 1364137015Sdes } else if (max_req <= conn->num_requests) { 136592555Sdes ++max_req; 136692555Sdes } 136792555Sdes } 136892555Sdes break; 136992555Sdes default: 137099060Sdes fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", 137176259Sgreen SSH2_FXP_DATA, type); 137276259Sgreen } 137392555Sdes } 137476259Sgreen 1375113908Sdes if (showprogress && size) 1376113908Sdes stop_progress_meter(); 1377113908Sdes 137892555Sdes /* Sanity check */ 137992555Sdes if (TAILQ_FIRST(&requests) != NULL) 138092555Sdes fatal("Transfer complete, but requests still in queue"); 1381255767Sdes /* Truncate at highest contiguous point to avoid holes on interrupt */ 1382255767Sdes if (read_error || write_error || interrupted) { 1383262566Sdes if (reordered && resume_flag) { 1384255767Sdes error("Unable to resume download of \"%s\": " 1385255767Sdes "server reordered requests", local_path); 1386255767Sdes } 1387255767Sdes debug("truncating at %llu", (unsigned long long)highwater); 1388295367Sdes if (ftruncate(local_fd, highwater) == -1) 1389295367Sdes error("ftruncate \"%s\": %s", local_path, 1390295367Sdes strerror(errno)); 1391255767Sdes } 139292555Sdes if (read_error) { 139398675Sdes error("Couldn't read from remote file \"%s\" : %s", 139492555Sdes remote_path, fx2txt(status)); 1395262566Sdes status = -1; 139692555Sdes do_close(conn, handle, handle_len); 139792555Sdes } else if (write_error) { 139892555Sdes error("Couldn't write to \"%s\": %s", local_path, 139992555Sdes strerror(write_errno)); 1400295367Sdes status = SSH2_FX_FAILURE; 140192555Sdes do_close(conn, handle, handle_len); 140292555Sdes } else { 1403295367Sdes if (do_close(conn, handle, handle_len) != 0 || interrupted) 1404295367Sdes status = SSH2_FX_FAILURE; 1405295367Sdes else 1406295367Sdes status = SSH2_FX_OK; 140792555Sdes /* Override umask and utimes if asked */ 140898937Sdes#ifdef HAVE_FCHMOD 1409262566Sdes if (preserve_flag && fchmod(local_fd, mode) == -1) 1410126274Sdes#else 1411262566Sdes if (preserve_flag && chmod(local_path, mode) == -1) 141298937Sdes#endif /* HAVE_FCHMOD */ 141392555Sdes error("Couldn't set mode on \"%s\": %s", local_path, 1414113908Sdes strerror(errno)); 1415262566Sdes if (preserve_flag && 1416262566Sdes (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { 141792555Sdes struct timeval tv[2]; 141892555Sdes tv[0].tv_sec = a->atime; 141992555Sdes tv[1].tv_sec = a->mtime; 142092555Sdes tv[0].tv_usec = tv[1].tv_usec = 0; 142192555Sdes if (utimes(local_path, tv) == -1) 142292555Sdes error("Can't set times on \"%s\": %s", 1423113908Sdes local_path, strerror(errno)); 142476259Sgreen } 1425262566Sdes if (fsync_flag) { 1426262566Sdes debug("syncing \"%s\"", local_path); 1427262566Sdes if (fsync(local_fd) == -1) 1428262566Sdes error("Couldn't sync file \"%s\": %s", 1429262566Sdes local_path, strerror(errno)); 1430262566Sdes } 143176259Sgreen } 143276259Sgreen close(local_fd); 1433295367Sdes sshbuf_free(msg); 1434255767Sdes free(handle); 143592555Sdes 143692555Sdes return(status); 143776259Sgreen} 143876259Sgreen 1439204917Sdesstatic int 1440295367Sdesdownload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, 1441295367Sdes int depth, Attrib *dirattrib, int preserve_flag, int print_flag, 1442295367Sdes int resume_flag, int fsync_flag) 1443204917Sdes{ 1444204917Sdes int i, ret = 0; 1445204917Sdes SFTP_DIRENT **dir_entries; 1446204917Sdes char *filename, *new_src, *new_dst; 1447204917Sdes mode_t mode = 0777; 1448204917Sdes 1449204917Sdes if (depth >= MAX_DIR_DEPTH) { 1450204917Sdes error("Maximum directory depth exceeded: %d levels", depth); 1451204917Sdes return -1; 1452204917Sdes } 1453204917Sdes 1454204917Sdes if (dirattrib == NULL && 1455204917Sdes (dirattrib = do_stat(conn, src, 1)) == NULL) { 1456204917Sdes error("Unable to stat remote directory \"%s\"", src); 1457204917Sdes return -1; 1458204917Sdes } 1459204917Sdes if (!S_ISDIR(dirattrib->perm)) { 1460204917Sdes error("\"%s\" is not a directory", src); 1461204917Sdes return -1; 1462204917Sdes } 1463262566Sdes if (print_flag) 1464204917Sdes printf("Retrieving %s\n", src); 1465204917Sdes 1466204917Sdes if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 1467204917Sdes mode = dirattrib->perm & 01777; 1468204917Sdes else { 1469204917Sdes debug("Server did not send permissions for " 1470204917Sdes "directory \"%s\"", dst); 1471204917Sdes } 1472204917Sdes 1473204917Sdes if (mkdir(dst, mode) == -1 && errno != EEXIST) { 1474204917Sdes error("mkdir %s: %s", dst, strerror(errno)); 1475204917Sdes return -1; 1476204917Sdes } 1477204917Sdes 1478204917Sdes if (do_readdir(conn, src, &dir_entries) == -1) { 1479204917Sdes error("%s: Failed to get directory contents", src); 1480204917Sdes return -1; 1481204917Sdes } 1482204917Sdes 1483204917Sdes for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { 1484204917Sdes filename = dir_entries[i]->filename; 1485204917Sdes 1486204917Sdes new_dst = path_append(dst, filename); 1487204917Sdes new_src = path_append(src, filename); 1488204917Sdes 1489204917Sdes if (S_ISDIR(dir_entries[i]->a.perm)) { 1490204917Sdes if (strcmp(filename, ".") == 0 || 1491204917Sdes strcmp(filename, "..") == 0) 1492204917Sdes continue; 1493204917Sdes if (download_dir_internal(conn, new_src, new_dst, 1494262566Sdes depth + 1, &(dir_entries[i]->a), preserve_flag, 1495262566Sdes print_flag, resume_flag, fsync_flag) == -1) 1496204917Sdes ret = -1; 1497204917Sdes } else if (S_ISREG(dir_entries[i]->a.perm) ) { 1498204917Sdes if (do_download(conn, new_src, new_dst, 1499262566Sdes &(dir_entries[i]->a), preserve_flag, 1500262566Sdes resume_flag, fsync_flag) == -1) { 1501204917Sdes error("Download of file %s to %s failed", 1502204917Sdes new_src, new_dst); 1503204917Sdes ret = -1; 1504204917Sdes } 1505204917Sdes } else 1506204917Sdes logit("%s: not a regular file\n", new_src); 1507204917Sdes 1508255767Sdes free(new_dst); 1509255767Sdes free(new_src); 1510204917Sdes } 1511204917Sdes 1512262566Sdes if (preserve_flag) { 1513204917Sdes if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { 1514204917Sdes struct timeval tv[2]; 1515204917Sdes tv[0].tv_sec = dirattrib->atime; 1516204917Sdes tv[1].tv_sec = dirattrib->mtime; 1517204917Sdes tv[0].tv_usec = tv[1].tv_usec = 0; 1518204917Sdes if (utimes(dst, tv) == -1) 1519204917Sdes error("Can't set times on \"%s\": %s", 1520204917Sdes dst, strerror(errno)); 1521204917Sdes } else 1522204917Sdes debug("Server did not send times for directory " 1523204917Sdes "\"%s\"", dst); 1524204917Sdes } 1525204917Sdes 1526204917Sdes free_sftp_dirents(dir_entries); 1527204917Sdes 1528204917Sdes return ret; 1529204917Sdes} 1530204917Sdes 153176259Sgreenint 1532295367Sdesdownload_dir(struct sftp_conn *conn, const char *src, const char *dst, 1533295367Sdes Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag, 1534295367Sdes int fsync_flag) 1535204917Sdes{ 1536204917Sdes char *src_canon; 1537204917Sdes int ret; 1538204917Sdes 1539204917Sdes if ((src_canon = do_realpath(conn, src)) == NULL) { 1540262566Sdes error("Unable to canonicalize path \"%s\"", src); 1541204917Sdes return -1; 1542204917Sdes } 1543204917Sdes 1544262566Sdes ret = download_dir_internal(conn, src_canon, dst, 0, 1545262566Sdes dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag); 1546255767Sdes free(src_canon); 1547204917Sdes return ret; 1548204917Sdes} 1549204917Sdes 1550204917Sdesint 1551295367Sdesdo_upload(struct sftp_conn *conn, const char *local_path, 1552295367Sdes const char *remote_path, int preserve_flag, int resume, int fsync_flag) 155376259Sgreen{ 1554295367Sdes int r, local_fd; 1555295367Sdes u_int status = SSH2_FX_OK; 1556295367Sdes u_int id; 1557295367Sdes u_char type; 1558255767Sdes off_t offset, progress_counter; 1559295367Sdes u_char *handle, *data; 1560295367Sdes struct sshbuf *msg; 156176259Sgreen struct stat sb; 1562295367Sdes Attrib a, *c = NULL; 156392555Sdes u_int32_t startid; 156492555Sdes u_int32_t ackid; 156592555Sdes struct outstanding_ack { 156692555Sdes u_int id; 156792555Sdes u_int len; 1568181111Sdes off_t offset; 156998675Sdes TAILQ_ENTRY(outstanding_ack) tq; 157092555Sdes }; 157192555Sdes TAILQ_HEAD(ackhead, outstanding_ack) acks; 1572137015Sdes struct outstanding_ack *ack = NULL; 1573295367Sdes size_t handle_len; 157476259Sgreen 157592555Sdes TAILQ_INIT(&acks); 157692555Sdes 157776259Sgreen if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) { 157876259Sgreen error("Couldn't open local file \"%s\" for reading: %s", 157976259Sgreen local_path, strerror(errno)); 158076259Sgreen return(-1); 158176259Sgreen } 158276259Sgreen if (fstat(local_fd, &sb) == -1) { 158376259Sgreen error("Couldn't fstat local file \"%s\": %s", 158476259Sgreen local_path, strerror(errno)); 158576259Sgreen close(local_fd); 158676259Sgreen return(-1); 158776259Sgreen } 1588113908Sdes if (!S_ISREG(sb.st_mode)) { 1589113908Sdes error("%s is not a regular file", local_path); 1590113908Sdes close(local_fd); 1591113908Sdes return(-1); 1592113908Sdes } 159376259Sgreen stat_to_attrib(&sb, &a); 159476259Sgreen 159576259Sgreen a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 159676259Sgreen a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 159776259Sgreen a.perm &= 0777; 1598262566Sdes if (!preserve_flag) 159976259Sgreen a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 160076259Sgreen 1601295367Sdes if (resume) { 1602295367Sdes /* Get remote file size if it exists */ 1603295367Sdes if ((c = do_stat(conn, remote_path, 0)) == NULL) { 1604295367Sdes close(local_fd); 1605295367Sdes return -1; 1606295367Sdes } 160776259Sgreen 1608295367Sdes if ((off_t)c->size >= sb.st_size) { 1609295367Sdes error("destination file bigger or same size as " 1610295367Sdes "source file"); 1611295367Sdes close(local_fd); 1612295367Sdes return -1; 1613295367Sdes } 1614295367Sdes 1615295367Sdes if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) { 1616295367Sdes close(local_fd); 1617295367Sdes return -1; 1618295367Sdes } 1619295367Sdes } 1620295367Sdes 1621295367Sdes if ((msg = sshbuf_new()) == NULL) 1622295367Sdes fatal("%s: sshbuf_new failed", __func__); 1623295367Sdes 162476259Sgreen /* Send open request */ 162592555Sdes id = conn->msg_id++; 1626295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || 1627295367Sdes (r = sshbuf_put_u32(msg, id)) != 0 || 1628295367Sdes (r = sshbuf_put_cstring(msg, remote_path)) != 0 || 1629295367Sdes (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| 1630295367Sdes (resume ? SSH2_FXF_APPEND : SSH2_FXF_TRUNC))) != 0 || 1631295367Sdes (r = encode_attrib(msg, &a)) != 0) 1632295367Sdes fatal("%s: buffer error: %s", __func__, ssh_err(r)); 1633295367Sdes send_msg(conn, msg); 163499060Sdes debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); 163576259Sgreen 1636295367Sdes sshbuf_reset(msg); 163776259Sgreen 1638221420Sdes handle = get_handle(conn, id, &handle_len, 1639204917Sdes "remote open(\"%s\")", remote_path); 164076259Sgreen if (handle == NULL) { 164176259Sgreen close(local_fd); 1642295367Sdes sshbuf_free(msg); 1643181111Sdes return -1; 164476259Sgreen } 164576259Sgreen 164692555Sdes startid = ackid = id + 1; 164792555Sdes data = xmalloc(conn->transfer_buflen); 164892555Sdes 164976259Sgreen /* Read from local and write to remote */ 1650295367Sdes offset = progress_counter = (resume ? c->size : 0); 1651113908Sdes if (showprogress) 1652255767Sdes start_progress_meter(local_path, sb.st_size, 1653255767Sdes &progress_counter); 1654113908Sdes 165592555Sdes for (;;) { 165676259Sgreen int len; 165776259Sgreen 165876259Sgreen /* 1659137015Sdes * Can't use atomicio here because it returns 0 on EOF, 1660137015Sdes * thus losing the last block of the file. 1661137015Sdes * Simulate an EOF on interrupt, allowing ACKs from the 1662137015Sdes * server to drain. 166376259Sgreen */ 1664181111Sdes if (interrupted || status != SSH2_FX_OK) 1665137015Sdes len = 0; 1666137015Sdes else do 166792555Sdes len = read(local_fd, data, conn->transfer_buflen); 1668181111Sdes while ((len == -1) && 1669181111Sdes (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); 167076259Sgreen 167176259Sgreen if (len == -1) 167276259Sgreen fatal("Couldn't read from \"%s\": %s", local_path, 167376259Sgreen strerror(errno)); 167492555Sdes 167592555Sdes if (len != 0) { 1676258343Sdes ack = xcalloc(1, sizeof(*ack)); 167792555Sdes ack->id = ++id; 167892555Sdes ack->offset = offset; 167992555Sdes ack->len = len; 168092555Sdes TAILQ_INSERT_TAIL(&acks, ack, tq); 168192555Sdes 1682295367Sdes sshbuf_reset(msg); 1683295367Sdes if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 || 1684295367Sdes (r = sshbuf_put_u32(msg, ack->id)) != 0 || 1685295367Sdes (r = sshbuf_put_string(msg, handle, 1686295367Sdes handle_len)) != 0 || 1687295367Sdes (r = sshbuf_put_u64(msg, offset)) != 0 || 1688295367Sdes (r = sshbuf_put_string(msg, data, len)) != 0) 1689295367Sdes fatal("%s: buffer error: %s", 1690295367Sdes __func__, ssh_err(r)); 1691295367Sdes send_msg(conn, msg); 169299060Sdes debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u", 1693113908Sdes id, (unsigned long long)offset, len); 169492555Sdes } else if (TAILQ_FIRST(&acks) == NULL) 169576259Sgreen break; 169676259Sgreen 169792555Sdes if (ack == NULL) 169892555Sdes fatal("Unexpected ACK %u", id); 169976259Sgreen 170098675Sdes if (id == startid || len == 0 || 170192555Sdes id - ackid >= conn->num_requests) { 1702295367Sdes u_int rid; 170398675Sdes 1704295367Sdes sshbuf_reset(msg); 1705295367Sdes get_msg(conn, msg); 1706295367Sdes if ((r = sshbuf_get_u8(msg, &type)) != 0 || 1707295367Sdes (r = sshbuf_get_u32(msg, &rid)) != 0) 1708295367Sdes fatal("%s: buffer error: %s", 1709295367Sdes __func__, ssh_err(r)); 171092555Sdes 171192555Sdes if (type != SSH2_FXP_STATUS) 171292555Sdes fatal("Expected SSH2_FXP_STATUS(%d) packet, " 171392555Sdes "got %d", SSH2_FXP_STATUS, type); 171492555Sdes 1715295367Sdes if ((r = sshbuf_get_u32(msg, &status)) != 0) 1716295367Sdes fatal("%s: buffer error: %s", 1717295367Sdes __func__, ssh_err(r)); 1718295367Sdes debug3("SSH2_FXP_STATUS %u", status); 171992555Sdes 172092555Sdes /* Find the request in our queue */ 1721147001Sdes for (ack = TAILQ_FIRST(&acks); 1722295367Sdes ack != NULL && ack->id != rid; 172392555Sdes ack = TAILQ_NEXT(ack, tq)) 172492555Sdes ; 172592555Sdes if (ack == NULL) 1726295367Sdes fatal("Can't find request for ID %u", rid); 172792555Sdes TAILQ_REMOVE(&acks, ack, tq); 1728181111Sdes debug3("In write loop, ack for %u %u bytes at %lld", 1729181111Sdes ack->id, ack->len, (long long)ack->offset); 173092555Sdes ++ackid; 1731255767Sdes progress_counter += ack->len; 1732255767Sdes free(ack); 173376259Sgreen } 173476259Sgreen offset += len; 1735181111Sdes if (offset < 0) 1736181111Sdes fatal("%s: offset < 0", __func__); 173776259Sgreen } 1738295367Sdes sshbuf_free(msg); 1739181111Sdes 1740113908Sdes if (showprogress) 1741113908Sdes stop_progress_meter(); 1742255767Sdes free(data); 174376259Sgreen 1744181111Sdes if (status != SSH2_FX_OK) { 1745181111Sdes error("Couldn't write to remote file \"%s\": %s", 1746181111Sdes remote_path, fx2txt(status)); 1747295367Sdes status = SSH2_FX_FAILURE; 1748181111Sdes } 1749181111Sdes 175076259Sgreen if (close(local_fd) == -1) { 175176259Sgreen error("Couldn't close local file \"%s\": %s", local_path, 175276259Sgreen strerror(errno)); 1753295367Sdes status = SSH2_FX_FAILURE; 175476259Sgreen } 175576259Sgreen 175676259Sgreen /* Override umask and utimes if asked */ 1757262566Sdes if (preserve_flag) 175892555Sdes do_fsetstat(conn, handle, handle_len, &a); 175976259Sgreen 1760262566Sdes if (fsync_flag) 1761262566Sdes (void)do_fsync(conn, handle, handle_len); 1762262566Sdes 1763296853Sdes if (do_close(conn, handle, handle_len) != 0) 1764295367Sdes status = SSH2_FX_FAILURE; 1765295367Sdes 1766255767Sdes free(handle); 176776259Sgreen 1768295367Sdes return status == SSH2_FX_OK ? 0 : -1; 176976259Sgreen} 1770204917Sdes 1771204917Sdesstatic int 1772295367Sdesupload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, 1773295367Sdes int depth, int preserve_flag, int print_flag, int resume, int fsync_flag) 1774204917Sdes{ 1775295367Sdes int ret = 0; 1776204917Sdes DIR *dirp; 1777204917Sdes struct dirent *dp; 1778204917Sdes char *filename, *new_src, *new_dst; 1779204917Sdes struct stat sb; 1780296853Sdes Attrib a, *dirattrib; 1781204917Sdes 1782204917Sdes if (depth >= MAX_DIR_DEPTH) { 1783204917Sdes error("Maximum directory depth exceeded: %d levels", depth); 1784204917Sdes return -1; 1785204917Sdes } 1786204917Sdes 1787204917Sdes if (stat(src, &sb) == -1) { 1788204917Sdes error("Couldn't stat directory \"%s\": %s", 1789204917Sdes src, strerror(errno)); 1790204917Sdes return -1; 1791204917Sdes } 1792204917Sdes if (!S_ISDIR(sb.st_mode)) { 1793204917Sdes error("\"%s\" is not a directory", src); 1794204917Sdes return -1; 1795204917Sdes } 1796262566Sdes if (print_flag) 1797204917Sdes printf("Entering %s\n", src); 1798204917Sdes 1799204917Sdes attrib_clear(&a); 1800204917Sdes stat_to_attrib(&sb, &a); 1801204917Sdes a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; 1802204917Sdes a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; 1803204917Sdes a.perm &= 01777; 1804262566Sdes if (!preserve_flag) 1805204917Sdes a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; 1806255767Sdes 1807204917Sdes /* 1808296853Sdes * sftp lacks a portable status value to match errno EEXIST, 1809296853Sdes * so if we get a failure back then we must check whether 1810296853Sdes * the path already existed and is a directory. 1811204917Sdes */ 1812296853Sdes if (do_mkdir(conn, dst, &a, 0) != 0) { 1813296853Sdes if ((dirattrib = do_stat(conn, dst, 0)) == NULL) 1814204917Sdes return -1; 1815296853Sdes if (!S_ISDIR(dirattrib->perm)) { 1816296853Sdes error("\"%s\" exists but is not a directory", dst); 1817204917Sdes return -1; 1818296853Sdes } 1819204917Sdes } 1820204917Sdes 1821204917Sdes if ((dirp = opendir(src)) == NULL) { 1822204917Sdes error("Failed to open dir \"%s\": %s", src, strerror(errno)); 1823204917Sdes return -1; 1824204917Sdes } 1825255767Sdes 1826204917Sdes while (((dp = readdir(dirp)) != NULL) && !interrupted) { 1827204917Sdes if (dp->d_ino == 0) 1828204917Sdes continue; 1829204917Sdes filename = dp->d_name; 1830204917Sdes new_dst = path_append(dst, filename); 1831204917Sdes new_src = path_append(src, filename); 1832204917Sdes 1833204917Sdes if (lstat(new_src, &sb) == -1) { 1834204917Sdes logit("%s: lstat failed: %s", filename, 1835204917Sdes strerror(errno)); 1836204917Sdes ret = -1; 1837204917Sdes } else if (S_ISDIR(sb.st_mode)) { 1838204917Sdes if (strcmp(filename, ".") == 0 || 1839204917Sdes strcmp(filename, "..") == 0) 1840204917Sdes continue; 1841204917Sdes 1842204917Sdes if (upload_dir_internal(conn, new_src, new_dst, 1843295367Sdes depth + 1, preserve_flag, print_flag, resume, 1844262566Sdes fsync_flag) == -1) 1845204917Sdes ret = -1; 1846204917Sdes } else if (S_ISREG(sb.st_mode)) { 1847262566Sdes if (do_upload(conn, new_src, new_dst, 1848295367Sdes preserve_flag, resume, fsync_flag) == -1) { 1849204917Sdes error("Uploading of file %s to %s failed!", 1850204917Sdes new_src, new_dst); 1851204917Sdes ret = -1; 1852204917Sdes } 1853204917Sdes } else 1854204917Sdes logit("%s: not a regular file\n", filename); 1855255767Sdes free(new_dst); 1856255767Sdes free(new_src); 1857204917Sdes } 1858204917Sdes 1859204917Sdes do_setstat(conn, dst, &a); 1860204917Sdes 1861204917Sdes (void) closedir(dirp); 1862204917Sdes return ret; 1863204917Sdes} 1864204917Sdes 1865204917Sdesint 1866295367Sdesupload_dir(struct sftp_conn *conn, const char *src, const char *dst, 1867295367Sdes int preserve_flag, int print_flag, int resume, int fsync_flag) 1868204917Sdes{ 1869204917Sdes char *dst_canon; 1870204917Sdes int ret; 1871204917Sdes 1872204917Sdes if ((dst_canon = do_realpath(conn, dst)) == NULL) { 1873262566Sdes error("Unable to canonicalize path \"%s\"", dst); 1874204917Sdes return -1; 1875204917Sdes } 1876204917Sdes 1877262566Sdes ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag, 1878295367Sdes print_flag, resume, fsync_flag); 1879262566Sdes 1880255767Sdes free(dst_canon); 1881204917Sdes return ret; 1882204917Sdes} 1883204917Sdes 1884204917Sdeschar * 1885295367Sdespath_append(const char *p1, const char *p2) 1886204917Sdes{ 1887204917Sdes char *ret; 1888204917Sdes size_t len = strlen(p1) + strlen(p2) + 2; 1889204917Sdes 1890204917Sdes ret = xmalloc(len); 1891204917Sdes strlcpy(ret, p1, len); 1892204917Sdes if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') 1893204917Sdes strlcat(ret, "/", len); 1894204917Sdes strlcat(ret, p2, len); 1895204917Sdes 1896204917Sdes return(ret); 1897204917Sdes} 1898204917Sdes 1899