From 1b0dd175377c86abb7f3a3da2e9b1a89f36c7a1a Mon Sep 17 00:00:00 2001 From: Darren Tucker Date: Wed, 7 Oct 2009 08:37:48 +1100 Subject: - 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@ --- ChangeLog | 5 ++ sftp-client.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- sftp-client.h | 21 ++++- sftp.1 | 44 ++++++++-- sftp.c | 219 +++++++++++++++++++++++++------------------------ 5 files changed, 423 insertions(+), 126 deletions(-) diff --git a/ChangeLog b/ChangeLog index 60eae3a42..2fedeccbd 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 14c172d2f..cc4a5b15b 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 * @@ -36,6 +36,7 @@ #endif #include +#include #include #include #include @@ -61,6 +62,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; @@ -497,6 +501,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)); @@ -505,7 +520,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); } @@ -560,7 +575,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; @@ -569,7 +584,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); @@ -908,9 +923,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; @@ -929,9 +944,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) @@ -1146,6 +1160,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) @@ -1328,3 +1450,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/sftp-client.h b/sftp-client.h index edb46790f..1d08c4049 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 @@ -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/sftp.1 b/sftp.1 index fcd1d240a..21dc5d8d6 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 @@ 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/sftp.c b/sftp.c index 0123fd72c..75b16b27e 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 * @@ -35,6 +35,9 @@ #ifdef HAVE_PATHS_H # include #endif +#ifdef HAVE_LIBGEN_H +#include +#endif #ifdef USE_LIBEDIT #include #else @@ -83,6 +86,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; @@ -216,7 +225,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" @@ -227,7 +236,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" @@ -313,21 +322,6 @@ path_strip(char *path, char *strip) return (xstrdup(path)); } -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) { @@ -343,27 +337,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; @@ -371,13 +346,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; @@ -489,62 +468,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; } @@ -556,14 +552,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) { @@ -573,16 +570,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; } @@ -593,38 +594,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: @@ -1065,7 +1066,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; @@ -1109,13 +1110,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) { @@ -1235,7 +1236,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]; @@ -1243,7 +1244,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) @@ -1261,10 +1262,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); @@ -1290,7 +1291,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); @@ -1668,7 +1669,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] " @@ -1710,7 +1711,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': @@ -1764,9 +1765,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') -- cgit v1.2.3