diff options
Diffstat (limited to 'sftp-client.c')
-rw-r--r-- | sftp-client.c | 301 |
1 files changed, 281 insertions, 20 deletions
diff --git a/sftp-client.c b/sftp-client.c index 0990b7912..6124c0f40 100644 --- a/sftp-client.c +++ b/sftp-client.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp-client.c,v 1.87 2009/06/22 05:39:28 dtucker Exp $ */ | 1 | /* $OpenBSD: sftp-client.c,v 1.90 2009/10/11 10:41:26 dtucker 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; |
@@ -74,6 +78,10 @@ struct sftp_conn { | |||
74 | u_int exts; | 78 | u_int exts; |
75 | }; | 79 | }; |
76 | 80 | ||
81 | static char * | ||
82 | get_handle(int fd, u_int expected_id, u_int *len, const char *errfmt, ...) | ||
83 | __attribute__((format(printf, 4, 5))); | ||
84 | |||
77 | static void | 85 | static void |
78 | send_msg(int fd, Buffer *m) | 86 | send_msg(int fd, Buffer *m) |
79 | { | 87 | { |
@@ -179,11 +187,18 @@ get_status(int fd, u_int expected_id) | |||
179 | } | 187 | } |
180 | 188 | ||
181 | static char * | 189 | static char * |
182 | get_handle(int fd, u_int expected_id, u_int *len) | 190 | get_handle(int fd, u_int expected_id, u_int *len, const char *errfmt, ...) |
183 | { | 191 | { |
184 | Buffer msg; | 192 | Buffer msg; |
185 | u_int type, id; | 193 | u_int type, id; |
186 | char *handle; | 194 | char *handle, errmsg[256]; |
195 | va_list args; | ||
196 | int status; | ||
197 | |||
198 | va_start(args, errfmt); | ||
199 | if (errfmt != NULL) | ||
200 | vsnprintf(errmsg, sizeof(errmsg), errfmt, args); | ||
201 | va_end(args); | ||
187 | 202 | ||
188 | buffer_init(&msg); | 203 | buffer_init(&msg); |
189 | get_msg(fd, &msg); | 204 | get_msg(fd, &msg); |
@@ -191,16 +206,17 @@ get_handle(int fd, u_int expected_id, u_int *len) | |||
191 | id = buffer_get_int(&msg); | 206 | id = buffer_get_int(&msg); |
192 | 207 | ||
193 | if (id != expected_id) | 208 | if (id != expected_id) |
194 | fatal("ID mismatch (%u != %u)", id, expected_id); | 209 | fatal("%s: ID mismatch (%u != %u)", |
210 | errfmt == NULL ? __func__ : errmsg, id, expected_id); | ||
195 | if (type == SSH2_FXP_STATUS) { | 211 | if (type == SSH2_FXP_STATUS) { |
196 | int status = buffer_get_int(&msg); | 212 | status = buffer_get_int(&msg); |
197 | 213 | if (errfmt != NULL) | |
198 | error("Couldn't get handle: %s", fx2txt(status)); | 214 | error("%s: %s", errmsg, fx2txt(status)); |
199 | buffer_free(&msg); | 215 | buffer_free(&msg); |
200 | return(NULL); | 216 | return(NULL); |
201 | } else if (type != SSH2_FXP_HANDLE) | 217 | } else if (type != SSH2_FXP_HANDLE) |
202 | fatal("Expected SSH2_FXP_HANDLE(%u) packet, got %u", | 218 | fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u", |
203 | SSH2_FXP_HANDLE, type); | 219 | errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type); |
204 | 220 | ||
205 | handle = buffer_get_string(&msg, len); | 221 | handle = buffer_get_string(&msg, len); |
206 | buffer_free(&msg); | 222 | buffer_free(&msg); |
@@ -418,7 +434,8 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag, | |||
418 | 434 | ||
419 | buffer_clear(&msg); | 435 | buffer_clear(&msg); |
420 | 436 | ||
421 | handle = get_handle(conn->fd_in, id, &handle_len); | 437 | handle = get_handle(conn->fd_in, id, &handle_len, |
438 | "remote readdir(\"%s\")", path); | ||
422 | if (handle == NULL) | 439 | if (handle == NULL) |
423 | return(-1); | 440 | return(-1); |
424 | 441 | ||
@@ -484,6 +501,17 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag, | |||
484 | if (printflag) | 501 | if (printflag) |
485 | printf("%s\n", longname); | 502 | printf("%s\n", longname); |
486 | 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 | |||
487 | if (dir) { | 515 | if (dir) { |
488 | *dir = xrealloc(*dir, ents + 2, sizeof(**dir)); | 516 | *dir = xrealloc(*dir, ents + 2, sizeof(**dir)); |
489 | (*dir)[ents] = xmalloc(sizeof(***dir)); | 517 | (*dir)[ents] = xmalloc(sizeof(***dir)); |
@@ -492,7 +520,7 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag, | |||
492 | memcpy(&(*dir)[ents]->a, a, sizeof(*a)); | 520 | memcpy(&(*dir)[ents]->a, a, sizeof(*a)); |
493 | (*dir)[++ents] = NULL; | 521 | (*dir)[++ents] = NULL; |
494 | } | 522 | } |
495 | 523 | next: | |
496 | xfree(filename); | 524 | xfree(filename); |
497 | xfree(longname); | 525 | xfree(longname); |
498 | } | 526 | } |
@@ -547,7 +575,7 @@ do_rm(struct sftp_conn *conn, char *path) | |||
547 | } | 575 | } |
548 | 576 | ||
549 | int | 577 | int |
550 | do_mkdir(struct sftp_conn *conn, char *path, Attrib *a) | 578 | do_mkdir(struct sftp_conn *conn, char *path, Attrib *a, int printflag) |
551 | { | 579 | { |
552 | u_int status, id; | 580 | u_int status, id; |
553 | 581 | ||
@@ -556,7 +584,7 @@ do_mkdir(struct sftp_conn *conn, char *path, Attrib *a) | |||
556 | strlen(path), a); | 584 | strlen(path), a); |
557 | 585 | ||
558 | status = get_status(conn->fd_in, id); | 586 | status = get_status(conn->fd_in, id); |
559 | if (status != SSH2_FX_OK) | 587 | if (status != SSH2_FX_OK && printflag) |
560 | error("Couldn't create directory: %s", fx2txt(status)); | 588 | error("Couldn't create directory: %s", fx2txt(status)); |
561 | 589 | ||
562 | return(status); | 590 | return(status); |
@@ -895,9 +923,9 @@ send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len, | |||
895 | 923 | ||
896 | int | 924 | int |
897 | 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, |
898 | int pflag) | 926 | Attrib *a, int pflag) |
899 | { | 927 | { |
900 | Attrib junk, *a; | 928 | Attrib junk; |
901 | Buffer msg; | 929 | Buffer msg; |
902 | char *handle; | 930 | char *handle; |
903 | int local_fd, status = 0, write_error; | 931 | int local_fd, status = 0, write_error; |
@@ -916,9 +944,8 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
916 | 944 | ||
917 | TAILQ_INIT(&requests); | 945 | TAILQ_INIT(&requests); |
918 | 946 | ||
919 | a = do_stat(conn, remote_path, 0); | 947 | if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL) |
920 | if (a == NULL) | 948 | return -1; |
921 | return(-1); | ||
922 | 949 | ||
923 | /* 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 */ |
924 | if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) | 951 | if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) |
@@ -951,7 +978,8 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
951 | send_msg(conn->fd_out, &msg); | 978 | send_msg(conn->fd_out, &msg); |
952 | debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); | 979 | debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path); |
953 | 980 | ||
954 | handle = get_handle(conn->fd_in, id, &handle_len); | 981 | handle = get_handle(conn->fd_in, id, &handle_len, |
982 | "remote open(\"%s\")", remote_path); | ||
955 | if (handle == NULL) { | 983 | if (handle == NULL) { |
956 | buffer_free(&msg); | 984 | buffer_free(&msg); |
957 | return(-1); | 985 | return(-1); |
@@ -1132,6 +1160,114 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path, | |||
1132 | return(status); | 1160 | return(status); |
1133 | } | 1161 | } |
1134 | 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 | |||
1135 | int | 1271 | int |
1136 | 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, |
1137 | int pflag) | 1273 | int pflag) |
@@ -1195,7 +1331,8 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, | |||
1195 | 1331 | ||
1196 | buffer_clear(&msg); | 1332 | buffer_clear(&msg); |
1197 | 1333 | ||
1198 | handle = get_handle(conn->fd_in, id, &handle_len); | 1334 | handle = get_handle(conn->fd_in, id, &handle_len, |
1335 | "remote open(\"%s\")", remote_path); | ||
1199 | if (handle == NULL) { | 1336 | if (handle == NULL) { |
1200 | close(local_fd); | 1337 | close(local_fd); |
1201 | buffer_free(&msg); | 1338 | buffer_free(&msg); |
@@ -1313,3 +1450,127 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path, | |||
1313 | 1450 | ||
1314 | return status; | 1451 | return status; |
1315 | } | 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 (lstat(new_src, &sb) == -1) { | ||
1516 | logit("%s: lstat failed: %s", filename, | ||
1517 | strerror(errno)); | ||
1518 | ret = -1; | ||
1519 | } else if (S_ISDIR(sb.st_mode)) { | ||
1520 | if (strcmp(filename, ".") == 0 || | ||
1521 | strcmp(filename, "..") == 0) | ||
1522 | continue; | ||
1523 | |||
1524 | if (upload_dir_internal(conn, new_src, new_dst, | ||
1525 | pflag, depth + 1, printflag) == -1) | ||
1526 | ret = -1; | ||
1527 | } else if (S_ISREG(sb.st_mode)) { | ||
1528 | if (do_upload(conn, new_src, new_dst, pflag) == -1) { | ||
1529 | error("Uploading of file %s to %s failed!", | ||
1530 | new_src, new_dst); | ||
1531 | ret = -1; | ||
1532 | } | ||
1533 | } else | ||
1534 | logit("%s: not a regular file\n", filename); | ||
1535 | xfree(new_dst); | ||
1536 | xfree(new_src); | ||
1537 | } | ||
1538 | |||
1539 | do_setstat(conn, dst, &a); | ||
1540 | |||
1541 | (void) closedir(dirp); | ||
1542 | return ret; | ||
1543 | } | ||
1544 | |||
1545 | int | ||
1546 | upload_dir(struct sftp_conn *conn, char *src, char *dst, int printflag, | ||
1547 | int pflag) | ||
1548 | { | ||
1549 | char *dst_canon; | ||
1550 | int ret; | ||
1551 | |||
1552 | if ((dst_canon = do_realpath(conn, dst)) == NULL) { | ||
1553 | error("Unable to canonicalise path \"%s\"", dst); | ||
1554 | return -1; | ||
1555 | } | ||
1556 | |||
1557 | ret = upload_dir_internal(conn, src, dst_canon, pflag, printflag, 0); | ||
1558 | xfree(dst_canon); | ||
1559 | return ret; | ||
1560 | } | ||
1561 | |||
1562 | char * | ||
1563 | path_append(char *p1, char *p2) | ||
1564 | { | ||
1565 | char *ret; | ||
1566 | size_t len = strlen(p1) + strlen(p2) + 2; | ||
1567 | |||
1568 | ret = xmalloc(len); | ||
1569 | strlcpy(ret, p1, len); | ||
1570 | if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') | ||
1571 | strlcat(ret, "/", len); | ||
1572 | strlcat(ret, p2, len); | ||
1573 | |||
1574 | return(ret); | ||
1575 | } | ||
1576 | |||