diff options
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | sftp-client.c | 260 | ||||
-rw-r--r-- | sftp-client.h | 21 | ||||
-rw-r--r-- | sftp.1 | 44 | ||||
-rw-r--r-- | sftp.c | 219 |
5 files changed, 423 insertions, 126 deletions
@@ -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 | ||
36 | 20091002 | 41 | 20091002 |
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 | |||
64 | struct sftp_conn { | 68 | struct 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 | ||
562 | int | 577 | int |
563 | do_mkdir(struct sftp_conn *conn, char *path, Attrib *a) | 578 | do_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 | ||
909 | int | 924 | int |
910 | do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | 925 | do_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 | ||
1163 | static int | ||
1164 | download_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 | |||
1253 | int | ||
1254 | download_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 | |||
1149 | int | 1271 | int |
1150 | do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, | 1272 | do_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 | |||
1454 | static int | ||
1455 | upload_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 | |||
1541 | int | ||
1542 | upload_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 | |||
1558 | char * | ||
1559 | path_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 **); | |||
68 | int do_rm(struct sftp_conn *, char *); | 68 | int do_rm(struct sftp_conn *, char *); |
69 | 69 | ||
70 | /* Create directory 'path' */ | 70 | /* Create directory 'path' */ |
71 | int do_mkdir(struct sftp_conn *, char *, Attrib *); | 71 | int do_mkdir(struct sftp_conn *, char *, Attrib *, int); |
72 | 72 | ||
73 | /* Remove directory 'path' */ | 73 | /* Remove directory 'path' */ |
74 | int do_rmdir(struct sftp_conn *, char *); | 74 | int 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 | */ |
106 | int do_download(struct sftp_conn *, char *, char *, int); | 106 | int 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 | */ | ||
112 | int 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 | */ |
112 | int do_upload(struct sftp_conn *, char *, char *, int); | 118 | int 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 | */ | ||
124 | int upload_dir(struct sftp_conn *, char *, char *, int, int); | ||
125 | |||
126 | /* Concatenate paths, taking care of slashes. Caller must free result. */ | ||
127 | char *path_append(char *, char *); | ||
128 | |||
114 | #endif | 129 | #endif |
@@ -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 |
225 | Specifies the port to connect to on the remote host. | 225 | Specifies the port to connect to on the remote host. |
226 | .It Fl p | ||
227 | Preserves modification times, access times, and modes from the | ||
228 | original files transferred. | ||
226 | .It Fl q | 229 | .It Fl q |
227 | Quiet mode: disables the progress meter as well as warning and | 230 | Quiet mode: disables the progress meter as well as warning and |
228 | diagnostic messages from | 231 | diagnostic messages from |
@@ -232,6 +235,11 @@ Specify how many requests may be outstanding at any one time. | |||
232 | Increasing this may slightly improve file transfer speed | 235 | Increasing this may slightly improve file transfer speed |
233 | but will increase memory usage. | 236 | but will increase memory usage. |
234 | The default is 64 outstanding requests. | 237 | The default is 64 outstanding requests. |
238 | .It Fl r | ||
239 | Recursively copy entire directories when uploading and downloading. | ||
240 | Note that | ||
241 | .Nm | ||
242 | does not follow symbolic links encountered in the tree traversal. | ||
235 | .It Fl S Ar program | 243 | .It Fl S Ar program |
236 | Name of the | 244 | Name of the |
237 | .Ar program | 245 | .Ar program |
@@ -322,7 +330,7 @@ extension. | |||
322 | Quit | 330 | Quit |
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 | |||
341 | is specified, then | 349 | is specified, then |
342 | .Ar local-path | 350 | .Ar local-path |
343 | must specify a directory. | 351 | must specify a directory. |
344 | If the | 352 | .Pp |
345 | .Fl P | 353 | If ether the |
354 | .Fl Ppr | ||
355 | or | ||
356 | .Fl p | ||
346 | flag is specified, then full file permissions and access times are | 357 | flag is specified, then full file permissions and access times are |
347 | copied too. | 358 | copied too. |
359 | .Pp | ||
360 | If the | ||
361 | .Fl r | ||
362 | flag is specified then directories will be copied recursively. | ||
363 | Note that | ||
364 | .Nm | ||
365 | does not follow symbolic links when performing recursive transfers. | ||
348 | .It Ic help | 366 | .It Ic help |
349 | Display help text. | 367 | Display help text. |
350 | .It Ic lcd Ar path | 368 | .It Ic lcd Ar path |
@@ -440,10 +458,20 @@ If it does and | |||
440 | is specified, then | 458 | is specified, then |
441 | .Ar remote-path | 459 | .Ar remote-path |
442 | must specify a directory. | 460 | must specify a directory. |
443 | If the | 461 | .Pp |
462 | If ether the | ||
444 | .Fl P | 463 | .Fl P |
445 | flag is specified, then the file's full permission and access time are | 464 | or |
465 | .Fl p | ||
466 | flag is specified, then full file permissions and access times are | ||
446 | copied too. | 467 | copied too. |
468 | .Pp | ||
469 | If the | ||
470 | .Fl r | ||
471 | flag is specified then directories will be copied recursively. | ||
472 | Note that | ||
473 | .Nm | ||
474 | does not follow symbolic links when performing recursive transfers. | ||
447 | .It Ic pwd | 475 | .It Ic pwd |
448 | Display remote working directory. | 476 | Display remote working directory. |
449 | .It Ic quit | 477 | .It Ic quit |
@@ -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. */ |
84 | int showprogress = 1; | 87 | int showprogress = 1; |
85 | 88 | ||
89 | /* When this option is set, we always recursively download/upload directories */ | ||
90 | int global_rflag = 0; | ||
91 | |||
92 | /* When this option is set, the file transfers will always preserve times */ | ||
93 | int global_pflag = 0; | ||
94 | |||
86 | /* SIGINT received during command processing */ | 95 | /* SIGINT received during command processing */ |
87 | volatile sig_atomic_t interrupted = 0; | 96 | volatile 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 | ||
316 | static char * | 325 | static char * |
317 | path_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 | |||
331 | static char * | ||
332 | make_absolute(char *p, char *pwd) | 326 | make_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 | ||
345 | static int | 339 | static int |
346 | infer_path(const char *p, char **ifp) | 340 | parse_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 | |||
365 | static int | ||
366 | parse_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 */ | ||
472 | static int | ||
473 | pathname_is_dir(char *pathname) | ||
474 | { | ||
475 | size_t l = strlen(pathname); | ||
476 | |||
477 | return l > 0 && pathname[l - 1] == '/'; | ||
478 | } | ||
479 | |||
492 | static int | 480 | static int |
493 | process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) | 481 | process_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 | ||
558 | static int | 554 | static int |
559 | process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) | 555 | process_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 | ||
630 | out: | 631 | out: |
@@ -1065,7 +1066,7 @@ makeargv(const char *arg, int *argcp) | |||
1065 | } | 1066 | } |
1066 | 1067 | ||
1067 | static int | 1068 | static int |
1068 | parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag, | 1069 | parse_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') |