diff options
author | Darren Tucker <dtucker@zip.com.au> | 2009-10-07 08:37:48 +1100 |
---|---|---|
committer | Darren Tucker <dtucker@zip.com.au> | 2009-10-07 08:37:48 +1100 |
commit | 1b0dd175377c86abb7f3a3da2e9b1a89f36c7a1a (patch) | |
tree | d6b5f501c4c9890ed91ce4fd86be583213d7e8e6 /sftp-client.c | |
parent | 1477ea162c05c09b4b5ebc19ac588fbf469349dc (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@
Diffstat (limited to 'sftp-client.c')
-rw-r--r-- | sftp-client.c | 260 |
1 files changed, 251 insertions, 9 deletions
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 | |||