summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarren Tucker <dtucker@zip.com.au>2009-10-07 08:37:48 +1100
committerDarren Tucker <dtucker@zip.com.au>2009-10-07 08:37:48 +1100
commit1b0dd175377c86abb7f3a3da2e9b1a89f36c7a1a (patch)
treed6b5f501c4c9890ed91ce4fd86be583213d7e8e6
parent1477ea162c05c09b4b5ebc19ac588fbf469349dc (diff)
- 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@
-rw-r--r--ChangeLog5
-rw-r--r--sftp-client.c260
-rw-r--r--sftp-client.h21
-rw-r--r--sftp.144
-rw-r--r--sftp.c219
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 @@
32 - dtucker@cvs.openbsd.org 2009/08/16 23:29:26 32 - dtucker@cvs.openbsd.org 2009/08/16 23:29:26
33 [sshd_config.5] 33 [sshd_config.5]
34 Add PubkeyAuthentication to the list allowed in a Match block (bz #1577) 34 Add PubkeyAuthentication to the list allowed in a Match block (bz #1577)
35 - djm@cvs.openbsd.org 2009/08/18 18:36:21
36 [sftp-client.h sftp.1 sftp-client.c sftp.c]
37 recursive transfer support for get/put and on the commandline
38 work mostly by carlosvsilvapt@gmail.com for the Google Summer of Code
39 with some tweaks by me; "go for it" deraadt@
35 40
3620091002 4120091002
37 - (djm) [Makefile.in] Mention readconf.o in ssh-keysign's make deps. 42 - (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 @@
1/* $OpenBSD: sftp-client.c,v 1.88 2009/08/14 18:17:49 djm Exp $ */ 1/* $OpenBSD: sftp-client.c,v 1.89 2009/08/18 18:36:20 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
4 * 4 *
@@ -36,6 +36,7 @@
36#endif 36#endif
37#include <sys/uio.h> 37#include <sys/uio.h>
38 38
39#include <dirent.h>
39#include <errno.h> 40#include <errno.h>
40#include <fcntl.h> 41#include <fcntl.h>
41#include <signal.h> 42#include <signal.h>
@@ -61,6 +62,9 @@ extern int showprogress;
61/* Minimum amount of data to read at a time */ 62/* Minimum amount of data to read at a time */
62#define MIN_READ_SIZE 512 63#define MIN_READ_SIZE 512
63 64
65/* Maximum depth to descend in directory trees */
66#define MAX_DIR_DEPTH 64
67
64struct sftp_conn { 68struct sftp_conn {
65 int fd_in; 69 int fd_in;
66 int fd_out; 70 int fd_out;
@@ -497,6 +501,17 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
497 if (printflag) 501 if (printflag)
498 printf("%s\n", longname); 502 printf("%s\n", longname);
499 503
504 /*
505 * Directory entries should never contain '/'
506 * These can be used to attack recursive ops
507 * (e.g. send '../../../../etc/passwd')
508 */
509 if (strchr(filename, '/') != NULL) {
510 error("Server sent suspect path \"%s\" "
511 "during readdir of \"%s\"", filename, path);
512 goto next;
513 }
514
500 if (dir) { 515 if (dir) {
501 *dir = xrealloc(*dir, ents + 2, sizeof(**dir)); 516 *dir = xrealloc(*dir, ents + 2, sizeof(**dir));
502 (*dir)[ents] = xmalloc(sizeof(***dir)); 517 (*dir)[ents] = xmalloc(sizeof(***dir));
@@ -505,7 +520,7 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
505 memcpy(&(*dir)[ents]->a, a, sizeof(*a)); 520 memcpy(&(*dir)[ents]->a, a, sizeof(*a));
506 (*dir)[++ents] = NULL; 521 (*dir)[++ents] = NULL;
507 } 522 }
508 523 next:
509 xfree(filename); 524 xfree(filename);
510 xfree(longname); 525 xfree(longname);
511 } 526 }
@@ -560,7 +575,7 @@ do_rm(struct sftp_conn *conn, char *path)
560} 575}
561 576
562int 577int
563do_mkdir(struct sftp_conn *conn, char *path, Attrib *a) 578do_mkdir(struct sftp_conn *conn, char *path, Attrib *a, int printflag)
564{ 579{
565 u_int status, id; 580 u_int status, id;
566 581
@@ -569,7 +584,7 @@ do_mkdir(struct sftp_conn *conn, char *path, Attrib *a)
569 strlen(path), a); 584 strlen(path), a);
570 585
571 status = get_status(conn->fd_in, id); 586 status = get_status(conn->fd_in, id);
572 if (status != SSH2_FX_OK) 587 if (status != SSH2_FX_OK && printflag)
573 error("Couldn't create directory: %s", fx2txt(status)); 588 error("Couldn't create directory: %s", fx2txt(status));
574 589
575 return(status); 590 return(status);
@@ -908,9 +923,9 @@ send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len,
908 923
909int 924int
910do_download(struct sftp_conn *conn, char *remote_path, char *local_path, 925do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
911 int pflag) 926 Attrib *a, int pflag)
912{ 927{
913 Attrib junk, *a; 928 Attrib junk;
914 Buffer msg; 929 Buffer msg;
915 char *handle; 930 char *handle;
916 int local_fd, status = 0, write_error; 931 int local_fd, status = 0, write_error;
@@ -929,9 +944,8 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
929 944
930 TAILQ_INIT(&requests); 945 TAILQ_INIT(&requests);
931 946
932 a = do_stat(conn, remote_path, 0); 947 if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
933 if (a == NULL) 948 return -1;
934 return(-1);
935 949
936 /* Do not preserve set[ug]id here, as we do not preserve ownership */ 950 /* Do not preserve set[ug]id here, as we do not preserve ownership */
937 if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) 951 if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
@@ -1146,6 +1160,114 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
1146 return(status); 1160 return(status);
1147} 1161}
1148 1162
1163static int
1164download_dir_internal(struct sftp_conn *conn, char *src, char *dst,
1165 Attrib *dirattrib, int pflag, int printflag, int depth)
1166{
1167 int i, ret = 0;
1168 SFTP_DIRENT **dir_entries;
1169 char *filename, *new_src, *new_dst;
1170 mode_t mode = 0777;
1171
1172 if (depth >= MAX_DIR_DEPTH) {
1173 error("Maximum directory depth exceeded: %d levels", depth);
1174 return -1;
1175 }
1176
1177 if (dirattrib == NULL &&
1178 (dirattrib = do_stat(conn, src, 1)) == NULL) {
1179 error("Unable to stat remote directory \"%s\"", src);
1180 return -1;
1181 }
1182 if (!S_ISDIR(dirattrib->perm)) {
1183 error("\"%s\" is not a directory", src);
1184 return -1;
1185 }
1186 if (printflag)
1187 printf("Retrieving %s\n", src);
1188
1189 if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
1190 mode = dirattrib->perm & 01777;
1191 else {
1192 debug("Server did not send permissions for "
1193 "directory \"%s\"", dst);
1194 }
1195
1196 if (mkdir(dst, mode) == -1 && errno != EEXIST) {
1197 error("mkdir %s: %s", dst, strerror(errno));
1198 return -1;
1199 }
1200
1201 if (do_readdir(conn, src, &dir_entries) == -1) {
1202 error("%s: Failed to get directory contents", src);
1203 return -1;
1204 }
1205
1206 for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
1207 filename = dir_entries[i]->filename;
1208
1209 new_dst = path_append(dst, filename);
1210 new_src = path_append(src, filename);
1211
1212 if (S_ISDIR(dir_entries[i]->a.perm)) {
1213 if (strcmp(filename, ".") == 0 ||
1214 strcmp(filename, "..") == 0)
1215 continue;
1216 if (download_dir_internal(conn, new_src, new_dst,
1217 &(dir_entries[i]->a), pflag, printflag,
1218 depth + 1) == -1)
1219 ret = -1;
1220 } else if (S_ISREG(dir_entries[i]->a.perm) ) {
1221 if (do_download(conn, new_src, new_dst,
1222 &(dir_entries[i]->a), pflag) == -1) {
1223 error("Download of file %s to %s failed",
1224 new_src, new_dst);
1225 ret = -1;
1226 }
1227 } else
1228 logit("%s: not a regular file\n", new_src);
1229
1230 xfree(new_dst);
1231 xfree(new_src);
1232 }
1233
1234 if (pflag) {
1235 if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
1236 struct timeval tv[2];
1237 tv[0].tv_sec = dirattrib->atime;
1238 tv[1].tv_sec = dirattrib->mtime;
1239 tv[0].tv_usec = tv[1].tv_usec = 0;
1240 if (utimes(dst, tv) == -1)
1241 error("Can't set times on \"%s\": %s",
1242 dst, strerror(errno));
1243 } else
1244 debug("Server did not send times for directory "
1245 "\"%s\"", dst);
1246 }
1247
1248 free_sftp_dirents(dir_entries);
1249
1250 return ret;
1251}
1252
1253int
1254download_dir(struct sftp_conn *conn, char *src, char *dst,
1255 Attrib *dirattrib, int pflag, int printflag)
1256{
1257 char *src_canon;
1258 int ret;
1259
1260 if ((src_canon = do_realpath(conn, src)) == NULL) {
1261 error("Unable to canonicalise path \"%s\"", src);
1262 return -1;
1263 }
1264
1265 ret = download_dir_internal(conn, src_canon, dst,
1266 dirattrib, pflag, printflag, 0);
1267 xfree(src_canon);
1268 return ret;
1269}
1270
1149int 1271int
1150do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, 1272do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
1151 int pflag) 1273 int pflag)
@@ -1328,3 +1450,123 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
1328 1450
1329 return status; 1451 return status;
1330} 1452}
1453
1454static int
1455upload_dir_internal(struct sftp_conn *conn, char *src, char *dst,
1456 int pflag, int printflag, int depth)
1457{
1458 int ret = 0, status;
1459 DIR *dirp;
1460 struct dirent *dp;
1461 char *filename, *new_src, *new_dst;
1462 struct stat sb;
1463 Attrib a;
1464
1465 if (depth >= MAX_DIR_DEPTH) {
1466 error("Maximum directory depth exceeded: %d levels", depth);
1467 return -1;
1468 }
1469
1470 if (stat(src, &sb) == -1) {
1471 error("Couldn't stat directory \"%s\": %s",
1472 src, strerror(errno));
1473 return -1;
1474 }
1475 if (!S_ISDIR(sb.st_mode)) {
1476 error("\"%s\" is not a directory", src);
1477 return -1;
1478 }
1479 if (printflag)
1480 printf("Entering %s\n", src);
1481
1482 attrib_clear(&a);
1483 stat_to_attrib(&sb, &a);
1484 a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
1485 a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
1486 a.perm &= 01777;
1487 if (!pflag)
1488 a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
1489
1490 status = do_mkdir(conn, dst, &a, 0);
1491 /*
1492 * we lack a portable status for errno EEXIST,
1493 * so if we get a SSH2_FX_FAILURE back we must check
1494 * if it was created successfully.
1495 */
1496 if (status != SSH2_FX_OK) {
1497 if (status != SSH2_FX_FAILURE)
1498 return -1;
1499 if (do_stat(conn, dst, 0) == NULL)
1500 return -1;
1501 }
1502
1503 if ((dirp = opendir(src)) == NULL) {
1504 error("Failed to open dir \"%s\": %s", src, strerror(errno));
1505 return -1;
1506 }
1507
1508 while (((dp = readdir(dirp)) != NULL) && !interrupted) {
1509 if (dp->d_ino == 0)
1510 continue;
1511 filename = dp->d_name;
1512 new_dst = path_append(dst, filename);
1513 new_src = path_append(src, filename);
1514
1515 if (S_ISDIR(DTTOIF(dp->d_type))) {
1516 if (strcmp(filename, ".") == 0 ||
1517 strcmp(filename, "..") == 0)
1518 continue;
1519
1520 if (upload_dir_internal(conn, new_src, new_dst,
1521 pflag, depth + 1, printflag) == -1)
1522 ret = -1;
1523 } else if (S_ISREG(DTTOIF(dp->d_type)) ) {
1524 if (do_upload(conn, new_src, new_dst, pflag) == -1) {
1525 error("Uploading of file %s to %s failed!",
1526 new_src, new_dst);
1527 ret = -1;
1528 }
1529 } else
1530 logit("%s: not a regular file\n", filename);
1531 xfree(new_dst);
1532 xfree(new_src);
1533 }
1534
1535 do_setstat(conn, dst, &a);
1536
1537 (void) closedir(dirp);
1538 return ret;
1539}
1540
1541int
1542upload_dir(struct sftp_conn *conn, char *src, char *dst, int printflag,
1543 int pflag)
1544{
1545 char *dst_canon;
1546 int ret;
1547
1548 if ((dst_canon = do_realpath(conn, dst)) == NULL) {
1549 error("Unable to canonicalise path \"%s\"", dst);
1550 return -1;
1551 }
1552
1553 ret = upload_dir_internal(conn, src, dst_canon, pflag, printflag, 0);
1554 xfree(dst_canon);
1555 return ret;
1556}
1557
1558char *
1559path_append(char *p1, char *p2)
1560{
1561 char *ret;
1562 size_t len = strlen(p1) + strlen(p2) + 2;
1563
1564 ret = xmalloc(len);
1565 strlcpy(ret, p1, len);
1566 if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
1567 strlcat(ret, "/", len);
1568 strlcat(ret, p2, len);
1569
1570 return(ret);
1571}
1572
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 @@
1/* $OpenBSD: sftp-client.h,v 1.17 2008/06/08 20:15:29 dtucker Exp $ */ 1/* $OpenBSD: sftp-client.h,v 1.18 2009/08/18 18:36:20 djm Exp $ */
2 2
3/* 3/*
4 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 4 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
@@ -68,7 +68,7 @@ void free_sftp_dirents(SFTP_DIRENT **);
68int do_rm(struct sftp_conn *, char *); 68int do_rm(struct sftp_conn *, char *);
69 69
70/* Create directory 'path' */ 70/* Create directory 'path' */
71int do_mkdir(struct sftp_conn *, char *, Attrib *); 71int do_mkdir(struct sftp_conn *, char *, Attrib *, int);
72 72
73/* Remove directory 'path' */ 73/* Remove directory 'path' */
74int do_rmdir(struct sftp_conn *, char *); 74int do_rmdir(struct sftp_conn *, char *);
@@ -103,7 +103,13 @@ int do_symlink(struct sftp_conn *, char *, char *);
103 * Download 'remote_path' to 'local_path'. Preserve permissions and times 103 * Download 'remote_path' to 'local_path'. Preserve permissions and times
104 * if 'pflag' is set 104 * if 'pflag' is set
105 */ 105 */
106int do_download(struct sftp_conn *, char *, char *, int); 106int do_download(struct sftp_conn *, char *, char *, Attrib *, int);
107
108/*
109 * Recursively download 'remote_directory' to 'local_directory'. Preserve
110 * times if 'pflag' is set
111 */
112int download_dir(struct sftp_conn *, char *, char *, Attrib *, int, int);
107 113
108/* 114/*
109 * Upload 'local_path' to 'remote_path'. Preserve permissions and times 115 * Upload 'local_path' to 'remote_path'. Preserve permissions and times
@@ -111,4 +117,13 @@ int do_download(struct sftp_conn *, char *, char *, int);
111 */ 117 */
112int do_upload(struct sftp_conn *, char *, char *, int); 118int do_upload(struct sftp_conn *, char *, char *, int);
113 119
120/*
121 * Recursively upload 'local_directory' to 'remote_directory'. Preserve
122 * times if 'pflag' is set
123 */
124int upload_dir(struct sftp_conn *, char *, char *, int, int);
125
126/* Concatenate paths, taking care of slashes. Caller must free result. */
127char *path_append(char *, char *);
128
114#endif 129#endif
diff --git a/sftp.1 b/sftp.1
index fcd1d240a..21dc5d8d6 100644
--- a/sftp.1
+++ b/sftp.1
@@ -1,4 +1,4 @@
1.\" $OpenBSD: sftp.1,v 1.73 2009/08/13 13:39:54 jmc Exp $ 1.\" $OpenBSD: sftp.1,v 1.74 2009/08/18 18:36:20 djm Exp $
2.\" 2.\"
3.\" Copyright (c) 2001 Damien Miller. All rights reserved. 3.\" Copyright (c) 2001 Damien Miller. All rights reserved.
4.\" 4.\"
@@ -22,7 +22,7 @@
22.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24.\" 24.\"
25.Dd $Mdocdate: August 13 2009 $ 25.Dd $Mdocdate: August 18 2009 $
26.Dt SFTP 1 26.Dt SFTP 1
27.Os 27.Os
28.Sh NAME 28.Sh NAME
@@ -31,7 +31,7 @@
31.Sh SYNOPSIS 31.Sh SYNOPSIS
32.Nm sftp 32.Nm sftp
33.Bk -words 33.Bk -words
34.Op Fl 1246Cqv 34.Op Fl 1246Cpqrv
35.Op Fl B Ar buffer_size 35.Op Fl B Ar buffer_size
36.Op Fl b Ar batchfile 36.Op Fl b Ar batchfile
37.Op Fl c Ar cipher 37.Op Fl c Ar cipher
@@ -223,6 +223,9 @@ For full details of the options listed below, and their possible values, see
223.El 223.El
224.It Fl P Ar port 224.It Fl P Ar port
225Specifies the port to connect to on the remote host. 225Specifies the port to connect to on the remote host.
226.It Fl p
227Preserves modification times, access times, and modes from the
228original files transferred.
226.It Fl q 229.It Fl q
227Quiet mode: disables the progress meter as well as warning and 230Quiet mode: disables the progress meter as well as warning and
228diagnostic messages from 231diagnostic messages from
@@ -232,6 +235,11 @@ Specify how many requests may be outstanding at any one time.
232Increasing this may slightly improve file transfer speed 235Increasing this may slightly improve file transfer speed
233but will increase memory usage. 236but will increase memory usage.
234The default is 64 outstanding requests. 237The default is 64 outstanding requests.
238.It Fl r
239Recursively copy entire directories when uploading and downloading.
240Note that
241.Nm
242does not follow symbolic links encountered in the tree traversal.
235.It Fl S Ar program 243.It Fl S Ar program
236Name of the 244Name of the
237.Ar program 245.Ar program
@@ -322,7 +330,7 @@ extension.
322Quit 330Quit
323.Nm sftp . 331.Nm sftp .
324.It Xo Ic get 332.It Xo Ic get
325.Op Fl P 333.Op Fl Ppr
326.Ar remote-path 334.Ar remote-path
327.Op Ar local-path 335.Op Ar local-path
328.Xc 336.Xc
@@ -341,10 +349,20 @@ If it does and
341is specified, then 349is specified, then
342.Ar local-path 350.Ar local-path
343must specify a directory. 351must specify a directory.
344If the 352.Pp
345.Fl P 353If ether the
354.Fl Ppr
355or
356.Fl p
346flag is specified, then full file permissions and access times are 357flag is specified, then full file permissions and access times are
347copied too. 358copied too.
359.Pp
360If the
361.Fl r
362flag is specified then directories will be copied recursively.
363Note that
364.Nm
365does not follow symbolic links when performing recursive transfers.
348.It Ic help 366.It Ic help
349Display help text. 367Display help text.
350.It Ic lcd Ar path 368.It Ic lcd Ar path
@@ -440,10 +458,20 @@ If it does and
440is specified, then 458is specified, then
441.Ar remote-path 459.Ar remote-path
442must specify a directory. 460must specify a directory.
443If the 461.Pp
462If ether the
444.Fl P 463.Fl P
445flag is specified, then the file's full permission and access time are 464or
465.Fl p
466flag is specified, then full file permissions and access times are
446copied too. 467copied too.
468.Pp
469If the
470.Fl r
471flag is specified then directories will be copied recursively.
472Note that
473.Nm
474does not follow symbolic links when performing recursive transfers.
447.It Ic pwd 475.It Ic pwd
448Display remote working directory. 476Display remote working directory.
449.It Ic quit 477.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 @@
1/* $OpenBSD: sftp.c,v 1.110 2009/08/13 13:39:54 jmc Exp $ */ 1/* $OpenBSD: sftp.c,v 1.111 2009/08/18 18:36:21 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
4 * 4 *
@@ -35,6 +35,9 @@
35#ifdef HAVE_PATHS_H 35#ifdef HAVE_PATHS_H
36# include <paths.h> 36# include <paths.h>
37#endif 37#endif
38#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
38#ifdef USE_LIBEDIT 41#ifdef USE_LIBEDIT
39#include <histedit.h> 42#include <histedit.h>
40#else 43#else
@@ -83,6 +86,12 @@ static pid_t sshpid = -1;
83/* This is set to 0 if the progressmeter is not desired. */ 86/* This is set to 0 if the progressmeter is not desired. */
84int showprogress = 1; 87int showprogress = 1;
85 88
89/* When this option is set, we always recursively download/upload directories */
90int global_rflag = 0;
91
92/* When this option is set, the file transfers will always preserve times */
93int global_pflag = 0;
94
86/* SIGINT received during command processing */ 95/* SIGINT received during command processing */
87volatile sig_atomic_t interrupted = 0; 96volatile sig_atomic_t interrupted = 0;
88 97
@@ -216,7 +225,7 @@ help(void)
216 "df [-hi] [path] Display statistics for current directory or\n" 225 "df [-hi] [path] Display statistics for current directory or\n"
217 " filesystem containing 'path'\n" 226 " filesystem containing 'path'\n"
218 "exit Quit sftp\n" 227 "exit Quit sftp\n"
219 "get [-P] remote-path [local-path] Download file\n" 228 "get [-Pr] remote-path [local-path] Download file\n"
220 "help Display this help text\n" 229 "help Display this help text\n"
221 "lcd path Change local directory to 'path'\n" 230 "lcd path Change local directory to 'path'\n"
222 "lls [ls-options [path]] Display local directory listing\n" 231 "lls [ls-options [path]] Display local directory listing\n"
@@ -227,7 +236,7 @@ help(void)
227 "lumask umask Set local umask to 'umask'\n" 236 "lumask umask Set local umask to 'umask'\n"
228 "mkdir path Create remote directory\n" 237 "mkdir path Create remote directory\n"
229 "progress Toggle display of progress meter\n" 238 "progress Toggle display of progress meter\n"
230 "put [-P] local-path [remote-path] Upload file\n" 239 "put [-Pr] local-path [remote-path] Upload file\n"
231 "pwd Display remote working directory\n" 240 "pwd Display remote working directory\n"
232 "quit Quit sftp\n" 241 "quit Quit sftp\n"
233 "rename oldpath newpath Rename remote file\n" 242 "rename oldpath newpath Rename remote file\n"
@@ -314,21 +323,6 @@ path_strip(char *path, char *strip)
314} 323}
315 324
316static char * 325static char *
317path_append(char *p1, char *p2)
318{
319 char *ret;
320 size_t len = strlen(p1) + strlen(p2) + 2;
321
322 ret = xmalloc(len);
323 strlcpy(ret, p1, len);
324 if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
325 strlcat(ret, "/", len);
326 strlcat(ret, p2, len);
327
328 return(ret);
329}
330
331static char *
332make_absolute(char *p, char *pwd) 326make_absolute(char *p, char *pwd)
333{ 327{
334 char *abs_str; 328 char *abs_str;
@@ -343,27 +337,8 @@ make_absolute(char *p, char *pwd)
343} 337}
344 338
345static int 339static int
346infer_path(const char *p, char **ifp) 340parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
347{ 341 int *rflag)
348 char *cp;
349
350 cp = strrchr(p, '/');
351 if (cp == NULL) {
352 *ifp = xstrdup(p);
353 return(0);
354 }
355
356 if (!cp[1]) {
357 error("Invalid path");
358 return(-1);
359 }
360
361 *ifp = xstrdup(cp + 1);
362 return(0);
363}
364
365static int
366parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
367{ 342{
368 extern int opterr, optind, optopt, optreset; 343 extern int opterr, optind, optopt, optreset;
369 int ch; 344 int ch;
@@ -371,13 +346,17 @@ parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
371 optind = optreset = 1; 346 optind = optreset = 1;
372 opterr = 0; 347 opterr = 0;
373 348
374 *pflag = 0; 349 *rflag = *pflag = 0;
375 while ((ch = getopt(argc, argv, "Pp")) != -1) { 350 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
376 switch (ch) { 351 switch (ch) {
377 case 'p': 352 case 'p':
378 case 'P': 353 case 'P':
379 *pflag = 1; 354 *pflag = 1;
380 break; 355 break;
356 case 'r':
357 case 'R':
358 *rflag = 1;
359 break;
381 default: 360 default:
382 error("%s: Invalid flag -%c", cmd, optopt); 361 error("%s: Invalid flag -%c", cmd, optopt);
383 return -1; 362 return -1;
@@ -489,62 +468,79 @@ remote_is_dir(struct sftp_conn *conn, char *path)
489 return(S_ISDIR(a->perm)); 468 return(S_ISDIR(a->perm));
490} 469}
491 470
471/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
472static int
473pathname_is_dir(char *pathname)
474{
475 size_t l = strlen(pathname);
476
477 return l > 0 && pathname[l - 1] == '/';
478}
479
492static int 480static int
493process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) 481process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
482 int pflag, int rflag)
494{ 483{
495 char *abs_src = NULL; 484 char *abs_src = NULL;
496 char *abs_dst = NULL; 485 char *abs_dst = NULL;
497 char *tmp;
498 glob_t g; 486 glob_t g;
499 int err = 0; 487 char *filename, *tmp=NULL;
500 int i; 488 int i, err = 0;
501 489
502 abs_src = xstrdup(src); 490 abs_src = xstrdup(src);
503 abs_src = make_absolute(abs_src, pwd); 491 abs_src = make_absolute(abs_src, pwd);
504
505 memset(&g, 0, sizeof(g)); 492 memset(&g, 0, sizeof(g));
493
506 debug3("Looking up %s", abs_src); 494 debug3("Looking up %s", abs_src);
507 if (remote_glob(conn, abs_src, 0, NULL, &g)) { 495 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
508 error("File \"%s\" not found.", abs_src); 496 error("File \"%s\" not found.", abs_src);
509 err = -1; 497 err = -1;
510 goto out; 498 goto out;
511 } 499 }
512 500
513 /* If multiple matches, dst must be a directory or unspecified */ 501 /*
514 if (g.gl_matchc > 1 && dst && !is_dir(dst)) { 502 * If multiple matches then dst must be a directory or
515 error("Multiple files match, but \"%s\" is not a directory", 503 * unspecified.
516 dst); 504 */
505 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
506 error("Multiple source paths, but destination "
507 "\"%s\" is not a directory", dst);
517 err = -1; 508 err = -1;
518 goto out; 509 goto out;
519 } 510 }
520 511
521 for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 512 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
522 if (infer_path(g.gl_pathv[i], &tmp)) { 513 tmp = xstrdup(g.gl_pathv[i]);
514 if ((filename = basename(tmp)) == NULL) {
515 error("basename %s: %s", tmp, strerror(errno));
516 xfree(tmp);
523 err = -1; 517 err = -1;
524 goto out; 518 goto out;
525 } 519 }
526 520
527 if (g.gl_matchc == 1 && dst) { 521 if (g.gl_matchc == 1 && dst) {
528 /* If directory specified, append filename */
529 xfree(tmp);
530 if (is_dir(dst)) { 522 if (is_dir(dst)) {
531 if (infer_path(g.gl_pathv[0], &tmp)) { 523 abs_dst = path_append(dst, filename);
532 err = 1; 524 } else {
533 goto out;
534 }
535 abs_dst = path_append(dst, tmp);
536 xfree(tmp);
537 } else
538 abs_dst = xstrdup(dst); 525 abs_dst = xstrdup(dst);
526 }
539 } else if (dst) { 527 } else if (dst) {
540 abs_dst = path_append(dst, tmp); 528 abs_dst = path_append(dst, filename);
541 xfree(tmp); 529 } else {
542 } else 530 abs_dst = xstrdup(filename);
543 abs_dst = tmp; 531 }
532 xfree(tmp);
544 533
545 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); 534 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
546 if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1) 535 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
547 err = -1; 536 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
537 pflag || global_pflag, 1) == -1)
538 err = -1;
539 } else {
540 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
541 pflag || global_pflag) == -1)
542 err = -1;
543 }
548 xfree(abs_dst); 544 xfree(abs_dst);
549 abs_dst = NULL; 545 abs_dst = NULL;
550 } 546 }
@@ -556,14 +552,15 @@ out:
556} 552}
557 553
558static int 554static int
559process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) 555process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
556 int pflag, int rflag)
560{ 557{
561 char *tmp_dst = NULL; 558 char *tmp_dst = NULL;
562 char *abs_dst = NULL; 559 char *abs_dst = NULL;
563 char *tmp; 560 char *tmp = NULL, *filename = NULL;
564 glob_t g; 561 glob_t g;
565 int err = 0; 562 int err = 0;
566 int i; 563 int i, dst_is_dir = 1;
567 struct stat sb; 564 struct stat sb;
568 565
569 if (dst) { 566 if (dst) {
@@ -573,16 +570,20 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
573 570
574 memset(&g, 0, sizeof(g)); 571 memset(&g, 0, sizeof(g));
575 debug3("Looking up %s", src); 572 debug3("Looking up %s", src);
576 if (glob(src, GLOB_NOCHECK, NULL, &g)) { 573 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
577 error("File \"%s\" not found.", src); 574 error("File \"%s\" not found.", src);
578 err = -1; 575 err = -1;
579 goto out; 576 goto out;
580 } 577 }
581 578
579 /* If we aren't fetching to pwd then stash this status for later */
580 if (tmp_dst != NULL)
581 dst_is_dir = remote_is_dir(conn, tmp_dst);
582
582 /* If multiple matches, dst may be directory or unspecified */ 583 /* If multiple matches, dst may be directory or unspecified */
583 if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) { 584 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
584 error("Multiple files match, but \"%s\" is not a directory", 585 error("Multiple paths match, but destination "
585 tmp_dst); 586 "\"%s\" is not a directory", tmp_dst);
586 err = -1; 587 err = -1;
587 goto out; 588 goto out;
588 } 589 }
@@ -593,38 +594,38 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
593 error("stat %s: %s", g.gl_pathv[i], strerror(errno)); 594 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
594 continue; 595 continue;
595 } 596 }
596 597
597 if (!S_ISREG(sb.st_mode)) { 598 tmp = xstrdup(g.gl_pathv[i]);
598 error("skipping non-regular file %s", 599 if ((filename = basename(tmp)) == NULL) {
599 g.gl_pathv[i]); 600 error("basename %s: %s", tmp, strerror(errno));
600 continue; 601 xfree(tmp);
601 }
602 if (infer_path(g.gl_pathv[i], &tmp)) {
603 err = -1; 602 err = -1;
604 goto out; 603 goto out;
605 } 604 }
606 605
607 if (g.gl_matchc == 1 && tmp_dst) { 606 if (g.gl_matchc == 1 && tmp_dst) {
608 /* If directory specified, append filename */ 607 /* If directory specified, append filename */
609 if (remote_is_dir(conn, tmp_dst)) { 608 if (dst_is_dir)
610 if (infer_path(g.gl_pathv[0], &tmp)) { 609 abs_dst = path_append(tmp_dst, filename);
611 err = 1; 610 else
612 goto out;
613 }
614 abs_dst = path_append(tmp_dst, tmp);
615 xfree(tmp);
616 } else
617 abs_dst = xstrdup(tmp_dst); 611 abs_dst = xstrdup(tmp_dst);
618
619 } else if (tmp_dst) { 612 } else if (tmp_dst) {
620 abs_dst = path_append(tmp_dst, tmp); 613 abs_dst = path_append(tmp_dst, filename);
621 xfree(tmp); 614 } else {
622 } else 615 abs_dst = make_absolute(xstrdup(filename), pwd);
623 abs_dst = make_absolute(tmp, pwd); 616 }
617 xfree(tmp);
624 618
625 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); 619 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
626 if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1) 620 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
627 err = -1; 621 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
622 pflag || global_pflag, 1) == -1)
623 err = -1;
624 } else {
625 if (do_upload(conn, g.gl_pathv[i], abs_dst,
626 pflag || global_pflag) == -1)
627 err = -1;
628 }
628 } 629 }
629 630
630out: 631out:
@@ -1065,7 +1066,7 @@ makeargv(const char *arg, int *argcp)
1065} 1066}
1066 1067
1067static int 1068static int
1068parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag, 1069parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, int *hflag,
1069 unsigned long *n_arg, char **path1, char **path2) 1070 unsigned long *n_arg, char **path1, char **path2)
1070{ 1071{
1071 const char *cmd, *cp = *cpp; 1072 const char *cmd, *cp = *cpp;
@@ -1109,13 +1110,13 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
1109 } 1110 }
1110 1111
1111 /* Get arguments and parse flags */ 1112 /* Get arguments and parse flags */
1112 *lflag = *pflag = *hflag = *n_arg = 0; 1113 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
1113 *path1 = *path2 = NULL; 1114 *path1 = *path2 = NULL;
1114 optidx = 1; 1115 optidx = 1;
1115 switch (cmdnum) { 1116 switch (cmdnum) {
1116 case I_GET: 1117 case I_GET:
1117 case I_PUT: 1118 case I_PUT:
1118 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1) 1119 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
1119 return -1; 1120 return -1;
1120 /* Get first pathname (mandatory) */ 1121 /* Get first pathname (mandatory) */
1121 if (argc - optidx < 1) { 1122 if (argc - optidx < 1) {
@@ -1235,7 +1236,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1235 int err_abort) 1236 int err_abort)
1236{ 1237{
1237 char *path1, *path2, *tmp; 1238 char *path1, *path2, *tmp;
1238 int pflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i; 1239 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
1239 unsigned long n_arg = 0; 1240 unsigned long n_arg = 0;
1240 Attrib a, *aa; 1241 Attrib a, *aa;
1241 char path_buf[MAXPATHLEN]; 1242 char path_buf[MAXPATHLEN];
@@ -1243,7 +1244,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1243 glob_t g; 1244 glob_t g;
1244 1245
1245 path1 = path2 = NULL; 1246 path1 = path2 = NULL;
1246 cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg, 1247 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
1247 &path1, &path2); 1248 &path1, &path2);
1248 1249
1249 if (iflag != 0) 1250 if (iflag != 0)
@@ -1261,10 +1262,10 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1261 err = -1; 1262 err = -1;
1262 break; 1263 break;
1263 case I_GET: 1264 case I_GET:
1264 err = process_get(conn, path1, path2, *pwd, pflag); 1265 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
1265 break; 1266 break;
1266 case I_PUT: 1267 case I_PUT:
1267 err = process_put(conn, path1, path2, *pwd, pflag); 1268 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
1268 break; 1269 break;
1269 case I_RENAME: 1270 case I_RENAME:
1270 path1 = make_absolute(path1, *pwd); 1271 path1 = make_absolute(path1, *pwd);
@@ -1290,7 +1291,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1290 attrib_clear(&a); 1291 attrib_clear(&a);
1291 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1292 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1292 a.perm = 0777; 1293 a.perm = 0777;
1293 err = do_mkdir(conn, path1, &a); 1294 err = do_mkdir(conn, path1, &a, 1);
1294 break; 1295 break;
1295 case I_RMDIR: 1296 case I_RMDIR:
1296 path1 = make_absolute(path1, *pwd); 1297 path1 = make_absolute(path1, *pwd);
@@ -1668,7 +1669,7 @@ usage(void)
1668 extern char *__progname; 1669 extern char *__progname;
1669 1670
1670 fprintf(stderr, 1671 fprintf(stderr,
1671 "usage: %s [-1246Cqv] [-B buffer_size] [-b batchfile] [-c cipher]\n" 1672 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
1672 " [-D sftp_server_path] [-F ssh_config] " 1673 " [-D sftp_server_path] [-F ssh_config] "
1673 "[-i identity_file]\n" 1674 "[-i identity_file]\n"
1674 " [-o ssh_option] [-P port] [-R num_requests] " 1675 " [-o ssh_option] [-P port] [-R num_requests] "
@@ -1710,7 +1711,7 @@ main(int argc, char **argv)
1710 infile = stdin; 1711 infile = stdin;
1711 1712
1712 while ((ch = getopt(argc, argv, 1713 while ((ch = getopt(argc, argv,
1713 "1246hqvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) { 1714 "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
1714 switch (ch) { 1715 switch (ch) {
1715 /* Passed through to ssh(1) */ 1716 /* Passed through to ssh(1) */
1716 case '4': 1717 case '4':
@@ -1764,9 +1765,15 @@ main(int argc, char **argv)
1764 batchmode = 1; 1765 batchmode = 1;
1765 addargs(&args, "-obatchmode yes"); 1766 addargs(&args, "-obatchmode yes");
1766 break; 1767 break;
1768 case 'p':
1769 global_pflag = 1;
1770 break;
1767 case 'D': 1771 case 'D':
1768 sftp_direct = optarg; 1772 sftp_direct = optarg;
1769 break; 1773 break;
1774 case 'r':
1775 global_rflag = 1;
1776 break;
1770 case 'R': 1777 case 'R':
1771 num_requests = strtol(optarg, &cp, 10); 1778 num_requests = strtol(optarg, &cp, 10);
1772 if (num_requests == 0 || *cp != '\0') 1779 if (num_requests == 0 || *cp != '\0')