- djm@cvs.openbsd.org 2009/08/18 18:36:21
[sftp-client.h sftp.1 sftp-client.c sftp.c]
recursive transfer support for get/put and on the commandline
work mostly by carlosvsilvapt@gmail.com for the Google Summer of Code
with some tweaks by me; "go for it" deraadt@
diff --git a/ChangeLog b/ChangeLog
index 60eae3a..2fedecc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -32,6 +32,11 @@
- dtucker@cvs.openbsd.org 2009/08/16 23:29:26
[sshd_config.5]
Add PubkeyAuthentication to the list allowed in a Match block (bz #1577)
+ - djm@cvs.openbsd.org 2009/08/18 18:36:21
+ [sftp-client.h sftp.1 sftp-client.c sftp.c]
+ recursive transfer support for get/put and on the commandline
+ work mostly by carlosvsilvapt@gmail.com for the Google Summer of Code
+ with some tweaks by me; "go for it" deraadt@
20091002
- (djm) [Makefile.in] Mention readconf.o in ssh-keysign's make deps.
diff --git a/sftp-client.c b/sftp-client.c
index 14c172d..cc4a5b1 100644
--- a/sftp-client.c
+++ b/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>
*
@@ -36,6 +36,7 @@
#endif
#include <sys/uio.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
@@ -61,6 +62,9 @@
/* 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;
@@ -497,6 +501,17 @@
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));
@@ -505,7 +520,7 @@
memcpy(&(*dir)[ents]->a, a, sizeof(*a));
(*dir)[++ents] = NULL;
}
-
+ next:
xfree(filename);
xfree(longname);
}
@@ -560,7 +575,7 @@
}
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;
@@ -569,7 +584,7 @@
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);
@@ -908,9 +923,9 @@
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;
@@ -929,9 +944,8 @@
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)
@@ -1146,6 +1160,114 @@
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)
@@ -1328,3 +1450,123 @@
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/sftp-client.h b/sftp-client.h
index edb4679..1d08c40 100644
--- a/sftp-client.h
+++ b/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 @@
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 @@
* 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_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/sftp.1 b/sftp.1
index fcd1d24..21dc5d8 100644
--- a/sftp.1
+++ b/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 @@
.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 @@
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 @@
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 @@
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 @@
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/sftp.c b/sftp.c
index 0123fd7..75b16b2 100644
--- a/sftp.c
+++ b/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>
*
@@ -35,6 +35,9 @@
#ifdef HAVE_PATHS_H
# include <paths.h>
#endif
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
#ifdef USE_LIBEDIT
#include <histedit.h>
#else
@@ -83,6 +86,12 @@
/* 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;
@@ -216,7 +225,7 @@
"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"
@@ -227,7 +236,7 @@
"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"
@@ -314,21 +323,6 @@
}
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;
@@ -343,27 +337,8 @@
}
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;
@@ -371,13 +346,17 @@
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;
@@ -489,62 +468,79 @@
return(S_ISDIR(a->perm));
}
+/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
static int
-process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+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, 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;
}
@@ -556,14 +552,15 @@
}
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) {
@@ -573,16 +570,20 @@
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;
}
@@ -593,38 +594,38 @@
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:
@@ -1065,7 +1066,7 @@
}
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;
@@ -1109,13 +1110,13 @@
}
/* 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) {
@@ -1235,7 +1236,7 @@
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];
@@ -1243,7 +1244,7 @@
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)
@@ -1261,10 +1262,10 @@
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);
@@ -1290,7 +1291,7 @@
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);
@@ -1668,7 +1669,7 @@
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] "
@@ -1710,7 +1711,7 @@
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':
@@ -1764,9 +1765,15 @@
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')