version 1.172, 2023/09/08 05:50:12 |
version 1.173, 2023/09/08 05:56:13 |
|
|
return handle; |
return handle; |
} |
} |
|
|
/* XXX returning &static is error-prone. Refactor to fill *Attrib argument */ |
static int |
static Attrib * |
get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet, Attrib *a) |
get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet) |
|
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
u_int id; |
u_int id; |
u_char type; |
u_char type; |
int r; |
int r; |
static Attrib a; |
Attrib attr; |
|
|
|
if (a != NULL) |
|
memset(a, '\0', sizeof(*a)); |
if ((msg = sshbuf_new()) == NULL) |
if ((msg = sshbuf_new()) == NULL) |
fatal_f("sshbuf_new failed"); |
fatal_f("sshbuf_new failed"); |
get_msg(conn, msg); |
get_msg(conn, msg); |
|
|
else |
else |
error("stat remote: %s", fx2txt(status)); |
error("stat remote: %s", fx2txt(status)); |
sshbuf_free(msg); |
sshbuf_free(msg); |
return(NULL); |
return -1; |
} else if (type != SSH2_FXP_ATTRS) { |
} else if (type != SSH2_FXP_ATTRS) { |
fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", |
fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", |
SSH2_FXP_ATTRS, type); |
SSH2_FXP_ATTRS, type); |
} |
} |
if ((r = decode_attrib(msg, &a)) != 0) { |
if ((r = decode_attrib(msg, &attr)) != 0) { |
error_fr(r, "decode_attrib"); |
error_fr(r, "decode_attrib"); |
sshbuf_free(msg); |
sshbuf_free(msg); |
return NULL; |
return -1; |
} |
} |
|
/* success */ |
|
if (a != NULL) |
|
*a = attr; |
debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o", |
debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o", |
type, id, a.flags, a.perm); |
type, id, attr.flags, attr.perm); |
sshbuf_free(msg); |
sshbuf_free(msg); |
|
|
return &a; |
return 0; |
} |
} |
|
|
static int |
static int |
|
|
} |
} |
|
|
struct sftp_conn * |
struct sftp_conn * |
do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, |
sftp_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, |
u_int64_t limit_kbps) |
u_int64_t limit_kbps) |
{ |
{ |
u_char type; |
u_char type; |
|
|
/* Query the server for its limits */ |
/* Query the server for its limits */ |
if (ret->exts & SFTP_EXT_LIMITS) { |
if (ret->exts & SFTP_EXT_LIMITS) { |
struct sftp_limits limits; |
struct sftp_limits limits; |
if (do_limits(ret, &limits) != 0) |
if (sftp_get_limits(ret, &limits) != 0) |
fatal_f("limits failed"); |
fatal_f("limits failed"); |
|
|
/* If the caller did not specify, find a good value */ |
/* If the caller did not specify, find a good value */ |
|
|
} |
} |
|
|
int |
int |
do_limits(struct sftp_conn *conn, struct sftp_limits *limits) |
sftp_get_limits(struct sftp_conn *conn, struct sftp_limits *limits) |
{ |
{ |
u_int id, msg_id; |
u_int id, msg_id; |
u_char type; |
u_char type; |
|
|
} |
} |
|
|
int |
int |
do_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) |
sftp_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) |
{ |
{ |
u_int id, status; |
u_int id, status; |
struct sshbuf *msg; |
struct sshbuf *msg; |
|
|
|
|
|
|
static int |
static int |
do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag, |
sftp_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag, |
SFTP_DIRENT ***dir) |
SFTP_DIRENT ***dir) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
|
|
|
|
out: |
out: |
sshbuf_free(msg); |
sshbuf_free(msg); |
do_close(conn, handle, handle_len); |
sftp_close(conn, handle, handle_len); |
free(handle); |
free(handle); |
|
|
if (status != 0 && dir != NULL) { |
if (status != 0 && dir != NULL) { |
/* Don't return results on error */ |
/* Don't return results on error */ |
free_sftp_dirents(*dir); |
sftp_free_dirents(*dir); |
*dir = NULL; |
*dir = NULL; |
} else if (interrupted && dir != NULL && *dir != NULL) { |
} else if (interrupted && dir != NULL && *dir != NULL) { |
/* Don't return partial matches on interrupt */ |
/* Don't return partial matches on interrupt */ |
free_sftp_dirents(*dir); |
sftp_free_dirents(*dir); |
*dir = xcalloc(1, sizeof(**dir)); |
*dir = xcalloc(1, sizeof(**dir)); |
**dir = NULL; |
**dir = NULL; |
} |
} |
|
|
} |
} |
|
|
int |
int |
do_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir) |
sftp_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir) |
{ |
{ |
return(do_lsreaddir(conn, path, 0, dir)); |
return sftp_lsreaddir(conn, path, 0, dir); |
} |
} |
|
|
void free_sftp_dirents(SFTP_DIRENT **s) |
void sftp_free_dirents(SFTP_DIRENT **s) |
{ |
{ |
int i; |
int i; |
|
|
|
|
} |
} |
|
|
int |
int |
do_rm(struct sftp_conn *conn, const char *path) |
sftp_rm(struct sftp_conn *conn, const char *path) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
|
|
} |
} |
|
|
int |
int |
do_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) |
sftp_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
|
|
} |
} |
|
|
int |
int |
do_rmdir(struct sftp_conn *conn, const char *path) |
sftp_rmdir(struct sftp_conn *conn, const char *path) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
|
|
return status == SSH2_FX_OK ? 0 : -1; |
return status == SSH2_FX_OK ? 0 : -1; |
} |
} |
|
|
Attrib * |
int |
do_stat(struct sftp_conn *conn, const char *path, int quiet) |
sftp_stat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a) |
{ |
{ |
u_int id; |
u_int id; |
|
|
|
|
conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, |
conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, |
path, strlen(path)); |
path, strlen(path)); |
|
|
return(get_decode_stat(conn, id, quiet)); |
return get_decode_stat(conn, id, quiet, a); |
} |
} |
|
|
Attrib * |
int |
do_lstat(struct sftp_conn *conn, const char *path, int quiet) |
sftp_lstat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a) |
{ |
{ |
u_int id; |
u_int id; |
|
|
if (conn->version == 0) { |
if (conn->version == 0) { |
if (quiet) |
do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO, |
debug("Server version does not support lstat operation"); |
"Server version does not support lstat operation"); |
else |
return sftp_stat(conn, path, quiet, a); |
logit("Server version does not support lstat operation"); |
|
return(do_stat(conn, path, quiet)); |
|
} |
} |
|
|
id = conn->msg_id++; |
id = conn->msg_id++; |
send_string_request(conn, id, SSH2_FXP_LSTAT, path, |
send_string_request(conn, id, SSH2_FXP_LSTAT, path, |
strlen(path)); |
strlen(path)); |
|
|
return(get_decode_stat(conn, id, quiet)); |
return get_decode_stat(conn, id, quiet, a); |
} |
} |
|
|
#ifdef notyet |
#ifdef notyet |
Attrib * |
int |
do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
sftp_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
int quiet) |
int quiet, Attrib *a) |
{ |
{ |
u_int id; |
u_int id; |
|
|
|
|
send_string_request(conn, id, SSH2_FXP_FSTAT, handle, |
send_string_request(conn, id, SSH2_FXP_FSTAT, handle, |
handle_len); |
handle_len); |
|
|
return(get_decode_stat(conn, id, quiet)); |
return get_decode_stat(conn, id, quiet, a); |
} |
} |
#endif |
#endif |
|
|
int |
int |
do_setstat(struct sftp_conn *conn, const char *path, Attrib *a) |
sftp_setstat(struct sftp_conn *conn, const char *path, Attrib *a) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
|
|
} |
} |
|
|
int |
int |
do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
sftp_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
Attrib *a) |
Attrib *a) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
|
|
/* Implements both the realpath and expand-path operations */ |
/* Implements both the realpath and expand-path operations */ |
static char * |
static char * |
do_realpath_expand(struct sftp_conn *conn, const char *path, int expand) |
sftp_realpath_expand(struct sftp_conn *conn, const char *path, int expand) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
u_int expected_id, count, id; |
u_int expected_id, count, id; |
|
|
} |
} |
|
|
char * |
char * |
do_realpath(struct sftp_conn *conn, const char *path) |
sftp_realpath(struct sftp_conn *conn, const char *path) |
{ |
{ |
return do_realpath_expand(conn, path, 0); |
return sftp_realpath_expand(conn, path, 0); |
} |
} |
|
|
int |
int |
can_expand_path(struct sftp_conn *conn) |
sftp_can_expand_path(struct sftp_conn *conn) |
{ |
{ |
return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0; |
return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0; |
} |
} |
|
|
char * |
char * |
do_expand_path(struct sftp_conn *conn, const char *path) |
sftp_expand_path(struct sftp_conn *conn, const char *path) |
{ |
{ |
if (!can_expand_path(conn)) { |
if (!sftp_can_expand_path(conn)) { |
debug3_f("no server support, fallback to realpath"); |
debug3_f("no server support, fallback to realpath"); |
return do_realpath_expand(conn, path, 0); |
return sftp_realpath_expand(conn, path, 0); |
} |
} |
return do_realpath_expand(conn, path, 1); |
return sftp_realpath_expand(conn, path, 1); |
} |
} |
|
|
int |
int |
do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
sftp_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
{ |
{ |
Attrib junk, *a; |
Attrib junk, attr; |
struct sshbuf *msg; |
struct sshbuf *msg; |
u_char *old_handle, *new_handle; |
u_char *old_handle, *new_handle; |
u_int mode, status, id; |
u_int mode, status, id; |
|
|
} |
} |
|
|
/* Make sure the file exists, and we can copy its perms */ |
/* Make sure the file exists, and we can copy its perms */ |
if ((a = do_stat(conn, oldpath, 0)) == NULL) |
if (sftp_stat(conn, oldpath, 0, &attr) != 0) |
return -1; |
return -1; |
|
|
/* Do not preserve set[ug]id here, as we do not preserve ownership */ |
/* Do not preserve set[ug]id here, as we do not preserve ownership */ |
if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { |
if (attr.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { |
mode = a->perm & 0777; |
mode = attr.perm & 0777; |
|
|
if (!S_ISREG(a->perm)) { |
if (!S_ISREG(attr.perm)) { |
error("Cannot copy non-regular file: %s", oldpath); |
error("Cannot copy non-regular file: %s", oldpath); |
return -1; |
return -1; |
} |
} |
|
|
} |
} |
|
|
/* Set up the new perms for the new file */ |
/* Set up the new perms for the new file */ |
attrib_clear(a); |
attrib_clear(&attr); |
a->perm = mode; |
attr.perm = mode; |
a->flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; |
attr.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; |
|
|
if ((msg = sshbuf_new()) == NULL) |
if ((msg = sshbuf_new()) == NULL) |
fatal("%s: sshbuf_new failed", __func__); |
fatal("%s: sshbuf_new failed", __func__); |
|
|
(r = sshbuf_put_cstring(msg, newpath)) != 0 || |
(r = sshbuf_put_cstring(msg, newpath)) != 0 || |
(r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| |
(r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| |
SSH2_FXF_TRUNC)) != 0 || |
SSH2_FXF_TRUNC)) != 0 || |
(r = encode_attrib(msg, a)) != 0) |
(r = encode_attrib(msg, &attr)) != 0) |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
send_msg(conn, msg); |
send_msg(conn, msg); |
debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath); |
debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath); |
|
|
|
|
/* Clean up everything */ |
/* Clean up everything */ |
sshbuf_free(msg); |
sshbuf_free(msg); |
do_close(conn, old_handle, old_handle_len); |
sftp_close(conn, old_handle, old_handle_len); |
do_close(conn, new_handle, new_handle_len); |
sftp_close(conn, new_handle, new_handle_len); |
free(old_handle); |
free(old_handle); |
free(new_handle); |
free(new_handle); |
|
|
|
|
} |
} |
|
|
int |
int |
do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, |
sftp_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, |
int force_legacy) |
int force_legacy) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
|
|
} |
} |
|
|
int |
int |
do_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
sftp_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
u_int status, id; |
u_int status, id; |
|
|
} |
} |
|
|
int |
int |
do_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
sftp_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
u_int status, id; |
u_int status, id; |
|
|
} |
} |
|
|
int |
int |
do_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len) |
sftp_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
u_int status, id; |
u_int status, id; |
|
|
|
|
#ifdef notyet |
#ifdef notyet |
char * |
char * |
do_readlink(struct sftp_conn *conn, const char *path) |
sftp_readlink(struct sftp_conn *conn, const char *path) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
u_int expected_id, count, id; |
u_int expected_id, count, id; |
|
|
#endif |
#endif |
|
|
int |
int |
do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, |
sftp_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, |
int quiet) |
int quiet) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
|
|
|
|
#ifdef notyet |
#ifdef notyet |
int |
int |
do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
sftp_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
struct sftp_statvfs *st, int quiet) |
struct sftp_statvfs *st, int quiet) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
|
|
#endif |
#endif |
|
|
int |
int |
do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a) |
sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a) |
{ |
{ |
struct sshbuf *msg; |
struct sshbuf *msg; |
u_int status, id; |
u_int status, id; |
|
|
} |
} |
|
|
int |
int |
do_download(struct sftp_conn *conn, const char *remote_path, |
sftp_download(struct sftp_conn *conn, const char *remote_path, |
const char *local_path, Attrib *a, int preserve_flag, int resume_flag, |
const char *local_path, Attrib *a, int preserve_flag, int resume_flag, |
int fsync_flag, int inplace_flag) |
int fsync_flag, int inplace_flag) |
{ |
{ |
|
|
struct requests requests; |
struct requests requests; |
struct request *req; |
struct request *req; |
u_char type; |
u_char type; |
|
Attrib attr; |
|
|
debug2_f("download remote \"%s\" to local \"%s\"", |
debug2_f("download remote \"%s\" to local \"%s\"", |
remote_path, local_path); |
remote_path, local_path); |
|
|
TAILQ_INIT(&requests); |
TAILQ_INIT(&requests); |
|
|
if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL) |
if (a == NULL) { |
return -1; |
if (sftp_stat(conn, remote_path, 0, &attr) != 0) |
|
return -1; |
|
a = &attr; |
|
} |
|
|
/* Do not preserve set[ug]id here, as we do not preserve ownership */ |
/* Do not preserve set[ug]id here, as we do not preserve ownership */ |
if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) |
if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) |
|
|
error("Unable to resume download of \"%s\": " |
error("Unable to resume download of \"%s\": " |
"local file is larger than remote", local_path); |
"local file is larger than remote", local_path); |
fail: |
fail: |
do_close(conn, handle, handle_len); |
sftp_close(conn, handle, handle_len); |
free(handle); |
free(handle); |
if (local_fd != -1) |
if (local_fd != -1) |
close(local_fd); |
close(local_fd); |
|
|
if (read_error) { |
if (read_error) { |
error("read remote \"%s\" : %s", remote_path, fx2txt(status)); |
error("read remote \"%s\" : %s", remote_path, fx2txt(status)); |
status = -1; |
status = -1; |
do_close(conn, handle, handle_len); |
sftp_close(conn, handle, handle_len); |
} else if (write_error) { |
} else if (write_error) { |
error("write local \"%s\": %s", local_path, |
error("write local \"%s\": %s", local_path, |
strerror(write_errno)); |
strerror(write_errno)); |
status = SSH2_FX_FAILURE; |
status = SSH2_FX_FAILURE; |
do_close(conn, handle, handle_len); |
sftp_close(conn, handle, handle_len); |
} else { |
} else { |
if (do_close(conn, handle, handle_len) != 0 || interrupted) |
if (sftp_close(conn, handle, handle_len) != 0 || interrupted) |
status = SSH2_FX_FAILURE; |
status = SSH2_FX_FAILURE; |
else |
else |
status = SSH2_FX_OK; |
status = SSH2_FX_OK; |
|
|
debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst); |
debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst); |
|
|
if (dirattrib == NULL) { |
if (dirattrib == NULL) { |
if ((a = do_stat(conn, src, 1)) == NULL) { |
if (sftp_stat(conn, src, 1, &ldirattrib) != 0) { |
error("stat remote \"%s\" directory failed", src); |
error("stat remote \"%s\" directory failed", src); |
return -1; |
return -1; |
} |
} |
/* Don't let this be clobbered by later do_stat calls */ |
|
ldirattrib = *a; |
|
dirattrib = &ldirattrib; |
dirattrib = &ldirattrib; |
} |
} |
if (!S_ISDIR(dirattrib->perm)) { |
if (!S_ISDIR(dirattrib->perm)) { |
|
|
return -1; |
return -1; |
} |
} |
|
|
if (do_readdir(conn, src, &dir_entries) == -1) { |
if (sftp_readdir(conn, src, &dir_entries) == -1) { |
error("remote readdir \"%s\" failed", src); |
error("remote readdir \"%s\" failed", src); |
return -1; |
return -1; |
} |
} |
|
|
free(new_src); |
free(new_src); |
|
|
filename = dir_entries[i]->filename; |
filename = dir_entries[i]->filename; |
new_dst = path_append(dst, filename); |
new_dst = sftp_path_append(dst, filename); |
new_src = path_append(src, filename); |
new_src = sftp_path_append(src, filename); |
|
|
a = &dir_entries[i]->a; |
a = &dir_entries[i]->a; |
if (S_ISLNK(a->perm)) { |
if (S_ISLNK(a->perm)) { |
|
|
continue; |
continue; |
} |
} |
/* Replace the stat contents with the symlink target */ |
/* Replace the stat contents with the symlink target */ |
if ((a = do_stat(conn, new_src, 1)) == NULL) { |
if (sftp_stat(conn, new_src, 1, &lsym) != 0) { |
logit("remote stat \"%s\" failed", new_src); |
logit("remote stat \"%s\" failed", new_src); |
ret = -1; |
ret = -1; |
continue; |
continue; |
} |
} |
/* Don't let this be clobbered by later do_stat calls */ |
|
lsym = *a; |
|
a = &lsym; |
a = &lsym; |
} |
} |
|
|
|
|
fsync_flag, follow_link_flag, inplace_flag) == -1) |
fsync_flag, follow_link_flag, inplace_flag) == -1) |
ret = -1; |
ret = -1; |
} else if (S_ISREG(a->perm)) { |
} else if (S_ISREG(a->perm)) { |
if (do_download(conn, new_src, new_dst, a, |
if (sftp_download(conn, new_src, new_dst, a, |
preserve_flag, resume_flag, fsync_flag, |
preserve_flag, resume_flag, fsync_flag, |
inplace_flag) == -1) { |
inplace_flag) == -1) { |
error("Download of file %s to %s failed", |
error("Download of file %s to %s failed", |
|
|
error("local chmod directory \"%s\": %s", dst, |
error("local chmod directory \"%s\": %s", dst, |
strerror(errno)); |
strerror(errno)); |
|
|
free_sftp_dirents(dir_entries); |
sftp_free_dirents(dir_entries); |
|
|
return ret; |
return ret; |
} |
} |
|
|
int |
int |
download_dir(struct sftp_conn *conn, const char *src, const char *dst, |
sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst, |
Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag, |
Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag, |
int fsync_flag, int follow_link_flag, int inplace_flag) |
int fsync_flag, int follow_link_flag, int inplace_flag) |
{ |
{ |
char *src_canon; |
char *src_canon; |
int ret; |
int ret; |
|
|
if ((src_canon = do_realpath(conn, src)) == NULL) { |
if ((src_canon = sftp_realpath(conn, src)) == NULL) { |
error("download \"%s\": path canonicalization failed", src); |
error("download \"%s\": path canonicalization failed", src); |
return -1; |
return -1; |
} |
} |
|
|
} |
} |
|
|
int |
int |
do_upload(struct sftp_conn *conn, const char *local_path, |
sftp_upload(struct sftp_conn *conn, const char *local_path, |
const char *remote_path, int preserve_flag, int resume, |
const char *remote_path, int preserve_flag, int resume, |
int fsync_flag, int inplace_flag) |
int fsync_flag, int inplace_flag) |
{ |
{ |
|
|
u_char type, *handle, *data; |
u_char type, *handle, *data; |
struct sshbuf *msg; |
struct sshbuf *msg; |
struct stat sb; |
struct stat sb; |
Attrib a, t, *c = NULL; |
Attrib a, t, c; |
u_int32_t startid, ackid; |
u_int32_t startid, ackid; |
u_int64_t highwater = 0, maxack = 0; |
u_int64_t highwater = 0, maxack = 0; |
struct request *ack = NULL; |
struct request *ack = NULL; |
|
|
|
|
if (resume) { |
if (resume) { |
/* Get remote file size if it exists */ |
/* Get remote file size if it exists */ |
if ((c = do_stat(conn, remote_path, 0)) == NULL) { |
if (sftp_stat(conn, remote_path, 0, &c) != 0) { |
close(local_fd); |
close(local_fd); |
return -1; |
return -1; |
} |
} |
|
|
if ((off_t)c->size >= sb.st_size) { |
if ((off_t)c.size >= sb.st_size) { |
error("resume \"%s\": destination file " |
error("resume \"%s\": destination file " |
"same size or larger", local_path); |
"same size or larger", local_path); |
close(local_fd); |
close(local_fd); |
return -1; |
return -1; |
} |
} |
|
|
if (lseek(local_fd, (off_t)c->size, SEEK_SET) == -1) { |
if (lseek(local_fd, (off_t)c.size, SEEK_SET) == -1) { |
close(local_fd); |
close(local_fd); |
return -1; |
return -1; |
} |
} |
|
|
data = xmalloc(conn->upload_buflen); |
data = xmalloc(conn->upload_buflen); |
|
|
/* Read from local and write to remote */ |
/* Read from local and write to remote */ |
offset = progress_counter = (resume ? c->size : 0); |
offset = progress_counter = (resume ? c.size : 0); |
if (showprogress) { |
if (showprogress) { |
start_progress_meter(progress_meter_path(local_path), |
start_progress_meter(progress_meter_path(local_path), |
sb.st_size, &progress_counter); |
sb.st_size, &progress_counter); |
|
|
attrib_clear(&t); |
attrib_clear(&t); |
t.flags = SSH2_FILEXFER_ATTR_SIZE; |
t.flags = SSH2_FILEXFER_ATTR_SIZE; |
t.size = highwater; |
t.size = highwater; |
do_fsetstat(conn, handle, handle_len, &t); |
sftp_fsetstat(conn, handle, handle_len, &t); |
} |
} |
|
|
if (close(local_fd) == -1) { |
if (close(local_fd) == -1) { |
|
|
|
|
/* Override umask and utimes if asked */ |
/* Override umask and utimes if asked */ |
if (preserve_flag) |
if (preserve_flag) |
do_fsetstat(conn, handle, handle_len, &a); |
sftp_fsetstat(conn, handle, handle_len, &a); |
|
|
if (fsync_flag) |
if (fsync_flag) |
(void)do_fsync(conn, handle, handle_len); |
(void)sftp_fsync(conn, handle, handle_len); |
|
|
if (do_close(conn, handle, handle_len) != 0) |
if (sftp_close(conn, handle, handle_len) != 0) |
status = SSH2_FX_FAILURE; |
status = SSH2_FX_FAILURE; |
|
|
free(handle); |
free(handle); |
|
|
struct dirent *dp; |
struct dirent *dp; |
char *filename, *new_src = NULL, *new_dst = NULL; |
char *filename, *new_src = NULL, *new_dst = NULL; |
struct stat sb; |
struct stat sb; |
Attrib a, *dirattrib; |
Attrib a, dirattrib; |
u_int32_t saved_perm; |
u_int32_t saved_perm; |
|
|
debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst); |
debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst); |
|
|
*/ |
*/ |
saved_perm = a.perm; |
saved_perm = a.perm; |
a.perm |= (S_IWUSR|S_IXUSR); |
a.perm |= (S_IWUSR|S_IXUSR); |
if (do_mkdir(conn, dst, &a, 0) != 0) { |
if (sftp_mkdir(conn, dst, &a, 0) != 0) { |
if ((dirattrib = do_stat(conn, dst, 0)) == NULL) |
if (sftp_stat(conn, dst, 0, &dirattrib) != 0) |
return -1; |
return -1; |
if (!S_ISDIR(dirattrib->perm)) { |
if (!S_ISDIR(dirattrib.perm)) { |
error("\"%s\" exists but is not a directory", dst); |
error("\"%s\" exists but is not a directory", dst); |
return -1; |
return -1; |
} |
} |
|
|
free(new_dst); |
free(new_dst); |
free(new_src); |
free(new_src); |
filename = dp->d_name; |
filename = dp->d_name; |
new_dst = path_append(dst, filename); |
new_dst = sftp_path_append(dst, filename); |
new_src = path_append(src, filename); |
new_src = sftp_path_append(src, filename); |
|
|
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) |
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) |
continue; |
continue; |
|
|
fsync_flag, follow_link_flag, inplace_flag) == -1) |
fsync_flag, follow_link_flag, inplace_flag) == -1) |
ret = -1; |
ret = -1; |
} else if (S_ISREG(sb.st_mode)) { |
} else if (S_ISREG(sb.st_mode)) { |
if (do_upload(conn, new_src, new_dst, |
if (sftp_upload(conn, new_src, new_dst, |
preserve_flag, resume, fsync_flag, |
preserve_flag, resume, fsync_flag, |
inplace_flag) == -1) { |
inplace_flag) == -1) { |
error("upload \"%s\" to \"%s\" failed", |
error("upload \"%s\" to \"%s\" failed", |
|
|
free(new_dst); |
free(new_dst); |
free(new_src); |
free(new_src); |
|
|
do_setstat(conn, dst, &a); |
sftp_setstat(conn, dst, &a); |
|
|
(void) closedir(dirp); |
(void) closedir(dirp); |
return ret; |
return ret; |
} |
} |
|
|
int |
int |
upload_dir(struct sftp_conn *conn, const char *src, const char *dst, |
sftp_upload_dir(struct sftp_conn *conn, const char *src, const char *dst, |
int preserve_flag, int print_flag, int resume, int fsync_flag, |
int preserve_flag, int print_flag, int resume, int fsync_flag, |
int follow_link_flag, int inplace_flag) |
int follow_link_flag, int inplace_flag) |
{ |
{ |
char *dst_canon; |
char *dst_canon; |
int ret; |
int ret; |
|
|
if ((dst_canon = do_realpath(conn, dst)) == NULL) { |
if ((dst_canon = sftp_realpath(conn, dst)) == NULL) { |
error("upload \"%s\": path canonicalization failed", dst); |
error("upload \"%s\": path canonicalization failed", dst); |
return -1; |
return -1; |
} |
} |
|
|
*write_errorp = status; |
*write_errorp = status; |
} |
} |
/* |
/* |
* XXX this doesn't do full reply matching like do_upload and |
* XXX this doesn't do full reply matching like sftp_upload and |
* so cannot gracefully truncate terminated uploads at a |
* so cannot gracefully truncate terminated uploads at a |
* high-water mark. ATM the only caller of this function (scp) |
* high-water mark. ATM the only caller of this function (scp) |
* doesn't support transfer resumption, so this doesn't matter |
* doesn't support transfer resumption, so this doesn't matter |
* a whole lot. |
* a whole lot. |
* |
* |
* To be safe, do_crossload truncates the destination file to |
* To be safe, sftp_crossload truncates the destination file to |
* zero length on upload failure, since we can't trust the |
* zero length on upload failure, since we can't trust the |
* server not to have reordered replies that could have |
* server not to have reordered replies that could have |
* inserted holes where none existed in the source file. |
* inserted holes where none existed in the source file. |
|
|
} |
} |
|
|
int |
int |
do_crossload(struct sftp_conn *from, struct sftp_conn *to, |
sftp_crossload(struct sftp_conn *from, struct sftp_conn *to, |
const char *from_path, const char *to_path, |
const char *from_path, const char *to_path, |
Attrib *a, int preserve_flag) |
Attrib *a, int preserve_flag) |
{ |
{ |
|
|
struct requests requests; |
struct requests requests; |
struct request *req; |
struct request *req; |
u_char type; |
u_char type; |
|
Attrib attr; |
|
|
debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path); |
debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path); |
|
|
TAILQ_INIT(&requests); |
TAILQ_INIT(&requests); |
|
|
if (a == NULL && (a = do_stat(from, from_path, 0)) == NULL) |
if (a == NULL) { |
return -1; |
if (sftp_stat(from, from_path, 0, &attr) != 0) |
|
return -1; |
|
a = &attr; |
|
} |
|
|
if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && |
if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && |
(!S_ISREG(a->perm))) { |
(!S_ISREG(a->perm))) { |
|
|
if (send_open(to, to_path, "dest", |
if (send_open(to, to_path, "dest", |
SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a, |
SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a, |
&to_handle, &to_handle_len) != 0) { |
&to_handle, &to_handle_len) != 0) { |
do_close(from, from_handle, from_handle_len); |
sftp_close(from, from_handle, from_handle_len); |
return -1; |
return -1; |
} |
} |
|
|
|
|
/* Truncate at 0 length on interrupt or error to avoid holes at dest */ |
/* Truncate at 0 length on interrupt or error to avoid holes at dest */ |
if (read_error || write_error || interrupted) { |
if (read_error || write_error || interrupted) { |
debug("truncating \"%s\" at 0", to_path); |
debug("truncating \"%s\" at 0", to_path); |
do_close(to, to_handle, to_handle_len); |
sftp_close(to, to_handle, to_handle_len); |
free(to_handle); |
free(to_handle); |
if (send_open(to, to_path, "dest", |
if (send_open(to, to_path, "dest", |
SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a, |
SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a, |
|
|
if (read_error) { |
if (read_error) { |
error("read origin \"%s\": %s", from_path, fx2txt(status)); |
error("read origin \"%s\": %s", from_path, fx2txt(status)); |
status = -1; |
status = -1; |
do_close(from, from_handle, from_handle_len); |
sftp_close(from, from_handle, from_handle_len); |
if (to_handle != NULL) |
if (to_handle != NULL) |
do_close(to, to_handle, to_handle_len); |
sftp_close(to, to_handle, to_handle_len); |
} else if (write_error) { |
} else if (write_error) { |
error("write dest \"%s\": %s", to_path, fx2txt(write_error)); |
error("write dest \"%s\": %s", to_path, fx2txt(write_error)); |
status = SSH2_FX_FAILURE; |
status = SSH2_FX_FAILURE; |
do_close(from, from_handle, from_handle_len); |
sftp_close(from, from_handle, from_handle_len); |
if (to_handle != NULL) |
if (to_handle != NULL) |
do_close(to, to_handle, to_handle_len); |
sftp_close(to, to_handle, to_handle_len); |
} else { |
} else { |
if (do_close(from, from_handle, from_handle_len) != 0 || |
if (sftp_close(from, from_handle, from_handle_len) != 0 || |
interrupted) |
interrupted) |
status = -1; |
status = -1; |
else |
else |
|
|
if (to_handle != NULL) { |
if (to_handle != NULL) { |
/* Need to resend utimes after write */ |
/* Need to resend utimes after write */ |
if (preserve_flag) |
if (preserve_flag) |
do_fsetstat(to, to_handle, to_handle_len, a); |
sftp_fsetstat(to, to_handle, to_handle_len, a); |
do_close(to, to_handle, to_handle_len); |
sftp_close(to, to_handle, to_handle_len); |
} |
} |
} |
} |
sshbuf_free(msg); |
sshbuf_free(msg); |
|
|
SFTP_DIRENT **dir_entries; |
SFTP_DIRENT **dir_entries; |
char *filename, *new_from_path = NULL, *new_to_path = NULL; |
char *filename, *new_from_path = NULL, *new_to_path = NULL; |
mode_t mode = 0777; |
mode_t mode = 0777; |
Attrib curdir; |
Attrib curdir, ldirattrib, newdir; |
|
|
debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path); |
debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path); |
|
|
|
|
return -1; |
return -1; |
} |
} |
|
|
if (dirattrib == NULL && |
if (dirattrib == NULL) { |
(dirattrib = do_stat(from, from_path, 1)) == NULL) { |
if (sftp_stat(from, from_path, 1, &ldirattrib) != 0) { |
error("stat remote \"%s\" failed", from_path); |
error("stat remote \"%s\" failed", from_path); |
return -1; |
return -1; |
|
} |
|
dirattrib = &ldirattrib; |
} |
} |
if (!S_ISDIR(dirattrib->perm)) { |
if (!S_ISDIR(dirattrib->perm)) { |
error("\"%s\" is not a directory", from_path); |
error("\"%s\" is not a directory", from_path); |
|
|
* the path already existed and is a directory. Ensure we can |
* the path already existed and is a directory. Ensure we can |
* write to the directory we create for the duration of the transfer. |
* write to the directory we create for the duration of the transfer. |
*/ |
*/ |
if (do_mkdir(to, to_path, &curdir, 0) != 0) { |
if (sftp_mkdir(to, to_path, &curdir, 0) != 0) { |
if ((dirattrib = do_stat(to, to_path, 0)) == NULL) |
if (sftp_stat(to, to_path, 0, &newdir) != 0) |
return -1; |
return -1; |
if (!S_ISDIR(dirattrib->perm)) { |
if (!S_ISDIR(newdir.perm)) { |
error("\"%s\" exists but is not a directory", to_path); |
error("\"%s\" exists but is not a directory", to_path); |
return -1; |
return -1; |
} |
} |
} |
} |
curdir.perm = mode; |
curdir.perm = mode; |
|
|
if (do_readdir(from, from_path, &dir_entries) == -1) { |
if (sftp_readdir(from, from_path, &dir_entries) == -1) { |
error("origin readdir \"%s\" failed", from_path); |
error("origin readdir \"%s\" failed", from_path); |
return -1; |
return -1; |
} |
} |
|
|
free(new_to_path); |
free(new_to_path); |
|
|
filename = dir_entries[i]->filename; |
filename = dir_entries[i]->filename; |
new_from_path = path_append(from_path, filename); |
new_from_path = sftp_path_append(from_path, filename); |
new_to_path = path_append(to_path, filename); |
new_to_path = sftp_path_append(to_path, filename); |
|
|
if (S_ISDIR(dir_entries[i]->a.perm)) { |
if (S_ISDIR(dir_entries[i]->a.perm)) { |
if (strcmp(filename, ".") == 0 || |
if (strcmp(filename, ".") == 0 || |
|
|
(follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) { |
(follow_link_flag && S_ISLNK(dir_entries[i]->a.perm))) { |
/* |
/* |
* If this is a symlink then don't send the link's |
* If this is a symlink then don't send the link's |
* Attrib. do_download() will do a FXP_STAT operation |
* Attrib. sftp_download() will do a FXP_STAT operation |
* and get the link target's attributes. |
* and get the link target's attributes. |
*/ |
*/ |
if (do_crossload(from, to, new_from_path, new_to_path, |
if (sftp_crossload(from, to, new_from_path, new_to_path, |
S_ISLNK(dir_entries[i]->a.perm) ? NULL : |
S_ISLNK(dir_entries[i]->a.perm) ? NULL : |
&(dir_entries[i]->a), preserve_flag) == -1) { |
&(dir_entries[i]->a), preserve_flag) == -1) { |
error("crossload \"%s\" to \"%s\" failed", |
error("crossload \"%s\" to \"%s\" failed", |
|
|
free(new_to_path); |
free(new_to_path); |
free(new_from_path); |
free(new_from_path); |
|
|
do_setstat(to, to_path, &curdir); |
sftp_setstat(to, to_path, &curdir); |
|
|
free_sftp_dirents(dir_entries); |
sftp_free_dirents(dir_entries); |
|
|
return ret; |
return ret; |
} |
} |
|
|
int |
int |
crossload_dir(struct sftp_conn *from, struct sftp_conn *to, |
sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to, |
const char *from_path, const char *to_path, |
const char *from_path, const char *to_path, |
Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag) |
Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag) |
{ |
{ |
char *from_path_canon; |
char *from_path_canon; |
int ret; |
int ret; |
|
|
if ((from_path_canon = do_realpath(from, from_path)) == NULL) { |
if ((from_path_canon = sftp_realpath(from, from_path)) == NULL) { |
error("crossload \"%s\": path canonicalization failed", |
error("crossload \"%s\": path canonicalization failed", |
from_path); |
from_path); |
return -1; |
return -1; |
|
|
} |
} |
|
|
int |
int |
can_get_users_groups_by_id(struct sftp_conn *conn) |
sftp_can_get_users_groups_by_id(struct sftp_conn *conn) |
{ |
{ |
return (conn->exts & SFTP_EXT_GETUSERSGROUPS_BY_ID) != 0; |
return (conn->exts & SFTP_EXT_GETUSERSGROUPS_BY_ID) != 0; |
} |
} |
|
|
int |
int |
do_get_users_groups_by_id(struct sftp_conn *conn, |
sftp_get_users_groups_by_id(struct sftp_conn *conn, |
const u_int *uids, u_int nuids, |
const u_int *uids, u_int nuids, |
const u_int *gids, u_int ngids, |
const u_int *gids, u_int ngids, |
char ***usernamesp, char ***groupnamesp) |
char ***usernamesp, char ***groupnamesp) |
|
|
int r; |
int r; |
|
|
*usernamesp = *groupnamesp = NULL; |
*usernamesp = *groupnamesp = NULL; |
if (!can_get_users_groups_by_id(conn)) |
if (!sftp_can_get_users_groups_by_id(conn)) |
return SSH_ERR_FEATURE_UNSUPPORTED; |
return SSH_ERR_FEATURE_UNSUPPORTED; |
|
|
if ((msg = sshbuf_new()) == NULL || |
if ((msg = sshbuf_new()) == NULL || |
|
|
} |
} |
|
|
char * |
char * |
path_append(const char *p1, const char *p2) |
sftp_path_append(const char *p1, const char *p2) |
{ |
{ |
char *ret; |
char *ret; |
size_t len = strlen(p1) + strlen(p2) + 2; |
size_t len = strlen(p1) + strlen(p2) + 2; |
|
|
* freed and a replacement allocated. Caller must free returned string. |
* freed and a replacement allocated. Caller must free returned string. |
*/ |
*/ |
char * |
char * |
make_absolute(char *p, const char *pwd) |
sftp_make_absolute(char *p, const char *pwd) |
{ |
{ |
char *abs_str; |
char *abs_str; |
|
|
/* Derelativise */ |
/* Derelativise */ |
if (p && !path_absolute(p)) { |
if (p && !path_absolute(p)) { |
abs_str = path_append(pwd, p); |
abs_str = sftp_path_append(pwd, p); |
free(p); |
free(p); |
return(abs_str); |
return(abs_str); |
} else |
} else |
|
|
} |
} |
|
|
int |
int |
remote_is_dir(struct sftp_conn *conn, const char *path) |
sftp_remote_is_dir(struct sftp_conn *conn, const char *path) |
{ |
{ |
Attrib *a; |
Attrib a; |
|
|
/* XXX: report errors? */ |
/* XXX: report errors? */ |
if ((a = do_stat(conn, path, 1)) == NULL) |
if (sftp_stat(conn, path, 1, &a) != 0) |
return(0); |
return(0); |
if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) |
if (!(a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) |
return(0); |
return(0); |
return(S_ISDIR(a->perm)); |
return S_ISDIR(a.perm); |
} |
} |
|
|
|
|
int |
|
local_is_dir(const char *path) |
|
{ |
|
struct stat sb; |
|
|
|
/* XXX: report errors? */ |
|
if (stat(path, &sb) == -1) |
|
return(0); |
|
|
|
return(S_ISDIR(sb.st_mode)); |
|
} |
|
|
|
/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ |
/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ |
int |
int |
globpath_is_dir(const char *pathname) |
sftp_globpath_is_dir(const char *pathname) |
{ |
{ |
size_t l = strlen(pathname); |
size_t l = strlen(pathname); |
|
|