version 1.88, 2009/08/14 18:17:49 |
version 1.89, 2009/08/18 18:36:20 |
|
|
#include <sys/statvfs.h> |
#include <sys/statvfs.h> |
#include <sys/uio.h> |
#include <sys/uio.h> |
|
|
|
#include <dirent.h> |
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <signal.h> |
#include <signal.h> |
|
|
/* Minimum amount of data to read at a time */ |
/* Minimum amount of data to read at a time */ |
#define MIN_READ_SIZE 512 |
#define MIN_READ_SIZE 512 |
|
|
|
/* Maximum depth to descend in directory trees */ |
|
#define MAX_DIR_DEPTH 64 |
|
|
struct sftp_conn { |
struct sftp_conn { |
int fd_in; |
int fd_in; |
int fd_out; |
int fd_out; |
|
|
if (printflag) |
if (printflag) |
printf("%s\n", longname); |
printf("%s\n", longname); |
|
|
|
/* |
|
* Directory entries should never contain '/' |
|
* These can be used to attack recursive ops |
|
* (e.g. send '../../../../etc/passwd') |
|
*/ |
|
if (strchr(filename, '/') != NULL) { |
|
error("Server sent suspect path \"%s\" " |
|
"during readdir of \"%s\"", filename, path); |
|
goto next; |
|
} |
|
|
if (dir) { |
if (dir) { |
*dir = xrealloc(*dir, ents + 2, sizeof(**dir)); |
*dir = xrealloc(*dir, ents + 2, sizeof(**dir)); |
(*dir)[ents] = xmalloc(sizeof(***dir)); |
(*dir)[ents] = xmalloc(sizeof(***dir)); |
|
|
memcpy(&(*dir)[ents]->a, a, sizeof(*a)); |
memcpy(&(*dir)[ents]->a, a, sizeof(*a)); |
(*dir)[++ents] = NULL; |
(*dir)[++ents] = NULL; |
} |
} |
|
next: |
xfree(filename); |
xfree(filename); |
xfree(longname); |
xfree(longname); |
} |
} |
|
|
} |
} |
|
|
int |
int |
do_mkdir(struct sftp_conn *conn, char *path, Attrib *a) |
do_mkdir(struct sftp_conn *conn, char *path, Attrib *a, int printflag) |
{ |
{ |
u_int status, id; |
u_int status, id; |
|
|
|
|
strlen(path), a); |
strlen(path), a); |
|
|
status = get_status(conn->fd_in, id); |
status = get_status(conn->fd_in, id); |
if (status != SSH2_FX_OK) |
if (status != SSH2_FX_OK && printflag) |
error("Couldn't create directory: %s", fx2txt(status)); |
error("Couldn't create directory: %s", fx2txt(status)); |
|
|
return(status); |
return(status); |
|
|
|
|
int |
int |
do_download(struct sftp_conn *conn, char *remote_path, char *local_path, |
do_download(struct sftp_conn *conn, char *remote_path, char *local_path, |
int pflag) |
Attrib *a, int pflag) |
{ |
{ |
Attrib junk, *a; |
Attrib junk; |
Buffer msg; |
Buffer msg; |
char *handle; |
char *handle; |
int local_fd, status = 0, write_error; |
int local_fd, status = 0, write_error; |
|
|
|
|
TAILQ_INIT(&requests); |
TAILQ_INIT(&requests); |
|
|
a = do_stat(conn, remote_path, 0); |
if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL) |
if (a == NULL) |
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 (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) |
|
|
return(status); |
return(status); |
} |
} |
|
|
|
static int |
|
download_dir_internal(struct sftp_conn *conn, char *src, char *dst, |
|
Attrib *dirattrib, int pflag, int printflag, int depth) |
|
{ |
|
int i, ret = 0; |
|
SFTP_DIRENT **dir_entries; |
|
char *filename, *new_src, *new_dst; |
|
mode_t mode = 0777; |
|
|
|
if (depth >= MAX_DIR_DEPTH) { |
|
error("Maximum directory depth exceeded: %d levels", depth); |
|
return -1; |
|
} |
|
|
|
if (dirattrib == NULL && |
|
(dirattrib = do_stat(conn, src, 1)) == NULL) { |
|
error("Unable to stat remote directory \"%s\"", src); |
|
return -1; |
|
} |
|
if (!S_ISDIR(dirattrib->perm)) { |
|
error("\"%s\" is not a directory", src); |
|
return -1; |
|
} |
|
if (printflag) |
|
printf("Retrieving %s\n", src); |
|
|
|
if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) |
|
mode = dirattrib->perm & 01777; |
|
else { |
|
debug("Server did not send permissions for " |
|
"directory \"%s\"", dst); |
|
} |
|
|
|
if (mkdir(dst, mode) == -1 && errno != EEXIST) { |
|
error("mkdir %s: %s", dst, strerror(errno)); |
|
return -1; |
|
} |
|
|
|
if (do_readdir(conn, src, &dir_entries) == -1) { |
|
error("%s: Failed to get directory contents", src); |
|
return -1; |
|
} |
|
|
|
for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { |
|
filename = dir_entries[i]->filename; |
|
|
|
new_dst = path_append(dst, filename); |
|
new_src = path_append(src, filename); |
|
|
|
if (S_ISDIR(dir_entries[i]->a.perm)) { |
|
if (strcmp(filename, ".") == 0 || |
|
strcmp(filename, "..") == 0) |
|
continue; |
|
if (download_dir_internal(conn, new_src, new_dst, |
|
&(dir_entries[i]->a), pflag, printflag, |
|
depth + 1) == -1) |
|
ret = -1; |
|
} else if (S_ISREG(dir_entries[i]->a.perm) ) { |
|
if (do_download(conn, new_src, new_dst, |
|
&(dir_entries[i]->a), pflag) == -1) { |
|
error("Download of file %s to %s failed", |
|
new_src, new_dst); |
|
ret = -1; |
|
} |
|
} else |
|
logit("%s: not a regular file\n", new_src); |
|
|
|
xfree(new_dst); |
|
xfree(new_src); |
|
} |
|
|
|
if (pflag) { |
|
if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { |
|
struct timeval tv[2]; |
|
tv[0].tv_sec = dirattrib->atime; |
|
tv[1].tv_sec = dirattrib->mtime; |
|
tv[0].tv_usec = tv[1].tv_usec = 0; |
|
if (utimes(dst, tv) == -1) |
|
error("Can't set times on \"%s\": %s", |
|
dst, strerror(errno)); |
|
} else |
|
debug("Server did not send times for directory " |
|
"\"%s\"", dst); |
|
} |
|
|
|
free_sftp_dirents(dir_entries); |
|
|
|
return ret; |
|
} |
|
|
int |
int |
|
download_dir(struct sftp_conn *conn, char *src, char *dst, |
|
Attrib *dirattrib, int pflag, int printflag) |
|
{ |
|
char *src_canon; |
|
int ret; |
|
|
|
if ((src_canon = do_realpath(conn, src)) == NULL) { |
|
error("Unable to canonicalise path \"%s\"", src); |
|
return -1; |
|
} |
|
|
|
ret = download_dir_internal(conn, src_canon, dst, |
|
dirattrib, pflag, printflag, 0); |
|
xfree(src_canon); |
|
return ret; |
|
} |
|
|
|
int |
do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, |
do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, |
int pflag) |
int pflag) |
{ |
{ |
|
|
|
|
return status; |
return status; |
} |
} |
|
|
|
static int |
|
upload_dir_internal(struct sftp_conn *conn, char *src, char *dst, |
|
int pflag, int printflag, int depth) |
|
{ |
|
int ret = 0, status; |
|
DIR *dirp; |
|
struct dirent *dp; |
|
char *filename, *new_src, *new_dst; |
|
struct stat sb; |
|
Attrib a; |
|
|
|
if (depth >= MAX_DIR_DEPTH) { |
|
error("Maximum directory depth exceeded: %d levels", depth); |
|
return -1; |
|
} |
|
|
|
if (stat(src, &sb) == -1) { |
|
error("Couldn't stat directory \"%s\": %s", |
|
src, strerror(errno)); |
|
return -1; |
|
} |
|
if (!S_ISDIR(sb.st_mode)) { |
|
error("\"%s\" is not a directory", src); |
|
return -1; |
|
} |
|
if (printflag) |
|
printf("Entering %s\n", src); |
|
|
|
attrib_clear(&a); |
|
stat_to_attrib(&sb, &a); |
|
a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; |
|
a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; |
|
a.perm &= 01777; |
|
if (!pflag) |
|
a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; |
|
|
|
status = do_mkdir(conn, dst, &a, 0); |
|
/* |
|
* we lack a portable status for errno EEXIST, |
|
* so if we get a SSH2_FX_FAILURE back we must check |
|
* if it was created successfully. |
|
*/ |
|
if (status != SSH2_FX_OK) { |
|
if (status != SSH2_FX_FAILURE) |
|
return -1; |
|
if (do_stat(conn, dst, 0) == NULL) |
|
return -1; |
|
} |
|
|
|
if ((dirp = opendir(src)) == NULL) { |
|
error("Failed to open dir \"%s\": %s", src, strerror(errno)); |
|
return -1; |
|
} |
|
|
|
while (((dp = readdir(dirp)) != NULL) && !interrupted) { |
|
if (dp->d_ino == 0) |
|
continue; |
|
filename = dp->d_name; |
|
new_dst = path_append(dst, filename); |
|
new_src = path_append(src, filename); |
|
|
|
if (S_ISDIR(DTTOIF(dp->d_type))) { |
|
if (strcmp(filename, ".") == 0 || |
|
strcmp(filename, "..") == 0) |
|
continue; |
|
|
|
if (upload_dir_internal(conn, new_src, new_dst, |
|
pflag, depth + 1, printflag) == -1) |
|
ret = -1; |
|
} else if (S_ISREG(DTTOIF(dp->d_type)) ) { |
|
if (do_upload(conn, new_src, new_dst, pflag) == -1) { |
|
error("Uploading of file %s to %s failed!", |
|
new_src, new_dst); |
|
ret = -1; |
|
} |
|
} else |
|
logit("%s: not a regular file\n", filename); |
|
xfree(new_dst); |
|
xfree(new_src); |
|
} |
|
|
|
do_setstat(conn, dst, &a); |
|
|
|
(void) closedir(dirp); |
|
return ret; |
|
} |
|
|
|
int |
|
upload_dir(struct sftp_conn *conn, char *src, char *dst, int printflag, |
|
int pflag) |
|
{ |
|
char *dst_canon; |
|
int ret; |
|
|
|
if ((dst_canon = do_realpath(conn, dst)) == NULL) { |
|
error("Unable to canonicalise path \"%s\"", dst); |
|
return -1; |
|
} |
|
|
|
ret = upload_dir_internal(conn, src, dst_canon, pflag, printflag, 0); |
|
xfree(dst_canon); |
|
return ret; |
|
} |
|
|
|
char * |
|
path_append(char *p1, char *p2) |
|
{ |
|
char *ret; |
|
size_t len = strlen(p1) + strlen(p2) + 2; |
|
|
|
ret = xmalloc(len); |
|
strlcpy(ret, p1, len); |
|
if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') |
|
strlcat(ret, "/", len); |
|
strlcat(ret, p2, len); |
|
|
|
return(ret); |
|
} |
|
|