summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.bin/ssh/sftp-client.c260
-rw-r--r--usr.bin/ssh/sftp-client.h21
-rw-r--r--usr.bin/ssh/sftp.144
-rw-r--r--usr.bin/ssh/sftp.c217
4 files changed, 416 insertions, 126 deletions
diff --git a/usr.bin/ssh/sftp-client.c b/usr.bin/ssh/sftp-client.c
index 8736546b51c..32a8dd7e125 100644
--- a/usr.bin/ssh/sftp-client.c
+++ b/usr.bin/ssh/sftp-client.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.c,v 1.88 2009/08/14 18:17:49 djm Exp $ */
+/* $OpenBSD: sftp-client.c,v 1.89 2009/08/18 18:36:20 djm Exp $ */
/*
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
*
@@ -28,6 +28,7 @@
#include <sys/statvfs.h>
#include <sys/uio.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
@@ -53,6 +54,9 @@ extern int showprogress;
/* Minimum amount of data to read at a time */
#define MIN_READ_SIZE 512
+/* Maximum depth to descend in directory trees */
+#define MAX_DIR_DEPTH 64
+
struct sftp_conn {
int fd_in;
int fd_out;
@@ -489,6 +493,17 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
if (printflag)
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) {
*dir = xrealloc(*dir, ents + 2, sizeof(**dir));
(*dir)[ents] = xmalloc(sizeof(***dir));
@@ -497,7 +512,7 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
memcpy(&(*dir)[ents]->a, a, sizeof(*a));
(*dir)[++ents] = NULL;
}
-
+ next:
xfree(filename);
xfree(longname);
}
@@ -552,7 +567,7 @@ do_rm(struct sftp_conn *conn, char *path)
}
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;
@@ -561,7 +576,7 @@ do_mkdir(struct sftp_conn *conn, char *path, Attrib *a)
strlen(path), a);
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));
return(status);
@@ -900,9 +915,9 @@ send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len,
int
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;
char *handle;
int local_fd, status = 0, write_error;
@@ -921,9 +936,8 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
TAILQ_INIT(&requests);
- a = do_stat(conn, remote_path, 0);
- if (a == NULL)
- return(-1);
+ if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
+ return -1;
/* Do not preserve set[ug]id here, as we do not preserve ownership */
if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
@@ -1134,6 +1148,114 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
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
+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,
int pflag)
@@ -1315,3 +1437,123 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
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);
+}
+
diff --git a/usr.bin/ssh/sftp-client.h b/usr.bin/ssh/sftp-client.h
index edb46790f3e..1d08c40493b 100644
--- a/usr.bin/ssh/sftp-client.h
+++ b/usr.bin/ssh/sftp-client.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.h,v 1.17 2008/06/08 20:15:29 dtucker Exp $ */
+/* $OpenBSD: sftp-client.h,v 1.18 2009/08/18 18:36:20 djm Exp $ */
/*
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
@@ -68,7 +68,7 @@ void free_sftp_dirents(SFTP_DIRENT **);
int do_rm(struct sftp_conn *, char *);
/* Create directory 'path' */
-int do_mkdir(struct sftp_conn *, char *, Attrib *);
+int do_mkdir(struct sftp_conn *, char *, Attrib *, int);
/* Remove directory 'path' */
int do_rmdir(struct sftp_conn *, char *);
@@ -103,7 +103,13 @@ int do_symlink(struct sftp_conn *, char *, char *);
* Download 'remote_path' to 'local_path'. Preserve permissions and times
* if 'pflag' is set
*/
-int do_download(struct sftp_conn *, char *, char *, int);
+int do_download(struct sftp_conn *, char *, char *, Attrib *, int);
+
+/*
+ * Recursively download 'remote_directory' to 'local_directory'. Preserve
+ * times if 'pflag' is set
+ */
+int download_dir(struct sftp_conn *, char *, char *, Attrib *, int, int);
/*
* Upload 'local_path' to 'remote_path'. Preserve permissions and times
@@ -111,4 +117,13 @@ int do_download(struct sftp_conn *, char *, char *, int);
*/
int do_upload(struct sftp_conn *, char *, char *, int);
+/*
+ * Recursively upload 'local_directory' to 'remote_directory'. Preserve
+ * times if 'pflag' is set
+ */
+int upload_dir(struct sftp_conn *, char *, char *, int, int);
+
+/* Concatenate paths, taking care of slashes. Caller must free result. */
+char *path_append(char *, char *);
+
#endif
diff --git a/usr.bin/ssh/sftp.1 b/usr.bin/ssh/sftp.1
index fcd1d240a98..21dc5d8d632 100644
--- a/usr.bin/ssh/sftp.1
+++ b/usr.bin/ssh/sftp.1
@@ -1,4 +1,4 @@
-.\" $OpenBSD: sftp.1,v 1.73 2009/08/13 13:39:54 jmc Exp $
+.\" $OpenBSD: sftp.1,v 1.74 2009/08/18 18:36:20 djm Exp $
.\"
.\" Copyright (c) 2001 Damien Miller. All rights reserved.
.\"
@@ -22,7 +22,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd $Mdocdate: August 13 2009 $
+.Dd $Mdocdate: August 18 2009 $
.Dt SFTP 1
.Os
.Sh NAME
@@ -31,7 +31,7 @@
.Sh SYNOPSIS
.Nm sftp
.Bk -words
-.Op Fl 1246Cqv
+.Op Fl 1246Cpqrv
.Op Fl B Ar buffer_size
.Op Fl b Ar batchfile
.Op Fl c Ar cipher
@@ -223,6 +223,9 @@ For full details of the options listed below, and their possible values, see
.El
.It Fl P Ar port
Specifies the port to connect to on the remote host.
+.It Fl p
+Preserves modification times, access times, and modes from the
+original files transferred.
.It Fl q
Quiet mode: disables the progress meter as well as warning and
diagnostic messages from
@@ -232,6 +235,11 @@ Specify how many requests may be outstanding at any one time.
Increasing this may slightly improve file transfer speed
but will increase memory usage.
The default is 64 outstanding requests.
+.It Fl r
+Recursively copy entire directories when uploading and downloading.
+Note that
+.Nm
+does not follow symbolic links encountered in the tree traversal.
.It Fl S Ar program
Name of the
.Ar program
@@ -322,7 +330,7 @@ extension.
Quit
.Nm sftp .
.It Xo Ic get
-.Op Fl P
+.Op Fl Ppr
.Ar remote-path
.Op Ar local-path
.Xc
@@ -341,10 +349,20 @@ If it does and
is specified, then
.Ar local-path
must specify a directory.
-If the
-.Fl P
+.Pp
+If ether the
+.Fl Ppr
+or
+.Fl p
flag is specified, then full file permissions and access times are
copied too.
+.Pp
+If the
+.Fl r
+flag is specified then directories will be copied recursively.
+Note that
+.Nm
+does not follow symbolic links when performing recursive transfers.
.It Ic help
Display help text.
.It Ic lcd Ar path
@@ -440,10 +458,20 @@ If it does and
is specified, then
.Ar remote-path
must specify a directory.
-If the
+.Pp
+If ether the
.Fl P
-flag is specified, then the file's full permission and access time are
+or
+.Fl p
+flag is specified, then full file permissions and access times are
copied too.
+.Pp
+If the
+.Fl r
+flag is specified then directories will be copied recursively.
+Note that
+.Nm
+does not follow symbolic links when performing recursive transfers.
.It Ic pwd
Display remote working directory.
.It Ic quit
diff --git a/usr.bin/ssh/sftp.c b/usr.bin/ssh/sftp.c
index 61bad00f409..f9afe6c4072 100644
--- a/usr.bin/ssh/sftp.c
+++ b/usr.bin/ssh/sftp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp.c,v 1.110 2009/08/13 13:39:54 jmc Exp $ */
+/* $OpenBSD: sftp.c,v 1.111 2009/08/18 18:36:21 djm Exp $ */
/*
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
*
@@ -28,6 +28,7 @@
#include <glob.h>
#include <histedit.h>
#include <paths.h>
+#include <libgen.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
@@ -64,6 +65,12 @@ static pid_t sshpid = -1;
/* This is set to 0 if the progressmeter is not desired. */
int showprogress = 1;
+/* When this option is set, we always recursively download/upload directories */
+int global_rflag = 0;
+
+/* When this option is set, the file transfers will always preserve times */
+int global_pflag = 0;
+
/* SIGINT received during command processing */
volatile sig_atomic_t interrupted = 0;
@@ -195,7 +202,7 @@ help(void)
"df [-hi] [path] Display statistics for current directory or\n"
" filesystem containing 'path'\n"
"exit Quit sftp\n"
- "get [-P] remote-path [local-path] Download file\n"
+ "get [-Pr] remote-path [local-path] Download file\n"
"help Display this help text\n"
"lcd path Change local directory to 'path'\n"
"lls [ls-options [path]] Display local directory listing\n"
@@ -206,7 +213,7 @@ help(void)
"lumask umask Set local umask to 'umask'\n"
"mkdir path Create remote directory\n"
"progress Toggle display of progress meter\n"
- "put [-P] local-path [remote-path] Upload file\n"
+ "put [-Pr] local-path [remote-path] Upload file\n"
"pwd Display remote working directory\n"
"quit Quit sftp\n"
"rename oldpath newpath Rename remote file\n"
@@ -293,21 +300,6 @@ path_strip(char *path, char *strip)
}
static 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);
-}
-
-static char *
make_absolute(char *p, char *pwd)
{
char *abs_str;
@@ -322,27 +314,8 @@ make_absolute(char *p, char *pwd)
}
static int
-infer_path(const char *p, char **ifp)
-{
- char *cp;
-
- cp = strrchr(p, '/');
- if (cp == NULL) {
- *ifp = xstrdup(p);
- return(0);
- }
-
- if (!cp[1]) {
- error("Invalid path");
- return(-1);
- }
-
- *ifp = xstrdup(cp + 1);
- return(0);
-}
-
-static int
-parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
+parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
+ int *rflag)
{
extern int opterr, optind, optopt, optreset;
int ch;
@@ -350,13 +323,17 @@ parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
optind = optreset = 1;
opterr = 0;
- *pflag = 0;
- while ((ch = getopt(argc, argv, "Pp")) != -1) {
+ *rflag = *pflag = 0;
+ while ((ch = getopt(argc, argv, "PpRr")) != -1) {
switch (ch) {
case 'p':
case 'P':
*pflag = 1;
break;
+ case 'r':
+ case 'R':
+ *rflag = 1;
+ break;
default:
error("%s: Invalid flag -%c", cmd, optopt);
return -1;
@@ -468,62 +445,79 @@ remote_is_dir(struct sftp_conn *conn, char *path)
return(S_ISDIR(a->perm));
}
+/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
+static int
+pathname_is_dir(char *pathname)
+{
+ size_t l = strlen(pathname);
+
+ return l > 0 && pathname[l - 1] == '/';
+}
+
static int
-process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
+ int pflag, int rflag)
{
char *abs_src = NULL;
char *abs_dst = NULL;
- char *tmp;
glob_t g;
- int err = 0;
- int i;
+ char *filename, *tmp=NULL;
+ int i, err = 0;
abs_src = xstrdup(src);
abs_src = make_absolute(abs_src, pwd);
-
memset(&g, 0, sizeof(g));
+
debug3("Looking up %s", abs_src);
- if (remote_glob(conn, abs_src, 0, NULL, &g)) {
+ if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
error("File \"%s\" not found.", abs_src);
err = -1;
goto out;
}
- /* If multiple matches, dst must be a directory or unspecified */
- if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
- error("Multiple files match, but \"%s\" is not a directory",
- dst);
+ /*
+ * If multiple matches then dst must be a directory or
+ * unspecified.
+ */
+ if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
+ error("Multiple source paths, but destination "
+ "\"%s\" is not a directory", dst);
err = -1;
goto out;
}
for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
- if (infer_path(g.gl_pathv[i], &tmp)) {
+ tmp = xstrdup(g.gl_pathv[i]);
+ if ((filename = basename(tmp)) == NULL) {
+ error("basename %s: %s", tmp, strerror(errno));
+ xfree(tmp);
err = -1;
goto out;
}
if (g.gl_matchc == 1 && dst) {
- /* If directory specified, append filename */
- xfree(tmp);
if (is_dir(dst)) {
- if (infer_path(g.gl_pathv[0], &tmp)) {
- err = 1;
- goto out;
- }
- abs_dst = path_append(dst, tmp);
- xfree(tmp);
- } else
+ abs_dst = path_append(dst, filename);
+ } else {
abs_dst = xstrdup(dst);
+ }
} else if (dst) {
- abs_dst = path_append(dst, tmp);
- xfree(tmp);
- } else
- abs_dst = tmp;
+ abs_dst = path_append(dst, filename);
+ } else {
+ abs_dst = xstrdup(filename);
+ }
+ xfree(tmp);
printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
- if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
- err = -1;
+ if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
+ if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
+ pflag || global_pflag, 1) == -1)
+ err = -1;
+ } else {
+ if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
+ pflag || global_pflag) == -1)
+ err = -1;
+ }
xfree(abs_dst);
abs_dst = NULL;
}
@@ -535,14 +529,15 @@ out:
}
static int
-process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
+ int pflag, int rflag)
{
char *tmp_dst = NULL;
char *abs_dst = NULL;
- char *tmp;
+ char *tmp = NULL, *filename = NULL;
glob_t g;
int err = 0;
- int i;
+ int i, dst_is_dir = 1;
struct stat sb;
if (dst) {
@@ -552,16 +547,20 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
memset(&g, 0, sizeof(g));
debug3("Looking up %s", src);
- if (glob(src, GLOB_NOCHECK, NULL, &g)) {
+ if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
error("File \"%s\" not found.", src);
err = -1;
goto out;
}
+ /* If we aren't fetching to pwd then stash this status for later */
+ if (tmp_dst != NULL)
+ dst_is_dir = remote_is_dir(conn, tmp_dst);
+
/* If multiple matches, dst may be directory or unspecified */
- if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
- error("Multiple files match, but \"%s\" is not a directory",
- tmp_dst);
+ if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
+ error("Multiple paths match, but destination "
+ "\"%s\" is not a directory", tmp_dst);
err = -1;
goto out;
}
@@ -572,38 +571,38 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
error("stat %s: %s", g.gl_pathv[i], strerror(errno));
continue;
}
-
- if (!S_ISREG(sb.st_mode)) {
- error("skipping non-regular file %s",
- g.gl_pathv[i]);
- continue;
- }
- if (infer_path(g.gl_pathv[i], &tmp)) {
+
+ tmp = xstrdup(g.gl_pathv[i]);
+ if ((filename = basename(tmp)) == NULL) {
+ error("basename %s: %s", tmp, strerror(errno));
+ xfree(tmp);
err = -1;
goto out;
}
if (g.gl_matchc == 1 && tmp_dst) {
/* If directory specified, append filename */
- if (remote_is_dir(conn, tmp_dst)) {
- if (infer_path(g.gl_pathv[0], &tmp)) {
- err = 1;
- goto out;
- }
- abs_dst = path_append(tmp_dst, tmp);
- xfree(tmp);
- } else
+ if (dst_is_dir)
+ abs_dst = path_append(tmp_dst, filename);
+ else
abs_dst = xstrdup(tmp_dst);
-
} else if (tmp_dst) {
- abs_dst = path_append(tmp_dst, tmp);
- xfree(tmp);
- } else
- abs_dst = make_absolute(tmp, pwd);
+ abs_dst = path_append(tmp_dst, filename);
+ } else {
+ abs_dst = make_absolute(xstrdup(filename), pwd);
+ }
+ xfree(tmp);
printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
- if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
- err = -1;
+ if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
+ if (upload_dir(conn, g.gl_pathv[i], abs_dst,
+ pflag || global_pflag, 1) == -1)
+ err = -1;
+ } else {
+ if (do_upload(conn, g.gl_pathv[i], abs_dst,
+ pflag || global_pflag) == -1)
+ err = -1;
+ }
}
out:
@@ -1044,7 +1043,7 @@ makeargv(const char *arg, int *argcp)
}
static int
-parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
+parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, int *hflag,
unsigned long *n_arg, char **path1, char **path2)
{
const char *cmd, *cp = *cpp;
@@ -1088,13 +1087,13 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
}
/* Get arguments and parse flags */
- *lflag = *pflag = *hflag = *n_arg = 0;
+ *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
*path1 = *path2 = NULL;
optidx = 1;
switch (cmdnum) {
case I_GET:
case I_PUT:
- if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
+ if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
return -1;
/* Get first pathname (mandatory) */
if (argc - optidx < 1) {
@@ -1214,7 +1213,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
int err_abort)
{
char *path1, *path2, *tmp;
- int pflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
+ int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
unsigned long n_arg = 0;
Attrib a, *aa;
char path_buf[MAXPATHLEN];
@@ -1222,7 +1221,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
glob_t g;
path1 = path2 = NULL;
- cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg,
+ cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
&path1, &path2);
if (iflag != 0)
@@ -1240,10 +1239,10 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
err = -1;
break;
case I_GET:
- err = process_get(conn, path1, path2, *pwd, pflag);
+ err = process_get(conn, path1, path2, *pwd, pflag, rflag);
break;
case I_PUT:
- err = process_put(conn, path1, path2, *pwd, pflag);
+ err = process_put(conn, path1, path2, *pwd, pflag, rflag);
break;
case I_RENAME:
path1 = make_absolute(path1, *pwd);
@@ -1269,7 +1268,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
attrib_clear(&a);
a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
a.perm = 0777;
- err = do_mkdir(conn, path1, &a);
+ err = do_mkdir(conn, path1, &a, 1);
break;
case I_RMDIR:
path1 = make_absolute(path1, *pwd);
@@ -1622,7 +1621,7 @@ usage(void)
extern char *__progname;
fprintf(stderr,
- "usage: %s [-1246Cqv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
+ "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
" [-D sftp_server_path] [-F ssh_config] "
"[-i identity_file]\n"
" [-o ssh_option] [-P port] [-R num_requests] "
@@ -1663,7 +1662,7 @@ main(int argc, char **argv)
infile = stdin;
while ((ch = getopt(argc, argv,
- "1246hqvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
+ "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
switch (ch) {
/* Passed through to ssh(1) */
case '4':
@@ -1717,9 +1716,15 @@ main(int argc, char **argv)
batchmode = 1;
addargs(&args, "-obatchmode yes");
break;
+ case 'p':
+ global_pflag = 1;
+ break;
case 'D':
sftp_direct = optarg;
break;
+ case 'r':
+ global_rflag = 1;
+ break;
case 'R':
num_requests = strtol(optarg, &cp, 10);
if (num_requests == 0 || *cp != '\0')